diff --git a/src/Milestone.md b/src/Milestone.md index abae93e1..b8d90e83 100644 --- a/src/Milestone.md +++ b/src/Milestone.md @@ -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. diff --git a/src/ac6_native_graphics.cpp b/src/ac6_native_graphics.cpp index be558549..ed8c2bac 100644 --- a/src/ac6_native_graphics.cpp +++ b/src/ac6_native_graphics.cpp @@ -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(); } diff --git a/src/ac6_native_graphics.h b/src/ac6_native_graphics.h index 89d24c38..31a54be9 100644 --- a/src/ac6_native_graphics.h +++ b/src/ac6_native_graphics.h @@ -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{}; }; diff --git a/src/ac6_native_graphics_overlay.cpp b/src/ac6_native_graphics_overlay.cpp index e6071e4c..d8481020 100644 --- a/src/ac6_native_graphics_overlay.cpp +++ b/src/ac6_native_graphics_overlay.cpp @@ -37,6 +37,8 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) { static_cast(status.renderer_stats.frame_count)); ImGui::Text("render passes built: %llu", static_cast(status.renderer_stats.built_pass_count)); + ImGui::Text("backend submits: %llu", + static_cast(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", diff --git a/src/ac6_native_renderer/backends/d3d12_backend.cpp b/src/ac6_native_renderer/backends/d3d12_backend.cpp index 3331c93d..9c7fc796 100644 --- a/src/ac6_native_renderer/backends/d3d12_backend.cpp +++ b/src/ac6_native_renderer/backends/d3d12_backend.cpp @@ -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; } diff --git a/src/ac6_native_renderer/backends/d3d12_backend.h b/src/ac6_native_renderer/backends/d3d12_backend.h index c3c612ce..025a3a78 100644 --- a/src/ac6_native_renderer/backends/d3d12_backend.h +++ b/src/ac6_native_renderer/backends/d3d12_backend.h @@ -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; }; diff --git a/src/ac6_native_renderer/backends/metal_backend.cpp b/src/ac6_native_renderer/backends/metal_backend.cpp index db4594cf..78f5f6e5 100644 --- a/src/ac6_native_renderer/backends/metal_backend.cpp +++ b/src/ac6_native_renderer/backends/metal_backend.cpp @@ -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; } diff --git a/src/ac6_native_renderer/backends/metal_backend.h b/src/ac6_native_renderer/backends/metal_backend.h index c06813d7..74f0f636 100644 --- a/src/ac6_native_renderer/backends/metal_backend.h +++ b/src/ac6_native_renderer/backends/metal_backend.h @@ -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; }; diff --git a/src/ac6_native_renderer/backends/vulkan_backend.cpp b/src/ac6_native_renderer/backends/vulkan_backend.cpp index e6497800..9ad88ecf 100644 --- a/src/ac6_native_renderer/backends/vulkan_backend.cpp +++ b/src/ac6_native_renderer/backends/vulkan_backend.cpp @@ -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; } diff --git a/src/ac6_native_renderer/backends/vulkan_backend.h b/src/ac6_native_renderer/backends/vulkan_backend.h index 9402dd07..c7ffad1f 100644 --- a/src/ac6_native_renderer/backends/vulkan_backend.h +++ b/src/ac6_native_renderer/backends/vulkan_backend.h @@ -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; }; diff --git a/src/ac6_native_renderer/native_renderer.cpp b/src/ac6_native_renderer/native_renderer.cpp index 8a79e40b..7458ea6a 100644 --- a/src/ac6_native_renderer/native_renderer.cpp +++ b/src/ac6_native_renderer/native_renderer.cpp @@ -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); diff --git a/src/ac6_native_renderer/native_renderer.h b/src/ac6_native_renderer/native_renderer.h index 56456888..d93c2f37 100644 --- a/src/ac6_native_renderer/native_renderer.h +++ b/src/ac6_native_renderer/native_renderer.h @@ -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_{}; diff --git a/src/ac6_native_renderer/render_device.cpp b/src/ac6_native_renderer/render_device.cpp index 8263e9b7..88ee484f 100644 --- a/src/ac6_native_renderer/render_device.cpp +++ b/src/ac6_native_renderer/render_device.cpp @@ -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 diff --git a/src/ac6_native_renderer/render_device.h b/src/ac6_native_renderer/render_device.h index 4b891018..fff562a0 100644 --- a/src/ac6_native_renderer/render_device.h +++ b/src/ac6_native_renderer/render_device.h @@ -3,6 +3,7 @@ #include #include +#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 backend_; diff --git a/src/ac6_native_renderer/types.h b/src/ac6_native_renderer/types.h index 592e5daf..766d7df9 100644 --- a/src/ac6_native_renderer/types.h +++ b/src/ac6_native_renderer/types.h @@ -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: