Merge branch 'stenzek:master' into master

This commit is contained in:
Daniel Nylander 2024-11-27 13:42:00 +01:00 committed by GitHub
commit d549ce2f85
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
107 changed files with 5911 additions and 2131 deletions

View File

Before

Width:  |  Height:  |  Size: 241 B

After

Width:  |  Height:  |  Size: 241 B

View File

@ -0,0 +1,2 @@
<?xml version="1.0" encoding="UTF-8"?>
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 9 6" width="900" height="600"><rect fill="#fff" width="9" height="3"/><rect fill="#d52b1e" y="3" width="9" height="3"/><rect fill="#0039a6" y="2" width="9" height="2"/></svg>

After

Width:  |  Height:  |  Size: 266 B

View File

@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="800" viewBox="0 -30000 90000 60000">
<title>Flag of Turkey</title>
<path fill="#e30a17" d="m0-30000h90000v60000H0z"/>
<path fill="#fff" d="m41750 0 13568-4408-8386 11541V-7133l8386 11541zm925 8021a15000 15000 0 1 1 0-16042 12000 12000 0 1 0 0 16042z"/>
</svg>

After

Width:  |  Height:  |  Size: 323 B

View File

@ -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

View File

@ -2563,6 +2563,31 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
</pre>
<h3>Texture Decompression Routines</h3>
<pre>
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.
</pre>
Some shaders provided with the application are sourced from:
<ul>
<li><a href="https://github.com/Matsilagi/RSRetroArch/">https://github.com/Matsilagi/RSRetroArch/</a></li>

View File

@ -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<std::string> 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<char[]> name_buf = std::make_unique<char[]>(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<const u8> 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<DynamicHeapArray<u8>> FileSystem::ReadBinaryFile(const char* filename, Error* error)
std::optional<DynamicHeapArray<u8>> FileSystem::ReadBinaryFile(const char* path, Error* error)
{
std::optional<DynamicHeapArray<u8>> ret;
ManagedCFilePtr fp = OpenManagedCFile(filename, "rb", error);
ManagedCFilePtr fp = OpenManagedCFile(path, "rb", error);
if (!fp)
return ret;
@ -1506,11 +1510,11 @@ std::optional<DynamicHeapArray<u8>> FileSystem::ReadBinaryFile(std::FILE* fp, Er
return ret;
}
std::optional<std::string> FileSystem::ReadFileToString(const char* filename, Error* error)
std::optional<std::string> FileSystem::ReadFileToString(const char* path, Error* error)
{
std::optional<std::string> ret;
ManagedCFilePtr fp = OpenManagedCFile(filename, "rb", error);
ManagedCFilePtr fp = OpenManagedCFile(path, "rb", error);
if (!fp)
return ret;
@ -1562,9 +1566,9 @@ std::optional<std::string> 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<const u8> 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<time_t>(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;
}
bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
return true;
}
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;
}
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
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, 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<char*>(std::realloc(nullptr, curSize));
for (;;)
{
int len = readlink(exeFileName, buffer, curSize);
int len = readlink(exe_path, buffer, curSize);
if (len < 0)
{
std::free(buffer);

View File

@ -10,6 +10,7 @@
#include <ctime>
#include <memory>
#include <optional>
#include <span>
#include <string>
#include <sys/stat.h>
#include <vector>
@ -65,10 +66,10 @@ std::vector<std::string> 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<std::FILE, FileDeleter>;
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<std::FILE, AtomicRenamedFileDeleter>;
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<const u8> data, Error* error = nullptr);
bool CommitAtomicRenamedFile(AtomicRenamedFile& file, Error* error);
void DiscardAtomicRenamedFile(AtomicRenamedFile& file);
@ -166,12 +168,13 @@ private:
};
#endif
std::optional<DynamicHeapArray<u8>> ReadBinaryFile(const char* filename, Error* error = nullptr);
std::optional<DynamicHeapArray<u8>> ReadBinaryFile(const char* path, Error* error = nullptr);
std::optional<DynamicHeapArray<u8>> ReadBinaryFile(std::FILE* fp, Error* error = nullptr);
std::optional<std::string> ReadFileToString(const char* filename, Error* error = nullptr);
std::optional<std::string> ReadFileToString(const char* path, Error* error = nullptr);
std::optional<std::string> 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<const u8> 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.

View File

@ -31,7 +31,6 @@
X(GPUDevice) \
X(GPUDump) \
X(GPUShaderCache) \
X(GPUTexture) \
X(GPUTextureCache) \
X(GPU_HW) \
X(GPU_SW) \

View File

@ -395,7 +395,7 @@ void Timer::NanoSleep(std::uint64_t ns)
// Round down to the next millisecond.
usleep(static_cast<useconds_t>((ns / 1000000) * 1000));
#else
const struct timespec ts = {0, static_cast<long>(ns)};
const struct timespec ts = {static_cast<long>(ns / 1000000000ULL), static_cast<long>(ns % 1000000000ULL)};
nanosleep(&ts, nullptr);
#endif
}

View File

@ -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())

View File

@ -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();

View File

