From 7b288c2d500bb165735c0b1e62b8ffb637b683eb Mon Sep 17 00:00:00 2001 From: johndoe6345789 Date: Fri, 9 Jan 2026 17:50:33 +0000 Subject: [PATCH] ROADMAP.md --- CMakeLists.txt | 35 ++++ ROADMAP.md | 5 +- config/schema/runtime_config_v2.schema.json | 91 +++++++++ src/app/service_based_app.cpp | 11 +- src/services/impl/bgfx_graphics_backend.hpp | 6 + src/services/impl/config_compiler_service.cpp | 173 ++++++++++++++++++ src/services/impl/glsl_shader_system.cpp | 81 ++++++++ src/services/impl/glsl_shader_system.hpp | 36 ++++ src/services/impl/materialx_shader_system.cpp | 136 ++++++++++++++ src/services/impl/materialx_shader_system.hpp | 38 ++++ src/services/impl/shader_script_service.cpp | 105 +---------- src/services/impl/shader_script_service.hpp | 8 +- src/services/impl/shader_system_registry.cpp | 141 ++++++++++++++ src/services/impl/shader_system_registry.hpp | 41 +++++ src/services/interfaces/config_ir_types.hpp | 16 ++ src/services/interfaces/i_shader_system.hpp | 40 ++++ .../interfaces/i_shader_system_registry.hpp | 28 +++ .../interfaces/shader_system_types.hpp | 16 ++ tests/bgfx_texture_budget_tracker_test.cpp | 123 +++++++++++++ ...fig_compiler_reference_validation_test.cpp | 57 ++++++ tests/test_cube_script.cpp | 11 +- 21 files changed, 1092 insertions(+), 106 deletions(-) create mode 100644 src/services/impl/glsl_shader_system.cpp create mode 100644 src/services/impl/glsl_shader_system.hpp create mode 100644 src/services/impl/materialx_shader_system.cpp create mode 100644 src/services/impl/materialx_shader_system.hpp create mode 100644 src/services/impl/shader_system_registry.cpp create mode 100644 src/services/impl/shader_system_registry.hpp create mode 100644 src/services/interfaces/i_shader_system.hpp create mode 100644 src/services/interfaces/i_shader_system_registry.hpp create mode 100644 src/services/interfaces/shader_system_types.hpp create mode 100644 tests/bgfx_texture_budget_tracker_test.cpp diff --git a/CMakeLists.txt b/CMakeLists.txt index 374b7f5..cd9d3c7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -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 diff --git a/ROADMAP.md b/ROADMAP.md index 541e579..c2a8494 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -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) diff --git a/config/schema/runtime_config_v2.schema.json b/config/schema/runtime_config_v2.schema.json index ab3bd13..11c0af7 100644 --- a/config/schema/runtime_config_v2.schema.json +++ b/config/schema/runtime_config_v2.schema.json @@ -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 diff --git a/src/app/service_based_app.cpp b/src/app/service_based_app.cpp index 19df74f..1209e26 100644 --- a/src/app/service_based_app.cpp +++ b/src/app/service_based_app.cpp @@ -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 #include @@ -284,13 +286,20 @@ void ServiceBasedApp::RegisterServices() { registry_.GetService(), runtimeConfig_.luaDebug); + // Shader system registry (pluggable shader system selection) + registry_.RegisterService( + registry_.GetService(), + registry_.GetService(), + registry_.GetService(), + registry_.GetService()); + // Script-facing services registry_.RegisterService( registry_.GetService(), registry_.GetService()); registry_.RegisterService( registry_.GetService(), - registry_.GetService(), + registry_.GetService(), registry_.GetService()); registry_.RegisterService( registry_.GetService(), diff --git a/src/services/impl/bgfx_graphics_backend.hpp b/src/services/impl/bgfx_graphics_backend.hpp index 949e201..b0ecc1b 100644 --- a/src/services/impl/bgfx_graphics_backend.hpp +++ b/src/services/impl/bgfx_graphics_backend.hpp @@ -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 { diff --git a/src/services/impl/config_compiler_service.cpp b/src/services/impl/config_compiler_service.cpp index c07902c..34e7426 100644 --- a/src/services/impl/config_compiler_service.cpp +++ b/src/services/impl/config_compiler_service.cpp @@ -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(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(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{}); if (passValue.HasMember("inputs")) { diff --git a/src/services/impl/glsl_shader_system.cpp b/src/services/impl/glsl_shader_system.cpp new file mode 100644 index 0000000..1a2159f --- /dev/null +++ b/src/services/impl/glsl_shader_system.cpp @@ -0,0 +1,81 @@ +#include "glsl_shader_system.hpp" + +#include +#include + +namespace sdl3cpp::services::impl { + +GlslShaderSystem::GlslShaderSystem(std::shared_ptr configService, + std::shared_ptr configCompilerService, + std::shared_ptr 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 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 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 GlslShaderSystem::GetDefaultTextures( + const std::string& shaderKey) const { + if (logger_) { + logger_->Trace("GlslShaderSystem", "GetDefaultTextures", "shaderKey=" + shaderKey); + } + return {}; +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/glsl_shader_system.hpp b/src/services/impl/glsl_shader_system.hpp new file mode 100644 index 0000000..9369311 --- /dev/null +++ b/src/services/impl/glsl_shader_system.hpp @@ -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 +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief GLSL shader system implementation backed by asset shader entries. + */ +class GlslShaderSystem final : public IShaderSystem { +public: + GlslShaderSystem(std::shared_ptr configService, + std::shared_ptr configCompilerService, + std::shared_ptr logger); + + std::string GetId() const override { return "glsl"; } + + std::unordered_map BuildShaderMap() override; + + ShaderReflection GetReflection(const std::string& shaderKey) const override; + + std::vector GetDefaultTextures(const std::string& shaderKey) const override; + +private: + std::shared_ptr configService_; + std::shared_ptr configCompilerService_; + std::shared_ptr logger_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/materialx_shader_system.cpp b/src/services/impl/materialx_shader_system.cpp new file mode 100644 index 0000000..ab899dd --- /dev/null +++ b/src/services/impl/materialx_shader_system.cpp @@ -0,0 +1,136 @@ +#include "materialx_shader_system.hpp" + +#include +#include +#include + +namespace sdl3cpp::services::impl { + +MaterialXShaderSystem::MaterialXShaderSystem(std::shared_ptr configService, + std::shared_ptr scriptEngineService, + std::shared_ptr 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 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 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 MaterialXShaderSystem::GetDefaultTextures( + const std::string& shaderKey) const { + if (logger_) { + logger_->Trace("MaterialXShaderSystem", "GetDefaultTextures", "shaderKey=" + shaderKey); + } + return {}; +} + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/materialx_shader_system.hpp b/src/services/impl/materialx_shader_system.hpp new file mode 100644 index 0000000..3d74d6c --- /dev/null +++ b/src/services/impl/materialx_shader_system.hpp @@ -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 +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief MaterialX-backed shader system implementation. + */ +class MaterialXShaderSystem final : public IShaderSystem { +public: + MaterialXShaderSystem(std::shared_ptr configService, + std::shared_ptr scriptEngineService, + std::shared_ptr logger); + + std::string GetId() const override { return "materialx"; } + + std::unordered_map BuildShaderMap() override; + + ShaderReflection GetReflection(const std::string& shaderKey) const override; + + std::vector GetDefaultTextures(const std::string& shaderKey) const override; + +private: + std::shared_ptr configService_; + std::shared_ptr scriptEngineService_; + std::shared_ptr logger_; + MaterialXShaderGenerator materialxGenerator_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/impl/shader_script_service.cpp b/src/services/impl/shader_script_service.cpp index 45210c6..6521502 100644 --- a/src/services/impl/shader_script_service.cpp +++ b/src/services/impl/shader_script_service.cpp @@ -2,7 +2,6 @@ #include "services/interfaces/i_logger.hpp" -#include #include #include #include @@ -10,113 +9,23 @@ namespace sdl3cpp::services::impl { ShaderScriptService::ShaderScriptService(std::shared_ptr engineService, - std::shared_ptr configService, + std::shared_ptr shaderSystemRegistry, std::shared_ptr 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 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 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 { diff --git a/src/services/impl/shader_script_service.hpp b/src/services/impl/shader_script_service.hpp index 50bedc1..776b252 100644 --- a/src/services/impl/shader_script_service.hpp +++ b/src/services/impl/shader_script_service.hpp @@ -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 struct lua_State; @@ -17,7 +16,7 @@ namespace sdl3cpp::services::impl { class ShaderScriptService : public IShaderScriptService { public: ShaderScriptService(std::shared_ptr engineService, - std::shared_ptr configService, + std::shared_ptr shaderSystemRegistry, std::shared_ptr logger); std::unordered_map LoadShaderPathsMap() override; @@ -28,9 +27,8 @@ private: std::string ResolveShaderPath(const std::string& path) const; std::shared_ptr engineService_; - std::shared_ptr configService_; + std::shared_ptr shaderSystemRegistry_; std::shared_ptr logger_; - MaterialXShaderGenerator materialxGenerator_; }; } // namespace sdl3cpp::services::impl diff --git a/src/services/impl/shader_system_registry.cpp b/src/services/impl/shader_system_registry.cpp new file mode 100644 index 0000000..e777553 --- /dev/null +++ b/src/services/impl/shader_system_registry.cpp @@ -0,0 +1,141 @@ +#include "shader_system_registry.hpp" + +#include + +#include +#include + +namespace sdl3cpp::services::impl { + +ShaderSystemRegistry::ShaderSystemRegistry(std::shared_ptr configService, + std::shared_ptr configCompilerService, + std::shared_ptr scriptEngineService, + std::shared_ptr 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(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(configService_, scriptEngineService, logger_); + systems_.emplace(materialxSystem->GetId(), std::move(materialxSystem)); +} + +std::unordered_map 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 diff --git a/src/services/impl/shader_system_registry.hpp b/src/services/impl/shader_system_registry.hpp new file mode 100644 index 0000000..909b9c0 --- /dev/null +++ b/src/services/impl/shader_system_registry.hpp @@ -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 +#include +#include + +namespace sdl3cpp::services::impl { + +/** + * @brief Registry for shader systems with active system selection. + */ +class ShaderSystemRegistry final : public IShaderSystemRegistry { +public: + ShaderSystemRegistry(std::shared_ptr configService, + std::shared_ptr configCompilerService, + std::shared_ptr scriptEngineService, + std::shared_ptr logger); + + std::unordered_map 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 configService_; + std::shared_ptr logger_; + std::unordered_map> systems_; +}; + +} // namespace sdl3cpp::services::impl diff --git a/src/services/interfaces/config_ir_types.hpp b/src/services/interfaces/config_ir_types.hpp index 99e86d4..be3c2fd 100644 --- a/src/services/interfaces/config_ir_types.hpp +++ b/src/services/interfaces/config_ir_types.hpp @@ -1,6 +1,7 @@ #pragma once #include "probe_types.hpp" +#include #include #include #include @@ -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 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 inputs; std::vector outputs; std::string jsonPath; diff --git a/src/services/interfaces/i_shader_system.hpp b/src/services/interfaces/i_shader_system.hpp new file mode 100644 index 0000000..ef87f96 --- /dev/null +++ b/src/services/interfaces/i_shader_system.hpp @@ -0,0 +1,40 @@ +#pragma once + +#include "graphics_types.hpp" +#include "shader_system_types.hpp" + +#include +#include +#include + +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 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 GetDefaultTextures(const std::string& shaderKey) const = 0; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/i_shader_system_registry.hpp b/src/services/interfaces/i_shader_system_registry.hpp new file mode 100644 index 0000000..14dd8f9 --- /dev/null +++ b/src/services/interfaces/i_shader_system_registry.hpp @@ -0,0 +1,28 @@ +#pragma once + +#include "graphics_types.hpp" + +#include +#include + +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 BuildShaderMap() = 0; + + /** + * @brief Resolve the active shader system id. + */ + virtual std::string GetActiveSystemId() const = 0; +}; + +} // namespace sdl3cpp::services diff --git a/src/services/interfaces/shader_system_types.hpp b/src/services/interfaces/shader_system_types.hpp new file mode 100644 index 0000000..153649b --- /dev/null +++ b/src/services/interfaces/shader_system_types.hpp @@ -0,0 +1,16 @@ +#pragma once + +#include +#include + +namespace sdl3cpp::services { + +/** + * @brief Minimal reflection snapshot for a shader system. + */ +struct ShaderReflection { + std::vector uniforms; + std::vector textures; +}; + +} // namespace sdl3cpp::services diff --git a/tests/bgfx_texture_budget_tracker_test.cpp b/tests/bgfx_texture_budget_tracker_test.cpp new file mode 100644 index 0000000..913459d --- /dev/null +++ b/tests/bgfx_texture_budget_tracker_test.cpp @@ -0,0 +1,123 @@ +#include + +#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& GetMaterialXMaterialConfigs() const override { + return materialXMaterials_; + } + const sdl3cpp::services::GuiFontConfig& GetGuiFontConfig() const override { return guiFontConfig_; } + const sdl3cpp::services::RenderBudgetConfig& GetRenderBudgetConfig() const override { return budgets_; } + const sdl3cpp::services::CrashRecoveryConfig& GetCrashRecoveryConfig() const override { return crashRecovery_; } + const std::string& GetConfigJson() const override { return configJson_; } + +private: + sdl3cpp::services::InputBindings inputBindings_{}; + sdl3cpp::services::MouseGrabConfig mouseGrabConfig_{}; + sdl3cpp::services::BgfxConfig bgfxConfig_{}; + sdl3cpp::services::MaterialXConfig materialXConfig_{}; + std::vector materialXMaterials_{}; + sdl3cpp::services::GuiFontConfig guiFontConfig_{}; + sdl3cpp::services::RenderBudgetConfig budgets_{}; + sdl3cpp::services::CrashRecoveryConfig crashRecovery_{}; + std::string configJson_{}; +}; + +TEST(BgfxTextureBudgetTrackerTest, UsesConfigBudgetForMaxBytes) { + auto logger = std::make_shared(); + auto configService = std::make_shared(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(); + auto configService = std::make_shared(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 diff --git a/tests/config_compiler_reference_validation_test.cpp b/tests/config_compiler_reference_validation_test.cpp index bbadd06..541ead5 100644 --- a/tests/config_compiler_reference_validation_test.cpp +++ b/tests/config_compiler_reference_validation_test.cpp @@ -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 diff --git a/tests/test_cube_script.cpp b/tests/test_cube_script.cpp index 6163376..d3bca4e 100644 --- a/tests/test_cube_script.cpp +++ b/tests/test_cube_script.cpp @@ -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( + 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);