Route framebuffer texture copies through GL backend

This commit is contained in:
2026-06-04 21:12:46 +02:00
parent 15c58bfb21
commit 6440bde002
13 changed files with 285 additions and 46 deletions

View File

@@ -292,7 +292,11 @@ 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. It also canvas pick/history/snapshot and transform history paths. 2D
framebuffer-to-texture copies used by retained canvas, transform,
layer-conversion, panorama UI, and brush-preview paths now execute through a
tested `pp_renderer_gl` dispatch via `copy_framebuffer_to_texture_2d`; the
retained cube-map framebuffer copy remains tracked by `DEBT-0036`. It also
validates renderer API primitive-topology to OpenGL draw-mode mapping, Shape validates renderer API primitive-topology to OpenGL draw-mode mapping, Shape
index-type, fill/stroke primitive-mode, buffer target, static upload usage, index-type, fill/stroke primitive-mode, buffer target, static upload usage,
and vertex attribute component/normalization mapping used by and vertex attribute component/normalization mapping used by
@@ -414,12 +418,12 @@ Known local toolchain state:
Early canvas draw helpers also consume backend-owned pick readback Early canvas draw helpers also consume backend-owned pick readback
format/type and RTT-backed region-readback execution, stroke mixer format/type and RTT-backed region-readback execution, stroke mixer
depth/scissor/blend state, saved viewport and clear-state queries, active depth/scissor/blend state, saved viewport and clear-state queries, active
texture units, fallback 2D texture unbind targets, and stroke background copy texture units, fallback 2D texture unbind targets, and stroke background
targets. framebuffer-copy dispatch.
Canvas stroke commit also consumes backend-owned saved viewport/clear/blend Canvas stroke commit also consumes backend-owned saved viewport/clear/blend
state, history readback format/type and RTT-backed region-readback execution, state, history readback format/type and RTT-backed region-readback execution,
active texture units, fallback 2D texture unbind targets, and layer active texture units, fallback 2D texture unbind targets, and layer
compositing copy targets. compositing framebuffer-copy dispatch.
Canvas layer merge rendering and explicit layer-merge compositing also consume Canvas layer merge rendering and explicit layer-merge compositing also consume
backend-owned depth/blend state, active texture units, fallback 2D texture backend-owned depth/blend state, active texture units, fallback 2D texture
unbind targets, and merge framebuffer copy targets. unbind targets, and merge framebuffer copy targets.
@@ -615,7 +619,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 framebuffer paths, tested framebuffer-to-texture 2D copy dispatch consumed by retained
canvas/UI paint paths, tested framebuffer
bind/restore dispatch consumed by retained `RTT` render-target pass entry bind/restore dispatch consumed by retained `RTT` render-target pass entry
and exit paths, tested depth renderbuffer allocation/delete and framebuffer and exit paths, tested depth renderbuffer allocation/delete and framebuffer
depth attach/detach dispatch consumed by canvas object-drawing helpers, depth attach/detach dispatch consumed by canvas object-drawing helpers,

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. 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, but actual live stroke rasterization, dual-brush compositing, pattern feedback math, thumbnail layer compositing, brush-preview compositing, the retained cube-map framebuffer copy, 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.*`, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, PBO readback through `App::rec_loop`, 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`; `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, 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.*`, but the bridge still owns legacy recording thread startup/shutdown, platform recorded-file cleanup, progress UI, PBO readback through `App::rec_loop`, 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`; `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, 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

@@ -1005,7 +1005,11 @@ framebuffer targets, binding queries, attachment points, and completion status
used by `RTT::create` and framebuffer bind/restore paths, also live in used by `RTT::create` and framebuffer bind/restore paths, also live in
`pp_renderer_gl`. Depth renderbuffer allocation/storage/delete and framebuffer `pp_renderer_gl`. Depth renderbuffer allocation/storage/delete and framebuffer
depth attach/detach sequences used by canvas object-drawing helpers now execute depth attach/detach sequences used by canvas object-drawing helpers now execute
through tested `pp_renderer_gl` dispatch contracts. RTT clear color/depth masks, renderer API render-pass through tested `pp_renderer_gl` dispatch contracts. 2D framebuffer-to-texture
copies used by canvas, transform, layer-conversion, panorama UI, and brush
preview paths now route through a tested `pp_renderer_gl` copy dispatch via the
retained `copy_framebuffer_to_texture_2d` utility bridge; the remaining cube-map
copy is tracked under `DEBT-0036`. RTT clear color/depth masks, renderer API render-pass
color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens also color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens also
live in `pp_renderer_gl`. `RTT` no longer spells GL enum names directly. live in `pp_renderer_gl`. `RTT` no longer spells GL enum names directly.
Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type Renderer API primitive-topology to OpenGL draw-mode mapping, mesh index-type
@@ -1979,13 +1983,13 @@ Results:
and fallback 2D texture unbinds through the renderer GL backend mapping; and fallback 2D texture unbinds through the renderer GL backend mapping;
platform VR SDK bridges remain isolated for later platform-shell extraction. platform VR SDK bridges remain isolated for later platform-shell extraction.
- Canvas mode overlay, mask, and transform paths now route generic OpenGL - Canvas mode overlay, mask, and transform paths now route generic OpenGL
blend/depth state, active texture units, 2D copy targets, RGBA8 readback blend/depth state, active texture units, 2D framebuffer-to-texture copy
formats, and RTT-backed transform history region readbacks through the dispatch, RGBA8 readback formats, and RTT-backed transform history region
renderer GL backend mapping. readbacks through the renderer GL backend mapping.
- `NodeCanvas` panorama UI rendering now routes sampler defaults, saved - `NodeCanvas` panorama UI rendering now routes sampler defaults, saved
viewport/clear/blend/depth/scissor state, color clears, active texture units, viewport/clear/blend/depth/scissor state, color clears, active texture units,
fallback 2D texture unbinds, copy targets, and RGBA8 render-target formats fallback 2D texture unbinds, 2D framebuffer-to-texture copy dispatch, and
through the renderer GL backend mapping. RGBA8 render-target formats through the renderer GL backend mapping.
- Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F - Canvas resource setup now routes stroke-buffer RGBA8/RGBA16F/RGBA32F
formats, flood-fill texture upload format/type, brush/stencil/mix sampler formats, flood-fill texture upload format/type, brush/stencil/mix sampler
filters and wraps, and cube-strip import channel formats through the renderer filters and wraps, and cube-strip import channel formats through the renderer
@@ -1999,7 +2003,9 @@ Results:
readbacks, active texture units, fallback 2D texture unbinds, and layer readbacks, active texture units, fallback 2D texture unbinds, and layer
compositing copy targets through the renderer GL backend mapping; the compositing copy targets through the renderer GL backend mapping; the
RTT-backed dirty-region readbacks now execute through the retained `RTT` RTT-backed dirty-region readbacks now execute through the retained `RTT`
region-readback helper rather than direct `glReadPixels`. region-readback helper rather than direct `glReadPixels`, and 2D framebuffer
copies now execute through the retained utility bridge instead of direct
`glCopyTexSubImage2D`.
- Canvas layer merge rendering and explicit layer-merge compositing now route - Canvas layer merge rendering and explicit layer-merge compositing now route
depth/blend state, active texture units, fallback 2D texture unbinds, and depth/blend state, active texture units, fallback 2D texture unbinds, and
merge framebuffer copy targets through the renderer GL backend mapping. merge framebuffer copy targets through the renderer GL backend mapping.

View File