@ -263,7 +263,7 @@ union XA_ADPCMBlockHeader
u8 bits;
BitField<u8, u8, 0, 4> shift;
BitField<u8, u8, 4, 2> filter;
BitField<u8, u8, 4, 4> 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<bool IS_STEREO, bool IS_8BIT>
void CDROM::DecodeXAADPCMChunks(const u8* chunk_ptr, s16* samples)
{
static constexpr std::array<s8, 16> 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<s8, 16> 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<s8, 16> filter_table_pos = {{0, 60, 115, 98, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<s8, 16> 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<s32>(
static_cast<s32>(sample) + ((prev[0] * filter_pos) >> 6) + ((prev[1] * filter_neg) >> 6), -32767, 32768);
static_cast<s32>(sample) + ((prev[0] * filter_pos) >> 6) + ((prev[1] * filter_neg) >> 6), -32768, 32767);
// update previous values
prev[1] = prev[0];

View File

@ -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)
{

View File

@ -21,6 +21,7 @@
#include "ryml.hpp"
#include <bit>
#include <iomanip>
#include <memory>
#include <optional>
@ -138,9 +139,9 @@ static constexpr const std::array<const char*, static_cast<size_t>(Trait::MaxCou
}};
static constexpr std::array<const char*, static_cast<size_t>(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::Language> 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<size_t>(GameDatabase::Language::English)))
ret = GameDatabase::GetLanguageName(static_cast<GameDatabase::Language>(std::countr_zero(languages.to_ulong())));
else
ret = Settings::GetDiscRegionName(region);
return ret;
}
SmallString GameDatabase::Entry::GetLanguagesString() const
{
SmallString ret;

View File

@ -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<int>(trait)]; }
ALWAYS_INLINE bool HasLanguage(Language language) const { return languages.test(static_cast<size_t>(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<Language> ParseLanguageName(std::string_view str);
TinyString GetLanguageFlagResourceName(std::string_view language_name);
/// Map of track hashes for image verification
struct TrackData

View File

@ -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<TinyString> custom_language_str =
custom_attributes_ini.GetOptionalTinyStringValue(path.c_str(), "Language");
if (custom_language_str.has_value())
{
const std::optional<GameDatabase::Language> 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<std::recursive_mutex> 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<size_t>(GameDatabase::Language::English)))
{
ret = GameDatabase::GetLanguageName(
static_cast<GameDatabase::Language>(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<DiscRegion> custom_region)
bool GameList::SaveCustomRegionForPath(const std::string& path, const std::optional<DiscRegion> 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<GameDatabase::Language> 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());

View File

@ -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<std::string>& url_templates, bool use_seri
std::function<void(const Entry*, std::string)> 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<DiscRegion> custom_region);
bool SaveCustomTitleForPath(const std::string& path, const std::string& custom_title);
bool SaveCustomRegionForPath(const std::string& path, const std::optional<DiscRegion> custom_region);
bool SaveCustomLanguageForPath(const std::string& path, const std::optional<GameDatabase::Language> custom_language);
std::string GetCustomTitleForPath(const std::string_view path);
std::optional<DiscRegion> GetCustomRegionForPath(const std::string_view path);

View File

@ -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<u32> 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,21 +594,61 @@ float GPU::ComputeVerticalFrequency() const
float GPU::ComputeDisplayAspectRatio() const
{
if (g_settings.debugging.show_vram)
{
return static_cast<float>(VRAM_WIDTH) / static_cast<float>(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;
// PAR 1:1 is not corrected.
if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
return static_cast<float>(m_crtc_state.display_width) / static_cast<float>(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)
{
if (g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow && g_gpu_device->HasMainSwapChain())
{
ar = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) /
static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
}
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Auto)
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Custom)
{
ar = static_cast<float>(g_settings.display_aspect_ratio_custom_numerator) /
static_cast<float>(g_settings.display_aspect_ratio_custom_denominator);
}
else
{
ar = g_settings.GetDisplayAspectRatioValue();
}
}
return ar;
}
float GPU::ComputeSourceAspectRatio() const
{
const float source_aspect_ratio =
static_cast<float>(m_crtc_state.display_width) / static_cast<float>(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<float>(cs.horizontal_visible_end - cs.horizontal_visible_start);
float relative_height = static_cast<float>(cs.vertical_visible_end - cs.vertical_visible_start);
if (relative_width <= 0 || relative_height <= 0)
return 4.0f / 3.0f;
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)
{
@ -622,18 +660,25 @@ float GPU::ComputeDisplayAspectRatio() const
relative_width /= static_cast<float>(NTSC_HORIZONTAL_ACTIVE_END - NTSC_HORIZONTAL_ACTIVE_START);
relative_height /= static_cast<float>(NTSC_VERTICAL_ACTIVE_END - NTSC_VERTICAL_ACTIVE_START);
}
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<float>(m_crtc_state.display_width) / static_cast<float>(m_crtc_state.display_height);
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
{
return g_settings.GetDisplayAspectRatioValue();
// stretch width, preserve height
*width = std::ceil(*width * par);
}
}
@ -727,6 +772,10 @@ void GPU::UpdateCRTCDisplayParameters()
(std::min<u16>(cs.regs.X2, horizontal_total) / cs.dot_clock_divider) * cs.dot_clock_divider;
const u16 vertical_display_start = std::min<u16>(cs.regs.Y1, vertical_total);
const u16 vertical_display_end = std::min<u16>(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<u16>(std::max<int>(0, 628 + g_settings.display_active_start_offset));
cs.horizontal_visible_end =
static_cast<u16>(std::max<int>(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<u16>(std::max<int>(0, 608 + g_settings.display_active_start_offset));
cs.horizontal_visible_end =
static_cast<u16>(std::max<int>(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<float>(window_width) / static_cast<float>(window_height);
const float crtc_display_width = static_cast<float>(show_vram ? VRAM_WIDTH : m_crtc_state.display_width);
const float crtc_display_height = static_cast<float>(show_vram ? VRAM_HEIGHT : m_crtc_state.display_height);
const float x_scale =
apply_aspect_ratio ?
(display_aspect_ratio / (static_cast<float>(crtc_display_width) / static_cast<float>(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<float>(show_vram ? 0 : m_crtc_state.display_origin_left);
float active_top = static_cast<float>(show_vram ? 0 : m_crtc_state.display_origin_top);
float active_width = static_cast<float>(show_vram ? VRAM_WIDTH : m_crtc_state.display_vram_width);
float active_height = static_cast<float>(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<u32> 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;
const char* extension = std::strrchr(filename.c_str(), '.');
if (extension)
{
if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
{
if (clear_alpha)
{
for (u32& pixel : texture_data)
pixel |= 0xFF000000u;
}
Error error;
if (flip_y)
GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast<u8*>(texture_data.data()),
texture_data_stride);
image.FlipY();
Assert(texture_data_stride == sizeof(u32) * width);
RGBA8Image image(width, height, std::move(texture_data));
if (image.SaveToFile(filename.c_str(), fp.get(), quality))
if (image.GetFormat() != ImageFormat::RGBA8)
{
result = true;
std::optional<Image> convert_image = image.ConvertToRGBA8(&error);
if (!convert_image.has_value())
{
ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", Image::GetFormatName(image.GetFormat()),
error.GetDescription());
image.Invalidate();
}
else
{
ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
result = false;
image = std::move(convert_image.value());
}
}
else
bool result = false;
if (image.IsValid())
{
result = false;
}
}
else
{
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<u32>(m_display_texture_view_y);
const u32 read_width = static_cast<u32>(m_display_texture_view_width);
const u32 read_height = static_cast<u32>(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<u32> texture_data((texture_data_stride * read_height) / sizeof(u32));
Image image(read_width, read_height, read_format);
std::unique_ptr<GPUDownloadTexture> 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<u32>* 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<GPUDownloadTexture> 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<u32>(display_rect->width());
const u32 draw_height = static_cast<u32>(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<float>(m_display_texture_view_width) / static_cast<float>(m_display_texture_view_height);
const float dar = static_cast<float>(draw_width) / static_cast<float>(draw_height);
if (sar >= dar)
{
// stretch height, preserve width
const float scale = static_cast<float>(m_display_texture_view_width) / static_cast<float>(draw_width);
*width = m_display_texture_view_width;
*height = static_cast<u32>(std::round(static_cast<float>(draw_height) * scale));
}
else
{
// stretch width, preserve height
const float scale = static_cast<float>(m_display_texture_view_height) / static_cast<float>(draw_height);
*width = static_cast<u32>(std::round(static_cast<float>(draw_width) * scale));
*height = m_display_texture_view_height;
}
float f_width = static_cast<float>(m_display_texture_view_width);
float f_height = static_cast<float>(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<float>(g_gpu_device->GetMaxTextureSize());
if (f_width > max_texture_size)
{
*height = static_cast<u32>(static_cast<float>(*height) /
(static_cast<float>(*width) / static_cast<float>(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<u32>(static_cast<float>(*width) /
(static_cast<float>(*height) / static_cast<float>(max_texture_size)));
f_height = max_texture_size;
f_width = f_width / (f_height / max_texture_size);
}
*width = static_cast<u32>(std::ceil(f_width));
*height = static_cast<u32>(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<s32>(*width), static_cast<s32>(*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<u32> 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<const char*>(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;

View File

@ -24,6 +24,7 @@
#include <vector>
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<GPU> CreateHardwareRenderer(Error* error);
static std::unique_ptr<GPU> 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<GPU> CreateHardwareRenderer();
static std::unique_ptr<GPU> 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<u32>* 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,

View File

@ -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<u8>(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<GPUShader> 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<GPUShader> fs =
g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateVRAMCopyFragmentShader(m_write_mask_as_depth), error);
std::unique_ptr<GPUShader> 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<GPUShader> 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<GPUShader> fs = g_gpu_device->CreateShader(GPUShaderStage::Fragment, shadergen.GetLanguage(),
shadergen.GenerateCopyFragmentShader(), error);
std::unique_ptr<GPUShader> 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<float>(tex->GetWidth()) / static_cast<float>(m_vram_replacement_texture->GetWidth()),
static_cast<float>(tex->GetHeight()) / static_cast<float>(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<GPUTexture, GPUDevice::PooledTextureDeleter> level_texture = g_gpu_device->FetchAutoRecycleTexture(
width, height, 1, m_downsample_scale_or_levels, 1, GPUTexture::Type::Texture, VRAM_RT_FORMAT);
std::unique_ptr<GPUTexture, GPUDevice::PooledTextureDeleter> 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<GPUTexture, GPUDevice::PooledTextureDeleter> 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<GPUTexture, GPUDevice::PooledTextureDeleter> 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> GPU::CreateHardwareRenderer(Error* error)
std::unique_ptr<GPU> GPU::CreateHardwareRenderer()
{
std::unique_ptr<GPU_HW> gpu(std::make_unique<GPU_HW>());
if (!gpu->Initialize(error))
gpu.reset();
return gpu;
return std::make_unique<GPU_HW>();
}

View File

@ -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<GPUTexture> m_vram_read_texture;
std::unique_ptr<GPUTexture> m_vram_readback_texture;
std::unique_ptr<GPUDownloadTexture> m_vram_readback_download_texture;
std::unique_ptr<GPUTexture> m_vram_replacement_texture;
std::unique_ptr<GPUTextureBuffer> m_vram_upload_buffer;
std::unique_ptr<GPUTexture> m_vram_write_texture;

View File

@ -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
#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
}
)";

View File

@ -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;

View File

@ -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<HashCacheKey, HashCacheEntry, HashCacheKeyHash>;
using TextureCache = std::unordered_map<std::string, TextureReplacementImage>;
using ReplacementImageCache = PreferUnorderedStringMap<TextureReplacementImage>;
using GPUReplacementImageCache = PreferUnorderedStringMap<std::pair<std::unique_ptr<GPUTexture>, u32>>;
using VRAMReplacementMap = std::unordered_map<VRAMReplacementName, std::string, VRAMReplacementNameHash>;
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<std::pair<GPUReplacementImageCache::iterator, s32>> gpu_replacement_image_cache_purge_list;
std::unordered_set<VRAMReplacementName, VRAMReplacementNameHash> dumped_vram_writes;
std::unordered_set<DumpedTextureKey, DumpedTextureKeyHash> 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<size_t>(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<s32>(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<float>(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<float>(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<const u16*>(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<u32*>(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<u32*>(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::vector<TextureReplace
if (!IsMatchingReplacementPalette(palette_hash, mode, palette, it->second.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<TextureReplace
DebugAssert(rect_in_page_space.width() <= static_cast<s32>(TEXTURE_PAGE_WIDTH));
DebugAssert(rect_in_page_space.height() <= static_cast<s32>(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::vector<TextureRepla
continue;
}
const TextureReplacementImage* image = GetTextureReplacementImage(it->second.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<GPUTexture> 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<float>(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<size_t>(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<float>(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<GPUTexture> 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<float>(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<bool>(s_state.config.reduce_palette_range));
s_state.config.convert_copies_to_writes = GetOptionalTFromObject<bool>(root, "ConvertCopiesToWrites")
.value_or(static_cast<bool>(s_state.config.convert_copies_to_writes));
s_state.config.replacement_scale_linear_filter =
GetOptionalTFromObject<bool>(root, "ReplacementScaleLinearFilter")
.value_or(static_cast<bool>(s_state.config.replacement_scale_linear_filter));
s_state.config.max_vram_write_splits =
GetOptionalTFromObject<bool>(root, "MaxVRAMWriteSplits").value_or(s_state.config.max_vram_write_splits);
s_state.config.max_vram_write_coalesce_width = GetOptionalTFromObject<u32>(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<u32>(root, "DumpVRAMWriteHeightThreshold")
.value_or(s_state.config.vram_write_dump_height_threshold);
s_state.config.max_hash_cache_entries =
GetOptionalTFromObject<u32>(root, "MaxHashCacheEntries").value_or(s_state.config.max_hash_cache_entries);
s_state.config.max_hash_cache_vram_usage_mb =
GetOptionalTFromObject<u32>(root, "MaxHashCacheVRAMUsageMB").value_or(s_state.config.max_hash_cache_vram_usage_mb);
s_state.config.max_replacement_cache_vram_usage_mb = GetOptionalTFromObject<u32>(root, "MaxReplacementCacheVRAMUsage")
.value_or(s_state.config.max_replacement_cache_vram_usage_mb);
s_state.config.replacement_scale_linear_filter =
GetOptionalTFromObject<bool>(root, "ReplacementScaleLinearFilter")
.value_or(static_cast<bool>(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();
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.emplace(name, std::move(it2->second));
old_map.erase(it2);
}
const auto it3 = old_gpu_map.find(name);
if (it3 != old_gpu_map.end())
{
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)
{
const auto it2 = old_map.find(it.second);
if (it2 != old_map.end())
{
s_state.replacement_image_cache[it.second] = std::move(it2->second);
old_map.erase(it2);
}
}
reinsert_texture(it.second);
for (const auto& map : {s_state.vram_write_texture_replacements, s_state.texture_page_texture_replacements})
{
for (const auto& it : map)
{
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);
}
}
}
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<GPUTexture> replacement_tex =
g_gpu_device->FetchTexture(new_width, new_height, 1, 1, 1, GPUTexture::Type::Texture, REPLACEMENT_TEXTURE_FORMAT);
std::unique_ptr<GPUTexture> 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<true>(&uniforms[0], texture_size);
GSVector2::store<true>(&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);

View File

@ -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);

View File

@ -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<u32>(format));
}
@ -824,11 +824,7 @@ void GPU_SW::UpdateCLUT(GPUTexturePaletteReg reg, bool clut_is_8bit)
m_backend.PushCommand(cmd);
}
std::unique_ptr<GPU> GPU::CreateSoftwareRenderer(Error* error)
std::unique_ptr<GPU> GPU::CreateSoftwareRenderer()
{
std::unique_ptr<GPU_SW> gpu(std::make_unique<GPU_SW>());
if (!gpu->Initialize(error))
gpu.reset();
return gpu;
return std::make_unique<GPU_SW>();
}

View File

@ -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<float>((4.0 / 3.0) / (static_cast<double>(num) / static_cast<double>(denom)));
static_cast<float>((4.0 / 3.0) / (static_cast<double>(custom_num) / static_cast<double>(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;

View File

@ -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);

View File

@ -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<float>(new_width);
const float f_height = static_cast<float>(new_height);
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(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<float>(new_width);
const float f_height = static_cast<float>(new_height);
const float f_width = static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth());
const float f_height = static_cast<float>(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::ReleaseGPUDevice()

View File

@ -139,29 +139,18 @@ static void HotkeyToggleOSD()
static bool CanPause()
{
static constexpr const float PAUSE_INTERVAL = 3.0f;
static Common::Timer::Value s_last_pause_time = 0;
if (!Achievements::IsHardcoreModeActive() || System::IsPaused())
const u32 frames_until_pause_allowed = Achievements::GetPauseThrottleFrames();
if (frames_until_pause_allowed == 0)
return true;
const Common::Timer::Value time = Common::Timer::GetCurrentValue();
const float delta = static_cast<float>(Common::Timer::ConvertValueToSeconds(time - s_last_pause_time));
if (delta < PAUSE_INTERVAL)
{
const float seconds = static_cast<float>(frames_until_pause_allowed) / System::GetVideoFrameRate();
Host::AddIconOSDMessage("PauseCooldown", ICON_FA_CLOCK,
TRANSLATE_PLURAL_STR("Hotkeys", "You cannot pause until another %n second(s) have passed.",
"", static_cast<int>(std::ceil(PAUSE_INTERVAL - delta))),
Host::OSD_QUICK_DURATION);
"", static_cast<int>(std::ceil(seconds))),
std::max(seconds, Host::OSD_QUICK_DURATION));
return false;
}
Host::RemoveKeyedOSDMessage("PauseCooldown");
s_last_pause_time = time;
return true;
}
#endif
BEGIN_HOTKEY_LIST(g_common_hotkeys)

View File

@ -389,7 +389,7 @@ void ImGuiManager::DrawPerformanceOverlay(float& position_y, float scale, float
if (g_settings.display_show_resolution)
{
const u32 resolution_scale = g_gpu->GetResolutionScale();
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution();
const auto [display_width, display_height] = g_gpu->GetFullDisplayResolution();// wrong
const bool interlaced = g_gpu->IsInterlacedDisplayEnabled();
const bool pal = g_gpu->IsInPALMode();
text.format("{}x{} {} {} [{}x]", display_width * resolution_scale, display_height * resolution_scale,
@ -1049,9 +1049,9 @@ void SaveStateSelectorUI::InitializeListEntry(ListEntry* li, ExtendedSaveStateIn
if (ssi->screenshot.IsValid())
{
li->preview_texture = g_gpu_device->FetchTexture(ssi->screenshot.GetWidth(), ssi->screenshot.GetHeight(), 1, 1, 1,
GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
ssi->screenshot.GetPixels(), ssi->screenshot.GetPitch());
li->preview_texture = g_gpu_device->FetchTexture(
ssi->screenshot.GetWidth(), ssi->screenshot.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, GPUTexture::Flags::None, ssi->screenshot.GetPixels(), ssi->screenshot.GetPitch());
if (!li->preview_texture) [[unlikely]]
ERROR_LOG("Failed to upload save state image to GPU");
}

View File

@ -340,6 +340,14 @@ bool MemoryWatchList::AddEntry(std::string description, u32 address, MemoryAcces
return true;
}
bool MemoryWatchList::GetEntryFreeze(u32 index) const
{
if (index >= m_entries.size())
return false;
return m_entries[index].freeze;
}
void MemoryWatchList::RemoveEntry(u32 index)
{
if (index >= m_entries.size())

View File

@ -112,6 +112,8 @@ public:
u32 GetEntryCount() const { return static_cast<u32>(m_entries.size()); }
bool AddEntry(std::string description, u32 address, MemoryAccessSize size, bool is_signed, bool freeze);
bool GetEntryFreeze(u32 index) const;
void RemoveEntry(u32 index);
bool RemoveEntryByDescription(const char* description);
bool RemoveEntryByAddress(u32 address);

View File

@ -454,6 +454,16 @@ void Settings::Load(const SettingsInterface& si, const SettingsInterface& contro
texture_replacements.config.replacement_scale_linear_filter =
si.GetBoolValue("TextureReplacements", "ReplacementScaleLinearFilter", false);
texture_replacements.config.max_hash_cache_entries =
si.GetUIntValue("TextureReplacements", "MaxHashCacheEntries",
TextureReplacementSettings::Configuration::DEFAULT_MAX_HASH_CACHE_ENTRIES);
texture_replacements.config.max_hash_cache_vram_usage_mb =
si.GetUIntValue("TextureReplacements", "MaxHashCacheVRAMUsageMB",
TextureReplacementSettings::Configuration::DEFAULT_MAX_HASH_CACHE_VRAM_USAGE_MB);
texture_replacements.config.max_replacement_cache_vram_usage_mb =
si.GetUIntValue("TextureReplacements", "MaxReplacementCacheVRAMUsage",
TextureReplacementSettings::Configuration::DEFAULT_MAX_REPLACEMENT_CACHE_VRAM_USAGE_MB);
texture_replacements.config.max_vram_write_splits = si.GetUIntValue("TextureReplacements", "MaxVRAMWriteSplits", 0u);
texture_replacements.config.max_vram_write_coalesce_width =
si.GetUIntValue("TextureReplacements", "MaxVRAMWriteCoalesceWidth", 0u);
@ -714,6 +724,12 @@ void Settings::Save(SettingsInterface& si, bool ignore_base) const
si.SetBoolValue("TextureReplacements", "ReplacementScaleLinearFilter",
texture_replacements.config.replacement_scale_linear_filter);
si.SetUIntValue("TextureReplacements", "MaxHashCacheEntries", texture_replacements.config.max_hash_cache_entries);
si.SetUIntValue("TextureReplacements", "MaxHashCacheVRAMUsageMB",
texture_replacements.config.max_hash_cache_vram_usage_mb);
si.SetUIntValue("TextureReplacements", "MaxReplacementCacheVRAMUsage",
texture_replacements.config.max_replacement_cache_vram_usage_mb);
si.SetUIntValue("TextureReplacements", "MaxVRAMWriteSplits", texture_replacements.config.max_vram_write_splits);
si.SetUIntValue("TextureReplacements", "MaxVRAMWriteCoalesceWidth",
texture_replacements.config.max_vram_write_coalesce_width);
@ -864,6 +880,21 @@ std::string Settings::TextureReplacementSettings::Configuration::ExportToYAML(bo
{}DumpVRAMWriteWidthThreshold: {}
{}DumpVRAMWriteHeightThreshold: {}
# Sets the maximum size of the hash cache that manages texture replacements.
# Generally the default is sufficient, but some games may require increasing the
# size. Do not set too high, otherwise mobile drivers will break.
{}MaxHashCacheEntries: {}
# Sets the maximum amount of VRAM in megabytes that the hash cache can utilize.
# Keep in mind your target system requirements, using too much VRAM will result
# in swapping and significantly decreased performance.
{}MaxHashCacheVRAMUsageMB: {}
# Sets the maximum amount of VRAM in megabytes that are reserved for the cache of
# replacement textures. The cache usage for any given texture is approximately the
# same size as the uncompressed source image on disk.
{}MaxReplacementCacheVRAMUsage: {}
# Enables the use of a bilinear filter when scaling replacement textures.
# If more than one replacement texture in a 256x256 texture page has a different
# scaling over the native resolution, or the texture page is not covered, a
@ -895,6 +926,9 @@ std::string Settings::TextureReplacementSettings::Configuration::ExportToYAML(bo
comment_str, texture_dump_height_threshold, // DumpTextureHeightThreshold
comment_str, vram_write_dump_width_threshold, // DumpVRAMWriteWidthThreshold
comment_str, vram_write_dump_height_threshold, // DumpVRAMWriteHeightThreshold
comment_str, max_hash_cache_entries, // MaxHashCacheEntries
comment_str, max_hash_cache_vram_usage_mb, // MaxHashCacheVRAMUsageMB
comment_str, max_replacement_cache_vram_usage_mb, // MaxReplacementCacheVRAMUsage
comment_str, replacement_scale_linear_filter); // ReplacementScaleLinearFilter
}
@ -1614,11 +1648,15 @@ const char* Settings::GetDisplayDeinterlacingModeDisplayName(DisplayDeinterlacin
"DisplayDeinterlacingMode");
}
static constexpr const std::array s_display_crop_mode_names = {"None", "Overscan", "Borders"};
static constexpr const std::array s_display_crop_mode_names = {
"None", "Overscan", "OverscanUncorrected", "Borders", "BordersUncorrected",
};
static constexpr const std::array s_display_crop_mode_display_names = {
TRANSLATE_DISAMBIG_NOOP("Settings", "None", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Only Overscan Area", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "Only Overscan Area (Aspect Uncorrected)", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "All Borders", "DisplayCropMode"),
TRANSLATE_DISAMBIG_NOOP("Settings", "All Borders (Aspect Uncorrected)", "DisplayCropMode"),
};
std::optional<DisplayCropMode> Settings::ParseDisplayCropMode(const char* str)
@ -1662,7 +1700,7 @@ static constexpr const std::array s_display_aspect_ratio_names = {
"20:9",
"PAR 1:1"};
static constexpr const std::array s_display_aspect_ratio_values = {
-1.0f, -1.0f, -1.0f, 4.0f / 3.0f, 16.0f / 9.0f, 19.0f / 9.0f, 20.0f / 9.0f, -1.0f};
4.0f / 3.0f, 4.0f / 3.0f, 4.0f / 3.0f, 4.0f / 3.0f, 16.0f / 9.0f, 19.0f / 9.0f, 20.0f / 9.0f, -1.0f};
std::optional<DisplayAspectRatio> Settings::ParseDisplayAspectRatio(const char* str)
{
@ -1690,30 +1728,9 @@ const char* Settings::GetDisplayAspectRatioDisplayName(DisplayAspectRatio ar)
}
float Settings::GetDisplayAspectRatioValue() const
{
switch (display_aspect_ratio)
{
case DisplayAspectRatio::MatchWindow:
{
if (!g_gpu_device || !g_gpu_device->HasMainSwapChain())
return s_display_aspect_ratio_values[static_cast<size_t>(DEFAULT_DISPLAY_ASPECT_RATIO)];
return static_cast<float>(g_gpu_device->GetMainSwapChain()->GetWidth()) /
static_cast<float>(g_gpu_device->GetMainSwapChain()->GetHeight());
}
case DisplayAspectRatio::Custom:
{
return static_cast<float>(display_aspect_ratio_custom_numerator) /
static_cast<float>(display_aspect_ratio_custom_denominator);
}
default:
{
return s_display_aspect_ratio_values[static_cast<size_t>(display_aspect_ratio)];
}
}
}
static constexpr const std::array s_display_alignment_names = {"LeftOrTop", "Center", "RightOrBottom"};
static constexpr const std::array s_display_alignment_display_names = {

View File

@ -234,6 +234,10 @@ struct Settings
{
struct Configuration
{
static constexpr u32 DEFAULT_MAX_HASH_CACHE_ENTRIES = 500;
static constexpr u32 DEFAULT_MAX_HASH_CACHE_VRAM_USAGE_MB = 2048;
static constexpr u32 DEFAULT_MAX_REPLACEMENT_CACHE_VRAM_USAGE_MB = 512;
constexpr Configuration() = default;
bool dump_texture_pages : 1 = false;
@ -245,6 +249,10 @@ struct Settings
bool convert_copies_to_writes : 1 = false;
bool replacement_scale_linear_filter = false;
u32 max_hash_cache_entries = DEFAULT_MAX_HASH_CACHE_ENTRIES;
u32 max_hash_cache_vram_usage_mb = DEFAULT_MAX_HASH_CACHE_VRAM_USAGE_MB;
u32 max_replacement_cache_vram_usage_mb = DEFAULT_MAX_REPLACEMENT_CACHE_VRAM_USAGE_MB;
u32 max_vram_write_splits = 0;
u32 max_vram_write_coalesce_width = 0;
u32 max_vram_write_coalesce_height = 0;

View File

@ -5,4 +5,4 @@
#include "common/types.h"
static constexpr u32 SHADER_CACHE_VERSION = 20;
static constexpr u32 SHADER_CACHE_VERSION = 22;

View File

@ -189,7 +189,7 @@ struct ADPCMBlock
u8 bits;
BitField<u8, u8, 0, 4> shift;
BitField<u8, u8, 4, 3> filter;
BitField<u8, u8, 4, 4> filter;
} shift_filter;
ADPCMFlags flags;
u8 data[NUM_SAMPLES_PER_ADPCM_BLOCK / 2];
@ -201,7 +201,7 @@ struct ADPCMBlock
return (shift > 12) ? 9 : shift;
}
u8 GetFilter() const { return std::min<u8>(shift_filter.filter, 4); }
u8 GetFilter() const { return shift_filter.filter; }
u8 GetNibble(u32 index) const { return (data[index / 2] >> ((index % 2) * 4)) & 0x0F; }
};
@ -1877,8 +1877,8 @@ void SPU::Voice::TickADSR()
void SPU::Voice::DecodeBlock(const ADPCMBlock& block)
{
static constexpr std::array<s32, 5> filter_table_pos = {{0, 60, 115, 98, 122}};
static constexpr std::array<s32, 5> filter_table_neg = {{0, 0, -52, -55, -60}};
static constexpr std::array<s8, 16> filter_table_pos = {{0, 60, 115, 98, 122, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
static constexpr std::array<s8, 16> filter_table_neg = {{0, 0, -52, -55, -60, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}};
// store samples needed for interpolation
current_block_samples[2] = current_block_samples[NUM_SAMPLES_FROM_LAST_ADPCM_BLOCK + NUM_SAMPLES_PER_ADPCM_BLOCK - 1];

View File

@ -128,7 +128,7 @@ struct SaveStateBuffer
std::string media_path;
u32 media_subimage_index;
u32 version;
RGBA8Image screenshot;
Image screenshot;
DynamicHeapArray<u8> state_data;
size_t state_size;
};
@ -1941,9 +1941,6 @@ bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, b
if (!CreateGPU(force_software_renderer ? GPURenderer::Software : g_settings.gpu_renderer, false, fullscreen, error))
return false;
if (GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
GTE::UpdateAspectRatio(swap_chain->GetWidth(), swap_chain->GetHeight());
if (g_settings.gpu_pgxp_enable)
CPU::PGXP::Initialize();
@ -1965,6 +1962,7 @@ bool System::Initialize(std::unique_ptr<CDImage> disc, DiscRegion disc_region, b
s_state.cpu_thread_handle = Threading::ThreadHandle::GetForCallingThread();
UpdateGTEAspectRatio();
UpdateThrottlePeriod();
UpdateMemorySaveStateSettings();
@ -2434,11 +2432,11 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen,
}
if (renderer == GPURenderer::Software)
g_gpu = GPU::CreateSoftwareRenderer(error);
g_gpu = GPU::CreateSoftwareRenderer();
else
g_gpu = GPU::CreateHardwareRenderer(error);
g_gpu = GPU::CreateHardwareRenderer();
if (!g_gpu)
if (!g_gpu->Initialize(error))
{
ERROR_LOG("Failed to initialize {} renderer, falling back to software renderer",
Settings::GetRendererName(renderer));
@ -2447,8 +2445,8 @@ bool System::CreateGPU(GPURenderer renderer, bool is_switching, bool fullscreen,
Settings::GetRendererName(renderer)),
Host::OSD_CRITICAL_ERROR_DURATION);
g_gpu.reset();
g_gpu = GPU::CreateSoftwareRenderer(error);
if (!g_gpu)
g_gpu = GPU::CreateSoftwareRenderer();
if (!g_gpu->Initialize(error))
{
ERROR_LOG("Failed to create fallback software renderer.");
if (!s_state.keep_gpu_device_on_shutdown)
@ -2916,15 +2914,14 @@ bool System::LoadStateBufferFromFile(SaveStateBuffer* buffer, std::FILE* fp, Err
// Read screenshot if requested.
if (read_screenshot)
{
buffer->screenshot.SetSize(header.screenshot_width, header.screenshot_height);
const u32 uncompressed_size = buffer->screenshot.GetPitch() * buffer->screenshot.GetHeight();
const u32 compressed_size = (header.version >= 69) ? header.screenshot_compressed_size : uncompressed_size;
buffer->screenshot.Resize(header.screenshot_width, header.screenshot_height, ImageFormat::RGBA8, true);
const u32 compressed_size =
(header.version >= 69) ? header.screenshot_compressed_size : buffer->screenshot.GetStorageSize();
const SAVE_STATE_HEADER::CompressionType compression_type =
(header.version >= 69) ? static_cast<SAVE_STATE_HEADER::CompressionType>(header.screenshot_compression_type) :
SAVE_STATE_HEADER::CompressionType::None;
if (!ReadAndDecompressStateData(
fp, std::span<u8>(reinterpret_cast<u8*>(buffer->screenshot.GetPixels()), uncompressed_size),
header.offset_to_screenshot, compressed_size, compression_type, error)) [[unlikely]]
if (!ReadAndDecompressStateData(fp, buffer->screenshot.GetPixelsSpan(), header.offset_to_screenshot,
compressed_size, compression_type, error)) [[unlikely]]
{
return false;
}
@ -3104,29 +3101,27 @@ bool System::SaveStateToBuffer(SaveStateBuffer* buffer, Error* error, u32 screen
screenshot_display_rect = screenshot_display_rect.sub32(screenshot_display_rect.xyxy());
VERBOSE_LOG("Saving {}x{} screenshot for state", screenshot_width, screenshot_height);
std::vector<u32> screenshot_buffer;
u32 screenshot_stride;
GPUTexture::Format screenshot_format;
if (g_gpu->RenderScreenshotToBuffer(screenshot_width, screenshot_height, screenshot_display_rect,
screenshot_draw_rect, false, &screenshot_buffer, &screenshot_stride,
&screenshot_format) &&
GPUTexture::ConvertTextureDataToRGBA8(screenshot_width, screenshot_height, screenshot_buffer, screenshot_stride,
screenshot_format))
screenshot_draw_rect, false, &buffer->screenshot))
{
if (screenshot_stride != (screenshot_width * sizeof(u32)))
if (g_gpu_device->UsesLowerLeftOrigin())
buffer->screenshot.FlipY();
// Ensure it's RGBA8.
if (buffer->screenshot.GetFormat() != ImageFormat::RGBA8)
{
WARNING_LOG("Failed to save {}x{} screenshot for save state due to incorrect stride({})", screenshot_width,
screenshot_height, screenshot_stride);
Error convert_error;
std::optional<Image> screenshot_rgba8 = buffer->screenshot.ConvertToRGBA8(&convert_error);
if (!screenshot_rgba8.has_value())
{
ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}",
Image::GetFormatName(buffer->screenshot.GetFormat()), convert_error.GetDescription());
buffer->screenshot.Invalidate();
}
else
{
if (g_gpu_device->UsesLowerLeftOrigin())
{
GPUTexture::FlipTextureDataRGBA8(screenshot_width, screenshot_height,
reinterpret_cast<u8*>(screenshot_buffer.data()), screenshot_stride);
buffer->screenshot = std::move(screenshot_rgba8.value());
}
buffer->screenshot.SetPixels(screenshot_width, screenshot_height, std::move(screenshot_buffer));
}
}
else
@ -4405,8 +4400,7 @@ void System::CheckForSettingsChanges(const Settings& old_settings)
(g_settings.display_aspect_ratio_custom_numerator != old_settings.display_aspect_ratio_custom_numerator ||
g_settings.display_aspect_ratio_custom_denominator != old_settings.display_aspect_ratio_custom_denominator)))
{
if (GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
GTE::UpdateAspectRatio(swap_chain->GetWidth(), swap_chain->GetHeight());
UpdateGTEAspectRatio();
}
if (g_settings.gpu_pgxp_enable != old_settings.gpu_pgxp_enable ||
@ -5654,8 +5648,7 @@ void System::ToggleWidescreen()
Settings::GetDisplayAspectRatioDisplayName(g_settings.display_aspect_ratio), 5.0f));
}
if (GPUSwapChain* swap_chain = g_gpu_device->GetMainSwapChain())
GTE::UpdateAspectRatio(swap_chain->GetWidth(), swap_chain->GetHeight());
UpdateGTEAspectRatio();
}
void System::ToggleSoftwareRendering()
@ -5685,29 +5678,26 @@ void System::RequestDisplaySize(float scale /*= 0.0f*/)
if (scale == 0.0f)
scale = g_gpu->IsHardwareRenderer() ? static_cast<float>(g_settings.gpu_resolution_scale) : 1.0f;
const float y_scale =
(static_cast<float>(g_gpu->GetCRTCDisplayWidth()) / static_cast<float>(g_gpu->GetCRTCDisplayHeight())) /
g_gpu->ComputeDisplayAspectRatio();
u32 requested_width =
std::max<u32>(static_cast<u32>(std::ceil(static_cast<float>(g_gpu->GetCRTCDisplayWidth()) * scale)), 1);
u32 requested_height =
std::max<u32>(static_cast<u32>(std::ceil(static_cast<float>(g_gpu->GetCRTCDisplayHeight()) * y_scale * scale)), 1);
float requested_width = static_cast<float>(g_gpu->GetCRTCDisplayWidth()) * scale;
float requested_height = static_cast<float>(g_gpu->GetCRTCDisplayHeight()) * scale;
g_gpu->ApplyPixelAspectRatioToSize(&requested_width, &requested_height);
if (g_settings.display_rotation == DisplayRotation::Rotate90 ||
g_settings.display_rotation == DisplayRotation::Rotate270)
{
std::swap(requested_width, requested_height);
Host::RequestResizeHostDisplay(static_cast<s32>(requested_width), static_cast<s32>(requested_height));
}
void System::DisplayWindowResized(u32 width, u32 height)
Host::RequestResizeHostDisplay(static_cast<s32>(std::ceil(requested_width)),
static_cast<s32>(std::ceil(requested_height)));
}
void System::DisplayWindowResized()
{
if (!IsValid())
return;
if (g_settings.gpu_widescreen_hack && g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow)
GTE::UpdateAspectRatio(width, height);
UpdateGTEAspectRatio();
g_gpu->RestoreDeviceContext();
g_gpu->UpdateResolutionScale();
@ -5722,6 +5712,47 @@ void System::DisplayWindowResized(u32 width, u32 height)
}
}
void System::UpdateGTEAspectRatio()
{
if (!IsValid())
return;
DisplayAspectRatio gte_ar = g_settings.display_aspect_ratio;
u32 custom_num = 0;
u32 custom_denom = 0;
if (!g_settings.gpu_widescreen_hack)
{
// No WS hack => no correction.
gte_ar = DisplayAspectRatio::R4_3;
}
else if (gte_ar == DisplayAspectRatio::Custom)
{
// Custom AR => use values.
custom_num = g_settings.display_aspect_ratio_custom_numerator;
custom_denom = g_settings.display_aspect_ratio_custom_denominator;
}
else if (gte_ar == DisplayAspectRatio::MatchWindow)
{
if (const GPUSwapChain* main_swap_chain = g_gpu_device->GetMainSwapChain())
{
// Pre-apply the native aspect ratio correction to the window size.
// MatchWindow does not correct the display aspect ratio, so we need to apply it here.
const float correction = g_gpu->ComputeAspectRatioCorrection();
custom_num =
static_cast<u32>(std::max(std::round(static_cast<float>(main_swap_chain->GetWidth()) / correction), 1.0f));
custom_denom = std::max<u32>(main_swap_chain->GetHeight(), 1u);
gte_ar = DisplayAspectRatio::Custom;
}
else
{
// Assume 4:3 until we get a window.
gte_ar = DisplayAspectRatio::R4_3;
}
}
GTE::SetAspectRatio(gte_ar, custom_num, custom_denom);
}
bool System::PresentDisplay(bool explicit_present, u64 present_time)
{
// acquire for IO.MousePos.

View File

@ -74,7 +74,7 @@ struct ExtendedSaveStateInfo
std::string media_path;
std::time_t timestamp;
RGBA8Image screenshot;
Image screenshot;
};
namespace System {

View File

@ -33,8 +33,11 @@ void FrameDone();
GPUVSyncMode GetEffectiveVSyncMode();
bool ShouldAllowPresentThrottle();
/// Call when host display size changes, use with "match display" aspect ratio setting.
void DisplayWindowResized(u32 width, u32 height);
/// Call when host display size changes.
void DisplayWindowResized();
/// Updates the internal GTE aspect ratio. Use with "match display" aspect ratio setting.
void UpdateGTEAspectRatio();
/// Performs mandatory hardware checks.
bool PerformEarlyHardwareChecks(Error* error);

View File

@ -141,8 +141,10 @@ enum class DisplayCropMode : u8
{
None,
Overscan,
OverscanUncorrected,
Borders,
Count
BordersUncorrected,
MaxCount
};
enum class DisplayAspectRatio : u8

View File

@ -22,7 +22,7 @@
#include "fmt/format.h"
#include <QtCore/QCoreApplication>
#include <QtCore/QFile>
#include <QtCore/QFileInfo>
#include <QtCore/QJsonArray>
#include <QtCore/QJsonDocument>
#include <QtCore/QJsonObject>
@ -44,6 +44,8 @@ static constexpr u32 HTTP_POLL_INTERVAL = 10;
#include <shellapi.h>
#elif defined(__APPLE__)
#include "common/cocoa_tools.h"
#else
#include <sys/stat.h>
#endif
// Logic to detect whether we can use the auto updater.
@ -509,6 +511,11 @@ void AutoUpdaterDialog::getChangesComplete(s32 status_code, std::vector<u8> resp
void AutoUpdaterDialog::downloadUpdateClicked()
{
#ifdef AUTO_UPDATER_SUPPORTED
// Prevent multiple clicks of the button.
if (!m_ui.downloadAndInstall->isEnabled())
return;
m_ui.downloadAndInstall->setEnabled(false);
m_display_messages = true;
std::optional<bool> download_result;
@ -602,8 +609,7 @@ static constexpr char UPDATER_ARCHIVE_NAME[] = "update.zip";
bool AutoUpdaterDialog::doesUpdaterNeedElevation(const std::string& application_dir) const
{
// Try to create a dummy text file in the PCSX2 updater directory. If it fails, we probably won't have write
// permission.
// Try to create a dummy text file in the updater directory. If it fails, we probably won't have write permission.
const std::string dummy_path = Path::Combine(application_dir, "update.txt");
auto fp = FileSystem::OpenManagedCFile(dummy_path.c_str(), "wb");
if (!fp)
@ -620,15 +626,16 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
const std::string update_zip_path = Path::Combine(EmuFolders::DataRoot, UPDATER_ARCHIVE_NAME);
const std::string updater_path = Path::Combine(EmuFolders::DataRoot, UPDATER_EXECUTABLE);
if ((FileSystem::FileExists(update_zip_path.c_str()) && !FileSystem::DeleteFile(update_zip_path.c_str())))
Error error;
if ((FileSystem::FileExists(update_zip_path.c_str()) && !FileSystem::DeleteFile(update_zip_path.c_str(), &error)))
{
reportError("Removing existing update zip failed");
reportError(fmt::format("Removing existing update zip failed:\n{}", error.GetDescription()));
return false;
}
if (!FileSystem::WriteBinaryFile(update_zip_path.c_str(), update_data.data(), update_data.size()))
if (!FileSystem::WriteAtomicRenamedFile(update_zip_path.c_str(), update_data, &error))
{
reportError(fmt::format("Writing update zip to '{}' failed", update_zip_path));
reportError(fmt::format("Writing update zip to '{}' failed:\n{}", update_zip_path, error.GetDescription()));
return false;
}
@ -686,7 +693,7 @@ bool AutoUpdaterDialog::extractUpdater(const std::string& zip_path, const std::s
if (std::fwrite(chunk, size, 1, fp.get()) != 1)
{
Error::SetString(error, "Failed to write updater exe");
Error::SetErrno(error, "Failed to write updater exe: fwrite() failed: ", errno);
unzClose(zf);
fp.reset();
FileSystem::DeleteFile(destination_path.c_str());
@ -742,9 +749,12 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
if (!FileSystem::FileExists(updater_path.c_str()))
return;
if (!FileSystem::DeleteFile(updater_path.c_str()))
Error error;
if (!FileSystem::DeleteFile(updater_path.c_str(), &error))
{
QMessageBox::critical(nullptr, tr("Updater Error"), tr("Failed to remove updater exe after update."));
QMessageBox::critical(
nullptr, tr("Updater Error"),
tr("Failed to remove updater exe after update:\n%1").arg(QString::fromStdString(error.GetDescription())));
return;
}
}
@ -784,24 +794,19 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
// We use the user data directory to temporarily store the update zip.
const std::string zip_path = Path::Combine(EmuFolders::DataRoot, "update.zip");
const std::string staging_directory = Path::Combine(EmuFolders::DataRoot, "UPDATE_STAGING");
if (FileSystem::FileExists(zip_path.c_str()) && !FileSystem::DeleteFile(zip_path.c_str()))
Error error;
if (FileSystem::FileExists(zip_path.c_str()) && !FileSystem::DeleteFile(zip_path.c_str(), &error))
{
reportError("Failed to remove old update zip.");
reportError(fmt::format("Failed to remove old update zip:\n{}", error.GetDescription()));
return false;
}
// Save update.
if (!FileSystem::WriteAtomicRenamedFile(zip_path.c_str(), update_data, &error))
{
QFile zip_file(QString::fromStdString(zip_path));
if (!zip_file.open(QIODevice::WriteOnly) ||
zip_file.write(reinterpret_cast<const char*>(update_data.data()), static_cast<qint64>(update_data.size())) !=
static_cast<qint64>(update_data.size()))
{
reportError(fmt::format("Writing update zip to '{}' failed", zip_path));
reportError(fmt::format("Writing update zip to '{}' failed:\n{}", zip_path, error.GetDescription()));
return false;
}
zip_file.close();
}
INFO_LOG("Beginning update:\nUpdater path: {}\nZip path: {}\nStaging directory: {}\nOutput directory: {}",
updater_app, zip_path, staging_directory, bundle_path.value());
@ -832,66 +837,105 @@ bool AutoUpdaterDialog::processUpdate(const std::vector<u8>& update_data)
return false;
}
const QString qappimage_path(QString::fromUtf8(appimage_path));
if (!QFile::exists(qappimage_path))
if (!FileSystem::FileExists(appimage_path))
{
reportError(fmt::format("Current AppImage does not exist: {}", appimage_path));
return false;
}
const QString new_appimage_path(qappimage_path + QStringLiteral(".new"));
const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup"));
const std::string new_appimage_path = fmt::format("{}.new", appimage_path);
const std::string backup_appimage_path = fmt::format("{}.backup", appimage_path);
INFO_LOG("APPIMAGE = {}", appimage_path);
INFO_LOG("Backup AppImage path = {}", backup_appimage_path.toStdString());
INFO_LOG("New AppImage path = {}", new_appimage_path.toStdString());
INFO_LOG("Backup AppImage path = {}", backup_appimage_path);
INFO_LOG("New AppImage path = {}", new_appimage_path);
// Remove old "new" appimage and existing backup appimage.
if (QFile::exists(new_appimage_path) && !QFile::remove(new_appimage_path))
Error error;
if (FileSystem::FileExists(new_appimage_path.c_str()) && !FileSystem::DeleteFile(new_appimage_path.c_str(), &error))
{
reportError(fmt::format("Failed to remove old destination AppImage: {}", new_appimage_path.toStdString()));
reportError(
fmt::format("Failed to remove old destination AppImage: {}:\n{}", new_appimage_path, error.GetDescription()));
return false;
}
if (QFile::exists(backup_appimage_path) && !QFile::remove(backup_appimage_path))
if (FileSystem::FileExists(backup_appimage_path.c_str()) &&
!FileSystem::DeleteFile(backup_appimage_path.c_str(), &error))
{
reportError(fmt::format("Failed to remove old backup AppImage: {}", new_appimage_path.toStdString()));
reportError(
fmt::format("Failed to remove old backup AppImage: {}:\n{}", backup_appimage_path, error.GetDescription()));
return false;
}
// Write "new" appimage.
{
// We want to copy the permissions from the old appimage to the new one.
QFile old_file(qappimage_path);
const QFileDevice::Permissions old_permissions = old_file.permissions();
QFile new_file(new_appimage_path);
if (!new_file.open(QIODevice::WriteOnly) ||
new_file.write(reinterpret_cast<const char*>(update_data.data()), static_cast<qint64>(update_data.size())) !=
static_cast<qint64>(update_data.size()) ||
!new_file.setPermissions(old_permissions))
static constexpr int permission_mask = S_IRWXU | S_IRWXG | S_IRWXO;
struct stat old_stat;
if (!FileSystem::StatFile(appimage_path, &old_stat, &error))
{
QFile::remove(new_appimage_path);
reportError(fmt::format("Failed to write new destination AppImage: {}", new_appimage_path.toStdString()));
reportError(fmt::format("Failed to get old AppImage {} permissions:\n{}", appimage_path, error.GetDescription()));
return false;
}
// We do this as a manual write here, rather than using WriteAtomicUpdatedFile(), because we want to write the file
// and set the permissions as one atomic operation.
FileSystem::ManagedCFilePtr fp = FileSystem::OpenManagedCFile(new_appimage_path.c_str(), "wb", &error);
bool success = static_cast<bool>(fp);
if (fp)
{
if (std::fwrite(update_data.data(), update_data.size(), 1, fp.get()) == 1 && std::fflush(fp.get()) == 0)
{
const int fd = fileno(fp.get());
if (fd >= 0)
{
if (fchmod(fd, old_stat.st_mode & permission_mask) != 0)
{
error.SetErrno("fchmod() failed: ", errno);
success = false;
}
}
else
{
error.SetErrno("fileno() failed: ", errno);
success = false;
}
}
else
{
error.SetErrno("fwrite() failed: ", errno);
success = false;
}
fp.reset();
if (!success)
FileSystem::DeleteFile(new_appimage_path.c_str());
}
if (!success)
{
reportError(
fmt::format("Failed to write new destination AppImage: {}:\n{}", new_appimage_path, error.GetDescription()));
return false;
}
}
// Rename "old" appimage.
if (!QFile::rename(qappimage_path, backup_appimage_path))
if (!FileSystem::RenamePath(appimage_path, backup_appimage_path.c_str(), &error))
{
reportError(fmt::format("Failed to rename old AppImage to {}", backup_appimage_path.toStdString()));
QFile::remove(new_appimage_path);
reportError(fmt::format("Failed to rename old AppImage to {}:\n{}", backup_appimage_path, error.GetDescription()));
FileSystem::DeleteFile(new_appimage_path.c_str());
return false;
}
// Rename "new" appimage.
if (!QFile::rename(new_appimage_path, qappimage_path))
if (!FileSystem::RenamePath(new_appimage_path.c_str(), appimage_path, &error))
{
reportError(fmt::format("Failed to rename new AppImage to {}", qappimage_path.toStdString()));
reportError(fmt::format("Failed to rename new AppImage to {}:\n{}", appimage_path, error.GetDescription()));
return false;
}
// Execute new appimage.
QProcess* new_process = new QProcess();
new_process->setProgram(qappimage_path);
new_process->setProgram(QString::fromUtf8(appimage_path));
new_process->setArguments(QStringList{QStringLiteral("-updatecleanup")});
if (!new_process->startDetached())
{
@ -910,16 +954,14 @@ void AutoUpdaterDialog::cleanupAfterUpdate()
if (!appimage_path)
return;
const QString qappimage_path(QString::fromUtf8(appimage_path));
const QString backup_appimage_path(qappimage_path + QStringLiteral(".backup"));
if (!QFile::exists(backup_appimage_path))
const std::string backup_appimage_path = fmt::format("{}.backup", appimage_path);
if (!FileSystem::FileExists(backup_appimage_path.c_str()))
return;
INFO_LOG(QStringLiteral("Removing backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
if (!QFile::remove(backup_appimage_path))
{
ERROR_LOG(QStringLiteral("Failed to remove backup AppImage %1").arg(backup_appimage_path).toStdString().c_str());
}
Error error;
INFO_LOG("Removing backup AppImage: {}", backup_appimage_path);
if (!FileSystem::DeleteFile(backup_appimage_path.c_str(), &error))
ERROR_LOG("Failed to remove backup AppImage {}: {}", backup_appimage_path, error.GetDescription());
}
#else

View File

@ -294,7 +294,7 @@ const QPixmap& GameListModel::getFlagPixmapForEntry(const GameList::Entry* ge) c
if (it != m_flag_pixmap_cache.end())
return it->second;
const QIcon icon(QString::fromStdString(QtHost::GetResourcePath(ge->GetLanguageIconFileName(), true)));
const QIcon icon(QString::fromStdString(QtHost::GetResourcePath(ge->GetLanguageIconName(), true)));
it = m_flag_pixmap_cache.emplace(name, icon.pixmap(FLAG_PIXMAP_WIDTH, FLAG_PIXMAP_HEIGHT)).first;
return it->second;
}

View File

@ -52,6 +52,16 @@ GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string&
static_cast<GameDatabase::CompatibilityRating>(i))));
}
// I hate this so much.
const std::string_view default_language =
entry ? entry->GetLanguageFlagName(region) : Settings::GetDiscRegionName(region);
m_ui.customLanguage->addItem(QtUtils::GetIconForLanguage(default_language), tr("Show Default Flag"));
for (u32 i = 0; i < static_cast<u32>(GameDatabase::Language::MaxCount); i++)
{
const char* language_name = GameDatabase::GetLanguageName(static_cast<GameDatabase::Language>(i));
m_ui.customLanguage->addItem(QtUtils::GetIconForLanguage(language_name), QString::fromUtf8(language_name));
}
populateUi(path, serial, region, entry);
connect(m_ui.compatibilityComments, &QToolButton::clicked, this, &GameSummaryWidget::onCompatibilityCommentsClicked);
@ -69,6 +79,7 @@ GameSummaryWidget::GameSummaryWidget(const std::string& path, const std::string&
connect(m_ui.restoreTitle, &QAbstractButton::clicked, this, [this]() { setCustomTitle(std::string()); });
connect(m_ui.region, &QComboBox::currentIndexChanged, this, [this](int index) { setCustomRegion(index); });
connect(m_ui.restoreRegion, &QAbstractButton::clicked, this, [this]() { setCustomRegion(-1); });
connect(m_ui.customLanguage, &QComboBox::currentIndexChanged, this, &GameSummaryWidget::onCustomLanguageChanged);
}
GameSummaryWidget::~GameSummaryWidget() = default;
@ -147,6 +158,8 @@ void GameSummaryWidget::populateUi(const std::string& path, const std::string& s
else
m_ui.releaseInfo->setText(tr("Unknown"));
m_ui.languages->setText(QtUtils::StringViewToQString(entry->GetLanguagesString()));
QString controllers;
if (entry->supported_controllers != 0 && entry->supported_controllers != static_cast<u16>(-1))
{
@ -201,7 +214,10 @@ void GameSummaryWidget::populateCustomAttributes()
auto lock = GameList::GetLock();
const GameList::Entry* entry = GameList::GetEntryForPath(m_path);
if (!entry || entry->IsDiscSet())
{
m_ui.customLanguage->setEnabled(false);
return;
}
{
QSignalBlocker sb(m_ui.title);
@ -214,6 +230,12 @@ void GameSummaryWidget::populateCustomAttributes()
m_ui.region->setCurrentIndex(static_cast<int>(entry->region));
m_ui.restoreRegion->setEnabled(entry->has_custom_region);
}
{
QSignalBlocker sb(m_ui.customLanguage);
m_ui.customLanguage->setCurrentIndex(entry->HasCustomLanguage() ? (static_cast<u32>(entry->custom_language) + 1) :
0);
}
}
void GameSummaryWidget::updateWindowTitle()
@ -238,7 +260,15 @@ void GameSummaryWidget::setCustomRegion(int region)
GameList::SaveCustomRegionForPath(m_path, (region >= 0) ? std::optional<DiscRegion>(static_cast<DiscRegion>(region)) :
std::optional<DiscRegion>());
populateCustomAttributes();
updateWindowTitle();
g_main_window->refreshGameListModel();
}
void GameSummaryWidget::onCustomLanguageChanged(int language)
{
GameList::SaveCustomLanguageForPath(
m_path, (language > 0) ? std::optional<GameDatabase::Language>(static_cast<GameDatabase::Language>(language - 1)) :
std::optional<GameDatabase::Language>());
populateCustomAttributes();
g_main_window->refreshGameListModel();
}

View File

@ -27,6 +27,7 @@ public:
void reloadGameSettings();
private Q_SLOTS:
void onCustomLanguageChanged(int language);
void onCompatibilityCommentsClicked();
void onInputProfileChanged(int index);
void onEditInputProfileClicked();

View File

@ -30,67 +30,36 @@
<property name="bottomMargin">
<number>0</number>
</property>
<item row="8" column="1">
<widget class="QLineEdit" name="genre">
<item row="10" column="1">
<widget class="QLineEdit" name="developer">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="title">
<property name="placeholderText">
<string>Clear the line to restore the original title...</string>
<item row="13" column="1">
<widget class="QLineEdit" name="controllers">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreTitle">
<property name="enabled">
<bool>false</bool>
</property>
<item row="17" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Restore</string>
<string>Tracks:</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreRegion">
<property name="enabled">
<bool>false</bool>
</property>
<item row="14" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Restore</string>
<string>Input Profile:</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Image Path:</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="path">
<item row="9" column="1">
<widget class="QLineEdit" name="genre">
<property name="readOnly">
<bool>true</bool>
</property>
@ -103,7 +72,35 @@
</property>
</widget>
</item>
<item row="18" column="0" colspan="2">
<item row="12" column="1">
<widget class="QLineEdit" name="releaseInfo">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="serial">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Controllers:</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Release Info:</string>
</property>
</widget>
</item>
<item row="19" column="0" colspan="2">
<widget class="QTableWidget" name="tracks">
<property name="editTriggers">
<set>QAbstractItemView::EditTrigger::NoEditTriggers</set>
@ -146,77 +143,7 @@
</column>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="9" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Developer:</string>
</property>
</widget>
</item>
<item row="12" column="0">
<widget class="QLabel" name="label_15">
<property name="text">
<string>Controllers:</string>
</property>
</widget>
</item>
<item row="16" column="0">
<widget class="QLabel" name="label_8">
<property name="text">
<string>Tracks:</string>
</property>
</widget>
</item>
<item row="12" column="1">
<widget class="QLineEdit" name="controllers">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QComboBox" name="entryType">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="11" column="0">
<widget class="QLabel" name="label_14">
<property name="text">
<string>Release Info:</string>
</property>
</widget>
</item>
<item row="13" column="0">
<widget class="QLabel" name="label_9">
<property name="text">
<string>Input Profile:</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="serial">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Genre:</string>
</property>
</widget>
</item>
<item row="16" column="1">
<item row="17" column="1">
<layout class="QHBoxLayout" name="verifyLayout" stretch="0,1,0">
<item>
<spacer name="verifySpacer">
@ -253,42 +180,42 @@
</item>
</layout>
</item>
<item row="9" column="1">
<widget class="QLineEdit" name="developer">
<property name="readOnly">
<bool>true</bool>
<item row="3" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_7">
<item>
<widget class="QLineEdit" name="title">
<property name="placeholderText">
<string>Clear the line to restore the original title...</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<item>
<widget class="QPushButton" name="restoreTitle">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Type:</string>
<string>Restore</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
</layout>
</item>
<item row="0" column="0">
<widget class="QLabel" name="label_4">
<property name="text">
<string>Title:</string>
<string>Image Path:</string>
</property>
</widget>
</item>
<item row="11" column="1">
<widget class="QLineEdit" name="releaseInfo">
<property name="readOnly">
<bool>true</bool>
<item row="5" column="1">
<widget class="QComboBox" name="entryType">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compatibility:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<item row="8" column="1">
<layout class="QHBoxLayout" name="horizontalLayout" stretch="1,0">
<item>
<widget class="QComboBox" name="compatibility">
@ -309,7 +236,28 @@
</item>
</layout>
</item>
<item row="13" column="1">
<item row="9" column="0">
<widget class="QLabel" name="label_11">
<property name="text">
<string>Genre:</string>
</property>
</widget>
</item>
<item row="10" column="0">
<widget class="QLabel" name="label_12">
<property name="text">
<string>Developer:</string>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_3">
<property name="text">
<string>Region:</string>
</property>
</widget>
</item>
<item row="14" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_2" stretch="1,0">
<item>
<widget class="QComboBox" name="inputProfile"/>
@ -323,6 +271,79 @@
</item>
</layout>
</item>
<item row="8" column="0">
<widget class="QLabel" name="label_7">
<property name="text">
<string>Compatibility:</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_10">
<property name="text">
<string>Type:</string>
</property>
</widget>
</item>
<item row="6" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_6">
<item>
<widget class="QComboBox" name="region">
<property name="sizePolicy">
<sizepolicy hsizetype="Expanding" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="restoreRegion">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Restore</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="path">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_2">
<property name="text">
<string>Title:</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_5">
<property name="text">
<string>Languages:</string>
</property>
</widget>
</item>
<item row="7" column="1">
<layout class="QHBoxLayout" name="horizontalLayout_3" stretch="1,0">
<item>
<widget class="QLineEdit" name="languages">
<property name="readOnly">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="customLanguage"/>
</item>
</layout>
</item>
</layout>
</widget>
<resources/>

View File

@ -671,7 +671,7 @@ void GraphicsSettingsWidget::setupAdditionalUi()
QString::fromUtf8(Settings::GetDisplayDeinterlacingModeDisplayName(static_cast<DisplayDeinterlacingMode>(i))));
}
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::Count); i++)
for (u32 i = 0; i < static_cast<u32>(DisplayCropMode::MaxCount); i++)
{
m_ui.displayCropMode->addItem(
QString::fromUtf8(Settings::GetDisplayCropModeDisplayName(static_cast<DisplayCropMode>(i))));

View File

@ -2030,7 +2030,7 @@ void MainWindow::connectSignals()
Settings::DEFAULT_GPU_RENDERER, GPURenderer::Count);
SettingWidgetBinder::BindMenuToEnumSetting(
m_ui.menuCropMode, "Display", "CropMode", &Settings::ParseDisplayCropMode, &Settings::GetDisplayCropModeName,
&Settings::GetDisplayCropModeDisplayName, Settings::DEFAULT_DISPLAY_CROP_MODE, DisplayCropMode::Count);
&Settings::GetDisplayCropModeDisplayName, Settings::DEFAULT_DISPLAY_CROP_MODE, DisplayCropMode::MaxCount);
SettingWidgetBinder::BindMenuToEnumSetting(m_ui.menuLogLevel, "Logging", "LogLevel", &Settings::ParseLogLevelName,
&Settings::GetLogLevelName, &Settings::GetLogLevelDisplayName,
Settings::DEFAULT_LOG_LEVEL, Log::Level::MaxCount);

View File

@ -171,6 +171,7 @@ void MemoryScannerWindow::connectUi()
});
connect(m_ui.scanAddWatch, &QPushButton::clicked, this, &MemoryScannerWindow::addToWatchClicked);
connect(m_ui.scanAddManualAddress, &QPushButton::clicked, this, &MemoryScannerWindow::addManualWatchAddressClicked);
connect(m_ui.scanFreezeWatch, &QPushButton::clicked, this, &MemoryScannerWindow::freezeWatchClicked);
connect(m_ui.scanRemoveWatch, &QPushButton::clicked, this, &MemoryScannerWindow::removeWatchClicked);
connect(m_ui.scanTable, &QTableWidget::currentItemChanged, this, &MemoryScannerWindow::scanCurrentItemChanged);
connect(m_ui.watchTable, &QTableWidget::currentItemChanged, this, &MemoryScannerWindow::watchCurrentItemChanged);
@ -208,6 +209,7 @@ void MemoryScannerWindow::enableUi(bool enabled)
m_ui.scanAddWatch->setEnabled(enabled && !m_ui.scanTable->selectedItems().empty());
m_ui.watchTable->setEnabled(enabled);
m_ui.scanAddManualAddress->setEnabled(enabled);
m_ui.scanFreezeWatch->setEnabled(enabled && !m_ui.watchTable->selectedItems().empty());
m_ui.scanRemoveWatch->setEnabled(enabled && !m_ui.watchTable->selectedItems().empty());
}
@ -330,6 +332,22 @@ void MemoryScannerWindow::addManualWatchAddressClicked()
updateWatch();
}
void MemoryScannerWindow::freezeWatchClicked()
{
const int indexFirst = getSelectedWatchIndexFirst();
const int indexLast = getSelectedWatchIndexLast();
if (indexFirst < 0)
return;
const bool freeze = m_watch.GetEntryFreeze(indexFirst);
for (int index = indexLast; index >= indexFirst; index--)
{
m_watch.SetEntryFreeze(static_cast<u32>(index), !freeze);
updateWatch();
}
}
void MemoryScannerWindow::removeWatchClicked()
{
const int indexFirst = getSelectedWatchIndexFirst();
@ -351,6 +369,7 @@ void MemoryScannerWindow::scanCurrentItemChanged(QTableWidgetItem* current, QTab
void MemoryScannerWindow::watchCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous)
{
m_ui.scanFreezeWatch->setEnabled((current != nullptr));
m_ui.scanRemoveWatch->setEnabled((current != nullptr));
}
@ -569,6 +588,7 @@ void MemoryScannerWindow::updateWatch()
}
m_ui.scanSaveWatch->setEnabled(!entries.empty());
m_ui.scanFreezeWatch->setEnabled(false);
m_ui.scanRemoveWatch->setEnabled(false);
}

View File

@ -37,6 +37,7 @@ private Q_SLOTS:
void addToWatchClicked();
void addManualWatchAddressClicked();
void freezeWatchClicked();
void removeWatchClicked();
void scanCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous);
void watchCurrentItemChanged(QTableWidgetItem* current, QTableWidgetItem* previous);

View File

@ -436,13 +436,23 @@
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanFreezeWatch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Freeze Selected Entries</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="scanRemoveWatch">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Remove Selected Entries from Watch List</string>
<string>Remove Selected Entries</string>
</property>
</widget>
</item>

View File

@ -312,6 +312,12 @@ QIcon QtUtils::GetIconForCompatibility(GameDatabase::CompatibilityRating rating)
QtHost::GetResourcePath(TinyString::from_format("images/star-{}.svg", static_cast<u32>(rating)), true)));
}
QIcon QtUtils::GetIconForLanguage(std::string_view language_name)
{
return QIcon(
QString::fromStdString(QtHost::GetResourcePath(GameDatabase::GetLanguageFlagResourceName(language_name), true)));
}
qreal QtUtils::GetDevicePixelRatioForWidget(const QWidget* widget)
{
const QScreen* screen_for_ratio = widget->screen();

View File

@ -113,6 +113,7 @@ QIcon GetIconForRegion(DiscRegion region);
/// Returns icon for entry type.
QIcon GetIconForEntryType(GameList::EntryType type);
QIcon GetIconForCompatibility(GameDatabase::CompatibilityRating rating);
QIcon GetIconForLanguage(std::string_view language_name);
/// Returns the pixel ratio/scaling factor for a widget.
qreal GetDevicePixelRatioForWidget(const QWidget* widget);

View File

@ -65,6 +65,8 @@ add_library(util
sockets.h
state_wrapper.cpp
state_wrapper.h
texture_decompress.cpp
texture_decompress.h
wav_reader_writer.cpp
wav_reader_writer.h
window_info.cpp

View File

@ -139,11 +139,8 @@ bool D3D11Device::CreateDeviceAndMainSwapChain(std::string_view adapter, Feature
return false;
}
if (!CreateBuffers())
{
Error::SetStringView(error, "Failed to create buffers");
if (!CreateBuffers(error))
return false;
}
return true;
}
@ -185,6 +182,8 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
m_features.texture_buffers_emulated_with_ssbo = false;
m_features.feedback_loops = false;
m_features.geometry_shaders = !(disabled_features & FEATURE_MASK_GEOMETRY_SHADERS);
m_features.compute_shaders =
(!(disabled_features & FEATURE_MASK_COMPUTE_SHADERS) && feature_level >= D3D_FEATURE_LEVEL_11_0);
m_features.partial_msaa_resolve = false;
m_features.memory_import = false;
m_features.explicit_present = false;
@ -201,6 +200,13 @@ void D3D11Device::SetFeatures(FeatureMask disabled_features)
(SUCCEEDED(m_device->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS2, &data, sizeof(data))) &&
data.ROVsSupported);
}
m_features.dxt_textures =
(!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) &&
(SupportsTextureFormat(GPUTexture::Format::BC1) && SupportsTextureFormat(GPUTexture::Format::BC2) &&
SupportsTextureFormat(GPUTexture::Format::BC3)));
m_features.bptc_textures =
(!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) && SupportsTextureFormat(GPUTexture::Format::BC7));
}
D3D11SwapChain::D3D11SwapChain(const WindowInfo& wi, GPUVSyncMode vsync_mode, bool allow_present_throttle,
@ -512,11 +518,11 @@ void D3D11Device::WaitForGPUIdle()
TrimTexturePool();
}
bool D3D11Device::CreateBuffers()
bool D3D11Device::CreateBuffers(Error* error)
{
if (!m_vertex_buffer.Create(D3D11_BIND_VERTEX_BUFFER, VERTEX_BUFFER_SIZE, VERTEX_BUFFER_SIZE) ||
!m_index_buffer.Create(D3D11_BIND_INDEX_BUFFER, INDEX_BUFFER_SIZE, INDEX_BUFFER_SIZE) ||
!m_uniform_buffer.Create(D3D11_BIND_CONSTANT_BUFFER, MIN_UNIFORM_BUFFER_SIZE, MAX_UNIFORM_BUFFER_SIZE))
if (!m_vertex_buffer.Create(D3D11_BIND_VERTEX_BUFFER, VERTEX_BUFFER_SIZE, VERTEX_BUFFER_SIZE, error) ||
!m_index_buffer.Create(D3D11_BIND_INDEX_BUFFER, INDEX_BUFFER_SIZE, INDEX_BUFFER_SIZE, error) ||
!m_uniform_buffer.Create(D3D11_BIND_CONSTANT_BUFFER, MIN_UNIFORM_BUFFER_SIZE, MAX_UNIFORM_BUFFER_SIZE, error))
{
ERROR_LOG("Failed to create vertex/index/uniform buffers.");
return false;
@ -610,7 +616,7 @@ void D3D11Device::ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u3
bool D3D11Device::IsRenderTargetBound(const D3D11Texture* tex) const
{
if (tex->IsRenderTarget() || tex->IsRWTexture())
if (tex->IsRenderTarget() || tex->HasFlag(GPUTexture::Flags::AllowBindAsImage))
{
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
@ -896,19 +902,7 @@ void D3D11Device::PushUniformBuffer(const void* data, u32 data_size)
m_uniform_buffer.Unmap(m_context.Get(), req_size);
s_stats.buffer_streamed += data_size;
if (m_uniform_buffer.IsUsingMapNoOverwrite())
{
const UINT first_constant = (res.index_aligned * UNIFORM_BUFFER_ALIGNMENT) / 16u;
const UINT num_constants = req_size / 16u;
m_context->VSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
m_context->PSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
}
else
{
DebugAssert(res.index_aligned == 0);
m_context->VSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
m_context->PSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
}
BindUniformBuffer(res.index_aligned * UNIFORM_BUFFER_ALIGNMENT, req_size);
}
void* D3D11Device::MapUniformBuffer(u32 size)
@ -930,20 +924,39 @@ void D3D11Device::UnmapUniformBuffer(u32 size)
m_uniform_buffer.Unmap(m_context.Get(), req_size);
s_stats.buffer_streamed += size;
BindUniformBuffer(pos, req_size);
}
void D3D11Device::BindUniformBuffer(u32 offset, u32 size)
{
if (m_uniform_buffer.IsUsingMapNoOverwrite())
{
const UINT first_constant = pos / 16u;
const UINT num_constants = req_size / 16u;
m_context->VSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
m_context->PSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
const UINT first_constant = offset / 16u;
const UINT num_constants = size / 16u;
if (m_current_compute_shader)
{
m_context->CSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
}
else
{
m_context->VSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
m_context->PSSetConstantBuffers1(0, 1, m_uniform_buffer.GetD3DBufferArray(), &first_constant, &num_constants);
}
}
else
{
DebugAssert(offset == 0);
if (m_current_compute_shader)
{
m_context->CSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
}
else
{
DebugAssert(pos == 0);
m_context->VSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
m_context->PSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
}
}
}
void D3D11Device::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds,
GPUPipeline::RenderPassFlag flags)
@ -1004,11 +1017,18 @@ void D3D11Device::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTextu
for (u32 i = 0; i < m_num_current_render_targets; i++)
uavs[i] = m_current_render_targets[i]->GetD3DUAV();
if (!m_current_compute_shader)
{
m_context->OMSetRenderTargetsAndUnorderedAccessViews(
0, nullptr, m_current_depth_target ? m_current_depth_target->GetD3DDSV() : nullptr, 0,
m_num_current_render_targets, uavs.data(), nullptr);
}
else
{
m_context->CSSetUnorderedAccessViews(0, m_num_current_render_targets, uavs.data(), nullptr);
}
}
else
{
std::array<ID3D11RenderTargetView*, MAX_RENDER_TARGETS> rtvs;
for (u32 i = 0; i < m_num_current_render_targets; i++)
@ -1037,7 +1057,7 @@ void D3D11Device::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* s
// Runtime will null these if we don't...
DebugAssert(!texture ||
!((texture->IsRenderTarget() || texture->IsRWTexture()) &&
!((texture->IsRenderTarget() || texture->HasFlag(GPUTexture::Flags::AllowBindAsImage)) &&
IsRenderTargetBound(static_cast<D3D11Texture*>(texture))) ||
!(texture->IsDepthStencil() &&
(!m_current_depth_target || m_current_depth_target != static_cast<D3D11Texture*>(texture))));
@ -1046,11 +1066,15 @@ void D3D11Device::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* s
{
m_current_textures[slot] = T;
m_context->PSSetShaderResources(slot, 1, &T);
if (m_current_compute_shader)
m_context->CSSetShaderResources(slot, 1, &T);
}
if (m_current_samplers[slot] != S)
{
m_current_samplers[slot] = S;
m_context->PSSetSamplers(slot, 1, &S);
if (m_current_compute_shader)
m_context->CSSetSamplers(slot, 1, &S);
}
}
@ -1060,6 +1084,8 @@ void D3D11Device::SetTextureBuffer(u32 slot, GPUTextureBuffer* buffer)
if (m_current_textures[slot] != B)
{
m_current_textures[slot] = B;
// Compute doesn't support texture buffers, yet...
m_context->PSSetShaderResources(slot, 1, &B);
}
}
@ -1078,7 +1104,7 @@ void D3D11Device::UnbindTexture(D3D11Texture* tex)
}
}
if (tex->IsRenderTarget() || tex->IsRWTexture())
if (tex->IsRenderTarget() || tex->HasFlag(GPUTexture::Flags::AllowBindAsImage))
{
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
@ -1113,14 +1139,14 @@ void D3D11Device::SetScissor(const GSVector4i rc)
void D3D11Device::Draw(u32 vertex_count, u32 base_vertex)
{
DebugAssert(!m_vertex_buffer.IsMapped() && !m_index_buffer.IsMapped());
DebugAssert(!m_vertex_buffer.IsMapped() && !m_index_buffer.IsMapped() && !m_current_compute_shader);
s_stats.num_draws++;
m_context->Draw(vertex_count, base_vertex);
}
void D3D11Device::DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex)
{
DebugAssert(!m_vertex_buffer.IsMapped() && !m_index_buffer.IsMapped());
DebugAssert(!m_vertex_buffer.IsMapped() && !m_index_buffer.IsMapped() && !m_current_compute_shader);
s_stats.num_draws++;
m_context->DrawIndexed(index_count, base_index, base_vertex);
}
@ -1129,3 +1155,15 @@ void D3D11Device::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 ba
{
Panic("Barriers are not supported");
}
void D3D11Device::Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z)
{
DebugAssert(m_current_compute_shader);
s_stats.num_draws++;
const u32 groups_x = threads_x / group_size_x;
const u32 groups_y = threads_y / group_size_y;
const u32 groups_z = threads_z / group_size_z;
m_context->Dispatch(groups_x, groups_y, groups_z);
}

View File

@ -50,15 +50,18 @@ public:
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements) override;
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
const void* data = nullptr, u32 data_stride = 0,
Error* error = nullptr) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config, Error* error = nullptr) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements,
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride) override;
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_stride,
Error* error = nullptr) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
void CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src,
@ -75,6 +78,7 @@ public:
std::string_view source, const char* entry_point,
DynamicHeapArray<u8>* out_binary, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override;
void PushDebugGroup(const char* name) override;
void PopDebugGroup() override;
@ -98,6 +102,8 @@ public:
void Draw(u32 vertex_count, u32 base_vertex) override;
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
@ -138,8 +144,10 @@ private:
void SetFeatures(FeatureMask disabled_features);
bool CreateBuffers();
bool CreateBuffers(Error* error);
void DestroyBuffers();
void BindUniformBuffer(u32 offset, u32 size);
void UnbindComputePipeline();
bool IsRenderTargetBound(const D3D11Texture* tex) const;
@ -180,6 +188,7 @@ private:
ID3D11VertexShader* m_current_vertex_shader = nullptr;
ID3D11GeometryShader* m_current_geometry_shader = nullptr;
ID3D11PixelShader* m_current_pixel_shader = nullptr;
ID3D11ComputeShader* m_current_compute_shader = nullptr;
ID3D11RasterizerState* m_current_rasterizer_state = nullptr;
ID3D11DepthStencilState* m_current_depth_state = nullptr;
ID3D11BlendState* m_current_blend_state = nullptr;

View File

@ -3,6 +3,7 @@
#include "d3d11_pipeline.h"
#include "d3d11_device.h"
#include "d3d11_texture.h"
#include "d3d_common.h"
#include "common/assert.h"
@ -121,10 +122,10 @@ std::unique_ptr<GPUShader> D3D11Device::CreateShaderFromSource(GPUShaderStage st
D3D11Pipeline::D3D11Pipeline(ComPtr<ID3D11RasterizerState> rs, ComPtr<ID3D11DepthStencilState> ds,
ComPtr<ID3D11BlendState> bs, ComPtr<ID3D11InputLayout> il, ComPtr<ID3D11VertexShader> vs,
ComPtr<ID3D11GeometryShader> gs, ComPtr<ID3D11PixelShader> ps,
ComPtr<ID3D11GeometryShader> gs, ComPtr<ID3D11DeviceChild> ps_or_cs,
D3D11_PRIMITIVE_TOPOLOGY topology, u32 vertex_stride, u32 blend_factor)
: m_rs(std::move(rs)), m_ds(std::move(ds)), m_bs(std::move(bs)), m_il(std::move(il)), m_vs(std::move(vs)),
m_gs(std::move(gs)), m_ps(std::move(ps)), m_topology(topology), m_vertex_stride(vertex_stride),
m_gs(std::move(gs)), m_ps_or_cs(std::move(ps_or_cs)), m_topology(topology), m_vertex_stride(vertex_stride),
m_blend_factor(blend_factor), m_blend_factor_float(GPUDevice::RGBA8ToFloat(blend_factor))
{
}
@ -215,7 +216,8 @@ size_t D3D11Device::BlendStateMapHash::operator()(const BlendStateMapKey& key) c
return h;
}
D3D11Device::ComPtr<ID3D11BlendState> D3D11Device::GetBlendState(const GPUPipeline::BlendState& bs, u32 num_rts, Error* error)
D3D11Device::ComPtr<ID3D11BlendState> D3D11Device::GetBlendState(const GPUPipeline::BlendState& bs, u32 num_rts,
Error* error)
{
ComPtr<ID3D11BlendState> dbs;
@ -365,14 +367,34 @@ std::unique_ptr<GPUPipeline> D3D11Device::CreatePipeline(const GPUPipeline::Grap
primitives[static_cast<u8>(config.primitive)], vertex_stride, config.blend.constant));
}
std::unique_ptr<GPUPipeline> D3D11Device::CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error)
{
if (!config.compute_shader) [[unlikely]]
{
Error::SetStringView(error, "Missing compute shader.");
return {};
}
return std::unique_ptr<GPUPipeline>(
new D3D11Pipeline(nullptr, nullptr, nullptr, nullptr, nullptr, nullptr,
static_cast<const D3D11Shader*>(config.compute_shader)->GetComputeShader(),
D3D11_PRIMITIVE_TOPOLOGY_UNDEFINED, 0, 0));
}
void D3D11Device::SetPipeline(GPUPipeline* pipeline)
{
if (m_current_pipeline == pipeline)
return;
const bool was_compute = m_current_pipeline && m_current_pipeline->IsComputePipeline();
D3D11Pipeline* const PL = static_cast<D3D11Pipeline*>(pipeline);
m_current_pipeline = PL;
if (!PL->IsComputePipeline())
{
if (was_compute)
UnbindComputePipeline();
if (ID3D11InputLayout* il = PL->GetInputLayout(); m_current_input_layout != il)
{
m_current_input_layout = il;
@ -430,12 +452,64 @@ void D3D11Device::SetPipeline(GPUPipeline* pipeline)
m_context->OMSetBlendState(bs, RGBA8ToFloat(m_current_blend_factor).data(), 0xFFFFFFFFu);
}
}
else
{
if (ID3D11ComputeShader* cs = m_current_pipeline->GetComputeShader(); cs != m_current_compute_shader)
{
m_current_compute_shader = cs;
m_context->CSSetShader(cs, nullptr, 0);
}
if (!was_compute)
{
// need to bind all SRVs/samplers
u32 count;
for (count = 0; count < MAX_TEXTURE_SAMPLERS; count++)
{
if (!m_current_textures[count])
break;
}
if (count > 0)
{
m_context->CSSetShaderResources(0, count, m_current_textures.data());
m_context->CSSetSamplers(0, count, m_current_samplers.data());
}
if (m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages)
{
ID3D11UnorderedAccessView* uavs[MAX_TEXTURE_SAMPLERS];
for (u32 i = 0; i < m_num_current_render_targets; i++)
uavs[i] = m_current_render_targets[i]->GetD3DUAV();
m_context->OMSetRenderTargets(0, nullptr, nullptr);
m_context->CSSetUnorderedAccessViews(0, m_num_current_render_targets, uavs, nullptr);
}
}
}
}
void D3D11Device::UnbindPipeline(D3D11Pipeline* pl)
{
if (m_current_pipeline != pl)
return;
if (pl->IsComputePipeline())
UnbindComputePipeline();
// Let the runtime deal with the dead objects...
m_current_pipeline = nullptr;
}
void D3D11Device::UnbindComputePipeline()
{
m_current_compute_shader = nullptr;
ID3D11ShaderResourceView* null_srvs[MAX_TEXTURE_SAMPLERS] = {};
ID3D11SamplerState* null_samplers[MAX_TEXTURE_SAMPLERS] = {};
ID3D11UnorderedAccessView* null_uavs[MAX_RENDER_TARGETS] = {};
m_context->CSSetShader(nullptr, nullptr, 0);
m_context->CSSetShaderResources(0, MAX_TEXTURE_SAMPLERS, null_srvs);
m_context->CSSetSamplers(0, MAX_TEXTURE_SAMPLERS, null_samplers);
if (m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages)
m_context->CSSetUnorderedAccessViews(0, m_num_current_render_targets, null_uavs, nullptr);
}

View File

@ -51,13 +51,18 @@ public:
void SetDebugName(std::string_view name) override;
ALWAYS_INLINE bool IsComputePipeline() const { return !m_vs; }
ALWAYS_INLINE ID3D11RasterizerState* GetRasterizerState() const { return m_rs.Get(); }
ALWAYS_INLINE ID3D11DepthStencilState* GetDepthStencilState() const { return m_ds.Get(); }
ALWAYS_INLINE ID3D11BlendState* GetBlendState() const { return m_bs.Get(); }
ALWAYS_INLINE ID3D11InputLayout* GetInputLayout() const { return m_il.Get(); }
ALWAYS_INLINE ID3D11VertexShader* GetVertexShader() const { return m_vs.Get(); }
ALWAYS_INLINE ID3D11GeometryShader* GetGeometryShader() const { return m_gs.Get(); }
ALWAYS_INLINE ID3D11PixelShader* GetPixelShader() const { return m_ps.Get(); }
ALWAYS_INLINE ID3D11PixelShader* GetPixelShader() const { return static_cast<ID3D11PixelShader*>(m_ps_or_cs.Get()); }
ALWAYS_INLINE ID3D11ComputeShader* GetComputeShader() const
{
return static_cast<ID3D11ComputeShader*>(m_ps_or_cs.Get());
}
ALWAYS_INLINE D3D11_PRIMITIVE_TOPOLOGY GetPrimitiveTopology() const { return m_topology; }
ALWAYS_INLINE u32 GetVertexStride() const { return m_vertex_stride; }
ALWAYS_INLINE u32 GetBlendFactor() const { return m_blend_factor; }
@ -66,7 +71,8 @@ public:
private:
D3D11Pipeline(ComPtr<ID3D11RasterizerState> rs, ComPtr<ID3D11DepthStencilState> ds, ComPtr<ID3D11BlendState> bs,
ComPtr<ID3D11InputLayout> il, ComPtr<ID3D11VertexShader> vs, ComPtr<ID3D11GeometryShader> gs,
ComPtr<ID3D11PixelShader> ps, D3D11_PRIMITIVE_TOPOLOGY topology, u32 vertex_stride, u32 blend_factor);
ComPtr<ID3D11DeviceChild> ps_or_cs, D3D11_PRIMITIVE_TOPOLOGY topology, u32 vertex_stride,
u32 blend_factor);
ComPtr<ID3D11RasterizerState> m_rs;
ComPtr<ID3D11DepthStencilState> m_ds;
@ -74,7 +80,7 @@ private:
ComPtr<ID3D11InputLayout> m_il;
ComPtr<ID3D11VertexShader> m_vs;
ComPtr<ID3D11GeometryShader> m_gs;
ComPtr<ID3D11PixelShader> m_ps;
ComPtr<ID3D11DeviceChild> m_ps_or_cs;
D3D11_PRIMITIVE_TOPOLOGY m_topology;
u32 m_vertex_stride;
u32 m_blend_factor;

View File

@ -8,6 +8,7 @@
#include "common/assert.h"
#include "common/error.h"
#include "common/log.h"
#include "common/small_string.h"
LOG_CHANNEL(GPUDevice);
@ -27,7 +28,7 @@ D3D11StreamBuffer::~D3D11StreamBuffer()
Destroy();
}
bool D3D11StreamBuffer::Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max_size)
bool D3D11StreamBuffer::Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max_size, Error* error)
{
D3D11_FEATURE_DATA_D3D11_OPTIONS options = {};
HRESULT hr = D3D11Device::GetD3DDevice()->CheckFeatureSupport(D3D11_FEATURE_D3D11_OPTIONS, &options, sizeof(options));
@ -72,7 +73,7 @@ bool D3D11StreamBuffer::Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max
hr = D3D11Device::GetD3DDevice()->CreateBuffer(&desc, nullptr, &buffer);
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("Creating buffer failed: {}", Error::CreateHResult(hr).GetDescription());
Error::SetHResult(error, TinyString::from_format("CreateBuffer({}) failed: ", create_size), hr);
return false;
}

View File

@ -9,6 +9,8 @@
#include <d3d11_1.h>
#include <wrl/client.h>
class Error;
class D3D11StreamBuffer
{
public:
@ -26,7 +28,7 @@ public:
ALWAYS_INLINE bool IsMapped() const { return m_mapped; }
ALWAYS_INLINE bool IsUsingMapNoOverwrite() const { return m_use_map_no_overwrite; }
bool Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max_size);
bool Create(D3D11_BIND_FLAG bind_flags, u32 min_size, u32 max_size, Error* error);
void Destroy();
struct MappingResult

View File

@ -6,6 +6,7 @@
#include "d3d_common.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/log.h"
#include "common/string_util.h"
@ -17,9 +18,11 @@ LOG_CHANNEL(GPUDevice);
std::unique_ptr<GPUTexture> D3D11Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data, u32 data_stride)
GPUTexture::Flags flags, const void* data /* = nullptr */,
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
{
return D3D11Texture::Create(m_device.Get(), width, height, layers, levels, samples, type, format, data, data_stride);
return D3D11Texture::Create(m_device.Get(), width, height, layers, levels, samples, type, format, flags, data,
data_stride, error);
}
bool D3D11Device::SupportsTextureFormat(GPUTexture::Format format) const
@ -44,7 +47,7 @@ void D3D11Sampler::SetDebugName(std::string_view name)
SetD3DDebugObjectName(m_ss.Get(), name);
}
std::unique_ptr<GPUSampler> D3D11Device::CreateSampler(const GPUSampler::Config& config)
std::unique_ptr<GPUSampler> D3D11Device::CreateSampler(const GPUSampler::Config& config, Error* error)
{
static constexpr std::array<D3D11_TEXTURE_ADDRESS_MODE, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{
D3D11_TEXTURE_ADDRESS_WRAP, // Repeat
@ -87,7 +90,7 @@ std::unique_ptr<GPUSampler> D3D11Device::CreateSampler(const GPUSampler::Config&
const HRESULT hr = m_device->CreateSamplerState(&desc, ss.GetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("CreateSamplerState() failed: {:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateSamplerState() failed: ", hr);
return {};
}
@ -95,10 +98,10 @@ std::unique_ptr<GPUSampler> D3D11Device::CreateSampler(const GPUSampler::Config&
}
D3D11Texture::D3D11Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
ComPtr<ID3D11Texture2D> texture, ComPtr<ID3D11ShaderResourceView> srv,
Flags flags, ComPtr<ID3D11Texture2D> texture, ComPtr<ID3D11ShaderResourceView> srv,
ComPtr<ID3D11View> rtv_dsv, ComPtr<ID3D11UnorderedAccessView> uav)
: GPUTexture(static_cast<u16>(width), static_cast<u16>(height), static_cast<u8>(layers), static_cast<u8>(levels),
static_cast<u8>(samples), type, format),
static_cast<u8>(samples), type, format, flags),
m_texture(std::move(texture)), m_srv(std::move(srv)), m_rtv_dsv(std::move(rtv_dsv)), m_uav(std::move(uav))
{
}
@ -127,7 +130,7 @@ void D3D11Texture::CommitClear(ID3D11DeviceContext1* context)
else
context->ClearDepthStencilView(GetD3DDSV(), D3D11_CLEAR_DEPTH, GetClearDepth(), 0);
}
else if (IsRenderTarget() || IsRWTexture())
else if (IsRenderTarget())
{
if (m_state == GPUTexture::State::Invalidated)
context->DiscardView(GetD3DRTV());
@ -141,26 +144,27 @@ void D3D11Texture::CommitClear(ID3D11DeviceContext1* context)
bool D3D11Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer /*= 0*/,
u32 level /*= 0*/)
{
if (m_type == Type::DynamicTexture)
if (HasFlag(Flags::AllowMap))
{
void* map;
u32 map_stride;
if (!Map(&map, &map_stride, x, y, width, height, layer, level))
u32 map_pitch;
if (!Map(&map, &map_pitch, x, y, width, height, layer, level))
return false;
StringUtil::StrideMemCpy(map, map_stride, data, pitch, GetPixelSize() * width, height);
CopyTextureDataForUpload(width, height, m_format, map, map_pitch, data, pitch);
Unmap();
return true;
}
const CD3D11_BOX box(static_cast<LONG>(x), static_cast<LONG>(y), 0, static_cast<LONG>(x + width),
static_cast<LONG>(y + height), 1);
const u32 bs = GetBlockSize();
const D3D11_BOX box = {Common::AlignDownPow2(x, bs), Common::AlignDownPow2(y, bs), 0U,
Common::AlignUpPow2(x + width, bs), Common::AlignUpPow2(y + height, bs), 1U};
const u32 srnum = D3D11CalcSubresource(level, layer, m_levels);
ID3D11DeviceContext1* context = D3D11Device::GetD3DContext();
CommitClear(context);
GPUDevice::GetStatistics().buffer_streamed += height * pitch;
GPUDevice::GetStatistics().buffer_streamed += CalcUploadSize(height, pitch);
GPUDevice::GetStatistics().num_uploads++;
context->UpdateSubresource(m_texture.Get(), srnum, &box, data, pitch, 0);
@ -171,7 +175,7 @@ bool D3D11Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
bool D3D11Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer /*= 0*/,
u32 level /*= 0*/)
{
if (m_type != Type::DynamicTexture || (x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) ||
if (!HasFlag(Flags::AllowMap) || (x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) ||
layer > m_layers || level > m_levels)
{
return false;
@ -191,10 +195,18 @@ bool D3D11Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32
return false;
}
GPUDevice::GetStatistics().buffer_streamed += height * sr.RowPitch;
GPUDevice::GetStatistics().buffer_streamed += CalcUploadSize(height, sr.RowPitch);
GPUDevice::GetStatistics().num_uploads++;
if (IsCompressedFormat(m_format))
{
*map = static_cast<u8*>(sr.pData) + ((y / GetBlockSize()) * sr.RowPitch) +
((x / GetBlockSize()) * GetPixelSize());
}
else
{
*map = static_cast<u8*>(sr.pData) + (y * sr.RowPitch) + (x * GetPixelSize());
}
*map_stride = sr.RowPitch;
m_mapped_subresource = srnum;
m_state = GPUTexture::State::Dirty;
@ -207,6 +219,12 @@ void D3D11Texture::Unmap()
m_mapped_subresource = 0;
}
void D3D11Texture::GenerateMipmaps()
{
DebugAssert(HasFlag(Flags::AllowGenerateMipmaps));
D3D11Device::GetD3DContext()->GenerateMips(m_srv.Get());
}
void D3D11Texture::SetDebugName(std::string_view name)
{
SetD3DDebugObjectName(m_texture.Get(), name);
@ -218,43 +236,57 @@ DXGI_FORMAT D3D11Texture::GetDXGIFormat() const
}
std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 width, u32 height, u32 layers, u32 levels,
u32 samples, Type type, Format format,
const void* initial_data /* = nullptr */,
u32 initial_data_stride /* = 0 */)
u32 samples, Type type, Format format, Flags flags,
const void* initial_data, u32 initial_data_stride, Error* error)
{
if (!ValidateConfig(width, height, layers, layers, samples, type, format))
if (!ValidateConfig(width, height, layers, levels, samples, type, format, flags, error))
return nullptr;
u32 bind_flags = 0;
D3D11_USAGE usage = D3D11_USAGE_DEFAULT;
u32 cpu_access = 0;
u32 misc = 0;
switch (type)
{
case Type::RenderTarget:
bind_flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
break;
case Type::DepthStencil:
bind_flags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
break;
case Type::Texture:
bind_flags = D3D11_BIND_SHADER_RESOURCE;
break;
case Type::DynamicTexture:
bind_flags = D3D11_BIND_SHADER_RESOURCE;
case Type::RenderTarget:
bind_flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
break;
case Type::DepthStencil:
bind_flags = D3D11_BIND_DEPTH_STENCIL | D3D11_BIND_SHADER_RESOURCE;
break;
DefaultCaseIsUnreachable();
}
if ((flags & Flags::AllowBindAsImage) != Flags::None)
{
DebugAssert(levels == 1);
bind_flags |= D3D11_BIND_UNORDERED_ACCESS;
}
if ((flags & Flags::AllowGenerateMipmaps) != Flags::None)
{
// Needs RT annoyingly.
bind_flags |= D3D11_BIND_RENDER_TARGET;
misc = D3D11_RESOURCE_MISC_GENERATE_MIPS;
}
if ((flags & Flags::AllowMap) != Flags::None)
{
DebugAssert(type == Type::Texture);
usage = D3D11_USAGE_DYNAMIC;
cpu_access = D3D11_CPU_ACCESS_WRITE;
break;
case Type::RWTexture:
bind_flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
break;
default:
break;
}
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(format);
CD3D11_TEXTURE2D_DESC desc(fm.resource_format, width, height, layers, levels, bind_flags, usage, cpu_access, samples,
0, 0);
0, misc);
D3D11_SUBRESOURCE_DATA srd;
srd.pSysMem = initial_data;
@ -265,15 +297,13 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
const HRESULT tex_hr = device->CreateTexture2D(&desc, initial_data ? &srd : nullptr, texture.GetAddressOf());
if (FAILED(tex_hr))
{
ERROR_LOG("Create texture failed: 0x{:08X} ({}x{} levels:{} samples:{} format:{} bind_flags:{:X} initial_data:{})",
static_cast<unsigned>(tex_hr), width, height, levels, samples, static_cast<unsigned>(format), bind_flags,
initial_data);
Error::SetHResult(error, "CreateTexture2D() failed: ", tex_hr);
return nullptr;
}
if (initial_data)
{
GPUDevice::GetStatistics().buffer_streamed += height * initial_data_stride;
GPUDevice::GetStatistics().buffer_streamed += CalcUploadSize(format, height, initial_data_stride);
GPUDevice::GetStatistics().num_uploads++;
}
@ -288,7 +318,7 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
const HRESULT hr = device->CreateShaderResourceView(texture.Get(), &srv_desc, srv.GetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("Create SRV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateShaderResourceView() failed: ", hr);
return nullptr;
}
}
@ -303,7 +333,7 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
const HRESULT hr = device->CreateRenderTargetView(texture.Get(), &rtv_desc, rtv.GetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("Create RTV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateRenderTargetView() failed: ", hr);
return nullptr;
}
@ -318,7 +348,7 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
const HRESULT hr = device->CreateDepthStencilView(texture.Get(), &dsv_desc, dsv.GetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("Create DSV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateDepthStencilView() failed: ", hr);
return nullptr;
}
@ -334,12 +364,12 @@ std::unique_ptr<D3D11Texture> D3D11Texture::Create(ID3D11Device* device, u32 wid
const HRESULT hr = device->CreateUnorderedAccessView(texture.Get(), &uav_desc, uav.GetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("Create UAV for texture failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateUnorderedAccessView() failed: ", hr);
return nullptr;
}
}
return std::unique_ptr<D3D11Texture>(new D3D11Texture(width, height, layers, levels, samples, type, format,
return std::unique_ptr<D3D11Texture>(new D3D11Texture(width, height, layers, levels, samples, type, format, flags,
std::move(texture), std::move(srv), std::move(rtv_dsv),
std::move(uav)));
}
@ -350,10 +380,10 @@ D3D11TextureBuffer::D3D11TextureBuffer(Format format, u32 size_in_elements) : GP
D3D11TextureBuffer::~D3D11TextureBuffer() = default;
bool D3D11TextureBuffer::CreateBuffer()
bool D3D11TextureBuffer::CreateBuffer(Error* error)
{
const u32 size_in_bytes = GetSizeInBytes();
if (!m_buffer.Create(D3D11_BIND_SHADER_RESOURCE, size_in_bytes, size_in_bytes))
if (!m_buffer.Create(D3D11_BIND_SHADER_RESOURCE, size_in_bytes, size_in_bytes, error))
return false;
static constexpr std::array<DXGI_FORMAT, static_cast<u32>(Format::MaxCount)> dxgi_formats = {{
@ -366,7 +396,7 @@ bool D3D11TextureBuffer::CreateBuffer()
D3D11Device::GetD3DDevice()->CreateShaderResourceView(m_buffer.GetD3DBuffer(), &srv_desc, m_srv.GetAddressOf());
if (FAILED(hr)) [[unlikely]]
{
ERROR_LOG("CreateShaderResourceView() failed: {:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateShaderResourceView() failed: ", hr);
return false;
}
@ -395,10 +425,10 @@ void D3D11TextureBuffer::SetDebugName(std::string_view name)
}
std::unique_ptr<GPUTextureBuffer> D3D11Device::CreateTextureBuffer(GPUTextureBuffer::Format format,
u32 size_in_elements)
u32 size_in_elements, Error* error /* = nullptr */)
{
std::unique_ptr<D3D11TextureBuffer> tb = std::make_unique<D3D11TextureBuffer>(format, size_in_elements);
if (!tb->CreateBuffer())
if (!tb->CreateBuffer(error))
tb.reset();
return tb;
@ -416,7 +446,8 @@ D3D11DownloadTexture::~D3D11DownloadTexture()
D3D11DownloadTexture::Unmap();
}
std::unique_ptr<D3D11DownloadTexture> D3D11DownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format)
std::unique_ptr<D3D11DownloadTexture> D3D11DownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format,
Error* error)
{
D3D11_TEXTURE2D_DESC desc = {};
desc.Width = width;
@ -433,7 +464,7 @@ std::unique_ptr<D3D11DownloadTexture> D3D11DownloadTexture::Create(u32 width, u3
HRESULT hr = D3D11Device::GetD3DDevice()->CreateTexture2D(&desc, nullptr, tex.GetAddressOf());
if (FAILED(hr))
{
ERROR_LOG("CreateTexture2D() failed: {:08X}", hr);
Error::SetHResult(error, "CreateTexture2D() failed: ", hr);
return {};
}
@ -520,15 +551,16 @@ void D3D11DownloadTexture::SetDebugName(std::string_view name)
SetD3DDebugObjectName(m_texture.Get(), name);
}
std::unique_ptr<GPUDownloadTexture> D3D11Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format)
std::unique_ptr<GPUDownloadTexture> D3D11Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
Error* error /* = nullptr */)
{
return D3D11DownloadTexture::Create(width, height, format);
return D3D11DownloadTexture::Create(width, height, format, error);
}
std::unique_ptr<GPUDownloadTexture> D3D11Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride)
u32 memory_stride, Error* error /* = nullptr */)
{
ERROR_LOG("D3D11 cannot import memory for download textures");
Error::SetStringView(error, "D3D11 cannot import memory for download textures");
return {};
}

