From 6bdbc861955ade6fab83142c2ba2378e11bc6421 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 07:40:59 -0400 Subject: [PATCH 1/9] Indexed GLTF PBR batching: phase 1 (static opaque) Extend the per-vertex texture-index batching (previously diffuse-only) to GLTF PBR materials, so one draw call can cover multiple materials selected per-vertex by the texture_index attribute. Static opaque faces only for now. - sIndexedGLTFChannels = clamp(texUnits/4, 1, 8); zeroed if the indexed shader fails to load so everything degrades to the scalar path. - genDrawInfo gains a batch_gltf accumulation branch that dedups up to N distinct materials into per-face slots; registerFace builds mGLTFMaterialList parallel to mTextureList (bypassing the per-material-id batch break for indexed faces). - Indexed draw infos stay in PASS_GLTF_PBR (flagged by mGLTFMaterialList.size()>1) rather than a new pass, so shadow/probe sweeps still cover them (depth-only) and only the main opaque GBuffer pass splits scalar vs indexed. - New pbropaqueIndexed{V,F}.glsl: slot s binds its four maps to units [s,N+s,2N+s,3N+s]; per-slot KHR transforms (VS arrays) and scalar factors (FS arrays); reuses position.w for the slot, no new vertex attribute. - RenderGLTFPBRBatching setting (default on) for runtime A/B comparison. Co-Authored-By: Claude Opus 4.8 --- indra/llrender/llglslshader.cpp | 1 + indra/llrender/llglslshader.h | 6 + .../newview/app_settings/settings_alchemy.xml | 11 + .../class1/deferred/pbropaqueIndexedF.glsl | 241 ++++++++++++++++++ .../class1/deferred/pbropaqueIndexedV.glsl | 99 +++++++ indra/newview/lldrawpool.cpp | 140 ++++++++++ indra/newview/lldrawpool.h | 11 + indra/newview/lldrawpoolpbropaque.cpp | 18 +- indra/newview/llspatialpartition.h | 9 +- indra/newview/llviewershadermgr.cpp | 52 ++++ indra/newview/llviewershadermgr.h | 1 + indra/newview/llvovolume.cpp | 176 ++++++++++++- 12 files changed, 756 insertions(+), 9 deletions(-) create mode 100644 indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 0d6b15cfd3..0ea8f98f72 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -54,6 +54,7 @@ using std::string; GLuint LLGLSLShader::sCurBoundShader = 0; LLGLSLShader* LLGLSLShader::sCurBoundShaderPtr = NULL; S32 LLGLSLShader::sIndexedTextureChannels = 0; +S32 LLGLSLShader::sIndexedGLTFChannels = 0; U32 LLGLSLShader::sMaxGLTFMaterials = 0; U32 LLGLSLShader::sMaxGLTFNodes = 0; bool LLGLSLShader::sProfileEnabled = false; diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index 704ecf90a1..a5a5c32dad 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -171,6 +171,12 @@ class LLGLSLShader static LLGLSLShader* sCurBoundShaderPtr; static S32 sIndexedTextureChannels; + // Number of GLTF PBR materials that can be batched into one indexed draw call. + // Each material consumes four texture units (base color, normal, ORM, emissive), + // so this is roughly (available fragment texture units) / 4. See + // PASS_GLTF_PBR_INDEXED and LLVolumeGeometryManager::genDrawInfo. + static S32 sIndexedGLTFChannels; + static U32 sMaxGLTFMaterials; static U32 sMaxGLTFNodes; diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index 48f2c098e5..ce1ca2f75c 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -1235,6 +1235,17 @@ Value 1 + RenderGLTFPBRBatching + + Comment + Batch multiple GLTF PBR materials into a single indexed draw call (static opaque only). 0 = one draw call per material (legacy), 1 = indexed multi-material batching. Reduces draw calls in material-heavy scenes. + Persist + 1 + Type + Boolean + Value + 1 + RenderBloomThreshold Comment diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedF.glsl new file mode 100644 index 0000000000..74fffcc01d --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedF.glsl @@ -0,0 +1,241 @@ +/** + * @file pbropaqueIndexedF.glsl + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/*[EXTRA_CODE_HERE]*/ + +// Indexed (multi-material) PBR opaque GBuffer-write fragment shader. +// vary_material_index selects this primitive's material slot. Each slot s owns a +// dedicated four-sampler block bound by the CPU to texture units +// [s, N+s, 2N+s, 3N+s] (N == GLTF_INDEXED_CHANNELS); scalar factors arrive as +// uniform arrays indexed by the slot. If-chains (not switch) are used for sampler +// selection for driver portability. See pbropaqueF.glsl for the single-material +// equivalent. + +out vec4 frag_data[4]; + +in vec3 vary_position; +in vec4 vertex_color; +in vec3 vary_normal; +in vec3 vary_tangent; +flat in float vary_sign; +flat in int vary_material_index; + +in vec2 base_color_texcoord; +in vec2 normal_texcoord; +in vec2 metallic_roughness_texcoord; +in vec2 emissive_texcoord; + +uniform float gltf_roughness_factor[GLTF_INDEXED_CHANNELS]; +uniform float gltf_metallic_factor[GLTF_INDEXED_CHANNELS]; +uniform vec3 gltf_emissive_color[GLTF_INDEXED_CHANNELS]; +uniform float gltf_minimum_alpha[GLTF_INDEXED_CHANNELS]; // PBR alphaMode MASK cutoff, -1 for opaque + +uniform sampler2D basecolor0; // always in sRGB space +uniform sampler2D normalmap0; +uniform sampler2D ormmap0; // Packed: Occlusion, Roughness, Metal +uniform sampler2D emissivemap0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D basecolor1; uniform sampler2D normalmap1; uniform sampler2D ormmap1; uniform sampler2D emissivemap1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D basecolor2; uniform sampler2D normalmap2; uniform sampler2D ormmap2; uniform sampler2D emissivemap2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D basecolor3; uniform sampler2D normalmap3; uniform sampler2D ormmap3; uniform sampler2D emissivemap3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D basecolor4; uniform sampler2D normalmap4; uniform sampler2D ormmap4; uniform sampler2D emissivemap4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D basecolor5; uniform sampler2D normalmap5; uniform sampler2D ormmap5; uniform sampler2D emissivemap5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D basecolor6; uniform sampler2D normalmap6; uniform sampler2D ormmap6; uniform sampler2D emissivemap6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D basecolor7; uniform sampler2D normalmap7; uniform sampler2D ormmap7; uniform sampler2D emissivemap7; +#endif + +vec3 linear_to_srgb(vec3 c); +vec3 srgb_to_linear(vec3 c); + +void mirrorClip(vec3 pos); +vec4 encodeNormal(vec3 n, float env, float gbuffer_flag); + +vec4 sample_basecolor(vec2 uv) +{ + if (vary_material_index == 0) return texture(basecolor0, uv); +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(basecolor1, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(basecolor2, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(basecolor3, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(basecolor4, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(basecolor5, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(basecolor6, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(basecolor7, uv); +#endif + return vec4(1, 0, 1, 1); +} + +vec3 sample_normal(vec2 uv) +{ + if (vary_material_index == 0) return texture(normalmap0, uv).xyz; +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(normalmap1, uv).xyz; +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(normalmap2, uv).xyz; +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(normalmap3, uv).xyz; +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(normalmap4, uv).xyz; +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(normalmap5, uv).xyz; +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(normalmap6, uv).xyz; +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(normalmap7, uv).xyz; +#endif + return vec3(0.5, 0.5, 1.0); +} + +vec3 sample_orm(vec2 uv) +{ + if (vary_material_index == 0) return texture(ormmap0, uv).rgb; +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(ormmap1, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(ormmap2, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(ormmap3, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(ormmap4, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(ormmap5, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(ormmap6, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(ormmap7, uv).rgb; +#endif + return vec3(1.0, 0.0, 0.0); +} + +vec3 sample_emissive(vec2 uv) +{ + if (vary_material_index == 0) return texture(emissivemap0, uv).rgb; +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(emissivemap1, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(emissivemap2, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(emissivemap3, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(emissivemap4, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(emissivemap5, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(emissivemap6, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(emissivemap7, uv).rgb; +#endif + return vec3(1.0); +} + +void main() +{ + mirrorClip(vary_position); + + int mi = vary_material_index; + + vec4 basecolor = sample_basecolor(base_color_texcoord.xy).rgba; + basecolor.rgb = srgb_to_linear(basecolor.rgb); + + basecolor *= vertex_color; + + if (basecolor.a < gltf_minimum_alpha[mi]) + { + discard; + } + + vec3 col = basecolor.rgb; + + // from mikktspace.com + vec3 vNt = sample_normal(normal_texcoord.xy) * 2.0 - 1.0; + float sign = vary_sign; + vec3 vN = vary_normal; + vec3 vT = vary_tangent.xyz; + + vec3 vB = sign * cross(vN, vT); + vec3 tnorm = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); + + // RGB = Occlusion, Roughness, Metal + vec3 spec = sample_orm(metallic_roughness_texcoord.xy); + + spec.g *= gltf_roughness_factor[mi]; + spec.b *= gltf_metallic_factor[mi]; + + vec3 emissive = gltf_emissive_color[mi]; + emissive *= srgb_to_linear(sample_emissive(emissive_texcoord.xy)); + + tnorm *= gl_FrontFacing ? 1.0 : -1.0; + + // See: C++: addDeferredAttachments(), GLSL: softenLightF + frag_data[0] = max(vec4(col, 0.0), vec4(0)); // Diffuse + frag_data[1] = max(vec4(spec.rgb, 0.0), vec4(0)); // PBR linear packed Occlusion, Roughness, Metal. + frag_data[2] = encodeNormal(tnorm, 0, GBUFFER_FLAG_HAS_PBR); // normal, environment intensity, flags + +#if defined(HAS_EMISSIVE) + frag_data[3] = max(vec4(emissive, 0), vec4(0)); // PBR sRGB Emissive +#endif +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl new file mode 100644 index 0000000000..c9df33dbb7 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl @@ -0,0 +1,99 @@ +/** + * @file pbropaqueIndexedV.glsl + * + * $LicenseInfo:firstyear=2022&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2022, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) PBR opaque GBuffer-write vertex shader. +// One draw call covers up to GLTF_INDEXED_CHANNELS materials. The per-vertex +// material slot arrives in the texture_index attribute (packed in position.w); +// it selects this vertex's KHR_texture_transform set and is forwarded to the +// fragment shader as vary_material_index. See pbropaqueV.glsl for the +// single-material equivalent. + +uniform mat4 modelview_matrix; +uniform mat3 normal_matrix; +uniform mat4 modelview_projection_matrix; + +// Per-material KHR_texture_transform, two vec4 (packed scale/rotation/offset) +// per slot. Indexed by the material slot (texture_index). +uniform vec4 gltf_basecolor_transform[2*GLTF_INDEXED_CHANNELS]; +uniform vec4 gltf_normal_transform[2*GLTF_INDEXED_CHANNELS]; +uniform vec4 gltf_mr_transform[2*GLTF_INDEXED_CHANNELS]; +uniform vec4 gltf_emissive_transform[2*GLTF_INDEXED_CHANNELS]; + +in vec3 position; +in vec4 diffuse_color; +in vec3 normal; +in vec4 tangent; +in vec2 texcoord0; +in int texture_index; + +flat out int vary_material_index; + +out vec2 base_color_texcoord; +out vec2 normal_texcoord; +out vec2 metallic_roughness_texcoord; +out vec2 emissive_texcoord; + +out vec4 vertex_color; + +out vec3 vary_tangent; +flat out float vary_sign; +out vec3 vary_normal; +out vec3 vary_position; + +vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); +vec4 tangent_space_transform(vec4 vertex_tangent, vec3 vertex_normal, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); + +void main() +{ + vary_position = (modelview_matrix * vec4(position.xyz, 1.0)).xyz; + gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); + + int mi = texture_index; + vary_material_index = mi; + + // Indexed faces never carry texture animation (excluded from batching), so + // the SL animation transform is identity. + mat4 ident = mat4(1.0); + + vec4 bc[2]; bc[0] = gltf_basecolor_transform[2*mi]; bc[1] = gltf_basecolor_transform[2*mi+1]; + vec4 nm[2]; nm[0] = gltf_normal_transform[2*mi]; nm[1] = gltf_normal_transform[2*mi+1]; + vec4 mr[2]; mr[0] = gltf_mr_transform[2*mi]; mr[1] = gltf_mr_transform[2*mi+1]; + vec4 em[2]; em[0] = gltf_emissive_transform[2*mi]; em[1] = gltf_emissive_transform[2*mi+1]; + + base_color_texcoord = texture_transform(texcoord0, bc, ident); + normal_texcoord = texture_transform(texcoord0, nm, ident); + metallic_roughness_texcoord = texture_transform(texcoord0, mr, ident); + emissive_texcoord = texture_transform(texcoord0, em, ident); + + vec3 n = normalize(normal_matrix * normal); + vec3 t = normal_matrix * tangent.xyz; + + vec4 transformed_tangent = tangent_space_transform(vec4(t, tangent.w), n, nm, ident); + vary_tangent = normalize(transformed_tangent.xyz); + vary_sign = transformed_tangent.w; + vary_normal = n; + + vertex_color = diffuse_color; +} diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 56a3baed34..635f59c799 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -53,6 +53,8 @@ #include "llglcommonfunc.h" #include "llvoavatar.h" #include "llviewershadermgr.h" +#include "llfetchedgltfmaterial.h" +#include "llviewertexture.h" S32 LLDrawPool::sNumDrawPools = 0; @@ -815,6 +817,144 @@ void LLRenderPass::pushUntexturedGLTFBatches(U32 type) } } +// Like pushGLTFBatches, but skips multi-material (indexed) draw infos -- those are +// rendered separately by pushGLTFBatchesIndexed under the indexed shader. Used by +// the main opaque GBuffer pass only. +void LLRenderPass::pushGLTFBatchesScalar(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + LLFetchedGLTFMaterial* lastMat = nullptr; + LLViewerTexture* lastTex = nullptr; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mGLTFMaterialList.size() > 1) + { // multi-material batch -- handled by the indexed sweep + continue; + } + + pushGLTFBatch(params, lastMat, lastTex); + } +} + +// Renders only the multi-material (indexed) draw infos. Assumes the indexed PBR +// shader is bound. +void LLRenderPass::pushGLTFBatchesIndexed(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mGLTFMaterialList.size() < 2) + { // single-material batch -- handled by the scalar sweep + continue; + } + + pushGLTFBatchIndexed(params); + } +} + +// static +// Bind one draw call's worth of indexed materials and emit it. Each material slot +// s binds its four maps to texture units [s, N+s, 2N+s, 3N+s] (N == shader's +// sIndexedGLTFChannels) and contributes one element to the per-slot scalar/transform +// uniform arrays. Mirrors LLFetchedGLTFMaterial::bind for the default-texture and +// factor handling. +void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + + const S32 N = LLGLSLShader::sIndexedGLTFChannels; // shader sampler-array stride + const S32 n = (S32)params.mGLTFMaterialList.size(); // materials in this batch + + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + + // gathered per-slot uniform data (max 8 slots; see sIndexedGLTFChannels clamp) + F32 roughness[8] = { 0.f }; + F32 metallic[8] = { 0.f }; + F32 min_alpha[8] = { 0.f }; + F32 emissive[3 * 8] = { 0.f }; + F32 bc_xform[8 * 8] = { 0.f }; // 2 vec4 (8 floats) per slot + F32 nm_xform[8 * 8] = { 0.f }; + F32 mr_xform[8 * 8] = { 0.f }; + F32 em_xform[8 * 8] = { 0.f }; + + bool double_sided = false; + + for (S32 s = 0; s < n; ++s) + { + LLFetchedGLTFMaterial* mat = params.mGLTFMaterialList[s].get(); + if (mat == nullptr) + { // gap left by a fragmented batch -- this slot is never sampled + min_alpha[s] = -1.f; + continue; + } + + double_sided = double_sided || mat->mDoubleSided; + + LLViewerTexture* base = mat->mBaseColorTexture.notNull() ? mat->mBaseColorTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + LLViewerTexture* norm = (mat->mNormalTexture.notNull() && mat->mNormalTexture->getDiscardLevel() <= 4) ? mat->mNormalTexture.get() : LLViewerFetchedTexture::sFlatNormalImagep.get(); + LLViewerTexture* orm = mat->mMetallicRoughnessTexture.notNull() ? mat->mMetallicRoughnessTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + LLViewerTexture* em = mat->mEmissiveTexture.notNull() ? mat->mEmissiveTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + + gGL.getTexUnit(s)->bindFast(base); + gGL.getTexUnit(N + s)->bindFast(norm); + gGL.getTexUnit(2 * N + s)->bindFast(orm); + gGL.getTexUnit(3 * N + s)->bindFast(em); + + roughness[s] = mat->mRoughnessFactor; + metallic[s] = mat->mMetallicFactor; + emissive[3 * s + 0] = mat->mEmissiveColor.mV[0]; + emissive[3 * s + 1] = mat->mEmissiveColor.mV[1]; + emissive[3 * s + 2] = mat->mEmissiveColor.mV[2]; + min_alpha[s] = (mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) ? mat->mAlphaCutoff : -1.f; + + // getPacked() takes F32(&)[8]; copy each transform into its slot stride. + LLGLTFMaterial::TextureTransform::Pack packed; + mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(packed); + memcpy(&bc_xform[8 * s], packed, sizeof(packed)); + mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(packed); + memcpy(&nm_xform[8 * s], packed, sizeof(packed)); + mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(packed); + memcpy(&mr_xform[8 * s], packed, sizeof(packed)); + mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(packed); + memcpy(&em_xform[8 * s], packed, sizeof(packed)); + } + + static const LLStaticHashedString sRoughness("gltf_roughness_factor"); + static const LLStaticHashedString sMetallic("gltf_metallic_factor"); + static const LLStaticHashedString sEmissive("gltf_emissive_color"); + static const LLStaticHashedString sMinAlpha("gltf_minimum_alpha"); + static const LLStaticHashedString sBcXform("gltf_basecolor_transform"); + static const LLStaticHashedString sNmXform("gltf_normal_transform"); + static const LLStaticHashedString sMrXform("gltf_mr_transform"); + static const LLStaticHashedString sEmXform("gltf_emissive_transform"); + + shader->uniform1fv(sRoughness, n, roughness); + shader->uniform1fv(sMetallic, n, metallic); + shader->uniform1fv(sMinAlpha, n, min_alpha); + shader->uniform3fv(sEmissive, n, emissive); + shader->uniform4fv(sBcXform, 2 * n, bc_xform); + shader->uniform4fv(sNmXform, 2 * n, nm_xform); + shader->uniform4fv(sMrXform, 2 * n, mr_xform); + shader->uniform4fv(sEmXform, 2 * n, em_xform); + + LLGLDisable cull_face(double_sided ? GL_CULL_FACE : 0); + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); +} + // static void LLRenderPass::pushGLTFBatch(LLDrawInfo& params, LLFetchedGLTFMaterial*& lastMat, LLViewerTexture*& lastTex) { diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index c645565f06..cca5b79298 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -364,6 +364,17 @@ class LLRenderPass : public LLDrawPool // assumes draw infos of given type have valid GLTF materials void pushGLTFBatches(U32 type); + // Indexed (multi-material) GLTF PBR helpers. Indexed and scalar draw infos + // coexist in the same render map (PASS_GLTF_PBR); they are distinguished by + // mGLTFMaterialList.size() > 1. Shadow/probe passes use the plain + // pushGLTFBatches (rendering everything scalar for depth); only the main + // opaque GBuffer pass splits the two: + // pushGLTFBatchesScalar -- renders only single-material infos + // pushGLTFBatchesIndexed -- renders only multi-material infos (indexed program bound) + void pushGLTFBatchesScalar(U32 type); + void pushGLTFBatchesIndexed(U32 type); + static void pushGLTFBatchIndexed(LLDrawInfo& params); + // like pushGLTFBatches, but will not bind textures or set up texture transforms void pushUntexturedGLTFBatches(U32 type); diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp index eb387e1f46..2dd33a07df 100644 --- a/indra/newview/lldrawpoolpbropaque.cpp +++ b/indra/newview/lldrawpoolpbropaque.cpp @@ -55,11 +55,27 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass) LLGLEnable srgb(GL_FRAMEBUFFER_SRGB); + // Indexed (multi-material) batching applies to the static opaque pass only. + bool indexed = (mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR) && LLGLSLShader::sIndexedGLTFChannels >= 2; + gDeferredPBROpaqueProgram.bind(); - pushGLTFBatches(mRenderType); + if (indexed) + { // multi-material infos are drawn separately below; render only scalar here + pushGLTFBatchesScalar(mRenderType); + } + else + { + pushGLTFBatches(mRenderType); + } gDeferredPBROpaqueProgram.bind(true); pushRiggedGLTFBatches(mRenderType + 1); + + if (indexed) + { + gDeferredPBROpaqueIndexedProgram.bind(); + pushGLTFBatchesIndexed(mRenderType); + } } S32 LLDrawPoolGLTFPBR::getNumPostDeferredPasses() diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index ff90725981..b69f87d27a 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -117,6 +117,13 @@ class alignas(16) LLDrawInfo final : public LLRefCount // PBR material parameters LLPointer mGLTFMaterial; + // Indexed (multi-material) GLTF PBR batching: when size() > 1 this draw call + // covers several materials, selected per-vertex by the texture_index attribute + // (the material slot). Slot s binds its four maps to texture units + // [s, N+s, 2N+s, 3N+s] where N == mGLTFMaterialList.size(). Empty for the + // single-material path (which uses mGLTFMaterial above). + std::vector > mGLTFMaterialList; + LLVector4 mSpecColor = LLVector4(1.f, 1.f, 1.f, 0.5f); // XYZ = Specular RGB, W = Specular Exponent std::vector > mTextureList; @@ -669,7 +676,7 @@ class LLVolumeGeometryManager: public LLGeometryManager virtual void rebuildMesh(LLSpatialGroup* group); virtual void getGeometry(LLSpatialGroup* group); virtual void addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count); - U32 genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort = false, bool batch_textures = false, bool rigged = false); + U32 genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort = false, bool batch_textures = false, bool rigged = false, bool batch_gltf = false); void registerFace(LLSpatialGroup* group, LLFace* facep, U32 type); private: diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index e1b51e05f2..09324d286f 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -248,6 +248,7 @@ LLGLSLShader gHUDPBROpaqueProgram; LLGLSLShader gPBRGlowProgram; LLGLSLShader gPBRGlowSkinnedProgram; LLGLSLShader gDeferredPBROpaqueProgram; +LLGLSLShader gDeferredPBROpaqueIndexedProgram; LLGLSLShader gDeferredSkinnedPBROpaqueProgram; LLGLSLShader gHUDPBRAlphaProgram; LLGLSLShader gDeferredPBRAlphaProgram; @@ -483,6 +484,13 @@ void LLViewerShaderMgr::setShaders() LLGLSLShader::sIndexedTextureChannels = llmax(4, gGLManager.mNumTextureImageUnits - reserved_texture_units); + // Indexed GLTF PBR batches one material per four texture units (base color, + // normal, ORM, emissive). The PBR opaque GBuffer-write pass binds no + // shadow/reflection maps, so the full fragment texture-unit budget is + // available here -- unlike sIndexedTextureChannels above, no units are + // reserved. Capped at 8 to bound shader sampler declarations. + LLGLSLShader::sIndexedGLTFChannels = llclamp(gGLManager.mNumTextureImageUnits / 4, 1, 8); + reentrance = true; // Make sure the compiled shader map is cleared before we recompile shaders. @@ -1352,6 +1360,50 @@ bool LLViewerShaderMgr::loadShadersDeferred() llassert(success); } + if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) + { + // Indexed (multi-material) PBR opaque. Optional acceleration: failure here + // disables GLTF batching but must NOT fail overall shader loading, so the + // result is kept out of the `success` chain. + gDeferredPBROpaqueIndexedProgram.mName = "Deferred PBR Opaque Indexed Shader"; + gDeferredPBROpaqueIndexedProgram.mFeatures.hasSrgb = true; + gDeferredPBROpaqueIndexedProgram.mShaderFiles.clear(); + gDeferredPBROpaqueIndexedProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueIndexedV.glsl", GL_VERTEX_SHADER)); + gDeferredPBROpaqueIndexedProgram.mShaderFiles.push_back(make_pair("deferred/pbropaqueIndexedF.glsl", GL_FRAGMENT_SHADER)); + gDeferredPBROpaqueIndexedProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredPBROpaqueIndexedProgram.clearPermutations(); + gDeferredPBROpaqueIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); + add_common_permutations(&gDeferredPBROpaqueIndexedProgram); + + bool indexed_ok = gDeferredPBROpaqueIndexedProgram.createShader(); + if (indexed_ok) + { + // Map each slot's four material samplers to texture units: + // base color s -> s, normal s -> N+s, ORM s -> 2N+s, emissive s -> 3N+s + const S32 n = LLGLSLShader::sIndexedGLTFChannels; + gDeferredPBROpaqueIndexedProgram.bind(); + for (S32 s = 0; s < n; ++s) + { + gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("basecolor%d", s)), s); + gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("normalmap%d", s)), n + s); + gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("ormmap%d", s)), 2 * n + s); + gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("emissivemap%d", s)), 3 * n + s); + } + gDeferredPBROpaqueIndexedProgram.unbind(); + } + else + { + // Degrade gracefully: route all PBR faces back to the scalar path. + LL_WARNS("ShaderLoading") << "Indexed PBR shader failed to load; GLTF batching disabled." << LL_ENDL; + gDeferredPBROpaqueIndexedProgram.unload(); + LLGLSLShader::sIndexedGLTFChannels = 0; + } + } + else + { + LLGLSLShader::sIndexedGLTFChannels = 0; + } + if (success) { gPBRGlowProgram.mName = " PBR Glow Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 75bedaf985..7dc5f13775 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -309,6 +309,7 @@ extern LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2] extern LLGLSLShader gHUDPBROpaqueProgram; extern LLGLSLShader gPBRGlowProgram; extern LLGLSLShader gDeferredPBROpaqueProgram; +extern LLGLSLShader gDeferredPBROpaqueIndexedProgram; // multi-material indexed PBR opaque extern LLGLSLShader gDeferredPBRAlphaProgram; extern LLGLSLShader gHUDPBRAlphaProgram; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index a1580dc711..f660d2db4c 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5233,6 +5233,45 @@ bool can_batch_texture(LLFace* facep) return true; } +// Whether a face is eligible for indexed (multi-material) GLTF PBR batching. +// Double-sidedness is intentionally NOT checked here -- it is a fixed-function +// cull state, so it is handled as a batch-break key in genDrawInfo rather than +// excluding the face outright. +bool can_batch_gltf_material(LLFace* facep) +{ + if (LLGLSLShader::sIndexedGLTFChannels < 2) + { // no headroom to batch more than one material; use the scalar path + return false; + } + + const LLTextureEntry* te = facep->getTextureEntry(); + + const LLGLTFMaterial* gltf_mat = te->getGLTFRenderMaterial(); + if (gltf_mat == nullptr) + { // not a PBR face + return false; + } + + if (gltf_mat->mAlphaMode != LLGLTFMaterial::ALPHA_MODE_OPAQUE) + { // Phase 1 batches opaque only. Blend is depth-sorted in PASS_ALPHA; mask + // routes to PASS_GLTF_PBR_ALPHA_MASK which the indexed sweep does not cover, + // so a masked face must never receive a material slot. + return false; + } + + if (te->hasMedia()) + { // media overrides base color per-face; keep on the scalar bind path + return false; + } + + if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) + { // texture animation drives texture_matrix0 per-face -- breaks batches + return false; + } + + return true; +} + const static U32 MAX_FACE_COUNT = 4096U; int32_t LLVolumeGeometryManager::sInstanceCount = 0; LLFace** LLVolumeGeometryManager::sFullbrightFaces[2] = { NULL }; @@ -5425,6 +5464,11 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, } } + // A GLTF PBR face carrying a real material slot (assigned by the indexed + // accumulation in genDrawInfo) participates in multi-material batching via + // mGLTFMaterialList, parallel to mTextureList for diffuse texture batching. + bool gltf_indexed = (gltf_mat != nullptr) && (index < FACE_DO_NOT_BATCH_TEXTURES); + bool batchable = false; U32 shader_mask = 0xFFFFFFFF; //no shader @@ -5444,7 +5488,26 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, if (index < FACE_DO_NOT_BATCH_TEXTURES && idx >= 0) { - if (mat || gltf_mat || draw_vec[idx]->mMaterial) + if (gltf_indexed) + { //indexed GLTF PBR: batch by material slot (parallel to mTextureList) + if (index < draw_vec[idx]->mGLTFMaterialList.size()) + { + if (draw_vec[idx]->mGLTFMaterialList[index].isNull()) + { + batchable = true; + draw_vec[idx]->mGLTFMaterialList[index] = gltf_mat; + } + else if (draw_vec[idx]->mGLTFMaterialList[index] == gltf_mat) + { //this face's material slot can be used with this batch + batchable = true; + } + } + else + { //material list can be expanded to fit this slot + batchable = true; + } + } + else if (mat || gltf_mat || draw_vec[idx]->mMaterial) { //can't batch textures when materials are present (yet) batchable = false; } @@ -5476,7 +5539,10 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, info->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange && info->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && #endif - info->mMaterialID == mat_id && + // indexed GLTF batches deliberately merge different materials, so the + // per-material id differs across the batch -- the material slot list + // (checked via `batchable` above) governs membership instead. + (gltf_indexed || info->mMaterialID == mat_id) && info->mFullbright == fullbright && info->mBump == bump && (!mat || (info->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different @@ -5489,7 +5555,15 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, info->mCount += facep->getIndicesCount(); info->mEnd += facep->getGeomCount(); - if (index < FACE_DO_NOT_BATCH_TEXTURES && index >= info->mTextureList.size()) + if (gltf_indexed) + { + if (index >= info->mGLTFMaterialList.size()) + { + info->mGLTFMaterialList.resize(index+1); + } + info->mGLTFMaterialList[index] = gltf_mat; + } + else if (index < FACE_DO_NOT_BATCH_TEXTURES && index >= info->mTextureList.size()) { info->mTextureList.resize(index+1); info->mTextureList[index] = tex; @@ -5576,7 +5650,12 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, facep->setDrawInfo(draw_info); } - if (index < FACE_DO_NOT_BATCH_TEXTURES) + if (gltf_indexed) + { //initialize material slot list for indexed GLTF batching + draw_info->mGLTFMaterialList.resize(index+1); + draw_info->mGLTFMaterialList[index] = gltf_mat; + } + else if (index < FACE_DO_NOT_BATCH_TEXTURES) { //initialize texture list for texture batching draw_info->mTextureList.resize(index+1); draw_info->mTextureList[index] = tex; @@ -6122,6 +6201,12 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) U32 geometryBytes = 0; + // Indexed (multi-material) GLTF PBR batching. Enabled when the indexed shader + // is available (sIndexedGLTFChannels gets zeroed on load failure) and not + // disabled by setting. Applied to static opaque PBR faces only (see below). + static LLCachedControl gltf_batching(gSavedSettings, "RenderGLTFPBRBatching", true); + bool gltf_batch_enabled = gltf_batching && LLGLSLShader::sIndexedGLTFChannels >= 2; + // generate render batches for static geometry U32 extra_mask = LLVertexBuffer::MAP_TEXTURE_INDEX; bool alpha_sort = true; @@ -6135,7 +6220,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged); geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged); geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged); + geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged, !rigged && gltf_batch_enabled); // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) extra_mask |= LLVertexBuffer::MAP_WEIGHT4; @@ -6307,7 +6392,7 @@ struct CompareBatchBreakerRigged } }; -U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort, bool batch_textures, bool rigged) +U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort, bool batch_textures, bool rigged, bool batch_gltf) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; @@ -6390,7 +6475,84 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace { LL_PROFILE_ZONE_NAMED("genDrawInfo - face size"); - if (batch_textures) + if (batch_gltf && !hud_group && can_batch_gltf_material(facep)) + { + // Indexed (multi-material) GLTF PBR: accumulate up to + // sIndexedGLTFChannels distinct materials into one batch, assigning + // each face a material slot via setTextureIndex. The shader selects + // per-vertex by that slot. See LLRenderPass::pushGLTFBatchIndexed. + const S32 gltf_channels = llmin((S32)LLGLSLShader::sIndexedGLTFChannels, 8); + const LLGLTFMaterial* mat_slots[8]; + U32 slot_count = 0; + + const bool anchor_double = facep->getTextureEntry()->getGLTFRenderMaterial()->mDoubleSided; + mat_slots[slot_count++] = facep->getTextureEntry()->getGLTFRenderMaterial(); + facep->setTextureIndex(0); + + while (i != end_faces) + { + facep = *i; + + if (!can_batch_gltf_material(facep)) + { // not an opaque batchable PBR face -- ends this batch + break; + } + + const LLGLTFMaterial* m = facep->getTextureEntry()->getGLTFRenderMaterial(); + if (m->mDoubleSided != anchor_double) + { // different cull state can't share a draw call + break; + } + + // find this material's slot, or assign a new one + S32 slot = -1; + for (U32 s = 0; s < slot_count; ++s) + { + if (mat_slots[s] == m) + { + slot = (S32)s; + break; + } + } + if (slot < 0) + { + if ((S32)slot_count >= gltf_channels) + { // material channels depleted -- cut the batch + break; + } + slot = (S32)slot_count; + mat_slots[slot_count++] = m; + } + + if (geom_count + facep->getGeomCount() > max_vertices) + { // cut batches on geom count too big + break; + } + + ++i; + index_count += facep->getIndicesCount(); + geom_count += facep->getGeomCount(); + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + + facep->setTextureIndex((U8)slot); + } + + if (slot_count < 2) + { // only one material in this span -- fall back to the scalar PBR + // path (registerFace merges these by material id as usual). + // Clear mDrawInfo first, like the non-batch path below: + // setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES) asserts (LL_ERRS) if + // the face still references an indexed-texture draw info, which it + // may from a prior frame. + for (LLFace** f = face_iter; f != i; ++f) + { + (*f)->mDrawInfo = NULL; + (*f)->setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES); + } + } + } + else if (batch_textures) { U8 cur_tex = 0; facep->setTextureIndex(cur_tex); From e8643696d35d7b6fe3dbd0821bd124a509e4a034 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 07:54:05 -0400 Subject: [PATCH 2/9] Indexed GLTF PBR batching: phase 2 (alpha mask) Extend indexed batching to GLTF alpha-mask materials alongside opaque. - can_batch_gltf_material now accepts opaque and mask (blend still excluded); the accumulation keeps each batch to a single alpha mode so opaque/mask faces land in their respective passes (PASS_GLTF_PBR / PASS_GLTF_PBR_ALPHA_MASK). - The main GBuffer scalar/indexed split now covers the alpha-mask pass too, reusing the opaque indexed program -- the per-slot gltf_minimum_alpha array drives the mask discard (-1 == opaque). - Alpha-mask faces alpha-test per-face in the shadow map, so a scalar slot-0 fallback would corrupt batched cutouts. Add gDeferredShadowGLTFAlphaMaskIndexed program + pbrShadowAlphaMaskIndexed{V,F}.glsl (base-color only, reuses pushGLTFBatchIndexed) and split the sun-shadow alpha-mask sweep accordingly. - Register both indexed programs in the shader unload list; the shadow split is gated on isComplete() so a failed indexed shadow shader degrades to scalar. Co-Authored-By: Claude Opus 4.8 --- .../deferred/pbrShadowAlphaMaskIndexedF.glsl | 99 +++++++++++++++++++ .../deferred/pbrShadowAlphaMaskIndexedV.glsl | 65 ++++++++++++ indra/newview/lldrawpoolpbropaque.cpp | 8 +- indra/newview/llviewershadermgr.cpp | 35 +++++++ indra/newview/llviewershadermgr.h | 1 + indra/newview/llvovolume.cpp | 22 +++-- indra/newview/pipeline.cpp | 11 +++ 7 files changed, 232 insertions(+), 9 deletions(-) create mode 100644 indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedF.glsl new file mode 100644 index 0000000000..d53744b113 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedF.glsl @@ -0,0 +1,99 @@ +/** + * @file pbrShadowAlphaMaskIndexedF.glsl + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) variant of pbrShadowAlphaMaskF. Each material slot owns +// a base-color sampler bound by the CPU to unit s, and a per-slot alpha cutoff. + +out vec4 frag_color; + +flat in int vary_material_index; +in vec4 post_pos; +in float target_pos_x; +in vec4 vertex_color; +in vec2 vary_texcoord0; + +uniform float gltf_minimum_alpha[GLTF_INDEXED_CHANNELS]; + +uniform sampler2D basecolor0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D basecolor1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D basecolor2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D basecolor3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D basecolor4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D basecolor5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D basecolor6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D basecolor7; +#endif + +float sample_alpha(vec2 uv) +{ + if (vary_material_index == 0) return texture(basecolor0, uv).a; +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(basecolor1, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(basecolor2, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(basecolor3, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(basecolor4, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(basecolor5, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(basecolor6, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(basecolor7, uv).a; +#endif + return 1.0; +} + +void main() +{ + float alpha = sample_alpha(vary_texcoord0.xy) * vertex_color.a; + + if (alpha < gltf_minimum_alpha[vary_material_index]) + { + discard; + } + + frag_color = vec4(1, 1, 1, 1); +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl new file mode 100644 index 0000000000..2a97e93cb1 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl @@ -0,0 +1,65 @@ +/** + * @file pbrShadowAlphaMaskIndexedV.glsl + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) variant of pbrShadowAlphaMaskV. The per-vertex material +// slot (texture_index) selects this vertex's base-color transform and is forwarded +// to the fragment shader for per-slot alpha testing. + +uniform mat4 modelview_projection_matrix; +uniform float shadow_target_width; + +uniform vec4 gltf_basecolor_transform[2*GLTF_INDEXED_CHANNELS]; +vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); + +in vec3 position; +in vec4 diffuse_color; +in vec2 texcoord0; +in int texture_index; + +flat out int vary_material_index; +out vec4 post_pos; +out float target_pos_x; +out vec4 vertex_color; +out vec2 vary_texcoord0; + +void main() +{ + vec4 pre_pos = vec4(position.xyz, 1.0); + vec4 pos = modelview_projection_matrix * pre_pos; + + target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x; + post_pos = pos; + gl_Position = pos; + + int mi = texture_index; + vary_material_index = mi; + + // Indexed faces never carry texture animation, so identity SL transform. + mat4 ident = mat4(1.0); + vec4 bc[2]; bc[0] = gltf_basecolor_transform[2*mi]; bc[1] = gltf_basecolor_transform[2*mi+1]; + vary_texcoord0 = texture_transform(texcoord0, bc, ident); + + vertex_color = diffuse_color; +} diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp index 2dd33a07df..4db0efc3b1 100644 --- a/indra/newview/lldrawpoolpbropaque.cpp +++ b/indra/newview/lldrawpoolpbropaque.cpp @@ -55,8 +55,12 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass) LLGLEnable srgb(GL_FRAMEBUFFER_SRGB); - // Indexed (multi-material) batching applies to the static opaque pass only. - bool indexed = (mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR) && LLGLSLShader::sIndexedGLTFChannels >= 2; + // Indexed (multi-material) batching applies to the static opaque and alpha-mask + // passes. The indexed program writes the GBuffer the same way for both; the + // per-slot gltf_minimum_alpha array drives the mask discard (-1 == opaque). + bool indexed = (mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR || + mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK) && + LLGLSLShader::sIndexedGLTFChannels >= 2; gDeferredPBROpaqueProgram.bind(); if (indexed) diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 09324d286f..a794484e57 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -177,6 +177,7 @@ LLGLSLShader gDeferredShadowCubeProgram; LLGLSLShader gDeferredShadowAlphaMaskProgram; LLGLSLShader gDeferredSkinnedShadowAlphaMaskProgram; LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; +LLGLSLShader gDeferredShadowGLTFAlphaMaskIndexedProgram; // multi-material indexed LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskProgram; LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; LLGLSLShader gDeferredSkinnedShadowGLTFAlphaBlendProgram; @@ -1090,6 +1091,7 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredShadowAlphaMaskProgram.unload(); gDeferredSkinnedShadowAlphaMaskProgram.unload(); gDeferredShadowGLTFAlphaMaskProgram.unload(); + gDeferredShadowGLTFAlphaMaskIndexedProgram.unload(); gDeferredSkinnedShadowGLTFAlphaMaskProgram.unload(); gDeferredShadowFullbrightAlphaMaskProgram.unload(); gDeferredSkinnedShadowFullbrightAlphaMaskProgram.unload(); @@ -1154,6 +1156,7 @@ bool LLViewerShaderMgr::loadShadersDeferred() gHUDPBROpaqueProgram.unload(); gPBRGlowProgram.unload(); gDeferredPBROpaqueProgram.unload(); + gDeferredPBROpaqueIndexedProgram.unload(); gDeferredSkinnedPBROpaqueProgram.unload(); gDeferredPBRAlphaProgram.unload(); gDeferredSkinnedPBRAlphaProgram.unload(); @@ -2282,6 +2285,38 @@ bool LLViewerShaderMgr::loadShadersDeferred() llassert(success); } + if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) + { + // Indexed (multi-material) shadow alpha mask, so batched mask faces alpha-test + // per-slot in the shadow map. Optional: if it fails to load the shadow pass + // falls back to the scalar program (slightly wrong per-face cutouts, no crash), + // so this is kept out of the `success` chain. + gDeferredShadowGLTFAlphaMaskIndexedProgram.mName = "Deferred GLTF Shadow Alpha Mask Indexed Shader"; + gDeferredShadowGLTFAlphaMaskIndexedProgram.mShaderFiles.clear(); + gDeferredShadowGLTFAlphaMaskIndexedProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskIndexedV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowGLTFAlphaMaskIndexedProgram.mShaderFiles.push_back(make_pair("deferred/pbrShadowAlphaMaskIndexedF.glsl", GL_FRAGMENT_SHADER)); + gDeferredShadowGLTFAlphaMaskIndexedProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredShadowGLTFAlphaMaskIndexedProgram.clearPermutations(); + gDeferredShadowGLTFAlphaMaskIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); + add_common_permutations(&gDeferredShadowGLTFAlphaMaskIndexedProgram); + + if (gDeferredShadowGLTFAlphaMaskIndexedProgram.createShader()) + { + const S32 n = LLGLSLShader::sIndexedGLTFChannels; + gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); + for (S32 s = 0; s < n; ++s) + { // only base color is sampled for the shadow alpha test + gDeferredShadowGLTFAlphaMaskIndexedProgram.uniform1i(LLStaticHashedString(llformat("basecolor%d", s)), s); + } + gDeferredShadowGLTFAlphaMaskIndexedProgram.unbind(); + } + else + { + LL_WARNS("ShaderLoading") << "Indexed PBR shadow alpha mask shader failed to load." << LL_ENDL; + gDeferredShadowGLTFAlphaMaskIndexedProgram.unload(); + } + } + if (success) { gDeferredShadowGLTFAlphaBlendProgram.mName = "Deferred GLTF Shadow Alpha Blend Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 7dc5f13775..43808acc03 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -247,6 +247,7 @@ extern LLGLSLShader gDeferredShadowProgram; extern LLGLSLShader gDeferredShadowCubeProgram; extern LLGLSLShader gDeferredShadowAlphaMaskProgram; extern LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; +extern LLGLSLShader gDeferredShadowGLTFAlphaMaskIndexedProgram; // multi-material indexed extern LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; extern LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; extern LLGLSLShader gDeferredPostProgram; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index f660d2db4c..2c3296caaa 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5252,10 +5252,10 @@ bool can_batch_gltf_material(LLFace* facep) return false; } - if (gltf_mat->mAlphaMode != LLGLTFMaterial::ALPHA_MODE_OPAQUE) - { // Phase 1 batches opaque only. Blend is depth-sorted in PASS_ALPHA; mask - // routes to PASS_GLTF_PBR_ALPHA_MASK which the indexed sweep does not cover, - // so a masked face must never receive a material slot. + if (gltf_mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_BLEND) + { // blend is depth-sorted in PASS_ALPHA, can't be batched across materials. + // Opaque and mask are both eligible; the accumulation keeps each batch to a + // single alpha mode so opaque/mask faces register to their respective passes. return false; } @@ -6485,8 +6485,10 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace const LLGLTFMaterial* mat_slots[8]; U32 slot_count = 0; - const bool anchor_double = facep->getTextureEntry()->getGLTFRenderMaterial()->mDoubleSided; - mat_slots[slot_count++] = facep->getTextureEntry()->getGLTFRenderMaterial(); + const LLGLTFMaterial* anchor_mat = facep->getTextureEntry()->getGLTFRenderMaterial(); + const bool anchor_double = anchor_mat->mDoubleSided; + const U8 anchor_alpha = (U8)anchor_mat->mAlphaMode; + mat_slots[slot_count++] = anchor_mat; facep->setTextureIndex(0); while (i != end_faces) @@ -6494,7 +6496,7 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace facep = *i; if (!can_batch_gltf_material(facep)) - { // not an opaque batchable PBR face -- ends this batch + { // not a batchable PBR face (blend / media / tex-anim) -- ends this batch break; } @@ -6504,6 +6506,12 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace break; } + if ((U8)m->mAlphaMode != anchor_alpha) + { // opaque and mask faces register to different passes -- keep + // each indexed batch to a single alpha mode + break; + } + // find this material's slot, or assign a new one S32 slot = -1; for (U32 s = 0; s < slot_count; ++s) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index ac0aba85e0..30ad4eac40 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -10527,6 +10527,17 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa { mAlphaMaskPool->pushRiggedGLTFBatches(type + 1); } + else if (LLGLSLShader::sIndexedGLTFChannels >= 2 && gDeferredShadowGLTFAlphaMaskIndexedProgram.isComplete()) + { + // multi-material batches alpha-test per-slot; render them with the + // indexed shadow program so batched cutouts stay correct + mAlphaMaskPool->pushGLTFBatchesScalar(type); + + gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); + gDeferredShadowGLTFAlphaMaskIndexedProgram.uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + gDeferredShadowGLTFAlphaMaskIndexedProgram.uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + mAlphaMaskPool->pushGLTFBatchesIndexed(type); + } else { mAlphaMaskPool->pushGLTFBatches(type); From 3314b789801b3ee8e5c1c32dc0586b721841d42b Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 08:02:30 -0400 Subject: [PATCH 3/9] Indexed GLTF PBR batching: phase 3 (rigged) Extend indexed batching to rigged (skinned) meshes -- a multi-material rigged mesh now draws in one indexed call per avatar+skin instead of one per material. - Add HAS_SKIN matrix-palette paths to pbropaqueIndexedV and pbrShadowAlphaMaskIndexedV. - Build rigged variants of both indexed programs via make_rigged_variant; a shared setup_gltf_indexed_samplers() maps each variant's per-slot samplers to units. - Accumulation now runs for rigged face sets too, breaking each batch on avatar / skin-hash change (one matrix palette per skin). - Add pushRiggedGLTFBatchesScalar/Indexed + pushRiggedGLTFBatchIndexed (upload the matrix palette, then the indexed draw); wire the rigged sweep into the opaque pool and the sun-shadow alpha-mask pass for both static and rigged. - Register the skinned indexed variants in the unload list. Co-Authored-By: Claude Opus 4.8 --- .../deferred/pbrShadowAlphaMaskIndexedV.glsl | 18 ++++- .../class1/deferred/pbropaqueIndexedV.glsl | 27 +++++++- indra/newview/lldrawpool.cpp | 60 +++++++++++++++++ indra/newview/lldrawpool.h | 7 ++ indra/newview/lldrawpoolpbropaque.cpp | 12 +++- indra/newview/llviewershadermgr.cpp | 66 +++++++++++++------ indra/newview/llvovolume.cpp | 12 +++- indra/newview/pipeline.cpp | 41 ++++++++---- 8 files changed, 206 insertions(+), 37 deletions(-) diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl index 2a97e93cb1..aba3b7887b 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl @@ -25,9 +25,17 @@ // Indexed (multi-material) variant of pbrShadowAlphaMaskV. The per-vertex material // slot (texture_index) selects this vertex's base-color transform and is forwarded -// to the fragment shader for per-slot alpha testing. +// to the fragment shader for per-slot alpha testing. The HAS_SKIN variant adds +// rigged-mesh matrix-palette skinning. +#if defined(HAS_SKIN) +uniform mat4 modelview_matrix; +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else uniform mat4 modelview_projection_matrix; +#endif + uniform float shadow_target_width; uniform vec4 gltf_basecolor_transform[2*GLTF_INDEXED_CHANNELS]; @@ -46,8 +54,16 @@ out vec2 vary_texcoord0; void main() { +#if defined(HAS_SKIN) + vec4 pre_pos = vec4(position.xyz, 1.0); + mat4 mat = getObjectSkinnedTransform(); + mat = modelview_matrix * mat; + vec4 pos = mat * pre_pos; + pos = projection_matrix * pos; +#else vec4 pre_pos = vec4(position.xyz, 1.0); vec4 pos = modelview_projection_matrix * pre_pos; +#endif target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x; post_pos = pos; diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl index c9df33dbb7..5ff068b3d6 100644 --- a/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl +++ b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl @@ -27,12 +27,18 @@ // One draw call covers up to GLTF_INDEXED_CHANNELS materials. The per-vertex // material slot arrives in the texture_index attribute (packed in position.w); // it selects this vertex's KHR_texture_transform set and is forwarded to the -// fragment shader as vary_material_index. See pbropaqueV.glsl for the -// single-material equivalent. +// fragment shader as vary_material_index. The HAS_SKIN variant adds rigged-mesh +// matrix-palette skinning. See pbropaqueV.glsl for the single-material equivalent. uniform mat4 modelview_matrix; + +#ifdef HAS_SKIN +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else uniform mat3 normal_matrix; uniform mat4 modelview_projection_matrix; +#endif // Per-material KHR_texture_transform, two vec4 (packed scale/rotation/offset) // per slot. Indexed by the material slot (texture_index). @@ -67,8 +73,16 @@ vec4 tangent_space_transform(vec4 vertex_tangent, vec3 vertex_normal, vec4[2] kh void main() { +#ifdef HAS_SKIN + mat4 mat = getObjectSkinnedTransform(); + mat = modelview_matrix * mat; + vec3 pos = (mat * vec4(position.xyz, 1.0)).xyz; + vary_position = pos; + gl_Position = projection_matrix * vec4(pos, 1.0); +#else vary_position = (modelview_matrix * vec4(position.xyz, 1.0)).xyz; gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); +#endif int mi = texture_index; vary_material_index = mi; @@ -87,8 +101,15 @@ void main() metallic_roughness_texcoord = texture_transform(texcoord0, mr, ident); emissive_texcoord = texture_transform(texcoord0, em, ident); - vec3 n = normalize(normal_matrix * normal); +#ifdef HAS_SKIN + vec3 n = (mat * vec4(normal.xyz + position.xyz, 1.0)).xyz - pos.xyz; + vec3 t = (mat * vec4(tangent.xyz + position.xyz, 1.0)).xyz - pos.xyz; +#else + vec3 n = normal_matrix * normal; vec3 t = normal_matrix * tangent.xyz; +#endif + + n = normalize(n); vec4 transformed_tangent = tangent_space_transform(vec4(t, tangent.w), n, nm, ident); vary_tangent = normalize(transformed_tangent.xyz); diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 635f59c799..933ddba0f8 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -1070,3 +1070,63 @@ void LLRenderPass::pushUntexturedRiggedGLTFBatch(LLDrawInfo& params, const LLVOA } } +// rigged counterpart of pushGLTFBatchesScalar -- skips multi-material infos +void LLRenderPass::pushRiggedGLTFBatchesScalar(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + bool skipLastSkin = false; + LLFetchedGLTFMaterial* lastMat = nullptr; + LLViewerTexture* lastTex = nullptr; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mGLTFMaterialList.size() > 1) + { // multi-material batch -- handled by the indexed sweep + continue; + } + + pushRiggedGLTFBatch(params, lastAvatar, lastMeshId, skipLastSkin, lastMat, lastTex); + } +} + +// rigged counterpart of pushGLTFBatchesIndexed -- only multi-material infos. +// Assumes the rigged indexed program is bound. +void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + bool skipLastSkin = false; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mGLTFMaterialList.size() < 2) + { // single-material batch -- handled by the scalar sweep + continue; + } + + pushRiggedGLTFBatchIndexed(params, lastAvatar, lastMeshId, skipLastSkin); + } +} + +// static +void LLRenderPass::pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin) +{ + if (uploadMatrixPalette(params.mAvatar, params.mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + { + pushGLTFBatchIndexed(params); + } +} + diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index cca5b79298..100d3b6514 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -387,6 +387,13 @@ class LLRenderPass : public LLDrawPool void pushRiggedGLTFBatches(U32 type, bool textured); void pushUntexturedRiggedGLTFBatches(U32 type); + // rigged indexed/scalar split (see pushGLTFBatchesScalar/Indexed); each batch + // is one avatar+skin (the accumulation breaks on skin change), so the matrix + // palette is uploaded per draw info as usual. + void pushRiggedGLTFBatchesScalar(U32 type); + void pushRiggedGLTFBatchesIndexed(U32 type); + static void pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin); + // push a single GLTF draw call // lastMat/lastTex track the most recently bound material+media texture so // consecutive draws sharing a material skip the redundant LLFetchedGLTFMaterial::bind diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp index 4db0efc3b1..4fb537778e 100644 --- a/indra/newview/lldrawpoolpbropaque.cpp +++ b/indra/newview/lldrawpoolpbropaque.cpp @@ -73,12 +73,22 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass) } gDeferredPBROpaqueProgram.bind(true); - pushRiggedGLTFBatches(mRenderType + 1); + if (indexed) + { + pushRiggedGLTFBatchesScalar(mRenderType + 1); + } + else + { + pushRiggedGLTFBatches(mRenderType + 1); + } if (indexed) { gDeferredPBROpaqueIndexedProgram.bind(); pushGLTFBatchesIndexed(mRenderType); + + gDeferredPBROpaqueIndexedProgram.bind(true); // rigged variant + pushRiggedGLTFBatchesIndexed(mRenderType + 1); } } diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index a794484e57..44c13a17f6 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -178,6 +178,7 @@ LLGLSLShader gDeferredShadowAlphaMaskProgram; LLGLSLShader gDeferredSkinnedShadowAlphaMaskProgram; LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; LLGLSLShader gDeferredShadowGLTFAlphaMaskIndexedProgram; // multi-material indexed +LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram; LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskProgram; LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; LLGLSLShader gDeferredSkinnedShadowGLTFAlphaBlendProgram; @@ -250,6 +251,7 @@ LLGLSLShader gPBRGlowProgram; LLGLSLShader gPBRGlowSkinnedProgram; LLGLSLShader gDeferredPBROpaqueProgram; LLGLSLShader gDeferredPBROpaqueIndexedProgram; +LLGLSLShader gDeferredSkinnedPBROpaqueIndexedProgram; LLGLSLShader gDeferredSkinnedPBROpaqueProgram; LLGLSLShader gHUDPBRAlphaProgram; LLGLSLShader gDeferredPBRAlphaProgram; @@ -283,6 +285,27 @@ static void add_common_permutations(LLGLSLShader* shader) } } +// Map an indexed GLTF PBR program's per-slot samplers to texture units. Slot s +// uses base color unit s; when full (the GBuffer-write shaders, not the shadow +// alpha-mask shader) it also uses normal N+s, ORM 2N+s and emissive 3N+s. Inactive +// samplers resolve to -1 and are skipped by uniform1i. Safe to call on a program's +// rigged variant too. +static void setup_gltf_indexed_samplers(LLGLSLShader& shader, S32 n, bool full) +{ + shader.bind(); + for (S32 s = 0; s < n; ++s) + { + shader.uniform1i(LLStaticHashedString(llformat("basecolor%d", s)), s); + if (full) + { + shader.uniform1i(LLStaticHashedString(llformat("normalmap%d", s)), n + s); + shader.uniform1i(LLStaticHashedString(llformat("ormmap%d", s)), 2 * n + s); + shader.uniform1i(LLStaticHashedString(llformat("emissivemap%d", s)), 3 * n + s); + } + } + shader.unbind(); +} + #ifdef SHOW_ASSERT // return true if there are no redundant shaders in the given vector // also checks for redundant variants @@ -1092,6 +1115,7 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredSkinnedShadowAlphaMaskProgram.unload(); gDeferredShadowGLTFAlphaMaskProgram.unload(); gDeferredShadowGLTFAlphaMaskIndexedProgram.unload(); + gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram.unload(); gDeferredSkinnedShadowGLTFAlphaMaskProgram.unload(); gDeferredShadowFullbrightAlphaMaskProgram.unload(); gDeferredSkinnedShadowFullbrightAlphaMaskProgram.unload(); @@ -1157,6 +1181,7 @@ bool LLViewerShaderMgr::loadShadersDeferred() gPBRGlowProgram.unload(); gDeferredPBROpaqueProgram.unload(); gDeferredPBROpaqueIndexedProgram.unload(); + gDeferredSkinnedPBROpaqueIndexedProgram.unload(); gDeferredSkinnedPBROpaqueProgram.unload(); gDeferredPBRAlphaProgram.unload(); gDeferredSkinnedPBRAlphaProgram.unload(); @@ -1378,27 +1403,27 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredPBROpaqueIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); add_common_permutations(&gDeferredPBROpaqueIndexedProgram); - bool indexed_ok = gDeferredPBROpaqueIndexedProgram.createShader(); + // rigged (skinned) variant for animesh / avatar attachments + bool indexed_ok = make_rigged_variant(gDeferredPBROpaqueIndexedProgram, gDeferredSkinnedPBROpaqueIndexedProgram); + if (indexed_ok) + { + indexed_ok = gDeferredPBROpaqueIndexedProgram.createShader(); + } + if (indexed_ok) { - // Map each slot's four material samplers to texture units: - // base color s -> s, normal s -> N+s, ORM s -> 2N+s, emissive s -> 3N+s + // Map each slot's four material samplers to texture units, on both the + // static and rigged variants. const S32 n = LLGLSLShader::sIndexedGLTFChannels; - gDeferredPBROpaqueIndexedProgram.bind(); - for (S32 s = 0; s < n; ++s) - { - gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("basecolor%d", s)), s); - gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("normalmap%d", s)), n + s); - gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("ormmap%d", s)), 2 * n + s); - gDeferredPBROpaqueIndexedProgram.uniform1i(LLStaticHashedString(llformat("emissivemap%d", s)), 3 * n + s); - } - gDeferredPBROpaqueIndexedProgram.unbind(); + setup_gltf_indexed_samplers(gDeferredPBROpaqueIndexedProgram, n, true); + setup_gltf_indexed_samplers(gDeferredSkinnedPBROpaqueIndexedProgram, n, true); } else { // Degrade gracefully: route all PBR faces back to the scalar path. LL_WARNS("ShaderLoading") << "Indexed PBR shader failed to load; GLTF batching disabled." << LL_ENDL; gDeferredPBROpaqueIndexedProgram.unload(); + gDeferredSkinnedPBROpaqueIndexedProgram.unload(); LLGLSLShader::sIndexedGLTFChannels = 0; } } @@ -2300,20 +2325,23 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredShadowGLTFAlphaMaskIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); add_common_permutations(&gDeferredShadowGLTFAlphaMaskIndexedProgram); - if (gDeferredShadowGLTFAlphaMaskIndexedProgram.createShader()) + bool shadow_indexed_ok = make_rigged_variant(gDeferredShadowGLTFAlphaMaskIndexedProgram, gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram); + if (shadow_indexed_ok) { + shadow_indexed_ok = gDeferredShadowGLTFAlphaMaskIndexedProgram.createShader(); + } + + if (shadow_indexed_ok) + { // only base color is sampled for the shadow alpha test const S32 n = LLGLSLShader::sIndexedGLTFChannels; - gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); - for (S32 s = 0; s < n; ++s) - { // only base color is sampled for the shadow alpha test - gDeferredShadowGLTFAlphaMaskIndexedProgram.uniform1i(LLStaticHashedString(llformat("basecolor%d", s)), s); - } - gDeferredShadowGLTFAlphaMaskIndexedProgram.unbind(); + setup_gltf_indexed_samplers(gDeferredShadowGLTFAlphaMaskIndexedProgram, n, false); + setup_gltf_indexed_samplers(gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram, n, false); } else { LL_WARNS("ShaderLoading") << "Indexed PBR shadow alpha mask shader failed to load." << LL_ENDL; gDeferredShadowGLTFAlphaMaskIndexedProgram.unload(); + gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram.unload(); } } diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 2c3296caaa..1159ea1844 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -6220,7 +6220,7 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged); geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged); geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged, !rigged && gltf_batch_enabled); + geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged, gltf_batch_enabled); // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) extra_mask |= LLVertexBuffer::MAP_WEIGHT4; @@ -6488,6 +6488,11 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace const LLGLTFMaterial* anchor_mat = facep->getTextureEntry()->getGLTFRenderMaterial(); const bool anchor_double = anchor_mat->mDoubleSided; const U8 anchor_alpha = (U8)anchor_mat->mAlphaMode; + // Rigged batches must be a single avatar+skin -- the matrix palette + // is uploaded per skin. (Null/0 for the static set; the rigged guard + // below keeps it inert there.) + const LLVOAvatar* anchor_avatar = facep->mAvatar; + const U64 anchor_skin = facep->getSkinHash(); mat_slots[slot_count++] = anchor_mat; facep->setTextureIndex(0); @@ -6512,6 +6517,11 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace break; } + if (rigged && (facep->mAvatar != anchor_avatar || facep->getSkinHash() != anchor_skin)) + { // rigged batch is limited to one avatar+skin (matrix palette) + break; + } + // find this material's slot, or assign a new one S32 slot = -1; for (U32 s = 0; s < slot_count; ++s) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 30ad4eac40..55d84e4a2e 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -10523,24 +10523,41 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa U32 type = LLRenderPass::PASS_GLTF_PBR_ALPHA_MASK; + // multi-material batches alpha-test per-slot; render them with the + // indexed shadow program so batched cutouts stay correct + bool gltf_indexed = LLGLSLShader::sIndexedGLTFChannels >= 2 && gDeferredShadowGLTFAlphaMaskIndexedProgram.isComplete(); + if (rigged) { - mAlphaMaskPool->pushRiggedGLTFBatches(type + 1); - } - else if (LLGLSLShader::sIndexedGLTFChannels >= 2 && gDeferredShadowGLTFAlphaMaskIndexedProgram.isComplete()) - { - // multi-material batches alpha-test per-slot; render them with the - // indexed shadow program so batched cutouts stay correct - mAlphaMaskPool->pushGLTFBatchesScalar(type); + if (gltf_indexed) + { + mAlphaMaskPool->pushRiggedGLTFBatchesScalar(type + 1); - gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); - gDeferredShadowGLTFAlphaMaskIndexedProgram.uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); - gDeferredShadowGLTFAlphaMaskIndexedProgram.uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - mAlphaMaskPool->pushGLTFBatchesIndexed(type); + gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(true); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + mAlphaMaskPool->pushRiggedGLTFBatchesIndexed(type + 1); + } + else + { + mAlphaMaskPool->pushRiggedGLTFBatches(type + 1); + } } else { - mAlphaMaskPool->pushGLTFBatches(type); + if (gltf_indexed) + { + mAlphaMaskPool->pushGLTFBatchesScalar(type); + + gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); + LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + mAlphaMaskPool->pushGLTFBatchesIndexed(type); + } + else + { + mAlphaMaskPool->pushGLTFBatches(type); + } } gGL.loadMatrix(gGLModelView); From 37198c59b784991f6b84072bcb12eee37ce483c5 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 08:09:15 -0400 Subject: [PATCH 4/9] Indexed GLTF PBR batching: phase 4 (profiling + shadow bind reduction) - Record materials-per-indexed-batch via LL_PROFILE_ZONE_NUM so the draw-call reduction is visible in Tracy. - Add a base_color_only fast path to pushGLTFBatchIndexed: the shadow alpha-mask pass samples only base color, so skip the normal/ORM/emissive binds and their uniform uploads there (~4x fewer texture binds per indexed shadow batch). Threaded through pushGLTFBatchesIndexed / pushRiggedGLTFBatchesIndexed; the main GBuffer pass still binds the full set. Note: true texture-unit reclaiming (sharing default textures across slots) is not possible with the fixed per-slot sampler switch; it needs bindless / array textures, which remains the documented future direction. NVIDIA if-chain selection was already in place from phase 1. Co-Authored-By: Claude Opus 4.8 --- indra/newview/lldrawpool.cpp | 71 ++++++++++++++++++++++-------------- indra/newview/lldrawpool.h | 8 ++-- indra/newview/pipeline.cpp | 4 +- 3 files changed, 49 insertions(+), 34 deletions(-) diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 933ddba0f8..b23fb2eeb4 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -842,8 +842,8 @@ void LLRenderPass::pushGLTFBatchesScalar(U32 type) } // Renders only the multi-material (indexed) draw infos. Assumes the indexed PBR -// shader is bound. -void LLRenderPass::pushGLTFBatchesIndexed(U32 type) +// shader is bound. base_color_only is forwarded for the shadow alpha-mask pass. +void LLRenderPass::pushGLTFBatchesIndexed(U32 type, bool base_color_only) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; auto* begin = gPipeline.beginRenderMap(type); @@ -858,22 +858,24 @@ void LLRenderPass::pushGLTFBatchesIndexed(U32 type) continue; } - pushGLTFBatchIndexed(params); + pushGLTFBatchIndexed(params, base_color_only); } } // static // Bind one draw call's worth of indexed materials and emit it. Each material slot -// s binds its four maps to texture units [s, N+s, 2N+s, 3N+s] (N == shader's +// s binds its maps to texture units [s, N+s, 2N+s, 3N+s] (N == shader's // sIndexedGLTFChannels) and contributes one element to the per-slot scalar/transform // uniform arrays. Mirrors LLFetchedGLTFMaterial::bind for the default-texture and -// factor handling. -void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params) +// factor handling. base_color_only skips the normal/ORM/emissive maps and their +// uniforms -- used by the shadow alpha-mask pass, which samples only base color. +void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, bool base_color_only) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; const S32 N = LLGLSLShader::sIndexedGLTFChannels; // shader sampler-array stride const S32 n = (S32)params.mGLTFMaterialList.size(); // materials in this batch + LL_PROFILE_ZONE_NUM(n); LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; @@ -901,11 +903,24 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params) double_sided = double_sided || mat->mDoubleSided; LLViewerTexture* base = mat->mBaseColorTexture.notNull() ? mat->mBaseColorTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + gGL.getTexUnit(s)->bindFast(base); + + min_alpha[s] = (mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) ? mat->mAlphaCutoff : -1.f; + + // getPacked() takes F32(&)[8]; copy each transform into its slot stride. + LLGLTFMaterial::TextureTransform::Pack packed; + mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(packed); + memcpy(&bc_xform[8 * s], packed, sizeof(packed)); + + if (base_color_only) + { // shadow alpha-mask samples only base color + continue; + } + LLViewerTexture* norm = (mat->mNormalTexture.notNull() && mat->mNormalTexture->getDiscardLevel() <= 4) ? mat->mNormalTexture.get() : LLViewerFetchedTexture::sFlatNormalImagep.get(); LLViewerTexture* orm = mat->mMetallicRoughnessTexture.notNull() ? mat->mMetallicRoughnessTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); LLViewerTexture* em = mat->mEmissiveTexture.notNull() ? mat->mEmissiveTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); - gGL.getTexUnit(s)->bindFast(base); gGL.getTexUnit(N + s)->bindFast(norm); gGL.getTexUnit(2 * N + s)->bindFast(orm); gGL.getTexUnit(3 * N + s)->bindFast(em); @@ -915,12 +930,7 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params) emissive[3 * s + 0] = mat->mEmissiveColor.mV[0]; emissive[3 * s + 1] = mat->mEmissiveColor.mV[1]; emissive[3 * s + 2] = mat->mEmissiveColor.mV[2]; - min_alpha[s] = (mat->mAlphaMode == LLGLTFMaterial::ALPHA_MODE_MASK) ? mat->mAlphaCutoff : -1.f; - // getPacked() takes F32(&)[8]; copy each transform into its slot stride. - LLGLTFMaterial::TextureTransform::Pack packed; - mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(packed); - memcpy(&bc_xform[8 * s], packed, sizeof(packed)); mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(packed); memcpy(&nm_xform[8 * s], packed, sizeof(packed)); mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(packed); @@ -929,23 +939,28 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params) memcpy(&em_xform[8 * s], packed, sizeof(packed)); } - static const LLStaticHashedString sRoughness("gltf_roughness_factor"); - static const LLStaticHashedString sMetallic("gltf_metallic_factor"); - static const LLStaticHashedString sEmissive("gltf_emissive_color"); static const LLStaticHashedString sMinAlpha("gltf_minimum_alpha"); static const LLStaticHashedString sBcXform("gltf_basecolor_transform"); - static const LLStaticHashedString sNmXform("gltf_normal_transform"); - static const LLStaticHashedString sMrXform("gltf_mr_transform"); - static const LLStaticHashedString sEmXform("gltf_emissive_transform"); - shader->uniform1fv(sRoughness, n, roughness); - shader->uniform1fv(sMetallic, n, metallic); shader->uniform1fv(sMinAlpha, n, min_alpha); - shader->uniform3fv(sEmissive, n, emissive); shader->uniform4fv(sBcXform, 2 * n, bc_xform); - shader->uniform4fv(sNmXform, 2 * n, nm_xform); - shader->uniform4fv(sMrXform, 2 * n, mr_xform); - shader->uniform4fv(sEmXform, 2 * n, em_xform); + + if (!base_color_only) + { + static const LLStaticHashedString sRoughness("gltf_roughness_factor"); + static const LLStaticHashedString sMetallic("gltf_metallic_factor"); + static const LLStaticHashedString sEmissive("gltf_emissive_color"); + static const LLStaticHashedString sNmXform("gltf_normal_transform"); + static const LLStaticHashedString sMrXform("gltf_mr_transform"); + static const LLStaticHashedString sEmXform("gltf_emissive_transform"); + + shader->uniform1fv(sRoughness, n, roughness); + shader->uniform1fv(sMetallic, n, metallic); + shader->uniform3fv(sEmissive, n, emissive); + shader->uniform4fv(sNmXform, 2 * n, nm_xform); + shader->uniform4fv(sMrXform, 2 * n, mr_xform); + shader->uniform4fv(sEmXform, 2 * n, em_xform); + } LLGLDisable cull_face(double_sided ? GL_CULL_FACE : 0); @@ -1098,7 +1113,7 @@ void LLRenderPass::pushRiggedGLTFBatchesScalar(U32 type) // rigged counterpart of pushGLTFBatchesIndexed -- only multi-material infos. // Assumes the rigged indexed program is bound. -void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type) +void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type, bool base_color_only) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; const LLVOAvatar* lastAvatar = nullptr; @@ -1117,16 +1132,16 @@ void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type) continue; } - pushRiggedGLTFBatchIndexed(params, lastAvatar, lastMeshId, skipLastSkin); + pushRiggedGLTFBatchIndexed(params, lastAvatar, lastMeshId, skipLastSkin, base_color_only); } } // static -void LLRenderPass::pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin) +void LLRenderPass::pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin, bool base_color_only) { if (uploadMatrixPalette(params.mAvatar, params.mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) { - pushGLTFBatchIndexed(params); + pushGLTFBatchIndexed(params, base_color_only); } } diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index 100d3b6514..332c93f215 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -372,8 +372,8 @@ class LLRenderPass : public LLDrawPool // pushGLTFBatchesScalar -- renders only single-material infos // pushGLTFBatchesIndexed -- renders only multi-material infos (indexed program bound) void pushGLTFBatchesScalar(U32 type); - void pushGLTFBatchesIndexed(U32 type); - static void pushGLTFBatchIndexed(LLDrawInfo& params); + void pushGLTFBatchesIndexed(U32 type, bool base_color_only = false); + static void pushGLTFBatchIndexed(LLDrawInfo& params, bool base_color_only = false); // like pushGLTFBatches, but will not bind textures or set up texture transforms void pushUntexturedGLTFBatches(U32 type); @@ -391,8 +391,8 @@ class LLRenderPass : public LLDrawPool // is one avatar+skin (the accumulation breaks on skin change), so the matrix // palette is uploaded per draw info as usual. void pushRiggedGLTFBatchesScalar(U32 type); - void pushRiggedGLTFBatchesIndexed(U32 type); - static void pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin); + void pushRiggedGLTFBatchesIndexed(U32 type, bool base_color_only = false); + static void pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin, bool base_color_only = false); // push a single GLTF draw call // lastMat/lastTex track the most recently bound material+media texture so diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 55d84e4a2e..d870000585 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -10536,7 +10536,7 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(true); LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - mAlphaMaskPool->pushRiggedGLTFBatchesIndexed(type + 1); + mAlphaMaskPool->pushRiggedGLTFBatchesIndexed(type + 1, true); // shadow samples base color only } else { @@ -10552,7 +10552,7 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - mAlphaMaskPool->pushGLTFBatchesIndexed(type); + mAlphaMaskPool->pushGLTFBatchesIndexed(type, true); // shadow samples base color only } else { From 9fa7f8b9b5afb3413a98748c5981ff8b8f8c4609 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 08:42:18 -0400 Subject: [PATCH 5/9] Indexed legacy material batching: phase 5a (static) Extend indexed batching to the legacy Blinn-Phong POOL_MATERIALS path (GBuffer write), so multi-material legacy meshes draw in one call per shader mask. Static opaque + alpha-mask, all normal/spec map combinations. Rigged and shadow follow. - New materialIndexed{V,F}.glsl: GBuffer-write only (no forward/atmospherics), N-slot diffuse/normal/spec lookups + per-slot scalar arrays (specular color/glossiness, env intensity, min-alpha, emissive). Per-map texture transforms are baked into the texcoords at buffer build, so no per-slot matrices are needed. - gDeferredMaterialIndexedProgram[] parallels gDeferredMaterialProgram for the non-blend (GBuffer) masks; LLGLSLShader::sIndexedLegacyMaterials gates the feature (independent of PBR so a material-shader failure can't disable it). - LLDrawInfo::mMaterialSlotList holds the per-slot data; genDrawInfo accumulates distinct (diffuse, material) pairs, breaking each batch on shader-mask change (a different mask is a different program); registerFace builds the slot list parallel to the GLTF path. - LLDrawPoolMaterials::renderDeferred skips multi-material infos in the scalar loop and draws them via pushMaterialBatchIndexed (lazy-bound indexed program). Co-Authored-By: Claude Opus 4.8 --- indra/llrender/llglslshader.cpp | 1 + indra/llrender/llglslshader.h | 5 + .../class1/deferred/materialIndexedF.glsl | 291 ++++++++++++++++++ .../class1/deferred/materialIndexedV.glsl | 119 +++++++ indra/newview/lldrawpoolmaterials.cpp | 148 +++++++-- indra/newview/llspatialpartition.h | 17 +- indra/newview/llviewershadermgr.cpp | 88 ++++++ indra/newview/llviewershadermgr.h | 1 + indra/newview/llvovolume.cpp | 224 +++++++++++++- 9 files changed, 863 insertions(+), 31 deletions(-) create mode 100644 indra/newview/app_settings/shaders/class1/deferred/materialIndexedF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/materialIndexedV.glsl diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 0ea8f98f72..3898cd5a47 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -55,6 +55,7 @@ GLuint LLGLSLShader::sCurBoundShader = 0; LLGLSLShader* LLGLSLShader::sCurBoundShaderPtr = NULL; S32 LLGLSLShader::sIndexedTextureChannels = 0; S32 LLGLSLShader::sIndexedGLTFChannels = 0; +bool LLGLSLShader::sIndexedLegacyMaterials = false; U32 LLGLSLShader::sMaxGLTFMaterials = 0; U32 LLGLSLShader::sMaxGLTFNodes = 0; bool LLGLSLShader::sProfileEnabled = false; diff --git a/indra/llrender/llglslshader.h b/indra/llrender/llglslshader.h index a5a5c32dad..ecae11511b 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -177,6 +177,11 @@ class LLGLSLShader // PASS_GLTF_PBR_INDEXED and LLVolumeGeometryManager::genDrawInfo. static S32 sIndexedGLTFChannels; + // True once the indexed legacy (Blinn-Phong) material programs have loaded. + // Gates indexed POOL_MATERIALS batching (shares sIndexedGLTFChannels for the + // per-slot stride); independent so a material-shader failure doesn't disable PBR. + static bool sIndexedLegacyMaterials; + static U32 sMaxGLTFMaterials; static U32 sMaxGLTFNodes; diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialIndexedF.glsl new file mode 100644 index 0000000000..a3c52daca1 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/materialIndexedF.glsl @@ -0,0 +1,291 @@ +/** + * @file materialIndexedF.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/*[EXTRA_CODE_HERE]*/ + +// Indexed (multi-material) variant of the legacy Blinn-Phong material GBuffer write +// (the non-blend path of materialF.glsl). One draw call covers up to +// GLTF_INDEXED_CHANNELS materials selected per-vertex by vary_material_index. Each +// slot owns its diffuse (and, per permutation, normal/spec) sampler bound to units +// [s], [N+s], [2N+s]; per-material scalars arrive as uniform arrays. Forward/alpha +// shading lives in materialF.glsl and is not batched here. + +#define DIFFUSE_ALPHA_MODE_NONE 0 +#define DIFFUSE_ALPHA_MODE_BLEND 1 +#define DIFFUSE_ALPHA_MODE_MASK 2 +#define DIFFUSE_ALPHA_MODE_EMISSIVE 3 + +out vec4 frag_data[4]; + +in vec3 vary_position; +in vec4 vertex_color; +in vec2 vary_texcoord0; +flat in int vary_material_index; + +in vec3 vary_normal; +#ifdef HAS_NORMAL_MAP +in vec3 vary_tangent; +flat in float vary_sign; +in vec2 vary_texcoord1; +#endif +#ifdef HAS_SPECULAR_MAP +in vec2 vary_texcoord2; +#endif + +uniform vec4 mat_specular_color[GLTF_INDEXED_CHANNELS]; // rgb = specular color, a = glossiness/exponent +uniform float mat_env_intensity[GLTF_INDEXED_CHANNELS]; +uniform float mat_emissive_brightness[GLTF_INDEXED_CHANNELS]; // fullbright flag +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_MASK) +uniform float mat_minimum_alpha[GLTF_INDEXED_CHANNELS]; +#endif + +uniform sampler2D diffuse0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D diffuse1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D diffuse2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D diffuse3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D diffuse4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D diffuse5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D diffuse6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D diffuse7; +#endif + +vec4 sample_diffuse(vec2 uv) +{ + if (vary_material_index == 0) return texture(diffuse0, uv); +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(diffuse1, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(diffuse2, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(diffuse3, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(diffuse4, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(diffuse5, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(diffuse6, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(diffuse7, uv); +#endif + return vec4(1, 0, 1, 1); +} + +#ifdef HAS_NORMAL_MAP +uniform sampler2D bump0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D bump1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D bump2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D bump3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D bump4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D bump5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D bump6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D bump7; +#endif + +vec4 sample_bump(vec2 uv) +{ + if (vary_material_index == 0) return texture(bump0, uv); +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(bump1, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(bump2, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(bump3, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(bump4, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(bump5, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(bump6, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(bump7, uv); +#endif + return vec4(0.5, 0.5, 1.0, 1.0); +} +#endif + +#ifdef HAS_SPECULAR_MAP +uniform sampler2D spec0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D spec1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D spec2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D spec3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D spec4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D spec5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D spec6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D spec7; +#endif + +vec4 sample_spec(vec2 uv) +{ + if (vary_material_index == 0) return texture(spec0, uv); +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(spec1, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(spec2, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(spec3, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(spec4, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(spec5, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(spec6, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(spec7, uv); +#endif + return vec4(1.0); +} +#endif + +void mirrorClip(vec3 pos); +vec4 encodeNormal(vec3 n, float env, float gbuffer_flag); + +vec3 getNormal(int mi, inout float glossiness) +{ +#ifdef HAS_NORMAL_MAP + vec4 vNt = sample_bump(vary_texcoord1.xy); + glossiness *= vNt.a; + vNt.xyz = vNt.xyz * 2 - 1; + float sign = vary_sign; + vec3 vN = vary_normal; + vec3 vT = vary_tangent.xyz; + vec3 vB = sign * cross(vN, vT); + vec3 tnorm = normalize( vNt.x * vT + vNt.y * vB + vNt.z * vN ); + return tnorm; +#else + return normalize(vary_normal); +#endif +} + +vec4 getSpecular(int mi) +{ +#ifdef HAS_SPECULAR_MAP + vec4 spec = sample_spec(vary_texcoord2.xy); + spec.rgb *= mat_specular_color[mi].rgb; +#else + vec4 spec = vec4(mat_specular_color[mi].rgb, 1.0); +#endif + return spec; +} + +float getEmissive(int mi, vec4 diffcol) +{ +#if (DIFFUSE_ALPHA_MODE != DIFFUSE_ALPHA_MODE_EMISSIVE) + return mat_emissive_brightness[mi]; +#else + return max(diffcol.a, mat_emissive_brightness[mi]); +#endif +} + +void main() +{ + mirrorClip(vary_position); + + int mi = vary_material_index; + + vec4 diffcol = sample_diffuse(vary_texcoord0.xy); + diffcol.rgb *= vertex_color.rgb; + +#if (DIFFUSE_ALPHA_MODE == DIFFUSE_ALPHA_MODE_MASK) + float bias = 0.001953125; // 1/512, half an 8-bit quantization + if (diffcol.a < mat_minimum_alpha[mi] - bias) + { + discard; + } +#endif + + vec4 spec = getSpecular(mi); + float env = mat_env_intensity[mi] * spec.a; + float glossiness = mat_specular_color[mi].a; + vec3 norm = getNormal(mi, glossiness); + + float emissive = getEmissive(mi, diffcol); + + float flag = GBUFFER_FLAG_HAS_ATMOS; + + frag_data[0] = max(vec4(diffcol.rgb, emissive), vec4(0)); // gbuffer is sRGB for legacy materials + frag_data[1] = max(vec4(spec.rgb, glossiness), vec4(0)); // XYZ = Specular color. W = Specular exponent. + frag_data[2] = encodeNormal(norm, env, flag); // XY = Normal. Z = Env intensity. + +#if defined(HAS_EMISSIVE) + frag_data[3] = vec4(0, 0, 0, 0); +#endif +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialIndexedV.glsl new file mode 100644 index 0000000000..c14427b1ad --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/materialIndexedV.glsl @@ -0,0 +1,119 @@ +/** + * @file materialIndexedV.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) variant of materialV.glsl (GBuffer write only). Forwards +// the per-vertex material slot to the fragment shader. Per-map texture transforms +// are baked into the texcoords at buffer build (indexed faces exclude texture +// animation), so texture_matrix0 is not applied here. HAS_SKIN adds rigged skinning. + +uniform mat4 modelview_matrix; +uniform mat4 projection_matrix; +uniform mat4 modelview_projection_matrix; + +#ifdef HAS_SKIN +mat4 getObjectSkinnedTransform(); +#else +uniform mat3 normal_matrix; +#endif + +out vec3 vary_position; + +in vec3 position; +in vec4 diffuse_color; +in vec3 normal; +in vec2 texcoord0; +in int texture_index; + +flat out int vary_material_index; + +#ifdef HAS_NORMAL_MAP +in vec4 tangent; +in vec2 texcoord1; +out vec3 vary_tangent; +flat out float vary_sign; +out vec3 vary_normal; +out vec2 vary_texcoord1; +#else +out vec3 vary_normal; +#endif + +#ifdef HAS_SPECULAR_MAP +in vec2 texcoord2; +out vec2 vary_texcoord2; +#endif + +out vec4 vertex_color; +out vec2 vary_texcoord0; + +void main() +{ +#ifdef HAS_SKIN + mat4 mat = getObjectSkinnedTransform(); + mat = modelview_matrix * mat; + vec3 pos = (mat * vec4(position.xyz, 1.0)).xyz; + vary_position = pos; + gl_Position = projection_matrix * vec4(pos, 1.0); +#else + gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); +#endif + + vary_material_index = texture_index; + + vary_texcoord0 = texcoord0; +#ifdef HAS_NORMAL_MAP + vary_texcoord1 = texcoord1; +#endif +#ifdef HAS_SPECULAR_MAP + vary_texcoord2 = texcoord2; +#endif + +#ifdef HAS_SKIN + vec3 n = normalize((mat * vec4(normal.xyz + position.xyz, 1.0)).xyz - pos.xyz); +#ifdef HAS_NORMAL_MAP + vec3 t = normalize((mat * vec4(tangent.xyz + position.xyz, 1.0)).xyz - pos.xyz); + vary_tangent = t; + vary_sign = tangent.w; + vary_normal = n; +#else + vary_normal = n; +#endif +#else + vec3 n = normalize(normal_matrix * normal); +#ifdef HAS_NORMAL_MAP + vec3 t = normalize(normal_matrix * tangent.xyz); + vary_tangent = t; + vary_sign = tangent.w; + vary_normal = n; +#else + vary_normal = n; +#endif +#endif + + vertex_color = diffuse_color; + +#if !defined(HAS_SKIN) + vary_position = (modelview_matrix * vec4(position.xyz, 1.0)).xyz; +#endif +} diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp index b330785811..be3ffe8720 100644 --- a/indra/newview/lldrawpoolmaterials.cpp +++ b/indra/newview/lldrawpoolmaterials.cpp @@ -32,6 +32,8 @@ #include "pipeline.h" #include "llglcommonfunc.h" #include "llvoavatar.h" +#include "llviewertexture.h" +#include "llspatialpartition.h" LLDrawPoolMaterials::LLDrawPoolMaterials() : LLRenderPass(LLDrawPool::POOL_MATERIALS) @@ -69,6 +71,113 @@ static const U32 sMaterialPassType[] = LLRenderPass::PASS_NORMSPEC_EMISSIVE, }; +// gDeferredMaterialProgram / gDeferredMaterialIndexedProgram index (shader mask) +// for each of the 12 non-rigged material passes; rigged uses the program's +// mRiggedVariant. Kept in sync with sMaterialPassType above. +static const U32 sMaterialShaderIdx[] = +{ + 0, // PASS_MATERIAL + 2, // PASS_MATERIAL_ALPHA_MASK + 3, // PASS_MATERIAL_ALPHA_EMISSIVE + 4, // PASS_SPECMAP + 6, // PASS_SPECMAP_MASK + 7, // PASS_SPECMAP_EMISSIVE + 8, // PASS_NORMMAP + 10, // PASS_NORMMAP_MASK + 11, // PASS_NORMMAP_EMISSIVE + 12, // PASS_NORMSPEC + 14, // PASS_NORMSPEC_MASK + 15, // PASS_NORMSPEC_EMISSIVE +}; + +// Render the multi-material (indexed) draw infos for one material pass. The indexed +// program is bound lazily on the first such batch (most passes have none). Each slot +// s binds diffuse->unit s, normal->N+s, spec->2N+s and contributes one element to +// the per-slot scalar uniform arrays. +static void pushMaterialBatchIndexed(LLGLSLShader& program, U32 type, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_MATERIAL; + + const S32 N = LLGLSLShader::sIndexedGLTFChannels; + LLGLSLShader* shader = &program; + bool bound = false; + + const LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + bool skipLastSkin = false; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mMaterialSlotList.size() < 2) + { // single-material batch -- handled by the scalar loop + continue; + } + + if (!bound) + { // defer the bind until we actually have an indexed batch this pass + program.bind(); + bound = true; + } + + if (rigged) + { + if (!LLRenderPass::uploadMatrixPalette(params.mAvatar, params.mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + { + continue; + } + } + + const S32 n = (S32)params.mMaterialSlotList.size(); + LL_PROFILE_ZONE_NUM(n); + + F32 spec_color[4 * 8] = { 0.f }; + F32 env[8] = { 0.f }; + F32 min_alpha[8] = { 0.f }; + F32 fullbright[8] = { 0.f }; + + for (S32 s = 0; s < n; ++s) + { + const LLDrawInfo::MaterialSlot& slot = params.mMaterialSlotList[s]; + + LLViewerTexture* diffuse = slot.mDiffuse.notNull() ? slot.mDiffuse.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + LLViewerTexture* normal = slot.mNormalMap.notNull() ? slot.mNormalMap.get() : LLViewerFetchedTexture::sFlatNormalImagep.get(); + LLViewerTexture* spec = slot.mSpecularMap.notNull() ? slot.mSpecularMap.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + + gGL.getTexUnit(s)->bindFast(diffuse); + gGL.getTexUnit(N + s)->bindFast(normal); + gGL.getTexUnit(2 * N + s)->bindFast(spec); + + spec_color[4 * s + 0] = slot.mSpecColor.mV[0]; + spec_color[4 * s + 1] = slot.mSpecColor.mV[1]; + spec_color[4 * s + 2] = slot.mSpecColor.mV[2]; + spec_color[4 * s + 3] = slot.mSpecColor.mV[3]; + env[s] = slot.mEnvIntensity; + min_alpha[s] = slot.mAlphaMaskCutoff; + fullbright[s] = slot.mFullbright; + } + + static const LLStaticHashedString sSpecColor("mat_specular_color"); + static const LLStaticHashedString sEnv("mat_env_intensity"); + static const LLStaticHashedString sMinAlpha("mat_minimum_alpha"); + static const LLStaticHashedString sEmissive("mat_emissive_brightness"); + + shader->uniform4fv(sSpecColor, n, spec_color); + shader->uniform1fv(sEnv, n, env); + shader->uniform1fv(sMinAlpha, n, min_alpha); // inactive outside MASK shaders + shader->uniform1fv(sEmissive, n, fullbright); + + LLRenderPass::applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + } +} + bool LLDrawPoolMaterials::isPassEmpty(S32 pass) { bool rigged = false; @@ -96,27 +205,7 @@ void LLDrawPoolMaterials::beginDeferredPass(S32 pass) rigged = true; pass -= 12; } - U32 shader_idx[] = - { - 0, //LLRenderPass::PASS_MATERIAL, - //1, //LLRenderPass::PASS_MATERIAL_ALPHA, - 2, //LLRenderPass::PASS_MATERIAL_ALPHA_MASK, - 3, //LLRenderPass::PASS_MATERIAL_ALPHA_GLOW, - 4, //LLRenderPass::PASS_SPECMAP, - //5, //LLRenderPass::PASS_SPECMAP_BLEND, - 6, //LLRenderPass::PASS_SPECMAP_MASK, - 7, //LLRenderPass::PASS_SPECMAP_GLOW, - 8, //LLRenderPass::PASS_NORMMAP, - //9, //LLRenderPass::PASS_NORMMAP_BLEND, - 10, //LLRenderPass::PASS_NORMMAP_MASK, - 11, //LLRenderPass::PASS_NORMMAP_GLOW, - 12, //LLRenderPass::PASS_NORMSPEC, - //13, //LLRenderPass::PASS_NORMSPEC_BLEND, - 14, //LLRenderPass::PASS_NORMSPEC_MASK, - 15, //LLRenderPass::PASS_NORMSPEC_GLOW, - }; - - U32 idx = shader_idx[pass]; + U32 idx = sMaterialShaderIdx[pass]; mShader = &(gDeferredMaterialProgram[idx]); @@ -219,6 +308,11 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass) LLCullResult::increment_iterator(i, end); + if (params.mMaterialSlotList.size() > 1) + { // multi-material batch -- drawn by the indexed sweep below + continue; + } + if (specular > -1 && params.mSpecColor != lastSpecular) { lastSpecular = params.mSpecColor; @@ -311,4 +405,16 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass) gGL.matrixMode(LLRender::MM_MODELVIEW); } } + + // Draw the multi-material (indexed) batches for this pass with the indexed + // program. Static only for now (rigged indexed material batches aren't formed + // yet); rigged passes simply have none. + if (!rigged && LLGLSLShader::sIndexedLegacyMaterials) + { + LLGLSLShader& indexed = gDeferredMaterialIndexedProgram[sMaterialShaderIdx[pass]]; + if (indexed.isComplete()) + { + pushMaterialBatchIndexed(indexed, type, false); + } + } } diff --git a/indra/newview/llspatialpartition.h b/indra/newview/llspatialpartition.h index b69f87d27a..b3d2c1c541 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -124,6 +124,21 @@ class alignas(16) LLDrawInfo final : public LLRefCount // single-material path (which uses mGLTFMaterial above). std::vector > mGLTFMaterialList; + // Indexed (multi-material) legacy Blinn-Phong batching: one entry per material + // slot (the texture_index attribute), parallel to the GLTF list above but for + // the POOL_MATERIALS path. Empty unless this is a multi-material legacy batch. + struct MaterialSlot + { + LLPointer mDiffuse; + LLPointer mNormalMap; + LLPointer mSpecularMap; + LLVector4 mSpecColor = LLVector4(1.f, 1.f, 1.f, 0.5f); // XYZ = specular RGB, W = glossiness + F32 mEnvIntensity = 0.f; + F32 mAlphaMaskCutoff = 0.5f; + F32 mFullbright = 0.f; + }; + std::vector mMaterialSlotList; + LLVector4 mSpecColor = LLVector4(1.f, 1.f, 1.f, 0.5f); // XYZ = Specular RGB, W = Specular Exponent std::vector > mTextureList; @@ -676,7 +691,7 @@ class LLVolumeGeometryManager: public LLGeometryManager virtual void rebuildMesh(LLSpatialGroup* group); virtual void getGeometry(LLSpatialGroup* group); virtual void addGeometryCount(LLSpatialGroup* group, U32& vertex_count, U32& index_count); - U32 genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort = false, bool batch_textures = false, bool rigged = false, bool batch_gltf = false); + U32 genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort = false, bool batch_textures = false, bool rigged = false, bool batch_gltf = false, bool batch_legacy = false); void registerFace(LLSpatialGroup* group, LLFace* facep, U32 type); private: diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 44c13a17f6..455559f39f 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -246,6 +246,7 @@ LLGLSLShader gRlvSphereProgram; // Deferred materials shaders LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; +LLGLSLShader gDeferredMaterialIndexedProgram[LLMaterial::SHADER_COUNT*2]; // multi-material indexed (GBuffer masks only) LLGLSLShader gHUDPBROpaqueProgram; LLGLSLShader gPBRGlowProgram; LLGLSLShader gPBRGlowSkinnedProgram; @@ -306,6 +307,27 @@ static void setup_gltf_indexed_samplers(LLGLSLShader& shader, S32 n, bool full) shader.unbind(); } +// Map an indexed legacy material program's per-slot samplers to texture units: +// diffuse slot s -> unit s; normal s -> N+s (HAS_NORMAL_MAP); spec s -> 2N+s +// (HAS_SPECULAR_MAP). Inactive samplers resolve to -1 and are skipped. +static void setup_material_indexed_samplers(LLGLSLShader& shader, S32 n, bool has_normal, bool has_spec) +{ + shader.bind(); + for (S32 s = 0; s < n; ++s) + { + shader.uniform1i(LLStaticHashedString(llformat("diffuse%d", s)), s); + if (has_normal) + { + shader.uniform1i(LLStaticHashedString(llformat("bump%d", s)), n + s); + } + if (has_spec) + { + shader.uniform1i(LLStaticHashedString(llformat("spec%d", s)), 2 * n + s); + } + } + shader.unbind(); +} + #ifdef SHOW_ASSERT // return true if there are no redundant shaders in the given vector // also checks for redundant variants @@ -1175,7 +1197,9 @@ bool LLViewerShaderMgr::loadShadersDeferred() for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) { gDeferredMaterialProgram[i].unload(); + gDeferredMaterialIndexedProgram[i].unload(); } + LLGLSLShader::sIndexedLegacyMaterials = false; gHUDPBROpaqueProgram.unload(); gPBRGlowProgram.unload(); @@ -1367,6 +1391,70 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) + { + // Indexed (multi-material) legacy material GBuffer-write programs, parallel to + // gDeferredMaterialProgram but covering only the non-blend (GBuffer) masks and + // sampling the GBuffer-relevant maps only. Optional: failure leaves + // sIndexedLegacyMaterials false so legacy batching is skipped (the pool falls + // back to scalar). Kept out of the `success` chain. + bool material_indexed_ok = true; + for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2 && material_indexed_ok; ++i) + { + U32 alpha_mode = i & 0x3; + if (alpha_mode == 1) // DIFFUSE_ALPHA_MODE_BLEND -- forward/alpha pool, not indexed + { + continue; + } + + bool has_skin = (i & 0x10) != 0; + bool has_spec = (i & 0x4) != 0; + bool has_normal = (i & 0x8) != 0; + + LLGLSLShader& prog = gDeferredMaterialIndexedProgram[i]; + prog.mName = llformat("Material Indexed Shader %d", i); + prog.mShaderFiles.clear(); + prog.mShaderFiles.push_back(make_pair("deferred/materialIndexedV.glsl", GL_VERTEX_SHADER)); + prog.mShaderFiles.push_back(make_pair("deferred/materialIndexedF.glsl", GL_FRAGMENT_SHADER)); + prog.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + prog.clearPermutations(); + if (has_normal) prog.addPermutation("HAS_NORMAL_MAP", "1"); + if (has_spec) prog.addPermutation("HAS_SPECULAR_MAP", "1"); + prog.addPermutation("DIFFUSE_ALPHA_MODE", llformat("%d", alpha_mode)); + prog.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); + add_common_permutations(&prog); + + if (has_skin) + { + prog.addPermutation("HAS_SKIN", "1"); + prog.mFeatures.hasObjectSkinning = true; + } + else + { + prog.mRiggedVariant = &gDeferredMaterialIndexedProgram[i + 0x10]; + } + + material_indexed_ok = prog.createShader(); + if (material_indexed_ok) + { + setup_material_indexed_samplers(prog, LLGLSLShader::sIndexedGLTFChannels, has_normal, has_spec); + } + } + + if (material_indexed_ok) + { + LLGLSLShader::sIndexedLegacyMaterials = true; + } + else + { + LL_WARNS("ShaderLoading") << "Indexed legacy material shaders failed to load; legacy batching disabled." << LL_ENDL; + for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) + { + gDeferredMaterialIndexedProgram[i].unload(); + } + } + } + if (success) { gDeferredPBROpaqueProgram.mName = "Deferred PBR Opaque Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 43808acc03..432baa406b 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -306,6 +306,7 @@ extern LLGLSLShader gRlvSphereProgram; // Deferred materials shaders extern LLGLSLShader gDeferredMaterialProgram[LLMaterial::SHADER_COUNT*2]; +extern LLGLSLShader gDeferredMaterialIndexedProgram[LLMaterial::SHADER_COUNT*2]; // multi-material indexed (GBuffer masks only) extern LLGLSLShader gHUDPBROpaqueProgram; extern LLGLSLShader gPBRGlowProgram; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 1159ea1844..3b8827f2f6 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5272,6 +5272,65 @@ bool can_batch_gltf_material(LLFace* facep) return true; } +// Whether a face is eligible for indexed (multi-material) legacy Blinn-Phong +// batching. Only faces that register to a POOL_MATERIALS pass qualify, so this +// mirrors the material_pass conditions in genDrawInfo (no fullbright, no blend, no +// legacy emboss bump). Map-presence (normal/spec) and alpha mode are folded into +// the shader mask, which the accumulation uses as a batch-break key. +bool can_batch_legacy_material(LLFace* facep) +{ + if (LLGLSLShader::sIndexedGLTFChannels < 2) + { + return false; + } + + const LLTextureEntry* te = facep->getTextureEntry(); + + if (te->getGLTFRenderMaterial() != nullptr) + { // PBR handled separately + return false; + } + + const LLMaterial* mat = te->getMaterialParams().get(); + if (mat == nullptr) + { // not a legacy material face + return false; + } + + if (mat->getDiffuseAlphaMode() == LLMaterial::DIFFUSE_ALPHA_MODE_BLEND || + te->getColor().mV[3] < 0.999f) + { // blend is depth-sorted in the alpha pool + return false; + } + + if (te->getFullbright()) + { // fullbright materials route to the fullbright passes, not POOL_MATERIALS + return false; + } + + if (te->getBumpmap() && (te->getBumpmap() < 18) && mat->getNormalID().isNull()) + { // legacy emboss bump with no normal map routes to PASS_BUMP + return false; + } + + if (te->hasMedia()) + { + return false; + } + + if (facep->isState(LLFace::TEXTURE_ANIM) && facep->getVirtualSize() > MIN_TEX_ANIM_SIZE) + { // texture animation isn't baked into texcoords -- breaks batches + return false; + } + + if (facep->getTexture() && (facep->getTexture()->getPrimaryFormat() == GL_ALPHA || facep->getTexture()->getPrimaryFormat() == GL_RED)) + { // invisiprim + return false; + } + + return true; +} + const static U32 MAX_FACE_COUNT = 4096U; int32_t LLVolumeGeometryManager::sInstanceCount = 0; LLFace** LLVolumeGeometryManager::sFullbrightFaces[2] = { NULL }; @@ -5469,6 +5528,10 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, // mGLTFMaterialList, parallel to mTextureList for diffuse texture batching. bool gltf_indexed = (gltf_mat != nullptr) && (index < FACE_DO_NOT_BATCH_TEXTURES); + // A legacy Blinn-Phong face carrying a material slot participates in indexed + // batching via mMaterialSlotList (parallel to mGLTFMaterialList for PBR). + bool legacy_indexed = (mat != nullptr) && (index < FACE_DO_NOT_BATCH_TEXTURES); + bool batchable = false; U32 shader_mask = 0xFFFFFFFF; //no shader @@ -5486,6 +5549,35 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, } } + // Build this face's per-slot legacy material data (mirrors the scalar field + // assignment below). Diffuse is per-face; normal/spec/scalars come from the + // material and the face's texture entry. + LLDrawInfo::MaterialSlot legacy_slot; + if (legacy_indexed) + { + legacy_slot.mDiffuse = tex; + legacy_slot.mNormalMap = facep->getViewerObject()->getTENormalMap(facep->getTEOffset()); + legacy_slot.mFullbright = fullbright ? 1.f : 0.f; + legacy_slot.mAlphaMaskCutoff = mat->getAlphaMaskCutoff() * (1.f / 255.f); + + static const float spec_lut[4] = { 0.f, 0.25f, 0.5f, 0.75f }; + float spec_default = spec_lut[shiny & TEM_SHINY_MASK]; + legacy_slot.mSpecColor = LLVector4(spec_default, spec_default, spec_default, spec_default); + legacy_slot.mEnvIntensity = spec_default; + + if (!mat->getSpecularID().isNull()) + { + LLVector4 sc; + sc.mV[0] = mat->getSpecularLightColor().mV[0] * (1.f / 255.f); + sc.mV[1] = mat->getSpecularLightColor().mV[1] * (1.f / 255.f); + sc.mV[2] = mat->getSpecularLightColor().mV[2] * (1.f / 255.f); + sc.mV[3] = mat->getSpecularLightExponent() * (1.f / 255.f); + legacy_slot.mSpecColor = sc; + legacy_slot.mEnvIntensity = mat->getEnvironmentIntensity() * (1.f / 255.f); + legacy_slot.mSpecularMap = facep->getViewerObject()->getTESpecularMap(facep->getTEOffset()); + } + } + if (index < FACE_DO_NOT_BATCH_TEXTURES && idx >= 0) { if (gltf_indexed) @@ -5507,6 +5599,25 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, batchable = true; } } + else if (legacy_indexed) + { //indexed legacy material: batch by material slot (mMaterialSlotList) + if (index < draw_vec[idx]->mMaterialSlotList.size()) + { + if (draw_vec[idx]->mMaterialSlotList[index].mDiffuse.isNull()) + { + batchable = true; + draw_vec[idx]->mMaterialSlotList[index] = legacy_slot; + } + else if (draw_vec[idx]->mMaterialSlotList[index].mDiffuse == tex) + { //this face's material slot can be used with this batch + batchable = true; + } + } + else + { //material slot list can be expanded to fit this slot + batchable = true; + } + } else if (mat || gltf_mat || draw_vec[idx]->mMaterial) { //can't batch textures when materials are present (yet) batchable = false; @@ -5539,13 +5650,13 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, info->mEnd - draw_vec[idx]->mStart + facep->getGeomCount() <= (U32) gGLManager.mGLMaxVertexRange && info->mCount + facep->getIndicesCount() <= (U32) gGLManager.mGLMaxIndexRange && #endif - // indexed GLTF batches deliberately merge different materials, so the - // per-material id differs across the batch -- the material slot list - // (checked via `batchable` above) governs membership instead. - (gltf_indexed || info->mMaterialID == mat_id) && + // indexed batches deliberately merge different materials, so the + // per-material id (and legacy shiny) differs across the batch -- the + // slot list (checked via `batchable` above) governs membership instead. + (gltf_indexed || legacy_indexed || info->mMaterialID == mat_id) && info->mFullbright == fullbright && info->mBump == bump && - (!mat || (info->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different + (!mat || legacy_indexed || (info->mShiny == shiny)) && // need to break batches when a material is shared, but legacy settings are different info->mTextureMatrix == tex_mat && info->mModelMatrix == model_mat && info->mShaderMask == shader_mask && @@ -5563,6 +5674,14 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, } info->mGLTFMaterialList[index] = gltf_mat; } + else if (legacy_indexed) + { + if (index >= info->mMaterialSlotList.size()) + { + info->mMaterialSlotList.resize(index+1); + } + info->mMaterialSlotList[index] = legacy_slot; + } else if (index < FACE_DO_NOT_BATCH_TEXTURES && index >= info->mTextureList.size()) { info->mTextureList.resize(index+1); @@ -5655,6 +5774,11 @@ void LLVolumeGeometryManager::registerFace(LLSpatialGroup* group, LLFace* facep, draw_info->mGLTFMaterialList.resize(index+1); draw_info->mGLTFMaterialList[index] = gltf_mat; } + else if (legacy_indexed) + { //initialize material slot list for indexed legacy batching + draw_info->mMaterialSlotList.resize(index+1); + draw_info->mMaterialSlotList[index] = legacy_slot; + } else if (index < FACE_DO_NOT_BATCH_TEXTURES) { //initialize texture list for texture batching draw_info->mTextureList.resize(index+1); @@ -6206,6 +6330,9 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) // disabled by setting. Applied to static opaque PBR faces only (see below). static LLCachedControl gltf_batching(gSavedSettings, "RenderGLTFPBRBatching", true); bool gltf_batch_enabled = gltf_batching && LLGLSLShader::sIndexedGLTFChannels >= 2; + // Legacy (Blinn-Phong) indexed batching shares the toggle and per-slot stride, + // but also requires its own programs to have loaded. + bool legacy_batch_enabled = gltf_batch_enabled && LLGLSLShader::sIndexedLegacyMaterials; // generate render batches for static geometry U32 extra_mask = LLVertexBuffer::MAP_TEXTURE_INDEX; @@ -6217,9 +6344,9 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[i], fullbright_count[i], false, batch_textures, rigged); geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[i], alpha_count[i], alpha_sort, batch_textures, rigged); geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[i], bump_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged); + geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged, false, !rigged && legacy_batch_enabled); + geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged, false, !rigged && legacy_batch_enabled); + geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged, false, !rigged && legacy_batch_enabled); geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged, gltf_batch_enabled); // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) @@ -6392,7 +6519,7 @@ struct CompareBatchBreakerRigged } }; -U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort, bool batch_textures, bool rigged, bool batch_gltf) +U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace** faces, U32 face_count, bool distance_sort, bool batch_textures, bool rigged, bool batch_gltf, bool batch_legacy) { LL_PROFILE_ZONE_SCOPED_CATEGORY_VOLUME; @@ -6570,6 +6697,85 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace } } } + else if (batch_legacy && !hud_group && can_batch_legacy_material(facep)) + { + // Indexed (multi-material) legacy Blinn-Phong: accumulate distinct + // (diffuse, material) pairs into one batch. Faces must share the same + // shader mask (== gDeferredMaterialProgram index, i.e. the bound + // program), so the mask is a hard batch-break key. + const S32 gltf_channels = llmin((S32)LLGLSLShader::sIndexedGLTFChannels, 8); + LLViewerTexture* diffuse_slots[8]; + LLMaterial* mat_slots[8]; + U32 slot_count = 0; + + LLMaterial* anchor_mat = facep->getTextureEntry()->getMaterialParams().get(); + const U32 anchor_mask = anchor_mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, false); + + diffuse_slots[0] = facep->getTexture(); + mat_slots[0] = anchor_mat; + slot_count = 1; + facep->setTextureIndex(0); + + while (i != end_faces) + { + facep = *i; + + if (!can_batch_legacy_material(facep)) + { + break; + } + + LLMaterial* m = facep->getTextureEntry()->getMaterialParams().get(); + if (m->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, false) != anchor_mask) + { // different program -- can't share a draw call + break; + } + + LLViewerTexture* d = facep->getTexture(); + S32 slot = -1; + for (U32 s = 0; s < slot_count; ++s) + { + if (mat_slots[s] == m && diffuse_slots[s] == d) + { + slot = (S32)s; + break; + } + } + if (slot < 0) + { + if ((S32)slot_count >= gltf_channels) + { + break; + } + slot = (S32)slot_count; + diffuse_slots[slot_count] = d; + mat_slots[slot_count] = m; + slot_count++; + } + + if (geom_count + facep->getGeomCount() > max_vertices) + { + break; + } + + ++i; + index_count += facep->getIndicesCount(); + geom_count += facep->getGeomCount(); + + flexi = flexi || facep->getViewerObject()->getVolume()->isUnique(); + + facep->setTextureIndex((U8)slot); + } + + if (slot_count < 2) + { // single material -- fall back to the scalar material path + for (LLFace** f = face_iter; f != i; ++f) + { + (*f)->mDrawInfo = NULL; + (*f)->setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES); + } + } + } else if (batch_textures) { U8 cur_tex = 0; From 1df8e0a7abb44398462d323a00ec7ecbfd7fc7e1 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 09:27:32 -0400 Subject: [PATCH 6/9] Indexed legacy material batching: phase 5b+5c (rigged+shadows) --- .../deferred/materialShadowIndexedF.glsl | 99 +++++++++++++++++++ .../deferred/materialShadowIndexedV.glsl | 70 +++++++++++++ indra/newview/lldrawpool.cpp | 61 ++++++++++++ indra/newview/lldrawpool.h | 4 + indra/newview/lldrawpoolmaterials.cpp | 10 +- indra/newview/llviewershadermgr.cpp | 38 +++++++ indra/newview/llviewershadermgr.h | 1 + indra/newview/llvovolume.cpp | 14 ++- indra/newview/pipeline.cpp | 12 +++ 9 files changed, 301 insertions(+), 8 deletions(-) create mode 100644 indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedV.glsl diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedF.glsl new file mode 100644 index 0000000000..d4dbce90e2 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedF.glsl @@ -0,0 +1,99 @@ +/** + * @file materialShadowIndexedF.glsl + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) shadow alpha-mask fragment shader for legacy materials. +// Each material slot owns a diffuse sampler bound by the CPU to unit s and its own +// alpha cutoff, matching the scalar material shadow (which sets minimum_alpha per +// batch from mAlphaMaskCutoff). + +out vec4 frag_color; + +flat in int vary_material_index; +in vec4 post_pos; +in float target_pos_x; +in vec4 vertex_color; +in vec2 vary_texcoord0; + +uniform float mat_minimum_alpha[GLTF_INDEXED_CHANNELS]; + +uniform sampler2D diffuse0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D diffuse1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D diffuse2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D diffuse3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D diffuse4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D diffuse5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D diffuse6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D diffuse7; +#endif + +float sample_diffuse_alpha(vec2 uv) +{ + if (vary_material_index == 0) return texture(diffuse0, uv).a; +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(diffuse1, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(diffuse2, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(diffuse3, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(diffuse4, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(diffuse5, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(diffuse6, uv).a; +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(diffuse7, uv).a; +#endif + return 1.0; +} + +void main() +{ + if (sample_diffuse_alpha(vary_texcoord0.xy) < mat_minimum_alpha[vary_material_index]) + { + discard; + } + + frag_color = vec4(1, 1, 1, 1); +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedV.glsl new file mode 100644 index 0000000000..4fe8095270 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/materialShadowIndexedV.glsl @@ -0,0 +1,70 @@ +/** + * @file materialShadowIndexedV.glsl + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) shadow alpha-mask vertex shader for legacy materials. +// Forwards the per-vertex material slot and the (baked) diffuse texcoord for the +// per-slot alpha test. HAS_SKIN adds rigged skinning. + +#if defined(HAS_SKIN) +uniform mat4 modelview_matrix; +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else +uniform mat4 modelview_projection_matrix; +#endif + +uniform float shadow_target_width; + +in vec3 position; +in vec4 diffuse_color; +in vec2 texcoord0; +in int texture_index; + +flat out int vary_material_index; +out vec4 post_pos; +out float target_pos_x; +out vec4 vertex_color; +out vec2 vary_texcoord0; + +void main() +{ +#if defined(HAS_SKIN) + vec4 pre_pos = vec4(position.xyz, 1.0); + mat4 mat = getObjectSkinnedTransform(); + mat = modelview_matrix * mat; + vec4 pos = mat * pre_pos; + pos = projection_matrix * pos; +#else + vec4 pos = modelview_projection_matrix * vec4(position.xyz, 1.0); +#endif + + target_pos_x = 0.5 * (shadow_target_width - 1.0) * pos.x; + post_pos = pos; + gl_Position = pos; + + vary_material_index = texture_index; + vary_texcoord0 = texcoord0; // per-map transform baked at buffer build + vertex_color = diffuse_color; +} diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index b23fb2eeb4..a593794045 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -522,6 +522,10 @@ void LLRenderPass::pushMaskBatches(U32 type, bool texture, bool batch_textures) { LLDrawInfo* pparams = *i; LLCullResult::increment_iterator(i, end); + if (pparams->mMaterialSlotList.size() > 1) + { // multi-material legacy batch -- drawn by pushMaskBatchesIndexed + continue; + } LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); pushBatch(*pparams, texture, batch_textures); } @@ -543,6 +547,11 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text llassert(pparams); + if (pparams->mMaterialSlotList.size() > 1) + { // multi-material legacy batch -- drawn by pushMaskBatchesIndexed + continue; + } + LLGLSLShader::sCurBoundShaderPtr->setMinimumAlpha(pparams->mAlphaMaskCutoff); if (uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) @@ -552,6 +561,58 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text } } +void LLRenderPass::pushMaskBatchesIndexed(U32 type, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const S32 N = LLGLSLShader::sIndexedGLTFChannels; + LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; + + const LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + bool skipLastSkin = false; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mMaterialSlotList.size() < 2) + { + continue; + } + + if (rigged) + { + if (!uploadMatrixPalette(params.mAvatar, params.mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + { + continue; + } + } + + const S32 n = (S32)params.mMaterialSlotList.size(); + LL_PROFILE_ZONE_NUM(n); + + F32 min_alpha[8] = { 0.f }; + for (S32 s = 0; s < n; ++s) + { + const LLDrawInfo::MaterialSlot& slot = params.mMaterialSlotList[s]; + LLViewerTexture* diffuse = slot.mDiffuse.notNull() ? slot.mDiffuse.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + gGL.getTexUnit(s)->bindFast(diffuse); + min_alpha[s] = slot.mAlphaMaskCutoff; + } + + static const LLStaticHashedString sMinAlpha("mat_minimum_alpha"); + shader->uniform1fv(sMinAlpha, n, min_alpha); + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + } +} + void LLRenderPass::applyModelMatrix(const LLDrawInfo& params) { applyModelMatrix(params.mModelMatrix); diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index 332c93f215..e658642b69 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -404,6 +404,10 @@ class LLRenderPass : public LLDrawPool void pushMaskBatches(U32 type, bool texture = true, bool batch_textures = false); void pushRiggedMaskBatches(U32 type, bool texture = true, bool batch_textures = false); + // indexed (multi-material) legacy material shadow alpha-mask: binds per-slot + // diffuse + per-slot cutoff array, then draws. Assumes the indexed material + // shadow program is bound. + void pushMaskBatchesIndexed(U32 type, bool rigged); void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); void pushUntexturedBatch(LLDrawInfo& params); void pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp index be3ffe8720..45129a869b 100644 --- a/indra/newview/lldrawpoolmaterials.cpp +++ b/indra/newview/lldrawpoolmaterials.cpp @@ -407,14 +407,14 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass) } // Draw the multi-material (indexed) batches for this pass with the indexed - // program. Static only for now (rigged indexed material batches aren't formed - // yet); rigged passes simply have none. - if (!rigged && LLGLSLShader::sIndexedLegacyMaterials) + // program (its rigged variant for the rigged passes). + if (LLGLSLShader::sIndexedLegacyMaterials) { LLGLSLShader& indexed = gDeferredMaterialIndexedProgram[sMaterialShaderIdx[pass]]; - if (indexed.isComplete()) + LLGLSLShader* prog = rigged ? indexed.mRiggedVariant : &indexed; + if (prog && prog->isComplete()) { - pushMaterialBatchIndexed(indexed, type, false); + pushMaterialBatchIndexed(*prog, type, rigged); } } } diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 455559f39f..798cdacf69 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -180,6 +180,8 @@ LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; LLGLSLShader gDeferredShadowGLTFAlphaMaskIndexedProgram; // multi-material indexed LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram; LLGLSLShader gDeferredSkinnedShadowGLTFAlphaMaskProgram; +LLGLSLShader gDeferredShadowMaterialIndexedProgram; // multi-material indexed legacy mask shadow +LLGLSLShader gDeferredSkinnedShadowMaterialIndexedProgram; LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; LLGLSLShader gDeferredSkinnedShadowGLTFAlphaBlendProgram; LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; @@ -1138,6 +1140,8 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredShadowGLTFAlphaMaskProgram.unload(); gDeferredShadowGLTFAlphaMaskIndexedProgram.unload(); gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram.unload(); + gDeferredShadowMaterialIndexedProgram.unload(); + gDeferredSkinnedShadowMaterialIndexedProgram.unload(); gDeferredSkinnedShadowGLTFAlphaMaskProgram.unload(); gDeferredShadowFullbrightAlphaMaskProgram.unload(); gDeferredSkinnedShadowFullbrightAlphaMaskProgram.unload(); @@ -2433,6 +2437,40 @@ bool LLViewerShaderMgr::loadShadersDeferred() } } + if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) + { + // Indexed (multi-material) legacy material shadow alpha mask, so batched + // masked legacy faces alpha-test per-slot in the shadow map. Optional: a + // failure leaves the program incomplete and the shadow split falls back to + // scalar (slightly wrong cutouts, no crash). Kept out of `success`. + gDeferredShadowMaterialIndexedProgram.mName = "Deferred Material Shadow Indexed Shader"; + gDeferredShadowMaterialIndexedProgram.mShaderFiles.clear(); + gDeferredShadowMaterialIndexedProgram.mShaderFiles.push_back(make_pair("deferred/materialShadowIndexedV.glsl", GL_VERTEX_SHADER)); + gDeferredShadowMaterialIndexedProgram.mShaderFiles.push_back(make_pair("deferred/materialShadowIndexedF.glsl", GL_FRAGMENT_SHADER)); + gDeferredShadowMaterialIndexedProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredShadowMaterialIndexedProgram.clearPermutations(); + gDeferredShadowMaterialIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); + + bool mat_shadow_ok = make_rigged_variant(gDeferredShadowMaterialIndexedProgram, gDeferredSkinnedShadowMaterialIndexedProgram); + if (mat_shadow_ok) + { + mat_shadow_ok = gDeferredShadowMaterialIndexedProgram.createShader(); + } + + if (mat_shadow_ok) + { // only diffuse is sampled for the shadow alpha test + const S32 n = LLGLSLShader::sIndexedGLTFChannels; + setup_material_indexed_samplers(gDeferredShadowMaterialIndexedProgram, n, false, false); + setup_material_indexed_samplers(gDeferredSkinnedShadowMaterialIndexedProgram, n, false, false); + } + else + { + LL_WARNS("ShaderLoading") << "Indexed legacy material shadow shader failed to load." << LL_ENDL; + gDeferredShadowMaterialIndexedProgram.unload(); + gDeferredSkinnedShadowMaterialIndexedProgram.unload(); + } + } + if (success) { gDeferredShadowGLTFAlphaBlendProgram.mName = "Deferred GLTF Shadow Alpha Blend Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 432baa406b..822c8f2466 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -248,6 +248,7 @@ extern LLGLSLShader gDeferredShadowCubeProgram; extern LLGLSLShader gDeferredShadowAlphaMaskProgram; extern LLGLSLShader gDeferredShadowGLTFAlphaMaskProgram; extern LLGLSLShader gDeferredShadowGLTFAlphaMaskIndexedProgram; // multi-material indexed +extern LLGLSLShader gDeferredShadowMaterialIndexedProgram; // multi-material indexed legacy mask shadow extern LLGLSLShader gDeferredShadowGLTFAlphaBlendProgram; extern LLGLSLShader gDeferredShadowFullbrightAlphaMaskProgram; extern LLGLSLShader gDeferredPostProgram; diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index 3b8827f2f6..f40260c0f2 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -6344,9 +6344,9 @@ void LLVolumeGeometryManager::rebuildGeom(LLSpatialGroup* group) geometryBytes += genDrawInfo(group, fullbright_mask | extra_mask, sFullbrightFaces[i], fullbright_count[i], false, batch_textures, rigged); geometryBytes += genDrawInfo(group, alpha_mask | extra_mask, sAlphaFaces[i], alpha_count[i], alpha_sort, batch_textures, rigged); geometryBytes += genDrawInfo(group, bump_mask | extra_mask, sBumpFaces[i], bump_count[i], false, false, rigged); - geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged, false, !rigged && legacy_batch_enabled); - geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged, false, !rigged && legacy_batch_enabled); - geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged, false, !rigged && legacy_batch_enabled); + geometryBytes += genDrawInfo(group, norm_mask | extra_mask, sNormFaces[i], norm_count[i], false, false, rigged, false, legacy_batch_enabled); + geometryBytes += genDrawInfo(group, spec_mask | extra_mask, sSpecFaces[i], spec_count[i], false, false, rigged, false, legacy_batch_enabled); + geometryBytes += genDrawInfo(group, normspec_mask | extra_mask, sNormSpecFaces[i], normspec_count[i], false, false, rigged, false, legacy_batch_enabled); geometryBytes += genDrawInfo(group, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged, gltf_batch_enabled); // for rigged set, add weights and disable alpha sorting (rigged items use depth buffer) @@ -6710,6 +6710,9 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace LLMaterial* anchor_mat = facep->getTextureEntry()->getMaterialParams().get(); const U32 anchor_mask = anchor_mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, false); + // rigged batches are one avatar+skin (matrix palette per skin) + const LLVOAvatar* anchor_avatar = facep->mAvatar; + const U64 anchor_skin = facep->getSkinHash(); diffuse_slots[0] = facep->getTexture(); mat_slots[0] = anchor_mat; @@ -6731,6 +6734,11 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace break; } + if (rigged && (facep->mAvatar != anchor_avatar || facep->getSkinHash() != anchor_skin)) + { // rigged batch is limited to one avatar+skin (matrix palette) + break; + } + LLViewerTexture* d = facep->getTexture(); S32 slot = -1; for (U32 s = 0; s < slot_count; ++s) diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index d870000585..3444890f34 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -10507,6 +10507,18 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa renderMaskedObjects(LLRenderPass::PASS_MATERIAL_ALPHA_MASK, true, false, rigged); renderMaskedObjects(LLRenderPass::PASS_SPECMAP_MASK, true, false, rigged); renderMaskedObjects(LLRenderPass::PASS_NORMMAP_MASK, true, false, rigged); + + // multi-material indexed legacy mask batches alpha-test per-slot + if (LLGLSLShader::sIndexedLegacyMaterials && gDeferredShadowMaterialIndexedProgram.isComplete()) + { + gDeferredShadowMaterialIndexedProgram.bind(rigged); + LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); + U32 off = rigged ? 1 : 0; + mAlphaMaskPool->pushMaskBatchesIndexed(LLRenderPass::PASS_NORMSPEC_MASK + off, rigged); + mAlphaMaskPool->pushMaskBatchesIndexed(LLRenderPass::PASS_MATERIAL_ALPHA_MASK + off, rigged); + mAlphaMaskPool->pushMaskBatchesIndexed(LLRenderPass::PASS_SPECMAP_MASK + off, rigged); + mAlphaMaskPool->pushMaskBatchesIndexed(LLRenderPass::PASS_NORMMAP_MASK + off, rigged); + } } } } From c27f97c1a2e3eb9d429e030eef409d7561caeb5e Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 14:50:09 -0400 Subject: [PATCH 7/9] Indexed legacy material batching: phase 5 fixups Follow-ups to the 5b+5c commit: - Drop an unused local in pushMaskBatchesIndexed (would trip -Wunused-variable). - Gate the indexed legacy shadow program on sIndexedLegacyMaterials and disable legacy batching entirely if it fails to load -- otherwise indexed mask batches would form but cast no shadow (skipped by the scalar pass, no indexed sweep). Co-Authored-By: Claude Opus 4.8 --- indra/newview/lldrawpool.cpp | 1 - indra/newview/llviewershadermgr.cpp | 12 +++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index a593794045..18a6583ba1 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -564,7 +564,6 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text void LLRenderPass::pushMaskBatchesIndexed(U32 type, bool rigged) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; - const S32 N = LLGLSLShader::sIndexedGLTFChannels; LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; const LLVOAvatar* lastAvatar = nullptr; diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index 798cdacf69..c2d5ca3e8c 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -2437,12 +2437,13 @@ bool LLViewerShaderMgr::loadShadersDeferred() } } - if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) + if (success && LLGLSLShader::sIndexedLegacyMaterials) { // Indexed (multi-material) legacy material shadow alpha mask, so batched - // masked legacy faces alpha-test per-slot in the shadow map. Optional: a - // failure leaves the program incomplete and the shadow split falls back to - // scalar (slightly wrong cutouts, no crash). Kept out of `success`. + // masked legacy faces alpha-test per-slot in the shadow map. Required when + // legacy batching is on: a failure here would leave indexed mask batches + // casting no shadow (skipped by the scalar pass, no indexed sweep), so on + // failure we disable legacy batching entirely rather than degrade silently. gDeferredShadowMaterialIndexedProgram.mName = "Deferred Material Shadow Indexed Shader"; gDeferredShadowMaterialIndexedProgram.mShaderFiles.clear(); gDeferredShadowMaterialIndexedProgram.mShaderFiles.push_back(make_pair("deferred/materialShadowIndexedV.glsl", GL_VERTEX_SHADER)); @@ -2465,9 +2466,10 @@ bool LLViewerShaderMgr::loadShadersDeferred() } else { - LL_WARNS("ShaderLoading") << "Indexed legacy material shadow shader failed to load." << LL_ENDL; + LL_WARNS("ShaderLoading") << "Indexed legacy material shadow shader failed to load; legacy batching disabled." << LL_ENDL; gDeferredShadowMaterialIndexedProgram.unload(); gDeferredSkinnedShadowMaterialIndexedProgram.unload(); + LLGLSLShader::sIndexedLegacyMaterials = false; // can't shadow indexed batches -- don't form them } } From e6f7cac7c83c6b2da918bb0d0a36cb5097fe1d29 Mon Sep 17 00:00:00 2001 From: Rye Date: Mon, 15 Jun 2026 17:44:57 -0400 Subject: [PATCH 8/9] Indexed material batching: phase 6 (glow/emissive) Extend indexed multi-material batching to the glow/emissive passes. Multi-material batches whose faces have glow>0 were registered into the glow passes as multi-material draw infos (registerFace keys its indexed paths on slot index, not pass type) but rendered scalar with slot 0's emissive/diffuse, giving the wrong glow on non-slot-0 faces. PBR glow (PASS_GLTF_GLOW +rigged): split the sweep in LLDrawPoolGLTFPBR::renderPostDeferred into scalar (skips multi-material) and indexed under new gPBRGlowIndexedProgram(+Skinned), reusing pushGLTFBatchIndexed on the shared GBuffer per-slot unit layout (base color s, emissive 3N+s). Legacy glow (PASS_GLOW +rigged): new pushEmissiveBatchesScalar / pushEmissiveBatchesIndexed; indexed binds each slot's diffuse to unit s under new gDeferredEmissiveIndexedProgram(+Skinned). Plain texture-index glow still goes through the stock diffuseLookup program. New shaders pbrglowIndexed{V,F}.glsl, emissiveIndexed{V,F}.glsl. Both new programs are gated and kept out of the success chain: on load failure the pass falls back to the pre-batching scalar behavior, never disabling the core feature. pushGLTFBatchIndexed's bool base_color_only becomes an eGLTFIndexedMaps enum {FULL, BASE_COLOR, GLOW}: the glow pass binds base color + emissive only, skipping the normal/ORM binds, the normal discard-level fetch and the roughness/metallic/normal/MR uniform uploads it never samples. Robustness: a PBR-opaque-indexed shader load failure (which zeroes sIndexedGLTFChannels) now also unloads the legacy material indexed programs and clears sIndexedLegacyMaterials, preserving the invariant sIndexedLegacyMaterials => sIndexedGLTFChannels >= 2 so the whole indexed feature (PBR, legacy, glow, shadows) disables together. Co-Authored-By: Claude Opus 4.8 --- .../class1/deferred/emissiveIndexedF.glsl | 94 +++++++++++ .../class1/deferred/emissiveIndexedV.glsl | 63 ++++++++ .../class1/deferred/pbrglowIndexedF.glsl | 146 ++++++++++++++++++ .../class1/deferred/pbrglowIndexedV.glsl | 84 ++++++++++ indra/newview/lldrawpool.cpp | 139 ++++++++++++++--- indra/newview/lldrawpool.h | 26 +++- indra/newview/lldrawpoolpbropaque.cpp | 33 +++- indra/newview/lldrawpoolsimple.cpp | 34 +++- indra/newview/llviewershadermgr.cpp | 91 +++++++++++ indra/newview/llviewershadermgr.h | 2 + indra/newview/pipeline.cpp | 4 +- 11 files changed, 683 insertions(+), 33 deletions(-) create mode 100644 indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedV.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedF.glsl create mode 100644 indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedV.glsl diff --git a/indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedF.glsl new file mode 100644 index 0000000000..62400c635c --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedF.glsl @@ -0,0 +1,94 @@ +/** + * @file emissiveIndexedF.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/*[EXTRA_CODE_HERE]*/ + +// Indexed (multi-material) variant of emissiveF.glsl (legacy glow). Only alpha is +// written. vary_material_index selects this primitive's diffuse map (bound to unit +// s); its alpha modulates the per-vertex glow factor. If-chains (not switch) are +// used for driver portability. + +out vec4 frag_color; + +in vec4 vertex_color; +in vec2 vary_texcoord0; +flat in int vary_material_index; + +uniform sampler2D diffuse0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D diffuse1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D diffuse2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D diffuse3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D diffuse4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D diffuse5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D diffuse6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D diffuse7; +#endif + +vec4 sample_diffuse(vec2 uv) +{ + if (vary_material_index == 0) return texture(diffuse0, uv); +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(diffuse1, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(diffuse2, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(diffuse3, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(diffuse4, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(diffuse5, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(diffuse6, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(diffuse7, uv); +#endif + return vec4(1, 0, 1, 1); +} + +void main() +{ + // NOTE: when this shader is used, only alpha is being written to + float a = sample_diffuse(vary_texcoord0.xy).a * vertex_color.a; + frag_color = max(vec4(0, 0, 0, a), vec4(0)); +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedV.glsl new file mode 100644 index 0000000000..ca2c33142f --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/emissiveIndexedV.glsl @@ -0,0 +1,63 @@ +/** + * @file emissiveIndexedV.glsl + * + * $LicenseInfo:firstyear=2007&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2007, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) variant of emissiveV.glsl (legacy glow). Forwards the +// per-vertex material slot to the fragment shader so each slot's diffuse alpha can +// be sampled. The diffuse texture transform is baked into texcoord0 at buffer build +// (indexed faces exclude texture animation), so texture_matrix0 is not applied. +// HAS_SKIN adds rigged matrix-palette skinning. + +#ifdef HAS_SKIN +uniform mat4 modelview_matrix; +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else +uniform mat4 modelview_projection_matrix; +#endif + +in vec3 position; +in vec4 emissive; +in vec2 texcoord0; +in int texture_index; + +flat out int vary_material_index; +out vec4 vertex_color; +out vec2 vary_texcoord0; + +void main() +{ +#ifdef HAS_SKIN + mat4 mat = getObjectSkinnedTransform(); + mat = modelview_matrix * mat; + vec3 pos = (mat*vec4(position.xyz,1.0)).xyz; + gl_Position = projection_matrix*vec4(pos,1.0); +#else + gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); +#endif + + vary_material_index = texture_index; + vary_texcoord0 = texcoord0; + vertex_color = emissive; +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedF.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedF.glsl new file mode 100644 index 0000000000..85f6e797d4 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedF.glsl @@ -0,0 +1,146 @@ +/** + * @file pbrglowIndexedF.glsl + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +/*[EXTRA_CODE_HERE]*/ + +// Indexed (multi-material) PBR glow/emissive fragment shader. vary_material_index +// selects this primitive's material slot; each slot binds its base-color map to +// unit s and its emissive map to unit 3N+s (N == GLTF_INDEXED_CHANNELS), matching +// the GBuffer indexed layout so pushGLTFBatchIndexed can drive both. If-chains +// (not switch) are used for driver portability. See pbrglowF.glsl for the +// single-material equivalent. + +uniform vec3 gltf_emissive_color[GLTF_INDEXED_CHANNELS]; +uniform float gltf_minimum_alpha[GLTF_INDEXED_CHANNELS]; // PBR alphaMode MASK cutoff, -1 for opaque + +uniform sampler2D basecolor0; // always in sRGB space +uniform sampler2D emissivemap0; +#if GLTF_INDEXED_CHANNELS > 1 +uniform sampler2D basecolor1; uniform sampler2D emissivemap1; +#endif +#if GLTF_INDEXED_CHANNELS > 2 +uniform sampler2D basecolor2; uniform sampler2D emissivemap2; +#endif +#if GLTF_INDEXED_CHANNELS > 3 +uniform sampler2D basecolor3; uniform sampler2D emissivemap3; +#endif +#if GLTF_INDEXED_CHANNELS > 4 +uniform sampler2D basecolor4; uniform sampler2D emissivemap4; +#endif +#if GLTF_INDEXED_CHANNELS > 5 +uniform sampler2D basecolor5; uniform sampler2D emissivemap5; +#endif +#if GLTF_INDEXED_CHANNELS > 6 +uniform sampler2D basecolor6; uniform sampler2D emissivemap6; +#endif +#if GLTF_INDEXED_CHANNELS > 7 +uniform sampler2D basecolor7; uniform sampler2D emissivemap7; +#endif + +out vec4 frag_color; + +in vec4 vertex_emissive; +flat in int vary_material_index; + +in vec2 base_color_texcoord; +in vec2 emissive_texcoord; + +vec3 linear_to_srgb(vec3 c); +vec3 srgb_to_linear(vec3 c); + +vec4 sample_basecolor(vec2 uv) +{ + if (vary_material_index == 0) return texture(basecolor0, uv); +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(basecolor1, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(basecolor2, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(basecolor3, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(basecolor4, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(basecolor5, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(basecolor6, uv); +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(basecolor7, uv); +#endif + return vec4(1, 0, 1, 1); +} + +vec3 sample_emissive(vec2 uv) +{ + if (vary_material_index == 0) return texture(emissivemap0, uv).rgb; +#if GLTF_INDEXED_CHANNELS > 1 + if (vary_material_index == 1) return texture(emissivemap1, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 2 + if (vary_material_index == 2) return texture(emissivemap2, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 3 + if (vary_material_index == 3) return texture(emissivemap3, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 4 + if (vary_material_index == 4) return texture(emissivemap4, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 5 + if (vary_material_index == 5) return texture(emissivemap5, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 6 + if (vary_material_index == 6) return texture(emissivemap6, uv).rgb; +#endif +#if GLTF_INDEXED_CHANNELS > 7 + if (vary_material_index == 7) return texture(emissivemap7, uv).rgb; +#endif + return vec3(1.0); +} + +void main() +{ + int mi = vary_material_index; + + vec4 basecolor = sample_basecolor(base_color_texcoord.xy).rgba; + + if (basecolor.a < gltf_minimum_alpha[mi]) + { + discard; + } + + vec3 emissive = gltf_emissive_color[mi]; + emissive *= srgb_to_linear(sample_emissive(emissive_texcoord.xy)); + + float lum = max(max(emissive.r, emissive.g), emissive.b); + lum *= vertex_emissive.a; + + frag_color.rgb = vec3(0); + frag_color.a = lum; +} diff --git a/indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedV.glsl b/indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedV.glsl new file mode 100644 index 0000000000..d9e199a638 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbrglowIndexedV.glsl @@ -0,0 +1,84 @@ +/** + * @file pbrglowIndexedV.glsl + * + * $LicenseInfo:firstyear=2023&license=viewerlgpl$ + * Second Life Viewer Source Code + * Copyright (C) 2023, Linden Research, Inc. + * + * This library is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License only. + * + * This library is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + * Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this library; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA + * + * Linden Research, Inc., 945 Battery Street, San Francisco, CA 94111 USA + * $/LicenseInfo$ + */ + +// Indexed (multi-material) PBR glow/emissive vertex shader. One draw call covers +// up to GLTF_INDEXED_CHANNELS materials; the per-vertex material slot arrives in +// texture_index (packed in position.w) and selects this vertex's base-color and +// emissive KHR_texture_transform sets. See pbrglowV.glsl for the single-material +// equivalent and pbropaqueIndexedV.glsl for the GBuffer-write indexed shader. + +#ifdef HAS_SKIN +uniform mat4 modelview_matrix; +uniform mat4 projection_matrix; +mat4 getObjectSkinnedTransform(); +#else +uniform mat4 modelview_projection_matrix; +#endif + +// Per-material KHR_texture_transform, two vec4 (packed scale/rotation/offset) per +// slot, indexed by the material slot (texture_index). +uniform vec4 gltf_basecolor_transform[2*GLTF_INDEXED_CHANNELS]; +uniform vec4 gltf_emissive_transform[2*GLTF_INDEXED_CHANNELS]; + +in vec3 position; +in vec4 emissive; +in vec2 texcoord0; +in int texture_index; + +flat out int vary_material_index; + +out vec2 base_color_texcoord; +out vec2 emissive_texcoord; + +out vec4 vertex_emissive; + +vec2 texture_transform(vec2 vertex_texcoord, vec4[2] khr_gltf_transform, mat4 sl_animation_transform); + +void main() +{ +#ifdef HAS_SKIN + mat4 mat = getObjectSkinnedTransform(); + mat = modelview_matrix * mat; + vec3 pos = (mat*vec4(position.xyz,1.0)).xyz; + gl_Position = projection_matrix*vec4(pos,1.0); +#else + gl_Position = modelview_projection_matrix * vec4(position.xyz, 1.0); +#endif + + int mi = texture_index; + vary_material_index = mi; + + // Indexed faces never carry texture animation (excluded from batching), so the + // SL animation transform is identity. + mat4 ident = mat4(1.0); + + vec4 bc[2]; bc[0] = gltf_basecolor_transform[2*mi]; bc[1] = gltf_basecolor_transform[2*mi+1]; + vec4 em[2]; em[0] = gltf_emissive_transform[2*mi]; em[1] = gltf_emissive_transform[2*mi+1]; + + base_color_texcoord = texture_transform(texcoord0, bc, ident); + emissive_texcoord = texture_transform(texcoord0, em, ident); + + vertex_emissive = emissive; +} diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index 18a6583ba1..e0a614e9b8 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -612,6 +612,81 @@ void LLRenderPass::pushMaskBatchesIndexed(U32 type, bool rigged) } } +void LLRenderPass::pushEmissiveBatchesScalar(U32 type, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + bool skipLastSkin = false; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo* pparams = *i; + LLCullResult::increment_iterator(i, end); + + if (pparams->mMaterialSlotList.size() > 1) + { // multi-material glow batch -- drawn by pushEmissiveBatchesIndexed + continue; + } + + if (rigged) + { + if (!uploadMatrixPalette(pparams->mAvatar, pparams->mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + { + continue; + } + } + + pushBatch(*pparams, true, true); + } +} + +void LLRenderPass::pushEmissiveBatchesIndexed(U32 type, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const LLVOAvatar* lastAvatar = nullptr; + U64 lastMeshId = 0; + bool skipLastSkin = false; + + auto* begin = gPipeline.beginRenderMap(type); + auto* end = gPipeline.endRenderMap(type); + for (LLCullResult::drawinfo_iterator i = begin; i != end; ) + { + LLDrawInfo& params = **i; + LLCullResult::increment_iterator(i, end); + + if (params.mMaterialSlotList.size() < 2) + { + continue; + } + + if (rigged) + { + if (!uploadMatrixPalette(params.mAvatar, params.mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) + { + continue; + } + } + + const S32 n = (S32)params.mMaterialSlotList.size(); + LL_PROFILE_ZONE_NUM(n); + + for (S32 s = 0; s < n; ++s) + { + const LLDrawInfo::MaterialSlot& slot = params.mMaterialSlotList[s]; + LLViewerTexture* diffuse = slot.mDiffuse.notNull() ? slot.mDiffuse.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + gGL.getTexUnit(s)->bindFast(diffuse); + } + + applyModelMatrix(params); + + params.mVertexBuffer->setBuffer(); + params.mVertexBuffer->drawRange(LLRender::TRIANGLES, params.mStart, params.mEnd, params.mCount, params.mOffset); + } +} + void LLRenderPass::applyModelMatrix(const LLDrawInfo& params) { applyModelMatrix(params.mModelMatrix); @@ -902,8 +977,8 @@ void LLRenderPass::pushGLTFBatchesScalar(U32 type) } // Renders only the multi-material (indexed) draw infos. Assumes the indexed PBR -// shader is bound. base_color_only is forwarded for the shadow alpha-mask pass. -void LLRenderPass::pushGLTFBatchesIndexed(U32 type, bool base_color_only) +// shader is bound. maps selects which material maps to bind (see eGLTFIndexedMaps). +void LLRenderPass::pushGLTFBatchesIndexed(U32 type, eGLTFIndexedMaps maps) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; auto* begin = gPipeline.beginRenderMap(type); @@ -918,7 +993,7 @@ void LLRenderPass::pushGLTFBatchesIndexed(U32 type, bool base_color_only) continue; } - pushGLTFBatchIndexed(params, base_color_only); + pushGLTFBatchIndexed(params, maps); } } @@ -927,12 +1002,16 @@ void LLRenderPass::pushGLTFBatchesIndexed(U32 type, bool base_color_only) // s binds its maps to texture units [s, N+s, 2N+s, 3N+s] (N == shader's // sIndexedGLTFChannels) and contributes one element to the per-slot scalar/transform // uniform arrays. Mirrors LLFetchedGLTFMaterial::bind for the default-texture and -// factor handling. base_color_only skips the normal/ORM/emissive maps and their -// uniforms -- used by the shadow alpha-mask pass, which samples only base color. -void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, bool base_color_only) +// factor handling. maps trims the bound/uploaded set: GLTF_MAPS_BASE_COLOR (shadow +// alpha-mask) binds only base color; GLTF_MAPS_GLOW (glow pass) adds emissive but +// skips normal/ORM; GLTF_MAPS_FULL (GBuffer write) binds everything. +void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, eGLTFIndexedMaps maps) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + const bool want_emissive = (maps == GLTF_MAPS_FULL || maps == GLTF_MAPS_GLOW); + const bool want_full = (maps == GLTF_MAPS_FULL); // normal + ORM + const S32 N = LLGLSLShader::sIndexedGLTFChannels; // shader sampler-array stride const S32 n = (S32)params.mGLTFMaterialList.size(); // materials in this batch LL_PROFILE_ZONE_NUM(n); @@ -972,31 +1051,40 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, bool base_color_only mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_BASE_COLOR].getPacked(packed); memcpy(&bc_xform[8 * s], packed, sizeof(packed)); - if (base_color_only) + if (!want_emissive) { // shadow alpha-mask samples only base color continue; } + // emissive map/color/transform -- needed by both the glow and GBuffer passes + LLViewerTexture* em = mat->mEmissiveTexture.notNull() ? mat->mEmissiveTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); + gGL.getTexUnit(3 * N + s)->bindFast(em); + + emissive[3 * s + 0] = mat->mEmissiveColor.mV[0]; + emissive[3 * s + 1] = mat->mEmissiveColor.mV[1]; + emissive[3 * s + 2] = mat->mEmissiveColor.mV[2]; + + mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(packed); + memcpy(&em_xform[8 * s], packed, sizeof(packed)); + + if (!want_full) + { // glow needs base color + emissive only + continue; + } + LLViewerTexture* norm = (mat->mNormalTexture.notNull() && mat->mNormalTexture->getDiscardLevel() <= 4) ? mat->mNormalTexture.get() : LLViewerFetchedTexture::sFlatNormalImagep.get(); LLViewerTexture* orm = mat->mMetallicRoughnessTexture.notNull() ? mat->mMetallicRoughnessTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); - LLViewerTexture* em = mat->mEmissiveTexture.notNull() ? mat->mEmissiveTexture.get() : LLViewerFetchedTexture::sWhiteImagep.get(); gGL.getTexUnit(N + s)->bindFast(norm); gGL.getTexUnit(2 * N + s)->bindFast(orm); - gGL.getTexUnit(3 * N + s)->bindFast(em); roughness[s] = mat->mRoughnessFactor; metallic[s] = mat->mMetallicFactor; - emissive[3 * s + 0] = mat->mEmissiveColor.mV[0]; - emissive[3 * s + 1] = mat->mEmissiveColor.mV[1]; - emissive[3 * s + 2] = mat->mEmissiveColor.mV[2]; mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_NORMAL].getPacked(packed); memcpy(&nm_xform[8 * s], packed, sizeof(packed)); mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_METALLIC_ROUGHNESS].getPacked(packed); memcpy(&mr_xform[8 * s], packed, sizeof(packed)); - mat->mTextureTransform[LLGLTFMaterial::GLTF_TEXTURE_INFO_EMISSIVE].getPacked(packed); - memcpy(&em_xform[8 * s], packed, sizeof(packed)); } static const LLStaticHashedString sMinAlpha("gltf_minimum_alpha"); @@ -1005,21 +1093,26 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, bool base_color_only shader->uniform1fv(sMinAlpha, n, min_alpha); shader->uniform4fv(sBcXform, 2 * n, bc_xform); - if (!base_color_only) + if (want_emissive) + { + static const LLStaticHashedString sEmissive("gltf_emissive_color"); + static const LLStaticHashedString sEmXform("gltf_emissive_transform"); + + shader->uniform3fv(sEmissive, n, emissive); + shader->uniform4fv(sEmXform, 2 * n, em_xform); + } + + if (want_full) { static const LLStaticHashedString sRoughness("gltf_roughness_factor"); static const LLStaticHashedString sMetallic("gltf_metallic_factor"); - static const LLStaticHashedString sEmissive("gltf_emissive_color"); static const LLStaticHashedString sNmXform("gltf_normal_transform"); static const LLStaticHashedString sMrXform("gltf_mr_transform"); - static const LLStaticHashedString sEmXform("gltf_emissive_transform"); shader->uniform1fv(sRoughness, n, roughness); shader->uniform1fv(sMetallic, n, metallic); - shader->uniform3fv(sEmissive, n, emissive); shader->uniform4fv(sNmXform, 2 * n, nm_xform); shader->uniform4fv(sMrXform, 2 * n, mr_xform); - shader->uniform4fv(sEmXform, 2 * n, em_xform); } LLGLDisable cull_face(double_sided ? GL_CULL_FACE : 0); @@ -1173,7 +1266,7 @@ void LLRenderPass::pushRiggedGLTFBatchesScalar(U32 type) // rigged counterpart of pushGLTFBatchesIndexed -- only multi-material infos. // Assumes the rigged indexed program is bound. -void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type, bool base_color_only) +void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type, eGLTFIndexedMaps maps) { LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; const LLVOAvatar* lastAvatar = nullptr; @@ -1192,16 +1285,16 @@ void LLRenderPass::pushRiggedGLTFBatchesIndexed(U32 type, bool base_color_only) continue; } - pushRiggedGLTFBatchIndexed(params, lastAvatar, lastMeshId, skipLastSkin, base_color_only); + pushRiggedGLTFBatchIndexed(params, lastAvatar, lastMeshId, skipLastSkin, maps); } } // static -void LLRenderPass::pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin, bool base_color_only) +void LLRenderPass::pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin, eGLTFIndexedMaps maps) { if (uploadMatrixPalette(params.mAvatar, params.mSkinInfo, lastAvatar, lastMeshId, skipLastSkin)) { - pushGLTFBatchIndexed(params, base_color_only); + pushGLTFBatchIndexed(params, maps); } } diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index e658642b69..28fdb5458e 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -364,6 +364,16 @@ class LLRenderPass : public LLDrawPool // assumes draw infos of given type have valid GLTF materials void pushGLTFBatches(U32 type); + // Which material maps an indexed GLTF batch binds and uploads. Trimming the set + // skips needless texture binds / uniform uploads (and the normal-map discard-level + // fetch) when a pass samples only some maps. + enum eGLTFIndexedMaps + { + GLTF_MAPS_FULL = 0, // base color + normal + ORM + emissive (GBuffer write) + GLTF_MAPS_BASE_COLOR, // base color only (shadow alpha-mask discard) + GLTF_MAPS_GLOW, // base color + emissive (glow/emissive pass) + }; + // Indexed (multi-material) GLTF PBR helpers. Indexed and scalar draw infos // coexist in the same render map (PASS_GLTF_PBR); they are distinguished by // mGLTFMaterialList.size() > 1. Shadow/probe passes use the plain @@ -372,8 +382,8 @@ class LLRenderPass : public LLDrawPool // pushGLTFBatchesScalar -- renders only single-material infos // pushGLTFBatchesIndexed -- renders only multi-material infos (indexed program bound) void pushGLTFBatchesScalar(U32 type); - void pushGLTFBatchesIndexed(U32 type, bool base_color_only = false); - static void pushGLTFBatchIndexed(LLDrawInfo& params, bool base_color_only = false); + void pushGLTFBatchesIndexed(U32 type, eGLTFIndexedMaps maps = GLTF_MAPS_FULL); + static void pushGLTFBatchIndexed(LLDrawInfo& params, eGLTFIndexedMaps maps = GLTF_MAPS_FULL); // like pushGLTFBatches, but will not bind textures or set up texture transforms void pushUntexturedGLTFBatches(U32 type); @@ -391,8 +401,8 @@ class LLRenderPass : public LLDrawPool // is one avatar+skin (the accumulation breaks on skin change), so the matrix // palette is uploaded per draw info as usual. void pushRiggedGLTFBatchesScalar(U32 type); - void pushRiggedGLTFBatchesIndexed(U32 type, bool base_color_only = false); - static void pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin, bool base_color_only = false); + void pushRiggedGLTFBatchesIndexed(U32 type, eGLTFIndexedMaps maps = GLTF_MAPS_FULL); + static void pushRiggedGLTFBatchIndexed(LLDrawInfo& params, const LLVOAvatar*& lastAvatar, U64& lastMeshId, bool& skipLastSkin, eGLTFIndexedMaps maps = GLTF_MAPS_FULL); // push a single GLTF draw call // lastMat/lastTex track the most recently bound material+media texture so @@ -408,6 +418,14 @@ class LLRenderPass : public LLDrawPool // diffuse + per-slot cutoff array, then draws. Assumes the indexed material // shadow program is bound. void pushMaskBatchesIndexed(U32 type, bool rigged); + + // Emissive/glow indexed split. Multi-material (indexed) glow batches coexist + // with scalar/plain ones in PASS_GLOW, distinguished by mMaterialSlotList.size() + // > 1. The scalar sweep skips multi-material infos (they would render the whole + // range with slot 0's diffuse); the indexed sweep binds each slot's diffuse to + // unit s and draws under the indexed emissive program. + void pushEmissiveBatchesScalar(U32 type, bool rigged); + void pushEmissiveBatchesIndexed(U32 type, bool rigged); void pushBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); void pushUntexturedBatch(LLDrawInfo& params); void pushBumpBatch(LLDrawInfo& params, bool texture, bool batch_textures = false); diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp index 4fb537778e..142dc969f6 100644 --- a/indra/newview/lldrawpoolpbropaque.cpp +++ b/indra/newview/lldrawpoolpbropaque.cpp @@ -107,11 +107,40 @@ void LLDrawPoolGLTFPBR::renderPostDeferred(S32 pass) 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); + + // Multi-material (indexed) glow batches render with the indexed program; the + // scalar sweep skips them. If the indexed glow shader failed to load, fall + // back to scalar for everything (slot-0 glow, the pre-batching behavior). + bool glow_indexed = LLGLSLShader::sIndexedGLTFChannels >= 2 && gPBRGlowIndexedProgram.isComplete(); + gPBRGlowProgram.bind(); - pushGLTFBatches(LLRenderPass::PASS_GLTF_GLOW); + if (glow_indexed) + { + pushGLTFBatchesScalar(LLRenderPass::PASS_GLTF_GLOW); + } + else + { + pushGLTFBatches(LLRenderPass::PASS_GLTF_GLOW); + } gPBRGlowProgram.bind(true); - pushRiggedGLTFBatches(LLRenderPass::PASS_GLTF_GLOW_RIGGED); + if (glow_indexed) + { + pushRiggedGLTFBatchesScalar(LLRenderPass::PASS_GLTF_GLOW_RIGGED); + } + else + { + pushRiggedGLTFBatches(LLRenderPass::PASS_GLTF_GLOW_RIGGED); + } + + if (glow_indexed) + { + gPBRGlowIndexedProgram.bind(); + pushGLTFBatchesIndexed(LLRenderPass::PASS_GLTF_GLOW, GLTF_MAPS_GLOW); + + gPBRGlowIndexedProgram.bind(true); // rigged variant + pushRiggedGLTFBatchesIndexed(LLRenderPass::PASS_GLTF_GLOW_RIGGED, GLTF_MAPS_GLOW); + } gGL.setColorMask(true, false); } diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp index 40f36e6a98..353af839f3 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -56,14 +56,44 @@ void LLDrawPoolGlow::renderPostDeferred(S32 pass) LLGLDepthTest depth(GL_TRUE, GL_FALSE); gGL.setColorMask(false, true); + // Multi-material (indexed) legacy glow batches carry a per-slot diffuse list and + // must be drawn with the indexed program; the scalar sweep skips them. If the + // indexed glow shader failed to load, fall back to scalar for everything (slot-0 + // diffuse alpha, the pre-batching behavior). + bool glow_indexed = LLGLSLShader::sIndexedLegacyMaterials && gDeferredEmissiveIndexedProgram.isComplete(); + //first pass -- static objects shader->bind(); - pushBatches(LLRenderPass::PASS_GLOW, true, true); + if (glow_indexed) + { + pushEmissiveBatchesScalar(LLRenderPass::PASS_GLOW, false); + } + else + { + pushBatches(LLRenderPass::PASS_GLOW, true, true); + } // second pass -- rigged objects shader = shader->mRiggedVariant; shader->bind(); - pushRiggedBatches(LLRenderPass::PASS_GLOW_RIGGED, true, true); + if (glow_indexed) + { + pushEmissiveBatchesScalar(LLRenderPass::PASS_GLOW_RIGGED, true); + } + else + { + pushRiggedBatches(LLRenderPass::PASS_GLOW_RIGGED, true, true); + } + + // indexed (multi-material) passes + if (glow_indexed) + { + gDeferredEmissiveIndexedProgram.bind(); + pushEmissiveBatchesIndexed(LLRenderPass::PASS_GLOW, false); + + gDeferredEmissiveIndexedProgram.bind(true); // rigged variant + pushEmissiveBatchesIndexed(LLRenderPass::PASS_GLOW_RIGGED, true); + } gGL.setColorMask(true, false); gGL.setSceneBlendType(LLRender::BT_ALPHA); diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index c2d5ca3e8c..b73a8dc492 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -203,6 +203,8 @@ LLGLSLShader gDeferredFullbrightAlphaMaskAlphaProgram; LLGLSLShader gHUDFullbrightAlphaMaskAlphaProgram; LLGLSLShader gDeferredEmissiveProgram; LLGLSLShader gDeferredSkinnedEmissiveProgram; +LLGLSLShader gDeferredEmissiveIndexedProgram; // multi-material indexed legacy glow +LLGLSLShader gDeferredSkinnedEmissiveIndexedProgram; LLGLSLShader gDeferredPostProgram; LLGLSLShader gDeferredPostProgramNoNear; LLGLSLShader gDeferredCoFProgram; @@ -252,6 +254,8 @@ LLGLSLShader gDeferredMaterialIndexedProgram[LLMaterial::SHADER_COUNT LLGLSLShader gHUDPBROpaqueProgram; LLGLSLShader gPBRGlowProgram; LLGLSLShader gPBRGlowSkinnedProgram; +LLGLSLShader gPBRGlowIndexedProgram; // multi-material indexed PBR glow +LLGLSLShader gPBRGlowSkinnedIndexedProgram; LLGLSLShader gDeferredPBROpaqueProgram; LLGLSLShader gDeferredPBROpaqueIndexedProgram; LLGLSLShader gDeferredSkinnedPBROpaqueIndexedProgram; @@ -1161,6 +1165,8 @@ bool LLViewerShaderMgr::loadShadersDeferred() gHUDFullbrightAlphaMaskAlphaProgram.unload(); gDeferredEmissiveProgram.unload(); gDeferredSkinnedEmissiveProgram.unload(); + gDeferredEmissiveIndexedProgram.unload(); + gDeferredSkinnedEmissiveIndexedProgram.unload(); gDeferredAvatarEyesProgram.unload(); gDeferredPostProgram.unload(); gDeferredCoFProgram.unload(); @@ -1207,6 +1213,8 @@ bool LLViewerShaderMgr::loadShadersDeferred() gHUDPBROpaqueProgram.unload(); gPBRGlowProgram.unload(); + gPBRGlowIndexedProgram.unload(); + gPBRGlowSkinnedIndexedProgram.unload(); gDeferredPBROpaqueProgram.unload(); gDeferredPBROpaqueIndexedProgram.unload(); gDeferredSkinnedPBROpaqueIndexedProgram.unload(); @@ -1517,6 +1525,18 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredPBROpaqueIndexedProgram.unload(); gDeferredSkinnedPBROpaqueIndexedProgram.unload(); LLGLSLShader::sIndexedGLTFChannels = 0; + + // The legacy material indexed programs were built earlier (above) with the + // now-stale channel count and share sIndexedGLTFChannels. Tear them down so + // the invariant sIndexedLegacyMaterials => sIndexedGLTFChannels >= 2 holds. + if (LLGLSLShader::sIndexedLegacyMaterials) + { + for (U32 i = 0; i < LLMaterial::SHADER_COUNT*2; ++i) + { + gDeferredMaterialIndexedProgram[i].unload(); + } + LLGLSLShader::sIndexedLegacyMaterials = false; + } } } else @@ -1543,6 +1563,42 @@ bool LLViewerShaderMgr::loadShadersDeferred() llassert(success); } + if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) + { + // Indexed (multi-material) PBR glow, parallel to gPBRGlowProgram. Shares the + // GBuffer indexed sampler-unit layout (base color s, emissive 3N+s) so + // pushGLTFBatchIndexed drives it directly. Optional: failure leaves the + // program incomplete and the pool falls back to scalar glow. Kept out of the + // `success` chain. + gPBRGlowIndexedProgram.mName = "PBR Glow Indexed Shader"; + gPBRGlowIndexedProgram.mFeatures.hasSrgb = true; + gPBRGlowIndexedProgram.mShaderFiles.clear(); + gPBRGlowIndexedProgram.mShaderFiles.push_back(make_pair("deferred/pbrglowIndexedV.glsl", GL_VERTEX_SHADER)); + gPBRGlowIndexedProgram.mShaderFiles.push_back(make_pair("deferred/pbrglowIndexedF.glsl", GL_FRAGMENT_SHADER)); + gPBRGlowIndexedProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gPBRGlowIndexedProgram.clearPermutations(); + gPBRGlowIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); + add_common_permutations(&gPBRGlowIndexedProgram); + + bool glow_indexed_ok = make_rigged_variant(gPBRGlowIndexedProgram, gPBRGlowSkinnedIndexedProgram); + if (glow_indexed_ok) + { + glow_indexed_ok = gPBRGlowIndexedProgram.createShader(); + } + if (glow_indexed_ok) + { + S32 n = LLGLSLShader::sIndexedGLTFChannels; + setup_gltf_indexed_samplers(gPBRGlowIndexedProgram, n, true); + setup_gltf_indexed_samplers(gPBRGlowSkinnedIndexedProgram, n, true); + } + else + { + LL_WARNS("ShaderLoading") << "Indexed PBR glow shader failed to load; multi-material glow falls back to scalar." << LL_ENDL; + gPBRGlowIndexedProgram.unload(); + gPBRGlowSkinnedIndexedProgram.unload(); + } + } + if (success) { gHUDPBROpaqueProgram.mName = "HUD PBR Opaque Shader"; @@ -2220,6 +2276,41 @@ bool LLViewerShaderMgr::loadShadersDeferred() llassert(success); } + if (success && LLGLSLShader::sIndexedLegacyMaterials) + { + // Indexed (multi-material) legacy glow, parallel to gDeferredEmissiveProgram. + // Selects each slot's diffuse map (bound to unit s) for the glow alpha mask. + // Only enabled when legacy material batching is active; failure leaves the + // program incomplete and the pool falls back to scalar glow. Kept out of the + // `success` chain. + gDeferredEmissiveIndexedProgram.mName = "Deferred Emissive Indexed Shader"; + gDeferredEmissiveIndexedProgram.mShaderFiles.clear(); + gDeferredEmissiveIndexedProgram.mShaderFiles.push_back(make_pair("deferred/emissiveIndexedV.glsl", GL_VERTEX_SHADER)); + gDeferredEmissiveIndexedProgram.mShaderFiles.push_back(make_pair("deferred/emissiveIndexedF.glsl", GL_FRAGMENT_SHADER)); + gDeferredEmissiveIndexedProgram.mShaderLevel = mShaderLevel[SHADER_DEFERRED]; + gDeferredEmissiveIndexedProgram.clearPermutations(); + gDeferredEmissiveIndexedProgram.addPermutation("GLTF_INDEXED_CHANNELS", llformat("%d", LLGLSLShader::sIndexedGLTFChannels)); + add_common_permutations(&gDeferredEmissiveIndexedProgram); + + bool emissive_indexed_ok = make_rigged_variant(gDeferredEmissiveIndexedProgram, gDeferredSkinnedEmissiveIndexedProgram); + if (emissive_indexed_ok) + { + emissive_indexed_ok = gDeferredEmissiveIndexedProgram.createShader(); + } + if (emissive_indexed_ok) + { + S32 n = LLGLSLShader::sIndexedGLTFChannels; + setup_material_indexed_samplers(gDeferredEmissiveIndexedProgram, n, false, false); + setup_material_indexed_samplers(gDeferredSkinnedEmissiveIndexedProgram, n, false, false); + } + else + { + LL_WARNS("ShaderLoading") << "Indexed legacy glow shader failed to load; multi-material glow falls back to scalar." << LL_ENDL; + gDeferredEmissiveIndexedProgram.unload(); + gDeferredSkinnedEmissiveIndexedProgram.unload(); + } + } + if (success) { gDeferredSoftenProgram.mName = "Deferred Soften Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 822c8f2466..8354cc7cac 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -277,6 +277,7 @@ extern LLGLSLShader gHUDFullbrightAlphaMaskProgram; extern LLGLSLShader gDeferredFullbrightAlphaMaskAlphaProgram; extern LLGLSLShader gHUDFullbrightAlphaMaskAlphaProgram; extern LLGLSLShader gDeferredEmissiveProgram; +extern LLGLSLShader gDeferredEmissiveIndexedProgram; // multi-material indexed legacy glow extern LLGLSLShader gDeferredAvatarEyesProgram; extern LLGLSLShader gDeferredAvatarAlphaProgram; extern LLGLSLShader gEnvironmentMapProgram; @@ -311,6 +312,7 @@ extern LLGLSLShader gDeferredMaterialIndexedProgram[LLMaterial::SHADER_C extern LLGLSLShader gHUDPBROpaqueProgram; extern LLGLSLShader gPBRGlowProgram; +extern LLGLSLShader gPBRGlowIndexedProgram; // multi-material indexed PBR glow extern LLGLSLShader gDeferredPBROpaqueProgram; extern LLGLSLShader gDeferredPBROpaqueIndexedProgram; // multi-material indexed PBR opaque extern LLGLSLShader gDeferredPBRAlphaProgram; diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index 3444890f34..b335217b0e 100644 --- a/indra/newview/pipeline.cpp +++ b/indra/newview/pipeline.cpp @@ -10548,7 +10548,7 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(true); LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - mAlphaMaskPool->pushRiggedGLTFBatchesIndexed(type + 1, true); // shadow samples base color only + mAlphaMaskPool->pushRiggedGLTFBatchesIndexed(type + 1, LLRenderPass::GLTF_MAPS_BASE_COLOR); // shadow samples base color only } else { @@ -10564,7 +10564,7 @@ void LLPipeline::renderShadow(const glm::mat4& view, const glm::mat4& proj, LLCa gDeferredShadowGLTFAlphaMaskIndexedProgram.bind(); LLGLSLShader::sCurBoundShaderPtr->uniform1i(LLShaderMgr::SUN_UP_FACTOR, sun_up); LLGLSLShader::sCurBoundShaderPtr->uniform1f(LLShaderMgr::DEFERRED_SHADOW_TARGET_WIDTH, (float)target_width); - mAlphaMaskPool->pushGLTFBatchesIndexed(type, true); // shadow samples base color only + mAlphaMaskPool->pushGLTFBatchesIndexed(type, LLRenderPass::GLTF_MAPS_BASE_COLOR); // shadow samples base color only } else { From d047980f05982b7e12d9ccc48afe628bf70a8206 Mon Sep 17 00:00:00 2001 From: Rye Date: Tue, 16 Jun 2026 03:20:00 -0400 Subject: [PATCH 9/9] Indexed material batching: address PR #322 review CodeRabbit findings on the indexed multi-material batching: - Reset the scalar anchor face index in genDrawInfo before registerFace, so a stale material slot from a prior indexed rebuild cannot make a scalar face be treated as indexed (e.g. after toggling RenderGLTFPBRBatching off or shader fallback). - Include TE shiny in the legacy slot dedup key: spec color/env derive from shiny when there is no specular map, so faces sharing (material, diffuse) but differing in shiny must not share a slot. - Clamp/assert per-batch slot counts (n <= sIndexedGLTFChannels <= 8) before writing the fixed 8-slot arrays or binding slot texture units in the PBR/legacy/glow/material indexed push helpers. - Gate the scalar-skip + indexed dispatch on completeness of BOTH the indexed program and its rigged variant (PBR deferred, PBR glow, legacy glow); otherwise fall back to full scalar. - Reset sIndexedLegacyMaterials to false before the optional legacy-indexed load block so a stale true from a prior load cannot survive a skipped/failed reload. - Update the RenderGLTFPBRBatching setting description to reflect the full scope (PBR + legacy; opaque/mask/rigged/shadow/glow). Co-Authored-By: Claude Opus 4.8 --- .../newview/app_settings/settings_alchemy.xml | 2 +- indra/newview/lldrawpool.cpp | 17 +++++++++++--- indra/newview/lldrawpoolmaterials.cpp | 5 +++- indra/newview/lldrawpoolpbropaque.cpp | 18 +++++++++++---- indra/newview/lldrawpoolsimple.cpp | 11 +++++---- indra/newview/llviewershadermgr.cpp | 5 ++++ indra/newview/llvovolume.cpp | 23 ++++++++++++++++++- 7 files changed, 67 insertions(+), 14 deletions(-) diff --git a/indra/newview/app_settings/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index ce1ca2f75c..960048deae 100644 --- a/indra/newview/app_settings/settings_alchemy.xml +++ b/indra/newview/app_settings/settings_alchemy.xml @@ -1238,7 +1238,7 @@ RenderGLTFPBRBatching Comment - Batch multiple GLTF PBR materials into a single indexed draw call (static opaque only). 0 = one draw call per material (legacy), 1 = indexed multi-material batching. Reduces draw calls in material-heavy scenes. + Batch multiple materials into a single indexed draw call across the supported GLTF PBR and legacy material passes (opaque, alpha-mask, rigged, shadow, glow). 0 = one draw call per material (legacy), 1 = indexed multi-material batching. Reduces draw calls in material-heavy scenes. Persist 1 Type diff --git a/indra/newview/lldrawpool.cpp b/indra/newview/lldrawpool.cpp index e0a614e9b8..2e4e99455c 100644 --- a/indra/newview/lldrawpool.cpp +++ b/indra/newview/lldrawpool.cpp @@ -590,7 +590,11 @@ void LLRenderPass::pushMaskBatchesIndexed(U32 type, bool rigged) } } - const S32 n = (S32)params.mMaterialSlotList.size(); + // Slot count is capped at N (<= 8) by genDrawInfo; clamp defensively so a + // stale/over-long list can never overrun the 8-slot array or N sampler units. + const S32 N = LLGLSLShader::sIndexedGLTFChannels; + llassert((S32)params.mMaterialSlotList.size() <= N); + const S32 n = llmin((S32)params.mMaterialSlotList.size(), N); LL_PROFILE_ZONE_NUM(n); F32 min_alpha[8] = { 0.f }; @@ -670,7 +674,11 @@ void LLRenderPass::pushEmissiveBatchesIndexed(U32 type, bool rigged) } } - const S32 n = (S32)params.mMaterialSlotList.size(); + // Slot count is capped at N (<= 8) by genDrawInfo; clamp defensively so a + // stale/over-long list can never bind past the N diffuse sampler units. + const S32 N = LLGLSLShader::sIndexedGLTFChannels; + llassert((S32)params.mMaterialSlotList.size() <= N); + const S32 n = llmin((S32)params.mMaterialSlotList.size(), N); LL_PROFILE_ZONE_NUM(n); for (S32 s = 0; s < n; ++s) @@ -1013,7 +1021,10 @@ void LLRenderPass::pushGLTFBatchIndexed(LLDrawInfo& params, eGLTFIndexedMaps map const bool want_full = (maps == GLTF_MAPS_FULL); // normal + ORM const S32 N = LLGLSLShader::sIndexedGLTFChannels; // shader sampler-array stride - const S32 n = (S32)params.mGLTFMaterialList.size(); // materials in this batch + // Slot count is capped at N (<= 8) by genDrawInfo; clamp defensively so a stale + // or over-long list can never overrun the fixed 8-slot arrays / N sampler units. + llassert((S32)params.mGLTFMaterialList.size() <= N); + const S32 n = llmin((S32)params.mGLTFMaterialList.size(), N); // materials in this batch LL_PROFILE_ZONE_NUM(n); LLGLSLShader* shader = LLGLSLShader::sCurBoundShaderPtr; diff --git a/indra/newview/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp index 45129a869b..e539f3c3af 100644 --- a/indra/newview/lldrawpoolmaterials.cpp +++ b/indra/newview/lldrawpoolmaterials.cpp @@ -132,7 +132,10 @@ static void pushMaterialBatchIndexed(LLGLSLShader& program, U32 type, bool rigge } } - const S32 n = (S32)params.mMaterialSlotList.size(); + // Slot count is capped at N (<= 8) by genDrawInfo; clamp defensively so a + // stale/over-long list can never overrun the 8-slot arrays / 2N+s units. + llassert((S32)params.mMaterialSlotList.size() <= N); + const S32 n = llmin((S32)params.mMaterialSlotList.size(), N); LL_PROFILE_ZONE_NUM(n); F32 spec_color[4 * 8] = { 0.f }; diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp index 142dc969f6..335b425ec4 100644 --- a/indra/newview/lldrawpoolpbropaque.cpp +++ b/indra/newview/lldrawpoolpbropaque.cpp @@ -58,9 +58,15 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass) // Indexed (multi-material) batching applies to the static opaque and alpha-mask // passes. The indexed program writes the GBuffer the same way for both; the // per-slot gltf_minimum_alpha array drives the mask discard (-1 == opaque). + // Only skip multi-material infos in the scalar sweep once BOTH the indexed + // program and its rigged variant are complete -- otherwise scalar would skip + // them and the indexed sweep would bind an incomplete program. bool indexed = (mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR || mRenderType == LLPipeline::RENDER_TYPE_PASS_GLTF_PBR_ALPHA_MASK) && - LLGLSLShader::sIndexedGLTFChannels >= 2; + LLGLSLShader::sIndexedGLTFChannels >= 2 && + gDeferredPBROpaqueIndexedProgram.isComplete() && + gDeferredPBROpaqueIndexedProgram.mRiggedVariant && + gDeferredPBROpaqueIndexedProgram.mRiggedVariant->isComplete(); gDeferredPBROpaqueProgram.bind(); if (indexed) @@ -109,9 +115,13 @@ void LLDrawPoolGLTFPBR::renderPostDeferred(S32 pass) gGL.setColorMask(false, true); // Multi-material (indexed) glow batches render with the indexed program; the - // scalar sweep skips them. If the indexed glow shader failed to load, fall - // back to scalar for everything (slot-0 glow, the pre-batching behavior). - bool glow_indexed = LLGLSLShader::sIndexedGLTFChannels >= 2 && gPBRGlowIndexedProgram.isComplete(); + // scalar sweep skips them. Require both the static and rigged indexed glow + // programs to be complete; otherwise fall back to scalar for everything + // (slot-0 glow, the pre-batching behavior). + bool glow_indexed = LLGLSLShader::sIndexedGLTFChannels >= 2 && + gPBRGlowIndexedProgram.isComplete() && + gPBRGlowIndexedProgram.mRiggedVariant && + gPBRGlowIndexedProgram.mRiggedVariant->isComplete(); gPBRGlowProgram.bind(); if (glow_indexed) diff --git a/indra/newview/lldrawpoolsimple.cpp b/indra/newview/lldrawpoolsimple.cpp index 353af839f3..6de7a968d0 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -57,10 +57,13 @@ void LLDrawPoolGlow::renderPostDeferred(S32 pass) gGL.setColorMask(false, true); // Multi-material (indexed) legacy glow batches carry a per-slot diffuse list and - // must be drawn with the indexed program; the scalar sweep skips them. If the - // indexed glow shader failed to load, fall back to scalar for everything (slot-0 - // diffuse alpha, the pre-batching behavior). - bool glow_indexed = LLGLSLShader::sIndexedLegacyMaterials && gDeferredEmissiveIndexedProgram.isComplete(); + // must be drawn with the indexed program; the scalar sweep skips them. Require + // both the static and rigged indexed glow programs to be complete; otherwise fall + // back to scalar for everything (slot-0 diffuse alpha, the pre-batching behavior). + bool glow_indexed = LLGLSLShader::sIndexedLegacyMaterials && + gDeferredEmissiveIndexedProgram.isComplete() && + gDeferredEmissiveIndexedProgram.mRiggedVariant && + gDeferredEmissiveIndexedProgram.mRiggedVariant->isComplete(); //first pass -- static objects shader->bind(); diff --git a/indra/newview/llviewershadermgr.cpp b/indra/newview/llviewershadermgr.cpp index b73a8dc492..7483f2c07e 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -1403,6 +1403,11 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredMaterialProgram[9+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; gDeferredMaterialProgram[13+LLMaterial::SHADER_COUNT].mFeatures.hasLighting = true; + // Clear any stale value from a previous load before (re)deciding legacy indexed + // eligibility -- if the block below is skipped or fails partway, the flag must + // not carry a prior 'true' while the indexed programs are unloaded/incomplete. + LLGLSLShader::sIndexedLegacyMaterials = false; + if (success && LLGLSLShader::sIndexedGLTFChannels >= 2) { // Indexed (multi-material) legacy material GBuffer-write programs, parallel to diff --git a/indra/newview/llvovolume.cpp b/indra/newview/llvovolume.cpp index f40260c0f2..78d711e369 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -6706,8 +6706,19 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace const S32 gltf_channels = llmin((S32)LLGLSLShader::sIndexedGLTFChannels, 8); LLViewerTexture* diffuse_slots[8]; LLMaterial* mat_slots[8]; + U8 shiny_slots[8]; U32 slot_count = 0; + // A slot's spec color / env intensity come from the TE shiny value when + // the material has no specular map, so faces sharing (material, diffuse) + // but differing in shiny must NOT share a slot (the later would overwrite + // the slot data the earlier vertices reference). Fold shiny into the key. + auto shiny_slot_key = [](LLFace* f) -> U8 + { + LLMaterial* fm = f->getTextureEntry()->getMaterialParams().get(); + return (fm && fm->getSpecularID().isNull()) ? f->getTextureEntry()->getShiny() : (U8)0; + }; + LLMaterial* anchor_mat = facep->getTextureEntry()->getMaterialParams().get(); const U32 anchor_mask = anchor_mat->getShaderMask(LLMaterial::DIFFUSE_ALPHA_MODE_DEFAULT, false); // rigged batches are one avatar+skin (matrix palette per skin) @@ -6716,6 +6727,7 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace diffuse_slots[0] = facep->getTexture(); mat_slots[0] = anchor_mat; + shiny_slots[0] = shiny_slot_key(facep); slot_count = 1; facep->setTextureIndex(0); @@ -6740,10 +6752,11 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace } LLViewerTexture* d = facep->getTexture(); + U8 sh = shiny_slot_key(facep); S32 slot = -1; for (U32 s = 0; s < slot_count; ++s) { - if (mat_slots[s] == m && diffuse_slots[s] == d) + if (mat_slots[s] == m && diffuse_slots[s] == d && shiny_slots[s] == sh) { slot = (S32)s; break; @@ -6758,6 +6771,7 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace slot = (S32)slot_count; diffuse_slots[slot_count] = d; mat_slots[slot_count] = m; + shiny_slots[slot_count] = sh; slot_count++; } @@ -6869,6 +6883,13 @@ U32 LLVolumeGeometryManager::genDrawInfo(LLSpatialGroup* group, U32 mask, LLFace } else { + // The anchor face is scalar in this path too: clear any material/texture + // slot left on it by a previous indexed rebuild before registerFace runs, + // otherwise registerFace (which infers indexed mode from index < + // FACE_DO_NOT_BATCH_TEXTURES) would wrongly populate an indexed list. + facep->mDrawInfo = NULL; + facep->setTextureIndex(FACE_DO_NOT_BATCH_TEXTURES); + while (i != end_faces && (LLPipeline::sTextureBindTest || (distance_sort ||