Files
2025-09-04 10:54:47 +08:00

94 lines
4.8 KiB
GLSL

#version 450
/* Copyright (c) 2021-2024, Arm Limited and Contributors
*
* SPDX-License-Identifier: Apache-2.0
*
* Licensed under the Apache License, Version 2.0 the "License";
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Adds support for nonuniformEXT and unsized descriptor arrays.
#extension GL_EXT_nonuniform_qualifier : require
layout(set = 0, binding = 0) uniform texture2D Textures[];
layout(set = 1, binding = 0) uniform sampler ImmutableSampler;
layout(location = 0) in vec2 in_uv;
layout(location = 1) flat in int in_texture_index;
layout(location = 0) out vec4 out_frag_color;
void main()
{
// Here we are indexing into the texture array, with a non-uniform index.
// Across the draw-call, there are different instance indices being used, so we must use nonuniformEXT.
// It is important to note that to be 100% correct, we must use:
// nonuniformEXT(sampler2D()).
// It is the final argument to a call like texture() which determines if the access is to be considered non-uniform.
// It is very common in the wild to see code like:
// - sampler2D(Textures[nonuniformEXT(in_texture_index)], ...)
// This looks very similar to HLSL, but it is somewhat wrong.
// Generally, it will work on drivers, but it is not technically correct.
// To quote GL_EXT_nonuniform_qualifier:
/*
Only some operations discussed in Chapter 5 (Operators and Expressions)
can be applied to nonuniform value(s) and still yield a result that is
nonuniform. The operations that do so are listed below. When a
nonuniform value is operated on with one of these operators (regardless
of whether any and other operands are nonuniform), the result is
implicitly nonuniform:
...
* Structure and Array Operations in Section 5.7, except for the length
method and assignment operator.
...
Constructors and builtin functions, which all have return types that
are not qualified by nonuniformEXT, will not generate nonuniform results.
Shaders need to use the constructor syntax (or assignment to a
nonuniformEXT-qualified variable) to re-add the nonuniformEXT qualifier
to the result of builtin functions.
...
*/
// sampler2D is such a constructor, so we must add nonuniformEXT afterwards.
out_frag_color = texture(nonuniformEXT(sampler2D(Textures[in_texture_index], ImmutableSampler)), in_uv);
// For all other use cases of nonuniformEXT however, we can write code like:
// uniform UBO { vec4 data; } UBOs[]; vec4 foo = UBOs[nonuniformEXT(index)].data;
// buffer SSBO { vec4 data; } SSBOs[]; vec4 foo = SSBOs[nonuniformEXT(index)].data;
// uniform sampler2D Tex[]; vec4 foo = texture(Tex[nonuniformEXT(index)], uv);
// uniform uimage2D Img[]; uint count = imageAtomicAdd(Img[nonuniformEXT(index)], uv, val);
// etc. The nonuniform qualifier will propagate up to the final argument which is used in the load/store or atomic operation.
// Using implicit LOD with nonuniformEXT can be spicy! If the threads in a quad do not have the same index,
// LOD might not be computed correctly. The quadDivergentImplicitLOD property lets you know if it will work.
// In this case however, it is completely fine, since the helper lanes in a quad must come from the same primitive,
// which all have the same flat fragment input.
// You might consider using subgroup operations to implement nonuniformEXT on your own.
// This is technically out of spec, since the SPIR-V specification states that to avoid nonuniformEXT,
// the shader must guarantee that the index is "dynamically uniform".
// "Dynamically uniform" means the value is the same across all invocations in an "invocation group".
// The invocation group is defined to be all invocations (threads) for:
// - An entire draw command (for graphics)
// - A single workgroup (for compute).
// Avoiding nonuniformEXT with clever programming is far more likely to succeed when writing compute shaders,
// since the workgroup boundary serves as a much easier boundary to control than entire draw commands.
// It is often possible to match workgroup to subgroup 1:1, unlike graphics where you cannot control how
// quads are packed into subgroups at all.
// The recommended approach here is to just let the compiler do its thing to avoid horrible bugs in the future.
}