View File

@ -77,8 +77,8 @@ public:
ALWAYS_INLINE operator bool() const { return static_cast<bool>(m_texture); }
static std::unique_ptr<D3D11Texture> Create(ID3D11Device* device, u32 width, u32 height, u32 layers, u32 levels,
u32 samples, Type type, Format format, const void* initial_data = nullptr,
u32 initial_data_stride = 0);
u32 samples, Type type, Format format, Flags flags,
const void* initial_data, u32 initial_data_stride, Error* error);
D3D11_TEXTURE2D_DESC GetDesc() const;
void CommitClear(ID3D11DeviceContext1* context);
@ -86,11 +86,12 @@ public:
bool Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer = 0, u32 level = 0) override;
bool Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer = 0, u32 level = 0) override;
void Unmap() override;
void GenerateMipmaps() override;
void SetDebugName(std::string_view name) override;
private:
D3D11Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
D3D11Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags,
ComPtr<ID3D11Texture2D> texture, ComPtr<ID3D11ShaderResourceView> srv, ComPtr<ID3D11View> rtv_dsv,
ComPtr<ID3D11UnorderedAccessView> uav);
@ -111,7 +112,7 @@ public:
ALWAYS_INLINE ID3D11ShaderResourceView* GetSRV() const { return m_srv.Get(); }
ALWAYS_INLINE ID3D11ShaderResourceView* const* GetSRVArray() const { return m_srv.GetAddressOf(); }
bool CreateBuffer();
bool CreateBuffer(Error* error);
// Inherited via GPUTextureBuffer
void* Map(u32 required_elements) override;
@ -129,7 +130,7 @@ class D3D11DownloadTexture final : public GPUDownloadTexture
public:
~D3D11DownloadTexture() override;
static std::unique_ptr<D3D11DownloadTexture> Create(u32 width, u32 height, GPUTexture::Format format);
static std::unique_ptr<D3D11DownloadTexture> Create(u32 width, u32 height, GPUTexture::Format format, Error* error);
void CopyFromTexture(u32 dst_x, u32 dst_y, GPUTexture* src, u32 src_x, u32 src_y, u32 width, u32 height,
u32 src_layer, u32 src_level, bool use_transfer_pitch) override;

View File

