/* =========================================================================== Renderer masked occlusion culling for one-frame-late light rejection. =========================================================================== */ #include "precompiled.h" #pragma hdrstop #include "tr_local.h" #include "../opengl/gl_radiant_moc.h" #include #include #include struct rendererMOCLightQuery_t { int lightIndex; int lastModifiedFrameNum; const srfTriangles_t* tri; int numTriangles; float modelToClip[16]; float xmin; float ymin; float xmax; float ymax; float wmin; }; struct rendererMOCIndexedMesh_t { const srfTriangles_t* tri; int numTriangles; float modelToClip[16]; }; struct rendererMOCJob_t { int sequence; idRenderWorldLocal* world; int lightCount; int width; int height; float nearClip; std::vector occluders; std::vector lightQueries; }; struct rendererMOCResult_t { bool valid; bool consumed; int sequence; idRenderWorldLocal* world; std::vector lightVisible; std::vector lightModifiedFrames; int occluderTriangles; int testedLights; int occludedLights; }; static CRITICAL_SECTION s_rendererMOCCriticalSection; static HANDLE s_rendererMOCWorkEvent = NULL; static HANDLE s_rendererMOCThread = NULL; static bool s_rendererMOCInitialized = false; static bool s_rendererMOCShutdown = false; static bool s_rendererMOCHasPendingJob = false; static bool s_rendererMOCWorkerBusy = false; static bool s_rendererMOCHasCompletedResult = false; static int s_rendererMOCSequence = 0; static rendererMOCJob_t s_rendererMOCPendingJob; static rendererMOCResult_t s_rendererMOCCompletedResult; static rendererMOCResult_t s_rendererMOCLastResult; static float R_MOC_ClampFloat(float value, float minValue, float maxValue) { if (value < minValue) { return minValue; } if (value > maxValue) { return maxValue; } return value; } static bool R_MOC_BuildModelToClipMatrix(const viewDef_t* viewDef, const float* modelMatrix, float matrix[16]) { const float tanX = idMath::Tan16(DEG2RAD(viewDef->renderView.fov_x * 0.5f)); const float tanY = idMath::Tan16(DEG2RAD(viewDef->renderView.fov_y * 0.5f)); if (tanX <= 0.0f || tanY <= 0.0f) { return false; } for (int column = 0; column < 3; column++) { idVec3 worldAxis; if (modelMatrix != NULL) { worldAxis.Set(modelMatrix[column * 4 + 0], modelMatrix[column * 4 + 1], modelMatrix[column * 4 + 2]); } else { worldAxis.Zero(); worldAxis[column] = 1.0f; } matrix[column * 4 + 0] = (worldAxis * viewDef->renderView.viewaxis[1]) / tanX; matrix[column * 4 + 1] = (worldAxis * viewDef->renderView.viewaxis[2]) / tanY; matrix[column * 4 + 2] = 0.0f; matrix[column * 4 + 3] = worldAxis * viewDef->renderView.viewaxis[0]; } idVec3 worldOrigin; if (modelMatrix != NULL) { worldOrigin.Set(modelMatrix[12], modelMatrix[13], modelMatrix[14]); } else { worldOrigin.Zero(); } const idVec3 local = worldOrigin - viewDef->renderView.vieworg; matrix[12] = (local * viewDef->renderView.viewaxis[1]) / tanX; matrix[13] = (local * viewDef->renderView.viewaxis[2]) / tanY; matrix[14] = 0.0f; matrix[15] = local * viewDef->renderView.viewaxis[0]; return true; } static bool R_MOC_MaterialCanOcclude(const idMaterial* shader) { if (shader == NULL || !shader->IsDrawn()) { return false; } if (shader->Coverage() != MC_OPAQUE) { return false; } if (shader->HasSubview() || shader->IsSky()) { return false; } if (shader->GetContentFlags() & CONTENTS_AREAPORTAL) { return false; } return true; } static void R_MOC_AddOccluderSurface(const viewDef_t* viewDef, const viewEntity_t* vEntity, const modelSurface_t* surface, rendererMOCJob_t& job) { if (surface == NULL || surface->geometry == NULL || !R_MOC_MaterialCanOcclude(surface->shader)) { return; } const srfTriangles_t* tri = surface->geometry; if (tri->numIndexes < 3 || tri->numVerts <= 0 || tri->indexes == NULL || tri->verts == NULL) { return; } if (sizeof(tri->indexes[0]) != sizeof(unsigned int)) { return; } rendererMOCIndexedMesh_t mesh; mesh.tri = tri; mesh.numTriangles = tri->numIndexes / 3; if (!R_MOC_BuildModelToClipMatrix(viewDef, vEntity->modelMatrix, mesh.modelToClip)) { return; } job.occluders.push_back(mesh); } static void R_MOC_AddOccludersFromView(const viewDef_t* viewDef, rendererMOCJob_t& job) { for (const viewEntity_t* vEntity = viewDef->viewEntitys; vEntity; vEntity = vEntity->next) { const idRenderEntityLocal* entity = vEntity->entityDef; if (entity == NULL || entity->parms.hModel == NULL) { continue; } if (vEntity->scissorRect.IsEmpty()) { continue; } if (entity->parms.hModel->IsDynamicModel() != DM_STATIC) { continue; } const int numSurfaces = entity->parms.hModel->NumSurfaces(); for (int surfaceNum = 0; surfaceNum < numSurfaces; surfaceNum++) { R_MOC_AddOccluderSurface(viewDef, vEntity, entity->parms.hModel->Surface(surfaceNum), job); } } } static bool R_MOC_BuildLightQuery(const viewDef_t* viewDef, const idRenderLightLocal* light, rendererMOCLightQuery_t& query) { if (light == NULL || light->frustumTris == NULL || light->frustumTris->numVerts <= 0) { return false; } const srfTriangles_t* tri = light->frustumTris; if (tri->numIndexes < 3 || tri->indexes == NULL) { return false; } if (sizeof(tri->indexes[0]) != sizeof(unsigned int)) { return false; } const int numTriangles = tri->numIndexes / 3; if (numTriangles <= 0) { return false; } query.lightIndex = light->index; query.lastModifiedFrameNum = light->lastModifiedFrameNum; query.tri = tri; query.numTriangles = numTriangles; query.xmin = -1.0f; query.ymin = -1.0f; query.xmax = 1.0f; query.ymax = 1.0f; query.wmin = r_znear.GetFloat(); if (!R_MOC_BuildModelToClipMatrix(viewDef, NULL, query.modelToClip)) { return false; } return true; } static void R_MOC_AddLightQueriesFromView(const viewDef_t* viewDef, rendererMOCJob_t& job) { for (const viewLight_t* vLight = viewDef->viewLights; vLight; vLight = vLight->next) { if (vLight->viewInsideLight) { continue; } rendererMOCLightQuery_t query; if (R_MOC_BuildLightQuery(viewDef, vLight->lightDef, query)) { job.lightQueries.push_back(query); } } } static bool R_MOC_BuildJob(const viewDef_t* viewDef, rendererMOCJob_t& job) { if (viewDef == NULL || viewDef->renderWorld == NULL) { return false; } if (viewDef->isSubview || viewDef->isMirror || viewDef->isXraySubview) { return false; } if (viewDef->renderView.width <= 0 || viewDef->renderView.height <= 0) { return false; } idRenderWorldLocal* world = static_cast(viewDef->renderWorld); const float scale = R_MOC_ClampFloat(r_mocResolutionScale.GetFloat(), 0.125f, 1.0f); job.sequence = 0; job.world = world; job.lightCount = world->lightDefs.Num(); job.width = Max(64, idMath::Ftoi(viewDef->renderView.width * scale)); job.height = Max(64, idMath::Ftoi(viewDef->renderView.height * scale)); job.nearClip = r_znear.GetFloat() > 0.0f ? r_znear.GetFloat() : 1.0f; job.occluders.clear(); job.lightQueries.clear(); job.occluders.reserve(4096); job.lightQueries.reserve(256); R_MOC_AddOccludersFromView(viewDef, job); R_MOC_AddLightQueriesFromView(viewDef, job); return job.lightCount > 0 && !job.lightQueries.empty(); } static void R_MOC_RunJob(const rendererMOCJob_t& job, rendererMOCResult_t& result, qglMOCContext_t context) { result.valid = true; result.consumed = false; result.sequence = job.sequence; result.world = job.world; result.lightVisible.assign(job.lightCount, 1); result.lightModifiedFrames.assign(job.lightCount, -1); result.occluderTriangles = 0; result.testedLights = static_cast(job.lightQueries.size()); result.occludedLights = 0; if (!QGL_MOC_BeginFrame(context, job.width, job.height, job.nearClip)) { return; } for (size_t i = 0; i < job.occluders.size(); i++) { const rendererMOCIndexedMesh_t& mesh = job.occluders[i]; if (mesh.tri == NULL || mesh.tri->verts == NULL || mesh.tri->indexes == NULL || mesh.numTriangles <= 0) { continue; } QGL_MOC_RenderIndexedTriangles(context, mesh.tri->verts[0].xyz.ToFloatPtr(), reinterpret_cast(mesh.tri->indexes), mesh.numTriangles, mesh.modelToClip, sizeof(mesh.tri->verts[0]), 4, 8); result.occluderTriangles += mesh.numTriangles; } for (size_t i = 0; i < job.lightQueries.size(); i++) { const rendererMOCLightQuery_t& query = job.lightQueries[i]; if (query.lightIndex < 0 || query.lightIndex >= static_cast(result.lightVisible.size())) { continue; } bool visible = true; if (query.tri != NULL && query.tri->verts != NULL && query.tri->indexes != NULL && query.numTriangles > 0) { visible = QGL_MOC_TestIndexedTriangles(context, query.tri->verts[0].xyz.ToFloatPtr(), reinterpret_cast(query.tri->indexes), query.numTriangles, query.modelToClip, sizeof(query.tri->verts[0]), 4, 8); } else if (query.xmin < query.xmax && query.ymin < query.ymax) { visible = QGL_MOC_TestRect(context, query.xmin, query.ymin, query.xmax, query.ymax, query.wmin); } result.lightVisible[query.lightIndex] = visible ? 1 : 0; result.lightModifiedFrames[query.lightIndex] = query.lastModifiedFrameNum; if (!visible) { result.occludedLights++; } } QGL_MOC_EndFrame(context); } static DWORD WINAPI R_MOC_ThreadProc(LPVOID) { qglMOCContext_t context = QGL_MOC_Create(); for (;;) { WaitForSingleObject(s_rendererMOCWorkEvent, INFINITE); rendererMOCJob_t job; EnterCriticalSection(&s_rendererMOCCriticalSection); if (s_rendererMOCShutdown) { LeaveCriticalSection(&s_rendererMOCCriticalSection); break; } if (!s_rendererMOCHasPendingJob) { LeaveCriticalSection(&s_rendererMOCCriticalSection); continue; } job = s_rendererMOCPendingJob; s_rendererMOCPendingJob = rendererMOCJob_t(); s_rendererMOCHasPendingJob = false; s_rendererMOCWorkerBusy = true; LeaveCriticalSection(&s_rendererMOCCriticalSection); rendererMOCResult_t result; R_MOC_RunJob(job, result, context); EnterCriticalSection(&s_rendererMOCCriticalSection); s_rendererMOCCompletedResult = result; s_rendererMOCHasCompletedResult = true; s_rendererMOCWorkerBusy = false; LeaveCriticalSection(&s_rendererMOCCriticalSection); } QGL_MOC_Destroy(context); return 0; } static void R_MOC_Init() { if (s_rendererMOCInitialized) { return; } InitializeCriticalSection(&s_rendererMOCCriticalSection); s_rendererMOCWorkEvent = CreateEvent(NULL, FALSE, FALSE, NULL); if (s_rendererMOCWorkEvent == NULL) { DeleteCriticalSection(&s_rendererMOCCriticalSection); return; } s_rendererMOCShutdown = false; s_rendererMOCThread = CreateThread(NULL, 0, R_MOC_ThreadProc, NULL, 0, NULL); if (s_rendererMOCThread == NULL) { CloseHandle(s_rendererMOCWorkEvent); s_rendererMOCWorkEvent = NULL; DeleteCriticalSection(&s_rendererMOCCriticalSection); return; } s_rendererMOCInitialized = true; } static void R_MOC_HarvestCompletedResult() { if (!s_rendererMOCInitialized) { return; } EnterCriticalSection(&s_rendererMOCCriticalSection); if (s_rendererMOCHasCompletedResult) { s_rendererMOCLastResult = s_rendererMOCCompletedResult; s_rendererMOCCompletedResult = rendererMOCResult_t(); s_rendererMOCHasCompletedResult = false; } LeaveCriticalSection(&s_rendererMOCCriticalSection); } static void R_MOC_TrySubmitJob(rendererMOCJob_t job) { if (!s_rendererMOCInitialized || s_rendererMOCThread == NULL || s_rendererMOCWorkEvent == NULL) { return; } EnterCriticalSection(&s_rendererMOCCriticalSection); if (!s_rendererMOCHasPendingJob && !s_rendererMOCWorkerBusy) { job.sequence = ++s_rendererMOCSequence; s_rendererMOCPendingJob = job; s_rendererMOCHasPendingJob = true; SetEvent(s_rendererMOCWorkEvent); } LeaveCriticalSection(&s_rendererMOCCriticalSection); } static void R_MOC_PruneViewLights(viewDef_t* viewDef) { if (!s_rendererMOCLastResult.valid || s_rendererMOCLastResult.consumed || s_rendererMOCLastResult.world != viewDef->renderWorld) { return; } viewLight_t** link = &viewDef->viewLights; while (*link != NULL) { viewLight_t* vLight = *link; idRenderLightLocal* light = vLight->lightDef; const int lightIndex = light ? light->index : -1; if (lightIndex >= 0 && lightIndex < static_cast(s_rendererMOCLastResult.lightVisible.size()) && lightIndex < static_cast(s_rendererMOCLastResult.lightModifiedFrames.size()) && s_rendererMOCLastResult.lightModifiedFrames[lightIndex] == light->lastModifiedFrameNum && s_rendererMOCLastResult.lightVisible[lightIndex] == 0) { *link = vLight->next; light->viewCount = -1; continue; } link = &(*link)->next; } s_rendererMOCLastResult.consumed = true; } bool R_MaskedOcclusionCulling_LightIsVisible(const viewDef_t* viewDef, const idRenderLightLocal* light) { if (!r_useMaskedOcclusionCulling.GetBool() || viewDef == NULL || light == NULL) { return true; } if (!s_rendererMOCLastResult.valid || s_rendererMOCLastResult.world != viewDef->renderWorld) { return true; } const int lightIndex = light->index; if (lightIndex < 0 || lightIndex >= static_cast(s_rendererMOCLastResult.lightVisible.size()) || lightIndex >= static_cast(s_rendererMOCLastResult.lightModifiedFrames.size())) { return true; } if (s_rendererMOCLastResult.lightModifiedFrames[lightIndex] != light->lastModifiedFrameNum) { return true; } return s_rendererMOCLastResult.lightVisible[lightIndex] != 0; } void R_MaskedOcclusionCulling_ProcessView(viewDef_t* viewDef) { if (!r_useMaskedOcclusionCulling.GetBool()) { return; } R_MOC_Init(); R_MOC_HarvestCompletedResult(); rendererMOCJob_t job; if (R_MOC_BuildJob(viewDef, job)) { R_MOC_TrySubmitJob(job); } R_MOC_PruneViewLights(viewDef); } void R_MaskedOcclusionCulling_Shutdown() { if (!s_rendererMOCInitialized) { return; } EnterCriticalSection(&s_rendererMOCCriticalSection); s_rendererMOCShutdown = true; SetEvent(s_rendererMOCWorkEvent); LeaveCriticalSection(&s_rendererMOCCriticalSection); if (s_rendererMOCThread != NULL) { WaitForSingleObject(s_rendererMOCThread, INFINITE); CloseHandle(s_rendererMOCThread); s_rendererMOCThread = NULL; } if (s_rendererMOCWorkEvent != NULL) { CloseHandle(s_rendererMOCWorkEvent); s_rendererMOCWorkEvent = NULL; } DeleteCriticalSection(&s_rendererMOCCriticalSection); s_rendererMOCInitialized = false; s_rendererMOCPendingJob = rendererMOCJob_t(); s_rendererMOCCompletedResult = rendererMOCResult_t(); s_rendererMOCLastResult = rendererMOCResult_t(); s_rendererMOCHasPendingJob = false; s_rendererMOCHasCompletedResult = false; s_rendererMOCWorkerBusy = false; }