Merge branch 'stenzek:master' into master
This commit is contained in:
commit
d549ce2f85
Before Width: | Height: | Size: 241 B After Width: | Height: | Size: 241 B |
|
@ -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 |
|
@ -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 |
|
@ -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
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::StatFile(std::FILE* fp, struct stat* st)
|
||||
bool FileSystem::StatFile(std::FILE* fp, struct stat* st, Error* error)
|
||||
{
|
||||
const int fd = fileno(fp);
|
||||
if (fd < 0)
|
||||
{
|
||||
Error::SetErrno(error, "fileno() failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return fstat(fd, st) == 0;
|
||||
if (fstat(fd, st) != 0)
|
||||
{
|
||||
Error::SetErrno(error, "fstat() failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
|
||||
bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd, Error* error)
|
||||
{
|
||||
// stat file
|
||||
struct stat sysStatData;
|
||||
if (stat(path, &sysStatData) < 0)
|
||||
{
|
||||
Error::SetErrno(error, "stat() failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse attributes
|
||||
sd->CreationTime = sysStatData.st_ctime;
|
||||
|
@ -2442,16 +2494,22 @@ bool FileSystem::StatFile(const char* path, FILESYSTEM_STAT_DATA* sd)
|
|||
return true;
|
||||
}
|
||||
|
||||
bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd)
|
||||
bool FileSystem::StatFile(std::FILE* fp, FILESYSTEM_STAT_DATA* sd, Error* error)
|
||||
{
|
||||
const int fd = fileno(fp);
|
||||
if (fd < 0)
|
||||
{
|
||||
Error::SetErrno(error, "fileno() failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// stat file
|
||||
struct stat sysStatData;
|
||||
if (fstat(fd, &sysStatData) < 0)
|
||||
if (fstat(fd, &sysStatData) != 0)
|
||||
{
|
||||
Error::SetErrno(error, "stat() failed: ", errno);
|
||||
return false;
|
||||
}
|
||||
|
||||
// parse attributes
|
||||
sd->CreationTime = sysStatData.st_ctime;
|
||||
|
@ -2636,13 +2694,13 @@ bool FileSystem::DeleteDirectory(const char* path)
|
|||
std::string FileSystem::GetProgramPath()
|
||||
{
|
||||
#if defined(__linux__)
|
||||
static const char* exeFileName = "/proc/self/exe";
|
||||
static const char* exe_path = "/proc/self/exe";
|
||||
|
||||
int curSize = PATH_MAX;
|
||||
char* buffer = static_cast<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);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -31,7 +31,6 @@
|
|||
X(GPUDevice) \
|
||||
X(GPUDump) \
|
||||
X(GPUShaderCache) \
|
||||
X(GPUTexture) \
|
||||
X(GPUTextureCache) \
|
||||
X(GPU_HW) \
|
||||
X(GPU_SW) \
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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();
|
||||
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
323
src/core/gpu.cpp
323
src/core/gpu.cpp
|
@ -74,10 +74,8 @@ static u32 s_active_gpu_cycles_frames = 0;
|
|||
|
||||
static constexpr GPUTexture::Format DISPLAY_INTERNAL_POSTFX_FORMAT = GPUTexture::Format::RGBA8;
|
||||
|
||||
static bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename, FileSystem::ManagedCFilePtr fp,
|
||||
u8 quality, bool clear_alpha, bool flip_y, std::vector<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,44 +594,91 @@ 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;
|
||||
}
|
||||
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::Auto)
|
||||
|
||||
// PAR 1:1 is not corrected.
|
||||
if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
|
||||
return static_cast<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)
|
||||
{
|
||||
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 (m_GPUSTAT.pal_mode)
|
||||
if (g_settings.display_aspect_ratio == DisplayAspectRatio::MatchWindow && g_gpu_device->HasMainSwapChain())
|
||||
{
|
||||
relative_width /= static_cast<float>(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START);
|
||||
relative_height /= static_cast<float>(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START);
|
||||
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::Custom)
|
||||
{
|
||||
ar = static_cast<float>(g_settings.display_aspect_ratio_custom_numerator) /
|
||||
static_cast<float>(g_settings.display_aspect_ratio_custom_denominator);
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
ar = g_settings.GetDisplayAspectRatioValue();
|
||||
}
|
||||
return (relative_width / relative_height) * (4.0f / 3.0f);
|
||||
}
|
||||
else if (g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1)
|
||||
{
|
||||
if (m_crtc_state.display_width == 0 || m_crtc_state.display_height == 0)
|
||||
return 4.0f / 3.0f;
|
||||
|
||||
return static_cast<float>(m_crtc_state.display_width) / static_cast<float>(m_crtc_state.display_height);
|
||||
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 || g_settings.display_aspect_ratio == DisplayAspectRatio::PAR1_1 ||
|
||||
g_settings.display_crop_mode == DisplayCropMode::OverscanUncorrected ||
|
||||
g_settings.display_crop_mode == DisplayCropMode::BordersUncorrected)
|
||||
{
|
||||
return 1.0f;
|
||||
}
|
||||
|
||||
if (m_GPUSTAT.pal_mode)
|
||||
{
|
||||
relative_width /= static_cast<float>(PAL_HORIZONTAL_ACTIVE_END - PAL_HORIZONTAL_ACTIVE_START);
|
||||
relative_height /= static_cast<float>(PAL_VERTICAL_ACTIVE_END - PAL_VERTICAL_ACTIVE_START);
|
||||
}
|
||||
else
|
||||
{
|
||||
return g_settings.GetDisplayAspectRatioValue();
|
||||
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);
|
||||
}
|
||||
|
||||
void GPU::ApplyPixelAspectRatioToSize(float* width, float* height) const
|
||||
{
|
||||
const float dar = ComputeDisplayAspectRatio();
|
||||
const float sar = ComputeSourceAspectRatio();
|
||||
const float par = dar / sar;
|
||||
|
||||
if (par < 1.0f)
|
||||
{
|
||||
// stretch height, preserve width
|
||||
*height = std::ceil(*height / par);
|
||||
}
|
||||
else
|
||||
{
|
||||
// stretch width, preserve height
|
||||
*width = std::ceil(*width * par);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -727,6 +772,10 @@ void GPU::UpdateCRTCDisplayParameters()
|
|||
(std::min<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;
|
||||
Error error;
|
||||
|
||||
const char* extension = std::strrchr(filename.c_str(), '.');
|
||||
if (extension)
|
||||
if (flip_y)
|
||||
image.FlipY();
|
||||
|
||||
if (image.GetFormat() != ImageFormat::RGBA8)
|
||||
{
|
||||
if (GPUTexture::ConvertTextureDataToRGBA8(width, height, texture_data, texture_data_stride, texture_format))
|
||||
std::optional<Image> convert_image = image.ConvertToRGBA8(&error);
|
||||
if (!convert_image.has_value())
|
||||
{
|
||||
if (clear_alpha)
|
||||
{
|
||||
for (u32& pixel : texture_data)
|
||||
pixel |= 0xFF000000u;
|
||||
}
|
||||
|
||||
if (flip_y)
|
||||
GPUTexture::FlipTextureDataRGBA8(width, height, reinterpret_cast<u8*>(texture_data.data()),
|
||||
texture_data_stride);
|
||||
|
||||
Assert(texture_data_stride == sizeof(u32) * width);
|
||||
RGBA8Image image(width, height, std::move(texture_data));
|
||||
if (image.SaveToFile(filename.c_str(), fp.get(), quality))
|
||||
{
|
||||
result = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
ERROR_LOG("Unknown extension in filename '{}' or save error: '{}'", filename, extension);
|
||||
result = false;
|
||||
}
|
||||
ERROR_LOG("Failed to convert {} screenshot to RGBA8: {}", Image::GetFormatName(image.GetFormat()),
|
||||
error.GetDescription());
|
||||
image.Invalidate();
|
||||
}
|
||||
else
|
||||
{
|
||||
result = false;
|
||||
image = std::move(convert_image.value());
|
||||
}
|
||||
}
|
||||
else
|
||||
|
||||
bool result = false;
|
||||
if (image.IsValid())
|
||||
{
|
||||
ERROR_LOG("Unable to determine file extension for '{}'", filename);
|
||||
result = false;
|
||||
if (clear_alpha)
|
||||
image.SetAllPixelsOpaque();
|
||||
|
||||
result = image.SaveToFile(path.c_str(), fp.get(), quality, &error);
|
||||
if (!result)
|
||||
ERROR_LOG("Failed to save screenshot to '{}': '{}'", Path::GetFileName(path), error.GetDescription());
|
||||
}
|
||||
|
||||
if (!osd_key.empty())
|
||||
|
@ -2470,7 +2522,7 @@ bool CompressAndWriteTextureToFile(u32 width, u32 height, std::string filename,
|
|||
Host::AddIconOSDMessage(std::move(osd_key), ICON_EMOJI_CAMERA,
|
||||
fmt::format(result ? TRANSLATE_FS("GPU", "Saved screenshot to '{}'.") :
|
||||
TRANSLATE_FS("GPU", "Failed to save screenshot to '{}'."),
|
||||
Path::GetFileName(filename),
|
||||
Path::GetFileName(path),
|
||||
result ? Host::OSD_INFO_DURATION : Host::OSD_ERROR_DURATION));
|
||||
}
|
||||
|
||||
|
@ -2486,17 +2538,16 @@ bool GPU::WriteDisplayTextureToFile(std::string filename)
|
|||
const u32 read_y = static_cast<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;
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -822,11 +822,34 @@ uint2 FloatToIntegerCoords(float2 coords)
|
|||
return uint2((UPSCALED == 0 || FORCE_ROUND_TEXCOORDS != 0) ? roundEven(coords) : floor(coords));
|
||||
}
|
||||
|
||||
#if !PAGE_TEXTURE
|
||||
#if PAGE_TEXTURE
|
||||
|
||||
float4 SampleFromPageTexture(float2 coords)
|
||||
{
|
||||
// Cached textures.
|
||||
uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
|
||||
#if UPSCALED
|
||||
float2 fpart = frac(coords);
|
||||
coords = (float2(icoord) + fpart);
|
||||
#else
|
||||
// Drop fractional part.
|
||||
coords = float2(icoord);
|
||||
#endif
|
||||
|
||||
// Normalize.
|
||||
coords = coords * (1.0f / 256.0f);
|
||||
return SAMPLE_TEXTURE(samp0, coords);
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if !PAGE_TEXTURE || TEXTURE_FILTERING
|
||||
|
||||
float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
|
||||
{
|
||||
#if PALETTE
|
||||
#if PAGE_TEXTURE
|
||||
return SampleFromPageTexture(coords);
|
||||
#elif PALETTE
|
||||
uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
|
||||
|
||||
uint2 vicoord;
|
||||
|
@ -875,22 +898,7 @@ float4 SampleFromVRAM(TEXPAGE_VALUE texpage, float2 coords)
|
|||
#endif
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
float4 SampleFromPageTexture(float2 coords)
|
||||
{
|
||||
// Cached textures.
|
||||
#if UPSCALED == 0
|
||||
float2 fpart = coords - roundEven(coords);
|
||||
#else
|
||||
float2 fpart = frac(coords);
|
||||
#endif
|
||||
uint2 icoord = ApplyTextureWindow(FloatToIntegerCoords(coords));
|
||||
coords = (float2(icoord) + fpart) * (1.0f / 256.0f);
|
||||
return SAMPLE_TEXTURE(samp0, coords);
|
||||
}
|
||||
|
||||
#endif
|
||||
#endif // !PAGE_TEXTURE || TEXTURE_FILTERING
|
||||
|
||||
#endif // TEXTURED
|
||||
)";
|
||||
|
@ -898,6 +906,9 @@ float4 SampleFromPageTexture(float2 coords)
|
|||
const u32 num_fragment_outputs = use_rov ? 0 : (use_dual_source ? 2 : 1);
|
||||
if (textured && page_texture)
|
||||
{
|
||||
if (texture_filtering != GPUTextureFilter::Nearest)
|
||||
WriteBatchTextureFilter(ss, texture_filtering);
|
||||
|
||||
if (uv_limits)
|
||||
{
|
||||
DeclareFragmentEntryPoint(ss, 1, 1, {{"nointerpolation", "float4 v_uv_limits"}}, true, num_fragment_outputs,
|
||||
|
@ -956,7 +967,7 @@ float4 SampleFromPageTexture(float2 coords)
|
|||
|
||||
#if TEXTURED
|
||||
float4 texcol;
|
||||
#if PAGE_TEXTURE
|
||||
#if PAGE_TEXTURE && !TEXTURE_FILTERING
|
||||
#if UV_LIMITS
|
||||
texcol = SampleFromPageTexture(clamp(v_tex0, v_uv_limits.xy, v_uv_limits.zw));
|
||||
#else
|
||||
|
@ -967,7 +978,11 @@ float4 SampleFromPageTexture(float2 coords)
|
|||
|
||||
ialpha = 1.0;
|
||||
#elif TEXTURE_FILTERING
|
||||
FilteredSampleFromVRAM(v_texpage, v_tex0, v_uv_limits, texcol, ialpha);
|
||||
#if PAGE_TEXTURE
|
||||
FilteredSampleFromVRAM(int2(0, 0), v_tex0, v_uv_limits, texcol, ialpha);
|
||||
#else
|
||||
FilteredSampleFromVRAM(v_texpage, v_tex0, v_uv_limits, texcol, ialpha);
|
||||
#endif
|
||||
if (ialpha < 0.5)
|
||||
discard;
|
||||
#else
|
||||
|
@ -1269,6 +1284,22 @@ float3 SampleVRAM24(uint2 icoords)
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMReplacementBlitFragmentShader() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DeclareTexture(ss, "samp0", 0);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1);
|
||||
|
||||
ss << R"(
|
||||
{
|
||||
o_col0 = SAMPLE_TEXTURE(samp0, v_tex0);
|
||||
}
|
||||
)";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateWireframeGeometryShader() const
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
@ -1422,14 +1453,17 @@ uint SampleVRAM(uint2 coords)
|
|||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, bool use_ssbo,
|
||||
bool write_mask_as_depth) const
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, bool use_ssbo, bool write_mask_as_depth,
|
||||
bool write_depth_as_rt) const
|
||||
{
|
||||
Assert(!write_mask_as_depth || (write_mask_as_depth != write_depth_as_rt));
|
||||
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
WriteColorConversionFunctions(ss);
|
||||
|
||||
DefineMacro(ss, "WRITE_MASK_AS_DEPTH", write_mask_as_depth);
|
||||
DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
|
||||
DefineMacro(ss, "USE_BUFFER", use_buffer);
|
||||
|
||||
ss << "CONSTANT float2 VRAM_SIZE = float2(" << VRAM_WIDTH << ".0, " << VRAM_HEIGHT << ".0);\n";
|
||||
|
@ -1465,7 +1499,7 @@ std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, b
|
|||
ss << "#define GET_VALUE(buffer_offset) (LOAD_TEXTURE_BUFFER(samp0, int(buffer_offset)).r)\n\n";
|
||||
}
|
||||
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, write_mask_as_depth);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1 + BoolToUInt32(write_depth_as_rt), false, write_mask_as_depth);
|
||||
ss << R"(
|
||||
{
|
||||
float2 coords = floor(v_pos.xy / u_resolution_scale);
|
||||
|
@ -1492,20 +1526,25 @@ std::string GPU_HW_ShaderGen::GenerateVRAMWriteFragmentShader(bool use_buffer, b
|
|||
o_col0 = RGBA5551ToRGBA8(value);
|
||||
#if WRITE_MASK_AS_DEPTH
|
||||
o_depth = (o_col0.a == 1.0) ? u_depth_value : 0.0;
|
||||
#elif WRITE_DEPTH_AS_RT
|
||||
o_col1 = float4(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
#endif
|
||||
})";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_depth) const
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_depth, bool write_depth_as_rt) const
|
||||
{
|
||||
Assert(!write_mask_as_depth || (write_mask_as_depth != write_depth_as_rt));
|
||||
|
||||
// TODO: This won't currently work because we can't bind the texture to both the shader and framebuffer.
|
||||
const bool msaa = false;
|
||||
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DefineMacro(ss, "WRITE_MASK_AS_DEPTH", write_mask_as_depth);
|
||||
DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
|
||||
DefineMacro(ss, "MSAA_COPY", msaa);
|
||||
|
||||
DeclareUniformBuffer(ss,
|
||||
|
@ -1514,7 +1553,8 @@ std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_
|
|||
true);
|
||||
|
||||
DeclareTexture(ss, "samp0", 0, msaa);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1, false, write_mask_as_depth, false, false, msaa);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, true, 1 + BoolToUInt32(write_depth_as_rt), false, write_mask_as_depth, false,
|
||||
false, msaa);
|
||||
ss << R"(
|
||||
{
|
||||
float2 dst_coords = floor(v_pos.xy);
|
||||
|
@ -1544,25 +1584,31 @@ std::string GPU_HW_ShaderGen::GenerateVRAMCopyFragmentShader(bool write_mask_as_
|
|||
o_col0 = float4(color.xyz, u_set_mask_bit ? 1.0 : color.a);
|
||||
#if WRITE_MASK_AS_DEPTH
|
||||
o_depth = (u_set_mask_bit ? 1.0f : ((o_col0.a == 1.0) ? u_depth_value : 0.0));
|
||||
#elif WRITE_DEPTH_AS_RT
|
||||
o_col1 = float4(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
#endif
|
||||
})";
|
||||
|
||||
return ss.str();
|
||||
}
|
||||
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced,
|
||||
bool write_mask_as_depth) const
|
||||
std::string GPU_HW_ShaderGen::GenerateVRAMFillFragmentShader(bool wrapped, bool interlaced, bool write_mask_as_depth,
|
||||
bool write_depth_as_rt) const
|
||||
{
|
||||
Assert(!write_mask_as_depth || (write_mask_as_depth != write_depth_as_rt));
|
||||
|
||||
std::stringstream ss;
|
||||
WriteHeader(ss);
|
||||
DefineMacro(ss, "WRITE_MASK_AS_DEPTH", write_mask_as_depth);
|
||||
DefineMacro(ss, "WRITE_DEPTH_AS_RT", write_depth_as_rt);
|
||||
DefineMacro(ss, "WRAPPED", wrapped);
|
||||
DefineMacro(ss, "INTERLACED", interlaced);
|
||||
|
||||
DeclareUniformBuffer(
|
||||
ss, {"uint2 u_dst_coords", "uint2 u_end_coords", "float4 u_fill_color", "uint u_interlaced_displayed_field"}, true);
|
||||
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, interlaced || wrapped, 1, false, write_mask_as_depth, false, false, false);
|
||||
DeclareFragmentEntryPoint(ss, 0, 1, {}, interlaced || wrapped, 1 + BoolToUInt32(write_depth_as_rt), false,
|
||||
write_mask_as_depth, false, false, false);
|
||||
ss << R"(
|
||||
{
|
||||
#if INTERLACED || WRAPPED
|
||||
|
@ -1586,6 +1632,8 @@ std::string GPU_HW_ShaderGen::GenerateVRAMFillFragmentShader(bool wrapped, bool
|
|||
o_col0 = u_fill_color;
|
||||
#if WRITE_MASK_AS_DEPTH
|
||||
o_depth = u_fill_color.a;
|
||||
#elif WRITE_DEPTH_AS_RT
|
||||
o_col1 = float4(1.0f, 0.0f, 0.0f, 0.0f);
|
||||
#endif
|
||||
})";
|
||||
|
||||
|
@ -1811,6 +1859,9 @@ std::string GPU_HW_ShaderGen::GenerateReplacementMergeFragmentShader(bool semitr
|
|||
#else
|
||||
// Leave (0,0,0,0) as 0000 for opaque replacements for cutout alpha.
|
||||
o_col0.a = color.a;
|
||||
|
||||
// Map anything with an alpha below 0.5 to transparent.
|
||||
o_col0 = lerp(o_col0, float4(0.0, 0.0, 0.0, 0.0), float(o_col0.a < 0.5));
|
||||
#endif
|
||||
}
|
||||
)";
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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();
|
||||
|
||||
for (const auto& it : s_state.vram_replacements)
|
||||
{
|
||||
const auto it2 = old_map.find(it.second);
|
||||
const auto reinsert_texture = [&old_map, &old_gpu_map](const std::string& name) {
|
||||
const auto it2 = old_map.find(name);
|
||||
if (it2 != old_map.end())
|
||||
{
|
||||
s_state.replacement_image_cache[it.second] = std::move(it2->second);
|
||||
s_state.replacement_image_cache.emplace(name, std::move(it2->second));
|
||||
old_map.erase(it2);
|
||||
}
|
||||
}
|
||||
|
||||
for (const auto& map : {s_state.vram_write_texture_replacements, s_state.texture_page_texture_replacements})
|
||||
{
|
||||
for (const auto& it : map)
|
||||
const auto it3 = old_gpu_map.find(name);
|
||||
if (it3 != old_gpu_map.end())
|
||||
{
|
||||
const auto it2 = old_map.find(it.second.second);
|
||||
if (it2 != old_map.end())
|
||||
{
|
||||
s_state.replacement_image_cache[it.second.second] = std::move(it2->second);
|
||||
old_map.erase(it2);
|
||||
}
|
||||
s_state.gpu_replacement_image_cache.emplace(name, std::move(it3->second));
|
||||
old_gpu_map.erase(it3);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
for (const auto& it : s_state.vram_replacements)
|
||||
reinsert_texture(it.second);
|
||||
|
||||
for (const auto& it : s_state.vram_write_texture_replacements)
|
||||
reinsert_texture(it.second.second);
|
||||
|
||||
for (const auto& it : s_state.texture_page_texture_replacements)
|
||||
reinsert_texture(it.second.second);
|
||||
}
|
||||
|
||||
void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash, HashType pal_hash,
|
||||
|
@ -3285,8 +3404,9 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
|
|||
{
|
||||
// NOTE: Not recycled, it's unlikely to be reused.
|
||||
s_state.replacement_texture_render_target.reset();
|
||||
if (!(s_state.replacement_texture_render_target = g_gpu_device->CreateTexture(
|
||||
new_width, new_height, 1, 1, 1, GPUTexture::Type::RenderTarget, REPLACEMENT_TEXTURE_FORMAT)))
|
||||
if (!(s_state.replacement_texture_render_target =
|
||||
g_gpu_device->CreateTexture(new_width, new_height, 1, 1, 1, GPUTexture::Type::RenderTarget,
|
||||
REPLACEMENT_TEXTURE_FORMAT, GPUTexture::Flags::None)))
|
||||
{
|
||||
ERROR_LOG("Failed to create {}x{} render target.", new_width, new_height);
|
||||
return;
|
||||
|
@ -3294,8 +3414,8 @@ void GPUTextureCache::ApplyTextureReplacements(SourceKey key, HashType tex_hash,
|
|||
}
|
||||
|
||||
// Grab the actual texture beforehand, in case we OOM.
|
||||
std::unique_ptr<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);
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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>();
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -139,27 +139,16 @@ 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)
|
||||
{
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
Host::RemoveKeyedOSDMessage("PauseCooldown");
|
||||
s_last_pause_time = time;
|
||||
|
||||
return true;
|
||||
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(seconds))),
|
||||
std::max(seconds, Host::OSD_QUICK_DURATION));
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
|
|
@ -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");
|
||||
}
|
||||
|
|
|
@ -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())
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
@ -1691,28 +1729,7 @@ 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)];
|
||||
}
|
||||
}
|
||||
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"};
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -5,4 +5,4 @@
|
|||
|
||||
#include "common/types.h"
|
||||
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 20;
|
||||
static constexpr u32 SHADER_CACHE_VERSION = 22;
|
||||
|
|
|
@ -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];
|
||||
|
|
|
@ -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)))
|
||||
{
|
||||
WARNING_LOG("Failed to save {}x{} screenshot for save state due to incorrect stride({})", screenshot_width,
|
||||
screenshot_height, screenshot_stride);
|
||||
}
|
||||
else
|
||||
{
|
||||
if (g_gpu_device->UsesLowerLeftOrigin())
|
||||
{
|
||||
GPUTexture::FlipTextureDataRGBA8(screenshot_width, screenshot_height,
|
||||
reinterpret_cast<u8*>(screenshot_buffer.data()), screenshot_stride);
|
||||
}
|
||||
if (g_gpu_device->UsesLowerLeftOrigin())
|
||||
buffer->screenshot.FlipY();
|
||||
|
||||
buffer->screenshot.SetPixels(screenshot_width, screenshot_height, std::move(screenshot_buffer));
|
||||
// Ensure it's RGBA8.
|
||||
if (buffer->screenshot.GetFormat() != ImageFormat::RGBA8)
|
||||
{
|
||||
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
|
||||
{
|
||||
buffer->screenshot = std::move(screenshot_rgba8.value());
|
||||
}
|
||||
}
|
||||
}
|
||||
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));
|
||||
Host::RequestResizeHostDisplay(static_cast<s32>(std::ceil(requested_width)),
|
||||
static_cast<s32>(std::ceil(requested_height)));
|
||||
}
|
||||
|
||||
void System::DisplayWindowResized(u32 width, u32 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.
|
||||
|
|
|
@ -74,7 +74,7 @@ struct ExtendedSaveStateInfo
|
|||
std::string media_path;
|
||||
std::time_t timestamp;
|
||||
|
||||
RGBA8Image screenshot;
|
||||
Image screenshot;
|
||||
};
|
||||
|
||||
namespace System {
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -141,8 +141,10 @@ enum class DisplayCropMode : u8
|
|||
{
|
||||
None,
|
||||
Overscan,
|
||||
OverscanUncorrected,
|
||||
Borders,
|
||||
Count
|
||||
BordersUncorrected,
|
||||
MaxCount
|
||||
};
|
||||
|
||||
enum class DisplayAspectRatio : u8
|
||||
|
|
|
@ -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,23 +794,18 @@ 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));
|
||||
return false;
|
||||
}
|
||||
zip_file.close();
|
||||
reportError(fmt::format("Writing update zip to '{}' failed:\n{}", zip_path, error.GetDescription()));
|
||||
return false;
|
||||
}
|
||||
|
||||
INFO_LOG("Beginning update:\nUpdater path: {}\nZip path: {}\nStaging directory: {}\nOutput directory: {}",
|
||||
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -27,6 +27,7 @@ public:
|
|||
void reloadGameSettings();
|
||||
|
||||
private Q_SLOTS:
|
||||
void onCustomLanguageChanged(int language);
|
||||
void onCompatibilityCommentsClicked();
|
||||
void onInputProfileChanged(int index);
|
||||
void onEditInputProfileClicked();
|
||||
|
|
|
@ -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>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QPushButton" name="restoreTitle">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Restore</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>
|
||||
<property name="text">
|
||||
<string>Restore</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Image Path:</string>
|
||||
<item row="13" column="1">
|
||||
<widget class="QLineEdit" name="controllers">
|
||||
<property name="readOnly">
|
||||
<bool>true</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="0" column="1">
|
||||
<widget class="QLineEdit" name="path">
|
||||
<item row="17" column="0">
|
||||
<widget class="QLabel" name="label_8">
|
||||
<property name="text">
|
||||
<string>Tracks:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="14" column="0">
|
||||
<widget class="QLabel" name="label_9">
|
||||
<property name="text">
|
||||
<string>Input Profile:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<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>
|
||||
</property>
|
||||
</widget>
|
||||
<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>
|
||||
<widget class="QPushButton" name="restoreTitle">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Restore</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</item>
|
||||
<item row="5" column="0">
|
||||
<widget class="QLabel" name="label_10">
|
||||
<item row="0" column="0">
|
||||
<widget class="QLabel" name="label_4">
|
||||
<property name="text">
|
||||
<string>Type:</string>
|
||||
<string>Image Path:</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="3" column="0">
|
||||
<widget class="QLabel" name="label_2">
|
||||
<property name="text">
|
||||
<string>Title:</string>
|
||||
<item row="5" column="1">
|
||||
<widget class="QComboBox" name="entryType">
|
||||
<property name="enabled">
|
||||
<bool>false</bool>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item row="11" column="1">
|
||||
<widget class="QLineEdit" name="releaseInfo">
|
||||
<property name="readOnly">
|
||||
<bool>true</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/>
|
||||
|
|
|
@ -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))));
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,18 +924,37 @@ 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(pos == 0);
|
||||
m_context->VSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
|
||||
m_context->PSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
|
||||
DebugAssert(offset == 0);
|
||||
if (m_current_compute_shader)
|
||||
{
|
||||
m_context->CSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
|
||||
}
|
||||
else
|
||||
{
|
||||
m_context->VSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
|
||||
m_context->PSSetConstantBuffers(0, 1, m_uniform_buffer.GetD3DBufferArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1004,9 +1017,16 @@ 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();
|
||||
|
||||
m_context->OMSetRenderTargetsAndUnorderedAccessViews(
|
||||
0, nullptr, m_current_depth_target ? m_current_depth_target->GetD3DDSV() : nullptr, 0,
|
||||
m_num_current_render_targets, uavs.data(), nullptr);
|
||||
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
|
||||
{
|
||||
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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,69 +367,124 @@ 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 (ID3D11InputLayout* il = PL->GetInputLayout(); m_current_input_layout != il)
|
||||
if (!PL->IsComputePipeline())
|
||||
{
|
||||
m_current_input_layout = il;
|
||||
m_context->IASetInputLayout(il);
|
||||
}
|
||||
if (was_compute)
|
||||
UnbindComputePipeline();
|
||||
|
||||
if (const u32 vertex_stride = PL->GetVertexStride(); m_current_vertex_stride != vertex_stride)
|
||||
{
|
||||
const UINT offset = 0;
|
||||
m_current_vertex_stride = PL->GetVertexStride();
|
||||
m_context->IASetVertexBuffers(0, 1, m_vertex_buffer.GetD3DBufferArray(), &m_current_vertex_stride, &offset);
|
||||
}
|
||||
if (ID3D11InputLayout* il = PL->GetInputLayout(); m_current_input_layout != il)
|
||||
{
|
||||
m_current_input_layout = il;
|
||||
m_context->IASetInputLayout(il);
|
||||
}
|
||||
|
||||
if (D3D_PRIMITIVE_TOPOLOGY topology = PL->GetPrimitiveTopology(); m_current_primitive_topology != topology)
|
||||
{
|
||||
m_current_primitive_topology = topology;
|
||||
m_context->IASetPrimitiveTopology(topology);
|
||||
}
|
||||
if (const u32 vertex_stride = PL->GetVertexStride(); m_current_vertex_stride != vertex_stride)
|
||||
{
|
||||
const UINT offset = 0;
|
||||
m_current_vertex_stride = PL->GetVertexStride();
|
||||
m_context->IASetVertexBuffers(0, 1, m_vertex_buffer.GetD3DBufferArray(), &m_current_vertex_stride, &offset);
|
||||
}
|
||||
|
||||
if (ID3D11VertexShader* vs = PL->GetVertexShader(); m_current_vertex_shader != vs)
|
||||
{
|
||||
m_current_vertex_shader = vs;
|
||||
m_context->VSSetShader(vs, nullptr, 0);
|
||||
}
|
||||
if (D3D_PRIMITIVE_TOPOLOGY topology = PL->GetPrimitiveTopology(); m_current_primitive_topology != topology)
|
||||
{
|
||||
m_current_primitive_topology = topology;
|
||||
m_context->IASetPrimitiveTopology(topology);
|
||||
}
|
||||
|
||||
if (ID3D11GeometryShader* gs = PL->GetGeometryShader(); m_current_geometry_shader != gs)
|
||||
{
|
||||
m_current_geometry_shader = gs;
|
||||
m_context->GSSetShader(gs, nullptr, 0);
|
||||
}
|
||||
if (ID3D11VertexShader* vs = PL->GetVertexShader(); m_current_vertex_shader != vs)
|
||||
{
|
||||
m_current_vertex_shader = vs;
|
||||
m_context->VSSetShader(vs, nullptr, 0);
|
||||
}
|
||||
|
||||
if (ID3D11PixelShader* ps = PL->GetPixelShader(); m_current_pixel_shader != ps)
|
||||
{
|
||||
m_current_pixel_shader = ps;
|
||||
m_context->PSSetShader(ps, nullptr, 0);
|
||||
}
|
||||
if (ID3D11GeometryShader* gs = PL->GetGeometryShader(); m_current_geometry_shader != gs)
|
||||
{
|
||||
m_current_geometry_shader = gs;
|
||||
m_context->GSSetShader(gs, nullptr, 0);
|
||||
}
|
||||
|
||||
if (ID3D11RasterizerState* rs = PL->GetRasterizerState(); m_current_rasterizer_state != rs)
|
||||
{
|
||||
m_current_rasterizer_state = rs;
|
||||
m_context->RSSetState(rs);
|
||||
}
|
||||
if (ID3D11PixelShader* ps = PL->GetPixelShader(); m_current_pixel_shader != ps)
|
||||
{
|
||||
m_current_pixel_shader = ps;
|
||||
m_context->PSSetShader(ps, nullptr, 0);
|
||||
}
|
||||
|
||||
if (ID3D11DepthStencilState* ds = PL->GetDepthStencilState(); m_current_depth_state != ds)
|
||||
{
|
||||
m_current_depth_state = ds;
|
||||
m_context->OMSetDepthStencilState(ds, 0);
|
||||
}
|
||||
if (ID3D11RasterizerState* rs = PL->GetRasterizerState(); m_current_rasterizer_state != rs)
|
||||
{
|
||||
m_current_rasterizer_state = rs;
|
||||
m_context->RSSetState(rs);
|
||||
}
|
||||
|
||||
if (ID3D11BlendState* bs = PL->GetBlendState();
|
||||
m_current_blend_state != bs || m_current_blend_factor != PL->GetBlendFactor())
|
||||
if (ID3D11DepthStencilState* ds = PL->GetDepthStencilState(); m_current_depth_state != ds)
|
||||
{
|
||||
m_current_depth_state = ds;
|
||||
m_context->OMSetDepthStencilState(ds, 0);
|
||||
}
|
||||
|
||||
if (ID3D11BlendState* bs = PL->GetBlendState();
|
||||
m_current_blend_state != bs || m_current_blend_factor != PL->GetBlendFactor())
|
||||
{
|
||||
m_current_blend_state = bs;
|
||||
m_current_blend_factor = PL->GetBlendFactor();
|
||||
m_context->OMSetBlendState(bs, RGBA8ToFloat(m_current_blend_factor).data(), 0xFFFFFFFFu);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
m_current_blend_state = bs;
|
||||
m_current_blend_factor = PL->GetBlendFactor();
|
||||
m_context->OMSetBlendState(bs, RGBA8ToFloat(m_current_blend_factor).data(), 0xFFFFFFFFu);
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -436,6 +493,23 @@ 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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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++;
|
||||
|
||||
*map = static_cast<u8*>(sr.pData) + (y * sr.RowPitch) + (x * GetPixelSize());
|
||||
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;
|
||||
usage = D3D11_USAGE_DYNAMIC;
|
||||
cpu_access = D3D11_CPU_ACCESS_WRITE;
|
||||
|
||||
case Type::RenderTarget:
|
||||
bind_flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_SHADER_RESOURCE;
|
||||
break;
|
||||
case Type::RWTexture:
|
||||
bind_flags = D3D11_BIND_RENDER_TARGET | D3D11_BIND_UNORDERED_ACCESS | D3D11_BIND_SHADER_RESOURCE;
|
||||
break;
|
||||
default:
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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 {};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -83,7 +83,6 @@ bool D3D12DescriptorHeapManager::Allocate(D3D12DescriptorHandle* handle)
|
|||
return true;
|
||||
}
|
||||
|
||||
Panic("Out of fixed descriptors");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
|
|
@ -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,15 +2384,88 @@ 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(
|
||||
m_root_signatures[BoolToUInt8(IsUsingROVRootSignature())][static_cast<u8>(m_current_pipeline_layout)].Get());
|
||||
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>
|
||||
|
@ -2223,7 +2506,10 @@ bool D3D12Device::UpdateParametersForLayout(u32 dirty)
|
|||
D3D12_DESCRIPTOR_HEAP_TYPE_CBV_SRV_UAV);
|
||||
}
|
||||
|
||||
cmdlist->SetGraphicsRootDescriptorTable(0, gpu_handle);
|
||||
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;
|
||||
}
|
||||
|
||||
cmdlist->SetGraphicsRootDescriptorTable(1, gpu_handle);
|
||||
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);
|
||||
cmdlist->SetGraphicsRootDescriptorTable(rov_param, gpu_handle);
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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));
|
||||
}
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
{
|
||||
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;
|
||||
}
|
||||
break;
|
||||
DefaultCaseIsUnreachable();
|
||||
}
|
||||
|
||||
default:
|
||||
return {};
|
||||
if ((flags & GPUTexture::Flags::AllowBindAsImage) != GPUTexture::Flags::None)
|
||||
{
|
||||
DebugAssert(levels == 1);
|
||||
allocationDesc.Flags |= D3D12MA::ALLOCATION_FLAG_COMMITTED;
|
||||
desc.Flags |= D3D12_RESOURCE_FLAG_ALLOW_UNORDERED_ACCESS;
|
||||
}
|
||||
|
||||
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:
|
||||
DefaultCaseIsUnreachable();
|
||||
}
|
||||
|
||||
if ((flags & GPUTexture::Flags::AllowBindAsImage) != GPUTexture::Flags::None)
|
||||
{
|
||||
if (!CreateUAVDescriptor(resource.Get(), samples, fm.srv_format, &uav_descriptor, error))
|
||||
{
|
||||
write_descriptor_type = D3D12Texture::WriteDescriptorType::RTV;
|
||||
if (!CreateRTVDescriptor(resource.Get(), samples, fm.rtv_format, &write_descriptor))
|
||||
{
|
||||
m_descriptor_heap_manager.Free(&srv_descriptor);
|
||||
return {};
|
||||
}
|
||||
|
||||
if (!CreateUAVDescriptor(resource.Get(), samples, fm.srv_format, &uav_descriptor))
|
||||
{
|
||||
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;
|
||||
m_descriptor_heap_manager.Free(&srv_descriptor);
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
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,24 +340,24 @@ 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,
|
||||
D3D12_RESOURCE_FLAG_NONE};
|
||||
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(),
|
||||
IID_PPV_ARGS(resource.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 {};
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}};
|
||||
|
||||
|
|
|
@ -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 = ℑ
|
||||
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,16 +1833,31 @@ 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 = 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)
|
||||
{
|
||||
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() for FB failed: {}",
|
||||
static_cast<int>(sres));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
||||
if (stage == GPUShaderStage::Compute)
|
||||
{
|
||||
for (u32 i = 0; i < MAX_IMAGE_RENDER_TARGETS; i++)
|
||||
{
|
||||
const spvc_msl_resource_binding rb = {
|
||||
.stage = SpvExecutionModelFragment, .desc_set = 2, .binding = 0, .msl_texture = MAX_TEXTURE_SAMPLERS};
|
||||
.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() for FB failed: {}",
|
||||
static_cast<int>(sres));
|
||||
Error::SetStringFmt(error, "spvc_compiler_msl_add_resource_binding() failed: {}", static_cast<int>(sres));
|
||||
return {};
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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
|
||||
{
|
||||
|
|
|
@ -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))
|
||||
|
|
|
@ -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*/
|
||||
return 1;
|
||||
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())
|
||||
{
|
||||
width = (width > 1) ? (width / 2) : width;
|
||||
height = (height > 1) ? (height / 2) : height;
|
||||
ts += static_cast<size_t>(width * height) * ps;
|
||||
#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;
|
||||
mem += COMPRESSED_SIZE(width, height);
|
||||
}
|
||||
|
||||
#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 ts;
|
||||
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 > MAX_WIDTH || height > MAX_HEIGHT || layers > MAX_LAYERS || levels > MAX_LEVELS || samples > MAX_SAMPLES)
|
||||
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)
|
||||
{
|
||||
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()
|
||||
{
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
1040
src/util/image.cpp
1040
src/util/image.cpp
File diff suppressed because it is too large
Load Diff
205
src/util/image.h
205
src/util/image.h
|
@ -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;
|
||||
};
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -904,13 +904,32 @@ 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);
|
||||
AddBindings(bindings, InputAxisEventHandler{[pad_index, macro_button_index, deadzone](float value) {
|
||||
if (!System::IsValid())
|
||||
return;
|
||||
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);
|
||||
|
||||
const bool state = (value > deadzone);
|
||||
SetMacroButtonState(pad_index, macro_button_index, state);
|
||||
}});
|
||||
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;
|
||||
|
||||
const bool state = (value > deadzone);
|
||||
SetMacroButtonState(pad_index, macro_button_index, state);
|
||||
}});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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,12 +1952,19 @@ 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1931,26 +1975,34 @@ void MetalDevice::SetPipeline(GPUPipeline* pipeline)
|
|||
return;
|
||||
|
||||
m_current_pipeline = static_cast<MetalPipeline*>(pipeline);
|
||||
if (InRenderPass())
|
||||
if (!m_current_pipeline->IsComputePipeline())
|
||||
{
|
||||
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetPipelineState()];
|
||||
if (InRenderPass())
|
||||
{
|
||||
[m_render_encoder setRenderPipelineState:m_current_pipeline->GetRenderPipelineState()];
|
||||
|
||||
if (m_current_depth_state != m_current_pipeline->GetDepthState())
|
||||
{
|
||||
m_current_depth_state = m_current_pipeline->GetDepthState();
|
||||
[m_render_encoder setDepthStencilState:m_current_depth_state];
|
||||
if (m_current_depth_state != m_current_pipeline->GetDepthState())
|
||||
{
|
||||
m_current_depth_state = m_current_pipeline->GetDepthState();
|
||||
[m_render_encoder setDepthStencilState:m_current_depth_state];
|
||||
}
|
||||
if (m_current_cull_mode != m_current_pipeline->GetCullMode())
|
||||
{
|
||||
m_current_cull_mode = m_current_pipeline->GetCullMode();
|
||||
[m_render_encoder setCullMode:m_current_cull_mode];
|
||||
}
|
||||
}
|
||||
if (m_current_cull_mode != m_current_pipeline->GetCullMode())
|
||||
else
|
||||
{
|
||||
// Still need to set depth state before the draw begins.
|
||||
m_current_depth_state = m_current_pipeline->GetDepthState();
|
||||
m_current_cull_mode = m_current_pipeline->GetCullMode();
|
||||
[m_render_encoder setCullMode:m_current_cull_mode];
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// Still need to set depth state before the draw begins.
|
||||
m_current_depth_state = m_current_pipeline->GetDepthState();
|
||||
m_current_cull_mode = m_current_pipeline->GetCullMode();
|
||||
if (InComputePass())
|
||||
[m_compute_encoder setComputePipelineState:m_current_pipeline->GetComputePipelineState()];
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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,7 +2354,12 @@ 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;
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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"
|
||||
|
@ -32,61 +33,69 @@ const std::tuple<GLenum, GLenum, GLenum>& OpenGLTexture::GetPixelFormatMapping(G
|
|||
{
|
||||
static constexpr std::array<std::tuple<GLenum, GLenum, GLenum>, static_cast<u32>(GPUTexture::Format::MaxCount)>
|
||||
mapping = {{
|
||||
{}, // Unknown
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGBA5551
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32F
|
||||
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}, // D32FS8
|
||||
{GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16
|
||||
{GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16I
|
||||
{GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16U
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16F
|
||||
{GL_R32I, GL_RED_INTEGER, GL_INT}, // R32I
|
||||
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32U
|
||||
{GL_R32F, GL_RED, GL_FLOAT}, // R32F
|
||||
{GL_RG8, GL_RG_INTEGER, GL_UNSIGNED_BYTE}, // RG8
|
||||
{GL_RG16F, GL_RG, GL_UNSIGNED_SHORT}, // RG16
|
||||
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16F
|
||||
{GL_RG32F, GL_RG, GL_FLOAT}, // RG32F
|
||||
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA16
|
||||
{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
|
||||
{}, // Unknown
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGBA5551
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32F
|
||||
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}, // D32FS8
|
||||
{GL_R16, GL_RED, GL_UNSIGNED_SHORT}, // R16
|
||||
{GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16I
|
||||
{GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16U
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16F
|
||||
{GL_R32I, GL_RED_INTEGER, GL_INT}, // R32I
|
||||
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32U
|
||||
{GL_R32F, GL_RED, GL_FLOAT}, // R32F
|
||||
{GL_RG8, GL_RG_INTEGER, GL_UNSIGNED_BYTE}, // RG8
|
||||
{GL_RG16F, GL_RG, GL_UNSIGNED_SHORT}, // RG16
|
||||
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16F
|
||||
{GL_RG32F, GL_RG, GL_FLOAT}, // RG32F
|
||||
{GL_RGBA16, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA16
|
||||
{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.
|
||||
static constexpr std::array<std::tuple<GLenum, GLenum, GLenum>, static_cast<u32>(GPUTexture::Format::MaxCount)>
|
||||
mapping_gles = {{
|
||||
{}, // Unknown
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGBA5551
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32F
|
||||
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}, // D32FS8
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16
|
||||
{GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16I
|
||||
{GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16U
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16F
|
||||
{GL_R32I, GL_RED_INTEGER, GL_INT}, // R32I
|
||||
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32U
|
||||
{GL_R32F, GL_RED, GL_FLOAT}, // R32F
|
||||
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // RG8
|
||||
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16
|
||||
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16F
|
||||
{GL_RG32F, GL_RG, GL_FLOAT}, // RG32F
|
||||
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // RGBA16
|
||||
{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
|
||||
{}, // Unknown
|
||||
{GL_RGBA8, GL_RGBA, GL_UNSIGNED_BYTE}, // RGBA8
|
||||
{GL_RGBA8, GL_BGRA, GL_UNSIGNED_BYTE}, // BGRA8
|
||||
{GL_RGB565, GL_RGB, GL_UNSIGNED_SHORT_5_6_5}, // RGB565
|
||||
{GL_RGB5_A1, GL_BGRA, GL_UNSIGNED_SHORT_1_5_5_5_REV}, // RGBA5551
|
||||
{GL_R8, GL_RED, GL_UNSIGNED_BYTE}, // R8
|
||||
{GL_DEPTH_COMPONENT16, GL_DEPTH_COMPONENT, GL_SHORT}, // D16
|
||||
{GL_DEPTH24_STENCIL8, GL_DEPTH_STENCIL, GL_UNSIGNED_INT}, // D24S8
|
||||
{GL_DEPTH_COMPONENT32F, GL_DEPTH_COMPONENT, GL_FLOAT}, // D32F
|
||||
{GL_DEPTH32F_STENCIL8, GL_DEPTH_STENCIL, GL_FLOAT}, // D32FS8
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16
|
||||
{GL_R16I, GL_RED_INTEGER, GL_SHORT}, // R16I
|
||||
{GL_R16UI, GL_RED_INTEGER, GL_UNSIGNED_SHORT}, // R16U
|
||||
{GL_R16F, GL_RED, GL_HALF_FLOAT}, // R16F
|
||||
{GL_R32I, GL_RED_INTEGER, GL_INT}, // R32I
|
||||
{GL_R32UI, GL_RED_INTEGER, GL_UNSIGNED_INT}, // R32U
|
||||
{GL_R32F, GL_RED, GL_FLOAT}, // R32F
|
||||
{GL_RG8, GL_RG, GL_UNSIGNED_BYTE}, // RG8
|
||||
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16
|
||||
{GL_RG16F, GL_RG, GL_HALF_FLOAT}, // RG16F
|
||||
{GL_RG32F, GL_RG, GL_FLOAT}, // RG32F
|
||||
{GL_RGBA16F, GL_RGBA, GL_HALF_FLOAT}, // RGBA16
|
||||
{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);
|
||||
}
|
||||
|
@ -196,18 +207,55 @@ std::unique_ptr<OpenGLTexture> OpenGLTexture::Create(u32 width, u32 height, u32
|
|||
{
|
||||
if (use_texture_storage)
|
||||
{
|
||||
if (layers > 1)
|
||||
glTexSubImage3D(target, i, 0, 0, 0, current_width, current_height, layers, gl_format, gl_type, data_ptr);
|
||||
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
|
||||
glTexSubImage2D(target, i, 0, 0, current_width, current_height, gl_format, gl_type, data_ptr);
|
||||
{
|
||||
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 (layers > 1)
|
||||
glTexImage3D(target, i, gl_internal_format, current_width, current_height, layers, 0, gl_format, gl_type,
|
||||
data_ptr);
|
||||
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
|
||||
glTexImage2D(target, i, gl_internal_format, current_width, current_height, 0, gl_format, gl_type, data_ptr);
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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,
|
||||
reinterpret_cast<void*>(static_cast<uintptr_t>(map.buffer_offset)));
|
||||
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,18 +437,35 @@ 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 (IsTextureArray())
|
||||
if (IsCompressedFormat())
|
||||
{
|
||||
glTexSubImage3D(target, m_map_level, m_map_x, m_map_y, m_map_layer, m_map_width, m_map_height, 1, gl_format,
|
||||
gl_type, reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
|
||||
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
|
||||
{
|
||||
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)));
|
||||
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,
|
||||
gl_type, reinterpret_cast<void*>(static_cast<uintptr_t>(m_map_offset)));
|
||||
}
|
||||
else
|
||||
{
|
||||
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);
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.");
|
||||
|
||||
|
|
|
@ -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,12 +1455,10 @@ 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;
|
||||
|
|
File diff suppressed because it is too large
Load Diff
|
@ -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
|
|
@ -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" />
|
||||
|
|
|
@ -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
Loading…
Reference in New Issue