GameDatabase: Add ability to override GS fixes

This commit is contained in:
Connor McLaughlin 2022-03-03 22:36:05 +10:00 committed by refractionpcsx2
parent d35db63d73
commit 96269db93e
11 changed files with 286 additions and 40 deletions

View File

@ -141,8 +141,8 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
// HW Renderer Fixes
//////////////////////////////////////////////////////////////////////////
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.halfScreenFix, "EmuCore/GS", "UserHacks_Half_Bottom_Override", -1, -1);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.skipDrawRangeStart, "EmuCore/GS", "UserHacks_SkipDraw", 0);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.skipDrawRangeCount, "EmuCore/GS", "UserHacks_SkipDraw_Offset", 0);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.skipDrawStart, "EmuCore/GS", "UserHacks_SkipDraw_Offset", 0);
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.skipDrawEnd, "EmuCore/GS", "UserHacks_SkipDraw", 0);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.hwAutoFlush, "EmuCore/GS", "UserHacks_AutoFlush", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.frameBufferConversion, "EmuCore/GS", "UserHacks_CPU_FB_Conversion", false);
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.disableDepthEmulation, "EmuCore/GS", "UserHacks_DisableDepthSupport", false);

View File

@ -586,7 +586,7 @@
<item row="1" column="1">
<widget class="QCheckBox" name="enableHWFixes">
<property name="text">
<string>Enable Hardware Renderer Fixes</string>
<string>Manual Hardware Renderer Fixes</string>
</property>
</widget>
</item>
@ -661,10 +661,10 @@
<item row="1" column="1">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QSpinBox" name="skipDrawRangeStart"/>
<widget class="QSpinBox" name="skipDrawStart"/>
</item>
<item>
<widget class="QSpinBox" name="skipDrawRangeCount"/>
<widget class="QSpinBox" name="skipDrawEnd"/>
</item>
</layout>
</item>

View File

@ -442,7 +442,7 @@ struct Pcsx2Config
WrapGSMem : 1,
Mipmap : 1,
AA1 : 1,
UserHacks : 1,
ManualUserHacks : 1,
UserHacks_AlignSpriteX : 1,
UserHacks_AutoFlush : 1,
UserHacks_CPUFBConversion : 1,
@ -510,8 +510,8 @@ struct Pcsx2Config
int SWExtraThreads{2};
int SWExtraThreadsHeight{4};
int TVShader{0};
int SkipDraw{0};
int SkipDrawOffset{0};
int SkipDrawStart{0};
int SkipDrawEnd{0};
int UserHacks_HalfBottomOverride{-1};
int UserHacks_HalfPixelOffset{0};

View File

@ -310,8 +310,6 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
GSConfig = config;
GSConfig.Renderer = renderer;
GSConfig.MaskUserHacks();
GSConfig.MaskUpscalingHacks();
if (!Host::AcquireHostDisplay(GetAPIForRenderer(renderer)))
{
@ -724,8 +722,6 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
Pcsx2Config::GSOptions old_config(std::move(GSConfig));
GSConfig = new_config;
GSConfig.Renderer = (GSConfig.Renderer == GSRendererType::Auto) ? GSUtil::GetPreferredRenderer() : GSConfig.Renderer;
GSConfig.MaskUserHacks();
GSConfig.MaskUpscalingHacks();
if (!s_gs)
return;

View File

@ -1053,7 +1053,7 @@ bool GSState::IsBadFrame()
return false;
}
if (m_skip == 0 && GSConfig.SkipDraw > 0)
if (m_skip == 0 && GSConfig.SkipDrawEnd > 0)
{
if (fi.TME)
{
@ -1061,8 +1061,8 @@ bool GSState::IsBadFrame()
// General, often problematic post processing
if (GSLocalMemory::m_psm[fi.TPSM].depth || GSUtil::HasSharedBits(fi.FBP, fi.FPSM, fi.TBP0, fi.TPSM))
{
m_skip_offset = GSConfig.SkipDrawOffset;
m_skip = std::max(GSConfig.SkipDraw, m_skip_offset);
m_skip_offset = GSConfig.SkipDrawStart;
m_skip = GSConfig.SkipDrawEnd;
}
}
}

