From 6b92d0bfea409ae8d7c7dc4465650d77013e8eca Mon Sep 17 00:00:00 2001 From: omigamedev Date: Wed, 3 Jun 2026 19:27:57 +0200 Subject: [PATCH] Plan thumbnail blend feedback copies --- docs/modernization/capability-map.md | 4 ++-- docs/modernization/debt.md | 2 +- docs/modernization/roadmap.md | 10 +++++++--- src/canvas.cpp | 18 ++++++++++-------- 4 files changed, 20 insertions(+), 14 deletions(-) diff --git a/docs/modernization/capability-map.md b/docs/modernization/capability-map.md index 72536e7..606bbb6 100644 --- a/docs/modernization/capability-map.md +++ b/docs/modernization/capability-map.md @@ -15,7 +15,7 @@ and validation command. | Open-document routing | `App::open_document` | `pp_app_core`, `pano_cli`, `pp_panopainter_ui`, `pp_document`, `pp_assets` | Project/ABR/PPBR route tests, malformed path tests, open-action plan tests, CLI route/action smoke, app open smoke | | Document session decisions | `App::open_document`, `App::request_close`, save hotkeys, file menu, dialogs | `pp_app_core`, `pano_cli`, `pp_panopainter_ui` | Clean/dirty/prompt-open/save/save-as/save-version/save-before-workflow/name/new-document resolution/overwrite/version-target decision tests, CLI session, new-document, document-file, and document-version smoke, app close/open/save/new/browse smoke | | Version metadata | `scripts/pre-build.py`, `version.*` | build system, `pp_foundation` | Generated header smoke test, missing-tag behavior | -| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input | +| Thumbnail generation/read | `Canvas`, `Image` | `pp_assets`, `pp_paint_renderer` | Golden thumbnail, corrupt input, destination-feedback copy/fetch gate | | Save-as, overwrite prompts | App/dialogs | `pp_app_core`, `pp_panopainter_ui`, `pp_platform_*` | Decision tests, UI automation, and platform smoke | ## Image And Export @@ -37,7 +37,7 @@ and validation command. | PPBR import/export | brush panel/dialog | `pp_assets`, `pp_panopainter_ui` | Round-trip fixture | | Stroke sampling | `Stroke`, `Canvas` | `pp_paint` | Property tests for spacing, pressure, jitter | | Dual brush/pattern behavior | `Brush`, shaders | `pp_paint`, `pp_paint_renderer` | Stroke-alpha CPU reference, dual/pattern feedback planning, GPU golden | -| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors, fixed-function/framebuffer-fetch/ping-pong stroke composite planning, live `Canvas`/`NodeCanvas` blend-gate coverage, live canvas stroke-feedback destination-copy coverage, and GPU parity | +| Blend modes | GLSL include files, layer rendering | `pp_paint`, `pp_paint_renderer` | Final RGBA and stroke-alpha CPU reference vectors, fixed-function/framebuffer-fetch/ping-pong stroke composite planning, live `Canvas`/`NodeCanvas` blend-gate coverage, live canvas stroke/thumbnail destination-copy coverage, and GPU parity | | Erase/flood fill/masks | `Canvas`, modes, shaders | `pp_document`, `pp_paint_renderer` | Edge masks, alpha lock, dirty rects | ## Layers And Animation diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 2cb3268..80e2cf5 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -53,7 +53,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and call the iOS SonarPen bridge directly | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pano_cli plan-tools-menu --command shortcuts`; `pano_cli plan-tools-panel --panel layers`; `pano_cli plan-tools-panel --panel animation --already-visible`; `ctest --preset desktop-fast --build-config Debug` | Tools panel creation, submenu routing, grid clear, camera reset, shortcuts dialog, and SonarPen dispatch are owned by injected app/UI/platform services with `App::init_menu_tools` acting only as a UI adapter and no legacy Tools adapter | | DEBT-0034 | Open | Modernization | About menu command planning and execution dispatch now consume pure `pp_app_core` through `App::init_menu_about`, `pano_cli plan-about-menu`, and the `AboutMenuServices` boundary, but the live adapter still opens legacy About/manual/what's-new dialogs, invokes the injected crash hook, and runs the legacy Canvas stroke performance test directly | Preserve About menu behavior while dialogs and diagnostics move toward app/UI/platform services | `pp_app_core_about_menu_tests`; `pano_cli plan-about-menu --command news --version-major 2 --version-minor 5 --version-fix 7`; `pano_cli plan-about-menu --command performance --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | About/manual/what's-new dialog dispatch, crash-test dispatch, and performance-test execution are owned by injected app/UI/platform services with `App::init_menu_about` acting only as a UI adapter and no legacy About adapter | | DEBT-0035 | Open | Modernization | Main toolbar/status command planning and execution dispatch now consume pure `pp_app_core` through `App::init_toolbar_main`, `pano_cli plan-main-toolbar`, and the `MainToolbarServices` boundary, and history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, but the live adapter still opens legacy open/save/settings/message-box dialogs and delegates to legacy history/canvas adapters | Preserve reachable toolbar/status behavior while app shell commands move toward app/document/UI services | `pp_app_core_main_toolbar_tests`; `pano_cli plan-main-toolbar --command undo --undo-count 2`; `pano_cli plan-main-toolbar --command clear-canvas --no-canvas`; `ctest --preset desktop-fast --build-config Debug` | Open/save/settings/message-box routing, undo/redo/clear-history execution, and canvas-clear execution are owned by injected app/document/UI services with `App::init_toolbar_main` acting only as a UI adapter and no legacy toolbar adapter | -| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::render_device_features` as the backend conversion point. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current stroke shader feedback decision, and live `Canvas::stroke_draw` uses it for main-brush, dual-brush, and stroke-pad destination-copy decisions. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, and thumbnail layer blending still use legacy OpenGL canvas execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | +| DEBT-0036 | Open | Modernization | `pp_renderer_api`, `pp_paint_renderer`, `pano_cli plan-paint-feedback`, and `pano_cli plan-stroke-composite` can choose backend-neutral complex paint feedback strategies for fixed-function blending, framebuffer-fetch-capable renderers, or ping-pong render targets. OpenGL extension detection now stores `pp::renderer::RenderDeviceFeatures` through `ShaderManager`, using `pp_renderer_gl::render_device_features` as the backend conversion point. `pp_paint_renderer::plan_canvas_blend_gate` owns the compatibility mapping from persisted layer/brush blend indices to the extracted stroke-composite planner, and live `Canvas::draw_merge` plus `NodeCanvas` panorama rendering both call it with the stored renderer-neutral feature set for their existing shader-blend gates and destination-copy versus framebuffer-fetch decisions. `pp_paint_renderer::plan_canvas_stroke_feedback` also owns the current destination-feedback decision, and live `Canvas::stroke_draw` plus thumbnail layer blending use it for framebuffer-fetch versus destination-copy decisions. Actual live stroke rasterization, dual-brush compositing, pattern feedback math, and thumbnail layer compositing still use legacy OpenGL canvas execution | Preserve current painting behavior while the renderer boundary matures for OpenGL parity and later Vulkan/Metal experiments | `pp_renderer_api_tests`; `pp_renderer_gl_capabilities_tests`; `pp_paint_renderer_compositor_tests`; `pano_cli plan-paint-feedback --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-paint-feedback --texture-copy`; `pano_cli plan-stroke-composite --stroke-blend 10 --framebuffer-fetch --explicit-transitions --render-only`; `pano_cli plan-stroke-composite --layer-blend 4 --dual-blend --texture-copy`; `ctest --preset desktop-fast --build-config Debug`; `cmake --build --preset windows-msvc-default --config Debug --target PanoPainter` | Live stroke/layer compositing chooses its feedback path through `pp_paint_renderer` and renderer services, with OpenGL golden parity and Vulkan/Metal lab tests covering framebuffer-fetch and ping-pong behavior | ## Closed Debt diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index f77f2ce..646168b 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -891,9 +891,10 @@ individual `ShaderManager` extension booleans. `pp_paint_renderer::plan_canvas_stroke_feedback` now models the current stroke shader's required destination feedback without changing the legacy shader math. Live `Canvas::stroke_draw` consumes that plan for main-brush, dual-brush, and -stroke-pad destination-copy versus framebuffer-fetch decisions; thumbnail layer -blending is the remaining direct canvas feedback branch before a fuller live -paint-renderer execution boundary can take over. +stroke-pad destination-copy versus framebuffer-fetch decisions. Thumbnail layer +blending now consumes the same canvas destination-feedback decision for its +legacy `TextureBlend` path; the full thumbnail compositing execution remains +legacy OpenGL until a fuller live paint-renderer boundary can take over. The existing renderer classes are not yet fully behind the renderer interfaces. @@ -1633,6 +1634,9 @@ Results: `pp_paint_renderer` stroke-feedback plan to decide whether framebuffer fetch supplies destination color or the legacy OpenGL path must copy the target texture before drawing. +- Canvas thumbnail layer blending now uses the same canvas destination-feedback + plan for framebuffer-fetch versus texture-copy decisions; the thumbnail draw + itself still executes through retained OpenGL canvas code under DEBT-0036. - Canvas equirectangular import drawing and depth export rendering now route depth/blend state and active texture units through the renderer GL backend mapping. diff --git a/src/canvas.cpp b/src/canvas.cpp index c9a9030..091fec9 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -44,17 +44,17 @@ GLenum rgba_pixel_format() return static_cast(pp::renderer::gl::rgba_pixel_format()); } -pp::renderer::RenderDeviceFeatures canvas_stroke_composite_features() noexcept +pp::renderer::RenderDeviceFeatures canvas_render_device_features() noexcept { return ShaderManager::render_device_features(); } -pp::paint_renderer::CanvasStrokeFeedbackPlan canvas_stroke_feedback_plan( +pp::paint_renderer::CanvasStrokeFeedbackPlan canvas_destination_feedback_plan( int width, int height) noexcept { const auto plan = pp::paint_renderer::plan_canvas_stroke_feedback( - canvas_stroke_composite_features(), + canvas_render_device_features(), pp::renderer::Extent2D { .width = static_cast(std::max(width, 0)), .height = static_cast(std::max(height, 0)), @@ -86,7 +86,7 @@ pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan( } const auto plan = pp::paint_renderer::plan_canvas_blend_gate( - canvas_stroke_composite_features(), + canvas_render_device_features(), pp::paint_renderer::CanvasBlendGateRequest { .extent = pp::renderer::Extent2D { .width = static_cast(std::max(width, 0)), @@ -654,7 +654,7 @@ void Canvas::stroke_draw() if (brush->m_pattern_flipx) patt_scale.x *= -1.f; if (brush->m_pattern_flipy) patt_scale.y *= -1.f; - const auto stroke_feedback = canvas_stroke_feedback_plan(m_width, m_height); + const auto stroke_feedback = canvas_destination_feedback_plan(m_width, m_height); const bool copy_stroke_destination = !stroke_feedback.reads_destination_color; glDisable(blend_state()); @@ -2842,6 +2842,8 @@ Image Canvas::thumbnail_generate(int w, int h) m_face_plane.create<1>(2, 2); Texture2D blendtex; blendtex.create(w, h); + const auto layer_feedback = canvas_destination_feedback_plan(w, h); + const bool copy_layer_destination = !layer_feedback.reads_destination_color; // recalculate because of different aspect ratio than the m_proj matrix glm::mat4 proj = glm::perspective(glm::radians(m_cam_fov), (float)w / (float)h, 0.1f, 1000.f); @@ -2855,7 +2857,7 @@ Image Canvas::thumbnail_generate(int w, int h) ShaderManager::use(kShader::TextureBlend); ShaderManager::u_int(kShaderUniform::Tex, 0); ShaderManager::u_mat4(kShaderUniform::MVP, plane_mvp); - if (!ShaderManager::ext_framebuffer_fetch) + if (copy_layer_destination) { ShaderManager::u_int(kShaderUniform::TexBG, 2); set_active_texture_unit(2); @@ -2869,7 +2871,7 @@ Image Canvas::thumbnail_generate(int w, int h) m_layers[layer_index]->m_opacity == 0.f || !m_layers[layer_index]->face(i)) continue; - if (!ShaderManager::ext_framebuffer_fetch) + if (copy_layer_destination) { set_active_texture_unit(2); glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, w, h); @@ -2882,7 +2884,7 @@ Image Canvas::thumbnail_generate(int w, int h) m_layers[layer_index]->rtt(i).unbindTexture(); } - if (!ShaderManager::ext_framebuffer_fetch) + if (copy_layer_destination) { set_active_texture_unit(2); blendtex.unbind();