Add renderer mipmap command contract

This commit is contained in:
2026-06-02 16:47:44 +02:00
parent 901aff1051
commit 07293c0590
7 changed files with 279 additions and 29 deletions

View File

@@ -58,6 +58,7 @@ using pp::renderer::TextureUsage;
using pp::renderer::Viewport;
using pp::renderer::max_shader_source_bytes;
using pp::renderer::max_shader_uniform_bytes;
using pp::renderer::max_mip_levels_for_extent;
using pp::renderer::max_texture_dimension;
using pp::renderer::max_texture_slots;
using pp::renderer::panopainter_shader_catalog;
@@ -77,6 +78,7 @@ using pp::renderer::validate_compare_op;
using pp::renderer::validate_depth_state;
using pp::renderer::validate_draw_desc;
using pp::renderer::validate_mesh_desc;
using pp::renderer::validate_mipmap_generation_desc;
using pp::renderer::validate_readback_region;
using pp::renderer::validate_render_pass_desc;
using pp::renderer::validate_sampler_address_mode;
@@ -397,6 +399,24 @@ public:
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status generate_mipmaps(
pp::renderer::ITexture2D& texture) noexcept override
{
const auto status = validate_mipmap_generation_desc(texture.desc());
if (!status.ok()) {
return status;
}
const auto bytes = texture_byte_size(texture.desc());
if (!bytes.ok()) {
return bytes.status();
}
last_generated_mip_levels = texture.desc().mip_levels;
last_generated_mip_bytes = bytes.value();
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status copy_texture(
pp::renderer::ITexture2D& source,
ReadbackRegion source_region,
@@ -497,6 +517,8 @@ public:
std::uint32_t last_sampler_slot = 0;
SamplerDesc last_sampler_desc {};
std::uint64_t last_upload_bytes = 0;
std::uint32_t last_generated_mip_levels = 0;
std::uint64_t last_generated_mip_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;
@@ -629,6 +651,14 @@ void computes_texture_sizes(pp::tests::Harness& h)
PP_EXPECT(h, r8.ok());
PP_EXPECT(h, r8.value() == 128U);
PP_EXPECT(h, texture_format_name(TextureFormat::depth24_stencil8) == std::string_view("depth24_stencil8"));
const auto mipmapped = texture_byte_size(TextureDesc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 3,
});
PP_EXPECT(h, mipmapped.ok());
PP_EXPECT(h, mipmapped.value() == 84U);
}
void validates_texture_usage_contract(pp::tests::Harness& h)
@@ -676,6 +706,68 @@ void validates_texture_usage_contract(pp::tests::Harness& h)
PP_EXPECT(h, unknown_format.code == StatusCode::invalid_argument);
}
void validates_mipmap_generation_contract(pp::tests::Harness& h)
{
const TextureDesc mipmapped_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 3,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc one_mip_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 1,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc too_many_mips_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 4,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc missing_sampled_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 3,
.usage = TextureUsage::copy_source | TextureUsage::copy_destination,
};
const TextureDesc missing_copy_source_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 3,
.usage = TextureUsage::sampled | TextureUsage::copy_destination,
};
const TextureDesc missing_copy_destination_desc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 3,
.usage = TextureUsage::sampled | TextureUsage::copy_source,
};
PP_EXPECT(h, max_mip_levels_for_extent(Extent2D { .width = 1, .height = 1 }) == 1U);
PP_EXPECT(h, max_mip_levels_for_extent(Extent2D { .width = 4, .height = 3 }) == 3U);
PP_EXPECT(h, max_mip_levels_for_extent(Extent2D { .width = max_texture_dimension, .height = 1 }) == 16U);
PP_EXPECT(h, validate_mipmap_generation_desc(mipmapped_desc).ok());
const auto one_mip = validate_mipmap_generation_desc(one_mip_desc);
const auto too_many_mips = validate_mipmap_generation_desc(too_many_mips_desc);
const auto missing_sampled = validate_mipmap_generation_desc(missing_sampled_desc);
const auto missing_copy_source = validate_mipmap_generation_desc(missing_copy_source_desc);
const auto missing_copy_destination = validate_mipmap_generation_desc(missing_copy_destination_desc);
PP_EXPECT(h, !one_mip.ok());
PP_EXPECT(h, one_mip.code == StatusCode::invalid_argument);
PP_EXPECT(h, !too_many_mips.ok());
PP_EXPECT(h, too_many_mips.code == StatusCode::out_of_range);
PP_EXPECT(h, !missing_sampled.ok());
PP_EXPECT(h, missing_sampled.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);
}
void rejects_invalid_or_excessive_extents(pp::tests::Harness& h)
{
const auto zero = validate_extent(Extent2D { .width = 0, .height = 1 });
@@ -1410,6 +1502,48 @@ void recording_renderer_records_shader_uniform_writes(pp::tests::Harness& h)
PP_EXPECT(h, commands[3].kind == RecordedRenderCommandKind::end_render_pass);
}
void recording_renderer_records_mipmap_generation(pp::tests::Harness& h)
{
RecordingRenderDevice device;
RecordingTexture2D texture(TextureDesc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 3,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingTexture2D one_mip_texture(TextureDesc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.mip_levels = 1,
.usage = TextureUsage::sampled | TextureUsage::copy_source | TextureUsage::copy_destination,
});
RecordingRenderTarget target(TextureDesc {
.extent = Extent2D { .width = 4, .height = 4 },
.format = TextureFormat::rgba8,
.usage = all_texture_usages,
});
auto& context = device.immediate_context();
PP_EXPECT(h, context.generate_mipmaps(texture).ok());
const auto commands = device.commands();
PP_EXPECT(h, commands.size() == 1U);
PP_EXPECT(h, commands[0].kind == RecordedRenderCommandKind::generate_mipmaps);
PP_EXPECT(h, commands[0].texture_desc.mip_levels == 3U);
PP_EXPECT(h, commands[0].generated_mip_levels == 3U);
PP_EXPECT(h, commands[0].generated_mip_bytes == 84U);
PP_EXPECT(h, recorded_render_command_kind_name(commands[0].kind) == std::string_view("generate_mipmaps"));
const auto one_mip = context.generate_mipmaps(one_mip_texture);
PP_EXPECT(h, !one_mip.ok());
PP_EXPECT(h, one_mip.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.begin_render_pass(target, RenderPassDesc {}).ok());
const auto during_render_pass = context.generate_mipmaps(texture);
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;
@@ -1997,6 +2131,7 @@ int main()
pp::tests::Harness harness;
harness.run("computes_texture_sizes", computes_texture_sizes);
harness.run("validates_texture_usage_contract", validates_texture_usage_contract);
harness.run("validates_mipmap_generation_contract", validates_mipmap_generation_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);
@@ -2016,6 +2151,7 @@ int main()
harness.run("renderer_interfaces_support_backend_neutral_dispatch", renderer_interfaces_support_backend_neutral_dispatch);
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_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();