From 8941905f8589375e783b019ee877bc72c923b7de Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 9 Jan 2026 17:59:06 +0000 Subject: [PATCH] ROADMAP.md --- CMakeLists.txt | 20 ++++ ROADMAP.md | 4 +- src/services/impl/config_compiler_service.cpp | 98 +++++++++++++++ src/services/impl/glsl_shader_system.cpp | 7 +- src/services/impl/glsl_shader_system.hpp | 1 + src/services/impl/materialx_shader_system.cpp | 7 +- src/services/impl/materialx_shader_system.hpp | 1 + src/services/impl/shader_system_registry.cpp | 25 ++++ src/services/impl/shader_system_registry.hpp | 5 + .../interfaces/i_shader_system_registry.hpp | 13 ++ ...fig_compiler_reference_validation_test.cpp | 38 ++++++ tests/shader_system_registry_test.cpp | 112 ++++++++++++++++++ 12 files changed, 327 insertions(+), 4 deletions(-) create mode 100644 tests/shader_system_registry_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index cd9d3c7..e02e504 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/ROADMAP.md b/ROADMAP.md index c2a8494..0e6d0cf 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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. diff --git a/src/services/impl/config_compiler_service.cpp b/src/services/impl/config_compiler_service.cpp index 34e7426..382cbbc 100644 --- a/src/services/impl/config_compiler_service.cpp +++ b/src/services/impl/config_compiler_service.cpp @@ -185,6 +185,86 @@ ConfigCompilerResult ConfigCompilerService::Compile(const std::string& configJso const std::string assetsPath = "/assets"; std::unordered_set textureIds; std::unordered_set shaderIds; + std::unordered_map 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); diff --git a/src/services/impl/glsl_shader_system.cpp b/src/services/impl/glsl_shader_system.cpp index 1a2159f..2de5382 100644 --- a/src/services/impl/glsl_shader_system.cpp +++ b/src/services/impl/glsl_shader_system.cpp @@ -60,6 +60,7 @@ std::unordered_map GlslShaderSystem::BuildShaderMap() throw std::runtime_error("No GLSL shaders found in assets.shaders"); } + lastShaderMap_ = shaderMap; return shaderMap; } @@ -75,7 +76,11 @@ std::vector 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 diff --git a/src/services/impl/glsl_shader_system.hpp b/src/services/impl/glsl_shader_system.hpp index 9369311..f7e7a67 100644 --- a/src/services/impl/glsl_shader_system.hpp +++ b/src/services/impl/glsl_shader_system.hpp @@ -31,6 +31,7 @@ private: std::shared_ptr configService_; std::shared_ptr configCompilerService_; std::shared_ptr logger_; + std::unordered_map lastShaderMap_; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/materialx_shader_system.cpp b/src/services/impl/materialx_shader_system.cpp index ab899dd..b84c2cd 100644 --- a/src/services/impl/materialx_shader_system.cpp +++ b/src/services/impl/materialx_shader_system.cpp @@ -115,6 +115,7 @@ std::unordered_map MaterialXShaderSystem::BuildShaderM throw std::runtime_error("No MaterialX shaders were generated from JSON config"); } + lastShaderMap_ = shaderMap; return shaderMap; } @@ -130,7 +131,11 @@ std::vector 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 diff --git a/src/services/impl/materialx_shader_system.hpp b/src/services/impl/materialx_shader_system.hpp index 3d74d6c..947d8be 100644 --- a/src/services/impl/materialx_shader_system.hpp +++ b/src/services/impl/materialx_shader_system.hpp @@ -33,6 +33,7 @@ private: std::shared_ptr scriptEngineService_; std::shared_ptr logger_; MaterialXShaderGenerator materialxGenerator_; + std::unordered_map lastShaderMap_; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/shader_system_registry.cpp b/src/services/impl/shader_system_registry.cpp index e777553..98796bd 100644 --- a/src/services/impl/shader_system_registry.cpp +++ b/src/services/impl/shader_system_registry.cpp @@ -45,6 +45,31 @@ std::unordered_map 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 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(); } diff --git a/src/services/impl/shader_system_registry.hpp b/src/services/impl/shader_system_registry.hpp index 909b9c0..f7ed90e 100644 --- a/src/services/impl/shader_system_registry.hpp +++ b/src/services/impl/shader_system_registry.hpp @@ -26,6 +26,11 @@ public: std::unordered_map BuildShaderMap() override; + ShaderReflection GetReflection(const std::string& shaderKey) const override; + + std::vector GetDefaultTextures( + const std::string& shaderKey) const override; + std::string GetActiveSystemId() const override; private: diff --git a/src/services/interfaces/i_shader_system_registry.hpp b/src/services/interfaces/i_shader_system_registry.hpp index 14dd8f9..043df39 100644 --- a/src/services/interfaces/i_shader_system_registry.hpp +++ b/src/services/interfaces/i_shader_system_registry.hpp @@ -1,9 +1,11 @@ #pragma once #include "graphics_types.hpp" +#include "shader_system_types.hpp" #include #include +#include namespace sdl3cpp::services { @@ -19,6 +21,17 @@ public: */ virtual std::unordered_map 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 GetDefaultTextures( + const std::string& shaderKey) const = 0; + /** * @brief Resolve the active shader system id. */ diff --git a/tests/config_compiler_reference_validation_test.cpp b/tests/config_compiler_reference_validation_test.cpp index 541ead5..14eac2e 100644 --- a/tests/config_compiler_reference_validation_test.cpp +++ b/tests/config_compiler_reference_validation_test.cpp @@ -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 diff --git a/tests/shader_system_registry_test.cpp b/tests/shader_system_registry_test.cpp new file mode 100644 index 0000000..3e9e43d --- /dev/null +++ b/tests/shader_system_registry_test.cpp @@ -0,0 +1,112 @@ +#include + +#include "services/impl/config_compiler_service.hpp" +#include "services/impl/shader_system_registry.hpp" + +#include + +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& 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 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(json); + auto configCompiler = std::make_shared( + 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(json); + auto configCompiler = std::make_shared( + 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