diff --git a/neo/engine/opengl/gl_radiant_moc.cpp b/neo/engine/opengl/gl_radiant_moc.cpp new file mode 100644 index 00000000..59d8412c --- /dev/null +++ b/neo/engine/opengl/gl_radiant_moc.cpp @@ -0,0 +1,220 @@ +/* +=========================================================================== + +Radiant masked occlusion culling bridge + +=========================================================================== +*/ + +#include "gl_radiant_moc.h" + +#define USE_D3D 0 +#define USE_AVX512 0 +#include "thirdparty/MaskedOcclusionCulling/MaskedOcclusionCulling.h" + +#include + +namespace { + +struct radiantMOCState_t { + MaskedOcclusionCulling* moc; + unsigned int width; + unsigned int height; + bool active; + qglRadiantMOCStats_t stats; +}; + +radiantMOCState_t s_radiantMOC = {}; + +static unsigned int RadiantMOC_RoundUp(unsigned int value, unsigned int alignment) { + return (value + alignment - 1) & ~(alignment - 1); +} + +static radiantMOCState_t* RadiantMOC_FromContext(qglMOCContext_t context) { + return reinterpret_cast(context); +} + +} // namespace + +qglMOCContext_t QGL_MOC_Create() { + radiantMOCState_t* context = new radiantMOCState_t; + context->moc = NULL; + context->width = 0; + context->height = 0; + context->active = false; + context->stats.occluderTriangles = 0; + context->stats.testedRects = 0; + context->stats.occludedRects = 0; + return context; +} + +void QGL_MOC_Destroy(qglMOCContext_t context) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (state == NULL) { + return; + } + if (state->moc != NULL) { + MaskedOcclusionCulling::Destroy(state->moc); + state->moc = NULL; + } + delete state; +} + +bool QGL_MOC_BeginFrame(qglMOCContext_t context, int width, int height, float nearClip) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (state == NULL) { + return false; + } + + const unsigned int mocWidth = RadiantMOC_RoundUp(std::max(width, 64), 8); + const unsigned int mocHeight = RadiantMOC_RoundUp(std::max(height, 64), 4); + + if (state->moc == NULL) { + state->moc = MaskedOcclusionCulling::Create(MaskedOcclusionCulling::AVX2); + } + + if (state->moc == NULL) { + state->active = false; + return false; + } + + if (state->width != mocWidth || state->height != mocHeight) { + state->moc->SetResolution(mocWidth, mocHeight); + state->width = mocWidth; + state->height = mocHeight; + } + + state->moc->SetNearClipPlane(nearClip); + state->moc->ClearBuffer(); + state->active = true; + state->stats.occluderTriangles = 0; + state->stats.testedRects = 0; + state->stats.occludedRects = 0; + return true; +} + +void QGL_MOC_EndFrame(qglMOCContext_t context) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (state != NULL) { + state->active = false; + } +} + +bool QGL_MOC_IsActive(qglMOCContext_t context) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + return state != NULL && state->active && state->moc != NULL; +} + +void QGL_MOC_RenderTriangles(qglMOCContext_t context, const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (!QGL_MOC_IsActive(context) || verts == NULL || indices == NULL || numTriangles <= 0) { + return; + } + + MaskedOcclusionCulling::VertexLayout layout(sizeof(qglRadiantMOCClipVertex_t), 4, 8); + state->moc->RenderTriangles(&verts[0].x, indices, numTriangles, NULL, MaskedOcclusionCulling::BACKFACE_NONE, MaskedOcclusionCulling::CLIP_PLANE_ALL, layout); + state->stats.occluderTriangles += numTriangles; +} + +bool QGL_MOC_TestTriangles(qglMOCContext_t context, const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (!QGL_MOC_IsActive(context) || verts == NULL || indices == NULL || numTriangles <= 0) { + return true; + } + + MaskedOcclusionCulling::VertexLayout layout(sizeof(qglRadiantMOCClipVertex_t), 4, 8); + state->stats.testedRects++; + const MaskedOcclusionCulling::CullingResult result = state->moc->TestTriangles(&verts[0].x, indices, numTriangles, NULL, MaskedOcclusionCulling::BACKFACE_NONE, MaskedOcclusionCulling::CLIP_PLANE_ALL, layout); + if (result == MaskedOcclusionCulling::OCCLUDED || result == MaskedOcclusionCulling::VIEW_CULLED) { + state->stats.occludedRects++; + return false; + } + return true; +} + +void QGL_MOC_RenderIndexedTriangles(qglMOCContext_t context, const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (!QGL_MOC_IsActive(context) || verts == NULL || indices == NULL || numTriangles <= 0 || modelToClipMatrix == NULL) { + return; + } + + MaskedOcclusionCulling::VertexLayout layout(vertexStride, yOffset, zOffset); + state->moc->RenderTriangles(verts, indices, numTriangles, modelToClipMatrix, MaskedOcclusionCulling::BACKFACE_NONE, MaskedOcclusionCulling::CLIP_PLANE_ALL, layout); + state->stats.occluderTriangles += numTriangles; +} + +bool QGL_MOC_TestIndexedTriangles(qglMOCContext_t context, const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (!QGL_MOC_IsActive(context) || verts == NULL || indices == NULL || numTriangles <= 0 || modelToClipMatrix == NULL) { + return true; + } + + MaskedOcclusionCulling::VertexLayout layout(vertexStride, yOffset, zOffset); + state->stats.testedRects++; + const MaskedOcclusionCulling::CullingResult result = state->moc->TestTriangles(verts, indices, numTriangles, modelToClipMatrix, MaskedOcclusionCulling::BACKFACE_NONE, MaskedOcclusionCulling::CLIP_PLANE_ALL, layout); + if (result == MaskedOcclusionCulling::OCCLUDED || result == MaskedOcclusionCulling::VIEW_CULLED) { + state->stats.occludedRects++; + return false; + } + return true; +} + +bool QGL_MOC_TestRect(qglMOCContext_t context, float xmin, float ymin, float xmax, float ymax, float wmin) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (!QGL_MOC_IsActive(context)) { + return true; + } + + state->stats.testedRects++; + const MaskedOcclusionCulling::CullingResult result = state->moc->TestRect(xmin, ymin, xmax, ymax, wmin); + if (result == MaskedOcclusionCulling::OCCLUDED || result == MaskedOcclusionCulling::VIEW_CULLED) { + state->stats.occludedRects++; + return false; + } + return true; +} + +qglRadiantMOCStats_t QGL_MOC_GetStats(qglMOCContext_t context) { + radiantMOCState_t* state = RadiantMOC_FromContext(context); + if (state == NULL) { + qglRadiantMOCStats_t emptyStats = {}; + return emptyStats; + } + return state->stats; +} + +bool QGL_RadiantMOC_BeginFrame(int width, int height, float nearClip) { + return QGL_MOC_BeginFrame(&s_radiantMOC, width, height, nearClip); +} + +void QGL_RadiantMOC_EndFrame() { + QGL_MOC_EndFrame(&s_radiantMOC); +} + +bool QGL_RadiantMOC_IsActive() { + return QGL_MOC_IsActive(&s_radiantMOC); +} + +void QGL_RadiantMOC_RenderTriangles(const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles) { + QGL_MOC_RenderTriangles(&s_radiantMOC, verts, indices, numTriangles); +} + +bool QGL_RadiantMOC_TestTriangles(const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles) { + return QGL_MOC_TestTriangles(&s_radiantMOC, verts, indices, numTriangles); +} + +void QGL_RadiantMOC_RenderIndexedTriangles(const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset) { + QGL_MOC_RenderIndexedTriangles(&s_radiantMOC, verts, indices, numTriangles, modelToClipMatrix, vertexStride, yOffset, zOffset); +} + +bool QGL_RadiantMOC_TestIndexedTriangles(const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset) { + return QGL_MOC_TestIndexedTriangles(&s_radiantMOC, verts, indices, numTriangles, modelToClipMatrix, vertexStride, yOffset, zOffset); +} + +bool QGL_RadiantMOC_TestRect(float xmin, float ymin, float xmax, float ymax, float wmin) { + return QGL_MOC_TestRect(&s_radiantMOC, xmin, ymin, xmax, ymax, wmin); +} + +qglRadiantMOCStats_t QGL_RadiantMOC_GetStats() { + return s_radiantMOC.stats; +} diff --git a/neo/engine/opengl/gl_radiant_moc.h b/neo/engine/opengl/gl_radiant_moc.h new file mode 100644 index 00000000..527c5af0 --- /dev/null +++ b/neo/engine/opengl/gl_radiant_moc.h @@ -0,0 +1,45 @@ +/* +=========================================================================== + +Radiant masked occlusion culling bridge + +=========================================================================== +*/ + +#pragma once + +struct qglRadiantMOCClipVertex_t { + float x; + float y; + float w; +}; + +struct qglRadiantMOCStats_t { + int occluderTriangles; + int testedRects; + int occludedRects; +}; + +typedef void* qglMOCContext_t; + +qglMOCContext_t QGL_MOC_Create(); +void QGL_MOC_Destroy(qglMOCContext_t context); +bool QGL_MOC_BeginFrame(qglMOCContext_t context, int width, int height, float nearClip); +void QGL_MOC_EndFrame(qglMOCContext_t context); +bool QGL_MOC_IsActive(qglMOCContext_t context); +void QGL_MOC_RenderTriangles(qglMOCContext_t context, const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles); +bool QGL_MOC_TestTriangles(qglMOCContext_t context, const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles); +void QGL_MOC_RenderIndexedTriangles(qglMOCContext_t context, const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset); +bool QGL_MOC_TestIndexedTriangles(qglMOCContext_t context, const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset); +bool QGL_MOC_TestRect(qglMOCContext_t context, float xmin, float ymin, float xmax, float ymax, float wmin); +qglRadiantMOCStats_t QGL_MOC_GetStats(qglMOCContext_t context); + +bool QGL_RadiantMOC_BeginFrame(int width, int height, float nearClip); +void QGL_RadiantMOC_EndFrame(); +bool QGL_RadiantMOC_IsActive(); +void QGL_RadiantMOC_RenderTriangles(const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles); +bool QGL_RadiantMOC_TestTriangles(const qglRadiantMOCClipVertex_t* verts, const unsigned int* indices, int numTriangles); +void QGL_RadiantMOC_RenderIndexedTriangles(const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset); +bool QGL_RadiantMOC_TestIndexedTriangles(const float* verts, const unsigned int* indices, int numTriangles, const float* modelToClipMatrix, int vertexStride, int yOffset, int zOffset); +bool QGL_RadiantMOC_TestRect(float xmin, float ymin, float xmax, float ymax, float wmin); +qglRadiantMOCStats_t QGL_RadiantMOC_GetStats(); diff --git a/neo/engine/renderer/tr_masked_occlusion.cpp b/neo/engine/renderer/tr_masked_occlusion.cpp new file mode 100644 index 00000000..c503f74c --- /dev/null +++ b/neo/engine/renderer/tr_masked_occlusion.cpp @@ -0,0 +1,496 @@ +/* +=========================================================================== + +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; +}