From 05049e0769ff49c928beaa6414d225715876c978 Mon Sep 17 00:00:00 2001 From: mchughalex <25671999+mchughalex@users.noreply.github.com> Date: Tue, 9 Jun 2026 21:33:53 +0100 Subject: [PATCH] add Skate 3 ultrawide runtime support --- include/rex/graphics/command_processor.h | 1 + .../rex/graphics/d3d12/command_processor.h | 2 + include/rex/graphics/pipeline/texture/cache.h | 19 + include/rex/graphics/ultrawide_debug.h | 180 ++ .../rex/graphics/vulkan/command_processor.h | 1 + include/rex/system/xmemory.h | 14 + .../rex/ui/overlay/simple_settings_overlay.h | 2 + .../ui/overlay/ultrawide_targets_overlay.h | 36 + include/rex/ui/presenter.h | 8 + src/core/CMakeLists.txt | 1 + src/core/perf/counter.cpp | 4 +- src/core/skate3_ultrawide_classifier.inc | 184 ++ src/core/ultrawide_debug.cpp | 1526 +++++++++++++++++ src/filesystem/virtual_file_system.cpp | 127 +- src/graphics/command_processor.cpp | 6 +- src/graphics/d3d12/command_processor.cpp | 299 +++- src/graphics/pipeline/texture/cache.cpp | 179 ++ src/graphics/vulkan/command_processor.cpp | 231 ++- src/kernel/xam/apps/xgi_app.cpp | 3 + src/kernel/xboxkrnl/xboxkrnl_video.cpp | 22 +- src/system/xmemory.cpp | 25 + src/ui/CMakeLists.txt | 1 + src/ui/overlay/simple_settings_overlay.cpp | 57 +- src/ui/overlay/ultrawide_targets_overlay.cpp | 1431 ++++++++++++++++ 24 files changed, 4344 insertions(+), 15 deletions(-) create mode 100644 include/rex/graphics/ultrawide_debug.h create mode 100644 include/rex/ui/overlay/ultrawide_targets_overlay.h create mode 100644 src/core/skate3_ultrawide_classifier.inc create mode 100644 src/core/ultrawide_debug.cpp create mode 100644 src/ui/overlay/ultrawide_targets_overlay.cpp diff --git a/include/rex/graphics/command_processor.h b/include/rex/graphics/command_processor.h index 21d6946..38a3a9e 100644 --- a/include/rex/graphics/command_processor.h +++ b/include/rex/graphics/command_processor.h @@ -274,6 +274,7 @@ class CommandProcessor { uint32_t primary_buffer_ptr_ = 0; uint32_t primary_buffer_size_ = 0; + uint32_t current_packet_ptr_ = 0; uint32_t read_ptr_index_ = 0; uint32_t read_ptr_update_freq_ = 0; diff --git a/include/rex/graphics/d3d12/command_processor.h b/include/rex/graphics/d3d12/command_processor.h index 389eed8..72bbe7c 100644 --- a/include/rex/graphics/d3d12/command_processor.h +++ b/include/rex/graphics/d3d12/command_processor.h @@ -369,9 +369,11 @@ class D3D12CommandProcessor : public CommandProcessor { const draw_util::Scissor& scissor, bool primitive_polygonal, reg::RB_DEPTHCONTROL normalized_depth_control); void UpdateSystemConstantValues(bool shared_memory_is_uav, bool primitive_polygonal, + Shader::HostVertexShaderType host_vertex_shader_type, uint32_t line_loop_closing_index, xenos::Endian index_endian, const draw_util::ViewportInfo& viewport_info, uint32_t used_texture_mask, + uint64_t vertex_shader_hash, uint64_t pixel_shader_hash, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask); bool UpdateBindings(const D3D12Shader* vertex_shader, const D3D12Shader* pixel_shader, diff --git a/include/rex/graphics/pipeline/texture/cache.h b/include/rex/graphics/pipeline/texture/cache.h index 04db0f1..1909cdb 100644 --- a/include/rex/graphics/pipeline/texture/cache.h +++ b/include/rex/graphics/pipeline/texture/cache.h @@ -125,6 +125,21 @@ class TextureCache { bool& signed_view_out, uint32_t* texture_base_out = nullptr, uint32_t* texture_length_out = nullptr) const; + struct DebugActiveTextureBinding { + uint64_t key_hash = 0; + uint32_t fetch_index = 0; + uint32_t base_address = 0; + uint32_t base_length = 0; + uint32_t mip_address = 0; + uint32_t mip_length = 0; + uint32_t width = 0; + uint32_t height = 0; + uint32_t depth_or_array_size = 0; + uint32_t format = 0; + bool scaled_resolve = false; + }; + bool DebugGetActiveTextureBinding(uint32_t fetch_constant_index, + DebugActiveTextureBinding& binding_out) const; // "ActiveTexture" means as of the latest RequestTextures call. @@ -590,6 +605,10 @@ class TextureCache { static bool DebugIsTeamProfileBackgroundCandidateKey(const TextureKey& key); void DebugLogTeamProfileBackgroundTextureCandidate(const char* action, uint32_t fetch_index, const Texture& texture); + bool DebugConsumeSkate3UltrawideTextureTraceLog(); + void DebugLogSkate3UltrawideTextureTrace(const char* action, uint32_t fetch_index, + const TextureKey& key, + const Texture* texture = nullptr); void UpdateTexturesTotalHostMemoryUsage(uint64_t add, uint64_t subtract); diff --git a/include/rex/graphics/ultrawide_debug.h b/include/rex/graphics/ultrawide_debug.h new file mode 100644 index 0000000..db6acd1 --- /dev/null +++ b/include/rex/graphics/ultrawide_debug.h @@ -0,0 +1,180 @@ +#pragma once + +#include +#include +#include +#include +#include +#include + +namespace rex::graphics::ultrawide_debug { + +struct TargetKey { + std::array color_info{}; + uint32_t depth_info = 0; + uint32_t surface_info = 0; + uint32_t viewport_x = 0; + uint32_t viewport_y = 0; + uint32_t viewport_width = 0; + uint32_t viewport_height = 0; + uint32_t color_mask = 0; + uint32_t depth_control = 0; + uint32_t pa_cl_vte_cntl = 0; + uint32_t primitive_type = 0; + uint32_t host_vertex_shader_type = 0; + uint64_t vertex_shader_hash = 0; + uint64_t pixel_shader_hash = 0; + + bool operator==(const TargetKey& other) const; +}; + +struct TargetEntry { + uint64_t hash = 0; + TargetKey key; + bool enabled = false; + bool default_enabled = false; + bool shadow_candidate = false; + uint64_t draw_count = 0; + uint64_t applied_draw_count = 0; + uint64_t last_seen_order = 0; +}; + +enum class ScreenCallbackKind : uint32_t { + kWidth = 0, + kHeight = 1, + kScreenFlag = 2, + kWide = 3, +}; + +struct ScreenCallbackEntry { + uint64_t hash = 0; + ScreenCallbackKind kind = ScreenCallbackKind::kWidth; + uint32_t caller_lr = 0; + uint32_t last_value = 0; + uint32_t last_returned_value = 0; + bool last_spoofed = false; + bool enabled = false; + uint64_t call_count = 0; + uint64_t last_seen_order = 0; +}; + +struct GuestStoreWatchEntry { + uint64_t sequence = 0; + std::string target; + uint32_t address = 0; + uint32_t value = 0; + uint32_t caller_lr = 0; + std::string function; + std::string stack; + uint64_t count = 1; +}; + +class GuestFunctionScope { + public: + explicit GuestFunctionScope(const char* function_name); + ~GuestFunctionScope(); + + private: + bool active_ = false; +}; + +enum class DrawBucket : uint32_t { + kMainColorDepth = 0, + kDepthOnly = 1, + kCopyResolve = 2, + kMemexport = 3, + kNoPixelShader = 4, +}; + +struct DrawFingerprint { + DrawBucket bucket = DrawBucket::kMainColorDepth; + uint32_t guest_render_object = 0; + uint32_t guest_render_vtable = 0; + uint32_t guest_render_target = 0; + uint64_t vertex_shader_hash = 0; + uint64_t pixel_shader_hash = 0; + uint32_t primitive_type = 0; + uint32_t vertex_count = 0; + uint32_t primitive_count = 0; + uint32_t packet_ptr = 0; + uint32_t index_guest_base = 0; + uint32_t index_length = 0; + static constexpr size_t kVertexFetchCount = 4; + uint32_t vertex_fetch_address[kVertexFetchCount] = {}; + uint32_t vertex_fetch_size[kVertexFetchCount] = {}; + static constexpr size_t kTextureFetchCount = 8; + uint64_t texture_key_hash[kTextureFetchCount] = {}; + uint32_t texture_fetch_index[kTextureFetchCount] = {}; + uint32_t texture_base_address[kTextureFetchCount] = {}; + uint32_t texture_base_length[kTextureFetchCount] = {}; + uint32_t texture_width[kTextureFetchCount] = {}; + uint32_t texture_height[kTextureFetchCount] = {}; + uint32_t texture_format[kTextureFetchCount] = {}; +}; + +struct DrawFingerprintEntry { + DrawFingerprint fingerprint; + uint64_t hash = 0; + bool enabled = true; + uint64_t draw_count = 0; + uint64_t submitted_count = 0; + uint64_t skipped_count = 0; + uint64_t vertices = 0; + uint64_t primitives = 0; + uint64_t last_seen_order = 0; + uint64_t last_seen_ms = 0; +}; + +enum class ApplyMode : uint32_t { + kTargetList = 0, + kForceAll = 1, + kForceNone = 2, + kSavedOnly = 3, +}; + +uint64_t HashKey(const TargetKey& key); +void NotifySkate3GameplayContext(uint32_t user_index, uint32_t context_id, uint32_t value); +bool IsSkate3GameplayUltrawideActive(); +bool ShouldApplyAndRecord(const TargetKey& key, bool default_enabled); +uint32_t ShadowMapCandidateLevel(const TargetKey& key); +bool IsShadowMapCandidate(const TargetKey& key, uint32_t mode = 1); +std::vector SnapshotTargets(); +void SetTargetEnabled(uint64_t hash, bool enabled); +void SetAllTargetsEnabled(bool enabled); +void ResetTargetOverrides(); +void SetTargetRecordingActive(bool active); +uint64_t HashScreenCallback(ScreenCallbackKind kind, uint32_t caller_lr); +bool RecordScreenCallback(ScreenCallbackKind kind, uint32_t caller_lr, uint32_t value); +void RecordScreenCallbackResult(ScreenCallbackKind kind, uint32_t caller_lr, uint32_t native_value, + uint32_t returned_value); +std::vector SnapshotScreenCallbacks(); +void SetScreenCallbackEnabled(uint64_t hash, bool enabled); +void SetAllScreenCallbacksEnabled(bool enabled); +void SetGuestStoreWatchTargets(uint32_t index_base, uint32_t vertex_fetch_address, + uint32_t texture_base_address, uint64_t group_hash = 0); +void ClearGuestStoreWatchTargets(); +void ClearGuestStoreWatchEvents(); +void SetGuestStoreWatchLogPath(const std::filesystem::path& path); +std::vector SnapshotGuestStoreWatchEvents(); +void RecordGuestStoreU32(uint32_t address, uint32_t value, const char* function_name, + uint32_t caller_lr = 0); +void RecordDrawWatchTransition(bool present, uint64_t age, uint32_t packet_ptr, + uint32_t index_base, uint32_t vertex_fetch_address, + uint64_t group_hash); +bool IsGuestStoreWatchActive(); +void SetCurrentDrawOwner(uint32_t object, uint32_t vtable, uint32_t target); +void ClearCurrentDrawOwner(); +uint64_t HashDrawFingerprint(const DrawFingerprint& fingerprint); +bool RecordDrawFingerprint(const DrawFingerprint& fingerprint); +std::vector SnapshotDrawFingerprints(); +void SetDrawFingerprintEnabled(uint64_t hash, bool enabled); +void SetAllDrawFingerprintsEnabled(bool enabled); +void ResetDrawFingerprintOverrides(); +void ClearDrawFingerprints(); +ApplyMode GetApplyMode(); +void SetApplyMode(ApplyMode mode); +void LoadBuiltInSkate3Classifier(); +std::optional LoadTargets(const std::filesystem::path& path); +std::optional SaveTargets(const std::filesystem::path& path); + +} // namespace rex::graphics::ultrawide_debug diff --git a/include/rex/graphics/vulkan/command_processor.h b/include/rex/graphics/vulkan/command_processor.h index 470bdf2..cf14d72 100644 --- a/include/rex/graphics/vulkan/command_processor.h +++ b/include/rex/graphics/vulkan/command_processor.h @@ -514,6 +514,7 @@ class VulkanCommandProcessor : public CommandProcessor { const PrimitiveProcessor::ProcessingResult& primitive_processing_result, bool shader_32bit_index_dma, uint32_t compute_memexport_vertex_count, const draw_util::ViewportInfo& viewport_info, uint32_t used_texture_mask, + uint64_t vertex_shader_hash, uint64_t pixel_shader_hash, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask); bool UpdateBindings(const VulkanShader* vertex_shader, const VulkanShader* pixel_shader); // Allocates a descriptor set and fills one or two VkWriteDescriptorSet diff --git a/include/rex/system/xmemory.h b/include/rex/system/xmemory.h index 62a214c..944e71f 100644 --- a/include/rex/system/xmemory.h +++ b/include/rex/system/xmemory.h @@ -40,6 +40,20 @@ inline u32 PhysicalHostOffset(u32 guest_addr) noexcept { namespace rex::memory { +struct PhysicalMemoryInvalidationDiagnostics { + bool valid = false; + uint32_t virtual_address = 0; + uint32_t physical_address = 0; + uint32_t length = 0; + uint32_t thread_id = 0xFFFFFFFF; + uint32_t lr = 0; + uint32_t stack = 0; + bool exact_range = false; + bool unprotect = false; +}; + +const PhysicalMemoryInvalidationDiagnostics& GetPhysicalMemoryInvalidationDiagnostics(); + /// Lightweight guest-to-host pointer translation using the memory base. /// For hooks and kernel code operating with the base pointer directly. /// Same raw arithmetic as recompiled code; no Memory* or heap lookup needed. diff --git a/include/rex/ui/overlay/simple_settings_overlay.h b/include/rex/ui/overlay/simple_settings_overlay.h index d9a8b2b..8c13d7d 100644 --- a/include/rex/ui/overlay/simple_settings_overlay.h +++ b/include/rex/ui/overlay/simple_settings_overlay.h @@ -68,6 +68,8 @@ class SimpleSettingsDialog final : public ImGuiDialog { bool visible_ = false; int resolution_scale_index_ = 0; int frame_cap_index_ = 0; + int aspect_ratio_index_ = 0; + float field_of_view_ = 60.0f; bool fullscreen_ = true; bool vsync_ = false; bool tearing_ = true; diff --git a/include/rex/ui/overlay/ultrawide_targets_overlay.h b/include/rex/ui/overlay/ultrawide_targets_overlay.h new file mode 100644 index 0000000..c412af1 --- /dev/null +++ b/include/rex/ui/overlay/ultrawide_targets_overlay.h @@ -0,0 +1,36 @@ +#pragma once + +#include +#include + +#include + +namespace rex::ui { + +class UltrawideTargetsDialog final : public ImGuiDialog { + public: + using CloseCallback = std::function; + + UltrawideTargetsDialog(ImGuiDrawer* drawer, std::filesystem::path export_path, + CloseCallback close_callback = {}); + ~UltrawideTargetsDialog(); + + void Show(); + void Toggle(); + void Hide(); + bool visible() const { return visible_; } + + protected: + void OnDraw(ImGuiIO& io) override; + + private: + std::filesystem::path export_path_; + CloseCallback close_callback_; + bool visible_ = false; + bool show_only_seen_ = true; + bool draw_section_expanded_ = false; + bool draw_show_exact_fingerprints_ = false; + int draw_group_mode_ = 1; +}; + +} // namespace rex::ui diff --git a/include/rex/ui/presenter.h b/include/rex/ui/presenter.h index 95c8d30..bba6e15 100644 --- a/include/rex/ui/presenter.h +++ b/include/rex/ui/presenter.h @@ -660,6 +660,14 @@ class Presenter { uint32_t host_rt_width, uint32_t host_rt_height, uint32_t max_rt_width, uint32_t max_rt_height, const GuestOutputPaintConfig& config) const; + public: + bool GetSurfacePaintConnectionSize(uint32_t& width_out, uint32_t& height_out) const { + width_out = surface_width_in_paint_connection_; + height_out = surface_height_in_paint_connection_; + return width_out && height_out; + } + + protected: // is_8bpc_out_ref is where to write whether the source actually has no more // than 8 bits of precision per channel (though the image provided by the // refresher may still have a higher storage precision) - if not written, it diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 06e8b7a..1a365aa 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -20,6 +20,7 @@ add_library(rexcore OBJECT string_buffer.cpp threading.cpp timer_queue.cpp + ultrawide_debug.cpp utf8.cpp vec128.cpp ) diff --git a/src/core/perf/counter.cpp b/src/core/perf/counter.cpp index d5701c2..17954e6 100644 --- a/src/core/perf/counter.cpp +++ b/src/core/perf/counter.cpp @@ -694,7 +694,9 @@ bool ShouldCollectDrawDiagnostics() { } bool ShouldCollectDrawFingerprints() { - return REXCVAR_GET(perf_draw_fingerprints) || ShouldCollectDrawDiagnostics(); + return REXCVAR_GET(perf_draw_fingerprints) || + rex::cvar::Query("skate3_ultrawide_trace_draws") || + ShouldCollectDrawDiagnostics(); } int64_t GetCounter(CounterId id) { diff --git a/src/core/skate3_ultrawide_classifier.inc b/src/core/skate3_ultrawide_classifier.inc new file mode 100644 index 0000000..26d2e6f --- /dev/null +++ b/src/core/skate3_ultrawide_classifier.inc @@ -0,0 +1,184 @@ +// Generated from skate3_ultrawide_targets.toml. Do not edit by hand. +constexpr BuiltInTargetOverride kBuiltInSkate3TargetOverrides[] = { + {UINT64_C(0xA78541591A629661), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x09020258), UINT32_C(0x0000000F), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0xA4A965C189287B99)}, true}, + {UINT64_C(0x9D52F6177C7B389D), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0xA4A965C189287B99)}, true}, + {UINT64_C(0xBFD1461001CB97E4), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x0DFDF1BCC0E7B56D), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xC205003194EFBC71), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x28D0A573402806EB), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xD878108EB66E9B28), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x07CF21EDB84C9E22), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xF9AB52AE558D3F50), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x00000000), UINT32_C(0x04020118), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x076AF30F69242406), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x00000000), UINT32_C(0x04020118), UINT32_C(0x00000000), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xA4EF713C0B27236D), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x00000000), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x8197FF5E7840DA63), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x9971A6822FCF2DD2), UINT64_C(0x12049EA773BAF206)}, true}, + {UINT64_C(0xCE27E0AA3C30B48A), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x01799B5C04C8EFE8), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xDD4117074EF2EAA6), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x1F005A21696CC37E), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xFDC400257C26BEFC), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x38B43E35C163614C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x61EFEEA3DF226046), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x1153D3552EF6E624), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x40E624D71F989930), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x00000000), UINT32_C(0x0C020320), UINT32_C(0x00000000), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xEF3B5EA129E40669), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xEB009E6404D91B5A), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x9971A6822FCF2DD2), UINT64_C(0x70E709E9650F8CC0)}, true}, + {UINT64_C(0x30625B4C1DF99FE8), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x9971A6822FCF2DD2), UINT64_C(0x8E01B3337D22E487)}, true}, + {UINT64_C(0xA9446AF4EF2988DA), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x1F005A21696CC37E), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xF82D5E2AA3448EC8), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x38B43E35C163614C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xCDB5E7DDBF3745FA), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x1153D3552EF6E624), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x586B971BC5301FD6), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x9971A6822FCF2DD2), UINT64_C(0xDCFF2237FACA6FA2)}, true}, + {UINT64_C(0x5A53AE14E6682AF0), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x9971A6822FCF2DD2), UINT64_C(0x6D81B22A5A72F2F7)}, true}, + {UINT64_C(0x9B7DAC17F3087CA7), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x8A6134DE7D8980C0), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x56CF04EDC1C67AD2), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x1F005A21696CC37E), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x630D4F6C8FF3BAC3), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC0A7FEA59D737772), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xF3FB10C786C01BBF), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xAAE4AE2E54C97D0F), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x2B13F106B03A4C7D), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x69162B400E55DAC4), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xE47983C5D73AB140), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x38B43E35C163614C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x0B7CE4C6F4A49838), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x99340CC09E2F9371), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x8F9F6723F328A425), UINT64_C(0xAA0DF76CD5E0EDC6)}, true}, + {UINT64_C(0xE4F667D1EF0C3541), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xA43EDAB8CF26AC43), UINT64_C(0x1C8C6412B70D4904)}, true}, + {UINT64_C(0x4CC45A2F1E231172), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0xA43EDAB8CF26AC43), UINT64_C(0x1C8C6412B70D4904)}, true}, + {UINT64_C(0x6ACFCA21C84DD04F), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x0A020280), UINT32_C(0x00000000), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xEC8FBC04212C0A4B), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x0A020280), UINT32_C(0x00000000), UINT32_C(0x00000076), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xD541A82D0F859592), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x09020258), UINT32_C(0x00000000), UINT32_C(0x00000076), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xE5F9E8A8BD7B81FF), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x00000000), UINT32_C(0x00000076), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xE380D9E97118C6B7), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x156953004B00BE2F), UINT64_C(0x6122239794A98F31)}, true}, + {UINT64_C(0xAB60CA4C0329178E), {SemanticViewportClass::kUnknown, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000040), UINT32_C(0x00000020), UINT32_C(0x000002E0), UINT32_C(0x00800050), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0x3A7C78D0EB806EE8)}, true}, + {UINT64_C(0xD59F8C3273E11AB5), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000090), UINT32_C(0x00000050), UINT32_C(0x000002E0), UINT32_C(0x01800050), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0x3A7C78D0EB806EE8)}, true}, + {UINT64_C(0x2769C85DCDEB8FDC), {SemanticViewportClass::kUnknown, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000040), UINT32_C(0x00000020), UINT32_C(0x000002E0), UINT32_C(0x00800050), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0xE12EB29D7EA6D021)}, true}, + {UINT64_C(0x79A2F6E528FFF85B), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000090), UINT32_C(0x00000050), UINT32_C(0x000002E0), UINT32_C(0x01800050), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0xE12EB29D7EA6D021)}, true}, + {UINT64_C(0xE25A14DA07768B39), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000240), UINT32_C(0x00000140), UINT32_C(0x000002E0), UINT32_C(0x04800140), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x34FDABFF54D0767C), UINT64_C(0x670F9657E351B89B)}, true}, + {UINT64_C(0xD899F54B1EFCB5C4), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x1591D381BF0A7CE5), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x2A6DB4A54C1D6A44), UINT64_C(0x06CA19234014B7CF)}, true}, + {UINT64_C(0x199AA29FA7991FC5), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x2A6DB4A54C1D6A44), UINT64_C(0xEFAEF20D9DD3A856)}, true}, + {UINT64_C(0x85508A201CA4F08B), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x229B213C18BF3580), UINT64_C(0x002060C7FFA5DBEE)}, true}, + {UINT64_C(0x01A2EDFB77F55C72), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC186F78859736B49), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x4098E5BF99169505), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB814BA67EA32F032), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x9D3ACA8E7E627D61), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708733), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC186F78859736B49), UINT64_C(0xEB864605A91539ED)}, true}, + {UINT64_C(0x96D40D4C80EFF24A), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708733), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB814BA67EA32F032), UINT64_C(0xE1D8B5E58C05821E)}, true}, + {UINT64_C(0x1FC318709FD6C1D9), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4DA02D43D609305C), UINT64_C(0x826E70D1F5709AB4)}, true}, + {UINT64_C(0x7154460DEA2784F2), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x87E91F0B7DE6C7A5), UINT64_C(0xBD90261648A8EE85)}, true}, + {UINT64_C(0xD2593ADB4AABB99E), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4884B48A4C4D42BD), UINT64_C(0x7386295CA842ED62)}, true}, + {UINT64_C(0xED4AE420F8A90FAF), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4DA02D43D609305C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xB3F551C98DA3B0D8), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x87E91F0B7DE6C7A5), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xF2CC74A09A11CAB3), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4884B48A4C4D42BD), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x15C0DC717C5BE5C0), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x129E7F5770905F10), UINT64_C(0x437E40C3B326C513)}, true}, + {UINT64_C(0x50A5FDF3723B2EE6), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xBBD7F3BD8E43C865), UINT64_C(0x26A449F09A551230)}, true}, + {UINT64_C(0xDD5AABF82064D371), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x11D7AC81789C78E2), UINT64_C(0x0D70BBC32834DD35)}, true}, + {UINT64_C(0x34F99C310F21DAE1), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4DA02D43D609305C), UINT64_C(0x826E70D1F5709AB4)}, true}, + {UINT64_C(0x623695D495D11C5A), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x87E91F0B7DE6C7A5), UINT64_C(0xBD90261648A8EE85)}, true}, + {UINT64_C(0xEA1E4969126AB23C), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5678306911DBFA21), UINT64_C(0x0BFC7C383593DB01)}, true}, + {UINT64_C(0x05CC09312C3E9F9B), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB59486CC29609065), UINT64_C(0x300AB8E7F58DD49E)}, true}, + {UINT64_C(0x66DE18BBF0EC8645), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xEECAE2FDF2D987A8), UINT64_C(0x903FD6B5FDBD396C)}, true}, + {UINT64_C(0x771C5EDB6865E300), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x10807D45C83F929C), UINT64_C(0xAA010FA0ACB4A2DE)}, true}, + {UINT64_C(0x0D8656F581B26A36), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4884B48A4C4D42BD), UINT64_C(0x7386295CA842ED62)}, true}, + {UINT64_C(0x82D90DCCF0EF67D4), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x6D83BC72961EFDD6), UINT64_C(0x703CCA8334D5F0D3)}, true}, + {UINT64_C(0x77FFFC7414262CCC), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x80F5E44CF8F8C0F6), UINT64_C(0x6A7FC4375C8A174E)}, true}, + {UINT64_C(0x4DB8FFE5868CE91E), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB56724DBA4CA4FE0), UINT64_C(0xC24E796B8CA7A91A)}, true}, + {UINT64_C(0x6FA9EAA188A8E9A8), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x2645EEDA2D96AD00), UINT64_C(0x4E669779FCFA28A7)}, true}, + {UINT64_C(0xD62FCC7C5DA7DF5F), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x3E6FCA0DE0B3A8C2), UINT64_C(0xB3F33593C6D09C78)}, true}, + {UINT64_C(0xA2476D49450F3D33), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xA0BE8B27220B13DA), UINT64_C(0xAE440AA8E3BD1EFF)}, true}, + {UINT64_C(0x11A78BDBB7A54683), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x843B72BABFC575D2), UINT64_C(0x273A3914C45D195F)}, true}, + {UINT64_C(0x4878EFC57C23F637), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x77B1A22643B2D4A5), UINT64_C(0x2650312EFC7A723E)}, true}, + {UINT64_C(0x3CBC4808BED3C0CE), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xF98383D7822FCD4B), UINT64_C(0xB8659B1565935E42)}, true}, + {UINT64_C(0x157511950F5C4257), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x57FCA2530F71C768), UINT64_C(0x31E37C33EEAE5603)}, true}, + {UINT64_C(0x67D5ABC6E2419EBC), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xFF866FD9D067A561), UINT64_C(0x31E37C33EEAE5603)}, true}, + {UINT64_C(0x1CF9353AA1132855), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xBF42AC470AA4CE13), UINT64_C(0xFF772F11F9DA66C7)}, true}, + {UINT64_C(0xC59FCB667E0B50CA), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB35C2C3AC53D594A), UINT64_C(0x59F928CA74FC1F1B)}, true}, + {UINT64_C(0xD2B3995707485985), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x2BA7E44E7E1A1176), UINT64_C(0x4CDED5DED927F9B0)}, true}, + {UINT64_C(0x8B1DB6B090F019E4), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xF91B29D8FC044DBF), UINT64_C(0x959B4F51AB838BB0)}, true}, + {UINT64_C(0x3F5A4F2F89867D02), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x112A2355CC0D704D), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x1D77B495DAD1A0ED), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB4E21448711110DE), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x576ABA2EB83FD092), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x9176F3F9F09C4B6D), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xC4D3A56D246F5008), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x09020258), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x3E9C933709DBFC34), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x2A6DB4A54C1D6A44), UINT64_C(0x06CA19234014B7CF)}, true}, + {UINT64_C(0x1043548A365028B4), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x2A6DB4A54C1D6A44), UINT64_C(0xEFAEF20D9DD3A856)}, true}, + {UINT64_C(0xE44B22B012480B7E), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x229B213C18BF3580), UINT64_C(0x002060C7FFA5DBEE)}, true}, + {UINT64_C(0xDF2B9CC0644CF96B), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC186F78859736B49), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xE53ACE77F5E03E94), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB814BA67EA32F032), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x07F4727750423C34), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708733), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC186F78859736B49), UINT64_C(0xEB864605A91539ED)}, true}, + {UINT64_C(0xBAF6F0A8238A1617), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708733), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB814BA67EA32F032), UINT64_C(0xE1D8B5E58C05821E)}, true}, + {UINT64_C(0x95825DA3BEB63618), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4DA02D43D609305C), UINT64_C(0x826E70D1F5709AB4)}, true}, + {UINT64_C(0xA86486AAEC6B8FD3), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x87E91F0B7DE6C7A5), UINT64_C(0xBD90261648A8EE85)}, true}, + {UINT64_C(0x219888D0C81A4EBB), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4884B48A4C4D42BD), UINT64_C(0x7386295CA842ED62)}, true}, + {UINT64_C(0xFE2CEB59D8971A5A), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4DA02D43D609305C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x6DBBC8AC52778C65), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x87E91F0B7DE6C7A5), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xF8D6304318883E2E), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4884B48A4C4D42BD), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x6F421B04AD229861), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x129E7F5770905F10), UINT64_C(0x437E40C3B326C513)}, true}, + {UINT64_C(0xBE24F96A2F86A743), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xBBD7F3BD8E43C865), UINT64_C(0x26A449F09A551230)}, true}, + {UINT64_C(0xBC63805B609732D4), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x11D7AC81789C78E2), UINT64_C(0x0D70BBC32834DD35)}, true}, + {UINT64_C(0xF88AEB71252E4AC0), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4DA02D43D609305C), UINT64_C(0x826E70D1F5709AB4)}, true}, + {UINT64_C(0x045001575D46C1BB), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x87E91F0B7DE6C7A5), UINT64_C(0xBD90261648A8EE85)}, true}, + {UINT64_C(0xB48E7907D9CC5CDD), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5678306911DBFA21), UINT64_C(0x0BFC7C383593DB01)}, true}, + {UINT64_C(0x6C53B91F9E7CDEEA), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB59486CC29609065), UINT64_C(0x300AB8E7F58DD49E)}, true}, + {UINT64_C(0x1A2A2338B4866624), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xEECAE2FDF2D987A8), UINT64_C(0x903FD6B5FDBD396C)}, true}, + {UINT64_C(0xAD058C3644007689), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x10807D45C83F929C), UINT64_C(0xAA010FA0ACB4A2DE)}, true}, + {UINT64_C(0xA376541F441E4FB3), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x4884B48A4C4D42BD), UINT64_C(0x7386295CA842ED62)}, true}, + {UINT64_C(0x5B5BDF4A7233BCB5), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x6D83BC72961EFDD6), UINT64_C(0x703CCA8334D5F0D3)}, true}, + {UINT64_C(0x1D397F204E732829), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x80F5E44CF8F8C0F6), UINT64_C(0x6A7FC4375C8A174E)}, true}, + {UINT64_C(0x5E9A899407A60B07), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB56724DBA4CA4FE0), UINT64_C(0xC24E796B8CA7A91A)}, true}, + {UINT64_C(0xBFF2A6E479D2494D), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x2645EEDA2D96AD00), UINT64_C(0x4E669779FCFA28A7)}, true}, + {UINT64_C(0x0562682FA93BDFC6), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x3E6FCA0DE0B3A8C2), UINT64_C(0xB3F33593C6D09C78)}, true}, + {UINT64_C(0x5933941C92C6ACE2), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xA0BE8B27220B13DA), UINT64_C(0xAE440AA8E3BD1EFF)}, true}, + {UINT64_C(0x5490C50558B0A6FA), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x843B72BABFC575D2), UINT64_C(0x273A3914C45D195F)}, true}, + {UINT64_C(0x8E3E16B4041D10A6), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x77B1A22643B2D4A5), UINT64_C(0x2650312EFC7A723E)}, true}, + {UINT64_C(0x80D634DEEEFA3023), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xF98383D7822FCD4B), UINT64_C(0xB8659B1565935E42)}, true}, + {UINT64_C(0x3B4A27CE871F46DE), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x57FCA2530F71C768), UINT64_C(0x31E37C33EEAE5603)}, true}, + {UINT64_C(0xAFD8D05CE702C08D), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xFF866FD9D067A561), UINT64_C(0x31E37C33EEAE5603)}, true}, + {UINT64_C(0xFB8DCC055DF6E894), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xBF42AC470AA4CE13), UINT64_C(0xFF772F11F9DA66C7)}, true}, + {UINT64_C(0x86158F7A9968A9FF), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB35C2C3AC53D594A), UINT64_C(0x59F928CA74FC1F1B)}, true}, + {UINT64_C(0x546215D3B88C4748), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x2BA7E44E7E1A1176), UINT64_C(0x4CDED5DED927F9B0)}, true}, + {UINT64_C(0xF6B8FF0D0BDCD5AD), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xF91B29D8FC044DBF), UINT64_C(0x959B4F51AB838BB0)}, true}, + {UINT64_C(0x4F1CF2D9F10DDC4F), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x112A2355CC0D704D), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x67D152C6CBD00FD8), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB4E21448711110DE), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x09BC09F4487FDEEF), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x9176F3F9F09C4B6D), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xEEAE693F419ED9C4), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0xA4A965C189287B99)}, true}, + {UINT64_C(0xC4EC310C78501C89), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x11D7AC81789C78E2), UINT64_C(0x0D70BBC32834DD35)}, true}, + {UINT64_C(0x57C306778FFCE634), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x11D7AC81789C78E2), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x7E8329B3B34B568C), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x11D7AC81789C78E2), UINT64_C(0x0D70BBC32834DD35)}, true}, + {UINT64_C(0x9AF67C1814B20861), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x11D7AC81789C78E2), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xBCF17602372C8923), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x768D0C1832BAA99F), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x863A82C331CA4E7A), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xDD8686FDE7D4FE2F), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xA1039E2BA9F6F0F2), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x1153D3552EF6E624), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xD30D492BB72A8E84), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5678306911DBFA21), UINT64_C(0x0BFC7C383593DB01)}, true}, + {UINT64_C(0x6C53DC64C88E5653), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB59486CC29609065), UINT64_C(0x300AB8E7F58DD49E)}, true}, + {UINT64_C(0x112DABFB9C0D2C5D), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xEECAE2FDF2D987A8), UINT64_C(0x903FD6B5FDBD396C)}, true}, + {UINT64_C(0x75445340AA61B6B8), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x10807D45C83F929C), UINT64_C(0xAA010FA0ACB4A2DE)}, true}, + {UINT64_C(0xFBFF33846B090543), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5678306911DBFA21), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x19B440C9ECEB7000), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB59486CC29609065), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x7A1B51CF16B6E6BA), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xEECAE2FDF2D987A8), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xF122FFFE0AC6ACA0), {SemanticViewportClass::kDoubleWideGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000280), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x10807D45C83F929C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x46DAB5A8908F95E5), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5678306911DBFA21), UINT64_C(0x0BFC7C383593DB01)}, true}, + {UINT64_C(0x3F49FEE14362C5E2), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB59486CC29609065), UINT64_C(0x300AB8E7F58DD49E)}, true}, + {UINT64_C(0x69EF120F538756DC), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xEECAE2FDF2D987A8), UINT64_C(0x903FD6B5FDBD396C)}, true}, + {UINT64_C(0xEADF80DF6BCFBDA1), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000007), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x10807D45C83F929C), UINT64_C(0xAA010FA0ACB4A2DE)}, true}, + {UINT64_C(0x130AEC24E5FEB916), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5678306911DBFA21), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x544BEB932F0B2CCD), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xB59486CC29609065), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x457ECAD3E8859627), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xEECAE2FDF2D987A8), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0xD465D0FF609B8C01), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x10807D45C83F929C), UINT64_C(0x0000000000000000)}, true}, + {UINT64_C(0x992F65716DDE5AB0), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x120104B0), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0xA4A965C189287B99)}, true}, + {UINT64_C(0x47CEC4DBD2618777), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700732), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x3D5BF97DD0CF2BF4), UINT64_C(0xB1592798F0CD1839)}, true}, + {UINT64_C(0x16721B512CC8E52A), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700736), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0xE91029D930637F6E), UINT64_C(0x38818B3E2ED482F4)}, true}, + {UINT64_C(0xA2D3BF5F1FAE89A5), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x08000230), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x5AB7345B9857FD4B), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x6F2FE2DB5E2C9D9B), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x8A6134DE7D8980C0), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x3819C68257AAB4A7), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC0A7FEA59D737772), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x161CCBE6FB7FA2CB), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xAAE4AE2E54C97D0F), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x0C945E0D516ACB09), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x69162B400E55DAC4), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x593760193C97EA86), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xDD8686FDE7D4FE2F), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x1760A25BDB3C3A8F), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x768D0C1832BAA99F), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0xF43F611A283B53B4), {SemanticViewportClass::kSquare, UINT32_C(0x00000800), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x3F7D7B06780F7147), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0xD760920DBAB9A3BF), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x8A6134DE7D8980C0), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x26BDC6C599B4969B), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xC0A7FEA59D737772), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x0B1AAB009E382577), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xAAE4AE2E54C97D0F), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0xA965E653AFB7AA55), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x69162B400E55DAC4), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0xCF98B5A98C16103B), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0x768D0C1832BAA99F), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x1287298BB43864D2), {SemanticViewportClass::kSquare, UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x00000400), UINT32_C(0x00000400), UINT32_C(0x00000000), UINT32_C(0x18000640), UINT32_C(0x00000000), UINT32_C(0x00708737), UINT32_C(0x0000043F), UINT32_C(0x00000004), UINT32_C(0x00000000), UINT64_C(0xDD8686FDE7D4FE2F), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0xB8ED22303014A9A0), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x47A7554B0F7A34EC), UINT64_C(0xC9BD70547A68F4C5)}, false}, + {UINT64_C(0xAE5EF820D5073D51), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x0000000F), UINT32_C(0x00700770), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x3EC739DF445745A9), UINT64_C(0x36F744866629EE18)}, false}, + {UINT64_C(0xE28F8BEB4E09C12D), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x0000000F), UINT32_C(0x00700770), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x3EC739DF445745A9), UINT64_C(0x7AF459B0BFF69478)}, false}, + {UINT64_C(0xBFC1B2D8F2E33B38), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000900), UINT32_C(0x00000500), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x8895AB90906ECBC1), UINT64_C(0x5928557DC4F52989)}, false}, + {UINT64_C(0x9560A636E29DFA8A), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000240), UINT32_C(0x00000140), UINT32_C(0x000002E0), UINT32_C(0x04800140), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0x8064167EECA65C8D)}, false}, + {UINT64_C(0x130889F224084C65), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000240), UINT32_C(0x00000140), UINT32_C(0x000002E0), UINT32_C(0x04800140), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0x35754B7A87E5683D)}, false}, + {UINT64_C(0xAD7D02EB7119AD40), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000240), UINT32_C(0x00000140), UINT32_C(0x000002E0), UINT32_C(0x04800140), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x00000006), UINT32_C(0x00000000), UINT64_C(0x08016D40A0EF5371), UINT64_C(0x3A7C78D0EB806EE8)}, false}, + {UINT64_C(0xCC1AC526FE146AB0), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x3D5BF97DD0CF2BF4), UINT64_C(0xB1592798F0CD1839)}, false}, + {UINT64_C(0x6329B6390DA4F1F9), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x0A020280), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x4001CB4958EAF13D), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x0A020280), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0xA4A965C189287B99)}, false}, + {UINT64_C(0xAF9DF4D3A87129B8), {SemanticViewportClass::kGameAspect, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000A00), UINT32_C(0x000005A0), UINT32_C(0x000002D0), UINT32_C(0x14000500), UINT32_C(0x0000000F), UINT32_C(0x00700730), UINT32_C(0x0000043F), UINT32_C(0x0000000D), UINT32_C(0x00000000), UINT64_C(0x166196AE4F110875), UINT64_C(0x39004B0872361843)}, false}, + {UINT64_C(0xBD81D7A062FC455D), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00000300), UINT32_C(0x00000001), UINT32_C(0x00000000), UINT64_C(0xB6C9863F710683EC), UINT64_C(0x0000000000000000)}, false}, + {UINT64_C(0x73EBDF3D39AFE8D4), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002E0), UINT32_C(0x120004B0), UINT32_C(0x0000000F), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0xA4A965C189287B99)}, false}, + {UINT64_C(0x8CC88D3928860FC0), {SemanticViewportClass::kSquare, UINT32_C(0x00000000), UINT32_C(0x00000000), UINT32_C(0x00004000), UINT32_C(0x00004000), UINT32_C(0x000002D0), UINT32_C(0x0A020280), UINT32_C(0x0000000F), UINT32_C(0x00008777), UINT32_C(0x00000300), UINT32_C(0x00000008), UINT32_C(0x00000000), UINT64_C(0x1E6883FCCDE1F688), UINT64_C(0xA4A965C189287B99)}, false}, +}; diff --git a/src/core/ultrawide_debug.cpp b/src/core/ultrawide_debug.cpp new file mode 100644 index 0000000..7002514 --- /dev/null +++ b/src/core/ultrawide_debug.cpp @@ -0,0 +1,1526 @@ +#include + +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +REXCVAR_DEFINE_BOOL(skate3_ultrawide_screen_callback_tracking, false, "Skate 3/Ultrawide", + "Track guest screen callback callers in the F7 ultrawide overlay") + .lifecycle(rex::cvar::Lifecycle::kHotReload); + +namespace rex::graphics::ultrawide_debug { + +namespace { + +void HashAdd(uint64_t& hash, uint64_t value); + +struct TargetKeyHash { + size_t operator()(const TargetKey& key) const { + return size_t(HashKey(key)); + } +}; + +enum class SemanticViewportClass : uint32_t { + kUnknown = 0, + kGameAspect = 1, + kDoubleWideGameAspect = 2, + kSquare = 3, +}; + +struct NormalizedViewport { + SemanticViewportClass viewport_class = SemanticViewportClass::kUnknown; + uint32_t x = 0; + uint32_t y = 0; + uint32_t width = 0; + uint32_t height = 0; +}; + +struct SemanticTargetKey { + SemanticViewportClass viewport_class = SemanticViewportClass::kUnknown; + uint32_t viewport_x = 0; + uint32_t viewport_y = 0; + uint32_t viewport_width = 0; + uint32_t viewport_height = 0; + uint32_t depth_info = 0; + uint32_t surface_info = 0; + uint32_t color_mask = 0; + uint32_t depth_control = 0; + uint32_t pa_cl_vte_cntl = 0; + uint32_t primitive_type = 0; + uint32_t host_vertex_shader_type = 0; + uint64_t vertex_shader_hash = 0; + uint64_t pixel_shader_hash = 0; + + bool operator==(const SemanticTargetKey& other) const { + return viewport_class == other.viewport_class && color_mask == other.color_mask && + viewport_x == other.viewport_x && viewport_y == other.viewport_y && + viewport_width == other.viewport_width && viewport_height == other.viewport_height && + depth_info == other.depth_info && surface_info == other.surface_info && + depth_control == other.depth_control && pa_cl_vte_cntl == other.pa_cl_vte_cntl && + primitive_type == other.primitive_type && + host_vertex_shader_type == other.host_vertex_shader_type && + vertex_shader_hash == other.vertex_shader_hash && + pixel_shader_hash == other.pixel_shader_hash; + } +}; + +struct SemanticTargetKeyHash { + size_t operator()(const SemanticTargetKey& key) const { + uint64_t hash = 1469598103934665603ull; + HashAdd(hash, uint32_t(key.viewport_class)); + HashAdd(hash, key.viewport_x); + HashAdd(hash, key.viewport_y); + HashAdd(hash, key.viewport_width); + HashAdd(hash, key.viewport_height); + HashAdd(hash, key.depth_info); + HashAdd(hash, key.surface_info); + HashAdd(hash, key.color_mask); + HashAdd(hash, key.depth_control); + HashAdd(hash, key.pa_cl_vte_cntl); + HashAdd(hash, key.primitive_type); + HashAdd(hash, key.host_vertex_shader_type); + HashAdd(hash, key.vertex_shader_hash); + HashAdd(hash, key.pixel_shader_hash); + return size_t(hash); + } +}; + +struct BuiltInTargetOverride { + uint64_t hash = 0; + SemanticTargetKey key; + bool enabled = false; +}; + +#include "skate3_ultrawide_classifier.inc" + +struct DrawFingerprintKey { + DrawFingerprint fingerprint; + + bool operator==(const DrawFingerprintKey& other) const { + const DrawFingerprint& a = fingerprint; + const DrawFingerprint& b = other.fingerprint; + return a.bucket == b.bucket && a.guest_render_object == b.guest_render_object && + a.guest_render_vtable == b.guest_render_vtable && + a.guest_render_target == b.guest_render_target && + a.vertex_shader_hash == b.vertex_shader_hash && + a.pixel_shader_hash == b.pixel_shader_hash && a.primitive_type == b.primitive_type && + a.vertex_count == b.vertex_count && a.primitive_count == b.primitive_count && + a.index_guest_base == b.index_guest_base && a.index_length == b.index_length && + std::equal(std::begin(a.vertex_fetch_address), std::end(a.vertex_fetch_address), + std::begin(b.vertex_fetch_address)) && + std::equal(std::begin(a.vertex_fetch_size), std::end(a.vertex_fetch_size), + std::begin(b.vertex_fetch_size)) && + std::equal(std::begin(a.texture_key_hash), std::end(a.texture_key_hash), + std::begin(b.texture_key_hash)) && + std::equal(std::begin(a.texture_fetch_index), std::end(a.texture_fetch_index), + std::begin(b.texture_fetch_index)); + } +}; + +struct DrawFingerprintKeyHash { + size_t operator()(const DrawFingerprintKey& key) const { + return size_t(HashDrawFingerprint(key.fingerprint)); + } +}; + +std::mutex g_mutex; +std::unordered_map g_targets; +std::unordered_map g_hash_overrides; +std::atomic>> g_hash_overrides_snapshot = + std::make_shared>(); +std::unordered_map g_semantic_overrides; +std::atomic>> + g_semantic_overrides_snapshot = + std::make_shared>(); +std::unordered_map g_screen_callbacks; +std::unordered_map g_screen_callback_overrides; +std::deque g_guest_store_watch_events; +std::unordered_map + g_draw_fingerprints; +std::unordered_map g_draw_fingerprint_overrides; +uint64_t g_seen_order = 0; +uint64_t g_guest_store_watch_sequence = 0; +uint64_t g_guest_store_watch_group_hash = 0; +std::filesystem::path g_guest_store_watch_log_path; +std::ofstream g_guest_store_watch_log; +std::atomic g_apply_mode = ApplyMode::kTargetList; +std::atomic g_target_overrides_loaded = false; +std::atomic g_builtin_target_overrides_active = false; +std::atomic g_target_recording_active = false; +std::atomic g_skate3_gameplay_ultrawide_latched = false; +std::atomic g_target_decision_cache_epoch = 1; +std::atomic g_guest_store_watch_enabled = false; +std::atomic g_guest_store_watch_index_base = 0; +std::atomic g_guest_store_watch_vertex_fetch_address = 0; +std::atomic g_guest_store_watch_texture_base_address = 0; +constexpr size_t kMaxGuestStoreWatchEvents = 256; +constexpr size_t kMaxDrawFingerprints = 4096; +constexpr size_t kMaxGuestFunctionStackDepth = 96; +thread_local const char* g_guest_function_stack[kMaxGuestFunctionStackDepth] = {}; +thread_local uint32_t g_guest_function_stack_depth = 0; +thread_local uint32_t g_current_draw_owner_object = 0; +thread_local uint32_t g_current_draw_owner_vtable = 0; +thread_local uint32_t g_current_draw_owner_target = 0; + +void InvalidateTargetDecisionCache() { + g_target_decision_cache_epoch.fetch_add(1, std::memory_order_acq_rel); +} + +void PublishHashOverridesSnapshotLocked() { + g_hash_overrides_snapshot.store( + std::make_shared>(g_hash_overrides), + std::memory_order_release); + InvalidateTargetDecisionCache(); +} + +void PublishSemanticOverridesSnapshotLocked() { + g_semantic_overrides_snapshot.store( + std::make_shared>( + g_semantic_overrides), + std::memory_order_release); + InvalidateTargetDecisionCache(); +} + +void InstallBuiltInSkate3ClassifierLocked() { + std::unordered_map loaded_overrides; + std::unordered_map loaded_semantic_overrides; + loaded_overrides.reserve( + sizeof(kBuiltInSkate3TargetOverrides) / sizeof(kBuiltInSkate3TargetOverrides[0])); + loaded_semantic_overrides.reserve( + sizeof(kBuiltInSkate3TargetOverrides) / sizeof(kBuiltInSkate3TargetOverrides[0])); + for (const BuiltInTargetOverride& target : kBuiltInSkate3TargetOverrides) { + loaded_overrides[target.hash] = target.enabled; + loaded_semantic_overrides[target.key] = target.enabled; + } + + g_hash_overrides = std::move(loaded_overrides); + PublishHashOverridesSnapshotLocked(); + g_semantic_overrides = std::move(loaded_semantic_overrides); + PublishSemanticOverridesSnapshotLocked(); + g_apply_mode.store(ApplyMode::kTargetList, std::memory_order_relaxed); + g_builtin_target_overrides_active.store(true, std::memory_order_release); + g_target_overrides_loaded.store(true, std::memory_order_release); +} + +void EnsureBuiltInSkate3ClassifierLoaded() { + if (g_target_overrides_loaded.load(std::memory_order_acquire)) { + return; + } + + std::lock_guard lock(g_mutex); + if (g_target_overrides_loaded.load(std::memory_order_relaxed)) { + return; + } + InstallBuiltInSkate3ClassifierLocked(); +} + +void HashAdd(uint64_t& hash, uint64_t value) { + for (size_t i = 0; i < sizeof(value); ++i) { + hash ^= uint8_t(value >> (i * 8)); + hash *= 1099511628211ull; + } +} + +void WriteHex(std::ostream& stream, uint64_t value, uint32_t width) { + stream << "0x" << std::uppercase << std::hex << std::setw(width) << std::setfill('0') << value + << std::dec << std::setfill(' '); +} + +std::string_view ScreenCallbackKindName(ScreenCallbackKind kind) { + switch (kind) { + case ScreenCallbackKind::kWidth: + return "width"; + case ScreenCallbackKind::kHeight: + return "height"; + case ScreenCallbackKind::kScreenFlag: + return "screen_flag"; + case ScreenCallbackKind::kWide: + return "wide"; + } + return "unknown"; +} + +std::optional ParseScreenCallbackKind(std::string_view name) { + if (name == "width") { + return ScreenCallbackKind::kWidth; + } + if (name == "height") { + return ScreenCallbackKind::kHeight; + } + if (name == "screen_flag") { + return ScreenCallbackKind::kScreenFlag; + } + if (name == "wide") { + return ScreenCallbackKind::kWide; + } + return std::nullopt; +} + +std::optional ParseHexString(std::string_view text) { + if (text.starts_with("0x") || text.starts_with("0X")) { + text.remove_prefix(2); + } + if (text.empty()) { + return std::nullopt; + } + + uint64_t value = 0; + for (char c : text) { + uint32_t digit = 0; + if (c >= '0' && c <= '9') { + digit = uint32_t(c - '0'); + } else if (c >= 'a' && c <= 'f') { + digit = uint32_t(c - 'a') + 10; + } else if (c >= 'A' && c <= 'F') { + digit = uint32_t(c - 'A') + 10; + } else { + return std::nullopt; + } + value = (value << 4) | digit; + } + return value; +} + +std::optional ParseU32TomlValue(toml::node_view value) { + if (auto integer = value.value()) { + if (*integer >= 0 && *integer <= UINT32_MAX) { + return uint32_t(*integer); + } + return std::nullopt; + } + if (auto text = value.value()) { + const auto parsed = ParseHexString(*text); + if (parsed && *parsed <= UINT32_MAX) { + return uint32_t(*parsed); + } + } + return std::nullopt; +} + +std::optional ParseU64TomlValue(toml::node_view value) { + if (auto integer = value.value()) { + if (*integer >= 0) { + return uint64_t(*integer); + } + return std::nullopt; + } + if (auto text = value.value()) { + return ParseHexString(*text); + } + return std::nullopt; +} + +SemanticViewportClass ClassifyViewport(uint32_t width, uint32_t height) { + if (!width || !height) { + return SemanticViewportClass::kUnknown; + } + + const double aspect = double(width) / double(height); + if (std::abs(aspect - (16.0 / 9.0)) <= 0.08) { + return SemanticViewportClass::kGameAspect; + } + if (std::abs(aspect - (32.0 / 9.0)) <= 0.16) { + return SemanticViewportClass::kDoubleWideGameAspect; + } + if (std::abs(aspect - 1.0) <= 0.03) { + return SemanticViewportClass::kSquare; + } + return SemanticViewportClass::kUnknown; +} + +uint32_t DivRound(uint64_t value, uint64_t divisor) { + if (!divisor) { + return value > UINT32_MAX ? UINT32_MAX : uint32_t(value); + } + const uint64_t result = (value + divisor / 2) / divisor; + return result > UINT32_MAX ? UINT32_MAX : uint32_t(result); +} + +std::optional NormalizeKnownViewport(uint32_t x, uint32_t y, uint32_t width, + uint32_t height) { + struct KnownViewportSize { + uint32_t width; + uint32_t height; + }; + struct KnownViewportScale { + uint32_t numerator; + uint32_t denominator; + }; + constexpr KnownViewportSize kKnownBaseSizes[] = { + {2304, 1280}, {2304, 640}, {2560, 1440}, {64, 32}, {144, 80}, + {576, 320}, {1024, 1024}, {16384, 16384}, + }; + constexpr KnownViewportScale kKnownScales[] = { + {1, 2}, {1, 1}, {3, 2}, {2, 1}, {3, 1}, {4, 1}, + }; + + for (const KnownViewportSize& base : kKnownBaseSizes) { + if (!base.width || !base.height) { + continue; + } + for (const KnownViewportScale& scale : kKnownScales) { + if (uint64_t(base.width) * scale.numerator != uint64_t(width) * scale.denominator || + uint64_t(base.height) * scale.numerator != uint64_t(height) * scale.denominator) { + continue; + } + + NormalizedViewport viewport; + viewport.viewport_class = ClassifyViewport(base.width, base.height); + viewport.x = DivRound(uint64_t(x) * scale.denominator, scale.numerator); + viewport.y = DivRound(uint64_t(y) * scale.denominator, scale.numerator); + viewport.width = base.width; + viewport.height = base.height; + return viewport; + } + } + return std::nullopt; +} + +NormalizedViewport NormalizeViewport(uint32_t x, uint32_t y, uint32_t width, uint32_t height) { + NormalizedViewport viewport; + viewport.viewport_class = ClassifyViewport(width, height); + viewport.x = x; + viewport.y = y; + viewport.width = width; + viewport.height = height; + + if (const auto normalized_viewport = NormalizeKnownViewport(x, y, width, height)) { + return *normalized_viewport; + } + + return viewport; +} + +SemanticTargetKey MakeSemanticTargetKey(const TargetKey& key) { + SemanticTargetKey semantic_key; + const NormalizedViewport viewport = + NormalizeViewport(key.viewport_x, key.viewport_y, key.viewport_width, key.viewport_height); + semantic_key.viewport_class = viewport.viewport_class; + semantic_key.viewport_x = viewport.x; + semantic_key.viewport_y = viewport.y; + semantic_key.viewport_width = viewport.width; + semantic_key.viewport_height = viewport.height; + semantic_key.depth_info = key.depth_info; + semantic_key.surface_info = key.surface_info; + semantic_key.color_mask = key.color_mask; + semantic_key.depth_control = key.depth_control; + semantic_key.pa_cl_vte_cntl = key.pa_cl_vte_cntl; + semantic_key.primitive_type = key.primitive_type; + semantic_key.host_vertex_shader_type = key.host_vertex_shader_type; + semantic_key.vertex_shader_hash = key.vertex_shader_hash; + semantic_key.pixel_shader_hash = key.pixel_shader_hash; + return semantic_key; +} + +std::optional ParseSemanticTargetKey(toml::table& target) { + auto* viewport = target["viewport"].as_array(); + if (!viewport || viewport->size() < 4) { + return std::nullopt; + } + + const auto viewport_x = viewport->get(0) ? viewport->get(0)->value() : std::nullopt; + const auto viewport_y = viewport->get(1) ? viewport->get(1)->value() : std::nullopt; + const auto viewport_width = viewport->get(2) ? viewport->get(2)->value() : std::nullopt; + const auto viewport_height = + viewport->get(3) ? viewport->get(3)->value() : std::nullopt; + const auto depth_info = ParseU32TomlValue(target["depth_info"]); + const auto surface_info = ParseU32TomlValue(target["surface_info"]); + const auto color_mask = ParseU32TomlValue(target["color_mask"]); + const auto depth_control = ParseU32TomlValue(target["depth_control"]); + const auto pa_cl_vte_cntl = ParseU32TomlValue(target["pa_cl_vte_cntl"]); + const auto primitive_type = ParseU32TomlValue(target["primitive_type"]); + const auto host_vertex_shader_type = ParseU32TomlValue(target["host_vertex_shader_type"]); + const auto vertex_shader_hash = ParseU64TomlValue(target["vertex_shader_hash"]); + const auto pixel_shader_hash = ParseU64TomlValue(target["pixel_shader_hash"]); + if (!viewport_x || !viewport_y || !viewport_width || !viewport_height || *viewport_x < 0 || + *viewport_y < 0 || *viewport_width < 0 || *viewport_height < 0 || + *viewport_x > UINT32_MAX || *viewport_y > UINT32_MAX || *viewport_width > UINT32_MAX || + *viewport_height > UINT32_MAX || !depth_info || !surface_info || !color_mask || + !depth_control || !pa_cl_vte_cntl || !primitive_type || !host_vertex_shader_type || + !vertex_shader_hash || !pixel_shader_hash) { + return std::nullopt; + } + + SemanticTargetKey semantic_key; + const NormalizedViewport normalized_viewport = + NormalizeViewport(uint32_t(*viewport_x), uint32_t(*viewport_y), uint32_t(*viewport_width), + uint32_t(*viewport_height)); + semantic_key.viewport_class = normalized_viewport.viewport_class; + semantic_key.viewport_x = normalized_viewport.x; + semantic_key.viewport_y = normalized_viewport.y; + semantic_key.viewport_width = normalized_viewport.width; + semantic_key.viewport_height = normalized_viewport.height; + semantic_key.depth_info = *depth_info; + semantic_key.surface_info = *surface_info; + semantic_key.color_mask = *color_mask; + semantic_key.depth_control = *depth_control; + semantic_key.pa_cl_vte_cntl = *pa_cl_vte_cntl; + semantic_key.primitive_type = *primitive_type; + semantic_key.host_vertex_shader_type = *host_vertex_shader_type; + semantic_key.vertex_shader_hash = *vertex_shader_hash; + semantic_key.pixel_shader_hash = *pixel_shader_hash; + return semantic_key; +} + +std::optional FindSemanticOverride(const TargetKey& key) { + const auto overrides = g_semantic_overrides_snapshot.load(std::memory_order_acquire); + if (!overrides) { + return std::nullopt; + } + const auto semantic_key = MakeSemanticTargetKey(key); + const auto it = overrides->find(semantic_key); + if (it == overrides->end()) { + return std::nullopt; + } + return it->second; +} + +std::optional FindBuiltInFallbackOverride(const TargetKey& key) { + if (!g_builtin_target_overrides_active.load(std::memory_order_acquire)) { + return std::nullopt; + } + + const NormalizedViewport viewport = + NormalizeViewport(key.viewport_x, key.viewport_y, key.viewport_width, key.viewport_height); + if (viewport.x == 0 && viewport.y == 0 && viewport.width == 2304 && viewport.height == 640) { + return true; + } + return std::nullopt; +} + +uint64_t NowMilliseconds() { + return uint64_t(std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count()); +} + +void OpenGuestStoreWatchLogLocked(bool truncate) { + if (g_guest_store_watch_log_path.empty()) { + return; + } + if (g_guest_store_watch_log.is_open()) { + return; + } + std::error_code error; + std::filesystem::create_directories(g_guest_store_watch_log_path.parent_path(), error); + g_guest_store_watch_log.open( + g_guest_store_watch_log_path, + std::ios::out | (truncate ? std::ios::trunc : std::ios::app)); + if (g_guest_store_watch_log) { + g_guest_store_watch_log + << "ms,event,seq,group,target,state,age,pkt,ib,vf0,tex,addr,value,caller_lr,function,stack,count\n"; + } +} + +void WriteGuestStoreWatchTargetsLocked(const char* event_name) { + OpenGuestStoreWatchLogLocked(false); + if (!g_guest_store_watch_log) { + return; + } + const uint32_t index_base = + g_guest_store_watch_index_base.load(std::memory_order_relaxed); + const uint32_t vertex_fetch_address = + g_guest_store_watch_vertex_fetch_address.load(std::memory_order_relaxed); + const uint32_t texture_base_address = + g_guest_store_watch_texture_base_address.load(std::memory_order_relaxed); + g_guest_store_watch_log << NowMilliseconds() << "," << event_name << ",0,"; + WriteHex(g_guest_store_watch_log, g_guest_store_watch_group_hash, 16); + g_guest_store_watch_log << ",,,0,,"; + WriteHex(g_guest_store_watch_log, index_base, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, vertex_fetch_address, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, texture_base_address, 8); + g_guest_store_watch_log << ",,,,,0\n"; + g_guest_store_watch_log.flush(); +} + +void WriteGuestStoreWatchStoreLocked(const GuestStoreWatchEntry& entry) { + OpenGuestStoreWatchLogLocked(false); + if (!g_guest_store_watch_log) { + return; + } + const uint32_t index_base = + g_guest_store_watch_index_base.load(std::memory_order_relaxed); + const uint32_t vertex_fetch_address = + g_guest_store_watch_vertex_fetch_address.load(std::memory_order_relaxed); + const uint32_t texture_base_address = + g_guest_store_watch_texture_base_address.load(std::memory_order_relaxed); + g_guest_store_watch_log << NowMilliseconds() << ",store," << entry.sequence << ","; + WriteHex(g_guest_store_watch_log, g_guest_store_watch_group_hash, 16); + g_guest_store_watch_log << "," << entry.target << ",,,0,"; + WriteHex(g_guest_store_watch_log, index_base, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, vertex_fetch_address, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, texture_base_address, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, entry.address, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, entry.value, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, entry.caller_lr, 8); + g_guest_store_watch_log << "," << entry.function << "," << entry.stack << "," << entry.count + << "\n"; + g_guest_store_watch_log.flush(); +} + +const char* DrawBucketName(DrawBucket bucket) { + switch (bucket) { + case DrawBucket::kMainColorDepth: + return "Main"; + case DrawBucket::kDepthOnly: + return "Depth"; + case DrawBucket::kCopyResolve: + return "Copy"; + case DrawBucket::kMemexport: + return "Memexport"; + case DrawBucket::kNoPixelShader: + return "NoPS"; + } + return "Unknown"; +} + +bool DrawHasWatchedTexture(const DrawFingerprint& fingerprint, uint32_t texture_base_address) { + if (texture_base_address == 0) { + return false; + } + for (uint32_t address : fingerprint.texture_base_address) { + if (address == texture_base_address) { + return true; + } + } + return false; +} + +bool DrawHasWatchedVertexFetch(const DrawFingerprint& fingerprint, + uint32_t vertex_fetch_address) { + if (vertex_fetch_address == 0) { + return true; + } + for (uint32_t address : fingerprint.vertex_fetch_address) { + if (address == vertex_fetch_address) { + return true; + } + } + return false; +} + +bool DrawMatchesGuestStoreWatchTargets(const DrawFingerprint& fingerprint) { + if (!g_guest_store_watch_enabled.load(std::memory_order_relaxed)) { + return false; + } + const uint32_t index_base = + g_guest_store_watch_index_base.load(std::memory_order_relaxed); + const uint32_t vertex_fetch_address = + g_guest_store_watch_vertex_fetch_address.load(std::memory_order_relaxed); + if (index_base != 0 && fingerprint.index_guest_base != index_base) { + return false; + } + return DrawHasWatchedVertexFetch(fingerprint, vertex_fetch_address); +} + +void WriteGuestStoreWatchDrawLocked(const DrawFingerprint& fingerprint, bool submitted, + uint64_t exact_hash) { + if (!DrawMatchesGuestStoreWatchTargets(fingerprint)) { + return; + } + OpenGuestStoreWatchLogLocked(false); + if (!g_guest_store_watch_log) { + return; + } + const uint32_t texture_base_address = + g_guest_store_watch_texture_base_address.load(std::memory_order_relaxed); + const bool has_watched_texture = DrawHasWatchedTexture(fingerprint, texture_base_address); + const bool main_textured = + has_watched_texture && fingerprint.bucket == DrawBucket::kMainColorDepth; + g_guest_store_watch_log << NowMilliseconds() << ",draw_seen,0,"; + WriteHex(g_guest_store_watch_log, g_guest_store_watch_group_hash, 16); + g_guest_store_watch_log << "," << DrawBucketName(fingerprint.bucket) << "," + << (main_textured ? "main_tex" : (has_watched_texture ? "tex" : "no_tex")) + << ",0,"; + WriteHex(g_guest_store_watch_log, fingerprint.packet_ptr, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, fingerprint.index_guest_base, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, fingerprint.vertex_fetch_address[0], 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, texture_base_address, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, uint32_t(fingerprint.vertex_shader_hash), 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, uint32_t(fingerprint.pixel_shader_hash), 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, uint32_t(fingerprint.primitive_type), 8); + g_guest_store_watch_log << ",vs="; + WriteHex(g_guest_store_watch_log, fingerprint.vertex_shader_hash, 16); + g_guest_store_watch_log << " ps="; + WriteHex(g_guest_store_watch_log, fingerprint.pixel_shader_hash, 16); + g_guest_store_watch_log << " exact="; + WriteHex(g_guest_store_watch_log, exact_hash, 16); + g_guest_store_watch_log << " submitted=" << (submitted ? "1" : "0"); + g_guest_store_watch_log << ",v=" << fingerprint.vertex_count << " p=" + << fingerprint.primitive_count << ",1\n"; + g_guest_store_watch_log.flush(); +} + +std::string CurrentGuestFunctionStack() { + std::string stack; + const uint32_t depth = std::min(g_guest_function_stack_depth, 12); + for (uint32_t i = 0; i < depth; ++i) { + const uint32_t index = g_guest_function_stack_depth - depth + i; + const char* function_name = g_guest_function_stack[index]; + if (!function_name) { + continue; + } + if (!stack.empty()) { + stack += ">"; + } + stack += function_name; + } + return stack; +} + +} // namespace + +void NotifySkate3GameplayContext(uint32_t user_index, uint32_t context_id, uint32_t value) { + constexpr uint32_t kSkate3GameplayContext = 0x8001; + constexpr uint32_t kSkate3GameplayValue = 1; + if (user_index != 0 || context_id != kSkate3GameplayContext || value != kSkate3GameplayValue) { + return; + } + + bool expected = false; + if (g_skate3_gameplay_ultrawide_latched.compare_exchange_strong(expected, true)) { + REXLOG_INFO("Skate 3 ultrawide: gameplay context reached; enabling ultrawide correction"); + } +} + +bool IsSkate3GameplayUltrawideActive() { + return g_skate3_gameplay_ultrawide_latched.load(std::memory_order_relaxed); +} + +GuestFunctionScope::GuestFunctionScope(const char* function_name) { + if (!IsGuestStoreWatchActive()) { + return; + } + if (g_guest_function_stack_depth >= kMaxGuestFunctionStackDepth) { + return; + } + g_guest_function_stack[g_guest_function_stack_depth++] = function_name; + active_ = true; +} + +GuestFunctionScope::~GuestFunctionScope() { + if (active_ && g_guest_function_stack_depth != 0) { + --g_guest_function_stack_depth; + } +} + +bool TargetKey::operator==(const TargetKey& other) const { + return color_info == other.color_info && depth_info == other.depth_info && + surface_info == other.surface_info && viewport_x == other.viewport_x && + viewport_y == other.viewport_y && viewport_width == other.viewport_width && + viewport_height == other.viewport_height && color_mask == other.color_mask && + depth_control == other.depth_control && pa_cl_vte_cntl == other.pa_cl_vte_cntl && + primitive_type == other.primitive_type && + host_vertex_shader_type == other.host_vertex_shader_type && + vertex_shader_hash == other.vertex_shader_hash && + pixel_shader_hash == other.pixel_shader_hash; +} + +uint32_t ShadowMapCandidateLevel(const TargetKey& key) { + const bool depth_only = key.color_mask == 0 && key.pixel_shader_hash == 0; + const bool square = key.viewport_width == key.viewport_height && key.viewport_width != 0; + const bool atlas_aligned = square && (key.viewport_x == 0 || key.viewport_x % key.viewport_width == 0); + + // Skate 3 uses square 1024x1024 shadow atlas slices at X offsets that are + // multiples of the slice size. These may have either depth-only or color + // resolve/filtering passes, so don't require color_mask or pixel shader tests. + if (atlas_aligned && key.viewport_width >= 512 && key.viewport_width <= 4096) { + return 1; + } + + // Clip-disabled offscreen passes often come through as very large square + // viewports. Keep this separate because it's broader than the atlas rule. + if (depth_only && square && key.viewport_width > 4096) { + return 2; + } + + // Broad diagnostic: any depth-only pass. This may include depth pre-passes, + // so it should be used only if the narrower modes still leave shadow offsets. + if (depth_only) { + return 3; + } + + return 0; +} + +bool IsShadowMapCandidate(const TargetKey& key, uint32_t mode) { + const uint32_t level = ShadowMapCandidateLevel(key); + return level != 0 && level <= mode; +} + +uint64_t HashKey(const TargetKey& key) { + uint64_t hash = 1469598103934665603ull; + for (uint32_t color_info : key.color_info) { + HashAdd(hash, color_info); + } + HashAdd(hash, key.depth_info); + HashAdd(hash, key.surface_info); + HashAdd(hash, key.viewport_x); + HashAdd(hash, key.viewport_y); + HashAdd(hash, key.viewport_width); + HashAdd(hash, key.viewport_height); + HashAdd(hash, key.color_mask); + HashAdd(hash, key.depth_control); + HashAdd(hash, key.pa_cl_vte_cntl); + HashAdd(hash, key.primitive_type); + HashAdd(hash, key.host_vertex_shader_type); + HashAdd(hash, key.vertex_shader_hash); + HashAdd(hash, key.pixel_shader_hash); + return hash; +} + +bool ShouldApplyFast(const TargetKey& key, bool default_enabled, ApplyMode apply_mode) { + if (apply_mode == ApplyMode::kForceAll) { + return true; + } + if (apply_mode == ApplyMode::kForceNone) { + return false; + } + + struct TargetDecisionCacheEntry { + TargetKey key; + uint64_t hash = 0; + uint32_t epoch = 0; + ApplyMode apply_mode = ApplyMode::kTargetList; + bool default_enabled = false; + bool result = false; + bool valid = false; + }; + + constexpr size_t kTargetDecisionCacheSize = 8192; + thread_local std::array + target_decision_cache; + + const uint64_t key_hash = HashKey(key); + const uint32_t epoch = g_target_decision_cache_epoch.load(std::memory_order_acquire); + TargetDecisionCacheEntry& cached = + target_decision_cache[size_t(key_hash) & (kTargetDecisionCacheSize - 1)]; + if (cached.valid && cached.epoch == epoch && cached.hash == key_hash && + cached.apply_mode == apply_mode && cached.default_enabled == default_enabled && + cached.key == key) { + return cached.result; + } + + bool result = default_enabled; + const auto overrides = g_hash_overrides_snapshot.load(std::memory_order_acquire); + if (overrides) { + const auto it = overrides->find(key_hash); + if (it != overrides->end()) { + result = it->second; + cached = TargetDecisionCacheEntry{key, key_hash, epoch, apply_mode, default_enabled, result, + true}; + return result; + } + } + if (const auto semantic_override = FindSemanticOverride(key)) { + result = *semantic_override; + } else if (const auto fallback_override = FindBuiltInFallbackOverride(key)) { + result = *fallback_override; + } + cached = + TargetDecisionCacheEntry{key, key_hash, epoch, apply_mode, default_enabled, result, true}; + return result; +} + +bool ShouldApplyAndRecord(const TargetKey& key, bool default_enabled) { + EnsureBuiltInSkate3ClassifierLoaded(); + const ApplyMode apply_mode = g_apply_mode.load(std::memory_order_relaxed); + if (apply_mode == ApplyMode::kForceNone) { + return false; + } + if (apply_mode == ApplyMode::kSavedOnly || + !g_target_recording_active.load(std::memory_order_relaxed)) { + return ShouldApplyFast(key, default_enabled, apply_mode); + } + + std::lock_guard lock(g_mutex); + auto [it, inserted] = g_targets.try_emplace(key); + TargetEntry& entry = it->second; + if (inserted) { + entry.hash = HashKey(key); + entry.key = key; + if (auto override_it = g_hash_overrides.find(entry.hash); + override_it != g_hash_overrides.end()) { + entry.enabled = override_it->second; + } else if (const auto semantic_override = FindSemanticOverride(key)) { + entry.enabled = *semantic_override; + } else if (const auto fallback_override = FindBuiltInFallbackOverride(key)) { + entry.enabled = *fallback_override; + } else { + entry.enabled = default_enabled; + } + entry.default_enabled = default_enabled; + entry.shadow_candidate = ShadowMapCandidateLevel(key) != 0; + } else { + entry.default_enabled = default_enabled; + entry.shadow_candidate = ShadowMapCandidateLevel(key) != 0; + } + ++entry.draw_count; + entry.last_seen_order = ++g_seen_order; + const bool should_apply = + apply_mode == ApplyMode::kForceAll || + (apply_mode == ApplyMode::kTargetList && entry.enabled); + if (should_apply) { + ++entry.applied_draw_count; + } + return should_apply; +} + +std::vector SnapshotTargets() { + std::lock_guard lock(g_mutex); + std::vector targets; + targets.reserve(g_targets.size()); + for (const auto& [key, entry] : g_targets) { + targets.push_back(entry); + } + return targets; +} + +void SetTargetEnabled(uint64_t hash, bool enabled) { + std::lock_guard lock(g_mutex); + g_hash_overrides[hash] = enabled; + PublishHashOverridesSnapshotLocked(); + for (auto& [key, entry] : g_targets) { + if (entry.hash == hash) { + entry.enabled = enabled; + } + } +} + +void SetAllTargetsEnabled(bool enabled) { + std::lock_guard lock(g_mutex); + for (auto& [key, entry] : g_targets) { + entry.enabled = enabled; + g_hash_overrides[entry.hash] = enabled; + } + PublishHashOverridesSnapshotLocked(); +} + +void ResetTargetOverrides() { + std::lock_guard lock(g_mutex); + g_hash_overrides.clear(); + PublishHashOverridesSnapshotLocked(); + for (auto& [key, entry] : g_targets) { + entry.enabled = entry.default_enabled; + } +} + +void SetTargetRecordingActive(bool active) { + g_target_recording_active.store(active, std::memory_order_release); +} + +uint64_t HashScreenCallback(ScreenCallbackKind kind, uint32_t caller_lr) { + uint64_t hash = 1469598103934665603ull; + HashAdd(hash, uint32_t(kind)); + HashAdd(hash, caller_lr); + return hash; +} + +bool RecordScreenCallback(ScreenCallbackKind kind, uint32_t caller_lr, uint32_t value) { + if (!REXCVAR_GET(skate3_ultrawide_screen_callback_tracking)) { + return false; + } + + const uint64_t hash = HashScreenCallback(kind, caller_lr); + std::lock_guard lock(g_mutex); + auto [it, inserted] = g_screen_callbacks.try_emplace(hash); + ScreenCallbackEntry& entry = it->second; + if (inserted) { + entry.hash = hash; + entry.kind = kind; + entry.caller_lr = caller_lr; + if (auto override_it = g_screen_callback_overrides.find(hash); + override_it != g_screen_callback_overrides.end()) { + entry.enabled = override_it->second; + } + } + entry.last_value = value; + entry.last_returned_value = value; + entry.last_spoofed = false; + ++entry.call_count; + entry.last_seen_order = ++g_seen_order; + return entry.enabled; +} + +void RecordScreenCallbackResult(ScreenCallbackKind kind, uint32_t caller_lr, + uint32_t native_value, uint32_t returned_value) { + if (!REXCVAR_GET(skate3_ultrawide_screen_callback_tracking)) { + return; + } + + const uint64_t hash = HashScreenCallback(kind, caller_lr); + std::lock_guard lock(g_mutex); + auto [it, inserted] = g_screen_callbacks.try_emplace(hash); + ScreenCallbackEntry& entry = it->second; + if (inserted) { + entry.hash = hash; + entry.kind = kind; + entry.caller_lr = caller_lr; + if (auto override_it = g_screen_callback_overrides.find(hash); + override_it != g_screen_callback_overrides.end()) { + entry.enabled = override_it->second; + } + } + entry.last_value = native_value; + entry.last_returned_value = returned_value; + entry.last_spoofed = native_value != returned_value; +} + +std::vector SnapshotScreenCallbacks() { + if (!REXCVAR_GET(skate3_ultrawide_screen_callback_tracking)) { + return {}; + } + + std::lock_guard lock(g_mutex); + std::vector entries; + entries.reserve(g_screen_callbacks.size()); + for (const auto& [hash, entry] : g_screen_callbacks) { + entries.push_back(entry); + } + return entries; +} + +void SetScreenCallbackEnabled(uint64_t hash, bool enabled) { + std::lock_guard lock(g_mutex); + g_screen_callback_overrides[hash] = enabled; + if (auto it = g_screen_callbacks.find(hash); it != g_screen_callbacks.end()) { + it->second.enabled = enabled; + } +} + +void SetAllScreenCallbacksEnabled(bool enabled) { + std::lock_guard lock(g_mutex); + for (auto& [hash, entry] : g_screen_callbacks) { + entry.enabled = enabled; + g_screen_callback_overrides[hash] = enabled; + } +} + +void SetGuestStoreWatchTargets(uint32_t index_base, uint32_t vertex_fetch_address, + uint32_t texture_base_address, uint64_t group_hash) { + g_guest_store_watch_index_base.store(index_base, std::memory_order_relaxed); + g_guest_store_watch_vertex_fetch_address.store(vertex_fetch_address, std::memory_order_relaxed); + g_guest_store_watch_texture_base_address.store(texture_base_address, std::memory_order_relaxed); + g_guest_store_watch_enabled.store(index_base != 0 || vertex_fetch_address != 0 || + texture_base_address != 0, + std::memory_order_relaxed); + std::lock_guard lock(g_mutex); + g_guest_store_watch_group_hash = group_hash; + g_guest_store_watch_events.clear(); + if (g_guest_store_watch_log.is_open()) { + g_guest_store_watch_log.close(); + } + OpenGuestStoreWatchLogLocked(true); + WriteGuestStoreWatchTargetsLocked("watch_begin"); +} + +void ClearGuestStoreWatchTargets() { + { + std::lock_guard lock(g_mutex); + WriteGuestStoreWatchTargetsLocked("watch_clear"); + g_guest_store_watch_events.clear(); + if (g_guest_store_watch_log.is_open()) { + g_guest_store_watch_log.flush(); + } + } + g_guest_store_watch_enabled.store(false, std::memory_order_relaxed); + g_guest_store_watch_index_base.store(0, std::memory_order_relaxed); + g_guest_store_watch_vertex_fetch_address.store(0, std::memory_order_relaxed); + g_guest_store_watch_texture_base_address.store(0, std::memory_order_relaxed); +} + +void ClearGuestStoreWatchEvents() { + std::lock_guard lock(g_mutex); + g_guest_store_watch_events.clear(); +} + +std::vector SnapshotGuestStoreWatchEvents() { + std::lock_guard lock(g_mutex); + return {g_guest_store_watch_events.begin(), g_guest_store_watch_events.end()}; +} + +void SetGuestStoreWatchLogPath(const std::filesystem::path& path) { + std::lock_guard lock(g_mutex); + g_guest_store_watch_log_path = path; + if (g_guest_store_watch_log.is_open()) { + g_guest_store_watch_log.close(); + } +} + +void RecordGuestStoreU32(uint32_t address, uint32_t value, const char* function_name, + uint32_t caller_lr) { + if (!g_guest_store_watch_enabled.load(std::memory_order_relaxed)) { + return; + } + + const uint32_t index_base = + g_guest_store_watch_index_base.load(std::memory_order_relaxed); + const uint32_t vertex_fetch_address = + g_guest_store_watch_vertex_fetch_address.load(std::memory_order_relaxed); + const uint32_t texture_base_address = + g_guest_store_watch_texture_base_address.load(std::memory_order_relaxed); + + const char* target = nullptr; + if (index_base != 0 && value == index_base) { + target = "IB"; + } else if (vertex_fetch_address != 0 && value == vertex_fetch_address) { + target = "VF0"; + } else if (vertex_fetch_address != 0 && value == (vertex_fetch_address >> 2)) { + target = "VF0>>2"; + } else if (texture_base_address != 0 && value == texture_base_address) { + target = "Tex"; + } else if (texture_base_address != 0 && value == (texture_base_address >> 12)) { + target = "Tex>>12"; + } else { + return; + } + + const std::string current_stack = CurrentGuestFunctionStack(); + std::lock_guard lock(g_mutex); + if (!g_guest_store_watch_events.empty()) { + GuestStoreWatchEntry& previous = g_guest_store_watch_events.back(); + if (previous.target == target && previous.address == address && previous.value == value && + previous.caller_lr == caller_lr && + previous.function == (function_name ? function_name : "") && + previous.stack == current_stack) { + previous.sequence = ++g_guest_store_watch_sequence; + ++previous.count; + WriteGuestStoreWatchStoreLocked(previous); + return; + } + } + + GuestStoreWatchEntry entry; + entry.sequence = ++g_guest_store_watch_sequence; + entry.target = target; + entry.address = address; + entry.value = value; + entry.caller_lr = caller_lr; + entry.function = function_name ? function_name : ""; + entry.stack = current_stack; + g_guest_store_watch_events.push_back(std::move(entry)); + WriteGuestStoreWatchStoreLocked(g_guest_store_watch_events.back()); + while (g_guest_store_watch_events.size() > kMaxGuestStoreWatchEvents) { + g_guest_store_watch_events.pop_front(); + } +} + +void RecordDrawWatchTransition(bool present, uint64_t age, uint32_t packet_ptr, + uint32_t index_base, uint32_t vertex_fetch_address, + uint64_t group_hash) { + std::lock_guard lock(g_mutex); + OpenGuestStoreWatchLogLocked(false); + if (!g_guest_store_watch_log) { + return; + } + const uint32_t texture_base_address = + g_guest_store_watch_texture_base_address.load(std::memory_order_relaxed); + g_guest_store_watch_log << NowMilliseconds() << ",draw_transition,0,"; + WriteHex(g_guest_store_watch_log, group_hash, 16); + g_guest_store_watch_log << ",," << (present ? "present" : "missing") << ","; + if (age == UINT64_MAX) { + g_guest_store_watch_log << "n/a"; + } else { + g_guest_store_watch_log << age; + } + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, packet_ptr, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, index_base, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, vertex_fetch_address, 8); + g_guest_store_watch_log << ","; + WriteHex(g_guest_store_watch_log, texture_base_address, 8); + g_guest_store_watch_log << ",,,,,0\n"; + g_guest_store_watch_log.flush(); +} + +bool IsGuestStoreWatchActive() { + return g_guest_store_watch_enabled.load(std::memory_order_relaxed); +} + +void SetCurrentDrawOwner(uint32_t object, uint32_t vtable, uint32_t target) { + g_current_draw_owner_object = object; + g_current_draw_owner_vtable = vtable; + g_current_draw_owner_target = target; +} + +void ClearCurrentDrawOwner() { + g_current_draw_owner_object = 0; + g_current_draw_owner_vtable = 0; + g_current_draw_owner_target = 0; +} + +uint64_t HashDrawFingerprint(const DrawFingerprint& fingerprint) { + uint64_t hash = 1469598103934665603ull; + HashAdd(hash, uint32_t(fingerprint.bucket)); + HashAdd(hash, fingerprint.guest_render_object); + HashAdd(hash, fingerprint.guest_render_vtable); + HashAdd(hash, fingerprint.guest_render_target); + HashAdd(hash, fingerprint.vertex_shader_hash); + HashAdd(hash, fingerprint.pixel_shader_hash); + HashAdd(hash, fingerprint.primitive_type); + HashAdd(hash, fingerprint.vertex_count); + HashAdd(hash, fingerprint.primitive_count); + HashAdd(hash, fingerprint.index_guest_base); + HashAdd(hash, fingerprint.index_length); + for (size_t i = 0; i < DrawFingerprint::kVertexFetchCount; ++i) { + HashAdd(hash, fingerprint.vertex_fetch_address[i]); + HashAdd(hash, fingerprint.vertex_fetch_size[i]); + } + for (size_t i = 0; i < DrawFingerprint::kTextureFetchCount; ++i) { + HashAdd(hash, fingerprint.texture_key_hash[i]); + HashAdd(hash, fingerprint.texture_fetch_index[i]); + } + return hash; +} + +bool RecordDrawFingerprint(const DrawFingerprint& fingerprint) { + DrawFingerprint fingerprint_with_owner = fingerprint; + fingerprint_with_owner.guest_render_object = g_current_draw_owner_object; + fingerprint_with_owner.guest_render_vtable = g_current_draw_owner_vtable; + fingerprint_with_owner.guest_render_target = g_current_draw_owner_target; + const uint64_t hash = HashDrawFingerprint(fingerprint_with_owner); + std::lock_guard lock(g_mutex); + DrawFingerprintKey key{fingerprint_with_owner}; + auto it = g_draw_fingerprints.find(key); + if (it == g_draw_fingerprints.end()) { + if (g_draw_fingerprints.size() >= kMaxDrawFingerprints) { + return true; + } + it = g_draw_fingerprints.try_emplace(key).first; + } + DrawFingerprintEntry& entry = it->second; + if (!entry.draw_count) { + entry.fingerprint = fingerprint_with_owner; + entry.hash = hash; + } else { + entry.fingerprint.guest_render_object = fingerprint_with_owner.guest_render_object; + entry.fingerprint.guest_render_vtable = fingerprint_with_owner.guest_render_vtable; + entry.fingerprint.guest_render_target = fingerprint_with_owner.guest_render_target; + entry.fingerprint.packet_ptr = fingerprint_with_owner.packet_ptr; + entry.fingerprint.index_guest_base = fingerprint_with_owner.index_guest_base; + entry.fingerprint.index_length = fingerprint_with_owner.index_length; + std::copy(std::begin(fingerprint_with_owner.vertex_fetch_address), + std::end(fingerprint_with_owner.vertex_fetch_address), + std::begin(entry.fingerprint.vertex_fetch_address)); + std::copy(std::begin(fingerprint_with_owner.texture_key_hash), + std::end(fingerprint_with_owner.texture_key_hash), + std::begin(entry.fingerprint.texture_key_hash)); + std::copy(std::begin(fingerprint_with_owner.texture_fetch_index), + std::end(fingerprint_with_owner.texture_fetch_index), + std::begin(entry.fingerprint.texture_fetch_index)); + std::copy(std::begin(fingerprint_with_owner.texture_base_address), + std::end(fingerprint_with_owner.texture_base_address), + std::begin(entry.fingerprint.texture_base_address)); + std::copy(std::begin(fingerprint_with_owner.texture_base_length), + std::end(fingerprint_with_owner.texture_base_length), + std::begin(entry.fingerprint.texture_base_length)); + std::copy(std::begin(fingerprint_with_owner.texture_width), + std::end(fingerprint_with_owner.texture_width), + std::begin(entry.fingerprint.texture_width)); + std::copy(std::begin(fingerprint_with_owner.texture_height), + std::end(fingerprint_with_owner.texture_height), + std::begin(entry.fingerprint.texture_height)); + std::copy(std::begin(fingerprint_with_owner.texture_format), + std::end(fingerprint_with_owner.texture_format), + std::begin(entry.fingerprint.texture_format)); + } + if (auto override_it = g_draw_fingerprint_overrides.find(hash); + override_it != g_draw_fingerprint_overrides.end()) { + entry.enabled = override_it->second; + } + ++entry.draw_count; + if (entry.enabled) { + ++entry.submitted_count; + entry.vertices += fingerprint_with_owner.vertex_count; + entry.primitives += fingerprint_with_owner.primitive_count; + } else { + ++entry.skipped_count; + } + entry.last_seen_order = ++g_seen_order; + entry.last_seen_ms = NowMilliseconds(); + WriteGuestStoreWatchDrawLocked(fingerprint_with_owner, entry.enabled, hash); + return entry.enabled; +} + +std::vector SnapshotDrawFingerprints() { + std::lock_guard lock(g_mutex); + std::vector entries; + entries.reserve(g_draw_fingerprints.size()); + for (const auto& [_, entry] : g_draw_fingerprints) { + entries.push_back(entry); + } + return entries; +} + +void ClearDrawFingerprints() { + std::lock_guard lock(g_mutex); + g_draw_fingerprints.clear(); +} + +void SetDrawFingerprintEnabled(uint64_t hash, bool enabled) { + std::lock_guard lock(g_mutex); + g_draw_fingerprint_overrides[hash] = enabled; + for (auto& [key, entry] : g_draw_fingerprints) { + if (entry.hash == hash) { + entry.enabled = enabled; + } + } +} + +void SetAllDrawFingerprintsEnabled(bool enabled) { + std::lock_guard lock(g_mutex); + for (auto& [key, entry] : g_draw_fingerprints) { + entry.enabled = enabled; + g_draw_fingerprint_overrides[entry.hash] = enabled; + } +} + +void ResetDrawFingerprintOverrides() { + std::lock_guard lock(g_mutex); + g_draw_fingerprint_overrides.clear(); + for (auto& [key, entry] : g_draw_fingerprints) { + entry.enabled = true; + } +} + +ApplyMode GetApplyMode() { + return g_apply_mode.load(std::memory_order_relaxed); +} + +void SetApplyMode(ApplyMode mode) { + const ApplyMode previous = g_apply_mode.exchange(mode, std::memory_order_acq_rel); + if (previous != mode) { + InvalidateTargetDecisionCache(); + } +} + +void LoadBuiltInSkate3Classifier() { + std::lock_guard lock(g_mutex); + InstallBuiltInSkate3ClassifierLocked(); + for (auto& [key, entry] : g_targets) { + if (const auto semantic_override = FindSemanticOverride(key)) { + entry.enabled = *semantic_override; + } + } +} + +std::optional LoadTargets(const std::filesystem::path& path) { + if (path.empty() || !std::filesystem::exists(path)) { + return std::nullopt; + } + + toml::table config; + try { + config = toml::parse_file(path.string()); + } catch (const toml::parse_error&) { + return std::nullopt; + } + + std::unordered_map loaded_overrides; + std::unordered_map loaded_semantic_overrides; + std::unordered_map loaded_screen_callback_overrides; + if (auto* targets = config["target"].as_array()) { + targets->for_each([&](toml::table& target) { + const auto hash_string = target["hash"].value(); + const auto enabled = target["enabled"].value(); + if (!hash_string || !enabled) { + return; + } + const auto hash = ParseHexString(*hash_string); + if (hash) { + loaded_overrides[*hash] = *enabled; + } + if (auto semantic_key = ParseSemanticTargetKey(target)) { + loaded_semantic_overrides[*semantic_key] = *enabled; + } + }); + } + if (auto* callbacks = config["screen_callback"].as_array()) { + callbacks->for_each([&](toml::table& callback) { + const auto kind_string = callback["kind"].value(); + const auto caller_lr_string = callback["caller_lr"].value(); + const auto enabled = callback["enabled"].value(); + if (!kind_string || !caller_lr_string || !enabled) { + return; + } + const auto kind = ParseScreenCallbackKind(*kind_string); + const auto caller_lr = ParseHexString(*caller_lr_string); + if (kind && caller_lr) { + loaded_screen_callback_overrides[HashScreenCallback(*kind, uint32_t(*caller_lr))] = + *enabled; + } + }); + } + + ApplyMode loaded_mode = ApplyMode::kTargetList; + if (auto mode = config["apply_mode"].value()) { + if (*mode == "force_all") { + loaded_mode = ApplyMode::kForceAll; + } else if (*mode == "force_none") { + loaded_mode = ApplyMode::kForceNone; + } else if (*mode == "saved_only" || *mode == "hard_config") { + loaded_mode = ApplyMode::kSavedOnly; + } + } + + { + std::lock_guard lock(g_mutex); + g_hash_overrides = std::move(loaded_overrides); + PublishHashOverridesSnapshotLocked(); + g_semantic_overrides = std::move(loaded_semantic_overrides); + PublishSemanticOverridesSnapshotLocked(); + g_builtin_target_overrides_active.store(false, std::memory_order_release); + g_screen_callback_overrides = std::move(loaded_screen_callback_overrides); + g_target_overrides_loaded.store(true, std::memory_order_release); + for (auto& [key, entry] : g_targets) { + if (auto override_it = g_hash_overrides.find(entry.hash); + override_it != g_hash_overrides.end()) { + entry.enabled = override_it->second; + } else if (const auto semantic_override = FindSemanticOverride(key)) { + entry.enabled = *semantic_override; + } + } + for (auto& [hash, entry] : g_screen_callbacks) { + if (auto override_it = g_screen_callback_overrides.find(hash); + override_it != g_screen_callback_overrides.end()) { + entry.enabled = override_it->second; + } + } + } + SetApplyMode(loaded_mode); + return path; +} + +std::optional SaveTargets(const std::filesystem::path& path) { + std::ofstream stream(path, std::ios::binary | std::ios::trunc); + if (!stream) { + return std::nullopt; + } + + auto targets = SnapshotTargets(); + std::sort(targets.begin(), targets.end(), [](const TargetEntry& a, const TargetEntry& b) { + if (a.enabled != b.enabled) { + return a.enabled > b.enabled; + } + if (a.last_seen_order != b.last_seen_order) { + return a.last_seen_order > b.last_seen_order; + } + return a.hash < b.hash; + }); + + stream << "# Skate 3 ultrawide NDC target selection export\n"; + stream << "# enabled targets are the draw groups currently receiving Hor+ NDC correction\n\n"; + const ApplyMode apply_mode = GetApplyMode(); + stream << "apply_mode = \"" + << (apply_mode == ApplyMode::kForceAll + ? "force_all" + : (apply_mode == ApplyMode::kForceNone + ? "force_none" + : (apply_mode == ApplyMode::kSavedOnly ? "saved_only" : "target_list"))) + << "\"\n\n"; + for (const TargetEntry& entry : targets) { + stream << "[[target]]\n"; + stream << "enabled = " << (entry.enabled ? "true" : "false") << "\n"; + stream << "default_enabled = " << (entry.default_enabled ? "true" : "false") << "\n"; + stream << "shadow_candidate = " << (entry.shadow_candidate ? "true" : "false") << "\n"; + stream << "draw_count = " << entry.draw_count << "\n"; + stream << "applied_draw_count = " << entry.applied_draw_count << "\n"; + stream << "hash = \""; + WriteHex(stream, entry.hash, 16); + stream << "\"\n"; + stream << "color_info = ["; + for (size_t i = 0; i < entry.key.color_info.size(); ++i) { + if (i) { + stream << ", "; + } + stream << "\""; + WriteHex(stream, entry.key.color_info[i], 8); + stream << "\""; + } + stream << "]\n"; + stream << "depth_info = \""; + WriteHex(stream, entry.key.depth_info, 8); + stream << "\"\n"; + stream << "surface_info = \""; + WriteHex(stream, entry.key.surface_info, 8); + stream << "\"\n"; + stream << "viewport = [" << entry.key.viewport_x << ", " << entry.key.viewport_y << ", " + << entry.key.viewport_width << ", " << entry.key.viewport_height << "]\n"; + stream << "color_mask = \""; + WriteHex(stream, entry.key.color_mask, 8); + stream << "\"\n"; + stream << "depth_control = \""; + WriteHex(stream, entry.key.depth_control, 8); + stream << "\"\n"; + stream << "pa_cl_vte_cntl = \""; + WriteHex(stream, entry.key.pa_cl_vte_cntl, 8); + stream << "\"\n"; + stream << "primitive_type = " << entry.key.primitive_type << "\n"; + stream << "host_vertex_shader_type = " << entry.key.host_vertex_shader_type << "\n"; + stream << "vertex_shader_hash = \""; + WriteHex(stream, entry.key.vertex_shader_hash, 16); + stream << "\"\n"; + stream << "pixel_shader_hash = \""; + WriteHex(stream, entry.key.pixel_shader_hash, 16); + stream << "\"\n\n"; + } + + auto callbacks = SnapshotScreenCallbacks(); + std::sort(callbacks.begin(), callbacks.end(), + [](const ScreenCallbackEntry& a, const ScreenCallbackEntry& b) { + if (a.kind != b.kind) { + return uint32_t(a.kind) < uint32_t(b.kind); + } + return a.caller_lr < b.caller_lr; + }); + + for (const ScreenCallbackEntry& entry : callbacks) { + stream << "[[screen_callback]]\n"; + stream << "enabled = " << (entry.enabled ? "true" : "false") << "\n"; + stream << "kind = \"" << ScreenCallbackKindName(entry.kind) << "\"\n"; + stream << "caller_lr = \""; + WriteHex(stream, entry.caller_lr, 8); + stream << "\"\n"; + stream << "last_value = " << entry.last_value << "\n"; + stream << "call_count = " << entry.call_count << "\n"; + stream << "hash = \""; + WriteHex(stream, entry.hash, 16); + stream << "\"\n\n"; + } + + return path; +} + +} // namespace rex::graphics::ultrawide_debug diff --git a/src/filesystem/virtual_file_system.cpp b/src/filesystem/virtual_file_system.cpp index e4bad13..b56664b 100644 --- a/src/filesystem/virtual_file_system.cpp +++ b/src/filesystem/virtual_file_system.cpp @@ -10,6 +10,10 @@ */ #include +#include +#include +#include +#include #include #include @@ -30,13 +34,102 @@ REXCVAR_DEFINE_INT32(filesystem_debug_log_fe_asset_ops_remaining, 0, "Filesystem .lifecycle(rex::cvar::Lifecycle::kHotReload) .debug_only(); -REXCVAR_DEFINE_INT32(filesystem_debug_log_team_profile_background_remaining, 200, "Filesystem", +REXCVAR_DEFINE_INT32(filesystem_debug_log_team_profile_background_remaining, 0, "Filesystem", "Log team_profile_background_0.rx2 file opens and reads for this many " "operations") .range(0, 10000) .lifecycle(rex::cvar::Lifecycle::kHotReload) .debug_only(); +REXCVAR_DEFINE_BOOL(filesystem_debug_log_chan_center_stream_files, false, "Filesystem", + "Log focused Chan Center world chunk file opens to disk") + .lifecycle(rex::cvar::Lifecycle::kHotReload) + .debug_only(); + +namespace { + +bool IsSkate3ChanCenterStreamPath(std::string_view path) { + constexpr std::string_view kNeedles[] = { + "DIST_University", "cTex_500_-300_high", "cTex_500_-200_high", + "cTex_100_200_high", "cPres_350_-50_high", "cPres_350_-350_high", + "cPres_550_-150_high", "cSim_150_-350_high", + }; + for (std::string_view needle : kNeedles) { + if (path.find(needle) != std::string_view::npos) { + return true; + } + } + return false; +} + +std::filesystem::path Skate3ChanCenterCacheLogPath(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; +} + +void LogSkate3ChanCenterStreamFileOpen(std::string_view stage, std::string_view path, + std::string_view absolute_path, uint32_t result, + uint32_t action, uint32_t desired_access, + uint32_t creation_disposition) { + if (!REXCVAR_GET(filesystem_debug_log_chan_center_stream_files) || + !IsSkate3ChanCenterStreamPath(path)) { + return; + } + + static std::mutex log_mutex; + static std::ofstream log_file; + static uint64_t sequence = 0; + std::lock_guard lock(log_mutex); + if (!log_file.is_open()) { + std::filesystem::path log_path = + Skate3ChanCenterCacheLogPath("skate3_chan_center_file_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,stage,result,action,access,disposition,path,absolute\n"; + } + } + if (!log_file.is_open()) { + return; + } + + auto write_escaped = [&](std::string_view text) { + for (char ch : text) { + log_file << (ch == ',' ? ';' : ch); + } + }; + + log_file << std::dec << sequence++ << ',' << stage << ",0x" << std::hex << result << ",0x" + << action << ",0x" << desired_access << ",0x" << creation_disposition << ','; + write_escaped(path); + log_file << ','; + write_escaped(absolute_path); + log_file << '\n'; + log_file.flush(); + + REXFS_WARN("Skate3 Chan stream file: stage={} result={:#x} action={:#x} path='{}' absolute='{}'", + stage, result, action, path, absolute_path); +} + +} // namespace + namespace rex::filesystem { namespace { @@ -283,6 +376,9 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p parent_entry = !root_entry ? ResolvePath(base_path) : root_entry->ResolvePath(base_path); if (!parent_entry) { *out_action = FileAction::kDoesNotExist; + LogSkate3ChanCenterStreamFileOpen("missing-parent", path, {}, X_STATUS_NO_SUCH_FILE, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_NO_SUCH_FILE; } @@ -303,6 +399,10 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p if (entry) { if (entry->attributes() & kFileAttributeDirectory && is_non_directory) { + LogSkate3ChanCenterStreamFileOpen("is-directory", path, entry->absolute_path(), + X_STATUS_FILE_IS_A_DIRECTORY, + static_cast(FileAction::kOpened), desired_access, + static_cast(creation_disposition)); return X_STATUS_FILE_IS_A_DIRECTORY; } @@ -326,6 +426,9 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p // Must exist. if (!entry) { *out_action = FileAction::kDoesNotExist; + LogSkate3ChanCenterStreamFileOpen("missing-entry", path, {}, X_STATUS_NO_SUCH_FILE, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_NO_SUCH_FILE; } break; @@ -333,6 +436,10 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p // Must not exist. if (entry) { *out_action = FileAction::kExists; + LogSkate3ChanCenterStreamFileOpen( + "collision", path, entry->absolute_path(), X_STATUS_OBJECT_NAME_COLLISION, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_OBJECT_NAME_COLLISION; } break; @@ -359,10 +466,18 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p case FileDisposition::kCreate: // Shouldn't be possible to hit this. assert_always(); + LogSkate3ChanCenterStreamFileOpen( + "invalid-create", path, entry->absolute_path(), X_STATUS_ACCESS_DENIED, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_ACCESS_DENIED; case FileDisposition::kSuperscede: // Replace (by delete + recreate). if (!entry->Delete()) { + LogSkate3ChanCenterStreamFileOpen( + "delete-denied", path, entry->absolute_path(), X_STATUS_ACCESS_DENIED, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_ACCESS_DENIED; } entry = nullptr; @@ -380,6 +495,10 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p if (entry->Delete()) { entry = nullptr; } else if (!entry->Truncate()) { + LogSkate3ChanCenterStreamFileOpen( + "truncate-denied", path, entry->absolute_path(), X_STATUS_ACCESS_DENIED, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_ACCESS_DENIED; } *out_action = FileAction::kOverwritten; @@ -390,6 +509,9 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p // Create if needed (either new or as a replacement). entry = CreatePath(path, !is_directory ? kFileAttributeNormal : kFileAttributeDirectory); if (!entry) { + LogSkate3ChanCenterStreamFileOpen("create-denied", path, {}, X_STATUS_ACCESS_DENIED, + static_cast(*out_action), desired_access, + static_cast(creation_disposition)); return X_STATUS_ACCESS_DENIED; } } @@ -410,6 +532,9 @@ X_STATUS VirtualFileSystem::OpenFile(Entry* root_entry, const std::string_view p path, desired_access, static_cast(creation_disposition), static_cast(*out_action), result, entry ? entry->absolute_path() : std::string_view("")); } + LogSkate3ChanCenterStreamFileOpen("open", path, entry ? entry->absolute_path() : std::string_view{}, + result, static_cast(*out_action), desired_access, + static_cast(creation_disposition)); if (XFAILED(result)) { *out_action = FileAction::kDoesNotExist; } diff --git a/src/graphics/command_processor.cpp b/src/graphics/command_processor.cpp index 74424a0..1e2b37d 100644 --- a/src/graphics/command_processor.cpp +++ b/src/graphics/command_processor.cpp @@ -885,11 +885,13 @@ bool CommandProcessor::ExecutePacketType3(memory::RingBuffer* reader, uint32_t p return false; } + current_packet_ptr_ = uint32_t(reader->read_ptr() - 4); + // To handle nesting behavior when tracing we special case indirect buffers. if (opcode == PM4_INDIRECT_BUFFER) { - trace_writer_.WritePacketStart(uint32_t(reader->read_ptr() - 4), 2); + trace_writer_.WritePacketStart(current_packet_ptr_, 2); } else { - trace_writer_.WritePacketStart(uint32_t(reader->read_ptr() - 4), 1 + count); + trace_writer_.WritePacketStart(current_packet_ptr_, 1 + count); } // & 1 == predicate - when set, we do bin check to see if we should execute diff --git a/src/graphics/d3d12/command_processor.cpp b/src/graphics/d3d12/command_processor.cpp index bbdabbd..7e13c51 100644 --- a/src/graphics/d3d12/command_processor.cpp +++ b/src/graphics/d3d12/command_processor.cpp @@ -25,6 +25,7 @@ #include #include #include +#include #include #include #include @@ -49,6 +50,23 @@ REXCVAR_DEFINE_BOOL(d3d12_submit_on_primary_buffer_end, true, "GPU/D3D12", "Submit command list when PM4 primary buffer ends") .lifecycle(rex::cvar::Lifecycle::kHotReload); +REXCVAR_DEFINE_BOOL(skate3_ultrawide_skip_shadow_targets, true, "Skate 3", + "Do not apply Skate 3 Hor+ NDC correction while rendering likely shadow maps") + .lifecycle(rex::cvar::Lifecycle::kHotReload); +REXCVAR_DEFINE_INT32(skate3_ultrawide_shadow_skip_mode, 2, "Skate 3", + "Shadow pass skip mode: 1 = square atlases, 2 = plus large offscreen, " + "3 = plus all depth-only") + .range(1, 3) + .lifecycle(rex::cvar::Lifecycle::kHotReload); +REXCVAR_DEFINE_BOOL(skate3_ultrawide_fake_occlusion_queries, false, "Skate 3", + "Return positive visibility query results while Skate 3 Hor+ ultrawide is " + "active so side-of-frame assets remain resident") + .lifecycle(rex::cvar::Lifecycle::kHotReload); +REXCVAR_DEFINE_INT32(skate3_ultrawide_fake_occlusion_sample_count, 1000, "Skate 3", + "Sample count returned by Skate 3 ultrawide visibility query faking") + .range(1, 1000000) + .lifecycle(rex::cvar::Lifecycle::kHotReload); + namespace rex::graphics::d3d12 { namespace { @@ -61,6 +79,142 @@ bool IsGameplayStateActive(const system::KernelState* kernel_state) { return gameplay_context && *gameplay_context == 1; } +struct Skate3UltrawideDrawState { + bool correction_active = false; + bool skip_shadow_targets = false; + uint32_t shadow_skip_mode = 1; + float hor_plus_scale = 1.0f; +}; + +double GetSkate3UltrawideTargetAspect(const ui::Presenter* presenter = nullptr) { + if (!rex::cvar::Query("skate3_ultrawide") || + !ultrawide_debug::IsSkate3GameplayUltrawideActive()) { + return 0.0; + } + + constexpr double kBaseAspect = 16.0 / 9.0; + uint32_t surface_width = 0; + uint32_t surface_height = 0; + if (presenter && presenter->GetSurfacePaintConnectionSize(surface_width, surface_height) && + surface_width && surface_height) { + const double surface_aspect = double(surface_width) / double(surface_height); + if (surface_aspect > kBaseAspect + 0.01) { + return std::clamp(surface_aspect, kBaseAspect, 8.0); + } + } + + const double target_aspect = rex::cvar::Query("skate3_ultrawide_target_aspect"); + return target_aspect > kBaseAspect + 0.01 ? std::clamp(target_aspect, kBaseAspect, 8.0) : 0.0; +} + +void ApplySkate3UltrawidePresenterAspect(const ui::Presenter* presenter, uint32_t& display_width, + uint32_t& display_height) { + const double target_aspect = GetSkate3UltrawideTargetAspect(presenter); + if (target_aspect <= 0.0) { + return; + } + + rex::cvar::SetFlagByName("skate3_ultrawide_target_aspect", std::to_string(target_aspect)); + + display_height = + uint32_t(std::clamp(rex::cvar::Query("skate3_ultrawide_base_height"), 480, 2160)); + display_width = + uint32_t(std::clamp(target_aspect * double(display_height) + 0.5, 1.0, 4095.0)); +} + +float ComputeSkate3UltrawideHorPlusScale() { + const double manual_scale = rex::cvar::Query("skate3_ultrawide_hor_plus_scale"); + if (manual_scale > 0.0) { + return float(std::clamp(manual_scale, 0.25, 4.0)); + } + + constexpr double kBaseAspect = 16.0 / 9.0; + double aspect = GetSkate3UltrawideTargetAspect(); + if (aspect <= 0.0) { + const int32_t display_width = REXCVAR_GET(video_mode_width); + const int32_t display_height = REXCVAR_GET(video_mode_height); + if (display_width <= 0 || display_height <= 0) { + return 1.0f; + } + aspect = double(display_width) / double(display_height); + } + + if (aspect <= kBaseAspect + 0.01f) { + return 1.0f; + } + + return float(std::clamp(kBaseAspect / aspect, 0.25, 1.0)); +} + +Skate3UltrawideDrawState QuerySkate3UltrawideDrawState() { + Skate3UltrawideDrawState state; + state.correction_active = rex::cvar::Query("skate3_ultrawide") && + rex::cvar::Query("skate3_ultrawide_hor_plus") && + ultrawide_debug::IsSkate3GameplayUltrawideActive() && + !rex::cvar::Query("skate3_ultrawide_disable_ndc_correction"); + if (!state.correction_active) { + return state; + } + + state.hor_plus_scale = ComputeSkate3UltrawideHorPlusScale(); + state.skip_shadow_targets = REXCVAR_GET(skate3_ultrawide_skip_shadow_targets); + state.shadow_skip_mode = uint32_t(REXCVAR_GET(skate3_ultrawide_shadow_skip_mode)); + return state; +} + +Skate3UltrawideDrawState SnapshotSkate3UltrawideDrawState(uint64_t frame) { + constexpr uint64_t kUncachedFrame = ~uint64_t(0); + if (frame == kUncachedFrame) { + return QuerySkate3UltrawideDrawState(); + } + + thread_local uint64_t cached_frame = 0; + thread_local Skate3UltrawideDrawState cached_state; + if (cached_frame != frame) { + cached_state = QuerySkate3UltrawideDrawState(); + cached_frame = frame; + } + return cached_state; +} + +bool ShouldFakeSkate3UltrawideOcclusionQueries() { + const Skate3UltrawideDrawState state = SnapshotSkate3UltrawideDrawState(~uint64_t(0)); + return rex::cvar::Query("skate3_ultrawide_fake_occlusion_queries") && + state.correction_active && state.hor_plus_scale < 0.999f; +} + +bool IsSkate3UltrawideDisplayViewport(const draw_util::ViewportInfo& viewport_info) { + const uint32_t width = viewport_info.xy_extent[0]; + const uint32_t height = viewport_info.xy_extent[1]; + if (!width || !height) { + return false; + } + + const uint64_t scaled_width = uint64_t(width) * 9; + const uint64_t scaled_height = uint64_t(height) * 16; + const uint64_t difference = + scaled_width > scaled_height ? scaled_width - scaled_height : scaled_height - scaled_width; + return difference <= scaled_height / 20; +} + +bool ShouldApplySkate3UltrawideHorPlus( + const ultrawide_debug::TargetKey& target_key, bool primitive_polygonal, + const draw_util::ViewportInfo& viewport_info, reg::RB_DEPTHCONTROL normalized_depth_control, + const Skate3UltrawideDrawState& ultrawide_state) { + if (!ultrawide_state.correction_active) { + return false; + } + + const bool default_enabled = primitive_polygonal && normalized_depth_control.z_enable && + IsSkate3UltrawideDisplayViewport(viewport_info); + const bool selected = ultrawide_debug::ShouldApplyAndRecord(target_key, default_enabled); + if (selected && ultrawide_state.skip_shadow_targets && + ultrawide_debug::IsShadowMapCandidate(target_key, ultrawide_state.shadow_skip_mode)) { + return false; + } + return selected; +} + #ifdef REXGLUE_ENABLE_PERF_COUNTERS class ScopedDrawStageTimer { public: @@ -222,6 +376,45 @@ void D3D12CommandProcessor::RestoreEdramSnapshot(const void* snapshot) { bool D3D12CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(memory::RingBuffer* reader, uint32_t packet, uint32_t count) { + auto write_fake_result = [this, reader, count](int32_t fake_sample_count) -> bool { + // Set by D3D as BE but struct ABI is LE. + const uint32_t kQueryFinished = rex::byte_swap(0xFFFFFEED); + assert_true(count == 1); + uint32_t initiator = reader->ReadAndSwap(); + WriteRegister(XE_GPU_REG_VGT_EVENT_INITIATOR, initiator & 0x3F); + + uint32_t sample_count_addr = register_file_->values[XE_GPU_REG_RB_SAMPLE_COUNT_ADDR]; + auto* sample_counts = + memory_->TranslatePhysical(sample_count_addr); + if (!sample_counts) { + return true; + } + + bool is_end_via_z_pass = + sample_counts->ZPass_A == kQueryFinished && sample_counts->ZPass_B == kQueryFinished; + bool is_end_via_z_fail = + sample_counts->ZFail_A == kQueryFinished && sample_counts->ZFail_B == kQueryFinished; + std::memset(sample_counts, 0, sizeof(xenos::xe_gpu_depth_sample_counts)); + if (is_end_via_z_pass || is_end_via_z_fail) { + sample_counts->ZPass_A = fake_sample_count; + sample_counts->Total_A = fake_sample_count; + } + return true; + }; + + if (ShouldFakeSkate3UltrawideOcclusionQueries()) { + if (active_occlusion_query_.valid) { + uint32_t host_index = active_occlusion_query_.host_index; + active_occlusion_query_ = {}; + if (occlusion_query_heap_ && BeginSubmission(true)) { + deferred_command_list_.D3DEndQuery(occlusion_query_heap_.Get(), D3D12_QUERY_TYPE_OCCLUSION, + host_index); + EndSubmission(false); + } + } + return write_fake_result(REXCVAR_GET(skate3_ultrawide_fake_occlusion_sample_count)); + } + if (!REXCVAR_GET(occlusion_query_enable) || !occlusion_query_resources_available_) { return CommandProcessor::ExecutePacketType3_EVENT_WRITE_ZPD(reader, packet, count); } @@ -2107,6 +2300,7 @@ void D3D12CommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontbu kernel::xboxkrnl::VdQueryVideoMode(&video_mode); uint32_t display_width = std::max(uint32_t(1), uint32_t(video_mode.display_width)); uint32_t display_height = std::max(uint32_t(1), uint32_t(video_mode.display_height)); + ApplySkate3UltrawidePresenterAspect(presenter, display_width, display_height); presenter->RefreshGuestOutput( guest_output_width, guest_output_height, display_width, display_height, @@ -2622,9 +2816,12 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint3 // Update system constants before uploading them. // TODO(Triang3l): With ROV, pass the disabled render target mask for safety. UpdateSystemConstantValues(memexport_used, primitive_polygonal, + primitive_processing_result.host_vertex_shader_type, primitive_processing_result.line_loop_closing_index, primitive_processing_result.host_shader_index_endian, viewport_info, - used_texture_mask, normalized_depth_control, normalized_color_mask); + used_texture_mask, vertex_shader->ucode_data_hash(), + pixel_shader ? pixel_shader->ucode_data_hash() : 0, + normalized_depth_control, normalized_color_mask); } // Update constant buffers, descriptors and root parameters. @@ -2783,6 +2980,69 @@ bool D3D12CommandProcessor::IssueDraw(xenos::PrimitiveType primitive_type, uint3 // Must not call anything that may change the primitive topology from now on! const uint32_t host_draw_vertex_count = primitive_processing_result.host_draw_vertex_count; + const uint32_t skate3_ultrawide_draw_primitive_count = draw_util::EstimatePrimitiveCount( + primitive_processing_result.host_primitive_type, host_draw_vertex_count); + bool skate3_ultrawide_submit_draw = true; + if (rex::cvar::Query("skate3_ultrawide_trace_draws")) { + ultrawide_debug::DrawFingerprint fingerprint; + fingerprint.bucket = + memexport_used + ? ultrawide_debug::DrawBucket::kMemexport + : (edram_mode == xenos::EdramMode::kDepthOnly + ? ultrawide_debug::DrawBucket::kDepthOnly + : (pixel_shader == nullptr ? ultrawide_debug::DrawBucket::kNoPixelShader + : ultrawide_debug::DrawBucket::kMainColorDepth)); + fingerprint.vertex_shader_hash = vertex_shader->ucode_data_hash(); + fingerprint.pixel_shader_hash = pixel_shader ? pixel_shader->ucode_data_hash() : 0; + fingerprint.primitive_type = uint32_t(primitive_processing_result.host_primitive_type); + fingerprint.vertex_count = host_draw_vertex_count; + fingerprint.primitive_count = skate3_ultrawide_draw_primitive_count; + fingerprint.packet_ptr = current_packet_ptr_; + if (index_buffer_info) { + fingerprint.index_guest_base = index_buffer_info->guest_base; + fingerprint.index_length = uint32_t(index_buffer_info->length); + } + const Shader::ConstantRegisterMap& constant_map = vertex_shader->constant_register_map(); + size_t fetch_count = 0; + for (uint32_t i = 0; i < rex::countof(constant_map.vertex_fetch_bitmap); ++i) { + uint32_t fetch_bits = constant_map.vertex_fetch_bitmap[i]; + uint32_t bit = 0; + while (fetch_count < ultrawide_debug::DrawFingerprint::kVertexFetchCount && + rex::bit_scan_forward(fetch_bits, &bit)) { + fetch_bits &= ~(uint32_t(1) << bit); + xenos::xe_gpu_vertex_fetch_t fetch = regs.GetVertexFetch(i * 32 + bit); + fingerprint.vertex_fetch_address[fetch_count] = fetch.address << 2; + fingerprint.vertex_fetch_size[fetch_count] = fetch.size << 2; + ++fetch_count; + } + if (fetch_count >= ultrawide_debug::DrawFingerprint::kVertexFetchCount) { + break; + } + } + size_t texture_count = 0; + uint32_t texture_bits = used_texture_mask; + uint32_t texture_index = 0; + while (texture_count < ultrawide_debug::DrawFingerprint::kTextureFetchCount && + rex::bit_scan_forward(texture_bits, &texture_index)) { + texture_bits &= ~(UINT32_C(1) << texture_index); + TextureCache::DebugActiveTextureBinding binding; + if (!texture_cache_->DebugGetActiveTextureBinding(texture_index, binding)) { + continue; + } + fingerprint.texture_key_hash[texture_count] = binding.key_hash; + fingerprint.texture_fetch_index[texture_count] = binding.fetch_index; + fingerprint.texture_base_address[texture_count] = binding.base_address; + fingerprint.texture_base_length[texture_count] = binding.base_length; + fingerprint.texture_width[texture_count] = binding.width; + fingerprint.texture_height[texture_count] = binding.height; + fingerprint.texture_format[texture_count] = binding.format; + ++texture_count; + } + skate3_ultrawide_submit_draw = ultrawide_debug::RecordDrawFingerprint(fingerprint); + } + if (!skate3_ultrawide_submit_draw) { + return true; + } #ifdef REXGLUE_ENABLE_PERF_COUNTERS const uint32_t host_draw_primitive_count = draw_util::EstimatePrimitiveCount( primitive_processing_result.host_primitive_type, host_draw_vertex_count); @@ -3937,9 +4197,11 @@ void D3D12CommandProcessor::UpdateFixedFunctionState( } void D3D12CommandProcessor::UpdateSystemConstantValues( - bool shared_memory_is_uav, bool primitive_polygonal, uint32_t line_loop_closing_index, + bool shared_memory_is_uav, bool primitive_polygonal, + Shader::HostVertexShaderType host_vertex_shader_type, uint32_t line_loop_closing_index, xenos::Endian index_endian, const draw_util::ViewportInfo& viewport_info, - uint32_t used_texture_mask, reg::RB_DEPTHCONTROL normalized_depth_control, + uint32_t used_texture_mask, uint64_t vertex_shader_hash, uint64_t pixel_shader_hash, + reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask) { #if XE_GPU_FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); @@ -4129,10 +4391,37 @@ void D3D12CommandProcessor::UpdateSystemConstantValues( } // Conversion to Direct3D 12 normalized device coordinates. + ultrawide_debug::TargetKey skate3_ultrawide_target_key; + for (uint32_t i = 0; i < 4; ++i) { + skate3_ultrawide_target_key.color_info[i] = color_infos[i].value; + } + skate3_ultrawide_target_key.depth_info = rb_depth_info.value; + skate3_ultrawide_target_key.surface_info = rb_surface_info.value; + skate3_ultrawide_target_key.viewport_x = viewport_info.xy_offset[0]; + skate3_ultrawide_target_key.viewport_y = viewport_info.xy_offset[1]; + skate3_ultrawide_target_key.viewport_width = viewport_info.xy_extent[0]; + skate3_ultrawide_target_key.viewport_height = viewport_info.xy_extent[1]; + skate3_ultrawide_target_key.color_mask = normalized_color_mask; + skate3_ultrawide_target_key.depth_control = normalized_depth_control.value; + skate3_ultrawide_target_key.pa_cl_vte_cntl = pa_cl_vte_cntl.value; + skate3_ultrawide_target_key.primitive_type = uint32_t(vgt_draw_initiator.prim_type); + skate3_ultrawide_target_key.host_vertex_shader_type = uint32_t(host_vertex_shader_type); + skate3_ultrawide_target_key.vertex_shader_hash = vertex_shader_hash; + skate3_ultrawide_target_key.pixel_shader_hash = pixel_shader_hash; + const Skate3UltrawideDrawState skate3_ultrawide_state = + SnapshotSkate3UltrawideDrawState(frame_current_); + const bool skate3_ultrawide_hor_plus_draw = ShouldApplySkate3UltrawideHorPlus( + skate3_ultrawide_target_key, primitive_polygonal, viewport_info, normalized_depth_control, + skate3_ultrawide_state); + const float skate3_ultrawide_hor_plus_scale = + skate3_ultrawide_hor_plus_draw ? skate3_ultrawide_state.hor_plus_scale : 1.0f; for (uint32_t i = 0; i < 3; ++i) { - dirty |= system_constants_.ndc_scale[i] != viewport_info.ndc_scale[i]; + const float ndc_scale = + i == 0 ? viewport_info.ndc_scale[i] * skate3_ultrawide_hor_plus_scale + : viewport_info.ndc_scale[i]; + dirty |= system_constants_.ndc_scale[i] != ndc_scale; dirty |= system_constants_.ndc_offset[i] != viewport_info.ndc_offset[i]; - system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i]; + system_constants_.ndc_scale[i] = ndc_scale; system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i]; } diff --git a/src/graphics/pipeline/texture/cache.cpp b/src/graphics/pipeline/texture/cache.cpp index 2f1c588..25f4d50 100644 --- a/src/graphics/pipeline/texture/cache.cpp +++ b/src/graphics/pipeline/texture/cache.cpp @@ -24,10 +24,12 @@ #include #include #include +#include #include #include #include #include +#include REXCVAR_DEFINE_INT32(texture_cache_memory_limit_render_to_texture, 128, "GPU", "Texture cache memory limit for render-to-texture (MB)") @@ -106,6 +108,25 @@ REXCVAR_DEFINE_INT32(vulkan_debug_log_team_profile_background_bindings_remaining .lifecycle(rex::cvar::Lifecycle::kHotReload) .debug_only(); +REXCVAR_DEFINE_INT32(skate3_ultrawide_texture_trace_remaining, 0, "Skate 3/Ultrawide", + "Log this many Skate 3 texture lifecycle events for ultrawide pop-in tracing") + .range(0, 1000000) + .lifecycle(rex::cvar::Lifecycle::kHotReload); + +REXCVAR_DEFINE_BOOL(skate3_ultrawide_texture_trace_bind_keys, false, "Skate 3/Ultrawide", + "Include high-frequency texture bind-key churn in ultrawide texture tracing") + .lifecycle(rex::cvar::Lifecycle::kHotReload); + +REXCVAR_DEFINE_BOOL(skate3_ultrawide_texture_trace_scaled_resolve, false, "Skate 3/Ultrawide", + "Include scaled render-to-texture resolve targets in ultrawide texture tracing") + .lifecycle(rex::cvar::Lifecycle::kHotReload); + +REXCVAR_DEFINE_BOOL(skate3_ultrawide_ignore_streamer_texture_invalidations, false, + "Skate 3/Ultrawide", + "Diagnostic: keep host textures resident when guest streamer writes from " + "82CD4300 invalidate watched texture pages") + .lifecycle(rex::cvar::Lifecycle::kHotReload); + REXCVAR_DEFINE_BOOL(pre_mask_resolve_l2_block, true, "GPU", "Pre-mask scaled resolve L2 blocks to the write range before iterating"); @@ -323,6 +344,9 @@ void TextureCache::CompletedSubmissionUpdated(uint64_t completed_submission_inde assert_true(found_texture_it != textures_.end()); if (found_texture_it != textures_.end()) { assert_true(found_texture_it->second.get() == texture); + DebugLogSkate3UltrawideTextureTrace(limit_hard_exceeded ? "destroy-hard-limit" + : "destroy-soft-limit", + UINT32_MAX, texture->key(), texture); textures_.erase(found_texture_it); // `texture` is invalid now. } @@ -493,6 +517,11 @@ bool TextureCache::CommitPreparedTextureLoad(const PendingTextureLoad& pending_l // not up to date anymore. texture.MakeUpToDateAndWatch(global_critical_region_.Acquire()); texture.LogAction("Loaded"); + DebugLogSkate3UltrawideTextureTrace(pending_load.load_base + ? (pending_load.load_mips ? "loaded-base-mips" + : "loaded-base") + : "loaded-mips", + UINT32_MAX, texture.key(), &texture); DebugLogTeamProfileBackgroundTextureCandidate("loaded", UINT32_MAX, texture); return true; @@ -569,6 +598,7 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { if (!binding.key.is_valid) { if (old_key.is_valid) { bindings_changed |= index_bit; + DebugLogSkate3UltrawideTextureTrace("unbind-invalid-fetch", index, old_key); } binding.Reset(); continue; @@ -630,6 +660,15 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { if (load_signed_data) { queue_pending_texture_load(binding.texture_signed); } + if (key_changed) { + DebugLogSkate3UltrawideTextureTrace("bind-key", index, binding.key, + binding.texture ? binding.texture + : binding.texture_signed); + } else if (bindings_changed & index_bit) { + DebugLogSkate3UltrawideTextureTrace("bind-view", index, binding.key, + binding.texture ? binding.texture + : binding.texture_signed); + } } COUNT_profile_set("gpu/texture_cache/request_textures_pending_load_count", @@ -691,6 +730,36 @@ void TextureCache::RequestTextures(uint32_t used_texture_mask) { DebugLogScaledTextureBindings(used_texture_mask); } +bool TextureCache::DebugGetActiveTextureBinding( + uint32_t fetch_constant_index, DebugActiveTextureBinding& binding_out) const { + binding_out = {}; + if (fetch_constant_index >= texture_bindings_.size()) { + return false; + } + const TextureBinding* binding = GetValidTextureBinding(fetch_constant_index); + if (binding == nullptr) { + return false; + } + + const TextureKey& key = binding->key; + binding_out.key_hash = uint64_t(TextureKey::Hasher{}(key)); + binding_out.fetch_index = fetch_constant_index; + binding_out.base_address = key.base_page << 12; + binding_out.mip_address = key.mip_page << 12; + binding_out.width = key.GetWidth(); + binding_out.height = key.GetHeight(); + binding_out.depth_or_array_size = key.GetDepthOrArraySize(); + binding_out.format = uint32_t(key.format); + binding_out.scaled_resolve = key.scaled_resolve != 0; + + const Texture* texture = binding->texture ? binding->texture : binding->texture_signed; + if (texture != nullptr) { + binding_out.base_length = texture->GetGuestBaseSize(); + binding_out.mip_length = texture->GetGuestMipsSize(); + } + return true; +} + void TextureCache::DebugRecordResolveReadback(uint32_t start, uint32_t length, bool scaled) { if (!length) { return; @@ -821,6 +890,90 @@ bool TextureCache::DebugFindTeamProfileBackgroundBinding(uint32_t used_texture_m return false; } +bool TextureCache::DebugConsumeSkate3UltrawideTextureTraceLog() { + int32_t remaining = REXCVAR_GET(skate3_ultrawide_texture_trace_remaining); + if (remaining <= 0) { + return false; + } + REXCVAR_SET(skate3_ultrawide_texture_trace_remaining, remaining - 1); + return true; +} + +void TextureCache::DebugLogSkate3UltrawideTextureTrace(const char* action, uint32_t fetch_index, + const TextureKey& key, + const Texture* texture) { + const bool is_bind_action = std::string_view(action).starts_with("bind-"); + if (is_bind_action && !REXCVAR_GET(skate3_ultrawide_texture_trace_bind_keys)) { + return; + } + if (key.is_valid && key.scaled_resolve && + !REXCVAR_GET(skate3_ultrawide_texture_trace_scaled_resolve)) { + return; + } + + if (!key.is_valid) { + if (!DebugConsumeSkate3UltrawideTextureTraceLog()) { + return; + } + REXGPU_WARN("Skate3 texture trace: action={}, submission={}, fetch={}, invalid-key", action, + current_submission_index_, fetch_index == UINT32_MAX ? -1 : int32_t(fetch_index)); + return; + } + + const FormatInfo* format_info = FormatInfo::Get(key.format); + const uint64_t key_hash = TextureKey::Hasher{}(key); + uint32_t guest_base_size = 0; + uint32_t guest_mips_size = 0; + if (texture) { + guest_base_size = texture->GetGuestBaseSize(); + guest_mips_size = texture->GetGuestMipsSize(); + } else { + const texture_util::TextureGuestLayout fallback_layout = key.GetGuestLayout(); + guest_base_size = fallback_layout.base.level_data_extent_bytes; + guest_mips_size = fallback_layout.mips_total_extent_bytes; + } + const uint32_t host_width = key.GetWidth() * (key.scaled_resolve ? draw_resolution_scale_x_ : 1); + const uint32_t host_height = + key.GetHeight() * (key.scaled_resolve ? draw_resolution_scale_y_ : 1); + + if (!DebugConsumeSkate3UltrawideTextureTraceLog()) { + return; + } + + const auto& invalidation_diagnostics = + rex::memory::GetPhysicalMemoryInvalidationDiagnostics(); + if (std::string_view(action).starts_with("invalidate-") && invalidation_diagnostics.valid) { + REXGPU_WARN( + "Skate3 texture trace: action={}, submission={}, fetch={}, key={:016X}, scaled={}, {} " + "{}x{}x{} host={}x{} {}, base={:08X}+{:X}, mips={:08X}+{:X}, mip_max={}, pitch={}, " + "outdated={:X}, host_mem_kb={}, write_va={:08X}, write_phys={:08X}+{:X}, " + "thread={:08X}, lr={:08X}, stack={:08X}, exact={}, unprotect={}", + action, current_submission_index_, fetch_index == UINT32_MAX ? -1 : int32_t(fetch_index), + key_hash, bool(key.scaled_resolve), key.tiled ? "tiled" : "linear", key.GetWidth(), + key.GetHeight(), key.GetDepthOrArraySize(), host_width, host_height, + format_info ? format_info->name : "unknown", key.base_page << 12, guest_base_size, + key.mip_page << 12, guest_mips_size, key.mip_max_level, key.pitch << 5, + texture ? texture->outdated_mask() : 0, + texture ? uint32_t((texture->GetHostMemoryUsage() + 1023) >> 10) : 0, + invalidation_diagnostics.virtual_address, invalidation_diagnostics.physical_address, + invalidation_diagnostics.length, invalidation_diagnostics.thread_id, + invalidation_diagnostics.lr, invalidation_diagnostics.stack, + invalidation_diagnostics.exact_range, invalidation_diagnostics.unprotect); + return; + } + REXGPU_WARN( + "Skate3 texture trace: action={}, submission={}, fetch={}, key={:016X}, scaled={}, {} " + "{}x{}x{} host={}x{} {}, base={:08X}+{:X}, mips={:08X}+{:X}, mip_max={}, pitch={}, " + "outdated={:X}, host_mem_kb={}", + action, current_submission_index_, fetch_index == UINT32_MAX ? -1 : int32_t(fetch_index), + key_hash, bool(key.scaled_resolve), key.tiled ? "tiled" : "linear", key.GetWidth(), + key.GetHeight(), key.GetDepthOrArraySize(), host_width, host_height, + format_info ? format_info->name : "unknown", key.base_page << 12, guest_base_size, + key.mip_page << 12, guest_mips_size, key.mip_max_level, key.pitch << 5, + texture ? texture->outdated_mask() : 0, + texture ? uint32_t((texture->GetHostMemoryUsage() + 1023) >> 10) : 0); +} + void TextureCache::DebugLogTeamProfileBackgroundTextureCandidate(const char* action, uint32_t fetch_index, const Texture& texture) { @@ -1216,6 +1369,26 @@ void TextureCache::Texture::MarkAsUsed() { void TextureCache::Texture::WatchCallback( [[maybe_unused]] const std::unique_lock& global_lock, bool is_mip, bool invalidated_by_gpu) { + const auto& invalidation_diagnostics = + rex::memory::GetPhysicalMemoryInvalidationDiagnostics(); + const bool ignore_skate3_streamer_invalidation = + !invalidated_by_gpu && REXCVAR_GET(skate3_ultrawide_ignore_streamer_texture_invalidations) && + invalidation_diagnostics.valid && invalidation_diagnostics.lr == 0x82CD4300; + if (ignore_skate3_streamer_invalidation) { + texture_cache().DebugLogSkate3UltrawideTextureTrace( + is_mip ? "suppress-streamer-mips" : "suppress-streamer-base", UINT32_MAX, key(), this); + if (is_mip) { + mips_watch_handle_ = nullptr; + } else { + base_watch_handle_ = nullptr; + } + return; + } + + texture_cache().DebugLogSkate3UltrawideTextureTrace( + invalidated_by_gpu ? (is_mip ? "invalidate-gpu-mips" : "invalidate-gpu-base") + : (is_mip ? "invalidate-cpu-mips" : "invalidate-cpu-base"), + UINT32_MAX, key(), this); texture_cache().DebugLogTeamProfileBackgroundTextureCandidate( invalidated_by_gpu ? (is_mip ? "invalidate-gpu-mips" : "invalidate-gpu-base") : (is_mip ? "invalidate-cpu-mips" : "invalidate-cpu-base"), @@ -1243,6 +1416,11 @@ void TextureCache::WatchCallback(const std::unique_lock& g void TextureCache::DestroyAllTextures(bool from_destructor) { ResetTextureBindings(from_destructor); + if (!from_destructor) { + for (const auto& [key, texture] : textures_) { + DebugLogSkate3UltrawideTextureTrace("destroy-clear", UINT32_MAX, key, texture.get()); + } + } textures_.clear(); COUNT_profile_set("gpu/texture_cache/textures", 0); } @@ -1365,6 +1543,7 @@ TextureCache::Texture* TextureCache::FindOrCreateTexture(TextureKey key) { } COUNT_profile_set("gpu/texture_cache/textures", textures_.size()); texture->LogAction("Created"); + DebugLogSkate3UltrawideTextureTrace("create", UINT32_MAX, texture->key(), texture); DebugLogTeamProfileBackgroundTextureCandidate("create", UINT32_MAX, *texture); return texture; } diff --git a/src/graphics/vulkan/command_processor.cpp b/src/graphics/vulkan/command_processor.cpp index 02bbe4a..ca243cc 100644 --- a/src/graphics/vulkan/command_processor.cpp +++ b/src/graphics/vulkan/command_processor.cpp @@ -39,6 +39,7 @@ #include #include #include +#include #include #include #include @@ -156,6 +157,138 @@ bool IsGameplayStateActive(const system::KernelState* kernel_state) { return gameplay_context && *gameplay_context == 1; } +struct Skate3UltrawideDrawState { + bool correction_active = false; + bool skip_shadow_targets = false; + uint32_t shadow_skip_mode = 1; + float hor_plus_scale = 1.0f; +}; + +double GetSkate3UltrawideTargetAspect(const ui::Presenter* presenter = nullptr) { + if (!rex::cvar::Query("skate3_ultrawide") || + !ultrawide_debug::IsSkate3GameplayUltrawideActive()) { + return 0.0; + } + + constexpr double kBaseAspect = 16.0 / 9.0; + uint32_t surface_width = 0; + uint32_t surface_height = 0; + if (presenter && presenter->GetSurfacePaintConnectionSize(surface_width, surface_height) && + surface_width && surface_height) { + const double surface_aspect = double(surface_width) / double(surface_height); + if (surface_aspect > kBaseAspect + 0.01) { + return std::clamp(surface_aspect, kBaseAspect, 8.0); + } + } + + const double target_aspect = rex::cvar::Query("skate3_ultrawide_target_aspect"); + return target_aspect > kBaseAspect + 0.01 ? std::clamp(target_aspect, kBaseAspect, 8.0) : 0.0; +} + +void ApplySkate3UltrawidePresenterAspect(const ui::Presenter* presenter, uint32_t& display_width, + uint32_t& display_height) { + const double target_aspect = GetSkate3UltrawideTargetAspect(presenter); + if (target_aspect <= 0.0) { + return; + } + + rex::cvar::SetFlagByName("skate3_ultrawide_target_aspect", std::to_string(target_aspect)); + + display_height = + uint32_t(std::clamp(rex::cvar::Query("skate3_ultrawide_base_height"), 480, 2160)); + display_width = + uint32_t(std::clamp(target_aspect * double(display_height) + 0.5, 1.0, 4095.0)); +} + +float ComputeSkate3UltrawideHorPlusScale() { + const double manual_scale = rex::cvar::Query("skate3_ultrawide_hor_plus_scale"); + if (manual_scale > 0.0) { + return float(std::clamp(manual_scale, 0.25, 4.0)); + } + + constexpr double kBaseAspect = 16.0 / 9.0; + double aspect = GetSkate3UltrawideTargetAspect(); + if (aspect <= 0.0) { + const int32_t display_width = REXCVAR_GET(video_mode_width); + const int32_t display_height = REXCVAR_GET(video_mode_height); + if (display_width <= 0 || display_height <= 0) { + return 1.0f; + } + aspect = double(display_width) / double(display_height); + } + + if (aspect <= kBaseAspect + 0.01f) { + return 1.0f; + } + + return float(std::clamp(kBaseAspect / aspect, 0.25, 1.0)); +} + +Skate3UltrawideDrawState QuerySkate3UltrawideDrawState() { + Skate3UltrawideDrawState state; + state.correction_active = rex::cvar::Query("skate3_ultrawide") && + rex::cvar::Query("skate3_ultrawide_hor_plus") && + ultrawide_debug::IsSkate3GameplayUltrawideActive() && + !rex::cvar::Query("skate3_ultrawide_disable_ndc_correction"); + if (!state.correction_active) { + return state; + } + + state.hor_plus_scale = ComputeSkate3UltrawideHorPlusScale(); + state.skip_shadow_targets = rex::cvar::Query("skate3_ultrawide_skip_shadow_targets"); + state.shadow_skip_mode = uint32_t(std::clamp( + rex::cvar::Query("skate3_ultrawide_shadow_skip_mode"), 1, 3)); + return state; +} + +Skate3UltrawideDrawState SnapshotSkate3UltrawideDrawState(uint64_t frame) { + thread_local uint64_t cached_frame = 0; + thread_local Skate3UltrawideDrawState cached_state; + if (cached_frame != frame) { + cached_state = QuerySkate3UltrawideDrawState(); + cached_frame = frame; + } + return cached_state; +} + +bool IsSkate3UltrawideDisplayViewport(const draw_util::ViewportInfo& viewport_info) { + const uint32_t width = viewport_info.xy_extent[0]; + const uint32_t height = viewport_info.xy_extent[1]; + if (!width || !height) { + return false; + } + + const uint64_t scaled_width = uint64_t(width) * 9; + const uint64_t scaled_height = uint64_t(height) * 16; + const uint64_t difference = + scaled_width > scaled_height ? scaled_width - scaled_height : scaled_height - scaled_width; + return difference <= scaled_height / 20; +} + +bool ShouldApplySkate3UltrawideHorPlus( + const ultrawide_debug::TargetKey& target_key, bool primitive_polygonal, + Shader::HostVertexShaderType host_vertex_shader_type, reg::PA_CL_VTE_CNTL pa_cl_vte_cntl, + const draw_util::ViewportInfo& viewport_info, + reg::RB_DEPTHCONTROL normalized_depth_control, + const Skate3UltrawideDrawState& ultrawide_state) { + if (!ultrawide_state.correction_active) { + return false; + } + + // Pre-divided XY is used by screen-space and orthographic passes. Widening + // those produces the menu/HUD fragments seen in the side areas. + const bool default_enabled = + host_vertex_shader_type == Shader::HostVertexShaderType::kVertex && primitive_polygonal && + normalized_depth_control.z_enable && normalized_depth_control.z_write_enable && + !pa_cl_vte_cntl.vtx_xy_fmt && IsSkate3UltrawideDisplayViewport(viewport_info); + const bool selected = ultrawide_debug::ShouldApplyAndRecord(target_key, default_enabled); + if (selected && ultrawide_state.skip_shadow_targets && + ultrawide_debug::IsShadowMapCandidate(target_key, ultrawide_state.shadow_skip_mode)) { + return false; + } + return selected; +} + bool IsInGameContextActive(const system::KernelState* kernel_state) { if (!kernel_state) { return false; @@ -2646,6 +2779,7 @@ void VulkanCommandProcessor::IssueSwap(uint32_t frontbuffer_ptr, uint32_t frontb kernel::xboxkrnl::VdQueryVideoMode(&video_mode); uint32_t display_width = std::max(uint32_t(1), uint32_t(video_mode.display_width)); uint32_t display_height = std::max(uint32_t(1), uint32_t(video_mode.display_height)); + ApplySkate3UltrawidePresenterAspect(presenter, display_width, display_height); presenter->RefreshGuestOutput( guest_output_width, guest_output_height, display_width, display_height, @@ -4325,6 +4459,8 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t // Update system constants before uploading them. UpdateSystemConstantValues(primitive_polygonal, primitive_processing_result, shader_32bit_index_dma, 0, viewport_info, used_texture_mask, + vertex_shader->ucode_data_hash(), + pixel_shader ? pixel_shader->ucode_data_hash() : 0, normalized_depth_control, normalized_color_mask); // Update uniform buffers and descriptor sets after binding the pipeline with @@ -4527,6 +4663,67 @@ bool VulkanCommandProcessor::IssueDraw(xenos::PrimitiveType prim_type, uint32_t } } } + bool skate3_ultrawide_submit_draw = true; + if (rex::cvar::Query("skate3_ultrawide_trace_draws")) { + ultrawide_debug::DrawFingerprint fingerprint; + fingerprint.bucket = + memexport_writes_possible + ? ultrawide_debug::DrawBucket::kMemexport + : (edram_mode == xenos::EdramMode::kDepthOnly + ? ultrawide_debug::DrawBucket::kDepthOnly + : (pixel_shader == nullptr ? ultrawide_debug::DrawBucket::kNoPixelShader + : ultrawide_debug::DrawBucket::kMainColorDepth)); + fingerprint.vertex_shader_hash = vertex_shader->ucode_data_hash(); + fingerprint.pixel_shader_hash = pixel_shader ? pixel_shader->ucode_data_hash() : 0; + fingerprint.primitive_type = uint32_t(primitive_processing_result.host_primitive_type); + fingerprint.vertex_count = host_draw_vertex_count; + fingerprint.primitive_count = host_draw_primitive_count; + fingerprint.packet_ptr = current_packet_ptr_; + if (index_buffer_info) { + fingerprint.index_guest_base = index_buffer_info->guest_base; + fingerprint.index_length = uint32_t(index_buffer_info->length); + } + const Shader::ConstantRegisterMap& constant_map = vertex_shader->constant_register_map(); + size_t fetch_count = 0; + for (uint32_t i = 0; i < rex::countof(constant_map.vertex_fetch_bitmap); ++i) { + uint32_t fetch_bits = constant_map.vertex_fetch_bitmap[i]; + uint32_t bit = 0; + while (fetch_count < ultrawide_debug::DrawFingerprint::kVertexFetchCount && + rex::bit_scan_forward(fetch_bits, &bit)) { + fetch_bits &= ~(uint32_t(1) << bit); + xenos::xe_gpu_vertex_fetch_t fetch = regs.GetVertexFetch(i * 32 + bit); + fingerprint.vertex_fetch_address[fetch_count] = fetch.address << 2; + fingerprint.vertex_fetch_size[fetch_count] = fetch.size << 2; + ++fetch_count; + } + if (fetch_count >= ultrawide_debug::DrawFingerprint::kVertexFetchCount) { + break; + } + } + size_t texture_count = 0; + uint32_t texture_bits = used_texture_mask; + uint32_t texture_index = 0; + while (texture_count < ultrawide_debug::DrawFingerprint::kTextureFetchCount && + rex::bit_scan_forward(texture_bits, &texture_index)) { + texture_bits &= ~(UINT32_C(1) << texture_index); + TextureCache::DebugActiveTextureBinding binding; + if (!texture_cache_->DebugGetActiveTextureBinding(texture_index, binding)) { + continue; + } + fingerprint.texture_key_hash[texture_count] = binding.key_hash; + fingerprint.texture_fetch_index[texture_count] = binding.fetch_index; + fingerprint.texture_base_address[texture_count] = binding.base_address; + fingerprint.texture_base_length[texture_count] = binding.base_length; + fingerprint.texture_width[texture_count] = binding.width; + fingerprint.texture_height[texture_count] = binding.height; + fingerprint.texture_format[texture_count] = binding.format; + ++texture_count; + } + skate3_ultrawide_submit_draw = ultrawide_debug::RecordDrawFingerprint(fingerprint); + } + if (!skate3_ultrawide_submit_draw) { + return true; + } // Draw. uint32_t gpu_timestamp_start = BeginGpuTimestampedDraw(draw_bucket); @@ -7101,6 +7298,7 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( const PrimitiveProcessor::ProcessingResult& primitive_processing_result, bool shader_32bit_index_dma, uint32_t compute_memexport_vertex_count, const draw_util::ViewportInfo& viewport_info, uint32_t used_texture_mask, + uint64_t vertex_shader_hash, uint64_t pixel_shader_hash, reg::RB_DEPTHCONTROL normalized_depth_control, uint32_t normalized_color_mask) { #if XE_GPU_FINE_GRAINED_DRAW_SCOPES SCOPE_profile_cpu_f("gpu"); @@ -7360,10 +7558,39 @@ void VulkanCommandProcessor::UpdateSystemConstantValues( system_constants_.tessellation_factor_range_max = tessellation_factor_max; // Conversion to host normalized device coordinates. + ultrawide_debug::TargetKey skate3_ultrawide_target_key; + for (uint32_t i = 0; i < 4; ++i) { + skate3_ultrawide_target_key.color_info[i] = color_infos[i].value; + } + skate3_ultrawide_target_key.depth_info = rb_depth_info.value; + skate3_ultrawide_target_key.surface_info = rb_surface_info.value; + skate3_ultrawide_target_key.viewport_x = viewport_info.xy_offset[0]; + skate3_ultrawide_target_key.viewport_y = viewport_info.xy_offset[1]; + skate3_ultrawide_target_key.viewport_width = viewport_info.xy_extent[0]; + skate3_ultrawide_target_key.viewport_height = viewport_info.xy_extent[1]; + skate3_ultrawide_target_key.color_mask = normalized_color_mask; + skate3_ultrawide_target_key.depth_control = normalized_depth_control.value; + skate3_ultrawide_target_key.pa_cl_vte_cntl = pa_cl_vte_cntl.value; + skate3_ultrawide_target_key.primitive_type = uint32_t(vgt_draw_initiator.prim_type); + skate3_ultrawide_target_key.host_vertex_shader_type = + uint32_t(primitive_processing_result.host_vertex_shader_type); + skate3_ultrawide_target_key.vertex_shader_hash = vertex_shader_hash; + skate3_ultrawide_target_key.pixel_shader_hash = pixel_shader_hash; + const Skate3UltrawideDrawState skate3_ultrawide_state = + SnapshotSkate3UltrawideDrawState(frame_current_); + const bool skate3_ultrawide_hor_plus_draw = ShouldApplySkate3UltrawideHorPlus( + skate3_ultrawide_target_key, primitive_polygonal, + primitive_processing_result.host_vertex_shader_type, pa_cl_vte_cntl, viewport_info, + normalized_depth_control, skate3_ultrawide_state); + const float skate3_ultrawide_hor_plus_scale = + skate3_ultrawide_hor_plus_draw ? skate3_ultrawide_state.hor_plus_scale : 1.0f; for (uint32_t i = 0; i < 3; ++i) { - dirty |= system_constants_.ndc_scale[i] != viewport_info.ndc_scale[i]; + const float ndc_scale = + i == 0 ? viewport_info.ndc_scale[i] * skate3_ultrawide_hor_plus_scale + : viewport_info.ndc_scale[i]; + dirty |= system_constants_.ndc_scale[i] != ndc_scale; dirty |= system_constants_.ndc_offset[i] != viewport_info.ndc_offset[i]; - system_constants_.ndc_scale[i] = viewport_info.ndc_scale[i]; + system_constants_.ndc_scale[i] = ndc_scale; system_constants_.ndc_offset[i] = viewport_info.ndc_offset[i]; } diff --git a/src/kernel/xam/apps/xgi_app.cpp b/src/kernel/xam/apps/xgi_app.cpp index a4d2011..8b019bd 100644 --- a/src/kernel/xam/apps/xgi_app.cpp +++ b/src/kernel/xam/apps/xgi_app.cpp @@ -10,6 +10,7 @@ */ #include +#include #include #include @@ -42,6 +43,8 @@ X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr, uint32_t context_value = memory::load_and_swap(buffer + 20); auto old_value = kernel_state_->GetUserContext(user_index, context_id); kernel_state_->SetUserContext(user_index, context_id, context_value); + rex::graphics::ultrawide_debug::NotifySkate3GameplayContext(user_index, context_id, + context_value); if (!old_value || *old_value != context_value) { REXKRNL_INFO("XGIUserSetContextEx(user={}, context={:#x}, value={:#x})", user_index, context_id, context_value); diff --git a/src/kernel/xboxkrnl/xboxkrnl_video.cpp b/src/kernel/xboxkrnl/xboxkrnl_video.cpp index 4bbd539..ab32f58 100644 --- a/src/kernel/xboxkrnl/xboxkrnl_video.cpp +++ b/src/kernel/xboxkrnl/xboxkrnl_video.cpp @@ -19,6 +19,7 @@ #include #include #include +#include #include #include #include @@ -38,10 +39,26 @@ namespace { constexpr uint32_t kDisplayGammaType = 2; // Display gamma power (used with gamma type 3) constexpr double kDisplayGammaPower = 2.22222233; +constexpr double kSkate3BaseAspect = 16.0 / 9.0; + +bool Skate3UltrawidePresentationActive() { + return rex::cvar::Query("skate3_ultrawide") && + rex::graphics::ultrawide_debug::IsSkate3GameplayUltrawideActive() && + rex::cvar::Query("skate3_ultrawide_target_aspect") > + kSkate3BaseAspect + 0.01; +} + +bool Skate3UltrawideConfiguredBeforeGameplay() { + return rex::cvar::Query("skate3_ultrawide") && + !rex::graphics::ultrawide_debug::IsSkate3GameplayUltrawideActive() && + rex::cvar::Query("skate3_ultrawide_target_aspect") > + kSkate3BaseAspect + 0.01; +} uint32_t GetConfiguredVideoModeWidth() { int32_t configured_width = REXCVAR_GET(video_mode_width); - if (!rex::cvar::HasNonDefaultValue("video_mode_width")) { + if (!rex::cvar::HasNonDefaultValue("video_mode_width") && + !Skate3UltrawidePresentationActive() && !Skate3UltrawideConfiguredBeforeGameplay()) { if (rex::cvar::HasNonDefaultValue("window_width") && REXCVAR_GET(window_width) > 0) { configured_width = REXCVAR_GET(window_width); } else { @@ -58,7 +75,8 @@ uint32_t GetConfiguredVideoModeWidth() { uint32_t GetConfiguredVideoModeHeight() { int32_t configured_height = REXCVAR_GET(video_mode_height); - if (!rex::cvar::HasNonDefaultValue("video_mode_height")) { + if (!rex::cvar::HasNonDefaultValue("video_mode_height") && + !Skate3UltrawidePresentationActive() && !Skate3UltrawideConfiguredBeforeGameplay()) { if (rex::cvar::HasNonDefaultValue("window_height") && REXCVAR_GET(window_height) > 0) { configured_height = REXCVAR_GET(window_height); } else { diff --git a/src/system/xmemory.cpp b/src/system/xmemory.cpp index 6dbec66..0c542aa 100644 --- a/src/system/xmemory.cpp +++ b/src/system/xmemory.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include @@ -43,6 +44,12 @@ REXCVAR_DEFINE_BOOL(scribble_heap, false, "Memory", "Scribble 0xCD into all allo namespace rex::memory { +thread_local PhysicalMemoryInvalidationDiagnostics physical_memory_invalidation_diagnostics_; + +const PhysicalMemoryInvalidationDiagnostics& GetPhysicalMemoryInvalidationDiagnostics() { + return physical_memory_invalidation_diagnostics_; +} + uint32_t get_page_count(uint32_t value, uint32_t page_size, uint32_t page_size_shift) { return rex::round_up(value, page_size) >> page_size_shift; } @@ -2024,6 +2031,23 @@ bool PhysicalHeap::TriggerCallbacks(std::unique_lock globa heap_size_ - (physical_address_start - physical_address_offset)); uint32_t unwatch_first = 0; uint32_t unwatch_last = UINT32_MAX; + + PhysicalMemoryInvalidationDiagnostics previous_diagnostics = + physical_memory_invalidation_diagnostics_; + physical_memory_invalidation_diagnostics_ = {}; + physical_memory_invalidation_diagnostics_.valid = true; + physical_memory_invalidation_diagnostics_.virtual_address = virtual_address; + physical_memory_invalidation_diagnostics_.physical_address = physical_address_start; + physical_memory_invalidation_diagnostics_.length = physical_length; + physical_memory_invalidation_diagnostics_.exact_range = unwatch_exact_range; + physical_memory_invalidation_diagnostics_.unprotect = unprotect; + if (rex::runtime::ThreadState* thread_state = rex::runtime::ThreadState::Get()) { + physical_memory_invalidation_diagnostics_.thread_id = thread_state->thread_id(); + if (PPCContext* ppc_context = thread_state->context()) { + physical_memory_invalidation_diagnostics_.lr = static_cast(ppc_context->lr); + physical_memory_invalidation_diagnostics_.stack = ppc_context->r1.u32; + } + } for (auto invalidation_callback : memory_->physical_memory_invalidation_callbacks_) { std::pair callback_unwatch_range = invalidation_callback->first(invalidation_callback->second, physical_address_start, @@ -2035,6 +2059,7 @@ bool PhysicalHeap::TriggerCallbacks(std::unique_lock globa std::max(callback_unwatch_range.second, uint32_t(1)) - 1)); } } + physical_memory_invalidation_diagnostics_ = previous_diagnostics; if (!unwatch_exact_range) { // Always unwatch at least the requested pages. unwatch_first = std::min(unwatch_first, physical_address_start); diff --git a/src/ui/CMakeLists.txt b/src/ui/CMakeLists.txt index cc6f113..a8f4629 100644 --- a/src/ui/CMakeLists.txt +++ b/src/ui/CMakeLists.txt @@ -23,6 +23,7 @@ set(REXUI_OVERLAY_SOURCES overlay/install_wizard_overlay.cpp overlay/simple_settings_overlay.cpp overlay/settings_overlay.cpp + overlay/ultrawide_targets_overlay.cpp ) # Vulkan UI backend diff --git a/src/ui/overlay/simple_settings_overlay.cpp b/src/ui/overlay/simple_settings_overlay.cpp index bbe16f6..ade8ef6 100644 --- a/src/ui/overlay/simple_settings_overlay.cpp +++ b/src/ui/overlay/simple_settings_overlay.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include @@ -18,7 +19,9 @@ namespace rex::ui { namespace { constexpr std::array kResolutionScales = {1, 2, 3}; -constexpr std::array kResolutionLabels = {"720p", "1440p", "2160p"}; +constexpr std::array kResolutionLabels = {"720p (1x)", "1440p (2x)", + "2160p (3x)"}; +constexpr std::array kAspectRatioLabels = {"16:9", "21:9 (Experimental)"}; constexpr std::array kFrameCapRates = {60.0, 90.0, 120.0, 144.0, 165.0, 240.0}; constexpr std::array kFrameCapLabels = {"Unlimited", "60 FPS", "90 FPS", "120 FPS", "144 FPS", "165 FPS", @@ -40,6 +43,12 @@ std::vector GetSimpleSettingsCvars() { std::vector cvars; cvars.reserve(14); cvars.insert(cvars.end(), kCoreSimpleSettingsCvars.begin(), kCoreSimpleSettingsCvars.end()); + if (HasCvar("skate3_ultrawide")) { + cvars.push_back("skate3_ultrawide"); + } + if (HasCvar("skate3_field_of_view")) { + cvars.push_back("skate3_field_of_view"); + } if (HasCvar("d3d12_present_frame_limiter")) { cvars.push_back("d3d12_present_frame_limiter"); } @@ -100,6 +109,17 @@ int FrameCapIndexFromCvar() { return best; } +bool HasFieldOfViewCvar() { + return HasCvar("skate3_field_of_view"); +} + +float FieldOfViewFromCvar() { + if (!HasFieldOfViewCvar()) { + return 60.0f; + } + return float(std::clamp(rex::cvar::Query("skate3_field_of_view"), 40.0, 120.0)); +} + void CopyToBuffer(char* buffer, size_t buffer_size, const std::string& value) { size_t count = std::min(value.size(), buffer_size - 1); std::copy_n(value.data(), count, buffer); @@ -215,6 +235,9 @@ void SimpleSettingsDialog::Show() { void SimpleSettingsDialog::LoadSettingsFromCvars() { resolution_scale_index_ = ResolutionIndexFromCvar(); frame_cap_index_ = FrameCapIndexFromCvar(); + aspect_ratio_index_ = + HasCvar("skate3_ultrawide") && rex::cvar::Query("skate3_ultrawide") ? 1 : 0; + field_of_view_ = FieldOfViewFromCvar(); fullscreen_ = rex::cvar::Query("fullscreen"); vsync_ = rex::cvar::Query("vsync"); tearing_ = TearingFromCvar(); @@ -225,6 +248,8 @@ void SimpleSettingsDialog::LoadSettingsFromCvars() { bool SimpleSettingsDialog::HasSettingsChanges() const { return resolution_scale_index_ != ResolutionIndexFromCvar() || frame_cap_index_ != FrameCapIndexFromCvar() || + (HasCvar("skate3_ultrawide") && + (aspect_ratio_index_ != 0) != rex::cvar::Query("skate3_ultrawide")) || fullscreen_ != rex::cvar::Query("fullscreen") || vsync_ != rex::cvar::Query("vsync") || tearing_ != TearingFromCvar() || @@ -269,6 +294,9 @@ void SimpleSettingsDialog::SaveVideo() { std::clamp(resolution_scale_index_, 0, static_cast(kResolutionScales.size()) - 1); frame_cap_index_ = std::clamp(frame_cap_index_, 0, static_cast(kFrameCapLabels.size()) - 1); + aspect_ratio_index_ = + std::clamp(aspect_ratio_index_, 0, static_cast(kAspectRatioLabels.size()) - 1); + field_of_view_ = std::clamp(field_of_view_, 40.0f, 120.0f); const auto scale = std::to_string(kResolutionScales[resolution_scale_index_]); rex::cvar::SetFlagByName("resolution_scale", scale); rex::cvar::SetFlagByName("draw_resolution_scale_x", scale); @@ -281,6 +309,12 @@ void SimpleSettingsDialog::SaveVideo() { std::to_string(kFrameCapRates[frame_cap_index_ - 1])); } SetBoolCvar("fullscreen", fullscreen_); + if (HasCvar("skate3_ultrawide")) { + SetBoolCvar("skate3_ultrawide", aspect_ratio_index_ != 0); + } + if (HasFieldOfViewCvar()) { + rex::cvar::SetFlagByName("skate3_field_of_view", std::to_string(field_of_view_)); + } SetBoolCvar("vsync", vsync_); SetTearingCvars(tearing_); SetBoolCvar("mnk_mode", mnk_mode_); @@ -396,7 +430,7 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) { if (selected_tab_ == 0) { SectionTitle("Video"); - BeginFieldRow("Resolution scale"); + BeginFieldRow("Resolution Scale"); ImGui::Combo("##resolution_scale", &resolution_scale_index_, kResolutionLabels.data(), static_cast(kResolutionLabels.size())); EndFieldRow(); @@ -408,6 +442,25 @@ void SimpleSettingsDialog::OnDraw(ImGuiIO& io) { EndFieldRow(); } + if (HasCvar("skate3_ultrawide")) { + BeginFieldRow("Aspect Ratio"); + ImGui::Combo("##aspect_ratio", &aspect_ratio_index_, kAspectRatioLabels.data(), + static_cast(kAspectRatioLabels.size())); + EndFieldRow(); + } + + if (HasFieldOfViewCvar()) { + BeginFieldRow("Field of View"); + if (ImGui::SliderFloat("##field_of_view", &field_of_view_, 40.0f, 120.0f, "%.0f")) { + field_of_view_ = std::clamp(field_of_view_, 40.0f, 120.0f); + rex::cvar::SetFlagByName("skate3_field_of_view", std::to_string(field_of_view_)); + } + if (ImGui::IsItemDeactivatedAfterEdit()) { + SaveSimpleSettingsConfig(config_path_); + } + EndFieldRow(); + } + ImGui::Separator(); ImGui::Spacing(); ImGui::Checkbox("Fullscreen", &fullscreen_); diff --git a/src/ui/overlay/ultrawide_targets_overlay.cpp b/src/ui/overlay/ultrawide_targets_overlay.cpp new file mode 100644 index 0000000..174ca4e --- /dev/null +++ b/src/ui/overlay/ultrawide_targets_overlay.cpp @@ -0,0 +1,1431 @@ +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +namespace rex::ui { + +namespace { + +std::string Hex(uint64_t value, uint32_t width) { + std::ostringstream stream; + stream << std::uppercase << std::hex << std::setw(width) << std::setfill('0') << value; + return stream.str(); +} + +std::string TargetGroupLabel(const graphics::ultrawide_debug::TargetEntry& entry) { + const auto& key = entry.key; + std::ostringstream stream; + stream << "RT0 " << Hex(key.color_info[0], 8) << " / D " << Hex(key.depth_info, 8) + << " / " << key.viewport_width << "x" << key.viewport_height; + return stream.str(); +} + +const char* ScreenCallbackKindLabel(graphics::ultrawide_debug::ScreenCallbackKind kind) { + switch (kind) { + case graphics::ultrawide_debug::ScreenCallbackKind::kWidth: + return "Width"; + case graphics::ultrawide_debug::ScreenCallbackKind::kHeight: + return "Height"; + case graphics::ultrawide_debug::ScreenCallbackKind::kScreenFlag: + return "Screen flag"; + case graphics::ultrawide_debug::ScreenCallbackKind::kWide: + return "Wide"; + } + return "Unknown"; +} + +const char* DrawBucketLabel(graphics::ultrawide_debug::DrawBucket bucket) { + switch (bucket) { + case graphics::ultrawide_debug::DrawBucket::kMainColorDepth: + return "Main"; + case graphics::ultrawide_debug::DrawBucket::kDepthOnly: + return "Depth"; + case graphics::ultrawide_debug::DrawBucket::kCopyResolve: + return "Copy"; + case graphics::ultrawide_debug::DrawBucket::kMemexport: + return "Memexport"; + case graphics::ultrawide_debug::DrawBucket::kNoPixelShader: + return "No PS"; + } + return "Unknown"; +} + +struct DrawDiffEntry { + graphics::ultrawide_debug::DrawFingerprint fingerprint; + uint64_t visible_count = 0; + uint64_t popped_count = 0; +}; + +enum class DrawGroupMode : uint32_t { + kShader = 0, + kObject = 1, + kTexture = 2, +}; + +struct DrawGroupKey { + DrawGroupMode mode = DrawGroupMode::kObject; + graphics::ultrawide_debug::DrawBucket bucket = + graphics::ultrawide_debug::DrawBucket::kMainColorDepth; + uint32_t guest_render_object = 0; + uint32_t guest_render_vtable = 0; + uint32_t guest_render_target = 0; + uint64_t vertex_shader_hash = 0; + uint64_t pixel_shader_hash = 0; + uint32_t primitive_type = 0; + uint32_t index_guest_base = 0; + uint32_t index_length = 0; + std::array + vertex_fetch_address{}; + std::array + vertex_fetch_size{}; + std::array + texture_key_hash{}; + + bool operator==(const DrawGroupKey& other) const { + return mode == other.mode && bucket == other.bucket && + guest_render_object == other.guest_render_object && + guest_render_vtable == other.guest_render_vtable && + guest_render_target == other.guest_render_target && + vertex_shader_hash == other.vertex_shader_hash && + pixel_shader_hash == other.pixel_shader_hash && primitive_type == other.primitive_type && + index_guest_base == other.index_guest_base && index_length == other.index_length && + vertex_fetch_address == other.vertex_fetch_address && + vertex_fetch_size == other.vertex_fetch_size && + texture_key_hash == other.texture_key_hash; + } +}; + +struct DrawGroupKeyHash { + size_t operator()(const DrawGroupKey& key) const { + uint64_t hash = 1469598103934665603ull; + auto hash_add = [&hash](uint64_t value) { + for (size_t i = 0; i < sizeof(value); ++i) { + hash ^= uint8_t(value >> (i * 8)); + hash *= 1099511628211ull; + } + }; + hash_add(uint32_t(key.mode)); + hash_add(uint32_t(key.bucket)); + hash_add(key.guest_render_object); + hash_add(key.guest_render_vtable); + hash_add(key.guest_render_target); + hash_add(key.vertex_shader_hash); + hash_add(key.pixel_shader_hash); + hash_add(key.primitive_type); + hash_add(key.index_guest_base); + hash_add(key.index_length); + for (uint32_t fetch_address : key.vertex_fetch_address) { + hash_add(fetch_address); + } + for (uint32_t fetch_size : key.vertex_fetch_size) { + hash_add(fetch_size); + } + for (uint64_t texture_hash : key.texture_key_hash) { + hash_add(texture_hash); + } + return size_t(hash); + } +}; + +struct DrawGroupEntry { + DrawGroupKey key; + graphics::ultrawide_debug::DrawFingerprint representative; + graphics::ultrawide_debug::DrawFingerprint textured_representative; + graphics::ultrawide_debug::DrawFingerprint main_textured_representative; + uint64_t group_hash = 0; + uint64_t draw_count = 0; + uint64_t textured_draw_count = 0; + uint64_t main_textured_draw_count = 0; + uint64_t submitted_count = 0; + uint64_t skipped_count = 0; + uint64_t vertices = 0; + uint64_t primitives = 0; + uint32_t min_vertex_count = 0; + uint32_t max_vertex_count = 0; + uint32_t min_primitive_count = 0; + uint32_t max_primitive_count = 0; + uint32_t exact_count = 0; + uint32_t enabled_count = 0; + uint64_t last_seen_order = 0; + uint64_t last_textured_seen_order = 0; + uint64_t last_main_textured_seen_order = 0; + uint64_t last_seen_ms = 0; + uint64_t last_textured_seen_ms = 0; + uint64_t last_main_textured_seen_ms = 0; + std::vector exact_hashes; +}; + +struct DrawGroupDiffEntry { + DrawGroupKey key; + graphics::ultrawide_debug::DrawFingerprint representative; + uint64_t group_hash = 0; + uint64_t visible_count = 0; + uint64_t popped_count = 0; +}; + +struct DrawWatchEvent { + uint64_t sequence = 0; + bool present = false; + uint64_t age = 0; + uint32_t packet_ptr = 0; + uint32_t index_guest_base = 0; + uint32_t vertex_fetch_address = 0; + uint64_t group_hash = 0; +}; + +std::vector g_visible_draw_capture; +std::vector g_popped_draw_capture; +std::optional g_watched_draw_group; +uint64_t g_watched_draw_group_hash = 0; +std::deque g_draw_watch_events; +uint64_t g_draw_watch_event_sequence = 0; +bool g_draw_watch_state_valid = false; +bool g_draw_watch_present = false; +int g_draw_watch_age_threshold = 120; + +uint64_t NowMilliseconds() { + return uint64_t(std::chrono::duration_cast( + std::chrono::steady_clock::now().time_since_epoch()) + .count()); +} + +bool DrawHasTexture(const graphics::ultrawide_debug::DrawFingerprint& fingerprint) { + for (uint64_t texture_hash : fingerprint.texture_key_hash) { + if (texture_hash != 0) { + return true; + } + } + return false; +} + +DrawGroupKey MakeDrawGroupKey(const graphics::ultrawide_debug::DrawFingerprint& fingerprint, + DrawGroupMode mode) { + DrawGroupKey key; + key.mode = mode; + key.guest_render_object = fingerprint.guest_render_object; + key.guest_render_vtable = fingerprint.guest_render_vtable; + key.guest_render_target = fingerprint.guest_render_target; + + if (mode == DrawGroupMode::kShader) { + key.bucket = fingerprint.bucket; + key.vertex_shader_hash = fingerprint.vertex_shader_hash; + key.pixel_shader_hash = fingerprint.pixel_shader_hash; + key.primitive_type = fingerprint.primitive_type; + std::copy(std::begin(fingerprint.vertex_fetch_size), std::end(fingerprint.vertex_fetch_size), + key.vertex_fetch_size.begin()); + return key; + } + + if (mode == DrawGroupMode::kTexture) { + size_t texture_count = 0; + for (uint64_t texture_hash : fingerprint.texture_key_hash) { + if (texture_hash != 0 && texture_count < key.texture_key_hash.size()) { + key.texture_key_hash[texture_count++] = texture_hash; + } + } + std::sort(key.texture_key_hash.begin(), key.texture_key_hash.end()); + if (texture_count != 0) { + return key; + } + } + + if (mode == DrawGroupMode::kObject && fingerprint.guest_render_object != 0) { + key.bucket = fingerprint.bucket; + return key; + } + + key.index_guest_base = fingerprint.index_guest_base; + key.index_length = fingerprint.index_length; + std::copy(std::begin(fingerprint.vertex_fetch_address), + std::end(fingerprint.vertex_fetch_address), key.vertex_fetch_address.begin()); + std::copy(std::begin(fingerprint.vertex_fetch_size), std::end(fingerprint.vertex_fetch_size), + key.vertex_fetch_size.begin()); + return key; +} + +uint64_t HashDrawGroupKey(const DrawGroupKey& key) { + return uint64_t(DrawGroupKeyHash{}(key)); +} + +std::string DrawObjectLabel(const graphics::ultrawide_debug::DrawFingerprint& fingerprint) { + std::ostringstream stream; + if (fingerprint.guest_render_object != 0) { + stream << "Obj " << Hex(fingerprint.guest_render_object, 8); + if (fingerprint.guest_render_vtable != 0) { + stream << " VT " << Hex(fingerprint.guest_render_vtable, 8); + } + return stream.str(); + } + if (fingerprint.index_guest_base != 0) { + stream << "IB " << Hex(fingerprint.index_guest_base, 8); + if (fingerprint.index_length != 0) { + stream << "+" << std::uppercase << std::hex << fingerprint.index_length; + } + } else { + stream << "NoIB"; + } + for (size_t i = 0; i < graphics::ultrawide_debug::DrawFingerprint::kVertexFetchCount; ++i) { + if (fingerprint.vertex_fetch_address[i] == 0) { + continue; + } + stream << " VF" << i << " " << Hex(fingerprint.vertex_fetch_address[i], 8); + break; + } + return stream.str(); +} + +std::string DrawTextureLabel(const graphics::ultrawide_debug::DrawFingerprint& fingerprint) { + for (size_t i = 0; i < graphics::ultrawide_debug::DrawFingerprint::kTextureFetchCount; ++i) { + if (fingerprint.texture_key_hash[i] == 0) { + continue; + } + std::ostringstream stream; + stream << "t" << fingerprint.texture_fetch_index[i] << " " + << Hex(fingerprint.texture_key_hash[i], 16); + if (fingerprint.texture_width[i] != 0 || fingerprint.texture_height[i] != 0) { + stream << " " << fingerprint.texture_width[i] << "x" << fingerprint.texture_height[i]; + } + if (fingerprint.texture_base_address[i] != 0) { + stream << " @" << Hex(fingerprint.texture_base_address[i], 8); + } + return stream.str(); + } + return ""; +} + +uint32_t FirstVertexFetchAddress( + const graphics::ultrawide_debug::DrawFingerprint& fingerprint) { + for (uint32_t address : fingerprint.vertex_fetch_address) { + if (address != 0) { + return address; + } + } + return 0; +} + +uint32_t FirstTextureBaseAddress( + const graphics::ultrawide_debug::DrawFingerprint& fingerprint) { + for (uint32_t address : fingerprint.texture_base_address) { + if (address != 0) { + return address; + } + } + return 0; +} + +const graphics::ultrawide_debug::DrawFingerprint& BestWatchFingerprint( + const DrawGroupEntry* group, + const graphics::ultrawide_debug::DrawFingerprint& fallback) { + if (group && group->last_main_textured_seen_order != 0) { + return group->main_textured_representative; + } + if (group && group->last_textured_seen_order != 0) { + return group->textured_representative; + } + return fallback; +} + +void SetWatchedDrawGroup(const DrawGroupKey& key, uint64_t group_hash, + const graphics::ultrawide_debug::DrawFingerprint& fingerprint) { + g_watched_draw_group = key; + g_watched_draw_group_hash = group_hash; + g_draw_watch_events.clear(); + g_draw_watch_state_valid = false; + graphics::ultrawide_debug::SetGuestStoreWatchTargets( + fingerprint.index_guest_base, FirstVertexFetchAddress(fingerprint), + FirstTextureBaseAddress(fingerprint), group_hash); +} + +std::vector BuildDrawGroups( + const std::vector& entries, + DrawGroupMode mode) { + std::unordered_map rows; + for (const auto& entry : entries) { + DrawGroupKey key = MakeDrawGroupKey(entry.fingerprint, mode); + auto [it, inserted] = rows.try_emplace(key); + DrawGroupEntry& row = it->second; + if (inserted) { + row.key = key; + row.representative = entry.fingerprint; + row.group_hash = HashDrawGroupKey(key); + row.min_vertex_count = entry.fingerprint.vertex_count; + row.max_vertex_count = entry.fingerprint.vertex_count; + row.min_primitive_count = entry.fingerprint.primitive_count; + row.max_primitive_count = entry.fingerprint.primitive_count; + } else { + row.min_vertex_count = std::min(row.min_vertex_count, entry.fingerprint.vertex_count); + row.max_vertex_count = std::max(row.max_vertex_count, entry.fingerprint.vertex_count); + row.min_primitive_count = std::min(row.min_primitive_count, entry.fingerprint.primitive_count); + row.max_primitive_count = std::max(row.max_primitive_count, entry.fingerprint.primitive_count); + } + if (entry.last_seen_order >= row.last_seen_order) { + row.representative = entry.fingerprint; + row.last_seen_ms = entry.last_seen_ms; + } + const bool has_texture = DrawHasTexture(entry.fingerprint); + const bool main_textured = + has_texture && + entry.fingerprint.bucket == graphics::ultrawide_debug::DrawBucket::kMainColorDepth; + if (has_texture) { + row.textured_draw_count += entry.draw_count; + if (entry.last_seen_order >= row.last_textured_seen_order) { + row.textured_representative = entry.fingerprint; + row.last_textured_seen_order = entry.last_seen_order; + row.last_textured_seen_ms = entry.last_seen_ms; + } + } + if (main_textured) { + row.main_textured_draw_count += entry.draw_count; + if (entry.last_seen_order >= row.last_main_textured_seen_order) { + row.main_textured_representative = entry.fingerprint; + row.last_main_textured_seen_order = entry.last_seen_order; + row.last_main_textured_seen_ms = entry.last_seen_ms; + } + } + row.draw_count += entry.draw_count; + row.submitted_count += entry.submitted_count; + row.skipped_count += entry.skipped_count; + row.vertices += entry.vertices; + row.primitives += entry.primitives; + ++row.exact_count; + if (entry.enabled) { + ++row.enabled_count; + } + row.last_seen_order = std::max(row.last_seen_order, entry.last_seen_order); + row.last_seen_ms = std::max(row.last_seen_ms, entry.last_seen_ms); + row.exact_hashes.push_back(entry.hash ? entry.hash + : graphics::ultrawide_debug::HashDrawFingerprint(entry.fingerprint)); + } + + std::vector groups; + groups.reserve(rows.size()); + for (auto& [_, row] : rows) { + groups.push_back(std::move(row)); + } + std::sort(groups.begin(), groups.end(), [](const DrawGroupEntry& a, const DrawGroupEntry& b) { + if (a.key.bucket != b.key.bucket) { + return uint32_t(a.key.bucket) < uint32_t(b.key.bucket); + } + if (a.draw_count != b.draw_count) { + return a.draw_count > b.draw_count; + } + if (a.exact_count != b.exact_count) { + return a.exact_count > b.exact_count; + } + return a.group_hash < b.group_hash; + }); + return groups; +} + +std::vector BuildDrawDiff() { + std::unordered_map rows; + for (const auto& entry : g_visible_draw_capture) { + auto& row = rows[graphics::ultrawide_debug::HashDrawFingerprint(entry.fingerprint)]; + row.fingerprint = entry.fingerprint; + row.visible_count += entry.draw_count; + } + for (const auto& entry : g_popped_draw_capture) { + auto& row = rows[graphics::ultrawide_debug::HashDrawFingerprint(entry.fingerprint)]; + if (!row.visible_count) { + row.fingerprint = entry.fingerprint; + } + row.popped_count += entry.draw_count; + } + + std::vector diff; + for (const auto& [hash, row] : rows) { + (void)hash; + if (row.visible_count > row.popped_count) { + diff.push_back(row); + } + } + std::sort(diff.begin(), diff.end(), [](const DrawDiffEntry& a, const DrawDiffEntry& b) { + const uint64_t a_delta = a.visible_count - a.popped_count; + const uint64_t b_delta = b.visible_count - b.popped_count; + if (a_delta != b_delta) { + return a_delta > b_delta; + } + if (a.fingerprint.vertex_count != b.fingerprint.vertex_count) { + return a.fingerprint.vertex_count > b.fingerprint.vertex_count; + } + return graphics::ultrawide_debug::HashDrawFingerprint(a.fingerprint) < + graphics::ultrawide_debug::HashDrawFingerprint(b.fingerprint); + }); + return diff; +} + +std::vector BuildDrawGroupDiff(DrawGroupMode mode) { + std::unordered_map rows; + for (const auto& entry : g_visible_draw_capture) { + DrawGroupKey key = MakeDrawGroupKey(entry.fingerprint, mode); + auto [it, inserted] = rows.try_emplace(key); + DrawGroupDiffEntry& row = it->second; + if (inserted) { + row.key = key; + row.representative = entry.fingerprint; + row.group_hash = HashDrawGroupKey(key); + } + row.visible_count += entry.draw_count; + } + for (const auto& entry : g_popped_draw_capture) { + DrawGroupKey key = MakeDrawGroupKey(entry.fingerprint, mode); + auto [it, inserted] = rows.try_emplace(key); + DrawGroupDiffEntry& row = it->second; + if (inserted) { + row.key = key; + row.representative = entry.fingerprint; + row.group_hash = HashDrawGroupKey(key); + } + row.popped_count += entry.draw_count; + } + + std::vector diff; + for (const auto& [_, row] : rows) { + if (row.visible_count > row.popped_count) { + diff.push_back(row); + } + } + std::sort(diff.begin(), diff.end(), [](const DrawGroupDiffEntry& a, const DrawGroupDiffEntry& b) { + const uint64_t a_delta = a.visible_count - a.popped_count; + const uint64_t b_delta = b.visible_count - b.popped_count; + if (a_delta != b_delta) { + return a_delta > b_delta; + } + return a.group_hash < b.group_hash; + }); + return diff; +} + +} // namespace + +UltrawideTargetsDialog::UltrawideTargetsDialog(ImGuiDrawer* drawer, + std::filesystem::path export_path, + CloseCallback close_callback) + : ImGuiDialog(drawer), + export_path_(std::move(export_path)), + close_callback_(std::move(close_callback)) { + graphics::ultrawide_debug::SetGuestStoreWatchLogPath( + export_path_.parent_path() / "skate3_ultrawide_watch.log"); + graphics::ultrawide_debug::SetTargetRecordingActive(false); + SetDrawActive(false); +} + +UltrawideTargetsDialog::~UltrawideTargetsDialog() = default; + +void UltrawideTargetsDialog::Show() { + visible_ = true; + graphics::ultrawide_debug::SetTargetRecordingActive(true); + SetDrawActive(true); +} + +void UltrawideTargetsDialog::Toggle() { + if (visible_) { + Hide(); + } else { + Show(); + } +} + +void UltrawideTargetsDialog::Hide() { + if (!visible_) { + return; + } + visible_ = false; + graphics::ultrawide_debug::SetTargetRecordingActive(false); + SetDrawActive(false); + if (close_callback_) { + close_callback_(); + } +} + +void UltrawideTargetsDialog::OnDraw(ImGuiIO& io) { + (void)io; + if (!visible_) { + return; + } + + bool open = visible_; + ImGui::SetNextWindowSize(ImVec2(1160.0f, 620.0f), ImGuiCond_FirstUseEver); + if (!ImGui::Begin("Ultrawide Render Targets", &open, ImGuiWindowFlags_NoCollapse)) { + ImGui::End(); + if (!open) { + Hide(); + } + return; + } + + auto targets = graphics::ultrawide_debug::SnapshotTargets(); + auto screen_callbacks = graphics::ultrawide_debug::SnapshotScreenCallbacks(); + auto guest_store_watch_events = graphics::ultrawide_debug::SnapshotGuestStoreWatchEvents(); + std::sort(targets.begin(), targets.end(), + [](const graphics::ultrawide_debug::TargetEntry& a, + const graphics::ultrawide_debug::TargetEntry& b) { + if (a.default_enabled != b.default_enabled) { + return a.default_enabled > b.default_enabled; + } + if (a.key.color_info[0] != b.key.color_info[0]) { + return a.key.color_info[0] < b.key.color_info[0]; + } + if (a.key.depth_info != b.key.depth_info) { + return a.key.depth_info < b.key.depth_info; + } + if (a.key.viewport_width != b.key.viewport_width) { + return a.key.viewport_width > b.key.viewport_width; + } + if (a.key.viewport_height != b.key.viewport_height) { + return a.key.viewport_height > b.key.viewport_height; + } + if (a.key.vertex_shader_hash != b.key.vertex_shader_hash) { + return a.key.vertex_shader_hash < b.key.vertex_shader_hash; + } + return a.hash < b.hash; + }); + std::sort(screen_callbacks.begin(), screen_callbacks.end(), + [](const graphics::ultrawide_debug::ScreenCallbackEntry& a, + const graphics::ultrawide_debug::ScreenCallbackEntry& b) { + if (a.kind != b.kind) { + return uint32_t(a.kind) < uint32_t(b.kind); + } + if (a.caller_lr != b.caller_lr) { + return a.caller_lr < b.caller_lr; + } + return a.hash < b.hash; + }); + if (ImGui::Button("Enable all")) { + graphics::ultrawide_debug::SetAllTargetsEnabled(true); + } + ImGui::SameLine(); + if (ImGui::Button("Disable all")) { + graphics::ultrawide_debug::SetAllTargetsEnabled(false); + } + ImGui::SameLine(); + if (ImGui::Button("Reset defaults")) { + graphics::ultrawide_debug::ResetTargetOverrides(); + } + ImGui::SameLine(); + if (ImGui::Button("Save config")) { + std::error_code ec; + std::filesystem::create_directories(export_path_.parent_path(), ec); + const auto saved = graphics::ultrawide_debug::SaveTargets(export_path_); + if (saved) { + REXLOG_INFO("Saved ultrawide render target config to {}", saved->string()); + } else { + REXLOG_WARN("Failed to save ultrawide render target config to {}", export_path_.string()); + } + } + ImGui::SameLine(); + ImGui::Checkbox("Seen this session", &show_only_seen_); + + bool skip_shadow_targets = rex::cvar::Query("skate3_ultrawide_skip_shadow_targets"); + ImGui::SameLine(); + if (ImGui::Checkbox("Skip shadow maps", &skip_shadow_targets)) { + rex::cvar::SetFlagByName("skate3_ultrawide_skip_shadow_targets", + skip_shadow_targets ? "true" : "false"); + } + int shadow_skip_mode = std::clamp(rex::cvar::Query("skate3_ultrawide_shadow_skip_mode"), + 1, 3) - + 1; + const char* shadow_mode_labels[] = {"Atlas", "Atlas+large", "Depth-only"}; + ImGui::SameLine(); + ImGui::SetNextItemWidth(110.0f); + if (ImGui::Combo("Shadow mode", &shadow_skip_mode, shadow_mode_labels, + IM_ARRAYSIZE(shadow_mode_labels))) { + rex::cvar::SetFlagByName("skate3_ultrawide_shadow_skip_mode", + std::to_string(shadow_skip_mode + 1)); + } + + int apply_mode_index = static_cast(graphics::ultrawide_debug::GetApplyMode()); + const char* apply_mode_labels[] = {"Target list", "Force all", "Force none", "Saved only"}; + ImGui::SameLine(); + ImGui::SetNextItemWidth(130.0f); + if (ImGui::Combo("Mode", &apply_mode_index, apply_mode_labels, + IM_ARRAYSIZE(apply_mode_labels))) { + graphics::ultrawide_debug::SetApplyMode( + static_cast(apply_mode_index)); + } + + bool fake_occlusion_queries = + rex::cvar::Query("skate3_ultrawide_fake_occlusion_queries"); + if (ImGui::Checkbox("Fake visibility queries", &fake_occlusion_queries)) { + rex::cvar::SetFlagByName("skate3_ultrawide_fake_occlusion_queries", + fake_occlusion_queries ? "true" : "false"); + } + ImGui::SameLine(); + int fake_occlusion_sample_count = std::clamp( + rex::cvar::Query("skate3_ultrawide_fake_occlusion_sample_count"), 1, 1000000); + ImGui::SetNextItemWidth(110.0f); + if (ImGui::InputInt("Visibility samples", &fake_occlusion_sample_count, 1000, 10000)) { + fake_occlusion_sample_count = std::clamp(fake_occlusion_sample_count, 1, 1000000); + rex::cvar::SetFlagByName("skate3_ultrawide_fake_occlusion_sample_count", + std::to_string(fake_occlusion_sample_count)); + } + + bool widen_game_frustum = + rex::cvar::Query("skate3_ultrawide_widen_game_frustum"); + if (ImGui::Checkbox("Widen game frustum", &widen_game_frustum)) { + rex::cvar::SetFlagByName("skate3_ultrawide_widen_game_frustum", + widen_game_frustum ? "true" : "false"); + } + ImGui::SameLine(); + bool force_main_visibility_flags = + rex::cvar::Query("skate3_ultrawide_force_main_visibility_flags"); + if (ImGui::Checkbox("Force main visibility", &force_main_visibility_flags)) { + rex::cvar::SetFlagByName("skate3_ultrawide_force_main_visibility_flags", + force_main_visibility_flags ? "true" : "false"); + } + ImGui::SameLine(); + bool disable_ndc_correction = + rex::cvar::Query("skate3_ultrawide_disable_ndc_correction"); + if (ImGui::Checkbox("Disable NDC correction", &disable_ndc_correction)) { + rex::cvar::SetFlagByName("skate3_ultrawide_disable_ndc_correction", + disable_ndc_correction ? "true" : "false"); + } + ImGui::SameLine(); + if (ImGui::Button("Trace 1k")) { + rex::cvar::SetFlagByName("skate3_ultrawide_texture_trace_remaining", "1000"); + } + ImGui::SameLine(); + if (ImGui::Button("Trace 10k")) { + rex::cvar::SetFlagByName("skate3_ultrawide_texture_trace_remaining", "10000"); + } + ImGui::SameLine(); + if (ImGui::Button("Stop trace")) { + rex::cvar::SetFlagByName("skate3_ultrawide_texture_trace_remaining", "0"); + } + ImGui::SameLine(); + int texture_trace_remaining = + std::clamp(rex::cvar::Query("skate3_ultrawide_texture_trace_remaining"), 0, + 1000000); + ImGui::SetNextItemWidth(105.0f); + if (ImGui::InputInt("Trace left", &texture_trace_remaining, 100, 1000)) { + texture_trace_remaining = std::clamp(texture_trace_remaining, 0, 1000000); + rex::cvar::SetFlagByName("skate3_ultrawide_texture_trace_remaining", + std::to_string(texture_trace_remaining)); + } + + bool texture_trace_bind_keys = + rex::cvar::Query("skate3_ultrawide_texture_trace_bind_keys"); + if (ImGui::Checkbox("Trace binds", &texture_trace_bind_keys)) { + rex::cvar::SetFlagByName("skate3_ultrawide_texture_trace_bind_keys", + texture_trace_bind_keys ? "true" : "false"); + } + ImGui::SameLine(); + bool texture_trace_scaled_resolve = + rex::cvar::Query("skate3_ultrawide_texture_trace_scaled_resolve"); + if (ImGui::Checkbox("Trace scaled", &texture_trace_scaled_resolve)) { + rex::cvar::SetFlagByName("skate3_ultrawide_texture_trace_scaled_resolve", + texture_trace_scaled_resolve ? "true" : "false"); + } + bool ignore_streamer_texture_invalidations = + rex::cvar::Query("skate3_ultrawide_ignore_streamer_texture_invalidations"); + if (ImGui::Checkbox("Pin streamer textures", &ignore_streamer_texture_invalidations)) { + rex::cvar::SetFlagByName("skate3_ultrawide_ignore_streamer_texture_invalidations", + ignore_streamer_texture_invalidations ? "true" : "false"); + } + auto draw_fingerprints = graphics::ultrawide_debug::SnapshotDrawFingerprints(); + std::sort(draw_fingerprints.begin(), draw_fingerprints.end(), + [](const graphics::ultrawide_debug::DrawFingerprintEntry& a, + const graphics::ultrawide_debug::DrawFingerprintEntry& b) { + if (a.fingerprint.bucket != b.fingerprint.bucket) { + return uint32_t(a.fingerprint.bucket) < uint32_t(b.fingerprint.bucket); + } + if (a.fingerprint.packet_ptr != b.fingerprint.packet_ptr) { + return a.fingerprint.packet_ptr < b.fingerprint.packet_ptr; + } + if (a.fingerprint.index_guest_base != b.fingerprint.index_guest_base) { + return a.fingerprint.index_guest_base < b.fingerprint.index_guest_base; + } + return graphics::ultrawide_debug::HashDrawFingerprint(a.fingerprint) < + graphics::ultrawide_debug::HashDrawFingerprint(b.fingerprint); + }); + draw_group_mode_ = std::clamp(draw_group_mode_, 0, 2); + const auto draw_group_mode = static_cast(draw_group_mode_); + auto draw_groups = BuildDrawGroups(draw_fingerprints, draw_group_mode); + + ImGui::Separator(); + bool trace_draws = rex::cvar::Query("skate3_ultrawide_trace_draws"); + if (ImGui::Checkbox("Trace draws", &trace_draws)) { + rex::cvar::SetFlagByName("skate3_ultrawide_trace_draws", trace_draws ? "true" : "false"); + graphics::ultrawide_debug::ClearDrawFingerprints(); + g_visible_draw_capture.clear(); + g_popped_draw_capture.clear(); + } + bool object_stage_trace_continuous = + rex::cvar::Query("skate3_ultrawide_object_stage_trace_continuous"); + if (ImGui::Checkbox("Object stages", &object_stage_trace_continuous)) { + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_trace_continuous", + object_stage_trace_continuous ? "true" : "false"); + } + ImGui::SameLine(); + if (ImGui::Button("Obj trace 300")) { + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_trace_frames_remaining", "300"); + } + ImGui::SameLine(); + if (ImGui::Button("Mark visible")) { + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_marker", "1"); + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_trace_frames_remaining", "60"); + } + ImGui::SameLine(); + if (ImGui::Button("Mark invisible")) { + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_marker", "2"); + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_trace_frames_remaining", "60"); + } + ImGui::SameLine(); + int object_stage_trace_frames = std::clamp( + rex::cvar::Query("skate3_ultrawide_object_stage_trace_frames_remaining"), 0, + 1000000); + ImGui::SetNextItemWidth(88.0f); + if (ImGui::InputInt("Obj left", &object_stage_trace_frames, 30, 300)) { + object_stage_trace_frames = std::clamp(object_stage_trace_frames, 0, 1000000); + rex::cvar::SetFlagByName("skate3_ultrawide_object_stage_trace_frames_remaining", + std::to_string(object_stage_trace_frames)); + } + ImGui::SameLine(); + ImGui::Text("Draw groups: %zu Exact: %zu", draw_groups.size(), draw_fingerprints.size()); + ImGui::SameLine(); + const char* draw_group_mode_labels[] = {"Shader", "Object", "Texture"}; + ImGui::SetNextItemWidth(92.0f); + if (ImGui::Combo("Group", &draw_group_mode_, draw_group_mode_labels, + IM_ARRAYSIZE(draw_group_mode_labels))) { + draw_group_mode_ = std::clamp(draw_group_mode_, 0, 2); + } + ImGui::SameLine(); + ImGui::Checkbox("Expand draw section", &draw_section_expanded_); + ImGui::SameLine(); + ImGui::BeginDisabled(!draw_section_expanded_); + ImGui::Checkbox("Exact rows", &draw_show_exact_fingerprints_); + ImGui::EndDisabled(); + ImGui::SameLine(); + if (ImGui::Button("Capture visible")) { + g_visible_draw_capture = draw_fingerprints; + graphics::ultrawide_debug::ClearDrawFingerprints(); + } + ImGui::SameLine(); + if (ImGui::Button("Capture popped")) { + g_popped_draw_capture = draw_fingerprints; + } + ImGui::SameLine(); + if (ImGui::Button("Clear draw captures")) { + g_visible_draw_capture.clear(); + g_popped_draw_capture.clear(); + graphics::ultrawide_debug::ClearDrawFingerprints(); + } + ImGui::SameLine(); + if (ImGui::Button("Show all draws")) { + graphics::ultrawide_debug::SetAllDrawFingerprintsEnabled(true); + } + ImGui::SameLine(); + if (ImGui::Button("Hide all draws")) { + graphics::ultrawide_debug::SetAllDrawFingerprintsEnabled(false); + } + ImGui::SameLine(); + if (ImGui::Button("Reset draw visibility")) { + graphics::ultrawide_debug::ResetDrawFingerprintOverrides(); + } + + if (g_watched_draw_group) { + const DrawGroupEntry* watched_group = nullptr; + for (const auto& group : draw_groups) { + if (group.key == *g_watched_draw_group) { + watched_group = &group; + break; + } + } + + ImGui::Text("Watch: %s", Hex(g_watched_draw_group_hash, 16).c_str()); + ImGui::SameLine(); + if (ImGui::Button("Clear watch")) { + g_watched_draw_group.reset(); + g_watched_draw_group_hash = 0; + g_draw_watch_events.clear(); + g_draw_watch_state_valid = false; + graphics::ultrawide_debug::ClearGuestStoreWatchTargets(); + } + ImGui::SameLine(); + ImGui::SetNextItemWidth(96.0f); + if (ImGui::InputInt("Ms threshold", &g_draw_watch_age_threshold, 10, 50)) { + g_draw_watch_age_threshold = std::clamp(g_draw_watch_age_threshold, 1, 10000); + g_draw_watch_state_valid = false; + } + ImGui::SameLine(); + if (watched_group) { + const uint64_t now_ms = NowMilliseconds(); + const uint64_t age = watched_group->last_seen_ms == 0 + ? UINT64_MAX + : (now_ms > watched_group->last_seen_ms + ? now_ms - watched_group->last_seen_ms + : 0); + const uint64_t textured_age = + watched_group->last_textured_seen_ms == 0 + ? UINT64_MAX + : (now_ms > watched_group->last_textured_seen_ms + ? now_ms - watched_group->last_textured_seen_ms + : 0); + const uint64_t main_textured_age = + watched_group->last_main_textured_seen_ms == 0 + ? UINT64_MAX + : (now_ms > watched_group->last_main_textured_seen_ms + ? now_ms - watched_group->last_main_textured_seen_ms + : 0); + const auto& watch_fingerprint = + watched_group->last_main_textured_seen_order != 0 + ? watched_group->main_textured_representative + : (watched_group->last_textured_seen_order != 0 + ? watched_group->textured_representative + : watched_group->representative); + const bool watch_present = main_textured_age <= uint64_t(g_draw_watch_age_threshold); + if (!g_draw_watch_state_valid || g_draw_watch_present != watch_present) { + DrawWatchEvent event; + event.sequence = ++g_draw_watch_event_sequence; + event.present = watch_present; + event.age = main_textured_age; + event.packet_ptr = watch_fingerprint.packet_ptr; + event.index_guest_base = watch_fingerprint.index_guest_base; + event.vertex_fetch_address = watch_fingerprint.vertex_fetch_address[0]; + event.group_hash = g_watched_draw_group_hash; + g_draw_watch_events.push_front(event); + while (g_draw_watch_events.size() > 24) { + g_draw_watch_events.pop_back(); + } + graphics::ultrawide_debug::RecordDrawWatchTransition( + event.present, event.age, event.packet_ptr, event.index_guest_base, + event.vertex_fetch_address, event.group_hash); + g_draw_watch_state_valid = true; + g_draw_watch_present = watch_present; + } + ImGui::Text("calls=%llu textured=%llu main_tex=%llu exact=%u shown=%llu hidden=%llu", + static_cast(watched_group->draw_count), + static_cast(watched_group->textured_draw_count), + static_cast(watched_group->main_textured_draw_count), + watched_group->exact_count, + static_cast(watched_group->submitted_count), + static_cast(watched_group->skipped_count)); + ImGui::Text("any_ms=%s tex_ms=%s main_tex_ms=%s pkt=%s", + age == UINT64_MAX + ? "n/a" + : std::to_string(age).c_str(), + textured_age == UINT64_MAX + ? "n/a" + : std::to_string(textured_age).c_str(), + main_textured_age == UINT64_MAX + ? "n/a" + : std::to_string(main_textured_age).c_str(), + Hex(watch_fingerprint.packet_ptr, 8).c_str()); + ImGui::Text("Watch object: %s", DrawObjectLabel(watch_fingerprint).c_str()); + ImGui::SameLine(); + ImGui::Text("texture: %s", DrawTextureLabel(watch_fingerprint).c_str()); + } else { + ImGui::TextUnformatted("not present in current group mode"); + if (g_draw_watch_state_valid && g_draw_watch_present) { + DrawWatchEvent event; + event.sequence = ++g_draw_watch_event_sequence; + event.present = false; + event.age = UINT64_MAX; + event.group_hash = g_watched_draw_group_hash; + g_draw_watch_events.push_front(event); + while (g_draw_watch_events.size() > 24) { + g_draw_watch_events.pop_back(); + } + graphics::ultrawide_debug::RecordDrawWatchTransition( + event.present, event.age, event.packet_ptr, event.index_guest_base, + event.vertex_fetch_address, event.group_hash); + g_draw_watch_present = false; + } + } + if (!g_draw_watch_events.empty() && + ImGui::BeginTable("draw_watch_events", 7, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit, + ImVec2(0.0f, 92.0f))) { + ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 42.0f); + ImGui::TableSetupColumn("State", ImGuiTableColumnFlags_WidthFixed, 64.0f); + ImGui::TableSetupColumn("Age", ImGuiTableColumnFlags_WidthFixed, 70.0f); + ImGui::TableSetupColumn("Pkt", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("IB", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("VF0", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Group", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableHeadersRow(); + for (const DrawWatchEvent& event : g_draw_watch_events) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(event.sequence)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.present ? "present" : "missing"); + ImGui::TableNextColumn(); + if (event.age == UINT64_MAX) { + ImGui::TextUnformatted("n/a"); + } else { + ImGui::Text("%llu", static_cast(event.age)); + } + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.packet_ptr ? Hex(event.packet_ptr, 8).c_str() : ""); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.index_guest_base ? Hex(event.index_guest_base, 8).c_str() + : ""); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.vertex_fetch_address + ? Hex(event.vertex_fetch_address, 8).c_str() + : ""); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Hex(event.group_hash, 16).c_str()); + } + ImGui::EndTable(); + } + const auto watch_log_path = export_path_.parent_path() / "skate3_ultrawide_watch.log"; + ImGui::Text("Watch log: %s", watch_log_path.string().c_str()); + if (!guest_store_watch_events.empty()) { + ImGui::Text("Guest stores: %zu", guest_store_watch_events.size()); + ImGui::SameLine(); + if (ImGui::Button("Clear store hits")) { + graphics::ultrawide_debug::ClearGuestStoreWatchEvents(); + guest_store_watch_events.clear(); + } + if (ImGui::BeginTable("guest_store_watch_events", 8, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_SizingFixedFit, + ImVec2(0.0f, 130.0f))) { + ImGui::TableSetupColumn("#", ImGuiTableColumnFlags_WidthFixed, 42.0f); + ImGui::TableSetupColumn("Target", ImGuiTableColumnFlags_WidthFixed, 70.0f); + ImGui::TableSetupColumn("Addr", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Value", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("LR", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Function", ImGuiTableColumnFlags_WidthFixed, 170.0f); + ImGui::TableSetupColumn("Stack", ImGuiTableColumnFlags_WidthFixed, 260.0f); + ImGui::TableSetupColumn("x", ImGuiTableColumnFlags_WidthFixed, 48.0f); + ImGui::TableHeadersRow(); + for (auto it = guest_store_watch_events.rbegin(); + it != guest_store_watch_events.rend(); ++it) { + const auto& event = *it; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(event.sequence)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.target.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Hex(event.address, 8).c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(Hex(event.value, 8).c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.caller_lr ? Hex(event.caller_lr, 8).c_str() : ""); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.function.c_str()); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(event.stack.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(event.count)); + } + ImGui::EndTable(); + } + } + } + + const float draw_diff_height = draw_section_expanded_ ? 180.0f : 86.0f; + const float draw_live_height = draw_section_expanded_ ? 260.0f : 120.0f; + const auto draw_diff = BuildDrawDiff(); + const auto draw_group_diff = BuildDrawGroupDiff(draw_group_mode); + ImGui::Text("Visible capture: %zu Popped capture: %zu Missing groups: %zu Exact missing: %zu", + g_visible_draw_capture.size(), g_popped_draw_capture.size(), draw_group_diff.size(), + draw_diff.size()); + if (ImGui::BeginTable("draw_group_diff", 13, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_SizingFixedFit, + ImVec2(0.0f, draw_diff_height))) { + ImGui::TableSetupColumn("Draw", ImGuiTableColumnFlags_WidthFixed, 48.0f); + ImGui::TableSetupColumn("Watch", ImGuiTableColumnFlags_WidthFixed, 52.0f); + ImGui::TableSetupColumn("Delta", ImGuiTableColumnFlags_WidthFixed, 62.0f); + ImGui::TableSetupColumn("Vis", ImGuiTableColumnFlags_WidthFixed, 48.0f); + ImGui::TableSetupColumn("Pop", ImGuiTableColumnFlags_WidthFixed, 48.0f); + ImGui::TableSetupColumn("Bucket", ImGuiTableColumnFlags_WidthFixed, 78.0f); + ImGui::TableSetupColumn("Exact", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Object", ImGuiTableColumnFlags_WidthFixed, 190.0f); + ImGui::TableSetupColumn("Texture", ImGuiTableColumnFlags_WidthFixed, 250.0f); + ImGui::TableSetupColumn("VS", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("PS", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("Prim", ImGuiTableColumnFlags_WidthFixed, 54.0f); + ImGui::TableSetupColumn("Group", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableHeadersRow(); + for (const auto& row : draw_group_diff) { + const auto& fp = row.representative; + const uint64_t group_hash = row.group_hash; + const DrawGroupEntry* live_group = nullptr; + for (const auto& group : draw_groups) { + if (group.group_hash == group_hash) { + live_group = &group; + break; + } + } + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool draw_enabled = !live_group || live_group->enabled_count > 0; + std::string draw_id = "##draw_group_diff_" + Hex(group_hash, 16); + if (ImGui::Checkbox(draw_id.c_str(), &draw_enabled)) { + if (live_group) { + for (uint64_t exact_hash : live_group->exact_hashes) { + graphics::ultrawide_debug::SetDrawFingerprintEnabled(exact_hash, draw_enabled); + } + } + } + ImGui::TableNextColumn(); + std::string watch_id = "W##draw_group_diff_watch_" + Hex(group_hash, 16); + if (ImGui::SmallButton(watch_id.c_str())) { + SetWatchedDrawGroup(row.key, group_hash, BestWatchFingerprint(live_group, fp)); + } + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(row.visible_count - row.popped_count)); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(row.visible_count)); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(row.popped_count)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(DrawBucketLabel(fp.bucket)); + ImGui::TableNextColumn(); + ImGui::Text("%u", live_group ? live_group->exact_count : 0); + ImGui::TableNextColumn(); + const std::string object = DrawObjectLabel(fp); + ImGui::TextUnformatted(object.c_str()); + ImGui::TableNextColumn(); + const auto& texture_fp = + live_group && live_group->last_main_textured_seen_order != 0 + ? live_group->main_textured_representative + : (live_group && live_group->last_textured_seen_order != 0 + ? live_group->textured_representative + : fp); + const std::string texture = DrawTextureLabel(texture_fp); + ImGui::TextUnformatted(texture.c_str()); + ImGui::TableNextColumn(); + const std::string vs = Hex(fp.vertex_shader_hash, 16); + ImGui::TextUnformatted(vs.c_str()); + ImGui::TableNextColumn(); + const std::string ps = Hex(fp.pixel_shader_hash, 16); + ImGui::TextUnformatted(ps.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%u", fp.primitive_type); + ImGui::TableNextColumn(); + const std::string hash = Hex(group_hash, 16); + ImGui::TextUnformatted(hash.c_str()); + } + ImGui::EndTable(); + } + + if (ImGui::BeginTable("draw_groups", 16, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_SizingFixedFit, + ImVec2(0.0f, draw_live_height))) { + ImGui::TableSetupColumn("Draw", ImGuiTableColumnFlags_WidthFixed, 48.0f); + ImGui::TableSetupColumn("Watch", ImGuiTableColumnFlags_WidthFixed, 52.0f); + ImGui::TableSetupColumn("Calls", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Shown", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Hidden", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Bucket", ImGuiTableColumnFlags_WidthFixed, 78.0f); + ImGui::TableSetupColumn("Exact", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Verts", ImGuiTableColumnFlags_WidthFixed, 92.0f); + ImGui::TableSetupColumn("Prims", ImGuiTableColumnFlags_WidthFixed, 92.0f); + ImGui::TableSetupColumn("Object", ImGuiTableColumnFlags_WidthFixed, 190.0f); + ImGui::TableSetupColumn("Texture", ImGuiTableColumnFlags_WidthFixed, 250.0f); + ImGui::TableSetupColumn("VS", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("PS", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("Prim", ImGuiTableColumnFlags_WidthFixed, 54.0f); + ImGui::TableSetupColumn("Enabled", ImGuiTableColumnFlags_WidthFixed, 70.0f); + ImGui::TableSetupColumn("Group", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableHeadersRow(); + for (const auto& group : draw_groups) { + const auto& fp = group.representative; + const uint64_t group_hash = group.group_hash; + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool draw_enabled = group.enabled_count > 0; + std::string draw_id = "##draw_group_live_" + Hex(group_hash, 16); + if (ImGui::Checkbox(draw_id.c_str(), &draw_enabled)) { + for (uint64_t exact_hash : group.exact_hashes) { + graphics::ultrawide_debug::SetDrawFingerprintEnabled(exact_hash, draw_enabled); + } + } + ImGui::TableNextColumn(); + std::string watch_id = "W##draw_group_live_watch_" + Hex(group_hash, 16); + if (ImGui::SmallButton(watch_id.c_str())) { + SetWatchedDrawGroup(group.key, group_hash, BestWatchFingerprint(&group, fp)); + } + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(group.draw_count)); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(group.submitted_count)); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(group.skipped_count)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(DrawBucketLabel(fp.bucket)); + ImGui::TableNextColumn(); + ImGui::Text("%u", group.exact_count); + ImGui::TableNextColumn(); + if (group.min_vertex_count == group.max_vertex_count) { + ImGui::Text("%u", group.min_vertex_count); + } else { + ImGui::Text("%u-%u", group.min_vertex_count, group.max_vertex_count); + } + ImGui::TableNextColumn(); + if (group.min_primitive_count == group.max_primitive_count) { + ImGui::Text("%u", group.min_primitive_count); + } else { + ImGui::Text("%u-%u", group.min_primitive_count, group.max_primitive_count); + } + ImGui::TableNextColumn(); + const std::string object = DrawObjectLabel(fp); + ImGui::TextUnformatted(object.c_str()); + ImGui::TableNextColumn(); + const auto& texture_fp = group.last_main_textured_seen_order != 0 + ? group.main_textured_representative + : (group.last_textured_seen_order != 0 + ? group.textured_representative + : fp); + const std::string texture = DrawTextureLabel(texture_fp); + ImGui::TextUnformatted(texture.c_str()); + ImGui::TableNextColumn(); + const std::string vs = Hex(fp.vertex_shader_hash, 16); + ImGui::TextUnformatted(vs.c_str()); + ImGui::TableNextColumn(); + const std::string ps = Hex(fp.pixel_shader_hash, 16); + ImGui::TextUnformatted(ps.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%u", fp.primitive_type); + ImGui::TableNextColumn(); + ImGui::Text("%u/%u", group.enabled_count, group.exact_count); + ImGui::TableNextColumn(); + const std::string hash = Hex(group_hash, 16); + ImGui::TextUnformatted(hash.c_str()); + } + ImGui::EndTable(); + } + + if (draw_section_expanded_ && draw_show_exact_fingerprints_ && + ImGui::BeginTable("draw_fingerprints", 18, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | ImGuiTableFlags_ScrollY | + ImGuiTableFlags_SizingFixedFit, + ImVec2(0.0f, 180.0f))) { + ImGui::TableSetupColumn("Draw", ImGuiTableColumnFlags_WidthFixed, 48.0f); + ImGui::TableSetupColumn("Calls", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Shown", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Hidden", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Bucket", ImGuiTableColumnFlags_WidthFixed, 78.0f); + ImGui::TableSetupColumn("Obj", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("VT", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Target", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Verts", ImGuiTableColumnFlags_WidthFixed, 62.0f); + ImGui::TableSetupColumn("Prims", ImGuiTableColumnFlags_WidthFixed, 62.0f); + ImGui::TableSetupColumn("VS", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("PS", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("Pkt", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("IB", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("IB Len", ImGuiTableColumnFlags_WidthFixed, 62.0f); + ImGui::TableSetupColumn("VF0", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("VF1", ImGuiTableColumnFlags_WidthFixed, 90.0f); + ImGui::TableSetupColumn("Hash", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableHeadersRow(); + for (const auto& entry : draw_fingerprints) { + const auto& fp = entry.fingerprint; + const uint64_t fp_hash = entry.hash ? entry.hash : graphics::ultrawide_debug::HashDrawFingerprint(fp); + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool draw_enabled = entry.enabled; + std::string draw_id = "##draw_live_" + Hex(fp_hash, 16); + if (ImGui::Checkbox(draw_id.c_str(), &draw_enabled)) { + graphics::ultrawide_debug::SetDrawFingerprintEnabled(fp_hash, draw_enabled); + } + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(entry.draw_count)); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(entry.submitted_count)); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(entry.skipped_count)); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(DrawBucketLabel(fp.bucket)); + ImGui::TableNextColumn(); + const std::string owner = fp.guest_render_object ? Hex(fp.guest_render_object, 8) : ""; + ImGui::TextUnformatted(owner.c_str()); + ImGui::TableNextColumn(); + const std::string owner_vtable = fp.guest_render_vtable ? Hex(fp.guest_render_vtable, 8) : ""; + ImGui::TextUnformatted(owner_vtable.c_str()); + ImGui::TableNextColumn(); + const std::string owner_target = fp.guest_render_target ? Hex(fp.guest_render_target, 8) : ""; + ImGui::TextUnformatted(owner_target.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%u", fp.vertex_count); + ImGui::TableNextColumn(); + ImGui::Text("%u", fp.primitive_count); + ImGui::TableNextColumn(); + const std::string vs = Hex(fp.vertex_shader_hash, 16); + ImGui::TextUnformatted(vs.c_str()); + ImGui::TableNextColumn(); + const std::string ps = Hex(fp.pixel_shader_hash, 16); + ImGui::TextUnformatted(ps.c_str()); + ImGui::TableNextColumn(); + const std::string pkt = fp.packet_ptr ? Hex(fp.packet_ptr, 8) : ""; + ImGui::TextUnformatted(pkt.c_str()); + ImGui::TableNextColumn(); + const std::string ib = fp.index_guest_base ? Hex(fp.index_guest_base, 8) : ""; + ImGui::TextUnformatted(ib.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%u", fp.index_length); + ImGui::TableNextColumn(); + const std::string vf0 = fp.vertex_fetch_address[0] ? Hex(fp.vertex_fetch_address[0], 8) : ""; + ImGui::TextUnformatted(vf0.c_str()); + ImGui::TableNextColumn(); + const std::string vf1 = fp.vertex_fetch_address[1] ? Hex(fp.vertex_fetch_address[1], 8) : ""; + ImGui::TextUnformatted(vf1.c_str()); + ImGui::TableNextColumn(); + const std::string hash = Hex(fp_hash, 16); + ImGui::TextUnformatted(hash.c_str()); + } + ImGui::EndTable(); + } + + ImGui::Separator(); + ImGui::Text("Targets: %zu", targets.size()); + ImGui::SameLine(); + ImGui::Text("Screen read sites: %zu", screen_callbacks.size()); + ImGui::SameLine(); + ImGui::Text("Target aspect: %.4f", + rex::cvar::Query("skate3_ultrawide_target_aspect")); + ImGui::SameLine(); + ImGui::Text("Export: %s", export_path_.string().c_str()); + + if (ImGui::Button("Enable screen reads")) { + graphics::ultrawide_debug::SetAllScreenCallbacksEnabled(true); + } + ImGui::SameLine(); + if (ImGui::Button("Disable screen reads")) { + graphics::ultrawide_debug::SetAllScreenCallbacksEnabled(false); + } + + if (ImGui::BeginTable("screen_callbacks", 8, + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | + ImGuiTableFlags_Resizable | ImGuiTableFlags_SizingFixedFit, + ImVec2(0.0f, 130.0f))) { + ImGui::TableSetupColumn("Spoof", ImGuiTableColumnFlags_WidthFixed, 54.0f); + ImGui::TableSetupColumn("Kind", ImGuiTableColumnFlags_WidthFixed, 86.0f); + ImGui::TableSetupColumn("Site", ImGuiTableColumnFlags_WidthFixed, 94.0f); + ImGui::TableSetupColumn("Native", ImGuiTableColumnFlags_WidthFixed, 70.0f); + ImGui::TableSetupColumn("Returned", ImGuiTableColumnFlags_WidthFixed, 78.0f); + ImGui::TableSetupColumn("Spoofed", ImGuiTableColumnFlags_WidthFixed, 62.0f); + ImGui::TableSetupColumn("Calls", ImGuiTableColumnFlags_WidthFixed, 82.0f); + ImGui::TableSetupColumn("Hash", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableHeadersRow(); + for (const auto& entry : screen_callbacks) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool enabled = entry.enabled; + const std::string checkbox_id = "##screen_" + Hex(entry.hash, 16); + if (ImGui::Checkbox(checkbox_id.c_str(), &enabled)) { + graphics::ultrawide_debug::SetScreenCallbackEnabled(entry.hash, enabled); + } + ImGui::TableNextColumn(); + ImGui::TextUnformatted(ScreenCallbackKindLabel(entry.kind)); + ImGui::TableNextColumn(); + const std::string lr = Hex(entry.caller_lr, 8); + ImGui::TextUnformatted(lr.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%u", entry.last_value); + ImGui::TableNextColumn(); + ImGui::Text("%u", entry.last_returned_value); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(entry.last_spoofed ? "yes" : "no"); + ImGui::TableNextColumn(); + ImGui::Text("%llu", static_cast(entry.call_count)); + ImGui::TableNextColumn(); + const std::string hash = Hex(entry.hash, 16); + ImGui::TextUnformatted(hash.c_str()); + } + ImGui::EndTable(); + } + + const ImGuiTableFlags table_flags = + ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | + ImGuiTableFlags_ScrollY | ImGuiTableFlags_SizingFixedFit; + if (ImGui::BeginTable("ultrawide_targets", 13, table_flags, ImVec2(0.0f, 0.0f))) { + ImGui::TableSetupColumn("Apply", ImGuiTableColumnFlags_WidthFixed, 54.0f); + ImGui::TableSetupColumn("Default", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Shadow", ImGuiTableColumnFlags_WidthFixed, 58.0f); + ImGui::TableSetupColumn("Group", ImGuiTableColumnFlags_WidthFixed, 285.0f); + ImGui::TableSetupColumn("Hash", ImGuiTableColumnFlags_WidthFixed, 132.0f); + ImGui::TableSetupColumn("RT0"); + ImGui::TableSetupColumn("Depth"); + ImGui::TableSetupColumn("Surface"); + ImGui::TableSetupColumn("Viewport", ImGuiTableColumnFlags_WidthFixed, 118.0f); + ImGui::TableSetupColumn("DepthCtl"); + ImGui::TableSetupColumn("VTE"); + ImGui::TableSetupColumn("VS"); + ImGui::TableSetupColumn("Draws"); + ImGui::TableHeadersRow(); + + for (const auto& target : targets) { + ImGui::TableNextRow(); + ImGui::TableNextColumn(); + bool enabled = target.enabled; + const std::string checkbox_id = "##apply_" + Hex(target.hash, 16); + if (ImGui::Checkbox(checkbox_id.c_str(), &enabled)) { + graphics::ultrawide_debug::SetTargetEnabled(target.hash, enabled); + } + + ImGui::TableNextColumn(); + ImGui::TextUnformatted(target.default_enabled ? "yes" : "no"); + ImGui::TableNextColumn(); + ImGui::TextUnformatted(target.shadow_candidate ? "yes" : "no"); + ImGui::TableNextColumn(); + const std::string group = TargetGroupLabel(target); + ImGui::TextUnformatted(group.c_str()); + ImGui::TableNextColumn(); + const std::string hash = Hex(target.hash, 16); + ImGui::TextUnformatted(hash.c_str()); + ImGui::TableNextColumn(); + const std::string rt0 = Hex(target.key.color_info[0], 8); + ImGui::TextUnformatted(rt0.c_str()); + ImGui::TableNextColumn(); + const std::string depth = Hex(target.key.depth_info, 8); + ImGui::TextUnformatted(depth.c_str()); + ImGui::TableNextColumn(); + const std::string surface = Hex(target.key.surface_info, 8); + ImGui::TextUnformatted(surface.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%u,%u %ux%u", target.key.viewport_x, target.key.viewport_y, + target.key.viewport_width, target.key.viewport_height); + ImGui::TableNextColumn(); + const std::string depth_control = Hex(target.key.depth_control, 8); + ImGui::TextUnformatted(depth_control.c_str()); + ImGui::TableNextColumn(); + const std::string vte = Hex(target.key.pa_cl_vte_cntl, 8); + ImGui::TextUnformatted(vte.c_str()); + ImGui::TableNextColumn(); + const std::string vs = Hex(target.key.vertex_shader_hash, 16); + ImGui::TextUnformatted(vs.c_str()); + ImGui::TableNextColumn(); + ImGui::Text("%llu / %llu", static_cast(target.applied_draw_count), + static_cast(target.draw_count)); + } + ImGui::EndTable(); + } + + ImGui::End(); + if (!open) { + Hide(); + } +} + +} // namespace rex::ui