ROADMAP.md

This commit is contained in:
2026-01-09 17:59:06 +00:00
parent 7b288c2d50
commit 8941905f85
12 changed files with 327 additions and 4 deletions

View File

@@ -758,6 +758,26 @@ target_link_libraries(config_compiler_reference_validation_test PRIVATE
)
add_test(NAME config_compiler_reference_validation_test COMMAND config_compiler_reference_validation_test)
# Test: Shader system registry selection
add_executable(shader_system_registry_test
tests/shader_system_registry_test.cpp
src/services/impl/config_compiler_service.cpp
src/services/impl/shader_system_registry.cpp
src/services/impl/glsl_shader_system.cpp
src/services/impl/materialx_shader_system.cpp
src/services/impl/materialx_shader_generator.cpp
src/services/impl/shader_pipeline_validator.cpp
)
target_include_directories(shader_system_registry_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(shader_system_registry_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
target_link_libraries(shader_system_registry_test PRIVATE
GTest::gtest
GTest::gtest_main
rapidjson
${SDL3CPP_RENDER_STACK_LIBS}
)
add_test(NAME shader_system_registry_test COMMAND shader_system_registry_test)
# Test: Bgfx Draw bounds validation (TDD test for buffer overflow crash)
add_executable(bgfx_draw_bounds_validation_test
tests/bgfx_draw_bounds_validation_test.cpp

View File

@@ -94,7 +94,7 @@ Treat JSON config as a declarative control plane that compiles into scene, resou
- Implement `MaterialXShaderSystem` using existing MaterialX generator logic.
- Update shader loading to use the selected shader system to build `ShaderPaths`.
- Deliverable: shader generation/compilation becomes a plugin choice, not hardcoded.
- Status: `IShaderSystem` + registry wired into shader loading, with `materialx` and `glsl` systems registered (reflection/default textures stubbed).
- Status: `IShaderSystem` + registry wired into shader loading, with `materialx` and `glsl` systems registered; config compiler validates shader system declarations; default texture lookup is now exposed via the registry.
- Acceptance: MaterialX stays working, and a second stub system (e.g., `glsl`) can be registered without touching `IGraphicsService`.
### Phase 3: Resource IR → Runtime Resource Registry (3-6 days)
@@ -137,7 +137,7 @@ Treat JSON config as a declarative control plane that compiles into scene, resou
### Phase 8: Tests And Docs (2-5 days, overlaps phases)
- Add unit tests for config merge rules (`extends`, `@delete`).
- Add render graph validation tests for cycles and invalid outputs.
- Add shader system registry tests for multi-system support.
- Add shader system registry tests for multi-system support. (done)
- Update docs with a "Config First Pipeline" guide and known limitations.
- Deliverable: regression protection for the new pipeline.
- Acceptance: new tests pass alongside existing integration tests.

View File

@@ -185,6 +185,86 @@ ConfigCompilerResult ConfigCompilerService::Compile(const std::string& configJso
const std::string assetsPath = "/assets";
std::unordered_set<std::string> textureIds;
std::unordered_set<std::string> shaderIds;
std::unordered_map<std::string, bool> shaderSystems;
std::string activeShaderSystem;
const std::string shaderSystemsPath = "/shader_systems";
if (document.HasMember("shader_systems")) {
const auto& shaderSystemsValue = document["shader_systems"];
if (!shaderSystemsValue.IsObject()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"SHADER_SYSTEMS_TYPE",
shaderSystemsPath,
"shader_systems must be an object");
} else {
if (shaderSystemsValue.HasMember("active")) {
const auto& activeValue = shaderSystemsValue["active"];
if (!activeValue.IsString()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"SHADER_SYSTEMS_ACTIVE_TYPE",
JoinPath(shaderSystemsPath, "active"),
"shader_systems.active must be a string");
} else {
activeShaderSystem = activeValue.GetString();
}
}
if (shaderSystemsValue.HasMember("systems")) {
const auto& systemsValue = shaderSystemsValue["systems"];
const std::string systemsPath = JoinPath(shaderSystemsPath, "systems");
if (!systemsValue.IsObject()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"SHADER_SYSTEMS_ENTRIES_TYPE",
systemsPath,
"shader_systems.systems must be an object");
} else {
for (auto it = systemsValue.MemberBegin(); it != systemsValue.MemberEnd(); ++it) {
const std::string systemId = it->name.GetString();
const std::string systemPath = JoinPath(systemsPath, systemId);
bool enabled = true;
if (!it->value.IsObject()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"SHADER_SYSTEMS_ENTRY_TYPE",
systemPath,
"shader_systems.systems entries must be objects");
continue;
}
if (it->value.HasMember("enabled")) {
const auto& enabledValue = it->value["enabled"];
if (!enabledValue.IsBool()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"SHADER_SYSTEMS_ENABLED_TYPE",
JoinPath(systemPath, "enabled"),
"shader_systems.systems.enabled must be a boolean");
} else {
enabled = enabledValue.GetBool();
}
}
shaderSystems.emplace(systemId, enabled);
}
}
}
}
}
if (!activeShaderSystem.empty() && !shaderSystems.empty()) {
auto activeIt = shaderSystems.find(activeShaderSystem);
if (activeIt == shaderSystems.end()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"SHADER_SYSTEMS_ACTIVE_UNKNOWN",
JoinPath(shaderSystemsPath, "active"),
"shader_systems.active is not declared in shader_systems.systems");
} else if (!activeIt->second) {
AddDiagnostic(result,
ProbeSeverity::Warn,
"SHADER_SYSTEMS_ACTIVE_DISABLED",
JoinPath(shaderSystemsPath, "active"),
"shader_systems.active references a disabled shader system");
}
}
if (const auto* assetsValue = getObjectMember(document, "assets", assetsPath)) {
if (const auto* texturesValue = getObjectMember(*assetsValue, "textures", JoinPath(assetsPath, "textures"))) {
for (auto it = texturesValue->MemberBegin(); it != texturesValue->MemberEnd(); ++it) {
@@ -285,6 +365,24 @@ ConfigCompilerResult ConfigCompilerService::Compile(const std::string& configJso
if (!systemValid) {
continue;
}
if (!shader.system.empty() && !shaderSystems.empty()) {
auto systemIt = shaderSystems.find(shader.system);
if (systemIt == shaderSystems.end()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"ASSET_SHADER_SYSTEM_UNKNOWN",
JoinPath(shaderPath, "system"),
"Shader system is not declared in shader_systems.systems");
continue;
}
if (!systemIt->second) {
AddDiagnostic(result,
ProbeSeverity::Warn,
"ASSET_SHADER_SYSTEM_DISABLED",
JoinPath(shaderPath, "system"),
"Shader system is disabled in shader_systems.systems");
}
}
shader.jsonPath = shaderPath;
result.resources.shaders.push_back(std::move(shader));
shaderIds.insert(id);

View File

@@ -60,6 +60,7 @@ std::unordered_map<std::string, ShaderPaths> GlslShaderSystem::BuildShaderMap()
throw std::runtime_error("No GLSL shaders found in assets.shaders");
}
lastShaderMap_ = shaderMap;
return shaderMap;
}
@@ -75,7 +76,11 @@ std::vector<ShaderPaths::TextureBinding> GlslShaderSystem::GetDefaultTextures(
if (logger_) {
logger_->Trace("GlslShaderSystem", "GetDefaultTextures", "shaderKey=" + shaderKey);
}
return {};
auto it = lastShaderMap_.find(shaderKey);
if (it == lastShaderMap_.end()) {
return {};
}
return it->second.textures;
}
} // namespace sdl3cpp::services::impl

