// 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 #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(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(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(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(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(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(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(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 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