Route RTT texture updates through GL backend

This commit is contained in:
2026-06-04 23:44:25 +02:00
parent c9fb91ab48
commit 111cc8c892
11 changed files with 115 additions and 44 deletions

View File

@@ -301,7 +301,9 @@ Known local toolchain state:
clear-mask and clear-value mapping, and color-write-mask query tokens. `RTT` no longer clear-mask and clear-value mapping, and color-write-mask query tokens. `RTT` no longer
spells GL enum names directly. `RTT` also exposes a retained RGBA8 spells GL enum names directly. `RTT` also exposes a retained RGBA8
region-readback helper that uses the tested framebuffer readback dispatch for region-readback helper that uses the tested framebuffer readback dispatch for
canvas pick/history/snapshot and transform history paths. 2D canvas pick/history/snapshot and transform history paths, plus a retained
RGBA8 region-update helper that uses the tested texture-update dispatch for
canvas undo, layer restore, and flood-fill texture writes. 2D
framebuffer-to-texture copies used by retained canvas, transform, framebuffer-to-texture copies used by retained canvas, transform,
layer-conversion, panorama UI, and brush-preview paths now execute through a layer-conversion, panorama UI, and brush-preview paths now execute through a
tested `pp_renderer_gl` dispatch via `copy_framebuffer_to_texture_2d`; the tested `pp_renderer_gl` dispatch via `copy_framebuffer_to_texture_2d`; the
@@ -659,7 +661,8 @@ Known local toolchain state:
consumed by the retained `gl_state` utility, tested texture lifecycle/readback dispatch consumed by consumed by the retained `gl_state` utility, tested texture lifecycle/readback dispatch consumed by
the retained `Texture2D` utility, tested framebuffer blit/readback dispatch the retained `Texture2D` utility, tested framebuffer blit/readback dispatch
consumed by retained `RTT` resize/copy/readback and RGBA8 region-readback consumed by retained `RTT` resize/copy/readback and RGBA8 region-readback
paths, tested render-target texture parameter, framebuffer allocation/delete, paths, tested texture-update dispatch consumed by retained `RTT` RGBA8
dirty-region writes, tested render-target texture parameter, framebuffer allocation/delete,
color/depth attachment, status-check, and binding-restore dispatch consumed color/depth attachment, status-check, and binding-restore dispatch consumed
by retained `RTT::create`/`RTT::destroy`, tested RTT render-target clear, by retained `RTT::create`/`RTT::destroy`, tested RTT render-target clear,
masked color clear with color-write-mask restore, and texture-bind dispatch, masked color clear with color-write-mask restore, and texture-bind dispatch,

View File

