From 8b12ae35d479d6f82f35fa5a6d3b47c4b2b7296f Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 05:30:54 +0200 Subject: [PATCH] Route render debug callback through platform services --- docs/modernization/build-inventory.md | 4 +- docs/modernization/debt.md | 2 +- docs/modernization/roadmap.md | 20 +++-- src/app.cpp | 73 +---------------- src/app.h | 1 + src/app_events.cpp | 5 ++ src/platform_api/platform_services.h | 1 + .../legacy_platform_services.cpp | 4 + .../windows_platform_services.cpp | 81 +++++++++++++++++++ .../platform_api/platform_services_tests.cpp | 8 ++ 10 files changed, 117 insertions(+), 82 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 165ff11..e481d12 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -449,8 +449,8 @@ Known local toolchain state: virtual-keyboard visibility, external file display, file sharing, native app/window close, UI-thread lifecycle hooks, render-context lifecycle hooks, render-target binding hooks, render platform hint hooks, render-capture frame - hooks, per-frame platform hooks, picker callbacks, and recording cleanup, - live asset/layout reload policy, diagnostic + hooks, render debug callback hooks, per-frame platform hooks, picker + callbacks, and recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, prepared-file save/download handoff; Windows live app execution now uses injected diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 3a22573..800f8b9 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -35,7 +35,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0014 | Open | Modernization | `windows-clangcl-asan` now configures as a headless Ninja/clang-cl preset and uses the release MSVC runtime required by ASan, but local builds still fail because installed clang-cl 18.1.8 is paired with VS 2026-preview STL headers that require Clang 20 or newer | Sanitizer validation should be local and repeatable, but this machine's compiler/header pairing is incompatible | `cmake --fresh --preset windows-clangcl-asan`; `cmake --build --preset windows-clangcl-asan --target pp_foundation` | Install/use Clang 20+ with the VS 2026 STL, or point the preset at a compatible VS 2022 toolchain, then make `platform-build.ps1 -Presets windows-clangcl-asan` pass for the headless matrix | | DEBT-0015 | Open | Modernization | Cursor visibility requests now consume pure `pp_app_core` planning through `pano_cli plan-cursor-visibility`, `App::show_cursor`/`App::hide_cursor` dispatch through `PlatformServices` without platform guards, and Windows live execution uses injected `WindowsPlatformServices`, but macOS cursor execution still reaches the retained fallback adapter | Keep canvas cursor behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-cursor-visibility --visible`; `ctest --preset desktop-fast --build-config Debug` | Cursor visibility execution is owned by injected `pp_platform_*` services for every supported platform | | DEBT-0016 | Open | Modernization | Clipboard get/set requests now consume pure `pp_app_core` planning through `pano_cli plan-clipboard-read` and `pano_cli plan-clipboard-write`, and Windows live execution uses injected `WindowsPlatformServices`, but Apple/Android clipboard execution still reaches retained fallback adapter branches from `App::clipboard_get_text` and `App::clipboard_set_text` | Keep picker/color text clipboard behavior stable while platform shells are extracted incrementally | `pp_app_core_document_platform_io_tests`; `pano_cli plan-clipboard-write --text #ff00aa`; `ctest --preset desktop-fast --build-config Debug` | Clipboard execution is owned by injected `pp_platform_*` services for every supported platform | -| DEBT-0017 | Open | Modernization | Startup storage path preparation, `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, native app/window close, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render platform hint hooks, render-capture frame hooks, recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, per-frame platform hooks, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, `App::pick_dir`, and prepared-file save/download handoff now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`; non-Windows live implementations still use `src/platform_legacy/legacy_platform_services.*`, a named fallback adapter that forwards to retained Apple/Android/Linux/Web bridge functions and retained no-op branches | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace `src/platform_legacy/legacy_platform_services.*` with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | +| DEBT-0017 | Open | Modernization | Startup storage path preparation, `App::clipboard_get_text`, `App::clipboard_set_text`, `App::show_cursor`, `App::hide_cursor`, `App::showKeyboard`, `App::hideKeyboard`, `App::display_file`, `App::share_file`, native app/window close, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render platform hint hooks, render debug callback hooks, render-capture frame hooks, recording cleanup, live asset/layout reload policy, diagnostic stacktrace/crash hooks, per-frame platform hooks, `App::pick_image`, `App::pick_file`, the non-writer `App::pick_file_save`, `App::pick_dir`, and prepared-file save/download handoff now call the SDK-free `pp::platform::PlatformServices` interface, and Windows injects `WindowsPlatformServices` from `src/platform_windows/windows_platform_services.*`; non-Windows live implementations still use `src/platform_legacy/legacy_platform_services.*`, a named fallback adapter that forwards to retained Apple/Android/Linux/Web bridge functions and retained no-op branches | Preserve behavior while moving platform execution behind a testable service boundary before platform shell implementations are injected | `pp_platform_api_tests`; `pp_app_core_document_platform_io_tests`; `ctest --preset desktop-fast --build-config Debug`; `powershell -ExecutionPolicy Bypass -File scripts\automation\package-smoke.ps1 -Preset windows-msvc-default -Configuration Debug` | Replace `src/platform_legacy/legacy_platform_services.*` with injected `pp_platform_*` service implementations owned by each non-Windows platform shell | ## Closed Debt diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index bac2aa2..0e4c8d3 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -472,9 +472,10 @@ before retained platform clipboard bridges continue. startup storage path preparation, clipboard text, cursor visibility, virtual-keyboard visibility, UI-thread lifecycle hooks, render-context acquire/release/present hooks, render-target binding hooks, render-capture -frame hooks, render platform hint hooks, external file display, file sharing, -recording file cleanup, live asset/layout reload policy, diagnostic -stacktrace/crash hooks, image/file/save-file pickers, and directory pickers. +frame hooks, render platform hint hooks, render debug callback hooks, external +file display, file sharing, recording file cleanup, live asset/layout reload +policy, diagnostic stacktrace/crash hooks, image/file/save-file pickers, and +directory pickers. Windows installs an injected `WindowsPlatformServices` implementation from `src/platform_windows/windows_platform_services.*` in `pp_platform_windows`; other platforms still route through the debt-tracked legacy fallback adapter @@ -509,6 +510,10 @@ legacy adapter while removing the iOS drawable branch from `App::draw`. Initial render platform hints now also dispatch through `PlatformServices`, preserving the previous Windows/macOS program-point-size and line-smoothing enablement while removing the Windows/macOS branch from `App::init`. +Windows OpenGL debug callback setup now dispatches through `PlatformServices`, +moving Win32 console coloring, debug-output enablement, and debug-break callback +behavior into `WindowsPlatformServices` while keeping other platform adapters +as no-ops. Windows RenderDoc frame capture hooks now also dispatch through `PlatformServices`, keeping capture integration in the platform service while leaving non-Windows adapters as no-ops. @@ -1042,10 +1047,11 @@ Results: dispatch, external file display dispatch, file sharing dispatch, native app/window close dispatch, UI-thread lifecycle dispatch, render-context lifecycle dispatch, render-target binding dispatch, render platform hint - dispatch, render-capture frame hook dispatch, recording cleanup dispatch, - live asset/layout reload policy dispatch, diagnostic hook dispatch, per-frame - platform hook dispatch, picker callback dispatch, and prepared-file - save/download callback dispatch. The live Windows app now + dispatch, render debug callback dispatch, render-capture frame hook dispatch, + recording cleanup dispatch, live asset/layout reload policy dispatch, + diagnostic hook dispatch, per-frame platform hook dispatch, picker callback + dispatch, and prepared-file save/download callback dispatch. The live Windows + app now consumes this interface through an injected `WindowsPlatformServices` instance isolated in `src/platform_windows/windows_platform_services.*`; other platforms still diff --git a/src/app.cpp b/src/app.cpp index 8828dc3..e0c39f5 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -25,36 +25,6 @@ std::condition_variable App::render_cv; namespace { -[[nodiscard]] GLenum debug_severity_notification() noexcept -{ - return static_cast(pp::renderer::gl::debug_severity_notification()); -} - -[[nodiscard]] GLenum debug_severity_low() noexcept -{ - return static_cast(pp::renderer::gl::debug_severity_low()); -} - -[[nodiscard]] GLenum debug_severity_medium() noexcept -{ - return static_cast(pp::renderer::gl::debug_severity_medium()); -} - -[[nodiscard]] GLenum debug_severity_high() noexcept -{ - return static_cast(pp::renderer::gl::debug_severity_high()); -} - -[[nodiscard]] GLenum debug_output_state() noexcept -{ - return static_cast(pp::renderer::gl::debug_output_state()); -} - -[[nodiscard]] GLenum debug_output_synchronous_state() noexcept -{ - return static_cast(pp::renderer::gl::debug_output_synchronous_state()); -} - [[nodiscard]] GLenum version_string_name() noexcept { return static_cast(pp::renderer::gl::version_string_name()); @@ -421,54 +391,13 @@ void App::upload(std::string filename, std::string name, std::function colors = { - { debug_severity_notification(), 8 }, - { debug_severity_low(), 8 }, - { debug_severity_medium(), FOREGROUND_GREEN | FOREGROUND_INTENSITY }, - { debug_severity_high(), FOREGROUND_RED | FOREGROUND_INTENSITY }, - }; - if (severity == debug_severity_high() - || severity == debug_severity_medium() - || severity == debug_severity_low()) - { - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colors[severity]); - LOG("OPENGL: %.*s", length, message); - FlushConsoleInputBuffer(GetStdHandle(STD_OUTPUT_HANDLE)); - SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), info.wAttributes); -#ifdef _DEBUG - if (severity == debug_severity_high()) - __debugbreak(); -#endif - } -} -#endif - void App::init() { -#ifdef _WIN32 - if (glDebugMessageCallback) - { - // colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c - GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &info); - - render_task([] - { - glDebugMessageCallback(handle_gl_callback, nullptr); - glEnable(debug_output_state()); - glEnable(debug_output_synchronous_state()); - }); - } -#endif - LOG("Screen Resolution: %dx%d", (int)width, (int)height); render_task([] { + App::I->install_render_debug_callback(); LOG("GL version: %s", glGetString(version_string_name())); LOG("GL vendor: %s", glGetString(vendor_string_name())); LOG("GL renderer: %s", glGetString(renderer_string_name())); diff --git a/src/app.h b/src/app.h index e5a62a3..6367493 100644 --- a/src/app.h +++ b/src/app.h @@ -199,6 +199,7 @@ public: void bind_default_render_target(); void bind_main_render_target(); void apply_render_platform_hints(); + void install_render_debug_callback(); void begin_render_capture_frame(); void end_render_capture_frame(); [[nodiscard]] bool platform_deletes_recorded_files_on_clear(); diff --git a/src/app_events.cpp b/src/app_events.cpp index 7857aae..74727fb 100644 --- a/src/app_events.cpp +++ b/src/app_events.cpp @@ -252,6 +252,11 @@ void App::apply_render_platform_hints() active_platform_services().apply_render_platform_hints(); } +void App::install_render_debug_callback() +{ + active_platform_services().install_render_debug_callback(); +} + void App::begin_render_capture_frame() { active_platform_services().begin_render_capture_frame(); diff --git a/src/platform_api/platform_services.h b/src/platform_api/platform_services.h index 9d8aa2c..a0ded6a 100644 --- a/src/platform_api/platform_services.h +++ b/src/platform_api/platform_services.h @@ -36,6 +36,7 @@ public: virtual void bind_default_render_target() = 0; virtual void bind_main_render_target() = 0; virtual void apply_render_platform_hints() = 0; + virtual void install_render_debug_callback() = 0; virtual void begin_render_capture_frame() = 0; virtual void end_render_capture_frame() = 0; [[nodiscard]] virtual bool deletes_recorded_files_on_clear() = 0; diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp index 6bc13c5..9abb84e 100644 --- a/src/platform_legacy/legacy_platform_services.cpp +++ b/src/platform_legacy/legacy_platform_services.cpp @@ -249,6 +249,10 @@ public: #endif } + void install_render_debug_callback() override + { + } + void begin_render_capture_frame() override { } diff --git a/src/platform_windows/windows_platform_services.cpp b/src/platform_windows/windows_platform_services.cpp index e788e9c..112e4eb 100644 --- a/src/platform_windows/windows_platform_services.cpp +++ b/src/platform_windows/windows_platform_services.cpp @@ -1,9 +1,11 @@ #include "pch.h" #include "platform_windows/windows_platform_services.h" +#include "log.h" #include "renderer_gl/opengl_capabilities.h" #include +#include extern HWND hWnd; extern std::deque> main_tasklist; @@ -20,6 +22,73 @@ void win32_update_stylus(float dt); namespace { +static CONSOLE_SCREEN_BUFFER_INFO render_debug_console_info; + +[[nodiscard]] GLenum debug_severity_notification() noexcept +{ + return static_cast(pp::renderer::gl::debug_severity_notification()); +} + +[[nodiscard]] GLenum debug_severity_low() noexcept +{ + return static_cast(pp::renderer::gl::debug_severity_low()); +} + +[[nodiscard]] GLenum debug_severity_medium() noexcept +{ + return static_cast(pp::renderer::gl::debug_severity_medium()); +} + +[[nodiscard]] GLenum debug_severity_high() noexcept +{ + return static_cast(pp::renderer::gl::debug_severity_high()); +} + +[[nodiscard]] GLenum debug_output_state() noexcept +{ + return static_cast(pp::renderer::gl::debug_output_state()); +} + +[[nodiscard]] GLenum debug_output_synchronous_state() noexcept +{ + return static_cast(pp::renderer::gl::debug_output_synchronous_state()); +} + +void handle_gl_callback( + GLenum source, + GLenum type, + GLuint id, + GLenum severity, + GLsizei length, + const GLchar* message, + const void* userParam) +{ + (void)source; + (void)type; + (void)id; + (void)userParam; + + static std::map colors = { + { debug_severity_notification(), static_cast(8) }, + { debug_severity_low(), static_cast(8) }, + { debug_severity_medium(), static_cast(FOREGROUND_GREEN | FOREGROUND_INTENSITY) }, + { debug_severity_high(), static_cast(FOREGROUND_RED | FOREGROUND_INTENSITY) }, + }; + if (severity == debug_severity_high() + || severity == debug_severity_medium() + || severity == debug_severity_low()) + { + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), colors[severity]); + LOG("OPENGL: %.*s", length, message); + FlushConsoleInputBuffer(GetStdHandle(STD_OUTPUT_HANDLE)); + SetConsoleTextAttribute(GetStdHandle(STD_OUTPUT_HANDLE), render_debug_console_info.wAttributes); +#ifdef _DEBUG + if (severity == debug_severity_high()) + __debugbreak(); +#endif + } +} + void show_cursor(bool visible) { std::lock_guard lock(main_task_mutex); @@ -268,6 +337,18 @@ public: glEnable(static_cast(pp::renderer::gl::line_smooth_state())); } + void install_render_debug_callback() override + { + if (!glDebugMessageCallback) + return; + + // colors: http://stackoverflow.com/questions/4053837/colorizing-text-in-the-console-with-c + GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &render_debug_console_info); + glDebugMessageCallback(handle_gl_callback, nullptr); + glEnable(debug_output_state()); + glEnable(debug_output_synchronous_state()); + } + void begin_render_capture_frame() override { win32_renderdoc_frame_start(); diff --git a/tests/platform_api/platform_services_tests.cpp b/tests/platform_api/platform_services_tests.cpp index da71c5a..3c69680 100644 --- a/tests/platform_api/platform_services_tests.cpp +++ b/tests/platform_api/platform_services_tests.cpp @@ -96,6 +96,11 @@ public: ++render_platform_hint_applies; } + void install_render_debug_callback() override + { + ++render_debug_callback_installs; + } + void begin_render_capture_frame() override { ++render_capture_begins; @@ -202,6 +207,7 @@ public: int default_render_target_binds = 0; int main_render_target_binds = 0; int render_platform_hint_applies = 0; + int render_debug_callback_installs = 0; int render_capture_begins = 0; int render_capture_ends = 0; int storage_prepares = 0; @@ -334,6 +340,7 @@ void platform_services_dispatch_render_context_lifecycle(pp::tests::Harness& har services.bind_default_render_target(); services.bind_main_render_target(); services.apply_render_platform_hints(); + services.install_render_debug_callback(); services.release_render_context(); PP_EXPECT(harness, fake.render_context_acquires == 1); @@ -341,6 +348,7 @@ void platform_services_dispatch_render_context_lifecycle(pp::tests::Harness& har PP_EXPECT(harness, fake.default_render_target_binds == 1); PP_EXPECT(harness, fake.main_render_target_binds == 1); PP_EXPECT(harness, fake.render_platform_hint_applies == 1); + PP_EXPECT(harness, fake.render_debug_callback_installs == 1); PP_EXPECT(harness, fake.render_context_releases == 1); }