View File

@@ -31,6 +31,7 @@ private:
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<IConfigCompilerService> configCompilerService_;
std::shared_ptr<ILogger> logger_;
std::unordered_map<std::string, ShaderPaths> lastShaderMap_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -115,6 +115,7 @@ std::unordered_map<std::string, ShaderPaths> MaterialXShaderSystem::BuildShaderM
throw std::runtime_error("No MaterialX shaders were generated from JSON config");
}
lastShaderMap_ = shaderMap;
return shaderMap;
}
@@ -130,7 +131,11 @@ std::vector<ShaderPaths::TextureBinding> MaterialXShaderSystem::GetDefaultTextur
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "GetDefaultTextures", "shaderKey=" + shaderKey);
}
return {};
auto it = lastShaderMap_.find(shaderKey);
if (it == lastShaderMap_.end()) {
return {};
}
return it->second.textures;
}
} // namespace sdl3cpp::services::impl

View File

@@ -33,6 +33,7 @@ private:
std::shared_ptr<IScriptEngineService> scriptEngineService_;
std::shared_ptr<ILogger> logger_;
MaterialXShaderGenerator materialxGenerator_;
std::unordered_map<std::string, ShaderPaths> lastShaderMap_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -45,6 +45,31 @@ std::unordered_map<std::string, ShaderPaths> ShaderSystemRegistry::BuildShaderMa
return it->second->BuildShaderMap();
}
ShaderReflection ShaderSystemRegistry::GetReflection(const std::string& shaderKey) const {
const std::string activeSystem = ResolveActiveSystemId();
auto it = systems_.find(activeSystem);
if (it == systems_.end()) {
if (logger_) {
logger_->Warn("ShaderSystemRegistry::GetReflection: Active system not registered");
}
return {};
}
return it->second->GetReflection(shaderKey);
}
std::vector<ShaderPaths::TextureBinding> ShaderSystemRegistry::GetDefaultTextures(
const std::string& shaderKey) const {
const std::string activeSystem = ResolveActiveSystemId();
auto it = systems_.find(activeSystem);
if (it == systems_.end()) {
if (logger_) {
logger_->Warn("ShaderSystemRegistry::GetDefaultTextures: Active system not registered");
}
return {};
}
return it->second->GetDefaultTextures(shaderKey);
}
std::string ShaderSystemRegistry::GetActiveSystemId() const {
return ResolveActiveSystemId();
}

View File

@@ -26,6 +26,11 @@ public:
std::unordered_map<std::string, ShaderPaths> BuildShaderMap() override;
ShaderReflection GetReflection(const std::string& shaderKey) const override;
std::vector<ShaderPaths::TextureBinding> GetDefaultTextures(
const std::string& shaderKey) const override;
std::string GetActiveSystemId() const override;
private:

View File

@@ -1,9 +1,11 @@
#pragma once
#include "graphics_types.hpp"
#include "shader_system_types.hpp"
#include <string>
#include <unordered_map>
#include <vector>
namespace sdl3cpp::services {
@@ -19,6 +21,17 @@ public:
*/
virtual std::unordered_map<std::string, ShaderPaths> BuildShaderMap() = 0;
/**
* @brief Get reflection metadata for the active shader system.
*/
virtual ShaderReflection GetReflection(const std::string& shaderKey) const = 0;
/**
* @brief Get default textures for the active shader system.
*/
virtual std::vector<ShaderPaths::TextureBinding> GetDefaultTextures(
const std::string& shaderKey) const = 0;
/**
* @brief Resolve the active shader system id.
*/

View File

@@ -191,4 +191,42 @@ TEST(ConfigCompilerReferenceValidationTest, FlagsInvalidShaderSystemType) {
EXPECT_TRUE(HasDiagnosticCode(result.diagnostics, "ASSET_SHADER_SYSTEM_TYPE"));
}
TEST(ConfigCompilerReferenceValidationTest, FlagsUnknownShaderSystem) {
const std::string json = R"({
"shader_systems": {
"systems": {
"glsl": { "enabled": true }
}
},
"assets": {
"shaders": {
"pbr": { "vs": "shaders/pbr.vs", "fs": "shaders/pbr.fs", "system": "materialx" }
}
}
})";
sdl3cpp::services::impl::ConfigCompilerService compiler(nullptr, nullptr, nullptr, nullptr);
auto result = compiler.Compile(json);
EXPECT_FALSE(result.success);
EXPECT_TRUE(HasDiagnosticCode(result.diagnostics, "ASSET_SHADER_SYSTEM_UNKNOWN"));
}
TEST(ConfigCompilerReferenceValidationTest, FlagsUnknownActiveShaderSystem) {
const std::string json = R"({
"shader_systems": {
"active": "missing",
"systems": {
"glsl": { "enabled": true }
}
}
})";
sdl3cpp::services::impl::ConfigCompilerService compiler(nullptr, nullptr, nullptr, nullptr);
auto result = compiler.Compile(json);
EXPECT_FALSE(result.success);
EXPECT_TRUE(HasDiagnosticCode(result.diagnostics, "SHADER_SYSTEMS_ACTIVE_UNKNOWN"));
}
} // namespace

View File