@ -118,11 +118,6 @@ void D3D12::GraphicsPipelineBuilder::SetMultisamples(u32 multisamples)
m_desc.SampleDesc.Count = multisamples;
}
void D3D12::GraphicsPipelineBuilder::SetNoCullRasterizationState()
{
SetRasterizationState(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE, false);
}
void D3D12::GraphicsPipelineBuilder::SetDepthState(bool depth_test, bool depth_write, D3D12_COMPARISON_FUNC compare_op)
{
m_desc.DepthStencilState.DepthEnable = depth_test;
@ -141,11 +136,6 @@ void D3D12::GraphicsPipelineBuilder::SetStencilState(bool stencil_test, u8 read_
m_desc.DepthStencilState.BackFace = back;
}
void D3D12::GraphicsPipelineBuilder::SetNoDepthTestState()
{
SetDepthState(false, false, D3D12_COMPARISON_FUNC_ALWAYS);
}
void D3D12::GraphicsPipelineBuilder::SetNoStencilState()
{
D3D12_DEPTH_STENCILOP_DESC empty = {};
@ -170,18 +160,6 @@ void D3D12::GraphicsPipelineBuilder::SetBlendState(u32 rt, bool blend_enable, D3
m_desc.BlendState.IndependentBlendEnable = TRUE;
}
void D3D12::GraphicsPipelineBuilder::SetColorWriteMask(u32 rt, u8 write_mask /* = D3D12_COLOR_WRITE_ENABLE_ALL */)
{
m_desc.BlendState.RenderTarget[rt].RenderTargetWriteMask = write_mask;
}
void D3D12::GraphicsPipelineBuilder::SetNoBlendingState()
{
SetBlendState(0, false, D3D12_BLEND_ONE, D3D12_BLEND_ZERO, D3D12_BLEND_OP_ADD, D3D12_BLEND_ONE, D3D12_BLEND_ZERO,
D3D12_BLEND_OP_ADD, D3D12_COLOR_WRITE_ENABLE_ALL);
m_desc.BlendState.IndependentBlendEnable = FALSE;
}
void D3D12::GraphicsPipelineBuilder::ClearRenderTargets()
{
m_desc.NumRenderTargets = 0;

View File

@ -80,21 +80,14 @@ public:
void SetMultisamples(u32 multisamples);
void SetNoCullRasterizationState();
void SetDepthState(bool depth_test, bool depth_write, D3D12_COMPARISON_FUNC compare_op);
void SetStencilState(bool stencil_test, u8 read_mask, u8 write_mask, const D3D12_DEPTH_STENCILOP_DESC& front,
const D3D12_DEPTH_STENCILOP_DESC& back);
void SetNoDepthTestState();
void SetNoStencilState();
void SetBlendState(u32 rt, bool blend_enable, D3D12_BLEND src_factor, D3D12_BLEND dst_factor, D3D12_BLEND_OP op,
D3D12_BLEND alpha_src_factor, D3D12_BLEND alpha_dst_factor, D3D12_BLEND_OP alpha_op,
u8 write_mask = D3D12_COLOR_WRITE_ENABLE_ALL);
void SetColorWriteMask(u32 rt, u8 write_mask = D3D12_COLOR_WRITE_ENABLE_ALL);
void SetNoBlendingState();
void ClearRenderTargets();
@ -115,6 +108,8 @@ public:
ComputePipelineBuilder();
~ComputePipelineBuilder() = default;
ALWAYS_INLINE const D3D12_COMPUTE_PIPELINE_STATE_DESC* GetDesc() const { return &m_desc; }
void Clear();
Microsoft::WRL::ComPtr<ID3D12PipelineState> Create(ID3D12Device* device, Error* error, bool clear);

View File

@ -83,7 +83,6 @@ bool D3D12DescriptorHeapManager::Allocate(D3D12DescriptorHandle* handle)
return true;
}
Panic("Out of fixed descriptors");
return false;
}

View File

@ -46,7 +46,6 @@ enum : u32
FRAGMENT_UNIFORM_BUFFER_SIZE = 8 * 1024 * 1024,
TEXTURE_BUFFER_SIZE = 64 * 1024 * 1024,
// UNIFORM_PUSH_CONSTANTS_STAGES = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT,
UNIFORM_PUSH_CONSTANTS_SIZE = 128,
MAX_UNIFORM_BUFFER_SIZE = 1024,
@ -65,6 +64,55 @@ static DynamicHeapArray<u8> s_pipeline_cache_data;
static u32 s_debug_scope_depth = 0;
#endif
static constexpr const u32 s_mipmap_blit_vs[] = {
0x43425844, 0xe0f571cf, 0x51234ef3, 0x3a6beab4, 0x141cd2ef, 0x00000001, 0x000003ac, 0x00000005, 0x00000034,
0x00000144, 0x00000178, 0x000001d0, 0x00000310, 0x46454452, 0x00000108, 0x00000001, 0x00000068, 0x00000001,
0x0000003c, 0xfffe0500, 0x00008100, 0x000000e0, 0x31314452, 0x0000003c, 0x00000018, 0x00000020, 0x00000028,
0x00000024, 0x0000000c, 0x00000000, 0x0000005c, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000001, 0x424f4255, 0x6b636f6c, 0xababab00, 0x0000005c, 0x00000001, 0x00000080, 0x00000010,
0x00000000, 0x00000000, 0x000000a8, 0x00000000, 0x00000010, 0x00000002, 0x000000bc, 0x00000000, 0xffffffff,
0x00000000, 0xffffffff, 0x00000000, 0x72735f75, 0x65725f63, 0x66007463, 0x74616f6c, 0xabab0034, 0x00030001,
0x00040001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x000000b3, 0x7263694d,
0x666f736f, 0x52282074, 0x4c482029, 0x53204c53, 0x65646168, 0x6f432072, 0x6c69706d, 0x31207265, 0x00312e30,
0x4e475349, 0x0000002c, 0x00000001, 0x00000008, 0x00000020, 0x00000000, 0x00000006, 0x00000001, 0x00000000,
0x00000101, 0x565f5653, 0x65747265, 0x00444978, 0x4e47534f, 0x00000050, 0x00000002, 0x00000008, 0x00000038,
0x00000000, 0x00000000, 0x00000003, 0x00000000, 0x00000c03, 0x00000041, 0x00000000, 0x00000001, 0x00000003,
0x00000001, 0x0000000f, 0x43584554, 0x44524f4f, 0x5f565300, 0x69736f50, 0x6e6f6974, 0xababab00, 0x58454853,
0x00000138, 0x00010050, 0x0000004e, 0x0100086a, 0x04000059, 0x00208e46, 0x00000000, 0x00000001, 0x04000060,
0x00101012, 0x00000000, 0x00000006, 0x03000065, 0x00102032, 0x00000000, 0x04000067, 0x001020f2, 0x00000001,
0x00000001, 0x02000068, 0x00000001, 0x0b00008c, 0x00100012, 0x00000000, 0x00004001, 0x00000001, 0x00004001,
0x00000001, 0x0010100a, 0x00000000, 0x00004001, 0x00000000, 0x07000001, 0x00100042, 0x00000000, 0x0010100a,
0x00000000, 0x00004001, 0x00000002, 0x05000056, 0x00100032, 0x00000000, 0x00100086, 0x00000000, 0x0b000032,
0x00102032, 0x00000000, 0x00100046, 0x00000000, 0x00208ae6, 0x00000000, 0x00000000, 0x00208046, 0x00000000,
0x00000000, 0x0f000032, 0x00102032, 0x00000001, 0x00100046, 0x00000000, 0x00004002, 0x40000000, 0xc0000000,
0x00000000, 0x00000000, 0x00004002, 0xbf800000, 0x3f800000, 0x00000000, 0x00000000, 0x08000036, 0x001020c2,
0x00000001, 0x00004002, 0x00000000, 0x00000000, 0x00000000, 0x3f800000, 0x0100003e, 0x54415453, 0x00000094,
0x00000007, 0x00000001, 0x00000000, 0x00000003, 0x00000002, 0x00000000, 0x00000001, 0x00000001, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000001, 0x00000000, 0x00000001, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000};
static constexpr const u32 s_mipmap_blit_ps[] = {
0x43425844, 0x25500f77, 0x71f24271, 0x5f83f8b8, 0x3f405943, 0x00000001, 0x0000026c, 0x00000005, 0x00000034,
0x000000f0, 0x00000124, 0x00000158, 0x000001d0, 0x46454452, 0x000000b4, 0x00000000, 0x00000000, 0x00000002,
0x0000003c, 0xffff0500, 0x00008100, 0x0000008b, 0x31314452, 0x0000003c, 0x00000018, 0x00000020, 0x00000028,
0x00000024, 0x0000000c, 0x00000000, 0x0000007c, 0x00000003, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000001, 0x00000001, 0x00000085, 0x00000002, 0x00000005, 0x00000004, 0xffffffff, 0x00000000, 0x00000001,
0x0000000d, 0x706d6173, 0x73735f30, 0x6d617300, 0x4d003070, 0x6f726369, 0x74666f73, 0x29522820, 0x534c4820,
0x6853204c, 0x72656461, 0x6d6f4320, 0x656c6970, 0x30312072, 0xab00312e, 0x4e475349, 0x0000002c, 0x00000001,
0x00000008, 0x00000020, 0x00000000, 0x00000000, 0x00000003, 0x00000000, 0x00000303, 0x43584554, 0x44524f4f,
0xababab00, 0x4e47534f, 0x0000002c, 0x00000001, 0x00000008, 0x00000020, 0x00000000, 0x00000000, 0x00000003,
0x00000000, 0x0000000f, 0x545f5653, 0x65677261, 0xabab0074, 0x58454853, 0x00000070, 0x00000050, 0x0000001c,
0x0100086a, 0x0300005a, 0x00106000, 0x00000000, 0x04001858, 0x00107000, 0x00000000, 0x00005555, 0x03001062,
0x00101032, 0x00000000, 0x03000065, 0x001020f2, 0x00000000, 0x8b000045, 0x800000c2, 0x00155543, 0x001020f2,
0x00000000, 0x00101046, 0x00000000, 0x00107e46, 0x00000000, 0x00106000, 0x00000000, 0x0100003e, 0x54415453,
0x00000094, 0x00000002, 0x00000000, 0x00000000, 0x00000002, 0x00000000, 0x00000000, 0x00000000, 0x00000001,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000001, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000};
D3D12Device::D3D12Device()
{
m_render_api = RenderAPI::D3D12;
@ -523,7 +571,9 @@ bool D3D12Device::CreateDescriptorHeaps(Error* error)
m_device->CreateUnorderedAccessView(nullptr, nullptr, &null_uav_desc, m_null_uav_descriptor.cpu_handle);
// Same for samplers.
m_point_sampler = GetSampler(GPUSampler::GetNearestConfig());
m_point_sampler = GetSampler(GPUSampler::GetNearestConfig(), error);
if (!m_point_sampler) [[unlikely]]
return false;
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
m_current_samplers[i] = m_point_sampler;
return true;
@ -1298,6 +1348,7 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab
m_features.texture_buffers_emulated_with_ssbo = false;
m_features.feedback_loops = false;
m_features.geometry_shaders = !(disabled_features & FEATURE_MASK_GEOMETRY_SHADERS);
m_features.compute_shaders = !(disabled_features & FEATURE_MASK_COMPUTE_SHADERS);
m_features.partial_msaa_resolve = true;
m_features.memory_import = false;
m_features.explicit_present = true;
@ -1315,6 +1366,13 @@ void D3D12Device::SetFeatures(D3D_FEATURE_LEVEL feature_level, FeatureMask disab
SUCCEEDED(m_device->CheckFeatureSupport(D3D12_FEATURE_D3D12_OPTIONS, &options, sizeof(options))) &&
options.ROVsSupported;
}
m_features.dxt_textures =
(!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) &&
(SupportsTextureFormat(GPUTexture::Format::BC1) && SupportsTextureFormat(GPUTexture::Format::BC2) &&
SupportsTextureFormat(GPUTexture::Format::BC3)));
m_features.bptc_textures =
(!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) && SupportsTextureFormat(GPUTexture::Format::BC7));
}
void D3D12Device::CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level,
@ -1552,6 +1610,7 @@ void D3D12Device::PushUniformBuffer(const void* data, u32 data_size)
1, // SingleTextureBufferAndPushConstants
0, // MultiTextureAndUBO
2, // MultiTextureAndPushConstants
2, // ComputeSingleTextureAndPushConstants
};
DebugAssert(data_size < UNIFORM_PUSH_CONSTANTS_SIZE);
@ -1565,7 +1624,11 @@ void D3D12Device::PushUniformBuffer(const void* data, u32 data_size)
const u32 push_param =
push_parameters[static_cast<u8>(m_current_pipeline_layout)] + BoolToUInt8(IsUsingROVRootSignature());
GetCommandList()->SetGraphicsRoot32BitConstants(push_param, data_size / 4u, data, 0);
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
if (!IsUsingComputeRootSignature())
cmdlist->SetGraphicsRoot32BitConstants(push_param, data_size / 4u, data, 0);
else
cmdlist->SetComputeRoot32BitConstants(push_param, data_size / 4u, data, 0);
}
void* D3D12Device::MapUniformBuffer(u32 size)
@ -1687,6 +1750,18 @@ bool D3D12Device::CreateRootSignatures(Error* error)
}
}
{
auto& rs = m_root_signatures[0][static_cast<u8>(GPUPipeline::Layout::ComputeSingleTextureAndPushConstants)];
rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SRV, 0, MAX_TEXTURE_SAMPLERS, D3D12_SHADER_VISIBILITY_ALL);
rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_SAMPLER, 0, MAX_TEXTURE_SAMPLERS, D3D12_SHADER_VISIBILITY_ALL);
rsb.AddDescriptorTable(D3D12_DESCRIPTOR_RANGE_TYPE_UAV, 0, MAX_IMAGE_RENDER_TARGETS, D3D12_SHADER_VISIBILITY_ALL);
rsb.Add32BitConstants(0, UNIFORM_PUSH_CONSTANTS_SIZE / sizeof(u32), D3D12_SHADER_VISIBILITY_ALL);
if (!(rs = rsb.Create(error, true)))
return false;
D3D12::SetObjectName(rs.Get(), "Compute Single Texture Pipeline Layout");
}
return true;
}
@ -1810,6 +1885,7 @@ void D3D12Device::BeginRenderPass()
rt->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
rt->SetUseFenceValue(GetCurrentFenceValue());
rt->CommitClear(cmdlist);
rt->SetState(GPUTexture::State::Dirty);
}
}
if (m_current_depth_target)
@ -2081,7 +2157,7 @@ void D3D12Device::UnbindTexture(D3D12Texture* tex)
}
}
if (tex->IsRenderTarget() || tex->IsRWTexture())
if (tex->IsRenderTarget() || tex->HasFlag(GPUTexture::Flags::AllowBindAsImage))
{
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
@ -2115,6 +2191,140 @@ void D3D12Device::UnbindTextureBuffer(D3D12TextureBuffer* buf)
m_dirty_flags |= DIRTY_FLAG_TEXTURES;
}
void D3D12Device::RenderTextureMipmap(D3D12Texture* texture, u32 dst_level, u32 dst_width, u32 dst_height,
u32 src_level, u32 src_width, u32 src_height)
{
ID3D12RootSignature* rootsig =
m_root_signatures[0][static_cast<size_t>(GPUPipeline::Layout::SingleTextureAndPushConstants)].Get();
ComPtr<ID3D12PipelineState>& pipeline = m_mipmap_render_pipelines[static_cast<size_t>(texture->GetFormat())];
if (!pipeline)
{
D3D12::GraphicsPipelineBuilder gpb;
gpb.SetRootSignature(rootsig);
gpb.SetPrimitiveTopologyType(D3D12_PRIMITIVE_TOPOLOGY_TYPE_TRIANGLE);
gpb.SetRenderTarget(0, texture->GetDXGIFormat());
gpb.SetVertexShader(s_mipmap_blit_vs, std::size(s_mipmap_blit_vs));
gpb.SetPixelShader(s_mipmap_blit_ps, std::size(s_mipmap_blit_ps));
gpb.SetRasterizationState(D3D12_FILL_MODE_SOLID, D3D12_CULL_MODE_NONE, false);
gpb.SetDepthState(false, false, D3D12_COMPARISON_FUNC_ALWAYS);
gpb.SetBlendState(0, false, D3D12_BLEND_ZERO, D3D12_BLEND_ONE, D3D12_BLEND_OP_ADD, D3D12_BLEND_ZERO,
D3D12_BLEND_ONE, D3D12_BLEND_OP_ADD, D3D12_COLOR_WRITE_ENABLE_ALL);
const std::wstring name = StringUtil::UTF8StringToWideString(
TinyString::from_format("MipmapRender-{}", GPUTexture::GetFormatName(texture->GetFormat())));
Error error;
if (m_pipeline_library)
{
HRESULT hr =
m_pipeline_library->LoadGraphicsPipeline(name.c_str(), gpb.GetDesc(), IID_PPV_ARGS(pipeline.GetAddressOf()));
if (FAILED(hr))
{
// E_INVALIDARG = not found.
if (hr != E_INVALIDARG)
ERROR_LOG("LoadGraphicsPipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
// Need to create it normally.
pipeline = gpb.Create(m_device.Get(), &error, false);
// Store if it wasn't an OOM or something else.
if (pipeline && hr == E_INVALIDARG)
{
hr = m_pipeline_library->StorePipeline(name.c_str(), pipeline.Get());
if (FAILED(hr))
ERROR_LOG("StorePipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
}
}
}
else
{
pipeline = gpb.Create(m_device.Get(), &error, false);
}
if (!pipeline)
{
ERROR_LOG("Failed to compile mipmap render pipeline for {}: {}", GPUTexture::GetFormatName(texture->GetFormat()),
error.GetDescription());
return;
}
}
EndRenderPass();
// we need a temporary SRV and RTV for each mip level
// Safe to use the init buffer after exec, because everything will be done with the texture.
D3D12DescriptorHandle rtv_handle;
while (!GetRTVHeapManager().Allocate(&rtv_handle))
SubmitCommandList(false, "Allocate RTV for RenderTextureMipmap()");
D3D12DescriptorHandle srv_handle;
while (!GetDescriptorHeapManager().Allocate(&srv_handle))
SubmitCommandList(false, "Allocate SRV for RenderTextureMipmap()");
// Setup views. This will be a partial view for the SRV.
const D3D12_RENDER_TARGET_VIEW_DESC rtv_desc = {.Format = texture->GetDXGIFormat(),
.ViewDimension = D3D12_RTV_DIMENSION_TEXTURE2D,
.Texture2D = {.MipSlice = dst_level, .PlaneSlice = 0}};
m_device->CreateRenderTargetView(texture->GetResource(), &rtv_desc, rtv_handle);
const D3D12_SHADER_RESOURCE_VIEW_DESC srv_desc = {
.Format = texture->GetDXGIFormat(),
.ViewDimension = D3D12_SRV_DIMENSION_TEXTURE2D,
.Shader4ComponentMapping = D3D12_DEFAULT_SHADER_4_COMPONENT_MAPPING,
.Texture2D = {.MostDetailedMip = src_level, .MipLevels = 1, .PlaneSlice = 0, .ResourceMinLODClamp = 0.0f}};
m_device->CreateShaderResourceView(texture->GetResource(), &srv_desc, srv_handle);
// *now* we don't have to worry about running out of anything.
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
if (texture->GetResourceState() != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
{
texture->TransitionSubresourceToState(cmdlist, src_level, texture->GetResourceState(),
D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
}
if (texture->GetResourceState() != D3D12_RESOURCE_STATE_RENDER_TARGET)
{
texture->TransitionSubresourceToState(cmdlist, dst_level, texture->GetResourceState(),
D3D12_RESOURCE_STATE_RENDER_TARGET);
}
const D3D12_RENDER_PASS_RENDER_TARGET_DESC rt_desc = {
.cpuDescriptor = rtv_handle,
.BeginningAccess = {.Type = D3D12_RENDER_PASS_BEGINNING_ACCESS_TYPE_DISCARD, .Clear = {}},
.EndingAccess = {.Type = D3D12_RENDER_PASS_ENDING_ACCESS_TYPE_PRESERVE, .Resolve = {}}};
cmdlist->BeginRenderPass(1, &rt_desc, nullptr, D3D12_RENDER_PASS_FLAG_NONE);
const D3D12_VIEWPORT vp = {0.0f, 0.0f, static_cast<float>(dst_width), static_cast<float>(dst_height), 0.0f, 1.0f};
cmdlist->RSSetViewports(1, &vp);
const D3D12_RECT scissor = {0, 0, static_cast<LONG>(dst_width), static_cast<LONG>(dst_height)};
cmdlist->RSSetScissorRects(1, &scissor);
cmdlist->SetPipelineState(pipeline.Get());
cmdlist->SetGraphicsRootDescriptorTable(0, srv_handle);
cmdlist->SetGraphicsRootDescriptorTable(1, static_cast<D3D12Sampler*>(m_linear_sampler.get())->GetDescriptor());
cmdlist->DrawInstanced(3, 1, 0, 0);
cmdlist->EndRenderPass();
if (texture->GetResourceState() != D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE)
{
texture->TransitionSubresourceToState(cmdlist, src_level, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE,
texture->GetResourceState());
}
if (texture->GetResourceState() != D3D12_RESOURCE_STATE_RENDER_TARGET)
{
texture->TransitionSubresourceToState(cmdlist, dst_level, D3D12_RESOURCE_STATE_RENDER_TARGET,
texture->GetResourceState());
}
// Must destroy after current cmdlist.
DeferDescriptorDestruction(m_descriptor_heap_manager, &srv_handle);
DeferDescriptorDestruction(m_rtv_heap_manager, &rtv_handle);
// Restore for next normal draw.
SetViewport(GetCommandList());
SetScissor(GetCommandList());
m_dirty_flags |= LAYOUT_DEPENDENT_DIRTY_STATE;
}
void D3D12Device::SetViewport(const GSVector4i rc)
{
if (m_current_viewport.eq(rc))
@ -2174,16 +2384,89 @@ void D3D12Device::PreDrawCheck()
BeginRenderPass();
}
void D3D12Device::PreDispatchCheck()
{
if (InRenderPass())
EndRenderPass();
// Transition images.
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
// All textures should be in shader read only optimal already, but just in case..
const u32 num_textures = GetActiveTexturesForLayout(m_current_pipeline_layout);
for (u32 i = 0; i < num_textures; i++)
{
if (m_current_textures[i])
m_current_textures[i]->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_PIXEL_SHADER_RESOURCE);
}
if (m_num_current_render_targets > 0 && (m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages))
{
// Still need to clear the RTs.
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
D3D12Texture* const rt = m_current_render_targets[i];
rt->TransitionToState(cmdlist, D3D12_RESOURCE_STATE_UNORDERED_ACCESS);
rt->SetUseFenceValue(GetCurrentFenceValue());
rt->CommitClear(cmdlist);
rt->SetState(GPUTexture::State::Dirty);
}
}
// If this is a new command buffer, bind the pipeline and such.
if (m_dirty_flags & DIRTY_FLAG_INITIAL)
SetInitialPipelineState();
// TODO: Flushing cmdbuffer because of descriptor OOM will lose push constants.
DebugAssert(!(m_dirty_flags & DIRTY_FLAG_INITIAL));
const u32 dirty = std::exchange(m_dirty_flags, 0);
if (dirty != 0)
{
if (dirty & DIRTY_FLAG_PIPELINE_LAYOUT)
{
UpdateRootSignature();
if (!UpdateRootParameters(dirty))
{
SubmitCommandList(false, "out of descriptors");
PreDispatchCheck();
return;
}
}
else if (dirty & (DIRTY_FLAG_CONSTANT_BUFFER | DIRTY_FLAG_TEXTURES | DIRTY_FLAG_SAMPLERS | DIRTY_FLAG_RT_UAVS))
{
if (!UpdateRootParameters(dirty))
{
SubmitCommandList(false, "out of descriptors");
PreDispatchCheck();
return;
}
}
}
}
bool D3D12Device::IsUsingROVRootSignature() const
{
return ((m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages) != 0);
}
bool D3D12Device::IsUsingComputeRootSignature() const
{
return (m_current_pipeline_layout >= GPUPipeline::Layout::ComputeSingleTextureAndPushConstants);
}
void D3D12Device::UpdateRootSignature()
{
GetCommandList()->SetGraphicsRootSignature(
ID3D12GraphicsCommandList4* cmdlist = GetCommandList();
if (!IsUsingComputeRootSignature())
{
cmdlist->SetGraphicsRootSignature(
m_root_signatures[BoolToUInt8(IsUsingROVRootSignature())][static_cast<u8>(m_current_pipeline_layout)].Get());
}
else
{
cmdlist->SetComputeRootSignature(m_root_signatures[0][static_cast<u8>(m_current_pipeline_layout)].Get());
}
}
template<GPUPipeline::Layout layout>
bool D3D12Device::UpdateParametersForLayout(u32 dirty)
@ -2223,7 +2506,10 @@ bool D3D12Device::UpdateParametersForLayout(u32 dirty)
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
}
if constexpr (layout < GPUPipeline::Layout::ComputeSingleTextureAndPushConstants)
cmdlist->SetGraphicsRootDescriptorTable(0, gpu_handle);
else
cmdlist->SetComputeRootDescriptorTable(0, gpu_handle);
}
if (dirty & DIRTY_FLAG_SAMPLERS && num_textures > 0)
@ -2241,7 +2527,10 @@ bool D3D12Device::UpdateParametersForLayout(u32 dirty)
return false;
}
if constexpr (layout < GPUPipeline::Layout::ComputeSingleTextureAndPushConstants)
cmdlist->SetGraphicsRootDescriptorTable(1, gpu_handle);
else
cmdlist->SetComputeRootDescriptorTable(1, gpu_handle);
}
if (dirty & DIRTY_FLAG_TEXTURES && layout == GPUPipeline::Layout::SingleTextureBufferAndPushConstants)
@ -2283,7 +2572,10 @@ bool D3D12Device::UpdateParametersForLayout(u32 dirty)
1 :
((layout == GPUPipeline::Layout::SingleTextureAndUBO || layout == GPUPipeline::Layout::MultiTextureAndUBO) ? 3 :
2);
if constexpr (layout < GPUPipeline::Layout::ComputeSingleTextureAndPushConstants)
cmdlist->SetGraphicsRootDescriptorTable(rov_param, gpu_handle);
else
cmdlist->SetComputeRootDescriptorTable(rov_param, gpu_handle);
}
return true;
@ -2308,6 +2600,9 @@ bool D3D12Device::UpdateRootParameters(u32 dirty)
case GPUPipeline::Layout::MultiTextureAndPushConstants:
return UpdateParametersForLayout<GPUPipeline::Layout::MultiTextureAndPushConstants>(dirty);
case GPUPipeline::Layout::ComputeSingleTextureAndPushConstants:
return UpdateParametersForLayout<GPUPipeline::Layout::ComputeSingleTextureAndPushConstants>(dirty);
default:
UnreachableCode();
}
@ -2331,3 +2626,15 @@ void D3D12Device::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 ba
{
Panic("Barriers are not supported");
}
void D3D12Device::Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z)
{
PreDispatchCheck();
s_stats.num_draws++;
const u32 groups_x = threads_x / group_size_x;
const u32 groups_y = threads_y / group_size_y;
const u32 groups_z = threads_z / group_size_z;
GetCommandList()->Dispatch(groups_x, groups_y, groups_z);
}

View File

@ -71,15 +71,18 @@ public:
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements) override;
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
const void* data = nullptr, u32 data_stride = 0,
Error* error = nullptr) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config, Error* error = nullptr) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements,
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride) override;
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_stride,
Error* error = nullptr) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
void CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src,
@ -96,6 +99,7 @@ public:
std::string_view source, const char* entry_point,
DynamicHeapArray<u8>* out_binary, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override;
void PushDebugGroup(const char* name) override;
void PopDebugGroup() override;
@ -119,6 +123,8 @@ public:
void Draw(u32 vertex_count, u32 base_vertex) override;
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
@ -188,6 +194,9 @@ public:
void UnbindTexture(D3D12Texture* tex);
void UnbindTextureBuffer(D3D12TextureBuffer* buf);
void RenderTextureMipmap(D3D12Texture* texture, u32 dst_level, u32 dst_width, u32 dst_height, u32 src_level,
u32 src_width, u32 src_height);
protected:
bool CreateDeviceAndMainSwapChain(std::string_view adapter, FeatureMask disabled_features, const WindowInfo& wi,
GPUVSyncMode vsync_mode, bool allow_present_throttle,
@ -250,7 +259,7 @@ private:
void DestroyDescriptorHeaps();
bool CreateTimestampQuery();
void DestroyTimestampQuery();
D3D12DescriptorHandle GetSampler(const GPUSampler::Config& config);
D3D12DescriptorHandle GetSampler(const GPUSampler::Config& config, Error* error);
void DestroySamplers();
void DestroyDeferredObjects(u64 fence_value);
@ -258,10 +267,13 @@ private:
void MoveToNextCommandList();
bool CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format,
D3D12DescriptorHandle* dh);
bool CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
bool CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
bool CreateUAVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh);
D3D12DescriptorHandle* dh, Error* error);
bool CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh,
Error* error);
bool CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh,
Error* error);
bool CreateUAVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format, D3D12DescriptorHandle* dh,
Error* error);
bool IsRenderTargetBound(const GPUTexture* tex) const;
@ -275,8 +287,10 @@ private:
ID3D12RootSignature* GetCurrentRootSignature() const;
void SetInitialPipelineState();
void PreDrawCheck();
void PreDispatchCheck();
bool IsUsingROVRootSignature() const;
bool IsUsingComputeRootSignature() const;
void UpdateRootSignature();
template<GPUPipeline::Layout layout>
bool UpdateParametersForLayout(u32 dirty);
@ -349,6 +363,9 @@ private:
GSVector4i m_current_scissor = {};
D3D12SwapChain* m_current_swap_chain = nullptr;
std::array<ComPtr<ID3D12PipelineState>, static_cast<size_t>(GPUTexture::Format::MaxCount)> m_mipmap_render_pipelines =
{};
};
class D3D12SwapChain : public GPUSwapChain

View File

