2 ===========================================================================
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company.
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).
9 Doom 3 Source Code is free software: you can redistribute it and/or modify
10 it under the terms of the GNU General Public License as published by
11 the Free Software Foundation, either version 3 of the License, or
12 (at your option) any later version.
14 Doom 3 Source Code is distributed in the hope that it will be useful,
15 but WITHOUT ANY WARRANTY; without even the implied warranty of
16 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 GNU General Public License for more details.
19 You should have received a copy of the GNU General Public License
20 along with Doom 3 Source Code. If not, see <http://www.gnu.org/licenses/>.
22 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.
24 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.
26 ===========================================================================
29 #include "../idlib/precompiled.h"
35 static const int FRAME_MEMORY_BYTES = 0x200000;
36 static const int EXPAND_HEADERS = 1024;
38 idCVar idVertexCache::r_showVertexCache( "r_showVertexCache", "0", CVAR_INTEGER|CVAR_RENDERER, "" );
39 idCVar idVertexCache::r_vertexBufferMegs( "r_vertexBufferMegs", "32", CVAR_INTEGER|CVAR_RENDERER, "" );
41 idVertexCache vertexCache;
48 static void R_ListVertexCache_f( const idCmdArgs &args ) {
54 idVertexCache::ActuallyFree
57 void idVertexCache::ActuallyFree( vertCache_t *block ) {
59 common->Error( "idVertexCache Free: NULL pointer" );
63 // let the owner know we have purged it
68 // temp blocks are in a shared space that won't be freed
69 if ( block->tag != TAG_TEMP ) {
70 staticAllocTotal -= block->size;
74 #if 0 // this isn't really necessary, it will be reused soon enough
75 // filling with zero length data is the equivalent of freeing
76 qglBindBufferARB(GL_ARRAY_BUFFER_ARB, block->vbo);
77 qglBufferDataARB(GL_ARRAY_BUFFER_ARB, 0, 0, GL_DYNAMIC_DRAW_ARB);
79 } else if ( block->virtMem ) {
80 Mem_Free( block->virtMem );
81 block->virtMem = NULL;
84 block->tag = TAG_FREE; // mark as free
86 // unlink stick it back on the free list
87 block->next->prev = block->prev;
88 block->prev->next = block->next;
91 // stick it on the front of the free list so it will be reused immediately
92 block->next = freeStaticHeaders.next;
93 block->prev = &freeStaticHeaders;
95 // stick it on the back of the free list so it won't be reused soon (just for debugging)
96 block->next = &freeStaticHeaders;
97 block->prev = freeStaticHeaders.prev;
100 block->next->prev = block;
101 block->prev->next = block;
106 idVertexCache::Position
108 this will be a real pointer with virtual memory,
109 but it will be an int offset cast to a pointer with
110 ARB_vertex_buffer_object
112 The ARB_vertex_buffer_object will be bound
115 void *idVertexCache::Position( vertCache_t *buffer ) {
116 if ( !buffer || buffer->tag == TAG_FREE ) {
117 common->FatalError( "idVertexCache::Position: bad vertCache_t" );
120 // the ARB vertex object just uses an offset
122 if ( r_showVertexCache.GetInteger() == 2 ) {
123 if ( buffer->tag == TAG_TEMP ) {
124 common->Printf( "GL_ARRAY_BUFFER_ARB = %i + %i (%i bytes)\n", buffer->vbo, buffer->offset, buffer->size );
126 common->Printf( "GL_ARRAY_BUFFER_ARB = %i (%i bytes)\n", buffer->vbo, buffer->size );
129 if ( buffer->indexBuffer ) {
130 qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, buffer->vbo );
132 qglBindBufferARB( GL_ARRAY_BUFFER_ARB, buffer->vbo );
134 return (void *)buffer->offset;
137 // virtual memory is a real pointer
138 return (void *)((byte *)buffer->virtMem + buffer->offset);
141 void idVertexCache::UnbindIndex() {
142 qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
146 //================================================================================
153 void idVertexCache::Init() {
154 cmdSystem->AddCommand( "listVertexCache", R_ListVertexCache_f, CMD_FL_RENDERER, "lists vertex cache" );
156 if ( r_vertexBufferMegs.GetInteger() < 8 ) {
157 r_vertexBufferMegs.SetInteger( 8 );
160 virtualMemory = false;
162 // use ARB_vertex_buffer_object unless explicitly disabled
163 if( r_useVertexBuffers.GetInteger() && glConfig.ARBVertexBufferObjectAvailable ) {
164 common->Printf( "using ARB_vertex_buffer_object memory\n" );
166 virtualMemory = true;
167 r_useIndexBuffers.SetBool( false );
168 common->Printf( "WARNING: vertex array range in virtual memory (SLOW)\n" );
171 // initialize the cache memory blocks
172 freeStaticHeaders.next = freeStaticHeaders.prev = &freeStaticHeaders;
173 staticHeaders.next = staticHeaders.prev = &staticHeaders;
174 freeDynamicHeaders.next = freeDynamicHeaders.prev = &freeDynamicHeaders;
175 dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders;
176 deferredFreeList.next = deferredFreeList.prev = &deferredFreeList;
178 // set up the dynamic frame memory
179 frameBytes = FRAME_MEMORY_BYTES;
180 staticAllocTotal = 0;
182 byte *junk = (byte *)Mem_Alloc( frameBytes );
183 for ( int i = 0 ; i < NUM_VERTEX_FRAMES ; i++ ) {
184 allocatingTempBuffer = true; // force the alloc to use GL_STREAM_DRAW_ARB
185 Alloc( junk, frameBytes, &tempBuffers[i] );
186 allocatingTempBuffer = false;
187 tempBuffers[i]->tag = TAG_FIXED;
188 // unlink these from the static list, so they won't ever get purged
189 tempBuffers[i]->next->prev = tempBuffers[i]->prev;
190 tempBuffers[i]->prev->next = tempBuffers[i]->next;
199 idVertexCache::PurgeAll
201 Used when toggling vertex programs on or off, because
202 the cached data isn't valid
205 void idVertexCache::PurgeAll() {
206 while( staticHeaders.next != &staticHeaders ) {
207 ActuallyFree( staticHeaders.next );
213 idVertexCache::Shutdown
216 void idVertexCache::Shutdown() {
217 // PurgeAll(); // !@#: also purge the temp buffers
219 headerAllocator.Shutdown();
227 void idVertexCache::Alloc( void *data, int size, vertCache_t **buffer, bool indexBuffer ) {
231 common->Error( "idVertexCache::Alloc: size = %i\n", size );
234 // if we can't find anything, it will be NULL
237 // if we don't have any remaining unused headers, allocate some more
238 if ( freeStaticHeaders.next == &freeStaticHeaders ) {
240 for ( int i = 0; i < EXPAND_HEADERS; i++ ) {
241 block = headerAllocator.Alloc();
242 block->next = freeStaticHeaders.next;
243 block->prev = &freeStaticHeaders;
244 block->next->prev = block;
245 block->prev->next = block;
247 if( !virtualMemory ) {
248 qglGenBuffersARB( 1, & block->vbo );
253 // move it from the freeStaticHeaders list to the staticHeaders list
254 block = freeStaticHeaders.next;
255 block->next->prev = block->prev;
256 block->prev->next = block->next;
257 block->next = staticHeaders.next;
258 block->prev = &staticHeaders;
259 block->next->prev = block;
260 block->prev->next = block;
264 block->tag = TAG_USED;
266 // save data for debugging
267 staticAllocThisFrame += block->size;
268 staticCountThisFrame++;
270 staticAllocTotal += block->size;
272 // this will be set to zero when it is purged
273 block->user = buffer;
276 // allocation doesn't imply used-for-drawing, because at level
277 // load time lots of things may be created, but they aren't
278 // referenced by the GPU yet, and can be purged if needed.
279 block->frameUsed = currentFrame - NUM_VERTEX_FRAMES;
281 block->indexBuffer = indexBuffer;
286 qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, block->vbo );
287 qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB );
289 qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo );
290 if ( allocatingTempBuffer ) {
291 qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STREAM_DRAW_ARB );
293 qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB );
297 block->virtMem = Mem_Alloc( size );
298 SIMDProcessor->Memcpy( block->virtMem, data, size );
307 void idVertexCache::Touch( vertCache_t *block ) {
309 common->Error( "idVertexCache Touch: NULL pointer" );
312 if ( block->tag == TAG_FREE ) {
313 common->FatalError( "idVertexCache Touch: freed pointer" );
315 if ( block->tag == TAG_TEMP ) {
316 common->FatalError( "idVertexCache Touch: temporary pointer" );
319 block->frameUsed = currentFrame;
321 // move to the head of the LRU list
322 block->next->prev = block->prev;
323 block->prev->next = block->next;
325 block->next = staticHeaders.next;
326 block->prev = &staticHeaders;
327 staticHeaders.next->prev = block;
328 staticHeaders.next = block;
336 void idVertexCache::Free( vertCache_t *block ) {
341 if ( block->tag == TAG_FREE ) {
342 common->FatalError( "idVertexCache Free: freed pointer" );
344 if ( block->tag == TAG_TEMP ) {
345 common->FatalError( "idVertexCache Free: temporary pointer" );
348 // this block still can't be purged until the frame count has expired,
349 // but it won't need to clear a user pointer when it is
352 block->next->prev = block->prev;
353 block->prev->next = block->next;
355 block->next = deferredFreeList.next;
356 block->prev = &deferredFreeList;
357 deferredFreeList.next->prev = block;
358 deferredFreeList.next = block;
363 idVertexCache::AllocFrameTemp
365 A frame temp allocation must never be allowed to fail due to overflow.
366 We can't simply sync with the GPU and overwrite what we have, because
367 there may still be future references to dynamically created surfaces.
370 vertCache_t *idVertexCache::AllocFrameTemp( void *data, int size ) {
374 common->Error( "idVertexCache::AllocFrameTemp: size = %i\n", size );
377 if ( dynamicAllocThisFrame + size > frameBytes ) {
378 // if we don't have enough room in the temp block, allocate a static block,
379 // but immediately free it so it will get freed at the next frame
381 Alloc( data, size, &block );
386 // this data is just going on the shared dynamic list
388 // if we don't have any remaining unused headers, allocate some more
389 if ( freeDynamicHeaders.next == &freeDynamicHeaders ) {
391 for ( int i = 0; i < EXPAND_HEADERS; i++ ) {
392 block = headerAllocator.Alloc();
393 block->next = freeDynamicHeaders.next;
394 block->prev = &freeDynamicHeaders;
395 block->next->prev = block;
396 block->prev->next = block;
400 // move it from the freeDynamicHeaders list to the dynamicHeaders list
401 block = freeDynamicHeaders.next;
402 block->next->prev = block->prev;
403 block->prev->next = block->next;
404 block->next = dynamicHeaders.next;
405 block->prev = &dynamicHeaders;
406 block->next->prev = block;
407 block->prev->next = block;
410 block->tag = TAG_TEMP;
411 block->indexBuffer = false;
412 block->offset = dynamicAllocThisFrame;
413 dynamicAllocThisFrame += block->size;
414 dynamicCountThisFrame++;
416 block->frameUsed = 0;
419 block->virtMem = tempBuffers[listNum]->virtMem;
420 block->vbo = tempBuffers[listNum]->vbo;
423 qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo );
424 qglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, block->offset, (GLsizeiptrARB)size, data );
426 SIMDProcessor->Memcpy( (byte *)block->virtMem + block->offset, data, size );
434 idVertexCache::EndFrame
437 void idVertexCache::EndFrame() {
438 // display debug information
439 if ( r_showVertexCache.GetBool() ) {
440 int staticUseCount = 0;
441 int staticUseSize = 0;
443 for ( vertCache_t *block = staticHeaders.next ; block != &staticHeaders ; block = block->next ) {
444 if ( block->frameUsed == currentFrame ) {
446 staticUseSize += block->size;
450 const char *frameOverflow = tempOverflow ? "(OVERFLOW)" : "";
452 common->Printf( "vertex dynamic:%i=%ik%s, static alloc:%i=%ik used:%i=%ik total:%i=%ik\n",
453 dynamicCountThisFrame, dynamicAllocThisFrame/1024, frameOverflow,
454 staticCountThisFrame, staticAllocThisFrame/1024,
455 staticUseCount, staticUseSize/1024,
456 staticCountTotal, staticAllocTotal/1024 );
460 // if our total static count is above our working memory limit, start purging things
461 while ( staticAllocTotal > r_vertexBufferMegs.GetInteger() * 1024 * 1024 ) {
462 // free the least recently used
467 if( !virtualMemory ) {
468 // unbind vertex buffers so normal virtual memory will be used in case
469 // r_useVertexBuffers / r_useIndexBuffers
470 qglBindBufferARB( GL_ARRAY_BUFFER_ARB, 0 );
471 qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
475 currentFrame = tr.frameCount;
476 listNum = currentFrame % NUM_VERTEX_FRAMES;
477 staticAllocThisFrame = 0;
478 staticCountThisFrame = 0;
479 dynamicAllocThisFrame = 0;
480 dynamicCountThisFrame = 0;
481 tempOverflow = false;
483 // free all the deferred free headers
484 while( deferredFreeList.next != &deferredFreeList ) {
485 ActuallyFree( deferredFreeList.next );
488 // free all the frame temp headers
489 vertCache_t *block = dynamicHeaders.next;
490 if ( block != &dynamicHeaders ) {
491 block->prev = &freeDynamicHeaders;
492 dynamicHeaders.prev->next = freeDynamicHeaders.next;
493 freeDynamicHeaders.next->prev = dynamicHeaders.prev;
494 freeDynamicHeaders.next = block;
496 dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders;
505 void idVertexCache::List( void ) {
510 int deferredSpace = 0;
513 for ( block = staticHeaders.next ; block != &staticHeaders ; block = block->next) {
516 totalStatic += block->size;
517 if ( block->frameUsed == currentFrame ) {
518 frameStatic += block->size;
522 int numFreeStaticHeaders = 0;
523 for ( block = freeStaticHeaders.next ; block != &freeStaticHeaders ; block = block->next ) {
524 numFreeStaticHeaders++;
527 int numFreeDynamicHeaders = 0;
528 for ( block = freeDynamicHeaders.next ; block != &freeDynamicHeaders ; block = block->next ) {
529 numFreeDynamicHeaders++;
532 common->Printf( "%i megs working set\n", r_vertexBufferMegs.GetInteger() );
533 common->Printf( "%i dynamic temp buffers of %ik\n", NUM_VERTEX_FRAMES, frameBytes / 1024 );
534 common->Printf( "%5i active static headers\n", numActive );
535 common->Printf( "%5i free static headers\n", numFreeStaticHeaders );
536 common->Printf( "%5i free dynamic headers\n", numFreeDynamicHeaders );
538 if ( !virtualMemory ) {
539 common->Printf( "Vertex cache is in ARB_vertex_buffer_object memory (FAST).\n");
541 common->Printf( "Vertex cache is in virtual memory (SLOW)\n" );
544 if ( r_useIndexBuffers.GetBool() ) {
545 common->Printf( "Index buffers are accelerated.\n" );
547 common->Printf( "Index buffers are not used.\n" );
553 idVertexCache::IsFast
555 just for gfxinfo printing
558 bool idVertexCache::IsFast() {
559 if ( virtualMemory ) {