@@ -0,0 +1,112 @@
#include <gtest/gtest.h>
#include "services/impl/config_compiler_service.hpp"
#include "services/impl/shader_system_registry.hpp"
#include <string>
namespace {
class StubConfigService final : public sdl3cpp::services::IConfigService {
public:
explicit StubConfigService(std::string json)
: configJson_(std::move(json)) {}
uint32_t GetWindowWidth() const override { return 0; }
uint32_t GetWindowHeight() const override { return 0; }
std::filesystem::path GetScriptPath() const override { return {}; }
bool IsLuaDebugEnabled() const override { return false; }
std::string GetWindowTitle() const override { return {}; }
sdl3cpp::services::SceneSource GetSceneSource() const override {
return sdl3cpp::services::SceneSource::Config;
}
const sdl3cpp::services::InputBindings& GetInputBindings() const override { return inputBindings_; }
const sdl3cpp::services::MouseGrabConfig& GetMouseGrabConfig() const override { return mouseGrabConfig_; }
const sdl3cpp::services::BgfxConfig& GetBgfxConfig() const override { return bgfxConfig_; }
const sdl3cpp::services::MaterialXConfig& GetMaterialXConfig() const override { return materialXConfig_; }
const std::vector<sdl3cpp::services::MaterialXMaterialConfig>& GetMaterialXMaterialConfigs() const override {
return materialXMaterials_;
}
const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; }
const sdl3cpp::services::RenderBudgetConfig& GetRenderBudgetConfig() const override { return budgets_; }
const sdl3cpp::services::CrashRecoveryConfig& GetCrashRecoveryConfig() const override { return crashRecovery_; }
const std::string& GetConfigJson() const override { return configJson_; }
private:
sdl3cpp::services::InputBindings inputBindings_{};
sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{};
sdl3cpp::services::BgfxConfig bgfxConfig_{};
sdl3cpp::services::MaterialXConfig materialXConfig_{};
std::vector<sdl3cpp::services::MaterialXMaterialConfig> materialXMaterials_{};
sdl3cpp::services::GuiFontConfig guiFontConfig_{};
sdl3cpp::services::RenderBudgetConfig budgets_{};
sdl3cpp::services::CrashRecoveryConfig crashRecovery_{};
std::string configJson_;
};
TEST(ShaderSystemRegistryTest, UsesActiveGlslSystem) {
const std::string json = R"({
"shader_systems": {
"active": "glsl"
},
"assets": {
"shaders": {
"flat": { "vs": "shaders/flat.vs", "fs": "shaders/flat.fs" }
}
}
})";
auto configService = std::make_shared<StubConfigService>(json);
auto configCompiler = std::make_shared<sdl3cpp::services::impl::ConfigCompilerService>(
configService,
nullptr,
nullptr,
nullptr);
sdl3cpp::services::impl::ShaderSystemRegistry registry(
configService,
configCompiler,
nullptr,
nullptr);
auto shaderMap = registry.BuildShaderMap();
EXPECT_EQ(registry.GetActiveSystemId(), "glsl");
auto it = shaderMap.find("flat");
ASSERT_NE(it, shaderMap.end());
EXPECT_EQ(it->second.vertex, "shaders/flat.vs");
EXPECT_EQ(it->second.fragment, "shaders/flat.fs");
}
TEST(ShaderSystemRegistryTest, FiltersShadersBySystem) {
const std::string json = R"({
"shader_systems": {
"active": "glsl"
},
"assets": {
"shaders": {
"mx": { "vs": "shaders/mx.vs", "fs": "shaders/mx.fs", "system": "materialx" },
"glsl": { "vs": "shaders/glsl.vs", "fs": "shaders/glsl.fs", "system": "glsl" },
"default": { "vs": "shaders/default.vs", "fs": "shaders/default.fs" }
}
}
})";
auto configService = std::make_shared<StubConfigService>(json);
auto configCompiler = std::make_shared<sdl3cpp::services::impl::ConfigCompilerService>(
configService,
nullptr,
nullptr,
nullptr);
sdl3cpp::services::impl::ShaderSystemRegistry registry(
configService,
configCompiler,
nullptr,
nullptr);
auto shaderMap = registry.BuildShaderMap();
EXPECT_EQ(shaderMap.size(), 2u);
EXPECT_NE(shaderMap.find("glsl"), shaderMap.end());
EXPECT_NE(shaderMap.find("default"), shaderMap.end());
EXPECT_EQ(shaderMap.find("mx"), shaderMap.end());
}
} // namespace