@@ -6,6 +6,7 @@
#include "node_progress_bar.h" #include "node_progress_bar.h"
#include "paint_renderer/compositor.h" #include "paint_renderer/compositor.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#include "util.h"
#include <thread> #include <thread>
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@@ -564,8 +565,7 @@ glm::vec4 Canvas::stroke_draw_samples(
glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz) + pad * 2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(m_width, m_height) - tex_pos)); glm::ivec2 tex_sz = glm::clamp(glm::ceil(bb_sz) + pad * 2.f, { 0, 0 }, (glm::vec2)(glm::ivec2(m_width, m_height) - tex_pos));
if (copy_stroke_destination) if (copy_stroke_destination)
{ {
glCopyTexSubImage2D(texture_2d_target(), 0, tex_pos.x, tex_pos.y, copy_framebuffer_to_texture_2d(tex_pos.x, tex_pos.y, tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
} }
if (P.size() == 4) if (P.size() == 4)
@@ -835,7 +835,7 @@ void Canvas::stroke_draw()
glm::vec2 sz = glm::min(m_size, zw(b) + pad) - o; glm::vec2 sz = glm::min(m_size, zw(b) + pad) - o;
m_tex[i].bind(); m_tex[i].bind();
if (sz.x > 0 && sz.y > 0) if (sz.x > 0 && sz.y > 0)
glCopyTexSubImage2D(texture_2d_target(), 0, o.x, o.y, o.x, o.y, sz.x, sz.y); copy_framebuffer_to_texture_2d(o.x, o.y, o.x, o.y, sz.x, sz.y);
} }
m_brush_shape.draw_fill(); m_brush_shape.draw_fill();
m_tmp[i].unbindFramebuffer(); m_tmp[i].unbindFramebuffer();
@@ -1050,7 +1050,7 @@ void Canvas::stroke_commit()
// copy to tmp2 for layer blending // copy to tmp2 for layer blending
set_active_texture_unit(0); set_active_texture_unit(0);
m_tex2[i].bind(); m_tex2[i].bind();
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height); copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
m_tex2[i].unbind(); m_tex2[i].unbind();
m_tmp[i].bindTexture(); m_tmp[i].bindTexture();
@@ -1161,7 +1161,7 @@ void Canvas::stroke_commit()
ShaderManager::u_int(kShaderUniform::TexBG, 0); ShaderManager::u_int(kShaderUniform::TexBG, 0);
set_active_texture_unit(0); set_active_texture_unit(0);
m_tex2[i].bind(); m_tex2[i].bind();
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height); copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
m_plane.draw_fill(); m_plane.draw_fill();
m_layers[m_current_layer_idx]->rtt(i).unbindFramebuffer(); m_layers[m_current_layer_idx]->rtt(i).unbindFramebuffer();
@@ -1390,7 +1390,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SI
{ {
set_active_texture_unit(2); set_active_texture_unit(2);
m_merge_tex.bind(); m_merge_tex.bind();
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height); copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
} }
m_plane.draw_fill(); m_plane.draw_fill();
@@ -1407,7 +1407,7 @@ void Canvas::draw_merge(bool draw_checkerboard, std::array<bool, 6> faces /*= SI
set_active_texture_unit(2); set_active_texture_unit(2);
m_merge_tex.bind(); m_merge_tex.bind();
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height); copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
// draw the grid behind the layers using a temporary copy // draw the grid behind the layers using a temporary copy
if (use_blend) if (use_blend)
@@ -1593,7 +1593,7 @@ void Canvas::layer_merge(int source_idx, int dest_idx) // m_layer index
// copy to tmp2 for layer blending // copy to tmp2 for layer blending
set_active_texture_unit(0); set_active_texture_unit(0);
m_tex2[i].bind(); m_tex2[i].bind();
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, m_width, m_height); copy_framebuffer_to_texture_2d(0, 0, 0, 0, m_width, m_height);
m_tex2[i].unbind(); m_tex2[i].unbind();
m_sampler.bind(0); m_sampler.bind(0);
@@ -2920,7 +2920,7 @@ Image Canvas::thumbnail_generate(int w, int h)
if (copy_layer_destination) if (copy_layer_destination)
{ {
set_active_texture_unit(2); set_active_texture_unit(2);
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, w, h); copy_framebuffer_to_texture_2d(0, 0, 0, 0, w, h);
} }
ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[layer_index]->m_blend_mode); ShaderManager::u_int(kShaderUniform::BlendMode, m_layers[layer_index]->m_blend_mode);
ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index]->m_opacity); ShaderManager::u_float(kShaderUniform::Alpha, m_layers[layer_index]->m_opacity);
@@ -2939,7 +2939,7 @@ Image Canvas::thumbnail_generate(int w, int h)
set_active_texture_unit(0); set_active_texture_unit(0);
blendtex.bind(); blendtex.bind();
// copy the content of the fb before drawing the grid // copy the content of the fb before drawing the grid
glCopyTexSubImage2D(texture_2d_target(), 0, 0, 0, 0, 0, w, h); copy_framebuffer_to_texture_2d(0, 0, 0, 0, w, h);
// draw the grid // draw the grid
ShaderManager::use(kShader::Checkerboard); ShaderManager::use(kShader::Checkerboard);

View File

@@ -3,6 +3,7 @@
#include "app.h" #include "app.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#include "rtt.h" #include "rtt.h"
#include "util.h"
uint32_t Layer::s_count = 0; uint32_t Layer::s_count = 0;
@@ -89,7 +90,7 @@ Texture2D Layer::gen_equirect(glm::ivec2 size /*= { 0, 0 }*/)
Canvas::I->m_plane.draw_fill(); Canvas::I->m_plane.draw_fill();
ret.bind(); ret.bind();
glCopyTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, 0, 0, 0, 0, latlong.getWidth(), latlong.getHeight()); copy_framebuffer_to_texture_2d(0, 0, 0, 0, latlong.getWidth(), latlong.getHeight());
latlong.unbindFramebuffer(); latlong.unbindFramebuffer();

