mirror of https://github.com/PCSX2/pcsx2.git
GSRunner: Add standalone GS runner/dumper
This commit is contained in:
parent
5ffcbad18b
commit
e9a61c24df
|
@ -61,6 +61,8 @@ Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "rainterface", "3rdparty\rai
|
|||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "discord-rpc", "3rdparty\discord-rpc\discord-rpc.vcxproj", "{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}"
|
||||
EndProject
|
||||
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "pcsx2-gsrunner", "pcsx2-gsrunner\pcsx2-gsrunner.vcxproj", "{BB98BF81-A132-444A-BB81-96D510F433A8}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug AVX2|x64 = Debug AVX2|x64
|
||||
|
@ -407,6 +409,12 @@ Global
|
|||
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release AVX2|x64.Build.0 = Release|x64
|
||||
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release|x64.ActiveCfg = Release|x64
|
||||
{E960DFDF-1BD3-4C29-B251-D1A0919C9B09}.Release|x64.Build.0 = Release|x64
|
||||
{BB98BF81-A132-444A-BB81-96D510F433A8}.Debug AVX2|x64.ActiveCfg = Debug AVX2|x64
|
||||
{BB98BF81-A132-444A-BB81-96D510F433A8}.Debug|x64.ActiveCfg = Debug|x64
|
||||
{BB98BF81-A132-444A-BB81-96D510F433A8}.Devel AVX2|x64.ActiveCfg = Devel AVX2|x64
|
||||
{BB98BF81-A132-444A-BB81-96D510F433A8}.Devel|x64.ActiveCfg = Devel|x64
|
||||
{BB98BF81-A132-444A-BB81-96D510F433A8}.Release AVX2|x64.ActiveCfg = Release AVX2|x64
|
||||
{BB98BF81-A132-444A-BB81-96D510F433A8}.Release|x64.ActiveCfg = Release|x64
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
add_executable(pcsx2-gsrunnner)
|
||||
|
||||
if (PACKAGE_MODE)
|
||||
install(TARGETS pcsx2-gsrunnner DESTINATION ${CMAKE_INSTALL_BINDIR})
|
||||
else()
|
||||
install(TARGETS pcsx2-gsrunnner DESTINATION ${CMAKE_SOURCE_DIR}/bin)
|
||||
endif()
|
||||
|
||||
target_sources(pcsx2-gsrunner PRIVATE
|
||||
Main.cpp
|
||||
)
|
||||
|
||||
target_include_directories(pcsx2-gsrunner PRIVATE
|
||||
"${CMAKE_BINARY_DIR}/common/include"
|
||||
"${CMAKE_SOURCE_DIR}/pcsx2"
|
||||
)
|
||||
|
||||
target_link_libraries(pcsx2-gsrunner PRIVATE
|
||||
PCSX2_FLAGS
|
||||
PCSX2
|
||||
)
|
|
@ -0,0 +1,719 @@
|
|||
/* PCSX2 - PS2 Emulator for PCs
|
||||
* Copyright (C) 2002-2022 PCSX2 Dev Team
|
||||
*
|
||||
* PCSX2 is free software: you can redistribute it and/or modify it under the terms
|
||||
* of the GNU Lesser General Public License as published by the Free Software Found-
|
||||
* ation, either version 3 of the License, or (at your option) any later version.
|
||||
*
|
||||
* PCSX2 is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
|
||||
* without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR
|
||||
* PURPOSE. See the GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along with PCSX2.
|
||||
* If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include <chrono>
|
||||
#include <csignal>
|
||||
#include <condition_variable>
|
||||
#include <mutex>
|
||||
#include <thread>
|
||||
|
||||
#ifdef _WIN32
|
||||
#include "common/RedtapeWindows.h"
|
||||
#endif
|
||||
|
||||
#include "fmt/core.h"
|
||||
|
||||
#include "common/Assertions.h"
|
||||
#include "common/Console.h"
|
||||
#include "common/Exceptions.h"
|
||||
#include "common/FileSystem.h"
|
||||
#include "common/MemorySettingsInterface.h"
|
||||
#include "common/Path.h"
|
||||
#include "common/SettingsWrapper.h"
|
||||
#include "common/StringUtil.h"
|
||||
|
||||
#include "pcsx2/PrecompiledHeader.h"
|
||||
|
||||
#include "pcsx2/CDVD/CDVD.h"
|
||||
#include "pcsx2/Frontend/CommonHost.h"
|
||||
#include "pcsx2/Frontend/InputManager.h"
|
||||
#include "pcsx2/Frontend/ImGuiManager.h"
|
||||
#include "pcsx2/Frontend/LogSink.h"
|
||||
#include "pcsx2/GS.h"
|
||||
#include "pcsx2/GS/GS.h"
|
||||
#include "pcsx2/GSDumpReplayer.h"
|
||||
#include "pcsx2/HostDisplay.h"
|
||||
#include "pcsx2/HostSettings.h"
|
||||
#include "pcsx2/INISettingsInterface.h"
|
||||
#include "pcsx2/PerformanceMetrics.h"
|
||||
#include "pcsx2/VMManager.h"
|
||||
|
||||
#ifdef ENABLE_ACHIEVEMENTS
|
||||
#include "pcsx2/Frontend/Achievements.h"
|
||||
#endif
|
||||
|
||||
#include "svnrev.h"
|
||||
|
||||
namespace GSRunner
|
||||
{
|
||||
static bool InitializeConfig();
|
||||
static bool SetCriticalFolders();
|
||||
|
||||
static bool CreatePlatformWindow();
|
||||
static void DestroyPlatformWindow();
|
||||
static std::optional<WindowInfo> GetPlatformWindowInfo();
|
||||
static void PumpPlatformMessages();
|
||||
} // namespace GSRunner
|
||||
|
||||
static constexpr u32 WINDOW_WIDTH = 640;
|
||||
static constexpr u32 WINDOW_HEIGHT = 480;
|
||||
|
||||
static MemorySettingsInterface s_settings_interface;
|
||||
alignas(16) static SysMtgsThread s_mtgs_thread;
|
||||
|
||||
static std::string s_output_prefix;
|
||||
static s32 s_loop_count = 1;
|
||||
|
||||
bool GSRunner::SetCriticalFolders()
|
||||
{
|
||||
EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(FileSystem::GetProgramPath()));
|
||||
EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources");
|
||||
EmuFolders::DataRoot = EmuFolders::AppRoot;
|
||||
|
||||
// allow SetDataDirectory() to change settings directory (if we want to split config later on)
|
||||
if (EmuFolders::Settings.empty())
|
||||
EmuFolders::Settings = Path::Combine(EmuFolders::DataRoot, "inis");
|
||||
|
||||
// the resources directory should exist, bail out if not
|
||||
if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str()))
|
||||
{
|
||||
Console.Error("Resources directory is missing, your installation is incomplete.");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool GSRunner::InitializeConfig()
|
||||
{
|
||||
if (!CommonHost::InitializeCriticalFolders())
|
||||
return false;
|
||||
|
||||
// don't provide an ini path, or bother loading. we'll store everything in memory.
|
||||
MemorySettingsInterface& si = s_settings_interface;
|
||||
Host::Internal::SetBaseSettingsLayer(&si);
|
||||
|
||||
CommonHost::SetDefaultSettings(si, true, true, true, true, true);
|
||||
|
||||
// complete as quickly as possible
|
||||
si.SetBoolValue("EmuCore/GS", "FrameLimitEnable", false);
|
||||
si.SetIntValue("EmuCore/GS", "VsyncEnable", static_cast<int>(VsyncMode::Off));
|
||||
|
||||
// ensure all input sources are disabled, we're not using them
|
||||
si.SetBoolValue("InputSources", "SDL", false);
|
||||
si.SetBoolValue("InputSources", "XInput", false);
|
||||
|
||||
// we don't need any sound output
|
||||
si.SetStringValue("SPU2/Output", "OutputModule", "nullout");
|
||||
|
||||
// force logging
|
||||
si.SetBoolValue("Logging", "EnableSystemConsole", true);
|
||||
si.SetBoolValue("Logging", "EnableTimestamps", true);
|
||||
si.SetBoolValue("Logging", "EnableVerbose", true);
|
||||
|
||||
// and show some stats :)
|
||||
si.SetBoolValue("EmuCore/GS", "OsdShowFPS", true);
|
||||
si.SetBoolValue("EmuCore/GS", "OsdShowResolution", true);
|
||||
si.SetBoolValue("EmuCore/GS", "OsdShowGSStats", true);
|
||||
|
||||
CommonHost::LoadStartupSettings();
|
||||
return true;
|
||||
}
|
||||
|
||||
void Host::CommitBaseSettingChanges()
|
||||
{
|
||||
// nothing to save, we're all in memory
|
||||
}
|
||||
|
||||
void Host::LoadSettings(SettingsInterface& si, std::unique_lock<std::mutex>& lock)
|
||||
{
|
||||
CommonHost::LoadSettings(si, lock);
|
||||
}
|
||||
|
||||
void Host::CheckForSettingsChanges(const Pcsx2Config& old_config)
|
||||
{
|
||||
CommonHost::CheckForSettingsChanges(old_config);
|
||||
}
|
||||
|
||||
bool Host::RequestResetSettings(bool folders, bool core, bool controllers, bool hotkeys, bool ui)
|
||||
{
|
||||
// not running any UI, so no settings requests will come in
|
||||
return false;
|
||||
}
|
||||
|
||||
void Host::SetDefaultUISettings(SettingsInterface& si)
|
||||
{
|
||||
// nothing
|
||||
}
|
||||
|
||||
std::optional<std::vector<u8>> Host::ReadResourceFile(const char* filename)
|
||||
{
|
||||
const std::string path(Path::Combine(EmuFolders::Resources, filename));
|
||||
std::optional<std::vector<u8>> ret(FileSystem::ReadBinaryFile(path.c_str()));
|
||||
if (!ret.has_value())
|
||||
Console.Error("Failed to read resource file '%s'", filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<std::string> Host::ReadResourceFileToString(const char* filename)
|
||||
{
|
||||
const std::string path(Path::Combine(EmuFolders::Resources, filename));
|
||||
std::optional<std::string> ret(FileSystem::ReadFileToString(path.c_str()));
|
||||
if (!ret.has_value())
|
||||
Console.Error("Failed to read resource file to string '%s'", filename);
|
||||
return ret;
|
||||
}
|
||||
|
||||
std::optional<std::time_t> Host::GetResourceFileTimestamp(const char* filename)
|
||||
{
|
||||
const std::string path(Path::Combine(EmuFolders::Resources, filename));
|
||||
FILESYSTEM_STAT_DATA sd;
|
||||
if (!FileSystem::StatFile(filename, &sd))
|
||||
return std::nullopt;
|
||||
|
||||
return sd.ModificationTime;
|
||||
}
|
||||
|
||||
void Host::ReportErrorAsync(const std::string_view& title, const std::string_view& message)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
{
|
||||
Console.Error(
|
||||
"ReportErrorAsync: %.*s: %.*s", static_cast<int>(title.size()), title.data(), static_cast<int>(message.size()), message.data());
|
||||
}
|
||||
else if (!message.empty())
|
||||
{
|
||||
Console.Error("ReportErrorAsync: %.*s", static_cast<int>(message.size()), message.data());
|
||||
}
|
||||
}
|
||||
|
||||
bool Host::ConfirmMessage(const std::string_view& title, const std::string_view& message)
|
||||
{
|
||||
if (!title.empty() && !message.empty())
|
||||
{
|
||||
Console.Error(
|
||||
"ConfirmMessage: %.*s: %.*s", static_cast<int>(title.size()), title.data(), static_cast<int>(message.size()), message.data());
|
||||
}
|
||||
else if (!message.empty())
|
||||
{
|
||||
Console.Error("ConfirmMessage: %.*s", static_cast<int>(message.size()), message.data());
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void Host::OpenURL(const std::string_view& url)
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
bool Host::CopyTextToClipboard(const std::string_view& text)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Host::BeginTextInput()
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
void Host::EndTextInput()
|
||||
{
|
||||
// noop
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
|
||||
{
|
||||
return GSRunner::GetPlatformWindowInfo();
|
||||
}
|
||||
|
||||
void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnInputDeviceDisconnected(const std::string_view& identifier)
|
||||
{
|
||||
}
|
||||
|
||||
bool Host::AcquireHostDisplay(RenderAPI api)
|
||||
{
|
||||
const std::optional<WindowInfo> wi(GSRunner::GetPlatformWindowInfo());
|
||||
if (!wi.has_value())
|
||||
return false;
|
||||
|
||||
g_host_display = HostDisplay::CreateForAPI(api);
|
||||
if (!g_host_display)
|
||||
return false;
|
||||
|
||||
if (!g_host_display->CreateDevice(wi.value()) || !g_host_display->MakeCurrent() || !g_host_display->SetupDevice() || !ImGuiManager::Initialize())
|
||||
{
|
||||
ReleaseHostDisplay();
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLn(Color_StrongGreen, "%s Graphics Driver Info:", HostDisplay::RenderAPIToString(g_host_display->GetRenderAPI()));
|
||||
Console.Indent().WriteLn(g_host_display->GetDriverInfo());
|
||||
|
||||
return g_host_display.get();
|
||||
}
|
||||
|
||||
void Host::ReleaseHostDisplay()
|
||||
{
|
||||
if (!g_host_display)
|
||||
return;
|
||||
|
||||
ImGuiManager::Shutdown();
|
||||
g_host_display.reset();
|
||||
}
|
||||
|
||||
VsyncMode Host::GetEffectiveVSyncMode()
|
||||
{
|
||||
// Never vsync! We want to finish as quickly as possible.
|
||||
return VsyncMode::Off;
|
||||
}
|
||||
|
||||
bool Host::BeginPresentFrame(bool frame_skip)
|
||||
{
|
||||
return g_host_display->BeginPresent(frame_skip);
|
||||
}
|
||||
|
||||
void Host::EndPresentFrame()
|
||||
{
|
||||
if (GSDumpReplayer::IsReplayingDump())
|
||||
GSDumpReplayer::RenderUI();
|
||||
|
||||
ImGuiManager::RenderOSD();
|
||||
g_host_display->EndPresent();
|
||||
ImGuiManager::NewFrame();
|
||||
}
|
||||
|
||||
void Host::ResizeHostDisplay(u32 new_window_width, u32 new_window_height, float new_window_scale)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::UpdateHostDisplay()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::RequestResizeHostDisplay(s32 width, s32 height)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnVMStarting()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnVMStarted()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnVMDestroyed()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnVMPaused()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnVMResumed()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name, u32 game_crc)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnPerformanceMetricsUpdated()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnSaveStateLoading(const std::string_view& filename)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnSaveStateLoaded(const std::string_view& filename, bool was_successful)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::OnSaveStateSaved(const std::string_view& filename)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::InvalidateSaveStateCache()
|
||||
{
|
||||
}
|
||||
|
||||
void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false */)
|
||||
{
|
||||
pxFailRel("Not implemented");
|
||||
}
|
||||
|
||||
void Host::RefreshGameListAsync(bool invalidate_cache)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::CancelGameListRefresh()
|
||||
{
|
||||
}
|
||||
|
||||
bool Host::IsFullscreen()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
void Host::SetFullscreen(bool enabled)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::RequestExit(bool save_state_if_running)
|
||||
{
|
||||
}
|
||||
|
||||
void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state)
|
||||
{
|
||||
VMManager::SetState(VMState::Stopping);
|
||||
}
|
||||
|
||||
#ifdef ENABLE_ACHIEVEMENTS
|
||||
void Host::OnAchievementsRefreshed()
|
||||
{
|
||||
// noop
|
||||
}
|
||||
#endif
|
||||
|
||||
std::optional<u32> InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
std::optional<std::string> InputManager::ConvertHostKeyboardCodeToString(u32 code)
|
||||
{
|
||||
return std::nullopt;
|
||||
}
|
||||
|
||||
SysMtgsThread& GetMTGS()
|
||||
{
|
||||
return s_mtgs_thread;
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Interface Stuff
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
const IConsoleWriter* PatchesCon = &Console;
|
||||
BEGIN_HOTKEY_LIST(g_host_hotkeys)
|
||||
END_HOTKEY_LIST()
|
||||
|
||||
static void PrintCommandLineVersion()
|
||||
{
|
||||
std::fprintf(stderr, "PCSX2 GS Runner Version %s\n", GIT_REV);
|
||||
std::fprintf(stderr, "https://pcsx2.net/\n");
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static void PrintCommandLineHelp(const char* progname)
|
||||
{
|
||||
PrintCommandLineVersion();
|
||||
std::fprintf(stderr, "Usage: %s [parameters] [--] [filename]\n", 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, " -dumpdir <dir>: Frame dump directory (will be dumped as filename_frameN.png).\n");
|
||||
std::fprintf(stderr, " -loop <count>: Loops dump playback N times. Defaults to 1. 0 will loop infinitely.\n");
|
||||
std::fprintf(stderr, " -renderer <renderer>: Sets the graphics renderer. Defaults to Auto.\n");
|
||||
std::fprintf(stderr, " --: Signals that no more arguments will follow and the remaining\n"
|
||||
" parameters make up the filename. Use when the filename contains\n"
|
||||
" spaces or starts with a dash.\n");
|
||||
std::fprintf(stderr, "\n");
|
||||
}
|
||||
|
||||
static bool ParseCommandLineArgs(int argc, char* argv[], VMBootParameters& params)
|
||||
{
|
||||
bool no_more_args = false;
|
||||
for (int i = 1; i < argc; i++)
|
||||
{
|
||||
if (!no_more_args)
|
||||
{
|
||||
#define CHECK_ARG(str) !std::strcmp(argv[i], str)
|
||||
#define CHECK_ARG_PARAM(str) (!std::strcmp(argv[i], str) && ((i + 1) < argc))
|
||||
|
||||
if (CHECK_ARG("-help"))
|
||||
{
|
||||
PrintCommandLineHelp(argv[0]);
|
||||
return false;
|
||||
}
|
||||
else if (CHECK_ARG("-version"))
|
||||
{
|
||||
PrintCommandLineVersion();
|
||||
return false;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-dumpdir"))
|
||||
{
|
||||
s_output_prefix = argv[++i];
|
||||
if (s_output_prefix.empty())
|
||||
{
|
||||
Console.Error("Invalid dump directory specified.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!FileSystem::DirectoryExists(s_output_prefix.c_str()) && !FileSystem::CreateDirectoryPath(s_output_prefix.c_str(), false))
|
||||
{
|
||||
Console.Error("Failed to create output directory");
|
||||
return false;
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-loop"))
|
||||
{
|
||||
s_loop_count = StringUtil::FromChars<s32>(argv[++i]).value_or(0);
|
||||
Console.WriteLn("Looping dump playback %d times.", s_loop_count);
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG_PARAM("-renderer"))
|
||||
{
|
||||
const char* rname = argv[++i];
|
||||
|
||||
GSRendererType type = GSRendererType::Auto;
|
||||
if (StringUtil::Strcasecmp(rname, "Auto") == 0)
|
||||
type = GSRendererType::Auto;
|
||||
#ifdef _WIN32
|
||||
else if (StringUtil::Strcasecmp(rname, "dx11") == 0)
|
||||
type = GSRendererType::DX11;
|
||||
else if (StringUtil::Strcasecmp(rname, "dx12") == 0)
|
||||
type = GSRendererType::DX12;
|
||||
#endif
|
||||
#ifdef ENABLE_OPENGL
|
||||
else if (StringUtil::Strcasecmp(rname, "gl") == 0)
|
||||
type = GSRendererType::OGL;
|
||||
#endif
|
||||
#ifdef ENABLE_VULKAN
|
||||
else if (StringUtil::Strcasecmp(rname, "vulkan") == 0)
|
||||
type = GSRendererType::VK;
|
||||
#endif
|
||||
#ifdef __APPLE__
|
||||
else if (StringUtil::Strcasecmp(rname, "metal") == 0)
|
||||
type = GSRendererType::Metal;
|
||||
#endif
|
||||
else if (StringUtil::Strcasecmp(rname, "sw") == 0)
|
||||
type = GSRendererType::SW;
|
||||
else
|
||||
{
|
||||
Console.Error("Unknown renderer '%s'", rname);
|
||||
return false;
|
||||
}
|
||||
|
||||
Console.WriteLn("Using %s renderer.", Pcsx2Config::GSOptions::GetRendererName(type));
|
||||
s_settings_interface.SetIntValue("EmuCore/GS", "Renderer", static_cast<int>(type));
|
||||
continue;
|
||||
}
|
||||
else if (CHECK_ARG("--"))
|
||||
{
|
||||
no_more_args = true;
|
||||
continue;
|
||||
}
|
||||
else if (argv[i][0] == '-')
|
||||
{
|
||||
Console.Error("Unknown parameter: '%s'", argv[i]);
|
||||
return false;
|
||||
}
|
||||
|
||||
#undef CHECK_ARG
|
||||
#undef CHECK_ARG_PARAM
|
||||
}
|
||||
|
||||
if (!params.filename.empty())
|
||||
params.filename += ' ';
|
||||
params.filename += argv[i];
|
||||
}
|
||||
|
||||
if (params.filename.empty())
|
||||
{
|
||||
Console.Error("No dump filename provided.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!VMManager::IsGSDumpFileName(params.filename))
|
||||
{
|
||||
Console.Error("Provided filename is not a GS dump.");
|
||||
return false;
|
||||
}
|
||||
|
||||
// set up the frame dump directory
|
||||
if (!s_output_prefix.empty())
|
||||
{
|
||||
// strip off all extensions
|
||||
std::string_view title(Path::GetFileTitle(params.filename));
|
||||
if (StringUtil::EndsWithNoCase(title, ".gs"))
|
||||
title = Path::GetFileTitle(title);
|
||||
|
||||
s_output_prefix = Path::Combine(s_output_prefix, title);
|
||||
Console.WriteLn(fmt::format("Saving dumps as {}_frameN.png", s_output_prefix));
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
int main(int argc, char* argv[])
|
||||
{
|
||||
CommonHost::InitializeEarlyConsole();
|
||||
|
||||
if (!GSRunner::InitializeConfig())
|
||||
{
|
||||
Console.Error("Failed to initialize config.");
|
||||
return false;
|
||||
}
|
||||
|
||||
VMBootParameters params;
|
||||
if (!ParseCommandLineArgs(argc, argv, params))
|
||||
return false;
|
||||
|
||||
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle::GetForCallingThread());
|
||||
if (!VMManager::Internal::InitializeGlobals() || !VMManager::Internal::InitializeMemory())
|
||||
{
|
||||
Console.Error("Failed to allocate globals/memory.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!GSRunner::CreatePlatformWindow())
|
||||
{
|
||||
Console.Error("Failed to create window.");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (VMManager::Initialize(params))
|
||||
{
|
||||
// run until end
|
||||
GSDumpReplayer::SetLoopCount(s_loop_count);
|
||||
VMManager::SetState(VMState::Running);
|
||||
while (VMManager::GetState() == VMState::Running)
|
||||
VMManager::Execute();
|
||||
VMManager::Shutdown(false);
|
||||
}
|
||||
|
||||
InputManager::CloseSources();
|
||||
VMManager::Internal::ReleaseMemory();
|
||||
PerformanceMetrics::SetCPUThread(Threading::ThreadHandle());
|
||||
GSRunner::DestroyPlatformWindow();
|
||||
|
||||
return EXIT_SUCCESS;
|
||||
}
|
||||
|
||||
void Host::CPUThreadVSync()
|
||||
{
|
||||
if (!s_output_prefix.empty())
|
||||
{
|
||||
// wait for the previous frame to complete (and thus dump)
|
||||
GetMTGS().WaitGS(false, false, false);
|
||||
|
||||
// queue dumping of this frame
|
||||
std::string dump_path(fmt::format("{}_frame{}.png", s_output_prefix, GSDumpReplayer::GetFrameNumber()));
|
||||
GetMTGS().RunOnGSThread([dump_path = std::move(dump_path)]() { GSQueueSnapshot(dump_path); });
|
||||
}
|
||||
|
||||
// process any window messages (but we shouldn't really have any)
|
||||
GSRunner::PumpPlatformMessages();
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
// Platform specific code
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
|
||||
#ifdef _WIN32
|
||||
|
||||
static constexpr LPCWSTR WINDOW_CLASS_NAME = L"PCSX2GSRunner";
|
||||
static HWND s_hwnd = NULL;
|
||||
|
||||
static LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam);
|
||||
|
||||
bool GSRunner::CreatePlatformWindow()
|
||||
{
|
||||
WNDCLASSEXW wc = {};
|
||||
wc.cbSize = sizeof(WNDCLASSEXW);
|
||||
wc.style = 0;
|
||||
wc.lpfnWndProc = WndProc;
|
||||
wc.cbClsExtra = 0;
|
||||
wc.cbWndExtra = 0;
|
||||
wc.hInstance = GetModuleHandle(nullptr);
|
||||
wc.hIcon = NULL;
|
||||
wc.hCursor = LoadCursor(NULL, IDC_ARROW);
|
||||
wc.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
|
||||
wc.lpszMenuName = NULL;
|
||||
wc.lpszClassName = WINDOW_CLASS_NAME;
|
||||
wc.hIconSm = NULL;
|
||||
|
||||
if (!RegisterClassExW(&wc))
|
||||
{
|
||||
Console.Error("Window registration failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
s_hwnd = CreateWindowExW(WS_EX_CLIENTEDGE, WINDOW_CLASS_NAME, L"PCSX2 GS Runner",
|
||||
WS_OVERLAPPEDWINDOW | WS_CAPTION | WS_MINIMIZEBOX | WS_SYSMENU | WS_SIZEBOX, CW_USEDEFAULT, CW_USEDEFAULT, WINDOW_WIDTH,
|
||||
WINDOW_HEIGHT, nullptr, nullptr, GetModuleHandleW(nullptr), nullptr);
|
||||
if (!s_hwnd)
|
||||
{
|
||||
Console.Error("CreateWindowEx failed.");
|
||||
return false;
|
||||
}
|
||||
|
||||
ShowWindow(s_hwnd, SW_SHOW);
|
||||
UpdateWindow(s_hwnd);
|
||||
|
||||
// make sure all messages are processed before returning
|
||||
PumpPlatformMessages();
|
||||
return true;
|
||||
}
|
||||
|
||||
void GSRunner::DestroyPlatformWindow()
|
||||
{
|
||||
if (!s_hwnd)
|
||||
return;
|
||||
|
||||
PumpPlatformMessages();
|
||||
DestroyWindow(s_hwnd);
|
||||
s_hwnd = {};
|
||||
}
|
||||
|
||||
std::optional<WindowInfo> GSRunner::GetPlatformWindowInfo()
|
||||
{
|
||||
RECT rc = {};
|
||||
GetWindowRect(s_hwnd, &rc);
|
||||
|
||||
WindowInfo wi;
|
||||
wi.surface_width = static_cast<u32>(rc.right - rc.left);
|
||||
wi.surface_height = static_cast<u32>(rc.bottom - rc.top);
|
||||
wi.surface_scale = 1.0f;
|
||||
wi.type = WindowInfo::Type::Win32;
|
||||
wi.window_handle = s_hwnd;
|
||||
return wi;
|
||||
}
|
||||
|
||||
void GSRunner::PumpPlatformMessages()
|
||||
{
|
||||
MSG msg;
|
||||
while (PeekMessageW(&msg, NULL, 0, 0, PM_REMOVE))
|
||||
{
|
||||
TranslateMessage(&msg);
|
||||
DispatchMessageW(&msg);
|
||||
}
|
||||
}
|
||||
|
||||
LRESULT CALLBACK WndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
|
||||
{
|
||||
return DefWindowProcW(hwnd, msg, wParam, lParam);
|
||||
}
|
||||
|
||||
#endif // _WIN32
|
|
@ -0,0 +1,123 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<Import Project="$(SolutionDir)common\vsprops\BaseProjectConfig.props" />
|
||||
<Import Project="$(SolutionDir)common\vsprops\ProjectConfigAVX2.props" />
|
||||
<Import Project="$(SolutionDir)common\vsprops\WinSDK.props" />
|
||||
<PropertyGroup Label="Globals">
|
||||
<ProjectGuid>{BB98BF81-A132-444A-BB81-96D510F433A8}</ProjectGuid>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
|
||||
<PropertyGroup Label="Configuration">
|
||||
<ConfigurationType>Application</ConfigurationType>
|
||||
<CharacterSet>Unicode</CharacterSet>
|
||||
<PlatformToolset>$(DefaultPlatformToolset)</PlatformToolset>
|
||||
<WholeProgramOptimization Condition="$(Configuration.Contains(Release))">true</WholeProgramOptimization>
|
||||
<UseDebugLibraries Condition="$(Configuration.Contains(Debug))">true</UseDebugLibraries>
|
||||
<UseDebugLibraries Condition="!$(Configuration.Contains(Debug))">false</UseDebugLibraries>
|
||||
</PropertyGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
|
||||
<ImportGroup Label="ExtensionSettings" />
|
||||
<ImportGroup Label="PropertySheets">
|
||||
<Import Project="$(SolutionDir)common\vsprops\common.props" />
|
||||
<Import Project="$(SolutionDir)common\vsprops\BaseProperties.props" />
|
||||
<Import Project="$(SolutionDir)common\vsprops\3rdpartyDeps.props" />
|
||||
<Import Condition="$(Configuration.Contains(Debug))" Project="$(SolutionDir)common\vsprops\CodeGen_Debug.props" />
|
||||
<Import Condition="$(Configuration.Contains(Devel))" Project="$(SolutionDir)common\vsprops\CodeGen_Devel.props" />
|
||||
<Import Condition="$(Configuration.Contains(Release))" Project="$(SolutionDir)common\vsprops\CodeGen_Release.props" />
|
||||
<Import Condition="!$(Configuration.Contains(Release))" Project="$(SolutionDir)common\vsprops\IncrementalLinking.props" />
|
||||
</ImportGroup>
|
||||
<PropertyGroup Label="UserMacros" />
|
||||
<PropertyGroup>
|
||||
<CodeAnalysisRuleSet>AllRules.ruleset</CodeAnalysisRuleSet>
|
||||
<TargetName>$(EXEString)</TargetName>
|
||||
</PropertyGroup>
|
||||
<ItemDefinitionGroup>
|
||||
<ClCompile>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\xbyak;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\freetype\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\xz\xz\src\liblzma\api;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\baseclasses;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\zlib;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\libpng;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\glad\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\simpleini\include;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(SolutionDir)3rdparty\rapidyaml\rapidyaml\ext\c4core\src\c4\ext\fast_float\include;%(AdditionalIncludeDirectories);</AdditionalIncludeDirectories>
|
||||
<AdditionalIncludeDirectories>$(ProjectDir);$(SolutionDir)pcsx2;%(AdditionalIncludeDirectories)</AdditionalIncludeDirectories>
|
||||
<!-- Needed for moc pch -->
|
||||
<AdditionalIncludeDirectories>%(AdditionalIncludeDirectories);$(ProjectDir)\Settings;$(ProjectDir)\GameList</AdditionalIncludeDirectories>
|
||||
<ExceptionHandling>Async</ExceptionHandling>
|
||||
<PrecompiledHeader>NotUsing</PrecompiledHeader>
|
||||
<EnableEnhancedInstructionSet>NoExtensions</EnableEnhancedInstructionSet>
|
||||
<PreprocessorDefinitions>WIN32_LEAN_AND_MEAN;LZMA_API_STATIC;BUILD_DX=1;ENABLE_RAINTEGRATION;ENABLE_ACHIEVEMENTS;ENABLE_DISCORD_PRESENCE;ENABLE_OPENGL;ENABLE_VULKAN;DIRECTINPUT_VERSION=0x0800;PCSX2_CORE;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="$(Configuration.Contains(Debug))">PCSX2_DEBUG;PCSX2_DEVBUILD;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="$(Configuration.Contains(Devel))">PCSX2_DEVEL;PCSX2_DEVBUILD;NDEBUG;_SECURE_SCL_=1;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="$(Configuration.Contains(Release))">NDEBUG;_SECURE_SCL_=0;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="'$(CI)'=='true'">PCSX2_CI;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="!$(Configuration.Contains(AVX2))">_M_SSE=0x401;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<PreprocessorDefinitions Condition="$(Configuration.Contains(AVX2))">_M_SSE=0x501;%(PreprocessorDefinitions)</PreprocessorDefinitions>
|
||||
<EnableEnhancedInstructionSet Condition="!$(Configuration.Contains(AVX2))">NotSet</EnableEnhancedInstructionSet>
|
||||
<EnableEnhancedInstructionSet Condition="$(Configuration.Contains(AVX2))">AdvancedVectorExtensions2</EnableEnhancedInstructionSet>
|
||||
<MinimalRebuild>false</MinimalRebuild>
|
||||
<ObjectFileName>$(IntDir)%(RelativeDir)</ObjectFileName>
|
||||
<!-- SH 2/3 flashlight, explicitly set here don't change -->
|
||||
<!-- https://github.com/PCSX2/pcsx2/commit/16431653e4d92fda4069031897e24fbe4688d36a -->
|
||||
<FloatingPointModel>Precise</FloatingPointModel>
|
||||
<MultiProcessorCompilation>true</MultiProcessorCompilation>
|
||||
<ConformanceMode>true</ConformanceMode>
|
||||
<AdditionalOptions>/Zc:__cplusplus /Zo /utf-8%(AdditionalOptions)</AdditionalOptions>
|
||||
</ClCompile>
|
||||
<Link>
|
||||
<SubSystem>Console</SubSystem>
|
||||
<LargeAddressAware>Yes</LargeAddressAware>
|
||||
<AdditionalDependencies>comctl32.lib;ws2_32.lib;shlwapi.lib;winmm.lib;rpcrt4.lib;iphlpapi.lib;dsound.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
<AdditionalDependencies>dxguid.lib;dinput8.lib;hid.lib;PowrProf.lib;d3dcompiler.lib;d3d11.lib;dxgi.lib;strmiids.lib;opengl32.lib;comsuppw.lib;OneCore.lib;%(AdditionalDependencies)</AdditionalDependencies>
|
||||
</Link>
|
||||
</ItemDefinitionGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\baseclasses\baseclasses.vcxproj">
|
||||
<Project>{27f17499-a372-4408-8afa-4f9f4584fbd3}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\fmt\fmt.vcxproj">
|
||||
<Project>{449ad25e-424a-4714-babc-68706cdcc33b}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\libjpeg\libjpeg.vcxproj">
|
||||
<Project>{bc236261-77e8-4567-8d09-45cd02965eb6}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\libpng\projects\vstudio\libpng\libpng.vcxproj">
|
||||
<Project>{d6973076-9317-4ef2-a0b8-b7a18ac0713e}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\soundtouch\SoundTouch.vcxproj">
|
||||
<Project>{e9b51944-7e6d-4bcd-83f2-7bbd5a46182d}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\xz\liblzma.vcxproj">
|
||||
<Project>{12728250-16ec-4dc6-94d7-e21dd88947f8}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\libchdr\libchdr.vcxproj">
|
||||
<Project>{a0d2b3ad-1f72-4ee3-8b5c-f2c358da35f0}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\zlib\zlib.vcxproj">
|
||||
<Project>{2f6c0388-20cb-4242-9f6c-a6ebb6a83f47}</Project>
|
||||
<ReferenceOutputAssembly>false</ReferenceOutputAssembly>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\jpgd\jpgd.vcxproj">
|
||||
<Project>{ed2f21fd-0a36-4a8f-9b90-e7d92a2acb63}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\imgui\imgui.vcxproj">
|
||||
<Project>{88fb34ec-845e-4f21-a552-f1573b9ed167}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)common\common.vcxproj">
|
||||
<Project>{4639972e-424e-4e13-8b07-ca403c481346}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)pcsx2\pcsx2core.vcxproj">
|
||||
<Project>{6c7986c4-3e4d-4dcc-b3c6-6bb12b238995}</Project>
|
||||
</ProjectReference>
|
||||
<ProjectReference Include="$(SolutionDir)3rdparty\glad\glad.vcxproj">
|
||||
<Project>{c0293b32-5acf-40f0-aa6c-e6da6f3bf33a}</Project>
|
||||
</ProjectReference>
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Main.cpp" />
|
||||
</ItemGroup>
|
||||
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
|
||||
<ImportGroup Label="ExtensionTargets" />
|
||||
</Project>
|
|
@ -0,0 +1,6 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||
<ItemGroup>
|
||||
<ClCompile Include="Main.cpp" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,127 @@
|
|||
import argparse
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
import re
|
||||
import hashlib
|
||||
|
||||
from pathlib import Path
|
||||
|
||||
|
||||
FILE_HEADER = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<title>Comparison</title>
|
||||
</head>
|
||||
<body>
|
||||
"""
|
||||
|
||||
FILE_FOOTER = """
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
outfile = None
|
||||
def write(line):
|
||||
outfile.write(line + "\n")
|
||||
|
||||
|
||||
def compare_frames(path1, path2):
|
||||
try:
|
||||
with open(path1, "rb") as f:
|
||||
hash1 = hashlib.md5(f.read()).digest()
|
||||
with open(path2, "rb") as f:
|
||||
hash2 = hashlib.md5(f.read()).digest()
|
||||
|
||||
return hash1 == hash2
|
||||
except (FileNotFoundError, IOError):
|
||||
return False
|
||||
|
||||
|
||||
def check_regression_test(baselinedir, testdir, name):
|
||||
#print("Checking '%s'..." % name)
|
||||
|
||||
dir1 = os.path.join(baselinedir, name)
|
||||
dir2 = os.path.join(testdir, name)
|
||||
if not os.path.isdir(dir2):
|
||||
#print("*** %s is missing in test set" % name)
|
||||
return False
|
||||
|
||||
images = glob.glob(os.path.join(dir1, "*_frame*.png"))
|
||||
diff_frames = []
|
||||
first_fail = True
|
||||
|
||||
for imagepath in images:
|
||||
imagename = Path(imagepath).name
|
||||
matches = re.match(".*_frame([0-9]+).png", imagename)
|
||||
if matches is None:
|
||||
continue
|
||||
|
||||
framenum = int(matches[1])
|
||||
|
||||
path1 = os.path.join(dir1, imagename)
|
||||
path2 = os.path.join(dir2, imagename)
|
||||
if not os.path.isfile(path2):
|
||||
print("--- Frame %u for %s is missing in test set" % (framenum, name))
|
||||
write("<h1>{}</h1>".format(name))
|
||||
write("--- Frame %u for %s is missing in test set" % (framenum, name))
|
||||
return False
|
||||
|
||||
if not compare_frames(path1, path2):
|
||||
diff_frames.append(framenum)
|
||||
|
||||
if first_fail:
|
||||
write("<h1>{}</h1>".format(name))
|
||||
write("<table width=\"100%\">")
|
||||
first_fail = False
|
||||
|
||||
imguri1 = Path(path1).as_uri()
|
||||
imguri2 = Path(path2).as_uri()
|
||||
write("<tr><td colspan=\"2\">Frame %d</td></tr>" % (framenum))
|
||||
write("<tr><td><img src=\"%s\" /></td><td><img src=\"%s\" /></td></tr>" % (imguri1, imguri2))
|
||||
|
||||
if len(diff_frames) > 0:
|
||||
write("</table>")
|
||||
write("<pre>Difference in frames [%s] for %s</pre>" % (",".join(map(str, diff_frames)), name))
|
||||
print("*** Difference in frames [%s] for %s" % (",".join(map(str, diff_frames)), name))
|
||||
return False
|
||||
|
||||
return True
|
||||
|
||||
|
||||
def check_regression_tests(baselinedir, testdir):
|
||||
gamedirs = glob.glob(baselinedir + "/*", recursive=False)
|
||||
|
||||
success = 0
|
||||
failure = 0
|
||||
|
||||
for gamedir in gamedirs:
|
||||
name = Path(gamedir).name
|
||||
if check_regression_test(baselinedir, testdir, name):
|
||||
success += 1
|
||||
else:
|
||||
failure += 1
|
||||
|
||||
return (failure == 0)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Check frame dump images for regression tests")
|
||||
parser.add_argument("-baselinedir", action="store", required=True, help="Directory containing baseline frames to check against")
|
||||
parser.add_argument("-testdir", action="store", required=True, help="Directory containing frames to check")
|
||||
parser.add_argument("outfile", action="store", help="The file to write the output to")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
outfile = open(args.outfile, "w")
|
||||
write(FILE_HEADER)
|
||||
|
||||
if not check_regression_tests(os.path.realpath(args.baselinedir), os.path.realpath(args.testdir)):
|
||||
write(FILE_FOOTER)
|
||||
outfile.close()
|
||||
sys.exit(1)
|
||||
else:
|
||||
outfile.close()
|
||||
os.remove(args.outfile)
|
||||
sys.exit(0)
|
|
@ -0,0 +1,81 @@
|
|||
import argparse
|
||||
import glob
|
||||
import sys
|
||||
import os
|
||||
import subprocess
|
||||
import multiprocessing
|
||||
from pathlib import Path
|
||||
from functools import partial
|
||||
|
||||
def is_gs_path(path):
|
||||
ppath = Path(path)
|
||||
for extension in [[".gs"], [".gs", ".xz"], [".gs", ".zst"]]:
|
||||
if ppath.suffixes == extension:
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
|
||||
def run_regression_test(runner, dumpdir, renderer, gspath):
|
||||
args = [runner]
|
||||
gsname = Path(gspath).name
|
||||
while gsname.rfind('.') >= 0:
|
||||
gsname = gsname[:gsname.rfind('.')]
|
||||
|
||||
real_dumpdir = os.path.join(dumpdir, gsname)
|
||||
if not os.path.exists(real_dumpdir):
|
||||
os.mkdir(real_dumpdir)
|
||||
|
||||
if renderer is not None:
|
||||
args.extend(["-renderer", renderer])
|
||||
args.extend(["-dumpdir", real_dumpdir])
|
||||
|
||||
# loop a couple of times for those stubborn merge/interlace dumps that don't render anything
|
||||
# the first time around
|
||||
args.extend(["-loop", "2"])
|
||||
|
||||
args.append("--")
|
||||
args.append(gspath)
|
||||
|
||||
print("Running '%s'" % (" ".join(args)))
|
||||
subprocess.run(args)
|
||||
|
||||
|
||||
def run_regression_tests(runner, gsdir, dumpdir, renderer, parallel=1):
|
||||
paths = glob.glob(gsdir + "/*.*", recursive=True)
|
||||
gamepaths = list(filter(is_gs_path, paths))
|
||||
|
||||
if not os.path.isdir(dumpdir):
|
||||
os.mkdir(dumpdir)
|
||||
|
||||
print("Found %u GS dumps" % len(gamepaths))
|
||||
|
||||
if parallel <= 1:
|
||||
for game in gamepaths:
|
||||
run_regression_test(runner, dumpdir, renderer, game)
|
||||
else:
|
||||
print("Processing %u games on %u processors" % (len(gamepaths), parallel))
|
||||
func = partial(run_regression_test, runner, dumpdir, renderer)
|
||||
pool = multiprocessing.Pool(parallel)
|
||||
pool.map(func, gamepaths)
|
||||
pool.close()
|
||||
|
||||
|
||||
return True
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
parser = argparse.ArgumentParser(description="Generate frame dump images for regression tests")
|
||||
parser.add_argument("-runner", action="store", required=True, help="Path to PCSX2 GS runner")
|
||||
parser.add_argument("-gsdir", action="store", required=True, help="Directory containing GS dumps")
|
||||
parser.add_argument("-dumpdir", action="store", required=True, help="Base directory to dump frames to")
|
||||
parser.add_argument("-renderer", action="store", required=False, help="Renderer to use")
|
||||
parser.add_argument("-parallel", action="store", type=int, default=1, help="Number of proceeses to run")
|
||||
|
||||
args = parser.parse_args()
|
||||
|
||||
if not run_regression_tests(args.runner, os.path.realpath(args.gsdir), os.path.realpath(args.dumpdir), args.renderer, args.parallel):
|
||||
sys.exit(1)
|
||||
else:
|
||||
sys.exit(0)
|
||||
|
Loading…
Reference in New Issue