From 19f7497d9a01731cbd82be4b522d8b879cdcb8a0 Mon Sep 17 00:00:00 2001
From: Dave Parks <davep@lindenlab.com>
Date: Tue, 21 Feb 2023 20:42:25 -0600
Subject: [PATCH] DRTVWR-559 WIP -- occlusion culling for reflection probes --
 has a defect for objects close to the camera at some angles and leaks query
 objects, will follow up.

---
 indra/newview/lldrawpool.cpp             |  3 +
 indra/newview/lldrawpool.h               |  5 +-
 indra/newview/lldrawpoolpbropaque.cpp    | 31 ++++-----
 indra/newview/lldrawpoolpbropaque.h      | 16 +----
 indra/newview/llreflectionmap.cpp        | 81 ++++++++++++++++++++++++
 indra/newview/llreflectionmap.h          | 11 ++++
 indra/newview/llreflectionmapmanager.cpp | 54 ++++++++++++++--
 indra/newview/llreflectionmapmanager.h   |  3 +
 indra/newview/pipeline.cpp               | 42 ++++++++++++
 indra/newview/pipeline.h                 |  2 +
 10 files changed, 210 insertions(+), 38 deletions(-)

diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp
index 56377069bb2..c61618c056a 100644
--- a/indra/newview/lldrawpool.cpp
+++ b/indra/newview/lldrawpool.cpp
@@ -116,6 +116,9 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0)
 		break;
 	case POOL_GLTF_PBR:
 		poolp = new LLDrawPoolGLTFPBR();
+        break;
+    case POOL_GLTF_PBR_ALPHA_MASK:
+        poolp = new LLDrawPoolGLTFPBR(LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK);
 		break;
 	default:
 		LL_ERRS() << "Unknown draw pool type!" << LL_ENDL;
diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h
index 2c5e31f579e..5e741b2b952 100644
--- a/indra/newview/lldrawpool.h
+++ b/indra/newview/lldrawpool.h
@@ -58,8 +58,9 @@ class LLDrawPool
 		POOL_BUMP,
 		POOL_TERRAIN,
         POOL_MATERIALS,
-        POOL_GRASS,
         POOL_GLTF_PBR,
+        POOL_GRASS,
+        POOL_GLTF_PBR_ALPHA_MASK,
 		POOL_TREE,
 		POOL_ALPHA_MASK,
 		POOL_FULLBRIGHT_ALPHA_MASK,
@@ -109,7 +110,7 @@ class LLDrawPool
 
     virtual void render(S32 pass = 0) {};
     virtual void prerender() {};
-	virtual U32 getVertexDataMask() = 0;
+    virtual U32 getVertexDataMask() { return 0; } // DEPRECATED -- draw pool doesn't actually determine vertex data mask any more
 	virtual BOOL verify() const { return TRUE; }		// Verify that all data in the draw pool is correct!
 	virtual S32 getShaderLevel() const { return mShaderLevel; }
 	
diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp
index d30fc22393f..b75c83e73a3 100644
--- a/indra/newview/lldrawpoolpbropaque.cpp
+++ b/indra/newview/lldrawpoolpbropaque.cpp
@@ -33,9 +33,17 @@
 
 static const U32 gltf_render_types[] = { LLPipeline::RENDER_TYPE_PASS_GLTF_PBR, LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK };
 
-LLDrawPoolGLTFPBR::LLDrawPoolGLTFPBR() :
-    LLRenderPass(POOL_GLTF_PBR)
+LLDrawPoolGLTFPBR::LLDrawPoolGLTFPBR(U32 type) :
+    LLRenderPass(type)
 {
+    if (type == LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK)
+    {
+        mRenderType = LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK;
+    }
+    else
+    {
+        mRenderType = LLPipeline::RENDER_TYPE_PASS_GLTF_PBR;
+    }
 }
 
 S32 LLDrawPoolGLTFPBR::getNumDeferredPasses()
@@ -47,14 +55,11 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass)
 {
     llassert(!LLPipeline::sRenderingHUDs);
 
-    for (U32 type : gltf_render_types)
-    {
-        gDeferredPBROpaqueProgram.bind();
-        pushGLTFBatches(type);
+    gDeferredPBROpaqueProgram.bind();
+    pushGLTFBatches(mRenderType);
 
-        gDeferredPBROpaqueProgram.bind(true);
-        pushRiggedGLTFBatches(type + 1);
-    }
+    gDeferredPBROpaqueProgram.bind(true);
+    pushRiggedGLTFBatches(mRenderType + 1);
 }
 
 S32 LLDrawPoolGLTFPBR::getNumPostDeferredPasses()