View File

@@ -1252,9 +1252,13 @@ void CanvasModeTransform::enter(kCanvasMode prev)
Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).bindFramebuffer(); Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).bindFramebuffer();
m_tex[plane].create(bb_sz.x, bb_sz.y); m_tex[plane].create(bb_sz.x, bb_sz.y);
m_tex[plane].bind(); m_tex[plane].bind();
glCopyTexSubImage2D( copy_framebuffer_to_texture_2d(
pp::renderer::gl::texture_2d_target(), 0,
0, 0, 0, bb_min.x, bb_min.y, bb_sz.x, bb_sz.y); 0,
static_cast<int>(bb_min.x),
static_cast<int>(bb_min.y),
static_cast<int>(bb_sz.x),
static_cast<int>(bb_sz.y));
m_tex[plane].unbind(); m_tex[plane].unbind();
Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).unbindFramebuffer(); Canvas::I->m_layers[Canvas::I->m_current_layer_idx]->rtt(plane).unbindFramebuffer();
}); });
@@ -1433,9 +1437,13 @@ void CanvasModeTransform::leave(kCanvasMode next)
// copy fb content to texture for blending // copy fb content to texture for blending
set_active_texture_unit(0); set_active_texture_unit(0);
Canvas::I->m_tex2[i].bind(); Canvas::I->m_tex2[i].bind();
glCopyTexSubImage2D( copy_framebuffer_to_texture_2d(
pp::renderer::gl::texture_2d_target(), static_cast<int>(bb_min.x),
0, bb_min.x, bb_min.y, bb_min.x, bb_min.y, bb_sz.x, bb_sz.y); static_cast<int>(bb_min.y),
static_cast<int>(bb_min.x),
static_cast<int>(bb_min.y),
static_cast<int>(bb_sz.x),
static_cast<int>(bb_sz.y));
// slot for m_tex // slot for m_tex
set_active_texture_unit(1); set_active_texture_unit(1);
for (int j = 0; j < 6; j++) for (int j = 0; j < 6; j++)

View File