@@ -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, and direct command execution is centralized in `src/legacy_app_shell_services.*`; SonarPen availability/startup now routes through `PlatformServices`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pp_platform_api_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-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, and direct command execution is centralized in `src/legacy_app_shell_services.*`; SonarPen availability/startup now routes through `PlatformServices`, but live adapters still construct legacy `NodePanelFloating` panels, mutate legacy panel nodes, clear `CanvasModeGrid`, reset `NodeCanvas` camera state, open legacy shortcuts UI, and rely on the legacy platform adapter for the retained iOS SonarPen bridge | Preserve current Tools menu behavior while UI shell actions move toward app/UI/platform services | `pp_app_core_tools_menu_tests`; `pp_platform_api_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, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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-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, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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, history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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-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, history/canvas commands now hand off through `HistoryUiServices` and `DocumentCanvasClearServices`, and live execution is centralized in `src/legacy_app_shell_services.*`, but the bridge 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::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `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`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, retained RTT clear, masked clear with color-write-mask restore, and texture bind/unbind now route through tested `pp_renderer_gl` dispatch, retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches now route through tested active-texture dispatch, retained Canvas, NodeCanvas, NodeStrokePreview, and desktop HMD viewport/scissor/capability execution now route through tested `pp_renderer_gl` dispatch adapters, retained NodeCanvas, CanvasMode, and NodePanelGrid capability-state snapshots now route through tested `pp_renderer_gl` query dispatch, CanvasLayer cube/equirect generation plus frame clears now route blend state, active texture units, viewport execution, color clears, and cube-face framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch adapters, `NodePanelGrid` live heightmap draw and bake setup now route depth/blend state, depth clears, color-write-mask toggles, active texture selection, bake viewport execution, sun-overlay viewport query, and desktop texture-resize readback through tested `pp_renderer_gl` dispatch adapters, retained CanvasMode overlay/mask/transform paths now route active texture, depth/blend state, transform/cut viewport execution, paint-mode blend/depth state snapshots, and canvas-tip pick framebuffer readback through tested `pp_renderer_gl` dispatch adapters, retained simple UI draw paths now share `legacy_ui_gl_dispatch` for blend-state execution, fallback 2D texture unbinds, `NodeViewport` viewport query/restore, color-buffer clears, and clear-color restore, retained `NodeCanvas` plus `NodeStrokePreview` draw-state paths now route viewport query, clear-color query, color-buffer clear, and clear-color restore through tested `pp_renderer_gl` dispatch helpers, and retained `Canvas` plus `CanvasLayer` stroke/object/thumbnail/frame-clear draw-state paths now route saved viewport or clear-color query and restore through the same tested helpers, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, and the retained `ShaderManager::ext_*` compatibility fields still use legacy OpenGL canvas/UI 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::query_opengl_capability_detection`, `detect_opengl_feature_state`, and `render_device_features` as the backend conversion point; that feature snapshot now includes float32-linear filtering, so canvas stroke texture format selection, renderer diagnostics, grid lightmap render planning, and grid bake target selection no longer read `ShaderManager::ext_*` flags directly. `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`, thumbnail layer blending, and `NodeStrokePreview` brush-preview rendering use it for framebuffer-fetch versus destination-copy decisions. The retained `copy_framebuffer_to_texture_2d` utility bridge now routes 2D framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch, retained `RTT::create`/`RTT::destroy` render-target texture parameter setup, optional depth renderbuffer allocation, framebuffer allocation/attachment/status checks, binding restore, and resource deletion now route through tested `pp_renderer_gl` dispatch, retained RTT clear, masked clear with color-write-mask restore, texture bind/unbind, and RGBA8 dirty-region texture writes now route through tested `pp_renderer_gl` dispatch, retained Canvas, NodeCanvas, and NodeStrokePreview texture-unit switches now route through tested active-texture dispatch, retained Canvas, NodeCanvas, NodeStrokePreview, and desktop HMD viewport/scissor/capability execution now route through tested `pp_renderer_gl` dispatch adapters, retained NodeCanvas, CanvasMode, and NodePanelGrid capability-state snapshots now route through tested `pp_renderer_gl` query dispatch, CanvasLayer cube/equirect generation plus frame clears now route blend state, active texture units, viewport execution, color clears, and cube-face framebuffer-to-texture copies through tested `pp_renderer_gl` dispatch adapters, `NodePanelGrid` live heightmap draw and bake setup now route depth/blend state, depth clears, color-write-mask toggles, active texture selection, bake viewport execution, sun-overlay viewport query, and desktop texture-resize readback through tested `pp_renderer_gl` dispatch adapters, retained CanvasMode overlay/mask/transform paths now route active texture, depth/blend state, transform/cut viewport execution, paint-mode blend/depth state snapshots, and canvas-tip pick framebuffer readback through tested `pp_renderer_gl` dispatch adapters, retained simple UI draw paths now share `legacy_ui_gl_dispatch` for blend-state execution, fallback 2D texture unbinds, `NodeViewport` viewport query/restore, color-buffer clears, and clear-color restore, retained `NodeCanvas` plus `NodeStrokePreview` draw-state paths now route viewport query, clear-color query, color-buffer clear, and clear-color restore through tested `pp_renderer_gl` dispatch helpers, and retained `Canvas` plus `CanvasLayer` stroke/object/thumbnail/frame-clear draw-state paths now route saved viewport or clear-color query and restore through the same tested helpers, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, and the retained `ShaderManager::ext_*` compatibility fields still use legacy OpenGL canvas/UI 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-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, and retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, retained `App::rec_loop` readback call sites, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pp_renderer_gl_capabilities_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback scheduling, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters | | DEBT-0037 | Open | Modernization | Recording lifecycle/export planning and execution dispatch now consume pure `pp_app_core` through `App::rec_start`, `App::rec_stop`, `App::rec_clear`, `App::rec_export`, `pano_cli plan-recording-session`, and the `RecordingServices` boundary; live execution is centralized in `src/legacy_recording_services.*`, and retained `PBO` allocation/readback/map/unmap/delete operations now route through tested `pp_renderer_gl` dispatch, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, retained `App::rec_loop` readback call sites, and `MP4Encoder::write_mp4` execution | Preserve current timelapse/MP4 behavior while recording moves toward app/document/renderer/video services | `pp_app_core_document_recording_tests`; `pp_renderer_gl_capabilities_tests`; `pano_cli plan-recording-session --running --frame-count 12`; `pano_cli plan-recording-session --platform-clears-files`; `ctest --preset desktop-fast --build-config Debug` | Recording thread lifecycle, frame readback scheduling, platform cleanup, progress reporting, and MP4 writing are owned by injected app/renderer/video services with `App` methods acting only as adapters |
| DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, the app-owned `upload`/`download`/license curl helpers now ask `PlatformServices` for the Android TLS-verification bypass policy, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally, but the bridge still uses legacy save-before-upload, app-owned curl helpers instead of an injected network service, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters | | DEBT-0038 | Open | Modernization | Cloud upload/browse/bulk planning and execution dispatch now consume pure `pp_app_core` through `App::cloud_upload`, `App::cloud_upload_all`, `App::cloud_browse`, `pano_cli plan-cloud-upload`, `pano_cli plan-cloud-upload-all`, `pano_cli plan-cloud-browse`, and the `CloudServices` boundary; live execution is centralized in `src/legacy_cloud_services.*`, the app-owned `upload`/`download`/license curl helpers now ask `PlatformServices` for the Android TLS-verification bypass policy, and retained `Asset::open_url`, `LogRemote::net_init`, and `NodeDialogCloud::load_thumbs_thread` curl sites consume the `pp_platform_api` default TLS policy helper instead of spelling Android branches locally, but the bridge still uses legacy save-before-upload, app-owned curl helpers instead of an injected network service, progress/message UI, OpenGL context guarding, `NodeDialogCloud`, `Canvas` project open, layer refresh, and `ActionManager` reset | Preserve current cloud behavior while cloud/network/document import flows move toward app/document/platform services | `pp_app_core_document_cloud_tests`; `pp_platform_api_tests`; `pano_cli plan-cloud-upload --new-document --unsaved`; `pano_cli plan-cloud-browse --selected-file demo.ppi`; `pano_cli plan-cloud-upload-all --file-count 3`; `ctest --preset desktop-fast --build-config Debug` | Cloud upload/download, TLS policy, save-before-upload, progress reporting, cloud browse dialog, downloaded project opening, layer refresh, OpenGL context ownership, and action-history reset are owned by injected app/document/network/platform/renderer services with `App` methods acting only as adapters |
| DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter | | DEBT-0039 | Open | Modernization | Document-open planning and execution dispatch now consume pure `pp_app_core` through `App::open_document`, `pano_cli plan-open-route`, `DocumentOpenServices`, and `src/legacy_document_open_services.*`, but the bridge still opens ABR/PPBR import prompts before delegating import execution to `src/legacy_brush_package_import_services.*`, applies unsaved-project discard prompts, calls legacy project-open execution, refreshes layer UI, updates the app title, and clears legacy history directly | Preserve current file-open/import behavior while document loading and brush import move toward app/document/asset/UI services | `pp_app_core_document_route_tests`; `pp_app_core_document_session_tests`; `pano_cli plan-open-route --path D:/Paint/Scenes/demo.ppi --unsaved`; `pano_cli plan-open-route --path D:/Paint/Brushes/clouds.ABR --unsaved`; `ctest --preset desktop-fast --build-config Debug` | Brush import prompting, project-open execution, unsaved-project discard prompting, layer refresh, title updates, and history clearing are owned by injected app/document/asset/UI services with `App::open_document` acting only as an adapter |

