Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions indra/newview/app_settings/settings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -7848,6 +7848,17 @@
<key>Value</key>
<real>0.5</real>
</map>
<key>RenderInterleavedAlpha</key>
<map>
<key>Comment</key>
<string>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.</string>
<key>Persist</key>
<integer>1</integer>
<key>Type</key>
<string>Boolean</string>
<key>Value</key>
<integer>1</integer>
</map>
<key>RenderDebugGLSession</key>
<map>
<key>Comment</key>
Expand Down
107 changes: 86 additions & 21 deletions indra/newview/lldrawpoolalpha.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,8 @@

#include "llviewerprecompiledheaders.h"

#include <optional>

#include "lldrawpoolalpha.h"

#include "llglheaders.h"
Expand Down Expand Up @@ -198,14 +200,28 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
// already being setup for rendering
LLGLSLShader::unbind();

if (!LLPipeline::sRenderingHUDs)
static LLCachedControl<bool> 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)
Expand All @@ -228,7 +244,7 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass)
}
}

void LLDrawPoolAlpha::forwardRender(bool rigged)
void LLDrawPoolAlpha::forwardRender(bool rigged, bool merged)
{
gPipeline.enableLightsDynamic();

Expand All @@ -245,16 +261,20 @@ 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; // }
mAlphaSFactor = LLRender::BF_ZERO; // } glow suppression
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);
Expand All @@ -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
Expand Down Expand Up @@ -561,7 +581,7 @@ void LLDrawPoolAlpha::renderRiggedPbrEmissives(std::vector<LLDrawInfo*>& 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;
Expand All @@ -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();
Expand All @@ -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());

Expand Down Expand Up @@ -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<LLGLDepthTest> 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<LLDrawInfo*> emissives;
static std::vector<LLDrawInfo*> rigged_emissives;
static std::vector<LLDrawInfo*> pbr_emissives;
Expand Down
9 changes: 7 additions & 2 deletions indra/newview/lldrawpoolalpha.h
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
31 changes: 31 additions & 0 deletions indra/newview/llspatialpartition.h
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
21 changes: 15 additions & 6 deletions indra/newview/llvoavatar.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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)
{
Expand Down Expand Up @@ -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;
}
}

Expand Down
33 changes: 31 additions & 2 deletions indra/newview/pipeline.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3591,6 +3591,8 @@ void LLPipeline::postSort(LLCamera &camera)

LL_PUSH_CALLSTACKS();

static LLCachedControl<bool> interleaved_alpha(gSavedSettings, "RenderInterleavedAlpha", true);

// build render map
for (LLCullResult::sg_iterator i = sCull->beginVisibleGroups(); i != sCull->endVisibleGroups(); ++i)
{
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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();
Expand Down
Loading