mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-16 05:21:22 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a99cc426d6 | |||
| 4d49341918 | |||
| 2f0f8a979c | |||
| 413c7543ba | |||
| 975aa4e2f2 | |||
| a1f9e68f46 | |||
| 02dee4a20b | |||
| bc9b9480fb |
+165
-287
@@ -7,25 +7,18 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/bit_util.h"
|
||||
#include "common/fs/file.h"
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/fs_types.h"
|
||||
#ifdef ANDROID
|
||||
#include "common/fs/fs_android.h"
|
||||
#endif
|
||||
#include "common/logging.h"
|
||||
#include "common/literals.h"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <io.h>
|
||||
#include <share.h>
|
||||
#include <windows.h>
|
||||
#else
|
||||
#include <unistd.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
#endif
|
||||
|
||||
#ifdef _MSC_VER
|
||||
@@ -43,13 +36,13 @@ namespace {
|
||||
#ifdef _WIN32
|
||||
|
||||
/**
|
||||
* Converts the file access mode and file type enums to a file access mode wide string.
|
||||
*
|
||||
* @param mode File access mode
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A pointer to a wide string representing the file access mode.
|
||||
*/
|
||||
* Converts the file access mode and file type enums to a file access mode wide string.
|
||||
*
|
||||
* @param mode File access mode
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A pointer to a wide string representing the file access mode.
|
||||
*/
|
||||
[[nodiscard]] constexpr const wchar_t* AccessModeToWStr(FileAccessMode mode, FileType type) {
|
||||
switch (type) {
|
||||
case FileType::BinaryFile:
|
||||
@@ -86,12 +79,12 @@ namespace {
|
||||
}
|
||||
|
||||
/**
|
||||
* Converts the file-share access flag enum to a Windows defined file-share access flag.
|
||||
*
|
||||
* @param flag File-share access flag
|
||||
*
|
||||
* @returns Windows defined file-share access flag.
|
||||
*/
|
||||
* Converts the file-share access flag enum to a Windows defined file-share access flag.
|
||||
*
|
||||
* @param flag File-share access flag
|
||||
*
|
||||
* @returns Windows defined file-share access flag.
|
||||
*/
|
||||
[[nodiscard]] constexpr int ToWindowsFileShareFlag(FileShareFlag flag) {
|
||||
switch (flag) {
|
||||
case FileShareFlag::ShareNone:
|
||||
@@ -109,13 +102,13 @@ namespace {
|
||||
#else
|
||||
|
||||
/**
|
||||
* Converts the file access mode and file type enums to a file access mode string.
|
||||
*
|
||||
* @param mode File access mode
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A pointer to a string representing the file access mode.
|
||||
*/
|
||||
* Converts the file access mode and file type enums to a file access mode string.
|
||||
*
|
||||
* @param mode File access mode
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A pointer to a string representing the file access mode.
|
||||
*/
|
||||
[[nodiscard]] constexpr const char* AccessModeToStr(FileAccessMode mode, FileType type) {
|
||||
switch (type) {
|
||||
case FileType::BinaryFile:
|
||||
@@ -154,12 +147,12 @@ namespace {
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Converts the seek origin enum to a seek origin integer.
|
||||
*
|
||||
* @param origin Seek origin
|
||||
*
|
||||
* @returns Seek origin integer.
|
||||
*/
|
||||
* Converts the seek origin enum to a seek origin integer.
|
||||
*
|
||||
* @param origin Seek origin
|
||||
*
|
||||
* @returns Seek origin integer.
|
||||
*/
|
||||
[[nodiscard]] constexpr int ToSeekOrigin(SeekOrigin origin) {
|
||||
switch (origin) {
|
||||
case SeekOrigin::SetOrigin:
|
||||
@@ -185,7 +178,7 @@ std::string ReadStringFromFile(const std::filesystem::path& path, FileType type)
|
||||
}
|
||||
|
||||
size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string) {
|
||||
std::string_view string) {
|
||||
if (Exists(path) && !IsFile(path)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -196,7 +189,7 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
}
|
||||
|
||||
size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string) {
|
||||
std::string_view string) {
|
||||
if (Exists(path) && !IsFile(path)) {
|
||||
return 0;
|
||||
}
|
||||
@@ -251,189 +244,69 @@ FileType IOFile::GetType() const {
|
||||
return file_type;
|
||||
}
|
||||
|
||||
#if defined(__unix__)
|
||||
static int PlatformMapReadOnly(IOFile& io, const char* path) {
|
||||
io.mmap_fd = open(path, O_RDONLY);
|
||||
if (io.mmap_fd > 0) {
|
||||
struct stat st;
|
||||
fstat(io.mmap_fd, &st);
|
||||
io.mmap_size = st.st_size;
|
||||
|
||||
int map_flags = MAP_PRIVATE;
|
||||
#ifdef MAP_PREFAULT_READ
|
||||
// Prefaults reads so the final resulting pagetable from this big stupid mmap()
|
||||
// isn't comically lazily loaded, we just coalesce everything in-place for our
|
||||
// lovely mmap flags; if we didn't prefault the reads the page table will be
|
||||
// constructed in-place (i.e on a read-by-read basis) causing lovely soft-faults
|
||||
// which would nuke any performance gains.
|
||||
//
|
||||
// This of course incurs a cost in the initial mmap(2) call, but that is fine.
|
||||
map_flags |= MAP_PREFAULT_READ;
|
||||
#endif
|
||||
#ifdef MAP_NOSYNC
|
||||
// This causes physical media to not be synched to our file/memory
|
||||
// This means that if the read-only file is written to, we won't see changes
|
||||
// or we may see changes which are just funnily scattered, in any case
|
||||
// this presumes the files won't be changed during execution
|
||||
//
|
||||
// Do not ever use this on write files (if we ever support that); this will create
|
||||
// a fun amount of fragmentation on the disk.
|
||||
map_flags |= MAP_NOSYNC;
|
||||
#endif
|
||||
#if defined(NDEBUG) && defined(MAP_NOCORE)
|
||||
map_flags |= MAP_NOCORE;
|
||||
#endif
|
||||
#ifdef MAP_HUGE_2MB
|
||||
map_flags |= MAP_HUGE_2MB; //2mb pages
|
||||
#elif defined(MAP_ALIGNED)
|
||||
// File must be big enough that it's worth to super align. We can't just super-align every
|
||||
// file otherwise we will run out of alignments for actually important files :)
|
||||
// System doesn't guarantee a super alignment, but if it's available it will delete
|
||||
// about 3 layers(?) of the TLB tree for each read/write.
|
||||
// Again the cost of faults may make this negligible gains, but hey, we gotta work
|
||||
// what we gotta work with.
|
||||
map_flags |= MAP_ALIGNED(21); //2^21 = 2mb use 2MB pages
|
||||
#endif
|
||||
io.mmap_base = (u8*)mmap(nullptr, io.mmap_size, PROT_READ, map_flags, io.mmap_fd, 0);
|
||||
if (io.mmap_base == MAP_FAILED) {
|
||||
close(io.mmap_fd);
|
||||
io.mmap_fd = -1;
|
||||
} else {
|
||||
using namespace Common::Literals;
|
||||
// For small files it is acceptable to use a full readahead
|
||||
// See https://github.com/torvalds/linux/blob/e80d033851b3bc94c3d254ac66660ddd0a49d72c/include/linux/pagemap.h#L1392
|
||||
if (u64(st.st_size) >= 256_MiB) {
|
||||
posix_madvise(io.mmap_base, io.mmap_size, POSIX_MADV_RANDOM);
|
||||
} else {
|
||||
posix_madvise(io.mmap_base, io.mmap_size, POSIX_MADV_SEQUENTIAL);
|
||||
}
|
||||
}
|
||||
}
|
||||
return io.mmap_fd;
|
||||
}
|
||||
static void PlatformUnmap(IOFile& io) {
|
||||
if (io.mmap_fd != -1) {
|
||||
munmap(io.mmap_base, io.mmap_size);
|
||||
close(io.mmap_fd);
|
||||
io.mmap_fd = -1;
|
||||
}
|
||||
}
|
||||
#elif defined(__APPLE__)
|
||||
// NO IMPLEMENTATION YET
|
||||
#else
|
||||
static int PlatformMapReadOnly(IOFile& io, const wchar_t* path) {
|
||||
io.file_handle = CreateFileW(path, GENERIC_READ, FILE_SHARE_READ, nullptr, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL | FILE_FLAG_SEQUENTIAL_SCAN, nullptr);
|
||||
if (HANDLE(io.file_handle) != INVALID_HANDLE_VALUE) {
|
||||
io.mapping_handle = CreateFileMappingW(HANDLE(io.file_handle), nullptr, PAGE_READONLY, 0, 0, nullptr);
|
||||
if (io.mapping_handle) {
|
||||
io.mmap_base = (u8*)MapViewOfFile(HANDLE(io.mapping_handle), FILE_MAP_READ, 0, 0, 0);
|
||||
if (io.mmap_base) {
|
||||
_LARGE_INTEGER pvalue;
|
||||
GetFileSizeEx(io.file_handle, &pvalue);
|
||||
io.mmap_size = uint32_t(pvalue.QuadPart);
|
||||
} else {
|
||||
CloseHandle(io.mapping_handle);
|
||||
CloseHandle(io.file_handle);
|
||||
return -1;
|
||||
}
|
||||
} else {
|
||||
CloseHandle(io.file_handle);
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
static void PlatformUnmap(IOFile& io) {
|
||||
if(io.mapping_handle) {
|
||||
if(io.mmap_base)
|
||||
UnmapViewOfFile(HANDLE(io.mmap_base));
|
||||
CloseHandle(HANDLE(io.mapping_handle));
|
||||
}
|
||||
if(io.file_handle != INVALID_HANDLE_VALUE)
|
||||
CloseHandle(HANDLE(io.file_handle));
|
||||
}
|
||||
#endif
|
||||
|
||||
void IOFile::Open(const fs::path& path, FileAccessMode mode, FileType type, FileShareFlag flag) {
|
||||
Close();
|
||||
|
||||
file_path = path;
|
||||
file_access_mode = mode;
|
||||
file_type = type;
|
||||
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
// TODO: this probably can use better logic but oh well I'm not a windowser
|
||||
file_handle = nullptr;
|
||||
if (type == FileType::BinaryFile && mode == FileAccessMode::Read) {
|
||||
if (PlatformMapReadOnly(*this, path.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "Error mmap'ing file"); //: {}", path.c_str());
|
||||
}
|
||||
}
|
||||
if (file_handle == nullptr) {
|
||||
if (flag != FileShareFlag::ShareNone) {
|
||||
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
||||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
}
|
||||
if (flag != FileShareFlag::ShareNone) {
|
||||
file = _wfsopen(path.c_str(), AccessModeToWStr(mode, type), ToWindowsFileShareFlag(flag));
|
||||
} else {
|
||||
_wfopen_s(&file, path.c_str(), AccessModeToWStr(mode, type));
|
||||
}
|
||||
#elif ANDROID
|
||||
if (Android::IsContentUri(path)) {
|
||||
ASSERT_MSG(mode == FileAccessMode::Read, "Content URI file access is for read-only!");
|
||||
if (PlatformMapReadOnly(*this, path.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "Error mmap'ing file: {}", path.c_str());
|
||||
int const fd = Android::OpenContentUri(path, Android::OpenMode::Read);
|
||||
if (fd != -1) {
|
||||
file = fdopen(fd, "r");
|
||||
if (errno != 0 && file == nullptr)
|
||||
LOG_ERROR(Common_Filesystem, "Error opening file: {}, error: {}", path.c_str(), strerror(errno));
|
||||
} else {
|
||||
LOG_ERROR(Common_Filesystem, "Error opening file: {}", path.c_str());
|
||||
const auto fd = Android::OpenContentUri(path, Android::OpenMode::Read);
|
||||
if (fd != -1) {
|
||||
file = fdopen(fd, "r");
|
||||
const auto error_num = errno;
|
||||
if (error_num != 0 && file == nullptr) {
|
||||
LOG_ERROR(Common_Filesystem, "Error opening file: {}, error: {}", path.c_str(),
|
||||
strerror(error_num));
|
||||
}
|
||||
} else {
|
||||
LOG_ERROR(Common_Filesystem, "Error opening file: {}", path.c_str());
|
||||
}
|
||||
} else {
|
||||
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
|
||||
}
|
||||
#elif defined(__HAIKU__) || defined(__managarm__) || defined(__OPENORBIS__) || defined(__APPLE__)
|
||||
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
|
||||
#elif defined(__unix__)
|
||||
if (type == FileType::BinaryFile && mode == FileAccessMode::Read) {
|
||||
if (PlatformMapReadOnly(*this, path.c_str()) == -1) {
|
||||
LOG_ERROR(Common_Filesystem, "Error mmap'ing file: {}", path.c_str());
|
||||
}
|
||||
}
|
||||
if (mmap_fd == -1) {
|
||||
file = std::fopen(path.c_str(), AccessModeToStr(mode, type)); // mmap(2) failed or simply we can't use it
|
||||
}
|
||||
#else
|
||||
// Some other fancy OS (ahem fucking Darwin/Mac OSX)
|
||||
file = std::fopen(path.c_str(), AccessModeToStr(mode, type));
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to open the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
}
|
||||
|
||||
void IOFile::Close() {
|
||||
#if defined(__APPLE__)
|
||||
// NO IMPLEMENTATION YET
|
||||
#else
|
||||
PlatformUnmap(*this);
|
||||
#endif
|
||||
if (file) {
|
||||
errno = 0;
|
||||
const auto close_result = std::fclose(file) == 0;
|
||||
if (!close_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
file = nullptr;
|
||||
if (!IsOpen()) {
|
||||
return;
|
||||
}
|
||||
|
||||
errno = 0;
|
||||
|
||||
const auto close_result = std::fclose(file) == 0;
|
||||
|
||||
if (!close_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to close the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
|
||||
file = nullptr;
|
||||
}
|
||||
|
||||
bool IOFile::IsOpen() const {
|
||||
return file != nullptr || IsMappedFile();
|
||||
return file != nullptr;
|
||||
}
|
||||
|
||||
std::string IOFile::ReadString(size_t length) const {
|
||||
@@ -450,132 +323,137 @@ size_t IOFile::WriteString(std::span<const char> string) const {
|
||||
}
|
||||
|
||||
bool IOFile::Flush() const {
|
||||
ASSERT(!IsMappedFile());
|
||||
if (file) {
|
||||
errno = 0;
|
||||
auto const flush_result = std::fflush(file) == 0;
|
||||
if (!flush_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
return flush_result;
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
const auto flush_result = std::fflush(file) == 0;
|
||||
#else
|
||||
const auto flush_result = std::fflush(file) == 0;
|
||||
#endif
|
||||
|
||||
if (!flush_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to flush the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
|
||||
return flush_result;
|
||||
}
|
||||
|
||||
bool IOFile::Commit() const {
|
||||
ASSERT(!IsMappedFile());
|
||||
if (file) {
|
||||
errno = 0;
|
||||
#ifdef _WIN32
|
||||
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
|
||||
#else
|
||||
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
|
||||
#endif
|
||||
if (!commit_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
return commit_result;
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
const auto commit_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
|
||||
#else
|
||||
const auto commit_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
|
||||
#endif
|
||||
|
||||
if (!commit_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to commit the file at path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
}
|
||||
|
||||
return commit_result;
|
||||
}
|
||||
|
||||
bool IOFile::SetSize(u64 size) const {
|
||||
ASSERT(!IsMappedFile());
|
||||
if (file) {
|
||||
errno = 0;
|
||||
#ifdef _WIN32
|
||||
const auto set_size_result = _chsize_s(fileno(file), s64(size)) == 0;
|
||||
#else
|
||||
const auto set_size_result = ftruncate(fileno(file), s64(size)) == 0;
|
||||
#endif
|
||||
if (!set_size_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
|
||||
PathToUTF8String(file_path), size, ec.message());
|
||||
}
|
||||
return set_size_result;
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
return false;
|
||||
|
||||
errno = 0;
|
||||
|
||||
#ifdef _WIN32
|
||||
const auto set_size_result = _chsize_s(fileno(file), static_cast<s64>(size)) == 0;
|
||||
#else
|
||||
const auto set_size_result = ftruncate(fileno(file), static_cast<s64>(size)) == 0;
|
||||
#endif
|
||||
|
||||
if (!set_size_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to resize the file at path={}, size={}, ec_message={}",
|
||||
PathToUTF8String(file_path), size, ec.message());
|
||||
}
|
||||
|
||||
return set_size_result;
|
||||
}
|
||||
|
||||
u64 IOFile::GetSize() const {
|
||||
if (IsMappedFile())
|
||||
return mmap_size;
|
||||
if (file) {
|
||||
// Flush any unwritten buffered data into the file prior to retrieving the file mmap_size.
|
||||
std::fflush(file);
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Flush any unwritten buffered data into the file prior to retrieving the file size.
|
||||
std::fflush(file);
|
||||
|
||||
#if ANDROID
|
||||
u64 file_size = 0;
|
||||
if (Android::IsContentUri(file_path)) {
|
||||
file_size = Android::GetSize(file_path);
|
||||
} else {
|
||||
std::error_code ec;
|
||||
|
||||
file_size = fs::file_size(file_path, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file mmap_size of path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#else
|
||||
u64 file_size = 0;
|
||||
if (Android::IsContentUri(file_path)) {
|
||||
file_size = Android::GetSize(file_path);
|
||||
} else {
|
||||
std::error_code ec;
|
||||
auto const file_size = fs::file_size(file_path, ec);
|
||||
|
||||
file_size = fs::file_size(file_path, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file mmap_size of path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Failed to retrieve the file size of path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
return file_size;
|
||||
}
|
||||
return 0;
|
||||
#else
|
||||
std::error_code ec;
|
||||
|
||||
const auto file_size = fs::file_size(file_path, ec);
|
||||
|
||||
if (ec) {
|
||||
LOG_ERROR(Common_Filesystem, "Failed to retrieve the file size of path={}, ec_message={}",
|
||||
PathToUTF8String(file_path), ec.message());
|
||||
return 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
return file_size;
|
||||
}
|
||||
|
||||
bool IOFile::Seek(s64 offset, SeekOrigin origin) const {
|
||||
if (IsMappedFile()) {
|
||||
// fuck you to whoever made this method const
|
||||
switch (origin) {
|
||||
case SeekOrigin::SetOrigin:
|
||||
mmap_offset = s64(offset);
|
||||
break;
|
||||
case SeekOrigin::CurrentPosition:
|
||||
mmap_offset += s64(offset);
|
||||
break;
|
||||
case SeekOrigin::End:
|
||||
mmap_offset = s64(mmap_size) + s64(offset);
|
||||
break;
|
||||
}
|
||||
return true;
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
if (file) {
|
||||
errno = 0;
|
||||
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
|
||||
if (!seek_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem, "Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
|
||||
PathToUTF8String(file_path), offset, origin, ec.message());
|
||||
}
|
||||
return seek_result;
|
||||
|
||||
errno = 0;
|
||||
|
||||
const auto seek_result = fseeko(file, offset, ToSeekOrigin(origin)) == 0;
|
||||
|
||||
if (!seek_result) {
|
||||
const auto ec = std::error_code{errno, std::generic_category()};
|
||||
LOG_ERROR(Common_Filesystem,
|
||||
"Failed to seek the file at path={}, offset={}, origin={}, ec_message={}",
|
||||
PathToUTF8String(file_path), offset, origin, ec.message());
|
||||
}
|
||||
return false;
|
||||
|
||||
return seek_result;
|
||||
}
|
||||
|
||||
s64 IOFile::Tell() const {
|
||||
if (IsMappedFile()) {
|
||||
errno = 0;
|
||||
return s64(mmap_offset);
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
if (file) {
|
||||
errno = 0;
|
||||
return ftello(file);
|
||||
}
|
||||
return 0;
|
||||
|
||||
errno = 0;
|
||||
|
||||
return ftello(file);
|
||||
}
|
||||
|
||||
} // namespace Common::FS
|
||||
|
||||
+249
-258
@@ -1,13 +1,9 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <span>
|
||||
#include <type_traits>
|
||||
@@ -25,12 +21,12 @@ enum class SeekOrigin {
|
||||
};
|
||||
|
||||
/**
|
||||
* Opens a file stream at path with the specified open mode.
|
||||
*
|
||||
* @param file_stream Reference to file stream
|
||||
* @param path Filesystem path
|
||||
* @param open_mode File stream open mode
|
||||
*/
|
||||
* Opens a file stream at path with the specified open mode.
|
||||
*
|
||||
* @param file_stream Reference to file stream
|
||||
* @param path Filesystem path
|
||||
* @param open_mode File stream open mode
|
||||
*/
|
||||
template <typename FileStream>
|
||||
void OpenFileStream(FileStream& file_stream, const std::filesystem::path& path,
|
||||
std::ios_base::openmode open_mode) {
|
||||
@@ -49,14 +45,14 @@ void OpenFileStream(FileStream& file_stream, const Path& path, std::ios_base::op
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Reads an entire file at path and returns a string of the contents read from the file.
|
||||
* If the filesystem object at path is not a regular file, this function returns an empty string.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A string of the contents read from the file.
|
||||
*/
|
||||
* Reads an entire file at path and returns a string of the contents read from the file.
|
||||
* If the filesystem object at path is not a regular file, this function returns an empty string.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns A string of the contents read from the file.
|
||||
*/
|
||||
[[nodiscard]] std::string ReadStringFromFile(const std::filesystem::path& path, FileType type);
|
||||
|
||||
#ifdef _WIN32
|
||||
@@ -71,18 +67,18 @@ template <typename Path>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Writes a string to a file at path and returns the number of characters successfully written.
|
||||
* If a file already exists at path, its contents will be erased.
|
||||
* If a file does not exist at path, it creates and opens a new empty file for writing.
|
||||
* If the filesystem object at path exists and is not a regular file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
* Writes a string to a file at path and returns the number of characters successfully written.
|
||||
* If a file already exists at path, its contents will be erased.
|
||||
* If a file does not exist at path, it creates and opens a new empty file for writing.
|
||||
* If the filesystem object at path exists and is not a regular file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
[[nodiscard]] size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string);
|
||||
std::string_view string);
|
||||
|
||||
#ifdef _WIN32
|
||||
template <typename Path>
|
||||
@@ -96,15 +92,15 @@ template <typename Path>
|
||||
#endif
|
||||
|
||||
/**
|
||||
* Appends a string to a file at path and returns the number of characters successfully written.
|
||||
* If a file does not exist at path, it creates and opens a new empty file for appending.
|
||||
* If the filesystem object at path exists and is not a regular file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
* Appends a string to a file at path and returns the number of characters successfully written.
|
||||
* If a file does not exist at path, it creates and opens a new empty file for appending.
|
||||
* If the filesystem object at path exists and is not a regular file, this function returns 0.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param type File type
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
[[nodiscard]] size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
|
||||
std::string_view string);
|
||||
|
||||
@@ -132,14 +128,14 @@ public:
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
/**
|
||||
* An IOFile is a lightweight wrapper on C Library file operations.
|
||||
* Automatically closes an open file on the destruction of an IOFile object.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param mode File access mode
|
||||
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
|
||||
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
|
||||
*/
|
||||
* An IOFile is a lightweight wrapper on C Library file operations.
|
||||
* Automatically closes an open file on the destruction of an IOFile object.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param mode File access mode
|
||||
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
|
||||
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
|
||||
*/
|
||||
explicit IOFile(const std::filesystem::path& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
@@ -153,70 +149,84 @@ public:
|
||||
IOFile& operator=(IOFile&& other) noexcept;
|
||||
|
||||
/**
|
||||
* Gets the path of the file.
|
||||
*
|
||||
* @returns The path of the file.
|
||||
*/
|
||||
* Gets the path of the file.
|
||||
*
|
||||
* @returns The path of the file.
|
||||
*/
|
||||
[[nodiscard]] std::filesystem::path GetPath() const;
|
||||
|
||||
/**
|
||||
* Gets the access mode of the file.
|
||||
*
|
||||
* @returns The access mode of the file.
|
||||
*/
|
||||
* Gets the access mode of the file.
|
||||
*
|
||||
* @returns The access mode of the file.
|
||||
*/
|
||||
[[nodiscard]] FileAccessMode GetAccessMode() const;
|
||||
|
||||
/**
|
||||
* Gets the type of the file.
|
||||
*
|
||||
* @returns The type of the file.
|
||||
*/
|
||||
* Gets the type of the file.
|
||||
*
|
||||
* @returns The type of the file.
|
||||
*/
|
||||
[[nodiscard]] FileType GetType() const;
|
||||
|
||||
/**
|
||||
* Opens a file at path with the specified file access mode.
|
||||
* This function behaves differently depending on the FileAccessMode.
|
||||
* These behaviors are documented in each enum value of FileAccessMode.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param mode File access mode
|
||||
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
|
||||
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
|
||||
*/
|
||||
* Opens a file at path with the specified file access mode.
|
||||
* This function behaves differently depending on the FileAccessMode.
|
||||
* These behaviors are documented in each enum value of FileAccessMode.
|
||||
*
|
||||
* @param path Filesystem path
|
||||
* @param mode File access mode
|
||||
* @param type File type, default is BinaryFile. Use TextFile to open the file as a text file
|
||||
* @param flag (Windows only) File-share access flag, default is ShareReadOnly
|
||||
*/
|
||||
void Open(const std::filesystem::path& path, FileAccessMode mode,
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
FileType type = FileType::BinaryFile,
|
||||
FileShareFlag flag = FileShareFlag::ShareReadOnly);
|
||||
|
||||
// #ifdef _WIN32
|
||||
// template <typename Path>
|
||||
// void Open(const Path& path, FileAccessMode mode, FileType type = FileType::BinaryFile,
|
||||
// FileShareFlag flag = FileShareFlag::ShareReadOnly) {
|
||||
// using ValueType = typename Path::value_type;
|
||||
// if constexpr (IsChar<ValueType>) {
|
||||
// Open(ToU8String(path), mode, type, flag);
|
||||
// } else {
|
||||
// Open(std::filesystem::path{path}, mode, type, flag);
|
||||
// }
|
||||
// }
|
||||
// #endif
|
||||
|
||||
/// Closes the file if it is opened.
|
||||
void Close();
|
||||
|
||||
/**
|
||||
* Checks whether the file is open.
|
||||
* Use this to check whether the calls to Open() or Close() succeeded.
|
||||
*
|
||||
* @returns True if the file is open, false otherwise.
|
||||
*/
|
||||
* Checks whether the file is open.
|
||||
* Use this to check whether the calls to Open() or Close() succeeded.
|
||||
*
|
||||
* @returns True if the file is open, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool IsOpen() const;
|
||||
|
||||
/**
|
||||
* Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
|
||||
* If T is not a contiguous container as defined by the concept IsContiguousContainer, this
|
||||
* calls ReadObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See ReadSpan for more details if T is a contiguous container.
|
||||
* See ReadObject for more details if T is a trivially copyable object.
|
||||
*
|
||||
* @tparam T Contiguous container or trivially copyable object
|
||||
*
|
||||
* @param data Container of T::value_type data or reference to object
|
||||
*
|
||||
* @returns Count of T::value_type data or objects successfully read.
|
||||
*/
|
||||
* Helper function which deduces the value type of a contiguous STL container used in ReadSpan.
|
||||
* If T is not a contiguous container as defined by the concept IsContiguousContainer, this
|
||||
* calls ReadObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See ReadSpan for more details if T is a contiguous container.
|
||||
* See ReadObject for more details if T is a trivially copyable object.
|
||||
*
|
||||
* @tparam T Contiguous container or trivially copyable object
|
||||
*
|
||||
* @param data Container of T::value_type data or reference to object
|
||||
*
|
||||
* @returns Count of T::value_type data or objects successfully read.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t Read(T& data) const {
|
||||
if constexpr (IsContiguousContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>, "Data type must be trivially copyable.");
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Data type must be trivially copyable.");
|
||||
return ReadSpan<ContiguousType>(data);
|
||||
} else {
|
||||
return ReadObject(data) ? 1 : 0;
|
||||
@@ -224,24 +234,25 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
|
||||
* If T is not a contiguous STL container as defined by the concept IsContiguousContainer, this
|
||||
* calls WriteObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See WriteSpan for more details if T is a contiguous container.
|
||||
* See WriteObject for more details if T is a trivially copyable object.
|
||||
*
|
||||
* @tparam T Contiguous container or trivially copyable object
|
||||
*
|
||||
* @param data Container of T::value_type data or const reference to object
|
||||
*
|
||||
* @returns Count of T::value_type data or objects successfully written.
|
||||
*/
|
||||
* Helper function which deduces the value type of a contiguous STL container used in WriteSpan.
|
||||
* If T is not a contiguous STL container as defined by the concept IsContiguousContainer, this
|
||||
* calls WriteObject and T must be a trivially copyable object.
|
||||
*
|
||||
* See WriteSpan for more details if T is a contiguous container.
|
||||
* See WriteObject for more details if T is a trivially copyable object.
|
||||
*
|
||||
* @tparam T Contiguous container or trivially copyable object
|
||||
*
|
||||
* @param data Container of T::value_type data or const reference to object
|
||||
*
|
||||
* @returns Count of T::value_type data or objects successfully written.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t Write(const T& data) const {
|
||||
if constexpr (IsContiguousContainer<T>) {
|
||||
using ContiguousType = typename T::value_type;
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>, "Data type must be trivially copyable.");
|
||||
static_assert(std::is_trivially_copyable_v<ContiguousType>,
|
||||
"Data type must be trivially copyable.");
|
||||
return WriteSpan<ContiguousType>(data);
|
||||
} else {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
@@ -250,219 +261,199 @@ public:
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a span of T data from a file sequentially.
|
||||
* This function reads from the current position of the file pointer and
|
||||
* advances it by the (count of T * sizeof(T)) bytes successfully read.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks read permissions
|
||||
* - Attempting to read beyond the end-of-file
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param data Span of T data
|
||||
*
|
||||
* @returns Count of T data successfully read.
|
||||
*/
|
||||
* Reads a span of T data from a file sequentially.
|
||||
* This function reads from the current position of the file pointer and
|
||||
* advances it by the (count of T * sizeof(T)) bytes successfully read.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks read permissions
|
||||
* - Attempting to read beyond the end-of-file
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param data Span of T data
|
||||
*
|
||||
* @returns Count of T data successfully read.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t ReadSpan(std::span<T> data) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
if (IsMappedFile()) {
|
||||
std::memcpy(data.data(), mmap_base + mmap_offset, sizeof(T) * data.size());
|
||||
return data.size();
|
||||
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
return IsOpen() ? std::fread(data.data(), sizeof(T), data.size(), file) : 0;
|
||||
|
||||
return std::fread(data.data(), sizeof(T), data.size(), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a span of T data to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the (count of T * sizeof(T)) bytes successfully written.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks write permissions
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param data Span of T data
|
||||
*
|
||||
* @returns Count of T data successfully written.
|
||||
*/
|
||||
* Writes a span of T data to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the (count of T * sizeof(T)) bytes successfully written.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks write permissions
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param data Span of T data
|
||||
*
|
||||
* @returns Count of T data successfully written.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] size_t WriteSpan(std::span<const T> data) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
if (IsMappedFile()) {
|
||||
std::memcpy(mmap_base + mmap_offset, data.data(), sizeof(T) * data.size());
|
||||
return data.size();
|
||||
|
||||
if (!IsOpen()) {
|
||||
return 0;
|
||||
}
|
||||
return IsOpen() ? std::fwrite(data.data(), sizeof(T), data.size(), file) : 0;
|
||||
|
||||
return std::fwrite(data.data(), sizeof(T), data.size(), file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads a T object from a file sequentially.
|
||||
* This function reads from the current position of the file pointer and
|
||||
* advances it by the sizeof(T) bytes successfully read.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks read permissions
|
||||
* - Attempting to read beyond the end-of-file
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param object Reference to object
|
||||
*
|
||||
* @returns True if the object is successfully read from the file, false otherwise.
|
||||
*/
|
||||
* Reads a T object from a file sequentially.
|
||||
* This function reads from the current position of the file pointer and
|
||||
* advances it by the sizeof(T) bytes successfully read.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks read permissions
|
||||
* - Attempting to read beyond the end-of-file
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param object Reference to object
|
||||
*
|
||||
* @returns True if the object is successfully read from the file, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] bool ReadObject(T& object) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
|
||||
if (IsMappedFile()) {
|
||||
std::memcpy(&object, mmap_base + mmap_offset, sizeof(T));
|
||||
#ifdef _WIN32
|
||||
return bool(sizeof(T));
|
||||
#else
|
||||
return sizeof(T);
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
return IsOpen() ? std::fread(&object, sizeof(T), 1, file) == 1 : false;
|
||||
|
||||
return std::fread(&object, sizeof(T), 1, file) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes a T object to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the sizeof(T) bytes successfully written.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks write permissions
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param object Const reference to object
|
||||
*
|
||||
* @returns True if the object is successfully written to the file, false otherwise.
|
||||
*/
|
||||
* Writes a T object to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the sizeof(T) bytes successfully written.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
* - The opened file lacks write permissions
|
||||
*
|
||||
* @tparam T Data type
|
||||
*
|
||||
* @param object Const reference to object
|
||||
*
|
||||
* @returns True if the object is successfully written to the file, false otherwise.
|
||||
*/
|
||||
template <typename T>
|
||||
[[nodiscard]] bool WriteObject(const T& object) const {
|
||||
static_assert(std::is_trivially_copyable_v<T>, "Data type must be trivially copyable.");
|
||||
static_assert(!std::is_pointer_v<T>, "T must not be a pointer to an object.");
|
||||
if (IsMappedFile()) {
|
||||
std::memcpy(mmap_base + mmap_offset, &object, sizeof(T));
|
||||
#ifdef _WIN32
|
||||
return sizeof(T) != 0;
|
||||
#else
|
||||
return sizeof(T);
|
||||
#endif
|
||||
|
||||
if (!IsOpen()) {
|
||||
return false;
|
||||
}
|
||||
return IsOpen() ? std::fwrite(&object, sizeof(T), 1, file) == 1 : false;
|
||||
|
||||
return std::fwrite(&object, sizeof(T), 1, file) == 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specialized function to read a string of a given length from a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the number of characters successfully read.
|
||||
* The size of the returned string may not match length if not all bytes are successfully read.
|
||||
*
|
||||
* @param length Length of the string
|
||||
*
|
||||
* @returns A string read from the file.
|
||||
*/
|
||||
* Specialized function to read a string of a given length from a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the number of characters successfully read.
|
||||
* The size of the returned string may not match length if not all bytes are successfully read.
|
||||
*
|
||||
* @param length Length of the string
|
||||
*
|
||||
* @returns A string read from the file.
|
||||
*/
|
||||
[[nodiscard]] std::string ReadString(size_t length) const;
|
||||
|
||||
/**
|
||||
* Specialized function to write a string to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the number of characters successfully written.
|
||||
*
|
||||
* @param string Span of const char backed std::string or std::string_view
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
* Specialized function to write a string to a file sequentially.
|
||||
* This function writes from the current position of the file pointer and
|
||||
* advances it by the number of characters successfully written.
|
||||
*
|
||||
* @param string Span of const char backed std::string or std::string_view
|
||||
*
|
||||
* @returns Number of characters successfully written.
|
||||
*/
|
||||
[[nodiscard]] size_t WriteString(std::span<const char> string) const;
|
||||
|
||||
/**
|
||||
* Attempts to flush any unwritten buffered data into the file.
|
||||
*
|
||||
* @returns True if the flush was successful, false otherwise.
|
||||
*/
|
||||
* Attempts to flush any unwritten buffered data into the file.
|
||||
*
|
||||
* @returns True if the flush was successful, false otherwise.
|
||||
*/
|
||||
bool Flush() const;
|
||||
|
||||
/**
|
||||
* Attempts to commit the file into the disk.
|
||||
* Note that this is an expensive operation as this forces the operating system to write
|
||||
* the contents of the file associated with the file descriptor into the disk.
|
||||
*
|
||||
* @returns True if the commit was successful, false otherwise.
|
||||
*/
|
||||
* Attempts to commit the file into the disk.
|
||||
* Note that this is an expensive operation as this forces the operating system to write
|
||||
* the contents of the file associated with the file descriptor into the disk.
|
||||
*
|
||||
* @returns True if the commit was successful, false otherwise.
|
||||
*/
|
||||
bool Commit() const;
|
||||
|
||||
/**
|
||||
* Resizes the file to a given size.
|
||||
* If the file is resized to a smaller size, the remainder of the file is discarded.
|
||||
* If the file is resized to a larger size, the new area appears as if zero-filled.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
*
|
||||
* @param size File size in bytes
|
||||
*
|
||||
* @returns True if the file resize succeeded, false otherwise.
|
||||
*/
|
||||
* Resizes the file to a given size.
|
||||
* If the file is resized to a smaller size, the remainder of the file is discarded.
|
||||
* If the file is resized to a larger size, the new area appears as if zero-filled.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
*
|
||||
* @param size File size in bytes
|
||||
*
|
||||
* @returns True if the file resize succeeded, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool SetSize(u64 size) const;
|
||||
|
||||
/**
|
||||
* Gets the size of the file.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
*
|
||||
* @returns The file size in bytes of the file. Returns 0 on failure.
|
||||
*/
|
||||
* Gets the size of the file.
|
||||
*
|
||||
* Failures occur when:
|
||||
* - The file is not open
|
||||
*
|
||||
* @returns The file size in bytes of the file. Returns 0 on failure.
|
||||
*/
|
||||
[[nodiscard]] u64 GetSize() const;
|
||||
|
||||
/**
|
||||
* Moves the current position of the file pointer with the specified offset and seek origin.
|
||||
*
|
||||
* @param offset Offset from seek origin
|
||||
* @param origin Seek origin
|
||||
*
|
||||
* @returns True if the file pointer has moved to the specified offset, false otherwise.
|
||||
*/
|
||||
* Moves the current position of the file pointer with the specified offset and seek origin.
|
||||
*
|
||||
* @param offset Offset from seek origin
|
||||
* @param origin Seek origin
|
||||
*
|
||||
* @returns True if the file pointer has moved to the specified offset, false otherwise.
|
||||
*/
|
||||
[[nodiscard]] bool Seek(s64 offset, SeekOrigin origin = SeekOrigin::SetOrigin) const;
|
||||
|
||||
/**
|
||||
* Gets the current position of the file pointer.
|
||||
*
|
||||
* @returns The current position of the file pointer.
|
||||
*/
|
||||
* Gets the current position of the file pointer.
|
||||
*
|
||||
* @returns The current position of the file pointer.
|
||||
*/
|
||||
[[nodiscard]] s64 Tell() const;
|
||||
|
||||
#ifdef _WIN32
|
||||
inline bool IsMappedFile() const noexcept { return mapping_handle != nullptr; }
|
||||
#else // POSIX
|
||||
inline bool IsMappedFile() const noexcept { return mmap_fd != -1; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
std::filesystem::path file_path;
|
||||
FileAccessMode file_access_mode{};
|
||||
FileType file_type{};
|
||||
|
||||
std::FILE* file = nullptr;
|
||||
// Any decent system should have mmap() for files
|
||||
// Systems with artifical mmap() limitations should simply change the logic within file.cpp
|
||||
// and reduce the threshold for which the mmap() is set to
|
||||
#ifdef _WIN32
|
||||
void *mapping_handle = nullptr;
|
||||
void *file_handle = nullptr;
|
||||
#else // POSIX
|
||||
int mmap_fd = -1;
|
||||
#endif
|
||||
u8* mmap_base = nullptr;
|
||||
size_t mmap_size = 0;
|
||||
mutable s64 mmap_offset = 0; // fuck you
|
||||
};
|
||||
|
||||
} // namespace Common::FS
|
||||
|
||||
@@ -591,6 +591,8 @@ struct Values {
|
||||
SwitchableSetting<bool> gpu_unswizzle_enabled{linkage, false, "gpu_unswizzle_enabled",
|
||||
Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<bool> legacy_descriptor_indices{linkage, true, "legacy_descriptor_indices", Category::RendererHacks};
|
||||
|
||||
SwitchableSetting<ExtendedDynamicState> dyna_state{linkage,
|
||||
#if defined(ANDROID)
|
||||
ExtendedDynamicState::Disabled,
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -10,30 +7,28 @@
|
||||
namespace Core {
|
||||
|
||||
DynarmicExclusiveMonitor::DynarmicExclusiveMonitor(Memory::Memory& memory_, std::size_t core_count_)
|
||||
: monitor{}
|
||||
, memory{memory_}
|
||||
{}
|
||||
: monitor{core_count_}, memory{memory_} {}
|
||||
|
||||
DynarmicExclusiveMonitor::~DynarmicExclusiveMonitor() = default;
|
||||
|
||||
u8 DynarmicExclusiveMonitor::ExclusiveRead8(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u8>(core_index, addr, [=]() -> u8 { return memory.Read8(addr); });
|
||||
return monitor.ReadAndMark<u8>(core_index, addr, [&]() -> u8 { return memory.Read8(addr); });
|
||||
}
|
||||
|
||||
u16 DynarmicExclusiveMonitor::ExclusiveRead16(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u16>(core_index, addr, [=]() -> u16 { return memory.Read16(addr); });
|
||||
return monitor.ReadAndMark<u16>(core_index, addr, [&]() -> u16 { return memory.Read16(addr); });
|
||||
}
|
||||
|
||||
u32 DynarmicExclusiveMonitor::ExclusiveRead32(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u32>(core_index, addr, [=]() -> u32 { return memory.Read32(addr); });
|
||||
return monitor.ReadAndMark<u32>(core_index, addr, [&]() -> u32 { return memory.Read32(addr); });
|
||||
}
|
||||
|
||||
u64 DynarmicExclusiveMonitor::ExclusiveRead64(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u64>(core_index, addr, [=]() -> u64 { return memory.Read64(addr); });
|
||||
return monitor.ReadAndMark<u64>(core_index, addr, [&]() -> u64 { return memory.Read64(addr); });
|
||||
}
|
||||
|
||||
u128 DynarmicExclusiveMonitor::ExclusiveRead128(std::size_t core_index, VAddr addr) {
|
||||
return monitor.ReadAndMark<u128>(core_index, addr, [=]() -> u128 {
|
||||
return monitor.ReadAndMark<u128>(core_index, addr, [&]() -> u128 {
|
||||
u128 result;
|
||||
result[0] = memory.Read64(addr);
|
||||
result[1] = memory.Read64(addr + 8);
|
||||
@@ -46,31 +41,31 @@ void DynarmicExclusiveMonitor::ClearExclusive(std::size_t core_index) {
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite8(std::size_t core_index, VAddr vaddr, u8 value) {
|
||||
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [=](u8 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u8>(core_index, vaddr, [&](u8 expected) -> bool {
|
||||
return memory.WriteExclusive8(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite16(std::size_t core_index, VAddr vaddr, u16 value) {
|
||||
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [=](u16 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u16>(core_index, vaddr, [&](u16 expected) -> bool {
|
||||
return memory.WriteExclusive16(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite32(std::size_t core_index, VAddr vaddr, u32 value) {
|
||||
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [=](u32 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u32>(core_index, vaddr, [&](u32 expected) -> bool {
|
||||
return memory.WriteExclusive32(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite64(std::size_t core_index, VAddr vaddr, u64 value) {
|
||||
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [=](u64 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u64>(core_index, vaddr, [&](u64 expected) -> bool {
|
||||
return memory.WriteExclusive64(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
bool DynarmicExclusiveMonitor::ExclusiveWrite128(std::size_t core_index, VAddr vaddr, u128 value) {
|
||||
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [=](u128 expected) -> bool {
|
||||
return monitor.DoExclusiveOperation<u128>(core_index, vaddr, [&](u128 expected) -> bool {
|
||||
return memory.WriteExclusive128(vaddr, value, expected);
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2016 Citra Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -13,6 +16,16 @@ namespace IPC {
|
||||
/// Size of the command buffer area, in 32-bit words.
|
||||
constexpr std::size_t COMMAND_BUFFER_LENGTH = 0x100 / sizeof(u32);
|
||||
|
||||
/// Must match bitfields
|
||||
constexpr std::size_t MAX_BUFFER_DESCRIPTORS = 16;
|
||||
constexpr std::size_t MAX_INCOMING_MOVE_HANDLERS = 16;
|
||||
constexpr std::size_t MAX_INCOMING_COPY_HANDLERS = 16;
|
||||
|
||||
/// Doesn't need to match bitfields but usually not big enough
|
||||
constexpr std::size_t MAX_OUTGOING_COPY_OBJECTS = 16;
|
||||
constexpr std::size_t MAX_OUTGOING_MOVE_OBJECTS = 16;
|
||||
constexpr std::size_t MAX_OUTGOING_DOMAIN_OBJECTS = 16;
|
||||
|
||||
enum class ControlCommand : u32 {
|
||||
ConvertSessionToDomain = 0,
|
||||
ConvertDomainToSession = 1,
|
||||
|
||||
@@ -128,10 +128,12 @@ Result SessionRequestManager::HandleDomainSyncRequest(Kernel::KServerSession* se
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_,
|
||||
Kernel::KServerSession* server_session_,
|
||||
Kernel::KThread* thread_)
|
||||
: server_session(server_session_), thread(thread_), kernel{kernel_}, memory{memory_} {
|
||||
HLERequestContext::HLERequestContext(Kernel::KernelCore& kernel_, Core::Memory::Memory& memory_, Kernel::KServerSession* server_session_, Kernel::KThread* thread_)
|
||||
: server_session(server_session_)
|
||||
, thread(thread_)
|
||||
, kernel{kernel_}
|
||||
, memory{memory_}
|
||||
{
|
||||
cmd_buf[0] = 0;
|
||||
}
|
||||
|
||||
@@ -155,9 +157,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
}
|
||||
if (incoming) {
|
||||
// Populate the object lists with the data in the IPC request.
|
||||
incoming_copy_handles.reserve(handle_descriptor_header->num_handles_to_copy);
|
||||
incoming_move_handles.reserve(handle_descriptor_header->num_handles_to_move);
|
||||
|
||||
for (u32 handle = 0; handle < handle_descriptor_header->num_handles_to_copy; ++handle) {
|
||||
incoming_copy_handles.push_back(rp.Pop<Handle>());
|
||||
}
|
||||
@@ -172,11 +171,6 @@ void HLERequestContext::ParseCommandBuffer(u32_le* src_cmdbuf, bool incoming) {
|
||||
}
|
||||
}
|
||||
|
||||
buffer_x_descriptors.reserve(command_header->num_buf_x_descriptors);
|
||||
buffer_a_descriptors.reserve(command_header->num_buf_a_descriptors);
|
||||
buffer_b_descriptors.reserve(command_header->num_buf_b_descriptors);
|
||||
buffer_w_descriptors.reserve(command_header->num_buf_w_descriptors);
|
||||
|
||||
for (u32 i = 0; i < command_header->num_buf_x_descriptors; ++i) {
|
||||
buffer_x_descriptors.push_back(rp.PopRaw<IPC::BufferDescriptorX>());
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -11,6 +14,7 @@
|
||||
#include <string>
|
||||
#include <type_traits>
|
||||
#include <vector>
|
||||
#include <boost/container/static_vector.hpp>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -181,8 +185,7 @@ private:
|
||||
*/
|
||||
class HLERequestContext {
|
||||
public:
|
||||
explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory,
|
||||
Kernel::KServerSession* session, Kernel::KThread* thread);
|
||||
explicit HLERequestContext(Kernel::KernelCore& kernel, Core::Memory::Memory& memory, Kernel::KServerSession* session, Kernel::KThread* thread);
|
||||
~HLERequestContext();
|
||||
|
||||
/// Returns a pointer to the IPC command buffer for this request.
|
||||
@@ -233,19 +236,19 @@ public:
|
||||
return data_payload_offset;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorX>& BufferDescriptorX() const {
|
||||
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorX, 16>& BufferDescriptorX() const {
|
||||
return buffer_x_descriptors;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorA() const {
|
||||
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorABW, 16>& BufferDescriptorA() const {
|
||||
return buffer_a_descriptors;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorABW>& BufferDescriptorB() const {
|
||||
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorABW, 16>& BufferDescriptorB() const {
|
||||
return buffer_b_descriptors;
|
||||
}
|
||||
|
||||
[[nodiscard]] const std::vector<IPC::BufferDescriptorC>& BufferDescriptorC() const {
|
||||
[[nodiscard]] const boost::container::static_vector<IPC::BufferDescriptorC, 16>& BufferDescriptorC() const {
|
||||
return buffer_c_descriptors;
|
||||
}
|
||||
|
||||
@@ -399,38 +402,35 @@ private:
|
||||
Kernel::KHandleTable* client_handle_table{};
|
||||
Kernel::KThread* thread{};
|
||||
|
||||
std::vector<Handle> incoming_move_handles;
|
||||
std::vector<Handle> incoming_copy_handles;
|
||||
boost::container::static_vector<IPC::BufferDescriptorX, IPC::MAX_BUFFER_DESCRIPTORS> buffer_x_descriptors;
|
||||
boost::container::static_vector<IPC::BufferDescriptorABW, IPC::MAX_BUFFER_DESCRIPTORS> buffer_a_descriptors;
|
||||
boost::container::static_vector<IPC::BufferDescriptorABW, IPC::MAX_BUFFER_DESCRIPTORS> buffer_b_descriptors;
|
||||
boost::container::static_vector<IPC::BufferDescriptorABW, IPC::MAX_BUFFER_DESCRIPTORS> buffer_w_descriptors;
|
||||
boost::container::static_vector<IPC::BufferDescriptorC, IPC::MAX_BUFFER_DESCRIPTORS> buffer_c_descriptors;
|
||||
boost::container::static_vector<Handle, IPC::MAX_INCOMING_MOVE_HANDLERS> incoming_move_handles;
|
||||
boost::container::static_vector<Handle, IPC::MAX_INCOMING_COPY_HANDLERS> incoming_copy_handles;
|
||||
boost::container::static_vector<Kernel::KAutoObject*, IPC::MAX_OUTGOING_MOVE_OBJECTS> outgoing_move_objects;
|
||||
boost::container::static_vector<Kernel::KAutoObject*, IPC::MAX_OUTGOING_COPY_OBJECTS> outgoing_copy_objects;
|
||||
boost::container::static_vector<SessionRequestHandlerPtr, IPC::MAX_OUTGOING_DOMAIN_OBJECTS> outgoing_domain_objects;
|
||||
|
||||
std::vector<Kernel::KAutoObject*> outgoing_move_objects;
|
||||
std::vector<Kernel::KAutoObject*> outgoing_copy_objects;
|
||||
std::vector<SessionRequestHandlerPtr> outgoing_domain_objects;
|
||||
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_a{};
|
||||
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_x{};
|
||||
|
||||
std::optional<IPC::CommandHeader> command_header;
|
||||
std::optional<IPC::HandleDescriptorHeader> handle_descriptor_header;
|
||||
std::optional<IPC::DataPayloadHeader> data_payload_header;
|
||||
std::optional<IPC::DomainMessageHeader> domain_message_header;
|
||||
std::vector<IPC::BufferDescriptorX> buffer_x_descriptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_a_descriptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_b_descriptors;
|
||||
std::vector<IPC::BufferDescriptorABW> buffer_w_descriptors;
|
||||
std::vector<IPC::BufferDescriptorC> buffer_c_descriptors;
|
||||
std::weak_ptr<SessionRequestManager> manager{};
|
||||
Kernel::KernelCore& kernel;
|
||||
Core::Memory::Memory& memory;
|
||||
|
||||
u32_le command{};
|
||||
u64 pid{};
|
||||
u32_le command{};
|
||||
u32 write_size{};
|
||||
u32 data_payload_offset{};
|
||||
u32 handles_offset{};
|
||||
u32 domain_offset{};
|
||||
|
||||
std::weak_ptr<SessionRequestManager> manager{};
|
||||
bool is_deferred{false};
|
||||
|
||||
Kernel::KernelCore& kernel;
|
||||
Core::Memory::Memory& memory;
|
||||
|
||||
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_a{};
|
||||
mutable std::array<Common::ScratchBuffer<u8>, 3> read_buffer_data_x{};
|
||||
bool is_deferred = false;
|
||||
};
|
||||
|
||||
} // namespace Service
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
|
||||
@@ -20,6 +20,7 @@
|
||||
#include "common/settings.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/k_page_table.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/result.h"
|
||||
@@ -124,8 +125,7 @@ json GetFullDataAuto(const std::string& timestamp, u64 title_id, Core::System& s
|
||||
}
|
||||
|
||||
template <bool read_value, typename DescriptorType>
|
||||
json GetHLEBufferDescriptorData(const std::vector<DescriptorType>& buffer,
|
||||
Core::Memory::Memory& memory) {
|
||||
json GetHLEBufferDescriptorData(const boost::container::static_vector<DescriptorType, IPC::MAX_BUFFER_DESCRIPTORS>& buffer, Core::Memory::Memory& memory) {
|
||||
auto buffer_out = json::array();
|
||||
for (const auto& desc : buffer) {
|
||||
auto entry = json{
|
||||
|
||||
@@ -169,6 +169,8 @@ if ("x86_64" IN_LIST ARCHITECTURE)
|
||||
backend/x64/emit_x64_vector.cpp
|
||||
backend/x64/emit_x64_vector_floating_point.cpp
|
||||
backend/x64/emit_x64_vector_saturation.cpp
|
||||
backend/x64/exclusive_monitor.cpp
|
||||
backend/x64/exclusive_monitor_friend.h
|
||||
backend/x64/host_feature.h
|
||||
backend/x64/hostloc.h
|
||||
backend/x64/jitstate_info.h
|
||||
@@ -229,6 +231,7 @@ if ("arm64" IN_LIST ARCHITECTURE)
|
||||
backend/arm64/emit_arm64_vector_floating_point.cpp
|
||||
backend/arm64/emit_arm64_vector_saturation.cpp
|
||||
backend/arm64/emit_context.h
|
||||
backend/arm64/exclusive_monitor.cpp
|
||||
backend/arm64/fastmem.h
|
||||
backend/arm64/fpsr_manager.cpp
|
||||
backend/arm64/fpsr_manager.h
|
||||
@@ -275,6 +278,7 @@ if ("riscv64" IN_LIST ARCHITECTURE)
|
||||
backend/riscv64/emit_riscv64_vector.cpp
|
||||
backend/riscv64/emit_riscv64.cpp
|
||||
backend/riscv64/emit_riscv64.h
|
||||
backend/riscv64/exclusive_monitor.cpp
|
||||
backend/riscv64/reg_alloc.cpp
|
||||
backend/riscv64/reg_alloc.h
|
||||
backend/riscv64/stack_layout.h
|
||||
|
||||
@@ -135,9 +135,12 @@ static void* EmitExclusiveWriteCallTrampoline(oaknut::CodeGenerator& code, const
|
||||
oaknut::Label l_addr, l_this;
|
||||
|
||||
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, T value) -> u32 {
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr, [&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
}) ? 0 : 1;
|
||||
return conf.global_monitor->DoExclusiveOperation<T>(conf.processor_id, vaddr,
|
||||
[&](T expected) -> bool {
|
||||
return (conf.callbacks->*callback)(vaddr, value, expected);
|
||||
})
|
||||
? 0
|
||||
: 1;
|
||||
};
|
||||
|
||||
void* target = code.xptr<void*>();
|
||||
@@ -297,9 +300,12 @@ static void* EmitExclusiveWrite128CallTrampoline(oaknut::CodeGenerator& code, co
|
||||
oaknut::Label l_addr, l_this;
|
||||
|
||||
auto fn = [](const A64::UserConfig& conf, A64::VAddr vaddr, Vector value) -> u32 {
|
||||
return conf.global_monitor->DoExclusiveOperation<Vector>(conf.processor_id, vaddr, [&](Vector expected) -> bool {
|
||||
return conf.callbacks->MemoryWriteExclusive128(vaddr, value, expected);
|
||||
}) ? 0 : 1;
|
||||
return conf.global_monitor->DoExclusiveOperation<Vector>(conf.processor_id, vaddr,
|
||||
[&](Vector expected) -> bool {
|
||||
return conf.callbacks->MemoryWriteExclusive128(vaddr, value, expected);
|
||||
})
|
||||
? 0
|
||||
: 1;
|
||||
};
|
||||
|
||||
void* target = code.xptr<void*>();
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2022 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
*/
|
||||
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#include "common/assert.h"
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
ExclusiveMonitor::ExclusiveMonitor(std::size_t processor_count)
|
||||
: exclusive_addresses(processor_count, INVALID_EXCLUSIVE_ADDRESS), exclusive_values(processor_count) {}
|
||||
|
||||
size_t ExclusiveMonitor::GetProcessorCount() const {
|
||||
return exclusive_addresses.size();
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::Lock() {
|
||||
lock.Lock();
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::Unlock() {
|
||||
lock.Unlock();
|
||||
}
|
||||
|
||||
bool ExclusiveMonitor::CheckAndClear(std::size_t processor_id, VAddr address) {
|
||||
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
|
||||
|
||||
Lock();
|
||||
if (exclusive_addresses[processor_id] != masked_address) {
|
||||
Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (VAddr& other_address : exclusive_addresses) {
|
||||
if (other_address == masked_address) {
|
||||
other_address = INVALID_EXCLUSIVE_ADDRESS;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::Clear() {
|
||||
Lock();
|
||||
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::ClearProcessor(std::size_t processor_id) {
|
||||
Lock();
|
||||
exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS;
|
||||
Unlock();
|
||||
}
|
||||
|
||||
} // namespace Dynarmic
|
||||
@@ -0,0 +1,54 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
ExclusiveMonitor::ExclusiveMonitor(std::size_t processor_count)
|
||||
: exclusive_addresses(processor_count, INVALID_EXCLUSIVE_ADDRESS), exclusive_values(processor_count) {}
|
||||
|
||||
size_t ExclusiveMonitor::GetProcessorCount() const {
|
||||
return exclusive_addresses.size();
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::Lock() {
|
||||
lock.Lock();
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::Unlock() {
|
||||
lock.Unlock();
|
||||
}
|
||||
|
||||
bool ExclusiveMonitor::CheckAndClear(size_t processor_id, VAddr address) {
|
||||
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
|
||||
|
||||
Lock();
|
||||
if (exclusive_addresses[processor_id] != masked_address) {
|
||||
Unlock();
|
||||
return false;
|
||||
}
|
||||
|
||||
for (VAddr& other_address : exclusive_addresses) {
|
||||
if (other_address == masked_address) {
|
||||
other_address = INVALID_EXCLUSIVE_ADDRESS;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::Clear() {
|
||||
Lock();
|
||||
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
|
||||
Unlock();
|
||||
}
|
||||
|
||||
void ExclusiveMonitor::ClearProcessor(size_t processor_id) {
|
||||
Lock();
|
||||
exclusive_addresses[processor_id] = INVALID_EXCLUSIVE_ADDRESS;
|
||||
Unlock();
|
||||
}
|
||||
|
||||
} // namespace Dynarmic
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "dynarmic/backend/x64/abi.h"
|
||||
#include "dynarmic/backend/x64/devirtualize.h"
|
||||
#include "dynarmic/backend/x64/emit_x64_memory.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/backend/x64/exclusive_monitor_friend.h"
|
||||
#include "dynarmic/backend/x64/perf_map.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
|
||||
@@ -174,35 +174,67 @@ void A32EmitX64::EmitA32ClearExclusive(A32EmitContext&, IR::Inst*) {
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<8, &A32::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<16, &A32::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<32, &A32::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveReadMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<64, &A32::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory8(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<8, &A32::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory16(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<16, &A32::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory32(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<32, &A32::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitA32ExclusiveWriteMemory64(A32EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<64, &A32::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A32EmitX64::EmitCheckMemoryAbort(A32EmitContext& ctx, IR::Inst* inst, Xbyak::Label* end) {
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
#include "dynarmic/backend/x64/abi.h"
|
||||
#include "dynarmic/backend/x64/devirtualize.h"
|
||||
#include "dynarmic/backend/x64/emit_x64_memory.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/backend/x64/exclusive_monitor_friend.h"
|
||||
#include "dynarmic/backend/x64/perf_map.h"
|
||||
#include "dynarmic/common/spin_lock_x64.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
@@ -330,43 +330,83 @@ void A64EmitX64::EmitA64ClearExclusive(A64EmitContext&, IR::Inst*) {
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<8, &A64::UserCallbacks::MemoryRead8>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<16, &A64::UserCallbacks::MemoryRead16>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<32, &A64::UserCallbacks::MemoryRead32>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<64, &A64::UserCallbacks::MemoryRead64>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveReadMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveReadMemoryInline<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveReadMemoryInline<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveReadMemory<128, &A64::UserCallbacks::MemoryRead128>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory8(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<8, &A64::UserCallbacks::MemoryWriteExclusive8>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory16(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<16, &A64::UserCallbacks::MemoryWriteExclusive16>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory32(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<32, &A64::UserCallbacks::MemoryWriteExclusive32>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory64(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<64, &A64::UserCallbacks::MemoryWriteExclusive64>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitA64ExclusiveWriteMemory128(A64EmitContext& ctx, IR::Inst* inst) {
|
||||
EmitExclusiveWriteMemoryInline<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
|
||||
if (conf.fastmem_exclusive_access) {
|
||||
EmitExclusiveWriteMemoryInline<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
|
||||
} else {
|
||||
EmitExclusiveWriteMemory<128, &A64::UserCallbacks::MemoryWriteExclusive128>(ctx, inst);
|
||||
}
|
||||
}
|
||||
|
||||
void A64EmitX64::EmitCheckMemoryAbort(A64EmitContext&, IR::Inst* inst, Xbyak::Label* end) {
|
||||
|
||||
@@ -230,11 +230,12 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
code.CallLambda(
|
||||
[](AxxUserConfig& conf, Axx::VAddr vaddr) -> T {
|
||||
return conf.global_monitor->ReadAndMark<T>(conf.processor_id, vaddr, [&]() -> T {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
});
|
||||
});
|
||||
code.ZeroExtendFrom(bitsize, code.ABI_RETURN);
|
||||
} else {
|
||||
const Xbyak::Xmm result = ctx.reg_alloc.ScratchXmm(code);
|
||||
@@ -249,11 +250,12 @@ void AxxEmitX64::EmitExclusiveReadMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
if (ordered) {
|
||||
code.mfence();
|
||||
}
|
||||
code.CallLambda([](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
|
||||
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
code.CallLambda(
|
||||
[](AxxUserConfig& conf, Axx::VAddr vaddr, Vector& ret) {
|
||||
ret = conf.global_monitor->ReadAndMark<Vector>(conf.processor_id, vaddr, [&]() -> Vector {
|
||||
return (conf.callbacks->*callback)(vaddr);
|
||||
});
|
||||
});
|
||||
});
|
||||
code.movups(result, xword[rsp + ABI_SHADOW_SPACE]);
|
||||
ctx.reg_alloc.ReleaseStackSpace(code, 16 + ABI_SHADOW_SPACE);
|
||||
|
||||
@@ -318,12 +320,11 @@ void AxxEmitX64::EmitExclusiveWriteMemory(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
|
||||
template<std::size_t bitsize, auto callback>
|
||||
void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
ASSERT(conf.global_monitor);
|
||||
if (!conf.fastmem_exclusive_access || !exception_handler.SupportsFastmem()) {
|
||||
ASSERT(conf.global_monitor && conf.fastmem_pointer);
|
||||
if (!exception_handler.SupportsFastmem()) {
|
||||
EmitExclusiveReadMemory<bitsize, callback>(ctx, inst);
|
||||
return;
|
||||
}
|
||||
ASSERT(conf.fastmem_pointer);
|
||||
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
constexpr bool ordered = true;
|
||||
@@ -343,10 +344,10 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
|
||||
|
||||
const auto wrapped_fn = read_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value_idx)];
|
||||
|
||||
EmitExclusiveLock(code, conf, tmp2.cvt32());
|
||||
EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
|
||||
|
||||
code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(1));
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + conf.processor_id));
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, conf.processor_id)));
|
||||
code.mov(qword[tmp], vaddr);
|
||||
|
||||
const auto fastmem_marker = ShouldFastmem(ctx, inst);
|
||||
@@ -380,10 +381,10 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
|
||||
code.call(wrapped_fn);
|
||||
}
|
||||
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_values.data() + conf.processor_id));
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorValuePointer(conf.global_monitor, conf.processor_id)));
|
||||
EmitWriteMemoryMov<bitsize>(code, tmp, value_idx, false);
|
||||
|
||||
EmitExclusiveUnlock(code, conf, tmp2.cvt32());
|
||||
EmitExclusiveUnlock(code, conf, tmp, tmp2.cvt32());
|
||||
|
||||
if constexpr (bitsize == 128) {
|
||||
ctx.reg_alloc.DefineValue(code, inst, Xbyak::Xmm{value_idx});
|
||||
@@ -396,12 +397,11 @@ void AxxEmitX64::EmitExclusiveReadMemoryInline(AxxEmitContext& ctx, IR::Inst* in
|
||||
|
||||
template<std::size_t bitsize, auto callback>
|
||||
void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* inst) {
|
||||
ASSERT(conf.global_monitor);
|
||||
if (!conf.fastmem_exclusive_access || !exception_handler.SupportsFastmem()) {
|
||||
ASSERT(conf.global_monitor && conf.fastmem_pointer);
|
||||
if (!exception_handler.SupportsFastmem()) {
|
||||
EmitExclusiveWriteMemory<bitsize, callback>(ctx, inst);
|
||||
return;
|
||||
}
|
||||
ASSERT(conf.fastmem_pointer);
|
||||
|
||||
auto args = ctx.reg_alloc.GetArgumentInfo(inst);
|
||||
constexpr bool ordered = true;
|
||||
@@ -425,7 +425,7 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
|
||||
const auto wrapped_fn = exclusive_write_fallbacks[std::make_tuple(ordered, bitsize, vaddr.getIdx(), value.getIdx())];
|
||||
|
||||
EmitExclusiveLock(code, conf, tmp2.cvt32());
|
||||
EmitExclusiveLock(code, conf, tmp, tmp2.cvt32());
|
||||
|
||||
SharedLabel end = ctx.GenSharedLabel();
|
||||
|
||||
@@ -433,14 +433,14 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
code.movzx(tmp.cvt32(), code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)]);
|
||||
code.test(tmp.cvt8(), tmp.cvt8());
|
||||
code.je(*end, code.T_NEAR);
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + conf.processor_id));
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, conf.processor_id)));
|
||||
code.cmp(qword[tmp], vaddr);
|
||||
code.jne(*end, code.T_NEAR);
|
||||
|
||||
EmitExclusiveTestAndClear(code, conf, vaddr, tmp, rax);
|
||||
|
||||
code.mov(code.byte[code.ABI_JIT_PTR + offsetof(AxxJitState, exclusive_state)], u8(0));
|
||||
code.mov(tmp, std::bit_cast<u64>(conf.global_monitor->exclusive_values.data() + conf.processor_id));
|
||||
code.mov(tmp, std::bit_cast<u64>(GetExclusiveMonitorValuePointer(conf.global_monitor, conf.processor_id)));
|
||||
|
||||
if constexpr (bitsize == 128) {
|
||||
code.mov(rax, qword[tmp + 0]);
|
||||
@@ -519,7 +519,7 @@ void AxxEmitX64::EmitExclusiveWriteMemoryInline(AxxEmitContext& ctx, IR::Inst* i
|
||||
}
|
||||
|
||||
code.L(*end);
|
||||
EmitExclusiveUnlock(code, conf, eax);
|
||||
EmitExclusiveUnlock(code, conf, tmp, eax);
|
||||
ctx.reg_alloc.DefineValue(code, inst, status);
|
||||
EmitCheckMemoryAbort(ctx, inst);
|
||||
}
|
||||
|
||||
@@ -13,7 +13,7 @@
|
||||
|
||||
#include "dynarmic/backend/x64/a32_emit_x64.h"
|
||||
#include "dynarmic/backend/x64/a64_emit_x64.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/backend/x64/exclusive_monitor_friend.h"
|
||||
#include "dynarmic/common/spin_lock_x64.h"
|
||||
#include "dynarmic/interface/exclusive_monitor.h"
|
||||
#include "dynarmic/ir/acc_type.h"
|
||||
@@ -344,36 +344,43 @@ const void* EmitWriteMemoryMov(BlockOfCode& code, const Xbyak::RegExp& addr, int
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg32 tmp) {
|
||||
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
u64 const slp = std::bit_cast<u64>(std::addressof(conf.global_monitor->lock.storage));
|
||||
EmitSpinLockLock(code, dword[slp], tmp, code.HasHostFeature(HostFeature::WAITPKG));
|
||||
void EmitExclusiveLock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 pointer, Xbyak::Reg32 tmp) {
|
||||
if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
|
||||
EmitSpinLockLock(code, pointer, tmp, code.HasHostFeature(HostFeature::WAITPKG));
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
void EmitExclusiveUnlock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg32 tmp) {
|
||||
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
u64 const slp = std::bit_cast<u64>(std::addressof(conf.global_monitor->lock.storage));
|
||||
EmitSpinLockUnlock(code, dword[slp], tmp);
|
||||
void EmitExclusiveUnlock(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 pointer, Xbyak::Reg32 tmp) {
|
||||
if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorLockPointer(conf.global_monitor)));
|
||||
EmitSpinLockUnlock(code, pointer, tmp);
|
||||
}
|
||||
|
||||
template<typename UserConfig>
|
||||
void EmitExclusiveTestAndClear(BlockOfCode& code, const UserConfig& conf, Xbyak::Reg64 vaddr, Xbyak::Reg64 pointer, Xbyak::Reg64 tmp) {
|
||||
if (!conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
code.mov(tmp, 0xDEAD'DEAD'DEAD'DEAD);
|
||||
static_assert(ExclusiveMonitor::MAX_NUM_CPU_CORES == 4);
|
||||
for (size_t i = 0; i < ExclusiveMonitor::MAX_NUM_CPU_CORES; i++) {
|
||||
if (i != conf.processor_id) {
|
||||
Xbyak::Label ok;
|
||||
code.mov(pointer, std::bit_cast<u64>(conf.global_monitor->exclusive_addresses.data() + i));
|
||||
code.cmp(qword[pointer], vaddr);
|
||||
code.jne(ok, code.T_NEAR);
|
||||
code.mov(qword[pointer], tmp);
|
||||
code.L(ok);
|
||||
}
|
||||
if (conf.HasOptimization(OptimizationFlag::Unsafe_IgnoreGlobalMonitor)) {
|
||||
return;
|
||||
}
|
||||
|
||||
code.mov(tmp, 0xDEAD'DEAD'DEAD'DEAD);
|
||||
const size_t processor_count = GetExclusiveMonitorProcessorCount(conf.global_monitor);
|
||||
for (size_t processor_index = 0; processor_index < processor_count; processor_index++) {
|
||||
if (processor_index == conf.processor_id) {
|
||||
continue;
|
||||
}
|
||||
Xbyak::Label ok;
|
||||
code.mov(pointer, std::bit_cast<u64>(GetExclusiveMonitorAddressPointer(conf.global_monitor, processor_index)));
|
||||
code.cmp(qword[pointer], vaddr);
|
||||
code.jne(ok, code.T_NEAR);
|
||||
code.mov(qword[pointer], tmp);
|
||||
code.L(ok);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -22,23 +22,26 @@ static const auto default_cg_mode = nullptr; //Allow RWE
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
/// @brief Emits a lock path for a given spinlock
|
||||
/// @arg ptr Operand must be a dword[ptr]
|
||||
/// @arg waitpkg Whetever or not the "UMWAIT" instruction can be used
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp, bool waitpkg) {
|
||||
// TODO: this is because we lack regalloc - so better to be safe :(
|
||||
// TODO: really involve regalloc when we require a 64 bit disp temporal... aside from the one we got handed of course
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg) {
|
||||
Xbyak::Label start, loop;
|
||||
code.jmp(start, code.T_NEAR);
|
||||
code.L(loop);
|
||||
|
||||
if (waitpkg) {
|
||||
Xbyak::Label start, loop;
|
||||
code.jmp(start, code.T_NEAR);
|
||||
code.L(loop);
|
||||
code.push(Xbyak::util::eax);
|
||||
code.push(Xbyak::util::ebx);
|
||||
code.push(Xbyak::util::edx);
|
||||
// TODO: this is because we lack regalloc - so better to be safe :(
|
||||
code.push(Xbyak::util::rax);
|
||||
code.push(Xbyak::util::rbx);
|
||||
code.push(Xbyak::util::rdx);
|
||||
// TODO: This clobbers EAX and EDX did we tell the regalloc?
|
||||
// ARM ptr for address-monitoring
|
||||
code.mov(Xbyak::util::eax, ptr);
|
||||
code.umonitor(Xbyak::util::eax);
|
||||
|
||||
// XBYAK BUG: code.umonitor(ptr); see issue #255
|
||||
// replace once xbyak has been fixed
|
||||
code.db(0xF3);
|
||||
if (ptr.getIdx() >= 8) code.db(0x41);
|
||||
code.db(0x0F); code.db(0xAE);
|
||||
code.db(uint8_t((3 << 6) | ((6 & 7) << 3) | (ptr.getIdx() & 7)));
|
||||
|
||||
// tmp.bit[0] = 0: C0.1 | Slow Wakup | Better Savings
|
||||
// tmp.bit[0] = 1: C0.2 | Fast Wakup | Lesser Savings
|
||||
// edx:eax is implicitly used as a 64-bit deadline timestamp
|
||||
@@ -52,64 +55,22 @@ void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg
|
||||
code.umwait(Xbyak::util::ebx);
|
||||
// CF == 1 if we hit the OS-timeout in IA32_UMWAIT_CONTROL without a write
|
||||
// CF == 0 if we exited the wait for any other reason
|
||||
code.pop(Xbyak::util::edx);
|
||||
code.pop(Xbyak::util::ebx);
|
||||
code.pop(Xbyak::util::eax);
|
||||
code.L(start);
|
||||
code.mov(tmp, 1);
|
||||
if (ptr.is64bitDisp()) {
|
||||
// if tmp is on eax, use ebx, otherwise use eax!
|
||||
auto const other_tmp = tmp.cvt32() == Xbyak::util::eax
|
||||
? Xbyak::util::rbx
|
||||
: Xbyak::util::rax;
|
||||
code.push(other_tmp);
|
||||
code.mov(other_tmp, ptr.getDisp());
|
||||
/*code.lock();*/ code.xchg(code.dword[other_tmp], tmp);
|
||||
code.pop(other_tmp);
|
||||
} else {
|
||||
/*code.lock();*/ code.xchg(ptr, tmp);
|
||||
}
|
||||
code.test(tmp, tmp);
|
||||
code.jnz(loop, code.T_NEAR);
|
||||
code.pop(Xbyak::util::rdx);
|
||||
code.pop(Xbyak::util::rbx);
|
||||
code.pop(Xbyak::util::rax);
|
||||
} else {
|
||||
Xbyak::Label start, loop;
|
||||
code.jmp(start, code.T_NEAR);
|
||||
code.L(loop);
|
||||
code.pause();
|
||||
code.L(start);
|
||||
code.mov(tmp, 1);
|
||||
if (ptr.is64bitDisp()) {
|
||||
// if tmp is on eax, use ebx, otherwise use eax!
|
||||
auto const other_tmp = tmp.cvt32() == Xbyak::util::eax
|
||||
? Xbyak::util::rbx
|
||||
: Xbyak::util::rax;
|
||||
code.push(other_tmp);
|
||||
code.mov(other_tmp, ptr.getDisp());
|
||||
/*code.lock();*/ code.xchg(code.dword[other_tmp], tmp);
|
||||
code.pop(other_tmp);
|
||||
} else {
|
||||
/*code.lock();*/ code.xchg(ptr, tmp);
|
||||
}
|
||||
code.test(tmp, tmp);
|
||||
code.jnz(loop, code.T_NEAR);
|
||||
}
|
||||
code.L(start);
|
||||
code.mov(tmp, 1);
|
||||
/*code.lock();*/ code.xchg(code.dword[ptr], tmp);
|
||||
code.test(tmp, tmp);
|
||||
code.jnz(loop, code.T_NEAR);
|
||||
}
|
||||
|
||||
// ptr operand must be a dword[ptr]
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp) {
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp) {
|
||||
code.xor_(tmp, tmp);
|
||||
if (ptr.is64bitDisp()) {
|
||||
// if tmp is on eax, use ebx, otherwise use eax!
|
||||
auto const other_tmp = tmp.cvt32() == Xbyak::util::eax
|
||||
? Xbyak::util::rbx
|
||||
: Xbyak::util::rax;
|
||||
code.push(other_tmp);
|
||||
code.mov(other_tmp, ptr.getDisp());
|
||||
/*code.lock();*/ code.xchg(code.dword[other_tmp], tmp);
|
||||
code.pop(other_tmp);
|
||||
} else {
|
||||
/*code.lock();*/ code.xchg(ptr, tmp);
|
||||
}
|
||||
code.xchg(code.dword[ptr], tmp);
|
||||
code.mfence();
|
||||
}
|
||||
|
||||
@@ -132,12 +93,11 @@ void SpinLockImpl::Initialize() noexcept {
|
||||
Xbyak::Reg64 const ABI_PARAM1 = Backend::X64::HostLocToReg64(Backend::X64::ABI_PARAM1);
|
||||
code.align();
|
||||
lock = code.getCurr<void (*)(volatile int*)>();
|
||||
EmitSpinLockLock(code, code.dword[ABI_PARAM1], code.eax, false);
|
||||
EmitSpinLockLock(code, ABI_PARAM1, code.eax, false);
|
||||
code.ret();
|
||||
|
||||
code.align();
|
||||
unlock = code.getCurr<void (*)(volatile int*)>();
|
||||
EmitSpinLockUnlock(code, code.dword[ABI_PARAM1], code.eax);
|
||||
EmitSpinLockUnlock(code, ABI_PARAM1, code.eax);
|
||||
code.ret();
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace Dynarmic {
|
||||
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp, bool waitpkg);
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Address ptr, Xbyak::Reg32 tmp);
|
||||
void EmitSpinLockLock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp, bool waitpkg);
|
||||
void EmitSpinLockUnlock(Xbyak::CodeGenerator& code, Xbyak::Reg64 ptr, Xbyak::Reg32 tmp);
|
||||
|
||||
} // namespace Dynarmic
|
||||
|
||||
@@ -1,6 +1,3 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2018 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -23,71 +20,68 @@ using Vector = std::array<std::uint64_t, 2>;
|
||||
|
||||
class ExclusiveMonitor {
|
||||
public:
|
||||
explicit ExclusiveMonitor() noexcept {
|
||||
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
|
||||
}
|
||||
/// @param processor_count Maximum number of processors using this global
|
||||
/// exclusive monitor. Each processor must have a
|
||||
/// unique id.
|
||||
explicit ExclusiveMonitor(size_t processor_count);
|
||||
|
||||
size_t GetProcessorCount() const;
|
||||
|
||||
/// Marks a region containing [address, address+size) to be exclusive to
|
||||
/// processor index.
|
||||
template<typename T, typename F>
|
||||
[[nodiscard]] inline T ReadAndMark(std::size_t index, VAddr address, F f) {
|
||||
/// processor processor_id.
|
||||
template<typename T, typename Function>
|
||||
T ReadAndMark(size_t processor_id, VAddr address, Function op) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
|
||||
lock.Lock();
|
||||
exclusive_addresses[index] = masked_address;
|
||||
T const value = f();
|
||||
std::memcpy(exclusive_values[index].data(), std::addressof(value), sizeof(T));
|
||||
lock.Unlock();
|
||||
|
||||
Lock();
|
||||
exclusive_addresses[processor_id] = masked_address;
|
||||
const T value = op();
|
||||
std::memcpy(exclusive_values[processor_id].data(), &value, sizeof(T));
|
||||
Unlock();
|
||||
return value;
|
||||
}
|
||||
|
||||
[[nodiscard]] inline bool CheckAndClear(std::size_t index, VAddr address) {
|
||||
const VAddr masked_address = address & RESERVATION_GRANULE_MASK;
|
||||
if (exclusive_addresses[index] != masked_address)
|
||||
return false;
|
||||
for (VAddr& other_address : exclusive_addresses)
|
||||
if (other_address == masked_address)
|
||||
other_address = INVALID_EXCLUSIVE_ADDRESS;
|
||||
return true;
|
||||
}
|
||||
|
||||
/// Checks to see if processor index has exclusive access to the
|
||||
/// Checks to see if processor processor_id has exclusive access to the
|
||||
/// specified region. If it does, executes the operation then clears
|
||||
/// the exclusive state for processors if their exclusive region(s)
|
||||
/// contain [address, address+size).
|
||||
template<typename T, typename F>
|
||||
[[nodiscard]] inline bool DoExclusiveOperation(std::size_t index, VAddr address, F&& f) {
|
||||
template<typename T, typename Function>
|
||||
bool DoExclusiveOperation(size_t processor_id, VAddr address, Function op) {
|
||||
static_assert(std::is_trivially_copyable_v<T>);
|
||||
bool result = false;
|
||||
lock.Lock();
|
||||
if (CheckAndClear(index, address)) {
|
||||
T saved_value{};
|
||||
std::memcpy(std::addressof(saved_value), exclusive_values[index].data(), sizeof(T));
|
||||
result = f(saved_value);
|
||||
if (!CheckAndClear(processor_id, address)) {
|
||||
return false;
|
||||
}
|
||||
lock.Unlock();
|
||||
|
||||
T saved_value;
|
||||
std::memcpy(&saved_value, exclusive_values[processor_id].data(), sizeof(T));
|
||||
const bool result = op(saved_value);
|
||||
|
||||
Unlock();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// Unmark everything.
|
||||
inline void Clear() {
|
||||
lock.Lock();
|
||||
std::fill(exclusive_addresses.begin(), exclusive_addresses.end(), INVALID_EXCLUSIVE_ADDRESS);
|
||||
lock.Unlock();
|
||||
}
|
||||
|
||||
void Clear();
|
||||
/// Unmark processor id
|
||||
inline void ClearProcessor(size_t index) {
|
||||
lock.Lock();
|
||||
exclusive_addresses[index] = INVALID_EXCLUSIVE_ADDRESS;
|
||||
lock.Unlock();
|
||||
}
|
||||
void ClearProcessor(size_t processor_id);
|
||||
|
||||
private:
|
||||
bool CheckAndClear(size_t processor_id, VAddr address);
|
||||
|
||||
void Lock();
|
||||
void Unlock();
|
||||
|
||||
friend volatile int* GetExclusiveMonitorLockPointer(ExclusiveMonitor*);
|
||||
friend size_t GetExclusiveMonitorProcessorCount(ExclusiveMonitor*);
|
||||
friend VAddr* GetExclusiveMonitorAddressPointer(ExclusiveMonitor*, size_t index);
|
||||
friend Vector* GetExclusiveMonitorValuePointer(ExclusiveMonitor*, size_t index);
|
||||
|
||||
static constexpr VAddr RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFFFull;
|
||||
static constexpr VAddr INVALID_EXCLUSIVE_ADDRESS = 0xDEAD'DEAD'DEAD'DEADull;
|
||||
static constexpr size_t MAX_NUM_CPU_CORES = 4; // Sync with src/core/hardware_properties
|
||||
std::array<VAddr, MAX_NUM_CPU_CORES> exclusive_addresses;
|
||||
std::array<Vector, MAX_NUM_CPU_CORES> exclusive_values;
|
||||
boost::container::static_vector<VAddr, MAX_NUM_CPU_CORES> exclusive_addresses;
|
||||
boost::container::static_vector<Vector, MAX_NUM_CPU_CORES> exclusive_values;
|
||||
SpinLock lock;
|
||||
};
|
||||
|
||||
|
||||
@@ -221,12 +221,17 @@ private:
|
||||
Id Texture(EmitContext& ctx, IR::TextureInstInfo info, [[maybe_unused]] const IR::Value& index) {
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
if (def.count > 1) {
|
||||
const DescriptorIndex idx{ctx, index};
|
||||
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
|
||||
idx.Decorate(ctx, pointer);
|
||||
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
|
||||
idx.Decorate(ctx, object);
|
||||
return object;
|
||||
if (Settings::values.legacy_descriptor_indices.GetValue()) {
|
||||
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, ctx.Def(index))};
|
||||
return ctx.OpLoad(def.sampled_type, pointer);
|
||||
} else {
|
||||
const DescriptorIndex idx{ctx, index};
|
||||
const Id pointer{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
|
||||
idx.Decorate(ctx, pointer);
|
||||
const Id object{ctx.OpLoad(def.sampled_type, pointer)};
|
||||
idx.Decorate(ctx, object);
|
||||
return object;
|
||||
}
|
||||
} else {
|
||||
return ctx.OpLoad(def.sampled_type, def.id);
|
||||
}
|
||||
@@ -244,14 +249,20 @@ Id TextureImage(EmitContext& ctx, IR::TextureInstInfo info, const IR::Value& ind
|
||||
} else {
|
||||
const TextureDefinition& def{ctx.textures.at(info.descriptor_index)};
|
||||
if (def.count > 1) {
|
||||
const DescriptorIndex idx{ctx, index};
|
||||
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
|
||||
idx.Decorate(ctx, ptr);
|
||||
const Id object{ctx.OpLoad(def.sampled_type, ptr)};
|
||||
idx.Decorate(ctx, object);
|
||||
const Id image{ctx.OpImage(def.image_type, object)};
|
||||
idx.Decorate(ctx, image);
|
||||
return image;
|
||||
if (Settings::values.legacy_descriptor_indices.GetValue()) {
|
||||
const Id idx{index.IsImmediate() ? ctx.Const(index.U32()) : ctx.Def(index)};
|
||||
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx)};
|
||||
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, ptr));
|
||||
} else {
|
||||
const DescriptorIndex idx{ctx, index};
|
||||
const Id ptr{ctx.OpAccessChain(def.pointer_type, def.id, idx.Value())};
|
||||
idx.Decorate(ctx, ptr);
|
||||
const Id object{ctx.OpLoad(def.sampled_type, ptr)};
|
||||
idx.Decorate(ctx, object);
|
||||
const Id image{ctx.OpImage(def.image_type, object)};
|
||||
idx.Decorate(ctx, image);
|
||||
return image;
|
||||
}
|
||||
}
|
||||
return ctx.OpImage(def.image_type, ctx.OpLoad(def.sampled_type, def.id));
|
||||
}
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include <limits>
|
||||
#include <boost/container/small_vector.hpp>
|
||||
|
||||
#include "common/settings.h"
|
||||
#include "shader_recompiler/environment.h"
|
||||
#include "shader_recompiler/frontend/ir/basic_block.h"
|
||||
#include "shader_recompiler/frontend/ir/breadth_first_search.h"
|
||||
@@ -461,7 +462,7 @@ std::optional<ConstBufferAddr> TryGetConstBuffer(const IR::Inst* inst, Environme
|
||||
.secondary_offset = 0,
|
||||
.secondary_shift_left = 0,
|
||||
.dynamic_offset = dynamic_offset,
|
||||
.count = DynamicDescriptorCount(base_offset, size_shift),
|
||||
.count = Settings::values.legacy_descriptor_indices.GetValue() ? 8 : DynamicDescriptorCount(base_offset, size_shift),
|
||||
.has_secondary = false,
|
||||
};
|
||||
}
|
||||
@@ -733,9 +734,10 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
||||
break;
|
||||
}
|
||||
u32 index;
|
||||
const u32 size_shift{cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset)
|
||||
: DESCRIPTOR_SIZE_SHIFT};
|
||||
u32 count{cbuf.count};
|
||||
u32 size_shift = cbuf.count > 1 ? DynamicDescriptorSizeShift(cbuf.dynamic_offset) : DESCRIPTOR_SIZE_SHIFT;
|
||||
if (Settings::values.legacy_descriptor_indices.GetValue())
|
||||
size_shift = DESCRIPTOR_SIZE_SHIFT;
|
||||
u32 count = cbuf.count;
|
||||
switch (inst->GetOpcode()) {
|
||||
case IR::Opcode::ImageRead:
|
||||
case IR::Opcode::ImageAtomicIAdd32:
|
||||
@@ -821,8 +823,7 @@ void TexturePass(Environment& env, IR::Program& program, const HostTranslateInfo
|
||||
const auto insert_point{IR::Block::InstructionList::s_iterator_to(*inst)};
|
||||
IR::IREmitter ir{*texture_inst.block, insert_point};
|
||||
const IR::U32 shift{ir.Imm32(size_shift)};
|
||||
inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift),
|
||||
ir.Imm32(count - 1)));
|
||||
inst->SetArg(0, ir.UMin(ir.ShiftRightLogical(cbuf.dynamic_offset, shift), ir.Imm32(count - 1)));
|
||||
} else {
|
||||
inst->SetArg(0, IR::Value{});
|
||||
}
|
||||
|
||||
@@ -217,6 +217,12 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_UNORM_BLOCK, 0, ASTC_2D_6X5_UNORM) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ASTC_6x5_SRGB_BLOCK, 0, ASTC_2D_6X5_SRGB) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_E5B9G9R9_UFLOAT_PACK32, 0, E5B9G9R9_FLOAT) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_UNORM_BLOCK, 0, ETC2_RGB_UNORM) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_UNORM_BLOCK, 0, ETC2_RGBA_UNORM) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_UNORM_BLOCK, 0, ETC2_RGB_PTA_UNORM) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8_SRGB_BLOCK, 0, ETC2_RGB_SRGB) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A8_SRGB_BLOCK, 0, ETC2_RGBA_SRGB) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_ETC2_R8G8B8A1_SRGB_BLOCK, 0, ETC2_RGB_PTA_SRGB) \
|
||||
/* Depth formats */ \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_D32_SFLOAT, usage_attachable, D32_FLOAT) \
|
||||
SURFACE_FORMAT_ELEM(VK_FORMAT_D16_UNORM, usage_attachable, D16_UNORM) \
|
||||
@@ -253,8 +259,8 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Transcode on hardware that doesn't support BCn natively
|
||||
if (!device.IsOptimalBcnSupported() && VideoCore::Surface::IsPixelFormatBCn(pixel_format)) {
|
||||
// Transcode on hardware that doesn't support BCn natively
|
||||
if (pixel_format == PixelFormat::BC4_SNORM) {
|
||||
tuple.format = VK_FORMAT_R8_SNORM;
|
||||
} else if (pixel_format == PixelFormat::BC4_UNORM) {
|
||||
@@ -270,6 +276,9 @@ FormatInfo SurfaceFormat(const Device& device, FormatType format_type, bool with
|
||||
} else {
|
||||
tuple.format = VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
}
|
||||
} else if (!device.IsOptimalEtc2Supported() && VideoCore::Surface::IsPixelFormatETC2(pixel_format)) {
|
||||
// Transcode on hardware that doesn't support ETC2 natively
|
||||
tuple.format = is_srgb ? VK_FORMAT_A8B8G8R8_SRGB_PACK32 : VK_FORMAT_A8B8G8R8_UNORM_PACK32;
|
||||
}
|
||||
bool const attachable = (tuple.usage & usage_attachable) != 0;
|
||||
bool const storage = (tuple.usage & usage_storage) != 0;
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: 2014 Citra Emulator Project
|
||||
@@ -336,6 +336,20 @@ bool IsPixelFormatBCn(PixelFormat format) {
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPixelFormatETC2(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::ETC2_RGB_UNORM:
|
||||
case PixelFormat::ETC2_RGBA_UNORM:
|
||||
case PixelFormat::ETC2_RGB_PTA_UNORM:
|
||||
case PixelFormat::ETC2_RGB_SRGB:
|
||||
case PixelFormat::ETC2_RGBA_SRGB:
|
||||
case PixelFormat::ETC2_RGB_PTA_SRGB:
|
||||
return true;
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
bool IsPixelFormatSRGB(PixelFormat format) {
|
||||
switch (format) {
|
||||
case PixelFormat::A8B8G8R8_SRGB:
|
||||
@@ -344,6 +358,9 @@ bool IsPixelFormatSRGB(PixelFormat format) {
|
||||
case PixelFormat::BC2_SRGB:
|
||||
case PixelFormat::BC3_SRGB:
|
||||
case PixelFormat::BC7_SRGB:
|
||||
case PixelFormat::ETC2_RGB_SRGB:
|
||||
case PixelFormat::ETC2_RGBA_SRGB:
|
||||
case PixelFormat::ETC2_RGB_PTA_SRGB:
|
||||
case PixelFormat::ASTC_2D_4X4_SRGB:
|
||||
case PixelFormat::ASTC_2D_8X8_SRGB:
|
||||
case PixelFormat::ASTC_2D_8X5_SRGB:
|
||||
|
||||
@@ -111,6 +111,12 @@ namespace VideoCore::Surface {
|
||||
PIXEL_FORMAT_ELEM(ASTC_2D_6X5_UNORM, 6, 5, 128) \
|
||||
PIXEL_FORMAT_ELEM(ASTC_2D_6X5_SRGB, 6, 5, 128) \
|
||||
PIXEL_FORMAT_ELEM(E5B9G9R9_FLOAT, 1, 1, 32) \
|
||||
PIXEL_FORMAT_ELEM(ETC2_RGB_UNORM, 4, 4, 64) \
|
||||
PIXEL_FORMAT_ELEM(ETC2_RGBA_UNORM, 4, 4, 128) \
|
||||
PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_UNORM, 4, 4, 64) \
|
||||
PIXEL_FORMAT_ELEM(ETC2_RGB_SRGB, 4, 4, 64) \
|
||||
PIXEL_FORMAT_ELEM(ETC2_RGBA_SRGB, 4, 4, 128) \
|
||||
PIXEL_FORMAT_ELEM(ETC2_RGB_PTA_SRGB, 4, 4, 64) \
|
||||
/* Depth formats */ \
|
||||
PIXEL_FORMAT_ELEM(D32_FLOAT, 1, 1, 32) \
|
||||
PIXEL_FORMAT_ELEM(D16_UNORM, 1, 1, 16) \
|
||||
@@ -181,8 +187,6 @@ constexpr u32 BitsPerBlock(PixelFormat format) noexcept {
|
||||
}
|
||||
}
|
||||
|
||||
#undef PIXEL_FORMAT_LIST
|
||||
|
||||
/// Returns the sizer in bytes of the specified pixel format
|
||||
constexpr u32 BytesPerBlock(PixelFormat pixel_format) {
|
||||
return BitsPerBlock(pixel_format) / CHAR_BIT;
|
||||
@@ -198,6 +202,7 @@ SurfaceType GetFormatType(PixelFormat pixel_format);
|
||||
bool HasAlpha(PixelFormat pixel_format);
|
||||
bool IsPixelFormatASTC(PixelFormat format);
|
||||
bool IsPixelFormatBCn(PixelFormat format);
|
||||
bool IsPixelFormatETC2(PixelFormat format);
|
||||
bool IsPixelFormatSRGB(PixelFormat format);
|
||||
bool IsPixelFormatInteger(PixelFormat format);
|
||||
bool IsPixelFormatSignedInteger(PixelFormat format);
|
||||
|
||||
@@ -191,6 +191,20 @@ PixelFormat PixelFormatFromTextureInfo(TextureFormat format, ComponentType red,
|
||||
return PixelFormat::BC6H_SFLOAT;
|
||||
case Hash(TextureFormat::BC6H_U16, FLOAT):
|
||||
return PixelFormat::BC6H_UFLOAT;
|
||||
/* ETC2 */
|
||||
case Hash(TextureFormat::ETC2_RGB, UNORM, LINEAR):
|
||||
return PixelFormat::ETC2_RGB_UNORM;
|
||||
case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, LINEAR):
|
||||
return PixelFormat::ETC2_RGB_PTA_UNORM;
|
||||
case Hash(TextureFormat::ETC2_RGBA, UNORM, LINEAR):
|
||||
return PixelFormat::ETC2_RGBA_UNORM;
|
||||
case Hash(TextureFormat::ETC2_RGB, UNORM, SRGB):
|
||||
return PixelFormat::ETC2_RGB_SRGB;
|
||||
case Hash(TextureFormat::ETC2_RGB_PTA, UNORM, SRGB):
|
||||
return PixelFormat::ETC2_RGB_PTA_SRGB;
|
||||
case Hash(TextureFormat::ETC2_RGBA, UNORM, SRGB):
|
||||
return PixelFormat::ETC2_RGBA_SRGB;
|
||||
/* ASTC */
|
||||
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, LINEAR):
|
||||
return PixelFormat::ASTC_2D_4X4_UNORM;
|
||||
case Hash(TextureFormat::ASTC_2D_4X4, UNORM, SRGB):
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -17,210 +20,9 @@ struct fmt::formatter<VideoCore::Surface::PixelFormat> : fmt::formatter<fmt::str
|
||||
using VideoCore::Surface::PixelFormat;
|
||||
const string_view name = [format] {
|
||||
switch (format) {
|
||||
case PixelFormat::A8B8G8R8_UNORM:
|
||||
return "A8B8G8R8_UNORM";
|
||||
case PixelFormat::A8B8G8R8_SNORM:
|
||||
return "A8B8G8R8_SNORM";
|
||||
case PixelFormat::A8B8G8R8_SINT:
|
||||
return "A8B8G8R8_SINT";
|
||||
case PixelFormat::A8B8G8R8_UINT:
|
||||
return "A8B8G8R8_UINT";
|
||||
case PixelFormat::R5G6B5_UNORM:
|
||||
return "R5G6B5_UNORM";
|
||||
case PixelFormat::B5G6R5_UNORM:
|
||||
return "B5G6R5_UNORM";
|
||||
case PixelFormat::A1R5G5B5_UNORM:
|
||||
return "A1R5G5B5_UNORM";
|
||||
case PixelFormat::A2B10G10R10_UNORM:
|
||||
return "A2B10G10R10_UNORM";
|
||||
case PixelFormat::A2B10G10R10_UINT:
|
||||
return "A2B10G10R10_UINT";
|
||||
case PixelFormat::A2R10G10B10_UNORM:
|
||||
return "A2R10G10B10_UNORM";
|
||||
case PixelFormat::A1B5G5R5_UNORM:
|
||||
return "A1B5G5R5_UNORM";
|
||||
case PixelFormat::A5B5G5R1_UNORM:
|
||||
return "A5B5G5R1_UNORM";
|
||||
case PixelFormat::R8_UNORM:
|
||||
return "R8_UNORM";
|
||||
case PixelFormat::R8_SNORM:
|
||||
return "R8_SNORM";
|
||||
case PixelFormat::R8_SINT:
|
||||
return "R8_SINT";
|
||||
case PixelFormat::R8_UINT:
|
||||
return "R8_UINT";
|
||||
case PixelFormat::R16G16B16A16_FLOAT:
|
||||
return "R16G16B16A16_FLOAT";
|
||||
case PixelFormat::R16G16B16A16_UNORM:
|
||||
return "R16G16B16A16_UNORM";
|
||||
case PixelFormat::R16G16B16A16_SNORM:
|
||||
return "R16G16B16A16_SNORM";
|
||||
case PixelFormat::R16G16B16A16_SINT:
|
||||
return "R16G16B16A16_SINT";
|
||||
case PixelFormat::R16G16B16A16_UINT:
|
||||
return "R16G16B16A16_UINT";
|
||||
case PixelFormat::B10G11R11_FLOAT:
|
||||
return "B10G11R11_FLOAT";
|
||||
case PixelFormat::R32G32B32A32_UINT:
|
||||
return "R32G32B32A32_UINT";
|
||||
case PixelFormat::BC1_RGBA_UNORM:
|
||||
return "BC1_RGBA_UNORM";
|
||||
case PixelFormat::BC2_UNORM:
|
||||
return "BC2_UNORM";
|
||||
case PixelFormat::BC3_UNORM:
|
||||
return "BC3_UNORM";
|
||||
case PixelFormat::BC4_UNORM:
|
||||
return "BC4_UNORM";
|
||||
case PixelFormat::BC4_SNORM:
|
||||
return "BC4_SNORM";
|
||||
case PixelFormat::BC5_UNORM:
|
||||
return "BC5_UNORM";
|
||||
case PixelFormat::BC5_SNORM:
|
||||
return "BC5_SNORM";
|
||||
case PixelFormat::BC7_UNORM:
|
||||
return "BC7_UNORM";
|
||||
case PixelFormat::BC6H_UFLOAT:
|
||||
return "BC6H_UFLOAT";
|
||||
case PixelFormat::BC6H_SFLOAT:
|
||||
return "BC6H_SFLOAT";
|
||||
case PixelFormat::ASTC_2D_4X4_UNORM:
|
||||
return "ASTC_2D_4X4_UNORM";
|
||||
case PixelFormat::B8G8R8A8_UNORM:
|
||||
return "B8G8R8A8_UNORM";
|
||||
case PixelFormat::R32G32B32A32_FLOAT:
|
||||
return "R32G32B32A32_FLOAT";
|
||||
case PixelFormat::R32G32B32A32_SINT:
|
||||
return "R32G32B32A32_SINT";
|
||||
case PixelFormat::R32G32_FLOAT:
|
||||
return "R32G32_FLOAT";
|
||||
case PixelFormat::R32G32_SINT:
|
||||
return "R32G32_SINT";
|
||||
case PixelFormat::R32_FLOAT:
|
||||
return "R32_FLOAT";
|
||||
case PixelFormat::R16_FLOAT:
|
||||
return "R16_FLOAT";
|
||||
case PixelFormat::R16_UNORM:
|
||||
return "R16_UNORM";
|
||||
case PixelFormat::R16_SNORM:
|
||||
return "R16_SNORM";
|
||||
case PixelFormat::R16_UINT:
|
||||
return "R16_UINT";
|
||||
case PixelFormat::R16_SINT:
|
||||
return "R16_SINT";
|
||||
case PixelFormat::R16G16_UNORM:
|
||||
return "R16G16_UNORM";
|
||||
case PixelFormat::R16G16_FLOAT:
|
||||
return "R16G16_FLOAT";
|
||||
case PixelFormat::R16G16_UINT:
|
||||
return "R16G16_UINT";
|
||||
case PixelFormat::R16G16_SINT:
|
||||
return "R16G16_SINT";
|
||||
case PixelFormat::R16G16_SNORM:
|
||||
return "R16G16_SNORM";
|
||||
case PixelFormat::R32G32B32_FLOAT:
|
||||
return "R32G32B32_FLOAT";
|
||||
case PixelFormat::A8B8G8R8_SRGB:
|
||||
return "A8B8G8R8_SRGB";
|
||||
case PixelFormat::R8G8_UNORM:
|
||||
return "R8G8_UNORM";
|
||||
case PixelFormat::R8G8_SNORM:
|
||||
return "R8G8_SNORM";
|
||||
case PixelFormat::R8G8_SINT:
|
||||
return "R8G8_SINT";
|
||||
case PixelFormat::R8G8_UINT:
|
||||
return "R8G8_UINT";
|
||||
case PixelFormat::R32G32_UINT:
|
||||
return "R32G32_UINT";
|
||||
case PixelFormat::R16G16B16X16_FLOAT:
|
||||
return "R16G16B16X16_FLOAT";
|
||||
case PixelFormat::R32_UINT:
|
||||
return "R32_UINT";
|
||||
case PixelFormat::R32_SINT:
|
||||
return "R32_SINT";
|
||||
case PixelFormat::ASTC_2D_8X8_UNORM:
|
||||
return "ASTC_2D_8X8_UNORM";
|
||||
case PixelFormat::ASTC_2D_8X5_UNORM:
|
||||
return "ASTC_2D_8X5_UNORM";
|
||||
case PixelFormat::ASTC_2D_5X4_UNORM:
|
||||
return "ASTC_2D_5X4_UNORM";
|
||||
case PixelFormat::B8G8R8A8_SRGB:
|
||||
return "B8G8R8A8_SRGB";
|
||||
case PixelFormat::BC1_RGBA_SRGB:
|
||||
return "BC1_RGBA_SRGB";
|
||||
case PixelFormat::BC2_SRGB:
|
||||
return "BC2_SRGB";
|
||||
case PixelFormat::BC3_SRGB:
|
||||
return "BC3_SRGB";
|
||||
case PixelFormat::BC7_SRGB:
|
||||
return "BC7_SRGB";
|
||||
case PixelFormat::A4B4G4R4_UNORM:
|
||||
return "A4B4G4R4_UNORM";
|
||||
case PixelFormat::G4R4_UNORM:
|
||||
return "G4R4_UNORM";
|
||||
case PixelFormat::ASTC_2D_4X4_SRGB:
|
||||
return "ASTC_2D_4X4_SRGB";
|
||||
case PixelFormat::ASTC_2D_8X8_SRGB:
|
||||
return "ASTC_2D_8X8_SRGB";
|
||||
case PixelFormat::ASTC_2D_8X5_SRGB:
|
||||
return "ASTC_2D_8X5_SRGB";
|
||||
case PixelFormat::ASTC_2D_5X4_SRGB:
|
||||
return "ASTC_2D_5X4_SRGB";
|
||||
case PixelFormat::ASTC_2D_5X5_UNORM:
|
||||
return "ASTC_2D_5X5_UNORM";
|
||||
case PixelFormat::ASTC_2D_5X5_SRGB:
|
||||
return "ASTC_2D_5X5_SRGB";
|
||||
case PixelFormat::ASTC_2D_10X8_UNORM:
|
||||
return "ASTC_2D_10X8_UNORM";
|
||||
case PixelFormat::ASTC_2D_10X8_SRGB:
|
||||
return "ASTC_2D_10X8_SRGB";
|
||||
case PixelFormat::ASTC_2D_6X6_UNORM:
|
||||
return "ASTC_2D_6X6_UNORM";
|
||||
case PixelFormat::ASTC_2D_6X6_SRGB:
|
||||
return "ASTC_2D_6X6_SRGB";
|
||||
case PixelFormat::ASTC_2D_10X6_UNORM:
|
||||
return "ASTC_2D_10X6_UNORM";
|
||||
case PixelFormat::ASTC_2D_10X6_SRGB:
|
||||
return "ASTC_2D_10X6_SRGB";
|
||||
case PixelFormat::ASTC_2D_10X5_UNORM:
|
||||
return "ASTC_2D_10X5_UNORM";
|
||||
case PixelFormat::ASTC_2D_10X5_SRGB:
|
||||
return "ASTC_2D_10X5_SRGB";
|
||||
case PixelFormat::ASTC_2D_10X10_UNORM:
|
||||
return "ASTC_2D_10X10_UNORM";
|
||||
case PixelFormat::ASTC_2D_10X10_SRGB:
|
||||
return "ASTC_2D_10X10_SRGB";
|
||||
case PixelFormat::ASTC_2D_12X10_UNORM:
|
||||
return "ASTC_2D_12X10_UNORM";
|
||||
case PixelFormat::ASTC_2D_12X10_SRGB:
|
||||
return "ASTC_2D_12X10_SRGB";
|
||||
case PixelFormat::ASTC_2D_12X12_UNORM:
|
||||
return "ASTC_2D_12X12_UNORM";
|
||||
case PixelFormat::ASTC_2D_12X12_SRGB:
|
||||
return "ASTC_2D_12X12_SRGB";
|
||||
case PixelFormat::ASTC_2D_8X6_UNORM:
|
||||
return "ASTC_2D_8X6_UNORM";
|
||||
case PixelFormat::ASTC_2D_8X6_SRGB:
|
||||
return "ASTC_2D_8X6_SRGB";
|
||||
case PixelFormat::ASTC_2D_6X5_UNORM:
|
||||
return "ASTC_2D_6X5_UNORM";
|
||||
case PixelFormat::ASTC_2D_6X5_SRGB:
|
||||
return "ASTC_2D_6X5_SRGB";
|
||||
case PixelFormat::E5B9G9R9_FLOAT:
|
||||
return "E5B9G9R9_FLOAT";
|
||||
case PixelFormat::D32_FLOAT:
|
||||
return "D32_FLOAT";
|
||||
case PixelFormat::D16_UNORM:
|
||||
return "D16_UNORM";
|
||||
case PixelFormat::X8_D24_UNORM:
|
||||
return "X8_D24_UNORM";
|
||||
case PixelFormat::S8_UINT:
|
||||
return "S8_UINT";
|
||||
case PixelFormat::D24_UNORM_S8_UINT:
|
||||
return "D24_UNORM_S8_UINT";
|
||||
case PixelFormat::S8_UINT_D24_UNORM:
|
||||
return "S8_UINT_D24_UNORM";
|
||||
case PixelFormat::D32_FLOAT_S8_UINT:
|
||||
return "D32_FLOAT_S8_UINT";
|
||||
#define PIXEL_FORMAT_ELEM(NAME, ...) case PixelFormat::NAME: return #NAME;
|
||||
PIXEL_FORMAT_LIST
|
||||
#undef PIXEL_FORMAT_ELEM
|
||||
case PixelFormat::MaxDepthStencilFormat:
|
||||
case PixelFormat::Invalid:
|
||||
return "Invalid";
|
||||
|
||||
@@ -363,6 +363,11 @@ public:
|
||||
return features.features.textureCompressionBC;
|
||||
}
|
||||
|
||||
/// Returns true if ETC2 is natively supported.
|
||||
bool IsOptimalEtc2Supported() const {
|
||||
return features.features.textureCompressionETC2;
|
||||
}
|
||||
|
||||
/// Returns true if descriptor aliasing is natively supported.
|
||||
bool IsDescriptorAliasingSupported() const {
|
||||
return GetDriverID() != VK_DRIVER_ID_QUALCOMM_PROPRIETARY;
|
||||
|
||||
Reference in New Issue
Block a user