SL-18190 Fix alpha not playing nice with water surface by split LLDrawPoolAlpha into two passes, one above water, one below water, and clip against water plane. Currently brute forces two complete alpha passes, still need to cull against water plane and add support for fullbright shaders.

master
Dave Parks 2022-10-10 18:53:43 -05:00
parent 5890f423b4
commit 07bca31e06
17 changed files with 168 additions and 102 deletions

View File

@ -505,3 +505,28 @@ vec3 pbrPunctual(vec3 diffuseColor, vec3 specularColor,
return color;
}
uniform vec4 waterPlane;
uniform float waterSign;
// discard if given position in eye space is on the wrong side of the waterPlane according to waterSign
void waterClip(vec3 pos)
{
// TODO: make this less branchy
if (waterSign > 0)
{
if ((dot(pos.xyz, waterPlane.xyz) + waterPlane.w) < -0.1)
{
discard;
}
}
else
{
if ((dot(pos.xyz, waterPlane.xyz) + waterPlane.w) > -0.1)
{
discard;
}
}
}

View File

@ -69,6 +69,8 @@ uniform vec3 light_direction[8];
uniform vec4 light_attenuation[8];
uniform vec3 light_diffuse[8];
void waterClip(vec3 pos);
#ifdef WATER_FOG
vec4 applyWaterFogView(vec3 pos, vec4 color);
#endif
@ -181,6 +183,7 @@ void main()
frag *= screen_res;
vec4 pos = vec4(vary_position, 1.0);
waterClip(pos.xyz);
vec3 norm = vary_norm;
float shadow = 1.0f;
@ -295,6 +298,7 @@ void main()
#endif // #else // FOR_IMPOSTOR
//color.rgb = waterPlane.xyz * 0.5 + 0.5;
frag_color = color;
}

View File

