mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
feat(shader): Enhance shader validation and refactor uniform handling in BgfxGuiService
This commit is contained in:
@@ -298,7 +298,6 @@ void BgfxGuiService::InitializeResources() {
|
||||
if (configService_) {
|
||||
const auto& materialConfig = configService_->GetMaterialXConfig();
|
||||
if (materialConfig.enabled && materialConfig.shaderKey == "gui") {
|
||||
usingMaterialX = true;
|
||||
try {
|
||||
ShaderPaths generated = materialxGenerator_.Generate(materialConfig, {});
|
||||
if (!generated.vertexSource.empty() && !generated.fragmentSource.empty()) {
|
||||
@@ -306,6 +305,7 @@ void BgfxGuiService::InitializeResources() {
|
||||
guiFragmentSourceOverride_ = std::move(generated.fragmentSource);
|
||||
vertexSource = guiVertexSourceOverride_.c_str();
|
||||
fragmentSource = guiFragmentSourceOverride_.c_str();
|
||||
usingMaterialX = true;
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGuiService", "InitializeResources",
|
||||
"Using MaterialX GUI shaders");
|
||||
@@ -321,9 +321,13 @@ void BgfxGuiService::InitializeResources() {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
usesMaterialXShaders_ = usingMaterialX;
|
||||
usesPredefinedModelViewProj_ = false;
|
||||
modelViewProjUniform_ = BGFX_INVALID_HANDLE;
|
||||
|
||||
// Create uniforms matching the shader we're using
|
||||
if (usingMaterialX) {
|
||||
if (usesMaterialXShaders_) {
|
||||
// MaterialX shaders use separate world and viewProjection matrices
|
||||
worldMatrixUniform_ = bgfx::createUniform("u_worldMatrix", bgfx::UniformType::Mat4);
|
||||
viewProjMatrixUniform_ = bgfx::createUniform("u_viewProjectionMatrix", bgfx::UniformType::Mat4);
|
||||
@@ -334,13 +338,13 @@ void BgfxGuiService::InitializeResources() {
|
||||
", sampler=" + std::to_string(bgfx::isValid(sampler_)));
|
||||
}
|
||||
} else {
|
||||
// Built-in shader uses combined modelViewProj matrix
|
||||
modelViewProjUniform_ = bgfx::createUniform("u_modelViewProj", bgfx::UniformType::Mat4);
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGuiService", "InitializeResources",
|
||||
"Built-in uniforms: modelViewProj=" + std::to_string(bgfx::isValid(modelViewProjUniform_)) +
|
||||
", sampler=" + std::to_string(bgfx::isValid(sampler_)));
|
||||
}
|
||||
modelViewProjUniform_ = BGFX_INVALID_HANDLE;
|
||||
usesPredefinedModelViewProj_ = false;
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGuiService", "InitializeResources",
|
||||
"GUI shader mode=" + std::string(usesMaterialXShaders_ ? "materialx" : "builtin"));
|
||||
}
|
||||
|
||||
program_ = CreateProgram(vertexSource, fragmentSource);
|
||||
@@ -743,7 +747,7 @@ void BgfxGuiService::SubmitQuad(const GuiVertex& v0,
|
||||
"], color=[" + std::to_string(v0.r) + "," + std::to_string(v0.g) + "," + std::to_string(v0.b) + "," + std::to_string(v0.a) +
|
||||
"], uv=[" + std::to_string(v0.u) + "," + std::to_string(v0.v) + "]");
|
||||
logger_->Trace("BgfxGuiService", "SubmitQuad",
|
||||
"uniforms: mvp=" + std::to_string(bgfx::isValid(modelViewProjUniform_)) +
|
||||
"uniforms: mode=" + std::string(usesMaterialXShaders_ ? "materialx" : "builtin") +
|
||||
", sampler=" + std::to_string(bgfx::isValid(sampler_)) +
|
||||
", program=" + std::to_string(bgfx::isValid(program_)) +
|
||||
", texture=" + std::to_string(bgfx::isValid(texture)) +
|
||||
@@ -755,19 +759,35 @@ void BgfxGuiService::SubmitQuad(const GuiVertex& v0,
|
||||
std::to_string(viewProjection_[3]) + "]");
|
||||
}
|
||||
|
||||
if (!bgfx::isValid(sampler_)) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGuiService::SubmitQuad: Sampler uniform not initialized");
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
SetScissor(scissor);
|
||||
bgfx::setTransform(identity);
|
||||
|
||||
// Use appropriate uniforms based on shader type
|
||||
if (bgfx::isValid(modelViewProjUniform_)) {
|
||||
// Built-in shader: single combined matrix
|
||||
bgfx::setUniform(modelViewProjUniform_, viewProjection_.data());
|
||||
} else if (bgfx::isValid(worldMatrixUniform_) && bgfx::isValid(viewProjMatrixUniform_)) {
|
||||
if (usesMaterialXShaders_) {
|
||||
if (!bgfx::isValid(worldMatrixUniform_) || !bgfx::isValid(viewProjMatrixUniform_)) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGuiService::SubmitQuad: MaterialX uniforms not initialized");
|
||||
}
|
||||
return;
|
||||
}
|
||||
// MaterialX shader: separate matrices
|
||||
bgfx::setUniform(worldMatrixUniform_, identity);
|
||||
bgfx::setUniform(viewProjMatrixUniform_, viewProjection_.data());
|
||||
} else if (logger_) {
|
||||
logger_->Error("BgfxGuiService::SubmitQuad: No valid uniforms for shader!");
|
||||
} else if (!usesPredefinedModelViewProj_) {
|
||||
if (!bgfx::isValid(modelViewProjUniform_)) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGuiService::SubmitQuad: GUI modelViewProj uniform not initialized");
|
||||
}
|
||||
return;
|
||||
}
|
||||
bgfx::setUniform(modelViewProjUniform_, viewProjection_.data());
|
||||
}
|
||||
bgfx::setTexture(0, sampler_, texture);
|
||||
bgfx::setVertexBuffer(0, &tvb, 0, 4);
|
||||
@@ -959,7 +979,7 @@ bgfx::TextureHandle BgfxGuiService::CreateTexture(const uint8_t* rgba,
|
||||
}
|
||||
|
||||
bgfx::ProgramHandle BgfxGuiService::CreateProgram(const char* vertexSource,
|
||||
const char* fragmentSource) const {
|
||||
const char* fragmentSource) {
|
||||
if (!vertexSource || !fragmentSource) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxGuiService::CreateProgram: null shader source");
|
||||
@@ -988,6 +1008,10 @@ bgfx::ProgramHandle BgfxGuiService::CreateProgram(const char* vertexSource,
|
||||
}
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
if (!usesMaterialXShaders_) {
|
||||
ResolveGuiMatrixUniform(vs);
|
||||
}
|
||||
|
||||
bgfx::ProgramHandle program = bgfx::createProgram(vs, fs, true);
|
||||
if (!bgfx::isValid(program) && logger_) {
|
||||
@@ -1038,6 +1062,55 @@ bgfx::ShaderHandle BgfxGuiService::CreateShader(const std::string& label,
|
||||
return compiler.CompileShader(label, source, isVertex, uniforms, attributes);
|
||||
}
|
||||
|
||||
void BgfxGuiService::ResolveGuiMatrixUniform(bgfx::ShaderHandle shader) {
|
||||
modelViewProjUniform_ = BGFX_INVALID_HANDLE;
|
||||
usesPredefinedModelViewProj_ = false;
|
||||
|
||||
if (!bgfx::isValid(shader)) {
|
||||
usesPredefinedModelViewProj_ = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const uint16_t uniformCount = bgfx::getShaderUniforms(shader, nullptr, 0);
|
||||
std::string modelViewProjName = "predefined";
|
||||
std::string uniformNames;
|
||||
|
||||
if (uniformCount > 0) {
|
||||
std::vector<bgfx::UniformHandle> uniforms(uniformCount);
|
||||
bgfx::getShaderUniforms(shader, uniforms.data(), uniformCount);
|
||||
|
||||
for (const auto& uniform : uniforms) {
|
||||
bgfx::UniformInfo info{};
|
||||
bgfx::getUniformInfo(uniform, info);
|
||||
if (!uniformNames.empty()) {
|
||||
uniformNames += ", ";
|
||||
}
|
||||
uniformNames += info.name;
|
||||
if (std::strcmp(info.name, "u_modelViewProj") == 0 ||
|
||||
std::strcmp(info.name, "UniformBuffer.u_modelViewProj") == 0) {
|
||||
modelViewProjUniform_ = uniform;
|
||||
modelViewProjName = info.name;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!bgfx::isValid(modelViewProjUniform_)) {
|
||||
usesPredefinedModelViewProj_ = true;
|
||||
}
|
||||
|
||||
if (logger_) {
|
||||
logger_->Trace("BgfxGuiService", "ResolveGuiMatrixUniform",
|
||||
"uniformCount=" + std::to_string(uniformCount) +
|
||||
", modelViewProj=" + modelViewProjName +
|
||||
(uniformNames.empty() ? "" : ", uniforms=[" + uniformNames + "]"));
|
||||
if (!uniformNames.empty() && modelViewProjName == "predefined") {
|
||||
logger_->Warn("BgfxGuiService::ResolveGuiMatrixUniform: u_modelViewProj not found; uniforms=[" +
|
||||
uniformNames + "]");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void BgfxGuiService::PruneTextCache() {
|
||||
if (textCache_.size() <= maxTextCacheEntries_) {
|
||||
return;
|
||||
|
||||
@@ -143,8 +143,9 @@ private:
|
||||
uint32_t width,
|
||||
uint32_t height,
|
||||
uint64_t flags) const;
|
||||
bgfx::ProgramHandle CreateProgram(const char* vertexSource, const char* fragmentSource) const;
|
||||
bgfx::ProgramHandle CreateProgram(const char* vertexSource, const char* fragmentSource);
|
||||
bgfx::ShaderHandle CreateShader(const std::string& label, const std::string& source, bool isVertex) const;
|
||||
void ResolveGuiMatrixUniform(bgfx::ShaderHandle shader);
|
||||
|
||||
std::shared_ptr<IConfigService> configService_;
|
||||
std::shared_ptr<ILogger> logger_;
|
||||
@@ -173,6 +174,8 @@ private:
|
||||
uint16_t viewId_ = 1;
|
||||
bool initialized_ = false;
|
||||
bool loggedMissingResources_ = false;
|
||||
bool usesMaterialXShaders_ = false;
|
||||
bool usesPredefinedModelViewProj_ = false;
|
||||
uint64_t frameIndex_ = 0;
|
||||
size_t maxTextCacheEntries_ = 256;
|
||||
size_t maxSvgCacheEntries_ = 64;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include "bgfx_shader_compiler.hpp"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdint>
|
||||
#include <cstring>
|
||||
#include <fstream>
|
||||
#include <stdexcept>
|
||||
@@ -39,6 +40,154 @@ const char* ShadercTargetName(bgfx::RendererType::Enum type) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ValidateBgfxShaderBinary(const std::vector<char>& buffer,
|
||||
char expectedType,
|
||||
std::string& error) {
|
||||
if (buffer.size() < sizeof(uint32_t) * 3 + sizeof(uint16_t)) {
|
||||
error = "buffer too small for header";
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
auto readBytes = [&](void* out, size_t size) {
|
||||
if (offset + size > buffer.size()) {
|
||||
return false;
|
||||
}
|
||||
std::memcpy(out, buffer.data() + offset, size);
|
||||
offset += size;
|
||||
return true;
|
||||
};
|
||||
|
||||
uint32_t magic = 0;
|
||||
if (!readBytes(&magic, sizeof(magic))) {
|
||||
error = "failed to read magic";
|
||||
return false;
|
||||
}
|
||||
|
||||
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 = (expectedType == 'v') ? vsh : (expectedType == 'c' ? csh : fsh);
|
||||
if (base != vsh && base != fsh && base != csh) {
|
||||
error = "invalid magic";
|
||||
return false;
|
||||
}
|
||||
if (base != expectedBase) {
|
||||
error = "shader type mismatch";
|
||||
return false;
|
||||
}
|
||||
|
||||
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(&hashIn, sizeof(hashIn)) || !readBytes(&hashOut, sizeof(hashOut))) {
|
||||
error = "failed to read hashes";
|
||||
return false;
|
||||
}
|
||||
|
||||
uint16_t uniformCount = 0;
|
||||
if (!readBytes(&uniformCount, sizeof(uniformCount))) {
|
||||
error = "failed to read uniform count";
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint16_t i = 0; i < uniformCount; ++i) {
|
||||
uint8_t nameSize = 0;
|
||||
if (!readBytes(&nameSize, sizeof(nameSize))) {
|
||||
error = "failed to read uniform name size";
|
||||
return false;
|
||||
}
|
||||
if (offset + nameSize > buffer.size()) {
|
||||
error = "uniform name out of bounds";
|
||||
return false;
|
||||
}
|
||||
offset += nameSize;
|
||||
|
||||
uint8_t type = 0;
|
||||
uint8_t num = 0;
|
||||
uint16_t regIndex = 0;
|
||||
uint16_t regCount = 0;
|
||||
if (!readBytes(&type, sizeof(type)) ||
|
||||
!readBytes(&num, sizeof(num)) ||
|
||||
!readBytes(®Index, sizeof(regIndex)) ||
|
||||
!readBytes(®Count, sizeof(regCount))) {
|
||||
error = "failed to read uniform metadata";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (hasTexData) {
|
||||
uint8_t texComponent = 0;
|
||||
uint8_t texDimension = 0;
|
||||
if (!readBytes(&texComponent, sizeof(texComponent)) ||
|
||||
!readBytes(&texDimension, sizeof(texDimension))) {
|
||||
error = "failed to read texture metadata";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
if (hasTexFormat) {
|
||||
uint16_t texFormat = 0;
|
||||
if (!readBytes(&texFormat, sizeof(texFormat))) {
|
||||
error = "failed to read texture format";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t shaderSize = 0;
|
||||
if (!readBytes(&shaderSize, sizeof(shaderSize))) {
|
||||
error = "failed to read shader size";
|
||||
return false;
|
||||
}
|
||||
if (shaderSize % 4 != 0) {
|
||||
error = "shader size not aligned";
|
||||
return false;
|
||||
}
|
||||
if (offset + shaderSize + 1 > buffer.size()) {
|
||||
error = "shader code out of bounds";
|
||||
return false;
|
||||
}
|
||||
|
||||
if (shaderSize >= sizeof(uint32_t)) {
|
||||
uint32_t spirvMagic = 0;
|
||||
std::memcpy(&spirvMagic, buffer.data() + offset, sizeof(uint32_t));
|
||||
if (spirvMagic != 0x07230203u) {
|
||||
error = "invalid SPIR-V magic";
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
offset += shaderSize + 1;
|
||||
|
||||
uint8_t numAttrs = 0;
|
||||
if (!readBytes(&numAttrs, sizeof(numAttrs))) {
|
||||
error = "failed to read attribute count";
|
||||
return false;
|
||||
}
|
||||
if (offset + static_cast<size_t>(numAttrs) * sizeof(uint16_t) > buffer.size()) {
|
||||
error = "attribute list out of bounds";
|
||||
return false;
|
||||
}
|
||||
offset += static_cast<size_t>(numAttrs) * sizeof(uint16_t);
|
||||
|
||||
uint16_t constantSize = 0;
|
||||
if (!readBytes(&constantSize, sizeof(constantSize))) {
|
||||
error = "failed to read constant size";
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
BgfxShaderCompiler::BgfxShaderCompiler(std::shared_ptr<ILogger> logger,
|
||||
@@ -107,6 +256,15 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader(
|
||||
", target=" + std::string(target) +
|
||||
", size=" + std::to_string(outSize));
|
||||
}
|
||||
std::string validationError;
|
||||
if (!ValidateBgfxShaderBinary(buffer, isVertex ? 'v' : 'f', validationError)) {
|
||||
compiledInMemory = false;
|
||||
buffer.clear();
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxShaderCompiler: invalid shader binary for " + label +
|
||||
" (" + validationError + ")");
|
||||
}
|
||||
}
|
||||
} else if (logger_) {
|
||||
logger_->Trace("BgfxShaderCompiler", "CompileShader",
|
||||
"in-memory compile failed for " + label +
|
||||
@@ -171,6 +329,15 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader(
|
||||
remove(tempOutputPath.c_str());
|
||||
}
|
||||
|
||||
std::string validationError;
|
||||
if (!ValidateBgfxShaderBinary(buffer, isVertex ? 'v' : 'f', validationError)) {
|
||||
if (logger_) {
|
||||
logger_->Error("BgfxShaderCompiler: invalid shader binary for " + label +
|
||||
" (" + validationError + ")");
|
||||
}
|
||||
return BGFX_INVALID_HANDLE;
|
||||
}
|
||||
|
||||
uint32_t binSize = static_cast<uint32_t>(buffer.size());
|
||||
const bgfx::Memory* mem = bgfx::copy(buffer.data(), binSize);
|
||||
bgfx::ShaderHandle handle = bgfx::createShader(mem);
|
||||
@@ -178,6 +345,9 @@ bgfx::ShaderHandle BgfxShaderCompiler::CompileShader(
|
||||
logger_->Error("BgfxShaderCompiler: bgfx::createShader failed for " + label +
|
||||
" (binSize=" + std::to_string(binSize) + ")");
|
||||
} else if (logger_) {
|
||||
logger_->Info("BgfxShaderCompiler: created shader " + label +
|
||||
" (binSize=" + std::to_string(binSize) +
|
||||
", renderer=" + std::string(RendererTypeName(rendererType)) + ")");
|
||||
logger_->Trace("BgfxShaderCompiler", "CompileShader",
|
||||
"label=" + label + " shader created successfully, handle=" + std::to_string(handle.idx));
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user