| // Copyright (C) 2025 NVIDIA Corporation |
| // |
| // Permission is hereby granted, free of charge, to any person obtaining a copy |
| // of this software and associated documentation files (the "Software"), to deal |
| // in the Software without restriction, including without limitation the rights |
| // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell |
| // copies of the Software, and to permit persons to whom the Software is |
| // furnished to do so, subject to the following conditions: |
| // |
| // The above copyright notice and this permission notice shall be included in all |
| // copies or substantial portions of the Software. |
| // |
| // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR |
| // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, |
| // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE |
| // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER |
| // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, |
| // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
| // SOFTWARE. |
| |
| #include <gtest/gtest.h> |
| |
| #include "TestFixture.h" |
| |
| namespace glslangtest { |
| namespace { |
| |
| class UboUnsizedArrayTest : public GlslangTest<::testing::Test> { |
| protected: |
| // Helper function to compile shader and check for specific error message. |
| bool compileShouldFailWith(const std::string& code, const std::string& expectedError, |
| EShLanguage stage = EShLangVertex) |
| { |
| glslang::TShader shader(stage); |
| EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); |
| |
| bool success = compile(&shader, code, "", controls); |
| |
| if (success) { |
| // Compilation should have failed. |
| return false; |
| } |
| |
| std::string errorLog = shader.getInfoLog(); |
| return errorLog.find(expectedError) != std::string::npos; |
| } |
| }; |
| |
| // Test that unsized arrays in uniform blocks work when extension is enabled. |
| TEST_F(UboUnsizedArrayTest, BasicFunctionality) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; // unsized array as last member |
| }; |
| |
| void main() { |
| gl_Position = vec4(values[0] * scale, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| glslang::TShader shader(EShLangVertex); |
| EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); |
| EXPECT_TRUE(compile(&shader, code, "", controls)); |
| } |
| |
| // Test that unsized arrays work when extension is enabled. |
| TEST_F(UboUnsizedArrayTest, ExtensionRequired) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; // Should work with extension |
| }; |
| |
| void main() { |
| gl_Position = vec4(values[0] * scale, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| glslang::TShader shader(EShLangVertex); |
| EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); |
| EXPECT_TRUE(compile(&shader, code, "", controls)); |
| } |
| |
| // Test that only the last member can be unsized. |
| TEST_F(UboUnsizedArrayTest, OnlyLastMemberCanBeUnsized) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; // Last member - should work |
| }; |
| |
| void main() { |
| gl_Position = vec4(values[0] * scale, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| glslang::TShader shader(EShLangVertex); |
| EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); |
| EXPECT_TRUE(compile(&shader, code, "", controls)); |
| } |
| |
| // Test that .length() method fails on unsized arrays. |
| TEST_F(UboUnsizedArrayTest, LengthMethodNotAllowed) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; |
| }; |
| |
| layout(location = 0) out vec4 fragColor; |
| |
| void main() { |
| int len = values.length(); // Should fail - length() not supported for unsized arrays in uniform blocks |
| fragColor = vec4(len, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| EXPECT_TRUE( |
| compileShouldFailWith(code, "array must be declared with a size before using this method", EShLangFragment)); |
| } |
| |
| // Test that function parameters cannot be unsized arrays from uniform blocks. |
| TEST_F(UboUnsizedArrayTest, FunctionParameterRestriction) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; |
| }; |
| |
| void processArray(float arr[10]) { |
| // Process array |
| } |
| |
| void main() { |
| processArray(values); // Should fail - cannot pass unsized arrays as function arguments |
| gl_Position = vec4(0.0, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| EXPECT_TRUE(compileShouldFailWith(code, "no matching overloaded function found")); |
| } |
| |
| // Test negative constant indexing. |
| TEST_F(UboUnsizedArrayTest, NegativeIndexingNotAllowed) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; |
| }; |
| |
| void main() { |
| float value = values[-1]; // Should fail |
| gl_Position = vec4(value * scale, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| EXPECT_TRUE(compileShouldFailWith(code, "index out of range")); |
| } |
| |
| // Test that different data types work correctly. |
| TEST_F(UboUnsizedArrayTest, MultipleDataTypes) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform FloatBlock { |
| float scale; |
| float floatValues[]; |
| }; |
| |
| layout(std140, binding=1) uniform IntBlock { |
| int count; |
| int intValues[]; |
| }; |
| |
| layout(std140, binding=2) uniform VecBlock { |
| mat4 transform; |
| vec4 vecValues[]; |
| }; |
| |
| void main() { |
| int baseIndex = gl_VertexIndex % 10; |
| float value = floatValues[baseIndex] * scale; |
| int ivalue = intValues[baseIndex] * count; |
| vec4 vvalue = vecValues[baseIndex] * transform; |
| |
| gl_Position = vec4(value + float(ivalue), vvalue.xy, 1.0); |
| } |
| )"; |
| |
| glslang::TShader shader(EShLangVertex); |
| EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); |
| EXPECT_TRUE(compile(&shader, code, "", controls)); |
| } |
| |
| // Test that general integer expressions work for indexing. |
| TEST_F(UboUnsizedArrayTest, GeneralIntegerIndexing) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; |
| }; |
| |
| layout(std140, binding=1) uniform SizeInfo { |
| int arraySize; |
| }; |
| |
| void main() { |
| // Various forms of general integer expressions |
| int baseIndex = gl_VertexIndex % arraySize; |
| int offsetIndex = (baseIndex + 1) % arraySize; |
| int computedIndex = min(baseIndex + offsetIndex, arraySize - 1); |
| |
| float result = values[baseIndex] + values[offsetIndex] + values[computedIndex]; |
| gl_Position = vec4(result * scale, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| glslang::TShader shader(EShLangVertex); |
| EShMessages controls = static_cast<EShMessages>(EShMsgDefault | EShMsgSpvRules | EShMsgVulkanRules); |
| EXPECT_TRUE(compile(&shader, code, "", controls)); |
| } |
| |
| // Test SPIR-V generation for unsized arrays in uniform blocks. |
| TEST_F(UboUnsizedArrayTest, SpvGeneration) |
| { |
| const std::string code = R"( |
| #version 450 |
| #extension GL_EXT_uniform_buffer_unsized_array : require |
| |
| layout(std140, binding=0) uniform DataBlock { |
| float scale; |
| float values[]; |
| }; |
| |
| layout(std140, binding=1) uniform SizeBlock { |
| int arraySize; |
| }; |
| |
| void main() { |
| int index = gl_VertexIndex % arraySize; |
| float value = values[index]; |
| gl_Position = vec4(value * scale, 0.0, 0.0, 1.0); |
| } |
| )"; |
| |
| // Compile the shader. |
| glslang::TShader shader(EShLangVertex); |
| const char* shaderStrings[1] = {code.c_str()}; |
| shader.setStrings(shaderStrings, 1); |
| |
| // Set up compilation options. |
| EShMessages messages = static_cast<EShMessages>(EShMsgSpvRules | EShMsgVulkanRules); |
| shader.setEnvInput(glslang::EShSourceGlsl, EShLangVertex, glslang::EShClientVulkan, 450); |
| shader.setEnvClient(glslang::EShClientVulkan, glslang::EShTargetVulkan_1_0); |
| shader.setEnvTarget(glslang::EShTargetSpv, glslang::EShTargetSpv_1_0); |
| |
| // Compile. |
| bool success = shader.parse(GetDefaultResources(), 450, false, messages); |
| EXPECT_TRUE(success) << "Shader compilation failed: " << shader.getInfoLog(); |
| |
| // Link and generate SPIR-V. |
| glslang::TProgram program; |
| program.addShader(&shader); |
| success = program.link(messages); |
| EXPECT_TRUE(success) << "Program linking failed: " << program.getInfoLog(); |
| |
| // Generate SPIR-V. |
| spv::SpvBuildLogger logger; |
| std::vector<uint32_t> spirv; |
| glslang::SpvOptions options; |
| glslang::GlslangToSpv(*program.getIntermediate(EShLangVertex), spirv, &logger, &options); |
| |
| // Disassemble SPIR-V to text for easier checking. |
| std::ostringstream disassembly_stream; |
| spv::Disassemble(disassembly_stream, spirv); |
| std::string spirvText = disassembly_stream.str(); |
| |
| // Check for key SPIR-V elements that indicate successful compilation. |
| // 1. SourceExtension for the extension |
| EXPECT_TRUE(spirvText.find("SourceExtension") != std::string::npos) |
| << "SPIR-V should contain SourceExtension for GL_EXT_uniform_buffer_unsized_array"; |
| |
| // 2. TypeRuntimeArray for the unsized array |
| EXPECT_TRUE(spirvText.find("TypeRuntimeArray") != std::string::npos) |
| << "SPIR-V should contain TypeRuntimeArray for unsized arrays"; |
| |
| // 3. Block decoration (for uniform blocks with runtime arrays) |
| EXPECT_TRUE(spirvText.find("Block") != std::string::npos) |
| << "SPIR-V should contain Block decoration for uniform blocks with runtime arrays"; |
| |
| // 4. RuntimeDescriptorArrayEXT capability |
| EXPECT_TRUE(spirvText.find("RuntimeDescriptorArrayEXT") != std::string::npos) |
| << "SPIR-V should contain RuntimeDescriptorArrayEXT capability"; |
| |
| // 5. SPV_EXT_descriptor_indexing extension |
| EXPECT_TRUE(spirvText.find("SPV_EXT_descriptor_indexing") != std::string::npos) |
| << "SPIR-V should contain SPV_EXT_descriptor_indexing extension"; |
| } |
| |
| } // anonymous namespace |
| } // namespace glslangtest |