@ -107,6 +107,18 @@ std::string D3D12Pipeline::GetPipelineName(const GraphicsConfig& config)
return SHA1Digest::DigestToString(digest);
}
std::string D3D12Pipeline::GetPipelineName(const ComputeConfig& config)
{
SHA1Digest hash;
hash.Update(&config.layout, sizeof(config.layout));
if (const D3D12Shader* shader = static_cast<const D3D12Shader*>(config.compute_shader))
hash.Update(shader->GetBytecodeData(), shader->GetBytecodeSize());
u8 digest[SHA1Digest::DIGEST_SIZE];
hash.Final(digest);
return SHA1Digest::DigestToString(digest);
}
std::unique_ptr<GPUPipeline> D3D12Device::CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error)
{
static constexpr std::array<D3D12_PRIMITIVE_TOPOLOGY, static_cast<u32>(GPUPipeline::Primitive::MaxCount)> primitives =
@ -274,3 +286,46 @@ std::unique_ptr<GPUPipeline> D3D12Device::CreatePipeline(const GPUPipeline::Grap
pipeline, config.layout, primitives[static_cast<u8>(config.primitive)],
config.input_layout.vertex_attributes.empty() ? 0 : config.input_layout.vertex_stride, config.blend.constant));
}
std::unique_ptr<GPUPipeline> D3D12Device::CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error)
{
D3D12::ComputePipelineBuilder cpb;
cpb.SetRootSignature(m_root_signatures[0][static_cast<u8>(config.layout)].Get());
cpb.SetShader(static_cast<const D3D12Shader*>(config.compute_shader)->GetBytecodeData(),
static_cast<const D3D12Shader*>(config.compute_shader)->GetBytecodeSize());
ComPtr<ID3D12PipelineState> pipeline;
if (m_pipeline_library)
{
const std::wstring name = StringUtil::UTF8StringToWideString(D3D12Pipeline::GetPipelineName(config));
HRESULT hr =
m_pipeline_library->LoadComputePipeline(name.c_str(), cpb.GetDesc(), IID_PPV_ARGS(pipeline.GetAddressOf()));
if (FAILED(hr))
{
// E_INVALIDARG = not found.
if (hr != E_INVALIDARG)
ERROR_LOG("LoadComputePipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
// Need to create it normally.
pipeline = cpb.Create(m_device.Get(), error, false);
// Store if it wasn't an OOM or something else.
if (pipeline && hr == E_INVALIDARG)
{
hr = m_pipeline_library->StorePipeline(name.c_str(), pipeline.Get());
if (FAILED(hr))
ERROR_LOG("StorePipeline() failed with HRESULT {:08X}", static_cast<unsigned>(hr));
}
}
}
else
{
pipeline = cpb.Create(m_device.Get(), error, false);
}
if (!pipeline)
return {};
return std::unique_ptr<GPUPipeline>(
new D3D12Pipeline(pipeline, config.layout, D3D_PRIMITIVE_TOPOLOGY_UNDEFINED, 0, 0));
}

View File

@ -51,6 +51,7 @@ public:
void SetDebugName(std::string_view name) override;
static std::string GetPipelineName(const GraphicsConfig& config);
static std::string GetPipelineName(const ComputeConfig& config);
private:
D3D12Pipeline(Microsoft::WRL::ComPtr<ID3D12PipelineState> pipeline, Layout layout, D3D12_PRIMITIVE_TOPOLOGY topology,

View File

@ -18,12 +18,12 @@
LOG_CHANNEL(GPUDevice);
D3D12Texture::D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource,
Flags flags, DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource,
ComPtr<D3D12MA::Allocation> allocation, const D3D12DescriptorHandle& srv_descriptor,
const D3D12DescriptorHandle& write_descriptor, const D3D12DescriptorHandle& uav_descriptor,
WriteDescriptorType wdtype, D3D12_RESOURCE_STATES resource_state)
: GPUTexture(static_cast<u16>(width), static_cast<u16>(height), static_cast<u8>(layers), static_cast<u8>(levels),
static_cast<u8>(samples), type, format),
static_cast<u8>(samples), type, format, flags),
m_resource(std::move(resource)), m_allocation(std::move(allocation)), m_srv_descriptor(srv_descriptor),
m_write_descriptor(write_descriptor), m_uav_descriptor(uav_descriptor), m_dxgi_format(dxgi_format),
m_resource_state(resource_state), m_write_descriptor_type(wdtype)
@ -37,9 +37,10 @@ D3D12Texture::~D3D12Texture()
std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data /* = nullptr */, u32 data_stride /* = 0 */)
GPUTexture::Flags flags, const void* data /* = nullptr */,
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
{
if (!GPUTexture::ValidateConfig(width, height, layers, levels, samples, type, format))
if (!GPUTexture::ValidateConfig(width, height, layers, levels, samples, type, format, flags, error))
return {};
const D3DCommon::DXGIFormatMapping& fm = D3DCommon::GetFormatMapping(format);
@ -64,7 +65,6 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
switch (type)
{
case GPUTexture::Type::Texture:
case GPUTexture::Type::DynamicTexture:
{
desc.Flags = D3D12_RESOURCE_FLAG_NONE;
state = D3D12_RESOURCE_STATE_COPY_DEST;
@ -92,18 +92,20 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
}
break;
case GPUTexture::Type::RWTexture:
DefaultCaseIsUnreachable();
}
if ((flags & GPUTexture::Flags::AllowBindAsImage) != GPUTexture::Flags::None)
{
DebugAssert(levels == 1);
allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
desc.Flags = D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET | D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
optimized_clear_value.Format = fm.rtv_format;
state = D3D12_RESOURCE_STATE_UNORDERED_ACCESS;
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
}
break;
default:
return {};
if ((flags & GPUTexture::Flags::AllowGenerateMipmaps) != GPUTexture::Flags::None)
{
// requires RTs since we need to draw the mips
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_RENDER_TARGET;
}
ComPtr<ID3D12Resource> resource;
@ -115,10 +117,7 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
allocation.GetAddressOf(), IID_PPV_ARGS(resource.GetAddressOf()));
if (FAILED(hr)) [[unlikely]]
{
// OOM isn't fatal.
if (hr != E_OUTOFMEMORY)
ERROR_LOG("Create texture failed: 0x{:08X}", static_cast<unsigned>(hr));
Error::SetHResult(error, "CreateResource() failed: ", hr);
return {};
}
@ -126,16 +125,19 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
D3D12Texture::WriteDescriptorType write_descriptor_type = D3D12Texture::WriteDescriptorType::None;
if (fm.srv_format != DXGI_FORMAT_UNKNOWN)
{
if (!CreateSRVDescriptor(resource.Get(), layers, levels, samples, fm.srv_format, &srv_descriptor))
if (!CreateSRVDescriptor(resource.Get(), layers, levels, samples, fm.srv_format, &srv_descriptor, error))
return {};
}
switch (type)
{
case GPUTexture::Type::Texture:
break;
case GPUTexture::Type::RenderTarget:
{
write_descriptor_type = D3D12Texture::WriteDescriptorType::RTV;
if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor))
if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor, error))
{
m_descriptor_heap_manager.Free(&srv_descriptor);
return {};
@ -146,7 +148,7 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
case GPUTexture::Type::DepthStencil:
{
write_descriptor_type = D3D12Texture::WriteDescriptorType::DSV;
if (!CreateDSVDescriptor(resource.Get(), samples, fm.dsv_format, &write_descriptor))
if (!CreateDSVDescriptor(resource.Get(), samples, fm.dsv_format, &write_descriptor, error))
{
m_descriptor_heap_manager.Free(&srv_descriptor);
return {};
@ -154,30 +156,23 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
}
break;
case GPUTexture::Type::RWTexture:
{
write_descriptor_type = D3D12Texture::WriteDescriptorType::RTV;
if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor))
{
m_descriptor_heap_manager.Free(&srv_descriptor);
return {};
DefaultCaseIsUnreachable();
}
if (!CreateUAVDescriptor(resource.Get(), samples, fm.srv_format, &uav_descriptor))
if ((flags & GPUTexture::Flags::AllowBindAsImage) != GPUTexture::Flags::None)
{
if (!CreateUAVDescriptor(resource.Get(), samples, fm.srv_format, &uav_descriptor, error))
{
if (write_descriptor_type != D3D12Texture::WriteDescriptorType::None)
m_descriptor_heap_manager.Free(&write_descriptor);
m_descriptor_heap_manager.Free(&srv_descriptor);
return {};
}
}
break;
default:
break;
}
std::unique_ptr<D3D12Texture> tex(new D3D12Texture(
width, height, layers, levels, samples, type, format, fm.resource_format, std::move(resource),
width, height, layers, levels, samples, type, format, flags, fm.resource_format, std::move(resource),
std::move(allocation), srv_descriptor, write_descriptor, uav_descriptor, write_descriptor_type, state));
if (data)
@ -190,11 +185,11 @@ std::unique_ptr<GPUTexture> D3D12Device::CreateTexture(u32 width, u32 height, u3
}
bool D3D12Device::CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32 levels, u32 samples, DXGI_FORMAT format,
D3D12DescriptorHandle* dh)
D3D12DescriptorHandle* dh, Error* error)
{
if (!m_descriptor_heap_manager.Allocate(dh))
{
ERROR_LOG("Failed to allocate SRV descriptor");
Error::SetStringView(error, "Failed to allocate SRV descriptor");
return false;
}
@ -233,11 +228,11 @@ bool D3D12Device::CreateSRVDescriptor(ID3D12Resource* resource, u32 layers, u32
}
bool D3D12Device::CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format,
D3D12DescriptorHandle* dh)
D3D12DescriptorHandle* dh, Error* error)
{
if (!m_rtv_heap_manager.Allocate(dh))
{
ERROR_LOG("Failed to allocate SRV descriptor");
Error::SetStringView(error, "Failed to allocate SRV descriptor");
return false;
}
@ -248,11 +243,11 @@ bool D3D12Device::CreateRTVDescriptor(ID3D12Resource* resource, u32 samples, DXG
}
bool D3D12Device::CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format,
D3D12DescriptorHandle* dh)
D3D12DescriptorHandle* dh, Error* error)
{
if (!m_dsv_heap_manager.Allocate(dh))
{
ERROR_LOG("Failed to allocate SRV descriptor");
Error::SetStringView(error, "Failed to allocate SRV descriptor");
return false;
}
@ -263,11 +258,11 @@ bool D3D12Device::CreateDSVDescriptor(ID3D12Resource* resource, u32 samples, DXG
}
bool D3D12Device::CreateUAVDescriptor(ID3D12Resource* resource, u32 samples, DXGI_FORMAT format,
D3D12DescriptorHandle* dh)
D3D12DescriptorHandle* dh, Error* error)
{
if (!m_descriptor_heap_manager.Allocate(dh))
{
ERROR_LOG("Failed to allocate UAV descriptor");
Error::SetStringView(error, "Failed to allocate UAV descriptor");
return false;
}
@ -334,9 +329,9 @@ void D3D12Texture::Destroy(bool defer)
ID3D12GraphicsCommandList4* D3D12Texture::GetCommandBufferForUpdate()
{
D3D12Device& dev = D3D12Device::GetInstance();
if ((m_type != Type::Texture && m_type != Type::DynamicTexture) || m_use_fence_counter == dev.GetCurrentFenceValue())
if (m_type != Type::Texture || m_use_fence_counter == dev.GetCurrentFenceValue())
{
// Console.WriteLn("Texture update within frame, can't use do beforehand");
// DEV_LOG("Texture update within frame, can't use do beforehand");
if (dev.InRenderPass())
dev.EndRenderPass();
return dev.GetCommandList();
@ -345,23 +340,23 @@ ID3D12GraphicsCommandList4* D3D12Texture::GetCommandBufferForUpdate()
return dev.GetInitCommandList();
}
void D3D12Texture::CopyTextureDataForUpload(void* dst, const void* src, u32 width, u32 height, u32 pitch,
u32 upload_pitch) const
{
StringUtil::StrideMemCpy(dst, upload_pitch, src, pitch, GetPixelSize() * width, height);
}
ID3D12Resource* D3D12Texture::AllocateUploadStagingBuffer(const void* data, u32 pitch, u32 upload_pitch, u32 width,
u32 height) const
u32 height, u32 buffer_size) const
{
const u32 size = upload_pitch * height;
ComPtr<ID3D12Resource> resource;
ComPtr<D3D12MA::Allocation> allocation;
const D3D12MA::ALLOCATION_DESC allocation_desc = {D3D12MA::ALLOCATION_FLAG_NONE, D3D12_HEAP_TYPE_UPLOAD,
D3D12_HEAP_FLAG_NONE, nullptr, nullptr};
const D3D12_RESOURCE_DESC resource_desc = {
D3D12_RESOURCE_DIMENSION_BUFFER, 0, size, 1, 1, 1, DXGI_FORMAT_UNKNOWN, {1, 0}, D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
const D3D12_RESOURCE_DESC resource_desc = {D3D12_RESOURCE_DIMENSION_BUFFER,
0,
buffer_size,
1,
1,
1,
DXGI_FORMAT_UNKNOWN,
{1, 0},
D3D12_TEXTURE_LAYOUT_ROW_MAJOR,
D3D12_RESOURCE_FLAG_NONE};
HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource(
&allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_GENERIC_READ, nullptr, allocation.GetAddressOf(),
@ -380,9 +375,9 @@ ID3D12Resource* D3D12Texture::AllocateUploadStagingBuffer(const void* data, u32
return nullptr;
}
CopyTextureDataForUpload(map_ptr, data, width, height, pitch, upload_pitch);
CopyTextureDataForUpload(width, height, m_format, map_ptr, upload_pitch, data, pitch);
const D3D12_RANGE write_range = {0, size};
const D3D12_RANGE write_range = {0, buffer_size};
resource->Unmap(0, &write_range);
// Immediately queue it for freeing after the command buffer finishes, since it's only needed for the copy.
@ -400,8 +395,8 @@ bool D3D12Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
D3D12Device& dev = D3D12Device::GetInstance();
D3D12StreamBuffer& sbuffer = dev.GetTextureUploadBuffer();
const u32 upload_pitch = Common::AlignUpPow2<u32>(pitch, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 required_size = height * upload_pitch;
const u32 upload_pitch = Common::AlignUpPow2<u32>(CalcUploadPitch(width), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 required_size = CalcUploadSize(height, upload_pitch);
D3D12_TEXTURE_COPY_LOCATION srcloc;
srcloc.Type = D3D12_TEXTURE_COPY_TYPE_PLACED_FOOTPRINT;
@ -415,7 +410,7 @@ bool D3D12Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
// Otherwise allocation will either fail, or require lots of cmdbuffer submissions.
if (required_size > (sbuffer.GetSize() / 2))
{
srcloc.pResource = AllocateUploadStagingBuffer(data, pitch, upload_pitch, width, height);
srcloc.pResource = AllocateUploadStagingBuffer(data, pitch, upload_pitch, width, height, required_size);
if (!srcloc.pResource)
return false;
@ -436,7 +431,7 @@ bool D3D12Texture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
srcloc.pResource = sbuffer.GetBuffer();
srcloc.PlacedFootprint.Offset = sbuffer.GetCurrentOffset();
CopyTextureDataForUpload(sbuffer.GetCurrentHostPointer(), data, width, height, pitch, upload_pitch);
CopyTextureDataForUpload(width, height, m_format, sbuffer.GetCurrentHostPointer(), upload_pitch, data, pitch);
sbuffer.CommitMemory(required_size);
}
@ -487,8 +482,8 @@ bool D3D12Texture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32
CommitClear(GetCommandBufferForUpdate());
// see note in Update() for the reason why.
const u32 aligned_pitch = Common::AlignUpPow2(width * GetPixelSize(), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 req_size = height * aligned_pitch;
const u32 aligned_pitch = Common::AlignUpPow2(CalcUploadPitch(m_width), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 req_size = CalcUploadSize(m_height, aligned_pitch);
D3D12StreamBuffer& buffer = dev.GetTextureUploadBuffer();
if (req_size >= (buffer.GetSize() / 2))
return false;
@ -517,8 +512,8 @@ void D3D12Texture::Unmap()
{
D3D12Device& dev = D3D12Device::GetInstance();
D3D12StreamBuffer& sb = dev.GetTextureUploadBuffer();
const u32 aligned_pitch = Common::AlignUpPow2(m_map_width * GetPixelSize(), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 req_size = m_map_height * aligned_pitch;
const u32 aligned_pitch = Common::AlignUpPow2(CalcUploadPitch(m_width), D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
const u32 req_size = CalcUploadSize(m_map_height, aligned_pitch);
const u32 offset = sb.GetCurrentOffset();
sb.CommitMemory(req_size);
@ -562,6 +557,28 @@ void D3D12Texture::Unmap()
m_map_level = 0;
}
void D3D12Texture::GenerateMipmaps()
{
Panic("Not implemented");
for (u32 layer = 0; layer < m_layers; layer++)
{
for (u32 dst_level = 1; dst_level < m_levels; dst_level++)
{
const u32 src_level = dst_level - 1;
const u32 src_width = std::max<u32>(m_width >> src_level, 1u);
const u32 src_height = std::max<u32>(m_height >> src_level, 1u);
const u32 dst_width = std::max<u32>(m_width >> dst_level, 1u);
const u32 dst_height = std::max<u32>(m_height >> dst_level, 1u);
D3D12Device::GetInstance().RenderTextureMipmap(this, dst_level, dst_width, dst_height, src_level, src_width,
src_height);
}
}
SetUseFenceValue(D3D12Device::GetInstance().GetCurrentFenceValue());
}
void D3D12Texture::CommitClear()
{
if (m_state != GPUTexture::State::Cleared)
@ -685,7 +702,7 @@ void D3D12Sampler::SetDebugName(std::string_view name)
{
}
D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config)
D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config, Error* error)
{
const auto it = m_sampler_map.find(config.key);
if (it != m_sampler_map.end())
@ -730,8 +747,10 @@ D3D12DescriptorHandle D3D12Device::GetSampler(const GPUSampler::Config& config)
}
D3D12DescriptorHandle handle;
if (m_sampler_heap_manager.Allocate(&handle))
if (m_sampler_heap_manager.Allocate(&handle)) [[likely]]
m_device->CreateSampler(&desc, handle);
else
Error::SetStringView(error, "Failed to allocate sampler handle.");
m_sampler_map.emplace(config.key, handle);
return handle;
@ -747,9 +766,9 @@ void D3D12Device::DestroySamplers()
m_sampler_map.clear();
}
std::unique_ptr<GPUSampler> D3D12Device::CreateSampler(const GPUSampler::Config& config)
std::unique_ptr<GPUSampler> D3D12Device::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{
const D3D12DescriptorHandle handle = GetSampler(config);
const D3D12DescriptorHandle handle = GetSampler(config, error);
if (!handle)
return {};
@ -765,21 +784,20 @@ D3D12TextureBuffer::~D3D12TextureBuffer()
Destroy(true);
}
bool D3D12TextureBuffer::Create(D3D12Device& dev)
bool D3D12TextureBuffer::Create(D3D12Device& dev, Error* error)
{
static constexpr std::array<DXGI_FORMAT, static_cast<u8>(GPUTextureBuffer::Format::MaxCount)> format_mapping = {{
DXGI_FORMAT_R16_UINT, // R16UI
}};
Error error;
if (!m_buffer.Create(GetSizeInBytes(), &error)) [[unlikely]]
{
ERROR_LOG("Failed to create stream buffer: {}", error.GetDescription());
if (!m_buffer.Create(GetSizeInBytes(), error)) [[unlikely]]
return false;
}
if (!dev.GetDescriptorHeapManager().Allocate(&m_descriptor)) [[unlikely]]
{
Error::SetStringView(error, "Failed to allocate descriptor.");
return {};
}
D3D12_SHADER_RESOURCE_VIEW_DESC desc = {format_mapping[static_cast<u8>(m_format)],
D3D12_SRV_DIMENSION_BUFFER,
@ -831,11 +849,11 @@ void D3D12TextureBuffer::SetDebugName(std::string_view name)
}
std::unique_ptr<GPUTextureBuffer> D3D12Device::CreateTextureBuffer(GPUTextureBuffer::Format format,
u32 size_in_elements)
u32 size_in_elements, Error* error /* = nullptr */)
{
std::unique_ptr<D3D12TextureBuffer> tb = std::make_unique<D3D12TextureBuffer>(format, size_in_elements);
if (!tb->Create(*this))
if (!tb->Create(*this, error))
tb.reset();
return tb;
@ -858,7 +876,8 @@ D3D12DownloadTexture::~D3D12DownloadTexture()
D3D12Device::GetInstance().DeferResourceDestruction(m_allocation.Get(), m_buffer.Get());
}
std::unique_ptr<D3D12DownloadTexture> D3D12DownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format)
std::unique_ptr<D3D12DownloadTexture> D3D12DownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format,
Error* error)
{
const u32 buffer_size = GetBufferSize(width, height, format, D3D12_TEXTURE_DATA_PITCH_ALIGNMENT);
@ -879,12 +898,12 @@ std::unique_ptr<D3D12DownloadTexture> D3D12DownloadTexture::Create(u32 width, u3
ComPtr<D3D12MA::Allocation> allocation;
ComPtr<ID3D12Resource> buffer;
HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource(
const HRESULT hr = D3D12Device::GetInstance().GetAllocator()->CreateResource(
&allocation_desc, &resource_desc, D3D12_RESOURCE_STATE_COPY_DEST, nullptr, allocation.GetAddressOf(),
IID_PPV_ARGS(buffer.GetAddressOf()));
if (FAILED(hr))
{
ERROR_LOG("CreateResource() failed with HRESULT {:08X}", hr);
Error::SetHResult(error, "CreateResource() failed: ", hr);
return {};
}
@ -1015,15 +1034,16 @@ void D3D12DownloadTexture::SetDebugName(std::string_view name)
D3D12::SetObjectName(m_buffer.Get(), name);
}
std::unique_ptr<GPUDownloadTexture> D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format)
std::unique_ptr<GPUDownloadTexture> D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
Error* error /* = nullptr */)
{
return D3D12DownloadTexture::Create(width, height, format);
return D3D12DownloadTexture::Create(width, height, format, error);
}
std::unique_ptr<GPUDownloadTexture> D3D12Device::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride)
u32 memory_stride, Error* error /* = nullptr */)
{
ERROR_LOG("D3D12 cannot import memory for download textures");
Error::SetStringView(error, "D3D12 cannot import memory for download textures");
return {};
}

View File

@ -40,6 +40,7 @@ public:
bool Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer = 0, u32 level = 0) override;
bool Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer = 0, u32 level = 0) override;
void Unmap() override;
void GenerateMipmaps() override;
void MakeReadyForSampling() override;
void SetDebugName(std::string_view name) override;
@ -71,7 +72,7 @@ private:
DSV
};
D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
D3D12Texture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags,
DXGI_FORMAT dxgi_format, ComPtr<ID3D12Resource> resource, ComPtr<D3D12MA::Allocation> allocation,
const D3D12DescriptorHandle& srv_descriptor, const D3D12DescriptorHandle& write_descriptor,
const D3D12DescriptorHandle& uav_descriptor, WriteDescriptorType wdtype,
@ -79,8 +80,7 @@ private:
ID3D12GraphicsCommandList4* GetCommandBufferForUpdate();
ID3D12Resource* AllocateUploadStagingBuffer(const void* data, u32 pitch, u32 upload_pitch, u32 width,
u32 height) const;
void CopyTextureDataForUpload(void* dst, const void* src, u32 width, u32 height, u32 pitch, u32 upload_pitch) const;
u32 height, u32 buffer_size) const;
void ActuallyCommitClear(ID3D12GraphicsCommandList* cmdlist);
ComPtr<ID3D12Resource> m_resource;
@ -133,7 +133,7 @@ public:
ALWAYS_INLINE const D3D12DescriptorHandle& GetDescriptor() const { return m_descriptor; }
bool Create(D3D12Device& dev);
bool Create(D3D12Device& dev, Error* error);
void Destroy(bool defer);
// Inherited via GPUTextureBuffer
@ -155,7 +155,7 @@ public:
~D3D12DownloadTexture() override;
static std::unique_ptr<D3D12DownloadTexture> Create(u32 width, u32 height, GPUTexture::Format format);
static std::unique_ptr<D3D12DownloadTexture> Create(u32 width, u32 height, GPUTexture::Format format, Error* error);
void CopyFromTexture(u32 dst_x, u32 dst_y, GPUTexture* src, u32 src_x, u32 src_y, u32 width, u32 height,
u32 src_layer, u32 src_level, bool use_transfer_pitch) override;

View File

@ -650,6 +650,10 @@ static constexpr std::array<D3DCommon::DXGIFormatMapping, static_cast<int>(GPUTe
{DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_R16G16B16A16_FLOAT, DXGI_FORMAT_UNKNOWN }, // RGBA16F
{DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_R32G32B32A32_FLOAT, DXGI_FORMAT_UNKNOWN }, // RGBA32F
{DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_R10G10B10A2_UNORM, DXGI_FORMAT_UNKNOWN }, // RGB10A2
{DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_BC1_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // BC1
{DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_BC2_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // BC2
{DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_BC3_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // BC3
{DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_BC7_UNORM, DXGI_FORMAT_UNKNOWN, DXGI_FORMAT_UNKNOWN }, // BC7
// clang-format on
}};

View File

@ -4,6 +4,7 @@
#include "gpu_device.h"
#include "compress_helpers.h"
#include "gpu_framebuffer_manager.h"
#include "image.h"
#include "shadergen.h"
#include "common/assert.h"
@ -590,10 +591,10 @@ bool GPUDevice::GetPipelineCacheData(DynamicHeapArray<u8>* data, Error* error)
bool GPUDevice::CreateResources(Error* error)
{
if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig())) ||
!(m_linear_sampler = CreateSampler(GPUSampler::GetLinearConfig())))
if (!(m_nearest_sampler = CreateSampler(GPUSampler::GetNearestConfig(), error)) ||
!(m_linear_sampler = CreateSampler(GPUSampler::GetLinearConfig(), error)))
{
Error::SetStringView(error, "Failed to create samplers");
Error::AddPrefix(error, "Failed to create samplers: ");
return false;
}
@ -922,10 +923,15 @@ bool GPUDevice::UpdateImGuiFontTexture()
return true;
}
Error error;
std::unique_ptr<GPUTexture> new_font =
FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8, pixels, pitch);
if (!new_font)
FetchTexture(width, height, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8, GPUTexture::Flags::None,
pixels, pitch, &error);
if (!new_font) [[unlikely]]
{
ERROR_LOG("Failed to create new ImGui font texture: {}", error.GetDescription());
return false;
}
RecycleTexture(std::move(m_imgui_font_texture));
m_imgui_font_texture = std::move(new_font);
@ -950,12 +956,13 @@ GSVector4i GPUDevice::FlipToLowerLeft(GSVector4i rc, s32 target_height)
bool GPUDevice::IsTexturePoolType(GPUTexture::Type type)
{
return (type == GPUTexture::Type::Texture || type == GPUTexture::Type::DynamicTexture);
return (type == GPUTexture::Type::Texture);
}
std::unique_ptr<GPUTexture> GPUDevice::FetchTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data /*= nullptr*/, u32 data_stride /*= 0*/)
GPUTexture::Flags flags, const void* data /* = nullptr */,
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
{
std::unique_ptr<GPUTexture> ret;
@ -966,7 +973,7 @@ std::unique_ptr<GPUTexture> GPUDevice::FetchTexture(u32 width, u32 height, u32 l
static_cast<u8>(samples),
type,
format,
0u};
flags};
const bool is_texture = IsTexturePoolType(type);
TexturePool& pool = is_texture ? m_texture_pool : m_target_pool;
@ -1018,20 +1025,67 @@ std::unique_ptr<GPUTexture> GPUDevice::FetchTexture(u32 width, u32 height, u32 l
}
}
ret = CreateTexture(width, height, layers, levels, samples, type, format, data, data_stride);
Error create_error;
ret = CreateTexture(width, height, layers, levels, samples, type, format, flags, data, data_stride, &create_error);
if (!ret) [[unlikely]]
{
Error::SetStringFmt(
error ? error : &create_error, "Failed to create {}x{} {} {}: {}", width, height,
GPUTexture::GetFormatName(format),
((type == GPUTexture::Type::RenderTarget) ? "RT" : (type == GPUTexture::Type::DepthStencil ? "DS" : "Texture")),
create_error.TakeDescription());
if (!error)
ERROR_LOG(create_error.GetDescription());
}
return ret;
}
std::unique_ptr<GPUTexture, GPUDevice::PooledTextureDeleter>
GPUDevice::FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type,
GPUTexture::Format format, const void* data /*= nullptr*/, u32 data_stride /*= 0*/,
bool dynamic /*= false*/)
GPUTexture::Format format, GPUTexture::Flags flags, const void* data /* = nullptr */,
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
{
std::unique_ptr<GPUTexture> ret =
FetchTexture(width, height, layers, levels, samples, type, format, data, data_stride);
FetchTexture(width, height, layers, levels, samples, type, format, flags, data, data_stride, error);
return std::unique_ptr<GPUTexture, PooledTextureDeleter>(ret.release());
}
std::unique_ptr<GPUTexture> GPUDevice::FetchAndUploadTextureImage(const Image& image,
GPUTexture::Flags flags /*= GPUTexture::Flags::None*/,
Error* error /*= nullptr*/)
{
const Image* image_to_upload = &image;
GPUTexture::Format gpu_format = GPUTexture::GetTextureFormatForImageFormat(image.GetFormat());
bool gpu_format_supported;
// avoid device query for compressed formats that we've already pretested
if (gpu_format >= GPUTexture::Format::BC1 && gpu_format <= GPUTexture::Format::BC3)
gpu_format_supported = m_features.dxt_textures;
else if (gpu_format == GPUTexture::Format::BC7)
gpu_format_supported = m_features.bptc_textures;
else if (gpu_format == GPUTexture::Format::RGBA8) // always supported
gpu_format_supported = true;
else if (gpu_format != GPUTexture::Format::Unknown)
gpu_format_supported = SupportsTextureFormat(gpu_format);
else
gpu_format_supported = false;
std::optional<Image> converted_image;
if (!gpu_format_supported)
{
converted_image = image.ConvertToRGBA8(error);
if (!converted_image.has_value())
return nullptr;
image_to_upload = &converted_image.value();
gpu_format = GPUTexture::GetTextureFormatForImageFormat(converted_image->GetFormat());
}
return FetchTexture(image_to_upload->GetWidth(), image_to_upload->GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
gpu_format, flags, image_to_upload->GetPixels(), image_to_upload->GetPitch(), error);
}
void GPUDevice::RecycleTexture(std::unique_ptr<GPUTexture> texture)
{
if (!texture)
@ -1044,7 +1098,7 @@ void GPUDevice::RecycleTexture(std::unique_ptr<GPUTexture> texture)
static_cast<u8>(texture->GetSamples()),
texture->GetType(),
texture->GetFormat(),
0u};
texture->GetFlags()};
const bool is_texture = IsTexturePoolType(texture->GetType());
TexturePool& pool = is_texture ? m_texture_pool : m_target_pool;
@ -1118,11 +1172,11 @@ void GPUDevice::TrimTexturePool()
}
bool GPUDevice::ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve /* = true */)
GPUTexture::Format format, GPUTexture::Flags flags, bool preserve /* = true */)
{
GPUTexture* old_tex = tex->get();
DebugAssert(!old_tex || (old_tex->GetLayers() == 1 && old_tex->GetLevels() == 1 && old_tex->GetSamples() == 1));
std::unique_ptr<GPUTexture> new_tex = FetchTexture(new_width, new_height, 1, 1, 1, type, format);
std::unique_ptr<GPUTexture> new_tex = FetchTexture(new_width, new_height, 1, 1, 1, type, format, flags);
if (!new_tex) [[unlikely]]
{
ERROR_LOG("Failed to create new {}x{} texture", new_width, new_height);
@ -1579,11 +1633,13 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
// Need to know if there's UBOs for mapping.
const spvc_reflected_resource *ubos, *textures;
size_t ubos_count, textures_count;
size_t ubos_count, textures_count, images_count;
if ((sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_UNIFORM_BUFFER, &ubos,
&ubos_count)) != SPVC_SUCCESS ||
(sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_SAMPLED_IMAGE,
&textures, &textures_count)) != SPVC_SUCCESS)
&textures, &textures_count)) != SPVC_SUCCESS ||
(sres = dyn_libs::spvc_resources_get_resource_list_for_type(resources, SPVC_RESOURCE_TYPE_STORAGE_IMAGE,
&textures, &images_count)) != SPVC_SUCCESS)
{
Error::SetStringFmt(error, "spvc_resources_get_resource_list_for_type() failed: {}", static_cast<int>(sres));
return {};
@ -1592,6 +1648,7 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
[[maybe_unused]] const SpvExecutionModel execmodel = dyn_libs::spvc_compiler_get_execution_model(scompiler);
[[maybe_unused]] static constexpr u32 UBO_DESCRIPTOR_SET = 0;
[[maybe_unused]] static constexpr u32 TEXTURE_DESCRIPTOR_SET = 1;
[[maybe_unused]] static constexpr u32 IMAGE_DESCRIPTOR_SET = 2;
switch (target_language)
{
@ -1659,6 +1716,25 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
}
}
}
if (stage == GPUShaderStage::Compute)
{
for (u32 i = 0; i < images_count; i++)
{
const spvc_hlsl_resource_binding rb = {.stage = execmodel,
.desc_set = IMAGE_DESCRIPTOR_SET,
.binding = i,
.cbv = {},
.uav = {.register_space = 0, .register_binding = i},
.srv = {},
.sampler = {}};
if ((sres = dyn_libs::spvc_compiler_hlsl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
{
Error::SetStringFmt(error, "spvc_compiler_hlsl_add_resource_binding() failed: {}", static_cast<int>(sres));
return {};
}
}
}
}
break;
#endif
@ -1727,12 +1803,25 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
return {};
}
if (stage == GPUShaderStage::Fragment)
const spvc_msl_resource_binding pc_rb = {.stage = execmodel,
.desc_set = SPVC_MSL_PUSH_CONSTANT_DESC_SET,
.binding = SPVC_MSL_PUSH_CONSTANT_BINDING,
.msl_buffer = 0,
.msl_texture = 0,
.msl_sampler = 0};
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &pc_rb)) != SPVC_SUCCESS)
{
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() for push constant failed: {}",
static_cast<int>(sres));
return {};
}
if (stage == GPUShaderStage::Fragment || stage == GPUShaderStage::Compute)
{
for (u32 i = 0; i < MAX_TEXTURE_SAMPLERS; i++)
{
const spvc_msl_resource_binding rb = {.stage = SpvExecutionModelFragment,
.desc_set = 1,
const spvc_msl_resource_binding rb = {.stage = execmodel,
.desc_set = TEXTURE_DESCRIPTOR_SET,
.binding = i,
.msl_buffer = i,
.msl_texture = i,
@ -1744,11 +1833,12 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
return {};
}
}
}
if (!m_features.framebuffer_fetch)
if (stage == GPUShaderStage::Fragment && !m_features.framebuffer_fetch)
{
const spvc_msl_resource_binding rb = {
.stage = SpvExecutionModelFragment, .desc_set = 2, .binding = 0, .msl_texture = MAX_TEXTURE_SAMPLERS};
.stage = execmodel, .desc_set = 2, .binding = 0, .msl_texture = MAX_TEXTURE_SAMPLERS};
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
{
@ -1757,6 +1847,20 @@ bool GPUDevice::TranslateVulkanSpvToLanguage(const std::span<const u8> spirv, GP
return {};
}
}
if (stage == GPUShaderStage::Compute)
{
for (u32 i = 0; i < MAX_IMAGE_RENDER_TARGETS; i++)
{
const spvc_msl_resource_binding rb = {
.stage = execmodel, .desc_set = 2, .binding = i, .msl_buffer = i, .msl_texture = i, .msl_sampler = i};
if ((sres = dyn_libs::spvc_compiler_msl_add_resource_binding(scompiler, &rb)) != SPVC_SUCCESS)
{
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() failed: {}", static_cast<int>(sres));
return {};
}
}
}
}
break;

View File

