diff --git a/neo/renderer/VertexCache.cpp b/neo/renderer/VertexCache.cpp index ec0184e59..fd3ba5225 100644 --- a/neo/renderer/VertexCache.cpp +++ b/neo/renderer/VertexCache.cpp @@ -1,561 +1,627 @@ -/* -=========================================================================== - -Doom 3 GPL Source Code -Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. - -This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). - -Doom 3 Source Code is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Doom 3 Source Code is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Doom 3 Source Code. If not, see . - -In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. - -If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. - -=========================================================================== -*/ - -#include "sys/platform.h" -#include "framework/Common.h" -#include "renderer/tr_local.h" - -#include "renderer/VertexCache.h" - -static const int FRAME_MEMORY_BYTES = 0x200000; -static const int EXPAND_HEADERS = 1024; - -idCVar idVertexCache::r_showVertexCache( "r_showVertexCache", "0", CVAR_INTEGER|CVAR_RENDERER, "" ); -idCVar idVertexCache::r_vertexBufferMegs( "r_vertexBufferMegs", "32", CVAR_INTEGER|CVAR_RENDERER, "" ); - -idVertexCache vertexCache; - -/* -============== -R_ListVertexCache_f -============== -*/ -static void R_ListVertexCache_f( const idCmdArgs &args ) { - vertexCache.List(); -} - -/* -============== -idVertexCache::ActuallyFree -============== -*/ -void idVertexCache::ActuallyFree( vertCache_t *block ) { - if (!block) { - common->Error( "idVertexCache Free: NULL pointer" ); - } - - if ( block->user ) { - // let the owner know we have purged it - *block->user = NULL; - block->user = NULL; - } - - // temp blocks are in a shared space that won't be freed - if ( block->tag != TAG_TEMP ) { - staticAllocTotal -= block->size; - staticCountTotal--; - - if ( block->vbo ) { -#if 0 // this isn't really necessary, it will be reused soon enough - // filling with zero length data is the equivalent of freeing - qglBindBufferARB(GL_ARRAY_BUFFER_ARB, block->vbo); - qglBufferDataARB(GL_ARRAY_BUFFER_ARB, 0, 0, GL_DYNAMIC_DRAW_ARB); -#endif - } else if ( block->virtMem ) { - Mem_Free( block->virtMem ); - block->virtMem = NULL; - } - } - block->tag = TAG_FREE; // mark as free - - // unlink stick it back on the free list - block->next->prev = block->prev; - block->prev->next = block->next; - -#if 1 - // stick it on the front of the free list so it will be reused immediately - block->next = freeStaticHeaders.next; - block->prev = &freeStaticHeaders; -#else - // stick it on the back of the free list so it won't be reused soon (just for debugging) - block->next = &freeStaticHeaders; - block->prev = freeStaticHeaders.prev; -#endif - - block->next->prev = block; - block->prev->next = block; -} - -/* -============== -idVertexCache::Position - -this will be a real pointer with virtual memory, -but it will be an int offset cast to a pointer with -ARB_vertex_buffer_object - -The ARB_vertex_buffer_object will be bound -============== -*/ -void *idVertexCache::Position( vertCache_t *buffer ) { - if ( !buffer || buffer->tag == TAG_FREE ) { - common->FatalError( "idVertexCache::Position: bad vertCache_t" ); - } - - // the ARB vertex object just uses an offset - if ( buffer->vbo ) { - if ( r_showVertexCache.GetInteger() == 2 ) { - if ( buffer->tag == TAG_TEMP ) { - common->Printf( "GL_ARRAY_BUFFER_ARB = %i + %zd (%i bytes)\n", buffer->vbo, buffer->offset, buffer->size ); - } else { - common->Printf( "GL_ARRAY_BUFFER_ARB = %i (%i bytes)\n", buffer->vbo, buffer->size ); - } - } - if ( buffer->indexBuffer ) { - qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, buffer->vbo ); - } else { - qglBindBufferARB( GL_ARRAY_BUFFER_ARB, buffer->vbo ); - } - return (void *)buffer->offset; - } - - // virtual memory is a real pointer - return (void *)((byte *)buffer->virtMem + buffer->offset); -} - -void idVertexCache::UnbindIndex() { - qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); -} - - -//================================================================================ - -/* -=========== -idVertexCache::Init -=========== -*/ -void idVertexCache::Init() { - cmdSystem->AddCommand( "listVertexCache", R_ListVertexCache_f, CMD_FL_RENDERER, "lists vertex cache" ); - - if ( r_vertexBufferMegs.GetInteger() < 8 ) { - r_vertexBufferMegs.SetInteger( 8 ); - } - - virtualMemory = false; - - // use ARB_vertex_buffer_object unless explicitly disabled - if( r_useVertexBuffers.GetInteger() && glConfig.ARBVertexBufferObjectAvailable ) { - common->Printf( "using ARB_vertex_buffer_object memory\n" ); - } else { - virtualMemory = true; - r_useIndexBuffers.SetBool( false ); - common->Printf( "WARNING: vertex array range in virtual memory (SLOW)\n" ); - } - - // initialize the cache memory blocks - freeStaticHeaders.next = freeStaticHeaders.prev = &freeStaticHeaders; - staticHeaders.next = staticHeaders.prev = &staticHeaders; - freeDynamicHeaders.next = freeDynamicHeaders.prev = &freeDynamicHeaders; - dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders; - deferredFreeList.next = deferredFreeList.prev = &deferredFreeList; - - // set up the dynamic frame memory - frameBytes = FRAME_MEMORY_BYTES; - staticAllocTotal = 0; - - byte *junk = (byte *)Mem_Alloc( frameBytes ); - for ( int i = 0 ; i < NUM_VERTEX_FRAMES ; i++ ) { - allocatingTempBuffer = true; // force the alloc to use GL_STREAM_DRAW_ARB - Alloc( junk, frameBytes, &tempBuffers[i] ); - allocatingTempBuffer = false; - tempBuffers[i]->tag = TAG_FIXED; - // unlink these from the static list, so they won't ever get purged - tempBuffers[i]->next->prev = tempBuffers[i]->prev; - tempBuffers[i]->prev->next = tempBuffers[i]->next; - } - Mem_Free( junk ); - - EndFrame(); -} - -/* -=========== -idVertexCache::PurgeAll - -Used when toggling vertex programs on or off, because -the cached data isn't valid -=========== -*/ -void idVertexCache::PurgeAll() { - while( staticHeaders.next != &staticHeaders ) { - ActuallyFree( staticHeaders.next ); - } -} - -/* -=========== -idVertexCache::Shutdown -=========== -*/ -void idVertexCache::Shutdown() { -// PurgeAll(); // !@#: also purge the temp buffers - - headerAllocator.Shutdown(); -} - -/* -=========== -idVertexCache::Alloc -=========== -*/ -void idVertexCache::Alloc( void *data, int size, vertCache_t **buffer, bool indexBuffer ) { - vertCache_t *block; - - if ( size <= 0 ) { - common->Error( "idVertexCache::Alloc: size = %i\n", size ); - } - - // if we can't find anything, it will be NULL - *buffer = NULL; - - // if we don't have any remaining unused headers, allocate some more - if ( freeStaticHeaders.next == &freeStaticHeaders ) { - - for ( int i = 0; i < EXPAND_HEADERS; i++ ) { - block = headerAllocator.Alloc(); - block->next = freeStaticHeaders.next; - block->prev = &freeStaticHeaders; - block->next->prev = block; - block->prev->next = block; - - if( !virtualMemory ) { - qglGenBuffersARB( 1, & block->vbo ); - } - } - } - - // move it from the freeStaticHeaders list to the staticHeaders list - block = freeStaticHeaders.next; - block->next->prev = block->prev; - block->prev->next = block->next; - block->next = staticHeaders.next; - block->prev = &staticHeaders; - block->next->prev = block; - block->prev->next = block; - - block->size = size; - block->offset = 0; - block->tag = TAG_USED; - - // save data for debugging - staticAllocThisFrame += block->size; - staticCountThisFrame++; - staticCountTotal++; - staticAllocTotal += block->size; - - // this will be set to zero when it is purged - block->user = buffer; - *buffer = block; - - // allocation doesn't imply used-for-drawing, because at level - // load time lots of things may be created, but they aren't - // referenced by the GPU yet, and can be purged if needed. - block->frameUsed = currentFrame - NUM_VERTEX_FRAMES; - - block->indexBuffer = indexBuffer; - - // copy the data - if ( block->vbo ) { - if ( indexBuffer ) { - qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, block->vbo ); - qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB ); - } else { - qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo ); - if ( allocatingTempBuffer ) { - qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STREAM_DRAW_ARB ); - } else { - qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB ); - } - } - } else { - block->virtMem = Mem_Alloc( size ); - SIMDProcessor->Memcpy( block->virtMem, data, size ); - } -} - -/* -=========== -idVertexCache::Touch -=========== -*/ -void idVertexCache::Touch( vertCache_t *block ) { - if ( !block ) { - common->Error( "idVertexCache Touch: NULL pointer" ); - } - - if ( block->tag == TAG_FREE ) { - common->FatalError( "idVertexCache Touch: freed pointer" ); - } - if ( block->tag == TAG_TEMP ) { - common->FatalError( "idVertexCache Touch: temporary pointer" ); - } - - block->frameUsed = currentFrame; - - // move to the head of the LRU list - block->next->prev = block->prev; - block->prev->next = block->next; - - block->next = staticHeaders.next; - block->prev = &staticHeaders; - staticHeaders.next->prev = block; - staticHeaders.next = block; -} - -/* -=========== -idVertexCache::Free -=========== -*/ -void idVertexCache::Free( vertCache_t *block ) { - if (!block) { - return; - } - - if ( block->tag == TAG_FREE ) { - common->FatalError( "idVertexCache Free: freed pointer" ); - } - if ( block->tag == TAG_TEMP ) { - common->FatalError( "idVertexCache Free: temporary pointer" ); - } - - // this block still can't be purged until the frame count has expired, - // but it won't need to clear a user pointer when it is - block->user = NULL; - - block->next->prev = block->prev; - block->prev->next = block->next; - - block->next = deferredFreeList.next; - block->prev = &deferredFreeList; - deferredFreeList.next->prev = block; - deferredFreeList.next = block; -} - -/* -=========== -idVertexCache::AllocFrameTemp - -A frame temp allocation must never be allowed to fail due to overflow. -We can't simply sync with the GPU and overwrite what we have, because -there may still be future references to dynamically created surfaces. -=========== -*/ -vertCache_t *idVertexCache::AllocFrameTemp( void *data, int size ) { - vertCache_t *block; - - if ( size <= 0 ) { - common->Error( "idVertexCache::AllocFrameTemp: size = %i\n", size ); - } - - if ( dynamicAllocThisFrame + size > frameBytes ) { - // if we don't have enough room in the temp block, allocate a static block, - // but immediately free it so it will get freed at the next frame - tempOverflow = true; - Alloc( data, size, &block ); - Free( block); - return block; - } - - // this data is just going on the shared dynamic list - - // if we don't have any remaining unused headers, allocate some more - if ( freeDynamicHeaders.next == &freeDynamicHeaders ) { - - for ( int i = 0; i < EXPAND_HEADERS; i++ ) { - block = headerAllocator.Alloc(); - block->next = freeDynamicHeaders.next; - block->prev = &freeDynamicHeaders; - block->next->prev = block; - block->prev->next = block; - } - } - - // move it from the freeDynamicHeaders list to the dynamicHeaders list - block = freeDynamicHeaders.next; - block->next->prev = block->prev; - block->prev->next = block->next; - block->next = dynamicHeaders.next; - block->prev = &dynamicHeaders; - block->next->prev = block; - block->prev->next = block; - - block->size = size; - block->tag = TAG_TEMP; - block->indexBuffer = false; - block->offset = dynamicAllocThisFrame; - dynamicAllocThisFrame += block->size; - dynamicCountThisFrame++; - block->user = NULL; - block->frameUsed = 0; - - // copy the data - block->virtMem = tempBuffers[listNum]->virtMem; - block->vbo = tempBuffers[listNum]->vbo; - - if ( block->vbo ) { - qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo ); - qglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, block->offset, (GLsizeiptrARB)size, data ); - } else { - SIMDProcessor->Memcpy( (byte *)block->virtMem + block->offset, data, size ); - } - - return block; -} - -/* -=========== -idVertexCache::EndFrame -=========== -*/ -void idVertexCache::EndFrame() { - // display debug information - if ( r_showVertexCache.GetBool() ) { - int staticUseCount = 0; - int staticUseSize = 0; - - for ( vertCache_t *block = staticHeaders.next ; block != &staticHeaders ; block = block->next ) { - if ( block->frameUsed == currentFrame ) { - staticUseCount++; - staticUseSize += block->size; - } - } - - const char *frameOverflow = tempOverflow ? "(OVERFLOW)" : ""; - - common->Printf( "vertex dynamic:%i=%ik%s, static alloc:%i=%ik used:%i=%ik total:%i=%ik\n", - dynamicCountThisFrame, dynamicAllocThisFrame/1024, frameOverflow, - staticCountThisFrame, staticAllocThisFrame/1024, - staticUseCount, staticUseSize/1024, - staticCountTotal, staticAllocTotal/1024 ); - } - -#if 0 - // if our total static count is above our working memory limit, start purging things - while ( staticAllocTotal > r_vertexBufferMegs.GetInteger() * 1024 * 1024 ) { - // free the least recently used - - } -#endif - - if( !virtualMemory ) { - // unbind vertex buffers so normal virtual memory will be used in case - // r_useVertexBuffers / r_useIndexBuffers - qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 ); - qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 ); - } - - - currentFrame = tr.frameCount; - listNum = currentFrame % NUM_VERTEX_FRAMES; - staticAllocThisFrame = 0; - staticCountThisFrame = 0; - dynamicAllocThisFrame = 0; - dynamicCountThisFrame = 0; - tempOverflow = false; - - // free all the deferred free headers - while( deferredFreeList.next != &deferredFreeList ) { - ActuallyFree( deferredFreeList.next ); - } - - // free all the frame temp headers - vertCache_t *block = dynamicHeaders.next; - if ( block != &dynamicHeaders ) { - block->prev = &freeDynamicHeaders; - dynamicHeaders.prev->next = freeDynamicHeaders.next; - freeDynamicHeaders.next->prev = dynamicHeaders.prev; - freeDynamicHeaders.next = block; - - dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders; - } -} - -/* -============= -idVertexCache::List -============= -*/ -void idVertexCache::List( void ) { - int numActive = 0; - int frameStatic = 0; - int totalStatic = 0; - - vertCache_t *block; - for ( block = staticHeaders.next ; block != &staticHeaders ; block = block->next) { - numActive++; - - totalStatic += block->size; - if ( block->frameUsed == currentFrame ) { - frameStatic += block->size; - } - } - - int numFreeStaticHeaders = 0; - for ( block = freeStaticHeaders.next ; block != &freeStaticHeaders ; block = block->next ) { - numFreeStaticHeaders++; - } - - int numFreeDynamicHeaders = 0; - for ( block = freeDynamicHeaders.next ; block != &freeDynamicHeaders ; block = block->next ) { - numFreeDynamicHeaders++; - } - - common->Printf( "%i megs working set\n", r_vertexBufferMegs.GetInteger() ); - common->Printf( "%i dynamic temp buffers of %ik\n", NUM_VERTEX_FRAMES, frameBytes / 1024 ); - common->Printf( "%5i active static headers\n", numActive ); - common->Printf( "%5i free static headers\n", numFreeStaticHeaders ); - common->Printf( "%5i free dynamic headers\n", numFreeDynamicHeaders ); - - if ( !virtualMemory ) { - common->Printf( "Vertex cache is in ARB_vertex_buffer_object memory (FAST).\n"); - } else { - common->Printf( "Vertex cache is in virtual memory (SLOW)\n" ); - } - - if ( r_useIndexBuffers.GetBool() ) { - common->Printf( "Index buffers are accelerated.\n" ); - } else { - common->Printf( "Index buffers are not used.\n" ); - } -} - -/* -============= -idVertexCache::IsFast - -just for gfxinfo printing -============= -*/ -bool idVertexCache::IsFast() { - if ( virtualMemory ) { - return false; - } - return true; -} +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of +these additional terms immediately following the terms and conditions of the GNU General Public License which +accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address +below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id +Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "sys/platform.h" +#include "framework/Common.h" +#include "renderer/tr_local.h" +#include "renderer/VertexCache.h" + +static const int FRAME_MEMORY_BYTES = 0x400000; +static const int EXPAND_HEADERS = 1024; + +idCVar idVertexCache::r_showVertexCache( "r_showVertexCache", "0", CVAR_INTEGER | CVAR_RENDERER, + "show vertex cache, 0 = off, 1 = vbo debug, 2 = vbo mem", + idCmdSystem::ArgCompletion_Integer<0, 2> ); +idCVar idVertexCache::r_useArbBufferRange( "r_useArbBufferRange", "1", CVAR_BOOL | CVAR_RENDERER, + "use ARB_map_buffer_range for optimization, 0 = off, 1 = on", + idCmdSystem::ArgCompletion_Integer<0, 1> ); +idCVar +idVertexCache::r_reuseVertexCacheSooner( "r_reuseVertexCacheSooner", "1", CVAR_BOOL | CVAR_RENDERER, + "reuse vertex buffers as soon as possible after freeing, 0 = off, 1 = on", + idCmdSystem::ArgCompletion_Integer<0, 1> ); + +idVertexCache vertexCache; + +/* +============== +R_ShowVBOMem_f +============== +*/ +void R_ShowVBOMem_f( const idCmdArgs &args ) { + vertexCache.Show(); +} + +/* +============== +R_ListVBOMem_f +============== +*/ +void R_ListVBOMem_f( const idCmdArgs &args ) { + vertexCache.List(); +} + +/* +============== +idVertexCache::ActuallyFree +============== +*/ +void idVertexCache::ActuallyFree( vertCache_t *block ) { + if ( !block ) { + common->Error( "idVertexCache Free: NULL pointer" ); + } + + if ( block->user ) { + // let the owner know we have purged it + *block->user = NULL; + block->user = NULL; + } + + // temp blocks are in a shared space that won't be freed + if ( block->tag != TAG_TEMP ) { + staticAllocTotal -= block->size; + staticCountTotal--; + } + block->tag = TAG_FREE; // mark as free + + // unlink stick it back on the free list + block->next->prev = block->prev; + block->prev->next = block->next; + + if ( r_reuseVertexCacheSooner.GetBool() ) { + // stick it on the front of the free list so it will be reused immediately + block->next = freeStaticHeaders.next; + block->prev = &freeStaticHeaders; + } else { + // stick it on the back of the free list so it won't be reused soon (just for debugging) + block->next = &freeStaticHeaders; + block->prev = freeStaticHeaders.prev; + } + block->next->prev = block; + block->prev->next = block; +} + +/* +============== +idVertexCache::Position + +this will be a real pointer with virtual memory, +but it will be an int offset cast to a pointer with +ARB_vertex_buffer_object + +The ARB_vertex_buffer_object will be bound +============== +*/ +const void *idVertexCache::Position( vertCache_t *buffer ) { + if ( !buffer || buffer->tag == TAG_FREE ) { + common->FatalError( "idVertexCache::Position: bad vertCache_t" ); + } + + // the ARB vertex object just uses an offset + if ( buffer->vbo ) { + if ( r_showVertexCache.GetInteger() == 2 ) { + if ( buffer->tag == TAG_TEMP ) { + common->Printf( "GL_ARRAY_BUFFER_ARB = %i + %i (%i bytes)\n", buffer->vbo, buffer->offset, buffer->size ); + } else { + common->Printf( "GL_ARRAY_BUFFER_ARB = %i (%i bytes)\n", buffer->vbo, buffer->size ); + } + } + BindIndex( ( buffer->indexBuffer ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER ), buffer->vbo ); + return reinterpret_cast( buffer->offset ); + } + return NULL; +} + +//================================================================================ + +/* +=========== +idVertexCache::BindIndex + +Makes sure it only allocates the right buffers once. +=========== +*/ +void idVertexCache::BindIndex( GLenum target, GLuint vbo ) { + switch ( target ) { + case GL_ARRAY_BUFFER: + if ( vertexBuffer != vbo ) { + // this happens more often than you might think :( + qglBindBufferARB( target, vbo ); + vertexBuffer = vbo; + return; + } + break; + case GL_ELEMENT_ARRAY_BUFFER: + if ( indexBuffer != vbo ) { + // this happens more often than you might think :( + qglBindBufferARB( target, vbo ); + indexBuffer = vbo; + return; + } + break; + default: + common->FatalError( "BindIndex : unknown buffer target : %i\n", static_cast( target ) ); + break; + } +} + +/* +=========== +idVertexCache::UnbindIndex + +Pass 0 to vbo to invalidate it. +=========== +*/ +void idVertexCache::UnbindIndex( GLenum target ) { + BindIndex( target, 0 ); +} + +//================================================================================ + +/* +=========== +idVertexCache::Init +=========== +*/ +void idVertexCache::Init() { + // well this would suck... + if ( !glConfig.ARBVertexBufferObjectAvailable ) { + common->FatalError( "idVertexCache::Init: Your card is to old\n" ); + } + cmdSystem->AddCommand( "showVBOMem", R_ShowVBOMem_f, CMD_FL_RENDERER, "Shows Allocated Vertex Buffer Memory" ); + cmdSystem->AddCommand( "ListVBOMem", R_ListVBOMem_f, CMD_FL_RENDERER, "lists Objects Allocated in Vertex Cache" ); + + // initialize the buffers + vertexBuffer = 0; + indexBuffer = 0; + + // initialize the cache memory blocks + freeStaticHeaders.next = freeStaticHeaders.prev = &freeStaticHeaders; + staticHeaders.next = staticHeaders.prev = &staticHeaders; + freeDynamicHeaders.next = freeDynamicHeaders.prev = &freeDynamicHeaders; + dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders; + deferredFreeList.next = deferredFreeList.prev = &deferredFreeList; + + // set up the dynamic frame memory + frameBytes = FRAME_MEMORY_BYTES; + staticAllocTotal = 0; + + // allocate a dummy buffer + byte *frameBuffer = new byte[frameBytes]; + + for ( int i = 0; i < NUM_VERTEX_FRAMES; i++ ) { + // force the alloc to use GL_STREAM_DRAW_ARB + allocatingTempBuffer = true; + Alloc( frameBuffer, frameBytes, &tempBuffers[i] ); + allocatingTempBuffer = false; + tempBuffers[i]->tag = TAG_FIXED; + + // unlink these from the static list, so they won't ever get purged + tempBuffers[i]->next->prev = tempBuffers[i]->prev; + tempBuffers[i]->prev->next = tempBuffers[i]->next; + } + + // use C++ allocation + delete[] frameBuffer; + frameBuffer = NULL; + EndFrame(); +} + +/* +=========== +idVertexCache::PurgeAll + +Used when toggling vertex programs on or off, because +the cached data isn't valid +=========== +*/ +void idVertexCache::PurgeAll() { + while ( staticHeaders.next != &staticHeaders ) { + ActuallyFree( staticHeaders.next ); + } +} + +/* +=========== +idVertexCache::Shutdown +=========== +*/ +void idVertexCache::Shutdown() { + headerAllocator.Shutdown(); +} + +/* +=========== +idVertexCache::Alloc +=========== +*/ +void idVertexCache::Alloc( void *data, int size, vertCache_t **buffer, bool doIndex ) { + vertCache_t *block = NULL; + + if ( size <= 0 ) { + common->Error( "idVertexCache::Alloc: size = %i\n", size ); + } + + // if we can't find anything, it will be NULL + *buffer = NULL; + + // if we don't have any remaining unused headers, allocate some more + if ( freeStaticHeaders.next == &freeStaticHeaders ) { + for ( int i = 0; i < EXPAND_HEADERS; i++ ) { + block = headerAllocator.Alloc(); + qglGenBuffersARB( 1, &block->vbo ); + block->size = 0; + block->next = freeStaticHeaders.next; + block->prev = &freeStaticHeaders; + block->next->prev = block; + block->prev->next = block; + } + } + GLenum target = ( doIndex ? GL_ELEMENT_ARRAY_BUFFER : GL_ARRAY_BUFFER ); + GLenum usage = ( allocatingTempBuffer ? GL_STREAM_DRAW : GL_STATIC_DRAW ); + + // try to find a matching block to replace so that we're not continually respecifying vbo data each frame + for ( vertCache_t *findblock = freeStaticHeaders.next; /**/; findblock = findblock->next ) { + if ( findblock == &freeStaticHeaders ) { + block = freeStaticHeaders.next; + break; + } + + if ( findblock->target != target ) { + continue; + } + + if ( findblock->usage != usage ) { + continue; + } + + if ( findblock->size != size ) { + continue; + } + block = findblock; + break; + } + + // move it from the freeStaticHeaders list to the staticHeaders list + block->target = target; + block->usage = usage; + + if ( block->vbo ) { + // orphan the buffer in case it needs respecifying (it usually will) + BindIndex( target, block->vbo ); + qglBufferDataARB( target, static_cast( size ), NULL, usage ); + qglBufferDataARB( target, static_cast( size ), data, usage ); + } + block->next->prev = block->prev; + block->prev->next = block->next; + block->next = staticHeaders.next; + block->prev = &staticHeaders; + block->next->prev = block; + block->prev->next = block; + block->size = size; + block->offset = 0; + block->tag = TAG_USED; + + // save data for debugging + staticAllocThisFrame += block->size; + staticCountThisFrame++; + staticCountTotal++; + staticAllocTotal += block->size; + + // this will be set to zero when it is purged + block->user = buffer; + *buffer = block; + + // allocation doesn't imply used-for-drawing, because at level + // load time lots of things may be created, but they aren't + // referenced by the GPU yet, and can be purged if needed. + block->frameUsed = currentFrame - NUM_VERTEX_FRAMES; + block->indexBuffer = doIndex; +} + +/* +=========== +idVertexCache::Touch +=========== +*/ +void idVertexCache::Touch( vertCache_t *block ) { + if ( !block ) { + common->Error( "idVertexCache Touch: NULL pointer" ); + } + + if ( block->tag == TAG_FREE ) { + common->FatalError( "idVertexCache Touch: freed pointer" ); + } + + if ( block->tag == TAG_TEMP ) { + common->FatalError( "idVertexCache Touch: temporary pointer" ); + } + block->frameUsed = currentFrame; + + // move to the head of the LRU list + block->next->prev = block->prev; + block->prev->next = block->next; + block->next = staticHeaders.next; + block->prev = &staticHeaders; + + staticHeaders.next->prev = block; + staticHeaders.next = block; +} + +/* +=========== +idVertexCache::Free +=========== +*/ +void idVertexCache::Free( vertCache_t *block ) { + if ( !block ) { + return; + } + + if ( block->tag == TAG_FREE ) { + common->FatalError( "idVertexCache Free: freed pointer" ); + } + + if ( block->tag == TAG_TEMP ) { + common->FatalError( "idVertexCache Free: temporary pointer" ); + } + + // this block still can't be purged until the frame count has expired, + // but it won't need to clear a user pointer when it is + block->user = NULL; + block->next->prev = block->prev; + block->prev->next = block->next; + block->next = deferredFreeList.next; + block->prev = &deferredFreeList; + + deferredFreeList.next->prev = block; + deferredFreeList.next = block; +} + +/* +=========== +idVertexCache::AllocFrameTemp + +A frame temp allocation must never be allowed to fail due to overflow. +We can't simply sync with the GPU and overwrite what we have, because +there may still be future references to dynamically created surfaces. +=========== +*/ +vertCache_t *idVertexCache::AllocFrameTemp( void *data, int size ) { + vertCache_t *block = NULL; + + if ( size <= 0 ) { + common->Error( "idVertexCache::AllocFrameTemp: size = %i\n", size ); + } + + if ( dynamicAllocThisFrame + size > frameBytes ) { + // if we don't have enough room in the temp block, allocate a static block, + // but immediately free it so it will get freed at the next frame + tempOverflow = true; + Alloc( data, size, &block ); + Free( block ); + return block; + } + + // this data is just going on the shared dynamic list + // if we don't have any remaining unused headers, allocate some more + if ( freeDynamicHeaders.next == &freeDynamicHeaders ) { + for ( int i = 0; i < EXPAND_HEADERS; i++ ) { + block = headerAllocator.Alloc(); + block->next = freeDynamicHeaders.next; + block->prev = &freeDynamicHeaders; + block->next->prev = block; + block->prev->next = block; + } + } + + // move it from the freeDynamicHeaders list to the dynamicHeaders list + block = freeDynamicHeaders.next; + block->next->prev = block->prev; + block->prev->next = block->next; + block->next = dynamicHeaders.next; + block->prev = &dynamicHeaders; + block->next->prev = block; + block->prev->next = block; + block->size = size; + block->tag = TAG_TEMP; + block->indexBuffer = false; + block->offset = dynamicAllocThisFrame; + + dynamicAllocThisFrame += block->size; + dynamicCountThisFrame++; + + block->user = NULL; + block->frameUsed = 0; + + // copy the data + block->vbo = tempBuffers[listNum]->vbo; + + // mh code start + if ( block->vbo ) { + BindIndex( GL_ARRAY_BUFFER, block->vbo ); + + // try to get an unsynchronized map if at all possible + if ( r_useArbBufferRange.GetBool() && glConfig.ARBMapBufferRangeAvailable ) { + GLbitfield access = ( GL_MAP_WRITE_BIT | ( ( block->offset == 0 ) ? GL_MAP_INVALIDATE_BUFFER_BIT : GL_MAP_UNSYNCHRONIZED_BIT | GL_MAP_INVALIDATE_RANGE_BIT ) ); + GLvoid *dst = qglMapBufferRange( GL_ARRAY_BUFFER, block->offset, static_cast( size ), access ); + + // if the buffer has wrapped then we orphan it + if ( dst ) { + SIMDProcessor->Memcpy( reinterpret_cast( dst ), data, size ); + qglUnmapBufferARB( GL_ARRAY_BUFFER ); + return block; + } else { + qglBufferSubDataARB( GL_ARRAY_BUFFER, block->offset, static_cast( size ), data ); + } + } else { + qglBufferSubDataARB( GL_ARRAY_BUFFER, block->offset, static_cast( size ), data ); + } + } + return block; +} + +/* +=========== +idVertexCache::EndFrame +=========== +*/ +void idVertexCache::EndFrame() { + // display debug information + if ( r_showVertexCache.GetBool() == 1 ) { + int staticUseCount = 0; + int staticUseSize = 0; + + for ( vertCache_t *block = staticHeaders.next; block != &staticHeaders; block = block->next ) { + if ( block->frameUsed == currentFrame ) { + staticUseCount++; + staticUseSize += block->size; + } + } + const char *frameOverflow = tempOverflow ? "(OVERFLOW)" : ""; + common->Printf( "vertex dynamic:%i=%ik%s, static alloc:%i=%ik used:%i=%ik total:%i=%ik\n", dynamicCountThisFrame, + dynamicAllocThisFrame / 1024, frameOverflow, staticCountThisFrame, staticAllocThisFrame / 1024, + staticUseCount, staticUseSize / 1024, staticCountTotal, staticAllocTotal / 1024 ); + } + + // unbind vertex buffers + UnbindIndex( GL_ARRAY_BUFFER_ARB ); + UnbindIndex( GL_ELEMENT_ARRAY_BUFFER_ARB ); + + currentFrame = tr.frameCount; + listNum = currentFrame % NUM_VERTEX_FRAMES; + staticAllocThisFrame = 0; + staticCountThisFrame = 0; + dynamicAllocThisFrame = 0; + dynamicCountThisFrame = 0; + tempOverflow = false; + + // free all the deferred free headers + while ( deferredFreeList.next != &deferredFreeList ) { + ActuallyFree( deferredFreeList.next ); + } + + // free all the frame temp headers + vertCache_t *block = dynamicHeaders.next; + + if ( block != &dynamicHeaders ) { + block->prev = &freeDynamicHeaders; + dynamicHeaders.prev->next = freeDynamicHeaders.next; + freeDynamicHeaders.next->prev = dynamicHeaders.prev; + freeDynamicHeaders.next = block; + dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders; + } +} + +/* +============= +idVertexCache::List +============= +*/ +void idVertexCache::List( void ) { + int numActive = 0; + int frameStatic = 0; + int totalStatic = 0; + vertCache_t *block = NULL; + + for ( block = staticHeaders.next; block != &staticHeaders; block = block->next ) { + numActive++; + totalStatic += block->size; + + if ( block->frameUsed == currentFrame ) { + frameStatic += block->size; + } + } + int numFreeStaticHeaders = 0; + + for ( block = freeStaticHeaders.next; block != &freeStaticHeaders; block = block->next ) { + numFreeStaticHeaders++; + } + int numFreeDynamicHeaders = 0; + + for ( block = freeDynamicHeaders.next; block != &freeDynamicHeaders; block = block->next ) { + numFreeDynamicHeaders++; + } + common->Printf( "%i dynamic temp buffers of %ik\n", NUM_VERTEX_FRAMES, frameBytes / 1024 ); + common->Printf( "%5i active static headers\n", numActive ); + common->Printf( "%5i free static headers\n", numFreeStaticHeaders ); + common->Printf( "%5i free dynamic headers\n", numFreeDynamicHeaders ); +} + +/* +============= +idVertexCache::Show + +Barnes, +replaces the broken glconfig string version. +Real performance killer here, use with care. +============= +*/ +void idVertexCache::Show( void ) { + GLint mem[4]; + + if ( glConfig.vendor == glvAMD ) { + common->Printf( "\nATI/AMD specific memory info:\n" ); + common->Printf( "\n" ); + qglGetIntegerv( GL_VBO_FREE_MEMORY_ATI, mem ); + common->Printf( "VBO: total memory free in the pool %i MB\n", mem[0] >> 10 ); + common->Printf( "VBO: largest available free block in the pool %i MB\n", mem[1] >> 10 ); + common->Printf( "VBO: total auxiliary memory free %i MB\n", mem[2] >> 10 ); + common->Printf( "VBO: largest auxiliary free block %i MB\n", mem[3] >> 10 ); + qglGetIntegerv( GL_TEXTURE_FREE_MEMORY_ATI, mem ); + common->Printf( "Texture: total memory free in the pool %i MB\n", mem[0] >> 10 ); + common->Printf( "Texture: largest available free block in the pool %i MB\n", mem[1] >> 10 ); + common->Printf( "Texture: total auxiliary memory free %i MB\n", mem[2] >> 10 ); + common->Printf( "Texture: largest auxiliary free block %i MB\n", mem[3] >> 10 ); + qglGetIntegerv( GL_RENDERBUFFER_FREE_MEMORY_ATI, mem ); + common->Printf( "RenderBuffer: total memory free in the pool %i MB\n", mem[0] >> 10 ); + common->Printf( "RenderBuffer: largest available free block in the pool %i MB\n", mem[1] >> 10 ); + common->Printf( "RenderBuffer: total auxiliary memory free %i MB\n", mem[2] >> 10 ); + common->Printf( "RenderBuffer: largest auxiliary free block %i MB\n", mem[3] >> 10 ); + } else if ( glConfig.vendor == glvNVIDIA ) { + common->Printf( "\nNvidia specific memory info:\n" ); + common->Printf( "\n" ); + qglGetIntegerv( GL_GPU_MEMORY_INFO_DEDICATED_VIDMEM_NVX, mem ); + common->Printf( "dedicated video memory %i MB\n", mem[0] >> 10 ); + qglGetIntegerv( GL_GPU_MEMORY_INFO_TOTAL_AVAILABLE_MEMORY_NVX, mem ); + common->Printf( "total available memory %i MB\n", mem[0] >> 10 ); + qglGetIntegerv( GL_GPU_MEMORY_INFO_CURRENT_AVAILABLE_VIDMEM_NVX, mem ); + common->Printf( "currently unused GPU memory %i MB\n", mem[0] >> 10 ); + qglGetIntegerv( GL_GPU_MEMORY_INFO_EVICTION_COUNT_NVX, mem ); + common->Printf( "count of total evictions seen by system %i MB\n", mem[0] >> 10 ); + qglGetIntegerv( GL_GPU_MEMORY_INFO_EVICTED_MEMORY_NVX, mem ); + common->Printf( "total video memory evicted %i MB\n", mem[0] >> 10 ); + } else if ( glConfig.vendor == glvIntel ) { + common->Printf( "\nIntel cannot do memory info, sorry:\n" ); + } else { + common->Printf( "\nUnsupported card cannot do memory info, sorry:\n" ); + } +} diff --git a/neo/renderer/VertexCache.h b/neo/renderer/VertexCache.h index a1d0a0d56..0a65dfa65 100644 --- a/neo/renderer/VertexCache.h +++ b/neo/renderer/VertexCache.h @@ -1,146 +1,151 @@ -/* -=========================================================================== - -Doom 3 GPL Source Code -Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. - -This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). - -Doom 3 Source Code is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. - -Doom 3 Source Code is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. - -You should have received a copy of the GNU General Public License -along with Doom 3 Source Code. If not, see . - -In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of these additional terms immediately following the terms and conditions of the GNU General Public License which accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address below. - -If you have questions concerning this license or the applicable additional terms, you may contact in writing id Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. - -=========================================================================== -*/ - -#include "framework/CVarSystem.h" -#include "renderer/qgl.h" - -// vertex cache calls should only be made by the front end - -const int NUM_VERTEX_FRAMES = 2; - -typedef enum { - TAG_FREE, - TAG_USED, - TAG_FIXED, // for the temp buffers - TAG_TEMP // in frame temp area, not static area -} vertBlockTag_t; - -typedef struct vertCache_s { - GLuint vbo; - void *virtMem; // only one of vbo / virtMem will be set - bool indexBuffer; // holds indexes instead of vertexes - - intptr_t offset; - int size; // may be larger than the amount asked for, due - // to round up and minimum fragment sizes - int tag; // a tag of 0 is a free block - struct vertCache_s ** user; // will be set to zero when purged - struct vertCache_s *next, *prev; // may be on the static list or one of the frame lists - int frameUsed; // it can't be purged if near the current frame -} vertCache_t; - - -class idVertexCache { -public: - void Init(); - void Shutdown(); - - // just for gfxinfo printing - bool IsFast(); - - // called when vertex programs are enabled or disabled, because - // the cached data is no longer valid - void PurgeAll(); - - // Tries to allocate space for the given data in fast vertex - // memory, and copies it over. - // Alloc does NOT do a touch, which allows purging of things - // created at level load time even if a frame hasn't passed yet. - // These allocations can be purged, which will zero the pointer. - void Alloc( void *data, int bytes, vertCache_t **buffer, bool indexBuffer = false ); - - // This will be a real pointer with virtual memory, - // but it will be an int offset cast to a pointer of ARB_vertex_buffer_object - void * Position( vertCache_t *buffer ); - - // if r_useIndexBuffers is enabled, but you need to draw something without - // an indexCache, this must be called to reset GL_ELEMENT_ARRAY_BUFFER_ARB - void UnbindIndex(); - - // automatically freed at the end of the next frame - // used for specular texture coordinates and gui drawing, which - // will change every frame. - // will return NULL if the vertex cache is completely full - // As with Position(), this may not actually be a pointer you can access. - vertCache_t * AllocFrameTemp( void *data, int bytes ); - - // notes that a buffer is used this frame, so it can't be purged - // out from under the GPU - void Touch( vertCache_t *buffer ); - - // this block won't have to zero a buffer pointer when it is purged, - // but it must still wait for the frames to pass, in case the GPU - // is still referencing it - void Free( vertCache_t *buffer ); - - // updates the counter for determining which temp space to use - // and which blocks can be purged - // Also prints debugging info when enabled - void EndFrame(); - - // listVertexCache calls this - void List(); - -private: - void InitMemoryBlocks( int size ); - void ActuallyFree( vertCache_t *block ); - - static idCVar r_showVertexCache; - static idCVar r_vertexBufferMegs; - - int staticCountTotal; - int staticAllocTotal; // for end of frame purging - - int staticAllocThisFrame; // debug counter - int staticCountThisFrame; - int dynamicAllocThisFrame; - int dynamicCountThisFrame; - - int currentFrame; // for purgable block tracking - int listNum; // currentFrame % NUM_VERTEX_FRAMES, determines which tempBuffers to use - - bool virtualMemory; // not fast stuff - - bool allocatingTempBuffer; // force GL_STREAM_DRAW_ARB - - vertCache_t *tempBuffers[NUM_VERTEX_FRAMES]; // allocated at startup - bool tempOverflow; // had to alloc a temp in static memory - - idBlockAlloc headerAllocator; - - vertCache_t freeStaticHeaders; // head of doubly linked list - vertCache_t freeDynamicHeaders; // head of doubly linked list - vertCache_t dynamicHeaders; // head of doubly linked list - vertCache_t deferredFreeList; // head of doubly linked list - vertCache_t staticHeaders; // head of doubly linked list in MRU order, - // staticHeaders.next is most recently used - - int frameBytes; // for each of NUM_VERTEX_FRAMES frames -}; - -extern idVertexCache vertexCache; +/* +=========================================================================== + +Doom 3 GPL Source Code +Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. + +This file is part of the Doom 3 GPL Source Code ("Doom 3 Source Code"). + +Doom 3 Source Code is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +Doom 3 Source Code is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with Doom 3 Source Code. If not, see . + +In addition, the Doom 3 Source Code is also subject to certain additional terms. You should have received a copy of +these additional terms immediately following the terms and conditions of the GNU General Public License which +accompanied the Doom 3 Source Code. If not, please request a copy in writing from id Software at the address +below. + +If you have questions concerning this license or the applicable additional terms, you may contact in writing id +Software LLC, c/o ZeniMax Media Inc., Suite 120, Rockville, Maryland 20850 USA. + +=========================================================================== +*/ + +#include "framework/CVarSystem.h" +#include "renderer/qgl.h" + +// vertex cache calls should only be made by the front end +const int NUM_VERTEX_FRAMES = 2; + +typedef enum { + TAG_FREE, + TAG_USED, + TAG_FIXED, // for the temp buffers + TAG_TEMP // in frame temp area, not static area +} vertBlockTag_t; + +typedef struct vertCache_s { + GLuint vbo; + GLenum target; + GLenum usage; + bool indexBuffer; // holds indexes instead of vertexes + + intptr_t offset; + int size; // may be larger than the amount asked for, due + // to round up and minimum fragment sizes + int tag; // a tag of 0 is a free block + struct vertCache_s **user; // will be set to zero when purged + struct vertCache_s *next, *prev; // may be on the static list or one of the frame lists + int frameUsed; // it can't be purged if near the current frame +} vertCache_t; + +class idVertexCache { +public: + void Init(); + void Shutdown(); + + // called when vertex programs are enabled or disabled, because + // the cached data is no longer valid + void PurgeAll(); + + // Tries to allocate space for the given data in fast vertex + // memory, and copies it over. + // Alloc does NOT do a touch, which allows purging of things + // created at level load time even if a frame hasn't passed yet. + // These allocations can be purged, which will zero the pointer. + void Alloc( void *data, int bytes, vertCache_t **buffer, bool indexBuffer = false ); + + // This will be a real pointer with virtual memory, + // but it will be an int offset cast to a pointer of ARB_vertex_buffer_object + const void *Position( vertCache_t *buffer ); + + // initialize the element array buffers + void BindIndex( GLenum target, GLuint vbo ); + + // if you need to draw something without an indexCache, + // this must be called to reset GL_ELEMENT_ARRAY_BUFFER_ARB + void UnbindIndex( GLenum target ); + + // automatically freed at the end of the next frame + // used for specular texture coordinates and gui drawing, which + // will change every frame. + // will return NULL if the vertex cache is completely full + // As with Position(), this may not actually be a pointer you can access. + vertCache_t *AllocFrameTemp( void *data, int bytes ); + + // notes that a buffer is used this frame, so it can't be purged + // out from under the GPU + void Touch( vertCache_t *buffer ); + + // this block won't have to zero a buffer pointer when it is purged, + // but it must still wait for the frames to pass, in case the GPU + // is still referencing it + void Free( vertCache_t *buffer ); + + // updates the counter for determining which temp space to use + // and which blocks can be purged + // Also prints debugging info when enabled + void EndFrame(); + + // listVBOMem calls this + void List(); + + // showVBOMem calls this + void Show(); + +private: + void ActuallyFree( vertCache_t *block ); + + static idCVar r_showVertexCache; + static idCVar r_useArbBufferRange; + static idCVar r_reuseVertexCacheSooner; + + GLuint vertexBuffer; // pointer into vertexbuffer + GLuint indexBuffer; // pointer into indexbuffer + + int staticCountTotal; + int staticAllocTotal; // for end of frame purging + + int staticAllocThisFrame; // debug counter + int staticCountThisFrame; + int dynamicAllocThisFrame; + int dynamicCountThisFrame; + + int currentFrame; // for purgable block tracking + int listNum; // currentFrame % NUM_VERTEX_FRAMES, determines which tempBuffers to use + + bool allocatingTempBuffer; // force GL_STREAM_DRAW_ARB + + vertCache_t *tempBuffers[NUM_VERTEX_FRAMES]; // allocated at startup + bool tempOverflow; // had to alloc a temp in static memory + + idBlockAlloc headerAllocator; + + vertCache_t freeStaticHeaders; // head of doubly linked list + vertCache_t freeDynamicHeaders; // head of doubly linked list + vertCache_t dynamicHeaders; // head of doubly linked list + vertCache_t deferredFreeList; // head of doubly linked list + vertCache_t staticHeaders; // head of doubly linked list in MRU order, staticHeaders.next is most recently used + int frameBytes; // for each of NUM_VERTEX_FRAMES frames +}; + +extern idVertexCache vertexCache; diff --git a/neo/renderer/tr_render.cpp b/neo/renderer/tr_render.cpp index 2b7e10fce..2be64a75e 100644 --- a/neo/renderer/tr_render.cpp +++ b/neo/renderer/tr_render.cpp @@ -61,8 +61,8 @@ void RB_DrawElementsImmediate( const srfTriangles_t *tri ) { backEnd.pc.c_drawRefVertexes += tri->numVerts; } } - qglBegin( GL_TRIANGLES ); + for ( int i = 0 ; i < tri->numIndexes ; i++ ) { qglTexCoord2fv( tri->verts[ tri->indexes[i] ].st.ToFloatPtr() ); qglVertex3fv( tri->verts[ tri->indexes[i] ].xyz.ToFloatPtr() ); @@ -86,20 +86,22 @@ void RB_DrawElementsWithCounters( const srfTriangles_t *tri ) { if ( tri->indexes == tri->ambientSurface->indexes ) { backEnd.pc.c_drawRefIndexes += tri->numIndexes; } + if ( tri->verts == tri->ambientSurface->verts ) { backEnd.pc.c_drawRefVertexes += tri->numVerts; } } if ( tri->indexCache && r_useIndexBuffers.GetBool() ) { + // cast to wrong type int * but needs const GLvoid * the type however is const void * so just remove the cast qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : tri->numIndexes, GL_INDEX_TYPE, - (int *)vertexCache.Position( tri->indexCache ) ); + vertexCache.Position( tri->indexCache ) ); backEnd.pc.c_vboIndexes += tri->numIndexes; } else { if ( r_useIndexBuffers.GetBool() ) { - vertexCache.UnbindIndex(); + vertexCache.UnbindIndex( GL_ELEMENT_ARRAY_BUFFER ); } qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : tri->numIndexes, @@ -128,7 +130,7 @@ void RB_DrawShadowElementsWithCounters( const srfTriangles_t *tri, int numIndexe backEnd.pc.c_vboIndexes += numIndexes; } else { if ( r_useIndexBuffers.GetBool() ) { - vertexCache.UnbindIndex(); + vertexCache.UnbindIndex( GL_ELEMENT_ARRAY_BUFFER ); } qglDrawElements( GL_TRIANGLES, r_singleTriangle.GetBool() ? 3 : numIndexes, @@ -137,7 +139,6 @@ void RB_DrawShadowElementsWithCounters( const srfTriangles_t *tri, int numIndexe } } - /* =============== RB_RenderTriangleSurface @@ -150,9 +151,8 @@ void RB_RenderTriangleSurface( const srfTriangles_t *tri ) { RB_DrawElementsImmediate( tri ); return; } + idDrawVert *ac = ( idDrawVert * )vertexCache.Position( tri->ambientCache ); - - idDrawVert *ac = (idDrawVert *)vertexCache.Position( tri->ambientCache ); qglVertexPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ac->xyz.ToFloatPtr() ); qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), ac->st.ToFloatPtr() ); @@ -232,8 +232,8 @@ be updated after the triangle function completes. */ void RB_RenderDrawSurfListWithFunction( drawSurf_t **drawSurfs, int numDrawSurfs, void (*triFunc_)( const drawSurf_t *) ) { - int i; - const drawSurf_t *drawSurf; + int i; + const drawSurf_t *drawSurf; backEnd.currentSpace = NULL; @@ -256,10 +256,11 @@ void RB_RenderDrawSurfListWithFunction( drawSurf_t **drawSurfs, int numDrawSurfs // change the scissor if needed if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( drawSurf->scissorRect ) ) { backEnd.currentScissor = drawSurf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, - backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, - backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, - backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); } // render it @@ -268,7 +269,6 @@ void RB_RenderDrawSurfListWithFunction( drawSurf_t **drawSurfs, int numDrawSurfs if ( drawSurf->space->weaponDepthHack || drawSurf->space->modelDepthHack != 0.0f ) { RB_LeaveDepthHack(); } - backEnd.currentSpace = drawSurf->space; } } @@ -279,8 +279,8 @@ RB_RenderDrawSurfChainWithFunction ====================== */ void RB_RenderDrawSurfChainWithFunction( const drawSurf_t *drawSurfs, - void (*triFunc_)( const drawSurf_t *) ) { - const drawSurf_t *drawSurf; + void ( *triFunc_ )( const drawSurf_t *) ) { + const drawSurf_t *drawSurf; backEnd.currentSpace = NULL; @@ -301,10 +301,11 @@ void RB_RenderDrawSurfChainWithFunction( const drawSurf_t *drawSurfs, // change the scissor if needed if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( drawSurf->scissorRect ) ) { backEnd.currentScissor = drawSurf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, - backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, - backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, - backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); } // render it @@ -313,7 +314,6 @@ void RB_RenderDrawSurfChainWithFunction( const drawSurf_t *drawSurfs, if ( drawSurf->space->weaponDepthHack || drawSurf->space->modelDepthHack != 0.0f ) { RB_LeaveDepthHack(); } - backEnd.currentSpace = drawSurf->space; } } @@ -324,7 +324,7 @@ RB_GetShaderTextureMatrix ====================== */ void RB_GetShaderTextureMatrix( const float *shaderRegisters, - const textureStage_t *texture, float matrix[16] ) { + const textureStage_t *texture, float matrix[16] ) { matrix[0] = shaderRegisters[ texture->matrix[0][0] ]; matrix[4] = shaderRegisters[ texture->matrix[0][1] ]; matrix[8] = 0; @@ -335,15 +335,14 @@ void RB_GetShaderTextureMatrix( const float *shaderRegisters, if ( matrix[12] < -40 || matrix[12] > 40 ) { matrix[12] -= (int)matrix[12]; } - matrix[1] = shaderRegisters[ texture->matrix[1][0] ]; matrix[5] = shaderRegisters[ texture->matrix[1][1] ]; matrix[9] = 0; matrix[13] = shaderRegisters[ texture->matrix[1][2] ]; + if ( matrix[13] < -40 || matrix[13] > 40 ) { matrix[13] -= (int)matrix[13]; } - matrix[2] = 0; matrix[6] = 0; matrix[10] = 1; @@ -364,6 +363,7 @@ void RB_LoadShaderTextureMatrix( const float *shaderRegisters, const textureStag float matrix[16]; RB_GetShaderTextureMatrix( shaderRegisters, texture, matrix ); + qglMatrixMode( GL_TEXTURE ); qglLoadMatrixf( matrix ); qglMatrixMode( GL_MODELVIEW ); @@ -397,91 +397,14 @@ void RB_BindVariableStageImage( const textureStage_t *texture, const float *shad } } else { //FIXME: see why image is invalid - if (texture->image) { + if ( texture->image ) { texture->image->Bind(); } } } -/* -====================== -RB_BindStageTexture -====================== -*/ -void RB_BindStageTexture( const float *shaderRegisters, const textureStage_t *texture, const drawSurf_t *surf ) { - // image - RB_BindVariableStageImage( texture, shaderRegisters ); - - // texgens - if ( texture->texgen == TG_DIFFUSE_CUBE ) { - qglTexCoordPointer( 3, GL_FLOAT, sizeof( idDrawVert ), ((idDrawVert *)vertexCache.Position( surf->geo->ambientCache ))->normal.ToFloatPtr() ); - } - if ( texture->texgen == TG_SKYBOX_CUBE || texture->texgen == TG_WOBBLESKY_CUBE ) { - qglTexCoordPointer( 3, GL_FLOAT, 0, vertexCache.Position( surf->dynamicTexCoords ) ); - } - if ( texture->texgen == TG_REFLECT_CUBE ) { - qglEnable( GL_TEXTURE_GEN_S ); - qglEnable( GL_TEXTURE_GEN_T ); - qglEnable( GL_TEXTURE_GEN_R ); - qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); - qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); - qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_REFLECTION_MAP_EXT ); - qglEnableClientState( GL_NORMAL_ARRAY ); - qglNormalPointer( GL_FLOAT, sizeof( idDrawVert ), ((idDrawVert *)vertexCache.Position( surf->geo->ambientCache ))->normal.ToFloatPtr() ); - - qglMatrixMode( GL_TEXTURE ); - float mat[16]; - - R_TransposeGLMatrix( backEnd.viewDef->worldSpace.modelViewMatrix, mat ); - - qglLoadMatrixf( mat ); - qglMatrixMode( GL_MODELVIEW ); - } - - // matrix - if ( texture->hasMatrix ) { - RB_LoadShaderTextureMatrix( shaderRegisters, texture ); - } -} - -/* -====================== -RB_FinishStageTexture -====================== -*/ -void RB_FinishStageTexture( const textureStage_t *texture, const drawSurf_t *surf ) { - if ( texture->texgen == TG_DIFFUSE_CUBE || texture->texgen == TG_SKYBOX_CUBE - || texture->texgen == TG_WOBBLESKY_CUBE ) { - qglTexCoordPointer( 2, GL_FLOAT, sizeof( idDrawVert ), - (void *)&(((idDrawVert *)vertexCache.Position( surf->geo->ambientCache ))->st) ); - } - - if ( texture->texgen == TG_REFLECT_CUBE ) { - qglDisable( GL_TEXTURE_GEN_S ); - qglDisable( GL_TEXTURE_GEN_T ); - qglDisable( GL_TEXTURE_GEN_R ); - qglTexGenf( GL_S, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); - qglTexGenf( GL_T, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); - qglTexGenf( GL_R, GL_TEXTURE_GEN_MODE, GL_OBJECT_LINEAR ); - qglDisableClientState( GL_NORMAL_ARRAY ); - - qglMatrixMode( GL_TEXTURE ); - qglLoadIdentity(); - qglMatrixMode( GL_MODELVIEW ); - } - - if ( texture->hasMatrix ) { - qglMatrixMode( GL_TEXTURE ); - qglLoadIdentity(); - qglMatrixMode( GL_MODELVIEW ); - } -} - - - //============================================================================================= - /* ================= RB_DetermineLightScale @@ -511,30 +434,33 @@ void RB_DetermineLightScale( void ) { // if there are no lights, this will remain at 1.0, so GUI-only // rendering will not lose any bits of precision - max = 1.0; + max = 1.0f; for ( vLight = backEnd.viewDef->viewLights ; vLight ; vLight = vLight->next ) { // lights with no surfaces or shaderparms may still be present // for debug display - if ( !vLight->localInteractions && !vLight->globalInteractions - && !vLight->translucentInteractions ) { + if ( !vLight->localInteractions && + !vLight->globalInteractions && + !vLight->translucentInteractions ) { continue; } - shader = vLight->lightShader; numStages = shader->GetNumStages(); + for ( i = 0 ; i < numStages ; i++ ) { stage = shader->GetStage( i ); + for ( j = 0 ; j < 3 ; j++ ) { float v = r_lightScale.GetFloat() * vLight->shaderRegisters[ stage->color.registers[j] ]; + if ( v > max ) { max = v; } } } } - backEnd.pc.maxLightValue = max; + if ( max <= tr.backEndRendererMaxLight ) { backEnd.lightScale = r_lightScale.GetFloat(); backEnd.overBright = 1.0; @@ -553,8 +479,7 @@ Any mirrored or portaled views have already been drawn, so prepare to actually render the visible surfaces for this view ================= */ -void RB_BeginDrawingView (void) { - +void RB_BeginDrawingView ( void ) { const viewDef_t* viewDef = backEnd.viewDef; // set the modelview matrix for the viewer @@ -564,15 +489,16 @@ void RB_BeginDrawingView (void) { // set the window clipping qglViewport( tr.viewportOffset[0] + viewDef->viewport.x1, - tr.viewportOffset[1] + viewDef->viewport.y1, - viewDef->viewport.x2 + 1 - viewDef->viewport.x1, - viewDef->viewport.y2 + 1 - viewDef->viewport.y1 ); + tr.viewportOffset[1] + viewDef->viewport.y1, + viewDef->viewport.x2 + 1 - viewDef->viewport.x1, + viewDef->viewport.y2 + 1 - viewDef->viewport.y1 ); // the scissor may be smaller than the viewport for subviews qglScissor( tr.viewportOffset[0] + viewDef->viewport.x1 + viewDef->scissor.x1, - tr.viewportOffset[1] + viewDef->viewport.y1 + viewDef->scissor.y1, - viewDef->scissor.x2 + 1 - viewDef->scissor.x1, - viewDef->scissor.y2 + 1 - viewDef->scissor.y1 ); + tr.viewportOffset[1] + viewDef->viewport.y1 + viewDef->scissor.y1, + viewDef->scissor.x2 + 1 - viewDef->scissor.x1, + viewDef->scissor.y2 + 1 - viewDef->scissor.y1 ); + backEnd.currentScissor = viewDef->scissor; // ensures that depth writes are enabled for the depth clear @@ -590,10 +516,9 @@ void RB_BeginDrawingView (void) { qglDisable( GL_DEPTH_TEST ); qglDisable( GL_STENCIL_TEST ); } - backEnd.glState.faceCulling = -1; // force face culling to set next time - GL_Cull( CT_FRONT_SIDED ); + GL_Cull( CT_FRONT_SIDED ); } /* @@ -604,6 +529,7 @@ R_SetDrawInteractions void R_SetDrawInteraction( const shaderStage_t *surfaceStage, const float *surfaceRegs, idImage **image, idVec4 matrix[2], float color[4] ) { *image = surfaceStage->texture.image; + if ( surfaceStage->texture.hasMatrix ) { matrix[0][0] = surfaceRegs[surfaceStage->texture.matrix[0][0]]; matrix[0][1] = surfaceRegs[surfaceStage->texture.matrix[0][1]]; @@ -620,6 +546,7 @@ void R_SetDrawInteraction( const shaderStage_t *surfaceStage, const float *surfa if ( matrix[0][3] < -40 || matrix[0][3] > 40 ) { matrix[0][3] -= (int)matrix[0][3]; } + if ( matrix[1][3] < -40 || matrix[1][3] > 40 ) { matrix[1][3] -= (int)matrix[1][3]; } @@ -638,6 +565,7 @@ void R_SetDrawInteraction( const shaderStage_t *surfaceStage, const float *surfa if ( color ) { for ( int i = 0 ; i < 4 ; i++ ) { color[i] = surfaceRegs[surfaceStage->color.registers[i]]; + // clamp here, so card with greater range don't look different. // we could perform overbrighting like we do for lights, but // it doesn't currently look worth it. @@ -663,21 +591,24 @@ static void RB_SubmittInteraction( drawInteraction_t *din, void (*DrawInteractio if ( !din->diffuseImage || r_skipDiffuse.GetBool() ) { din->diffuseImage = globalImages->blackImage; } + if ( !din->specularImage || r_skipSpecular.GetBool() || din->ambientLight ) { din->specularImage = globalImages->blackImage; } + if ( !din->bumpImage || r_skipBump.GetBool() ) { din->bumpImage = globalImages->flatNormalMap; } // if we wouldn't draw anything, don't call the Draw function - if ( - ( ( din->diffuseColor[0] > 0 || - din->diffuseColor[1] > 0 || - din->diffuseColor[2] > 0 ) && din->diffuseImage != globalImages->blackImage ) - || ( ( din->specularColor[0] > 0 || - din->specularColor[1] > 0 || - din->specularColor[2] > 0 ) && din->specularImage != globalImages->blackImage ) ) { + if ( ( ( din->diffuseColor[0] > 0 || + din->diffuseColor[1] > 0 || + din->diffuseColor[2] > 0 ) && + din->diffuseImage != globalImages->blackImage ) || + ( ( din->specularColor[0] > 0 || + din->specularColor[1] > 0 || + din->specularColor[2] > 0 ) && + din->specularImage != globalImages->blackImage ) ) { DrawInteraction( din ); } } @@ -697,6 +628,7 @@ void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInterac const idMaterial *lightShader = vLight->lightShader; const float *lightRegs = vLight->shaderRegisters; drawInteraction_t inter; + inter.diffuseMatrix[0].Zero(); inter.diffuseMatrix[1].Zero(); inter.specularMatrix[0].Zero(); @@ -709,16 +641,18 @@ void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInterac // change the matrix and light projection vectors if needed if ( surf->space != backEnd.currentSpace ) { backEnd.currentSpace = surf->space; + qglLoadMatrixf( surf->space->modelViewMatrix ); } // change the scissor if needed if ( r_useScissor.GetBool() && !backEnd.currentScissor.Equals( surf->scissorRect ) ) { backEnd.currentScissor = surf->scissorRect; + qglScissor( backEnd.viewDef->viewport.x1 + backEnd.currentScissor.x1, - backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, - backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, - backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); + backEnd.viewDef->viewport.y1 + backEnd.currentScissor.y1, + backEnd.currentScissor.x2 + 1 - backEnd.currentScissor.x1, + backEnd.currentScissor.y2 + 1 - backEnd.currentScissor.y1 ); } // hack depth range if needed @@ -729,7 +663,6 @@ void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInterac if ( surf->space->modelDepthHack ) { RB_EnterModelDepthHack( surf->space->modelDepthHack ); } - inter.surf = surf; inter.lightFalloffImage = vLight->falloffImage; @@ -752,16 +685,15 @@ void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInterac if ( !lightRegs[ lightStage->conditionRegister ] ) { continue; } - inter.lightImage = lightStage->texture.image; memcpy( inter.lightProjection, lightProject, sizeof( inter.lightProjection ) ); + // now multiply the texgen by the light texture matrix if ( lightStage->texture.hasMatrix ) { RB_GetShaderTextureMatrix( lightRegs, &lightStage->texture, backEnd.lightTextureMatrix ); - RB_BakeTextureMatrixIntoTexgen( reinterpret_cast(inter.lightProjection), backEnd.lightTextureMatrix ); + RB_BakeTextureMatrixIntoTexgen( reinterpret_cast( inter.lightProjection ), backEnd.lightTextureMatrix ); } - inter.bumpImage = NULL; inter.specularImage = NULL; inter.diffuseImage = NULL; @@ -807,7 +739,7 @@ void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInterac RB_SubmittInteraction( &inter, DrawInteraction ); } R_SetDrawInteraction( surfaceStage, surfaceRegs, &inter.diffuseImage, - inter.diffuseMatrix, inter.diffuseColor.ToFloatPtr() ); + inter.diffuseMatrix, inter.diffuseColor.ToFloatPtr() ); inter.diffuseColor[0] *= lightColor[0]; inter.diffuseColor[1] *= lightColor[1]; inter.diffuseColor[2] *= lightColor[2]; @@ -824,7 +756,7 @@ void RB_CreateSingleDrawInteractions( const drawSurf_t *surf, void (*DrawInterac RB_SubmittInteraction( &inter, DrawInteraction ); } R_SetDrawInteraction( surfaceStage, surfaceRegs, &inter.specularImage, - inter.specularMatrix, inter.specularColor.ToFloatPtr() ); + inter.specularMatrix, inter.specularColor.ToFloatPtr() ); inter.specularColor[0] *= lightColor[0]; inter.specularColor[1] *= lightColor[1]; inter.specularColor[2] *= lightColor[2]; @@ -874,12 +806,9 @@ void RB_DrawView( const void *data ) { parms->connectedAreas = origParms.connectedAreas; for( viewEntity_t* vModel = parms->viewEntitys ; vModel ; vModel = vModel->next ) { - myGlMultMatrix( vModel->modelMatrix, - parms->worldSpace.modelViewMatrix, - vModel->modelViewMatrix ); + R_MatrixMultiply( vModel->modelMatrix, parms->worldSpace.modelViewMatrix, vModel->modelViewMatrix ); } } - backEnd.viewDef = cmd->viewDef; // we will need to do a new copyTexSubImage of the screen @@ -903,7 +832,6 @@ void RB_DrawView( const void *data ) { if ( r_skipRenderContext.GetBool() && backEnd.viewDef->viewEntitys ) { GLimp_DeactivateContext(); } - backEnd.pc.c_surfaces += backEnd.viewDef->numDrawSurfs; RB_ShowOverdraw();