Wire executor frames into backend submission scaffolding

This commit is contained in:
salh
2026-04-18 11:44:18 +03:00
parent e0cf1eea03
commit 440bbbca88
15 changed files with 165 additions and 9 deletions
+11 -7
View File
@@ -29,29 +29,33 @@ Work Completed
- Added a new replay-executor layer in `replay_executor.h` and `replay_executor.cpp`.
- Introduced `SubmissionQueueType`, `ReplayExecutorCommandPacket`, `ReplayExecutorPassPacket`, `ReplayExecutorFrameSummary`, and `ReplayExecutorFrame`.
- Added `ReplayExecutorPlanBuilder` so the renderer can derive submission-oriented pass packets from `ExecutionFramePlan`.
- Updated `NativeRenderer` to build replay IR first, then execution plan, then replay-executor packets, then derive the current `RenderGraph` from executor passes.
- Added a backend executor-consumption contract in `render_device.h` and `render_device.cpp`.
- Introduced `BackendExecutorStatus` plus backend-facing `SubmitExecutorFrame()` reporting for active backends.
- Updated `NativeRenderer` to build replay IR first, then execution plan, then replay-executor packets, submit them to the active backend scaffold, then derive the current `RenderGraph` from executor passes.
- Exposed replay summary data through `ac6_native_graphics.h` and `ac6_native_graphics.cpp`.
- Exposed execution-plan summary data through `ac6_native_graphics.h` and `ac6_native_graphics.cpp`.
- Exposed replay-executor summary data through `ac6_native_graphics.h` and `ac6_native_graphics.cpp`.
- Surfaced replay, execution, and executor pass/command counts plus output-present state in `ac6_native_graphics_overlay.cpp`.
- Exposed backend executor status through `ac6_native_graphics.h` and `ac6_native_graphics.cpp`.
- Surfaced replay, execution, executor, and backend-consumption pass/command state in `ac6_native_graphics_overlay.cpp`.
- Updated `CMakeLists.txt` to compile `replay_ir.cpp`, `execution_plan.cpp`, and `replay_executor.cpp`.
Why This Matters
- The renderer no longer stops at pass heuristics alone; it now carries replay IR, execution-plan, and executor artifacts forward.
- This creates the bridge between capture analysis and future backend execution without forcing full D3D12 command-list submission too early.
- The execution plan tracks stable per-pass resource requirements and command categories, while the replay executor now shapes queue-ready submission packets.
- The overlay now shows whether frontend analysis, replay IR, execution planning, and executor shaping stay aligned frame to frame.
- The execution plan tracks stable per-pass resource requirements and command categories, while the replay executor shapes queue-ready submission packets and the backend scaffold now consumes them directly.
- The D3D12 path now records submission-oriented frame, pass, resource, pipeline, and descriptor counts even before real command-list recording exists.
- The overlay now shows whether frontend analysis, replay IR, execution planning, executor shaping, and backend consumption stay aligned frame to frame.
Verification
- VS Code diagnostics are clean for the edited files.
- Full preset build verification is still blocked by an existing Ninja build-tree issue: `Re-checking globbed directories... ninja: fatal: GetOverlappedResult: The operation completed successfully.`
- A fresh scratch configure also cannot complete in the current terminal environment because no C++ compiler is available in `PATH`.
- I did not see source-level diagnostics from the replay-executor changes themselves.
- I did not see source-level diagnostics from the backend-consumption scaffold changes themselves.
Next Step
- Start consuming `ReplayExecutorFrame` in a real D3D12 submission path instead of only translating it back into `RenderGraph`.
- Replace the D3D12 scaffold `SubmitExecutorFrame()` path with real device, queue, allocator, fence, and command-list recording.
- Add guest-to-host resource translation for executor packets: render targets, depth, textures, vertex/index buffers, and fetch constants.
- Add D3D12-side placeholders for PSO binding, descriptor setup, and barrier/state transitions while the executor contract stabilizes.
- Upgrade the current submission counters into actual PSO binding, descriptor setup, and barrier/state transitions for the selected passes.
+2
View File
@@ -94,6 +94,8 @@ void UpdateStatusFromRendererUnlocked() {
g_runtime_status.replay_summary = g_native_renderer.replay_summary();
g_runtime_status.execution_summary = g_native_renderer.execution_summary();
g_runtime_status.executor_summary = g_native_renderer.executor_summary();
g_runtime_status.backend_executor_status =
g_native_renderer.backend_executor_status();
g_runtime_status.frame_plan = g_native_renderer.frame_plan();
}
+1
View File
@@ -27,6 +27,7 @@ struct NativeGraphicsRuntimeStatus {
ac6::renderer::ReplayFrameSummary replay_summary{};
ac6::renderer::ExecutionFrameSummary execution_summary{};
ac6::renderer::ReplayExecutorFrameSummary executor_summary{};
ac6::renderer::BackendExecutorStatus backend_executor_status{};
ac6::d3d::FrameCaptureSummary capture_summary{};
ac6::renderer::NativeFramePlan frame_plan{};
};
+10
View File
@@ -37,6 +37,8 @@ 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("backend submits: %llu",
static_cast<unsigned long long>(status.renderer_stats.backend_submit_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,
@@ -85,6 +87,14 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
status.executor_summary.graphics_pass_count,
status.executor_summary.present_pass_count,
status.executor_summary.resource_translation_pass_count);
ImGui::Text("backend consumed frame/passes/cmds: %s / %u / %u",
status.backend_executor_status.frame_valid ? "yes" : "no",
status.backend_executor_status.submitted_pass_count,
status.backend_executor_status.submitted_command_count);
ImGui::Text("backend resource/pso/descriptors: %u / %u / %u",
status.backend_executor_status.resource_translation_pass_count,
status.backend_executor_status.pipeline_state_pass_count,
status.backend_executor_status.descriptor_setup_pass_count);
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",
@@ -23,15 +23,50 @@ bool D3D12Backend::Initialize(const NativeRendererConfig& config) {
}
// Phase-1 scaffold: we deliberately do not create a device yet, to avoid
// conflicting with the existing Rexglue provider during parallel bring-up.
executor_status_ = {};
executor_status_.initialized = true;
initialized_ = true;
REXLOG_INFO("AC6 native renderer D3D12 backend initialized (scaffold)");
return true;
}
bool D3D12Backend::SubmitExecutorFrame(const ReplayExecutorFrame& frame) {
if (!initialized_) {
return false;
}
executor_status_ = {
.initialized = true,
.frame_valid = frame.summary.valid,
.frame_index = frame.summary.frame_index,
.submitted_pass_count = frame.summary.pass_count,
.submitted_command_count = frame.summary.command_count,
.graphics_pass_count = frame.summary.graphics_pass_count,
.async_compute_pass_count = frame.summary.async_compute_pass_count,
.copy_pass_count = frame.summary.copy_pass_count,
.present_pass_count = frame.summary.present_pass_count,
.resource_translation_pass_count =
frame.summary.resource_translation_pass_count,
.pipeline_state_pass_count = frame.summary.pipeline_state_pass_count,
.descriptor_setup_pass_count = frame.summary.descriptor_setup_pass_count,
};
REXLOG_TRACE(
"AC6 native renderer D3D12 scaffold submit frame={} passes={} commands={} graphics={} present={} resource={} pso={} descriptors={}",
executor_status_.frame_index, executor_status_.submitted_pass_count,
executor_status_.submitted_command_count,
executor_status_.graphics_pass_count, executor_status_.present_pass_count,
executor_status_.resource_translation_pass_count,
executor_status_.pipeline_state_pass_count,
executor_status_.descriptor_setup_pass_count);
return true;
}
void D3D12Backend::Shutdown() {
if (!initialized_) {
return;
}
executor_status_ = {};
initialized_ = false;
}
@@ -10,9 +10,12 @@ class D3D12Backend final : public RenderDeviceBackend {
std::string_view GetName() const override { return "d3d12"; }
bool IsSupported() const override;
bool Initialize(const NativeRendererConfig& config) override;
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) override;
BackendExecutorStatus GetExecutorStatus() const override { return executor_status_; }
void Shutdown() override;
private:
BackendExecutorStatus executor_status_{};
bool initialized_ = false;
};
@@ -17,15 +17,41 @@ bool MetalBackend::Initialize(const NativeRendererConfig& config) {
if (initialized_) {
return true;
}
executor_status_ = {};
executor_status_.initialized = true;
initialized_ = true;
REXLOG_INFO("AC6 native renderer Metal backend initialized (scaffold)");
return true;
}
bool MetalBackend::SubmitExecutorFrame(const ReplayExecutorFrame& frame) {
if (!initialized_) {
return false;
}
executor_status_.initialized = true;
executor_status_.frame_valid = frame.summary.valid;
executor_status_.frame_index = frame.summary.frame_index;
executor_status_.submitted_pass_count = frame.summary.pass_count;
executor_status_.submitted_command_count = frame.summary.command_count;
executor_status_.graphics_pass_count = frame.summary.graphics_pass_count;
executor_status_.async_compute_pass_count =
frame.summary.async_compute_pass_count;
executor_status_.copy_pass_count = frame.summary.copy_pass_count;
executor_status_.present_pass_count = frame.summary.present_pass_count;
executor_status_.resource_translation_pass_count =
frame.summary.resource_translation_pass_count;
executor_status_.pipeline_state_pass_count =
frame.summary.pipeline_state_pass_count;
executor_status_.descriptor_setup_pass_count =
frame.summary.descriptor_setup_pass_count;
return true;
}
void MetalBackend::Shutdown() {
if (!initialized_) {
return;
}
executor_status_ = {};
initialized_ = false;
}
@@ -10,9 +10,12 @@ class MetalBackend final : public RenderDeviceBackend {
std::string_view GetName() const override { return "metal"; }
bool IsSupported() const override;
bool Initialize(const NativeRendererConfig& config) override;
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) override;
BackendExecutorStatus GetExecutorStatus() const override { return executor_status_; }
void Shutdown() override;
private:
BackendExecutorStatus executor_status_{};
bool initialized_ = false;
};
@@ -17,15 +17,41 @@ bool VulkanBackend::Initialize(const NativeRendererConfig& config) {
if (initialized_) {
return true;
}
executor_status_ = {};
executor_status_.initialized = true;
initialized_ = true;
REXLOG_INFO("AC6 native renderer Vulkan backend initialized (scaffold)");
return true;
}
bool VulkanBackend::SubmitExecutorFrame(const ReplayExecutorFrame& frame) {
if (!initialized_) {
return false;
}
executor_status_.initialized = true;
executor_status_.frame_valid = frame.summary.valid;
executor_status_.frame_index = frame.summary.frame_index;
executor_status_.submitted_pass_count = frame.summary.pass_count;
executor_status_.submitted_command_count = frame.summary.command_count;
executor_status_.graphics_pass_count = frame.summary.graphics_pass_count;
executor_status_.async_compute_pass_count =
frame.summary.async_compute_pass_count;
executor_status_.copy_pass_count = frame.summary.copy_pass_count;
executor_status_.present_pass_count = frame.summary.present_pass_count;
executor_status_.resource_translation_pass_count =
frame.summary.resource_translation_pass_count;
executor_status_.pipeline_state_pass_count =
frame.summary.pipeline_state_pass_count;
executor_status_.descriptor_setup_pass_count =
frame.summary.descriptor_setup_pass_count;
return true;
}
void VulkanBackend::Shutdown() {
if (!initialized_) {
return;
}
executor_status_ = {};
initialized_ = false;
}
@@ -10,9 +10,12 @@ class VulkanBackend final : public RenderDeviceBackend {
std::string_view GetName() const override { return "vulkan"; }
bool IsSupported() const override;
bool Initialize(const NativeRendererConfig& config) override;
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) override;
BackendExecutorStatus GetExecutorStatus() const override { return executor_status_; }
void Shutdown() override;
private:
BackendExecutorStatus executor_status_{};
bool initialized_ = false;
};
+10 -2
View File
@@ -62,6 +62,7 @@ bool NativeRenderer::Initialize(const NativeRendererConfig& config) {
stats_.active_backend = device_.active_backend();
stats_.frame_count = 0;
stats_.built_pass_count = 0;
stats_.backend_submit_count = 0;
stats_.transient_allocation_count = 0;
return true;
}
@@ -95,6 +96,9 @@ void NativeRenderer::BuildBootstrapFrame() {
replay_frame_ = replay_builder_.BuildBootstrapFrame(scheduler_.frame_index());
execution_plan_ = execution_builder_.BuildBootstrapPlan(scheduler_.frame_index());
executor_frame_ = executor_builder_.BuildBootstrapFrame(scheduler_.frame_index());
if (device_.SubmitExecutorFrame(executor_frame_)) {
++stats_.backend_submit_count;
}
// Phase-1: do not present. Build a minimal graph to prove deterministic
// ownership without touching Rexglue emulation paths.
@@ -123,6 +127,9 @@ void NativeRenderer::BuildCapturedFrame(
replay_frame_ = replay_builder_.Build(summary, frontend_.passes(), frame_plan_);
execution_plan_ = execution_builder_.Build(replay_frame_, frame_plan_);
executor_frame_ = executor_builder_.Build(execution_plan_);
if (device_.SubmitExecutorFrame(executor_frame_)) {
++stats_.backend_submit_count;
}
for (const ReplayExecutorPassPacket& pass : executor_frame_.passes) {
graph_.AddPass(BuildRenderPassDesc(pass));
@@ -130,11 +137,12 @@ void NativeRenderer::BuildCapturedFrame(
stats_.built_pass_count += graph_.pass_count();
REXLOG_TRACE(
"AC6 native renderer observed frame={} frontend_passes={} replay_passes={} replay_commands={} execution_passes={} execution_commands={} executor_passes={} executor_commands={} selected={} draws={} clears={} resolves={} plan_valid={} out={}x{}",
"AC6 native renderer observed frame={} frontend_passes={} replay_passes={} replay_commands={} execution_passes={} execution_commands={} executor_passes={} executor_commands={} backend_submits={} selected={} draws={} clears={} resolves={} plan_valid={} out={}x{}",
summary.frame_index, summary.pass_count, replay_frame_.summary.pass_count,
replay_frame_.summary.command_count, execution_plan_.summary.pass_count,
execution_plan_.summary.command_count, executor_frame_.summary.pass_count,
executor_frame_.summary.command_count, summary.selected_pass_index,
executor_frame_.summary.command_count, stats_.backend_submit_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);
@@ -44,6 +44,9 @@ class NativeRenderer {
const ExecutionFramePlan& execution_plan() const { return execution_plan_; }
ReplayExecutorFrameSummary executor_summary() const { return executor_frame_.summary; }
const ReplayExecutorFrame& executor_frame() const { return executor_frame_; }
BackendExecutorStatus backend_executor_status() const {
return device_.executor_status();
}
private:
NativeRendererConfig config_{};
+11
View File
@@ -54,8 +54,19 @@ void RenderDevice::Shutdown() {
active_backend_ = BackendType::kUnknown;
}
bool RenderDevice::SubmitExecutorFrame(const ReplayExecutorFrame& frame) {
if (!backend_ || !initialized_) {
return false;
}
return backend_->SubmitExecutorFrame(frame);
}
std::string_view RenderDevice::backend_name() const {
return backend_ ? backend_->GetName() : ToString(BackendType::kUnknown);
}
BackendExecutorStatus RenderDevice::executor_status() const {
return backend_ ? backend_->GetExecutorStatus() : BackendExecutorStatus{};
}
} // namespace ac6::renderer
+5
View File
@@ -3,6 +3,7 @@
#include <memory>
#include <string_view>
#include "replay_executor.h"
#include "types.h"
namespace ac6::renderer {
@@ -15,6 +16,8 @@ class RenderDeviceBackend {
virtual std::string_view GetName() const = 0;
virtual bool IsSupported() const = 0;
virtual bool Initialize(const NativeRendererConfig& config) = 0;
virtual bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) = 0;
virtual BackendExecutorStatus GetExecutorStatus() const = 0;
virtual void Shutdown() = 0;
};
@@ -28,10 +31,12 @@ class RenderDevice {
bool Initialize(const NativeRendererConfig& config);
void Shutdown();
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame);
bool initialized() const { return initialized_; }
BackendType active_backend() const { return active_backend_; }
std::string_view backend_name() const;
BackendExecutorStatus executor_status() const;
private:
std::unique_ptr<RenderDeviceBackend> backend_;
+16
View File
@@ -32,9 +32,25 @@ struct NativeRendererStats {
BackendType active_backend = BackendType::kUnknown;
uint64_t frame_count = 0;
uint64_t built_pass_count = 0;
uint64_t backend_submit_count = 0;
uint64_t transient_allocation_count = 0;
};
struct BackendExecutorStatus {
bool initialized = false;
bool frame_valid = false;
uint64_t frame_index = 0;
uint32_t submitted_pass_count = 0;
uint32_t submitted_command_count = 0;
uint32_t graphics_pass_count = 0;
uint32_t async_compute_pass_count = 0;
uint32_t copy_pass_count = 0;
uint32_t present_pass_count = 0;
uint32_t resource_translation_pass_count = 0;
uint32_t pipeline_state_pass_count = 0;
uint32_t descriptor_setup_pass_count = 0;
};
constexpr std::string_view ToString(BackendType backend) {
switch (backend) {
case BackendType::kD3D12: