]> icculus.org git repositories - icculus/iodoom3.git/blob - neo/renderer/VertexCache.cpp
hello world
[icculus/iodoom3.git] / neo / renderer / VertexCache.cpp
1 /*
2 ===========================================================================
3
4 Doom 3 GPL Source Code
5 Copyright (C) 1999-2011 id Software LLC, a ZeniMax Media company. 
6
7 This file is part of the Doom 3 GPL Source Code (?Doom 3 Source Code?).  
8
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.
13
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.
18
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/>.
21
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.
23
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.
25
26 ===========================================================================
27 */
28
29 #include "../idlib/precompiled.h"
30 #pragma hdrstop
31
32 #include "tr_local.h"
33
34
35 static const int        FRAME_MEMORY_BYTES = 0x200000;
36 static const int        EXPAND_HEADERS = 1024;
37
38 idCVar idVertexCache::r_showVertexCache( "r_showVertexCache", "0", CVAR_INTEGER|CVAR_RENDERER, "" );
39 idCVar idVertexCache::r_vertexBufferMegs( "r_vertexBufferMegs", "32", CVAR_INTEGER|CVAR_RENDERER, "" );
40
41 idVertexCache           vertexCache;
42
43 /*
44 ==============
45 R_ListVertexCache_f
46 ==============
47 */
48 static void R_ListVertexCache_f( const idCmdArgs &args ) {
49         vertexCache.List();
50 }
51
52 /*
53 ==============
54 idVertexCache::ActuallyFree
55 ==============
56 */
57 void idVertexCache::ActuallyFree( vertCache_t *block ) {
58         if (!block) {
59                 common->Error( "idVertexCache Free: NULL pointer" );
60         }
61
62         if ( block->user ) {
63                 // let the owner know we have purged it
64                 *block->user = NULL;
65                 block->user = NULL;
66         }
67
68         // temp blocks are in a shared space that won't be freed
69         if ( block->tag != TAG_TEMP ) {
70                 staticAllocTotal -= block->size;
71                 staticCountTotal--;
72
73                 if ( block->vbo ) {
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);
78 #endif
79                 } else if ( block->virtMem ) {
80                         Mem_Free( block->virtMem );
81                         block->virtMem = NULL;
82                 }
83         }
84         block->tag = TAG_FREE;          // mark as free
85
86         // unlink stick it back on the free list
87         block->next->prev = block->prev;
88         block->prev->next = block->next;
89
90 #if 1
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;
94 #else
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;
98 #endif
99
100         block->next->prev = block;
101         block->prev->next = block;
102 }
103
104 /*
105 ==============
106 idVertexCache::Position
107
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
111
112 The ARB_vertex_buffer_object will be bound
113 ==============
114 */
115 void *idVertexCache::Position( vertCache_t *buffer ) {
116         if ( !buffer || buffer->tag == TAG_FREE ) {
117                 common->FatalError( "idVertexCache::Position: bad vertCache_t" );
118         }
119
120         // the ARB vertex object just uses an offset
121         if ( buffer->vbo ) {
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 ); 
125                         } else {
126                                 common->Printf( "GL_ARRAY_BUFFER_ARB = %i (%i bytes)\n", buffer->vbo, buffer->size ); 
127                         }
128                 }
129                 if ( buffer->indexBuffer ) {
130                         qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, buffer->vbo );
131                 } else {
132                         qglBindBufferARB( GL_ARRAY_BUFFER_ARB, buffer->vbo );
133                 }
134                 return (void *)buffer->offset;
135         }
136
137         // virtual memory is a real pointer
138         return (void *)((byte *)buffer->virtMem + buffer->offset);
139 }
140
141 void idVertexCache::UnbindIndex() {
142         qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, 0 );
143 }
144
145
146 //================================================================================
147
148 /*
149 ===========
150 idVertexCache::Init
151 ===========
152 */
153 void idVertexCache::Init() {
154         cmdSystem->AddCommand( "listVertexCache", R_ListVertexCache_f, CMD_FL_RENDERER, "lists vertex cache" );
155
156         if ( r_vertexBufferMegs.GetInteger() < 8 ) {
157                 r_vertexBufferMegs.SetInteger( 8 );
158         }
159
160         virtualMemory = false;
161
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" );
165         } else {
166                 virtualMemory = true;
167                 r_useIndexBuffers.SetBool( false );
168                 common->Printf( "WARNING: vertex array range in virtual memory (SLOW)\n" );
169         }
170
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;
177
178         // set up the dynamic frame memory
179         frameBytes = FRAME_MEMORY_BYTES;
180         staticAllocTotal = 0;
181
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;
191         }
192         Mem_Free( junk );
193
194         EndFrame();
195 }
196
197 /*
198 ===========
199 idVertexCache::PurgeAll
200
201 Used when toggling vertex programs on or off, because
202 the cached data isn't valid
203 ===========
204 */
205 void idVertexCache::PurgeAll() {
206         while( staticHeaders.next != &staticHeaders ) {
207                 ActuallyFree( staticHeaders.next );
208         }
209 }
210
211 /*
212 ===========
213 idVertexCache::Shutdown
214 ===========
215 */
216 void idVertexCache::Shutdown() {
217 //      PurgeAll();     // !@#: also purge the temp buffers
218
219         headerAllocator.Shutdown();
220 }
221
222 /*
223 ===========
224 idVertexCache::Alloc
225 ===========
226 */
227 void idVertexCache::Alloc( void *data, int size, vertCache_t **buffer, bool indexBuffer ) {
228         vertCache_t     *block;
229
230         if ( size <= 0 ) {
231                 common->Error( "idVertexCache::Alloc: size = %i\n", size );
232         }
233
234         // if we can't find anything, it will be NULL
235         *buffer = NULL;
236
237         // if we don't have any remaining unused headers, allocate some more
238         if ( freeStaticHeaders.next == &freeStaticHeaders ) {
239
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;
246
247                         if( !virtualMemory ) {
248                                 qglGenBuffersARB( 1, & block->vbo );
249                         }
250                 }
251         }
252
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;
261
262         block->size = size;
263         block->offset = 0;
264         block->tag = TAG_USED;
265
266         // save data for debugging
267         staticAllocThisFrame += block->size;
268         staticCountThisFrame++;
269         staticCountTotal++;
270         staticAllocTotal += block->size;
271
272         // this will be set to zero when it is purged
273         block->user = buffer;
274         *buffer = block;
275
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;
280
281         block->indexBuffer = indexBuffer;
282
283         // copy the data
284         if ( block->vbo ) {
285                 if ( indexBuffer ) {
286                         qglBindBufferARB( GL_ELEMENT_ARRAY_BUFFER_ARB, block->vbo );
287                         qglBufferDataARB( GL_ELEMENT_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB );
288                 } else {
289                         qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo );
290                         if ( allocatingTempBuffer ) {
291                                 qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STREAM_DRAW_ARB );
292                         } else {
293                                 qglBufferDataARB( GL_ARRAY_BUFFER_ARB, (GLsizeiptrARB)size, data, GL_STATIC_DRAW_ARB );
294                         }
295                 }
296         } else {
297                 block->virtMem = Mem_Alloc( size );
298                 SIMDProcessor->Memcpy( block->virtMem, data, size );
299         }
300 }
301
302 /*
303 ===========
304 idVertexCache::Touch
305 ===========
306 */
307 void idVertexCache::Touch( vertCache_t *block ) {
308         if ( !block ) {
309                 common->Error( "idVertexCache Touch: NULL pointer" );
310         }
311
312         if ( block->tag == TAG_FREE ) {
313                 common->FatalError( "idVertexCache Touch: freed pointer" );
314         }
315         if ( block->tag == TAG_TEMP ) {
316                 common->FatalError( "idVertexCache Touch: temporary pointer" );
317         }
318
319         block->frameUsed = currentFrame;
320
321         // move to the head of the LRU list
322         block->next->prev = block->prev;
323         block->prev->next = block->next;
324
325         block->next = staticHeaders.next;
326         block->prev = &staticHeaders;
327         staticHeaders.next->prev = block;
328         staticHeaders.next = block;
329 }
330
331 /*
332 ===========
333 idVertexCache::Free
334 ===========
335 */
336 void idVertexCache::Free( vertCache_t *block ) {
337         if (!block) {
338                 return;
339         }
340
341         if ( block->tag == TAG_FREE ) {
342                 common->FatalError( "idVertexCache Free: freed pointer" );
343         }
344         if ( block->tag == TAG_TEMP ) {
345                 common->FatalError( "idVertexCache Free: temporary pointer" );
346         }
347
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
350         block->user = NULL;
351
352         block->next->prev = block->prev;
353         block->prev->next = block->next;
354
355         block->next = deferredFreeList.next;
356         block->prev = &deferredFreeList;
357         deferredFreeList.next->prev = block;
358         deferredFreeList.next = block;
359 }
360
361 /*
362 ===========
363 idVertexCache::AllocFrameTemp
364
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.
368 ===========
369 */
370 vertCache_t     *idVertexCache::AllocFrameTemp( void *data, int size ) {
371         vertCache_t     *block;
372
373         if ( size <= 0 ) {
374                 common->Error( "idVertexCache::AllocFrameTemp: size = %i\n", size );
375         }
376
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
380                 tempOverflow = true;
381                 Alloc( data, size, &block );
382                 Free( block);
383                 return block;
384         }
385
386         // this data is just going on the shared dynamic list
387
388         // if we don't have any remaining unused headers, allocate some more
389         if ( freeDynamicHeaders.next == &freeDynamicHeaders ) {
390
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;
397                 }
398         }
399
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;
408
409         block->size = size;
410         block->tag = TAG_TEMP;
411         block->indexBuffer = false;
412         block->offset = dynamicAllocThisFrame;
413         dynamicAllocThisFrame += block->size;
414         dynamicCountThisFrame++;
415         block->user = NULL;
416         block->frameUsed = 0;
417
418         // copy the data
419         block->virtMem = tempBuffers[listNum]->virtMem;
420         block->vbo = tempBuffers[listNum]->vbo;
421
422         if ( block->vbo ) {
423                 qglBindBufferARB( GL_ARRAY_BUFFER_ARB, block->vbo );
424                 qglBufferSubDataARB( GL_ARRAY_BUFFER_ARB, block->offset, (GLsizeiptrARB)size, data );
425         } else {
426                 SIMDProcessor->Memcpy( (byte *)block->virtMem + block->offset, data, size );
427         }
428
429         return block;
430 }
431
432 /*
433 ===========
434 idVertexCache::EndFrame
435 ===========
436 */
437 void idVertexCache::EndFrame() {
438         // display debug information
439         if ( r_showVertexCache.GetBool() ) {
440                 int     staticUseCount = 0;
441                 int staticUseSize = 0;
442
443                 for ( vertCache_t *block = staticHeaders.next ; block != &staticHeaders ; block = block->next ) {
444                         if ( block->frameUsed == currentFrame ) {
445                                 staticUseCount++;
446                                 staticUseSize += block->size;
447                         }
448                 }
449
450                 const char *frameOverflow = tempOverflow ? "(OVERFLOW)" : "";
451
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 );
457         }
458
459 #if 0
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
463
464         }
465 #endif
466
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 );
472         }
473
474
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;
482
483         // free all the deferred free headers
484         while( deferredFreeList.next != &deferredFreeList ) {
485                 ActuallyFree( deferredFreeList.next );
486         }
487
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;
495
496                 dynamicHeaders.next = dynamicHeaders.prev = &dynamicHeaders;
497         }
498 }
499
500 /*
501 =============
502 idVertexCache::List
503 =============
504 */
505 void idVertexCache::List( void ) {
506         int     numActive = 0;
507         int     numDeferred = 0;
508         int frameStatic = 0;
509         int     totalStatic = 0;
510         int     deferredSpace = 0;
511
512         vertCache_t *block;
513         for ( block = staticHeaders.next ; block != &staticHeaders ; block = block->next) {
514                 numActive++;
515
516                 totalStatic += block->size;
517                 if ( block->frameUsed == currentFrame ) {
518                         frameStatic += block->size;
519                 }
520         }
521
522         int     numFreeStaticHeaders = 0;
523         for ( block = freeStaticHeaders.next ; block != &freeStaticHeaders ; block = block->next ) {
524                 numFreeStaticHeaders++;
525         }
526
527         int     numFreeDynamicHeaders = 0;
528         for ( block = freeDynamicHeaders.next ; block != &freeDynamicHeaders ; block = block->next ) {
529                 numFreeDynamicHeaders++;
530         }
531
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 );
537
538         if ( !virtualMemory  ) {
539                 common->Printf( "Vertex cache is in ARB_vertex_buffer_object memory (FAST).\n");
540         } else {
541                 common->Printf( "Vertex cache is in virtual memory (SLOW)\n" );
542         }
543
544         if ( r_useIndexBuffers.GetBool() ) {
545                 common->Printf( "Index buffers are accelerated.\n" );
546         } else {
547                 common->Printf( "Index buffers are not used.\n" );
548         }
549 }
550
551 /*
552 =============
553 idVertexCache::IsFast
554
555 just for gfxinfo printing
556 =============
557 */
558 bool idVertexCache::IsFast() {
559         if ( virtualMemory ) {
560                 return false;
561         }
562         return true;
563 }