From 0c72aa0312db51de08461877072742b5265010fd Mon Sep 17 00:00:00 2001 From: omigamedev Date: Tue, 16 Jun 2026 07:43:44 +0200 Subject: [PATCH] Own main workers and narrow Apple render hooks --- docs/modernization/debt.md | 18 +++++ docs/modernization/roadmap.md | 11 ++-- docs/modernization/tasks.md | 8 +++ src/legacy_canvas_draw_merge_services.h | 25 +++++++ src/main.cpp | 18 +++-- src/node_canvas.cpp | 66 ++++++++++--------- .../apple_platform_services.cpp | 24 +++++++ src/platform_apple/apple_platform_services.h | 8 +++ .../legacy_platform_services.cpp | 41 ++++++++---- 9 files changed, 167 insertions(+), 52 deletions(-) diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 8e846b28..d064e3f8 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,24 @@ agent or engineer to remove them without reconstructing context from chat. ## Reductions +- 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` merged-path and + non-blend checkerboard background setup now route through + `execute_legacy_canvas_draw_merge_background_setup(...)` in + `src/legacy_canvas_draw_merge_services.h` instead of keeping that shared + background loop inline in both branches of `NodeCanvas::draw()`; broader + canvas draw orchestration remains. +- 2026-06-16: `DEBT-0003` was narrowed again. The Windows splash-dialog worker + and HMD renderer worker in `src/main.cpp` now use `std::jthread` with + explicit stop requests instead of raw `std::thread` ownership; retained + global VR state, Win32 message-loop ownership, and broader app runtime + coupling remain. +- 2026-06-16: `DEBT-0017` was narrowed again. Apple render-context + acquire/release/present hooks and iOS main-render-target binding now route + through explicit bridge callbacks in + `src/platform_apple/apple_platform_services.*` consumed by + `src/platform_legacy/legacy_platform_services.cpp` instead of direct `App::I` + calls in those legacy platform methods; the retained Apple fallback adapter + and broader platform singleton reach remain. - 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` non-`draw_merged` per-layer/per-plane retained draw execution now routes through `execute_legacy_canvas_draw_merge_layer_plane(...)` in diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 04c9df91..4890335c 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -98,15 +98,17 @@ Current architecture mismatches that must be treated as real blockers: directly, and Linux FPS title reporting now uses an injected callback, but retained Apple bridging in `platform_legacy` and other platform/app coupling remain, even though iOS keyboard visibility and prepared-file save handoff - now also route through explicit Apple bridge callbacks. + now also route through explicit Apple bridge callbacks and Apple render- + context hooks plus iOS main-render-target binding now route through the same + bridge style. - `src/platform_legacy/legacy_platform_services.*` is still part of the live app shell. - `pp_panopainter_ui` still depends on `pp_legacy_app`. - `Canvas`, `NodeCanvas`, and `NodeStrokePreview` still own too much live OpenGL execution around the renderer boundary, even though `NodeCanvas` display resolve, cache-to-screen composite, post-draw mask/grid/current-mode - sequencing, and per-layer/per-plane retained draw execution now route - through retained draw-merge helpers. + sequencing, per-layer/per-plane retained draw execution, and shared + checkerboard background setup now route through retained draw-merge helpers. - `app_layout.cpp` and `app_dialogs.cpp` are still mixed shell/controller files rather than thin composition/binding surfaces. - `App`, `Canvas`, `Node`, retained workers, and platform entrypoints still use @@ -115,7 +117,8 @@ Current architecture mismatches that must be treated as real blockers: ownership, even though most previously detached or raw app-facing worker launches now use owned `std::jthread` or service-owned worker queues and `AppRuntime` now owns render/UI workers with explicit `std::jthread` - shutdown semantics. + shutdown semantics while the Windows splash-dialog and HMD renderer workers + also use owned `std::jthread` lifecycle. - Modern C++23 usage exists in extracted components, especially `std::span`, explicit result/status objects, and a few concepts, but the live app still does not consistently express ownership, thread affinity, or renderer diff --git a/docs/modernization/tasks.md b/docs/modernization/tasks.md index 87d073fe..c2ca8731 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -131,6 +131,8 @@ Current slice: - `NodeStrokePreview` final composite plus preview-texture copy now route through `legacy_node_stroke_preview_execution_services.h`, but the preview node still owns most live-pass and retained GL resource execution. +- `NodeCanvas` merged-path and non-blend checkerboard background setup now also + route through `execute_legacy_canvas_draw_merge_background_setup(...)`. - `NodeCanvas` display resolve plus cache-to-screen checkerboard/cache-texture composite now route through `legacy_canvas_draw_merge_services.h`. - `NodeCanvas` smoothing-mask overlay, smoothing-mask face pass, grid keepalive @@ -370,6 +372,9 @@ modernization foundation. Current slice: - app-owned render/UI runtime queues and cloud worker ownership are already moving behind owned runtime/service objects +- Windows splash-dialog and HMD renderer worker ownership in `main.cpp` now + also use `std::jthread` with explicit stop requests instead of raw + `std::thread` - brush package import/export now use service-owned `std::jthread` workers and UI-thread completion handoff - prepared-file save work and grid lightmap launch now also use service-owned @@ -585,6 +590,9 @@ Current slice: - iOS virtual-keyboard visibility and prepared-file save handoff now also route through explicit Apple bridge callbacks instead of direct `App::I` calls in `LegacyPlatformServices` +- Apple render-context acquire/release/present hooks and iOS + `bind_main_render_target()` now also route through explicit Apple bridge + callbacks instead of direct `App::I` calls in `LegacyPlatformServices` - retained Apple callback injection and broader `platform_legacy` singleton reach are still open diff --git a/src/legacy_canvas_draw_merge_services.h b/src/legacy_canvas_draw_merge_services.h index 6c1dc15e..887daefc 100644 --- a/src/legacy_canvas_draw_merge_services.h +++ b/src/legacy_canvas_draw_merge_services.h @@ -118,6 +118,16 @@ struct LegacyCanvasDrawMergeTemporaryCompositeExecution { std::function unbind_textures; }; +struct LegacyCanvasDrawMergeBackgroundSetupUniforms { + bool draw_merged = false; + bool use_blend = false; +}; + +struct LegacyCanvasDrawMergeBackgroundSetupExecution { + std::function disable_blend; + std::function draw_checkerboard_plane; +}; + template < typename Setup, typename BindSamplers, @@ -485,6 +495,21 @@ inline void execute_legacy_canvas_draw_merge_temporary_composite( execution.unbind_textures(); } +inline void execute_legacy_canvas_draw_merge_background_setup( + const LegacyCanvasDrawMergeBackgroundSetupUniforms& uniforms, + const LegacyCanvasDrawMergeBackgroundSetupExecution& execution) +{ + if (uniforms.draw_merged) { + execution.disable_blend(); + } + + if (uniforms.draw_merged || !uniforms.use_blend) { + for (int plane_index = 0; plane_index < 6; ++plane_index) { + execution.draw_checkerboard_plane(plane_index); + } + } +} + inline void execute_legacy_canvas_draw_merge_plane_setup( const LegacyCanvasDrawMergePlaneSetupUniforms& uniforms, const LegacyCanvasDrawMergePlaneSetupExecution& execution) diff --git a/src/main.cpp b/src/main.cpp index 2bf372e9..87b9658c 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -30,6 +30,7 @@ #include #include #include +#include #define WM_USER_CLOSE (WM_USER + 1) #define WM_USER_WAKEUP (WM_USER + 2) @@ -47,7 +48,7 @@ std::thread::id gl_thread; std::map vkey_map; static wchar_t window_title[512]; -std::thread hmd_renderer; +std::jthread hmd_renderer; int vr_frames = 0; int running = -1; int vr_running = 0; @@ -535,8 +536,11 @@ bool win32_vr_start() } if (hmd_renderer.joinable()) + { + hmd_renderer.request_stop(); hmd_renderer.join(); - hmd_renderer = std::thread([&] { + } + hmd_renderer = std::jthread([&](std::stop_token stop_token) { if (!vive) return; @@ -558,7 +562,7 @@ bool win32_vr_start() auto t0 = GetTickCount64(); float one_sec_timer = 0; int frames = 0; - while (vr_running && running == 1 && vive->Valid()) + while (!stop_token.stop_requested() && vr_running && running == 1 && vive->Valid()) { std::unique_lock lock(hmd_render_mutex); auto t1 = GetTickCount64(); @@ -606,7 +610,10 @@ void win32_vr_stop() { vr_running = false; if (hmd_renderer.joinable()) + { + hmd_renderer.request_stop(); hmd_renderer.join(); + } vive->Terminate(); delete vive; vive = nullptr; @@ -764,7 +771,7 @@ int main(int argc, char** argv) LOG("data files ok"); } - std::thread dialog_thread(splash_thread_loop); + std::jthread dialog_thread(splash_thread_loop); init_vk_map(); @@ -1034,7 +1041,10 @@ LRESULT CALLBACK WndProc(HWND hWnd, UINT msg, WPARAM wp, LPARAM lp) case WM_USER_CLOSE: running = 0; if (hmd_renderer.joinable()) + { + hmd_renderer.request_stop(); hmd_renderer.join(); + } App::I->runtime().ui_thread_stop(); App::I->runtime().render_thread_stop(); App::I->terminate(); diff --git a/src/node_canvas.cpp b/src/node_canvas.cpp index 39f8347e..53ea5013 100644 --- a/src/node_canvas.cpp +++ b/src/node_canvas.cpp @@ -365,24 +365,35 @@ void NodeCanvas::draw() bool draw_merged = !(m_canvas->m_current_mode == kCanvasMode::Camera); draw_merged = false; + const auto draw_background_plane = [&](int plane_index) { + auto plane_mvp = proj * camera * + glm::scale(glm::vec3(m_canvas->m_layers.size() + 500)) * + m_canvas->m_plane_transform[plane_index] * + glm::translate(glm::vec3(0, 0, -1)); + + pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader( + pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms { + .mvp = plane_mvp, + .colorize = false, + }); + m_face_plane.draw_fill(); + }; + if (draw_merged) { - apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); - // draw the grid + pp::panopainter::execute_legacy_canvas_draw_merge_background_setup( + { + .draw_merged = true, + }, + { + .disable_blend = [&] { + apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); + }, + .draw_checkerboard_plane = draw_background_plane, + }); + for (int plane_index = 0; plane_index < 6; plane_index++) { - auto plane_mvp = proj * camera * - glm::scale(glm::vec3(m_canvas->m_layers.size() + 500)) * - m_canvas->m_plane_transform[plane_index] * - glm::translate(glm::vec3(0, 0, -1)); - - pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader( - pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms { - .mvp = plane_mvp, - .colorize = false, - }); - m_face_plane.draw_fill(); - int z = 1; auto plane_mvp_z = proj * camera * //glm::scale(glm::vec3(z + 1)) * @@ -421,24 +432,17 @@ void NodeCanvas::draw() m_cache_rtt.bindFramebuffer(); m_cache_rtt.clear({ 1, 1, 1, 0 }); } - else - { - // draw the grid - for (int plane_index = 0; plane_index < 6; plane_index++) - { - auto plane_mvp = proj * camera * - glm::scale(glm::vec3(m_canvas->m_layers.size() + 500)) * - m_canvas->m_plane_transform[plane_index] * - glm::translate(glm::vec3(0, 0, -1)); - pp::panopainter::setup_legacy_canvas_draw_merge_checkerboard_shader( - pp::panopainter::LegacyCanvasDrawMergeCheckerboardUniforms { - .mvp = plane_mvp, - .colorize = false, - }); - m_face_plane.draw_fill(); - } - } + pp::panopainter::execute_legacy_canvas_draw_merge_background_setup( + { + .use_blend = use_blend, + }, + { + .disable_blend = [&] { + apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); + }, + .draw_checkerboard_plane = draw_background_plane, + }); // if not using shader blend, use gl rasterizer blend use_blend ? apply_node_canvas_capability(pp::renderer::gl::blend_state(), false) : apply_node_canvas_capability(pp::renderer::gl::blend_state(), true); diff --git a/src/platform_apple/apple_platform_services.cpp b/src/platform_apple/apple_platform_services.cpp index 78f255de..013674ad 100644 --- a/src/platform_apple/apple_platform_services.cpp +++ b/src/platform_apple/apple_platform_services.cpp @@ -183,6 +183,30 @@ void AppleDocumentPlatformServices::set_cursor_visible(bool visible) const #endif } +void AppleDocumentPlatformServices::acquire_render_context() const +{ + if (bridge_.acquire_render_context) + bridge_.acquire_render_context(); +} + +void AppleDocumentPlatformServices::release_render_context() const +{ + if (bridge_.release_render_context) + bridge_.release_render_context(); +} + +void AppleDocumentPlatformServices::present_render_context() const +{ + if (bridge_.present_render_context) + bridge_.present_render_context(); +} + +void AppleDocumentPlatformServices::bind_main_render_target() const +{ + if (bridge_.bind_main_render_target) + bridge_.bind_main_render_target(); +} + void AppleDocumentPlatformServices::save_prepared_file( std::string_view path, std::string_view suggested_name, diff --git a/src/platform_apple/apple_platform_services.h b/src/platform_apple/apple_platform_services.h index 48f3316d..f96f57f4 100644 --- a/src/platform_apple/apple_platform_services.h +++ b/src/platform_apple/apple_platform_services.h @@ -22,6 +22,10 @@ struct AppleDocumentPickerBridge { std::function display_file; std::function share_file; std::function set_cursor_visible; + std::function acquire_render_context; + std::function release_render_context; + std::function present_render_context; + std::function bind_main_render_target; std::function save_prepared_file; std::function save_ui_state; }; @@ -49,6 +53,10 @@ public: void display_file(std::string_view path) const; void share_file(std::string_view path) const; void set_cursor_visible(bool visible) const; + void acquire_render_context() const; + void release_render_context() const; + void present_render_context() const; + void bind_main_render_target() const; void save_prepared_file( std::string_view path, std::string_view suggested_name, diff --git a/src/platform_legacy/legacy_platform_services.cpp b/src/platform_legacy/legacy_platform_services.cpp index 4dd2d173..333c5f28 100644 --- a/src/platform_legacy/legacy_platform_services.cpp +++ b/src/platform_legacy/legacy_platform_services.cpp @@ -143,6 +143,18 @@ public: [ios_view hide_keyboard]; }); }; + bridge.acquire_render_context = [ios_view] { + [ios_view async_lock]; + }; + bridge.release_render_context = [ios_view] { + [ios_view async_unlock]; + }; + bridge.present_render_context = [ios_view] { + [ios_view async_swap]; + }; + bridge.bind_main_render_target = [ios_view] { + [ios_view->glview bindDrawable]; + }; bridge.display_file = [ios_view](std::string path) { dispatch_async(dispatch_get_main_queue(), ^{ [ios_view display_file:path]; @@ -197,6 +209,15 @@ public: [osx_view share_file:[NSString stringWithUTF8String:path.c_str()]]; }); }; + bridge.acquire_render_context = [osx_view] { + [osx_view async_lock]; + }; + bridge.release_render_context = [osx_view] { + [osx_view async_unlock]; + }; + bridge.present_render_context = [osx_view] { + [osx_view async_swap]; + }; bridge.set_cursor_visible = [osx_view](bool visible) { [osx_view show_cursor:visible]; }; @@ -372,10 +393,8 @@ public: void acquire_render_context() override { -#if __OSX__ - [App::I->osx_view async_lock]; -#elif __IOS__ - [App::I->ios_view async_lock]; +#if defined(__IOS__) || defined(__OSX__) + active_apple_document_platform_services().acquire_render_context(); #elif __ANDROID__ android_async_lock(); #elif __LINUX__ || __WEB__ @@ -385,10 +404,8 @@ public: void release_render_context() override { -#if __OSX__ - [App::I->osx_view async_unlock]; -#elif __IOS__ - [App::I->ios_view async_unlock]; +#if defined(__IOS__) || defined(__OSX__) + active_apple_document_platform_services().release_render_context(); #elif __ANDROID__ android_async_unlock(); #endif @@ -396,10 +413,8 @@ public: void present_render_context() override { -#if __OSX__ - [App::I->osx_view async_swap]; -#elif __IOS__ - [App::I->ios_view async_swap]; +#if defined(__IOS__) || defined(__OSX__) + active_apple_document_platform_services().present_render_context(); #elif __ANDROID__ android_async_swap(); #elif __LINUX__ || __WEB__ @@ -417,7 +432,7 @@ public: void bind_main_render_target() override { #if __IOS__ - [App::I->ios_view->glview bindDrawable]; + active_apple_document_platform_services().bind_main_render_target(); #else bind_default_render_target(); #endif