Plan recorded OpenGL command streams
This commit is contained in:
@@ -273,7 +273,11 @@ Known local toolchain state:
|
|||||||
commands and maps render-pass clear masks/values, viewport/scissor state,
|
commands and maps render-pass clear masks/values, viewport/scissor state,
|
||||||
blend/depth/sampler state, texture formats, primitive modes, draw counts, and
|
blend/depth/sampler state, texture formats, primitive modes, draw counts, and
|
||||||
blit filters into GL-facing planned command data while rejecting unsupported
|
blit filters into GL-facing planned command data while rejecting unsupported
|
||||||
enum tokens before a real GL context is needed.
|
enum tokens before a real GL context is needed. It also plans whole recorded
|
||||||
|
command streams, preserving per-command planned data while counting render
|
||||||
|
passes, draws, passthrough commands, trace commands, unsupported commands,
|
||||||
|
and render-pass ordering errors such as state changes outside a pass, nested
|
||||||
|
passes, and unclosed passes.
|
||||||
Desktop VR drawing also consumes backend-owned scissor/depth/blend state,
|
Desktop VR drawing also consumes backend-owned scissor/depth/blend state,
|
||||||
depth clear masks, active texture units, and fallback 2D texture unbind
|
depth clear masks, active texture units, and fallback 2D texture unbind
|
||||||
targets while retaining the existing VR SDK/platform bridge shape.
|
targets while retaining the existing VR SDK/platform bridge shape.
|
||||||
|
|||||||
@@ -532,7 +532,11 @@ The headless OpenGL command planner now consumes `pp_renderer_api` recorded
|
|||||||
commands and maps render-pass clear masks/values, viewport/scissor state,
|
commands and maps render-pass clear masks/values, viewport/scissor state,
|
||||||
blend/depth/sampler state, texture formats, primitive modes, draw counts, and
|
blend/depth/sampler state, texture formats, primitive modes, draw counts, and
|
||||||
blit filters into GL-facing planned command data with explicit unsupported-token
|
blit filters into GL-facing planned command data with explicit unsupported-token
|
||||||
rejection before a runtime GL context is needed.
|
rejection before a runtime GL context is needed. It also plans whole recorded
|
||||||
|
command streams, preserving per-command planned data while counting render
|
||||||
|
passes, draws, passthrough commands, trace commands, unsupported commands, and
|
||||||
|
render-pass ordering errors such as state changes outside a pass, nested passes,
|
||||||
|
and unclosed passes.
|
||||||
The existing renderer classes are not yet fully
|
The existing renderer classes are not yet fully
|
||||||
behind the renderer interfaces.
|
behind the renderer interfaces.
|
||||||
|
|
||||||
@@ -843,8 +847,9 @@ Results:
|
|||||||
- `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command
|
- `pp_renderer_gl_command_plan_tests` covers the headless OpenGL command
|
||||||
planner for recorded render-pass clear masks/values, viewport/scissor state,
|
planner for recorded render-pass clear masks/values, viewport/scissor state,
|
||||||
blend/depth/sampler state, texture format mapping, mesh/draw primitive modes,
|
blend/depth/sampler state, texture format mapping, mesh/draw primitive modes,
|
||||||
draw counts, blit filters, planned command names, and unsupported enum
|
draw counts, blit filters, planned command names, unsupported enum rejection,
|
||||||
rejection.
|
whole recorded stream planning, valid trace/render/draw/blit ordering, and
|
||||||
|
broken render-pass order detection.
|
||||||
- PowerShell analyze automation returns JSON summaries and includes the shader
|
- PowerShell analyze automation returns JSON summaries and includes the shader
|
||||||
validation target and renderer-boundary guard.
|
validation target and renderer-boundary guard.
|
||||||
- `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled
|
- `windows-msvc-vcpkg-headless` configured through the Visual Studio bundled
|
||||||
|
|||||||
@@ -31,6 +31,22 @@ namespace {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void record_unsupported_command(OpenGlCommandPlan& plan, std::size_t index) noexcept
|
||||||
|
{
|
||||||
|
++plan.unsupported_command_count;
|
||||||
|
if (plan.first_unsupported_command == OpenGlCommandPlan::npos) {
|
||||||
|
plan.first_unsupported_command = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void record_render_pass_order_error(OpenGlCommandPlan& plan, std::size_t index) noexcept
|
||||||
|
{
|
||||||
|
++plan.render_pass_order_error_count;
|
||||||
|
if (plan.first_render_pass_order_error == OpenGlCommandPlan::npos) {
|
||||||
|
plan.first_render_pass_order_error = index;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept
|
const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept
|
||||||
@@ -169,4 +185,67 @@ OpenGlPlannedCommand plan_recorded_render_command(pp::renderer::RecordedRenderCo
|
|||||||
return planned;
|
return planned;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
OpenGlCommandPlan plan_recorded_render_commands(
|
||||||
|
std::span<const pp::renderer::RecordedRenderCommand> commands)
|
||||||
|
{
|
||||||
|
OpenGlCommandPlan plan;
|
||||||
|
plan.commands.reserve(commands.size());
|
||||||
|
|
||||||
|
bool in_render_pass = false;
|
||||||
|
for (std::size_t index = 0; index < commands.size(); ++index) {
|
||||||
|
OpenGlPlannedCommand planned = plan_recorded_render_command(commands[index]);
|
||||||
|
|
||||||
|
if (!planned.supported) {
|
||||||
|
record_unsupported_command(plan, index);
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (planned.kind) {
|
||||||
|
case OpenGlPlannedCommandKind::begin_render_pass:
|
||||||
|
++plan.render_pass_count;
|
||||||
|
if (in_render_pass) {
|
||||||
|
record_render_pass_order_error(plan, index);
|
||||||
|
}
|
||||||
|
in_render_pass = true;
|
||||||
|
break;
|
||||||
|
case OpenGlPlannedCommandKind::end_render_pass:
|
||||||
|
if (!in_render_pass) {
|
||||||
|
record_render_pass_order_error(plan, index);
|
||||||
|
}
|
||||||
|
in_render_pass = false;
|
||||||
|
break;
|
||||||
|
case OpenGlPlannedCommandKind::draw:
|
||||||
|
++plan.draw_command_count;
|
||||||
|
if (!in_render_pass) {
|
||||||
|
record_render_pass_order_error(plan, index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OpenGlPlannedCommandKind::passthrough:
|
||||||
|
++plan.passthrough_command_count;
|
||||||
|
if (planned.requires_render_pass && !in_render_pass) {
|
||||||
|
record_render_pass_order_error(plan, index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
case OpenGlPlannedCommandKind::trace:
|
||||||
|
++plan.trace_command_count;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
if (planned.requires_render_pass && !in_render_pass) {
|
||||||
|
record_render_pass_order_error(plan, index);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.commands.push_back(planned);
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.ended_in_render_pass = in_render_pass;
|
||||||
|
if (in_render_pass) {
|
||||||
|
record_render_pass_order_error(plan, commands.size());
|
||||||
|
}
|
||||||
|
|
||||||
|
plan.supported = plan.unsupported_command_count == 0U
|
||||||
|
&& plan.render_pass_order_error_count == 0U;
|
||||||
|
return plan;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
#include "renderer_api/recording_renderer.h"
|
#include "renderer_api/recording_renderer.h"
|
||||||
#include "renderer_gl/opengl_capabilities.h"
|
#include "renderer_gl/opengl_capabilities.h"
|
||||||
|
|
||||||
|
#include <cstddef>
|
||||||
#include <cstdint>
|
#include <cstdint>
|
||||||
|
#include <span>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace pp::renderer::gl {
|
namespace pp::renderer::gl {
|
||||||
|
|
||||||
@@ -42,8 +45,26 @@ struct OpenGlPlannedCommand {
|
|||||||
bool supported = false;
|
bool supported = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
struct OpenGlCommandPlan {
|
||||||
|
static constexpr std::size_t npos = static_cast<std::size_t>(-1);
|
||||||
|
|
||||||
|
std::vector<OpenGlPlannedCommand> commands;
|
||||||
|
std::uint32_t render_pass_count = 0;
|
||||||
|
std::uint32_t draw_command_count = 0;
|
||||||
|
std::uint32_t passthrough_command_count = 0;
|
||||||
|
std::uint32_t trace_command_count = 0;
|
||||||
|
std::uint32_t unsupported_command_count = 0;
|
||||||
|
std::uint32_t render_pass_order_error_count = 0;
|
||||||
|
std::size_t first_unsupported_command = npos;
|
||||||
|
std::size_t first_render_pass_order_error = npos;
|
||||||
|
bool ended_in_render_pass = false;
|
||||||
|
bool supported = true;
|
||||||
|
};
|
||||||
|
|
||||||
[[nodiscard]] const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept;
|
[[nodiscard]] const char* planned_command_kind_name(OpenGlPlannedCommandKind kind) noexcept;
|
||||||
[[nodiscard]] OpenGlPlannedCommand plan_recorded_render_command(
|
[[nodiscard]] OpenGlPlannedCommand plan_recorded_render_command(
|
||||||
pp::renderer::RecordedRenderCommand command) noexcept;
|
pp::renderer::RecordedRenderCommand command) noexcept;
|
||||||
|
[[nodiscard]] OpenGlCommandPlan plan_recorded_render_commands(
|
||||||
|
std::span<const pp::renderer::RecordedRenderCommand> commands);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,9 +2,54 @@
|
|||||||
#include "test_harness.h"
|
#include "test_harness.h"
|
||||||
|
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
#include <vector>
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
|
|
||||||
|
pp::renderer::RecordedRenderCommand begin_render_pass_command() noexcept
|
||||||
|
{
|
||||||
|
pp::renderer::RecordedRenderCommand command;
|
||||||
|
command.kind = pp::renderer::RecordedRenderCommandKind::begin_render_pass;
|
||||||
|
command.target_desc.extent = pp::renderer::Extent2D { .width = 64U, .height = 64U };
|
||||||
|
command.target_desc.format = pp::renderer::TextureFormat::rgba8;
|
||||||
|
command.clear_color_enabled = true;
|
||||||
|
command.clear_depth_enabled = true;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pp::renderer::RecordedRenderCommand command_with_kind(
|
||||||
|
pp::renderer::RecordedRenderCommandKind kind) noexcept
|
||||||
|
{
|
||||||
|
pp::renderer::RecordedRenderCommand command;
|
||||||
|
command.kind = kind;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pp::renderer::RecordedRenderCommand viewport_command() noexcept
|
||||||
|
{
|
||||||
|
pp::renderer::RecordedRenderCommand command;
|
||||||
|
command.kind = pp::renderer::RecordedRenderCommandKind::set_viewport;
|
||||||
|
command.viewport = pp::renderer::Viewport { .width = 64U, .height = 64U };
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pp::renderer::RecordedRenderCommand draw_command() noexcept
|
||||||
|
{
|
||||||
|
pp::renderer::RecordedRenderCommand command;
|
||||||
|
command.kind = pp::renderer::RecordedRenderCommandKind::draw;
|
||||||
|
command.mesh_desc.topology = pp::renderer::PrimitiveTopology::triangles;
|
||||||
|
command.draw_desc.vertex_count = 3U;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
|
pp::renderer::RecordedRenderCommand blit_command(pp::renderer::BlitFilter filter) noexcept
|
||||||
|
{
|
||||||
|
pp::renderer::RecordedRenderCommand command;
|
||||||
|
command.kind = pp::renderer::RecordedRenderCommandKind::blit_render_target;
|
||||||
|
command.blit_filter = filter;
|
||||||
|
return command;
|
||||||
|
}
|
||||||
|
|
||||||
void maps_render_pass_and_state_commands(pp::tests::Harness& h)
|
void maps_render_pass_and_state_commands(pp::tests::Harness& h)
|
||||||
{
|
{
|
||||||
const auto begin = pp::renderer::gl::plan_recorded_render_command(pp::renderer::RecordedRenderCommand {
|
const auto begin = pp::renderer::gl::plan_recorded_render_command(pp::renderer::RecordedRenderCommand {
|
||||||
@@ -180,6 +225,87 @@ void names_planned_command_kinds(pp::tests::Harness& h)
|
|||||||
== std::string_view("unknown"));
|
== std::string_view("unknown"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void plans_valid_recorded_command_streams(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
const std::vector<pp::renderer::RecordedRenderCommand> commands {
|
||||||
|
command_with_kind(pp::renderer::RecordedRenderCommandKind::trace_begin_scope),
|
||||||
|
begin_render_pass_command(),
|
||||||
|
viewport_command(),
|
||||||
|
command_with_kind(pp::renderer::RecordedRenderCommandKind::bind_shader),
|
||||||
|
draw_command(),
|
||||||
|
command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass),
|
||||||
|
blit_command(pp::renderer::BlitFilter::nearest),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto plan = pp::renderer::gl::plan_recorded_render_commands(commands);
|
||||||
|
|
||||||
|
PP_EXPECT(h, plan.supported);
|
||||||
|
PP_EXPECT(h, plan.commands.size() == commands.size());
|
||||||
|
PP_EXPECT(h, plan.render_pass_count == 1U);
|
||||||
|
PP_EXPECT(h, plan.draw_command_count == 1U);
|
||||||
|
PP_EXPECT(h, plan.passthrough_command_count == 1U);
|
||||||
|
PP_EXPECT(h, plan.trace_command_count == 1U);
|
||||||
|
PP_EXPECT(h, plan.unsupported_command_count == 0U);
|
||||||
|
PP_EXPECT(h, plan.render_pass_order_error_count == 0U);
|
||||||
|
PP_EXPECT(h, plan.first_unsupported_command == pp::renderer::gl::OpenGlCommandPlan::npos);
|
||||||
|
PP_EXPECT(h, plan.first_render_pass_order_error == pp::renderer::gl::OpenGlCommandPlan::npos);
|
||||||
|
PP_EXPECT(h, !plan.ended_in_render_pass);
|
||||||
|
PP_EXPECT(h, plan.commands[1].kind == pp::renderer::gl::OpenGlPlannedCommandKind::begin_render_pass);
|
||||||
|
PP_EXPECT(h, plan.commands[3].kind == pp::renderer::gl::OpenGlPlannedCommandKind::passthrough);
|
||||||
|
PP_EXPECT(h, plan.commands[4].kind == pp::renderer::gl::OpenGlPlannedCommandKind::draw);
|
||||||
|
PP_EXPECT(h, plan.commands[6].kind == pp::renderer::gl::OpenGlPlannedCommandKind::blit_render_target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void flags_broken_render_pass_command_order(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
const std::vector<pp::renderer::RecordedRenderCommand> outside_pass {
|
||||||
|
viewport_command(),
|
||||||
|
};
|
||||||
|
const std::vector<pp::renderer::RecordedRenderCommand> nested_pass {
|
||||||
|
begin_render_pass_command(),
|
||||||
|
begin_render_pass_command(),
|
||||||
|
command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass),
|
||||||
|
};
|
||||||
|
const std::vector<pp::renderer::RecordedRenderCommand> unclosed_pass {
|
||||||
|
begin_render_pass_command(),
|
||||||
|
draw_command(),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto outside = pp::renderer::gl::plan_recorded_render_commands(outside_pass);
|
||||||
|
const auto nested = pp::renderer::gl::plan_recorded_render_commands(nested_pass);
|
||||||
|
const auto unclosed = pp::renderer::gl::plan_recorded_render_commands(unclosed_pass);
|
||||||
|
|
||||||
|
PP_EXPECT(h, !outside.supported);
|
||||||
|
PP_EXPECT(h, outside.render_pass_order_error_count == 1U);
|
||||||
|
PP_EXPECT(h, outside.first_render_pass_order_error == 0U);
|
||||||
|
PP_EXPECT(h, !nested.supported);
|
||||||
|
PP_EXPECT(h, nested.render_pass_order_error_count == 1U);
|
||||||
|
PP_EXPECT(h, nested.first_render_pass_order_error == 1U);
|
||||||
|
PP_EXPECT(h, nested.render_pass_count == 2U);
|
||||||
|
PP_EXPECT(h, !unclosed.supported);
|
||||||
|
PP_EXPECT(h, unclosed.render_pass_order_error_count == 1U);
|
||||||
|
PP_EXPECT(h, unclosed.first_render_pass_order_error == unclosed_pass.size());
|
||||||
|
PP_EXPECT(h, unclosed.ended_in_render_pass);
|
||||||
|
}
|
||||||
|
|
||||||
|
void tracks_unsupported_commands_in_streams(pp::tests::Harness& h)
|
||||||
|
{
|
||||||
|
std::vector<pp::renderer::RecordedRenderCommand> commands {
|
||||||
|
begin_render_pass_command(),
|
||||||
|
draw_command(),
|
||||||
|
command_with_kind(pp::renderer::RecordedRenderCommandKind::end_render_pass),
|
||||||
|
command_with_kind(static_cast<pp::renderer::RecordedRenderCommandKind>(255U)),
|
||||||
|
};
|
||||||
|
|
||||||
|
const auto plan = pp::renderer::gl::plan_recorded_render_commands(commands);
|
||||||
|
|
||||||
|
PP_EXPECT(h, !plan.supported);
|
||||||
|
PP_EXPECT(h, plan.unsupported_command_count == 1U);
|
||||||
|
PP_EXPECT(h, plan.first_unsupported_command == 3U);
|
||||||
|
PP_EXPECT(h, plan.render_pass_order_error_count == 0U);
|
||||||
|
PP_EXPECT(h, plan.commands[3].kind == pp::renderer::gl::OpenGlPlannedCommandKind::unknown);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
int main()
|
int main()
|
||||||
@@ -189,5 +315,8 @@ int main()
|
|||||||
harness.run("maps_binding_draw_and_blit_commands", maps_binding_draw_and_blit_commands);
|
harness.run("maps_binding_draw_and_blit_commands", maps_binding_draw_and_blit_commands);
|
||||||
harness.run("rejects_unsupported_command_tokens", rejects_unsupported_command_tokens);
|
harness.run("rejects_unsupported_command_tokens", rejects_unsupported_command_tokens);
|
||||||
harness.run("names_planned_command_kinds", names_planned_command_kinds);
|
harness.run("names_planned_command_kinds", names_planned_command_kinds);
|
||||||
|
harness.run("plans_valid_recorded_command_streams", plans_valid_recorded_command_streams);
|
||||||
|
harness.run("flags_broken_render_pass_command_order", flags_broken_render_pass_command_order);
|
||||||
|
harness.run("tracks_unsupported_commands_in_streams", tracks_unsupported_commands_in_streams);
|
||||||
return harness.finish();
|
return harness.finish();
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user