@@ -67,12 +72,9 @@ void LLDrawPoolGLTFPBR::renderPostDeferred(S32 pass)
     if (LLPipeline::sRenderingHUDs)
     {
         gHUDPBROpaqueProgram.bind();
-        for (U32 type : gltf_render_types)
-        {
-            pushGLTFBatches(type);
-        }
+        pushGLTFBatches(mRenderType);
     }
-    else
+    else if (mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR) // HACK -- don't render glow except for the non-alpha masked implementation
     {
         gGL.setColorMask(false, true);
         gPBRGlowProgram.bind();
@@ -85,4 +87,3 @@ void LLDrawPoolGLTFPBR::renderPostDeferred(S32 pass)
     }
 }
 
-
diff --git a/indra/newview/lldrawpoolpbropaque.h b/indra/newview/lldrawpoolpbropaque.h
index 69e063b3222..c8a28461fa3 100644
--- a/indra/newview/lldrawpoolpbropaque.h
+++ b/indra/newview/lldrawpoolpbropaque.h
@@ -32,21 +32,9 @@
 class LLDrawPoolGLTFPBR final : public LLRenderPass
 {
 public:
-    enum
-    {
-        // See: DEFERRED_VB_MASK
-        VERTEX_DATA_MASK = 0
-                         | LLVertexBuffer::MAP_VERTEX
-                         | LLVertexBuffer::MAP_NORMAL
-                         | LLVertexBuffer::MAP_TEXCOORD0 // Diffuse
-                         | LLVertexBuffer::MAP_TEXCOORD1 // Normal
-                         | LLVertexBuffer::MAP_TEXCOORD2 // Spec <-- ORM Occlusion Roughness Metal
-                         | LLVertexBuffer::MAP_TANGENT
-                         | LLVertexBuffer::MAP_COLOR
-    };
-    U32 getVertexDataMask() override { return VERTEX_DATA_MASK; }
+    LLDrawPoolGLTFPBR(U32 type = LLDrawPool::POOL_GLTF_PBR);
 
-    LLDrawPoolGLTFPBR();
+    U32 mRenderType = 0;
 
     S32 getNumDeferredPasses() override;
     void renderDeferred(S32 pass) override;
diff --git a/indra/newview/llreflectionmap.cpp b/indra/newview/llreflectionmap.cpp
index 394596feea8..9fcc6ae902e 100644
--- a/indra/newview/llreflectionmap.cpp
+++ b/indra/newview/llreflectionmap.cpp
@@ -31,9 +31,12 @@
 #include "llviewerwindow.h"
 #include "llviewerregion.h"
 #include "llworld.h"
+#include "llshadermgr.h"
 
 extern F32SecondsImplicit gFrameTimeSeconds;
 
+extern U32 get_box_fan_indices(LLCamera* camera, const LLVector4a& center);
+
 LLReflectionMap::LLReflectionMap()
 {
 }
@@ -245,3 +248,81 @@ bool LLReflectionMap::getBox(LLMatrix4& box)
 
     return false;
 }
+
+bool LLReflectionMap::isActive()
+{
+    return mCubeIndex != -1;
+}
+
+void LLReflectionMap::doOcclusion(const LLVector4a& eye)
+{
+    LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE;
+#if 1
+    // super sloppy, but we're doing an occlusion cull against a bounding cube of
+    // a bounding sphere, pad radius so we assume if the eye is within
+    // the bounding sphere of the bounding cube, the node is not culled
+    F32 dist = mRadius * F_SQRT3 + 1.f;
+
+    LLVector4a o;
+    o.setSub(mOrigin, eye);
+
+    bool do_query = false;
+
+    if (o.getLength3().getF32() < dist)
+    { // eye is inside radius, don't attempt to occlude
+        mOccluded = false;
+        if (mViewerObject)
+        {
+            mViewerObject->setDebugText("Camera Non-Occluded");
+        }
+        return;
+    }
+    
+    if (mOcclusionQuery == 0)
+    { // no query was previously issued, allocate one and issue
+        glGenQueries(1, &mOcclusionQuery);
+        do_query = true;
+    }
+    else
+    { // query was previously issued, check it and only issue a new query
+        // if previous query is available
+        GLuint result = (GLuint) 0xFFFFFFFF;
+        glGetQueryObjectuiv(mOcclusionQuery, GL_QUERY_RESULT_NO_WAIT, &result);
+
+        if (result != (GLuint) 0xFFFFFFFF)
+        {
+            do_query = true;
+            mOccluded = result == 0;
+            mOcclusionPendingFrames = 0;
+        }
+        else
+        {
+            mOcclusionPendingFrames++;
+            if (mViewerObject)
+            {
+                mViewerObject->setDebugText(llformat("Query Pending - %d", mOcclusionPendingFrames));
+            }
+        }
+    }
+
+    if (do_query)
+    {
+        glBeginQuery(GL_ANY_SAMPLES_PASSED, mOcclusionQuery);
+
+        LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr;
+
+        shader->uniform3fv(LLShaderMgr::BOX_CENTER, 1, mOrigin.getF32ptr());
+        F32 r = mRadius + 0.25f; // pad by 1/4m for near clip plane etc
+        shader->uniform3f(LLShaderMgr::BOX_SIZE, mRadius, mRadius, mRadius);
+
+        gPipeline.mCubeVB->drawRange(LLRender::TRIANGLE_FAN, 0, 7, 8, get_box_fan_indices(LLViewerCamera::getInstance(), mOrigin));
+
+        glEndQuery(GL_ANY_SAMPLES_PASSED);
+
+        if (mViewerObject)
+        {
+            mViewerObject->setDebugText(llformat("Query Issued - %.2f, %.2f, %.2f", o.getLength3().getF32(), dist, mRadius));
+        }
+    }
+#endif
+}
diff --git a/indra/newview/llreflectionmap.h b/indra/newview/llreflectionmap.h
index 04bc8d824c0..0405d06eb50 100644
--- a/indra/newview/llreflectionmap.h
+++ b/indra/newview/llreflectionmap.h
@@ -64,6 +64,12 @@ class alignas(16) LLReflectionMap : public LLRefCount
     // return false if no bounding box (treat as sphere influence volume)
     bool getBox(LLMatrix4& box);
 
