First Commit

This commit is contained in:
2025-11-18 14:18:26 -07:00
parent 33eb6e3707
commit 27277ec342
6106 changed files with 3571167 additions and 0 deletions

View File

@@ -0,0 +1,27 @@
enable_testing()
add_custom_target(unittests)
add_custom_command(TARGET unittests POST_BUILD COMMAND ${CMAKE_CTEST_COMMAND} --output-on-failure)
macro(add_pcsx2_test target)
add_executable(${target} EXCLUDE_FROM_ALL ${ARGN})
target_link_libraries(${target} PRIVATE gtest gtest_main)
if(APPLE)
# For some reason this doesn't get pulled in implicitly...
target_link_libraries(${target} PRIVATE
"-framework Foundation"
"-framework Cocoa"
)
endif()
add_dependencies(unittests ${target})
add_test(NAME ${target} COMMAND ${target})
if(MSVC AND NOT CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
# For some reason, the stack refuses to grow with the latest MSVC updates.
# Just force the commit size to 1MB for now.
set_target_properties(${target} PROPERTIES LINK_FLAGS "/STACK:1048576,1048576")
endif()
endmacro()
add_subdirectory(common)
add_subdirectory(core)

View File

@@ -0,0 +1,18 @@
add_pcsx2_test(common_test
byteswap_tests.cpp
filesystem_tests.cpp
path_tests.cpp
string_util_tests.cpp
)
if(_M_X86)
target_sources(common_test PRIVATE
x86emitter/codegen_tests.cpp
x86emitter/codegen_tests.h
x86emitter/codegen_tests_main.cpp
)
endif()
target_link_libraries(common_test PRIVATE
common
)

View File

@@ -0,0 +1,14 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/Pcsx2Defs.h"
#include "common/ByteSwap.h"
#include <gtest/gtest.h>
TEST(ByteSwap, ByteSwap)
{
ASSERT_EQ(ByteSwap(static_cast<u16>(0xabcd)), 0xcdabu);
ASSERT_EQ(ByteSwap(static_cast<u32>(0xabcdef01)), 0x01efcdabu);
ASSERT_EQ(ByteSwap(static_cast<u64>(0xabcdef0123456789ULL)), 0x8967452301efcdabu);
ASSERT_EQ(ByteSwap(static_cast<s32>(0x80123456)), 0x56341280);
}

View File

@@ -0,0 +1,58 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/FileSystem.h"
#include "common/Path.h"
#include <gtest/gtest.h>
#ifdef __linux__
static std::optional<std::string> create_test_directory()
{
for (u16 i = 0; i < UINT16_MAX; i++)
{
std::string path = std::string("/tmp/pcsx2_filesystem_test_") + std::to_string(i);
if (!FileSystem::DirectoryExists(path.c_str()))
{
if (!FileSystem::CreateDirectoryPath(path.c_str(), false))
break;
return path;
}
}
return std::nullopt;
}
TEST(FileSystem, RecursiveDeleteDirectoryDontFollowSymbolicLinks)
{
// Find a suitable location to write some test files.
std::optional<std::string> test_dir = create_test_directory();
ASSERT_TRUE(test_dir.has_value());
// Create a target directory containing a file that shouldn't be deleted.
std::string target_dir = Path::Combine(*test_dir, "target_dir");
ASSERT_TRUE(FileSystem::CreateDirectoryPath(target_dir.c_str(), false));
std::string file_path = Path::Combine(target_dir, "file.txt");
ASSERT_TRUE(FileSystem::WriteStringToFile(file_path.c_str(), "Lorem ipsum!"));
// Create a directory containing a symlink to the target directory.
std::string dir_to_delete = Path::Combine(*test_dir, "dir_to_delete");
ASSERT_TRUE(FileSystem::CreateDirectoryPath(dir_to_delete.c_str(), false));
std::string symlink_path = Path::Combine(dir_to_delete, "link");
ASSERT_TRUE(FileSystem::CreateSymLink(symlink_path.c_str(), target_dir.c_str()));
// Delete the directory containing the symlink.
ASSERT_TRUE(dir_to_delete.starts_with("/tmp/pcsx2_filesystem_test_"));
ASSERT_TRUE(FileSystem::RecursiveDeleteDirectory(dir_to_delete.c_str()));
// Make sure the file in the target directory didn't get deleted.
ASSERT_TRUE(FileSystem::FileExists(file_path.c_str()));
// Clean up.
ASSERT_TRUE(FileSystem::DeleteFilePath(file_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(target_dir.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(test_dir->c_str()));
}
#endif

View File

@@ -0,0 +1,367 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/FileSystem.h"
#include "common/Pcsx2Defs.h"
#include "common/Path.h"
#include <gtest/gtest.h>
TEST(Path, ToNativePath)
{
ASSERT_EQ(Path::ToNativePath(""), "");
#ifdef _WIN32
ASSERT_EQ(Path::ToNativePath("foo"), "foo");
ASSERT_EQ(Path::ToNativePath("foo\\"), "foo");
ASSERT_EQ(Path::ToNativePath("foo\\\\bar"), "foo\\bar");
ASSERT_EQ(Path::ToNativePath("foo\\bar"), "foo\\bar");
ASSERT_EQ(Path::ToNativePath("foo\\bar\\baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::ToNativePath("foo\\bar/baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::ToNativePath("foo/bar/baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::ToNativePath("foo/🙃bar/b🙃az"), "foo\\🙃bar\\b🙃az");
ASSERT_EQ(Path::ToNativePath("\\\\foo\\bar\\baz"), "\\\\foo\\bar\\baz");
#else
ASSERT_EQ(Path::ToNativePath("foo"), "foo");
ASSERT_EQ(Path::ToNativePath("foo/"), "foo");
ASSERT_EQ(Path::ToNativePath("foo//bar"), "foo/bar");
ASSERT_EQ(Path::ToNativePath("foo/bar"), "foo/bar");
ASSERT_EQ(Path::ToNativePath("foo/bar/baz"), "foo/bar/baz");
ASSERT_EQ(Path::ToNativePath("/foo/bar/baz"), "/foo/bar/baz");
#endif
}
TEST(Path, IsValidFileName)
{
#if defined(_WIN32) || defined(__APPLE__)
ASSERT_FALSE(Path::IsValidFileName("foo:bar", false));
ASSERT_FALSE(Path::IsValidFileName("baz\\foo:bar", false));
ASSERT_FALSE(Path::IsValidFileName("baz/foo:bar", false));
ASSERT_FALSE(Path::IsValidFileName("baz\\foo:bar", true));
ASSERT_FALSE(Path::IsValidFileName("baz/foo:bar", true));
#endif
#ifdef _WIN32
ASSERT_TRUE(Path::IsValidFileName("baz\\foo", true));
ASSERT_FALSE(Path::IsValidFileName("baz\\foo", false));
ASSERT_FALSE(Path::IsValidFileName("foo.", true));
ASSERT_FALSE(Path::IsValidFileName("foo\\.", true));
#else
ASSERT_FALSE(Path::IsValidFileName("foo\\*", true));
ASSERT_FALSE(Path::IsValidFileName("foo*", true));
#endif
ASSERT_TRUE(Path::IsValidFileName("baz/foo", true));
ASSERT_FALSE(Path::IsValidFileName("baz/foo", false));
}
TEST(Path, IsAbsolute)
{
ASSERT_FALSE(Path::IsAbsolute(""));
ASSERT_FALSE(Path::IsAbsolute("foo"));
ASSERT_FALSE(Path::IsAbsolute("foo/bar"));
ASSERT_FALSE(Path::IsAbsolute("foo/b🙃ar"));
#ifdef _WIN32
ASSERT_TRUE(Path::IsAbsolute("C:\\foo/bar"));
ASSERT_TRUE(Path::IsAbsolute("C://foo\\bar"));
ASSERT_FALSE(Path::IsAbsolute("\\foo/bar"));
ASSERT_TRUE(Path::IsAbsolute("\\\\foo\\bar\\baz"));
#else
ASSERT_TRUE(Path::IsAbsolute("/foo/bar"));
#endif
}
TEST(Path, Canonicalize)
{
ASSERT_EQ(Path::Canonicalize(""), Path::ToNativePath(""));
ASSERT_EQ(Path::Canonicalize("foo/bar/../baz"), Path::ToNativePath("foo/baz"));
ASSERT_EQ(Path::Canonicalize("foo/bar/./baz"), Path::ToNativePath("foo/bar/baz"));
ASSERT_EQ(Path::Canonicalize("foo/./bar/./baz"), Path::ToNativePath("foo/bar/baz"));
ASSERT_EQ(Path::Canonicalize("foo/bar/../baz/../foo"), Path::ToNativePath("foo/foo"));
ASSERT_EQ(Path::Canonicalize("foo/bar/../baz/./foo"), Path::ToNativePath("foo/baz/foo"));
ASSERT_EQ(Path::Canonicalize("./foo"), Path::ToNativePath("foo"));
ASSERT_EQ(Path::Canonicalize("../foo"), Path::ToNativePath("../foo"));
ASSERT_EQ(Path::Canonicalize("foo/b🙃ar/../b🙃az/./foo"), Path::ToNativePath("foo/b🙃az/foo"));
ASSERT_EQ(Path::Canonicalize("ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤ∩₲ ₱⟑♰⫳🐱/b🙃az/../foo"), Path::ToNativePath("ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤ∩₲ ₱⟑♰⫳🐱/foo"));
#ifdef _WIN32
ASSERT_EQ(Path::Canonicalize("C:\\foo\\bar\\..\\baz\\.\\foo"), "C:\\foo\\baz\\foo");
ASSERT_EQ(Path::Canonicalize("C:/foo\\bar\\..\\baz\\.\\foo"), "C:\\foo\\baz\\foo");
ASSERT_EQ(Path::Canonicalize("foo\\bar\\..\\baz\\.\\foo"), "foo\\baz\\foo");
ASSERT_EQ(Path::Canonicalize("foo\\bar/..\\baz/.\\foo"), "foo\\baz\\foo");
ASSERT_EQ(Path::Canonicalize("\\\\foo\\bar\\baz/..\\foo"), "\\\\foo\\bar\\foo");
#else
ASSERT_EQ(Path::Canonicalize("/foo/bar/../baz/./foo"), "/foo/baz/foo");
#endif
}
TEST(Path, Combine)
{
ASSERT_EQ(Path::Combine("", ""), Path::ToNativePath(""));
ASSERT_EQ(Path::Combine("foo", "bar"), Path::ToNativePath("foo/bar"));
ASSERT_EQ(Path::Combine("foo/bar", "baz"), Path::ToNativePath("foo/bar/baz"));
ASSERT_EQ(Path::Combine("foo/bar", "../baz"), Path::ToNativePath("foo/bar/../baz"));
ASSERT_EQ(Path::Combine("foo/bar/", "/baz/"), Path::ToNativePath("foo/bar/baz"));
ASSERT_EQ(Path::Combine("foo//bar", "baz/"), Path::ToNativePath("foo/bar/baz"));
ASSERT_EQ(Path::Combine("foo//ba🙃r", "b🙃az/"), Path::ToNativePath("foo/ba🙃r/b🙃az"));
#ifdef _WIN32
ASSERT_EQ(Path::Combine("C:\\foo\\bar", "baz"), "C:\\foo\\bar\\baz");
ASSERT_EQ(Path::Combine("\\\\server\\foo\\bar", "baz"), "\\\\server\\foo\\bar\\baz");
ASSERT_EQ(Path::Combine("foo\\bar", "baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::Combine("foo\\bar\\", "baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::Combine("foo/bar\\", "\\baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::Combine("\\\\foo\\bar", "baz"), "\\\\foo\\bar\\baz");
#else
ASSERT_EQ(Path::Combine("/foo/bar", "baz"), "/foo/bar/baz");
#endif
}
TEST(Path, AppendDirectory)
{
ASSERT_EQ(Path::AppendDirectory("foo/bar", "baz"), Path::ToNativePath("foo/baz/bar"));
ASSERT_EQ(Path::AppendDirectory("", "baz"), Path::ToNativePath("baz"));
ASSERT_EQ(Path::AppendDirectory("", ""), Path::ToNativePath(""));
ASSERT_EQ(Path::AppendDirectory("foo/bar", "🙃"), Path::ToNativePath("foo/🙃/bar"));
#ifdef _WIN32
ASSERT_EQ(Path::AppendDirectory("foo\\bar", "baz"), "foo\\baz\\bar");
ASSERT_EQ(Path::AppendDirectory("\\\\foo\\bar", "baz"), "\\\\foo\\baz\\bar");
#else
ASSERT_EQ(Path::AppendDirectory("/foo/bar", "baz"), "/foo/baz/bar");
#endif
}
TEST(Path, MakeRelative)
{
ASSERT_EQ(Path::MakeRelative("", ""), Path::ToNativePath(""));
ASSERT_EQ(Path::MakeRelative("foo", ""), Path::ToNativePath("foo"));
ASSERT_EQ(Path::MakeRelative("", "foo"), Path::ToNativePath(""));
ASSERT_EQ(Path::MakeRelative("foo", "bar"), Path::ToNativePath("foo"));
#ifdef _WIN32
#define A "C:\\"
#else
#define A "/"
#endif
ASSERT_EQ(Path::MakeRelative(A "foo", A "bar"), Path::ToNativePath("../foo"));
ASSERT_EQ(Path::MakeRelative(A "foo/bar", A "foo"), Path::ToNativePath("bar"));
ASSERT_EQ(Path::MakeRelative(A "foo/bar", A "foo/baz"), Path::ToNativePath("../bar"));
ASSERT_EQ(Path::MakeRelative(A "foo/b🙃ar", A "foo/b🙃az"), Path::ToNativePath("../b🙃ar"));
ASSERT_EQ(Path::MakeRelative(A "f🙃oo/b🙃ar", A "f🙃oo/b🙃az"), Path::ToNativePath("../b🙃ar"));
ASSERT_EQ(Path::MakeRelative(A "ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤ∩₲ ₱⟑♰⫳🐱/b🙃ar", A "ŻąłóРстуぬねのはen🍪⟑η∏☉ⴤ∩₲ ₱⟑♰⫳🐱/b🙃az"), Path::ToNativePath("../b🙃ar"));
#undef A
#ifdef _WIN32
ASSERT_EQ(Path::MakeRelative("\\\\foo\\bar\\baz\\foo", "\\\\foo\\bar\\baz"), "foo");
ASSERT_EQ(Path::MakeRelative("\\\\foo\\bar\\foo", "\\\\foo\\bar\\baz"), "..\\foo");
ASSERT_EQ(Path::MakeRelative("\\\\foo\\bar\\foo", "\\\\other\\bar\\foo"), "\\\\foo\\bar\\foo");
#endif
}
TEST(Path, GetExtension)
{
ASSERT_EQ(Path::GetExtension("foo"), "");
ASSERT_EQ(Path::GetExtension("foo.txt"), "txt");
ASSERT_EQ(Path::GetExtension("foo.t🙃t"), "t🙃t");
ASSERT_EQ(Path::GetExtension("foo."), "");
ASSERT_EQ(Path::GetExtension("a/b/foo.txt"), "txt");
ASSERT_EQ(Path::GetExtension("a/b/foo"), "");
}
TEST(Path, GetFileName)
{
ASSERT_EQ(Path::GetFileName(""), "");
ASSERT_EQ(Path::GetFileName("foo"), "foo");
ASSERT_EQ(Path::GetFileName("foo.txt"), "foo.txt");
ASSERT_EQ(Path::GetFileName("foo"), "foo");
ASSERT_EQ(Path::GetFileName("foo/bar/."), ".");
ASSERT_EQ(Path::GetFileName("foo/bar/baz"), "baz");
ASSERT_EQ(Path::GetFileName("foo/bar/baz.txt"), "baz.txt");
#ifdef _WIN32
ASSERT_EQ(Path::GetFileName("foo/bar\\baz"), "baz");
ASSERT_EQ(Path::GetFileName("foo\\bar\\baz.txt"), "baz.txt");
#endif
}
TEST(Path, GetFileTitle)
{
ASSERT_EQ(Path::GetFileTitle(""), "");
ASSERT_EQ(Path::GetFileTitle("foo"), "foo");
ASSERT_EQ(Path::GetFileTitle("foo.txt"), "foo");
ASSERT_EQ(Path::GetFileTitle("foo/bar/."), "");
ASSERT_EQ(Path::GetFileTitle("foo/bar/baz"), "baz");
ASSERT_EQ(Path::GetFileTitle("foo/bar/baz.txt"), "baz");
#ifdef _WIN32
ASSERT_EQ(Path::GetFileTitle("foo/bar\\baz"), "baz");
ASSERT_EQ(Path::GetFileTitle("foo\\bar\\baz.txt"), "baz");
#endif
}
TEST(Path, GetDirectory)
{
ASSERT_EQ(Path::GetDirectory(""), "");
ASSERT_EQ(Path::GetDirectory("foo"), "");
ASSERT_EQ(Path::GetDirectory("foo.txt"), "");
ASSERT_EQ(Path::GetDirectory("foo/bar/."), "foo/bar");
ASSERT_EQ(Path::GetDirectory("foo/bar/baz"), "foo/bar");
ASSERT_EQ(Path::GetDirectory("foo/bar/baz.txt"), "foo/bar");
#ifdef _WIN32
ASSERT_EQ(Path::GetDirectory("foo\\bar\\baz"), "foo\\bar");
ASSERT_EQ(Path::GetDirectory("foo\\bar/baz.txt"), "foo\\bar");
#endif
}
TEST(Path, ChangeFileName)
{
ASSERT_EQ(Path::ChangeFileName("", ""), Path::ToNativePath(""));
ASSERT_EQ(Path::ChangeFileName("", "bar"), Path::ToNativePath("bar"));
ASSERT_EQ(Path::ChangeFileName("bar", ""), Path::ToNativePath(""));
ASSERT_EQ(Path::ChangeFileName("foo/bar", ""), Path::ToNativePath("foo"));
ASSERT_EQ(Path::ChangeFileName("foo/", "bar"), Path::ToNativePath("foo/bar"));
ASSERT_EQ(Path::ChangeFileName("foo/bar", "baz"), Path::ToNativePath("foo/baz"));
ASSERT_EQ(Path::ChangeFileName("foo//bar", "baz"), Path::ToNativePath("foo/baz"));
ASSERT_EQ(Path::ChangeFileName("foo//bar.txt", "baz.txt"), Path::ToNativePath("foo/baz.txt"));
ASSERT_EQ(Path::ChangeFileName("foo//ba🙃r.txt", "ba🙃z.txt"), Path::ToNativePath("foo/ba🙃z.txt"));
#ifdef _WIN32
ASSERT_EQ(Path::ChangeFileName("foo/bar", "baz"), "foo\\baz");
ASSERT_EQ(Path::ChangeFileName("foo//bar\\foo", "baz"), "foo\\bar\\baz");
ASSERT_EQ(Path::ChangeFileName("\\\\foo\\bar\\foo", "baz"), "\\\\foo\\bar\\baz");
#else
ASSERT_EQ(Path::ChangeFileName("/foo/bar", "baz"), "/foo/baz");
#endif
}
TEST(Path, CreateFileURL)
{
#ifdef _WIN32
ASSERT_EQ(Path::CreateFileURL("C:\\foo\\bar"), "file:///C:/foo/bar");
ASSERT_EQ(Path::CreateFileURL("\\\\server\\share\\file.txt"), "file://server/share/file.txt");
#else
ASSERT_EQ(Path::CreateFileURL("/foo/bar"), "file:///foo/bar");
#endif
}
#if __linux__
static std::optional<std::string> create_test_directory()
{
for (u16 i = 0; i < UINT16_MAX; i++)
{
std::string path = std::string("/tmp/pcsx2_path_test_") + std::to_string(i);
if (!FileSystem::DirectoryExists(path.c_str()))
{
if (!FileSystem::CreateDirectoryPath(path.c_str(), false))
break;
return path;
}
}
return std::nullopt;
}
TEST(Path, RealPathAbsoluteSymbolicLink)
{
std::optional<std::string> test_dir = create_test_directory();
ASSERT_TRUE(test_dir.has_value());
// Create a file to point at.
std::string file_path = Path::Combine(*test_dir, "file");
ASSERT_TRUE(FileSystem::WriteStringToFile(file_path.c_str(), "Hello, world!"));
// Create a symbolic link that points to said file.
std::string link_path = Path::Combine(*test_dir, "link");
ASSERT_TRUE(FileSystem::CreateSymLink(link_path.c_str(), file_path.c_str()));
// Make sure the symbolic link is resolved correctly.
ASSERT_EQ(Path::RealPath(link_path), file_path);
// Clean up.
ASSERT_TRUE(FileSystem::DeleteSymbolicLink(link_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteFilePath(file_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(test_dir->c_str()));
}
TEST(Path, RealPathRelativeSymbolicLink)
{
std::optional<std::string> test_dir = create_test_directory();
ASSERT_TRUE(test_dir.has_value());
// Create a file to point at.
std::string file_path = Path::Combine(*test_dir, "file");
ASSERT_TRUE(FileSystem::WriteStringToFile(file_path.c_str(), "Hello, world!"));
// Create a symbolic link that points to said file.
std::string link_path = Path::Combine(*test_dir, "link");
ASSERT_TRUE(FileSystem::CreateSymLink(link_path.c_str(), "file"));
// Make sure the symbolic link is resolved correctly.
ASSERT_EQ(Path::RealPath(link_path), file_path);
// Clean up.
ASSERT_TRUE(FileSystem::DeleteSymbolicLink(link_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteFilePath(file_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(test_dir->c_str()));
}
TEST(Path, RealPathDotDotSymbolicLink)
{
std::optional<std::string> test_dir = create_test_directory();
ASSERT_TRUE(test_dir.has_value());
// Create a file to point at.
std::string file_path = Path::Combine(*test_dir, "file");
ASSERT_TRUE(FileSystem::WriteStringToFile(file_path.c_str(), "Hello, world!"));
// Create a directory to put the link in.
std::string link_dir = Path::Combine(*test_dir, "dir");
ASSERT_TRUE(FileSystem::CreateDirectoryPath(link_dir.c_str(), false));
// Create a symbolic link that points to said file.
std::string link_path = Path::Combine(link_dir, "link");
ASSERT_TRUE(FileSystem::CreateSymLink(link_path.c_str(), "../file"));
// Make sure the symbolic link is resolved correctly.
ASSERT_EQ(Path::RealPath(link_path), file_path);
// Clean up.
ASSERT_TRUE(FileSystem::DeleteSymbolicLink(link_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(link_dir.c_str()));
ASSERT_TRUE(FileSystem::DeleteFilePath(file_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(test_dir->c_str()));
}
TEST(Path, RealPathCircularSymbolicLink)
{
std::optional<std::string> test_dir = create_test_directory();
ASSERT_TRUE(test_dir.has_value());
// Create a circular symbolic link.
std::string link_path = Path::Combine(*test_dir, "link");
ASSERT_TRUE(FileSystem::CreateSymLink(link_path.c_str(), "."));
// Make sure the link gets resolved correctly.
ASSERT_EQ(Path::RealPath(link_path), *test_dir);
ASSERT_EQ(Path::RealPath(Path::Combine(link_path, "link")), *test_dir);
// Clean up.
ASSERT_TRUE(FileSystem::DeleteSymbolicLink(link_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(test_dir->c_str()));
}
TEST(Path, RealPathLoopingSymbolicLink)
{
std::optional<std::string> test_dir = create_test_directory();
ASSERT_TRUE(test_dir.has_value());
// Create a symbolic link that points to itself.
std::string link_path = Path::Combine(*test_dir, "link");
ASSERT_TRUE(FileSystem::CreateSymLink(link_path.c_str(), "link"));
// Make sure this doesn't cause problems.
ASSERT_EQ(Path::RealPath(link_path), link_path);
// Clean up.
ASSERT_TRUE(FileSystem::DeleteSymbolicLink(link_path.c_str()));
ASSERT_TRUE(FileSystem::DeleteDirectory(test_dir->c_str()));
}
#endif

View File

@@ -0,0 +1,132 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/Pcsx2Defs.h"
#include "common/StringUtil.h"
#include <locale>
#include <clocale>
#include <gtest/gtest.h>
TEST(StringUtil, ToChars)
{
ASSERT_EQ(StringUtil::ToChars(false), "false");
ASSERT_EQ(StringUtil::ToChars(true), "true");
ASSERT_EQ(StringUtil::ToChars(0), "0");
ASSERT_EQ(StringUtil::ToChars(-1337), "-1337");
ASSERT_EQ(StringUtil::ToChars(1337), "1337");
ASSERT_EQ(StringUtil::ToChars(1337u), "1337");
ASSERT_EQ(StringUtil::ToChars(13.37f), "13.37");
ASSERT_EQ(StringUtil::ToChars(255, 16), "ff");
}
TEST(StringUtil, FromChars)
{
ASSERT_EQ(StringUtil::FromChars<bool>("false").value_or(true), false);
ASSERT_EQ(StringUtil::FromChars<bool>("true").value_or(false), true);
ASSERT_EQ(StringUtil::FromChars<int>("0").value_or(-1), 0);
ASSERT_EQ(StringUtil::FromChars<int>("-1337").value_or(0), -1337);
ASSERT_EQ(StringUtil::FromChars<int>("1337").value_or(0), 1337);
ASSERT_EQ(StringUtil::FromChars<u32>("1337").value_or(0), 1337);
ASSERT_TRUE(std::abs(StringUtil::FromChars<float>("13.37").value_or(0.0f) - 13.37) < 0.01f);
ASSERT_EQ(StringUtil::FromChars<int>("ff", 16).value_or(0), 255);
}
TEST(StringUtil, FromCharsWithEndPtr)
{
using namespace std::literals;
std::string_view endptr;
ASSERT_EQ(StringUtil::FromChars<u32>("123x456", 16, &endptr), std::optional<u32>(0x123));
ASSERT_EQ(endptr, "x456"sv);
ASSERT_EQ(StringUtil::FromChars<u32>("0x1234", 16, &endptr), std::optional<u32>(0u));
ASSERT_EQ(endptr, "x1234"sv);
ASSERT_EQ(StringUtil::FromChars<u32>("1234", 16, &endptr), std::optional<u32>(0x1234u));
ASSERT_TRUE(endptr.empty());
ASSERT_EQ(StringUtil::FromChars<u32>("abcdefg", 16, &endptr), std::optional<u32>(0xabcdef));
ASSERT_EQ(endptr, "g"sv);
ASSERT_EQ(StringUtil::FromChars<u32>("123abc", 10, &endptr), std::optional<u32>(123));
ASSERT_EQ(endptr, "abc"sv);
ASSERT_EQ(StringUtil::FromChars<float>("1.0g", &endptr), std::optional<float>(1.0f));
ASSERT_EQ(endptr, "g"sv);
ASSERT_EQ(StringUtil::FromChars<float>("2x", &endptr), std::optional<float>(2.0f));
ASSERT_EQ(endptr, "x"sv);
ASSERT_EQ(StringUtil::FromChars<float>(".1p", &endptr), std::optional<float>(0.1f));
ASSERT_EQ(endptr, "p"sv);
ASSERT_EQ(StringUtil::FromChars<float>("1", &endptr), std::optional<float>(1.0f));
ASSERT_TRUE(endptr.empty());
}
#if 0
// NOTE: These tests are disabled, because they require the da_DK locale to actually be present.
// Which probably isn't going to be the case on the CI.
TEST(StringUtil, ToCharsIsLocaleIndependent)
{
const auto old_locale = std::locale();
std::locale::global(std::locale::classic());
std::string classic_result(StringUtil::ToChars(13.37f));
std::locale::global(std::locale("da_DK"));
std::string dk_result(StringUtil::ToChars(13.37f));
std::locale::global(old_locale);
ASSERT_EQ(classic_result, dk_result);
}
TEST(StringUtil, FromCharsIsLocaleIndependent)
{
const auto old_locale = std::locale();
std::locale::global(std::locale::classic());
const float classic_result = StringUtil::FromChars<float>("13.37").value_or(0.0f);
std::locale::global(std::locale("da_DK"));
const float dk_result = StringUtil::FromChars<float>("13.37").value_or(0.0f);
std::locale::global(old_locale);
ASSERT_EQ(classic_result, dk_result);
}
#endif
TEST(StringUtil, Ellipsise)
{
ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 6, "..."), "Hel...");
ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 7, ".."), "Hello..");
ASSERT_EQ(StringUtil::Ellipsise("HelloWorld", 20, ".."), "HelloWorld");
ASSERT_EQ(StringUtil::Ellipsise("", 20, "..."), "");
ASSERT_EQ(StringUtil::Ellipsise("Hello", 10, "..."), "Hello");
}
TEST(StringUtil, EllipsiseInPlace)
{
std::string s;
s = "HelloWorld";
StringUtil::EllipsiseInPlace(s, 6, "...");
ASSERT_EQ(s, "Hel...");
s = "HelloWorld";
StringUtil::EllipsiseInPlace(s, 7, "..");
ASSERT_EQ(s, "Hello..");
s = "HelloWorld";
StringUtil::EllipsiseInPlace(s, 20, "..");
ASSERT_EQ(s, "HelloWorld");
s = "";
StringUtil::EllipsiseInPlace(s, 20, "...");
ASSERT_EQ(s, "");
s = "Hello";
StringUtil::EllipsiseInPlace(s, 10, "...");
ASSERT_EQ(s, "Hello");
}

View File

@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "codegen_tests.h"
#include <gtest/gtest.h>
#include <common/emitter/x86emitter.h>
#include <common/Assertions.h>
using namespace x86Emitter;
thread_local const char *currentTest;
void runCodegenTest(void (*exec)(void *base), const char* description, const char* expected) {
u8 code[4096];
memset(code, 0xcc, sizeof(code));
char str[4096] = {0};
if (!expected) return;
currentTest = description;
xSetPtr(code);
exec(code);
char *strPtr = str;
for (u8* ptr = code; ptr < xGetPtr(); ptr++) {
sprintf(strPtr, "%02x ", *ptr);
strPtr += 3;
}
if (strPtr != str) {
// Remove final space
*--strPtr = '\0';
}
EXPECT_STRCASEEQ(expected, str) << "Unexpected codegen from " << description;
}

View File

@@ -0,0 +1,8 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "common/Pcsx2Defs.h"
void runCodegenTest(void (*exec)(void *base), const char* description, const char* expected);
#define CODEGEN_TEST(command, expected) runCodegenTest([](void *base){ command; }, #command, expected)

View File

@@ -0,0 +1,285 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "codegen_tests.h"
#include <gtest/gtest.h>
#include <common/emitter/x86emitter.h>
#include <cstdio>
using namespace x86Emitter;
TEST(CodegenTests, MOVTest)
{
CODEGEN_TEST(xMOV(rax, 0), "31 c0");
CODEGEN_TEST(xMOV(rax, rcx), "48 89 c8");
CODEGEN_TEST(xMOV(eax, ecx), "89 c8");
CODEGEN_TEST(xMOV(r8, 0), "45 31 c0");
CODEGEN_TEST(xMOV(rax, r8), "4c 89 c0");
CODEGEN_TEST(xMOV(r8, rax), "49 89 c0");
CODEGEN_TEST(xMOV(r8, r9), "4d 89 c8");
CODEGEN_TEST(xMOV(rax, ptr64[rcx]), "48 8b 01");
CODEGEN_TEST(xMOV(eax, ptr32[rcx]), "8b 01");
CODEGEN_TEST(xMOV(ptr64[rax], rcx), "48 89 08");
CODEGEN_TEST(xMOV(ptr32[rax], ecx), "89 08");
CODEGEN_TEST(xMOV(rax, ptr64[r8]), "49 8b 00");
CODEGEN_TEST(xMOV(ptr64[r8], rax), "49 89 00");
CODEGEN_TEST(xMOV(r8, ptr64[r9]), "4d 8b 01");
CODEGEN_TEST(xMOV(ptr64[r8], r9), "4d 89 08");
CODEGEN_TEST(xMOV(rax, ptr64[rbx*4+3+rcx]), "48 8b 44 99 03");
CODEGEN_TEST(xMOV(ptr64[rbx*4+3+rax], rcx), "48 89 4c 98 03");
CODEGEN_TEST(xMOV(eax, ptr32[rbx*4+3+rcx]), "8b 44 99 03");
CODEGEN_TEST(xMOV(ptr32[rbx*4+3+rax], ecx), "89 4c 98 03");
CODEGEN_TEST(xMOV(r8, ptr64[r10*4+3+r9]), "4f 8b 44 91 03");
CODEGEN_TEST(xMOV(ptr64[r9*4+3+r8], r10), "4f 89 54 88 03");
CODEGEN_TEST(xMOV(ptr64[r8], 0), "49 c7 00 00 00 00 00");
CODEGEN_TEST(xMOV(ptr32[rax], 0), "c7 00 00 00 00 00");
CODEGEN_TEST(xMOV(ptr32[rbx*4+3+rax], -1), "c7 44 98 03 ff ff ff ff");
CODEGEN_TEST(xMOV(rax, 0xffffffff), "b8 ff ff ff ff");
CODEGEN_TEST(xMOV(r8, -1), "49 c7 c0 ff ff ff ff");
CODEGEN_TEST(xMOV64(rax, 0x1234567890), "48 b8 90 78 56 34 12 00 00 00");
CODEGEN_TEST(xMOV64(r8, 0x1234567890), "49 b8 90 78 56 34 12 00 00 00");
CODEGEN_TEST(xMOV(ptr32[base], 0x12), "c7 05 f6 ff ff ff 12 00 00 00");
CODEGEN_TEST(xMOVSX(eax, dx), "0f bf c2");
CODEGEN_TEST(xMOVSX(rax, r8d), "49 63 c0");
CODEGEN_TEST(xMOVSX(rax, ebx), "48 63 c3");
}
TEST(CodegenTests, LEATest)
{
CODEGEN_TEST(xLEA(rax, ptr[rcx]), "48 89 c8"); // Converted to mov rax, rcx
CODEGEN_TEST(xLEA(eax, ptr[rcx]), "89 c8"); // Converted to mov eax, ecx
CODEGEN_TEST(xLEA(rax, ptr[r8]), "4c 89 c0"); // Converted to mov rax, r8
CODEGEN_TEST(xLEA(r8, ptr[r9]), "4d 89 c8"); // Converted to mov r8, r9
CODEGEN_TEST(xLEA(rax, ptr[rbx*4+3+rcx]), "48 8d 44 99 03");
CODEGEN_TEST(xLEA(eax, ptr32[rbx*4+3+rcx]), "8d 44 99 03");
CODEGEN_TEST(xLEA(r8, ptr[r10*4+3+r9]), "4f 8d 44 91 03");
CODEGEN_TEST(xLEA(r8, ptr[base]), "4c 8d 05 f9 ff ff ff");
CODEGEN_TEST(xLoadFarAddr(r8, base), "4c 8d 05 f9 ff ff ff");
CODEGEN_TEST(xLoadFarAddr(r8, (void*)0xff00001234567890), "49 b8 90 78 56 34 12 00 00 ff");
CODEGEN_TEST(xLEA(rax, ptr[(void*)0x1234]), "b8 34 12 00 00"); // Converted to mov rax, 0x1234
CODEGEN_TEST(xLoadFarAddr(rax, (void*)0x1234), "b8 34 12 00 00");
CODEGEN_TEST(xLEA_Writeback(rbx), "48 8d 1d cd cd cd 0d");
}
TEST(CodegenTests, PUSHTest)
{
CODEGEN_TEST(xPUSH(rax), "50");
CODEGEN_TEST(xPUSH(r8), "41 50");
CODEGEN_TEST(xPUSH(0x1234), "68 34 12 00 00");
CODEGEN_TEST(xPUSH(0x12), "6a 12");
CODEGEN_TEST(xPUSH(ptr64[rax]), "ff 30");
CODEGEN_TEST(xPUSH(ptr64[r8]), "41 ff 30");
CODEGEN_TEST(xPUSH(ptr64[rax*2+3+rbx]), "ff 74 43 03");
CODEGEN_TEST(xPUSH(ptr64[rax*2+3+r8]), "41 ff 74 40 03");
CODEGEN_TEST(xPUSH(ptr64[r9*4+3+r8]), "43 ff 74 88 03");
CODEGEN_TEST(xPUSH(ptr64[r8*4+3+rax]), "42 ff 74 80 03");
CODEGEN_TEST(xPUSH(ptr64[rax*8+0x1234+rbx]), "ff b4 c3 34 12 00 00");
CODEGEN_TEST(xPUSH(ptr64[base]), "ff 35 fa ff ff ff");
CODEGEN_TEST(xPUSH(ptr64[(void*)0x1234]), "ff 34 25 34 12 00 00");
}
TEST(CodegenTests, POPTest)
{
CODEGEN_TEST(xPOP(rax), "58");
CODEGEN_TEST(xPOP(r8), "41 58");
CODEGEN_TEST(xPOP(ptr64[rax]), "8f 00");
CODEGEN_TEST(xPOP(ptr64[r8]), "41 8f 00");
CODEGEN_TEST(xPOP(ptr64[rax*2+3+rbx]), "8f 44 43 03");
CODEGEN_TEST(xPOP(ptr64[rax*2+3+r8]), "41 8f 44 40 03");
CODEGEN_TEST(xPOP(ptr64[r9*4+3+r8]), "43 8f 44 88 03");
CODEGEN_TEST(xPOP(ptr64[r8*4+3+rax]), "42 8f 44 80 03");
CODEGEN_TEST(xPOP(ptr64[rax*8+0x1234+rbx]), "8f 84 c3 34 12 00 00");
CODEGEN_TEST(xPOP(ptr64[base]), "8f 05 fa ff ff ff");
CODEGEN_TEST(xPOP(ptr64[(void*)0x1234]), "8f 04 25 34 12 00 00");
}
TEST(CodegenTests, MathTest)
{
CODEGEN_TEST(xINC(eax), "ff c0");
CODEGEN_TEST(xDEC(rax), "48 ff c8");
CODEGEN_TEST(xINC(r8), "49 ff c0");
CODEGEN_TEST(xADD(r8, r9), "4d 01 c8");
CODEGEN_TEST(xADD(r8, 0x12), "49 83 c0 12");
CODEGEN_TEST(xADD(rax, 0x1234), "48 05 34 12 00 00");
CODEGEN_TEST(xADD(ptr8[base], 1), "80 05 f9 ff ff ff 01");
CODEGEN_TEST(xADD(ptr32[base], -0x60), "83 05 f9 ff ff ff a0");
CODEGEN_TEST(xADD(ptr32[base], 0x1234), "81 05 f6 ff ff ff 34 12 00 00");
CODEGEN_TEST(xADD(eax, ebx), "01 d8");
CODEGEN_TEST(xADD(eax, 0x1234), "05 34 12 00 00");
CODEGEN_TEST(xADD(r8, ptr64[r10*4+3+r9]), "4f 03 44 91 03");
CODEGEN_TEST(xADD(ptr64[r9*4+3+r8], r10), "4f 01 54 88 03");
CODEGEN_TEST(xADD(eax, ptr32[rbx*4+3+rcx]), "03 44 99 03");
CODEGEN_TEST(xADD(ptr32[rax*4+3+rbx], ecx), "01 4c 83 03");
CODEGEN_TEST(xSUB(r8, 0x12), "49 83 e8 12");
CODEGEN_TEST(xSUB(rax, 0x1234), "48 2d 34 12 00 00");
CODEGEN_TEST(xSUB(eax, ptr32[rcx*4+rax]), "2b 04 88");
CODEGEN_TEST(xMUL(ptr32[base]), "f7 2d fa ff ff ff");
CODEGEN_TEST(xMUL(ptr32[(void*)0x1234]), "f7 2c 25 34 12 00 00");
CODEGEN_TEST(xDIV(ecx), "f7 f9");
}
TEST(CodegenTests, BitwiseTest)
{
CODEGEN_TEST(xSHR(r8, cl), "49 d3 e8");
CODEGEN_TEST(xSHR(rax, cl), "48 d3 e8");
CODEGEN_TEST(xSHR(ecx, cl), "d3 e9");
CODEGEN_TEST(xSAR(r8, 1), "49 d1 f8");
CODEGEN_TEST(xSAR(rax, 60), "48 c1 f8 3c");
CODEGEN_TEST(xSAR(eax, 30), "c1 f8 1e");
CODEGEN_TEST(xSHL(ebx, 30), "c1 e3 1e");
CODEGEN_TEST(xSHL(ptr32[base], 4), "c1 25 f9 ff ff ff 04");
CODEGEN_TEST(xAND(r8, r9), "4d 21 c8");
CODEGEN_TEST(xXOR(rax, ptr64[r10]), "49 33 02");
CODEGEN_TEST(xOR(esi, ptr32[rax+rbx]), "0b 34 18");
CODEGEN_TEST(xNOT(r8), "49 f7 d0");
CODEGEN_TEST(xNOT(ptr64[rax]), "48 f7 10");
CODEGEN_TEST(xNOT(ptr32[rbx]), "f7 13");
}
TEST(CodegenTests, JmpTest)
{
CODEGEN_TEST(xJMP(r8), "41 ff e0");
CODEGEN_TEST(xJMP(rdi), "ff e7");
CODEGEN_TEST(xJMP(ptr64[rax]), "ff 20");
CODEGEN_TEST(xJA(base), "77 fe");
CODEGEN_TEST(xJB((char*)base - 0xFFFF), "0f 82 fb ff fe ff");
}
TEST(CodegenTests, SSETest)
{
CODEGEN_TEST(xMOVAPS(xmm0, xmm1), "0f 28 c1");
CODEGEN_TEST(xMOVAPS(xmm8, xmm9), "45 0f 28 c1");
CODEGEN_TEST(xMOVUPS(xmm8, ptr128[r8+r9]), "47 0f 10 04 08");
CODEGEN_TEST(xMOVAPS(ptr128[rax+r9], xmm8), "46 0f 29 04 08");
CODEGEN_TEST(xBLEND.PS(xmm0, xmm1, 0x55), "66 0f 3a 0c c1 55");
CODEGEN_TEST(xBLEND.PD(xmm8, xmm9, 0xaa), "66 45 0f 3a 0d c1 aa");
CODEGEN_TEST(xPBLEND.W(xmm0, xmm1, 0x55), "66 0f 3a 0e c1 55");
CODEGEN_TEST(xPBLEND.VB(xmm1, xmm2), "66 0f 38 10 ca");
CODEGEN_TEST(xEXTRACTPS(ptr32[base], xmm1, 2), "66 0f 3a 17 0d f6 ff ff ff 02");
CODEGEN_TEST(xMOVD(eax, xmm1), "66 0f 7e c8");
CODEGEN_TEST(xMOVD(eax, xmm10), "66 44 0f 7e d0");
CODEGEN_TEST(xMOVD(rax, xmm1), "66 48 0f 7e c8");
CODEGEN_TEST(xMOVD(r10, xmm1), "66 49 0f 7e ca");
CODEGEN_TEST(xMOVD(rax, xmm10), "66 4c 0f 7e d0");
CODEGEN_TEST(xMOVD(r10, xmm10), "66 4d 0f 7e d2");
CODEGEN_TEST(xPINSR.B(xmm0, ebx, 1), "66 0f 3a 20 c3 01");
CODEGEN_TEST(xPINSR.W(xmm0, ebx, 1), "66 0f c4 c3 01");
CODEGEN_TEST(xPINSR.D(xmm0, ebx, 1), "66 0f 3a 22 c3 01");
CODEGEN_TEST(xPINSR.Q(xmm0, rbx, 1), "66 48 0f 3a 22 c3 01");
CODEGEN_TEST(xPEXTR.B(ebx, xmm0, 1), "66 0f 3a 14 c3 01");
CODEGEN_TEST(xPEXTR.W(ebx, xmm0, 1), "66 0f c5 c3 01");
CODEGEN_TEST(xPEXTR.D(ebx, xmm0, 1), "66 0f 3a 16 c3 01");
CODEGEN_TEST(xPEXTR.Q(rbx, xmm0, 1), "66 48 0f 3a 16 c3 01");
CODEGEN_TEST(xPEXTR.Q(ptr64[rax], xmm0, 1), "66 48 0f 3a 16 00 01");
}
TEST(CodegenTests, AVXTest)
{
CODEGEN_TEST(xVMOVAPS(xmm0, xmm1), "c5 f8 28 c1");
CODEGEN_TEST(xVMOVAPS(xmm0, ptr32[rdi]), "c5 f8 28 07");
CODEGEN_TEST(xVMOVAPS(ptr32[rdi], xmm0), "c5 f8 29 07");
CODEGEN_TEST(xVMOVUPS(xmm0, ptr32[rdi]), "c5 f8 10 07");
CODEGEN_TEST(xVMOVUPS(ptr32[rdi], xmm0), "c5 f8 11 07");
CODEGEN_TEST(xVADD.PS(xmm0, xmm1, xmm2), "c5 f0 58 c2");
CODEGEN_TEST(xVADD.PD(xmm0, xmm1, xmm2), "c5 f1 58 c2");
CODEGEN_TEST(xVADD.SS(xmm0, xmm1, xmm2), "c5 f2 58 c2");
CODEGEN_TEST(xVADD.SD(xmm0, xmm1, xmm2), "c5 f3 58 c2");
CODEGEN_TEST(xVSUB.PS(xmm0, xmm1, xmm2), "c5 f0 5c c2");
CODEGEN_TEST(xVSUB.PD(xmm0, xmm1, xmm2), "c5 f1 5c c2");
CODEGEN_TEST(xVSUB.SS(xmm0, xmm1, xmm2), "c5 f2 5c c2");
CODEGEN_TEST(xVSUB.SD(xmm0, xmm1, xmm2), "c5 f3 5c c2");
CODEGEN_TEST(xVMUL.PS(xmm0, xmm1, xmm2), "c5 f0 59 c2");
CODEGEN_TEST(xVMUL.PD(xmm0, xmm1, xmm2), "c5 f1 59 c2");
CODEGEN_TEST(xVMUL.SS(xmm0, xmm1, xmm2), "c5 f2 59 c2");
CODEGEN_TEST(xVMUL.SD(xmm0, xmm1, xmm2), "c5 f3 59 c2");
CODEGEN_TEST(xVDIV.PS(xmm0, xmm1, xmm2), "c5 f0 5e c2");
CODEGEN_TEST(xVDIV.PD(xmm0, xmm1, xmm2), "c5 f1 5e c2");
CODEGEN_TEST(xVDIV.SS(xmm0, xmm1, xmm2), "c5 f2 5e c2");
CODEGEN_TEST(xVDIV.SD(xmm0, xmm1, xmm2), "c5 f3 5e c2");
// Don't need to test all variants, since they just change the condition immediate.
CODEGEN_TEST(xVCMP.EQ.PS(xmm0, xmm1, xmm2), "c5 f0 c2 c2 00");
CODEGEN_TEST(xVCMP.EQ.PD(xmm0, xmm1, xmm2), "c5 f1 c2 c2 00");
CODEGEN_TEST(xVCMP.EQ.SS(xmm0, xmm1, xmm2), "c5 f2 c2 c2 00");
CODEGEN_TEST(xVCMP.EQ.SD(xmm0, xmm1, xmm2), "c5 f3 c2 c2 00");
CODEGEN_TEST(xVCMP.LE.PS(xmm0, xmm1, xmm2), "c5 f0 c2 c2 02");
CODEGEN_TEST(xVCMP.LE.PD(xmm0, xmm1, xmm2), "c5 f1 c2 c2 02");
CODEGEN_TEST(xVCMP.LE.SS(xmm0, xmm1, xmm2), "c5 f2 c2 c2 02");
CODEGEN_TEST(xVCMP.LE.SD(xmm0, xmm1, xmm2), "c5 f3 c2 c2 02");
CODEGEN_TEST(xVPCMP.EQB(xmm0, xmm1, xmm2), "c5 f1 74 c2");
CODEGEN_TEST(xVPCMP.EQW(xmm0, xmm1, xmm2), "c5 f1 75 c2");
CODEGEN_TEST(xVPCMP.EQD(xmm0, xmm1, xmm2), "c5 f1 76 c2");
CODEGEN_TEST(xVPCMP.GTB(xmm0, xmm1, xmm2), "c5 f1 64 c2");
CODEGEN_TEST(xVPCMP.GTW(xmm0, xmm1, xmm2), "c5 f1 65 c2");
CODEGEN_TEST(xVPCMP.GTD(xmm0, xmm1, xmm2), "c5 f1 66 c2");
CODEGEN_TEST(xVPAND(xmm0, xmm1, xmm2), "c5 f1 db c2");
CODEGEN_TEST(xVPANDN(xmm0, xmm1, xmm2), "c5 f1 df c2");
CODEGEN_TEST(xVPOR(xmm0, xmm1, xmm2), "c5 f1 eb c2");
CODEGEN_TEST(xVPXOR(xmm0, xmm1, xmm2), "c5 f1 ef c2");
CODEGEN_TEST(xVMOVMSKPS(eax, xmm1), "c5 f8 50 c1");
CODEGEN_TEST(xVMOVMSKPD(eax, xmm1), "c5 f9 50 c1");
}
TEST(CodegenTests, AVX256Test)
{
CODEGEN_TEST(xVMOVAPS(ymm0, ymm1), "c5 fc 28 c1");
CODEGEN_TEST(xVMOVAPS(ymm0, ptr32[rdi]), "c5 fc 28 07");
CODEGEN_TEST(xVMOVAPS(ptr32[rdi], ymm0), "c5 fc 29 07");
CODEGEN_TEST(xVMOVUPS(ymm0, ptr32[rdi]), "c5 fc 10 07");
CODEGEN_TEST(xVMOVUPS(ptr32[rdi], ymm0), "c5 fc 11 07");
CODEGEN_TEST(xVZEROUPPER(), "c5 f8 77");
CODEGEN_TEST(xVADD.PS(ymm0, ymm1, ymm2), "c5 f4 58 c2");
CODEGEN_TEST(xVADD.PD(ymm0, ymm1, ymm2), "c5 f5 58 c2");
CODEGEN_TEST(xVSUB.PS(ymm0, ymm1, ymm2), "c5 f4 5c c2");
CODEGEN_TEST(xVSUB.PD(ymm0, ymm1, ymm2), "c5 f5 5c c2");
CODEGEN_TEST(xVMUL.PS(ymm0, ymm1, ymm2), "c5 f4 59 c2");
CODEGEN_TEST(xVMUL.PD(ymm0, ymm1, ymm2), "c5 f5 59 c2");
CODEGEN_TEST(xVDIV.PS(ymm0, ymm1, ymm2), "c5 f4 5e c2");
CODEGEN_TEST(xVDIV.PD(ymm0, ymm1, ymm2), "c5 f5 5e c2");
CODEGEN_TEST(xVCMP.EQ.PS(ymm0, ymm1, ymm2), "c5 f4 c2 c2 00");
CODEGEN_TEST(xVCMP.EQ.PD(ymm0, ymm1, ymm2), "c5 f5 c2 c2 00");
CODEGEN_TEST(xVCMP.LE.PS(ymm0, ymm1, ymm2), "c5 f4 c2 c2 02");
CODEGEN_TEST(xVCMP.LE.PD(ymm0, ymm1, ymm2), "c5 f5 c2 c2 02");
CODEGEN_TEST(xVPCMP.EQB(ymm0, ymm1, ymm2), "c5 f5 74 c2");
CODEGEN_TEST(xVPCMP.EQW(ymm0, ymm1, ymm2), "c5 f5 75 c2");
CODEGEN_TEST(xVPCMP.EQD(ymm0, ymm1, ymm2), "c5 f5 76 c2");
CODEGEN_TEST(xVPCMP.GTB(ymm0, ymm1, ymm2), "c5 f5 64 c2");
CODEGEN_TEST(xVPCMP.GTW(ymm0, ymm1, ymm2), "c5 f5 65 c2");
CODEGEN_TEST(xVPCMP.GTD(ymm0, ymm1, ymm2), "c5 f5 66 c2");
CODEGEN_TEST(xVPAND(ymm0, ymm1, ymm2), "c5 f5 db c2");
CODEGEN_TEST(xVPANDN(ymm0, ymm1, ymm2), "c5 f5 df c2");
CODEGEN_TEST(xVPOR(ymm0, ymm1, ymm2), "c5 f5 eb c2");
CODEGEN_TEST(xVPXOR(ymm0, ymm1, ymm2), "c5 f5 ef c2");
CODEGEN_TEST(xVMOVMSKPS(eax, ymm1), "c5 fc 50 c1");
CODEGEN_TEST(xVMOVMSKPD(eax, ymm1), "c5 fd 50 c1");
}
TEST(CodegenTests, Extended8BitTest)
{
CODEGEN_TEST(xSETL(al), "0f 9c c0");
CODEGEN_TEST(xSETL(cl), "0f 9c c1");
CODEGEN_TEST(xSETL(dl), "0f 9c c2");
CODEGEN_TEST(xSETL(bl), "0f 9c c3");
CODEGEN_TEST(xSETL(spl), "40 0f 9c c4");
CODEGEN_TEST(xSETL(bpl), "40 0f 9c c5");
CODEGEN_TEST(xSETL(sil), "40 0f 9c c6");
CODEGEN_TEST(xSETL(dil), "40 0f 9c c7");
CODEGEN_TEST(xSETL(r8b), "41 0f 9c c0");
CODEGEN_TEST(xSETL(r9b), "41 0f 9c c1");
CODEGEN_TEST(xSETL(r10b), "41 0f 9c c2");
CODEGEN_TEST(xSETL(r11b), "41 0f 9c c3");
CODEGEN_TEST(xSETL(r12b), "41 0f 9c c4");
CODEGEN_TEST(xSETL(r13b), "41 0f 9c c5");
CODEGEN_TEST(xSETL(r14b), "41 0f 9c c6");
CODEGEN_TEST(xSETL(r15b), "41 0f 9c c7");
}

View File

@@ -0,0 +1,75 @@
add_pcsx2_test(core_test
StubHost.cpp
)
set(multi_isa_sources
GS/swizzle_test_main.cpp
)
target_link_libraries(core_test PUBLIC
PCSX2_FLAGS
PCSX2
common
)
if(DISABLE_ADVANCE_SIMD)
if(WIN32)
set(compile_options_avx2 /arch:AVX2)
set(compile_options_avx /arch:AVX)
elseif(USE_GCC)
# GCC can't inline into multi-isa functions if we use march and mtune, but can if we use feature flags
set(compile_options_avx2 -msse4.1 -mavx -mavx2 -mbmi -mbmi2 -mfma)
set(compile_options_avx -msse4.1 -mavx)
set(compile_options_sse4 -msse4.1)
else()
set(compile_options_avx2 -march=haswell -mtune=haswell)
set(compile_options_avx -march=sandybridge -mtune=sandybridge)
set(compile_options_sse4 -msse4.1 -mtune=nehalem)
endif()
# This breaks when running on Apple Silicon, because even though we skip the test itself, the
# gtest constructor still generates AVX code, and that's a global object which gets constructed
# at binary load time. So, for now, only compile SSE4 if running on ARM64.
if (NOT APPLE OR "${CMAKE_HOST_SYSTEM_PROCESSOR}" STREQUAL "x86_64")
set(isa_list "sse4" "avx" "avx2")
else()
set(isa_list "sse4")
endif()
# ODR violation time!
# Everything would be fine if we only defined things in cpp files, but C++ tends to like inline functions (STL anyone?)
# Each ISA will bring with it its own copies of these inline header functions, and the linker gets to choose whichever one it wants! Not fun if the linker chooses the avx2 version and uses it with everything
# Thankfully, most linkers don't choose at random. When presented with a bunch of .o files, most linkers seem to choose the first implementation they see, so make sure you order these from oldest to newest
# Note: ld64 (macOS's linker) does not act the same way when presented with .a files, unless linked with `-force_load` (cmake WHOLE_ARCHIVE).
set(is_first_isa "1")
foreach(isa IN LISTS isa_list)
add_library(core_test_${isa} STATIC ${multi_isa_sources})
target_link_libraries(core_test_${isa} PRIVATE PCSX2_FLAGS gtest)
target_compile_definitions(core_test_${isa} PRIVATE MULTI_ISA_UNSHARED_COMPILATION=isa_${isa} MULTI_ISA_IS_FIRST=${is_first_isa} ${pcsx2_defs_${isa}})
target_compile_options(core_test_${isa} PRIVATE ${compile_options_${isa}})
if (${CMAKE_VERSION} VERSION_GREATER_EQUAL 3.24)
target_link_libraries(core_test PRIVATE $<LINK_LIBRARY:WHOLE_ARCHIVE,core_test_${isa}>)
elseif(APPLE)
message(FATAL_ERROR "MacOS builds with DISABLE_ADVANCE_SIMD=ON require CMake 3.24")
else()
target_link_libraries(core_test PRIVATE core_test_${isa})
endif()
set(is_first_isa "0")
endforeach()
else()
target_sources(core_test PRIVATE ${multi_isa_sources})
endif()
if(WIN32 AND TARGET SDL3::SDL3)
# Copy SDL3 DLL to binary directory.
if(CMAKE_BUILD_TYPE STREQUAL "Debug")
get_property(SDL3_DLL_PATH TARGET SDL3::SDL3 PROPERTY IMPORTED_LOCATION_DEBUG)
else()
get_property(SDL3_DLL_PATH TARGET SDL3::SDL3 PROPERTY IMPORTED_LOCATION_RELEASE)
endif()
if(SDL3_DLL_PATH)
add_custom_command(TARGET core_test POST_BUILD
COMMAND "${CMAKE_COMMAND}" -E make_directory "$<TARGET_FILE_DIR:core_test>"
COMMAND "${CMAKE_COMMAND}" -E copy_if_different "${SDL3_DLL_PATH}" "$<TARGET_FILE_DIR:core_test>")
endif()
endif()

View File

@@ -0,0 +1,590 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "pcsx2/GS/GSBlock.h"
#include "pcsx2/GS/GSClut.h"
#include "pcsx2/GS/MultiISA.h"
#include <gtest/gtest.h>
#include <string.h>
#include "cpuinfo.h"
#ifdef MULTI_ISA_UNSHARED_COMPILATION
enum class TestISA
{
isa_sse4,
isa_avx,
isa_avx2,
isa_native,
};
static bool CheckCapabilities(TestISA required_caps)
{
cpuinfo_initialize();
if (required_caps == TestISA::isa_avx && !cpuinfo_has_x86_avx())
return false;
if (required_caps == TestISA::isa_avx2 && !cpuinfo_has_x86_avx2())
return false;
return true;
}
#define MULTI_ISA_STRINGIZE_(x) #x
#define MULTI_ISA_STRINGIZE(x) MULTI_ISA_STRINGIZE_(x)
#define MULTI_ISA_CONCAT_(a, b) a##b
#define MULTI_ISA_CONCAT(a, b) MULTI_ISA_CONCAT_(a, b)
#define MULTI_ISA_TEST(group, name) TEST(MULTI_ISA_CONCAT(MULTI_ISA_CONCAT(MULTI_ISA_UNSHARED_COMPILATION, _), group), name)
#define SKIP_IF_UNSUPPORTED() \
if (!CheckCapabilities(TestISA::MULTI_ISA_UNSHARED_COMPILATION)) { \
GTEST_SKIP() << "Host CPU does not support " MULTI_ISA_STRINGIZE(MULTI_ISA_UNSHARED_COMPILATION); \
}
#else
#define MULTI_ISA_TEST(group, name) TEST(group, name)
#define SKIP_IF_UNSUPPORTED()
#endif
MULTI_ISA_UNSHARED_START
static void swizzle(const u8* table, u8* dst, const u8* src, int bpp, bool deswizzle)
{
int pxbytes = bpp / 8;
for (int i = 0; i < (256 / pxbytes); i++)
{
int soff = (deswizzle ? table[i] : i) * pxbytes;
int doff = (deswizzle ? i : table[i]) * pxbytes;
memcpy(&dst[doff], &src[soff], pxbytes);
}
}
static void swizzle4(const u16* table, u8* dst, const u8* src, bool deswizzle)
{
for (int i = 0; i < 512; i++)
{
int soff = (deswizzle ? table[i] : i);
int doff = (deswizzle ? i : table[i]);
int spx = src[soff >> 1] >> ((soff & 1) * 4) & 0xF;
u8* dpx = &dst[doff >> 1];
int dshift = (doff & 1) * 4;
*dpx &= (0xF0 >> dshift);
*dpx |= (spx << dshift);
}
}
static void swizzleH(const u8* table, u32* dst, const u8* src, int bpp, int shift)
{
for (int i = 0; i < 64; i++)
{
int spx;
if (bpp == 8)
spx = src[i];
else
spx = (src[i >> 1] >> ((i & 1) * 4)) & 0xF;
spx <<= shift;
dst[table[i]] = spx;
}
}
static void expand16(u32* dst, const u16* src, const GIFRegTEXA& texa)
{
for (int i = 0; i < 128; i++)
{
int r = (src[i] << 3) & 0x0000F8;
int g = (src[i] << 6) & 0x00F800;
int b = (src[i] << 9) & 0xF80000;
dst[i] = r | g | b;
if (src[i] & 0x8000)
{
dst[i] |= texa.TA1 << 24;
}
else if (!texa.AEM || src[i])
{
dst[i] |= texa.TA0 << 24;
}
}
}
static void expand8(u32* dst, const u8* src, const u32* palette)
{
for (int i = 0; i < 256; i++)
{
dst[i] = palette[src[i]];
}
}
static void expand4(u32* dst, const u8* src, const u32* palette)
{
for (int i = 0; i < 512; i++)
{
dst[i] = palette[(src[i >> 1] >> ((i & 1) * 4)) & 0xF];
}
}
static void expand4P(u8* dst, const u8* src)
{
for (int i = 0; i < 512; i++)
{
dst[i] = (src[i >> 1] >> ((i & 1) * 4)) & 0xF;
}
}
static void expandH(u32* dst, const u32* src, const u32* palette, int shift, int mask)
{
for (int i = 0; i < 64; i++)
{
dst[i] = palette[(src[i] >> shift) & mask];
}
}
static void expandHP(u8* dst, const u32* src, int shift, int mask)
{
for (int i = 0; i < 64; i++)
{
dst[i] = (src[i] >> shift) & mask;
}
}
static std::string image2hex(const u8* bin, int rows, int columns, int bpp)
{
std::string out;
const char* hex = "0123456789ABCDEF";
for (int y = 0; y < rows; y++)
{
if (y != 0)
out.push_back('\n');
for (int x = 0; x < columns; x++)
{
if (x != 0)
out.push_back(' ');
if (bpp == 4)
{
if (x & 1)
{
out.push_back(hex[*bin >> 4]);
bin++;
}
else
{
out.push_back(hex[*bin & 0xF]);
}
}
else
{
for (int z = 0; z < (bpp / 8); z++)
{
out.push_back(hex[*bin >> 4]);
out.push_back(hex[*bin & 0xF]);
bin++;
}
}
}
}
return out;
}
struct TestData
{
alignas(64) u8 block[256];
alignas(64) u8 output[256 * (32 / 4)];
alignas(64) u32 clut32[256];
alignas(64) u64 clut64[256];
/// Get some input data with pixel values counting up from 0
static TestData Linear()
{
TestData output;
memset(output.output, 0, sizeof(output.output));
for (int i = 0; i < 256; i++)
{
output.block[i] = i;
output.clut32[i] = i | (i << 16);
}
GSClut::ExpandCLUT64_T32_I8(output.clut32, output.clut64);
return output;
}
/// Get some input data with random-ish (but consistent across runs) pixel values
static TestData Random()
{
srand(0);
TestData output;
memset(output.output, 0, sizeof(output.output));
for (int i = 0; i < 256; i++)
{
output.block[i] = rand();
output.clut32[i] = rand();
}
GSClut::ExpandCLUT64_T32_I8(output.clut32, output.clut64);
return output;
}
/// Move data from output back to block to run an expand
TestData prepareExpand()
{
TestData output = *this;
memcpy(output.block, output.output, sizeof(output.block));
return output;
}
};
static TestData swizzle(const u8* table, TestData data, int bpp, bool deswizzle)
{
swizzle(table, data.output, data.block, bpp, deswizzle);
return data;
}
static TestData swizzle4(const u16* table, TestData data, bool deswizzle)
{
swizzle4(table, data.output, data.block, deswizzle);
return data;
}
static TestData swizzleH(const u8* table, TestData data, int bpp, int shift)
{
swizzleH(table, reinterpret_cast<u32*>(data.output), data.block, bpp, shift);
return data;
}
static TestData expand16(TestData data, const GIFRegTEXA& texa)
{
expand16(reinterpret_cast<u32*>(data.output), reinterpret_cast<const u16*>(data.block), texa);
return data;
}
static TestData expand8(TestData data)
{
expand8(reinterpret_cast<u32*>(data.output), data.block, data.clut32);
return data;
}
static TestData expand4(TestData data)
{
expand4(reinterpret_cast<u32*>(data.output), data.block, data.clut32);
return data;
}
static TestData expand4P(TestData data)
{
expand4P(data.output, data.block);
return data;
}
static TestData expandH(TestData data, int shift, int mask)
{
expandH(reinterpret_cast<u32*>(data.output), reinterpret_cast<const u32*>(data.block), data.clut32, shift, mask);
return data;
}
static TestData expandHP(TestData data, int shift, int mask)
{
expandHP(data.output, reinterpret_cast<u32*>(data.block), shift, mask);
return data;
}
static void runTest(void (*fn)(TestData))
{
fn(TestData::Linear());
fn(TestData::Random());
}
static void assertEqual(const TestData& expected, const TestData& actual, const char* name, int rows, int columns, int bpp)
{
std::string estr = image2hex(expected.output, rows, columns, bpp);
std::string astr = image2hex(actual.output, rows, columns, bpp);
EXPECT_STREQ(estr.c_str(), astr.c_str()) << "Unexpected " << name;
}
MULTI_ISA_TEST(ReadTest, Read32)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
GSBlock::ReadBlock32(data.block, data.output, 32);
assertEqual(expected, data, "Read32", 8, 8, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write32)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, false);
GSBlock::WriteBlock32<32, 0xFFFFFFFF>(data.output, data.block, 32);
assertEqual(expected, data, "Write32", 8, 8, 32);
});
}
MULTI_ISA_TEST(ReadTest, Read16)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable16[0][0], data, 16, true);
GSBlock::ReadBlock16(data.block, data.output, 32);
assertEqual(expected, data, "Read16", 8, 16, 16);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read16)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
GIFRegTEXA texa = {0};
texa.TA0 = 1;
texa.TA1 = 2;
TestData expected = swizzle(&columnTable16[0][0], data, 16, true);
expected = expand16(expected.prepareExpand(), texa);
GSBlock::ReadAndExpandBlock16<false>(data.block, data.output, 64, texa);
assertEqual(expected, data, "ReadAndExpand16", 8, 16, 32);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read16AEM)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
// Actually test AEM
u8 idx = data.block[0] >> 1;
data.block[idx * 2 + 0] = 0;
data.block[idx * 2 + 1] = 0;
GIFRegTEXA texa = {0};
texa.TA0 = 1;
texa.TA1 = 2;
texa.AEM = 1;
TestData expected = swizzle(&columnTable16[0][0], data, 16, true);
expected = expand16(expected.prepareExpand(), texa);
GSBlock::ReadAndExpandBlock16<true>(data.block, data.output, 64, texa);
assertEqual(expected, data, "ReadAndExpand16AEM", 8, 16, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write16)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable16[0][0], data, 16, false);
GSBlock::WriteBlock16<32>(data.output, data.block, 32);
assertEqual(expected, data, "Read16", 8, 16, 16);
});
}
MULTI_ISA_TEST(ReadTest, Read8)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable8[0][0], data, 8, true);
GSBlock::ReadBlock8(data.block, data.output, 16);
assertEqual(expected, data, "Read8", 16, 16, 8);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read8)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable8[0][0], data, 8, true);
expected = expand8(expected.prepareExpand());
GSBlock::ReadAndExpandBlock8_32(data.block, data.output, 64, data.clut32);
assertEqual(expected, data, "ReadAndExpand8", 16, 16, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write8)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable8[0][0], data, 8, false);
GSBlock::WriteBlock8<32>(data.output, data.block, 16);
assertEqual(expected, data, "Write8", 16, 16, 8);
});
}
MULTI_ISA_TEST(ReadTest, Read8H)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
expected = expandHP(expected.prepareExpand(), 24, 0xFF);
GSBlock::ReadBlock8HP(data.block, data.output, 8);
assertEqual(expected, data, "Read8H", 8, 8, 8);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read8H)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
expected = expandH(expected.prepareExpand(), 24, 0xFF);
GSBlock::ReadAndExpandBlock8H_32(data.block, data.output, 32, data.clut32);
assertEqual(expected, data, "ReadAndExpand8H", 8, 8, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write8H)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzleH(&columnTable32[0][0], data, 8, 24);
GSBlock::UnpackAndWriteBlock8H(data.block, 8, data.output);
assertEqual(expected, data, "Write8H", 8, 8, 32);
});
}
MULTI_ISA_TEST(ReadTest, Read4)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle4(&columnTable4[0][0], data, true);
GSBlock::ReadBlock4(data.block, data.output, 16);
assertEqual(expected, data, "Read4", 16, 32, 4);
});
}
MULTI_ISA_TEST(ReadTest, Read4P)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle4(&columnTable4[0][0], data, true);
expected = expand4P(expected.prepareExpand());
GSBlock::ReadBlock4P(data.block, data.output, 32);
assertEqual(expected, data, "Read4P", 16, 32, 8);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read4)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle4(&columnTable4[0][0], data, true);
expected = expand4(expected.prepareExpand());
GSBlock::ReadAndExpandBlock4_32(data.block, data.output, 128, data.clut32);
assertEqual(expected, data, "ReadAndExpand4", 16, 32, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write4)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle4(&columnTable4[0][0], data, false);
GSBlock::WriteBlock4<32>(data.output, data.block, 16);
assertEqual(expected, data, "Write4", 16, 16, 4);
});
}
MULTI_ISA_TEST(ReadTest, Read4HH)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
expected = expandHP(expected.prepareExpand(), 28, 0xF);
GSBlock::ReadBlock4HHP(data.block, data.output, 8);
assertEqual(expected, data, "Read4HH", 8, 8, 8);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read4HH)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
expected = expandH(expected.prepareExpand(), 28, 0xF);
GSBlock::ReadAndExpandBlock4HH_32(data.block, data.output, 32, data.clut32);
assertEqual(expected, data, "ReadAndExpand4HH", 8, 8, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write4HH)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzleH(&columnTable32[0][0], data, 4, 28);
GSBlock::UnpackAndWriteBlock4HH(data.block, 4, data.output);
assertEqual(expected, data, "Write4HH", 8, 8, 32);
});
}
MULTI_ISA_TEST(ReadTest, Read4HL)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
expected = expandHP(expected.prepareExpand(), 24, 0xF);
GSBlock::ReadBlock4HLP(data.block, data.output, 8);
assertEqual(expected, data, "Read4HL", 8, 8, 8);
});
}
MULTI_ISA_TEST(ReadAndExpandTest, Read4HL)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzle(&columnTable32[0][0], data, 32, true);
expected = expandH(expected.prepareExpand(), 24, 0xF);
GSBlock::ReadAndExpandBlock4HL_32(data.block, data.output, 32, data.clut32);
assertEqual(expected, data, "ReadAndExpand4HL", 8, 8, 32);
});
}
MULTI_ISA_TEST(WriteTest, Write4HL)
{
SKIP_IF_UNSUPPORTED();
runTest([](TestData data)
{
TestData expected = swizzleH(&columnTable32[0][0], data, 4, 24);
GSBlock::UnpackAndWriteBlock4HL(data.block, 4, data.output);
assertEqual(expected, data, "Write4HL", 8, 8, 32);
});
}
MULTI_ISA_UNSHARED_END