@@ -16,6 +16,7 @@
#include "paint_renderer/compositor.h" #include "paint_renderer/compositor.h"
#include "settings.h" #include "settings.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#include "util.h"
namespace { namespace {
@@ -523,8 +524,13 @@ void NodeCanvas::draw()
{ {
set_active_texture_unit(2); set_active_texture_unit(2);
m_blender_bg.bind(); m_blender_bg.bind();
glCopyTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, 0, 0, 0, 0, copy_framebuffer_to_texture_2d(
m_blender_bg.size().x, m_blender_bg.size().y); 0,
0,
0,
0,
m_blender_bg.size().x,
m_blender_bg.size().y);
} }
m_face_plane.draw_fill(); m_face_plane.draw_fill();

View File

@@ -8,6 +8,7 @@
#include "app.h" #include "app.h"
#include "paint_renderer/compositor.h" #include "paint_renderer/compositor.h"
#include "renderer_gl/opengl_capabilities.h" #include "renderer_gl/opengl_capabilities.h"
#include "util.h"
#include <algorithm> #include <algorithm>
#include <cstdint> #include <cstdint>
@@ -208,8 +209,7 @@ glm::vec4 NodeStrokePreview::stroke_draw_samples(
if (copy_stroke_destination) if (copy_stroke_destination)
{ {
// this is also used by the mixer // this is also used by the mixer
glCopyTexSubImage2D(pp::renderer::gl::texture_2d_target(), 0, tex_pos.x, tex_pos.y, copy_framebuffer_to_texture_2d(tex_pos.x, tex_pos.y, tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
tex_pos.x, tex_pos.y, tex_sz.x, tex_sz.y);
} }
if (P.size() == 4) if (P.size() == 4)
@@ -432,9 +432,7 @@ void NodeStrokePreview::draw_stroke_immediate()
// copy raw stroke to tex // copy raw stroke to tex
glActiveTexture(pp::renderer::gl::active_texture_unit(1U)); glActiveTexture(pp::renderer::gl::active_texture_unit(1U));
m_tex_dual.bind(); m_tex_dual.bind();
glCopyTexSubImage2D( copy_framebuffer_to_texture_2d(
pp::renderer::gl::texture_2d_target(),
0,
0, 0,
0, 0,
0, 0,
@@ -453,9 +451,7 @@ void NodeStrokePreview::draw_stroke_immediate()
m_plane.draw_fill(); m_plane.draw_fill();
//m_rtt.clear({ .3f, .3f, .3f, 1.f }); //m_rtt.clear({ .3f, .3f, .3f, 1.f });
m_tex_background.bind(); m_tex_background.bind();
glCopyTexSubImage2D( copy_framebuffer_to_texture_2d(
pp::renderer::gl::texture_2d_target(),
0,
0, 0,
0, 0,
0, 0,
@@ -508,9 +504,7 @@ void NodeStrokePreview::draw_stroke_immediate()
// copy raw stroke to tex // copy raw stroke to tex
glActiveTexture(pp::renderer::gl::active_texture_unit(1U)); glActiveTexture(pp::renderer::gl::active_texture_unit(1U));
m_tex.bind(); m_tex.bind();
glCopyTexSubImage2D( copy_framebuffer_to_texture_2d(
pp::renderer::gl::texture_2d_target(),
0,
0, 0,
0, 0,
0, 0,
@@ -564,9 +558,7 @@ void NodeStrokePreview::draw_stroke_immediate()
// copy the result to the actual preview // copy the result to the actual preview
m_tex_preview.bind(); m_tex_preview.bind();
glCopyTexSubImage2D( copy_framebuffer_to_texture_2d(
pp::renderer::gl::texture_2d_target(),
0,
0, 0,
0, 0,
0, 0,

View File

@@ -748,6 +748,36 @@ pp::foundation::Status update_opengl_texture_2d(
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }
pp::foundation::Status copy_opengl_framebuffer_to_texture_2d(
OpenGlTexture2DFramebufferCopy copy,
OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept
{
if (dispatch.copy_tex_sub_image_2d == nullptr) {
return pp::foundation::Status::invalid_argument(
"OpenGL framebuffer-to-texture copy dispatch callback must not be null");
}
if (copy.width < 0 || copy.height < 0) {
return pp::foundation::Status::invalid_argument(
"OpenGL framebuffer-to-texture copy dimensions are invalid");
}
if (copy.width == 0 || copy.height == 0) {
return pp::foundation::Status::success();
}
dispatch.copy_tex_sub_image_2d(
texture_2d_target(),
copy.level,
copy.destination_x,
copy.destination_y,
copy.source_x,
copy.source_y,
copy.width,
copy.height);
return pp::foundation::Status::success();
}
pp::foundation::Status generate_opengl_texture_2d_mipmaps( pp::foundation::Status generate_opengl_texture_2d_mipmaps(
std::uint32_t texture_id, std::uint32_t texture_id,
OpenGlTexture2DMipmapDispatch dispatch) noexcept OpenGlTexture2DMipmapDispatch dispatch) noexcept

View File

@@ -149,6 +149,16 @@ struct OpenGlTexture2DUpdate {
const void* data = nullptr; const void* data = nullptr;
}; };
struct OpenGlTexture2DFramebufferCopy {
std::int32_t level = 0;
std::int32_t destination_x = 0;
std::int32_t destination_y = 0;
std::int32_t source_x = 0;
std::int32_t source_y = 0;
std::int32_t width = 0;
std::int32_t height = 0;
};
struct OpenGlTexture2DReadback { struct OpenGlTexture2DReadback {
std::uint32_t texture_id = 0; std::uint32_t texture_id = 0;
std::int32_t width = 0; std::int32_t width = 0;
@@ -426,6 +436,15 @@ using OpenGlTexSubImage2DFn = void (*)(
std::uint32_t pixel_format, std::uint32_t pixel_format,
std::uint32_t component_type, std::uint32_t component_type,
const void* data) noexcept; const void* data) noexcept;
using OpenGlCopyTexSubImage2DFn = void (*)(
std::uint32_t target,
std::int32_t level,
std::int32_t destination_x,
std::int32_t destination_y,
std::int32_t source_x,
std::int32_t source_y,
std::int32_t width,
std::int32_t height) noexcept;
using OpenGlGenerateMipmapFn = void (*)(std::uint32_t target) noexcept; using OpenGlGenerateMipmapFn = void (*)(std::uint32_t target) noexcept;
using OpenGlFramebufferTexture2DFn = void (*)( using OpenGlFramebufferTexture2DFn = void (*)(
std::uint32_t target, std::uint32_t target,
@@ -582,6 +601,10 @@ struct OpenGlTexture2DUpdateDispatch {
OpenGlTexSubImage2DFn tex_sub_image_2d = nullptr; OpenGlTexSubImage2DFn tex_sub_image_2d = nullptr;
}; };
struct OpenGlTexture2DFramebufferCopyDispatch {
OpenGlCopyTexSubImage2DFn copy_tex_sub_image_2d = nullptr;
};
struct OpenGlTexture2DMipmapDispatch { struct OpenGlTexture2DMipmapDispatch {
OpenGlBindTextureFn bind_texture = nullptr; OpenGlBindTextureFn bind_texture = nullptr;
OpenGlGenerateMipmapFn generate_mipmap = nullptr; OpenGlGenerateMipmapFn generate_mipmap = nullptr;
@@ -820,6 +843,9 @@ struct OpenGlMeshDeleteDispatch {
[[nodiscard]] pp::foundation::Status update_opengl_texture_2d( [[nodiscard]] pp::foundation::Status update_opengl_texture_2d(
OpenGlTexture2DUpdate update, OpenGlTexture2DUpdate update,
OpenGlTexture2DUpdateDispatch dispatch) noexcept; OpenGlTexture2DUpdateDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Status copy_opengl_framebuffer_to_texture_2d(
OpenGlTexture2DFramebufferCopy copy,
OpenGlTexture2DFramebufferCopyDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Status generate_opengl_texture_2d_mipmaps( [[nodiscard]] pp::foundation::Status generate_opengl_texture_2d_mipmaps(
std::uint32_t texture_id, std::uint32_t texture_id,
OpenGlTexture2DMipmapDispatch dispatch) noexcept; OpenGlTexture2DMipmapDispatch dispatch) noexcept;

View File

@@ -67,6 +67,27 @@ void bind_opengl_sampler(std::uint32_t unit, std::uint32_t sampler) noexcept
glBindSampler(static_cast<GLuint>(unit), static_cast<GLuint>(sampler)); glBindSampler(static_cast<GLuint>(unit), static_cast<GLuint>(sampler));
} }
void copy_opengl_tex_sub_image_2d(
std::uint32_t target,
std::int32_t level,
std::int32_t destination_x,
std::int32_t destination_y,
std::int32_t source_x,
std::int32_t source_y,
std::int32_t width,
std::int32_t height) noexcept
{
glCopyTexSubImage2D(
static_cast<GLenum>(target),
static_cast<GLint>(level),
static_cast<GLint>(destination_x),
static_cast<GLint>(destination_y),
static_cast<GLint>(source_x),
static_cast<GLint>(source_y),
static_cast<GLsizei>(width),
static_cast<GLsizei>(height));
}
} }
template<> template<>
@@ -719,6 +740,35 @@ void check_OpenGLError(const char* stmt, const char* fname, int line)
} }
} }
bool copy_framebuffer_to_texture_2d(
int destination_x,
int destination_y,
int source_x,
int source_y,
int width,
int height,
int level) noexcept
{
const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
.level = level,
.destination_x = destination_x,
.destination_y = destination_y,
.source_x = source_x,
.source_y = source_y,
.width = width,
.height = height,
},
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
.copy_tex_sub_image_2d = copy_opengl_tex_sub_image_2d,
});
if (!status.ok()) {
LOG("OpenGL framebuffer-to-texture copy failed: %s", status.message);
return false;
}
return true;
}
size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp) size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp)
{ {
auto buffer = reinterpret_cast<std::string*>(userp); auto buffer = reinterpret_cast<std::string*>(userp);

View File

@@ -201,6 +201,14 @@ std::string str_replace(const std::string& string, const std::string& search, co
size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp); size_t curl_data_handler(void *contents, size_t size, size_t nmemb, void *userp);
size_t curl_data_write(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t curl_data_write(void *ptr, size_t size, size_t nmemb, FILE *stream);
void check_OpenGLError(const char* stmt, const char* fname, int line); void check_OpenGLError(const char* stmt, const char* fname, int line);
bool copy_framebuffer_to_texture_2d(
int destination_x,
int destination_y,
int source_x,
int source_y,
int width,
int height,
int level = 0) noexcept;
inline glm::vec2 xy(const glm::vec4& v) { return glm::vec2(v.x, v.y); } inline glm::vec2 xy(const glm::vec4& v) { return glm::vec2(v.x, v.y); }
inline glm::vec3 xyz(const glm::vec4& v) { return glm::vec3(v.x, v.y, v.z); } inline glm::vec3 xyz(const glm::vec4& v) { return glm::vec3(v.x, v.y, v.z); }
inline glm::vec2 zw(const glm::vec4& v) { return glm::vec2(v.z, v.w); } inline glm::vec2 zw(const glm::vec4& v) { return glm::vec2(v.z, v.w); }

View File

@@ -55,6 +55,17 @@ struct RecordedOpenGlTextureImageCall {
bool sub_image = false; bool sub_image = false;
}; };
struct RecordedOpenGlFramebufferTextureCopyCall {
std::uint32_t target = 0;
std::int32_t level = 0;
std::int32_t destination_x = 0;
std::int32_t destination_y = 0;
std::int32_t source_x = 0;
std::int32_t source_y = 0;
std::int32_t width = 0;
std::int32_t height = 0;
};
struct RecordedOpenGlFramebufferAttachmentCall { struct RecordedOpenGlFramebufferAttachmentCall {
std::uint32_t target = 0; std::uint32_t target = 0;
std::uint32_t attachment = 0; std::uint32_t attachment = 0;
@@ -186,6 +197,7 @@ std::vector<RecordedOpenGlBindingCall> recorded_binding_calls;
std::vector<std::uint32_t> recorded_generated_texture_counts; std::vector<std::uint32_t> recorded_generated_texture_counts;
std::vector<std::uint32_t> recorded_deleted_textures; std::vector<std::uint32_t> recorded_deleted_textures;
std::vector<RecordedOpenGlTextureImageCall> recorded_texture_image_calls; std::vector<RecordedOpenGlTextureImageCall> recorded_texture_image_calls;
std::vector<RecordedOpenGlFramebufferTextureCopyCall> recorded_framebuffer_texture_copy_calls;
std::vector<std::uint32_t> recorded_mipmap_targets; std::vector<std::uint32_t> recorded_mipmap_targets;
std::vector<std::uint32_t> recorded_generated_framebuffer_counts; std::vector<std::uint32_t> recorded_generated_framebuffer_counts;
std::vector<std::uint32_t> recorded_deleted_framebuffers; std::vector<std::uint32_t> recorded_deleted_framebuffers;
@@ -524,6 +536,28 @@ void record_tex_sub_image_2d(
}); });
} }
void record_copy_tex_sub_image_2d(
std::uint32_t target,
std::int32_t level,
std::int32_t destination_x,
std::int32_t destination_y,
std::int32_t source_x,
std::int32_t source_y,
std::int32_t width,
std::int32_t height) noexcept
{
recorded_framebuffer_texture_copy_calls.push_back(RecordedOpenGlFramebufferTextureCopyCall {
.target = target,
.level = level,
.destination_x = destination_x,
.destination_y = destination_y,
.source_x = source_x,
.source_y = source_y,
.width = width,
.height = height,
});
}
void record_generate_mipmap(std::uint32_t target) noexcept void record_generate_mipmap(std::uint32_t target) noexcept
{ {
recorded_mipmap_targets.push_back(target); recorded_mipmap_targets.push_back(target);
@@ -3775,6 +3809,76 @@ void updates_texture_2d_through_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data()); PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data());
} }
void copies_framebuffer_to_texture_2d_through_dispatch(pp::tests::Harness& h)
{
recorded_framebuffer_texture_copy_calls.clear();
const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
.level = 1,
.destination_x = 2,
.destination_y = 3,
.source_x = 4,
.source_y = 5,
.width = 6,
.height = 7,
},
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
.copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
});
PP_EXPECT(h, status.ok());
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.size() == 1U);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].target == 0x0DE1U);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].level == 1);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].destination_x == 2);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].destination_y == 3);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].source_x == 4);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].source_y == 5);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].width == 6);
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls[0].height == 7);
}
void skips_zero_sized_framebuffer_to_texture_copies(pp::tests::Harness& h)
{
recorded_framebuffer_texture_copy_calls.clear();
const auto status = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
.width = 0,
.height = 5,
},
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
.copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
});
PP_EXPECT(h, status.ok());
PP_EXPECT(h, recorded_framebuffer_texture_copy_calls.empty());
}
void rejects_invalid_framebuffer_to_texture_copies(pp::tests::Harness& h)
{
const auto missing_dispatch = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
.width = 1,
.height = 1,
},
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {});
const auto invalid_dimensions = pp::renderer::gl::copy_opengl_framebuffer_to_texture_2d(
pp::renderer::gl::OpenGlTexture2DFramebufferCopy {
.width = -1,
.height = 1,
},
pp::renderer::gl::OpenGlTexture2DFramebufferCopyDispatch {
.copy_tex_sub_image_2d = record_copy_tex_sub_image_2d,
});
PP_EXPECT(h, !missing_dispatch.ok());
PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !invalid_dimensions.ok());
PP_EXPECT(h, invalid_dimensions.code == pp::foundation::StatusCode::invalid_argument);
}
void generates_texture_2d_mipmaps_through_dispatch(pp::tests::Harness& h) void generates_texture_2d_mipmaps_through_dispatch(pp::tests::Harness& h)
{ {
recorded_binding_calls.clear(); recorded_binding_calls.clear();
@@ -4656,6 +4760,9 @@ int main()
harness.run("updates_draws_and_deletes_mesh_through_dispatch", updates_draws_and_deletes_mesh_through_dispatch); harness.run("updates_draws_and_deletes_mesh_through_dispatch", updates_draws_and_deletes_mesh_through_dispatch);
harness.run("rejects_invalid_mesh_dispatch", rejects_invalid_mesh_dispatch); harness.run("rejects_invalid_mesh_dispatch", rejects_invalid_mesh_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("copies_framebuffer_to_texture_2d_through_dispatch", copies_framebuffer_to_texture_2d_through_dispatch);
harness.run("skips_zero_sized_framebuffer_to_texture_copies", skips_zero_sized_framebuffer_to_texture_copies);
harness.run("rejects_invalid_framebuffer_to_texture_copies", rejects_invalid_framebuffer_to_texture_copies);
harness.run("generates_texture_2d_mipmaps_through_dispatch", generates_texture_2d_mipmaps_through_dispatch); harness.run("generates_texture_2d_mipmaps_through_dispatch", generates_texture_2d_mipmaps_through_dispatch);
harness.run("reads_back_texture_2d_through_framebuffer_dispatch", reads_back_texture_2d_through_framebuffer_dispatch); harness.run("reads_back_texture_2d_through_framebuffer_dispatch", reads_back_texture_2d_through_framebuffer_dispatch);
harness.run("reports_incomplete_texture_2d_readback_framebuffer", reports_incomplete_texture_2d_readback_framebuffer); harness.run("reports_incomplete_texture_2d_readback_framebuffer", reports_incomplete_texture_2d_readback_framebuffer);