/* ================== idInteraction::CalcInteractionScissorRectangle ================== */ idScreenRect idInteraction::CalcInteractionScissorRectangle( const idFrustum &viewFrustum ) { idBounds projectionBounds; idScreenRect portalRect; idScreenRect scissorRect; if ( r_useInteractionScissors.GetInteger() == 0 ) { return lightDef->viewLight->scissorRect; } if ( r_useInteractionScissors.GetInteger() < 0 ) { // this is the code from Cass at nvidia, it is more precise, but slower return R_CalcIntersectionScissor( lightDef, entityDef, tr.viewDef ); } // the following is Mr.E's code // frustum must be initialized and valid if ( frustumState == idInteraction::FRUSTUM_UNINITIALIZED || frustumState == idInteraction::FRUSTUM_INVALID ) { return lightDef->viewLight->scissorRect; } // calculate scissors for the portals through which the interaction is visible if ( r_useInteractionScissors.GetInteger() > 1 ) { areaNumRef_t *area; if ( frustumState == idInteraction::FRUSTUM_VALID ) { // retrieve all the areas the interaction frustum touches for ( areaReference_t *ref = entityDef->entityRefs; ref; ref = ref->ownerNext ) { area = entityDef->world->areaNumRefAllocator.Alloc(); area->areaNum = ref->area->areaNum; area->next = frustumAreas; frustumAreas = area; } frustumAreas = tr.viewDef->renderWorld->FloodFrustumAreas( frustum, frustumAreas ); frustumState = idInteraction::FRUSTUM_VALIDAREAS; } portalRect.Clear(); for ( area = frustumAreas; area; area = area->next ) { portalRect.Union( entityDef->world->GetAreaScreenRect( area->areaNum ) ); } portalRect.Intersect( lightDef->viewLight->scissorRect ); } else { portalRect = lightDef->viewLight->scissorRect; } // early out if the interaction is not visible through any portals if ( portalRect.IsEmpty() ) { return portalRect; } // calculate bounds of the interaction frustum projected into the view frustum if ( lightDef->parms.pointLight ) { viewFrustum.ClippedProjectionBounds( frustum, idBox( lightDef->parms.origin, lightDef->parms.lightRadius, lightDef->parms.axis ), projectionBounds ); } else { viewFrustum.ClippedProjectionBounds( frustum, idBox( lightDef->frustumTris->bounds ), projectionBounds ); } if ( projectionBounds.IsCleared() ) { return portalRect; } // derive a scissor rectangle from the projection bounds scissorRect = R_ScreenRectFromViewFrustumBounds( projectionBounds ); // intersect with the portal crossing scissor rectangle scissorRect.Intersect( portalRect ); if ( r_showInteractionScissors.GetInteger() > 0 ) { R_ShowColoredScreenRect( scissorRect, lightDef->index ); } return scissorRect; }
/* ================= R_AddLightSurfaces 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. Adds entities to the viewEntity list if they are needed for shadow casting. Add any precomputed shadow volumes. Removes lights from the viewLights list if they are completely turned off, or completely off screen. Create any new interactions needed between the viewLights and the viewEntitys due to game movement ================= */ void R_AddLightSurfaces( void ) { viewLight_t *vLight; idRenderLightLocal *light; viewLight_t **ptr; // go through each visible light, possibly removing some from the list ptr = &tr.viewDef->viewLights; while( *ptr ) { vLight = *ptr; light = vLight->lightDef; const idMaterial *lightShader = light->lightShader; if( !lightShader ) { common->Error( "R_AddLightSurfaces: NULL lightShader" ); } // see if we are suppressing the light in this view if( !r_skipSuppress.GetBool() ) { if( light->parms.suppressLightInViewID && light->parms.suppressLightInViewID == tr.viewDef->renderView.viewID ) { *ptr = vLight->next; light->viewCount = -1; continue; } if( light->parms.allowLightInViewID && light->parms.allowLightInViewID != tr.viewDef->renderView.viewID ) { *ptr = vLight->next; light->viewCount = -1; continue; } } // evaluate the light shader registers float *lightRegs = ( float * ) R_FrameAlloc( lightShader->GetNumRegisters() * sizeof( float ) ); vLight->shaderRegisters = lightRegs; lightShader->EvaluateRegisters( lightRegs, light->parms.shaderParms, tr.viewDef, 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 to avoid lights showing up with the wrong color 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 *ptr = vLight->next; light->viewCount = -1; continue; } } if( r_useLightScissors.GetBool() ) { // calculate the screen area covered by the light frustum // which will be used to crop the stencil cull idScreenRect scissorRect = R_CalcLightScissorRectangle( vLight ); // intersect with the portal crossing scissor rectangle vLight->scissorRect.Intersect( scissorRect ); if( r_showLightScissors.GetBool() ) { R_ShowColoredScreenRect( vLight->scissorRect, light->index ); } } // this one stays on the list ptr = &vLight->next; // if we are doing a soft-shadow novelty test, regenerate the light with // a random offset every time if( r_lightSourceRadius.GetFloat() != 0.0f ) { for( int i = 0; i < 3; i++ ) { light->globalLightOrigin[i] += r_lightSourceRadius.GetFloat() * ( -1 + 2 * ( rand() & 0xfff ) / ( float ) 0xfff ); } } // 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 tr.viewDef->renderWorld->CreateLightDefInteractions( light ); tr.pc.c_viewLights++; // fog lights will need to draw the light frustum triangles, so make sure they // are in the vertex cache if( lightShader->IsFogLight() ) { if( !light->frustumTris->ambientCache ) { if( !R_CreateAmbientCache( light->frustumTris, false ) ) { // skip if we are out of vertex memory continue; } } // touch the surface so it won't get purged vertexCache.Touch( light->frustumTris->ambientCache ); } // add the prelight shadows for the static world geometry if( light->parms.prelightModel && r_useOptimizedShadows.GetBool() ) { if( !light->parms.prelightModel->NumSurfaces() ) { common->Error( "no surfs in prelight model '%s'", light->parms.prelightModel->Name() ); } srfTriangles_t *tri = light->parms.prelightModel->Surface( 0 )->geometry; if( !tri->shadowVertexes ) { common->Error( "R_AddLightSurfaces: prelight model '%s' without shadowVertexes", light->parms.prelightModel->Name() ); } // these shadows will all have valid bounds, and can be culled normally if( r_useShadowCulling.GetBool() ) { if( R_CullLocalBox( tri->bounds, tr.viewDef->worldSpace.modelMatrix, 5, tr.viewDef->frustum ) ) { continue; } } // if we have been purged, re-upload the shadowVertexes if( !tri->shadowCache ) { R_CreatePrivateShadowCache( tri ); if( !tri->shadowCache ) { continue; } } // touch the shadow surface so it won't get purged vertexCache.Touch( tri->shadowCache ); if( !tri->indexCache ) { vertexCache.Alloc( tri->indexes, tri->numIndexes * sizeof( tri->indexes[0] ), &tri->indexCache, true ); } if( tri->indexCache ) { vertexCache.Touch( tri->indexCache ); } R_LinkLightSurf( &vLight->globalShadows, tri, NULL, light, NULL, vLight->scissorRect, 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_AddModelSurfaces Here is where dynamic models actually get instantiated, and necessary interactions 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_AddModelSurfaces( void ) { viewEntity_t *vEntity; idInteraction *inter, *next; idRenderModel *model; // clear the ambient surface list tr.viewDef->numDrawSurfs = 0; tr.viewDef->maxDrawSurfs = 0; // will be set to INITIAL_DRAWSURFS on R_AddDrawSurf // go through each entity that is either visible to the view, or to // any light that intersects the view (for shadows) for( vEntity = tr.viewDef->viewEntitys; vEntity; vEntity = vEntity->next ) { if( r_useEntityScissors.GetBool() ) { // calculate the screen area covered by the entity idScreenRect scissorRect = R_CalcEntityScissorRectangle( vEntity ); // intersect with the portal crossing scissor rectangle vEntity->scissorRect.Intersect( scissorRect ); if( r_showEntityScissors.GetBool() ) { R_ShowColoredScreenRect( vEntity->scissorRect, vEntity->entityDef->index ); } } float oldFloatTime; int oldTime; game->SelectTimeGroup( vEntity->entityDef->parms.timeGroup ); if( vEntity->entityDef->parms.timeGroup ) { oldFloatTime = tr.viewDef->floatTime; oldTime = tr.viewDef->renderView.time; tr.viewDef->floatTime = game->GetTimeGroupTime( vEntity->entityDef->parms.timeGroup ) * 0.001; tr.viewDef->renderView.time = game->GetTimeGroupTime( vEntity->entityDef->parms.timeGroup ); } if( tr.viewDef->isXraySubview && vEntity->entityDef->parms.xrayIndex == 1 ) { if( vEntity->entityDef->parms.timeGroup ) { tr.viewDef->floatTime = oldFloatTime; tr.viewDef->renderView.time = oldTime; } continue; } else if( !tr.viewDef->isXraySubview && vEntity->entityDef->parms.xrayIndex == 2 ) { if( vEntity->entityDef->parms.timeGroup ) { tr.viewDef->floatTime = oldFloatTime; tr.viewDef->renderView.time = oldTime; } continue; } // add the ambient surface if it has a visible rectangle if( !vEntity->scissorRect.IsEmpty() ) { model = R_EntityDefDynamicModel( vEntity->entityDef ); if( model == NULL || model->NumSurfaces() <= 0 ) { if( vEntity->entityDef->parms.timeGroup ) { tr.viewDef->floatTime = oldFloatTime; tr.viewDef->renderView.time = oldTime; } continue; } R_AddAmbientDrawsurfs( vEntity ); tr.pc.c_visibleViewEntities++; } else { tr.pc.c_shadowViewEntities++; } // for all the entity / light interactions on this entity, add them to the view if( tr.viewDef->isXraySubview ) { if( vEntity->entityDef->parms.xrayIndex == 2 ) { for( inter = vEntity->entityDef->firstInteraction; inter != NULL && !inter->IsEmpty(); inter = next ) { next = inter->entityNext; if( inter->lightDef->viewCount != tr.viewCount ) { continue; } inter->AddActiveInteraction(); } } } else { // all empty interactions are at the end of the list so once the // first is encountered all the remaining interactions are empty for( inter = vEntity->entityDef->firstInteraction; inter != NULL && !inter->IsEmpty(); inter = next ) { next = inter->entityNext; // skip any lights that aren't currently visible // this is run after any lights that are turned off have already // been removed from the viewLights list, and had their viewCount cleared if( inter->lightDef->viewCount != tr.viewCount ) { continue; } inter->AddActiveInteraction(); } } if( vEntity->entityDef->parms.timeGroup ) { tr.viewDef->floatTime = oldFloatTime; tr.viewDef->renderView.time = oldTime; } } }