summaryrefslogtreecommitdiffstats
path: root/src/common
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--src/common/CMakeLists.txt20
-rw-r--r--src/common/fs/file.cpp10
-rw-r--r--src/common/fs/file.h3
-rw-r--r--src/common/fs/fs.cpp18
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/host_memory.cpp538
-rw-r--r--src/common/host_memory.h70
-rw-r--r--src/common/logging/backend.cpp147
-rw-r--r--src/common/logging/backend.h43
-rw-r--r--src/common/logging/filter.cpp132
-rw-r--r--src/common/logging/filter.h12
-rw-r--r--src/common/logging/log.h120
-rw-r--r--src/common/logging/text_formatter.cpp2
-rw-r--r--src/common/logging/types.h142
-rw-r--r--src/common/page_table.h2
-rw-r--r--src/common/settings.cpp10
-rw-r--r--src/common/settings.h6
17 files changed, 975 insertions, 302 deletions
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt
index 7a4d9e354..7534eb8f1 100644
--- a/src/common/CMakeLists.txt
+++ b/src/common/CMakeLists.txt
@@ -21,14 +21,14 @@ find_package(Git QUIET)
add_custom_command(OUTPUT scm_rev.cpp
COMMAND ${CMAKE_COMMAND}
- -DSRC_DIR="${CMAKE_SOURCE_DIR}"
- -DBUILD_REPOSITORY="${BUILD_REPOSITORY}"
- -DTITLE_BAR_FORMAT_IDLE="${TITLE_BAR_FORMAT_IDLE}"
- -DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}"
- -DBUILD_TAG="${BUILD_TAG}"
- -DBUILD_ID="${DISPLAY_VERSION}"
- -DGIT_EXECUTABLE="${GIT_EXECUTABLE}"
- -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
+ -DSRC_DIR=${CMAKE_SOURCE_DIR}
+ -DBUILD_REPOSITORY=${BUILD_REPOSITORY}
+ -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE}
+ -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING}
+ -DBUILD_TAG=${BUILD_TAG}
+ -DBUILD_ID=${DISPLAY_VERSION}
+ -DGIT_EXECUTABLE=${GIT_EXECUTABLE}
+ -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake
DEPENDS
# WARNING! It was too much work to try and make a common location for this list,
# so if you need to change it, please update CMakeModules/GenerateSCMRev.cmake as well
@@ -92,6 +92,7 @@ add_custom_command(OUTPUT scm_rev.cpp
"${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h"
# technically we should regenerate if the git version changed, but its not worth the effort imo
"${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake"
+ VERBATIM
)
add_library(common STATIC
@@ -130,6 +131,8 @@ add_library(common STATIC
hash.h
hex_util.cpp
hex_util.h
+ host_memory.cpp
+ host_memory.h
intrusive_red_black_tree.h
logging/backend.cpp
logging/backend.h
@@ -138,6 +141,7 @@ add_library(common STATIC
logging/log.h
logging/text_formatter.cpp
logging/text_formatter.h
+ logging/types.h
lz4_compression.cpp
lz4_compression.h
math_util.h
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp
index 9f3de1cb0..710e88b39 100644
--- a/src/common/fs/file.cpp
+++ b/src/common/fs/file.cpp
@@ -183,10 +183,6 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type,
size_t AppendStringToFile(const std::filesystem::path& path, FileType type,
std::string_view string) {
- if (!Exists(path)) {
- return WriteStringToFile(path, type, string);
- }
-
if (!IsFile(path)) {
return 0;
}
@@ -309,7 +305,11 @@ bool IOFile::Flush() const {
errno = 0;
- const auto flush_result = std::fflush(file) == 0;
+#ifdef _WIN32
+ const auto flush_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0;
+#else
+ const auto flush_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0;
+#endif
if (!flush_result) {
const auto ec = std::error_code{errno, std::generic_category()};
diff --git a/src/common/fs/file.h b/src/common/fs/file.h
index 50e270c5b..0f10b6003 100644
--- a/src/common/fs/file.h
+++ b/src/common/fs/file.h
@@ -71,7 +71,7 @@ template <typename Path>
/**
* Writes a string to a file at path and returns the number of characters successfully written.
- * If an file already exists at path, its contents will be erased.
+ * If a file already exists at path, its contents will be erased.
* If the filesystem object at path is not a file, this function returns 0.
*
* @param path Filesystem path
@@ -95,7 +95,6 @@ template <typename Path>
/**
* Appends a string to a file at path and returns the number of characters successfully written.
- * If a file does not exist at path, WriteStringToFile is called instead.
* If the filesystem object at path is not a file, this function returns 0.
*
* @param path Filesystem path
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp
index d492480d9..d3159e908 100644
--- a/src/common/fs/fs.cpp
+++ b/src/common/fs/fs.cpp
@@ -321,7 +321,8 @@ bool RemoveDirContentsRecursively(const fs::path& path) {
std::error_code ec;
- for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
+ // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC.
+ for (const auto& entry : fs::directory_iterator(path, ec)) {
if (ec) {
LOG_ERROR(Common_Filesystem,
"Failed to completely enumerate the directory at path={}, ec_message={}",
@@ -337,6 +338,12 @@ bool RemoveDirContentsRecursively(const fs::path& path) {
PathToUTF8String(entry.path()), ec.message());
break;
}
+
+ // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator.
+ // recursive_directory_iterator throws an exception despite passing in a std::error_code.
+ if (entry.status().type() == fs::file_type::directory) {
+ return RemoveDirContentsRecursively(entry.path());
+ }
}
if (ec) {
@@ -475,7 +482,8 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
std::error_code ec;
- for (const auto& entry : fs::recursive_directory_iterator(path, ec)) {
+ // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC.
+ for (const auto& entry : fs::directory_iterator(path, ec)) {
if (ec) {
break;
}
@@ -495,6 +503,12 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path,
break;
}
}
+
+ // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator.
+ // recursive_directory_iterator throws an exception despite passing in a std::error_code.
+ if (entry.status().type() == fs::file_type::directory) {
+ IterateDirEntriesRecursively(entry.path(), callback, filter);
+ }
}
if (callback_error || ec) {
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h
index 14e8c35d7..f956ac9a2 100644
--- a/src/common/fs/path_util.h
+++ b/src/common/fs/path_util.h
@@ -209,7 +209,7 @@ void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path);
#ifdef _WIN32
template <typename Path>
-[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
+void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) {
if constexpr (IsChar<typename Path::value_type>) {
SetYuzuPath(yuzu_path, ToU8String(new_path));
} else {
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp
new file mode 100644
index 000000000..8bd70abc7
--- /dev/null
+++ b/src/common/host_memory.cpp
@@ -0,0 +1,538 @@
+#ifdef _WIN32
+
+#include <iterator>
+#include <unordered_map>
+#include <boost/icl/separate_interval_set.hpp>
+#include <windows.h>
+#include "common/dynamic_library.h"
+
+#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv
+
+#ifndef _GNU_SOURCE
+#define _GNU_SOURCE
+#endif
+#include <fcntl.h>
+#include <sys/mman.h>
+#include <unistd.h>
+
+#endif // ^^^ Linux ^^^
+
+#include <mutex>
+
+#include "common/alignment.h"
+#include "common/assert.h"
+#include "common/host_memory.h"
+#include "common/logging/log.h"
+#include "common/scope_exit.h"
+
+namespace Common {
+
+constexpr size_t PageAlignment = 0x1000;
+constexpr size_t HugePageSize = 0x200000;
+
+#ifdef _WIN32
+
+// Manually imported for MinGW compatibility
+#ifndef MEM_RESERVE_PLACEHOLDER
+#define MEM_RESERVE_PLACEHOLDER 0x0004000
+#endif
+#ifndef MEM_REPLACE_PLACEHOLDER
+#define MEM_REPLACE_PLACEHOLDER 0x00004000
+#endif
+#ifndef MEM_COALESCE_PLACEHOLDERS
+#define MEM_COALESCE_PLACEHOLDERS 0x00000001
+#endif
+#ifndef MEM_PRESERVE_PLACEHOLDER
+#define MEM_PRESERVE_PLACEHOLDER 0x00000002
+#endif
+
+using PFN_CreateFileMapping2 = _Ret_maybenull_ HANDLE(WINAPI*)(
+ _In_ HANDLE File, _In_opt_ SECURITY_ATTRIBUTES* SecurityAttributes, _In_ ULONG DesiredAccess,
+ _In_ ULONG PageProtection, _In_ ULONG AllocationAttributes, _In_ ULONG64 MaximumSize,
+ _In_opt_ PCWSTR Name,
+ _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ _In_ ULONG ParameterCount);
+
+using PFN_VirtualAlloc2 = _Ret_maybenull_ PVOID(WINAPI*)(
+ _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress, _In_ SIZE_T Size,
+ _In_ ULONG AllocationType, _In_ ULONG PageProtection,
+ _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ _In_ ULONG ParameterCount);
+
+using PFN_MapViewOfFile3 = _Ret_maybenull_ PVOID(WINAPI*)(
+ _In_ HANDLE FileMapping, _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress,
+ _In_ ULONG64 Offset, _In_ SIZE_T ViewSize, _In_ ULONG AllocationType, _In_ ULONG PageProtection,
+ _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters,
+ _In_ ULONG ParameterCount);
+
+using PFN_UnmapViewOfFile2 = BOOL(WINAPI*)(_In_ HANDLE Process, _In_ PVOID BaseAddress,
+ _In_ ULONG UnmapFlags);
+
+template <typename T>
+static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn) {
+ if (!dll.GetSymbol(name, &pfn)) {
+ LOG_CRITICAL(HW_Memory, "Failed to load {}", name);
+ throw std::bad_alloc{};
+ }
+}
+
+class HostMemory::Impl {
+public:
+ explicit Impl(size_t backing_size_, size_t virtual_size_)
+ : backing_size{backing_size_}, virtual_size{virtual_size_}, process{GetCurrentProcess()},
+ kernelbase_dll("Kernelbase") {
+ if (!kernelbase_dll.IsOpen()) {
+ LOG_CRITICAL(HW_Memory, "Failed to load Kernelbase.dll");
+ throw std::bad_alloc{};
+ }
+ GetFuncAddress(kernelbase_dll, "CreateFileMapping2", pfn_CreateFileMapping2);
+ GetFuncAddress(kernelbase_dll, "VirtualAlloc2", pfn_VirtualAlloc2);
+ GetFuncAddress(kernelbase_dll, "MapViewOfFile3", pfn_MapViewOfFile3);
+ GetFuncAddress(kernelbase_dll, "UnmapViewOfFile2", pfn_UnmapViewOfFile2);
+
+ // Allocate backing file map
+ backing_handle =
+ pfn_CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ,
+ PAGE_READWRITE, SEC_COMMIT, backing_size, nullptr, nullptr, 0);
+ if (!backing_handle) {
+ LOG_CRITICAL(HW_Memory, "Failed to allocate {} MiB of backing memory",
+ backing_size >> 20);
+ throw std::bad_alloc{};
+ }
+ // Allocate a virtual memory for the backing file map as placeholder
+ backing_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, backing_size,
+ MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
+ PAGE_NOACCESS, nullptr, 0));
+ if (!backing_base) {
+ Release();
+ LOG_CRITICAL(HW_Memory, "Failed to reserve {} MiB of virtual memory",
+ backing_size >> 20);
+ throw std::bad_alloc{};
+ }
+ // Map backing placeholder
+ void* const ret = pfn_MapViewOfFile3(backing_handle, process, backing_base, 0, backing_size,
+ MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0);
+ if (ret != backing_base) {
+ Release();
+ LOG_CRITICAL(HW_Memory, "Failed to map {} MiB of virtual memory", backing_size >> 20);
+ throw std::bad_alloc{};
+ }
+ // Allocate virtual address placeholder
+ virtual_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, virtual_size,
+ MEM_RESERVE | MEM_RESERVE_PLACEHOLDER,
+ PAGE_NOACCESS, nullptr, 0));
+ if (!virtual_base) {
+ Release();
+ LOG_CRITICAL(HW_Memory, "Failed to reserve {} GiB of virtual memory",
+ virtual_size >> 30);
+ throw std::bad_alloc{};
+ }
+ }
+
+ ~Impl() {
+ Release();
+ }
+
+ void Map(size_t virtual_offset, size_t host_offset, size_t length) {
+ std::unique_lock lock{placeholder_mutex};
+ if (!IsNiechePlaceholder(virtual_offset, length)) {
+ Split(virtual_offset, length);
+ }
+ ASSERT(placeholders.find({virtual_offset, virtual_offset + length}) == placeholders.end());
+ TrackPlaceholder(virtual_offset, host_offset, length);
+
+ MapView(virtual_offset, host_offset, length);
+ }
+
+ void Unmap(size_t virtual_offset, size_t length) {
+ std::lock_guard lock{placeholder_mutex};
+
+ // Unmap until there are no more placeholders
+ while (UnmapOnePlaceholder(virtual_offset, length)) {
+ }
+ }
+
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+ DWORD new_flags{};
+ if (read && write) {
+ new_flags = PAGE_READWRITE;
+ } else if (read && !write) {
+ new_flags = PAGE_READONLY;
+ } else if (!read && !write) {
+ new_flags = PAGE_NOACCESS;
+ } else {
+ UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write);
+ }
+ const size_t virtual_end = virtual_offset + length;
+
+ std::lock_guard lock{placeholder_mutex};
+ auto [it, end] = placeholders.equal_range({virtual_offset, virtual_end});
+ while (it != end) {
+ const size_t offset = std::max(it->lower(), virtual_offset);
+ const size_t protect_length = std::min(it->upper(), virtual_end) - offset;
+ DWORD old_flags{};
+ if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) {
+ LOG_CRITICAL(HW_Memory, "Failed to change virtual memory protect rules");
+ }
+ ++it;
+ }
+ }
+
+ const size_t backing_size; ///< Size of the backing memory in bytes
+ const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
+
+ u8* backing_base{};
+ u8* virtual_base{};
+
+private:
+ /// Release all resources in the object
+ void Release() {
+ if (!placeholders.empty()) {
+ for (const auto& placeholder : placeholders) {
+ if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder.lower(),
+ MEM_PRESERVE_PLACEHOLDER)) {
+ LOG_CRITICAL(HW_Memory, "Failed to unmap virtual memory placeholder");
+ }
+ }
+ Coalesce(0, virtual_size);
+ }
+ if (virtual_base) {
+ if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) {
+ LOG_CRITICAL(HW_Memory, "Failed to free virtual memory");
+ }
+ }
+ if (backing_base) {
+ if (!pfn_UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) {
+ LOG_CRITICAL(HW_Memory, "Failed to unmap backing memory placeholder");
+ }
+ if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) {
+ LOG_CRITICAL(HW_Memory, "Failed to free backing memory");
+ }
+ }
+ if (!CloseHandle(backing_handle)) {
+ LOG_CRITICAL(HW_Memory, "Failed to free backing memory file handle");
+ }
+ }
+
+ /// Unmap one placeholder in the given range (partial unmaps are supported)
+ /// Return true when there are no more placeholders to unmap
+ bool UnmapOnePlaceholder(size_t virtual_offset, size_t length) {
+ const auto it = placeholders.find({virtual_offset, virtual_offset + length});
+ const auto begin = placeholders.begin();
+ const auto end = placeholders.end();
+ if (it == end) {
+ return false;
+ }
+ const size_t placeholder_begin = it->lower();
+ const size_t placeholder_end = it->upper();
+ const size_t unmap_begin = std::max(virtual_offset, placeholder_begin);
+ const size_t unmap_end = std::min(virtual_offset + length, placeholder_end);
+ ASSERT(unmap_begin >= placeholder_begin && unmap_begin < placeholder_end);
+ ASSERT(unmap_end <= placeholder_end && unmap_end > placeholder_begin);
+
+ const auto host_pointer_it = placeholder_host_pointers.find(placeholder_begin);
+ ASSERT(host_pointer_it != placeholder_host_pointers.end());
+ const size_t host_offset = host_pointer_it->second;
+
+ const bool split_left = unmap_begin > placeholder_begin;
+ const bool split_right = unmap_end < placeholder_end;
+
+ if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder_begin,
+ MEM_PRESERVE_PLACEHOLDER)) {
+ LOG_CRITICAL(HW_Memory, "Failed to unmap placeholder");
+ }
+ // If we have to remap memory regions due to partial unmaps, we are in a data race as
+ // Windows doesn't support remapping memory without unmapping first. Avoid adding any extra
+ // logic within the panic region described below.
+
+ // Panic region, we are in a data race right now
+ if (split_left || split_right) {
+ Split(unmap_begin, unmap_end - unmap_begin);
+ }
+ if (split_left) {
+ MapView(placeholder_begin, host_offset, unmap_begin - placeholder_begin);
+ }
+ if (split_right) {
+ MapView(unmap_end, host_offset + unmap_end - placeholder_begin,
+ placeholder_end - unmap_end);
+ }
+ // End panic region
+
+ size_t coalesce_begin = unmap_begin;
+ if (!split_left) {
+ // Try to coalesce pages to the left
+ coalesce_begin = it == begin ? 0 : std::prev(it)->upper();
+ if (coalesce_begin != placeholder_begin) {
+ Coalesce(coalesce_begin, unmap_end - coalesce_begin);
+ }
+ }
+ if (!split_right) {
+ // Try to coalesce pages to the right
+ const auto next = std::next(it);
+ const size_t next_begin = next == end ? virtual_size : next->lower();
+ if (placeholder_end != next_begin) {
+ // We can coalesce to the right
+ Coalesce(coalesce_begin, next_begin - coalesce_begin);
+ }
+ }
+ // Remove and reinsert placeholder trackers
+ UntrackPlaceholder(it);
+ if (split_left) {
+ TrackPlaceholder(placeholder_begin, host_offset, unmap_begin - placeholder_begin);
+ }
+ if (split_right) {
+ TrackPlaceholder(unmap_end, host_offset + unmap_end - placeholder_begin,
+ placeholder_end - unmap_end);
+ }
+ return true;
+ }
+
+ void MapView(size_t virtual_offset, size_t host_offset, size_t length) {
+ if (!pfn_MapViewOfFile3(backing_handle, process, virtual_base + virtual_offset, host_offset,
+ length, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0)) {
+ LOG_CRITICAL(HW_Memory, "Failed to map placeholder");
+ }
+ }
+
+ void Split(size_t virtual_offset, size_t length) {
+ if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length,
+ MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) {
+ LOG_CRITICAL(HW_Memory, "Failed to split placeholder");
+ }
+ }
+
+ void Coalesce(size_t virtual_offset, size_t length) {
+ if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length,
+ MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) {
+ LOG_CRITICAL(HW_Memory, "Failed to coalesce placeholders");
+ }
+ }
+
+ void TrackPlaceholder(size_t virtual_offset, size_t host_offset, size_t length) {
+ placeholders.insert({virtual_offset, virtual_offset + length});
+ placeholder_host_pointers.emplace(virtual_offset, host_offset);
+ }
+
+ void UntrackPlaceholder(boost::icl::separate_interval_set<size_t>::iterator it) {
+ placeholders.erase(it);
+ placeholder_host_pointers.erase(it->lower());
+ }
+
+ /// Return true when a given memory region is a "nieche" and the placeholders don't have to be
+ /// splitted.
+ bool IsNiechePlaceholder(size_t virtual_offset, size_t length) const {
+ const auto it = placeholders.upper_bound({virtual_offset, virtual_offset + length});
+ if (it != placeholders.end() && it->lower() == virtual_offset + length) {
+ const bool is_root = it == placeholders.begin() && virtual_offset == 0;
+ return is_root || std::prev(it)->upper() == virtual_offset;
+ }
+ return false;
+ }
+
+ HANDLE process{}; ///< Current process handle
+ HANDLE backing_handle{}; ///< File based backing memory
+
+ DynamicLibrary kernelbase_dll;
+ PFN_CreateFileMapping2 pfn_CreateFileMapping2{};
+ PFN_VirtualAlloc2 pfn_VirtualAlloc2{};
+ PFN_MapViewOfFile3 pfn_MapViewOfFile3{};
+ PFN_UnmapViewOfFile2 pfn_UnmapViewOfFile2{};
+
+ std::mutex placeholder_mutex; ///< Mutex for placeholders
+ boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders
+ std::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset
+};
+
+#elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv
+
+class HostMemory::Impl {
+public:
+ explicit Impl(size_t backing_size_, size_t virtual_size_)
+ : backing_size{backing_size_}, virtual_size{virtual_size_} {
+ bool good = false;
+ SCOPE_EXIT({
+ if (!good) {
+ Release();
+ }
+ });
+
+ // Backing memory initialization
+ fd = memfd_create("HostMemory", 0);
+ if (fd == -1) {
+ LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno));
+ throw std::bad_alloc{};
+ }
+
+ // Defined to extend the file with zeros
+ int ret = ftruncate(fd, backing_size);
+ if (ret != 0) {
+ LOG_CRITICAL(HW_Memory, "ftruncate failed with {}, are you out-of-memory?",
+ strerror(errno));
+ throw std::bad_alloc{};
+ }
+
+ backing_base = static_cast<u8*>(
+ mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0));
+ if (backing_base == MAP_FAILED) {
+ LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
+ throw std::bad_alloc{};
+ }
+
+ // Virtual memory initialization
+ virtual_base = static_cast<u8*>(
+ mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0));
+ if (virtual_base == MAP_FAILED) {
+ LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
+ throw std::bad_alloc{};
+ }
+
+ good = true;
+ }
+
+ ~Impl() {
+ Release();
+ }
+
+ void Map(size_t virtual_offset, size_t host_offset, size_t length) {
+
+ void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE,
+ MAP_SHARED | MAP_FIXED, fd, host_offset);
+ ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
+ }
+
+ void Unmap(size_t virtual_offset, size_t length) {
+ // The method name is wrong. We're still talking about the virtual range.
+ // We don't want to unmap, we want to reserve this memory.
+
+ void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE,
+ MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0);
+ ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno));
+ }
+
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+ int flags = 0;
+ if (read) {
+ flags |= PROT_READ;
+ }
+ if (write) {
+ flags |= PROT_WRITE;
+ }
+ int ret = mprotect(virtual_base + virtual_offset, length, flags);
+ ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno));
+ }
+
+ const size_t backing_size; ///< Size of the backing memory in bytes
+ const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
+
+ u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
+ u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
+
+private:
+ /// Release all resources in the object
+ void Release() {
+ if (virtual_base != MAP_FAILED) {
+ int ret = munmap(virtual_base, virtual_size);
+ ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
+ }
+
+ if (backing_base != MAP_FAILED) {
+ int ret = munmap(backing_base, backing_size);
+ ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno));
+ }
+
+ if (fd != -1) {
+ int ret = close(fd);
+ ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
+ }
+ }
+
+ int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
+};
+
+#else // ^^^ Linux ^^^ vvv Generic vvv
+
+class HostMemory::Impl {
+public:
+ explicit Impl(size_t /*backing_size */, size_t /* virtual_size */) {
+ // This is just a place holder.
+ // Please implement fastmem in a propper way on your platform.
+ throw std::bad_alloc{};
+ }
+
+ void Map(size_t virtual_offset, size_t host_offset, size_t length) {}
+
+ void Unmap(size_t virtual_offset, size_t length) {}
+
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write) {}
+
+ u8* backing_base{nullptr};
+ u8* virtual_base{nullptr};
+};
+
+#endif // ^^^ Generic ^^^
+
+HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
+ : backing_size(backing_size_), virtual_size(virtual_size_) {
+ try {
+ // Try to allocate a fastmem arena.
+ // The implementation will fail with std::bad_alloc on errors.
+ impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment),
+ AlignUp(virtual_size, PageAlignment) +
+ 3 * HugePageSize);
+ backing_base = impl->backing_base;
+ virtual_base = impl->virtual_base;
+
+ if (virtual_base) {
+ virtual_base += 2 * HugePageSize - 1;
+ virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1);
+ virtual_base_offset = virtual_base - impl->virtual_base;
+ }
+
+ } catch (const std::bad_alloc&) {
+ LOG_CRITICAL(HW_Memory,
+ "Fastmem unavailable, falling back to VirtualBuffer for memory allocation");
+ fallback_buffer = std::make_unique<Common::VirtualBuffer<u8>>(backing_size);
+ backing_base = fallback_buffer->data();
+ virtual_base = nullptr;
+ }
+}
+
+HostMemory::~HostMemory() = default;
+
+HostMemory::HostMemory(HostMemory&&) noexcept = default;
+
+HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
+
+void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) {
+ ASSERT(virtual_offset % PageAlignment == 0);
+ ASSERT(host_offset % PageAlignment == 0);
+ ASSERT(length % PageAlignment == 0);
+ ASSERT(virtual_offset + length <= virtual_size);
+ ASSERT(host_offset + length <= backing_size);
+ if (length == 0 || !virtual_base || !impl) {
+ return;
+ }
+ impl->Map(virtual_offset + virtual_base_offset, host_offset, length);
+}
+
+void HostMemory::Unmap(size_t virtual_offset, size_t length) {
+ ASSERT(virtual_offset % PageAlignment == 0);
+ ASSERT(length % PageAlignment == 0);
+ ASSERT(virtual_offset + length <= virtual_size);
+ if (length == 0 || !virtual_base || !impl) {
+ return;
+ }
+ impl->Unmap(virtual_offset + virtual_base_offset, length);
+}
+
+void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) {
+ ASSERT(virtual_offset % PageAlignment == 0);
+ ASSERT(length % PageAlignment == 0);
+ ASSERT(virtual_offset + length <= virtual_size);
+ if (length == 0 || !virtual_base || !impl) {
+ return;
+ }
+ impl->Protect(virtual_offset + virtual_base_offset, length, read, write);
+}
+
+} // namespace Common
diff --git a/src/common/host_memory.h b/src/common/host_memory.h
new file mode 100644
index 000000000..9b8326d0f
--- /dev/null
+++ b/src/common/host_memory.h
@@ -0,0 +1,70 @@
+// Copyright 2019 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#pragma once
+
+#include <memory>
+#include "common/common_types.h"
+#include "common/virtual_buffer.h"
+
+namespace Common {
+
+/**
+ * A low level linear memory buffer, which supports multiple mappings
+ * Its purpose is to rebuild a given sparse memory layout, including mirrors.
+ */
+class HostMemory {
+public:
+ explicit HostMemory(size_t backing_size_, size_t virtual_size_);
+ ~HostMemory();
+
+ /**
+ * Copy constructors. They shall return a copy of the buffer without the mappings.
+ * TODO: Implement them with COW if needed.
+ */
+ HostMemory(const HostMemory& other) = delete;
+ HostMemory& operator=(const HostMemory& other) = delete;
+
+ /**
+ * Move constructors. They will move the buffer and the mappings to the new object.
+ */
+ HostMemory(HostMemory&& other) noexcept;
+ HostMemory& operator=(HostMemory&& other) noexcept;
+
+ void Map(size_t virtual_offset, size_t host_offset, size_t length);
+
+ void Unmap(size_t virtual_offset, size_t length);
+
+ void Protect(size_t virtual_offset, size_t length, bool read, bool write);
+
+ [[nodiscard]] u8* BackingBasePointer() noexcept {
+ return backing_base;
+ }
+ [[nodiscard]] const u8* BackingBasePointer() const noexcept {
+ return backing_base;
+ }
+
+ [[nodiscard]] u8* VirtualBasePointer() noexcept {
+ return virtual_base;
+ }
+ [[nodiscard]] const u8* VirtualBasePointer() const noexcept {
+ return virtual_base;
+ }
+
+private:
+ size_t backing_size{};
+ size_t virtual_size{};
+
+ // Low level handler for the platform dependent memory routines
+ class Impl;
+ std::unique_ptr<Impl> impl;
+ u8* backing_base{};
+ u8* virtual_base{};
+ size_t virtual_base_offset{};
+
+ // Fallback if fastmem is not supported on this platform
+ std::unique_ptr<Common::VirtualBuffer<u8>> fallback_buffer;
+};
+
+} // namespace Common
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp
index 6aa8ac960..d5cff400f 100644
--- a/src/common/logging/backend.cpp
+++ b/src/common/logging/backend.cpp
@@ -17,6 +17,7 @@
#endif
#include "common/assert.h"
+#include "common/fs/file.h"
#include "common/fs/fs.h"
#include "common/logging/backend.h"
#include "common/logging/log.h"
@@ -140,10 +141,14 @@ private:
std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()};
};
+ConsoleBackend::~ConsoleBackend() = default;
+
void ConsoleBackend::Write(const Entry& entry) {
PrintMessage(entry);
}
+ColorConsoleBackend::~ColorConsoleBackend() = default;
+
void ColorConsoleBackend::Write(const Entry& entry) {
PrintColoredMessage(entry);
}
@@ -157,16 +162,19 @@ FileBackend::FileBackend(const std::filesystem::path& filename) {
void(FS::RemoveFile(old_filename));
void(FS::RenameFile(filename, old_filename));
- file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
+ file =
+ std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile);
}
+FileBackend::~FileBackend() = default;
+
void FileBackend::Write(const Entry& entry) {
// prevent logs from going over the maximum size (in case its spamming and the user doesn't
// know)
constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024;
constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024;
- if (!file.IsOpen()) {
+ if (!file->IsOpen()) {
return;
}
@@ -176,147 +184,20 @@ void FileBackend::Write(const Entry& entry) {
return;
}
- bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n'));
+ bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n'));
if (entry.log_level >= Level::Error) {
- void(file.Flush());
+ void(file->Flush());
}
}
+DebuggerBackend::~DebuggerBackend() = default;
+
void DebuggerBackend::Write(const Entry& entry) {
#ifdef _WIN32
::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str());
#endif
}
-/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
-#define ALL_LOG_CLASSES() \
- CLS(Log) \
- CLS(Common) \
- SUB(Common, Filesystem) \
- SUB(Common, Memory) \
- CLS(Core) \
- SUB(Core, ARM) \
- SUB(Core, Timing) \
- CLS(Config) \
- CLS(Debug) \
- SUB(Debug, Emulated) \
- SUB(Debug, GPU) \
- SUB(Debug, Breakpoint) \
- SUB(Debug, GDBStub) \
- CLS(Kernel) \
- SUB(Kernel, SVC) \
- CLS(Service) \
- SUB(Service, ACC) \
- SUB(Service, Audio) \
- SUB(Service, AM) \
- SUB(Service, AOC) \
- SUB(Service, APM) \
- SUB(Service, ARP) \
- SUB(Service, BCAT) \
- SUB(Service, BPC) \
- SUB(Service, BGTC) \
- SUB(Service, BTDRV) \
- SUB(Service, BTM) \
- SUB(Service, Capture) \
- SUB(Service, ERPT) \
- SUB(Service, ETicket) \
- SUB(Service, EUPLD) \
- SUB(Service, Fatal) \
- SUB(Service, FGM) \
- SUB(Service, Friend) \
- SUB(Service, FS) \
- SUB(Service, GRC) \
- SUB(Service, HID) \
- SUB(Service, IRS) \
- SUB(Service, LBL) \
- SUB(Service, LDN) \
- SUB(Service, LDR) \
- SUB(Service, LM) \
- SUB(Service, Migration) \
- SUB(Service, Mii) \
- SUB(Service, MM) \
- SUB(Service, NCM) \
- SUB(Service, NFC) \
- SUB(Service, NFP) \
- SUB(Service, NIFM) \
- SUB(Service, NIM) \
- SUB(Service, NPNS) \
- SUB(Service, NS) \
- SUB(Service, NVDRV) \
- SUB(Service, OLSC) \
- SUB(Service, PCIE) \
- SUB(Service, PCTL) \
- SUB(Service, PCV) \
- SUB(Service, PM) \
- SUB(Service, PREPO) \
- SUB(Service, PSC) \
- SUB(Service, PSM) \
- SUB(Service, SET) \
- SUB(Service, SM) \
- SUB(Service, SPL) \
- SUB(Service, SSL) \
- SUB(Service, TCAP) \
- SUB(Service, Time) \
- SUB(Service, USB) \
- SUB(Service, VI) \
- SUB(Service, WLAN) \
- CLS(HW) \
- SUB(HW, Memory) \
- SUB(HW, LCD) \
- SUB(HW, GPU) \
- SUB(HW, AES) \
- CLS(IPC) \
- CLS(Frontend) \
- CLS(Render) \
- SUB(Render, Software) \
- SUB(Render, OpenGL) \
- SUB(Render, Vulkan) \
- CLS(Audio) \
- SUB(Audio, DSP) \
- SUB(Audio, Sink) \
- CLS(Input) \
- CLS(Network) \
- CLS(Loader) \
- CLS(CheatEngine) \
- CLS(Crypto) \
- CLS(WebService)
-
-// GetClassName is a macro defined by Windows.h, grrr...
-const char* GetLogClassName(Class log_class) {
- switch (log_class) {
-#define CLS(x) \
- case Class::x: \
- return #x;
-#define SUB(x, y) \
- case Class::x##_##y: \
- return #x "." #y;
- ALL_LOG_CLASSES()
-#undef CLS
-#undef SUB
- case Class::Count:
- break;
- }
- return "Invalid";
-}
-
-const char* GetLevelName(Level log_level) {
-#define LVL(x) \
- case Level::x: \
- return #x
- switch (log_level) {
- LVL(Trace);
- LVL(Debug);
- LVL(Info);
- LVL(Warning);
- LVL(Error);
- LVL(Critical);
- case Level::Count:
- break;
- }
-#undef LVL
- return "Invalid";
-}
-
void SetGlobalFilter(const Filter& filter) {
Impl::Instance().SetGlobalFilter(filter);
}
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h
index eb629a33f..4b9a910c1 100644
--- a/src/common/logging/backend.h
+++ b/src/common/logging/backend.h
@@ -1,43 +1,32 @@
// Copyright 2014 Citra Emulator Project
// Licensed under GPLv2 or any later version
// Refer to the license.txt file included.
+
#pragma once
-#include <chrono>
#include <filesystem>
#include <memory>
#include <string>
#include <string_view>
-#include "common/fs/file.h"
#include "common/logging/filter.h"
#include "common/logging/log.h"
+namespace Common::FS {
+class IOFile;
+}
+
namespace Common::Log {
class Filter;
/**
- * A log entry. Log entries are store in a structured format to permit more varied output
- * formatting on different frontends, as well as facilitating filtering and aggregation.
- */
-struct Entry {
- std::chrono::microseconds timestamp;
- Class log_class{};
- Level log_level{};
- const char* filename = nullptr;
- unsigned int line_num = 0;
- std::string function;
- std::string message;
- bool final_entry = false;
-};
-
-/**
* Interface for logging backends. As loggers can be created and removed at runtime, this can be
* used by a frontend for adding a custom logging backend as needed
*/
class Backend {
public:
virtual ~Backend() = default;
+
virtual void SetFilter(const Filter& new_filter) {
filter = new_filter;
}
@@ -53,6 +42,8 @@ private:
*/
class ConsoleBackend : public Backend {
public:
+ ~ConsoleBackend() override;
+
static const char* Name() {
return "console";
}
@@ -67,6 +58,8 @@ public:
*/
class ColorConsoleBackend : public Backend {
public:
+ ~ColorConsoleBackend() override;
+
static const char* Name() {
return "color_console";
}
@@ -83,6 +76,7 @@ public:
class FileBackend : public Backend {
public:
explicit FileBackend(const std::filesystem::path& filename);
+ ~FileBackend() override;
static const char* Name() {
return "file";
@@ -95,7 +89,7 @@ public:
void Write(const Entry& entry) override;
private:
- FS::IOFile file;
+ std::unique_ptr<FS::IOFile> file;
std::size_t bytes_written = 0;
};
@@ -104,6 +98,8 @@ private:
*/
class DebuggerBackend : public Backend {
public:
+ ~DebuggerBackend() override;
+
static const char* Name() {
return "debugger";
}
@@ -120,17 +116,6 @@ void RemoveBackend(std::string_view backend_name);
Backend* GetBackend(std::string_view backend_name);
/**
- * Returns the name of the passed log class as a C-string. Subclasses are separated by periods
- * instead of underscores as in the enumeration.
- */
-const char* GetLogClassName(Class log_class);
-
-/**
- * Returns the name of the passed log level as a C-string.
- */
-const char* GetLevelName(Level log_level);
-
-/**
* The global filter will prevent any messages from even being processed if they are filtered. Each
* backend can have a filter, but if the level is lower than the global filter, the backend will
* never get the message
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp
index 20a2dd106..4f2cc29e1 100644
--- a/src/common/logging/filter.cpp
+++ b/src/common/logging/filter.cpp
@@ -3,7 +3,6 @@
// Refer to the license.txt file included.
#include <algorithm>
-#include "common/logging/backend.h"
#include "common/logging/filter.h"
#include "common/string_util.h"
@@ -22,7 +21,7 @@ Level GetLevelByName(const It begin, const It end) {
template <typename It>
Class GetClassByName(const It begin, const It end) {
- for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) {
+ for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) {
const char* level_name = GetLogClassName(static_cast<Class>(i));
if (Common::ComparePartialString(begin, end, level_name)) {
return static_cast<Class>(i);
@@ -62,6 +61,135 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) {
}
} // Anonymous namespace
+/// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this.
+#define ALL_LOG_CLASSES() \
+ CLS(Log) \
+ CLS(Common) \
+ SUB(Common, Filesystem) \
+ SUB(Common, Memory) \
+ CLS(Core) \
+ SUB(Core, ARM) \
+ SUB(Core, Timing) \
+ CLS(Config) \
+ CLS(Debug) \
+ SUB(Debug, Emulated) \
+ SUB(Debug, GPU) \
+ SUB(Debug, Breakpoint) \
+ SUB(Debug, GDBStub) \
+ CLS(Kernel) \
+ SUB(Kernel, SVC) \
+ CLS(Service) \
+ SUB(Service, ACC) \
+ SUB(Service, Audio) \
+ SUB(Service, AM) \
+ SUB(Service, AOC) \
+ SUB(Service, APM) \
+ SUB(Service, ARP) \
+ SUB(Service, BCAT) \
+ SUB(Service, BPC) \
+ SUB(Service, BGTC) \
+ SUB(Service, BTDRV) \
+ SUB(Service, BTM) \
+ SUB(Service, Capture) \
+ SUB(Service, ERPT) \
+ SUB(Service, ETicket) \
+ SUB(Service, EUPLD) \
+ SUB(Service, Fatal) \
+ SUB(Service, FGM) \
+ SUB(Service, Friend) \
+ SUB(Service, FS) \
+ SUB(Service, GRC) \
+ SUB(Service, HID) \
+ SUB(Service, IRS) \
+ SUB(Service, LBL) \
+ SUB(Service, LDN) \
+ SUB(Service, LDR) \
+ SUB(Service, LM) \
+ SUB(Service, Migration) \
+ SUB(Service, Mii) \
+ SUB(Service, MM) \
+ SUB(Service, NCM) \
+ SUB(Service, NFC) \
+ SUB(Service, NFP) \
+ SUB(Service, NIFM) \
+ SUB(Service, NIM) \
+ SUB(Service, NPNS) \
+ SUB(Service, NS) \
+ SUB(Service, NVDRV) \
+ SUB(Service, OLSC) \
+ SUB(Service, PCIE) \
+ SUB(Service, PCTL) \
+ SUB(Service, PCV) \
+ SUB(Service, PM) \
+ SUB(Service, PREPO) \
+ SUB(Service, PSC) \
+ SUB(Service, PSM) \
+ SUB(Service, SET) \
+ SUB(Service, SM) \
+ SUB(Service, SPL) \
+ SUB(Service, SSL) \
+ SUB(Service, TCAP) \
+ SUB(Service, Time) \
+ SUB(Service, USB) \
+ SUB(Service, VI) \
+ SUB(Service, WLAN) \
+ CLS(HW) \
+ SUB(HW, Memory) \
+ SUB(HW, LCD) \
+ SUB(HW, GPU) \
+ SUB(HW, AES) \
+ CLS(IPC) \
+ CLS(Frontend) \
+ CLS(Render) \
+ SUB(Render, Software) \
+ SUB(Render, OpenGL) \
+ SUB(Render, Vulkan) \
+ CLS(Audio) \
+ SUB(Audio, DSP) \
+ SUB(Audio, Sink) \
+ CLS(Input) \
+ CLS(Network) \
+ CLS(Loader) \
+ CLS(CheatEngine) \
+ CLS(Crypto) \
+ CLS(WebService)
+
+// GetClassName is a macro defined by Windows.h, grrr...
+const char* GetLogClassName(Class log_class) {
+ switch (log_class) {
+#define CLS(x) \
+ case Class::x: \
+ return #x;
+#define SUB(x, y) \
+ case Class::x##_##y: \
+ return #x "." #y;
+ ALL_LOG_CLASSES()
+#undef CLS
+#undef SUB
+ case Class::Count:
+ break;
+ }
+ return "Invalid";
+}
+
+const char* GetLevelName(Level log_level) {
+#define LVL(x) \
+ case Level::x: \
+ return #x
+ switch (log_level) {
+ LVL(Trace);
+ LVL(Debug);
+ LVL(Info);
+ LVL(Warning);
+ LVL(Error);
+ LVL(Critical);
+ case Level::Count:
+ break;
+ }
+#undef LVL
+ return "Invalid";
+}
+
Filter::Filter(Level default_level) {
ResetAll(default_level);
}
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h
index f5673a9f6..1a3074e04 100644
--- a/src/common/logging/filter.h
+++ b/src/common/logging/filter.h
@@ -5,6 +5,7 @@
#pragma once
#include <array>
+#include <chrono>
#include <cstddef>
#include <string_view>
#include "common/logging/log.h"
@@ -12,6 +13,17 @@
namespace Common::Log {
/**
+ * Returns the name of the passed log class as a C-string. Subclasses are separated by periods
+ * instead of underscores as in the enumeration.
+ */
+const char* GetLogClassName(Class log_class);
+
+/**
+ * Returns the name of the passed log level as a C-string.
+ */
+const char* GetLevelName(Level log_level);
+
+/**
* Implements a log message filter which allows different log classes to have different minimum
* severity levels. The filter can be changed at runtime and can be parsed from a string to allow
* editing via the interface or loading from a configuration file.
diff --git a/src/common/logging/log.h b/src/common/logging/log.h
index 1f0f8db52..8d43eddc7 100644
--- a/src/common/logging/log.h
+++ b/src/common/logging/log.h
@@ -5,7 +5,7 @@
#pragma once
#include <fmt/format.h>
-#include "common/common_types.h"
+#include "common/logging/types.h"
namespace Common::Log {
@@ -18,124 +18,6 @@ constexpr const char* TrimSourcePath(std::string_view source) {
return source.data() + idx;
}
-/// Specifies the severity or level of detail of the log message.
-enum class Level : u8 {
- Trace, ///< Extremely detailed and repetitive debugging information that is likely to
- ///< pollute logs.
- Debug, ///< Less detailed debugging information.
- Info, ///< Status information from important points during execution.
- Warning, ///< Minor or potential problems found during execution of a task.
- Error, ///< Major problems found during execution of a task that prevent it from being
- ///< completed.
- Critical, ///< Major problems during execution that threaten the stability of the entire
- ///< application.
-
- Count ///< Total number of logging levels
-};
-
-typedef u8 ClassType;
-
-/**
- * Specifies the sub-system that generated the log message.
- *
- * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in
- * backend.cpp.
- */
-enum class Class : ClassType {
- Log, ///< Messages about the log system itself
- Common, ///< Library routines
- Common_Filesystem, ///< Filesystem interface library
- Common_Memory, ///< Memory mapping and management functions
- Core, ///< LLE emulation core
- Core_ARM, ///< ARM CPU core
- Core_Timing, ///< CoreTiming functions
- Config, ///< Emulator configuration (including commandline)
- Debug, ///< Debugging tools
- Debug_Emulated, ///< Debug messages from the emulated programs
- Debug_GPU, ///< GPU debugging tools
- Debug_Breakpoint, ///< Logging breakpoints and watchpoints
- Debug_GDBStub, ///< GDB Stub
- Kernel, ///< The HLE implementation of the CTR kernel
- Kernel_SVC, ///< Kernel system calls
- Service, ///< HLE implementation of system services. Each major service
- ///< should have its own subclass.
- Service_ACC, ///< The ACC (Accounts) service
- Service_AM, ///< The AM (Applet manager) service
- Service_AOC, ///< The AOC (AddOn Content) service
- Service_APM, ///< The APM (Performance) service
- Service_ARP, ///< The ARP service
- Service_Audio, ///< The Audio (Audio control) service
- Service_BCAT, ///< The BCAT service
- Service_BGTC, ///< The BGTC (Background Task Controller) service
- Service_BPC, ///< The BPC service
- Service_BTDRV, ///< The Bluetooth driver service
- Service_BTM, ///< The BTM service
- Service_Capture, ///< The capture service
- Service_ERPT, ///< The error reporting service
- Service_ETicket, ///< The ETicket service
- Service_EUPLD, ///< The error upload service
- Service_Fatal, ///< The Fatal service
- Service_FGM, ///< The FGM service
- Service_Friend, ///< The friend service
- Service_FS, ///< The FS (Filesystem) service
- Service_GRC, ///< The game recording service
- Service_HID, ///< The HID (Human interface device) service
- Service_IRS, ///< The IRS service
- Service_LBL, ///< The LBL (LCD backlight) service
- Service_LDN, ///< The LDN (Local domain network) service
- Service_LDR, ///< The loader service
- Service_LM, ///< The LM (Logger) service
- Service_Migration, ///< The migration service
- Service_Mii, ///< The Mii service
- Service_MM, ///< The MM (Multimedia) service
- Service_NCM, ///< The NCM service
- Service_NFC, ///< The NFC (Near-field communication) service
- Service_NFP, ///< The NFP service
- Service_NIFM, ///< The NIFM (Network interface) service
- Service_NIM, ///< The NIM service
- Service_NPNS, ///< The NPNS service
- Service_NS, ///< The NS services
- Service_NVDRV, ///< The NVDRV (Nvidia driver) service
- Service_OLSC, ///< The OLSC service
- Service_PCIE, ///< The PCIe service
- Service_PCTL, ///< The PCTL (Parental control) service
- Service_PCV, ///< The PCV service
- Service_PM, ///< The PM service
- Service_PREPO, ///< The PREPO (Play report) service
- Service_PSC, ///< The PSC service
- Service_PSM, ///< The PSM service
- Service_SET, ///< The SET (Settings) service
- Service_SM, ///< The SM (Service manager) service
- Service_SPL, ///< The SPL service
- Service_SSL, ///< The SSL service
- Service_TCAP, ///< The TCAP service.
- Service_Time, ///< The time service
- Service_USB, ///< The USB (Universal Serial Bus) service
- Service_VI, ///< The VI (Video interface) service
- Service_WLAN, ///< The WLAN (Wireless local area network) service
- HW, ///< Low-level hardware emulation
- HW_Memory, ///< Memory-map and address translation
- HW_LCD, ///< LCD register emulation
- HW_GPU, ///< GPU control emulation
- HW_AES, ///< AES engine emulation
- IPC, ///< IPC interface
- Frontend, ///< Emulator UI
- Render, ///< Emulator video output and hardware acceleration
- Render_Software, ///< Software renderer backend
- Render_OpenGL, ///< OpenGL backend
- Render_Vulkan, ///< Vulkan backend
- Audio, ///< Audio emulation
- Audio_DSP, ///< The HLE implementation of the DSP
- Audio_Sink, ///< Emulator audio output backend
- Loader, ///< ROM loader
- CheatEngine, ///< Memory manipulation and engine VM functions
- Crypto, ///< Cryptographic engine/functions
- Input, ///< Input emulation
- Network, ///< Network emulation
- WebService, ///< Interface to yuzu Web Services
- Count ///< Total number of logging classes
-};
-
/// Logs a message to the global logger, using fmt
void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename,
unsigned int line_num, const char* function, const char* format,
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp
index 80ee2cca1..cfc0d5846 100644
--- a/src/common/logging/text_formatter.cpp
+++ b/src/common/logging/text_formatter.cpp
@@ -11,7 +11,7 @@
#include "common/assert.h"
#include "common/common_funcs.h"
-#include "common/logging/backend.h"
+#include "common/logging/filter.h"
#include "common/logging/log.h"
#include "common/logging/text_formatter.h"
#include "common/string_util.h"
diff --git a/src/common/logging/types.h b/src/common/logging/types.h
new file mode 100644
index 000000000..ee9a1ed84
--- /dev/null
+++ b/src/common/logging/types.h
@@ -0,0 +1,142 @@
+// Copyright 2021 yuzu Emulator Project
+// Licensed under GPLv2 or any later version
+// Refer to the license.txt file included.
+
+#include <chrono>
+
+#include "common/common_types.h"
+
+namespace Common::Log {
+
+/// Specifies the severity or level of detail of the log message.
+enum class Level : u8 {
+ Trace, ///< Extremely detailed and repetitive debugging information that is likely to
+ ///< pollute logs.
+ Debug, ///< Less detailed debugging information.
+ Info, ///< Status information from important points during execution.
+ Warning, ///< Minor or potential problems found during execution of a task.
+ Error, ///< Major problems found during execution of a task that prevent it from being
+ ///< completed.
+ Critical, ///< Major problems during execution that threaten the stability of the entire
+ ///< application.
+
+ Count ///< Total number of logging levels
+};
+
+/**
+ * Specifies the sub-system that generated the log message.
+ *
+ * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in
+ * filter.cpp.
+ */
+enum class Class : u8 {
+ Log, ///< Messages about the log system itself
+ Common, ///< Library routines
+ Common_Filesystem, ///< Filesystem interface library
+ Common_Memory, ///< Memory mapping and management functions
+ Core, ///< LLE emulation core
+ Core_ARM, ///< ARM CPU core
+ Core_Timing, ///< CoreTiming functions
+ Config, ///< Emulator configuration (including commandline)
+ Debug, ///< Debugging tools
+ Debug_Emulated, ///< Debug messages from the emulated programs
+ Debug_GPU, ///< GPU debugging tools
+ Debug_Breakpoint, ///< Logging breakpoints and watchpoints
+ Debug_GDBStub, ///< GDB Stub
+ Kernel, ///< The HLE implementation of the CTR kernel
+ Kernel_SVC, ///< Kernel system calls
+ Service, ///< HLE implementation of system services. Each major service
+ ///< should have its own subclass.
+ Service_ACC, ///< The ACC (Accounts) service
+ Service_AM, ///< The AM (Applet manager) service
+ Service_AOC, ///< The AOC (AddOn Content) service
+ Service_APM, ///< The APM (Performance) service
+ Service_ARP, ///< The ARP service
+ Service_Audio, ///< The Audio (Audio control) service
+ Service_BCAT, ///< The BCAT service
+ Service_BGTC, ///< The BGTC (Background Task Controller) service
+ Service_BPC, ///< The BPC service
+ Service_BTDRV, ///< The Bluetooth driver service
+ Service_BTM, ///< The BTM service
+ Service_Capture, ///< The capture service
+ Service_ERPT, ///< The error reporting service
+ Service_ETicket, ///< The ETicket service
+ Service_EUPLD, ///< The error upload service
+ Service_Fatal, ///< The Fatal service
+ Service_FGM, ///< The FGM service
+ Service_Friend, ///< The friend service
+ Service_FS, ///< The FS (Filesystem) service
+ Service_GRC, ///< The game recording service
+ Service_HID, ///< The HID (Human interface device) service
+ Service_IRS, ///< The IRS service
+ Service_LBL, ///< The LBL (LCD backlight) service
+ Service_LDN, ///< The LDN (Local domain network) service
+ Service_LDR, ///< The loader service
+ Service_LM, ///< The LM (Logger) service
+ Service_Migration, ///< The migration service
+ Service_Mii, ///< The Mii service
+ Service_MM, ///< The MM (Multimedia) service
+ Service_NCM, ///< The NCM service
+ Service_NFC, ///< The NFC (Near-field communication) service
+ Service_NFP, ///< The NFP service
+ Service_NIFM, ///< The NIFM (Network interface) service
+ Service_NIM, ///< The NIM service
+ Service_NPNS, ///< The NPNS service
+ Service_NS, ///< The NS services
+ Service_NVDRV, ///< The NVDRV (Nvidia driver) service
+ Service_OLSC, ///< The OLSC service
+ Service_PCIE, ///< The PCIe service
+ Service_PCTL, ///< The PCTL (Parental control) service
+ Service_PCV, ///< The PCV service
+ Service_PM, ///< The PM service
+ Service_PREPO, ///< The PREPO (Play report) service
+ Service_PSC, ///< The PSC service
+ Service_PSM, ///< The PSM service
+ Service_SET, ///< The SET (Settings) service
+ Service_SM, ///< The SM (Service manager) service
+ Service_SPL, ///< The SPL service
+ Service_SSL, ///< The SSL service
+ Service_TCAP, ///< The TCAP service.
+ Service_Time, ///< The time service
+ Service_USB, ///< The USB (Universal Serial Bus) service
+ Service_VI, ///< The VI (Video interface) service
+ Service_WLAN, ///< The WLAN (Wireless local area network) service
+ HW, ///< Low-level hardware emulation
+ HW_Memory, ///< Memory-map and address translation
+ HW_LCD, ///< LCD register emulation
+ HW_GPU, ///< GPU control emulation
+ HW_AES, ///< AES engine emulation
+ IPC, ///< IPC interface
+ Frontend, ///< Emulator UI
+ Render, ///< Emulator video output and hardware acceleration
+ Render_Software, ///< Software renderer backend
+ Render_OpenGL, ///< OpenGL backend
+ Render_Vulkan, ///< Vulkan backend
+ Audio, ///< Audio emulation
+ Audio_DSP, ///< The HLE implementation of the DSP
+ Audio_Sink, ///< Emulator audio output backend
+ Loader, ///< ROM loader
+ CheatEngine, ///< Memory manipulation and engine VM functions
+ Crypto, ///< Cryptographic engine/functions
+ Input, ///< Input emulation
+ Network, ///< Network emulation
+ WebService, ///< Interface to yuzu Web Services
+ Count ///< Total number of logging classes
+};
+
+/**
+ * A log entry. Log entries are store in a structured format to permit more varied output
+ * formatting on different frontends, as well as facilitating filtering and aggregation.
+ */
+struct Entry {
+ std::chrono::microseconds timestamp;
+ Class log_class{};
+ Level log_level{};
+ const char* filename = nullptr;
+ unsigned int line_num = 0;
+ std::string function;
+ std::string message;
+ bool final_entry = false;
+};
+
+} // namespace Common::Log
diff --git a/src/common/page_table.h b/src/common/page_table.h
index e92b66b2b..8267e8b4d 100644
--- a/src/common/page_table.h
+++ b/src/common/page_table.h
@@ -111,6 +111,8 @@ struct PageTable {
VirtualBuffer<u64> backing_addr;
size_t current_address_space_width_in_bits;
+
+ u8* fastmem_arena;
};
} // namespace Common
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index bcb4e4be1..9ec71eced 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -55,6 +55,7 @@ void LogSettings() {
log_setting("Renderer_UseAsynchronousGpuEmulation",
values.use_asynchronous_gpu_emulation.GetValue());
log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue());
+ log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue());
log_setting("Renderer_UseVsync", values.use_vsync.GetValue());
log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue());
log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue());
@@ -90,6 +91,13 @@ bool IsGPULevelHigh() {
values.gpu_accuracy.GetValue() == GPUAccuracy::High;
}
+bool IsFastmemEnabled() {
+ if (values.cpu_accuracy.GetValue() == CPUAccuracy::DebugMode) {
+ return values.cpuopt_fastmem;
+ }
+ return true;
+}
+
float Volume() {
if (values.audio_muted) {
return 0.0f;
@@ -115,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.cpuopt_unsafe_unfuse_fma.SetGlobal(true);
values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true);
values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true);
+ values.cpuopt_unsafe_fastmem_check.SetGlobal(true);
// Renderer
values.renderer_backend.SetGlobal(true);
@@ -127,6 +136,7 @@ void RestoreGlobalState(bool is_powered_on) {
values.gpu_accuracy.SetGlobal(true);
values.use_asynchronous_gpu_emulation.SetGlobal(true);
values.use_nvdec_emulation.SetGlobal(true);
+ values.accelerate_astc.SetGlobal(true);
values.use_vsync.SetGlobal(true);
values.use_assembly_shaders.SetGlobal(true);
values.use_asynchronous_shaders.SetGlobal(true);
diff --git a/src/common/settings.h b/src/common/settings.h
index 48085b9a9..6198f2d9f 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -125,10 +125,12 @@ struct Values {
bool cpuopt_const_prop;
bool cpuopt_misc_ir;
bool cpuopt_reduce_misalign_checks;
+ bool cpuopt_fastmem;
Setting<bool> cpuopt_unsafe_unfuse_fma;
Setting<bool> cpuopt_unsafe_reduce_fp_error;
Setting<bool> cpuopt_unsafe_inaccurate_nan;
+ Setting<bool> cpuopt_unsafe_fastmem_check;
// Renderer
Setting<RendererBackend> renderer_backend;
@@ -145,6 +147,7 @@ struct Values {
Setting<GPUAccuracy> gpu_accuracy;
Setting<bool> use_asynchronous_gpu_emulation;
Setting<bool> use_nvdec_emulation;
+ Setting<bool> accelerate_astc;
Setting<bool> use_vsync;
Setting<bool> use_assembly_shaders;
Setting<bool> use_asynchronous_shaders;
@@ -216,6 +219,7 @@ struct Values {
std::string program_args;
bool dump_exefs;
bool dump_nso;
+ bool enable_fs_access_log;
bool reporting_services;
bool quest_flag;
bool disable_macro_jit;
@@ -249,6 +253,8 @@ void SetConfiguringGlobal(bool is_global);
bool IsGPULevelExtreme();
bool IsGPULevelHigh();
+bool IsFastmemEnabled();
+
float Volume();
std::string GetTimeZoneString();