mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
ROADMAP.md
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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
|
||||
|
||||
112
tests/shader_system_registry_test.cpp
Normal file
112
tests/shader_system_registry_test.cpp
Normal 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
|
||||
Reference in New Issue
Block a user