Add renderer texture binding contract

This commit is contained in:
2026-06-02 15:34:57 +02:00
parent ee3fb36047
commit 5dbeb0504d
9 changed files with 163 additions and 53 deletions

View File

@@ -285,13 +285,13 @@ Known local toolchain state:
source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed source code reintroduces raw `GL_*`/`WGL_*` constants outside the allowed
legacy OpenGL implementation files. legacy OpenGL implementation files.
- `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates - `pp_renderer_api` exposes a headless `RecordingRenderDevice` that validates
command order, texture-upload byte counts, readback bounds, frame-capture command order, texture-slot binding, texture-upload byte counts, readback
sources, destination buffer sizes, and render-target blit regions, records bounds, frame-capture sources, destination buffer sizes, and render-target
render/upload/readback/frame-capture/blit commands, and records trace markers blit regions, records render/texture-bind/upload/readback/frame-capture/blit
without a window or GL context. commands, and records trace markers without a window or GL context.
- `pano_cli record-render` exposes the recording renderer through JSON - `pano_cli record-render` exposes the recording renderer through JSON
automation, including upload/readback/frame-capture/blit command and byte automation, including texture-bind/upload/readback/frame-capture/blit command
totals, and is covered by `pano_cli_record_render_smoke` plus and byte totals, and is covered by `pano_cli_record_render_smoke` plus
`pano_cli_record_render_rejects_oversized_target`. `pano_cli_record_render_rejects_oversized_target`.
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory` - `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`
apply/undo/redo state through JSON automation and is covered by apply/undo/redo state through JSON automation and is covered by

View File

