renderer: add replay IR scaffolding

This commit is contained in:
salh
2026-04-18 09:51:58 +03:00
parent 2701342e34
commit 84334d3aec
10 changed files with 442 additions and 36 deletions
+1
View File
@@ -24,6 +24,7 @@ set(AC6RECOMP_SOURCES
src/ac6_native_graphics_overlay.cpp
src/ac6_native_renderer/ac6_render_frontend.cpp
src/ac6_native_renderer/frame_plan.cpp
src/ac6_native_renderer/replay_ir.cpp
src/ac6_native_renderer/backends/backend_factory.cpp
src/ac6_native_renderer/backends/d3d12_backend.cpp
src/ac6_native_renderer/backends/metal_backend.cpp
+2
View File
@@ -90,6 +90,8 @@ bool EnsureInitialized() {
void UpdateStatusFromRendererUnlocked() {
g_runtime_status.renderer_stats = g_native_renderer.GetStats();
g_runtime_status.active_backend = g_runtime_status.renderer_stats.active_backend;
g_runtime_status.frontend_summary = g_native_renderer.frontend_summary();
g_runtime_status.replay_summary = g_native_renderer.replay_summary();
g_runtime_status.frame_plan = g_native_renderer.frame_plan();
}
+4
View File
@@ -2,7 +2,9 @@
#include <cstdint>
#include "ac6_native_renderer/ac6_render_frontend.h"
#include "ac6_native_renderer/frame_plan.h"
#include "ac6_native_renderer/replay_ir.h"
#include "ac6_native_renderer/types.h"
#include "d3d_state.h"
@@ -19,6 +21,8 @@ struct NativeGraphicsRuntimeStatus {
ac6::renderer::BackendType active_backend = ac6::renderer::BackendType::kUnknown;
ac6::renderer::FeatureLevel feature_level = ac6::renderer::FeatureLevel::kBootstrap;
ac6::renderer::NativeRendererStats renderer_stats{};
ac6::renderer::FrontendFrameSummary frontend_summary{};
ac6::renderer::ReplayFrameSummary replay_summary{};
ac6::d3d::FrameCaptureSummary capture_summary{};
ac6::renderer::NativeFramePlan frame_plan{};
};
+7
View File
@@ -37,6 +37,10 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
static_cast<unsigned long long>(status.renderer_stats.frame_count));
ImGui::Text("render passes built: %llu",
static_cast<unsigned long long>(status.renderer_stats.built_pass_count));
ImGui::Text("frontend passes/commands: %u / %u", status.frontend_summary.pass_count,
status.frontend_summary.total_command_count);
ImGui::Text("replay passes/commands: %u / %u", status.replay_summary.pass_count,
status.replay_summary.command_count);
ImGui::Separator();
ImGui::Text("capture frame: %llu",
static_cast<unsigned long long>(status.capture_summary.frame_index));
@@ -64,6 +68,9 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
ImGui::Separator();
ImGui::Text("planned output: %ux%u", status.frame_plan.output_width,
status.frame_plan.output_height);
ImGui::Text("replay output/present: %ux%u / %s", status.replay_summary.output_width,
status.replay_summary.output_height,
status.replay_summary.has_present_pass ? "yes" : "no");
ImGui::Text("stages scene/post/ui: %s / %s / %s",
status.frame_plan.has_scene_stage ? "yes" : "no",
status.frame_plan.has_post_process_stage ? "yes" : "no",
@@ -48,9 +48,74 @@ enum class CapturedFrameEventType : uint8_t {
struct CapturedFrameEvent {
uint32_t sequence = 0;
CapturedFrameEventType type = CapturedFrameEventType::kDraw;
const ac6::d3d::DrawCallRecord* draw = nullptr;
const ac6::d3d::ClearRecord* clear = nullptr;
const ac6::d3d::ResolveRecord* resolve = nullptr;
const ac6::d3d::ShadowState* shadow_state = nullptr;
};
ObservedCommandDesc MakeObservedCommand(const ac6::d3d::DrawCallRecord& draw) {
return ObservedCommandDesc{
.type = ObservedCommandType::kDraw,
.sequence = draw.sequence,
.draw_kind = draw.kind,
.primitive_type = draw.primitive_type,
.start = draw.start,
.count = draw.count,
.flags = draw.flags,
.texture_count = CountNonZeroEntries(draw.shadow_state.textures),
.stream_count = CountBoundStreams(draw.shadow_state.streams),
.sampler_count = CountBoundSamplers(draw.shadow_state.samplers),
.fetch_constant_count = CountNonZeroEntries(draw.shadow_state.texture_fetch_ptrs),
.render_target_0 = draw.shadow_state.render_targets[0],
.depth_stencil = draw.shadow_state.depth_stencil,
.viewport_x = draw.shadow_state.viewport.x,
.viewport_y = draw.shadow_state.viewport.y,
.viewport_width = draw.shadow_state.viewport.width,
.viewport_height = draw.shadow_state.viewport.height,
};
}
ObservedCommandDesc MakeObservedCommand(const ac6::d3d::ClearRecord& clear) {
return ObservedCommandDesc{
.type = ObservedCommandType::kClear,
.sequence = clear.sequence,
.rect_count = clear.rect_count,
.captured_rect_count = clear.captured_rect_count,
.flags = clear.flags,
.color = clear.color,
.stencil = clear.stencil,
.depth = clear.depth,
.texture_count = CountNonZeroEntries(clear.shadow_state.textures),
.stream_count = CountBoundStreams(clear.shadow_state.streams),
.sampler_count = CountBoundSamplers(clear.shadow_state.samplers),
.fetch_constant_count = CountNonZeroEntries(clear.shadow_state.texture_fetch_ptrs),
.render_target_0 = clear.shadow_state.render_targets[0],
.depth_stencil = clear.shadow_state.depth_stencil,
.viewport_x = clear.shadow_state.viewport.x,
.viewport_y = clear.shadow_state.viewport.y,
.viewport_width = clear.shadow_state.viewport.width,
.viewport_height = clear.shadow_state.viewport.height,
};
}
ObservedCommandDesc MakeObservedCommand(const ac6::d3d::ResolveRecord& resolve) {
return ObservedCommandDesc{
.type = ObservedCommandType::kResolve,
.sequence = resolve.sequence,
.texture_count = CountNonZeroEntries(resolve.shadow_state.textures),
.stream_count = CountBoundStreams(resolve.shadow_state.streams),
.sampler_count = CountBoundSamplers(resolve.shadow_state.samplers),
.fetch_constant_count = CountNonZeroEntries(resolve.shadow_state.texture_fetch_ptrs),
.render_target_0 = resolve.shadow_state.render_targets[0],
.depth_stencil = resolve.shadow_state.depth_stencil,
.viewport_x = resolve.shadow_state.viewport.x,
.viewport_y = resolve.shadow_state.viewport.y,
.viewport_width = resolve.shadow_state.viewport.width,
.viewport_height = resolve.shadow_state.viewport.height,
};
}
bool SamePassBinding(const ac6::d3d::ShadowState& left,
const ac6::d3d::ShadowState& right) {
return left.render_targets[0] == right.render_targets[0] &&
@@ -121,6 +186,19 @@ const char* ToString(ObservedPassKind kind) {
}
}
const char* ToString(ObservedCommandType type) {
switch (type) {
case ObservedCommandType::kDraw:
return "draw";
case ObservedCommandType::kClear:
return "clear";
case ObservedCommandType::kResolve:
return "resolve";
default:
return "unknown";
}
}
void Ac6RenderFrontend::Reset() {
summary_ = {};
passes_.clear();
@@ -143,18 +221,18 @@ FrontendFrameSummary Ac6RenderFrontend::BuildFromCapture(
frame_capture.resolves.size());
for (const auto& draw : frame_capture.draws) {
events.push_back({draw.sequence, CapturedFrameEventType::kDraw,
&draw.shadow_state});
events.push_back({draw.sequence, CapturedFrameEventType::kDraw, &draw, nullptr,
nullptr, &draw.shadow_state});
++summary_.total_draw_count;
}
for (const auto& clear : frame_capture.clears) {
events.push_back({clear.sequence, CapturedFrameEventType::kClear,
&clear.shadow_state});
events.push_back({clear.sequence, CapturedFrameEventType::kClear, nullptr, &clear,
nullptr, &clear.shadow_state});
++summary_.total_clear_count;
}
for (const auto& resolve : frame_capture.resolves) {
events.push_back({resolve.sequence, CapturedFrameEventType::kResolve,
&resolve.shadow_state});
events.push_back({resolve.sequence, CapturedFrameEventType::kResolve, nullptr, nullptr,
&resolve, &resolve.shadow_state});
++summary_.total_resolve_count;
}
@@ -198,6 +276,10 @@ FrontendFrameSummary Ac6RenderFrontend::BuildFromCapture(
switch (event.type) {
case CapturedFrameEventType::kDraw:
++current_pass.draw_count;
if (event.draw != nullptr) {
current_pass.commands.push_back(MakeObservedCommand(*event.draw));
++summary_.total_command_count;
}
if (event.shadow_state != nullptr) {
current_pass.max_texture_count = std::max(
current_pass.max_texture_count,
@@ -215,9 +297,17 @@ FrontendFrameSummary Ac6RenderFrontend::BuildFromCapture(
break;
case CapturedFrameEventType::kClear:
++current_pass.clear_count;
if (event.clear != nullptr) {
current_pass.commands.push_back(MakeObservedCommand(*event.clear));
++summary_.total_command_count;
}
break;
case CapturedFrameEventType::kResolve:
++current_pass.resolve_count;
if (event.resolve != nullptr) {
current_pass.commands.push_back(MakeObservedCommand(*event.resolve));
++summary_.total_command_count;
}
break;
}
}
@@ -14,6 +14,37 @@ enum class ObservedPassKind : uint8_t {
kUiComposite = 3,
};
enum class ObservedCommandType : uint8_t {
kDraw = 0,
kClear = 1,
kResolve = 2,
};
struct ObservedCommandDesc {
ObservedCommandType type = ObservedCommandType::kDraw;
uint32_t sequence = 0;
ac6::d3d::DrawCallKind draw_kind = ac6::d3d::DrawCallKind::kIndexed;
uint32_t primitive_type = 0;
uint32_t start = 0;
uint32_t count = 0;
uint32_t flags = 0;
uint32_t rect_count = 0;
uint32_t captured_rect_count = 0;
uint32_t color = 0;
uint32_t stencil = 0;
float depth = 1.0f;
uint32_t texture_count = 0;
uint32_t stream_count = 0;
uint32_t sampler_count = 0;
uint32_t fetch_constant_count = 0;
uint32_t render_target_0 = 0;
uint32_t depth_stencil = 0;
uint32_t viewport_x = 0;
uint32_t viewport_y = 0;
uint32_t viewport_width = 0;
uint32_t viewport_height = 0;
};
struct ObservedPassDesc {
ObservedPassKind kind = ObservedPassKind::kUnknown;
uint32_t start_sequence = 0;
@@ -37,12 +68,14 @@ struct ObservedPassDesc {
uint32_t viewport_height = 0;
bool selected_for_present = false;
bool matches_frame_end_viewport = false;
std::vector<ObservedCommandDesc> commands;
};
struct FrontendFrameSummary {
bool capture_valid = false;
uint64_t frame_index = 0;
uint32_t pass_count = 0;
uint32_t total_command_count = 0;
uint32_t scene_pass_count = 0;
uint32_t post_process_pass_count = 0;
uint32_t ui_pass_count = 0;
@@ -66,5 +99,6 @@ class Ac6RenderFrontend {
};
const char* ToString(ObservedPassKind kind);
const char* ToString(ObservedCommandType type);
} // namespace ac6::renderer
+39 -30
View File
@@ -21,10 +21,21 @@ RenderPassKind ToRenderPassKind(ObservedPassKind kind) {
}
}
std::string BuildObservedPassName(const ObservedPassDesc& pass,
uint32_t pass_index) {
return "ac6.observed." + std::string(ToString(pass.kind)) + "." +
std::to_string(pass_index);
RenderPassKind ToRenderPassKind(ReplayPassRole role) {
switch (role) {
case ReplayPassRole::kScene:
return RenderPassKind::kScene;
case ReplayPassRole::kPostProcess:
case ReplayPassRole::kPresent:
return RenderPassKind::kPostProcess;
case ReplayPassRole::kUiComposite:
return RenderPassKind::kUiComposite;
case ReplayPassRole::kBootstrap:
return RenderPassKind::kBootstrap;
case ReplayPassRole::kUnknown:
default:
return RenderPassKind::kUnknown;
}
}
} // namespace
@@ -58,6 +69,7 @@ void NativeRenderer::Shutdown() {
graph_.Reset();
frontend_.Reset();
frame_plan_ = {};
replay_frame_ = {};
stats_ = {};
}
@@ -76,14 +88,25 @@ void NativeRenderer::BuildBootstrapFrame() {
}
frame_plan_ = {};
replay_frame_ = replay_builder_.BuildBootstrapFrame(scheduler_.frame_index());
// Phase-1: do not present. Build a minimal graph to prove deterministic
// ownership without touching Rexglue emulation paths.
graph_.AddPass(RenderPassDesc{
.name = "ac6.native.bootstrap",
.kind = RenderPassKind::kBootstrap,
.async_compute = false,
});
for (const ReplayPassDesc& pass : replay_frame_.passes) {
graph_.AddPass(RenderPassDesc{
.name = pass.name,
.kind = ToRenderPassKind(pass.role),
.async_compute = false,
.draw_count = pass.draw_count,
.clear_count = pass.clear_count,
.resolve_count = pass.resolve_count,
.render_target_0 = pass.render_target_0,
.depth_stencil = pass.depth_stencil,
.viewport_width = pass.viewport_width,
.viewport_height = pass.viewport_height,
.selected_for_present = pass.selected_for_present,
});
}
stats_.built_pass_count += graph_.pass_count();
REXLOG_TRACE("AC6 native renderer bootstrap frame built passes={}",
@@ -103,12 +126,12 @@ void NativeRenderer::BuildCapturedFrame(
}
frame_plan_ = planner_.Build(summary, frontend_.passes());
replay_frame_ = replay_builder_.Build(summary, frontend_.passes(), frame_plan_);
uint32_t pass_index = 0;
for (const ObservedPassDesc& pass : frontend_.passes()) {
for (const ReplayPassDesc& pass : replay_frame_.passes) {
graph_.AddPass(RenderPassDesc{
.name = BuildObservedPassName(pass, pass_index++),
.kind = ToRenderPassKind(pass.kind),
.name = pass.name,
.kind = ToRenderPassKind(pass.role),
.async_compute = false,
.draw_count = pass.draw_count,
.clear_count = pass.clear_count,
@@ -120,26 +143,12 @@ void NativeRenderer::BuildCapturedFrame(
.selected_for_present = pass.selected_for_present,
});
}
if (frame_plan_.valid && frame_plan_.requires_present_pass) {
graph_.AddPass(RenderPassDesc{
.name = "ac6.plan.present",
.kind = RenderPassKind::kPostProcess,
.async_compute = false,
.draw_count = 0,
.clear_count = 0,
.resolve_count = 0,
.render_target_0 = frame_plan_.present_stage.render_target_0,
.depth_stencil = frame_plan_.present_stage.depth_stencil,
.viewport_width = frame_plan_.output_width,
.viewport_height = frame_plan_.output_height,
.selected_for_present = true,
});
}
stats_.built_pass_count += graph_.pass_count();
REXLOG_TRACE(
"AC6 native renderer observed frame={} passes={} selected={} draws={} clears={} resolves={} plan_valid={} out={}x{}",
summary.frame_index, summary.pass_count, summary.selected_pass_index,
"AC6 native renderer observed frame={} frontend_passes={} replay_passes={} replay_commands={} selected={} draws={} clears={} resolves={} plan_valid={} out={}x{}",
summary.frame_index, summary.pass_count, replay_frame_.summary.pass_count,
replay_frame_.summary.command_count, summary.selected_pass_index,
summary.total_draw_count, summary.total_clear_count,
summary.total_resolve_count, frame_plan_.valid, frame_plan_.output_width,
frame_plan_.output_height);
@@ -5,6 +5,7 @@
#include "ac6_render_frontend.h"
#include "frame_scheduler.h"
#include "frame_plan.h"
#include "replay_ir.h"
#include "render_device.h"
#include "render_graph.h"
#include "types.h"
@@ -35,6 +36,8 @@ class NativeRenderer {
return frontend_.passes();
}
NativeFramePlan frame_plan() const { return frame_plan_; }
ReplayFrameSummary replay_summary() const { return replay_frame_.summary; }
const ReplayFrame& replay_frame() const { return replay_frame_; }
private:
NativeRendererConfig config_{};
@@ -44,7 +47,9 @@ class NativeRenderer {
FrameScheduler scheduler_{};
Ac6RenderFrontend frontend_{};
FramePlanner planner_{};
ReplayIrBuilder replay_builder_{};
NativeFramePlan frame_plan_{};
ReplayFrame replay_frame_{};
};
} // namespace ac6::renderer
+164
View File
@@ -0,0 +1,164 @@
#include "replay_ir.h"
#include <string>
namespace ac6::renderer {
namespace {
ReplayPassRole ToReplayPassRole(ObservedPassKind kind) {
switch (kind) {
case ObservedPassKind::kScene:
return ReplayPassRole::kScene;
case ObservedPassKind::kPostProcess:
return ReplayPassRole::kPostProcess;
case ObservedPassKind::kUiComposite:
return ReplayPassRole::kUiComposite;
case ObservedPassKind::kUnknown:
default:
return ReplayPassRole::kUnknown;
}
}
std::string BuildObservedPassName(const ObservedPassDesc& pass,
uint32_t pass_index) {
return "ac6.replay." + std::string(ToString(pass.kind)) + "." +
std::to_string(pass_index);
}
ReplayCommandDesc BuildReplayCommand(const ObservedCommandDesc& command) {
return ReplayCommandDesc{
.type = command.type,
.sequence = command.sequence,
.draw_kind = command.draw_kind,
.primitive_type = command.primitive_type,
.start = command.start,
.count = command.count,
.flags = command.flags,
.rect_count = command.rect_count,
.captured_rect_count = command.captured_rect_count,
.color = command.color,
.stencil = command.stencil,
.depth = command.depth,
.texture_count = command.texture_count,
.stream_count = command.stream_count,
.sampler_count = command.sampler_count,
.fetch_constant_count = command.fetch_constant_count,
.render_target_0 = command.render_target_0,
.depth_stencil = command.depth_stencil,
.viewport_x = command.viewport_x,
.viewport_y = command.viewport_y,
.viewport_width = command.viewport_width,
.viewport_height = command.viewport_height,
};
}
ReplayPassDesc BuildReplayPass(const ObservedPassDesc& pass, uint32_t pass_index) {
ReplayPassDesc replay_pass;
replay_pass.name = BuildObservedPassName(pass, pass_index);
replay_pass.role = ToReplayPassRole(pass.kind);
replay_pass.source_pass_valid = true;
replay_pass.source_pass_index = pass_index;
replay_pass.draw_count = pass.draw_count;
replay_pass.clear_count = pass.clear_count;
replay_pass.resolve_count = pass.resolve_count;
replay_pass.render_target_0 = pass.render_target_0;
replay_pass.depth_stencil = pass.depth_stencil;
replay_pass.viewport_width = pass.viewport_width;
replay_pass.viewport_height = pass.viewport_height;
replay_pass.selected_for_present = pass.selected_for_present;
replay_pass.commands.reserve(pass.commands.size());
for (const ObservedCommandDesc& command : pass.commands) {
replay_pass.commands.push_back(BuildReplayCommand(command));
}
return replay_pass;
}
void AccumulateSummary(ReplayFrameSummary& summary, const ReplayPassDesc& pass) {
++summary.pass_count;
summary.command_count += static_cast<uint32_t>(pass.commands.size());
summary.draw_count += pass.draw_count;
summary.clear_count += pass.clear_count;
summary.resolve_count += pass.resolve_count;
summary.valid = summary.pass_count != 0;
}
} // namespace
const char* ToString(ReplayPassRole role) {
switch (role) {
case ReplayPassRole::kBootstrap:
return "bootstrap";
case ReplayPassRole::kScene:
return "scene";
case ReplayPassRole::kPostProcess:
return "post_process";
case ReplayPassRole::kUiComposite:
return "ui_composite";
case ReplayPassRole::kPresent:
return "present";
case ReplayPassRole::kUnknown:
default:
return "unknown";
}
}
ReplayFrame ReplayIrBuilder::BuildBootstrapFrame(uint64_t frame_index) const {
ReplayFrame replay_frame;
replay_frame.summary.frame_index = frame_index;
ReplayPassDesc bootstrap_pass;
bootstrap_pass.name = "ac6.replay.bootstrap";
bootstrap_pass.role = ReplayPassRole::kBootstrap;
bootstrap_pass.viewport_width = 0;
bootstrap_pass.viewport_height = 0;
AccumulateSummary(replay_frame.summary, bootstrap_pass);
replay_frame.passes.push_back(std::move(bootstrap_pass));
return replay_frame;
}
ReplayFrame ReplayIrBuilder::Build(
const FrontendFrameSummary& summary,
const std::vector<ObservedPassDesc>& passes,
const NativeFramePlan& frame_plan) const {
ReplayFrame replay_frame;
replay_frame.summary.frame_index = summary.frame_index;
replay_frame.summary.output_width = frame_plan.output_width;
replay_frame.summary.output_height = frame_plan.output_height;
if (!summary.capture_valid || passes.empty()) {
return replay_frame;
}
replay_frame.passes.reserve(passes.size() + (frame_plan.requires_present_pass ? 1u : 0u));
for (uint32_t i = 0; i < passes.size(); ++i) {
ReplayPassDesc replay_pass = BuildReplayPass(passes[i], i);
AccumulateSummary(replay_frame.summary, replay_pass);
replay_frame.passes.push_back(std::move(replay_pass));
}
if (frame_plan.valid && frame_plan.requires_present_pass) {
ReplayPassDesc present_pass;
present_pass.name = "ac6.replay.present";
present_pass.role = ReplayPassRole::kPresent;
present_pass.source_pass_valid = frame_plan.present_stage.valid;
present_pass.source_pass_index = frame_plan.present_stage.pass_index;
present_pass.render_target_0 = frame_plan.present_stage.render_target_0;
present_pass.depth_stencil = frame_plan.present_stage.depth_stencil;
present_pass.viewport_width = frame_plan.output_width;
present_pass.viewport_height = frame_plan.output_height;
present_pass.selected_for_present = true;
replay_frame.summary.has_present_pass = true;
AccumulateSummary(replay_frame.summary, present_pass);
replay_frame.passes.push_back(std::move(present_pass));
}
replay_frame.summary.valid =
replay_frame.summary.pass_count != 0 &&
(!frame_plan.valid ||
(replay_frame.summary.output_width != 0 &&
replay_frame.summary.output_height != 0));
return replay_frame;
}
} // namespace ac6::renderer
+90
View File
@@ -0,0 +1,90 @@
#pragma once
#include <cstdint>
#include <string>
#include <vector>
#include "ac6_render_frontend.h"
#include "frame_plan.h"
namespace ac6::renderer {
enum class ReplayPassRole : uint8_t {
kUnknown = 0,
kBootstrap = 1,
kScene = 2,
kPostProcess = 3,
kUiComposite = 4,
kPresent = 5,
};
struct ReplayCommandDesc {
ObservedCommandType type = ObservedCommandType::kDraw;
uint32_t sequence = 0;
ac6::d3d::DrawCallKind draw_kind = ac6::d3d::DrawCallKind::kIndexed;
uint32_t primitive_type = 0;
uint32_t start = 0;
uint32_t count = 0;
uint32_t flags = 0;
uint32_t rect_count = 0;
uint32_t captured_rect_count = 0;
uint32_t color = 0;
uint32_t stencil = 0;
float depth = 1.0f;
uint32_t texture_count = 0;
uint32_t stream_count = 0;
uint32_t sampler_count = 0;
uint32_t fetch_constant_count = 0;
uint32_t render_target_0 = 0;
uint32_t depth_stencil = 0;
uint32_t viewport_x = 0;
uint32_t viewport_y = 0;
uint32_t viewport_width = 0;
uint32_t viewport_height = 0;
};
struct ReplayPassDesc {
std::string name;
ReplayPassRole role = ReplayPassRole::kUnknown;
bool source_pass_valid = false;
uint32_t source_pass_index = 0;
uint32_t draw_count = 0;
uint32_t clear_count = 0;
uint32_t resolve_count = 0;
uint32_t render_target_0 = 0;
uint32_t depth_stencil = 0;
uint32_t viewport_width = 0;
uint32_t viewport_height = 0;
bool selected_for_present = false;
std::vector<ReplayCommandDesc> commands;
};
struct ReplayFrameSummary {
bool valid = false;
uint64_t frame_index = 0;
uint32_t pass_count = 0;
uint32_t command_count = 0;
uint32_t draw_count = 0;
uint32_t clear_count = 0;
uint32_t resolve_count = 0;
uint32_t output_width = 0;
uint32_t output_height = 0;
bool has_present_pass = false;
};
struct ReplayFrame {
ReplayFrameSummary summary{};
std::vector<ReplayPassDesc> passes;
};
class ReplayIrBuilder {
public:
ReplayFrame BuildBootstrapFrame(uint64_t frame_index) const;
ReplayFrame Build(const FrontendFrameSummary& summary,
const std::vector<ObservedPassDesc>& passes,
const NativeFramePlan& frame_plan) const;
};
const char* ToString(ReplayPassRole role);
} // namespace ac6::renderer