diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 97ec36dc..690f331a 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -18,6 +18,18 @@ agent or engineer to remove them without reconstructing context from chat. ## Reductions +- 2026-06-16: `DEBT-0037` was narrowed again. `App::rec_loop()` in + `src/app.cpp` now delegates recording worker iteration orchestration into + `process_legacy_recording_worker_iteration(...)` in + `src/legacy_recording_services.cpp` instead of owning the wait/plan/encode + shell itself; retained recording loop control, readback call sites, and MP4 + execution remain. +- 2026-06-16: `DEBT-0036` was narrowed again. `NodeCanvas` checkerboard + background-plane callback setup now routes through + `make_legacy_canvas_draw_merge_background_checkerboard_plane(...)` in + `src/legacy_canvas_draw_merge_services.h` instead of keeping that callback + inline in `NodeCanvas::draw()`; broader canvas draw orchestration and + retained GL resource ownership remain. - 2026-06-16: `DEBT-0003` was narrowed again. Canvas async import/export/save/open background work now runs through an `AppRuntime`-owned queue/worker in `src/app_runtime.h/.cpp`, and diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 49cbc904..22ec169c 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -116,9 +116,10 @@ Current architecture mismatches that must be treated as real blockers: sequencing, per-layer/per-plane retained draw execution, and shared checkerboard background setup now route through retained draw-merge helpers, with the cache-to-screen checkerboard-plane callback setup also reduced and - the merged-path per-plane merged-texture draw plus the smoothing-mask face - shader/draw pass plus heightmap, current-mode, and grid-mode callback setup - now routed through the same retained helper family. + the merged-path checkerboard background-plane callback plus per-plane + merged-texture draw plus the smoothing-mask face shader/draw pass plus + heightmap, current-mode, and grid-mode callback setup now routed through the + same retained helper family. - `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 @@ -138,8 +139,9 @@ Current architecture mismatches that must be treated as real blockers: helper instead of a bare static accessor, the prepared-file worker and the canvas async import/export/save/open worker now live under `AppRuntime` instead of retained static app-events/canvas workers, and `App::rec_loop()` - now has a smaller local wait/iteration/encode shell even though the retained - recording loop still owns the worker-side readback flow. + now delegates worker-iteration orchestration into the retained recording + bridge even though that retained recording path still owns the worker-side + readback flow. - 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 c269568e..549a46e0 100644 --- a/docs/modernization/tasks.md +++ b/docs/modernization/tasks.md @@ -151,6 +151,10 @@ Current slice: callback setup now also routes through `make_legacy_canvas_draw_merge_grid_modes_draw(...)`, but broader post-draw orchestration is still inline. +- `NodeCanvas` checkerboard background plane callback setup now also routes + through `make_legacy_canvas_draw_merge_background_checkerboard_plane(...)`, + but the node still owns broader live layer traversal and renderer-state + sequencing. - `NodeCanvas` smoothing-mask face shader setup plus per-face draw execution now also route through `execute_legacy_canvas_draw_merge_smask_faces(...)`, but the node still owns @@ -420,12 +424,10 @@ Current slice: - preview background rendering, recording, and the retained `NodePanelGrid::bake_uvs()` worker now also use `std::jthread`, but their retained loop/control flow is still open -- `App::rec_loop()` now routes its frame encode/update chunk through a local - helper, its iteration-context setup now also routes through a local helper, - and its wait/plan/encode iteration shell now also routes through a local - helper, while `App::update()` no longer carries the dead update mutex - residue; retained recording loop control and readback ownership are still - open +- `App::rec_loop()` now delegates its worker-iteration orchestration into the + retained recording bridge in `src/legacy_recording_services.cpp`, while + `App::update()` no longer carries the dead update mutex residue; retained + recording loop control, readback ownership, and MP4 execution are still open Write scope: - `src/canvas.cpp` diff --git a/src/app.cpp b/src/app.cpp index fa0dc5fd..59f89b73 100644 --- a/src/app.cpp +++ b/src/app.cpp @@ -211,6 +211,11 @@ void App::initLog() LOG("load preferences failed"); } +namespace pp::panopainter +{ + bool process_legacy_recording_worker_iteration(App& app); +} + bool App::check_license() { return true; // TODO: for distribuiton only @@ -641,78 +646,13 @@ void App::rec_export(std::string path) LOG("Recording export action failed: %s", status.message); } -namespace -{ - template - struct RecordingWorkerIterationContext - { - Canvas* legacy_canvas = nullptr; - CanvasDocument* canvas_document = nullptr; - CanvasEncoder* encoder = nullptr; - pp::app::RecordingWorkerIterationPlan plan{}; - }; - - template - RecordingWorkerIterationContext make_recording_worker_iteration_context(App& app) - { - RecordingWorkerIterationContext context; - context.legacy_canvas = Canvas::I; - context.canvas_document = app.canvas ? app.canvas->m_canvas.get() : nullptr; - context.encoder = context.legacy_canvas ? context.legacy_canvas->m_encoder.get() : nullptr; - context.plan = pp::app::plan_recording_worker_iteration( - app.rec_running, - context.encoder != nullptr, - context.legacy_canvas != nullptr && context.canvas_document != nullptr); - return context; - } - - template - void encode_recording_frame( - App& app, - pp::app::RecordingWorkerIterationPlan const& plan, - Canvas* legacy_canvas, - CanvasDocument* canvas_document, - CanvasEncoder* encoder) - { - if (plan.clear_dirty_stroke) - canvas_document->m_dirty_stroke = false; - - PBO equirect = legacy_canvas->m_layers_merge.gen_equirect_pbo(encoder->frame_size()); - std::this_thread::yield(); - - ImageRef img; - img.create(equirect.width, equirect.height, equirect.map()); - encoder->encode(img); - equirect.unmap(); - LOG("rec frame encoded"); - - if (plan.update_frame_label) - app.update_rec_frames(); - } - - template - bool process_recording_worker_iteration(App& app) - { - std::unique_lock lock(app.rec_mutex); - app.rec_cv.wait(lock); - - const auto iteration = make_recording_worker_iteration_context(app); - if (!iteration.plan.continue_running) - return false; - - if (iteration.plan.encode_frame && iteration.legacy_canvas && iteration.canvas_document && iteration.encoder) - encode_recording_frame(app, iteration.plan, iteration.legacy_canvas, iteration.canvas_document, iteration.encoder); - return true; - } -} - void App::rec_loop() { BT_SetTerminate(); rec_running = true; while (rec_running) { - if (!process_recording_worker_iteration(*this)) + if (!pp::panopainter::process_legacy_recording_worker_iteration(*this)) break; } } diff --git a/src/legacy_canvas_draw_merge_services.h b/src/legacy_canvas_draw_merge_services.h index a0797278..755de7bb 100644 --- a/src/legacy_canvas_draw_merge_services.h +++ b/src/legacy_canvas_draw_merge_services.h @@ -276,6 +276,28 @@ template }; } +template +[[nodiscard]] inline auto make_legacy_canvas_draw_merge_background_checkerboard_plane( + glm::mat4 proj, + glm::mat4 camera, + float layer_scale, + PlaneTransform plane_transform, + DrawPlane draw_plane) +{ + return [proj, camera, layer_scale, plane_transform, draw_plane](int plane_index) { + auto checkerboard_uniforms = LegacyCanvasDrawMergeCheckerboardUniforms { + .mvp = proj * camera * + glm::scale(glm::vec3(layer_scale)) * + plane_transform[plane_index] * + glm::translate(glm::vec3(0, 0, -1.f)), + .colorize = false, + }; + + setup_legacy_canvas_draw_merge_checkerboard_shader(checkerboard_uniforms); + draw_plane(); + }; +} + struct LegacyCanvasDrawMergeSmaskFacesExecution { std::function set_active_texture_unit; std::function enable_blend; diff --git a/src/legacy_recording_services.cpp b/src/legacy_recording_services.cpp index 7fc2e428..eb21b6ef 100644 --- a/src/legacy_recording_services.cpp +++ b/src/legacy_recording_services.cpp @@ -11,6 +11,40 @@ namespace pp::panopainter { namespace { +pp::app::RecordingWorkerIterationPlan make_recording_worker_iteration_plan(App& app) +{ + auto* legacy_canvas = Canvas::I; + auto* canvas_document = app.canvas ? app.canvas->m_canvas.get() : nullptr; + auto* encoder = legacy_canvas ? legacy_canvas->m_encoder.get() : nullptr; + return pp::app::plan_recording_worker_iteration( + app.rec_running, + encoder != nullptr, + legacy_canvas != nullptr && canvas_document != nullptr); +} + +void encode_recording_frame( + App& app, + const pp::app::RecordingWorkerIterationPlan& plan, + Canvas* legacy_canvas, + Canvas* canvas_document, + CanvasEncoder* encoder) +{ + if (plan.clear_dirty_stroke) + canvas_document->m_dirty_stroke = false; + + PBO equirect = legacy_canvas->m_layers_merge.gen_equirect_pbo(encoder->frame_size()); + std::this_thread::yield(); + + ImageRef img; + img.create(equirect.width, equirect.height, equirect.map()); + encoder->encode(img); + equirect.unmap(); + LOG("rec frame encoded"); + + if (plan.update_frame_label) + app.update_rec_frames(); +} + class LegacyRecordingServices final : public pp::app::RecordingServices { public: explicit LegacyRecordingServices(App& app) noexcept @@ -77,6 +111,23 @@ private: } // namespace +bool process_legacy_recording_worker_iteration(App& app) +{ + std::unique_lock lock(app.rec_mutex); + app.rec_cv.wait(lock); + + const auto plan = make_recording_worker_iteration_plan(app); + if (!plan.continue_running) + return false; + + auto* legacy_canvas = Canvas::I; + auto* canvas_document = app.canvas ? app.canvas->m_canvas.get() : nullptr; + auto* encoder = legacy_canvas ? legacy_canvas->m_encoder.get() : nullptr; + if (plan.encode_frame && legacy_canvas && canvas_document && encoder) + encode_recording_frame(app, plan, legacy_canvas, canvas_document, encoder); + return true; +} + pp::foundation::Status execute_legacy_recording_start_action( App& app, pp::app::RecordingStartAction action) diff --git a/src/node_canvas.cpp b/src/node_canvas.cpp index c370384d..82cacf87 100644 --- a/src/node_canvas.cpp +++ b/src/node_canvas.cpp @@ -365,20 +365,6 @@ 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) { pp::panopainter::execute_legacy_canvas_draw_merge_background_setup( @@ -389,7 +375,14 @@ void NodeCanvas::draw() .disable_blend = [&] { apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); }, - .draw_checkerboard_plane = draw_background_plane, + .draw_checkerboard_plane = pp::panopainter::make_legacy_canvas_draw_merge_background_checkerboard_plane( + proj, + camera, + m_canvas->m_layers.size() + 500.f, + m_canvas->m_plane_transform, + [&] { + m_face_plane.draw_fill(); + }), }); const auto draw_merged_texture_plane = [&](int plane_index) { @@ -449,7 +442,14 @@ void NodeCanvas::draw() .disable_blend = [&] { apply_node_canvas_capability(pp::renderer::gl::blend_state(), false); }, - .draw_checkerboard_plane = draw_background_plane, + .draw_checkerboard_plane = pp::panopainter::make_legacy_canvas_draw_merge_background_checkerboard_plane( + proj, + camera, + m_canvas->m_layers.size() + 500.f, + m_canvas->m_plane_transform, + [&] { + m_face_plane.draw_fill(); + }), }); // if not using shader blend, use gl rasterizer blend