mirror of
https://github.com/sal063/AC6_recomp
synced 2026-08-14 20:44:00 -04:00
feat: wire AC6 native graphics runtime and bootstrap configure path
Run the native renderer every frame from AC6 present timing hooks with a visible runtime status overlay so migration progress is observable in-game. Also add a tracked CMake bootstrap include and migration mapping docs so fresh clones can configure before generated glue exists. Made-with: Cursor
This commit is contained in:
+142
-1
@@ -1 +1,142 @@
|
||||
|
||||
#include "ac6_native_graphics.h"
|
||||
|
||||
#include <mutex>
|
||||
#include <string_view>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/logging.h>
|
||||
|
||||
#include "ac6_native_renderer/native_renderer.h"
|
||||
#include "d3d_hooks.h"
|
||||
|
||||
REXCVAR_DEFINE_BOOL(ac6_native_graphics_enabled, true, "AC6/NativeGraphics",
|
||||
"Enable AC6 native renderer frame-plan execution from captured D3D state");
|
||||
REXCVAR_DEFINE_BOOL(ac6_native_graphics_require_capture, true, "AC6/NativeGraphics",
|
||||
"Force render-capture on while native graphics execution is enabled");
|
||||
REXCVAR_DEFINE_STRING(ac6_native_graphics_backend, "auto", "AC6/NativeGraphics",
|
||||
"Preferred native backend: auto, d3d12, vulkan, metal")
|
||||
.allowed({"auto", "d3d12", "vulkan", "metal"});
|
||||
REXCVAR_DEFINE_STRING(ac6_native_graphics_feature_level, "scene_submission", "AC6/NativeGraphics",
|
||||
"Native renderer feature level: bootstrap, scene_submission, parity_validation, shipping")
|
||||
.allowed({"bootstrap", "scene_submission", "parity_validation", "shipping"});
|
||||
REXCVAR_DEFINE_INT32(ac6_native_graphics_frames_in_flight, 2, "AC6/NativeGraphics",
|
||||
"Native renderer max frames in flight")
|
||||
.range(1, 4);
|
||||
|
||||
namespace ac6::graphics {
|
||||
namespace {
|
||||
|
||||
std::mutex g_native_graphics_mutex;
|
||||
ac6::renderer::NativeRenderer g_native_renderer;
|
||||
NativeGraphicsRuntimeStatus g_runtime_status{};
|
||||
|
||||
ac6::renderer::BackendType ParseBackend(std::string_view value) {
|
||||
if (value == "d3d12") {
|
||||
return ac6::renderer::BackendType::kD3D12;
|
||||
}
|
||||
if (value == "vulkan") {
|
||||
return ac6::renderer::BackendType::kVulkan;
|
||||
}
|
||||
if (value == "metal") {
|
||||
return ac6::renderer::BackendType::kMetal;
|
||||
}
|
||||
return ac6::renderer::BackendType::kUnknown;
|
||||
}
|
||||
|
||||
ac6::renderer::FeatureLevel ParseFeatureLevel(std::string_view value) {
|
||||
if (value == "bootstrap") {
|
||||
return ac6::renderer::FeatureLevel::kBootstrap;
|
||||
}
|
||||
if (value == "parity_validation") {
|
||||
return ac6::renderer::FeatureLevel::kParityValidation;
|
||||
}
|
||||
if (value == "shipping") {
|
||||
return ac6::renderer::FeatureLevel::kShipping;
|
||||
}
|
||||
return ac6::renderer::FeatureLevel::kSceneSubmission;
|
||||
}
|
||||
|
||||
ac6::renderer::NativeRendererConfig BuildRendererConfig() {
|
||||
ac6::renderer::NativeRendererConfig config;
|
||||
config.preferred_backend = ParseBackend(REXCVAR_GET(ac6_native_graphics_backend));
|
||||
config.feature_level = ParseFeatureLevel(REXCVAR_GET(ac6_native_graphics_feature_level));
|
||||
config.max_frames_in_flight = static_cast<uint32_t>(REXCVAR_GET(ac6_native_graphics_frames_in_flight));
|
||||
config.enable_debug_markers = true;
|
||||
config.enable_validation = true;
|
||||
return config;
|
||||
}
|
||||
|
||||
bool EnsureInitialized() {
|
||||
if (g_runtime_status.initialized) {
|
||||
return true;
|
||||
}
|
||||
|
||||
++g_runtime_status.init_attempts;
|
||||
const ac6::renderer::NativeRendererConfig config = BuildRendererConfig();
|
||||
if (!g_native_renderer.Initialize(config)) {
|
||||
g_runtime_status.had_init_failure = true;
|
||||
REXLOG_ERROR("AC6 native graphics failed to initialize backend={}",
|
||||
ac6::renderer::ToString(ac6::renderer::ResolveBackend(config.preferred_backend)));
|
||||
return false;
|
||||
}
|
||||
|
||||
g_runtime_status.initialized = true;
|
||||
g_runtime_status.had_init_failure = false;
|
||||
++g_runtime_status.init_successes;
|
||||
g_runtime_status.feature_level = config.feature_level;
|
||||
return true;
|
||||
}
|
||||
|
||||
void UpdateStatusFromRendererUnlocked() {
|
||||
g_runtime_status.renderer_stats = g_native_renderer.GetStats();
|
||||
g_runtime_status.active_backend = g_runtime_status.renderer_stats.active_backend;
|
||||
g_runtime_status.frame_plan = g_native_renderer.frame_plan();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void OnFrameBoundary() {
|
||||
std::scoped_lock<std::mutex> lock(g_native_graphics_mutex);
|
||||
|
||||
g_runtime_status.enabled = REXCVAR_GET(ac6_native_graphics_enabled);
|
||||
if (!g_runtime_status.enabled) {
|
||||
if (g_runtime_status.initialized) {
|
||||
g_native_renderer.Shutdown();
|
||||
g_runtime_status.initialized = false;
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
if (REXCVAR_GET(ac6_native_graphics_require_capture) && !REXCVAR_GET(ac6_render_capture)) {
|
||||
REXCVAR_SET(ac6_render_capture, true);
|
||||
}
|
||||
|
||||
if (!EnsureInitialized()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const ac6::d3d::FrameCaptureSnapshot frame_capture = ac6::d3d::GetFrameCapture();
|
||||
g_runtime_status.capture_summary = ac6::d3d::GetFrameCaptureSummary();
|
||||
|
||||
g_native_renderer.BeginFrame();
|
||||
g_native_renderer.BuildCapturedFrame(frame_capture);
|
||||
++g_runtime_status.frames_built;
|
||||
UpdateStatusFromRendererUnlocked();
|
||||
}
|
||||
|
||||
void Shutdown() {
|
||||
std::scoped_lock<std::mutex> lock(g_native_graphics_mutex);
|
||||
if (!g_runtime_status.initialized) {
|
||||
return;
|
||||
}
|
||||
g_native_renderer.Shutdown();
|
||||
g_runtime_status.initialized = false;
|
||||
}
|
||||
|
||||
NativeGraphicsRuntimeStatus GetRuntimeStatus() {
|
||||
std::scoped_lock<std::mutex> lock(g_native_graphics_mutex);
|
||||
return g_runtime_status;
|
||||
}
|
||||
|
||||
} // namespace ac6::graphics
|
||||
|
||||
|
||||
@@ -1 +1,32 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
#include "ac6_native_renderer/frame_plan.h"
|
||||
#include "ac6_native_renderer/types.h"
|
||||
#include "d3d_state.h"
|
||||
|
||||
namespace ac6::graphics {
|
||||
|
||||
struct NativeGraphicsRuntimeStatus {
|
||||
bool enabled = false;
|
||||
bool initialized = false;
|
||||
bool had_init_failure = false;
|
||||
uint64_t init_attempts = 0;
|
||||
uint64_t init_successes = 0;
|
||||
uint64_t frames_built = 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::d3d::FrameCaptureSummary capture_summary{};
|
||||
ac6::renderer::NativeFramePlan frame_plan{};
|
||||
};
|
||||
|
||||
void OnFrameBoundary();
|
||||
void Shutdown();
|
||||
|
||||
NativeGraphicsRuntimeStatus GetRuntimeStatus();
|
||||
|
||||
} // namespace ac6::graphics
|
||||
|
||||
|
||||
@@ -1 +1,57 @@
|
||||
|
||||
#include "ac6_native_graphics_overlay.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include "ac6_native_graphics.h"
|
||||
|
||||
namespace ac6::graphics {
|
||||
|
||||
NativeGraphicsStatusDialog::NativeGraphicsStatusDialog(rex::ui::ImGuiDrawer* imgui_drawer)
|
||||
: ImGuiDialog(imgui_drawer) {}
|
||||
|
||||
NativeGraphicsStatusDialog::~NativeGraphicsStatusDialog() = default;
|
||||
|
||||
void NativeGraphicsStatusDialog::OnDraw(ImGuiIO& io) {
|
||||
(void)io;
|
||||
if (!visible_) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!ImGui::Begin("AC6 Native Graphics##status", &visible_, ImGuiWindowFlags_NoCollapse)) {
|
||||
ImGui::End();
|
||||
return;
|
||||
}
|
||||
|
||||
const NativeGraphicsRuntimeStatus status = GetRuntimeStatus();
|
||||
ImGui::Text("enabled: %s", status.enabled ? "true" : "false");
|
||||
ImGui::Text("initialized: %s", status.initialized ? "true" : "false");
|
||||
ImGui::Text("init failures seen: %s", status.had_init_failure ? "true" : "false");
|
||||
ImGui::Text("init attempts/successes: %llu / %llu",
|
||||
static_cast<unsigned long long>(status.init_attempts),
|
||||
static_cast<unsigned long long>(status.init_successes));
|
||||
ImGui::Text("frames built: %llu", static_cast<unsigned long long>(status.frames_built));
|
||||
ImGui::Separator();
|
||||
ImGui::Text("backend: %s", ac6::renderer::ToString(status.active_backend).data());
|
||||
ImGui::Text("feature level: %s", ac6::renderer::ToString(status.feature_level).data());
|
||||
ImGui::Text("renderer frames: %llu",
|
||||
static_cast<unsigned long long>(status.renderer_stats.frame_count));
|
||||
ImGui::Text("render passes built: %llu",
|
||||
static_cast<unsigned long long>(status.renderer_stats.built_pass_count));
|
||||
ImGui::Separator();
|
||||
ImGui::Text("capture frame: %llu",
|
||||
static_cast<unsigned long long>(status.capture_summary.frame_index));
|
||||
ImGui::Text("capture draws/clears/resolves: %u / %u / %u",
|
||||
status.capture_summary.draw_count, status.capture_summary.clear_count,
|
||||
status.capture_summary.resolve_count);
|
||||
ImGui::Text("planned output: %ux%u", status.frame_plan.output_width,
|
||||
status.frame_plan.output_height);
|
||||
ImGui::Text("stages scene/post/ui: %s / %s / %s",
|
||||
status.frame_plan.has_scene_stage ? "yes" : "no",
|
||||
status.frame_plan.has_post_process_stage ? "yes" : "no",
|
||||
status.frame_plan.has_ui_stage ? "yes" : "no");
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
} // namespace ac6::graphics
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ class NativeGraphicsStatusDialog final : public rex::ui::ImGuiDialog {
|
||||
explicit NativeGraphicsStatusDialog(rex::ui::ImGuiDrawer* imgui_drawer);
|
||||
~NativeGraphicsStatusDialog();
|
||||
|
||||
void Show() { visible_ = true; }
|
||||
void ToggleVisible() { visible_ = !visible_; }
|
||||
bool IsVisible() const { return visible_; }
|
||||
|
||||
|
||||
+30
-1
@@ -1 +1,30 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <memory>
|
||||
|
||||
#include <rex/rex_app.h>
|
||||
|
||||
#include "ac6_native_graphics_overlay.h"
|
||||
#include "generated/ac6recomp_config.h"
|
||||
|
||||
class Ac6recompApp : public rex::ReXApp {
|
||||
public:
|
||||
using rex::ReXApp::ReXApp;
|
||||
|
||||
static std::unique_ptr<rex::ui::WindowedApp> Create(
|
||||
rex::ui::WindowedAppContext& ctx) {
|
||||
return std::unique_ptr<Ac6recompApp>(new Ac6recompApp(ctx, "ac6recomp", PPCImageConfig));
|
||||
}
|
||||
|
||||
protected:
|
||||
void OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) override {
|
||||
rex::ReXApp::OnCreateDialogs(drawer);
|
||||
native_graphics_status_dialog_ =
|
||||
std::make_unique<ac6::graphics::NativeGraphicsStatusDialog>(drawer);
|
||||
native_graphics_status_dialog_->Show();
|
||||
}
|
||||
|
||||
private:
|
||||
std::unique_ptr<ac6::graphics::NativeGraphicsStatusDialog> native_graphics_status_dialog_;
|
||||
};
|
||||
|
||||
|
||||
+19
-1
@@ -1 +1,19 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <rex/cvar.h>
|
||||
|
||||
#include "d3d_state.h"
|
||||
|
||||
REXCVAR_DECLARE(bool, ac6_render_capture);
|
||||
|
||||
namespace ac6::d3d {
|
||||
|
||||
void OnFrameBoundary();
|
||||
|
||||
DrawStatsSnapshot GetDrawStats();
|
||||
FrameCaptureSnapshot GetFrameCapture();
|
||||
FrameCaptureSummary GetFrameCaptureSummary();
|
||||
ShadowState GetShadowState();
|
||||
|
||||
} // namespace ac6::d3d
|
||||
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "render_hooks.h"
|
||||
#include "d3d_hooks.h"
|
||||
#include "ac6_native_graphics.h"
|
||||
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
@@ -47,6 +48,7 @@ void ac6DeltaDivisorHook(PPCRegister& r29) {
|
||||
|
||||
void ac6PresentTimingHook(PPCRegister& /*r31*/) {
|
||||
ac6::d3d::OnFrameBoundary();
|
||||
ac6::graphics::OnFrameBoundary();
|
||||
|
||||
const auto now = Clock::now();
|
||||
double frame_time_ms = 0.0;
|
||||
|
||||
Reference in New Issue
Block a user