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

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