/* ======================== R_XrayViewBySurface ======================== */ static viewDef_t *R_XrayViewBySurface( drawSurf_t *drawSurf ) { viewDef_t *parms; orientation_t surface, camera; idPlane originalPlane, plane; // copy the viewport size from the original parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); *parms = *tr.viewDef; parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons parms->isSubview = true; parms->isXraySubview = true; return parms; }
/* =================== idRenderWorldLocal::BuildConnectedAreas This is only valid for a given view, not all views in a frame =================== */ void idRenderWorldLocal::BuildConnectedAreas() { tr.viewDef->connectedAreas = (bool *)R_FrameAlloc( numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); // if we are outside the world, we can see all areas if ( tr.viewDef->areaNum == -1 ) { for ( int i = 0; i < numPortalAreas; i++ ) { tr.viewDef->connectedAreas[i] = true; } return; } // start with none visible, and flood fill from the current area memset( tr.viewDef->connectedAreas, 0, numPortalAreas * sizeof( tr.viewDef->connectedAreas[0] ) ); BuildConnectedAreas_r( tr.viewDef->areaNum ); }
/* ======================== R_MirrorViewBySurface ======================== */ static viewDef_t *R_MirrorViewBySurface( drawSurf_t *drawSurf ) { viewDef_t *parms; orientation_t surface, camera; idPlane originalPlane, plane; // copy the viewport size from the original parms = (viewDef_t *)R_FrameAlloc( sizeof( *parms ) ); *parms = *tr.viewDef; parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons parms->isSubview = true; parms->isMirror = true; // create plane axis for the portal we are seeing R_PlaneForSurface( drawSurf->geo, originalPlane ); R_LocalPlaneToGlobal( drawSurf->space->modelMatrix, originalPlane, plane ); surface.origin = plane.Normal() * -plane[3]; surface.axis[0] = plane.Normal(); surface.axis[0].NormalVectors( surface.axis[1], surface.axis[2] ); surface.axis[2] = -surface.axis[2]; camera.origin = surface.origin; camera.axis[0] = -surface.axis[0]; camera.axis[1] = surface.axis[1]; camera.axis[2] = surface.axis[2]; // set the mirrored origin and axis R_MirrorPoint( tr.viewDef->renderView.vieworg, &surface, &camera, parms->renderView.vieworg ); R_MirrorVector( tr.viewDef->renderView.viewaxis[0], &surface, &camera, parms->renderView.viewaxis[0] ); R_MirrorVector( tr.viewDef->renderView.viewaxis[1], &surface, &camera, parms->renderView.viewaxis[1] ); R_MirrorVector( tr.viewDef->renderView.viewaxis[2], &surface, &camera, parms->renderView.viewaxis[2] ); // make the view origin 16 units away from the center of the surface idVec3 viewOrigin = ( drawSurf->geo->bounds[0] + drawSurf->geo->bounds[1] ) * 0.5; viewOrigin += ( originalPlane.Normal() * 16 ); R_LocalPointToGlobal( drawSurf->space->modelMatrix, viewOrigin, parms->initialViewAreaOrigin ); // set the mirror clip plane parms->numClipPlanes = 1; parms->clipPlanes[0] = -camera.axis[0]; parms->clipPlanes[0][3] = -( camera.origin * parms->clipPlanes[0].Normal() ); return parms; }
/* ===================== idRenderModelDecal::AddDecalDrawSurf ===================== */ void idRenderModelDecal::AddDecalDrawSurf( viewEntity_t *space ) { int i, j, maxTime; float f; decalInfo_t decalInfo; if( tri.numIndexes == 0 ) { return; } // fade down all the verts with time decalInfo = material->GetDecalInfo(); maxTime = decalInfo.stayTime + decalInfo.fadeTime; // set vertex colors and remove faded triangles for( i = 0 ; i < tri.numIndexes ; i += 3 ) { int deltaTime = tr.viewDef->renderView.time - indexStartTime[i]; if( deltaTime > maxTime ) { continue; } if( deltaTime <= decalInfo.stayTime ) { continue; } deltaTime -= decalInfo.stayTime; f = ( float )deltaTime / decalInfo.fadeTime; for( j = 0; j < 3; j++ ) { int ind = tri.indexes[i + j]; for( int k = 0; k < 4; k++ ) { float fcolor = decalInfo.start[k] + ( decalInfo.end[k] - decalInfo.start[k] ) * f; int icolor = idMath::FtoiFast( fcolor * vertDepthFade[ind] * 255.0f ); if( icolor < 0 ) { icolor = 0; } else if( icolor > 255 ) { icolor = 255; } tri.verts[ind].color[k] = icolor; } } } // copy the tri and indexes to temp heap memory, // because if we are running multi-threaded, we wouldn't // be able to reorganize the index list srfTriangles_t *newTri = ( srfTriangles_t * )R_FrameAlloc( sizeof( *newTri ) ); *newTri = tri; // copy the current vertexes to temp vertex cache newTri->ambientCache = vertexCache.AllocFrameTemp( tri.verts, tri.numVerts * sizeof( idDrawVert ) ); // create the drawsurf R_AddDrawSurf( newTri, space, &space->entityDef->parms, material, space->scissorRect ); }
/* =============== R_RemoteRender =============== */ static void R_RemoteRender( drawSurf_t *surf, textureStage_t *stage ) { viewDef_t *parms; // remote views can be reused in a single frame if( stage->dynamicFrameCount == tr.frameCount ) { return; } // if the entity doesn't have a remoteRenderView, do nothing if( !surf->space->entityDef->parms.remoteRenderView ) { return; } // copy the viewport size from the original parms = ( viewDef_t * )R_FrameAlloc( sizeof( *parms ) ); *parms = *tr.viewDef; parms->isSubview = true; parms->isMirror = false; parms->renderView = *surf->space->entityDef->parms.remoteRenderView; parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons parms->initialViewAreaOrigin = parms->renderView.vieworg; tr.CropRenderSize( stage->width, stage->height, true ); parms->renderView.x = 0; parms->renderView.y = 0; parms->renderView.width = SCREEN_WIDTH; parms->renderView.height = SCREEN_HEIGHT; tr.RenderViewToViewport( &parms->renderView, &parms->viewport ); parms->scissor.x1 = 0; parms->scissor.y1 = 0; parms->scissor.x2 = parms->viewport.x2 - parms->viewport.x1; parms->scissor.y2 = parms->viewport.y2 - parms->viewport.y1; parms->superView = tr.viewDef; parms->subviewSurface = surf; // generate render commands for it R_RenderView( parms ); // copy this rendering to the image stage->dynamicFrameCount = tr.frameCount; if( !stage->image ) { stage->image = globalImages->scratchImage; } tr.CaptureRenderToImage( stage->image->imgName ); tr.UnCrop(); }
/* ==================== R_ToggleSmpFrame ==================== */ void R_ToggleSmpFrame() { // update the highwater mark if( frameData->frameMemoryAllocated.GetValue() > frameData->highWaterAllocated ) { frameData->highWaterAllocated = frameData->frameMemoryAllocated.GetValue(); #if defined( TRACK_FRAME_ALLOCS ) frameData->highWaterUsed = frameData->frameMemoryUsed.GetValue(); for( int i = 0; i < FRAME_ALLOC_MAX; i++ ) { frameHighWaterTypeCount[i] = frameAllocTypeCount[i].GetValue(); } #endif } // switch to the next frame smpFrame++; frameData = &smpFrameData[smpFrame % NUM_FRAME_DATA]; // reset the memory allocation // RB: 64 bit fixes, changed unsigned int to uintptr_t const uintptr_t bytesNeededForAlignment = FRAME_ALLOC_ALIGNMENT - ( ( uintptr_t )frameData->frameMemory & ( FRAME_ALLOC_ALIGNMENT - 1 ) ); // RB end frameData->frameMemoryAllocated.SetValue( bytesNeededForAlignment ); frameData->frameMemoryUsed.SetValue( 0 ); #if defined( TRACK_FRAME_ALLOCS ) for( int i = 0; i < FRAME_ALLOC_MAX; i++ ) { frameAllocTypeCount[i].SetValue( 0 ); } #endif // clear the command chain and make a RC_NOP command the only thing on the list frameData->cmdHead = frameData->cmdTail = ( emptyCommand_t* )R_FrameAlloc( sizeof( *frameData->cmdHead ), FRAME_ALLOC_DRAW_COMMAND ); frameData->cmdHead->commandId = RC_NOP; frameData->cmdHead->next = NULL; }
/* =================== R_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; } }
/* ===================== idRenderModelDecal::CreateDecalDrawSurf ===================== */ drawSurf_t * idRenderModelDecal::CreateDecalDrawSurf( const viewEntity_t *space, unsigned int index ) { if ( index < 0 || index >= numDecalMaterials ) { return NULL; } const idMaterial * material = decalMaterials[index]; int maxVerts = 0; int maxIndexes = 0; for ( unsigned int i = firstDecal; i < nextDecal; i++ ) { const decal_t & decal = decals[i & ( MAX_DECALS - 1 )]; if ( decal.material == material ) { maxVerts += decal.numVerts; maxIndexes += decal.numIndexes; } } if ( maxVerts == 0 || maxIndexes == 0 ) { return NULL; } // create a new triangle surface in frame memory so it gets automatically disposed of srfTriangles_t *newTri = (srfTriangles_t *)R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); newTri->numVerts = maxVerts; newTri->numIndexes = maxIndexes; newTri->ambientCache = vertexCache.AllocVertex( NULL, ALIGN( maxVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); newTri->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); idDrawVert * mappedVerts = (idDrawVert *)vertexCache.MappedVertexBuffer( newTri->ambientCache ); triIndex_t * mappedIndexes = (triIndex_t *)vertexCache.MappedIndexBuffer( newTri->indexCache ); const decalInfo_t decalInfo = material->GetDecalInfo(); const int maxTime = decalInfo.stayTime + decalInfo.fadeTime; const int time = tr.viewDef->renderView.time[0]; int numVerts = 0; int numIndexes = 0; for ( unsigned int i = firstDecal; i < nextDecal; i++ ) { const decal_t & decal = decals[i & ( MAX_DECALS - 1 )]; if ( decal.numVerts == 0 ) { if ( i == firstDecal ) { firstDecal++; } continue; } if ( decal.material != material ) { continue; } const int deltaTime = time - decal.startTime; const int fadeTime = deltaTime - decalInfo.stayTime; if ( deltaTime > maxTime ) { continue; // already completely faded away, but not yet removed } const float f = ( deltaTime > decalInfo.stayTime ) ? ( (float) fadeTime / decalInfo.fadeTime ) : 0.0f; ALIGNTYPE16 float fadeColor[4]; for ( int j = 0; j < 4; j++ ) { fadeColor[j] = 255.0f * ( decalInfo.start[j] + ( decalInfo.end[j] - decalInfo.start[j] ) * f ); } // use SIMD optimized routine to copy the vertices and indices directly to write-combined memory // this also applies any depth/time based fading while copying R_CopyDecalSurface( mappedVerts, numVerts, mappedIndexes, numIndexes, &decal, fadeColor ); numVerts += decal.numVerts; numIndexes += decal.numIndexes; } newTri->numVerts = numVerts; newTri->numIndexes = numIndexes; // create the drawsurf drawSurf_t * drawSurf = (drawSurf_t *)R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); drawSurf->frontEndGeo = newTri; drawSurf->numIndexes = newTri->numIndexes; drawSurf->ambientCache = newTri->ambientCache; drawSurf->indexCache = newTri->indexCache; drawSurf->shadowCache = 0; drawSurf->jointCache = 0; drawSurf->space = space; drawSurf->scissorRect = space->scissorRect; drawSurf->extraGLState = 0; drawSurf->renderZFail = 0; R_SetupDrawSurfShader( drawSurf, material, &space->entityDef->parms ); return drawSurf; }
/* ==================== idRenderModelOverlay::CreateOverlayDrawSurf ==================== */ drawSurf_t* idRenderModelOverlay::CreateOverlayDrawSurf( const viewEntity_t* space, const idRenderModel* baseModel, unsigned int index ) { if( index < 0 || index >= numOverlayMaterials ) { return NULL; } // md5 models won't have any surfaces when r_showSkel is set if( baseModel == NULL || baseModel->IsDefaultModel() || baseModel->NumSurfaces() == 0 ) { return NULL; } assert( baseModel->IsDynamicModel() == DM_STATIC ); const idRenderModelStatic* staticModel = static_cast< const idRenderModelStatic* >( baseModel ); const idMaterial* material = overlayMaterials[index]; int maxVerts = 0; int maxIndexes = 0; for( unsigned int i = firstOverlay; i < nextOverlay; i++ ) { const overlay_t& overlay = overlays[i & ( MAX_OVERLAYS - 1 )]; if( overlay.material == material ) { maxVerts += overlay.numVerts; maxIndexes += overlay.numIndexes; } } if( maxVerts == 0 || maxIndexes == 0 ) { return NULL; } // create a new triangle surface in frame memory so it gets automatically disposed of srfTriangles_t* newTri = ( srfTriangles_t* )R_ClearedFrameAlloc( sizeof( *newTri ), FRAME_ALLOC_SURFACE_TRIANGLES ); newTri->staticModelWithJoints = ( staticModel->jointsInverted != NULL ) ? const_cast< idRenderModelStatic* >( staticModel ) : NULL; // allow GPU skinning newTri->ambientCache = vertexCache.AllocVertex( NULL, ALIGN( maxVerts * sizeof( idDrawVert ), VERTEX_CACHE_ALIGN ) ); newTri->indexCache = vertexCache.AllocIndex( NULL, ALIGN( maxIndexes * sizeof( triIndex_t ), INDEX_CACHE_ALIGN ) ); idDrawVert* mappedVerts = ( idDrawVert* )vertexCache.MappedVertexBuffer( newTri->ambientCache ); triIndex_t* mappedIndexes = ( triIndex_t* )vertexCache.MappedIndexBuffer( newTri->indexCache ); int numVerts = 0; int numIndexes = 0; for( unsigned int i = firstOverlay; i < nextOverlay; i++ ) { overlay_t& overlay = overlays[i & ( MAX_OVERLAYS - 1 )]; if( overlay.numVerts == 0 ) { if( i == firstOverlay ) { firstOverlay++; } continue; } if( overlay.material != material ) { continue; } // get the source model surface for this overlay surface const modelSurface_t* baseSurf = ( overlay.surfaceNum < staticModel->NumSurfaces() ) ? staticModel->Surface( overlay.surfaceNum ) : NULL; // if the surface ids no longer match if( baseSurf == NULL || baseSurf->id != overlay.surfaceId ) { // find the surface with the correct id if( staticModel->FindSurfaceWithId( overlay.surfaceId, overlay.surfaceNum ) ) { baseSurf = staticModel->Surface( overlay.surfaceNum ); } else { // the surface with this id no longer exists FreeOverlay( overlay ); if( i == firstOverlay ) { firstOverlay++; } continue; } } // check for out of range vertex references const srfTriangles_t* baseTri = baseSurf->geometry; if( overlay.maxReferencedVertex >= baseTri->numVerts ) { // This can happen when playing a demofile and a model has been changed since it was recorded, so just issue a warning and go on. common->Warning( "idRenderModelOverlay::CreateOverlayDrawSurf: overlay vertex out of range. Model has probably changed since generating the overlay." ); FreeOverlay( overlay ); if( i == firstOverlay ) { firstOverlay++; } continue; } // use SIMD optimized routine to copy the vertices and indices directly to write-combined memory R_CopyOverlaySurface( mappedVerts, numVerts, mappedIndexes, numIndexes, &overlay, baseTri->verts ); numIndexes += overlay.numIndexes; numVerts += overlay.numVerts; } newTri->numVerts = numVerts; newTri->numIndexes = numIndexes; // create the drawsurf drawSurf_t* drawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); drawSurf->frontEndGeo = newTri; drawSurf->numIndexes = newTri->numIndexes; drawSurf->ambientCache = newTri->ambientCache; drawSurf->indexCache = newTri->indexCache; drawSurf->shadowCache = 0; drawSurf->space = space; drawSurf->scissorRect = space->scissorRect; drawSurf->extraGLState = 0; drawSurf->renderZFail = 0; R_SetupDrawSurfShader( drawSurf, material, &space->entityDef->parms ); R_SetupDrawSurfJoints( drawSurf, newTri, NULL ); return drawSurf; }
/* ================ idGuiModel::EmitFullScreen Creates a view that covers the screen and emit the surfaces ================ */ void idGuiModel::EmitFullScreen( void ) { viewDef_t *viewDef; if ( surfaces[0].numVerts == 0 ) { return; } viewDef = (viewDef_t *)R_ClearedFrameAlloc( sizeof( *viewDef ) ); // for gui editor if ( !tr.viewDef || !tr.viewDef->isEditor ) { viewDef->renderView.x = 0; viewDef->renderView.y = 0; viewDef->renderView.width = SCREEN_WIDTH; viewDef->renderView.height = SCREEN_HEIGHT; tr.RenderViewToViewport( &viewDef->renderView, &viewDef->viewport ); 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; } else { viewDef->renderView.x = tr.viewDef->renderView.x; viewDef->renderView.y = tr.viewDef->renderView.y; viewDef->renderView.width = tr.viewDef->renderView.width; viewDef->renderView.height = tr.viewDef->renderView.height; viewDef->viewport.x1 = tr.viewDef->renderView.x; viewDef->viewport.x2 = tr.viewDef->renderView.x + tr.viewDef->renderView.width; viewDef->viewport.y1 = tr.viewDef->renderView.y; viewDef->viewport.y2 = tr.viewDef->renderView.y + tr.viewDef->renderView.height; viewDef->scissor.x1 = tr.viewDef->scissor.x1; viewDef->scissor.y1 = tr.viewDef->scissor.y1; viewDef->scissor.x2 = tr.viewDef->scissor.x2; viewDef->scissor.y2 = tr.viewDef->scissor.y2; } viewDef->floatTime = tr.frameShaderTime; // qglOrtho( 0, 640, 480, 0, 0, 1 ); // always assume 640x480 virtual coordinates viewDef->projectionMatrix[0] = 2.0f / 640.0f; viewDef->projectionMatrix[5] = -2.0f / 480.0f; viewDef->projectionMatrix[10] = -2.0f / 1.0f; viewDef->projectionMatrix[12] = -1.0f; viewDef->projectionMatrix[13] = 1.0f; viewDef->projectionMatrix[14] = -1.0f; viewDef->projectionMatrix[15] = 1.0f; viewDef->worldSpace.modelViewMatrix[0] = 1.0f; viewDef->worldSpace.modelViewMatrix[5] = 1.0f; viewDef->worldSpace.modelViewMatrix[10] = 1.0f; viewDef->worldSpace.modelViewMatrix[15] = 1.0f; viewDef->maxDrawSurfs = surfaces.Num(); viewDef->drawSurfs = (drawSurf_t **)R_FrameAlloc( viewDef->maxDrawSurfs * sizeof( viewDef->drawSurfs[0] ) ); viewDef->numDrawSurfs = 0; viewDef_t *oldViewDef = tr.viewDef; tr.viewDef = viewDef; // add the surfaces to this view for ( int i = 0 ; i < surfaces.Num() ; i++ ) { EmitSurface( &surfaces[i], viewDef->worldSpace.modelMatrix, viewDef->worldSpace.modelViewMatrix, false ); } tr.viewDef = oldViewDef; // add the command to draw this view R_AddDrawViewCmd( viewDef ); }
/* ==================== R_ClearCommandChain Called after every buffer submission and by R_ToggleSmpFrame ==================== */ void R_ClearCommandChain( void ) { // clear the command chain frameData->cmdHead = frameData->cmdTail = (emptyCommand_t *)R_FrameAlloc( sizeof( *frameData->cmdHead ) ); frameData->cmdHead->commandId = RC_NOP; frameData->cmdHead->next = NULL; }
/* ======================== R_MirrorViewBySurface ======================== */ static viewDef_t* R_MirrorViewBySurface( const drawSurf_t* drawSurf ) { // copy the viewport size from the original viewDef_t* parms = ( viewDef_t* )R_FrameAlloc( sizeof( *parms ) ); *parms = *tr.viewDef; parms->renderView.viewID = 0; // clear to allow player bodies to show up, and suppress view weapons parms->isSubview = true; parms->isMirror = true; parms->isObliqueProjection = false; // create plane axis for the portal we are seeing idPlane originalPlane, plane; R_PlaneForSurface( drawSurf->frontEndGeo, originalPlane ); R_LocalPlaneToGlobal( drawSurf->space->modelMatrix, originalPlane, plane ); orientation_t surface; surface.origin = plane.Normal() * -plane[3]; surface.axis[0] = plane.Normal(); surface.axis[0].NormalVectors( surface.axis[1], surface.axis[2] ); surface.axis[2] = -surface.axis[2]; orientation_t camera; camera.origin = surface.origin; camera.axis[0] = -surface.axis[0]; camera.axis[1] = surface.axis[1]; camera.axis[2] = surface.axis[2]; // set the mirrored origin and axis R_MirrorPoint( tr.viewDef->renderView.vieworg, &surface, &camera, parms->renderView.vieworg ); R_MirrorVector( tr.viewDef->renderView.viewaxis[0], &surface, &camera, parms->renderView.viewaxis[0] ); R_MirrorVector( tr.viewDef->renderView.viewaxis[1], &surface, &camera, parms->renderView.viewaxis[1] ); R_MirrorVector( tr.viewDef->renderView.viewaxis[2], &surface, &camera, parms->renderView.viewaxis[2] ); // make the view origin 16 units away from the center of the surface const idVec3 center = (drawSurf->frontEndGeo->bounds[0] + drawSurf->frontEndGeo->bounds[1]) * 0.5f; const idVec3 viewOrigin = center + (originalPlane.Normal() * 16.0f); R_LocalPointToGlobal(drawSurf->space->modelMatrix, viewOrigin, parms->initialViewAreaOrigin); // set the mirror clip plane parms->numClipPlanes = 1; parms->clipPlanes[0] = -camera.axis[0]; parms->clipPlanes[0][3] = -( camera.origin * parms->clipPlanes[0].Normal() ); if (r_waterReflectFix.GetBool() && !parms->is2Dgui && drawSurf->material->GetSurfaceType() == SURFTYPE_MIRROR) { parms->isObliqueProjection = true; float dist = parms->clipPlanes[0].Dist(); float viewdist = parms->renderView.vieworg * parms->clipPlanes[0].Normal(); float fDist = -dist + viewdist; static const float fudge = 2.f; //fudge avoids depth precision artifacts when performing oblique projection if (fDist > fudge || fDist < -fudge) { if (fDist < 0.f) fDist += fudge; else fDist -= fudge; } parms->clipPlanes[0][3] = fDist; R_SetupViewMatrix(parms); R_SetupProjectionMatrix(parms); R_ObliqueProjection(parms); } return parms; }
/* ================== RB_ShowOverdraw ================== */ void RB_ShowOverdraw() { const idMaterial * material; int i; drawSurf_t * * drawSurfs; const drawSurf_t * surf; int numDrawSurfs; viewLight_t * vLight; if ( r_showOverDraw.GetInteger() == 0 ) { return; } material = declManager->FindMaterial( "textures/common/overdrawtest", false ); if ( material == NULL ) { return; } drawSurfs = backEnd.viewDef->drawSurfs; numDrawSurfs = backEnd.viewDef->numDrawSurfs; int interactions = 0; for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { for ( surf = vLight->localInteractions; surf; surf = surf->nextOnLight ) { interactions++; } for ( surf = vLight->globalInteractions; surf; surf = surf->nextOnLight ) { interactions++; } } // FIXME: can't frame alloc from the renderer back-end drawSurf_t **newDrawSurfs = (drawSurf_t **)R_FrameAlloc( numDrawSurfs + interactions * sizeof( newDrawSurfs[0] ), FRAME_ALLOC_DRAW_SURFACE_POINTER ); for ( i = 0; i < numDrawSurfs; i++ ) { surf = drawSurfs[i]; if ( surf->material ) { const_cast<drawSurf_t *>(surf)->material = material; } newDrawSurfs[i] = const_cast<drawSurf_t *>(surf); } for ( vLight = backEnd.viewDef->viewLights; vLight; vLight = vLight->next ) { for ( surf = vLight->localInteractions; surf; surf = surf->nextOnLight ) { const_cast<drawSurf_t *>(surf)->material = material; newDrawSurfs[i++] = const_cast<drawSurf_t *>(surf); } for ( surf = vLight->globalInteractions; surf; surf = surf->nextOnLight ) { const_cast<drawSurf_t *>(surf)->material = material; newDrawSurfs[i++] = const_cast<drawSurf_t *>(surf); } vLight->localInteractions = NULL; vLight->globalInteractions = NULL; } switch( r_showOverDraw.GetInteger() ) { case 1: // geometry overdraw const_cast<viewDef_t *>(backEnd.viewDef)->drawSurfs = newDrawSurfs; const_cast<viewDef_t *>(backEnd.viewDef)->numDrawSurfs = numDrawSurfs; break; case 2: // light interaction overdraw const_cast<viewDef_t *>(backEnd.viewDef)->drawSurfs = &newDrawSurfs[numDrawSurfs]; const_cast<viewDef_t *>(backEnd.viewDef)->numDrawSurfs = interactions; break; case 3: // geometry + light interaction overdraw const_cast<viewDef_t *>(backEnd.viewDef)->drawSurfs = newDrawSurfs; const_cast<viewDef_t *>(backEnd.viewDef)->numDrawSurfs += interactions; break; } }
/* =================== 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; } } }
/* ================ EmitSurfaces For full screen GUIs, we can add in per-surface stereoscopic depth effects ================ */ void idGuiModel::EmitSurfaces( float modelMatrix[16], float modelViewMatrix[16], bool depthHack, bool allowFullScreenStereoDepth, bool linkAsEntity ) { viewEntity_t* guiSpace = ( viewEntity_t* )R_ClearedFrameAlloc( sizeof( *guiSpace ), FRAME_ALLOC_VIEW_ENTITY ); memcpy( guiSpace->modelMatrix, modelMatrix, sizeof( guiSpace->modelMatrix ) ); memcpy( guiSpace->modelViewMatrix, modelViewMatrix, sizeof( guiSpace->modelViewMatrix ) ); guiSpace->weaponDepthHack = depthHack; guiSpace->isGuiSurface = true; // If this is an in-game gui, we need to be able to find the matrix again for head mounted // display bypass matrix fixup. if( linkAsEntity ) { guiSpace->next = tr.viewDef->viewEntitys; tr.viewDef->viewEntitys = guiSpace; } //--------------------------- // make a tech5 renderMatrix //--------------------------- idRenderMatrix viewMat; idRenderMatrix::Transpose( *( idRenderMatrix* )modelViewMatrix, viewMat ); idRenderMatrix::Multiply( tr.viewDef->projectionRenderMatrix, viewMat, guiSpace->mvp ); if( depthHack ) { idRenderMatrix::ApplyDepthHack( guiSpace->mvp ); } // to allow 3D-TV effects in the menu system, we define surface flags to set // depth fractions between 0=screen and 1=infinity, which directly modulate the // screenSeparation parameter for an X offset. // The value is stored in the drawSurf sort value, which adjusts the matrix in the // backend. float defaultStereoDepth = stereoRender_defaultGuiDepth.GetFloat(); // default to at-screen // add the surfaces to this view for( int i = 0; i < surfaces.Num(); i++ ) { const guiModelSurface_t& guiSurf = surfaces[i]; if( guiSurf.numIndexes == 0 ) { continue; } const idMaterial* shader = guiSurf.material; drawSurf_t* drawSurf = ( drawSurf_t* )R_FrameAlloc( sizeof( *drawSurf ), FRAME_ALLOC_DRAW_SURFACE ); drawSurf->numIndexes = guiSurf.numIndexes; drawSurf->ambientCache = vertexBlock; // build a vertCacheHandle_t that points inside the allocated block drawSurf->indexCache = indexBlock + ( ( int64 )( guiSurf.firstIndex * sizeof( triIndex_t ) ) << VERTCACHE_OFFSET_SHIFT ); drawSurf->shadowCache = 0; drawSurf->jointCache = 0; drawSurf->frontEndGeo = NULL; drawSurf->space = guiSpace; drawSurf->material = shader; drawSurf->extraGLState = guiSurf.glState; drawSurf->scissorRect = tr.viewDef->scissor; drawSurf->sort = shader->GetSort(); drawSurf->renderZFail = 0; // process the shader expressions for conditionals / color / texcoords const float* constRegs = shader->ConstantRegisters(); if( constRegs ) { // shader only uses constant values drawSurf->shaderRegisters = constRegs; } else { float* regs = ( float* )R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ), FRAME_ALLOC_SHADER_REGISTER ); drawSurf->shaderRegisters = regs; shader->EvaluateRegisters( regs, shaderParms, tr.viewDef->renderView.shaderParms, tr.viewDef->renderView.time[1] * 0.001f, NULL ); } R_LinkDrawSurfToView( drawSurf, tr.viewDef ); if( allowFullScreenStereoDepth ) { // override sort with the stereoDepth //drawSurf->sort = stereoDepth; switch( guiSurf.stereoType ) { case STEREO_DEPTH_TYPE_NEAR: drawSurf->sort = STEREO_DEPTH_NEAR; break; case STEREO_DEPTH_TYPE_MID: drawSurf->sort = STEREO_DEPTH_MID; break; case STEREO_DEPTH_TYPE_FAR: drawSurf->sort = STEREO_DEPTH_FAR; break; case STEREO_DEPTH_TYPE_NONE: default: drawSurf->sort = defaultStereoDepth; break; } } } }
/* ================ 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_AddDrawSurf ================= */ void R_AddDrawSurf( const srfTriangles_t *tri, const viewEntity_t *space, const renderEntity_t *renderEntity, const idMaterial *shader, const idScreenRect &scissor ) { drawSurf_t *drawSurf; const float *shaderParms; static float refRegs[MAX_EXPRESSION_REGISTERS]; // don't put on stack, or VC++ will do a page touch float generatedShaderParms[MAX_ENTITY_SHADER_PARMS]; drawSurf = ( drawSurf_t * ) R_FrameAlloc( sizeof( *drawSurf ) ); drawSurf->geo = tri; drawSurf->space = space; drawSurf->material = shader; drawSurf->scissorRect = scissor; drawSurf->sort = shader->GetSort() + tr.sortOffset; drawSurf->dsFlags = 0; // bumping this offset each time causes surfaces with equal sort orders to still // deterministically draw in the order they are added tr.sortOffset += 0.000001f; // if it doesn't fit, resize the list if( tr.viewDef->numDrawSurfs == tr.viewDef->maxDrawSurfs ) { drawSurf_t **old = tr.viewDef->drawSurfs; int count; if( tr.viewDef->maxDrawSurfs == 0 ) { tr.viewDef->maxDrawSurfs = INITIAL_DRAWSURFS; count = 0; } else { count = tr.viewDef->maxDrawSurfs * sizeof( tr.viewDef->drawSurfs[0] ); tr.viewDef->maxDrawSurfs *= 2; } tr.viewDef->drawSurfs = ( drawSurf_t ** ) R_FrameAlloc( tr.viewDef->maxDrawSurfs * sizeof( tr.viewDef->drawSurfs[0] ) ); memcpy( tr.viewDef->drawSurfs, old, count ); } tr.viewDef->drawSurfs[tr.viewDef->numDrawSurfs] = drawSurf; tr.viewDef->numDrawSurfs++; // process the shader expressions for conditionals / color / texcoords const float *constRegs = shader->ConstantRegisters(); if( constRegs ) { // shader only uses constant values drawSurf->shaderRegisters = constRegs; } else { float *regs = ( float * ) R_FrameAlloc( shader->GetNumRegisters() * sizeof( float ) ); drawSurf->shaderRegisters = regs; // a reference shader will take the calculated stage color value from another shader // and use that for the parm0-parm3 of the current shader, which allows a stage of // a light model and light flares to pick up different flashing tables from // different light shaders if( renderEntity->referenceShader ) { // evaluate the reference shader to find our shader parms const shaderStage_t *pStage; renderEntity->referenceShader->EvaluateRegisters( refRegs, renderEntity->shaderParms, tr.viewDef, renderEntity->referenceSound ); pStage = renderEntity->referenceShader->GetStage( 0 ); memcpy( generatedShaderParms, renderEntity->shaderParms, sizeof( generatedShaderParms ) ); generatedShaderParms[0] = refRegs[pStage->color.registers[0]]; generatedShaderParms[1] = refRegs[pStage->color.registers[1]]; generatedShaderParms[2] = refRegs[pStage->color.registers[2]]; shaderParms = generatedShaderParms; } else { // evaluate with the entityDef's shader parms shaderParms = renderEntity->shaderParms; } float oldFloatTime; int oldTime; if( space->entityDef && space->entityDef->parms.timeGroup ) { oldFloatTime = tr.viewDef->floatTime; oldTime = tr.viewDef->renderView.time; tr.viewDef->floatTime = game->GetTimeGroupTime( space->entityDef->parms.timeGroup ) * 0.001; tr.viewDef->renderView.time = game->GetTimeGroupTime( space->entityDef->parms.timeGroup ); } shader->EvaluateRegisters( regs, shaderParms, tr.viewDef, renderEntity->referenceSound ); if( space->entityDef && space->entityDef->parms.timeGroup ) { tr.viewDef->floatTime = oldFloatTime; tr.viewDef->renderView.time = oldTime; } } // check for deformations R_DeformDrawSurf( drawSurf ); // skybox surfaces need a dynamic texgen switch( shader->Texgen() ) { case TG_SKYBOX_CUBE: R_SkyboxTexGen( drawSurf, tr.viewDef->renderView.vieworg ); break; case TG_WOBBLESKY_CUBE: R_WobbleskyTexGen( drawSurf, tr.viewDef->renderView.vieworg ); break; default: break; } // check for gui surfaces idUserInterface *gui = NULL; if( !space->entityDef ) { gui = shader->GlobalGui(); } else { int guiNum = shader->GetEntityGui() - 1; if( guiNum >= 0 && guiNum < MAX_RENDERENTITY_GUI ) { gui = renderEntity->gui[guiNum]; } if( gui == NULL ) { gui = shader->GlobalGui(); } } if( gui ) { // force guis on the fast time float oldFloatTime; int oldTime; oldFloatTime = tr.viewDef->floatTime; oldTime = tr.viewDef->renderView.time; tr.viewDef->floatTime = game->GetTimeGroupTime( 1 ) * 0.001; tr.viewDef->renderView.time = game->GetTimeGroupTime( 1 ); idBounds ndcBounds; if( !R_PreciseCullSurface( drawSurf, ndcBounds ) ) { R_RenderGuiSurf( gui, drawSurf ); } tr.viewDef->floatTime = oldFloatTime; tr.viewDef->renderView.time = oldTime; } // we can't add subviews at this point, because that would // increment tr.viewCount, messing up the rest of the surface // adds for this view }
/* ================== R_ClearedFrameAlloc ================== */ void *R_ClearedFrameAlloc( int bytes, frameAllocType_t type ) { // NOTE: every allocation is cache line cleared return R_FrameAlloc( bytes, type ); }
/* ================= 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 ); } } }