@ -84,6 +84,8 @@ float sampleDirectionalShadow(vec3 pos, vec3 norm, vec2 pos_screen);
void sampleReflectionProbes(inout vec3 ambenv, inout vec3 glossenv,
vec3 pos, vec3 norm, float glossiness);
void waterClip(vec3 pos);
// PBR interface
vec3 pbrIbl(vec3 diffuseColor,
vec3 specularColor,
@ -139,6 +141,7 @@ void main()
vec3 light_dir = (sun_up_factor == 1) ? sun_dir : moon_dir;
vec3 pos = vary_position;
waterClip(pos);
// IF .mFeatures.mIndexedTextureChannels = LLGLSLShader::sIndexedTextureChannels;
// vec3 col = vertex_color.rgb * diffuseLookup(vary_texcoord0.xy).rgb;

View File

@ -90,6 +90,7 @@ uniform vec4 light_attenuation[8];
uniform vec3 light_diffuse[8];
float getAmbientClamp();
void waterClip(vec3 pos);
vec3 calcPointLightOrSpotLight(vec3 light_col, vec3 npos, vec3 diffuse, vec4 spec, vec3 v, vec3 n, vec4 lp, vec3 ln, float la, float fa, float is_pointlight, float ambiance)
{
@ -219,6 +220,10 @@ vec2 encode_normal(vec3 n);
void main()
{
#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND)
waterClip(vary_position.xyz);
#endif
vec2 pos_screen = vary_texcoord0.xy;
vec4 diffcol = texture2D(diffuseMap, vary_texcoord0.xy);
@ -277,7 +282,6 @@ void main()
#endif
#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_BLEND)
//forward rendering, output lit linear color
diffcol.rgb = srgb_to_linear(diffcol.rgb);
final_specular.rgb = srgb_to_linear(final_specular.rgb);

View File

@ -882,7 +882,7 @@ void LLDrawable::updateDistance(LLCamera& camera, bool force_update)
{
LLFace* facep = getFace(i);
if (facep &&
(force_update || facep->getPoolType() == LLDrawPool::POOL_ALPHA))
(force_update || facep->isInAlphaPool()))
{
LLVector4a box;
box.setSub(facep->mExtents[1], facep->mExtents[0]);

View File

@ -86,9 +86,12 @@ LLDrawPool *LLDrawPool::createPool(const U32 type, LLViewerTexture *tex0)
case POOL_GLOW:
poolp = new LLDrawPoolGlow();
break;
case POOL_ALPHA:
poolp = new LLDrawPoolAlpha();
case POOL_ALPHA_PRE_WATER:
poolp = new LLDrawPoolAlpha(LLDrawPool::POOL_ALPHA_PRE_WATER);
break;
case POOL_ALPHA_POST_WATER:
poolp = new LLDrawPoolAlpha(LLDrawPool::POOL_ALPHA_POST_WATER);
break;
case POOL_AVATAR:
case POOL_CONTROL_AV:
poolp = new LLDrawPoolAvatar(type);

View File

@ -49,29 +49,32 @@ public:
{
// Correspond to LLPipeline render type
// Also controls render order, so passes that don't use alpha masking/blending should come before
// other passes and occlusion culling should happen just before rendering alpha masked passes
// in order to take advantage of hierarchical Z
// NOTE: Keep in sync with gPoolNames
// other passes to preserve hierarchical Z for occlusion queries. Occlusion queries happen just
// before grass, so grass should be the first alpha masked pool. Other ordering should be done
// based on fill rate and likelihood to occlude future passes (faster, large occluders first).
//
POOL_SIMPLE = 1,
POOL_GROUND,
POOL_FULLBRIGHT,
POOL_BUMP,
POOL_MATERIALS,
POOL_TERRAIN,
POOL_SKY,
POOL_WL_SKY,
POOL_TERRAIN,
POOL_MATERIALS,
POOL_GRASS,
POOL_TREE,
POOL_ALPHA_MASK,
POOL_FULLBRIGHT_ALPHA_MASK,
POOL_GRASS,
POOL_SKY,
POOL_WL_SKY,
POOL_INVISIBLE, // see below *
POOL_AVATAR,
POOL_CONTROL_AV, // Animesh
POOL_VOIDWATER,
POOL_WATER,
POOL_GLOW,
POOL_ALPHA,
POOL_ALPHA_PRE_WATER,
POOL_VOIDWATER,
POOL_WATER,
POOL_ALPHA_POST_WATER,
POOL_PBR_OPAQUE,
POOL_ALPHA, // note there is no actual "POOL_ALPHA" but pre-water and post-water pools consume POOL_ALPHA faces
NUM_POOL_TYPES,
// * invisiprims work by rendering to the depth buffer but not the color buffer, occluding anything rendered after them
// - and the LLDrawPool types enum controls what order things are rendered in

View File

@ -56,6 +56,8 @@ BOOL LLDrawPoolAlpha::sShowDebugAlpha = FALSE;
#define current_shader (LLGLSLShader::sCurBoundShaderPtr)
LLVector4 LLDrawPoolAlpha::sWaterPlane;
static BOOL deferred_render = FALSE;
// minimum alpha before discarding a fragment
@ -96,11 +98,13 @@ S32 LLDrawPoolAlpha::getNumPostDeferredPasses()
}
// set some common parameters on the given shader to prepare for alpha rendering
static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool deferredEnvironment)
static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool deferredEnvironment, F32 water_sign)
{
static LLCachedControl<F32> displayGamma(gSavedSettings, "RenderDeferredDisplayGamma");
F32 gamma = displayGamma;
static LLStaticHashedString waterSign("waterSign");
// Does this deferred shader need environment uniforms set such as sun_dir, etc. ?
// NOTE: We don't actually need a gbuffer since we are doing forward rendering (for transparency) post deferred rendering
// TODO: bindDeferredShader() probably should have the updating of the environment uniforms factored out into updateShaderEnvironmentUniforms()
@ -115,6 +119,8 @@ static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool d
}
shader->uniform1i(LLShaderMgr::NO_ATMO, (LLPipeline::sRenderingHUDs) ? 1 : 0);
shader->uniform1f(LLShaderMgr::DISPLAY_GAMMA, (gamma > 0.1f) ? 1.0f / gamma : (1.0f / 2.2f));
shader->uniform1f(waterSign, water_sign);
shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, 1, LLDrawPoolAlpha::sWaterPlane.mV);
if (LLPipeline::sImpostorRender)
{
@ -132,7 +138,7 @@ static void prepare_alpha_shader(LLGLSLShader* shader, bool textureGamma, bool d
//also prepare rigged variant
if (shader->mRiggedVariant && shader->mRiggedVariant != shader)
{
prepare_alpha_shader(shader->mRiggedVariant, textureGamma, deferredEnvironment);
prepare_alpha_shader(shader->mRiggedVariant, textureGamma, deferredEnvironment, water_sign);
}
}
@ -143,20 +149,40 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL;
deferred_render = TRUE;
F32 water_sign = 1.f;
if (getType() == LLDrawPool::POOL_ALPHA_PRE_WATER)
{
water_sign = -1.f;
}
if (LLPipeline::sUnderWaterRender)
{
water_sign *= -1.f;
}
// prepare shaders
emissive_shader = (LLPipeline::sRenderDeferred) ? &gDeferredEmissiveProgram :
(LLPipeline::sUnderWaterRender) ? &gObjectEmissiveWaterProgram : &gObjectEmissiveProgram;
prepare_alpha_shader(emissive_shader, true, false);
prepare_alpha_shader(emissive_shader, true, false, water_sign);
fullbright_shader = (LLPipeline::sImpostorRender) ? &gDeferredFullbrightAlphaMaskProgram :
(LLPipeline::sUnderWaterRender) ? &gDeferredFullbrightWaterProgram : &gDeferredFullbrightAlphaMaskProgram;
prepare_alpha_shader(fullbright_shader, true, true);
prepare_alpha_shader(fullbright_shader, true, true, water_sign);
simple_shader = (LLPipeline::sImpostorRender) ? &gDeferredAlphaImpostorProgram :
(LLPipeline::sUnderWaterRender) ? &gDeferredAlphaWaterProgram : &gDeferredAlphaProgram;
prepare_alpha_shader(simple_shader, false, true); //prime simple shader (loads shadow relevant uniforms)
prepare_alpha_shader(simple_shader, false, true, water_sign); //prime simple shader (loads shadow relevant uniforms)
LLGLSLShader* materialShader = LLPipeline::sUnderWaterRender ? gDeferredMaterialWaterProgram : gDeferredMaterialProgram;
for (int i = 0; i < LLMaterial::SHADER_COUNT*2; ++i)
{
prepare_alpha_shader(&materialShader[i], false, false, water_sign);
}
prepare_alpha_shader(&gDeferredPBRAlphaProgram, false, false, water_sign);
// first pass, render rigged objects only and render to depth buffer
forwardRender(true);

View File

@ -35,9 +35,13 @@ class LLFace;
class LLColor4;
class LLGLSLShader;
class LLDrawPoolAlpha: public LLRenderPass
class LLDrawPoolAlpha final: public LLRenderPass
{
public:
// set by llsettingsvo so lldrawpoolalpha has quick access to the water plane in eye space
static LLVector4 sWaterPlane;
enum
{
VERTEX_DATA_MASK = LLVertexBuffer::MAP_VERTEX |
@ -47,7 +51,7 @@ public:
};
virtual U32 getVertexDataMask() { return VERTEX_DATA_MASK; }
LLDrawPoolAlpha(U32 type = LLDrawPool::POOL_ALPHA);
LLDrawPoolAlpha(U32 type);
/*virtual*/ ~LLDrawPoolAlpha();
/*virtual*/ S32 getNumPostDeferredPasses();
@ -91,11 +95,4 @@ private:
bool mRigged = false;
};
class LLDrawPoolAlphaPostWater : public LLDrawPoolAlpha
{
public:
LLDrawPoolAlphaPostWater();
virtual void render(S32 pass = 0);
};
#endif // LL_LLDRAWPOOLALPHA_H

View File

@ -1342,7 +1342,7 @@ BOOL LLFace::getGeometryVolume(const LLVolume& volume,
if (rebuild_color)
{ //decide if shiny goes in alpha channel of color
if (tep &&
getPoolType() != LLDrawPool::POOL_ALPHA) // <--- alpha channel MUST contain transparency, not shiny
!isInAlphaPool()) // <--- alpha channel MUST contain transparency, not shiny
{
LLMaterial* mat = tep->getMaterialParams().get();
@ -2601,3 +2601,10 @@ U64 LLFace::getSkinHash()
{
return mSkinInfo ? mSkinInfo->mHash : 0;
}
bool LLFace::isInAlphaPool() const
{
return getPoolType() == LLDrawPool::POOL_ALPHA ||
getPoolType() == LLDrawPool::POOL_ALPHA_PRE_WATER ||
getPoolType() == LLDrawPool::POOL_ALPHA_POST_WATER;
}

View File

@ -237,6 +237,8 @@ public:
void setDrawOrderIndex(U32 index) { mDrawOrderIndex = index; }
U32 getDrawOrderIndex() const { return mDrawOrderIndex; }
// return true if this face is in an alpha draw pool
bool isInAlphaPool() const;
public: //aligned members
LLVector4a mExtents[2];

View File

@ -949,6 +949,8 @@ void LLSettingsVOWater::applySpecial(void *ptarget, bool force)
LLVector4 waterPlane(enorm.v[0], enorm.v[1], enorm.v[2], -ep.dot(enorm));
LLDrawPoolAlpha::sWaterPlane = waterPlane;
shader->uniform4fv(LLShaderMgr::WATER_WATERPLANE, waterPlane.mV);
LLVector4 light_direction = env.getClampedLightNorm();

View File

@ -1221,6 +1221,8 @@ void render_hud_attachments()
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_SIMPLE);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_VOLUME);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_POST_WATER);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_ALPHA_MASK);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT_ALPHA_MASK);
gPipeline.toggleRenderType(LLPipeline::RENDER_TYPE_FULLBRIGHT);

