ROADMAP.md

This commit is contained in:
2026-01-09 17:50:33 +00:00
parent 73eee3038e
commit 7b288c2d50
21 changed files with 1092 additions and 106 deletions

View File

@@ -281,6 +281,9 @@ if(BUILD_SDL3_APP)
src/services/impl/lua_helpers.cpp
src/services/impl/scene_script_service.cpp
src/services/impl/shader_script_service.cpp
src/services/impl/shader_system_registry.cpp
src/services/impl/materialx_shader_system.cpp
src/services/impl/glsl_shader_system.cpp
src/services/impl/materialx_shader_generator.cpp
src/services/impl/shader_pipeline_validator.cpp
src/services/impl/gui_script_service.cpp
@@ -394,6 +397,9 @@ add_executable(script_engine_tests
src/services/impl/lua_helpers.cpp
src/services/impl/scene_script_service.cpp
src/services/impl/shader_script_service.cpp
src/services/impl/shader_system_registry.cpp
src/services/impl/materialx_shader_system.cpp
src/services/impl/glsl_shader_system.cpp
src/services/impl/materialx_shader_generator.cpp
src/services/impl/shader_pipeline_validator.cpp
src/services/impl/sdl_window_service.cpp
@@ -408,6 +414,7 @@ target_include_directories(script_engine_tests PRIVATE "${CMAKE_CURRENT_SOURCE_D
target_link_libraries(script_engine_tests PRIVATE
${SDL_TARGET}
lua::lua
rapidjson
${SDL3CPP_RENDER_STACK_LIBS}
assimp::assimp
Bullet::Bullet
@@ -493,6 +500,34 @@ else()
endif()
add_test(NAME bgfx_gui_budget_enforcement_test COMMAND bgfx_gui_budget_enforcement_test)
# Test: Texture budget tracker (allocation + free)
add_executable(bgfx_texture_budget_tracker_test
tests/bgfx_texture_budget_tracker_test.cpp
src/services/impl/bgfx_graphics_backend.cpp
src/services/impl/bgfx_shader_compiler.cpp
src/stb_image.cpp
)
target_include_directories(bgfx_texture_budget_tracker_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src")
target_include_directories(bgfx_texture_budget_tracker_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc")
target_link_libraries(bgfx_texture_budget_tracker_test PRIVATE
GTest::gtest
GTest::gtest_main
${SDL3CPP_RENDER_STACK_LIBS}
${SDL3CPP_STB_LIBS}
glm::glm
SDL3::SDL3
)
if(TARGET shaderc_local)
target_link_libraries(bgfx_texture_budget_tracker_test PRIVATE shaderc_local)
elseif(TARGET shaderc::shaderc)
target_link_libraries(bgfx_texture_budget_tracker_test PRIVATE shaderc::shaderc)
elseif(TARGET shaderc::shaderc_combined)
target_link_libraries(bgfx_texture_budget_tracker_test PRIVATE shaderc::shaderc_combined)
else()
message(WARNING "shaderc CMake target not found; skipping link for bgfx_texture_budget_tracker_test.")
endif()
add_test(NAME bgfx_texture_budget_tracker_test COMMAND bgfx_texture_budget_tracker_test)
# Test: Vulkan shader linking (TDD test for walls/ceiling/floor rendering)
add_executable(vulkan_shader_linking_test
tests/test_vulkan_shader_linking.cpp

View File

@@ -16,6 +16,7 @@ Treat JSON config as a declarative control plane that compiles into scene, resou
## Current Snapshot (Codebase Audit)
- Config intake: version gating + schema validation + layered merges (`extends`, `@delete`) with JSON Pointer diagnostics.
- Config compiler builds Scene/Resource/RenderGraph IR, resolves asset/material/render-pass refs, and schedules a render pass DAG; IR is not yet driving runtime rendering.
- Schema now covers assets/materials/shaders, `shader_systems`, and render-pass `view_id` + `clear` metadata.
- Runtime rendering is still Lua-driven, with MaterialX shader generation, pipeline validation, sampler caps, and texture/GUI cache budget enforcement.
- Diagnostics include ProbeService reports plus CrashRecoveryService heartbeats/GPU hang detection; runtime probe hooks (draw/present/frame) are still missing.
@@ -83,6 +84,7 @@ Treat JSON config as a declarative control plane that compiles into scene, resou
- Extend schema to fully cover `assets`, `materials`, and `render.passes` (inputs/outputs, pass types).
- Add schema for render pass clear state, attachment format, and view metadata.
- Add a `shader_systems` section and allow per-shader system selection.
- Status: assets/materials/shaders + `shader_systems` + render-pass `view_id`/`clear` metadata are now in schema.
- Deliverable: schema guarantees all data needed for IR compilation and render execution.
- Acceptance: invalid configs fail with JSON Pointer diagnostics from schema validation.
@@ -92,6 +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).
- 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)
@@ -210,7 +213,7 @@ Option B: per-shader only
- [x] Graph validation tests for cycles and invalid dependencies
- [x] Pipeline compatibility tests (shader inputs vs mesh layouts)
- [x] Crash recovery timeout tests (`tests/crash_recovery_timeout_test.cpp`)
- [~] Budget enforcement tests (GUI cache pruning covered; texture/transient pool pending)
- [~] Budget enforcement tests (GUI cache pruning + texture tracker covered; transient pool pending)
- [ ] Smoke test: cube demo boots with config-first scene definition
## Test Strategy (Solid Coverage Plan)

View File

@@ -9,6 +9,24 @@
"items": {"type": "number"},
"minItems": 3,
"maxItems": 3
},
"float4": {
"type": "array",
"items": {"type": "number"},
"minItems": 4,
"maxItems": 4
},
"stringMap": {
"type": "object",
"additionalProperties": {"type": "string"}
},
"assetUri": {
"type": "object",
"properties": {
"uri": {"type": "string"}
},
"required": ["uri"],
"additionalProperties": true
}
},
"properties": {
@@ -157,6 +175,23 @@
},
"additionalProperties": true
},
"shader_systems": {
"type": "object",
"properties": {
"active": {"type": "string"},
"systems": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"enabled": {"type": "boolean"}
},
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"budgets": {
"type": "object",
"properties": {
@@ -167,6 +202,45 @@
},
"additionalProperties": true
},
"assets": {
"type": "object",
"properties": {
"textures": {
"type": "object",
"additionalProperties": {"$ref": "#/definitions/assetUri"}
},
"meshes": {
"type": "object",
"additionalProperties": {"$ref": "#/definitions/assetUri"}
},
"shaders": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"vs": {"type": "string"},
"fs": {"type": "string"},
"system": {"type": "string"}
},
"required": ["vs", "fs"],
"additionalProperties": true
}
}
},
"additionalProperties": true
},
"materials": {
"type": "object",
"additionalProperties": {
"type": "object",
"properties": {
"shader": {"type": "string"},
"textures": {"$ref": "#/definitions/stringMap"}
},
"required": ["shader"],
"additionalProperties": true
}
},
"crash_recovery": {
"type": "object",
"properties": {
@@ -207,6 +281,7 @@
"properties": {
"id": {"type": "string"},
"type": {"type": "string"},
"view_id": {"type": "integer", "minimum": 0},
"inputs": {
"type": "object",
"properties": {
@@ -233,6 +308,22 @@
]
}
},
"clear": {
"type": "object",
"properties": {
"flags": {
"type": "array",
"items": {
"type": "string",
"enum": ["color", "depth", "stencil"]
}
},
"color": {"$ref": "#/definitions/float4"},
"depth": {"type": "number"},
"stencil": {"type": "integer", "minimum": 0}
},
"additionalProperties": true
},
"parameters": {"type": "object", "additionalProperties": true}
},
"additionalProperties": true

View File

@@ -21,6 +21,7 @@
#include "services/impl/script_engine_service.hpp"
#include "services/impl/scene_script_service.hpp"
#include "services/impl/shader_script_service.hpp"
#include "services/impl/shader_system_registry.hpp"
#include "services/impl/gui_script_service.hpp"
#include "services/impl/audio_command_service.hpp"
#include "services/impl/physics_bridge_service.hpp"
@@ -38,6 +39,7 @@
#include "services/interfaces/i_platform_service.hpp"
#include "services/interfaces/i_probe_service.hpp"
#include "services/interfaces/i_render_graph_service.hpp"
#include "services/interfaces/i_shader_system_registry.hpp"
#include "services/interfaces/i_config_compiler_service.hpp"
#include <iostream>
#include <stdexcept>
@@ -284,13 +286,20 @@ void ServiceBasedApp::RegisterServices() {
registry_.GetService<services::IConfigService>(),
runtimeConfig_.luaDebug);
// Shader system registry (pluggable shader system selection)
registry_.RegisterService<services::IShaderSystemRegistry, services::impl::ShaderSystemRegistry>(
registry_.GetService<services::IConfigService>(),
registry_.GetService<services::IConfigCompilerService>(),
registry_.GetService<services::IScriptEngineService>(),
registry_.GetService<services::ILogger>());
// Script-facing services
registry_.RegisterService<services::ISceneScriptService, services::impl::SceneScriptService>(
registry_.GetService<services::IScriptEngineService>(),
registry_.GetService<services::ILogger>());
registry_.RegisterService<services::IShaderScriptService, services::impl::ShaderScriptService>(
registry_.GetService<services::IScriptEngineService>(),
registry_.GetService<services::IConfigService>(),
registry_.GetService<services::IShaderSystemRegistry>(),
registry_.GetService<services::ILogger>());
registry_.RegisterService<services::IGuiScriptService, services::impl::GuiScriptService>(
registry_.GetService<services::IScriptEngineService>(),

View File

@@ -56,6 +56,12 @@ private:
friend bgfx::TextureHandle LoadTextureFromFileForTest(const BgfxGraphicsBackend& backend,
const std::string& path,
uint64_t samplerFlags);
friend bool CanAllocateTextureBudgetForTest(const BgfxGraphicsBackend& backend, size_t bytes);
friend void AllocateTextureBudgetForTest(BgfxGraphicsBackend& backend, size_t bytes);
friend void FreeTextureBudgetForTest(BgfxGraphicsBackend& backend, size_t bytes);
friend void SetTextureBudgetMaxForTest(BgfxGraphicsBackend& backend, size_t maxBytes);
friend size_t GetTextureBudgetMaxForTest(const BgfxGraphicsBackend& backend);
friend size_t GetTextureBudgetUsedForTest(const BgfxGraphicsBackend& backend);
// Texture memory budget tracker to prevent GPU memory exhaustion
class TextureMemoryTracker {

View File

@@ -268,6 +268,23 @@ ConfigCompilerResult ConfigCompilerService::Compile(const std::string& configJso
shader.id = id;
shader.vertexPath = it->value["vs"].GetString();
shader.fragmentPath = it->value["fs"].GetString();
bool systemValid = true;
if (it->value.HasMember("system")) {
const auto& systemValue = it->value["system"];
if (!systemValue.IsString()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"ASSET_SHADER_SYSTEM_TYPE",
JoinPath(shaderPath, "system"),
"Shader system must be a string");
systemValid = false;
} else {
shader.system = systemValue.GetString();
}
}
if (!systemValid) {
continue;
}
shader.jsonPath = shaderPath;
result.resources.shaders.push_back(std::move(shader));
shaderIds.insert(id);
@@ -376,6 +393,162 @@ ConfigCompilerResult ConfigCompilerService::Compile(const std::string& configJso
if (passValue.HasMember("type") && passValue["type"].IsString()) {
pass.type = passValue["type"].GetString();
}
if (passValue.HasMember("view_id")) {
const auto& viewValue = passValue["view_id"];
const std::string viewPath = JoinPath(passPath, "view_id");
if (!viewValue.IsInt()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_VIEW_ID_TYPE",
viewPath,
"Render pass view_id must be an integer");
} else if (viewValue.GetInt() < 0) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_VIEW_ID_RANGE",
viewPath,
"Render pass view_id must be zero or greater");
} else {
pass.hasViewId = true;
pass.viewId = viewValue.GetInt();
}
}
if (passValue.HasMember("clear")) {
const auto& clearValue = passValue["clear"];
const std::string clearPath = JoinPath(passPath, "clear");
if (!clearValue.IsObject()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_TYPE",
clearPath,
"Render pass clear must be an object");
} else {
RenderPassClearIR clear;
clear.enabled = true;
clear.jsonPath = clearPath;
bool hasFlags = false;
bool hasColor = false;
bool hasDepth = false;
bool hasStencil = false;
if (clearValue.HasMember("flags")) {
const auto& flagsValue = clearValue["flags"];
const std::string flagsPath = JoinPath(clearPath, "flags");
if (!flagsValue.IsArray()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_FLAGS_TYPE",
flagsPath,
"Render pass clear flags must be an array");
} else {
hasFlags = true;
for (rapidjson::SizeType f = 0; f < flagsValue.Size(); ++f) {
const auto& flagValue = flagsValue[f];
if (!flagValue.IsString()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_FLAGS_VALUE",
flagsPath,
"Render pass clear flag entries must be strings");
continue;
}
const std::string flagName = flagValue.GetString();
if (flagName == "color") {
clear.clearColor = true;
} else if (flagName == "depth") {
clear.clearDepth = true;
} else if (flagName == "stencil") {
clear.clearStencil = true;
} else {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_FLAGS_VALUE",
flagsPath,
"Render pass clear flag must be color, depth, or stencil");
}
}
}
}
if (clearValue.HasMember("color")) {
const auto& colorValue = clearValue["color"];
const std::string colorPath = JoinPath(clearPath, "color");
if (!colorValue.IsArray() || colorValue.Size() != 4) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_COLOR",
colorPath,
"Render pass clear color must be a 4-element array");
} else {
bool colorValid = true;
for (rapidjson::SizeType c = 0; c < colorValue.Size(); ++c) {
if (!colorValue[c].IsNumber()) {
colorValid = false;
break;
}
}
if (!colorValid) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_COLOR",
colorPath,
"Render pass clear color values must be numbers");
} else {
for (rapidjson::SizeType c = 0; c < colorValue.Size(); ++c) {
clear.color[c] = static_cast<float>(colorValue[c].GetDouble());
}
hasColor = true;
}
}
}
if (clearValue.HasMember("depth")) {
const auto& depthValue = clearValue["depth"];
const std::string depthPath = JoinPath(clearPath, "depth");
if (!depthValue.IsNumber()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_DEPTH",
depthPath,
"Render pass clear depth must be a number");
} else {
clear.depth = static_cast<float>(depthValue.GetDouble());
hasDepth = true;
}
}
if (clearValue.HasMember("stencil")) {
const auto& stencilValue = clearValue["stencil"];
const std::string stencilPath = JoinPath(clearPath, "stencil");
if (!stencilValue.IsInt()) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_STENCIL",
stencilPath,
"Render pass clear stencil must be an integer");
} else if (stencilValue.GetInt() < 0) {
AddDiagnostic(result,
ProbeSeverity::Error,
"RG_CLEAR_STENCIL",
stencilPath,
"Render pass clear stencil must be zero or greater");
} else {
clear.stencil = stencilValue.GetInt();
hasStencil = true;
}
}
if (!hasFlags) {
if (hasColor) {
clear.clearColor = true;
}
if (hasDepth) {
clear.clearDepth = true;
}
if (hasStencil) {
clear.clearStencil = true;
}
}
pass.clear = std::move(clear);
}
}
outputsByPass.emplace(pass.id, std::unordered_set<std::string>{});
if (passValue.HasMember("inputs")) {

View File

@@ -0,0 +1,81 @@
#include "glsl_shader_system.hpp"
#include <stdexcept>
#include <string>
namespace sdl3cpp::services::impl {
GlslShaderSystem::GlslShaderSystem(std::shared_ptr<IConfigService> configService,
std::shared_ptr<IConfigCompilerService> configCompilerService,
std::shared_ptr<ILogger> logger)
: configService_(std::move(configService)),
configCompilerService_(std::move(configCompilerService)),
logger_(std::move(logger)) {
if (logger_) {
logger_->Trace("GlslShaderSystem", "GlslShaderSystem",
"configService=" + std::string(configService_ ? "set" : "null") +
", configCompilerService=" + std::string(configCompilerService_ ? "set" : "null"));
}
}
std::unordered_map<std::string, ShaderPaths> GlslShaderSystem::BuildShaderMap() {
if (logger_) {
logger_->Trace("GlslShaderSystem", "BuildShaderMap", "source=assets.shaders");
}
if (!configService_ || !configCompilerService_) {
throw std::runtime_error("GlslShaderSystem requires config and compiler services");
}
const std::string& configJson = configService_->GetConfigJson();
if (configJson.empty()) {
throw std::runtime_error("GlslShaderSystem requires JSON config content");
}
const auto compileResult = configCompilerService_->Compile(configJson);
if (logger_) {
logger_->Trace("GlslShaderSystem", "BuildShaderMap",
"shaderCount=" + std::to_string(compileResult.resources.shaders.size()) +
", compileSuccess=" + std::string(compileResult.success ? "true" : "false"));
}
std::unordered_map<std::string, ShaderPaths> shaderMap;
for (const auto& shader : compileResult.resources.shaders) {
if (!shader.system.empty() && shader.system != GetId()) {
continue;
}
ShaderPaths paths;
paths.vertex = shader.vertexPath;
paths.fragment = shader.fragmentPath;
auto [it, inserted] = shaderMap.emplace(shader.id, std::move(paths));
if (!inserted && logger_) {
logger_->Warn("GlslShaderSystem::BuildShaderMap: Duplicate shader id skipped: " + shader.id);
}
}
if (shaderMap.empty()) {
if (logger_) {
logger_->Error("GlslShaderSystem::BuildShaderMap: No GLSL shaders found in assets.shaders");
}
throw std::runtime_error("No GLSL shaders found in assets.shaders");
}
return shaderMap;
}
ShaderReflection GlslShaderSystem::GetReflection(const std::string& shaderKey) const {
if (logger_) {
logger_->Trace("GlslShaderSystem", "GetReflection", "shaderKey=" + shaderKey);
}
return {};
}
std::vector<ShaderPaths::TextureBinding> GlslShaderSystem::GetDefaultTextures(
const std::string& shaderKey) const {
if (logger_) {
logger_->Trace("GlslShaderSystem", "GetDefaultTextures", "shaderKey=" + shaderKey);
}
return {};
}
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,36 @@
#pragma once
#include "../interfaces/i_config_compiler_service.hpp"
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_shader_system.hpp"
#include <memory>
#include <unordered_map>
namespace sdl3cpp::services::impl {
/**
* @brief GLSL shader system implementation backed by asset shader entries.
*/
class GlslShaderSystem final : public IShaderSystem {
public:
GlslShaderSystem(std::shared_ptr<IConfigService> configService,
std::shared_ptr<IConfigCompilerService> configCompilerService,
std::shared_ptr<ILogger> logger);
std::string GetId() const override { return "glsl"; }
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;
private:
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<IConfigCompilerService> configCompilerService_;
std::shared_ptr<ILogger> logger_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,136 @@
#include "materialx_shader_system.hpp"
#include <filesystem>
#include <stdexcept>
#include <string>
namespace sdl3cpp::services::impl {
MaterialXShaderSystem::MaterialXShaderSystem(std::shared_ptr<IConfigService> configService,
std::shared_ptr<IScriptEngineService> scriptEngineService,
std::shared_ptr<ILogger> logger)
: configService_(std::move(configService)),
scriptEngineService_(std::move(scriptEngineService)),
logger_(std::move(logger)),
materialxGenerator_(logger_) {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "MaterialXShaderSystem",
"configService=" + std::string(configService_ ? "set" : "null") +
", scriptEngineService=" + std::string(scriptEngineService_ ? "set" : "null"));
}
}
std::unordered_map<std::string, ShaderPaths> MaterialXShaderSystem::BuildShaderMap() {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "BuildShaderMap",
"Generating MaterialX shaders from config");
}
if (!configService_) {
throw std::runtime_error("MaterialXShaderSystem requires a config service");
}
std::unordered_map<std::string, ShaderPaths> shaderMap;
const auto& materialConfig = configService_->GetMaterialXConfig();
const auto& materialOverrides = configService_->GetMaterialXMaterialConfigs();
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "BuildShaderMap",
"materialOverrides=" + std::to_string(materialOverrides.size()) +
", baseEnabled=" + std::string(materialConfig.enabled ? "true" : "false"));
}
const auto scriptDirectory = scriptEngineService_
? scriptEngineService_->GetScriptDirectory()
: std::filesystem::path{};
const auto addShader = [&](const MaterialXConfig& config, const std::string& sourceLabel) {
if (!config.enabled) {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "BuildShaderMap",
"Skipping MaterialX shader generation (disabled) source=" + sourceLabel);
}
return;
}
if (config.shaderKey.empty()) {
if (logger_) {
logger_->Error("MaterialX shader generation requires a shaderKey (source=" + sourceLabel + ")");
}
return;
}
if (config.documentPath.empty() && !config.useConstantColor) {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "BuildShaderMap",
"Skipping MaterialX shader generation (no document/constant color) key=" +
config.shaderKey + ", source=" + sourceLabel);
}
return;
}
if (shaderMap.find(config.shaderKey) != shaderMap.end()) {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "BuildShaderMap",
"Skipping MaterialX shader generation (duplicate key) key=" +
config.shaderKey + ", source=" + sourceLabel);
}
return;
}
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "BuildShaderMap",
"Generating MaterialX shader key=" + config.shaderKey +
", document=" + config.documentPath.string() +
", material=" + config.materialName +
", source=" + sourceLabel);
}
try {
ShaderPaths materialShader = materialxGenerator_.Generate(config, scriptDirectory);
shaderMap[config.shaderKey] = std::move(materialShader);
} catch (const std::exception& ex) {
if (logger_) {
logger_->Error("MaterialX shader generation failed for key=" +
config.shaderKey + ": " + std::string(ex.what()));
}
}
};
for (const auto& overrideConfig : materialOverrides) {
if (!overrideConfig.enabled) {
continue;
}
MaterialXConfig resolvedConfig = materialConfig;
resolvedConfig.enabled = true;
resolvedConfig.documentPath = overrideConfig.documentPath;
resolvedConfig.shaderKey = overrideConfig.shaderKey;
resolvedConfig.materialName = overrideConfig.materialName;
resolvedConfig.useConstantColor = overrideConfig.useConstantColor;
resolvedConfig.constantColor = overrideConfig.constantColor;
addShader(resolvedConfig, "materialx_materials");
}
addShader(materialConfig, "materialx");
if (shaderMap.empty()) {
if (logger_) {
logger_->Error("No MaterialX shaders were generated from JSON config");
}
throw std::runtime_error("No MaterialX shaders were generated from JSON config");
}
return shaderMap;
}
ShaderReflection MaterialXShaderSystem::GetReflection(const std::string& shaderKey) const {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "GetReflection", "shaderKey=" + shaderKey);
}
return {};
}
std::vector<ShaderPaths::TextureBinding> MaterialXShaderSystem::GetDefaultTextures(
const std::string& shaderKey) const {
if (logger_) {
logger_->Trace("MaterialXShaderSystem", "GetDefaultTextures", "shaderKey=" + shaderKey);
}
return {};
}
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,38 @@
#pragma once
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_script_engine_service.hpp"
#include "../interfaces/i_shader_system.hpp"
#include "materialx_shader_generator.hpp"
#include <memory>
#include <unordered_map>
namespace sdl3cpp::services::impl {
/**
* @brief MaterialX-backed shader system implementation.
*/
class MaterialXShaderSystem final : public IShaderSystem {
public:
MaterialXShaderSystem(std::shared_ptr<IConfigService> configService,
std::shared_ptr<IScriptEngineService> scriptEngineService,
std::shared_ptr<ILogger> logger);
std::string GetId() const override { return "materialx"; }
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;
private:
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<IScriptEngineService> scriptEngineService_;
std::shared_ptr<ILogger> logger_;
MaterialXShaderGenerator materialxGenerator_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -2,7 +2,6 @@
#include "services/interfaces/i_logger.hpp"
#include <filesystem>
#include <stdexcept>
#include <string>
#include <unordered_map>
@@ -10,113 +9,23 @@
namespace sdl3cpp::services::impl {
ShaderScriptService::ShaderScriptService(std::shared_ptr<IScriptEngineService> engineService,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<IShaderSystemRegistry> shaderSystemRegistry,
std::shared_ptr<ILogger> logger)
: engineService_(std::move(engineService)),
configService_(std::move(configService)),
logger_(std::move(logger)),
materialxGenerator_(logger_) {
shaderSystemRegistry_(std::move(shaderSystemRegistry)),
logger_(std::move(logger)) {
if (logger_) {
logger_->Trace("ShaderScriptService", "ShaderScriptService",
"engineService=" + std::string(engineService_ ? "set" : "null") +
", configService=" + std::string(configService_ ? "set" : "null"));
", shaderSystemRegistry=" + std::string(shaderSystemRegistry_ ? "set" : "null"));
}
}
std::unordered_map<std::string, ShaderPaths> ShaderScriptService::LoadShaderPathsMap() {
if (logger_) {
logger_->Trace("ShaderScriptService", "LoadShaderPathsMap",
"Generating MaterialX shaders from JSON config only");
if (!shaderSystemRegistry_) {
throw std::runtime_error("Shader script service requires a shader system registry");
}
std::unordered_map<std::string, ShaderPaths> shaderMap;
if (configService_) {
const auto& materialConfig = configService_->GetMaterialXConfig();
const auto& materialOverrides = configService_->GetMaterialXMaterialConfigs();
if (logger_) {
logger_->Trace("ShaderScriptService", "LoadShaderPathsMap",
"materialOverrides=" + std::to_string(materialOverrides.size()) +
", baseEnabled=" + std::string(materialConfig.enabled ? "true" : "false"));
}
const auto scriptDirectory = engineService_
? engineService_->GetScriptDirectory()
: std::filesystem::path{};
const auto addShader = [&](const MaterialXConfig& config, const std::string& sourceLabel) {
if (!config.enabled) {
if (logger_) {
logger_->Trace("ShaderScriptService", "LoadShaderPathsMap",
"Skipping MaterialX shader generation (disabled) source=" + sourceLabel);
}
return;
}
if (config.shaderKey.empty()) {
if (logger_) {
logger_->Error("MaterialX shader generation requires a shaderKey (source=" + sourceLabel + ")");
}
return;
}
if (config.documentPath.empty() && !config.useConstantColor) {
if (logger_) {
logger_->Trace("ShaderScriptService", "LoadShaderPathsMap",
"Skipping MaterialX shader generation (no document/constant color) key=" +
config.shaderKey + ", source=" + sourceLabel);
}
return;
}
if (shaderMap.find(config.shaderKey) != shaderMap.end()) {
if (logger_) {
logger_->Trace("ShaderScriptService", "LoadShaderPathsMap",
"Skipping MaterialX shader generation (duplicate key) key=" +
config.shaderKey + ", source=" + sourceLabel);
}
return;
}
if (logger_) {
logger_->Trace("ShaderScriptService", "LoadShaderPathsMap",
"Generating MaterialX shader key=" + config.shaderKey +
", document=" + config.documentPath.string() +
", material=" + config.materialName +
", source=" + sourceLabel);
}
try {
ShaderPaths materialShader = materialxGenerator_.Generate(config, scriptDirectory);
shaderMap[config.shaderKey] = std::move(materialShader);
} catch (const std::exception& ex) {
if (logger_) {
logger_->Error("MaterialX shader generation failed for key=" +
config.shaderKey + ": " + std::string(ex.what()));
}
}
};
for (const auto& overrideConfig : materialOverrides) {
if (!overrideConfig.enabled) {
continue;
}
MaterialXConfig resolvedConfig = materialConfig;
resolvedConfig.enabled = true;
resolvedConfig.documentPath = overrideConfig.documentPath;
resolvedConfig.shaderKey = overrideConfig.shaderKey;
resolvedConfig.materialName = overrideConfig.materialName;
resolvedConfig.useConstantColor = overrideConfig.useConstantColor;
resolvedConfig.constantColor = overrideConfig.constantColor;
addShader(resolvedConfig, "materialx_materials");
}
addShader(materialConfig, "materialx");
}
if (shaderMap.empty()) {
if (logger_) {
logger_->Error("No MaterialX shaders were generated from JSON config");
}
throw std::runtime_error("No MaterialX shaders were generated from JSON config");
}
return shaderMap;
return shaderSystemRegistry_->BuildShaderMap();
}
lua_State* ShaderScriptService::GetLuaState() const {

View File

@@ -2,9 +2,8 @@
#include "../interfaces/i_shader_script_service.hpp"
#include "../interfaces/i_script_engine_service.hpp"
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "materialx_shader_generator.hpp"
#include "../interfaces/i_shader_system_registry.hpp"
#include <memory>
struct lua_State;
@@ -17,7 +16,7 @@ namespace sdl3cpp::services::impl {
class ShaderScriptService : public IShaderScriptService {
public:
ShaderScriptService(std::shared_ptr<IScriptEngineService> engineService,
std::shared_ptr<IConfigService> configService,
std::shared_ptr<IShaderSystemRegistry> shaderSystemRegistry,
std::shared_ptr<ILogger> logger);
std::unordered_map<std::string, ShaderPaths> LoadShaderPathsMap() override;
@@ -28,9 +27,8 @@ private:
std::string ResolveShaderPath(const std::string& path) const;
std::shared_ptr<IScriptEngineService> engineService_;
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<IShaderSystemRegistry> shaderSystemRegistry_;
std::shared_ptr<ILogger> logger_;
MaterialXShaderGenerator materialxGenerator_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,141 @@
#include "shader_system_registry.hpp"
#include <rapidjson/document.h>
#include <stdexcept>
#include <utility>
namespace sdl3cpp::services::impl {
ShaderSystemRegistry::ShaderSystemRegistry(std::shared_ptr<IConfigService> configService,
std::shared_ptr<IConfigCompilerService> configCompilerService,
std::shared_ptr<IScriptEngineService> scriptEngineService,
std::shared_ptr<ILogger> logger)
: configService_(std::move(configService)),
logger_(std::move(logger)) {
if (logger_) {
logger_->Trace("ShaderSystemRegistry", "ShaderSystemRegistry",
"configService=" + std::string(configService_ ? "set" : "null") +
", configCompilerService=" + std::string(configCompilerService ? "set" : "null") +
", scriptEngineService=" + std::string(scriptEngineService ? "set" : "null"));
}
if (configCompilerService) {
auto glslSystem = std::make_shared<GlslShaderSystem>(configService_, configCompilerService, logger_);
systems_.emplace(glslSystem->GetId(), std::move(glslSystem));
} else if (logger_) {
logger_->Warn("ShaderSystemRegistry: Config compiler missing; GLSL shader system disabled");
}
auto materialxSystem = std::make_shared<MaterialXShaderSystem>(configService_, scriptEngineService, logger_);
systems_.emplace(materialxSystem->GetId(), std::move(materialxSystem));
}
std::unordered_map<std::string, ShaderPaths> ShaderSystemRegistry::BuildShaderMap() {
const std::string activeSystem = ResolveActiveSystemId();
auto it = systems_.find(activeSystem);
if (it == systems_.end()) {
throw std::runtime_error("Active shader system not registered: " + activeSystem);
}
if (logger_) {
logger_->Trace("ShaderSystemRegistry", "BuildShaderMap", "activeSystem=" + activeSystem);
}
return it->second->BuildShaderMap();
}
std::string ShaderSystemRegistry::GetActiveSystemId() const {
return ResolveActiveSystemId();
}
std::string ShaderSystemRegistry::ResolveActiveSystemId() const {
std::string activeSystem = "materialx";
const std::string& configJson = configService_ ? configService_->GetConfigJson() : std::string{};
if (!configJson.empty()) {
rapidjson::Document document;
document.Parse(configJson.c_str());
if (document.IsObject() && document.HasMember("shader_systems")) {
const auto& shaderSystemsValue = document["shader_systems"];
if (shaderSystemsValue.IsObject() && shaderSystemsValue.HasMember("active")) {
const auto& activeValue = shaderSystemsValue["active"];
if (activeValue.IsString()) {
activeSystem = activeValue.GetString();
} else if (logger_) {
logger_->Warn("ShaderSystemRegistry::ResolveActiveSystemId: shader_systems.active must be a string");
}
}
}
}
if (!IsSystemEnabled(activeSystem)) {
if (logger_) {
logger_->Warn("ShaderSystemRegistry::ResolveActiveSystemId: active system disabled, falling back");
}
activeSystem = ResolveFallbackSystemId();
}
if (systems_.find(activeSystem) == systems_.end()) {
if (logger_) {
logger_->Warn("ShaderSystemRegistry::ResolveActiveSystemId: active system not registered, falling back");
}
activeSystem = ResolveFallbackSystemId();
}
if (activeSystem.empty()) {
return {};
}
return activeSystem;
}
std::string ShaderSystemRegistry::ResolveFallbackSystemId() const {
auto it = systems_.find("materialx");
if (it != systems_.end()) {
return it->first;
}
if (!systems_.empty()) {
return systems_.begin()->first;
}
return {};
}
bool ShaderSystemRegistry::IsSystemEnabled(const std::string& systemId) const {
if (systemId.empty()) {
return false;
}
const std::string& configJson = configService_ ? configService_->GetConfigJson() : std::string{};
if (configJson.empty()) {
return true;
}
rapidjson::Document document;
document.Parse(configJson.c_str());
if (!document.IsObject() || !document.HasMember("shader_systems")) {
return true;
}
const auto& shaderSystemsValue = document["shader_systems"];
if (!shaderSystemsValue.IsObject() || !shaderSystemsValue.HasMember("systems")) {
return true;
}
const auto& systemsValue = shaderSystemsValue["systems"];
if (!systemsValue.IsObject() || !systemsValue.HasMember(systemId.c_str())) {
return true;
}
const auto& systemValue = systemsValue[systemId.c_str()];
if (!systemValue.IsObject()) {
return true;
}
if (!systemValue.HasMember("enabled")) {
return true;
}
const auto& enabledValue = systemValue["enabled"];
if (!enabledValue.IsBool()) {
if (logger_) {
logger_->Warn("ShaderSystemRegistry::IsSystemEnabled: enabled must be boolean for system=" + systemId);
}
return true;
}
return enabledValue.GetBool();
}
} // namespace sdl3cpp::services::impl

View File

@@ -0,0 +1,41 @@
#pragma once
#include "../interfaces/i_config_compiler_service.hpp"
#include "../interfaces/i_config_service.hpp"
#include "../interfaces/i_logger.hpp"
#include "../interfaces/i_script_engine_service.hpp"
#include "../interfaces/i_shader_system_registry.hpp"
#include "glsl_shader_system.hpp"
#include "materialx_shader_system.hpp"
#include <memory>
#include <string>
#include <unordered_map>
namespace sdl3cpp::services::impl {
/**
* @brief Registry for shader systems with active system selection.
*/
class ShaderSystemRegistry final : public IShaderSystemRegistry {
public:
ShaderSystemRegistry(std::shared_ptr<IConfigService> configService,
std::shared_ptr<IConfigCompilerService> configCompilerService,
std::shared_ptr<IScriptEngineService> scriptEngineService,
std::shared_ptr<ILogger> logger);
std::unordered_map<std::string, ShaderPaths> BuildShaderMap() override;
std::string GetActiveSystemId() const override;
private:
std::string ResolveActiveSystemId() const;
std::string ResolveFallbackSystemId() const;
bool IsSystemEnabled(const std::string& systemId) const;
std::shared_ptr<IConfigService> configService_;
std::shared_ptr<ILogger> logger_;
std::unordered_map<std::string, std::shared_ptr<IShaderSystem>> systems_;
};
} // namespace sdl3cpp::services::impl

View File

@@ -1,6 +1,7 @@
#pragma once
#include "probe_types.hpp"
#include <array>
#include <string>
#include <unordered_map>
#include <vector>
@@ -33,6 +34,7 @@ struct ShaderIR {
std::string id;
std::string vertexPath;
std::string fragmentPath;
std::string system;
std::string jsonPath;
};
@@ -77,9 +79,23 @@ struct RenderPassOutputIR {
std::string jsonPath;
};
struct RenderPassClearIR {
bool enabled = false;
bool clearColor = false;
bool clearDepth = false;
bool clearStencil = false;
std::array<float, 4> color{0.0f, 0.0f, 0.0f, 1.0f};
float depth = 1.0f;
int stencil = 0;
std::string jsonPath;
};
struct RenderPassIR {
std::string id;
std::string type;
bool hasViewId = false;
int viewId = 0;
RenderPassClearIR clear;
std::vector<RenderPassInputIR> inputs;
std::vector<RenderPassOutputIR> outputs;
std::string jsonPath;

View File

@@ -0,0 +1,40 @@
#pragma once
#include "graphics_types.hpp"
#include "shader_system_types.hpp"
#include <string>
#include <unordered_map>
#include <vector>
namespace sdl3cpp::services {
/**
* @brief Interface for shader system implementations.
*/
class IShaderSystem {
public:
virtual ~IShaderSystem() = default;
/**
* @brief Unique system identifier (e.g., "materialx", "glsl").
*/
virtual std::string GetId() const = 0;
/**
* @brief Build a shader map for the active system.
*/
virtual std::unordered_map<std::string, ShaderPaths> BuildShaderMap() = 0;
/**
* @brief Get reflection metadata for a shader key (if supported).
*/
virtual ShaderReflection GetReflection(const std::string& shaderKey) const = 0;
/**
* @brief Get default textures for a shader key (if supported).
*/
virtual std::vector<ShaderPaths::TextureBinding> GetDefaultTextures(const std::string& shaderKey) const = 0;
};
} // namespace sdl3cpp::services

View File

@@ -0,0 +1,28 @@
#pragma once
#include "graphics_types.hpp"
#include <string>
#include <unordered_map>
namespace sdl3cpp::services {
/**
* @brief Registry that selects and executes the active shader system.
*/
class IShaderSystemRegistry {
public:
virtual ~IShaderSystemRegistry() = default;
/**
* @brief Build a shader map using the active shader system.
*/
virtual std::unordered_map<std::string, ShaderPaths> BuildShaderMap() = 0;
/**
* @brief Resolve the active shader system id.
*/
virtual std::string GetActiveSystemId() const = 0;
};
} // namespace sdl3cpp::services

View File

@@ -0,0 +1,16 @@
#pragma once
#include <string>
#include <vector>
namespace sdl3cpp::services {
/**
* @brief Minimal reflection snapshot for a shader system.
*/
struct ShaderReflection {
std::vector<std::string> uniforms;
std::vector<std::string> textures;
};
} // namespace sdl3cpp::services

View File

@@ -0,0 +1,123 @@
#include <gtest/gtest.h>
#include "services/impl/bgfx_graphics_backend.hpp"
#include "services/interfaces/i_logger.hpp"
namespace sdl3cpp::services::impl {
bool CanAllocateTextureBudgetForTest(const BgfxGraphicsBackend& backend, size_t bytes) {
return backend.textureMemoryTracker_.CanAllocate(bytes);
}
void AllocateTextureBudgetForTest(BgfxGraphicsBackend& backend, size_t bytes) {
backend.textureMemoryTracker_.Allocate(bytes);
}
void FreeTextureBudgetForTest(BgfxGraphicsBackend& backend, size_t bytes) {
backend.textureMemoryTracker_.Free(bytes);
}
void SetTextureBudgetMaxForTest(BgfxGraphicsBackend& backend, size_t maxBytes) {
backend.textureMemoryTracker_.SetMaxBytes(maxBytes);
}
size_t GetTextureBudgetMaxForTest(const BgfxGraphicsBackend& backend) {
return backend.textureMemoryTracker_.GetMaxBytes();
}
size_t GetTextureBudgetUsedForTest(const BgfxGraphicsBackend& backend) {
return backend.textureMemoryTracker_.GetUsedBytes();
}
} // namespace sdl3cpp::services::impl
namespace {
class NullLogger final : public sdl3cpp::services::ILogger {
public:
void SetLevel(sdl3cpp::services::LogLevel) override {}
sdl3cpp::services::LogLevel GetLevel() const override { return sdl3cpp::services::LogLevel::OFF; }
void SetOutputFile(const std::string&) override {}
void SetMaxLinesPerFile(size_t) override {}
void EnableConsoleOutput(bool) override {}
void Log(sdl3cpp::services::LogLevel, const std::string&) override {}
void Trace(const std::string&) override {}
void Trace(const std::string&, const std::string&, const std::string&, const std::string&) override {}
void Debug(const std::string&) override {}
void Info(const std::string&) override {}
void Warn(const std::string&) override {}
void Error(const std::string&) override {}
void TraceFunction(const std::string&) override {}
void TraceVariable(const std::string&, const std::string&) override {}
void TraceVariable(const std::string&, int) override {}
void TraceVariable(const std::string&, size_t) override {}
void TraceVariable(const std::string&, bool) override {}
void TraceVariable(const std::string&, float) override {}
void TraceVariable(const std::string&, double) override {}
};
class BudgetConfigService final : public sdl3cpp::services::IConfigService {
public:
explicit BudgetConfigService(size_t vramMb) {
budgets_.vramMB = vramMb;
}
uint32_t GetWindowWidth() const override { return 1; }
uint32_t GetWindowHeight() const override { return 1; }
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::Lua;
}
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(BgfxTextureBudgetTrackerTest, UsesConfigBudgetForMaxBytes) {
auto logger = std::make_shared<NullLogger>();
auto configService = std::make_shared<BudgetConfigService>(2);
sdl3cpp::services::impl::BgfxGraphicsBackend backend(
configService, nullptr, logger, nullptr, nullptr);
EXPECT_EQ(sdl3cpp::services::impl::GetTextureBudgetMaxForTest(backend), 2u * 1024u * 1024u);
}
TEST(BgfxTextureBudgetTrackerTest, TracksAllocationsAndFrees) {
auto logger = std::make_shared<NullLogger>();
auto configService = std::make_shared<BudgetConfigService>(1);
sdl3cpp::services::impl::BgfxGraphicsBackend backend(
configService, nullptr, logger, nullptr, nullptr);
sdl3cpp::services::impl::SetTextureBudgetMaxForTest(backend, 100);
EXPECT_TRUE(sdl3cpp::services::impl::CanAllocateTextureBudgetForTest(backend, 60));
sdl3cpp::services::impl::AllocateTextureBudgetForTest(backend, 60);
EXPECT_EQ(sdl3cpp::services::impl::GetTextureBudgetUsedForTest(backend), 60u);
EXPECT_FALSE(sdl3cpp::services::impl::CanAllocateTextureBudgetForTest(backend, 50));
sdl3cpp::services::impl::FreeTextureBudgetForTest(backend, 30);
EXPECT_EQ(sdl3cpp::services::impl::GetTextureBudgetUsedForTest(backend), 30u);
EXPECT_TRUE(sdl3cpp::services::impl::CanAllocateTextureBudgetForTest(backend, 50));
}
} // namespace

View File

@@ -134,4 +134,61 @@ TEST(ConfigCompilerReferenceValidationTest, FlagsSelfReference) {
EXPECT_TRUE(HasDiagnosticCode(result.diagnostics, "RG_INPUT_SELF_REFERENCE"));
}
TEST(ConfigCompilerReferenceValidationTest, FlagsInvalidViewId) {
const std::string json = R"({
"render": {
"passes": [
{
"id": "main",
"view_id": "bad"
}
]
}
})";
sdl3cpp::services::impl::ConfigCompilerService compiler(nullptr, nullptr, nullptr, nullptr);
auto result = compiler.Compile(json);
EXPECT_FALSE(result.success);
EXPECT_TRUE(HasDiagnosticCode(result.diagnostics, "RG_VIEW_ID_TYPE"));
}
TEST(ConfigCompilerReferenceValidationTest, FlagsInvalidClearColor) {
const std::string json = R"({
"render": {
"passes": [
{
"id": "main",
"clear": {
"flags": ["color"],
"color": [1.0, 0.5, 0.25]
}
}
]
}
})";
sdl3cpp::services::impl::ConfigCompilerService compiler(nullptr, nullptr, nullptr, nullptr);
auto result = compiler.Compile(json);
EXPECT_FALSE(result.success);
EXPECT_TRUE(HasDiagnosticCode(result.diagnostics, "RG_CLEAR_COLOR"));
}
TEST(ConfigCompilerReferenceValidationTest, FlagsInvalidShaderSystemType) {
const std::string json = R"({
"assets": {
"shaders": {
"pbr": { "vs": "shaders/pbr.vs", "fs": "shaders/pbr.fs", "system": 3 }
}
}
})";
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_TYPE"));
}
} // namespace

View File

@@ -7,6 +7,7 @@
#include "services/impl/script_engine_service.hpp"
#include "services/impl/scene_script_service.hpp"
#include "services/impl/shader_script_service.hpp"
#include "services/impl/shader_system_registry.hpp"
#include "services/impl/ecs_service.hpp"
#include "services/impl/scene_service.hpp"
#include "services/impl/sdl_window_service.hpp"
@@ -792,7 +793,15 @@ int main() {
engineService->Initialize();
sdl3cpp::services::impl::SceneScriptService sceneService(engineService, logger);
sdl3cpp::services::impl::ShaderScriptService shaderService(engineService, configService, logger);
auto shaderSystemRegistry = std::make_shared<sdl3cpp::services::impl::ShaderSystemRegistry>(
configService,
nullptr,
engineService,
logger);
sdl3cpp::services::impl::ShaderScriptService shaderService(
engineService,
shaderSystemRegistry,
logger);
auto objects = sceneService.LoadSceneObjects();
Assert(objects.size() == 1, "expected exactly one scene object", failures);