yum-mirror/glslang

git clone https://git.yummers.dev/yum-mirror/glslang

Piers DaniellAdd support for GLSL_EXT_uniform_buffer_unsized_array (#4075)ffcdd3ea

main
11.4 KiB332 linesraw
1// Copyright (C) 2025 NVIDIA Corporation
2//
3// Permission is hereby granted, free of charge, to any person obtaining a copy
4// of this software and associated documentation files (the "Software"), to deal
5// in the Software without restriction, including without limitation the rights
6// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7// copies of the Software, and to permit persons to whom the Software is
8// furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in all
11// copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
19// SOFTWARE.
20
21#include <gtest/gtest.h>
22
23#include "TestFixture.h"
24
25namespace glslangtest {
26namespace {
27
28class UboUnsizedArrayTest : public GlslangTest<::testing::Test> {
29protected:
30    // Helper function to compile shader and check for specific error message.
31    bool compileShouldFailWith(const std::string& code, const std::string& expectedError,
32                               EShLanguage stage = EShLangVertex)
33    {
34        glslang::TShader shader(stage);
35        EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
36
37        bool success = compile(&shader, code, "", controls);
38
39        if (success) {
40            // Compilation should have failed.
41            return false;
42        }
43
44        std::string errorLog = shader.getInfoLog();
45        return errorLog.find(expectedError) != std::string::npos;
46    }
47};
48
49// Test that unsized arrays in uniform blocks work when extension is enabled.
50TEST_F(UboUnsizedArrayTest, BasicFunctionality)
51{
52    const std::string code = R"(
53        #version 450
54        #extension GL_EXT_uniform_buffer_unsized_array : require
55        
56        layout(std140, binding=0) uniform DataBlock {
57            float scale;
58            float values[];  // unsized array as last member
59        };
60        
61        void main() {
62            gl_Position = vec4(values[0] * scale, 0.0, 0.0, 1.0);
63        }
64    )";
65
66    glslang::TShader shader(EShLangVertex);
67    EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
68    EXPECT_TRUE(compile(&shader, code, "", controls));
69}
70
71// Test that unsized arrays work when extension is enabled.
72TEST_F(UboUnsizedArrayTest, ExtensionRequired)
73{
74    const std::string code = R"(
75        #version 450
76        #extension GL_EXT_uniform_buffer_unsized_array : require
77        
78        layout(std140, binding=0) uniform DataBlock {
79            float scale;
80            float values[];  // Should work with extension
81        };
82        
83        void main() {
84            gl_Position = vec4(values[0] * scale, 0.0, 0.0, 1.0);
85        }
86    )";
87
88    glslang::TShader shader(EShLangVertex);
89    EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
90    EXPECT_TRUE(compile(&shader, code, "", controls));
91}
92
93// Test that only the last member can be unsized.
94TEST_F(UboUnsizedArrayTest, OnlyLastMemberCanBeUnsized)
95{
96    const std::string code = R"(
97        #version 450
98        #extension GL_EXT_uniform_buffer_unsized_array : require
99        
100        layout(std140, binding=0) uniform DataBlock {
101            float scale;
102            float values[];  // Last member - should work
103        };
104        
105        void main() {
106            gl_Position = vec4(values[0] * scale, 0.0, 0.0, 1.0);
107        }
108    )";
109
110    glslang::TShader shader(EShLangVertex);
111    EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
112    EXPECT_TRUE(compile(&shader, code, "", controls));
113}
114
115// Test that .length() method fails on unsized arrays.
116TEST_F(UboUnsizedArrayTest, LengthMethodNotAllowed)
117{
118    const std::string code = R"(
119        #version 450
120        #extension GL_EXT_uniform_buffer_unsized_array : require
121        
122        layout(std140, binding=0) uniform DataBlock {
123            float scale;
124            float values[];
125        };
126        
127        layout(location = 0) out vec4 fragColor;
128        
129        void main() {
130            int len = values.length();  // Should fail - length() not supported for unsized arrays in uniform blocks
131            fragColor = vec4(len, 0.0, 0.0, 1.0);
132        }
133    )";
134
135    EXPECT_TRUE(
136        compileShouldFailWith(code, "array must be declared with a size before using this method", EShLangFragment));
137}
138
139// Test that function parameters cannot be unsized arrays from uniform blocks.
140TEST_F(UboUnsizedArrayTest, FunctionParameterRestriction)
141{
142    const std::string code = R"(
143        #version 450
144        #extension GL_EXT_uniform_buffer_unsized_array : require
145        
146        layout(std140, binding=0) uniform DataBlock {
147            float scale;
148            float values[];
149        };
150        
151        void processArray(float arr[10]) {
152            // Process array
153        }
154        
155        void main() {
156            processArray(values);  // Should fail - cannot pass unsized arrays as function arguments
157            gl_Position = vec4(0.0, 0.0, 0.0, 1.0);
158        }
159    )";
160
161    EXPECT_TRUE(compileShouldFailWith(code, "no matching overloaded function found"));
162}
163
164// Test negative constant indexing.
165TEST_F(UboUnsizedArrayTest, NegativeIndexingNotAllowed)
166{
167    const std::string code = R"(
168        #version 450
169        #extension GL_EXT_uniform_buffer_unsized_array : require
170        
171        layout(std140, binding=0) uniform DataBlock {
172            float scale;
173            float values[];
174        };
175        
176        void main() {
177            float value = values[-1];  // Should fail
178            gl_Position = vec4(value * scale, 0.0, 0.0, 1.0);
179        }
180    )";
181
182    EXPECT_TRUE(compileShouldFailWith(code, "index out of range"));
183}
184
185// Test that different data types work correctly.
186TEST_F(UboUnsizedArrayTest, MultipleDataTypes)
187{
188    const std::string code = R"(
189        #version 450
190        #extension GL_EXT_uniform_buffer_unsized_array : require
191        
192        layout(std140, binding=0) uniform FloatBlock {
193            float scale;
194            float floatValues[];
195        };
196        
197        layout(std140, binding=1) uniform IntBlock {
198            int count;
199            int intValues[];
200        };
201        
202        layout(std140, binding=2) uniform VecBlock {
203            mat4 transform;
204            vec4 vecValues[];
205        };
206        
207        void main() {
208            int baseIndex = gl_VertexIndex % 10;
209            float value = floatValues[baseIndex] * scale;
210            int ivalue = intValues[baseIndex] * count;
211            vec4 vvalue = vecValues[baseIndex] * transform;
212            
213            gl_Position = vec4(value + float(ivalue), vvalue.xy, 1.0);
214        }
215    )";
216
217    glslang::TShader shader(EShLangVertex);
218    EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
219    EXPECT_TRUE(compile(&shader, code, "", controls));
220}
221
222// Test that general integer expressions work for indexing.
223TEST_F(UboUnsizedArrayTest, GeneralIntegerIndexing)
224{
225    const std::string code = R"(
226        #version 450
227        #extension GL_EXT_uniform_buffer_unsized_array : require
228        
229        layout(std140, binding=0) uniform DataBlock {
230            float scale;
231            float values[];
232        };
233        
234        layout(std140, binding=1) uniform SizeInfo {
235            int arraySize;
236        };
237        
238        void main() {
239            // Various forms of general integer expressions
240            int baseIndex = gl_VertexIndex % arraySize;
241            int offsetIndex = (baseIndex + 1) % arraySize;
242            int computedIndex = min(baseIndex + offsetIndex, arraySize - 1);
243            
244            float result = values[baseIndex] + values[offsetIndex] + values[computedIndex];
245            gl_Position = vec4(result * scale, 0.0, 0.0, 1.0);
246        }
247    )";
248
249    glslang::TShader shader(EShLangVertex);
250    EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules);
251    EXPECT_TRUE(compile(&shader, code, "", controls));
252}
253
254// Test SPIR-V generation for unsized arrays in uniform blocks.
255TEST_F(UboUnsizedArrayTest, SpvGeneration)
256{
257    const std::string code = R"(
258        #version 450
259        #extension GL_EXT_uniform_buffer_unsized_array : require
260        
261        layout(std140, binding=0) uniform DataBlock {
262            float scale;
263            float values[];
264        };
265        
266        layout(std140, binding=1) uniform SizeBlock {
267            int arraySize;
268        };
269        
270        void main() {
271            int index = gl_VertexIndex % arraySize;
272            float value = values[index];
273            gl_Position = vec4(value * scale, 0.0, 0.0, 1.0);
274        }
275    )";
276
277    // Compile the shader.
278    glslang::TShader shader(EShLangVertex);
279    const char* shaderStrings[1] = {code.c_str()};
280    shader.setStrings(shaderStrings, 1);
281
282    // Set up compilation options.
283    EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules);
284    shader.setEnvInput(glslang::EShSourceGlsl, EShLangVertex, glslang::EShClientVulkan, 450);
285    shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0);
286    shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0);
287
288    // Compile.
289    bool success = shader.parse(GetDefaultResources(), 450, false, messages);
290    EXPECT_TRUE(success) << "Shader compilation failed: " << shader.getInfoLog();
291
292    // Link and generate SPIR-V.
293    glslang::TProgram program;
294    program.addShader(&shader);
295    success = program.link(messages);
296    EXPECT_TRUE(success) << "Program linking failed: " << program.getInfoLog();
297
298    // Generate SPIR-V.
299    spv::SpvBuildLogger logger;
300    std::vector<uint32_t> spirv;
301    glslang::SpvOptions options;
302    glslang::GlslangToSpv(*program.getIntermediate(EShLangVertex), spirv, &logger, &options);
303
304    // Disassemble SPIR-V to text for easier checking.
305    std::ostringstream disassembly_stream;
306    spv::Disassemble(disassembly_stream, spirv);
307    std::string spirvText = disassembly_stream.str();
308
309    // Check for key SPIR-V elements that indicate successful compilation.
310    // 1. SourceExtension for the extension
311    EXPECT_TRUE(spirvText.find("SourceExtension") != std::string::npos)
312        << "SPIR-V should contain SourceExtension for GL_EXT_uniform_buffer_unsized_array";
313
314    // 2. TypeRuntimeArray for the unsized array
315    EXPECT_TRUE(spirvText.find("TypeRuntimeArray") != std::string::npos)
316        << "SPIR-V should contain TypeRuntimeArray for unsized arrays";
317
318    // 3. Block decoration (for uniform blocks with runtime arrays)
319    EXPECT_TRUE(spirvText.find("Block") != std::string::npos)
320        << "SPIR-V should contain Block decoration for uniform blocks with runtime arrays";
321
322    // 4. RuntimeDescriptorArrayEXT capability
323    EXPECT_TRUE(spirvText.find("RuntimeDescriptorArrayEXT") != std::string::npos)
324        << "SPIR-V should contain RuntimeDescriptorArrayEXT capability";
325
326    // 5. SPV_EXT_descriptor_indexing extension
327    EXPECT_TRUE(spirvText.find("SPV_EXT_descriptor_indexing") != std::string::npos)
328        << "SPIR-V should contain SPV_EXT_descriptor_indexing extension";
329}
330
331} // anonymous namespace
332} // namespace glslangtest