mirror of
https://github.com/johndoe6345789/SDL3CPlusPlus.git
synced 2026-04-24 13:44:58 +00:00
ROADMAP.md
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>(),
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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")) {
|
||||
|
||||
81
src/services/impl/glsl_shader_system.cpp
Normal file
81
src/services/impl/glsl_shader_system.cpp
Normal 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
|
||||
36
src/services/impl/glsl_shader_system.hpp
Normal file
36
src/services/impl/glsl_shader_system.hpp
Normal 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
|
||||
136
src/services/impl/materialx_shader_system.cpp
Normal file
136
src/services/impl/materialx_shader_system.cpp
Normal 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
|
||||
38
src/services/impl/materialx_shader_system.hpp
Normal file
38
src/services/impl/materialx_shader_system.hpp
Normal 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
|
||||
@@ -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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
141
src/services/impl/shader_system_registry.cpp
Normal file
141
src/services/impl/shader_system_registry.cpp
Normal 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
|
||||
41
src/services/impl/shader_system_registry.hpp
Normal file
41
src/services/impl/shader_system_registry.hpp
Normal 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
|
||||
@@ -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;
|
||||
|
||||
40
src/services/interfaces/i_shader_system.hpp
Normal file
40
src/services/interfaces/i_shader_system.hpp
Normal 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
|
||||
28
src/services/interfaces/i_shader_system_registry.hpp
Normal file
28
src/services/interfaces/i_shader_system_registry.hpp
Normal 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
|
||||
16
src/services/interfaces/shader_system_types.hpp
Normal file
16
src/services/interfaces/shader_system_types.hpp
Normal 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
|
||||
123
tests/bgfx_texture_budget_tracker_test.cpp
Normal file
123
tests/bgfx_texture_budget_tracker_test.cpp
Normal 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
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
|
||||
Reference in New Issue
Block a user