+    // return true if this probe is active for rendering
+    bool isActive();
+
+    // perform occlusion query/readback
+    void doOcclusion(const LLVector4a& eye);
+
     // point at which environment map was last generated from (in agent space)
     LLVector4a mOrigin;
     
@@ -101,5 +107,10 @@ class alignas(16) LLReflectionMap : public LLRefCount
     // 0 - automatic probe
     // 1 - manual probe
     U32 mPriority = 0;
+
+    // occlusion culling state
+    GLuint mOcclusionQuery = 0;
+    bool mOccluded = false;
+    U32 mOcclusionPendingFrames = 0;
 };
 
diff --git a/indra/newview/llreflectionmapmanager.cpp b/indra/newview/llreflectionmapmanager.cpp
index e760bc794c8..bfc8b595c2c 100644
--- a/indra/newview/llreflectionmapmanager.cpp
+++ b/indra/newview/llreflectionmapmanager.cpp
@@ -157,6 +157,7 @@ void LLReflectionMapManager::update()
     LLReflectionMap* closestDynamic = nullptr;
 
     LLReflectionMap* oldestProbe = nullptr;
+    LLReflectionMap* oldestOccluded = nullptr;
 
     if (mUpdatingProbe != nullptr)
     {
@@ -179,12 +180,27 @@ void LLReflectionMapManager::update()
         probe->mProbeIndex = i;
 
         LLVector4a d;
-        
-        if (!did_update && 
-            i < mReflectionProbeCount &&
-            (oldestProbe == nullptr || probe->mLastUpdateTime < oldestProbe->mLastUpdateTime))
+
+        if (probe->mOccluded)
         {
-            oldestProbe = probe;
+            if (oldestOccluded == nullptr)
+            {
+                oldestOccluded = probe;
+            }
+            else if (probe->mLastUpdateTime < oldestOccluded->mLastUpdateTime)
+            {
+                oldestOccluded = probe;
+            }
+        }
+        else
+        {
+            if (!did_update &&
+                i < mReflectionProbeCount &&
+                (oldestProbe == nullptr ||
+                    probe->mLastUpdateTime < oldestProbe->mLastUpdateTime))
+            {
+               oldestProbe = probe;
+            }
         }
 
         if (realtime && 
@@ -240,6 +256,13 @@ void LLReflectionMapManager::update()
         doProbeUpdate();
     }
 
+    if (oldestOccluded)
+    {
+        // as far as this occluded probe is concerned, an origin/radius update is as good as a full update
+        oldestOccluded->autoAdjustOrigin();
+        oldestOccluded->mLastUpdateTime = gFrameTimeSeconds;
+    }
+
     // update distance to camera for all probes
     std::sort(mProbes.begin(), mProbes.end(), CompareProbeDistance());
 }
@@ -277,8 +300,11 @@ void LLReflectionMapManager::getReflectionMaps(std::vector<LLReflectionMap*>& ma
         mProbes[i]->mLastBindTime = gFrameTimeSeconds; // something wants to use this probe, indicate it's been requested
         if (mProbes[i]->mCubeIndex != -1)
         {
-            mProbes[i]->mProbeIndex = count;
-            maps[count++] = mProbes[i];
+            if (!mProbes[i]->mOccluded)
+            {
+                mProbes[i]->mProbeIndex = count;
+                maps[count++] = mProbes[i];
+            }
         }
         else
         {
@@ -1038,3 +1064,17 @@ void LLReflectionMapManager::cleanup()
     // note: also called on teleport (not just shutdown), so make sure we're in a good "starting" state
     initCubeFree();
 }
+
+void LLReflectionMapManager::doOcclusion()
+{
+    LLVector4a eye;
+    eye.load3(LLViewerCamera::instance().getOrigin().mV);
+
+    for (auto& probe : mProbes)
+    {
+        if (probe != nullptr)
+        {
+            probe->doOcclusion(eye);
+        }
+    }
+}
diff --git a/indra/newview/llreflectionmapmanager.h b/indra/newview/llreflectionmapmanager.h
index 85f428d75ba..fef308541df 100644
--- a/indra/newview/llreflectionmapmanager.h
+++ b/indra/newview/llreflectionmapmanager.h
@@ -96,6 +96,9 @@ class alignas(16) LLReflectionMapManager
     // True if currently updating a radiance map, false if currently updating an irradiance map
     bool isRadiancePass() { return mRadiancePass; }
 
+    // perform occlusion culling on all active reflection probes
+    void doOcclusion();
+
 private:
     friend class LLPipeline;
 
diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp
index 69b149f82da..d0688d26a95 100644
--- a/indra/newview/pipeline.cpp
+++ b/indra/newview/pipeline.cpp
@@ -432,6 +432,7 @@ void LLPipeline::init()
 	getPool(LLDrawPool::POOL_MATERIALS);
 	getPool(LLDrawPool::POOL_GLOW);
 	getPool(LLDrawPool::POOL_GLTF_PBR);
+    getPool(LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK);
 
 	resetFrameStats();
 
@@ -1545,6 +1546,9 @@ LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0)
 	case LLDrawPool::POOL_GLTF_PBR:
 		poolp = mPBROpaquePool;
 		break;
+    case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK:
+        poolp = mPBRAlphaMaskPool;
+        break;
 
 	default:
 		llassert(0);
@@ -2432,6 +2436,26 @@ void LLPipeline::doOcclusion(LLCamera& camera)
     LL_PROFILE_GPU_ZONE("doOcclusion");
     llassert(!gCubeSnapshot);
 
+    if (sReflectionProbesEnabled && sUseOcclusion > 1 && !LLPipeline::sShadowRender && !gCubeSnapshot)
+    {
+        gGL.setColorMask(false, false);
+        LLGLDepthTest depth(GL_TRUE, GL_FALSE);
+        LLGLDisable cull(GL_CULL_FACE);
+
+        gOcclusionCubeProgram.bind();
+
+        if (mCubeVB.isNull())
+        { //cube VB will be used for issuing occlusion queries
+            mCubeVB = ll_create_cube_vb(LLVertexBuffer::MAP_VERTEX);
+        }
+        mCubeVB->setBuffer();
+
+        mReflectionMapManager.doOcclusion();
+        gOcclusionCubeProgram.unbind();
+
+        gGL.setColorMask(true, true);
+    }
+
     if (LLPipeline::sUseOcclusion > 1 && !LLSpatialPartition::sTeleportRequested &&
 		(sCull->hasOcclusionGroups() || LLVOCachePartition::sNeedsOcclusionCheck))
 	{
@@ -5192,6 +5216,19 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp )
         }
         break;
 
+    case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK:
+        if (mPBRAlphaMaskPool)
+        {
+            llassert(0);
+            LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate PBR Alpha Mask Pool" << LL_ENDL;
+        }
+        else
+        {
+            mPBRAlphaMaskPool = new_poolp;
+        }
+        break;
+
+
 	default:
 		llassert(0);
 		LL_WARNS() << "Invalid Pool Type in  LLPipeline::addPool()" << LL_ENDL;
@@ -5308,6 +5345,11 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp )
         mPBROpaquePool = NULL;
         break;
 
+    case LLDrawPool::POOL_GLTF_PBR_ALPHA_MASK:
+        llassert(poolp == mPBRAlphaMaskPool);
+        mPBRAlphaMaskPool = NULL;
+        break;
+
 	default:
 		llassert(0);
 		LL_WARNS() << "Invalid Pool Type in  LLPipeline::removeFromQuickLookup() type=" << poolp->getType() << LL_ENDL;
diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h
index 85878dd21d2..29dd42e4ec4 100644
--- a/indra/newview/pipeline.h
+++ b/indra/newview/pipeline.h
@@ -895,6 +895,8 @@ class LLPipeline
 	LLDrawPool*					mMaterialsPool = nullptr;
 	LLDrawPool*					mWLSkyPool = nullptr;
 	LLDrawPool*					mPBROpaquePool = nullptr;
+    LLDrawPool*                 mPBRAlphaMaskPool = nullptr;
+
 	// Note: no need to keep an quick-lookup to avatar pools, since there's only one per avatar
 	
 public:
-- 
GitLab