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/
|
/out/
|
||||||
/build/
|
/build/
|
||||||
/generated/
|
/generated/
|
||||||
|
/generated.backup-*/
|
||||||
|
/tools/
|
||||||
|
|
||||||
# Local game dump and retail media. Users must provide their own copy.
|
# Local game dump and retail media. Users must provide their own copy.
|
||||||
/game/
|
/game/
|
||||||
|
|
@ -29,3 +31,6 @@ CMakeUserPresets.json
|
||||||
/.vs/
|
/.vs/
|
||||||
/.vscode/
|
/.vscode/
|
||||||
/.cache/
|
/.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
|
set(SKATE3_COMMON_SOURCES
|
||||||
src/exception_compat.cpp
|
src/exception_compat.cpp
|
||||||
src/skate3_app_common.cpp
|
src/skate3_app_common.cpp
|
||||||
|
src/skate3_fov.cpp
|
||||||
src/skate3_iso_installer.cpp
|
src/skate3_iso_installer.cpp
|
||||||
src/skate3_user_settings.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.
|
6. Wait for the installer to extract the game files.
|
||||||
7. Click "Start Game".
|
7. Click "Start Game".
|
||||||
|
|
||||||
### DLC
|
## Installing DLC
|
||||||
|
|
||||||
Skate3Recomp does not include Skate 3 DLC files. To use DLC, provide package
|
To use DLC, you must provide package files from your own legally obtained Xbox 360 DLC.
|
||||||
files from your own legally obtained Xbox 360 DLC.
|
|
||||||
|
|
||||||
Create a `dlc` folder beside the executable, inside the installed game folder,
|
Create a `dlc` folder either beside the executable, inside the installed game folder,
|
||||||
or in the Skate3Recomp user data folder. Place the DLC package files in that
|
or in the user data folder. Place the DLC package files in that
|
||||||
folder and start the game. Skate3Recomp will validate matching Skate 3
|
folder and start the game.
|
||||||
marketplace packages and install them into the local content store
|
|
||||||
automatically.
|
|
||||||
|
|
||||||
## Controls
|
## Controls
|
||||||
|
|
||||||
|
|
@ -201,6 +198,10 @@ SKATE3_MNK=1 ./scripts/run-linux.sh
|
||||||
```
|
```
|
||||||
|
|
||||||
Fullscreen is on by default. Pass `--no-fullscreen` to start windowed.
|
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:
|
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
|
add_custom_target(generate-skate3
|
||||||
COMMAND $<TARGET_FILE:rex::rexglue> codegen
|
COMMAND $<TARGET_FILE:rex::rexglue> codegen
|
||||||
"${CMAKE_CURRENT_BINARY_DIR}/manifests/skate3.toml"
|
"${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}"
|
WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}"
|
||||||
COMMENT "Generating recompiled code for default.xex"
|
COMMENT "Generating recompiled code for default.xex"
|
||||||
VERBATIM
|
VERBATIM
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,20 @@ mnk_mode = false
|
||||||
mnk_capture_mouse = false
|
mnk_capture_mouse = false
|
||||||
|
|
||||||
fullscreen = true
|
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
|
vsync = false
|
||||||
video_mode_refresh_rate = 60.0
|
video_mode_refresh_rate = 60.0
|
||||||
resolution_scale = 2
|
resolution_scale = 2
|
||||||
|
|
|
||||||
|
|
@ -158,8 +158,15 @@ def render_rx2(
|
||||||
return output_path
|
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(
|
def write_texture_page(
|
||||||
rendered: list[tuple[Path, Path]],
|
rendered: list[tuple[Path, Path, str]],
|
||||||
output_dir: Path,
|
output_dir: Path,
|
||||||
page_number: int,
|
page_number: int,
|
||||||
page_count: int,
|
page_count: int,
|
||||||
|
|
@ -182,18 +189,18 @@ def write_texture_page(
|
||||||
if page_number < page_count:
|
if page_number < page_count:
|
||||||
rows.append(f"<a href='page-{page_number + 1:03d}.html'>Next</a>")
|
rows.append(f"<a href='page-{page_number + 1:03d}.html'>Next</a>")
|
||||||
rows.append("</div><div class='grid'>")
|
rows.append("</div><div class='grid'>")
|
||||||
for source, image in rendered:
|
for source, image, label in rendered:
|
||||||
rel_image = image.relative_to(output_dir)
|
rel_image = image.relative_to(output_dir)
|
||||||
rows.append(
|
rows.append(
|
||||||
f"<div class='item'><img loading='lazy' src='{html.escape(str(rel_image))}'>"
|
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>")
|
rows.append("</div>")
|
||||||
(output_dir / page_name).write_text("\n".join(rows), encoding="utf-8")
|
(output_dir / page_name).write_text("\n".join(rows), encoding="utf-8")
|
||||||
return page_name
|
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)
|
page_size = max(1, page_size)
|
||||||
pages = [rendered[offset : offset + page_size] for offset in range(0, len(rendered), page_size)]
|
pages = [rendered[offset : offset + page_size] for offset in range(0, len(rendered), page_size)]
|
||||||
page_names = [
|
page_names = [
|
||||||
|
|
@ -297,11 +304,11 @@ def main() -> int:
|
||||||
output_dir = Path(args.output_dir)
|
output_dir = Path(args.output_dir)
|
||||||
output_dir.mkdir(parents=True, exist_ok=True)
|
output_dir.mkdir(parents=True, exist_ok=True)
|
||||||
|
|
||||||
rendered: list[tuple[Path, Path]] = []
|
rendered: list[tuple[Path, Path, str]] = []
|
||||||
for source in sources:
|
for source in sources:
|
||||||
image = render_rx2(source, output_dir, source_root, args.layout)
|
image = render_rx2(source, output_dir, source_root, args.layout)
|
||||||
if image:
|
if image:
|
||||||
rendered.append((source, image))
|
rendered.append((source, image, display_source_name(source, source_root)))
|
||||||
if args.verbose:
|
if args.verbose:
|
||||||
print(f"Rendered {source.name}")
|
print(f"Rendered {source.name}")
|
||||||
if args.limit and len(rendered) >= args.limit:
|
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_app_common.h"
|
||||||
|
|
||||||
|
#include "skate3_fov.h"
|
||||||
#include "skate3_iso_installer.h"
|
#include "skate3_iso_installer.h"
|
||||||
#include "skate3_user_settings.h"
|
#include "skate3_user_settings.h"
|
||||||
|
|
||||||
|
|
@ -12,6 +13,7 @@
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <iomanip>
|
#include <iomanip>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
#include <optional>
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <string_view>
|
#include <string_view>
|
||||||
|
|
@ -39,6 +41,8 @@
|
||||||
#include <rex/filesystem/devices/stfs_container_device.h>
|
#include <rex/filesystem/devices/stfs_container_device.h>
|
||||||
#include <rex/filesystem/devices/host_path_device.h>
|
#include <rex/filesystem/devices/host_path_device.h>
|
||||||
#include <rex/filesystem/vfs.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/input/input_system.h>
|
||||||
#include <rex/kernel/xam/module.h>
|
#include <rex/kernel/xam/module.h>
|
||||||
#include <rex/logging.h>
|
#include <rex/logging.h>
|
||||||
|
|
@ -51,8 +55,10 @@
|
||||||
#include <rex/system/xam/content_manager.h>
|
#include <rex/system/xam/content_manager.h>
|
||||||
#include <rex/system/xam/user_profile.h>
|
#include <rex/system/xam/user_profile.h>
|
||||||
#include <rex/system.h>
|
#include <rex/system.h>
|
||||||
|
#include <rex/ui/flags.h>
|
||||||
#include <rex/ui/keybinds.h>
|
#include <rex/ui/keybinds.h>
|
||||||
#include <rex/ui/overlay/simple_settings_overlay.h>
|
#include <rex/ui/overlay/simple_settings_overlay.h>
|
||||||
|
#include <rex/ui/overlay/ultrawide_targets_overlay.h>
|
||||||
|
|
||||||
#include <imgui.h>
|
#include <imgui.h>
|
||||||
#include <toml++/toml.hpp>
|
#include <toml++/toml.hpp>
|
||||||
|
|
@ -71,6 +77,46 @@ REXCVAR_DEFINE_STRING(skate3_dlc_root, "", "Skate 3",
|
||||||
"Directory containing Skate 3 DLC package files");
|
"Directory containing Skate 3 DLC package files");
|
||||||
REXCVAR_DEFINE_BOOL(skate3_auto_install_dlc, true, "Skate 3",
|
REXCVAR_DEFINE_BOOL(skate3_auto_install_dlc, true, "Skate 3",
|
||||||
"Install DLC package files found in configured DLC folders");
|
"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 {
|
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 kUserDirectoryName = "skate3";
|
||||||
constexpr std::string_view kSettingsFilename = "settings.toml";
|
constexpr std::string_view kSettingsFilename = "settings.toml";
|
||||||
constexpr std::string_view kDlcDirectoryName = "dlc";
|
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() {
|
std::filesystem::path DefaultDocumentsUserRoot() {
|
||||||
return rex::filesystem::GetUserFolder() / std::string(kUserDirectoryName);
|
return rex::filesystem::GetUserFolder() / std::string(kUserDirectoryName);
|
||||||
|
|
@ -342,6 +523,13 @@ void Skate3BaseApp::OnConfigurePaths(rex::PathConfig& paths) {
|
||||||
ConfigureSkate3UserPaths(paths, user_settings_path_, profiles_path_);
|
ConfigureSkate3UserPaths(paths, user_settings_path_, profiles_path_);
|
||||||
config_path_ = paths.config_path;
|
config_path_ = paths.config_path;
|
||||||
LoadAndNormalizeSimpleSettings(user_settings_path_, 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) {
|
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] {
|
rex::ui::RegisterBind("bind_skate3_menu_alt", "F1", "Skate 3 settings alternate", [this] {
|
||||||
ToggleSimpleSettings();
|
ToggleSimpleSettings();
|
||||||
});
|
});
|
||||||
|
rex::ui::RegisterBind("bind_skate3_ultrawide_targets", "F7",
|
||||||
|
"Skate 3 ultrawide targets", [this] {
|
||||||
|
ToggleUltrawideTargets();
|
||||||
|
});
|
||||||
rex::ui::RegisterBind("bind_skate3_save_draw_fingerprints", "F8",
|
rex::ui::RegisterBind("bind_skate3_save_draw_fingerprints", "F8",
|
||||||
"Save draw fingerprint log", [this] {
|
"Save draw fingerprint log", [this] {
|
||||||
SaveDrawFingerprintLog();
|
SaveDrawFingerprintLog();
|
||||||
|
|
@ -506,11 +698,13 @@ void Skate3BaseApp::OnPostSetup() {
|
||||||
void Skate3BaseApp::OnShutdown() {
|
void Skate3BaseApp::OnShutdown() {
|
||||||
rex::ui::UnregisterBind("bind_skate3_menu");
|
rex::ui::UnregisterBind("bind_skate3_menu");
|
||||||
rex::ui::UnregisterBind("bind_skate3_menu_alt");
|
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_save_draw_fingerprints");
|
||||||
rex::ui::UnregisterBind("bind_skate3_log_debug_marker");
|
rex::ui::UnregisterBind("bind_skate3_log_debug_marker");
|
||||||
rex::ui::UnregisterBind("bind_skate3_log_user_marker");
|
rex::ui::UnregisterBind("bind_skate3_log_user_marker");
|
||||||
ApplyGameplayCursorMode();
|
ApplyGameplayCursorMode();
|
||||||
simple_settings_dialog_.reset();
|
simple_settings_dialog_.reset();
|
||||||
|
ultrawide_targets_dialog_.reset();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::ToggleSimpleSettings() {
|
void Skate3BaseApp::ToggleSimpleSettings() {
|
||||||
|
|
@ -588,6 +782,24 @@ void Skate3BaseApp::ToggleSimpleSettings() {
|
||||||
simple_settings_dialog_->Show();
|
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() {
|
void Skate3BaseApp::ApplySettingsCursorMode() {
|
||||||
if (window()) {
|
if (window()) {
|
||||||
window()->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible);
|
window()->SetCursorVisibility(rex::ui::Window::CursorVisibility::kVisible);
|
||||||
|
|
@ -714,19 +926,8 @@ void Skate3BaseApp::LogUserMarker() {
|
||||||
|
|
||||||
void Skate3BaseApp::LogDebugMarker() {
|
void Skate3BaseApp::LogDebugMarker() {
|
||||||
const uint32_t marker = debug_marker_count_.fetch_add(1, std::memory_order_relaxed) + 1;
|
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");
|
DisableActiveUltrawideDiagnostics();
|
||||||
rex::cvar::SetFlagByName("vulkan_debug_frame_summary_interval_frames", "300");
|
REXLOG_WARN("USER DEBUG MARKER #{}: F9 pressed; active diagnostics disabled", marker);
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Skate3BaseApp::ApplySelectedProfileToRuntime() {
|
void Skate3BaseApp::ApplySelectedProfileToRuntime() {
|
||||||
|
|
|
||||||
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <rex/rex_app.h>
|
#include <rex/rex_app.h>
|
||||||
#include <rex/ui/overlay/simple_settings_overlay.h>
|
#include <rex/ui/overlay/simple_settings_overlay.h>
|
||||||
|
#include <rex/ui/overlay/ultrawide_targets_overlay.h>
|
||||||
|
|
||||||
namespace rex::ui {
|
namespace rex::ui {
|
||||||
class ImGuiDrawer;
|
class ImGuiDrawer;
|
||||||
|
|
@ -37,6 +38,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
||||||
void InstallBigDeviceAliases();
|
void InstallBigDeviceAliases();
|
||||||
void InstallDlcPackages();
|
void InstallDlcPackages();
|
||||||
void ToggleSimpleSettings();
|
void ToggleSimpleSettings();
|
||||||
|
void ToggleUltrawideTargets();
|
||||||
void ApplySettingsCursorMode();
|
void ApplySettingsCursorMode();
|
||||||
void ApplyGameplayCursorMode();
|
void ApplyGameplayCursorMode();
|
||||||
void RestartGame();
|
void RestartGame();
|
||||||
|
|
@ -55,6 +57,7 @@ class Skate3BaseApp : public rex::ReXApp {
|
||||||
std::filesystem::path user_settings_path_;
|
std::filesystem::path user_settings_path_;
|
||||||
std::filesystem::path profiles_path_;
|
std::filesystem::path profiles_path_;
|
||||||
std::unique_ptr<rex::ui::SimpleSettingsDialog> simple_settings_dialog_;
|
std::unique_ptr<rex::ui::SimpleSettingsDialog> simple_settings_dialog_;
|
||||||
|
std::unique_ptr<rex::ui::UltrawideTargetsDialog> ultrawide_targets_dialog_;
|
||||||
bool recipe_overlay_installed_ = false;
|
bool recipe_overlay_installed_ = false;
|
||||||
bool big_device_aliases_installed_ = false;
|
bool big_device_aliases_installed_ = false;
|
||||||
std::atomic<uint32_t> debug_marker_count_{0};
|
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