mirror of
https://github.com/thejerf/baijiSteamVR.git
synced 2026-10-19 18:03:10 +02:00
465 lines
19 KiB
C++
465 lines
19 KiB
C++
// Copyright 2026 Dolphin Emulator Project
|
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
|
|
#pragma once
|
|
|
|
#include <atomic>
|
|
#include <array>
|
|
#include <cstdint>
|
|
#include <mutex>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <unordered_set>
|
|
#include <utility>
|
|
#include <vector>
|
|
|
|
#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,
|
|
HeadLocked = 3,
|
|
Flag = 4,
|
|
UnitsPerMeter = 5,
|
|
FullscreenMono = 6
|
|
};
|
|
|
|
enum class HuntingOption
|
|
{
|
|
Skip = 0,
|
|
Pink = 1
|
|
};
|
|
|
|
enum class MatchMode
|
|
{
|
|
ExactHash = 0,
|
|
ShaderFamily = 1,
|
|
RuntimeElement = 2
|
|
};
|
|
|
|
struct RuntimeElementSignature
|
|
{
|
|
bool valid = false;
|
|
bool perspective = false;
|
|
|
|
bool use_projection = true;
|
|
bool use_layer = false;
|
|
bool use_viewport = true;
|
|
bool use_scissor = false;
|
|
bool use_render_state = true;
|
|
|
|
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;
|
|
int ortho_near_x100 = 0;
|
|
int ortho_far_x100 = 0;
|
|
|
|
int ortho_layer = -1;
|
|
|
|
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 blend_color_update = false;
|
|
bool blend_alpha_update = false;
|
|
bool ztest = false;
|
|
bool zupdate = false;
|
|
int zfunc = 0;
|
|
};
|
|
|
|
struct HuntedDraw
|
|
{
|
|
bool valid = false;
|
|
ShaderType type = ShaderType::Pixel;
|
|
u64 hash = 0;
|
|
RuntimeElementSignature signature;
|
|
std::array<u64, 8> textures{};
|
|
std::array<std::string, 8> texture_names{};
|
|
int draw_index = -1;
|
|
int match_index = -1;
|
|
};
|
|
|
|
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<u64> 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;
|
|
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;
|
|
|
|
// Element mode: cycle through individual draw calls of the selected shader
|
|
void SetElementMode(bool enabled);
|
|
bool IsElementMode() const;
|
|
void NextElement();
|
|
void PrevElement();
|
|
int GetSelectedElement() const;
|
|
int GetElementTotal() const;
|
|
|
|
// Element range: skip a contiguous range of draw calls instead of just one
|
|
void MarkRangeStart(); // Set range start to current cursor position
|
|
void MarkRangeEnd(); // Set range end to current cursor position
|
|
void ClearRange(); // Reset to single-element mode
|
|
int GetElementStart() const;
|
|
int GetElementEnd() const;
|
|
|
|
// Texture tracking: capture bound textures per draw call for element identification
|
|
void SetCurrentDrawTextures(const std::array<u64, 8>& hashes,
|
|
const std::array<std::string, 8>& names);
|
|
void SetCurrentDrawSignature(const RuntimeElementSignature& signature);
|
|
std::array<u64, 8> GetElementTextures(int element) const;
|
|
std::array<std::string, 8> GetElementTextureNames(int element) const;
|
|
std::optional<RuntimeElementSignature> GetSelectedRuntimeElementSignature() const;
|
|
void SetGroupHuntEnabled(bool enabled);
|
|
bool IsGroupHuntEnabled() const;
|
|
bool SeedGroupHuntFromCurrentDraw();
|
|
void SetGroupHuntGroupMask(bool use_projection, bool use_layer, bool use_viewport,
|
|
bool use_scissor, bool use_render_state);
|
|
RuntimeElementSignature GetGroupHuntSeedSignature() const;
|
|
std::optional<HuntedDraw> GetGroupHuntHighlightedDraw() const;
|
|
|
|
// --- Persistent overrides (per-game INI) ---
|
|
struct ShaderOverride
|
|
{
|
|
std::string name;
|
|
std::string comments;
|
|
std::string credits;
|
|
u64 hash = 0;
|
|
ShaderType type = ShaderType::Pixel;
|
|
MatchMode match_mode = MatchMode::ExactHash;
|
|
HandlingType handling = HandlingType::Skip;
|
|
int layer = -1; // Manual layer index for Screen handling (-1 = auto)
|
|
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)
|
|
bool clear_efb = false; // Clear the next EFB copy to transparent when this shader draws
|
|
int clear_efb_min_width = 0; // Min native width for EFB clear (0 = no lower bound)
|
|
int clear_efb_max_width = 0; // Max native width for EFB clear (0 = no upper bound)
|
|
int element_start = -1; // First draw call index to apply override (-1 = all)
|
|
int element_end = -1; // Last draw call index (-1 = all)
|
|
int element_reference_total = 0; // Draw-call count used when range was authored (0 = fixed)
|
|
std::vector<u64> 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
|
|
RuntimeElementSignature runtime_element;
|
|
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<ShaderOverride> LoadOverridesFromINI(const std::string& game_id);
|
|
static void SaveOverridesToINI(const std::string& game_id,
|
|
const std::vector<ShaderOverride>& 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;
|
|
|
|
// Advance per-hash draw counters for element-aware overrides.
|
|
// Must be called once per draw from VertexManagerBase, before any override checks.
|
|
void AdvanceOverrideDrawCounters(u64 vs_hash, u64 ps_hash, u64 gs_hash);
|
|
|
|
// 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/FullscreenMono/HeadLocked/UnitsPerMeter),
|
|
// or Skip if no non-skip override matches.
|
|
HandlingType GetOverrideHandling(u64 vs_hash, u64 ps_hash, u64 gs_hash) const;
|
|
|
|
// Returns the manual layer for a Screen override (-1 = auto).
|
|
int GetOverrideLayer(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;
|
|
|
|
// ClearEFB: check if any hash has clear_efb set, and mark pending clear with size bounds.
|
|
// Called from VertexManagerBase::Flush when a shader with clear_efb=true is drawn.
|
|
void CheckClearEFBForDraw(u64 vs_hash, u64 ps_hash, u64 gs_hash);
|
|
// Called from TextureCacheBase: returns true (and resets the flag) if EFB copy should be cleared.
|
|
// Checks the copy's native width against stored per-override size bounds.
|
|
bool ShouldClearEFBCopy(int width);
|
|
|
|
// Flag system: register flag shaders and check conditions.
|
|
void RegisterFlags(u64 vs_hash, u64 ps_hash, u64 gs_hash);
|
|
// Video-thread snapshot for OSD display (name + active state).
|
|
struct FlagStatus
|
|
{
|
|
std::string flag_name;
|
|
bool is_active = false;
|
|
std::vector<std::string> impacted_shader_names;
|
|
};
|
|
std::vector<FlagStatus> GetFlagStatusesForOSD() const;
|
|
struct HuntingStatus
|
|
{
|
|
bool enabled = false;
|
|
HuntingOption option = HuntingOption::Skip;
|
|
ShaderType active_type = ShaderType::Pixel;
|
|
u64 selected_hash = 0;
|
|
int selected_position = -1;
|
|
int selected_total = 0;
|
|
bool element_mode = false;
|
|
int selected_element = -1;
|
|
int element_total = 0;
|
|
int element_start = -1;
|
|
int element_end = -1;
|
|
std::vector<u64> texture_filters;
|
|
bool group_hunt_enabled = false;
|
|
bool group_hunt_seed_valid = false;
|
|
ShaderType group_hunt_seed_type = ShaderType::Pixel;
|
|
u64 group_hunt_seed_hash = 0;
|
|
int group_hunt_selected_match = -1;
|
|
int group_hunt_total_matches = 0;
|
|
int group_hunt_draw_index = -1;
|
|
u64 group_hunt_current_hash = 0;
|
|
RuntimeElementSignature group_hunt_seed_signature;
|
|
};
|
|
HuntingStatus GetHuntingStatusForOSD() const;
|
|
|
|
// 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/<game_id>/<hash>-<type>.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<TextureUsage> GetTexturesForHash(ShaderType type, u64 hash) const;
|
|
|
|
// Query texture hashes seen with the currently selected shader in the previous frame.
|
|
std::vector<TextureUsage> 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 NextTextureHash();
|
|
void PrevTextureHash();
|
|
u64 GetSelectedTextureHash() const;
|
|
bool ToggleSelectedTextureHashFilter();
|
|
bool SaveSelectedShaderOverride(const std::string& game_id, HandlingType handling);
|
|
void SetTextureToolActive(bool active);
|
|
|
|
private:
|
|
ShaderHunter() = default;
|
|
|
|
static constexpr int TYPE_COUNT = static_cast<int>(ShaderType::Count);
|
|
|
|
mutable std::mutex m_mutex;
|
|
bool m_enabled = false;
|
|
HuntingOption m_hunting_option = HuntingOption::Skip;
|
|
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<std::set<u64>, TYPE_COUNT> m_collecting{};
|
|
std::array<std::set<u64>, TYPE_COUNT> m_display{};
|
|
|
|
std::array<u64, TYPE_COUNT> m_selected_hash{~0ULL, ~0ULL, ~0ULL};
|
|
std::array<int, TYPE_COUNT> m_selected_pos{-1, -1, -1};
|
|
|
|
// Element mode: per-draw-call cycling within a selected shader
|
|
bool m_element_mode = false;
|
|
int m_selected_element = -1; // Cursor position for identification (-1 = skip all)
|
|
int m_element_start = -1; // Range start (-1 = not set, use cursor)
|
|
int m_element_end = -1; // Range end (-1 = not set, use cursor)
|
|
int m_element_total = 0; // Total draw calls using selected hash last frame
|
|
int m_element_counter = 0; // Per-frame counter, incremented in ShouldSkipDraw
|
|
|
|
// UID cache: hash → raw UID bytes (for shader source regeneration/dump)
|
|
std::array<std::unordered_map<u64, std::vector<u8>>, TYPE_COUNT> m_uid_cache{};
|
|
|
|
// ClearEFB: when a shader with clear_efb=true is drawn, set this flag.
|
|
// The next EFB copy operation will clear to transparent instead of copying.
|
|
bool m_clear_next_efb = false;
|
|
int m_pending_clear_min = 0; // Pending clear lower width bound (0 = no lower bound)
|
|
int m_pending_clear_max = 0; // Pending clear upper width bound (0 = no upper bound)
|
|
std::unordered_set<u64> m_clear_efb_hashes; // Hashes with clear_efb=true (any handling type)
|
|
std::unordered_map<u64, std::pair<int, int>> m_clear_efb_bounds; // hash → (min_w, max_w)
|
|
|
|
// Persistent overrides loaded from per-game INI
|
|
std::vector<ShaderOverride> m_overrides;
|
|
std::unordered_set<u64> m_override_ps_hashes; // Skip handling
|
|
std::unordered_set<u64> m_override_vs_hashes;
|
|
std::unordered_set<u64> m_override_gs_hashes;
|
|
std::unordered_set<u64> m_screen_hashes; // Screen handling (all shader types)
|
|
std::unordered_set<u64> m_fullscreen_hashes; // Fullscreen handling (all shader types)
|
|
std::unordered_set<u64> m_fullscreen_mono_hashes; // FullscreenMono handling (all shader types)
|
|
std::unordered_set<u64> m_headlocked_hashes; // HeadLocked handling (all shader types)
|
|
std::unordered_map<u64, float> m_units_per_meter_overrides; // hash -> per-override UPM
|
|
std::unordered_map<u64, int> m_screen_layers; // hash → manual layer (-1 = auto)
|
|
std::unordered_map<u64, float> m_element_depths; // hash → per-override element depth (-1 = global)
|
|
std::string m_loaded_game_id;
|
|
|
|
// 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;
|
|
std::string flag_group;
|
|
std::vector<u64> texture_hashes;
|
|
bool texture_hashes_excluded = false;
|
|
};
|
|
std::vector<FlagRule> m_flag_rules; // flag activation rules
|
|
std::unordered_set<std::string> m_flags_seen_this_frame; // flags seen in current frame
|
|
std::unordered_map<std::string, int> 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<u64> m_debug_logged_combos; // combined hash of vs+ps logged this frame
|
|
std::unordered_set<u64> 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;
|
|
MatchMode match_mode;
|
|
int layer;
|
|
float element_depth;
|
|
float units_per_meter;
|
|
int element_start; // First draw index (-1 = all)
|
|
int element_end; // Last draw index (-1 = all)
|
|
int element_reference_total; // Draw-call count used when range was authored (0 = fixed)
|
|
std::vector<u64> 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
|
|
RuntimeElementSignature runtime_element;
|
|
std::string condition_flag;
|
|
bool condition_inverted;
|
|
};
|
|
std::vector<ConditionalOverride> m_conditional_overrides;
|
|
|
|
// Per-hash per-frame draw counters for element-aware overrides.
|
|
// Written by AdvanceOverrideDrawCounters(), read by ShouldSkipByOverride/GetOverrideHandling.
|
|
// All access is on the video thread only, no lock needed.
|
|
bool m_has_element_overrides = false;
|
|
mutable std::unordered_map<u64, int> m_override_draw_counters;
|
|
mutable std::unordered_map<u64, int> m_override_draw_totals_prev; // previous frame totals
|
|
mutable std::unordered_map<u64, int> m_runtime_element_reference_totals;
|
|
mutable int m_current_vs_draw_idx = 0;
|
|
mutable int m_current_ps_draw_idx = 0;
|
|
mutable int m_current_gs_draw_idx = 0;
|
|
|
|
std::pair<int, int> ResolveElementRange(const ConditionalOverride& cond) const;
|
|
bool IsConditionFlagMatch(const ConditionalOverride& cond) const;
|
|
bool IsConditionalHashMatch(const ConditionalOverride& cond, u64 vs_hash, u64 ps_hash,
|
|
u64 gs_hash) const;
|
|
bool IsRuntimeElementMatch(const RuntimeElementSignature& signature) const;
|
|
u64 ResolveConditionalFamilySignature(const ConditionalOverride& cond) 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<u64, 8> m_current_draw_textures{};
|
|
std::array<std::string, 8> m_current_draw_texture_names{};
|
|
RuntimeElementSignature m_current_draw_signature{};
|
|
RuntimeElementSignature m_selected_draw_signature{};
|
|
ShaderType m_selected_draw_signature_type = ShaderType::Pixel;
|
|
u64 m_selected_draw_signature_hash = 0;
|
|
std::array<std::unordered_map<u64, u64>, 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::vector<std::array<u64, 8>> m_element_textures_collecting;
|
|
std::vector<std::array<u64, 8>> m_element_textures_display;
|
|
std::vector<std::array<std::string, 8>> m_element_tex_names_collecting;
|
|
std::vector<std::array<std::string, 8>> m_element_tex_names_display;
|
|
std::array<std::unordered_map<u64, std::unordered_map<u64, std::string>>, TYPE_COUNT>
|
|
m_shader_texture_usage_collecting{};
|
|
std::array<std::unordered_map<u64, std::unordered_map<u64, std::string>>, TYPE_COUNT>
|
|
m_shader_texture_usage_display{};
|
|
std::unordered_map<u64, std::string> m_texture_usage_collecting;
|
|
std::unordered_map<u64, std::string> m_texture_usage_display;
|
|
std::unordered_set<u64> m_texture_skip_filters;
|
|
u64 m_selected_texture_hash = 0;
|
|
int m_selected_texture_pos = -1;
|
|
bool m_texture_skip_mode_active = false;
|
|
std::atomic<bool> m_texture_tool_active{false};
|
|
bool m_has_texture_overrides = false;
|
|
|
|
bool HasActiveGroupHuntLocked() const;
|
|
u64 GetHashForType(ShaderType type, u64 vs_hash, u64 ps_hash, u64 gs_hash) const;
|
|
|
|
bool m_group_hunt_enabled = false;
|
|
RuntimeElementSignature m_group_hunt_seed_signature{};
|
|
ShaderType m_group_hunt_seed_type = ShaderType::Pixel;
|
|
u64 m_group_hunt_seed_hash = 0;
|
|
int m_group_hunt_selected_match = 0;
|
|
int m_group_hunt_match_total = 0;
|
|
int m_group_hunt_match_counter = 0;
|
|
int m_group_hunt_draw_counter = 0;
|
|
HuntedDraw m_group_hunt_selected_draw{};
|
|
};
|