diff --git a/data/resources/images/flags/Finish.svg b/data/resources/images/flags/Finnish.svg
similarity index 100%
rename from data/resources/images/flags/Finish.svg
rename to data/resources/images/flags/Finnish.svg
diff --git a/data/resources/images/flags/Russian.svg b/data/resources/images/flags/Russian.svg
new file mode 100644
index 000000000..553014621
--- /dev/null
+++ b/data/resources/images/flags/Russian.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/Turkish.svg b/data/resources/images/flags/Turkish.svg
new file mode 100644
index 000000000..0ce226481
--- /dev/null
+++ b/data/resources/images/flags/Turkish.svg
@@ -0,0 +1,5 @@
+
+ Flag of Turkey
+
+
+
\ No newline at end of file
diff --git a/data/resources/images/flags/sources.txt b/data/resources/images/flags/sources.txt
index 3f2dc4f9f..9d41adee9 100644
--- a/data/resources/images/flags/sources.txt
+++ b/data/resources/images/flags/sources.txt
@@ -4,7 +4,7 @@ Czech.svg: https://commons.wikimedia.org/wiki/Flag_of_the_Czech_Republic.svg
Danish.svg: https://commons.wikimedia.org/wiki/Flag_of_Denmark.svg
Dutch.svg: https://commons.wikimedia.org/wiki/Flag_of_the_Netherlands.svg
English.svg: https://commons.wikimedia.org/wiki/Flag_of_the_United_Kingdom_(1-2).svg
-Finish.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Finland.svg
+Finnish.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Finland.svg
French.svg: https://commons.wikimedia.org/wiki/File:Flag_of_France.svg
German.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Germany.svg
Greek.svg: https://commons.wikimedia.org/wiki/Flag_of_Greece.svg
@@ -17,4 +17,5 @@ Polish.svg: https://commons.wikimedia.org/wiki/Flag_of_Poland.svg
Portuguese.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Portugal.svg
Spanish.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Spain.svg
Swedish.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Sweden.svg
+Russian.svg: https://commons.wikimedia.org/wiki/File:Flag_of_Russia.svg
Other.svg: https://en.wikipedia.org/wiki/File:Flag_with_question_mark.svg
diff --git a/data/resources/thirdparty.html b/data/resources/thirdparty.html
index 231d40d31..6b5b5e307 100644
--- a/data/resources/thirdparty.html
+++ b/data/resources/thirdparty.html
@@ -2563,6 +2563,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+
Texture Decompression Routines
+
+Copyright (C) 2009 Benjamin Dobell, Glass Echidna
+Copyright (C) 2012 - 2022, Matthäus G. "Anteru" Chajdas (https://anteru.net)
+Copyright (C) 2020 Richard Geldreich, Jr.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
+of the Software, and to permit persons to whom the Software is furnished to do
+so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+
Some shaders provided with the application are sourced from:
https://github.com/Matsilagi/RSRetroArch/
diff --git a/src/common/file_system.cpp b/src/common/file_system.cpp
index 279fdcdee..d195cbca4 100644
--- a/src/common/file_system.cpp
+++ b/src/common/file_system.cpp
@@ -608,14 +608,14 @@ std::string Path::ReplaceExtension(std::string_view path, std::string_view new_e
return ret;
}
-static std::string_view::size_type GetLastSeperatorPosition(std::string_view filename, bool include_separator)
+static std::string_view::size_type GetLastSeperatorPosition(std::string_view path, bool include_separator)
{
- std::string_view::size_type last_separator = filename.rfind('/');
+ std::string_view::size_type last_separator = path.rfind('/');
if (include_separator && last_separator != std::string_view::npos)
last_separator++;
#if defined(_WIN32)
- std::string_view::size_type other_last_separator = filename.rfind('\\');
+ std::string_view::size_type other_last_separator = path.rfind('\\');
if (other_last_separator != std::string_view::npos)
{
if (include_separator)
@@ -845,13 +845,13 @@ std::vector FileSystem::GetRootDirectoryList()
return results;
}
-std::string Path::BuildRelativePath(std::string_view filename, std::string_view new_filename)
+std::string Path::BuildRelativePath(std::string_view path, std::string_view new_filename)
{
std::string new_string;
- std::string_view::size_type pos = GetLastSeperatorPosition(filename, true);
+ std::string_view::size_type pos = GetLastSeperatorPosition(path, true);
if (pos != std::string_view::npos)
- new_string.assign(filename, 0, pos);
+ new_string.assign(path, 0, pos);
new_string.append(new_filename);
return new_string;
}
@@ -873,10 +873,10 @@ std::string Path::Combine(std::string_view base, std::string_view next)
return ret;
}
-std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error* error)
+std::FILE* FileSystem::OpenCFile(const char* path, const char* mode, Error* error)
{
#ifdef _WIN32
- const std::wstring wfilename = GetWin32Path(filename);
+ const std::wstring wfilename = GetWin32Path(path);
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
if (!wfilename.empty() && !wmode.empty())
{
@@ -892,7 +892,7 @@ std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error*
}
std::FILE* fp;
- const errno_t err = fopen_s(&fp, filename, mode);
+ const errno_t err = fopen_s(&fp, path, mode);
if (err != 0)
{
Error::SetErrno(error, err);
@@ -901,24 +901,24 @@ std::FILE* FileSystem::OpenCFile(const char* filename, const char* mode, Error*
return fp;
#else
- std::FILE* fp = std::fopen(filename, mode);
+ std::FILE* fp = std::fopen(path, mode);
if (!fp)
Error::SetErrno(error, errno);
return fp;
#endif
}
-std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* filename, s32 retry_ms, Error* error /*= nullptr*/)
+std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* path, s32 retry_ms, Error* error /*= nullptr*/)
{
#ifdef _WIN32
- const std::wstring wfilename = GetWin32Path(filename);
- if (wfilename.empty())
+ const std::wstring wpath = GetWin32Path(path);
+ if (wpath.empty())
{
Error::SetStringView(error, "Invalid path.");
return nullptr;
}
- HANDLE file = CreateFileW(wfilename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
+ HANDLE file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
// if there's a sharing violation, keep retrying
if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_SHARING_VIOLATION && retry_ms >= 0)
@@ -927,7 +927,7 @@ std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* filename, s32 retry
while (retry_ms == 0 || timer.GetTimeMilliseconds() <= retry_ms)
{
Sleep(1);
- file = CreateFileW(wfilename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
+ file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
if (file != INVALID_HANDLE_VALUE || GetLastError() != ERROR_SHARING_VIOLATION)
break;
}
@@ -936,11 +936,11 @@ std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* filename, s32 retry
if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_NOT_FOUND)
{
// try creating it
- file = CreateFileW(wfilename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, NULL);
+ file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, CREATE_NEW, 0, NULL);
if (file == INVALID_HANDLE_VALUE && GetLastError() == ERROR_FILE_EXISTS)
{
// someone else beat us in the race, try again with existing.
- file = CreateFileW(wfilename.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
+ file = CreateFileW(wpath.c_str(), GENERIC_READ | GENERIC_WRITE, 0, nullptr, OPEN_EXISTING, 0, NULL);
}
}
@@ -970,7 +970,7 @@ std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* filename, s32 retry
return cfile;
#else
- std::FILE* fp = std::fopen(filename, "r+b");
+ std::FILE* fp = std::fopen(path, "r+b");
if (fp)
return fp;
@@ -982,13 +982,13 @@ std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* filename, s32 retry
}
// try again, but create the file. mode "x" exists on all platforms.
- fp = std::fopen(filename, "w+bx");
+ fp = std::fopen(path, "w+bx");
if (fp)
return fp;
// if it already exists, someone else beat us in the race. try again with existing.
if (errno == EEXIST)
- fp = std::fopen(filename, "r+b");
+ fp = std::fopen(path, "r+b");
if (!fp)
{
Error::SetErrno(error, errno);
@@ -999,28 +999,28 @@ std::FILE* FileSystem::OpenExistingOrCreateCFile(const char* filename, s32 retry
#endif
}
-int FileSystem::OpenFDFile(const char* filename, int flags, int mode, Error* error)
+int FileSystem::OpenFDFile(const char* path, int flags, int mode, Error* error)
{
#ifdef _WIN32
- const std::wstring wfilename(GetWin32Path(filename));
- if (!wfilename.empty())
- return _wopen(wfilename.c_str(), flags, mode);
+ const std::wstring wpath = GetWin32Path(path);
+ if (!wpath.empty())
+ return _wopen(wpath.c_str(), flags, mode);
return -1;
#else
- const int fd = open(filename, flags, mode);
+ const int fd = open(path, flags, mode);
if (fd < 0)
Error::SetErrno(error, errno);
return fd;
#endif
}
-std::FILE* FileSystem::OpenSharedCFile(const char* filename, const char* mode, FileShareMode share_mode, Error* error)
+std::FILE* FileSystem::OpenSharedCFile(const char* path, const char* mode, FileShareMode share_mode, Error* error)
{
#ifdef _WIN32
- const std::wstring wfilename = GetWin32Path(filename);
+ const std::wstring wpath = GetWin32Path(path);
const std::wstring wmode = StringUtil::UTF8StringToWideString(mode);
- if (wfilename.empty() || wmode.empty())
+ if (wpath.empty() || wmode.empty())
return nullptr;
int share_flags = 0;
@@ -1041,14 +1041,14 @@ std::FILE* FileSystem::OpenSharedCFile(const char* filename, const char* mode, F
break;
}
- std::FILE* fp = _wfsopen(wfilename.c_str(), wmode.c_str(), share_flags);
+ std::FILE* fp = _wfsopen(wpath.c_str(), wmode.c_str(), share_flags);
if (fp)
return fp;
Error::SetErrno(error, errno);
return nullptr;
#else
- std::FILE* fp = std::fopen(filename, mode);
+ std::FILE* fp = std::fopen(path, mode);
if (!fp)
Error::SetErrno(error, errno);
return fp;
@@ -1165,8 +1165,8 @@ std::string Path::CreateFileURL(std::string_view path)
return ret;
}
-FileSystem::AtomicRenamedFileDeleter::AtomicRenamedFileDeleter(std::string temp_filename, std::string final_filename)
- : m_temp_filename(std::move(temp_filename)), m_final_filename(std::move(final_filename))
+FileSystem::AtomicRenamedFileDeleter::AtomicRenamedFileDeleter(std::string temp_path, std::string final_path)
+ : m_temp_path(std::move(temp_path)), m_final_path(std::move(final_path))
{
}
@@ -1180,11 +1180,11 @@ void FileSystem::AtomicRenamedFileDeleter::operator()(std::FILE* fp)
Error error;
// final filename empty => discarded.
- if (!m_final_filename.empty())
+ if (!m_final_path.empty())
{
if (!commit(fp, &error))
{
- ERROR_LOG("Failed to commit temporary file '{}', discarding. Error was {}.", Path::GetFileName(m_temp_filename),
+ ERROR_LOG("Failed to commit temporary file '{}', discarding. Error was {}.", Path::GetFileName(m_temp_path),
error.GetDescription());
}
@@ -1194,8 +1194,8 @@ void FileSystem::AtomicRenamedFileDeleter::operator()(std::FILE* fp)
// we're discarding the file, don't care if it fails.
std::fclose(fp);
- if (!DeleteFile(m_temp_filename.c_str(), &error))
- ERROR_LOG("Failed to delete temporary file '{}': {}", Path::GetFileName(m_temp_filename), error.GetDescription());
+ if (!DeleteFile(m_temp_path.c_str(), &error))
+ ERROR_LOG("Failed to delete temporary file '{}': {}", Path::GetFileName(m_temp_path), error.GetDescription());
}
bool FileSystem::AtomicRenamedFileDeleter::commit(std::FILE* fp, Error* error)
@@ -1209,38 +1209,38 @@ bool FileSystem::AtomicRenamedFileDeleter::commit(std::FILE* fp, Error* error)
if (std::fclose(fp) != 0)
{
Error::SetErrno(error, "fclose() failed: ", errno);
- m_final_filename.clear();
+ m_final_path.clear();
}
// Should not have been discarded.
- if (!m_final_filename.empty())
+ if (!m_final_path.empty())
{
- return RenamePath(m_temp_filename.c_str(), m_final_filename.c_str(), error);
+ return RenamePath(m_temp_path.c_str(), m_final_path.c_str(), error);
}
else
{
Error::SetStringView(error, "File has already been discarded.");
- return DeleteFile(m_temp_filename.c_str(), error);
+ return DeleteFile(m_temp_path.c_str(), error);
}
}
void FileSystem::AtomicRenamedFileDeleter::discard()
{
- m_final_filename = {};
+ m_final_path = {};
}
-FileSystem::AtomicRenamedFile FileSystem::CreateAtomicRenamedFile(std::string filename, Error* error /*= nullptr*/)
+FileSystem::AtomicRenamedFile FileSystem::CreateAtomicRenamedFile(std::string path, Error* error /*= nullptr*/)
{
- std::string temp_filename;
+ std::string temp_path;
std::FILE* fp = nullptr;
- if (!filename.empty())
+ if (!path.empty())
{
// this is disgusting, but we need null termination, and std::string::data() does not guarantee it.
- const size_t filename_length = filename.length();
- const size_t name_buf_size = filename_length + 8;
+ const size_t path_length = path.length();
+ const size_t name_buf_size = path_length + 8;
std::unique_ptr name_buf = std::make_unique(name_buf_size);
- std::memcpy(name_buf.get(), filename.c_str(), filename_length);
- StringUtil::Strlcpy(name_buf.get() + filename_length, ".XXXXXX", name_buf_size);
+ std::memcpy(name_buf.get(), path.c_str(), path_length);
+ StringUtil::Strlcpy(name_buf.get() + path_length, ".XXXXXX", name_buf_size);
#ifdef _WIN32
const errno_t err = _mktemp_s(name_buf.get(), name_buf_size);
@@ -1267,18 +1267,18 @@ FileSystem::AtomicRenamedFile FileSystem::CreateAtomicRenamedFile(std::string fi
#endif
if (fp)
- temp_filename.assign(name_buf.get(), name_buf_size - 1);
+ temp_path.assign(name_buf.get(), name_buf_size - 1);
else
- filename.clear();
+ path.clear();
}
- return AtomicRenamedFile(fp, AtomicRenamedFileDeleter(std::move(temp_filename), std::move(filename)));
+ return AtomicRenamedFile(fp, AtomicRenamedFileDeleter(std::move(temp_path), std::move(path)));
}
-bool FileSystem::WriteAtomicRenamedFile(std::string filename, const void* data, size_t data_length,
+bool FileSystem::WriteAtomicRenamedFile(std::string path, const void* data, size_t data_length,
Error* error /*= nullptr*/)
{
- AtomicRenamedFile fp = CreateAtomicRenamedFile(std::move(filename), error);
+ AtomicRenamedFile fp = CreateAtomicRenamedFile(std::move(path), error);
if (!fp)
return false;
@@ -1292,6 +1292,11 @@ bool FileSystem::WriteAtomicRenamedFile(std::string filename, const void* data,
return true;
}
+bool FileSystem::WriteAtomicRenamedFile(std::string path, const std::span data, Error* error /* = nullptr */)
+{
+ return WriteAtomicRenamedFile(std::move(path), data.empty() ? nullptr : data.data(), data.size(), error);
+}
+
void FileSystem::DiscardAtomicRenamedFile(AtomicRenamedFile& file)
{
file.get_deleter().discard();
@@ -1306,21 +1311,20 @@ bool FileSystem::CommitAtomicRenamedFile(AtomicRenamedFile& file, Error* error)
return false;
}
-FileSystem::ManagedCFilePtr FileSystem::OpenManagedCFile(const char* filename, const char* mode, Error* error)
+FileSystem::ManagedCFilePtr FileSystem::OpenManagedCFile(const char* path, const char* mode, Error* error)
{
- return ManagedCFilePtr(OpenCFile(filename, mode, error));
+ return ManagedCFilePtr(OpenCFile(path, mode, error));
}
-FileSystem::ManagedCFilePtr FileSystem::OpenExistingOrCreateManagedCFile(const char* filename, s32 retry_ms,
- Error* error)
+FileSystem::ManagedCFilePtr FileSystem::OpenExistingOrCreateManagedCFile(const char* path, s32 retry_ms, Error* error)
{
- return ManagedCFilePtr(OpenExistingOrCreateCFile(filename, retry_ms, error));
+ return ManagedCFilePtr(OpenExistingOrCreateCFile(path, retry_ms, error));
}
-FileSystem::ManagedCFilePtr FileSystem::OpenManagedSharedCFile(const char* filename, const char* mode,
+FileSystem::ManagedCFilePtr FileSystem::OpenManagedSharedCFile(const char* path, const char* mode,
FileShareMode share_mode, Error* error)
{
- return ManagedCFilePtr(OpenSharedCFile(filename, mode, share_mode, error));
+ return ManagedCFilePtr(OpenSharedCFile(path, mode, share_mode, error));
}
int FileSystem::FSeek64(std::FILE* fp, s64 offset, int whence)
@@ -1443,20 +1447,20 @@ bool FileSystem::FTruncate64(std::FILE* fp, s64 size, Error* error)
#endif
}
-s64 FileSystem::GetPathFileSize(const char* Path)
+s64 FileSystem::GetPathFileSize(const char* path)
{
FILESYSTEM_STAT_DATA sd;
- if (!StatFile(Path, &sd))
+ if (!StatFile(path, &sd))
return -1;
return sd.Size;
}
-std::optional> FileSystem::ReadBinaryFile(const char* filename, Error* error)
+std::optional> FileSystem::ReadBinaryFile(const char* path, Error* error)
{
std::optional> ret;
- ManagedCFilePtr fp = OpenManagedCFile(filename, "rb", error);
+ ManagedCFilePtr fp = OpenManagedCFile(path, "rb", error);
if (!fp)
return ret;
@@ -1506,11 +1510,11 @@ std::optional> FileSystem::ReadBinaryFile(std::FILE* fp, Er
return ret;
}
-std::optional FileSystem::ReadFileToString(const char* filename, Error* error)
+std::optional FileSystem::ReadFileToString(const char* path, Error* error)
{
std::optional ret;
- ManagedCFilePtr fp = OpenManagedCFile(filename, "rb", error);
+ ManagedCFilePtr fp = OpenManagedCFile(path, "rb", error);
if (!fp)
return ret;
@@ -1562,9 +1566,9 @@ std::optional FileSystem::ReadFileToString(std::FILE* fp, Error* er
return ret;
}
-bool FileSystem::WriteBinaryFile(const char* filename, const void* data, size_t data_length, Error* error)
+bool FileSystem::WriteBinaryFile(const char* path, const void* data, size_t data_length, Error* error)
{
- ManagedCFilePtr fp = OpenManagedCFile(filename, "wb", error);
+ ManagedCFilePtr fp = OpenManagedCFile(path, "wb", error);
if (!fp)
return false;
@@ -1577,9 +1581,14 @@ bool FileSystem::WriteBinaryFile(const char* filename, const void* data, size_t
return true;
}
-bool FileSystem::WriteStringToFile(const char* filename, std::string_view sv, Error* error)
+bool FileSystem::WriteBinaryFile(const char* path, const std::span data, Error* error /*= nullptr*/)
{
- ManagedCFilePtr fp = OpenManagedCFile(filename, "wb", error);
+ return WriteBinaryFile(path, data.empty() ? nullptr : data.data(), data.size(), error);
+}
+
+bool FileSystem::WriteStringToFile(const char* path, std::string_view sv, Error* error)
+{
+ ManagedCFilePtr fp = OpenManagedCFile(path, "wb", error);
if (!fp)
return false;
@@ -1865,12 +1874,15 @@ static void TranslateStat64(struct stat* st, const struct _stat64& st64)
st->st_ctime = static_cast(st64.st_ctime);
}
-bool FileSystem::StatFile(const char* path, struct stat* st)
+bool FileSystem::StatFile(const char* path, struct stat* st, Error* error)
{
// convert to wide string
const std::wstring wpath = GetWin32Path(path);
- if (wpath.empty())
+ if (wpath.empty()) [[unlikely]]
+ {
+ Error::SetStringView(error, "Path is empty.");
return false;
+ }
struct _stat64 st64;
if (_wstati64(wpath.c_str(), &st64) != 0)
@@ -1880,31 +1892,43 @@ bool FileSystem::StatFile(const char* path, struct stat* st)
return true;
}
-bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
+bool FileSystem::StatFile(std::FILE* fp, struct stat* st, Error* error)
{
const int fd = _fileno(fp);
if (fd < 0)
+ {
+ Error::SetErrno(error, "_fileno() failed: ", errno);
return false;
+ }
struct _stat64 st64;
if (_fstati64(fd, &st64) != 0)
+ {
+ Error::SetErrno(error, "_fstati64() failed: ", errno);
return false;
+ }
TranslateStat64(st, st64);
return true;
}
-bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
+bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error)
{
// convert to wide string
const std::wstring wpath = GetWin32Path(path);
- if (wpath.empty())
+ if (wpath.empty()) [[unlikely]]
+ {
+ Error::SetStringView(error, "Path is empty.");
return false;
+ }
// determine attributes for the path. if it's a directory, things have to be handled differently..
DWORD fileAttributes = GetFileAttributesW(wpath.c_str());
if (fileAttributes == INVALID_FILE_ATTRIBUTES)
+ {
+ Error::SetWin32(error, "GetFileAttributesW() failed: ", GetLastError());
return false;
+ }
// test if it is a directory
HANDLE hFile;
@@ -1921,12 +1945,16 @@ bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
// createfile succeded?
if (hFile == INVALID_HANDLE_VALUE)
+ {
+ Error::SetWin32(error, "CreateFileW() failed: ", GetLastError());
return false;
+ }
// use GetFileInformationByHandle
BY_HANDLE_FILE_INFORMATION bhfi;
if (GetFileInformationByHandle(hFile, &bhfi) == FALSE)
{
+ Error::SetWin32(error, "GetFileInformationByHandle() failed: ", GetLastError());
CloseHandle(hFile);
return false;
}
@@ -1942,15 +1970,21 @@ bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
return true;
}
-bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd)
+bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error)
{
const int fd = _fileno(fp);
if (fd < 0)
+ {
+ Error::SetErrno(error, "_fileno() failed: ", errno);
return false;
+ }
struct _stat64 st;
if (_fstati64(fd, &st) != 0)
+ {
+ Error::SetErrno(error, "_fstati64() failed: ", errno);
return false;
+ }
// parse attributes
sd->CreationTime = st.st_ctime;
@@ -2411,26 +2445,44 @@ bool FileSystem::FindFiles(const char* path, const char* pattern, u32 flags, Fin
return true;
}
-bool FileSystem::StatFile(const char* path, struct stat* st)
+bool FileSystem::StatFile(const char* path, struct stat* st, Error* error)
{
- return stat(path, st) == 0;
+ if (stat(path, st) != 0)
+ {
+ Error::SetErrno(error, "stat() failed: ", errno);
+ return false;
+ }
+
+ return true;
}
-bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
+bool FileSystem::StatFile(std::FILE* fp, struct stat* st, Error* error)
{
const int fd = fileno(fp);
if (fd < 0)
+ {
+ Error::SetErrno(error, "fileno() failed: ", errno);
return false;
+ }
- return fstat(fd, st) == 0;
+ if (fstat(fd, st) != 0)
+ {
+ Error::SetErrno(error, "fstat() failed: ", errno);
+ return false;
+ }
+
+ return true;
}
-bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
+bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error)
{
// stat file
struct stat sysStatData;
if (stat(path, &sysStatData) < 0)
+ {
+ Error::SetErrno(error, "stat() failed: ", errno);
return false;
+ }
// parse attributes
sd->CreationTime = sysStatData.st_ctime;
@@ -2442,16 +2494,22 @@ bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
return true;
}
-bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd)
+bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error)
{
const int fd = fileno(fp);
if (fd < 0)
+ {
+ Error::SetErrno(error, "fileno() failed: ", errno);
return false;
+ }
// stat file
struct stat sysStatData;
- if (fstat(fd, &sysStatData) < 0)
+ if (fstat(fd, &sysStatData) != 0)
+ {
+ Error::SetErrno(error, "stat() failed: ", errno);
return false;
+ }
// parse attributes
sd->CreationTime = sysStatData.st_ctime;
@@ -2636,13 +2694,13 @@ bool FileSystem::DeleteDirectory(const char* path)
std::string FileSystem::GetProgramPath()
{
#if defined(__linux__)
- static const char* exeFileName = "/proc/self/exe";
+ static const char* exe_path = "/proc/self/exe";
int curSize = PATH_MAX;
char* buffer = static_cast(std::realloc(nullptr, curSize));
for (;;)
{
- int len = readlink(exeFileName, buffer, curSize);
+ int len = readlink(exe_path, buffer, curSize);
if (len < 0)
{
std::free(buffer);
diff --git a/src/common/file_system.h b/src/common/file_system.h
index 3396f1307..7003001ea 100644
--- a/src/common/file_system.h
+++ b/src/common/file_system.h
@@ -10,6 +10,7 @@
#include
#include
#include
+#include
#include
#include
#include
@@ -65,10 +66,10 @@ std::vector GetRootDirectoryList();
bool FindFiles(const char* path, const char* pattern, u32 flags, FindResultsArray* results);
/// Stat file
-bool StatFile(const char* path, struct stat* st);
-bool StatFile(std::FILE* fp, struct stat* st);
-bool StatFile(const char* path, FILESYSTEM_STAT_DATA* pStatData);
-bool StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* pStatData);
+bool StatFile(const char* path, struct stat* st, Error* error = nullptr);
+bool StatFile(std::FILE* fp, struct stat* st, Error* error = nullptr);
+bool StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error = nullptr);
+bool StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error = nullptr);
s64 GetPathFileSize(const char* path);
/// File exists?
@@ -99,14 +100,14 @@ struct FileDeleter
/// open files
using ManagedCFilePtr = std::unique_ptr;
-ManagedCFilePtr OpenManagedCFile(const char* filename, const char* mode, Error* error = nullptr);
-std::FILE* OpenCFile(const char* filename, const char* mode, Error* error = nullptr);
+ManagedCFilePtr OpenManagedCFile(const char* path, const char* mode, Error* error = nullptr);
+std::FILE* OpenCFile(const char* path, const char* mode, Error* error = nullptr);
/// Atomically opens a file in read/write mode, and if the file does not exist, creates it.
/// On Windows, if retry_ms is positive, this function will retry opening the file for this
/// number of milliseconds. NOTE: The file is opened in binary mode.
-std::FILE* OpenExistingOrCreateCFile(const char* filename, s32 retry_ms = -1, Error* error = nullptr);
-ManagedCFilePtr OpenExistingOrCreateManagedCFile(const char* filename, s32 retry_ms = -1, Error* error = nullptr);
+std::FILE* OpenExistingOrCreateCFile(const char* path, s32 retry_ms = -1, Error* error = nullptr);
+ManagedCFilePtr OpenExistingOrCreateManagedCFile(const char* path, s32 retry_ms = -1, Error* error = nullptr);
int FSeek64(std::FILE* fp, s64 offset, int whence);
bool FSeek64(std::FILE* fp, s64 offset, int whence, Error* error);
@@ -114,7 +115,7 @@ s64 FTell64(std::FILE* fp);
s64 FSize64(std::FILE* fp, Error* error = nullptr);
bool FTruncate64(std::FILE* fp, s64 size, Error* error = nullptr);
-int OpenFDFile(const char* filename, int flags, int mode, Error* error = nullptr);
+int OpenFDFile(const char* path, int flags, int mode, Error* error = nullptr);
/// Sharing modes for OpenSharedCFile().
enum class FileShareMode
@@ -127,15 +128,15 @@ enum class FileShareMode
/// Opens a file in shareable mode (where other processes can access it concurrently).
/// Only has an effect on Windows systems.
-ManagedCFilePtr OpenManagedSharedCFile(const char* filename, const char* mode, FileShareMode share_mode,
+ManagedCFilePtr OpenManagedSharedCFile(const char* path, const char* mode, FileShareMode share_mode,
Error* error = nullptr);
-std::FILE* OpenSharedCFile(const char* filename, const char* mode, FileShareMode share_mode, Error* error = nullptr);
+std::FILE* OpenSharedCFile(const char* path, const char* mode, FileShareMode share_mode, Error* error = nullptr);
/// Atomically-updated file creation.
class AtomicRenamedFileDeleter
{
public:
- AtomicRenamedFileDeleter(std::string temp_filename, std::string final_filename);
+ AtomicRenamedFileDeleter(std::string temp_path, std::string final_path);
~AtomicRenamedFileDeleter();
void operator()(std::FILE* fp);
@@ -143,12 +144,13 @@ public:
void discard();
private:
- std::string m_temp_filename;
- std::string m_final_filename;
+ std::string m_temp_path;
+ std::string m_final_path;
};
using AtomicRenamedFile = std::unique_ptr;
-AtomicRenamedFile CreateAtomicRenamedFile(std::string filename, Error* error = nullptr);
-bool WriteAtomicRenamedFile(std::string filename, const void* data, size_t data_length, Error* error = nullptr);
+AtomicRenamedFile CreateAtomicRenamedFile(std::string path, Error* error = nullptr);
+bool WriteAtomicRenamedFile(std::string path, const void* data, size_t data_length, Error* error = nullptr);
+bool WriteAtomicRenamedFile(std::string path, const std::span data, Error* error = nullptr);
bool CommitAtomicRenamedFile(AtomicRenamedFile& file, Error* error);
void DiscardAtomicRenamedFile(AtomicRenamedFile& file);
@@ -166,12 +168,13 @@ private:
};
#endif
-std::optional> ReadBinaryFile(const char* filename, Error* error = nullptr);
+std::optional> ReadBinaryFile(const char* path, Error* error = nullptr);
std::optional> ReadBinaryFile(std::FILE* fp, Error* error = nullptr);
-std::optional ReadFileToString(const char* filename, Error* error = nullptr);
+std::optional ReadFileToString(const char* path, Error* error = nullptr);
std::optional ReadFileToString(std::FILE* fp, Error* error = nullptr);
-bool WriteBinaryFile(const char* filename, const void* data, size_t data_length, Error* error = nullptr);
-bool WriteStringToFile(const char* filename, std::string_view sv, Error* error = nullptr);
+bool WriteBinaryFile(const char* path, const void* data, size_t data_length, Error* error = nullptr);
+bool WriteBinaryFile(const char* path, const std::span data, Error* error = nullptr);
+bool WriteStringToFile(const char* path, std::string_view sv, Error* error = nullptr);
/// creates a directory in the local filesystem
/// if the directory already exists, the return value will be true.
diff --git a/src/common/log_channels.h b/src/common/log_channels.h
index 305f06490..b075a8764 100644
--- a/src/common/log_channels.h
+++ b/src/common/log_channels.h
@@ -31,7 +31,6 @@
X(GPUDevice) \
X(GPUDump) \
X(GPUShaderCache) \
- X(GPUTexture) \
X(GPUTextureCache) \
X(GPU_HW) \
X(GPU_SW) \
diff --git a/src/common/timer.cpp b/src/common/timer.cpp
index bdcb20cde..6b74097d8 100644
--- a/src/common/timer.cpp
+++ b/src/common/timer.cpp
@@ -395,7 +395,7 @@ void Timer::NanoSleep(std::uint64_t ns)
// Round down to the next millisecond.
usleep(static_cast((ns / 1000000) * 1000));
#else
- const struct timespec ts = {0, static_cast(ns)};
+ const struct timespec ts = {static_cast(ns / 1000000000ULL), static_cast(ns % 1000000000ULL)};
nanosleep(&ts, nullptr);
#endif
}
diff --git a/src/core/achievements.cpp b/src/core/achievements.cpp
index 1ccadad2d..b8d75c51f 100644
--- a/src/core/achievements.cpp
+++ b/src/core/achievements.cpp
@@ -1969,6 +1969,15 @@ std::string Achievements::GetLoggedInUserBadgePath()
return badge_path;
}
+u32 Achievements::GetPauseThrottleFrames()
+{
+ if (!IsActive() || !IsHardcoreModeActive() || IsUsingRAIntegration())
+ return 0;
+
+ u32 frames_remaining = 0;
+ return rc_client_can_pause(s_client, &frames_remaining) ? 0 : frames_remaining;
+}
+
void Achievements::Logout()
{
if (IsActive())
diff --git a/src/core/achievements.h b/src/core/achievements.h
index a572f5e60..7a2b5dc89 100644
--- a/src/core/achievements.h
+++ b/src/core/achievements.h
@@ -129,6 +129,9 @@ const char* GetLoggedInUserName();
/// Should be called with the lock held.
std::string GetLoggedInUserBadgePath();
+/// Returns 0 if pausing is allowed, otherwise the number of frames until pausing is allowed.
+u32 GetPauseThrottleFrames();
+
/// Clears all cached state used to render the UI.
void ClearUIState();
diff --git a/src/core/cdrom.cpp b/src/core/cdrom.cpp
index b9e423ab8..4cec6c0f2 100644
--- a/src/core/cdrom.cpp
+++ b/src/core/cdrom.cpp
@@ -263,7 +263,7 @@ union XA_ADPCMBlockHeader
u8 bits;
BitField shift;
- BitField filter;
+ BitField filter;
// For both 4bit and 8bit ADPCM, reserved shift values 13..15 will act same as shift=9).
u8 GetShift() const
@@ -3415,11 +3415,8 @@ s16 CDROM::SaturateVolume(s32 volume)
template
void CDROM::DecodeXAADPCMChunks(const u8* chunk_ptr, s16* samples)
{
- static constexpr std::array s_xa_adpcm_filter_table_pos = {
- {0, 60, 115, 98, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
-
- static constexpr std::array s_xa_adpcm_filter_table_neg = {
- {0, 0, -52, -55, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
+ static constexpr std::array filter_table_pos = {{0, 60, 115, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
+ static constexpr std::array filter_table_neg = {{0, 0, -52, -55, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
// The data layout is annoying here. Each word of data is interleaved with the other blocks, requiring multiple
// passes to decode the whole chunk.
@@ -3440,8 +3437,8 @@ void CDROM::DecodeXAADPCMChunks(const u8* chunk_ptr, s16* samples)
const XA_ADPCMBlockHeader block_header{headers_ptr[block]};
const u8 shift = block_header.GetShift();
const u8 filter = block_header.GetFilter();
- const s32 filter_pos = s_xa_adpcm_filter_table_pos[filter];
- const s32 filter_neg = s_xa_adpcm_filter_table_neg[filter];
+ const s32 filter_pos = filter_table_pos[filter];
+ const s32 filter_neg = filter_table_neg[filter];
s16* out_samples_ptr =
IS_STEREO ? &samples[(block / 2) * (WORDS_PER_BLOCK * 2) + (block % 2)] : &samples[block * WORDS_PER_BLOCK];
@@ -3460,7 +3457,7 @@ void CDROM::DecodeXAADPCMChunks(const u8* chunk_ptr, s16* samples)
// mix in previous values
s32* prev = IS_STEREO ? &s_state.xa_last_samples[(block & 1) * 2] : &s_state.xa_last_samples[0];
const s32 interp_sample = std::clamp(
- static_cast(sample) + ((prev[0] * filter_pos) >> 6) + ((prev[1] * filter_neg) >> 6), -32767, 32768);
+ static_cast(sample) + ((prev[0] * filter_pos) >> 6) + ((prev[1] * filter_neg) >> 6), -32768, 32767);
// update previous values
prev[1] = prev[0];
diff --git a/src/core/fullscreen_ui.cpp b/src/core/fullscreen_ui.cpp
index 16e189aac..6b3c97e2c 100644
--- a/src/core/fullscreen_ui.cpp
+++ b/src/core/fullscreen_ui.cpp
@@ -112,7 +112,6 @@ using ImGuiFullscreen::BeginNavBar;
using ImGuiFullscreen::CenterImage;
using ImGuiFullscreen::CloseChoiceDialog;
using ImGuiFullscreen::CloseFileSelector;
-using ImGuiFullscreen::CreateTextureFromImage;
using ImGuiFullscreen::DefaultActiveButton;
using ImGuiFullscreen::DrawShadowedText;
using ImGuiFullscreen::EndFullscreenColumns;
@@ -4324,7 +4323,7 @@ void FullscreenUI::DrawGraphicsSettingsPage()
DrawEnumSetting(bsi, FSUI_ICONSTR(ICON_FA_CROP_ALT, "Crop Mode"),
FSUI_CSTR("Determines how much of the area typically not visible on a consumer TV set to crop/hide."),
"Display", "CropMode", Settings::DEFAULT_DISPLAY_CROP_MODE, &Settings::ParseDisplayCropMode,
- &Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, DisplayCropMode::Count);
+ &Settings::GetDisplayCropModeName, &Settings::GetDisplayCropModeDisplayName, DisplayCropMode::MaxCount);
DrawEnumSetting(
bsi, FSUI_ICONSTR(ICON_FA_EXPAND, "Scaling"),
@@ -5966,7 +5965,7 @@ bool FullscreenUI::InitializeSaveStateListEntryFromPath(SaveStateListEntry* li,
li->path = std::move(path);
li->global = global;
if (ssi->screenshot.IsValid())
- li->preview_texture = CreateTextureFromImage(ssi->screenshot);
+ li->preview_texture = g_gpu_device->FetchAndUploadTextureImage(ssi->screenshot);
return true;
}
@@ -5994,7 +5993,7 @@ u32 FullscreenUI::PopulateSaveStateListEntries(const std::string& title, const s
li.title = FSUI_STR("Undo Load State");
li.summary = FSUI_STR("Restores the state of the system prior to the last state loaded.");
if (ssi->screenshot.IsValid())
- li.preview_texture = CreateTextureFromImage(ssi->screenshot);
+ li.preview_texture = g_gpu_device->FetchAndUploadTextureImage(ssi->screenshot);
s_save_state_selector_slots.push_back(std::move(li));
}
}
@@ -6883,7 +6882,7 @@ void FullscreenUI::DrawGameList(const ImVec2& heading_size)
const bool display_as_language = (selected_entry->dbentry && selected_entry->dbentry->HasAnyLanguage());
ImGui::TextUnformatted(display_as_language ? FSUI_CSTR("Language: ") : FSUI_CSTR("Region: "));
ImGui::SameLine();
- ImGui::Image(GetCachedTexture(selected_entry->GetLanguageIconFileName(), 23, 16), LayoutScale(23.0f, 16.0f));
+ ImGui::Image(GetCachedTexture(selected_entry->GetLanguageIconName(), 23, 16), LayoutScale(23.0f, 16.0f));
ImGui::SameLine();
if (display_as_language)
{
diff --git a/src/core/game_database.cpp b/src/core/game_database.cpp
index 4e07a01b0..6d81b5da9 100644
--- a/src/core/game_database.cpp
+++ b/src/core/game_database.cpp
@@ -21,6 +21,7 @@
#include "ryml.hpp"
+#include
#include
#include
#include
@@ -138,9 +139,9 @@ static constexpr const std::array(Trait::MaxCou
}};
static constexpr std::array(Language::MaxCount)> s_language_names = {{
- "Catalan", "Chinese", "Czech", "Danish", "Dutch", "English", "Finnish",
- "French", "German", "Greek", "Hebrew", "Iranian", "Italian", "Japanese",
- "Korean", "Norwegian", "Polish", "Portuguese", "Russian", "Spanish", "Swedish",
+ "Catalan", "Chinese", "Czech", "Danish", "Dutch", "English", "Finnish", "French",
+ "German", "Greek", "Hebrew", "Iranian", "Italian", "Japanese", "Korean", "Norwegian",
+ "Polish", "Portuguese", "Russian", "Spanish", "Swedish", "Turkish",
}};
static constexpr const char* GAMEDB_YAML_FILENAME = "gamedb.yaml";
@@ -312,6 +313,24 @@ std::optional GameDatabase::ParseLanguageName(std::strin
return std::nullopt;
}
+TinyString GameDatabase::GetLanguageFlagResourceName(std::string_view language_name)
+{
+ return TinyString::from_format("images/flags/{}.svg", language_name);
+}
+
+std::string_view GameDatabase::Entry::GetLanguageFlagName(DiscRegion region) const
+{
+ // If there's only one language, this is the flag we want to use.
+ // Except if it's English, then we want to use the disc region's flag.
+ std::string_view ret;
+ if (languages.count() == 1 && !languages.test(static_cast(GameDatabase::Language::English)))
+ ret = GameDatabase::GetLanguageName(static_cast(std::countr_zero(languages.to_ulong())));
+ else
+ ret = Settings::GetDiscRegionName(region);
+
+ return ret;
+}
+
SmallString GameDatabase::Entry::GetLanguagesString() const
{
SmallString ret;
diff --git a/src/core/game_database.h b/src/core/game_database.h
index b4b5d4753..7ac805e86 100644
--- a/src/core/game_database.h
+++ b/src/core/game_database.h
@@ -88,6 +88,7 @@ enum class Language : u8
Russian,
Spanish,
Swedish,
+ Turkish,
MaxCount,
};
@@ -129,8 +130,9 @@ struct Entry
ALWAYS_INLINE bool HasTrait(Trait trait) const { return traits[static_cast(trait)]; }
ALWAYS_INLINE bool HasLanguage(Language language) const { return languages.test(static_cast(language)); }
- ALWAYS_INLINE bool HasAnyLanguage() const { return !languages.none(); }
+ ALWAYS_INLINE bool HasAnyLanguage() const { return languages.any(); }
+ std::string_view GetLanguageFlagName(DiscRegion region) const;
SmallString GetLanguagesString() const;
void ApplySettings(Settings& settings, bool display_osd_messages) const;
@@ -155,6 +157,7 @@ const char* GetCompatibilityRatingDisplayName(CompatibilityRating rating);
const char* GetLanguageName(Language language);
std::optional ParseLanguageName(std::string_view str);
+TinyString GetLanguageFlagResourceName(std::string_view language_name);
/// Map of track hashes for image verification
struct TrackData
diff --git a/src/core/game_list.cpp b/src/core/game_list.cpp
index 3db8ec8ea..773cd457f 100644
--- a/src/core/game_list.cpp
+++ b/src/core/game_list.cpp
@@ -117,6 +117,8 @@ static PlayedTimeEntry UpdatePlayedTimeFile(const std::string& path, const std::
std::time_t add_time);
static std::string GetCustomPropertiesFile();
+static bool PutCustomPropertiesField(INISettingsInterface& ini, const std::string& path, const char* field,
+ const char* value);
static FileSystem::ManagedCFilePtr OpenMemoryCardTimestampCache(bool for_write);
static bool UpdateMemcardTimestampCache(const MemcardTimestampCacheEntry& entry);
@@ -627,6 +629,21 @@ void GameList::ApplyCustomAttributes(const std::string& path, Entry* entry,
WARNING_LOG("Invalid region '{}' in custom attributes for '{}'", custom_region_str.value(), path);
}
}
+ const std::optional custom_language_str =
+ custom_attributes_ini.GetOptionalTinyStringValue(path.c_str(), "Language");
+ if (custom_language_str.has_value())
+ {
+ const std::optional custom_region =
+ GameDatabase::ParseLanguageName(custom_region_str.value());
+ if (custom_region.has_value())
+ {
+ entry->custom_language = custom_region.value();
+ }
+ else
+ {
+ WARNING_LOG("Invalid language '{}' in custom attributes for '{}'", custom_region_str.value(), path);
+ }
+ }
}
std::unique_lock GameList::GetLock()
@@ -990,26 +1007,20 @@ std::string GameList::GetNewCoverImagePathForEntry(const Entry* entry, const cha
std::string_view GameList::Entry::GetLanguageIcon() const
{
- // If there's only one language, this is the flag we want to use.
- // Except if it's English, then we want to use the disc region's flag.
std::string_view ret;
- if (dbentry && dbentry->languages.count() == 1 &&
- !dbentry->languages.test(static_cast(GameDatabase::Language::English)))
- {
- ret = GameDatabase::GetLanguageName(
- static_cast(std::countr_zero(dbentry->languages.to_ulong())));
- }
+ if (custom_language != GameDatabase::Language::MaxCount)
+ ret = GameDatabase::GetLanguageName(custom_language);
+ else if (dbentry)
+ ret = dbentry->GetLanguageFlagName(region);
else
- {
ret = Settings::GetDiscRegionName(region);
- }
return ret;
}
-TinyString GameList::Entry::GetLanguageIconFileName() const
+TinyString GameList::Entry::GetLanguageIconName() const
{
- return TinyString::from_format("images/flags/{}.svg", GetLanguageIcon());
+ return GameDatabase::GetLanguageFlagResourceName(GetLanguageIcon());
}
TinyString GameList::Entry::GetCompatibilityIconFileName() const
@@ -1518,28 +1529,37 @@ std::string GameList::GetCustomPropertiesFile()
return Path::Combine(EmuFolders::DataRoot, "custom_properties.ini");
}
-void GameList::SaveCustomTitleForPath(const std::string& path, const std::string& custom_title)
+bool GameList::PutCustomPropertiesField(INISettingsInterface& ini, const std::string& path, const char* field,
+ const char* value)
{
- INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
- custom_attributes_ini.Load();
+ ini.Load();
- if (!custom_title.empty())
+ if (value && *value != '\0')
{
- custom_attributes_ini.SetStringValue(path.c_str(), "Title", custom_title.c_str());
+ ini.SetStringValue(path.c_str(), field, value);
}
else
{
- custom_attributes_ini.DeleteValue(path.c_str(), "Title");
- custom_attributes_ini.RemoveEmptySections();
+ ini.DeleteValue(path.c_str(), field);
+ ini.RemoveEmptySections();
}
Error error;
- if (!custom_attributes_ini.Save(&error))
+ if (!ini.Save(&error))
{
ERROR_LOG("Failed to save custom attributes: {}", error.GetDescription());
- return;
+ return false;
}
+ return true;
+}
+
+bool GameList::SaveCustomTitleForPath(const std::string& path, const std::string& custom_title)
+{
+ INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
+ if (!PutCustomPropertiesField(custom_attributes_ini, path, "Title", custom_title.c_str()))
+ return false;
+
if (!custom_title.empty())
{
// Can skip the rescan and just update the value directly.
@@ -1556,28 +1576,18 @@ void GameList::SaveCustomTitleForPath(const std::string& path, const std::string
// Let the cache update by rescanning. Only need to do this on deletion, to get the original value.
RescanCustomAttributesForPath(path, custom_attributes_ini);
}
+
+ return true;
}
-void GameList::SaveCustomRegionForPath(const std::string& path, const std::optional custom_region)
+bool GameList::SaveCustomRegionForPath(const std::string& path, const std::optional custom_region)
{
INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
- custom_attributes_ini.Load();
-
- if (custom_region.has_value())
+ if (!PutCustomPropertiesField(custom_attributes_ini, path, "Region",
+ custom_region.has_value() ? Settings::GetDiscRegionName(custom_region.value()) :
+ nullptr))
{
- custom_attributes_ini.SetStringValue(path.c_str(), "Region", Settings::GetDiscRegionName(custom_region.value()));
- }
- else
- {
- custom_attributes_ini.DeleteValue(path.c_str(), "Region");
- custom_attributes_ini.RemoveEmptySections();
- }
-
- Error error;
- if (!custom_attributes_ini.Save(&error))
- {
- ERROR_LOG("Failed to save custom attributes: {}", error.GetDescription());
- return;
+ return false;
}
if (custom_region.has_value())
@@ -1596,6 +1606,28 @@ void GameList::SaveCustomRegionForPath(const std::string& path, const std::optio
// Let the cache update by rescanning. Only need to do this on deletion, to get the original value.
RescanCustomAttributesForPath(path, custom_attributes_ini);
}
+
+ return true;
+}
+
+bool GameList::SaveCustomLanguageForPath(const std::string& path,
+ const std::optional custom_language)
+{
+ INISettingsInterface custom_attributes_ini(GetCustomPropertiesFile());
+ if (!PutCustomPropertiesField(custom_attributes_ini, path, "Language",
+ custom_language.has_value() ? GameDatabase::GetLanguageName(custom_language.value()) :
+ nullptr))
+ {
+ return false;
+ }
+
+ // Don't need to rescan, since there's no original value to restore.
+ auto lock = GetLock();
+ Entry* entry = GetMutableEntryForPath(path);
+ if (entry)
+ entry->custom_language = custom_language.value_or(GameDatabase::Language::MaxCount);
+
+ return true;
}
std::string GameList::GetCustomTitleForPath(const std::string_view path)
@@ -1778,7 +1810,7 @@ std::string GameList::GetGameIconPath(std::string_view serial, std::string_view
INFO_LOG("Extracting memory card icon from {} ({}) to {}", fi.filename, Path::GetFileTitle(memcard_path),
Path::GetFileTitle(ret));
- RGBA8Image image(MemoryCardImage::ICON_WIDTH, MemoryCardImage::ICON_HEIGHT);
+ Image image(MemoryCardImage::ICON_WIDTH, MemoryCardImage::ICON_HEIGHT, ImageFormat::RGBA8);
std::memcpy(image.GetPixels(), &fi.icon_frames.front().pixels,
MemoryCardImage::ICON_WIDTH * MemoryCardImage::ICON_HEIGHT * sizeof(u32));
serial_entry->icon_was_extracted = image.SaveToFile(ret.c_str());
diff --git a/src/core/game_list.h b/src/core/game_list.h
index 1d87635b2..beb697f99 100644
--- a/src/core/game_list.h
+++ b/src/core/game_list.h
@@ -40,6 +40,7 @@ struct Entry
bool disc_set_member = false;
bool has_custom_title = false;
bool has_custom_region = false;
+ GameDatabase::Language custom_language = GameDatabase::Language::MaxCount;
std::string path;
std::string serial;
@@ -57,13 +58,14 @@ struct Entry
std::string_view GetLanguageIcon() const;
- TinyString GetLanguageIconFileName() const;
+ TinyString GetLanguageIconName() const;
TinyString GetCompatibilityIconFileName() const;
TinyString GetReleaseDateString() const;
ALWAYS_INLINE bool IsDisc() const { return (type == EntryType::Disc); }
ALWAYS_INLINE bool IsDiscSet() const { return (type == EntryType::DiscSet); }
+ ALWAYS_INLINE bool HasCustomLanguage() const { return (custom_language != GameDatabase::Language::MaxCount); }
ALWAYS_INLINE EntryType GetSortType() const { return (type == EntryType::DiscSet) ? EntryType::Disc : type; }
};
@@ -128,8 +130,9 @@ bool DownloadCovers(const std::vector& url_templates, bool use_seri
std::function save_callback = {});
// Custom properties support
-void SaveCustomTitleForPath(const std::string& path, const std::string& custom_title);
-void SaveCustomRegionForPath(const std::string& path, const std::optional custom_region);
+bool SaveCustomTitleForPath(const std::string& path, const std::string& custom_title);
+bool SaveCustomRegionForPath(const std::string& path, const std::optional custom_region);
+bool SaveCustomLanguageForPath(const std::string& path, const std::optional custom_language);
std::string GetCustomTitleForPath(const std::string_view path);
std::optional GetCustomRegionForPath(const std::string_view path);
diff --git a/src/core/gpu.cpp b/src/core/gpu.cpp
index 9ec22af4a..c0748bb6e 100644
--- a/src/core/gpu.cpp
+++ b/src/core/gpu.cpp
@@ -74,10 +74,8 @@ static u32 s_active_gpu_cycles_frames = 0;
static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture::Format::RGBA8;
-static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
- u8 quality, bool clear_alpha, bool flip_y, std::vector texture_data,
- u32 texture_data_stride, GPUTexture::Format texture_format,
- std::string osd_key);
+static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string path, FileSystem::ManagedCFilePtr fp,
+ u8 quality, bool clear_alpha, bool flip_y, Image image, std::string osd_key);
GPU::GPU()
{
@@ -596,44 +594,91 @@ float GPU::ComputeVerticalFrequency() const
float GPU::ComputeDisplayAspectRatio() const
{
if (g_settings.debugging.show_vram)
- {
return static_cast(VRAM_WIDTH) / static_cast(VRAM_HEIGHT);
- }
- else if (g_settings.display_force_4_3_for_24bit && m_GPUSTAT.display_area_color_depth_24)
- {
+
+ // Display off => Doesn't matter.
+ if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0)
return 4.0f / 3.0f;
- }
- else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Auto)
+
+ // PAR 1:1 is not corrected.
+ if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
+ return static_cast(m_crtc_state.display_width) / static_cast(m_crtc_state.display_height);
+
+ float ar = 4.0f / 3.0f;
+ if (!g_settings.display_force_4_3_for_24bit || !m_GPUSTAT.display_area_color_depth_24)
{
- const CRTCState& cs = m_crtc_state;
- float relative_width = static_cast(cs.horizontal_visible_end - cs.horizontal_visible_start);
- float relative_height = static_cast(cs.vertical_visible_end - cs.vertical_visible_start);
-
- if (relative_width <= 0 || relative_height <= 0)
- return 4.0f / 3.0f;
-
- if (m_GPUSTAT.pal_mode)
+ if (g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow && g_gpu_device->HasMainSwapChain())
{
- relative_width /= static_cast(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START);
- relative_height /= static_cast(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START);
+ ar = static_cast(g_gpu_device->GetMainSwapChain()->GetWidth()) /
+ static_cast(g_gpu_device->GetMainSwapChain()->GetHeight());
+ }
+ else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Custom)
+ {
+ ar = static_cast(g_settings.display_aspect_ratio_custom_numerator) /
+ static_cast(g_settings.display_aspect_ratio_custom_denominator);
}
else
{
- relative_width /= static_cast(NTSC_HORIZONTAL_ACTIVE_END - NTSC_HORIZONTAL_ACTIVE_START);
- relative_height /= static_cast(NTSC_VERTICAL_ACTIVE_END - NTSC_VERTICAL_ACTIVE_START);
+ ar = g_settings.GetDisplayAspectRatioValue();
}
- return (relative_width / relative_height) * (4.0f / 3.0f);
}
- else if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
- {
- if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0)
- return 4.0f / 3.0f;
- return static_cast(m_crtc_state.display_width) / static_cast(m_crtc_state.display_height);
+ return ar;
+}
+
+float GPU::ComputeSourceAspectRatio() const
+{
+ const float source_aspect_ratio =
+ static_cast(m_crtc_state.display_width) / static_cast(m_crtc_state.display_height);
+
+ // Correction is applied to the GTE for stretch to fit, that way it fills the window.
+ const float source_aspect_ratio_correction =
+ (g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow) ? 1.0f : ComputeAspectRatioCorrection();
+
+ return source_aspect_ratio / source_aspect_ratio_correction;
+}
+
+float GPU::ComputeAspectRatioCorrection() const
+{
+ const CRTCState& cs = m_crtc_state;
+ float relative_width = static_cast(cs.horizontal_visible_end - cs.horizontal_visible_start);
+ float relative_height = static_cast(cs.vertical_visible_end - cs.vertical_visible_start);
+ if (relative_width <= 0 || relative_height <= 0 || g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1 ||
+ g_settings.display_crop_mode == DisplayCropMode::OverscanUncorrected ||
+ g_settings.display_crop_mode == DisplayCropMode::BordersUncorrected)
+ {
+ return 1.0f;
+ }
+
+ if (m_GPUSTAT.pal_mode)
+ {
+ relative_width /= static_cast(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START);
+ relative_height /= static_cast(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START);
}
else
{
- return g_settings.GetDisplayAspectRatioValue();
+ relative_width /= static_cast(NTSC_HORIZONTAL_ACTIVE_END - NTSC_HORIZONTAL_ACTIVE_START);
+ relative_height /= static_cast(NTSC_VERTICAL_ACTIVE_END - NTSC_VERTICAL_ACTIVE_START);
+ }
+
+ return (relative_width / relative_height);
+}
+
+void GPU::ApplyPixelAspectRatioToSize(float* width, float* height) const
+{
+ const float dar = ComputeDisplayAspectRatio();
+ const float sar = ComputeSourceAspectRatio();
+ const float par = dar / sar;
+
+ if (par < 1.0f)
+ {
+ // stretch height, preserve width
+ *height = std::ceil(*height / par);
+ }
+ else
+ {
+ // stretch width, preserve height
+ *width = std::ceil(*width * par);
}
}
@@ -727,6 +772,10 @@ void GPU::UpdateCRTCDisplayParameters()
(std::min(cs.regs.X2, horizontal_total) / cs.dot_clock_divider) * cs.dot_clock_divider;
const u16 vertical_display_start = std::min(cs.regs.Y1, vertical_total);
const u16 vertical_display_end = std::min(cs.regs.Y2, vertical_total);
+ const u16 old_horizontal_visible_start = cs.horizontal_visible_start;
+ const u16 old_horizontal_visible_end = cs.horizontal_visible_end;
+ const u16 old_vertical_visible_start = cs.vertical_visible_start;
+ const u16 old_vertical_visible_end = cs.vertical_visible_end;
if (m_GPUSTAT.pal_mode)
{
@@ -741,6 +790,7 @@ void GPU::UpdateCRTCDisplayParameters()
break;
case DisplayCropMode::Overscan:
+ case DisplayCropMode::OverscanUncorrected:
cs.horizontal_visible_start = static_cast(std::max(0, 628 + g_settings.display_active_start_offset));
cs.horizontal_visible_end =
static_cast(std::max(cs.horizontal_visible_start, 3188 + g_settings.display_active_end_offset));
@@ -750,6 +800,7 @@ void GPU::UpdateCRTCDisplayParameters()
break;
case DisplayCropMode::Borders:
+ case DisplayCropMode::BordersUncorrected:
default:
cs.horizontal_visible_start = horizontal_display_start;
cs.horizontal_visible_end = horizontal_display_end;
@@ -778,6 +829,7 @@ void GPU::UpdateCRTCDisplayParameters()
break;
case DisplayCropMode::Overscan:
+ case DisplayCropMode::OverscanUncorrected:
cs.horizontal_visible_start = static_cast(std::max(0, 608 + g_settings.display_active_start_offset));
cs.horizontal_visible_end =
static_cast(std::max(cs.horizontal_visible_start, 3168 + g_settings.display_active_end_offset));
@@ -787,6 +839,7 @@ void GPU::UpdateCRTCDisplayParameters()
break;
case DisplayCropMode::Borders:
+ case DisplayCropMode::BordersUncorrected:
default:
cs.horizontal_visible_start = horizontal_display_start;
cs.horizontal_visible_end = horizontal_display_end;
@@ -874,6 +927,13 @@ void GPU::UpdateCRTCDisplayParameters()
<< height_shift;
}
+ if (old_horizontal_visible_start != cs.horizontal_visible_start ||
+ old_horizontal_visible_end != cs.horizontal_visible_end ||
+ old_vertical_visible_start != cs.vertical_visible_start || old_vertical_visible_end != cs.vertical_visible_end)
+ {
+ System::UpdateGTEAspectRatio();
+ }
+
if (cs.display_vram_width != old_vram_width || cs.display_vram_height != old_vram_height)
UpdateResolutionScale();
}
@@ -1470,7 +1530,8 @@ void GPU::WriteGP1(u32 value)
}
break;
- [[unlikely]] default : ERROR_LOG("Unimplemented GP1 command 0x{:02X}", command);
+ [[unlikely]] default:
+ ERROR_LOG("Unimplemented GP1 command 0x{:02X}", command);
break;
}
}
@@ -1518,7 +1579,8 @@ void GPU::HandleGetGPUInfoCommand(u32 value)
}
break;
- [[unlikely]] default : WARNING_LOG("Unhandled GetGPUInfo(0x{:02X})", subcommand);
+ [[unlikely]] default:
+ WARNING_LOG("Unhandled GetGPUInfo(0x{:02X})", subcommand);
break;
}
}
@@ -2213,7 +2275,7 @@ bool GPU::DeinterlaceExtractField(u32 dst_bufidx, GPUTexture* src, u32 x, u32 y,
m_deinterlace_buffers[dst_bufidx]->GetHeight() != height)
{
if (!g_gpu_device->ResizeTexture(&m_deinterlace_buffers[dst_bufidx], width, height, GPUTexture::Type::RenderTarget,
- GPUTexture::Format::RGBA8, false)) [[unlikely]]
+ GPUTexture::Format::RGBA8, GPUTexture::Flags::None, false)) [[unlikely]]
{
return false;
}
@@ -2258,7 +2320,7 @@ bool GPU::DeinterlaceSetTargetSize(u32 width, u32 height, bool preserve)
m_deinterlace_texture->GetHeight() != height)
{
if (!g_gpu_device->ResizeTexture(&m_deinterlace_texture, width, height, GPUTexture::Type::RenderTarget,
- GPUTexture::Format::RGBA8, preserve)) [[unlikely]]
+ GPUTexture::Format::RGBA8, GPUTexture::Flags::None, preserve)) [[unlikely]]
{
return false;
}
@@ -2279,7 +2341,7 @@ bool GPU::ApplyChromaSmoothing()
m_chroma_smoothing_texture->GetHeight() != height)
{
if (!g_gpu_device->ResizeTexture(&m_chroma_smoothing_texture, width, height, GPUTexture::Type::RenderTarget,
- GPUTexture::Format::RGBA8, false))
+ GPUTexture::Format::RGBA8, GPUTexture::Flags::None, false))
{
ClearDisplayTexture();
return false;
@@ -2311,20 +2373,20 @@ void GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rota
const bool integer_scale = (g_settings.display_scaling == DisplayScalingMode::NearestInteger ||
g_settings.display_scaling == DisplayScalingMode::BilinearInteger);
const bool show_vram = g_settings.debugging.show_vram;
- const float display_aspect_ratio = ComputeDisplayAspectRatio();
const float window_ratio = static_cast(window_width) / static_cast(window_height);
const float crtc_display_width = static_cast(show_vram ? VRAM_WIDTH : m_crtc_state.display_width);
const float crtc_display_height = static_cast(show_vram ? VRAM_HEIGHT : m_crtc_state.display_height);
- const float x_scale =
- apply_aspect_ratio ?
- (display_aspect_ratio / (static_cast(crtc_display_width) / static_cast(crtc_display_height))) :
- 1.0f;
+ const float display_aspect_ratio = ComputeDisplayAspectRatio();
+ const float source_aspect_ratio = ComputeSourceAspectRatio();
+ const float pixel_aspect_ratio = display_aspect_ratio / source_aspect_ratio;
+ const float x_scale = apply_aspect_ratio ? pixel_aspect_ratio : 1.0f;
float display_width = crtc_display_width;
float display_height = crtc_display_height;
float active_left = static_cast(show_vram ? 0 : m_crtc_state.display_origin_left);
float active_top = static_cast(show_vram ? 0 : m_crtc_state.display_origin_top);
float active_width = static_cast(show_vram ? VRAM_WIDTH : m_crtc_state.display_vram_width);
float active_height = static_cast(show_vram ? VRAM_HEIGHT : m_crtc_state.display_vram_height);
+
if (!g_settings.display_stretch_vertically)
{
display_width *= x_scale;
@@ -2421,48 +2483,38 @@ void GPU::CalculateDrawRect(s32 window_width, s32 window_height, bool apply_rota
GSVector4(left_padding, top_padding, left_padding + display_width * scale, top_padding + display_height * scale));
}
-bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
- u8 quality, bool clear_alpha, bool flip_y, std::vector texture_data,
- u32 texture_data_stride, GPUTexture::Format texture_format, std::string osd_key)
+bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string path, FileSystem::ManagedCFilePtr fp, u8 quality,
+ bool clear_alpha, bool flip_y, Image image, std::string osd_key)
{
- bool result;
+ Error error;
- const char* extension = std::strrchr(filename.c_str(), '.');
- if (extension)
+ if (flip_y)
+ image.FlipY();
+
+ if (image.GetFormat() != ImageFormat::RGBA8)
{
- if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
+ std::optional convert_image = image.ConvertToRGBA8(&error);
+ if (!convert_image.has_value())
{
- if (clear_alpha)
- {
- for (u32& pixel : texture_data)
- pixel |= 0xFF000000u;
- }
-
- if (flip_y)
- GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast(texture_data.data()),
- texture_data_stride);
-
- Assert(texture_data_stride == sizeof(u32) * width);
- RGBA8Image image(width, height, std::move(texture_data));
- if (image.SaveToFile(filename.c_str(), fp.get(), quality))
- {
- result = true;
- }
- else
- {
- ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
- result = false;
- }
+ ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", Image::GetFormatName(image.GetFormat()),
+ error.GetDescription());
+ image.Invalidate();
}
else
{
- result = false;
+ image = std::move(convert_image.value());
}
}
- else
+
+ bool result = false;
+ if (image.IsValid())
{
- ERROR_LOG("Unable to determine file extension for '{}'", filename);
- result = false;
+ if (clear_alpha)
+ image.SetAllPixelsOpaque();
+
+ result = image.SaveToFile(path.c_str(), fp.get(), quality, &error);
+ if (!result)
+ ERROR_LOG("Failed to save screenshot to '{}': '{}'", Path::GetFileName(path), error.GetDescription());
}
if (!osd_key.empty())
@@ -2470,7 +2522,7 @@ bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename,
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_CAMERA,
fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") :
TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."),
- Path::GetFileName(filename),
+ Path::GetFileName(path),
result ? Host::OSD_INFO_DURATION : Host::OSD_ERROR_DURATION));
}
@@ -2486,17 +2538,16 @@ bool GPU::WriteDisplayTextureToFile(std::string filename)
const u32 read_y = static_cast(m_display_texture_view_y);
const u32 read_width = static_cast(m_display_texture_view_width);
const u32 read_height = static_cast(m_display_texture_view_height);
+ const ImageFormat read_format = GPUTexture::GetImageFormatForTextureFormat(m_display_texture->GetFormat());
+ if (read_format == ImageFormat::None)
+ return false;
- const u32 texture_data_stride =
- Common::AlignUpPow2(GPUTexture::GetPixelSize(m_display_texture->GetFormat()) * read_width, 4);
- std::vector texture_data((texture_data_stride * read_height) / sizeof(u32));
-
+ Image image(read_width, read_height, read_format);
std::unique_ptr dltex;
if (g_gpu_device->GetFeatures().memory_import)
{
- dltex =
- g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(), texture_data.data(),
- texture_data.size() * sizeof(u32), texture_data_stride);
+ dltex = g_gpu_device->CreateDownloadTexture(read_width, read_height, m_display_texture->GetFormat(),
+ image.GetPixels(), image.GetStorageSize(), image.GetPitch());
}
if (!dltex)
{
@@ -2509,7 +2560,7 @@ bool GPU::WriteDisplayTextureToFile(std::string filename)
}
dltex->CopyFromTexture(0, 0, m_display_texture, read_x, read_y, read_width, read_height, 0, 0, !dltex->IsImported());
- if (!dltex->ReadTexels(0, 0, read_width, read_height, texture_data.data(), texture_data_stride))
+ if (!dltex->ReadTexels(0, 0, read_width, read_height, image.GetPixels(), image.GetPitch()))
{
RestoreDeviceContext();
return false;
@@ -2528,20 +2579,22 @@ bool GPU::WriteDisplayTextureToFile(std::string filename)
constexpr bool clear_alpha = true;
const bool flip_y = g_gpu_device->UsesLowerLeftOrigin();
- return CompressAndWriteTextureToFile(
- read_width, read_height, std::move(filename), std::move(fp), g_settings.display_screenshot_quality, clear_alpha,
- flip_y, std::move(texture_data), texture_data_stride, m_display_texture->GetFormat(), std::string());
+ return CompressAndWriteTextureToFile(read_width, read_height, std::move(filename), std::move(fp),
+ g_settings.display_screenshot_quality, clear_alpha, flip_y, std::move(image),
+ std::string());
}
bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
- bool postfx, std::vector* out_pixels, u32* out_stride,
- GPUTexture::Format* out_format)
+ bool postfx, Image* out_image)
{
const GPUTexture::Format hdformat =
g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetFormat() : GPUTexture::Format::RGBA8;
+ const ImageFormat image_format = GPUTexture::GetImageFormatForTextureFormat(hdformat);
+ if (image_format == ImageFormat::None)
+ return false;
- auto render_texture =
- g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, hdformat);
+ auto render_texture = g_gpu_device->FetchAutoRecycleTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget,
+ hdformat, GPUTexture::Flags::None);
if (!render_texture)
return false;
@@ -2550,86 +2603,63 @@ bool GPU::RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i displ
// TODO: this should use copy shader instead.
RenderDisplay(render_texture.get(), display_rect, draw_rect, postfx);
- const u32 stride = Common::AlignUpPow2(GPUTexture::GetPixelSize(hdformat) * width, sizeof(u32));
- out_pixels->resize((height * stride) / sizeof(u32));
+ Image image(width, height, image_format);
+ Error error;
std::unique_ptr dltex;
if (g_gpu_device->GetFeatures().memory_import)
{
- dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, out_pixels->data(),
- out_pixels->size() * sizeof(u32), stride);
+ dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, image.GetPixels(), image.GetStorageSize(),
+ image.GetPitch(), &error);
}
if (!dltex)
{
- if (!(dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat)))
+ if (!(dltex = g_gpu_device->CreateDownloadTexture(width, height, hdformat, &error)))
{
- ERROR_LOG("Failed to create {}x{} download texture", width, height);
+ ERROR_LOG("Failed to create {}x{} download texture: {}", width, height, error.GetDescription());
return false;
}
}
dltex->CopyFromTexture(0, 0, render_texture.get(), 0, 0, width, height, 0, 0, false);
- if (!dltex->ReadTexels(0, 0, width, height, out_pixels->data(), stride))
+ if (!dltex->ReadTexels(0, 0, width, height, image.GetPixels(), image.GetPitch()))
{
RestoreDeviceContext();
return false;
}
- *out_stride = stride;
- *out_format = hdformat;
RestoreDeviceContext();
+ *out_image = std::move(image);
return true;
}
void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* height, GSVector4i* display_rect,
GSVector4i* draw_rect) const
{
- *width = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWidth() : 1;
- *height = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetHeight() : 1;
- CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect);
-
const bool internal_resolution = (mode != DisplayScreenshotMode::ScreenResolution || g_settings.debugging.show_vram);
if (internal_resolution && m_display_texture_view_width != 0 && m_display_texture_view_height != 0)
{
if (mode == DisplayScreenshotMode::InternalResolution)
{
- const u32 draw_width = static_cast(display_rect->width());
- const u32 draw_height = static_cast(display_rect->height());
-
- // If internal res, scale the computed draw rectangle to the internal res.
- // We re-use the draw rect because it's already been AR corrected.
- const float sar =
- static_cast(m_display_texture_view_width) / static_cast(m_display_texture_view_height);
- const float dar = static_cast(draw_width) / static_cast(draw_height);
- if (sar >= dar)
- {
- // stretch height, preserve width
- const float scale = static_cast(m_display_texture_view_width) / static_cast(draw_width);
- *width = m_display_texture_view_width;
- *height = static_cast(std::round(static_cast(draw_height) * scale));
- }
- else
- {
- // stretch width, preserve height
- const float scale = static_cast(m_display_texture_view_height) / static_cast(draw_height);
- *width = static_cast(std::round(static_cast(draw_width) * scale));
- *height = m_display_texture_view_height;
- }
+ float f_width = static_cast(m_display_texture_view_width);
+ float f_height = static_cast(m_display_texture_view_height);
+ ApplyPixelAspectRatioToSize(&f_width, &f_height);
// DX11 won't go past 16K texture size.
- const u32 max_texture_size = g_gpu_device->GetMaxTextureSize();
- if (*width > max_texture_size)
+ const float max_texture_size = static_cast(g_gpu_device->GetMaxTextureSize());
+ if (f_width > max_texture_size)
{
- *height = static_cast(static_cast(*height) /
- (static_cast(*width) / static_cast(max_texture_size)));
- *width = max_texture_size;
+ f_height = f_height / (f_width / max_texture_size);
+ f_width = max_texture_size;
}
- if (*height > max_texture_size)
+ if (f_height > max_texture_size)
{
- *height = max_texture_size;
- *width = static_cast(static_cast(*width) /
- (static_cast(*height) / static_cast(max_texture_size)));
+ f_height = max_texture_size;
+ f_width = f_width / (f_height / max_texture_size);
}
+
+ *width = static_cast(std::ceil(f_width));
+ *height = static_cast(std::ceil(f_height));
}
else // if (mode == DisplayScreenshotMode::UncorrectedInternalResolution)
{
@@ -2641,6 +2671,12 @@ void GPU::CalculateScreenshotSize(DisplayScreenshotMode mode, u32* width, u32* h
*draw_rect = GSVector4i(0, 0, static_cast(*width), static_cast(*height));
*display_rect = *draw_rect;
}
+ else
+ {
+ *width = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetWidth() : 1;
+ *height = g_gpu_device->HasMainSwapChain() ? g_gpu_device->GetMainSwapChain()->GetHeight() : 1;
+ CalculateDrawRect(*width, *height, true, !g_settings.debugging.show_vram, display_rect, draw_rect);
+ }
}
bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
@@ -2654,11 +2690,8 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u
if (width == 0 || height == 0)
return false;
- std::vector pixels;
- u32 pixels_stride;
- GPUTexture::Format pixels_format;
- if (!RenderScreenshotToBuffer(width, height, display_rect, draw_rect, !internal_resolution, &pixels, &pixels_stride,
- &pixels_format))
+ Image image;
+ if (!RenderScreenshotToBuffer(width, height, display_rect, draw_rect, !internal_resolution, &image))
{
ERROR_LOG("Failed to render {}x{} screenshot", width, height);
return false;
@@ -2685,10 +2718,10 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u
if (compress_on_thread)
{
System::QueueTaskOnThread([width, height, path = std::move(path), fp = fp.release(), quality,
- flip_y = g_gpu_device->UsesLowerLeftOrigin(), pixels = std::move(pixels), pixels_stride,
- pixels_format, osd_key = std::move(osd_key)]() mutable {
+ flip_y = g_gpu_device->UsesLowerLeftOrigin(), image = std::move(image),
+ osd_key = std::move(osd_key)]() mutable {
CompressAndWriteTextureToFile(width, height, std::move(path), FileSystem::ManagedCFilePtr(fp), quality, true,
- flip_y, std::move(pixels), pixels_stride, pixels_format, std::move(osd_key));
+ flip_y, std::move(image), std::move(osd_key));
System::RemoveSelfFromTaskThreads();
});
@@ -2697,8 +2730,7 @@ bool GPU::RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u
else
{
return CompressAndWriteTextureToFile(width, height, std::move(path), std::move(fp), quality, true,
- g_gpu_device->UsesLowerLeftOrigin(), std::move(pixels), pixels_stride,
- pixels_format, std::move(osd_key));
+ g_gpu_device->UsesLowerLeftOrigin(), std::move(image), std::move(osd_key));
}
}
@@ -2724,20 +2756,23 @@ bool GPU::DumpVRAMToFile(const char* filename)
bool GPU::DumpVRAMToFile(const char* filename, u32 width, u32 height, u32 stride, const void* buffer, bool remove_alpha)
{
- RGBA8Image image(width, height);
+ Image image(width, height, ImageFormat::RGBA8);
const char* ptr_in = static_cast(buffer);
for (u32 row = 0; row < height; row++)
{
const char* row_ptr_in = ptr_in;
- u32* ptr_out = image.GetRowPixels(row);
+ u8* ptr_out = image.GetRowPixels(row);
for (u32 col = 0; col < width; col++)
{
u16 src_col;
std::memcpy(&src_col, row_ptr_in, sizeof(u16));
row_ptr_in += sizeof(u16);
- *(ptr_out++) = VRAMRGBA5551ToRGBA8888(remove_alpha ? (src_col | u16(0x8000)) : src_col);
+
+ const u32 pixel32 = VRAMRGBA5551ToRGBA8888(remove_alpha ? (src_col | u16(0x8000)) : src_col);
+ std::memcpy(ptr_out, &pixel32, sizeof(pixel32));
+ ptr_out += sizeof(pixel32);
}
ptr_in += stride;
diff --git a/src/core/gpu.h b/src/core/gpu.h
index a98b20cbb..cb57fa3d5 100644
--- a/src/core/gpu.h
+++ b/src/core/gpu.h
@@ -24,6 +24,7 @@
#include
class Error;
+class Image;
class SmallStringBase;
class StateWrapper;
@@ -183,9 +184,17 @@ public:
float ComputeHorizontalFrequency() const;
float ComputeVerticalFrequency() const;
float ComputeDisplayAspectRatio() const;
+ float ComputeSourceAspectRatio() const;
- static std::unique_ptr CreateHardwareRenderer(Error* error);
- static std::unique_ptr CreateSoftwareRenderer(Error* error);
+ /// Computes aspect ratio correction, i.e. the scale to apply to the source aspect ratio to preserve
+ /// the original pixel aspect ratio regardless of how much cropping has been applied.
+ float ComputeAspectRatioCorrection() const;
+
+ /// Applies the pixel aspect ratio to a given size, preserving the larger dimension.
+ void ApplyPixelAspectRatioToSize(float* width, float* height) const;
+
+ static std::unique_ptr CreateHardwareRenderer();
+ static std::unique_ptr CreateSoftwareRenderer();
// Converts window coordinates into horizontal ticks and scanlines. Returns false if out of range. Used for lightguns.
void ConvertScreenCoordinatesToDisplayCoordinates(float window_x, float window_y, float* display_x,
@@ -233,8 +242,7 @@ public:
/// Renders the display, optionally with postprocessing to the specified image.
bool RenderScreenshotToBuffer(u32 width, u32 height, const GSVector4i display_rect, const GSVector4i draw_rect,
- bool postfx, std::vector* out_pixels, u32* out_stride,
- GPUTexture::Format* out_format);
+ bool postfx, Image* out_image);
/// Helper function to save screenshot to PNG.
bool RenderScreenshotToFile(std::string path, DisplayScreenshotMode mode, u8 quality, bool compress_on_thread,
diff --git a/src/core/gpu_hw.cpp b/src/core/gpu_hw.cpp
index 3b93b2f3d..8540f4328 100644
--- a/src/core/gpu_hw.cpp
+++ b/src/core/gpu_hw.cpp
@@ -275,15 +275,9 @@ bool GPU_HW::Initialize(Error* error)
PrintSettingsToLog();
- if (!CompileCommonShaders(error) || !CompilePipelines(error))
+ if (!CompileCommonShaders(error) || !CompilePipelines(error) || !CreateBuffers(error))
return false;
- if (!CreateBuffers())
- {
- Error::SetStringView(error, "Failed to create framebuffer");
- return false;
- }
-
if (m_use_texture_cache)
{
if (!GPUTextureCache::Initialize())
@@ -366,7 +360,7 @@ bool GPU_HW::DoState(StateWrapper& sw, GPUTexture** host_texture, bool update_di
->FetchTexture(
m_vram_texture->GetWidth(), m_vram_texture->GetHeight(), 1, 1, m_vram_texture->GetSamples(),
m_vram_texture->IsMultisampled() ? GPUTexture::Type::RenderTarget : GPUTexture::Type::Texture,
- GPUTexture::Format::RGBA8, nullptr, 0)
+ GPUTexture::Format::RGBA8, GPUTexture::Flags::None)
.release();
*host_texture = tex;
if (!tex)
@@ -423,7 +417,7 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
m_pgxp_depth_buffer != g_settings.UsingPGXPDepthBuffer() ||
(!old_settings.gpu_texture_cache && g_settings.gpu_texture_cache));
const bool shaders_changed =
- ((m_resolution_scale > 1) != (resolution_scale > 1) || (m_multisamples > 1) != (multisamples > 1) ||
+ ((m_resolution_scale > 1) != (resolution_scale > 1) || m_multisamples != multisamples ||
m_true_color != g_settings.gpu_true_color || prev_force_progressive_scan != m_force_progressive_scan ||
(multisamples > 1 && g_settings.gpu_per_sample_shading != old_settings.gpu_per_sample_shading) ||
(resolution_scale > 1 && g_settings.gpu_scaled_dithering != old_settings.gpu_scaled_dithering) ||
@@ -538,8 +532,12 @@ void GPU_HW::UpdateSettings(const Settings& old_settings)
g_gpu_device->PurgeTexturePool();
g_gpu_device->WaitForGPUIdle();
- if (!CreateBuffers())
+ Error error;
+ if (!CreateBuffers(&error))
+ {
+ ERROR_LOG("Failed to recreate buffers: {}", error.GetDescription());
Panic("Failed to recreate buffers.");
+ }
UpdateDownsamplingLevels();
RestoreDeviceContext();
@@ -849,7 +847,7 @@ GPUTexture::Format GPU_HW::GetDepthBufferFormat() const
VRAM_DS_FORMAT;
}
-bool GPU_HW::CreateBuffers()
+bool GPU_HW::CreateBuffers(Error* error)
{
DestroyBuffers();
@@ -859,28 +857,30 @@ bool GPU_HW::CreateBuffers()
const u8 samples = static_cast(m_multisamples);
const bool needs_depth_buffer = m_write_mask_as_depth || m_pgxp_depth_buffer;
- // Needed for Metal resolve.
- const GPUTexture::Type read_texture_type = (g_gpu_device->GetRenderAPI() == RenderAPI::Metal && m_multisamples > 1) ?
- GPUTexture::Type::RWTexture :
- GPUTexture::Type::Texture;
- const GPUTexture::Type vram_texture_type =
- m_use_rov_for_shader_blend ? GPUTexture::Type::RWTexture : GPUTexture::Type::RenderTarget;
+ const GPUTexture::Flags read_texture_flags =
+ (m_multisamples > 1) ? GPUTexture::Flags::AllowMSAAResolveTarget : GPUTexture::Flags::None;
+ const GPUTexture::Flags vram_texture_flags =
+ m_use_rov_for_shader_blend ? GPUTexture::Flags::AllowBindAsImage : GPUTexture::Flags::None;
const GPUTexture::Type depth_texture_type =
- m_use_rov_for_shader_blend ? GPUTexture::Type::RWTexture : GPUTexture::Type::DepthStencil;
+ m_use_rov_for_shader_blend ? GPUTexture::Type::RenderTarget : GPUTexture::Type::DepthStencil;
- if (!(m_vram_texture = g_gpu_device->FetchTexture(texture_width, texture_height, 1, 1, samples, vram_texture_type,
- VRAM_RT_FORMAT)) ||
- (needs_depth_buffer &&
- !(m_vram_depth_texture = g_gpu_device->FetchTexture(texture_width, texture_height, 1, 1, samples,
- depth_texture_type, GetDepthBufferFormat()))) ||
- (m_pgxp_depth_buffer && !(m_vram_depth_copy_texture =
- g_gpu_device->FetchTexture(texture_width, texture_height, 1, 1, samples,
- GPUTexture::Type::RenderTarget, VRAM_DS_COLOR_FORMAT))) ||
+ if (!(m_vram_texture =
+ g_gpu_device->FetchTexture(texture_width, texture_height, 1, 1, samples, GPUTexture::Type::RenderTarget,
+ VRAM_RT_FORMAT, vram_texture_flags, nullptr, 0, error)) ||
+ (needs_depth_buffer && !(m_vram_depth_texture = g_gpu_device->FetchTexture(
+ texture_width, texture_height, 1, 1, samples, depth_texture_type,
+ GetDepthBufferFormat(), vram_texture_flags, nullptr, 0, error))) ||
+ (m_pgxp_depth_buffer && !(m_vram_depth_copy_texture = g_gpu_device->FetchTexture(
+ texture_width, texture_height, 1, 1, samples, GPUTexture::Type::RenderTarget,
+ VRAM_DS_COLOR_FORMAT, GPUTexture::Flags::None, nullptr, 0, error))) ||
!(m_vram_read_texture =
- g_gpu_device->FetchTexture(texture_width, texture_height, 1, 1, 1, read_texture_type, VRAM_RT_FORMAT)) ||
- !(m_vram_readback_texture = g_gpu_device->FetchTexture(VRAM_WIDTH / 2, VRAM_HEIGHT, 1, 1, 1,
- GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT)))
+ g_gpu_device->FetchTexture(texture_width, texture_height, 1, 1, 1, GPUTexture::Type::Texture, VRAM_RT_FORMAT,
+ read_texture_flags, nullptr, 0, error)) ||
+ !(m_vram_readback_texture =
+ g_gpu_device->FetchTexture(VRAM_WIDTH / 2, VRAM_HEIGHT, 1, 1, 1, GPUTexture::Type::RenderTarget,
+ VRAM_RT_FORMAT, GPUTexture::Flags::None, nullptr, 0, error)))
{
+ Error::AddPrefix(error, "Failed to create VRAM textures: ");
return false;
}
@@ -895,26 +895,28 @@ bool GPU_HW::CreateBuffers()
DEV_LOG("Trying to import guest VRAM buffer for downloads...");
m_vram_readback_download_texture = g_gpu_device->CreateDownloadTexture(
m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(), m_vram_readback_texture->GetFormat(),
- g_vram, sizeof(g_vram), VRAM_WIDTH * sizeof(u16));
+ g_vram, sizeof(g_vram), VRAM_WIDTH * sizeof(u16), error);
if (!m_vram_readback_download_texture)
ERROR_LOG("Failed to create imported readback buffer");
}
if (!m_vram_readback_download_texture)
{
- m_vram_readback_download_texture = g_gpu_device->CreateDownloadTexture(
- m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(), m_vram_readback_texture->GetFormat());
+ m_vram_readback_download_texture =
+ g_gpu_device->CreateDownloadTexture(m_vram_readback_texture->GetWidth(), m_vram_readback_texture->GetHeight(),
+ m_vram_readback_texture->GetFormat(), error);
if (!m_vram_readback_download_texture)
{
- ERROR_LOG("Failed to create readback download texture");
+ Error::AddPrefix(error, "Failed to create readback download texture: ");
return false;
}
}
if (g_gpu_device->GetFeatures().supports_texture_buffers)
{
- if (!(m_vram_upload_buffer =
- g_gpu_device->CreateTextureBuffer(GPUTextureBuffer::Format::R16UI, GPUDevice::MIN_TEXEL_BUFFER_ELEMENTS)))
+ if (!(m_vram_upload_buffer = g_gpu_device->CreateTextureBuffer(GPUTextureBuffer::Format::R16UI,
+ GPUDevice::MIN_TEXEL_BUFFER_ELEMENTS, error)))
{
+ Error::AddPrefix(error, "Failed to create texture buffer: ");
return false;
}
@@ -1465,7 +1467,7 @@ bool GPU_HW::CompilePipelines(Error* error)
std::unique_ptr fs = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateVRAMFillFragmentShader(ConvertToBoolUnchecked(wrapped), ConvertToBoolUnchecked(interlaced),
- m_write_mask_as_depth),
+ m_write_mask_as_depth, needs_rov_depth),
error);
if (!fs)
return false;
@@ -1483,9 +1485,9 @@ bool GPU_HW::CompilePipelines(Error* error)
// VRAM copy
{
- std::unique_ptr fs =
- g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
- shadergen.GenerateVRAMCopyFragmentShader(m_write_mask_as_depth), error);
+ std::unique_ptr fs = g_gpu_device->CreateShader(
+ GPUShaderStage::Fragment, shadergen.GetLanguage(),
+ shadergen.GenerateVRAMCopyFragmentShader(m_write_mask_as_depth, needs_rov_depth), error);
if (!fs)
return false;
@@ -1514,7 +1516,7 @@ bool GPU_HW::CompilePipelines(Error* error)
const bool use_ssbo = features.texture_buffers_emulated_with_ssbo;
std::unique_ptr fs = g_gpu_device->CreateShader(
GPUShaderStage::Fragment, shadergen.GetLanguage(),
- shadergen.GenerateVRAMWriteFragmentShader(use_buffer, use_ssbo, m_write_mask_as_depth), error);
+ shadergen.GenerateVRAMWriteFragmentShader(use_buffer, use_ssbo, m_write_mask_as_depth, needs_rov_depth), error);
if (!fs)
return false;
@@ -1543,8 +1545,8 @@ bool GPU_HW::CompilePipelines(Error* error)
// VRAM write replacement
{
- std::unique_ptr fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
- shadergen.GenerateCopyFragmentShader(), error);
+ std::unique_ptr fs = g_gpu_device->CreateShader(
+ GPUShaderStage::Fragment, shadergen.GetLanguage(), shadergen.GenerateVRAMReplacementBlitFragmentShader(), error);
if (!fs)
return false;
@@ -2549,7 +2551,7 @@ void GPU_HW::LoadVertices()
if (m_resolution_scale > 1 && !is_3d && rc.quad_polygon)
HandleFlippedQuadTextureCoordinates(vertices.data());
else if (m_allow_sprite_mode)
- SetBatchSpriteMode((pgxp && !is_3d) || IsPossibleSpritePolygon(vertices.data()));
+ SetBatchSpriteMode(pgxp ? !is_3d : IsPossibleSpritePolygon(vertices.data()));
if (m_sw_renderer)
{
@@ -2922,41 +2924,14 @@ void GPU_HW::LoadVertices()
}
}
-bool GPU_HW::BlitVRAMReplacementTexture(const GPUTextureCache::TextureReplacementImage* tex, u32 dst_x, u32 dst_y,
- u32 width, u32 height)
+bool GPU_HW::BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height)
{
- if (!m_vram_replacement_texture || m_vram_replacement_texture->GetWidth() < tex->GetWidth() ||
- m_vram_replacement_texture->GetHeight() < tex->GetHeight() || g_gpu_device->GetFeatures().prefer_unused_textures)
- {
- g_gpu_device->RecycleTexture(std::move(m_vram_replacement_texture));
-
- if (!(m_vram_replacement_texture =
- g_gpu_device->FetchTexture(tex->GetWidth(), tex->GetHeight(), 1, 1, 1, GPUTexture::Type::DynamicTexture,
- GPUTexture::Format::RGBA8, tex->GetPixels(), tex->GetPitch())))
- {
- return false;
- }
- }
- else
- {
- if (!m_vram_replacement_texture->Update(0, 0, tex->GetWidth(), tex->GetHeight(), tex->GetPixels(), tex->GetPitch()))
- {
- ERROR_LOG("Update {}x{} texture failed.", width, height);
- return false;
- }
- }
-
GL_SCOPE_FMT("BlitVRAMReplacementTexture() {}x{} to {},{} => {},{} ({}x{})", tex->GetWidth(), tex->GetHeight(), dst_x,
dst_y, dst_x + width, dst_y + height, width, height);
- const float src_rect[4] = {
- 0.0f, 0.0f, static_cast(tex->GetWidth()) / static_cast(m_vram_replacement_texture->GetWidth()),
- static_cast(tex->GetHeight()) / static_cast(m_vram_replacement_texture->GetHeight())};
-
- g_gpu_device->SetTextureSampler(0, m_vram_replacement_texture.get(), g_gpu_device->GetLinearSampler());
+ g_gpu_device->SetTextureSampler(0, tex, g_gpu_device->GetLinearSampler());
g_gpu_device->SetPipeline(m_vram_write_replacement_pipeline.get());
g_gpu_device->SetViewportAndScissor(dst_x, dst_y, width, height);
- g_gpu_device->PushUniformBuffer(src_rect, sizeof(src_rect));
g_gpu_device->Draw(3, 0);
RestoreDeviceContext();
@@ -3379,7 +3354,7 @@ void GPU_HW::UpdateVRAM(u32 x, u32 y, u32 width, u32 height, const void* data, b
}
else
{
- const GPUTextureCache::TextureReplacementImage* rtex = GPUTextureCache::GetVRAMReplacement(width, height, data);
+ GPUTexture* rtex = GPUTextureCache::GetVRAMReplacement(width, height, data);
if (rtex && BlitVRAMReplacementTexture(rtex, x * m_resolution_scale, y * m_resolution_scale,
width * m_resolution_scale, height * m_resolution_scale))
{
@@ -3402,7 +3377,7 @@ void GPU_HW::UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* da
{
map_index = 0;
upload_texture = g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::Texture,
- GPUTexture::Format::R16U, data, data_pitch);
+ GPUTexture::Format::R16U, GPUTexture::Flags::None, data, data_pitch);
if (!upload_texture)
{
ERROR_LOG("Failed to get {}x{} upload texture. Things are gonna break.", width, height);
@@ -3938,7 +3913,8 @@ void GPU_HW::UpdateDisplay()
m_vram_extract_texture->GetHeight() != read_height)
{
if (!g_gpu_device->ResizeTexture(&m_vram_extract_texture, scaled_display_width, read_height,
- GPUTexture::Type::RenderTarget, GPUTexture::Format::RGBA8)) [[unlikely]]
+ GPUTexture::Type::RenderTarget, GPUTexture::Format::RGBA8,
+ GPUTexture::Flags::None)) [[unlikely]]
{
ClearDisplayTexture();
return;
@@ -3952,7 +3928,7 @@ void GPU_HW::UpdateDisplay()
((m_vram_extract_depth_texture && m_vram_extract_depth_texture->GetWidth() == scaled_display_width &&
m_vram_extract_depth_texture->GetHeight() == scaled_display_height) ||
!g_gpu_device->ResizeTexture(&m_vram_extract_depth_texture, scaled_display_width, scaled_display_height,
- GPUTexture::Type::RenderTarget, VRAM_DS_COLOR_FORMAT)))
+ GPUTexture::Type::RenderTarget, VRAM_DS_COLOR_FORMAT, GPUTexture::Flags::None)))
{
depth_source->MakeReadyForSampling();
g_gpu_device->InvalidateRenderTarget(m_vram_extract_depth_texture.get());
@@ -4090,15 +4066,16 @@ void GPU_HW::DownsampleFramebufferAdaptive(GPUTexture* source, u32 left, u32 top
if (!m_downsample_texture || m_downsample_texture->GetWidth() != width || m_downsample_texture->GetHeight() != height)
{
g_gpu_device->RecycleTexture(std::move(m_downsample_texture));
- m_downsample_texture =
- g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT);
+ m_downsample_texture = g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::RenderTarget,
+ VRAM_RT_FORMAT, GPUTexture::Flags::None);
}
- std::unique_ptr level_texture = g_gpu_device->FetchAutoRecycleTexture(
- width, height, 1, m_downsample_scale_or_levels, 1, GPUTexture::Type::Texture, VRAM_RT_FORMAT);
- std::unique_ptr weight_texture =
- g_gpu_device->FetchAutoRecycleTexture(std::max(width >> (m_downsample_scale_or_levels - 1), 1u),
- std::max(height >> (m_downsample_scale_or_levels - 1), 1u), 1, 1, 1,
- GPUTexture::Type::RenderTarget, GPUTexture::Format::R8);
+ std::unique_ptr level_texture =
+ g_gpu_device->FetchAutoRecycleTexture(width, height, 1, m_downsample_scale_or_levels, 1, GPUTexture::Type::Texture,
+ VRAM_RT_FORMAT, GPUTexture::Flags::None);
+ std::unique_ptr weight_texture = g_gpu_device->FetchAutoRecycleTexture(
+ std::max(width >> (m_downsample_scale_or_levels - 1), 1u),
+ std::max(height >> (m_downsample_scale_or_levels - 1), 1u), 1, 1, 1, GPUTexture::Type::RenderTarget,
+ GPUTexture::Format::R8, GPUTexture::Flags::None);
if (!m_downsample_texture || !level_texture || !weight_texture)
{
ERROR_LOG("Failed to create {}x{} RTs for adaptive downsampling", width, height);
@@ -4205,8 +4182,8 @@ void GPU_HW::DownsampleFramebufferBoxFilter(GPUTexture* source, u32 left, u32 to
m_downsample_texture->GetHeight() != ds_height)
{
g_gpu_device->RecycleTexture(std::move(m_downsample_texture));
- m_downsample_texture =
- g_gpu_device->FetchTexture(ds_width, ds_height, 1, 1, 1, GPUTexture::Type::RenderTarget, VRAM_RT_FORMAT);
+ m_downsample_texture = g_gpu_device->FetchTexture(ds_width, ds_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
+ VRAM_RT_FORMAT, GPUTexture::Flags::None);
}
if (!m_downsample_texture)
{
@@ -4288,11 +4265,7 @@ void GPU_HW::DrawRendererStats()
}
}
-std::unique_ptr GPU::CreateHardwareRenderer(Error* error)
+std::unique_ptr GPU::CreateHardwareRenderer()
{
- std::unique_ptr gpu(std::make_unique());
- if (!gpu->Initialize(error))
- gpu.reset();
-
- return gpu;
+ return std::make_unique();
}
diff --git a/src/core/gpu_hw.h b/src/core/gpu_hw.h
index 46bdc78fa..77595b5ba 100644
--- a/src/core/gpu_hw.h
+++ b/src/core/gpu_hw.h
@@ -155,7 +155,7 @@ private:
/// Returns true if a depth buffer should be created.
GPUTexture::Format GetDepthBufferFormat() const;
- bool CreateBuffers();
+ bool CreateBuffers(Error* error);
void ClearFramebuffer();
void DestroyBuffers();
@@ -228,8 +228,7 @@ private:
void UpdateVRAMOnGPU(u32 x, u32 y, u32 width, u32 height, const void* data, u32 data_pitch, bool set_mask,
bool check_mask, const GSVector4i bounds);
- bool BlitVRAMReplacementTexture(const GPUTextureCache::TextureReplacementImage* tex, u32 dst_x, u32 dst_y, u32 width,
- u32 height);
+ bool BlitVRAMReplacementTexture(GPUTexture* tex, u32 dst_x, u32 dst_y, u32 width, u32 height);
/// Expands a line into two triangles.
void DrawLine(const GSVector4 bounds, u32 col0, u32 col1, float depth);
@@ -259,7 +258,6 @@ private:
std::unique_ptr m_vram_read_texture;
std::unique_ptr m_vram_readback_texture;
std::unique_ptr m_vram_readback_download_texture;
- std::unique_ptr m_vram_replacement_texture;
std::unique_ptr m_vram_upload_buffer;
std::unique_ptr m_vram_write_texture;
diff --git a/src/core/gpu_hw_shadergen.cpp b/src/core/gpu_hw_shadergen.cpp
index 997cd86e9..e13cf4dbd 100644
--- a/src/core/gpu_hw_shadergen.cpp
+++ b/src/core/gpu_hw_shadergen.cpp
@@ -822,11 +822,34 @@ uint2 FloatToIntegerCoords(float2 coords)
return uint2((UPSCALED == 0 || FORCE_ROUND_TEXCOORDS != 0) ? roundEven(coords) : floor(coords));
}
-#if !PAGE_TEXTURE
+#if PAGE_TEXTURE
+
+float4 SampleFromPageTexture(float2 coords)
+{
+ // Cached textures.
+ uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
+#if UPSCALED
+ float2 fpart = frac(coords);
+ coords = (float2(icoord) + fpart);
+#else
+ // Drop fractional part.
+ coords = float2(icoord);
+#endif
+
+ // Normalize.
+ coords = coords * (1.0f / 256.0f);
+ return SAMPLE_TEXTURE(samp0, coords);
+}
+
+#endif
+
+#if !PAGE_TEXTURE || TEXTURE_FILTERING
float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
{
- #if PALETTE
+ #if PAGE_TEXTURE
+ return SampleFromPageTexture(coords);
+ #elif PALETTE
uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
uint2 vicoord;
@@ -875,22 +898,7 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
#endif
}
-#else
-
-float4 SampleFromPageTexture(float2 coords)
-{
- // Cached textures.
-#if UPSCALED == 0
- float2 fpart = coords - roundEven(coords);
-#else
- float2 fpart = frac(coords);
-#endif
- uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
- coords = (float2(icoord) + fpart) * (1.0f / 256.0f);
- return SAMPLE_TEXTURE(samp0, coords);
-}
-
-#endif
+#endif // !PAGE_TEXTURE || TEXTURE_FILTERING
#endif // TEXTURED
)";
@@ -898,6 +906,9 @@ float4 SampleFromPageTexture(float2 coords)
const u32 num_fragment_outputs = use_rov ? 0 : (use_dual_source ? 2 : 1);
if (textured && page_texture)
{
+ if (texture_filtering != GPUTextureFilter::Nearest)
+ WriteBatchTextureFilter(ss, texture_filtering);
+
if (uv_limits)
{
DeclareFragmentEntryPoint(ss, 1, 1, {{"nointerpolation", "float4 v_uv_limits"}}, true, num_fragment_outputs,
@@ -956,7 +967,7 @@ float4 SampleFromPageTexture(float2 coords)
#if TEXTURED
float4 texcol;
- #if PAGE_TEXTURE
+ #if PAGE_TEXTURE && !TEXTURE_FILTERING
#if UV_LIMITS
texcol = SampleFromPageTexture(clamp(v_tex0, v_uv_limits.xy, v_uv_limits.zw));
#else
@@ -967,7 +978,11 @@ float4 SampleFromPageTexture(float2 coords)
ialpha = 1.0;
#elif TEXTURE_FILTERING
- FilteredSampleFromVRAM(v_texpage, v_tex0, v_uv_limits, texcol, ialpha);
+ #if PAGE_TEXTURE
+ FilteredSampleFromVRAM(int2(0, 0), v_tex0, v_uv_limits, texcol, ialpha);
+ #else
+ FilteredSampleFromVRAM(v_texpage, v_tex0, v_uv_limits, texcol, ialpha);
+ #endif
if (ialpha < 0.5)
discard;
#else
@@ -1269,6 +1284,22 @@ float3 SampleVRAM24(uint2 icoords)
return ss.str();
}
+std::string GPU_HW_ShaderGen::GenerateVRAMReplacementBlitFragmentShader() const
+{
+ std::stringstream ss;
+ WriteHeader(ss);
+ DeclareTexture(ss, "samp0", 0);
+ DeclareFragmentEntryPoint(ss, 0, 1);
+
+ ss << R"(
+{
+ o_col0 = SAMPLE_TEXTURE(samp0, v_tex0);
+}
+)";
+
+ return ss.str();
+}
+
std::string GPU_HW_ShaderGen::GenerateWireframeGeometryShader() const
{
std::stringstream ss;
@@ -1422,14 +1453,17 @@ uint SampleVRAM(uint2 coords)
return ss.str();
}
-std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, bool use_ssbo,
- bool write_mask_as_depth) const
+std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, bool use_ssbo, bool write_mask_as_depth,
+ bool write_depth_as_rt) const
{
+ Assert(!write_mask_as_depth || (write_mask_as_depth != write_depth_as_rt));
+
std::stringstream ss;
WriteHeader(ss);
WriteColorConversionFunctions(ss);
DefineMacro(ss, "WRITE_MASK_AS_DEPTH", write_mask_as_depth);
+ DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
DefineMacro(ss, "USE_BUFFER", use_buffer);
ss << "CONSTANT float2 VRAM_SIZE = float2(" << VRAM_WIDTH << ".0, " << VRAM_HEIGHT << ".0);\n";
@@ -1465,7 +1499,7 @@ std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, b
ss << "#define GET_VALUE(buffer_offset) (LOAD_TEXTURE_BUFFER(samp0, int(buffer_offset)).r)\n\n";
}
- DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, write_mask_as_depth);
+ DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1 + BoolToUInt32(write_depth_as_rt), false, write_mask_as_depth);
ss << R"(
{
float2 coords = floor(v_pos.xy / u_resolution_scale);
@@ -1492,20 +1526,25 @@ std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, b
o_col0 = RGBA5551ToRGBA8(value);
#if WRITE_MASK_AS_DEPTH
o_depth = (o_col0.a == 1.0) ? u_depth_value : 0.0;
+#elif WRITE_DEPTH_AS_RT
+ o_col1 = float4(1.0f, 0.0f, 0.0f, 0.0f);
#endif
})";
return ss.str();
}
-std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_depth) const
+std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_depth, bool write_depth_as_rt) const
{
+ Assert(!write_mask_as_depth || (write_mask_as_depth != write_depth_as_rt));
+
// TODO: This won't currently work because we can't bind the texture to both the shader and framebuffer.
const bool msaa = false;
std::stringstream ss;
WriteHeader(ss);
DefineMacro(ss, "WRITE_MASK_AS_DEPTH", write_mask_as_depth);
+ DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
DefineMacro(ss, "MSAA_COPY", msaa);
DeclareUniformBuffer(ss,
@@ -1514,7 +1553,8 @@ std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_
true);
DeclareTexture(ss, "samp0", 0, msaa);
- DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, write_mask_as_depth, false, false, msaa);
+ DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1 + BoolToUInt32(write_depth_as_rt), false, write_mask_as_depth, false,
+ false, msaa);
ss << R"(
{
float2 dst_coords = floor(v_pos.xy);
@@ -1544,25 +1584,31 @@ std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_
o_col0 = float4(color.xyz, u_set_mask_bit ? 1.0 : color.a);
#if WRITE_MASK_AS_DEPTH
o_depth = (u_set_mask_bit ? 1.0f : ((o_col0.a == 1.0) ? u_depth_value : 0.0));
+#elif WRITE_DEPTH_AS_RT
+ o_col1 = float4(1.0f, 0.0f, 0.0f, 0.0f);
#endif
})";
return ss.str();
}
-std::string GPU_HW_ShaderGen::GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced,
- bool write_mask_as_depth) const
+std::string GPU_HW_ShaderGen::GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced, bool write_mask_as_depth,
+ bool write_depth_as_rt) const
{
+ Assert(!write_mask_as_depth || (write_mask_as_depth != write_depth_as_rt));
+
std::stringstream ss;
WriteHeader(ss);
DefineMacro(ss, "WRITE_MASK_AS_DEPTH", write_mask_as_depth);
+ DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
DefineMacro(ss, "WRAPPED", wrapped);
DefineMacro(ss, "INTERLACED", interlaced);
DeclareUniformBuffer(
ss, {"uint2 u_dst_coords", "uint2 u_end_coords", "float4 u_fill_color", "uint u_interlaced_displayed_field"}, true);
- DeclareFragmentEntryPoint(ss, 0, 1, {}, interlaced || wrapped, 1, false, write_mask_as_depth, false, false, false);
+ DeclareFragmentEntryPoint(ss, 0, 1, {}, interlaced || wrapped, 1 + BoolToUInt32(write_depth_as_rt), false,
+ write_mask_as_depth, false, false, false);
ss << R"(
{
#if INTERLACED || WRAPPED
@@ -1586,6 +1632,8 @@ std::string GPU_HW_ShaderGen::GenerateVRAMFillFragmentShader(bool wrapped, bool
o_col0 = u_fill_color;
#if WRITE_MASK_AS_DEPTH
o_depth = u_fill_color.a;
+#elif WRITE_DEPTH_AS_RT
+ o_col1 = float4(1.0f, 0.0f, 0.0f, 0.0f);
#endif
})";
@@ -1811,6 +1859,9 @@ std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool semitr
#else
// Leave (0,0,0,0) as 0000 for opaque replacements for cutout alpha.
o_col0.a = color.a;
+
+ // Map anything with an alpha below 0.5 to transparent.
+ o_col0 = lerp(o_col0, float4(0.0, 0.0, 0.0, 0.0), float(o_col0.a < 0.5));
#endif
}
)";
diff --git a/src/core/gpu_hw_shadergen.h b/src/core/gpu_hw_shadergen.h
index 8894e484b..e68323564 100644
--- a/src/core/gpu_hw_shadergen.h
+++ b/src/core/gpu_hw_shadergen.h
@@ -26,12 +26,15 @@ public:
std::string GenerateWireframeGeometryShader() const;
std::string GenerateWireframeFragmentShader() const;
std::string GenerateVRAMReadFragmentShader(u32 resolution_scale, u32 multisamples) const;
- std::string GenerateVRAMWriteFragmentShader(bool use_buffer, bool use_ssbo, bool write_mask_as_depth) const;
- std::string GenerateVRAMCopyFragmentShader(bool write_mask_as_depth) const;
- std::string GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced, bool write_mask_as_depth) const;
+ std::string GenerateVRAMWriteFragmentShader(bool use_buffer, bool use_ssbo, bool write_mask_as_depth,
+ bool write_depth_as_rt) const;
+ std::string GenerateVRAMCopyFragmentShader(bool write_mask_as_depth, bool write_depth_as_rt) const;
+ std::string GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced, bool write_mask_as_depth,
+ bool write_depth_as_rt) const;
std::string GenerateVRAMUpdateDepthFragmentShader(bool msaa) const;
std::string GenerateVRAMExtractFragmentShader(u32 resolution_scale, u32 multisamples, bool color_24bit,
bool depth_buffer) const;
+ std::string GenerateVRAMReplacementBlitFragmentShader() const;
std::string GenerateAdaptiveDownsampleVertexShader() const;
std::string GenerateAdaptiveDownsampleMipFragmentShader() const;
diff --git a/src/core/gpu_hw_texture_cache.cpp b/src/core/gpu_hw_texture_cache.cpp
index f7f825887..5c2484914 100644
--- a/src/core/gpu_hw_texture_cache.cpp
+++ b/src/core/gpu_hw_texture_cache.cpp
@@ -16,6 +16,7 @@
#include "common/error.h"
#include "common/file_system.h"
#include "common/gsvector_formatter.h"
+#include "common/heterogeneous_containers.h"
#include "common/log.h"
#include "common/path.h"
#include "common/string_util.h"
@@ -128,7 +129,7 @@ struct TextureReplacementSubImage
{
GSVector4i dst_rect;
GSVector4i src_rect;
- const TextureReplacementImage& image;
+ GPUTexture* texture;
float scale_x;
float scale_y;
bool invert_alpha;
@@ -229,7 +230,8 @@ struct DumpedTextureKeyHash
} // namespace
using HashCache = std::unordered_map;
-using TextureCache = std::unordered_map;
+using ReplacementImageCache = PreferUnorderedStringMap;
+using GPUReplacementImageCache = PreferUnorderedStringMap, u32>>;
using VRAMReplacementMap = std::unordered_map;
using TextureReplacementMap =
@@ -303,7 +305,9 @@ static void FindTextureReplacements(bool load_vram_write_replacements, bool load
static void LoadTextureReplacementAliases(const ryml::ConstNodeRef& root, bool load_vram_write_replacement_aliases,
bool load_texture_replacement_aliases);
-static const TextureReplacementImage* GetTextureReplacementImage(const std::string& filename);
+static const TextureReplacementImage* GetTextureReplacementImage(const std::string& path);
+static GPUTexture* GetTextureReplacementGPUImage(const std::string& path);
+static void CompactTextureReplacementGPUImages();
static void PreloadReplacementTextures();
static void PurgeUnreferencedTexturesFromCache();
@@ -505,7 +509,6 @@ struct GPUTextureCacheState
{
Settings::TextureReplacementSettings::Configuration config;
size_t hash_cache_memory_usage = 0;
- size_t max_hash_cache_memory_usage = 1ULL * 1024ULL * 1024ULL * 1024ULL; // 2GB
VRAMWrite* last_vram_write = nullptr;
bool track_vram_writes = false;
@@ -529,7 +532,10 @@ struct GPUTextureCacheState
TextureReplacementMap texture_page_texture_replacements;
// TODO: Check the size, purge some when it gets too large.
- TextureCache replacement_image_cache;
+ ReplacementImageCache replacement_image_cache;
+ GPUReplacementImageCache gpu_replacement_image_cache;
+ size_t gpu_replacement_image_cache_vram_usage = 0;
+ std::vector> gpu_replacement_image_cache_purge_list;
std::unordered_set dumped_vram_writes;
std::unordered_set dumped_textures;
@@ -744,10 +750,18 @@ void GPUTextureCache::Shutdown()
ClearHashCache();
DestroyPipelines();
s_state.replacement_texture_render_target.reset();
+ s_state.gpu_replacement_image_cache_purge_list = {};
s_state.hash_cache_purge_list = {};
s_state.temp_vram_write_list = {};
s_state.track_vram_writes = false;
+ for (auto it = s_state.gpu_replacement_image_cache.begin(); it != s_state.gpu_replacement_image_cache.end();)
+ {
+ g_gpu_device->RecycleTexture(std::move(it->second.first));
+ it = s_state.gpu_replacement_image_cache.erase(it);
+ }
+ s_state.gpu_replacement_image_cache_vram_usage = 0;
+
s_state.replacement_image_cache.clear();
s_state.vram_replacements.clear();
s_state.vram_write_texture_replacements.clear();
@@ -2048,8 +2062,9 @@ GPUTextureCache::HashCacheEntry* GPUTextureCache::LookupHashCache(SourceKey key,
entry.ref_count = 0;
entry.last_used_frame = 0;
entry.sources = {};
- entry.texture = g_gpu_device->FetchTexture(TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, 1, 1, 1,
- GPUTexture::Type::Texture, GPUTexture::Format::RGBA8);
+ entry.texture =
+ g_gpu_device->FetchTexture(TEXTURE_PAGE_WIDTH, TEXTURE_PAGE_HEIGHT, 1, 1, 1, GPUTexture::Type::Texture,
+ GPUTexture::Format::RGBA8, GPUTexture::Flags::None);
if (!entry.texture)
{
ERROR_LOG("Failed to create texture.");
@@ -2098,10 +2113,11 @@ void GPUTextureCache::Compact()
static constexpr u32 MAX_HASH_CACHE_AGE = 600;
// Maximum number of textures which are permitted in the hash cache at the end of the frame.
- static constexpr u32 MAX_HASH_CACHE_SIZE = 500;
+ const u32 max_hash_cache_size = s_state.config.max_hash_cache_entries;
+ const size_t max_hash_cache_memory = static_cast(s_state.config.max_hash_cache_vram_usage_mb) * 1048576;
- bool might_need_cache_purge = (s_state.hash_cache.size() > MAX_HASH_CACHE_SIZE ||
- s_state.hash_cache_memory_usage >= s_state.max_hash_cache_memory_usage);
+ bool might_need_cache_purge =
+ (s_state.hash_cache.size() > max_hash_cache_size || s_state.hash_cache_memory_usage >= max_hash_cache_memory);
if (might_need_cache_purge)
s_state.hash_cache_purge_list.clear();
@@ -2120,8 +2136,8 @@ void GPUTextureCache::Compact()
// We might free up enough just with "normal" removals above.
if (might_need_cache_purge)
{
- might_need_cache_purge = (s_state.hash_cache.size() > MAX_HASH_CACHE_SIZE ||
- s_state.hash_cache_memory_usage >= s_state.max_hash_cache_memory_usage);
+ might_need_cache_purge =
+ (s_state.hash_cache.size() > max_hash_cache_size || s_state.hash_cache_memory_usage >= max_hash_cache_memory);
if (might_need_cache_purge)
s_state.hash_cache_purge_list.emplace_back(it, static_cast(e.last_used_frame));
}
@@ -2132,12 +2148,14 @@ void GPUTextureCache::Compact()
// Pushing to a list, sorting, and removing ends up faster than re-iterating the map.
if (might_need_cache_purge)
{
+ DEV_LOG("Force compacting hash cache, count = {}, size = {:.1f} MB", s_state.hash_cache.size(),
+ static_cast(s_state.hash_cache_memory_usage) / 1048576.0f);
+
std::sort(s_state.hash_cache_purge_list.begin(), s_state.hash_cache_purge_list.end(),
[](const auto& lhs, const auto& rhs) { return lhs.second < rhs.second; });
size_t purge_index = 0;
- while (s_state.hash_cache.size() > MAX_HASH_CACHE_SIZE ||
- s_state.hash_cache_memory_usage >= s_state.max_hash_cache_memory_usage)
+ while (s_state.hash_cache.size() > max_hash_cache_size || s_state.hash_cache_memory_usage >= max_hash_cache_memory)
{
if (purge_index == s_state.hash_cache_purge_list.size())
{
@@ -2148,7 +2166,12 @@ void GPUTextureCache::Compact()
RemoveFromHashCache(s_state.hash_cache_purge_list[purge_index++].first);
}
+
+ DEV_LOG("Finished compacting hash cache, count = {}, size = {:.1f} MB", s_state.hash_cache.size(),
+ static_cast(s_state.hash_cache_memory_usage) / 1048576.0f);
}
+
+ CompactTextureReplacementGPUImages();
}
size_t GPUTextureCache::HashCacheKeyHash::operator()(const HashCacheKey& k) const
@@ -2462,8 +2485,7 @@ void GPUTextureCache::SetGameID(std::string game_id)
ReloadTextureReplacements(false);
}
-const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetVRAMReplacement(u32 width, u32 height,
- const void* pixels)
+GPUTexture* GPUTextureCache::GetVRAMReplacement(u32 width, u32 height, const void* pixels)
{
const VRAMReplacementName hash = GetVRAMWriteHash(width, height, pixels);
@@ -2471,7 +2493,7 @@ const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetVRAMReplacem
if (it == s_state.vram_replacements.end())
return nullptr;
- return GetTextureReplacementImage(it->second);
+ return GetTextureReplacementGPUImage(it->second);
}
bool GPUTextureCache::ShouldDumpVRAMWrite(u32 width, u32 height)
@@ -2492,28 +2514,23 @@ void GPUTextureCache::DumpVRAMWrite(u32 width, u32 height, const void* pixels)
if (filename.empty() || FileSystem::FileExists(filename.c_str()))
return;
- RGBA8Image image;
- image.SetSize(width, height);
+ Image image(width, height, ImageFormat::RGBA8);
const u16* src_pixels = reinterpret_cast(pixels);
for (u32 y = 0; y < height; y++)
{
+ u8* row_ptr = image.GetPixels();
for (u32 x = 0; x < width; x++)
{
- image.SetPixel(x, y, VRAMRGBA5551ToRGBA8888(*src_pixels));
- src_pixels++;
+ const u32 pixel32 = VRAMRGBA5551ToRGBA8888(*(src_pixels++));
+ std::memcpy(row_ptr, &pixel32, sizeof(pixel32));
+ row_ptr += sizeof(pixel32);
}
}
if (s_state.config.dump_vram_write_force_alpha_channel)
- {
- for (u32 y = 0; y < height; y++)
- {
- for (u32 x = 0; x < width; x++)
- image.SetPixel(x, y, image.GetPixel(x, y) | 0xFF000000u);
- }
- }
+ image.SetAllPixelsOpaque();
INFO_LOG("Dumping {}x{} VRAM write to '{}'", width, height, Path::GetFileName(filename));
if (!image.SaveToFile(filename.c_str())) [[unlikely]]
@@ -2598,12 +2615,13 @@ void GPUTextureCache::DumpTexture(TextureReplacementType type, u32 offset_x, u32
DEV_LOG("Dumping VRAM write {:016X} [{}x{}] at {}", src_hash, width, height, rect);
- RGBA8Image image(width, height);
- GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data, image.GetPixels(),
- image.GetPitch(), width, height);
+ Image image(width, height, ImageFormat::RGBA8);
+ GPUTextureCache::DecodeTexture(mode, &g_vram[rect.top * VRAM_WIDTH + rect.left], palette_data,
+ reinterpret_cast(image.GetPixels()), image.GetPitch(), width, height);
- u32* image_pixels = image.GetPixels();
- const u32* image_pixels_end = image.GetPixels() + (width * height);
+ // TODO: Vectorize this.
+ u32* image_pixels = reinterpret_cast(image.GetPixels());
+ const u32* image_pixels_end = image_pixels + (width * height);
if (s_state.config.dump_texture_force_alpha_channel)
{
for (u32* pixel = image_pixels; pixel != image_pixels_end; pixel++)
@@ -2678,12 +2696,7 @@ void GPUTextureCache::GetVRAMWriteTextureReplacements(std::vectorsecond.first))
continue;
- const TextureReplacementImage* image = GetTextureReplacementImage(it->second.second);
- if (!image)
- continue;
-
const TextureReplacementName& name = it->second.first;
- const GSVector2 scale = GSVector2(GSVector2i(image->GetWidth(), image->GetHeight())) / GSVector2(name.GetSizeVec());
const GSVector4i rect_in_write_space = name.GetDestRect();
const GSVector4i rect_in_page_space = rect_in_write_space.sub32(offset_to_page_v);
@@ -2703,7 +2716,12 @@ void GPUTextureCache::GetVRAMWriteTextureReplacements(std::vector(TEXTURE_PAGE_WIDTH));
DebugAssert(rect_in_page_space.height() <= static_cast(TEXTURE_PAGE_HEIGHT));
- replacements.push_back(TextureReplacementSubImage{rect_in_page_space, GSVector4i::zero(), *image, scale.x, scale.y,
+ GPUTexture* texture = GetTextureReplacementGPUImage(it->second.second);
+ if (!texture)
+ continue;
+
+ const GSVector2 scale = GSVector2(texture->GetSizeVec()) / GSVector2(name.GetSizeVec());
+ replacements.push_back(TextureReplacementSubImage{rect_in_page_space, GSVector4i::zero(), texture, scale.x, scale.y,
name.IsSemitransparent()});
}
}
@@ -2758,12 +2776,12 @@ void GPUTextureCache::GetTexturePageTextureReplacements(std::vectorsecond.second);
- if (!image)
+ GPUTexture* texture = GetTextureReplacementGPUImage(it->second.second);
+ if (!texture)
continue;
- const GSVector2 scale = GSVector2(GSVector2i(image->GetWidth(), image->GetHeight())) / GSVector2(name.GetSizeVec());
- replacements.push_back(TextureReplacementSubImage{rect_in_page_space, GSVector4i::zero(), *image, scale.x, scale.y,
+ const GSVector2 scale = GSVector2(texture->GetSizeVec()) / GSVector2(name.GetSizeVec());
+ replacements.push_back(TextureReplacementSubImage{rect_in_page_space, GSVector4i::zero(), texture, scale.x, scale.y,
name.IsSemitransparent()});
}
}
@@ -2969,24 +2987,115 @@ void GPUTextureCache::LoadTextureReplacementAliases(const ryml::ConstNodeRef& ro
s_state.game_id);
}
-const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& filename)
+const GPUTextureCache::TextureReplacementImage* GPUTextureCache::GetTextureReplacementImage(const std::string& path)
{
- auto it = s_state.replacement_image_cache.find(filename);
+ auto it = s_state.replacement_image_cache.find(path);
if (it != s_state.replacement_image_cache.end())
return &it->second;
- RGBA8Image image;
- if (!image.LoadFromFile(filename.c_str()))
+ Image image;
+ Error error;
+ if (!image.LoadFromFile(path.c_str(), &error))
{
- ERROR_LOG("Failed to load '{}'", Path::GetFileName(filename));
+ ERROR_LOG("Failed to load '{}': {}", Path::GetFileName(path), error.GetDescription());
return nullptr;
}
- VERBOSE_LOG("Loaded '{}': {}x{}", Path::GetFileName(filename), image.GetWidth(), image.GetHeight());
- it = s_state.replacement_image_cache.emplace(filename, std::move(image)).first;
+ VERBOSE_LOG("Loaded '{}': {}x{} {}", Path::GetFileName(path), image.GetWidth(), image.GetHeight(),
+ Image::GetFormatName(image.GetFormat()));
+ it = s_state.replacement_image_cache.emplace(path, std::move(image)).first;
return &it->second;
}
+GPUTexture* GPUTextureCache::GetTextureReplacementGPUImage(const std::string& path)
+{
+ // Already in cache?
+ const auto git = s_state.gpu_replacement_image_cache.find(path);
+ if (git != s_state.gpu_replacement_image_cache.end())
+ {
+ git->second.second = System::GetFrameNumber();
+ return git->second.first.get();
+ }
+
+ // Need to upload it.
+ Error error;
+ std::unique_ptr tex;
+
+ // Check CPU cache first.
+ const auto it = s_state.replacement_image_cache.find(path);
+ if (it != s_state.replacement_image_cache.end())
+ {
+ tex = g_gpu_device->FetchAndUploadTextureImage(it->second, GPUTexture::Flags::None, &error);
+ }
+ else
+ {
+ // Need to load it.
+ Image cpu_image;
+ if (cpu_image.LoadFromFile(path.c_str(), &error))
+ tex = g_gpu_device->FetchAndUploadTextureImage(cpu_image, GPUTexture::Flags::None, &error);
+ }
+
+ if (!tex)
+ {
+ ERROR_LOG("Failed to load/upload '{}': {}", Path::GetFileName(path), error.GetDescription());
+ return nullptr;
+ }
+
+ const size_t vram_usage = tex->GetVRAMUsage();
+ s_state.gpu_replacement_image_cache_vram_usage += vram_usage;
+
+ VERBOSE_LOG("Uploaded '{}': {}x{} {} {:.2f} KB", Path::GetFileName(path), tex->GetWidth(), tex->GetHeight(),
+ GPUTexture::GetFormatName(tex->GetFormat()), static_cast(vram_usage) / 1024.0f);
+
+ return s_state.gpu_replacement_image_cache.emplace(path, std::make_pair(std::move(tex), System::GetFrameNumber()))
+ .first->second.first.get();
+}
+
+void GPUTextureCache::CompactTextureReplacementGPUImages()
+{
+ // Instead of compacting to exactly the maximum, let's go down to the maximum less 16MB.
+ // That way we can hopefully avoid compacting again for a few frames.
+ static constexpr size_t EXTRA_COMPACT_SIZE = 16 * 1024 * 1024;
+
+ const size_t max_usage = static_cast(s_state.config.max_replacement_cache_vram_usage_mb) * 1048576;
+ if (s_state.gpu_replacement_image_cache_vram_usage <= max_usage)
+ return;
+
+ DEV_LOG("Compacting replacement GPU image cache, count = {}, size = {:.1f} MB",
+ s_state.gpu_replacement_image_cache.size(),
+ static_cast(s_state.gpu_replacement_image_cache_vram_usage) / 1048576.0f);
+
+ const u32 frame_number = System::GetFrameNumber();
+ s_state.gpu_replacement_image_cache_purge_list.reserve(s_state.gpu_replacement_image_cache.size());
+ for (auto it = s_state.gpu_replacement_image_cache.begin(); it != s_state.gpu_replacement_image_cache.end(); ++it)
+ s_state.gpu_replacement_image_cache_purge_list.emplace_back(it, frame_number - it->second.second);
+
+ // Reverse sort, put the oldest on the end.
+ std::sort(s_state.gpu_replacement_image_cache_purge_list.begin(),
+ s_state.gpu_replacement_image_cache_purge_list.end(),
+ [](const auto& lhs, const auto& rhs) { return lhs.second > rhs.second; });
+
+ // See first comment above.
+ const size_t target_size = (max_usage < EXTRA_COMPACT_SIZE) ? max_usage : (max_usage - EXTRA_COMPACT_SIZE);
+ while (s_state.gpu_replacement_image_cache_vram_usage > target_size &&
+ !s_state.gpu_replacement_image_cache_purge_list.empty())
+ {
+ GPUReplacementImageCache::iterator iter = s_state.gpu_replacement_image_cache_purge_list.back().first;
+ s_state.gpu_replacement_image_cache_purge_list.pop_back();
+
+ std::unique_ptr tex = std::move(iter->second.first);
+ s_state.gpu_replacement_image_cache.erase(iter);
+ s_state.gpu_replacement_image_cache_vram_usage -= tex->GetVRAMUsage();
+ g_gpu_device->RecycleTexture(std::move(tex));
+ }
+
+ s_state.gpu_replacement_image_cache_purge_list.clear();
+
+ DEV_LOG("Finished compacting replacement GPU image cache, count = {}, size = {:.1f} MB",
+ s_state.gpu_replacement_image_cache.size(),
+ static_cast(s_state.gpu_replacement_image_cache_vram_usage) / 1048576.0f);
+}
+
void GPUTextureCache::PreloadReplacementTextures()
{
static constexpr float UPDATE_INTERVAL = 1.0f;
@@ -3137,9 +3246,6 @@ bool GPUTextureCache::LoadLocalConfiguration(bool load_vram_write_replacement_al
.value_or(static_cast(s_state.config.reduce_palette_range));
s_state.config.convert_copies_to_writes = GetOptionalTFromObject(root, "ConvertCopiesToWrites")
.value_or(static_cast(s_state.config.convert_copies_to_writes));
- s_state.config.replacement_scale_linear_filter =
- GetOptionalTFromObject(root, "ReplacementScaleLinearFilter")
- .value_or(static_cast(s_state.config.replacement_scale_linear_filter));
s_state.config.max_vram_write_splits =
GetOptionalTFromObject(root, "MaxVRAMWriteSplits").value_or(s_state.config.max_vram_write_splits);
s_state.config.max_vram_write_coalesce_width = GetOptionalTFromObject(root, "MaxVRAMWriteCoalesceWidth")
@@ -3154,6 +3260,15 @@ bool GPUTextureCache::LoadLocalConfiguration(bool load_vram_write_replacement_al
.value_or(s_state.config.vram_write_dump_width_threshold);
s_state.config.vram_write_dump_height_threshold = GetOptionalTFromObject(root, "DumpVRAMWriteHeightThreshold")
.value_or(s_state.config.vram_write_dump_height_threshold);
+ s_state.config.max_hash_cache_entries =
+ GetOptionalTFromObject(root, "MaxHashCacheEntries").value_or(s_state.config.max_hash_cache_entries);
+ s_state.config.max_hash_cache_vram_usage_mb =
+ GetOptionalTFromObject(root, "MaxHashCacheVRAMUsageMB").value_or(s_state.config.max_hash_cache_vram_usage_mb);
+ s_state.config.max_replacement_cache_vram_usage_mb = GetOptionalTFromObject(root, "MaxReplacementCacheVRAMUsage")
+ .value_or(s_state.config.max_replacement_cache_vram_usage_mb);
+ s_state.config.replacement_scale_linear_filter =
+ GetOptionalTFromObject(root, "ReplacementScaleLinearFilter")
+ .value_or(static_cast(s_state.config.replacement_scale_linear_filter));
if (load_vram_write_replacement_aliases || load_texture_replacement_aliases)
{
@@ -3204,31 +3319,35 @@ void GPUTextureCache::ReloadTextureReplacements(bool show_info)
void GPUTextureCache::PurgeUnreferencedTexturesFromCache()
{
- TextureCache old_map = std::move(s_state.replacement_image_cache);
- s_state.replacement_image_cache = {};
+ ReplacementImageCache old_map = std::move(s_state.replacement_image_cache);
+ GPUReplacementImageCache old_gpu_map = std::move(s_state.gpu_replacement_image_cache);
+ s_state.replacement_image_cache = ReplacementImageCache();
+ s_state.gpu_replacement_image_cache = GPUReplacementImageCache();
- for (const auto& it : s_state.vram_replacements)
- {
- const auto it2 = old_map.find(it.second);
+ const auto reinsert_texture = [&old_map, &old_gpu_map](const std::string& name) {
+ const auto it2 = old_map.find(name);
if (it2 != old_map.end())
{
- s_state.replacement_image_cache[it.second] = std::move(it2->second);
+ s_state.replacement_image_cache.emplace(name, std::move(it2->second));
old_map.erase(it2);
}
- }
- for (const auto& map : {s_state.vram_write_texture_replacements, s_state.texture_page_texture_replacements})
- {
- for (const auto& it : map)
+ const auto it3 = old_gpu_map.find(name);
+ if (it3 != old_gpu_map.end())
{
- const auto it2 = old_map.find(it.second.second);
- if (it2 != old_map.end())
- {
- s_state.replacement_image_cache[it.second.second] = std::move(it2->second);
- old_map.erase(it2);
- }
+ s_state.gpu_replacement_image_cache.emplace(name, std::move(it3->second));
+ old_gpu_map.erase(it3);
}
- }
+ };
+
+ for (const auto& it : s_state.vram_replacements)
+ reinsert_texture(it.second);
+
+ for (const auto& it : s_state.vram_write_texture_replacements)
+ reinsert_texture(it.second.second);
+
+ for (const auto& it : s_state.texture_page_texture_replacements)
+ reinsert_texture(it.second.second);
}
void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash, HashType pal_hash,
@@ -3285,8 +3404,9 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
{
// NOTE: Not recycled, it's unlikely to be reused.
s_state.replacement_texture_render_target.reset();
- if (!(s_state.replacement_texture_render_target = g_gpu_device->CreateTexture(
- new_width, new_height, 1, 1, 1, GPUTexture::Type::RenderTarget, REPLACEMENT_TEXTURE_FORMAT)))
+ if (!(s_state.replacement_texture_render_target =
+ g_gpu_device->CreateTexture(new_width, new_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
+ REPLACEMENT_TEXTURE_FORMAT, GPUTexture::Flags::None)))
{
ERROR_LOG("Failed to create {}x{} render target.", new_width, new_height);
return;
@@ -3294,8 +3414,8 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
}
// Grab the actual texture beforehand, in case we OOM.
- std::unique_ptr replacement_tex =
- g_gpu_device->FetchTexture(new_width, new_height, 1, 1, 1, GPUTexture::Type::Texture, REPLACEMENT_TEXTURE_FORMAT);
+ std::unique_ptr replacement_tex = g_gpu_device->FetchTexture(
+ new_width, new_height, 1, 1, 1, GPUTexture::Type::Texture, REPLACEMENT_TEXTURE_FORMAT, GPUTexture::Flags::None);
if (!replacement_tex)
{
ERROR_LOG("Failed to create {}x{} texture.", new_width, new_height);
@@ -3317,18 +3437,12 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
for (const TextureReplacementSubImage& si : subimages)
{
- const auto temp_texture = g_gpu_device->FetchAutoRecycleTexture(
- si.image.GetWidth(), si.image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture, REPLACEMENT_TEXTURE_FORMAT,
- si.image.GetPixels(), si.image.GetPitch());
- if (!temp_texture)
- continue;
-
const GSVector4i dst_rect = GSVector4i(GSVector4(si.dst_rect) * max_scale_v);
- texture_size = GSVector2(GSVector2i(temp_texture->GetWidth(), temp_texture->GetHeight()));
+ texture_size = GSVector2(si.texture->GetSizeVec());
GSVector2::store(&uniforms[0], texture_size);
GSVector2::store(&uniforms[2], GSVector2::cxpr(1.0f) / texture_size);
g_gpu_device->SetViewportAndScissor(dst_rect);
- g_gpu_device->SetTextureSampler(0, temp_texture.get(), g_gpu_device->GetNearestSampler());
+ g_gpu_device->SetTextureSampler(0, si.texture, g_gpu_device->GetNearestSampler());
g_gpu_device->SetPipeline(si.invert_alpha ? s_state.replacement_semitransparent_draw_pipeline.get() :
s_state.replacement_draw_pipeline.get());
g_gpu_device->Draw(3, 0);
diff --git a/src/core/gpu_hw_texture_cache.h b/src/core/gpu_hw_texture_cache.h
index 8fc601e64..07482da7b 100644
--- a/src/core/gpu_hw_texture_cache.h
+++ b/src/core/gpu_hw_texture_cache.h
@@ -5,8 +5,8 @@
#include "gpu_types.h"
+class Image;
class GPUTexture;
-class RGBA8Image;
class StateWrapper;
struct Settings;
@@ -29,7 +29,7 @@ enum class PaletteRecordFlags : u32
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(PaletteRecordFlags);
using HashType = u64;
-using TextureReplacementImage = RGBA8Image;
+using TextureReplacementImage = Image;
struct Source;
struct HashCacheEntry;
@@ -128,7 +128,7 @@ void SetGameID(std::string game_id);
void ReloadTextureReplacements(bool show_info);
// VRAM Write Replacements
-const TextureReplacementImage* GetVRAMReplacement(u32 width, u32 height, const void* pixels);
+GPUTexture* GetVRAMReplacement(u32 width, u32 height, const void* pixels);
void DumpVRAMWrite(u32 width, u32 height, const void* pixels);
bool ShouldDumpVRAMWrite(u32 width, u32 height);
diff --git a/src/core/gpu_sw.cpp b/src/core/gpu_sw.cpp
index 068db4164..c8b12fa87 100644
--- a/src/core/gpu_sw.cpp
+++ b/src/core/gpu_sw.cpp
@@ -99,8 +99,8 @@ GPUTexture* GPU_SW::GetDisplayTexture(u32 width, u32 height, GPUTexture::Format
{
ClearDisplayTexture();
g_gpu_device->RecycleTexture(std::move(m_upload_texture));
- m_upload_texture =
- g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::DynamicTexture, format, nullptr, 0);
+ m_upload_texture = g_gpu_device->FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::Texture, format,
+ GPUTexture::Flags::AllowMap, nullptr, 0);
if (!m_upload_texture) [[unlikely]]
ERROR_LOG("Failed to create {}x{} {} texture", width, height, static_cast(format));
}
@@ -824,11 +824,7 @@ void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
m_backend.PushCommand(cmd);
}
-std::unique_ptr GPU::CreateSoftwareRenderer(Error* error)
+std::unique_ptr GPU::CreateSoftwareRenderer()
{
- std::unique_ptr gpu(std::make_unique());
- if (!gpu->Initialize(error))
- gpu.reset();
-
- return gpu;
+ return std::make_unique();
}
diff --git a/src/core/gte.cpp b/src/core/gte.cpp
index f6d340f41..6b0dcb1ff 100644
--- a/src/core/gte.cpp
+++ b/src/core/gte.cpp
@@ -2,7 +2,6 @@
// SPDX-License-Identifier: CC-BY-NC-ND-4.0
#include "gte.h"
-
#include "cpu_core.h"
#include "cpu_core_private.h"
#include "cpu_pgxp.h"
@@ -227,47 +226,22 @@ bool GTE::DoState(StateWrapper& sw)
return !sw.HasError();
}
-void GTE::UpdateAspectRatio(u32 window_width, u32 window_height)
+void GTE::SetAspectRatio(DisplayAspectRatio aspect, u32 custom_num, u32 custom_denom)
{
- if (!g_settings.gpu_widescreen_hack)
- {
- s_config.aspect_ratio = DisplayAspectRatio::R4_3;
+ s_config.aspect_ratio = aspect;
+ if (aspect != DisplayAspectRatio::Custom)
return;
- }
-
- s_config.aspect_ratio = g_settings.display_aspect_ratio;
-
- u32 num, denom;
- switch (s_config.aspect_ratio)
- {
- case DisplayAspectRatio::MatchWindow:
- {
- num = window_width;
- denom = window_height;
- }
- break;
-
- case DisplayAspectRatio::Custom:
- {
- num = g_settings.display_aspect_ratio_custom_numerator;
- denom = g_settings.display_aspect_ratio_custom_denominator;
- }
- break;
-
- default:
- return;
- }
// (4 / 3) / (num / denom) => gcd((4 * denom) / (3 * num))
- const u32 x = 4u * denom;
- const u32 y = 3u * num;
+ const u32 x = 4u * custom_denom;
+ const u32 y = 3u * custom_num;
const u32 gcd = std::gcd(x, y);
s_config.custom_aspect_ratio_numerator = x / gcd;
s_config.custom_aspect_ratio_denominator = y / gcd;
s_config.custom_aspect_ratio_f =
- static_cast((4.0 / 3.0) / (static_cast(num) / static_cast(denom)));
+ static_cast((4.0 / 3.0) / (static_cast(custom_num) / static_cast(custom_denom)));
}
u32 GTE::ReadRegister(u32 index)
@@ -709,7 +683,6 @@ void GTE::RTPS(const s16 V[3], u8 shift, bool lm, bool last)
break;
case DisplayAspectRatio::Custom:
- case DisplayAspectRatio::MatchWindow:
Sx = ((((s64(result) * s64(REGS.IR1)) * s64(s_config.custom_aspect_ratio_numerator)) /
s64(s_config.custom_aspect_ratio_denominator)) +
s64(REGS.OFX));
@@ -764,7 +737,6 @@ void GTE::RTPS(const s16 V[3], u8 shift, bool lm, bool last)
switch (s_config.aspect_ratio)
{
- case DisplayAspectRatio::MatchWindow:
case DisplayAspectRatio::Custom:
precise_x = precise_x * s_config.custom_aspect_ratio_f;
break;
diff --git a/src/core/gte.h b/src/core/gte.h
index 444b39c10..eeb702251 100644
--- a/src/core/gte.h
+++ b/src/core/gte.h
@@ -6,12 +6,14 @@
class StateWrapper;
+enum class DisplayAspectRatio : u8;
+
namespace GTE {
void Initialize();
void Reset();
bool DoState(StateWrapper& sw);
-void UpdateAspectRatio(u32 window_width, u32 window_height);
+void SetAspectRatio(DisplayAspectRatio aspect, u32 custom_num, u32 custom_denom);
// control registers are offset by +32
u32 ReadRegister(u32 index);
diff --git a/src/core/host.cpp b/src/core/host.cpp
index 1d26548be..22a23be10 100644
--- a/src/core/host.cpp
+++ b/src/core/host.cpp
@@ -465,13 +465,11 @@ void Host::UpdateDisplayWindow(bool fullscreen)
return;
}
- const u32 new_width = g_gpu_device->GetMainSwapChain()->GetWidth();
- const u32 new_height = g_gpu_device->GetMainSwapChain()->GetHeight();
- const float f_width = static_cast(new_width);
- const float f_height = static_cast(new_height);
+ const float f_width = static_cast(g_gpu_device->GetMainSwapChain()->GetWidth());
+ const float f_height = static_cast(g_gpu_device->GetMainSwapChain()->GetHeight());
ImGuiManager::WindowResized(f_width, f_height);
InputManager::SetDisplayWindowSize(f_width, f_height);
- System::DisplayWindowResized(new_width, new_height);
+ System::DisplayWindowResized();
}
void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
@@ -489,13 +487,11 @@ void Host::ResizeDisplayWindow(s32 width, s32 height, float scale)
return;
}
- const u32 new_width = g_gpu_device->GetMainSwapChain()->GetWidth();
- const u32 new_height = g_gpu_device->GetMainSwapChain()->GetHeight();
- const float f_width = static_cast(new_width);
- const float f_height = static_cast