From 111cc8c892be7685c8302b0a14001c9bd7e65c41 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Thu, 4 Jun 2026 23:44:25 +0200 Subject: [PATCH] Route RTT texture updates through GL backend --- docs/modernization/build-inventory.md | 7 +++- docs/modernization/debt.md | 2 +- docs/modernization/roadmap.md | 11 +++-- src/canvas.cpp | 20 +--------- src/canvas_actions.cpp | 13 +----- src/canvas_layer.cpp | 6 +-- src/renderer_gl/opengl_capabilities.cpp | 6 ++- src/renderer_gl/opengl_capabilities.h | 2 + src/rtt.cpp | 51 ++++++++++++++++++++++++ src/rtt.h | 1 + tests/renderer_gl/capabilities_tests.cpp | 40 +++++++++++++++++++ 11 files changed, 115 insertions(+), 44 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index 431eca4..59c6db7 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -301,7 +301,9 @@ Known local toolchain state: 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 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, layer-conversion, panorama UI, and brush-preview paths now execute through a 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 the retained `Texture2D` utility, tested framebuffer blit/readback dispatch 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 by retained `RTT::create`/`RTT::destroy`, tested RTT render-target clear, masked color clear with color-write-mask restore, and texture-bind dispatch, diff --git a/docs/modernization/debt.md b/docs/modernization/debt.md index 148bae7..7594eb4 100644 --- a/docs/modernization/debt.md +++ b/docs/modernization/debt.md @@ -53,7 +53,7 @@ agent or engineer to remove them without reconstructing context from chat. | DEBT-0033 | Open | Modernization | Tools menu planning and direct command execution dispatch now consume pure `pp_app_core` through `App::init_menu_tools`, `pano_cli plan-tools-menu`, `pano_cli plan-tools-panel`, and the `ToolsMenuServices` boundary, 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-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-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 | diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index 4675c9b..747b256 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -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 transform history paths now call that helper instead of binding an RTT and 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, framebuffer readback, map, unmap, and deletion through tested `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 mesh dispatch contracts used by `Shape`, leaving the retained `Font` utility with thin GL adapter functions for mesh operations. -Canvas undo/redo dirty-region texture updates and readbacks now also delegate -their 2D texture target, RGBA pixel format, and unsigned-byte component type -mapping to `pp_renderer_gl`. +Canvas undo/redo dirty-region texture updates and readbacks now also execute +through retained `RTT` helpers backed by `pp_renderer_gl`, including 2D texture +target, dirty-region offsets, RGBA pixel format, and unsigned-byte component +type mapping. `NodeViewport` preview rendering now also delegates viewport query, clear-color query, color-buffer clear mask, viewport execution, color clear, clear-color restore, and blend-state execution through the shared diff --git a/src/canvas.cpp b/src/canvas.cpp index fcaa616..9692efc 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -34,16 +34,6 @@ GLint rgba8_internal_format() return static_cast(pp::renderer::gl::rgba8_internal_format()); } -GLenum texture_2d_target() -{ - return static_cast(pp::renderer::gl::texture_2d_target()); -} - -GLenum rgba_pixel_format() -{ - return static_cast(pp::renderer::gl::rgba_pixel_format()); -} - pp::renderer::RenderDeviceFeatures canvas_render_device_features() noexcept { return ShaderManager::render_device_features(); @@ -107,11 +97,6 @@ pp::paint_renderer::CanvasBlendGatePlan draw_merge_blend_gate_plan( return fallback; } -GLenum unsigned_byte_component_type() -{ - return static_cast(pp::renderer::gl::unsigned_byte_component_type()); -} - GLenum depth_test_state() { return static_cast(pp::renderer::gl::depth_test_state()); @@ -1961,10 +1946,7 @@ void Canvas::FloodData::apply() auto& rtt = layer->rtt(plane); App::I->render_task([&] { - rtt.bindTexture(); - glTexSubImage2D(texture_2d_target(), 0, 0, 0, rtt.getWidth(), rtt.getHeight(), - rgba_pixel_format(), unsigned_byte_component_type(), rgb[plane].get()); - rtt.unbindTexture(); + rtt.updateRgba8(0, 0, rtt.getWidth(), rtt.getHeight(), rgb[plane].get()); }); layer->face(plane) = true; layer->box(plane) = box_union(layer->box(plane), bb[plane]); diff --git a/src/canvas_actions.cpp b/src/canvas_actions.cpp index bcce4b5..b1964be 100644 --- a/src/canvas_actions.cpp +++ b/src/canvas_actions.cpp @@ -4,7 +4,6 @@ #include "canvas.h" #include "canvas_actions.h" #include "node_panel_layer.h" -#include "renderer_gl/opengl_capabilities.h" void ActionStroke::undo() { @@ -37,22 +36,12 @@ void ActionStroke::undo() { App::I->render_task([&] { - const auto texture_target = pp::renderer::gl::texture_2d_target(); - 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, + m_canvas->m_layers[m_layer_idx]->rtt(i, m_frame_idx).updateRgba8( (int)m_box[i].x, (int)m_box[i].y, (int)box_sz.x, (int)box_sz.y, - pixel_format, - component_type, m_image[i].get()); - m_canvas->m_layers[m_layer_idx]->rtt(i, m_frame_idx).unbindTexture(); }); } else diff --git a/src/canvas_layer.cpp b/src/canvas_layer.cpp index 94a7749..e93a18c 100644 --- a/src/canvas_layer.cpp +++ b/src/canvas_layer.cpp @@ -695,15 +695,11 @@ void LayerFrame::restore(const Snapshot& snap) // it's just a quick fix DON'T SHIP!! //m_rtt[i].recreate(); - m_rtt[i].bindTexture(); 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(m_dirty_box[i].x), static_cast(m_dirty_box[i].y), static_cast(box_sz.x), static_cast(box_sz.y), - pp::renderer::gl::rgba_pixel_format(), - pp::renderer::gl::unsigned_byte_component_type(), snap.image[i].get()); - m_rtt[i].unbindTexture(); 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); } diff --git a/src/renderer_gl/opengl_capabilities.cpp b/src/renderer_gl/opengl_capabilities.cpp index 03b4d5e..d9f6c03 100644 --- a/src/renderer_gl/opengl_capabilities.cpp +++ b/src/renderer_gl/opengl_capabilities.cpp @@ -832,6 +832,8 @@ pp::foundation::Status update_opengl_texture_2d( } if (update.texture_id == 0U + || update.x < 0 + || update.y < 0 || update.width <= 0 || update.height <= 0 || update.pixel_format == 0U @@ -844,8 +846,8 @@ pp::foundation::Status update_opengl_texture_2d( dispatch.tex_sub_image_2d( texture_2d_target(), 0, - 0, - 0, + update.x, + update.y, update.width, update.height, update.pixel_format, diff --git a/src/renderer_gl/opengl_capabilities.h b/src/renderer_gl/opengl_capabilities.h index 55e5371..3baa018 100644 --- a/src/renderer_gl/opengl_capabilities.h +++ b/src/renderer_gl/opengl_capabilities.h @@ -142,6 +142,8 @@ struct OpenGlTextureCubeAllocation { struct OpenGlTexture2DUpdate { std::uint32_t texture_id = 0; + std::int32_t x = 0; + std::int32_t y = 0; std::int32_t width = 0; std::int32_t height = 0; std::uint32_t pixel_format = 0; diff --git a/src/rtt.cpp b/src/rtt.cpp index cdd35a0..48b2ae6 100644 --- a/src/rtt.cpp +++ b/src/rtt.cpp @@ -92,6 +92,29 @@ void set_opengl_texture_2d_image( 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(target), + static_cast(level), + static_cast(x), + static_cast(y), + static_cast(width), + static_cast(height), + static_cast(pixel_format), + static_cast(component_type), + data); +} + void set_opengl_texture_parameter_f(std::uint32_t target, std::uint32_t parameter, float value) noexcept { glTexParameterf(static_cast(target), static_cast(parameter), static_cast(value)); @@ -760,6 +783,34 @@ bool RTT::readPixelsRgba8(int x, int y, int width, int height, void* buffer) con 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 { if (!valid()) diff --git a/src/rtt.h b/src/rtt.h index f924f3a..530f613 100644 --- a/src/rtt.h +++ b/src/rtt.h @@ -68,6 +68,7 @@ public: void clear(glm::vec4 color = glm::vec4(0)); void clear_mask(glm::bool4 mask, glm::vec4 color = glm::vec4(0)); 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; uint8_t* readTextureData(uint8_t* buffer = nullptr) const noexcept; float* readTextureDataFloat(float* buffer = nullptr) const noexcept; diff --git a/tests/renderer_gl/capabilities_tests.cpp b/tests/renderer_gl/capabilities_tests.cpp index 6cbbe3a..65490fd 100644 --- a/tests/renderer_gl/capabilities_tests.cpp +++ b/tests/renderer_gl/capabilities_tests.cpp @@ -4342,6 +4342,8 @@ void updates_texture_2d_through_dispatch(pp::tests::Harness& h) const auto status = pp::renderer::gl::update_opengl_texture_2d( pp::renderer::gl::OpenGlTexture2DUpdate { .texture_id = 31U, + .x = 3, + .y = 4, .width = 2, .height = 2, .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_texture_image_calls.size() == 1U); 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].height == 2); PP_EXPECT(h, recorded_texture_image_calls[0].data == pixels.data()); } +void rejects_invalid_texture_2d_updates(pp::tests::Harness& h) +{ + const std::array 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) { 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("rejects_invalid_pixel_buffer_dispatch", rejects_invalid_pixel_buffer_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_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);