@@ -418,9 +418,9 @@ with texture descriptor, byte-size, viewport, mesh, readback bounds, command
context, render device, shader program descriptor, mesh, render target, context, render device, shader program descriptor, mesh, render target,
readback byte-size helpers, texture-upload/readback command validation, readback byte-size helpers, texture-upload/readback command validation,
frame-capture byte-size helpers, frame-capture command validation, frame-capture byte-size helpers, frame-capture command validation,
render-target blit validation, trace interface validation, and the canonical render-target blit validation, texture-slot binding validation, trace
PanoPainter shader catalog now consumed by the legacy OpenGL app initialization interface validation, and the canonical PanoPainter shader catalog now consumed
path. by the legacy OpenGL app initialization path.
`pp_renderer_gl` now exists as the first OpenGL backend library and owns pure `pp_renderer_gl` now exists as the first OpenGL backend library and owns pure
OpenGL capability detection for framebuffer fetch, map-buffer alignment, and OpenGL capability detection for framebuffer fetch, map-buffer alignment, and
float texture support. It also owns the OpenGL texture upload-type mapping used float texture support. It also owns the OpenGL texture upload-type mapping used
@@ -721,9 +721,9 @@ Results:
- `pp_renderer_api_tests` passed, including shader descriptor validation, - `pp_renderer_api_tests` passed, including shader descriptor validation,
PanoPainter shader catalog validation, readback byte-size and command-order PanoPainter shader catalog validation, readback byte-size and command-order
validation, texture-upload byte-count validation, frame-capture byte-size and validation, texture-upload byte-count validation, frame-capture byte-size and
command-order validation, render-target blit validation, recording command-order validation, render-target blit validation, texture-slot binding
upload/readback/frame-capture/blit command capture, and invalid catalog validation, recording texture-bind/upload/readback/frame-capture/blit command
rejection. capture, and invalid catalog rejection.
- `pp_paint_renderer_compositor_tests` passed. - `pp_paint_renderer_compositor_tests` passed.
- `pp_ui_core_color_tests` passed. - `pp_ui_core_color_tests` passed.
- `pp_ui_core_layout_value_tests` passed. - `pp_ui_core_layout_value_tests` passed.
@@ -817,13 +817,13 @@ Results:
reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL reintroduces raw `GL_*`/`WGL_*` constants outside the allowed legacy OpenGL
implementation files. implementation files.
- `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict - `pp_renderer_api` now includes a headless `RecordingRenderDevice` with strict
command-order/texture-upload/readback/frame-capture/blit validation; it command-order/texture-bind/texture-upload/readback/frame-capture/blit
records commands, trace markers, texture uploads/readbacks, frame captures, validation; it records commands, trace markers, texture binds,
and render-target blits, giving automation a backend-neutral render path that uploads/readbacks, frame captures, and render-target blits, giving automation
does not require a window or GL context. a backend-neutral render path that does not require a window or GL context.
- `pano_cli record-render` exercises that headless recording renderer and emits - `pano_cli record-render` exercises that headless recording renderer and emits
JSON command counts, target dimensions, backend name, trace/draw summary, and JSON command counts, target dimensions, backend name, trace/draw summary, and
texture-upload/readback/frame-capture/blit command/byte totals for agent texture-bind/upload/readback/frame-capture/blit command/byte totals for agent
automation, with an expected-failure smoke for oversized render/readback automation, with an expected-failure smoke for oversized render/readback
targets. targets.
- `pano_cli simulate-document-history` exercises pure document history - `pano_cli simulate-document-history` exercises pure document history

View File

@@ -136,6 +136,33 @@ pp::foundation::Status RecordingCommandContext::bind_shader(IShaderProgram& shad
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }
pp::foundation::Status RecordingCommandContext::bind_texture(
std::uint32_t slot,
ITexture2D& texture) noexcept
{
if (!in_render_pass_) {
return pp::foundation::Status::invalid_argument("render pass has not begun");
}
const auto slot_status = validate_texture_slot(slot);
if (!slot_status.ok()) {
return slot_status;
}
const auto desc = texture.desc();
const auto size_status = texture_byte_size(desc);
if (!size_status.ok()) {
return size_status.status();
}
push_command(commands_, RecordedRenderCommand {
.kind = RecordedRenderCommandKind::bind_texture,
.texture_desc = desc,
.texture_slot = slot,
});
return pp::foundation::Status::success();
}
pp::foundation::Status RecordingCommandContext::bind_mesh(IMesh& mesh) noexcept pp::foundation::Status RecordingCommandContext::bind_mesh(IMesh& mesh) noexcept
{ {
if (!in_render_pass_) { if (!in_render_pass_) {
@@ -375,6 +402,8 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no
return "set_viewport"; return "set_viewport";
case RecordedRenderCommandKind::bind_shader: case RecordedRenderCommandKind::bind_shader:
return "bind_shader"; return "bind_shader";
case RecordedRenderCommandKind::bind_texture:
return "bind_texture";
case RecordedRenderCommandKind::bind_mesh: case RecordedRenderCommandKind::bind_mesh:
return "bind_mesh"; return "bind_mesh";
case RecordedRenderCommandKind::draw: case RecordedRenderCommandKind::draw:

View File

@@ -11,6 +11,7 @@ enum class RecordedRenderCommandKind : std::uint8_t {
begin_render_pass, begin_render_pass,
set_viewport, set_viewport,
bind_shader, bind_shader,
bind_texture,
bind_mesh, bind_mesh,
draw, draw,
upload_texture, upload_texture,
@@ -28,6 +29,7 @@ struct RecordedRenderCommand {
Viewport viewport {}; Viewport viewport {};
MeshDesc mesh_desc {}; MeshDesc mesh_desc {};
TextureDesc texture_desc {}; TextureDesc texture_desc {};
std::uint32_t texture_slot = 0;
TextureDesc source_desc {}; TextureDesc source_desc {};
TextureDesc destination_desc {}; TextureDesc destination_desc {};
ReadbackRegion readback_region {}; ReadbackRegion readback_region {};
@@ -97,6 +99,9 @@ public:
ClearColor clear_color) noexcept override; ClearColor clear_color) noexcept override;
[[nodiscard]] pp::foundation::Status set_viewport(Viewport viewport) noexcept override; [[nodiscard]] pp::foundation::Status set_viewport(Viewport viewport) noexcept override;
[[nodiscard]] pp::foundation::Status bind_shader(IShaderProgram& shader) noexcept override; [[nodiscard]] pp::foundation::Status bind_shader(IShaderProgram& shader) noexcept override;
[[nodiscard]] pp::foundation::Status bind_texture(
std::uint32_t slot,
ITexture2D& texture) noexcept override;
[[nodiscard]] pp::foundation::Status bind_mesh(IMesh& mesh) noexcept override; [[nodiscard]] pp::foundation::Status bind_mesh(IMesh& mesh) noexcept override;
[[nodiscard]] pp::foundation::Status draw() noexcept override; [[nodiscard]] pp::foundation::Status draw() noexcept override;
[[nodiscard]] pp::foundation::Status read_texture( [[nodiscard]] pp::foundation::Status read_texture(

View File

@@ -151,6 +151,15 @@ pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept
return pp::foundation::Status::invalid_argument("mesh topology is not supported"); return pp::foundation::Status::invalid_argument("mesh topology is not supported");
} }
pp::foundation::Status validate_texture_slot(std::uint32_t slot) noexcept
{
if (slot >= max_texture_slots) {
return pp::foundation::Status::out_of_range("texture slot exceeds the configured limit");
}
return pp::foundation::Status::success();
}
pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept
{ {
if (desc.debug_name == nullptr) { if (desc.debug_name == nullptr) {

View File

@@ -10,6 +10,7 @@ namespace pp::renderer {
constexpr std::uint32_t max_texture_dimension = 32768; constexpr std::uint32_t max_texture_dimension = 32768;
constexpr std::uint32_t max_mesh_vertices = 16777216; constexpr std::uint32_t max_mesh_vertices = 16777216;
constexpr std::uint32_t max_texture_slots = 32;
constexpr std::uint64_t max_texture_bytes = 1024ULL * 1024ULL * 1024ULL; constexpr std::uint64_t max_texture_bytes = 1024ULL * 1024ULL * 1024ULL;
constexpr std::size_t max_shader_source_bytes = 4ULL * 1024ULL * 1024ULL; constexpr std::size_t max_shader_source_bytes = 4ULL * 1024ULL * 1024ULL;
@@ -126,6 +127,9 @@ public:
ClearColor clear_color) noexcept = 0; ClearColor clear_color) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status set_viewport(Viewport viewport) noexcept = 0; [[nodiscard]] virtual pp::foundation::Status set_viewport(Viewport viewport) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status bind_shader(IShaderProgram& shader) noexcept = 0; [[nodiscard]] virtual pp::foundation::Status bind_shader(IShaderProgram& shader) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status bind_texture(
std::uint32_t slot,
ITexture2D& texture) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status bind_mesh(IMesh& mesh) noexcept = 0; [[nodiscard]] virtual pp::foundation::Status bind_mesh(IMesh& mesh) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status draw() noexcept = 0; [[nodiscard]] virtual pp::foundation::Status draw() noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status read_texture( [[nodiscard]] virtual pp::foundation::Status read_texture(
@@ -160,6 +164,7 @@ public:
[[nodiscard]] pp::foundation::Status validate_extent(Extent2D extent) noexcept; [[nodiscard]] pp::foundation::Status validate_extent(Extent2D extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_viewport(Viewport viewport, Extent2D target_extent) noexcept; [[nodiscard]] pp::foundation::Status validate_viewport(Viewport viewport, Extent2D target_extent) noexcept;
[[nodiscard]] pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept; [[nodiscard]] pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_slot(std::uint32_t slot) noexcept;
[[nodiscard]] pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept; [[nodiscard]] pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept; [[nodiscard]] pp::foundation::Result<std::uint64_t> texture_byte_size(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> readback_byte_size( [[nodiscard]] pp::foundation::Result<std::uint64_t> readback_byte_size(

View File

@@ -365,7 +365,7 @@ if(TARGET pano_cli)
COMMAND pano_cli record-render --width 32 --height 16) COMMAND pano_cli record-render --width 32 --height 16)
set_tests_properties(pano_cli_record_render_smoke PROPERTIES set_tests_properties(pano_cli_record_render_smoke PROPERTIES
LABELS "renderer;integration;desktop-fast" LABELS "renderer;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"commands\":11.*\"drawCommands\":1.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048") PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"commands\":12.*\"drawCommands\":1.*\"bindTextureCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048")
add_test(NAME pano_cli_record_render_rejects_oversized_target add_test(NAME pano_cli_record_render_rejects_oversized_target
COMMAND "${CMAKE_COMMAND}" COMMAND "${CMAKE_COMMAND}"

View File

@@ -36,6 +36,7 @@ using pp::renderer::TextureFormat;
using pp::renderer::Viewport; using pp::renderer::Viewport;
using pp::renderer::max_shader_source_bytes; using pp::renderer::max_shader_source_bytes;
using pp::renderer::max_texture_dimension; using pp::renderer::max_texture_dimension;
using pp::renderer::max_texture_slots;
using pp::renderer::panopainter_shader_catalog; using pp::renderer::panopainter_shader_catalog;
using pp::renderer::primitive_topology_name; using pp::renderer::primitive_topology_name;
using pp::renderer::readback_byte_size; using pp::renderer::readback_byte_size;
@@ -50,6 +51,7 @@ using pp::renderer::validate_mesh_desc;
using pp::renderer::validate_readback_region; using pp::renderer::validate_readback_region;
using pp::renderer::validate_shader_catalog; using pp::renderer::validate_shader_catalog;
using pp::renderer::validate_shader_program_desc; using pp::renderer::validate_shader_program_desc;
using pp::renderer::validate_texture_slot;
using pp::renderer::validate_viewport; using pp::renderer::validate_viewport;
namespace { namespace {
@@ -146,6 +148,26 @@ public:
return pp::foundation::Status::success(); return pp::foundation::Status::success();
} }
[[nodiscard]] pp::foundation::Status bind_texture(
std::uint32_t slot,
pp::renderer::ITexture2D& texture) noexcept override
{
if (!in_render_pass) {
return pp::foundation::Status::invalid_argument("render pass has not begun");
}
const auto slot_status = validate_texture_slot(slot);
if (!slot_status.ok()) {
return slot_status;
}
const auto bytes = texture_byte_size(texture.desc());
if (!bytes) {
return bytes.status();
}
last_texture_slot = slot;
last_texture_bytes = bytes.value();
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status bind_mesh(IMesh& mesh) noexcept override [[nodiscard]] pp::foundation::Status bind_mesh(IMesh& mesh) noexcept override
{ {
return validate_mesh_desc(mesh.desc()); return validate_mesh_desc(mesh.desc());
@@ -243,6 +265,8 @@ public:
bool in_render_pass = false; bool in_render_pass = false;
const char* shader_name = nullptr; const char* shader_name = nullptr;
std::uint32_t last_texture_slot = 0;
std::uint64_t last_texture_bytes = 0;
std::uint64_t last_upload_bytes = 0; std::uint64_t last_upload_bytes = 0;
std::uint64_t last_readback_bytes = 0; std::uint64_t last_readback_bytes = 0;
std::uint64_t last_capture_bytes = 0; std::uint64_t last_capture_bytes = 0;
@@ -439,6 +463,12 @@ void validates_viewports_and_mesh_descriptors(pp::tests::Harness& h)
PP_EXPECT(h, bad_depth.code == StatusCode::out_of_range); PP_EXPECT(h, bad_depth.code == StatusCode::out_of_range);
PP_EXPECT(h, !empty_mesh.ok()); PP_EXPECT(h, !empty_mesh.ok());
PP_EXPECT(h, empty_mesh.code == StatusCode::invalid_argument); PP_EXPECT(h, empty_mesh.code == StatusCode::invalid_argument);
PP_EXPECT(h, validate_texture_slot(0).ok());
PP_EXPECT(h, validate_texture_slot(max_texture_slots - 1U).ok());
const auto invalid_slot = validate_texture_slot(max_texture_slots);
PP_EXPECT(h, !invalid_slot.ok());
PP_EXPECT(h, invalid_slot.code == StatusCode::out_of_range);
} }
void validates_shader_program_descriptors(pp::tests::Harness& h) void validates_shader_program_descriptors(pp::tests::Harness& h)
@@ -569,6 +599,7 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, context.begin_render_pass(target, ClearColor { .r = 0.1F, .g = 0.2F, .b = 0.3F, .a = 1.0F }).ok()); PP_EXPECT(h, context.begin_render_pass(target, ClearColor { .r = 0.1F, .g = 0.2F, .b = 0.3F, .a = 1.0F }).ok());
PP_EXPECT(h, context.set_viewport(Viewport { .x = 0, .y = 0, .width = 64, .height = 32 }).ok()); PP_EXPECT(h, context.set_viewport(Viewport { .x = 0, .y = 0, .width = 64, .height = 32 }).ok());
PP_EXPECT(h, context.bind_shader(shader).ok()); PP_EXPECT(h, context.bind_shader(shader).ok());
PP_EXPECT(h, context.bind_texture(2, texture).ok());
PP_EXPECT(h, context.bind_mesh(mesh).ok()); PP_EXPECT(h, context.bind_mesh(mesh).ok());
PP_EXPECT(h, context.draw().ok()); PP_EXPECT(h, context.draw().ok());
context.end_render_pass(); context.end_render_pass();
@@ -595,6 +626,8 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
BlitFilter::linear) BlitFilter::linear)
.ok()); .ok());
PP_EXPECT(h, device.context.shader_name == std::string_view("fake-shader")); PP_EXPECT(h, device.context.shader_name == std::string_view("fake-shader"));
PP_EXPECT(h, device.context.last_texture_slot == 2U);
PP_EXPECT(h, device.context.last_texture_bytes == 8192U);
PP_EXPECT(h, device.context.last_upload_bytes == 80U); PP_EXPECT(h, device.context.last_upload_bytes == 80U);
PP_EXPECT(h, device.context.last_readback_bytes == 80U); PP_EXPECT(h, device.context.last_readback_bytes == 80U);
PP_EXPECT(h, device.context.last_capture_bytes == 8192U); PP_EXPECT(h, device.context.last_capture_bytes == 8192U);
@@ -633,12 +666,13 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
PP_EXPECT(h, context.begin_render_pass(target, ClearColor { .r = 0.2F, .g = 0.3F, .b = 0.4F, .a = 1.0F }).ok()); PP_EXPECT(h, context.begin_render_pass(target, ClearColor { .r = 0.2F, .g = 0.3F, .b = 0.4F, .a = 1.0F }).ok());
PP_EXPECT(h, context.set_viewport(Viewport { .x = 0, .y = 0, .width = 64, .height = 32 }).ok()); PP_EXPECT(h, context.set_viewport(Viewport { .x = 0, .y = 0, .width = 64, .height = 32 }).ok());
PP_EXPECT(h, context.bind_shader(shader).ok()); PP_EXPECT(h, context.bind_shader(shader).ok());
PP_EXPECT(h, context.bind_texture(1, texture).ok());
PP_EXPECT(h, context.bind_mesh(mesh).ok()); PP_EXPECT(h, context.bind_mesh(mesh).ok());
PP_EXPECT(h, context.draw().ok()); PP_EXPECT(h, context.draw().ok());
context.end_render_pass(); context.end_render_pass();
const auto commands = device.commands(); const auto commands = device.commands();
PP_EXPECT(h, commands.size() == 7U); PP_EXPECT(h, commands.size() == 8U);
PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::trace_marker); PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::trace_marker);
PP_EXPECT(h, commands[0].component == std::string_view("renderer")); PP_EXPECT(h, commands[0].component == std::string_view("renderer"));
PP_EXPECT(h, commands[0].name == std::string_view("frame")); PP_EXPECT(h, commands[0].name == std::string_view("frame"));
@@ -649,11 +683,15 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
PP_EXPECT(h, commands[2].viewport.height == 32U); PP_EXPECT(h, commands[2].viewport.height == 32U);
PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::bind_shader); PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::bind_shader);
PP_EXPECT(h, commands[3].name == std::string_view("recorded-shader")); PP_EXPECT(h, commands[3].name == std::string_view("recorded-shader"));
PP_EXPECT(h, commands[4].kind == RecordedRenderCommandKind::bind_mesh); PP_EXPECT(h, commands[4].kind == RecordedRenderCommandKind::bind_texture);
PP_EXPECT(h, commands[4].mesh_desc.vertex_count == 3U); PP_EXPECT(h, commands[4].texture_slot == 1U);
PP_EXPECT(h, commands[5].kind == RecordedRenderCommandKind::draw); PP_EXPECT(h, commands[4].texture_desc.extent.height == 32U);
PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::end_render_pass); PP_EXPECT(h, recorded_render_command_kind_name(commands[4].kind) == std::string_view("bind_texture"));
PP_EXPECT(h, recorded_render_command_kind_name(commands[5].kind) == std::string_view("draw")); PP_EXPECT(h, commands[5].kind == RecordedRenderCommandKind::bind_mesh);
PP_EXPECT(h, commands[5].mesh_desc.vertex_count == 3U);
PP_EXPECT(h, commands[6].kind == RecordedRenderCommandKind::draw);
PP_EXPECT(h, commands[7].kind == RecordedRenderCommandKind::end_render_pass);
PP_EXPECT(h, recorded_render_command_kind_name(commands[6].kind) == std::string_view("draw"));
PP_EXPECT(h, context.upload_texture( PP_EXPECT(h, context.upload_texture(
texture, texture,
@@ -661,12 +699,12 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
upload_bytes) upload_bytes)
.ok()); .ok());
const auto commands_after_upload = device.commands(); const auto commands_after_upload = device.commands();
PP_EXPECT(h, commands_after_upload.size() == 8U); PP_EXPECT(h, commands_after_upload.size() == 9U);
PP_EXPECT(h, commands_after_upload[7].kind == RecordedRenderCommandKind::upload_texture); PP_EXPECT(h, commands_after_upload[8].kind == RecordedRenderCommandKind::upload_texture);
PP_EXPECT(h, commands_after_upload[7].texture_desc.extent.width == 64U); PP_EXPECT(h, commands_after_upload[8].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_upload[7].readback_region.x == 4U); PP_EXPECT(h, commands_after_upload[8].readback_region.x == 4U);
PP_EXPECT(h, commands_after_upload[7].upload_bytes == 96U); PP_EXPECT(h, commands_after_upload[8].upload_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[7].kind) == std::string_view("upload_texture")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[8].kind) == std::string_view("upload_texture"));
PP_EXPECT(h, context.read_texture( PP_EXPECT(h, context.read_texture(
texture, texture,
@@ -674,22 +712,22 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
readback_buffer) readback_buffer)
.ok()); .ok());
const auto commands_after_readback = device.commands(); const auto commands_after_readback = device.commands();
PP_EXPECT(h, commands_after_readback.size() == 9U); PP_EXPECT(h, commands_after_readback.size() == 10U);
PP_EXPECT(h, commands_after_readback[8].kind == RecordedRenderCommandKind::read_texture); PP_EXPECT(h, commands_after_readback[9].kind == RecordedRenderCommandKind::read_texture);
PP_EXPECT(h, commands_after_readback[8].texture_desc.extent.width == 64U); PP_EXPECT(h, commands_after_readback[9].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_readback[8].readback_region.x == 4U); PP_EXPECT(h, commands_after_readback[9].readback_region.x == 4U);
PP_EXPECT(h, commands_after_readback[8].readback_region.height == 3U); PP_EXPECT(h, commands_after_readback[9].readback_region.height == 3U);
PP_EXPECT(h, commands_after_readback[8].readback_bytes == 96U); PP_EXPECT(h, commands_after_readback[9].readback_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[8].kind) == std::string_view("read_texture")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[9].kind) == std::string_view("read_texture"));
PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok()); PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok());
const auto commands_after_capture = device.commands(); const auto commands_after_capture = device.commands();
PP_EXPECT(h, commands_after_capture.size() == 10U); PP_EXPECT(h, commands_after_capture.size() == 11U);
PP_EXPECT(h, commands_after_capture[9].kind == RecordedRenderCommandKind::capture_frame); PP_EXPECT(h, commands_after_capture[10].kind == RecordedRenderCommandKind::capture_frame);
PP_EXPECT(h, commands_after_capture[9].target_desc.extent.width == 64U); PP_EXPECT(h, commands_after_capture[10].target_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_capture[9].target_desc.extent.height == 32U); PP_EXPECT(h, commands_after_capture[10].target_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_capture[9].capture_bytes == 8192U); PP_EXPECT(h, commands_after_capture[10].capture_bytes == 8192U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[9].kind) == std::string_view("capture_frame")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[10].kind) == std::string_view("capture_frame"));
PP_EXPECT(h, context.blit_render_target( PP_EXPECT(h, context.blit_render_target(
target, target,
@@ -699,16 +737,16 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
BlitFilter::linear) BlitFilter::linear)
.ok()); .ok());
const auto commands_after_blit = device.commands(); const auto commands_after_blit = device.commands();
PP_EXPECT(h, commands_after_blit.size() == 11U); PP_EXPECT(h, commands_after_blit.size() == 12U);
PP_EXPECT(h, commands_after_blit[10].kind == RecordedRenderCommandKind::blit_render_target); PP_EXPECT(h, commands_after_blit[11].kind == RecordedRenderCommandKind::blit_render_target);
PP_EXPECT(h, commands_after_blit[10].source_desc.extent.width == 64U); PP_EXPECT(h, commands_after_blit[11].source_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_blit[10].destination_desc.extent.height == 32U); PP_EXPECT(h, commands_after_blit[11].destination_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_blit[10].source_region.width == 16U); PP_EXPECT(h, commands_after_blit[11].source_region.width == 16U);
PP_EXPECT(h, commands_after_blit[10].destination_region.x == 2U); PP_EXPECT(h, commands_after_blit[11].destination_region.x == 2U);
PP_EXPECT(h, commands_after_blit[10].blit_filter == BlitFilter::linear); PP_EXPECT(h, commands_after_blit[11].blit_filter == BlitFilter::linear);
PP_EXPECT(h, commands_after_blit[10].blit_source_bytes == 512U); PP_EXPECT(h, commands_after_blit[11].blit_source_bytes == 512U);
PP_EXPECT(h, commands_after_blit[10].blit_destination_bytes == 128U); PP_EXPECT(h, commands_after_blit[11].blit_destination_bytes == 128U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_blit[10].kind) == std::string_view("blit_render_target")); PP_EXPECT(h, recorded_render_command_kind_name(commands_after_blit[11].kind) == std::string_view("blit_render_target"));
device.clear(); device.clear();
PP_EXPECT(h, device.commands().empty()); PP_EXPECT(h, device.commands().empty());
@@ -792,6 +830,11 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, draw_without_bindings.code == StatusCode::invalid_argument); PP_EXPECT(h, draw_without_bindings.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.bind_shader(shader).ok()); PP_EXPECT(h, context.bind_shader(shader).ok());
const auto bind_texture_bad_slot = context.bind_texture(max_texture_slots, texture);
PP_EXPECT(h, !bind_texture_bad_slot.ok());
PP_EXPECT(h, bind_texture_bad_slot.code == StatusCode::out_of_range);
PP_EXPECT(h, context.bind_texture(0, texture).ok());
const auto draw_without_mesh = context.draw(); const auto draw_without_mesh = context.draw();
PP_EXPECT(h, !draw_without_mesh.ok()); PP_EXPECT(h, !draw_without_mesh.ok());
PP_EXPECT(h, draw_without_mesh.code == StatusCode::invalid_argument); PP_EXPECT(h, draw_without_mesh.code == StatusCode::invalid_argument);
@@ -808,6 +851,10 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !viewport_after_end.ok()); PP_EXPECT(h, !viewport_after_end.ok());
PP_EXPECT(h, viewport_after_end.code == StatusCode::invalid_argument); PP_EXPECT(h, viewport_after_end.code == StatusCode::invalid_argument);
const auto bind_texture_after_end = context.bind_texture(0, texture);
PP_EXPECT(h, !bind_texture_after_end.ok());
PP_EXPECT(h, bind_texture_after_end.code == StatusCode::invalid_argument);
const auto read_outside_bounds = context.read_texture( const auto read_outside_bounds = context.read_texture(
texture, texture,
ReadbackRegion { .x = 31, .y = 15, .width = 2, .height = 1 }, ReadbackRegion { .x = 31, .y = 15, .width = 2, .height = 1 },

View File

@@ -2273,6 +2273,7 @@ int record_render(int argc, char** argv)
} }
const auto shader_status = context.bind_shader(shader); const auto shader_status = context.bind_shader(shader);
const auto bind_texture_status = context.bind_texture(0, texture);
const auto mesh_status = context.bind_mesh(mesh); const auto mesh_status = context.bind_mesh(mesh);
const auto draw_status = context.draw(); const auto draw_status = context.draw();
context.end_render_pass(); context.end_render_pass();
@@ -2281,6 +2282,10 @@ int record_render(int argc, char** argv)
print_error("record-render", shader_status.message); print_error("record-render", shader_status.message);
return 2; return 2;
} }
if (!bind_texture_status.ok()) {
print_error("record-render", bind_texture_status.message);
return 2;
}
if (!mesh_status.ok()) { if (!mesh_status.ok()) {
print_error("record-render", mesh_status.message); print_error("record-render", mesh_status.message);
return 2; return 2;
@@ -2332,12 +2337,14 @@ int record_render(int argc, char** argv)
} }
std::size_t draw_commands = 0; std::size_t draw_commands = 0;
std::size_t bind_texture_commands = 0;
std::size_t upload_commands = 0; std::size_t upload_commands = 0;
std::size_t readback_commands = 0; std::size_t readback_commands = 0;
std::size_t capture_commands = 0; std::size_t capture_commands = 0;
std::size_t blit_commands = 0; std::size_t blit_commands = 0;
std::size_t trace_markers = 0; std::size_t trace_markers = 0;
std::uint64_t upload_bytes = 0; std::uint64_t upload_bytes = 0;
std::uint64_t bound_texture_bytes = 0;
std::uint64_t readback_bytes = 0; std::uint64_t readback_bytes = 0;
std::uint64_t capture_bytes = 0; std::uint64_t capture_bytes = 0;
std::uint64_t blit_source_bytes = 0; std::uint64_t blit_source_bytes = 0;
@@ -2346,6 +2353,12 @@ int record_render(int argc, char** argv)
for (const auto& command : commands) { for (const auto& command : commands) {
if (command.kind == pp::renderer::RecordedRenderCommandKind::draw) { if (command.kind == pp::renderer::RecordedRenderCommandKind::draw) {
++draw_commands; ++draw_commands;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::bind_texture) {
++bind_texture_commands;
const auto bound_bytes = pp::renderer::texture_byte_size(command.texture_desc);
if (bound_bytes.ok()) {
bound_texture_bytes += bound_bytes.value();
}
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::upload_texture) { } else if (command.kind == pp::renderer::RecordedRenderCommandKind::upload_texture) {
++upload_commands; ++upload_commands;
upload_bytes += command.upload_bytes; upload_bytes += command.upload_bytes;
@@ -2371,6 +2384,8 @@ int record_render(int argc, char** argv)
<< ",\"format\":\"rgba8\"}" << ",\"format\":\"rgba8\"}"
<< ",\"commands\":" << commands.size() << ",\"commands\":" << commands.size()
<< ",\"drawCommands\":" << draw_commands << ",\"drawCommands\":" << draw_commands
<< ",\"bindTextureCommands\":" << bind_texture_commands
<< ",\"boundTextureBytes\":" << bound_texture_bytes
<< ",\"uploadCommands\":" << upload_commands << ",\"uploadCommands\":" << upload_commands
<< ",\"uploadBytes\":" << upload_bytes << ",\"uploadBytes\":" << upload_bytes
<< ",\"readbackCommands\":" << readback_commands << ",\"readbackCommands\":" << readback_commands