blob: eec7642e806e61cf8441dc532df76a5a5b4f209a [file] [edit]
// 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