Route RTT region readbacks through backend

This commit is contained in:
2026-06-04 21:01:13 +02:00
parent b9dbcd10d7
commit 15c58bfb21
8 changed files with 86 additions and 58 deletions

View File

@@ -290,7 +290,9 @@ Known local toolchain state:
allocation/storage/delete and framebuffer depth attach/detach also execute allocation/storage/delete and framebuffer depth attach/detach also execute
through tested dispatch contracts here. Renderer API render-pass color/depth/stencil 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 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 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
@@ -410,12 +412,14 @@ Known local toolchain state:
formats for cube-strip imports. Clamp-to-border sampler wrap is now part of formats for cube-strip imports. Clamp-to-border sampler wrap is now part of
the backend capability catalog and test coverage. the backend capability catalog and test coverage.
Early canvas draw helpers also consume backend-owned pick readback Early canvas draw helpers also consume backend-owned pick readback
format/type, stroke mixer depth/scissor/blend state, saved viewport and format/type and RTT-backed region-readback execution, stroke mixer
clear-state queries, active texture units, fallback 2D texture unbind depth/scissor/blend state, saved viewport and clear-state queries, active
targets, and stroke background copy targets. texture units, fallback 2D texture unbind targets, and stroke background copy
targets.
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, active texture units, fallback 2D state, history readback format/type and RTT-backed region-readback execution,
texture unbind targets, and layer compositing copy targets. active texture units, fallback 2D texture unbind targets, and layer
compositing copy targets.
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.
@@ -610,7 +614,8 @@ Known local toolchain state:
VR draw state setup and restore, tested saved-state snapshot/restore dispatch 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 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 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 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

@@ -810,6 +810,10 @@ resource lifecycle path behind the renderer backend boundary.
Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute Legacy `RTT` resize/copy blits and byte/float framebuffer readbacks now execute
through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read through tested `pp_renderer_gl` framebuffer dispatch contracts with draw/read
framebuffer binding restore handled by the backend helper. 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 Legacy `RTT::bindFramebuffer` and `RTT::unbindFramebuffer` now use tested
`pp_renderer_gl` draw/read framebuffer binding snapshot and restore contracts, `pp_renderer_gl` draw/read framebuffer binding snapshot and restore contracts,
moving render-target pass entry/exit state management behind the backend. 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; 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, and RGBA8 blend/depth state, active texture units, 2D copy targets, RGBA8 readback
readback formats through the renderer GL backend mapping. formats, and RTT-backed transform history region 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, copy targets, and RGBA8 render-target formats
@@ -1992,7 +1997,9 @@ Results:
renderer GL backend mapping. renderer GL backend mapping.
- Canvas stroke commit now routes saved viewport/clear/blend state, history - Canvas stroke commit now routes saved viewport/clear/blend state, history
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. 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 - 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

@@ -325,18 +325,14 @@ void Canvas::pick_update(int plane)
draw_merge(true, faces); draw_merge(true, faces);
int i = plane; int i = plane;
m_layers_merge.rtt(i).bindFramebuffer();
if (!m_pick_data[plane]) if (!m_pick_data[plane])
m_pick_data[plane] = std::make_unique<glm::u8vec4[]>(m_width * m_height); m_pick_data[plane] = std::make_unique<glm::u8vec4[]>(m_width * m_height);
glReadPixels( m_layers_merge.rtt(i).readPixelsRgba8(
0, 0,
0, 0,
m_width, m_width,
m_height, m_height,
rgba_pixel_format(),
unsigned_byte_component_type(),
m_pick_data[plane].get()); m_pick_data[plane].get());
m_layers_merge.rtt(i).unbindFramebuffer();
}); });
m_pick_ready[plane] = true; m_pick_ready[plane] = true;
@@ -1030,13 +1026,11 @@ void Canvas::stroke_commit()
// save image before commit // save image before commit
glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]); glm::vec2 box_sz = zw(m_dirty_box[i]) - xy(m_dirty_box[i]);
action->m_image[i] = std::make_unique<uint8_t[]>(box_sz.x * box_sz.y * 4); action->m_image[i] = std::make_unique<uint8_t[]>(box_sz.x * box_sz.y * 4);
glReadPixels( m_layers[m_current_layer_idx]->rtt(i).readPixelsRgba8(
m_dirty_box[i].x, static_cast<int>(m_dirty_box[i].x),
m_dirty_box[i].y, static_cast<int>(m_dirty_box[i].y),
box_sz.x, static_cast<int>(box_sz.x),
box_sz.y, static_cast<int>(box_sz.y),
rgba_pixel_format(),
unsigned_byte_component_type(),
action->m_image[i].get()); action->m_image[i].get());
action->m_box[i] = m_dirty_box[i]; action->m_box[i] = m_dirty_box[i];
@@ -3105,7 +3099,12 @@ void Canvas::draw_objects(std::function<void(const glm::mat4& camera, const glm:
if (has_data) if (has_data)
{ {
action->m_image[i] = std::make_unique<uint8_t[]>(box_sz.x * box_sz.y * 4); action->m_image[i] = std::make_unique<uint8_t[]>(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<int>(bounds.x),
static_cast<int>(bounds.y),
static_cast<int>(box_sz.x),
static_cast<int>(box_sz.y),
action->m_image[i].get());
action->m_box[i] = bounds; action->m_box[i] = bounds;
} }
action->m_old_box[i] = layer.box(i, frame); action->m_old_box[i] = layer.box(i, frame);

View File

@@ -92,22 +92,12 @@ Action* ActionStroke::get_redo()
{ {
action->m_image[i] = std::make_unique<uint8_t[]>( action->m_image[i] = std::make_unique<uint8_t[]>(
static_cast<size_t>((int)box_sz.x) * static_cast<size_t>((int)box_sz.y) * 4U); static_cast<size_t>((int)box_sz.x) * static_cast<size_t>((int)box_sz.y) * 4U);
App::I->render_task([&] layer->rtt(i, m_frame_idx).readPixelsRgba8(
{ static_cast<int>(box_or.x),
const auto pixel_format = pp::renderer::gl::rgba_pixel_format(); static_cast<int>(box_or.y),
const auto component_type = pp::renderer::gl::unsigned_byte_component_type(); static_cast<int>(box_sz.x),
static_cast<int>(box_sz.y),
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()); action->m_image[i].get());
layer->rtt(i, m_frame_idx).unbindFramebuffer();
});
} }
else else
{ {

View File

@@ -561,14 +561,12 @@ LayerFrame::Snapshot LayerFrame::snapshot(std::array<glm::vec4, 6>* dirty_box /*
snap.image[i] = std::make_unique<uint8_t[]>(m_rtt[i].bytes()); snap.image[i] = std::make_unique<uint8_t[]>(m_rtt[i].bytes());
m_rtt[i].bindFramebuffer();
glm::vec2 box_sz = zw(snap.m_dirty_box[i]) - xy(snap.m_dirty_box[i]); glm::vec2 box_sz = zw(snap.m_dirty_box[i]) - xy(snap.m_dirty_box[i]);
glReadPixels(static_cast<int>(snap.m_dirty_box[i].x), static_cast<int>(snap.m_dirty_box[i].y), m_rtt[i].readPixelsRgba8(
static_cast<int>(snap.m_dirty_box[i].x),
static_cast<int>(snap.m_dirty_box[i].y),
static_cast<int>(box_sz.x), static_cast<int>(box_sz.y), static_cast<int>(box_sz.x), static_cast<int>(box_sz.y),
pp::renderer::gl::rgba_pixel_format(),
pp::renderer::gl::unsigned_byte_component_type(),
snap.image[i].get()); snap.image[i].get());
m_rtt[i].unbindFramebuffer();
} }
}); });
return snap; return snap;

View File

@@ -1321,13 +1321,11 @@ void CanvasModeTransform::enter(kCanvasMode prev)
ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 0 }); ShaderManager::u_vec4(kShaderUniform::Col, { 0, 0, 0, 0 });
layer->rtt(i).bindFramebuffer(); layer->rtt(i).bindFramebuffer();
// copy framebuffer to action data // copy framebuffer to action data
glReadPixels( layer->rtt(i).readPixelsRgba8(
bb_min.x, static_cast<int>(bb_min.x),
bb_min.y, static_cast<int>(bb_min.y),
bb_sz.x, static_cast<int>(bb_sz.x),
bb_sz.y, static_cast<int>(bb_sz.y),
pp::renderer::gl::rgba_pixel_format(),
pp::renderer::gl::unsigned_byte_component_type(),
action->m_image[i].get()); action->m_image[i].get());
for (int j = 0; j < 6; j++) for (int j = 0; j < 6; j++)
m_shape[j].draw_fill(); 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()); glViewport(0, 0, layer->rtt(i).getWidth(), layer->rtt(i).getHeight());
// save fb content for history // save fb content for history
glReadPixels( layer->rtt(i).readPixelsRgba8(
bb_min.x, static_cast<int>(bb_min.x),
bb_min.y, static_cast<int>(bb_min.y),
bb_sz.x, static_cast<int>(bb_sz.x),
bb_sz.y, static_cast<int>(bb_sz.y),
pp::renderer::gl::rgba_pixel_format(),
pp::renderer::gl::unsigned_byte_component_type(),
action->m_image[i].get()); action->m_image[i].get());
// copy fb content to texture for blending // copy fb content to texture for blending
set_active_texture_unit(0); set_active_texture_unit(0);

View File

@@ -503,6 +503,38 @@ glm::ivec4 RTT::calc_bounds() const noexcept
return { bbmin, bbmax }; 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<std::uint32_t>(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 uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept
{ {
if (!valid()) if (!valid())

View File

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