Add renderer draw descriptor contract
This commit is contained in:
@@ -288,16 +288,18 @@ Known local toolchain state:
|
||||
backend-owned resource creation, command order, render-pass color/depth/
|
||||
stencil clear intent, scissor state, depth state, blend state, texture-slot
|
||||
binding, sampler-state binding, texture-upload byte counts,
|
||||
shader-uniform writes, readback bounds, frame-capture sources, destination
|
||||
buffer sizes, and render-target blit regions, records
|
||||
shader-uniform writes, explicit draw descriptor ranges, 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/upload/readback/frame-capture/blit commands, draw mesh inputs,
|
||||
and records trace markers without a window or GL context.
|
||||
sampler-bind/draw/upload/readback/frame-capture/blit commands, draw mesh
|
||||
inputs, explicit draw ranges, and records trace markers 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/readback/frame-capture/blit
|
||||
command and byte totals, backend resource creation counts, plus draw
|
||||
vertex/index totals, and is covered by
|
||||
descriptor vertex/index totals, and is covered by
|
||||
`pano_cli_record_render_smoke` plus
|
||||
`pano_cli_record_render_rejects_oversized_target`.
|
||||
- `pano_cli simulate-document-history` exposes `pp_document::DocumentHistory`
|
||||
|
||||
@@ -725,10 +725,11 @@ Results:
|
||||
command-order validation, render-target blit validation, texture-slot binding
|
||||
validation, blend-state validation, scissor-state validation,
|
||||
render-pass color/depth/stencil clear validation, shader-uniform write
|
||||
validation, backend-neutral resource factory validation, recording
|
||||
validation, draw descriptor/range validation, backend-neutral resource
|
||||
factory validation, recording
|
||||
render-pass clear/scissor/depth/blend/shader-uniform/texture/sampler-bind/
|
||||
upload/readback/frame-capture/blit command capture, draw mesh-input capture,
|
||||
and invalid catalog rejection.
|
||||
explicit draw-range capture, and invalid catalog rejection.
|
||||
- `pp_paint_renderer_compositor_tests` passed.
|
||||
- `pp_ui_core_color_tests` passed.
|
||||
- `pp_ui_core_layout_value_tests` passed.
|
||||
@@ -825,18 +826,19 @@ Results:
|
||||
renderer-owned resource factory and
|
||||
command-order/render-pass-clear/scissor-state/depth-state/blend-state/
|
||||
texture-bind/sampler-bind/shader-uniform/texture-upload/readback/
|
||||
frame-capture/blit validation; it creates validated textures, render targets,
|
||||
shaders, meshes, and readback buffers, then records commands, trace markers,
|
||||
render-pass color/depth/stencil clear intent, scissor state, depth state,
|
||||
blend state, shader uniform writes, texture/sampler binds, draw mesh inputs,
|
||||
frame-capture/blit validation plus explicit draw descriptor validation; it
|
||||
creates validated textures, render targets, shaders, meshes, and readback
|
||||
buffers, then records commands, trace markers, 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,
|
||||
uploads/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
|
||||
JSON command counts, resource creation counts, target dimensions, backend
|
||||
name, trace/draw summary, render-pass/depth-clear counts, and draw
|
||||
vertex/index totals, scissor/depth/blend-state plus shader-uniform/texture/
|
||||
sampler-bind/upload/readback/frame-capture/blit command/byte totals for
|
||||
agent automation, with an expected-failure smoke for oversized
|
||||
descriptor vertex/index totals, scissor/depth/blend-state plus
|
||||
shader-uniform/texture/sampler-bind/upload/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
|
||||
apply/undo/redo behavior and emits JSON layer/frame/history state for agent
|
||||
|
||||
@@ -316,7 +316,7 @@ pp::foundation::Status RecordingCommandContext::bind_mesh(IMesh& mesh) noexcept
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status RecordingCommandContext::draw() noexcept
|
||||
pp::foundation::Status RecordingCommandContext::draw(DrawDesc desc) noexcept
|
||||
{
|
||||
if (!in_render_pass_) {
|
||||
return pp::foundation::Status::invalid_argument("render pass has not begun");
|
||||
@@ -328,9 +328,15 @@ pp::foundation::Status RecordingCommandContext::draw() noexcept
|
||||
return pp::foundation::Status::invalid_argument("mesh must be bound before draw");
|
||||
}
|
||||
|
||||
const auto draw_status = validate_draw_desc(active_mesh_, desc);
|
||||
if (!draw_status.ok()) {
|
||||
return draw_status;
|
||||
}
|
||||
|
||||
push_command(commands_, RecordedRenderCommand {
|
||||
.kind = RecordedRenderCommandKind::draw,
|
||||
.mesh_desc = active_mesh_,
|
||||
.draw_desc = desc,
|
||||
});
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
@@ -41,6 +41,7 @@ struct RecordedRenderCommand {
|
||||
BlendState blend_state {};
|
||||
DepthState depth_state {};
|
||||
MeshDesc mesh_desc {};
|
||||
DrawDesc draw_desc {};
|
||||
TextureDesc texture_desc {};
|
||||
std::uint32_t texture_slot = 0;
|
||||
SamplerDesc sampler_desc {};
|
||||
@@ -128,7 +129,7 @@ public:
|
||||
std::uint32_t slot,
|
||||
SamplerDesc sampler) noexcept override;
|
||||
[[nodiscard]] pp::foundation::Status bind_mesh(IMesh& mesh) noexcept override;
|
||||
[[nodiscard]] pp::foundation::Status draw() noexcept override;
|
||||
[[nodiscard]] pp::foundation::Status draw(DrawDesc desc) noexcept override;
|
||||
[[nodiscard]] pp::foundation::Status read_texture(
|
||||
ITexture2D& texture,
|
||||
ReadbackRegion region,
|
||||
|
||||
@@ -337,6 +337,40 @@ pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept
|
||||
return pp::foundation::Status::invalid_argument("mesh topology is not supported");
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_draw_desc(MeshDesc mesh, DrawDesc draw) noexcept
|
||||
{
|
||||
const auto mesh_status = validate_mesh_desc(mesh);
|
||||
if (!mesh_status.ok()) {
|
||||
return mesh_status;
|
||||
}
|
||||
|
||||
if (draw.instance_count == 0) {
|
||||
return pp::foundation::Status::invalid_argument("draw instance count must be greater than zero");
|
||||
}
|
||||
|
||||
if (draw.vertex_count == 0 && draw.index_count == 0) {
|
||||
return pp::foundation::Status::invalid_argument("draw must include vertices or indices");
|
||||
}
|
||||
|
||||
if (draw.index_count > 0) {
|
||||
if (mesh.index_count == 0) {
|
||||
return pp::foundation::Status::invalid_argument("indexed draw requires an indexed mesh");
|
||||
}
|
||||
|
||||
if (draw.first_index > mesh.index_count || draw.index_count > mesh.index_count - draw.first_index) {
|
||||
return pp::foundation::Status::out_of_range("draw index range exceeds the bound mesh");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
if (draw.first_vertex > mesh.vertex_count || draw.vertex_count > mesh.vertex_count - draw.first_vertex) {
|
||||
return pp::foundation::Status::out_of_range("draw vertex range exceeds the bound mesh");
|
||||
}
|
||||
|
||||
return pp::foundation::Status::success();
|
||||
}
|
||||
|
||||
pp::foundation::Status validate_texture_slot(std::uint32_t slot) noexcept
|
||||
{
|
||||
if (slot >= max_texture_slots) {
|
||||
|
||||
@@ -157,6 +157,14 @@ struct MeshDesc {
|
||||
PrimitiveTopology topology = PrimitiveTopology::triangles;
|
||||
};
|
||||
|
||||
struct DrawDesc {
|
||||
std::uint32_t first_vertex = 0;
|
||||
std::uint32_t vertex_count = 0;
|
||||
std::uint32_t first_index = 0;
|
||||
std::uint32_t index_count = 0;
|
||||
std::uint32_t instance_count = 1;
|
||||
};
|
||||
|
||||
struct ShaderStageSource {
|
||||
const char* entry_point = "main";
|
||||
const char* source = nullptr;
|
||||
@@ -226,7 +234,7 @@ public:
|
||||
std::uint32_t slot,
|
||||
SamplerDesc sampler) noexcept = 0;
|
||||
[[nodiscard]] virtual pp::foundation::Status bind_mesh(IMesh& mesh) noexcept = 0;
|
||||
[[nodiscard]] virtual pp::foundation::Status draw() noexcept = 0;
|
||||
[[nodiscard]] virtual pp::foundation::Status draw(DrawDesc desc) noexcept = 0;
|
||||
[[nodiscard]] virtual pp::foundation::Status read_texture(
|
||||
ITexture2D& texture,
|
||||
ReadbackRegion region,
|
||||
@@ -279,6 +287,7 @@ public:
|
||||
[[nodiscard]] pp::foundation::Status validate_sampler_address_mode(SamplerAddressMode mode) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_sampler_desc(SamplerDesc desc) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_mesh_desc(MeshDesc desc) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_draw_desc(MeshDesc mesh, DrawDesc draw) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_texture_slot(std::uint32_t slot) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_shader_program_desc(ShaderProgramDesc desc) noexcept;
|
||||
[[nodiscard]] pp::foundation::Status validate_shader_uniform_write(
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -2350,7 +2350,10 @@ int record_render(int argc, char** argv)
|
||||
.address_w = pp::renderer::SamplerAddressMode::clamp_to_edge,
|
||||
});
|
||||
const auto mesh_status = context.bind_mesh(*mesh.value());
|
||||
const auto draw_status = context.draw();
|
||||
const auto draw_status = context.draw(pp::renderer::DrawDesc {
|
||||
.vertex_count = 3,
|
||||
.index_count = 3,
|
||||
});
|
||||
context.end_render_pass();
|
||||
|
||||
if (!shader_status.ok()) {
|
||||
@@ -2463,8 +2466,8 @@ int record_render(int argc, char** argv)
|
||||
}
|
||||
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::draw) {
|
||||
++draw_commands;
|
||||
draw_vertices += command.mesh_desc.vertex_count;
|
||||
draw_indices += command.mesh_desc.index_count;
|
||||
draw_vertices += command.draw_desc.vertex_count;
|
||||
draw_indices += command.draw_desc.index_count;
|
||||
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::set_scissor) {
|
||||
++scissor_commands;
|
||||
} else if (command.kind == pp::renderer::RecordedRenderCommandKind::set_blend_state) {
|
||||
|
||||
Reference in New Issue
Block a user