diff --git a/indra/newview/app_settings/settings.xml b/indra/newview/app_settings/settings.xml index b1e161182e..e4853a7ea4 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/llcontrolavatar.cpp b/indra/newview/llcontrolavatar.cpp index 660fb1b41a..e9e6b73352 100644 --- a/indra/newview/llcontrolavatar.cpp +++ b/indra/newview/llcontrolavatar.cpp @@ -366,6 +366,21 @@ void LLControlAvatar::idleUpdate(LLAgent &agent, const F64 &time) else { LLVOAvatar::idleUpdate(agent,time); + + // stamp the animesh bridge like LLVOAvatar::idleUpdateMisc stamps + // attachments. Worn animesh is skipped: the wearer's loop stamps this + // same bridge with its own depth and order. Bridge fetched from the + // drawable -- mControlAVBridge can go stale (see markForDeath). + if (mRootVolp && mRootVolp->mDrawable && !getAttachedAvatar()) + { + LLSpatialBridge* bridge = mRootVolp->mDrawable->getSpatialBridge(); + if (bridge) + { + bridge->mAvatarp = this; + bridge->mRenderOrder = 0; + bridge->mAvatarDepth = calcRiggedAlphaDepth(); + } + } } } diff --git a/indra/newview/lldrawpoolalpha.cpp b/indra/newview/lldrawpoolalpha.cpp index 8755120920..afda976ed9 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" @@ -162,6 +164,15 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass) // prepare shaders llassert(LLPipeline::sRenderDeferred); + const bool interleave = LLPipeline::canUseInterleavedAlpha() && + getType() == LLDrawPool::POOL_ALPHA_POST_WATER; + if (interleave) + { + // postSort deliberately leaves the shared lists in legacy order for + // pre-water; switch them only for the consumer that performs the merge + gPipeline.sortAlphaGroupsForInterleaving(); + } + emissive_shader = &gDeferredEmissiveProgram; prepare_alpha_shader(emissive_shader, false, water_sign); @@ -198,14 +209,25 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass) // already being setup for rendering LLGLSLShader::unbind(); - if (!LLPipeline::sRenderingHUDs) + if (interleave) { - // first pass, render rigged objects only and render to depth buffer - forwardRender(true); + // single pass: depth-interleave whole avatars with the distance-sorted + // alpha groups so world alpha composites correctly around avatars. + // The centralized eligibility gate limits the merged walk to the world + // camera; HUD, cube, and reflection renders keep legacy two-pass order. + forwardRender(EAlphaStream::INTERLEAVED); } + else + { + if (!LLPipeline::sRenderingHUDs) + { + // first pass, render rigged objects only and render to depth buffer + forwardRender(EAlphaStream::RIGGED); + } - // 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 +250,7 @@ void LLDrawPoolAlpha::renderPostDeferred(S32 pass) } } -void LLDrawPoolAlpha::forwardRender(bool rigged) +void LLDrawPoolAlpha::forwardRender(EAlphaStream stream) { gPipeline.enableLightsDynamic(); @@ -237,7 +259,7 @@ void LLDrawPoolAlpha::forwardRender(bool rigged) //enable writing to alpha for emissive effects gGL.setColorMask(true, true); - bool write_depth = rigged || + bool write_depth = stream == EAlphaStream::RIGGED || LLDrawPoolWater::sSkipScreenCopy // we want depth written so that rendered alpha will // contribute to the alpha mask used for impostors @@ -245,7 +267,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 interleaved mode depth writes are decided per group inside renderAlpha + LLGLDepthTest depth(GL_TRUE, (write_depth && stream != EAlphaStream::INTERLEAVED) ? GL_TRUE : GL_FALSE); mColorSFactor = LLRender::BF_SOURCE_ALPHA; // } regular alpha blend mColorDFactor = LLRender::BF_ONE_MINUS_SOURCE_ALPHA; // } @@ -253,8 +276,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 (stream != EAlphaStream::WORLD && mType == LLDrawPool::POOL_ALPHA_POST_WATER) { // draw GLTF scene to depth buffer before rigged alpha + // (explicit depth-write scope: in interleaved 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 +289,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, stream); gGL.setColorMask(true, false); - if (!rigged && (LLPipeline::sRenderingHUDs || getType() == LLDrawPoolAlpha::POOL_ALPHA_POST_WATER)) + if (stream != EAlphaStream::RIGGED && (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,9 +587,13 @@ void LLDrawPoolAlpha::renderRiggedPbrEmissives(std::vector& emissiv } } -void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) +void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, EAlphaStream stream) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const bool merged = stream == EAlphaStream::INTERLEAVED; + // stream of the current group; flips per group in the interleaved walk + bool rigged = stream == EAlphaStream::RIGGED; + bool initialized_lighting = false; bool light_enabled = true; @@ -572,18 +602,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 +629,57 @@ 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; + + // merged mode: per-group depth-write guard, re-emplaced only when the + // write state changes (one glDepthMask per run, not per group) + std::optional depth_state; + bool depth_state_writes = false; + + while (iter != iter_end || rigged_iter != rigged_end) { LL_PROFILE_ZONE_NAMED_CATEGORY_DRAWPOOL("renderAlpha - group"); - LLSpatialGroup* group = *i; + + if (merged) + { // take the farther of the two stream heads; an ensemble's groups + // share one avatar depth, so each avatar drains contiguously -- + // rigged run first (ties go rigged), then its unrigged attachment + // groups, which composite over it + if (rigged_iter == rigged_end) + { + rigged = false; + } + else if (iter == iter_end) + { + rigged = true; + } + else + { + F32 rigged_depth = (*rigged_iter)->mAvatarDepth; + F32 world_depth = (*iter)->worldAlphaDepth(); + if (rigged_depth != world_depth) + { + rigged = rigged_depth > world_depth; + } + else + { + // equal depth: keep each avatar's ensemble contiguous. Its + // own rigged draws before its own unrigged; a plain world + // group composites over it (rigged first); two coincident + // avatars drain in the sorts' std::less identity order. + const LLVOAvatar* world_av = (*iter)->mAvatarp; + const LLVOAvatar* rigged_av = (*rigged_iter)->mAvatarp; + rigged = !rigged_av || !world_av || world_av == rigged_av || + std::less()(rigged_av, world_av); + } + } + } + + LLSpatialGroup* group = rigged ? *rigged_iter++ : *iter++; llassert(group); llassert(group->getSpatialPartition()); @@ -628,6 +708,20 @@ void LLDrawPoolAlpha::renderAlpha(U32 mask, bool depth_only, bool rigged) } } + // merged mode: stamped rigged groups write depth so attachment-order + // layering holds; an unstamped group has no defined position in the + // rigged order and must not depth-reject geometry behind it (it + // still blends). Non-merged passes keep the caller's depth state. + if (merged) + { + bool write_depth = write_depth_always || (rigged && group->mAvatarp != nullptr); + if (!depth_state || write_depth != depth_state_writes) + { + depth_state.emplace(GL_TRUE, write_depth ? GL_TRUE : GL_FALSE); + depth_state_writes = write_depth; + } + } + 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 25044beda0..571e3f1555 100644 --- a/indra/newview/lldrawpoolalpha.h +++ b/indra/newview/lldrawpoolalpha.h @@ -58,13 +58,22 @@ class LLDrawPoolAlpha final: public LLRenderPass /*virtual*/ void renderPostDeferred(S32 pass); /*virtual*/ S32 getNumPasses() { return 1; } - void forwardRender(bool write_depth = false); + // which alpha stream(s) a pass draws + enum class EAlphaStream + { + WORLD, // the distance-sorted alpha groups + RIGGED, // the rigged alpha groups only (depth-writing prepass) + INTERLEAVED // both streams in one back-to-front walk; depth writes + // decided per group inside renderAlpha + }; + + void forwardRender(EAlphaStream stream = EAlphaStream::WORLD); /*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, EAlphaStream stream = EAlphaStream::WORLD); void renderAlphaHighlight(); static bool sShowDebugAlpha; diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index 4b312b1597..527053160a 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -43,6 +43,7 @@ #include "llvoavatar.h" #include "llfetchedgltfmaterial.h" +#include #include #include @@ -236,19 +237,92 @@ class LLSpatialGroup : public LLOcclusionCullingGroup } }; + // interleaved world-alpha sort key: a stamped attachment's groups sort at + // the wearer's avatar depth, everything else at bounds depth + F32 worldAlphaDepth() const { return mAvatarp ? mAvatarDepth : mDepth; } + + struct CompareWorldAlphaDepth + { + bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) + { + F32 lhs_depth = lhs->worldAlphaDepth(); + F32 rhs_depth = rhs->worldAlphaDepth(); + if (lhs_depth != rhs_depth) + { + return lhs_depth > rhs_depth; + } + + if (lhs->mAvatarp != rhs->mAvatarp) + { + // at an exact depth tie, drain stamped ensembles before plain + // world groups so a null head cannot split every ensemble + if (!lhs->mAvatarp) + { + return false; + } + if (!rhs->mAvatarp) + { + return true; + } + // std::less: raw < on unrelated pointers is unspecified + return std::less()(lhs->mAvatarp, rhs->mAvatarp); + } + + // groups of one ensemble keep their bounds order among themselves + return lhs->mDepth > rhs->mDepth; + } + }; + struct CompareRenderOrder { bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) { if (lhs->mAvatarp != rhs->mAvatarp) { - return lhs->mAvatarp < rhs->mAvatarp; + // std::less: raw < on unrelated pointers is unspecified + return std::less()(lhs->mAvatarp, rhs->mAvatarp); } return lhs->mRenderOrder > rhs->mRenderOrder; } }; + struct CompareDepthRenderOrder + { + bool operator()(const LLSpatialGroup* const& lhs, const LLSpatialGroup* const& rhs) + { + // farther avatars draw first; all of an avatar's groups share one + // depth, keeping its run contiguous and in attachment order + if (lhs->mAvatarDepth != rhs->mAvatarDepth) + { + return lhs->mAvatarDepth > rhs->mAvatarDepth; + } + + if (lhs->mAvatarp != rhs->mAvatarp) + { + // An unstamped rigged group has no ensemble key; keep it first + // at an exact depth tie, matching the merge's legacy fallback. + if (!lhs->mAvatarp) + { + return true; + } + if (!rhs->mAvatarp) + { + return false; + } + return std::less()(lhs->mAvatarp, rhs->mAvatarp); + } + + if (lhs->mRenderOrder != rhs->mRenderOrder) + { + return lhs->mRenderOrder > rhs->mRenderOrder; + } + + // bounds depth: stable back-to-front order within one attachment + return lhs->mDepth > rhs->mDepth; + } + }; + typedef enum { GEOM_DIRTY = LLViewerOctreeGroup::INVALID_STATE, @@ -351,6 +425,9 @@ class LLSpatialGroup : public LLOcclusionCullingGroup //used by LLVOAVatar to set render order in alpha draw pool to preserve legacy render order behavior LLVOAvatar* mAvatarp = nullptr; U32 mRenderOrder = 0; + // avatar-ensemble sort key, fanned out from the bridge in + // LLPipeline::postSort; mDepth stays bounds-derived + F32 mAvatarDepth = 0.f; // Reflection Probe associated with this node (if any) LLPointer mReflectionProbe = nullptr; } LL_ALIGN_POSTFIX(16); @@ -458,6 +535,14 @@ 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; + + // alpha draw-order stamp, set by LLVOAvatar::idleUpdateMisc / + // LLControlAvatar::idleUpdate and fanned out to this bridge's alpha + // groups in LLPipeline::postSort. mAvatarp is only ever compared, + // never dereferenced (avatar may die first). + LLVOAvatar* mAvatarp = nullptr; + U32 mRenderOrder = 0; + F32 mAvatarDepth = 0.f; }; class LLCullResult @@ -760,4 +845,3 @@ extern const F32 SG_MAX_OBJ_RAD; #endif //LL_LLSPATIALPARTITION_H - diff --git a/indra/newview/llvoavatar.cpp b/indra/newview/llvoavatar.cpp index cd71a2c660..2481790cc5 100644 --- a/indra/newview/llvoavatar.cpp +++ b/indra/newview/llvoavatar.cpp @@ -3008,6 +3008,27 @@ static void override_bbox(LLDrawable* drawable, LLVector4a* extents) drawable->movePartition(); } +// camera-axis depth of this avatar, stamped onto every rigged alpha bridge +// (attachments below, animesh in LLControlAvatar::idleUpdate) so its alpha +// interleaves with world alpha (LLDrawPoolAlpha::renderAlpha). From the +// animated extents, so avatars sharing a sit target still order +// deterministically; pulled a quarter of the bounds toward the camera to match +// the metric LLSpatialPartition::calcDistance stamps onto world alpha groups. +F32 LLVOAvatar::calcRiggedAlphaDepth() const +{ + LLViewerCamera* camera = LLViewerCamera::getInstance(); + const LLVector3& at_axis = camera->getAtAxis(); + if (mLastAnimExtents[0] == LLVector3() || mLastAnimExtents[1] == LLVector3()) + { // extents not computed yet + return (getRenderPosition() - camera->getOrigin()) * at_axis; + } + + const LLVector3 center = (mLastAnimExtents[0] + mLastAnimExtents[1]) * 0.5f; + const LLVector3 half = (mLastAnimExtents[1] - mLastAnimExtents[0]) * 0.5f; + return (center - camera->getOrigin()) * at_axis - + 0.25f * (half * at_axis.scaledVec(at_axis)); +} + void LLVOAvatar::idleUpdateMisc(bool detailed_update) { LL_PROFILE_ZONE_SCOPED_CATEGORY_AVATAR; @@ -3025,6 +3046,7 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update) if (detailed_update) { U32 draw_order = 0; + const F32 rigged_depth = calcRiggedAlphaDepth(); bool attachment_selected = LLSelectMgr::getInstance()->getSelection()->getObjectCount() > 0 && LLSelectMgr::getInstance()->getSelection()->isAttachment(); for (const auto& [attachment_point_id, attachment] : mAttachmentPoints) { @@ -3085,13 +3107,19 @@ void LLVOAvatar::idleUpdateMisc(bool detailed_update) bridge->setState(LLDrawable::MOVE_UNDAMPED); 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++; - } + //stamp the attachment's draw order; LLPipeline::postSort + //fans it out to the bridge's alpha groups, sorting the + //wearer's whole ensemble at one avatar depth. HUD alpha + //sorts in HUD space, so unrigged HUDs stay unstamped. + //Stale-safe: mUpdatePeriod > 1 implies isImpostor(), + //whose attachments never enter the live alpha streams + if (rigged || !attached_object->isHUDAttachment()) + { + bridge->mAvatarp = this; + bridge->mRenderOrder = draw_order++; + bridge->mAvatarDepth = rigged_depth; } } diff --git a/indra/newview/llvoavatar.h b/indra/newview/llvoavatar.h index 3f33dc54b8..6bf975a202 100644 --- a/indra/newview/llvoavatar.h +++ b/indra/newview/llvoavatar.h @@ -282,6 +282,7 @@ class LLVOAvatar : void updateRootPositionAndRotation(LLAgent &agent, F32 speed, bool was_sit_ground_constrained); void idleUpdateVoiceVisualizer(bool voice_enabled, const LLVector3 &position); + F32 calcRiggedAlphaDepth() const; void idleUpdateMisc(bool detailed_update); virtual void idleUpdateAppearanceAnimation(); void idleUpdateLipSync(bool voice_enabled); diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 4ef88f5deb..2786448694 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -3559,6 +3559,19 @@ void renderSoundHighlights(LLDrawable *drawablep) } } +bool LLPipeline::canUseInterleavedAlpha() +{ + static LLCachedControl interleaved_alpha(gSavedSettings, "RenderInterleavedAlpha", true); + return interleaved_alpha && !sRenderingHUDs && !sShadowRender && !gCubeSnapshot && + LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD; +} + +void LLPipeline::sortAlphaGroupsForInterleaving() +{ + std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareWorldAlphaDepth()); + std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareDepthRenderOrder()); +} + void LLPipeline::postSort(LLCamera &camera) { LL_PROFILE_ZONE_SCOPED_CATEGORY_PIPELINE; @@ -3635,11 +3648,22 @@ void LLPipeline::postSort(LLCamera &camera) if (hasRenderType(LLPipeline::RENDER_TYPE_PASS_ALPHA)) { + LLSpatialBridge *bridge = group->getSpatialPartition()->asBridge(); + + // fan the attachment's stamp (LLVOAvatar::idleUpdateMisc) out from + // the bridge to every visible alpha group of the linkset; the shared + // mAvatarDepth keys the wearer's ensemble as one block in the walk + if (bridge && bridge->mAvatarp) + { + group->mAvatarp = bridge->mAvatarp; + group->mRenderOrder = bridge->mRenderOrder; + group->mAvatarDepth = bridge->mAvatarDepth; + } + LLSpatialGroup::draw_map_t::iterator alpha = group->mDrawMap.find(LLRenderPass::PASS_ALPHA); if (alpha != group->mDrawMap.end()) { // store alpha groups for sorting - LLSpatialBridge *bridge = group->getSpatialPartition()->asBridge(); if (LLViewerCamera::sCurCameraID == LLViewerCamera::CAMERA_WORLD && !gCubeSnapshot) { if (bridge) @@ -3704,10 +3728,9 @@ void LLPipeline::postSort(LLCamera &camera) if (!sShadowRender) { - // order alpha groups by distance + // Legacy order is the baseline for every consumer. Post-water replaces + // it with the interleaved order only when it actually uses the merge. std::sort(sCull->beginAlphaGroups(), sCull->endAlphaGroups(), LLSpatialGroup::CompareDepthGreater()); - - // order rigged alpha groups by avatar attachment order std::sort(sCull->beginRiggedAlphaGroups(), sCull->endRiggedAlphaGroups(), LLSpatialGroup::CompareRenderOrder()); } diff --git a/indra/newview/pipeline.h b/indra/newview/pipeline.h index c051306385..a9aac2d038 100644 --- a/indra/newview/pipeline.h +++ b/indra/newview/pipeline.h @@ -383,6 +383,7 @@ class LLPipeline LLCullResult::sg_iterator endAlphaGroups(); LLCullResult::sg_iterator beginRiggedAlphaGroups(); LLCullResult::sg_iterator endRiggedAlphaGroups(); + void sortAlphaGroupsForInterleaving(); void addTrianglesDrawn(S32 index_count); void recordTrianglesDrawn(); @@ -398,6 +399,7 @@ class LLPipeline bool hasAnyRenderType(const U32 type, ...) const; static bool isWaterClip(); + static bool canUseInterleavedAlpha(); void setRenderTypeMask(U32 type, ...); // This is equivalent to 'setRenderTypeMask'