View File

@@ -821,6 +821,10 @@ Legacy `RTT` also exposes an RGBA8 region-readback helper that uses the same
backend framebuffer readback dispatch; canvas pick/history/snapshot and backend framebuffer readback dispatch; canvas pick/history/snapshot and
transform history paths now call that helper instead of binding an RTT and transform history paths now call that helper instead of binding an RTT and
calling `glReadPixels` directly. calling `glReadPixels` directly.
Legacy `RTT` now also exposes an RGBA8 region-update helper that routes dirty
rectangle texture writes through the tested `pp_renderer_gl` texture-update
dispatch; canvas undo, layer restore, and flood-fill apply paths now call it
instead of issuing direct `glTexSubImage2D` calls.
Retained `PBO` recording readbacks now route pixel-buffer allocation, Retained `PBO` recording readbacks now route pixel-buffer allocation,
framebuffer readback, map, unmap, and deletion through tested framebuffer readback, map, unmap, and deletion through tested
`pp_renderer_gl` dispatch helpers; recording thread ownership, progress UI, and `pp_renderer_gl` dispatch helpers; recording thread ownership, progress UI, and
@@ -1077,9 +1081,10 @@ delegate to `pp_renderer_gl`; text mesh buffer/VAO creation, deferred index
and vertex uploads, and indexed draw calls now execute through the same tested and vertex uploads, and indexed draw calls now execute through the same tested
mesh dispatch contracts used by `Shape`, leaving the retained `Font` utility mesh dispatch contracts used by `Shape`, leaving the retained `Font` utility
with thin GL adapter functions for mesh operations. with thin GL adapter functions for mesh operations.
Canvas undo/redo dirty-region texture updates and readbacks now also delegate Canvas undo/redo dirty-region texture updates and readbacks now also execute
their 2D texture target, RGBA pixel format, and unsigned-byte component type through retained `RTT` helpers backed by `pp_renderer_gl`, including 2D texture
mapping to `pp_renderer_gl`. target, dirty-region offsets, RGBA pixel format, and unsigned-byte component
type mapping.
`NodeViewport` preview rendering now also delegates viewport query, `NodeViewport` preview rendering now also delegates viewport query,
clear-color query, color-buffer clear mask, viewport execution, color clear, clear-color query, color-buffer clear mask, viewport execution, color clear,
clear-color restore, and blend-state execution through the shared clear-color restore, and blend-state execution through the shared

