/* =========================================================================== Doom 3 BFG Edition GPL Source Code Copyright (C) 1993-2012 id Software LLC, a ZeniMax Media company. This file is part of the Doom 3 BFG Edition GPL Source Code ("Doom 3 BFG Edition Source Code"). Doom 3 BFG Edition 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 BFG Edition 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 BFG Edition Source Code. If not, see . In addition, the Doom 3 BFG Edition 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 BFG Edition 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. =========================================================================== */ #pragma hdrstop #include "../idlib/precompiled.h" #include "tr_local.h" /* ========================================================================================== FRAME MEMORY ALLOCATION ========================================================================================== */ static const unsigned int NUM_FRAME_DATA = 2; static const unsigned int FRAME_ALLOC_ALIGNMENT = 128; static const unsigned int MAX_FRAME_MEMORY = 64 * 1024 * 1024; // larger so that we can noclip on PC for dev purposes idFrameData smpFrameData[NUM_FRAME_DATA]; idFrameData * frameData; unsigned int smpFrame; //#define TRACK_FRAME_ALLOCS #if defined( TRACK_FRAME_ALLOCS ) idSysInterlockedInteger frameAllocTypeCount[FRAME_ALLOC_MAX]; int frameHighWaterTypeCount[FRAME_ALLOC_MAX]; #endif /* ==================== R_ToggleSmpFrame ==================== */ void R_ToggleSmpFrame() { // update the highwater mark if ( frameData->frameMemoryAllocated.GetValue() > frameData->highWaterAllocated ) { frameData->highWaterAllocated = frameData->frameMemoryAllocated.GetValue(); #if defined( TRACK_FRAME_ALLOCS ) frameData->highWaterUsed = frameData->frameMemoryUsed.GetValue(); for ( int i = 0; i < FRAME_ALLOC_MAX; i++ ) { frameHighWaterTypeCount[i] = frameAllocTypeCount[i].GetValue(); } #endif } // switch to the next frame smpFrame++; frameData = &smpFrameData[smpFrame % NUM_FRAME_DATA]; // reset the memory allocation const unsigned int bytesNeededForAlignment = FRAME_ALLOC_ALIGNMENT - ( (unsigned int)frameData->frameMemory & ( FRAME_ALLOC_ALIGNMENT - 1 ) ); frameData->frameMemoryAllocated.SetValue( bytesNeededForAlignment ); frameData->frameMemoryUsed.SetValue( 0 ); #if defined( TRACK_FRAME_ALLOCS ) for ( int i = 0; i < FRAME_ALLOC_MAX; i++ ) { frameAllocTypeCount[i].SetValue( 0 ); } #endif // clear the command chain and make a RC_NOP command the only thing on the list frameData->cmdHead = frameData->cmdTail = (emptyCommand_t *)R_FrameAlloc( sizeof( *frameData->cmdHead ), FRAME_ALLOC_DRAW_COMMAND ); frameData->cmdHead->commandId = RC_NOP; frameData->cmdHead->next = NULL; } /* ===================== R_ShutdownFrameData ===================== */ void R_ShutdownFrameData() { frameData = NULL; for ( int i = 0; i < NUM_FRAME_DATA; i++ ) { Mem_Free16( smpFrameData[i].frameMemory ); smpFrameData[i].frameMemory = NULL; } } /* ===================== R_InitFrameData ===================== */ void R_InitFrameData() { R_ShutdownFrameData(); for ( int i = 0; i < NUM_FRAME_DATA; i++ ) { smpFrameData[i].frameMemory = (byte *) Mem_Alloc16( MAX_FRAME_MEMORY, TAG_RENDER ); } // must be set before calling R_ToggleSmpFrame() frameData = &smpFrameData[ 0 ]; R_ToggleSmpFrame(); } /* ================ R_FrameAlloc This data will be automatically freed when the current frame's back end completes. This should only be called by the front end. The back end shouldn't need to allocate memory. All temporary data, like dynamic tesselations and local spaces are allocated here. All memory is cache-line-cleared for the best performance. ================ */ void *R_FrameAlloc( int bytes, frameAllocType_t type ) { #if defined( TRACK_FRAME_ALLOCS ) frameData->frameMemoryUsed.Add( bytes ); frameAllocTypeCount[type].Add( bytes ); #endif bytes = ( bytes + FRAME_ALLOC_ALIGNMENT - 1 ) & ~ ( FRAME_ALLOC_ALIGNMENT - 1 ); // thread safe add int end = frameData->frameMemoryAllocated.Add( bytes ); if ( end > MAX_FRAME_MEMORY ) { idLib::Error( "R_FrameAlloc ran out of memory. bytes = %d, end = %d, highWaterAllocated = %d\n", bytes, end, frameData->highWaterAllocated ); } byte * ptr = frameData->frameMemory + end - bytes; // cache line clear the memory for ( int offset = 0; offset < bytes; offset += CACHE_LINE_SIZE ) { ZeroCacheLine( ptr, offset ); } return ptr; } /* ================== R_ClearedFrameAlloc ================== */ void *R_ClearedFrameAlloc( int bytes, frameAllocType_t type ) { // NOTE: every allocation is cache line cleared return R_FrameAlloc( bytes, type ); } /* ========================================================================================== FONT-END STATIC MEMORY ALLOCATION ========================================================================================== */ /* ================= R_StaticAlloc ================= */ void *R_StaticAlloc( int bytes, const memTag_t tag ) { tr.pc.c_alloc++; void * buf = Mem_Alloc( bytes, tag ); // don't exit on failure on zero length allocations since the old code didn't if ( buf == NULL && bytes != 0 ) { common->FatalError( "R_StaticAlloc failed on %i bytes", bytes ); } return buf; } /* ================= R_ClearedStaticAlloc ================= */ void *R_ClearedStaticAlloc( int bytes ) { void * buf = R_StaticAlloc( bytes ); memset( buf, 0, bytes ); return buf; } /* ================= R_StaticFree ================= */ void R_StaticFree( void *data ) { tr.pc.c_free++; Mem_Free( data ); } /* ========================================================================================== FONT-END RENDERING ========================================================================================== */ /* ================= R_SortDrawSurfs ================= */ static void R_SortDrawSurfs( drawSurf_t ** drawSurfs, const int numDrawSurfs ) { #if 1 uint64 * indices = (uint64 *) _alloca16( numDrawSurfs * sizeof( indices[0] ) ); // sort the draw surfs based on: // 1. sort value (largest first) // 2. depth (smallest first) // 3. index (largest first) assert( numDrawSurfs <= 0xFFFF ); for ( int i = 0; i < numDrawSurfs; i++ ) { float sort = SS_POST_PROCESS - drawSurfs[i]->sort; assert( sort >= 0.0f ); uint64 dist = 0; if ( drawSurfs[i]->frontEndGeo != NULL ) { float min = 0.0f; float max = 1.0f; idRenderMatrix::DepthBoundsForBounds( min, max, drawSurfs[i]->space->mvp, drawSurfs[i]->frontEndGeo->bounds ); dist = idMath::Ftoui16( min * 0xFFFF ); } indices[i] = ( ( numDrawSurfs - i ) & 0xFFFF ) | ( dist << 16 ) | ( (uint64) ( *(uint32 *)&sort ) << 32 ); } const int64 MAX_LEVELS = 128; int64 lo[MAX_LEVELS]; int64 hi[MAX_LEVELS]; // Keep the top of the stack in registers to avoid load-hit-stores. register int64 st_lo = 0; register int64 st_hi = numDrawSurfs - 1; register int64 level = 0; for ( ; ; ) { register int64 i = st_lo; register int64 j = st_hi; if ( j - i >= 4 && level < MAX_LEVELS - 1 ) { register uint64 pivot = indices[( i + j ) / 2]; do { while ( indices[i] > pivot ) i++; while ( indices[j] < pivot ) j--; if ( i > j ) break; uint64 h = indices[i]; indices[i] = indices[j]; indices[j] = h; } while ( ++i <= --j ); // No need for these iterations because we are always sorting unique values. //while ( indices[j] == pivot && st_lo < j ) j--; //while ( indices[i] == pivot && i < st_hi ) i++; assert( level < MAX_LEVELS - 1 ); lo[level] = i; hi[level] = st_hi; st_hi = j; level++; } else { for( ; i < j; j-- ) { register int64 m = i; for ( int64 k = i + 1; k <= j; k++ ) { if ( indices[k] < indices[m] ) { m = k; } } uint64 h = indices[m]; indices[m] = indices[j]; indices[j] = h; } if ( --level < 0 ) { break; } st_lo = lo[level]; st_hi = hi[level]; } } drawSurf_t ** newDrawSurfs = (drawSurf_t **) indices; for ( int i = 0; i < numDrawSurfs; i++ ) { newDrawSurfs[i] = drawSurfs[numDrawSurfs - ( indices[i] & 0xFFFF )]; } memcpy( drawSurfs, newDrawSurfs, numDrawSurfs * sizeof( drawSurfs[0] ) ); #else struct local_t { static int R_QsortSurfaces( const void *a, const void *b ) { const drawSurf_t * ea = *(drawSurf_t **)a; const drawSurf_t * eb = *(drawSurf_t **)b; if ( ea->sort < eb->sort ) { return -1; } if ( ea->sort > eb->sort ) { return 1; } return 0; } }; // Add a sort offset so surfaces with equal sort orders still deterministically // draw in the order they were added, at least within a given model. float sorfOffset = 0.0f; for ( int i = 0; i < numDrawSurfs; i++ ) { drawSurf[i]->sort += sorfOffset; sorfOffset += 0.000001f; } // sort the drawsurfs qsort( drawSurfs, numDrawSurfs, sizeof( drawSurfs[0] ), local_t::R_QsortSurfaces ); #endif } /* ================ R_RenderView A view may be either the actual camera view, a mirror / remote location, or a 3D view on a gui surface. Parms will typically be allocated with R_FrameAlloc ================ */ void R_RenderView( viewDef_t *parms ) { // save view in case we are a subview viewDef_t * oldView = tr.viewDef; tr.viewDef = parms; // setup the matrix for world space to eye space R_SetupViewMatrix( tr.viewDef ); // we need to set the projection matrix before doing // portal-to-screen scissor calculations R_SetupProjectionMatrix( tr.viewDef ); // setup render matrices for faster culling idRenderMatrix::Transpose( *(idRenderMatrix *)tr.viewDef->projectionMatrix, tr.viewDef->projectionRenderMatrix ); idRenderMatrix viewRenderMatrix; idRenderMatrix::Transpose( *(idRenderMatrix *)tr.viewDef->worldSpace.modelViewMatrix, viewRenderMatrix ); idRenderMatrix::Multiply( tr.viewDef->projectionRenderMatrix, viewRenderMatrix, tr.viewDef->worldSpace.mvp ); // the planes of the view frustum are needed for portal visibility culling idRenderMatrix::GetFrustumPlanes( tr.viewDef->frustum, tr.viewDef->worldSpace.mvp, false, true ); // the DOOM 3 frustum planes point outside the frustum for ( int i = 0; i < 6; i++ ) { tr.viewDef->frustum[i] = - tr.viewDef->frustum[i]; } // remove the Z-near to avoid portals from being near clipped tr.viewDef->frustum[4][3] -= r_znear.GetFloat(); // identify all the visible portal areas, and create view lights and view entities // for all the the entityDefs and lightDefs that are in the visible portal areas static_cast(parms->renderWorld)->FindViewLightsAndEntities(); // wait for any shadow volume jobs from the previous frame to finish tr.frontEndJobList->Wait(); // make sure that interactions exist for all light / entity combinations that are visible // add any pre-generated light shadows, and calculate the light shader values R_AddLights(); // adds ambient surfaces and create any necessary interaction surfaces to add to the light lists R_AddModels(); // build up the GUIs on world surfaces R_AddInGameGuis( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs ); // any viewLight that didn't have visible surfaces can have it's shadows removed R_OptimizeViewLightsList(); // sort all the ambient surfaces for translucency ordering R_SortDrawSurfs( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs ); // generate any subviews (mirrors, cameras, etc) before adding this view if ( R_GenerateSubViews( tr.viewDef->drawSurfs, tr.viewDef->numDrawSurfs ) ) { // if we are debugging subviews, allow the skipping of the main view draw if ( r_subviewOnly.GetBool() ) { return; } } // write everything needed to the demo file if ( common->WriteDemo() ) { static_cast(parms->renderWorld)->WriteVisibleDefs( tr.viewDef ); } // add the rendering commands for this viewDef R_AddDrawViewCmd( parms, false ); // restore view in case we are a subview tr.viewDef = oldView; } /* ================ R_RenderPostProcess Because R_RenderView may be called by subviews we have to make sure the post process pass happens after the active view and its subviews is done rendering. ================ */ void R_RenderPostProcess( viewDef_t *parms ) { viewDef_t * oldView = tr.viewDef; R_AddDrawPostProcess( parms ); tr.viewDef = oldView; }