| /* |
| * Copyright 2000-2001 VA Linux Systems, Inc. |
| * (C) Copyright IBM Corporation 2002, 2003 |
| * All Rights Reserved. |
| * |
| * Permission is hereby granted, free of charge, to any person obtaining a |
| * copy of this software and associated documentation files (the "Software"), |
| * to deal in the Software without restriction, including without limitation |
| * on the rights to use, copy, modify, merge, publish, distribute, sub |
| * license, and/or sell copies of the Software, and to permit persons to whom |
| * the Software is furnished to do so, subject to the following conditions: |
| * |
| * The above copyright notice and this permission notice (including the next |
| * paragraph) shall be included in all copies or substantial portions of the |
| * Software. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL |
| * VA LINUX SYSTEM, IBM AND/OR THEIR SUPPLIERS BE LIABLE FOR ANY CLAIM, |
| * DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR |
| * OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE |
| * USE OR OTHER DEALINGS IN THE SOFTWARE. |
| * |
| * Authors: |
| * Ian Romanick <idr@us.ibm.com> |
| * Keith Whitwell <keithw@tungstengraphics.com> |
| * Kevin E. Martin <kem@users.sourceforge.net> |
| * Gareth Hughes <gareth@nvidia.com> |
| */ |
| /* $XFree86:$ */ |
| |
| /** \file texmem.c |
| * Implements all of the device-independent texture memory management. |
| * |
| * Currently, only a simple LRU texture memory management policy is |
| * implemented. In the (hopefully very near) future, better policies will be |
| * implemented. The idea is that the DRI should be able to run in one of two |
| * modes. In the default mode the DRI will dynamically attempt to discover |
| * the best texture management policy for the running application. In the |
| * other mode, the user (via some sort of as yet TBD mechanism) will select |
| * a texture management policy that is known to work well with the |
| * application. |
| */ |
| |
| #include "texmem.h" |
| #include "simple_list.h" |
| #include "imports.h" |
| #include "macros.h" |
| #include "texformat.h" |
| |
| #include <assert.h> |
| |
| |
| |
| static unsigned dummy_swap_counter; |
| |
| |
| /** |
| * Calculate \f$\log_2\f$ of a value. This is a particularly poor |
| * implementation of this function. However, since system performance is in |
| * no way dependent on this function, the slowness of the implementation is |
| * irrelevent. |
| * |
| * \param n Value whose \f$\log_2\f$ is to be calculated |
| */ |
| |
| static GLuint |
| driLog2( GLuint n ) |
| { |
| GLuint log2; |
| |
| for ( log2 = 1 ; n > 1 ; log2++ ) { |
| n >>= 1; |
| } |
| |
| return log2; |
| } |
| |
| |
| |
| |
| /** |
| * Determine if a texture is resident in textureable memory. Depending on |
| * the driver, this may or may not be on-card memory. It could be AGP memory |
| * or anyother type of memory from which the hardware can directly read |
| * texels. |
| * |
| * This function is intended to be used as the \c IsTextureResident function |
| * in the device's \c dd_function_table. |
| * |
| * \param ctx GL context pointer (currently unused) |
| * \param texObj Texture object to be tested |
| */ |
| |
| GLboolean |
| driIsTextureResident( GLcontext * ctx, |
| struct gl_texture_object * texObj ) |
| { |
| driTextureObject * t; |
| |
| |
| t = (driTextureObject *) texObj->DriverData; |
| return( (t != NULL) && (t->memBlock != NULL) ); |
| } |
| |
| |
| |
| |
| /** |
| * (Re)initialize the global circular LRU list. The last element |
| * in the array (\a heap->nrRegions) is the sentinal. Keeping it |
| * at the end of the array allows the other elements of the array |
| * to be addressed rationally when looking up objects at a particular |
| * location in texture memory. |
| * |
| * \param heap Texture heap to be reset |
| */ |
| |
| static void resetGlobalLRU( driTexHeap * heap ) |
| { |
| drmTextureRegionPtr list = heap->global_regions; |
| unsigned sz = 1U << heap->logGranularity; |
| unsigned i; |
| |
| for (i = 0 ; (i+1) * sz <= heap->size ; i++) { |
| list[i].prev = i-1; |
| list[i].next = i+1; |
| list[i].age = 0; |
| } |
| |
| i--; |
| list[0].prev = heap->nrRegions; |
| list[i].prev = i-1; |
| list[i].next = heap->nrRegions; |
| list[heap->nrRegions].prev = i; |
| list[heap->nrRegions].next = 0; |
| heap->global_age[0] = 0; |
| } |
| |
| /** |
| * Print out debugging information about the local texture LRU. |
| * |
| * \param heap Texture heap to be printed |
| * \param callername Name of calling function |
| */ |
| static void printLocalLRU( driTexHeap * heap, const char *callername ) |
| { |
| driTextureObject *t; |
| unsigned sz = 1U << heap->logGranularity; |
| |
| fprintf( stderr, "%s in %s:\nLocal LRU, heap %d:\n", |
| __FUNCTION__, callername, heap->heapId ); |
| |
| foreach ( t, &heap->texture_objects ) { |
| if (!t->memBlock) |
| continue; |
| if (!t->tObj) { |
| fprintf( stderr, "Placeholder (%p) %d at 0x%x sz 0x%x\n", |
| (void *)t, |
| t->memBlock->ofs / sz, |
| t->memBlock->ofs, |
| t->memBlock->size ); |
| } else { |
| fprintf( stderr, "Texture (%p) at 0x%x sz 0x%x\n", |
| (void *)t, |
| t->memBlock->ofs, |
| t->memBlock->size ); |
| } |
| } |
| foreach ( t, heap->swapped_objects ) { |
| if (!t->tObj) { |
| fprintf( stderr, "Swapped Placeholder (%p)\n", (void *)t ); |
| } else { |
| fprintf( stderr, "Swapped Texture (%p)\n", (void *)t ); |
| } |
| } |
| |
| fprintf( stderr, "\n" ); |
| } |
| |
| /** |
| * Print out debugging information about the global texture LRU. |
| * |
| * \param heap Texture heap to be printed |
| * \param callername Name of calling function |
| */ |
| static void printGlobalLRU( driTexHeap * heap, const char *callername ) |
| { |
| drmTextureRegionPtr list = heap->global_regions; |
| unsigned int i, j; |
| |
| fprintf( stderr, "%s in %s:\nGlobal LRU, heap %d list %p:\n", |
| __FUNCTION__, callername, heap->heapId, (void *)list ); |
| |
| for ( i = 0, j = heap->nrRegions ; i < heap->nrRegions ; i++ ) { |
| fprintf( stderr, "list[%d] age %d next %d prev %d in_use %d\n", |
| j, list[j].age, list[j].next, list[j].prev, list[j].in_use ); |
| j = list[j].next; |
| if ( j == heap->nrRegions ) break; |
| } |
| |
| if ( j != heap->nrRegions ) { |
| fprintf( stderr, "Loop detected in global LRU\n" ); |
| for ( i = 0 ; i < heap->nrRegions ; i++ ) { |
| fprintf( stderr, "list[%d] age %d next %d prev %d in_use %d\n", |
| i, list[i].age, list[i].next, list[i].prev, list[i].in_use ); |
| } |
| } |
| |
| fprintf( stderr, "\n" ); |
| } |
| |
| |
| /** |
| * Called by the client whenever it touches a local texture. |
| * |
| * \param t Texture object that the client has accessed |
| */ |
| |
| void driUpdateTextureLRU( driTextureObject * t ) |
| { |
| driTexHeap * heap; |
| drmTextureRegionPtr list; |
| unsigned shift; |
| unsigned start; |
| unsigned end; |
| unsigned i; |
| |
| |
| heap = t->heap; |
| if ( heap != NULL ) { |
| shift = heap->logGranularity; |
| start = t->memBlock->ofs >> shift; |
| end = (t->memBlock->ofs + t->memBlock->size - 1) >> shift; |
| |
| |
| heap->local_age = ++heap->global_age[0]; |
| list = heap->global_regions; |
| |
| |
| /* Update the context's local LRU |
| */ |
| |
| move_to_head( & heap->texture_objects, t ); |
| |
| |
| for (i = start ; i <= end ; i++) { |
| list[i].age = heap->local_age; |
| |
| /* remove_from_list(i) |
| */ |
| list[(unsigned)list[i].next].prev = list[i].prev; |
| list[(unsigned)list[i].prev].next = list[i].next; |
| |
| /* insert_at_head(list, i) |
| */ |
| list[i].prev = heap->nrRegions; |
| list[i].next = list[heap->nrRegions].next; |
| list[(unsigned)list[heap->nrRegions].next].prev = i; |
| list[heap->nrRegions].next = i; |
| } |
| |
| if ( 0 ) { |
| printGlobalLRU( heap, __FUNCTION__ ); |
| printLocalLRU( heap, __FUNCTION__ ); |
| } |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Keep track of swapped out texture objects. |
| * |
| * \param t Texture object to be "swapped" out of its texture heap |
| */ |
| |
| void driSwapOutTextureObject( driTextureObject * t ) |
| { |
| unsigned face; |
| |
| |
| if ( t->memBlock != NULL ) { |
| assert( t->heap != NULL ); |
| mmFreeMem( t->memBlock ); |
| t->memBlock = NULL; |
| |
| if (t->timestamp > t->heap->timestamp) |
| t->heap->timestamp = t->timestamp; |
| |
| t->heap->texture_swaps[0]++; |
| move_to_tail( t->heap->swapped_objects, t ); |
| t->heap = NULL; |
| } |
| else { |
| assert( t->heap == NULL ); |
| } |
| |
| |
| for ( face = 0 ; face < 6 ; face++ ) { |
| t->dirty_images[face] = ~0; |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Destroy hardware state associated with texture \a t. Calls the |
| * \a destroy_texture_object method associated with the heap from which |
| * \a t was allocated. |
| * |
| * \param t Texture object to be destroyed |
| */ |
| |
| void driDestroyTextureObject( driTextureObject * t ) |
| { |
| driTexHeap * heap; |
| |
| |
| if ( 0 ) { |
| fprintf( stderr, "[%s:%d] freeing %p (tObj = %p, DriverData = %p)\n", |
| __FILE__, __LINE__, |
| (void *)t, |
| (void *)((t != NULL) ? t->tObj : NULL), |
| (void *)((t != NULL && t->tObj != NULL) ? t->tObj->DriverData : NULL )); |
| } |
| |
| if ( t != NULL ) { |
| if ( t->memBlock ) { |
| heap = t->heap; |
| assert( heap != NULL ); |
| |
| heap->texture_swaps[0]++; |
| |
| mmFreeMem( t->memBlock ); |
| t->memBlock = NULL; |
| |
| if (t->timestamp > t->heap->timestamp) |
| t->heap->timestamp = t->timestamp; |
| |
| heap->destroy_texture_object( heap->driverContext, t ); |
| t->heap = NULL; |
| } |
| |
| if ( t->tObj != NULL ) { |
| assert( t->tObj->DriverData == t ); |
| t->tObj->DriverData = NULL; |
| } |
| |
| remove_from_list( t ); |
| FREE( t ); |
| } |
| |
| if ( 0 ) { |
| fprintf( stderr, "[%s:%d] done freeing %p\n", __FILE__, __LINE__, (void *)t ); |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Update the local heap's representation of texture memory based on |
| * data in the SAREA. This is done each time it is detected that some other |
| * direct rendering client has held the lock. This pertains to both our local |
| * textures and the textures belonging to other clients. Keep track of other |
| * client's textures by pushing a placeholder texture onto the LRU list -- |
| * these are denoted by \a tObj being \a NULL. |
| * |
| * \param heap Heap whose state is to be updated |
| * \param offset Byte offset in the heap that has been stolen |
| * \param size Size, in bytes, of the stolen block |
| * \param in_use Non-zero if the block is pinned/reserved by the kernel |
| */ |
| |
| static void driTexturesGone( driTexHeap * heap, int offset, int size, |
| int in_use ) |
| { |
| driTextureObject * t; |
| driTextureObject * tmp; |
| |
| |
| foreach_s ( t, tmp, & heap->texture_objects ) { |
| if ( (t->memBlock->ofs < (offset + size)) |
| && ((t->memBlock->ofs + t->memBlock->size) > offset) ) { |
| /* It overlaps - kick it out. If the texture object is just a |
| * place holder, then destroy it all together. Otherwise, mark |
| * it as being swapped out. |
| */ |
| |
| if ( t->tObj != NULL ) { |
| driSwapOutTextureObject( t ); |
| } |
| else { |
| driDestroyTextureObject( t ); |
| } |
| } |
| } |
| |
| |
| { |
| t = (driTextureObject *) CALLOC( heap->texture_object_size ); |
| if ( t == NULL ) return; |
| |
| t->memBlock = mmAllocMem( heap->memory_heap, size, 0, offset ); |
| if ( t->memBlock == NULL ) { |
| fprintf( stderr, "Couldn't alloc placeholder: heap %u sz %x ofs %x\n", heap->heapId, |
| (int)size, (int)offset ); |
| mmDumpMemInfo( heap->memory_heap ); |
| FREE(t); |
| return; |
| } |
| t->heap = heap; |
| if (in_use) |
| t->reserved = 1; |
| insert_at_head( & heap->texture_objects, t ); |
| } |
| } |
| |
| |
| |
| |
| /** |
| * Called by the client on lock contention to determine whether textures have |
| * been stolen. If another client has modified a region in which we have |
| * textures, then we need to figure out which of our textures have been |
| * removed and update our global LRU. |
| * |
| * \param heap Texture heap to be updated |
| */ |
| |
| void driAgeTextures( driTexHeap * heap ) |
| { |
| drmTextureRegionPtr list = heap->global_regions; |
| unsigned sz = 1U << (heap->logGranularity); |
| unsigned i, nr = 0; |
| |
| |
| /* Have to go right round from the back to ensure stuff ends up |
| * LRU in the local list... Fix with a cursor pointer. |
| */ |
| |
| for (i = list[heap->nrRegions].prev ; |
| i != heap->nrRegions && nr < heap->nrRegions ; |
| i = list[i].prev, nr++) { |
| /* If switching texturing schemes, then the SAREA might not have been |
| * properly cleared, so we need to reset the global texture LRU. |
| */ |
| |
| if ( (i * sz) > heap->size ) { |
| nr = heap->nrRegions; |
| break; |
| } |
| |
| if (list[i].age > heap->local_age) |
| driTexturesGone( heap, i * sz, sz, list[i].in_use); |
| } |
| |
| /* Loop or uninitialized heap detected. Reset. |
| */ |
| |
| if (nr == heap->nrRegions) { |
| driTexturesGone( heap, 0, heap->size, 0); |
| resetGlobalLRU( heap ); |
| } |
| |
| if ( 0 ) { |
| printGlobalLRU( heap, __FUNCTION__ ); |
| printLocalLRU( heap, __FUNCTION__ ); |
| } |
| |
| heap->local_age = heap->global_age[0]; |
| } |
| |
| |
| |
| |
| #define INDEX_ARRAY_SIZE 6 /* I'm not aware of driver with more than 2 heaps */ |
| |
| /** |
| * Allocate memory from a texture heap to hold a texture object. This |
| * routine will attempt to allocate memory for the texture from the heaps |
| * specified by \c heap_array in order. That is, first it will try to |
| * allocate from \c heap_array[0], then \c heap_array[1], and so on. |
| * |
| * \param heap_array Array of pointers to texture heaps to use |
| * \param nr_heaps Number of heap pointer in \a heap_array |
| * \param t Texture object for which space is needed |
| * \return The ID of the heap from which memory was allocated, or -1 if |
| * memory could not be allocated. |
| * |
| * \bug The replacement policy implemented by this function is horrible. |
| */ |
| |
| |
| int |
| driAllocateTexture( driTexHeap * const * heap_array, unsigned nr_heaps, |
| driTextureObject * t ) |
| { |
| driTexHeap * heap; |
| driTextureObject * temp; |
| driTextureObject * cursor; |
| unsigned id; |
| |
| |
| /* In case it already has texture space, initialize heap. This also |
| * prevents GCC from issuing a warning that heap might be used |
| * uninitialized. |
| */ |
| |
| heap = t->heap; |
| |
| |
| /* Run through each of the existing heaps and try to allocate a buffer |
| * to hold the texture. |
| */ |
| |
| for ( id = 0 ; (t->memBlock == NULL) && (id < nr_heaps) ; id++ ) { |
| heap = heap_array[ id ]; |
| if ( heap != NULL ) { |
| t->memBlock = mmAllocMem( heap->memory_heap, t->totalSize, |
| heap->alignmentShift, 0 ); |
| } |
| } |
| |
| |
| /* Kick textures out until the requested texture fits. |
| */ |
| |
| if ( t->memBlock == NULL ) { |
| unsigned index[INDEX_ARRAY_SIZE]; |
| unsigned nrGoodHeaps = 0; |
| |
| /* Trying to avoid dynamic memory allocation. If you have more |
| * heaps, increase INDEX_ARRAY_SIZE. I'm not aware of any |
| * drivers with more than 2 tex heaps. */ |
| assert( nr_heaps < INDEX_ARRAY_SIZE ); |
| |
| /* Sort large enough heaps by duty. Insertion sort should be |
| * fast enough for such a short array. */ |
| for ( id = 0 ; id < nr_heaps ; id++ ) { |
| heap = heap_array[ id ]; |
| |
| if ( heap != NULL && t->totalSize <= heap->size ) { |
| unsigned j; |
| |
| for ( j = 0 ; j < nrGoodHeaps; j++ ) { |
| if ( heap->duty > heap_array[ index[ j ] ]->duty ) |
| break; |
| } |
| |
| if ( j < nrGoodHeaps ) { |
| memmove( &index[ j+1 ], &index[ j ], |
| sizeof(index[ 0 ]) * (nrGoodHeaps - j) ); |
| } |
| |
| index[ j ] = id; |
| |
| nrGoodHeaps++; |
| } |
| } |
| |
| for ( id = 0 ; (t->memBlock == NULL) && (id < nrGoodHeaps) ; id++ ) { |
| heap = heap_array[ index[ id ] ]; |
| |
| for ( cursor = heap->texture_objects.prev, temp = cursor->prev; |
| cursor != &heap->texture_objects ; |
| cursor = temp, temp = cursor->prev ) { |
| |
| /* The the LRU element. If the texture is bound to one of |
| * the texture units, then we cannot kick it out. |
| */ |
| if ( cursor->bound || cursor->reserved ) { |
| continue; |
| } |
| |
| if ( cursor->memBlock ) |
| heap->duty -= cursor->memBlock->size; |
| |
| /* If this is a placeholder, there's no need to keep it */ |
| if (cursor->tObj) |
| driSwapOutTextureObject( cursor ); |
| else |
| driDestroyTextureObject( cursor ); |
| |
| t->memBlock = mmAllocMem( heap->memory_heap, t->totalSize, |
| heap->alignmentShift, 0 ); |
| |
| if (t->memBlock) |
| break; |
| } |
| } |
| |
| /* Rebalance duties. If a heap kicked more data than its duty, |
| * then all other heaps get that amount multiplied with their |
| * relative weight added to their duty. The negative duty is |
| * reset to 0. In the end all heaps have a duty >= 0. |
| * |
| * CAUTION: we must not change the heap pointer here, because it |
| * is used below to update the texture object. |
| */ |
| for ( id = 0 ; id < nr_heaps ; id++ ) |
| if ( heap_array[ id ] != NULL && heap_array[ id ]->duty < 0) { |
| int duty = -heap_array[ id ]->duty; |
| double weight = heap_array[ id ]->weight; |
| unsigned j; |
| |
| for ( j = 0 ; j < nr_heaps ; j++ ) |
| if ( j != id && heap_array[ j ] != NULL ) { |
| heap_array[ j ]->duty += (double) duty * |
| heap_array[ j ]->weight / weight; |
| } |
| |
| heap_array[ id ]->duty = 0; |
| } |
| } |
| |
| |
| if ( t->memBlock != NULL ) { |
| /* id and heap->heapId may or may not be the same value here. |
| */ |
| |
| assert( heap != NULL ); |
| assert( (t->heap == NULL) || (t->heap == heap) ); |
| |
| t->heap = heap; |
| return heap->heapId; |
| } |
| else { |
| assert( t->heap == NULL ); |
| |
| fprintf( stderr, "[%s:%d] unable to allocate texture\n", |
| __FUNCTION__, __LINE__ ); |
| return -1; |
| } |
| } |
| |
| |
| |
| |
| |
| |
| /** |
| * Set the location where the texture-swap counter is stored. |
| */ |
| |
| void |
| driSetTextureSwapCounterLocation( driTexHeap * heap, unsigned * counter ) |
| { |
| heap->texture_swaps = (counter == NULL) ? & dummy_swap_counter : counter; |
| } |
| |
| |
| |
| |
| /** |
| * Create a new heap for texture data. |
| * |
| * \param heap_id Device-dependent heap identifier. This value |
| * will returned by driAllocateTexture when memory |
| * is allocated from this heap. |
| * \param context Device-dependent driver context. This is |
| * supplied as the first parameter to the |
| * \c destroy_tex_obj function. |
| * \param size Size, in bytes, of the texture region |
| * \param alignmentShift Alignment requirement for textures. If textures |
| * must be allocated on a 4096 byte boundry, this |
| * would be 12. |
| * \param nr_regions Number of regions into which this texture space |
| * should be partitioned |
| * \param global_regions Array of \c drmTextureRegion structures in the SAREA |
| * \param global_age Pointer to the global texture age in the SAREA |
| * \param swapped_objects Pointer to the list of texture objects that are |
| * not in texture memory (i.e., have been swapped |
| * out). |
| * \param texture_object_size Size, in bytes, of a device-dependent texture |
| * object |
| * \param destroy_tex_obj Function used to destroy a device-dependent |
| * texture object |
| * |
| * \sa driDestroyTextureHeap |
| */ |
| |
| driTexHeap * |
| driCreateTextureHeap( unsigned heap_id, void * context, unsigned size, |
| unsigned alignmentShift, unsigned nr_regions, |
| drmTextureRegionPtr global_regions, unsigned * global_age, |
| driTextureObject * swapped_objects, |
| unsigned texture_object_size, |
| destroy_texture_object_t * destroy_tex_obj |
| ) |
| { |
| driTexHeap * heap; |
| unsigned l; |
| |
| |
| if ( 0 ) |
| fprintf( stderr, "%s( %u, %p, %u, %u, %u )\n", |
| __FUNCTION__, |
| heap_id, (void *)context, size, alignmentShift, nr_regions ); |
| |
| heap = (driTexHeap *) CALLOC( sizeof( driTexHeap ) ); |
| if ( heap != NULL ) { |
| l = driLog2( (size - 1) / nr_regions ); |
| if ( l < alignmentShift ) |
| { |
| l = alignmentShift; |
| } |
| |
| heap->logGranularity = l; |
| heap->size = size & ~((1L << l) - 1); |
| |
| heap->memory_heap = mmInit( 0, heap->size ); |
| if ( heap->memory_heap != NULL ) { |
| heap->heapId = heap_id; |
| heap->driverContext = context; |
| |
| heap->alignmentShift = alignmentShift; |
| heap->nrRegions = nr_regions; |
| heap->global_regions = global_regions; |
| heap->global_age = global_age; |
| heap->swapped_objects = swapped_objects; |
| heap->texture_object_size = texture_object_size; |
| heap->destroy_texture_object = destroy_tex_obj; |
| |
| /* Force global heap init */ |
| if (heap->global_age[0] == 0) |
| heap->local_age = ~0; |
| else |
| heap->local_age = 0; |
| |
| make_empty_list( & heap->texture_objects ); |
| driSetTextureSwapCounterLocation( heap, NULL ); |
| |
| heap->weight = heap->size; |
| heap->duty = 0; |
| } |
| else { |
| FREE( heap ); |
| heap = NULL; |
| } |
| } |
| |
| |
| if ( 0 ) |
| fprintf( stderr, "%s returning %p\n", __FUNCTION__, (void *)heap ); |
| |
| return heap; |
| } |
| |
| |
| |
| |
| /** Destroys a texture heap |
| * |
| * \param heap Texture heap to be destroyed |
| */ |
| |
| void |
| driDestroyTextureHeap( driTexHeap * heap ) |
| { |
| driTextureObject * t; |
| driTextureObject * temp; |
| |
| |
| if ( heap != NULL ) { |
| foreach_s( t, temp, & heap->texture_objects ) { |
| driDestroyTextureObject( t ); |
| } |
| foreach_s( t, temp, heap->swapped_objects ) { |
| driDestroyTextureObject( t ); |
| } |
| |
| mmDestroy( heap->memory_heap ); |
| FREE( heap ); |
| } |
| } |
| |
| |
| |
| |
| /****************************************************************************/ |
| /** |
| * Determine how many texels (including all mipmap levels) would be required |
| * for a texture map of size \f$2^^\c base_size_log2\f$ would require. |
| * |
| * \param base_size_log2 \f$log_2\f$ of the size of a side of the texture |
| * \param dimensions Number of dimensions of the texture. Either 2 or 3. |
| * \param faces Number of faces of the texture. Either 1 or 6 (for cube maps). |
| * \return Number of texels |
| */ |
| |
| static unsigned |
| texels_this_map_size( int base_size_log2, unsigned dimensions, unsigned faces ) |
| { |
| unsigned texels; |
| |
| |
| assert( (faces == 1) || (faces == 6) ); |
| assert( (dimensions == 2) || (dimensions == 3) ); |
| |
| texels = 0; |
| if ( base_size_log2 >= 0 ) { |
| texels = (1U << (dimensions * base_size_log2)); |
| |
| /* See http://www.mail-archive.com/dri-devel@lists.sourceforge.net/msg03636.html |
| * for the complete explaination of why this formulation is used. |
| * Basically, the smaller mipmap levels sum to 0.333 the size of the |
| * level 0 map. The total size is therefore the size of the map |
| * multipled by 1.333. The +2 is there to round up. |
| */ |
| |
| texels = (texels * 4 * faces + 2) / 3; |
| } |
| |
| return texels; |
| } |
| |
| |
| |
| |
| struct maps_per_heap { |
| unsigned c[32]; |
| }; |
| |
| static void |
| fill_in_maximums( driTexHeap * const * heaps, unsigned nr_heaps, |
| unsigned max_bytes_per_texel, unsigned max_size, |
| unsigned mipmaps_at_once, unsigned dimensions, |
| unsigned faces, struct maps_per_heap * max_textures ) |
| { |
| unsigned heap; |
| unsigned log2_size; |
| unsigned mask; |
| |
| |
| /* Determine how many textures of each size can be stored in each |
| * texture heap. |
| */ |
| |
| for ( heap = 0 ; heap < nr_heaps ; heap++ ) { |
| if ( heaps[ heap ] == NULL ) { |
| (void) memset( max_textures[ heap ].c, 0, |
| sizeof( max_textures[ heap ].c ) ); |
| continue; |
| } |
| |
| mask = (1U << heaps[ heap ]->logGranularity) - 1; |
| |
| if ( 0 ) { |
| fprintf( stderr, "[%s:%d] heap[%u] = %u bytes, mask = 0x%08x\n", |
| __FILE__, __LINE__, |
| heap, heaps[ heap ]->size, mask ); |
| } |
| |
| for ( log2_size = max_size ; log2_size > 0 ; log2_size-- ) { |
| unsigned total; |
| |
| |
| /* Determine the total number of bytes required by a texture of |
| * size log2_size. |
| */ |
| |
| total = texels_this_map_size( log2_size, dimensions, faces ) |
| - texels_this_map_size( log2_size - mipmaps_at_once, |
| dimensions, faces ); |
| total *= max_bytes_per_texel; |
| total = (total + mask) & ~mask; |
| |
| /* The number of textures of a given size that will fit in a heap |
| * is equal to the size of the heap divided by the size of the |
| * texture. |
| */ |
| |
| max_textures[ heap ].c[ log2_size ] = heaps[ heap ]->size / total; |
| |
| if ( 0 ) { |
| fprintf( stderr, "[%s:%d] max_textures[%u].c[%02u] " |
| "= 0x%08x / 0x%08x " |
| "= %u (%u)\n", |
| __FILE__, __LINE__, |
| heap, log2_size, |
| heaps[ heap ]->size, total, |
| heaps[ heap ]->size / total, |
| max_textures[ heap ].c[ log2_size ] ); |
| } |
| } |
| } |
| } |
| |
| |
| static unsigned |
| get_max_size( unsigned nr_heaps, |
| unsigned texture_units, |
| unsigned max_size, |
| int all_textures_one_heap, |
| struct maps_per_heap * max_textures ) |
| { |
| unsigned heap; |
| unsigned log2_size; |
| |
| |
| /* Determine the largest texture size such that a texture of that size |
| * can be bound to each texture unit at the same time. Some hardware |
| * may require that all textures be in the same texture heap for |
| * multitexturing. |
| */ |
| |
| for ( log2_size = max_size ; log2_size > 0 ; log2_size-- ) { |
| unsigned total = 0; |
| |
| for ( heap = 0 ; heap < nr_heaps ; heap++ ) |
| { |
| total += max_textures[ heap ].c[ log2_size ]; |
| |
| if ( 0 ) { |
| fprintf( stderr, "[%s:%d] max_textures[%u].c[%02u] = %u, " |
| "total = %u\n", __FILE__, __LINE__, heap, log2_size, |
| max_textures[ heap ].c[ log2_size ], total ); |
| } |
| |
| if ( (max_textures[ heap ].c[ log2_size ] >= texture_units) |
| || (!all_textures_one_heap && (total >= texture_units)) ) { |
| /* The number of mipmap levels is the log-base-2 of the |
| * maximum texture size plus 1. If the maximum texture size |
| * is 1x1, the log-base-2 is 0 and 1 mipmap level (the base |
| * level) is available. |
| */ |
| |
| return log2_size + 1; |
| } |
| } |
| } |
| |
| /* This should NEVER happen. It should always be possible to have at |
| * *least* a 1x1 texture in memory! |
| */ |
| assert( log2_size != 0 ); |
| return 0; |
| } |
| |
| #define SET_MAX(f,v) \ |
| do { if ( max_sizes[v] != 0 ) { limits-> f = max_sizes[v]; } } while( 0 ) |
| |
| #define SET_MAX_RECT(f,v) \ |
| do { if ( max_sizes[v] != 0 ) { limits-> f = 1 << (max_sizes[v] - 1); } } while( 0 ) |
| |
| |
| /** |
| * Given the amount of texture memory, the number of texture units, and the |
| * maximum size of a texel, calculate the maximum texture size the driver can |
| * advertise. |
| * |
| * \param heaps Texture heaps for this card |
| * \param nr_heap Number of texture heaps |
| * \param limits OpenGL contants. MaxTextureUnits must be set. |
| * \param max_bytes_per_texel Maximum size of a single texel, in bytes |
| * \param max_2D_size \f$\log_2\f$ of the maximum 2D texture size (i.e., |
| * 1024x1024 textures, this would be 10) |
| * \param max_3D_size \f$\log_2\f$ of the maximum 3D texture size (i.e., |
| * 1024x1024x1024 textures, this would be 10) |
| * \param max_cube_size \f$\log_2\f$ of the maximum cube texture size (i.e., |
| * 1024x1024 textures, this would be 10) |
| * \param max_rect_size \f$\log_2\f$ of the maximum texture rectangle size |
| * (i.e., 1024x1024 textures, this would be 10). This is a power-of-2 |
| * even though texture rectangles need not be a power-of-2. |
| * \param mipmaps_at_once Total number of mipmaps that can be used |
| * at one time. For most hardware this will be \f$\c max_size + 1\f$. |
| * For hardware that does not support mipmapping, this will be 1. |
| * \param all_textures_one_heap True if the hardware requires that all |
| * textures be in a single texture heap for multitexturing. |
| * \param allow_larger_textures 0 conservative, 1 calculate limits |
| * so at least one worst-case texture can fit, 2 just use hw limits. |
| */ |
| |
| void |
| driCalculateMaxTextureLevels( driTexHeap * const * heaps, |
| unsigned nr_heaps, |
| struct gl_constants * limits, |
| unsigned max_bytes_per_texel, |
| unsigned max_2D_size, |
| unsigned max_3D_size, |
| unsigned max_cube_size, |
| unsigned max_rect_size, |
| unsigned mipmaps_at_once, |
| int all_textures_one_heap, |
| int allow_larger_textures ) |
| { |
| struct maps_per_heap max_textures[8]; |
| unsigned i; |
| const unsigned dimensions[4] = { 2, 3, 2, 2 }; |
| const unsigned faces[4] = { 1, 1, 6, 1 }; |
| unsigned max_sizes[4]; |
| unsigned mipmaps[4]; |
| |
| |
| max_sizes[0] = max_2D_size; |
| max_sizes[1] = max_3D_size; |
| max_sizes[2] = max_cube_size; |
| max_sizes[3] = max_rect_size; |
| |
| mipmaps[0] = mipmaps_at_once; |
| mipmaps[1] = mipmaps_at_once; |
| mipmaps[2] = mipmaps_at_once; |
| mipmaps[3] = 1; |
| |
| |
| /* Calculate the maximum number of texture levels in two passes. The |
| * first pass determines how many textures of each power-of-two size |
| * (including all mipmap levels for that size) can fit in each texture |
| * heap. The second pass finds the largest texture size that allows |
| * a texture of that size to be bound to every texture unit. |
| */ |
| |
| for ( i = 0 ; i < 4 ; i++ ) { |
| if ( (allow_larger_textures != 2) && (max_sizes[ i ] != 0) ) { |
| fill_in_maximums( heaps, nr_heaps, max_bytes_per_texel, |
| max_sizes[ i ], mipmaps[ i ], |
| dimensions[ i ], faces[ i ], |
| max_textures ); |
| |
| max_sizes[ i ] = get_max_size( nr_heaps, |
| allow_larger_textures == 1 ? |
| 1 : limits->MaxTextureUnits, |
| max_sizes[ i ], |
| all_textures_one_heap, |
| max_textures ); |
| } |
| else if (max_sizes[ i ] != 0) { |
| max_sizes[ i ] += 1; |
| } |
| } |
| |
| SET_MAX( MaxTextureLevels, 0 ); |
| SET_MAX( Max3DTextureLevels, 1 ); |
| SET_MAX( MaxCubeTextureLevels, 2 ); |
| SET_MAX_RECT( MaxTextureRectSize, 3 ); |
| } |
| |
| |
| |
| |
| /** |
| * Perform initial binding of default textures objects on a per unit, per |
| * texture target basis. |
| * |
| * \param ctx Current OpenGL context |
| * \param swapped List of swapped-out textures |
| * \param targets Bit-mask of value texture targets |
| */ |
| |
| void driInitTextureObjects( GLcontext *ctx, driTextureObject * swapped, |
| GLuint targets ) |
| { |
| struct gl_texture_object *texObj; |
| GLuint tmp = ctx->Texture.CurrentUnit; |
| unsigned i; |
| |
| |
| for ( i = 0 ; i < ctx->Const.MaxTextureUnits ; i++ ) { |
| ctx->Texture.CurrentUnit = i; |
| |
| if ( (targets & DRI_TEXMGR_DO_TEXTURE_1D) != 0 ) { |
| texObj = ctx->Texture.Unit[i].Current1D; |
| ctx->Driver.BindTexture( ctx, GL_TEXTURE_1D, texObj ); |
| move_to_tail( swapped, (driTextureObject *) texObj->DriverData ); |
| } |
| |
| if ( (targets & DRI_TEXMGR_DO_TEXTURE_2D) != 0 ) { |
| texObj = ctx->Texture.Unit[i].Current2D; |
| ctx->Driver.BindTexture( ctx, GL_TEXTURE_2D, texObj ); |
| move_to_tail( swapped, (driTextureObject *) texObj->DriverData ); |
| } |
| |
| if ( (targets & DRI_TEXMGR_DO_TEXTURE_3D) != 0 ) { |
| texObj = ctx->Texture.Unit[i].Current3D; |
| ctx->Driver.BindTexture( ctx, GL_TEXTURE_3D, texObj ); |
| move_to_tail( swapped, (driTextureObject *) texObj->DriverData ); |
| } |
| |
| if ( (targets & DRI_TEXMGR_DO_TEXTURE_CUBE) != 0 ) { |
| texObj = ctx->Texture.Unit[i].CurrentCubeMap; |
| ctx->Driver.BindTexture( ctx, GL_TEXTURE_CUBE_MAP_ARB, texObj ); |
| move_to_tail( swapped, (driTextureObject *) texObj->DriverData ); |
| } |
| |
| if ( (targets & DRI_TEXMGR_DO_TEXTURE_RECT) != 0 ) { |
| texObj = ctx->Texture.Unit[i].CurrentRect; |
| ctx->Driver.BindTexture( ctx, GL_TEXTURE_RECTANGLE_NV, texObj ); |
| move_to_tail( swapped, (driTextureObject *) texObj->DriverData ); |
| } |
| } |
| |
| ctx->Texture.CurrentUnit = tmp; |
| } |
| |
| |
| |
| |
| /** |
| * Verify that the specified texture is in the specificed heap. |
| * |
| * \param tex Texture to be tested. |
| * \param heap Texture memory heap to be tested. |
| * \return True if the texture is in the heap, false otherwise. |
| */ |
| |
| static GLboolean |
| check_in_heap( const driTextureObject * tex, const driTexHeap * heap ) |
| { |
| #if 1 |
| return tex->heap == heap; |
| #else |
| driTextureObject * curr; |
| |
| foreach( curr, & heap->texture_objects ) { |
| if ( curr == tex ) { |
| break; |
| } |
| } |
| |
| return curr == tex; |
| #endif |
| } |
| |
| |
| |
| /****************************************************************************/ |
| /** |
| * Validate the consistency of a set of texture heaps. |
| * Original version by Keith Whitwell in r200/r200_sanity.c. |
| */ |
| |
| GLboolean |
| driValidateTextureHeaps( driTexHeap * const * texture_heaps, |
| unsigned nr_heaps, const driTextureObject * swapped ) |
| { |
| driTextureObject *t; |
| unsigned i; |
| |
| for ( i = 0 ; i < nr_heaps ; i++ ) { |
| int last_end = 0; |
| unsigned textures_in_heap = 0; |
| unsigned blocks_in_mempool = 0; |
| const driTexHeap * heap = texture_heaps[i]; |
| const struct mem_block *p = heap->memory_heap; |
| |
| /* Check each texture object has a MemBlock, and is linked into |
| * the correct heap. |
| * |
| * Check the texobj base address corresponds to the MemBlock |
| * range. Check the texobj size (recalculate?) fits within |
| * the MemBlock. |
| * |
| * Count the number of texobj's using this heap. |
| */ |
| |
| foreach ( t, &heap->texture_objects ) { |
| if ( !check_in_heap( t, heap ) ) { |
| fprintf( stderr, "%s memory block for texture object @ %p not " |
| "found in heap #%d\n", |
| __FUNCTION__, (void *)t, i ); |
| return GL_FALSE; |
| } |
| |
| |
| if ( t->totalSize > t->memBlock->size ) { |
| fprintf( stderr, "%s: Memory block for texture object @ %p is " |
| "only %u bytes, but %u are required\n", |
| __FUNCTION__, (void *)t, t->totalSize, t->memBlock->size ); |
| return GL_FALSE; |
| } |
| |
| textures_in_heap++; |
| } |
| |
| /* Validate the contents of the heap: |
| * - Ordering |
| * - Overlaps |
| * - Bounds |
| */ |
| |
| while ( p != NULL ) { |
| if (p->reserved) { |
| fprintf( stderr, "%s: Block (%08x,%x), is reserved?!\n", |
| __FUNCTION__, p->ofs, p->size ); |
| return GL_FALSE; |
| } |
| |
| if (p->ofs != last_end) { |
| fprintf( stderr, "%s: blocks_in_mempool = %d, last_end = %d, p->ofs = %d\n", |
| __FUNCTION__, blocks_in_mempool, last_end, p->ofs ); |
| return GL_FALSE; |
| } |
| |
| if (!p->reserved && !p->free) { |
| blocks_in_mempool++; |
| } |
| |
| last_end = p->ofs + p->size; |
| p = p->next; |
| } |
| |
| if (textures_in_heap != blocks_in_mempool) { |
| fprintf( stderr, "%s: Different number of textures objects (%u) and " |
| "inuse memory blocks (%u)\n", |
| __FUNCTION__, textures_in_heap, blocks_in_mempool ); |
| return GL_FALSE; |
| } |
| |
| #if 0 |
| fprintf( stderr, "%s: textures_in_heap = %u\n", |
| __FUNCTION__, textures_in_heap ); |
| #endif |
| } |
| |
| |
| /* Check swapped texobj's have zero memblocks |
| */ |
| i = 0; |
| foreach ( t, swapped ) { |
| if ( t->memBlock != NULL ) { |
| fprintf( stderr, "%s: Swapped texobj %p has non-NULL memblock %p\n", |
| __FUNCTION__, (void *)t, (void *)t->memBlock ); |
| return GL_FALSE; |
| } |
| i++; |
| } |
| |
| #if 0 |
| fprintf( stderr, "%s: swapped texture count = %u\n", __FUNCTION__, i ); |
| #endif |
| |
| return GL_TRUE; |
| } |
| |
| |
| |
| |
| /****************************************************************************/ |
| /** |
| * Compute which mipmap levels that really need to be sent to the hardware. |
| * This depends on the base image size, GL_TEXTURE_MIN_LOD, |
| * GL_TEXTURE_MAX_LOD, GL_TEXTURE_BASE_LEVEL, and GL_TEXTURE_MAX_LEVEL. |
| */ |
| |
| void |
| driCalculateTextureFirstLastLevel( driTextureObject * t ) |
| { |
| struct gl_texture_object * const tObj = t->tObj; |
| const struct gl_texture_image * const baseImage = |
| tObj->Image[0][tObj->BaseLevel]; |
| |
| /* These must be signed values. MinLod and MaxLod can be negative numbers, |
| * and having firstLevel and lastLevel as signed prevents the need for |
| * extra sign checks. |
| */ |
| int firstLevel; |
| int lastLevel; |
| |
| /* Yes, this looks overly complicated, but it's all needed. |
| */ |
| |
| switch (tObj->Target) { |
| case GL_TEXTURE_1D: |
| case GL_TEXTURE_2D: |
| case GL_TEXTURE_3D: |
| case GL_TEXTURE_CUBE_MAP: |
| if (tObj->MinFilter == GL_NEAREST || tObj->MinFilter == GL_LINEAR) { |
| /* GL_NEAREST and GL_LINEAR only care about GL_TEXTURE_BASE_LEVEL. |
| */ |
| |
| firstLevel = lastLevel = tObj->BaseLevel; |
| } |
| else { |
| firstLevel = tObj->BaseLevel + (GLint)(tObj->MinLod + 0.5); |
| firstLevel = MAX2(firstLevel, tObj->BaseLevel); |
| firstLevel = MIN2(firstLevel, tObj->BaseLevel + baseImage->MaxLog2); |
| lastLevel = tObj->BaseLevel + (GLint)(tObj->MaxLod + 0.5); |
| lastLevel = MAX2(lastLevel, t->tObj->BaseLevel); |
| lastLevel = MIN2(lastLevel, t->tObj->BaseLevel + baseImage->MaxLog2); |
| lastLevel = MIN2(lastLevel, t->tObj->MaxLevel); |
| lastLevel = MAX2(firstLevel, lastLevel); /* need at least one level */ |
| } |
| break; |
| case GL_TEXTURE_RECTANGLE_NV: |
| case GL_TEXTURE_4D_SGIS: |
| firstLevel = lastLevel = 0; |
| break; |
| default: |
| return; |
| } |
| |
| /* save these values */ |
| t->firstLevel = firstLevel; |
| t->lastLevel = lastLevel; |
| } |
| |
| |
| |
| |
| /** |
| * \name DRI texture formats. Pointers initialized to either the big- or |
| * little-endian Mesa formats. |
| */ |
| /*@{*/ |
| const struct gl_texture_format *_dri_texformat_rgba8888 = NULL; |
| const struct gl_texture_format *_dri_texformat_argb8888 = NULL; |
| const struct gl_texture_format *_dri_texformat_rgb565 = NULL; |
| const struct gl_texture_format *_dri_texformat_argb4444 = NULL; |
| const struct gl_texture_format *_dri_texformat_argb1555 = NULL; |
| const struct gl_texture_format *_dri_texformat_al88 = NULL; |
| const struct gl_texture_format *_dri_texformat_a8 = &_mesa_texformat_a8; |
| const struct gl_texture_format *_dri_texformat_ci8 = &_mesa_texformat_ci8; |
| const struct gl_texture_format *_dri_texformat_i8 = &_mesa_texformat_i8; |
| const struct gl_texture_format *_dri_texformat_l8 = &_mesa_texformat_l8; |
| /*@}*/ |
| |
| |
| /** |
| * Initialize little endian target, host byte order independent texture formats |
| */ |
| void |
| driInitTextureFormats(void) |
| { |
| const GLuint ui = 1; |
| const GLubyte littleEndian = *((const GLubyte *) &ui); |
| |
| if (littleEndian) { |
| _dri_texformat_rgba8888 = &_mesa_texformat_rgba8888; |
| _dri_texformat_argb8888 = &_mesa_texformat_argb8888; |
| _dri_texformat_rgb565 = &_mesa_texformat_rgb565; |
| _dri_texformat_argb4444 = &_mesa_texformat_argb4444; |
| _dri_texformat_argb1555 = &_mesa_texformat_argb1555; |
| _dri_texformat_al88 = &_mesa_texformat_al88; |
| } |
| else { |
| _dri_texformat_rgba8888 = &_mesa_texformat_rgba8888_rev; |
| _dri_texformat_argb8888 = &_mesa_texformat_argb8888_rev; |
| _dri_texformat_rgb565 = &_mesa_texformat_rgb565_rev; |
| _dri_texformat_argb4444 = &_mesa_texformat_argb4444_rev; |
| _dri_texformat_argb1555 = &_mesa_texformat_argb1555_rev; |
| _dri_texformat_al88 = &_mesa_texformat_al88_rev; |
| } |
| } |