From 5498ddfab7c9cd4571eac0d692ea43481d67e745 Mon Sep 17 00:00:00 2001 From: Connor McLaughlin Date: Tue, 11 Oct 2022 19:31:19 +1000 Subject: [PATCH] RegTest: Port to new host abstractions --- scripts/check_regression_tests.py | 39 +- src/core/cdrom.cpp | 3 +- src/duckstation-regtest/CMakeLists.txt | 3 +- .../duckstation-regtest.vcxproj | 3 +- .../duckstation-regtest.vcxproj.filters | 3 +- src/duckstation-regtest/regtest_host.cpp | 671 ++++++++++++++++++ .../regtest_host_display.cpp | 148 ++-- .../regtest_host_display.h | 40 +- .../regtest_host_interface.cpp | 530 -------------- .../regtest_host_interface.h | 65 -- 10 files changed, 834 insertions(+), 671 deletions(-) create mode 100644 src/duckstation-regtest/regtest_host.cpp delete mode 100644 src/duckstation-regtest/regtest_host_interface.cpp delete mode 100644 src/duckstation-regtest/regtest_host_interface.h diff --git a/scripts/check_regression_tests.py b/scripts/check_regression_tests.py index c33f07f4f..cd01b575c 100644 --- a/scripts/check_regression_tests.py +++ b/scripts/check_regression_tests.py @@ -7,6 +7,25 @@ import hashlib from pathlib import Path +FILE_HEADER = """ + + + +Comparison + + +""" + +FILE_FOOTER = """ + + +""" + +outfile = None +def write(line): + outfile.write(line + "\n") + + def compare_frames(path1, path2): try: with open(path1, "rb") as f: @@ -37,15 +56,25 @@ def check_regression_test(baselinedir, testdir, name): 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("

{}

".format(name)) + write("--- Frame %u for %s is missing in test set" % (framenum, name)) return False if not compare_frames(path1, path2): print("*** Difference in frame %u for %s" % (framenum, name)) + + imguri1 = Path(path1).as_uri() + imguri2 = Path(path2).as_uri() + write("

{}

".format(name)) + write("") + write("".format(framenum)) + write("".format(imguri1, imguri2)) + write("
Frame {}
") return False return True @@ -71,11 +100,19 @@ 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) diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp index 30bedd329..6e8e7a068 100644 --- a/src/core/cdrom.cpp +++ b/src/core/cdrom.cpp @@ -1352,7 +1352,6 @@ void CDROM::ExecuteCommand(TickCount ticks_late) case Command::Reset: { Log_DebugPrintf("CDROM reset command"); - SendACKAndStat(); if (m_command_second_response == Command::Reset) { @@ -1361,6 +1360,8 @@ void CDROM::ExecuteCommand(TickCount ticks_late) return; } + SendACKAndStat(); + if (IsSeeking()) UpdatePositionWhileSeeking(); diff --git a/src/duckstation-regtest/CMakeLists.txt b/src/duckstation-regtest/CMakeLists.txt index 0cbe8236a..6ef355335 100644 --- a/src/duckstation-regtest/CMakeLists.txt +++ b/src/duckstation-regtest/CMakeLists.txt @@ -1,8 +1,7 @@ add_executable(duckstation-regtest regtest_host_display.cpp regtest_host_display.h - regtest_host_interface.cpp - regtest_host_interface.h + regtest_host.cpp ) target_link_libraries(duckstation-regtest PRIVATE core common frontend-common scmversion) diff --git a/src/duckstation-regtest/duckstation-regtest.vcxproj b/src/duckstation-regtest/duckstation-regtest.vcxproj index 4752d5266..f4b6d0843 100644 --- a/src/duckstation-regtest/duckstation-regtest.vcxproj +++ b/src/duckstation-regtest/duckstation-regtest.vcxproj @@ -6,11 +6,10 @@ - + - diff --git a/src/duckstation-regtest/duckstation-regtest.vcxproj.filters b/src/duckstation-regtest/duckstation-regtest.vcxproj.filters index 667d931ba..83f206b77 100644 --- a/src/duckstation-regtest/duckstation-regtest.vcxproj.filters +++ b/src/duckstation-regtest/duckstation-regtest.vcxproj.filters @@ -1,11 +1,10 @@  - + - \ No newline at end of file diff --git a/src/duckstation-regtest/regtest_host.cpp b/src/duckstation-regtest/regtest_host.cpp new file mode 100644 index 000000000..bea83f91a --- /dev/null +++ b/src/duckstation-regtest/regtest_host.cpp @@ -0,0 +1,671 @@ +#include "common/assert.h" +#include "common/crash_handler.h" +#include "common/file_system.h" +#include "common/log.h" +#include "common/memory_settings_interface.h" +#include "common/path.h" +#include "common/string_util.h" +#include "core/host.h" +#include "core/host_display.h" +#include "core/host_settings.h" +#include "core/system.h" +#include "frontend-common/common_host.h" +#include "frontend-common/game_list.h" +#include "frontend-common/input_manager.h" +#include "regtest_host_display.h" +#include "scmversion/scmversion.h" +#include +#include +Log_SetChannel(RegTestHost); + +#ifdef WITH_CHEEVOS +#include "frontend-common/achievements.h" +#endif + +namespace RegTestHost { +static bool ParseCommandLineParameters(int argc, char* argv[], std::optional& autoboot); +static void PrintCommandLineVersion(); +static void PrintCommandLineHelp(const char* progname); +static bool InitializeConfig(); +static void InitializeEarlyConsole(); +static void HookSignals(); +static void SetAppRoot(); +static bool SetFolders(); +static std::string GetFrameDumpFilename(u32 frame); +} // namespace RegTestHost + +static std::unique_ptr s_base_settings_interface; + +static u32 s_frames_to_run = 60 * 60; +static u32 s_frame_dump_interval = 0; +static std::string s_dump_base_directory; +static std::string s_dump_game_directory; +static GPURenderer s_renderer_to_use = GPURenderer::Software; + +bool RegTestHost::SetFolders() +{ + std::string program_path(FileSystem::GetProgramPath()); + Log_InfoPrintf("Program Path: %s", program_path.c_str()); + + EmuFolders::AppRoot = Path::Canonicalize(Path::GetDirectory(program_path)); + EmuFolders::DataRoot = EmuFolders::AppRoot; + +#ifndef __APPLE__ + // On Windows/Linux, these are in the binary directory. + EmuFolders::Resources = Path::Combine(EmuFolders::AppRoot, "resources"); +#else + // On macOS, this is in the bundle resources directory. + EmuFolders::Resources = Path::Canonicalize(Path::Combine(EmuFolders::AppRoot, "../Resources")); +#endif + + Log_DevPrintf("AppRoot Directory: %s", EmuFolders::AppRoot.c_str()); + Log_DevPrintf("DataRoot Directory: %s", EmuFolders::DataRoot.c_str()); + Log_DevPrintf("Resources Directory: %s", EmuFolders::Resources.c_str()); + + // Write crash dumps to the data directory, since that'll be accessible for certain. + CrashHandler::SetWriteDirectory(EmuFolders::DataRoot); + + // the resources directory should exist, bail out if not + if (!FileSystem::DirectoryExists(EmuFolders::Resources.c_str())) + { + Log_ErrorPrintf("Error", "Resources directory is missing, your installation is incomplete."); + return false; + } + + return true; +} + +bool RegTestHost::InitializeConfig() +{ + SetFolders(); + + s_base_settings_interface = std::make_unique(); + Host::Internal::SetBaseSettingsLayer(s_base_settings_interface.get()); + + // default settings for runner + SettingsInterface& si = *s_base_settings_interface.get(); + g_settings.Save(si); + si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(GPURenderer::Software)); + si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(ControllerType::DigitalController)); + si.SetStringValue("Controller2", "Type", Settings::GetControllerTypeName(ControllerType::None)); + si.SetStringValue("MemoryCards", "Card1Type", Settings::GetMemoryCardTypeName(MemoryCardType::NonPersistent)); + si.SetStringValue("MemoryCards", "Card2Type", Settings::GetMemoryCardTypeName(MemoryCardType::None)); + si.SetStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(MultitapMode::Disabled)); + si.SetStringValue("Audio", "Backend", Settings::GetAudioBackendName(AudioBackend::Null)); + si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(LOGLEVEL_VERBOSE)); + si.SetBoolValue("Logging", "LogToConsole", true); + + // disable all sources + for (u32 i = 0; i < static_cast(InputSourceType::Count); i++) + si.SetBoolValue("InputSources", InputManager::InputSourceToString(static_cast(i)), false); + + EmuFolders::LoadConfig(*s_base_settings_interface.get()); + EmuFolders::EnsureFoldersExist(); + + return true; +} + +void Host::ReportErrorAsync(const std::string_view& title, const std::string_view& message) +{ + if (!title.empty() && !message.empty()) + { + Log_ErrorPrintf("ReportErrorAsync: %.*s: %.*s", static_cast(title.size()), title.data(), + static_cast(message.size()), message.data()); + } + else if (!message.empty()) + { + Log_ErrorPrintf("ReportErrorAsync: %.*s", static_cast(message.size()), message.data()); + } +} + +bool Host::ConfirmMessage(const std::string_view& title, const std::string_view& message) +{ + if (!title.empty() && !message.empty()) + { + Log_ErrorPrintf("ConfirmMessage: %.*s: %.*s", static_cast(title.size()), title.data(), + static_cast(message.size()), message.data()); + } + else if (!message.empty()) + { + Log_ErrorPrintf("ConfirmMessage: %.*s", static_cast(message.size()), message.data()); + } + + return true; +} + +void Host::ReportDebuggerMessage(const std::string_view& message) +{ + Log_ErrorPrintf("ReportDebuggerMessage: %.*s", static_cast(message.size()), message.data()); +} + +TinyString Host::TranslateString(const char* context, const char* str, const char* disambiguation, int n) +{ + return str; +} + +std::string Host::TranslateStdString(const char* context, const char* str, const char* disambiguation, int n) +{ + return str; +} + +void Host::LoadSettings(SettingsInterface& si, std::unique_lock& lock) +{ + CommonHost::LoadSettings(si, lock); +} + +void Host::CheckForSettingsChanges(const Settings& old_settings) +{ + CommonHost::CheckForSettingsChanges(old_settings); +} + +void Host::CommitBaseSettingChanges() +{ + // noop, in memory +} + +std::optional> Host::ReadResourceFile(const char* filename) +{ + const std::string path(Path::Combine(EmuFolders::Resources, filename)); + std::optional> ret(FileSystem::ReadBinaryFile(path.c_str())); + if (!ret.has_value()) + Log_ErrorPrintf("Failed to read resource file '%s'", filename); + return ret; +} + +std::optional Host::ReadResourceFileToString(const char* filename) +{ + const std::string path(Path::Combine(EmuFolders::Resources, filename)); + std::optional ret(FileSystem::ReadFileToString(path.c_str())); + if (!ret.has_value()) + Log_ErrorPrintf("Failed to read resource file to string '%s'", filename); + return ret; +} + +std::optional Host::GetResourceFileTimestamp(const char* filename) +{ + const std::string path(Path::Combine(EmuFolders::Resources, filename)); + FILESYSTEM_STAT_DATA sd; + if (!FileSystem::StatFile(path.c_str(), &sd)) + { + Log_ErrorPrintf("Failed to stat resource file '%s'", filename); + return std::nullopt; + } + + return sd.ModificationTime; +} + +void Host::OnSystemStarting() +{ + // +} + +void Host::OnSystemStarted() +{ + // +} + +void Host::OnSystemDestroyed() +{ + // +} + +void Host::OnSystemPaused() +{ + // +} + +void Host::OnSystemResumed() +{ + // +} + +void Host::OnPerformanceCountersUpdated() +{ + // +} + +void Host::OnGameChanged(const std::string& disc_path, const std::string& game_serial, const std::string& game_name) +{ + Log_InfoPrintf("Disc Path: %s", disc_path.c_str()); + Log_InfoPrintf("Game Serial: %s", game_serial.c_str()); + Log_InfoPrintf("Game Name: %s", game_name.c_str()); + + if (!s_dump_base_directory.empty()) + { + s_dump_game_directory = Path::Combine(s_dump_base_directory, game_name); + if (!FileSystem::DirectoryExists(s_dump_game_directory.c_str())) + { + Log_InfoPrintf("Creating directory '%s'...", s_dump_game_directory.c_str()); + if (!FileSystem::CreateDirectory(s_dump_game_directory.c_str(), false)) + Panic("Failed to create dump directory."); + } + + Log_InfoPrintf("Dumping frames to '%s'...", s_dump_game_directory.c_str()); + } +} + +void Host::PumpMessagesOnCPUThread() +{ + // +} + +void Host::RunOnCPUThread(std::function function, bool block /* = false */) +{ + // only one thread in this version... + function(); +} + +void Host::RequestResizeHostDisplay(s32 width, s32 height) +{ + // +} + +void Host::RequestExit(bool save_state_if_running) +{ + // +} + +void Host::RequestSystemShutdown(bool allow_confirm, bool save_state) +{ + // +} + +bool Host::IsFullscreen() +{ + return false; +} + +void Host::SetFullscreen(bool enabled) +{ + // +} + +bool Host::AcquireHostDisplay(RenderAPI api) +{ + g_host_display = std::make_unique(); + return true; +} + +void Host::ReleaseHostDisplay() +{ + g_host_display.reset(); +} + +void Host::RenderDisplay(bool skip_present) +{ + const u32 frame = System::GetFrameNumber(); + if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame % s_frame_dump_interval) == 0)) + { + std::string dump_filename(RegTestHost::GetFrameDumpFilename(frame)); + g_host_display->WriteDisplayTextureToFile(std::move(dump_filename)); + } +} + +void Host::InvalidateDisplay() +{ + // +} + +void Host::OpenURL(const std::string_view& url) +{ + // +} + +bool Host::CopyTextToClipboard(const std::string_view& text) +{ + return false; +} + +void Host::SetMouseMode(bool relative, bool hide_cursor) +{ + // +} + +#ifdef WITH_CHEEVOS + +void Host::OnAchievementsRefreshed() +{ + // noop +} + +#endif + +std::optional InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str) +{ + return std::nullopt; +} + +std::optional InputManager::ConvertHostKeyboardCodeToString(u32 code) +{ + return std::nullopt; +} + +void Host::OnInputDeviceConnected(const std::string_view& identifier, const std::string_view& device_name) +{ + // noop +} + +void Host::OnInputDeviceDisconnected(const std::string_view& identifier) +{ + // noop +} + +void* Host::GetTopLevelWindowHandle() +{ + return nullptr; +} + +void Host::RefreshGameListAsync(bool invalidate_cache) +{ + // noop +} + +void Host::CancelGameListRefresh() +{ + // noop +} + +BEGIN_HOTKEY_LIST(g_host_hotkeys) +END_HOTKEY_LIST() + +#if 0 + +void RegTestHostInterface::InitializeSettings() +{ + SettingsInterface& si = m_settings_interface; + HostInterface::SetDefaultSettings(si); + + // Set the settings we need for testing. + si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(s_renderer_to_use)); + si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(ControllerType::DigitalController)); + si.SetStringValue("Controller2", "Type", Settings::GetControllerTypeName(ControllerType::None)); + si.SetStringValue("MemoryCards", "Card1Type", Settings::GetMemoryCardTypeName(MemoryCardType::NonPersistent)); + si.SetStringValue("MemoryCards", "Card2Type", Settings::GetMemoryCardTypeName(MemoryCardType::None)); + si.SetStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(MultitapMode::Disabled)); + si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(LOGLEVEL_DEV)); + si.SetBoolValue("Logging", "LogToConsole", true); + + HostInterface::LoadSettings(si); +} + +bool RegTestHostInterface::AcquireHostDisplay() +{ + switch (g_settings.gpu_renderer) + { +#ifdef _WIN32 + case GPURenderer::HardwareD3D11: + m_display = std::make_unique(); + break; + + case GPURenderer::HardwareD3D12: + m_display = std::make_unique(); + break; +#endif + + case GPURenderer::HardwareOpenGL: + m_display = std::make_unique(); + break; + + case GPURenderer::HardwareVulkan: + m_display = std::make_unique(); + break; + + case GPURenderer::Software: + default: + m_display = std::make_unique(); + break; + } + + WindowInfo wi; + wi.type = WindowInfo::Type::Surfaceless; + wi.surface_width = 640; + wi.surface_height = 480; + if (!m_display->CreateRenderDevice(wi, std::string_view(), false, false)) + { + Log_ErrorPrintf("Failed to create render device"); + m_display.reset(); + return false; + } + + if (!m_display->InitializeRenderDevice(std::string_view(), false, false)) + { + Log_ErrorPrintf("Failed to initialize render device"); + m_display->DestroyRenderDevice(); + m_display.reset(); + return false; + } + + return true; +} + +void RegTestHostInterface::ReleaseHostDisplay() +{ + if (!m_display) + return; + + m_display->DestroyRenderDevice(); + m_display.reset(); +} +#endif + +static void SignalHandler(int signal) +{ + std::signal(signal, SIG_DFL); + + // MacOS is missing std::quick_exit() despite it being C++11... +#ifndef __APPLE__ + std::quick_exit(1); +#else + _Exit(1); +#endif +} + +void RegTestHost::HookSignals() +{ + std::signal(SIGINT, SignalHandler); + std::signal(SIGTERM, SignalHandler); +} + +void RegTestHost::InitializeEarlyConsole() +{ + const bool was_console_enabled = Log::IsConsoleOutputEnabled(); + if (!was_console_enabled) + Log::SetConsoleOutputParams(true); +} + +void RegTestHost::PrintCommandLineVersion() +{ + InitializeEarlyConsole(); + std::fprintf(stderr, "DuckStation Regression Test Runner Version %s (%s)\n", g_scm_tag_str, g_scm_branch_str); + std::fprintf(stderr, "https://github.com/stenzek/duckstation\n"); + std::fprintf(stderr, "\n"); +} + +void RegTestHost::PrintCommandLineHelp(const char* progname) +{ + InitializeEarlyConsole(); + PrintCommandLineVersion(); + std::fprintf(stderr, "Usage: %s [parameters] [--] [boot 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: Set frame dump base directory (will be dumped to basedir/gametitle).\n"); + std::fprintf(stderr, " -dumpinterval: Dumps every N frames.\n"); + std::fprintf(stderr, " -frames: Sets the number of frames to execute.\n"); + std::fprintf(stderr, " -log : Sets the log level. Defaults to verbose.\n"); + std::fprintf(stderr, " -renderer : Sets the graphics renderer. Default to software.\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 std::optional& AutoBoot(std::optional& autoboot) +{ + if (!autoboot) + autoboot.emplace(); + + return autoboot; +} + +bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::optional& autoboot) +{ + 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_dump_base_directory = argv[++i]; + if (s_dump_base_directory.empty()) + { + Log_ErrorPrintf("Invalid dump directory specified."); + return false; + } + + continue; + } + else if (CHECK_ARG_PARAM("-dumpinterval")) + { + s_frame_dump_interval = StringUtil::FromChars(argv[++i]).value_or(0); + if (s_frames_to_run <= 0) + { + Log_ErrorPrintf("Invalid dump interval specified: %s", argv[i]); + return false; + } + + continue; + } + else if (CHECK_ARG_PARAM("-frames")) + { + s_frames_to_run = StringUtil::FromChars(argv[++i]).value_or(0); + if (s_frames_to_run == 0) + { + Log_ErrorPrintf("Invalid frame count specified: %s", argv[i]); + return false; + } + + continue; + } + else if (CHECK_ARG_PARAM("-log")) + { + std::optional level = Settings::ParseLogLevelName(argv[++i]); + if (!level.has_value()) + { + Log_ErrorPrintf("Invalid log level specified."); + return false; + } + + Log::SetConsoleOutputParams(true, nullptr, level.value()); + continue; + } + else if (CHECK_ARG_PARAM("-renderer")) + { + std::optional renderer = Settings::ParseRendererName(argv[++i]); + if (!renderer.has_value()) + { + Log_ErrorPrintf("Invalid renderer specified."); + return false; + } + + s_base_settings_interface->SetStringValue("GPU", "Renderer", Settings::GetRendererName(renderer.value())); + continue; + } + else if (CHECK_ARG("--")) + { + no_more_args = true; + continue; + } + else if (argv[i][0] == '-') + { + Log_ErrorPrintf("Unknown parameter: '%s'", argv[i]); + return false; + } + +#undef CHECK_ARG +#undef CHECK_ARG_PARAM + } + + if (autoboot && !autoboot->filename.empty()) + autoboot->filename += ' '; + AutoBoot(autoboot)->filename += argv[i]; + } + + return true; +} + +std::string RegTestHost::GetFrameDumpFilename(u32 frame) +{ + return Path::Combine(s_dump_game_directory, fmt::format("frame_{:05d}.png", frame)); +} + +int main(int argc, char* argv[]) +{ + RegTestHost::InitializeEarlyConsole(); + + if (!RegTestHost::InitializeConfig()) + return EXIT_FAILURE; + + std::optional autoboot; + if (!RegTestHost::ParseCommandLineParameters(argc, argv, autoboot)) + return EXIT_FAILURE; + + if (!autoboot || autoboot->filename.empty()) + { + Log_ErrorPrintf("No boot path specified."); + return EXIT_FAILURE; + } + + RegTestHost::HookSignals(); + + int result = -1; + Log_InfoPrintf("Trying to boot '%s'...", autoboot->filename.c_str()); + if (!System::BootSystem(std::move(autoboot.value()))) + { + Log_ErrorPrintf("Failed to boot system."); + goto cleanup; + } + + if (s_frame_dump_interval > 0) + { + if (s_dump_base_directory.empty()) + { + Log_ErrorPrint("Dump directory not specified."); + goto cleanup; + } + + Log_InfoPrintf("Dumping every %dth frame to '%s'.", s_frame_dump_interval, s_dump_base_directory.c_str()); + } + + Log_InfoPrintf("Running for %d frames...", s_frames_to_run); + + for (u32 frame = 0; frame < s_frames_to_run; frame++) + { + System::RunFrame(); + Host::RenderDisplay(false); + System::UpdatePerformanceCounters(); + } + + Log_InfoPrintf("All done, shutting down system."); + System::ShutdownSystem(false); + + Log_InfoPrintf("Exiting with success."); + result = 0; + +cleanup: + return result; +} diff --git a/src/duckstation-regtest/regtest_host_display.cpp b/src/duckstation-regtest/regtest_host_display.cpp index 141d443ad..cb8bd9ca0 100644 --- a/src/duckstation-regtest/regtest_host_display.cpp +++ b/src/duckstation-regtest/regtest_host_display.cpp @@ -13,7 +13,7 @@ RegTestHostDisplay::RegTestHostDisplay() = default; RegTestHostDisplay::~RegTestHostDisplay() = default; -HostDisplay::RenderAPI RegTestHostDisplay::GetRenderAPI() const +RenderAPI RegTestHostDisplay::GetRenderAPI() const { return RenderAPI::None; } @@ -61,11 +61,6 @@ bool RegTestHostDisplay::DoneRenderContextCurrent() return true; } -void RegTestHostDisplay::DestroyRenderDevice() -{ - ClearSoftwareCursor(); -} - void RegTestHostDisplay::DestroyRenderSurface() {} bool RegTestHostDisplay::CreateResources() @@ -129,26 +124,40 @@ bool RegTestHostDisplay::SetPostProcessingChain(const std::string_view& config) } std::unique_ptr RegTestHostDisplay::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, - u32 samples, GPUTexture::Format format, - const void* data, u32 data_stride, - bool dynamic /* = false */) + u32 samples, GPUTexture::Format format, const void* data, + u32 data_stride, bool dynamic /* = false */) { - return nullptr; + std::unique_ptr tex = std::make_unique(); + if (!tex->Create(width, height, layers, levels, samples, format)) + return {}; + + if (data && !tex->Upload(0, 0, width, height, data, data_stride)) + return {}; + + return tex; } -void RegTestHostDisplay::UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, - const void* data, u32 data_stride) +bool RegTestHostDisplay::BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, + u32* out_pitch) { + return static_cast(texture)->BeginUpload(width, height, out_buffer, out_pitch); } -bool RegTestHostDisplay::DownloadTexture(const void* texture_handle, GPUTexture::Format texture_format, u32 x, - u32 y, u32 width, u32 height, void* out_data, u32 out_data_stride) +void RegTestHostDisplay::EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) { - const u32 pixel_size = GPUTexture::GetPixelSize(texture_format); - const u32 input_stride = Common::AlignUpPow2(width * pixel_size, 4); - const u8* input_start = static_cast(texture_handle) + (x * pixel_size); - StringUtil::StrideMemCpy(out_data, out_data_stride, input_start, input_stride, width * pixel_size, height); - return true; + static_cast(texture)->EndUpload(x, y, width, height); +} + +bool RegTestHostDisplay::UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, + u32 data_stride) +{ + return static_cast(texture)->Upload(x, y, width, height, data, data_stride); +} + +bool RegTestHostDisplay::DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data, + u32 out_data_stride) +{ + return static_cast(texture)->Download(x, y, width, height, out_data, out_data_stride); } bool RegTestHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const @@ -156,65 +165,90 @@ bool RegTestHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const return (format == GPUTexture::Format::RGBA8); } -bool RegTestHostDisplay::BeginSetDisplayPixels(GPUTexture::Format format, u32 width, u32 height, void** out_buffer, - u32* out_pitch) +void RegTestHostDisplay::SetVSync(bool enabled) { - const u32 pitch = Common::AlignUpPow2(width * GPUTexture::GetPixelSize(format), 4); - const u32 required_size = height * pitch; - if (m_frame_buffer.size() != (required_size / 4)) - { - m_frame_buffer.clear(); - m_frame_buffer.resize(required_size / 4); - } + Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled)); +} - // border is already filled here - m_frame_buffer_pitch = pitch; - SetDisplayTexture(m_frame_buffer.data(), format, width, height, 0, 0, width, height); - *out_buffer = reinterpret_cast(m_frame_buffer.data()); - *out_pitch = pitch; +bool RegTestHostDisplay::Render(bool skip_present) +{ return true; } -void RegTestHostDisplay::EndSetDisplayPixels() +bool RegTestHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, + GPUTexture::Format* out_format) { - // noop + return false; +} + +RegTestTexture::RegTestTexture() = default; + +RegTestTexture::~RegTestTexture() = default; + +bool RegTestTexture::IsValid() const +{ + return !m_frame_buffer.empty(); } -void RegTestHostDisplay::DumpFrame(const std::string& filename) +bool RegTestTexture::Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Format format) { - if (!HasDisplayTexture()) - { - if (FileSystem::FileExists(filename.c_str())) - FileSystem::DeleteFile(filename.c_str()); + if (width == 0 || height == 0 || layers != 1 || levels != 1 || samples != 1 || format == GPUTexture::Format::Unknown) + return false; - return; - } + m_width = static_cast(width); + m_height = static_cast(height); + m_layers = static_cast(layers); + m_levels = static_cast(levels); + m_samples = static_cast(samples); + m_format = format; - Common::RGBA8Image image(m_display_texture_width, m_display_texture_height, - static_cast(m_display_texture)); + m_frame_buffer_pitch = width * GPUTexture::GetPixelSize(format); + m_frame_buffer.resize(m_frame_buffer_pitch * height); + return true; +} - // set alpha channel on all pixels - u32* pixels = image.GetPixels(); - u32* pixels_end = pixels + (image.GetWidth() * image.GetHeight()); - while (pixels != pixels_end) - *(pixels++) |= 0xFF000000u; +bool RegTestTexture::Upload(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) +{ + if ((static_cast(x) + width) > m_width || (static_cast(y) + height) > m_height) + return false; - if (!Common::WriteImageToFile(image, filename.c_str())) - Log_ErrorPrintf("Failed to dump frame '%s'", filename.c_str()); + const u32 ps = GetPixelSize(m_format); + const u32 copy_size = width * ps; + StringUtil::StrideMemCpy(&m_frame_buffer[y * m_frame_buffer_pitch + x * ps], m_frame_buffer_pitch, data, data_stride, + copy_size, height); + return true; } -void RegTestHostDisplay::SetVSync(bool enabled) +bool RegTestTexture::Download(u32 x, u32 y, u32 width, u32 height, void* data, u32 data_stride) const { - Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled)); + if ((static_cast(x) + width) > m_width || (static_cast(y) + height) > m_height) + return false; + + const u32 ps = GetPixelSize(m_format); + const u32 copy_size = width * ps; + StringUtil::StrideMemCpy(data, data_stride, &m_frame_buffer[y * m_frame_buffer_pitch + x * ps], m_frame_buffer_pitch, + copy_size, height); + return true; } -bool RegTestHostDisplay::Render() +bool RegTestTexture::BeginUpload(u32 width, u32 height, void** out_buffer, u32* out_pitch) { + if (width > m_width || height > m_height) + return false; + + const u32 pitch = GetPixelSize(m_format) * width; + m_staging_buffer.resize(pitch * height); + *out_buffer = m_staging_buffer.data(); + *out_pitch = pitch; return true; } -bool RegTestHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, - GPUTexture::Format* out_format) +void RegTestTexture::EndUpload(u32 x, u32 y, u32 width, u32 height) { - return false; + Assert((static_cast(x) + width) <= m_width && (static_cast(y) + height) <= m_height); + + const u32 ps = GetPixelSize(m_format); + const u32 pitch = ps * width; + StringUtil::StrideMemCpy(&m_frame_buffer[y * m_frame_buffer_pitch + x * ps], m_frame_buffer_pitch, + m_staging_buffer.data(), pitch, pitch, height); } diff --git a/src/duckstation-regtest/regtest_host_display.h b/src/duckstation-regtest/regtest_host_display.h index bf0ed9cfe..3672423da 100644 --- a/src/duckstation-regtest/regtest_host_display.h +++ b/src/duckstation-regtest/regtest_host_display.h @@ -8,8 +8,6 @@ public: RegTestHostDisplay(); ~RegTestHostDisplay(); - void DumpFrame(const std::string& filename); - RenderAPI GetRenderAPI() const override; void* GetRenderDevice() const override; void* GetRenderContext() const override; @@ -21,7 +19,6 @@ public: bool threaded_presentation) override; bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device, bool threaded_presentation) override; - void DestroyRenderDevice() override; bool MakeRenderContextCurrent() override; bool DoneRenderContextCurrent() override; @@ -46,25 +43,46 @@ public: std::unique_ptr CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Format format, const void* data, u32 data_stride, bool dynamic = false) override; - void UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, - u32 data_stride) override; - bool DownloadTexture(const void* texture_handle, GPUTexture::Format texture_format, u32 x, u32 y, u32 width, - u32 height, void* out_data, u32 out_data_stride) override; + bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) override; + void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) override; + bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override; + bool DownloadTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, void* out_data, + u32 out_data_stride) override; void SetVSync(bool enabled) override; - bool Render() override; + bool Render(bool skip_present) override; bool RenderScreenshot(u32 width, u32 height, std::vector* out_pixels, u32* out_stride, GPUTexture::Format* out_format) override; bool SupportsTextureFormat(GPUTexture::Format format) const override; - bool BeginSetDisplayPixels(GPUTexture::Format format, u32 width, u32 height, void** out_buffer, - u32* out_pitch) override; - void EndSetDisplayPixels() override; +private: + +}; + +class RegTestTexture : public GPUTexture +{ +public: + RegTestTexture(); + ~RegTestTexture() override; + + ALWAYS_INLINE const std::vector& GetPixels() const { return m_frame_buffer; } + ALWAYS_INLINE u32 GetPitch() const { return m_frame_buffer_pitch; } + + bool IsValid() const override; + + bool Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Format format); + + bool Upload(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride); + bool Download(u32 x, u32 y, u32 width, u32 height, void* data, u32 data_stride) const; + + bool BeginUpload(u32 width, u32 height, void** out_buffer, u32* out_pitch); + void EndUpload(u32 x, u32 y, u32 width, u32 height); private: std::vector m_frame_buffer; + std::vector m_staging_buffer; GPUTexture::Format m_frame_buffer_format = GPUTexture::Format::Unknown; u32 m_frame_buffer_pitch = 0; }; diff --git a/src/duckstation-regtest/regtest_host_interface.cpp b/src/duckstation-regtest/regtest_host_interface.cpp deleted file mode 100644 index a26ef36b6..000000000 --- a/src/duckstation-regtest/regtest_host_interface.cpp +++ /dev/null @@ -1,530 +0,0 @@ -#include "regtest_host_interface.h" -#include "common/assert.h" -#include "common/byte_stream.h" -#include "common/file_system.h" -#include "common/log.h" -#include "common/path.h" -#include "common/string_util.h" -#include "core/system.h" -#include "frontend-common/game_database.h" -#include "frontend-common/game_settings.h" -#include "regtest_host_display.h" -#include "scmversion/scmversion.h" -#include "util/audio_stream.h" -#include -Log_SetChannel(RegTestHostInterface); - -#ifdef _WIN32 -#include "frontend-common/d3d11_host_display.h" -#include "frontend-common/d3d12_host_display.h" -#endif - -#include "frontend-common/opengl_host_display.h" -#include "frontend-common/vulkan_host_display.h" - -static int s_frames_to_run = 60 * 60; -static int s_frame_dump_interval = 0; -static std::shared_ptr s_boot_parameters; -static std::string s_dump_base_directory; -static std::string s_dump_game_directory; -static GPURenderer s_renderer_to_use = GPURenderer::Software; -static GameSettings::Database s_game_settings_db; -static GameDatabase s_game_database; - -RegTestHostInterface::RegTestHostInterface() = default; - -RegTestHostInterface::~RegTestHostInterface() = default; - -bool RegTestHostInterface::Initialize() -{ - if (!HostInterface::Initialize()) - return false; - - SetUserDirectoryToProgramDirectory(); - s_game_database.Load(); - LoadGameSettingsDatabase(); - InitializeSettings(); - return true; -} - -void RegTestHostInterface::Shutdown() -{ - HostInterface::Shutdown(); -} - -void RegTestHostInterface::ReportError(const char* message) -{ - Log_ErrorPrintf("Error: %s", message); -} - -void RegTestHostInterface::ReportMessage(const char* message) -{ - Log_InfoPrintf("Info: %s", message); -} - -void RegTestHostInterface::ReportDebuggerMessage(const char* message) -{ - Log_DevPrintf("Debugger: %s", message); -} - -bool RegTestHostInterface::ConfirmMessage(const char* message) -{ - Log_InfoPrintf("Confirm: %s", message); - return false; -} - -void RegTestHostInterface::AddOSDMessage(std::string message, float duration /*= 2.0f*/) -{ - Log_InfoPrintf("OSD: %s", message.c_str()); -} - -void RegTestHostInterface::AddKeyedOSDMessage(std::string key, std::string message, float duration /* = 2.0f */) -{ - Log_InfoPrintf("OSD: %s", message.c_str()); -} - -void RegTestHostInterface::RemoveKeyedOSDMessage(std::string key) {} - -void RegTestHostInterface::DisplayLoadingScreen(const char* message, int progress_min /*= -1*/, - int progress_max /*= -1*/, int progress_value /*= -1*/) -{ - Log_InfoPrintf("Loading: %s (%d / %d)", message, progress_value + progress_min, progress_max); -} - -void RegTestHostInterface::GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) -{ - if (image) - { - GameDatabaseEntry database_entry; - if (s_game_database.GetEntryForDisc(image, &database_entry)) - { - *code = std::move(database_entry.serial); - *title = std::move(database_entry.title); - return; - } - - *code = System::GetGameIdFromImage(image, true); - } - - *title = Path::GetFileTitle(path); -} - -void RegTestHostInterface::OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, - const std::string& game_title) -{ - Log_InfoPrintf("Game Path: %s", path.c_str()); - Log_InfoPrintf("Game Code: %s", game_code.c_str()); - Log_InfoPrintf("Game Title: %s", game_title.c_str()); - - if (!s_dump_base_directory.empty()) - { - s_dump_game_directory = StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "%s", - s_dump_base_directory.c_str(), game_title.c_str()); - - if (!FileSystem::DirectoryExists(s_dump_game_directory.c_str())) - { - Log_InfoPrintf("Creating directory '%s'...", s_dump_game_directory.c_str()); - if (!FileSystem::CreateDirectory(s_dump_game_directory.c_str(), false)) - Panic("Failed to create dump directory."); - } - - Log_InfoPrintf("Dumping frames to '%s'...", s_dump_game_directory.c_str()); - } - - UpdateSettings(); -} - -void RegTestHostInterface::OnSystemPerformanceCountersUpdated() {} - -void RegTestHostInterface::OnDisplayInvalidated() {} - -void RegTestHostInterface::OnAchievementsRefreshed() {} - -std::string RegTestHostInterface::GetStringSettingValue(const char* section, const char* key, - const char* default_value /*= ""*/) -{ - return m_settings_interface.GetStringValue(section, key, default_value); -} - -bool RegTestHostInterface::GetBoolSettingValue(const char* section, const char* key, bool default_value /*= false*/) -{ - return m_settings_interface.GetBoolValue(section, key, default_value); -} - -int RegTestHostInterface::GetIntSettingValue(const char* section, const char* key, int default_value /*= 0*/) -{ - return m_settings_interface.GetIntValue(section, key, default_value); -} - -float RegTestHostInterface::GetFloatSettingValue(const char* section, const char* key, float default_value /*= 0.0f*/) -{ - return m_settings_interface.GetFloatValue(section, key, default_value); -} - -std::vector RegTestHostInterface::GetSettingStringList(const char* section, const char* key) -{ - return m_settings_interface.GetStringList(section, key); -} - -SettingsInterface* RegTestHostInterface::GetSettingsInterface() -{ - return &m_settings_interface; -} - -std::lock_guard RegTestHostInterface::GetSettingsLock() -{ - return std::lock_guard(m_settings_mutex); -} - -void RegTestHostInterface::UpdateSettings() -{ - SettingsInterface& si = m_settings_interface; - HostInterface::LoadSettings(si); - - const std::string& serial = System::GetRunningSerial(); - if (!serial.empty()) - { - const GameSettings::Entry* entry = s_game_settings_db.GetEntry(serial); - if (entry) - { - Log_InfoPrintf("Applying game settings for '%s'", serial.c_str()); - entry->ApplySettings(true); - } - } - - HostInterface::FixIncompatibleSettings(true); -} - -void RegTestHostInterface::LoadGameSettingsDatabase() -{ - const char* path = "database" FS_OSPATH_SEPARATOR_STR "gamesettings.ini"; - std::unique_ptr stream = OpenPackageFile(path, BYTESTREAM_OPEN_READ | BYTESTREAM_OPEN_STREAMED); - if (!stream) - { - Log_ErrorPrintf("Failed to open game settings database from '%s'. This could cause compatibility issues.", path); - return; - } - - const std::string data(ByteStream::ReadStreamToString(stream.get())); - if (data.empty() || !s_game_settings_db.Load(data)) - { - Log_ErrorPrintf("Failed to load game settings database from '%s'. This could cause compatibility issues.", path); - return; - } -} - -void RegTestHostInterface::InitializeSettings() -{ - SettingsInterface& si = m_settings_interface; - HostInterface::SetDefaultSettings(si); - - // Set the settings we need for testing. - si.SetStringValue("GPU", "Renderer", Settings::GetRendererName(s_renderer_to_use)); - si.SetStringValue("Controller1", "Type", Settings::GetControllerTypeName(ControllerType::DigitalController)); - si.SetStringValue("Controller2", "Type", Settings::GetControllerTypeName(ControllerType::None)); - si.SetStringValue("MemoryCards", "Card1Type", Settings::GetMemoryCardTypeName(MemoryCardType::NonPersistent)); - si.SetStringValue("MemoryCards", "Card2Type", Settings::GetMemoryCardTypeName(MemoryCardType::None)); - si.SetStringValue("ControllerPorts", "MultitapMode", Settings::GetMultitapModeName(MultitapMode::Disabled)); - si.SetStringValue("Logging", "LogLevel", Settings::GetLogLevelName(LOGLEVEL_DEV)); - si.SetBoolValue("Logging", "LogToConsole", true); - - HostInterface::LoadSettings(si); -} - -std::string RegTestHostInterface::GetBIOSDirectory() -{ - return GetUserDirectoryRelativePath("bios"); -} - -std::unique_ptr RegTestHostInterface::OpenPackageFile(const char* path, u32 flags) -{ - std::string full_path(GetProgramDirectoryRelativePath("%s", path)); - return ByteStream::OpenFile(full_path.c_str(), flags); -} - -bool RegTestHostInterface::AcquireHostDisplay() -{ - switch (g_settings.gpu_renderer) - { -#ifdef _WIN32 - case GPURenderer::HardwareD3D11: - m_display = std::make_unique(); - break; - - case GPURenderer::HardwareD3D12: - m_display = std::make_unique(); - break; -#endif - - case GPURenderer::HardwareOpenGL: - m_display = std::make_unique(); - break; - - case GPURenderer::HardwareVulkan: - m_display = std::make_unique(); - break; - - case GPURenderer::Software: - default: - m_display = std::make_unique(); - break; - } - - WindowInfo wi; - wi.type = WindowInfo::Type::Surfaceless; - wi.surface_width = 640; - wi.surface_height = 480; - if (!m_display->CreateRenderDevice(wi, std::string_view(), false, false)) - { - Log_ErrorPrintf("Failed to create render device"); - m_display.reset(); - return false; - } - - if (!m_display->InitializeRenderDevice(std::string_view(), false, false)) - { - Log_ErrorPrintf("Failed to initialize render device"); - m_display->DestroyRenderDevice(); - m_display.reset(); - return false; - } - - return true; -} - -void RegTestHostInterface::ReleaseHostDisplay() -{ - if (!m_display) - return; - - m_display->DestroyRenderDevice(); - m_display.reset(); -} - -std::unique_ptr RegTestHostInterface::CreateAudioStream(AudioBackend backend) -{ - return AudioStream::CreateNullAudioStream(); -} - -void RegTestHostInterface::OnSystemCreated() {} - -void RegTestHostInterface::OnSystemPaused(bool paused) {} - -void RegTestHostInterface::OnSystemDestroyed() {} - -void RegTestHostInterface::OnControllerTypeChanged(u32 slot) {} - -void RegTestHostInterface::SetMouseMode(bool relative, bool hide_cursor) {} - -static void PrintCommandLineVersion() -{ - const bool was_console_enabled = Log::IsConsoleOutputEnabled(); - if (!was_console_enabled) - Log::SetConsoleOutputParams(true); - - std::fprintf(stderr, "DuckStation Regression Test Runner Version %s (%s)\n", g_scm_tag_str, g_scm_branch_str); - std::fprintf(stderr, "https://github.com/stenzek/duckstation\n"); - std::fprintf(stderr, "\n"); - - if (!was_console_enabled) - Log::SetConsoleOutputParams(false); -} - -static void PrintCommandLineHelp(const char* progname) -{ - const bool was_console_enabled = Log::IsConsoleOutputEnabled(); - if (!was_console_enabled) - Log::SetConsoleOutputParams(true); - - PrintCommandLineVersion(); - std::fprintf(stderr, "Usage: %s [parameters] [--] [boot 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: Set frame dump base directory (will be dumped to basedir/gametitle).\n"); - std::fprintf(stderr, " -dumpinterval: Dumps every N frames.\n"); - std::fprintf(stderr, " -frames: Sets the number of frames to execute.\n"); - std::fprintf(stderr, " -log : Sets the log level. Defaults to verbose.\n"); - std::fprintf(stderr, " -renderer : Sets the graphics renderer. Default to software.\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"); - - if (!was_console_enabled) - Log::SetConsoleOutputParams(false); -} - -static bool ParseCommandLineArgs(int argc, char* argv[]) -{ - s_boot_parameters = std::make_shared(); - - 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_dump_base_directory = argv[++i]; - if (s_dump_base_directory.empty()) - { - Log_ErrorPrintf("Invalid dump directory specified."); - return false; - } - - continue; - } - else if (CHECK_ARG_PARAM("-dumpinterval")) - { - s_frame_dump_interval = StringUtil::FromChars(argv[++i]).value_or(0); - if (s_frames_to_run <= 0) - { - Log_ErrorPrintf("Invalid dump interval specified: -1", s_frame_dump_interval); - return false; - } - - continue; - } - else if (CHECK_ARG_PARAM("-frames")) - { - s_frames_to_run = StringUtil::FromChars(argv[++i]).value_or(-1); - if (s_frames_to_run <= 0) - { - Log_ErrorPrintf("Invalid frame count specified: %d", s_frames_to_run); - return false; - } - - continue; - } - else if (CHECK_ARG_PARAM("-log")) - { - std::optional level = Settings::ParseLogLevelName(argv[++i]); - if (!level.has_value()) - { - Log_ErrorPrintf("Invalid log level specified."); - return false; - } - - Log::SetConsoleOutputParams(true, nullptr, level.value()); - continue; - } - else if (CHECK_ARG_PARAM("-renderer")) - { - std::optional renderer = Settings::ParseRendererName(argv[++i]); - if (!renderer.has_value()) - { - Log_ErrorPrintf("Invalid renderer specified."); - return false; - } - - s_renderer_to_use = renderer.value(); - continue; - } - else if (CHECK_ARG("--")) - { - no_more_args = true; - continue; - } - else if (argv[i][0] == '-') - { - Log_ErrorPrintf("Unknown parameter: '%s'", argv[i]); - return false; - } - -#undef CHECK_ARG -#undef CHECK_ARG_PARAM - } - - if (!s_boot_parameters->filename.empty()) - s_boot_parameters->filename += ' '; - s_boot_parameters->filename += argv[i]; - } - - return true; -} - -static std::string GetFrameDumpFilename(int frame) -{ - return StringUtil::StdStringFromFormat("%s" FS_OSPATH_SEPARATOR_STR "frame_%05d.png", s_dump_game_directory.c_str(), - frame); -} - -int main(int argc, char* argv[]) -{ - Log::SetConsoleOutputParams(true, nullptr, LOGLEVEL_VERBOSE); - - if (!ParseCommandLineArgs(argc, argv)) - return -1; - - int result = -1; - - Log_InfoPrintf("Initializing..."); - g_host_interface = new RegTestHostInterface(); - if (!g_host_interface->Initialize()) - goto cleanup; - - if (s_boot_parameters->filename.empty()) - { - Log_ErrorPrintf("No boot path specified."); - goto cleanup; - } - - Log_InfoPrintf("Trying to boot '%s'...", s_boot_parameters->filename.c_str()); - if (!g_host_interface->BootSystem(std::move(s_boot_parameters))) - { - Log_ErrorPrintf("Failed to boot system."); - goto cleanup; - } - - if (s_frame_dump_interval > 0) - { - if (s_dump_base_directory.empty()) - { - Log_ErrorPrint("Dump directory not specified."); - goto cleanup; - } - - Log_InfoPrintf("Dumping every %dth frame to '%s'.", s_frame_dump_interval, s_dump_base_directory.c_str()); - } - - Log_InfoPrintf("Running for %d frames...", s_frames_to_run); - - for (int frame = 1; frame <= s_frames_to_run; frame++) - { - System::RunFrame(); - - if (s_frame_dump_interval > 0 && (s_frame_dump_interval == 1 || (frame % s_frame_dump_interval) == 0)) - { - std::string dump_filename(GetFrameDumpFilename(frame)); - g_host_interface->GetDisplay()->WriteDisplayTextureToFile(std::move(dump_filename)); - } - - g_host_interface->GetDisplay()->Render(); - - System::UpdatePerformanceCounters(); - } - - Log_InfoPrintf("All done, shutting down system."); - g_host_interface->DestroySystem(); - - Log_InfoPrintf("Exiting with success."); - result = 0; - -cleanup: - delete g_host_interface; - return result; -} diff --git a/src/duckstation-regtest/regtest_host_interface.h b/src/duckstation-regtest/regtest_host_interface.h deleted file mode 100644 index 954196356..000000000 --- a/src/duckstation-regtest/regtest_host_interface.h +++ /dev/null @@ -1,65 +0,0 @@ -#pragma once -#include "core/host_interface.h" -#include "common/memory_settings_interface.h" - -class RegTestHostInterface final : public HostInterface -{ -public: - RegTestHostInterface(); - ~RegTestHostInterface(); - - bool Initialize() override; - void Shutdown() override; - - void ReportError(const char* message) override; - void ReportMessage(const char* message) override; - void ReportDebuggerMessage(const char* message) override; - bool ConfirmMessage(const char* message) override; - - void AddOSDMessage(std::string message, float duration = 2.0f) override; - void AddKeyedOSDMessage(std::string key, std::string message, float duration = 2.0f) override; - void RemoveKeyedOSDMessage(std::string key) override; - void DisplayLoadingScreen(const char* message, int progress_min = -1, int progress_max = -1, - int progress_value = -1) override; - void GetGameInfo(const char* path, CDImage* image, std::string* code, std::string* title) override; - - void OnRunningGameChanged(const std::string& path, CDImage* image, const std::string& game_code, - const std::string& game_title); - - std::string GetStringSettingValue(const char* section, const char* key, const char* default_value = "") override; - bool GetBoolSettingValue(const char* section, const char* key, bool default_value = false) override; - int GetIntSettingValue(const char* section, const char* key, int default_value = 0) override; - float GetFloatSettingValue(const char* section, const char* key, float default_value = 0.0f) override; - std::vector GetSettingStringList(const char* section, const char* key) override; - SettingsInterface* GetSettingsInterface() override; - std::lock_guard GetSettingsLock() override; - - std::string GetBIOSDirectory() override; - - std::unique_ptr OpenPackageFile(const char* path, u32 flags) override; - - void OnSystemPerformanceCountersUpdated() override; - void OnDisplayInvalidated() override; - void OnAchievementsRefreshed() override; - -protected: - bool AcquireHostDisplay() override; - void ReleaseHostDisplay() override; - - std::unique_ptr CreateAudioStream(AudioBackend backend) override; - - void OnSystemCreated() override; - void OnSystemPaused(bool paused) override; - void OnSystemDestroyed() override; - void OnControllerTypeChanged(u32 slot) override; - - void SetMouseMode(bool relative, bool hide_cursor) override; - -private: - void LoadGameSettingsDatabase(); - void InitializeSettings(); - void UpdateSettings(); - - MemorySettingsInterface m_settings_interface; - std::recursive_mutex m_settings_mutex; -};