123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564 |
- /*
- ===========================================================================
- 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 <http://www.gnu.org/licenses/>.
- 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 "../idlib/precompiled.h"
- #pragma hdrstop
- #include "tr_local.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 + %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 );
- }
- }
- 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 numDeferred = 0;
- int frameStatic = 0;
- int totalStatic = 0;
- int deferredSpace = 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;
- }
|