@ -24,6 +24,7 @@
#include <vector>
class Error;
class Image;
enum class RenderAPI : u8
{
@ -160,6 +161,9 @@ public:
// Multiple textures, 128 byte UBO via push constants.
MultiTextureAndPushConstants,
// 128 byte UBO via push constants, 1 texture, compute shader.
ComputeSingleTextureAndPushConstants,
MaxCount
};
@ -416,6 +420,12 @@ public:
u32 GetRenderTargetCount() const;
};
struct ComputeConfig
{
Layout layout;
GPUShader* compute_shader;
};
GPUPipeline();
virtual ~GPUPipeline();
@ -501,9 +511,11 @@ public:
FEATURE_MASK_FRAMEBUFFER_FETCH = (1 << 2),
FEATURE_MASK_TEXTURE_BUFFERS = (1 << 3),
FEATURE_MASK_GEOMETRY_SHADERS = (1 << 4),
FEATURE_MASK_TEXTURE_COPY_TO_SELF = (1 << 5),
FEATURE_MASK_MEMORY_IMPORT = (1 << 6),
FEATURE_MASK_RASTER_ORDER_VIEWS = (1 << 7),
FEATURE_MASK_COMPUTE_SHADERS = (1 << 5),
FEATURE_MASK_TEXTURE_COPY_TO_SELF = (1 << 6),
FEATURE_MASK_MEMORY_IMPORT = (1 << 7),
FEATURE_MASK_RASTER_ORDER_VIEWS = (1 << 8),
FEATURE_MASK_COMPRESSED_TEXTURES = (1 << 9),
};
enum class DrawBarrier : u32
@ -532,6 +544,7 @@ public:
bool texture_buffers_emulated_with_ssbo : 1;
bool feedback_loops : 1;
bool geometry_shaders : 1;
bool compute_shaders : 1;
bool partial_msaa_resolve : 1;
bool memory_import : 1;
bool explicit_present : 1;
@ -541,6 +554,8 @@ public:
bool pipeline_cache : 1;
bool prefer_unused_textures : 1;
bool raster_order_views : 1;
bool dxt_textures : 1;
bool bptc_textures : 1;
};
struct Statistics
@ -625,11 +640,20 @@ public:
0, // SingleTextureBufferAndPushConstants
MAX_TEXTURE_SAMPLERS, // MultiTextureAndUBO
MAX_TEXTURE_SAMPLERS, // MultiTextureAndPushConstants
1, // ComputeSingleTextureAndPushConstants
};
return counts[static_cast<u8>(layout)];
}
/// Returns the number of thread groups to dispatch for a given total count and local size.
static constexpr std::tuple<u32, u32, u32> GetDispatchCount(u32 count_x, u32 count_y, u32 count_z, u32 local_size_x,
u32 local_size_y, u32 local_size_z)
{
return std::make_tuple((count_x + (local_size_x - 1)) / local_size_x, (count_y + (local_size_y - 1)) / local_size_y,
(count_z + (local_size_z - 1)) / local_size_z);
}
ALWAYS_INLINE const Features& GetFeatures() const { return m_features; }
ALWAYS_INLINE RenderAPI GetRenderAPI() const { return m_render_api; }
ALWAYS_INLINE u32 GetRenderAPIVersion() const { return m_render_api_version; }
@ -638,10 +662,6 @@ public:
ALWAYS_INLINE GPUSwapChain* GetMainSwapChain() const { return m_main_swap_chain.get(); }
ALWAYS_INLINE bool HasMainSwapChain() const { return static_cast<bool>(m_main_swap_chain); }
// ALWAYS_INLINE u32 GetMainSwapChainWidth() const { return m_main_swap_chain->GetWidth(); }
// ALWAYS_INLINE u32 GetMainSwapChainHeight() const { return m_main_swap_chain->GetHeight(); }
// ALWAYS_INLINE float GetWindowScale() const { return m_window_info.surface_scale; }
// ALWAYS_INLINE GPUTexture::Format GetWindowFormat() const { return m_window_info.surface_format; }
ALWAYS_INLINE GPUSampler* GetLinearSampler() const { return m_linear_sampler.get(); }
ALWAYS_INLINE GPUSampler* GetNearestSampler() const { return m_nearest_sampler.get(); }
@ -677,27 +697,31 @@ public:
virtual std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) = 0;
virtual std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config) = 0;
virtual std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format,
u32 size_in_elements) = 0;
GPUTexture::Flags flags, const void* data = nullptr,
u32 data_stride = 0, Error* error = nullptr) = 0;
virtual std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config, Error* error = nullptr) = 0;
virtual std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements,
Error* error = nullptr) = 0;
// Texture pooling.
std::unique_ptr<GPUTexture> FetchTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format, const void* data = nullptr,
u32 data_stride = 0);
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
const void* data = nullptr, u32 data_stride = 0, Error* error = nullptr);
std::unique_ptr<GPUTexture, PooledTextureDeleter>
FetchAutoRecycleTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, GPUTexture::Type type,
GPUTexture::Format format, const void* data = nullptr, u32 data_stride = 0,
bool dynamic = false);
GPUTexture::Format format, GPUTexture::Flags flags, const void* data = nullptr,
u32 data_stride = 0, Error* error = nullptr);
std::unique_ptr<GPUTexture> FetchAndUploadTextureImage(const Image& image,
GPUTexture::Flags flags = GPUTexture::Flags::None,
Error* error = nullptr);
void RecycleTexture(std::unique_ptr<GPUTexture> texture);
void PurgeTexturePool();
virtual std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height,
GPUTexture::Format format) = 0;
virtual std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride) = 0;
Error* error = nullptr) = 0;
virtual std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_stride,
Error* error = nullptr) = 0;
virtual void CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src,
u32 src_x, u32 src_y, u32 src_layer, u32 src_level, u32 width, u32 height) = 0;
@ -712,6 +736,8 @@ public:
Error* error = nullptr, const char* entry_point = "main");
virtual std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::GraphicsConfig& config,
Error* error = nullptr) = 0;
virtual std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::ComputeConfig& config,
Error* error = nullptr) = 0;
/// Debug messaging.
virtual void PushDebugGroup(const char* name) = 0;
@ -753,6 +779,8 @@ public:
virtual void Draw(u32 vertex_count, u32 base_vertex) = 0;
virtual void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) = 0;
virtual void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) = 0;
virtual void Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z) = 0;
/// Returns false if the window was completely occluded.
virtual PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color = DEFAULT_CLEAR_COLOR) = 0;
@ -769,7 +797,7 @@ public:
bool UsesLowerLeftOrigin() const;
static GSVector4i FlipToLowerLeft(GSVector4i rc, s32 target_height);
bool ResizeTexture(std::unique_ptr<GPUTexture>* tex, u32 new_width, u32 new_height, GPUTexture::Type type,
GPUTexture::Format format, bool preserve = true);
GPUTexture::Format format, GPUTexture::Flags flags, bool preserve = true);
virtual bool SupportsTextureFormat(GPUTexture::Format format) const = 0;
@ -843,7 +871,7 @@ private:
u8 samples;
GPUTexture::Type type;
GPUTexture::Format format;
u8 pad;
GPUTexture::Flags flags;
ALWAYS_INLINE bool operator==(const TexturePoolKey& rhs) const
{

View File

@ -92,7 +92,7 @@ template<typename FBOType, FBOType (*FactoryFunc)(GPUTexture* const* rts, u32 nu
void (*DestroyFunc)(FBOType fbo)>
void GPUFramebufferManager<FBOType, FactoryFunc, DestroyFunc>::RemoveRTReferences(const GPUTexture* tex)
{
DebugAssert(tex->IsRenderTarget() || tex->IsRWTexture());
DebugAssert(tex->IsRenderTarget());
for (auto it = m_map.begin(); it != m_map.end();)
{
if (!it->first.ContainsRT(tex))

View File

@ -3,18 +3,17 @@
#include "gpu_texture.h"
#include "gpu_device.h"
#include "image.h"
#include "common/align.h"
#include "common/assert.h"
#include "common/bitutils.h"
#include "common/log.h"
#include "common/error.h"
#include "common/string_util.h"
LOG_CHANNEL(GPUTexture);
GPUTexture::GPUTexture(u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type, Format format)
GPUTexture::GPUTexture(u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type, Format format, Flags flags)
: m_width(width), m_height(height), m_layers(layers), m_levels(levels), m_samples(samples), m_type(type),
m_format(format)
m_format(format), m_flags(flags)
{
GPUDevice::s_total_vram_usage += GetVRAMUsage();
}
@ -26,7 +25,7 @@ GPUTexture::~GPUTexture()
const char* GPUTexture::GetFormatName(Format format)
{
static constexpr const char* format_names[static_cast<u8>(Format::MaxCount)] = {
static constexpr const std::array<const char*, static_cast<size_t>(Format::MaxCount)> format_names = {{
"Unknown", // Unknown
"RGBA8", // RGBA8
"BGRA8", // BGRA8
@ -52,43 +51,35 @@ const char* GPUTexture::GetFormatName(Format format)
"RGBA16F", // RGBA16F
"RGBA32F", // RGBA32F
"RGB10A2", // RGB10A2
};
"BC1", // BC1
"BC2", // BC2
"BC3", // BC3
"BC7", // BC7
}};
return format_names[static_cast<u8>(format)];
}
u32 GPUTexture::GetCompressedBytesPerBlock() const
u32 GPUTexture::GetBlockSize() const
{
return GetCompressedBytesPerBlock(m_format);
return GetBlockSize(m_format);
}
u32 GPUTexture::GetCompressedBytesPerBlock(Format format)
u32 GPUTexture::GetBlockSize(Format format)
{
// TODO: Implement me
return GetPixelSize(format);
}
u32 GPUTexture::GetCompressedBlockSize() const
{
return GetCompressedBlockSize(m_format);
}
u32 GPUTexture::GetCompressedBlockSize(Format format)
{
// TODO: Implement me
/*if (format >= Format::BC1 && format <= Format::BC7)
return 4;
else*/
if (format >= Format::BC1 && format <= Format::BC7)
return COMPRESSED_TEXTURE_BLOCK_SIZE;
else
return 1;
}
u32 GPUTexture::CalcUploadPitch(Format format, u32 width)
{
/*
// convert to blocks
if (format >= Format::BC1 && format <= Format::BC7)
width = Common::AlignUpPow2(width, 4) / 4;
*/
return width * GetCompressedBytesPerBlock(format);
width = Common::AlignUpPow2(width, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE;
return width * GetPixelSize(format);
}
u32 GPUTexture::CalcUploadPitch(u32 width) const
@ -103,9 +94,11 @@ u32 GPUTexture::CalcUploadRowLengthFromPitch(u32 pitch) const
u32 GPUTexture::CalcUploadRowLengthFromPitch(Format format, u32 pitch)
{
const u32 block_size = GetCompressedBlockSize(format);
const u32 bytes_per_block = GetCompressedBytesPerBlock(format);
return ((pitch + (bytes_per_block - 1)) / bytes_per_block) * block_size;
const u32 pixel_size = GetPixelSize(format);
if (IsCompressedFormat(format))
return (Common::AlignUpPow2(pitch, pixel_size) / pixel_size) * COMPRESSED_TEXTURE_BLOCK_SIZE;
else
return pitch / pixel_size;
}
u32 GPUTexture::CalcUploadSize(u32 height, u32 pitch) const
@ -115,10 +108,99 @@ u32 GPUTexture::CalcUploadSize(u32 height, u32 pitch) const
u32 GPUTexture::CalcUploadSize(Format format, u32 height, u32 pitch)
{
const u32 block_size = GetCompressedBlockSize(format);
const u32 block_size = GetBlockSize(format);
return pitch * ((static_cast<u32>(height) + (block_size - 1)) / block_size);
}
bool GPUTexture::IsCompressedFormat(Format format)
{
return (format >= Format::BC1);
}
bool GPUTexture::IsCompressedFormat() const
{
return IsCompressedFormat(m_format);
}
u32 GPUTexture::GetFullMipmapCount(u32 width, u32 height)
{
const u32 max_dim = Common::PreviousPow2(std::max(width, height));
return (std::countr_zero(max_dim) + 1);
}
void GPUTexture::CopyTextureDataForUpload(u32 width, u32 height, Format format, void* dst, u32 dst_pitch,
const void* src, u32 src_pitch)
{
if (IsCompressedFormat(format))
{
const u32 blocks_wide = Common::AlignUpPow2(width, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE;
const u32 blocks_high = Common::AlignUpPow2(height, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE;
const u32 block_size = GetPixelSize(format);
StringUtil::StrideMemCpy(dst, dst_pitch, src, src_pitch, block_size * blocks_wide, blocks_high);
}
else
{
StringUtil::StrideMemCpy(dst, dst_pitch, src, src_pitch, width * GetPixelSize(format), height);
}
}
GPUTexture::Format GPUTexture::GetTextureFormatForImageFormat(ImageFormat format)
{
static constexpr const std::array mapping = {
Format::Unknown, // None
Format::RGBA8, // RGBA8
Format::BGRA8, // BGRA8
Format::RGB565, // RGB565
Format::RGBA5551, // RGBA5551
Format::Unknown, // BGR8
Format::BC1, // BC1
Format::BC2, // BC2
Format::BC3, // BC3
Format::BC7, // BC7
};
static_assert(mapping.size() == static_cast<size_t>(ImageFormat::MaxCount));
return mapping[static_cast<size_t>(format)];
}
ImageFormat GPUTexture::GetImageFormatForTextureFormat(Format format)
{
static constexpr const std::array mapping = {
ImageFormat::None, // Unknown
ImageFormat::RGBA8, // RGBA8
ImageFormat::BGRA8, // BGRA8
ImageFormat::RGB565, // RGB565
ImageFormat::RGBA5551, // RGBA5551
ImageFormat::None, // R8
ImageFormat::None, // D16
ImageFormat::None, // D24S8
ImageFormat::None, // D32F
ImageFormat::None, // D32FS8
ImageFormat::None, // R16
ImageFormat::None, // R16I
ImageFormat::None, // R16U
ImageFormat::None, // R16F
ImageFormat::None, // R32I
ImageFormat::None, // R32U
ImageFormat::None, // R32F
ImageFormat::None, // RG8
ImageFormat::None, // RG16
ImageFormat::None, // RG16F
ImageFormat::None, // RG32F
ImageFormat::None, // RGBA16
ImageFormat::None, // RGBA16F
ImageFormat::None, // RGBA32F
ImageFormat::None, // RGB10A2
ImageFormat::BC1, // BC1
ImageFormat::BC2, // BC2
ImageFormat::BC3, // BC3
ImageFormat::BC7, // BC7
};
static_assert(mapping.size() == static_cast<size_t>(Format::MaxCount));
return mapping[static_cast<size_t>(format)];
}
std::array<float, 4> GPUTexture::GetUNormClearColor() const
{
return GPUDevice::RGBA8ToFloat(m_clear_value.color);
@ -126,21 +208,41 @@ std::array<float, 4> GPUTexture::GetUNormClearColor() const
size_t GPUTexture::GetVRAMUsage() const
{
if (m_levels == 1) [[likely]]
return ((static_cast<size_t>(m_width * m_height) * GetPixelSize(m_format)) * m_layers * m_samples);
const size_t ps = GetPixelSize(m_format) * m_layers * m_samples;
u32 width = m_width;
u32 height = m_height;
size_t ts = 0;
for (u32 i = 0; i < m_levels; i++)
size_t mem;
// Max width/height is 65535, 65535*65535 as u32 is okay.
if (IsCompressedFormat())
{
#define COMPRESSED_SIZE(width, height) \
(static_cast<size_t>((Common::AlignUpPow2(width, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE) * \
(Common::AlignUpPow2(height, COMPRESSED_TEXTURE_BLOCK_SIZE) / COMPRESSED_TEXTURE_BLOCK_SIZE)) * \
ps)
u32 width = m_width, height = m_height;
mem = COMPRESSED_SIZE(width, height);
for (u32 i = 1; i < m_levels; i++)
{
width = (width > 1) ? (width / 2) : width;
height = (height > 1) ? (height / 2) : height;
ts += static_cast<size_t>(width * height) * ps;
mem += COMPRESSED_SIZE(width, height);
}
return ts;
#undef COMPRESSED_SIZE
}
else
{
u32 width = m_width, height = m_height;
mem = static_cast<size_t>(width * height) * ps;
for (u32 i = 1; i < m_levels; i++)
{
width = (width > 1) ? (width / 2) : width;
height = (height > 1) ? (height / 2) : height;
mem += static_cast<size_t>(width * height) * ps;
}
}
return mem;
}
u32 GPUTexture::GetPixelSize(GPUTexture::Format format)
@ -171,6 +273,10 @@ u32 GPUTexture::GetPixelSize(GPUTexture::Format format)
8, // RGBA16F
16, // RGBA32F
4, // RGB10A2
8, // BC1 - 16 pixels in 64 bits
16, // BC2 - 16 pixels in 128 bits
16, // BC3 - 16 pixels in 128 bits
16, // BC4 - 16 pixels in 128 bits
}};
return sizes[static_cast<size_t>(format)];
@ -186,31 +292,28 @@ bool GPUTexture::IsDepthStencilFormat(Format format)
return (format == Format::D24S8 || format == Format::D32FS8);
}
bool GPUTexture::IsCompressedFormat(Format format)
bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
Flags flags, Error* error)
{
// TODO: Implement me
return false;
}
bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format)
if (width == 0 || width > MAX_WIDTH || height == 0 || height > MAX_HEIGHT || layers == 0 || layers > MAX_LAYERS ||
levels == 0 || levels > MAX_LEVELS || samples == 0 || samples > MAX_SAMPLES)
{
if (width > MAX_WIDTH || height > MAX_HEIGHT || layers > MAX_LAYERS || levels > MAX_LEVELS || samples > MAX_SAMPLES)
{
ERROR_LOG("Invalid dimensions: {}x{}x{} {} {}.", width, height, layers, levels, samples);
Error::SetStringFmt(error, "Invalid dimensions: {}x{}x{} {} {}.", width, height, layers, levels, samples);
return false;
}
const u32 max_texture_size = g_gpu_device->GetMaxTextureSize();
if (width > max_texture_size || height > max_texture_size)
{
ERROR_LOG("Texture width ({}) or height ({}) exceeds max texture size ({}).", width, height, max_texture_size);
Error::SetStringFmt(error, "Texture width ({}) or height ({}) exceeds max texture size ({}).", width, height,
max_texture_size);
return false;
}
const u32 max_samples = g_gpu_device->GetMaxMultisamples();
if (samples > max_samples)
{
ERROR_LOG("Texture samples ({}) exceeds max samples ({}).", samples, max_samples);
Error::SetStringFmt(error, "Texture samples ({}) exceeds max samples ({}).", samples, max_samples);
return false;
}
@ -218,137 +321,57 @@ bool GPUTexture::ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u
{
if (levels > 1)
{
ERROR_LOG("Multisampled textures can't have mip levels.");
Error::SetStringView(error, "Multisampled textures can't have mip levels.");
return false;
}
else if (type != Type::RenderTarget && type != Type::DepthStencil)
{
ERROR_LOG("Multisampled textures must be render targets or depth stencil targets.");
Error::SetStringView(error, "Multisampled textures must be render targets or depth stencil targets.");
return false;
}
}
if (layers > 1 && type != Type::Texture && type != Type::DynamicTexture)
if (layers > 1 && type != Type::Texture)
{
ERROR_LOG("Texture arrays are not supported on targets.");
Error::SetStringView(error, "Texture arrays are not supported on targets.");
return false;
}
if (levels > 1 && type != Type::Texture && type != Type::DynamicTexture)
if (levels > 1 && type != Type::Texture)
{
ERROR_LOG("Mipmaps are not supported on targets.");
Error::SetStringView(error, "Mipmaps are not supported on targets.");
return false;
}
if ((flags & Flags::AllowGenerateMipmaps) != Flags::None && levels <= 1)
{
Error::SetStringView(error, "Allow generate mipmaps requires >1 level.");
return false;
}
if ((flags & Flags::AllowBindAsImage) != Flags::None &&
((type != Type::Texture && type != Type::RenderTarget) || levels > 1))
{
Error::SetStringView(error, "Bind as image is not allowed on depth or mipmapped targets.");
return false;
}
if ((flags & Flags::AllowMap) != Flags::None &&
(type != Type::Texture || (flags & Flags::AllowGenerateMipmaps) != Flags::None))
{
Error::SetStringView(error, "Allow map is not supported on targets.");
return false;
}
if (IsCompressedFormat(format) && (type != Type::Texture || ((flags & Flags::AllowBindAsImage) != Flags::None)))
{
Error::SetStringView(error, "Compressed formats are only supported for textures.");
return false;
}
return true;
}
bool GPUTexture::ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data,
u32& texture_data_stride, GPUTexture::Format format)
{
switch (format)
{
case Format::BGRA8:
{
for (u32 y = 0; y < height; y++)
{
u8* pixels = reinterpret_cast<u8*>(texture_data.data()) + (y * texture_data_stride);
for (u32 x = 0; x < width; x++)
{
u32 pixel;
std::memcpy(&pixel, pixels, sizeof(pixel));
pixel = (pixel & 0xFF00FF00) | ((pixel & 0xFF) << 16) | ((pixel >> 16) & 0xFF);
std::memcpy(pixels, &pixel, sizeof(pixel));
pixels += sizeof(pixel);
}
}
return true;
}
case Format::RGBA8:
return true;
case Format::RGB565:
{
std::vector<u32> temp(width * height);
for (u32 y = 0; y < height; y++)
{
const u8* pixels_in = reinterpret_cast<const u8*>(texture_data.data()) + (y * texture_data_stride);
u8* pixels_out = reinterpret_cast<u8*>(temp.data()) + (y * width * sizeof(u32));
for (u32 x = 0; x < width; x++)
{
// RGB565 -> RGBA8
u16 pixel_in;
std::memcpy(&pixel_in, pixels_in, sizeof(u16));
pixels_in += sizeof(u16);
const u8 r5 = Truncate8(pixel_in >> 11);
const u8 g6 = Truncate8((pixel_in >> 5) & 0x3F);
const u8 b5 = Truncate8(pixel_in & 0x1F);
const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 2) | (g6 & 3)) << 8) |
(ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (0xFF000000u);
std::memcpy(pixels_out, &rgba8, sizeof(u32));
pixels_out += sizeof(u32);
}
}
texture_data = std::move(temp);
texture_data_stride = sizeof(u32) * width;
return true;
}
case Format::RGBA5551:
{
std::vector<u32> temp(width * height);
for (u32 y = 0; y < height; y++)
{
const u8* pixels_in = reinterpret_cast<const u8*>(texture_data.data()) + (y * texture_data_stride);
u8* pixels_out = reinterpret_cast<u8*>(temp.data()) + (y * width * sizeof(u32));
for (u32 x = 0; x < width; x++)
{
// RGBA5551 -> RGBA8
u16 pixel_in;
std::memcpy(&pixel_in, pixels_in, sizeof(u16));
pixels_in += sizeof(u16);
const u8 a1 = Truncate8(pixel_in >> 15);
const u8 r5 = Truncate8((pixel_in >> 10) & 0x1F);
const u8 g6 = Truncate8((pixel_in >> 5) & 0x1F);
const u8 b5 = Truncate8(pixel_in & 0x1F);
const u32 rgba8 = ZeroExtend32((r5 << 3) | (r5 & 7)) | (ZeroExtend32((g6 << 3) | (g6 & 7)) << 8) |
(ZeroExtend32((b5 << 3) | (b5 & 7)) << 16) | (a1 ? 0xFF000000u : 0u);
std::memcpy(pixels_out, &rgba8, sizeof(u32));
pixels_out += sizeof(u32);
}
}
texture_data = std::move(temp);
texture_data_stride = sizeof(u32) * width;
return true;
}
default:
[[unlikely]] ERROR_LOG("Unknown pixel format {}", static_cast<u32>(format));
return false;
}
}
void GPUTexture::FlipTextureDataRGBA8(u32 width, u32 height, u8* texture_data, u32 texture_data_stride)
{
std::unique_ptr<u8[]> temp = std::make_unique<u8[]>(texture_data_stride);
for (u32 flip_row = 0; flip_row < (height / 2); flip_row++)
{
u8* top_ptr = &texture_data[flip_row * texture_data_stride];
u8* bottom_ptr = &texture_data[((height - 1) - flip_row) * texture_data_stride];
std::memcpy(temp.get(), top_ptr, texture_data_stride);
std::memcpy(top_ptr, bottom_ptr, texture_data_stride);
std::memcpy(bottom_ptr, temp.get(), texture_data_stride);
}
}
void GPUTexture::MakeReadyForSampling()
{
}

View File

@ -11,6 +11,10 @@
#include <string_view>
#include <vector>
class Error;
enum class ImageFormat : u8;
class GPUTexture
{
public:
@ -25,12 +29,9 @@ public:
enum class Type : u8
{
Unknown,
Texture,
RenderTarget,
DepthStencil,
Texture,
DynamicTexture,
RWTexture,
};
enum class Format : u8
@ -60,7 +61,11 @@ public:
RGBA16F,
RGBA32F,
RGB10A2,
MaxCount
BC1, ///< BC1, aka DXT1 compressed texture
BC2, ///< BC2, aka DXT2/3 compressed texture
BC3, ///< BC3, aka DXT4/5 compressed texture
BC7, ///< BC7, aka BPTC compressed texture
MaxCount,
};
enum class State : u8
@ -70,6 +75,15 @@ public:
Invalidated
};
enum class Flags : u8
{
None = 0,
AllowMap = (1 << 0),
AllowBindAsImage = (1 << 2),
AllowGenerateMipmaps = (1 << 3),
AllowMSAAResolveTarget = (1 << 4),
};
union ClearValue
{
u32 color;
@ -81,21 +95,23 @@ public:
virtual ~GPUTexture();
static const char* GetFormatName(Format format);
static u32 GetPixelSize(GPUTexture::Format format);
static bool IsDepthFormat(GPUTexture::Format format);
static bool IsDepthStencilFormat(GPUTexture::Format format);
static u32 GetPixelSize(Format format);
static bool IsDepthFormat(Format format);
static bool IsDepthStencilFormat(Format format);
static bool IsCompressedFormat(Format format);
static u32 GetCompressedBytesPerBlock(Format format);
static u32 GetCompressedBlockSize(Format format);
static u32 GetBlockSize(Format format);
static u32 CalcUploadPitch(Format format, u32 width);
static u32 CalcUploadRowLengthFromPitch(Format format, u32 pitch);
static u32 CalcUploadSize(Format format, u32 height, u32 pitch);
static u32 GetFullMipmapCount(u32 width, u32 height);
static void CopyTextureDataForUpload(u32 width, u32 height, Format format, void* dst, u32 dst_pitch, const void* src,
u32 src_pitch);
static bool ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format);
static Format GetTextureFormatForImageFormat(ImageFormat format);
static ImageFormat GetImageFormatForTextureFormat(Format format);
static bool ConvertTextureDataToRGBA8(u32 width, u32 height, std::vector<u32>& texture_data, u32& texture_data_stride,
GPUTexture::Format format);
static void FlipTextureDataRGBA8(u32 width, u32 height, u8* texture_data, u32 texture_data_stride);
static bool ValidateConfig(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
Flags flags, Error* error);
ALWAYS_INLINE u32 GetWidth() const { return m_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_height; }
@ -104,6 +120,9 @@ public:
ALWAYS_INLINE u32 GetSamples() const { return m_samples; }
ALWAYS_INLINE Type GetType() const { return m_type; }
ALWAYS_INLINE Format GetFormat() const { return m_format; }
ALWAYS_INLINE Flags GetFlags() const { return m_flags; }
ALWAYS_INLINE bool HasFlag(Flags flag) const { return ((static_cast<u8>(m_flags) & static_cast<u8>(flag)) != 0); }
ALWAYS_INLINE GSVector2i GetSizeVec() const { return GSVector2i(m_width, m_height); }
ALWAYS_INLINE GSVector4i GetRect() const
{
return GSVector4i(0, 0, static_cast<s32>(m_width), static_cast<s32>(m_height));
@ -121,15 +140,13 @@ public:
ALWAYS_INLINE bool IsDirty() const { return (m_state == State::Dirty); }
ALWAYS_INLINE bool IsClearedOrInvalidated() const { return (m_state != State::Dirty); }
ALWAYS_INLINE bool IsTexture() const { return (m_type == Type::Texture); }
ALWAYS_INLINE bool IsRenderTarget() const { return (m_type == Type::RenderTarget); }
ALWAYS_INLINE bool IsDepthStencil() const { return (m_type == Type::DepthStencil); }
ALWAYS_INLINE bool IsRenderTargetOrDepthStencil() const
{
return (m_type >= Type::RenderTarget && m_type <= Type::DepthStencil);
}
ALWAYS_INLINE bool IsRenderTarget() const { return (m_type == Type::RenderTarget); }
ALWAYS_INLINE bool IsDepthStencil() const { return (m_type == Type::DepthStencil); }
ALWAYS_INLINE bool IsTexture() const { return (m_type == Type::Texture || m_type == Type::DynamicTexture); }
ALWAYS_INLINE bool IsDynamicTexture() const { return (m_type == Type::DynamicTexture); }
ALWAYS_INLINE bool IsRWTexture() const { return (m_type == Type::RWTexture); }
ALWAYS_INLINE const ClearValue& GetClearValue() const { return m_clear_value; }
ALWAYS_INLINE u32 GetClearColor() const { return m_clear_value.color; }
@ -149,8 +166,8 @@ public:
size_t GetVRAMUsage() const;
u32 GetCompressedBytesPerBlock() const;
u32 GetCompressedBlockSize() const;
bool IsCompressedFormat() const;
u32 GetBlockSize() const;
u32 CalcUploadPitch(u32 width) const;
u32 CalcUploadRowLengthFromPitch(u32 pitch) const;
u32 CalcUploadSize(u32 height, u32 pitch) const;
@ -162,27 +179,34 @@ public:
virtual bool Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer = 0, u32 level = 0) = 0;
virtual void Unmap() = 0;
virtual void GenerateMipmaps() = 0;
// Instructs the backend that we're finished rendering to this texture. It may transition it to a new layout.
virtual void MakeReadyForSampling();
virtual void SetDebugName(std::string_view name) = 0;
protected:
GPUTexture(u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type, Format format);
GPUTexture(u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type, Format format, Flags flags);
static constexpr u32 COMPRESSED_TEXTURE_BLOCK_SIZE = 4;
u16 m_width = 0;
u16 m_height = 0;
u8 m_layers = 0;
u8 m_levels = 0;
u8 m_samples = 0;
Type m_type = Type::Unknown;
Type m_type = Type::Texture;
Format m_format = Format::Unknown;
Flags m_flags = Flags::None;
State m_state = State::Dirty;
ClearValue m_clear_value = {};
};
IMPLEMENT_ENUM_CLASS_BITWISE_OPERATORS(GPUTexture::Flags);
class GPUDownloadTexture
{
public:

File diff suppressed because it is too large Load Diff

View File

@ -3,154 +3,88 @@
#pragma once
#include "common/align.h"
#include "common/heap_array.h"
#include "common/types.h"
#include <algorithm>
#include <cstdio>
#include <cstring>
#include <optional>
#include <span>
#include <string_view>
#include <vector>
class Error;
template<typename PixelType>
class Image
enum class ImageFormat : u8
{
public:
Image() = default;
Image(u32 width, u32 height) { SetSize(width, height); }
Image(u32 width, u32 height, const PixelType* pixels) { SetPixels(width, height, pixels); }
Image(u32 width, u32 height, std::vector<PixelType> pixels) { SetPixels(width, height, std::move(pixels)); }
Image(const Image& copy)
{
m_width = copy.m_width;
m_height = copy.m_height;
m_pixels = copy.m_pixels;
}
Image(Image&& move)
{
m_width = move.m_width;
m_height = move.m_height;
m_pixels = std::move(move.m_pixels);
move.m_width = 0;
move.m_height = 0;
}
Image& operator=(const Image& copy)
{
m_width = copy.m_width;
m_height = copy.m_height;
m_pixels = copy.m_pixels;
return *this;
}
Image& operator=(Image&& move)
{
m_width = move.m_width;
m_height = move.m_height;
m_pixels = std::move(move.m_pixels);
move.m_width = 0;
move.m_height = 0;
return *this;
}
ALWAYS_INLINE bool IsValid() const { return (m_width > 0 && m_height > 0); }
ALWAYS_INLINE u32 GetWidth() const { return m_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_height; }
ALWAYS_INLINE u32 GetPitch() const { return (sizeof(PixelType) * m_width); }
ALWAYS_INLINE const PixelType* GetPixels() const { return m_pixels.data(); }
ALWAYS_INLINE PixelType* GetPixels() { return m_pixels.data(); }
ALWAYS_INLINE const PixelType* GetRowPixels(u32 y) const { return &m_pixels[y * m_width]; }
ALWAYS_INLINE PixelType* GetRowPixels(u32 y) { return &m_pixels[y * m_width]; }
ALWAYS_INLINE void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; }
ALWAYS_INLINE PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; }
void Clear(PixelType fill_value = static_cast<PixelType>(0))
{
std::fill(m_pixels.begin(), m_pixels.end(), fill_value);
}
void Invalidate()
{
m_width = 0;
m_height = 0;
m_pixels.clear();
}
void SetSize(u32 new_width, u32 new_height, PixelType fill_value = static_cast<PixelType>(0))
{
m_width = new_width;
m_height = new_height;
m_pixels.resize(new_width * new_height);
Clear(fill_value);
}
void SetPixels(u32 width, u32 height, const PixelType* pixels)
{
m_width = width;
m_height = height;
m_pixels.resize(width * height);
std::memcpy(m_pixels.data(), pixels, width * height * sizeof(PixelType));
}
void SetPixels(u32 width, u32 height, std::vector<PixelType> pixels)
{
m_width = width;
m_height = height;
m_pixels = std::move(pixels);
}
void SetPixels(u32 width, u32 height, const void* data, u32 stride)
{
const u32 copy_width = width * sizeof(PixelType);
if (stride == copy_width)
{
SetPixels(width, height, static_cast<const PixelType*>(data));
return;
}
m_width = width;
m_height = height;
m_pixels.resize(width, height);
PixelType* out_ptr = m_pixels.data();
const u8* in_ptr = static_cast<const u8*>(data);
for (u32 row = 0; row < height; row++)
{
std::memcpy(out_ptr, in_ptr, copy_width);
out_ptr += width;
in_ptr += stride;
}
}
std::vector<PixelType> TakePixels()
{
m_width = 0;
m_height = 0;
return std::move(m_pixels);
}
protected:
u32 m_width = 0;
u32 m_height = 0;
std::vector<PixelType> m_pixels;
None,
RGBA8,
BGRA8,
RGB565,
RGBA5551,
BGR8,
BC1,
BC2,
BC3,
BC7,
MaxCount,
};
class RGBA8Image : public Image<u32>
class Image
{
public:
static constexpr u8 DEFAULT_SAVE_QUALITY = 85;
RGBA8Image();
RGBA8Image(u32 width, u32 height);
RGBA8Image(u32 width, u32 height, const u32* pixels);
RGBA8Image(u32 width, u32 height, std::vector<u32> pixels);
RGBA8Image(const RGBA8Image& copy);
RGBA8Image(RGBA8Image&& move);
public:
using PixelStorage = Common::unique_aligned_ptr<u8[]>;
RGBA8Image& operator=(const RGBA8Image& copy);
RGBA8Image& operator=(RGBA8Image&& move);
Image();
Image(u32 width, u32 height, ImageFormat format);
Image(u32 width, u32 height, ImageFormat format, const void* pixels, u32 pitch);
Image(u32 width, u32 height, ImageFormat format, PixelStorage pixels, u32 pitch);
Image(const Image& copy);
Image(Image&& move);
Image& operator=(const Image& copy);
Image& operator=(Image&& move);
static const char* GetFormatName(ImageFormat format);
static u32 GetPixelSize(ImageFormat format);
static bool IsCompressedFormat(ImageFormat format);
static u32 CalculatePitch(u32 width, u32 height, ImageFormat format);
static u32 CalculateStorageSize(u32 width, u32 height, ImageFormat format);
static u32 CalculateStorageSize(u32 width, u32 height, u32 pitch, ImageFormat format);
ALWAYS_INLINE bool IsValid() const { return (m_width > 0 && m_height > 0); }
ALWAYS_INLINE u32 GetWidth() const { return m_width; }
ALWAYS_INLINE u32 GetHeight() const { return m_height; }
ALWAYS_INLINE u32 GetPitch() const { return m_pitch; }
ALWAYS_INLINE ImageFormat GetFormat() const { return m_format; }
ALWAYS_INLINE const u8* GetPixels() const { return m_pixels.get(); }
ALWAYS_INLINE u8* GetPixels() { return m_pixels.get(); }
ALWAYS_INLINE const u8* GetRowPixels(u32 y) const { return &m_pixels[y * m_pitch]; }
ALWAYS_INLINE u8* GetRowPixels(u32 y) { return &m_pixels[y * m_pitch]; }
// ALWAYS_INLINE void SetPixel(u32 x, u32 y, PixelType pixel) { m_pixels[y * m_width + x] = pixel; }
// ALWAYS_INLINE PixelType GetPixel(u32 x, u32 y) const { return m_pixels[y * m_width + x]; }
u32 GetBlocksWide() const;
u32 GetBlocksHigh() const;
u32 GetStorageSize() const;
std::span<const u8> GetPixelsSpan() const;
std::span<u8> GetPixelsSpan();
void Clear();
void Invalidate();
void Resize(u32 new_width, u32 new_height, bool preserve);
void Resize(u32 new_width, u32 new_height, ImageFormat format, bool preserve);
void SetPixels(u32 width, u32 height, ImageFormat format, const void* pixels, u32 pitch);
void SetPixels(u32 width, u32 height, ImageFormat format, PixelStorage pixels, u32 pitch);
bool SetAllPixelsOpaque();
PixelStorage TakePixels();
bool LoadFromFile(const char* filename, Error* error = nullptr);
bool LoadFromFile(std::string_view filename, std::FILE* fp, Error* error = nullptr);
@ -164,5 +98,14 @@ public:
std::optional<DynamicHeapArray<u8>> SaveToBuffer(std::string_view filename, u8 quality = DEFAULT_SAVE_QUALITY,
Error* error = nullptr) const;
static void SwapBGRAToRGBA(void* pixels, u32 width, u32 height, u32 pitch);
std::optional<Image> ConvertToRGBA8(Error* error) const;
void FlipY();
protected:
u32 m_width = 0;
u32 m_height = 0;
u32 m_pitch = 0;
ImageFormat m_format = ImageFormat::None;
PixelStorage m_pixels;
};

View File

@ -43,8 +43,8 @@ using MessageDialogCallbackVariant = std::variant<InfoMessageDialogCallback, Con
static constexpr float MENU_BACKGROUND_ANIMATION_TIME = 0.5f;
static constexpr float SMOOTH_SCROLLING_SPEED = 3.5f;
static std::optional<RGBA8Image> LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height);
static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const RGBA8Image& image);
static std::optional<Image> LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height);
static std::shared_ptr<GPUTexture> UploadTexture(std::string_view path, const Image& image);
static void TextureLoaderThread();
static void DrawFileSelector();
@ -100,7 +100,7 @@ static std::atomic_bool s_texture_load_thread_quit{false};
static std::mutex s_texture_load_mutex;
static std::condition_variable s_texture_load_cv;
static std::deque<std::string> s_texture_load_queue;
static std::deque<std::pair<std::string, RGBA8Image>> s_texture_upload_queue;
static std::deque<std::pair<std::string, Image>> s_texture_upload_queue;
static std::thread s_texture_load_thread;
static SmallString s_fullscreen_footer_text;
@ -288,19 +288,9 @@ const std::shared_ptr<GPUTexture>& ImGuiFullscreen::GetPlaceholderTexture()
return s_placeholder_texture;
}
std::unique_ptr<GPUTexture> ImGuiFullscreen::CreateTextureFromImage(const RGBA8Image& image)
std::optional<Image> ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height)
{
std::unique_ptr<GPUTexture> ret =
g_gpu_device->CreateTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
if (!ret) [[unlikely]]
ERROR_LOG("Failed to upload {}x{} RGBA8Image to GPU", image.GetWidth(), image.GetHeight());
return ret;
}
std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view path, u32 svg_width, u32 svg_height)
{
std::optional<RGBA8Image> image;
std::optional<Image> image;
Error error;
if (StringUtil::EqualNoCase(Path::GetExtension(path), "svg"))
@ -313,7 +303,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
if (svg_data.has_value())
{
image = RGBA8Image();
image = Image();
if (!image->RasterizeSVG(svg_data->cspan(), svg_width, svg_height))
{
ERROR_LOG("Failed to rasterize SVG texture file '{}': {}", path, error.GetDescription());
@ -331,7 +321,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
auto fp = FileSystem::OpenManagedCFile(path_str.c_str(), "rb", &error);
if (fp)
{
image = RGBA8Image();
image = Image();
if (!image->LoadFromFile(path_str.c_str(), fp.get(), &error))
{
ERROR_LOG("Failed to read texture file '{}': {}", path, error.GetDescription());
@ -348,7 +338,7 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
std::optional<DynamicHeapArray<u8>> data = Host::ReadResourceFile(path, true, &error);
if (data.has_value())
{
image = RGBA8Image();
image = Image();
if (!image->LoadFromBuffer(path, data->cspan(), &error))
{
ERROR_LOG("Failed to read texture resource '{}': {}", path, error.GetDescription());
@ -364,14 +354,13 @@ std::optional<RGBA8Image> ImGuiFullscreen::LoadTextureImage(std::string_view pat
return image;
}
std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path, const RGBA8Image& image)
std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path, const Image& image)
{
std::unique_ptr<GPUTexture> texture =
g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
Error error;
std::unique_ptr<GPUTexture> texture = g_gpu_device->FetchAndUploadTextureImage(image, GPUTexture::Flags::None, &error);
if (!texture)
{
ERROR_LOG("Failed to create {}x{} texture for resource", image.GetWidth(), image.GetHeight());
ERROR_LOG("Failed to upload texture '{}': {}", Path::GetFileTitle(path), error.GetDescription());
return {};
}
@ -381,7 +370,7 @@ std::shared_ptr<GPUTexture> ImGuiFullscreen::UploadTexture(std::string_view path
std::shared_ptr<GPUTexture> ImGuiFullscreen::LoadTexture(std::string_view path, u32 width_hint, u32 height_hint)
{
std::optional<RGBA8Image> image(LoadTextureImage(path, width_hint, height_hint));
std::optional<Image> image(LoadTextureImage(path, width_hint, height_hint));
if (image.has_value())
{
std::shared_ptr<GPUTexture> ret(UploadTexture(path, image.value()));
@ -447,7 +436,7 @@ void ImGuiFullscreen::UploadAsyncTextures()
std::unique_lock lock(s_texture_load_mutex);
while (!s_texture_upload_queue.empty())
{
std::pair<std::string, RGBA8Image> it(std::move(s_texture_upload_queue.front()));
std::pair<std::string, Image> it(std::move(s_texture_upload_queue.front()));
s_texture_upload_queue.pop_front();
lock.unlock();
@ -480,7 +469,7 @@ void ImGuiFullscreen::TextureLoaderThread()
s_texture_load_queue.pop_front();
lock.unlock();
std::optional<RGBA8Image> image(LoadTextureImage(path.c_str(), 0, 0));
std::optional<Image> image(LoadTextureImage(path.c_str(), 0, 0));
lock.lock();
// don't bother queuing back if it doesn't exist

View File

@ -18,7 +18,7 @@
#include <utility>
#include <vector>
class RGBA8Image;
class Image;
class GPUTexture;
class SmallStringBase;
@ -129,7 +129,6 @@ void Shutdown();
/// Texture cache.
const std::shared_ptr<GPUTexture>& GetPlaceholderTexture();
std::unique_ptr<GPUTexture> CreateTextureFromImage(const RGBA8Image& image);
std::shared_ptr<GPUTexture> LoadTexture(std::string_view path, u32 svg_width = 0, u32 svg_height = 0);
GPUTexture* GetCachedTexture(std::string_view name);
GPUTexture* GetCachedTexture(std::string_view name, u32 svg_width, u32 svg_height);

View File

@ -1241,19 +1241,21 @@ void ImGuiManager::UpdateSoftwareCursorTexture(u32 index)
return;
}
RGBA8Image image;
if (!image.LoadFromFile(sc.image_path.c_str()))
Error error;
Image image;
if (!image.LoadFromFile(sc.image_path.c_str(), &error))
{
ERROR_LOG("Failed to load software cursor {} image '{}'", index, sc.image_path);
ERROR_LOG("Failed to load software cursor {} image '{}': {}", index, sc.image_path, error.GetDescription());
return;
}
g_gpu_device->RecycleTexture(std::move(sc.texture));
sc.texture = g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
GPUTexture::Format::RGBA8, GPUTexture::Flags::None, image.GetPixels(),
image.GetPitch(), &error);
if (!sc.texture)
{
ERROR_LOG("Failed to upload {}x{} software cursor {} image '{}'", image.GetWidth(), image.GetHeight(), index,
sc.image_path);
ERROR_LOG("Failed to upload {}x{} software cursor {} image '{}': {}", image.GetWidth(), image.GetHeight(), index,
sc.image_path, error.GetDescription());
return;
}

View File

@ -904,6 +904,23 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
{
const float deadzone =
si.GetFloatValue(section.c_str(), fmt::format("Macro{}Deadzone", macro_button_index + 1).c_str(), 0.0f);
for (const std::string& binding : bindings)
{
// We currently can't use chords with a deadzone.
if (binding.find('&') != std::string::npos || deadzone == 0.0f)
{
if (deadzone != 0.0f)
WARNING_LOG("Chord binding {} not supported with trigger deadzone {}.", binding, deadzone);
AddBinding(binding, InputButtonEventHandler{[pad_index, macro_button_index](bool state) {
if (!System::IsValid())
return;
SetMacroButtonState(pad_index, macro_button_index, state);
}});
}
else
{
AddBindings(bindings, InputAxisEventHandler{[pad_index, macro_button_index, deadzone](float value) {
if (!System::IsValid())
return;
@ -913,6 +930,8 @@ void InputManager::AddPadBindings(const SettingsInterface& si, const std::string
}});
}
}
}
}
if (cinfo->vibration_caps != Controller::VibrationCapabilities::NoVibration)
{

View File

@ -244,7 +244,7 @@ GPUTexture* MediaCaptureBase::GetRenderTexture()
return m_render_texture.get();
m_render_texture = g_gpu_device->CreateTexture(m_video_width, m_video_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
m_video_render_texture_format);
m_video_render_texture_format, GPUTexture::Flags::None);
if (!m_render_texture) [[unlikely]]
{
ERROR_LOG("Failed to create {}x{} render texture.", m_video_width, m_video_height);

View File

@ -78,7 +78,16 @@ class MetalPipeline final : public GPUPipeline
public:
~MetalPipeline() override;
ALWAYS_INLINE id<MTLRenderPipelineState> GetPipelineState() const { return m_pipeline; }
ALWAYS_INLINE bool IsRenderPipeline() const { return (m_depth != nil); }
ALWAYS_INLINE bool IsComputePipeline() const { return (m_depth == nil); }
ALWAYS_INLINE id<MTLRenderPipelineState> GetRenderPipelineState() const
{
return (id<MTLRenderPipelineState>)m_pipeline;
}
ALWAYS_INLINE id<MTLComputePipelineState> GetComputePipelineState() const
{
return (id<MTLComputePipelineState>)m_pipeline;
}
ALWAYS_INLINE id<MTLDepthStencilState> GetDepthState() const { return m_depth; }
ALWAYS_INLINE MTLCullMode GetCullMode() const { return m_cull_mode; }
ALWAYS_INLINE MTLPrimitiveType GetPrimitive() const { return m_primitive; }
@ -86,10 +95,9 @@ public:
void SetDebugName(std::string_view name) override;
private:
MetalPipeline(id<MTLRenderPipelineState> pipeline, id<MTLDepthStencilState> depth, MTLCullMode cull_mode,
MTLPrimitiveType primitive);
MetalPipeline(id pipeline, id<MTLDepthStencilState> depth, MTLCullMode cull_mode, MTLPrimitiveType primitive);
id<MTLRenderPipelineState> m_pipeline;
id m_pipeline;
id<MTLDepthStencilState> m_depth;
MTLCullMode m_cull_mode;
MTLPrimitiveType m_primitive;
@ -112,6 +120,7 @@ public:
void Unmap() override;
void MakeReadyForSampling() override;
void GenerateMipmaps() override;
void SetDebugName(std::string_view name) override;
@ -120,7 +129,7 @@ public:
private:
MetalTexture(id<MTLTexture> texture, u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type,
Format format);
Format format, Flags flags);
id<MTLTexture> m_texture;
@ -142,7 +151,7 @@ public:
~MetalDownloadTexture() override;
static std::unique_ptr<MetalDownloadTexture> Create(u32 width, u32 height, GPUTexture::Format format, void* memory,
size_t memory_size, u32 memory_stride);
size_t memory_size, u32 memory_stride, Error* error);
void CopyFromTexture(u32 dst_x, u32 dst_y, GPUTexture* src, u32 src_x, u32 src_y, u32 width, u32 height,
u32 src_layer, u32 src_level, bool use_transfer_pitch) override;
@ -172,7 +181,7 @@ public:
ALWAYS_INLINE id<MTLBuffer> GetMTLBuffer() const { return m_buffer.GetBuffer(); }
bool CreateBuffer(id<MTLDevice> device);
bool CreateBuffer(id<MTLDevice> device, Error* error);
// Inherited via GPUTextureBuffer
void* Map(u32 required_elements) override;
@ -226,15 +235,18 @@ public:
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements) override;
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
const void* data = nullptr, u32 data_stride = 0,
Error* error = nullptr) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config, Error* error = nullptr) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements,
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride) override;
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_stride,
Error* error = nullptr) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
void CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src,
@ -251,6 +263,7 @@ public:
std::string_view source, const char* entry_point,
DynamicHeapArray<u8>* out_binary, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override;
void PushDebugGroup(const char* name) override;
void PopDebugGroup() override;
@ -265,7 +278,7 @@ public:
void* MapUniformBuffer(u32 size) override;
void UnmapUniformBuffer(u32 size) override;
void SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds,
GPUPipeline::RenderPassFlag feedback_loop) override;
GPUPipeline::RenderPassFlag flags) override;
void SetPipeline(GPUPipeline* pipeline) override;
void SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* sampler) override;
void SetTextureBuffer(u32 slot, GPUTextureBuffer* buffer) override;
@ -274,6 +287,8 @@ public:
void Draw(u32 vertex_count, u32 base_vertex) override;
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z) override;
bool SetGPUTimingEnabled(bool enabled) override;
float GetAndResetAccumulatedGPUTime() override;
@ -314,7 +329,7 @@ private:
static constexpr u32 INDEX_BUFFER_SIZE = 4 * 1024 * 1024;
static constexpr u32 UNIFORM_BUFFER_SIZE = 2 * 1024 * 1024;
static constexpr u32 UNIFORM_BUFFER_ALIGNMENT = 256;
static constexpr u32 TEXTURE_STREAM_BUFFER_SIZE = 32 /*16*/ * 1024 * 1024; // TODO reduce after separate allocations
static constexpr u32 TEXTURE_STREAM_BUFFER_SIZE = 64 * 1024 * 1024; // TODO reduce after separate allocations
static constexpr u8 NUM_TIMESTAMP_QUERIES = 3;
using DepthStateMap = std::unordered_map<u8, id<MTLDepthStencilState>>;
@ -338,7 +353,6 @@ private:
std::unique_ptr<GPUShader> CreateShaderFromMSL(GPUShaderStage stage, std::string_view source,
std::string_view entry_point, Error* error);
id<MTLFunction> GetFunctionFromLibrary(id<MTLLibrary> library, NSString* name);
id<MTLComputePipelineState> CreateComputePipeline(id<MTLFunction> function, NSString* name);
ClearPipelineConfig GetCurrentClearPipelineConfig() const;
id<MTLRenderPipelineState> GetClearDepthPipeline(const ClearPipelineConfig& config);
id<MTLDepthStencilState> GetDepthState(const GPUPipeline::DepthState& ds);
@ -349,9 +363,12 @@ private:
void CleanupObjects();
ALWAYS_INLINE bool InRenderPass() const { return (m_render_encoder != nil); }
ALWAYS_INLINE bool InComputePass() const { return (m_compute_encoder != nil); }
ALWAYS_INLINE bool IsInlineUploading() const { return (m_inline_upload_encoder != nil); }
void BeginRenderPass();
void EndRenderPass();
void BeginComputePass();
void EndComputePass();
void EndInlineUploading();
void EndAnyEncoding();
@ -359,10 +376,12 @@ private:
void SetInitialEncoderState();
void SetViewportInRenderEncoder();
void SetScissorInRenderEncoder();
void CommitRenderTargetClears();
void BindRenderTargetsAsComputeImages();
void RenderBlankFrame(MetalSwapChain* swap_chain);
bool CreateBuffers();
bool CreateBuffers(Error* error);
void DestroyBuffers();
bool IsRenderTargetBound(const GPUTexture* tex) const;
@ -384,7 +403,7 @@ private:
id<MTLLibrary> m_shaders = nil;
id<MTLBinaryArchive> m_pipeline_archive = nil;
std::vector<std::pair<std::pair<GPUTexture::Format, GPUTexture::Format>, id<MTLComputePipelineState>>>
std::vector<std::pair<std::pair<GPUTexture::Format, GPUTexture::Format>, std::unique_ptr<GPUPipeline>>>
m_resolve_pipelines;
std::vector<std::pair<ClearPipelineConfig, id<MTLRenderPipelineState>>> m_clear_pipelines;
@ -394,9 +413,10 @@ private:
id<MTLCommandBuffer> m_render_cmdbuf = nil;
id<MTLRenderCommandEncoder> m_render_encoder = nil;
id<MTLComputeCommandEncoder> m_compute_encoder = nil;
u8 m_num_current_render_targets = 0;
GPUPipeline::RenderPassFlag m_current_feedback_loop = GPUPipeline::NoRenderPassFlags;
GPUPipeline::RenderPassFlag m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
std::array<MetalTexture*, MAX_RENDER_TARGETS> m_current_render_targets = {};
MetalTexture* m_current_depth_target = nullptr;

View File

@ -71,13 +71,19 @@ static constexpr std::array<MTLPixelFormat, static_cast<u32>(GPUTexture::Format:
MTLPixelFormatRGBA16Float, // RGBA16F
MTLPixelFormatRGBA32Float, // RGBA32F
MTLPixelFormatBGR10A2Unorm, // RGB10A2
MTLPixelFormatBC1_RGBA, // BC1
MTLPixelFormatBC2_RGBA, // BC2
MTLPixelFormatBC3_RGBA, // BC3
MTLPixelFormatBC7_RGBAUnorm, // BC7
};
static void LogNSError(NSError* error, std::string_view message)
{
Log::FastWrite(Log::Channel::GPUDevice, Log::Level::Error, message);
Log::FastWrite(Log::Channel::GPUDevice, Log::Level::Error, " NSError Code: {}", static_cast<u32>(error.code));
Log::FastWrite(Log::Channel::GPUDevice, Log::Level::Error, " NSError Description: {}", [error.description UTF8String]);
Log::FastWrite(Log::Channel::GPUDevice, Log::Level::Error, " NSError Description: {}",
[error.description UTF8String]);
}
static GPUTexture::Format GetTextureFormatForMTLFormat(MTLPixelFormat fmt)
@ -346,11 +352,8 @@ bool MetalDevice::CreateDeviceAndMainSwapChain(std::string_view adapter, Feature
return false;
}
if (!CreateBuffers())
{
Error::SetStringView(error, "Failed to create buffers.");
if (!CreateBuffers(error))
return false;
}
return true;
}
@ -387,6 +390,10 @@ void MetalDevice::SetFeatures(FeatureMask disabled_features)
m_features.pipeline_cache = true;
m_features.prefer_unused_textures = true;
// Same feature bit for both.
m_features.dxt_textures = m_features.bptc_textures =
!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) && m_device.supportsBCTextureCompression;
// Disable pipeline cache on Intel, apparently it's buggy.
if ([[m_device name] containsString:@"Intel"])
{
@ -503,28 +510,6 @@ id<MTLFunction> MetalDevice::GetFunctionFromLibrary(id<MTLLibrary> library, NSSt
return function;
}
id<MTLComputePipelineState> MetalDevice::CreateComputePipeline(id<MTLFunction> function, NSString* name)
{
MTLComputePipelineDescriptor* desc = [MTLComputePipelineDescriptor new];
if (name != nil)
[desc setLabel:name];
[desc setComputeFunction:function];
NSError* err = nil;
id<MTLComputePipelineState> pipeline = [m_device newComputePipelineStateWithDescriptor:desc
options:MTLPipelineOptionNone
reflection:nil
error:&err];
[desc release];
if (pipeline == nil)
{
LogNSError(err, "Create compute pipeline failed:");
return nil;
}
return pipeline;
}
void MetalDevice::DestroyDevice()
{
WaitForPreviousCommandBuffers();
@ -564,11 +549,6 @@ void MetalDevice::DestroyDevice()
[it.second release];
}
m_depth_states.clear();
for (auto& it : m_resolve_pipelines)
{
if (it.second != nil)
[it.second release];
}
m_resolve_pipelines.clear();
for (auto& it : m_clear_pipelines)
{
@ -601,13 +581,14 @@ std::string MetalDevice::GetDriverInfo() const
}
}
bool MetalDevice::CreateBuffers()
bool MetalDevice::CreateBuffers(Error* error)
{
if (!m_vertex_buffer.Create(m_device, VERTEX_BUFFER_SIZE) || !m_index_buffer.Create(m_device, INDEX_BUFFER_SIZE) ||
!m_uniform_buffer.Create(m_device, UNIFORM_BUFFER_SIZE) ||
!m_texture_upload_buffer.Create(m_device, TEXTURE_STREAM_BUFFER_SIZE))
if (!m_vertex_buffer.Create(m_device, VERTEX_BUFFER_SIZE, error) ||
!m_index_buffer.Create(m_device, INDEX_BUFFER_SIZE, error) ||
!m_uniform_buffer.Create(m_device, UNIFORM_BUFFER_SIZE, error) ||
!m_texture_upload_buffer.Create(m_device, TEXTURE_STREAM_BUFFER_SIZE, error))
{
ERROR_LOG("Failed to create vertex/index/uniform buffers.");
Error::AddPrefix(error, "Failed to create vertex/index/uniform buffers: ");
return false;
}
@ -755,7 +736,7 @@ std::unique_ptr<GPUShader> MetalDevice::CreateShaderFromSource(GPUShaderStage st
return CreateShaderFromMSL(stage, source, entry_point, error);
}
MetalPipeline::MetalPipeline(id<MTLRenderPipelineState> pipeline, id<MTLDepthStencilState> depth, MTLCullMode cull_mode,
MetalPipeline::MetalPipeline(id pipeline, id<MTLDepthStencilState> depth, MTLCullMode cull_mode,
MTLPrimitiveType primitive)
: m_pipeline(pipeline), m_depth(depth), m_cull_mode(cull_mode), m_primitive(primitive)
{
@ -982,9 +963,32 @@ std::unique_ptr<GPUPipeline> MetalDevice::CreatePipeline(const GPUPipeline::Grap
}
}
std::unique_ptr<GPUPipeline> MetalDevice::CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error)
{
@autoreleasepool
{
MTLComputePipelineDescriptor* desc = [[MTLComputePipelineDescriptor new] autorelease];
[desc setComputeFunction:static_cast<MetalShader*>(config.compute_shader)->GetFunction()];
NSError* nserror = nil;
id<MTLComputePipelineState> pipeline = [m_device newComputePipelineStateWithDescriptor:desc
options:MTLPipelineOptionNone
reflection:nil
error:&nserror];
if (pipeline == nil)
{
LogNSError(nserror, "Failed to create compute pipeline state");
CocoaTools::NSErrorToErrorObject(error, "newComputePipelineStateWithDescriptor failed: ", nserror);
return {};
}
return std::unique_ptr<GPUPipeline>(new MetalPipeline(pipeline, nil, MTLCullModeNone, MTLPrimitiveTypePoint));
}
}
MetalTexture::MetalTexture(id<MTLTexture> texture, u16 width, u16 height, u8 layers, u8 levels, u8 samples, Type type,
Format format)
: GPUTexture(width, height, layers, levels, samples, type, format), m_texture(texture)
Format format, Flags flags)
: GPUTexture(width, height, layers, levels, samples, type, format, flags), m_texture(texture)
{
}
@ -1000,8 +1004,8 @@ MetalTexture::~MetalTexture()
bool MetalTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer /*= 0*/,
u32 level /*= 0*/)
{
const u32 aligned_pitch = Common::AlignUpPow2(width * GetPixelSize(), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 req_size = height * aligned_pitch;
const u32 aligned_pitch = Common::AlignUpPow2(CalcUploadPitch(width), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 req_size = CalcUploadSize(height, aligned_pitch);
GPUDevice::GetStatistics().buffer_streamed += req_size;
GPUDevice::GetStatistics().num_uploads++;
@ -1018,7 +1022,7 @@ bool MetalTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
actual_buffer = [dev.GetMTLDevice() newBufferWithBytes:data length:upload_size options:options];
actual_offset = 0;
actual_pitch = pitch;
if (actual_buffer == nil)
if (actual_buffer == nil) [[unlikely]]
{
Panic("Failed to allocate temporary buffer.");
return false;
@ -1031,7 +1035,7 @@ bool MetalTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
if (!sb.ReserveMemory(req_size, TEXTURE_UPLOAD_ALIGNMENT))
{
dev.SubmitCommandBuffer();
if (!sb.ReserveMemory(req_size, TEXTURE_UPLOAD_ALIGNMENT))
if (!sb.ReserveMemory(req_size, TEXTURE_UPLOAD_ALIGNMENT)) [[unlikely]]
{
Panic("Failed to reserve texture upload space.");
return false;
@ -1039,7 +1043,7 @@ bool MetalTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data,
}
actual_offset = sb.GetCurrentOffset();
StringUtil::StrideMemCpy(sb.GetCurrentHostPointer(), aligned_pitch, data, pitch, width * GetPixelSize(), height);
CopyTextureDataForUpload(width, height, m_format, sb.GetCurrentHostPointer(), aligned_pitch, data, pitch);
sb.CommitMemory(req_size);
actual_buffer = sb.GetBuffer();
actual_pitch = aligned_pitch;
@ -1070,8 +1074,8 @@ bool MetalTexture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32
if ((x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers || level > m_levels)
return false;
const u32 aligned_pitch = Common::AlignUpPow2(width * GetPixelSize(), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 req_size = height * aligned_pitch;
const u32 aligned_pitch = Common::AlignUpPow2(CalcUploadPitch(width), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 req_size = CalcUploadSize(height, aligned_pitch);
MetalDevice& dev = MetalDevice::GetInstance();
if (m_state == GPUTexture::State::Cleared && (x != 0 || y != 0 || width != m_width || height != m_height))
@ -1102,8 +1106,8 @@ bool MetalTexture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32
void MetalTexture::Unmap()
{
const u32 aligned_pitch = Common::AlignUpPow2(m_map_width * GetPixelSize(), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 req_size = m_map_height * aligned_pitch;
const u32 aligned_pitch = Common::AlignUpPow2(CalcUploadPitch(m_map_width), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 req_size = CalcUploadSize(m_map_height, aligned_pitch);
GPUDevice::GetStatistics().buffer_streamed += req_size;
GPUDevice::GetStatistics().num_uploads++;
@ -1144,6 +1148,15 @@ void MetalTexture::MakeReadyForSampling()
dev.EndRenderPass();
}
void MetalTexture::GenerateMipmaps()
{
DebugAssert(HasFlag(Flags::AllowGenerateMipmaps));
MetalDevice& dev = MetalDevice::GetInstance();
const bool is_inline = (m_use_fence_counter == dev.GetCurrentFenceCounter());
id<MTLBlitCommandEncoder> encoder = dev.GetBlitEncoder(is_inline);
[encoder generateMipmapsForTexture:m_texture];
}
void MetalTexture::SetDebugName(std::string_view name)
{
@autoreleasepool
@ -1154,14 +1167,18 @@ void MetalTexture::SetDebugName(std::string_view name)
std::unique_ptr<GPUTexture> MetalDevice::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data, u32 data_stride)
GPUTexture::Flags flags, const void* data, u32 data_stride,
Error* error)
{
if (!GPUTexture::ValidateConfig(width, height, layers, layers, samples, type, format))
if (!GPUTexture::ValidateConfig(width, height, layers, layers, samples, type, format, flags, error))
return {};
const MTLPixelFormat pixel_format = s_pixel_format_mapping[static_cast<u8>(format)];
if (pixel_format == MTLPixelFormatInvalid)
{
Error::SetStringFmt(error, "Pixel format {} is not supported.", GPUTexture::GetFormatName(format));
return {};
}
@autoreleasepool
{
@ -1186,7 +1203,6 @@ std::unique_ptr<GPUTexture> MetalDevice::CreateTexture(u32 width, u32 height, u3
switch (type)
{
case GPUTexture::Type::Texture:
case GPUTexture::Type::DynamicTexture:
desc.usage = MTLTextureUsageShaderRead;
break;
@ -1195,25 +1211,25 @@ std::unique_ptr<GPUTexture> MetalDevice::CreateTexture(u32 width, u32 height, u3
desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageRenderTarget;
break;
case GPUTexture::Type::RWTexture:
desc.usage = MTLTextureUsageShaderRead | MTLTextureUsageShaderWrite;
break;
DefaultCaseIsUnreachable();
}
default:
UnreachableCode();
break;
if ((flags & (GPUTexture::Flags::AllowBindAsImage | GPUTexture::Flags::AllowMSAAResolveTarget)) !=
GPUTexture::Flags::None)
{
desc.usage |= MTLTextureUsageShaderWrite;
}
id<MTLTexture> tex = [m_device newTextureWithDescriptor:desc];
if (tex == nil)
{
ERROR_LOG("Failed to create {}x{} texture.", width, height);
Error::SetStringView(error, "newTextureWithDescriptor() failed");
return {};
}
// This one can *definitely* go on the upload buffer.
std::unique_ptr<GPUTexture> gtex(
new MetalTexture([tex retain], width, height, layers, levels, samples, type, format));
new MetalTexture([tex retain], width, height, layers, levels, samples, type, format, flags));
if (data)
{
// TODO: handle multi-level uploads...
@ -1239,7 +1255,8 @@ MetalDownloadTexture::~MetalDownloadTexture()
}
std::unique_ptr<MetalDownloadTexture> MetalDownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_stride)
void* memory, size_t memory_size, u32 memory_stride,
Error* error)
{
@autoreleasepool
{
@ -1260,7 +1277,7 @@ std::unique_ptr<MetalDownloadTexture> MetalDownloadTexture::Create(u32 width, u3
buffer = [[dev.m_device newBufferWithLength:buffer_size options:options] retain];
if (buffer == nil)
{
ERROR_LOG("Failed to create {} byte buffer", buffer_size);
Error::SetStringFmt(error, "Failed to create {} byte buffer", buffer_size);
return {};
}
@ -1285,7 +1302,7 @@ std::unique_ptr<MetalDownloadTexture> MetalDownloadTexture::Create(u32 width, u3
deallocator:nil] retain];
if (buffer == nil)
{
ERROR_LOG("Failed to import {} byte buffer", page_aligned_size);
Error::SetStringFmt(error, "Failed to import {} byte buffer", page_aligned_size);
return {};
}
@ -1372,16 +1389,17 @@ void MetalDownloadTexture::SetDebugName(std::string_view name)
}
}
std::unique_ptr<GPUDownloadTexture> MetalDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format)
std::unique_ptr<GPUDownloadTexture> MetalDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
Error* error)
{
return MetalDownloadTexture::Create(width, height, format, nullptr, 0, 0);
return MetalDownloadTexture::Create(width, height, format, nullptr, 0, 0, error);
}
std::unique_ptr<GPUDownloadTexture> MetalDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride)
u32 memory_stride, Error* error)
{
return MetalDownloadTexture::Create(width, height, format, memory, memory_size, memory_stride);
return MetalDownloadTexture::Create(width, height, format, memory, memory_size, memory_stride, error);
}
MetalSampler::MetalSampler(id<MTLSamplerState> ss) : m_ss(ss)
@ -1395,7 +1413,7 @@ void MetalSampler::SetDebugName(std::string_view name)
// lame.. have to put it on the descriptor :/
}
std::unique_ptr<GPUSampler> MetalDevice::CreateSampler(const GPUSampler::Config& config)
std::unique_ptr<GPUSampler> MetalDevice::CreateSampler(const GPUSampler::Config& config, Error* error)
{
@autoreleasepool
{
@ -1451,7 +1469,7 @@ std::unique_ptr<GPUSampler> MetalDevice::CreateSampler(const GPUSampler::Config&
}
if (i == std::size(border_color_mapping))
{
ERROR_LOG("Unsupported border color: {:08X}", config.border_color.GetValue());
Error::SetStringFmt(error, "Unsupported border color: {:08X}", config.border_color.GetValue());
return {};
}
@ -1462,7 +1480,7 @@ std::unique_ptr<GPUSampler> MetalDevice::CreateSampler(const GPUSampler::Config&
id<MTLSamplerState> ss = [m_device newSamplerStateWithDescriptor:desc];
if (ss == nil)
{
ERROR_LOG("Failed to create sampler state.");
Error::SetStringView(error, "newSamplerStateWithDescriptor failed");
return {};
}
@ -1479,6 +1497,11 @@ bool MetalDevice::SupportsTextureFormat(GPUTexture::Format format) const
if (![m_device supportsFamily:MTLGPUFamilyApple2])
return false;
}
else if (format >= GPUTexture::Format::BC1 && format <= GPUTexture::Format::BC7)
{
if (!m_device.supportsBCTextureCompression)
return false;
}
return (s_pixel_format_mapping[static_cast<u8>(format)] != MTLPixelFormatInvalid);
}
@ -1553,20 +1576,21 @@ void MetalDevice::ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u3
DebugAssert((dst_x + width) <= dst->GetMipWidth(dst_level));
DebugAssert((dst_y + height) <= dst->GetMipHeight(dst_level));
DebugAssert(!dst->IsMultisampled() && src->IsMultisampled());
DebugAssert(dst->HasFlag(GPUTexture::Flags::AllowMSAAResolveTarget));
// Only does first level for now..
DebugAssert(dst_level == 0 && dst_layer == 0);
const GPUTexture::Format src_format = dst->GetFormat();
const GPUTexture::Format dst_format = dst->GetFormat();
id<MTLComputePipelineState> resolve_pipeline = nil;
GPUPipeline* resolve_pipeline;
if (auto iter = std::find_if(m_resolve_pipelines.begin(), m_resolve_pipelines.end(),
[src_format, dst_format](const auto& it) {
return it.first.first == src_format && it.first.second == dst_format;
});
iter != m_resolve_pipelines.end())
{
resolve_pipeline = iter->second;
resolve_pipeline = iter->second.get();
}
else
{
@ -1579,32 +1603,41 @@ void MetalDevice::ResolveTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u3
if (function == nil)
Panic("Failed to get resolve kernel");
resolve_pipeline = [CreateComputePipeline(function, is_depth ? @"Depth Resolve" : @"Color Resolve") autorelease];
if (resolve_pipeline != nil)
[resolve_pipeline retain];
m_resolve_pipelines.emplace_back(std::make_pair(src_format, dst_format), resolve_pipeline);
MetalShader temp_shader(GPUShaderStage::Compute, m_shaders, function);
GPUPipeline::ComputeConfig config;
config.layout = GPUPipeline::Layout::ComputeSingleTextureAndPushConstants;
config.compute_shader = &temp_shader;
std::unique_ptr<GPUPipeline> pipeline = CreatePipeline(config, nullptr);
if (!pipeline)
Panic("Failed to create resolve pipeline");
GL_OBJECT_NAME(pipeline, is_depth ? "Depth Resolve" : "Color Resolve");
resolve_pipeline =
m_resolve_pipelines.emplace_back(std::make_pair(src_format, dst_format), std::move(pipeline)).second.get();
}
}
if (resolve_pipeline == nil)
Panic("Failed to get resolve pipeline");
if (InRenderPass())
EndRenderPass();
s_stats.num_copies++;
const u32 threadgroupHeight = resolve_pipeline.maxTotalThreadsPerThreadgroup / resolve_pipeline.threadExecutionWidth;
const MTLSize intrinsicThreadgroupSize = MTLSizeMake(resolve_pipeline.threadExecutionWidth, threadgroupHeight, 1);
const id<MTLComputePipelineState> mtl_pipeline =
static_cast<MetalPipeline*>(resolve_pipeline)->GetComputePipelineState();
const u32 threadgroupHeight = mtl_pipeline.maxTotalThreadsPerThreadgroup / mtl_pipeline.threadExecutionWidth;
const MTLSize intrinsicThreadgroupSize = MTLSizeMake(mtl_pipeline.threadExecutionWidth, threadgroupHeight, 1);
const MTLSize threadgroupsInGrid =
MTLSizeMake((src->GetWidth() + intrinsicThreadgroupSize.width - 1) / intrinsicThreadgroupSize.width,
(src->GetHeight() + intrinsicThreadgroupSize.height - 1) / intrinsicThreadgroupSize.height, 1);
id<MTLComputeCommandEncoder> computeEncoder = [m_render_cmdbuf computeCommandEncoder];
[computeEncoder setComputePipelineState:resolve_pipeline];
[computeEncoder setTexture:static_cast<MetalTexture*>(src)->GetMTLTexture() atIndex:0];
[computeEncoder setTexture:static_cast<MetalTexture*>(dst)->GetMTLTexture() atIndex:1];
[computeEncoder dispatchThreadgroups:threadgroupsInGrid threadsPerThreadgroup:intrinsicThreadgroupSize];
[computeEncoder endEncoding];
// Set up manually to not disturb state.
BeginComputePass();
[m_compute_encoder setComputePipelineState:mtl_pipeline];
[m_compute_encoder setTexture:static_cast<MetalTexture*>(src)->GetMTLTexture() atIndex:0];
[m_compute_encoder setTexture:static_cast<MetalTexture*>(dst)->GetMTLTexture() atIndex:1];
[m_compute_encoder dispatchThreadgroups:threadgroupsInGrid threadsPerThreadgroup:intrinsicThreadgroupSize];
EndComputePass();
}
void MetalDevice::ClearRenderTarget(GPUTexture* t, u32 c)
@ -1645,7 +1678,7 @@ void MetalDevice::ClearDepth(GPUTexture* t, float d)
[m_render_encoder setVertexBuffer:m_uniform_buffer.GetBuffer() offset:m_current_uniform_buffer_position atIndex:0];
if (m_current_pipeline)
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetPipelineState()];
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetRenderPipelineState()];
if (m_current_cull_mode != MTLCullModeNone)
[m_render_encoder setCullMode:m_current_cull_mode];
if (depth != m_current_depth_state)
@ -1674,6 +1707,8 @@ void MetalDevice::CommitClear(MetalTexture* tex)
// TODO: We could combine it with the current render pass.
if (InRenderPass())
EndRenderPass();
else if (InComputePass())
EndComputePass();
@autoreleasepool
{
@ -1759,9 +1794,9 @@ MetalTextureBuffer::~MetalTextureBuffer()
m_buffer.Destroy();
}
bool MetalTextureBuffer::CreateBuffer(id<MTLDevice> device)
bool MetalTextureBuffer::CreateBuffer(id<MTLDevice> device, Error* error)
{
return m_buffer.Create(device, GetSizeInBytes());
return m_buffer.Create(device, GetSizeInBytes(), error);
}
void* MetalTextureBuffer::Map(u32 required_elements)
@ -1796,10 +1831,10 @@ void MetalTextureBuffer::SetDebugName(std::string_view name)
}
std::unique_ptr<GPUTextureBuffer> MetalDevice::CreateTextureBuffer(GPUTextureBuffer::Format format,
u32 size_in_elements)
u32 size_in_elements, Error* error)
{
std::unique_ptr<MetalTextureBuffer> tb = std::make_unique<MetalTextureBuffer>(format, size_in_elements);
if (!tb->CreateBuffer(m_device))
if (!tb->CreateBuffer(m_device, error))
tb.reset();
return tb;
@ -1896,11 +1931,13 @@ void MetalDevice::UnmapUniformBuffer(u32 size)
}
void MetalDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTexture* ds,
GPUPipeline::RenderPassFlag feedback_loop)
GPUPipeline::RenderPassFlag flags)
{
bool changed = (m_num_current_render_targets != num_rts || m_current_depth_target != ds ||
(!m_features.framebuffer_fetch && ((feedback_loop & GPUPipeline::ColorFeedbackLoop) !=
(m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop))));
((flags & GPUPipeline::BindRenderTargetsAsImages) !=
(m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages)) ||
(!m_features.framebuffer_fetch && ((flags & GPUPipeline::ColorFeedbackLoop) !=
(m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop))));
bool needs_ds_clear = (ds && ds->IsClearedOrInvalidated());
bool needs_rt_clear = false;
@ -1915,13 +1952,20 @@ void MetalDevice::SetRenderTargets(GPUTexture* const* rts, u32 num_rts, GPUTextu
for (u32 i = num_rts; i < m_num_current_render_targets; i++)
m_current_render_targets[i] = nullptr;
m_num_current_render_targets = static_cast<u8>(num_rts);
m_current_feedback_loop = feedback_loop;
m_current_render_pass_flags = flags;
if (changed || needs_rt_clear || needs_ds_clear)
{
if (InRenderPass())
{
EndRenderPass();
}
else if (InComputePass() && (flags & GPUPipeline::BindRenderTargetsAsImages) != GPUPipeline::NoRenderPassFlags)
{
CommitRenderTargetClears();
BindRenderTargetsAsComputeImages();
}
}
}
void MetalDevice::SetPipeline(GPUPipeline* pipeline)
@ -1931,9 +1975,11 @@ void MetalDevice::SetPipeline(GPUPipeline* pipeline)
return;
m_current_pipeline = static_cast<MetalPipeline*>(pipeline);
if (!m_current_pipeline->IsComputePipeline())
{
if (InRenderPass())
{
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetPipelineState()];
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetRenderPipelineState()];
if (m_current_depth_state != m_current_pipeline->GetDepthState())
{
@ -1953,6 +1999,12 @@ void MetalDevice::SetPipeline(GPUPipeline* pipeline)
m_current_cull_mode = m_current_pipeline->GetCullMode();
}
}
else
{
if (InComputePass())
[m_compute_encoder setComputePipelineState:m_current_pipeline->GetComputePipelineState()];
}
}
void MetalDevice::UnbindPipeline(MetalPipeline* pl)
{
@ -1979,6 +2031,8 @@ void MetalDevice::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* s
m_current_textures[slot] = T;
if (InRenderPass())
[m_render_encoder setFragmentTexture:T atIndex:slot];
else if (InComputePass())
[m_compute_encoder setTexture:T atIndex:slot];
}
id<MTLSamplerState> S = sampler ? static_cast<MetalSampler*>(sampler)->GetSamplerState() : nil;
@ -1987,6 +2041,8 @@ void MetalDevice::SetTextureSampler(u32 slot, GPUTexture* texture, GPUSampler* s
m_current_samplers[slot] = S;
if (InRenderPass())
[m_render_encoder setFragmentSamplerState:S atIndex:slot];
else if (InComputePass())
[m_compute_encoder setTexture:T atIndex:slot];
}
}
@ -2011,6 +2067,8 @@ void MetalDevice::UnbindTexture(MetalTexture* tex)
m_current_textures[i] = nil;
if (InRenderPass())
[m_render_encoder setFragmentTexture:nil atIndex:i];
else if (InComputePass())
[m_compute_encoder setTexture:nil atIndex:0];
}
}
@ -2070,7 +2128,7 @@ void MetalDevice::SetScissor(const GSVector4i rc)
void MetalDevice::BeginRenderPass()
{
DebugAssert(m_render_encoder == nil);
DebugAssert(m_render_encoder == nil && !InComputePass());
// Inline writes :(
if (m_inline_upload_encoder != nil)
@ -2180,12 +2238,57 @@ void MetalDevice::BeginRenderPass()
void MetalDevice::EndRenderPass()
{
DebugAssert(InRenderPass() && !IsInlineUploading());
DebugAssert(InRenderPass() && !IsInlineUploading() && !InComputePass());
[m_render_encoder endEncoding];
[m_render_encoder release];
m_render_encoder = nil;
}
void MetalDevice::BeginComputePass()
{
DebugAssert(!InRenderPass() && !IsInlineUploading() && !InComputePass());
if ((m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages) != GPUPipeline::NoRenderPassFlags)
CommitRenderTargetClears();
m_compute_encoder = [[m_render_cmdbuf computeCommandEncoder] retain];
[m_compute_encoder setTextures:m_current_textures.data() withRange:NSMakeRange(0, MAX_TEXTURE_SAMPLERS)];
[m_compute_encoder setSamplerStates:m_current_samplers.data() withRange:NSMakeRange(0, MAX_TEXTURE_SAMPLERS)];
if ((m_current_render_pass_flags & GPUPipeline::BindRenderTargetsAsImages) != GPUPipeline::NoRenderPassFlags)
BindRenderTargetsAsComputeImages();
if (m_current_pipeline && m_current_pipeline->IsComputePipeline())
[m_compute_encoder setComputePipelineState:m_current_pipeline->GetComputePipelineState()];
}
void MetalDevice::CommitRenderTargetClears()
{
for (u32 i = 0; i < m_num_current_render_targets; i++)
{
MetalTexture* rt = m_current_render_targets[i];
if (rt->GetState() == GPUTexture::State::Invalidated)
rt->SetState(GPUTexture::State::Dirty);
else if (rt->GetState() == GPUTexture::State::Cleared)
CommitClear(rt);
}
}
void MetalDevice::BindRenderTargetsAsComputeImages()
{
for (u32 i = 0; i < m_num_current_render_targets; i++)
[m_compute_encoder setTexture:m_current_render_targets[i]->GetMTLTexture() atIndex:MAX_TEXTURE_SAMPLERS + i];
}
void MetalDevice::EndComputePass()
{
DebugAssert(InComputePass());
[m_compute_encoder endEncoding];
[m_compute_encoder release];
m_compute_encoder = nil;
}
void MetalDevice::EndInlineUploading()
{
DebugAssert(IsInlineUploading() && !InRenderPass());
@ -2198,6 +2301,8 @@ void MetalDevice::EndAnyEncoding()
{
if (InRenderPass())
EndRenderPass();
else if (InComputePass())
EndComputePass();
else if (IsInlineUploading())
EndInlineUploading();
}
@ -2213,14 +2318,14 @@ void MetalDevice::SetInitialEncoderState()
[m_render_encoder setCullMode:m_current_cull_mode];
if (m_current_depth_state != nil)
[m_render_encoder setDepthStencilState:m_current_depth_state];
if (m_current_pipeline != nil)
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetPipelineState()];
if (m_current_pipeline && m_current_pipeline->IsRenderPipeline())
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetRenderPipelineState()];
[m_render_encoder setFragmentTextures:m_current_textures.data() withRange:NSMakeRange(0, MAX_TEXTURE_SAMPLERS)];
[m_render_encoder setFragmentSamplerStates:m_current_samplers.data() withRange:NSMakeRange(0, MAX_TEXTURE_SAMPLERS)];
if (m_current_ssbo)
[m_render_encoder setFragmentBuffer:m_current_ssbo offset:0 atIndex:1];
if (!m_features.framebuffer_fetch && (m_current_feedback_loop & GPUPipeline::ColorFeedbackLoop))
if (!m_features.framebuffer_fetch && (m_current_render_pass_flags & GPUPipeline::ColorFeedbackLoop))
{
DebugAssert(m_current_render_targets[0]);
[m_render_encoder setFragmentTexture:m_current_render_targets[0]->GetMTLTexture() atIndex:MAX_TEXTURE_SAMPLERS];
@ -2249,8 +2354,13 @@ void MetalDevice::SetScissorInRenderEncoder()
void MetalDevice::PreDrawCheck()
{
if (!InRenderPass())
{
if (InComputePass())
EndComputePass();
BeginRenderPass();
}
}
void MetalDevice::Draw(u32 vertex_count, u32 base_vertex)
{
@ -2392,6 +2502,25 @@ void MetalDevice::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 ba
}
}
void MetalDevice::Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z)
{
if (!InComputePass())
{
if (InRenderPass())
EndRenderPass();
BeginComputePass();
}
DebugAssert(m_current_pipeline && m_current_pipeline->IsComputePipeline());
id<MTLComputePipelineState> pipeline = m_current_pipeline->GetComputePipelineState();
// TODO: We could remap to the optimal group size..
[m_compute_encoder dispatchThreads:MTLSizeMake(threads_x, threads_y, threads_z)
threadsPerThreadgroup:MTLSizeMake(group_size_x, group_size_y, group_size_z)];
}
id<MTLBlitCommandEncoder> MetalDevice::GetBlitEncoder(bool is_inline)
{
@autoreleasepool
@ -2450,7 +2579,7 @@ GPUDevice::PresentResult MetalDevice::BeginPresent(GPUSwapChain* swap_chain, u32
s_stats.num_render_passes++;
std::memset(m_current_render_targets.data(), 0, sizeof(m_current_render_targets));
m_num_current_render_targets = 0;
m_current_feedback_loop = GPUPipeline::NoRenderPassFlags;
m_current_render_pass_flags = GPUPipeline::NoRenderPassFlags;
m_current_depth_target = nullptr;
m_current_pipeline = nullptr;
m_current_depth_state = nil;

View File

@ -19,6 +19,8 @@
#include <deque>
#include <memory>
class Error;
class MetalStreamBuffer
{
public:
@ -38,7 +40,7 @@ public:
ALWAYS_INLINE u32 GetCurrentSpace() const { return m_current_space; }
ALWAYS_INLINE u32 GetCurrentOffset() const { return m_current_offset; }
bool Create(id<MTLDevice> device, u32 size);
bool Create(id<MTLDevice> device, u32 size, Error* error);
void Destroy();
bool ReserveMemory(u32 num_bytes, u32 alignment);

View File

@ -6,6 +6,7 @@
#include "common/align.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/log.h"
LOG_CHANNEL(GPUDevice);
@ -18,7 +19,7 @@ MetalStreamBuffer::~MetalStreamBuffer()
Destroy();
}
bool MetalStreamBuffer::Create(id<MTLDevice> device, u32 size)
bool MetalStreamBuffer::Create(id<MTLDevice> device, u32 size, Error* error)
{
@autoreleasepool
{
@ -27,7 +28,7 @@ bool MetalStreamBuffer::Create(id<MTLDevice> device, u32 size)
id<MTLBuffer> new_buffer = [device newBufferWithLength:size options:options];
if (new_buffer == nil)
{
ERROR_LOG("Failed to create buffer.");
Error::SetStringView(error, "newBufferWithLength failed");
return false;
}

View File

@ -60,9 +60,10 @@ void OpenGLDevice::SetErrorObject(Error* errptr, std::string_view prefix, GLenum
std::unique_ptr<GPUTexture> OpenGLDevice::CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data, u32 data_stride)
GPUTexture::Flags flags, const void* data /* = nullptr */,
u32 data_stride /* = 0 */, Error* error /* = nullptr */)
{
return OpenGLTexture::Create(width, height, layers, levels, samples, type, format, data, data_stride);
return OpenGLTexture::Create(width, height, layers, levels, samples, type, format, flags, data, data_stride, error);
}
bool OpenGLDevice::SupportsTextureFormat(GPUTexture::Format format) const
@ -207,6 +208,12 @@ void OpenGLDevice::InvalidateRenderTarget(GPUTexture* t)
}
}
std::unique_ptr<GPUPipeline> OpenGLDevice::CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error)
{
ERROR_LOG("Compute shaders are not yet supported.");
return {};
}
void OpenGLDevice::PushDebugGroup(const char* name)
{
#ifdef _DEBUG
@ -488,6 +495,7 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
m_features.geometry_shaders =
!(disabled_features & FEATURE_MASK_GEOMETRY_SHADERS) && (GLAD_GL_VERSION_3_2 || GLAD_GL_ES_VERSION_3_2);
m_features.compute_shaders = false;
m_features.gpu_timing = !(m_gl_context->IsGLES() &&
(!GLAD_GL_EXT_disjoint_timer_query || !glGetQueryObjectivEXT || !glGetQueryObjectui64vEXT));
@ -498,6 +506,12 @@ bool OpenGLDevice::CheckFeatures(FeatureMask disabled_features)
m_features.shader_cache = false;
m_features.dxt_textures =
(!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) && GLAD_GL_EXT_texture_compression_s3tc);
m_features.bptc_textures =
(!(disabled_features & FEATURE_MASK_COMPRESSED_TEXTURES) &&
(GLAD_GL_VERSION_4_2 || GLAD_GL_ARB_texture_compression_bptc || GLAD_GL_EXT_texture_compression_bptc));
m_features.pipeline_cache = m_gl_context->IsGLES() || GLAD_GL_ARB_get_program_binary;
if (m_features.pipeline_cache)
{
@ -1078,6 +1092,12 @@ void OpenGLDevice::DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 b
Panic("Barriers are not supported");
}
void OpenGLDevice::Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z)
{
Panic("Compute shaders are not supported");
}
void OpenGLDevice::MapVertexBuffer(u32 vertex_size, u32 vertex_count, void** map_ptr, u32* map_space,
u32* map_base_vertex)
{

View File

@ -52,15 +52,18 @@ public:
std::optional<bool> exclusive_fullscreen_control,
Error* error) override;
std::unique_ptr<GPUTexture> CreateTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
GPUTexture::Type type, GPUTexture::Format format,
const void* data = nullptr, u32 data_stride = 0) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements) override;
GPUTexture::Type type, GPUTexture::Format format, GPUTexture::Flags flags,
const void* data = nullptr, u32 data_stride = 0,
Error* error = nullptr) override;
std::unique_ptr<GPUSampler> CreateSampler(const GPUSampler::Config& config, Error* error = nullptr) override;
std::unique_ptr<GPUTextureBuffer> CreateTextureBuffer(GPUTextureBuffer::Format format, u32 size_in_elements,
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size,
u32 memory_stride) override;
Error* error = nullptr) override;
std::unique_ptr<GPUDownloadTexture> CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_stride,
Error* error = nullptr) override;
bool SupportsTextureFormat(GPUTexture::Format format) const override;
void CopyTextureRegion(GPUTexture* dst, u32 dst_x, u32 dst_y, u32 dst_layer, u32 dst_level, GPUTexture* src,
@ -77,6 +80,7 @@ public:
std::string_view source, const char* entry_point,
DynamicHeapArray<u8>* out_binary, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::GraphicsConfig& config, Error* error) override;
std::unique_ptr<GPUPipeline> CreatePipeline(const GPUPipeline::ComputeConfig& config, Error* error) override;
void PushDebugGroup(const char* name) override;
void PopDebugGroup() override;
@ -100,6 +104,8 @@ public:
void Draw(u32 vertex_count, u32 base_vertex) override;
void DrawIndexed(u32 index_count, u32 base_index, u32 base_vertex) override;
void DrawIndexedWithBarrier(u32 index_count, u32 base_index, u32 base_vertex, DrawBarrier type) override;
void Dispatch(u32 threads_x, u32 threads_y, u32 threads_z, u32 group_size_x, u32 group_size_y,
u32 group_size_z) override;
PresentResult BeginPresent(GPUSwapChain* swap_chain, u32 clear_color) override;
void EndPresent(GPUSwapChain* swap_chain, bool explicit_present, u64 present_time) override;