View File

@@ -0,0 +1,287 @@
// SPDX-FileCopyrightText: 2002-2025 PCSX2 Dev Team
// SPDX-License-Identifier: GPL-3.0+
#include "pcsx2/Achievements.h"
#include "pcsx2/GS.h"
#include "pcsx2/GameList.h"
#include "pcsx2/Host.h"
#include "pcsx2/ImGui/FullscreenUI.h"
#include "pcsx2/ImGui/ImGuiAnimated.h"
#include "pcsx2/ImGui/ImGuiFullscreen.h"
#include "pcsx2/ImGui/ImGuiManager.h"
#include "pcsx2/Input/InputManager.h"
#include "pcsx2/VMManager.h"
#include "common/ProgressCallback.h"
void Host::CommitBaseSettingChanges()
{
}
void Host::LoadSettings(SettingsInterface& si, std::unique_lock<std::mutex>& lock)
{
}
void Host::CheckForSettingsChanges(const Pcsx2Config& old_config)
{
}
bool Host::RequestResetSettings(bool folders, bool core, bool controllers, bool hotkeys, bool ui)
{
return false;
}
void Host::SetDefaultUISettings(SettingsInterface& si)
{
}
std::unique_ptr<ProgressCallback> Host::CreateHostProgressCallback()
{
return ProgressCallback::CreateNullProgressCallback();
}
void Host::ReportInfoAsync(const std::string_view title, const std::string_view message)
{
}
void Host::ReportErrorAsync(const std::string_view title, const std::string_view message)
{
}
bool Host::ConfirmMessage(const std::string_view title, const std::string_view message)
{
return true;
}
void Host::OpenURL(const std::string_view url)
{
}
bool Host::InBatchMode()
{
return false;
}
bool Host::InNoGUIMode()
{
return false;
}
bool Host::CopyTextToClipboard(const std::string_view text)
{
return false;
}
void Host::BeginTextInput()
{
}
void Host::EndTextInput()
{
}
std::optional<WindowInfo> Host::GetTopLevelWindowInfo()
{
return std::nullopt;
}
void Host::OnInputDeviceConnected(const std::string_view identifier, const std::string_view device_name)
{
}
void Host::OnInputDeviceDisconnected(const InputBindingKey key, const std::string_view identifier)
{
}
void Host::SetMouseMode(bool relative_mode, bool hide_cursor)
{
}
std::optional<WindowInfo> Host::AcquireRenderWindow(bool recreate_window)
{
return std::nullopt;
}
void Host::ReleaseRenderWindow()
{
}
void Host::BeginPresentFrame()
{
}
void Host::RequestResizeHostDisplay(s32 width, s32 height)
{
}
void Host::OnVMStarting()
{
}
void Host::OnVMStarted()
{
}
void Host::OnVMDestroyed()
{
}
void Host::OnVMPaused()
{
}
void Host::OnVMResumed()
{
}
void Host::OnGameChanged(const std::string& title, const std::string& elf_override, const std::string& disc_path,
const std::string& disc_serial, u32 disc_crc, u32 current_crc)
{
}
void Host::OnPerformanceMetricsUpdated()
{
}
void Host::OnSaveStateLoading(const std::string_view filename)
{
}
void Host::OnSaveStateLoaded(const std::string_view filename, bool was_successful)
{
}
void Host::OnSaveStateSaved(const std::string_view filename)
{
}
void Host::RunOnCPUThread(std::function<void()> function, bool block /* = false */)
{
}
void Host::RefreshGameListAsync(bool invalidate_cache)
{
}
void Host::CancelGameListRefresh()
{
}
bool Host::IsFullscreen()
{
return false;
}
void Host::SetFullscreen(bool enabled)
{
}
void Host::OnCaptureStarted(const std::string& filename)
{
}
void Host::OnCaptureStopped()
{
}
void Host::RequestExitApplication(bool allow_confirm)
{
}
void Host::RequestExitBigPicture()
{
}
void Host::RequestVMShutdown(bool allow_confirm, bool allow_save_state, bool default_save_state)
{
}
void Host::PumpMessagesOnCPUThread()
{
}
s32 Host::Internal::GetTranslatedStringImpl(
const std::string_view context, const std::string_view msg, char* tbuf, size_t tbuf_space)
{
if (msg.size() > tbuf_space)
return -1;
else if (msg.empty())
return 0;
std::memcpy(tbuf, msg.data(), msg.size());
return static_cast<s32>(msg.size());
}
std::string Host::TranslatePluralToString(const char* context, const char* msg, const char* disambiguation, int count)
{
TinyString count_str = TinyString::from_format("{}", count);
std::string ret(msg);
for (;;)
{
std::string::size_type pos = ret.find("%n");
if (pos == std::string::npos)
break;
ret.replace(pos, pos + 2, count_str.view());
}
return ret;
}
void Host::OnAchievementsLoginRequested(Achievements::LoginRequestReason reason)
{
}
void Host::OnAchievementsLoginSuccess(const char* username, u32 points, u32 sc_points, u32 unread_messages)
{
}
void Host::OnAchievementsRefreshed()
{
}
void Host::OnAchievementsHardcoreModeChanged(bool enabled)
{
}
void Host::OnCoverDownloaderOpenRequested()
{
}
void Host::OnCreateMemoryCardOpenRequested()
{
}
bool Host::LocaleCircleConfirm()
{
return false;
}
bool Host::ShouldPreferHostFileSelector()
{
return false;
}
void Host::OpenHostFileSelectorAsync(std::string_view title, bool select_directory, FileSelectorCallback callback,
FileSelectorFilters filters, std::string_view initial_directory)
{
callback(std::string());
}
std::optional<u32> InputManager::ConvertHostKeyboardStringToCode(const std::string_view str)
{
return std::nullopt;
}
std::optional<std::string> InputManager::ConvertHostKeyboardCodeToString(u32 code)
{
return std::nullopt;
}
const char* InputManager::ConvertHostKeyboardCodeToIcon(u32 code)
{
return nullptr;
}
BEGIN_HOTKEY_LIST(g_host_hotkeys)
END_HOTKEY_LIST()