mirror of
https://github.com/TTimo/doom3.gpl.git
synced 2026-03-19 16:39:24 +01:00
hello world
This commit is contained in:
563
neo/renderer/VertexCache.cpp
Normal file
563
neo/renderer/VertexCache.cpp
Normal file
@@ -0,0 +1,563 @@
|
||||
/*
|
||||
===========================================================================
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user