From 15c58bfb21b346cf90d1e07faa04c95b2104f252 Mon Sep 17 00:00:00 2001 From: omigamedev Date: Thu, 4 Jun 2026 21:01:13 +0200 Subject: [PATCH] Route RTT region readbacks through backend --- docs/modernization/build-inventory.md | 19 ++++++++++------ docs/modernization/roadmap.md | 13 ++++++++--- src/canvas.cpp | 25 ++++++++++----------- src/canvas_actions.cpp | 22 +++++------------- src/canvas_layer.cpp | 8 +++---- src/canvas_modes.cpp | 24 +++++++++----------- src/rtt.cpp | 32 +++++++++++++++++++++++++++ src/rtt.h | 1 + 8 files changed, 86 insertions(+), 58 deletions(-) diff --git a/docs/modernization/build-inventory.md b/docs/modernization/build-inventory.md index f406c96..a01793e 100644 --- a/docs/modernization/build-inventory.md +++ b/docs/modernization/build-inventory.md @@ -290,7 +290,9 @@ Known local toolchain state: allocation/storage/delete and framebuffer depth attach/detach also execute through tested dispatch contracts here. Renderer API render-pass color/depth/stencil clear-mask and clear-value mapping, and color-write-mask query tokens. `RTT` no longer - spells GL enum names directly. It also + 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. It also validates renderer API primitive-topology to OpenGL draw-mode mapping, Shape index-type, fill/stroke primitive-mode, buffer target, static upload usage, and vertex attribute component/normalization mapping used by @@ -410,12 +412,14 @@ Known local toolchain state: formats for cube-strip imports. Clamp-to-border sampler wrap is now part of the backend capability catalog and test coverage. Early canvas draw helpers also consume backend-owned pick readback - format/type, stroke mixer depth/scissor/blend state, saved viewport and - clear-state queries, active texture units, fallback 2D texture unbind - targets, and stroke background copy targets. + format/type and RTT-backed region-readback execution, stroke mixer + depth/scissor/blend state, saved viewport and clear-state queries, active + texture units, fallback 2D texture unbind targets, and stroke background copy + targets. Canvas stroke commit also consumes backend-owned saved viewport/clear/blend - state, history readback format/type, active texture units, fallback 2D - texture unbind targets, and layer compositing copy targets. + state, history readback format/type and RTT-backed region-readback execution, + active texture units, fallback 2D texture unbind targets, and layer + compositing copy targets. Canvas layer merge rendering and explicit layer-merge compositing also consume backend-owned depth/blend state, active texture units, fallback 2D texture unbind targets, and merge framebuffer copy targets. @@ -610,7 +614,8 @@ Known local toolchain state: VR draw state setup and restore, tested saved-state snapshot/restore dispatch 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 paths, tested framebuffer + consumed by retained `RTT` resize/copy/readback and RGBA8 region-readback + paths, tested framebuffer bind/restore dispatch consumed by retained `RTT` render-target pass entry and exit paths, tested depth renderbuffer allocation/delete and framebuffer depth attach/detach dispatch consumed by canvas object-drawing helpers, diff --git a/docs/modernization/roadmap.md b/docs/modernization/roadmap.md index bff039a..5b21fd0 100644 --- a/docs/modernization/roadmap.md +++ b/docs/modernization/roadmap.md @@ -810,6 +810,10 @@ resource lifecycle path behind the renderer backend boundary. Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read framebuffer binding restore handled by the backend helper. +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::bindFramebuffer` and `RTT::unbindFramebuffer` now use tested `pp_renderer_gl` draw/read framebuffer binding snapshot and restore contracts, moving render-target pass entry/exit state management behind the backend. @@ -1975,8 +1979,9 @@ Results: and fallback 2D texture unbinds through the renderer GL backend mapping; platform VR SDK bridges remain isolated for later platform-shell extraction. - Canvas mode overlay, mask, and transform paths now route generic OpenGL - blend/depth state, active texture units, 2D copy targets, and RGBA8 - readback formats through the renderer GL backend mapping. + blend/depth state, active texture units, 2D copy targets, RGBA8 readback + formats, and RTT-backed transform history region readbacks through the + renderer GL backend mapping. - `NodeCanvas` panorama UI rendering now routes sampler defaults, saved viewport/clear/blend/depth/scissor state, color clears, active texture units, fallback 2D texture unbinds, copy targets, and RGBA8 render-target formats @@ -1992,7 +1997,9 @@ Results: renderer GL backend mapping. - Canvas stroke commit now routes saved viewport/clear/blend state, history readbacks, active texture units, fallback 2D texture unbinds, and layer - compositing copy targets through the renderer GL backend mapping. + compositing copy targets through the renderer GL backend mapping; the + RTT-backed dirty-region readbacks now execute through the retained `RTT` + region-readback helper rather than direct `glReadPixels`. - Canvas layer merge rendering and explicit layer-merge compositing now route depth/blend state, active texture units, fallback 2D texture unbinds, and merge framebuffer copy targets through the renderer GL backend mapping. diff --git a/src/canvas.cpp b/src/canvas.cpp index e5da0ea..9e3b472 100644 --- a/src/canvas.cpp +++ b/src/canvas.cpp @@ -325,18 +325,14 @@ void Canvas::pick_update(int plane) draw_merge(true, faces); int i = plane; - m_layers_merge.rtt(i).bindFramebuffer(); if (!m_pick_data[plane]) m_pick_data[plane] = std::make_unique(m_width * m_height); - glReadPixels( + m_layers_merge.rtt(i).readPixelsRgba8( 0, 0, m_width, m_height, - rgba_pixel_format(), - unsigned_byte_component_type(), m_pick_data[plane].get()); - m_layers_merge.rtt(i).unbindFramebuffer(); }); m_pick_ready[plane] = true; @@ -1030,13 +1026,11 @@ void Canvas::stroke_commit() // save image before commit glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); action->m_image[i] = std::make_unique(box_sz.x * box_sz.y * 4); - glReadPixels( - m_dirty_box[i].x, - m_dirty_box[i].y, - box_sz.x, - box_sz.y, - rgba_pixel_format(), - unsigned_byte_component_type(), + m_layers[m_current_layer_idx]->rtt(i).readPixelsRgba8( + static_cast(m_dirty_box[i].x), + static_cast(m_dirty_box[i].y), + static_cast(box_sz.x), + static_cast(box_sz.y), action->m_image[i].get()); action->m_box[i] = m_dirty_box[i]; @@ -3105,7 +3099,12 @@ void Canvas::draw_objects(std::functionm_image[i] = std::make_unique(box_sz.x * box_sz.y * 4); - glReadPixels(bounds.x, bounds.y, box_sz.x, box_sz.y, rgba_pixel_format(), unsigned_byte_component_type(), action->m_image[i].get()); + layer.rtt(i, frame).readPixelsRgba8( + static_cast(bounds.x), + static_cast(bounds.y), + static_cast(box_sz.x), + static_cast(box_sz.y), + action->m_image[i].get()); action->m_box[i] = bounds; } action->m_old_box[i] = layer.box(i, frame); diff --git a/src/canvas_actions.cpp b/src/canvas_actions.cpp index fbe2d1c..bcce4b5 100644 --- a/src/canvas_actions.cpp +++ b/src/canvas_actions.cpp @@ -92,22 +92,12 @@ Action* ActionStroke::get_redo() { action->m_image[i] = std::make_unique( static_cast((int)box_sz.x) * static_cast((int)box_sz.y) * 4U); - App::I->render_task([&] - { - const auto pixel_format = pp::renderer::gl::rgba_pixel_format(); - const auto component_type = pp::renderer::gl::unsigned_byte_component_type(); - - layer->rtt(i, m_frame_idx).bindFramebuffer(); - glReadPixels( - (int)box_or.x, - (int)box_or.y, - (int)box_sz.x, - (int)box_sz.y, - pixel_format, - component_type, - action->m_image[i].get()); - layer->rtt(i, m_frame_idx).unbindFramebuffer(); - }); + layer->rtt(i, m_frame_idx).readPixelsRgba8( + static_cast(box_or.x), + static_cast(box_or.y), + static_cast(box_sz.x), + static_cast(box_sz.y), + action->m_image[i].get()); } else { diff --git a/src/canvas_layer.cpp b/src/canvas_layer.cpp index 51d9543..c163316 100644 --- a/src/canvas_layer.cpp +++ b/src/canvas_layer.cpp @@ -561,14 +561,12 @@ LayerFrame::Snapshot LayerFrame::snapshot(std::array* dirty_box /* snap.image[i] = std::make_unique(m_rtt[i].bytes()); - m_rtt[i].bindFramebuffer(); glm::vec2 box_sz = zw(snap.m_dirty_box[i]) - xy(snap.m_dirty_box[i]); - glReadPixels(static_cast(snap.m_dirty_box[i].x), static_cast(snap.m_dirty_box[i].y), + m_rtt[i].readPixelsRgba8( + static_cast(snap.m_dirty_box[i].x), + static_cast(snap.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].unbindFramebuffer(); } }); return snap; diff --git a/src/canvas_modes.cpp b/src/canvas_modes.cpp index e122b4d..99e7a16 100644 --- a/src/canvas_modes.cpp +++ b/src/canvas_modes.cpp @@ -1321,13 +1321,11 @@ void CanvasModeTransform::enter(kCanvasMode prev) ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 0 }); layer->rtt(i).bindFramebuffer(); // copy framebuffer to action data - glReadPixels( - bb_min.x, - bb_min.y, - bb_sz.x, - bb_sz.y, - pp::renderer::gl::rgba_pixel_format(), - pp::renderer::gl::unsigned_byte_component_type(), + layer->rtt(i).readPixelsRgba8( + static_cast(bb_min.x), + static_cast(bb_min.y), + static_cast(bb_sz.x), + static_cast(bb_sz.y), action->m_image[i].get()); for (int j = 0; j < 6; j++) m_shape[j].draw_fill(); @@ -1426,13 +1424,11 @@ void CanvasModeTransform::leave(kCanvasMode next) glViewport(0, 0, layer->rtt(i).getWidth(), layer->rtt(i).getHeight()); // save fb content for history - glReadPixels( - bb_min.x, - bb_min.y, - bb_sz.x, - bb_sz.y, - pp::renderer::gl::rgba_pixel_format(), - pp::renderer::gl::unsigned_byte_component_type(), + layer->rtt(i).readPixelsRgba8( + static_cast(bb_min.x), + static_cast(bb_min.y), + static_cast(bb_sz.x), + static_cast(bb_sz.y), action->m_image[i].get()); // copy fb content to texture for blending set_active_texture_unit(0); diff --git a/src/rtt.cpp b/src/rtt.cpp index 5e0e9b1..417e698 100644 --- a/src/rtt.cpp +++ b/src/rtt.cpp @@ -503,6 +503,38 @@ glm::ivec4 RTT::calc_bounds() const noexcept return { bbmin, bbmax }; } +bool RTT::readPixelsRgba8(int x, int y, int width, int height, void* buffer) const noexcept +{ + if (!valid() || buffer == nullptr) + return false; + + bool ret = true; + App::I->render_task([&] + { + const auto readback = pp::renderer::gl::rgba8_readback_format(); + const auto status = pp::renderer::gl::readback_opengl_framebuffer( + pp::renderer::gl::OpenGlFramebufferReadback { + .framebuffer = static_cast(fboID), + .x = x, + .y = y, + .width = width, + .height = height, + .format = readback, + .pixels = buffer, + }, + pp::renderer::gl::OpenGlFramebufferReadbackDispatch { + .get_integer = query_opengl_integer, + .bind_framebuffer = bind_opengl_framebuffer, + .read_pixels = read_opengl_pixels, + }); + if (!status.ok()) { + LOG("RTT::readPixelsRgba8() failed because: %s", status.message); + ret = false; + } + }); + return ret; +} + uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept { if (!valid()) diff --git a/src/rtt.h b/src/rtt.h index 0d8cff5..f924f3a 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 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; uint8_t* createBuffer() const noexcept;