Add renderer texture transition contract

This commit is contained in:
2026-06-02 17:13:44 +02:00
parent 56cb9eaacb
commit 18617cdbd2
9 changed files with 432 additions and 8 deletions

View File

@@ -54,6 +54,7 @@ using pp::renderer::ShaderProgramDesc;
using pp::renderer::ShaderStageSource;
using pp::renderer::TextureDesc;
using pp::renderer::TextureFormat;
using pp::renderer::TextureState;
using pp::renderer::TextureUsage;
using pp::renderer::Viewport;
using pp::renderer::max_shader_source_bytes;
@@ -70,6 +71,7 @@ using pp::renderer::recorded_render_command_kind_name;
using pp::renderer::ShaderCatalogEntry;
using pp::renderer::texture_byte_size;
using pp::renderer::texture_format_name;
using pp::renderer::texture_state_name;
using pp::renderer::validate_extent;
using pp::renderer::validate_blit_descs;
using pp::renderer::validate_blit_filter;
@@ -94,6 +96,8 @@ using pp::renderer::validate_shader_uniform_write;
using pp::renderer::validate_texture_copy_descs;
using pp::renderer::validate_texture_desc;
using pp::renderer::validate_texture_slot;
using pp::renderer::validate_texture_state;
using pp::renderer::validate_texture_transition_desc;
using pp::renderer::validate_texture_usage;
using pp::renderer::validate_trace_label;
using pp::renderer::validate_viewport;
@@ -457,6 +461,21 @@ public:
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status transition_texture(
pp::renderer::ITexture2D& texture,
TextureState before,
TextureState after) noexcept override
{
const auto status = validate_texture_transition_desc(texture.desc(), before, after);
if (!status.ok()) {
return status;
}
last_transition_before = before;
last_transition_after = after;
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status copy_texture(
pp::renderer::ITexture2D& source,
ReadbackRegion source_region,
@@ -559,6 +578,8 @@ public:
std::uint64_t last_upload_bytes = 0;
std::uint32_t last_generated_mip_levels = 0;
std::uint64_t last_generated_mip_bytes = 0;
TextureState last_transition_before = TextureState::undefined;
TextureState last_transition_after = TextureState::undefined;
std::uint64_t last_copy_source_bytes = 0;
std::uint64_t last_copy_destination_bytes = 0;
std::uint64_t last_readback_bytes = 0;
@@ -852,6 +873,133 @@ void validates_mipmap_generation_contract(pp::tests::Harness& h)
PP_EXPECT(h, missing_copy_destination.code == StatusCode::invalid_argument);
}
void validates_texture_transition_contract(pp::tests::Harness& h)
{
const TextureDesc full_usage_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc missing_sampled_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::upload_destination | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc missing_render_target_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc missing_upload_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc missing_copy_source_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::copy_destination,
};
const TextureDesc missing_copy_destination_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::copy_source,
};
const TextureDesc missing_readback_desc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
};
PP_EXPECT(h, validate_texture_state(TextureState::present).ok());
PP_EXPECT(h, validate_texture_transition_desc(
full_usage_desc,
TextureState::undefined,
TextureState::upload_destination)
.ok());
PP_EXPECT(h, validate_texture_transition_desc(
full_usage_desc,
TextureState::upload_destination,
TextureState::shader_read)
.ok());
PP_EXPECT(h, validate_texture_transition_desc(
full_usage_desc,
TextureState::shader_read,
TextureState::render_target)
.ok());
PP_EXPECT(h, validate_texture_transition_desc(
full_usage_desc,
TextureState::copy_destination,
TextureState::readback_source)
.ok());
PP_EXPECT(h, validate_texture_transition_desc(
full_usage_desc,
TextureState::render_target,
TextureState::present)
.ok());
PP_EXPECT(h, texture_state_name(TextureState::copy_destination) == std::string_view("copy_destination"));
const auto invalid_state = validate_texture_state(static_cast<TextureState>(255));
const auto same_state = validate_texture_transition_desc(
full_usage_desc,
TextureState::shader_read,
TextureState::shader_read);
const auto undefined_destination = validate_texture_transition_desc(
full_usage_desc,
TextureState::shader_read,
TextureState::undefined);
const auto invalid_before = validate_texture_transition_desc(
full_usage_desc,
static_cast<TextureState>(255),
TextureState::shader_read);
const auto missing_sampled = validate_texture_transition_desc(
missing_sampled_desc,
TextureState::copy_destination,
TextureState::shader_read);
const auto missing_render_target = validate_texture_transition_desc(
missing_render_target_desc,
TextureState::shader_read,
TextureState::render_target);
const auto missing_upload = validate_texture_transition_desc(
missing_upload_desc,
TextureState::undefined,
TextureState::upload_destination);
const auto missing_copy_source = validate_texture_transition_desc(
missing_copy_source_desc,
TextureState::shader_read,
TextureState::copy_source);
const auto missing_copy_destination = validate_texture_transition_desc(
missing_copy_destination_desc,
TextureState::shader_read,
TextureState::copy_destination);
const auto missing_readback = validate_texture_transition_desc(
missing_readback_desc,
TextureState::copy_source,
TextureState::readback_source);
PP_EXPECT(h, !invalid_state.ok());
PP_EXPECT(h, invalid_state.code == StatusCode::invalid_argument);
PP_EXPECT(h, !same_state.ok());
PP_EXPECT(h, same_state.code == StatusCode::invalid_argument);
PP_EXPECT(h, !undefined_destination.ok());
PP_EXPECT(h, undefined_destination.code == StatusCode::invalid_argument);
PP_EXPECT(h, !invalid_before.ok());
PP_EXPECT(h, invalid_before.code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_sampled.ok());
PP_EXPECT(h, missing_sampled.code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_render_target.ok());
PP_EXPECT(h, missing_render_target.code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_upload.ok());
PP_EXPECT(h, missing_upload.code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_copy_source.ok());
PP_EXPECT(h, missing_copy_source.code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_copy_destination.ok());
PP_EXPECT(h, missing_copy_destination.code == StatusCode::invalid_argument);
PP_EXPECT(h, !missing_readback.ok());
PP_EXPECT(h, missing_readback.code == StatusCode::invalid_argument);
PP_EXPECT(h, texture_state_name(static_cast<TextureState>(255)) == std::string_view("unknown"));
}
void rejects_invalid_or_excessive_extents(pp::tests::Harness& h)
{
const auto zero = validate_extent(Extent2D { .width = 0, .height = 1 });
@@ -1455,6 +1603,11 @@ 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.transition_texture(
texture,
TextureState::upload_destination,
TextureState::shader_read)
.ok());
PP_EXPECT(h, context.copy_texture(
texture,
ReadbackRegion { .x = 2, .y = 3, .width = 4, .height = 5 },
@@ -1497,6 +1650,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_transition_before == TextureState::upload_destination);
PP_EXPECT(h, device.context.last_transition_after == TextureState::shader_read);
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);
@@ -1665,6 +1820,72 @@ void recording_renderer_records_mipmap_generation(pp::tests::Harness& h)
context.end_render_pass();
}
void recording_renderer_records_texture_transitions(pp::tests::Harness& h)
{
RecordingRenderDevice device;
RecordingTexture2D texture(TextureDesc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::render_target | TextureUsage::sampled | TextureUsage::upload_destination | TextureUsage::readback_source | TextureUsage::copy_source | TextureUsage::copy_destination,
.debug_name = "transition-texture",
});
RecordingTexture2D texture_without_sampled(TextureDesc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = TextureUsage::upload_destination | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 8, .height = 4 },
.format = TextureFormat::rgba8,
.usage = all_texture_usages,
});
auto& context = device.immediate_context();
PP_EXPECT(h, context.transition_texture(
texture,
TextureState::undefined,
TextureState::upload_destination)
.ok());
PP_EXPECT(h, context.transition_texture(
texture,
TextureState::upload_destination,
TextureState::shader_read)
.ok());
const auto commands = device.commands();
PP_EXPECT(h, commands.size() == 2U);
PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::transition_texture);
PP_EXPECT(h, commands[0].texture_desc.debug_name == std::string_view("transition-texture"));
PP_EXPECT(h, commands[0].before_state == TextureState::undefined);
PP_EXPECT(h, commands[0].after_state == TextureState::upload_destination);
PP_EXPECT(h, commands[1].before_state == TextureState::upload_destination);
PP_EXPECT(h, commands[1].after_state == TextureState::shader_read);
PP_EXPECT(h, recorded_render_command_kind_name(commands[0].kind) == std::string_view("transition_texture"));
const auto no_op = context.transition_texture(
texture,
TextureState::shader_read,
TextureState::shader_read);
PP_EXPECT(h, !no_op.ok());
PP_EXPECT(h, no_op.code == StatusCode::invalid_argument);
const auto missing_usage = context.transition_texture(
texture_without_sampled,
TextureState::copy_destination,
TextureState::shader_read);
PP_EXPECT(h, !missing_usage.ok());
PP_EXPECT(h, missing_usage.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc {}).ok());
const auto during_render_pass = context.transition_texture(
texture,
TextureState::shader_read,
TextureState::render_target);
PP_EXPECT(h, !during_render_pass.ok());
PP_EXPECT(h, during_render_pass.code == StatusCode::invalid_argument);
context.end_render_pass();
}
void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
{
RecordingRenderDevice device;
@@ -2290,6 +2511,7 @@ int main()
harness.run("validates_texture_usage_contract", validates_texture_usage_contract);
harness.run("validates_resource_labels", validates_resource_labels);
harness.run("validates_mipmap_generation_contract", validates_mipmap_generation_contract);
harness.run("validates_texture_transition_contract", validates_texture_transition_contract);
harness.run("rejects_invalid_or_excessive_extents", rejects_invalid_or_excessive_extents);
harness.run("validates_readback_bounds", validates_readback_bounds);
harness.run("computes_readback_byte_sizes", computes_readback_byte_sizes);
@@ -2311,6 +2533,7 @@ int main()
harness.run("render_devices_create_validated_resources", render_devices_create_validated_resources);
harness.run("recording_renderer_records_shader_uniform_writes", recording_renderer_records_shader_uniform_writes);
harness.run("recording_renderer_records_mipmap_generation", recording_renderer_records_mipmap_generation);
harness.run("recording_renderer_records_texture_transitions", recording_renderer_records_texture_transitions);
harness.run("recording_renderer_records_valid_command_sequences", recording_renderer_records_valid_command_sequences);
harness.run("recording_renderer_rejects_invalid_command_order_and_targets", recording_renderer_rejects_invalid_command_order_and_targets);
return harness.finish();