mirror of
https://github.com/RetroDECK/Duckstation.git
synced 2024-11-22 13:55:38 +00:00
RegTest: Port to new host abstractions
This commit is contained in:
parent
c9cba5e220
commit
5498ddfab7
|
@ -7,6 +7,25 @@ import hashlib
|
||||||
|
|
||||||
from pathlib import Path
|
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):
|
def compare_frames(path1, path2):
|
||||||
try:
|
try:
|
||||||
with open(path1, "rb") as f:
|
with open(path1, "rb") as f:
|
||||||
|
@ -42,10 +61,20 @@ def check_regression_test(baselinedir, testdir, name):
|
||||||
path2 = os.path.join(dir2, imagename)
|
path2 = os.path.join(dir2, imagename)
|
||||||
if not os.path.isfile(path2):
|
if not os.path.isfile(path2):
|
||||||
print("--- Frame %u for %s is missing in test set" % (framenum, name))
|
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
|
return False
|
||||||
|
|
||||||
if not compare_frames(path1, path2):
|
if not compare_frames(path1, path2):
|
||||||
print("*** Difference in frame %u for %s" % (framenum, name))
|
print("*** Difference in frame %u for %s" % (framenum, name))
|
||||||
|
|
||||||
|
imguri1 = Path(path1).as_uri()
|
||||||
|
imguri2 = Path(path2).as_uri()
|
||||||
|
write("<h1>{}</h1>".format(name))
|
||||||
|
write("<table width=\"100%\">")
|
||||||
|
write("<tr><td colspan=\"2\">Frame {}</td></tr>".format(framenum))
|
||||||
|
write("<tr><td><img src=\"{}\" /></td><td><img src=\"{}\" /></td></tr>".format(imguri1, imguri2))
|
||||||
|
write("</table>")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
@ -71,11 +100,19 @@ if __name__ == "__main__":
|
||||||
parser = argparse.ArgumentParser(description="Check frame dump images for regression tests")
|
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("-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("-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()
|
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)):
|
if not check_regression_tests(os.path.realpath(args.baselinedir), os.path.realpath(args.testdir)):
|
||||||
|
write(FILE_FOOTER)
|
||||||
|
outfile.close()
|
||||||
sys.exit(1)
|
sys.exit(1)
|
||||||
else:
|
else:
|
||||||
|
outfile.close()
|
||||||
|
os.remove(args.outfile)
|
||||||
sys.exit(0)
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
|
@ -1352,7 +1352,6 @@ void CDROM::ExecuteCommand(TickCount ticks_late)
|
||||||
case Command::Reset:
|
case Command::Reset:
|
||||||
{
|
{
|
||||||
Log_DebugPrintf("CDROM reset command");
|
Log_DebugPrintf("CDROM reset command");
|
||||||
SendACKAndStat();
|
|
||||||
|
|
||||||
if (m_command_second_response == Command::Reset)
|
if (m_command_second_response == Command::Reset)
|
||||||
{
|
{
|
||||||
|
@ -1361,6 +1360,8 @@ void CDROM::ExecuteCommand(TickCount ticks_late)
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
SendACKAndStat();
|
||||||
|
|
||||||
if (IsSeeking())
|
if (IsSeeking())
|
||||||
UpdatePositionWhileSeeking();
|
UpdatePositionWhileSeeking();
|
||||||
|
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
add_executable(duckstation-regtest
|
add_executable(duckstation-regtest
|
||||||
regtest_host_display.cpp
|
regtest_host_display.cpp
|
||||||
regtest_host_display.h
|
regtest_host_display.h
|
||||||
regtest_host_interface.cpp
|
regtest_host.cpp
|
||||||
regtest_host_interface.h
|
|
||||||
)
|
)
|
||||||
|
|
||||||
target_link_libraries(duckstation-regtest PRIVATE core common frontend-common scmversion)
|
target_link_libraries(duckstation-regtest PRIVATE core common frontend-common scmversion)
|
||||||
|
|
|
@ -6,11 +6,10 @@
|
||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="regtest_host_display.cpp" />
|
<ClCompile Include="regtest_host_display.cpp" />
|
||||||
<ClCompile Include="regtest_host_interface.cpp" />
|
<ClCompile Include="regtest_host.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="regtest_host_display.h" />
|
<ClInclude Include="regtest_host_display.h" />
|
||||||
<ClInclude Include="regtest_host_interface.h" />
|
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<Import Project="..\..\dep\msvc\vsprops\ConsoleApplication.props" />
|
<Import Project="..\..\dep\msvc\vsprops\ConsoleApplication.props" />
|
||||||
<Import Project="..\frontend-common\frontend-common.props" />
|
<Import Project="..\frontend-common\frontend-common.props" />
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClCompile Include="regtest_host_interface.cpp" />
|
<ClCompile Include="regtest_host.cpp" />
|
||||||
<ClCompile Include="regtest_host_display.cpp" />
|
<ClCompile Include="regtest_host_display.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<ClInclude Include="regtest_host_interface.h" />
|
|
||||||
<ClInclude Include="regtest_host_display.h" />
|
<ClInclude Include="regtest_host_display.h" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
</Project>
|
</Project>
|
671
src/duckstation-regtest/regtest_host.cpp
Normal file
671
src/duckstation-regtest/regtest_host.cpp
Normal file
|
@ -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 <csignal>
|
||||||
|
#include <cstdio>
|
||||||
|
Log_SetChannel(RegTestHost);
|
||||||
|
|
||||||
|
#ifdef WITH_CHEEVOS
|
||||||
|
#include "frontend-common/achievements.h"
|
||||||
|
#endif
|
||||||
|
|
||||||
|
namespace RegTestHost {
|
||||||
|
static bool ParseCommandLineParameters(int argc, char* argv[], std::optional<SystemBootParameters>& 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<MemorySettingsInterface> 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<MemorySettingsInterface>();
|
||||||
|
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<u32>(InputSourceType::Count); i++)
|
||||||
|
si.SetBoolValue("InputSources", InputManager::InputSourceToString(static_cast<InputSourceType>(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<int>(title.size()), title.data(),
|
||||||
|
static_cast<int>(message.size()), message.data());
|
||||||
|
}
|
||||||
|
else if (!message.empty())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("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())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("ConfirmMessage: %.*s: %.*s", static_cast<int>(title.size()), title.data(),
|
||||||
|
static_cast<int>(message.size()), message.data());
|
||||||
|
}
|
||||||
|
else if (!message.empty())
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("ConfirmMessage: %.*s", static_cast<int>(message.size()), message.data());
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host::ReportDebuggerMessage(const std::string_view& message)
|
||||||
|
{
|
||||||
|
Log_ErrorPrintf("ReportDebuggerMessage: %.*s", static_cast<int>(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<std::mutex>& lock)
|
||||||
|
{
|
||||||
|
CommonHost::LoadSettings(si, lock);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host::CheckForSettingsChanges(const Settings& old_settings)
|
||||||
|
{
|
||||||
|
CommonHost::CheckForSettingsChanges(old_settings);
|
||||||
|
}
|
||||||
|
|
||||||
|
void Host::CommitBaseSettingChanges()
|
||||||
|
{
|
||||||
|
// noop, in memory
|
||||||
|
}
|
||||||
|
|
||||||
|
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())
|
||||||
|
Log_ErrorPrintf("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())
|
||||||
|
Log_ErrorPrintf("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(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<void()> 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<RegTestHostDisplay>();
|
||||||
|
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<u32> InputManager::ConvertHostKeyboardStringToCode(const std::string_view& str)
|
||||||
|
{
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::optional<std::string> 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<FrontendCommon::D3D11HostDisplay>();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GPURenderer::HardwareD3D12:
|
||||||
|
m_display = std::make_unique<FrontendCommon::D3D12HostDisplay>();
|
||||||
|
break;
|
||||||
|
#endif
|
||||||
|
|
||||||
|
case GPURenderer::HardwareOpenGL:
|
||||||
|
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GPURenderer::HardwareVulkan:
|
||||||
|
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case GPURenderer::Software:
|
||||||
|
default:
|
||||||
|
m_display = std::make_unique<RegTestHostDisplay>();
|
||||||
|
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 <level>: Sets the log level. Defaults to verbose.\n");
|
||||||
|
std::fprintf(stderr, " -renderer <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<SystemBootParameters>& AutoBoot(std::optional<SystemBootParameters>& autoboot)
|
||||||
|
{
|
||||||
|
if (!autoboot)
|
||||||
|
autoboot.emplace();
|
||||||
|
|
||||||
|
return autoboot;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegTestHost::ParseCommandLineParameters(int argc, char* argv[], std::optional<SystemBootParameters>& 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<u32>(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<u32>(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<LOGLEVEL> 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<GPURenderer> 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<SystemBootParameters> 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;
|
||||||
|
}
|
|
@ -13,7 +13,7 @@ RegTestHostDisplay::RegTestHostDisplay() = default;
|
||||||
|
|
||||||
RegTestHostDisplay::~RegTestHostDisplay() = default;
|
RegTestHostDisplay::~RegTestHostDisplay() = default;
|
||||||
|
|
||||||
HostDisplay::RenderAPI RegTestHostDisplay::GetRenderAPI() const
|
RenderAPI RegTestHostDisplay::GetRenderAPI() const
|
||||||
{
|
{
|
||||||
return RenderAPI::None;
|
return RenderAPI::None;
|
||||||
}
|
}
|
||||||
|
@ -61,11 +61,6 @@ bool RegTestHostDisplay::DoneRenderContextCurrent()
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void RegTestHostDisplay::DestroyRenderDevice()
|
|
||||||
{
|
|
||||||
ClearSoftwareCursor();
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegTestHostDisplay::DestroyRenderSurface() {}
|
void RegTestHostDisplay::DestroyRenderSurface() {}
|
||||||
|
|
||||||
bool RegTestHostDisplay::CreateResources()
|
bool RegTestHostDisplay::CreateResources()
|
||||||
|
@ -129,26 +124,40 @@ bool RegTestHostDisplay::SetPostProcessingChain(const std::string_view& config)
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<GPUTexture> RegTestHostDisplay::CreateTexture(u32 width, u32 height, u32 layers, u32 levels,
|
std::unique_ptr<GPUTexture> RegTestHostDisplay::CreateTexture(u32 width, u32 height, u32 layers, u32 levels,
|
||||||
u32 samples, GPUTexture::Format format,
|
u32 samples, GPUTexture::Format format, const void* data,
|
||||||
const void* data, u32 data_stride,
|
u32 data_stride, bool dynamic /* = false */)
|
||||||
bool dynamic /* = false */)
|
|
||||||
{
|
{
|
||||||
return nullptr;
|
std::unique_ptr<RegTestTexture> tex = std::make_unique<RegTestTexture>();
|
||||||
|
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,
|
bool RegTestHostDisplay::BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer,
|
||||||
const void* data, u32 data_stride)
|
u32* out_pitch)
|
||||||
{
|
{
|
||||||
|
return static_cast<RegTestTexture*>(texture)->BeginUpload(width, height, out_buffer, out_pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RegTestHostDisplay::DownloadTexture(const void* texture_handle, GPUTexture::Format texture_format, u32 x,
|
void RegTestHostDisplay::EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height)
|
||||||
u32 y, u32 width, u32 height, void* out_data, u32 out_data_stride)
|
|
||||||
{
|
{
|
||||||
const u32 pixel_size = GPUTexture::GetPixelSize(texture_format);
|
static_cast<RegTestTexture*>(texture)->EndUpload(x, y, width, height);
|
||||||
const u32 input_stride = Common::AlignUpPow2(width * pixel_size, 4);
|
}
|
||||||
const u8* input_start = static_cast<const u8*>(texture_handle) + (x * pixel_size);
|
|
||||||
StringUtil::StrideMemCpy(out_data, out_data_stride, input_start, input_stride, width * pixel_size, height);
|
bool RegTestHostDisplay::UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data,
|
||||||
return true;
|
u32 data_stride)
|
||||||
|
{
|
||||||
|
return static_cast<RegTestTexture*>(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<const RegTestTexture*>(texture)->Download(x, y, width, height, out_data, out_data_stride);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RegTestHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const
|
bool RegTestHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const
|
||||||
|
@ -156,59 +165,12 @@ bool RegTestHostDisplay::SupportsTextureFormat(GPUTexture::Format format) const
|
||||||
return (format == GPUTexture::Format::RGBA8);
|
return (format == GPUTexture::Format::RGBA8);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RegTestHostDisplay::BeginSetDisplayPixels(GPUTexture::Format format, u32 width, u32 height, void** out_buffer,
|
|
||||||
u32* out_pitch)
|
|
||||||
{
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 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<u8*>(m_frame_buffer.data());
|
|
||||||
*out_pitch = pitch;
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegTestHostDisplay::EndSetDisplayPixels()
|
|
||||||
{
|
|
||||||
// noop
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegTestHostDisplay::DumpFrame(const std::string& filename)
|
|
||||||
{
|
|
||||||
if (!HasDisplayTexture())
|
|
||||||
{
|
|
||||||
if (FileSystem::FileExists(filename.c_str()))
|
|
||||||
FileSystem::DeleteFile(filename.c_str());
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Common::RGBA8Image image(m_display_texture_width, m_display_texture_height,
|
|
||||||
static_cast<const u32*>(m_display_texture));
|
|
||||||
|
|
||||||
// set alpha channel on all pixels
|
|
||||||
u32* pixels = image.GetPixels();
|
|
||||||
u32* pixels_end = pixels + (image.GetWidth() * image.GetHeight());
|
|
||||||
while (pixels != pixels_end)
|
|
||||||
*(pixels++) |= 0xFF000000u;
|
|
||||||
|
|
||||||
if (!Common::WriteImageToFile(image, filename.c_str()))
|
|
||||||
Log_ErrorPrintf("Failed to dump frame '%s'", filename.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
void RegTestHostDisplay::SetVSync(bool enabled)
|
void RegTestHostDisplay::SetVSync(bool enabled)
|
||||||
{
|
{
|
||||||
Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled));
|
Log_DevPrintf("Ignoring SetVSync(%u)", BoolToUInt32(enabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RegTestHostDisplay::Render()
|
bool RegTestHostDisplay::Render(bool skip_present)
|
||||||
{
|
{
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -218,3 +180,75 @@ bool RegTestHostDisplay::RenderScreenshot(u32 width, u32 height, std::vector<u32
|
||||||
{
|
{
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
RegTestTexture::RegTestTexture() = default;
|
||||||
|
|
||||||
|
RegTestTexture::~RegTestTexture() = default;
|
||||||
|
|
||||||
|
bool RegTestTexture::IsValid() const
|
||||||
|
{
|
||||||
|
return !m_frame_buffer.empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegTestTexture::Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Format format)
|
||||||
|
{
|
||||||
|
if (width == 0 || height == 0 || layers != 1 || levels != 1 || samples != 1 || format == GPUTexture::Format::Unknown)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
m_width = static_cast<u16>(width);
|
||||||
|
m_height = static_cast<u16>(height);
|
||||||
|
m_layers = static_cast<u8>(layers);
|
||||||
|
m_levels = static_cast<u8>(levels);
|
||||||
|
m_samples = static_cast<u8>(samples);
|
||||||
|
m_format = format;
|
||||||
|
|
||||||
|
m_frame_buffer_pitch = width * GPUTexture::GetPixelSize(format);
|
||||||
|
m_frame_buffer.resize(m_frame_buffer_pitch * height);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegTestTexture::Upload(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride)
|
||||||
|
{
|
||||||
|
if ((static_cast<u64>(x) + width) > m_width || (static_cast<u64>(y) + height) > m_height)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool RegTestTexture::Download(u32 x, u32 y, u32 width, u32 height, void* data, u32 data_stride) const
|
||||||
|
{
|
||||||
|
if ((static_cast<u64>(x) + width) > m_width || (static_cast<u64>(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 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;
|
||||||
|
}
|
||||||
|
|
||||||
|
void RegTestTexture::EndUpload(u32 x, u32 y, u32 width, u32 height)
|
||||||
|
{
|
||||||
|
Assert((static_cast<u64>(x) + width) <= m_width && (static_cast<u64>(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);
|
||||||
|
}
|
||||||
|
|
|
@ -8,8 +8,6 @@ public:
|
||||||
RegTestHostDisplay();
|
RegTestHostDisplay();
|
||||||
~RegTestHostDisplay();
|
~RegTestHostDisplay();
|
||||||
|
|
||||||
void DumpFrame(const std::string& filename);
|
|
||||||
|
|
||||||
RenderAPI GetRenderAPI() const override;
|
RenderAPI GetRenderAPI() const override;
|
||||||
void* GetRenderDevice() const override;
|
void* GetRenderDevice() const override;
|
||||||
void* GetRenderContext() const override;
|
void* GetRenderContext() const override;
|
||||||
|
@ -21,7 +19,6 @@ public:
|
||||||
bool threaded_presentation) override;
|
bool threaded_presentation) override;
|
||||||
bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device,
|
bool InitializeRenderDevice(std::string_view shader_cache_directory, bool debug_device,
|
||||||
bool threaded_presentation) override;
|
bool threaded_presentation) override;
|
||||||
void DestroyRenderDevice() override;
|
|
||||||
|
|
||||||
bool MakeRenderContextCurrent() override;
|
bool MakeRenderContextCurrent() override;
|
||||||
bool DoneRenderContextCurrent() override;
|
bool DoneRenderContextCurrent() override;
|
||||||
|
@ -46,25 +43,46 @@ public:
|
||||||
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
|
||||||
GPUTexture::Format format, const void* data, u32 data_stride,
|
GPUTexture::Format format, const void* data, u32 data_stride,
|
||||||
bool dynamic = false) override;
|
bool dynamic = false) override;
|
||||||
void UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data,
|
bool BeginTextureUpdate(GPUTexture* texture, u32 width, u32 height, void** out_buffer, u32* out_pitch) override;
|
||||||
u32 data_stride) override;
|
void EndTextureUpdate(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height) override;
|
||||||
bool DownloadTexture(const void* texture_handle, GPUTexture::Format texture_format, u32 x, u32 y, u32 width,
|
bool UpdateTexture(GPUTexture* texture, u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_stride) override;
|
||||||
u32 height, void* out_data, u32 out_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;
|
void SetVSync(bool enabled) override;
|
||||||
|
|
||||||
bool Render() override;
|
bool Render(bool skip_present) override;
|
||||||
bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride,
|
bool RenderScreenshot(u32 width, u32 height, std::vector<u32>* out_pixels, u32* out_stride,
|
||||||
GPUTexture::Format* out_format) override;
|
GPUTexture::Format* out_format) override;
|
||||||
|
|
||||||
bool SupportsTextureFormat(GPUTexture::Format format) const override;
|
bool SupportsTextureFormat(GPUTexture::Format format) const override;
|
||||||
|
|
||||||
bool BeginSetDisplayPixels(GPUTexture::Format format, u32 width, u32 height, void** out_buffer,
|
private:
|
||||||
u32* out_pitch) override;
|
|
||||||
void EndSetDisplayPixels() override;
|
};
|
||||||
|
|
||||||
|
class RegTestTexture : public GPUTexture
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
RegTestTexture();
|
||||||
|
~RegTestTexture() override;
|
||||||
|
|
||||||
|
ALWAYS_INLINE const std::vector<u32>& 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:
|
private:
|
||||||
std::vector<u32> m_frame_buffer;
|
std::vector<u32> m_frame_buffer;
|
||||||
|
std::vector<u32> m_staging_buffer;
|
||||||
GPUTexture::Format m_frame_buffer_format = GPUTexture::Format::Unknown;
|
GPUTexture::Format m_frame_buffer_format = GPUTexture::Format::Unknown;
|
||||||
u32 m_frame_buffer_pitch = 0;
|
u32 m_frame_buffer_pitch = 0;
|
||||||
};
|
};
|
||||||
|
|
|
@ -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 <cstdio>
|
|
||||||
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<SystemBootParameters> 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<std::string> 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<std::recursive_mutex> RegTestHostInterface::GetSettingsLock()
|
|
||||||
{
|
|
||||||
return std::lock_guard<std::recursive_mutex>(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<ByteStream> 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<ByteStream> 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<FrontendCommon::D3D11HostDisplay>();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::HardwareD3D12:
|
|
||||||
m_display = std::make_unique<FrontendCommon::D3D12HostDisplay>();
|
|
||||||
break;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
case GPURenderer::HardwareOpenGL:
|
|
||||||
m_display = std::make_unique<FrontendCommon::OpenGLHostDisplay>();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::HardwareVulkan:
|
|
||||||
m_display = std::make_unique<FrontendCommon::VulkanHostDisplay>();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case GPURenderer::Software:
|
|
||||||
default:
|
|
||||||
m_display = std::make_unique<RegTestHostDisplay>();
|
|
||||||
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<AudioStream> 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 <level>: Sets the log level. Defaults to verbose.\n");
|
|
||||||
std::fprintf(stderr, " -renderer <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<SystemBootParameters>();
|
|
||||||
|
|
||||||
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<int>(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<int>(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<LOGLEVEL> 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<GPURenderer> 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;
|
|
||||||
}
|
|
|
@ -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<std::string> GetSettingStringList(const char* section, const char* key) override;
|
|
||||||
SettingsInterface* GetSettingsInterface() override;
|
|
||||||
std::lock_guard<std::recursive_mutex> GetSettingsLock() override;
|
|
||||||
|
|
||||||
std::string GetBIOSDirectory() override;
|
|
||||||
|
|
||||||
std::unique_ptr<ByteStream> 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<AudioStream> 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;
|
|
||||||
};
|
|
Loading…
Reference in a new issue