mirror of
https://github.com/PCSX2/pcsx2.git
synced 2026-08-12 17:10:23 +02:00
Compare commits
@@ -12,6 +12,7 @@ allowed_game_options = [
|
||||
"roundModes",
|
||||
"clampModes",
|
||||
"gameFixes",
|
||||
"gsHWFixes",
|
||||
"speedHacks",
|
||||
"memcardFilters",
|
||||
"patches",
|
||||
@@ -19,24 +20,54 @@ allowed_game_options = [
|
||||
allowed_round_modes = ["eeRoundMode", "vuRoundMode"]
|
||||
allowed_clamp_modes = ["eeClampMode", "vuClampMode"]
|
||||
allowed_game_fixes = [
|
||||
"VuAddSubHack",
|
||||
"FpuCompareHack",
|
||||
"FpuMulHack",
|
||||
"FpuNegDivHack",
|
||||
"XGKickHack",
|
||||
"EETimingHack",
|
||||
"GoemonTlbHack",
|
||||
"SoftwareRendererFMVHack",
|
||||
"SkipMPEGHack",
|
||||
"OPHFlagHack",
|
||||
"EETimingHack",
|
||||
"DMABusyHack",
|
||||
"GIFFIFOHack",
|
||||
"VIFFIFOHack",
|
||||
"VIF1StallHack",
|
||||
"GIFFIFOHack",
|
||||
"GoemonTlbHack",
|
||||
"VUSyncHack",
|
||||
"VuAddSubHack",
|
||||
"IbitHack",
|
||||
"VUSyncHack",
|
||||
"VUOverflowHack",
|
||||
"XGKickHack",
|
||||
]
|
||||
allowed_speed_hacks = ["mvuFlagSpeedHack", "InstantVU1SpeedHack"]
|
||||
allowed_gs_hw_fixes = [
|
||||
"autoFlush",
|
||||
"conservativeFramebuffer",
|
||||
"cpuFramebufferConversion",
|
||||
"disableDepthSupport",
|
||||
"wrapGSMem",
|
||||
"preloadFrameData",
|
||||
"fastTextureInvalidation",
|
||||
"textureInsideRT",
|
||||
"alignSprite",
|
||||
"mergeSprite",
|
||||
"wildArmsHack",
|
||||
"pointListPalette",
|
||||
"mipmap",
|
||||
"trilinearFiltering",
|
||||
"skipDrawStart",
|
||||
"skipDrawEnd",
|
||||
"halfBottomOverride",
|
||||
"halfPixelOffset",
|
||||
"roundSprite",
|
||||
"texturePreloading",
|
||||
]
|
||||
gs_hw_fix_ranges = {
|
||||
"mipmap": (0, 2),
|
||||
"trilinearFiltering": (0, 2),
|
||||
"skipDrawStart": (0, 100000),
|
||||
"skipDrawEnd": (0, 100000),
|
||||
"halfPixelOffset": (0, 3),
|
||||
"roundSprite": (0, 2),
|
||||
}
|
||||
allowed_speed_hacks = ["mvuFlagSpeedHack", "InstantVU1SpeedHack", "MTVUSpeedHack"]
|
||||
# Patches are allowed to have a 'default' key or a crc-32 key, followed by
|
||||
allowed_patch_options = ["author", "content"]
|
||||
|
||||
@@ -94,6 +125,29 @@ def validate_game_fixes(serial, key, value):
|
||||
validate_valid_options(serial, key, gamefix, allowed_game_fixes)
|
||||
|
||||
|
||||
def validate_gs_hw_fix_value(serial, key, value):
|
||||
low, high = 0, 1
|
||||
if key in gs_hw_fix_ranges:
|
||||
low, high = gs_hw_fix_ranges[key]
|
||||
validate_int_option(serial, key, value, low, high)
|
||||
|
||||
|
||||
def validate_gs_hw_fixes(serial, key, value):
|
||||
if not isinstance(value, dict):
|
||||
issue_list.append("[{}]: 'gsHWFixes' must be a valid object".format(serial))
|
||||
return
|
||||
for fix, fix_value in value.items():
|
||||
validate_valid_options(serial, key, fix, allowed_gs_hw_fixes)
|
||||
validate_gs_hw_fix_value(serial, fix, fix_value)
|
||||
|
||||
# skipdraw range must have end >= start
|
||||
skip_draw_start = value["skipDrawStart"] if "skipDrawStart" in value else 0
|
||||
skip_draw_end = value["skipDrawEnd"] if "skipDrawEnd" in value else 0
|
||||
if isinstance(skip_draw_start, int) and isinstance(skip_draw_end, int) and skip_draw_end < skip_draw_start:
|
||||
issue_list.append("[{}]: skipDrawStart({}) must be greater or equal to skipDrawEnd({})".format(
|
||||
serial, skip_draw_start, skip_draw_end))
|
||||
|
||||
|
||||
def validate_speed_hacks(serial, key, value):
|
||||
if not isinstance(value, dict):
|
||||
issue_list.append("[{}]: 'speedHacks' must be a valid object".format(serial))
|
||||
@@ -145,6 +199,7 @@ option_validation_handlers = {
|
||||
)
|
||||
),
|
||||
"gameFixes": (lambda serial, key, value: validate_game_fixes(serial, key, value)),
|
||||
"gsHWFixes": (lambda serial, key, value: validate_gs_hw_fixes(serial, key, value)),
|
||||
"speedHacks": (lambda serial, key, value: validate_speed_hacks(serial, key, value)),
|
||||
"memcardFilters": (
|
||||
lambda serial, key, value: validate_list_of_strings(serial, key, value)
|
||||
|
||||
Binary file not shown.
+2234
-27
File diff suppressed because it is too large
Load Diff
@@ -402,7 +402,7 @@
|
||||
03000000152000000182000000000000,NGDS,a:b2,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a3,righty:a4,start:b9,x:b3,y:b0,platform:Windows,
|
||||
030000007e0500001920000000000000,Nintendo Switch N64 Controller,+rightx:b8,+righty:b2,-rightx:b3,-righty:b7,a:b1,b:b0,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,leftx:a0,lefty:a1,misc1:b13,rightshoulder:b5,righttrigger:b10,start:b9,platform:Windows,
|
||||
030000007e0500001720000000000000,Nintendo Switch Online Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,lefttrigger:b6,rightshoulder:b5,righttrigger:b7,start:b9,x:b2,y:b3,platform:Windows,
|
||||
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Windows,
|
||||
030000007e0500000920000000000000,Nintendo Switch Pro Controller,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,misc1:b13,platform:Windows,
|
||||
030000000d0500000308000000000000,Nostromo N45,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b9,leftshoulder:b4,leftstick:b12,lefttrigger:b5,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b11,righttrigger:b7,rightx:a3,righty:a2,start:b10,x:b2,y:b3,platform:Windows,
|
||||
03000000550900001472000000000000,NVIDIA Controller,a:b11,b:b10,back:b13,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b7,leftstick:b5,lefttrigger:a4,leftx:a0,lefty:a1,rightshoulder:b6,rightstick:b4,righttrigger:a5,rightx:a3,righty:a6,start:b3,x:b9,y:b8,platform:Windows,
|
||||
03000000550900001072000000000000,NVIDIA Shield,a:b9,b:b8,back:b11,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b3,lefttrigger:a3,leftx:a0,lefty:a1,rightshoulder:b4,rightstick:b2,righttrigger:a4,rightx:a2,righty:a5,start:b0,x:b7,y:b6,platform:Windows,
|
||||
@@ -1112,6 +1112,7 @@ xinput,XInput Controller,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,
|
||||
030000005e040000000b000008040000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
050000005e040000050b000003090000,Microsoft Xbox One Elite 2,a:b0,b:b1,back:b17,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b12,leftshoulder:b6,leftstick:b13,lefttrigger:a6,leftx:a0,lefty:a1,rightshoulder:b7,rightstick:b14,righttrigger:a5,rightx:a2,righty:a3,start:b11,x:b3,y:b4,platform:Linux,
|
||||
030000005e040000ea02000008040000,Microsoft Xbox One S,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,guide:b8,leftshoulder:b4,leftstick:b9,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b10,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b2,y:b3,platform:Linux,
|
||||
060000005e040000120b000009050000,Microsoft Xbox One,a:b0,b:b1,x:b2,y:b3,back:b6,guide:b8,start:b7,leftshoulder:b4,rightshoulder:b5,leftstick:b9,rightstick:b10,dpup:h0.1,dpleft:h0.8,dpdown:h0.4,dpright:h0.2,leftx:a0,lefty:a1,rightx:a3,righty:a4,lefttrigger:a2,righttrigger:a5,platform:Linux,
|
||||
030000005e0400008902000021010000,Microsoft Xbox pad v2,a:b0,b:b1,back:b6,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b5,leftstick:b8,lefttrigger:a2,leftx:a0,lefty:a1,rightshoulder:b2,rightstick:b9,righttrigger:a5,rightx:a3,righty:a4,start:b7,x:b3,y:b4,platform:Linux,
|
||||
03000000030000000300000002000000,Miroof,a:b1,b:b0,back:b6,leftshoulder:b4,leftx:a0,lefty:a1,rightshoulder:b5,start:b7,x:b3,y:b2,platform:Linux,
|
||||
050000004d4f435554452d3035335800,Mocute 053X,a:b0,b:b1,back:b8,dpdown:h0.4,dpleft:h0.8,dpright:h0.2,dpup:h0.1,leftshoulder:b4,leftstick:b10,lefttrigger:b6,leftx:a0,lefty:a1,rightshoulder:b5,rightstick:b11,righttrigger:b7,rightx:a2,righty:a3,start:b9,x:b2,y:b3,platform:Linux,
|
||||
|
||||
@@ -1269,7 +1269,7 @@ bool FileSystem::FindFiles(const char* Path, const char* Pattern, u32 Flags, Fin
|
||||
|
||||
bool FileSystem::StatFile(const char* path, struct stat* st)
|
||||
{
|
||||
return stat(path, st);
|
||||
return stat(path, st) == 0;
|
||||
}
|
||||
|
||||
bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
|
||||
@@ -1278,7 +1278,7 @@ bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
|
||||
if (fd < 0)
|
||||
return false;
|
||||
|
||||
return fstat(fd, st);
|
||||
return fstat(fd, st) == 0;
|
||||
}
|
||||
|
||||
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
|
||||
|
||||
@@ -153,6 +153,17 @@ namespace StringUtil
|
||||
return (str.length() >= suffix_length && str.compare(str.length() - suffix_length, suffix_length, suffix) == 0);
|
||||
}
|
||||
|
||||
/// StartsWith/EndsWith variants which aren't case sensitive.
|
||||
static inline bool StartsWithNoCase(const std::string_view& str, const char* prefix)
|
||||
{
|
||||
return (Strncasecmp(str.data(), prefix, std::strlen(prefix)) == 0);
|
||||
}
|
||||
static inline bool EndsWithNoCase(const std::string_view& str, const char* suffix)
|
||||
{
|
||||
const std::size_t suffix_length = std::strlen(suffix);
|
||||
return (str.length() >= suffix_length && Strncasecmp(str.data() + (str.length() - suffix_length), suffix, suffix_length) == 0);
|
||||
}
|
||||
|
||||
/// Strip whitespace from the start/end of the string.
|
||||
std::string_view StripWhitespace(const std::string_view& str);
|
||||
|
||||
|
||||
@@ -45,7 +45,7 @@ namespace x86Emitter
|
||||
{
|
||||
if (sibdest.Is8BitOp())
|
||||
{
|
||||
xOpWrite(sibdest.GetPrefix16(), 0x80, InstType, sibdest);
|
||||
xOpWrite(sibdest.GetPrefix16(), 0x80, InstType, sibdest, 1);
|
||||
|
||||
xWrite<s8>(imm);
|
||||
}
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include "pcsx2/Frontend/InputManager.h"
|
||||
#include "pcsx2/Frontend/ImGuiManager.h"
|
||||
#include "pcsx2/GS.h"
|
||||
#include "pcsx2/GSDumpReplayer.h"
|
||||
#include "pcsx2/HostDisplay.h"
|
||||
#include "pcsx2/PAD/Host/PAD.h"
|
||||
#include "pcsx2/PerformanceMetrics.h"
|
||||
@@ -677,6 +678,9 @@ bool Host::BeginPresentFrame(bool frame_skip)
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
{
|
||||
if (GSDumpReplayer::IsReplayingDump())
|
||||
GSDumpReplayer::RenderUI();
|
||||
|
||||
ImGuiManager::RenderOSD();
|
||||
s_host_display->EndPresent();
|
||||
ImGuiManager::NewFrame();
|
||||
|
||||
+13
-9
@@ -41,7 +41,9 @@ static void PrintCommandLineHelp(const char* progname)
|
||||
std::fprintf(stderr, "\n");
|
||||
std::fprintf(stderr, " -help: Displays this information and exits.\n");
|
||||
std::fprintf(stderr, " -version: Displays version information and exits.\n");
|
||||
std::fprintf(stderr, " -batch: Enables batch mode (exits after powering off).\n");
|
||||
std::fprintf(stderr, " -batch: Enables batch mode (exits after shutting down).\n");
|
||||
std::fprintf(stderr, " -elf <file>: Overrides the boot ELF with the specified filename.\n");
|
||||
std::fprintf(stderr, " -disc <path>: Uses the specified host DVD drive as a source.\n");
|
||||
std::fprintf(stderr, " -fastboot: Force fast boot for provided filename.\n");
|
||||
std::fprintf(stderr, " -slowboot: Force slow boot for provided filename.\n");
|
||||
std::fprintf(stderr, " -resume: Load resume save state. If a boot filename is provided,\n"
|
||||
@@ -64,10 +66,8 @@ static void PrintCommandLineHelp(const char* progname)
|
||||
static std::shared_ptr<VMBootParameters>& AutoBoot(std::shared_ptr<VMBootParameters>& autoboot)
|
||||
{
|
||||
if (!autoboot)
|
||||
{
|
||||
autoboot = std::make_shared<VMBootParameters>();
|
||||
autoboot->source_type = CDVD_SourceType::NoDisc;
|
||||
}
|
||||
|
||||
return autoboot;
|
||||
}
|
||||
|
||||
@@ -134,6 +134,12 @@ static bool ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr<VMBo
|
||||
AutoBoot(autoboot)->elf_override = argv[++i];
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-disc"))
|
||||
{
|
||||
AutoBoot(autoboot)->source_type = CDVD_SourceType::Disc;
|
||||
AutoBoot(autoboot)->filename = argv[++i];
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG("-fullscreen"))
|
||||
{
|
||||
Console.WriteLn("Going fullscreen after booting.");
|
||||
@@ -172,12 +178,10 @@ static bool ParseCommandLineOptions(int argc, char* argv[], std::shared_ptr<VMBo
|
||||
#undef CHECK_ARG_PARAM
|
||||
}
|
||||
|
||||
if (!AutoBoot(autoboot)->source.empty())
|
||||
AutoBoot(autoboot)->source += ' ';
|
||||
else
|
||||
AutoBoot(autoboot)->source_type = CDVD_SourceType::Iso;
|
||||
if (!AutoBoot(autoboot)->filename.empty())
|
||||
AutoBoot(autoboot)->filename += ' ';
|
||||
|
||||
AutoBoot(autoboot)->source += argv[i];
|
||||
AutoBoot(autoboot)->filename += argv[i];
|
||||
}
|
||||
|
||||
return true;
|
||||
|
||||
+55
-17
@@ -44,9 +44,15 @@
|
||||
#include "svnrev.h"
|
||||
|
||||
static constexpr char DISC_IMAGE_FILTER[] =
|
||||
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u);;Single-Track Raw Images (*.bin "
|
||||
"*.iso);;Cue Sheets (*.cue);;MAME CHD Images (*.chd);;CSO Images (*.cso);;"
|
||||
"ELF Executables (*.elf);;IRX Executables (*.irx);;Playlists (*.m3u)");
|
||||
QT_TRANSLATE_NOOP("MainWindow", "All File Types (*.bin *.iso *.cue *.chd *.cso *.elf *.irx *.m3u *.gs *.gs.xz);;"
|
||||
"Single-Track Raw Images (*.bin *.iso);;"
|
||||
"Cue Sheets (*.cue);;"
|
||||
"MAME CHD Images (*.chd);;"
|
||||
"CSO Images (*.cso);;"
|
||||
"ELF Executables (*.elf);;"
|
||||
"IRX Executables (*.irx);;"
|
||||
"Playlists (*.m3u);;"
|
||||
"GS Dumps (*.gs *.gs.xz)");
|
||||
|
||||
const char* MainWindow::DEFAULT_THEME_NAME = "darkfusion";
|
||||
|
||||
@@ -602,7 +608,10 @@ void MainWindow::clearProgressBar()
|
||||
m_ui.statusBar->removeWidget(m_status_progress_widget);
|
||||
}
|
||||
|
||||
bool MainWindow::isShowingGameList() const { return m_ui.mainContainer->currentIndex() == 0; }
|
||||
bool MainWindow::isShowingGameList() const
|
||||
{
|
||||
return m_ui.mainContainer->currentIndex() == 0;
|
||||
}
|
||||
|
||||
void MainWindow::switchToGameListView()
|
||||
{
|
||||
@@ -635,11 +644,20 @@ void MainWindow::switchToEmulationView()
|
||||
m_display_widget->setFocus();
|
||||
}
|
||||
|
||||
void MainWindow::refreshGameList(bool invalidate_cache) { m_game_list_widget->refresh(invalidate_cache); }
|
||||
void MainWindow::refreshGameList(bool invalidate_cache)
|
||||
{
|
||||
m_game_list_widget->refresh(invalidate_cache);
|
||||
}
|
||||
|
||||
void MainWindow::invalidateSaveStateCache() { m_save_states_invalidated = true; }
|
||||
void MainWindow::invalidateSaveStateCache()
|
||||
{
|
||||
m_save_states_invalidated = true;
|
||||
}
|
||||
|
||||
void MainWindow::reportError(const QString& title, const QString& message) { QMessageBox::critical(this, title, message); }
|
||||
void MainWindow::reportError(const QString& title, const QString& message)
|
||||
{
|
||||
QMessageBox::critical(this, title, message);
|
||||
}
|
||||
|
||||
bool MainWindow::confirmShutdown()
|
||||
{
|
||||
@@ -660,7 +678,10 @@ void MainWindow::requestExit()
|
||||
close();
|
||||
}
|
||||
|
||||
void Host::InvalidateSaveStateCache() { QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection); }
|
||||
void Host::InvalidateSaveStateCache()
|
||||
{
|
||||
QMetaObject::invokeMethod(g_main_window, &MainWindow::invalidateSaveStateCache, Qt::QueuedConnection);
|
||||
}
|
||||
|
||||
void MainWindow::onGameListRefreshProgress(const QString& status, int current, int total)
|
||||
{
|
||||
@@ -668,7 +689,10 @@ void MainWindow::onGameListRefreshProgress(const QString& status, int current, i
|
||||
setProgressBar(current, total);
|
||||
}
|
||||
|
||||
void MainWindow::onGameListRefreshComplete() { clearProgressBar(); }
|
||||
void MainWindow::onGameListRefreshComplete()
|
||||
{
|
||||
clearProgressBar();
|
||||
}
|
||||
|
||||
void MainWindow::onGameListSelectionChanged()
|
||||
{
|
||||
@@ -779,14 +803,13 @@ void MainWindow::onStartFileActionTriggered()
|
||||
return;
|
||||
|
||||
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
|
||||
VMManager::SetBootParametersForPath(filename.toStdString(), params.get());
|
||||
params->filename = filename.toStdString();
|
||||
g_emu_thread->startVM(std::move(params));
|
||||
}
|
||||
|
||||
void MainWindow::onStartBIOSActionTriggered()
|
||||
{
|
||||
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
|
||||
params->source_type = CDVD_SourceType::NoDisc;
|
||||
g_emu_thread->startVM(std::move(params));
|
||||
}
|
||||
|
||||
@@ -801,7 +824,10 @@ void MainWindow::onChangeDiscFromFileActionTriggered()
|
||||
g_emu_thread->changeDisc(filename);
|
||||
}
|
||||
|
||||
void MainWindow::onChangeDiscFromGameListActionTriggered() { switchToGameListView(); }
|
||||
void MainWindow::onChangeDiscFromGameListActionTriggered()
|
||||
{
|
||||
switchToGameListView();
|
||||
}
|
||||
|
||||
void MainWindow::onChangeDiscFromDeviceActionTriggered()
|
||||
{
|
||||
@@ -885,11 +911,20 @@ void MainWindow::onViewGamePropertiesActionTriggered()
|
||||
SettingsDialog::openGamePropertiesDialog(nullptr, m_current_game_crc);
|
||||
}
|
||||
|
||||
void MainWindow::onGitHubRepositoryActionTriggered() { QtUtils::OpenURL(this, AboutDialog::getGitHubRepositoryUrl()); }
|
||||
void MainWindow::onGitHubRepositoryActionTriggered()
|
||||
{
|
||||
QtUtils::OpenURL(this, AboutDialog::getGitHubRepositoryUrl());
|
||||
}
|
||||
|
||||
void MainWindow::onSupportForumsActionTriggered() { QtUtils::OpenURL(this, AboutDialog::getSupportForumsUrl()); }
|
||||
void MainWindow::onSupportForumsActionTriggered()
|
||||
{
|
||||
QtUtils::OpenURL(this, AboutDialog::getSupportForumsUrl());
|
||||
}
|
||||
|
||||
void MainWindow::onDiscordServerActionTriggered() { QtUtils::OpenURL(this, AboutDialog::getDiscordServerUrl()); }
|
||||
void MainWindow::onDiscordServerActionTriggered()
|
||||
{
|
||||
QtUtils::OpenURL(this, AboutDialog::getDiscordServerUrl());
|
||||
}
|
||||
|
||||
void MainWindow::onAboutActionTriggered()
|
||||
{
|
||||
@@ -1191,7 +1226,10 @@ void MainWindow::displayResizeRequested(qint32 width, qint32 height)
|
||||
resize(QSize(std::max<qint32>(width, 1), std::max<qint32>(height + extra_height, 1)));
|
||||
}
|
||||
|
||||
void MainWindow::destroyDisplay() { destroyDisplayWidget(); }
|
||||
void MainWindow::destroyDisplay()
|
||||
{
|
||||
destroyDisplayWidget();
|
||||
}
|
||||
|
||||
void MainWindow::focusDisplayWidget()
|
||||
{
|
||||
@@ -1398,7 +1436,7 @@ void MainWindow::loadSaveStateFile(const QString& filename, const QString& state
|
||||
else
|
||||
{
|
||||
std::shared_ptr<VMBootParameters> params = std::make_shared<VMBootParameters>();
|
||||
VMManager::SetBootParametersForPath(filename.toStdString(), params.get());
|
||||
params->filename = filename.toStdString();
|
||||
params->save_state = state_filename.toStdString();
|
||||
g_emu_thread->startVM(std::move(params));
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
@@ -156,7 +156,7 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget*
|
||||
// HW Upscaling Fixes
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.halfPixelOffset, "EmuCore/GS", "UserHacks_HalfPixelOffset", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.roundSprite, "EmuCore/GS", "UserHacks_RoundSprite", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.roundSprite, "EmuCore/GS", "UserHacks_round_sprite_offset", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.textureOffsetX, "EmuCore/GS", "UserHacks_TCOffsetX", 0);
|
||||
SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.textureOffsetY, "EmuCore/GS", "UserHacks_TCOffsetY", 0);
|
||||
SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.alignSprite, "EmuCore/GS", "UserHacks_align_sprite_X", false);
|
||||
|
||||
@@ -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,18 @@
|
||||
<item row="1" column="1">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout">
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawRangeStart"/>
|
||||
<widget class="QSpinBox" name="skipDrawStart">
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="skipDrawRangeCount"/>
|
||||
<widget class="QSpinBox" name="skipDrawEnd">
|
||||
<property name="maximum">
|
||||
<number>10000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
@@ -809,7 +817,11 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="textureOffsetX"/>
|
||||
<widget class="QSpinBox" name="textureOffsetX">
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QLabel" name="label_18">
|
||||
@@ -819,7 +831,11 @@
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QSpinBox" name="textureOffsetY"/>
|
||||
<widget class="QSpinBox" name="textureOffsetY">
|
||||
<property name="maximum">
|
||||
<number>1000</number>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
|
||||
@@ -983,6 +983,7 @@ if(PCSX2_CORE)
|
||||
Frontend/InputManager.cpp
|
||||
Frontend/InputSource.cpp
|
||||
Frontend/LayeredSettingsInterface.cpp
|
||||
GSDumpReplayer.cpp
|
||||
HostSettings.cpp
|
||||
VMManager.cpp
|
||||
)
|
||||
@@ -992,6 +993,7 @@ if(PCSX2_CORE)
|
||||
Frontend/InputManager.h
|
||||
Frontend/InputSource.h
|
||||
Frontend/LayeredSettingsInterface.h
|
||||
GSDumpReplayer.h
|
||||
HostSettings.h
|
||||
VMManager.h)
|
||||
endif()
|
||||
|
||||
+9
-4
@@ -58,6 +58,7 @@ enum SpeedhackId
|
||||
|
||||
Speedhack_mvuFlag = SpeedhackId_FIRST,
|
||||
Speedhack_InstantVU1,
|
||||
Speedhack_MTVU,
|
||||
|
||||
SpeedhackId_COUNT
|
||||
};
|
||||
@@ -441,7 +442,8 @@ struct Pcsx2Config
|
||||
WrapGSMem : 1,
|
||||
Mipmap : 1,
|
||||
AA1 : 1,
|
||||
UserHacks : 1,
|
||||
PointListPalette : 1,
|
||||
ManualUserHacks : 1,
|
||||
UserHacks_AlignSpriteX : 1,
|
||||
UserHacks_AutoFlush : 1,
|
||||
UserHacks_CPUFBConversion : 1,
|
||||
@@ -509,8 +511,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};
|
||||
@@ -543,6 +545,9 @@ struct Pcsx2Config
|
||||
/// Sets user hack values to defaults when user hacks are not enabled.
|
||||
void MaskUserHacks();
|
||||
|
||||
/// Sets user hack values to defaults when upscaling is not enabled.
|
||||
void MaskUpscalingHacks();
|
||||
|
||||
/// Returns true if any of the hardware renderers are selected.
|
||||
bool UseHardwareRenderer() const;
|
||||
|
||||
@@ -668,7 +673,7 @@ struct Pcsx2Config
|
||||
* which is 2^32 * 512 byte sectors
|
||||
* Note that we don't yet support
|
||||
* 48bit LBA, so our limit is lower */
|
||||
uint HddSizeSectors{0};
|
||||
uint HddSizeSectors{40 * (1024 * 1024 * 1024 / 512)};
|
||||
|
||||
DEV9Options();
|
||||
|
||||
|
||||
+45
-7
@@ -48,7 +48,7 @@ SERIAL-12345: # !required! Serial number for the game, this is how games are loo
|
||||
memcardFilters:
|
||||
- "SERIAL-123"
|
||||
- "SERIAL-456"
|
||||
# You can define multple patches, but they are identified by the CRC.
|
||||
# You can define multiple patches, but they are identified by the CRC.
|
||||
patches:
|
||||
default: # Default CRC!
|
||||
author: "Some Person"
|
||||
@@ -124,6 +124,44 @@ The clamp modes are also numerically based.
|
||||
* `2` = Clamp **Extra** (clamp results as well as operands)
|
||||
* `3` = Clamp **Extra+Preserve Sign**
|
||||
|
||||
## GS Hardware Fixes
|
||||
|
||||
[ ] = GameDB values
|
||||
{ } = GUI options
|
||||
( ) = Default values
|
||||
|
||||
### GS Hardware Mipmap Fixes
|
||||
|
||||
* mipmap [`0` or `1` or `2`] {Off, Basic, Full} Default: Automatic (No value, looks up GameDB)
|
||||
* trilinearFiltering [`0` or `1` or `2`] {None, Trilinear, Trilinear Ultra} Default: None (`0`)
|
||||
|
||||
### GS Hardware General Fixes
|
||||
|
||||
* conservativeFramebuffer [`0` or `1`] {Off or On} Default: On (`1`)
|
||||
* texturePreloading [`0` or `1` or `2`] {None, Partial or Full Hash Cache} Default: None (`0`)
|
||||
|
||||
### GS Hardware Renderer Fixes
|
||||
|
||||
* autoFlush [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* disableDepthSupport [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* fastTextureInvalidation [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* cpuFramebufferConversion [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* wrapGSMem [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* preloadFrameData [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* textureInsideRT [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* halfBottomOverride [`0` or `1`] {Force-Disabled or Force-Enabled} Default: Automatic (No value, looks up GameDB)
|
||||
* pointListPalette [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
|
||||
### GS Hardware Upscaling Fixes
|
||||
|
||||
* alignSprite [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* mergeSprite [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* wildArmsHack [`0` or `1`] {Off or On} Default: Off (`0`)
|
||||
* skipDrawStart [Value between `0` to `100000`] {0-100000} Default: Off (`0`)
|
||||
* skipDrawEnd [Value between `0` to `100000`] {0-100000} Default: Off (`0`)
|
||||
* halfPixelOffset [`0` or `1` or `2` or `3`] {Off, Normal Vertex, Special Texture or Special Texture Aggressive} Default: Off (`0`)
|
||||
* roundSprite [`0` or `1` or `2`] {Off, Half or Full} Default: Off (`0`)
|
||||
|
||||
## Game Fixes
|
||||
|
||||
These values are case-sensitive so take care. If you incorrectly specify a GameFix, you will get a validation error on startup. Any invalid game-fixes will be dropped from the game's list of fixes.
|
||||
@@ -184,12 +222,12 @@ These values are in a key-value format, where the value is assumed to be an inte
|
||||
* `mvuFlagSpeedHack`
|
||||
* Accepted Values - `0` / `1`
|
||||
* Katamari Damacy have weird speed bug when this speed hack is enabled (and it is by default)
|
||||
|
||||
<!-- [list-item-spacing] Missing new line after list item->
|
||||
|
||||
- `InstantVU1SpeedHack`
|
||||
- Accepted Values - `0` / `1`
|
||||
- Games such as Parappa the Rapper 2 need VU1 to sync, so you can force disable the speedhack here
|
||||
* `MTVUSpeedHack`
|
||||
* Accepted Values - `0` / `1`
|
||||
* T-bit games dislike MTVU and some games are incompatible with MTVU.
|
||||
* `InstantVU1SpeedHack`
|
||||
* Accepted Values - `0` / `1`
|
||||
* Games such as Parappa the Rapper 2 need VU1 to sync, so you can force sync with this parameter.
|
||||
|
||||
## Memory Card Filter Override
|
||||
|
||||
|
||||
@@ -123,19 +123,19 @@ void GameList::FillBootParametersForEntry(VMBootParameters* params, const Entry*
|
||||
{
|
||||
if (entry->type == GameList::EntryType::PS1Disc || entry->type == GameList::EntryType::PS2Disc)
|
||||
{
|
||||
params->source = entry->path;
|
||||
params->filename = entry->path;
|
||||
params->source_type = CDVD_SourceType::Iso;
|
||||
params->elf_override.clear();
|
||||
}
|
||||
else if (entry->type == GameList::EntryType::ELF)
|
||||
{
|
||||
params->source.clear();
|
||||
params->filename.clear();
|
||||
params->source_type = CDVD_SourceType::NoDisc;
|
||||
params->elf_override = entry->path;
|
||||
}
|
||||
else
|
||||
{
|
||||
params->source.clear();
|
||||
params->filename.clear();
|
||||
params->source_type = CDVD_SourceType::NoDisc;
|
||||
params->elf_override.clear();
|
||||
}
|
||||
@@ -568,6 +568,17 @@ const GameList::Entry* GameList::GetEntryForPath(const char* path)
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GameList::Entry* GameList::GetEntryByCRC(u32 crc)
|
||||
{
|
||||
for (const Entry& entry : m_entries)
|
||||
{
|
||||
if (entry.crc == crc)
|
||||
return &entry;
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const GameList::Entry* GameList::GetEntryBySerialAndCRC(const std::string_view& serial, u32 crc)
|
||||
{
|
||||
for (const Entry& entry : m_entries)
|
||||
|
||||
@@ -80,6 +80,7 @@ namespace GameList
|
||||
std::unique_lock<std::recursive_mutex> GetLock();
|
||||
const Entry* GetEntryByIndex(u32 index);
|
||||
const Entry* GetEntryForPath(const char* path);
|
||||
const Entry* GetEntryByCRC(u32 crc);
|
||||
const Entry* GetEntryBySerialAndCRC(const std::string_view& serial, u32 crc);
|
||||
u32 GetEntryCount();
|
||||
|
||||
|
||||
@@ -630,3 +630,17 @@ void ImGuiManager::RenderOSD()
|
||||
DrawOSDMessages();
|
||||
}
|
||||
|
||||
float ImGuiManager::GetGlobalScale()
|
||||
{
|
||||
return s_global_scale;
|
||||
}
|
||||
|
||||
ImFont* ImGuiManager::GetStandardFont()
|
||||
{
|
||||
return s_standard_font;
|
||||
}
|
||||
|
||||
ImFont* ImGuiManager::GetFixedFont()
|
||||
{
|
||||
return s_fixed_font;
|
||||
}
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
struct ImFont;
|
||||
|
||||
namespace ImGuiManager
|
||||
{
|
||||
/// Initializes ImGui, creates fonts, etc.
|
||||
@@ -34,5 +36,14 @@ namespace ImGuiManager
|
||||
|
||||
/// Renders any on-screen display elements.
|
||||
void RenderOSD();
|
||||
|
||||
/// Returns the scale of all on-screen elements.
|
||||
float GetGlobalScale();
|
||||
|
||||
/// Returns the standard font for external drawing.
|
||||
ImFont* GetStandardFont();
|
||||
|
||||
/// Returns the fixed-width font for external drawing.
|
||||
ImFont* GetFixedFont();
|
||||
} // namespace ImGuiManager
|
||||
|
||||
|
||||
+17
-27
@@ -310,7 +310,6 @@ bool GSopen(const Pcsx2Config::GSOptions& config, GSRendererType renderer, u8* b
|
||||
|
||||
GSConfig = config;
|
||||
GSConfig.Renderer = renderer;
|
||||
GSConfig.MaskUserHacks();
|
||||
|
||||
if (!Host::AcquireHostDisplay(GetAPIForRenderer(renderer)))
|
||||
{
|
||||
@@ -723,7 +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();
|
||||
if (!s_gs)
|
||||
return;
|
||||
|
||||
@@ -758,21 +756,6 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
|
||||
// Options which aren't using the global struct yet, so we need to recreate all GS objects.
|
||||
if (
|
||||
GSConfig.ConservativeFramebuffer != old_config.ConservativeFramebuffer ||
|
||||
GSConfig.AutoFlushSW != old_config.AutoFlushSW ||
|
||||
GSConfig.PreloadFrameWithGSData != old_config.PreloadFrameWithGSData ||
|
||||
GSConfig.WrapGSMem != old_config.WrapGSMem ||
|
||||
GSConfig.Mipmap != old_config.Mipmap ||
|
||||
GSConfig.AA1 != old_config.AA1 ||
|
||||
GSConfig.UserHacks_AlignSpriteX != old_config.UserHacks_AlignSpriteX ||
|
||||
GSConfig.UserHacks_AutoFlush != old_config.UserHacks_AutoFlush ||
|
||||
GSConfig.UserHacks_CPUFBConversion != old_config.UserHacks_CPUFBConversion ||
|
||||
GSConfig.UserHacks_DisableDepthSupport != old_config.UserHacks_DisableDepthSupport ||
|
||||
GSConfig.UserHacks_DisablePartialInvalidation != old_config.UserHacks_DisablePartialInvalidation ||
|
||||
GSConfig.UserHacks_DisableSafeFeatures != old_config.UserHacks_DisableSafeFeatures ||
|
||||
GSConfig.UserHacks_MergePPSprite != old_config.UserHacks_MergePPSprite ||
|
||||
GSConfig.UserHacks_WildHack != old_config.UserHacks_WildHack ||
|
||||
GSConfig.UserHacks_TextureInsideRt != old_config.UserHacks_TextureInsideRt ||
|
||||
GSConfig.DumpGSData != old_config.DumpGSData ||
|
||||
GSConfig.SaveRT != old_config.SaveRT ||
|
||||
GSConfig.SaveFrame != old_config.SaveFrame ||
|
||||
@@ -784,12 +767,6 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
GSConfig.SWExtraThreads != old_config.SWExtraThreads ||
|
||||
GSConfig.SWExtraThreadsHeight != old_config.SWExtraThreadsHeight ||
|
||||
|
||||
GSConfig.UserHacks_HalfBottomOverride != old_config.UserHacks_HalfBottomOverride ||
|
||||
GSConfig.UserHacks_HalfPixelOffset != old_config.UserHacks_HalfPixelOffset ||
|
||||
GSConfig.UserHacks_RoundSprite != old_config.UserHacks_RoundSprite ||
|
||||
GSConfig.UserHacks_TCOffsetX != old_config.UserHacks_TCOffsetX ||
|
||||
GSConfig.UserHacks_TCOffsetY != old_config.UserHacks_TCOffsetY ||
|
||||
|
||||
GSConfig.ShadeBoost_Brightness != old_config.ShadeBoost_Brightness ||
|
||||
GSConfig.ShadeBoost_Contrast != old_config.ShadeBoost_Contrast ||
|
||||
GSConfig.ShadeBoost_Saturation != old_config.ShadeBoost_Saturation ||
|
||||
@@ -806,17 +783,29 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config)
|
||||
// This is where we would do finer-grained checks in the future.
|
||||
// For example, flushing the texture cache when mipmap settings change.
|
||||
|
||||
if (GSConfig.HWMipmap != old_config.HWMipmap || GSConfig.CRCHack != old_config.CRCHack)
|
||||
if (GSConfig.CRCHack != old_config.CRCHack ||
|
||||
GSConfig.PointListPalette != old_config.PointListPalette)
|
||||
{
|
||||
// for automatic mipmaps, we need to reload the crc
|
||||
s_gs->SetGameCRC(s_gs->GetGameCRC(), s_gs->GetGameCRCOptions());
|
||||
}
|
||||
|
||||
// reload texture cache when trilinear filtering or mipmap options change
|
||||
if (GSConfig.HWMipmap != old_config.HWMipmap ||
|
||||
// renderer-specific options (e.g. auto flush, TC offset)
|
||||
s_gs->UpdateSettings(old_config);
|
||||
|
||||
// reload texture cache when trilinear filtering or TC options change
|
||||
if (
|
||||
(GSConfig.UseHardwareRenderer() && GSConfig.HWMipmap != old_config.HWMipmap) ||
|
||||
GSConfig.ConservativeFramebuffer != old_config.ConservativeFramebuffer ||
|
||||
GSConfig.TexturePreloading != old_config.TexturePreloading ||
|
||||
GSConfig.UserHacks_TriFilter != old_config.UserHacks_TriFilter ||
|
||||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion)
|
||||
GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion ||
|
||||
GSConfig.PreloadFrameWithGSData != old_config.PreloadFrameWithGSData ||
|
||||
GSConfig.WrapGSMem != old_config.WrapGSMem ||
|
||||
GSConfig.UserHacks_CPUFBConversion != old_config.UserHacks_CPUFBConversion ||
|
||||
GSConfig.UserHacks_DisableDepthSupport != old_config.UserHacks_DisableDepthSupport ||
|
||||
GSConfig.UserHacks_DisablePartialInvalidation != old_config.UserHacks_DisablePartialInvalidation ||
|
||||
GSConfig.UserHacks_TextureInsideRt != old_config.UserHacks_TextureInsideRt)
|
||||
{
|
||||
s_gs->PurgeTextureCache();
|
||||
s_gs->PurgePool();
|
||||
@@ -1358,6 +1347,7 @@ void GSApp::Init()
|
||||
m_default_configuration["override_GL_ARB_texture_barrier"] = "-1";
|
||||
m_default_configuration["paltex"] = "0";
|
||||
m_default_configuration["png_compression_level"] = std::to_string(Z_BEST_SPEED);
|
||||
m_default_configuration["PointListPalette"] = "0";
|
||||
m_default_configuration["PrecacheTextureReplacements"] = "0";
|
||||
m_default_configuration["preload_frame_with_gs_data"] = "0";
|
||||
m_default_configuration["Renderer"] = std::to_string(static_cast<int>(GSRendererType::Auto));
|
||||
|
||||
+33
-185
@@ -26,14 +26,7 @@ const CRC::Game CRC::m_games[] =
|
||||
{0x68CE6801, ArTonelico2, JP, 0},
|
||||
{0xCE2C1DBF, ArTonelico2, EU, 0},
|
||||
{0x2113EA2E, MetalSlug6, JP, 0},
|
||||
{0x42E05BAF, TomoyoAfter, JP, PointListPalette},
|
||||
{0x7800DC84, Clannad, JP, PointListPalette},
|
||||
{0xA6167B59, Lamune, JP, PointListPalette},
|
||||
{0xDDB59F46, KyuuketsuKitanMoonties, JP, PointListPalette},
|
||||
{0xC8EE2562, PiaCarroteYoukosoGPGakuenPrincess, JP, PointListPalette},
|
||||
{0x6CF94A43, KazokuKeikakuKokoroNoKizuna, JP, PointListPalette},
|
||||
{0xEDAF602D, DuelSaviorDestiny, JP, PointListPalette},
|
||||
{0xBB63D785, OneTwentyYenStories, JP, PointListPalette}, // no Haru: 120 Yen Stories
|
||||
{0xA6167B59, Lamune, JP, 0},
|
||||
{0xA39517AB, FFX, EU, 0},
|
||||
{0x78D83FD5, FFX, EU, 0}, // Demo
|
||||
{0xA39517AE, FFX, FR, 0},
|
||||
@@ -94,12 +87,6 @@ const CRC::Game CRC::m_games[] =
|
||||
{0x71521863, SFEX3, US, 0},
|
||||
{0x63642E9F, SFEX3, JP, 0},
|
||||
{0xCA1F6E53, SFEX3, JP, 0}, // Taikenban Disc (Demo/Trial)
|
||||
{0xC19A374E, SoTC, US, 0},
|
||||
{0x7D8F539A, SoTC, EU, 0},
|
||||
{0x0F0C4A9C, SoTC, EU, 0},
|
||||
{0x877F3436, SoTC, JP, 0},
|
||||
{0xA17D6AAA, SoTC, KO, 0},
|
||||
{0x877B3D35, SoTC, CH, 0},
|
||||
{0x6F8545DB, ICO, US, 0},
|
||||
{0x48CDF317, ICO, US, 0}, // Demo
|
||||
{0xB01A4C95, ICO, JP, 0},
|
||||
@@ -163,9 +150,6 @@ const CRC::Game CRC::m_games[] =
|
||||
{0xA32F7CD0, AceCombat4, US, 0}, // Also needed for automatic mipmapping
|
||||
{0x5ED8FB53, AceCombat4, JP, 0},
|
||||
{0x1B9B7563, AceCombat4, EU, 0},
|
||||
{0x39B574F0, AceCombat5, US, 0}, // The Unsung War
|
||||
{0x86089F31, AceCombat5, JP, 0},
|
||||
{0x1D54FEA9, AceCombat5, EU, 0}, // Squadron Leader
|
||||
{0xFC46EA61, Tekken5, JP, 0},
|
||||
{0x1F88EE37, Tekken5, EU, 0},
|
||||
{0x1F88BECD, Tekken5, EU, 0}, // language selector...
|
||||
@@ -206,7 +190,6 @@ const CRC::Game CRC::m_games[] =
|
||||
{0xA3643EB1, GiTS, KO, 0},
|
||||
{0x28557423, GiTS, RU, 0},
|
||||
{0xBF6F101F, GiTS, EU, 0}, // same CRC as another US disc
|
||||
{0xF442260C, MajokkoALaMode2, JP, PointListPalette},
|
||||
{0xA616A6C2, TalesOfAbyss, US, 0},
|
||||
{0x14FE77F7, TalesOfAbyss, US, 0},
|
||||
{0xAA5EC3A3, TalesOfAbyss, JP, 0},
|
||||
@@ -243,10 +226,10 @@ const CRC::Game CRC::m_games[] =
|
||||
{0xC5B75C7C, Oneechanbara2Special, JP, 0},
|
||||
{0xC725CC6C, Oneechanbara2Special, JP, 0},
|
||||
{0x07608CA2, Oneechanbara2Special, EU, 0}, // Zombie Hunters 2
|
||||
{0xE0347841, XenosagaE3, JP, TextureInsideRt},
|
||||
{0xA707236E, XenosagaE3, JP, TextureInsideRt}, // Demo
|
||||
{0xA4E88698, XenosagaE3, CH, TextureInsideRt},
|
||||
{0x2088950A, XenosagaE3, US, TextureInsideRt},
|
||||
{0xE0347841, XenosagaE3, JP, 0},
|
||||
{0xA707236E, XenosagaE3, JP, 0}, // Demo
|
||||
{0xA4E88698, XenosagaE3, CH, 0},
|
||||
{0x2088950A, XenosagaE3, US, 0},
|
||||
{0xB1995E29, ShadowofRome, EU, 0},
|
||||
{0x958DCA28, ShadowofRome, EU, 0},
|
||||
{0x57818AF6, ShadowofRome, US, 0},
|
||||
@@ -315,14 +298,14 @@ const CRC::Game CRC::m_games[] =
|
||||
{0x812C5A96, ShinOnimusha, EU, 0},
|
||||
{0xFE44479E, ShinOnimusha, US, 0},
|
||||
{0xFFDE85E9, ShinOnimusha, US, 0},
|
||||
{0xE21404E2, GetawayGames, US, TextureInsideRt}, // Getaway
|
||||
{0xE8249852, GetawayGames, JP, TextureInsideRt}, // Getaway
|
||||
{0x458485EF, GetawayGames, EU, TextureInsideRt}, // Getaway
|
||||
{0x5DFBE144, GetawayGames, EU, TextureInsideRt}, // Getaway
|
||||
{0xE78971DF, GetawayGames, US, TextureInsideRt}, // GetawayBlackMonday
|
||||
{0x342D97FA, GetawayGames, US, TextureInsideRt}, // GetawayBlackMonday Demo
|
||||
{0xE8C0AD1A, GetawayGames, JP, TextureInsideRt}, // GetawayBlackMonday
|
||||
{0x09C3DF79, GetawayGames, EU, TextureInsideRt}, // GetawayBlackMonday
|
||||
{0xE21404E2, GetawayGames, US, 0}, // Getaway
|
||||
{0xE8249852, GetawayGames, JP, 0}, // Getaway
|
||||
{0x458485EF, GetawayGames, EU, 0}, // Getaway
|
||||
{0x5DFBE144, GetawayGames, EU, 0}, // Getaway
|
||||
{0xE78971DF, GetawayGames, US, 0}, // GetawayBlackMonday
|
||||
{0x342D97FA, GetawayGames, US, 0}, // GetawayBlackMonday Demo
|
||||
{0xE8C0AD1A, GetawayGames, JP, 0}, // GetawayBlackMonday
|
||||
{0x09C3DF79, GetawayGames, EU, 0}, // GetawayBlackMonday
|
||||
{0x1130BF23, SakuraTaisen, CH, 0},
|
||||
{0x4FAE8B83, SakuraTaisen, KO, 0},
|
||||
{0xEF06DBD6, SakuraWarsSoLongMyLove, JP, 0},
|
||||
@@ -350,161 +333,26 @@ const CRC::Game CRC::m_games[] =
|
||||
{0XE1BF5DCA, SuperManReturns, US, 0},
|
||||
{0XE8F7BAB6, SuperManReturns, EU, 0},
|
||||
{0x06A7506A, SacredBlaze, JP, 0},
|
||||
{0x9C712FF0, Jak1, EU, TextureInsideRt}, // Jak and Daxter: The Precursor Legacy
|
||||
{0x1B3976AB, Jak1, US, TextureInsideRt},
|
||||
{0x472E7699, Jak1, US, TextureInsideRt}, // Greatest Hits
|
||||
{0x96A608C5, Jak1, US, TextureInsideRt}, // Cingular Wireless Demo, PS Underground Demo
|
||||
{0xEDE4FE64, Jak1, JP, TextureInsideRt}, // Jak x Daxter: Kyuusekai no Isan
|
||||
{0x2A7FD3B4, Jak1, JP, TextureInsideRt}, // Demo
|
||||
{0x2479F4A9, Jak2, EU, TextureInsideRt},
|
||||
{0xF41C1B29, Jak2, EU, TextureInsideRt}, // Demo
|
||||
{0x9184AAF1, Jak2, US, TextureInsideRt},
|
||||
{0xA2034C69, Jak2, US, TextureInsideRt}, // Demo
|
||||
{0x25FE4D23, Jak2, KO, TextureInsideRt},
|
||||
{0xB4976DAF, Jak2, JP, TextureInsideRt}, // Jak II: Jak x Daxter 2
|
||||
{0x43D4FF3E, Jak2, JP, TextureInsideRt}, // Demo
|
||||
{0x12804727, Jak3, EU, TextureInsideRt},
|
||||
{0xE59E10BF, Jak3, EU, TextureInsideRt},
|
||||
{0xCA68E4D5, Jak3, EU, TextureInsideRt}, // Demo
|
||||
{0x644CFD03, Jak3, US, TextureInsideRt},
|
||||
{0xD401BC20, Jak3, US, TextureInsideRt}, // Demo
|
||||
{0xD1368EAE, Jak3, KO, TextureInsideRt},
|
||||
{0xDF659E77, JakX, EU, TextureInsideRt}, // Jak X: Combat Racing
|
||||
{0xC20596DB, JakX, EU, TextureInsideRt}, // Beta Trial Disc, v0.01
|
||||
{0x3091E6FB, JakX, US, TextureInsideRt},
|
||||
{0xC417D919, JakX, US, TextureInsideRt}, // Demo
|
||||
{0xDA366A53, JakX, US, TextureInsideRt}, // Public Beta v.1
|
||||
{0x7B564230, JakX, US, TextureInsideRt}, // Jak and Daxter Complete Trilogy Demo
|
||||
{0xDBA28C59, JakX, US, TextureInsideRt}, // Greatest Hits
|
||||
{0x4653CA3E, HarleyDavidson, US, 0},
|
||||
// Games list for Automatic Mipmapping
|
||||
// Basic mipmapping
|
||||
{0x194C9F38, AceCombatZero, EU, 0}, // Ace Combat: The Belkan War
|
||||
{0x65729657, AceCombatZero, US, 0},
|
||||
{0xA04B52DB, AceCombatZero, JP, 0},
|
||||
{0x2799A4E5, AceCombatZero, KO, 0},
|
||||
{0x09B3AD4D, ApeEscape2, EU, 0},
|
||||
{0xADCDCB88, ApeEscape2, EU, 0}, // Spanish version
|
||||
{0xBBB21612, ApeEscape2, DE, 0},
|
||||
{0xE2B8D3B2, ApeEscape2, IT, 0},
|
||||
{0x8B6FE2EA, ApeEscape2, FR, 0},
|
||||
{0xBDD9F5E1, ApeEscape2, US, 0},
|
||||
{0xFE0A6AB6, ApeEscape2, JP, 0}, // Saru! Get You! 2
|
||||
{0x64A9982B, ApeEscape2, CH, 0},
|
||||
{0xEC8EF2DE, Barnyard, US, 0}, // Nickelodeon: Barnyard
|
||||
{0x0B2F3DEE, Barnyard, KO, 0},
|
||||
{0x5267A845, Barnyard, EU, 0},
|
||||
{0x0940508D, BrianLaraInternationalCricket, EU, 0},
|
||||
{0x0BAA8DD8, DarkCloud, EU, 0},
|
||||
{0x1DF41F33, DarkCloud, US, 0},
|
||||
{0xA5C05C78, DarkCloud, US, 0},
|
||||
{0x60AA5049, DarkCloud, KO, 0},
|
||||
{0xECD8E386, DarkCloud, JP, 0},
|
||||
{0x67A29886, DestroyAllHumans, US, 0},
|
||||
{0xE3E8E893, DestroyAllHumans, EU, 0},
|
||||
{0x42DF8C8C, DestroyAllHumans2, US, 0},
|
||||
{0x743E10C2, DestroyAllHumans2, EU, 0},
|
||||
{0x67C38BAA, FIFA03, US, 0},
|
||||
{0x722BBD62, FIFA03, EU, 0},
|
||||
{0x2BCCF704, FIFA03, EU, 0},
|
||||
{0xCC6AA742, FIFA04, KO, 0},
|
||||
{0x2C6A4E2E, FIFA04, US, 0},
|
||||
{0x684ADFC6, FIFA04, EU, 0},
|
||||
{0x972611BB, FIFA05, US, 0},
|
||||
{0x972719A3, FIFA05, EU, 0},
|
||||
{0xC5473413, HarryPotterATCOS, NoRegion, 0}, // EU and US versions have the same CRC - Chamber Of Secrets
|
||||
{0xE1963055, HarryPotterATCOS, JP, 0}, // Harry Potter to Himitsu no Heya
|
||||
{0xE90BE9F8, HarryPotterATCOS, JP, 0}, // Coca Cola original Version
|
||||
{0xB38CC628, HarryPotterATGOF, US, 0},
|
||||
{0xCDE017A7, HarryPotterATGOF, KO, 0},
|
||||
{0xB18DC525, HarryPotterATGOF, EU, 0},
|
||||
{0x9C3A84F4, HarryPotterATHBP, US, 0}, // Half-Blood Prince
|
||||
{0xCB598BC2, HarryPotterATHBP, EU, 0},
|
||||
{0x51E019BC, HarryPotterATPOA, NoRegion, 0}, // EU and US versions have the same CRC - Prisoner of Azkaban
|
||||
{0x99A8B4FF, HarryPotterATPOA, KO, 0},
|
||||
{0xA8901AD6, HarryPotterATPOA, JP, 0}, // Harry Potter to Azkaban no Shuujin
|
||||
{0x51E417AA, HarryPotterATPOA, EU, 0},
|
||||
{0x4C01B1B0, HarryPotterOOTP, US, 0}, // Order Of The Phoenix
|
||||
{0x01A9BF0E, HarryPotterOOTP, EU, 0},
|
||||
{0x960FFA6A, JurassicPark, EU, 0},
|
||||
{0xA99B8FE7, JurassicPark, US, 0},
|
||||
{0x230CB71D, SoulReaver2, US, 0},
|
||||
{0x1771BFE4, SoulReaver2, US, 0},
|
||||
{0x6F991F52, SoulReaver2, JP, 0},
|
||||
{0x1B7FF35A, SoulReaver2, KO, 0},
|
||||
{0x6D8B4CD1, SoulReaver2, EU, 0},
|
||||
{0x728AB07C, LegacyOfKainDefiance, US, 0},
|
||||
{0xBCAD1E8A, LegacyOfKainDefiance, EU, 0},
|
||||
{0x28D09BF9, NicktoonsUnite, US, 0},
|
||||
{0xF25266C4, NicktoonsUnite, EU, 0}, // Nickelodeon SpongeBob SquarePants And Friends Unite
|
||||
{0x7AE1C04B, Persona3, US, 0}, // Regular Version
|
||||
{0x05C3D28F, Persona3, JP, 0},
|
||||
{0xBCD68B1E, Persona3, KO, 0},
|
||||
{0x8A557EE5, Persona3, EU, 0},
|
||||
{0x94A82AAA, Persona3, US, 0}, // FES
|
||||
{0x232C7D72, Persona3, JP, 0},
|
||||
{0x8897C208, Persona3, KO, 0},
|
||||
{0xF64A6AE5, Persona3, EU, 0},
|
||||
{0x2BDA8ADB, ProjectSnowblind, US, 0},
|
||||
{0xF00CA82B, ProjectSnowblind, EU, 0},
|
||||
{0xF1583665, ProjectSnowblind, EU, 0},
|
||||
{0xA56A0525, Quake3Revolution, US, 0},
|
||||
{0x2064ACE6, Quake3Revolution, EU, 0},
|
||||
{0xCE4933D0, RatchetAndClank, US, 0},
|
||||
{0x6F191506, RatchetAndClank, US, 0}, // E3 Demo
|
||||
{0x81CBFEA2, RatchetAndClank, US, 0}, // EB Games Demo
|
||||
{0x56A35F77, RatchetAndClank, JP, 0},
|
||||
{0x76F724A3, RatchetAndClank, EU, 0},
|
||||
{0x6A8F18B9, RatchetAndClank, EU, 0},
|
||||
{0x5C19F3B7, RatchetAndClank, EU, 0}, // Regular Demo
|
||||
{0xB3A71D10, RatchetAndClank2, US, 0}, // Going Commando
|
||||
{0x38996035, RatchetAndClank2, US, 0},
|
||||
{0xF700EE7E, RatchetAndClank2, US, 0}, // Regular Demo
|
||||
{0xF67ADF58, RatchetAndClank2, US, 0}, // Retail Employees Demo
|
||||
{0xDF6F94A1, RatchetAndClank2, US, 0}, // Demo - Going Commando & Jak II
|
||||
{0x89A26EC9, RatchetAndClank2, KO, 0},
|
||||
{0x8CAA5F16, RatchetAndClank2, JP, 0}, // Gagaga! Ginga no Commando-ssu
|
||||
{0x2F486E6F, RatchetAndClank2, EU, 0},
|
||||
{0x45FE0CC4, RatchetAndClank3, US, 0}, // Up Your Arsenal
|
||||
{0x2A12175A, RatchetAndClank3, US, 0}, // Regular Demo
|
||||
{0xCC53C3B4, RatchetAndClank3, US, 0}, // Public Beta v1.0
|
||||
{0x9FCC4BA4, RatchetAndClank3, KO, 0},
|
||||
{0x64DC6000, RatchetAndClank3, JP, 0}, // Totsugeki! Galactic Rangers
|
||||
{0x17125698, RatchetAndClank3, EU, 0},
|
||||
{0x4A85FC67, RatchetAndClank3, EU, 0}, // Beta Trial Disc
|
||||
{0x9BFBCD42, RatchetAndClank4, US, 0}, // Deadlocked
|
||||
{0xD301186D, RatchetAndClank4, US, 0}, // Regular Demo
|
||||
{0xBDF8A887, RatchetAndClank4, US, 0}, // Public Beta v.1
|
||||
{0x529BC7CC, RatchetAndClank4, KO, 0},
|
||||
{0x2EC9DA96, RatchetAndClank4, JP, 0}, // GiriGiri Ginga no Giga Battle
|
||||
{0x76975025, RatchetAndClank4, JP, 0}, // Regular Demo
|
||||
{0xD697D204, RatchetAndClank4, EU, 0}, // Ratchet Gladiator
|
||||
{0x8661F7BA, RatchetAndClank5, US, 0}, // Size Matters
|
||||
{0x7029DCE6, RatchetAndClank5, KO, 0},
|
||||
{0x9ADCF7AF, RatchetAndClank5, JP, 0}, // Gekitotsu! Dodeka Ginga no Miri Miri Gundan
|
||||
{0xFCB981D5, RatchetAndClank5, EU, 0},
|
||||
{0x8634861F, RickyPontingInternationalCricket, EU, 0},
|
||||
{0xDDAC3815, Shox, US, 0},
|
||||
{0xF84FE9DE, Shox, KO, 0},
|
||||
{0x09F4038B, Shox, EU, 0},
|
||||
{0x78FFA39F, Shox, EU, 0},
|
||||
{0x3DF10389, Shox, EU, 0},
|
||||
{0x43CC009B, SlamTennis, EU, 0},
|
||||
{0xF17AF8BD, TheIncredibleHulkUD, US, 0},
|
||||
{0xEA8D4BDF, TheIncredibleHulkUD, US, 0},
|
||||
{0x6B3D50A5, TheIncredibleHulkUD, EU, 0},
|
||||
{0x2B58234D, TribesAerialAssault, US, 0},
|
||||
{0x4D22DB95, Whiplash, US, 0},
|
||||
{0xE8A97250, Whiplash, EU, 0},
|
||||
{0xB1BE3E51, Whiplash, EU, 0},
|
||||
{0x4C33FA2A, IndianaJonesAndTheEmperorsTomb, US, TextureInsideRt}, // TODO Add more CRCs (https://pcsx2.net/compatibility-list.html).
|
||||
{0xAE0E098F, IndianaJonesAndTheEmperorsTomb, DE, TextureInsideRt},
|
||||
{0xBBC3EFFA, WildArms4, US, TextureInsideRt}, // TODO Add more CRCs (https://pcsx2.net/compatibility-list.html).
|
||||
{0x36802E57, BeyondGoodAndEvil, US, TextureInsideRt}, // TODO Add more CRCs (https://pcsx2.net/compatibility-list.html).
|
||||
{0x08FFF00D, SSX3, US, TextureInsideRt},
|
||||
{0xCE942B2A, SSX3, EU, TextureInsideRt}, // TODO Add more CRCs (https://pcsx2.net/compatibility-list.html).
|
||||
{0x1FCC0CFB, DrivingEmotionTypeS, US, TextureInsideRt},
|
||||
{0x034836F8, DrivingEmotionTypeS, JP, TextureInsideRt}, // TODO Add more CRCs (https://pcsx2.net/compatibility-list.html).
|
||||
{0x2479F4A9, Jak2, EU, 0},
|
||||
{0xF41C1B29, Jak2, EU, 0}, // Demo
|
||||
{0x9184AAF1, Jak2, US, 0},
|
||||
{0xA2034C69, Jak2, US, 0}, // Demo
|
||||
{0x25FE4D23, Jak2, KO, 0},
|
||||
{0xB4976DAF, Jak2, JP, 0}, // Jak II: Jak x Daxter 2
|
||||
{0x43D4FF3E, Jak2, JP, 0}, // Demo
|
||||
{0x12804727, Jak3, EU, 0},
|
||||
{0xE59E10BF, Jak3, EU, 0},
|
||||
{0xCA68E4D5, Jak3, EU, 0}, // Demo
|
||||
{0x644CFD03, Jak3, US, 0},
|
||||
{0xD401BC20, Jak3, US, 0}, // Demo
|
||||
{0xD1368EAE, Jak3, KO, 0},
|
||||
{0xDF659E77, JakX, EU, 0}, // Jak X: Combat Racing
|
||||
{0xC20596DB, JakX, EU, 0}, // Beta Trial Disc, v0.01
|
||||
{0x3091E6FB, JakX, US, 0},
|
||||
{0xC417D919, JakX, US, 0}, // Demo
|
||||
{0xDA366A53, JakX, US, 0}, // Public Beta v.1
|
||||
{0x7B564230, JakX, US, 0}, // Jak and Daxter Complete Trilogy Demo
|
||||
{0xDBA28C59, JakX, US, 0}, // Greatest Hits
|
||||
};
|
||||
|
||||
std::map<u32, const CRC::Game*> CRC::m_map;
|
||||
|
||||
@@ -23,116 +23,65 @@ public:
|
||||
enum Title
|
||||
{
|
||||
NoTitle,
|
||||
AceCombatZero,
|
||||
AceCombat4,
|
||||
AceCombat5,
|
||||
ApeEscape2,
|
||||
ArTonelico2,
|
||||
Barnyard,
|
||||
BeyondGoodAndEvil,
|
||||
BigMuthaTruckers,
|
||||
BrianLaraInternationalCricket,
|
||||
BurnoutGames,
|
||||
Clannad,
|
||||
CrashBandicootWoC,
|
||||
DarkCloud,
|
||||
DBZBT2,
|
||||
DBZBT3,
|
||||
DeathByDegreesTekkenNinaWilliams,
|
||||
DestroyAllHumans,
|
||||
DestroyAllHumans2,
|
||||
DrivingEmotionTypeS,
|
||||
DuelSaviorDestiny,
|
||||
EvangelionJo,
|
||||
FFX,
|
||||
FFX2,
|
||||
FFXII,
|
||||
FIFA03,
|
||||
FIFA04,
|
||||
FIFA05,
|
||||
FightingBeautyWulong,
|
||||
GetawayGames,
|
||||
GiTS,
|
||||
GodHand,
|
||||
GodOfWar,
|
||||
GodOfWar2,
|
||||
HarleyDavidson,
|
||||
HarryPotterATCOS,
|
||||
HarryPotterATGOF,
|
||||
HarryPotterATHBP,
|
||||
HarryPotterATPOA,
|
||||
HarryPotterOOTP,
|
||||
HauntingGround,
|
||||
ICO,
|
||||
IkkiTousen,
|
||||
IndianaJonesAndTheEmperorsTomb,
|
||||
Jak1,
|
||||
Jak2,
|
||||
Jak3,
|
||||
JakX,
|
||||
JurassicPark,
|
||||
KazokuKeikakuKokoroNoKizuna,
|
||||
KnightsOfTheTemple2,
|
||||
Kunoichi,
|
||||
KyuuketsuKitanMoonties,
|
||||
Lamune,
|
||||
LegacyOfKainDefiance,
|
||||
MajokkoALaMode2,
|
||||
Manhunt2,
|
||||
MetalSlug6,
|
||||
MidnightClub3,
|
||||
NicktoonsUnite,
|
||||
Okami,
|
||||
Oneechanbara2Special,
|
||||
OneTwentyYenStories,
|
||||
Persona3,
|
||||
PiaCarroteYoukosoGPGakuenPrincess,
|
||||
PolyphonyDigitalGames,
|
||||
ProjectSnowblind,
|
||||
Quake3Revolution,
|
||||
RatchetAndClank,
|
||||
RatchetAndClank2,
|
||||
RatchetAndClank3,
|
||||
RatchetAndClank4,
|
||||
RatchetAndClank5,
|
||||
RedDeadRevolver,
|
||||
RickyPontingInternationalCricket,
|
||||
RozenMaidenGebetGarden,
|
||||
SacredBlaze,
|
||||
SakuraTaisen,
|
||||
SakuraWarsSoLongMyLove,
|
||||
SFEX3,
|
||||
ShadowHearts,
|
||||
ShadowofRome,
|
||||
ShinOnimusha,
|
||||
Shox,
|
||||
Simple2000Vol114,
|
||||
SkyGunner,
|
||||
SlamTennis,
|
||||
SMTNocturne,
|
||||
SonicUnleashed,
|
||||
SoTC,
|
||||
SoulReaver2,
|
||||
Spartan,
|
||||
SteambotChronicles,
|
||||
SSX3,
|
||||
SuperManReturns,
|
||||
SVCChaos,
|
||||
TalesOfAbyss,
|
||||
TalesOfLegendia,
|
||||
TalesofSymphonia,
|
||||
Tekken5,
|
||||
TheIncredibleHulkUD,
|
||||
TombRaiderAnniversary,
|
||||
TombRaiderLegend,
|
||||
TombRaiderUnderworld,
|
||||
TriAceGames,
|
||||
TribesAerialAssault,
|
||||
TomoyoAfter,
|
||||
UltramanFightingEvolution,
|
||||
UrbanReign,
|
||||
Whiplash,
|
||||
WildArms4,
|
||||
XenosagaE3,
|
||||
YakuzaGames,
|
||||
ZettaiZetsumeiToshi2,
|
||||
@@ -156,12 +105,6 @@ public:
|
||||
RegionCount,
|
||||
};
|
||||
|
||||
enum Flags
|
||||
{
|
||||
PointListPalette = 1,
|
||||
TextureInsideRt = 2,
|
||||
};
|
||||
|
||||
struct Game
|
||||
{
|
||||
u32 crc;
|
||||
|
||||
+42
-9
@@ -16,6 +16,7 @@
|
||||
#include "PrecompiledHeader.h"
|
||||
#include "GSDump.h"
|
||||
#include "GSExtra.h"
|
||||
#include "GSState.h"
|
||||
|
||||
GSDumpBase::GSDumpBase(const std::string& fn)
|
||||
: m_frames(0)
|
||||
@@ -32,10 +33,38 @@ GSDumpBase::~GSDumpBase()
|
||||
fclose(m_gs);
|
||||
}
|
||||
|
||||
void GSDumpBase::AddHeader(u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
|
||||
void GSDumpBase::AddHeader(const std::string& serial, u32 crc,
|
||||
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||
const freezeData& fd, const GSPrivRegSet* regs)
|
||||
{
|
||||
AppendRawData(&crc, 4);
|
||||
AppendRawData(&fd.size, 4);
|
||||
// New header: CRC of FFFFFFFF, secondary header, full header follows.
|
||||
const u32 fake_crc = 0xFFFFFFFFu;
|
||||
AppendRawData(&fake_crc, 4);
|
||||
|
||||
// Compute full header size (with serial).
|
||||
// This acts as the state size for loading older dumps.
|
||||
const u32 screenshot_size = screenshot_width * screenshot_height * sizeof(screenshot_pixels[0]);
|
||||
const u32 header_size = sizeof(GSDumpHeader) + static_cast<u32>(serial.size()) + screenshot_size;
|
||||
AppendRawData(&header_size, 4);
|
||||
|
||||
// Write hader.
|
||||
GSDumpHeader header = {};
|
||||
header.state_version = GSState::STATE_VERSION;
|
||||
header.state_size = fd.size;
|
||||
header.crc = crc;
|
||||
header.serial_offset = sizeof(header);
|
||||
header.serial_size = static_cast<u32>(serial.size());
|
||||
header.screenshot_width = screenshot_width;
|
||||
header.screenshot_height = screenshot_height;
|
||||
header.screenshot_offset = header.serial_offset + header.serial_size;
|
||||
header.screenshot_size = screenshot_size;
|
||||
AppendRawData(&header, sizeof(header));
|
||||
if (!serial.empty())
|
||||
AppendRawData(serial.data(), serial.size());
|
||||
if (screenshot_pixels)
|
||||
AppendRawData(screenshot_pixels, screenshot_size);
|
||||
|
||||
// Then the real state data.
|
||||
AppendRawData(fd.data, fd.size);
|
||||
AppendRawData(regs, sizeof(*regs));
|
||||
}
|
||||
@@ -92,18 +121,20 @@ void GSDumpBase::Write(const void* data, size_t size)
|
||||
// GSDump implementation
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
GSDump::GSDump(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
|
||||
GSDumpUncompressed::GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc,
|
||||
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||
const freezeData& fd, const GSPrivRegSet* regs)
|
||||
: GSDumpBase(fn + ".gs")
|
||||
{
|
||||
AddHeader(crc, fd, regs);
|
||||
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
|
||||
}
|
||||
|
||||
void GSDump::AppendRawData(const void* data, size_t size)
|
||||
void GSDumpUncompressed::AppendRawData(const void* data, size_t size)
|
||||
{
|
||||
Write(data, size);
|
||||
}
|
||||
|
||||
void GSDump::AppendRawData(u8 c)
|
||||
void GSDumpUncompressed::AppendRawData(u8 c)
|
||||
{
|
||||
Write(&c, 1);
|
||||
}
|
||||
@@ -112,7 +143,9 @@ void GSDump::AppendRawData(u8 c)
|
||||
// GSDumpXz implementation
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
GSDumpXz::GSDumpXz(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs)
|
||||
GSDumpXz::GSDumpXz(const std::string& fn, const std::string& serial, u32 crc,
|
||||
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||
const freezeData& fd, const GSPrivRegSet* regs)
|
||||
: GSDumpBase(fn + ".gs.xz")
|
||||
{
|
||||
m_strm = LZMA_STREAM_INIT;
|
||||
@@ -123,7 +156,7 @@ GSDumpXz::GSDumpXz(const std::string& fn, u32 crc, const freezeData& fd, const G
|
||||
return;
|
||||
}
|
||||
|
||||
AddHeader(crc, fd, regs);
|
||||
AddHeader(serial, crc, screenshot_width, screenshot_height, screenshot_pixels, fd, regs);
|
||||
}
|
||||
|
||||
GSDumpXz::~GSDumpXz()
|
||||
|
||||
+27
-6
@@ -23,7 +23,7 @@
|
||||
/*
|
||||
|
||||
Dump file format:
|
||||
- [crc/4] [state size/4] [state data/size] [PMODE/0x2000] [id/1] [data/?] .. [id/1] [data/?]
|
||||
- [0xFFFFFFFF] [Header] [state size/4] [state data/size] [PMODE/0x2000] [id/1] [data/?] .. [id/1] [data/?]
|
||||
|
||||
Transfer data (id == 0)
|
||||
- [0/1] [path index/1] [size/4] [data/size]
|
||||
@@ -39,6 +39,21 @@ Regs data (id == 3)
|
||||
|
||||
*/
|
||||
|
||||
#pragma pack(push, 4)
|
||||
struct GSDumpHeader
|
||||
{
|
||||
u32 state_version; ///< Must always be first in struct to safely prevent old PCSX2 versions from crashing.
|
||||
u32 state_size;
|
||||
u32 serial_offset;
|
||||
u32 serial_size;
|
||||
u32 crc;
|
||||
u32 screenshot_width;
|
||||
u32 screenshot_height;
|
||||
u32 screenshot_offset;
|
||||
u32 screenshot_size;
|
||||
};
|
||||
#pragma pack(pop)
|
||||
|
||||
class GSDumpBase
|
||||
{
|
||||
int m_frames;
|
||||
@@ -46,7 +61,9 @@ class GSDumpBase
|
||||
FILE* m_gs;
|
||||
|
||||
protected:
|
||||
void AddHeader(u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
|
||||
void AddHeader(const std::string& serial, u32 crc,
|
||||
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||
const freezeData& fd, const GSPrivRegSet* regs);
|
||||
void Write(const void* data, size_t size);
|
||||
|
||||
virtual void AppendRawData(const void* data, size_t size) = 0;
|
||||
@@ -61,14 +78,16 @@ public:
|
||||
bool VSync(int field, bool last, const GSPrivRegSet* regs);
|
||||
};
|
||||
|
||||
class GSDump final : public GSDumpBase
|
||||
class GSDumpUncompressed final : public GSDumpBase
|
||||
{
|
||||
void AppendRawData(const void* data, size_t size) final;
|
||||
void AppendRawData(u8 c) final;
|
||||
|
||||
public:
|
||||
GSDump(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
|
||||
virtual ~GSDump() = default;
|
||||
GSDumpUncompressed(const std::string& fn, const std::string& serial, u32 crc,
|
||||
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||
const freezeData& fd, const GSPrivRegSet* regs);
|
||||
virtual ~GSDumpUncompressed() = default;
|
||||
};
|
||||
|
||||
class GSDumpXz final : public GSDumpBase
|
||||
@@ -83,6 +102,8 @@ class GSDumpXz final : public GSDumpBase
|
||||
void AppendRawData(u8 c);
|
||||
|
||||
public:
|
||||
GSDumpXz(const std::string& fn, u32 crc, const freezeData& fd, const GSPrivRegSet* regs);
|
||||
GSDumpXz(const std::string& fn, const std::string& serial, u32 crc,
|
||||
u32 screenshot_width, u32 screenshot_height, const u32* screenshot_pixels,
|
||||
const freezeData& fd, const GSPrivRegSet* regs);
|
||||
virtual ~GSDumpXz();
|
||||
};
|
||||
|
||||
@@ -106,6 +106,7 @@ GSLocalMemory::GSLocalMemory()
|
||||
psm.pgs = GSVector2i(64, 32);
|
||||
psm.msk = 0xff;
|
||||
psm.depth = 0;
|
||||
psm.fmsk = 0xffffffff;
|
||||
}
|
||||
|
||||
m_psm[PSM_PSGPU24].info = GSLocalMemory::swizzle16;
|
||||
@@ -346,6 +347,17 @@ GSLocalMemory::GSLocalMemory()
|
||||
m_psm[PSM_PSMZ24].depth = 1;
|
||||
m_psm[PSM_PSMZ16].depth = 1;
|
||||
m_psm[PSM_PSMZ16S].depth = 1;
|
||||
|
||||
m_psm[PSM_PSMCT24].fmsk = 0x00FFFFFF;
|
||||
m_psm[PSM_PSGPU24].fmsk = 0x00FFFFFF;
|
||||
m_psm[PSM_PSMCT16].fmsk = 0x80F8F8F8;
|
||||
m_psm[PSM_PSMCT16S].fmsk = 0x80F8F8F8;
|
||||
m_psm[PSM_PSMT8H].fmsk = 0xFF000000;
|
||||
m_psm[PSM_PSMT4HL].fmsk = 0x0F000000;
|
||||
m_psm[PSM_PSMT4HH].fmsk = 0xF0000000;
|
||||
m_psm[PSM_PSMZ24].fmsk = 0x00FFFFFF;
|
||||
m_psm[PSM_PSMZ16].fmsk = 0x80F8F8F8;
|
||||
m_psm[PSM_PSMZ16S].fmsk = 0x80F8F8F8;
|
||||
}
|
||||
|
||||
GSLocalMemory::~GSLocalMemory()
|
||||
|
||||
@@ -468,6 +468,7 @@ public:
|
||||
u16 bpp, trbpp, pal, fmt;
|
||||
GSVector2i bs, pgs;
|
||||
u8 msk, depth;
|
||||
u32 fmsk;
|
||||
};
|
||||
|
||||
static psm_t m_psm[64];
|
||||
|
||||
+211
-43
@@ -14,28 +14,18 @@
|
||||
*/
|
||||
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "GSDump.h"
|
||||
#include "GSLzma.h"
|
||||
|
||||
GSDumpFile::GSDumpFile(char* filename, const char* repack_filename)
|
||||
{
|
||||
m_fp = fopen(filename, "rb");
|
||||
if (m_fp == nullptr)
|
||||
{
|
||||
fprintf(stderr, "failed to open %s\n", filename);
|
||||
throw "BAD"; // Just exit the program
|
||||
}
|
||||
|
||||
m_repack_fp = nullptr;
|
||||
if (repack_filename)
|
||||
{
|
||||
m_repack_fp = fopen(repack_filename, "wb");
|
||||
if (m_repack_fp == nullptr)
|
||||
fprintf(stderr, "failed to open %s for repack\n", repack_filename);
|
||||
}
|
||||
}
|
||||
using namespace GSDumpTypes;
|
||||
|
||||
GSDumpFile::GSDumpFile(FILE* file, FILE* repack_file)
|
||||
: m_repack_fp(repack_file), m_fp(file)
|
||||
: m_repack_fp(repack_file)
|
||||
, m_fp(file)
|
||||
{
|
||||
}
|
||||
|
||||
@@ -57,13 +47,203 @@ GSDumpFile::~GSDumpFile()
|
||||
fclose(m_repack_fp);
|
||||
}
|
||||
|
||||
/******************************************************************/
|
||||
GSDumpLzma::GSDumpLzma(char* filename, const char* repack_filename)
|
||||
: GSDumpFile(filename, repack_filename)
|
||||
std::unique_ptr<GSDumpFile> GSDumpFile::OpenGSDump(const char* filename, const char* repack_filename /*= nullptr*/)
|
||||
{
|
||||
Initialize();
|
||||
std::FILE* fp = FileSystem::OpenCFile(filename, "rb");
|
||||
if (!fp)
|
||||
return nullptr;
|
||||
|
||||
std::FILE* repack_fp = nullptr;
|
||||
if (repack_filename && std::strlen(repack_filename) > 0)
|
||||
{
|
||||
repack_fp = FileSystem::OpenCFile(repack_filename, "wb");
|
||||
if (!repack_fp)
|
||||
{
|
||||
std::fclose(fp);
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
if (StringUtil::EndsWithNoCase(filename, ".xz"))
|
||||
return std::make_unique<GSDumpLzma>(fp, nullptr);
|
||||
else
|
||||
return std::make_unique<GSDumpRaw>(fp, nullptr);
|
||||
}
|
||||
|
||||
bool GSDumpFile::GetPreviewImageFromDump(const char* filename, u32* width, u32* height, std::vector<u32>* pixels)
|
||||
{
|
||||
std::unique_ptr<GSDumpFile> dump = OpenGSDump(filename);
|
||||
if (!dump)
|
||||
return false;
|
||||
|
||||
u32 crc;
|
||||
if (!dump->Read(&crc, sizeof(crc)) || crc != 0xFFFFFFFFu)
|
||||
{
|
||||
// not new header dump, so no preview
|
||||
return false;
|
||||
}
|
||||
|
||||
u32 header_size;
|
||||
if (!dump->Read(&header_size, sizeof(header_size)) || header_size < sizeof(GSDumpHeader))
|
||||
{
|
||||
// doesn't have the screenshot fields
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<u8[]> header_bits = std::make_unique<u8[]>(header_size);
|
||||
if (!dump->Read(header_bits.get(), header_size))
|
||||
return false;
|
||||
|
||||
GSDumpHeader header;
|
||||
std::memcpy(&header, header_bits.get(), sizeof(header));
|
||||
if (header.screenshot_size == 0 ||
|
||||
header.screenshot_size < (header.screenshot_width * header.screenshot_height * sizeof(u32)) ||
|
||||
(static_cast<u64>(header.screenshot_offset) + header.screenshot_size) > header_size)
|
||||
{
|
||||
// doesn't have a screenshot
|
||||
return false;
|
||||
}
|
||||
|
||||
*width = header.screenshot_width;
|
||||
*height = header.screenshot_height;
|
||||
pixels->resize(header.screenshot_width * header.screenshot_height);
|
||||
std::memcpy(pixels->data(), header_bits.get() + header.screenshot_offset, header.screenshot_size);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSDumpFile::ReadFile()
|
||||
{
|
||||
u32 ss;
|
||||
if (Read(&m_crc, sizeof(m_crc)) != sizeof(m_crc) || Read(&ss, sizeof(ss)) != sizeof(ss))
|
||||
return false;
|
||||
|
||||
m_state_data.resize(ss);
|
||||
if (Read(m_state_data.data(), ss) != ss)
|
||||
return false;
|
||||
|
||||
// Pull serial out of new header, if present.
|
||||
if (m_crc == 0xFFFFFFFFu)
|
||||
{
|
||||
GSDumpHeader header;
|
||||
if (m_state_data.size() < sizeof(header))
|
||||
{
|
||||
Console.Error("GSDump header is corrupted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&header, m_state_data.data(), sizeof(header));
|
||||
|
||||
m_crc = header.crc;
|
||||
|
||||
if (header.serial_size > 0)
|
||||
{
|
||||
if (header.serial_offset > ss || (static_cast<u64>(header.serial_offset) + header.serial_size) > ss)
|
||||
{
|
||||
Console.Error("GSDump header is corrupted.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.serial_size > 0)
|
||||
m_serial.assign(reinterpret_cast<const char*>(m_state_data.data()) + header.serial_offset, header.serial_size);
|
||||
}
|
||||
|
||||
// Read the real state data
|
||||
m_state_data.resize(header.state_size);
|
||||
if (Read(m_state_data.data(), header.state_size) != header.state_size)
|
||||
return false;
|
||||
}
|
||||
|
||||
m_regs_data.resize(8192);
|
||||
if (Read(m_regs_data.data(), m_regs_data.size()) != m_regs_data.size())
|
||||
return false;
|
||||
|
||||
// read all the packet data in
|
||||
// TODO: make this suck less by getting the full/extracted size and preallocating
|
||||
for (;;)
|
||||
{
|
||||
const size_t packet_data_size = m_packet_data.size();
|
||||
m_packet_data.resize(std::max<size_t>(packet_data_size * 2, 8 * _1mb));
|
||||
|
||||
const size_t read_size = m_packet_data.size() - packet_data_size;
|
||||
const size_t read = Read(m_packet_data.data() + packet_data_size, read_size);
|
||||
if (read != read_size)
|
||||
{
|
||||
if (!IsEof())
|
||||
return false;
|
||||
|
||||
m_packet_data.resize(packet_data_size + read);
|
||||
m_packet_data.shrink_to_fit();
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
u8* data = m_packet_data.data();
|
||||
size_t remaining = m_packet_data.size();
|
||||
|
||||
#define GET_BYTE(dst) \
|
||||
do \
|
||||
{ \
|
||||
if (remaining < sizeof(u8)) \
|
||||
return false; \
|
||||
std::memcpy(dst, data, sizeof(u8)); \
|
||||
data++; \
|
||||
remaining--; \
|
||||
} while (0)
|
||||
#define GET_WORD(dst) \
|
||||
do \
|
||||
{ \
|
||||
if (remaining < sizeof(u32)) \
|
||||
return false; \
|
||||
std::memcpy(dst, data, sizeof(u32)); \
|
||||
data += sizeof(u32); \
|
||||
remaining -= sizeof(u32); \
|
||||
} while (0)
|
||||
|
||||
while (remaining > 0)
|
||||
{
|
||||
GSData packet = {};
|
||||
packet.path = GSTransferPath::Dummy;
|
||||
GET_BYTE(&packet.id);
|
||||
|
||||
switch (packet.id)
|
||||
{
|
||||
case GSType::Transfer:
|
||||
GET_BYTE(&packet.path);
|
||||
GET_WORD(&packet.length);
|
||||
break;
|
||||
case GSType::VSync:
|
||||
packet.length = 1;
|
||||
break;
|
||||
case GSType::ReadFIFO2:
|
||||
packet.length = 4;
|
||||
break;
|
||||
case GSType::Registers:
|
||||
packet.length = 8192;
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
|
||||
if (packet.length > 0)
|
||||
{
|
||||
if (remaining < packet.length)
|
||||
return false;
|
||||
|
||||
packet.data = data;
|
||||
data += packet.length;
|
||||
remaining -= packet.length;
|
||||
}
|
||||
|
||||
m_dump_packets.push_back(std::move(packet));
|
||||
}
|
||||
|
||||
#undef GET_WORD
|
||||
#undef GET_BYTE
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/******************************************************************/
|
||||
GSDumpLzma::GSDumpLzma(FILE* file, FILE* repack_file)
|
||||
: GSDumpFile(file, repack_file)
|
||||
{
|
||||
@@ -137,11 +317,10 @@ bool GSDumpLzma::IsEof()
|
||||
return feof(m_fp) && m_avail == 0 && m_strm.avail_in == 0;
|
||||
}
|
||||
|
||||
bool GSDumpLzma::Read(void* ptr, size_t size)
|
||||
size_t GSDumpLzma::Read(void* ptr, size_t size)
|
||||
{
|
||||
size_t off = 0;
|
||||
uint8_t* dst = (uint8_t*)ptr;
|
||||
size_t full_size = size;
|
||||
while (size && !IsEof())
|
||||
{
|
||||
if (m_avail == 0)
|
||||
@@ -157,13 +336,10 @@ bool GSDumpLzma::Read(void* ptr, size_t size)
|
||||
off += l;
|
||||
}
|
||||
|
||||
if (size == 0)
|
||||
{
|
||||
Repack(ptr, full_size);
|
||||
return true;
|
||||
}
|
||||
if (off > 0)
|
||||
Repack(ptr, off);
|
||||
|
||||
return false;
|
||||
return off;
|
||||
}
|
||||
|
||||
GSDumpLzma::~GSDumpLzma()
|
||||
@@ -178,11 +354,6 @@ GSDumpLzma::~GSDumpLzma()
|
||||
|
||||
/******************************************************************/
|
||||
|
||||
GSDumpRaw::GSDumpRaw(char* filename, const char* repack_filename)
|
||||
: GSDumpFile(filename, repack_filename)
|
||||
{
|
||||
}
|
||||
|
||||
GSDumpRaw::GSDumpRaw(FILE* file, FILE* repack_file)
|
||||
: GSDumpFile(file, repack_file)
|
||||
{
|
||||
@@ -193,20 +364,17 @@ bool GSDumpRaw::IsEof()
|
||||
return !!feof(m_fp);
|
||||
}
|
||||
|
||||
bool GSDumpRaw::Read(void* ptr, size_t size)
|
||||
size_t GSDumpRaw::Read(void* ptr, size_t size)
|
||||
{
|
||||
size_t ret = fread(ptr, 1, size, m_fp);
|
||||
if (ret != size && ferror(m_fp))
|
||||
{
|
||||
fprintf(stderr, "GSDumpRaw:: Read error (%zu/%zu)\n", ret, size);
|
||||
throw "BAD"; // Just exit the program
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (ret == size)
|
||||
{
|
||||
Repack(ptr, size);
|
||||
return true;
|
||||
}
|
||||
if (ret > 0)
|
||||
Repack(ptr, ret);
|
||||
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
+313
-12
@@ -13,24 +13,327 @@
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <lzma.h>
|
||||
#include <memory>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#define GEN_REG_ENUM_CLASS_CONTENT(ClassName, EntryName, Value) \
|
||||
EntryName = Value,
|
||||
|
||||
#define GEN_REG_GETNAME_CONTENT(ClassName, EntryName, Value) \
|
||||
case ClassName::EntryName: \
|
||||
return #EntryName;
|
||||
|
||||
#define GEN_REG_ENUM_CLASS_AND_GETNAME(Macro, ClassName, Type, DefaultString) \
|
||||
enum class ClassName : Type \
|
||||
{ \
|
||||
Macro(GEN_REG_ENUM_CLASS_CONTENT) \
|
||||
}; \
|
||||
static constexpr const char* GetName(ClassName reg) \
|
||||
{ \
|
||||
switch (reg) \
|
||||
{ \
|
||||
Macro(GEN_REG_GETNAME_CONTENT) default : return DefaultString; \
|
||||
} \
|
||||
}
|
||||
|
||||
namespace GSDumpTypes
|
||||
{
|
||||
// clang-format off
|
||||
#define DEF_GSType(X) \
|
||||
X(GSType, Transfer, 0) \
|
||||
X(GSType, VSync, 1) \
|
||||
X(GSType, ReadFIFO2, 2) \
|
||||
X(GSType, Registers, 3)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GSType, GSType, u8, "UnknownType")
|
||||
#undef DEF_GSType
|
||||
|
||||
#define DEF_GSTransferPath(X) \
|
||||
X(GSTransferPath, Path1Old, 0) \
|
||||
X(GSTransferPath, Path2, 1) \
|
||||
X(GSTransferPath, Path3, 2) \
|
||||
X(GSTransferPath, Path1New, 3) \
|
||||
X(GSTransferPath, Dummy, 4)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GSTransferPath, GSTransferPath, u8, "UnknownPath")
|
||||
#undef DEF_GSTransferPath
|
||||
|
||||
#define DEF_GIFFlag(X) \
|
||||
X(GIFFlag, PACKED, 0) \
|
||||
X(GIFFlag, REGLIST, 1) \
|
||||
X(GIFFlag, IMAGE, 2) \
|
||||
X(GIFFlag, IMAGE2, 3)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GIFFlag, GIFFlag, u8, "UnknownFlag")
|
||||
#undef DEF_GifFlag
|
||||
|
||||
#define DEF_GIFReg(X) \
|
||||
X(GIFReg, PRIM, 0x00) \
|
||||
X(GIFReg, RGBAQ, 0x01) \
|
||||
X(GIFReg, ST, 0x02) \
|
||||
X(GIFReg, UV, 0x03) \
|
||||
X(GIFReg, XYZF2, 0x04) \
|
||||
X(GIFReg, XYZ2, 0x05) \
|
||||
X(GIFReg, TEX0_1, 0x06) \
|
||||
X(GIFReg, TEX0_2, 0x07) \
|
||||
X(GIFReg, CLAMP_1, 0x08) \
|
||||
X(GIFReg, CLAMP_2, 0x09) \
|
||||
X(GIFReg, FOG, 0x0a) \
|
||||
X(GIFReg, XYZF3, 0x0c) \
|
||||
X(GIFReg, XYZ3, 0x0d) \
|
||||
X(GIFReg, AD, 0x0e) \
|
||||
X(GIFReg, NOP, 0x0f) \
|
||||
X(GIFReg, TEX1_1, 0x14) \
|
||||
X(GIFReg, TEX1_2, 0x15) \
|
||||
X(GIFReg, TEX2_1, 0x16) \
|
||||
X(GIFReg, TEX2_2, 0x17) \
|
||||
X(GIFReg, XYOFFSET_1, 0x18) \
|
||||
X(GIFReg, XYOFFSET_2, 0x19) \
|
||||
X(GIFReg, PRMODECONT, 0x1a) \
|
||||
X(GIFReg, PRMODE, 0x1b) \
|
||||
X(GIFReg, TEXCLUT, 0x1c) \
|
||||
X(GIFReg, SCANMSK, 0x22) \
|
||||
X(GIFReg, MIPTBP1_1, 0x34) \
|
||||
X(GIFReg, MIPTBP1_2, 0x35) \
|
||||
X(GIFReg, MIPTBP2_1, 0x36) \
|
||||
X(GIFReg, MIPTBP2_2, 0x37) \
|
||||
X(GIFReg, TEXA, 0x3b) \
|
||||
X(GIFReg, FOGCOL, 0x3d) \
|
||||
X(GIFReg, TEXFLUSH, 0x3f) \
|
||||
X(GIFReg, SCISSOR_1, 0x40) \
|
||||
X(GIFReg, SCISSOR_2, 0x41) \
|
||||
X(GIFReg, ALPHA_1, 0x42) \
|
||||
X(GIFReg, ALPHA_2, 0x43) \
|
||||
X(GIFReg, DIMX, 0x44) \
|
||||
X(GIFReg, DTHE, 0x45) \
|
||||
X(GIFReg, COLCLAMP, 0x46) \
|
||||
X(GIFReg, TEST_1, 0x47) \
|
||||
X(GIFReg, TEST_2, 0x48) \
|
||||
X(GIFReg, PABE, 0x49) \
|
||||
X(GIFReg, FBA_1, 0x4a) \
|
||||
X(GIFReg, FBA_2, 0x4b) \
|
||||
X(GIFReg, FRAME_1, 0x4c) \
|
||||
X(GIFReg, FRAME_2, 0x4d) \
|
||||
X(GIFReg, ZBUF_1, 0x4e) \
|
||||
X(GIFReg, ZBUF_2, 0x4f) \
|
||||
X(GIFReg, BITBLTBUF, 0x50) \
|
||||
X(GIFReg, TRXPOS, 0x51) \
|
||||
X(GIFReg, TRXREG, 0x52) \
|
||||
X(GIFReg, TRXDIR, 0x53) \
|
||||
X(GIFReg, HWREG, 0x54) \
|
||||
X(GIFReg, SIGNAL, 0x60) \
|
||||
X(GIFReg, FINISH, 0x61) \
|
||||
X(GIFReg, LABEL, 0x62)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GIFReg, GIFReg, u8, "UnknownReg")
|
||||
#undef DEF_GIFReg
|
||||
// clang-format on
|
||||
|
||||
template <typename Output, typename Input, typename std::enable_if<sizeof(Input) == sizeof(Output), bool>::type = true>
|
||||
static constexpr Output BitCast(Input input)
|
||||
{
|
||||
Output output;
|
||||
memcpy(&output, &input, sizeof(input));
|
||||
return output;
|
||||
}
|
||||
template <typename Output = u32>
|
||||
static constexpr Output GetBits(u64 value, u32 shift, u32 numbits)
|
||||
{
|
||||
return static_cast<Output>((value >> shift) & ((1ull << numbits) - 1));
|
||||
}
|
||||
|
||||
template <typename Output = u32>
|
||||
static constexpr Output GetBits(u128 value, u32 shift, u32 numbits)
|
||||
{
|
||||
u64 outval = 0;
|
||||
if (shift == 0)
|
||||
outval = value.lo;
|
||||
else if (shift < 64)
|
||||
outval = (value.lo >> shift) | (value.hi << (64 - shift));
|
||||
else
|
||||
outval = value.hi >> (shift - 64);
|
||||
return static_cast<Output>(outval & ((1ull << numbits) - 1));
|
||||
}
|
||||
static constexpr const char* GetNameOneBit(u8 value, const char* zero, const char* one)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0:
|
||||
return zero;
|
||||
case 1:
|
||||
return one;
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNameBool(bool value)
|
||||
{
|
||||
return value ? "True" : "False";
|
||||
}
|
||||
|
||||
static constexpr const char* GetNamePRIMPRIM(u8 prim)
|
||||
{
|
||||
switch (prim)
|
||||
{
|
||||
case 0:
|
||||
return "Point";
|
||||
case 1:
|
||||
return "Line";
|
||||
case 2:
|
||||
return "Line Strip";
|
||||
case 3:
|
||||
return "Triangle";
|
||||
case 4:
|
||||
return "Triangle Strip";
|
||||
case 5:
|
||||
return "Triangle Fan";
|
||||
case 6:
|
||||
return "Sprite";
|
||||
case 7:
|
||||
return "Invalid";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNamePRIMIIP(u8 iip)
|
||||
{
|
||||
return GetNameOneBit(iip, "Flat Shading", "Gouraud Shading");
|
||||
}
|
||||
static constexpr const char* GetNamePRIMFST(u8 fst)
|
||||
{
|
||||
return GetNameOneBit(fst, "STQ Value", "UV Value");
|
||||
}
|
||||
static constexpr const char* GetNamePRIMCTXT(u8 ctxt)
|
||||
{
|
||||
return GetNameOneBit(ctxt, "Context 1", "Context 2");
|
||||
}
|
||||
static constexpr const char* GetNamePRIMFIX(u8 fix)
|
||||
{
|
||||
return GetNameOneBit(fix, "Unfixed", "Fixed");
|
||||
}
|
||||
static constexpr const char* GetNameTEXTCC(u8 tcc)
|
||||
{
|
||||
return GetNameOneBit(tcc, "RGB", "RGBA");
|
||||
}
|
||||
static constexpr const char* GetNameTEXTFX(u8 tfx)
|
||||
{
|
||||
switch (tfx)
|
||||
{
|
||||
case 0:
|
||||
return "Modulate";
|
||||
case 1:
|
||||
return "Decal";
|
||||
case 2:
|
||||
return "Highlight";
|
||||
case 3:
|
||||
return "Highlight2";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNameTEXCSM(u8 csm)
|
||||
{
|
||||
return GetNameOneBit(csm, "CSM1", "CSM2");
|
||||
}
|
||||
static constexpr const char* GetNameTEXPSM(u8 psm)
|
||||
{
|
||||
switch (psm)
|
||||
{
|
||||
case 000:
|
||||
return "PSMCT32";
|
||||
case 001:
|
||||
return "PSMCT24";
|
||||
case 002:
|
||||
return "PSMCT16";
|
||||
case 012:
|
||||
return "PSMCT16S";
|
||||
case 023:
|
||||
return "PSMT8";
|
||||
case 024:
|
||||
return "PSMT4";
|
||||
case 033:
|
||||
return "PSMT8H";
|
||||
case 044:
|
||||
return "PSMT4HL";
|
||||
case 054:
|
||||
return "PSMT4HH";
|
||||
case 060:
|
||||
return "PSMZ32";
|
||||
case 061:
|
||||
return "PSMZ24";
|
||||
case 062:
|
||||
return "PSMZ16";
|
||||
case 072:
|
||||
return "PSMZ16S";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNameTEXCPSM(u8 psm)
|
||||
{
|
||||
switch (psm)
|
||||
{
|
||||
case 000:
|
||||
return "PSMCT32";
|
||||
case 002:
|
||||
return "PSMCT16";
|
||||
case 012:
|
||||
return "PSMCT16S";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
} // namespace GSDumpTypes
|
||||
|
||||
class GSDumpFile
|
||||
{
|
||||
FILE* m_repack_fp;
|
||||
public:
|
||||
struct GSData
|
||||
{
|
||||
GSDumpTypes::GSType id;
|
||||
const u8* data;
|
||||
s32 length;
|
||||
GSDumpTypes::GSTransferPath path;
|
||||
};
|
||||
|
||||
using ByteArray = std::vector<u8>;
|
||||
using GSDataArray = std::vector<GSData>;
|
||||
|
||||
virtual ~GSDumpFile();
|
||||
|
||||
static std::unique_ptr<GSDumpFile> OpenGSDump(const char* filename, const char* repack_filename = nullptr);
|
||||
static bool GetPreviewImageFromDump(const char* filename, u32* width, u32* height, std::vector<u32>* pixels);
|
||||
|
||||
__fi const std::string& GetSerial() const { return m_serial; }
|
||||
__fi u32 GetCRC() const { return m_crc; }
|
||||
|
||||
__fi const ByteArray& GetRegsData() const { return m_regs_data; }
|
||||
__fi const ByteArray& GetStateData() const { return m_state_data; }
|
||||
__fi const GSDataArray& GetPackets() const { return m_dump_packets; }
|
||||
|
||||
bool ReadFile();
|
||||
|
||||
protected:
|
||||
FILE* m_fp;
|
||||
GSDumpFile(FILE* file, FILE* repack_file);
|
||||
|
||||
virtual bool IsEof() = 0;
|
||||
virtual size_t Read(void* ptr, size_t size) = 0;
|
||||
|
||||
void Repack(void* ptr, size_t size);
|
||||
|
||||
public:
|
||||
virtual bool IsEof() = 0;
|
||||
virtual bool Read(void* ptr, size_t size) = 0;
|
||||
FILE* m_fp = nullptr;
|
||||
|
||||
GSDumpFile(char* filename, const char* repack_filename);
|
||||
GSDumpFile(FILE* file, FILE* repack_file);
|
||||
virtual ~GSDumpFile();
|
||||
private:
|
||||
FILE* m_repack_fp = nullptr;
|
||||
|
||||
std::string m_serial;
|
||||
u32 m_crc = 0;
|
||||
|
||||
std::vector<u8> m_regs_data;
|
||||
std::vector<u8> m_state_data;
|
||||
std::vector<u8> m_packet_data;
|
||||
|
||||
GSDataArray m_dump_packets;
|
||||
};
|
||||
|
||||
class GSDumpLzma : public GSDumpFile
|
||||
@@ -48,21 +351,19 @@ class GSDumpLzma : public GSDumpFile
|
||||
void Initialize();
|
||||
|
||||
public:
|
||||
GSDumpLzma(char* filename, const char* repack_filename);
|
||||
GSDumpLzma(FILE* file, FILE* repack_file);
|
||||
virtual ~GSDumpLzma();
|
||||
|
||||
bool IsEof() final;
|
||||
bool Read(void* ptr, size_t size) final;
|
||||
size_t Read(void* ptr, size_t size) final;
|
||||
};
|
||||
|
||||
class GSDumpRaw : public GSDumpFile
|
||||
{
|
||||
public:
|
||||
GSDumpRaw(char* filename, const char* repack_filename);
|
||||
GSDumpRaw(FILE* file, FILE* repack_file);
|
||||
virtual ~GSDumpRaw() = default;
|
||||
|
||||
bool IsEof() final;
|
||||
bool Read(void* ptr, size_t size) final;
|
||||
size_t Read(void* ptr, size_t size) final;
|
||||
};
|
||||
|
||||
+271
-98
@@ -20,17 +20,29 @@
|
||||
|
||||
#include <algorithm> // clamp
|
||||
#include <cfloat> // FLT_MAX
|
||||
#include <iomanip> // Dump Verticles
|
||||
|
||||
int GSState::s_n = 0;
|
||||
|
||||
static __fi bool IsAutoFlushEnabled()
|
||||
{
|
||||
return (GSConfig.Renderer == GSRendererType::SW) ? GSConfig.AutoFlushSW : GSConfig.UserHacks_AutoFlush;
|
||||
}
|
||||
|
||||
static __fi bool IsFirstProvokingVertex()
|
||||
{
|
||||
return (GSConfig.Renderer != GSRendererType::SW && !g_gs_device->Features().provoking_vertex_last);
|
||||
}
|
||||
|
||||
GSState::GSState()
|
||||
: m_version(7)
|
||||
: m_version(STATE_VERSION)
|
||||
, m_gsc(NULL)
|
||||
, m_skip(0)
|
||||
, m_skip_offset(0)
|
||||
, m_q(1.0f)
|
||||
, m_scanmask_used(false)
|
||||
, m_vt(this)
|
||||
, tex_flushed(true)
|
||||
, m_vt(this, IsFirstProvokingVertex())
|
||||
, m_regs(NULL)
|
||||
, m_crc(0)
|
||||
, m_options(0)
|
||||
@@ -39,18 +51,8 @@ GSState::GSState()
|
||||
// m_nativeres seems to be a hack. Unfortunately it impacts draw call number which make debug painful in the replayer.
|
||||
// Let's keep it disabled to ease debug.
|
||||
m_nativeres = GSConfig.UpscaleMultiplier == 1;
|
||||
m_mipmap = theApp.GetConfigB("mipmap");
|
||||
m_mipmap = GSConfig.Mipmap;
|
||||
m_NTSC_Saturation = theApp.GetConfigB("NTSC_Saturation");
|
||||
if (theApp.GetConfigB("UserHacks"))
|
||||
{
|
||||
m_userhacks_auto_flush = theApp.GetConfigB("UserHacks_AutoFlush");
|
||||
m_userhacks_wildhack = theApp.GetConfigB("UserHacks_WildHack");
|
||||
}
|
||||
else
|
||||
{
|
||||
m_userhacks_auto_flush = false;
|
||||
m_userhacks_wildhack = false;
|
||||
}
|
||||
|
||||
s_n = 0;
|
||||
s_dump = theApp.GetConfigB("dump");
|
||||
@@ -132,7 +134,6 @@ GSState::GSState()
|
||||
PRIM = &m_env.PRIM;
|
||||
//CSR->rREV = 0x20;
|
||||
m_env.PRMODECONT.AC = 1;
|
||||
tex_flushed = true;
|
||||
|
||||
Reset();
|
||||
|
||||
@@ -246,7 +247,7 @@ void GSState::ResetHandlers()
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_PRIM] = (GIFPackedRegHandler)(GIFRegHandler)&GSState::GIFRegHandlerPRIM;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_RGBA] = &GSState::GIFPackedRegHandlerRGBA;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_STQ] = &GSState::GIFPackedRegHandlerSTQ;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_UV] = m_userhacks_wildhack ? &GSState::GIFPackedRegHandlerUV_Hack : &GSState::GIFPackedRegHandlerUV;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_UV] = GSConfig.UserHacks_WildHack ? &GSState::GIFPackedRegHandlerUV_Hack : &GSState::GIFPackedRegHandlerUV;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_TEX0_1] = (GIFPackedRegHandler)(GIFRegHandler)&GSState::GIFRegHandlerTEX0<0>;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_TEX0_2] = (GIFPackedRegHandler)(GIFRegHandler)&GSState::GIFRegHandlerTEX0<1>;
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_CLAMP_1] = (GIFPackedRegHandler)(GIFRegHandler)&GSState::GIFRegHandlerCLAMP<0>;
|
||||
@@ -256,11 +257,10 @@ void GSState::ResetHandlers()
|
||||
m_fpGIFPackedRegHandlers[GIF_REG_NOP] = &GSState::GIFPackedRegHandlerNOP;
|
||||
|
||||
// swap first/last indices when the provoking vertex is the first (D3D/Vulkan)
|
||||
const bool index_swap = GSConfig.UseHardwareRenderer() && !g_gs_device->Features().provoking_vertex_last;
|
||||
if (m_userhacks_auto_flush)
|
||||
index_swap ? SetPrimHandlers<true, true>() : SetPrimHandlers<true, false>();
|
||||
if (IsAutoFlushEnabled())
|
||||
IsFirstProvokingVertex() ? SetPrimHandlers<true, true>() : SetPrimHandlers<true, false>();
|
||||
else
|
||||
index_swap ? SetPrimHandlers<false, true>() : SetPrimHandlers<false, false>();
|
||||
IsFirstProvokingVertex() ? SetPrimHandlers<false, true>() : SetPrimHandlers<false, false>();
|
||||
|
||||
std::fill(std::begin(m_fpGIFRegHandlers), std::end(m_fpGIFRegHandlers), &GSState::GIFRegHandlerNull);
|
||||
|
||||
@@ -268,7 +268,7 @@ void GSState::ResetHandlers()
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_RGBAQ] = &GSState::GIFRegHandlerRGBAQ;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_RGBAQ + 0x10] = &GSState::GIFRegHandlerRGBAQ;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_ST] = &GSState::GIFRegHandlerST;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_UV] = m_userhacks_wildhack ? &GSState::GIFRegHandlerUV_Hack : &GSState::GIFRegHandlerUV;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_UV] = GSConfig.UserHacks_WildHack ? &GSState::GIFRegHandlerUV_Hack : &GSState::GIFRegHandlerUV;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_TEX0_1] = &GSState::GIFRegHandlerTEX0<0>;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_TEX0_2] = &GSState::GIFRegHandlerTEX0<1>;
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_CLAMP_1] = &GSState::GIFRegHandlerCLAMP<0>;
|
||||
@@ -319,6 +319,19 @@ void GSState::ResetHandlers()
|
||||
m_fpGIFRegHandlers[GIF_A_D_REG_LABEL] = &GSState::GIFRegHandlerNull;
|
||||
}
|
||||
|
||||
void GSState::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
|
||||
{
|
||||
m_mipmap = GSConfig.Mipmap;
|
||||
|
||||
if (
|
||||
GSConfig.AutoFlushSW != old_config.AutoFlushSW ||
|
||||
GSConfig.UserHacks_AutoFlush != old_config.UserHacks_AutoFlush ||
|
||||
GSConfig.UserHacks_WildHack != old_config.UserHacks_WildHack)
|
||||
{
|
||||
ResetHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
bool GSState::isinterlaced()
|
||||
{
|
||||
return !!m_regs->SMODE2.INT;
|
||||
@@ -543,6 +556,100 @@ float GSState::GetTvRefreshRate()
|
||||
__assume(0); // unreachable
|
||||
}
|
||||
|
||||
void GSState::DumpVertices(const std::string& filename)
|
||||
{
|
||||
std::ofstream file(filename);
|
||||
|
||||
if (!file.is_open())
|
||||
return;
|
||||
|
||||
size_t count = m_index.tail;
|
||||
GSVertex* buffer = &m_vertex.buff[0];
|
||||
|
||||
const char* DEL = ", ";
|
||||
|
||||
file << "VERTEX COORDS (XYZ)" << std::endl;
|
||||
file << std::fixed << std::setprecision(4);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
file << "\t" << "v" << i << ": ";
|
||||
GSVertex v = buffer[m_index.buff[i]];
|
||||
|
||||
float x = (v.XYZ.X - (int)m_context->XYOFFSET.OFX) / 16.0f;
|
||||
float y = (v.XYZ.Y - (int)m_context->XYOFFSET.OFY) / 16.0f;
|
||||
|
||||
file << x << DEL;
|
||||
file << y << DEL;
|
||||
file << v.XYZ.Z;
|
||||
file << std::endl;
|
||||
}
|
||||
|
||||
file << std::endl;
|
||||
|
||||
file << "VERTEX COLOR (RGBA)" << std::endl;
|
||||
file << std::fixed << std::setprecision(6);
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
file << "\t" << "v" << i << ": ";
|
||||
GSVertex v = buffer[m_index.buff[i]];
|
||||
|
||||
file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.R) << DEL;
|
||||
file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.G) << DEL;
|
||||
file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.B) << DEL;
|
||||
file << std::setfill('0') << std::setw(3) << unsigned(v.RGBAQ.A);
|
||||
file << std::endl;
|
||||
}
|
||||
|
||||
file << std::endl;
|
||||
|
||||
bool use_uv = PRIM->FST;
|
||||
std::string qualifier = use_uv ? "UV" : "STQ";
|
||||
|
||||
file << "TEXTURE COORDS (" << qualifier << ")" << std::endl;;
|
||||
for (size_t i = 0; i < count; ++i)
|
||||
{
|
||||
file << "\t" << "v" << i << ": ";
|
||||
GSVertex v = buffer[m_index.buff[i]];
|
||||
|
||||
// note
|
||||
// Yes, technically as far as the GS is concerned Q belongs
|
||||
// to RGBAQ. However, the purpose of this dump is to print
|
||||
// our data in a more human readable format and typically Q
|
||||
// is associated with STQ.
|
||||
if (use_uv)
|
||||
{
|
||||
float uv_U = v.U / 16.0f;
|
||||
float uv_V = v.V / 16.0f;
|
||||
|
||||
file << uv_U << DEL << uv_V;
|
||||
}
|
||||
else
|
||||
file << v.ST.S << DEL << v.ST.T << DEL << v.RGBAQ.Q;
|
||||
|
||||
file << std::endl;
|
||||
}
|
||||
|
||||
file << std::endl;
|
||||
|
||||
file << "TRACER" << std::endl;
|
||||
|
||||
GSVector4i v = m_vt.m_min.c;
|
||||
file << "\tmin c (x,y,z,w): " << v.x << DEL << v.y << DEL << v.z << DEL << v.w << std::endl;
|
||||
v = m_vt.m_max.c;
|
||||
file << "\tmax c (x,y,z,w): " << v.x << DEL << v.y << DEL << v.z << DEL << v.w << std::endl;
|
||||
|
||||
GSVector4 v2 = m_vt.m_min.p;
|
||||
file << "\tmin p (x,y,z,w): " << v2.x << DEL << v2.y << DEL << v2.z << DEL << v2.w << std::endl;
|
||||
v2 = m_vt.m_max.p;
|
||||
file << "\tmax p (x,y,z,w): " << v2.x << DEL << v2.y << DEL << v2.z << DEL << v2.w << std::endl;
|
||||
v2 = m_vt.m_min.t;
|
||||
file << "\tmin t (x,y,z,w): " << v2.x << DEL << v2.y << DEL << v2.z << DEL << v2.w << std::endl;
|
||||
v2 = m_vt.m_max.t;
|
||||
file << "\tmax t (x,y,z,w): " << v2.x << DEL << v2.y << DEL << v2.z << DEL << v2.w << std::endl;
|
||||
|
||||
file.close();
|
||||
}
|
||||
|
||||
void GSState::GIFPackedRegHandlerNull(const GIFPackedReg* RESTRICT r)
|
||||
{
|
||||
}
|
||||
@@ -894,7 +1001,10 @@ void GSState::GIFRegHandlerTEX0(const GIFReg* RESTRICT r)
|
||||
GL_REG("TEX0_%d = 0x%x_%x", i, r->U32[1], r->U32[0]);
|
||||
|
||||
GIFRegTEX0 TEX0 = r->TEX0;
|
||||
const bool MTBAreload = ((tex_flushed && (i == m_env.PRIM.CTXT)) || (r->TEX0.TBP0 != m_env.CTXT[i].TEX0.TBP0)) ? true : false;
|
||||
GIFRegMIPTBP1 temp_MIPTBP1;
|
||||
bool MTBAReloaded = false;
|
||||
// Max allowed MTBA size for 32bit swizzled textures (including 8H 4HL etc) is 512, 16bit and normal 8/4bit formats can be 1024
|
||||
const u32 maxTex = (GSLocalMemory::m_psm[TEX0.PSM].bpp < 32) ? 10 : 9;
|
||||
|
||||
// Spec max is 10
|
||||
//
|
||||
@@ -910,61 +1020,53 @@ void GSState::GIFRegHandlerTEX0(const GIFReg* RESTRICT r)
|
||||
TEX0.TW = std::clamp<u32>(TEX0.TW, 0, 10);
|
||||
TEX0.TH = std::clamp<u32>(TEX0.TH, 0, 10);
|
||||
|
||||
ApplyTEX0<i>(TEX0);
|
||||
|
||||
// When the texture cache reloads any MIPS need to update too, so let's do that here.
|
||||
// This is essentially triggered by the texture page change, so if TEXFLUSH is called and a draw doesn't proceed it, or TBP changes.
|
||||
// Textures must be of equal width/height and a minimum of 32x32
|
||||
if (MTBAreload && m_env.CTXT[i].TEX1.MTBA && m_env.CTXT[i].TEX0.TW == m_env.CTXT[i].TEX0.TH && m_env.CTXT[i].TEX0.TW >= 5)
|
||||
// MTBA loads are triggered by writes to TEX0 (but not TEX2!)
|
||||
// Textures MUST be a minimum width of 32 pixels
|
||||
// Format must be a color, Z formats do not trigger MTBA (but are valid for Mipmapping)
|
||||
if (m_env.CTXT[i].TEX1.MTBA && TEX0.TW >= 5 && TEX0.TW <= maxTex && (TEX0.PSM & 0x30) != 0x30)
|
||||
{
|
||||
// NOTE 1: TEX1.MXL must not be automatically set to 3 here.
|
||||
// NOTE 2: Mipmap levels are tightly packed, if (tbw << 6) > (1 << tw) then the left-over space to the right is used. (common for PSM_PSMT4)
|
||||
// NOTE 3: Non-rectangular textures are treated as rectangular when calculating the occupied space (height is extended, not sure about width)
|
||||
// NOTE 1: TEX1.MXL must not be automatically set to 3 here and it has no effect on MTBA.
|
||||
// NOTE 2: Mipmap levels are packed with a minimum distance between them of 1 block, even down at 4bit textures under 16x16.
|
||||
// NOTE 3: Everything is derrived from the width of the texture, TBW and TH are completely ignored (useful for handling non-rectangular ones)
|
||||
// NOTE 4: Cartoon Network Racing's menu is VERY sensitive to this as it uses 4bit sized textures for the sky.
|
||||
u32 bp = TEX0.TBP0;
|
||||
u32 bw = std::max(1u, (1u << TEX0.TW) >> 6);
|
||||
|
||||
u32 bp = m_env.CTXT[i].TEX0.TBP0;
|
||||
u32 bw = m_env.CTXT[i]. TEX0.TBW;
|
||||
u32 w = 1u << m_env.CTXT[i].TEX0.TW;
|
||||
u32 h = 1u << m_env.CTXT[i].TEX0.TH;
|
||||
u32 minwidth = m_env.CTXT[i].TEX1.MMIN >= 4 ? 8 : 1;
|
||||
// Address is calculated as a 4bit address space, then converted (/8) to 32bit address space
|
||||
// ((w * w * bpp) / 8) / 64. No the 'w' is not a typo ;)
|
||||
const u32 bpp = GSLocalMemory::m_psm[TEX0.PSM].bpp >> 2;
|
||||
u32 tex_size = ((1u << TEX0.TW) * (1u << TEX0.TW) * bpp) >> 9;
|
||||
|
||||
const u32 bpp = GSLocalMemory::m_psm[m_env.CTXT[i].TEX0.PSM].bpp;
|
||||
bp += tex_size;
|
||||
bw = std::max<u32>(bw >> 1, 1);
|
||||
tex_size = std::max<u32>(tex_size >> 2, 1);
|
||||
|
||||
bp += (int)((w * h * ((float)bpp / 8))) >> 8;
|
||||
temp_MIPTBP1.TBP1 = bp;
|
||||
temp_MIPTBP1.TBW1 = bw;
|
||||
|
||||
if (w > minwidth)
|
||||
{
|
||||
bw = std::max<u32>(bw >> 1, 1);
|
||||
w = std::max<u32>(w >> 1, 1);
|
||||
h = std::max<u32>(h >> 1, 1);
|
||||
}
|
||||
bp += tex_size;
|
||||
bw = std::max<u32>(bw >> 1, 1);
|
||||
tex_size = std::max<u32>(tex_size >> 2, 1);
|
||||
|
||||
m_env.CTXT[i].MIPTBP1.TBP1 = bp;
|
||||
m_env.CTXT[i].MIPTBP1.TBW1 = bw;
|
||||
temp_MIPTBP1.TBP2 = bp;
|
||||
temp_MIPTBP1.TBW2 = bw;
|
||||
|
||||
bp += (int)((w * h * ((float)bpp / 8))) >> 8;
|
||||
bp += tex_size;
|
||||
bw = std::max<u32>(bw >> 1, 1);
|
||||
|
||||
if (w > minwidth)
|
||||
{
|
||||
bw = std::max<u32>(bw >> 1, 1);
|
||||
w = std::max<u32>(w >> 1, 1);
|
||||
h = std::max<u32>(h >> 1, 1);
|
||||
}
|
||||
temp_MIPTBP1.TBP3 = bp;
|
||||
temp_MIPTBP1.TBW3 = bw;
|
||||
|
||||
m_env.CTXT[i].MIPTBP1.TBP2 = bp;
|
||||
m_env.CTXT[i].MIPTBP1.TBW2 = bw;
|
||||
if (temp_MIPTBP1 != m_env.CTXT[i].MIPTBP1)
|
||||
Flush();
|
||||
|
||||
bp += (int)((w * h * ((float)bpp / 8))) >> 8;
|
||||
|
||||
if (w > minwidth)
|
||||
{
|
||||
bw = std::max<u32>(bw >> 1, 1);
|
||||
}
|
||||
|
||||
m_env.CTXT[i].MIPTBP1.TBP3 = bp;
|
||||
m_env.CTXT[i].MIPTBP1.TBW3 = bw;
|
||||
MTBAReloaded = true;
|
||||
}
|
||||
|
||||
tex_flushed = false;
|
||||
ApplyTEX0<i>(TEX0);
|
||||
|
||||
if (MTBAReloaded)
|
||||
m_env.CTXT[i].MIPTBP1 = temp_MIPTBP1;
|
||||
}
|
||||
|
||||
template <int i>
|
||||
@@ -1132,11 +1234,10 @@ void GSState::GIFRegHandlerTEXFLUSH(const GIFReg* RESTRICT r)
|
||||
{
|
||||
GL_REG("TEXFLUSH = 0x%x_%x PRIM TME %x", r->U32[1], r->U32[0], PRIM->TME);
|
||||
|
||||
tex_flushed = true;
|
||||
// Some games do a single sprite draw to itself, then flush the texture cache, then use that texture again.
|
||||
// This won't get picked up by the new autoflush logic (which checks for page crossings for the PS2 Texture Cache flush)
|
||||
// so we need to do it here.
|
||||
if(m_userhacks_auto_flush)
|
||||
if (IsAutoFlushEnabled())
|
||||
Flush();
|
||||
}
|
||||
|
||||
@@ -1705,14 +1806,59 @@ void GSState::Move()
|
||||
int _sy = sy, _dy = dy; // Faster with local copied variables, compiler optimizations are dumb
|
||||
if (xinc > 0)
|
||||
{
|
||||
for (int y = 0; y < h; y++, _sy += yinc, _dy += yinc)
|
||||
const int page_width = GSLocalMemory::m_psm[m_env.BITBLTBUF.DPSM].pgs.x;
|
||||
const int page_height = GSLocalMemory::m_psm[m_env.BITBLTBUF.DPSM].pgs.y;
|
||||
const int xpage = sx & ~(page_width - 1);
|
||||
const int ypage = _sy & ~(page_height - 1);
|
||||
// Copying from itself to itself (rotating textures) used in Gitaroo Man stage 8
|
||||
// What probably happens is because the copy is buffered, the source stays just ahead of the destination.
|
||||
if (sbp == dbp && (((_sy < _dy) && ((ypage + page_height) > _dy)) || ((sx < dx) && ((xpage + page_width) > dx))))
|
||||
{
|
||||
auto s = getPAHelper(spo, sx, _sy);
|
||||
auto d = getPAHelper(dpo, dx, _dy);
|
||||
int starty = 0;
|
||||
int endy = h;
|
||||
int y_inc = yinc;
|
||||
|
||||
for (int x = 0; x < w; x++)
|
||||
if (((_sy < _dy) && ((ypage + page_height) > _dy)))
|
||||
{
|
||||
pxCopyFn(d, s, x);
|
||||
_sy += h;
|
||||
_dy += h;
|
||||
starty = h-1;
|
||||
endy = -1;
|
||||
y_inc = -y_inc;
|
||||
}
|
||||
|
||||
for (int y = starty; y != endy; y+= y_inc, _sy += y_inc, _dy += y_inc)
|
||||
{
|
||||
auto s = getPAHelper(spo, sx, _sy);
|
||||
auto d = getPAHelper(dpo, dx, _dy);
|
||||
|
||||
if (((sx < dx) && ((xpage + page_width) > dx)))
|
||||
{
|
||||
for (int x = w - 1; x >= 0; x--)
|
||||
{
|
||||
pxCopyFn(d, s, x);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
pxCopyFn(d, s, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int y = 0; y < h; y++, _sy += yinc, _dy += yinc)
|
||||
{
|
||||
auto s = getPAHelper(spo, sx, _sy);
|
||||
auto d = getPAHelper(dpo, dx, _dy);
|
||||
|
||||
for (int x = 0; x < w; x++)
|
||||
{
|
||||
pxCopyFn(d, s, x);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2127,7 +2273,7 @@ int GSState::Defrost(const freezeData* fd)
|
||||
|
||||
u8* data = fd->data;
|
||||
|
||||
int version;
|
||||
u32 version;
|
||||
|
||||
ReadState(&version, data);
|
||||
|
||||
@@ -2433,10 +2579,17 @@ GSState::PRIM_OVERLAP GSState::PrimitiveOverlap()
|
||||
|
||||
__forceinline void GSState::HandleAutoFlush()
|
||||
{
|
||||
const u32 frame_mask = GSLocalMemory::m_psm[m_context->TEX0.PSM].fmsk;
|
||||
const bool frame_hit = (m_context->FRAME.Block() == m_context->TEX0.TBP0) && !(m_context->TEST.ATE && m_context->TEST.ATST == 0 && m_context->TEST.AFAIL == 2) && ((m_context->FRAME.FBMSK & frame_mask) != frame_mask);
|
||||
// There's a strange behaviour we need to test on a PS2 here, if the FRAME is a Z format, like Powerdrome something swaps over, and it seems Alpha Fail of "FB Only" writes to the Z.. it's odd.
|
||||
const bool zbuf_hit = (m_context->ZBUF.Block() == m_context->TEX0.TBP0) && !(m_context->TEST.ATE && m_context->TEST.ATST == 0 && m_context->TEST.AFAIL != 2) && !m_context->ZBUF.ZMSK;
|
||||
const u32 frame_z_psm = frame_hit ? m_context->FRAME.PSM : m_context->ZBUF.PSM;
|
||||
const u32 frame_z_bp = frame_hit ? m_context->FRAME.Block() : m_context->ZBUF.Block();
|
||||
|
||||
// To briefly explain what's going on here, what we are checking for is draws over a texture when the source and destination are themselves.
|
||||
// Because one page of the texture gets buffered in the Texture Cache (the PS2's one) if any of those pixels are overwritten, you still read the old data.
|
||||
// So we need to calculate if a page boundary is being crossed for the format it is in and if the same part of the texture being written and read inside the draw.
|
||||
if ((m_context->FRAME.Block() == m_context->TEX0.TBP0) && (m_context->TEX0.PSM == m_context->FRAME.PSM) && PRIM->TME && (m_context->FRAME.FBMSK != 0xFFFFFFFF))
|
||||
if (PRIM->TME && (frame_hit || zbuf_hit) && GSUtil::HasSharedBits(frame_z_bp, frame_z_psm, m_context->TEX0.TBP0, m_context->TEX0.PSM))
|
||||
{
|
||||
const int page_mask_x = ~(GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.x - 1);
|
||||
const int page_mask_y = ~(GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.y - 1);
|
||||
@@ -2513,14 +2666,46 @@ __forceinline void GSState::HandleAutoFlush()
|
||||
|
||||
if(page_crossed)
|
||||
{
|
||||
// Update the vertex trace, scissor it (important for Jak 3!) and intersect with the current texture.
|
||||
if((m_index.tail - 1) == current_tex_end)
|
||||
m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail - m_vertex.head, m_index.tail, GSUtil::GetPrimClass(PRIM->PRIM));
|
||||
|
||||
GSVector4i area_out = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
if (!area_out.rintersect(tex_rect).rempty())
|
||||
// Make sure the format matches, otherwise the coordinates aren't gonna match, so the draws won't intersect.
|
||||
if (GSUtil::HasCompatibleBits(m_context->TEX0.PSM, frame_z_psm) && (m_context->FRAME.FBW == m_context->TEX0.TBW))
|
||||
{
|
||||
Flush();
|
||||
// Update the vertex trace, scissor it (important for Jak 3!) and intersect with the current texture.
|
||||
if ((m_index.tail - 1) == current_tex_end)
|
||||
m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail - m_vertex.head, m_index.tail, GSUtil::GetPrimClass(PRIM->PRIM));
|
||||
|
||||
// Intersect goes on space inside the rect
|
||||
GSVector4i area_out = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)) + GSVector4i(0, 0, 1, 1);
|
||||
// Scissor output
|
||||
area_out = area_out.rintersect(GSVector4i(m_context->scissor.in));
|
||||
// Intersect with texture
|
||||
if (!area_out.rintersect(tex_rect).rempty())
|
||||
Flush();
|
||||
}
|
||||
else // Storage of the TEX and FRAME/Z is different, so uhh, just fall back to flushing each page. It's slower, sorry.
|
||||
{
|
||||
|
||||
if (m_context->FRAME.FBW == m_context->TEX0.TBW)
|
||||
{
|
||||
//We know we've changed page, so let's set the dimension to cover the page they're in (for different pixel orders)
|
||||
tex_rect = tex_rect & page_mask;
|
||||
tex_rect += GSVector4i(0, 0, 1, 1); // Intersect goes on space inside the rect
|
||||
tex_rect.z += GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.x;
|
||||
tex_rect.w += GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.y;
|
||||
|
||||
if ((m_index.tail - 1) == current_tex_end)
|
||||
m_vt.Update(m_vertex.buff, m_index.buff, m_vertex.tail - m_vertex.head, m_index.tail, GSUtil::GetPrimClass(PRIM->PRIM));
|
||||
|
||||
GSVector4i area_out = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(m_context->scissor.in));
|
||||
area_out = area_out & page_mask;
|
||||
area_out += GSVector4i(0, 0, 1, 1); // Intersect goes on space inside the rect
|
||||
area_out.z += GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.x;
|
||||
area_out.w += GSLocalMemory::m_psm[m_context->TEX0.PSM].pgs.y;
|
||||
|
||||
if (!area_out.rintersect(tex_rect).rempty())
|
||||
Flush();
|
||||
}
|
||||
else // Page width is different, so it's much more difficult to calculate where it's modifying.
|
||||
Flush();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -2534,29 +2719,19 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
switch (prim)
|
||||
{
|
||||
case GS_POINTLIST:
|
||||
case GS_INVALID:
|
||||
n = 1;
|
||||
break;
|
||||
case GS_LINELIST:
|
||||
n = 2;
|
||||
break;
|
||||
case GS_SPRITE:
|
||||
case GS_LINESTRIP:
|
||||
n = 2;
|
||||
break;
|
||||
case GS_TRIANGLELIST:
|
||||
n = 3;
|
||||
break;
|
||||
case GS_TRIANGLESTRIP:
|
||||
n = 3;
|
||||
break;
|
||||
case GS_TRIANGLEFAN:
|
||||
n = 3;
|
||||
break;
|
||||
case GS_SPRITE:
|
||||
n = 2;
|
||||
break;
|
||||
case GS_INVALID:
|
||||
n = 1;
|
||||
break;
|
||||
}
|
||||
|
||||
if (m_context->FRAME.FBMSK != 0xFFFFFFFF)
|
||||
@@ -2768,9 +2943,6 @@ __forceinline void GSState::VertexKick(u32 skip)
|
||||
default:
|
||||
__assume(0);
|
||||
}
|
||||
|
||||
if (PRIM->TME)
|
||||
tex_flushed = false;
|
||||
}
|
||||
|
||||
/// Checks if region repeat is used (applying it does something to at least one of the values in min...max)
|
||||
@@ -3074,8 +3246,9 @@ bool GSState::TryAlphaTest(u32& fm, u32& zm)
|
||||
}
|
||||
else
|
||||
{
|
||||
const int amin = GetAlphaMinMax().min;
|
||||
const int amax = GetAlphaMinMax().max;
|
||||
const GSVertexTrace::VertexAlpha& aminmax = GetAlphaMinMax();
|
||||
const int amin = aminmax.min;
|
||||
const int amax = aminmax.max;
|
||||
|
||||
const int aref = m_context->TEST.AREF;
|
||||
|
||||
@@ -3201,7 +3374,7 @@ bool GSState::IsOpaque()
|
||||
|
||||
bool GSState::IsMipMapDraw()
|
||||
{
|
||||
return m_context->TEX1.MXL > 0 && m_context->TEX1.MMIN >= 2 && m_context->TEX1.MMIN <= 5 && m_vt.m_lod.y > 0 && (!m_context->TEX1.MTBA || m_context->TEX0.TH == m_context->TEX0.TW);
|
||||
return m_context->TEX1.MXL > 0 && m_context->TEX1.MMIN >= 2 && m_context->TEX1.MMIN <= 5 && m_vt.m_lod.y > 0;
|
||||
}
|
||||
|
||||
bool GSState::IsMipMapActive()
|
||||
|
||||
+7
-4
@@ -123,7 +123,7 @@ class GSState : public GSAlignedClass<32>
|
||||
template<bool auto_flush, bool index_swap>
|
||||
void SetPrimHandlers();
|
||||
|
||||
int m_version;
|
||||
u32 m_version;
|
||||
int m_sssize;
|
||||
|
||||
struct GSTransferBuffer
|
||||
@@ -149,14 +149,11 @@ protected:
|
||||
bool IsBadFrame();
|
||||
void SetupCrcHack();
|
||||
|
||||
bool m_userhacks_wildhack;
|
||||
bool m_isPackedUV_HackFlag;
|
||||
CRCHackLevel m_crc_hack_level;
|
||||
GetSkipCount m_gsc;
|
||||
int m_skip;
|
||||
int m_skip_offset;
|
||||
bool m_userhacks_auto_flush;
|
||||
bool tex_flushed;
|
||||
|
||||
GSVertex m_v;
|
||||
float m_q;
|
||||
@@ -164,6 +161,7 @@ protected:
|
||||
GSVector4i m_ofxy;
|
||||
|
||||
bool m_scanmask_used;
|
||||
bool tex_flushed;
|
||||
|
||||
struct
|
||||
{
|
||||
@@ -245,6 +243,8 @@ public:
|
||||
int s_savel;
|
||||
std::string m_dump_root;
|
||||
|
||||
static constexpr u32 STATE_VERSION = 8;
|
||||
|
||||
enum PRIM_OVERLAP
|
||||
{
|
||||
PRIM_OVERLAP_UNKNOW,
|
||||
@@ -273,6 +273,8 @@ public:
|
||||
float GetTvRefreshRate();
|
||||
|
||||
virtual void Reset();
|
||||
virtual void UpdateSettings(const Pcsx2Config::GSOptions& old_config);
|
||||
|
||||
void Flush();
|
||||
void FlushPrim();
|
||||
void FlushWrite();
|
||||
@@ -301,6 +303,7 @@ public:
|
||||
void SetRegsMem(u8* basemem) { m_regs = reinterpret_cast<GSPrivRegSet*>(basemem); }
|
||||
|
||||
void SetFrameSkip(int skip);
|
||||
void DumpVertices(const std::string& filename);
|
||||
|
||||
PRIM_OVERLAP PrimitiveOverlap();
|
||||
GIFRegTEX0 GetTex0Layer(u32 lod);
|
||||
|
||||
@@ -20,11 +20,36 @@
|
||||
#include "HostDisplay.h"
|
||||
#include "PerformanceMetrics.h"
|
||||
#include "pcsx2/Config.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#ifndef PCSX2_CORE
|
||||
#include "gui/AppCoreThread.h"
|
||||
#if defined(__unix__)
|
||||
#include <X11/keysym.h>
|
||||
#endif
|
||||
|
||||
static std::string GetDumpName()
|
||||
{
|
||||
return StringUtil::wxStringToUTF8String(GameInfo::gameName);
|
||||
}
|
||||
static std::string GetDumpSerial()
|
||||
{
|
||||
return StringUtil::wxStringToUTF8String(GameInfo::gameSerial);
|
||||
}
|
||||
#else
|
||||
#include "VMManager.h"
|
||||
|
||||
static std::string GetDumpName()
|
||||
{
|
||||
return VMManager::GetGameName();
|
||||
}
|
||||
static std::string GetDumpSerial()
|
||||
{
|
||||
return VMManager::GetGameSerial();
|
||||
}
|
||||
#endif
|
||||
|
||||
GSRenderer::GSRenderer()
|
||||
: m_shift_key(false)
|
||||
, m_control_key(false)
|
||||
@@ -454,10 +479,26 @@ void GSRenderer::VSync(u32 field, bool registers_written)
|
||||
fd.data = new u8[fd.size];
|
||||
Freeze(&fd, false);
|
||||
|
||||
// keep the screenshot relatively small so we don't bloat the dump
|
||||
static constexpr u32 DUMP_SCREENSHOT_WIDTH = 640;
|
||||
static constexpr u32 DUMP_SCREENSHOT_HEIGHT = 480;
|
||||
std::vector<u32> screenshot_pixels;
|
||||
SaveSnapshotToMemory(DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT, &screenshot_pixels);
|
||||
|
||||
if (m_control_key)
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDump(m_snapshot, m_crc, fd, m_regs));
|
||||
{
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpUncompressed(m_snapshot, GetDumpSerial(), m_crc,
|
||||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
}
|
||||
else
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, m_crc, fd, m_regs));
|
||||
{
|
||||
m_dump = std::unique_ptr<GSDumpBase>(new GSDumpXz(m_snapshot, GetDumpSerial(), m_crc,
|
||||
DUMP_SCREENSHOT_WIDTH, DUMP_SCREENSHOT_HEIGHT,
|
||||
screenshot_pixels.empty() ? nullptr : screenshot_pixels.data(),
|
||||
fd, m_regs));
|
||||
}
|
||||
|
||||
delete[] fd.data;
|
||||
}
|
||||
@@ -528,6 +569,18 @@ bool GSRenderer::MakeSnapshot(const std::string& path)
|
||||
}
|
||||
prev_snap = cur_time;
|
||||
}
|
||||
|
||||
// append the game serial and title
|
||||
if (std::string name(GetDumpName()); !name.empty())
|
||||
{
|
||||
FileSystem::SanitizeFileName(name);
|
||||
m_snapshot += format("_%s", name.c_str());
|
||||
}
|
||||
if (std::string serial(GetDumpSerial()); !serial.empty())
|
||||
{
|
||||
FileSystem::SanitizeFileName(serial);
|
||||
m_snapshot += format("_%s", serial.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -546,7 +599,7 @@ void GSRenderer::EndCapture()
|
||||
|
||||
void GSRenderer::KeyEvent(const HostKeyEvent& e)
|
||||
{
|
||||
#ifndef __APPLE__ // TODO: Add hotkey support on macOS
|
||||
#if !defined(PCSX2_CORE) && !defined(__APPLE__) // TODO: Add hotkey support on macOS
|
||||
#ifdef _WIN32
|
||||
m_shift_key = !!(::GetAsyncKeyState(VK_SHIFT) & 0x8000);
|
||||
m_control_key = !!(::GetAsyncKeyState(VK_CONTROL) & 0x8000);
|
||||
|
||||
@@ -21,13 +21,15 @@
|
||||
|
||||
CONSTINIT const GSVector4 GSVertexTrace::s_minmax = GSVector4::cxpr(FLT_MAX, -FLT_MAX, 0.f, 0.f);
|
||||
|
||||
GSVertexTrace::GSVertexTrace(const GSState* state)
|
||||
GSVertexTrace::GSVertexTrace(const GSState* state, bool provoking_vertex_first)
|
||||
: m_accurate_stq(false), m_state(state), m_primclass(GS_INVALID_CLASS)
|
||||
{
|
||||
memset(&m_alpha, 0, sizeof(m_alpha));
|
||||
|
||||
#define InitUpdate3(P, IIP, TME, FST, COLOR) \
|
||||
m_fmm[COLOR][FST][TME][IIP][P] = &GSVertexTrace::FindMinMax<P, IIP, TME, FST, COLOR>;
|
||||
m_fmm[COLOR][FST][TME][IIP][P] = \
|
||||
provoking_vertex_first ? &GSVertexTrace::FindMinMax<P, IIP, TME, FST, COLOR, true> : \
|
||||
&GSVertexTrace::FindMinMax<P, IIP, TME, FST, COLOR, false>;
|
||||
|
||||
#define InitUpdate2(P, IIP, TME) \
|
||||
InitUpdate3(P, IIP, TME, 0, 0) \
|
||||
@@ -151,7 +153,7 @@ void GSVertexTrace::Update(const void* vertex, const u32* index, int v_count, in
|
||||
}
|
||||
}
|
||||
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color, bool flat_swapped>
|
||||
void GSVertexTrace::FindMinMax(const void* vertex, const u32* index, int count)
|
||||
{
|
||||
const GSDrawingContext* context = m_state->m_context;
|
||||
@@ -283,16 +285,16 @@ void GSVertexTrace::FindMinMax(const void* vertex, const u32* index, int count)
|
||||
int i = 0;
|
||||
for (; i < (count - 3); i += 6)
|
||||
{
|
||||
processVertices(v[index[i + 0]], v[index[i + 3]], false);
|
||||
processVertices(v[index[i + 0]], v[index[i + 3]], flat_swapped);
|
||||
processVertices(v[index[i + 1]], v[index[i + 4]], false);
|
||||
processVertices(v[index[i + 2]], v[index[i + 5]], true);
|
||||
processVertices(v[index[i + 2]], v[index[i + 5]], !flat_swapped);
|
||||
}
|
||||
if (count & 1)
|
||||
{
|
||||
processVertices(v[index[i + 0]], v[index[i + 1]], false);
|
||||
processVertices(v[index[i + 0]], v[index[i + 1]], flat_swapped);
|
||||
// Compiler optimizations go!
|
||||
// (And if they don't, it's only one vertex out of many)
|
||||
processVertices(v[index[i + 2]], v[index[i + 2]], true);
|
||||
processVertices(v[index[i + 2]], v[index[i + 2]], !flat_swapped);
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
@@ -48,7 +48,7 @@ protected:
|
||||
|
||||
FindMinMaxPtr m_fmm[2][2][2][2][4];
|
||||
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color>
|
||||
template <GS_PRIM_CLASS primclass, u32 iip, u32 tme, u32 fst, u32 color, bool provoking_vertex_first>
|
||||
void FindMinMax(const void* vertex, const u32* index, int count);
|
||||
|
||||
public:
|
||||
@@ -73,7 +73,7 @@ public:
|
||||
GSVector2 m_lod; // x = min, y = max
|
||||
|
||||
public:
|
||||
GSVertexTrace(const GSState* state);
|
||||
GSVertexTrace(const GSState* state, bool provoking_vertex_first);
|
||||
virtual ~GSVertexTrace() {}
|
||||
|
||||
void Update(const void* vertex, const u32* index, int v_count, int i_count, GS_PRIM_CLASS primclass);
|
||||
|
||||
@@ -18,7 +18,6 @@
|
||||
|
||||
bool s_nativeres;
|
||||
static CRCHackLevel s_crc_hack_level = CRCHackLevel::Full;
|
||||
|
||||
// hacks
|
||||
#define CRC_Partial (s_crc_hack_level >= CRCHackLevel::Partial)
|
||||
#define CRC_Full (s_crc_hack_level >= CRCHackLevel::Full)
|
||||
@@ -419,7 +418,7 @@ bool GSC_BurnoutGames(const GSFrameInfo& fi, int& skip)
|
||||
// 0x01dc0 01c00(MP) ntsc, 0x01f00 0x01d40(MP) ntsc progressive, 0x02200(MP) pal.
|
||||
// Yellow stripes.
|
||||
// Multiplayer tested only on Takedown.
|
||||
skip = 4;
|
||||
skip = GSConfig.UserHacks_AutoFlush ? 2 : 4;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1054,7 +1053,7 @@ bool GSState::IsBadFrame()
|
||||
return false;
|
||||
}
|
||||
|
||||
if (m_skip == 0 && GSConfig.UserHacks && (GSConfig.SkipDraw > 0))
|
||||
if (m_skip == 0 && GSConfig.SkipDrawEnd > 0)
|
||||
{
|
||||
if (fi.TME)
|
||||
{
|
||||
@@ -1062,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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -25,10 +25,8 @@ GSRendererHW::GSRendererHW()
|
||||
, m_height(default_rt_size.y)
|
||||
, m_custom_width(1024)
|
||||
, m_custom_height(1024)
|
||||
, m_userhacks_ts_half_bottom(-1)
|
||||
, m_tc(new GSTextureCache(this))
|
||||
, m_src(nullptr)
|
||||
, m_hw_mipmap(GSConfig.HWMipmap)
|
||||
, m_userhacks_tcoffset(false)
|
||||
, m_userhacks_tcoffset_x(0)
|
||||
, m_userhacks_tcoffset_y(0)
|
||||
@@ -36,30 +34,8 @@ GSRendererHW::GSRendererHW()
|
||||
, m_reset(false)
|
||||
, m_lod(GSVector2i(0, 0))
|
||||
{
|
||||
m_mipmap = (m_hw_mipmap >= HWMipmapLevel::Basic);
|
||||
m_conservative_framebuffer = theApp.GetConfigB("conservative_framebuffer");
|
||||
|
||||
if (theApp.GetConfigB("UserHacks"))
|
||||
{
|
||||
m_userhacks_enabled_gs_mem_clear = !theApp.GetConfigB("UserHacks_Disable_Safe_Features");
|
||||
m_userHacks_enabled_unscale_ptln = !theApp.GetConfigB("UserHacks_Disable_Safe_Features");
|
||||
m_userhacks_align_sprite_X = theApp.GetConfigB("UserHacks_align_sprite_X");
|
||||
m_userHacks_merge_sprite = theApp.GetConfigB("UserHacks_merge_pp_sprite");
|
||||
m_userhacks_ts_half_bottom = theApp.GetConfigI("UserHacks_Half_Bottom_Override");
|
||||
m_userhacks_round_sprite_offset = theApp.GetConfigI("UserHacks_round_sprite_offset");
|
||||
m_userhacks_tcoffset_x = theApp.GetConfigI("UserHacks_TCOffsetX") / -1000.0f;
|
||||
m_userhacks_tcoffset_y = theApp.GetConfigI("UserHacks_TCOffsetY") / -1000.0f;
|
||||
m_userhacks_tcoffset = m_userhacks_tcoffset_x < 0.0f || m_userhacks_tcoffset_y < 0.0f;
|
||||
}
|
||||
else
|
||||
{
|
||||
m_userhacks_enabled_gs_mem_clear = true;
|
||||
m_userHacks_enabled_unscale_ptln = true;
|
||||
m_userhacks_align_sprite_X = false;
|
||||
m_userHacks_merge_sprite = false;
|
||||
m_userhacks_ts_half_bottom = -1;
|
||||
m_userhacks_round_sprite_offset = 0;
|
||||
}
|
||||
m_mipmap = (GSConfig.HWMipmap >= HWMipmapLevel::Basic);
|
||||
SetTCOffset();
|
||||
|
||||
if (!GSConfig.UpscaleMultiplier) // Custom Resolution
|
||||
{
|
||||
@@ -67,13 +43,6 @@ GSRendererHW::GSRendererHW()
|
||||
m_custom_height = m_height = theApp.GetConfigI("resy");
|
||||
}
|
||||
|
||||
if (GSConfig.UpscaleMultiplier == 1) // hacks are only needed for upscaling issues.
|
||||
{
|
||||
m_userhacks_round_sprite_offset = 0;
|
||||
m_userhacks_align_sprite_X = false;
|
||||
m_userHacks_merge_sprite = false;
|
||||
}
|
||||
|
||||
m_dump_root = root_hw;
|
||||
GSTextureReplacements::Initialize(m_tc);
|
||||
}
|
||||
@@ -127,7 +96,7 @@ void GSRendererHW::SetScaling()
|
||||
//
|
||||
// m_large_framebuffer has been inverted to m_conservative_framebuffer, it isn't an option that benefits being enabled all the time for everyone.
|
||||
int fb_height = 1280;
|
||||
if (m_conservative_framebuffer)
|
||||
if (GSConfig.ConservativeFramebuffer)
|
||||
{
|
||||
fb_height = fb_width < 1024 ? std::max(512, crtc_size.y) : 1024;
|
||||
}
|
||||
@@ -183,6 +152,13 @@ void GSRendererHW::CustomResolutionScaling()
|
||||
printf("Frame buffer size set to %dx%d (%dx%d)\n", scissored_buffer_size.x, scissored_buffer_size.y, m_width, m_height);
|
||||
}
|
||||
|
||||
void GSRendererHW::SetTCOffset()
|
||||
{
|
||||
m_userhacks_tcoffset_x = std::max<s32>(GSConfig.UserHacks_TCOffsetX, 0) / -1000.0f;
|
||||
m_userhacks_tcoffset_y = std::max<s32>(GSConfig.UserHacks_TCOffsetY, 0) / -1000.0f;
|
||||
m_userhacks_tcoffset = m_userhacks_tcoffset_x < 0.0f || m_userhacks_tcoffset_y < 0.0f;
|
||||
}
|
||||
|
||||
GSRendererHW::~GSRendererHW()
|
||||
{
|
||||
delete m_tc;
|
||||
@@ -207,63 +183,6 @@ void GSRendererHW::SetGameCRC(u32 crc, int options)
|
||||
|
||||
m_hacks.SetGameCRC(m_game);
|
||||
|
||||
// Code for Automatic Mipmapping. Relies on game CRCs.
|
||||
m_hw_mipmap = GSConfig.HWMipmap;
|
||||
m_mipmap = (m_hw_mipmap >= HWMipmapLevel::Basic);
|
||||
if (m_hw_mipmap == HWMipmapLevel::Automatic)
|
||||
{
|
||||
switch (CRC::Lookup(crc).title)
|
||||
{
|
||||
case CRC::AceCombatZero:
|
||||
case CRC::AceCombat4:
|
||||
case CRC::AceCombat5:
|
||||
case CRC::ApeEscape2:
|
||||
case CRC::Barnyard:
|
||||
case CRC::BrianLaraInternationalCricket:
|
||||
case CRC::DarkCloud:
|
||||
case CRC::DestroyAllHumans:
|
||||
case CRC::DestroyAllHumans2:
|
||||
case CRC::FIFA03:
|
||||
case CRC::FIFA04:
|
||||
case CRC::FIFA05:
|
||||
case CRC::HarryPotterATCOS:
|
||||
case CRC::HarryPotterATGOF:
|
||||
case CRC::HarryPotterATHBP:
|
||||
case CRC::HarryPotterATPOA:
|
||||
case CRC::HarryPotterOOTP:
|
||||
case CRC::ICO:
|
||||
case CRC::Jak1:
|
||||
case CRC::Jak3:
|
||||
case CRC::JurassicPark:
|
||||
case CRC::LegacyOfKainDefiance:
|
||||
case CRC::NicktoonsUnite:
|
||||
case CRC::Persona3:
|
||||
case CRC::ProjectSnowblind:
|
||||
case CRC::Quake3Revolution:
|
||||
case CRC::RatchetAndClank:
|
||||
case CRC::RatchetAndClank2:
|
||||
case CRC::RatchetAndClank3:
|
||||
case CRC::RatchetAndClank4:
|
||||
case CRC::RatchetAndClank5:
|
||||
case CRC::RickyPontingInternationalCricket:
|
||||
case CRC::Shox:
|
||||
case CRC::SlamTennis:
|
||||
case CRC::SoTC:
|
||||
case CRC::SoulReaver2:
|
||||
case CRC::TheIncredibleHulkUD:
|
||||
case CRC::TombRaiderAnniversary:
|
||||
case CRC::TribesAerialAssault:
|
||||
case CRC::Whiplash:
|
||||
m_hw_mipmap = HWMipmapLevel::Basic;
|
||||
m_mipmap = true;
|
||||
break;
|
||||
default:
|
||||
m_hw_mipmap = HWMipmapLevel::Off;
|
||||
m_mipmap = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
GSTextureReplacements::GameChanged();
|
||||
}
|
||||
|
||||
@@ -297,6 +216,13 @@ void GSRendererHW::Reset()
|
||||
GSRenderer::Reset();
|
||||
}
|
||||
|
||||
void GSRendererHW::UpdateSettings(const Pcsx2Config::GSOptions& old_config)
|
||||
{
|
||||
GSRenderer::UpdateSettings(old_config);
|
||||
m_mipmap = (GSConfig.HWMipmap >= HWMipmapLevel::Basic);
|
||||
SetTCOffset();
|
||||
}
|
||||
|
||||
void GSRendererHW::VSync(u32 field, bool registers_written)
|
||||
{
|
||||
if (m_reset)
|
||||
@@ -544,7 +470,7 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
read_ba = (tex_pos > 112 && tex_pos < 144);
|
||||
|
||||
bool half_bottom = false;
|
||||
switch (m_userhacks_ts_half_bottom)
|
||||
switch (GSConfig.UserHacks_HalfBottomOverride)
|
||||
{
|
||||
case 0:
|
||||
// Force Disabled.
|
||||
@@ -572,9 +498,30 @@ void GSRendererHW::ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba)
|
||||
//
|
||||
// 32bits emulation means we can do the effect once but double the size.
|
||||
// Test cases: Crash Twinsantiy and DBZ BT3
|
||||
const int height_delta = m_src->m_valid_rect.height() - m_r.height();
|
||||
// Test Case: NFS: HP2 splits the effect h:256 and h:192 so 64
|
||||
half_bottom = abs(height_delta) <= 64;
|
||||
// Other games: Midnight Club 3 headlights, black bar in Xenosaga 3 dialogue,
|
||||
// Firefighter FD18 fire occlusion, PSI Ops half screen green overlay, Lord of the Rings - Two Towers,
|
||||
// Demon Stone , Sonic Unleashed, Lord of the Rings Two Towers,
|
||||
// Superman Shadow of Apokolips, Matrix Path of Neo, Big Mutha Truckers
|
||||
|
||||
int maxvert = 0;
|
||||
int minvert = 4096;
|
||||
for (size_t i = 0; i < count; i ++)
|
||||
{
|
||||
int YCord = 0;
|
||||
|
||||
if (!PRIM->FST)
|
||||
YCord = (int)((1 << m_context->TEX0.TH) * (v[i].ST.T / v[i].RGBAQ.Q));
|
||||
else
|
||||
YCord = (v[i].V >> 4);
|
||||
|
||||
if (maxvert < YCord)
|
||||
maxvert = YCord;
|
||||
if (minvert > YCord)
|
||||
minvert = YCord;
|
||||
}
|
||||
|
||||
half_bottom = minvert == 0 && m_r.height() <= maxvert;
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -753,7 +700,7 @@ GSVector4i GSRendererHW::ComputeBoundingBox(const GSVector2& rtscale, const GSVe
|
||||
void GSRendererHW::MergeSprite(GSTextureCache::Source* tex)
|
||||
{
|
||||
// Upscaling hack to avoid various line/grid issues
|
||||
if (m_userHacks_merge_sprite && tex && tex->m_target && (m_vt.m_primclass == GS_SPRITE_CLASS))
|
||||
if (GSConfig.UserHacks_MergePPSprite && tex && tex->m_target && (m_vt.m_primclass == GS_SPRITE_CLASS))
|
||||
{
|
||||
if (PRIM->FST && GSLocalMemory::m_psm[tex->m_TEX0.PSM].fmt < 2 && ((m_vt.m_eq.value & 0xCFFFF) == 0xCFFFF))
|
||||
{
|
||||
@@ -1249,6 +1196,20 @@ void GSRendererHW::RoundSpriteOffset()
|
||||
|
||||
void GSRendererHW::Draw()
|
||||
{
|
||||
if (s_dump && (s_n >= s_saven))
|
||||
{
|
||||
std::string s;
|
||||
|
||||
// Dump Register state
|
||||
s = format("%05d_context.txt", s_n);
|
||||
|
||||
m_env.Dump(m_dump_root + s);
|
||||
m_context->Dump(m_dump_root + s);
|
||||
|
||||
// Dump vertices
|
||||
s = format("%05d_vertex.txt", s_n);
|
||||
DumpVertices(m_dump_root + s);
|
||||
}
|
||||
if (IsBadFrame())
|
||||
{
|
||||
GL_INS("Warning skipping a draw call (%d)", s_n);
|
||||
@@ -1424,7 +1385,7 @@ void GSRendererHW::Draw()
|
||||
|
||||
// upload the full chain (with offset) for the hash cache, in case some other texture uses more levels
|
||||
// for basic mipmapping, we can get away with just doing the base image, since all the mips get generated anyway.
|
||||
hash_lod_range = GSVector2i(m_lod.x, (m_hw_mipmap == HWMipmapLevel::Full) ? mxl : m_lod.x);
|
||||
hash_lod_range = GSVector2i(m_lod.x, (GSConfig.HWMipmap == HWMipmapLevel::Full) ? mxl : m_lod.x);
|
||||
|
||||
MIP_CLAMP.MINU >>= m_lod.x;
|
||||
MIP_CLAMP.MINV >>= m_lod.x;
|
||||
@@ -1449,7 +1410,7 @@ void GSRendererHW::Draw()
|
||||
TextureMinMaxResult tmm = GetTextureMinMax(TEX0, MIP_CLAMP, m_vt.IsLinear());
|
||||
|
||||
m_src = tex_psm.depth ? m_tc->LookupDepthSource(TEX0, env.TEXA, tmm.coverage) :
|
||||
m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, (m_hw_mipmap >= HWMipmapLevel::Basic ||
|
||||
m_tc->LookupSource(TEX0, env.TEXA, tmm.coverage, (GSConfig.HWMipmap >= HWMipmapLevel::Basic ||
|
||||
GSConfig.UserHacks_TriFilter == TriFiltering::Forced) ? &hash_lod_range : nullptr);
|
||||
|
||||
int tw = 1 << TEX0.TW;
|
||||
@@ -1504,7 +1465,7 @@ void GSRendererHW::Draw()
|
||||
}
|
||||
|
||||
// Round 2
|
||||
if (IsMipMapActive() && m_hw_mipmap == HWMipmapLevel::Full && !tex_psm.depth && !m_src->m_from_hash_cache)
|
||||
if (IsMipMapActive() && GSConfig.HWMipmap == HWMipmapLevel::Full && !tex_psm.depth && !m_src->m_from_hash_cache)
|
||||
{
|
||||
// Upload remaining texture layers
|
||||
const GSVector4 tmin = m_vt.m_min.t;
|
||||
@@ -1655,15 +1616,6 @@ void GSRendererHW::Draw()
|
||||
|
||||
std::string s;
|
||||
|
||||
if (s_n >= s_saven)
|
||||
{
|
||||
// Dump Register state
|
||||
s = format("%05d_context.txt", s_n);
|
||||
|
||||
m_env.Dump(m_dump_root + s);
|
||||
m_context->Dump(m_dump_root + s);
|
||||
}
|
||||
|
||||
if (s_savet && s_n >= s_saven && m_src)
|
||||
{
|
||||
s = format("%05d_f%lld_itex_%05x_%s_%d%d_%02x_%02x_%02x_%02x.dds",
|
||||
@@ -1711,7 +1663,7 @@ void GSRendererHW::Draw()
|
||||
return;
|
||||
}
|
||||
|
||||
if (m_userhacks_enabled_gs_mem_clear)
|
||||
if (!GSConfig.UserHacks_DisableSafeFeatures)
|
||||
{
|
||||
// Constant Direct Write without texture/test/blending (aka a GS mem clear)
|
||||
if ((m_vt.m_primclass == GS_SPRITE_CLASS) && !PRIM->TME // Direct write
|
||||
@@ -1737,7 +1689,7 @@ void GSRendererHW::Draw()
|
||||
GSVertex* v = &m_vertex.buff[0];
|
||||
|
||||
// Hack to avoid vertical black line in various games (ace combat/tekken)
|
||||
if (m_userhacks_align_sprite_X)
|
||||
if (GSConfig.UserHacks_AlignSpriteX)
|
||||
{
|
||||
// Note for performance reason I do the check only once on the first
|
||||
// primitive
|
||||
@@ -1763,7 +1715,7 @@ void GSRendererHW::Draw()
|
||||
// Noting to do if no texture is sampled
|
||||
if (PRIM->FST && draw_sprite_tex)
|
||||
{
|
||||
if ((m_userhacks_round_sprite_offset > 1) || (m_userhacks_round_sprite_offset == 1 && !m_vt.IsLinear()))
|
||||
if ((GSConfig.UserHacks_RoundSprite > 1) || (GSConfig.UserHacks_RoundSprite == 1 && !m_vt.IsLinear()))
|
||||
{
|
||||
if (m_vt.IsLinear())
|
||||
RoundSpriteOffset<true>();
|
||||
@@ -1888,9 +1840,10 @@ void GSRendererHW::Hacks::SetGameCRC(const CRC::Game& game)
|
||||
m_oo = m_oo_map[hash];
|
||||
m_cu = m_cu_map[hash];
|
||||
|
||||
if (game.flags & CRC::PointListPalette)
|
||||
if (GSConfig.PointListPalette)
|
||||
{
|
||||
ASSERT(m_oi == NULL);
|
||||
if (m_oi)
|
||||
Console.Warning("Overriding m_oi with PointListPalette");
|
||||
|
||||
m_oi = &GSRendererHW::OI_PointListPalette;
|
||||
}
|
||||
|
||||
@@ -27,12 +27,6 @@ private:
|
||||
int m_height;
|
||||
int m_custom_width;
|
||||
int m_custom_height;
|
||||
int m_userhacks_ts_half_bottom;
|
||||
|
||||
bool m_conservative_framebuffer;
|
||||
bool m_userhacks_align_sprite_X;
|
||||
bool m_userhacks_enabled_gs_mem_clear;
|
||||
bool m_userHacks_merge_sprite;
|
||||
|
||||
static constexpr float SSR_UV_TOLERANCE = 1.0f;
|
||||
|
||||
@@ -144,12 +138,10 @@ protected:
|
||||
GSTextureCache* m_tc;
|
||||
GSVector4i m_r;
|
||||
GSTextureCache::Source* m_src;
|
||||
HWMipmapLevel m_hw_mipmap;
|
||||
|
||||
virtual void DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* tex) = 0;
|
||||
|
||||
int m_userhacks_round_sprite_offset;
|
||||
bool m_userHacks_enabled_unscale_ptln;
|
||||
void SetTCOffset();
|
||||
|
||||
bool m_userhacks_tcoffset;
|
||||
float m_userhacks_tcoffset_x;
|
||||
@@ -185,6 +177,7 @@ public:
|
||||
GSVector2i GetTargetSize();
|
||||
|
||||
void Reset() override;
|
||||
void UpdateSettings(const Pcsx2Config::GSOptions& old_config) override;
|
||||
void VSync(u32 field, bool registers_written) override;
|
||||
|
||||
GSTexture* GetOutput(int i, int& y_offset) override;
|
||||
|
||||
@@ -33,12 +33,12 @@ void GSRendererNew::SetupIA(const float& sx, const float& sy)
|
||||
{
|
||||
GL_PUSH("IA");
|
||||
|
||||
if (m_userhacks_wildhack && !m_isPackedUV_HackFlag && PRIM->TME && PRIM->FST)
|
||||
if (GSConfig.UserHacks_WildHack && !m_isPackedUV_HackFlag && PRIM->TME && PRIM->FST)
|
||||
{
|
||||
for (unsigned int i = 0; i < m_vertex.next; i++)
|
||||
m_vertex.buff[i].UV &= 0x3FEF3FEF;
|
||||
}
|
||||
const bool unscale_pt_ln = m_userHacks_enabled_unscale_ptln && (GetUpscaleMultiplier() != 1);
|
||||
const bool unscale_pt_ln = !GSConfig.UserHacks_DisableSafeFeatures && (GetUpscaleMultiplier() != 1);
|
||||
const GSDevice::FeatureSupport features = g_gs_device->Features();
|
||||
|
||||
switch (m_vt.m_primclass)
|
||||
@@ -540,23 +540,68 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
|
||||
// Compute the blending equation to detect special case
|
||||
const GIFRegALPHA& ALPHA = m_context->ALPHA;
|
||||
|
||||
// Set blending to shader bits
|
||||
m_conf.ps.blend_a = ALPHA.A;
|
||||
m_conf.ps.blend_b = ALPHA.B;
|
||||
m_conf.ps.blend_c = ALPHA.C;
|
||||
m_conf.ps.blend_d = ALPHA.D;
|
||||
|
||||
// Get alpha value
|
||||
const bool alpha_c0_zero = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().max == 0);
|
||||
const bool alpha_c0_one = (m_conf.ps.blend_c == 0 && (GetAlphaMinMax().min == 128) && (GetAlphaMinMax().max == 128));
|
||||
const bool alpha_c0_high_max_one = (m_conf.ps.blend_c == 0 && GetAlphaMinMax().max > 128);
|
||||
const bool alpha_c2_zero = (m_conf.ps.blend_c == 2 && ALPHA.FIX == 0u);
|
||||
const bool alpha_c2_one = (m_conf.ps.blend_c == 2 && ALPHA.FIX == 128u);
|
||||
const bool alpha_c2_high_one = (m_conf.ps.blend_c == 2 && ALPHA.FIX > 128u);
|
||||
|
||||
// Optimize blending equations, must be done before index calculation
|
||||
if ((m_conf.ps.blend_a == m_conf.ps.blend_b) || ((m_conf.ps.blend_b == m_conf.ps.blend_d) && (alpha_c0_one || alpha_c2_one)))
|
||||
{
|
||||
// Condition 1:
|
||||
// A == B
|
||||
// (A - B) * C, result will be 0.0f so set A B to Cs, C to As
|
||||
// Condition 2:
|
||||
// B == D
|
||||
// Swap D with A
|
||||
// A == B
|
||||
// (A - B) * C, result will be 0.0f so set A B to Cs, C to As
|
||||
if (m_conf.ps.blend_a != m_conf.ps.blend_b)
|
||||
m_conf.ps.blend_d = m_conf.ps.blend_a;
|
||||
m_conf.ps.blend_a = 0;
|
||||
m_conf.ps.blend_b = 0;
|
||||
m_conf.ps.blend_c = 0;
|
||||
}
|
||||
else if (alpha_c0_zero || alpha_c2_zero)
|
||||
{
|
||||
// C == 0.0f
|
||||
// (A - B) * C, result will be 0.0f so set A B to Cs
|
||||
m_conf.ps.blend_a = 0;
|
||||
m_conf.ps.blend_b = 0;
|
||||
}
|
||||
|
||||
// Ad cases, alpha write is masked, one barrier is enough, for d3d11 read the fb
|
||||
// Replace Ad with As, blend flags will be used from As since we are chaging the blend_index value.
|
||||
bool blend_ad_alpha_masked = (ALPHA.C == 1) && (m_context->FRAME.FBMSK & 0xFF000000) == 0xFF000000;
|
||||
u8 ALPHA_C = ALPHA.C;
|
||||
if (g_gs_device->Features().texture_barrier && blend_ad_alpha_masked)
|
||||
ALPHA_C = 0;
|
||||
// Must be done before index calculation, after blending equation optimizations
|
||||
bool blend_ad_alpha_masked = (m_conf.ps.blend_c == 1) && (m_context->FRAME.FBMSK & 0xFF000000) == 0xFF000000;
|
||||
if (((GSConfig.AccurateBlendingUnit >= AccBlendLevel::Basic) || (m_env.COLCLAMP.CLAMP == 0))
|
||||
&& g_gs_device->Features().texture_barrier && blend_ad_alpha_masked)
|
||||
m_conf.ps.blend_c = 0;
|
||||
else if (((GSConfig.AccurateBlendingUnit >= AccBlendLevel::Medium)
|
||||
// Detect barrier aka fbmask on d3d11.
|
||||
|| m_conf.require_one_barrier)
|
||||
&& blend_ad_alpha_masked)
|
||||
ALPHA_C = 0;
|
||||
m_conf.ps.blend_c = 0;
|
||||
else
|
||||
blend_ad_alpha_masked = false;
|
||||
|
||||
u8 blend_index = u8(((ALPHA.A * 3 + ALPHA.B) * 3 + ALPHA_C) * 3 + ALPHA.D);
|
||||
u8 blend_index = u8(((m_conf.ps.blend_a * 3 + m_conf.ps.blend_b) * 3 + m_conf.ps.blend_c) * 3 + m_conf.ps.blend_d);
|
||||
const int blend_flag = g_gs_device->GetBlendFlags(blend_index);
|
||||
|
||||
// Re set alpha, it was modified, must be done after index calculation
|
||||
if (blend_ad_alpha_masked)
|
||||
m_conf.ps.blend_c = ALPHA.C;
|
||||
|
||||
// HW blend can handle Cd output.
|
||||
bool color_dest_blend = !!(blend_flag & BLEND_CD);
|
||||
|
||||
@@ -572,15 +617,12 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
const bool blend_mix3 = !!(blend_flag & BLEND_MIX3);
|
||||
bool blend_mix = (blend_mix1 || blend_mix2 || blend_mix3);
|
||||
|
||||
const bool alpha_c2_high_one = (ALPHA.C == 2 && ALPHA.FIX > 128u);
|
||||
const bool alpha_c0_high_max_one = (ALPHA.C == 0 && GetAlphaMinMax().max > 128);
|
||||
|
||||
// Blend can be done on hw. As and F cases should be accurate.
|
||||
// BLEND_C_CLR1 with Ad, BLEND_C_CLR3 Cs > 0.5f will require sw blend.
|
||||
// BLEND_C_CLR1 with As/F, BLEND_C_CLR2_AF, BLEND_C_CLR2_AS can be done in hw.
|
||||
const bool clr_blend = !!(blend_flag & (BLEND_C_CLR1 | BLEND_C_CLR2_AF | BLEND_C_CLR2_AS | BLEND_C_CLR3));
|
||||
bool clr_blend1_2 = (blend_flag & (BLEND_C_CLR1 | BLEND_C_CLR2_AF | BLEND_C_CLR2_AS))
|
||||
&& (ALPHA.C != 1) // Make sure it isn't an Ad case
|
||||
&& (m_conf.ps.blend_c != 1) // Make sure it isn't an Ad case
|
||||
&& !m_env.PABE.PABE // No PABE as it will require sw blending.
|
||||
&& (m_env.COLCLAMP.CLAMP) // Let's add a colclamp check too, hw blend will clamp to 0-1.
|
||||
&& !(m_conf.require_one_barrier || m_conf.require_full_barrier); // Also don't run if there are barriers present.
|
||||
@@ -608,10 +650,10 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
sw_blending |= true;
|
||||
[[fallthrough]];
|
||||
case AccBlendLevel::Full:
|
||||
sw_blending |= ALPHA.A != ALPHA.B && alpha_c0_high_max_one;
|
||||
sw_blending |= m_conf.ps.blend_a != m_conf.ps.blend_b && alpha_c0_high_max_one;
|
||||
[[fallthrough]];
|
||||
case AccBlendLevel::High:
|
||||
sw_blending |= ALPHA.C == 1 || (ALPHA.A != ALPHA.B && alpha_c2_high_one);
|
||||
sw_blending |= m_conf.ps.blend_c == 1 || (m_conf.ps.blend_a != m_conf.ps.blend_b && alpha_c2_high_one);
|
||||
[[fallthrough]];
|
||||
case AccBlendLevel::Medium:
|
||||
// Initial idea was to enable accurate blending for sprite rendering to handle
|
||||
@@ -654,7 +696,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
}
|
||||
[[fallthrough]];
|
||||
case AccBlendLevel::Full:
|
||||
sw_blending |= ((ALPHA.C == 1 || (blend_mix && (alpha_c2_high_one || alpha_c0_high_max_one))) && (m_prim_overlap == PRIM_OVERLAP_NO));
|
||||
sw_blending |= ((m_conf.ps.blend_c == 1 || (blend_mix && (alpha_c2_high_one || alpha_c0_high_max_one))) && (m_prim_overlap == PRIM_OVERLAP_NO));
|
||||
[[fallthrough]];
|
||||
case AccBlendLevel::High:
|
||||
sw_blending |= (!(clr_blend || blend_mix) && (m_prim_overlap == PRIM_OVERLAP_NO));
|
||||
@@ -694,7 +736,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
else
|
||||
free_colclip = blend_non_recursive;
|
||||
|
||||
GL_DBG("COLCLIP Info (Blending: %u/%u/%u/%u, OVERLAP: %d)", ALPHA.A, ALPHA.B, ALPHA.C, ALPHA.D, m_prim_overlap);
|
||||
GL_DBG("COLCLIP Info (Blending: %u/%u/%u/%u, OVERLAP: %d)", m_conf.ps.blend_a, m_conf.ps.blend_b, m_conf.ps.blend_c, m_conf.ps.blend_d, m_prim_overlap);
|
||||
if (color_dest_blend)
|
||||
{
|
||||
// No overflow, disable colclip.
|
||||
@@ -758,7 +800,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
m_conf.ps.pabe = !(accumulation_blend || blend_mix);
|
||||
}
|
||||
}
|
||||
else if (ALPHA.A == 0 && ALPHA.B == 1 && ALPHA.C == 0 && ALPHA.D == 1)
|
||||
else if (m_conf.ps.blend_a == 0 && m_conf.ps.blend_b == 1 && m_conf.ps.blend_c == 0 && m_conf.ps.blend_d == 1)
|
||||
{
|
||||
// this works because with PABE alpha blending is on when alpha >= 0x80, but since the pixel shader
|
||||
// cannot output anything over 0x80 (== 1.0) blending with 0x80 or turning it off gives the same result
|
||||
@@ -769,7 +811,8 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
// For stat to optimize accurate option
|
||||
#if 0
|
||||
GL_INS("BLEND_INFO: %u/%u/%u/%u. Clamp:%u. Prim:%d number %u (drawlist %u) (sw %d)",
|
||||
ALPHA.A, ALPHA.B, ALPHA.C, ALPHA.D, m_env.COLCLAMP.CLAMP, m_vt.m_primclass, m_vertex.next, m_drawlist.size(), sw_blending);
|
||||
m_conf.ps.blend_a, m_conf.ps.blend_b, m_conf.ps.blend_c, m_conf.ps.blend_d,
|
||||
m_env.COLCLAMP.CLAMP, m_vt.m_primclass, m_vertex.next, m_drawlist.size(), sw_blending);
|
||||
#endif
|
||||
if (color_dest_blend)
|
||||
{
|
||||
@@ -784,24 +827,9 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
}
|
||||
else if (sw_blending)
|
||||
{
|
||||
m_conf.ps.blend_a = ALPHA.A;
|
||||
m_conf.ps.blend_b = ALPHA.B;
|
||||
m_conf.ps.blend_c = ALPHA.C;
|
||||
m_conf.ps.blend_d = ALPHA.D;
|
||||
|
||||
if (m_conf.ps.blend_a == m_conf.ps.blend_b)
|
||||
{
|
||||
// A == B
|
||||
// (A - B) * C will be 0 so set A B to Cs, C to As
|
||||
m_conf.ps.blend_a = 0;
|
||||
m_conf.ps.blend_b = 0;
|
||||
m_conf.ps.blend_c = 0;
|
||||
}
|
||||
else if (m_conf.ps.blend_c == 2)
|
||||
{
|
||||
// Require the fix alpha vlaue
|
||||
// Require the fix alpha vlaue
|
||||
if (m_conf.ps.blend_c == 2)
|
||||
m_conf.cb_ps.TA_MaxDepth_Af.a = static_cast<float>(ALPHA.FIX) / 128.0f;
|
||||
}
|
||||
|
||||
if (accumulation_blend)
|
||||
{
|
||||
@@ -869,8 +897,12 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
}
|
||||
else
|
||||
{
|
||||
// Care for clr_hw value, 6 is for hw/sw, sw blending used.
|
||||
// No sw blending
|
||||
m_conf.ps.blend_a = 0;
|
||||
m_conf.ps.blend_b = 0;
|
||||
m_conf.ps.blend_d = 0;
|
||||
|
||||
// Care for clr_hw value, 6 is for hw/sw, sw blending used.
|
||||
if (blend_flag & BLEND_C_CLR1)
|
||||
{
|
||||
if (blend_ad_alpha_masked)
|
||||
@@ -892,13 +924,13 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
m_conf.ps.clr_hw = 4;
|
||||
m_conf.require_one_barrier |= true;
|
||||
}
|
||||
else if (ALPHA.C == 2)
|
||||
else if (m_conf.ps.blend_c == 2)
|
||||
{
|
||||
m_conf.ps.blend_c = 2;
|
||||
m_conf.cb_ps.TA_MaxDepth_Af.a = static_cast<float>(ALPHA.FIX) / 128.0f;
|
||||
m_conf.ps.clr_hw = 2;
|
||||
}
|
||||
else // ALPHA.C == 0
|
||||
else // m_conf.ps.blend_c == 0
|
||||
{
|
||||
m_conf.ps.blend_c = 0;
|
||||
m_conf.ps.clr_hw = 2;
|
||||
@@ -915,7 +947,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
m_conf.require_one_barrier |= true;
|
||||
}
|
||||
|
||||
if (m_conf.ps.dfmt == 1 && ALPHA.C == 1)
|
||||
if (m_conf.ps.dfmt == 1 && m_conf.ps.blend_c == 1)
|
||||
{
|
||||
// 24 bits doesn't have an alpha channel so use 1.0f fix factor as equivalent
|
||||
const u8 hacked_blend_index = blend_index + 3; // +3 <=> +1 on C
|
||||
@@ -923,7 +955,7 @@ void GSRendererNew::EmulateBlending(bool& DATE_PRIMID, bool& DATE_BARRIER)
|
||||
}
|
||||
else
|
||||
{
|
||||
m_conf.blend = {blend_index, ALPHA.FIX, ALPHA.C == 2, false, false};
|
||||
m_conf.blend = {blend_index, ALPHA.FIX, m_conf.ps.blend_c == 2, false, false};
|
||||
}
|
||||
}
|
||||
|
||||
@@ -955,7 +987,7 @@ void GSRendererNew::EmulateTextureSampler(const GSTextureCache::Source* tex)
|
||||
|
||||
const bool need_mipmap = IsMipMapDraw();
|
||||
const bool shader_emulated_sampler = tex->m_palette || cpsm.fmt != 0 || complex_wms_wmt || psm.depth;
|
||||
const bool trilinear_manual = need_mipmap && m_hw_mipmap == HWMipmapLevel::Full;
|
||||
const bool trilinear_manual = need_mipmap && GSConfig.HWMipmap == HWMipmapLevel::Full;
|
||||
|
||||
bool bilinear = m_vt.IsLinear();
|
||||
int trilinear = 0;
|
||||
@@ -964,11 +996,11 @@ void GSRendererNew::EmulateTextureSampler(const GSTextureCache::Source* tex)
|
||||
{
|
||||
case TriFiltering::Forced:
|
||||
trilinear = static_cast<u8>(GS_MIN_FILTER::Linear_Mipmap_Linear);
|
||||
trilinear_auto = !need_mipmap || m_hw_mipmap != HWMipmapLevel::Full;
|
||||
trilinear_auto = !need_mipmap || GSConfig.HWMipmap != HWMipmapLevel::Full;
|
||||
break;
|
||||
|
||||
case TriFiltering::PS2:
|
||||
if (need_mipmap && m_hw_mipmap != HWMipmapLevel::Full)
|
||||
if (need_mipmap && GSConfig.HWMipmap != HWMipmapLevel::Full)
|
||||
{
|
||||
trilinear = m_context->TEX1.MMIN;
|
||||
trilinear_auto = true;
|
||||
|
||||
@@ -27,35 +27,12 @@
|
||||
#define XXH_INLINE_ALL 1
|
||||
#include "xxhash.h"
|
||||
|
||||
bool GSTextureCache::m_disable_partial_invalidation = false;
|
||||
bool GSTextureCache::m_wrap_gs_mem = false;
|
||||
u8* GSTextureCache::m_temp;
|
||||
|
||||
GSTextureCache::GSTextureCache(GSRenderer* r)
|
||||
: m_renderer(r)
|
||||
, m_palette_map(r)
|
||||
{
|
||||
if (theApp.GetConfigB("UserHacks"))
|
||||
{
|
||||
UserHacks_HalfPixelOffset = theApp.GetConfigI("UserHacks_HalfPixelOffset") == 1;
|
||||
m_preload_frame = theApp.GetConfigB("preload_frame_with_gs_data");
|
||||
m_disable_partial_invalidation = theApp.GetConfigB("UserHacks_DisablePartialInvalidation");
|
||||
m_can_convert_depth = !theApp.GetConfigB("UserHacks_DisableDepthSupport");
|
||||
m_cpu_fb_conversion = theApp.GetConfigB("UserHacks_CPU_FB_Conversion");
|
||||
m_texture_inside_rt = theApp.GetConfigB("UserHacks_TextureInsideRt");
|
||||
m_wrap_gs_mem = theApp.GetConfigB("wrap_gs_mem");
|
||||
}
|
||||
else
|
||||
{
|
||||
UserHacks_HalfPixelOffset = false;
|
||||
m_preload_frame = false;
|
||||
m_disable_partial_invalidation = false;
|
||||
m_can_convert_depth = true;
|
||||
m_cpu_fb_conversion = false;
|
||||
m_texture_inside_rt = false;
|
||||
m_wrap_gs_mem = false;
|
||||
}
|
||||
|
||||
// In theory 4MB is enough but 9MB is safer for overflow (8MB
|
||||
// isn't enough in custom resolution)
|
||||
// Test: onimusha 3 PAL 60Hz
|
||||
@@ -110,7 +87,7 @@ void GSTextureCache::RemoveAll()
|
||||
|
||||
GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette)
|
||||
{
|
||||
if (!m_can_convert_depth)
|
||||
if (GSConfig.UserHacks_DisableDepthSupport)
|
||||
{
|
||||
GL_CACHE("LookupDepthSource not supported (0x%x, F:0x%x)", TEX0.TBP0, TEX0.PSM);
|
||||
throw GSRecoverableError();
|
||||
@@ -280,8 +257,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
||||
// (Simply not doing this code at all makes a lot of previsouly missing stuff show (but breaks pretty much everything
|
||||
// else.)
|
||||
|
||||
const bool texture_inside_rt = ShallSearchTextureInsideRt();
|
||||
|
||||
bool found_t = false;
|
||||
for (auto t : m_dst[RenderTarget])
|
||||
{
|
||||
@@ -305,7 +280,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
||||
// 1/ it just works :)
|
||||
// 2/ even with upscaling
|
||||
// 3/ for both Direct3D and OpenGL
|
||||
if (m_cpu_fb_conversion && (psm == PSM_PSMT4 || psm == PSM_PSMT8))
|
||||
if (GSConfig.UserHacks_CPUFBConversion && (psm == PSM_PSMT4 || psm == PSM_PSMT8))
|
||||
// Forces 4-bit and 8-bit frame buffer conversion to be done on the CPU instead of the GPU, but performance will be slower.
|
||||
// There is no dedicated shader to handle 4-bit conversion (Stuntman has been confirmed to use 4-bit).
|
||||
// Direct3D10/11 and OpenGL support 8-bit fb conversion but don't render some corner cases properly (Harry Potter games).
|
||||
@@ -326,7 +301,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
||||
found_t = true;
|
||||
break;
|
||||
}
|
||||
else if (texture_inside_rt && psm == PSM_PSMCT32 && t->m_TEX0.PSM == psm &&
|
||||
else if (GSConfig.UserHacks_TextureInsideRt && psm == PSM_PSMCT32 && t->m_TEX0.PSM == psm &&
|
||||
((t->m_TEX0.TBP0 < bp && t->m_end_block >= bp) || t_wraps))
|
||||
{
|
||||
// Only PSMCT32 to limit false hits.
|
||||
@@ -366,7 +341,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
||||
//
|
||||
// Sigh... They don't help us.
|
||||
|
||||
if (!found_t && m_can_convert_depth)
|
||||
if (!found_t && !GSConfig.UserHacks_DisableDepthSupport)
|
||||
{
|
||||
// Let's try a trick to avoid to use wrongly a depth buffer
|
||||
// Unfortunately, I don't have any Arc the Lad testcase
|
||||
@@ -439,11 +414,6 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con
|
||||
return src;
|
||||
}
|
||||
|
||||
bool GSTextureCache::ShallSearchTextureInsideRt()
|
||||
{
|
||||
return m_texture_inside_rt || (m_renderer->m_game.flags & CRC::Flags::TextureInsideRt);
|
||||
}
|
||||
|
||||
GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask, const bool is_frame, const int real_h)
|
||||
{
|
||||
const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM];
|
||||
@@ -545,7 +515,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
|
||||
if (!is_frame)
|
||||
dst->m_dirty_alpha |= (psm_s.trbpp == 32 && (fbmask & 0xFF000000) != 0xFF000000) || (psm_s.trbpp == 16);
|
||||
}
|
||||
else if (!is_frame && m_can_convert_depth)
|
||||
else if (!is_frame && !GSConfig.UserHacks_DisableDepthSupport)
|
||||
{
|
||||
|
||||
int rev_type = (type == DepthStencil) ? RenderTarget : DepthStencil;
|
||||
@@ -608,8 +578,8 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
|
||||
//
|
||||
// From a performance point of view, it might cost a little on big upscaling
|
||||
// but normally few RT are miss so it must remain reasonable.
|
||||
bool supported_fmt = m_can_convert_depth || psm_s.depth == 0;
|
||||
if (m_preload_frame && TEX0.TBW > 0 && supported_fmt)
|
||||
bool supported_fmt = !GSConfig.UserHacks_DisableDepthSupport || psm_s.depth == 0;
|
||||
if (GSConfig.PreloadFrameWithGSData && TEX0.TBW > 0 && supported_fmt)
|
||||
{
|
||||
GL_INS("Preloading the RT DATA");
|
||||
// RT doesn't have height but if we use a too big value, we will read outside of the GS memory.
|
||||
@@ -644,7 +614,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con
|
||||
// must invalidate the Target/Depth respectively
|
||||
void GSTextureCache::InvalidateVideoMemType(int type, u32 bp)
|
||||
{
|
||||
if (!m_can_convert_depth)
|
||||
if (GSConfig.UserHacks_DisableDepthSupport)
|
||||
return;
|
||||
|
||||
auto& list = m_dst[type];
|
||||
@@ -761,7 +731,7 @@ void GSTextureCache::InvalidateVideoMem(const GSOffset& off, const GSVector4i& r
|
||||
|
||||
// No point keeping invalidated sources around when the hash cache is active,
|
||||
// we can just re-hash and create a new source from the cached texture.
|
||||
if (s->m_from_hash_cache || (m_disable_partial_invalidation && s->m_repeating))
|
||||
if (s->m_from_hash_cache || (GSConfig.UserHacks_DisablePartialInvalidation && s->m_repeating))
|
||||
{
|
||||
m_src.RemoveAt(s);
|
||||
}
|
||||
@@ -937,7 +907,7 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
if (psm == PSM_PSMZ32 || psm == PSM_PSMZ24 || psm == PSM_PSMZ16 || psm == PSM_PSMZ16S)
|
||||
{
|
||||
GL_INS("ERROR: InvalidateLocalMem depth format isn't supported (%d,%d to %d,%d)", r.x, r.y, r.z, r.w);
|
||||
if (m_can_convert_depth)
|
||||
if (!GSConfig.UserHacks_DisableDepthSupport)
|
||||
{
|
||||
auto& dss = m_dst[DepthStencil];
|
||||
for (auto it = dss.rbegin(); it != dss.rend(); ++it) // Iterate targets from LRU to MRU.
|
||||
@@ -986,7 +956,7 @@ void GSTextureCache::InvalidateLocalMem(const GSOffset& off, const GSVector4i& r
|
||||
if (t->m_32_bits_fmt && t->m_TEX0.PSM > PSM_PSMCT24)
|
||||
t->m_TEX0.PSM = PSM_PSMCT32;
|
||||
|
||||
if (GSTextureCache::m_disable_partial_invalidation)
|
||||
if (GSConfig.UserHacks_DisablePartialInvalidation)
|
||||
{
|
||||
Read(t, r.rintersect(t->m_valid));
|
||||
}
|
||||
@@ -1430,7 +1400,7 @@ GSTextureCache::Source* GSTextureCache::CreateSource(const GIFRegTEX0& TEX0, con
|
||||
float modx = 0.0f;
|
||||
float mody = 0.0f;
|
||||
|
||||
if (UserHacks_HalfPixelOffset && hack)
|
||||
if (GSConfig.UserHacks_HalfPixelOffset == 1 && hack)
|
||||
{
|
||||
switch(m_renderer->GetUpscaleMultiplier())
|
||||
{
|
||||
@@ -1607,7 +1577,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int
|
||||
{
|
||||
ASSERT(type == RenderTarget || type == DepthStencil);
|
||||
|
||||
Target* t = new Target(m_renderer, TEX0, m_can_convert_depth, type);
|
||||
Target* t = new Target(m_renderer, TEX0, !GSConfig.UserHacks_DisableDepthSupport, type);
|
||||
|
||||
// FIXME: initial data should be unswizzled from local mem in Update() if dirty
|
||||
|
||||
@@ -1886,7 +1856,7 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int level)
|
||||
int i = (bn.blkY() << 7) + bn.blkX();
|
||||
u32 block = bn.valueNoWrap();
|
||||
|
||||
if (block < MAX_BLOCKS || m_wrap_gs_mem)
|
||||
if (block < MAX_BLOCKS || GSConfig.WrapGSMem)
|
||||
{
|
||||
u32 addr = i % MAX_BLOCKS;
|
||||
|
||||
@@ -1913,7 +1883,7 @@ void GSTextureCache::Source::Update(const GSVector4i& rect, int level)
|
||||
{
|
||||
u32 block = bn.valueNoWrap();
|
||||
|
||||
if (block < MAX_BLOCKS || m_wrap_gs_mem)
|
||||
if (block < MAX_BLOCKS || GSConfig.WrapGSMem)
|
||||
{
|
||||
block %= MAX_BLOCKS;
|
||||
|
||||
|
||||
@@ -282,13 +282,7 @@ protected:
|
||||
std::unordered_map<HashCacheKey, HashCacheEntry, HashCacheKeyHash> m_hash_cache;
|
||||
u64 m_hash_cache_memory_usage = 0;
|
||||
FastList<Target*> m_dst[2];
|
||||
bool m_preload_frame;
|
||||
static u8* m_temp;
|
||||
bool m_can_convert_depth;
|
||||
bool m_cpu_fb_conversion;
|
||||
static bool m_disable_partial_invalidation;
|
||||
bool m_texture_inside_rt;
|
||||
static bool m_wrap_gs_mem;
|
||||
constexpr static size_t S_SURFACE_OFFSET_CACHE_MAX_SIZE = std::numeric_limits<u16>::max();
|
||||
std::unordered_map<SurfaceOffsetKey, SurfaceOffset, SurfaceOffsetKeyHash, SurfaceOffsetKeyEqual> m_surface_offset_cache;
|
||||
|
||||
@@ -326,9 +320,6 @@ public:
|
||||
void InvalidateLocalMem(const GSOffset& off, const GSVector4i& r);
|
||||
|
||||
void IncAge();
|
||||
bool UserHacks_HalfPixelOffset;
|
||||
|
||||
bool ShallSearchTextureInsideRt();
|
||||
|
||||
const char* to_string(int type)
|
||||
{
|
||||
|
||||
@@ -881,7 +881,7 @@ GLuint GSDeviceOGL::CreateSampler(PSSamplerSelector sel)
|
||||
|
||||
glSamplerParameteri(sampler, GL_TEXTURE_WRAP_R, GL_CLAMP_TO_EDGE);
|
||||
|
||||
const int anisotropy = theApp.GetConfigI("MaxAnisotropy");
|
||||
const int anisotropy = GSConfig.MaxAnisotropy;
|
||||
if (anisotropy && sel.aniso)
|
||||
{
|
||||
if (GLExtension::Has("GL_ARB_texture_filter_anisotropic"))
|
||||
|
||||
@@ -58,14 +58,6 @@ GSRendererSW::GSRendererSW(int threads)
|
||||
InitCVB(GS_SPRITE_CLASS);
|
||||
|
||||
m_dump_root = root_sw;
|
||||
|
||||
// Reset handler with the auto flush hack enabled on the SW renderer.
|
||||
// Some games run better without the hack so rely on ini/gui option.
|
||||
if (theApp.GetConfigB("autoflush_sw"))
|
||||
{
|
||||
m_userhacks_auto_flush = true;
|
||||
ResetHandlers();
|
||||
}
|
||||
}
|
||||
|
||||
GSRendererSW::~GSRendererSW()
|
||||
@@ -325,6 +317,24 @@ void GSRendererSW::Draw()
|
||||
{
|
||||
const GSDrawingContext* context = m_context;
|
||||
|
||||
if (s_dump)
|
||||
{
|
||||
std::string s;
|
||||
|
||||
if (s_n >= s_saven)
|
||||
{
|
||||
// Dump Register state
|
||||
s = format("%05d_context.txt", s_n);
|
||||
|
||||
m_env.Dump(m_dump_root + s);
|
||||
m_context->Dump(m_dump_root + s);
|
||||
|
||||
// Dump vertices
|
||||
s = format("%05d_vertex.txt", s_n);
|
||||
DumpVertices(m_dump_root + s);
|
||||
}
|
||||
}
|
||||
|
||||
auto data = m_vertex_heap.make_shared<SharedData>(this).cast<GSRasterizerData>();
|
||||
SharedData* sd = static_cast<SharedData*>(data.get());
|
||||
|
||||
@@ -434,22 +444,13 @@ void GSRendererSW::Draw()
|
||||
{
|
||||
Sync(2);
|
||||
|
||||
std::string s;
|
||||
|
||||
u64 frame = g_perfmon.GetFrame();
|
||||
// Dump the texture in 32 bits format. It helps to debug texture shuffle effect
|
||||
// It will breaks the few games that really uses 16 bits RT
|
||||
bool texture_shuffle = ((context->FRAME.PSM & 0x2) && ((context->TEX0.PSM & 3) == 2) && (m_vt.m_primclass == GS_SPRITE_CLASS));
|
||||
|
||||
std::string s;
|
||||
|
||||
if (s_n >= s_saven)
|
||||
{
|
||||
// Dump Register state
|
||||
s = format("%05d_context.txt", s_n);
|
||||
|
||||
m_env.Dump(m_dump_root + s);
|
||||
m_context->Dump(m_dump_root + s);
|
||||
}
|
||||
|
||||
if (s_savet && s_n >= s_saven && PRIM->TME)
|
||||
{
|
||||
if (texture_shuffle)
|
||||
|
||||
@@ -2769,11 +2769,25 @@ void GSDeviceVK::RenderHW(GSHWDrawConfig& config)
|
||||
|
||||
const bool render_area_okay =
|
||||
(!hdr_rt && DATE_rp != DATE_RENDER_PASS_STENCIL_ONE && CheckRenderPassArea(render_area));
|
||||
const bool same_framebuffer =
|
||||
(InRenderPass() && m_current_render_target == draw_rt && m_current_depth_target == draw_ds);
|
||||
|
||||
// Prefer keeping feedback loop enabled, that way we're not constantly restarting render passes
|
||||
pipe.feedback_loop |= render_area_okay && same_framebuffer && CurrentFramebufferHasFeedbackLoop();
|
||||
// render pass restart optimizations
|
||||
if (render_area_okay)
|
||||
{
|
||||
// avoid restarting the render pass just to switch from rt+depth to rt and vice versa
|
||||
if (!draw_ds && m_current_depth_target && m_current_render_target == draw_rt &&
|
||||
config.tex != m_current_depth_target && !(pipe.feedback_loop && !CurrentFramebufferHasFeedbackLoop()))
|
||||
{
|
||||
draw_ds = m_current_depth_target;
|
||||
m_pipeline_selector.ds = true;
|
||||
m_pipeline_selector.dss.ztst = ZTST_ALWAYS;
|
||||
m_pipeline_selector.dss.zwe = false;
|
||||
}
|
||||
|
||||
// Prefer keeping feedback loop enabled, that way we're not constantly restarting render passes
|
||||
pipe.feedback_loop |= m_current_render_target == draw_rt && m_current_depth_target == draw_ds &&
|
||||
CurrentFramebufferHasFeedbackLoop();
|
||||
}
|
||||
|
||||
OMSetRenderTargets(draw_rt, draw_ds, config.scissor, pipe.feedback_loop);
|
||||
if (pipe.feedback_loop)
|
||||
PSSetShaderResource(2, draw_rt, false);
|
||||
|
||||
@@ -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(); };
|
||||
|
||||
|
||||
@@ -0,0 +1,342 @@
|
||||
#include "PrecompiledHeader.h"
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "common/Timer.h"
|
||||
|
||||
#include "imgui.h"
|
||||
|
||||
// Has to come before Gif.h
|
||||
#include "MemoryTypes.h"
|
||||
|
||||
#include "Frontend/ImGuiManager.h"
|
||||
#include "Frontend/GameList.h"
|
||||
#include "Gif.h"
|
||||
#include "Gif_Unit.h"
|
||||
#include "GSDumpReplayer.h"
|
||||
#include "GS/GSLzma.h"
|
||||
#include "GS.h"
|
||||
#include "Host.h"
|
||||
#include "R3000A.h"
|
||||
#include "R5900.h"
|
||||
#include "VMManager.h"
|
||||
#include "VUmicro.h"
|
||||
|
||||
static void GSDumpReplayerCpuReserve();
|
||||
static void GSDumpReplayerCpuShutdown();
|
||||
static void GSDumpReplayerCpuReset();
|
||||
static void GSDumpReplayerCpuStep();
|
||||
static void GSDumpReplayerCpuExecute();
|
||||
static void GSDumpReplayerCpuCheckExecutionState();
|
||||
static void GSDumpReplayerCpuThrowException(const BaseException& ex);
|
||||
static void GSDumpReplayerCpuThrowCpuException(const BaseR5900Exception& ex);
|
||||
static void GSDumpReplayerCpuClear(u32 addr, u32 size);
|
||||
static uint GSDumpReplayerCpuGetCacheReserve();
|
||||
static void GSDumpReplayerCpuSetCacheReserve(uint reserveInMegs);
|
||||
|
||||
static std::unique_ptr<GSDumpFile> s_dump_file;
|
||||
static u32 s_current_packet = 0;
|
||||
static u32 s_dump_frame_number = 0;
|
||||
static bool s_dump_running = false;
|
||||
static bool s_needs_state_loaded = false;
|
||||
static u64 s_frame_ticks = 0;
|
||||
static u64 s_next_frame_time = 0;
|
||||
|
||||
R5900cpu GSDumpReplayerCpu = {
|
||||
GSDumpReplayerCpuReserve,
|
||||
GSDumpReplayerCpuShutdown,
|
||||
GSDumpReplayerCpuReset,
|
||||
GSDumpReplayerCpuStep,
|
||||
GSDumpReplayerCpuExecute,
|
||||
GSDumpReplayerCpuCheckExecutionState,
|
||||
GSDumpReplayerCpuThrowException,
|
||||
GSDumpReplayerCpuThrowCpuException,
|
||||
GSDumpReplayerCpuClear,
|
||||
GSDumpReplayerCpuGetCacheReserve,
|
||||
GSDumpReplayerCpuSetCacheReserve};
|
||||
|
||||
static InterpVU0 gsDumpVU0;
|
||||
static InterpVU1 gsDumpVU1;
|
||||
|
||||
bool GSDumpReplayer::IsReplayingDump()
|
||||
{
|
||||
return static_cast<bool>(s_dump_file);
|
||||
}
|
||||
|
||||
bool GSDumpReplayer::Initialize(const char* filename)
|
||||
{
|
||||
Common::Timer timer;
|
||||
Console.WriteLn("(GSDumpReplayer) Reading file...");
|
||||
|
||||
s_dump_file = GSDumpFile::OpenGSDump(filename);
|
||||
if (!s_dump_file || !s_dump_file->ReadFile())
|
||||
{
|
||||
Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to open or read '%s'.", filename);
|
||||
s_dump_file.reset();
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLn("(GSDumpReplayer) Read file in %.2f ms.", timer.GetTimeMilliseconds());
|
||||
|
||||
// We replace all CPUs.
|
||||
Cpu = &GSDumpReplayerCpu;
|
||||
psxCpu = &psxInt;
|
||||
CpuVU0 = &gsDumpVU0;
|
||||
CpuVU1 = &gsDumpVU1;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSDumpReplayer::Reset()
|
||||
{
|
||||
GSDumpReplayerCpuReset();
|
||||
}
|
||||
|
||||
void GSDumpReplayer::Shutdown()
|
||||
{
|
||||
Console.WriteLn("(GSDumpReplayer) Shutting down.");
|
||||
|
||||
Cpu = nullptr;
|
||||
psxCpu = nullptr;
|
||||
CpuVU0 = nullptr;
|
||||
CpuVU1 = nullptr;
|
||||
s_dump_file.reset();
|
||||
}
|
||||
|
||||
std::string GSDumpReplayer::GetDumpSerial()
|
||||
{
|
||||
std::string ret;
|
||||
|
||||
if (!s_dump_file->GetSerial().empty())
|
||||
{
|
||||
ret = s_dump_file->GetSerial();
|
||||
}
|
||||
else if (s_dump_file->GetCRC() != 0)
|
||||
{
|
||||
// old dump files don't have serials, but we have the crc...
|
||||
// so, let's try searching the game list for a crc match.
|
||||
auto lock = GameList::GetLock();
|
||||
const GameList::Entry* entry = GameList::GetEntryByCRC(s_dump_file->GetCRC());
|
||||
if (entry)
|
||||
ret = entry->serial;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
u32 GSDumpReplayer::GetDumpCRC()
|
||||
{
|
||||
return s_dump_file->GetCRC();
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuReserve()
|
||||
{
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuShutdown()
|
||||
{
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuReset()
|
||||
{
|
||||
s_needs_state_loaded = true;
|
||||
s_current_packet = 0;
|
||||
s_dump_frame_number = 0;
|
||||
}
|
||||
|
||||
static void GSDumpReplayerLoadInitialState()
|
||||
{
|
||||
// reset GS registers to initial dump values
|
||||
std::memcpy(PS2MEM_GS, s_dump_file->GetRegsData().data(),
|
||||
std::min(Ps2MemSize::GSregs, static_cast<u32>(s_dump_file->GetRegsData().size())));
|
||||
|
||||
// load GS state
|
||||
freezeData fd = {static_cast<int>(s_dump_file->GetStateData().size()),
|
||||
const_cast<u8*>(s_dump_file->GetStateData().data())};
|
||||
MTGS_FreezeData mfd = {&fd, 0};
|
||||
GetMTGS().Freeze(FreezeAction::Load, mfd);
|
||||
if (mfd.retval != 0)
|
||||
Host::ReportFormattedErrorAsync("GSDumpReplayer", "Failed to load GS state.");
|
||||
}
|
||||
|
||||
static void GSDumpReplayerSendPacketToMTGS(GIF_PATH path, const u8* data, u32 length)
|
||||
{
|
||||
pxAssert((length % 16) == 0);
|
||||
|
||||
Gif_Path& gifPath = gifUnit.gifPath[path];
|
||||
gifPath.CopyGSPacketData(const_cast<u8*>(data), length);
|
||||
|
||||
GS_Packet gsPack;
|
||||
gsPack.offset = gifPath.curOffset;
|
||||
gsPack.size = length;
|
||||
gifPath.curOffset += length;
|
||||
Gif_AddCompletedGSPacket(gsPack, path);
|
||||
}
|
||||
|
||||
static void GSDumpReplayerUpdateFrameLimit()
|
||||
{
|
||||
constexpr u32 default_frame_limit = 60;
|
||||
const u32 frame_limit = static_cast<u32>(default_frame_limit * EmuConfig.GS.LimitScalar);
|
||||
|
||||
if (frame_limit > 0)
|
||||
s_frame_ticks = (GetTickFrequency() + (frame_limit / 2)) / frame_limit;
|
||||
else
|
||||
s_frame_ticks = 0;
|
||||
}
|
||||
|
||||
static void GSDumpReplayerFrameLimit()
|
||||
{
|
||||
if (s_frame_ticks == 0)
|
||||
return;
|
||||
|
||||
// Frame limiter
|
||||
u64 now = GetCPUTicks();
|
||||
const s64 ms = GetTickFrequency() / 1000;
|
||||
const s64 sleep = s_next_frame_time - now - ms;
|
||||
if (sleep > ms)
|
||||
Threading::Sleep(sleep / ms);
|
||||
while ((now = GetCPUTicks()) < s_next_frame_time)
|
||||
ShortSpin();
|
||||
s_next_frame_time = std::max(now, s_next_frame_time + s_frame_ticks);
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuStep()
|
||||
{
|
||||
if (s_needs_state_loaded)
|
||||
{
|
||||
GSDumpReplayerLoadInitialState();
|
||||
s_needs_state_loaded = false;
|
||||
}
|
||||
|
||||
const GSDumpFile::GSData& packet = s_dump_file->GetPackets()[s_current_packet];
|
||||
s_current_packet = (s_current_packet + 1) % static_cast<u32>(s_dump_file->GetPackets().size());
|
||||
if (s_current_packet == 0)
|
||||
s_dump_frame_number = 0;
|
||||
|
||||
switch (packet.id)
|
||||
{
|
||||
case GSDumpTypes::GSType::Transfer:
|
||||
{
|
||||
switch (packet.path)
|
||||
{
|
||||
case GSDumpTypes::GSTransferPath::Path1Old:
|
||||
{
|
||||
std::unique_ptr<u8[]> data(new u8[16384]);
|
||||
const s32 addr = 16384 - packet.length;
|
||||
std::memcpy(data.get(), packet.data + addr, packet.length);
|
||||
GSDumpReplayerSendPacketToMTGS(GIF_PATH_1, data.get(), packet.length);
|
||||
}
|
||||
break;
|
||||
|
||||
case GSDumpTypes::GSTransferPath::Path1New:
|
||||
case GSDumpTypes::GSTransferPath::Path2:
|
||||
case GSDumpTypes::GSTransferPath::Path3:
|
||||
{
|
||||
GSDumpReplayerSendPacketToMTGS(static_cast<GIF_PATH>(static_cast<u8>(packet.path) - 1),
|
||||
packet.data, packet.length);
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case GSDumpTypes::GSType::VSync:
|
||||
{
|
||||
s_dump_frame_number++;
|
||||
GSDumpReplayerCpuCheckExecutionState();
|
||||
GSDumpReplayerUpdateFrameLimit();
|
||||
GSDumpReplayerFrameLimit();
|
||||
GetMTGS().PostVsyncStart(false);
|
||||
VMManager::Internal::VSyncOnCPUThread();
|
||||
}
|
||||
break;
|
||||
|
||||
case GSDumpTypes::GSType::ReadFIFO2:
|
||||
{
|
||||
std::unique_ptr<char[]> arr(new char[*((int*)packet.data)]);
|
||||
GSreadFIFO2((u8*)arr.get(), *((int*)packet.data));
|
||||
}
|
||||
break;
|
||||
|
||||
case GSDumpTypes::GSType::Registers:
|
||||
{
|
||||
std::memcpy(PS2MEM_GS, packet.data, std::min<s32>(packet.length, Ps2MemSize::GSregs));
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuExecute()
|
||||
{
|
||||
s_dump_running = true;
|
||||
s_next_frame_time = GetCPUTicks();
|
||||
|
||||
while (s_dump_running)
|
||||
{
|
||||
GSDumpReplayerCpuStep();
|
||||
}
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuCheckExecutionState()
|
||||
{
|
||||
if (VMManager::Internal::IsExecutionInterrupted())
|
||||
s_dump_running = false;
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuThrowException(const BaseException& ex)
|
||||
{
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuThrowCpuException(const BaseR5900Exception& ex)
|
||||
{
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuClear(u32 addr, u32 size)
|
||||
{
|
||||
}
|
||||
|
||||
uint GSDumpReplayerCpuGetCacheReserve()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
void GSDumpReplayerCpuSetCacheReserve(uint reserveInMegs)
|
||||
{
|
||||
}
|
||||
|
||||
void GSDumpReplayer::RenderUI()
|
||||
{
|
||||
const float scale = ImGuiManager::GetGlobalScale();
|
||||
const float shadow_offset = std::ceil(1.0f * scale);
|
||||
const float margin = std::ceil(10.0f * scale);
|
||||
const float spacing = std::ceil(5.0f * scale);
|
||||
float position_y = margin;
|
||||
|
||||
ImDrawList* dl = ImGui::GetBackgroundDrawList();
|
||||
ImFont* font = ImGuiManager::GetFixedFont();
|
||||
FastFormatAscii text;
|
||||
ImVec2 text_size;
|
||||
|
||||
#define DRAW_LINE(font, text, color) \
|
||||
do \
|
||||
{ \
|
||||
text_size = font->CalcTextSizeA(font->FontSize, std::numeric_limits<float>::max(), -1.0f, (text), nullptr, nullptr); \
|
||||
dl->AddText(font, font->FontSize, ImVec2(margin + shadow_offset, position_y + shadow_offset), IM_COL32(0, 0, 0, 100), (text)); \
|
||||
dl->AddText(font, font->FontSize, ImVec2(margin, position_y), color, (text)); \
|
||||
position_y += text_size.y + spacing; \
|
||||
} while (0)
|
||||
|
||||
text.Write("Dump Frame: %u", s_dump_frame_number);
|
||||
DRAW_LINE(font, text.c_str(), IM_COL32(255, 255, 255, 255));
|
||||
|
||||
text.Clear();
|
||||
text.Write("Packet Number: %u/%u", s_current_packet, static_cast<u32>(s_dump_file->GetPackets().size()));
|
||||
DRAW_LINE(font, text.c_str(), IM_COL32(255, 255, 255, 255));
|
||||
|
||||
#undef DRAW_LINE
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
#pragma once
|
||||
|
||||
namespace GSDumpReplayer
|
||||
{
|
||||
bool IsReplayingDump();
|
||||
|
||||
bool Initialize(const char* filename);
|
||||
void Reset();
|
||||
void Shutdown();
|
||||
|
||||
std::string GetDumpSerial();
|
||||
u32 GetDumpCRC();
|
||||
|
||||
void RenderUI();
|
||||
}
|
||||
+220
-11
@@ -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,194 @@ 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",
|
||||
"pointListPalette",
|
||||
"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:
|
||||
case GSHWFixId::PointListPalette:
|
||||
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] Manual GS hardware renderer fixes are enabled, not using automatic hardware renderer fixes from GameDB.");
|
||||
|
||||
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::PointListPalette:
|
||||
config.PointListPalette = (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 +502,6 @@ static void initDatabase()
|
||||
ryml::reset_callbacks();
|
||||
}
|
||||
|
||||
|
||||
|
||||
void GameDatabase::ensureLoaded()
|
||||
{
|
||||
std::call_once(s_load_once_flag, []() {
|
||||
|
||||
+38
-3
@@ -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,35 @@ public:
|
||||
Full
|
||||
};
|
||||
|
||||
enum class GSHWFixId : u32
|
||||
{
|
||||
// boolean settings
|
||||
AutoFlush,
|
||||
ConservativeFramebuffer,
|
||||
CPUFramebufferConversion,
|
||||
DisableDepthSupport,
|
||||
WrapGSMem,
|
||||
PreloadFrameData,
|
||||
FastTextureInvalidation,
|
||||
TextureInsideRT,
|
||||
AlignSprite,
|
||||
MergeSprite,
|
||||
WildArmsHack,
|
||||
PointListPalette,
|
||||
|
||||
// integer settings
|
||||
Mipmap,
|
||||
TrilinearFiltering,
|
||||
SkipDrawStart,
|
||||
SkipDrawEnd,
|
||||
HalfBottomOverride,
|
||||
HalfPixelOffset,
|
||||
RoundSprite,
|
||||
TexturePreloading,
|
||||
|
||||
Count
|
||||
};
|
||||
|
||||
using Patch = std::vector<std::string>;
|
||||
|
||||
struct GameEntry
|
||||
@@ -67,6 +98,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 +106,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;
|
||||
};
|
||||
};
|
||||
|
||||
|
||||
+23
-7
@@ -60,16 +60,16 @@ struct Gif_Tag
|
||||
bool hasAD; // Has an A+D Write
|
||||
bool isValid; // Tag is valid
|
||||
|
||||
Gif_Tag() { Reset(); }
|
||||
Gif_Tag(u8* pMem, bool analyze = false)
|
||||
__ri Gif_Tag() { Reset(); }
|
||||
__ri Gif_Tag(u8* pMem, bool analyze = false)
|
||||
{
|
||||
setTag(pMem, analyze);
|
||||
}
|
||||
|
||||
void Reset() { memzero(*this); }
|
||||
u8 curReg() { return regs[nRegIdx & 0xf]; }
|
||||
__ri void Reset() { memzero(*this); }
|
||||
__ri u8 curReg() { return regs[nRegIdx & 0xf]; }
|
||||
|
||||
void packedStep()
|
||||
__ri void packedStep()
|
||||
{
|
||||
if (nLoop > 0)
|
||||
{
|
||||
@@ -82,7 +82,7 @@ struct Gif_Tag
|
||||
}
|
||||
}
|
||||
|
||||
void setTag(u8* pMem, bool analyze = false)
|
||||
__ri void setTag(u8* pMem, bool analyze = false)
|
||||
{
|
||||
tag = *(HW_Gif_Tag*)pMem;
|
||||
nLoop = tag.NLOOP;
|
||||
@@ -115,8 +115,23 @@ struct Gif_Tag
|
||||
}
|
||||
}
|
||||
|
||||
void analyzeTag()
|
||||
__ri void analyzeTag()
|
||||
{
|
||||
#ifdef _M_X86
|
||||
// zero out bits for registers which shouldn't be tested
|
||||
__m128i vregs = _mm_loadl_epi64(reinterpret_cast<const __m128i*>(tag.REGS));
|
||||
vregs = _mm_and_si128(vregs, _mm_srli_epi64(_mm_set1_epi32(0xFFFFFFFFu), (64 - nRegs * 4)));
|
||||
|
||||
// get upper nibbles, interleave with lower nibbles, clear upper bits from low nibbles
|
||||
vregs = _mm_and_si128(_mm_unpacklo_epi8(vregs, _mm_srli_epi32(vregs, 4)), _mm_set1_epi8(0x0F));
|
||||
|
||||
// compare with GIF_REG_A_D, set hasAD if any lanes passed
|
||||
hasAD = (_mm_movemask_epi8(_mm_cmpeq_epi8(vregs, _mm_set1_epi8(GIF_REG_A_D))) != 0);
|
||||
|
||||
// write out unpacked registers
|
||||
_mm_storeu_si128(reinterpret_cast<__m128i*>(regs), vregs);
|
||||
#else
|
||||
// Reference C implementation.
|
||||
hasAD = false;
|
||||
u32 t = tag.REGS[0];
|
||||
u32 i = 0;
|
||||
@@ -135,6 +150,7 @@ struct Gif_Tag
|
||||
hasAD |= (regs[i] == GIF_REG_A_D);
|
||||
t >>= 4;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+6
-5
@@ -146,7 +146,7 @@ void VU_Thread::ExecuteRingBuffer()
|
||||
s32 addr = Read();
|
||||
vifRegs.top = Read();
|
||||
vifRegs.itop = Read();
|
||||
|
||||
vuFBRST = Read();
|
||||
if (addr != -1)
|
||||
vuRegs.VI[REG_TPC].UL = addr & 0x7FF;
|
||||
vuCPU->SetStartPC(vuRegs.VI[REG_TPC].UL << 3);
|
||||
@@ -406,13 +406,13 @@ void VU_Thread::Get_MTVUChanges()
|
||||
{
|
||||
mtvuInterrupts.fetch_and(~InterruptFlagVUEBit, std::memory_order_relaxed);
|
||||
|
||||
VU0.VI[REG_VPU_STAT].UL &= ~0x0100;
|
||||
VU0.VI[REG_VPU_STAT].UL &= ~0xFF00;
|
||||
//DevCon.Warning("E-Bit registered %x", VU0.VI[REG_VPU_STAT].UL);
|
||||
}
|
||||
if (interrupts & InterruptFlagVUTBit)
|
||||
{
|
||||
mtvuInterrupts.fetch_and(~InterruptFlagVUTBit, std::memory_order_relaxed);
|
||||
VU0.VI[REG_VPU_STAT].UL &= ~0x0100;
|
||||
VU0.VI[REG_VPU_STAT].UL &= ~0xFF00;
|
||||
VU0.VI[REG_VPU_STAT].UL |= 0x0400;
|
||||
//DevCon.Warning("T-Bit registered %x", VU0.VI[REG_VPU_STAT].UL);
|
||||
hwIntcIrq(7);
|
||||
@@ -445,15 +445,16 @@ void VU_Thread::WaitVU()
|
||||
}
|
||||
}
|
||||
|
||||
void VU_Thread::ExecuteVU(u32 vu_addr, u32 vif_top, u32 vif_itop)
|
||||
void VU_Thread::ExecuteVU(u32 vu_addr, u32 vif_top, u32 vif_itop, u32 fbrst)
|
||||
{
|
||||
MTVU_LOG("MTVU - ExecuteVU!");
|
||||
Get_MTVUChanges(); // Clear any pending interrupts
|
||||
ReserveSpace(4);
|
||||
ReserveSpace(5);
|
||||
Write(MTVU_VU_EXECUTE);
|
||||
Write(vu_addr);
|
||||
Write(vif_top);
|
||||
Write(vif_itop);
|
||||
Write(fbrst);
|
||||
CommitWritePos();
|
||||
gifUnit.TransferGSPacketData(GIF_TRANS_MTVU, NULL, 0);
|
||||
KickStart();
|
||||
|
||||
+2
-1
@@ -47,6 +47,7 @@ public:
|
||||
Semaphore semaXGkick;
|
||||
std::atomic<unsigned int> vuCycles[4]; // Used for VU cycle stealing hack
|
||||
u32 vuCycleIdx; // Used for VU cycle stealing hack
|
||||
u32 vuFBRST;
|
||||
|
||||
enum InterruptFlag {
|
||||
InterruptFlagFinish = 1 << 0,
|
||||
@@ -76,7 +77,7 @@ public:
|
||||
|
||||
void Get_MTVUChanges();
|
||||
|
||||
void ExecuteVU(u32 vu_addr, u32 vif_top, u32 vif_itop);
|
||||
void ExecuteVU(u32 vu_addr, u32 vif_top, u32 vif_itop, u32 fbrst);
|
||||
|
||||
void VifUnpack(vifStruct& _vif, VIFregisters& _vifRegs, u8* data, u32 size);
|
||||
|
||||
|
||||
+11
-1
@@ -49,6 +49,10 @@ BIOS
|
||||
|
||||
#include "common/PageFaultSource.h"
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
#include "GSDumpReplayer.h"
|
||||
#endif
|
||||
|
||||
#ifdef ENABLECACHE
|
||||
#include "Cache.h"
|
||||
#endif
|
||||
@@ -850,7 +854,13 @@ void eeMemoryReserve::Reset()
|
||||
vtlb_VMap(0x00000000,0x00000000,0x20000000);
|
||||
vtlb_VMapUnmap(0x20000000,0x60000000);
|
||||
|
||||
if (!LoadBIOS())
|
||||
#ifdef PCSX2_CORE
|
||||
const bool needs_bios = !GSDumpReplayer::IsReplayingDump();
|
||||
#else
|
||||
constexpr bool needs_bios = true;
|
||||
#endif
|
||||
|
||||
if (needs_bios && !LoadBIOS())
|
||||
pxFailRel("Failed to load BIOS");
|
||||
}
|
||||
|
||||
|
||||
+37
-10
@@ -65,9 +65,11 @@ void TraceLogFilters::LoadSave(SettingsWrapper& wrap)
|
||||
}
|
||||
|
||||
const char* const tbl_SpeedhackNames[] =
|
||||
{
|
||||
"mvuFlag",
|
||||
"InstantVU1"};
|
||||
{
|
||||
"mvuFlag",
|
||||
"InstantVU1",
|
||||
"MTVU"
|
||||
};
|
||||
|
||||
const char* EnumToString(SpeedhackId id)
|
||||
{
|
||||
@@ -85,6 +87,9 @@ void Pcsx2Config::SpeedhackOptions::Set(SpeedhackId id, bool enabled)
|
||||
case Speedhack_InstantVU1:
|
||||
vu1Instant = enabled;
|
||||
break;
|
||||
case Speedhack_MTVU:
|
||||
vuThread = enabled;
|
||||
break;
|
||||
jNO_DEFAULT;
|
||||
}
|
||||
}
|
||||
@@ -308,8 +313,9 @@ Pcsx2Config::GSOptions::GSOptions()
|
||||
WrapGSMem = false;
|
||||
Mipmap = true;
|
||||
AA1 = true;
|
||||
PointListPalette = false;
|
||||
|
||||
UserHacks = false;
|
||||
ManualUserHacks = false;
|
||||
UserHacks_AlignSpriteX = false;
|
||||
UserHacks_AutoFlush = false;
|
||||
UserHacks_CPUFBConversion = false;
|
||||
@@ -380,8 +386,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) &&
|
||||
@@ -508,7 +514,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");
|
||||
@@ -550,8 +556,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");
|
||||
@@ -583,22 +590,27 @@ void Pcsx2Config::GSOptions::ReloadIniSettings()
|
||||
|
||||
void Pcsx2Config::GSOptions::MaskUserHacks()
|
||||
{
|
||||
if (UserHacks)
|
||||
if (ManualUserHacks)
|
||||
return;
|
||||
|
||||
UserHacks_AlignSpriteX = false;
|
||||
UserHacks_MergePPSprite = false;
|
||||
UserHacks_WildHack = false;
|
||||
UserHacks_DisableSafeFeatures = false;
|
||||
UserHacks_HalfBottomOverride = -1;
|
||||
UserHacks_HalfPixelOffset = 0;
|
||||
UserHacks_RoundSprite = 0;
|
||||
UserHacks_AutoFlush = false;
|
||||
PreloadFrameWithGSData = false;
|
||||
WrapGSMem = false;
|
||||
UserHacks_DisablePartialInvalidation = false;
|
||||
UserHacks_DisableDepthSupport = false;
|
||||
UserHacks_CPUFBConversion = false;
|
||||
UserHacks_TextureInsideRt = false;
|
||||
UserHacks_TCOffsetX = 0;
|
||||
UserHacks_TCOffsetY = 0;
|
||||
SkipDrawStart = 0;
|
||||
SkipDrawEnd = 0;
|
||||
|
||||
// in wx, we put trilinear filtering behind user hacks, but not in qt.
|
||||
#ifndef PCSX2_CORE
|
||||
@@ -606,6 +618,17 @@ void Pcsx2Config::GSOptions::MaskUserHacks()
|
||||
#endif
|
||||
}
|
||||
|
||||
void Pcsx2Config::GSOptions::MaskUpscalingHacks()
|
||||
{
|
||||
if (UpscaleMultiplier == 1 || ManualUserHacks)
|
||||
return;
|
||||
|
||||
UserHacks_AlignSpriteX = false;
|
||||
UserHacks_MergePPSprite = false;
|
||||
UserHacks_HalfPixelOffset = 0;
|
||||
UserHacks_RoundSprite = 0;
|
||||
}
|
||||
|
||||
bool Pcsx2Config::GSOptions::UseHardwareRenderer() const
|
||||
{
|
||||
return (Renderer == GSRendererType::DX11 || Renderer == GSRendererType::OGL || Renderer == GSRendererType::VK);
|
||||
@@ -680,23 +703,27 @@ void Pcsx2Config::DEV9Options::LoadSave(SettingsWrapper& wrap)
|
||||
SettingsWrapEntry(InterceptDHCP);
|
||||
|
||||
std::string ps2IPStr = "0.0.0.0";
|
||||
std::string maskStr = "0.0.0.0";
|
||||
std::string gatewayStr = "0.0.0.0";
|
||||
std::string dns1Str = "0.0.0.0";
|
||||
std::string dns2Str = "0.0.0.0";
|
||||
if (wrap.IsSaving())
|
||||
{
|
||||
ps2IPStr = SaveIPHelper(PS2IP);
|
||||
maskStr = SaveIPHelper(Mask);
|
||||
gatewayStr = SaveIPHelper(Gateway);
|
||||
dns1Str = SaveIPHelper(DNS1);
|
||||
dns2Str = SaveIPHelper(DNS2);
|
||||
}
|
||||
SettingsWrapEntryEx(ps2IPStr, "PS2IP");
|
||||
SettingsWrapEntryEx(maskStr, "Mask");
|
||||
SettingsWrapEntryEx(gatewayStr, "Gateway");
|
||||
SettingsWrapEntryEx(dns1Str, "DNS1");
|
||||
SettingsWrapEntryEx(dns2Str, "DNS2");
|
||||
if (wrap.IsLoading())
|
||||
{
|
||||
LoadIPHelper(PS2IP, ps2IPStr);
|
||||
LoadIPHelper(Mask, maskStr);
|
||||
LoadIPHelper(Gateway, gatewayStr);
|
||||
LoadIPHelper(DNS1, dns1Str);
|
||||
LoadIPHelper(DNS1, dns1Str);
|
||||
|
||||
+5
-2
@@ -37,9 +37,12 @@ static void TestClearVUs(u32 madr, u32 qwc, bool isWrite)
|
||||
//Catch up VU1 too
|
||||
CpuVU1->ExecuteBlock(0);
|
||||
}
|
||||
if ((madr >= 0x11008000) && (VU0.VI[REG_VPU_STAT].UL & 0x100) && !THREAD_VU1)
|
||||
if ((madr >= 0x11008000) && (VU0.VI[REG_VPU_STAT].UL & 0x100) && (!THREAD_VU1 || !isWrite))
|
||||
{
|
||||
CpuVU1->Execute(vu1RunCycles);
|
||||
if (THREAD_VU1)
|
||||
vu1Thread.WaitVU();
|
||||
else
|
||||
CpuVU1->Execute(vu1RunCycles);
|
||||
cpuRegs.cycle = VU1.cycle;
|
||||
//Catch up VU0 too
|
||||
CpuVU0->ExecuteBlock(0);
|
||||
|
||||
@@ -27,6 +27,11 @@
|
||||
#include "common/MemsetFast.inl"
|
||||
#include "common/Perf.h"
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
#include "GSDumpReplayer.h"
|
||||
|
||||
extern R5900cpu GSDumpReplayerCpu;
|
||||
#endif
|
||||
|
||||
// --------------------------------------------------------------------------------------
|
||||
// RecompiledCodeReserve (implementations)
|
||||
@@ -576,6 +581,11 @@ void SysCpuProviderPack::ApplyConfig() const
|
||||
|
||||
if( EmuConfig.Cpu.Recompiler.EnableVU1 )
|
||||
CpuVU1 = (BaseVUmicroCPU*)CpuProviders->microVU1;
|
||||
|
||||
#ifdef PCSX2_CORE
|
||||
if (GSDumpReplayer::IsReplayingDump())
|
||||
Cpu = &GSDumpReplayerCpu;
|
||||
#endif
|
||||
}
|
||||
|
||||
// Resets all PS2 cpu execution caches, which does not affect that actual PS2 state/condition.
|
||||
|
||||
+124
-48
@@ -35,6 +35,7 @@
|
||||
#include "Elfheader.h"
|
||||
#include "FW.h"
|
||||
#include "GS.h"
|
||||
#include "GSDumpReplayer.h"
|
||||
#include "HostDisplay.h"
|
||||
#include "HostSettings.h"
|
||||
#include "IopBios.h"
|
||||
@@ -74,6 +75,9 @@ namespace VMManager
|
||||
static void CheckForSPU2ConfigChanges(const Pcsx2Config& old_config);
|
||||
static void CheckForDEV9ConfigChanges(const Pcsx2Config& old_config);
|
||||
static void CheckForMemoryCardConfigChanges(const Pcsx2Config& old_config);
|
||||
|
||||
static bool AutoDetectSource(const std::string& filename);
|
||||
static bool ApplyBootParameters(const VMBootParameters& params);
|
||||
static void UpdateRunningGame(bool force);
|
||||
|
||||
static std::string GetCurrentSaveStateFileName(s32 slot);
|
||||
@@ -218,6 +222,14 @@ 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();
|
||||
|
||||
// Force MTVU off when playing back GS dumps, it doesn't get used.
|
||||
if (GSDumpReplayer::IsReplayingDump())
|
||||
EmuConfig.Speedhacks.vuThread = false;
|
||||
|
||||
if (HasValidVM())
|
||||
ApplyGameFixes();
|
||||
}
|
||||
@@ -295,6 +307,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)
|
||||
@@ -448,11 +462,20 @@ void VMManager::UpdateRunningGame(bool force)
|
||||
// we have the CRC but we're still at the bios and the settings are changed
|
||||
// (e.g. the user presses TAB to speed up emulation), we don't want to apply the
|
||||
// settings as if the game is already running (title, loadeding patches, etc).
|
||||
bool ingame = (ElfCRC && (g_GameLoading || g_GameStarted));
|
||||
const u32 new_crc = ingame ? ElfCRC : 0;
|
||||
const std::string crc_string(StringUtil::StdStringFromFormat("%08X", new_crc));
|
||||
u32 new_crc;
|
||||
std::string new_serial;
|
||||
if (!GSDumpReplayer::IsReplayingDump())
|
||||
{
|
||||
const bool ingame = (ElfCRC && (g_GameLoading || g_GameStarted));
|
||||
new_crc = ingame ? ElfCRC : 0;
|
||||
new_serial = ingame ? SysGetDiscID().ToStdString() : SysGetBiosDiscID().ToStdString();
|
||||
}
|
||||
else
|
||||
{
|
||||
new_crc = GSDumpReplayer::GetDumpCRC();
|
||||
new_serial = GSDumpReplayer::GetDumpSerial();
|
||||
}
|
||||
|
||||
std::string new_serial(ingame ? SysGetDiscID().ToStdString() : SysGetBiosDiscID().ToStdString());
|
||||
if (!force && s_game_crc == new_crc && s_game_serial == new_serial)
|
||||
return;
|
||||
|
||||
@@ -482,7 +505,7 @@ void VMManager::UpdateRunningGame(bool force)
|
||||
ApplySettings();
|
||||
|
||||
ForgetLoadedPatches();
|
||||
LoadPatches(crc_string, true, false);
|
||||
LoadPatches(StringUtil::StdStringFromFormat("%08X", new_crc), true, false);
|
||||
GetMTGS().SendGameCRC(new_crc);
|
||||
|
||||
Host::OnGameChanged(s_disc_path, s_game_serial, s_game_name, s_game_crc);
|
||||
@@ -499,30 +522,86 @@ static LimiterModeType GetInitialLimiterMode()
|
||||
return EmuConfig.GS.FrameLimitEnable ? LimiterModeType::Nominal : LimiterModeType::Unlimited;
|
||||
}
|
||||
|
||||
static void ApplyBootParameters(const VMBootParameters& params)
|
||||
bool VMManager::AutoDetectSource(const std::string& filename)
|
||||
{
|
||||
if (!filename.empty())
|
||||
{
|
||||
if (!FileSystem::FileExists(filename.c_str()))
|
||||
{
|
||||
Host::ReportFormattedErrorAsync("Error", "Requested filename '%s' does not exist.", filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string display_name(FileSystem::GetDisplayNameFromPath(filename));
|
||||
if (IsGSDumpFileName(display_name))
|
||||
{
|
||||
CDVDsys_ChangeSource(CDVD_SourceType::NoDisc);
|
||||
return GSDumpReplayer::Initialize(filename.c_str());
|
||||
}
|
||||
else if (IsElfFileName(display_name))
|
||||
{
|
||||
// alternative way of booting an elf, change the elf override, and use no disc.
|
||||
CDVDsys_ChangeSource(CDVD_SourceType::NoDisc);
|
||||
s_elf_override = filename;
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// TODO: Maybe we should check if it's a valid iso here...
|
||||
CDVDsys_SetFile(CDVD_SourceType::Iso, filename);
|
||||
CDVDsys_ChangeSource(CDVD_SourceType::Iso);
|
||||
s_disc_path = filename;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// make sure we're not fast booting when we have no filename
|
||||
CDVDsys_ChangeSource(CDVD_SourceType::NoDisc);
|
||||
EmuConfig.UseBOOT2Injection = false;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
bool VMManager::ApplyBootParameters(const VMBootParameters& params)
|
||||
{
|
||||
const bool default_fast_boot = Host::GetBoolSettingValue("EmuCore", "EnableFastBoot", true);
|
||||
EmuConfig.UseBOOT2Injection =
|
||||
(params.source_type != CDVD_SourceType::NoDisc && params.fast_boot.value_or(default_fast_boot));
|
||||
EmuConfig.UseBOOT2Injection = params.fast_boot.value_or(default_fast_boot);
|
||||
s_elf_override = params.elf_override;
|
||||
s_disc_path.clear();
|
||||
|
||||
CDVDsys_SetFile(CDVD_SourceType::Iso, params.source);
|
||||
CDVDsys_ChangeSource(params.source_type);
|
||||
|
||||
if (!params.elf_override.empty())
|
||||
if (params.source_type.has_value())
|
||||
{
|
||||
Hle_SetElfPath(params.elf_override.c_str());
|
||||
s_elf_override = std::move(params.elf_override);
|
||||
if (params.source_type.value() == CDVD_SourceType::Iso && !FileSystem::FileExists(params.filename.c_str()))
|
||||
{
|
||||
Host::ReportFormattedErrorAsync("Error", "Requested filename '%s' does not exist.", params.filename.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
// Use specified source type.
|
||||
CDVDsys_SetFile(params.source_type.value(), params.filename);
|
||||
CDVDsys_ChangeSource(params.source_type.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
// Automatic type detection of boot parameter based on filename.
|
||||
if (!AutoDetectSource(params.filename))
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!s_elf_override.empty())
|
||||
{
|
||||
if (!FileSystem::FileExists(s_elf_override.c_str()))
|
||||
{
|
||||
Host::ReportFormattedErrorAsync("Error", "Requested boot ELF '%s' does not exist.", s_elf_override.c_str());
|
||||
return false;
|
||||
}
|
||||
|
||||
Hle_SetElfPath(s_elf_override.c_str());
|
||||
EmuConfig.UseBOOT2Injection = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
std::string().swap(s_elf_override);
|
||||
}
|
||||
|
||||
if (params.source_type == CDVD_SourceType::Iso)
|
||||
s_disc_path = params.source;
|
||||
else
|
||||
s_disc_path.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool VMManager::Initialize(const VMBootParameters& boot_params)
|
||||
@@ -538,7 +617,10 @@ bool VMManager::Initialize(const VMBootParameters& boot_params)
|
||||
};
|
||||
|
||||
LoadSettings();
|
||||
ApplyBootParameters(boot_params);
|
||||
|
||||
if (!ApplyBootParameters(boot_params))
|
||||
return false;
|
||||
|
||||
EmuConfig.LimiterMode = GetInitialLimiterMode();
|
||||
|
||||
Console.WriteLn("Allocating memory map...");
|
||||
@@ -655,17 +737,18 @@ bool VMManager::Initialize(const VMBootParameters& boot_params)
|
||||
frameLimitReset();
|
||||
cpuReset();
|
||||
|
||||
Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds());
|
||||
s_state.store(VMState::Paused);
|
||||
Host::OnVMStarted();
|
||||
|
||||
UpdateRunningGame(true);
|
||||
|
||||
SetEmuThreadAffinities(true);
|
||||
|
||||
PerformanceMetrics::Clear();
|
||||
Console.WriteLn("VM subsystems initialized in %.2f ms", init_timer.GetTimeMilliseconds());
|
||||
s_state.store(VMState::Paused);
|
||||
Host::OnVMStarted();
|
||||
|
||||
// do we want to load state?
|
||||
if (!boot_params.save_state.empty())
|
||||
if (!GSDumpReplayer::IsReplayingDump() && !boot_params.save_state.empty())
|
||||
{
|
||||
if (!DoLoadState(boot_params.save_state.c_str()))
|
||||
{
|
||||
@@ -686,12 +769,16 @@ void VMManager::Shutdown(bool allow_save_resume_state /* = true */)
|
||||
vu1Thread.WaitVU();
|
||||
GetMTGS().WaitGS();
|
||||
|
||||
if (allow_save_resume_state && ShouldSaveResumeState())
|
||||
if (!GSDumpReplayer::IsReplayingDump() && allow_save_resume_state && ShouldSaveResumeState())
|
||||
{
|
||||
std::string resume_file_name(GetCurrentSaveStateFileName(-1));
|
||||
if (!resume_file_name.empty() && !DoSaveState(resume_file_name.c_str(), -1))
|
||||
Console.Error("Failed to save resume state");
|
||||
}
|
||||
else if (GSDumpReplayer::IsReplayingDump())
|
||||
{
|
||||
GSDumpReplayer::Shutdown();
|
||||
}
|
||||
|
||||
{
|
||||
std::unique_lock lock(s_info_mutex);
|
||||
@@ -784,6 +871,9 @@ std::string VMManager::GetCurrentSaveStateFileName(s32 slot)
|
||||
|
||||
bool VMManager::DoLoadState(const char* filename)
|
||||
{
|
||||
if (GSDumpReplayer::IsReplayingDump())
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
Host::OnSaveStateLoading(filename);
|
||||
@@ -802,6 +892,9 @@ bool VMManager::DoLoadState(const char* filename)
|
||||
|
||||
bool VMManager::DoSaveState(const char* filename, s32 slot_for_message)
|
||||
{
|
||||
if (GSDumpReplayer::IsReplayingDump())
|
||||
return false;
|
||||
|
||||
try
|
||||
{
|
||||
std::unique_ptr<ArchiveEntryList> elist = std::make_unique<ArchiveEntryList>(new VmStateBuffer(L"Zippable Savestate"));
|
||||
@@ -907,29 +1000,12 @@ bool VMManager::ChangeDisc(std::string path)
|
||||
|
||||
bool VMManager::IsElfFileName(const std::string& path)
|
||||
{
|
||||
const std::string::size_type pos = path.rfind('.');
|
||||
if (pos == std::string::npos)
|
||||
return false;
|
||||
|
||||
return (StringUtil::Strcasecmp(&path[pos], ".elf") == 0);
|
||||
return StringUtil::EndsWithNoCase(path, ".elf");
|
||||
}
|
||||
|
||||
void VMManager::SetBootParametersForPath(const std::string& path, VMBootParameters* params)
|
||||
bool VMManager::IsGSDumpFileName(const std::string& path)
|
||||
{
|
||||
if (IsElfFileName(path))
|
||||
{
|
||||
params->elf_override = path;
|
||||
params->source_type = CDVD_SourceType::NoDisc;
|
||||
}
|
||||
else if (!path.empty())
|
||||
{
|
||||
params->source_type = CDVD_SourceType::Iso;
|
||||
params->source = path;
|
||||
}
|
||||
else
|
||||
{
|
||||
params->source_type = CDVD_SourceType::NoDisc;
|
||||
}
|
||||
return (StringUtil::EndsWithNoCase(path, ".gs") || StringUtil::EndsWithNoCase(path, ".gs.xz"));
|
||||
}
|
||||
|
||||
void VMManager::Execute()
|
||||
|
||||
+6
-5
@@ -39,10 +39,11 @@ enum class VMState
|
||||
|
||||
struct VMBootParameters
|
||||
{
|
||||
std::string source;
|
||||
std::string save_state;
|
||||
CDVD_SourceType source_type;
|
||||
std::string filename;
|
||||
std::string elf_override;
|
||||
std::string save_state;
|
||||
std::optional<CDVD_SourceType> source_type;
|
||||
|
||||
std::optional<bool> fast_boot;
|
||||
std::optional<bool> fullscreen;
|
||||
std::optional<bool> batch_mode;
|
||||
@@ -135,8 +136,8 @@ namespace VMManager
|
||||
/// Returns true if the specified path is an ELF.
|
||||
bool IsElfFileName(const std::string& path);
|
||||
|
||||
/// Updates boot parameters for a given start filename. If it's an elf, it'll set elf_override, otherwise source.
|
||||
void SetBootParametersForPath(const std::string& path, VMBootParameters* params);
|
||||
/// Returns true if the specified path is a GS Dump.
|
||||
bool IsGSDumpFileName(const std::string& path);
|
||||
|
||||
/// Returns the path for the game settings ini file for the specified CRC.
|
||||
std::string GetGameSettingsPath(u32 game_crc);
|
||||
|
||||
@@ -30,6 +30,7 @@
|
||||
#include "R5900OpcodeTables.h"
|
||||
#include "VUmicro.h"
|
||||
#include "Vif_Dma.h"
|
||||
#include "MTVU.h"
|
||||
|
||||
#define _Ft_ _Rt_
|
||||
#define _Fs_ _Rd_
|
||||
|
||||
+4
-3
@@ -61,15 +61,16 @@ void __fastcall vu1ExecMicro(u32 addr)
|
||||
{
|
||||
if (THREAD_VU1) {
|
||||
VU0.VI[REG_VPU_STAT].UL &= ~0xFF00;
|
||||
|
||||
// Okay this is a little bit of a hack, but with good reason.
|
||||
// Most of the time with MTVU we want to pretend the VU has finished quickly as to gain the benefit from running another thread
|
||||
// however with T-Bit games when the T-Bit is enabled, it needs to wait in case a T-Bit happens, so we need to set "Busy"
|
||||
// We shouldn't do this all the time as it negates the extra thread and causes games like Ratchet & Clank to be no faster.
|
||||
if(VU0.VI[REG_FBRST].UL & 0x800)
|
||||
if (VU0.VI[REG_FBRST].UL & 0x800)
|
||||
{
|
||||
VU0.VI[REG_VPU_STAT].UL |= 0x0100;
|
||||
}
|
||||
|
||||
vu1Thread.ExecuteVU(addr, vif1Regs.top, vif1Regs.itop);
|
||||
vu1Thread.ExecuteVU(addr, vif1Regs.top, vif1Regs.itop, VU0.VI[REG_FBRST].UL);
|
||||
return;
|
||||
}
|
||||
static int count = 0;
|
||||
|
||||
+9
-16
@@ -305,6 +305,7 @@ _vifT __fi u32 vifRead32(u32 mem)
|
||||
{
|
||||
vifStruct& vif = MTVU_VifX;
|
||||
bool wait = idx && THREAD_VU1;
|
||||
|
||||
switch (mem)
|
||||
{
|
||||
case caseVif(ROW0):
|
||||
@@ -380,44 +381,36 @@ _vifT __fi bool vifWrite32(u32 mem, u32 value)
|
||||
|
||||
case caseVif(ROW0):
|
||||
vif.MaskRow._u32[0] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteRow(vif);
|
||||
vu1Thread.WriteRow(vif);
|
||||
return false;
|
||||
case caseVif(ROW1):
|
||||
vif.MaskRow._u32[1] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteRow(vif);
|
||||
vu1Thread.WriteRow(vif);
|
||||
return false;
|
||||
case caseVif(ROW2):
|
||||
vif.MaskRow._u32[2] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteRow(vif);
|
||||
vu1Thread.WriteRow(vif);
|
||||
return false;
|
||||
case caseVif(ROW3):
|
||||
vif.MaskRow._u32[3] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteRow(vif);
|
||||
vu1Thread.WriteRow(vif);
|
||||
return false;
|
||||
|
||||
case caseVif(COL0):
|
||||
vif.MaskCol._u32[0] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteCol(vif);
|
||||
vu1Thread.WriteCol(vif);
|
||||
return false;
|
||||
case caseVif(COL1):
|
||||
vif.MaskCol._u32[1] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteCol(vif);
|
||||
vu1Thread.WriteCol(vif);
|
||||
return false;
|
||||
case caseVif(COL2):
|
||||
vif.MaskCol._u32[2] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteCol(vif);
|
||||
vu1Thread.WriteCol(vif);
|
||||
return false;
|
||||
case caseVif(COL3):
|
||||
vif.MaskCol._u32[3] = value;
|
||||
if (idx && THREAD_VU1)
|
||||
vu1Thread.WriteCol(vif);
|
||||
vu1Thread.WriteCol(vif);
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
+4
-8
@@ -173,9 +173,6 @@ __fi void vif1SetupTransfer()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
if (vif1ch.chcr.TTE)
|
||||
{
|
||||
// Transfer dma tag if tte is set
|
||||
@@ -203,7 +200,7 @@ __fi void vif1SetupTransfer()
|
||||
ret = VIF1transfer((u32*)&masked_tag + 2, 2, true); //Transfer Tag
|
||||
//ret = VIF1transfer((u32*)ptag + 2, 2); //Transfer Tag
|
||||
}
|
||||
|
||||
|
||||
if (!ret && vif1.irqoffset.enabled)
|
||||
{
|
||||
vif1.inprogress &= ~1; // Better clear this so it has to do it again (Jak 1)
|
||||
@@ -233,8 +230,7 @@ __fi void vif1VUFinish()
|
||||
{
|
||||
if (VU0.VI[REG_VPU_STAT].UL & 0x500)
|
||||
{
|
||||
if(THREAD_VU1)
|
||||
vu1Thread.Get_MTVUChanges();
|
||||
vu1Thread.Get_MTVUChanges();
|
||||
|
||||
CPU_INT(VIF_VU1_FINISH, 128);
|
||||
return;
|
||||
@@ -355,11 +351,11 @@ __fi void vif1Interrupt()
|
||||
vif1.vifstalled.enabled = false;
|
||||
|
||||
//Mirroring change to VIF0
|
||||
if (vif1.cmd)
|
||||
if (vif1.cmd)
|
||||
{
|
||||
if (vif1.done && (vif1ch.qwc == 0)) vif1Regs.stat.VPS = VPS_WAITING;
|
||||
}
|
||||
else
|
||||
else
|
||||
{
|
||||
vif1Regs.stat.VPS = VPS_IDLE;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
+114
-207
@@ -22,8 +22,9 @@
|
||||
|
||||
|
||||
#include "common/EmbeddedImage.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/StringUtil.h"
|
||||
#include "gui/Resources/NoIcon.h"
|
||||
#include "GS.h"
|
||||
#include "HostDisplay.h"
|
||||
|
||||
#include "PathDefs.h"
|
||||
@@ -31,6 +32,7 @@
|
||||
#include "gui/GSFrame.h"
|
||||
#include "Counters.h"
|
||||
#include "PerformanceMetrics.h"
|
||||
#include "GameDatabase.h"
|
||||
|
||||
#include <wx/mstream.h>
|
||||
#include <wx/listctrl.h>
|
||||
@@ -45,125 +47,35 @@
|
||||
#include <wx/wfstream.h>
|
||||
#include <array>
|
||||
#include <functional>
|
||||
#include <optional>
|
||||
|
||||
template <typename Output, typename Input, typename std::enable_if<sizeof(Input) == sizeof(Output), bool>::type = true>
|
||||
static constexpr Output BitCast(Input input)
|
||||
{
|
||||
Output output;
|
||||
memcpy(&output, &input, sizeof(input));
|
||||
return output;
|
||||
}
|
||||
template <typename Output = u32>
|
||||
static constexpr Output GetBits(u64 value, u32 shift, u32 numbits)
|
||||
{
|
||||
return static_cast<Output>((value >> shift) & ((1ull << numbits) - 1));
|
||||
}
|
||||
using namespace GSDumpTypes;
|
||||
|
||||
template <typename Output = u32>
|
||||
static constexpr Output GetBits(u128 value, u32 shift, u32 numbits)
|
||||
static std::optional<wxImage> GetPreviewImageFromDump(const std::string& filename)
|
||||
{
|
||||
u64 outval = 0;
|
||||
if (shift == 0)
|
||||
outval = value.lo;
|
||||
else if (shift < 64)
|
||||
outval = (value.lo >> shift) | (value.hi << (64 - shift));
|
||||
else
|
||||
outval = value.hi >> (shift - 64);
|
||||
return static_cast<Output>(outval & ((1ull << numbits) - 1));
|
||||
}
|
||||
static constexpr const char* GetNameOneBit(u8 value, const char* zero, const char* one)
|
||||
{
|
||||
switch (value)
|
||||
{
|
||||
case 0: return zero;
|
||||
case 1: return one;
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNameBool(bool value)
|
||||
{
|
||||
return value ? "True" : "False";
|
||||
}
|
||||
std::vector<u32> pixels;
|
||||
u32 width, height;
|
||||
if (!GSDumpFile::GetPreviewImageFromDump(filename.c_str(), &width, &height, &pixels))
|
||||
return std::nullopt;
|
||||
|
||||
static constexpr const char* GetNamePRIMPRIM(u8 prim)
|
||||
{
|
||||
switch (prim)
|
||||
// strip alpha bytes because wx is dumb and stores on a separate plane
|
||||
// apparently this isn't aligned? stupidity...
|
||||
const u32 pitch = width * 3;
|
||||
u8* wxpixels = static_cast<u8*>(std::malloc(pitch * height));
|
||||
for (u32 y = 0; y < height; y++)
|
||||
{
|
||||
case 0: return "Point";
|
||||
case 1: return "Line";
|
||||
case 2: return "Line Strip";
|
||||
case 3: return "Triangle";
|
||||
case 4: return "Triangle Strip";
|
||||
case 5: return "Triangle Fan";
|
||||
case 6: return "Sprite";
|
||||
case 7: return "Invalid";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNamePRIMIIP(u8 iip)
|
||||
{
|
||||
return GetNameOneBit(iip, "Flat Shading", "Gouraud Shading");
|
||||
}
|
||||
static constexpr const char* GetNamePRIMFST(u8 fst)
|
||||
{
|
||||
return GetNameOneBit(fst, "STQ Value", "UV Value");
|
||||
}
|
||||
static constexpr const char* GetNamePRIMCTXT(u8 ctxt)
|
||||
{
|
||||
return GetNameOneBit(ctxt, "Context 1", "Context 2");
|
||||
}
|
||||
static constexpr const char* GetNamePRIMFIX(u8 fix)
|
||||
{
|
||||
return GetNameOneBit(fix, "Unfixed", "Fixed");
|
||||
}
|
||||
static constexpr const char* GetNameTEXTCC(u8 tcc)
|
||||
{
|
||||
return GetNameOneBit(tcc, "RGB", "RGBA");
|
||||
}
|
||||
static constexpr const char* GetNameTEXTFX(u8 tfx)
|
||||
{
|
||||
switch (tfx)
|
||||
{
|
||||
case 0: return "Modulate";
|
||||
case 1: return "Decal";
|
||||
case 2: return "Highlight";
|
||||
case 3: return "Highlight2";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNameTEXCSM(u8 csm)
|
||||
{
|
||||
return GetNameOneBit(csm, "CSM1", "CSM2");
|
||||
}
|
||||
static constexpr const char* GetNameTEXPSM(u8 psm)
|
||||
{
|
||||
switch (psm)
|
||||
{
|
||||
case 000: return "PSMCT32";
|
||||
case 001: return "PSMCT24";
|
||||
case 002: return "PSMCT16";
|
||||
case 012: return "PSMCT16S";
|
||||
case 023: return "PSMT8";
|
||||
case 024: return "PSMT4";
|
||||
case 033: return "PSMT8H";
|
||||
case 044: return "PSMT4HL";
|
||||
case 054: return "PSMT4HH";
|
||||
case 060: return "PSMZ32";
|
||||
case 061: return "PSMZ24";
|
||||
case 062: return "PSMZ16";
|
||||
case 072: return "PSMZ16S";
|
||||
default: return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
static constexpr const char* GetNameTEXCPSM(u8 psm)
|
||||
{
|
||||
switch (psm)
|
||||
{
|
||||
case 000: return "PSMCT32";
|
||||
case 002: return "PSMCT16";
|
||||
case 012: return "PSMCT16S";
|
||||
default: return "UNKNOWN";
|
||||
const u8* in = reinterpret_cast<const u8*>(pixels.data() + y * width);
|
||||
u8* out = wxpixels + y * pitch;
|
||||
for (u32 x = 0; x < width; x++)
|
||||
{
|
||||
*(out++) = in[0];
|
||||
*(out++) = in[1];
|
||||
*(out++) = in[2];
|
||||
in += sizeof(u32);
|
||||
}
|
||||
}
|
||||
|
||||
return wxImage(wxSize(width, height), wxpixels);
|
||||
}
|
||||
|
||||
namespace GSDump
|
||||
@@ -333,8 +245,17 @@ void Dialogs::GSDumpDialog::SelectedDump(wxListEvent& evt)
|
||||
img.Rescale(400,250, wxIMAGE_QUALITY_HIGH);
|
||||
m_preview_image->SetBitmap(wxBitmap(img));
|
||||
}
|
||||
else if (std::optional<wxImage> img = GetPreviewImageFromDump(StringUtil::wxStringToUTF8String(filename)); img.has_value())
|
||||
{
|
||||
// try embedded dump
|
||||
img->Rescale(400, 250, wxIMAGE_QUALITY_HIGH);
|
||||
m_preview_image->SetBitmap(img.value());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_preview_image->SetBitmap(EmbeddedImage<res_NoIcon>().Get());
|
||||
}
|
||||
|
||||
m_selected_dump = wxString(filename);
|
||||
}
|
||||
|
||||
@@ -368,18 +289,16 @@ void Dialogs::GSDumpDialog::RunDump(wxCommandEvent& event)
|
||||
{
|
||||
if (!m_run->IsEnabled())
|
||||
return;
|
||||
FILE* dumpfile = wxFopen(m_selected_dump, L"rb");
|
||||
if (!dumpfile)
|
||||
|
||||
m_thread->m_dump_file = GSDumpFile::OpenGSDump(m_selected_dump.ToUTF8());
|
||||
if (!m_thread->m_dump_file)
|
||||
{
|
||||
wxString s;
|
||||
s.Printf(_("Failed to load the dump %s !"), m_selected_dump);
|
||||
wxMessageBox(s, _("GS Debugger"), wxICON_ERROR);
|
||||
return;
|
||||
}
|
||||
if (m_selected_dump.EndsWith(".xz"))
|
||||
m_thread->m_dump_file = std::make_unique<GSDumpLzma>(dumpfile, nullptr);
|
||||
else
|
||||
m_thread->m_dump_file = std::make_unique<GSDumpRaw >(dumpfile, nullptr);
|
||||
|
||||
m_run->Disable();
|
||||
m_settings->Disable();
|
||||
m_debug_mode->Enable();
|
||||
@@ -483,11 +402,11 @@ void Dialogs::GSDumpDialog::GenPacketList()
|
||||
m_gif_items.clear();
|
||||
wxTreeItemId mainrootId = m_gif_list->AddRoot("root");
|
||||
wxTreeItemId rootId = m_gif_list->AppendItem(mainrootId, "0 - VSync");
|
||||
for (auto& element : m_dump_packets)
|
||||
for (auto& element : m_thread->m_dump_file->GetPackets())
|
||||
{
|
||||
wxString s, t;
|
||||
element.id == GSType::Transfer ? t.Printf(" - %s", GetName(element.path)) : t.Printf("");
|
||||
s.Printf("%d - %s%s - %d byte", i, GetName(element.id), t, element.length);
|
||||
element.id == GSType::Transfer ? t.Printf(" - %s", GSDumpTypes::GetName(element.path)) : t.Printf("");
|
||||
s.Printf("%d - %s%s - %d byte", i, GSDumpTypes::GetName(element.id), t, element.length);
|
||||
if (element.id == GSType::VSync)
|
||||
{
|
||||
m_gif_list->SetItemText(rootId, s);
|
||||
@@ -506,7 +425,7 @@ void Dialogs::GSDumpDialog::GenPacketList()
|
||||
m_gif_list->SelectItem(m_gif_items[0]);
|
||||
}
|
||||
|
||||
void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump)
|
||||
void Dialogs::GSDumpDialog::GenPacketInfo(const GSDumpFile::GSData& dump)
|
||||
{
|
||||
m_gif_packet->DeleteAllItems();
|
||||
wxTreeItemId rootId = m_gif_packet->AddRoot("root");
|
||||
@@ -514,12 +433,12 @@ void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump)
|
||||
{
|
||||
case GSType::Transfer:
|
||||
{
|
||||
char* data = dump.data.get();
|
||||
const u8* data = dump.data;
|
||||
u32 remaining = dump.length;
|
||||
int idx = 0;
|
||||
while (remaining >= 16)
|
||||
{
|
||||
wxTreeItemId trootId = m_gif_packet->AppendItem(rootId, wxString::Format("Transfer Path %s Packet %u", GetName(dump.path), idx));
|
||||
wxTreeItemId trootId = m_gif_packet->AppendItem(rootId, wxString::Format("Transfer Path %s Packet %u", GSDumpTypes::GetName(dump.path), idx));
|
||||
ParseTransfer(trootId, data);
|
||||
m_gif_packet->Expand(trootId);
|
||||
u32 size = ReadPacketSize(data);
|
||||
@@ -532,7 +451,7 @@ void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump)
|
||||
case GSType::VSync:
|
||||
{
|
||||
wxString s;
|
||||
s.Printf("Field = %u", *(u8*)(dump.data.get()));
|
||||
s.Printf("Field = %u", dump.data[0]);
|
||||
m_gif_packet->AppendItem(rootId, s);
|
||||
break;
|
||||
}
|
||||
@@ -549,7 +468,7 @@ void Dialogs::GSDumpDialog::GenPacketInfo(GSData& dump)
|
||||
}
|
||||
}
|
||||
|
||||
void Dialogs::GSDumpDialog::ParseTransfer(wxTreeItemId& trootId, char* data)
|
||||
void Dialogs::GSDumpDialog::ParseTransfer(wxTreeItemId& trootId, const u8* data)
|
||||
{
|
||||
u64 tag = *(u64*)data;
|
||||
u64 regs = *(u64*)(data + 8);
|
||||
@@ -567,7 +486,7 @@ void Dialogs::GSDumpDialog::ParseTransfer(wxTreeItemId& trootId, char* data)
|
||||
|
||||
m_gif_packet->AppendItem(trootId, wxString::Format("nloop = %u", nloop));
|
||||
m_gif_packet->AppendItem(trootId, wxString::Format("eop = %u", eop));
|
||||
m_gif_packet->AppendItem(trootId, wxString::Format("flg = %s", GetName(flg)));
|
||||
m_gif_packet->AppendItem(trootId, wxString::Format("flg = %s", GSDumpTypes::GetName(flg)));
|
||||
m_gif_packet->AppendItem(trootId, wxString::Format("pre = %u", pre));
|
||||
if (pre)
|
||||
{
|
||||
@@ -619,12 +538,12 @@ void Dialogs::GSDumpDialog::ParseTransfer(wxTreeItemId& trootId, char* data)
|
||||
|
||||
void Dialogs::GSDumpDialog::ParsePacket(wxTreeEvent& event)
|
||||
{
|
||||
GenPacketInfo(m_dump_packets[wxAtoi(m_gif_list->GetItemText(event.GetItem()).BeforeFirst('-'))]);
|
||||
GenPacketInfo(m_thread->m_dump_file->GetPackets()[wxAtoi(m_gif_list->GetItemText(event.GetItem()).BeforeFirst('-'))]);
|
||||
}
|
||||
|
||||
void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data, bool packed)
|
||||
{
|
||||
wxTreeItemId rootId = m_gif_packet->AppendItem(id, wxString(GetName(reg)));
|
||||
wxTreeItemId rootId = m_gif_packet->AppendItem(id, wxString(GSDumpTypes::GetName(reg)));
|
||||
switch (reg)
|
||||
{
|
||||
case GIFReg::PRIM:
|
||||
@@ -652,7 +571,7 @@ void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data
|
||||
q = BitCast<float>(data._u32[1]);
|
||||
}
|
||||
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - #%02x%02x%02x%02x, %g", GetName(reg), r, g, b, a, q));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - #%02x%02x%02x%02x, %g", GSDumpTypes::GetName(reg), r, g, b, a, q));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("R = %u", r));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("G = %u", g));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("B = %u", b));
|
||||
@@ -670,11 +589,11 @@ void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data
|
||||
{
|
||||
m_stored_q = BitCast<float>(data._u32[2]);
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("Q = %g", m_stored_q));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - (%g, %g, %g)", GetName(reg), s, t, m_stored_q));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - (%g, %g, %g)", GSDumpTypes::GetName(reg), s, t, m_stored_q));
|
||||
}
|
||||
else
|
||||
{
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - (%g, %g)", GetName(reg), s, t));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - (%g, %g)", GSDumpTypes::GetName(reg), s, t));
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -684,15 +603,15 @@ void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data
|
||||
float v = static_cast<float>(GetBits(data, packed ? 32 : 16, 14)) / 16.f;
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("U = %g", u));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("V = %g", v));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - (%g, %g)", GetName(reg), u, v));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - (%g, %g)", GSDumpTypes::GetName(reg), u, v));
|
||||
break;
|
||||
}
|
||||
case GIFReg::XYZF2:
|
||||
case GIFReg::XYZF3:
|
||||
{
|
||||
const char* name = GetName(reg);
|
||||
const char* name = GSDumpTypes::GetName(reg);
|
||||
if (packed && (reg == GIFReg::XYZF2) && GetBits(data, 111, 1))
|
||||
name = GetName(GIFReg::XYZF3);
|
||||
name = GSDumpTypes::GetName(GIFReg::XYZF3);
|
||||
|
||||
float x, y;
|
||||
u32 z, f;
|
||||
@@ -722,9 +641,9 @@ void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data
|
||||
case GIFReg::XYZ2:
|
||||
case GIFReg::XYZ3:
|
||||
{
|
||||
const char* name = GetName(reg);
|
||||
const char* name = GSDumpTypes::GetName(reg);
|
||||
if (packed && (reg == GIFReg::XYZ2) && GetBits(data, 111, 1))
|
||||
name = GetName(GIFReg::XYZ3);
|
||||
name = GSDumpTypes::GetName(GIFReg::XYZ3);
|
||||
|
||||
float x, y;
|
||||
u32 z;
|
||||
@@ -755,7 +674,7 @@ void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data
|
||||
u32 tw = GetBits(data, 26, 4);
|
||||
u32 th = GetBits(data, 30, 4);
|
||||
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - %ux%u %s", GetName(reg), 1 << tw, 1 << th, GetNameTEXPSM(psm)));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - %ux%u %s", GSDumpTypes::GetName(reg), 1 << tw, 1 << th, GetNameTEXPSM(psm)));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("TBP0 = %u", GetBits(data, 0, 14)));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("TBW = %u", GetBits(data, 14, 6)));
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("PSM = %s", GetNameTEXPSM(psm)));
|
||||
@@ -774,7 +693,7 @@ void Dialogs::GSDumpDialog::ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data
|
||||
{
|
||||
u32 f = GetBits(data, packed ? 100 : 56, 8);
|
||||
m_gif_packet->AppendItem(rootId, wxString::Format("F = %u", f));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - %u", GetName(reg), f));
|
||||
m_gif_packet->SetItemText(rootId, wxString::Format("%s - %u", GSDumpTypes::GetName(reg), f));
|
||||
break;
|
||||
}
|
||||
case GIFReg::AD:
|
||||
@@ -815,7 +734,7 @@ void Dialogs::GSDumpDialog::ParseTreePrim(wxTreeItemId& id, u32 prim)
|
||||
m_gif_packet->Expand(id);
|
||||
}
|
||||
|
||||
void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, char* regs)
|
||||
void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSDumpFile::GSData& event, u8* regs)
|
||||
{
|
||||
switch (event.id)
|
||||
{
|
||||
@@ -827,18 +746,18 @@ void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, char* regs)
|
||||
{
|
||||
std::unique_ptr<char[]> data(new char[16384]);
|
||||
int addr = 16384 - event.length;
|
||||
memcpy(data.get(), event.data.get() + addr, event.length);
|
||||
memcpy(data.get(), event.data + addr, event.length);
|
||||
GSgifTransfer1((u8*)data.get(), addr);
|
||||
break;
|
||||
}
|
||||
case GSTransferPath::Path1New:
|
||||
GSgifTransfer((u8*)event.data.get(), event.length / 16);
|
||||
GSgifTransfer((u8*)event.data, event.length / 16);
|
||||
break;
|
||||
case GSTransferPath::Path2:
|
||||
GSgifTransfer2((u8*)event.data.get(), event.length / 16);
|
||||
GSgifTransfer2((u8*)event.data, event.length / 16);
|
||||
break;
|
||||
case GSTransferPath::Path3:
|
||||
GSgifTransfer3((u8*)event.data.get(), event.length / 16);
|
||||
GSgifTransfer3((u8*)event.data, event.length / 16);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
@@ -853,12 +772,12 @@ void Dialogs::GSDumpDialog::ProcessDumpEvent(const GSData& event, char* regs)
|
||||
}
|
||||
case GSType::ReadFIFO2:
|
||||
{
|
||||
std::unique_ptr<char[]> arr(new char[*((int*)event.data.get())]);
|
||||
GSreadFIFO2((u8*)arr.get(), *((int*)event.data.get()));
|
||||
std::unique_ptr<char[]> arr(new char[*((int*)event.data)]);
|
||||
GSreadFIFO2((u8*)arr.get(), *((int*)event.data));
|
||||
break;
|
||||
}
|
||||
case GSType::Registers:
|
||||
memcpy(regs, event.data.get(), 8192);
|
||||
memcpy(regs, event.data, 8192);
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -893,8 +812,12 @@ void Dialogs::GSDumpDialog::GSThread::OnStop()
|
||||
|
||||
void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
{
|
||||
GSDump::isRunning = true;
|
||||
u32 crc = 0, ss = 0;
|
||||
if (!m_dump_file->ReadFile())
|
||||
{
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
|
||||
GSRendererType renderer = g_Conf->EmuOptions.GS.Renderer;
|
||||
switch (m_renderer)
|
||||
{
|
||||
@@ -921,45 +844,6 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
default:
|
||||
break;
|
||||
}
|
||||
char regs[8192];
|
||||
|
||||
m_dump_file->Read(&crc, 4);
|
||||
m_dump_file->Read(&ss, 4);
|
||||
|
||||
|
||||
std::unique_ptr<char[]> state_data(new char[ss]);
|
||||
m_dump_file->Read(state_data.get(), ss);
|
||||
m_dump_file->Read(®s, 8192);
|
||||
|
||||
freezeData fd = {(int)ss, (u8*)state_data.get()};
|
||||
m_root_window->m_dump_packets.clear();
|
||||
|
||||
while (!m_dump_file->IsEof())
|
||||
{
|
||||
GSType id;
|
||||
GSTransferPath id_transfer = GSTransferPath::Dummy;
|
||||
m_dump_file->Read(&id, 1);
|
||||
s32 size = 0;
|
||||
switch (id)
|
||||
{
|
||||
case GSType::Transfer:
|
||||
m_dump_file->Read(&id_transfer, 1);
|
||||
m_dump_file->Read(&size, 4);
|
||||
break;
|
||||
case GSType::VSync:
|
||||
size = 1;
|
||||
break;
|
||||
case GSType::ReadFIFO2:
|
||||
size = 4;
|
||||
break;
|
||||
case GSType::Registers:
|
||||
size = 8192;
|
||||
break;
|
||||
}
|
||||
std::unique_ptr<char[]> data(new char[size]);
|
||||
m_dump_file->Read(data.get(), size);
|
||||
m_root_window->m_dump_packets.push_back({id, std::move(data), size, id_transfer});
|
||||
}
|
||||
|
||||
GSinit();
|
||||
sApp.OpenGsPanel();
|
||||
@@ -974,16 +858,38 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
g_FrameCount = 0;
|
||||
}
|
||||
|
||||
if (!GSopen(g_Conf->EmuOptions.GS, renderer, (u8*)regs))
|
||||
Pcsx2Config::GSOptions config(g_Conf->EmuOptions.GS);
|
||||
config.MaskUserHacks();
|
||||
config.MaskUpscalingHacks();
|
||||
if (!m_dump_file->GetSerial().empty())
|
||||
{
|
||||
if (const GameDatabaseSchema::GameEntry* entry = GameDatabase::findGame(m_dump_file->GetSerial()); entry)
|
||||
{
|
||||
// apply hardware fixes to config before opening (e.g. tex in rt)
|
||||
entry->applyGSHardwareFixes(config);
|
||||
}
|
||||
}
|
||||
|
||||
u8 regs[8192] = {};
|
||||
if (!m_dump_file->GetRegsData().empty())
|
||||
std::memcpy(regs, m_dump_file->GetRegsData().data(), std::min(m_dump_file->GetRegsData().size(), sizeof(regs)));
|
||||
|
||||
if (!GSopen(config, renderer, regs))
|
||||
{
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
|
||||
GSsetGameCRC((int)crc, 0);
|
||||
GSsetGameCRC((int)m_dump_file->GetCRC(), 0);
|
||||
|
||||
freezeData fd = {static_cast<int>(m_dump_file->GetStateData().size()),
|
||||
const_cast<u8*>(m_dump_file->GetStateData().data())};
|
||||
if (GSfreeze(FreezeAction::Load, &fd))
|
||||
GSDump::isRunning = false;
|
||||
{
|
||||
OnStop();
|
||||
return;
|
||||
}
|
||||
|
||||
GSvsync(1, false);
|
||||
GSreset();
|
||||
GSfreeze(FreezeAction::Load, &fd);
|
||||
@@ -992,6 +898,7 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
m_debug_index = 0;
|
||||
size_t debug_idx = 0;
|
||||
|
||||
GSDump::isRunning = true;
|
||||
while (GSDump::isRunning)
|
||||
{
|
||||
if (m_debug)
|
||||
@@ -1001,7 +908,7 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
switch (m_root_window->m_button_events[0].btn)
|
||||
{
|
||||
case Step:
|
||||
if (debug_idx >= m_root_window->m_dump_packets.size())
|
||||
if (debug_idx >= m_dump_file->GetPackets().size())
|
||||
debug_idx = 0;
|
||||
m_debug_index = debug_idx;
|
||||
break;
|
||||
@@ -1011,13 +918,13 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
debug_idx = 0;
|
||||
break;
|
||||
case RunVSync:
|
||||
if (debug_idx >= m_root_window->m_dump_packets.size())
|
||||
if (debug_idx >= m_dump_file->GetPackets().size())
|
||||
debug_idx = 1;
|
||||
if ((debug_idx + 1) < m_root_window->m_dump_packets.size())
|
||||
if ((debug_idx + 1) < m_dump_file->GetPackets().size())
|
||||
{
|
||||
auto it = std::find_if(m_root_window->m_dump_packets.begin() + debug_idx + 1, m_root_window->m_dump_packets.end(), [](const GSData& gs) { return gs.id == GSType::Registers; });
|
||||
if (it != std::end(m_root_window->m_dump_packets))
|
||||
m_debug_index = std::distance(m_root_window->m_dump_packets.begin(), it);
|
||||
auto it = std::find_if(m_dump_file->GetPackets().begin() + debug_idx + 1, m_dump_file->GetPackets().end(), [](const GSDumpFile::GSData& gs) { return gs.id == GSType::Registers; });
|
||||
if (it != std::end(m_dump_file->GetPackets()))
|
||||
m_debug_index = std::distance(m_dump_file->GetPackets().begin(), it);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1027,12 +934,12 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
{
|
||||
while (debug_idx <= m_debug_index)
|
||||
{
|
||||
m_root_window->ProcessDumpEvent(m_root_window->m_dump_packets[debug_idx++], regs);
|
||||
m_root_window->ProcessDumpEvent(m_dump_file->GetPackets()[debug_idx++], regs);
|
||||
}
|
||||
if ((debug_idx + 1) < m_root_window->m_dump_packets.size())
|
||||
if ((debug_idx + 1) < m_dump_file->GetPackets().size())
|
||||
{
|
||||
auto it = std::find_if(m_root_window->m_dump_packets.begin() + debug_idx + 1, m_root_window->m_dump_packets.end(), [](const GSData& gs) { return gs.id == GSType::Registers; });
|
||||
if (it != std::end(m_root_window->m_dump_packets))
|
||||
auto it = std::find_if(m_dump_file->GetPackets().begin() + debug_idx + 1, m_dump_file->GetPackets().end(), [](const GSDumpFile::GSData& gs) { return gs.id == GSType::Registers; });
|
||||
if (it != std::end(m_dump_file->GetPackets()))
|
||||
m_root_window->ProcessDumpEvent(*it, regs);
|
||||
}
|
||||
}
|
||||
@@ -1041,11 +948,11 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
m_root_window->ProcessDumpEvent({GSType::VSync, 0, 0, GSTransferPath::Dummy}, regs);
|
||||
}
|
||||
}
|
||||
else if (m_root_window->m_dump_packets.size())
|
||||
else if (m_dump_file->GetPackets().size())
|
||||
{
|
||||
while (i < m_root_window->m_dump_packets.size())
|
||||
while (i < m_dump_file->GetPackets().size())
|
||||
{
|
||||
GSData& packet = m_root_window->m_dump_packets[i++];
|
||||
const GSDumpFile::GSData& packet = m_dump_file->GetPackets()[i++];
|
||||
m_root_window->ProcessDumpEvent(packet, regs);
|
||||
if (packet.id == GSType::VSync)
|
||||
{
|
||||
@@ -1065,7 +972,7 @@ void Dialogs::GSDumpDialog::GSThread::ExecuteTaskInThread()
|
||||
}
|
||||
}
|
||||
|
||||
if (i >= m_root_window->m_dump_packets.size())
|
||||
if (i >= m_dump_file->GetPackets().size())
|
||||
i = 0;
|
||||
}
|
||||
if (window)
|
||||
|
||||
@@ -25,28 +25,6 @@
|
||||
#include <wx/treectrl.h>
|
||||
#include <wx/fswatcher.h>
|
||||
|
||||
#define GEN_REG_ENUM_CLASS_CONTENT(ClassName, EntryName, Value) \
|
||||
EntryName = Value,
|
||||
|
||||
#define GEN_REG_GETNAME_CONTENT(ClassName, EntryName, Value) \
|
||||
case ClassName::EntryName: \
|
||||
return #EntryName;
|
||||
|
||||
#define GEN_REG_ENUM_CLASS_AND_GETNAME(Macro, ClassName, Type, DefaultString) \
|
||||
enum class ClassName : Type \
|
||||
{ \
|
||||
Macro(GEN_REG_ENUM_CLASS_CONTENT) \
|
||||
}; \
|
||||
static constexpr const char* GetName(ClassName reg) \
|
||||
{ \
|
||||
switch (reg) \
|
||||
{ \
|
||||
Macro(GEN_REG_GETNAME_CONTENT) \
|
||||
default: \
|
||||
return DefaultString; \
|
||||
} \
|
||||
}
|
||||
|
||||
class FirstTimeWizard : public wxWizard
|
||||
{
|
||||
typedef wxWizard _parent;
|
||||
@@ -147,102 +125,6 @@ namespace Dialogs
|
||||
ID_FRAMERATE,
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
|
||||
#define DEF_GSType(X) \
|
||||
X(GSType, Transfer, 0) \
|
||||
X(GSType, VSync, 1) \
|
||||
X(GSType, ReadFIFO2, 2) \
|
||||
X(GSType, Registers, 3)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GSType, GSType, u8, "UnknownType")
|
||||
#undef DEF_GSType
|
||||
|
||||
#define DEF_GSTransferPath(X) \
|
||||
X(GSTransferPath, Path1Old, 0) \
|
||||
X(GSTransferPath, Path2, 1) \
|
||||
X(GSTransferPath, Path3, 2) \
|
||||
X(GSTransferPath, Path1New, 3) \
|
||||
X(GSTransferPath, Dummy, 4)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GSTransferPath, GSTransferPath, u8, "UnknownPath")
|
||||
#undef DEF_GSTransferPath
|
||||
|
||||
#define DEF_GIFFlag(X) \
|
||||
X(GIFFlag, PACKED, 0) \
|
||||
X(GIFFlag, REGLIST, 1) \
|
||||
X(GIFFlag, IMAGE, 2) \
|
||||
X(GIFFlag, IMAGE2, 3)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GIFFlag, GIFFlag, u8, "UnknownFlag")
|
||||
#undef DEF_GifFlag
|
||||
|
||||
#define DEF_GIFReg(X) \
|
||||
X(GIFReg, PRIM, 0x00) \
|
||||
X(GIFReg, RGBAQ, 0x01) \
|
||||
X(GIFReg, ST, 0x02) \
|
||||
X(GIFReg, UV, 0x03) \
|
||||
X(GIFReg, XYZF2, 0x04) \
|
||||
X(GIFReg, XYZ2, 0x05) \
|
||||
X(GIFReg, TEX0_1, 0x06) \
|
||||
X(GIFReg, TEX0_2, 0x07) \
|
||||
X(GIFReg, CLAMP_1, 0x08) \
|
||||
X(GIFReg, CLAMP_2, 0x09) \
|
||||
X(GIFReg, FOG, 0x0a) \
|
||||
X(GIFReg, XYZF3, 0x0c) \
|
||||
X(GIFReg, XYZ3, 0x0d) \
|
||||
X(GIFReg, AD, 0x0e) \
|
||||
X(GIFReg, NOP, 0x0f) \
|
||||
X(GIFReg, TEX1_1, 0x14) \
|
||||
X(GIFReg, TEX1_2, 0x15) \
|
||||
X(GIFReg, TEX2_1, 0x16) \
|
||||
X(GIFReg, TEX2_2, 0x17) \
|
||||
X(GIFReg, XYOFFSET_1, 0x18) \
|
||||
X(GIFReg, XYOFFSET_2, 0x19) \
|
||||
X(GIFReg, PRMODECONT, 0x1a) \
|
||||
X(GIFReg, PRMODE, 0x1b) \
|
||||
X(GIFReg, TEXCLUT, 0x1c) \
|
||||
X(GIFReg, SCANMSK, 0x22) \
|
||||
X(GIFReg, MIPTBP1_1, 0x34) \
|
||||
X(GIFReg, MIPTBP1_2, 0x35) \
|
||||
X(GIFReg, MIPTBP2_1, 0x36) \
|
||||
X(GIFReg, MIPTBP2_2, 0x37) \
|
||||
X(GIFReg, TEXA, 0x3b) \
|
||||
X(GIFReg, FOGCOL, 0x3d) \
|
||||
X(GIFReg, TEXFLUSH, 0x3f) \
|
||||
X(GIFReg, SCISSOR_1, 0x40) \
|
||||
X(GIFReg, SCISSOR_2, 0x41) \
|
||||
X(GIFReg, ALPHA_1, 0x42) \
|
||||
X(GIFReg, ALPHA_2, 0x43) \
|
||||
X(GIFReg, DIMX, 0x44) \
|
||||
X(GIFReg, DTHE, 0x45) \
|
||||
X(GIFReg, COLCLAMP, 0x46) \
|
||||
X(GIFReg, TEST_1, 0x47) \
|
||||
X(GIFReg, TEST_2, 0x48) \
|
||||
X(GIFReg, PABE, 0x49) \
|
||||
X(GIFReg, FBA_1, 0x4a) \
|
||||
X(GIFReg, FBA_2, 0x4b) \
|
||||
X(GIFReg, FRAME_1, 0x4c) \
|
||||
X(GIFReg, FRAME_2, 0x4d) \
|
||||
X(GIFReg, ZBUF_1, 0x4e) \
|
||||
X(GIFReg, ZBUF_2, 0x4f) \
|
||||
X(GIFReg, BITBLTBUF, 0x50) \
|
||||
X(GIFReg, TRXPOS, 0x51) \
|
||||
X(GIFReg, TRXREG, 0x52) \
|
||||
X(GIFReg, TRXDIR, 0x53) \
|
||||
X(GIFReg, HWREG, 0x54) \
|
||||
X(GIFReg, SIGNAL, 0x60) \
|
||||
X(GIFReg, FINISH, 0x61) \
|
||||
X(GIFReg, LABEL, 0x62)
|
||||
GEN_REG_ENUM_CLASS_AND_GETNAME(DEF_GIFReg, GIFReg, u8, "UnknownReg")
|
||||
#undef DEF_GIFReg
|
||||
|
||||
// clang-format on
|
||||
|
||||
struct GSData
|
||||
{
|
||||
GSType id;
|
||||
std::unique_ptr<char[]> data;
|
||||
int length;
|
||||
GSTransferPath path;
|
||||
};
|
||||
enum ButtonState
|
||||
{
|
||||
Step,
|
||||
@@ -256,16 +138,15 @@ namespace Dialogs
|
||||
int index;
|
||||
};
|
||||
std::vector<GSEvent> m_button_events;
|
||||
std::vector<GSData> m_dump_packets;
|
||||
std::vector<wxTreeItemId> m_gif_items;
|
||||
|
||||
float m_stored_q = 1.0;
|
||||
void ProcessDumpEvent(const GSData& event, char* regs);
|
||||
void ProcessDumpEvent(const GSDumpFile::GSData& event, u8* regs);
|
||||
u32 ReadPacketSize(const void* packet);
|
||||
void GenPacketList();
|
||||
void GenPacketInfo(GSData& dump);
|
||||
void ParseTransfer(wxTreeItemId& id, char* data);
|
||||
void ParseTreeReg(wxTreeItemId& id, GIFReg reg, u128 data, bool packed);
|
||||
void GenPacketInfo(const GSDumpFile::GSData& dump);
|
||||
void ParseTransfer(wxTreeItemId& id, const u8* data);
|
||||
void ParseTreeReg(wxTreeItemId& id, GSDumpTypes::GIFReg reg, u128 data, bool packed);
|
||||
void ParseTreePrim(wxTreeItemId& id, u32 prim);
|
||||
void CloseDump(wxCommandEvent& event);
|
||||
class GSThread : public pxThread
|
||||
|
||||
@@ -186,6 +186,7 @@
|
||||
<ClCompile Include="GameDatabase.cpp" />
|
||||
<ClCompile Include="Gif_Logger.cpp" />
|
||||
<ClCompile Include="Gif_Unit.cpp" />
|
||||
<ClCompile Include="GSDumpReplayer.cpp" />
|
||||
<ClCompile Include="GS\Renderers\DX11\D3D.cpp" />
|
||||
<ClCompile Include="GS\Renderers\HW\GSTextureReplacementLoaders.cpp" />
|
||||
<ClCompile Include="GS\Renderers\HW\GSTextureReplacements.cpp" />
|
||||
@@ -488,6 +489,7 @@
|
||||
<ClInclude Include="Frontend\XInputSource.h" />
|
||||
<ClInclude Include="GameDatabase.h" />
|
||||
<ClInclude Include="Gif_Unit.h" />
|
||||
<ClInclude Include="GSDumpReplayer.h" />
|
||||
<ClInclude Include="GS\Renderers\DX11\D3D.h" />
|
||||
<ClInclude Include="GS\Renderers\HW\GSTextureReplacements.h" />
|
||||
<ClInclude Include="GS\Window\GSwxDialog.h" />
|
||||
|
||||
@@ -1202,6 +1202,7 @@
|
||||
<ClCompile Include="x86\iR5900Analysis.cpp">
|
||||
<Filter>System\Ps2\EmotionEngine\EE\Dynarec</Filter>
|
||||
</ClCompile>
|
||||
<ClCompile Include="GSDumpReplayer.cpp" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClInclude Include="Patch.h">
|
||||
@@ -1985,6 +1986,7 @@
|
||||
<ClInclude Include="x86\iR5900Analysis.h">
|
||||
<Filter>System\Ps2\EmotionEngine\EE\Dynarec</Filter>
|
||||
</ClInclude>
|
||||
<ClInclude Include="GSDumpReplayer.h" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ResourceCompile Include="GS\GS.rc">
|
||||
|
||||
@@ -327,19 +327,28 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
|
||||
{
|
||||
if ((psHu32(mem & ~0xff) & 0x100) && dmacRegs.ctrl.DMAE && !psHu8(DMAC_ENABLER + 2))
|
||||
{
|
||||
DevCon.Warning("Gamefix: Write to DMA addr %x while STR is busy!", mem);
|
||||
//DevCon.Warning("Gamefix: Write to DMA addr %x while STR is busy!", mem);
|
||||
while (psHu32(mem & ~0xff) & 0x100)
|
||||
{
|
||||
switch ((mem >> 8) & 0xFF)
|
||||
{
|
||||
case 0x80: // VIF0
|
||||
vif0Interrupt();
|
||||
cpuRegs.interrupt &= ~(1 << DMAC_VIF0);
|
||||
break;
|
||||
case 0x90: // VIF1
|
||||
vif1Interrupt();
|
||||
if (vif1Regs.stat.VEW)
|
||||
{
|
||||
vu1Finish(false);
|
||||
vif1VUFinish();
|
||||
}
|
||||
else
|
||||
vif1Interrupt();
|
||||
cpuRegs.interrupt &= ~(1 << DMAC_VIF1);
|
||||
break;
|
||||
case 0xA0: // GIF
|
||||
gifInterrupt();
|
||||
cpuRegs.interrupt &= ~(1 << DMAC_GIF);
|
||||
break;
|
||||
case 0xB0: // IPUFROM
|
||||
[[fallthrough]];
|
||||
@@ -351,9 +360,11 @@ __fi bool dmacWrite32( u32 mem, mem32_t& value )
|
||||
break;
|
||||
case 0xD0: // SPRFROM
|
||||
SPRFROMinterrupt();
|
||||
cpuRegs.interrupt &= ~(1 << DMAC_FROM_SPR);
|
||||
break;
|
||||
case 0xD4: // SPRTO
|
||||
SPRTOinterrupt();
|
||||
cpuRegs.interrupt &= ~(1 << DMAC_TO_SPR);
|
||||
break;
|
||||
default:
|
||||
return false;
|
||||
|
||||
@@ -127,21 +127,24 @@ void mVUDTendProgram(mV, microFlagCycles* mFC, int isEbit)
|
||||
|
||||
xMOV(ptr32[&mVU.regs().nextBlockCycles], 0);
|
||||
|
||||
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
|
||||
if (isEbit) // Clear 'is busy' Flags
|
||||
{
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
xAND(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? ~0x100 : ~0x001)); // VBS0/VBS1 flag
|
||||
}
|
||||
else
|
||||
xFastCall((void*)mVUTBit);
|
||||
}
|
||||
|
||||
if (isEbit != 2) // Save PC, and Jump to Exit Point
|
||||
{
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUTBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
}
|
||||
|
||||
memcpy(&mVUregs, &stateBackup, sizeof(mVUregs)); //Restore the state for the rest of the recompile
|
||||
}
|
||||
|
||||
@@ -244,6 +247,7 @@ void mVUendProgram(mV, microFlagCycles* mFC, int isEbit)
|
||||
xMOVAPS(ptr128[&mVU.regs().micro_statusflags], xmmT1);
|
||||
}
|
||||
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
|
||||
if ((isEbit && isEbit != 3)) // Clear 'is busy' Flags
|
||||
{
|
||||
@@ -252,8 +256,6 @@ void mVUendProgram(mV, microFlagCycles* mFC, int isEbit)
|
||||
{
|
||||
xAND(ptr32[&VU0.VI[REG_VPU_STAT].UL], (isVU1 ? ~0x100 : ~0x001)); // VBS0/VBS1 flag
|
||||
}
|
||||
else
|
||||
xFastCall((void*)mVUEBit);
|
||||
}
|
||||
else if(isEbit)
|
||||
{
|
||||
@@ -262,7 +264,8 @@ void mVUendProgram(mV, microFlagCycles* mFC, int isEbit)
|
||||
|
||||
if (isEbit != 2 && isEbit != 3) // Save PC, and Jump to Exit Point
|
||||
{
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
}
|
||||
memcpy(&mVUregs, &stateBackup, sizeof(mVUregs)); //Restore the state for the rest of the recompile
|
||||
@@ -321,6 +324,8 @@ void normJumpCompile(mV, microFlagCycles& mFC, bool isEvilJump)
|
||||
//So if it is taken, you need to end the program, else you get infinite loops.
|
||||
mVUendProgram(mVU, &mFC, 2);
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], arg1regd);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
}
|
||||
|
||||
@@ -340,7 +345,10 @@ void normBranch(mV, microFlagCycles& mFC)
|
||||
if (mVUup.dBit && doDBitHandling)
|
||||
{
|
||||
u32 tempPC = iPC;
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x400 : 0x4));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
@@ -355,7 +363,10 @@ void normBranch(mV, microFlagCycles& mFC)
|
||||
if (mVUup.tBit)
|
||||
{
|
||||
u32 tempPC = iPC;
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x800 : 0x8));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
@@ -381,6 +392,8 @@ void normBranch(mV, microFlagCycles& mFC)
|
||||
mVUendProgram(mVU, &mFC, 3);
|
||||
iPC = branchAddr(mVU) / 4;
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
iPC = tempPC;
|
||||
}
|
||||
@@ -407,7 +420,10 @@ void condBranch(mV, microFlagCycles& mFC, int JMPcc)
|
||||
{
|
||||
DevCon.Warning("T-Bit on branch, please report if broken");
|
||||
u32 tempPC = iPC;
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x800 : 0x8));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
@@ -419,11 +435,15 @@ void condBranch(mV, microFlagCycles& mFC, int JMPcc)
|
||||
xForwardJump32 tJMP(xInvertCond((JccComparisonType)JMPcc));
|
||||
incPC(4); // Set PC to First instruction of Non-Taken Side
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUTBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
tJMP.SetTarget();
|
||||
incPC(-4); // Go Back to Branch Opcode to get branchAddr
|
||||
iPC = branchAddr(mVU) / 4;
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUTBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
eJMP.SetTarget();
|
||||
iPC = tempPC;
|
||||
@@ -431,7 +451,10 @@ void condBranch(mV, microFlagCycles& mFC, int JMPcc)
|
||||
if (mVUup.dBit && doDBitHandling)
|
||||
{
|
||||
u32 tempPC = iPC;
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x400 : 0x4));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
@@ -466,11 +489,15 @@ void condBranch(mV, microFlagCycles& mFC, int JMPcc)
|
||||
xForwardJump32 dJMP((JccComparisonType)JMPcc);
|
||||
incPC(4); // Set PC to First instruction of Non-Taken Side
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
dJMP.SetTarget();
|
||||
incPC(-4); // Go Back to Branch Opcode to get branchAddr
|
||||
iPC = branchAddr(mVU) / 4;
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
iPC = tempPC;
|
||||
}
|
||||
@@ -486,12 +513,16 @@ void condBranch(mV, microFlagCycles& mFC, int JMPcc)
|
||||
xForwardJump32 eJMP(((JccComparisonType)JMPcc));
|
||||
incPC(1); // Set PC to First instruction of Non-Taken Side
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
eJMP.SetTarget();
|
||||
incPC(-4); // Go Back to Branch Opcode to get branchAddr
|
||||
|
||||
iPC = branchAddr(mVU) / 4;
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], xPC);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
return;
|
||||
}
|
||||
@@ -550,7 +581,10 @@ void normJump(mV, microFlagCycles& mFC)
|
||||
}
|
||||
if (mVUup.dBit && doDBitHandling)
|
||||
{
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
if (THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x400 : 0x4));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
@@ -565,7 +599,10 @@ void normJump(mV, microFlagCycles& mFC)
|
||||
}
|
||||
if (mVUup.tBit)
|
||||
{
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x800 : 0x8));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!mVU.index || !THREAD_VU1)
|
||||
{
|
||||
@@ -575,6 +612,8 @@ void normJump(mV, microFlagCycles& mFC)
|
||||
mVUDTendProgram(mVU, &mFC, 2);
|
||||
xMOV(gprT1, ptr32[&mVU.branch]);
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], gprT1);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUTBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
eJMP.SetTarget();
|
||||
}
|
||||
@@ -583,6 +622,8 @@ void normJump(mV, microFlagCycles& mFC)
|
||||
mVUendProgram(mVU, &mFC, 2);
|
||||
xMOV(gprT1, ptr32[&mVU.branch]);
|
||||
xMOV(ptr32[&mVU.regs().VI[REG_TPC].UL], gprT1);
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xFastCall((void*)mVUEBit);
|
||||
xJMP(mVU.exitFunct);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -549,7 +549,10 @@ __fi void mVUinitFirstPass(microVU& mVU, uptr pState, u8* thisPtr)
|
||||
|
||||
void mVUDoDBit(microVU& mVU, microFlagCycles* mFC)
|
||||
{
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x400 : 0x4));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x400 : 0x4));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!isVU1 || !THREAD_VU1)
|
||||
{
|
||||
@@ -564,7 +567,10 @@ void mVUDoDBit(microVU& mVU, microFlagCycles* mFC)
|
||||
|
||||
void mVUDoTBit(microVU& mVU, microFlagCycles* mFC)
|
||||
{
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
if (mVU.index && THREAD_VU1)
|
||||
xTEST(ptr32[&vu1Thread.vuFBRST], (isVU1 ? 0x800 : 0x8));
|
||||
else
|
||||
xTEST(ptr32[&VU0.VI[REG_FBRST].UL], (isVU1 ? 0x800 : 0x8));
|
||||
xForwardJump32 eJMP(Jcc_Zero);
|
||||
if (!isVU1 || !THREAD_VU1)
|
||||
{
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
extern void _vu0WaitMicro();
|
||||
extern void _vu0FinishMicro();
|
||||
|
||||
|
||||
@@ -113,6 +113,7 @@ TEST(CodegenTests, MathTest)
|
||||
CODEGEN_TEST_64(xADD(r8, r9), "4d 01 c8");
|
||||
CODEGEN_TEST_64(xADD(r8, 0x12), "49 83 c0 12");
|
||||
CODEGEN_TEST_64(xADD(rax, 0x1234), "48 05 34 12 00 00");
|
||||
CODEGEN_TEST_64(xADD(ptr8[base], 1), "80 05 f9 ff ff ff 01");
|
||||
CODEGEN_TEST_64(xADD(ptr32[base], -0x60), "83 05 f9 ff ff ff a0");
|
||||
CODEGEN_TEST_64(xADD(ptr32[base], 0x1234), "81 05 f6 ff ff ff 34 12 00 00");
|
||||
CODEGEN_TEST_BOTH(xADD(eax, ebx), "01 d8");
|
||||
|
||||
Reference in New Issue
Block a user