// Copyright 2026 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later #pragma once #include #include #include #include #include #include #include #include #include #include #include #include "Common/CommonTypes.h" // 3DMigoto-lite shader hunting: cycle through shaders used per frame and skip selected ones. // Thread-safe: video thread calls Register/ShouldSkip/OnFrameEnd, UI thread calls Next/Prev/Get*. class ShaderHunter { public: enum class ShaderType { Pixel = 0, Vertex = 1, Geometry = 2, Count = 3 }; enum class HandlingType { Skip = 0, Screen = 1, Fullscreen = 2, FullscreenMono = 3, HeadLocked = 4, Flag = 5, UnitsPerMeter = 6, // VR passthrough window: the element still draws, but its pixels write the given // opacity into the dedicated coverage target. Passthrough = 7, // VR camera anchor: the element's view-space origin becomes the VR camera position // (e.g. a character's head for first-person view). Optionally hides the element itself. CameraAnchor = 8, // VR controller anchor: the element is repositioned to a VR controller's location // (e.g. a sword on the right hand). Translation only; orientation stays game-driven. ControllerAnchor = 9, // World-fixed virtual-screen routing for a perspective draw which occupies a deliberate // sub-region. Unlike Screen, this preserves both the pane's position and its model depth. ScreenPane = 10 }; // CameraAnchor handling: how much of the anchor element's orientation the camera follows. // YawOnly keeps the horizon level (comfort); Full includes pitch/roll (slopes, tricks). enum class AnchorRotationMode { Off = 0, YawOnly = 1, Full = 2 }; // CameraAnchor parameters resolved for a matching draw. Shared transport type: the Elements // Group, Shader and Texture Element override systems all resolve an anchor into this, and the // draw path applies it identically regardless of which system matched. struct CameraAnchorParams { std::array offset{}; // meters: right, up, forward bool hide = true; AnchorRotationMode rotation = AnchorRotationMode::Off; float yaw_offset_deg = 0.0f; float units_per_meter = -1.0f; // -1 = keep the global Units per Meter }; // ControllerAnchor parameters resolved for a matching draw. Shared transport type, like // CameraAnchorParams: all three override systems resolve into this and the draw path // applies it identically regardless of which system matched. struct ControllerAnchorParams { int hand = 1; // 0 = left, 1 = right std::array offset{}; // meters: right, up, forward bool rotation = false; // follow the controller's orientation // Model-axis correction applied in the controller frame (degrees). float yaw_deg = 0.0f; float pitch_deg = 0.0f; float roll_deg = 0.0f; }; enum class HuntingOption { Skip = 0, Pink = 1 }; enum class MatchMode { ExactHash = 0, ShaderFamily = 1, RuntimeElement = 2 }; // Version of the family-signature scheme. Bump when ComputeShaderFamilySignature's field lists // change. Version 1 (files saved without a marker) computed VS/GS families as a CRC32 over the // raw UID bytes — the same value as the exact shader hash — so persisted v1 signatures are // matched against draw hashes for backward compatibility and flagged for re-capture in the UI. static constexpr u32 FAMILY_SCHEME_VERSION = 2; struct RuntimeElementSignature { bool valid = false; bool perspective = false; int perspective_hfov_x100 = 0; int perspective_vfov_x100 = 0; int perspective_near_x1000 = 0; int perspective_far_x100 = 0; int ortho_left_x100 = 0; int ortho_right_x100 = 0; int ortho_top_x100 = 0; int ortho_bottom_x100 = 0; bool use_projection = false; bool use_projection_type = false; // Match perspective-vs-ortho only, ignoring FOV/bounds values. bool use_layer = false; bool use_viewport = false; bool use_scissor = false; bool use_render_state = false; int ortho_layer = 0; int viewport_x = 0; int viewport_y = 0; int viewport_width = 0; int viewport_height = 0; int scissor_left = 0; int scissor_top = 0; int scissor_right = 0; int scissor_bottom = 0; u32 alpha_test_hex = 0; bool ztest = false; bool zupdate = false; int zfunc = 0; bool blend_color_update = false; bool blend_alpha_update = false; }; struct HighlightedDraw { ShaderType type = ShaderType::Pixel; u64 hash = 0; RuntimeElementSignature signature; std::array textures{}; }; static ShaderHunter& GetInstance(); // --- Video thread --- u64 RegisterShader(ShaderType type, u64 hash, const u8* uid_data, size_t uid_size); void RegisterDrawCombination(u64 vs_hash, u64 ps_hash, u64 gs_hash); void OnFrameEnd(); bool ShouldSkipDraw(u64 vs_hash, u64 ps_hash, u64 gs_hash); // Query and track relaxed per-shader "family" signatures used to match dynamic variants. std::optional GetShaderFamilySignature(ShaderType type, u64 hash) const; void SetCurrentDrawShaderFamilies(u64 vs_family, u64 ps_family, u64 gs_family); // --- UI thread --- void SetEnabled(bool enabled); bool IsEnabled() const; void SetHuntingOption(HuntingOption option); HuntingOption GetHuntingOption() const; void SetHuntingMatchMode(MatchMode mode); MatchMode GetHuntingMatchMode() const; bool ShouldHighlightSelectedDraw() const; void SetActiveType(ShaderType type); ShaderType GetActiveType() const; void NextShader(); void PrevShader(); bool SelectShader(ShaderType type, u64 hash); u64 GetSelectedHash() const; int GetSelectedPosition() const; int GetTotalCount() const; // Texture tracking: capture the current draw's bound textures/signature for override matching. void SetCurrentDrawSignature(const RuntimeElementSignature& signature); void SetCurrentDrawTextures(const std::array& hashes, const std::array& names); // --- Persistent overrides (per-game INI) --- struct ShaderOverride { std::string name; std::string comments; std::string credits; u64 hash = 0; ShaderType type = ShaderType::Pixel; HandlingType handling = HandlingType::Skip; MatchMode match_mode = MatchMode::ExactHash; RuntimeElementSignature runtime_element; float element_depth = -1.0f; // Per-override within-element depth (-1 = use global) float units_per_meter = -1.0f; // Per-override UPM for UnitsPerMeter handling (-1 = global) float passthrough_opacity = 0.0f; // Passthrough handling: element opacity (0 = fully camera) // CameraAnchor handling: offset from the element's origin (meters, right/up/forward), // whether to hide the anchor element, how much of its orientation to follow (+ a fixed yaw // correction), and the world scale to use while anchored (-1 = keep the global value). float anchor_right = 0.0f; float anchor_up = 0.0f; float anchor_forward = 0.0f; bool anchor_hide = true; AnchorRotationMode anchor_rotation = AnchorRotationMode::Off; float anchor_yaw_deg = 0.0f; float anchor_units_per_meter = -1.0f; // ControllerAnchor handling: which controller the element follows (0 = left, 1 = right). // Reuses anchor_right/up/forward as meter offsets and anchor_rotation (Off/Full) for // orientation follow; yaw/pitch/roll are the model-axis correction in the controller frame. int anchor_hand = 1; float anchor_pitch_deg = 0.0f; float anchor_roll_deg = 0.0f; std::vector texture_hashes; // Only apply when any listed texture is bound (empty = any) bool texture_hashes_excluded = false; // false=Include list, true=Exclude list bool hash_family_match = false; // false=exact hash, true=family signature u64 family_signature = 0; // 0=resolve from current hash when available u32 family_version = FAMILY_SCHEME_VERSION; // scheme of the persisted family_signature bool enabled = true; bool user_defined = true; std::string flag_group; // For Flag handling: the flag name this shader sets std::string condition_flag; // For other types: condition flag name bool condition_inverted = false; // false=active, true=inactive }; // Static INI helpers (used by both ShaderHunter and ShaderOverrideWidget) static std::vector LoadOverridesFromINI( const std::string& game_id, std::optional revision = std::nullopt); static void SaveOverridesToINI(const std::string& game_id, const std::vector& overrides); // Runtime override management void LoadOverrides(const std::string& game_id); void LoadOverridesIfNeeded(const std::string& game_id); void AddAndSaveOverride(const std::string& game_id, const std::string& name, ShaderType type, u64 hash, HandlingType handling); bool HasOverrides() const; bool NeedsShaderFamilySignatures() const; bool NeedsTextureHashes() const; // Always-active skip check (independent of hunting) bool ShouldSkipByOverride(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Returns the handling type for the first matching override // (Screen/Fullscreen/HeadLocked/UnitsPerMeter), // or Skip if no non-skip override matches. HandlingType GetOverrideHandling(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Returns the per-override element depth (-1 = use global setting). float GetOverrideElementDepth(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Returns the per-override units per meter (>0 = override, -1 = use global setting). float GetOverrideUnitsPerMeter(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Returns the opacity for a Passthrough override (0 = fully see-through to the camera). float GetOverridePassthroughOpacity(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Fills the parameters of the matching CameraAnchor override. // Returns false when no CameraAnchor override matches the draw. bool GetOverrideCameraAnchor(u64 vs_hash, u64 ps_hash, u64 gs_hash, CameraAnchorParams* out_params) const; // Fills the parameters of the matching ControllerAnchor override. // Returns false when no ControllerAnchor override matches the draw. bool GetOverrideControllerAnchor(u64 vs_hash, u64 ps_hash, u64 gs_hash, ControllerAnchorParams* out_params) const; // Flag system: register flag shaders and check conditions. void RegisterFlags(u64 vs_hash, u64 ps_hash, u64 gs_hash); void RegisterExternalFlag(const std::string& flag_name); bool IsFlagActive(const std::string& flag_name) const; // Video-thread snapshot for OSD display. struct FlagStatus { std::string flag_name; bool is_active = false; std::vector impacted_shader_names; }; std::vector GetFlagStatusesForOSD() const; struct HuntingStatus { bool enabled = false; HuntingOption option = HuntingOption::Skip; MatchMode match_mode = MatchMode::ShaderFamily; ShaderType active_type = ShaderType::Pixel; u64 selected_hash = 0; u64 selected_family_signature = 0; int selected_position = -1; int selected_total = 0; u64 selected_texture_hash = 0; std::vector texture_filters; }; using HuntingStatusForOSD = HuntingStatus; // Debug logging: log override matches per draw call (toggle via UI) void SetDebugLogging(bool enabled); bool IsDebugLogging() const; // Log draw calls involving any override-relevant hash that had no override match void DebugLogUnmatched(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Dump shader source to Dump/Shaders//-.txt bool DumpShader(const std::string& game_id, ShaderType type, u64 hash) const; struct TextureUsage { u64 hash = 0; std::string name; }; // Query texture hashes seen with a specific shader hash in the previous frame. std::vector GetTexturesForHash(ShaderType type, u64 hash) const; // Query texture hashes seen with the currently selected shader in the previous frame. std::vector GetTexturesForSelectedShader() const; // Hunting-only per-texture skip filters for the currently selected shader. void SetTextureSkipEnabled(u64 texture_hash, bool enabled); bool IsTextureSkipEnabled(u64 texture_hash) const; void ClearTextureSkipFilters(); void SetTextureToolActive(bool active); void PrevTextureHash(); void NextTextureHash(); u64 GetSelectedTextureHash() const; bool ToggleSelectedTextureHashFilter(); HuntingStatus GetHuntingStatusForOSD() const; std::optional GetSelectedRuntimeElementSignature() const; bool SaveSelectedShaderOverride(const std::string& game_id, HandlingType handling); private: ShaderHunter() = default; static constexpr int TYPE_COUNT = static_cast(ShaderType::Count); mutable std::mutex m_mutex; bool m_enabled = false; HuntingOption m_hunting_option = HuntingOption::Skip; MatchMode m_hunting_match_mode = MatchMode::ShaderFamily; bool m_should_highlight_selected_draw = false; ShaderType m_active_type = ShaderType::Pixel; // Double-buffered: video thread writes m_collecting, swaps to m_display at frame end. std::array, TYPE_COUNT> m_collecting{}; std::array, TYPE_COUNT> m_display{}; std::array m_selected_hash{~0ULL, ~0ULL, ~0ULL}; std::array m_selected_pos{-1, -1, -1}; // UID cache: hash → raw UID bytes (for shader source regeneration/dump) std::array>, TYPE_COUNT> m_uid_cache{}; // Persistent overrides loaded from per-game INI std::vector m_overrides; std::unordered_set m_override_ps_hashes; // Skip handling std::unordered_set m_override_vs_hashes; std::unordered_set m_override_gs_hashes; std::unordered_set m_screen_hashes; // Screen handling (all shader types) std::unordered_set m_fullscreen_hashes; // Fullscreen handling (all shader types) std::unordered_set m_headlocked_hashes; // HeadLocked handling (all shader types) std::unordered_map m_units_per_meter_overrides; // hash -> per-override UPM std::unordered_map m_element_depths; // hash → per-override element depth (-1 = global) std::unordered_map m_passthrough_opacities; // hash → Passthrough opacity std::unordered_map m_camera_anchors; // hash → CameraAnchor params // hash → ControllerAnchor params std::unordered_map m_controller_anchors; std::string m_loaded_game_id; std::atomic_bool m_has_overrides = false; std::atomic_bool m_needs_shader_family_signatures = false; std::atomic_bool m_needs_texture_hashes = false; // Flag system: flags are active only while their shader is drawn each frame. struct FlagRule { ShaderType type = ShaderType::Pixel; u64 hash = 0; bool hash_family_match = false; u64 family_signature = 0; u32 family_version = FAMILY_SCHEME_VERSION; std::string flag_group; std::vector texture_hashes; bool texture_hashes_excluded = false; }; std::vector m_flag_rules; // flag activation rules std::unordered_set m_flags_seen_this_frame; // flags seen in current frame std::unordered_map m_flag_age; // flag → 0 if active (from previous frame) // Debug logging: log override matches once per unique draw combo per frame bool m_debug_logging = false; mutable std::unordered_set m_debug_logged_combos; // combined hash of vs+ps logged this frame std::unordered_set m_all_override_hashes; // all hashes from any override (for unmatched log) // Conditional overrides: flag condition, draw-call range, and texture filters. struct ConditionalOverride { u64 hash; HandlingType handling; ShaderType type; float element_depth; float units_per_meter; float passthrough_opacity; CameraAnchorParams anchor; ControllerAnchorParams controller_anchor; std::vector texture_hashes; // Only apply when any listed texture is bound (empty = any) bool texture_hashes_excluded; // false=Include list, true=Exclude list bool hash_family_match; // false=exact hash, true=family signature u64 family_signature; // 0=resolve from current hash when available u32 family_version; // scheme of the persisted family_signature std::string condition_flag; bool condition_inverted; }; std::vector m_conditional_overrides; bool IsConditionFlagMatch(const ConditionalOverride& cond) const; bool IsConditionalHashMatch(const ConditionalOverride& cond, u64 vs_hash, u64 ps_hash, u64 gs_hash) const; std::vector GetHuntingCandidatesLocked(int type_index) const; int GetHuntingCandidatePositionLocked(int type_index, u64 hash) const; u64 ResolveConditionalFamilySignature(const ConditionalOverride& cond, bool* out_legacy_scheme) const; bool ShouldBypassSelectedOverrideForTextureTool(u64 vs_hash, u64 ps_hash, u64 gs_hash) const; // Texture tracking: current draw's bound textures + per-element capture during hunting. std::array m_current_draw_textures{}; std::array m_current_draw_texture_names{}; RuntimeElementSignature m_current_draw_signature{}; RuntimeElementSignature m_selected_draw_signature{}; std::array, TYPE_COUNT> m_shader_family_signatures{}; u64 m_current_vs_family = 0; u64 m_current_ps_family = 0; u64 m_current_gs_family = 0; std::array>, TYPE_COUNT> m_shader_texture_usage_collecting{}; std::array>, TYPE_COUNT> m_shader_texture_usage_display{}; std::unordered_map m_texture_usage_collecting; std::unordered_map m_texture_usage_display; std::unordered_set m_texture_skip_filters; bool m_texture_skip_mode_active = false; std::atomic m_texture_tool_active{false}; bool m_has_texture_overrides = false; u64 m_selected_texture_hash = 0; };