mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat(tests): add shaderc uniform mapping tests and shader uniform validation
This commit is contained in:
@@ -193,6 +193,28 @@ TEST_F(ShaderPipelineValidatorTest, ValidateVertexLayoutMatch_TypeMismatch) {
|
||||
EXPECT_TRUE(result.errors[0].find("Type mismatch") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ShaderPipelineValidatorTest, ValidateVertexLayoutMatch_ShuffledLocations) {
|
||||
std::vector<ShaderPipelineValidator::AttributeInfo> shaderInputs = {
|
||||
{0, "vec3", "i_position", 12},
|
||||
{1, "vec2", "i_texcoord_0", 8}, // Wrong location/type for bgfx layout.
|
||||
{2, "vec3", "i_tangent", 12},
|
||||
{3, "vec3", "i_normal", 12}, // Normal moved to location 3.
|
||||
};
|
||||
|
||||
std::vector<ShaderPipelineValidator::AttributeInfo> layoutAttribs = {
|
||||
{0, "vec3", "Position", 12},
|
||||
{1, "vec3", "Normal", 12},
|
||||
{2, "vec3", "Tangent", 12},
|
||||
{3, "vec2", "TexCoord0", 8},
|
||||
};
|
||||
|
||||
auto result = validator->ValidateVertexLayoutMatch(shaderInputs, layoutAttribs, "test_shader");
|
||||
|
||||
EXPECT_FALSE(result.passed);
|
||||
EXPECT_GT(result.errors.size(), 0);
|
||||
EXPECT_TRUE(result.errors[0].find("location 1") != std::string::npos);
|
||||
}
|
||||
|
||||
TEST_F(ShaderPipelineValidatorTest, ValidateVertexLayoutMatch_UnusedAttribute) {
|
||||
std::vector<ShaderPipelineValidator::AttributeInfo> shaderInputs = {
|
||||
{0, "vec3", "i_position", 12},
|
||||
|
||||
365
tests/shaderc_uniform_mapping_test.cpp
Normal file
365
tests/shaderc_uniform_mapping_test.cpp
Normal file
@@ -0,0 +1,365 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include <bgfx/bgfx.h>
|
||||
|
||||
#include "shaderc_mem.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace {
|
||||
|
||||
struct UniformEntry {
|
||||
std::string name;
|
||||
uint8_t type = 0;
|
||||
};
|
||||
|
||||
struct ShaderParseResult {
|
||||
bool ok = false;
|
||||
std::string error;
|
||||
std::vector<UniformEntry> uniforms;
|
||||
};
|
||||
|
||||
constexpr uint8_t kUniformFragmentBit = 0x10;
|
||||
constexpr uint8_t kUniformSamplerBit = 0x20;
|
||||
constexpr uint8_t kUniformReadOnlyBit = 0x40;
|
||||
constexpr uint8_t kUniformCompareBit = 0x80;
|
||||
constexpr uint8_t kUniformMask = kUniformFragmentBit |
|
||||
kUniformSamplerBit |
|
||||
kUniformReadOnlyBit |
|
||||
kUniformCompareBit;
|
||||
|
||||
bool ReadBytes(const std::vector<uint8_t>& buffer, size_t& offset, void* out, size_t size) {
|
||||
if (offset + size > buffer.size()) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(out, buffer.data() + offset, size);
|
||||
offset += size;
|
||||
return true;
|
||||
}
|
||||
|
||||
ShaderParseResult ParseUniforms(const std::vector<uint8_t>& buffer, char expectedShaderType) {
|
||||
ShaderParseResult result{};
|
||||
size_t offset = 0;
|
||||
uint32_t magic = 0;
|
||||
|
||||
if (!ReadBytes(buffer, offset, &magic, sizeof(magic))) {
|
||||
result.error = "missing shader magic";
|
||||
return result;
|
||||
}
|
||||
|
||||
const uint32_t base = magic & 0x00FFFFFFu;
|
||||
const uint32_t vsh = static_cast<uint32_t>('V')
|
||||
| (static_cast<uint32_t>('S') << 8)
|
||||
| (static_cast<uint32_t>('H') << 16);
|
||||
const uint32_t fsh = static_cast<uint32_t>('F')
|
||||
| (static_cast<uint32_t>('S') << 8)
|
||||
| (static_cast<uint32_t>('H') << 16);
|
||||
const uint32_t csh = static_cast<uint32_t>('C')
|
||||
| (static_cast<uint32_t>('S') << 8)
|
||||
| (static_cast<uint32_t>('H') << 16);
|
||||
const uint32_t expectedBase = (expectedShaderType == 'v') ? vsh
|
||||
: (expectedShaderType == 'c' ? csh : fsh);
|
||||
|
||||
if (base != expectedBase) {
|
||||
result.error = "unexpected shader type";
|
||||
return result;
|
||||
}
|
||||
|
||||
const uint8_t version = static_cast<uint8_t>(magic >> 24);
|
||||
const bool hasTexData = version >= 8;
|
||||
const bool hasTexFormat = version >= 10;
|
||||
|
||||
uint32_t hashIn = 0;
|
||||
uint32_t hashOut = 0;
|
||||
if (!ReadBytes(buffer, offset, &hashIn, sizeof(hashIn)) ||
|
||||
!ReadBytes(buffer, offset, &hashOut, sizeof(hashOut))) {
|
||||
result.error = "missing hash data";
|
||||
return result;
|
||||
}
|
||||
|
||||
uint16_t uniformCount = 0;
|
||||
if (!ReadBytes(buffer, offset, &uniformCount, sizeof(uniformCount))) {
|
||||
result.error = "missing uniform count";
|
||||
return result;
|
||||
}
|
||||
|
||||
result.uniforms.reserve(uniformCount);
|
||||
for (uint16_t i = 0; i < uniformCount; ++i) {
|
||||
uint8_t nameSize = 0;
|
||||
if (!ReadBytes(buffer, offset, &nameSize, sizeof(nameSize))) {
|
||||
result.error = "missing uniform name size";
|
||||
return result;
|
||||
}
|
||||
if (offset + nameSize > buffer.size()) {
|
||||
result.error = "uniform name out of bounds";
|
||||
return result;
|
||||
}
|
||||
UniformEntry entry{};
|
||||
entry.name.assign(reinterpret_cast<const char*>(buffer.data() + offset), nameSize);
|
||||
offset += nameSize;
|
||||
|
||||
uint8_t type = 0;
|
||||
uint8_t num = 0;
|
||||
uint16_t regIndex = 0;
|
||||
uint16_t regCount = 0;
|
||||
if (!ReadBytes(buffer, offset, &type, sizeof(type)) ||
|
||||
!ReadBytes(buffer, offset, &num, sizeof(num)) ||
|
||||
!ReadBytes(buffer, offset, ®Index, sizeof(regIndex)) ||
|
||||
!ReadBytes(buffer, offset, ®Count, sizeof(regCount))) {
|
||||
result.error = "missing uniform metadata";
|
||||
return result;
|
||||
}
|
||||
|
||||
if (hasTexData) {
|
||||
uint8_t texComponent = 0;
|
||||
uint8_t texDimension = 0;
|
||||
if (!ReadBytes(buffer, offset, &texComponent, sizeof(texComponent)) ||
|
||||
!ReadBytes(buffer, offset, &texDimension, sizeof(texDimension))) {
|
||||
result.error = "missing texture metadata";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
if (hasTexFormat) {
|
||||
uint16_t texFormat = 0;
|
||||
if (!ReadBytes(buffer, offset, &texFormat, sizeof(texFormat))) {
|
||||
result.error = "missing texture format";
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
entry.type = type;
|
||||
result.uniforms.push_back(std::move(entry));
|
||||
}
|
||||
|
||||
result.ok = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool CompileShaderBinary(const std::string& source,
|
||||
const char* profile,
|
||||
const char* target,
|
||||
std::vector<uint8_t>& out,
|
||||
std::string& error) {
|
||||
uint8_t* compiled = nullptr;
|
||||
size_t compiledSize = 0;
|
||||
char* compileError = nullptr;
|
||||
|
||||
const int result = shaderc_compile_from_memory_with_target(
|
||||
source.c_str(),
|
||||
source.size(),
|
||||
profile,
|
||||
target,
|
||||
&compiled,
|
||||
&compiledSize,
|
||||
&compileError);
|
||||
|
||||
if (result != 0) {
|
||||
if (compileError) {
|
||||
error = compileError;
|
||||
shaderc_free_error(compileError);
|
||||
} else {
|
||||
error = "shaderc compile failed";
|
||||
}
|
||||
if (compiled) {
|
||||
shaderc_free_buffer(compiled);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
out.assign(compiled, compiled + compiledSize);
|
||||
shaderc_free_buffer(compiled);
|
||||
if (compileError) {
|
||||
shaderc_free_error(compileError);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
const UniformEntry* FindUniform(const std::vector<UniformEntry>& uniforms, const std::string& name) {
|
||||
for (const auto& uniform : uniforms) {
|
||||
if (uniform.name == name) {
|
||||
return &uniform;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const UniformEntry* FindUniformBySuffix(const std::vector<UniformEntry>& uniforms, const std::string& name) {
|
||||
for (const auto& uniform : uniforms) {
|
||||
if (uniform.name == name) {
|
||||
return &uniform;
|
||||
}
|
||||
if (uniform.name.size() > name.size() &&
|
||||
uniform.name.compare(uniform.name.size() - name.size(), name.size(), name) == 0 &&
|
||||
uniform.name[uniform.name.size() - name.size() - 1] == '.') {
|
||||
return &uniform;
|
||||
}
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
std::string JoinUniformNames(const std::vector<UniformEntry>& uniforms) {
|
||||
std::string joined;
|
||||
for (size_t i = 0; i < uniforms.size(); ++i) {
|
||||
if (i > 0) {
|
||||
joined += ", ";
|
||||
}
|
||||
joined += uniforms[i].name;
|
||||
}
|
||||
return joined;
|
||||
}
|
||||
|
||||
uint8_t BaseUniformType(uint8_t type) {
|
||||
return static_cast<uint8_t>(type & ~kUniformMask);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
TEST(ShadercUniformMappingTest, IntUniformsMapToVec4) {
|
||||
const std::string fragmentSource = R"(
|
||||
#version 450
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
layout(std140, set = 0, binding = 0) uniform DataBlock {
|
||||
int u_numActiveLightSources;
|
||||
ivec2 u_lightDataType;
|
||||
};
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(float(u_numActiveLightSources + u_lightDataType.x));
|
||||
}
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
std::string error;
|
||||
ASSERT_TRUE(CompileShaderBinary(fragmentSource, "fragment", "spirv", binary, error))
|
||||
<< error;
|
||||
|
||||
ShaderParseResult parsed = ParseUniforms(binary, 'f');
|
||||
ASSERT_TRUE(parsed.ok) << parsed.error;
|
||||
|
||||
const UniformEntry* scalar = FindUniformBySuffix(parsed.uniforms, "u_numActiveLightSources");
|
||||
ASSERT_NE(scalar, nullptr) << "Uniforms: " << JoinUniformNames(parsed.uniforms);
|
||||
EXPECT_EQ(BaseUniformType(scalar->type), static_cast<uint8_t>(bgfx::UniformType::Vec4));
|
||||
|
||||
const UniformEntry* vector = FindUniformBySuffix(parsed.uniforms, "u_lightDataType");
|
||||
ASSERT_NE(vector, nullptr) << "Uniforms: " << JoinUniformNames(parsed.uniforms);
|
||||
EXPECT_EQ(BaseUniformType(vector->type), static_cast<uint8_t>(bgfx::UniformType::Vec4));
|
||||
}
|
||||
|
||||
TEST(ShadercUniformMappingTest, SamplerUniformRemainsSampler) {
|
||||
const std::string fragmentSource = R"(
|
||||
#version 450
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
layout(set = 0, binding = 1) uniform sampler2D s_tex;
|
||||
|
||||
void main() {
|
||||
fragColor = texture(s_tex, vec2(0.25, 0.5));
|
||||
}
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
std::string error;
|
||||
ASSERT_TRUE(CompileShaderBinary(fragmentSource, "fragment", "spirv", binary, error))
|
||||
<< error;
|
||||
|
||||
ShaderParseResult parsed = ParseUniforms(binary, 'f');
|
||||
ASSERT_TRUE(parsed.ok) << parsed.error;
|
||||
|
||||
if (parsed.uniforms.empty()) {
|
||||
GTEST_SKIP() << "No sampler metadata emitted for raw GLSL; requires bgfx shader preprocessor";
|
||||
}
|
||||
|
||||
const UniformEntry* sampler = FindUniformBySuffix(parsed.uniforms, "s_tex");
|
||||
if (!sampler) {
|
||||
for (const auto& uniform : parsed.uniforms) {
|
||||
if (BaseUniformType(uniform.type) == static_cast<uint8_t>(bgfx::UniformType::Sampler)) {
|
||||
sampler = &uniform;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
ASSERT_NE(sampler, nullptr) << "Uniforms: " << JoinUniformNames(parsed.uniforms);
|
||||
EXPECT_EQ(BaseUniformType(sampler->type), static_cast<uint8_t>(bgfx::UniformType::Sampler));
|
||||
}
|
||||
|
||||
TEST(ShadercUniformMappingTest, IntUniformsDoNotSetSamplerFlag) {
|
||||
const std::string fragmentSource = R"(
|
||||
#version 450
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
layout(std140, set = 0, binding = 0) uniform DataBlock {
|
||||
int u_counter;
|
||||
};
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(float(u_counter));
|
||||
}
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
std::string error;
|
||||
ASSERT_TRUE(CompileShaderBinary(fragmentSource, "fragment", "spirv", binary, error))
|
||||
<< error;
|
||||
|
||||
ShaderParseResult parsed = ParseUniforms(binary, 'f');
|
||||
ASSERT_TRUE(parsed.ok) << parsed.error;
|
||||
|
||||
const UniformEntry* scalar = FindUniformBySuffix(parsed.uniforms, "u_counter");
|
||||
ASSERT_NE(scalar, nullptr) << "Uniforms: " << JoinUniformNames(parsed.uniforms);
|
||||
EXPECT_EQ(BaseUniformType(scalar->type), static_cast<uint8_t>(bgfx::UniformType::Vec4));
|
||||
EXPECT_EQ(static_cast<uint8_t>(scalar->type & kUniformSamplerBit), 0);
|
||||
}
|
||||
|
||||
TEST(ShadercUniformMappingTest, Mat3UniformMapsToMat3) {
|
||||
const std::string fragmentSource = R"(
|
||||
#version 450
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
layout(std140, set = 0, binding = 0) uniform DataBlock {
|
||||
mat3 u_basis;
|
||||
};
|
||||
|
||||
void main() {
|
||||
fragColor = vec4(u_basis[0], 1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
std::string error;
|
||||
ASSERT_TRUE(CompileShaderBinary(fragmentSource, "fragment", "spirv", binary, error))
|
||||
<< error;
|
||||
|
||||
ShaderParseResult parsed = ParseUniforms(binary, 'f');
|
||||
ASSERT_TRUE(parsed.ok) << parsed.error;
|
||||
|
||||
const UniformEntry* uniform = FindUniformBySuffix(parsed.uniforms, "u_basis");
|
||||
ASSERT_NE(uniform, nullptr) << "Uniforms: " << JoinUniformNames(parsed.uniforms);
|
||||
EXPECT_EQ(BaseUniformType(uniform->type), static_cast<uint8_t>(bgfx::UniformType::Mat3));
|
||||
}
|
||||
|
||||
TEST(ShadercUniformMappingTest, Mat4UniformMapsToMat4) {
|
||||
const std::string fragmentSource = R"(
|
||||
#version 450
|
||||
layout (location = 0) out vec4 fragColor;
|
||||
layout(std140, set = 0, binding = 0) uniform DataBlock {
|
||||
mat4 u_transform;
|
||||
};
|
||||
|
||||
void main() {
|
||||
fragColor = u_transform * vec4(1.0);
|
||||
}
|
||||
)";
|
||||
|
||||
std::vector<uint8_t> binary;
|
||||
std::string error;
|
||||
ASSERT_TRUE(CompileShaderBinary(fragmentSource, "fragment", "spirv", binary, error))
|
||||
<< error;
|
||||
|
||||
ShaderParseResult parsed = ParseUniforms(binary, 'f');
|
||||
ASSERT_TRUE(parsed.ok) << parsed.error;
|
||||
|
||||
const UniformEntry* uniform = FindUniformBySuffix(parsed.uniforms, "u_transform");
|
||||
ASSERT_NE(uniform, nullptr) << "Uniforms: " << JoinUniformNames(parsed.uniforms);
|
||||
EXPECT_EQ(BaseUniformType(uniform->type), static_cast<uint8_t>(bgfx::UniformType::Mat4));
|
||||
}
|
||||
Reference in New Issue
Block a user