mirror of
https://github.com/sal063/AC6_recomp
synced 2026-05-26 23:48:01 -04:00
180 lines
7.0 KiB
C++
180 lines
7.0 KiB
C++
#include "ac6_native_graphics.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstdint>
|
|
#include <string_view>
|
|
|
|
#include <native/audio/audio_system.h>
|
|
#include <rex/cvar.h>
|
|
#include <rex/graphics/flags.h>
|
|
#include <rex/graphics/graphics_system.h>
|
|
#include <rex/logging.h>
|
|
#include <rex/system/kernel_state.h>
|
|
#include <rex/system/thread_state.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_STRING(ac6_graphics_mode, "hybrid_backend_fixes", "AC6/NativeGraphics",
|
|
"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");
|
|
|
|
namespace ac6::graphics {
|
|
namespace {
|
|
|
|
NativeGraphicsRuntimeStatus g_runtime_status{};
|
|
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};
|
|
|
|
GraphicsRuntimeMode ParseGraphicsMode(std::string_view value) {
|
|
if (value == "disabled") {
|
|
return GraphicsRuntimeMode::kDisabled;
|
|
}
|
|
if (value == "analysis_only") {
|
|
return GraphicsRuntimeMode::kAnalysisOnly;
|
|
}
|
|
return GraphicsRuntimeMode::kHybridBackendFixes;
|
|
}
|
|
|
|
void SyncRuntimeFlags() {
|
|
g_runtime_status.enabled = REXCVAR_GET(ac6_native_graphics_enabled);
|
|
g_runtime_status.mode = ParseGraphicsMode(REXCVAR_GET(ac6_graphics_mode));
|
|
g_runtime_status.capture_enabled = REXCVAR_GET(ac6_render_capture);
|
|
const uint32_t shared_scale =
|
|
static_cast<uint32_t>(std::max(INT32_C(1), REXCVAR_GET(resolution_scale)));
|
|
const bool use_shared_scale = rex::cvar::HasNonDefaultValue("resolution_scale");
|
|
const int32_t configured_scale_x =
|
|
use_shared_scale && !rex::cvar::HasNonDefaultValue("draw_resolution_scale_x")
|
|
? static_cast<int32_t>(shared_scale)
|
|
: REXCVAR_GET(draw_resolution_scale_x);
|
|
const int32_t configured_scale_y =
|
|
use_shared_scale && !rex::cvar::HasNonDefaultValue("draw_resolution_scale_y")
|
|
? static_cast<int32_t>(shared_scale)
|
|
: REXCVAR_GET(draw_resolution_scale_y);
|
|
g_runtime_status.draw_resolution_scale_x =
|
|
static_cast<uint32_t>(std::max(INT32_C(1), configured_scale_x));
|
|
g_runtime_status.draw_resolution_scale_y =
|
|
static_cast<uint32_t>(std::max(INT32_C(1), configured_scale_y));
|
|
g_runtime_status.direct_host_resolve = REXCVAR_GET(direct_host_resolve);
|
|
g_runtime_status.draw_resolution_scaled_texture_offsets =
|
|
REXCVAR_GET(draw_resolution_scaled_texture_offsets);
|
|
g_runtime_status.authoritative_renderer_active =
|
|
g_runtime_status.enabled &&
|
|
g_runtime_status.mode != GraphicsRuntimeMode::kDisabled;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
std::string_view ToString(const GraphicsRuntimeMode mode) {
|
|
switch (mode) {
|
|
case GraphicsRuntimeMode::kDisabled:
|
|
return "disabled";
|
|
case GraphicsRuntimeMode::kAnalysisOnly:
|
|
return "analysis_only";
|
|
case GraphicsRuntimeMode::kHybridBackendFixes:
|
|
return "hybrid_backend_fixes";
|
|
default:
|
|
return "unknown";
|
|
}
|
|
}
|
|
|
|
void OnFrameBoundary(rex::memory::Memory* memory) {
|
|
if (g_frame_boundary_active.test_and_set(std::memory_order_acquire)) {
|
|
const uint32_t reentry_count =
|
|
g_frame_boundary_reentry_count.fetch_add(1, std::memory_order_relaxed) + 1;
|
|
if (reentry_count == 1 || (reentry_count % 64) == 0) {
|
|
REXLOG_WARN("AC6 graphics: dropping re-entrant frame boundary callback (count={})",
|
|
reentry_count);
|
|
}
|
|
return;
|
|
}
|
|
|
|
struct FrameBoundaryScope {
|
|
~FrameBoundaryScope() {
|
|
g_frame_boundary_active.clear(std::memory_order_release);
|
|
}
|
|
} frame_boundary_scope;
|
|
|
|
SyncRuntimeFlags();
|
|
g_captured_memory.store(memory, std::memory_order_release);
|
|
|
|
if (!g_runtime_status.enabled || g_runtime_status.mode == GraphicsRuntimeMode::kDisabled) {
|
|
ac6::backend::ShutdownDiagnostics();
|
|
return;
|
|
}
|
|
|
|
ac6::d3d::OnFrameBoundary();
|
|
|
|
ac6::d3d::FrameCaptureSummary capture_summary;
|
|
const ac6::d3d::FrameCaptureSnapshot frame_capture =
|
|
ac6::d3d::TakeFrameCapture(&capture_summary);
|
|
const ac6::d3d::ShadowState shadow_state = ac6::d3d::GetShadowState();
|
|
|
|
++g_runtime_status.analysis_frames_observed;
|
|
g_runtime_status.capture_summary = capture_summary;
|
|
g_runtime_status.latest_capture_frame_index = capture_summary.frame_index;
|
|
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;
|
|
}
|
|
|
|
rex::system::GraphicsSwapSubmission swap_submission{};
|
|
uint64_t swap_sequence = 0;
|
|
uint64_t guest_vblank_interval_ticks = 0;
|
|
uint64_t last_guest_vblank_tick = 0;
|
|
rex::audio::AudioTelemetrySnapshot audio_telemetry{};
|
|
rex::audio::AudioClientTimingSnapshot audio_timing{};
|
|
const rex::audio::AudioTelemetrySnapshot* audio_telemetry_ptr = nullptr;
|
|
const rex::audio::AudioClientTimingSnapshot* audio_timing_ptr = nullptr;
|
|
|
|
auto* ts = rex::runtime::ThreadState::Get();
|
|
if (ts && ts->context() && ts->context()->kernel_state) {
|
|
auto* kernel_state = ts->context()->kernel_state;
|
|
if (auto* concrete_graphics =
|
|
dynamic_cast<rex::graphics::GraphicsSystem*>(kernel_state->graphics_system())) {
|
|
concrete_graphics->GetLastSwapSubmission(&swap_submission, &swap_sequence);
|
|
guest_vblank_interval_ticks = concrete_graphics->guest_vblank_interval_ticks();
|
|
last_guest_vblank_tick = concrete_graphics->last_vblank_interrupt_guest_tick();
|
|
}
|
|
if (auto* native_audio = kernel_state->native_audio_system()) {
|
|
audio_telemetry = native_audio->GetTelemetrySnapshot();
|
|
audio_telemetry_ptr = &audio_telemetry;
|
|
if (audio_telemetry.active_clients != 0) {
|
|
audio_timing = native_audio->GetClientTimingSnapshot(0);
|
|
audio_timing_ptr = &audio_timing;
|
|
}
|
|
}
|
|
}
|
|
|
|
ac6::backend::AnalyzeFrameBoundary(
|
|
frame_capture, capture_summary, shadow_state,
|
|
swap_sequence ? &swap_submission : nullptr, swap_sequence,
|
|
guest_vblank_interval_ticks, last_guest_vblank_tick, ac6::GetFrameStats(),
|
|
audio_telemetry_ptr, audio_timing_ptr);
|
|
g_runtime_status.backend_diagnostics = ac6::backend::GetDiagnosticsSnapshot();
|
|
}
|
|
|
|
void Shutdown() {
|
|
g_captured_memory.store(nullptr, std::memory_order_release);
|
|
}
|
|
|
|
NativeGraphicsRuntimeStatus GetRuntimeStatus() {
|
|
SyncRuntimeFlags();
|
|
g_runtime_status.backend_diagnostics = ac6::backend::GetDiagnosticsSnapshot();
|
|
return g_runtime_status;
|
|
}
|
|
|
|
rex::memory::Memory* GetCapturedMemory() {
|
|
return g_captured_memory.load(std::memory_order_acquire);
|
|
}
|
|
|
|
} // namespace ac6::graphics
|