View File

@@ -34,16 +34,6 @@ GLint rgba8_internal_format()
return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format()); return static_cast<GLint>(pp::renderer::gl::rgba8_internal_format());
} }
GLenum texture_2d_target()
{
return static_cast<GLenum>(pp::renderer::gl::texture_2d_target());
}
GLenum rgba_pixel_format()
{
return static_cast<GLenum>(pp::renderer::gl::rgba_pixel_format());
}
pp::renderer::RenderDeviceFeatures canvas_render_device_features() noexcept pp::renderer::RenderDeviceFeatures canvas_render_device_features() noexcept
{ {
return ShaderManager::render_device_features(); return ShaderManager::render_device_features();
@@ -107,11 +97,6 @@ pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan(
return fallback; return fallback;
} }
GLenum unsigned_byte_component_type()
{
return static_cast<GLenum>(pp::renderer::gl::unsigned_byte_component_type());
}
GLenum depth_test_state() GLenum depth_test_state()
{ {
return static_cast<GLenum>(pp::renderer::gl::depth_test_state()); return static_cast<GLenum>(pp::renderer::gl::depth_test_state());
@@ -1961,10 +1946,7 @@ void Canvas::FloodData::apply()
auto& rtt = layer->rtt(plane); auto& rtt = layer->rtt(plane);
App::I->render_task([&] App::I->render_task([&]
{ {
rtt.bindTexture(); rtt.updateRgba8(0, 0, rtt.getWidth(), rtt.getHeight(), rgb[plane].get());
glTexSubImage2D(texture_2d_target(), 0, 0, 0, rtt.getWidth(), rtt.getHeight(),
rgba_pixel_format(), unsigned_byte_component_type(), rgb[plane].get());
rtt.unbindTexture();
}); });
layer->face(plane) = true; layer->face(plane) = true;
layer->box(plane) = box_union(layer->box(plane), bb[plane]); layer->box(plane) = box_union(layer->box(plane), bb[plane]);

View File

@@ -4,7 +4,6 @@
#include "canvas.h" #include "canvas.h"
#include "canvas_actions.h" #include "canvas_actions.h"
#include "node_panel_layer.h" #include "node_panel_layer.h"
#include "renderer_gl/opengl_capabilities.h"
void ActionStroke::undo() void ActionStroke::undo()
{ {
@@ -37,22 +36,12 @@ void ActionStroke::undo()
{ {
App::I->render_task([&] App::I->render_task([&]
{ {
const auto texture_target = pp::renderer::gl::texture_2d_target(); m_canvas->m_layers[m_layer_idx]->rtt(i, m_frame_idx).updateRgba8(
const auto pixel_format = pp::renderer::gl::rgba_pixel_format();
const auto component_type = pp::renderer::gl::unsigned_byte_component_type();
m_canvas->m_layers[m_layer_idx]->rtt(i, m_frame_idx).bindTexture();
glTexSubImage2D(
texture_target,
0,
(int)m_box[i].x, (int)m_box[i].x,
(int)m_box[i].y, (int)m_box[i].y,
(int)box_sz.x, (int)box_sz.x,
(int)box_sz.y, (int)box_sz.y,
pixel_format,
component_type,
m_image[i].get()); m_image[i].get());
m_canvas->m_layers[m_layer_idx]->rtt(i, m_frame_idx).unbindTexture();
}); });
} }
else else

View File

@@ -695,15 +695,11 @@ void LayerFrame::restore(const Snapshot& snap)
// it's just a quick fix DON'T SHIP!! // it's just a quick fix DON'T SHIP!!
//m_rtt[i].recreate(); //m_rtt[i].recreate();
m_rtt[i].bindTexture();
glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]);
glTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, m_rtt[i].updateRgba8(
static_cast<int>(m_dirty_box[i].x), static_cast<int>(m_dirty_box[i].y), static_cast<int>(m_dirty_box[i].x), static_cast<int>(m_dirty_box[i].y),
static_cast<int>(box_sz.x), static_cast<int>(box_sz.y), static_cast<int>(box_sz.x), static_cast<int>(box_sz.y),
pp::renderer::gl::rgba_pixel_format(),
pp::renderer::gl::unsigned_byte_component_type(),
snap.image[i].get()); snap.image[i].get());
m_rtt[i].unbindTexture();
LOG("restore face %d - %d bytes (%dx%d)", i, LOG("restore face %d - %d bytes (%dx%d)", i,
(int)box_sz.x * (int)box_sz.y * 4, (int)box_sz.x, (int)box_sz.y); (int)box_sz.x * (int)box_sz.y * 4, (int)box_sz.x, (int)box_sz.y);
} }

View File

@@ -832,6 +832,8 @@ pp::foundation::Status update_opengl_texture_2d(
} }
if (update.texture_id == 0U if (update.texture_id == 0U
|| update.x < 0
|| update.y < 0
|| update.width <= 0 || update.width <= 0
|| update.height <= 0 || update.height <= 0
|| update.pixel_format == 0U || update.pixel_format == 0U
@@ -844,8 +846,8 @@ pp::foundation::Status update_opengl_texture_2d(
dispatch.tex_sub_image_2d( dispatch.tex_sub_image_2d(
texture_2d_target(), texture_2d_target(),
0, 0,
0, update.x,
0, update.y,
update.width, update.width,
update.height, update.height,
update.pixel_format, update.pixel_format,

View File

@@ -142,6 +142,8 @@ struct OpenGlTextureCubeAllocation {
struct OpenGlTexture2DUpdate { struct OpenGlTexture2DUpdate {
std::uint32_t texture_id = 0; std::uint32_t texture_id = 0;
std::int32_t x = 0;
std::int32_t y = 0;
std::int32_t width = 0; std::int32_t width = 0;
std::int32_t height = 0; std::int32_t height = 0;
std::uint32_t pixel_format = 0; std::uint32_t pixel_format = 0;

View File

@@ -92,6 +92,29 @@ void set_opengl_texture_2d_image(
data); data);
} }
void set_opengl_texture_2d_sub_image(
std::uint32_t target,
std::int32_t level,
std::int32_t x,
std::int32_t y,
std::int32_t width,
std::int32_t height,
std::uint32_t pixel_format,
std::uint32_t component_type,
const void* data) noexcept
{
glTexSubImage2D(
static_cast<GLenum>(target),
static_cast<GLint>(level),
static_cast<GLint>(x),
static_cast<GLint>(y),
static_cast<GLsizei>(width),
static_cast<GLsizei>(height),
static_cast<GLenum>(pixel_format),
static_cast<GLenum>(component_type),
data);
}
void set_opengl_texture_parameter_f(std::uint32_t target, std::uint32_t parameter, float value) noexcept void set_opengl_texture_parameter_f(std::uint32_t target, std::uint32_t parameter, float value) noexcept
{ {
glTexParameterf(static_cast<GLenum>(target), static_cast<GLenum>(parameter), static_cast<GLfloat>(value)); glTexParameterf(static_cast<GLenum>(target), static_cast<GLenum>(parameter), static_cast<GLfloat>(value));
@@ -760,6 +783,34 @@ bool RTT::readPixelsRgba8(int x, int y, int width, int height, void* buffer) con
return ret; return ret;
} }
bool RTT::updateRgba8(int x, int y, int width, int height, const void* data) noexcept
{
if (!valid() || data == nullptr)
return false;
const auto status = pp::renderer::gl::update_opengl_texture_2d(
pp::renderer::gl::OpenGlTexture2DUpdate {
.texture_id = texID,
.x = x,
.y = y,
.width = width,
.height = height,
.pixel_format = pp::renderer::gl::rgba_pixel_format(),
.component_type = pp::renderer::gl::unsigned_byte_component_type(),
.data = data,
},
pp::renderer::gl::OpenGlTexture2DUpdateDispatch {
.bind_texture = bind_opengl_texture,
.tex_sub_image_2d = set_opengl_texture_2d_sub_image,
});
if (!status.ok()) {
LOG("RTT::updateRgba8() failed because: %s", status.message);
return false;
}
unbindTexture();
return true;
}
uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept
{ {
if (!valid()) if (!valid())

View File

@@ -68,6 +68,7 @@ public:
void clear(glm::vec4 color = glm::vec4(0)); void clear(glm::vec4 color = glm::vec4(0));
void clear_mask(glm::bool4 mask, glm::vec4 color = glm::vec4(0)); void clear_mask(glm::bool4 mask, glm::vec4 color = glm::vec4(0));
glm::ivec4 calc_bounds() const noexcept; glm::ivec4 calc_bounds() const noexcept;
bool updateRgba8(int x, int y, int width, int height, const void* data) noexcept;
bool readPixelsRgba8(int x, int y, int width, int height, void* buffer) const noexcept; bool readPixelsRgba8(int x, int y, int width, int height, void* buffer) const noexcept;
uint8_t* readTextureData(uint8_t* buffer = nullptr) const noexcept; uint8_t* readTextureData(uint8_t* buffer = nullptr) const noexcept;
float* readTextureDataFloat(float* buffer = nullptr) const noexcept; float* readTextureDataFloat(float* buffer = nullptr) const noexcept;

View File

@@ -4342,6 +4342,8 @@ void updates_texture_2d_through_dispatch(pp::tests::Harness& h)
const auto status = pp::renderer::gl::update_opengl_texture_2d( const auto status = pp::renderer::gl::update_opengl_texture_2d(
pp::renderer::gl::OpenGlTexture2DUpdate { pp::renderer::gl::OpenGlTexture2DUpdate {
.texture_id = 31U, .texture_id = 31U,
.x = 3,
.y = 4,
.width = 2, .width = 2,
.height = 2, .height = 2,
.pixel_format = 0x1908U, .pixel_format = 0x1908U,
@@ -4358,11 +4360,48 @@ void updates_texture_2d_through_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, recorded_binding_calls[0].second == 31U); PP_EXPECT(h, recorded_binding_calls[0].second == 31U);
PP_EXPECT(h, recorded_texture_image_calls.size() == 1U); PP_EXPECT(h, recorded_texture_image_calls.size() == 1U);
PP_EXPECT(h, recorded_texture_image_calls[0].sub_image); PP_EXPECT(h, recorded_texture_image_calls[0].sub_image);
PP_EXPECT(h, recorded_texture_image_calls[0].x == 3);
PP_EXPECT(h, recorded_texture_image_calls[0].y == 4);
PP_EXPECT(h, recorded_texture_image_calls[0].width == 2); PP_EXPECT(h, recorded_texture_image_calls[0].width == 2);
PP_EXPECT(h, recorded_texture_image_calls[0].height == 2); PP_EXPECT(h, recorded_texture_image_calls[0].height == 2);
PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data()); PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data());
} }
void rejects_invalid_texture_2d_updates(pp::tests::Harness& h)
{
const std::array<std::uint8_t, 4> pixels { 9U, 8U, 7U, 6U };
const auto negative_offset = pp::renderer::gl::update_opengl_texture_2d(
pp::renderer::gl::OpenGlTexture2DUpdate {
.texture_id = 31U,
.x = -1,
.width = 2,
.height = 2,
.pixel_format = 0x1908U,
.component_type = 0x1401U,
.data = pixels.data(),
},
pp::renderer::gl::OpenGlTexture2DUpdateDispatch {
.bind_texture = record_bind_texture,
.tex_sub_image_2d = record_tex_sub_image_2d,
});
const auto missing_dispatch = pp::renderer::gl::update_opengl_texture_2d(
pp::renderer::gl::OpenGlTexture2DUpdate {
.texture_id = 31U,
.width = 2,
.height = 2,
.pixel_format = 0x1908U,
.component_type = 0x1401U,
.data = pixels.data(),
},
pp::renderer::gl::OpenGlTexture2DUpdateDispatch {});
PP_EXPECT(h, !negative_offset.ok());
PP_EXPECT(h, negative_offset.code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !missing_dispatch.ok());
PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
}
void copies_framebuffer_to_texture_2d_through_dispatch(pp::tests::Harness& h) void copies_framebuffer_to_texture_2d_through_dispatch(pp::tests::Harness& h)
{ {
recorded_framebuffer_texture_copy_calls.clear(); recorded_framebuffer_texture_copy_calls.clear();
@@ -5464,6 +5503,7 @@ int main()
harness.run("creates_reads_maps_and_deletes_pixel_buffers_through_dispatch", creates_reads_maps_and_deletes_pixel_buffers_through_dispatch); harness.run("creates_reads_maps_and_deletes_pixel_buffers_through_dispatch", creates_reads_maps_and_deletes_pixel_buffers_through_dispatch);
harness.run("rejects_invalid_pixel_buffer_dispatch", rejects_invalid_pixel_buffer_dispatch); harness.run("rejects_invalid_pixel_buffer_dispatch", rejects_invalid_pixel_buffer_dispatch);
harness.run("updates_texture_2d_through_dispatch", updates_texture_2d_through_dispatch); harness.run("updates_texture_2d_through_dispatch", updates_texture_2d_through_dispatch);
harness.run("rejects_invalid_texture_2d_updates", rejects_invalid_texture_2d_updates);
harness.run("copies_framebuffer_to_texture_2d_through_dispatch", copies_framebuffer_to_texture_2d_through_dispatch); harness.run("copies_framebuffer_to_texture_2d_through_dispatch", copies_framebuffer_to_texture_2d_through_dispatch);
harness.run("copies_framebuffer_to_requested_texture_target", copies_framebuffer_to_requested_texture_target); harness.run("copies_framebuffer_to_requested_texture_target", copies_framebuffer_to_requested_texture_target);
harness.run("skips_zero_sized_framebuffer_to_texture_copies", skips_zero_sized_framebuffer_to_texture_copies); harness.run("skips_zero_sized_framebuffer_to_texture_copies", skips_zero_sized_framebuffer_to_texture_copies);