mirror of
https://github.com/sal063/AC6_recomp
synced 2026-08-22 15:11:33 -04:00
deb406bdb0
Config presets now run after the toml loads: the ac6_fix_* switches and their scale gates finally honor user settings. The settings save persists only user-set values, with toml escaping - no leaked presets, and a saved Windows path can no longer void the whole config. Runtime-forced values (performance mode) can no longer overwrite the user's saved choices. Menu: wrapped tooltips, "(set by <cvar>)" on driven entries, a developer toggle; AC6-added cvars grouped under the AC6 node as AC6/Fixes and AC6/Enhancements, with true restart/live lifecycles.
276 lines
12 KiB
C++
276 lines
12 KiB
C++
|
|
// ac6recomp - ReXGlue Recompiled Project
|
|
//
|
|
// This file is yours to edit. 'rexglue migrate' will NOT overwrite it.
|
|
|
|
#include <rex/cvar.h>
|
|
#include <rex/logging.h>
|
|
|
|
REXCVAR_DECLARE(bool, ac6_render_capture);
|
|
REXCVAR_DECLARE(bool, ac6_unlock_fps);
|
|
REXCVAR_DECLARE(bool, ac6_native_graphics_enabled);
|
|
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);
|
|
REXCVAR_DECLARE(int32_t, draw_resolution_scale_x);
|
|
REXCVAR_DECLARE(int32_t, draw_resolution_scale_y);
|
|
REXCVAR_DECLARE(std::string, ac6_neutralize_deswizzle_hashes);
|
|
REXCVAR_DECLARE(std::string, ac6_snap_guest_texel_hashes);
|
|
REXCVAR_DECLARE(std::string, ac6_densify_x_fetch_hashes);
|
|
REXCVAR_DECLARE(std::string, ac6_densify_y_fetch_hashes);
|
|
REXCVAR_DECLARE(bool, param_gen_integer_guest_position);
|
|
REXCVAR_DECLARE(bool, param_gen_host_subpixel_restore);
|
|
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);
|
|
|
|
#if REX_HAS_VULKAN
|
|
#define AC6_DEFAULT_GRAPHICS_BACKEND "vulkan"
|
|
#elif REX_HAS_D3D12
|
|
#define AC6_DEFAULT_GRAPHICS_BACKEND "d3d12"
|
|
#else
|
|
#define AC6_DEFAULT_GRAPHICS_BACKEND "auto"
|
|
#endif
|
|
|
|
REXCVAR_DEFINE_STRING(ac6_graphics_backend, AC6_DEFAULT_GRAPHICS_BACKEND,
|
|
"AC6/NativeGraphics",
|
|
"Host graphics backend: vulkan, d3d12, or auto")
|
|
.allowed({"vulkan", "d3d12", "auto"})
|
|
.lifecycle(rex::cvar::Lifecycle::kInitOnly);
|
|
|
|
REXCVAR_DEFINE_BOOL(ac6_performance_mode, true, "AC6/Performance",
|
|
"Disable all diagnostics, logging, and development overlays for maximum runtime performance");
|
|
|
|
// Correctness-fix switches. Each expands to its detailed cvars + AC6-specific
|
|
// shader hashes in ApplyAc6FixDefaults() unless the user overrode them. The
|
|
// payloads apply at shader translation, so a change needs a restart.
|
|
REXCVAR_DEFINE_BOOL(ac6_fix_scaling, true, "AC6/Fixes",
|
|
"Fix the upscaling mosaic on deferred rendering passes. Only "
|
|
"active at resolution scale above 1x.")
|
|
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
|
REXCVAR_DEFINE_BOOL(ac6_fix_deswizzle, true, "AC6/Fixes",
|
|
"Fix mosaic and streak artifacts from the game's manual texture "
|
|
"de-swizzle. Effective at all resolution scales.")
|
|
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
|
REXCVAR_DEFINE_BOOL(ac6_fix_dof, true, "AC6/Fixes",
|
|
"Fix cutscene depth-of-field striping and ghosting at resolution "
|
|
"scale above 1x.")
|
|
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
|
|
|
#include "generated/ac6recomp_config.h"
|
|
#include "generated/ac6recomp_init.h"
|
|
|
|
#include <fstream>
|
|
#include <iostream>
|
|
|
|
#include "ac6recomp_app.h"
|
|
|
|
// Early boot log to catch crashes before the SDK logger is ready
|
|
std::ofstream g_boot_log;
|
|
|
|
namespace {
|
|
|
|
bool ShouldApplyAc6HybridStartupSafetyOverrides() {
|
|
return REXCVAR_GET(ac6_native_graphics_enabled) &&
|
|
REXCVAR_GET(ac6_graphics_mode) == "hybrid_backend_fixes";
|
|
}
|
|
|
|
void ApplyAc6HybridStartupSafetyOverrides() {
|
|
if (!ShouldApplyAc6HybridStartupSafetyOverrides()) {
|
|
return;
|
|
}
|
|
|
|
if (REXCVAR_GET(ac6_force_safe_draw_resolution_scale)) {
|
|
rex::cvar::SetSessionDefault("resolution_scale", "1",
|
|
"ac6_force_safe_draw_resolution_scale");
|
|
rex::cvar::SetSessionDefault("draw_resolution_scale_x", "1",
|
|
"ac6_force_safe_draw_resolution_scale");
|
|
rex::cvar::SetSessionDefault("draw_resolution_scale_y", "1",
|
|
"ac6_force_safe_draw_resolution_scale");
|
|
}
|
|
// (The >1x param-gen mosaic fix is applied by ApplyAc6FixDefaults via
|
|
// ac6_fix_scaling.)
|
|
|
|
if (REXCVAR_GET(ac6_force_safe_direct_host_resolve)) {
|
|
rex::cvar::SetSessionDefault("direct_host_resolve", "false",
|
|
"ac6_force_safe_direct_host_resolve");
|
|
}
|
|
}
|
|
|
|
void ApplyAc6DefaultSettings() {
|
|
rex::cvar::SetSessionDefault("vsync", "true");
|
|
rex::cvar::SetSessionDefault("guest_vblank_sync_to_refresh", "true");
|
|
rex::cvar::SetSessionDefault("host_present_from_non_ui_thread", "true");
|
|
#if REX_HAS_D3D12
|
|
rex::cvar::SetSessionDefault("d3d12_allow_variable_refresh_rate_and_tearing", "true");
|
|
rex::cvar::SetSessionDefault("d3d12_low_latency_swap_chain", "true");
|
|
rex::cvar::SetSessionDefault("d3d12_max_frame_latency", "1");
|
|
#endif
|
|
rex::cvar::SetSessionDefault("vfetch_index_rounding_bias", "true");
|
|
rex::cvar::SetSessionDefault("direct_host_resolve", "false");
|
|
rex::cvar::SetSessionDefault("video_mode_width", "1920");
|
|
rex::cvar::SetSessionDefault("video_mode_height", "1080");
|
|
rex::cvar::SetSessionDefault("resolution", "1080p");
|
|
rex::cvar::SetSessionDefault("window_width", "1920");
|
|
rex::cvar::SetSessionDefault("window_height", "1080");
|
|
}
|
|
|
|
void ApplyAc6PerformanceModeOverrides() {
|
|
if (REXCVAR_GET(ac6_performance_mode)) {
|
|
// Session-default RECORDING: gives the settings menu the "(set by
|
|
// ac6_performance_mode)" annotation and the boot-time value. The
|
|
// actual enforcement stays the pre-existing per-frame re-assert
|
|
// (EnforceAc6PerformanceModeOverrides below) - these cvars predate
|
|
// this rework and keep their upstream semantics: performance mode
|
|
// forces them off while enabled.
|
|
rex::cvar::SetSessionDefault("log_level", "error", "ac6_performance_mode");
|
|
rex::cvar::SetSessionDefault("ac6_d3d_trace", "false", "ac6_performance_mode");
|
|
rex::cvar::SetSessionDefault("ac6_render_capture", "false", "ac6_performance_mode");
|
|
rex::cvar::SetSessionDefault("ac6_backend_debug_swap", "false", "ac6_performance_mode");
|
|
rex::cvar::SetSessionDefault("ac6_backend_log_signatures", "false",
|
|
"ac6_performance_mode");
|
|
rex::cvar::SetSessionDefault("ac6_backend_signature_diagnostics", "false",
|
|
"ac6_performance_mode");
|
|
rex::cvar::SetSessionDefault("ac6_texture_swaps_dump_enabled", "false",
|
|
"ac6_performance_mode");
|
|
} else {
|
|
rex::cvar::SetSessionDefault("log_level", "debug");
|
|
}
|
|
}
|
|
|
|
// The pre-existing enforcement, unchanged from before this rework: while
|
|
// performance mode is on, the diagnostics are re-forced off every frame from
|
|
// the graphics overlay. These writes go through REXCVAR_SET (raw storage), so
|
|
// they no longer leak into a settings-menu save - the save writes the user's
|
|
// own values (FlagEntry::user_value).
|
|
void EnforceAc6PerformanceModeOverrides() {
|
|
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);
|
|
}
|
|
|
|
void ApplyAc6FixDefaults() {
|
|
// Post-config, these read the USER's draw scale - with the old app-create
|
|
// timing this gate always saw the default 1x, so the scaling fix payload
|
|
// never engaged for anyone who set their scale in the toml.
|
|
const bool scaled = REXCVAR_GET(draw_resolution_scale_x) > 1 ||
|
|
REXCVAR_GET(draw_resolution_scale_y) > 1;
|
|
|
|
// param_gen floor + host sub-pixel restore only bites at >1x --
|
|
// inert at 1x, so gate it on the draw scale.
|
|
if (REXCVAR_GET(ac6_fix_scaling) && scaled) {
|
|
rex::cvar::SetSessionDefault("param_gen_integer_guest_position", "true",
|
|
"ac6_fix_scaling");
|
|
rex::cvar::SetSessionDefault("param_gen_host_subpixel_restore", "true",
|
|
"ac6_fix_scaling");
|
|
}
|
|
// de-swizzle identity override is a texture-layout mismatch present
|
|
// at ALL draw scales, so it is NOT gated on scale.
|
|
if (REXCVAR_GET(ac6_fix_deswizzle)) {
|
|
rex::cvar::SetSessionDefault("ac6_neutralize_deswizzle_hashes",
|
|
"7d22894002d16018, 17e5e4ac3e713245:4",
|
|
"ac6_fix_deswizzle");
|
|
}
|
|
// Cutscene DoF chain (CoC pre-blur pair snapped to the guest grid; the
|
|
// two 13-tap gathers densified along their pass axes). The translator
|
|
// only emits these at draw scale > 1, so no scale gate is needed here.
|
|
if (REXCVAR_GET(ac6_fix_dof)) {
|
|
rex::cvar::SetSessionDefault("ac6_snap_guest_texel_hashes",
|
|
"85d733927e3bba9c, d050dfa6f58567f6", "ac6_fix_dof");
|
|
rex::cvar::SetSessionDefault("ac6_densify_x_fetch_hashes", "6328f9c40913c82c",
|
|
"ac6_fix_dof");
|
|
rex::cvar::SetSessionDefault("ac6_densify_y_fetch_hashes", "5bd20f9d0d911687",
|
|
"ac6_fix_dof");
|
|
}
|
|
}
|
|
|
|
} // namespace
|
|
|
|
// The whole preset suite. Called from Ac6recompApp::OnConfigLoaded - AFTER
|
|
// rex::cvar::LoadConfig parsed the user's toml (so the master switches and
|
|
// gates read the user's real values, fixing the "ac6_fix_* dead from the
|
|
// toml" bug) and BEFORE anything consumes cvars (logging, window, graphics).
|
|
// Everything goes through SetSessionDefault: a user-set value always wins,
|
|
// and none of these values leak into the user's config on a settings save.
|
|
// The game-specific fix constants stay here in code, not in the user's toml.
|
|
void ApplyAc6ConfigPresets() {
|
|
rex::cvar::SetSessionDefault("log_file", "ac6recomp.log");
|
|
ApplyAc6DefaultSettings();
|
|
ApplyAc6HybridStartupSafetyOverrides();
|
|
ApplyAc6FixDefaults();
|
|
ApplyAc6PerformanceModeOverrides();
|
|
// Runtime enforcement of performance mode stays the pre-existing
|
|
// per-frame re-assert from the graphics overlay (see
|
|
// ApplyAc6PerformanceModeOverridesPublic).
|
|
}
|
|
|
|
void ApplyAc6PerformanceModeOverridesPublic() {
|
|
EnforceAc6PerformanceModeOverrides();
|
|
}
|
|
|
|
// One greppable summary of what the presets decided, logged from OnPreSetup
|
|
// (the presets themselves run before logging is initialized). Logged at
|
|
// error level so it survives performance mode's log_level=error session
|
|
// default - the same reason the widescreen activation line logs at error.
|
|
// This is the support line: it shows whether a master switch genuinely
|
|
// disengaged its payload.
|
|
void LogAc6ConfigPresetSummary() {
|
|
REXLOG_ERROR("AC6 config: graphics mode={} capture={}", REXCVAR_GET(ac6_graphics_mode),
|
|
REXCVAR_GET(ac6_render_capture) ? "true" : "false");
|
|
REXLOG_ERROR("AC6 fixes: scaling={} deswizzle={} dof={} perf_mode={} unlock_fps={}",
|
|
REXCVAR_GET(ac6_fix_scaling), REXCVAR_GET(ac6_fix_deswizzle),
|
|
REXCVAR_GET(ac6_fix_dof), REXCVAR_GET(ac6_performance_mode),
|
|
REXCVAR_GET(ac6_unlock_fps));
|
|
REXLOG_ERROR("AC6 fix payloads: neutralize='{}' snap='{}' densify_x='{}' densify_y='{}' "
|
|
"param_gen={}/{}",
|
|
REXCVAR_GET(ac6_neutralize_deswizzle_hashes),
|
|
REXCVAR_GET(ac6_snap_guest_texel_hashes),
|
|
REXCVAR_GET(ac6_densify_x_fetch_hashes),
|
|
REXCVAR_GET(ac6_densify_y_fetch_hashes),
|
|
REXCVAR_GET(param_gen_integer_guest_position),
|
|
REXCVAR_GET(param_gen_host_subpixel_restore));
|
|
}
|
|
|
|
void InitEarlyLog() {
|
|
g_boot_log.open("boot.log", std::ios::out | std::ios::trunc);
|
|
if (g_boot_log.is_open()) {
|
|
g_boot_log << "AC6 Recompiled Early Boot Log" << std::endl;
|
|
g_boot_log << "-----------------------------" << std::endl;
|
|
g_boot_log.flush();
|
|
}
|
|
std::cout << "Early boot logging initialized." << std::endl;
|
|
}
|
|
|
|
std::unique_ptr<rex::ui::WindowedApp> Ac6recompAppCreate(rex::ui::WindowedAppContext& ctx) {
|
|
if (g_boot_log.is_open()) {
|
|
g_boot_log << "Ac6recompApp::Create called" << std::endl;
|
|
g_boot_log.flush();
|
|
}
|
|
|
|
// No cvar presets here: this runs BEFORE rex::cvar::LoadConfig parses
|
|
// the toml, so anything applied here reads defaults, not user values.
|
|
// The preset suite lives in ApplyAc6ConfigPresets, called from
|
|
// Ac6recompApp::OnConfigLoaded.
|
|
|
|
return Ac6recompApp::Create(ctx);
|
|
}
|
|
|
|
REX_DEFINE_APP(ac6recomp, Ac6recompAppCreate)
|
|
|
|
// Hook into static initialization to start log early
|
|
struct EarlyBoot {
|
|
EarlyBoot() {
|
|
InitEarlyLog();
|
|
}
|
|
} g_early_boot;
|