Add renderer draw descriptor contract

This commit is contained in:
2026-06-02 16:27:28 +02:00
parent 58f163788b
commit 483bbb4a9c
8 changed files with 157 additions and 31 deletions

View File

@@ -23,6 +23,7 @@ using pp::renderer::ClearColor;
using pp::renderer::CompareOp;
using pp::renderer::compare_op_name;
using pp::renderer::DepthState;
using pp::renderer::DrawDesc;
using pp::renderer::Extent2D;
using pp::renderer::frame_capture_byte_size;
using pp::renderer::ICommandContext;
@@ -72,6 +73,7 @@ using pp::renderer::validate_blend_op;
using pp::renderer::validate_blend_state;
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_readback_region;
using pp::renderer::validate_render_pass_desc;
@@ -319,13 +321,31 @@ public:
[[nodiscard]] pp::foundation::Status bind_mesh(IMesh& mesh) noexcept override
{
return validate_mesh_desc(mesh.desc());
const auto status = validate_mesh_desc(mesh.desc());
if (!status.ok()) {
return status;
}
last_mesh_desc = mesh.desc();
mesh_bound = true;
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status draw() noexcept override
[[nodiscard]] pp::foundation::Status draw(DrawDesc desc) noexcept override
{
return in_render_pass ? pp::foundation::Status::success()
: pp::foundation::Status::invalid_argument("render pass has not begun");
if (!in_render_pass) {
return pp::foundation::Status::invalid_argument("render pass has not begun");
}
if (!mesh_bound) {
return pp::foundation::Status::invalid_argument("mesh must be bound before draw");
}
const auto status = validate_draw_desc(last_mesh_desc, desc);
if (!status.ok()) {
return status;
}
last_draw_desc = desc;
return pp::foundation::Status::success();
}
[[nodiscard]] pp::foundation::Status read_texture(
@@ -410,10 +430,15 @@ public:
void end_render_pass() noexcept override
{
in_render_pass = false;
mesh_bound = false;
last_mesh_desc = MeshDesc {};
}
bool in_render_pass = false;
bool mesh_bound = false;
RenderPassDesc last_render_pass_desc {};
MeshDesc last_mesh_desc {};
DrawDesc last_draw_desc {};
const char* shader_name = nullptr;
const char* last_uniform_name = nullptr;
std::size_t last_uniform_bytes = 0;
@@ -810,6 +835,41 @@ void validates_viewports_and_mesh_descriptors(pp::tests::Harness& h)
PP_EXPECT(h, invalid_slot.code == StatusCode::out_of_range);
}
void validates_draw_descriptors(pp::tests::Harness& h)
{
const MeshDesc vertex_mesh {
.vertex_count = 8,
.index_count = 0,
.topology = PrimitiveTopology::triangles,
};
const MeshDesc indexed_mesh {
.vertex_count = 8,
.index_count = 12,
.topology = PrimitiveTopology::triangles,
};
PP_EXPECT(h, validate_draw_desc(vertex_mesh, DrawDesc { .first_vertex = 2, .vertex_count = 3 }).ok());
PP_EXPECT(h, validate_draw_desc(indexed_mesh, DrawDesc { .first_index = 3, .index_count = 6 }).ok());
PP_EXPECT(h, validate_draw_desc(indexed_mesh, DrawDesc { .index_count = 12, .instance_count = 4 }).ok());
const auto empty_draw = validate_draw_desc(vertex_mesh, DrawDesc {});
const auto zero_instances = validate_draw_desc(vertex_mesh, DrawDesc { .vertex_count = 3, .instance_count = 0 });
const auto vertex_overrun = validate_draw_desc(vertex_mesh, DrawDesc { .first_vertex = 7, .vertex_count = 2 });
const auto indexed_without_indices = validate_draw_desc(vertex_mesh, DrawDesc { .index_count = 3 });
const auto index_overrun = validate_draw_desc(indexed_mesh, DrawDesc { .first_index = 11, .index_count = 2 });
PP_EXPECT(h, !empty_draw.ok());
PP_EXPECT(h, empty_draw.code == StatusCode::invalid_argument);
PP_EXPECT(h, !zero_instances.ok());
PP_EXPECT(h, zero_instances.code == StatusCode::invalid_argument);
PP_EXPECT(h, !vertex_overrun.ok());
PP_EXPECT(h, vertex_overrun.code == StatusCode::out_of_range);
PP_EXPECT(h, !indexed_without_indices.ok());
PP_EXPECT(h, indexed_without_indices.code == StatusCode::invalid_argument);
PP_EXPECT(h, !index_overrun.ok());
PP_EXPECT(h, index_overrun.code == StatusCode::out_of_range);
}
void validates_render_pass_descriptors(pp::tests::Harness& h)
{
const auto valid = validate_render_pass_desc(RenderPassDesc {
@@ -1034,10 +1094,10 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
})
.ok());
PP_EXPECT(h, context.bind_mesh(mesh).ok());
PP_EXPECT(h, context.draw().ok());
PP_EXPECT(h, context.draw(DrawDesc { .vertex_count = 3 }).ok());
context.end_render_pass();
const auto draw_after_end = context.draw();
const auto draw_after_end = context.draw(DrawDesc { .vertex_count = 3 });
PP_EXPECT(h, !draw_after_end.ok());
PP_EXPECT(h, draw_after_end.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.upload_texture(
@@ -1073,6 +1133,8 @@ void renderer_interfaces_support_backend_neutral_dispatch(pp::tests::Harness& h)
PP_EXPECT(h, device.context.last_depth_state.compare == CompareOp::less_or_equal);
PP_EXPECT(h, device.context.last_uniform_name == std::string_view("mvp"));
PP_EXPECT(h, device.context.last_uniform_bytes == 64U);
PP_EXPECT(h, device.context.last_draw_desc.vertex_count == 3U);
PP_EXPECT(h, device.context.last_draw_desc.instance_count == 1U);
PP_EXPECT(h, device.context.last_texture_slot == 2U);
PP_EXPECT(h, device.context.last_texture_bytes == 8192U);
PP_EXPECT(h, device.context.last_sampler_slot == 2U);
@@ -1259,7 +1321,7 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
})
.ok());
PP_EXPECT(h, context.bind_mesh(mesh).ok());
PP_EXPECT(h, context.draw().ok());
PP_EXPECT(h, context.draw(DrawDesc { .vertex_count = 3, .index_count = 3 }).ok());
context.end_render_pass();
const auto commands = device.commands();
@@ -1309,6 +1371,9 @@ void recording_renderer_records_valid_command_sequences(pp::tests::Harness& h)
PP_EXPECT(h, commands[10].mesh_desc.vertex_count == 3U);
PP_EXPECT(h, commands[10].mesh_desc.index_count == 3U);
PP_EXPECT(h, commands[10].mesh_desc.topology == PrimitiveTopology::triangles);
PP_EXPECT(h, commands[10].draw_desc.vertex_count == 3U);
PP_EXPECT(h, commands[10].draw_desc.index_count == 3U);
PP_EXPECT(h, commands[10].draw_desc.instance_count == 1U);
PP_EXPECT(h, commands[11].kind == RecordedRenderCommandKind::end_render_pass);
PP_EXPECT(h, recorded_render_command_kind_name(commands[10].kind) == std::string_view("draw"));
@@ -1403,7 +1468,7 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
RecordingMesh empty_mesh(MeshDesc {});
auto& context = device.immediate_context();
const auto draw_before_begin = context.draw();
const auto draw_before_begin = context.draw(DrawDesc { .vertex_count = 3 });
PP_EXPECT(h, !draw_before_begin.ok());
PP_EXPECT(h, draw_before_begin.code == StatusCode::invalid_argument);
@@ -1513,7 +1578,7 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
})
.ok());
const auto draw_without_bindings = context.draw();
const auto draw_without_bindings = context.draw(DrawDesc { .vertex_count = 3 });
PP_EXPECT(h, !draw_without_bindings.ok());
PP_EXPECT(h, draw_without_bindings.code == StatusCode::invalid_argument);
@@ -1535,7 +1600,7 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, context.bind_sampler(0, SamplerDesc {}).ok());
const auto draw_without_mesh = context.draw();
const auto draw_without_mesh = context.draw(DrawDesc { .vertex_count = 3 });
PP_EXPECT(h, !draw_without_mesh.ok());
PP_EXPECT(h, draw_without_mesh.code == StatusCode::invalid_argument);
@@ -1544,7 +1609,10 @@ void recording_renderer_rejects_invalid_command_order_and_targets(pp::tests::Har
PP_EXPECT(h, invalid_mesh.code == StatusCode::invalid_argument);
PP_EXPECT(h, context.bind_mesh(mesh).ok());
PP_EXPECT(h, context.draw().ok());
const auto draw_outside_mesh = context.draw(DrawDesc { .first_vertex = 2, .vertex_count = 2 });
PP_EXPECT(h, !draw_outside_mesh.ok());
PP_EXPECT(h, draw_outside_mesh.code == StatusCode::out_of_range);
PP_EXPECT(h, context.draw(DrawDesc { .vertex_count = 3 }).ok());
context.end_render_pass();
const auto viewport_after_end = context.set_viewport(Viewport { .x = 0, .y = 0, .width = 1, .height = 1 });
@@ -1659,6 +1727,7 @@ int main()
harness.run("validates_depth_contract", validates_depth_contract);
harness.run("validates_sampler_contract", validates_sampler_contract);
harness.run("validates_viewports_and_mesh_descriptors", validates_viewports_and_mesh_descriptors);
harness.run("validates_draw_descriptors", validates_draw_descriptors);
harness.run("validates_render_pass_descriptors", validates_render_pass_descriptors);
harness.run("validates_shader_program_descriptors", validates_shader_program_descriptors);
harness.run("validates_shader_uniform_writes", validates_shader_uniform_writes);