diff --git a/CMakeLists.txt b/CMakeLists.txt index bdca470..374b7f5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -461,6 +461,38 @@ else() endif() add_test(NAME bgfx_gui_service_tests COMMAND bgfx_gui_service_tests) +# Test: GUI budget enforcement (cache pruning) +add_executable(bgfx_gui_budget_enforcement_test + tests/bgfx_gui_budget_enforcement_test.cpp + src/services/impl/bgfx_gui_service.cpp + src/services/impl/bgfx_shader_compiler.cpp + src/services/impl/materialx_shader_generator.cpp + src/services/impl/shader_pipeline_validator.cpp + src/services/impl/pipeline_compiler_service.cpp +) +target_include_directories(bgfx_gui_budget_enforcement_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src") +target_include_directories(bgfx_gui_budget_enforcement_test PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/src/bgfx_tools/shaderc") +target_link_libraries(bgfx_gui_budget_enforcement_test PRIVATE + GTest::gtest + GTest::gtest_main + ${SDL3CPP_RENDER_STACK_LIBS} + ${SDL3CPP_FREETYPE_LIBS} + lunasvg::lunasvg +) +if(TARGET shaderc_local) + target_link_libraries(bgfx_gui_budget_enforcement_test PRIVATE shaderc_local) +elseif(TARGET shaderc::shaderc) +elseif(TARGET shaderc::shaderc_combined) + target_link_libraries(bgfx_gui_budget_enforcement_test PRIVATE shaderc::shaderc_combined) +else() + if(TARGET shaderc_local) + target_link_libraries(bgfx_gui_budget_enforcement_test PRIVATE shaderc_local) + else() + message(WARNING "shaderc CMake target not found; skipping link for bgfx_gui_budget_enforcement_test.") + endif() +endif() +add_test(NAME bgfx_gui_budget_enforcement_test COMMAND bgfx_gui_budget_enforcement_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 53d60b6..541e579 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -210,7 +210,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 (over-limit textures, transient pool caps) +- [~] Budget enforcement tests (GUI cache pruning covered; texture/transient pool pending) - [ ] Smoke test: cube demo boots with config-first scene definition ## Test Strategy (Solid Coverage Plan) diff --git a/src/services/impl/bgfx_gui_service.hpp b/src/services/impl/bgfx_gui_service.hpp index 2569ff9..98ba15f 100644 --- a/src/services/impl/bgfx_gui_service.hpp +++ b/src/services/impl/bgfx_gui_service.hpp @@ -40,6 +40,20 @@ public: bool IsWhiteTextureReady() const; private: + friend void AddTextCacheEntryForTest(BgfxGuiService& service, + const std::string& text, + int fontSize, + uint64_t lastUsedFrame); + friend void AddSvgCacheEntryForTest(BgfxGuiService& service, + const std::string& path, + int width, + int height, + uint64_t lastUsedFrame); + friend size_t GetTextCacheSizeForTest(const BgfxGuiService& service); + friend size_t GetSvgCacheSizeForTest(const BgfxGuiService& service); + friend void PruneTextCacheForTest(BgfxGuiService& service); + friend void PruneSvgCacheForTest(BgfxGuiService& service); + struct GuiVertex { float x = 0.0f; float y = 0.0f; diff --git a/tests/bgfx_gui_budget_enforcement_test.cpp b/tests/bgfx_gui_budget_enforcement_test.cpp new file mode 100644 index 0000000..a310b7d --- /dev/null +++ b/tests/bgfx_gui_budget_enforcement_test.cpp @@ -0,0 +1,139 @@ +#include + +#include "services/impl/bgfx_gui_service.hpp" +#include "services/interfaces/i_logger.hpp" + +namespace sdl3cpp::services::impl { + +void AddTextCacheEntryForTest(BgfxGuiService& service, + const std::string& text, + int fontSize, + uint64_t lastUsedFrame) { + BgfxGuiService::TextKey key{text, fontSize}; + BgfxGuiService::TextTexture texture; + texture.fontSize = fontSize; + texture.lastUsedFrame = lastUsedFrame; + service.textCache_[std::move(key)] = texture; +} + +void AddSvgCacheEntryForTest(BgfxGuiService& service, + const std::string& path, + int width, + int height, + uint64_t lastUsedFrame) { + BgfxGuiService::SvgKey key{path, width, height}; + BgfxGuiService::SvgTexture texture; + texture.width = width; + texture.height = height; + texture.lastUsedFrame = lastUsedFrame; + service.svgCache_[std::move(key)] = texture; +} + +size_t GetTextCacheSizeForTest(const BgfxGuiService& service) { + return service.textCache_.size(); +} + +size_t GetSvgCacheSizeForTest(const BgfxGuiService& service) { + return service.svgCache_.size(); +} + +void PruneTextCacheForTest(BgfxGuiService& service) { + service.PruneTextCache(); +} + +void PruneSvgCacheForTest(BgfxGuiService& service) { + service.PruneSvgCache(); +} + +} // 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: + BudgetConfigService(size_t textBudget, size_t svgBudget) { + budgets_.guiTextCacheEntries = textBudget; + budgets_.guiSvgCacheEntries = svgBudget; + } + + 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(BgfxGuiBudgetEnforcementTest, TextCachePrunesToBudget) { + auto logger = std::make_shared(); + auto configService = std::make_shared(2, 2); + sdl3cpp::services::impl::BgfxGuiService service(configService, logger); + + sdl3cpp::services::impl::AddTextCacheEntryForTest(service, "a", 12, 1); + sdl3cpp::services::impl::AddTextCacheEntryForTest(service, "b", 12, 2); + sdl3cpp::services::impl::AddTextCacheEntryForTest(service, "c", 12, 3); + + sdl3cpp::services::impl::PruneTextCacheForTest(service); + EXPECT_EQ(sdl3cpp::services::impl::GetTextCacheSizeForTest(service), 2u); +} + +TEST(BgfxGuiBudgetEnforcementTest, SvgCachePrunesToBudget) { + auto logger = std::make_shared(); + auto configService = std::make_shared(2, 1); + sdl3cpp::services::impl::BgfxGuiService service(configService, logger); + + sdl3cpp::services::impl::AddSvgCacheEntryForTest(service, "a.svg", 64, 64, 1); + sdl3cpp::services::impl::AddSvgCacheEntryForTest(service, "b.svg", 64, 64, 2); + + sdl3cpp::services::impl::PruneSvgCacheForTest(service); + EXPECT_EQ(sdl3cpp::services::impl::GetSvgCacheSizeForTest(service), 1u); +} + +} // namespace