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

@@ -289,16 +289,16 @@ Known local toolchain state:
render-pass color/depth/stencil clear intent, scissor state, depth state,
blend state, texture-slot binding, sampler-state binding, texture-upload byte
counts, texture mip-level counts, resource debug labels, mipmap-generation commands,
shader-uniform writes, explicit draw descriptor ranges, texture-copy regions,
texture-state transitions, 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/mipmap-generation/texture-copy/readback/
sampler-bind/draw/upload/mipmap-generation/texture-transition/texture-copy/readback/
frame-capture/blit commands, draw mesh inputs, explicit draw ranges, and
records trace markers and scopes 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/mipmap-generation/texture-copy/readback/
shader-uniform/texture-bind/sampler-bind/upload/mipmap-generation/texture-transition/texture-copy/readback/
frame-capture/blit command and byte totals, trace marker/scope counts,
labeled descriptor counts, backend resource creation counts, plus draw
descriptor vertex/index totals, and is covered by

View File

@@ -419,7 +419,7 @@ mesh, readback bounds, command context, render device, shader program
descriptor, mesh, render target, readback byte-size helpers,
texture mip-level validation, resource debug-label validation,
texture-upload/readback command validation,
mipmap-generation command validation, frame-capture byte-size helpers,
mipmap-generation command validation, texture-state transition validation, frame-capture byte-size helpers,
frame-capture command validation, render-target blit validation, texture-slot
binding validation, blend-state validation, scissor-state validation,
depth-state validation, trace marker/scope validation, sampler-state validation,
@@ -833,13 +833,13 @@ Results:
renderer-owned resource factory and
command-order/render-pass-clear/scissor-state/depth-state/blend-state/
texture-usage/texture-bind/sampler-bind/shader-uniform/texture-upload/
mipmap-generation/readback/frame-capture/blit validation plus explicit draw
mipmap-generation/texture-transition/readback/frame-capture/blit validation plus explicit draw
descriptor and texture-copy validation; it creates validated textures,
render targets, shaders, meshes, and readback buffers with validated debug
labels, then records commands, trace markers/scopes, 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/mipmap generations/copies/readbacks, frame captures,
ranges, texture uploads/mipmap generations/state transitions/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
@@ -847,7 +847,7 @@ Results:
name, trace marker/scope and draw summary, labeled descriptor counts,
render-pass/depth-clear counts, and draw
descriptor vertex/index totals, scissor/depth/blend-state plus
shader-uniform/texture/sampler-bind/upload/mipmap-generation/texture-copy/readback/
shader-uniform/texture/sampler-bind/upload/mipmap-generation/texture-transition/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

View File

