Compare commits
74 Commits
__refs_pul
...
__refs_pul
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5fc8393125 | ||
|
|
f9bfeaa2bc | ||
|
|
b2955479e5 | ||
|
|
c4ff7ecf51 | ||
|
|
f3caf53648 | ||
|
|
422a15ee75 | ||
|
|
c8f86edee6 | ||
|
|
8d4dfc98ec | ||
|
|
de12dbb01a | ||
|
|
a4454329c1 | ||
|
|
391e823c79 | ||
|
|
8150c65c07 | ||
|
|
56afd4ab4b | ||
|
|
c11b4c45e1 | ||
|
|
c978f3144c | ||
|
|
8b5655a98e | ||
|
|
2817ef1a53 | ||
|
|
58180f9fa8 | ||
|
|
827483409b | ||
|
|
9951322e5a | ||
|
|
fa2aac1bf5 | ||
|
|
0c0c1a039e | ||
|
|
7f85abb281 | ||
|
|
f332d4a9b5 | ||
|
|
588ab44470 | ||
|
|
7b0d8bd1fb | ||
|
|
ee67460ff0 | ||
|
|
5ba28325b2 | ||
|
|
c4609c92ee | ||
|
|
621f3f5f47 | ||
|
|
740edacc8d | ||
|
|
5105318bbc | ||
|
|
a7837a3791 | ||
|
|
fbb170857f | ||
|
|
f738c6b231 | ||
|
|
c1b8e59ea0 | ||
|
|
46ec0ee55b | ||
|
|
ebd38d66db | ||
|
|
aa79ca7a7a | ||
|
|
4547b2735a | ||
|
|
6755025310 | ||
|
|
781c85b951 | ||
|
|
fa8a0065ca | ||
|
|
74f0087bfa | ||
|
|
b259e95c09 | ||
|
|
ec5674a6ad | ||
|
|
2aa6a8d889 | ||
|
|
b2971b48ed | ||
|
|
86d832ab9a | ||
|
|
61c7a81ec8 | ||
|
|
fbad68de0f | ||
|
|
c63ea608aa | ||
|
|
6eeb532c96 | ||
|
|
5857067a18 | ||
|
|
2d32fc2318 | ||
|
|
75a4ac12c6 | ||
|
|
eb9deffab6 | ||
|
|
15483c07c6 | ||
|
|
f9c3e2e872 | ||
|
|
3c621d37f0 | ||
|
|
dd8577e91d | ||
|
|
b3eb08254b | ||
|
|
f09c9b5fcc | ||
|
|
b8fb9b3f11 | ||
|
|
08d798b6fe | ||
|
|
3b5673daca | ||
|
|
a493ab2678 | ||
|
|
058196a089 | ||
|
|
2e1c58b905 | ||
|
|
9ff8504452 | ||
|
|
7395cd3124 | ||
|
|
890acfa2c0 | ||
|
|
ddc47e6df8 | ||
|
|
55dd027115 |
@@ -9,11 +9,5 @@ cp "${REV_NAME}-source.tar.xz" "$DIR_NAME"
|
||||
|
||||
tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$DIR_NAME"
|
||||
|
||||
mv "$DIR_NAME" $RELEASE_NAME
|
||||
mv "${REV_NAME}-source.tar.xz" $RELEASE_NAME
|
||||
|
||||
7z a "$REV_NAME.7z" $RELEASE_NAME
|
||||
|
||||
# move the compiled archive into the artifacts directory to be uploaded by travis releases
|
||||
mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/"
|
||||
mv "$REV_NAME.7z" "${ARTIFACTS_DIR}/"
|
||||
|
||||
@@ -3,8 +3,8 @@
|
||||
. .ci/scripts/common/pre-upload.sh
|
||||
|
||||
REV_NAME="yuzu-windows-mingw-${GITDATE}-${GITREV}"
|
||||
ARCHIVE_NAME="${REV_NAME}.tar.gz"
|
||||
COMPRESSION_FLAGS="-czvf"
|
||||
ARCHIVE_NAME="${REV_NAME}.tar.xz"
|
||||
COMPRESSION_FLAGS="-cJvf"
|
||||
|
||||
if [ "${RELEASE_NAME}" = "mainline" ]; then
|
||||
DIR_NAME="${REV_NAME}"
|
||||
|
||||
@@ -23,6 +23,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON)
|
||||
|
||||
option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF)
|
||||
|
||||
option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF)
|
||||
|
||||
CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF)
|
||||
|
||||
option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
|
||||
@@ -420,14 +422,22 @@ elseif (TARGET Boost::boost)
|
||||
endif()
|
||||
|
||||
# Ensure libusb is properly configured (based on dolphin libusb include)
|
||||
if(NOT APPLE)
|
||||
if(NOT APPLE AND NOT YUZU_USE_BUNDLED_LIBUSB)
|
||||
include(FindPkgConfig)
|
||||
find_package(LibUSB)
|
||||
endif()
|
||||
if (NOT LIBUSB_FOUND)
|
||||
add_subdirectory(externals/libusb)
|
||||
set(LIBUSB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/externals/libusb/libusb/libusb")
|
||||
set(LIBUSB_LIBRARIES usb)
|
||||
if (PKG_CONFIG_FOUND)
|
||||
pkg_check_modules(LIBUSB QUIET libusb-1.0>=1.0.24)
|
||||
else()
|
||||
find_package(LibUSB)
|
||||
endif()
|
||||
|
||||
if (LIBUSB_FOUND)
|
||||
add_library(usb INTERFACE)
|
||||
target_include_directories(usb INTERFACE "${LIBUSB_INCLUDE_DIRS}")
|
||||
target_link_libraries(usb INTERFACE "${LIBUSB_LIBRARIES}")
|
||||
else()
|
||||
message(WARNING "libusb not found, falling back to externals")
|
||||
set(YUZU_USE_BUNDLED_LIBUSB ON)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# List of all FFmpeg components required
|
||||
|
||||
5
externals/CMakeLists.txt
vendored
5
externals/CMakeLists.txt
vendored
@@ -45,6 +45,11 @@ target_include_directories(microprofile INTERFACE ./microprofile)
|
||||
add_library(unicorn-headers INTERFACE)
|
||||
target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
|
||||
|
||||
# libusb
|
||||
if (NOT LIBUSB_FOUND OR YUZU_USE_BUNDLED_LIBUSB)
|
||||
add_subdirectory(libusb)
|
||||
endif()
|
||||
|
||||
# SDL2
|
||||
if (NOT SDL2_FOUND AND ENABLE_SDL2)
|
||||
if (NOT WIN32)
|
||||
|
||||
2
externals/SDL
vendored
2
externals/SDL
vendored
Submodule externals/SDL updated: 107db2d899...2f248a2a31
2
externals/dynarmic
vendored
2
externals/dynarmic
vendored
Submodule externals/dynarmic updated: 828959caed...0c12614d1a
74
externals/libusb/CMakeLists.txt
vendored
74
externals/libusb/CMakeLists.txt
vendored
@@ -1,10 +1,13 @@
|
||||
if (MINGW)
|
||||
# The MinGW toolchain for some reason doesn't work with this CMakeLists file after updating to
|
||||
# 1.0.24, so we do it the old-fashioned way for now. We may want to move native Linux toolchains
|
||||
# to here, too (TODO lat9nq?).
|
||||
if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux"))
|
||||
set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE)
|
||||
set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE)
|
||||
|
||||
# GNU toolchains for some reason doesn't work with the later half of this CMakeLists after
|
||||
# updating to 1.0.24, so we do it the old-fashioned way for now.
|
||||
|
||||
set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb")
|
||||
set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb")
|
||||
|
||||
# Workarounds for MSYS/MinGW
|
||||
if (MSYS)
|
||||
# CMake on Windows passes `C:/`, but we need `/C/` or `/c/` to use `configure`
|
||||
@@ -19,36 +22,42 @@ if (MINGW)
|
||||
|
||||
set(LIBUSB_CONFIGURE "${LIBUSB_SRC_DIR}/configure")
|
||||
set(LIBUSB_MAKEFILE "${LIBUSB_PREFIX}/Makefile")
|
||||
set(LIBUSB_LIBRARY "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll.a")
|
||||
set(LIBUSB_SHARED_LIBRARY "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll")
|
||||
set(LIBUSB_SHARED_LIBRARY_DEST "${CMAKE_BINARY_DIR}/bin/libusb-1.0.dll")
|
||||
|
||||
# Causes "externals/libusb/libusb/libusb/os/windows_winusb.c:1427:2: error: conversion to non-scalar type requested", so cannot statically link it for now.
|
||||
# set(LIBUSB_CFLAGS "-DGUID_DEVINTERFACE_USB_DEVICE=\\(GUID\\){0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}}")
|
||||
if (MINGW)
|
||||
set(LIBUSB_LIBRARIES "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll.a" CACHE PATH "libusb library path" FORCE)
|
||||
set(LIBUSB_SHARED_LIBRARY "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll")
|
||||
set(LIBUSB_SHARED_LIBRARY_DEST "${CMAKE_BINARY_DIR}/bin/libusb-1.0.dll")
|
||||
|
||||
set(LIBUSB_CONFIGURE_ARGS --host=x86_64-w64-mingw32 --build=x86_64-windows)
|
||||
else()
|
||||
set(LIBUSB_LIBRARIES "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.a" CACHE PATH "libusb library path" FORCE)
|
||||
endif()
|
||||
|
||||
set(LIBUSB_INCLUDE_DIRS "${LIBUSB_SRC_DIR}/libusb" CACHE PATH "libusb headers path" FORCE)
|
||||
|
||||
# MINGW: causes "externals/libusb/libusb/libusb/os/windows_winusb.c:1427:2: error: conversion to non-scalar type requested", so cannot statically link it for now.
|
||||
if (NOT MINGW)
|
||||
set(LIBUSB_CFLAGS "-DGUID_DEVINTERFACE_USB_DEVICE=\\(GUID\\){0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}}")
|
||||
endif()
|
||||
|
||||
make_directory("${LIBUSB_PREFIX}")
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${LIBUSB_LIBRARY}"
|
||||
"${LIBUSB_LIBRARIES}"
|
||||
COMMAND
|
||||
make
|
||||
WORKING_DIRECTORY
|
||||
"${LIBUSB_PREFIX}"
|
||||
)
|
||||
|
||||
# We may use this path for other GNU toolchains, so put all of the MinGW-specific stuff here
|
||||
if (MINGW)
|
||||
set(LIBUSB_CONFIGURE_ARGS --host=x86_64-w64-mingw32 --build=x86_64-windows)
|
||||
endif()
|
||||
|
||||
add_custom_command(
|
||||
OUTPUT
|
||||
"${LIBUSB_MAKEFILE}"
|
||||
COMMAND
|
||||
# /bin/env
|
||||
# CFLAGS="${LIBUSB_CFLAGS}"
|
||||
/bin/sh "${LIBUSB_CONFIGURE}"
|
||||
env
|
||||
CFLAGS="${LIBUSB_CFLAGS}"
|
||||
sh "${LIBUSB_CONFIGURE}"
|
||||
${LIBUSB_CONFIGURE_ARGS}
|
||||
--srcdir="${LIBUSB_SRC_DIR}"
|
||||
WORKING_DIRECTORY
|
||||
@@ -59,7 +68,7 @@ if (MINGW)
|
||||
OUTPUT
|
||||
"${LIBUSB_CONFIGURE}"
|
||||
COMMAND
|
||||
/bin/sh "${LIBUSB_SRC_DIR}/bootstrap.sh"
|
||||
sh "${LIBUSB_SRC_DIR}/bootstrap.sh"
|
||||
WORKING_DIRECTORY
|
||||
"${LIBUSB_SRC_DIR}"
|
||||
)
|
||||
@@ -68,19 +77,30 @@ if (MINGW)
|
||||
OUTPUT
|
||||
"${LIBUSB_SHARED_LIBRARY_DEST}"
|
||||
COMMAND
|
||||
/bin/cp "${LIBUSB_SHARED_LIBRARY}" "${LIBUSB_SHARED_LIBRARY_DEST}"
|
||||
cp "${LIBUSB_SHARED_LIBRARY}" "${LIBUSB_SHARED_LIBRARY_DEST}"
|
||||
)
|
||||
|
||||
add_custom_target(usb-bootstrap ALL DEPENDS "${LIBUSB_CONFIGURE}")
|
||||
add_custom_target(usb-configure ALL DEPENDS "${LIBUSB_MAKEFILE}" usb-bootstrap)
|
||||
add_custom_target(usb-build ALL DEPENDS "${LIBUSB_LIBRARY}" usb-configure)
|
||||
add_custom_target(usb-bootstrap DEPENDS "${LIBUSB_CONFIGURE}")
|
||||
add_custom_target(usb-configure DEPENDS "${LIBUSB_MAKEFILE}" usb-bootstrap)
|
||||
add_custom_target(usb-build ALL DEPENDS "${LIBUSB_LIBRARIES}" usb-configure)
|
||||
# Workaround since static linking didn't work out -- We need to copy the DLL to the bin directory
|
||||
add_custom_target(usb-copy ALL DEPENDS "${LIBUSB_SHARED_LIBRARY_DEST}" usb-build)
|
||||
|
||||
# Make `usb` alias to LIBUSB_LIBRARY
|
||||
add_library(usb INTERFACE)
|
||||
target_link_libraries(usb INTERFACE "${LIBUSB_LIBRARY}")
|
||||
else() # MINGW
|
||||
add_dependencies(usb usb-copy)
|
||||
target_link_libraries(usb INTERFACE "${LIBUSB_LIBRARIES}")
|
||||
target_include_directories(usb INTERFACE "${LIBUSB_INCLUDE_DIRS}")
|
||||
|
||||
if (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
Include(FindPkgConfig)
|
||||
pkg_check_modules(LIBUDEV REQUIRED libudev)
|
||||
|
||||
if (LIBUDEV_FOUND)
|
||||
target_include_directories(usb INTERFACE "${LIBUDEV_INCLUDE_DIRS}")
|
||||
target_link_libraries(usb INTERFACE "${LIBUDEV_STATIC_LIBRARIES}")
|
||||
endif()
|
||||
endif()
|
||||
else() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
# Ensure libusb compiles with UTF-8 encoding on MSVC
|
||||
if(MSVC)
|
||||
add_compile_options(/utf-8)
|
||||
@@ -236,4 +256,4 @@ else() # MINGW
|
||||
|
||||
|
||||
configure_file(config.h.in config.h)
|
||||
endif() # MINGW
|
||||
endif() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
538
src/common/host_memory.cpp
Normal file
538
src/common/host_memory.cpp
Normal file
@@ -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
|
||||
70
src/common/host_memory.h
Normal file
70
src/common/host_memory.h
Normal file
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -1,36 +1,24 @@
|
||||
// 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
|
||||
@@ -38,6 +26,7 @@ struct Entry {
|
||||
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";
|
||||
}
|
||||
@@ -119,17 +115,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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -5,12 +5,24 @@
|
||||
#pragma once
|
||||
|
||||
#include <array>
|
||||
#include <chrono>
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
#include "common/logging/log.h"
|
||||
|
||||
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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"
|
||||
|
||||
142
src/common/logging/types.h
Normal file
142
src/common/logging/types.h
Normal file
@@ -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
|
||||
@@ -111,6 +111,8 @@ struct PageTable {
|
||||
VirtualBuffer<u64> backing_addr;
|
||||
|
||||
size_t current_address_space_width_in_bits;
|
||||
|
||||
u8* fastmem_arena;
|
||||
};
|
||||
|
||||
} // namespace Common
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
@@ -249,6 +252,8 @@ void SetConfiguringGlobal(bool is_global);
|
||||
bool IsGPULevelExtreme();
|
||||
bool IsGPULevelHigh();
|
||||
|
||||
bool IsFastmemEnabled();
|
||||
|
||||
float Volume();
|
||||
|
||||
std::string GetTimeZoneString();
|
||||
|
||||
@@ -128,6 +128,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||
if (page_table) {
|
||||
config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>(
|
||||
page_table->pointers.data());
|
||||
config.fastmem_pointer = page_table->fastmem_arena;
|
||||
}
|
||||
config.absolute_offset_page_table = true;
|
||||
config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS;
|
||||
@@ -143,7 +144,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||
|
||||
// Code cache size
|
||||
config.code_cache_size = 512 * 1024 * 1024;
|
||||
config.far_code_offset = 256 * 1024 * 1024;
|
||||
config.far_code_offset = 400 * 1024 * 1024;
|
||||
|
||||
// Safe optimizations
|
||||
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) {
|
||||
@@ -171,6 +172,9 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable*
|
||||
if (!Settings::values.cpuopt_reduce_misalign_checks) {
|
||||
config.only_detect_misalignment_via_page_table_on_page_boundary = false;
|
||||
}
|
||||
if (!Settings::values.cpuopt_fastmem) {
|
||||
config.fastmem_pointer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe optimizations
|
||||
|
||||
@@ -160,6 +160,10 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
config.absolute_offset_page_table = true;
|
||||
config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128;
|
||||
config.only_detect_misalignment_via_page_table_on_page_boundary = true;
|
||||
|
||||
config.fastmem_pointer = page_table->fastmem_arena;
|
||||
config.fastmem_address_space_bits = address_space_bits;
|
||||
config.silently_mirror_fastmem = false;
|
||||
}
|
||||
|
||||
// Multi-process state
|
||||
@@ -181,7 +185,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
|
||||
// Code cache size
|
||||
config.code_cache_size = 512 * 1024 * 1024;
|
||||
config.far_code_offset = 256 * 1024 * 1024;
|
||||
config.far_code_offset = 400 * 1024 * 1024;
|
||||
|
||||
// Safe optimizations
|
||||
if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) {
|
||||
@@ -209,6 +213,9 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
if (!Settings::values.cpuopt_reduce_misalign_checks) {
|
||||
config.only_detect_misalignment_via_page_table_on_page_boundary = false;
|
||||
}
|
||||
if (!Settings::values.cpuopt_fastmem) {
|
||||
config.fastmem_pointer = nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
// Unsafe optimizations
|
||||
@@ -223,6 +230,9 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable*
|
||||
if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) {
|
||||
config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN;
|
||||
}
|
||||
if (Settings::values.cpuopt_unsafe_fastmem_check.GetValue()) {
|
||||
config.fastmem_address_space_bits = 64;
|
||||
}
|
||||
}
|
||||
|
||||
return std::make_shared<Dynarmic::A64::Jit>(config);
|
||||
|
||||
@@ -6,7 +6,7 @@
|
||||
|
||||
namespace Core {
|
||||
|
||||
DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size} {}
|
||||
DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size, 1ULL << 39} {}
|
||||
DeviceMemory::~DeviceMemory() = default;
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/common_types.h"
|
||||
#include "common/virtual_buffer.h"
|
||||
#include "common/host_memory.h"
|
||||
|
||||
namespace Core {
|
||||
|
||||
@@ -21,27 +21,30 @@ enum : u64 {
|
||||
};
|
||||
}; // namespace DramMemoryMap
|
||||
|
||||
class DeviceMemory : NonCopyable {
|
||||
class DeviceMemory {
|
||||
public:
|
||||
explicit DeviceMemory();
|
||||
~DeviceMemory();
|
||||
|
||||
DeviceMemory& operator=(const DeviceMemory&) = delete;
|
||||
DeviceMemory(const DeviceMemory&) = delete;
|
||||
|
||||
template <typename T>
|
||||
PAddr GetPhysicalAddr(const T* ptr) const {
|
||||
return (reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(buffer.data())) +
|
||||
return (reinterpret_cast<uintptr_t>(ptr) -
|
||||
reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) +
|
||||
DramMemoryMap::Base;
|
||||
}
|
||||
|
||||
u8* GetPointer(PAddr addr) {
|
||||
return buffer.data() + (addr - DramMemoryMap::Base);
|
||||
return buffer.BackingBasePointer() + (addr - DramMemoryMap::Base);
|
||||
}
|
||||
|
||||
const u8* GetPointer(PAddr addr) const {
|
||||
return buffer.data() + (addr - DramMemoryMap::Base);
|
||||
return buffer.BackingBasePointer() + (addr - DramMemoryMap::Base);
|
||||
}
|
||||
|
||||
private:
|
||||
Common::VirtualBuffer<u8> buffer;
|
||||
Common::HostMemory buffer;
|
||||
};
|
||||
|
||||
} // namespace Core
|
||||
|
||||
@@ -150,7 +150,9 @@ void ProgramMetadata::Print() const {
|
||||
LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO");
|
||||
LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min);
|
||||
LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max);
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions);
|
||||
u64_le permissions_l; // local copy to fix alignment error
|
||||
std::memcpy(&permissions_l, &acid_file_access.permissions, sizeof(permissions_l));
|
||||
LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", permissions_l);
|
||||
|
||||
// Begin ACI0 printing (actual perms, unsigned)
|
||||
LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data());
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
#include <numeric>
|
||||
#include <string>
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "core/file_sys/mode.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
|
||||
|
||||
@@ -14,7 +14,6 @@
|
||||
#endif
|
||||
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "core/file_sys/vfs.h"
|
||||
#include "core/file_sys/vfs_libzip.h"
|
||||
#include "core/file_sys/vfs_vector.h"
|
||||
|
||||
@@ -41,12 +41,27 @@ SessionRequestManager::SessionRequestManager(KernelCore& kernel_) : kernel{kerne
|
||||
|
||||
SessionRequestManager::~SessionRequestManager() = default;
|
||||
|
||||
bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& context) const {
|
||||
if (IsDomain() && context.HasDomainMessageHeader()) {
|
||||
const auto& message_header = context.GetDomainMessageHeader();
|
||||
const auto object_id = message_header.object_id;
|
||||
|
||||
if (object_id > DomainHandlerCount()) {
|
||||
LOG_CRITICAL(IPC, "object_id {} is too big!", object_id);
|
||||
return false;
|
||||
}
|
||||
return DomainHandler(object_id - 1) != nullptr;
|
||||
} else {
|
||||
return session_handler != nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
void SessionRequestHandler::ClientConnected(KServerSession* session) {
|
||||
session->SetSessionHandler(shared_from_this());
|
||||
session->ClientConnected(shared_from_this());
|
||||
}
|
||||
|
||||
void SessionRequestHandler::ClientDisconnected(KServerSession* session) {
|
||||
session->SetSessionHandler(nullptr);
|
||||
session->ClientDisconnected();
|
||||
}
|
||||
|
||||
HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_,
|
||||
|
||||
@@ -85,8 +85,8 @@ public:
|
||||
*/
|
||||
void ClientDisconnected(KServerSession* session);
|
||||
|
||||
std::shared_ptr<ServiceThread> GetServiceThread() const {
|
||||
return service_thread.lock();
|
||||
std::weak_ptr<ServiceThread> GetServiceThread() const {
|
||||
return service_thread;
|
||||
}
|
||||
|
||||
protected:
|
||||
@@ -152,10 +152,12 @@ public:
|
||||
session_handler = std::move(handler);
|
||||
}
|
||||
|
||||
std::shared_ptr<ServiceThread> GetServiceThread() const {
|
||||
std::weak_ptr<ServiceThread> GetServiceThread() const {
|
||||
return session_handler->GetServiceThread();
|
||||
}
|
||||
|
||||
bool HasSessionRequestHandler(const HLERequestContext& context) const;
|
||||
|
||||
private:
|
||||
bool is_domain{};
|
||||
SessionRequestHandlerPtr session_handler;
|
||||
@@ -163,7 +165,6 @@ private:
|
||||
|
||||
private:
|
||||
KernelCore& kernel;
|
||||
std::weak_ptr<ServiceThread> service_thread;
|
||||
};
|
||||
|
||||
/**
|
||||
|
||||
@@ -28,6 +28,9 @@ void KClientPort::Initialize(KPort* parent_port_, s32 max_sessions_, std::string
|
||||
void KClientPort::OnSessionFinalized() {
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
// This might happen if a session was improperly used with this port.
|
||||
ASSERT_MSG(num_sessions > 0, "num_sessions is invalid");
|
||||
|
||||
const auto prev = num_sessions--;
|
||||
if (prev == max_sessions) {
|
||||
this->NotifyAvailable();
|
||||
@@ -66,7 +69,7 @@ ResultCode KClientPort::CreateSession(KClientSession** out,
|
||||
// Update the session counts.
|
||||
{
|
||||
// Atomically increment the number of sessions.
|
||||
s32 new_sessions;
|
||||
s32 new_sessions{};
|
||||
{
|
||||
const auto max = max_sessions;
|
||||
auto cur_sessions = num_sessions.load(std::memory_order_acquire);
|
||||
|
||||
@@ -18,41 +18,58 @@ class KernelCore;
|
||||
|
||||
class KLightConditionVariable {
|
||||
public:
|
||||
explicit KLightConditionVariable(KernelCore& kernel_)
|
||||
: thread_queue(kernel_), kernel(kernel_) {}
|
||||
explicit KLightConditionVariable(KernelCore& kernel_) : kernel{kernel_} {}
|
||||
|
||||
void Wait(KLightLock* lock, s64 timeout = -1) {
|
||||
WaitImpl(lock, timeout);
|
||||
lock->Lock();
|
||||
void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true) {
|
||||
WaitImpl(lock, timeout, allow_terminating_thread);
|
||||
}
|
||||
|
||||
void Broadcast() {
|
||||
KScopedSchedulerLock lk{kernel};
|
||||
while (thread_queue.WakeupFrontThread() != nullptr) {
|
||||
// We want to signal all threads, and so should continue waking up until there's nothing
|
||||
// to wake.
|
||||
|
||||
// Signal all threads.
|
||||
for (auto& thread : wait_list) {
|
||||
thread.SetState(ThreadState::Runnable);
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
void WaitImpl(KLightLock* lock, s64 timeout) {
|
||||
void WaitImpl(KLightLock* lock, s64 timeout, bool allow_terminating_thread) {
|
||||
KThread* owner = GetCurrentThreadPointer(kernel);
|
||||
|
||||
// Sleep the thread.
|
||||
{
|
||||
KScopedSchedulerLockAndSleep lk(kernel, owner, timeout);
|
||||
lock->Unlock();
|
||||
KScopedSchedulerLockAndSleep lk{kernel, owner, timeout};
|
||||
|
||||
if (!thread_queue.SleepThread(owner)) {
|
||||
if (!allow_terminating_thread && owner->IsTerminationRequested()) {
|
||||
lk.CancelSleep();
|
||||
return;
|
||||
}
|
||||
|
||||
lock->Unlock();
|
||||
|
||||
// Set the thread as waiting.
|
||||
GetCurrentThread(kernel).SetState(ThreadState::Waiting);
|
||||
|
||||
// Add the thread to the queue.
|
||||
wait_list.push_back(GetCurrentThread(kernel));
|
||||
}
|
||||
|
||||
// Remove the thread from the wait list.
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
|
||||
wait_list.erase(wait_list.iterator_to(GetCurrentThread(kernel)));
|
||||
}
|
||||
|
||||
// Cancel the task that the sleep setup.
|
||||
kernel.TimeManager().UnscheduleTimeEvent(owner);
|
||||
|
||||
// Re-acquire the lock.
|
||||
lock->Lock();
|
||||
}
|
||||
KThreadQueue thread_queue;
|
||||
|
||||
KernelCore& kernel;
|
||||
KThread::WaiterList wait_list{};
|
||||
};
|
||||
} // namespace Kernel
|
||||
|
||||
@@ -59,11 +59,7 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||
owner_thread->AddWaiter(cur_thread);
|
||||
|
||||
// Set thread states.
|
||||
if (cur_thread->GetState() == ThreadState::Runnable) {
|
||||
cur_thread->SetState(ThreadState::Waiting);
|
||||
} else {
|
||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
cur_thread->SetState(ThreadState::Waiting);
|
||||
|
||||
if (owner_thread->IsSuspended()) {
|
||||
owner_thread->ContinueIfHasKernelWaiters();
|
||||
@@ -73,10 +69,9 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) {
|
||||
// We're no longer waiting on the lock owner.
|
||||
{
|
||||
KScopedSchedulerLock sl{kernel};
|
||||
KThread* owner_thread = cur_thread->GetLockOwner();
|
||||
if (owner_thread) {
|
||||
|
||||
if (KThread* owner_thread = cur_thread->GetLockOwner(); owner_thread != nullptr) {
|
||||
owner_thread->RemoveWaiter(cur_thread);
|
||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -95,17 +90,13 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) {
|
||||
|
||||
// Pass the lock to the next owner.
|
||||
uintptr_t next_tag = 0;
|
||||
if (next_owner) {
|
||||
if (next_owner != nullptr) {
|
||||
next_tag = reinterpret_cast<uintptr_t>(next_owner);
|
||||
if (num_waiters > 1) {
|
||||
next_tag |= 0x1;
|
||||
}
|
||||
|
||||
if (next_owner->GetState() == ThreadState::Waiting) {
|
||||
next_owner->SetState(ThreadState::Runnable);
|
||||
} else {
|
||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
next_owner->SetState(ThreadState::Runnable);
|
||||
|
||||
if (next_owner->IsSuspended()) {
|
||||
next_owner->ContinueIfHasKernelWaiters();
|
||||
|
||||
@@ -201,17 +201,15 @@ bool KProcess::ReleaseUserException(KThread* thread) {
|
||||
|
||||
// Remove waiter thread.
|
||||
s32 num_waiters{};
|
||||
KThread* next = thread->RemoveWaiterByKey(
|
||||
std::addressof(num_waiters),
|
||||
reinterpret_cast<uintptr_t>(std::addressof(exception_thread)));
|
||||
if (next != nullptr) {
|
||||
if (next->GetState() == ThreadState::Waiting) {
|
||||
next->SetState(ThreadState::Runnable);
|
||||
} else {
|
||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||
}
|
||||
if (KThread* next = thread->RemoveWaiterByKey(
|
||||
std::addressof(num_waiters),
|
||||
reinterpret_cast<uintptr_t>(std::addressof(exception_thread)));
|
||||
next != nullptr) {
|
||||
next->SetState(ThreadState::Runnable);
|
||||
}
|
||||
|
||||
KScheduler::SetSchedulerUpdateNeeded(kernel);
|
||||
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
|
||||
@@ -117,7 +117,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) {
|
||||
if (current_hints[index] + value <= limit_values[index] &&
|
||||
(timeout < 0 || core_timing->GetGlobalTimeNs().count() < timeout)) {
|
||||
waiter_count++;
|
||||
cond_var.Wait(&lock, timeout);
|
||||
cond_var.Wait(&lock, timeout, false);
|
||||
waiter_count--;
|
||||
} else {
|
||||
break;
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/scope_exit.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/ipc_helpers.h"
|
||||
#include "core/hle/kernel/hle_ipc.h"
|
||||
@@ -119,11 +120,25 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor
|
||||
|
||||
context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf);
|
||||
|
||||
if (auto strong_ptr = manager->GetServiceThread(); strong_ptr) {
|
||||
strong_ptr->QueueSyncRequest(*parent, std::move(context));
|
||||
return ResultSuccess;
|
||||
// In the event that something fails here, stub a result to prevent the game from crashing.
|
||||
// This is a work-around in the event that somehow we process a service request after the
|
||||
// session has been closed by the game. This has been observed to happen rarely in Pokemon
|
||||
// Sword/Shield and is likely a result of us using host threads/scheduling for services.
|
||||
// TODO(bunnei): Find a better solution here.
|
||||
auto error_guard = SCOPE_GUARD({ CompleteSyncRequest(*context); });
|
||||
|
||||
// Ensure we have a session request handler
|
||||
if (manager->HasSessionRequestHandler(*context)) {
|
||||
if (auto strong_ptr = manager->GetServiceThread().lock()) {
|
||||
strong_ptr->QueueSyncRequest(*parent, std::move(context));
|
||||
|
||||
// We succeeded.
|
||||
error_guard.Cancel();
|
||||
} else {
|
||||
ASSERT_MSG(false, "strong_ptr is nullptr!");
|
||||
}
|
||||
} else {
|
||||
ASSERT_MSG(false, "strong_ptr was nullptr!");
|
||||
ASSERT_MSG(false, "handler is invalid!");
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
@@ -131,13 +146,20 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor
|
||||
|
||||
ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) {
|
||||
ResultCode result = ResultSuccess;
|
||||
|
||||
// If the session has been converted to a domain, handle the domain request
|
||||
if (IsDomain() && context.HasDomainMessageHeader()) {
|
||||
result = HandleDomainSyncRequest(context);
|
||||
// If there is no domain header, the regular session handler is used
|
||||
} else if (manager->HasSessionHandler()) {
|
||||
// If this ServerSession has an associated HLE handler, forward the request to it.
|
||||
result = manager->SessionHandler().HandleSyncRequest(*this, context);
|
||||
if (manager->HasSessionRequestHandler(context)) {
|
||||
if (IsDomain() && context.HasDomainMessageHeader()) {
|
||||
result = HandleDomainSyncRequest(context);
|
||||
// If there is no domain header, the regular session handler is used
|
||||
} else if (manager->HasSessionHandler()) {
|
||||
// If this ServerSession has an associated HLE handler, forward the request to it.
|
||||
result = manager->SessionHandler().HandleSyncRequest(*this, context);
|
||||
}
|
||||
} else {
|
||||
ASSERT_MSG(false, "Session handler is invalid, stubbing response!");
|
||||
IPC::ResponseBuilder rb(context, 2);
|
||||
rb.Push(ResultSuccess);
|
||||
}
|
||||
|
||||
if (convert_to_domain) {
|
||||
|
||||
@@ -62,15 +62,14 @@ public:
|
||||
|
||||
void OnClientClosed();
|
||||
|
||||
/**
|
||||
* Sets the HLE handler for the session. This handler will be called to service IPC requests
|
||||
* instead of the regular IPC machinery. (The regular IPC machinery is currently not
|
||||
* implemented.)
|
||||
*/
|
||||
void SetSessionHandler(SessionRequestHandlerPtr handler) {
|
||||
void ClientConnected(SessionRequestHandlerPtr handler) {
|
||||
manager->SetSessionHandler(std::move(handler));
|
||||
}
|
||||
|
||||
void ClientDisconnected() {
|
||||
manager = nullptr;
|
||||
}
|
||||
|
||||
/**
|
||||
* Handle a sync request from the emulated application.
|
||||
*
|
||||
|
||||
@@ -63,8 +63,6 @@ struct KernelCore::Impl {
|
||||
global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel);
|
||||
global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel);
|
||||
|
||||
service_thread_manager =
|
||||
std::make_unique<Common::ThreadWorker>(1, "yuzu:ServiceThreadManager");
|
||||
is_phantom_mode_for_singlecore = false;
|
||||
|
||||
InitializePhysicalCores();
|
||||
@@ -96,7 +94,6 @@ struct KernelCore::Impl {
|
||||
process_list.clear();
|
||||
|
||||
// Ensures all service threads gracefully shutdown
|
||||
service_thread_manager.reset();
|
||||
service_threads.clear();
|
||||
|
||||
next_object_id = 0;
|
||||
@@ -680,10 +677,6 @@ struct KernelCore::Impl {
|
||||
// Threads used for services
|
||||
std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads;
|
||||
|
||||
// Service threads are managed by a worker thread, so that a calling service thread can queue up
|
||||
// the release of itself
|
||||
std::unique_ptr<Common::ThreadWorker> service_thread_manager;
|
||||
|
||||
std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads;
|
||||
std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{};
|
||||
std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{};
|
||||
@@ -986,17 +979,14 @@ void KernelCore::ExitSVCProfile() {
|
||||
|
||||
std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) {
|
||||
auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name);
|
||||
impl->service_thread_manager->QueueWork(
|
||||
[this, service_thread] { impl->service_threads.emplace(service_thread); });
|
||||
impl->service_threads.emplace(service_thread);
|
||||
return service_thread;
|
||||
}
|
||||
|
||||
void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) {
|
||||
impl->service_thread_manager->QueueWork([this, service_thread] {
|
||||
if (auto strong_ptr = service_thread.lock()) {
|
||||
impl->service_threads.erase(strong_ptr);
|
||||
}
|
||||
});
|
||||
if (auto strong_ptr = service_thread.lock()) {
|
||||
impl->service_threads.erase(strong_ptr);
|
||||
}
|
||||
}
|
||||
|
||||
Init::KSlabResourceCounts& KernelCore::SlabResourceCounts() {
|
||||
|
||||
@@ -449,8 +449,8 @@ static ResultCode CancelSynchronization(Core::System& system, Handle handle) {
|
||||
|
||||
// Get the thread from its handle.
|
||||
KScopedAutoObject thread =
|
||||
system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(
|
||||
static_cast<Handle>(handle));
|
||||
system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle);
|
||||
R_UNLESS(thread.IsNotNull(), ResultInvalidHandle);
|
||||
|
||||
// Cancel the thread's wait.
|
||||
thread->WaitCancel();
|
||||
|
||||
@@ -19,7 +19,6 @@
|
||||
#include "common/fs/fs.h"
|
||||
#include "common/fs/path_util.h"
|
||||
#include "common/hex_util.h"
|
||||
#include "common/logging/backend.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
|
||||
@@ -236,7 +236,7 @@ Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} {
|
||||
{80, &Hid::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"},
|
||||
{81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"},
|
||||
{82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"},
|
||||
{83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"},
|
||||
{83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"},
|
||||
{91, &Hid::ActivateGesture, "ActivateGesture"},
|
||||
{100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"},
|
||||
{101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"},
|
||||
@@ -710,6 +710,27 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) {
|
||||
.IsSixAxisSensorAtRest());
|
||||
}
|
||||
|
||||
void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
struct Parameters {
|
||||
Controller_NPad::DeviceHandle sixaxis_handle;
|
||||
INSERT_PADDING_WORDS_NOINIT(1);
|
||||
u64 applet_resource_user_id;
|
||||
};
|
||||
|
||||
const auto parameters{rp.PopRaw<Parameters>()};
|
||||
|
||||
LOG_WARNING(
|
||||
Service_HID,
|
||||
"(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}",
|
||||
parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id,
|
||||
parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id);
|
||||
|
||||
IPC::ResponseBuilder rb{ctx, 3};
|
||||
rb.Push(ResultSuccess);
|
||||
rb.Push(false);
|
||||
}
|
||||
|
||||
void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) {
|
||||
IPC::RequestParser rp{ctx};
|
||||
struct Parameters {
|
||||
|
||||
@@ -100,6 +100,7 @@ private:
|
||||
void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
|
||||
void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx);
|
||||
void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx);
|
||||
void IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx);
|
||||
void ActivateGesture(Kernel::HLERequestContext& ctx);
|
||||
void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
|
||||
void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx);
|
||||
|
||||
@@ -51,6 +51,24 @@ struct hash<Service::LM::LogPacketHeaderEntry> {
|
||||
} // namespace std
|
||||
|
||||
namespace Service::LM {
|
||||
namespace {
|
||||
std::string_view NameOf(LogSeverity severity) {
|
||||
switch (severity) {
|
||||
case LogSeverity::Trace:
|
||||
return "TRACE";
|
||||
case LogSeverity::Info:
|
||||
return "INFO";
|
||||
case LogSeverity::Warning:
|
||||
return "WARNING";
|
||||
case LogSeverity::Error:
|
||||
return "ERROR";
|
||||
case LogSeverity::Fatal:
|
||||
return "FATAL";
|
||||
default:
|
||||
return "UNKNOWN";
|
||||
}
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
enum class LogDestination : u32 {
|
||||
TargetManager = 1 << 0,
|
||||
@@ -262,33 +280,8 @@ private:
|
||||
if (text_log) {
|
||||
output_log += fmt::format("Log Text: {}\n", *text_log);
|
||||
}
|
||||
|
||||
switch (entry.severity) {
|
||||
case LogSeverity::Trace:
|
||||
LOG_DEBUG(Service_LM, "LogManager TRACE ({}):\n{}", DestinationToString(destination),
|
||||
output_log);
|
||||
break;
|
||||
case LogSeverity::Info:
|
||||
LOG_INFO(Service_LM, "LogManager INFO ({}):\n{}", DestinationToString(destination),
|
||||
output_log);
|
||||
break;
|
||||
case LogSeverity::Warning:
|
||||
LOG_WARNING(Service_LM, "LogManager WARNING ({}):\n{}",
|
||||
DestinationToString(destination), output_log);
|
||||
break;
|
||||
case LogSeverity::Error:
|
||||
LOG_ERROR(Service_LM, "LogManager ERROR ({}):\n{}", DestinationToString(destination),
|
||||
output_log);
|
||||
break;
|
||||
case LogSeverity::Fatal:
|
||||
LOG_CRITICAL(Service_LM, "LogManager FATAL ({}):\n{}", DestinationToString(destination),
|
||||
output_log);
|
||||
break;
|
||||
default:
|
||||
LOG_CRITICAL(Service_LM, "LogManager UNKNOWN ({}):\n{}",
|
||||
DestinationToString(destination), output_log);
|
||||
break;
|
||||
}
|
||||
LOG_DEBUG(Service_LM, "LogManager {} ({}):\n{}", NameOf(entry.severity),
|
||||
DestinationToString(destination), output_log);
|
||||
}
|
||||
|
||||
static std::string DestinationToString(LogDestination destination) {
|
||||
|
||||
@@ -40,9 +40,11 @@ namespace SM {
|
||||
class ServiceManager;
|
||||
}
|
||||
|
||||
static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters)
|
||||
/// Arbitrary default number of maximum connections to an HLE service.
|
||||
static const u32 DefaultMaxSessions = 64;
|
||||
/// Default number of maximum connections to a server session.
|
||||
static constexpr u32 ServerSessionCountMax = 0x40;
|
||||
static_assert(ServerSessionCountMax == 0x40,
|
||||
"ServerSessionCountMax isn't 0x40 somehow, this assert is a reminder that this will "
|
||||
"break lots of things");
|
||||
|
||||
/**
|
||||
* This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it
|
||||
@@ -178,7 +180,7 @@ protected:
|
||||
* connected to this service at the same time.
|
||||
*/
|
||||
explicit ServiceFramework(Core::System& system_, const char* service_name_,
|
||||
u32 max_sessions_ = DefaultMaxSessions)
|
||||
u32 max_sessions_ = ServerSessionCountMax)
|
||||
: ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {}
|
||||
|
||||
/// Registers handlers in the service.
|
||||
|
||||
@@ -151,31 +151,23 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext&
|
||||
std::string name(PopServiceName(rp));
|
||||
|
||||
// Find the named port.
|
||||
auto result = service_manager.GetServicePort(name);
|
||||
if (result.Failed()) {
|
||||
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.Code().raw);
|
||||
return result.Code();
|
||||
auto port_result = service_manager.GetServicePort(name);
|
||||
if (port_result.Failed()) {
|
||||
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, port_result.Code().raw);
|
||||
return port_result.Code();
|
||||
}
|
||||
auto* port = result.Unwrap();
|
||||
|
||||
// Reserve a new session from the process resource limit.
|
||||
Kernel::KScopedResourceReservation session_reservation(
|
||||
kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions);
|
||||
R_UNLESS(session_reservation.Succeeded(), Kernel::ResultLimitReached);
|
||||
auto& port = port_result.Unwrap()->GetClientPort();
|
||||
|
||||
// Create a new session.
|
||||
auto* session = Kernel::KSession::Create(kernel);
|
||||
session->Initialize(&port->GetClientPort(), std::move(name));
|
||||
|
||||
// Commit the session reservation.
|
||||
session_reservation.Commit();
|
||||
|
||||
// Enqueue the session with the named port.
|
||||
port->EnqueueSession(&session->GetServerSession());
|
||||
Kernel::KClientSession* session{};
|
||||
if (const auto result = port.CreateSession(std::addressof(session)); result.IsError()) {
|
||||
LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw);
|
||||
return result;
|
||||
}
|
||||
|
||||
LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId());
|
||||
|
||||
return MakeResult(&session->GetClientSession());
|
||||
return MakeResult(session);
|
||||
}
|
||||
|
||||
void SM::RegisterService(Kernel::HLERequestContext& ctx) {
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#include "common/common_types.h"
|
||||
#include "common/logging/log.h"
|
||||
#include "common/page_table.h"
|
||||
#include "common/settings.h"
|
||||
#include "common/swap.h"
|
||||
#include "core/arm/arm_interface.h"
|
||||
#include "core/core.h"
|
||||
@@ -32,6 +33,7 @@ struct Memory::Impl {
|
||||
|
||||
void SetCurrentPageTable(Kernel::KProcess& process, u32 core_id) {
|
||||
current_page_table = &process.PageTable().PageTableImpl();
|
||||
current_page_table->fastmem_arena = system.DeviceMemory().buffer.VirtualBasePointer();
|
||||
|
||||
const std::size_t address_space_width = process.PageTable().GetAddressSpaceWidth();
|
||||
|
||||
@@ -41,13 +43,23 @@ struct Memory::Impl {
|
||||
void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
ASSERT_MSG(target >= DramMemoryMap::Base && target < DramMemoryMap::End,
|
||||
"Out of bounds target: {:016X}", target);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory);
|
||||
|
||||
if (Settings::IsFastmemEnabled()) {
|
||||
system.DeviceMemory().buffer.Map(base, target - DramMemoryMap::Base, size);
|
||||
}
|
||||
}
|
||||
|
||||
void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) {
|
||||
ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size);
|
||||
ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base);
|
||||
MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped);
|
||||
|
||||
if (Settings::IsFastmemEnabled()) {
|
||||
system.DeviceMemory().buffer.Unmap(base, size);
|
||||
}
|
||||
}
|
||||
|
||||
bool IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const {
|
||||
@@ -466,6 +478,12 @@ struct Memory::Impl {
|
||||
if (vaddr == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (Settings::IsFastmemEnabled()) {
|
||||
const bool is_read_enable = Settings::IsGPULevelHigh() || !cached;
|
||||
system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached);
|
||||
}
|
||||
|
||||
// Iterate over a contiguous CPU address space, which corresponds to the specified GPU
|
||||
// address space, marking the region as un/cached. The region is marked un/cached at a
|
||||
// granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size
|
||||
|
||||
@@ -230,6 +230,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader,
|
||||
Settings::values.use_asynchronous_gpu_emulation.GetValue());
|
||||
AddField(field_type, "Renderer_UseNvdecEmulation",
|
||||
Settings::values.use_nvdec_emulation.GetValue());
|
||||
AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue());
|
||||
AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue());
|
||||
AddField(field_type, "Renderer_UseAssemblyShaders",
|
||||
Settings::values.use_assembly_shaders.GetValue());
|
||||
|
||||
@@ -71,8 +71,7 @@ if (ENABLE_SDL2)
|
||||
target_compile_definitions(input_common PRIVATE HAVE_SDL2)
|
||||
endif()
|
||||
|
||||
target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR})
|
||||
target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES})
|
||||
target_link_libraries(input_common PRIVATE usb)
|
||||
|
||||
create_target_directory_groups(input_common)
|
||||
target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost)
|
||||
|
||||
@@ -2,6 +2,7 @@ add_executable(tests
|
||||
common/bit_field.cpp
|
||||
common/cityhash.cpp
|
||||
common/fibers.cpp
|
||||
common/host_memory.cpp
|
||||
common/param_package.cpp
|
||||
common/ring_buffer.cpp
|
||||
core/core_timing.cpp
|
||||
|
||||
183
src/tests/common/host_memory.cpp
Normal file
183
src/tests/common/host_memory.cpp
Normal file
@@ -0,0 +1,183 @@
|
||||
// Copyright 2021 yuzu emulator team
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <catch2/catch.hpp>
|
||||
|
||||
#include "common/host_memory.h"
|
||||
|
||||
using Common::HostMemory;
|
||||
|
||||
static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
|
||||
static constexpr size_t BACKING_SIZE = 4ULL * 1024 * 1024 * 1024;
|
||||
|
||||
TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
|
||||
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
|
||||
{ HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple map", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x8000, 0x1000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[0] = 50;
|
||||
REQUIRE(data[0] == 50);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple mirror map", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
mem.Map(0x8000, 0x4000, 0x1000);
|
||||
|
||||
volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000;
|
||||
volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000;
|
||||
mirror_b[0] = 76;
|
||||
REQUIRE(mirror_a[0x1000] == 76);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[75] = 50;
|
||||
REQUIRE(data[75] == 50);
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Simple unmap and remap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
|
||||
volatile u8* const data = mem.VirtualBasePointer() + 0x5000;
|
||||
data[0] = 50;
|
||||
REQUIRE(data[0] == 50);
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
|
||||
mem.Map(0x5000, 0x3000, 0x2000);
|
||||
REQUIRE(data[0] == 50);
|
||||
|
||||
mem.Map(0x7000, 0x2000, 0x5000);
|
||||
REQUIRE(data[0x3000] == 50);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Nieche allocation", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x20000);
|
||||
mem.Unmap(0x0000, 0x4000);
|
||||
mem.Map(0x1000, 0, 0x2000);
|
||||
mem.Map(0x3000, 0, 0x1000);
|
||||
mem.Map(0, 0, 0x1000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Full unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x8000, 0x4000);
|
||||
mem.Map(0x6000, 0, 0x16000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Unmap(0x2000, 0x4000);
|
||||
mem.Map(0x2000, 0x80000, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x6000, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x2000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x1b000);
|
||||
mem.Unmap(0x3000, 0x1c000);
|
||||
mem.Map(0x3000, 0, 0x20000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap between placeholders", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x0000, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Unmap(0x2000, 0x4000);
|
||||
mem.Map(0x2000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap to origin", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x4000, 0x4000);
|
||||
mem.Map(0, 0, 0x4000);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Unmap to right", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
mem.Unmap(0x8000, 0x4000);
|
||||
mem.Map(0x8000, 0, 0x4000);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x1000] = 17;
|
||||
|
||||
mem.Unmap(0x6000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x1000] == 17);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x3000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x4000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x3000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x4000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x0000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x1000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x0000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
|
||||
TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") {
|
||||
HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE);
|
||||
mem.Map(0x4000, 0x10000, 0x2000);
|
||||
mem.Map(0x6000, 0x20000, 0x2000);
|
||||
|
||||
volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000;
|
||||
ptr[0x0000] = 19;
|
||||
ptr[0x3fff] = 12;
|
||||
|
||||
mem.Unmap(0x5000, 0x2000);
|
||||
|
||||
REQUIRE(ptr[0x0000] == 19);
|
||||
REQUIRE(ptr[0x3fff] == 12);
|
||||
}
|
||||
@@ -237,6 +237,7 @@ add_library(video_core STATIC
|
||||
texture_cache/util.cpp
|
||||
texture_cache/util.h
|
||||
textures/astc.h
|
||||
textures/astc.cpp
|
||||
textures/decoders.cpp
|
||||
textures/decoders.h
|
||||
textures/texture.cpp
|
||||
|
||||
@@ -476,6 +476,9 @@ private:
|
||||
current_size = 0;
|
||||
on_going = false;
|
||||
}
|
||||
if (empty_bits == PAGES_PER_WORD) {
|
||||
break;
|
||||
}
|
||||
page += empty_bits;
|
||||
|
||||
const int continuous_bits = std::countr_one(word >> page);
|
||||
|
||||
@@ -99,25 +99,13 @@ void ThreadManager::FlushRegion(VAddr addr, u64 size) {
|
||||
PushCommand(FlushRegionCommand(addr, size));
|
||||
return;
|
||||
}
|
||||
|
||||
// Asynchronous GPU mode
|
||||
switch (Settings::values.gpu_accuracy.GetValue()) {
|
||||
case Settings::GPUAccuracy::Normal:
|
||||
PushCommand(FlushRegionCommand(addr, size));
|
||||
break;
|
||||
case Settings::GPUAccuracy::High:
|
||||
// TODO(bunnei): Is this right? Preserving existing behavior for now
|
||||
break;
|
||||
case Settings::GPUAccuracy::Extreme: {
|
||||
auto& gpu = system.GPU();
|
||||
u64 fence = gpu.RequestFlush(addr, size);
|
||||
PushCommand(GPUTickCommand(), true);
|
||||
ASSERT(fence <= gpu.CurrentFlushRequestFence());
|
||||
break;
|
||||
}
|
||||
default:
|
||||
UNIMPLEMENTED_MSG("Unsupported gpu_accuracy {}", Settings::values.gpu_accuracy.GetValue());
|
||||
if (!Settings::IsGPULevelExtreme()) {
|
||||
return;
|
||||
}
|
||||
auto& gpu = system.GPU();
|
||||
u64 fence = gpu.RequestFlush(addr, size);
|
||||
PushCommand(GPUTickCommand(), true);
|
||||
ASSERT(fence <= gpu.CurrentFlushRequestFence());
|
||||
}
|
||||
|
||||
void ThreadManager::InvalidateRegion(VAddr addr, u64 size) {
|
||||
|
||||
@@ -763,7 +763,7 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) {
|
||||
case 1: {
|
||||
READ_UINT_VALUES(2)
|
||||
uint L0 = (v[0] >> 2) | (v[1] & 0xC0);
|
||||
uint L1 = max(L0 + (v[1] & 0x3F), 0xFFU);
|
||||
uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU);
|
||||
ep1 = uvec4(0xFF, L0, L0, L0);
|
||||
ep2 = uvec4(0xFF, L1, L1, L1);
|
||||
break;
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
// Licensed under GPLv2 or any later version
|
||||
// Refer to the license.txt file included.
|
||||
|
||||
#include <atomic>
|
||||
|
||||
#include "common/assert.h"
|
||||
#include "common/common_types.h"
|
||||
#include "common/div_ceil.h"
|
||||
@@ -10,35 +12,59 @@
|
||||
|
||||
namespace VideoCore {
|
||||
|
||||
RasterizerAccelerated::RasterizerAccelerated(Core::Memory::Memory& cpu_memory_)
|
||||
: cpu_memory{cpu_memory_} {}
|
||||
using namespace Core::Memory;
|
||||
|
||||
RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) : cpu_memory{cpu_memory_} {}
|
||||
|
||||
RasterizerAccelerated::~RasterizerAccelerated() = default;
|
||||
|
||||
void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) {
|
||||
const auto page_end = Common::DivCeil(addr + size, Core::Memory::PAGE_SIZE);
|
||||
for (auto page = addr >> Core::Memory::PAGE_BITS; page != page_end; ++page) {
|
||||
auto& count = cached_pages.at(page >> 2).Count(page);
|
||||
u64 uncache_begin = 0;
|
||||
u64 cache_begin = 0;
|
||||
u64 uncache_bytes = 0;
|
||||
u64 cache_bytes = 0;
|
||||
|
||||
std::atomic_thread_fence(std::memory_order_acquire);
|
||||
const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE);
|
||||
for (u64 page = addr >> PAGE_BITS; page != page_end; ++page) {
|
||||
std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page);
|
||||
|
||||
if (delta > 0) {
|
||||
ASSERT_MSG(count < UINT16_MAX, "Count may overflow!");
|
||||
ASSERT_MSG(count.load(std::memory_order::relaxed) < UINT16_MAX, "Count may overflow!");
|
||||
} else if (delta < 0) {
|
||||
ASSERT_MSG(count > 0, "Count may underflow!");
|
||||
ASSERT_MSG(count.load(std::memory_order::relaxed) > 0, "Count may underflow!");
|
||||
} else {
|
||||
ASSERT_MSG(true, "Delta must be non-zero!");
|
||||
ASSERT_MSG(false, "Delta must be non-zero!");
|
||||
}
|
||||
|
||||
// Adds or subtracts 1, as count is a unsigned 8-bit value
|
||||
count += static_cast<u16>(delta);
|
||||
count.fetch_add(static_cast<u16>(delta), std::memory_order_release);
|
||||
|
||||
// Assume delta is either -1 or 1
|
||||
if (count == 0) {
|
||||
cpu_memory.RasterizerMarkRegionCached(page << Core::Memory::PAGE_BITS,
|
||||
Core::Memory::PAGE_SIZE, false);
|
||||
} else if (count == 1 && delta > 0) {
|
||||
cpu_memory.RasterizerMarkRegionCached(page << Core::Memory::PAGE_BITS,
|
||||
Core::Memory::PAGE_SIZE, true);
|
||||
if (count.load(std::memory_order::relaxed) == 0) {
|
||||
if (uncache_bytes == 0) {
|
||||
uncache_begin = page;
|
||||
}
|
||||
uncache_bytes += PAGE_SIZE;
|
||||
} else if (uncache_bytes > 0) {
|
||||
cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false);
|
||||
uncache_bytes = 0;
|
||||
}
|
||||
if (count.load(std::memory_order::relaxed) == 1 && delta > 0) {
|
||||
if (cache_bytes == 0) {
|
||||
cache_begin = page;
|
||||
}
|
||||
cache_bytes += PAGE_SIZE;
|
||||
} else if (cache_bytes > 0) {
|
||||
cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true);
|
||||
cache_bytes = 0;
|
||||
}
|
||||
}
|
||||
if (uncache_bytes > 0) {
|
||||
cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false);
|
||||
}
|
||||
if (cache_bytes > 0) {
|
||||
cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -9,6 +9,8 @@
|
||||
|
||||
#include <glad/glad.h>
|
||||
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "video_core/renderer_opengl/gl_device.h"
|
||||
#include "video_core/renderer_opengl/gl_shader_manager.h"
|
||||
#include "video_core/renderer_opengl/gl_state_tracker.h"
|
||||
@@ -307,7 +309,9 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4
|
||||
|
||||
[[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime,
|
||||
const VideoCommon::ImageInfo& info) {
|
||||
return !runtime.HasNativeASTC() && IsPixelFormatASTC(info.format);
|
||||
if (IsPixelFormatASTC(info.format)) {
|
||||
return !runtime.HasNativeASTC() && Settings::values.accelerate_astc.GetValue();
|
||||
}
|
||||
// Disable other accelerated uploads for now as they don't implement swizzled uploads
|
||||
return false;
|
||||
switch (info.type) {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "common/bit_cast.h"
|
||||
#include "common/settings.h"
|
||||
|
||||
#include "video_core/engines/fermi_2d.h"
|
||||
#include "video_core/renderer_vulkan/blit_image.h"
|
||||
@@ -828,7 +829,11 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_
|
||||
commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal);
|
||||
}
|
||||
if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) {
|
||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||
if (Settings::values.accelerate_astc.GetValue()) {
|
||||
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
|
||||
} else {
|
||||
flags |= VideoCommon::ImageFlagBits::Converted;
|
||||
}
|
||||
}
|
||||
if (runtime.device.HasDebuggingToolAttached()) {
|
||||
if (image) {
|
||||
|
||||
@@ -47,6 +47,7 @@
|
||||
#include "video_core/texture_cache/formatter.h"
|
||||
#include "video_core/texture_cache/samples_helper.h"
|
||||
#include "video_core/texture_cache/util.h"
|
||||
#include "video_core/textures/astc.h"
|
||||
#include "video_core/textures/decoders.h"
|
||||
|
||||
namespace VideoCommon {
|
||||
@@ -647,6 +648,9 @@ u32 CalculateLayerSize(const ImageInfo& info) noexcept {
|
||||
}
|
||||
|
||||
LevelArray CalculateMipLevelOffsets(const ImageInfo& info) noexcept {
|
||||
if (info.type == ImageType::Linear) {
|
||||
return {};
|
||||
}
|
||||
ASSERT(info.resources.levels <= static_cast<s32>(MAX_MIP_LEVELS));
|
||||
const LevelInfo level_info = MakeLevelInfo(info);
|
||||
LevelArray offsets{};
|
||||
@@ -881,8 +885,16 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8
|
||||
ASSERT(copy.image_extent == mip_size);
|
||||
ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width));
|
||||
ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height));
|
||||
DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent,
|
||||
output.subspan(output_offset));
|
||||
if (IsPixelFormatASTC(info.format)) {
|
||||
ASSERT(copy.image_extent.depth == 1);
|
||||
Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset),
|
||||
copy.image_extent.width, copy.image_extent.height,
|
||||
copy.image_subresource.num_layers, tile_size.width,
|
||||
tile_size.height, output.subspan(output_offset));
|
||||
} else {
|
||||
DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent,
|
||||
output.subspan(output_offset));
|
||||
}
|
||||
copy.buffer_offset = output_offset;
|
||||
copy.buffer_row_length = mip_size.width;
|
||||
copy.buffer_image_height = mip_size.height;
|
||||
|
||||
1577
src/video_core/textures/astc.cpp
Normal file
1577
src/video_core/textures/astc.cpp
Normal file
File diff suppressed because it is too large
Load Diff
@@ -129,4 +129,7 @@ struct AstcBufferData {
|
||||
decltype(REPLICATE_BYTE_TO_16_TABLE) replicate_byte_to_16 = REPLICATE_BYTE_TO_16_TABLE;
|
||||
} constexpr ASTC_BUFFER_DATA;
|
||||
|
||||
void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth,
|
||||
uint32_t block_width, uint32_t block_height, std::span<uint8_t> output);
|
||||
|
||||
} // namespace Tegra::Texture::ASTC
|
||||
|
||||
@@ -756,6 +756,8 @@ void Config::ReadCpuValues() {
|
||||
QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true);
|
||||
ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan,
|
||||
QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true);
|
||||
ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check,
|
||||
QStringLiteral("cpuopt_unsafe_fastmem_check"), true);
|
||||
|
||||
if (global) {
|
||||
Settings::values.cpuopt_page_tables =
|
||||
@@ -774,6 +776,8 @@ void Config::ReadCpuValues() {
|
||||
ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool();
|
||||
Settings::values.cpuopt_reduce_misalign_checks =
|
||||
ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool();
|
||||
Settings::values.cpuopt_fastmem =
|
||||
ReadSetting(QStringLiteral("cpuopt_fastmem"), true).toBool();
|
||||
}
|
||||
|
||||
qt_config->endGroup();
|
||||
@@ -803,6 +807,7 @@ void Config::ReadRendererValues() {
|
||||
QStringLiteral("use_asynchronous_gpu_emulation"), true);
|
||||
ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"),
|
||||
true);
|
||||
ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true);
|
||||
ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true);
|
||||
ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"),
|
||||
false);
|
||||
@@ -1332,6 +1337,8 @@ void Config::SaveCpuValues() {
|
||||
Settings::values.cpuopt_unsafe_reduce_fp_error, true);
|
||||
WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"),
|
||||
Settings::values.cpuopt_unsafe_inaccurate_nan, true);
|
||||
WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"),
|
||||
Settings::values.cpuopt_unsafe_fastmem_check, true);
|
||||
|
||||
if (global) {
|
||||
WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables,
|
||||
@@ -1348,6 +1355,7 @@ void Config::SaveCpuValues() {
|
||||
WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true);
|
||||
WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"),
|
||||
Settings::values.cpuopt_reduce_misalign_checks, true);
|
||||
WriteSetting(QStringLiteral("cpuopt_fastmem"), Settings::values.cpuopt_fastmem, true);
|
||||
}
|
||||
|
||||
qt_config->endGroup();
|
||||
@@ -1381,6 +1389,7 @@ void Config::SaveRendererValues() {
|
||||
Settings::values.use_asynchronous_gpu_emulation, true);
|
||||
WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation,
|
||||
true);
|
||||
WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true);
|
||||
WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true);
|
||||
WriteSettingGlobal(QStringLiteral("use_assembly_shaders"),
|
||||
Settings::values.use_assembly_shaders, false);
|
||||
|
||||
@@ -35,12 +35,15 @@ void ConfigureCpu::SetConfiguration() {
|
||||
ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock);
|
||||
ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock);
|
||||
ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock);
|
||||
ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock);
|
||||
|
||||
ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue());
|
||||
ui->cpuopt_unsafe_reduce_fp_error->setChecked(
|
||||
Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue());
|
||||
ui->cpuopt_unsafe_inaccurate_nan->setChecked(
|
||||
Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue());
|
||||
ui->cpuopt_unsafe_fastmem_check->setChecked(
|
||||
Settings::values.cpuopt_unsafe_fastmem_check.GetValue());
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue()));
|
||||
@@ -84,6 +87,9 @@ void ConfigureCpu::ApplyConfiguration() {
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan,
|
||||
ui->cpuopt_unsafe_inaccurate_nan,
|
||||
cpuopt_unsafe_inaccurate_nan);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_fastmem_check,
|
||||
ui->cpuopt_unsafe_fastmem_check,
|
||||
cpuopt_unsafe_fastmem_check);
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
// Guard if during game and set to game-specific value
|
||||
@@ -134,4 +140,7 @@ void ConfigureCpu::SetupPerGameUI() {
|
||||
ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan,
|
||||
Settings::values.cpuopt_unsafe_inaccurate_nan,
|
||||
cpuopt_unsafe_inaccurate_nan);
|
||||
ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_fastmem_check,
|
||||
Settings::values.cpuopt_unsafe_fastmem_check,
|
||||
cpuopt_unsafe_fastmem_check);
|
||||
}
|
||||
|
||||
@@ -41,4 +41,5 @@ private:
|
||||
ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma;
|
||||
ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error;
|
||||
ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan;
|
||||
ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check;
|
||||
};
|
||||
|
||||
@@ -123,6 +123,18 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cpuopt_unsafe_fastmem_check">
|
||||
<property name="toolTip">
|
||||
<string>
|
||||
<div>This option improves speed by eliminating a safety check before every memory read/write in guest. Disabling it may allow a game to read/write the emulator's memory.</div>
|
||||
</string>
|
||||
</property>
|
||||
<property name="text">
|
||||
<string>Disable address space checks</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -39,6 +39,8 @@ void ConfigureCpuDebug::SetConfiguration() {
|
||||
ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir);
|
||||
ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock);
|
||||
ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks);
|
||||
ui->cpuopt_fastmem->setEnabled(runtime_lock);
|
||||
ui->cpuopt_fastmem->setChecked(Settings::values.cpuopt_fastmem);
|
||||
}
|
||||
|
||||
void ConfigureCpuDebug::ApplyConfiguration() {
|
||||
@@ -50,6 +52,7 @@ void ConfigureCpuDebug::ApplyConfiguration() {
|
||||
Settings::values.cpuopt_const_prop = ui->cpuopt_const_prop->isChecked();
|
||||
Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked();
|
||||
Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked();
|
||||
Settings::values.cpuopt_fastmem = ui->cpuopt_fastmem->isChecked();
|
||||
}
|
||||
|
||||
void ConfigureCpuDebug::changeEvent(QEvent* event) {
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
<br>
|
||||
If you're not sure what these do, keep all of these enabled.
|
||||
<br>
|
||||
These settings only take effect when CPU Accuracy is "Debug Mode".
|
||||
These settings, when disabled, only take effect when CPU Accuracy is "Debug Mode".
|
||||
</div>
|
||||
</string>
|
||||
</property>
|
||||
@@ -139,6 +139,20 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="cpuopt_fastmem">
|
||||
<property name="text">
|
||||
<string>Enable Host MMU Emulation</string>
|
||||
</property>
|
||||
<property name="toolTip">
|
||||
<string>
|
||||
<div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div>
|
||||
<div style="white-space: nowrap">Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.</div>
|
||||
<div style="white-space: nowrap">Disabling this forces all memory accesses to use Software MMU Emulation.</div>
|
||||
</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
</layout>
|
||||
</widget>
|
||||
</item>
|
||||
|
||||
@@ -70,10 +70,12 @@ void ConfigureGraphics::SetConfiguration() {
|
||||
ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
|
||||
ui->use_disk_shader_cache->setEnabled(runtime_lock);
|
||||
ui->use_nvdec_emulation->setEnabled(runtime_lock);
|
||||
ui->accelerate_astc->setEnabled(runtime_lock);
|
||||
ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
|
||||
ui->use_asynchronous_gpu_emulation->setChecked(
|
||||
Settings::values.use_asynchronous_gpu_emulation.GetValue());
|
||||
ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue());
|
||||
ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue());
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue()));
|
||||
@@ -118,6 +120,8 @@ void ConfigureGraphics::ApplyConfiguration() {
|
||||
use_asynchronous_gpu_emulation);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation,
|
||||
ui->use_nvdec_emulation, use_nvdec_emulation);
|
||||
ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc,
|
||||
accelerate_astc);
|
||||
|
||||
if (Settings::IsConfiguringGlobal()) {
|
||||
// Guard if during game and set to game-specific value
|
||||
@@ -254,6 +258,7 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
ui->use_asynchronous_gpu_emulation->setEnabled(
|
||||
Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
|
||||
ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal());
|
||||
ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal());
|
||||
ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal());
|
||||
ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal());
|
||||
|
||||
@@ -269,6 +274,8 @@ void ConfigureGraphics::SetupPerGameUI() {
|
||||
ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache);
|
||||
ConfigurationShared::SetColoredTristate(
|
||||
ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation);
|
||||
ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc,
|
||||
accelerate_astc);
|
||||
ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation,
|
||||
Settings::values.use_asynchronous_gpu_emulation,
|
||||
use_asynchronous_gpu_emulation);
|
||||
|
||||
@@ -47,6 +47,7 @@ private:
|
||||
QColor bg_color;
|
||||
|
||||
ConfigurationShared::CheckState use_nvdec_emulation;
|
||||
ConfigurationShared::CheckState accelerate_astc;
|
||||
ConfigurationShared::CheckState use_disk_shader_cache;
|
||||
ConfigurationShared::CheckState use_asynchronous_gpu_emulation;
|
||||
|
||||
|
||||
@@ -104,6 +104,13 @@
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QCheckBox" name="accelerate_astc">
|
||||
<property name="text">
|
||||
<string>Accelerate ASTC texture decoding</string>
|
||||
</property>
|
||||
</widget>
|
||||
</item>
|
||||
<item>
|
||||
<widget class="QWidget" name="fullscreen_mode_layout" native="true">
|
||||
<layout class="QHBoxLayout" name="horizontalLayout_1">
|
||||
|
||||
@@ -1395,7 +1395,8 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) {
|
||||
|
||||
void ConfigureInputPlayer::CreateProfile() {
|
||||
const auto profile_name =
|
||||
LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20);
|
||||
LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20,
|
||||
LimitableInputDialog::InputLimiter::Filesystem);
|
||||
|
||||
if (profile_name.isEmpty()) {
|
||||
return;
|
||||
|
||||
@@ -17,17 +17,30 @@
|
||||
|
||||
namespace {
|
||||
constexpr std::array default_icon_sizes{
|
||||
std::make_pair(0, QT_TR_NOOP("None")),
|
||||
std::make_pair(32, QT_TR_NOOP("Small (32x32)")),
|
||||
std::make_pair(64, QT_TR_NOOP("Standard (64x64)")),
|
||||
std::make_pair(128, QT_TR_NOOP("Large (128x128)")),
|
||||
std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")),
|
||||
std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")),
|
||||
std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")),
|
||||
std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")),
|
||||
std::make_pair(128, QT_TRANSLATE_NOOP("ConfigureUI", "Large (128x128)")),
|
||||
std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")),
|
||||
};
|
||||
|
||||
// clang-format off
|
||||
constexpr std::array row_text_names{
|
||||
QT_TR_NOOP("Filename"), QT_TR_NOOP("Filetype"), QT_TR_NOOP("Title ID"),
|
||||
QT_TR_NOOP("Title Name"), QT_TR_NOOP("None"),
|
||||
QT_TRANSLATE_NOOP("ConfigureUI", "Filename"),
|
||||
QT_TRANSLATE_NOOP("ConfigureUI", "Filetype"),
|
||||
QT_TRANSLATE_NOOP("ConfigureUI", "Title ID"),
|
||||
QT_TRANSLATE_NOOP("ConfigureUI", "Title Name"),
|
||||
QT_TRANSLATE_NOOP("ConfigureUI", "None"),
|
||||
};
|
||||
// clang-format on
|
||||
|
||||
QString GetTranslatedIconSize(size_t index) {
|
||||
return QCoreApplication::translate("ConfigureUI", default_icon_sizes[index].second);
|
||||
}
|
||||
|
||||
QString GetTranslatedRowTextName(size_t index) {
|
||||
return QCoreApplication::translate("ConfigureUI", row_text_names[index]);
|
||||
}
|
||||
} // Anonymous namespace
|
||||
|
||||
ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) {
|
||||
@@ -121,11 +134,11 @@ void ConfigureUi::RetranslateUI() {
|
||||
ui->retranslateUi(this);
|
||||
|
||||
for (int i = 0; i < ui->icon_size_combobox->count(); i++) {
|
||||
ui->icon_size_combobox->setItemText(i, tr(default_icon_sizes[i].second));
|
||||
ui->icon_size_combobox->setItemText(i, GetTranslatedIconSize(static_cast<size_t>(i)));
|
||||
}
|
||||
|
||||
for (int i = 0; i < ui->row_1_text_combobox->count(); i++) {
|
||||
const QString name = tr(row_text_names[i]);
|
||||
const QString name = GetTranslatedRowTextName(static_cast<size_t>(i));
|
||||
|
||||
ui->row_1_text_combobox->setItemText(i, name);
|
||||
ui->row_2_text_combobox->setItemText(i, name);
|
||||
@@ -152,8 +165,9 @@ void ConfigureUi::InitializeLanguageComboBox() {
|
||||
}
|
||||
|
||||
void ConfigureUi::InitializeIconSizeComboBox() {
|
||||
for (const auto& size : default_icon_sizes) {
|
||||
ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first);
|
||||
for (size_t i = 0; i < default_icon_sizes.size(); i++) {
|
||||
const auto size = default_icon_sizes[i].first;
|
||||
ui->icon_size_combobox->addItem(GetTranslatedIconSize(i), size);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -170,7 +184,7 @@ void ConfigureUi::UpdateFirstRowComboBox(bool init) {
|
||||
ui->row_1_text_combobox->clear();
|
||||
|
||||
for (std::size_t i = 0; i < row_text_names.size(); i++) {
|
||||
const QString row_text_name = QString::fromUtf8(row_text_names[i]);
|
||||
const QString row_text_name = GetTranslatedRowTextName(i);
|
||||
ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
|
||||
}
|
||||
|
||||
@@ -189,7 +203,7 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) {
|
||||
ui->row_2_text_combobox->clear();
|
||||
|
||||
for (std::size_t i = 0; i < row_text_names.size(); ++i) {
|
||||
const QString row_text_name = QString::fromUtf8(row_text_names[i]);
|
||||
const QString row_text_name = GetTranslatedRowTextName(i);
|
||||
ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i));
|
||||
}
|
||||
|
||||
|
||||
@@ -1946,6 +1946,18 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa
|
||||
const auto full = res == selections.constFirst();
|
||||
const auto entry_size = CalculateRomFSEntrySize(extracted, full);
|
||||
|
||||
// The minimum required space is the size of the extracted RomFS + 1 GiB
|
||||
const auto minimum_free_space = extracted->GetSize() + 0x40000000;
|
||||
|
||||
if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) {
|
||||
QMessageBox::warning(this, tr("RomFS Extraction Failed!"),
|
||||
tr("There is not enough free space at %1 to extract the RomFS. Please "
|
||||
"free up space or select a different dump directory at "
|
||||
"Emulation > Configure > System > Filesystem > Dump Root")
|
||||
.arg(QString::fromStdString(path)));
|
||||
return;
|
||||
}
|
||||
|
||||
QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0,
|
||||
static_cast<s32>(entry_size), this);
|
||||
progress.setWindowModality(Qt::WindowModal);
|
||||
|
||||
@@ -21,11 +21,13 @@ void LimitableInputDialog::CreateUI() {
|
||||
|
||||
text_label = new QLabel(this);
|
||||
text_entry = new QLineEdit(this);
|
||||
text_label_invalid = new QLabel(this);
|
||||
buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this);
|
||||
|
||||
auto* const layout = new QVBoxLayout;
|
||||
layout->addWidget(text_label);
|
||||
layout->addWidget(text_entry);
|
||||
layout->addWidget(text_label_invalid);
|
||||
layout->addWidget(buttons);
|
||||
|
||||
setLayout(layout);
|
||||
@@ -37,18 +39,36 @@ void LimitableInputDialog::ConnectEvents() {
|
||||
}
|
||||
|
||||
QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, const QString& text,
|
||||
int min_character_limit, int max_character_limit) {
|
||||
int min_character_limit, int max_character_limit,
|
||||
InputLimiter limit_type) {
|
||||
Q_ASSERT(min_character_limit <= max_character_limit);
|
||||
|
||||
LimitableInputDialog dialog{parent};
|
||||
dialog.setWindowTitle(title);
|
||||
dialog.text_label->setText(text);
|
||||
dialog.text_entry->setMaxLength(max_character_limit);
|
||||
dialog.text_label_invalid->show();
|
||||
|
||||
switch (limit_type) {
|
||||
case InputLimiter::Filesystem:
|
||||
dialog.invalid_characters = QStringLiteral("<>:;\"/\\|,.!?*");
|
||||
break;
|
||||
default:
|
||||
dialog.invalid_characters.clear();
|
||||
dialog.text_label_invalid->hide();
|
||||
break;
|
||||
}
|
||||
dialog.text_label_invalid->setText(
|
||||
tr("The text can't contain any of the following characters:\n%1")
|
||||
.arg(dialog.invalid_characters));
|
||||
|
||||
auto* const ok_button = dialog.buttons->button(QDialogButtonBox::Ok);
|
||||
ok_button->setEnabled(false);
|
||||
connect(dialog.text_entry, &QLineEdit::textEdited, [&](const QString& new_text) {
|
||||
ok_button->setEnabled(new_text.length() >= min_character_limit);
|
||||
connect(dialog.text_entry, &QLineEdit::textEdited, [&] {
|
||||
if (!dialog.invalid_characters.isEmpty()) {
|
||||
dialog.RemoveInvalidCharacters();
|
||||
}
|
||||
ok_button->setEnabled(dialog.text_entry->text().length() >= min_character_limit);
|
||||
});
|
||||
|
||||
if (dialog.exec() != QDialog::Accepted) {
|
||||
@@ -57,3 +77,15 @@ QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, con
|
||||
|
||||
return dialog.text_entry->text();
|
||||
}
|
||||
|
||||
void LimitableInputDialog::RemoveInvalidCharacters() {
|
||||
auto cpos = text_entry->cursorPosition();
|
||||
for (int i = 0; i < text_entry->text().length(); i++) {
|
||||
if (invalid_characters.contains(text_entry->text().at(i))) {
|
||||
text_entry->setText(text_entry->text().remove(i, 1));
|
||||
i--;
|
||||
cpos--;
|
||||
}
|
||||
}
|
||||
text_entry->setCursorPosition(cpos);
|
||||
}
|
||||
|
||||
@@ -18,14 +18,24 @@ public:
|
||||
explicit LimitableInputDialog(QWidget* parent = nullptr);
|
||||
~LimitableInputDialog() override;
|
||||
|
||||
enum class InputLimiter {
|
||||
None,
|
||||
Filesystem,
|
||||
};
|
||||
|
||||
static QString GetText(QWidget* parent, const QString& title, const QString& text,
|
||||
int min_character_limit, int max_character_limit);
|
||||
int min_character_limit, int max_character_limit,
|
||||
InputLimiter limit_type = InputLimiter::None);
|
||||
|
||||
private:
|
||||
void CreateUI();
|
||||
void ConnectEvents();
|
||||
|
||||
void RemoveInvalidCharacters();
|
||||
QString invalid_characters;
|
||||
|
||||
QLabel* text_label;
|
||||
QLineEdit* text_entry;
|
||||
QLabel* text_label_invalid;
|
||||
QDialogButtonBox* buttons;
|
||||
};
|
||||
|
||||
@@ -447,8 +447,10 @@ void Config::ReadValues() {
|
||||
sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true));
|
||||
Settings::values.use_asynchronous_shaders.SetValue(
|
||||
sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false));
|
||||
Settings::values.use_asynchronous_shaders.SetValue(
|
||||
sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false));
|
||||
Settings::values.use_nvdec_emulation.SetValue(
|
||||
sdl2_config->GetBoolean("Renderer", "use_nvdec_emulation", true));
|
||||
Settings::values.accelerate_astc.SetValue(
|
||||
sdl2_config->GetBoolean("Renderer", "accelerate_astc", true));
|
||||
Settings::values.use_fast_gpu_time.SetValue(
|
||||
sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true));
|
||||
|
||||
|
||||
@@ -150,6 +150,10 @@ cpuopt_misc_ir =
|
||||
# 0: Disabled, 1 (default): Enabled
|
||||
cpuopt_reduce_misalign_checks =
|
||||
|
||||
# Enable Host MMU Emulation (faster guest memory access)
|
||||
# 0: Disabled, 1 (default): Enabled
|
||||
cpuopt_fastmem =
|
||||
|
||||
[Renderer]
|
||||
# Which backend API to use.
|
||||
# 0 (default): OpenGL, 1: Vulkan
|
||||
@@ -190,6 +194,14 @@ use_assembly_shaders =
|
||||
# 0 (default): Off, 1: On
|
||||
use_asynchronous_shaders =
|
||||
|
||||
# Enable NVDEC emulation.
|
||||
# 0: Off, 1 (default): On
|
||||
use_nvdec_emulation =
|
||||
|
||||
# Accelerate ASTC texture decoding.
|
||||
# 0: Off, 1 (default): On
|
||||
accelerate_astc =
|
||||
|
||||
# Turns on the frame limiter, which will limit frames output to the target game speed
|
||||
# 0: Off, 1: On (default)
|
||||
use_frame_limit =
|
||||
|
||||
@@ -32,17 +32,17 @@
|
||||
|
||||
class SDLGLContext : public Core::Frontend::GraphicsContext {
|
||||
public:
|
||||
explicit SDLGLContext() {
|
||||
// create a hidden window to make the shared context against
|
||||
window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
|
||||
explicit SDLGLContext(SDL_Window* window_) : window{window_} {
|
||||
context = SDL_GL_CreateContext(window);
|
||||
}
|
||||
|
||||
~SDLGLContext() {
|
||||
DoneCurrent();
|
||||
SDL_GL_DeleteContext(context);
|
||||
SDL_DestroyWindow(window);
|
||||
}
|
||||
|
||||
void SwapBuffers() override {
|
||||
SDL_GL_SwapWindow(window);
|
||||
}
|
||||
|
||||
void MakeCurrent() override {
|
||||
@@ -114,9 +114,6 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste
|
||||
exit(1);
|
||||
}
|
||||
|
||||
dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0,
|
||||
SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL);
|
||||
|
||||
SetWindowIcon();
|
||||
|
||||
if (fullscreen) {
|
||||
@@ -159,5 +156,5 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() {
|
||||
}
|
||||
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const {
|
||||
return std::make_unique<SDLGLContext>();
|
||||
return std::make_unique<SDLGLContext>(render_window);
|
||||
}
|
||||
|
||||
@@ -20,9 +20,6 @@ public:
|
||||
std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override;
|
||||
|
||||
private:
|
||||
/// Fake hidden window for the core context
|
||||
SDL_Window* dummy_window{};
|
||||
|
||||
/// Whether the GPU and driver supports the OpenGL extension required
|
||||
bool SupportsRequiredGLExtensions();
|
||||
|
||||
|
||||
Reference in New Issue
Block a user