add ultrawide and live FOV support
This commit is contained in:
parent
ec224f7d66
commit
1ed2841c5c
15 changed files with 8042 additions and 28 deletions
5
.gitignore
vendored
5
.gitignore
vendored
|
|
@ -2,6 +2,8 @@
|
|||
/out/
|
||||
/build/
|
||||
/generated/
|
||||
/generated.backup-*/
|
||||
/tools/
|
||||
|
||||
# Local game dump and retail media. Users must provide their own copy.
|
||||
/game/
|
||||
|
|
@ -29,3 +31,6 @@ CMakeUserPresets.json
|
|||
/.vs/
|
||||
/.vscode/
|
||||
/.cache/
|
||||
/scripts/__pycache__/
|
||||
/scripts/extract_eb_big.py
|
||||
/scripts/extract_embedded_rx2.py
|
||||
|
|
|
|||
|
|
@ -107,6 +107,7 @@ include(cmake/CodegenTargets.cmake)
|
|||
set(SKATE3_COMMON_SOURCES
|
||||
src/exception_compat.cpp
|
||||
src/skate3_app_common.cpp
|
||||
src/skate3_fov.cpp
|
||||
src/skate3_iso_installer.cpp
|
||||
src/skate3_user_settings.cpp
|
||||
)
|
||||
|
|
|
|||
17
README.md
17
README.md
|
|
@ -48,16 +48,13 @@ Gameplay (click to go to YouTube):
|
|||
6. Wait for the installer to extract the game files.
|
||||
7. Click "Start Game".
|
||||
|
||||
### DLC
|
||||
## Installing DLC
|
||||
|
||||
Skate3Recomp does not include Skate 3 DLC files. To use DLC, provide package
|
||||
files from your own legally obtained Xbox 360 DLC.
|
||||
To use DLC, you must provide package files from your own legally obtained Xbox 360 DLC.
|
||||
|
||||
Create a `dlc` folder beside the executable, inside the installed game folder,
|
||||
or in the Skate3Recomp user data folder. Place the DLC package files in that
|
||||
folder and start the game. Skate3Recomp will validate matching Skate 3
|
||||
marketplace packages and install them into the local content store
|
||||
automatically.
|
||||
Create a `dlc` folder either beside the executable, inside the installed game folder,
|
||||
or in the user data folder. Place the DLC package files in that
|
||||
folder and start the game.
|
||||
|
||||
## Controls
|
||||
|
||||
|
|
@ -201,6 +198,10 @@ SKATE3_MNK=1 ./scripts/run-linux.sh
|
|||
```
|
||||
|
||||
Fullscreen is on by default. Pass `--no-fullscreen` to start windowed.
|
||||
On Windows, ultrawide displays are detected automatically in fullscreen. For an
|
||||
ultrawide window, pass matching `--window_width` and `--window_height` values;
|
||||
the renderer keeps the guest frontbuffer at a stable 16:9 size while applying
|
||||
ultrawide presentation and Hor+ correction without stretching.
|
||||
|
||||
To run the Linux development executable directly:
|
||||
|
||||
|
|
|
|||
86
cmake/ApplySkate3CodegenPatches.cmake
Normal file
86
cmake/ApplySkate3CodegenPatches.cmake
Normal file
|
|
@ -0,0 +1,86 @@
|
|||
if(NOT DEFINED SKATE3_SOURCE_DIR)
|
||||
message(FATAL_ERROR "SKATE3_SOURCE_DIR is required")
|
||||
endif()
|
||||
|
||||
set(_skate3_recomp_39 "${SKATE3_SOURCE_DIR}/generated/skate3_recomp.39.cpp")
|
||||
if(NOT EXISTS "${_skate3_recomp_39}")
|
||||
message(FATAL_ERROR "Expected generated file not found: ${_skate3_recomp_39}")
|
||||
endif()
|
||||
|
||||
file(READ "${_skate3_recomp_39}" _contents)
|
||||
|
||||
if(NOT _contents MATCHES "#include \"skate3_ultrawide_guest\\.h\"")
|
||||
string(REPLACE
|
||||
"#include \"skate3_init.h\"\n"
|
||||
"#include \"skate3_init.h\"\n#include \"skate3_ultrawide_guest.h\"\n"
|
||||
_contents
|
||||
"${_contents}")
|
||||
endif()
|
||||
|
||||
set(_frustum_call
|
||||
" // bl 0x827f2330
|
||||
ctx.lr = 0x827F2AE8;
|
||||
sub_827F2330(ctx, base);")
|
||||
|
||||
set(_frustum_patch
|
||||
" // bl 0x827f2330
|
||||
ctx.lr = 0x827F2AE8;
|
||||
Skate3UltrawideGameFrustumPatchScope skate3_ultrawide_game_frustum_patch_scope(
|
||||
ctx, base, ctx.r4.u32);
|
||||
sub_827F2330(ctx, base);")
|
||||
|
||||
if(_contents MATCHES "Skate3UltrawideGameFrustumPatchScope")
|
||||
message(STATUS "Skate 3 generated frustum patch already applied")
|
||||
elseif(_contents MATCHES "sub_827F2330\\(ctx, base\\);")
|
||||
string(REPLACE "${_frustum_call}" "${_frustum_patch}" _patched "${_contents}")
|
||||
if(_patched STREQUAL _contents)
|
||||
message(FATAL_ERROR "Failed to apply Skate 3 generated frustum patch; expected call-site context changed")
|
||||
endif()
|
||||
set(_contents "${_patched}")
|
||||
else()
|
||||
message(FATAL_ERROR "Failed to apply Skate 3 generated frustum patch; call to sub_827F2330 not found")
|
||||
endif()
|
||||
|
||||
file(WRITE "${_skate3_recomp_39}" "${_contents}")
|
||||
message(STATUS "Applied Skate 3 generated frustum patch")
|
||||
|
||||
set(_skate3_recomp_38 "${SKATE3_SOURCE_DIR}/generated/skate3_recomp.38.cpp")
|
||||
if(NOT EXISTS "${_skate3_recomp_38}")
|
||||
message(FATAL_ERROR "Expected generated file not found: ${_skate3_recomp_38}")
|
||||
endif()
|
||||
|
||||
file(READ "${_skate3_recomp_38}" _contents)
|
||||
|
||||
if(NOT _contents MATCHES "#include \"skate3_fov\\.h\"")
|
||||
string(REPLACE
|
||||
"#include \"skate3_init.h\"\n"
|
||||
"#include \"skate3_init.h\"\n#include \"skate3_fov.h\"\n"
|
||||
_contents
|
||||
"${_contents}")
|
||||
endif()
|
||||
|
||||
set(_projection_fov_site
|
||||
"DEFINE_REX_FUNC(sub_827DE960) {
|
||||
REX_FUNC_PROLOGUE();
|
||||
PPCRegister temp{};
|
||||
uint32_t ea{};")
|
||||
|
||||
set(_projection_fov_patch
|
||||
"DEFINE_REX_FUNC(sub_827DE960) {
|
||||
REX_FUNC_PROLOGUE();
|
||||
PPCRegister temp{};
|
||||
uint32_t ea{};
|
||||
ctx.f1.f64 = double(Skate3MaybeOverrideProjectionFovRadians(float(ctx.f1.f64)));")
|
||||
|
||||
if(_contents MATCHES "Skate3MaybeOverrideProjectionFovRadians")
|
||||
message(STATUS "Skate 3 generated projection FOV patch already applied")
|
||||
else()
|
||||
string(REPLACE "${_projection_fov_site}" "${_projection_fov_patch}" _patched "${_contents}")
|
||||
if(_patched STREQUAL _contents)
|
||||
message(FATAL_ERROR "Failed to apply Skate 3 generated projection FOV patch; expected function prologue changed")
|
||||
endif()
|
||||
set(_contents "${_patched}")
|
||||
endif()
|
||||
|
||||
file(WRITE "${_skate3_recomp_38}" "${_contents}")
|
||||
message(STATUS "Applied Skate 3 generated projection FOV patch")
|
||||
|
|
@ -1,6 +1,9 @@
|
|||
add_custom_target(generate-skate3
|
||||
COMMAND $<TARGET_FILE:rex::rexglue> codegen
|
||||
"${CMAKE_CURRENT_BINARY_DIR}/manifests/skate3.toml"
|
||||
COMMAND "${CMAKE_COMMAND}"
|
||||
"-DSKATE3_SOURCE_DIR=${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
-P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/ApplySkate3CodegenPatches.cmake"
|
||||
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||
COMMENT "Generating recompiled code for default.xex"
|
||||
VERBATIM
|
||||
|
|
|
|||
|
|
@ -16,6 +16,20 @@ mnk_mode = false
|
|||
mnk_capture_mouse = false
|
||||
|
||||
fullscreen = true
|
||||
# Automatically derive an ultrawide host aspect from the fullscreen monitor on
|
||||
# Windows, or from an explicit window_width/window_height pair. Leave
|
||||
# video_mode_width and video_mode_height at defaults for this path.
|
||||
skate3_ultrawide = false
|
||||
skate3_field_of_view = 60.0
|
||||
skate3_ultrawide_base_height = 720
|
||||
skate3_ultrawide_hor_plus = true
|
||||
skate3_ultrawide_hor_plus_scale = 0.0
|
||||
# Risky diagnostics for narrowing texture/visibility pop-in. Enable one at a
|
||||
# time when testing.
|
||||
skate3_ultrawide_force_main_visibility_flags = true
|
||||
skate3_ultrawide_ignore_streamer_texture_invalidations = false
|
||||
# window_width = 3440
|
||||
# window_height = 1440
|
||||
vsync = false
|
||||
video_mode_refresh_rate = 60.0
|
||||
resolution_scale = 2
|
||||
|
|
|
|||
|
|
@ -158,8 +158,15 @@ def render_rx2(
|
|||
return output_path
|
||||
|
||||
|
||||
def display_source_name(source: Path, root: Path | None) -> str:
|
||||
try:
|
||||
return str(source.relative_to(root)) if root else str(source)
|
||||
except ValueError:
|
||||
return str(source)
|
||||
|
||||
|
||||
def write_texture_page(
|
||||
rendered: list[tuple[Path, Path]],
|
||||
rendered: list[tuple[Path, Path, str]],
|
||||
output_dir: Path,
|
||||
page_number: int,
|
||||
page_count: int,
|
||||
|
|
@ -182,18 +189,18 @@ def write_texture_page(
|
|||
if page_number < page_count:
|
||||
rows.append(f"<a href='page-{page_number + 1:03d}.html'>Next</a>")
|
||||
rows.append("</div><div class='grid'>")
|
||||
for source, image in rendered:
|
||||
for source, image, label in rendered:
|
||||
rel_image = image.relative_to(output_dir)
|
||||
rows.append(
|
||||
f"<div class='item'><img loading='lazy' src='{html.escape(str(rel_image))}'>"
|
||||
f"<p><code>{html.escape(source.name)}</code></p></div>"
|
||||
f"<p><code>{html.escape(label)}</code></p></div>"
|
||||
)
|
||||
rows.append("</div>")
|
||||
(output_dir / page_name).write_text("\n".join(rows), encoding="utf-8")
|
||||
return page_name
|
||||
|
||||
|
||||
def write_index(rendered: list[tuple[Path, Path]], output_dir: Path, page_size: int) -> None:
|
||||
def write_index(rendered: list[tuple[Path, Path, str]], output_dir: Path, page_size: int) -> None:
|
||||
page_size = max(1, page_size)
|
||||
pages = [rendered[offset : offset + page_size] for offset in range(0, len(rendered), page_size)]
|
||||
page_names = [
|
||||
|
|
@ -297,11 +304,11 @@ def main() -> int:
|
|||
output_dir = Path(args.output_dir)
|
||||
output_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
rendered: list[tuple[Path, Path]] = []
|
||||
rendered: list[tuple[Path, Path, str]] = []
|
||||
for source in sources:
|
||||
image = render_rx2(source, output_dir, source_root, args.layout)
|
||||
if image:
|
||||
rendered.append((source, image))
|
||||
rendered.append((source, image, display_source_name(source, source_root)))
|
||||
if args.verbose:
|
||||
print(f"Rendered {source.name}")
|
||||
if args.limit and len(rendered) >= args.limit:
|
||||
|
|
|
|||
3444
skate3_ultrawide_targets.toml
Normal file
3444
skate3_ultrawide_targets.toml
Normal file
File diff suppressed because it is too large
Load diff
3558
skate3_ultrawide_targets_4k.toml
Normal file
3558
skate3_ultrawide_targets_4k.toml
Normal file
File diff suppressed because it is too large
Load diff
|
|
@ -1,5 +1,6 @@
|
|||
#include "skate3_app_common.h"
|
||||
|
||||
#include "skate3_fov.h"
|
||||
#include "skate3_iso_installer.h"
|
||||
#include "skate3_user_settings.h"
|
||||
|
||||
|
|
@ -12,6 +13,7 @@
|
|||
#include <fstream>
|
||||
#include <iomanip>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
|
|
@ -39,6 +41,8 @@
|
|||
#include <rex/filesystem/devices/stfs_container_device.h>
|
||||
#include <rex/filesystem/devices/host_path_device.h>
|
||||
#include <rex/filesystem/vfs.h>
|
||||
#include <rex/graphics/flags.h>
|
||||
#include <rex/graphics/ultrawide_debug.h>
|
||||
#include <rex/input/input_system.h>
|
||||
#include <rex/kernel/xam/module.h>
|
||||
#include <rex/logging.h>
|
||||
|
|
@ -51,8 +55,10 @@
|
|||
#include <rex/system/xam/content_manager.h>
|
||||
#include <rex/system/xam/user_profile.h>
|
||||
#include <rex/system.h>
|
||||
#include <rex/ui/flags.h>
|
||||
#include <rex/ui/keybinds.h>
|
||||
#include <rex/ui/overlay/simple_settings_overlay.h>
|
||||
#include <rex/ui/overlay/ultrawide_targets_overlay.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <toml++/toml.hpp>
|
||||
|
|
@ -71,6 +77,46 @@ REXCVAR_DEFINE_STRING(skate3_dlc_root, "", "Skate 3",
|
|||
"Directory containing Skate 3 DLC package files");
|
||||
REXCVAR_DEFINE_BOOL(skate3_auto_install_dlc, true, "Skate 3",
|
||||
"Install DLC package files found in configured DLC folders");
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide, false, "Skate 3",
|
||||
"Automatically derive an ultrawide guest video mode from the host display");
|
||||
REXCVAR_DEFINE_DOUBLE(skate3_field_of_view, 60.0, "Skate 3",
|
||||
"Gameplay camera field of view in degrees")
|
||||
.range(40.0, 120.0);
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_base_height, 720, "Skate 3",
|
||||
"Guest video mode height used when deriving ultrawide modes")
|
||||
.range(480, 2160)
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_hor_plus, true, "Skate 3",
|
||||
"Apply Hor+ clip-space correction for ultrawide video modes");
|
||||
REXCVAR_DEFINE_DOUBLE(skate3_ultrawide_hor_plus_scale, 0.0, "Skate 3",
|
||||
"Manual Hor+ X scale override (0 = derive from host aspect)")
|
||||
.range(0.0, 4.0)
|
||||
.lifecycle(rex::cvar::Lifecycle::kRequiresRestart);
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_disable_ndc_correction, false, "Skate 3",
|
||||
"Diagnostic: disable the GPU NDC Hor+ correction while preserving guest-side "
|
||||
"ultrawide diagnostics");
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_trace_draws, false, "Skate 3",
|
||||
"Diagnostic: collect live draw fingerprints for the F7 ultrawide overlay");
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_object_stage_trace_to_disk, false, "Skate 3",
|
||||
"Diagnostic: write object render-stage transition summaries to disk");
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_object_stage_trace_continuous, false, "Skate 3",
|
||||
"Diagnostic: continuously trace object render-stage summaries");
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_object_stage_trace_frames_remaining, 0, "Skate 3",
|
||||
"Diagnostic: trace this many object render-stage frames")
|
||||
.range(0, 1000000);
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_object_stage_marker, 0, "Skate 3",
|
||||
"Diagnostic: mark the next object-stage frame (1 visible, 2 invisible)")
|
||||
.range(0, 2);
|
||||
REXCVAR_DEFINE_INT32(skate3_ultrawide_object_stage_missing_limit, 256, "Skate 3",
|
||||
"Diagnostic: maximum missing-main objects to log per object-stage frame")
|
||||
.range(0, 4096);
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_force_main_visibility_flags, false, "Skate 3",
|
||||
"Force main render object visibility flags while Hor+ ultrawide is active");
|
||||
REXCVAR_DEFINE_BOOL(skate3_ultrawide_widen_game_frustum, true, "Skate 3",
|
||||
"Widen the game-side main-world cull frustum to match the ultrawide view");
|
||||
REXCVAR_DEFINE_DOUBLE(skate3_ultrawide_target_aspect, 0.0, "Skate 3",
|
||||
"Derived host display aspect for ultrawide presentation (0 = disabled)")
|
||||
.range(0.0, 8.0);
|
||||
|
||||
namespace {
|
||||
|
||||
|
|
@ -137,6 +183,141 @@ void SetRestartArgument(std::vector<std::string>& args, std::string name, std::s
|
|||
constexpr std::string_view kUserDirectoryName = "skate3";
|
||||
constexpr std::string_view kSettingsFilename = "settings.toml";
|
||||
constexpr std::string_view kDlcDirectoryName = "dlc";
|
||||
constexpr double kSixteenNineAspect = 16.0 / 9.0;
|
||||
constexpr double kUltrawideAspectEpsilon = 0.01;
|
||||
|
||||
struct DisplaySize {
|
||||
int32_t width = 0;
|
||||
int32_t height = 0;
|
||||
};
|
||||
|
||||
#if defined(_WIN32)
|
||||
BOOL CALLBACK CollectMonitorCallback(HMONITOR monitor_handle, HDC, LPRECT, LPARAM data) {
|
||||
auto* monitors = reinterpret_cast<std::vector<HMONITOR>*>(data);
|
||||
monitors->push_back(monitor_handle);
|
||||
return TRUE;
|
||||
}
|
||||
|
||||
HMONITOR GetConfiguredMonitorHandle() {
|
||||
const int32_t monitor_index = REXCVAR_GET(monitor);
|
||||
if (monitor_index > 0) {
|
||||
std::vector<HMONITOR> monitors;
|
||||
EnumDisplayMonitors(nullptr, nullptr, CollectMonitorCallback,
|
||||
reinterpret_cast<LPARAM>(&monitors));
|
||||
if (monitor_index <= static_cast<int32_t>(monitors.size())) {
|
||||
return monitors[monitor_index - 1];
|
||||
}
|
||||
}
|
||||
|
||||
POINT origin{0, 0};
|
||||
return MonitorFromPoint(origin, MONITOR_DEFAULTTOPRIMARY);
|
||||
}
|
||||
|
||||
std::optional<DisplaySize> QueryFullscreenMonitorSize() {
|
||||
HMONITOR monitor_handle = GetConfiguredMonitorHandle();
|
||||
if (!monitor_handle) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
MONITORINFO monitor_info{};
|
||||
monitor_info.cbSize = sizeof(monitor_info);
|
||||
if (!GetMonitorInfo(monitor_handle, &monitor_info)) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
const int32_t width = monitor_info.rcMonitor.right - monitor_info.rcMonitor.left;
|
||||
const int32_t height = monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top;
|
||||
if (width <= 0 || height <= 0) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return DisplaySize{width, height};
|
||||
}
|
||||
#else
|
||||
std::optional<DisplaySize> QueryFullscreenMonitorSize() {
|
||||
return std::nullopt;
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<DisplaySize> ResolveUltrawideTargetDisplaySize() {
|
||||
if (rex::cvar::HasNonDefaultValue("resolution")) {
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
if (REXCVAR_GET(fullscreen)) {
|
||||
return QueryFullscreenMonitorSize();
|
||||
}
|
||||
|
||||
const int32_t configured_window_width = REXCVAR_GET(window_width);
|
||||
const int32_t configured_window_height = REXCVAR_GET(window_height);
|
||||
if (rex::cvar::HasNonDefaultValue("window_width") &&
|
||||
rex::cvar::HasNonDefaultValue("window_height") && configured_window_width > 0 &&
|
||||
configured_window_height > 0) {
|
||||
return DisplaySize{configured_window_width, configured_window_height};
|
||||
}
|
||||
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
void ApplyUltrawideVideoDefaults() {
|
||||
if (!REXCVAR_GET(skate3_ultrawide) ||
|
||||
rex::cvar::HasNonDefaultValue("skate3_ultrawide_target_aspect")) {
|
||||
return;
|
||||
}
|
||||
|
||||
const std::optional<DisplaySize> target_size = ResolveUltrawideTargetDisplaySize();
|
||||
if (!target_size || target_size->width <= 0 || target_size->height <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double target_aspect =
|
||||
static_cast<double>(target_size->width) / static_cast<double>(target_size->height);
|
||||
if (target_aspect <= kSixteenNineAspect + kUltrawideAspectEpsilon) {
|
||||
return;
|
||||
}
|
||||
|
||||
rex::cvar::SetFlagByName("skate3_ultrawide_target_aspect", std::to_string(target_aspect));
|
||||
|
||||
if (!rex::cvar::HasNonDefaultValue("present_letterbox")) {
|
||||
rex::cvar::SetFlagByName("present_letterbox", "true");
|
||||
}
|
||||
}
|
||||
|
||||
void DisableActiveUltrawideDiagnostics() {
|
||||
constexpr std::string_view kFalseFlags[] = {
|
||||
"skate3_ultrawide_trace_draws",
|
||||
"skate3_ultrawide_object_stage_trace_to_disk",
|
||||
"skate3_ultrawide_object_stage_trace_continuous",
|
||||
"skate3_ultrawide_force_main_visibility_flags",
|
||||
"skate3_ultrawide_texture_trace_bind_keys",
|
||||
"skate3_ultrawide_texture_trace_scaled_resolve",
|
||||
"skate3_ultrawide_ignore_streamer_texture_invalidations",
|
||||
"skate3_ultrawide_screen_callback_tracking",
|
||||
"skate3_ultrawide_fake_occlusion_queries",
|
||||
"perf_draw_fingerprints",
|
||||
"perf_keep_heavyweight_draw_diagnostics",
|
||||
"trace_gpu_stream",
|
||||
};
|
||||
for (std::string_view flag : kFalseFlags) {
|
||||
rex::cvar::SetFlagByName(std::string(flag), "false");
|
||||
}
|
||||
|
||||
constexpr std::string_view kZeroFlags[] = {
|
||||
"skate3_ultrawide_texture_trace_remaining",
|
||||
"skate3_ultrawide_object_stage_trace_frames_remaining",
|
||||
"skate3_ultrawide_object_stage_marker",
|
||||
"vulkan_debug_log_frame_summaries_remaining",
|
||||
"vulkan_debug_log_resolve_decisions_remaining",
|
||||
"vulkan_debug_log_team_profile_background_candidates_remaining",
|
||||
"vulkan_debug_log_team_profile_background_bindings_remaining",
|
||||
"filesystem_debug_log_fe_asset_ops_remaining",
|
||||
"filesystem_debug_log_team_profile_background_remaining",
|
||||
};
|
||||
for (std::string_view flag : kZeroFlags) {
|
||||
rex::cvar::SetFlagByName(std::string(flag), "0");
|
||||
}
|
||||
|
||||
rex::cvar::SetFlagByName("skate3_ultrawide_fake_occlusion_sample_count", "1000");
|
||||
}
|
||||
|
||||
std::filesystem::path DefaultDocumentsUserRoot() {
|
||||
return rex::filesystem::GetUserFolder() / std::string(kUserDirectoryName);
|
||||
|
|
@ -342,6 +523,13 @@ void Skate3BaseApp::OnConfigurePaths(rex::PathConfig& paths) {
|
|||
ConfigureSkate3UserPaths(paths, user_settings_path_, profiles_path_);
|
||||
config_path_ = paths.config_path;
|
||||
LoadAndNormalizeSimpleSettings(user_settings_path_, config_path_);
|
||||
Skate3InitializeFieldOfViewOverride();
|
||||
ApplyUltrawideVideoDefaults();
|
||||
if (!rex::graphics::ultrawide_debug::LoadTargets(paths.cache_root /
|
||||
"skate3_ultrawide_targets.toml")) {
|
||||
rex::graphics::ultrawide_debug::LoadBuiltInSkate3Classifier();
|
||||
}
|
||||
DisableActiveUltrawideDiagnostics();
|
||||
}
|
||||
|
||||
void Skate3BaseApp::OnConfigureFonts(ImFontAtlas* atlas) {
|
||||
|
|
@ -449,6 +637,10 @@ void Skate3BaseApp::OnCreateDialogs(rex::ui::ImGuiDrawer* drawer) {
|
|||
rex::ui::RegisterBind("bind_skate3_menu_alt", "F1", "Skate 3 settings alternate", [this] {
|
||||
ToggleSimpleSettings();
|
||||
});
|
||||
rex::ui::RegisterBind("bind_skate3_ultrawide_targets", "F7",
|
||||
"Skate 3 ultrawide targets", [this] {
|
||||
ToggleUltrawideTargets();
|
||||
});
|
||||
rex::ui::RegisterBind("bind_skate3_save_draw_fingerprints", "F8",
|
||||
"Save draw fingerprint log", [this] {
|
||||
SaveDrawFingerprintLog();
|
||||
|
|
@ -506,11 +698,13 @@ void Skate3BaseApp::OnPostSetup() {
|
|||
void Skate3BaseApp::OnShutdown() {
|
||||
rex::ui::UnregisterBind("bind_skate3_menu");
|
||||
rex::ui::UnregisterBind("bind_skate3_menu_alt");
|
||||
rex::ui::UnregisterBind("bind_skate3_ultrawide_targets");
|
||||
rex::ui::UnregisterBind("bind_skate3_save_draw_fingerprints");
|
||||
rex::ui::UnregisterBind("bind_skate3_log_debug_marker");
|
||||
rex::ui::UnregisterBind("bind_skate3_log_user_marker");
|
||||
ApplyGameplayCursorMode();
|
||||
simple_settings_dialog_.reset();
|
||||
ultrawide_targets_dialog_.reset();
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ToggleSimpleSettings() {
|
||||
|
|
@ -588,6 +782,24 @@ void Skate3BaseApp::ToggleSimpleSettings() {
|
|||
simple_settings_dialog_->Show();
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ToggleUltrawideTargets() {
|
||||
if (ultrawide_targets_dialog_) {
|
||||
if (ultrawide_targets_dialog_->visible()) {
|
||||
ultrawide_targets_dialog_->Hide();
|
||||
} else {
|
||||
ApplySettingsCursorMode();
|
||||
ultrawide_targets_dialog_->Show();
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
const auto export_path = cache_root() / "skate3_ultrawide_targets.toml";
|
||||
ultrawide_targets_dialog_ = std::make_unique<rex::ui::UltrawideTargetsDialog>(
|
||||
imgui_drawer(), export_path, [this]() { ApplyGameplayCursorMode(); });
|
||||
ApplySettingsCursorMode();
|
||||
ultrawide_targets_dialog_->Show();
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ApplySettingsCursorMode() {
|
||||
if (window()) {
|
||||
window()->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible);
|
||||
|
|
@ -714,19 +926,8 @@ void Skate3BaseApp::LogUserMarker() {
|
|||
|
||||
void Skate3BaseApp::LogDebugMarker() {
|
||||
const uint32_t marker = debug_marker_count_.fetch_add(1, std::memory_order_relaxed) + 1;
|
||||
rex::cvar::SetFlagByName("vulkan_debug_log_frame_summaries_remaining", "36000");
|
||||
rex::cvar::SetFlagByName("vulkan_debug_frame_summary_interval_frames", "300");
|
||||
rex::cvar::SetFlagByName("vulkan_debug_log_resolve_decisions_remaining", "10000");
|
||||
rex::cvar::SetFlagByName("vulkan_debug_log_team_profile_background_candidates_remaining",
|
||||
"1000");
|
||||
rex::cvar::SetFlagByName("vulkan_debug_log_team_profile_background_bindings_remaining",
|
||||
"256");
|
||||
rex::cvar::SetFlagByName("filesystem_debug_log_fe_asset_ops_remaining", "10000");
|
||||
rex::cvar::SetFlagByName("filesystem_debug_log_team_profile_background_remaining", "200");
|
||||
REXLOG_WARN(
|
||||
"USER DEBUG MARKER #{}: F9 pressed; enabled Vulkan frame summaries, resolve diagnostics, "
|
||||
"FE asset diagnostics, and team_profile_background_0 tracing",
|
||||
marker);
|
||||
DisableActiveUltrawideDiagnostics();
|
||||
REXLOG_WARN("USER DEBUG MARKER #{}: F9 pressed; active diagnostics disabled", marker);
|
||||
}
|
||||
|
||||
void Skate3BaseApp::ApplySelectedProfileToRuntime() {
|
||||
|
|
|
|||
|
|
@ -12,6 +12,7 @@
|
|||
|
||||
#include <rex/rex_app.h>
|
||||
#include <rex/ui/overlay/simple_settings_overlay.h>
|
||||
#include <rex/ui/overlay/ultrawide_targets_overlay.h>
|
||||
|
||||
namespace rex::ui {
|
||||
class ImGuiDrawer;
|
||||
|
|
@ -37,6 +38,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
|||
void InstallBigDeviceAliases();
|
||||
void InstallDlcPackages();
|
||||
void ToggleSimpleSettings();
|
||||
void ToggleUltrawideTargets();
|
||||
void ApplySettingsCursorMode();
|
||||
void ApplyGameplayCursorMode();
|
||||
void RestartGame();
|
||||
|
|
@ -55,6 +57,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
|||
std::filesystem::path user_settings_path_;
|
||||
std::filesystem::path profiles_path_;
|
||||
std::unique_ptr<rex::ui::SimpleSettingsDialog> simple_settings_dialog_;
|
||||
std::unique_ptr<rex::ui::UltrawideTargetsDialog> ultrawide_targets_dialog_;
|
||||
bool recipe_overlay_installed_ = false;
|
||||
bool big_device_aliases_installed_ = false;
|
||||
std::atomic<uint32_t> debug_marker_count_{0};
|
||||
|
|
|
|||
72
src/skate3_fov.cpp
Normal file
72
src/skate3_fov.cpp
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
#include "skate3_fov.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdint>
|
||||
#include <string_view>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
|
||||
namespace {
|
||||
|
||||
constexpr double kDefaultFieldOfViewDegrees = 60.0;
|
||||
constexpr double kFieldOfViewEpsilon = 0.001;
|
||||
constexpr double kDegreesToRadians = 3.14159265358979323846 / 180.0;
|
||||
|
||||
std::atomic<uint32_t> g_projection_fov_override_bits{0};
|
||||
|
||||
float FloatFromBits(uint32_t value) {
|
||||
union {
|
||||
uint32_t u;
|
||||
float f;
|
||||
} bits{value};
|
||||
return bits.f;
|
||||
}
|
||||
|
||||
uint32_t BitsFromFloat(float value) {
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} bits{value};
|
||||
return bits.u;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Skate3UpdateFieldOfViewOverride(double degrees) {
|
||||
if (!std::isfinite(degrees) ||
|
||||
std::abs(degrees - kDefaultFieldOfViewDegrees) <= kFieldOfViewEpsilon) {
|
||||
g_projection_fov_override_bits.store(0, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
const double clamped_degrees = std::clamp(degrees, 40.0, 120.0);
|
||||
const float radians = float(clamped_degrees * kDegreesToRadians);
|
||||
if (!std::isfinite(radians) || radians <= 0.0f) {
|
||||
g_projection_fov_override_bits.store(0, std::memory_order_relaxed);
|
||||
return;
|
||||
}
|
||||
|
||||
g_projection_fov_override_bits.store(BitsFromFloat(radians), std::memory_order_relaxed);
|
||||
}
|
||||
|
||||
void Skate3InitializeFieldOfViewOverride() {
|
||||
Skate3UpdateFieldOfViewOverride(rex::cvar::Query<double>("skate3_field_of_view"));
|
||||
rex::cvar::RegisterChangeCallback(
|
||||
"skate3_field_of_view", [](std::string_view, std::string_view value) {
|
||||
double degrees = 0.0;
|
||||
if (rex::cvar::ParseDouble(value, degrees)) {
|
||||
Skate3UpdateFieldOfViewOverride(degrees);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
float Skate3MaybeOverrideProjectionFovRadians(float native_radians) {
|
||||
const uint32_t override_bits = g_projection_fov_override_bits.load(std::memory_order_relaxed);
|
||||
if (!override_bits || !std::isfinite(native_radians) || native_radians < 0.5f ||
|
||||
native_radians > 2.4f) {
|
||||
return native_radians;
|
||||
}
|
||||
return FloatFromBits(override_bits);
|
||||
}
|
||||
5
src/skate3_fov.h
Normal file
5
src/skate3_fov.h
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
#pragma once
|
||||
|
||||
void Skate3InitializeFieldOfViewOverride();
|
||||
void Skate3UpdateFieldOfViewOverride(double degrees);
|
||||
float Skate3MaybeOverrideProjectionFovRadians(float native_radians);
|
||||
614
src/skate3_ultrawide_guest.h
Normal file
614
src/skate3_ultrawide_guest.h
Normal file
|
|
@ -0,0 +1,614 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <cmath>
|
||||
#include <cstdlib>
|
||||
#include <cstdint>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <unordered_map>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <mutex>
|
||||
|
||||
#include <rex/cvar.h>
|
||||
#include <rex/graphics/ultrawide_debug.h>
|
||||
#include <rex/logging.h>
|
||||
|
||||
inline bool Skate3UltrawideHasExpandedTargetAspect() {
|
||||
if (!rex::cvar::Query<bool>("skate3_ultrawide") ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_hor_plus") ||
|
||||
!rex::graphics::ultrawide_debug::IsSkate3GameplayUltrawideActive()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return rex::cvar::Query<double>("skate3_ultrawide_target_aspect") >
|
||||
(16.0 / 9.0) + 0.01;
|
||||
}
|
||||
|
||||
inline float Skate3UltrawideFloatFromU32(uint32_t value) {
|
||||
union {
|
||||
uint32_t u;
|
||||
float f;
|
||||
} bits{value};
|
||||
return bits.f;
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideU32FromFloat(float value) {
|
||||
union {
|
||||
float f;
|
||||
uint32_t u;
|
||||
} bits{value};
|
||||
return bits.u;
|
||||
}
|
||||
|
||||
inline double Skate3UltrawideMaybeOverrideGameplayFrustumAspect(uint32_t site_id,
|
||||
double native_aspect) {
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags") ||
|
||||
!std::isfinite(native_aspect) || native_aspect <= 0.0) {
|
||||
return native_aspect;
|
||||
}
|
||||
|
||||
const double forced_aspect = std::clamp(
|
||||
rex::cvar::Query<double>("skate3_ultrawide_target_aspect"), 16.0 / 9.0, 8.0);
|
||||
if (forced_aspect <= native_aspect + 0.001) {
|
||||
return native_aspect;
|
||||
}
|
||||
|
||||
const uint32_t native_milli = uint32_t(std::clamp(native_aspect * 1000.0, 0.0, 100000.0));
|
||||
const uint32_t forced_milli = uint32_t(std::clamp(forced_aspect * 1000.0, 0.0, 100000.0));
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallback(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kWidth, site_id, native_milli);
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallbackResult(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kWidth, site_id, native_milli,
|
||||
forced_milli);
|
||||
return forced_aspect;
|
||||
}
|
||||
|
||||
inline void Skate3UltrawideMaybeExpandMainCullRange(PPCContext& ctx, u8* base,
|
||||
uint32_t min_addr,
|
||||
uint32_t max_addr) {
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double target_aspect = std::clamp(
|
||||
rex::cvar::Query<double>("skate3_ultrawide_target_aspect"), 16.0 / 9.0, 8.0);
|
||||
const float scale = float(target_aspect / (16.0 / 9.0));
|
||||
if (scale <= 1.0f) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float native_min = Skate3UltrawideFloatFromU32(REX_LOAD_U32(min_addr));
|
||||
const float native_max = Skate3UltrawideFloatFromU32(REX_LOAD_U32(max_addr));
|
||||
if (!std::isfinite(native_min) || !std::isfinite(native_max) ||
|
||||
native_max <= native_min) {
|
||||
return;
|
||||
}
|
||||
|
||||
const float center = (native_min + native_max) * 0.5f;
|
||||
const float half_width = (native_max - native_min) * 0.5f * scale;
|
||||
const float widened_min = center - half_width;
|
||||
const float widened_max = center + half_width;
|
||||
if (!std::isfinite(widened_min) || !std::isfinite(widened_max)) {
|
||||
return;
|
||||
}
|
||||
|
||||
REX_STORE_U32(min_addr, Skate3UltrawideU32FromFloat(widened_min));
|
||||
REX_STORE_U32(max_addr, Skate3UltrawideU32FromFloat(widened_max));
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideRecordScreenHeight(uint32_t site_id, uint32_t native_height) {
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallback(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kHeight, site_id, native_height);
|
||||
return native_height;
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideMaybeOverrideScreenFlag(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind kind, uint32_t site_id,
|
||||
uint32_t native_value) {
|
||||
const bool selected =
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallback(kind, site_id, native_value);
|
||||
return selected ? 1 : native_value;
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideMaybeForceMainVisibilityFlag(uint32_t site_id,
|
||||
uint32_t native_value) {
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallback(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag, site_id, native_value);
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags")) {
|
||||
return native_value;
|
||||
}
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallbackResult(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag, site_id, native_value, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideMaybeForceHighestLodState(uint32_t site_id, uint32_t object,
|
||||
uint32_t native_state) {
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags") ||
|
||||
native_state == 0) {
|
||||
return native_state;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline thread_local bool g_skate3_ultrawide_force_main_cull_classifier = false;
|
||||
|
||||
struct Skate3UltrawideMainCullClassifierScope {
|
||||
explicit Skate3UltrawideMainCullClassifierScope(bool enabled)
|
||||
: previous(g_skate3_ultrawide_force_main_cull_classifier) {
|
||||
g_skate3_ultrawide_force_main_cull_classifier =
|
||||
previous || (enabled && Skate3UltrawideHasExpandedTargetAspect());
|
||||
}
|
||||
|
||||
~Skate3UltrawideMainCullClassifierScope() {
|
||||
g_skate3_ultrawide_force_main_cull_classifier = previous;
|
||||
}
|
||||
|
||||
bool previous;
|
||||
};
|
||||
|
||||
inline uint32_t Skate3UltrawideMaybeForceMainCullClassifierHalfword(uint32_t native_value) {
|
||||
if (!g_skate3_ultrawide_force_main_cull_classifier ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags")) {
|
||||
return native_value;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
inline bool Skate3UltrawideShouldForceWorldMainCullDescriptor(u8* base, uint32_t descriptor) {
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags") ||
|
||||
descriptor < 512) {
|
||||
return false;
|
||||
}
|
||||
|
||||
constexpr uint32_t kWorldVtable = 0x8230B278;
|
||||
const uint32_t object = descriptor - 512;
|
||||
if (REX_LOAD_U32(object) != kWorldVtable) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const uint32_t output_flags = REX_LOAD_U32(descriptor + 12);
|
||||
return output_flags != 0 && output_flags == REX_LOAD_U32(object + 496);
|
||||
}
|
||||
|
||||
inline bool Skate3UltrawideShouldWidenGameCullFrustum(u8* base, uint32_t cull_block) {
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_widen_game_frustum") || !cull_block) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return REX_LOAD_U32(cull_block + 5260) != 0;
|
||||
}
|
||||
|
||||
struct Skate3UltrawideGameFrustumPatchScope {
|
||||
Skate3UltrawideGameFrustumPatchScope(PPCContext& ctx_, u8* base_, uint32_t cull_block)
|
||||
: ctx(ctx_), base(base_) {
|
||||
if (!Skate3UltrawideShouldWidenGameCullFrustum(base, cull_block)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const double target_aspect = std::clamp(
|
||||
rex::cvar::Query<double>("skate3_ultrawide_target_aspect"), 16.0 / 9.0, 8.0);
|
||||
side_scale = float((16.0 / 9.0) / target_aspect);
|
||||
if (!(side_scale > 0.0f && side_scale < 0.999f)) {
|
||||
return;
|
||||
}
|
||||
|
||||
block = cull_block;
|
||||
PlaneBits current_left_bits = LoadPlaneBits(kLeftPlaneOffset);
|
||||
PlaneBits current_right_bits = LoadPlaneBits(kRightPlaneOffset);
|
||||
|
||||
WidenedPlaneCache& cache = CacheForBlock();
|
||||
if (cache.valid && cache.side_scale == side_scale &&
|
||||
current_left_bits == cache.widened[0] && current_right_bits == cache.widened[1]) {
|
||||
return;
|
||||
}
|
||||
|
||||
cache.original[0] = current_left_bits;
|
||||
cache.original[1] = current_right_bits;
|
||||
|
||||
Plane left = LoadPlane(kLeftPlaneOffset);
|
||||
Plane right = LoadPlane(kRightPlaneOffset);
|
||||
Plane widened_left{};
|
||||
Plane widened_right{};
|
||||
for (size_t i = 0; i < left.size(); ++i) {
|
||||
const float center = (left[i] + right[i]) * 0.5f;
|
||||
const float side = (left[i] - right[i]) * 0.5f * side_scale;
|
||||
widened_left[i] = center + side;
|
||||
widened_right[i] = center - side;
|
||||
}
|
||||
|
||||
if (!NormalizePlane(widened_left) || !NormalizePlane(widened_right)) {
|
||||
return;
|
||||
}
|
||||
|
||||
StorePlane(kLeftPlaneOffset, widened_left);
|
||||
StorePlane(kRightPlaneOffset, widened_right);
|
||||
cache.widened[0] = LoadPlaneBits(kLeftPlaneOffset);
|
||||
cache.widened[1] = LoadPlaneBits(kRightPlaneOffset);
|
||||
cache.side_scale = side_scale;
|
||||
cache.valid = true;
|
||||
active = true;
|
||||
}
|
||||
|
||||
~Skate3UltrawideGameFrustumPatchScope() = default;
|
||||
|
||||
Skate3UltrawideGameFrustumPatchScope(const Skate3UltrawideGameFrustumPatchScope&) = delete;
|
||||
Skate3UltrawideGameFrustumPatchScope& operator=(const Skate3UltrawideGameFrustumPatchScope&) =
|
||||
delete;
|
||||
|
||||
private:
|
||||
using Plane = std::array<float, 4>;
|
||||
using PlaneBits = std::array<uint32_t, 4>;
|
||||
|
||||
struct WidenedPlaneCache {
|
||||
uint32_t block = 0;
|
||||
std::array<PlaneBits, 2> original{};
|
||||
std::array<PlaneBits, 2> widened{};
|
||||
float side_scale = 1.0f;
|
||||
bool valid = false;
|
||||
};
|
||||
|
||||
static constexpr uint32_t kLeftPlaneOffset = 5152;
|
||||
static constexpr uint32_t kRightPlaneOffset = 5168;
|
||||
|
||||
float LoadFloat(uint32_t address) const {
|
||||
PPCRegister value{};
|
||||
value.u32 = REX_LOAD_U32(address);
|
||||
return value.f32;
|
||||
}
|
||||
|
||||
void StoreFloat(uint32_t address, float number) const {
|
||||
PPCRegister value{};
|
||||
value.f32 = number;
|
||||
REX_STORE_U32(address, value.u32);
|
||||
}
|
||||
|
||||
Plane LoadPlane(uint32_t offset) const {
|
||||
Plane plane{};
|
||||
for (uint32_t i = 0; i < plane.size(); ++i) {
|
||||
plane[i] = LoadFloat(block + offset + i * 4);
|
||||
}
|
||||
return plane;
|
||||
}
|
||||
|
||||
void StorePlane(uint32_t offset, const Plane& plane) const {
|
||||
for (uint32_t i = 0; i < plane.size(); ++i) {
|
||||
StoreFloat(block + offset + i * 4, plane[i]);
|
||||
}
|
||||
}
|
||||
|
||||
PlaneBits LoadPlaneBits(uint32_t offset) const {
|
||||
PlaneBits plane{};
|
||||
for (uint32_t i = 0; i < plane.size(); ++i) {
|
||||
plane[i] = REX_LOAD_U32(block + offset + i * 4);
|
||||
}
|
||||
return plane;
|
||||
}
|
||||
|
||||
WidenedPlaneCache& CacheForBlock() {
|
||||
static thread_local std::array<WidenedPlaneCache, 16> cache_entries{};
|
||||
WidenedPlaneCache* empty = nullptr;
|
||||
for (WidenedPlaneCache& entry : cache_entries) {
|
||||
if (entry.valid && entry.block == block) {
|
||||
return entry;
|
||||
}
|
||||
if (!entry.valid && !empty) {
|
||||
empty = &entry;
|
||||
}
|
||||
}
|
||||
|
||||
WidenedPlaneCache& entry = empty ? *empty : cache_entries[block % cache_entries.size()];
|
||||
entry = {};
|
||||
entry.block = block;
|
||||
return entry;
|
||||
}
|
||||
|
||||
static bool NormalizePlane(Plane& plane) {
|
||||
const float length =
|
||||
std::sqrt(plane[0] * plane[0] + plane[1] * plane[1] + plane[2] * plane[2]);
|
||||
if (!(length > 0.00001f) || !std::isfinite(length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const float inv_length = 1.0f / length;
|
||||
for (float& value : plane) {
|
||||
value *= inv_length;
|
||||
if (!std::isfinite(value)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
uint32_t block = 0;
|
||||
PPCContext& ctx;
|
||||
u8* base = nullptr;
|
||||
float side_scale = 1.0f;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
inline std::filesystem::path Skate3UltrawideCacheLogPath(const char* filename);
|
||||
|
||||
enum Skate3UltrawideObjectStageBits : uint32_t {
|
||||
kSkate3ObjectStageActive = 1u << 0,
|
||||
kSkate3ObjectStagePrep = 1u << 1,
|
||||
kSkate3ObjectStageList = 1u << 2,
|
||||
kSkate3ObjectStageMain = 1u << 3,
|
||||
};
|
||||
|
||||
struct Skate3UltrawideObjectStageRecord {
|
||||
uint32_t bits = 0;
|
||||
uint32_t vtable = 0;
|
||||
uint32_t target = 0;
|
||||
uint32_t first_site = 0;
|
||||
uint32_t last_site = 0;
|
||||
uint32_t aux0 = 0;
|
||||
uint32_t aux1 = 0;
|
||||
};
|
||||
|
||||
inline uint64_t& Skate3UltrawideObjectStageFrameId() {
|
||||
static uint64_t frame = 0;
|
||||
return frame;
|
||||
}
|
||||
|
||||
inline uint32_t& Skate3UltrawideObjectStageRenderer() {
|
||||
static uint32_t renderer = 0;
|
||||
return renderer;
|
||||
}
|
||||
|
||||
inline std::unordered_map<uint32_t, Skate3UltrawideObjectStageRecord>&
|
||||
Skate3UltrawideObjectStageRecords() {
|
||||
static std::unordered_map<uint32_t, Skate3UltrawideObjectStageRecord> records;
|
||||
return records;
|
||||
}
|
||||
|
||||
inline std::mutex& Skate3UltrawideObjectStageMutex() {
|
||||
static std::mutex mutex;
|
||||
return mutex;
|
||||
}
|
||||
|
||||
inline std::ofstream& Skate3UltrawideObjectStageLogFile() {
|
||||
static std::ofstream log_file;
|
||||
if (!log_file.is_open()) {
|
||||
std::filesystem::path log_path =
|
||||
Skate3UltrawideCacheLogPath("skate3_ultrawide_object_stage_trace.log");
|
||||
std::error_code ec;
|
||||
if (log_path.has_parent_path()) {
|
||||
std::filesystem::create_directories(log_path.parent_path(), ec);
|
||||
}
|
||||
log_file.open(log_path, std::ios::out | std::ios::trunc);
|
||||
if (log_file.is_open()) {
|
||||
log_file << "seq,frame,kind,marker,renderer,object,bits,site,vtable,target,aux0,aux1,"
|
||||
"active,prep,list,main,missing_main,total\n";
|
||||
}
|
||||
}
|
||||
return log_file;
|
||||
}
|
||||
|
||||
inline bool Skate3UltrawideObjectStageTraceEnabled() {
|
||||
return rex::cvar::Query<bool>("skate3_ultrawide_object_stage_trace_continuous") ||
|
||||
rex::cvar::Query<int32_t>("skate3_ultrawide_object_stage_trace_frames_remaining") > 0 ||
|
||||
rex::cvar::Query<int32_t>("skate3_ultrawide_object_stage_marker") != 0;
|
||||
}
|
||||
|
||||
inline void Skate3UltrawideWriteObjectStageLog(
|
||||
uint64_t frame, const char* kind, uint32_t marker, uint32_t renderer, uint32_t object,
|
||||
uint32_t bits, uint32_t site, uint32_t vtable, uint32_t target, uint32_t aux0,
|
||||
uint32_t aux1, uint32_t active_count, uint32_t prep_count, uint32_t list_count,
|
||||
uint32_t main_count, uint32_t missing_main_count, uint32_t total_count) {
|
||||
if (!rex::cvar::Query<bool>("skate3_ultrawide_object_stage_trace_to_disk")) {
|
||||
return;
|
||||
}
|
||||
|
||||
static std::atomic<uint64_t> sequence{0};
|
||||
std::ofstream& log_file = Skate3UltrawideObjectStageLogFile();
|
||||
if (!log_file.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
log_file << std::dec << sequence.fetch_add(1, std::memory_order_relaxed) << ',' << frame
|
||||
<< ',' << kind << ',' << marker << ",0x" << std::hex << renderer << ",0x"
|
||||
<< object << ",0x" << bits << ",0x" << site << ",0x" << vtable << ",0x"
|
||||
<< target << ",0x" << aux0 << ",0x" << aux1 << ',' << std::dec << active_count
|
||||
<< ',' << prep_count << ',' << list_count << ',' << main_count << ','
|
||||
<< missing_main_count << ',' << total_count << '\n';
|
||||
log_file.flush();
|
||||
}
|
||||
|
||||
inline void Skate3UltrawideFinalizeObjectStageFrameLocked(const char* reason) {
|
||||
auto& records = Skate3UltrawideObjectStageRecords();
|
||||
if (records.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t marker =
|
||||
static_cast<uint32_t>(rex::cvar::Query<int32_t>("skate3_ultrawide_object_stage_marker"));
|
||||
const bool enabled = Skate3UltrawideObjectStageTraceEnabled();
|
||||
uint32_t remaining =
|
||||
static_cast<uint32_t>(std::max(0, rex::cvar::Query<int32_t>(
|
||||
"skate3_ultrawide_object_stage_trace_frames_remaining")));
|
||||
if (!enabled) {
|
||||
records.clear();
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t active_count = 0;
|
||||
uint32_t prep_count = 0;
|
||||
uint32_t list_count = 0;
|
||||
uint32_t main_count = 0;
|
||||
uint32_t missing_main_count = 0;
|
||||
for (const auto& [object, record] : records) {
|
||||
active_count += (record.bits & kSkate3ObjectStageActive) ? 1 : 0;
|
||||
prep_count += (record.bits & kSkate3ObjectStagePrep) ? 1 : 0;
|
||||
list_count += (record.bits & kSkate3ObjectStageList) ? 1 : 0;
|
||||
main_count += (record.bits & kSkate3ObjectStageMain) ? 1 : 0;
|
||||
if ((record.bits & (kSkate3ObjectStagePrep | kSkate3ObjectStageList)) &&
|
||||
!(record.bits & kSkate3ObjectStageMain)) {
|
||||
++missing_main_count;
|
||||
}
|
||||
}
|
||||
|
||||
const uint64_t frame = Skate3UltrawideObjectStageFrameId();
|
||||
const uint32_t renderer = Skate3UltrawideObjectStageRenderer();
|
||||
Skate3UltrawideWriteObjectStageLog(frame, reason, marker, renderer, 0, 0, 0, 0, 0, 0, 0,
|
||||
active_count, prep_count, list_count, main_count,
|
||||
missing_main_count, static_cast<uint32_t>(records.size()));
|
||||
|
||||
uint32_t emitted_missing = 0;
|
||||
const uint32_t missing_limit = static_cast<uint32_t>(std::clamp(
|
||||
rex::cvar::Query<int32_t>("skate3_ultrawide_object_stage_missing_limit"), 0, 4096));
|
||||
for (const auto& [object, record] : records) {
|
||||
if (!((record.bits & (kSkate3ObjectStagePrep | kSkate3ObjectStageList)) &&
|
||||
!(record.bits & kSkate3ObjectStageMain))) {
|
||||
continue;
|
||||
}
|
||||
if (emitted_missing++ >= missing_limit) {
|
||||
break;
|
||||
}
|
||||
Skate3UltrawideWriteObjectStageLog(frame, "missing-main", marker, renderer, object,
|
||||
record.bits, record.last_site, record.vtable,
|
||||
record.target, record.aux0, record.aux1, active_count,
|
||||
prep_count, list_count, main_count, missing_main_count,
|
||||
static_cast<uint32_t>(records.size()));
|
||||
}
|
||||
|
||||
if (remaining > 0) {
|
||||
rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_trace_frames_remaining",
|
||||
std::to_string(remaining - 1));
|
||||
}
|
||||
if (marker != 0) {
|
||||
rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_marker", "0");
|
||||
}
|
||||
records.clear();
|
||||
}
|
||||
|
||||
inline void Skate3UltrawideBeginObjectStageFrame(uint32_t site_id, uint32_t renderer) {
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (renderer < 0x10000) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(Skate3UltrawideObjectStageMutex());
|
||||
Skate3UltrawideFinalizeObjectStageFrameLocked("frame");
|
||||
++Skate3UltrawideObjectStageFrameId();
|
||||
Skate3UltrawideObjectStageRenderer() = renderer;
|
||||
if (Skate3UltrawideObjectStageTraceEnabled()) {
|
||||
Skate3UltrawideWriteObjectStageLog(Skate3UltrawideObjectStageFrameId(), "begin", 0,
|
||||
renderer, 0, 0, site_id, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideObjectStageBitForAction(std::string_view action) {
|
||||
if (action == "render-active-v68") {
|
||||
return kSkate3ObjectStageActive;
|
||||
}
|
||||
if (action == "render-prep-v48") {
|
||||
return kSkate3ObjectStagePrep;
|
||||
}
|
||||
if (action == "render-list-v24") {
|
||||
return kSkate3ObjectStageList;
|
||||
}
|
||||
if (action == "render-main-v52") {
|
||||
return kSkate3ObjectStageMain;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
inline void Skate3UltrawideRecordObjectStage(const char* action, uint32_t site_id,
|
||||
uint32_t object, uint32_t vtable,
|
||||
uint32_t target, uint32_t aux0,
|
||||
uint32_t aux1, uint32_t renderer) {
|
||||
const uint32_t bit = Skate3UltrawideObjectStageBitForAction(action ? action : "");
|
||||
if (!bit || !object || !Skate3UltrawideHasExpandedTargetAspect()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::lock_guard lock(Skate3UltrawideObjectStageMutex());
|
||||
auto& record = Skate3UltrawideObjectStageRecords()[object];
|
||||
if (!record.bits) {
|
||||
record.first_site = site_id;
|
||||
record.vtable = vtable;
|
||||
record.target = target;
|
||||
}
|
||||
record.bits |= bit;
|
||||
record.last_site = site_id;
|
||||
record.aux0 = aux0;
|
||||
record.aux1 = aux1;
|
||||
if (vtable) {
|
||||
record.vtable = vtable;
|
||||
}
|
||||
if (target) {
|
||||
record.target = target;
|
||||
}
|
||||
if (renderer >= 0x10000) {
|
||||
Skate3UltrawideObjectStageRenderer() = renderer;
|
||||
}
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideMaybeForceMainObjectActive(uint32_t site_id,
|
||||
uint32_t native_value) {
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallback(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag, site_id, native_value);
|
||||
if (!Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags")) {
|
||||
return native_value;
|
||||
}
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallbackResult(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag, site_id, native_value, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
inline uint32_t Skate3UltrawideMaybeForceWorldPartVisible(uint32_t site_id,
|
||||
uint32_t native_value,
|
||||
uint32_t sibling_value) {
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallback(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag, site_id, native_value);
|
||||
if (native_value != 0 || sibling_value == 0 || !Skate3UltrawideHasExpandedTargetAspect() ||
|
||||
!rex::cvar::Query<bool>("skate3_ultrawide_force_main_visibility_flags")) {
|
||||
return native_value;
|
||||
}
|
||||
|
||||
rex::graphics::ultrawide_debug::RecordScreenCallbackResult(
|
||||
rex::graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag, site_id, native_value, 1);
|
||||
return 1;
|
||||
}
|
||||
|
||||
inline std::filesystem::path Skate3UltrawideCacheLogPath(const char* filename) {
|
||||
#if defined(_WIN32)
|
||||
char* appdata_raw = nullptr;
|
||||
size_t appdata_length = 0;
|
||||
if (_dupenv_s(&appdata_raw, &appdata_length, "APPDATA") == 0 && appdata_raw &&
|
||||
appdata_length > 0) {
|
||||
std::filesystem::path appdata_path(appdata_raw);
|
||||
std::free(appdata_raw);
|
||||
return appdata_path / "skate3" / "cache" / filename;
|
||||
}
|
||||
std::free(appdata_raw);
|
||||
#else
|
||||
if (const char* appdata = std::getenv("APPDATA")) {
|
||||
return std::filesystem::path(appdata) / "skate3" / "cache" / filename;
|
||||
}
|
||||
#endif
|
||||
return filename;
|
||||
}
|
||||
|
||||
inline void Skate3UltrawideTraceRenderVirtual(const char* action, uint32_t site_id,
|
||||
uint32_t object_ptr, uint32_t vtable_ptr,
|
||||
uint32_t target_ptr, uint32_t aux0 = 0,
|
||||
uint32_t aux1 = 0, uint32_t renderer = 0) {
|
||||
Skate3UltrawideRecordObjectStage(action, site_id, object_ptr, vtable_ptr, target_ptr, aux0,
|
||||
aux1, renderer);
|
||||
}
|
||||
2
third_party/rexglue-sdk
vendored
2
third_party/rexglue-sdk
vendored
|
|
@ -1 +1 @@
|
|||
Subproject commit 34accdd2419240d25f7fc9bfe8870886613d39d3
|
||||
Subproject commit 05049e0769ff49c928beaa6414d225715876c978
|
||||
Loading…
Add table
Reference in a new issue