View File

@ -1924,6 +1924,7 @@ BOOL LLViewerShaderMgr::loadShadersDeferred()
shader->mFeatures.hasTransport = true;
shader->mFeatures.hasShadows = use_sun_shadow;
shader->mFeatures.hasReflectionProbes = true;
shader->mFeatures.hasWaterFog = true;
if (mShaderLevel[SHADER_DEFERRED] < 1)
{

View File

@ -4212,7 +4212,7 @@ U32 LLVOVolume::getRenderCost(texture_cost_t &textures) const
}
}
if (face->getPoolType() == LLDrawPool::POOL_ALPHA)
if (face->isInAlphaPool())
{
alpha = 1;
}
@ -4576,7 +4576,7 @@ F32 LLVOVolume::getBinRadius()
{
LLFace* face = mDrawable->getFace(i);
if (!face) continue;
if (face->getPoolType() == LLDrawPool::POOL_ALPHA &&
if (face->isInAlphaPool() &&
!face->canRenderAsMask())
{
alpha_wrap = TRUE;

View File

@ -281,33 +281,6 @@ static LLStaticHashedString sKern("kern");
static LLStaticHashedString sKernScale("kern_scale");
//----------------------------------------
#if 0
std::string gPoolNames[LLDrawPool::NUM_POOL_TYPES] =
{
// Correspond to LLDrawpool enum render type
"NONE"
, "POOL_SIMPLE"
, "POOL_GROUND"
, "POOL_FULLBRIGHT"
, "POOL_BUMP"
, "POOL_MATERIALS"
, "POOL_TERRAIN"
, "POOL_SKY"
, "POOL_WL_SKY"
, "POOL_TREE"
, "POOL_ALPHA_MASK"
, "POOL_FULLBRIGHT_ALPHA_MASK"
, "POOL_GRASS"
, "POOL_INVISIBLE"
, "POOL_AVATAR"
, "POOL_CONTROL_AV" // Animesh
, "POOL_VOIDWATER"
, "POOL_WATER"
, "POOL_GLOW"
, "POOL_ALPHA"
, "POOL_PBR_OPAQUE"
};
#endif
void drawBox(const LLVector4a& c, const LLVector4a& r);
void drawBoxOutline(const LLVector3& pos, const LLVector3& size);
@ -397,21 +370,6 @@ LLPipeline::LLPipeline() :
mGroupQ2Locked(false),
mResetVertexBuffers(false),
mLastRebuildPool(NULL),
mAlphaPool(NULL),
mSkyPool(NULL),
mTerrainPool(NULL),
mWaterPool(NULL),
mGroundPool(NULL),
mSimplePool(NULL),
mGrassPool(NULL),
mAlphaMaskPool(NULL),
mFullbrightAlphaMaskPool(NULL),
mFullbrightPool(NULL),
mInvisiblePool(NULL),
mGlowPool(NULL),
mBumpPool(NULL),
mMaterialsPool(NULL),
mWLSkyPool(NULL),
mLightMask(0),
mLightMovingMask(0),
mLightingDetail(0)
@ -463,7 +421,8 @@ void LLPipeline::init()
LL_WARNS() << "No GL errors yet. Pipeline initialization will continue." << LL_ENDL; // TODO: Remove after testing
//create render pass pools
getPool(LLDrawPool::POOL_ALPHA);
getPool(LLDrawPool::POOL_ALPHA_PRE_WATER);
getPool(LLDrawPool::POOL_ALPHA_POST_WATER);
getPool(LLDrawPool::POOL_SIMPLE);
getPool(LLDrawPool::POOL_ALPHA_MASK);
getPool(LLDrawPool::POOL_FULLBRIGHT_ALPHA_MASK);
@ -671,8 +630,10 @@ void LLPipeline::cleanup()
LL_WARNS() << "Tree Pools not cleaned up" << LL_ENDL;
}
delete mAlphaPool;
mAlphaPool = NULL;
delete mAlphaPoolPreWater;
mAlphaPoolPreWater = nullptr;
delete mAlphaPoolPostWater;
mAlphaPoolPostWater = nullptr;
delete mSkyPool;
mSkyPool = NULL;
delete mTerrainPool;
@ -1589,9 +1550,12 @@ LLDrawPool *LLPipeline::findPool(const U32 type, LLViewerTexture *tex0)
case LLDrawPool::POOL_MATERIALS:
poolp = mMaterialsPool;
break;
case LLDrawPool::POOL_ALPHA:
poolp = mAlphaPool;
case LLDrawPool::POOL_ALPHA_PRE_WATER:
poolp = mAlphaPoolPreWater;
break;
case LLDrawPool::POOL_ALPHA_POST_WATER:
poolp = mAlphaPoolPostWater;
break;
case LLDrawPool::POOL_AVATAR:
case LLDrawPool::POOL_CONTROL_AV:
@ -5733,17 +5697,28 @@ void LLPipeline::addToQuickLookup( LLDrawPool* new_poolp )
mMaterialsPool = new_poolp;
}
break;
case LLDrawPool::POOL_ALPHA:
if( mAlphaPool )
case LLDrawPool::POOL_ALPHA_PRE_WATER:
if( mAlphaPoolPreWater )
{
llassert(0);
LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha pool" << LL_ENDL;
LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha pre-water pool" << LL_ENDL;
}
else
{
mAlphaPool = (LLDrawPoolAlpha*) new_poolp;
mAlphaPoolPreWater = (LLDrawPoolAlpha*) new_poolp;
}
break;
case LLDrawPool::POOL_ALPHA_POST_WATER:
if (mAlphaPoolPostWater)
{
llassert(0);
LL_WARNS() << "LLPipeline::addPool(): Ignoring duplicate Alpha post-water pool" << LL_ENDL;
}
else
{
mAlphaPoolPostWater = (LLDrawPoolAlpha*)new_poolp;
}
break;
case LLDrawPool::POOL_AVATAR:
case LLDrawPool::POOL_CONTROL_AV:
@ -5901,10 +5876,15 @@ void LLPipeline::removeFromQuickLookup( LLDrawPool* poolp )
mMaterialsPool = NULL;
break;
case LLDrawPool::POOL_ALPHA:
llassert( poolp == mAlphaPool );
mAlphaPool = NULL;
case LLDrawPool::POOL_ALPHA_PRE_WATER:
llassert( poolp == mAlphaPoolPreWater );
mAlphaPoolPreWater = nullptr;
break;
case LLDrawPool::POOL_ALPHA_POST_WATER:
llassert(poolp == mAlphaPoolPostWater);
mAlphaPoolPostWater = nullptr;
break;
case LLDrawPool::POOL_AVATAR:
case LLDrawPool::POOL_CONTROL_AV:
@ -9078,6 +9058,8 @@ void LLPipeline::renderDeferredLighting()
pushRenderTypeMask();
andRenderTypeMask(LLPipeline::RENDER_TYPE_ALPHA,
LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER,
LLPipeline::RENDER_TYPE_ALPHA_POST_WATER,
LLPipeline::RENDER_TYPE_FULLBRIGHT,
LLPipeline::RENDER_TYPE_VOLUME,
LLPipeline::RENDER_TYPE_GLOW,
@ -10296,6 +10278,8 @@ void LLPipeline::generateSunShadow(LLCamera& camera)
pushRenderTypeMask();
andRenderTypeMask(LLPipeline::RENDER_TYPE_SIMPLE,
LLPipeline::RENDER_TYPE_ALPHA,
LLPipeline::RENDER_TYPE_ALPHA_PRE_WATER,
LLPipeline::RENDER_TYPE_ALPHA_POST_WATER,
LLPipeline::RENDER_TYPE_GRASS,
LLPipeline::RENDER_TYPE_FULLBRIGHT,
LLPipeline::RENDER_TYPE_BUMP,

View File

@ -477,6 +477,8 @@ public:
RENDER_TYPE_VOIDWATER = LLDrawPool::POOL_VOIDWATER,
RENDER_TYPE_WATER = LLDrawPool::POOL_WATER,
RENDER_TYPE_ALPHA = LLDrawPool::POOL_ALPHA,
RENDER_TYPE_ALPHA_PRE_WATER = LLDrawPool::POOL_ALPHA_PRE_WATER,
RENDER_TYPE_ALPHA_POST_WATER = LLDrawPool::POOL_ALPHA_POST_WATER,
RENDER_TYPE_GLOW = LLDrawPool::POOL_GLOW,
RENDER_TYPE_PASS_SIMPLE = LLRenderPass::PASS_SIMPLE,
RENDER_TYPE_PASS_SIMPLE_RIGGED = LLRenderPass::PASS_SIMPLE_RIGGED,
@ -890,22 +892,23 @@ protected:
// For quick-lookups into mPools (mapped by texture pointer)
std::map<uintptr_t, LLDrawPool*> mTerrainPools;
std::map<uintptr_t, LLDrawPool*> mTreePools;
LLDrawPoolAlpha* mAlphaPool;
LLDrawPool* mSkyPool;
LLDrawPool* mTerrainPool;
LLDrawPool* mWaterPool;
LLDrawPool* mGroundPool;
LLRenderPass* mSimplePool;
LLRenderPass* mGrassPool;
LLRenderPass* mAlphaMaskPool;
LLRenderPass* mFullbrightAlphaMaskPool;
LLRenderPass* mFullbrightPool;
LLDrawPool* mInvisiblePool;
LLDrawPool* mGlowPool;
LLDrawPool* mBumpPool;
LLDrawPool* mMaterialsPool;
LLDrawPool* mWLSkyPool;
LLDrawPool* mPBROpaquePool;
LLDrawPoolAlpha* mAlphaPoolPreWater = nullptr;
LLDrawPoolAlpha* mAlphaPoolPostWater = nullptr;
LLDrawPool* mSkyPool = nullptr;
LLDrawPool* mTerrainPool = nullptr;
LLDrawPool* mWaterPool = nullptr;
LLDrawPool* mGroundPool = nullptr;
LLRenderPass* mSimplePool = nullptr;
LLRenderPass* mGrassPool = nullptr;
LLRenderPass* mAlphaMaskPool = nullptr;
LLRenderPass* mFullbrightAlphaMaskPool = nullptr;
LLRenderPass* mFullbrightPool = nullptr;
LLDrawPool* mInvisiblePool = nullptr;
LLDrawPool* mGlowPool = nullptr;
LLDrawPool* mBumpPool = nullptr;
LLDrawPool* mMaterialsPool = nullptr;
LLDrawPool* mWLSkyPool = nullptr;
LLDrawPool* mPBROpaquePool = nullptr;
// Note: no need to keep an quick-lookup to avatar pools, since there's only one per avatar
public: