diff --git a/indra/llrender/llglslshader.cpp b/indra/llrender/llglslshader.cpp index 0d6b15cfd3..3898cd5a47 100644 --- a/indra/llrender/llglslshader.cpp +++ b/indra/llrender/llglslshader.cpp @@ -54,6 +54,8 @@ using std::string; 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 704ecf90a1..ecae11511b 100644 --- a/indra/llrender/llglslshader.h +++ b/indra/llrender/llglslshader.h @@ -171,6 +171,17 @@ 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; + + // 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/settings_alchemy.xml b/indra/newview/app_settings/settings_alchemy.xml index 48f2c098e5..960048deae 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 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 + Boolean + Value + 1 + RenderBloomThreshold Comment 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/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/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/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..aba3b7887b --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbrShadowAlphaMaskIndexedV.glsl @@ -0,0 +1,81 @@ +/** + * @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. 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]; +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() +{ +#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; + 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/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/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..5ff068b3d6 --- /dev/null +++ b/indra/newview/app_settings/shaders/class1/deferred/pbropaqueIndexedV.glsl @@ -0,0 +1,120 @@ +/** + * @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. 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). +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() +{ +#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; + + // 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); + +#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); + 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..2e4e99455c 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; @@ -520,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); } @@ -541,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)) @@ -550,6 +561,140 @@ void LLRenderPass::pushRiggedMaskBatches(U32 type, bool texture, bool batch_text } } +void LLRenderPass::pushMaskBatchesIndexed(U32 type, bool rigged) +{ + LL_PROFILE_ZONE_SCOPED_CATEGORY_DRAWPOOL; + 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; + } + } + + // 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 }; + 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::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; + } + } + + // 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) + { + 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); @@ -815,6 +960,180 @@ 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. 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); + 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, maps); + } +} + +// static +// Bind one draw call's worth of indexed materials and emit it. Each material slot +// 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. 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 + // 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; + + // 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(); + 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 (!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(); + + gGL.getTexUnit(N + s)->bindFast(norm); + gGL.getTexUnit(2 * N + s)->bindFast(orm); + + roughness[s] = mat->mRoughnessFactor; + metallic[s] = mat->mMetallicFactor; + + 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)); + } + + static const LLStaticHashedString sMinAlpha("gltf_minimum_alpha"); + static const LLStaticHashedString sBcXform("gltf_basecolor_transform"); + + shader->uniform1fv(sMinAlpha, n, min_alpha); + shader->uniform4fv(sBcXform, 2 * n, bc_xform); + + 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 sNmXform("gltf_normal_transform"); + static const LLStaticHashedString sMrXform("gltf_mr_transform"); + + shader->uniform1fv(sRoughness, n, roughness); + shader->uniform1fv(sMetallic, n, metallic); + shader->uniform4fv(sNmXform, 2 * n, nm_xform); + shader->uniform4fv(sMrXform, 2 * n, mr_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) { @@ -930,3 +1249,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, eGLTFIndexedMaps maps) +{ + 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, maps); + } +} + +// static +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, maps); + } +} + diff --git a/indra/newview/lldrawpool.h b/indra/newview/lldrawpool.h index c645565f06..28fdb5458e 100644 --- a/indra/newview/lldrawpool.h +++ b/indra/newview/lldrawpool.h @@ -364,6 +364,27 @@ 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 + // 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, 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); @@ -376,6 +397,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, 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 // consecutive draws sharing a material skip the redundant LLFetchedGLTFMaterial::bind @@ -386,6 +414,18 @@ 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); + + // 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/lldrawpoolmaterials.cpp b/indra/newview/lldrawpoolmaterials.cpp index b330785811..e539f3c3af 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,116 @@ 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; + } + } + + // 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 }; + 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 +208,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 +311,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 +408,16 @@ void LLDrawPoolMaterials::renderDeferred(S32 pass) gGL.matrixMode(LLRender::MM_MODELVIEW); } } + + // Draw the multi-material (indexed) batches for this pass with the indexed + // program (its rigged variant for the rigged passes). + if (LLGLSLShader::sIndexedLegacyMaterials) + { + LLGLSLShader& indexed = gDeferredMaterialIndexedProgram[sMaterialShaderIdx[pass]]; + LLGLSLShader* prog = rigged ? indexed.mRiggedVariant : &indexed; + if (prog && prog->isComplete()) + { + pushMaterialBatchIndexed(*prog, type, rigged); + } + } } diff --git a/indra/newview/lldrawpoolpbropaque.cpp b/indra/newview/lldrawpoolpbropaque.cpp index eb387e1f46..335b425ec4 100644 --- a/indra/newview/lldrawpoolpbropaque.cpp +++ b/indra/newview/lldrawpoolpbropaque.cpp @@ -55,11 +55,47 @@ void LLDrawPoolGLTFPBR::renderDeferred(S32 pass) LLGLEnable srgb(GL_FRAMEBUFFER_SRGB); + // 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 && + gDeferredPBROpaqueIndexedProgram.isComplete() && + gDeferredPBROpaqueIndexedProgram.mRiggedVariant && + gDeferredPBROpaqueIndexedProgram.mRiggedVariant->isComplete(); + 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) + { + pushRiggedGLTFBatchesScalar(mRenderType + 1); + } + else + { + pushRiggedGLTFBatches(mRenderType + 1); + } + + if (indexed) + { + gDeferredPBROpaqueIndexedProgram.bind(); + pushGLTFBatchesIndexed(mRenderType); + + gDeferredPBROpaqueIndexedProgram.bind(true); // rigged variant + pushRiggedGLTFBatchesIndexed(mRenderType + 1); + } } S32 LLDrawPoolGLTFPBR::getNumPostDeferredPasses() @@ -77,11 +113,44 @@ 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. 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(); - 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..6de7a968d0 100644 --- a/indra/newview/lldrawpoolsimple.cpp +++ b/indra/newview/lldrawpoolsimple.cpp @@ -56,14 +56,47 @@ 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. 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(); - 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/llspatialpartition.h b/indra/newview/llspatialpartition.h index ff90725981..b3d2c1c541 100644 --- a/indra/newview/llspatialpartition.h +++ b/indra/newview/llspatialpartition.h @@ -117,6 +117,28 @@ 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; + + // 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; @@ -669,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); + 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 e1b51e05f2..7483f2c07e 100644 --- a/indra/newview/llviewershadermgr.cpp +++ b/indra/newview/llviewershadermgr.cpp @@ -177,7 +177,11 @@ LLGLSLShader gDeferredShadowCubeProgram; LLGLSLShader gDeferredShadowAlphaMaskProgram; LLGLSLShader gDeferredSkinnedShadowAlphaMaskProgram; 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; @@ -199,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; @@ -244,10 +250,15 @@ 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; +LLGLSLShader gPBRGlowIndexedProgram; // multi-material indexed PBR glow +LLGLSLShader gPBRGlowSkinnedIndexedProgram; LLGLSLShader gDeferredPBROpaqueProgram; +LLGLSLShader gDeferredPBROpaqueIndexedProgram; +LLGLSLShader gDeferredSkinnedPBROpaqueIndexedProgram; LLGLSLShader gDeferredSkinnedPBROpaqueProgram; LLGLSLShader gHUDPBRAlphaProgram; LLGLSLShader gDeferredPBRAlphaProgram; @@ -281,6 +292,48 @@ 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(); +} + +// 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 @@ -483,6 +536,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. @@ -1082,6 +1142,10 @@ bool LLViewerShaderMgr::loadShadersDeferred() gDeferredShadowAlphaMaskProgram.unload(); gDeferredSkinnedShadowAlphaMaskProgram.unload(); gDeferredShadowGLTFAlphaMaskProgram.unload(); + gDeferredShadowGLTFAlphaMaskIndexedProgram.unload(); + gDeferredSkinnedShadowGLTFAlphaMaskIndexedProgram.unload(); + gDeferredShadowMaterialIndexedProgram.unload(); + gDeferredSkinnedShadowMaterialIndexedProgram.unload(); gDeferredSkinnedShadowGLTFAlphaMaskProgram.unload(); gDeferredShadowFullbrightAlphaMaskProgram.unload(); gDeferredSkinnedShadowFullbrightAlphaMaskProgram.unload(); @@ -1101,6 +1165,8 @@ bool LLViewerShaderMgr::loadShadersDeferred() gHUDFullbrightAlphaMaskAlphaProgram.unload(); gDeferredEmissiveProgram.unload(); gDeferredSkinnedEmissiveProgram.unload(); + gDeferredEmissiveIndexedProgram.unload(); + gDeferredSkinnedEmissiveIndexedProgram.unload(); gDeferredAvatarEyesProgram.unload(); gDeferredPostProgram.unload(); gDeferredCoFProgram.unload(); @@ -1141,11 +1207,17 @@ 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(); + gPBRGlowIndexedProgram.unload(); + gPBRGlowSkinnedIndexedProgram.unload(); gDeferredPBROpaqueProgram.unload(); + gDeferredPBROpaqueIndexedProgram.unload(); + gDeferredSkinnedPBROpaqueIndexedProgram.unload(); gDeferredSkinnedPBROpaqueProgram.unload(); gDeferredPBRAlphaProgram.unload(); gDeferredSkinnedPBRAlphaProgram.unload(); @@ -1331,6 +1403,75 @@ 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 + // 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"; @@ -1352,6 +1493,62 @@ 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); + + // 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, on both the + // static and rigged variants. + const S32 n = LLGLSLShader::sIndexedGLTFChannels; + 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; + + // 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 + { + LLGLSLShader::sIndexedGLTFChannels = 0; + } + if (success) { gPBRGlowProgram.mName = " PBR Glow Shader"; @@ -1371,6 +1568,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"; @@ -2048,6 +2281,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"; @@ -2230,6 +2498,77 @@ 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); + + 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; + 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(); + } + } + + 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. 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)); + 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; legacy batching disabled." << LL_ENDL; + gDeferredShadowMaterialIndexedProgram.unload(); + gDeferredSkinnedShadowMaterialIndexedProgram.unload(); + LLGLSLShader::sIndexedLegacyMaterials = false; // can't shadow indexed batches -- don't form them + } + } + if (success) { gDeferredShadowGLTFAlphaBlendProgram.mName = "Deferred GLTF Shadow Alpha Blend Shader"; diff --git a/indra/newview/llviewershadermgr.h b/indra/newview/llviewershadermgr.h index 75bedaf985..8354cc7cac 100644 --- a/indra/newview/llviewershadermgr.h +++ b/indra/newview/llviewershadermgr.h @@ -247,6 +247,8 @@ extern LLGLSLShader gDeferredShadowProgram; 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; @@ -275,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; @@ -305,10 +308,13 @@ 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; +extern LLGLSLShader gPBRGlowIndexedProgram; // multi-material indexed PBR glow 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..78d711e369 100644 --- a/indra/newview/llvovolume.cpp +++ b/indra/newview/llvovolume.cpp @@ -5233,6 +5233,104 @@ 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_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; + } + + 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; +} + +// 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 }; @@ -5425,6 +5523,15 @@ 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); + + // 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 @@ -5442,9 +5549,76 @@ 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 (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 (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; } @@ -5476,10 +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 - 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 && @@ -5489,7 +5666,23 @@ 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 (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); info->mTextureList[index] = tex; @@ -5576,7 +5769,17 @@ 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 (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); draw_info->mTextureList[index] = tex; @@ -6122,6 +6325,15 @@ 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; + // 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; bool alpha_sort = true; @@ -6132,10 +6344,10 @@ 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, pbr_mask | extra_mask, sPbrFaces[i], pbr_count[i], false, false, rigged); + 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) extra_mask |= LLVertexBuffer::MAP_WEIGHT4; @@ -6307,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) +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; @@ -6390,7 +6602,203 @@ 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 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); + + while (i != end_faces) + { + facep = *i; + + if (!can_batch_gltf_material(facep)) + { // not a batchable PBR face (blend / media / tex-anim) -- 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; + } + + if ((U8)m->mAlphaMode != anchor_alpha) + { // opaque and mask faces register to different passes -- keep + // each indexed batch to a single alpha mode + 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) + { + 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_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]; + 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) + const LLVOAvatar* anchor_avatar = facep->mAvatar; + const U64 anchor_skin = facep->getSkinHash(); + + diffuse_slots[0] = facep->getTexture(); + mat_slots[0] = anchor_mat; + shiny_slots[0] = shiny_slot_key(facep); + 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; + } + + 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(); + 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 && shiny_slots[s] == sh) + { + 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; + shiny_slots[slot_count] = sh; + 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; facep->setTextureIndex(cur_tex); @@ -6475,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 || diff --git a/indra/newview/pipeline.cpp b/indra/newview/pipeline.cpp index ac0aba85e0..b335217b0e 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); + } } } } @@ -10523,13 +10535,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); + if (gltf_indexed) + { + mAlphaMaskPool->pushRiggedGLTFBatchesScalar(type + 1); + + 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, LLRenderPass::GLTF_MAPS_BASE_COLOR); // shadow samples base color only + } + 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, LLRenderPass::GLTF_MAPS_BASE_COLOR); // shadow samples base color only + } + else + { + mAlphaMaskPool->pushGLTFBatches(type); + } } gGL.loadMatrix(gGLModelView);