View File

@ -328,7 +328,7 @@ HacksTab::HacksTab(wxWindow* parent)
PaddedBoxSizer<wxBoxSizer> tab_box(wxVERTICAL);
auto hw_prereq = [this]{ return m_is_hardware; };
auto* hacks_check_box = m_ui.addCheckBox(tab_box.inner, "Enable HW Hacks", "UserHacks", -1, hw_prereq);
auto* hacks_check_box = m_ui.addCheckBox(tab_box.inner, "Manual HW Hacks", "UserHacks", -1, hw_prereq);
auto hacks_prereq = [this, hacks_check_box]{ return m_is_hardware && hacks_check_box->GetValue(); };
auto upscale_hacks_prereq = [this, hacks_check_box]{ return !m_is_native_res && hacks_check_box->GetValue(); };

View File

@ -16,8 +16,8 @@
#include "PrecompiledHeader.h"
#include "GameDatabase.h"
#include "Config.h"
#include "Host.h"
#include "Patch.h"
#include "common/FileSystem.h"
#include "common/Path.h"
@ -31,6 +31,20 @@
#include "fmt/ranges.h"
#include <fstream>
#include <mutex>
#include <optional>
namespace GameDatabaseSchema
{
static const char* getHWFixName(GSHWFixId id);
static std::optional<GSHWFixId> parseHWFixName(const std::string_view& name);
static bool isUserHackHWFix(GSHWFixId id);
} // namespace GameDatabaseSchema
namespace GameDatabase
{
static void parseAndInsert(const std::string_view& serial, const c4::yml::NodeRef& node);
static void initDatabase();
} // namespace GameDatabase
static constexpr char GAMEDB_YAML_FILE_NAME[] = "GameIndex.yaml";
@ -85,7 +99,7 @@ const char* GameDatabaseSchema::GameEntry::compatAsString() const
}
}
void parseAndInsert(const std::string_view& serial, const c4::yml::NodeRef& node)
void GameDatabase::parseAndInsert(const std::string_view& serial, const c4::yml::NodeRef& node)
{
GameDatabaseSchema::GameEntry gameEntry;
if (node.has_child("name"))
@ -195,6 +209,25 @@ void parseAndInsert(const std::string_view& serial, const c4::yml::NodeRef& node
}
}
if (node.has_child("gsHWFixes"))
{
for (const ryml::NodeRef& n : node["gsHWFixes"].children())
{
const std::string_view id_name(n.key().data(), n.key().size());
std::optional<GameDatabaseSchema::GSHWFixId> id = GameDatabaseSchema::parseHWFixName(id_name);
std::optional<s32> value = n.has_val() ? StringUtil::FromChars<s32>(std::string_view(n.val().data(), n.val().size())) : 1;
if (!id.has_value() || !value.has_value())
{
Console.Error("[GameDB] Invalid GS HW Fix: '%*s' specified for serial '%*s'. Dropping!",
static_cast<int>(id_name.size()), id_name.data(),
static_cast<int>(serial.size()), serial.data());
continue;
}
gameEntry.gsHWFixes.emplace_back(id.value(), value.value());
}
}
// Memory Card Filters - Store as a vector to allow flexibility in the future
// - currently they are used as a '\n' delimited string in the app
if (node.has_child("memcardFilters") && node["memcardFilters"].has_children())
@ -231,16 +264,188 @@ void parseAndInsert(const std::string_view& serial, const c4::yml::NodeRef& node
s_game_db.emplace(std::move(serial), std::move(gameEntry));
}
static std::ifstream getFileStream(std::string path)
static const char* s_gs_hw_fix_names[] = {
"autoFlush",
"conservativeFramebuffer",
"cpuFramebufferConversion",
"disableDepthSupport",
"wrapGSMem",
"preloadFrameData",
"fastTextureInvalidation",
"textureInsideRT",
"alignSprite",
"mergeSprite",
"wildArmsHack",
"mipmap",
"trilinearFiltering",
"skipDrawStart",
"skipDrawEnd",
"halfBottomOverride",
"halfPixelOffset",
"roundSprite",
"texturePreloading",
};
static_assert(std::size(s_gs_hw_fix_names) == static_cast<u32>(GameDatabaseSchema::GSHWFixId::Count), "HW fix name lookup is correct size");
const char* GameDatabaseSchema::getHWFixName(GSHWFixId id)
{
#ifdef _WIN32
return std::ifstream(StringUtil::UTF8StringToWideString(path));
#else
return std::ifstream(path.c_str());
#endif
return s_gs_hw_fix_names[static_cast<u32>(id)];
}
static void initDatabase()
static std::optional<GameDatabaseSchema::GSHWFixId> GameDatabaseSchema::parseHWFixName(const std::string_view& name)
{
for (u32 i = 0; i < std::size(s_gs_hw_fix_names); i++)
{
if (name.compare(s_gs_hw_fix_names[i]) == 0)
return static_cast<GameDatabaseSchema::GSHWFixId>(i);
}
return std::nullopt;
}
bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id)
{
switch (id)
{
case GSHWFixId::Mipmap:
case GSHWFixId::TexturePreloading:
case GSHWFixId::ConservativeFramebuffer:
return false;
#ifdef PCSX2_CORE
// Trifiltering isn't a hack in Qt.
case GSHWFixId::TrilinearFiltering:
return false;
#endif
default:
return true;
}
}
u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& config) const
{
// Only apply GS HW fixes if the user hasn't manually enabled HW fixes.
const bool apply_auto_fixes = !config.ManualUserHacks;
if (!apply_auto_fixes)
Console.Warning("[GameDB] Hardware fixes are enabled, not using automatic fixes.");
u32 num_applied_fixes = 0;
for (const auto& [id, value] : gsHWFixes)
{
if (isUserHackHWFix(id) && !apply_auto_fixes)
{
PatchesCon->Warning("[GameDB] Skipping GS Hardware Fix: %s to [mode=%d]", getHWFixName(id), value);
continue;
}
switch (id)
{
case GSHWFixId::AutoFlush:
config.UserHacks_AutoFlush = (value > 0);
break;
case GSHWFixId::ConservativeFramebuffer:
config.ConservativeFramebuffer = (value > 0);
break;
case GSHWFixId::CPUFramebufferConversion:
config.UserHacks_CPUFBConversion = (value > 0);
break;
case GSHWFixId::DisableDepthSupport:
config.UserHacks_DisableDepthSupport = (value > 0);
break;
case GSHWFixId::WrapGSMem:
config.WrapGSMem = (value > 0);
break;
case GSHWFixId::PreloadFrameData:
config.PreloadFrameWithGSData = (value > 0);
break;
case GSHWFixId::FastTextureInvalidation:
config.UserHacks_DisablePartialInvalidation = (value > 0);
break;
case GSHWFixId::TextureInsideRT:
config.UserHacks_TextureInsideRt = (value > 0);
break;
case GSHWFixId::AlignSprite:
config.UserHacks_AlignSpriteX = (value > 0);
break;
case GSHWFixId::MergeSprite:
config.UserHacks_MergePPSprite = (value > 0);
break;
case GSHWFixId::WildArmsHack:
config.UserHacks_WildHack = (value > 0);
break;
case GSHWFixId::Mipmap:
{
if (value >= 0 && value <= static_cast<int>(HWMipmapLevel::Full))
{
if (config.HWMipmap == HWMipmapLevel::Automatic)
config.HWMipmap = static_cast<HWMipmapLevel>(value);
else if (config.HWMipmap == HWMipmapLevel::Off)
Console.Warning("[GameDB] Game requires mipmapping but it has been force disabled.");
}
}
break;
case GSHWFixId::TrilinearFiltering:
{
if (value >= 0 && value <= static_cast<int>(TriFiltering::Forced))
config.UserHacks_TriFilter = static_cast<TriFiltering>(value);
}
break;
case GSHWFixId::SkipDrawStart:
config.SkipDrawStart = value;
break;
case GSHWFixId::SkipDrawEnd:
config.SkipDrawEnd = value;
break;
case GSHWFixId::HalfBottomOverride:
config.UserHacks_HalfBottomOverride = value;
break;
case GSHWFixId::HalfPixelOffset:
config.UserHacks_HalfPixelOffset = value;
break;
case GSHWFixId::RoundSprite:
config.UserHacks_RoundSprite = value;
break;
case GSHWFixId::TexturePreloading:
{
if (value >= 0 && value <= static_cast<int>(TexturePreloadingLevel::Full))
config.TexturePreloading = std::min(config.TexturePreloading, static_cast<TexturePreloadingLevel>(value));
}
break;
default:
break;
}
PatchesCon->WriteLn("[GameDB] Enabled GS Hardware Fix: %s to [mode=%d]", getHWFixName(id), value);
num_applied_fixes++;
}
// fixup skipdraw range just in case the db has a bad range (but the linter should catch this)
config.SkipDrawEnd = std::max(config.SkipDrawStart, config.SkipDrawEnd);
return num_applied_fixes;
}
void GameDatabase::initDatabase()
{
ryml::Callbacks rymlCallbacks = ryml::get_callbacks();
rymlCallbacks.m_error = [](const char* msg, size_t msg_len, ryml::Location loc, void*) {
@ -291,8 +496,6 @@ static void initDatabase()
ryml::reset_callbacks();
}
void GameDatabase::ensureLoaded()
{
std::call_once(s_load_once_flag, []() {

View File

@ -15,16 +15,18 @@
#pragma once
#include "Config.h"
#include <optional>
#include <string>
#include <string_view>
#include <unordered_map>
#include <vector>
#include <string>
enum GamefixId;
enum SpeedhackId;
class GameDatabaseSchema
namespace GameDatabaseSchema
{
public:
enum class Compatibility
{
Unknown = 0,
@ -54,6 +56,34 @@ public:
Full
};
enum class GSHWFixId : u32
{
// boolean settings
AutoFlush,
ConservativeFramebuffer,
CPUFramebufferConversion,
DisableDepthSupport,
WrapGSMem,
PreloadFrameData,
FastTextureInvalidation,
TextureInsideRT,
AlignSprite,
MergeSprite,
WildArmsHack,
// integer settings
Mipmap,
TrilinearFiltering,
SkipDrawStart,
SkipDrawEnd,
HalfBottomOverride,
HalfPixelOffset,
RoundSprite,
TexturePreloading,
Count
};
using Patch = std::vector<std::string>;
struct GameEntry
@ -67,6 +97,7 @@ public:
ClampMode vuClampMode = ClampMode::Undefined;
std::vector<GamefixId> gameFixes;
std::vector<std::pair<SpeedhackId, int>> speedHacks;
std::vector<std::pair<GSHWFixId, s32>> gsHWFixes;
std::vector<std::string> memcardFilters;
std::unordered_map<std::string, Patch> patches;
@ -74,6 +105,9 @@ public:
std::string memcardFiltersAsString() const;
const Patch* findPatch(const std::string_view& crc) const;
const char* compatAsString() const;
/// Applies GS hardware fixes to an existing config. Returns the number of applied fixes.
u32 applyGSHardwareFixes(Pcsx2Config::GSOptions& config) const;
};
};

View File

@ -314,7 +314,7 @@ Pcsx2Config::GSOptions::GSOptions()
Mipmap = true;
AA1 = true;
UserHacks = false;
ManualUserHacks = false;
UserHacks_AlignSpriteX = false;
UserHacks_AutoFlush = false;
UserHacks_CPUFBConversion = false;
@ -385,8 +385,8 @@ bool Pcsx2Config::GSOptions::OptionsAreEqual(const GSOptions& right) const
OpEqu(SWExtraThreads) &&
OpEqu(SWExtraThreadsHeight) &&
OpEqu(TVShader) &&
OpEqu(SkipDraw) &&
OpEqu(SkipDrawOffset) &&
OpEqu(SkipDrawEnd) &&
OpEqu(SkipDrawStart) &&
OpEqu(UserHacks_HalfBottomOverride) &&
OpEqu(UserHacks_HalfPixelOffset) &&
@ -513,7 +513,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
GSSettingBoolEx(WrapGSMem, "wrap_gs_mem");
GSSettingBoolEx(Mipmap, "mipmap");
GSSettingBoolEx(AA1, "aa1");
GSSettingBoolEx(UserHacks, "UserHacks");
GSSettingBoolEx(ManualUserHacks, "UserHacks");
GSSettingBoolEx(UserHacks_AlignSpriteX, "UserHacks_align_sprite_X");
GSSettingBoolEx(UserHacks_AutoFlush, "UserHacks_AutoFlush");
GSSettingBoolEx(UserHacks_CPUFBConversion, "UserHacks_CPU_FB_Conversion");
@ -555,8 +555,9 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
GSSettingIntEx(SWExtraThreads, "extrathreads");
GSSettingIntEx(SWExtraThreadsHeight, "extrathreads_height");
GSSettingIntEx(TVShader, "TVShader");
GSSettingIntEx(SkipDraw, "UserHacks_SkipDraw");
GSSettingIntEx(SkipDrawOffset, "UserHacks_SkipDraw_Offset");
GSSettingIntEx(SkipDrawStart, "UserHacks_SkipDraw_Offset");
GSSettingIntEx(SkipDrawEnd, "UserHacks_SkipDraw");
SkipDrawEnd = std::max(SkipDrawStart, SkipDrawEnd);
GSSettingIntEx(UserHacks_HalfBottomOverride, "UserHacks_Half_Bottom_Override");
GSSettingIntEx(UserHacks_HalfPixelOffset, "UserHacks_HalfPixelOffset");
@ -588,7 +589,7 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
void Pcsx2Config::GSOptions::MaskUserHacks()
{
if (UserHacks)
if (ManualUserHacks)
return;
UserHacks_AlignSpriteX = false;
@ -605,8 +606,8 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
UserHacks_TextureInsideRt = false;
UserHacks_TCOffsetX = 0;
UserHacks_TCOffsetY = 0;
SkipDraw = 0;
SkipDrawOffset = 0;
SkipDrawStart = 0;
SkipDrawEnd = 0;
// in wx, we put trilinear filtering behind user hacks, but not in qt.
#ifndef PCSX2_CORE
@ -616,7 +617,7 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
void Pcsx2Config::GSOptions::MaskUpscalingHacks()
{
if (UpscaleMultiplier == 1 || UserHacks)
if (UpscaleMultiplier == 1 || ManualUserHacks)
return;
UserHacks_AlignSpriteX = false;

View File

@ -218,6 +218,10 @@ void VMManager::LoadSettings()
InputManager::ReloadSources(*si);
InputManager::ReloadBindings(*si);
// Remove any user-specified hacks in the config (we don't want stale/conflicting values when it's globally disabled).
EmuConfig.GS.MaskUserHacks();
EmuConfig.GS.MaskUpscalingHacks();
if (HasValidVM())
ApplyGameFixes();
}
@ -295,6 +299,8 @@ void VMManager::ApplyGameFixes()
if (id == Fix_GoemonTlbMiss && true)
vtlb_Alloc_Ppmap();
}
s_active_game_fixes += game->applyGSHardwareFixes(EmuConfig.GS);
}
std::string VMManager::GetGameSettingsPath(u32 game_crc)

View File

@ -333,6 +333,8 @@ static int loadGameSettings(Pcsx2Config& dest, const GameDatabaseSchema::GameEnt
vtlb_Alloc_Ppmap();
}
gf += game.applyGSHardwareFixes(dest.GS);
return gf;
}
@ -404,6 +406,10 @@ static void _ApplySettings(const Pcsx2Config& src, Pcsx2Config& fixup)
fixup.GS.VsyncEnable = VsyncMode::Off;
}
// Remove any user-specified hacks in the config (we don't want stale/conflicting values when it's globally disabled).
fixup.GS.MaskUserHacks();
fixup.GS.MaskUpscalingHacks();
wxString gamePatch;
wxString gameFixes;
wxString gameCheats;