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
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,

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
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.

View File

@@ -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<glm::u8vec4[]>(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<uint8_t[]>(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<int>(m_dirty_box[i].x),
static_cast<int>(m_dirty_box[i].y),
static_cast<int>(box_sz.x),
static_cast<int>(box_sz.y),
action->m_image[i].get());
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)
{
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_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[]>(
static_cast<size_t>((int)box_sz.x) * static_cast<size_t>((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<int>(box_or.x),
static_cast<int>(box_or.y),
static_cast<int>(box_sz.x),
static_cast<int>(box_sz.y),
action->m_image[i].get());
}
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());
m_rtt[i].bindFramebuffer();
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),
pp::renderer::gl::rgba_pixel_format(),
pp::renderer::gl::unsigned_byte_component_type(),
snap.image[i].get());
m_rtt[i].unbindFramebuffer();
}
});
return snap;

View File

@@ -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<int>(bb_min.x),
static_cast<int>(bb_min.y),
static_cast<int>(bb_sz.x),
static_cast<int>(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<int>(bb_min.x),
static_cast<int>(bb_min.y),
static_cast<int>(bb_sz.x),
static_cast<int>(bb_sz.y),
action->m_image[i].get());
// copy fb content to texture for blending
set_active_texture_unit(0);

View File

@@ -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<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
{
if (!valid())

View File

@@ -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;