/* ================ R_GenerateSubViews If we need to render another view to complete the current view, generate it first. It is important to do this after all drawSurfs for the current view have been generated, because it may create a subview which would change tr.viewCount. ================ */ bool R_GenerateSubViews( const drawSurf_t* const drawSurfs[], const int numDrawSurfs ) { SCOPED_PROFILE_EVENT( "R_GenerateSubViews" ); // for testing the performance hit if( r_skipSubviews.GetBool() ) { return false; } // scan the surfaces until we either find a subview, or determine // there are no more subview surfaces. bool subviews = false; for( int i = 0; i < numDrawSurfs; i++ ) { const drawSurf_t* drawSurf = drawSurfs[i]; if( !drawSurf->material->HasSubview() ) { continue; } if( R_GenerateSurfaceSubview( drawSurf ) ) { subviews = true; } } return subviews; }
/* ================ R_AddInGameGuis ================ */ void R_AddInGameGuis( const drawSurf_t* const drawSurfs[], const int numDrawSurfs ) { SCOPED_PROFILE_EVENT( "R_AddInGameGuis" ); // check for gui surfaces for( int i = 0; i < numDrawSurfs; i++ ) { const drawSurf_t* drawSurf = drawSurfs[i]; idUserInterface* gui = drawSurf->material->GlobalGui(); int guiNum = drawSurf->material->GetEntityGui() - 1; if( guiNum >= 0 && guiNum < MAX_RENDERENTITY_GUI ) { if( drawSurf->space->entityDef != NULL ) { gui = drawSurf->space->entityDef->parms.gui[ guiNum ]; } } if( gui == NULL ) { continue; } idBounds ndcBounds; if( !R_PreciseCullSurface( drawSurf, ndcBounds ) ) { // did we ever use this to forward an entity color to a gui that didn't set color? // memcpy( tr.guiShaderParms, shaderParms, sizeof( tr.guiShaderParms ) ); R_RenderGuiSurf( gui, drawSurf ); } } }
/* ================ R_CalcInteractionFacing Determines which triangles of the surface are facing towards the light origin. The facing array should be allocated with one extra index than the number of surface triangles, which will be used to handle dangling edge silhouettes. ================ */ void R_CalcInteractionFacing( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, srfCullInfo_t &cullInfo ) { SCOPED_PROFILE_EVENT( "R_CalcInteractionFacing" ); if ( cullInfo.facing != NULL ) { return; } idVec3 localLightOrigin; R_GlobalPointToLocal( ent->modelMatrix, light->globalLightOrigin, localLightOrigin ); const int numFaces = tri->numIndexes / 3; cullInfo.facing = (byte *) R_StaticAlloc( ( numFaces + 1 ) * sizeof( cullInfo.facing[0] ), TAG_RENDER_INTERACTION ); // exact geometric cull against face for ( int i = 0, face = 0; i < tri->numIndexes; i += 3, face++ ) { const idDrawVert & v0 = tri->verts[tri->indexes[i + 0]]; const idDrawVert & v1 = tri->verts[tri->indexes[i + 1]]; const idDrawVert & v2 = tri->verts[tri->indexes[i + 2]]; const idPlane plane( v0.xyz, v1.xyz, v2.xyz ); const float d = plane.Distance( localLightOrigin ); cullInfo.facing[face] = ( d >= 0.0f ); } cullInfo.facing[numFaces] = 1; // for dangling edges to reference }
/* ===================== idRenderSystemLocal::SwapCommandBuffers_FinishRendering ===================== */ void idRenderSystemLocal::SwapCommandBuffers_FinishRendering( uint64 * frontEndMicroSec, uint64 * backEndMicroSec, uint64 * shadowMicroSec, uint64 * gpuMicroSec ) { SCOPED_PROFILE_EVENT( "SwapCommandBuffers" ); if ( gpuMicroSec != NULL ) { *gpuMicroSec = 0; // until shown otherwise } if ( !R_IsInitialized() ) { return; } // After coming back from an autoswap, we won't have anything to render if ( frameData->cmdHead->next != NULL ) { // wait for our fence to hit, which means the swap has actually happened // We must do this before clearing any resources the GPU may be using void GL_BlockingSwapBuffers(); GL_BlockingSwapBuffers(); } // read back the start and end timer queries from the previous frame if ( glConfig.timerQueryAvailable ) { uint64 drawingTimeNanoseconds = 0; if ( tr.timerQueryId != 0 ) { qglGetQueryObjectui64vEXT( tr.timerQueryId, GL_QUERY_RESULT, &drawingTimeNanoseconds ); } if ( gpuMicroSec != NULL ) { *gpuMicroSec = drawingTimeNanoseconds / 1000; } } //------------------------------ // save out timing information if ( frontEndMicroSec != NULL ) { *frontEndMicroSec = pc.frontEndMicroSec; } if ( backEndMicroSec != NULL ) { *backEndMicroSec = backEnd.pc.totalMicroSec; } if ( shadowMicroSec != NULL ) { *shadowMicroSec = backEnd.pc.shadowMicroSec; } // print any other statistics and clear all of them R_PerformanceCounters(); // check for dynamic changes that require some initialization R_CheckCvars(); // check for errors GL_CheckErrors(); }
/* ================= R_RenderGuiSurf Create a texture space on the given surface and call the GUI generator to create quads for it. ================= */ static void R_RenderGuiSurf( idUserInterface* gui, const drawSurf_t* drawSurf ) { SCOPED_PROFILE_EVENT( "R_RenderGuiSurf" ); // for testing the performance hit if( r_skipGuiShaders.GetInteger() == 1 ) { return; } // don't allow an infinite recursion loop if( tr.guiRecursionLevel == 4 ) { return; } tr.pc.c_guiSurfs++; // create the new matrix to draw on this surface idVec3 origin, axis[3]; R_SurfaceToTextureAxis( drawSurf->frontEndGeo, origin, axis ); float guiModelMatrix[16]; float modelMatrix[16]; guiModelMatrix[0 * 4 + 0] = axis[0][0] * ( 1.0f / 640.0f ); guiModelMatrix[1 * 4 + 0] = axis[1][0] * ( 1.0f / 480.0f ); guiModelMatrix[2 * 4 + 0] = axis[2][0]; guiModelMatrix[3 * 4 + 0] = origin[0]; guiModelMatrix[0 * 4 + 1] = axis[0][1] * ( 1.0f / 640.0f ); guiModelMatrix[1 * 4 + 1] = axis[1][1] * ( 1.0f / 480.0f ); guiModelMatrix[2 * 4 + 1] = axis[2][1]; guiModelMatrix[3 * 4 + 1] = origin[1]; guiModelMatrix[0 * 4 + 2] = axis[0][2] * ( 1.0f / 640.0f ); guiModelMatrix[1 * 4 + 2] = axis[1][2] * ( 1.0f / 480.0f ); guiModelMatrix[2 * 4 + 2] = axis[2][2]; guiModelMatrix[3 * 4 + 2] = origin[2]; guiModelMatrix[0 * 4 + 3] = 0.0f; guiModelMatrix[1 * 4 + 3] = 0.0f; guiModelMatrix[2 * 4 + 3] = 0.0f; guiModelMatrix[3 * 4 + 3] = 1.0f; R_MatrixMultiply( guiModelMatrix, drawSurf->space->modelMatrix, modelMatrix ); tr.guiRecursionLevel++; // call the gui, which will call the 2D drawing functions tr.guiModel->Clear(); gui->Redraw( tr.viewDef->renderView.time[0] ); tr.guiModel->EmitToCurrentView( modelMatrix, drawSurf->space->weaponDepthHack ); tr.guiModel->Clear(); tr.guiRecursionLevel--; }
/* ============= idRenderWorldLocal::FindViewLightsAndEntites All the modelrefs and lightrefs that are in visible areas will have viewEntitys and viewLights created for them. The scissorRects on the viewEntitys and viewLights may be empty if they were considered, but not actually visible. Entities and lights can have cached viewEntities / viewLights that will be used if the viewCount variable matches. ============= */ void idRenderWorldLocal::FindViewLightsAndEntities() { SCOPED_PROFILE_EVENT( "FindViewLightsAndEntities" ); // bumping this counter invalidates cached viewLights / viewEntities, // when a light or entity is next considered, it will create a new // viewLight / viewEntity tr.viewCount++; // clear the visible lightDef and entityDef lists tr.viewDef->viewLights = NULL; tr.viewDef->viewEntitys = NULL; // all areas are initially not visible, but each portal // chain that leads to them will expand the visible rectangle for ( int i = 0; i < numPortalAreas; i++ ) { areaScreenRect[i].Clear(); } // find the area to start the portal flooding in if ( !r_usePortals.GetBool() ) { // debug tool to force no portal culling tr.viewDef->areaNum = -1; } else { tr.viewDef->areaNum = PointInArea( tr.viewDef->initialViewAreaOrigin ); } // determine all possible connected areas for // light-behind-door culling BuildConnectedAreas(); // flow through all the portals and add models / lights if ( r_singleArea.GetBool() ) { // if debugging, only mark this area // if we are outside the world, don't draw anything if ( tr.viewDef->areaNum >= 0 ) { static int lastPrintedAreaNum; if ( tr.viewDef->areaNum != lastPrintedAreaNum ) { lastPrintedAreaNum = tr.viewDef->areaNum; common->Printf( "entering portal area %i\n", tr.viewDef->areaNum ); } portalStack_t ps; for ( int i = 0; i < 5; i++ ) { ps.portalPlanes[i] = tr.viewDef->frustum[i]; } ps.numPortalPlanes = 5; ps.rect = tr.viewDef->scissor; AddAreaToView( tr.viewDef->areaNum, &ps ); } } else { // note that the center of projection for flowing through portals may // be a different point than initialViewAreaOrigin for subviews that // may have the viewOrigin in a solid/invalid area FlowViewThroughPortals( tr.viewDef->renderView.vieworg, 5, tr.viewDef->frustum ); } }
/* ================== R_SortViewEntities ================== */ viewEntity_t* R_SortViewEntities( viewEntity_t* vEntities ) { SCOPED_PROFILE_EVENT( "R_SortViewEntities" ); // We want to avoid having a single AddModel for something complex be // the last thing processed and hurt the parallel occupancy, so // sort dynamic models first, _area models second, then everything else. viewEntity_t* dynamics = NULL; viewEntity_t* areas = NULL; viewEntity_t* others = NULL; for( viewEntity_t* vEntity = vEntities; vEntity != NULL; ) { viewEntity_t* next = vEntity->next; const idRenderModel* model = vEntity->entityDef->parms.hModel; if( model->IsDynamicModel() != DM_STATIC ) { vEntity->next = dynamics; dynamics = vEntity; } else if( model->IsStaticWorldModel() ) { vEntity->next = areas; areas = vEntity; } else { vEntity->next = others; others = vEntity; } vEntity = next; } // concatenate the lists viewEntity_t* all = others; for( viewEntity_t* vEntity = areas; vEntity != NULL; ) { viewEntity_t* next = vEntity->next; vEntity->next = all; all = vEntity; vEntity = next; } for( viewEntity_t* vEntity = dynamics; vEntity != NULL; ) { viewEntity_t* next = vEntity->next; vEntity->next = all; all = vEntity; vEntity = next; } return all; }
/* ===================== R_CalcInteractionCullBits We want to cull a little on the sloppy side, because the pre-clipping of geometry to the lights in dmap will give many cases that are right at the border. We throw things out on the border, because if any one vertex is clearly inside, the entire triangle will be accepted. ===================== */ void R_CalcInteractionCullBits( const idRenderEntityLocal* ent, const srfTriangles_t* tri, const idRenderLightLocal* light, srfCullInfo_t& cullInfo ) { SCOPED_PROFILE_EVENT( "R_CalcInteractionCullBits" ); if( cullInfo.cullBits != NULL ) { return; } idPlane frustumPlanes[6]; idRenderMatrix::GetFrustumPlanes( frustumPlanes, light->baseLightProject, true, true ); int frontBits = 0; // cull the triangle surface bounding box for( int i = 0; i < 6; i++ ) { R_GlobalPlaneToLocal( ent->modelMatrix, frustumPlanes[i], cullInfo.localClipPlanes[i] ); // get front bits for the whole surface if( tri->bounds.PlaneDistance( cullInfo.localClipPlanes[i] ) >= LIGHT_CLIP_EPSILON ) { frontBits |= 1 << i; } } // if the surface is completely inside the light frustum if( frontBits == ( ( 1 << 6 ) - 1 ) ) { cullInfo.cullBits = LIGHT_CULL_ALL_FRONT; return; } cullInfo.cullBits = ( byte* ) R_StaticAlloc( tri->numVerts * sizeof( cullInfo.cullBits[0] ), TAG_RENDER_INTERACTION ); memset( cullInfo.cullBits, 0, tri->numVerts * sizeof( cullInfo.cullBits[0] ) ); for( int i = 0; i < 6; i++ ) { // if completely infront of this clipping plane if( frontBits & ( 1 << i ) ) { continue; } for( int j = 0; j < tri->numVerts; j++ ) { float d = cullInfo.localClipPlanes[i].Distance( tri->verts[j].xyz ); cullInfo.cullBits[j] |= ( d < LIGHT_CLIP_EPSILON ) << i; } } }
/* ======================== idLobby::UpdateSnaps ======================== */ void idLobby::UpdateSnaps() { assert( lobbyType == GetActingGameStateLobbyType() ); SCOPED_PROFILE_EVENT( "UpdateSnaps" ); #if 0 uint64 startTimeMicroSec = Sys_Microseconds(); #endif haveSubmittedSnaps = false; if( !SendCompletedSnaps() ) { // If we weren't able to send all the submitted snaps, we need to wait till we can. // We can't start new jobs until they are all sent out. return; } for( int p = 0; p < peers.Num(); p++ ) { peer_t& peer = peers[p]; if( !peer.IsConnected() ) { continue; } if( peer.needToSubmitPendingSnap ) { // Submit the snap if( SubmitPendingSnap( p ) ) { peer.needToSubmitPendingSnap = false; // only clear this if we actually submitted the snap } } } #if 0 uint64 endTimeMicroSec = Sys_Microseconds(); if( endTimeMicroSec - startTimeMicroSec > 200 ) // .2 ms { idLib::Printf( "NET: UpdateSnaps time in ms: %f\n", ( float )( endTimeMicroSec - startTimeMicroSec ) / 1000.0f ); } #endif }
/* ================ idGuiModel::EmitFullScreen Creates a view that covers the screen and emit the surfaces ================ */ void idGuiModel::EmitFullScreen() { if( surfaces[0].numIndexes == 0 ) { return; } SCOPED_PROFILE_EVENT( "Gui::EmitFullScreen" ); viewDef_t* viewDef = ( viewDef_t* )R_ClearedFrameAlloc( sizeof( *viewDef ), FRAME_ALLOC_VIEW_DEF ); viewDef->is2Dgui = true; tr.GetCroppedViewport( &viewDef->viewport ); bool stereoEnabled = ( renderSystem->GetStereo3DMode() != STEREO3D_OFF ); if( stereoEnabled ) { const float screenSeparation = GetScreenSeparationForGuis(); // this will be negated on the alternate eyes, both rendered each frame viewDef->renderView.stereoScreenSeparation = screenSeparation; extern idCVar stereoRender_swapEyes; viewDef->renderView.viewEyeBuffer = 0; // render to both buffers if( stereoRender_swapEyes.GetBool() ) { viewDef->renderView.stereoScreenSeparation = -screenSeparation; } } viewDef->scissor.x1 = 0; viewDef->scissor.y1 = 0; viewDef->scissor.x2 = viewDef->viewport.x2 - viewDef->viewport.x1; viewDef->scissor.y2 = viewDef->viewport.y2 - viewDef->viewport.y1; viewDef->projectionMatrix[0 * 4 + 0] = 2.0f / renderSystem->GetVirtualWidth(); viewDef->projectionMatrix[0 * 4 + 1] = 0.0f; viewDef->projectionMatrix[0 * 4 + 2] = 0.0f; viewDef->projectionMatrix[0 * 4 + 3] = 0.0f; viewDef->projectionMatrix[1 * 4 + 0] = 0.0f; viewDef->projectionMatrix[1 * 4 + 1] = -2.0f / renderSystem->GetVirtualHeight(); viewDef->projectionMatrix[1 * 4 + 2] = 0.0f; viewDef->projectionMatrix[1 * 4 + 3] = 0.0f; viewDef->projectionMatrix[2 * 4 + 0] = 0.0f; viewDef->projectionMatrix[2 * 4 + 1] = 0.0f; viewDef->projectionMatrix[2 * 4 + 2] = -2.0f; viewDef->projectionMatrix[2 * 4 + 3] = 0.0f; viewDef->projectionMatrix[3 * 4 + 0] = -1.0f; viewDef->projectionMatrix[3 * 4 + 1] = 1.0f; viewDef->projectionMatrix[3 * 4 + 2] = -1.0f; viewDef->projectionMatrix[3 * 4 + 3] = 1.0f; // make a tech5 renderMatrix for faster culling idRenderMatrix::Transpose( *( idRenderMatrix* )viewDef->projectionMatrix, viewDef->projectionRenderMatrix ); viewDef->worldSpace.modelMatrix[0 * 4 + 0] = 1.0f; viewDef->worldSpace.modelMatrix[1 * 4 + 1] = 1.0f; viewDef->worldSpace.modelMatrix[2 * 4 + 2] = 1.0f; viewDef->worldSpace.modelMatrix[3 * 4 + 3] = 1.0f; viewDef->worldSpace.modelViewMatrix[0 * 4 + 0] = 1.0f; viewDef->worldSpace.modelViewMatrix[1 * 4 + 1] = 1.0f; viewDef->worldSpace.modelViewMatrix[2 * 4 + 2] = 1.0f; viewDef->worldSpace.modelViewMatrix[3 * 4 + 3] = 1.0f; viewDef->maxDrawSurfs = surfaces.Num(); viewDef->drawSurfs = ( drawSurf_t** )R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ), FRAME_ALLOC_DRAW_SURFACE_POINTER ); viewDef->numDrawSurfs = 0; #if 1 // RB: give renderView the current time to calculate 2D shader effects int shaderTime = tr.frameShaderTime * 1000; //Sys_Milliseconds(); viewDef->renderView.time[0] = shaderTime; viewDef->renderView.time[1] = shaderTime; // RB end #endif viewDef_t* oldViewDef = tr.viewDef; tr.viewDef = viewDef; EmitSurfaces( viewDef->worldSpace.modelMatrix, viewDef->worldSpace.modelViewMatrix, false /* depthHack */ , stereoEnabled /* stereoDepthSort */, false /* link as entity */ ); tr.viewDef = oldViewDef; // add the command to draw this view R_AddDrawViewCmd( viewDef, true ); }
/* ================= R_AddLights ================= */ void R_AddLights() { SCOPED_PROFILE_EVENT( "R_AddLights" ); //------------------------------------------------- // check each light individually, possibly in parallel //------------------------------------------------- if( r_useParallelAddLights.GetBool() ) { for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )R_AddSingleLight, vLight ); } tr.frontEndJobList->Submit(); tr.frontEndJobList->Wait(); } else { for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { R_AddSingleLight( vLight ); } } //------------------------------------------------- // cull lights from the list if they turned out to not be needed //------------------------------------------------- tr.pc.c_viewLights = 0; viewLight_t** ptr = &tr.viewDef->viewLights; while( *ptr != NULL ) { viewLight_t* vLight = *ptr; if( vLight->removeFromList ) { vLight->lightDef->viewCount = -1; // this probably doesn't matter with current code *ptr = vLight->next; continue; } ptr = &vLight->next; // serial work tr.pc.c_viewLights++; for( shadowOnlyEntity_t* shadEnt = vLight->shadowOnlyViewEntities; shadEnt != NULL; shadEnt = shadEnt->next ) { // this will add it to the viewEntities list, but with an empty scissor rect R_SetEntityDefViewEntity( shadEnt->edef ); } if( r_showLightScissors.GetBool() ) { R_ShowColoredScreenRect( vLight->scissorRect, vLight->lightDef->index ); } } //------------------------------------------------- // Add jobs to setup pre-light shadow volumes. //------------------------------------------------- if( r_useParallelAddShadows.GetInteger() == 1 ) { for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { for( preLightShadowVolumeParms_t* shadowParms = vLight->preLightShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )PreLightShadowVolumeJob, shadowParms ); } vLight->preLightShadowVolumes = NULL; } } else { int start = Sys_Microseconds(); for( viewLight_t* vLight = tr.viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { for( preLightShadowVolumeParms_t* shadowParms = vLight->preLightShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { PreLightShadowVolumeJob( shadowParms ); } vLight->preLightShadowVolumes = NULL; } int end = Sys_Microseconds(); tr.backend.pc.shadowMicroSec += end - start; } }
/* =================== R_AddSingleLight May be run in parallel. Sets vLight->removeFromList to true if the light should be removed from the list. Builds a chain of entities that need to be added for shadows only off vLight->shadowOnlyViewEntities. Allocates and fills in vLight->entityInteractionState. Calc the light shader values, removing any light from the viewLight list if it is determined to not have any visible effect due to being flashed off or turned off. Add any precomputed shadow volumes. =================== */ static void R_AddSingleLight( viewLight_t* vLight ) { // until proven otherwise vLight->removeFromList = true; vLight->shadowOnlyViewEntities = NULL; vLight->preLightShadowVolumes = NULL; // globals we really should pass in... const viewDef_t* viewDef = tr.viewDef; const idRenderLightLocal* light = vLight->lightDef; const idMaterial* lightShader = light->lightShader; if( lightShader == NULL ) { common->Error( "R_AddSingleLight: NULL lightShader" ); return; } SCOPED_PROFILE_EVENT( lightShader->GetName() ); // see if we are suppressing the light in this view if( !r_skipSuppress.GetBool() ) { if( light->parms.suppressLightInViewID && light->parms.suppressLightInViewID == viewDef->renderView.viewID ) { return; } if( light->parms.allowLightInViewID && light->parms.allowLightInViewID != viewDef->renderView.viewID ) { return; } } // evaluate the light shader registers float* lightRegs = ( float* )R_FrameAlloc( lightShader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER ); lightShader->EvaluateRegisters( lightRegs, light->parms.shaderParms, viewDef->renderView.shaderParms, tr.viewDef->renderView.time[0] * 0.001f, light->parms.referenceSound ); // if this is a purely additive light and no stage in the light shader evaluates // to a positive light value, we can completely skip the light if( !lightShader->IsFogLight() && !lightShader->IsBlendLight() ) { int lightStageNum; for( lightStageNum = 0; lightStageNum < lightShader->GetNumStages(); lightStageNum++ ) { const shaderStage_t* lightStage = lightShader->GetStage( lightStageNum ); // ignore stages that fail the condition if( !lightRegs[ lightStage->conditionRegister ] ) { continue; } const int* registers = lightStage->color.registers; // snap tiny values to zero if( lightRegs[ registers[0] ] < 0.001f ) { lightRegs[ registers[0] ] = 0.0f; } if( lightRegs[ registers[1] ] < 0.001f ) { lightRegs[ registers[1] ] = 0.0f; } if( lightRegs[ registers[2] ] < 0.001f ) { lightRegs[ registers[2] ] = 0.0f; } if( lightRegs[ registers[0] ] > 0.0f || lightRegs[ registers[1] ] > 0.0f || lightRegs[ registers[2] ] > 0.0f ) { break; } } if( lightStageNum == lightShader->GetNumStages() ) { // we went through all the stages and didn't find one that adds anything // remove the light from the viewLights list, and change its frame marker // so interaction generation doesn't think the light is visible and // create a shadow for it return; } } //-------------------------------------------- // copy data used by backend //-------------------------------------------- vLight->globalLightOrigin = light->globalLightOrigin; vLight->lightProject[0] = light->lightProject[0]; vLight->lightProject[1] = light->lightProject[1]; vLight->lightProject[2] = light->lightProject[2]; vLight->lightProject[3] = light->lightProject[3]; // the fog plane is the light far clip plane idPlane fogPlane( light->baseLightProject[2][0] - light->baseLightProject[3][0], light->baseLightProject[2][1] - light->baseLightProject[3][1], light->baseLightProject[2][2] - light->baseLightProject[3][2], light->baseLightProject[2][3] - light->baseLightProject[3][3] ); const float planeScale = idMath::InvSqrt( fogPlane.Normal().LengthSqr() ); vLight->fogPlane[0] = fogPlane[0] * planeScale; vLight->fogPlane[1] = fogPlane[1] * planeScale; vLight->fogPlane[2] = fogPlane[2] * planeScale; vLight->fogPlane[3] = fogPlane[3] * planeScale; // copy the matrix for deforming the 'zeroOneCubeModel' to exactly cover the light volume in world space vLight->inverseBaseLightProject = light->inverseBaseLightProject; // RB begin vLight->baseLightProject = light->baseLightProject; vLight->pointLight = light->parms.pointLight; vLight->parallel = light->parms.parallel; vLight->lightCenter = light->parms.lightCenter; // RB end vLight->falloffImage = light->falloffImage; vLight->lightShader = light->lightShader; vLight->shaderRegisters = lightRegs; const bool lightCastsShadows = light->LightCastsShadows(); if( r_useLightScissors.GetInteger() != 0 ) { // Calculate the matrix that projects the zero-to-one cube to exactly cover the // light frustum in clip space. idRenderMatrix invProjectMVPMatrix; idRenderMatrix::Multiply( viewDef->worldSpace.mvp, light->inverseBaseLightProject, invProjectMVPMatrix ); // Calculate the projected bounds, either not clipped at all, near clipped, or fully clipped. idBounds projected; if( r_useLightScissors.GetInteger() == 1 ) { idRenderMatrix::ProjectedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube ); } else if( r_useLightScissors.GetInteger() == 2 ) { idRenderMatrix::ProjectedNearClippedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube ); } else { idRenderMatrix::ProjectedFullyClippedBounds( projected, invProjectMVPMatrix, bounds_zeroOneCube ); } if( projected[0][2] >= projected[1][2] ) { // the light was culled to the view frustum return; } float screenWidth = ( float )viewDef->viewport.x2 - ( float )viewDef->viewport.x1; float screenHeight = ( float )viewDef->viewport.y2 - ( float )viewDef->viewport.y1; idScreenRect lightScissorRect; lightScissorRect.x1 = idMath::Ftoi( projected[0][0] * screenWidth ); lightScissorRect.x2 = idMath::Ftoi( projected[1][0] * screenWidth ); lightScissorRect.y1 = idMath::Ftoi( projected[0][1] * screenHeight ); lightScissorRect.y2 = idMath::Ftoi( projected[1][1] * screenHeight ); lightScissorRect.Expand(); vLight->scissorRect.Intersect( lightScissorRect ); vLight->scissorRect.zmin = projected[0][2]; vLight->scissorRect.zmax = projected[1][2]; // RB: calculate shadow LOD similar to Q3A .md3 LOD code vLight->shadowLOD = 0; if( r_useShadowMapping.GetBool() && lightCastsShadows ) { float flod, lodscale; float projectedRadius; int lod; int numLods; numLods = MAX_SHADOWMAP_RESOLUTIONS; // compute projected bounding sphere // and use that as a criteria for selecting LOD idVec3 center = projected.GetCenter(); projectedRadius = projected.GetRadius( center ); if( projectedRadius > 1.0f ) { projectedRadius = 1.0f; } if( projectedRadius != 0 ) { lodscale = r_shadowMapLodScale.GetFloat(); if( lodscale > 20 ) lodscale = 20; flod = 1.0f - projectedRadius * lodscale; } else { // object intersects near view plane, e.g. view weapon flod = 0; } flod *= numLods; if( flod < 0 ) { flod = 0; } lod = idMath::Ftoi( flod ); if( lod >= numLods ) { //lod = numLods - 1; } lod += r_shadowMapLodBias.GetInteger(); if( lod < 0 ) { lod = 0; } if( lod >= numLods ) { // don't draw any shadow //lod = -1; lod = numLods - 1; } // 2048^2 ultra quality is only for cascaded shadow mapping with sun lights if( lod == 0 && !light->parms.parallel ) { lod = 1; } vLight->shadowLOD = lod; } // RB end } // this one stays on the list vLight->removeFromList = false; //-------------------------------------------- // create interactions with all entities the light may touch, and add viewEntities // that may cast shadows, even if they aren't directly visible. Any real work // will be deferred until we walk through the viewEntities //-------------------------------------------- const int renderViewID = viewDef->renderView.viewID; // this bool array will be set true whenever the entity will visibly interact with the light vLight->entityInteractionState = ( byte* )R_ClearedFrameAlloc( light->world->entityDefs.Num() * sizeof( vLight->entityInteractionState[0] ), FRAME_ALLOC_INTERACTION_STATE ); idInteraction** const interactionTableRow = light->world->interactionTable + light->index * light->world->interactionTableWidth; for( areaReference_t* lref = light->references; lref != NULL; lref = lref->ownerNext ) { portalArea_t* area = lref->area; // some lights have their center of projection outside the world, but otherwise // we want to ignore areas that are not connected to the light center due to a closed door if( light->areaNum != -1 && r_useAreasConnectedForShadowCulling.GetInteger() == 2 ) { if( !light->world->AreasAreConnected( light->areaNum, area->areaNum, PS_BLOCK_VIEW ) ) { // can't possibly be seen or shadowed continue; } } // check all the models in this area for( areaReference_t* eref = area->entityRefs.areaNext; eref != &area->entityRefs; eref = eref->areaNext ) { idRenderEntityLocal* edef = eref->entity; if( vLight->entityInteractionState[ edef->index ] != viewLight_t::INTERACTION_UNCHECKED ) { continue; } // until proven otherwise vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_NO; // The table is updated at interaction::AllocAndLink() and interaction::UnlinkAndFree() const idInteraction* inter = interactionTableRow[ edef->index ]; const renderEntity_t& eParms = edef->parms; const idRenderModel* eModel = eParms.hModel; // a large fraction of static entity / light pairs will still have no interactions even though // they are both present in the same area(s) if( eModel != NULL && !eModel->IsDynamicModel() && inter == INTERACTION_EMPTY ) { // the interaction was statically checked, and it didn't generate any surfaces, // so there is no need to force the entity onto the view list if it isn't // already there continue; } // We don't want the lights on weapons to illuminate anything else. // There are two assumptions here -- that allowLightInViewID is only // used for weapon lights, and that all weapons will have weaponDepthHack. // A more general solution would be to have an allowLightOnEntityID field. // HACK: the armor-mounted flashlight is a private spot light, which is probably // wrong -- you would expect to see them in multiplayer. // if( light->parms.allowLightInViewID && light->parms.pointLight && !eParms.weaponDepthHack ) // { // continue; // } // non-shadow casting entities don't need to be added if they aren't // directly visible if( ( eParms.noShadow || ( eModel && !eModel->ModelHasShadowCastingSurfaces() ) ) && !edef->IsDirectlyVisible() ) { continue; } // if the model doesn't accept lighting or cast shadows, it doesn't need to be added if( eModel && !eModel->ModelHasInteractingSurfaces() && !eModel->ModelHasShadowCastingSurfaces() ) { continue; } // no interaction present, so either the light or entity has moved // assert( lightHasMoved || edef->entityHasMoved ); if( inter == NULL ) { // some big outdoor meshes are flagged to not create any dynamic interactions // when the level designer knows that nearby moving lights shouldn't actually hit them if( eParms.noDynamicInteractions ) { continue; } // do a check of the entity reference bounds against the light frustum to see if they can't // possibly interact, despite sharing one or more world areas if( R_CullModelBoundsToLight( light, edef->localReferenceBounds, edef->modelRenderMatrix ) ) { continue; } } // we now know that the entity and light do overlap if( edef->IsDirectlyVisible() ) { // entity is directly visible, so the interaction is definitely needed vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_YES; continue; } // the entity is not directly visible, but if we can tell that it may cast // shadows onto visible surfaces, we must make a viewEntity for it if( !lightCastsShadows ) { // surfaces are never shadowed in this light continue; } // if we are suppressing its shadow in this view (player shadows, etc), skip if( !r_skipSuppress.GetBool() ) { if( eParms.suppressShadowInViewID && eParms.suppressShadowInViewID == renderViewID ) { continue; } if( eParms.suppressShadowInLightID && eParms.suppressShadowInLightID == light->parms.lightId ) { continue; } } // should we use the shadow bounds from pre-calculated interactions? idBounds shadowBounds; R_ShadowBounds( edef->globalReferenceBounds, light->globalLightBounds, light->globalLightOrigin, shadowBounds ); // this test is pointless if we knew the light was completely contained // in the view frustum, but the entity would also be directly visible in most // of those cases. // this doesn't say that the shadow can't effect anything, only that it can't // effect anything in the view, so we shouldn't set up a view entity if( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, shadowBounds ) ) { continue; } // debug tool to allow viewing of only one entity at a time if( r_singleEntity.GetInteger() >= 0 && r_singleEntity.GetInteger() != edef->index ) { continue; } // we do need it for shadows vLight->entityInteractionState[ edef->index ] = viewLight_t::INTERACTION_YES; // we will need to create a viewEntity_t for it in the serial code section shadowOnlyEntity_t* shadEnt = ( shadowOnlyEntity_t* )R_FrameAlloc( sizeof( shadowOnlyEntity_t ), FRAME_ALLOC_SHADOW_ONLY_ENTITY ); shadEnt->next = vLight->shadowOnlyViewEntities; shadEnt->edef = edef; vLight->shadowOnlyViewEntities = shadEnt; } } //-------------------------------------------- // add the prelight shadows for the static world geometry //-------------------------------------------- if( light->parms.prelightModel != NULL && !r_useShadowMapping.GetBool() ) { srfTriangles_t* tri = light->parms.prelightModel->Surface( 0 )->geometry; // these shadows will have valid bounds, and can be culled normally, // but they will typically cover most of the light's bounds if( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, tri->bounds ) ) { return; } // prelight models should always have static data that never gets purged assert( vertexCache.CacheIsCurrent( tri->shadowCache ) ); assert( vertexCache.CacheIsCurrent( tri->indexCache ) ); drawSurf_t* shadowDrawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *shadowDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); shadowDrawSurf->frontEndGeo = tri; shadowDrawSurf->ambientCache = 0; shadowDrawSurf->indexCache = tri->indexCache; shadowDrawSurf->shadowCache = tri->shadowCache; shadowDrawSurf->jointCache = 0; shadowDrawSurf->numIndexes = 0; shadowDrawSurf->space = &viewDef->worldSpace; shadowDrawSurf->material = NULL; shadowDrawSurf->extraGLState = 0; shadowDrawSurf->shaderRegisters = NULL; shadowDrawSurf->scissorRect = vLight->scissorRect; // default to the light scissor and light depth bounds shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE; // assume the shadow volume is done in case r_skipPrelightShadows is set if( !r_skipPrelightShadows.GetBool() ) { preLightShadowVolumeParms_t* shadowParms = ( preLightShadowVolumeParms_t* )R_FrameAlloc( sizeof( shadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); shadowParms->verts = tri->preLightShadowVertexes; shadowParms->numVerts = tri->numVerts * 2; shadowParms->indexes = tri->indexes; shadowParms->numIndexes = tri->numIndexes; shadowParms->triangleBounds = tri->bounds; shadowParms->triangleMVP = viewDef->worldSpace.mvp; shadowParms->localLightOrigin = vLight->globalLightOrigin; shadowParms->localViewOrigin = viewDef->renderView.vieworg; shadowParms->zNear = r_znear.GetFloat(); shadowParms->lightZMin = vLight->scissorRect.zmin; shadowParms->lightZMax = vLight->scissorRect.zmax; shadowParms->forceShadowCaps = r_forceShadowCaps.GetBool(); shadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool(); shadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool(); shadowParms->numShadowIndices = & shadowDrawSurf->numIndexes; shadowParms->renderZFail = & shadowDrawSurf->renderZFail; shadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin; shadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax; shadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState; // the pre-light shadow volume "_prelight_light_3297" in "d3xpdm2" is malformed in that it contains the light origin so the precise inside test always fails if( tr.primaryWorld->mapName.IcmpPath( "maps/game/mp/d3xpdm2.map" ) == 0 && idStr::Icmp( light->parms.prelightModel->Name(), "_prelight_light_3297" ) == 0 ) { shadowParms->useShadowPreciseInsideTest = false; } shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; shadowParms->next = vLight->preLightShadowVolumes; vLight->preLightShadowVolumes = shadowParms; } // actually link it in shadowDrawSurf->nextOnLight = vLight->globalShadows; vLight->globalShadows = shadowDrawSurf; } }
/* =============== idGameThread::Run Run in a background thread for performance, but can also be called directly in the foreground thread for comparison. =============== */ int idGameThread::Run() { commonLocal.frameTiming.startGameTime = Sys_Microseconds(); // debugging tool to test frame dropping behavior if ( com_sleepGame.GetInteger() ) { Sys_Sleep( com_sleepGame.GetInteger() ); } if ( numGameFrames == 0 ) { // Ensure there's no stale gameReturn data from a paused game ret = gameReturn_t(); } if ( isClient ) { // run the game logic for ( int i = 0; i < numGameFrames; i++ ) { SCOPED_PROFILE_EVENT( "Client Prediction" ); if ( userCmdMgr ) { game->ClientRunFrame( *userCmdMgr, ( i == numGameFrames - 1 ), ret ); } if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) { break; } } } else { // run the game logic for ( int i = 0; i < numGameFrames; i++ ) { SCOPED_PROFILE_EVENT( "GameTic" ); if ( userCmdMgr ) { game->RunFrame( *userCmdMgr, ret ); } if ( ret.syncNextGameFrame || ret.sessionCommand[0] != 0 ) { break; } } } // we should have consumed all of our usercmds if ( userCmdMgr ) { if ( userCmdMgr->HasUserCmdForPlayer( game->GetLocalClientNum() ) && common->GetCurrentGame() == DOOM3_BFG ) { idLib::Printf( "idGameThread::Run: didn't consume all usercmds\n" ); } } commonLocal.frameTiming.finishGameTime = Sys_Microseconds(); SetThreadGameTime( ( commonLocal.frameTiming.finishGameTime - commonLocal.frameTiming.startGameTime ) / 1000 ); // build render commands and geometry { SCOPED_PROFILE_EVENT( "Draw" ); commonLocal.Draw(); } commonLocal.frameTiming.finishDrawTime = Sys_Microseconds(); SetThreadRenderTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.finishGameTime ) / 1000 ); SetThreadTotalTime( ( commonLocal.frameTiming.finishDrawTime - commonLocal.frameTiming.startGameTime ) / 1000 ); return 0; }
/* ================= idCommonLocal::Frame ================= */ void idCommonLocal::Frame() { try { SCOPED_PROFILE_EVENT( "Common::Frame" ); // This is the only place this is incremented idLib::frameNumber++; // allow changing SIMD usage on the fly if ( com_forceGenericSIMD.IsModified() ) { idSIMD::InitProcessor( "doom", com_forceGenericSIMD.GetBool() ); com_forceGenericSIMD.ClearModified(); } // Do the actual switch between Doom 3 and the classics here so // that things don't get confused in the middle of the frame. PerformGameSwitch(); // pump all the events Sys_GenerateEvents(); // write config file if anything changed WriteConfiguration(); eventLoop->RunEventLoop(); // Activate the shell if it's been requested if ( showShellRequested && game ) { game->Shell_Show( true ); showShellRequested = false; } // if the console or another gui is down, we don't need to hold the mouse cursor bool chatting = false; if ( console->Active() || Dialog().IsDialogActive() || session->IsSystemUIShowing() || ( game && game->InhibitControls() && !IsPlayingDoomClassic() ) ) { Sys_GrabMouseCursor( false ); usercmdGen->InhibitUsercmd( INHIBIT_SESSION, true ); chatting = true; } else { Sys_GrabMouseCursor( true ); usercmdGen->InhibitUsercmd( INHIBIT_SESSION, false ); } const bool pauseGame = ( !mapSpawned || ( !IsMultiplayer() && ( Dialog().IsDialogPausing() || session->IsSystemUIShowing() || ( game && game->Shell_IsActive() ) ) ) ) && !IsPlayingDoomClassic(); // save the screenshot and audio from the last draw if needed if ( aviCaptureMode ) { idStr name = va("demos/%s/%s_%05i.tga", aviDemoShortName.c_str(), aviDemoShortName.c_str(), aviDemoFrameCount++ ); renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); // remove any printed lines at the top before taking the screenshot console->ClearNotifyLines(); // this will call Draw, possibly multiple times if com_aviDemoSamples is > 1 renderSystem->TakeScreenshot( com_aviDemoWidth.GetInteger(), com_aviDemoHeight.GetInteger(), name, com_aviDemoSamples.GetInteger(), NULL ); } //-------------------------------------------- // wait for the GPU to finish drawing // // It is imporant to minimize the time spent between this // section and the call to renderSystem->RenderCommandBuffers(), // because the GPU is completely idle. //-------------------------------------------- // this should exit right after vsync, with the GPU idle and ready to draw // This may block if the GPU isn't finished renderng the previous frame. frameTiming.startSyncTime = Sys_Microseconds(); const emptyCommand_t * renderCommands = NULL; if ( com_smp.GetBool() ) { renderCommands = renderSystem->SwapCommandBuffers( &time_frontend, &time_backend, &time_shadows, &time_gpu ); } else { // the GPU will stay idle through command generation for minimal // input latency renderSystem->SwapCommandBuffers_FinishRendering( &time_frontend, &time_backend, &time_shadows, &time_gpu ); } frameTiming.finishSyncTime = Sys_Microseconds(); //-------------------------------------------- // Determine how many game tics we are going to run, // now that the previous frame is completely finished. // // It is important that any waiting on the GPU be done // before this, or there will be a bad stuttering when // dropping frames for performance management. //-------------------------------------------- // input: // thisFrameTime // com_noSleep // com_engineHz // com_fixedTic // com_deltaTimeClamp // IsMultiplayer // // in/out state: // gameFrame // gameTimeResidual // lastFrameTime // syncNextFrame // // Output: // numGameFrames // How many game frames to run int numGameFrames = 0; for(;;) { const int thisFrameTime = Sys_Milliseconds(); static int lastFrameTime = thisFrameTime; // initialized only the first time const int deltaMilliseconds = thisFrameTime - lastFrameTime; lastFrameTime = thisFrameTime; // if there was a large gap in time since the last frame, or the frame // rate is very very low, limit the number of frames we will run const int clampedDeltaMilliseconds = Min( deltaMilliseconds, com_deltaTimeClamp.GetInteger() ); gameTimeResidual += clampedDeltaMilliseconds * timescale.GetFloat(); // don't run any frames when paused if ( pauseGame ) { gameFrame++; gameTimeResidual = 0; break; } // debug cvar to force multiple game tics if ( com_fixedTic.GetInteger() > 0 ) { numGameFrames = com_fixedTic.GetInteger(); gameFrame += numGameFrames; gameTimeResidual = 0; break; } if ( syncNextGameFrame ) { // don't sleep at all syncNextGameFrame = false; gameFrame++; numGameFrames++; gameTimeResidual = 0; break; } for ( ;; ) { // How much time to wait before running the next frame, // based on com_engineHz const int frameDelay = FRAME_TO_MSEC( gameFrame + 1 ) - FRAME_TO_MSEC( gameFrame ); if ( gameTimeResidual < frameDelay ) { break; } gameTimeResidual -= frameDelay; gameFrame++; numGameFrames++; // if there is enough residual left, we may run additional frames } if ( numGameFrames > 0 ) { // ready to actually run them break; } // if we are vsyncing, we always want to run at least one game // frame and never sleep, which might happen due to scheduling issues // if we were just looking at real time. if ( com_noSleep.GetBool() ) { numGameFrames = 1; gameFrame += numGameFrames; gameTimeResidual = 0; break; } // not enough time has passed to run a frame, as might happen if // we don't have vsync on, or the monitor is running at 120hz while // com_engineHz is 60, so sleep a bit and check again Sys_Sleep( 0 ); } //-------------------------------------------- // It would be better to push as much of this as possible // either before or after the renderSystem->SwapCommandBuffers(), // because the GPU is completely idle. //-------------------------------------------- // Update session and syncronize to the new session state after sleeping session->UpdateSignInManager(); session->Pump(); session->ProcessSnapAckQueue(); if ( session->GetState() == idSession::LOADING ) { // If the session reports we should be loading a map, load it! ExecuteMapChange(); mapSpawnData.savegameFile = NULL; mapSpawnData.persistentPlayerInfo.Clear(); return; } else if ( session->GetState() != idSession::INGAME && mapSpawned ) { // If the game is running, but the session reports we are not in a game, disconnect // This happens when a server disconnects us or we sign out LeaveGame(); return; } if ( mapSpawned && !pauseGame ) { if ( IsClient() ) { RunNetworkSnapshotFrame(); } } ExecuteReliableMessages(); // send frame and mouse events to active guis GuiFrameEvents(); //-------------------------------------------- // Prepare usercmds and kick off the game processing // in a background thread //-------------------------------------------- // get the previous usercmd for bypassed head tracking transform const usercmd_t previousCmd = usercmdGen->GetCurrentUsercmd(); // build a new usercmd int deviceNum = session->GetSignInManager().GetMasterInputDevice(); usercmdGen->BuildCurrentUsercmd( deviceNum ); if ( deviceNum == -1 ) { for ( int i = 0; i < MAX_INPUT_DEVICES; i++ ) { Sys_PollJoystickInputEvents( i ); Sys_EndJoystickInputEvents(); } } if ( pauseGame ) { usercmdGen->Clear(); } usercmd_t newCmd = usercmdGen->GetCurrentUsercmd(); // Store server game time - don't let time go past last SS time in case we are extrapolating if ( IsClient() ) { newCmd.serverGameMilliseconds = std::min( Game()->GetServerGameTimeMs(), Game()->GetSSEndTime() ); } else { newCmd.serverGameMilliseconds = Game()->GetServerGameTimeMs(); } userCmdMgr.MakeReadPtrCurrentForPlayer( Game()->GetLocalClientNum() ); // Stuff a copy of this userCmd for each game frame we are going to run. // Ideally, the usercmds would be built in another thread so you could // still get 60hz control accuracy when the game is running slower. for ( int i = 0 ; i < numGameFrames ; i++ ) { newCmd.clientGameMilliseconds = FRAME_TO_MSEC( gameFrame-numGameFrames+i+1 ); userCmdMgr.PutUserCmdForPlayer( game->GetLocalClientNum(), newCmd ); } // If we're in Doom or Doom 2, run tics and upload the new texture. if ( ( GetCurrentGame() == DOOM_CLASSIC || GetCurrentGame() == DOOM2_CLASSIC ) && !( Dialog().IsDialogPausing() || session->IsSystemUIShowing() ) ) { RunDoomClassicFrame(); } // start the game / draw command generation thread going in the background gameReturn_t ret = gameThread.RunGameAndDraw( numGameFrames, userCmdMgr, IsClient(), gameFrame - numGameFrames ); if ( !com_smp.GetBool() ) { // in non-smp mode, run the commands we just generated, instead of // frame-delayed ones from a background thread renderCommands = renderSystem->SwapCommandBuffers_FinishCommandBuffers(); } //---------------------------------------- // Run the render back end, getting the GPU busy with new commands // ASAP to minimize the pipeline bubble. //---------------------------------------- frameTiming.startRenderTime = Sys_Microseconds(); renderSystem->RenderCommandBuffers( renderCommands ); if ( com_sleepRender.GetInteger() > 0 ) { // debug tool to test frame adaption Sys_Sleep( com_sleepRender.GetInteger() ); } frameTiming.finishRenderTime = Sys_Microseconds(); // make sure the game / draw thread has completed // This may block if the game is taking longer than the render back end gameThread.WaitForThread(); // Send local usermds to the server. // This happens after the game frame has run so that prediction data is up to date. SendUsercmds( Game()->GetLocalClientNum() ); // Now that we have an updated game frame, we can send out new snapshots to our clients session->Pump(); // Pump to get updated usercmds to relay SendSnapshots(); // Render the sound system using the latest commands from the game thread if ( pauseGame ) { soundWorld->Pause(); soundSystem->SetPlayingSoundWorld( menuSoundWorld ); } else { soundWorld->UnPause(); soundSystem->SetPlayingSoundWorld( soundWorld ); } soundSystem->Render(); // process the game return for map changes, etc ProcessGameReturn( ret ); idLobbyBase & lobby = session->GetActivePlatformLobbyBase(); if ( lobby.HasActivePeers() ) { if ( net_drawDebugHud.GetInteger() == 1 ) { lobby.DrawDebugNetworkHUD(); } if ( net_drawDebugHud.GetInteger() == 2 ) { lobby.DrawDebugNetworkHUD2(); } lobby.DrawDebugNetworkHUD_ServerSnapshotMetrics( net_drawDebugHud.GetInteger() == 3 ); } // report timing information if ( com_speeds.GetBool() ) { static int lastTime = Sys_Milliseconds(); int nowTime = Sys_Milliseconds(); int com_frameMsec = nowTime - lastTime; lastTime = nowTime; Printf( "frame:%d all:%3d gfr:%3d rf:%3lld bk:%3lld\n", idLib::frameNumber, com_frameMsec, time_gameFrame, time_frontend / 1000, time_backend / 1000 ); time_gameFrame = 0; time_gameDraw = 0; } // the FPU stack better be empty at this point or some bad code or compiler bug left values on the stack if ( !Sys_FPU_StackIsEmpty() ) { Printf( Sys_FPU_GetState() ); FatalError( "idCommon::Frame: the FPU stack is not empty at the end of the frame\n" ); } mainFrameTiming = frameTiming; session->GetSaveGameManager().Pump(); } catch( idException & ) { return; // an ERP_DROP was thrown } }
/* =============== idCommonLocal::Draw =============== */ void idCommonLocal::Draw() { // debugging tool to test frame dropping behavior if ( com_sleepDraw.GetInteger() ) { Sys_Sleep( com_sleepDraw.GetInteger() ); } if ( loadGUI != NULL ) { loadGUI->Render( renderSystem, Sys_Milliseconds() ); } else if ( currentGame == DOOM_CLASSIC || currentGame == DOOM2_CLASSIC ) { const float sysWidth = renderSystem->GetWidth() * renderSystem->GetPixelAspect(); const float sysHeight = renderSystem->GetHeight(); const float sysAspect = sysWidth / sysHeight; const float doomAspect = 4.0f / 3.0f; const float adjustment = sysAspect / doomAspect; const float barHeight = ( adjustment >= 1.0f ) ? 0.0f : ( 1.0f - adjustment ) * (float)SCREEN_HEIGHT * 0.25f; const float barWidth = ( adjustment <= 1.0f ) ? 0.0f : ( adjustment - 1.0f ) * (float)SCREEN_WIDTH * 0.25f; if ( barHeight > 0.0f ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial ); renderSystem->DrawStretchPic( 0, SCREEN_HEIGHT - barHeight, SCREEN_WIDTH, barHeight, 0, 0, 1, 1, whiteMaterial ); } if ( barWidth > 0.0f ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); renderSystem->DrawStretchPic( SCREEN_WIDTH - barWidth, 0, barWidth, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); } renderSystem->SetColor4( 1, 1, 1, 1 ); renderSystem->DrawStretchPic( barWidth, barHeight, SCREEN_WIDTH - barWidth * 2.0f, SCREEN_HEIGHT - barHeight * 2.0f, 0, 0, 1, 1, doomClassicMaterial ); } else if ( game && game->Shell_IsActive() ) { bool gameDraw = game->Draw( game->GetLocalClientNum() ); if ( !gameDraw ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); } game->Shell_Render(); } else if ( readDemo ) { renderWorld->RenderScene( ¤tDemoRenderView ); renderSystem->DrawDemoPics(); } else if ( mapSpawned ) { bool gameDraw = false; // normal drawing for both single and multi player if ( !com_skipGameDraw.GetBool() && Game()->GetLocalClientNum() >= 0 ) { // draw the game view int start = Sys_Milliseconds(); if ( game ) { gameDraw = game->Draw( Game()->GetLocalClientNum() ); } int end = Sys_Milliseconds(); time_gameDraw += ( end - start ); // note time used for com_speeds } if ( !gameDraw ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, 640, 480, 0, 0, 1, 1, whiteMaterial ); } // save off the 2D drawing from the game if ( writeDemo ) { renderSystem->WriteDemoPics(); } } else { renderSystem->SetColor4( 0, 0, 0, 1 ); renderSystem->DrawStretchPic( 0, 0, SCREEN_WIDTH, SCREEN_HEIGHT, 0, 0, 1, 1, whiteMaterial ); } { SCOPED_PROFILE_EVENT( "Post-Draw" ); // draw the wipe material on top of this if it hasn't completed yet DrawWipeModel(); Dialog().Render( loadGUI != NULL ); // draw the half console / notify console on top of everything console->Draw( false ); } }
/* =================== R_EntityDefDynamicModel This is also called by the game code for idRenderWorldLocal::ModelTrace(), and idRenderWorldLocal::Trace() which is bad for performance... Issues a deferred entity callback if necessary. If the model isn't dynamic, it returns the original. Returns the cached dynamic model if present, otherwise creates it. =================== */ idRenderModel* R_EntityDefDynamicModel( idRenderEntityLocal* def ) { if( def->dynamicModelFrameCount == tr.frameCount ) { return def->dynamicModel; } // allow deferred entities to construct themselves bool callbackUpdate; if( def->parms.callback != NULL ) { SCOPED_PROFILE_EVENT( "R_IssueEntityDefCallback" ); callbackUpdate = R_IssueEntityDefCallback( def ); } else { callbackUpdate = false; } idRenderModel* model = def->parms.hModel; if( model == NULL ) { common->Error( "R_EntityDefDynamicModel: NULL model" ); return NULL; } if( model->IsDynamicModel() == DM_STATIC ) { def->dynamicModel = NULL; def->dynamicModelFrameCount = 0; return model; } // continously animating models (particle systems, etc) will have their snapshot updated every single view if( callbackUpdate || ( model->IsDynamicModel() == DM_CONTINUOUS && def->dynamicModelFrameCount != tr.frameCount ) ) { R_ClearEntityDefDynamicModel( def ); } // if we don't have a snapshot of the dynamic model, generate it now if( def->dynamicModel == NULL ) { SCOPED_PROFILE_EVENT( "InstantiateDynamicModel" ); // instantiate the snapshot of the dynamic model, possibly reusing memory from the cached snapshot def->cachedDynamicModel = model->InstantiateDynamicModel( &def->parms, tr.viewDef, def->cachedDynamicModel ); if( def->cachedDynamicModel != NULL && r_checkBounds.GetBool() ) { idBounds b = def->cachedDynamicModel->Bounds(); if( b[0][0] < def->localReferenceBounds[0][0] - CHECK_BOUNDS_EPSILON || b[0][1] < def->localReferenceBounds[0][1] - CHECK_BOUNDS_EPSILON || b[0][2] < def->localReferenceBounds[0][2] - CHECK_BOUNDS_EPSILON || b[1][0] > def->localReferenceBounds[1][0] + CHECK_BOUNDS_EPSILON || b[1][1] > def->localReferenceBounds[1][1] + CHECK_BOUNDS_EPSILON || b[1][2] > def->localReferenceBounds[1][2] + CHECK_BOUNDS_EPSILON ) { common->Printf( "entity %i dynamic model exceeded reference bounds\n", def->index ); } } def->dynamicModel = def->cachedDynamicModel; def->dynamicModelFrameCount = tr.frameCount; } // set model depth hack value if( def->dynamicModel != NULL && model->DepthHack() != 0.0f && tr.viewDef != NULL ) { idPlane eye, clip; idVec3 ndc; R_TransformModelToClip( def->parms.origin, tr.viewDef->worldSpace.modelViewMatrix, tr.viewDef->projectionMatrix, eye, clip ); R_TransformClipToDevice( clip, ndc ); def->parms.modelDepthHack = model->DepthHack() * ( 1.0f - ndc.z ); } else { def->parms.modelDepthHack = 0.0f; } return def->dynamicModel; }
/* =================== R_AddSingleModel May be run in parallel. Here is where dynamic models actually get instantiated, and necessary interaction surfaces get created. This is all done on a sort-by-model basis to keep source data in cache (most likely L2) as any interactions and shadows are generated, since dynamic models will typically be lit by two or more lights. =================== */ void R_AddSingleModel( viewEntity_t* vEntity ) { // we will add all interaction surfs here, to be chained to the lights in later serial code vEntity->drawSurfs = NULL; vEntity->staticShadowVolumes = NULL; vEntity->dynamicShadowVolumes = NULL; // globals we really should pass in... const viewDef_t* viewDef = tr.viewDef; idRenderEntityLocal* entityDef = vEntity->entityDef; const renderEntity_t* renderEntity = &entityDef->parms; const idRenderWorldLocal* world = entityDef->world; if( viewDef->isXraySubview && entityDef->parms.xrayIndex == 1 ) { return; } else if( !viewDef->isXraySubview && entityDef->parms.xrayIndex == 2 ) { return; } SCOPED_PROFILE_EVENT( renderEntity->hModel == NULL ? "Unknown Model" : renderEntity->hModel->Name() ); // calculate the znear for testing whether or not the view is inside a shadow projection const float znear = ( viewDef->renderView.cramZNear ) ? ( r_znear.GetFloat() * 0.25f ) : r_znear.GetFloat(); // if the entity wasn't seen through a portal chain, it was added just for light shadows const bool modelIsVisible = !vEntity->scissorRect.IsEmpty(); const bool addInteractions = modelIsVisible && ( !viewDef->isXraySubview || entityDef->parms.xrayIndex == 2 ); const int entityIndex = entityDef->index; //--------------------------- // Find which of the visible lights contact this entity // // If the entity doesn't accept light or cast shadows from any surface, // this can be skipped. // // OPTIMIZE: world areas can assume all referenced lights are used //--------------------------- int numContactedLights = 0; static const int MAX_CONTACTED_LIGHTS = 128; viewLight_t* contactedLights[MAX_CONTACTED_LIGHTS]; idInteraction* staticInteractions[MAX_CONTACTED_LIGHTS]; if( renderEntity->hModel == NULL || renderEntity->hModel->ModelHasInteractingSurfaces() || renderEntity->hModel->ModelHasShadowCastingSurfaces() ) { SCOPED_PROFILE_EVENT( "Find lights" ); for( viewLight_t* vLight = viewDef->viewLights; vLight != NULL; vLight = vLight->next ) { if( vLight->scissorRect.IsEmpty() ) { continue; } if( vLight->entityInteractionState != NULL ) { // new code path, everything was done in AddLight if( vLight->entityInteractionState[entityIndex] == viewLight_t::INTERACTION_YES ) { contactedLights[numContactedLights] = vLight; staticInteractions[numContactedLights] = world->interactionTable[vLight->lightDef->index * world->interactionTableWidth + entityIndex]; if( ++numContactedLights == MAX_CONTACTED_LIGHTS ) { break; } } continue; } const idRenderLightLocal* lightDef = vLight->lightDef; if( !lightDef->globalLightBounds.IntersectsBounds( entityDef->globalReferenceBounds ) ) { continue; } if( R_CullModelBoundsToLight( lightDef, entityDef->localReferenceBounds, entityDef->modelRenderMatrix ) ) { continue; } if( !modelIsVisible ) { // some lights have their center of projection outside the world if( lightDef->areaNum != -1 ) { // if no part of the model is in an area that is connected to // the light center (it is behind a solid, closed door), we can ignore it bool areasConnected = false; for( areaReference_t* ref = entityDef->entityRefs; ref != NULL; ref = ref->ownerNext ) { if( world->AreasAreConnected( lightDef->areaNum, ref->area->areaNum, PS_BLOCK_VIEW ) ) { areasConnected = true; break; } } if( areasConnected == false ) { // can't possibly be seen or shadowed continue; } } // check more precisely for shadow visibility idBounds shadowBounds; R_ShadowBounds( entityDef->globalReferenceBounds, lightDef->globalLightBounds, lightDef->globalLightOrigin, shadowBounds ); // this doesn't say that the shadow can't effect anything, only that it can't // effect anything in the view if( idRenderMatrix::CullBoundsToMVP( viewDef->worldSpace.mvp, shadowBounds ) ) { continue; } } contactedLights[numContactedLights] = vLight; staticInteractions[numContactedLights] = world->interactionTable[vLight->lightDef->index * world->interactionTableWidth + entityIndex]; if( ++numContactedLights == MAX_CONTACTED_LIGHTS ) { break; } } } // if we aren't visible and none of the shadows stretch into the view, // we don't need to do anything else if( !modelIsVisible && numContactedLights == 0 ) { return; } //--------------------------- // create a dynamic model if the geometry isn't static //--------------------------- idRenderModel* model = R_EntityDefDynamicModel( entityDef ); if( model == NULL || model->NumSurfaces() <= 0 ) { return; } // add the lightweight blood decal surfaces if the model is directly visible if( modelIsVisible ) { assert( !vEntity->scissorRect.IsEmpty() ); if( entityDef->decals != NULL && !r_skipDecals.GetBool() ) { entityDef->decals->CreateDeferredDecals( model ); unsigned int numDrawSurfs = entityDef->decals->GetNumDecalDrawSurfs(); for( unsigned int i = 0; i < numDrawSurfs; i++ ) { drawSurf_t* decalDrawSurf = entityDef->decals->CreateDecalDrawSurf( vEntity, i ); if( decalDrawSurf != NULL ) { decalDrawSurf->linkChain = NULL; decalDrawSurf->nextOnLight = vEntity->drawSurfs; vEntity->drawSurfs = decalDrawSurf; } } } if( entityDef->overlays != NULL && !r_skipOverlays.GetBool() ) { entityDef->overlays->CreateDeferredOverlays( model ); unsigned int numDrawSurfs = entityDef->overlays->GetNumOverlayDrawSurfs(); for( unsigned int i = 0; i < numDrawSurfs; i++ ) { drawSurf_t* overlayDrawSurf = entityDef->overlays->CreateOverlayDrawSurf( vEntity, model, i ); if( overlayDrawSurf != NULL ) { overlayDrawSurf->linkChain = NULL; overlayDrawSurf->nextOnLight = vEntity->drawSurfs; vEntity->drawSurfs = overlayDrawSurf; } } } } //--------------------------- // copy matrix related stuff for back-end use // and setup a render matrix for faster culling //--------------------------- vEntity->modelDepthHack = renderEntity->modelDepthHack; vEntity->weaponDepthHack = renderEntity->weaponDepthHack; vEntity->skipMotionBlur = renderEntity->skipMotionBlur; memcpy( vEntity->modelMatrix, entityDef->modelMatrix, sizeof( vEntity->modelMatrix ) ); R_MatrixMultiply( entityDef->modelMatrix, viewDef->worldSpace.modelViewMatrix, vEntity->modelViewMatrix ); idRenderMatrix viewMat; idRenderMatrix::Transpose( *( idRenderMatrix* )vEntity->modelViewMatrix, viewMat ); idRenderMatrix::Multiply( viewDef->projectionRenderMatrix, viewMat, vEntity->mvp ); if( renderEntity->weaponDepthHack ) { idRenderMatrix::ApplyDepthHack( vEntity->mvp ); } if( renderEntity->modelDepthHack != 0.0f ) { idRenderMatrix::ApplyModelDepthHack( vEntity->mvp, renderEntity->modelDepthHack ); } // local light and view origins are used to determine if the view is definitely outside // an extruded shadow volume, which means we can skip drawing the end caps idVec3 localViewOrigin; R_GlobalPointToLocal( vEntity->modelMatrix, viewDef->renderView.vieworg, localViewOrigin ); //--------------------------- // add all the model surfaces //--------------------------- for( int surfaceNum = 0; surfaceNum < model->NumSurfaces(); surfaceNum++ ) { const modelSurface_t* surf = model->Surface( surfaceNum ); // for debugging, only show a single surface at a time if( r_singleSurface.GetInteger() >= 0 && surfaceNum != r_singleSurface.GetInteger() ) { continue; } srfTriangles_t* tri = surf->geometry; if( tri == NULL ) { continue; } if( tri->numIndexes == 0 ) { continue; // happens for particles } const idMaterial* shader = surf->shader; if( shader == NULL ) { continue; } if( !shader->IsDrawn() ) { continue; // collision hulls, etc } // RemapShaderBySkin if( entityDef->parms.customShader != NULL ) { // this is sort of a hack, but causes deformed surfaces to map to empty surfaces, // so the item highlight overlay doesn't highlight the autosprite surface if( shader->Deform() ) { continue; } shader = entityDef->parms.customShader; } else if( entityDef->parms.customSkin ) { shader = entityDef->parms.customSkin->RemapShaderBySkin( shader ); if( shader == NULL ) { continue; } if( !shader->IsDrawn() ) { continue; } } // optionally override with the renderView->globalMaterial if( tr.primaryRenderView.globalMaterial != NULL ) { shader = tr.primaryRenderView.globalMaterial; } SCOPED_PROFILE_EVENT( shader->GetName() ); // debugging tool to make sure we have the correct pre-calculated bounds if( r_checkBounds.GetBool() ) { for( int j = 0; j < tri->numVerts; j++ ) { int k; for( k = 0; k < 3; k++ ) { if( tri->verts[j].xyz[k] > tri->bounds[1][k] + CHECK_BOUNDS_EPSILON || tri->verts[j].xyz[k] < tri->bounds[0][k] - CHECK_BOUNDS_EPSILON ) { common->Printf( "bad tri->bounds on %s:%s\n", entityDef->parms.hModel->Name(), shader->GetName() ); break; } if( tri->verts[j].xyz[k] > entityDef->localReferenceBounds[1][k] + CHECK_BOUNDS_EPSILON || tri->verts[j].xyz[k] < entityDef->localReferenceBounds[0][k] - CHECK_BOUNDS_EPSILON ) { common->Printf( "bad referenceBounds on %s:%s\n", entityDef->parms.hModel->Name(), shader->GetName() ); break; } } if( k != 3 ) { break; } } } // view frustum culling for the precise surface bounds, which is tighter // than the entire entity reference bounds // If the entire model wasn't visible, there is no need to check the // individual surfaces. const bool surfaceDirectlyVisible = modelIsVisible && !idRenderMatrix::CullBoundsToMVP( vEntity->mvp, tri->bounds ); // RB: added check wether GPU skinning is available at all const bool gpuSkinned = ( tri->staticModelWithJoints != NULL && r_useGPUSkinning.GetBool() && glConfig.gpuSkinningAvailable ); // RB end //-------------------------- // base drawing surface //-------------------------- drawSurf_t* baseDrawSurf = NULL; if( surfaceDirectlyVisible ) { // make sure we have an ambient cache and all necessary normals / tangents if( !vertexCache.CacheIsCurrent( tri->indexCache ) ) { tri->indexCache = vertexCache.AllocIndex( tri->indexes, ALIGN( tri->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); } if( !vertexCache.CacheIsCurrent( tri->ambientCache ) ) { // we are going to use it for drawing, so make sure we have the tangents and normals if( shader->ReceivesLighting() && !tri->tangentsCalculated ) { assert( tri->staticModelWithJoints == NULL ); R_DeriveTangents( tri ); // RB: this was hit by parametric particle models .. //assert( false ); // this should no longer be hit // RB end } tri->ambientCache = vertexCache.AllocVertex( tri->verts, ALIGN( tri->numVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); } // add the surface for drawing // we can re-use some of the values for light interaction surfaces baseDrawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *baseDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); baseDrawSurf->frontEndGeo = tri; baseDrawSurf->space = vEntity; baseDrawSurf->scissorRect = vEntity->scissorRect; baseDrawSurf->extraGLState = 0; baseDrawSurf->renderZFail = 0; R_SetupDrawSurfShader( baseDrawSurf, shader, renderEntity ); // Check for deformations (eyeballs, flares, etc) const deform_t shaderDeform = shader->Deform(); if( shaderDeform != DFRM_NONE ) { drawSurf_t* deformDrawSurf = R_DeformDrawSurf( baseDrawSurf ); if( deformDrawSurf != NULL ) { // any deforms may have created multiple draw surfaces for( drawSurf_t* surf = deformDrawSurf, * next = NULL; surf != NULL; surf = next ) { next = surf->nextOnLight; surf->linkChain = NULL; surf->nextOnLight = vEntity->drawSurfs; vEntity->drawSurfs = surf; } } } // Most deform source surfaces do not need to be rendered. // However, particles are rendered in conjunction with the source surface. if( shaderDeform == DFRM_NONE || shaderDeform == DFRM_PARTICLE || shaderDeform == DFRM_PARTICLE2 ) { // copy verts and indexes to this frame's hardware memory if they aren't already there if( !vertexCache.CacheIsCurrent( tri->ambientCache ) ) { tri->ambientCache = vertexCache.AllocVertex( tri->verts, ALIGN( tri->numVerts * sizeof( tri->verts[0] ), VERTEX_CACHE_ALIGN ) ); } if( !vertexCache.CacheIsCurrent( tri->indexCache ) ) { tri->indexCache = vertexCache.AllocIndex( tri->indexes, ALIGN( tri->numIndexes * sizeof( tri->indexes[0] ), INDEX_CACHE_ALIGN ) ); } R_SetupDrawSurfJoints( baseDrawSurf, tri, shader ); baseDrawSurf->numIndexes = tri->numIndexes; baseDrawSurf->ambientCache = tri->ambientCache; baseDrawSurf->indexCache = tri->indexCache; baseDrawSurf->shadowCache = 0; baseDrawSurf->linkChain = NULL; // link to the view baseDrawSurf->nextOnLight = vEntity->drawSurfs; vEntity->drawSurfs = baseDrawSurf; } } //---------------------------------------- // add all light interactions //---------------------------------------- for( int contactedLight = 0; contactedLight < numContactedLights; contactedLight++ ) { viewLight_t* vLight = contactedLights[contactedLight]; const idRenderLightLocal* lightDef = vLight->lightDef; const idInteraction* interaction = staticInteractions[contactedLight]; // check for a static interaction surfaceInteraction_t* surfInter = NULL; if( interaction > INTERACTION_EMPTY && interaction->staticInteraction ) { // we have a static interaction that was calculated accurately assert( model->NumSurfaces() == interaction->numSurfaces ); surfInter = &interaction->surfaces[surfaceNum]; } else { // try to do a more precise cull of this model surface to the light if( R_CullModelBoundsToLight( lightDef, tri->bounds, entityDef->modelRenderMatrix ) ) { continue; } } // "invisible ink" lights and shaders (imp spawn drawing on walls, etc) if( shader->Spectrum() != lightDef->lightShader->Spectrum() ) { continue; } // Calculate the local light origin to determine if the view is inside the shadow // projection and to calculate the triangle facing for dynamic shadow volumes. idVec3 localLightOrigin; R_GlobalPointToLocal( vEntity->modelMatrix, lightDef->globalLightOrigin, localLightOrigin ); //-------------------------- // surface light interactions //-------------------------- dynamicShadowVolumeParms_t* dynamicShadowParms = NULL; if( addInteractions && surfaceDirectlyVisible && shader->ReceivesLighting() ) { // static interactions can commonly find that no triangles from a surface // contact the light, even when the total model does if( surfInter == NULL || surfInter->lightTrisIndexCache > 0 ) { // create a drawSurf for this interaction drawSurf_t* lightDrawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *lightDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); if( surfInter != NULL ) { // optimized static interaction lightDrawSurf->numIndexes = surfInter->numLightTrisIndexes; lightDrawSurf->indexCache = surfInter->lightTrisIndexCache; } else { // throw the entire source surface at it without any per-triangle culling lightDrawSurf->numIndexes = tri->numIndexes; lightDrawSurf->indexCache = tri->indexCache; // optionally cull the triangles to the light volume if( r_cullDynamicLightTriangles.GetBool() ) { vertCacheHandle_t lightIndexCache = vertexCache.AllocIndex( NULL, ALIGN( lightDrawSurf->numIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); if( vertexCache.CacheIsCurrent( lightIndexCache ) ) { lightDrawSurf->indexCache = lightIndexCache; dynamicShadowParms = ( dynamicShadowVolumeParms_t* )R_FrameAlloc( sizeof( dynamicShadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); dynamicShadowParms->verts = tri->verts; dynamicShadowParms->numVerts = tri->numVerts; dynamicShadowParms->indexes = tri->indexes; dynamicShadowParms->numIndexes = tri->numIndexes; dynamicShadowParms->silEdges = tri->silEdges; dynamicShadowParms->numSilEdges = tri->numSilEdges; dynamicShadowParms->joints = gpuSkinned ? tri->staticModelWithJoints->jointsInverted : NULL; dynamicShadowParms->numJoints = gpuSkinned ? tri->staticModelWithJoints->numInvertedJoints : 0; dynamicShadowParms->triangleBounds = tri->bounds; dynamicShadowParms->triangleMVP = vEntity->mvp; dynamicShadowParms->localLightOrigin = localLightOrigin; dynamicShadowParms->localViewOrigin = localViewOrigin; idRenderMatrix::Multiply( vLight->lightDef->baseLightProject, entityDef->modelRenderMatrix, dynamicShadowParms->localLightProject ); dynamicShadowParms->zNear = znear; dynamicShadowParms->lightZMin = vLight->scissorRect.zmin; dynamicShadowParms->lightZMax = vLight->scissorRect.zmax; dynamicShadowParms->cullShadowTrianglesToLight = false; dynamicShadowParms->forceShadowCaps = false; dynamicShadowParms->useShadowPreciseInsideTest = false; dynamicShadowParms->useShadowDepthBounds = false; dynamicShadowParms->tempFacing = NULL; dynamicShadowParms->tempCulled = NULL; dynamicShadowParms->tempVerts = NULL; dynamicShadowParms->indexBuffer = NULL; dynamicShadowParms->shadowIndices = NULL; dynamicShadowParms->maxShadowIndices = 0; dynamicShadowParms->numShadowIndices = NULL; dynamicShadowParms->lightIndices = ( triIndex_t* )vertexCache.MappedIndexBuffer( lightIndexCache ); dynamicShadowParms->maxLightIndices = lightDrawSurf->numIndexes; dynamicShadowParms->numLightIndices = &lightDrawSurf->numIndexes; dynamicShadowParms->renderZFail = NULL; dynamicShadowParms->shadowZMin = NULL; dynamicShadowParms->shadowZMax = NULL; dynamicShadowParms->shadowVolumeState = & lightDrawSurf->shadowVolumeState; lightDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; dynamicShadowParms->next = vEntity->dynamicShadowVolumes; vEntity->dynamicShadowVolumes = dynamicShadowParms; } } } lightDrawSurf->ambientCache = tri->ambientCache; lightDrawSurf->shadowCache = 0; lightDrawSurf->frontEndGeo = tri; lightDrawSurf->space = vEntity; lightDrawSurf->material = shader; lightDrawSurf->extraGLState = 0; lightDrawSurf->scissorRect = vLight->scissorRect; // interactionScissor; lightDrawSurf->sort = 0.0f; lightDrawSurf->renderZFail = 0; lightDrawSurf->shaderRegisters = baseDrawSurf->shaderRegisters; R_SetupDrawSurfJoints( lightDrawSurf, tri, shader ); // Determine which linked list to add the light surface to. // There will only be localSurfaces if the light casts shadows and // there are surfaces with NOSELFSHADOW. if( shader->Coverage() == MC_TRANSLUCENT ) { lightDrawSurf->linkChain = &vLight->translucentInteractions; } else if( !lightDef->parms.noShadows && shader->TestMaterialFlag( MF_NOSELFSHADOW ) ) { lightDrawSurf->linkChain = &vLight->localInteractions; } else { lightDrawSurf->linkChain = &vLight->globalInteractions; } lightDrawSurf->nextOnLight = vEntity->drawSurfs; vEntity->drawSurfs = lightDrawSurf; } } //-------------------------- // surface shadows //-------------------------- if( !shader->SurfaceCastsShadow() ) { continue; } if( !lightDef->LightCastsShadows() ) { continue; } if( tri->silEdges == NULL ) { continue; // can happen for beam models (shouldn't use a shadow casting material, though...) } // if the static shadow does not have any shadows if( surfInter != NULL && surfInter->numShadowIndexes == 0 ) { continue; } // some entities, like view weapons, don't cast any shadows if( entityDef->parms.noShadow ) { continue; } // No shadow if it's suppressed for this light. if( entityDef->parms.suppressShadowInLightID && entityDef->parms.suppressShadowInLightID == lightDef->parms.lightId ) { continue; } if( lightDef->parms.prelightModel && lightDef->lightHasMoved == false && entityDef->parms.hModel->IsStaticWorldModel() && !r_skipPrelightShadows.GetBool() ) { // static light / world model shadow interacitons // are always captured in the prelight shadow volume continue; } // If the shadow is drawn (or translucent), but the model isn't, we must include the shadow caps // because we may be able to see into the shadow volume even though the view is outside it. // This happens for the player world weapon and possibly some animations in multiplayer. const bool forceShadowCaps = !addInteractions || r_forceShadowCaps.GetBool(); drawSurf_t* shadowDrawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *shadowDrawSurf ), FRAME_ALLOC_DRAW_SURFACE ); if( surfInter != NULL ) { shadowDrawSurf->numIndexes = 0; shadowDrawSurf->indexCache = surfInter->shadowIndexCache; shadowDrawSurf->shadowCache = tri->shadowCache; shadowDrawSurf->scissorRect = vLight->scissorRect; // default to the light scissor and light depth bounds shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE; // assume the shadow volume is done in case r_skipStaticShadows is set if( !r_skipStaticShadows.GetBool() ) { staticShadowVolumeParms_t* staticShadowParms = ( staticShadowVolumeParms_t* )R_FrameAlloc( sizeof( staticShadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); staticShadowParms->verts = tri->staticShadowVertexes; staticShadowParms->numVerts = tri->numVerts * 2; staticShadowParms->indexes = surfInter->shadowIndexes; staticShadowParms->numIndexes = surfInter->numShadowIndexes; staticShadowParms->numShadowIndicesWithCaps = surfInter->numShadowIndexes; staticShadowParms->numShadowIndicesNoCaps = surfInter->numShadowIndexesNoCaps; staticShadowParms->triangleBounds = tri->bounds; staticShadowParms->triangleMVP = vEntity->mvp; staticShadowParms->localLightOrigin = localLightOrigin; staticShadowParms->localViewOrigin = localViewOrigin; staticShadowParms->zNear = znear; staticShadowParms->lightZMin = vLight->scissorRect.zmin; staticShadowParms->lightZMax = vLight->scissorRect.zmax; staticShadowParms->forceShadowCaps = forceShadowCaps; staticShadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool(); staticShadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool(); staticShadowParms->numShadowIndices = & shadowDrawSurf->numIndexes; staticShadowParms->renderZFail = & shadowDrawSurf->renderZFail; staticShadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin; staticShadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax; staticShadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState; shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; staticShadowParms->next = vEntity->staticShadowVolumes; vEntity->staticShadowVolumes = staticShadowParms; } } else { // When CPU skinning the dynamic shadow verts of a dynamic model may not have been copied to buffer memory yet. if( !vertexCache.CacheIsCurrent( tri->shadowCache ) ) { assert( !gpuSkinned ); // the shadow cache should be static when using GPU skinning // Extracts just the xyz values from a set of full size drawverts, and // duplicates them with w set to 0 and 1 for the vertex program to project. // This is constant for any number of lights, the vertex program takes care // of projecting the verts to infinity for a particular light. tri->shadowCache = vertexCache.AllocVertex( NULL, ALIGN( tri->numVerts * 2 * sizeof( idShadowVert ), VERTEX_CACHE_ALIGN ) ); idShadowVert* shadowVerts = ( idShadowVert* )vertexCache.MappedVertexBuffer( tri->shadowCache ); idShadowVert::CreateShadowCache( shadowVerts, tri->verts, tri->numVerts ); } const int maxShadowVolumeIndexes = tri->numSilEdges * 6 + tri->numIndexes * 2; shadowDrawSurf->numIndexes = 0; shadowDrawSurf->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxShadowVolumeIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); shadowDrawSurf->shadowCache = tri->shadowCache; shadowDrawSurf->scissorRect = vLight->scissorRect; // default to the light scissor and light depth bounds shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_DONE; // assume the shadow volume is done in case the index cache allocation failed // if the index cache was successfully allocated then setup the parms to create a shadow volume in parallel if( vertexCache.CacheIsCurrent( shadowDrawSurf->indexCache ) && !r_skipDynamicShadows.GetBool() ) { // if the parms were not already allocated for culling interaction triangles to the light frustum if( dynamicShadowParms == NULL ) { dynamicShadowParms = ( dynamicShadowVolumeParms_t* )R_FrameAlloc( sizeof( dynamicShadowParms[0] ), FRAME_ALLOC_SHADOW_VOLUME_PARMS ); } else { // the shadow volume will be rendered first so when the interaction surface is drawn the triangles have been culled for sure *dynamicShadowParms->shadowVolumeState = SHADOWVOLUME_DONE; } dynamicShadowParms->verts = tri->verts; dynamicShadowParms->numVerts = tri->numVerts; dynamicShadowParms->indexes = tri->indexes; dynamicShadowParms->numIndexes = tri->numIndexes; dynamicShadowParms->silEdges = tri->silEdges; dynamicShadowParms->numSilEdges = tri->numSilEdges; dynamicShadowParms->joints = gpuSkinned ? tri->staticModelWithJoints->jointsInverted : NULL; dynamicShadowParms->numJoints = gpuSkinned ? tri->staticModelWithJoints->numInvertedJoints : 0; dynamicShadowParms->triangleBounds = tri->bounds; dynamicShadowParms->triangleMVP = vEntity->mvp; dynamicShadowParms->localLightOrigin = localLightOrigin; dynamicShadowParms->localViewOrigin = localViewOrigin; idRenderMatrix::Multiply( vLight->lightDef->baseLightProject, entityDef->modelRenderMatrix, dynamicShadowParms->localLightProject ); dynamicShadowParms->zNear = znear; dynamicShadowParms->lightZMin = vLight->scissorRect.zmin; dynamicShadowParms->lightZMax = vLight->scissorRect.zmax; dynamicShadowParms->cullShadowTrianglesToLight = r_cullDynamicShadowTriangles.GetBool(); dynamicShadowParms->forceShadowCaps = forceShadowCaps; dynamicShadowParms->useShadowPreciseInsideTest = r_useShadowPreciseInsideTest.GetBool(); dynamicShadowParms->useShadowDepthBounds = r_useShadowDepthBounds.GetBool(); dynamicShadowParms->tempFacing = NULL; dynamicShadowParms->tempCulled = NULL; dynamicShadowParms->tempVerts = NULL; dynamicShadowParms->indexBuffer = NULL; dynamicShadowParms->shadowIndices = ( triIndex_t* )vertexCache.MappedIndexBuffer( shadowDrawSurf->indexCache ); dynamicShadowParms->maxShadowIndices = maxShadowVolumeIndexes; dynamicShadowParms->numShadowIndices = & shadowDrawSurf->numIndexes; // dynamicShadowParms->lightIndices may have already been set for the interaction surface // dynamicShadowParms->maxLightIndices may have already been set for the interaction surface // dynamicShadowParms->numLightIndices may have already been set for the interaction surface dynamicShadowParms->renderZFail = & shadowDrawSurf->renderZFail; dynamicShadowParms->shadowZMin = & shadowDrawSurf->scissorRect.zmin; dynamicShadowParms->shadowZMax = & shadowDrawSurf->scissorRect.zmax; dynamicShadowParms->shadowVolumeState = & shadowDrawSurf->shadowVolumeState; shadowDrawSurf->shadowVolumeState = SHADOWVOLUME_UNFINISHED; // if the parms we not already linked for culling interaction triangles to the light frustum if( dynamicShadowParms->lightIndices == NULL ) { dynamicShadowParms->next = vEntity->dynamicShadowVolumes; vEntity->dynamicShadowVolumes = dynamicShadowParms; } tr.pc.c_createShadowVolumes++; } } assert( vertexCache.CacheIsCurrent( shadowDrawSurf->shadowCache ) ); assert( vertexCache.CacheIsCurrent( shadowDrawSurf->indexCache ) ); shadowDrawSurf->ambientCache = 0; shadowDrawSurf->frontEndGeo = NULL; shadowDrawSurf->space = vEntity; shadowDrawSurf->material = NULL; shadowDrawSurf->extraGLState = 0; shadowDrawSurf->sort = 0.0f; shadowDrawSurf->shaderRegisters = NULL; R_SetupDrawSurfJoints( shadowDrawSurf, tri, NULL ); // determine which linked list to add the shadow surface to shadowDrawSurf->linkChain = shader->TestMaterialFlag( MF_NOSELFSHADOW ) ? &vLight->localShadows : &vLight->globalShadows; shadowDrawSurf->nextOnLight = vEntity->drawSurfs; vEntity->drawSurfs = shadowDrawSurf; } } }
/* ===================== R_CreateInteractionShadowVolume Note that dangling edges outside the light frustum don't make silhouette planes because a triangle outside the light frustum is considered facing and the "fake triangle" on the outside of the dangling edge is also set to facing: cullInfo.facing[numFaces] = 1; ===================== */ static srfTriangles_t *R_CreateInteractionShadowVolume( const idRenderEntityLocal * ent, const srfTriangles_t * tri, const idRenderLightLocal * light ) { SCOPED_PROFILE_EVENT( "R_CreateInteractionShadowVolume" ); srfCullInfo_t cullInfo = {}; R_CalcInteractionFacing( ent, tri, light, cullInfo ); R_CalcInteractionCullBits( ent, tri, light, cullInfo ); int numFaces = tri->numIndexes / 3; int numShadowingFaces = 0; const byte * facing = cullInfo.facing; // if all the triangles are inside the light frustum if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT ) { // count the number of shadowing faces for ( int i = 0; i < numFaces; i++ ) { numShadowingFaces += facing[i]; } numShadowingFaces = numFaces - numShadowingFaces; } else { // make all triangles that are outside the light frustum "facing", so they won't cast shadows const triIndex_t * indexes = tri->indexes; byte *modifyFacing = cullInfo.facing; const byte *cullBits = cullInfo.cullBits; for ( int i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { if ( !modifyFacing[j] ) { int i1 = indexes[i+0]; int i2 = indexes[i+1]; int i3 = indexes[i+2]; if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) { modifyFacing[j] = 1; } else { numShadowingFaces++; } } } } if ( !numShadowingFaces ) { // no faces are inside the light frustum and still facing the right way R_FreeInteractionCullInfo( cullInfo ); return NULL; } // shadowVerts will be NULL on these surfaces, so the shadowVerts will be taken from the ambient surface srfTriangles_t * newTri = R_AllocStaticTriSurf(); newTri->numVerts = tri->numVerts * 2; // alloc the max possible size R_AllocStaticTriSurfIndexes( newTri, ( numShadowingFaces + tri->numSilEdges ) * 6 ); triIndex_t * tempIndexes = newTri->indexes; triIndex_t * shadowIndexes = newTri->indexes; // create new triangles along sil planes const silEdge_t * sil = tri->silEdges; for ( int i = tri->numSilEdges; i > 0; i--, sil++ ) { int f1 = facing[sil->p1]; int f2 = facing[sil->p2]; if ( !( f1 ^ f2 ) ) { continue; } int v1 = sil->v1 << 1; int v2 = sil->v2 << 1; // set the two triangle winding orders based on facing // without using a poorly-predictable branch shadowIndexes[0] = v1; shadowIndexes[1] = v2 ^ f1; shadowIndexes[2] = v2 ^ f2; shadowIndexes[3] = v1 ^ f2; shadowIndexes[4] = v1 ^ f1; shadowIndexes[5] = v2 ^ 1; shadowIndexes += 6; } int numShadowIndexes = shadowIndexes - tempIndexes; // we aren't bothering to separate front and back caps on these newTri->numIndexes = newTri->numShadowIndexesNoFrontCaps = numShadowIndexes + numShadowingFaces * 6; newTri->numShadowIndexesNoCaps = numShadowIndexes; newTri->shadowCapPlaneBits = SHADOW_CAP_INFINITE; // decrease the size of the memory block to only store the used indexes // R_ResizeStaticTriSurfIndexes( newTri, newTri->numIndexes ); // these have no effect, because they extend to infinity newTri->bounds.Clear(); // put some faces on the model and some on the distant projection const triIndex_t * indexes = tri->indexes; shadowIndexes = newTri->indexes + numShadowIndexes; for ( int i = 0, j = 0; i < tri->numIndexes; i += 3, j++ ) { if ( facing[j] ) { continue; } int i0 = indexes[i+0] << 1; int i1 = indexes[i+1] << 1; int i2 = indexes[i+2] << 1; shadowIndexes[0] = i2; shadowIndexes[1] = i1; shadowIndexes[2] = i0; shadowIndexes[3] = i0 ^ 1; shadowIndexes[4] = i1 ^ 1; shadowIndexes[5] = i2 ^ 1; shadowIndexes += 6; } R_FreeInteractionCullInfo( cullInfo ); return newTri; }
/* =================== R_AddModels The end result of running this is the addition of drawSurf_t to the tr.viewDef->drawSurfs[] array and light link chains, along with frameData and vertexCache allocations to support the drawSurfs. =================== */ void R_AddModels() { SCOPED_PROFILE_EVENT( "R_AddModels" ); tr.viewDef->viewEntitys = R_SortViewEntities( tr.viewDef->viewEntitys ); //------------------------------------------------- // Go through each view entity that is either visible to the view, or to // any light that intersects the view (for shadows). //------------------------------------------------- if( r_useParallelAddModels.GetBool() ) { for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )R_AddSingleModel, vEntity ); } tr.frontEndJobList->Submit(); tr.frontEndJobList->Wait(); } else { for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { R_AddSingleModel( vEntity ); } } //------------------------------------------------- // Kick off jobs to setup static and dynamic shadow volumes. //------------------------------------------------- if( r_useParallelAddShadows.GetInteger() == 1 ) { for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { for( staticShadowVolumeParms_t* shadowParms = vEntity->staticShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )StaticShadowVolumeJob, shadowParms ); } for( dynamicShadowVolumeParms_t* shadowParms = vEntity->dynamicShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { tr.frontEndJobList->AddJob( ( jobRun_t )DynamicShadowVolumeJob, shadowParms ); } vEntity->staticShadowVolumes = NULL; vEntity->dynamicShadowVolumes = NULL; } tr.frontEndJobList->Submit(); // wait here otherwise the shadow volume index buffer may be unmapped before all shadow volumes have been constructed tr.frontEndJobList->Wait(); } else { int start = Sys_Microseconds(); for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { for( staticShadowVolumeParms_t* shadowParms = vEntity->staticShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { StaticShadowVolumeJob( shadowParms ); } for( dynamicShadowVolumeParms_t* shadowParms = vEntity->dynamicShadowVolumes; shadowParms != NULL; shadowParms = shadowParms->next ) { DynamicShadowVolumeJob( shadowParms ); } vEntity->staticShadowVolumes = NULL; vEntity->dynamicShadowVolumes = NULL; } int end = Sys_Microseconds(); backEnd.pc.shadowMicroSec += end - start; } //------------------------------------------------- // Move the draw surfs to the view. //------------------------------------------------- tr.viewDef->numDrawSurfs = 0; // clear the ambient surface list tr.viewDef->maxDrawSurfs = 0; // will be set to INITIAL_DRAWSURFS on R_LinkDrawSurfToView for( viewEntity_t* vEntity = tr.viewDef->viewEntitys; vEntity != NULL; vEntity = vEntity->next ) { for( drawSurf_t* ds = vEntity->drawSurfs; ds != NULL; ) { drawSurf_t* next = ds->nextOnLight; if( ds->linkChain == NULL ) { R_LinkDrawSurfToView( ds, tr.viewDef ); } else { ds->nextOnLight = *ds->linkChain; *ds->linkChain = ds; } ds = next; } vEntity->drawSurfs = NULL; } }
/* ==================== R_CreateInteractionLightTris This is only used for the static interaction case, dynamic interactions just draw everything and let the GPU deal with it. The resulting surface will be a subset of the original triangles, it will never clip triangles, but it may cull on a per-triangle basis. ==================== */ static srfTriangles_t *R_CreateInteractionLightTris( const idRenderEntityLocal *ent, const srfTriangles_t *tri, const idRenderLightLocal *light, const idMaterial *shader ) { SCOPED_PROFILE_EVENT( "R_CreateInteractionLightTris" ); int i; int numIndexes; triIndex_t *indexes; srfTriangles_t *newTri; int c_backfaced; int c_distance; idBounds bounds; bool includeBackFaces; int faceNum; c_backfaced = 0; c_distance = 0; numIndexes = 0; indexes = NULL; // it is debatable if non-shadowing lights should light back faces. we aren't at the moment if ( r_lightAllBackFaces.GetBool() || light->lightShader->LightEffectsBackSides() || shader->ReceivesLightingOnBackSides() || ent->parms.noSelfShadow || ent->parms.noShadow ) { includeBackFaces = true; } else { includeBackFaces = false; } // allocate a new surface for the lit triangles newTri = R_AllocStaticTriSurf(); // save a reference to the original surface newTri->ambientSurface = const_cast<srfTriangles_t *>(tri); // the light surface references the verts of the ambient surface newTri->numVerts = tri->numVerts; R_ReferenceStaticTriSurfVerts( newTri, tri ); // calculate cull information srfCullInfo_t cullInfo = {}; if ( !includeBackFaces ) { R_CalcInteractionFacing( ent, tri, light, cullInfo ); } R_CalcInteractionCullBits( ent, tri, light, cullInfo ); // if the surface is completely inside the light frustum if ( cullInfo.cullBits == LIGHT_CULL_ALL_FRONT ) { // if we aren't self shadowing, let back facing triangles get // through so the smooth shaded bump maps light all the way around if ( includeBackFaces ) { // the whole surface is lit so the light surface just references the indexes of the ambient surface newTri->indexes = tri->indexes; newTri->indexCache = tri->indexCache; // R_ReferenceStaticTriSurfIndexes( newTri, tri ); numIndexes = tri->numIndexes; bounds = tri->bounds; } else { // the light tris indexes are going to be a subset of the original indexes so we generally // allocate too much memory here but we decrease the memory block when the number of indexes is known R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); // back face cull the individual triangles indexes = newTri->indexes; const byte *facing = cullInfo.facing; for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) { if ( !facing[ faceNum ] ) { c_backfaced++; continue; } indexes[numIndexes+0] = tri->indexes[i+0]; indexes[numIndexes+1] = tri->indexes[i+1]; indexes[numIndexes+2] = tri->indexes[i+2]; numIndexes += 3; } // get bounds for the surface SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes ); // decrease the size of the memory block to the size of the number of used indexes newTri->numIndexes = numIndexes; R_ResizeStaticTriSurfIndexes( newTri, numIndexes ); } } else { // the light tris indexes are going to be a subset of the original indexes so we generally // allocate too much memory here but we decrease the memory block when the number of indexes is known R_AllocStaticTriSurfIndexes( newTri, tri->numIndexes ); // cull individual triangles indexes = newTri->indexes; const byte *facing = cullInfo.facing; const byte *cullBits = cullInfo.cullBits; for ( faceNum = i = 0; i < tri->numIndexes; i += 3, faceNum++ ) { int i1, i2, i3; // if we aren't self shadowing, let back facing triangles get // through so the smooth shaded bump maps light all the way around if ( !includeBackFaces ) { // back face cull if ( !facing[ faceNum ] ) { c_backfaced++; continue; } } i1 = tri->indexes[i+0]; i2 = tri->indexes[i+1]; i3 = tri->indexes[i+2]; // fast cull outside the frustum // if all three points are off one plane side, it definately isn't visible if ( cullBits[i1] & cullBits[i2] & cullBits[i3] ) { c_distance++; continue; } // add to the list indexes[numIndexes+0] = i1; indexes[numIndexes+1] = i2; indexes[numIndexes+2] = i3; numIndexes += 3; } // get bounds for the surface SIMDProcessor->MinMax( bounds[0], bounds[1], tri->verts, indexes, numIndexes ); // decrease the size of the memory block to the size of the number of used indexes newTri->numIndexes = numIndexes; R_ResizeStaticTriSurfIndexes( newTri, numIndexes ); } // free the cull information when it's no longer needed R_FreeInteractionCullInfo( cullInfo ); if ( !numIndexes ) { R_FreeStaticTriSurf( newTri ); return NULL; } newTri->numIndexes = numIndexes; newTri->bounds = bounds; return newTri; }
/* =============== idCommonLocal::Draw =============== */ void idCommonLocal::Draw() { // debugging tool to test frame dropping behavior if( com_sleepDraw.GetInteger() ) { Sys_Sleep( com_sleepDraw.GetInteger() ); } if( loadGUI != NULL ) { loadGUI->Render( renderSystem, Sys_Milliseconds() ); } else if( game && game->Shell_IsActive() ) { bool gameDraw = game->Draw( game->GetLocalClientNum() ); if( !gameDraw ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, renderSystem->GetVirtualWidth(), renderSystem->GetVirtualHeight(), 0, 0, 1, 1, whiteMaterial ); } game->Shell_Render(); } else if( readDemo ) { renderWorld->RenderScene( ¤tDemoRenderView ); renderSystem->DrawDemoPics(); } else if( mapSpawned ) { bool gameDraw = false; // normal drawing for both single and multi player if( !com_skipGameDraw.GetBool() && Game()->GetLocalClientNum() >= 0 ) { // draw the game view int start = Sys_Milliseconds(); if( game ) { gameDraw = game->Draw( Game()->GetLocalClientNum() ); } int end = Sys_Milliseconds(); time_gameDraw += ( end - start ); // note time used for com_speeds } if( !gameDraw ) { renderSystem->SetColor( colorBlack ); renderSystem->DrawStretchPic( 0, 0, renderSystem->GetVirtualWidth(), renderSystem->GetVirtualHeight(), 0, 0, 1, 1, whiteMaterial ); } // save off the 2D drawing from the game if( writeDemo ) { renderSystem->WriteDemoPics(); } } else { renderSystem->SetColor4( 0, 0, 0, 1 ); renderSystem->DrawStretchPic( 0, 0, renderSystem->GetVirtualWidth(), renderSystem->GetVirtualHeight(), 0, 0, 1, 1, whiteMaterial ); } { SCOPED_PROFILE_EVENT( "Post-Draw" ); // draw the wipe material on top of this if it hasn't completed yet DrawWipeModel(); Dialog().Render( loadGUI != NULL ); // draw the half console / notify console on top of everything console->Draw( false ); } }