View File

@ -5,9 +5,9 @@
#include "common/align.h"
#include "common/assert.h"
#include "common/error.h"
#include <array>
#include <cstdio>
OpenGLStreamBuffer::OpenGLStreamBuffer(GLenum target, GLuint buffer_id, u32 size)
: m_target(target), m_buffer_id(buffer_id), m_size(size)
@ -65,7 +65,7 @@ public:
u32 GetChunkSize() const override { return m_size; }
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size)
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, Error* error)
{
glGetError();
@ -74,9 +74,10 @@ public:
glBindBuffer(target, buffer_id);
glBufferData(target, size, nullptr, GL_STREAM_DRAW);
GLenum err = glGetError();
if (err != GL_NO_ERROR)
const GLenum err = glGetError();
if (err != GL_NO_ERROR) [[unlikely]]
{
Error::SetStringFmt(error, "Failed to create buffer: 0x{:X}", err);
glBindBuffer(target, 0);
glDeleteBuffers(1, &buffer_id);
return {};
@ -119,7 +120,7 @@ public:
u32 GetChunkSize() const override { return m_size; }
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size)
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, Error* error)
{
glGetError();
@ -128,9 +129,10 @@ public:
glBindBuffer(target, buffer_id);
glBufferData(target, size, nullptr, GL_STREAM_DRAW);
GLenum err = glGetError();
if (err != GL_NO_ERROR)
const GLenum err = glGetError();
if (err != GL_NO_ERROR) [[unlikely]]
{
Error::SetStringFmt(error, "Failed to create buffer: 0x{:X}", err);
glBindBuffer(target, 0);
glDeleteBuffers(1, &buffer_id);
return {};
@ -283,7 +285,7 @@ public:
return prev_position;
}
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, bool coherent = true)
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, Error* error, bool coherent = true)
{
glGetError();
@ -298,9 +300,10 @@ public:
else if (GLAD_GL_EXT_buffer_storage)
glBufferStorageEXT(target, size, nullptr, flags);
GLenum err = glGetError();
if (err != GL_NO_ERROR)
const GLenum err = glGetError();
if (err != GL_NO_ERROR) [[unlikely]]
{
Error::SetStringFmt(error, "Failed to create buffer: 0x{:X}", err);
glBindBuffer(target, 0);
glDeleteBuffers(1, &buffer_id);
return {};
@ -325,12 +328,12 @@ private:
} // namespace
std::unique_ptr<OpenGLStreamBuffer> OpenGLStreamBuffer::Create(GLenum target, u32 size)
std::unique_ptr<OpenGLStreamBuffer> OpenGLStreamBuffer::Create(GLenum target, u32 size, Error* error /* = nullptr */)
{
std::unique_ptr<OpenGLStreamBuffer> buf;
if (GLAD_GL_VERSION_4_4 || GLAD_GL_ARB_buffer_storage || GLAD_GL_EXT_buffer_storage)
{
buf = BufferStorageStreamBuffer::Create(target, size);
buf = BufferStorageStreamBuffer::Create(target, size, error);
if (buf)
return buf;
}
@ -341,11 +344,11 @@ std::unique_ptr<OpenGLStreamBuffer> OpenGLStreamBuffer::Create(GLenum target, u3
if (std::strcmp(vendor, "ARM") == 0 || std::strcmp(vendor, "Qualcomm") == 0)
{
// Mali and Adreno drivers can't do sub-buffer tracking...
return BufferDataStreamBuffer::Create(target, size);
return BufferDataStreamBuffer::Create(target, size, error);
}
return BufferSubDataStreamBuffer::Create(target, size);
return BufferSubDataStreamBuffer::Create(target, size, error);
#else
return BufferDataStreamBuffer::Create(target, size);
return BufferDataStreamBuffer::Create(target, size, error);
#endif
}

View File

@ -12,6 +12,8 @@
#include <tuple>
#include <vector>
class Error;
class OpenGLStreamBuffer
{
public:
@ -42,7 +44,7 @@ public:
/// Returns the minimum granularity of blocks which sync objects will be created around.
virtual u32 GetChunkSize() const = 0;
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size);
static std::unique_ptr<OpenGLStreamBuffer> Create(GLenum target, u32 size, Error* error = nullptr);
protected:
OpenGLStreamBuffer(GLenum target, GLuint buffer_id, u32 size);

View File

@ -7,6 +7,7 @@
#include "common/align.h"
#include "common/assert.h"
#include "common/error.h"
#include "common/intrin.h"
#include "common/log.h"
#include "common/string_util.h"
@ -57,6 +58,10 @@ const std::tuple<GLenum, GLenum, GLenum>& OpenGLTexture::GetPixelFormatMapping(G
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // RGBA16F
{GL_RGBA32F, GL_RGBA, GL_FLOAT}, // RGBA32F
{GL_RGB10_A2, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV}, // RGB10A2
{GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_UNSIGNED_BYTE}, // BC1
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_UNSIGNED_BYTE}, // BC2
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_UNSIGNED_BYTE}, // BC3
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_UNSIGNED_BYTE}, // BC7
}};
// GLES doesn't have the non-normalized 16-bit formats.. use float and hope for the best, lol.
@ -87,6 +92,10 @@ const std::tuple<GLenum, GLenum, GLenum>& OpenGLTexture::GetPixelFormatMapping(G
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // RGBA16F
{GL_RGBA32F, GL_RGBA, GL_FLOAT}, // RGBA32F
{GL_RGB10_A2, GL_BGRA, GL_UNSIGNED_INT_2_10_10_10_REV}, // RGB10A2
{GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_COMPRESSED_RGBA_S3TC_DXT1_EXT, GL_UNSIGNED_BYTE}, // BC1
{GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_COMPRESSED_RGBA_S3TC_DXT3_EXT, GL_UNSIGNED_BYTE}, // BC2
{GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_COMPRESSED_RGBA_S3TC_DXT5_EXT, GL_UNSIGNED_BYTE}, // BC3
{GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_COMPRESSED_RGBA_BPTC_UNORM_ARB, GL_UNSIGNED_BYTE}, // BC7
}};
return gles ? mapping_gles[static_cast<u32>(format)] : mapping[static_cast<u32>(format)];
@ -98,9 +107,9 @@ ALWAYS_INLINE static u32 GetUploadAlignment(u32 pitch)
}
OpenGLTexture::OpenGLTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format,
GLuint id)
Flags flags, GLuint id)
: GPUTexture(static_cast<u16>(width), static_cast<u16>(height), static_cast<u8>(layers), static_cast<u8>(levels),
static_cast<u8>(samples), type, format),
static_cast<u8>(samples), type, format, flags),
m_id(id)
{
}
@ -126,14 +135,15 @@ bool OpenGLTexture::UseTextureStorage() const
}
std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples,
Type type, Format format, const void* data, u32 data_pitch)
Type type, Format format, Flags flags, const void* data,
u32 data_pitch, Error* error)
{
if (!ValidateConfig(width, height, layers, levels, samples, type, format))
if (!ValidateConfig(width, height, layers, levels, samples, type, format, flags, error))
return nullptr;
if (layers > 1 && data)
{
ERROR_LOG("Loading texture array data not currently supported");
Error::SetStringView(error, "Loading texture array data not currently supported");
return nullptr;
}
@ -167,6 +177,7 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
else
{
const bool use_texture_storage = UseTextureStorage(false);
const bool is_compressed = IsCompressedFormat(format);
if (use_texture_storage)
{
if (layers > 1)
@ -181,10 +192,10 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
const u32 alignment = GetUploadAlignment(data_pitch);
if (data)
{
GPUDevice::GetStatistics().buffer_streamed += data_pitch * height;
GPUDevice::GetStatistics().buffer_streamed += CalcUploadSize(format, height, data_pitch);
GPUDevice::GetStatistics().num_uploads++;
glPixelStorei(GL_UNPACK_ROW_LENGTH, data_pitch / pixel_size);
glPixelStorei(GL_UNPACK_ROW_LENGTH, CalcUploadRowLengthFromPitch(format, data_pitch));
if (alignment != DEFAULT_UPLOAD_ALIGNMENT)
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
}
@ -195,19 +206,56 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
for (u32 i = 0; i < levels; i++)
{
if (use_texture_storage)
{
if (is_compressed)
{
const u32 size = CalcUploadSize(format, current_height, data_pitch);
if (layers > 1)
{
glCompressedTexSubImage3D(target, i, 0, 0, 0, current_width, current_height, layers, gl_format, size,
data_ptr);
}
else
{
glCompressedTexSubImage2D(target, i, 0, 0, current_width, current_height, gl_format, size, data_ptr);
}
}
else
{
if (layers > 1)
glTexSubImage3D(target, i, 0, 0, 0, current_width, current_height, layers, gl_format, gl_type, data_ptr);
else
glTexSubImage2D(target, i, 0, 0, current_width, current_height, gl_format, gl_type, data_ptr);
}
}
else
{
if (is_compressed)
{
const u32 size = CalcUploadSize(format, current_height, data_pitch);
if (layers > 1)
{
glCompressedTexImage3D(target, i, gl_internal_format, current_width, current_height, layers, 0, size,
data_ptr);
}
else
{
glCompressedTexImage2D(target, i, gl_internal_format, current_width, current_height, 0, size, data_ptr);
}
}
else
{
if (layers > 1)
{
glTexImage3D(target, i, gl_internal_format, current_width, current_height, layers, 0, gl_format, gl_type,
data_ptr);
}
else
glTexImage2D(target, i, gl_internal_format, current_width, current_height, 0, gl_format, gl_type, data_ptr);
{
glTexImage2D(target, i, gl_internal_format, current_width, current_height, 0, gl_format, gl_type,
data_ptr);
}
}
}
if (data_ptr)
@ -235,15 +283,16 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
}
}
GLenum error = glGetError();
if (error != GL_NO_ERROR)
const GLenum gl_error = glGetError();
if (gl_error != GL_NO_ERROR)
{
ERROR_LOG("Failed to create texture: 0x{:X}", error);
Error::SetStringFmt(error, "Failed to create texture: 0x{:X}", gl_error);
glDeleteTextures(1, &id);
return nullptr;
}
return std::unique_ptr<OpenGLTexture>(new OpenGLTexture(width, height, layers, levels, samples, type, format, id));
return std::unique_ptr<OpenGLTexture>(
new OpenGLTexture(width, height, layers, levels, samples, type, format, flags, id));
}
void OpenGLTexture::CommitClear()
@ -254,14 +303,11 @@ void OpenGLTexture::CommitClear()
bool OpenGLTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer /*= 0*/,
u32 level /*= 0*/)
{
// TODO: perf counters
// Worth using the PBO? Driver probably knows better...
const GLenum target = GetGLTarget();
const auto [gl_internal_format, gl_format, gl_type] = GetPixelFormatMapping(m_format, OpenGLDevice::IsGLES());
const u32 pixel_size = GetPixelSize();
const u32 preferred_pitch = Common::AlignUpPow2(static_cast<u32>(width) * pixel_size, TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 map_size = preferred_pitch * static_cast<u32>(height);
const u32 preferred_pitch = Common::AlignUpPow2(CalcUploadPitch(width), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 map_size = CalcUploadSize(height, pitch);
OpenGLStreamBuffer* sb = OpenGLDevice::GetTextureStreamBuffer();
CommitClear();
@ -280,8 +326,22 @@ bool OpenGLTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data
if (alignment != DEFAULT_UPLOAD_ALIGNMENT)
glPixelStorei(GL_UNPACK_ALIGNMENT, alignment);
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch / pixel_size);
glTexSubImage2D(target, layer, x, y, width, height, gl_format, gl_type, data);
glPixelStorei(GL_UNPACK_ROW_LENGTH, CalcUploadRowLengthFromPitch(pitch));
if (IsCompressedFormat())
{
const u32 size = CalcUploadSize(height, pitch);
if (IsTextureArray())
glCompressedTexSubImage3D(target, level, x, y, layer, width, height, 1, gl_format, size, data);
else
glCompressedTexSubImage2D(target, level, x, y, width, height, gl_format, size, data);
}
else
{
if (IsTextureArray())
glTexSubImage3D(target, level, x, y, layer, width, height, 1, gl_format, gl_type, data);
else
glTexSubImage2D(target, level, x, y, width, height, gl_format, gl_type, data);
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
if (alignment != DEFAULT_UPLOAD_ALIGNMENT)
@ -290,13 +350,39 @@ bool OpenGLTexture::Update(u32 x, u32 y, u32 width, u32 height, const void* data
else
{
const auto map = sb->Map(TEXTURE_UPLOAD_ALIGNMENT, map_size);
StringUtil::StrideMemCpy(map.pointer, preferred_pitch, data, pitch, width * pixel_size, height);
CopyTextureDataForUpload(width, height, m_format, map.pointer, preferred_pitch, data, pitch);
sb->Unmap(map_size);
sb->Bind();
glPixelStorei(GL_UNPACK_ROW_LENGTH, preferred_pitch / pixel_size);
glTexSubImage2D(GL_TEXTURE_2D, layer, x, y, width, height, gl_format, gl_type,
glPixelStorei(GL_UNPACK_ROW_LENGTH, CalcUploadRowLengthFromPitch(preferred_pitch));
if (IsCompressedFormat())
{
const u32 size = CalcUploadSize(height, pitch);
if (IsTextureArray())
{
glCompressedTexSubImage3D(target, level, x, y, layer, width, height, 1, gl_format, size,
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
}
else
{
glCompressedTexSubImage2D(target, level, x, y, width, height, gl_format, size,
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
}
}
else
{
if (IsTextureArray())
{
glTexSubImage3D(target, level, x, y, layer, width, height, 1, gl_format, gl_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
}
else
{
glTexSubImage2D(target, level, x, y, width, height, gl_format, gl_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
}
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
sb->Unbind();
@ -312,8 +398,8 @@ bool OpenGLTexture::Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u3
if ((x + width) > GetMipWidth(level) || (y + height) > GetMipHeight(level) || layer > m_layers || level > m_levels)
return false;
const u32 pitch = Common::AlignUpPow2(static_cast<u32>(width) * GetPixelSize(), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 upload_size = pitch * static_cast<u32>(height);
const u32 pitch = Common::AlignUpPow2(CalcUploadPitch(width), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 upload_size = CalcUploadSize(height, pitch);
OpenGLStreamBuffer* sb = OpenGLDevice::GetTextureStreamBuffer();
if (!sb || upload_size > sb->GetSize())
return false;
@ -336,8 +422,8 @@ void OpenGLTexture::Unmap()
{
CommitClear();
const u32 pitch = Common::AlignUpPow2(static_cast<u32>(m_map_width) * GetPixelSize(), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 upload_size = pitch * static_cast<u32>(m_map_height);
const u32 pitch = Common::AlignUpPow2(CalcUploadPitch(m_map_width), TEXTURE_UPLOAD_PITCH_ALIGNMENT);
const u32 upload_size = CalcUploadSize(m_map_height, pitch);
GPUDevice::GetStatistics().buffer_streamed += upload_size;
GPUDevice::GetStatistics().num_uploads++;
@ -351,9 +437,25 @@ void OpenGLTexture::Unmap()
const GLenum target = GetGLTarget();
glBindTexture(target, m_id);
glPixelStorei(GL_UNPACK_ROW_LENGTH, pitch / GetPixelSize());
glPixelStorei(GL_UNPACK_ROW_LENGTH, CalcUploadRowLengthFromPitch(pitch));
const auto [gl_internal_format, gl_format, gl_type] = GetPixelFormatMapping(m_format, OpenGLDevice::IsGLES());
if (IsCompressedFormat())
{
const u32 size = CalcUploadSize(m_map_height, pitch);
if (IsTextureArray())
{
glCompressedTexSubImage3D(target, m_map_level, m_map_x, m_map_y, m_map_layer, m_map_width, m_map_height, 1,
gl_format, size, reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
}
else
{
glCompressedTexSubImage2D(target, m_map_level, m_map_x, m_map_y, m_map_width, m_map_height, gl_format, size,
reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
}
}
else
{
if (IsTextureArray())
{
glTexSubImage3D(target, m_map_level, m_map_x, m_map_y, m_map_layer, m_map_width, m_map_height, 1, gl_format,
@ -364,6 +466,7 @@ void OpenGLTexture::Unmap()
glTexSubImage2D(target, m_map_level, m_map_x, m_map_y, m_map_width, m_map_height, gl_format, gl_type,
reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
}
}
glPixelStorei(GL_UNPACK_ROW_LENGTH, 0);
@ -372,6 +475,16 @@ void OpenGLTexture::Unmap()
sb->Unbind();
}
void OpenGLTexture::GenerateMipmaps()
{
DebugAssert(HasFlag(Flags::AllowGenerateMipmaps));
OpenGLDevice::BindUpdateTextureUnit();
const GLenum target = GetGLTarget();
glBindTexture(target, m_id);
glGenerateMipmap(target);
glBindTexture(target, 0);
}
void OpenGLTexture::SetDebugName(std::string_view name)
{
#ifdef _DEBUG
@ -405,7 +518,7 @@ void OpenGLSampler::SetDebugName(std::string_view name)
#endif
}
std::unique_ptr<GPUSampler> OpenGLDevice::CreateSampler(const GPUSampler::Config& config)
std::unique_ptr<GPUSampler> OpenGLDevice::CreateSampler(const GPUSampler::Config& config, Error* error /* = nullptr */)
{
static constexpr std::array<GLenum, static_cast<u8>(GPUSampler::AddressMode::MaxCount)> ta = {{
GL_REPEAT, // Repeat
@ -433,7 +546,7 @@ std::unique_ptr<GPUSampler> OpenGLDevice::CreateSampler(const GPUSampler::Config
glGenSamplers(1, &sampler);
if (glGetError() != GL_NO_ERROR)
{
ERROR_LOG("Failed to create sampler: {:X}", sampler);
Error::SetStringFmt(error, "Failed to create sampler: {:X}", sampler);
return {};
}
@ -697,7 +810,7 @@ void OpenGLTextureBuffer::SetDebugName(std::string_view name)
}
std::unique_ptr<GPUTextureBuffer> OpenGLDevice::CreateTextureBuffer(GPUTextureBuffer::Format format,
u32 size_in_elements)
u32 size_in_elements, Error* error)
{
const bool use_ssbo = OpenGLDevice::GetInstance().GetFeatures().texture_buffers_emulated_with_ssbo;
const u32 buffer_size = GPUTextureBuffer::GetElementSize(format) * size_in_elements;
@ -708,13 +821,13 @@ std::unique_ptr<GPUTextureBuffer> OpenGLDevice::CreateTextureBuffer(GPUTextureBu
glGetInteger64v(GL_MAX_SHADER_STORAGE_BLOCK_SIZE, &max_ssbo_size);
if (static_cast<GLint64>(buffer_size) > max_ssbo_size)
{
ERROR_LOG("Buffer size of {} not supported, max is {}", buffer_size, max_ssbo_size);
Error::SetStringFmt(error, "Buffer size of {} not supported, max is {}", buffer_size, max_ssbo_size);
return {};
}
}
const GLenum target = (use_ssbo ? GL_SHADER_STORAGE_BUFFER : GL_TEXTURE_BUFFER);
std::unique_ptr<OpenGLStreamBuffer> buffer = OpenGLStreamBuffer::Create(target, buffer_size);
std::unique_ptr<OpenGLStreamBuffer> buffer = OpenGLStreamBuffer::Create(target, buffer_size, error);
if (!buffer)
return {};
buffer->Unbind();
@ -726,7 +839,7 @@ std::unique_ptr<GPUTextureBuffer> OpenGLDevice::CreateTextureBuffer(GPUTextureBu
glGenTextures(1, &texture_id);
if (const GLenum err = glGetError(); err != GL_NO_ERROR)
{
ERROR_LOG("Failed to create texture for buffer: 0x{:X}", err);
Error::SetStringFmt(error, "Failed to create texture for buffer: 0x{:X}", err);
return {};
}
@ -772,7 +885,8 @@ OpenGLDownloadTexture::~OpenGLDownloadTexture()
}
std::unique_ptr<OpenGLDownloadTexture> OpenGLDownloadTexture::Create(u32 width, u32 height, GPUTexture::Format format,
void* memory, size_t memory_size, u32 memory_pitch)
void* memory, size_t memory_size, u32 memory_pitch,
Error* error)
{
const u32 buffer_pitch =
memory ? memory_pitch :
@ -801,7 +915,7 @@ std::unique_ptr<OpenGLDownloadTexture> OpenGLDownloadTexture::Create(u32 width,
if (!buffer_map)
{
ERROR_LOG("Failed to map persistent download buffer");
Error::SetStringView(error, "Failed to map persistent download buffer");
glDeleteBuffers(1, &buffer_id);
return {};
}
@ -814,8 +928,11 @@ std::unique_ptr<OpenGLDownloadTexture> OpenGLDownloadTexture::Create(u32 width,
const bool imported = (memory != nullptr);
u8* cpu_buffer =
imported ? static_cast<u8*>(memory) : static_cast<u8*>(Common::AlignedMalloc(buffer_size, VECTOR_ALIGNMENT));
if (!cpu_buffer)
if (!cpu_buffer) [[unlikely]]
{
Error::SetStringView(error, "Failed to get client-side memory pointer.");
return {};
}
return std::unique_ptr<OpenGLDownloadTexture>(
new OpenGLDownloadTexture(width, height, format, imported, 0, cpu_buffer, buffer_size, cpu_buffer, buffer_pitch));
@ -929,16 +1046,17 @@ void OpenGLDownloadTexture::SetDebugName(std::string_view name)
glObjectLabel(GL_BUFFER, m_buffer_id, static_cast<GLsizei>(name.length()), name.data());
}
std::unique_ptr<GPUDownloadTexture> OpenGLDevice::CreateDownloadTexture(u32 width, u32 height,
GPUTexture::Format format)
std::unique_ptr<GPUDownloadTexture>
OpenGLDevice::CreateDownloadTexture(u32 width, u32 height, GPUTexture::Format format, Error* error /* = nullptr */)
{
return OpenGLDownloadTexture::Create(width, height, format, nullptr, 0, 0);
return OpenGLDownloadTexture::Create(width, height, format, nullptr, 0, 0, error);
}
std::unique_ptr<GPUDownloadTexture> OpenGLDevice::CreateDownloadTexture(u32 width, u32 height,
GPUTexture::Format format, void* memory,
size_t memory_size, u32 memory_stride)
size_t memory_size, u32 memory_stride,
Error* error /* = nullptr */)
{
// not _really_ memory importing, but PBOs are broken on Intel....
return OpenGLDownloadTexture::Create(width, height, format, memory, memory_size, memory_stride);
return OpenGLDownloadTexture::Create(width, height, format, memory, memory_size, memory_stride, error);
}

View File

@ -28,11 +28,13 @@ public:
bool Update(u32 x, u32 y, u32 width, u32 height, const void* data, u32 pitch, u32 layer = 0, u32 level = 0) override;
bool Map(void** map, u32* map_stride, u32 x, u32 y, u32 width, u32 height, u32 layer = 0, u32 level = 0) override;
void Unmap() override;
void GenerateMipmaps() override;
void SetDebugName(std::string_view name) override;
static std::unique_ptr<OpenGLTexture> Create(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type,
Format format, const void* data = nullptr, u32 data_pitch = 0);
Format format, Flags flags, const void* data, u32 data_pitch,
Error* error);
bool UseTextureStorage() const;
@ -46,7 +48,8 @@ public:
OpenGLTexture& operator=(const OpenGLTexture&) = delete;
private:
OpenGLTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, GLuint id);
OpenGLTexture(u32 width, u32 height, u32 layers, u32 levels, u32 samples, Type type, Format format, Flags flags,
GLuint id);
GLuint m_id = 0;
@ -108,7 +111,7 @@ public:
~OpenGLDownloadTexture() override;
static std::unique_ptr<OpenGLDownloadTexture> Create(u32 width, u32 height, GPUTexture::Format format, void* memory,
size_t memory_size, u32 memory_pitch);
size_t memory_size, u32 memory_pitch, Error* error);
void CopyFromTexture(u32 dst_x, u32 dst_y, GPUTexture* src, u32 src_x, u32 src_y, u32 width, u32 height,
u32 src_layer, u32 src_level, bool use_transfer_pitch) override;

View File

@ -564,10 +564,12 @@ bool PostProcessing::Chain::CheckTargets(GPUTexture::Format target_format, u32 t
// In case any allocs fail.
DestroyTextures();
if (!(m_input_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1,
GPUTexture::Type::RenderTarget, target_format)) ||
!(m_output_texture = g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1,
GPUTexture::Type::RenderTarget, target_format)))
if (!(m_input_texture =
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
target_format, GPUTexture::Flags::None)) ||
!(m_output_texture =
g_gpu_device->FetchTexture(target_width, target_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
target_format, GPUTexture::Flags::None)))
{
DestroyTextures();
return false;
@ -806,7 +808,7 @@ GPUTexture* PostProcessing::GetDummyTexture()
const u32 zero = 0;
s_dummy_texture = g_gpu_device->FetchTexture(1, 1, 1, 1, 1, GPUTexture::Type::Texture, GPUTexture::Format::RGBA8,
&zero, sizeof(zero));
GPUTexture::Flags::None, &zero, sizeof(zero));
if (!s_dummy_texture)
ERROR_LOG("Failed to create dummy texture.");

View File

@ -50,7 +50,7 @@ static RenderAPI GetRenderAPI()
static bool PreprocessorFileExistsCallback(const std::string& path)
{
if (Path::IsAbsolute(path))
return FileSystem::FileExists(path.c_str());
return FileSystem::FileExists(Path::ToNativePath(path).c_str());
return Host::ResourceFileExists(path.c_str(), true);
}
@ -59,7 +59,7 @@ static bool PreprocessorReadFileCallback(const std::string& path, std::string& d
{
std::optional<std::string> rdata;
if (Path::IsAbsolute(path))
rdata = FileSystem::ReadFileToString(path.c_str());
rdata = FileSystem::ReadFileToString(Path::ToNativePath(path).c_str());
else
rdata = Host::ReadResourceFileToString(path.c_str(), true);
if (!rdata.has_value())
@ -1121,7 +1121,7 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
return false;
}
RGBA8Image image;
Image image;
if (const std::string image_path =
Path::Combine(EmuFolders::Shaders, Path::Combine("reshade" FS_OSPATH_SEPARATOR_STR "Textures", source));
!image.LoadFromFile(image_path.c_str()))
@ -1138,12 +1138,10 @@ bool PostProcessing::ReShadeFXShader::CreatePasses(GPUTexture::Format backbuffer
tex.rt_scale = 0.0f;
tex.texture = g_gpu_device->FetchTexture(image.GetWidth(), image.GetHeight(), 1, 1, 1, GPUTexture::Type::Texture,
GPUTexture::Format::RGBA8, image.GetPixels(), image.GetPitch());
GPUTexture::Format::RGBA8, GPUTexture::Flags::None, image.GetPixels(),
image.GetPitch(), error);
if (!tex.texture)
{
Error::SetStringFmt(error, "Failed to create {}x{} texture ({})", image.GetWidth(), image.GetHeight(), source);
return false;
}
DEV_LOG("Loaded {}x{} texture ({})", image.GetWidth(), image.GetHeight(), source);
}
@ -1457,13 +1455,11 @@ bool PostProcessing::ReShadeFXShader::ResizeOutput(GPUTexture::Format format, u3
const u32 t_width = std::max(static_cast<u32>(static_cast<float>(width) * tex.rt_scale), 1u);
const u32 t_height = std::max(static_cast<u32>(static_cast<float>(height) * tex.rt_scale), 1u);
tex.texture = g_gpu_device->FetchTexture(t_width, t_height, 1, 1, 1, GPUTexture::Type::RenderTarget, tex.format);
tex.texture = g_gpu_device->FetchTexture(t_width, t_height, 1, 1, 1, GPUTexture::Type::RenderTarget, tex.format,
GPUTexture::Flags::None);
if (!tex.texture)
{
ERROR_LOG("Failed to create {}x{} texture", t_width, t_height);
return {};
}
}
m_valid = true;
return true;

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,198 @@
// See TextureDecompress.cpp for license info.
#pragma once
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable:4201) // nonstandard extension used: nameless struct/union
#endif
#include <stdlib.h>
#include <stdint.h>
#include <algorithm>
#include <math.h>
#include <assert.h>
enum BC4Mode
{
BC4_UNORM = 0,
BC4_SNORM = 1
};
enum BC5Mode
{
BC5_UNORM = 0,
BC5_SNORM = 1
};
void DecompressBlockBC1(uint32_t x, uint32_t y, uint32_t stride,
const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC2(uint32_t x, uint32_t y, uint32_t stride,
const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC3(uint32_t x, uint32_t y, uint32_t stride,
const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC4(uint32_t x, uint32_t y, uint32_t stride,
enum BC4Mode mode, const uint8_t* blockStorage, unsigned char* image);
void DecompressBlockBC5(uint32_t x, uint32_t y, uint32_t stride,
enum BC5Mode mode, const uint8_t* blockStorage, unsigned char* image);
namespace bc7decomp
{
enum eNoClamp { cNoClamp };
template <typename S> inline S clamp(S value, S low, S high) { return (value < low) ? low : ((value > high) ? high : value); }
class color_rgba
{
public:
union
{
uint8_t m_comps[4];
struct
{
uint8_t r;
uint8_t g;
uint8_t b;
uint8_t a;
};
};
inline color_rgba() = default;
inline color_rgba(int y)
{
set(y);
}
inline color_rgba(int y, int na)
{
set(y, na);
}
inline color_rgba(int sr, int sg, int sb, int sa)
{
set(sr, sg, sb, sa);
}
inline color_rgba(eNoClamp, int sr, int sg, int sb, int sa)
{
set_noclamp_rgba((uint8_t)sr, (uint8_t)sg, (uint8_t)sb, (uint8_t)sa);
}
inline color_rgba& set_noclamp_y(int y)
{
m_comps[0] = (uint8_t)y;
m_comps[1] = (uint8_t)y;
m_comps[2] = (uint8_t)y;
m_comps[3] = (uint8_t)255;
return *this;
}
inline color_rgba &set_noclamp_rgba(int sr, int sg, int sb, int sa)
{
m_comps[0] = (uint8_t)sr;
m_comps[1] = (uint8_t)sg;
m_comps[2] = (uint8_t)sb;
m_comps[3] = (uint8_t)sa;
return *this;
}
inline color_rgba &set(int y)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
m_comps[1] = m_comps[0];
m_comps[2] = m_comps[0];
m_comps[3] = 255;
return *this;
}
inline color_rgba &set(int y, int na)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(y, 0, 255));
m_comps[1] = m_comps[0];
m_comps[2] = m_comps[0];
m_comps[3] = static_cast<uint8_t>(clamp<int>(na, 0, 255));
return *this;
}
inline color_rgba &set(int sr, int sg, int sb, int sa)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
m_comps[3] = static_cast<uint8_t>(clamp<int>(sa, 0, 255));
return *this;
}
inline color_rgba &set_rgb(int sr, int sg, int sb)
{
m_comps[0] = static_cast<uint8_t>(clamp<int>(sr, 0, 255));
m_comps[1] = static_cast<uint8_t>(clamp<int>(sg, 0, 255));
m_comps[2] = static_cast<uint8_t>(clamp<int>(sb, 0, 255));
return *this;
}
inline color_rgba &set_rgb(const color_rgba &other)
{
r = other.r;
g = other.g;
b = other.b;
return *this;
}
inline const uint8_t &operator[] (uint32_t index) const { assert(index < 4); return m_comps[index]; }
inline uint8_t &operator[] (uint32_t index) { assert(index < 4); return m_comps[index]; }
inline void clear()
{
m_comps[0] = 0;
m_comps[1] = 0;
m_comps[2] = 0;
m_comps[3] = 0;
}
inline bool operator== (const color_rgba &rhs) const
{
if (m_comps[0] != rhs.m_comps[0]) return false;
if (m_comps[1] != rhs.m_comps[1]) return false;
if (m_comps[2] != rhs.m_comps[2]) return false;
if (m_comps[3] != rhs.m_comps[3]) return false;
return true;
}
inline bool operator!= (const color_rgba &rhs) const
{
return !(*this == rhs);
}
inline bool operator<(const color_rgba &rhs) const
{
for (int i = 0; i < 4; i++)
{
if (m_comps[i] < rhs.m_comps[i])
return true;
else if (m_comps[i] != rhs.m_comps[i])
return false;
}
return false;
}
inline int get_601_luma() const { return (19595U * m_comps[0] + 38470U * m_comps[1] + 7471U * m_comps[2] + 32768U) >> 16U; }
inline int get_709_luma() const { return (13938U * m_comps[0] + 46869U * m_comps[1] + 4729U * m_comps[2] + 32768U) >> 16U; }
inline int get_luma(bool luma_601) const { return luma_601 ? get_601_luma() : get_709_luma(); }
static color_rgba comp_min(const color_rgba& a, const color_rgba& b) { return color_rgba(std::min(a[0], b[0]), std::min(a[1], b[1]), std::min(a[2], b[2]), std::min(a[3], b[3])); }
static color_rgba comp_max(const color_rgba& a, const color_rgba& b) { return color_rgba(std::max(a[0], b[0]), std::max(a[1], b[1]), std::max(a[2], b[2]), std::max(a[3], b[3])); }
};
static_assert(sizeof(color_rgba) == 4);
bool unpack_bc7(const void *pBlock, color_rgba *pPixels);
} // namespace bc7decomp
#ifdef _MSC_VER
#pragma warning(pop)
#endif

View File

@ -93,6 +93,7 @@
<ClInclude Include="shiftjis.h" />
<ClInclude Include="sockets.h" />
<ClInclude Include="state_wrapper.h" />
<ClInclude Include="texture_decompress.h" />
<ClInclude Include="vulkan_builders.h" />
<ClInclude Include="vulkan_device.h" />
<ClInclude Include="vulkan_entry_points.h" />
@ -199,6 +200,7 @@
<ClCompile Include="page_fault_handler.cpp" />
<ClCompile Include="sockets.cpp" />
<ClCompile Include="state_wrapper.cpp" />
<ClCompile Include="texture_decompress.cpp" />
<ClCompile Include="vulkan_builders.cpp" />
<ClCompile Include="vulkan_device.cpp" />
<ClCompile Include="vulkan_loader.cpp" />

View File

@ -75,6 +75,7 @@
<ClInclude Include="elf_file.h" />
<ClInclude Include="x11_tools.h" />
<ClInclude Include="opengl_context_egl_xlib.h" />
<ClInclude Include="texture_decompress.h" />
</ItemGroup>
<ItemGroup>
<ClCompile Include="state_wrapper.cpp" />
@ -156,6 +157,7 @@
<ClCompile Include="elf_file.cpp" />
<ClCompile Include="x11_tools.cpp" />
<ClCompile Include="opengl_context_egl_xlib.cpp" />
<ClCompile Include="texture_decompress.cpp" />
</ItemGroup>
<ItemGroup>
<None Include="metal_shaders.metal" />

Some files were not shown because too many files have changed in this diff Show More