mirror of
https://github.com/sal063/AC6_recomp
synced 2026-06-05 11:19:08 -04:00
Removed a whole lot of Dead legacy code. Add a performence that is enabled by Default.
This commit is contained in:
@@ -38,22 +38,6 @@ set(AC6RECOMP_SOURCES
|
||||
src/ac6_backend_fixes/ac6_backend_capture_bridge.cpp
|
||||
src/ac6_backend_fixes/ac6_backend_hooks.cpp
|
||||
src/ac6_backend_fixes/ac6_backend_pass_classifier.cpp
|
||||
src/ac6_native_renderer/ac6_render_frontend.cpp
|
||||
src/ac6_native_renderer/execution_plan.cpp
|
||||
src/ac6_native_renderer/frame_plan.cpp
|
||||
src/ac6_native_renderer/replay_ir.cpp
|
||||
src/ac6_native_renderer/replay_executor.cpp
|
||||
src/ac6_native_renderer/backends/backend_factory.cpp
|
||||
src/ac6_native_renderer/backends/d3d12_backend.cpp
|
||||
src/ac6_native_renderer/backends/d3d12_resource_manager.cpp
|
||||
src/ac6_native_renderer/backends/d3d12_resource_tracker.cpp
|
||||
src/ac6_native_renderer/backends/d3d12_shader_manager.cpp
|
||||
src/ac6_native_renderer/backends/metal_backend.cpp
|
||||
src/ac6_native_renderer/backends/vulkan_backend.cpp
|
||||
src/ac6_native_renderer/frame_scheduler.cpp
|
||||
src/ac6_native_renderer/native_renderer.cpp
|
||||
src/ac6_native_renderer/render_device.cpp
|
||||
src/ac6_native_renderer/render_graph.cpp
|
||||
)
|
||||
|
||||
if(WIN32)
|
||||
|
||||
@@ -14,9 +14,4 @@ resolution = "1080p"
|
||||
direct_host_resolve = false
|
||||
window_width = 1920
|
||||
window_height = 1080
|
||||
vfetch_index_rounding_bias = true
|
||||
d3d12_expand_point_sprites_in_vs = true
|
||||
d3d12_debug = true
|
||||
dump_shaders = "shaders"
|
||||
d3d12_dxbc_disasm = true
|
||||
d3d12_dxbc_disasm_dxilconv = true
|
||||
vfetch_index_rounding_bias = true
|
||||
+2
-365
@@ -12,45 +12,24 @@
|
||||
#include <rex/logging.h>
|
||||
#include <rex/system/kernel_state.h>
|
||||
#include <rex/system/thread_state.h>
|
||||
#include <rex/ui/d3d12/d3d12_provider.h>
|
||||
|
||||
#include "ac6_native_renderer/backends/d3d12_backend.h"
|
||||
#include "ac6_native_renderer/native_renderer.h"
|
||||
#include "d3d_hooks.h"
|
||||
#include "render_hooks.h"
|
||||
|
||||
REXCVAR_DEFINE_BOOL(ac6_native_graphics_enabled, true, "AC6/NativeGraphics",
|
||||
"Enable AC6 graphics capture analysis, overlay reporting, and backend fixes");
|
||||
REXCVAR_DEFINE_BOOL(ac6_native_graphics_require_capture, false, "AC6/NativeGraphics",
|
||||
"Keep render capture enabled while AC6 graphics analysis is active");
|
||||
REXCVAR_DEFINE_BOOL(ac6_force_safe_render_capture, true, "AC6/NativeGraphics",
|
||||
"Force AC6 hybrid backend fixes mode to keep per-draw render capture disabled until the capture path is stabilized");
|
||||
REXCVAR_DEFINE_STRING(ac6_graphics_mode, "hybrid_backend_fixes", "AC6/NativeGraphics",
|
||||
"AC6 graphics runtime mode: disabled, analysis_only, hybrid_backend_fixes, legacy_replay_experimental")
|
||||
.allowed({"disabled", "analysis_only", "hybrid_backend_fixes", "legacy_replay_experimental"});
|
||||
"AC6 graphics runtime mode: disabled, analysis_only, hybrid_backend_fixes")
|
||||
.allowed({"disabled", "analysis_only", "hybrid_backend_fixes"});
|
||||
REXCVAR_DEFINE_BOOL(ac6_force_safe_draw_resolution_scale, true, "AC6/NativeGraphics",
|
||||
"Force AC6 hybrid backend fixes mode to use 1x draw resolution scaling until the scaled path is fixed");
|
||||
REXCVAR_DEFINE_BOOL(ac6_force_safe_direct_host_resolve, true, "AC6/NativeGraphics",
|
||||
"Force AC6 hybrid backend fixes mode to keep direct_host_resolve disabled until the AC6 crash is fixed");
|
||||
REXCVAR_DEFINE_BOOL(ac6_experimental_replay_present, false, "AC6/NativeGraphics",
|
||||
"Allow the legacy AC6 replay renderer to override the RexGlue swap source");
|
||||
REXCVAR_DEFINE_STRING(ac6_native_graphics_backend, "auto", "AC6/NativeGraphics",
|
||||
"Legacy experimental replay backend preference");
|
||||
REXCVAR_DEFINE_STRING(ac6_native_graphics_feature_level, "scene_submission", "AC6/NativeGraphics",
|
||||
"Legacy experimental replay feature level (shipping is a legacy scaffold label)")
|
||||
.allowed({"bootstrap", "scene_submission", "parity_validation", "shipping"});
|
||||
REXCVAR_DEFINE_INT32(ac6_native_graphics_frames_in_flight, 2, "AC6/NativeGraphics",
|
||||
"Legacy experimental replay max frames in flight")
|
||||
.range(1, 4);
|
||||
|
||||
namespace ac6::graphics {
|
||||
namespace {
|
||||
|
||||
ac6::renderer::NativeRenderer g_native_renderer;
|
||||
ac6::renderer::Ac6RenderFrontend g_capture_frontend;
|
||||
ac6::renderer::FramePlanner g_capture_frame_planner;
|
||||
NativeGraphicsRuntimeStatus g_runtime_status{};
|
||||
ac6::renderer::D3D12Backend* g_d3d12_backend = nullptr;
|
||||
std::atomic<rex::memory::Memory*> g_captured_memory{nullptr};
|
||||
std::atomic_flag g_frame_boundary_active = ATOMIC_FLAG_INIT;
|
||||
std::atomic<uint32_t> g_frame_boundary_reentry_count{0};
|
||||
@@ -62,44 +41,9 @@ GraphicsRuntimeMode ParseGraphicsMode(std::string_view value) {
|
||||
if (value == "analysis_only") {
|
||||
return GraphicsRuntimeMode::kAnalysisOnly;
|
||||
}
|
||||
if (value == "legacy_replay_experimental") {
|
||||
return GraphicsRuntimeMode::kLegacyReplayExperimental;
|
||||
}
|
||||
return GraphicsRuntimeMode::kHybridBackendFixes;
|
||||
}
|
||||
|
||||
ac6::renderer::FeatureLevel ParseFeatureLevel(std::string_view value) {
|
||||
using ac6::renderer::FeatureLevel;
|
||||
if (value == "scene_submission") {
|
||||
return FeatureLevel::kSceneSubmission;
|
||||
}
|
||||
if (value == "parity_validation") {
|
||||
return FeatureLevel::kParityValidation;
|
||||
}
|
||||
if (value == "shipping") {
|
||||
return FeatureLevel::kShipping;
|
||||
}
|
||||
return FeatureLevel::kBootstrap;
|
||||
}
|
||||
|
||||
bool IsReplayMode(const GraphicsRuntimeMode mode) {
|
||||
return mode == GraphicsRuntimeMode::kLegacyReplayExperimental;
|
||||
}
|
||||
|
||||
void ResetReplayStatus() {
|
||||
g_runtime_status.initialized = false;
|
||||
g_runtime_status.replay_frames_built = 0;
|
||||
g_runtime_status.active_backend = ac6::renderer::BackendType::kUnknown;
|
||||
g_runtime_status.renderer_stats = {};
|
||||
g_runtime_status.replay_summary = {};
|
||||
g_runtime_status.execution_summary = {};
|
||||
g_runtime_status.executor_summary = {};
|
||||
g_runtime_status.backend_executor_status = {};
|
||||
g_runtime_status.latest_renderer_frame_index = 0;
|
||||
g_runtime_status.last_meaningful_renderer_frame_index = 0;
|
||||
g_runtime_status.showing_latched_snapshot = false;
|
||||
}
|
||||
|
||||
void SyncRuntimeFlags() {
|
||||
g_runtime_status.enabled = REXCVAR_GET(ac6_native_graphics_enabled);
|
||||
g_runtime_status.mode = ParseGraphicsMode(REXCVAR_GET(ac6_graphics_mode));
|
||||
@@ -125,239 +69,6 @@ void SyncRuntimeFlags() {
|
||||
g_runtime_status.authoritative_renderer_active =
|
||||
g_runtime_status.enabled &&
|
||||
g_runtime_status.mode != GraphicsRuntimeMode::kDisabled;
|
||||
g_runtime_status.experimental_replay_present =
|
||||
g_runtime_status.enabled &&
|
||||
IsReplayMode(g_runtime_status.mode) &&
|
||||
REXCVAR_GET(ac6_experimental_replay_present);
|
||||
}
|
||||
|
||||
void RefreshRuntimeStatusFromRenderer() {
|
||||
g_runtime_status.active_backend = g_native_renderer.GetStats().active_backend;
|
||||
g_runtime_status.feature_level = g_native_renderer.feature_level();
|
||||
g_runtime_status.renderer_stats = g_native_renderer.GetStats();
|
||||
g_runtime_status.frontend_summary = g_native_renderer.frontend_summary();
|
||||
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();
|
||||
}
|
||||
|
||||
bool IsMeaningfulRendererSnapshot(
|
||||
const ac6::d3d::FrameCaptureSummary& capture_summary,
|
||||
const ac6::renderer::FrontendFrameSummary& frontend_summary,
|
||||
const ac6::renderer::ReplayFrameSummary& replay_summary,
|
||||
const ac6::renderer::ExecutionFrameSummary& execution_summary,
|
||||
const ac6::renderer::ReplayExecutorFrameSummary& executor_summary,
|
||||
const ac6::renderer::BackendExecutorStatus& backend_status) {
|
||||
return capture_summary.draw_count != 0 || capture_summary.clear_count != 0 ||
|
||||
capture_summary.resolve_count != 0 ||
|
||||
frontend_summary.total_command_count != 0 ||
|
||||
replay_summary.command_count != 0 ||
|
||||
execution_summary.command_count != 0 ||
|
||||
executor_summary.command_count != 0 ||
|
||||
backend_status.draw_attempt_count != 0 ||
|
||||
backend_status.clear_command_count != 0 ||
|
||||
backend_status.resolve_command_count != 0;
|
||||
}
|
||||
|
||||
uint32_t ScoreObservedPassForDiagnostics(const ac6::renderer::ObservedPassDesc& pass) {
|
||||
const uint64_t viewport_area =
|
||||
uint64_t(pass.viewport_width) * uint64_t(pass.viewport_height);
|
||||
uint32_t score = 0;
|
||||
score += pass.selected_for_present ? 160u : 0u;
|
||||
score += pass.matches_frame_end_viewport ? 120u : 0u;
|
||||
score += pass.resolve_count * 80u;
|
||||
score += pass.draw_count * 4u;
|
||||
score += pass.clear_count * 6u;
|
||||
score += pass.max_texture_count * 3u;
|
||||
score += pass.max_stream_count * 2u;
|
||||
score += pass.max_sampler_count * 2u;
|
||||
score += static_cast<uint32_t>(std::min<uint64_t>(viewport_area / 32768u, 120u));
|
||||
switch (pass.kind) {
|
||||
case ac6::renderer::ObservedPassKind::kScene:
|
||||
score += 40u;
|
||||
break;
|
||||
case ac6::renderer::ObservedPassKind::kPostProcess:
|
||||
score += 50u;
|
||||
break;
|
||||
case ac6::renderer::ObservedPassKind::kUiComposite:
|
||||
score += 20u;
|
||||
break;
|
||||
case ac6::renderer::ObservedPassKind::kUnknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
void RefreshPassDiagnostics(const std::vector<ac6::renderer::ObservedPassDesc>& passes) {
|
||||
g_runtime_status.pass_diagnostics_count = 0;
|
||||
g_runtime_status.pass_diagnostics = {};
|
||||
if (passes.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
struct RankedPass {
|
||||
uint32_t pass_index = 0;
|
||||
uint32_t score = 0;
|
||||
};
|
||||
|
||||
std::vector<RankedPass> ranked;
|
||||
ranked.reserve(passes.size());
|
||||
for (uint32_t i = 0; i < passes.size(); ++i) {
|
||||
const auto& pass = passes[i];
|
||||
ranked.push_back({i, ScoreObservedPassForDiagnostics(pass)});
|
||||
}
|
||||
|
||||
std::sort(ranked.begin(), ranked.end(), [&](const RankedPass& left, const RankedPass& right) {
|
||||
if (left.score != right.score) {
|
||||
return left.score > right.score;
|
||||
}
|
||||
return left.pass_index < right.pass_index;
|
||||
});
|
||||
|
||||
const uint32_t count = std::min<uint32_t>(static_cast<uint32_t>(ranked.size()),
|
||||
static_cast<uint32_t>(g_runtime_status.pass_diagnostics.size()));
|
||||
g_runtime_status.pass_diagnostics_count = count;
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const auto& ranked_pass = ranked[i];
|
||||
const auto& pass = passes[ranked_pass.pass_index];
|
||||
g_runtime_status.pass_diagnostics[i] = {
|
||||
.valid = true,
|
||||
.pass_index = ranked_pass.pass_index,
|
||||
.kind = pass.kind,
|
||||
.score = ranked_pass.score,
|
||||
.render_target_0 = pass.render_target_0,
|
||||
.depth_stencil = pass.depth_stencil,
|
||||
.viewport_x = pass.viewport_x,
|
||||
.viewport_y = pass.viewport_y,
|
||||
.viewport_width = pass.viewport_width,
|
||||
.viewport_height = pass.viewport_height,
|
||||
.draw_count = pass.draw_count,
|
||||
.clear_count = pass.clear_count,
|
||||
.resolve_count = pass.resolve_count,
|
||||
.max_texture_count = pass.max_texture_count,
|
||||
.max_stream_count = pass.max_stream_count,
|
||||
.max_sampler_count = pass.max_sampler_count,
|
||||
.max_fetch_constant_count = pass.max_fetch_constant_count,
|
||||
.max_shader_gpr_alloc = pass.max_shader_gpr_alloc,
|
||||
.pass_signature = pass.pass_signature,
|
||||
.first_texture_fetch_layout_signature =
|
||||
pass.first_texture_fetch_layout_signature,
|
||||
.last_texture_fetch_layout_signature =
|
||||
pass.last_texture_fetch_layout_signature,
|
||||
.first_resource_binding_signature =
|
||||
pass.first_resource_binding_signature,
|
||||
.last_resource_binding_signature =
|
||||
pass.last_resource_binding_signature,
|
||||
.first_textures = {.count = pass.first_textures.count,
|
||||
.slots = pass.first_textures.slots,
|
||||
.values = pass.first_textures.values},
|
||||
.last_textures = {.count = pass.last_textures.count,
|
||||
.slots = pass.last_textures.slots,
|
||||
.values = pass.last_textures.values},
|
||||
.first_fetch_constants = {.count = pass.first_fetch_constants.count,
|
||||
.slots = pass.first_fetch_constants.slots,
|
||||
.values = pass.first_fetch_constants.values},
|
||||
.last_fetch_constants = {.count = pass.last_fetch_constants.count,
|
||||
.slots = pass.last_fetch_constants.slots,
|
||||
.values = pass.last_fetch_constants.values},
|
||||
.selected_for_present = pass.selected_for_present,
|
||||
.matches_frame_end_viewport = pass.matches_frame_end_viewport,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void RefreshResolveDiagnostics(const ac6::d3d::FrameCaptureSnapshot& frame_capture) {
|
||||
g_runtime_status.resolve_diagnostics_count = 0;
|
||||
g_runtime_status.resolve_diagnostics = {};
|
||||
if (frame_capture.resolves.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t total_resolves = static_cast<uint32_t>(frame_capture.resolves.size());
|
||||
const uint32_t count =
|
||||
std::min<uint32_t>(total_resolves,
|
||||
static_cast<uint32_t>(g_runtime_status.resolve_diagnostics.size()));
|
||||
const uint32_t start_index = total_resolves - count;
|
||||
g_runtime_status.resolve_diagnostics_count = count;
|
||||
for (uint32_t i = 0; i < count; ++i) {
|
||||
const auto& resolve = frame_capture.resolves[start_index + i];
|
||||
g_runtime_status.resolve_diagnostics[i] = {
|
||||
.valid = true,
|
||||
.sequence = resolve.sequence,
|
||||
.render_target_0 = resolve.shadow_state.render_targets[0],
|
||||
.depth_stencil = resolve.shadow_state.depth_stencil,
|
||||
.viewport_width = resolve.shadow_state.viewport.width,
|
||||
.viewport_height = resolve.shadow_state.viewport.height,
|
||||
.args = resolve.args,
|
||||
.depth_or_scale = resolve.depth_or_scale,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
void ShutdownReplayRenderer() {
|
||||
g_d3d12_backend = nullptr;
|
||||
if (!g_runtime_status.initialized) {
|
||||
return;
|
||||
}
|
||||
g_native_renderer.Shutdown();
|
||||
ResetReplayStatus();
|
||||
}
|
||||
|
||||
bool EnsureExperimentalReplayInitialized(rex::memory::Memory* memory) {
|
||||
if (!g_runtime_status.enabled || !IsReplayMode(g_runtime_status.mode)) {
|
||||
return false;
|
||||
}
|
||||
if (g_runtime_status.initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
++g_runtime_status.init_attempts;
|
||||
auto* ts = rex::runtime::ThreadState::Get();
|
||||
if (!ts || !ts->context() || !ts->context()->kernel_state) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* graphics_system = ts->context()->kernel_state->graphics_system();
|
||||
if (!graphics_system || !graphics_system->provider()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto* d3d_provider =
|
||||
dynamic_cast<rex::ui::d3d12::D3D12Provider*>(graphics_system->provider());
|
||||
if (!d3d_provider) {
|
||||
g_runtime_status.had_init_failure = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
ID3D12Device* device = d3d_provider->GetDevice();
|
||||
ID3D12CommandQueue* queue = d3d_provider->GetDirectQueue();
|
||||
if (!device || !queue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ac6::renderer::NativeRendererConfig config;
|
||||
config.preferred_backend = ac6::renderer::BackendType::kD3D12;
|
||||
config.feature_level =
|
||||
ParseFeatureLevel(REXCVAR_GET(ac6_native_graphics_feature_level));
|
||||
config.max_frames_in_flight = static_cast<uint32_t>(
|
||||
std::clamp(REXCVAR_GET(ac6_native_graphics_frames_in_flight), 1, 4));
|
||||
config.enable_debug_markers = true;
|
||||
config.enable_validation = false;
|
||||
|
||||
if (!g_native_renderer.InitializeShared(config, memory, device, queue)) {
|
||||
g_runtime_status.had_init_failure = true;
|
||||
return false;
|
||||
}
|
||||
|
||||
g_d3d12_backend = g_native_renderer.GetD3D12Backend();
|
||||
++g_runtime_status.init_successes;
|
||||
g_runtime_status.initialized = true;
|
||||
RefreshRuntimeStatusFromRenderer();
|
||||
REXLOG_INFO("AC6 graphics: legacy experimental replay renderer initialized");
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
@@ -370,8 +81,6 @@ std::string_view ToString(const GraphicsRuntimeMode mode) {
|
||||
return "analysis_only";
|
||||
case GraphicsRuntimeMode::kHybridBackendFixes:
|
||||
return "hybrid_backend_fixes";
|
||||
case GraphicsRuntimeMode::kLegacyReplayExperimental:
|
||||
return "legacy_replay_experimental";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
@@ -398,20 +107,10 @@ void OnFrameBoundary(rex::memory::Memory* memory) {
|
||||
g_captured_memory.store(memory, std::memory_order_release);
|
||||
|
||||
if (!g_runtime_status.enabled || g_runtime_status.mode == GraphicsRuntimeMode::kDisabled) {
|
||||
ShutdownReplayRenderer();
|
||||
ac6::backend::ShutdownDiagnostics();
|
||||
return;
|
||||
}
|
||||
|
||||
if (REXCVAR_GET(ac6_native_graphics_require_capture)) {
|
||||
REXCVAR_SET(ac6_render_capture, true);
|
||||
g_runtime_status.capture_enabled = true;
|
||||
}
|
||||
|
||||
if (!IsReplayMode(g_runtime_status.mode)) {
|
||||
ShutdownReplayRenderer();
|
||||
}
|
||||
|
||||
ac6::d3d::OnFrameBoundary();
|
||||
|
||||
ac6::d3d::FrameCaptureSummary capture_summary;
|
||||
@@ -422,19 +121,6 @@ void OnFrameBoundary(rex::memory::Memory* memory) {
|
||||
++g_runtime_status.analysis_frames_observed;
|
||||
g_runtime_status.capture_summary = capture_summary;
|
||||
g_runtime_status.latest_capture_frame_index = capture_summary.frame_index;
|
||||
g_runtime_status.frontend_summary = g_capture_frontend.BuildFromCapture(frame_capture);
|
||||
if (g_runtime_status.frontend_summary.capture_valid) {
|
||||
RefreshPassDiagnostics(g_capture_frontend.passes());
|
||||
RefreshResolveDiagnostics(frame_capture);
|
||||
g_runtime_status.frame_plan = g_capture_frame_planner.Build(
|
||||
g_runtime_status.frontend_summary, g_capture_frontend.passes());
|
||||
} else {
|
||||
g_runtime_status.pass_diagnostics_count = 0;
|
||||
g_runtime_status.pass_diagnostics = {};
|
||||
g_runtime_status.resolve_diagnostics_count = 0;
|
||||
g_runtime_status.resolve_diagnostics = {};
|
||||
g_runtime_status.frame_plan = {};
|
||||
}
|
||||
if (capture_summary.draw_count || capture_summary.clear_count ||
|
||||
capture_summary.resolve_count) {
|
||||
g_runtime_status.last_meaningful_capture_frame_index = capture_summary.frame_index;
|
||||
@@ -474,49 +160,10 @@ void OnFrameBoundary(rex::memory::Memory* memory) {
|
||||
guest_vblank_interval_ticks, last_guest_vblank_tick, ac6::GetFrameStats(),
|
||||
audio_telemetry_ptr, audio_timing_ptr);
|
||||
g_runtime_status.backend_diagnostics = ac6::backend::GetDiagnosticsSnapshot();
|
||||
|
||||
if (!IsReplayMode(g_runtime_status.mode)) {
|
||||
return;
|
||||
}
|
||||
if (!EnsureExperimentalReplayInitialized(memory)) {
|
||||
return;
|
||||
}
|
||||
|
||||
g_native_renderer.BeginFrame();
|
||||
g_native_renderer.BuildCapturedFrame(frame_capture);
|
||||
g_runtime_status.replay_frames_built = g_native_renderer.GetStats().frame_count;
|
||||
g_runtime_status.latest_renderer_frame_index =
|
||||
g_native_renderer.GetStats().frame_count;
|
||||
|
||||
const ac6::renderer::FrontendFrameSummary frontend_summary =
|
||||
g_native_renderer.frontend_summary();
|
||||
const ac6::renderer::ReplayFrameSummary replay_summary =
|
||||
g_native_renderer.replay_summary();
|
||||
const ac6::renderer::ExecutionFrameSummary execution_summary =
|
||||
g_native_renderer.execution_summary();
|
||||
const ac6::renderer::ReplayExecutorFrameSummary executor_summary =
|
||||
g_native_renderer.executor_summary();
|
||||
const ac6::renderer::BackendExecutorStatus backend_status =
|
||||
g_native_renderer.backend_executor_status();
|
||||
|
||||
if (IsMeaningfulRendererSnapshot(capture_summary, frontend_summary, replay_summary,
|
||||
execution_summary, executor_summary, backend_status) ||
|
||||
g_runtime_status.last_meaningful_renderer_frame_index == 0) {
|
||||
RefreshRuntimeStatusFromRenderer();
|
||||
g_runtime_status.showing_latched_snapshot = false;
|
||||
g_runtime_status.last_meaningful_renderer_frame_index =
|
||||
g_runtime_status.latest_renderer_frame_index;
|
||||
} else {
|
||||
g_runtime_status.active_backend = g_native_renderer.GetStats().active_backend;
|
||||
g_runtime_status.feature_level = g_native_renderer.feature_level();
|
||||
g_runtime_status.renderer_stats = g_native_renderer.GetStats();
|
||||
g_runtime_status.showing_latched_snapshot = true;
|
||||
}
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
g_captured_memory.store(nullptr, std::memory_order_release);
|
||||
ShutdownReplayRenderer();
|
||||
}
|
||||
|
||||
NativeGraphicsRuntimeStatus GetRuntimeStatus() {
|
||||
@@ -525,16 +172,6 @@ NativeGraphicsRuntimeStatus GetRuntimeStatus() {
|
||||
return g_runtime_status;
|
||||
}
|
||||
|
||||
ID3D12Resource* GetNativeOutputTexture() {
|
||||
SyncRuntimeFlags();
|
||||
if (!g_runtime_status.enabled || !g_runtime_status.initialized ||
|
||||
!IsReplayMode(g_runtime_status.mode) ||
|
||||
!REXCVAR_GET(ac6_experimental_replay_present)) {
|
||||
return nullptr;
|
||||
}
|
||||
return g_d3d12_backend ? g_d3d12_backend->GetOutputTexture() : nullptr;
|
||||
}
|
||||
|
||||
rex::memory::Memory* GetCapturedMemory() {
|
||||
return g_captured_memory.load(std::memory_order_acquire);
|
||||
}
|
||||
|
||||
@@ -1,126 +1,43 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#include <rex/memory.h>
|
||||
|
||||
#include "ac6_backend_fixes/ac6_backend_hooks.h"
|
||||
#include "ac6_native_renderer/ac6_render_frontend.h"
|
||||
#include "ac6_native_renderer/execution_plan.h"
|
||||
#include "ac6_native_renderer/frame_plan.h"
|
||||
#include "ac6_native_renderer/replay_executor.h"
|
||||
#include "ac6_native_renderer/replay_ir.h"
|
||||
#include "ac6_native_renderer/types.h"
|
||||
#include "d3d_state.h"
|
||||
|
||||
struct ID3D12Resource;
|
||||
|
||||
namespace ac6::graphics {
|
||||
|
||||
enum class GraphicsRuntimeMode : uint8_t {
|
||||
kDisabled,
|
||||
kAnalysisOnly,
|
||||
kHybridBackendFixes,
|
||||
kLegacyReplayExperimental,
|
||||
};
|
||||
|
||||
std::string_view ToString(GraphicsRuntimeMode mode);
|
||||
|
||||
struct NativeGraphicsRuntimeStatus {
|
||||
struct PassDiagnosticsEntry {
|
||||
struct BoundResourceSample {
|
||||
uint32_t count = 0;
|
||||
std::array<uint32_t, 4> slots{};
|
||||
std::array<uint32_t, 4> values{};
|
||||
};
|
||||
|
||||
bool valid = false;
|
||||
uint32_t pass_index = 0;
|
||||
ac6::renderer::ObservedPassKind kind = ac6::renderer::ObservedPassKind::kUnknown;
|
||||
uint32_t score = 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;
|
||||
uint32_t draw_count = 0;
|
||||
uint32_t clear_count = 0;
|
||||
uint32_t resolve_count = 0;
|
||||
uint32_t max_texture_count = 0;
|
||||
uint32_t max_stream_count = 0;
|
||||
uint32_t max_sampler_count = 0;
|
||||
uint32_t max_fetch_constant_count = 0;
|
||||
uint32_t max_shader_gpr_alloc = 0;
|
||||
uint64_t pass_signature = 0;
|
||||
uint64_t first_texture_fetch_layout_signature = 0;
|
||||
uint64_t last_texture_fetch_layout_signature = 0;
|
||||
uint64_t first_resource_binding_signature = 0;
|
||||
uint64_t last_resource_binding_signature = 0;
|
||||
BoundResourceSample first_textures{};
|
||||
BoundResourceSample last_textures{};
|
||||
BoundResourceSample first_fetch_constants{};
|
||||
BoundResourceSample last_fetch_constants{};
|
||||
bool selected_for_present = false;
|
||||
bool matches_frame_end_viewport = false;
|
||||
};
|
||||
|
||||
struct ResolveDiagnosticsEntry {
|
||||
bool valid = false;
|
||||
uint32_t sequence = 0;
|
||||
uint32_t render_target_0 = 0;
|
||||
uint32_t depth_stencil = 0;
|
||||
uint32_t viewport_width = 0;
|
||||
uint32_t viewport_height = 0;
|
||||
std::array<uint32_t, 7> args{};
|
||||
float depth_or_scale = 0.0f;
|
||||
};
|
||||
|
||||
bool enabled = false;
|
||||
GraphicsRuntimeMode mode = GraphicsRuntimeMode::kHybridBackendFixes;
|
||||
bool capture_enabled = false;
|
||||
bool authoritative_renderer_active = false;
|
||||
bool experimental_replay_present = false;
|
||||
uint32_t draw_resolution_scale_x = 1;
|
||||
uint32_t draw_resolution_scale_y = 1;
|
||||
bool direct_host_resolve = true;
|
||||
bool draw_resolution_scaled_texture_offsets = true;
|
||||
bool initialized = false;
|
||||
bool had_init_failure = false;
|
||||
bool showing_latched_snapshot = false;
|
||||
uint64_t init_attempts = 0;
|
||||
uint64_t init_successes = 0;
|
||||
uint64_t analysis_frames_observed = 0;
|
||||
uint64_t replay_frames_built = 0;
|
||||
uint64_t latest_capture_frame_index = 0;
|
||||
uint64_t latest_renderer_frame_index = 0;
|
||||
uint64_t last_meaningful_capture_frame_index = 0;
|
||||
uint64_t last_meaningful_renderer_frame_index = 0;
|
||||
|
||||
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::renderer::ExecutionFrameSummary execution_summary{};
|
||||
ac6::renderer::ReplayExecutorFrameSummary executor_summary{};
|
||||
ac6::renderer::BackendExecutorStatus backend_executor_status{};
|
||||
ac6::d3d::FrameCaptureSummary capture_summary{};
|
||||
ac6::backend::BackendDiagnosticsSnapshot backend_diagnostics{};
|
||||
ac6::renderer::NativeFramePlan frame_plan{};
|
||||
uint32_t pass_diagnostics_count = 0;
|
||||
std::array<PassDiagnosticsEntry, 6> pass_diagnostics{};
|
||||
uint32_t resolve_diagnostics_count = 0;
|
||||
std::array<ResolveDiagnosticsEntry, 8> resolve_diagnostics{};
|
||||
};
|
||||
|
||||
void OnFrameBoundary(rex::memory::Memory* memory);
|
||||
void Shutdown();
|
||||
|
||||
NativeGraphicsRuntimeStatus GetRuntimeStatus();
|
||||
ID3D12Resource* GetNativeOutputTexture();
|
||||
rex::memory::Memory* GetCapturedMemory();
|
||||
|
||||
} // namespace ac6::graphics
|
||||
|
||||
@@ -1,359 +1,15 @@
|
||||
#include "ac6_native_graphics_overlay.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstdio>
|
||||
#include <string>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <rex/graphics/pipeline/texture/info.h>
|
||||
#include <rex/graphics/xenos.h>
|
||||
#include <rex/memory.h>
|
||||
#include <rex/cvar.h>
|
||||
|
||||
#include "ac6_native_graphics.h"
|
||||
|
||||
REXCVAR_DECLARE(bool, ac6_performance_mode);
|
||||
|
||||
extern void ApplyAc6PerformanceModeOverridesPublic();
|
||||
|
||||
namespace ac6::graphics {
|
||||
namespace {
|
||||
|
||||
struct DecodedTextureFetch {
|
||||
bool valid = false;
|
||||
uint32_t header_offset = 0;
|
||||
uint32_t width = 0;
|
||||
uint32_t height = 0;
|
||||
uint32_t depth = 0;
|
||||
uint32_t base_address = 0;
|
||||
const char* format_name = nullptr;
|
||||
rex::graphics::xenos::DataDimension dimension =
|
||||
rex::graphics::xenos::DataDimension::k2DOrStacked;
|
||||
bool tiled = false;
|
||||
};
|
||||
|
||||
template <size_t N>
|
||||
struct DecodedSampleSet {
|
||||
uint32_t count = 0;
|
||||
std::array<DecodedTextureFetch, N> entries{};
|
||||
};
|
||||
|
||||
bool IsReadableGuestRange(rex::memory::Memory* memory, uint32_t guest_ptr, uint32_t size) {
|
||||
if (memory == nullptr || guest_ptr == 0 || size == 0) {
|
||||
return false;
|
||||
}
|
||||
const uint32_t guest_end = guest_ptr + size - 1;
|
||||
if (guest_end < guest_ptr) {
|
||||
return false;
|
||||
}
|
||||
auto* start_heap = memory->LookupHeap(guest_ptr);
|
||||
if (start_heap == nullptr) {
|
||||
return false;
|
||||
}
|
||||
auto* end_heap = memory->LookupHeap(guest_end);
|
||||
if (end_heap != start_heap) {
|
||||
return false;
|
||||
}
|
||||
const auto access = start_heap->QueryRangeAccess(guest_ptr, guest_end);
|
||||
return access == rex::memory::PageAccess::kReadOnly ||
|
||||
access == rex::memory::PageAccess::kReadWrite ||
|
||||
access == rex::memory::PageAccess::kExecuteReadOnly ||
|
||||
access == rex::memory::PageAccess::kExecuteReadWrite;
|
||||
}
|
||||
|
||||
bool LoadTextureFetchAt(rex::memory::Memory* memory, uint32_t guest_ptr,
|
||||
rex::graphics::xenos::xe_gpu_texture_fetch_t& fetch) {
|
||||
if (!IsReadableGuestRange(memory, guest_ptr, sizeof(fetch))) {
|
||||
return false;
|
||||
}
|
||||
const uint8_t* fetch_base = memory->TranslateVirtual<const uint8_t*>(guest_ptr);
|
||||
if (fetch_base == nullptr) {
|
||||
return false;
|
||||
}
|
||||
fetch.dword_0 = rex::memory::load_and_swap<uint32_t>(fetch_base + 0);
|
||||
fetch.dword_1 = rex::memory::load_and_swap<uint32_t>(fetch_base + 4);
|
||||
fetch.dword_2 = rex::memory::load_and_swap<uint32_t>(fetch_base + 8);
|
||||
fetch.dword_3 = rex::memory::load_and_swap<uint32_t>(fetch_base + 12);
|
||||
fetch.dword_4 = rex::memory::load_and_swap<uint32_t>(fetch_base + 16);
|
||||
fetch.dword_5 = rex::memory::load_and_swap<uint32_t>(fetch_base + 20);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsPlausibleTextureFetchHeader(const rex::graphics::xenos::xe_gpu_texture_fetch_t& fetch) {
|
||||
using rex::graphics::xenos::DataDimension;
|
||||
|
||||
if (static_cast<uint32_t>(fetch.format) >= 64) {
|
||||
return false;
|
||||
}
|
||||
|
||||
switch (fetch.dimension) {
|
||||
case DataDimension::k1D:
|
||||
case DataDimension::k3D:
|
||||
return !fetch.stacked;
|
||||
case DataDimension::k2DOrStacked:
|
||||
return true;
|
||||
case DataDimension::kCube:
|
||||
return !fetch.stacked && fetch.size_2d.stack_depth == 5;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPlausibleTextureInfo(const rex::graphics::TextureInfo& info) {
|
||||
const uint32_t width = info.width + 1;
|
||||
const uint32_t height = info.height + 1;
|
||||
const uint32_t depth = info.depth + 1;
|
||||
return width >= 1 && width <= 8192 && height >= 1 && height <= 8192 && depth >= 1 &&
|
||||
depth <= 2048 && info.memory.base_address != 0 && info.format_info() != nullptr;
|
||||
}
|
||||
|
||||
bool DecodeTextureFetch(uint32_t guest_ptr, DecodedTextureFetch& out) {
|
||||
rex::memory::Memory* memory = GetCapturedMemory();
|
||||
if (!IsReadableGuestRange(memory, guest_ptr, sizeof(rex::graphics::xenos::xe_gpu_texture_fetch_t))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr uint32_t kProbeLimit = 0x80;
|
||||
for (uint32_t offset = 0; offset + sizeof(rex::graphics::xenos::xe_gpu_texture_fetch_t) <=
|
||||
kProbeLimit;
|
||||
offset += 4) {
|
||||
if (!IsReadableGuestRange(memory, guest_ptr + offset,
|
||||
sizeof(rex::graphics::xenos::xe_gpu_texture_fetch_t))) {
|
||||
continue;
|
||||
}
|
||||
rex::graphics::xenos::xe_gpu_texture_fetch_t fetch{};
|
||||
if (!LoadTextureFetchAt(memory, guest_ptr + offset, fetch)) {
|
||||
continue;
|
||||
}
|
||||
if (fetch.type != rex::graphics::xenos::FetchConstantType::kTexture) {
|
||||
continue;
|
||||
}
|
||||
if (!IsPlausibleTextureFetchHeader(fetch)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
rex::graphics::TextureInfo texture_info;
|
||||
if (!rex::graphics::TextureInfo::Prepare(fetch, &texture_info) ||
|
||||
!IsPlausibleTextureInfo(texture_info)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
out.valid = true;
|
||||
out.header_offset = offset;
|
||||
out.dimension = texture_info.dimension;
|
||||
out.tiled = texture_info.is_tiled;
|
||||
out.width = texture_info.width + 1;
|
||||
out.height = texture_info.height + 1;
|
||||
out.depth = texture_info.depth + 1;
|
||||
out.base_address = texture_info.memory.base_address;
|
||||
out.format_name = texture_info.format_info()->name;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string FormatDecodedTextureObject(uint32_t guest_ptr) {
|
||||
if (guest_ptr == 0) {
|
||||
return "none";
|
||||
}
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(guest_ptr, decoded) || !decoded.valid) {
|
||||
char buffer[32];
|
||||
std::snprintf(buffer, sizeof(buffer), "%08X", guest_ptr);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
char buffer[128];
|
||||
const char* tiled_tag = decoded.tiled ? "t" : "l";
|
||||
if (decoded.dimension == rex::graphics::xenos::DataDimension::k3D || decoded.depth > 1) {
|
||||
std::snprintf(buffer, sizeof(buffer), "%08X+%02X[%ux%ux%u %s @%08X %s]", guest_ptr,
|
||||
decoded.header_offset, decoded.width, decoded.height, decoded.depth, tiled_tag,
|
||||
decoded.base_address, decoded.format_name ? decoded.format_name : "?");
|
||||
} else {
|
||||
std::snprintf(buffer, sizeof(buffer), "%08X+%02X[%ux%u %s @%08X %s]", guest_ptr,
|
||||
decoded.header_offset, decoded.width, decoded.height, tiled_tag,
|
||||
decoded.base_address, decoded.format_name ? decoded.format_name : "?");
|
||||
}
|
||||
return buffer;
|
||||
}
|
||||
|
||||
template <typename Sample>
|
||||
DecodedSampleSet<4> DecodeSampleSet(const Sample& sample) {
|
||||
DecodedSampleSet<4> decoded_set;
|
||||
const uint32_t shown =
|
||||
std::min<uint32_t>(sample.count, static_cast<uint32_t>(sample.values.size()));
|
||||
for (uint32_t i = 0; i < shown; ++i) {
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(sample.values[i], decoded) || !decoded.valid) {
|
||||
continue;
|
||||
}
|
||||
decoded_set.entries[decoded_set.count++] = decoded;
|
||||
}
|
||||
return decoded_set;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
bool SampleUsesBaseAddress(const DecodedSampleSet<N>& sample_set, uint32_t base_address) {
|
||||
if (base_address == 0) {
|
||||
return false;
|
||||
}
|
||||
for (uint32_t i = 0; i < sample_set.count; ++i) {
|
||||
if (sample_set.entries[i].base_address == base_address) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string FormatResolveArgCandidates(
|
||||
const NativeGraphicsRuntimeStatus::ResolveDiagnosticsEntry& resolve) {
|
||||
std::string formatted;
|
||||
for (uint32_t i = 0; i < resolve.args.size(); ++i) {
|
||||
if (resolve.args[i] == 0) {
|
||||
continue;
|
||||
}
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(resolve.args[i], decoded) || !decoded.valid) {
|
||||
continue;
|
||||
}
|
||||
char buffer[96];
|
||||
std::snprintf(buffer, sizeof(buffer), "r%u=%08X+%02X[%ux%u @%08X %s]", 4u + i,
|
||||
resolve.args[i], decoded.header_offset, decoded.width, decoded.height,
|
||||
decoded.base_address, decoded.format_name ? decoded.format_name : "?");
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
return formatted.empty() ? "none" : formatted;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::string FormatPassSources(const DecodedSampleSet<N>& first_fetch,
|
||||
const DecodedSampleSet<N>& last_fetch,
|
||||
const NativeGraphicsRuntimeStatus& status, uint32_t self_index) {
|
||||
std::string formatted;
|
||||
for (uint32_t other_index = 0; other_index < status.pass_diagnostics_count; ++other_index) {
|
||||
if (other_index == self_index) {
|
||||
continue;
|
||||
}
|
||||
const auto& other_pass = status.pass_diagnostics[other_index];
|
||||
if (!other_pass.valid || other_pass.render_target_0 == 0) {
|
||||
continue;
|
||||
}
|
||||
DecodedTextureFetch decoded_rt0;
|
||||
if (!DecodeTextureFetch(other_pass.render_target_0, decoded_rt0) || !decoded_rt0.valid ||
|
||||
decoded_rt0.base_address == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!SampleUsesBaseAddress(first_fetch, decoded_rt0.base_address) &&
|
||||
!SampleUsesBaseAddress(last_fetch, decoded_rt0.base_address)) {
|
||||
continue;
|
||||
}
|
||||
char buffer[48];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%u(rt0 #%u %ux%u @%08X)", other_index,
|
||||
other_pass.pass_index, decoded_rt0.width, decoded_rt0.height,
|
||||
decoded_rt0.base_address);
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
return formatted.empty() ? "none" : formatted;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
std::string FormatResolveSources(const DecodedSampleSet<N>& first_fetch,
|
||||
const DecodedSampleSet<N>& last_fetch,
|
||||
const NativeGraphicsRuntimeStatus& status) {
|
||||
std::string formatted;
|
||||
for (uint32_t resolve_index = 0; resolve_index < status.resolve_diagnostics_count;
|
||||
++resolve_index) {
|
||||
const auto& resolve = status.resolve_diagnostics[resolve_index];
|
||||
if (!resolve.valid) {
|
||||
continue;
|
||||
}
|
||||
for (uint32_t arg_index = 0; arg_index < resolve.args.size(); ++arg_index) {
|
||||
if (resolve.args[arg_index] == 0) {
|
||||
continue;
|
||||
}
|
||||
DecodedTextureFetch decoded;
|
||||
if (!DecodeTextureFetch(resolve.args[arg_index], decoded) || !decoded.valid ||
|
||||
decoded.base_address == 0) {
|
||||
continue;
|
||||
}
|
||||
if (!SampleUsesBaseAddress(first_fetch, decoded.base_address) &&
|
||||
!SampleUsesBaseAddress(last_fetch, decoded.base_address)) {
|
||||
continue;
|
||||
}
|
||||
char buffer[72];
|
||||
std::snprintf(buffer, sizeof(buffer), "#%u:r%u[%ux%u @%08X]", resolve_index, 4u + arg_index,
|
||||
decoded.width, decoded.height, decoded.base_address);
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
}
|
||||
return formatted.empty() ? "none" : formatted;
|
||||
}
|
||||
|
||||
template <typename Sample>
|
||||
std::string FormatBoundResourceSample(const Sample& sample) {
|
||||
if (sample.count == 0) {
|
||||
return "none";
|
||||
}
|
||||
std::string formatted;
|
||||
const uint32_t shown = std::min<uint32_t>(sample.count, static_cast<uint32_t>(sample.values.size()));
|
||||
for (uint32_t i = 0; i < shown; ++i) {
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
char buffer[32];
|
||||
std::snprintf(buffer, sizeof(buffer), "%u:%08X", sample.slots[i], sample.values[i]);
|
||||
formatted.append(buffer);
|
||||
}
|
||||
if (sample.count > shown) {
|
||||
formatted.append(", ...");
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
template <typename Sample>
|
||||
std::string FormatFetchConstantSample(const Sample& sample) {
|
||||
if (sample.count == 0) {
|
||||
return "none";
|
||||
}
|
||||
std::string formatted;
|
||||
const uint32_t shown = std::min<uint32_t>(sample.count, static_cast<uint32_t>(sample.values.size()));
|
||||
for (uint32_t i = 0; i < shown; ++i) {
|
||||
if (!formatted.empty()) {
|
||||
formatted.append(", ");
|
||||
}
|
||||
char buffer[96];
|
||||
DecodedTextureFetch decoded;
|
||||
if (DecodeTextureFetch(sample.values[i], decoded) && decoded.valid) {
|
||||
const char* tiled_tag = decoded.tiled ? "t" : "l";
|
||||
if (decoded.dimension == rex::graphics::xenos::DataDimension::k3D ||
|
||||
decoded.depth > 1) {
|
||||
std::snprintf(buffer, sizeof(buffer),
|
||||
"%u:%08X+%02X[%ux%ux%u %s @%08X %s]", sample.slots[i], sample.values[i],
|
||||
decoded.header_offset, decoded.width, decoded.height, decoded.depth,
|
||||
tiled_tag, decoded.base_address,
|
||||
decoded.format_name ? decoded.format_name : "?");
|
||||
} else {
|
||||
std::snprintf(buffer, sizeof(buffer), "%u:%08X+%02X[%ux%u %s @%08X %s]",
|
||||
sample.slots[i], sample.values[i], decoded.header_offset, decoded.width,
|
||||
decoded.height, tiled_tag, decoded.base_address,
|
||||
decoded.format_name ? decoded.format_name : "?");
|
||||
}
|
||||
} else {
|
||||
std::snprintf(buffer, sizeof(buffer), "%u:%08X", sample.slots[i], sample.values[i]);
|
||||
}
|
||||
formatted.append(buffer);
|
||||
}
|
||||
if (sample.count > shown) {
|
||||
formatted.append(", ...");
|
||||
}
|
||||
return formatted;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeGraphicsStatusDialog::NativeGraphicsStatusDialog(rex::ui::ImGuiDrawer* imgui_drawer)
|
||||
: ImGuiDialog(imgui_drawer) {}
|
||||
@@ -362,7 +18,10 @@ NativeGraphicsStatusDialog::~NativeGraphicsStatusDialog() = default;
|
||||
|
||||
void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
(void)io;
|
||||
if (!visible_) {
|
||||
|
||||
ApplyAc6PerformanceModeOverridesPublic();
|
||||
|
||||
if (REXCVAR_GET(ac6_performance_mode) || !visible_) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -387,11 +46,8 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
ImGui::Text("scaled tex offsets / direct host resolve: %s / %s",
|
||||
status.draw_resolution_scaled_texture_offsets ? "on" : "off",
|
||||
status.direct_host_resolve ? "on" : "off");
|
||||
ImGui::Text("experimental replay present override: %s",
|
||||
status.experimental_replay_present ? "enabled" : "disabled");
|
||||
ImGui::Text("analysis frames / replay frames: %llu / %llu",
|
||||
static_cast<unsigned long long>(status.analysis_frames_observed),
|
||||
static_cast<unsigned long long>(status.replay_frames_built));
|
||||
ImGui::Text("analysis frames: %llu",
|
||||
static_cast<unsigned long long>(status.analysis_frames_observed));
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("capture frame: %llu",
|
||||
@@ -409,109 +65,6 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
ImGui::Text("frame-end viewport: %ux%u",
|
||||
status.capture_summary.frame_end_viewport_width,
|
||||
status.capture_summary.frame_end_viewport_height);
|
||||
ImGui::Text("frontend passes scene/post/ui: %u / %u / %u / %u",
|
||||
status.frontend_summary.pass_count, status.frontend_summary.scene_pass_count,
|
||||
status.frontend_summary.post_process_pass_count,
|
||||
status.frontend_summary.ui_pass_count);
|
||||
if (status.frame_plan.present_stage.valid) {
|
||||
ImGui::Text(
|
||||
"present stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.present_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.present_stage.kind),
|
||||
status.frame_plan.present_stage.viewport_width,
|
||||
status.frame_plan.present_stage.viewport_height,
|
||||
status.frame_plan.present_stage.render_target_0,
|
||||
status.frame_plan.present_stage.depth_stencil,
|
||||
status.frame_plan.present_stage.draw_count,
|
||||
status.frame_plan.present_stage.resolve_count);
|
||||
}
|
||||
if (status.frame_plan.scene_stage.valid) {
|
||||
ImGui::Text(
|
||||
"scene stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.scene_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.scene_stage.kind),
|
||||
status.frame_plan.scene_stage.viewport_width,
|
||||
status.frame_plan.scene_stage.viewport_height,
|
||||
status.frame_plan.scene_stage.render_target_0,
|
||||
status.frame_plan.scene_stage.depth_stencil,
|
||||
status.frame_plan.scene_stage.draw_count,
|
||||
status.frame_plan.scene_stage.resolve_count);
|
||||
}
|
||||
if (status.frame_plan.post_process_stage.valid) {
|
||||
ImGui::Text(
|
||||
"post stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.post_process_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.post_process_stage.kind),
|
||||
status.frame_plan.post_process_stage.viewport_width,
|
||||
status.frame_plan.post_process_stage.viewport_height,
|
||||
status.frame_plan.post_process_stage.render_target_0,
|
||||
status.frame_plan.post_process_stage.depth_stencil,
|
||||
status.frame_plan.post_process_stage.draw_count,
|
||||
status.frame_plan.post_process_stage.resolve_count);
|
||||
}
|
||||
if (status.frame_plan.ui_stage.valid) {
|
||||
ImGui::Text(
|
||||
"ui stage: #%u %s vp=%ux%u rt0=%08X depth=%08X draws/resolves=%u/%u",
|
||||
status.frame_plan.ui_stage.pass_index,
|
||||
ac6::renderer::ToString(status.frame_plan.ui_stage.kind),
|
||||
status.frame_plan.ui_stage.viewport_width,
|
||||
status.frame_plan.ui_stage.viewport_height,
|
||||
status.frame_plan.ui_stage.render_target_0,
|
||||
status.frame_plan.ui_stage.depth_stencil,
|
||||
status.frame_plan.ui_stage.draw_count,
|
||||
status.frame_plan.ui_stage.resolve_count);
|
||||
}
|
||||
for (uint32_t i = 0; i < status.pass_diagnostics_count; ++i) {
|
||||
const auto& pass = status.pass_diagnostics[i];
|
||||
if (!pass.valid) {
|
||||
continue;
|
||||
}
|
||||
ImGui::Text(
|
||||
"pass[%u]: #%u %s score=%u vp=%ux%u+%u+%u rt0=%08X depth=%08X d/c/r=%u/%u/%u tex/str/samp/fetch=%u/%u/%u/%u gpr=%u %s%s",
|
||||
i, pass.pass_index, ac6::renderer::ToString(pass.kind), pass.score,
|
||||
pass.viewport_width, pass.viewport_height, pass.viewport_x, pass.viewport_y,
|
||||
pass.render_target_0, pass.depth_stencil, pass.draw_count, pass.clear_count,
|
||||
pass.resolve_count, pass.max_texture_count, pass.max_stream_count,
|
||||
pass.max_sampler_count, pass.max_fetch_constant_count, pass.max_shader_gpr_alloc,
|
||||
pass.selected_for_present ? "present " : "",
|
||||
pass.matches_frame_end_viewport ? "frame_end" : "");
|
||||
ImGui::Text(
|
||||
" sig=%016llX tfetch=%016llX->%016llX bind=%016llX->%016llX",
|
||||
static_cast<unsigned long long>(pass.pass_signature),
|
||||
static_cast<unsigned long long>(pass.first_texture_fetch_layout_signature),
|
||||
static_cast<unsigned long long>(pass.last_texture_fetch_layout_signature),
|
||||
static_cast<unsigned long long>(pass.first_resource_binding_signature),
|
||||
static_cast<unsigned long long>(pass.last_resource_binding_signature));
|
||||
const std::string first_textures = FormatBoundResourceSample(pass.first_textures);
|
||||
const std::string last_textures = FormatBoundResourceSample(pass.last_textures);
|
||||
const std::string first_fetch = FormatFetchConstantSample(pass.first_fetch_constants);
|
||||
const std::string last_fetch = FormatFetchConstantSample(pass.last_fetch_constants);
|
||||
const std::string rt0_object = FormatDecodedTextureObject(pass.render_target_0);
|
||||
const std::string depth_object = FormatDecodedTextureObject(pass.depth_stencil);
|
||||
const DecodedSampleSet<4> decoded_first_fetch = DecodeSampleSet(pass.first_fetch_constants);
|
||||
const DecodedSampleSet<4> decoded_last_fetch = DecodeSampleSet(pass.last_fetch_constants);
|
||||
const std::string source_passes =
|
||||
FormatPassSources(decoded_first_fetch, decoded_last_fetch, status, i);
|
||||
const std::string resolve_sources =
|
||||
FormatResolveSources(decoded_first_fetch, decoded_last_fetch, status);
|
||||
ImGui::Text(" tex %s -> %s", first_textures.c_str(), last_textures.c_str());
|
||||
ImGui::Text(" fet %s -> %s", first_fetch.c_str(), last_fetch.c_str());
|
||||
ImGui::Text(" rt0 %s depth %s", rt0_object.c_str(), depth_object.c_str());
|
||||
ImGui::Text(" src %s", source_passes.c_str());
|
||||
ImGui::Text(" rslv %s", resolve_sources.c_str());
|
||||
}
|
||||
for (uint32_t i = 0; i < status.resolve_diagnostics_count; ++i) {
|
||||
const auto& resolve = status.resolve_diagnostics[i];
|
||||
if (!resolve.valid) {
|
||||
continue;
|
||||
}
|
||||
ImGui::Text(
|
||||
"resolve[%u]: seq=%u vp=%ux%u rt0=%08X depth=%08X f1=%.3f", i, resolve.sequence,
|
||||
resolve.viewport_width, resolve.viewport_height, resolve.render_target_0,
|
||||
resolve.depth_stencil, resolve.depth_or_scale);
|
||||
const std::string candidates = FormatResolveArgCandidates(resolve);
|
||||
ImGui::Text(" %s", candidates.c_str());
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
ImGui::Text("swap source: %s", ac6::backend::ToString(diagnostics.swap_source));
|
||||
@@ -560,8 +113,6 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
: diagnostics.latest_signature_tags.c_str());
|
||||
const bool draw_scale_is_native =
|
||||
status.draw_resolution_scale_x <= 1 && status.draw_resolution_scale_y <= 1;
|
||||
const bool draw_scale_is_scaled =
|
||||
status.draw_resolution_scale_x > 1 || status.draw_resolution_scale_y > 1;
|
||||
const bool likely_point_filtered =
|
||||
diagnostics.latest_signature.point_min_sampler_count != 0 ||
|
||||
diagnostics.latest_signature.point_mip_sampler_count != 0;
|
||||
@@ -613,26 +164,6 @@ void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
diagnostics.audio_callback_dispatch_count,
|
||||
diagnostics.audio_callback_throttle_count);
|
||||
|
||||
if (status.mode == GraphicsRuntimeMode::kLegacyReplayExperimental) {
|
||||
ImGui::Separator();
|
||||
ImGui::TextUnformatted("legacy replay diagnostics (experimental):");
|
||||
ImGui::Text("initialized: %s", status.initialized ? "true" : "false");
|
||||
ImGui::Text("init failures seen: %s", status.had_init_failure ? "true" : "false");
|
||||
ImGui::Text("replay backend: %s",
|
||||
ac6::renderer::ToString(status.active_backend).data());
|
||||
ImGui::Text("replay feature level: %s",
|
||||
ac6::renderer::ToString(status.feature_level).data());
|
||||
ImGui::Text("frontend / replay / execution commands: %u / %u / %u",
|
||||
status.frontend_summary.total_command_count,
|
||||
status.replay_summary.command_count,
|
||||
status.execution_summary.command_count);
|
||||
ImGui::Text("backend draw attempts / success: %u / %u",
|
||||
status.backend_executor_status.draw_attempt_count,
|
||||
status.backend_executor_status.draw_success_count);
|
||||
ImGui::Text("planned output: %ux%u", status.frame_plan.output_width,
|
||||
status.frame_plan.output_height);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
|
||||
@@ -1,450 +0,0 @@
|
||||
#include "ac6_render_frontend.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace ac6::renderer {
|
||||
namespace {
|
||||
|
||||
void HashU32(uint64_t& hash, uint32_t value) {
|
||||
constexpr uint64_t kFnvPrime = 1099511628211ull;
|
||||
hash ^= value;
|
||||
hash *= kFnvPrime;
|
||||
}
|
||||
|
||||
void HashU64(uint64_t& hash, uint64_t value) {
|
||||
HashU32(hash, uint32_t(value & 0xFFFFFFFFull));
|
||||
HashU32(hash, uint32_t(value >> 32));
|
||||
}
|
||||
|
||||
template <typename Container>
|
||||
uint32_t CountNonZeroEntries(const Container& values) {
|
||||
uint32_t count = 0;
|
||||
for (const auto& value : values) {
|
||||
if (value) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
ObservedPassDesc::BoundResourceSample SampleBoundEntries(
|
||||
const std::array<uint32_t, N>& values) {
|
||||
ObservedPassDesc::BoundResourceSample sample;
|
||||
for (uint32_t slot = 0; slot < values.size(); ++slot) {
|
||||
if (!values[slot]) {
|
||||
continue;
|
||||
}
|
||||
if (sample.count < sample.values.size()) {
|
||||
sample.slots[sample.count] = slot;
|
||||
sample.values[sample.count] = values[slot];
|
||||
}
|
||||
++sample.count;
|
||||
}
|
||||
return sample;
|
||||
}
|
||||
|
||||
uint32_t CountBoundStreams(
|
||||
const std::array<ac6::d3d::StreamBinding, ac6::d3d::kMaxStreams>& streams) {
|
||||
uint32_t count = 0;
|
||||
for (const auto& stream : streams) {
|
||||
if (stream.buffer) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
uint32_t CountBoundSamplers(
|
||||
const std::array<ac6::d3d::SamplerBinding, ac6::d3d::kMaxSamplers>& samplers) {
|
||||
uint32_t count = 0;
|
||||
for (const auto& sampler : samplers) {
|
||||
if (sampler.mag_filter || sampler.min_filter || sampler.mip_filter ||
|
||||
sampler.mip_level || sampler.border_color) {
|
||||
++count;
|
||||
}
|
||||
}
|
||||
return count;
|
||||
}
|
||||
|
||||
enum class CapturedFrameEventType : uint8_t {
|
||||
kDraw = 0,
|
||||
kClear = 1,
|
||||
kResolve = 2,
|
||||
};
|
||||
|
||||
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,
|
||||
.shadow_state = draw.shadow_state,
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
.shadow_state = clear.shadow_state,
|
||||
};
|
||||
}
|
||||
|
||||
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,
|
||||
.shadow_state = resolve.shadow_state,
|
||||
};
|
||||
}
|
||||
|
||||
bool SamePassBinding(const ac6::d3d::ShadowState& left,
|
||||
const ac6::d3d::ShadowState& right) {
|
||||
return left.render_targets[0] == right.render_targets[0] &&
|
||||
left.depth_stencil == right.depth_stencil &&
|
||||
left.viewport.x == right.viewport.x &&
|
||||
left.viewport.y == right.viewport.y &&
|
||||
left.viewport.width == right.viewport.width &&
|
||||
left.viewport.height == right.viewport.height;
|
||||
}
|
||||
|
||||
bool MatchesFrameEndViewport(const ObservedPassDesc& pass,
|
||||
const ac6::d3d::ShadowState& frame_end_shadow) {
|
||||
return pass.viewport_width != 0 && pass.viewport_height != 0 &&
|
||||
pass.viewport_width == frame_end_shadow.viewport.width &&
|
||||
pass.viewport_height == frame_end_shadow.viewport.height;
|
||||
}
|
||||
|
||||
uint32_t ScorePassCandidate(const ObservedPassDesc& pass, bool is_last_pass) {
|
||||
uint32_t score = 0;
|
||||
score += std::min<uint32_t>(pass.draw_count * 3, 180u);
|
||||
score += std::min<uint32_t>(pass.clear_count * 8, 40u);
|
||||
score += std::min<uint32_t>(pass.resolve_count * 30, 90u);
|
||||
score += std::min<uint32_t>(pass.max_texture_count * 2, 24u);
|
||||
score += std::min<uint32_t>(pass.max_stream_count * 3, 24u);
|
||||
if (pass.matches_frame_end_viewport) {
|
||||
score += 80;
|
||||
}
|
||||
if (pass.render_target_0 != 0) {
|
||||
score += 20;
|
||||
}
|
||||
if (is_last_pass) {
|
||||
score += 25;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
ObservedPassKind ClassifyPass(const ObservedPassDesc& pass, bool is_last_pass) {
|
||||
if (pass.resolve_count != 0) {
|
||||
return ObservedPassKind::kPostProcess;
|
||||
}
|
||||
const bool likely_ui =
|
||||
pass.draw_count != 0 && pass.clear_count == 0 && pass.max_texture_count >= 2 &&
|
||||
pass.max_stream_count <= 2 && pass.max_sampler_count <= 4 &&
|
||||
pass.viewport_width != 0 && pass.viewport_height != 0;
|
||||
if ((is_last_pass && pass.draw_count <= 16 && pass.clear_count == 0) ||
|
||||
(likely_ui && pass.draw_count <= 24 && pass.max_draw_call_count <= 1024)) {
|
||||
return ObservedPassKind::kUiComposite;
|
||||
}
|
||||
if (pass.draw_count != 0 || pass.clear_count != 0) {
|
||||
return ObservedPassKind::kScene;
|
||||
}
|
||||
return ObservedPassKind::kUnknown;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* ToString(ObservedPassKind kind) {
|
||||
switch (kind) {
|
||||
case ObservedPassKind::kScene:
|
||||
return "scene";
|
||||
case ObservedPassKind::kPostProcess:
|
||||
return "post_process";
|
||||
case ObservedPassKind::kUiComposite:
|
||||
return "ui_composite";
|
||||
case ObservedPassKind::kUnknown:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
FrontendFrameSummary Ac6RenderFrontend::BuildFromCapture(
|
||||
const ac6::d3d::FrameCaptureSnapshot& frame_capture) {
|
||||
Reset();
|
||||
|
||||
summary_.frame_index = frame_capture.frame_index;
|
||||
summary_.capture_valid =
|
||||
!frame_capture.draws.empty() || !frame_capture.clears.empty() ||
|
||||
!frame_capture.resolves.empty();
|
||||
if (!summary_.capture_valid) {
|
||||
return summary_;
|
||||
}
|
||||
|
||||
std::vector<CapturedFrameEvent> events;
|
||||
events.reserve(frame_capture.draws.size() + frame_capture.clears.size() +
|
||||
frame_capture.resolves.size());
|
||||
|
||||
for (const auto& draw : frame_capture.draws) {
|
||||
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, nullptr, &clear,
|
||||
nullptr, &clear.shadow_state});
|
||||
++summary_.total_clear_count;
|
||||
}
|
||||
for (const auto& resolve : frame_capture.resolves) {
|
||||
events.push_back({resolve.sequence, CapturedFrameEventType::kResolve, nullptr, nullptr,
|
||||
&resolve, &resolve.shadow_state});
|
||||
++summary_.total_resolve_count;
|
||||
}
|
||||
|
||||
std::sort(events.begin(), events.end(),
|
||||
[](const CapturedFrameEvent& left, const CapturedFrameEvent& right) {
|
||||
return left.sequence < right.sequence;
|
||||
});
|
||||
|
||||
const ac6::d3d::ShadowState empty_shadow{};
|
||||
const ac6::d3d::ShadowState* current_binding = &empty_shadow;
|
||||
bool current_pass_valid = false;
|
||||
ObservedPassDesc current_pass{};
|
||||
|
||||
auto flush_pass = [&]() {
|
||||
if (!current_pass_valid) {
|
||||
return;
|
||||
}
|
||||
passes_.push_back(current_pass);
|
||||
current_pass = {};
|
||||
current_pass_valid = false;
|
||||
};
|
||||
|
||||
for (const CapturedFrameEvent& event : events) {
|
||||
if (event.shadow_state == nullptr) {
|
||||
continue;
|
||||
}
|
||||
if (!current_pass_valid || !SamePassBinding(*current_binding, *event.shadow_state)) {
|
||||
flush_pass();
|
||||
current_binding = event.shadow_state;
|
||||
current_pass_valid = true;
|
||||
current_pass.start_sequence = event.sequence;
|
||||
current_pass.render_target_0 = event.shadow_state->render_targets[0];
|
||||
current_pass.depth_stencil = event.shadow_state->depth_stencil;
|
||||
current_pass.viewport_x = event.shadow_state->viewport.x;
|
||||
current_pass.viewport_y = event.shadow_state->viewport.y;
|
||||
current_pass.viewport_width = event.shadow_state->viewport.width;
|
||||
current_pass.viewport_height = event.shadow_state->viewport.height;
|
||||
current_pass.first_vertex_fetch_layout_signature =
|
||||
event.shadow_state->vertex_fetch_layout_signature;
|
||||
current_pass.last_vertex_fetch_layout_signature =
|
||||
event.shadow_state->vertex_fetch_layout_signature;
|
||||
current_pass.first_texture_fetch_layout_signature =
|
||||
event.shadow_state->texture_fetch_layout_signature;
|
||||
current_pass.last_texture_fetch_layout_signature =
|
||||
event.shadow_state->texture_fetch_layout_signature;
|
||||
current_pass.first_resource_binding_signature =
|
||||
event.shadow_state->resource_binding_signature;
|
||||
current_pass.last_resource_binding_signature =
|
||||
event.shadow_state->resource_binding_signature;
|
||||
current_pass.first_textures = SampleBoundEntries(event.shadow_state->textures);
|
||||
current_pass.last_textures = current_pass.first_textures;
|
||||
current_pass.first_fetch_constants =
|
||||
SampleBoundEntries(event.shadow_state->texture_fetch_ptrs);
|
||||
current_pass.last_fetch_constants = current_pass.first_fetch_constants;
|
||||
constexpr uint64_t kFnvOffsetBasis = 1469598103934665603ull;
|
||||
current_pass.pass_signature = kFnvOffsetBasis;
|
||||
}
|
||||
|
||||
current_pass.end_sequence = event.sequence;
|
||||
HashU32(current_pass.pass_signature, static_cast<uint32_t>(event.type));
|
||||
HashU32(current_pass.pass_signature, event.sequence);
|
||||
HashU64(current_pass.pass_signature, event.shadow_state->vertex_fetch_layout_signature);
|
||||
HashU64(current_pass.pass_signature, event.shadow_state->texture_fetch_layout_signature);
|
||||
HashU64(current_pass.pass_signature, event.shadow_state->resource_binding_signature);
|
||||
current_pass.last_vertex_fetch_layout_signature =
|
||||
event.shadow_state->vertex_fetch_layout_signature;
|
||||
current_pass.last_texture_fetch_layout_signature =
|
||||
event.shadow_state->texture_fetch_layout_signature;
|
||||
current_pass.last_resource_binding_signature =
|
||||
event.shadow_state->resource_binding_signature;
|
||||
current_pass.last_textures = SampleBoundEntries(event.shadow_state->textures);
|
||||
current_pass.last_fetch_constants =
|
||||
SampleBoundEntries(event.shadow_state->texture_fetch_ptrs);
|
||||
current_pass.max_shader_gpr_alloc =
|
||||
std::max(current_pass.max_shader_gpr_alloc, event.shadow_state->shader_gpr_alloc);
|
||||
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,
|
||||
CountNonZeroEntries(event.shadow_state->textures));
|
||||
current_pass.max_stream_count = std::max(
|
||||
current_pass.max_stream_count,
|
||||
CountBoundStreams(event.shadow_state->streams));
|
||||
current_pass.max_sampler_count = std::max(
|
||||
current_pass.max_sampler_count,
|
||||
CountBoundSamplers(event.shadow_state->samplers));
|
||||
current_pass.max_fetch_constant_count = std::max(
|
||||
current_pass.max_fetch_constant_count,
|
||||
CountNonZeroEntries(event.shadow_state->texture_fetch_ptrs));
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
flush_pass();
|
||||
|
||||
summary_.pass_count = static_cast<uint32_t>(passes_.size());
|
||||
if (passes_.empty()) {
|
||||
summary_.capture_valid = false;
|
||||
return summary_;
|
||||
}
|
||||
|
||||
for (const auto& draw : frame_capture.draws) {
|
||||
for (ObservedPassDesc& pass : passes_) {
|
||||
if (draw.sequence < pass.start_sequence || draw.sequence > pass.end_sequence) {
|
||||
continue;
|
||||
}
|
||||
switch (draw.kind) {
|
||||
case ac6::d3d::DrawCallKind::kIndexed:
|
||||
++pass.indexed_draw_count;
|
||||
break;
|
||||
case ac6::d3d::DrawCallKind::kIndexedShared:
|
||||
++pass.indexed_shared_draw_count;
|
||||
break;
|
||||
case ac6::d3d::DrawCallKind::kPrimitive:
|
||||
++pass.primitive_draw_count;
|
||||
break;
|
||||
}
|
||||
pass.max_draw_call_count = std::max(pass.max_draw_call_count, draw.count);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
uint32_t selected_pass_index = 0;
|
||||
uint32_t selected_pass_score = 0;
|
||||
bool selected_pass_valid = false;
|
||||
for (uint32_t i = 0; i < passes_.size(); ++i) {
|
||||
ObservedPassDesc& pass = passes_[i];
|
||||
const bool is_last_pass = (i + 1) == passes_.size();
|
||||
pass.matches_frame_end_viewport =
|
||||
MatchesFrameEndViewport(pass, frame_capture.frame_end_shadow);
|
||||
pass.kind = ClassifyPass(pass, is_last_pass);
|
||||
const uint32_t score = ScorePassCandidate(pass, is_last_pass);
|
||||
if (!selected_pass_valid || score > selected_pass_score ||
|
||||
(score == selected_pass_score && is_last_pass)) {
|
||||
selected_pass_valid = true;
|
||||
selected_pass_index = i;
|
||||
selected_pass_score = score;
|
||||
}
|
||||
|
||||
switch (pass.kind) {
|
||||
case ObservedPassKind::kScene:
|
||||
++summary_.scene_pass_count;
|
||||
break;
|
||||
case ObservedPassKind::kPostProcess:
|
||||
++summary_.post_process_pass_count;
|
||||
break;
|
||||
case ObservedPassKind::kUiComposite:
|
||||
++summary_.ui_pass_count;
|
||||
break;
|
||||
case ObservedPassKind::kUnknown:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (selected_pass_valid) {
|
||||
passes_[selected_pass_index].selected_for_present = true;
|
||||
summary_.selected_pass_index = selected_pass_index;
|
||||
}
|
||||
|
||||
return summary_;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,124 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "d3d_state.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
enum class ObservedPassKind : uint8_t {
|
||||
kUnknown = 0,
|
||||
kScene = 1,
|
||||
kPostProcess = 2,
|
||||
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;
|
||||
ac6::d3d::ShadowState shadow_state{};
|
||||
};
|
||||
|
||||
struct ObservedPassDesc {
|
||||
struct BoundResourceSample {
|
||||
uint32_t count = 0;
|
||||
std::array<uint32_t, 4> slots{};
|
||||
std::array<uint32_t, 4> values{};
|
||||
};
|
||||
|
||||
ObservedPassKind kind = ObservedPassKind::kUnknown;
|
||||
uint64_t pass_signature = 0;
|
||||
uint32_t start_sequence = 0;
|
||||
uint32_t end_sequence = 0;
|
||||
uint32_t draw_count = 0;
|
||||
uint32_t clear_count = 0;
|
||||
uint32_t resolve_count = 0;
|
||||
uint32_t indexed_draw_count = 0;
|
||||
uint32_t indexed_shared_draw_count = 0;
|
||||
uint32_t primitive_draw_count = 0;
|
||||
uint32_t max_texture_count = 0;
|
||||
uint32_t max_stream_count = 0;
|
||||
uint32_t max_sampler_count = 0;
|
||||
uint32_t max_fetch_constant_count = 0;
|
||||
uint32_t max_draw_call_count = 0;
|
||||
uint32_t max_shader_gpr_alloc = 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;
|
||||
uint64_t first_vertex_fetch_layout_signature = 0;
|
||||
uint64_t last_vertex_fetch_layout_signature = 0;
|
||||
uint64_t first_texture_fetch_layout_signature = 0;
|
||||
uint64_t last_texture_fetch_layout_signature = 0;
|
||||
uint64_t first_resource_binding_signature = 0;
|
||||
uint64_t last_resource_binding_signature = 0;
|
||||
BoundResourceSample first_textures{};
|
||||
BoundResourceSample last_textures{};
|
||||
BoundResourceSample first_fetch_constants{};
|
||||
BoundResourceSample last_fetch_constants{};
|
||||
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;
|
||||
uint32_t selected_pass_index = 0;
|
||||
uint32_t total_draw_count = 0;
|
||||
uint32_t total_clear_count = 0;
|
||||
uint32_t total_resolve_count = 0;
|
||||
};
|
||||
|
||||
class Ac6RenderFrontend {
|
||||
public:
|
||||
void Reset();
|
||||
FrontendFrameSummary BuildFromCapture(const ac6::d3d::FrameCaptureSnapshot& frame_capture);
|
||||
|
||||
const FrontendFrameSummary& summary() const { return summary_; }
|
||||
const std::vector<ObservedPassDesc>& passes() const { return passes_; }
|
||||
|
||||
private:
|
||||
FrontendFrameSummary summary_{};
|
||||
std::vector<ObservedPassDesc> passes_;
|
||||
};
|
||||
|
||||
const char* ToString(ObservedPassKind kind);
|
||||
const char* ToString(ObservedCommandType type);
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,24 +0,0 @@
|
||||
#include "ac6_native_renderer/render_device.h"
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include "ac6_native_renderer/backends/d3d12_backend.h"
|
||||
#include "ac6_native_renderer/backends/metal_backend.h"
|
||||
#include "ac6_native_renderer/backends/vulkan_backend.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
std::unique_ptr<RenderDeviceBackend> CreateBackend(BackendType backend) {
|
||||
switch (backend) {
|
||||
case BackendType::kD3D12:
|
||||
return std::make_unique<D3D12Backend>();
|
||||
case BackendType::kVulkan:
|
||||
return std::make_unique<VulkanBackend>();
|
||||
case BackendType::kMetal:
|
||||
return std::make_unique<MetalBackend>();
|
||||
default:
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,137 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../render_device.h"
|
||||
#include "../frame_scheduler.h"
|
||||
|
||||
#include "d3d12_resource_manager.h"
|
||||
#include "d3d12_resource_tracker.h"
|
||||
#include "d3d12_shader_manager.h"
|
||||
|
||||
#include <vector>
|
||||
#include <unordered_map>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <wrl/client.h>
|
||||
#include <d3d12.h>
|
||||
#include <dxgi1_6.h>
|
||||
#endif
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
// Experimental replay backend retained for research and targeted override work.
|
||||
// The authoritative default presentation path remains the RexGlue backend.
|
||||
class D3D12Backend final : public RenderDeviceBackend {
|
||||
public:
|
||||
BackendType GetType() const override { return BackendType::kD3D12; }
|
||||
std::string_view GetName() const override { return "d3d12"; }
|
||||
bool IsSupported() const override;
|
||||
bool Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) override;
|
||||
bool InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory,
|
||||
ID3D12Device* device, ID3D12CommandQueue* queue);
|
||||
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) override;
|
||||
BackendExecutorStatus GetExecutorStatus() const override { return executor_status_; }
|
||||
void Shutdown() override;
|
||||
|
||||
// Phase 4: Returns the native output texture for swapchain blit.
|
||||
// nullptr until a frame has been rendered.
|
||||
ID3D12Resource* GetOutputTexture() const { return output_texture_.Get(); }
|
||||
|
||||
private:
|
||||
BackendExecutorStatus executor_status_{};
|
||||
bool initialized_ = false;
|
||||
rex::memory::Memory* memory_ = nullptr;
|
||||
|
||||
#if defined(_WIN32)
|
||||
struct FrameContext {
|
||||
Microsoft::WRL::ComPtr<ID3D12CommandAllocator> command_allocator;
|
||||
Microsoft::WRL::ComPtr<ID3D12GraphicsCommandList> command_list;
|
||||
uint64_t fence_value = 0;
|
||||
};
|
||||
|
||||
struct DrawResources {
|
||||
bool valid = false;
|
||||
bool indexed = false;
|
||||
uint32_t draw_count = 0;
|
||||
uint32_t draw_start = 0;
|
||||
uint32_t vertex_base_offset = 0;
|
||||
uint32_t vertex_stride = 0;
|
||||
uint32_t vertex_buffer_size = 0;
|
||||
uint32_t color_offset = 0xFFFFFFFFu;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE vertex_buffer_gpu{};
|
||||
D3D12_PRIMITIVE_TOPOLOGY topology = D3D_PRIMITIVE_TOPOLOGY_TRIANGLELIST;
|
||||
D3D12_PRIMITIVE_TOPOLOGY_TYPE topology_type = D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE;
|
||||
};
|
||||
|
||||
struct SubmissionDebugStats {
|
||||
uint32_t draw_attempt_count = 0;
|
||||
uint32_t draw_success_count = 0;
|
||||
uint32_t draw_prepare_failure_count = 0;
|
||||
uint32_t draw_pso_failure_count = 0;
|
||||
uint32_t indexed_draw_count = 0;
|
||||
uint32_t non_indexed_draw_count = 0;
|
||||
uint32_t clear_command_count = 0;
|
||||
uint32_t resolve_command_count = 0;
|
||||
uint32_t invalid_stream_binding_count = 0;
|
||||
uint32_t invalid_index_buffer_count = 0;
|
||||
uint32_t index_count_overflow_count = 0;
|
||||
uint32_t index_data_unavailable_count = 0;
|
||||
uint32_t index_buffer_create_failure_count = 0;
|
||||
uint32_t index_upload_failure_count = 0;
|
||||
uint32_t zero_vertex_count = 0;
|
||||
uint32_t invalid_vertex_range_count = 0;
|
||||
uint32_t vertex_buffer_size_invalid_count = 0;
|
||||
uint32_t vertex_buffer_create_failure_count = 0;
|
||||
uint32_t vertex_data_unavailable_count = 0;
|
||||
uint32_t vertex_upload_failure_count = 0;
|
||||
};
|
||||
|
||||
Microsoft::WRL::ComPtr<IDXGIFactory4> dxgi_factory_;
|
||||
Microsoft::WRL::ComPtr<ID3D12Device> device_;
|
||||
Microsoft::WRL::ComPtr<ID3D12CommandQueue> graphics_queue_;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D12Fence> fence_;
|
||||
void* fence_event_ = nullptr;
|
||||
uint64_t current_fence_value_ = 0;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D12RootSignature> root_signature_;
|
||||
|
||||
// Phase 4: output render target (native renderer draws into this)
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> output_texture_;
|
||||
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> output_rtv_heap_;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE output_rtv_{};
|
||||
uint32_t output_width_ = 0;
|
||||
uint32_t output_height_ = 0;
|
||||
static constexpr DXGI_FORMAT kOutputFormat = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
|
||||
FrameScheduler frame_scheduler_;
|
||||
std::vector<FrameContext> frame_contexts_;
|
||||
SubmissionDebugStats submission_debug_stats_{};
|
||||
|
||||
D3D12ResourceManager resource_manager_;
|
||||
D3D12ResourceTracker resource_tracker_;
|
||||
D3D12ShaderManager shader_manager_;
|
||||
|
||||
// PSO state hash helper
|
||||
uint64_t MakePSOHash(DXGI_FORMAT rt_fmt, DXGI_FORMAT ds_fmt,
|
||||
D3D12_PRIMITIVE_TOPOLOGY_TYPE topo, bool soft_particle) const;
|
||||
|
||||
// Ensure output texture is created at the right size
|
||||
bool EnsureOutputTexture(uint32_t width, uint32_t height);
|
||||
|
||||
bool CreateDevice();
|
||||
bool CreateCommandObjects(uint32_t num_frames);
|
||||
bool CreateRootSignature();
|
||||
void WaitForGpu();
|
||||
|
||||
// Phase 3 helpers
|
||||
void DispatchPassCommands(ID3D12GraphicsCommandList* cmd,
|
||||
const ReplayExecutorPassPacket& pass,
|
||||
uint32_t slot);
|
||||
bool PrepareDrawResources(ID3D12GraphicsCommandList* cmd,
|
||||
const ReplayExecutorCommandPacket& command,
|
||||
uint32_t slot,
|
||||
DrawResources& out_resources);
|
||||
#endif
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,258 +0,0 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include "d3d12_resource_manager.h"
|
||||
|
||||
#include <rex/logging.h>
|
||||
#include <rex/graphics/xenos.h>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
bool D3D12ResourceManager::Initialize(ID3D12Device* device, uint32_t max_frames) {
|
||||
device_ = device;
|
||||
max_frames_ = max_frames;
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC rtv_desc = {};
|
||||
rtv_desc.NumDescriptors = kMaxRtvDescriptors;
|
||||
rtv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_RTV;
|
||||
if (FAILED(device_->CreateDescriptorHeap(&rtv_desc, IID_PPV_ARGS(&rtv_heap_)))) return false;
|
||||
rtv_size_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_RTV);
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC dsv_desc = {};
|
||||
dsv_desc.NumDescriptors = kMaxDsvDescriptors;
|
||||
dsv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_DSV;
|
||||
if (FAILED(device_->CreateDescriptorHeap(&dsv_desc, IID_PPV_ARGS(&dsv_heap_)))) return false;
|
||||
dsv_size_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_DSV);
|
||||
|
||||
D3D12_DESCRIPTOR_HEAP_DESC srv_desc = {};
|
||||
srv_desc.NumDescriptors = kMaxSrvDescriptors;
|
||||
srv_desc.Type = D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV;
|
||||
srv_desc.Flags = D3D12_DESCRIPTOR_HEAP_FLAG_SHADER_VISIBLE;
|
||||
if (FAILED(device_->CreateDescriptorHeap(&srv_desc, IID_PPV_ARGS(&srv_heap_)))) return false;
|
||||
srv_size_ = device_->GetDescriptorHandleIncrementSize(D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
|
||||
frame_contexts_.resize(max_frames);
|
||||
for (uint32_t i = 0; i < max_frames; ++i) {
|
||||
D3D12_HEAP_PROPERTIES upload_props = {};
|
||||
upload_props.Type = D3D12_HEAP_TYPE_UPLOAD;
|
||||
|
||||
D3D12_RESOURCE_DESC buffer_desc = {};
|
||||
buffer_desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
buffer_desc.Width = kUploadBufferSize;
|
||||
buffer_desc.Height = 1;
|
||||
buffer_desc.DepthOrArraySize = 1;
|
||||
buffer_desc.MipLevels = 1;
|
||||
buffer_desc.Format = DXGI_FORMAT_UNKNOWN;
|
||||
buffer_desc.SampleDesc.Count = 1;
|
||||
buffer_desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
|
||||
if (FAILED(device_->CreateCommittedResource(&upload_props, D3D12_HEAP_FLAG_NONE, &buffer_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, IID_PPV_ARGS(&frame_contexts_[i].upload_buffer)))) {
|
||||
return false;
|
||||
}
|
||||
|
||||
D3D12_RANGE read_range = {0, 0};
|
||||
if (FAILED(frame_contexts_[i].upload_buffer->Map(0, &read_range, reinterpret_cast<void**>(&frame_contexts_[i].upload_ptr)))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12ResourceManager::Shutdown() {
|
||||
for (auto& ctx : frame_contexts_) {
|
||||
if (ctx.upload_buffer) {
|
||||
ctx.upload_buffer->Unmap(0, nullptr);
|
||||
}
|
||||
}
|
||||
frame_contexts_.clear();
|
||||
resource_cache_.clear();
|
||||
rtv_heap_.Reset();
|
||||
dsv_heap_.Reset();
|
||||
srv_heap_.Reset();
|
||||
}
|
||||
|
||||
void D3D12ResourceManager::BeginFrame(uint32_t frame_index) {
|
||||
current_frame_index_ = frame_index;
|
||||
// Reset transient descriptors
|
||||
rtv_ptr_ = 0;
|
||||
dsv_ptr_ = 0;
|
||||
srv_ptr_ = 0;
|
||||
|
||||
FrameContext& ctx = frame_contexts_[current_frame_index_ % max_frames_];
|
||||
ctx.upload_offset = 0;
|
||||
|
||||
// Simple LRU cleanup could go here
|
||||
}
|
||||
|
||||
ID3D12Resource* D3D12ResourceManager::GetOrCreateBuffer(uint32_t guest_address, uint32_t size, D3D12_RESOURCE_FLAGS flags) {
|
||||
if (guest_address == 0) return nullptr;
|
||||
|
||||
auto it = resource_cache_.find(guest_address);
|
||||
if (it != resource_cache_.end()) {
|
||||
if (it->second.size_bytes >= size) {
|
||||
it->second.last_used_frame = current_frame_index_;
|
||||
return it->second.resource.Get();
|
||||
}
|
||||
REXLOG_INFO("Growing cached D3D12 buffer for guest address 0x{:08X} from {} to {} bytes",
|
||||
guest_address, it->second.size_bytes, size);
|
||||
resource_cache_.erase(it);
|
||||
}
|
||||
|
||||
if (size == 0) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
D3D12_HEAP_PROPERTIES heap_props = {};
|
||||
heap_props.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_BUFFER;
|
||||
desc.Width = size;
|
||||
desc.Height = 1;
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = DXGI_FORMAT_UNKNOWN;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Layout = D3D12_TEXTURE_LAYOUT_ROW_MAJOR;
|
||||
desc.Flags = flags;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> resource;
|
||||
if (FAILED(device_->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&resource)))) {
|
||||
REXLOG_ERROR("Failed to create D3D12 buffer for guest address 0x{:08X}", guest_address);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
resource_cache_[guest_address] = {resource, size, current_frame_index_};
|
||||
return resource.Get();
|
||||
}
|
||||
|
||||
ID3D12Resource* D3D12ResourceManager::GetOrCreateTexture(uint32_t guest_address, const d3d::ShadowState& state) {
|
||||
// In a real implementation, we would extract width, height, format from the fetch constant at guest_address
|
||||
// For this scaffold realization, we use guest_address as the key.
|
||||
(void)state;
|
||||
|
||||
if (guest_address == 0) return nullptr;
|
||||
|
||||
auto it = resource_cache_.find(guest_address);
|
||||
if (it != resource_cache_.end()) {
|
||||
it->second.last_used_frame = current_frame_index_;
|
||||
return it->second.resource.Get();
|
||||
}
|
||||
|
||||
// Placeholder texture creation
|
||||
D3D12_RESOURCE_DESC desc = {};
|
||||
desc.Dimension = D3D12_RESOURCE_DIMENSION_TEXTURE2D;
|
||||
desc.Width = 1024; // Mock
|
||||
desc.Height = 1024; // Mock
|
||||
desc.DepthOrArraySize = 1;
|
||||
desc.MipLevels = 1;
|
||||
desc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
desc.SampleDesc.Count = 1;
|
||||
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
|
||||
|
||||
D3D12_HEAP_PROPERTIES heap_props = {};
|
||||
heap_props.Type = D3D12_HEAP_TYPE_DEFAULT;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> resource;
|
||||
if (FAILED(device_->CreateCommittedResource(&heap_props, D3D12_HEAP_FLAG_NONE, &desc, D3D12_RESOURCE_STATE_COMMON, nullptr, IID_PPV_ARGS(&resource)))) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
resource_cache_[guest_address] = {resource, 0, current_frame_index_};
|
||||
return resource.Get();
|
||||
}
|
||||
|
||||
D3D12ResourceManager::ResourceView D3D12ResourceManager::AllocateRTV() {
|
||||
uint32_t index = rtv_ptr_++;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpu = rtv_heap_->GetCPUDescriptorHandleForHeapStart();
|
||||
cpu.ptr += index * rtv_size_;
|
||||
return {cpu, {0}, index};
|
||||
}
|
||||
|
||||
D3D12ResourceManager::ResourceView D3D12ResourceManager::AllocateDSV() {
|
||||
uint32_t index = dsv_ptr_++;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpu = dsv_heap_->GetCPUDescriptorHandleForHeapStart();
|
||||
cpu.ptr += index * dsv_size_;
|
||||
return {cpu, {0}, index};
|
||||
}
|
||||
|
||||
D3D12ResourceManager::ResourceView D3D12ResourceManager::AllocateSRV() {
|
||||
uint32_t index = srv_ptr_++;
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpu = srv_heap_->GetCPUDescriptorHandleForHeapStart();
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu = srv_heap_->GetGPUDescriptorHandleForHeapStart();
|
||||
cpu.ptr += index * srv_size_;
|
||||
gpu.ptr += index * srv_size_;
|
||||
return {cpu, gpu, index};
|
||||
}
|
||||
|
||||
bool D3D12ResourceManager::UploadData(ID3D12GraphicsCommandList* command_list, ID3D12Resource* destination, const void* data, uint64_t size, uint64_t destination_offset) {
|
||||
if (!destination || !data || size == 0) return false;
|
||||
|
||||
FrameContext& ctx = frame_contexts_[current_frame_index_ % max_frames_];
|
||||
|
||||
// Align offset to 256 bytes for good practice, though not strictly required for all buffer copies
|
||||
uint32_t aligned_offset = (ctx.upload_offset + 255) & ~255;
|
||||
if (aligned_offset + size > kUploadBufferSize) {
|
||||
REXLOG_ERROR("Upload buffer overflow in frame {}", current_frame_index_);
|
||||
return false;
|
||||
}
|
||||
|
||||
memcpy(ctx.upload_ptr + aligned_offset, data, size);
|
||||
ctx.upload_offset = aligned_offset + static_cast<uint32_t>(size);
|
||||
|
||||
command_list->CopyBufferRegion(destination, destination_offset, ctx.upload_buffer.Get(), aligned_offset, size);
|
||||
return true;
|
||||
}
|
||||
|
||||
DXGI_FORMAT D3D12ResourceManager::TranslateColorFormat(uint32_t guest_format) {
|
||||
using namespace rex::graphics::xenos;
|
||||
switch (static_cast<ColorRenderTargetFormat>(guest_format)) {
|
||||
case ColorRenderTargetFormat::k_8_8_8_8: return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case ColorRenderTargetFormat::k_8_8_8_8_GAMMA: return DXGI_FORMAT_R8G8B8A8_UNORM_SRGB;
|
||||
case ColorRenderTargetFormat::k_2_10_10_10: return DXGI_FORMAT_R10G10B10A2_UNORM;
|
||||
case ColorRenderTargetFormat::k_16_16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT;
|
||||
case ColorRenderTargetFormat::k_16_16_16_16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
case ColorRenderTargetFormat::k_32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
|
||||
case ColorRenderTargetFormat::k_32_32_FLOAT: return DXGI_FORMAT_R32G32_FLOAT;
|
||||
default: return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
}
|
||||
|
||||
DXGI_FORMAT D3D12ResourceManager::TranslateDepthFormat(uint32_t guest_format) {
|
||||
using namespace rex::graphics::xenos;
|
||||
switch (static_cast<DepthRenderTargetFormat>(guest_format)) {
|
||||
case DepthRenderTargetFormat::kD24S8: return DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
case DepthRenderTargetFormat::kD24FS8: return DXGI_FORMAT_D32_FLOAT_S8X24_UINT; // Nearest
|
||||
default: return DXGI_FORMAT_D24_UNORM_S8_UINT;
|
||||
}
|
||||
}
|
||||
|
||||
DXGI_FORMAT D3D12ResourceManager::TranslateTextureFormat(uint32_t guest_format) {
|
||||
using namespace rex::graphics::xenos;
|
||||
switch (static_cast<TextureFormat>(guest_format)) {
|
||||
case TextureFormat::k_8_8_8_8: return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
case TextureFormat::k_DXT1: return DXGI_FORMAT_BC1_UNORM;
|
||||
case TextureFormat::k_DXT2_3: return DXGI_FORMAT_BC2_UNORM;
|
||||
case TextureFormat::k_DXT4_5: return DXGI_FORMAT_BC3_UNORM;
|
||||
case TextureFormat::k_16_16_16_16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
case TextureFormat::k_32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
|
||||
default: return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
}
|
||||
}
|
||||
|
||||
DXGI_FORMAT D3D12ResourceManager::TranslateVertexFormat(uint32_t guest_format) {
|
||||
using namespace rex::graphics::xenos;
|
||||
switch (static_cast<VertexFormat>(guest_format)) {
|
||||
case VertexFormat::k_32_32_32_32_FLOAT: return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
case VertexFormat::k_32_32_32_FLOAT: return DXGI_FORMAT_R32G32B32_FLOAT;
|
||||
case VertexFormat::k_32_32_FLOAT: return DXGI_FORMAT_R32G32_FLOAT;
|
||||
case VertexFormat::k_32_FLOAT: return DXGI_FORMAT_R32_FLOAT;
|
||||
case VertexFormat::k_16_16_16_16_FLOAT: return DXGI_FORMAT_R16G16B16A16_FLOAT;
|
||||
case VertexFormat::k_16_16_FLOAT: return DXGI_FORMAT_R16G16_FLOAT;
|
||||
case VertexFormat::k_8_8_8_8: return DXGI_FORMAT_R8G8B8A8_UNORM;
|
||||
default: return DXGI_FORMAT_R32G32B32A32_FLOAT;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,84 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <wrl/client.h>
|
||||
#include <d3d12.h>
|
||||
|
||||
#include "../types.h"
|
||||
#include "../../d3d_state.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
class D3D12ResourceManager {
|
||||
public:
|
||||
struct ResourceView {
|
||||
D3D12_CPU_DESCRIPTOR_HANDLE cpu_handle;
|
||||
D3D12_GPU_DESCRIPTOR_HANDLE gpu_handle;
|
||||
uint32_t heap_index;
|
||||
};
|
||||
|
||||
bool Initialize(ID3D12Device* device, uint32_t max_frames);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame(uint32_t frame_index);
|
||||
|
||||
// Translation
|
||||
ID3D12Resource* GetOrCreateBuffer(uint32_t guest_address, uint32_t size, D3D12_RESOURCE_FLAGS flags = D3D12_RESOURCE_FLAG_NONE);
|
||||
ID3D12Resource* GetOrCreateTexture(uint32_t guest_address, const d3d::ShadowState& state);
|
||||
|
||||
// Descriptor management
|
||||
ResourceView AllocateRTV();
|
||||
ResourceView AllocateDSV();
|
||||
ResourceView AllocateSRV();
|
||||
ID3D12DescriptorHeap* GetSrvHeap() const { return srv_heap_.Get(); }
|
||||
|
||||
// Data sync
|
||||
bool UploadData(ID3D12GraphicsCommandList* command_list, ID3D12Resource* destination, const void* data, uint64_t size, uint64_t destination_offset = 0);
|
||||
|
||||
// Format translation
|
||||
DXGI_FORMAT TranslateColorFormat(uint32_t guest_format);
|
||||
DXGI_FORMAT TranslateDepthFormat(uint32_t guest_format);
|
||||
DXGI_FORMAT TranslateTextureFormat(uint32_t guest_format);
|
||||
DXGI_FORMAT TranslateVertexFormat(uint32_t guest_format);
|
||||
|
||||
private:
|
||||
ID3D12Device* device_ = nullptr;
|
||||
uint32_t max_frames_ = 0;
|
||||
uint32_t current_frame_index_ = 0;
|
||||
|
||||
struct CachedResource {
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> resource;
|
||||
uint64_t size_bytes = 0;
|
||||
uint64_t last_used_frame = 0;
|
||||
};
|
||||
|
||||
std::unordered_map<uint32_t, CachedResource> resource_cache_;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> rtv_heap_;
|
||||
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> dsv_heap_;
|
||||
Microsoft::WRL::ComPtr<ID3D12DescriptorHeap> srv_heap_;
|
||||
|
||||
uint32_t rtv_ptr_ = 0;
|
||||
uint32_t dsv_ptr_ = 0;
|
||||
uint32_t srv_ptr_ = 0;
|
||||
|
||||
uint32_t rtv_size_ = 0;
|
||||
uint32_t dsv_size_ = 0;
|
||||
uint32_t srv_size_ = 0;
|
||||
|
||||
struct FrameContext {
|
||||
Microsoft::WRL::ComPtr<ID3D12Resource> upload_buffer;
|
||||
uint8_t* upload_ptr = nullptr;
|
||||
uint32_t upload_offset = 0;
|
||||
};
|
||||
std::vector<FrameContext> frame_contexts_;
|
||||
|
||||
static constexpr uint32_t kMaxRtvDescriptors = 1024;
|
||||
static constexpr uint32_t kMaxDsvDescriptors = 256;
|
||||
static constexpr uint32_t kMaxSrvDescriptors = 4096;
|
||||
static constexpr uint32_t kUploadBufferSize = 16 * 1024 * 1024; // 16 MB per frame
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,52 +0,0 @@
|
||||
#include "d3d12_resource_tracker.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
void D3D12ResourceTracker::TrackResource(ID3D12Resource* resource,
|
||||
D3D12_RESOURCE_STATES initial_state) {
|
||||
if (!resource) return;
|
||||
tracked_[resource] = {initial_state};
|
||||
}
|
||||
|
||||
bool D3D12ResourceTracker::TransitionBarrier(ID3D12GraphicsCommandList* cmd_list,
|
||||
ID3D12Resource* resource,
|
||||
D3D12_RESOURCE_STATES target_state) {
|
||||
if (!resource || !cmd_list) return false;
|
||||
|
||||
auto it = tracked_.find(resource);
|
||||
if (it == tracked_.end()) {
|
||||
// First time seeing this resource — assume COMMON
|
||||
tracked_[resource] = {D3D12_RESOURCE_STATE_COMMON};
|
||||
it = tracked_.find(resource);
|
||||
}
|
||||
|
||||
if (it->second.current_state == target_state) {
|
||||
return false; // No transition needed
|
||||
}
|
||||
|
||||
D3D12_RESOURCE_BARRIER barrier = {};
|
||||
barrier.Type = D3D12_RESOURCE_BARRIER_TYPE_TRANSITION;
|
||||
barrier.Flags = D3D12_RESOURCE_BARRIER_FLAG_NONE;
|
||||
barrier.Transition.pResource = resource;
|
||||
barrier.Transition.Subresource = D3D12_RESOURCE_BARRIER_ALL_SUBRESOURCES;
|
||||
barrier.Transition.StateBefore = it->second.current_state;
|
||||
barrier.Transition.StateAfter = target_state;
|
||||
|
||||
cmd_list->ResourceBarrier(1, &barrier);
|
||||
it->second.current_state = target_state;
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12ResourceTracker::FlushBarriers(ID3D12GraphicsCommandList* cmd_list) {
|
||||
if (pending_barriers_.empty() || !cmd_list) return;
|
||||
cmd_list->ResourceBarrier(static_cast<UINT>(pending_barriers_.size()),
|
||||
pending_barriers_.data());
|
||||
pending_barriers_.clear();
|
||||
}
|
||||
|
||||
void D3D12ResourceTracker::Reset() {
|
||||
tracked_.clear();
|
||||
pending_barriers_.clear();
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,33 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
#include <d3d12.h>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
// Tracks D3D12 resource states for automatic barrier generation.
|
||||
class D3D12ResourceTracker {
|
||||
public:
|
||||
void TrackResource(ID3D12Resource* resource, D3D12_RESOURCE_STATES initial_state);
|
||||
|
||||
// Returns true if a barrier was needed and appended.
|
||||
bool TransitionBarrier(ID3D12GraphicsCommandList* cmd_list,
|
||||
ID3D12Resource* resource,
|
||||
D3D12_RESOURCE_STATES target_state);
|
||||
|
||||
// Flush all pending barriers at once (batch mode).
|
||||
void FlushBarriers(ID3D12GraphicsCommandList* cmd_list);
|
||||
|
||||
void Reset();
|
||||
|
||||
private:
|
||||
struct TrackedResource {
|
||||
D3D12_RESOURCE_STATES current_state = D3D12_RESOURCE_STATE_COMMON;
|
||||
};
|
||||
|
||||
std::unordered_map<ID3D12Resource*, TrackedResource> tracked_;
|
||||
std::vector<D3D12_RESOURCE_BARRIER> pending_barriers_;
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,283 +0,0 @@
|
||||
#ifndef NOMINMAX
|
||||
#define NOMINMAX
|
||||
#endif
|
||||
#include "d3d12_shader_manager.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#include <rex/logging.h>
|
||||
|
||||
#pragma comment(lib, "d3dcompiler.lib")
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Embedded HLSL shaders (compiled inline at runtime, no external files)
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// Generic vertex-pulling shader. It reads stream 0 directly as a raw byte
|
||||
// buffer, byte-swaps guest big-endian dwords, and interprets the first float3
|
||||
// plus an optional fourth component as position data. Pre-transformed menu / UI vertices are projected from the
|
||||
// captured viewport into clip space; already-transformed clip-space vertices
|
||||
// are passed through.
|
||||
static constexpr const char kPassthroughVS[] = R"HLSL(
|
||||
cbuffer DrawConstants : register(b0) {
|
||||
uint vertex_base_offset;
|
||||
uint vertex_stride;
|
||||
uint vertex_buffer_size;
|
||||
uint viewport_x;
|
||||
uint viewport_y;
|
||||
uint viewport_width;
|
||||
uint viewport_height;
|
||||
uint color_offset;
|
||||
uint flags;
|
||||
};
|
||||
|
||||
ByteAddressBuffer g_vertex_buffer : register(t0);
|
||||
|
||||
struct VSOut {
|
||||
float4 pos : SV_Position;
|
||||
float4 color : COLOR0;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
uint ByteSwap32(uint v) {
|
||||
return (v << 24) | ((v << 8) & 0x00FF0000u) | ((v >> 8) & 0x0000FF00u) | (v >> 24);
|
||||
}
|
||||
|
||||
bool CanLoadDword(uint byte_offset) {
|
||||
return (byte_offset & 3u) == 0u && (byte_offset + 4u) <= vertex_buffer_size;
|
||||
}
|
||||
|
||||
float LoadGuestFloat(uint byte_offset, float fallback_value) {
|
||||
if (!CanLoadDword(byte_offset)) {
|
||||
return fallback_value;
|
||||
}
|
||||
return asfloat(ByteSwap32(g_vertex_buffer.Load(byte_offset)));
|
||||
}
|
||||
|
||||
float4 LoadGuestColor(uint byte_offset, float4 fallback_value) {
|
||||
if (!CanLoadDword(byte_offset)) {
|
||||
return fallback_value;
|
||||
}
|
||||
uint packed = ByteSwap32(g_vertex_buffer.Load(byte_offset));
|
||||
return float4(
|
||||
float((packed >> 16) & 0xFFu) / 255.0f,
|
||||
float((packed >> 8) & 0xFFu) / 255.0f,
|
||||
float((packed >> 0) & 0xFFu) / 255.0f,
|
||||
max(float((packed >> 24) & 0xFFu) / 255.0f, 1.0f / 255.0f));
|
||||
}
|
||||
|
||||
VSOut main(uint vid : SV_VertexID) {
|
||||
uint stride = vertex_stride;
|
||||
uint byte_offset = vertex_base_offset + vid * stride;
|
||||
float4 default_color = float4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
// Unsupported stream layouts should draw nothing rather than issue invalid
|
||||
// raw-buffer reads that can remove the device.
|
||||
if (stride == 0u || byte_offset >= vertex_buffer_size) {
|
||||
VSOut empty;
|
||||
empty.pos = float4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
empty.color = default_color;
|
||||
empty.uv = float2(0.0f, 0.0f);
|
||||
return empty;
|
||||
}
|
||||
|
||||
float4 raw_pos = float4(
|
||||
LoadGuestFloat(byte_offset + 0, 0.0f),
|
||||
LoadGuestFloat(byte_offset + 4, 0.0f),
|
||||
LoadGuestFloat(byte_offset + 8, 0.0f),
|
||||
1.0f);
|
||||
if (stride >= 16u && CanLoadDword(byte_offset + 12u)) {
|
||||
float candidate_w = LoadGuestFloat(byte_offset + 12, 1.0f);
|
||||
if (candidate_w == candidate_w && abs(candidate_w) < 10000.0f) {
|
||||
raw_pos.w = candidate_w;
|
||||
}
|
||||
}
|
||||
bool has_viewport = viewport_width != 0 && viewport_height != 0;
|
||||
bool looks_screen_space =
|
||||
has_viewport &&
|
||||
(abs(raw_pos.x) > 2.5f || abs(raw_pos.y) > 2.5f || raw_pos.w > 2.0f || raw_pos.w < 0.0f);
|
||||
|
||||
VSOut o;
|
||||
if (looks_screen_space) {
|
||||
float2 viewport_size = float2(max(viewport_width, 1u), max(viewport_height, 1u));
|
||||
float2 viewport_origin = float2(viewport_x, viewport_y);
|
||||
float2 pixel = raw_pos.xy - viewport_origin;
|
||||
float2 ndc = float2(
|
||||
pixel.x / viewport_size.x * 2.0f - 1.0f,
|
||||
1.0f - pixel.y / viewport_size.y * 2.0f);
|
||||
o.pos = float4(ndc, saturate(raw_pos.z), 1.0f);
|
||||
} else {
|
||||
float w = abs(raw_pos.w) > 1.0e-6f ? raw_pos.w : 1.0f;
|
||||
o.pos = float4(raw_pos.xyz, w);
|
||||
}
|
||||
|
||||
if (color_offset != 0xFFFFFFFFu && (color_offset + 4u) <= stride) {
|
||||
o.color = LoadGuestColor(byte_offset + color_offset, default_color);
|
||||
} else {
|
||||
o.color = default_color;
|
||||
}
|
||||
o.uv = raw_pos.xy;
|
||||
return o;
|
||||
}
|
||||
)HLSL";
|
||||
|
||||
// Simple pixel shader: output the pulled vertex color.
|
||||
static constexpr const char kPassthroughPS[] = R"HLSL(
|
||||
struct PSIn {
|
||||
float4 pos : SV_Position;
|
||||
float4 color : COLOR0;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
float4 main(PSIn i) : SV_Target {
|
||||
return i.color;
|
||||
}
|
||||
)HLSL";
|
||||
|
||||
// Diagnostic variant for post-process / present-tagged passes. This makes
|
||||
// successful fullscreen-style replay obvious even before real texture sampling
|
||||
// is implemented.
|
||||
static constexpr const char kSoftParticlePS[] = R"HLSL(
|
||||
struct PSIn {
|
||||
float4 pos : SV_Position;
|
||||
float4 color : COLOR0;
|
||||
float2 uv : TEXCOORD0;
|
||||
};
|
||||
|
||||
float4 main(PSIn i) : SV_Target {
|
||||
float2 pos_band = frac(abs(i.pos.xy) * 0.015625f);
|
||||
float2 uv_band = frac(abs(i.uv.xy) * 0.001953125f);
|
||||
float3 debug_color = float3(
|
||||
max(pos_band.x, 0.2f),
|
||||
max(pos_band.y, 0.2f),
|
||||
max(frac(uv_band.x + uv_band.y), 0.35f));
|
||||
return float4(debug_color, 1.0f);
|
||||
}
|
||||
)HLSL";
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
/*static*/ Microsoft::WRL::ComPtr<ID3DBlob> D3D12ShaderManager::CompileHLSL(
|
||||
const char* source, const char* entry_point, const char* target) {
|
||||
UINT flags = 0;
|
||||
#if defined(_DEBUG)
|
||||
flags = D3DCOMPILE_DEBUG | D3DCOMPILE_SKIP_OPTIMIZATION;
|
||||
#else
|
||||
flags = D3DCOMPILE_OPTIMIZATION_LEVEL3;
|
||||
#endif
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> blob;
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> error;
|
||||
HRESULT hr = D3DCompile(source, strlen(source), nullptr, nullptr, nullptr,
|
||||
entry_point, target, flags, 0, &blob, &error);
|
||||
if (FAILED(hr)) {
|
||||
if (error) {
|
||||
REXLOG_ERROR("Shader compile error [{}]: {}", entry_point,
|
||||
static_cast<const char*>(error->GetBufferPointer()));
|
||||
} else {
|
||||
REXLOG_ERROR("Shader compile error [{}]: hr=0x{:08X}", entry_point,
|
||||
static_cast<uint32_t>(hr));
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
return blob;
|
||||
}
|
||||
|
||||
bool D3D12ShaderManager::Initialize(ID3D12Device* device) {
|
||||
device_ = device;
|
||||
|
||||
REXLOG_INFO("D3D12ShaderManager: Compiling passthrough VS...");
|
||||
passthrough_vs_ = CompileHLSL(kPassthroughVS, "main", "vs_5_0");
|
||||
if (!passthrough_vs_) {
|
||||
REXLOG_ERROR("D3D12ShaderManager: Failed to compile passthrough VS");
|
||||
return false;
|
||||
}
|
||||
|
||||
REXLOG_INFO("D3D12ShaderManager: Compiling passthrough PS...");
|
||||
passthrough_ps_ = CompileHLSL(kPassthroughPS, "main", "ps_5_0");
|
||||
if (!passthrough_ps_) {
|
||||
REXLOG_ERROR("D3D12ShaderManager: Failed to compile passthrough PS");
|
||||
return false;
|
||||
}
|
||||
|
||||
REXLOG_INFO("D3D12ShaderManager: Compiling soft-particle PS...");
|
||||
soft_particle_ps_ = CompileHLSL(kSoftParticlePS, "main", "ps_5_0");
|
||||
if (!soft_particle_ps_) {
|
||||
REXLOG_WARN("D3D12ShaderManager: Soft-particle PS compile failed, using passthrough");
|
||||
soft_particle_ps_ = passthrough_ps_;
|
||||
}
|
||||
|
||||
REXLOG_INFO("D3D12ShaderManager: All shaders compiled successfully");
|
||||
return true;
|
||||
}
|
||||
|
||||
void D3D12ShaderManager::Shutdown() {
|
||||
pso_cache_.clear();
|
||||
passthrough_vs_.Reset();
|
||||
passthrough_ps_.Reset();
|
||||
soft_particle_ps_.Reset();
|
||||
device_ = nullptr;
|
||||
}
|
||||
|
||||
ID3D12PipelineState* D3D12ShaderManager::GetOrCreatePSO(
|
||||
uint64_t state_hash,
|
||||
ID3D12RootSignature* root_sig,
|
||||
DXGI_FORMAT rt_format,
|
||||
DXGI_FORMAT ds_format,
|
||||
D3D12_PRIMITIVE_TOPOLOGY_TYPE topology_type,
|
||||
bool use_soft_particle_ps) {
|
||||
auto it = pso_cache_.find(state_hash);
|
||||
if (it != pso_cache_.end()) {
|
||||
return it->second.pso.Get();
|
||||
}
|
||||
|
||||
if (!device_ || !passthrough_vs_ || !passthrough_ps_) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
ID3DBlob* ps = use_soft_particle_ps ? soft_particle_ps_.Get() : passthrough_ps_.Get();
|
||||
|
||||
D3D12_GRAPHICS_PIPELINE_STATE_DESC desc = {};
|
||||
desc.pRootSignature = root_sig;
|
||||
desc.VS = {passthrough_vs_->GetBufferPointer(), passthrough_vs_->GetBufferSize()};
|
||||
desc.PS = {ps->GetBufferPointer(), ps->GetBufferSize()};
|
||||
desc.RasterizerState.FillMode = D3D12_FILL_MODE_SOLID;
|
||||
desc.RasterizerState.CullMode = D3D12_CULL_MODE_NONE;
|
||||
desc.RasterizerState.DepthClipEnable = TRUE;
|
||||
desc.BlendState.RenderTarget[0].RenderTargetWriteMask = D3D12_COLOR_WRITE_ENABLE_ALL;
|
||||
desc.BlendState.RenderTarget[0].BlendEnable = use_soft_particle_ps ? TRUE : FALSE;
|
||||
desc.BlendState.RenderTarget[0].SrcBlend = D3D12_BLEND_SRC_ALPHA;
|
||||
desc.BlendState.RenderTarget[0].DestBlend = D3D12_BLEND_INV_SRC_ALPHA;
|
||||
desc.BlendState.RenderTarget[0].BlendOp = D3D12_BLEND_OP_ADD;
|
||||
desc.BlendState.RenderTarget[0].SrcBlendAlpha = D3D12_BLEND_ONE;
|
||||
desc.BlendState.RenderTarget[0].DestBlendAlpha = D3D12_BLEND_ZERO;
|
||||
desc.BlendState.RenderTarget[0].BlendOpAlpha = D3D12_BLEND_OP_ADD;
|
||||
desc.DepthStencilState.DepthEnable = (ds_format != DXGI_FORMAT_UNKNOWN) ? TRUE : FALSE;
|
||||
desc.DepthStencilState.DepthWriteMask = D3D12_DEPTH_WRITE_MASK_ALL;
|
||||
desc.DepthStencilState.DepthFunc = D3D12_COMPARISON_FUNC_LESS_EQUAL;
|
||||
desc.SampleMask = UINT_MAX;
|
||||
desc.PrimitiveTopologyType = topology_type;
|
||||
desc.NumRenderTargets = (rt_format != DXGI_FORMAT_UNKNOWN) ? 1 : 0;
|
||||
if (rt_format != DXGI_FORMAT_UNKNOWN) {
|
||||
desc.RTVFormats[0] = rt_format;
|
||||
}
|
||||
desc.DSVFormat = ds_format;
|
||||
desc.SampleDesc.Count = 1;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> pso;
|
||||
HRESULT hr = device_->CreateGraphicsPipelineState(&desc, IID_PPV_ARGS(&pso));
|
||||
if (FAILED(hr)) {
|
||||
REXLOG_ERROR(
|
||||
"D3D12ShaderManager: CreateGraphicsPipelineState failed 0x{:08X} hash=0x{:016X}",
|
||||
static_cast<uint32_t>(hr), state_hash);
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
auto& entry = pso_cache_[state_hash];
|
||||
entry.pso = pso;
|
||||
return entry.pso.Get();
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <wrl/client.h>
|
||||
#include <d3d12.h>
|
||||
#include <d3dcompiler.h>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
// Manages native passthrough shaders and a PSO cache for the native renderer.
|
||||
// Uses D3DCompile to compile embedded HLSL source inline at startup — no
|
||||
// external shader files required.
|
||||
class D3D12ShaderManager {
|
||||
public:
|
||||
bool Initialize(ID3D12Device* device);
|
||||
void Shutdown();
|
||||
|
||||
// Returns the shared passthrough vertex shader blob (compiled once).
|
||||
ID3DBlob* GetPassthroughVS() const { return passthrough_vs_.Get(); }
|
||||
|
||||
// Returns the shared solid-color pixel shader blob (compiled once).
|
||||
ID3DBlob* GetPassthroughPS() const { return passthrough_ps_.Get(); }
|
||||
|
||||
// Returns the soft-particle / high-precision effect pixel shader blob.
|
||||
ID3DBlob* GetSoftParticlePS() const { return soft_particle_ps_.Get(); }
|
||||
|
||||
// Get or create a PSO for the given state hash.
|
||||
// root_sig must already be created on the device.
|
||||
// Returns nullptr on failure.
|
||||
ID3D12PipelineState* GetOrCreatePSO(
|
||||
uint64_t state_hash,
|
||||
ID3D12RootSignature* root_sig,
|
||||
DXGI_FORMAT rt_format,
|
||||
DXGI_FORMAT ds_format,
|
||||
D3D12_PRIMITIVE_TOPOLOGY_TYPE topology_type,
|
||||
bool use_soft_particle_ps = false);
|
||||
|
||||
// Backwards compat stubs (unused but keep old callers linking)
|
||||
ID3DBlob* GetVertexShader(uint32_t /*guest_hash*/) { return passthrough_vs_.Get(); }
|
||||
ID3DBlob* GetPixelShader(uint32_t /*guest_hash*/) { return passthrough_ps_.Get(); }
|
||||
ID3DBlob* GetGenericSoftParticleShader() { return soft_particle_ps_.Get(); }
|
||||
|
||||
private:
|
||||
ID3D12Device* device_ = nullptr;
|
||||
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> passthrough_vs_;
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> passthrough_ps_;
|
||||
Microsoft::WRL::ComPtr<ID3DBlob> soft_particle_ps_;
|
||||
|
||||
struct CachedPSO {
|
||||
Microsoft::WRL::ComPtr<ID3D12PipelineState> pso;
|
||||
};
|
||||
std::unordered_map<uint64_t, CachedPSO> pso_cache_;
|
||||
|
||||
static Microsoft::WRL::ComPtr<ID3DBlob> CompileHLSL(
|
||||
const char* source, const char* entry_point, const char* target);
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,59 +0,0 @@
|
||||
#include "ac6_native_renderer/backends/metal_backend.h"
|
||||
|
||||
#include <rex/logging.h>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
bool MetalBackend::IsSupported() const {
|
||||
#if defined(__APPLE__)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool MetalBackend::Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) {
|
||||
(void)config;
|
||||
(void)memory;
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "ac6_native_renderer/render_device.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
class MetalBackend final : public RenderDeviceBackend {
|
||||
public:
|
||||
BackendType GetType() const override { return BackendType::kMetal; }
|
||||
std::string_view GetName() const override { return "metal"; }
|
||||
bool IsSupported() const override;
|
||||
bool Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) override;
|
||||
bool InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue) override { return false; }
|
||||
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) override;
|
||||
BackendExecutorStatus GetExecutorStatus() const override { return executor_status_; }
|
||||
void Shutdown() override;
|
||||
|
||||
private:
|
||||
BackendExecutorStatus executor_status_{};
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,59 +0,0 @@
|
||||
#include "ac6_native_renderer/backends/vulkan_backend.h"
|
||||
|
||||
#include <rex/logging.h>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
bool VulkanBackend::IsSupported() const {
|
||||
#if defined(__linux__)
|
||||
return true;
|
||||
#else
|
||||
return false;
|
||||
#endif
|
||||
}
|
||||
|
||||
bool VulkanBackend::Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) {
|
||||
(void)config;
|
||||
(void)memory;
|
||||
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;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,23 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "../render_device.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
class VulkanBackend final : public RenderDeviceBackend {
|
||||
public:
|
||||
BackendType GetType() const override { return BackendType::kVulkan; }
|
||||
std::string_view GetName() const override { return "vulkan"; }
|
||||
bool IsSupported() const override;
|
||||
bool Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) override;
|
||||
bool InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue) override { return false; }
|
||||
bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) override;
|
||||
BackendExecutorStatus GetExecutorStatus() const override { return executor_status_; }
|
||||
void Shutdown() override;
|
||||
|
||||
private:
|
||||
BackendExecutorStatus executor_status_{};
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,193 +0,0 @@
|
||||
#include "execution_plan.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <utility>
|
||||
|
||||
namespace ac6::renderer {
|
||||
namespace {
|
||||
|
||||
ExecutionCommandCategory ToExecutionCommandCategory(ObservedCommandType type) {
|
||||
switch (type) {
|
||||
case ObservedCommandType::kDraw:
|
||||
return ExecutionCommandCategory::kDraw;
|
||||
case ObservedCommandType::kClear:
|
||||
return ExecutionCommandCategory::kClear;
|
||||
case ObservedCommandType::kResolve:
|
||||
return ExecutionCommandCategory::kResolve;
|
||||
default:
|
||||
return ExecutionCommandCategory::kNone;
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionCommandPacket BuildExecutionCommandPacket(const ReplayCommandDesc& command,
|
||||
uint32_t replay_pass_index,
|
||||
uint32_t replay_command_index) {
|
||||
return ExecutionCommandPacket{
|
||||
.category = ToExecutionCommandCategory(command.type),
|
||||
.source_type = command.type,
|
||||
.replay_pass_index = replay_pass_index,
|
||||
.replay_command_index = replay_command_index,
|
||||
.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,
|
||||
.shadow_state = command.shadow_state,
|
||||
};
|
||||
}
|
||||
|
||||
void AccumulateResourceRequirements(ExecutionResourceRequirements& resources,
|
||||
const ExecutionCommandPacket& command) {
|
||||
resources.needs_render_target |= command.render_target_0 != 0;
|
||||
resources.needs_depth_stencil |= command.depth_stencil != 0;
|
||||
resources.max_texture_count =
|
||||
std::max(resources.max_texture_count, command.texture_count);
|
||||
resources.max_stream_count =
|
||||
std::max(resources.max_stream_count, command.stream_count);
|
||||
resources.max_sampler_count =
|
||||
std::max(resources.max_sampler_count, command.sampler_count);
|
||||
resources.max_fetch_constant_count =
|
||||
std::max(resources.max_fetch_constant_count, command.fetch_constant_count);
|
||||
resources.max_viewport_width =
|
||||
std::max(resources.max_viewport_width, command.viewport_width);
|
||||
resources.max_viewport_height =
|
||||
std::max(resources.max_viewport_height, command.viewport_height);
|
||||
|
||||
if (command.category != ExecutionCommandCategory::kDraw) {
|
||||
return;
|
||||
}
|
||||
|
||||
resources.needs_vertex_streams |= command.stream_count != 0;
|
||||
resources.needs_index_buffer |= command.draw_kind != ac6::d3d::DrawCallKind::kPrimitive;
|
||||
resources.needs_textures |= command.texture_count != 0;
|
||||
resources.needs_samplers |= command.sampler_count != 0;
|
||||
resources.needs_fetch_constants |= command.fetch_constant_count != 0;
|
||||
}
|
||||
|
||||
ExecutionPassPacket BuildExecutionPassPacket(const ReplayPassDesc& replay_pass,
|
||||
uint32_t replay_pass_index,
|
||||
const NativeFramePlan& frame_plan) {
|
||||
ExecutionPassPacket pass_packet;
|
||||
pass_packet.name = replay_pass.name;
|
||||
pass_packet.role = replay_pass.role;
|
||||
pass_packet.replay_pass_valid = true;
|
||||
pass_packet.replay_pass_index = replay_pass_index;
|
||||
pass_packet.source_pass_valid = replay_pass.source_pass_valid;
|
||||
pass_packet.source_pass_index = replay_pass.source_pass_index;
|
||||
pass_packet.draw_count = replay_pass.draw_count;
|
||||
pass_packet.clear_count = replay_pass.clear_count;
|
||||
pass_packet.resolve_count = replay_pass.resolve_count;
|
||||
pass_packet.render_target_0 = replay_pass.render_target_0;
|
||||
pass_packet.depth_stencil = replay_pass.depth_stencil;
|
||||
pass_packet.output_width = replay_pass.viewport_width;
|
||||
pass_packet.output_height = replay_pass.viewport_height;
|
||||
pass_packet.selected_for_present = replay_pass.selected_for_present;
|
||||
pass_packet.commands.reserve(replay_pass.commands.size());
|
||||
|
||||
if (pass_packet.role == ReplayPassRole::kPresent) {
|
||||
pass_packet.output_width = frame_plan.output_width;
|
||||
pass_packet.output_height = frame_plan.output_height;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < replay_pass.commands.size(); ++i) {
|
||||
ExecutionCommandPacket command_packet =
|
||||
BuildExecutionCommandPacket(replay_pass.commands[i], replay_pass_index, i);
|
||||
AccumulateResourceRequirements(pass_packet.resources, command_packet);
|
||||
pass_packet.commands.push_back(std::move(command_packet));
|
||||
}
|
||||
|
||||
pass_packet.resources.needs_render_target |= pass_packet.render_target_0 != 0;
|
||||
pass_packet.resources.needs_depth_stencil |= pass_packet.depth_stencil != 0;
|
||||
pass_packet.resources.max_viewport_width =
|
||||
std::max(pass_packet.resources.max_viewport_width, pass_packet.output_width);
|
||||
pass_packet.resources.max_viewport_height =
|
||||
std::max(pass_packet.resources.max_viewport_height, pass_packet.output_height);
|
||||
return pass_packet;
|
||||
}
|
||||
|
||||
void AccumulateSummary(ExecutionFrameSummary& summary,
|
||||
const ExecutionPassPacket& pass_packet) {
|
||||
++summary.pass_count;
|
||||
summary.command_count += static_cast<uint32_t>(pass_packet.commands.size());
|
||||
summary.draw_packet_count += pass_packet.draw_count;
|
||||
summary.clear_packet_count += pass_packet.clear_count;
|
||||
summary.resolve_packet_count += pass_packet.resolve_count;
|
||||
if (pass_packet.role == ReplayPassRole::kPresent) {
|
||||
++summary.present_pass_count;
|
||||
summary.has_present_pass = true;
|
||||
}
|
||||
summary.valid = summary.pass_count != 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* ToString(ExecutionCommandCategory category) {
|
||||
switch (category) {
|
||||
case ExecutionCommandCategory::kDraw:
|
||||
return "draw";
|
||||
case ExecutionCommandCategory::kClear:
|
||||
return "clear";
|
||||
case ExecutionCommandCategory::kResolve:
|
||||
return "resolve";
|
||||
case ExecutionCommandCategory::kNone:
|
||||
default:
|
||||
return "none";
|
||||
}
|
||||
}
|
||||
|
||||
ExecutionFramePlan ExecutionPlanBuilder::BuildBootstrapPlan(uint64_t frame_index) const {
|
||||
ExecutionFramePlan plan;
|
||||
plan.summary.frame_index = frame_index;
|
||||
|
||||
ExecutionPassPacket bootstrap_pass;
|
||||
bootstrap_pass.name = "ac6.execution.bootstrap";
|
||||
bootstrap_pass.role = ReplayPassRole::kBootstrap;
|
||||
|
||||
AccumulateSummary(plan.summary, bootstrap_pass);
|
||||
plan.passes.push_back(std::move(bootstrap_pass));
|
||||
return plan;
|
||||
}
|
||||
|
||||
ExecutionFramePlan ExecutionPlanBuilder::Build(
|
||||
const ReplayFrame& replay_frame, const NativeFramePlan& frame_plan) const {
|
||||
ExecutionFramePlan plan;
|
||||
plan.summary.frame_index = replay_frame.summary.frame_index;
|
||||
plan.summary.output_width = replay_frame.summary.output_width;
|
||||
plan.summary.output_height = replay_frame.summary.output_height;
|
||||
|
||||
if (!replay_frame.summary.valid || replay_frame.passes.empty()) {
|
||||
return plan;
|
||||
}
|
||||
|
||||
plan.passes.reserve(replay_frame.passes.size());
|
||||
for (uint32_t i = 0; i < replay_frame.passes.size(); ++i) {
|
||||
ExecutionPassPacket pass_packet =
|
||||
BuildExecutionPassPacket(replay_frame.passes[i], i, frame_plan);
|
||||
AccumulateSummary(plan.summary, pass_packet);
|
||||
plan.passes.push_back(std::move(pass_packet));
|
||||
}
|
||||
|
||||
plan.summary.valid =
|
||||
plan.summary.pass_count != 0 &&
|
||||
(!frame_plan.valid ||
|
||||
(plan.summary.output_width != 0 && plan.summary.output_height != 0));
|
||||
return plan;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,111 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "frame_plan.h"
|
||||
#include "replay_ir.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
enum class ExecutionCommandCategory : uint8_t {
|
||||
kNone = 0,
|
||||
kDraw = 1,
|
||||
kClear = 2,
|
||||
kResolve = 3,
|
||||
};
|
||||
|
||||
struct ExecutionCommandPacket {
|
||||
ExecutionCommandCategory category = ExecutionCommandCategory::kNone;
|
||||
ObservedCommandType source_type = ObservedCommandType::kDraw;
|
||||
uint32_t replay_pass_index = 0;
|
||||
uint32_t replay_command_index = 0;
|
||||
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;
|
||||
ac6::d3d::ShadowState shadow_state{};
|
||||
};
|
||||
|
||||
struct ExecutionResourceRequirements {
|
||||
bool needs_render_target = false;
|
||||
bool needs_depth_stencil = false;
|
||||
bool needs_vertex_streams = false;
|
||||
bool needs_index_buffer = false;
|
||||
bool needs_textures = false;
|
||||
bool needs_samplers = false;
|
||||
bool needs_fetch_constants = false;
|
||||
uint32_t max_texture_count = 0;
|
||||
uint32_t max_stream_count = 0;
|
||||
uint32_t max_sampler_count = 0;
|
||||
uint32_t max_fetch_constant_count = 0;
|
||||
uint32_t max_viewport_width = 0;
|
||||
uint32_t max_viewport_height = 0;
|
||||
};
|
||||
|
||||
struct ExecutionPassPacket {
|
||||
std::string name;
|
||||
ReplayPassRole role = ReplayPassRole::kUnknown;
|
||||
bool replay_pass_valid = false;
|
||||
uint32_t replay_pass_index = 0;
|
||||
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 output_width = 0;
|
||||
uint32_t output_height = 0;
|
||||
bool selected_for_present = false;
|
||||
ExecutionResourceRequirements resources{};
|
||||
std::vector<ExecutionCommandPacket> commands;
|
||||
};
|
||||
|
||||
struct ExecutionFrameSummary {
|
||||
bool valid = false;
|
||||
uint64_t frame_index = 0;
|
||||
uint32_t pass_count = 0;
|
||||
uint32_t command_count = 0;
|
||||
uint32_t draw_packet_count = 0;
|
||||
uint32_t clear_packet_count = 0;
|
||||
uint32_t resolve_packet_count = 0;
|
||||
uint32_t present_pass_count = 0;
|
||||
uint32_t output_width = 0;
|
||||
uint32_t output_height = 0;
|
||||
bool has_present_pass = false;
|
||||
};
|
||||
|
||||
struct ExecutionFramePlan {
|
||||
ExecutionFrameSummary summary{};
|
||||
std::vector<ExecutionPassPacket> passes;
|
||||
};
|
||||
|
||||
class ExecutionPlanBuilder {
|
||||
public:
|
||||
ExecutionFramePlan BuildBootstrapPlan(uint64_t frame_index) const;
|
||||
ExecutionFramePlan Build(const ReplayFrame& replay_frame,
|
||||
const NativeFramePlan& frame_plan) const;
|
||||
};
|
||||
|
||||
const char* ToString(ExecutionCommandCategory category);
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,142 +0,0 @@
|
||||
#include "frame_plan.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
namespace {
|
||||
|
||||
PlannedPassReference MakeReference(const ObservedPassDesc& pass, uint32_t pass_index) {
|
||||
return PlannedPassReference{
|
||||
.valid = true,
|
||||
.pass_index = pass_index,
|
||||
.kind = pass.kind,
|
||||
.render_target_0 = pass.render_target_0,
|
||||
.depth_stencil = pass.depth_stencil,
|
||||
.viewport_x = pass.viewport_x,
|
||||
.viewport_y = pass.viewport_y,
|
||||
.viewport_width = pass.viewport_width,
|
||||
.viewport_height = pass.viewport_height,
|
||||
.draw_count = pass.draw_count,
|
||||
.clear_count = pass.clear_count,
|
||||
.resolve_count = pass.resolve_count,
|
||||
.indexed_draw_count = pass.indexed_draw_count,
|
||||
.indexed_shared_draw_count = pass.indexed_shared_draw_count,
|
||||
.primitive_draw_count = pass.primitive_draw_count,
|
||||
.max_texture_count = pass.max_texture_count,
|
||||
.max_stream_count = pass.max_stream_count,
|
||||
.max_sampler_count = pass.max_sampler_count,
|
||||
.max_fetch_constant_count = pass.max_fetch_constant_count,
|
||||
};
|
||||
}
|
||||
|
||||
uint32_t ScoreScenePass(const ObservedPassDesc& pass) {
|
||||
uint32_t score = 0;
|
||||
score += pass.draw_count * 8;
|
||||
score += pass.indexed_draw_count * 6;
|
||||
score += pass.indexed_shared_draw_count * 7;
|
||||
score += pass.primitive_draw_count * 2;
|
||||
score += pass.clear_count * 3;
|
||||
score += pass.max_texture_count * 6;
|
||||
score += pass.max_stream_count * 8;
|
||||
score += pass.max_sampler_count * 4;
|
||||
score += pass.max_fetch_constant_count * 4;
|
||||
score += pass.matches_frame_end_viewport ? 12u : 0u;
|
||||
if (pass.resolve_count == 0) {
|
||||
score += 16;
|
||||
}
|
||||
return score;
|
||||
}
|
||||
|
||||
uint32_t ScorePostProcessPass(const ObservedPassDesc& pass, uint32_t pass_index,
|
||||
uint32_t selected_index) {
|
||||
uint32_t score = 0;
|
||||
score += pass.resolve_count * 48;
|
||||
score += pass.clear_count * 4;
|
||||
score += std::min<uint32_t>(pass.draw_count, 24u) * 2;
|
||||
score += pass.max_texture_count * 10;
|
||||
score += pass.max_sampler_count * 5;
|
||||
score += pass.matches_frame_end_viewport ? 18u : 0u;
|
||||
score += pass.max_stream_count <= 2 ? 10u : 0u;
|
||||
score += pass_index <= selected_index ? 6u : 0u;
|
||||
return score;
|
||||
}
|
||||
|
||||
uint32_t ScoreUiPass(const ObservedPassDesc& pass, uint32_t pass_index, uint32_t selected_index) {
|
||||
uint32_t score = 10;
|
||||
if (pass_index >= selected_index) {
|
||||
score += 15;
|
||||
}
|
||||
score += (pass.draw_count <= 16 ? 10u : 0u);
|
||||
score += (pass.matches_frame_end_viewport ? 5u : 0u);
|
||||
score += (pass.clear_count == 0 ? 8u : 0u);
|
||||
score += (pass.max_stream_count <= 2 ? 12u : 0u);
|
||||
score += (pass.max_texture_count <= 8 ? 8u : 0u);
|
||||
score += (pass.primitive_draw_count == 0 ? 6u : 0u);
|
||||
return score;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeFramePlan FramePlanner::Build(const FrontendFrameSummary& summary,
|
||||
const std::vector<ObservedPassDesc>& passes) {
|
||||
NativeFramePlan plan;
|
||||
plan.frame_index = summary.frame_index;
|
||||
plan.observed_pass_count = summary.pass_count;
|
||||
if (!summary.capture_valid || passes.empty()) {
|
||||
return plan;
|
||||
}
|
||||
|
||||
const uint32_t selected_index =
|
||||
summary.selected_pass_index < passes.size() ? summary.selected_pass_index
|
||||
: uint32_t(passes.size() - 1);
|
||||
|
||||
uint32_t best_scene_score = 0;
|
||||
bool have_scene = false;
|
||||
uint32_t best_ui_score = 0;
|
||||
bool have_ui = false;
|
||||
|
||||
for (uint32_t i = 0; i < passes.size(); ++i) {
|
||||
const ObservedPassDesc& pass = passes[i];
|
||||
if (pass.kind == ObservedPassKind::kScene) {
|
||||
const uint32_t score = ScoreScenePass(pass);
|
||||
if (!have_scene || score > best_scene_score) {
|
||||
plan.scene_stage = MakeReference(pass, i);
|
||||
plan.scene_stage_score = score;
|
||||
best_scene_score = score;
|
||||
have_scene = true;
|
||||
}
|
||||
}
|
||||
if (pass.kind == ObservedPassKind::kPostProcess) {
|
||||
const uint32_t score = ScorePostProcessPass(pass, i, selected_index);
|
||||
if (!plan.post_process_stage.valid || score >= plan.post_process_stage_score) {
|
||||
plan.post_process_stage = MakeReference(pass, i);
|
||||
plan.post_process_stage_score = score;
|
||||
}
|
||||
}
|
||||
if (pass.kind == ObservedPassKind::kUiComposite) {
|
||||
const uint32_t score = ScoreUiPass(pass, i, selected_index);
|
||||
if (!have_ui || score >= best_ui_score) {
|
||||
plan.ui_stage = MakeReference(pass, i);
|
||||
plan.ui_stage_score = score;
|
||||
best_ui_score = score;
|
||||
have_ui = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
plan.present_stage = MakeReference(passes[selected_index], selected_index);
|
||||
plan.present_stage_score =
|
||||
ScorePostProcessPass(passes[selected_index], selected_index, selected_index) +
|
||||
(passes[selected_index].selected_for_present ? 12u : 0u);
|
||||
plan.present_from_selected_pass = true;
|
||||
plan.has_scene_stage = plan.scene_stage.valid;
|
||||
plan.has_post_process_stage = plan.post_process_stage.valid;
|
||||
plan.has_ui_stage = plan.ui_stage.valid;
|
||||
plan.output_width = plan.present_stage.viewport_width;
|
||||
plan.output_height = plan.present_stage.viewport_height;
|
||||
plan.requires_present_pass = plan.present_stage.valid &&
|
||||
(plan.has_post_process_stage || plan.has_ui_stage ||
|
||||
plan.has_scene_stage);
|
||||
plan.valid = plan.present_stage.valid && plan.output_width != 0 && plan.output_height != 0;
|
||||
return plan;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,59 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <vector>
|
||||
|
||||
#include "ac6_render_frontend.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
struct PlannedPassReference {
|
||||
bool valid = false;
|
||||
uint32_t pass_index = 0;
|
||||
ObservedPassKind kind = ObservedPassKind::kUnknown;
|
||||
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;
|
||||
uint32_t draw_count = 0;
|
||||
uint32_t clear_count = 0;
|
||||
uint32_t resolve_count = 0;
|
||||
uint32_t indexed_draw_count = 0;
|
||||
uint32_t indexed_shared_draw_count = 0;
|
||||
uint32_t primitive_draw_count = 0;
|
||||
uint32_t max_texture_count = 0;
|
||||
uint32_t max_stream_count = 0;
|
||||
uint32_t max_sampler_count = 0;
|
||||
uint32_t max_fetch_constant_count = 0;
|
||||
};
|
||||
|
||||
struct NativeFramePlan {
|
||||
bool valid = false;
|
||||
uint64_t frame_index = 0;
|
||||
uint32_t observed_pass_count = 0;
|
||||
uint32_t output_width = 0;
|
||||
uint32_t output_height = 0;
|
||||
bool has_scene_stage = false;
|
||||
bool has_post_process_stage = false;
|
||||
bool has_ui_stage = false;
|
||||
bool requires_present_pass = false;
|
||||
bool present_from_selected_pass = false;
|
||||
uint32_t scene_stage_score = 0;
|
||||
uint32_t post_process_stage_score = 0;
|
||||
uint32_t ui_stage_score = 0;
|
||||
uint32_t present_stage_score = 0;
|
||||
PlannedPassReference scene_stage{};
|
||||
PlannedPassReference post_process_stage{};
|
||||
PlannedPassReference ui_stage{};
|
||||
PlannedPassReference present_stage{};
|
||||
};
|
||||
|
||||
class FramePlanner {
|
||||
public:
|
||||
NativeFramePlan Build(const FrontendFrameSummary& summary,
|
||||
const std::vector<ObservedPassDesc>& passes);
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,15 +0,0 @@
|
||||
#include "frame_scheduler.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
void FrameScheduler::Configure(uint32_t max_frames_in_flight) {
|
||||
max_frames_in_flight_ = max_frames_in_flight == 0 ? 1 : max_frames_in_flight;
|
||||
frame_slot_ = 0;
|
||||
}
|
||||
|
||||
void FrameScheduler::BeginFrame() {
|
||||
++frame_index_;
|
||||
frame_slot_ = static_cast<uint32_t>(frame_index_ % max_frames_in_flight_);
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,22 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
class FrameScheduler {
|
||||
public:
|
||||
void Configure(uint32_t max_frames_in_flight);
|
||||
void BeginFrame();
|
||||
|
||||
uint64_t frame_index() const { return frame_index_; }
|
||||
uint32_t frame_slot() const { return frame_slot_; }
|
||||
uint32_t max_frames_in_flight() const { return max_frames_in_flight_; }
|
||||
|
||||
private:
|
||||
uint64_t frame_index_ = 0;
|
||||
uint32_t frame_slot_ = 0;
|
||||
uint32_t max_frames_in_flight_ = 2;
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,189 +0,0 @@
|
||||
#include "native_renderer.h"
|
||||
|
||||
#include "backends/d3d12_backend.h"
|
||||
#include <string>
|
||||
|
||||
#include <rex/logging.h>
|
||||
|
||||
namespace ac6::renderer {
|
||||
namespace {
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
RenderPassDesc BuildRenderPassDesc(const ReplayExecutorPassPacket& pass) {
|
||||
return 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.output_width,
|
||||
.viewport_height = pass.output_height,
|
||||
.selected_for_present = pass.selected_for_present,
|
||||
};
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
NativeRenderer::NativeRenderer() = default;
|
||||
|
||||
NativeRenderer::~NativeRenderer() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool NativeRenderer::InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue) {
|
||||
Shutdown();
|
||||
config_ = config;
|
||||
scheduler_.Configure(config_.max_frames_in_flight);
|
||||
|
||||
if (!device_.InitializeShared(config, memory, device, queue)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
stats_.initialized = true;
|
||||
stats_.active_backend = BackendType::kD3D12;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool NativeRenderer::Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) {
|
||||
REXLOG_INFO("NativeRenderer::Initialize starting");
|
||||
Shutdown();
|
||||
|
||||
config_ = config;
|
||||
REXLOG_INFO("NativeRenderer: Configuring scheduler (max_frames={})", config_.max_frames_in_flight);
|
||||
scheduler_.Configure(config_.max_frames_in_flight);
|
||||
|
||||
REXLOG_INFO("NativeRenderer: Initializing render device...");
|
||||
if (!device_.Initialize(config_, memory)) {
|
||||
REXLOG_ERROR("NativeRenderer: device_.Initialize failed");
|
||||
return false;
|
||||
}
|
||||
|
||||
stats_.initialized = true;
|
||||
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;
|
||||
}
|
||||
|
||||
void NativeRenderer::Shutdown() {
|
||||
device_.Shutdown();
|
||||
graph_.Reset();
|
||||
frontend_.Reset();
|
||||
frame_plan_ = {};
|
||||
replay_frame_ = {};
|
||||
execution_plan_ = {};
|
||||
executor_frame_ = {};
|
||||
stats_ = {};
|
||||
}
|
||||
|
||||
void NativeRenderer::BeginFrame() {
|
||||
if (!stats_.initialized) {
|
||||
return;
|
||||
}
|
||||
scheduler_.BeginFrame();
|
||||
++stats_.frame_count;
|
||||
graph_.Reset();
|
||||
}
|
||||
|
||||
void NativeRenderer::BuildBootstrapFrame() {
|
||||
if (!stats_.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
frame_plan_ = {};
|
||||
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.
|
||||
for (const ReplayExecutorPassPacket& pass : executor_frame_.passes) {
|
||||
graph_.AddPass(BuildRenderPassDesc(pass));
|
||||
}
|
||||
stats_.built_pass_count += graph_.pass_count();
|
||||
|
||||
REXLOG_TRACE("AC6 native renderer bootstrap frame built passes={}",
|
||||
graph_.pass_count());
|
||||
}
|
||||
|
||||
void NativeRenderer::BuildCapturedFrame(
|
||||
const ac6::d3d::FrameCaptureSnapshot& frame_capture) {
|
||||
if (!stats_.initialized) {
|
||||
return;
|
||||
}
|
||||
|
||||
const FrontendFrameSummary summary = frontend_.BuildFromCapture(frame_capture);
|
||||
if (!summary.capture_valid) {
|
||||
BuildBootstrapFrame();
|
||||
return;
|
||||
}
|
||||
|
||||
frame_plan_ = planner_.Build(summary, frontend_.passes());
|
||||
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));
|
||||
}
|
||||
|
||||
stats_.built_pass_count += graph_.pass_count();
|
||||
REXLOG_INFO(
|
||||
"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, 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);
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Phase 4: backend accessors
|
||||
// ---------------------------------------------------------------------------
|
||||
namespace ac6::renderer {
|
||||
|
||||
D3D12Backend* NativeRenderer::GetD3D12Backend() const {
|
||||
if (!device_.backend() || device_.active_backend() != BackendType::kD3D12) {
|
||||
return nullptr;
|
||||
}
|
||||
return static_cast<D3D12Backend*>(device_.backend());
|
||||
}
|
||||
|
||||
ID3D12Resource* NativeRenderer::GetOutputTexture() const {
|
||||
D3D12Backend* b = GetD3D12Backend();
|
||||
return b ? b->GetOutputTexture() : nullptr;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,85 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <rex/memory.h>
|
||||
|
||||
#include "ac6_render_frontend.h"
|
||||
#include "execution_plan.h"
|
||||
#include "frame_scheduler.h"
|
||||
#include "frame_plan.h"
|
||||
#include "replay_ir.h"
|
||||
#include "replay_executor.h"
|
||||
#include "render_device.h"
|
||||
#include "render_graph.h"
|
||||
#include "types.h"
|
||||
|
||||
// Forward declare so callers can access the output texture without pulling in
|
||||
// all of d3d12_backend.h transitively.
|
||||
struct ID3D12Resource;
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
// Experimental capture-replay renderer retained for diagnostics and future
|
||||
// targeted overrides. It is not the default presentation path.
|
||||
class NativeRenderer {
|
||||
public:
|
||||
NativeRenderer();
|
||||
~NativeRenderer();
|
||||
|
||||
NativeRenderer(const NativeRenderer&) = delete;
|
||||
NativeRenderer& operator=(const NativeRenderer&) = delete;
|
||||
|
||||
bool Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory);
|
||||
bool InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue);
|
||||
void Shutdown();
|
||||
|
||||
void BeginFrame();
|
||||
void BuildBootstrapFrame();
|
||||
void BuildCapturedFrame(const ac6::d3d::FrameCaptureSnapshot& frame_capture);
|
||||
|
||||
NativeRendererStats GetStats() const { return stats_; }
|
||||
FeatureLevel feature_level() const { return config_.feature_level; }
|
||||
uint32_t frame_slot() const { return scheduler_.frame_slot(); }
|
||||
uint32_t max_frames_in_flight() const { return scheduler_.max_frames_in_flight(); }
|
||||
FrontendFrameSummary frontend_summary() const { return frontend_.summary(); }
|
||||
const std::vector<ObservedPassDesc>& frontend_passes() const {
|
||||
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_; }
|
||||
ExecutionFrameSummary execution_summary() const { return execution_plan_.summary; }
|
||||
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();
|
||||
}
|
||||
|
||||
// Phase 4: returns the native output texture produced by the D3D12 backend,
|
||||
// or nullptr if not yet available. The texture is in
|
||||
// D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE after SubmitExecutorFrame.
|
||||
ID3D12Resource* GetOutputTexture() const;
|
||||
|
||||
// Phase 4: returns the raw D3D12Backend* (nullptr for non-D3D12 backends).
|
||||
class D3D12Backend* GetD3D12Backend() const;
|
||||
|
||||
private:
|
||||
NativeRendererConfig config_{};
|
||||
NativeRendererStats stats_{};
|
||||
RenderDevice device_{};
|
||||
RenderGraph graph_{};
|
||||
FrameScheduler scheduler_{};
|
||||
Ac6RenderFrontend frontend_{};
|
||||
FramePlanner planner_{};
|
||||
ReplayIrBuilder replay_builder_{};
|
||||
ExecutionPlanBuilder execution_builder_{};
|
||||
ReplayExecutorPlanBuilder executor_builder_{};
|
||||
NativeFramePlan frame_plan_{};
|
||||
ReplayFrame replay_frame_{};
|
||||
ExecutionFramePlan execution_plan_{};
|
||||
ReplayExecutorFrame executor_frame_{};
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,91 +0,0 @@
|
||||
#include "render_device.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
#include <rex/logging.h>
|
||||
|
||||
#include "backends/d3d12_backend.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
RenderDevice::RenderDevice() = default;
|
||||
|
||||
RenderDevice::~RenderDevice() {
|
||||
Shutdown();
|
||||
}
|
||||
|
||||
bool RenderDevice::InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue) {
|
||||
Shutdown();
|
||||
|
||||
active_backend_ = BackendType::kD3D12;
|
||||
backend_ = CreateBackend(active_backend_);
|
||||
if (!backend_) return false;
|
||||
|
||||
auto* d3d_backend = static_cast<D3D12Backend*>(backend_.get());
|
||||
if (!d3d_backend->InitializeShared(config, memory, device, queue)) {
|
||||
backend_.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool RenderDevice::Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) {
|
||||
Shutdown();
|
||||
|
||||
active_backend_ = ResolveBackend(config.preferred_backend);
|
||||
backend_ = CreateBackend(active_backend_);
|
||||
if (!backend_) {
|
||||
REXLOG_ERROR("AC6 native renderer has no backend factory for {}",
|
||||
ToString(active_backend_));
|
||||
active_backend_ = BackendType::kUnknown;
|
||||
return false;
|
||||
}
|
||||
if (!backend_->IsSupported()) {
|
||||
REXLOG_WARN("AC6 native renderer backend {} is not supported on this platform",
|
||||
backend_->GetName());
|
||||
backend_.reset();
|
||||
active_backend_ = BackendType::kUnknown;
|
||||
return false;
|
||||
}
|
||||
if (!backend_->Initialize(config, memory)) {
|
||||
REXLOG_ERROR("AC6 native renderer backend {} failed initialization",
|
||||
backend_->GetName());
|
||||
backend_.reset();
|
||||
active_backend_ = BackendType::kUnknown;
|
||||
return false;
|
||||
}
|
||||
|
||||
initialized_ = true;
|
||||
REXLOG_INFO("AC6 native renderer initialized backend={} feature_level={} frames_in_flight={}",
|
||||
backend_->GetName(), ToString(config.feature_level),
|
||||
config.max_frames_in_flight);
|
||||
return true;
|
||||
}
|
||||
|
||||
void RenderDevice::Shutdown() {
|
||||
if (backend_) {
|
||||
backend_->Shutdown();
|
||||
backend_.reset();
|
||||
}
|
||||
initialized_ = false;
|
||||
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
|
||||
@@ -1,63 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
#include <string_view>
|
||||
|
||||
#if defined(_WIN32)
|
||||
#include <d3d12.h>
|
||||
#else
|
||||
struct ID3D12Device;
|
||||
struct ID3D12CommandQueue;
|
||||
#endif
|
||||
|
||||
#include <rex/memory.h>
|
||||
|
||||
#include "replay_executor.h"
|
||||
#include "types.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
class RenderDeviceBackend {
|
||||
public:
|
||||
virtual ~RenderDeviceBackend() = default;
|
||||
|
||||
virtual BackendType GetType() const = 0;
|
||||
virtual std::string_view GetName() const = 0;
|
||||
virtual bool IsSupported() const = 0;
|
||||
virtual bool Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory) = 0;
|
||||
virtual bool InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue) = 0;
|
||||
virtual bool SubmitExecutorFrame(const ReplayExecutorFrame& frame) = 0;
|
||||
virtual BackendExecutorStatus GetExecutorStatus() const = 0;
|
||||
virtual void Shutdown() = 0;
|
||||
};
|
||||
|
||||
class RenderDevice {
|
||||
public:
|
||||
RenderDevice();
|
||||
~RenderDevice();
|
||||
|
||||
RenderDevice(const RenderDevice&) = delete;
|
||||
RenderDevice& operator=(const RenderDevice&) = delete;
|
||||
|
||||
bool Initialize(const NativeRendererConfig& config, rex::memory::Memory* memory);
|
||||
bool InitializeShared(const NativeRendererConfig& config, rex::memory::Memory* memory, ID3D12Device* device, ID3D12CommandQueue* queue);
|
||||
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;
|
||||
|
||||
// Raw backend pointer — used by NativeRenderer to downcast to D3D12Backend*.
|
||||
RenderDeviceBackend* backend() const { return backend_.get(); }
|
||||
|
||||
private:
|
||||
std::unique_ptr<RenderDeviceBackend> backend_;
|
||||
BackendType active_backend_ = BackendType::kUnknown;
|
||||
bool initialized_ = false;
|
||||
};
|
||||
|
||||
std::unique_ptr<RenderDeviceBackend> CreateBackend(BackendType backend);
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,16 +0,0 @@
|
||||
#include "render_graph.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
void RenderGraph::Reset() {
|
||||
passes_.clear();
|
||||
}
|
||||
|
||||
uint32_t RenderGraph::AddPass(RenderPassDesc pass) {
|
||||
passes_.push_back(std::move(pass));
|
||||
return static_cast<uint32_t>(passes_.size() - 1);
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,43 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
enum class RenderPassKind : uint8_t {
|
||||
kUnknown = 0,
|
||||
kScene = 1,
|
||||
kPostProcess = 2,
|
||||
kUiComposite = 3,
|
||||
kBootstrap = 4,
|
||||
};
|
||||
|
||||
struct RenderPassDesc {
|
||||
std::string name;
|
||||
RenderPassKind kind = RenderPassKind::kUnknown;
|
||||
bool async_compute = false;
|
||||
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;
|
||||
};
|
||||
|
||||
class RenderGraph {
|
||||
public:
|
||||
void Reset();
|
||||
uint32_t AddPass(RenderPassDesc pass);
|
||||
|
||||
uint32_t pass_count() const { return static_cast<uint32_t>(passes_.size()); }
|
||||
const std::vector<RenderPassDesc>& passes() const { return passes_; }
|
||||
|
||||
private:
|
||||
std::vector<RenderPassDesc> passes_;
|
||||
};
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,187 +0,0 @@
|
||||
#include "replay_executor.h"
|
||||
|
||||
#include <utility>
|
||||
|
||||
namespace ac6::renderer {
|
||||
namespace {
|
||||
|
||||
SubmissionQueueType SelectQueue(const ExecutionPassPacket& pass) {
|
||||
(void)pass;
|
||||
// Current scaffold keeps all work on the graphics queue until backend
|
||||
// implementations can prove safe async-compute or copy splits.
|
||||
return SubmissionQueueType::kGraphics;
|
||||
}
|
||||
|
||||
ReplayExecutorCommandPacket BuildExecutorCommandPacket(
|
||||
const ExecutionCommandPacket& command, uint32_t execution_pass_index,
|
||||
uint32_t execution_command_index) {
|
||||
const bool is_draw = command.category == ExecutionCommandCategory::kDraw;
|
||||
return ReplayExecutorCommandPacket{
|
||||
.category = command.category,
|
||||
.execution_pass_index = execution_pass_index,
|
||||
.execution_command_index = execution_command_index,
|
||||
.sequence = command.sequence,
|
||||
.requires_resource_translation =
|
||||
command.render_target_0 != 0 || command.depth_stencil != 0 ||
|
||||
command.texture_count != 0 || command.stream_count != 0 ||
|
||||
command.fetch_constant_count != 0,
|
||||
.requires_pipeline_state = is_draw,
|
||||
.requires_descriptor_setup =
|
||||
is_draw &&
|
||||
(command.texture_count != 0 || command.sampler_count != 0 ||
|
||||
command.fetch_constant_count != 0),
|
||||
.touches_render_target = command.render_target_0 != 0,
|
||||
.touches_depth_stencil = command.depth_stencil != 0,
|
||||
// Forwarded dispatch fields
|
||||
.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,
|
||||
.shadow_state = command.shadow_state,
|
||||
};
|
||||
}
|
||||
|
||||
ReplayExecutorPassPacket BuildExecutorPassPacket(const ExecutionPassPacket& pass,
|
||||
uint32_t execution_pass_index) {
|
||||
ReplayExecutorPassPacket executor_pass;
|
||||
executor_pass.name = pass.name;
|
||||
executor_pass.role = pass.role;
|
||||
executor_pass.queue = SelectQueue(pass);
|
||||
executor_pass.execution_pass_valid = true;
|
||||
executor_pass.execution_pass_index = execution_pass_index;
|
||||
executor_pass.draw_count = pass.draw_count;
|
||||
executor_pass.clear_count = pass.clear_count;
|
||||
executor_pass.resolve_count = pass.resolve_count;
|
||||
executor_pass.render_target_0 = pass.render_target_0;
|
||||
executor_pass.depth_stencil = pass.depth_stencil;
|
||||
executor_pass.output_width = pass.output_width;
|
||||
executor_pass.output_height = pass.output_height;
|
||||
executor_pass.selected_for_present = pass.selected_for_present;
|
||||
executor_pass.requires_present = pass.selected_for_present;
|
||||
executor_pass.resources = pass.resources;
|
||||
executor_pass.commands.reserve(pass.commands.size());
|
||||
|
||||
for (uint32_t i = 0; i < pass.commands.size(); ++i) {
|
||||
ReplayExecutorCommandPacket command_packet =
|
||||
BuildExecutorCommandPacket(pass.commands[i], execution_pass_index, i);
|
||||
executor_pass.requires_resource_translation |=
|
||||
command_packet.requires_resource_translation;
|
||||
executor_pass.requires_pipeline_state |= command_packet.requires_pipeline_state;
|
||||
executor_pass.requires_descriptor_setup |=
|
||||
command_packet.requires_descriptor_setup;
|
||||
executor_pass.commands.push_back(std::move(command_packet));
|
||||
}
|
||||
|
||||
executor_pass.requires_resource_translation |=
|
||||
pass.resources.needs_render_target || pass.resources.needs_depth_stencil ||
|
||||
pass.resources.needs_vertex_streams || pass.resources.needs_index_buffer ||
|
||||
pass.resources.needs_textures || pass.resources.needs_fetch_constants;
|
||||
executor_pass.requires_pipeline_state |= pass.draw_count != 0;
|
||||
executor_pass.requires_descriptor_setup |=
|
||||
pass.resources.needs_textures || pass.resources.needs_samplers ||
|
||||
pass.resources.needs_fetch_constants;
|
||||
executor_pass.requires_present |= pass.role == ReplayPassRole::kPresent;
|
||||
return executor_pass;
|
||||
}
|
||||
|
||||
void AccumulateSummary(ReplayExecutorFrameSummary& summary,
|
||||
const ReplayExecutorPassPacket& pass) {
|
||||
++summary.pass_count;
|
||||
summary.command_count += static_cast<uint32_t>(pass.commands.size());
|
||||
|
||||
switch (pass.queue) {
|
||||
case SubmissionQueueType::kGraphics:
|
||||
++summary.graphics_pass_count;
|
||||
break;
|
||||
case SubmissionQueueType::kAsyncCompute:
|
||||
++summary.async_compute_pass_count;
|
||||
break;
|
||||
case SubmissionQueueType::kCopy:
|
||||
++summary.copy_pass_count;
|
||||
break;
|
||||
case SubmissionQueueType::kUnknown:
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (pass.requires_present) {
|
||||
++summary.present_pass_count;
|
||||
summary.has_present_pass = true;
|
||||
}
|
||||
if (pass.requires_resource_translation) {
|
||||
++summary.resource_translation_pass_count;
|
||||
}
|
||||
if (pass.requires_pipeline_state) {
|
||||
++summary.pipeline_state_pass_count;
|
||||
}
|
||||
if (pass.requires_descriptor_setup) {
|
||||
++summary.descriptor_setup_pass_count;
|
||||
}
|
||||
summary.valid = summary.pass_count != 0;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
const char* ToString(SubmissionQueueType queue) {
|
||||
switch (queue) {
|
||||
case SubmissionQueueType::kGraphics:
|
||||
return "graphics";
|
||||
case SubmissionQueueType::kAsyncCompute:
|
||||
return "async_compute";
|
||||
case SubmissionQueueType::kCopy:
|
||||
return "copy";
|
||||
case SubmissionQueueType::kUnknown:
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
ReplayExecutorFrame ReplayExecutorPlanBuilder::BuildBootstrapFrame(
|
||||
uint64_t frame_index) const {
|
||||
ReplayExecutorFrame frame;
|
||||
frame.summary.frame_index = frame_index;
|
||||
|
||||
ReplayExecutorPassPacket bootstrap_pass;
|
||||
bootstrap_pass.name = "ac6.executor.bootstrap";
|
||||
bootstrap_pass.role = ReplayPassRole::kBootstrap;
|
||||
bootstrap_pass.queue = SubmissionQueueType::kGraphics;
|
||||
|
||||
AccumulateSummary(frame.summary, bootstrap_pass);
|
||||
frame.passes.push_back(std::move(bootstrap_pass));
|
||||
return frame;
|
||||
}
|
||||
|
||||
ReplayExecutorFrame ReplayExecutorPlanBuilder::Build(
|
||||
const ExecutionFramePlan& execution_plan) const {
|
||||
ReplayExecutorFrame frame;
|
||||
frame.summary.frame_index = execution_plan.summary.frame_index;
|
||||
frame.summary.output_width = execution_plan.summary.output_width;
|
||||
frame.summary.output_height = execution_plan.summary.output_height;
|
||||
|
||||
if (!execution_plan.summary.valid || execution_plan.passes.empty()) {
|
||||
return frame;
|
||||
}
|
||||
|
||||
frame.passes.reserve(execution_plan.passes.size());
|
||||
for (uint32_t i = 0; i < execution_plan.passes.size(); ++i) {
|
||||
ReplayExecutorPassPacket pass =
|
||||
BuildExecutorPassPacket(execution_plan.passes[i], i);
|
||||
AccumulateSummary(frame.summary, pass);
|
||||
frame.passes.push_back(std::move(pass));
|
||||
}
|
||||
|
||||
frame.summary.valid =
|
||||
frame.summary.pass_count != 0 &&
|
||||
(frame.summary.output_width != 0 || frame.summary.output_height != 0 ||
|
||||
execution_plan.summary.frame_index != 0 ||
|
||||
!frame.passes.empty());
|
||||
return frame;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,94 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include "execution_plan.h"
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
enum class SubmissionQueueType : uint8_t {
|
||||
kUnknown = 0,
|
||||
kGraphics = 1,
|
||||
kAsyncCompute = 2,
|
||||
kCopy = 3,
|
||||
};
|
||||
|
||||
struct ReplayExecutorCommandPacket {
|
||||
ExecutionCommandCategory category = ExecutionCommandCategory::kNone;
|
||||
uint32_t execution_pass_index = 0;
|
||||
uint32_t execution_command_index = 0;
|
||||
uint32_t sequence = 0;
|
||||
bool requires_resource_translation = false;
|
||||
bool requires_pipeline_state = false;
|
||||
bool requires_descriptor_setup = false;
|
||||
bool touches_render_target = false;
|
||||
bool touches_depth_stencil = false;
|
||||
// Draw call dispatch fields (forwarded from ExecutionCommandPacket)
|
||||
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;
|
||||
ac6::d3d::ShadowState shadow_state{};
|
||||
};
|
||||
|
||||
struct ReplayExecutorPassPacket {
|
||||
std::string name;
|
||||
ReplayPassRole role = ReplayPassRole::kUnknown;
|
||||
SubmissionQueueType queue = SubmissionQueueType::kUnknown;
|
||||
bool execution_pass_valid = false;
|
||||
uint32_t execution_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 output_width = 0;
|
||||
uint32_t output_height = 0;
|
||||
bool selected_for_present = false;
|
||||
bool requires_present = false;
|
||||
bool requires_resource_translation = false;
|
||||
bool requires_pipeline_state = false;
|
||||
bool requires_descriptor_setup = false;
|
||||
ExecutionResourceRequirements resources{};
|
||||
std::vector<ReplayExecutorCommandPacket> commands;
|
||||
};
|
||||
|
||||
struct ReplayExecutorFrameSummary {
|
||||
bool valid = false;
|
||||
uint64_t frame_index = 0;
|
||||
uint32_t pass_count = 0;
|
||||
uint32_t 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;
|
||||
uint32_t output_width = 0;
|
||||
uint32_t output_height = 0;
|
||||
bool has_present_pass = false;
|
||||
};
|
||||
|
||||
struct ReplayExecutorFrame {
|
||||
ReplayExecutorFrameSummary summary{};
|
||||
std::vector<ReplayExecutorPassPacket> passes;
|
||||
};
|
||||
|
||||
class ReplayExecutorPlanBuilder {
|
||||
public:
|
||||
ReplayExecutorFrame BuildBootstrapFrame(uint64_t frame_index) const;
|
||||
ReplayExecutorFrame Build(const ExecutionFramePlan& execution_plan) const;
|
||||
};
|
||||
|
||||
const char* ToString(SubmissionQueueType queue);
|
||||
|
||||
} // namespace ac6::renderer
|
||||
@@ -1,165 +0,0 @@
|
||||
#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,
|
||||
.shadow_state = command.shadow_state,
|
||||
};
|
||||
}
|
||||
|
||||
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
|
||||
@@ -1,91 +0,0 @@
|
||||
#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;
|
||||
ac6::d3d::ShadowState shadow_state{};
|
||||
};
|
||||
|
||||
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
|
||||
@@ -1,115 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
namespace ac6::renderer {
|
||||
|
||||
enum class BackendType : uint8_t {
|
||||
kUnknown = 0,
|
||||
kD3D12 = 1,
|
||||
kVulkan = 2,
|
||||
kMetal = 3,
|
||||
};
|
||||
|
||||
enum class FeatureLevel : uint8_t {
|
||||
kBootstrap = 0,
|
||||
kSceneSubmission = 1,
|
||||
kParityValidation = 2,
|
||||
kShipping = 3,
|
||||
};
|
||||
|
||||
struct NativeRendererConfig {
|
||||
BackendType preferred_backend = BackendType::kUnknown;
|
||||
FeatureLevel feature_level = FeatureLevel::kBootstrap;
|
||||
uint32_t max_frames_in_flight = 2;
|
||||
bool enable_debug_markers = true;
|
||||
bool enable_validation = true;
|
||||
};
|
||||
|
||||
struct NativeRendererStats {
|
||||
bool initialized = false;
|
||||
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;
|
||||
uint32_t draw_attempt_count = 0;
|
||||
uint32_t draw_success_count = 0;
|
||||
uint32_t draw_prepare_failure_count = 0;
|
||||
uint32_t draw_pso_failure_count = 0;
|
||||
uint32_t indexed_draw_count = 0;
|
||||
uint32_t non_indexed_draw_count = 0;
|
||||
uint32_t clear_command_count = 0;
|
||||
uint32_t resolve_command_count = 0;
|
||||
uint32_t invalid_stream_binding_count = 0;
|
||||
uint32_t invalid_index_buffer_count = 0;
|
||||
uint32_t index_count_overflow_count = 0;
|
||||
uint32_t index_data_unavailable_count = 0;
|
||||
uint32_t index_buffer_create_failure_count = 0;
|
||||
uint32_t index_upload_failure_count = 0;
|
||||
uint32_t zero_vertex_count = 0;
|
||||
uint32_t invalid_vertex_range_count = 0;
|
||||
uint32_t vertex_buffer_size_invalid_count = 0;
|
||||
uint32_t vertex_buffer_create_failure_count = 0;
|
||||
uint32_t vertex_data_unavailable_count = 0;
|
||||
uint32_t vertex_upload_failure_count = 0;
|
||||
};
|
||||
|
||||
constexpr std::string_view ToString(BackendType backend) {
|
||||
switch (backend) {
|
||||
case BackendType::kD3D12:
|
||||
return "d3d12";
|
||||
case BackendType::kVulkan:
|
||||
return "vulkan";
|
||||
case BackendType::kMetal:
|
||||
return "metal";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
constexpr std::string_view ToString(FeatureLevel level) {
|
||||
switch (level) {
|
||||
case FeatureLevel::kBootstrap:
|
||||
return "bootstrap";
|
||||
case FeatureLevel::kSceneSubmission:
|
||||
return "scene_submission";
|
||||
case FeatureLevel::kParityValidation:
|
||||
return "parity_validation";
|
||||
case FeatureLevel::kShipping:
|
||||
return "shipping";
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
|
||||
#if defined(_WIN32)
|
||||
constexpr BackendType kPlatformDefaultBackend = BackendType::kD3D12;
|
||||
#elif defined(__APPLE__)
|
||||
constexpr BackendType kPlatformDefaultBackend = BackendType::kMetal;
|
||||
#else
|
||||
constexpr BackendType kPlatformDefaultBackend = BackendType::kVulkan;
|
||||
#endif
|
||||
|
||||
inline BackendType ResolveBackend(BackendType preferred_backend) {
|
||||
return preferred_backend == BackendType::kUnknown ? kPlatformDefaultBackend
|
||||
: preferred_backend;
|
||||
}
|
||||
|
||||
} // namespace ac6::renderer
|
||||
+75
-9
@@ -10,9 +10,6 @@ REXCVAR_DECLARE(bool, ac6_render_capture);
|
||||
REXCVAR_DECLARE(bool, ac6_timing_hooks_enabled);
|
||||
REXCVAR_DECLARE(bool, ac6_unlock_fps);
|
||||
REXCVAR_DECLARE(bool, ac6_native_graphics_enabled);
|
||||
REXCVAR_DECLARE(bool, ac6_native_graphics_require_capture);
|
||||
REXCVAR_DECLARE(bool, ac6_experimental_replay_present);
|
||||
REXCVAR_DECLARE(bool, ac6_force_safe_render_capture);
|
||||
REXCVAR_DECLARE(bool, ac6_force_safe_draw_resolution_scale);
|
||||
REXCVAR_DECLARE(bool, ac6_force_safe_direct_host_resolve);
|
||||
REXCVAR_DECLARE(std::string, ac6_graphics_mode);
|
||||
@@ -22,6 +19,24 @@ REXCVAR_DECLARE(int32_t, draw_resolution_scale_x);
|
||||
REXCVAR_DECLARE(int32_t, draw_resolution_scale_y);
|
||||
REXCVAR_DECLARE(std::string, log_file);
|
||||
REXCVAR_DECLARE(std::string, log_level);
|
||||
REXCVAR_DECLARE(bool, ac6_d3d_trace);
|
||||
REXCVAR_DECLARE(bool, ac6_backend_debug_swap);
|
||||
REXCVAR_DECLARE(bool, ac6_backend_log_signatures);
|
||||
REXCVAR_DECLARE(bool, ac6_backend_signature_diagnostics);
|
||||
REXCVAR_DECLARE(bool, ac6_texture_swaps_dump_enabled);
|
||||
REXCVAR_DECLARE(bool, vsync);
|
||||
REXCVAR_DECLARE(bool, guest_vblank_sync_to_refresh);
|
||||
REXCVAR_DECLARE(bool, host_present_from_non_ui_thread);
|
||||
REXCVAR_DECLARE(bool, d3d12_allow_variable_refresh_rate_and_tearing);
|
||||
REXCVAR_DECLARE(bool, vfetch_index_rounding_bias);
|
||||
REXCVAR_DECLARE(int32_t, video_mode_width);
|
||||
REXCVAR_DECLARE(int32_t, video_mode_height);
|
||||
REXCVAR_DECLARE(std::string, resolution);
|
||||
REXCVAR_DECLARE(int32_t, window_width);
|
||||
REXCVAR_DECLARE(int32_t, window_height);
|
||||
|
||||
REXCVAR_DEFINE_BOOL(ac6_performance_mode, true, "AC6/Performance",
|
||||
"Disable all diagnostics, logging, and development overlays for maximum runtime performance");
|
||||
|
||||
#include "generated/ac6recomp_config.h"
|
||||
#include "generated/ac6recomp_init.h"
|
||||
@@ -55,15 +70,63 @@ void ApplyAc6HybridStartupSafetyOverrides() {
|
||||
if (REXCVAR_GET(ac6_force_safe_direct_host_resolve)) {
|
||||
REXCVAR_SET(direct_host_resolve, false);
|
||||
}
|
||||
}
|
||||
|
||||
if (REXCVAR_GET(ac6_force_safe_render_capture)) {
|
||||
REXCVAR_SET(ac6_native_graphics_require_capture, false);
|
||||
REXCVAR_SET(ac6_render_capture, false);
|
||||
void ApplyAc6DefaultSettings() {
|
||||
if (!rex::cvar::HasNonDefaultValue("vsync")) {
|
||||
REXCVAR_SET(vsync, true);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("guest_vblank_sync_to_refresh")) {
|
||||
REXCVAR_SET(guest_vblank_sync_to_refresh, true);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("host_present_from_non_ui_thread")) {
|
||||
REXCVAR_SET(host_present_from_non_ui_thread, false);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("d3d12_allow_variable_refresh_rate_and_tearing")) {
|
||||
REXCVAR_SET(d3d12_allow_variable_refresh_rate_and_tearing, false);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("vfetch_index_rounding_bias")) {
|
||||
REXCVAR_SET(vfetch_index_rounding_bias, true);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("direct_host_resolve")) {
|
||||
REXCVAR_SET(direct_host_resolve, false);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("video_mode_width")) {
|
||||
REXCVAR_SET(video_mode_width, 1920);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("video_mode_height")) {
|
||||
REXCVAR_SET(video_mode_height, 1080);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("resolution")) {
|
||||
REXCVAR_SET(resolution, "1080p");
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("window_width")) {
|
||||
REXCVAR_SET(window_width, 1920);
|
||||
}
|
||||
if (!rex::cvar::HasNonDefaultValue("window_height")) {
|
||||
REXCVAR_SET(window_height, 1080);
|
||||
}
|
||||
}
|
||||
|
||||
void ApplyAc6PerformanceModeOverrides() {
|
||||
if (!REXCVAR_GET(ac6_performance_mode)) {
|
||||
return;
|
||||
}
|
||||
REXCVAR_SET(log_level, "error");
|
||||
REXCVAR_SET(ac6_d3d_trace, false);
|
||||
REXCVAR_SET(ac6_render_capture, false);
|
||||
REXCVAR_SET(ac6_backend_debug_swap, false);
|
||||
REXCVAR_SET(ac6_backend_log_signatures, false);
|
||||
REXCVAR_SET(ac6_backend_signature_diagnostics, false);
|
||||
REXCVAR_SET(ac6_texture_swaps_dump_enabled, false);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ApplyAc6PerformanceModeOverridesPublic() {
|
||||
ApplyAc6PerformanceModeOverrides();
|
||||
}
|
||||
|
||||
void InitEarlyLog() {
|
||||
g_boot_log.open("boot.log", std::ios::out | std::ios::trunc);
|
||||
if (g_boot_log.is_open()) {
|
||||
@@ -82,13 +145,16 @@ std::unique_ptr<rex::ui::WindowedApp> Ac6recompAppCreate(rex::ui::WindowedAppCon
|
||||
|
||||
// Force SDK logging to a file as well
|
||||
REXCVAR_SET(log_file, "ac6recomp.log");
|
||||
REXCVAR_SET(log_level, "info");
|
||||
if (!rex::cvar::HasNonDefaultValue("log_level")) {
|
||||
REXCVAR_SET(log_level, "debug");
|
||||
}
|
||||
REXCVAR_SET(ac6_unlock_fps, false);
|
||||
ApplyAc6DefaultSettings();
|
||||
ApplyAc6HybridStartupSafetyOverrides();
|
||||
ApplyAc6PerformanceModeOverrides();
|
||||
|
||||
REXLOG_INFO("Ac6recompAppCreate: graphics mode={} replay_present={} capture={}",
|
||||
REXLOG_INFO("Ac6recompAppCreate: graphics mode={} capture={}",
|
||||
REXCVAR_GET(ac6_graphics_mode),
|
||||
REXCVAR_GET(ac6_experimental_replay_present) ? "true" : "false",
|
||||
REXCVAR_GET(ac6_render_capture) ? "true" : "false");
|
||||
|
||||
return Ac6recompApp::Create(ctx);
|
||||
|
||||
@@ -2105,7 +2105,7 @@ bool D3D12CommandProcessor::IssueSwapInternal(uint32_t frontbuffer_ptr,
|
||||
bool using_native_swap_texture = false;
|
||||
bool used_direct_display_fallback = false;
|
||||
|
||||
ID3D12Resource* swap_texture_resource = ac6::graphics::GetNativeOutputTexture();
|
||||
ID3D12Resource* swap_texture_resource = nullptr;
|
||||
if (swap_texture_resource) {
|
||||
D3D12_RESOURCE_DESC native_desc = swap_texture_resource->GetDesc();
|
||||
swap_texture_srv_desc.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING;
|
||||
|
||||
Reference in New Issue
Block a user