Route RTT blit readback through renderer GL

This commit is contained in:
2026-06-03 06:31:41 +02:00
parent ae69f7437f
commit 3128a0d309
6 changed files with 487 additions and 73 deletions

View File

@@ -467,8 +467,10 @@ Known local toolchain state:
tested generic capability/buffer-clear dispatch consumed by VR draw state tested generic capability/buffer-clear dispatch consumed by VR draw state
setup, tested saved-state snapshot/restore dispatch consumed by the retained setup, tested saved-state snapshot/restore dispatch consumed by the retained
`gl_state` utility, tested texture lifecycle/readback dispatch consumed by `gl_state` utility, tested texture lifecycle/readback dispatch consumed by
the retained `Texture2D` utility, plus renderer API to OpenGL token mapping the retained `Texture2D` utility, tested framebuffer blit/readback dispatch
and command-planning contracts used by the OpenGL parity work. consumed by retained `RTT` resize/copy/readback paths, plus renderer API to
OpenGL token mapping and command-planning contracts used by the OpenGL parity
work.
- `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability, - `pano_cli plan-cloud-upload` exposes `pp_app_core` cloud upload availability,
new-document warning, publish prompt, and save-before-upload planning as JSON; new-document warning, publish prompt, and save-before-upload planning as JSON;
the live cloud upload command consumes the same start contract before the live cloud upload command consumes the same start contract before

View File

@@ -544,6 +544,9 @@ Legacy `Texture2D` allocation, binding, deletion, mipmap generation, region
update, and framebuffer readback now execute through tested `pp_renderer_gl` update, and framebuffer readback now execute through tested `pp_renderer_gl`
texture dispatch contracts. This keeps the app API stable while moving another texture dispatch contracts. This keeps the app API stable while moving another
resource lifecycle path behind the renderer backend boundary. 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.
Windows RenderDoc frame capture hooks now also dispatch through Windows RenderDoc frame capture hooks now also dispatch through
`PlatformServices`, keeping capture integration in the platform service while `PlatformServices`, keeping capture integration in the platform service while
leaving non-Windows adapters as no-ops. leaving non-Windows adapters as no-ops.

View File

@@ -608,6 +608,82 @@ pp::foundation::Result<OpenGlTexture2DReadbackResult> readback_opengl_texture_2d
return pp::foundation::Result<OpenGlTexture2DReadbackResult>::success(result); return pp::foundation::Result<OpenGlTexture2DReadbackResult>::success(result);
} }
pp::foundation::Status blit_opengl_framebuffer(
OpenGlFramebufferBlit blit,
OpenGlFramebufferBlitDispatch dispatch) noexcept
{
if (dispatch.get_integer == nullptr
|| dispatch.bind_framebuffer == nullptr
|| dispatch.blit_framebuffer == nullptr) {
return pp::foundation::Status::invalid_argument("OpenGL framebuffer blit dispatch callbacks must not be null");
}
if (blit.source_rect.x1 <= blit.source_rect.x0
|| blit.source_rect.y1 <= blit.source_rect.y0
|| blit.destination_rect.x1 <= blit.destination_rect.x0
|| blit.destination_rect.y1 <= blit.destination_rect.y0
|| blit.mask == 0U
|| blit.filter == 0U) {
return pp::foundation::Status::invalid_argument("OpenGL framebuffer blit parameters are invalid");
}
std::int32_t previous_draw_framebuffer = 0;
std::int32_t previous_read_framebuffer = 0;
dispatch.get_integer(draw_framebuffer_binding_query(), &previous_draw_framebuffer);
dispatch.get_integer(read_framebuffer_binding_query(), &previous_read_framebuffer);
dispatch.bind_framebuffer(draw_framebuffer_target(), blit.destination_framebuffer);
dispatch.bind_framebuffer(read_framebuffer_target(), blit.source_framebuffer);
dispatch.blit_framebuffer(
blit.source_rect.x0,
blit.source_rect.y0,
blit.source_rect.x1,
blit.source_rect.y1,
blit.destination_rect.x0,
blit.destination_rect.y0,
blit.destination_rect.x1,
blit.destination_rect.y1,
blit.mask,
blit.filter);
dispatch.bind_framebuffer(draw_framebuffer_target(), static_cast<std::uint32_t>(previous_draw_framebuffer));
dispatch.bind_framebuffer(read_framebuffer_target(), static_cast<std::uint32_t>(previous_read_framebuffer));
return pp::foundation::Status::success();
}
pp::foundation::Status readback_opengl_framebuffer(
OpenGlFramebufferReadback readback,
OpenGlFramebufferReadbackDispatch dispatch) noexcept
{
if (dispatch.get_integer == nullptr
|| dispatch.bind_framebuffer == nullptr
|| dispatch.read_pixels == nullptr) {
return pp::foundation::Status::invalid_argument(
"OpenGL framebuffer readback dispatch callbacks must not be null");
}
if (readback.width <= 0
|| readback.height <= 0
|| readback.format.pixel_format == 0U
|| readback.format.component_type == 0U
|| readback.format.bytes_per_pixel == 0U
|| readback.pixels == nullptr) {
return pp::foundation::Status::invalid_argument("OpenGL framebuffer readback parameters are invalid");
}
std::int32_t previous_read_framebuffer = 0;
dispatch.get_integer(read_framebuffer_binding_query(), &previous_read_framebuffer);
dispatch.bind_framebuffer(read_framebuffer_target(), readback.framebuffer);
dispatch.read_pixels(
readback.x,
readback.y,
readback.width,
readback.height,
readback.format.pixel_format,
readback.format.component_type,
readback.pixels);
dispatch.bind_framebuffer(read_framebuffer_target(), static_cast<std::uint32_t>(previous_read_framebuffer));
return pp::foundation::Status::success();
}
std::uint32_t extension_count_query() noexcept std::uint32_t extension_count_query() noexcept
{ {
return gl_num_extensions; return gl_num_extensions;

View File

@@ -141,6 +141,32 @@ struct OpenGlTexture2DReadbackResult {
bool pixels_read = false; bool pixels_read = false;
}; };
struct OpenGlFramebufferRect {
std::int32_t x0 = 0;
std::int32_t y0 = 0;
std::int32_t x1 = 0;
std::int32_t y1 = 0;
};
struct OpenGlFramebufferBlit {
std::uint32_t source_framebuffer = 0;
std::uint32_t destination_framebuffer = 0;
OpenGlFramebufferRect source_rect;
OpenGlFramebufferRect destination_rect;
std::uint32_t mask = 0;
std::uint32_t filter = 0;
};
struct OpenGlFramebufferReadback {
std::uint32_t framebuffer = 0;
std::int32_t x = 0;
std::int32_t y = 0;
std::int32_t width = 0;
std::int32_t height = 0;
OpenGlReadbackFormat format;
void* pixels = nullptr;
};
struct OpenGlWindowsWglContextConfig { struct OpenGlWindowsWglContextConfig {
std::array<std::int32_t, 9> context_attributes {}; std::array<std::int32_t, 9> context_attributes {};
std::array<std::int32_t, 15> pixel_format_attributes {}; std::array<std::int32_t, 15> pixel_format_attributes {};
@@ -223,6 +249,17 @@ using OpenGlReadPixelsFn = void (*)(
std::uint32_t pixel_format, std::uint32_t pixel_format,
std::uint32_t component_type, std::uint32_t component_type,
void* pixels) noexcept; void* pixels) noexcept;
using OpenGlBlitFramebufferFn = void (*)(
std::int32_t source_x0,
std::int32_t source_y0,
std::int32_t source_x1,
std::int32_t source_y1,
std::int32_t destination_x0,
std::int32_t destination_y0,
std::int32_t destination_x1,
std::int32_t destination_y1,
std::uint32_t mask,
std::uint32_t filter) noexcept;
struct OpenGlStateDispatch { struct OpenGlStateDispatch {
OpenGlCapabilityFn enable = nullptr; OpenGlCapabilityFn enable = nullptr;
@@ -332,6 +369,18 @@ struct OpenGlTexture2DReadbackDispatch {
OpenGlDeleteObjectsFn delete_framebuffers = nullptr; OpenGlDeleteObjectsFn delete_framebuffers = nullptr;
}; };
struct OpenGlFramebufferBlitDispatch {
OpenGlGetIntegerFn get_integer = nullptr;
OpenGlBindFramebufferFn bind_framebuffer = nullptr;
OpenGlBlitFramebufferFn blit_framebuffer = nullptr;
};
struct OpenGlFramebufferReadbackDispatch {
OpenGlGetIntegerFn get_integer = nullptr;
OpenGlBindFramebufferFn bind_framebuffer = nullptr;
OpenGlReadPixelsFn read_pixels = nullptr;
};
[[nodiscard]] OpenGlCapabilities detect_opengl_capabilities( [[nodiscard]] OpenGlCapabilities detect_opengl_capabilities(
std::span<const std::string_view> extensions, std::span<const std::string_view> extensions,
OpenGlRuntime runtime) noexcept; OpenGlRuntime runtime) noexcept;
@@ -382,6 +431,12 @@ struct OpenGlTexture2DReadbackDispatch {
[[nodiscard]] pp::foundation::Result<OpenGlTexture2DReadbackResult> readback_opengl_texture_2d( [[nodiscard]] pp::foundation::Result<OpenGlTexture2DReadbackResult> readback_opengl_texture_2d(
OpenGlTexture2DReadback readback, OpenGlTexture2DReadback readback,
OpenGlTexture2DReadbackDispatch dispatch) noexcept; OpenGlTexture2DReadbackDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Status blit_opengl_framebuffer(
OpenGlFramebufferBlit blit,
OpenGlFramebufferBlitDispatch dispatch) noexcept;
[[nodiscard]] pp::foundation::Status readback_opengl_framebuffer(
OpenGlFramebufferReadback readback,
OpenGlFramebufferReadbackDispatch dispatch) noexcept;
[[nodiscard]] std::uint32_t extension_count_query() noexcept; [[nodiscard]] std::uint32_t extension_count_query() noexcept;
[[nodiscard]] std::uint32_t extension_string_name() noexcept; [[nodiscard]] std::uint32_t extension_string_name() noexcept;

View File

@@ -44,6 +44,60 @@ namespace {
return static_cast<GLenum>(pp::renderer::gl::read_framebuffer_binding_query()); return static_cast<GLenum>(pp::renderer::gl::read_framebuffer_binding_query());
} }
void query_opengl_integer(std::uint32_t name, std::int32_t* value) noexcept
{
glGetIntegerv(static_cast<GLenum>(name), reinterpret_cast<GLint*>(value));
}
void bind_opengl_framebuffer(std::uint32_t target, std::uint32_t framebuffer) noexcept
{
glBindFramebuffer(static_cast<GLenum>(target), static_cast<GLuint>(framebuffer));
}
void blit_opengl_framebuffer(
std::int32_t source_x0,
std::int32_t source_y0,
std::int32_t source_x1,
std::int32_t source_y1,
std::int32_t destination_x0,
std::int32_t destination_y0,
std::int32_t destination_x1,
std::int32_t destination_y1,
std::uint32_t mask,
std::uint32_t filter) noexcept
{
glBlitFramebuffer(
static_cast<GLint>(source_x0),
static_cast<GLint>(source_y0),
static_cast<GLint>(source_x1),
static_cast<GLint>(source_y1),
static_cast<GLint>(destination_x0),
static_cast<GLint>(destination_y0),
static_cast<GLint>(destination_x1),
static_cast<GLint>(destination_y1),
static_cast<GLbitfield>(mask),
static_cast<GLenum>(filter));
}
void read_opengl_pixels(
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,
void* pixels) noexcept
{
glReadPixels(
static_cast<GLint>(x),
static_cast<GLint>(y),
static_cast<GLsizei>(width),
static_cast<GLsizei>(height),
static_cast<GLenum>(pixel_format),
static_cast<GLenum>(component_type),
pixels);
}
} }
RTT& RTT::operator=(RTT&& other) RTT& RTT::operator=(RTT&& other)
@@ -112,9 +166,6 @@ bool RTT::resize(int width, int height)
App::I->render_task([&] App::I->render_task([&]
{ {
glGetIntegerv(draw_framebuffer_binding_query(), &oldDFboID);
glGetIntegerv(read_framebuffer_binding_query(), &oldRFboID);
ret = new_rtt.create(width, height, -1, int_fmt, rboID != 0); ret = new_rtt.create(width, height, -1, int_fmt, rboID != 0);
if (!ret) if (!ret)
{ {
@@ -122,22 +173,28 @@ bool RTT::resize(int width, int height)
return; return;
} }
glBindFramebuffer(draw_framebuffer_target(), new_rtt.fboID); const auto status = pp::renderer::gl::blit_opengl_framebuffer(
glBindFramebuffer(read_framebuffer_target(), fboID); pp::renderer::gl::OpenGlFramebufferBlit {
glBlitFramebuffer( .source_framebuffer = static_cast<std::uint32_t>(fboID),
0, .destination_framebuffer = static_cast<std::uint32_t>(new_rtt.fboID),
0, .source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = w, .y1 = h },
w, .destination_rect = pp::renderer::gl::OpenGlFramebufferRect {
h, .x1 = new_rtt.w,
0, .y1 = new_rtt.h,
0, },
new_rtt.w, .mask = pp::renderer::gl::framebuffer_color_buffer_mask(),
new_rtt.h, .filter = pp::renderer::gl::framebuffer_blit_filter(true),
static_cast<GLbitfield>(pp::renderer::gl::framebuffer_color_buffer_mask()), },
static_cast<GLenum>(pp::renderer::gl::framebuffer_blit_filter(true))); pp::renderer::gl::OpenGlFramebufferBlitDispatch {
.get_integer = query_opengl_integer,
glBindFramebuffer(draw_framebuffer_target(), oldDFboID); .bind_framebuffer = bind_opengl_framebuffer,
glBindFramebuffer(read_framebuffer_target(), oldRFboID); .blit_framebuffer = blit_opengl_framebuffer,
});
if (!status.ok()) {
LOG("RTT::resize blit failed because: %s", status.message);
ret = false;
return;
}
destroy(); destroy();
}); });
@@ -188,19 +245,22 @@ void RTT::copy(const RTT & source)
return; return;
App::I->render_task([&] App::I->render_task([&]
{ {
GLint old_draw = 0; const auto status = pp::renderer::gl::blit_opengl_framebuffer(
GLint old_read = 0; pp::renderer::gl::OpenGlFramebufferBlit {
glGetIntegerv(draw_framebuffer_binding_query(), &old_draw); .source_framebuffer = static_cast<std::uint32_t>(source.fboID),
glGetIntegerv(read_framebuffer_binding_query(), &old_read); .destination_framebuffer = static_cast<std::uint32_t>(fboID),
.source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = source.w, .y1 = source.h },
glBindFramebuffer(draw_framebuffer_target(), fboID); .destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = w, .y1 = h },
glBindFramebuffer(read_framebuffer_target(), source.fboID); .mask = pp::renderer::gl::framebuffer_color_buffer_mask(),
glBlitFramebuffer(0, 0, source.w, source.h, 0, 0, w, h, .filter = pp::renderer::gl::framebuffer_blit_filter(true),
static_cast<GLbitfield>(pp::renderer::gl::framebuffer_color_buffer_mask()), },
static_cast<GLenum>(pp::renderer::gl::framebuffer_blit_filter(true))); pp::renderer::gl::OpenGlFramebufferBlitDispatch {
.get_integer = query_opengl_integer,
glBindFramebuffer(draw_framebuffer_target(), old_draw); .bind_framebuffer = bind_opengl_framebuffer,
glBindFramebuffer(read_framebuffer_target(), old_read); .blit_framebuffer = blit_opengl_framebuffer,
});
if (!status.ok())
LOG("RTT::copy blit failed because: %s", status.message);
}); });
} }
@@ -211,20 +271,32 @@ void RTT::copy(const RTT& source, const glm::vec4& rect)
App::I->render_task([&] App::I->render_task([&]
{ {
auto r = rect_intersection(rect, { 0, 0, w, h }); auto r = rect_intersection(rect, { 0, 0, w, h });
const auto status = pp::renderer::gl::blit_opengl_framebuffer(
GLint old_draw = 0; pp::renderer::gl::OpenGlFramebufferBlit {
GLint old_read = 0; .source_framebuffer = static_cast<std::uint32_t>(source.fboID),
glGetIntegerv(draw_framebuffer_binding_query(), &old_draw); .destination_framebuffer = static_cast<std::uint32_t>(fboID),
glGetIntegerv(read_framebuffer_binding_query(), &old_read); .source_rect = pp::renderer::gl::OpenGlFramebufferRect {
.x0 = static_cast<std::int32_t>(r.x),
glBindFramebuffer(draw_framebuffer_target(), fboID); .y0 = static_cast<std::int32_t>(r.y),
glBindFramebuffer(read_framebuffer_target(), source.fboID); .x1 = static_cast<std::int32_t>(r.z),
glBlitFramebuffer(r.x, r.y, r.z, r.w, r.x, r.y, r.z, r.w, .y1 = static_cast<std::int32_t>(r.w),
static_cast<GLbitfield>(pp::renderer::gl::framebuffer_color_buffer_mask()), },
static_cast<GLenum>(pp::renderer::gl::framebuffer_blit_filter(false))); .destination_rect = pp::renderer::gl::OpenGlFramebufferRect {
.x0 = static_cast<std::int32_t>(r.x),
glBindFramebuffer(draw_framebuffer_target(), old_draw); .y0 = static_cast<std::int32_t>(r.y),
glBindFramebuffer(read_framebuffer_target(), old_read); .x1 = static_cast<std::int32_t>(r.z),
.y1 = static_cast<std::int32_t>(r.w),
},
.mask = pp::renderer::gl::framebuffer_color_buffer_mask(),
.filter = pp::renderer::gl::framebuffer_blit_filter(false),
},
pp::renderer::gl::OpenGlFramebufferBlitDispatch {
.get_integer = query_opengl_integer,
.bind_framebuffer = bind_opengl_framebuffer,
.blit_framebuffer = blit_opengl_framebuffer,
});
if (!status.ok())
LOG("RTT::copy region blit failed because: %s", status.message);
}); });
} }
@@ -314,7 +386,7 @@ bool RTT::create(int width, int height, int tex/* = -1*/, GLint internal_format,
} }
GLint oldFboID; GLint oldFboID;
glGetIntegerv(draw_framebuffer_binding_query(), &oldFboID); glGetIntegerv(static_cast<GLenum>(pp::renderer::gl::draw_framebuffer_binding_query()), &oldFboID);
// Create a framebuffer object // Create a framebuffer object
glGenFramebuffers(1, &fboID); glGenFramebuffers(1, &fboID);
@@ -440,18 +512,21 @@ uint8_t* RTT::readTextureData(uint8_t* buffer) const noexcept
App::I->render_task([&] App::I->render_task([&]
{ {
const auto readback = pp::renderer::gl::rgba8_readback_format(); const auto readback = pp::renderer::gl::rgba8_readback_format();
GLint old; const auto status = pp::renderer::gl::readback_opengl_framebuffer(
glGetIntegerv(read_framebuffer_binding_query(), &old); pp::renderer::gl::OpenGlFramebufferReadback {
glBindFramebuffer(read_framebuffer_target(), fboID); .framebuffer = static_cast<std::uint32_t>(fboID),
glReadPixels( .width = w,
0, .height = h,
0, .format = readback,
w, .pixels = buffer,
h, },
static_cast<GLenum>(readback.pixel_format), pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
static_cast<GLenum>(readback.component_type), .get_integer = query_opengl_integer,
buffer); .bind_framebuffer = bind_opengl_framebuffer,
glBindFramebuffer(read_framebuffer_target(), old); .read_pixels = read_opengl_pixels,
});
if (!status.ok())
LOG("RTT::readTextureData() failed because: %s", status.message);
}); });
return buffer; return buffer;
} }
@@ -465,18 +540,21 @@ float* RTT::readTextureDataFloat(float* buffer) const noexcept
App::I->render_task([&] App::I->render_task([&]
{ {
const auto readback = pp::renderer::gl::rgba32f_readback_format(); const auto readback = pp::renderer::gl::rgba32f_readback_format();
GLint old; const auto status = pp::renderer::gl::readback_opengl_framebuffer(
glGetIntegerv(read_framebuffer_binding_query(), &old); pp::renderer::gl::OpenGlFramebufferReadback {
glBindFramebuffer(read_framebuffer_target(), fboID); .framebuffer = static_cast<std::uint32_t>(fboID),
glReadPixels( .width = w,
0, .height = h,
0, .format = readback,
w, .pixels = buffer,
h, },
static_cast<GLenum>(readback.pixel_format), pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
static_cast<GLenum>(readback.component_type), .get_integer = query_opengl_integer,
buffer); .bind_framebuffer = bind_opengl_framebuffer,
glBindFramebuffer(read_framebuffer_target(), old); .read_pixels = read_opengl_pixels,
});
if (!status.ok())
LOG("RTT::readTextureDataFloat() failed because: %s", status.message);
}); });
return buffer; return buffer;
} }

View File

@@ -71,6 +71,19 @@ struct RecordedOpenGlReadPixelsCall {
void* pixels = nullptr; void* pixels = nullptr;
}; };
struct RecordedOpenGlBlitFramebufferCall {
std::int32_t source_x0 = 0;
std::int32_t source_y0 = 0;
std::int32_t source_x1 = 0;
std::int32_t source_y1 = 0;
std::int32_t destination_x0 = 0;
std::int32_t destination_y0 = 0;
std::int32_t destination_x1 = 0;
std::int32_t destination_y1 = 0;
std::uint32_t mask = 0;
std::uint32_t filter = 0;
};
std::vector<RecordedOpenGlStateCall> recorded_state_calls; std::vector<RecordedOpenGlStateCall> recorded_state_calls;
std::vector<std::uint32_t> recorded_string_queries; std::vector<std::uint32_t> recorded_string_queries;
std::vector<pp::renderer::gl::OpenGlDefaultClear> recorded_clear_calls; std::vector<pp::renderer::gl::OpenGlDefaultClear> recorded_clear_calls;
@@ -89,6 +102,7 @@ std::vector<std::uint32_t> recorded_deleted_framebuffers;
std::vector<RecordedOpenGlFramebufferAttachmentCall> recorded_framebuffer_attachment_calls; std::vector<RecordedOpenGlFramebufferAttachmentCall> recorded_framebuffer_attachment_calls;
std::vector<std::uint32_t> recorded_framebuffer_status_queries; std::vector<std::uint32_t> recorded_framebuffer_status_queries;
std::vector<RecordedOpenGlReadPixelsCall> recorded_read_pixels_calls; std::vector<RecordedOpenGlReadPixelsCall> recorded_read_pixels_calls;
std::vector<RecordedOpenGlBlitFramebufferCall> recorded_blit_framebuffer_calls;
std::uint32_t next_texture_id = 91U; std::uint32_t next_texture_id = 91U;
std::uint32_t next_framebuffer_id = 44U; std::uint32_t next_framebuffer_id = 44U;
std::uint32_t configured_framebuffer_status = 0x8CD5U; std::uint32_t configured_framebuffer_status = 0x8CD5U;
@@ -407,6 +421,32 @@ void record_read_pixels(
}); });
} }
void record_blit_framebuffer(
std::int32_t source_x0,
std::int32_t source_y0,
std::int32_t source_x1,
std::int32_t source_y1,
std::int32_t destination_x0,
std::int32_t destination_y0,
std::int32_t destination_x1,
std::int32_t destination_y1,
std::uint32_t mask,
std::uint32_t filter) noexcept
{
recorded_blit_framebuffer_calls.push_back(RecordedOpenGlBlitFramebufferCall {
.source_x0 = source_x0,
.source_y0 = source_y0,
.source_x1 = source_x1,
.source_y1 = source_y1,
.destination_x0 = destination_x0,
.destination_y0 = destination_y0,
.destination_x1 = destination_x1,
.destination_y1 = destination_y1,
.mask = mask,
.filter = filter,
});
}
void detects_common_extension_capabilities(pp::tests::Harness& h) void detects_common_extension_capabilities(pp::tests::Harness& h)
{ {
constexpr std::array<std::string_view, 2> extensions { constexpr std::array<std::string_view, 2> extensions {
@@ -1743,6 +1783,162 @@ void rejects_incomplete_texture_2d_readback_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument); PP_EXPECT(h, result.status().code == pp::foundation::StatusCode::invalid_argument);
} }
void blits_framebuffers_through_dispatch(pp::tests::Harness& h)
{
recorded_integer_queries.clear();
recorded_binding_calls.clear();
recorded_blit_framebuffer_calls.clear();
const auto status = pp::renderer::gl::blit_opengl_framebuffer(
pp::renderer::gl::OpenGlFramebufferBlit {
.source_framebuffer = 13U,
.destination_framebuffer = 17U,
.source_rect = pp::renderer::gl::OpenGlFramebufferRect {
.x0 = 1,
.y0 = 2,
.x1 = 65,
.y1 = 34,
},
.destination_rect = pp::renderer::gl::OpenGlFramebufferRect {
.x0 = 3,
.y0 = 4,
.x1 = 131,
.y1 = 68,
},
.mask = 0x00004000U,
.filter = 0x2601U,
},
pp::renderer::gl::OpenGlFramebufferBlitDispatch {
.get_integer = record_get_integer,
.bind_framebuffer = record_bind_framebuffer,
.blit_framebuffer = record_blit_framebuffer,
});
PP_EXPECT(h, status.ok());
PP_EXPECT(h, recorded_integer_queries.size() == 2U);
PP_EXPECT(h, recorded_integer_queries[0] == 0x8CA6U);
PP_EXPECT(h, recorded_integer_queries[1] == 0x8CAAU);
PP_EXPECT(h, recorded_binding_calls.size() == 4U);
PP_EXPECT(h, recorded_binding_calls[0].kind == RecordedOpenGlBindingCall::Kind::bind_framebuffer);
PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA9U);
PP_EXPECT(h, recorded_binding_calls[0].second == 17U);
PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
PP_EXPECT(h, recorded_binding_calls[1].second == 13U);
PP_EXPECT(h, recorded_binding_calls[2].first == 0x8CA9U);
PP_EXPECT(h, recorded_binding_calls[2].second == 7U);
PP_EXPECT(h, recorded_binding_calls[3].first == 0x8CA8U);
PP_EXPECT(h, recorded_binding_calls[3].second == 9U);
PP_EXPECT(h, recorded_blit_framebuffer_calls.size() == 1U);
PP_EXPECT(h, recorded_blit_framebuffer_calls[0].source_x0 == 1);
PP_EXPECT(h, recorded_blit_framebuffer_calls[0].source_y1 == 34);
PP_EXPECT(h, recorded_blit_framebuffer_calls[0].destination_x1 == 131);
PP_EXPECT(h, recorded_blit_framebuffer_calls[0].mask == 0x00004000U);
PP_EXPECT(h, recorded_blit_framebuffer_calls[0].filter == 0x2601U);
}
void rejects_invalid_framebuffer_blits(pp::tests::Harness& h)
{
const auto missing_dispatch = pp::renderer::gl::blit_opengl_framebuffer(
pp::renderer::gl::OpenGlFramebufferBlit {
.source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 1, .y1 = 1 },
.destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 1, .y1 = 1 },
.mask = 0x00004000U,
.filter = 0x2601U,
},
pp::renderer::gl::OpenGlFramebufferBlitDispatch {
.get_integer = record_get_integer,
.bind_framebuffer = record_bind_framebuffer,
});
const auto empty_source = pp::renderer::gl::blit_opengl_framebuffer(
pp::renderer::gl::OpenGlFramebufferBlit {
.source_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 0, .y1 = 1 },
.destination_rect = pp::renderer::gl::OpenGlFramebufferRect { .x1 = 1, .y1 = 1 },
.mask = 0x00004000U,
.filter = 0x2601U,
},
pp::renderer::gl::OpenGlFramebufferBlitDispatch {
.get_integer = record_get_integer,
.bind_framebuffer = record_bind_framebuffer,
.blit_framebuffer = record_blit_framebuffer,
});
PP_EXPECT(h, !missing_dispatch.ok());
PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !empty_source.ok());
PP_EXPECT(h, empty_source.code == pp::foundation::StatusCode::invalid_argument);
}
void reads_back_framebuffer_through_dispatch(pp::tests::Harness& h)
{
recorded_integer_queries.clear();
recorded_binding_calls.clear();
recorded_read_pixels_calls.clear();
std::array<std::uint8_t, 16> pixels {};
const auto status = pp::renderer::gl::readback_opengl_framebuffer(
pp::renderer::gl::OpenGlFramebufferReadback {
.framebuffer = 23U,
.x = 2,
.y = 3,
.width = 4,
.height = 5,
.format = pp::renderer::gl::rgba8_readback_format(),
.pixels = pixels.data(),
},
pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
.get_integer = record_get_integer,
.bind_framebuffer = record_bind_framebuffer,
.read_pixels = record_read_pixels,
});
PP_EXPECT(h, status.ok());
PP_EXPECT(h, recorded_integer_queries.size() == 1U);
PP_EXPECT(h, recorded_integer_queries[0] == 0x8CAAU);
PP_EXPECT(h, recorded_binding_calls.size() == 2U);
PP_EXPECT(h, recorded_binding_calls[0].first == 0x8CA8U);
PP_EXPECT(h, recorded_binding_calls[0].second == 23U);
PP_EXPECT(h, recorded_binding_calls[1].first == 0x8CA8U);
PP_EXPECT(h, recorded_binding_calls[1].second == 9U);
PP_EXPECT(h, recorded_read_pixels_calls.size() == 1U);
PP_EXPECT(h, recorded_read_pixels_calls[0].x == 2);
PP_EXPECT(h, recorded_read_pixels_calls[0].y == 3);
PP_EXPECT(h, recorded_read_pixels_calls[0].width == 4);
PP_EXPECT(h, recorded_read_pixels_calls[0].height == 5);
PP_EXPECT(h, recorded_read_pixels_calls[0].pixels == pixels.data());
}
void rejects_invalid_framebuffer_readbacks(pp::tests::Harness& h)
{
std::array<std::uint8_t, 4> pixels {};
const auto missing_dispatch = pp::renderer::gl::readback_opengl_framebuffer(
pp::renderer::gl::OpenGlFramebufferReadback {
.width = 1,
.height = 1,
.format = pp::renderer::gl::rgba8_readback_format(),
.pixels = pixels.data(),
},
pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
.get_integer = record_get_integer,
.bind_framebuffer = record_bind_framebuffer,
});
const auto missing_pixels = pp::renderer::gl::readback_opengl_framebuffer(
pp::renderer::gl::OpenGlFramebufferReadback {
.width = 1,
.height = 1,
.format = pp::renderer::gl::rgba8_readback_format(),
},
pp::renderer::gl::OpenGlFramebufferReadbackDispatch {
.get_integer = record_get_integer,
.bind_framebuffer = record_bind_framebuffer,
.read_pixels = record_read_pixels,
});
PP_EXPECT(h, !missing_dispatch.ok());
PP_EXPECT(h, missing_dispatch.code == pp::foundation::StatusCode::invalid_argument);
PP_EXPECT(h, !missing_pixels.ok());
PP_EXPECT(h, missing_pixels.code == pp::foundation::StatusCode::invalid_argument);
}
void maps_renderer_viewports_and_scissors(pp::tests::Harness& h) void maps_renderer_viewports_and_scissors(pp::tests::Harness& h)
{ {
const auto viewport = pp::renderer::gl::viewport_for_renderer_viewport( const auto viewport = pp::renderer::gl::viewport_for_renderer_viewport(
@@ -2145,6 +2341,10 @@ int main()
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);
harness.run("rejects_incomplete_texture_2d_readback_dispatch", rejects_incomplete_texture_2d_readback_dispatch); harness.run("rejects_incomplete_texture_2d_readback_dispatch", rejects_incomplete_texture_2d_readback_dispatch);
harness.run("blits_framebuffers_through_dispatch", blits_framebuffers_through_dispatch);
harness.run("rejects_invalid_framebuffer_blits", rejects_invalid_framebuffer_blits);
harness.run("reads_back_framebuffer_through_dispatch", reads_back_framebuffer_through_dispatch);
harness.run("rejects_invalid_framebuffer_readbacks", rejects_invalid_framebuffer_readbacks);
harness.run("maps_renderer_viewports_and_scissors", maps_renderer_viewports_and_scissors); harness.run("maps_renderer_viewports_and_scissors", maps_renderer_viewports_and_scissors);
harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens); harness.run("maps_renderer_blend_state_tokens", maps_renderer_blend_state_tokens);
harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks); harness.run("maps_renderer_color_write_masks", maps_renderer_color_write_masks);