@@ -435,6 +435,30 @@ pp::foundation::Status RecordingCommandContext::generate_mipmaps(ITexture2D& tex
return pp::foundation::Status::success();
}
pp::foundation::Status RecordingCommandContext::transition_texture(
ITexture2D& texture,
TextureState before,
TextureState after) noexcept
{
if (in_render_pass_) {
return pp::foundation::Status::invalid_argument("texture transition must be outside a render pass");
}
const auto desc = texture.desc();
const auto desc_status = validate_texture_transition_desc(desc, before, after);
if (!desc_status.ok()) {
return desc_status;
}
push_command(commands_, RecordedRenderCommand {
.kind = RecordedRenderCommandKind::transition_texture,
.texture_desc = desc,
.before_state = before,
.after_state = after,
});
return pp::foundation::Status::success();
}
pp::foundation::Status RecordingCommandContext::copy_texture(
ITexture2D& source,
ReadbackRegion source_region,
@@ -754,6 +778,8 @@ const char* recorded_render_command_kind_name(RecordedRenderCommandKind kind) no
return "upload_texture";
case RecordedRenderCommandKind::generate_mipmaps:
return "generate_mipmaps";
case RecordedRenderCommandKind::transition_texture:
return "transition_texture";
case RecordedRenderCommandKind::copy_texture:
return "copy_texture";
case RecordedRenderCommandKind::read_texture:

View File

@@ -21,6 +21,7 @@ enum class RecordedRenderCommandKind : std::uint8_t {
draw,
upload_texture,
generate_mipmaps,
transition_texture,
copy_texture,
read_texture,
capture_frame,
@@ -52,6 +53,8 @@ struct RecordedRenderCommand {
std::uint32_t sampler_slot = 0;
TextureDesc source_desc {};
TextureDesc destination_desc {};
TextureState before_state = TextureState::undefined;
TextureState after_state = TextureState::undefined;
ReadbackRegion readback_region {};
ReadbackRegion source_region {};
ReadbackRegion destination_region {};
@@ -148,6 +151,10 @@ public:
std::span<const std::byte> rgba_or_channel_bytes) noexcept override;
[[nodiscard]] pp::foundation::Status generate_mipmaps(
ITexture2D& texture) noexcept override;
[[nodiscard]] pp::foundation::Status transition_texture(
ITexture2D& texture,
TextureState before,
TextureState after) noexcept override;
[[nodiscard]] pp::foundation::Status copy_texture(
ITexture2D& source,
ReadbackRegion source_region,

View File

@@ -692,6 +692,78 @@ pp::foundation::Status validate_mipmap_generation_desc(TextureDesc desc) noexcep
return pp::foundation::Status::success();
}
pp::foundation::Status validate_texture_state(TextureState state) noexcept
{
switch (state) {
case TextureState::undefined:
case TextureState::shader_read:
case TextureState::render_target:
case TextureState::upload_destination:
case TextureState::copy_source:
case TextureState::copy_destination:
case TextureState::readback_source:
case TextureState::present:
return pp::foundation::Status::success();
}
return pp::foundation::Status::invalid_argument("texture state is not supported");
}
pp::foundation::Status validate_texture_transition_desc(
TextureDesc desc,
TextureState before,
TextureState after) noexcept
{
const auto desc_status = validate_texture_desc(desc);
if (!desc_status.ok()) {
return desc_status;
}
const auto before_status = validate_texture_state(before);
if (!before_status.ok()) {
return before_status;
}
const auto after_status = validate_texture_state(after);
if (!after_status.ok()) {
return after_status;
}
if (before == after) {
return pp::foundation::Status::invalid_argument("texture transition must change state");
}
if (after == TextureState::undefined) {
return pp::foundation::Status::invalid_argument("texture transition destination must not be undefined");
}
if (after == TextureState::shader_read && !has_texture_usage(desc.usage, TextureUsage::sampled)) {
return pp::foundation::Status::invalid_argument("shader-read transition requires sampled usage");
}
if (after == TextureState::render_target && !has_texture_usage(desc.usage, TextureUsage::render_target)) {
return pp::foundation::Status::invalid_argument("render-target transition requires render_target usage");
}
if (after == TextureState::upload_destination && !has_texture_usage(desc.usage, TextureUsage::upload_destination)) {
return pp::foundation::Status::invalid_argument("upload transition requires upload_destination usage");
}
if (after == TextureState::copy_source && !has_texture_usage(desc.usage, TextureUsage::copy_source)) {
return pp::foundation::Status::invalid_argument("copy-source transition requires copy_source usage");
}
if (after == TextureState::copy_destination && !has_texture_usage(desc.usage, TextureUsage::copy_destination)) {
return pp::foundation::Status::invalid_argument("copy-destination transition requires copy_destination usage");
}
if (after == TextureState::readback_source && !has_texture_usage(desc.usage, TextureUsage::readback_source)) {
return pp::foundation::Status::invalid_argument("readback transition requires readback_source usage");
}
return pp::foundation::Status::success();
}
pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept
{
switch (filter) {
@@ -749,6 +821,30 @@ const char* texture_format_name(TextureFormat format) noexcept
return "unknown";
}
const char* texture_state_name(TextureState state) noexcept
{
switch (state) {
case TextureState::undefined:
return "undefined";
case TextureState::shader_read:
return "shader_read";
case TextureState::render_target:
return "render_target";
case TextureState::upload_destination:
return "upload_destination";
case TextureState::copy_source:
return "copy_source";
case TextureState::copy_destination:
return "copy_destination";
case TextureState::readback_source:
return "readback_source";
case TextureState::present:
return "present";
}
return "unknown";
}
const char* primitive_topology_name(PrimitiveTopology topology) noexcept
{
switch (topology) {

View File

@@ -35,6 +35,17 @@ enum class TextureUsage : std::uint32_t {
copy_destination = 1U << 5U,
};
enum class TextureState : std::uint8_t {
undefined,
shader_read,
render_target,
upload_destination,
copy_source,
copy_destination,
readback_source,
present,
};
[[nodiscard]] constexpr TextureUsage operator|(TextureUsage lhs, TextureUsage rhs) noexcept
{
return static_cast<TextureUsage>(
@@ -285,6 +296,10 @@ public:
std::span<const std::byte> rgba_or_channel_bytes) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status generate_mipmaps(
ITexture2D& texture) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status transition_texture(
ITexture2D& texture,
TextureState before,
TextureState after) noexcept = 0;
[[nodiscard]] virtual pp::foundation::Status copy_texture(
ITexture2D& source,
ReadbackRegion source_region,
@@ -358,11 +373,17 @@ public:
TextureDesc destination,
ReadbackRegion destination_region) noexcept;
[[nodiscard]] pp::foundation::Status validate_mipmap_generation_desc(TextureDesc desc) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_state(TextureState state) noexcept;
[[nodiscard]] pp::foundation::Status validate_texture_transition_desc(
TextureDesc desc,
TextureState before,
TextureState after) noexcept;
[[nodiscard]] pp::foundation::Status validate_blit_filter(BlitFilter filter) noexcept;
[[nodiscard]] pp::foundation::Status validate_blit_descs(
TextureDesc source,
TextureDesc destination) noexcept;
[[nodiscard]] const char* texture_format_name(TextureFormat format) noexcept;
[[nodiscard]] const char* texture_state_name(TextureState state) noexcept;
[[nodiscard]] const char* primitive_topology_name(PrimitiveTopology topology) noexcept;
[[nodiscard]] const char* blit_filter_name(BlitFilter filter) noexcept;
[[nodiscard]] const char* blend_factor_name(BlendFactor factor) noexcept;

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\":7.*\"labeledCommandDescriptors\":12.*\"commands\":21.*\"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.*\"mipmapCommands\":1.*\"mipmapLevels\":3.*\"mipmapBytes\":84.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048.*\"traceMarkers\":1.*\"traceBeginScopes\":1.*\"traceEndScopes\":1")
PASS_REGULAR_EXPRESSION "\"backend\":\"recording\".*\"width\":32.*\"height\":16.*\"createdResources\":7.*\"labeledCommandDescriptors\":16.*\"commands\":25.*\"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.*\"mipmapCommands\":1.*\"mipmapLevels\":3.*\"mipmapBytes\":84.*\"transitionCommands\":4.*\"transitionToUpload\":1.*\"transitionToShaderRead\":2.*\"copyCommands\":1.*\"copySourceBytes\":2048.*\"copyDestinationBytes\":2048.*\"readbackCommands\":1.*\"readbackBytes\":2048.*\"captureCommands\":1.*\"captureBytes\":2048.*\"blitCommands\":1.*\"blitSourceBytes\":2048.*\"blitDestinationBytes\":2048.*\"traceMarkers\":1.*\"traceBeginScopes\":1.*\"traceEndScopes\":1")
add_test(NAME pano_cli_record_render_rejects_oversized_target
COMMAND "${CMAKE_COMMAND}"

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

View File

@@ -2313,6 +2313,15 @@ int record_render(int argc, char** argv)
}
auto& context = device.immediate_context();
const auto transition_upload_status = context.transition_texture(
*texture.value(),
pp::renderer::TextureState::undefined,
pp::renderer::TextureState::upload_destination);
if (!transition_upload_status.ok()) {
print_error("record-render", transition_upload_status.message);
return 2;
}
const auto upload_status = context.upload_texture(
*texture.value(),
pp::renderer::ReadbackRegion {
@@ -2327,12 +2336,39 @@ int record_render(int argc, char** argv)
return 2;
}
const auto transition_shader_read_status = context.transition_texture(
*texture.value(),
pp::renderer::TextureState::upload_destination,
pp::renderer::TextureState::shader_read);
if (!transition_shader_read_status.ok()) {
print_error("record-render", transition_shader_read_status.message);
return 2;
}
const auto transition_mip_destination_status = context.transition_texture(
*mip_texture.value(),
pp::renderer::TextureState::undefined,
pp::renderer::TextureState::copy_destination);
if (!transition_mip_destination_status.ok()) {
print_error("record-render", transition_mip_destination_status.message);
return 2;
}
const auto mipmap_status = context.generate_mipmaps(*mip_texture.value());
if (!mipmap_status.ok()) {
print_error("record-render", mipmap_status.message);
return 2;
}
const auto transition_mip_shader_read_status = context.transition_texture(
*mip_texture.value(),
pp::renderer::TextureState::copy_destination,
pp::renderer::TextureState::shader_read);
if (!transition_mip_shader_read_status.ok()) {
print_error("record-render", transition_mip_shader_read_status.message);
return 2;
}
const auto begin_status = context.begin_render_pass(
*target.value(),
pp::renderer::RenderPassDesc {
@@ -2497,6 +2533,9 @@ int record_render(int argc, char** argv)
std::size_t bind_sampler_commands = 0;
std::size_t upload_commands = 0;
std::size_t mipmap_commands = 0;
std::size_t transition_commands = 0;
std::size_t transition_to_upload = 0;
std::size_t transition_to_shader_read = 0;
std::size_t copy_commands = 0;
std::size_t readback_commands = 0;
std::size_t capture_commands = 0;
@@ -2567,6 +2606,15 @@ int record_render(int argc, char** argv)
count_label(command.texture_desc.debug_name);
mipmap_levels += command.generated_mip_levels;
mipmap_bytes += command.generated_mip_bytes;
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::transition_texture) {
++transition_commands;
count_label(command.texture_desc.debug_name);
if (command.after_state == pp::renderer::TextureState::upload_destination) {
++transition_to_upload;
}
if (command.after_state == pp::renderer::TextureState::shader_read) {
++transition_to_shader_read;
}
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::upload_texture) {
++upload_commands;
count_label(command.texture_desc.debug_name);
@@ -2627,6 +2675,9 @@ int record_render(int argc, char** argv)
<< ",\"mipmapCommands\":" << mipmap_commands
<< ",\"mipmapLevels\":" << mipmap_levels
<< ",\"mipmapBytes\":" << mipmap_bytes
<< ",\"transitionCommands\":" << transition_commands
<< ",\"transitionToUpload\":" << transition_to_upload
<< ",\"transitionToShaderRead\":" << transition_to_shader_read
<< ",\"copyCommands\":" << copy_commands
<< ",\"copySourceBytes\":" << copy_source_bytes
<< ",\"copyDestinationBytes\":" << copy_destination_bytes