From 948780d5166e4c4e17a2fef9a160154662b2245c Mon Sep 17 00:00:00 2001 From: Viscerous Date: Mon, 15 Jun 2026 18:18:16 +0200 Subject: [PATCH] Depth-splice rigged attachment alpha into world alpha --- indra/newview/app_settings/settings.xml | 11 +++ indra/newview/lldrawpoolalpha.cpp | 107 +++++++++++++++++++----- indra/newview/lldrawpoolalpha.h | 9 +- indra/newview/llspatialpartition.h | 31 +++++++ indra/newview/llvoavatar.cpp | 21 +++-- indra/newview/pipeline.cpp | 33 +++++++- 6 files changed, 181 insertions(+), 31 deletions(-) diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b1e161182ee..e4853a7ea4f 100644 --- a/indra/newview/app_settings/settings.xml +++ b/indra/newview/app_settings/settings.xml @@ -7848,6 +7848,17 @@ Value 0.5 + RenderInterleavedAlpha + + Comment + Depth-interleave rigged attachment alpha with the distance-sorted world alpha so transparent objects composite correctly both in front of and behind avatars. Disable to restore two-pass (all rigged first) order. + Persist + 1 + Type + Boolean + Value + 1 + RenderDebugGLSession Comment diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index 8755120920b..d21e5db5265 100644 --- a/indra/newview/lldrawpoolalpha.cpp +++ b/indra/newview/lldrawpoolalpha.cpp @@ -26,6 +26,8 @@ #include "llviewerprecompiledheaders.h" +#include + #include "lldrawpoolalpha.h" #include "llglheaders.h" @@ -198,14 +200,28 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass) // already being setup for rendering LLGLSLShader::unbind(); - if (!LLPipeline::sRenderingHUDs) + static LLCachedControl interleaved_alpha(gSavedSettings, "RenderInterleavedAlpha", true); + + if (interleaved_alpha && !LLPipeline::sRenderingHUDs && + getType() == LLDrawPool::POOL_ALPHA_POST_WATER) { - // first pass, render rigged objects only and render to depth buffer - forwardRender(true); + // single pass: depth-interleave whole avatars (rigged alpha, drawn in + // attachment order with depth writes) with the distance-sorted alpha + // groups, so world alpha composites correctly both in front of and + // behind avatars. + forwardRender(false, true); } + else + { + if (!LLPipeline::sRenderingHUDs) + { + // first pass, render rigged objects only and render to depth buffer + forwardRender(true); + } - // second pass, regular forward alpha rendering - forwardRender(); + // second pass, regular forward alpha rendering + forwardRender(); + } // final pass, render to depth for depth of field effects if (!LLPipeline::sImpostorRender && LLPipeline::RenderDepthOfField && !gCubeSnapshot && !LLPipeline::sRenderingHUDs && getType() == LLDrawPool::POOL_ALPHA_POST_WATER) @@ -228,7 +244,7 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass) } } -void LLDrawPoolAlpha::forwardRender(bool rigged) +void LLDrawPoolAlpha::forwardRender(bool rigged, bool merged) { gPipeline.enableLightsDynamic(); @@ -245,7 +261,8 @@ void LLDrawPoolAlpha::forwardRender(bool rigged) || getType() == LLDrawPoolAlpha::POOL_ALPHA_PRE_WATER; // needed for accurate water fog - LLGLDepthTest depth(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE); + // in merged mode depth writes are decided per group inside renderAlpha + LLGLDepthTest depth(GL_TRUE, (write_depth && !merged) ? GL_TRUE : GL_FALSE); mColorSFactor = LLRender::BF_SOURCE_ALPHA; // } regular alpha blend mColorDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } @@ -253,8 +270,11 @@ void LLDrawPoolAlpha::forwardRender(bool rigged) mAlphaDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } gGL.blendFunc(mColorSFactor, mColorDFactor, mAlphaSFactor, mAlphaDFactor); - if (rigged && mType == LLDrawPool::POOL_ALPHA_POST_WATER) + if ((rigged || merged) && mType == LLDrawPool::POOL_ALPHA_POST_WATER) { // draw GLTF scene to depth buffer before rigged alpha + // (explicit depth-write scope: in merged mode the pass-wide depth state + // above has writes off) + LLGLDepthTest gltf_depth(GL_TRUE, GL_TRUE); LL::GLTFSceneManager::instance().render(false, false); LL::GLTFSceneManager::instance().render(false, true); LL::GLTFSceneManager::instance().render(false, false, true); @@ -263,11 +283,11 @@ void LLDrawPoolAlpha::forwardRender(bool rigged) // If the face is more than 90% transparent, then don't update the Depth buffer for Dof // We don't want the nearly invisible objects to cause of DoF effects - renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, rigged); + renderAlpha(getVertexDataMask() | LLVertexBuffer::MAP_TEXTURE_INDEX | LLVertexBuffer::MAP_TANGENT | LLVertexBuffer::MAP_TEXCOORD1 | LLVertexBuffer::MAP_TEXCOORD2, false, rigged, merged); gGL.setColorMask(true, false); - if (!rigged && (LLPipeline::sRenderingHUDs || getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER)) + if ((!rigged || merged) && (LLPipeline::sRenderingHUDs || getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER)) { //render "highlight alpha" on final non-rigged pass for non-HUDs (HUDs only run pre-water alpha pass) // NOTE -- hacky call here protected by !rigged instead of alongside "forwardRender" // so renderDebugAlpha is executed while gls_pipeline_alpha and depth GL state @@ -561,7 +581,7 @@ void LLDrawPoolAlpha::renderRiggedPbrEmissives(std::vector& emissiv } } -void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) +void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged, bool merged) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; bool initialized_lighting = false; @@ -572,18 +592,21 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) const LLGLSLShader* lastAvatarShader = nullptr; bool skipLastSkin = false; - LLCullResult::sg_iterator begin; - LLCullResult::sg_iterator end; + LLCullResult::sg_iterator iter = nullptr; + LLCullResult::sg_iterator iter_end = nullptr; + LLCullResult::sg_iterator rigged_iter = nullptr; + LLCullResult::sg_iterator rigged_end = nullptr; - if (rigged) + if (merged || rigged) { - begin = gPipeline.beginRiggedAlphaGroups(); - end = gPipeline.endRiggedAlphaGroups(); + rigged_iter = gPipeline.beginRiggedAlphaGroups(); + rigged_end = gPipeline.endRiggedAlphaGroups(); } - else + + if (merged || !rigged) { - begin = gPipeline.beginAlphaGroups(); - end = gPipeline.endAlphaGroups(); + iter = gPipeline.beginAlphaGroups(); + iter_end = gPipeline.endAlphaGroups(); } LLEnvironment& env = LLEnvironment::instance(); @@ -596,10 +619,37 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) } - for (LLCullResult::sg_iterator i = begin; i != end; ++i) + // depth-write conditions that apply to every group in this pass; in merged + // mode, stamped rigged groups additionally write depth (see below) + const bool write_depth_always = LLDrawPoolWater::sSkipScreenCopy || + LLPipeline::sImpostorRenderAlphaDepthPass || + getType() == LLDrawPoolAlpha::POOL_ALPHA_PRE_WATER; + + while (iter != iter_end || rigged_iter != rigged_end) { LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("renderAlpha - group"); - LLSpatialGroup* group = *i; + + if (merged) + { // single back-to-front walk over both streams: take the farther of + // the two heads. All of an avatar's rigged groups share one depth + // (see LLSpatialBridge::mAvatarp), so each avatar's attachment-order + // run drains contiguously; on ties rigged draws first so world + // alpha at the same depth composites over it. + if (rigged_iter == rigged_end) + { + rigged = false; + } + else if (iter == iter_end) + { + rigged = true; + } + else + { + rigged = (*rigged_iter)->mDepth >= (*iter)->mDepth; + } + } + + LLSpatialGroup* group = rigged ? *rigged_iter++ : *iter++; llassert(group); llassert(group->getSpatialPartition()); @@ -628,6 +678,21 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) } } + // merged mode: rigged alpha writes depth so attachment-order + // layering holds and impostors/DoF still see it -- but only when + // the group carries an avatar stamp (mAvatarp, see + // LLSpatialBridge). An unstamped group (e.g. animesh) has no + // defined position in the rigged order and must not be allowed to + // depth-reject geometry behind it (it still blends; it just can't + // erase). Non-merged passes keep the pass-wide depth state set by + // the caller. + std::optional depth_state; + if (merged) + { + bool write_depth = write_depth_always || (rigged && group->mAvatarp != nullptr); + depth_state.emplace(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE); + } + static std::vector emissives; static std::vector rigged_emissives; static std::vector pbr_emissives; diff --git a/indra/newview/lldrawpoolalpha.h b/indra/newview/lldrawpoolalpha.h index 25044beda04..c92c752f356 100644 --- a/indra/newview/lldrawpoolalpha.h +++ b/indra/newview/lldrawpoolalpha.h @@ -58,13 +58,18 @@ class LLDrawPoolAlpha final: public LLRenderPass /*virtual*/ void renderPostDeferred(S32 pass); /*virtual*/ S32 getNumPasses() { return 1; } - void forwardRender(bool write_depth = false); + // rigged: render the rigged alpha groups (depth-writing) instead of the + // distance-sorted alpha groups + // merged: render both streams in a single back-to-front walk, splicing each + // avatar's rigged run in at the avatar's depth; depth writes are + // then decided per group inside renderAlpha + void forwardRender(bool rigged = false, bool merged = false); /*virtual*/ void prerender(); void renderDebugAlpha(); void renderGroupAlpha(LLSpatialGroup* group, U32 type, U32 mask, bool texture = true); - void renderAlpha(U32 mask, bool depth_only = false, bool rigged = false); + void renderAlpha(U32 mask, bool depth_only = false, bool rigged = false, bool merged = false); void renderAlphaHighlight(); static bool sShowDebugAlpha; diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index 4b312b15978..88d68442c41 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -249,6 +249,28 @@ class LLSpatialGroup : public LLOcclusionCullingGroup } }; + struct CompareDepthRenderOrder + { + bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) + { + // farther avatars draw first so rigged alpha can be depth-interleaved + // with the distance-sorted alpha groups; all of an avatar's groups + // share one depth (see LLSpatialBridge::mAvatarp), keeping each + // avatar's run contiguous and in attachment render order + if (lhs->mDepth != rhs->mDepth) + { + return lhs->mDepth > rhs->mDepth; + } + + if (lhs->mAvatarp != rhs->mAvatarp) + { + return lhs->mAvatarp < rhs->mAvatarp; + } + + return lhs->mRenderOrder > rhs->mRenderOrder; + } + }; + typedef enum { GEOM_DIRTY = LLViewerOctreeGroup::INVALID_STATE, @@ -458,6 +480,15 @@ class LLSpatialBridge : public LLDrawable, public LLSpatialPartition //transform agent space bounding box into this Spatial Bridge's coordinate frame void transformExtents(const LLVector4a* src, LLVector4a* dst); LLDrawable* mDrawable; + + // rigged alpha draw-order stamp, set per detailed update by + // LLVOAvatar::idleUpdateMisc -- one stamp per attachment, however many + // child prims/groups the linkset spans. LLPipeline::postSort fans it out + // to each of this bridge's visible rigged alpha groups. Raw pointer is + // only ever compared, never dereferenced (avatar may die first). + LLVOAvatar* mAvatarp = nullptr; + U32 mRenderOrder = 0; + F32 mDepth = 0.f; }; class LLCullResult diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index 2f39a76156c..aa1ad46f76b 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -3001,6 +3001,13 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update) if (detailed_update) { U32 draw_order = 0; + // camera-axis depth of this avatar; stamped onto every rigged + // attachment bridge below so the rigged alpha groups can be + // depth-interleaved with the distance-sorted alpha groups while each + // avatar's attachment-order run stays contiguous + // (LLSpatialGroup::CompareDepthRenderOrder, LLDrawPoolAlpha::renderAlpha) + LLViewerCamera* camera = LLViewerCamera::getInstance(); + F32 rigged_depth = (getRenderPosition() - camera->getOrigin()) * camera->getAtAxis(); bool attachment_selected = LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 0 && LLSelectMgr::getInstance()->getSelection()->isAttachment(); for (const auto& [attachment_point_id, attachment] : mAttachmentPoints) { @@ -3062,12 +3069,14 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update) bridge->updateMove(); bridge->setState(LLDrawable::EARLY_MOVE); - LLSpatialGroup* group = attached_object->mDrawable->getSpatialGroup(); - if (group) - { //set draw order of group - group->mAvatarp = this; - group->mRenderOrder = draw_order++; - } + //set draw order of the attachment; LLPipeline::postSort + //fans the stamp out to each of the bridge's visible + //rigged alpha groups, so the whole linkset sorts with + //this avatar in attachment order no matter how its + //prims bin into the bridge octree + bridge->mAvatarp = this; + bridge->mRenderOrder = draw_order++; + bridge->mDepth = rigged_depth; } } diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 4ef88f5deb3..49062cff3df 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -3591,6 +3591,8 @@ void LLPipeline::postSort(LLCamera &camera) LL_PUSH_CALLSTACKS(); + static LLCachedControl interleaved_alpha(gSavedSettings, "RenderInterleavedAlpha", true); + // build render map for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i) { @@ -3663,6 +3665,23 @@ void LLPipeline::postSort(LLCamera &camera) if (rigged_alpha != group->mDrawMap.end()) { // store rigged alpha groups for LLDrawPoolAlpha prepass (skip distance update, rigged attachments use depth buffer) + // fan the attachment's draw-order stamp (LLVOAvatar::idleUpdateMisc) + // out from the bridge to every visible group of the linkset, + // however its prims bin into the bridge octree. The depth copy + // gives all of an avatar's groups one shared depth -- what lets + // the interleaved walk treat the avatar as a single contiguous + // run -- and is gated so the legacy path keeps bounds-front depths. + LLSpatialBridge* stamp_bridge = group->getSpatialPartition()->asBridge(); + if (stamp_bridge && stamp_bridge->mAvatarp) + { + group->mAvatarp = stamp_bridge->mAvatarp; + group->mRenderOrder = stamp_bridge->mRenderOrder; + if (interleaved_alpha) + { + group->mDepth = stamp_bridge->mDepth; + } + } + if (hasRenderType(LLDrawPool::POOL_ALPHA)) { sCull->pushRiggedAlphaGroup(group); @@ -3707,8 +3726,18 @@ void LLPipeline::postSort(LLCamera &camera) // order alpha groups by distance std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater()); - // order rigged alpha groups by avatar attachment order - std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareRenderOrder()); + if (interleaved_alpha) + { + // order rigged alpha groups by avatar depth, then attachment order, + // so LLDrawPoolAlpha can depth-interleave whole avatars with the + // distance-sorted alpha groups above + std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareDepthRenderOrder()); + } + else + { + // order rigged alpha groups by avatar attachment order + std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareRenderOrder()); + } } LL_PUSH_CALLSTACKS();