Add renderer texture copy command

This commit is contained in:
2026-06-02 16:35:38 +02:00
parent 483bbb4a9c
commit 75dd5cfdc9
9 changed files with 286 additions and 46 deletions

View File

@@ -288,18 +288,18 @@ Known local toolchain state:
backend-owned resource creation, command order, render-pass color/depth/
stencil clear intent, scissor state, depth state, blend state, texture-slot
binding, sampler-state binding, texture-upload byte counts,
shader-uniform writes, explicit draw descriptor ranges, readback bounds,
frame-capture sources, destination buffer sizes, and render-target blit
regions, records
shader-uniform writes, explicit draw descriptor ranges, texture-copy regions,
readback bounds, frame-capture sources, destination buffer sizes, and
render-target blit regions, records
render-pass-clear/scissor/depth/blend/shader-uniform/texture-bind/
sampler-bind/draw/upload/readback/frame-capture/blit commands, draw mesh
inputs, explicit draw ranges, and records trace markers without a window or
GL context.
sampler-bind/draw/upload/texture-copy/readback/frame-capture/blit commands,
draw mesh inputs, explicit draw ranges, and records trace markers without a
window or GL context.
- `pano_cli record-render` exposes the recording renderer through JSON
automation, including render-pass/depth-clear counts, scissor/depth/blend/
shader-uniform/texture-bind/sampler-bind/upload/readback/frame-capture/blit
command and byte totals, backend resource creation counts, plus draw
descriptor vertex/index totals, and is covered by
shader-uniform/texture-bind/sampler-bind/upload/texture-copy/readback/
frame-capture/blit command and byte totals, backend resource creation counts,
plus draw descriptor vertex/index totals, and is covered by
`pano_cli_record_render_smoke` plus
`pano_cli_record_render_rejects_oversized_target`.
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`

View File

@@ -726,10 +726,11 @@ Results:
validation, blend-state validation, scissor-state validation,
render-pass color/depth/stencil clear validation, shader-uniform write
validation, draw descriptor/range validation, backend-neutral resource
factory validation, recording
factory validation, texture-copy validation, recording
render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/
upload/readback/frame-capture/blit command capture, draw mesh-input capture,
explicit draw-range capture, and invalid catalog rejection.
upload/texture-copy/readback/frame-capture/blit command capture, draw
mesh-input capture, explicit draw-range capture, and invalid catalog
rejection.
- `pp_paint_renderer_compositor_tests` passed.
- `pp_ui_core_color_tests` passed.
- `pp_ui_core_layout_value_tests` passed.
@@ -826,20 +827,21 @@ Results:
renderer-owned resource factory and
command-order/render-pass-clear/scissor-state/depth-state/blend-state/
texture-bind/sampler-bind/shader-uniform/texture-upload/readback/
frame-capture/blit validation plus explicit draw descriptor validation; it
creates validated textures, render targets, shaders, meshes, and readback
buffers, then records commands, trace markers, render-pass color/depth/
stencil clear intent, scissor state, depth state, blend state, shader uniform
writes, texture/sampler binds, draw mesh inputs, explicit draw ranges,
uploads/readbacks, frame captures, and render-target blits, giving automation
a backend-neutral render path that does not require a window or GL context.
frame-capture/blit validation plus explicit draw descriptor and texture-copy
validation; it creates validated textures, render targets, shaders, meshes,
and readback buffers, then records commands, trace markers, render-pass
color/depth/stencil clear intent, scissor state, depth state, blend state,
shader uniform writes, texture/sampler binds, draw mesh inputs, explicit draw
ranges, texture uploads/copies/readbacks, frame captures, and render-target
blits, giving automation 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
JSON command counts, resource creation counts, target dimensions, backend
name, trace/draw summary, render-pass/depth-clear counts, and draw
descriptor vertex/index totals, scissor/depth/blend-state plus
shader-uniform/texture/sampler-bind/upload/readback/frame-capture/blit
command/byte totals for agent automation, with an expected-failure smoke for oversized
render/readback targets.
shader-uniform/texture/sampler-bind/upload/texture-copy/readback/
frame-capture/blit command/byte totals for agent automation, with an
expected-failure smoke for oversized render/readback targets.
- `pano_cli simulate-document-history` exercises pure document history
apply/undo/redo behavior and emits JSON layer/frame/history state for agent
automation.

View File

@@ -397,6 +397,49 @@ pp::foundation::Status RecordingCommandContext::upload_texture(
return pp::foundation::Status::success();
}
pp::foundation::Status RecordingCommandContext::copy_texture(
ITexture2D& source,
ReadbackRegion source_region,
ITexture2D& destination,
ReadbackRegion destination_region) noexcept
{
if (in_render_pass_) {
return pp::foundation::Status::invalid_argument("texture copy must be outside a render pass");
}
const auto source_desc = source.desc();
const auto destination_desc = destination.desc();
const auto desc_status = validate_texture_copy_descs(
source_desc,
source_region,
destination_desc,
destination_region);
if (!desc_status.ok()) {
return desc_status;
}
const auto source_bytes = readback_byte_size(source_desc, source_region);
if (!source_bytes.ok()) {
return source_bytes.status();
}
const auto destination_bytes = readback_byte_size(destination_desc, destination_region);
if (!destination_bytes.ok()) {
return destination_bytes.status();
}
push_command(commands_, RecordedRenderCommand {
.kind = RecordedRenderCommandKind::copy_texture,
.source_desc = source_desc,
.destination_desc = destination_desc,
.source_region = source_region,
.destination_region = destination_region,
.copy_source_bytes = source_bytes.value(),
.copy_destination_bytes = destination_bytes.value(),
});
return pp::foundation::Status::success();
}
pp::foundation::Status RecordingCommandContext::capture_frame(
IRenderTarget& target,
IReadbackBuffer& destination) noexcept
@@ -626,6 +669,8 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no
return "draw";
case RecordedRenderCommandKind::upload_texture:
return "upload_texture";
case RecordedRenderCommandKind::copy_texture:
return "copy_texture";
case RecordedRenderCommandKind::read_texture:
return "read_texture";
case RecordedRenderCommandKind::capture_frame:

View File

@@ -20,6 +20,7 @@ enum class RecordedRenderCommandKind : std::uint8_t {
bind_mesh,
draw,
upload_texture,
copy_texture,
read_texture,
capture_frame,
blit_render_target,
@@ -53,6 +54,8 @@ struct RecordedRenderCommand {
ReadbackRegion destination_region {};
BlitFilter blit_filter = BlitFilter::nearest;
std::uint64_t upload_bytes = 0;
std::uint64_t copy_source_bytes = 0;
std::uint64_t copy_destination_bytes = 0;
std::uint64_t readback_bytes = 0;
std::uint64_t capture_bytes = 0;
std::uint64_t blit_source_bytes = 0;
@@ -138,6 +141,11 @@ public:
ITexture2D& texture,
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept override;
[[nodiscard]] pp::foundation::Status copy_texture(
ITexture2D& source,
ReadbackRegion source_region,
ITexture2D& destination,
ReadbackRegion destination_region) noexcept override;
[[nodiscard]] pp::foundation::Status capture_frame(
IRenderTarget& target,
IReadbackBuffer& destination) noexcept override;

View File

@@ -489,6 +489,28 @@ pp::foundation::Result<std::uint64_t> frame_capture_byte_size(TextureDesc desc)
return texture_byte_size(desc);
}
pp::foundation::Status validate_texture_copy_descs(
TextureDesc source,
ReadbackRegion source_region,
TextureDesc destination,
ReadbackRegion destination_region) noexcept
{
if (source.format != destination.format) {
return pp::foundation::Status::invalid_argument("texture copy endpoints must use matching formats");
}
if (source_region.width != destination_region.width || source_region.height != destination_region.height) {
return pp::foundation::Status::invalid_argument("texture copy regions must have matching dimensions");
}
const auto source_status = validate_readback_region(source, source_region);
if (!source_status.ok()) {
return source_status;
}
return validate_readback_region(destination, destination_region);
}
pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept
{
switch (filter) {

View File

@@ -243,6 +243,11 @@ public:
ITexture2D& texture,
ReadbackRegion region,
std::span<const std::byte> rgba_or_channel_bytes) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status copy_texture(
ITexture2D& source,
ReadbackRegion source_region,
ITexture2D& destination,
ReadbackRegion destination_region) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status capture_frame(
IRenderTarget& target,
IReadbackBuffer& destination) noexcept = 0;
@@ -299,6 +304,11 @@ public:
ReadbackRegion region) noexcept;
[[nodiscard]] pp::foundation::Result<std::uint64_t> frame_capture_byte_size(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_readback_region(TextureDesc desc, ReadbackRegion region) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_copy_descs(
TextureDesc source,
ReadbackRegion source_region,
TextureDesc destination,
ReadbackRegion destination_region) noexcept;
[[nodiscard]] pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept;
[[nodiscard]] pp::foundation::Status validate_blit_descs(
TextureDesc source,

View File

@@ -365,7 +365,7 @@ if(TARGET pano_cli)
COMMAND pano_cli record-render --width 32 --height 16)
set_tests_properties(pano_cli_record_render_smoke PROPERTIES
LABELS "renderer;integration;desktop-fast"
PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"createdResources\":6.*\"commands\":17.*\"renderPasses\":1.*\"depthClears\":1.*\"stencilClears\":0.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"uniformCommands\":1.*\"uniformBytes\":64.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"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.*\"createdResources\":6.*\"commands\":18.*\"renderPasses\":1.*\"depthClears\":1.*\"stencilClears\":0.*\"drawCommands\":1.*\"drawVertices\":3.*\"drawIndices\":3.*\"scissorCommands\":1.*\"blendCommands\":1.*\"depthCommands\":1.*\"uniformCommands\":1.*\"uniformBytes\":64.*\"bindTextureCommands\":1.*\"bindSamplerCommands\":1.*\"boundTextureBytes\":2048.*\"uploadCommands\":1.*\"uploadBytes\":4.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048")
add_test(NAME pano_cli_record_render_rejects_oversized_target
COMMAND "${CMAKE_COMMAND}"

View File

@@ -84,6 +84,7 @@ using pp::renderer::validate_scissor;
using pp::renderer::validate_shader_catalog;
using pp::renderer::validate_shader_program_desc;
using pp::renderer::validate_shader_uniform_write;
using pp::renderer::validate_texture_copy_descs;
using pp::renderer::validate_texture_slot;
using pp::renderer::validate_viewport;
@@ -380,6 +381,36 @@ public:
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status copy_texture(
pp::renderer::ITexture2D& source,
ReadbackRegion source_region,
pp::renderer::ITexture2D& destination,
ReadbackRegion destination_region) noexcept override
{
const auto status = validate_texture_copy_descs(
source.desc(),
source_region,
destination.desc(),
destination_region);
if (!status.ok()) {
return status;
}
const auto source_bytes = readback_byte_size(source.desc(), source_region);
if (!source_bytes.ok()) {
return source_bytes.status();
}
const auto destination_bytes = readback_byte_size(destination.desc(), destination_region);
if (!destination_bytes.ok()) {
return destination_bytes.status();
}
last_copy_source_bytes = source_bytes.value();
last_copy_destination_bytes = destination_bytes.value();
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status capture_frame(
IRenderTarget& target,
pp::renderer::IReadbackBuffer& destination) noexcept override
@@ -450,6 +481,8 @@ public:
std::uint32_t last_sampler_slot = 0;
SamplerDesc last_sampler_desc {};
std::uint64_t last_upload_bytes = 0;
std::uint64_t last_copy_source_bytes = 0;
std::uint64_t last_copy_destination_bytes = 0;
std::uint64_t last_readback_bytes = 0;
std::uint64_t last_capture_bytes = 0;
std::uint64_t last_blit_source_bytes = 0;
@@ -698,6 +731,48 @@ void validates_blit_contract(pp::tests::Harness& h)
PP_EXPECT(h, blit_filter_name(static_cast<BlitFilter>(255)) == std::string_view("unknown"));
}
void validates_texture_copy_contract(pp::tests::Harness& h)
{
const TextureDesc rgba_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::rgba8,
};
const TextureDesc r8_desc {
.extent = Extent2D { .width = 16, .height = 8 },
.format = TextureFormat::r8,
};
PP_EXPECT(h, validate_texture_copy_descs(
rgba_desc,
ReadbackRegion { .x = 1, .y = 2, .width = 4, .height = 3 },
rgba_desc,
ReadbackRegion { .x = 5, .y = 4, .width = 4, .height = 3 })
.ok());
const auto mismatched_format = validate_texture_copy_descs(
rgba_desc,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
r8_desc,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 });
const auto mismatched_region = validate_texture_copy_descs(
rgba_desc,
ReadbackRegion { .x = 0, .y = 0, .width = 2, .height = 1 },
rgba_desc,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 });
const auto outside_source = validate_texture_copy_descs(
rgba_desc,
ReadbackRegion { .x = 15, .y = 0, .width = 2, .height = 1 },
rgba_desc,
ReadbackRegion { .x = 0, .y = 0, .width = 2, .height = 1 });
PP_EXPECT(h, !mismatched_format.ok());
PP_EXPECT(h, mismatched_format.code == StatusCode::invalid_argument);
PP_EXPECT(h, !mismatched_region.ok());
PP_EXPECT(h, mismatched_region.code == StatusCode::invalid_argument);
PP_EXPECT(h, !outside_source.ok());
PP_EXPECT(h, outside_source.code == StatusCode::out_of_range);
}
void validates_blend_contract(pp::tests::Harness& h)
{
const BlendState alpha_blend {
@@ -1105,6 +1180,12 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 },
upload_bytes)
.ok());
PP_EXPECT(h, context.copy_texture(
texture,
ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 },
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 4, .height = 5 })
.ok());
PP_EXPECT(h, context.read_texture(
texture,
ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 },
@@ -1141,6 +1222,8 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, device.context.last_sampler_desc.mag_filter == SamplerFilter::nearest);
PP_EXPECT(h, device.context.last_sampler_desc.address_u == SamplerAddressMode::repeat);
PP_EXPECT(h, device.context.last_upload_bytes == 80U);
PP_EXPECT(h, device.context.last_copy_source_bytes == 80U);
PP_EXPECT(h, device.context.last_copy_destination_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_blit_source_bytes == 80U);
@@ -1390,28 +1473,43 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
PP_EXPECT(h, commands_after_upload[12].upload_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_upload[12].kind) == std::string_view("upload_texture"));
PP_EXPECT(h, context.copy_texture(
texture,
ReadbackRegion { .x = 4, .y = 5, .width = 8, .height = 3 },
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 8, .height = 3 })
.ok());
const auto commands_after_copy = device.commands();
PP_EXPECT(h, commands_after_copy.size() == 14U);
PP_EXPECT(h, commands_after_copy[13].kind == RecordedRenderCommandKind::copy_texture);
PP_EXPECT(h, commands_after_copy[13].source_region.x == 4U);
PP_EXPECT(h, commands_after_copy[13].destination_region.x == 0U);
PP_EXPECT(h, commands_after_copy[13].copy_source_bytes == 96U);
PP_EXPECT(h, commands_after_copy[13].copy_destination_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_copy[13].kind) == std::string_view("copy_texture"));
PP_EXPECT(h, context.read_texture(
texture,
ReadbackRegion { .x = 4, .y = 5, .width = 8, .height = 3 },
readback_buffer)
.ok());
const auto commands_after_readback = device.commands();
PP_EXPECT(h, commands_after_readback.size() == 14U);
PP_EXPECT(h, commands_after_readback[13].kind == RecordedRenderCommandKind::read_texture);
PP_EXPECT(h, commands_after_readback[13].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_readback[13].readback_region.x == 4U);
PP_EXPECT(h, commands_after_readback[13].readback_region.height == 3U);
PP_EXPECT(h, commands_after_readback[13].readback_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[13].kind) == std::string_view("read_texture"));
PP_EXPECT(h, commands_after_readback.size() == 15U);
PP_EXPECT(h, commands_after_readback[14].kind == RecordedRenderCommandKind::read_texture);
PP_EXPECT(h, commands_after_readback[14].texture_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_readback[14].readback_region.x == 4U);
PP_EXPECT(h, commands_after_readback[14].readback_region.height == 3U);
PP_EXPECT(h, commands_after_readback[14].readback_bytes == 96U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_readback[14].kind) == std::string_view("read_texture"));
PP_EXPECT(h, context.capture_frame(target, readback_buffer).ok());
const auto commands_after_capture = device.commands();
PP_EXPECT(h, commands_after_capture.size() == 15U);
PP_EXPECT(h, commands_after_capture[14].kind == RecordedRenderCommandKind::capture_frame);
PP_EXPECT(h, commands_after_capture[14].target_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_capture[14].target_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_capture[14].capture_bytes == 8192U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[14].kind) == std::string_view("capture_frame"));
PP_EXPECT(h, commands_after_capture.size() == 16U);
PP_EXPECT(h, commands_after_capture[15].kind == RecordedRenderCommandKind::capture_frame);
PP_EXPECT(h, commands_after_capture[15].target_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_capture[15].target_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_capture[15].capture_bytes == 8192U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_capture[15].kind) == std::string_view("capture_frame"));
PP_EXPECT(h, context.blit_render_target(
target,
@@ -1421,16 +1519,16 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
BlitFilter::linear)
.ok());
const auto commands_after_blit = device.commands();
PP_EXPECT(h, commands_after_blit.size() == 16U);
PP_EXPECT(h, commands_after_blit[15].kind == RecordedRenderCommandKind::blit_render_target);
PP_EXPECT(h, commands_after_blit[15].source_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_blit[15].destination_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_blit[15].source_region.width == 16U);
PP_EXPECT(h, commands_after_blit[15].destination_region.x == 2U);
PP_EXPECT(h, commands_after_blit[15].blit_filter == BlitFilter::linear);
PP_EXPECT(h, commands_after_blit[15].blit_source_bytes == 512U);
PP_EXPECT(h, commands_after_blit[15].blit_destination_bytes == 128U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_blit[15].kind) == std::string_view("blit_render_target"));
PP_EXPECT(h, commands_after_blit.size() == 17U);
PP_EXPECT(h, commands_after_blit[16].kind == RecordedRenderCommandKind::blit_render_target);
PP_EXPECT(h, commands_after_blit[16].source_desc.extent.width == 64U);
PP_EXPECT(h, commands_after_blit[16].destination_desc.extent.height == 32U);
PP_EXPECT(h, commands_after_blit[16].source_region.width == 16U);
PP_EXPECT(h, commands_after_blit[16].destination_region.x == 2U);
PP_EXPECT(h, commands_after_blit[16].blit_filter == BlitFilter::linear);
PP_EXPECT(h, commands_after_blit[16].blit_source_bytes == 512U);
PP_EXPECT(h, commands_after_blit[16].blit_destination_bytes == 128U);
PP_EXPECT(h, recorded_render_command_kind_name(commands_after_blit[16].kind) == std::string_view("blit_render_target"));
device.clear();
PP_EXPECT(h, device.commands().empty());
@@ -1517,6 +1615,14 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !read_during_render_pass.ok());
PP_EXPECT(h, read_during_render_pass.code == StatusCode::invalid_argument);
const auto copy_during_render_pass = context.copy_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 });
PP_EXPECT(h, !copy_during_render_pass.ok());
PP_EXPECT(h, copy_during_render_pass.code == StatusCode::invalid_argument);
const auto capture_during_render_pass = context.capture_frame(target, full_readback_buffer);
PP_EXPECT(h, !capture_during_render_pass.ok());
PP_EXPECT(h, capture_during_render_pass.code == StatusCode::invalid_argument);
@@ -1653,6 +1759,22 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, !upload_outside_bounds.ok());
PP_EXPECT(h, upload_outside_bounds.code == StatusCode::out_of_range);
const auto copy_mismatched_regions = context.copy_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 2, .height = 1 },
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 });
PP_EXPECT(h, !copy_mismatched_regions.ok());
PP_EXPECT(h, copy_mismatched_regions.code == StatusCode::invalid_argument);
const auto copy_outside_bounds = context.copy_texture(
texture,
ReadbackRegion { .x = 31, .y = 15, .width = 2, .height = 1 },
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 2, .height = 1 });
PP_EXPECT(h, !copy_outside_bounds.ok());
PP_EXPECT(h, copy_outside_bounds.code == StatusCode::out_of_range);
const auto upload_with_wrong_size = context.upload_texture(
texture,
ReadbackRegion { .x = 0, .y = 0, .width = 1, .height = 1 },
@@ -1723,6 +1845,7 @@ int main()
harness.run("computes_readback_byte_sizes", computes_readback_byte_sizes);
harness.run("computes_frame_capture_byte_sizes", computes_frame_capture_byte_sizes);
harness.run("validates_blit_contract", validates_blit_contract);
harness.run("validates_texture_copy_contract", validates_texture_copy_contract);
harness.run("validates_blend_contract", validates_blend_contract);
harness.run("validates_depth_contract", validates_depth_contract);
harness.run("validates_sampler_contract", validates_sampler_contract);

View File

@@ -2403,6 +2403,26 @@ int record_render(int argc, char** argv)
return 2;
}
const auto copy_status = context.copy_texture(
*texture.value(),
pp::renderer::ReadbackRegion {
.x = 0,
.y = 0,
.width = args.width,
.height = args.height,
},
*texture.value(),
pp::renderer::ReadbackRegion {
.x = 0,
.y = 0,
.width = args.width,
.height = args.height,
});
if (!copy_status.ok()) {
print_error("record-render", copy_status.message);
return 2;
}
const auto capture_status = context.capture_frame(*target.value(), *readback_buffer.value());
if (!capture_status.ok()) {
print_error("record-render", capture_status.message);
@@ -2438,6 +2458,7 @@ int record_render(int argc, char** argv)
std::size_t bind_texture_commands = 0;
std::size_t bind_sampler_commands = 0;
std::size_t upload_commands = 0;
std::size_t copy_commands = 0;
std::size_t readback_commands = 0;
std::size_t capture_commands = 0;
std::size_t blit_commands = 0;
@@ -2449,6 +2470,8 @@ int record_render(int argc, char** argv)
std::uint64_t draw_indices = 0;
std::uint64_t uniform_bytes = 0;
std::uint64_t upload_bytes = 0;
std::uint64_t copy_source_bytes = 0;
std::uint64_t copy_destination_bytes = 0;
std::uint64_t bound_texture_bytes = 0;
std::uint64_t readback_bytes = 0;
std::uint64_t capture_bytes = 0;
@@ -2488,6 +2511,10 @@ int record_render(int argc, char** argv)
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::upload_texture) {
++upload_commands;
upload_bytes += command.upload_bytes;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::copy_texture) {
++copy_commands;
copy_source_bytes += command.copy_source_bytes;
copy_destination_bytes += command.copy_destination_bytes;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::read_texture) {
++readback_commands;
readback_bytes += command.readback_bytes;
@@ -2526,6 +2553,9 @@ int record_render(int argc, char** argv)
<< ",\"boundTextureBytes\":" << bound_texture_bytes
<< ",\"uploadCommands\":" << upload_commands
<< ",\"uploadBytes\":" << upload_bytes
<< ",\"copyCommands\":" << copy_commands
<< ",\"copySourceBytes\":" << copy_source_bytes
<< ",\"copyDestinationBytes\":" << copy_destination_bytes
<< ",\"readbackCommands\":" << readback_commands
<< ",\"readbackBytes\":" << readback_bytes
<< ",\"captureCommands\":" << capture_commands