diff --git a/src/mesa/drivers/dri/i915/Makefile b/src/mesa/drivers/dri/i915/Makefile index c269fb1290d..846acd32f78 100644 --- a/src/mesa/drivers/dri/i915/Makefile +++ b/src/mesa/drivers/dri/i915/Makefile @@ -5,6 +5,7 @@ include $(TOP)/configs/current LIBNAME = i915_dri.so DRIVER_SOURCES = \ + bufmgr_fake.c \ i915_context.c \ i915_debug.c \ i915_fragprog.c \ diff --git a/src/mesa/drivers/dri/i915/bufmgr.h b/src/mesa/drivers/dri/i915/bufmgr.h new file mode 100644 index 00000000000..c36561ba555 --- /dev/null +++ b/src/mesa/drivers/dri/i915/bufmgr.h @@ -0,0 +1,117 @@ +#ifndef BUFMGR_H +#define BUFMGR_H + +#include "intel_context.h" + +/* Note that this is destined to be external to Mesa, so don't use GL + * types like GLuint, etc. + */ + +/* The buffer manager context. Opaque. + */ +struct bufmgr; + +struct bufmgr *bm_fake_intel_Attach( struct intel_context *intel ); + + +/* struct bufmgr *bmCreate( ... ); */ +/* struct bufmgr *bmAttach( ... ); */ + +void bmInitPool( struct bufmgr *, + unsigned pool, + unsigned long low_offset, + unsigned long high_offset, + void *virtual_base ); + + +#define BM_READ 0x1 +#define BM_DRAW 0x2 +#define BM_MEM_SYS 0x4 +#define BM_MEM_GTT 0x8 +#define BM_MEM_VRAM 0x10 +#define BM_MEM_CACHED 0x20 + +/* Maximum number of buffers to pass to bmValidateBufferList: + */ +#define BM_VALIDATE_MAX 32 + +/* As in ARB_vbo, use integers to talk about buffers. Hopefully this + * facilitates sharing them between processes. + */ +struct buffer_usage { + unsigned buffer; + unsigned flags:16; + unsigned alignment:16; + unsigned offset; + unsigned pool; +}; + + +/* Stick closely to ARB_vbo semantics - they're well defined and + * understood, and drivers can just pass the calls through without too + * much thunking. + */ +void bmGenBuffers(struct bufmgr *, unsigned n, unsigned *buffers); +void bmDeleteBuffers(struct bufmgr *, unsigned n, unsigned *buffers); + +/* The driver has more intimate knowledge of the hardare than a GL + * client would, so flags here is more proscriptive than the usage + * values in the ARB_vbo interface: + */ +void bmBufferData(struct bufmgr *, + unsigned buffer, + unsigned size, + const void *data, + unsigned flags ); + +void bmBufferSubData(struct bufmgr *, + unsigned buffer, + unsigned offset, + unsigned size, + const void *data ); + +void *bmMapBuffer( struct bufmgr *, + unsigned buffer, + unsigned access ); + +void bmUnmapBuffer( struct bufmgr * ); + +/* To be called prior to emitting commands to hardware which reference + * these buffers. The buffer_usage list provides information on where + * the buffers should be placed and whether their contents need to be + * preserved on copying. The offset and pool data elements are return + * values from this function telling the driver exactly where the + * buffers are currently located. + */ +unsigned bmValidateBufferList( struct bufmgr *, + struct buffer_usage *, + unsigned nr ); + + +/* After commands are emitted but before unlocking, this must be + * called so that the buffer manager can correctly age the buffers. + * The buffer manager keeps track of the list of validated buffers, so + * already knows what to apply the fence to. + * + * The buffer manager knows how to emit and test fences directly + * through the drm and without callbacks or whatever into the driver. + */ +void bmFenceValidatedBuffers( struct bufmgr * ); + + +/* This functionality is used by the buffer manager, not really sure + * if we need to be exposing it in this way, probably libdrm will + * offer equivalent calls. + * + * For now they can stay, but will likely change/move before final: + */ +unsigned bmSetFence( struct bufmgr * ); +int bmTestFence( struct bufmgr *, unsigned fence ); +void bmFinishFence( struct bufmgr *, unsigned fence ); + +void bmFlushReadCaches( struct bufmgr *bm ); +void bmFlushDrawCache( struct bufmgr *bm ); + +void bm_fake_NotifyContendedLockTake( struct bufmgr * ); + +#endif diff --git a/src/mesa/drivers/dri/i915/bufmgr_fake.c b/src/mesa/drivers/dri/i915/bufmgr_fake.c new file mode 100644 index 00000000000..5f25a6a2e5c --- /dev/null +++ b/src/mesa/drivers/dri/i915/bufmgr_fake.c @@ -0,0 +1,466 @@ +/* Fake version of the buffer manager so that we can prototype the + * changes in a driver fairly quickly. Basically wraps the old style + * memory management in the new programming interface. + * + * Version 1: Just the soggy old dri memory manager. + * + * Future: Import the via memory manager semantics. + */ +#include "bufmgr.h" + +#include "intel_context.h" +#include "intel_ioctl.h" + +#include "hash.h" +#include "simple_list.h" +#include "mm.h" + +struct _mesa_HashTable; + + +/* Wrapper around mm.c's mem_block, which understands that you must + * wait for fences to expire before memory can be freed. This is + * specific to our use of memcpy for uploads - an upload that was + * processed through the command queue wouldn't need to care about + * fences. + */ +struct fenced_block { + struct fenced_block *next, *prev; + struct mem_block *mem; + unsigned fence; + struct buffer *buf; +}; + +struct buffer { + unsigned id; /* debug only */ + unsigned offset; + unsigned size; + unsigned alignment; + + void *local; /* backing store */ + struct fenced_block *block; /* uploaded agp */ +}; + +struct pool { + unsigned size; + struct mem_block *heap; + void *virtual; + struct fenced_block lru; + struct fenced_block freed; +}; + +struct bufmgr { + struct intel_context *intel; + struct buffer buffer_list; + struct pool pool; + + struct _mesa_HashTable *hash; + unsigned buf_nr; /* for generating ids */ + + /* Temporary storage while validating and unvalidating: + */ + struct buffer *validated[BM_VALIDATE_MAX]; + unsigned nr_validated; + + /* Context clobbering + */ + unsigned global_wait_fence; + unsigned last_fence; +}; + + +static int alloc_agp( struct bufmgr *bm, + struct buffer *buf ) +{ + struct fenced_block *block = (struct fenced_block *)calloc(sizeof *block, 1); + if (!block) + return 0; + + block->mem = mmAllocMem(bm->pool.heap, buf->size, buf->alignment, 0); + if (!block->mem) { + free(block); + return 0; + } + + make_empty_list(block); + block->buf = buf; + buf->block = block; + + return 1; +} + +/* Release the card storage associated with buf: + */ +static void free_agp( struct bufmgr *bm, + struct buffer *buf ) +{ + if (!buf->block) + return; + else if (bmTestFence(bm, buf->block->fence)) { + mmFreeMem(buf->block->mem); + buf->block = NULL; + } + else { + /* Add an entry to the free list for this range and the fence at + * which it becomes free. + */ + move_to_tail(&bm->pool.freed, buf->block); + buf->block->buf = NULL; + buf->block = NULL; + } +} + +static int delayed_free( struct bufmgr *bm ) +{ + struct fenced_block *s, *tmp; + int ret = 0; + + foreach_s(s, tmp, &bm->pool.freed ) { + if (bmTestFence(bm, s->fence)) { + remove_from_list(s); + mmFreeMem(s->mem); + free(s); + ret = 1; + } + } + + return ret; +} + + +static unsigned evict_lru( struct bufmgr *bm ) +{ + if (delayed_free(bm)) + return 1; + else { + struct fenced_block *block = bm->pool.lru.next; + + if (block == &bm->pool.lru) + return 0; + + bmFinishFence(bm, block->fence); + free_agp(bm, block->buf); + return 1; + } +} + + +/* This differs slightly from the old dri memory manager as it tries + * to make sure all allocations are satisfied at once. + */ +static unsigned try_validate( struct bufmgr *bm ) +{ + struct buffer *allocs[BM_VALIDATE_MAX]; + unsigned nr_allocs = 0; + unsigned i; + + for (i = 0; i < bm->nr_validated; i++) { + struct buffer *buf = bm->validated[i]; + if (buf->block) + continue; + if (!alloc_agp(bm, buf)) + goto fail; + allocs[nr_allocs++] = buf; + } + + /* If that succeeded, upload data to allocated regions: + */ + for (i = 0; i < nr_allocs; i++) { + struct buffer *buf = allocs[i]; + void *dst = bm->pool.virtual + buf->block->mem->ofs; + memcpy(dst, buf->local, buf->size); + } + + /* Tell hardware that its texture and other caches may be invalid: + */ + bmFlushReadCaches(bm); + return 1; + + /* Undo allocations if failed: + */ + fail: + for (i = 0; i < nr_allocs; i++) + free_agp(bm, allocs[i]); + + return 0; +} + + + + +/*********************************************************************** + * Public functions + */ + + +/* The initialization functions are skewed in the fake implementation. + * This call would be to attach to an existing manager, rather than to + * create a local one. + */ +struct bufmgr *bm_fake_intel_Attach( struct intel_context *intel ) +{ + struct bufmgr *bm = (struct bufmgr *)calloc(sizeof(*bm), 1); + + bm->intel = intel; + bm->hash = _mesa_NewHashTable(); + + return bm; +} + +/* The virtual pointer would go away in a true implementation. + */ +void bmInitPool( struct bufmgr *bm, + unsigned pool, + unsigned long low_offset, + unsigned long high_offset, + void *virtual_base ) +{ + if (pool > 0 || low_offset >= high_offset) + return; + + bm->pool.size = high_offset - low_offset; + bm->pool.heap = mmInit( low_offset, bm->pool.size ); + bm->pool.virtual = virtual_base; + + make_empty_list(&bm->pool.lru); + make_empty_list(&bm->pool.freed); +} + + + +void bmGenBuffers(struct bufmgr *bm, unsigned n, unsigned *buffers) +{ + unsigned i; + + for (i = 0; i < n; i++) { + buffers[i] = bm->buf_nr++; + _mesa_HashInsert(bm->hash, buffers[i], calloc(sizeof(struct buffer), 1)); + } +} + + +void bmDeleteBuffers(struct bufmgr *bm, unsigned n, unsigned *buffers) +{ + unsigned i; + + for (i = 0; i < n; i++) { + struct buffer *buf = _mesa_HashLookup(bm->hash, buffers[i]); + if (buf) { + free(buf); + _mesa_HashRemove(bm->hash, buffers[i]); + } + } +} + +void bmBufferData(struct bufmgr *bm, + unsigned buffer, + unsigned size, + const void *data, + unsigned flags ) +{ + struct buffer *buf = (struct buffer *)_mesa_HashLookup( bm->hash, buffer ); + + free_agp(bm, buf); + free(buf->local); + buf->size = size; + + if (data != NULL) { + buf->local = malloc(size); + memcpy(buf->local, data, size); + } +} + +void bmBufferSubData(struct bufmgr *bm, + unsigned buffer, + unsigned offset, + unsigned size, + const void *data ) +{ + struct buffer *buf = (struct buffer *)_mesa_HashLookup( bm->hash, buffer ); + + if (!buf->local) + buf->local = malloc(buf->size); + + memcpy(buf->local + offset, data, size); + + if (buf->block) { + if (1 || !bmTestFence(bm, buf->block->fence)) + free_agp(bm, buf); + else { + /* LOCK HARDWARE! */ + memcpy(buf->local + offset, data, size); + } + } +} + + +void *bmMapBuffer( struct bufmgr *bm, + unsigned buffer, + unsigned access ) +{ + struct buffer *buf = (struct buffer *)_mesa_HashLookup( bm->hash, buffer ); + + if (!buf->local) + buf->local = malloc(buf->size); + + if (buf->block) + free_agp(bm, buf); + + return buf->local; +} + +void bmUnmapBuffer( struct bufmgr *bm ) +{ + /* Nothing to do */ +} + + + + + +/* To be called prior to emitting commands to hardware which reference + * these buffers. The buffer_usage list provides information on where + * the buffers should be placed and whether their contents need to be + * preserved on copying. The offset and pool data elements are return + * values from this function telling the driver exactly where the + * buffers are currently located. + */ +unsigned bmValidateBufferList( struct bufmgr *bm, + struct buffer_usage *usage, + unsigned nr ) +{ + unsigned i; + unsigned total = 0; + unsigned align_total = 0; + + if (nr > BM_VALIDATE_MAX) + return 0; + + /* On context switch, all our buffers invalidated: (Fix by + * importing global lru if you can be bothered) + */ + if (bm->global_wait_fence) { + bmFinishFence(bm, bm->global_wait_fence); + while (evict_lru(bm)) + ; + bm->global_wait_fence = 0; + } + + /* Build validate list, calculate total size. Total calculated may + * underestimate real total because of alignment, packing issues. + * Only looking at a single pool here. + */ + for (i = 0; i < nr; i++) { + bm->validated[i] = (struct buffer *)_mesa_HashLookup( bm->hash, usage[i].buffer ); + + /* This is a bit odd - but where should alignment be specified? + */ + if (bm->validated[i]->alignment != usage[i].alignment && + bm->validated[i]->alignment != 0) + return 0; + + bm->validated[i]->alignment = usage[i].alignment; + + total += bm->validated[i]->size; + align_total += 1 << bm->validated[i]->alignment; + } + + /* Don't need to try allocation in this case: + */ + if (total > bm->pool.size) + return 0; + + bm->nr_validated = nr; + + /* The old story: evict one texture after another until allocation + * succeeds. This is pretty shite but really hard to do better + * without more infrastucture... Which is coming - hooray! + */ + while (!try_validate(bm)) { + if (!evict_lru(bm)) + return 0; + } + + return 1; +} + + +/* After commands are emitted but before unlocking, this must be + * called so that the buffer manager can correctly age the buffers. + * The buffer manager keeps track of the list of validated buffers, so + * already knows what to apply the fence to. + * + * The buffer manager knows how to emit and test fences directly + * through the drm and without callbacks or whatever into the driver. + */ +void bmFenceValidatedBuffers( struct bufmgr *bm ) +{ + unsigned i; + + bm->last_fence = bmSetFence( bm ); + + /* Move all buffers to head of resident list and set their fences + */ + for (i = 0; i < bm->nr_validated; i++) { + move_to_head(&bm->pool.lru, bm->validated[i]->block); + bm->validated[i]->block->fence = bm->last_fence; + } + +} + + +/* This functionality is used by the buffer manager, not really sure + * if we need to be exposing it in this way, probably libdrm will + * offer equivalent calls. + * + * For now they can stay, but will likely change/move before final: + */ +unsigned bmSetFence( struct bufmgr *bm ) +{ + assert(bm->intel->batch.space == bm->intel->batch.size); + assert(bm->intel->locked); + + return intelEmitIrqLocked( bm->intel ); +} + +int bmTestFence( struct bufmgr *bm, unsigned fence ) +{ + return fence <= bm->intel->sarea->last_dispatch; +} + +void bmFinishFence( struct bufmgr *bm, unsigned fence ) +{ + if (!bmTestFence(bm, fence)) + intelWaitIrq( bm->intel, fence ); +} + + +/* There is a need to tell the hardware to flush various caches + * before we can start reading and writing video memory. + * + * TODO: Need a flag value to tell hardware which caches have changed? + * Who would we rely on to populate the flag? + */ +void bmFlushReadCaches( struct bufmgr *bm ) +{ +} + +void bmFlushDrawCache( struct bufmgr *bm ) +{ +} + +/* A full version of this fake memory manager would take the Global + * LRU into account at this point. I can't be bothered and will just + * evict all local textures if it looks like another context has been + * texturing. The Global LRU is going away & doesn't have any + * relevence to the ultimate aims of this code. + */ +void bm_fake_NotifyContendedLockTake( struct bufmgr *bm ) +{ + /* Will wait for this value, then evict all local textures. + */ + if (bm->intel->sarea->last_enqueue != bm->last_fence) + bm->global_wait_fence = bm->intel->sarea->last_enqueue; +} + + diff --git a/src/mesa/drivers/dri/i915/intel_ioctl.c b/src/mesa/drivers/dri/i915/intel_ioctl.c index dd1e1d75870..e3ad40b873e 100644 --- a/src/mesa/drivers/dri/i915/intel_ioctl.c +++ b/src/mesa/drivers/dri/i915/intel_ioctl.c @@ -42,7 +42,7 @@ -static int intelEmitIrqLocked( intelContextPtr intel ) +int intelEmitIrqLocked( intelContextPtr intel ) { drmI830IrqEmit ie; int ret, seq = 0; @@ -65,7 +65,7 @@ static int intelEmitIrqLocked( intelContextPtr intel ) return seq; } -static void intelWaitIrq( intelContextPtr intel, int seq ) +void intelWaitIrq( intelContextPtr intel, int seq ) { drmI830IrqWait iw; int ret; diff --git a/src/mesa/drivers/dri/i915/intel_ioctl.h b/src/mesa/drivers/dri/i915/intel_ioctl.h index 7e629516760..89fc4a0e867 100644 --- a/src/mesa/drivers/dri/i915/intel_ioctl.h +++ b/src/mesa/drivers/dri/i915/intel_ioctl.h @@ -62,5 +62,8 @@ extern GLboolean intelIsAgpMemory( intelContextPtr intel, const GLvoid *pointer, extern GLuint intelAgpOffsetFromVirtual( intelContextPtr intel, const GLvoid *p ); +void intelWaitIrq( intelContextPtr intel, int seq ); +int intelEmitIrqLocked( intelContextPtr intel ); + #endif