diff options
125 files changed, 3863 insertions, 790 deletions
diff --git a/.ci/scripts/common/post-upload.sh b/.ci/scripts/common/post-upload.sh index 99e79fcb6..387431564 100644 --- a/.ci/scripts/common/post-upload.sh +++ b/.ci/scripts/common/post-upload.sh | |||
| @@ -9,11 +9,5 @@ cp "${REV_NAME}-source.tar.xz" "$DIR_NAME" | |||
| 9 | 9 | ||
| 10 | tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$DIR_NAME" | 10 | tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$DIR_NAME" |
| 11 | 11 | ||
| 12 | mv "$DIR_NAME" $RELEASE_NAME | ||
| 13 | mv "${REV_NAME}-source.tar.xz" $RELEASE_NAME | ||
| 14 | |||
| 15 | 7z a "$REV_NAME.7z" $RELEASE_NAME | ||
| 16 | |||
| 17 | # move the compiled archive into the artifacts directory to be uploaded by travis releases | 12 | # move the compiled archive into the artifacts directory to be uploaded by travis releases |
| 18 | mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/" | 13 | mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/" |
| 19 | mv "$REV_NAME.7z" "${ARTIFACTS_DIR}/" | ||
diff --git a/.ci/scripts/windows/upload.sh b/.ci/scripts/windows/upload.sh index ebf5b7dc1..3c6a74218 100644 --- a/.ci/scripts/windows/upload.sh +++ b/.ci/scripts/windows/upload.sh | |||
| @@ -3,8 +3,8 @@ | |||
| 3 | . .ci/scripts/common/pre-upload.sh | 3 | . .ci/scripts/common/pre-upload.sh |
| 4 | 4 | ||
| 5 | REV_NAME="yuzu-windows-mingw-${GITDATE}-${GITREV}" | 5 | REV_NAME="yuzu-windows-mingw-${GITDATE}-${GITREV}" |
| 6 | ARCHIVE_NAME="${REV_NAME}.tar.gz" | 6 | ARCHIVE_NAME="${REV_NAME}.tar.xz" |
| 7 | COMPRESSION_FLAGS="-czvf" | 7 | COMPRESSION_FLAGS="-cJvf" |
| 8 | 8 | ||
| 9 | if [ "${RELEASE_NAME}" = "mainline" ]; then | 9 | if [ "${RELEASE_NAME}" = "mainline" ]; then |
| 10 | DIR_NAME="${REV_NAME}" | 10 | DIR_NAME="${REV_NAME}" |
diff --git a/CMakeLists.txt b/CMakeLists.txt index ba207dfd1..97afaf1a9 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -23,6 +23,8 @@ option(ENABLE_WEB_SERVICE "Enable web services (telemetry, etc.)" ON) | |||
| 23 | 23 | ||
| 24 | option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF) | 24 | option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF) |
| 25 | 25 | ||
| 26 | option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF) | ||
| 27 | |||
| 26 | CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF) | 28 | CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF) |
| 27 | 29 | ||
| 28 | option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) | 30 | option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) |
| @@ -420,14 +422,22 @@ elseif (TARGET Boost::boost) | |||
| 420 | endif() | 422 | endif() |
| 421 | 423 | ||
| 422 | # Ensure libusb is properly configured (based on dolphin libusb include) | 424 | # Ensure libusb is properly configured (based on dolphin libusb include) |
| 423 | if(NOT APPLE) | 425 | if(NOT APPLE AND NOT YUZU_USE_BUNDLED_LIBUSB) |
| 424 | include(FindPkgConfig) | 426 | include(FindPkgConfig) |
| 425 | find_package(LibUSB) | 427 | if (PKG_CONFIG_FOUND) |
| 426 | endif() | 428 | pkg_check_modules(LIBUSB QUIET libusb-1.0>=1.0.24) |
| 427 | if (NOT LIBUSB_FOUND) | 429 | else() |
| 428 | add_subdirectory(externals/libusb) | 430 | find_package(LibUSB) |
| 429 | set(LIBUSB_INCLUDE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/externals/libusb/libusb/libusb") | 431 | endif() |
| 430 | set(LIBUSB_LIBRARIES usb) | 432 | |
| 433 | if (LIBUSB_FOUND) | ||
| 434 | add_library(usb INTERFACE) | ||
| 435 | target_include_directories(usb INTERFACE "${LIBUSB_INCLUDE_DIRS}") | ||
| 436 | target_link_libraries(usb INTERFACE "${LIBUSB_LIBRARIES}") | ||
| 437 | else() | ||
| 438 | message(WARNING "libusb not found, falling back to externals") | ||
| 439 | set(YUZU_USE_BUNDLED_LIBUSB ON) | ||
| 440 | endif() | ||
| 431 | endif() | 441 | endif() |
| 432 | 442 | ||
| 433 | # List of all FFmpeg components required | 443 | # List of all FFmpeg components required |
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index ec3c0432b..d1d1436da 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -45,6 +45,11 @@ target_include_directories(microprofile INTERFACE ./microprofile) | |||
| 45 | add_library(unicorn-headers INTERFACE) | 45 | add_library(unicorn-headers INTERFACE) |
| 46 | target_include_directories(unicorn-headers INTERFACE ./unicorn/include) | 46 | target_include_directories(unicorn-headers INTERFACE ./unicorn/include) |
| 47 | 47 | ||
| 48 | # libusb | ||
| 49 | if (NOT LIBUSB_FOUND OR YUZU_USE_BUNDLED_LIBUSB) | ||
| 50 | add_subdirectory(libusb) | ||
| 51 | endif() | ||
| 52 | |||
| 48 | # SDL2 | 53 | # SDL2 |
| 49 | if (NOT SDL2_FOUND AND ENABLE_SDL2) | 54 | if (NOT SDL2_FOUND AND ENABLE_SDL2) |
| 50 | if (NOT WIN32) | 55 | if (NOT WIN32) |
diff --git a/externals/SDL b/externals/SDL | |||
| Subproject 107db2d89953ee7cc03417d43da1f26bd03aad5 | Subproject 2f248a2a31c3323ecc37c00ad5e269e347ae392 | ||
diff --git a/externals/dynarmic b/externals/dynarmic | |||
| Subproject 36c3b289a090aaf59a24346f57ebe1b13efb36c | Subproject 0c12614d1a7a72d778609920dde96a4c63074ec | ||
diff --git a/externals/libusb/CMakeLists.txt b/externals/libusb/CMakeLists.txt index 3ef007b40..7180fd42a 100644 --- a/externals/libusb/CMakeLists.txt +++ b/externals/libusb/CMakeLists.txt | |||
| @@ -1,10 +1,13 @@ | |||
| 1 | if (MINGW) | 1 | if (MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux")) |
| 2 | # The MinGW toolchain for some reason doesn't work with this CMakeLists file after updating to | 2 | set(LIBUSB_FOUND ON CACHE BOOL "libusb is present" FORCE) |
| 3 | # 1.0.24, so we do it the old-fashioned way for now. We may want to move native Linux toolchains | 3 | set(LIBUSB_VERSION "1.0.24" CACHE STRING "libusb version string" FORCE) |
| 4 | # to here, too (TODO lat9nq?). | 4 | |
| 5 | # GNU toolchains for some reason doesn't work with the later half of this CMakeLists after | ||
| 6 | # updating to 1.0.24, so we do it the old-fashioned way for now. | ||
| 5 | 7 | ||
| 6 | set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb") | 8 | set(LIBUSB_PREFIX "${CMAKE_CURRENT_BINARY_DIR}/libusb") |
| 7 | set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb") | 9 | set(LIBUSB_SRC_DIR "${CMAKE_CURRENT_SOURCE_DIR}/libusb") |
| 10 | |||
| 8 | # Workarounds for MSYS/MinGW | 11 | # Workarounds for MSYS/MinGW |
| 9 | if (MSYS) | 12 | if (MSYS) |
| 10 | # CMake on Windows passes `C:/`, but we need `/C/` or `/c/` to use `configure` | 13 | # CMake on Windows passes `C:/`, but we need `/C/` or `/c/` to use `configure` |
| @@ -19,36 +22,42 @@ if (MINGW) | |||
| 19 | 22 | ||
| 20 | set(LIBUSB_CONFIGURE "${LIBUSB_SRC_DIR}/configure") | 23 | set(LIBUSB_CONFIGURE "${LIBUSB_SRC_DIR}/configure") |
| 21 | set(LIBUSB_MAKEFILE "${LIBUSB_PREFIX}/Makefile") | 24 | set(LIBUSB_MAKEFILE "${LIBUSB_PREFIX}/Makefile") |
| 22 | set(LIBUSB_LIBRARY "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll.a") | ||
| 23 | set(LIBUSB_SHARED_LIBRARY "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll") | ||
| 24 | set(LIBUSB_SHARED_LIBRARY_DEST "${CMAKE_BINARY_DIR}/bin/libusb-1.0.dll") | ||
| 25 | 25 | ||
| 26 | # 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. | 26 | if (MINGW) |
| 27 | # set(LIBUSB_CFLAGS "-DGUID_DEVINTERFACE_USB_DEVICE=\\(GUID\\){0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}}") | 27 | set(LIBUSB_LIBRARIES "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll.a" CACHE PATH "libusb library path" FORCE) |
| 28 | set(LIBUSB_SHARED_LIBRARY "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.dll") | ||
| 29 | set(LIBUSB_SHARED_LIBRARY_DEST "${CMAKE_BINARY_DIR}/bin/libusb-1.0.dll") | ||
| 30 | |||
| 31 | set(LIBUSB_CONFIGURE_ARGS --host=x86_64-w64-mingw32 --build=x86_64-windows) | ||
| 32 | else() | ||
| 33 | set(LIBUSB_LIBRARIES "${LIBUSB_PREFIX}/libusb/.libs/libusb-1.0.a" CACHE PATH "libusb library path" FORCE) | ||
| 34 | endif() | ||
| 35 | |||
| 36 | set(LIBUSB_INCLUDE_DIRS "${LIBUSB_SRC_DIR}/libusb" CACHE PATH "libusb headers path" FORCE) | ||
| 37 | |||
| 38 | # 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. | ||
| 39 | if (NOT MINGW) | ||
| 40 | set(LIBUSB_CFLAGS "-DGUID_DEVINTERFACE_USB_DEVICE=\\(GUID\\){0xA5DCBF10,0x6530,0x11D2,{0x90,0x1F,0x00,0xC0,0x4F,0xB9,0x51,0xED}}") | ||
| 41 | endif() | ||
| 28 | 42 | ||
| 29 | make_directory("${LIBUSB_PREFIX}") | 43 | make_directory("${LIBUSB_PREFIX}") |
| 30 | 44 | ||
| 31 | add_custom_command( | 45 | add_custom_command( |
| 32 | OUTPUT | 46 | OUTPUT |
| 33 | "${LIBUSB_LIBRARY}" | 47 | "${LIBUSB_LIBRARIES}" |
| 34 | COMMAND | 48 | COMMAND |
| 35 | make | 49 | make |
| 36 | WORKING_DIRECTORY | 50 | WORKING_DIRECTORY |
| 37 | "${LIBUSB_PREFIX}" | 51 | "${LIBUSB_PREFIX}" |
| 38 | ) | 52 | ) |
| 39 | 53 | ||
| 40 | # We may use this path for other GNU toolchains, so put all of the MinGW-specific stuff here | ||
| 41 | if (MINGW) | ||
| 42 | set(LIBUSB_CONFIGURE_ARGS --host=x86_64-w64-mingw32 --build=x86_64-windows) | ||
| 43 | endif() | ||
| 44 | |||
| 45 | add_custom_command( | 54 | add_custom_command( |
| 46 | OUTPUT | 55 | OUTPUT |
| 47 | "${LIBUSB_MAKEFILE}" | 56 | "${LIBUSB_MAKEFILE}" |
| 48 | COMMAND | 57 | COMMAND |
| 49 | # /bin/env | 58 | env |
| 50 | # CFLAGS="${LIBUSB_CFLAGS}" | 59 | CFLAGS="${LIBUSB_CFLAGS}" |
| 51 | /bin/sh "${LIBUSB_CONFIGURE}" | 60 | sh "${LIBUSB_CONFIGURE}" |
| 52 | ${LIBUSB_CONFIGURE_ARGS} | 61 | ${LIBUSB_CONFIGURE_ARGS} |
| 53 | --srcdir="${LIBUSB_SRC_DIR}" | 62 | --srcdir="${LIBUSB_SRC_DIR}" |
| 54 | WORKING_DIRECTORY | 63 | WORKING_DIRECTORY |
| @@ -59,7 +68,7 @@ if (MINGW) | |||
| 59 | OUTPUT | 68 | OUTPUT |
| 60 | "${LIBUSB_CONFIGURE}" | 69 | "${LIBUSB_CONFIGURE}" |
| 61 | COMMAND | 70 | COMMAND |
| 62 | /bin/sh "${LIBUSB_SRC_DIR}/bootstrap.sh" | 71 | sh "${LIBUSB_SRC_DIR}/bootstrap.sh" |
| 63 | WORKING_DIRECTORY | 72 | WORKING_DIRECTORY |
| 64 | "${LIBUSB_SRC_DIR}" | 73 | "${LIBUSB_SRC_DIR}" |
| 65 | ) | 74 | ) |
| @@ -68,19 +77,30 @@ if (MINGW) | |||
| 68 | OUTPUT | 77 | OUTPUT |
| 69 | "${LIBUSB_SHARED_LIBRARY_DEST}" | 78 | "${LIBUSB_SHARED_LIBRARY_DEST}" |
| 70 | COMMAND | 79 | COMMAND |
| 71 | /bin/cp "${LIBUSB_SHARED_LIBRARY}" "${LIBUSB_SHARED_LIBRARY_DEST}" | 80 | cp "${LIBUSB_SHARED_LIBRARY}" "${LIBUSB_SHARED_LIBRARY_DEST}" |
| 72 | ) | 81 | ) |
| 73 | 82 | ||
| 74 | add_custom_target(usb-bootstrap ALL DEPENDS "${LIBUSB_CONFIGURE}") | 83 | add_custom_target(usb-bootstrap DEPENDS "${LIBUSB_CONFIGURE}") |
| 75 | add_custom_target(usb-configure ALL DEPENDS "${LIBUSB_MAKEFILE}" usb-bootstrap) | 84 | add_custom_target(usb-configure DEPENDS "${LIBUSB_MAKEFILE}" usb-bootstrap) |
| 76 | add_custom_target(usb-build ALL DEPENDS "${LIBUSB_LIBRARY}" usb-configure) | 85 | add_custom_target(usb-build ALL DEPENDS "${LIBUSB_LIBRARIES}" usb-configure) |
| 77 | # Workaround since static linking didn't work out -- We need to copy the DLL to the bin directory | 86 | # Workaround since static linking didn't work out -- We need to copy the DLL to the bin directory |
| 78 | add_custom_target(usb-copy ALL DEPENDS "${LIBUSB_SHARED_LIBRARY_DEST}" usb-build) | 87 | add_custom_target(usb-copy ALL DEPENDS "${LIBUSB_SHARED_LIBRARY_DEST}" usb-build) |
| 79 | 88 | ||
| 80 | # Make `usb` alias to LIBUSB_LIBRARY | ||
| 81 | add_library(usb INTERFACE) | 89 | add_library(usb INTERFACE) |
| 82 | target_link_libraries(usb INTERFACE "${LIBUSB_LIBRARY}") | 90 | add_dependencies(usb usb-copy) |
| 83 | else() # MINGW | 91 | target_link_libraries(usb INTERFACE "${LIBUSB_LIBRARIES}") |
| 92 | target_include_directories(usb INTERFACE "${LIBUSB_INCLUDE_DIRS}") | ||
| 93 | |||
| 94 | if (${CMAKE_SYSTEM_NAME} MATCHES "Linux") | ||
| 95 | Include(FindPkgConfig) | ||
| 96 | pkg_check_modules(LIBUDEV REQUIRED libudev) | ||
| 97 | |||
| 98 | if (LIBUDEV_FOUND) | ||
| 99 | target_include_directories(usb INTERFACE "${LIBUDEV_INCLUDE_DIRS}") | ||
| 100 | target_link_libraries(usb INTERFACE "${LIBUDEV_STATIC_LIBRARIES}") | ||
| 101 | endif() | ||
| 102 | endif() | ||
| 103 | else() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") | ||
| 84 | # Ensure libusb compiles with UTF-8 encoding on MSVC | 104 | # Ensure libusb compiles with UTF-8 encoding on MSVC |
| 85 | if(MSVC) | 105 | if(MSVC) |
| 86 | add_compile_options(/utf-8) | 106 | add_compile_options(/utf-8) |
| @@ -236,4 +256,4 @@ else() # MINGW | |||
| 236 | 256 | ||
| 237 | 257 | ||
| 238 | configure_file(config.h.in config.h) | 258 | configure_file(config.h.in config.h) |
| 239 | endif() # MINGW | 259 | endif() # MINGW OR (${CMAKE_SYSTEM_NAME} MATCHES "Linux") |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7a4d9e354..7534eb8f1 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -21,14 +21,14 @@ find_package(Git QUIET) | |||
| 21 | 21 | ||
| 22 | add_custom_command(OUTPUT scm_rev.cpp | 22 | add_custom_command(OUTPUT scm_rev.cpp |
| 23 | COMMAND ${CMAKE_COMMAND} | 23 | COMMAND ${CMAKE_COMMAND} |
| 24 | -DSRC_DIR="${CMAKE_SOURCE_DIR}" | 24 | -DSRC_DIR=${CMAKE_SOURCE_DIR} |
| 25 | -DBUILD_REPOSITORY="${BUILD_REPOSITORY}" | 25 | -DBUILD_REPOSITORY=${BUILD_REPOSITORY} |
| 26 | -DTITLE_BAR_FORMAT_IDLE="${TITLE_BAR_FORMAT_IDLE}" | 26 | -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} |
| 27 | -DTITLE_BAR_FORMAT_RUNNING="${TITLE_BAR_FORMAT_RUNNING}" | 27 | -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} |
| 28 | -DBUILD_TAG="${BUILD_TAG}" | 28 | -DBUILD_TAG=${BUILD_TAG} |
| 29 | -DBUILD_ID="${DISPLAY_VERSION}" | 29 | -DBUILD_ID=${DISPLAY_VERSION} |
| 30 | -DGIT_EXECUTABLE="${GIT_EXECUTABLE}" | 30 | -DGIT_EXECUTABLE=${GIT_EXECUTABLE} |
| 31 | -P "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" | 31 | -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake |
| 32 | DEPENDS | 32 | DEPENDS |
| 33 | # WARNING! It was too much work to try and make a common location for this list, | 33 | # WARNING! It was too much work to try and make a common location for this list, |
| 34 | # so if you need to change it, please update CMakeModules/GenerateSCMRev.cmake as well | 34 | # 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 | |||
| 92 | "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" | 92 | "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" |
| 93 | # technically we should regenerate if the git version changed, but its not worth the effort imo | 93 | # technically we should regenerate if the git version changed, but its not worth the effort imo |
| 94 | "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" | 94 | "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" |
| 95 | VERBATIM | ||
| 95 | ) | 96 | ) |
| 96 | 97 | ||
| 97 | add_library(common STATIC | 98 | add_library(common STATIC |
| @@ -130,6 +131,8 @@ add_library(common STATIC | |||
| 130 | hash.h | 131 | hash.h |
| 131 | hex_util.cpp | 132 | hex_util.cpp |
| 132 | hex_util.h | 133 | hex_util.h |
| 134 | host_memory.cpp | ||
| 135 | host_memory.h | ||
| 133 | intrusive_red_black_tree.h | 136 | intrusive_red_black_tree.h |
| 134 | logging/backend.cpp | 137 | logging/backend.cpp |
| 135 | logging/backend.h | 138 | logging/backend.h |
| @@ -138,6 +141,7 @@ add_library(common STATIC | |||
| 138 | logging/log.h | 141 | logging/log.h |
| 139 | logging/text_formatter.cpp | 142 | logging/text_formatter.cpp |
| 140 | logging/text_formatter.h | 143 | logging/text_formatter.h |
| 144 | logging/types.h | ||
| 141 | lz4_compression.cpp | 145 | lz4_compression.cpp |
| 142 | lz4_compression.h | 146 | lz4_compression.h |
| 143 | math_util.h | 147 | math_util.h |
diff --git a/src/common/fs/file.cpp b/src/common/fs/file.cpp index 9f3de1cb0..710e88b39 100644 --- a/src/common/fs/file.cpp +++ b/src/common/fs/file.cpp | |||
| @@ -183,10 +183,6 @@ size_t WriteStringToFile(const std::filesystem::path& path, FileType type, | |||
| 183 | 183 | ||
| 184 | size_t AppendStringToFile(const std::filesystem::path& path, FileType type, | 184 | size_t AppendStringToFile(const std::filesystem::path& path, FileType type, |
| 185 | std::string_view string) { | 185 | std::string_view string) { |
| 186 | if (!Exists(path)) { | ||
| 187 | return WriteStringToFile(path, type, string); | ||
| 188 | } | ||
| 189 | |||
| 190 | if (!IsFile(path)) { | 186 | if (!IsFile(path)) { |
| 191 | return 0; | 187 | return 0; |
| 192 | } | 188 | } |
| @@ -309,7 +305,11 @@ bool IOFile::Flush() const { | |||
| 309 | 305 | ||
| 310 | errno = 0; | 306 | errno = 0; |
| 311 | 307 | ||
| 312 | const auto flush_result = std::fflush(file) == 0; | 308 | #ifdef _WIN32 |
| 309 | const auto flush_result = std::fflush(file) == 0 && _commit(fileno(file)) == 0; | ||
| 310 | #else | ||
| 311 | const auto flush_result = std::fflush(file) == 0 && fsync(fileno(file)) == 0; | ||
| 312 | #endif | ||
| 313 | 313 | ||
| 314 | if (!flush_result) { | 314 | if (!flush_result) { |
| 315 | const auto ec = std::error_code{errno, std::generic_category()}; | 315 | const auto ec = std::error_code{errno, std::generic_category()}; |
diff --git a/src/common/fs/file.h b/src/common/fs/file.h index 50e270c5b..0f10b6003 100644 --- a/src/common/fs/file.h +++ b/src/common/fs/file.h | |||
| @@ -71,7 +71,7 @@ template <typename Path> | |||
| 71 | 71 | ||
| 72 | /** | 72 | /** |
| 73 | * Writes a string to a file at path and returns the number of characters successfully written. | 73 | * Writes a string to a file at path and returns the number of characters successfully written. |
| 74 | * If an file already exists at path, its contents will be erased. | 74 | * If a file already exists at path, its contents will be erased. |
| 75 | * If the filesystem object at path is not a file, this function returns 0. | 75 | * If the filesystem object at path is not a file, this function returns 0. |
| 76 | * | 76 | * |
| 77 | * @param path Filesystem path | 77 | * @param path Filesystem path |
| @@ -95,7 +95,6 @@ template <typename Path> | |||
| 95 | 95 | ||
| 96 | /** | 96 | /** |
| 97 | * Appends a string to a file at path and returns the number of characters successfully written. | 97 | * Appends a string to a file at path and returns the number of characters successfully written. |
| 98 | * If a file does not exist at path, WriteStringToFile is called instead. | ||
| 99 | * If the filesystem object at path is not a file, this function returns 0. | 98 | * If the filesystem object at path is not a file, this function returns 0. |
| 100 | * | 99 | * |
| 101 | * @param path Filesystem path | 100 | * @param path Filesystem path |
diff --git a/src/common/fs/fs.cpp b/src/common/fs/fs.cpp index d492480d9..d3159e908 100644 --- a/src/common/fs/fs.cpp +++ b/src/common/fs/fs.cpp | |||
| @@ -321,7 +321,8 @@ bool RemoveDirContentsRecursively(const fs::path& path) { | |||
| 321 | 321 | ||
| 322 | std::error_code ec; | 322 | std::error_code ec; |
| 323 | 323 | ||
| 324 | for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { | 324 | // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. |
| 325 | for (const auto& entry : fs::directory_iterator(path, ec)) { | ||
| 325 | if (ec) { | 326 | if (ec) { |
| 326 | LOG_ERROR(Common_Filesystem, | 327 | LOG_ERROR(Common_Filesystem, |
| 327 | "Failed to completely enumerate the directory at path={}, ec_message={}", | 328 | "Failed to completely enumerate the directory at path={}, ec_message={}", |
| @@ -337,6 +338,12 @@ bool RemoveDirContentsRecursively(const fs::path& path) { | |||
| 337 | PathToUTF8String(entry.path()), ec.message()); | 338 | PathToUTF8String(entry.path()), ec.message()); |
| 338 | break; | 339 | break; |
| 339 | } | 340 | } |
| 341 | |||
| 342 | // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. | ||
| 343 | // recursive_directory_iterator throws an exception despite passing in a std::error_code. | ||
| 344 | if (entry.status().type() == fs::file_type::directory) { | ||
| 345 | return RemoveDirContentsRecursively(entry.path()); | ||
| 346 | } | ||
| 340 | } | 347 | } |
| 341 | 348 | ||
| 342 | if (ec) { | 349 | if (ec) { |
| @@ -475,7 +482,8 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, | |||
| 475 | 482 | ||
| 476 | std::error_code ec; | 483 | std::error_code ec; |
| 477 | 484 | ||
| 478 | for (const auto& entry : fs::recursive_directory_iterator(path, ec)) { | 485 | // TODO (Morph): Replace this with recursive_directory_iterator once it's fixed in MSVC. |
| 486 | for (const auto& entry : fs::directory_iterator(path, ec)) { | ||
| 479 | if (ec) { | 487 | if (ec) { |
| 480 | break; | 488 | break; |
| 481 | } | 489 | } |
| @@ -495,6 +503,12 @@ void IterateDirEntriesRecursively(const std::filesystem::path& path, | |||
| 495 | break; | 503 | break; |
| 496 | } | 504 | } |
| 497 | } | 505 | } |
| 506 | |||
| 507 | // TODO (Morph): Remove this when MSVC fixes recursive_directory_iterator. | ||
| 508 | // recursive_directory_iterator throws an exception despite passing in a std::error_code. | ||
| 509 | if (entry.status().type() == fs::file_type::directory) { | ||
| 510 | IterateDirEntriesRecursively(entry.path(), callback, filter); | ||
| 511 | } | ||
| 498 | } | 512 | } |
| 499 | 513 | ||
| 500 | if (callback_error || ec) { | 514 | if (callback_error || ec) { |
diff --git a/src/common/fs/path_util.h b/src/common/fs/path_util.h index 14e8c35d7..f956ac9a2 100644 --- a/src/common/fs/path_util.h +++ b/src/common/fs/path_util.h | |||
| @@ -209,7 +209,7 @@ void SetYuzuPath(YuzuPath yuzu_path, const std::filesystem::path& new_path); | |||
| 209 | 209 | ||
| 210 | #ifdef _WIN32 | 210 | #ifdef _WIN32 |
| 211 | template <typename Path> | 211 | template <typename Path> |
| 212 | [[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { | 212 | void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { |
| 213 | if constexpr (IsChar<typename Path::value_type>) { | 213 | if constexpr (IsChar<typename Path::value_type>) { |
| 214 | SetYuzuPath(yuzu_path, ToU8String(new_path)); | 214 | SetYuzuPath(yuzu_path, ToU8String(new_path)); |
| 215 | } else { | 215 | } else { |
diff --git a/src/common/host_memory.cpp b/src/common/host_memory.cpp new file mode 100644 index 000000000..8bd70abc7 --- /dev/null +++ b/src/common/host_memory.cpp | |||
| @@ -0,0 +1,538 @@ | |||
| 1 | #ifdef _WIN32 | ||
| 2 | |||
| 3 | #include <iterator> | ||
| 4 | #include <unordered_map> | ||
| 5 | #include <boost/icl/separate_interval_set.hpp> | ||
| 6 | #include <windows.h> | ||
| 7 | #include "common/dynamic_library.h" | ||
| 8 | |||
| 9 | #elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv | ||
| 10 | |||
| 11 | #ifndef _GNU_SOURCE | ||
| 12 | #define _GNU_SOURCE | ||
| 13 | #endif | ||
| 14 | #include <fcntl.h> | ||
| 15 | #include <sys/mman.h> | ||
| 16 | #include <unistd.h> | ||
| 17 | |||
| 18 | #endif // ^^^ Linux ^^^ | ||
| 19 | |||
| 20 | #include <mutex> | ||
| 21 | |||
| 22 | #include "common/alignment.h" | ||
| 23 | #include "common/assert.h" | ||
| 24 | #include "common/host_memory.h" | ||
| 25 | #include "common/logging/log.h" | ||
| 26 | #include "common/scope_exit.h" | ||
| 27 | |||
| 28 | namespace Common { | ||
| 29 | |||
| 30 | constexpr size_t PageAlignment = 0x1000; | ||
| 31 | constexpr size_t HugePageSize = 0x200000; | ||
| 32 | |||
| 33 | #ifdef _WIN32 | ||
| 34 | |||
| 35 | // Manually imported for MinGW compatibility | ||
| 36 | #ifndef MEM_RESERVE_PLACEHOLDER | ||
| 37 | #define MEM_RESERVE_PLACEHOLDER 0x0004000 | ||
| 38 | #endif | ||
| 39 | #ifndef MEM_REPLACE_PLACEHOLDER | ||
| 40 | #define MEM_REPLACE_PLACEHOLDER 0x00004000 | ||
| 41 | #endif | ||
| 42 | #ifndef MEM_COALESCE_PLACEHOLDERS | ||
| 43 | #define MEM_COALESCE_PLACEHOLDERS 0x00000001 | ||
| 44 | #endif | ||
| 45 | #ifndef MEM_PRESERVE_PLACEHOLDER | ||
| 46 | #define MEM_PRESERVE_PLACEHOLDER 0x00000002 | ||
| 47 | #endif | ||
| 48 | |||
| 49 | using PFN_CreateFileMapping2 = _Ret_maybenull_ HANDLE(WINAPI*)( | ||
| 50 | _In_ HANDLE File, _In_opt_ SECURITY_ATTRIBUTES* SecurityAttributes, _In_ ULONG DesiredAccess, | ||
| 51 | _In_ ULONG PageProtection, _In_ ULONG AllocationAttributes, _In_ ULONG64 MaximumSize, | ||
| 52 | _In_opt_ PCWSTR Name, | ||
| 53 | _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters, | ||
| 54 | _In_ ULONG ParameterCount); | ||
| 55 | |||
| 56 | using PFN_VirtualAlloc2 = _Ret_maybenull_ PVOID(WINAPI*)( | ||
| 57 | _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress, _In_ SIZE_T Size, | ||
| 58 | _In_ ULONG AllocationType, _In_ ULONG PageProtection, | ||
| 59 | _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters, | ||
| 60 | _In_ ULONG ParameterCount); | ||
| 61 | |||
| 62 | using PFN_MapViewOfFile3 = _Ret_maybenull_ PVOID(WINAPI*)( | ||
| 63 | _In_ HANDLE FileMapping, _In_opt_ HANDLE Process, _In_opt_ PVOID BaseAddress, | ||
| 64 | _In_ ULONG64 Offset, _In_ SIZE_T ViewSize, _In_ ULONG AllocationType, _In_ ULONG PageProtection, | ||
| 65 | _Inout_updates_opt_(ParameterCount) MEM_EXTENDED_PARAMETER* ExtendedParameters, | ||
| 66 | _In_ ULONG ParameterCount); | ||
| 67 | |||
| 68 | using PFN_UnmapViewOfFile2 = BOOL(WINAPI*)(_In_ HANDLE Process, _In_ PVOID BaseAddress, | ||
| 69 | _In_ ULONG UnmapFlags); | ||
| 70 | |||
| 71 | template <typename T> | ||
| 72 | static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn) { | ||
| 73 | if (!dll.GetSymbol(name, &pfn)) { | ||
| 74 | LOG_CRITICAL(HW_Memory, "Failed to load {}", name); | ||
| 75 | throw std::bad_alloc{}; | ||
| 76 | } | ||
| 77 | } | ||
| 78 | |||
| 79 | class HostMemory::Impl { | ||
| 80 | public: | ||
| 81 | explicit Impl(size_t backing_size_, size_t virtual_size_) | ||
| 82 | : backing_size{backing_size_}, virtual_size{virtual_size_}, process{GetCurrentProcess()}, | ||
| 83 | kernelbase_dll("Kernelbase") { | ||
| 84 | if (!kernelbase_dll.IsOpen()) { | ||
| 85 | LOG_CRITICAL(HW_Memory, "Failed to load Kernelbase.dll"); | ||
| 86 | throw std::bad_alloc{}; | ||
| 87 | } | ||
| 88 | GetFuncAddress(kernelbase_dll, "CreateFileMapping2", pfn_CreateFileMapping2); | ||
| 89 | GetFuncAddress(kernelbase_dll, "VirtualAlloc2", pfn_VirtualAlloc2); | ||
| 90 | GetFuncAddress(kernelbase_dll, "MapViewOfFile3", pfn_MapViewOfFile3); | ||
| 91 | GetFuncAddress(kernelbase_dll, "UnmapViewOfFile2", pfn_UnmapViewOfFile2); | ||
| 92 | |||
| 93 | // Allocate backing file map | ||
| 94 | backing_handle = | ||
| 95 | pfn_CreateFileMapping2(INVALID_HANDLE_VALUE, nullptr, FILE_MAP_WRITE | FILE_MAP_READ, | ||
| 96 | PAGE_READWRITE, SEC_COMMIT, backing_size, nullptr, nullptr, 0); | ||
| 97 | if (!backing_handle) { | ||
| 98 | LOG_CRITICAL(HW_Memory, "Failed to allocate {} MiB of backing memory", | ||
| 99 | backing_size >> 20); | ||
| 100 | throw std::bad_alloc{}; | ||
| 101 | } | ||
| 102 | // Allocate a virtual memory for the backing file map as placeholder | ||
| 103 | backing_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, backing_size, | ||
| 104 | MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, | ||
| 105 | PAGE_NOACCESS, nullptr, 0)); | ||
| 106 | if (!backing_base) { | ||
| 107 | Release(); | ||
| 108 | LOG_CRITICAL(HW_Memory, "Failed to reserve {} MiB of virtual memory", | ||
| 109 | backing_size >> 20); | ||
| 110 | throw std::bad_alloc{}; | ||
| 111 | } | ||
| 112 | // Map backing placeholder | ||
| 113 | void* const ret = pfn_MapViewOfFile3(backing_handle, process, backing_base, 0, backing_size, | ||
| 114 | MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0); | ||
| 115 | if (ret != backing_base) { | ||
| 116 | Release(); | ||
| 117 | LOG_CRITICAL(HW_Memory, "Failed to map {} MiB of virtual memory", backing_size >> 20); | ||
| 118 | throw std::bad_alloc{}; | ||
| 119 | } | ||
| 120 | // Allocate virtual address placeholder | ||
| 121 | virtual_base = static_cast<u8*>(pfn_VirtualAlloc2(process, nullptr, virtual_size, | ||
| 122 | MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, | ||
| 123 | PAGE_NOACCESS, nullptr, 0)); | ||
| 124 | if (!virtual_base) { | ||
| 125 | Release(); | ||
| 126 | LOG_CRITICAL(HW_Memory, "Failed to reserve {} GiB of virtual memory", | ||
| 127 | virtual_size >> 30); | ||
| 128 | throw std::bad_alloc{}; | ||
| 129 | } | ||
| 130 | } | ||
| 131 | |||
| 132 | ~Impl() { | ||
| 133 | Release(); | ||
| 134 | } | ||
| 135 | |||
| 136 | void Map(size_t virtual_offset, size_t host_offset, size_t length) { | ||
| 137 | std::unique_lock lock{placeholder_mutex}; | ||
| 138 | if (!IsNiechePlaceholder(virtual_offset, length)) { | ||
| 139 | Split(virtual_offset, length); | ||
| 140 | } | ||
| 141 | ASSERT(placeholders.find({virtual_offset, virtual_offset + length}) == placeholders.end()); | ||
| 142 | TrackPlaceholder(virtual_offset, host_offset, length); | ||
| 143 | |||
| 144 | MapView(virtual_offset, host_offset, length); | ||
| 145 | } | ||
| 146 | |||
| 147 | void Unmap(size_t virtual_offset, size_t length) { | ||
| 148 | std::lock_guard lock{placeholder_mutex}; | ||
| 149 | |||
| 150 | // Unmap until there are no more placeholders | ||
| 151 | while (UnmapOnePlaceholder(virtual_offset, length)) { | ||
| 152 | } | ||
| 153 | } | ||
| 154 | |||
| 155 | void Protect(size_t virtual_offset, size_t length, bool read, bool write) { | ||
| 156 | DWORD new_flags{}; | ||
| 157 | if (read && write) { | ||
| 158 | new_flags = PAGE_READWRITE; | ||
| 159 | } else if (read && !write) { | ||
| 160 | new_flags = PAGE_READONLY; | ||
| 161 | } else if (!read && !write) { | ||
| 162 | new_flags = PAGE_NOACCESS; | ||
| 163 | } else { | ||
| 164 | UNIMPLEMENTED_MSG("Protection flag combination read={} write={}", read, write); | ||
| 165 | } | ||
| 166 | const size_t virtual_end = virtual_offset + length; | ||
| 167 | |||
| 168 | std::lock_guard lock{placeholder_mutex}; | ||
| 169 | auto [it, end] = placeholders.equal_range({virtual_offset, virtual_end}); | ||
| 170 | while (it != end) { | ||
| 171 | const size_t offset = std::max(it->lower(), virtual_offset); | ||
| 172 | const size_t protect_length = std::min(it->upper(), virtual_end) - offset; | ||
| 173 | DWORD old_flags{}; | ||
| 174 | if (!VirtualProtect(virtual_base + offset, protect_length, new_flags, &old_flags)) { | ||
| 175 | LOG_CRITICAL(HW_Memory, "Failed to change virtual memory protect rules"); | ||
| 176 | } | ||
| 177 | ++it; | ||
| 178 | } | ||
| 179 | } | ||
| 180 | |||
| 181 | const size_t backing_size; ///< Size of the backing memory in bytes | ||
| 182 | const size_t virtual_size; ///< Size of the virtual address placeholder in bytes | ||
| 183 | |||
| 184 | u8* backing_base{}; | ||
| 185 | u8* virtual_base{}; | ||
| 186 | |||
| 187 | private: | ||
| 188 | /// Release all resources in the object | ||
| 189 | void Release() { | ||
| 190 | if (!placeholders.empty()) { | ||
| 191 | for (const auto& placeholder : placeholders) { | ||
| 192 | if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder.lower(), | ||
| 193 | MEM_PRESERVE_PLACEHOLDER)) { | ||
| 194 | LOG_CRITICAL(HW_Memory, "Failed to unmap virtual memory placeholder"); | ||
| 195 | } | ||
| 196 | } | ||
| 197 | Coalesce(0, virtual_size); | ||
| 198 | } | ||
| 199 | if (virtual_base) { | ||
| 200 | if (!VirtualFree(virtual_base, 0, MEM_RELEASE)) { | ||
| 201 | LOG_CRITICAL(HW_Memory, "Failed to free virtual memory"); | ||
| 202 | } | ||
| 203 | } | ||
| 204 | if (backing_base) { | ||
| 205 | if (!pfn_UnmapViewOfFile2(process, backing_base, MEM_PRESERVE_PLACEHOLDER)) { | ||
| 206 | LOG_CRITICAL(HW_Memory, "Failed to unmap backing memory placeholder"); | ||
| 207 | } | ||
| 208 | if (!VirtualFreeEx(process, backing_base, 0, MEM_RELEASE)) { | ||
| 209 | LOG_CRITICAL(HW_Memory, "Failed to free backing memory"); | ||
| 210 | } | ||
| 211 | } | ||
| 212 | if (!CloseHandle(backing_handle)) { | ||
| 213 | LOG_CRITICAL(HW_Memory, "Failed to free backing memory file handle"); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | /// Unmap one placeholder in the given range (partial unmaps are supported) | ||
| 218 | /// Return true when there are no more placeholders to unmap | ||
| 219 | bool UnmapOnePlaceholder(size_t virtual_offset, size_t length) { | ||
| 220 | const auto it = placeholders.find({virtual_offset, virtual_offset + length}); | ||
| 221 | const auto begin = placeholders.begin(); | ||
| 222 | const auto end = placeholders.end(); | ||
| 223 | if (it == end) { | ||
| 224 | return false; | ||
| 225 | } | ||
| 226 | const size_t placeholder_begin = it->lower(); | ||
| 227 | const size_t placeholder_end = it->upper(); | ||
| 228 | const size_t unmap_begin = std::max(virtual_offset, placeholder_begin); | ||
| 229 | const size_t unmap_end = std::min(virtual_offset + length, placeholder_end); | ||
| 230 | ASSERT(unmap_begin >= placeholder_begin && unmap_begin < placeholder_end); | ||
| 231 | ASSERT(unmap_end <= placeholder_end && unmap_end > placeholder_begin); | ||
| 232 | |||
| 233 | const auto host_pointer_it = placeholder_host_pointers.find(placeholder_begin); | ||
| 234 | ASSERT(host_pointer_it != placeholder_host_pointers.end()); | ||
| 235 | const size_t host_offset = host_pointer_it->second; | ||
| 236 | |||
| 237 | const bool split_left = unmap_begin > placeholder_begin; | ||
| 238 | const bool split_right = unmap_end < placeholder_end; | ||
| 239 | |||
| 240 | if (!pfn_UnmapViewOfFile2(process, virtual_base + placeholder_begin, | ||
| 241 | MEM_PRESERVE_PLACEHOLDER)) { | ||
| 242 | LOG_CRITICAL(HW_Memory, "Failed to unmap placeholder"); | ||
| 243 | } | ||
| 244 | // If we have to remap memory regions due to partial unmaps, we are in a data race as | ||
| 245 | // Windows doesn't support remapping memory without unmapping first. Avoid adding any extra | ||
| 246 | // logic within the panic region described below. | ||
| 247 | |||
| 248 | // Panic region, we are in a data race right now | ||
| 249 | if (split_left || split_right) { | ||
| 250 | Split(unmap_begin, unmap_end - unmap_begin); | ||
| 251 | } | ||
| 252 | if (split_left) { | ||
| 253 | MapView(placeholder_begin, host_offset, unmap_begin - placeholder_begin); | ||
| 254 | } | ||
| 255 | if (split_right) { | ||
| 256 | MapView(unmap_end, host_offset + unmap_end - placeholder_begin, | ||
| 257 | placeholder_end - unmap_end); | ||
| 258 | } | ||
| 259 | // End panic region | ||
| 260 | |||
| 261 | size_t coalesce_begin = unmap_begin; | ||
| 262 | if (!split_left) { | ||
| 263 | // Try to coalesce pages to the left | ||
| 264 | coalesce_begin = it == begin ? 0 : std::prev(it)->upper(); | ||
| 265 | if (coalesce_begin != placeholder_begin) { | ||
| 266 | Coalesce(coalesce_begin, unmap_end - coalesce_begin); | ||
| 267 | } | ||
| 268 | } | ||
| 269 | if (!split_right) { | ||
| 270 | // Try to coalesce pages to the right | ||
| 271 | const auto next = std::next(it); | ||
| 272 | const size_t next_begin = next == end ? virtual_size : next->lower(); | ||
| 273 | if (placeholder_end != next_begin) { | ||
| 274 | // We can coalesce to the right | ||
| 275 | Coalesce(coalesce_begin, next_begin - coalesce_begin); | ||
| 276 | } | ||
| 277 | } | ||
| 278 | // Remove and reinsert placeholder trackers | ||
| 279 | UntrackPlaceholder(it); | ||
| 280 | if (split_left) { | ||
| 281 | TrackPlaceholder(placeholder_begin, host_offset, unmap_begin - placeholder_begin); | ||
| 282 | } | ||
| 283 | if (split_right) { | ||
| 284 | TrackPlaceholder(unmap_end, host_offset + unmap_end - placeholder_begin, | ||
| 285 | placeholder_end - unmap_end); | ||
| 286 | } | ||
| 287 | return true; | ||
| 288 | } | ||
| 289 | |||
| 290 | void MapView(size_t virtual_offset, size_t host_offset, size_t length) { | ||
| 291 | if (!pfn_MapViewOfFile3(backing_handle, process, virtual_base + virtual_offset, host_offset, | ||
| 292 | length, MEM_REPLACE_PLACEHOLDER, PAGE_READWRITE, nullptr, 0)) { | ||
| 293 | LOG_CRITICAL(HW_Memory, "Failed to map placeholder"); | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | void Split(size_t virtual_offset, size_t length) { | ||
| 298 | if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length, | ||
| 299 | MEM_RELEASE | MEM_PRESERVE_PLACEHOLDER)) { | ||
| 300 | LOG_CRITICAL(HW_Memory, "Failed to split placeholder"); | ||
| 301 | } | ||
| 302 | } | ||
| 303 | |||
| 304 | void Coalesce(size_t virtual_offset, size_t length) { | ||
| 305 | if (!VirtualFreeEx(process, reinterpret_cast<LPVOID>(virtual_base + virtual_offset), length, | ||
| 306 | MEM_RELEASE | MEM_COALESCE_PLACEHOLDERS)) { | ||
| 307 | LOG_CRITICAL(HW_Memory, "Failed to coalesce placeholders"); | ||
| 308 | } | ||
| 309 | } | ||
| 310 | |||
| 311 | void TrackPlaceholder(size_t virtual_offset, size_t host_offset, size_t length) { | ||
| 312 | placeholders.insert({virtual_offset, virtual_offset + length}); | ||
| 313 | placeholder_host_pointers.emplace(virtual_offset, host_offset); | ||
| 314 | } | ||
| 315 | |||
| 316 | void UntrackPlaceholder(boost::icl::separate_interval_set<size_t>::iterator it) { | ||
| 317 | placeholders.erase(it); | ||
| 318 | placeholder_host_pointers.erase(it->lower()); | ||
| 319 | } | ||
| 320 | |||
| 321 | /// Return true when a given memory region is a "nieche" and the placeholders don't have to be | ||
| 322 | /// splitted. | ||
| 323 | bool IsNiechePlaceholder(size_t virtual_offset, size_t length) const { | ||
| 324 | const auto it = placeholders.upper_bound({virtual_offset, virtual_offset + length}); | ||
| 325 | if (it != placeholders.end() && it->lower() == virtual_offset + length) { | ||
| 326 | const bool is_root = it == placeholders.begin() && virtual_offset == 0; | ||
| 327 | return is_root || std::prev(it)->upper() == virtual_offset; | ||
| 328 | } | ||
| 329 | return false; | ||
| 330 | } | ||
| 331 | |||
| 332 | HANDLE process{}; ///< Current process handle | ||
| 333 | HANDLE backing_handle{}; ///< File based backing memory | ||
| 334 | |||
| 335 | DynamicLibrary kernelbase_dll; | ||
| 336 | PFN_CreateFileMapping2 pfn_CreateFileMapping2{}; | ||
| 337 | PFN_VirtualAlloc2 pfn_VirtualAlloc2{}; | ||
| 338 | PFN_MapViewOfFile3 pfn_MapViewOfFile3{}; | ||
| 339 | PFN_UnmapViewOfFile2 pfn_UnmapViewOfFile2{}; | ||
| 340 | |||
| 341 | std::mutex placeholder_mutex; ///< Mutex for placeholders | ||
| 342 | boost::icl::separate_interval_set<size_t> placeholders; ///< Mapped placeholders | ||
| 343 | std::unordered_map<size_t, size_t> placeholder_host_pointers; ///< Placeholder backing offset | ||
| 344 | }; | ||
| 345 | |||
| 346 | #elif defined(__linux__) // ^^^ Windows ^^^ vvv Linux vvv | ||
| 347 | |||
| 348 | class HostMemory::Impl { | ||
| 349 | public: | ||
| 350 | explicit Impl(size_t backing_size_, size_t virtual_size_) | ||
| 351 | : backing_size{backing_size_}, virtual_size{virtual_size_} { | ||
| 352 | bool good = false; | ||
| 353 | SCOPE_EXIT({ | ||
| 354 | if (!good) { | ||
| 355 | Release(); | ||
| 356 | } | ||
| 357 | }); | ||
| 358 | |||
| 359 | // Backing memory initialization | ||
| 360 | fd = memfd_create("HostMemory", 0); | ||
| 361 | if (fd == -1) { | ||
| 362 | LOG_CRITICAL(HW_Memory, "memfd_create failed: {}", strerror(errno)); | ||
| 363 | throw std::bad_alloc{}; | ||
| 364 | } | ||
| 365 | |||
| 366 | // Defined to extend the file with zeros | ||
| 367 | int ret = ftruncate(fd, backing_size); | ||
| 368 | if (ret != 0) { | ||
| 369 | LOG_CRITICAL(HW_Memory, "ftruncate failed with {}, are you out-of-memory?", | ||
| 370 | strerror(errno)); | ||
| 371 | throw std::bad_alloc{}; | ||
| 372 | } | ||
| 373 | |||
| 374 | backing_base = static_cast<u8*>( | ||
| 375 | mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0)); | ||
| 376 | if (backing_base == MAP_FAILED) { | ||
| 377 | LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); | ||
| 378 | throw std::bad_alloc{}; | ||
| 379 | } | ||
| 380 | |||
| 381 | // Virtual memory initialization | ||
| 382 | virtual_base = static_cast<u8*>( | ||
| 383 | mmap(nullptr, virtual_size, PROT_NONE, MAP_PRIVATE | MAP_ANONYMOUS, -1, 0)); | ||
| 384 | if (virtual_base == MAP_FAILED) { | ||
| 385 | LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno)); | ||
| 386 | throw std::bad_alloc{}; | ||
| 387 | } | ||
| 388 | |||
| 389 | good = true; | ||
| 390 | } | ||
| 391 | |||
| 392 | ~Impl() { | ||
| 393 | Release(); | ||
| 394 | } | ||
| 395 | |||
| 396 | void Map(size_t virtual_offset, size_t host_offset, size_t length) { | ||
| 397 | |||
| 398 | void* ret = mmap(virtual_base + virtual_offset, length, PROT_READ | PROT_WRITE, | ||
| 399 | MAP_SHARED | MAP_FIXED, fd, host_offset); | ||
| 400 | ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); | ||
| 401 | } | ||
| 402 | |||
| 403 | void Unmap(size_t virtual_offset, size_t length) { | ||
| 404 | // The method name is wrong. We're still talking about the virtual range. | ||
| 405 | // We don't want to unmap, we want to reserve this memory. | ||
| 406 | |||
| 407 | void* ret = mmap(virtual_base + virtual_offset, length, PROT_NONE, | ||
| 408 | MAP_PRIVATE | MAP_ANONYMOUS | MAP_FIXED, -1, 0); | ||
| 409 | ASSERT_MSG(ret != MAP_FAILED, "mmap failed: {}", strerror(errno)); | ||
| 410 | } | ||
| 411 | |||
| 412 | void Protect(size_t virtual_offset, size_t length, bool read, bool write) { | ||
| 413 | int flags = 0; | ||
| 414 | if (read) { | ||
| 415 | flags |= PROT_READ; | ||
| 416 | } | ||
| 417 | if (write) { | ||
| 418 | flags |= PROT_WRITE; | ||
| 419 | } | ||
| 420 | int ret = mprotect(virtual_base + virtual_offset, length, flags); | ||
| 421 | ASSERT_MSG(ret == 0, "mprotect failed: {}", strerror(errno)); | ||
| 422 | } | ||
| 423 | |||
| 424 | const size_t backing_size; ///< Size of the backing memory in bytes | ||
| 425 | const size_t virtual_size; ///< Size of the virtual address placeholder in bytes | ||
| 426 | |||
| 427 | u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)}; | ||
| 428 | u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)}; | ||
| 429 | |||
| 430 | private: | ||
| 431 | /// Release all resources in the object | ||
| 432 | void Release() { | ||
| 433 | if (virtual_base != MAP_FAILED) { | ||
| 434 | int ret = munmap(virtual_base, virtual_size); | ||
| 435 | ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); | ||
| 436 | } | ||
| 437 | |||
| 438 | if (backing_base != MAP_FAILED) { | ||
| 439 | int ret = munmap(backing_base, backing_size); | ||
| 440 | ASSERT_MSG(ret == 0, "munmap failed: {}", strerror(errno)); | ||
| 441 | } | ||
| 442 | |||
| 443 | if (fd != -1) { | ||
| 444 | int ret = close(fd); | ||
| 445 | ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno)); | ||
| 446 | } | ||
| 447 | } | ||
| 448 | |||
| 449 | int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create | ||
| 450 | }; | ||
| 451 | |||
| 452 | #else // ^^^ Linux ^^^ vvv Generic vvv | ||
| 453 | |||
| 454 | class HostMemory::Impl { | ||
| 455 | public: | ||
| 456 | explicit Impl(size_t /*backing_size */, size_t /* virtual_size */) { | ||
| 457 | // This is just a place holder. | ||
| 458 | // Please implement fastmem in a propper way on your platform. | ||
| 459 | throw std::bad_alloc{}; | ||
| 460 | } | ||
| 461 | |||
| 462 | void Map(size_t virtual_offset, size_t host_offset, size_t length) {} | ||
| 463 | |||
| 464 | void Unmap(size_t virtual_offset, size_t length) {} | ||
| 465 | |||
| 466 | void Protect(size_t virtual_offset, size_t length, bool read, bool write) {} | ||
| 467 | |||
| 468 | u8* backing_base{nullptr}; | ||
| 469 | u8* virtual_base{nullptr}; | ||
| 470 | }; | ||
| 471 | |||
| 472 | #endif // ^^^ Generic ^^^ | ||
| 473 | |||
| 474 | HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_) | ||
| 475 | : backing_size(backing_size_), virtual_size(virtual_size_) { | ||
| 476 | try { | ||
| 477 | // Try to allocate a fastmem arena. | ||
| 478 | // The implementation will fail with std::bad_alloc on errors. | ||
| 479 | impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), | ||
| 480 | AlignUp(virtual_size, PageAlignment) + | ||
| 481 | 3 * HugePageSize); | ||
| 482 | backing_base = impl->backing_base; | ||
| 483 | virtual_base = impl->virtual_base; | ||
| 484 | |||
| 485 | if (virtual_base) { | ||
| 486 | virtual_base += 2 * HugePageSize - 1; | ||
| 487 | virtual_base -= reinterpret_cast<size_t>(virtual_base) & (HugePageSize - 1); | ||
| 488 | virtual_base_offset = virtual_base - impl->virtual_base; | ||
| 489 | } | ||
| 490 | |||
| 491 | } catch (const std::bad_alloc&) { | ||
| 492 | LOG_CRITICAL(HW_Memory, | ||
| 493 | "Fastmem unavailable, falling back to VirtualBuffer for memory allocation"); | ||
| 494 | fallback_buffer = std::make_unique<Common::VirtualBuffer<u8>>(backing_size); | ||
| 495 | backing_base = fallback_buffer->data(); | ||
| 496 | virtual_base = nullptr; | ||
| 497 | } | ||
| 498 | } | ||
| 499 | |||
| 500 | HostMemory::~HostMemory() = default; | ||
| 501 | |||
| 502 | HostMemory::HostMemory(HostMemory&&) noexcept = default; | ||
| 503 | |||
| 504 | HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default; | ||
| 505 | |||
| 506 | void HostMemory::Map(size_t virtual_offset, size_t host_offset, size_t length) { | ||
| 507 | ASSERT(virtual_offset % PageAlignment == 0); | ||
| 508 | ASSERT(host_offset % PageAlignment == 0); | ||
| 509 | ASSERT(length % PageAlignment == 0); | ||
| 510 | ASSERT(virtual_offset + length <= virtual_size); | ||
| 511 | ASSERT(host_offset + length <= backing_size); | ||
| 512 | if (length == 0 || !virtual_base || !impl) { | ||
| 513 | return; | ||
| 514 | } | ||
| 515 | impl->Map(virtual_offset + virtual_base_offset, host_offset, length); | ||
| 516 | } | ||
| 517 | |||
| 518 | void HostMemory::Unmap(size_t virtual_offset, size_t length) { | ||
| 519 | ASSERT(virtual_offset % PageAlignment == 0); | ||
| 520 | ASSERT(length % PageAlignment == 0); | ||
| 521 | ASSERT(virtual_offset + length <= virtual_size); | ||
| 522 | if (length == 0 || !virtual_base || !impl) { | ||
| 523 | return; | ||
| 524 | } | ||
| 525 | impl->Unmap(virtual_offset + virtual_base_offset, length); | ||
| 526 | } | ||
| 527 | |||
| 528 | void HostMemory::Protect(size_t virtual_offset, size_t length, bool read, bool write) { | ||
| 529 | ASSERT(virtual_offset % PageAlignment == 0); | ||
| 530 | ASSERT(length % PageAlignment == 0); | ||
| 531 | ASSERT(virtual_offset + length <= virtual_size); | ||
| 532 | if (length == 0 || !virtual_base || !impl) { | ||
| 533 | return; | ||
| 534 | } | ||
| 535 | impl->Protect(virtual_offset + virtual_base_offset, length, read, write); | ||
| 536 | } | ||
| 537 | |||
| 538 | } // namespace Common | ||
diff --git a/src/common/host_memory.h b/src/common/host_memory.h new file mode 100644 index 000000000..9b8326d0f --- /dev/null +++ b/src/common/host_memory.h | |||
| @@ -0,0 +1,70 @@ | |||
| 1 | // Copyright 2019 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #pragma once | ||
| 6 | |||
| 7 | #include <memory> | ||
| 8 | #include "common/common_types.h" | ||
| 9 | #include "common/virtual_buffer.h" | ||
| 10 | |||
| 11 | namespace Common { | ||
| 12 | |||
| 13 | /** | ||
| 14 | * A low level linear memory buffer, which supports multiple mappings | ||
| 15 | * Its purpose is to rebuild a given sparse memory layout, including mirrors. | ||
| 16 | */ | ||
| 17 | class HostMemory { | ||
| 18 | public: | ||
| 19 | explicit HostMemory(size_t backing_size_, size_t virtual_size_); | ||
| 20 | ~HostMemory(); | ||
| 21 | |||
| 22 | /** | ||
| 23 | * Copy constructors. They shall return a copy of the buffer without the mappings. | ||
| 24 | * TODO: Implement them with COW if needed. | ||
| 25 | */ | ||
| 26 | HostMemory(const HostMemory& other) = delete; | ||
| 27 | HostMemory& operator=(const HostMemory& other) = delete; | ||
| 28 | |||
| 29 | /** | ||
| 30 | * Move constructors. They will move the buffer and the mappings to the new object. | ||
| 31 | */ | ||
| 32 | HostMemory(HostMemory&& other) noexcept; | ||
| 33 | HostMemory& operator=(HostMemory&& other) noexcept; | ||
| 34 | |||
| 35 | void Map(size_t virtual_offset, size_t host_offset, size_t length); | ||
| 36 | |||
| 37 | void Unmap(size_t virtual_offset, size_t length); | ||
| 38 | |||
| 39 | void Protect(size_t virtual_offset, size_t length, bool read, bool write); | ||
| 40 | |||
| 41 | [[nodiscard]] u8* BackingBasePointer() noexcept { | ||
| 42 | return backing_base; | ||
| 43 | } | ||
| 44 | [[nodiscard]] const u8* BackingBasePointer() const noexcept { | ||
| 45 | return backing_base; | ||
| 46 | } | ||
| 47 | |||
| 48 | [[nodiscard]] u8* VirtualBasePointer() noexcept { | ||
| 49 | return virtual_base; | ||
| 50 | } | ||
| 51 | [[nodiscard]] const u8* VirtualBasePointer() const noexcept { | ||
| 52 | return virtual_base; | ||
| 53 | } | ||
| 54 | |||
| 55 | private: | ||
| 56 | size_t backing_size{}; | ||
| 57 | size_t virtual_size{}; | ||
| 58 | |||
| 59 | // Low level handler for the platform dependent memory routines | ||
| 60 | class Impl; | ||
| 61 | std::unique_ptr<Impl> impl; | ||
| 62 | u8* backing_base{}; | ||
| 63 | u8* virtual_base{}; | ||
| 64 | size_t virtual_base_offset{}; | ||
| 65 | |||
| 66 | // Fallback if fastmem is not supported on this platform | ||
| 67 | std::unique_ptr<Common::VirtualBuffer<u8>> fallback_buffer; | ||
| 68 | }; | ||
| 69 | |||
| 70 | } // namespace Common | ||
diff --git a/src/common/logging/backend.cpp b/src/common/logging/backend.cpp index 6aa8ac960..d5cff400f 100644 --- a/src/common/logging/backend.cpp +++ b/src/common/logging/backend.cpp | |||
| @@ -17,6 +17,7 @@ | |||
| 17 | #endif | 17 | #endif |
| 18 | 18 | ||
| 19 | #include "common/assert.h" | 19 | #include "common/assert.h" |
| 20 | #include "common/fs/file.h" | ||
| 20 | #include "common/fs/fs.h" | 21 | #include "common/fs/fs.h" |
| 21 | #include "common/logging/backend.h" | 22 | #include "common/logging/backend.h" |
| 22 | #include "common/logging/log.h" | 23 | #include "common/logging/log.h" |
| @@ -140,10 +141,14 @@ private: | |||
| 140 | std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; | 141 | std::chrono::steady_clock::time_point time_origin{std::chrono::steady_clock::now()}; |
| 141 | }; | 142 | }; |
| 142 | 143 | ||
| 144 | ConsoleBackend::~ConsoleBackend() = default; | ||
| 145 | |||
| 143 | void ConsoleBackend::Write(const Entry& entry) { | 146 | void ConsoleBackend::Write(const Entry& entry) { |
| 144 | PrintMessage(entry); | 147 | PrintMessage(entry); |
| 145 | } | 148 | } |
| 146 | 149 | ||
| 150 | ColorConsoleBackend::~ColorConsoleBackend() = default; | ||
| 151 | |||
| 147 | void ColorConsoleBackend::Write(const Entry& entry) { | 152 | void ColorConsoleBackend::Write(const Entry& entry) { |
| 148 | PrintColoredMessage(entry); | 153 | PrintColoredMessage(entry); |
| 149 | } | 154 | } |
| @@ -157,16 +162,19 @@ FileBackend::FileBackend(const std::filesystem::path& filename) { | |||
| 157 | void(FS::RemoveFile(old_filename)); | 162 | void(FS::RemoveFile(old_filename)); |
| 158 | void(FS::RenameFile(filename, old_filename)); | 163 | void(FS::RenameFile(filename, old_filename)); |
| 159 | 164 | ||
| 160 | file = FS::IOFile(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); | 165 | file = |
| 166 | std::make_unique<FS::IOFile>(filename, FS::FileAccessMode::Write, FS::FileType::TextFile); | ||
| 161 | } | 167 | } |
| 162 | 168 | ||
| 169 | FileBackend::~FileBackend() = default; | ||
| 170 | |||
| 163 | void FileBackend::Write(const Entry& entry) { | 171 | void FileBackend::Write(const Entry& entry) { |
| 164 | // prevent logs from going over the maximum size (in case its spamming and the user doesn't | 172 | // prevent logs from going over the maximum size (in case its spamming and the user doesn't |
| 165 | // know) | 173 | // know) |
| 166 | constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024; | 174 | constexpr std::size_t MAX_BYTES_WRITTEN = 100 * 1024 * 1024; |
| 167 | constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024; | 175 | constexpr std::size_t MAX_BYTES_WRITTEN_EXTENDED = 1024 * 1024 * 1024; |
| 168 | 176 | ||
| 169 | if (!file.IsOpen()) { | 177 | if (!file->IsOpen()) { |
| 170 | return; | 178 | return; |
| 171 | } | 179 | } |
| 172 | 180 | ||
| @@ -176,147 +184,20 @@ void FileBackend::Write(const Entry& entry) { | |||
| 176 | return; | 184 | return; |
| 177 | } | 185 | } |
| 178 | 186 | ||
| 179 | bytes_written += file.WriteString(FormatLogMessage(entry).append(1, '\n')); | 187 | bytes_written += file->WriteString(FormatLogMessage(entry).append(1, '\n')); |
| 180 | if (entry.log_level >= Level::Error) { | 188 | if (entry.log_level >= Level::Error) { |
| 181 | void(file.Flush()); | 189 | void(file->Flush()); |
| 182 | } | 190 | } |
| 183 | } | 191 | } |
| 184 | 192 | ||
| 193 | DebuggerBackend::~DebuggerBackend() = default; | ||
| 194 | |||
| 185 | void DebuggerBackend::Write(const Entry& entry) { | 195 | void DebuggerBackend::Write(const Entry& entry) { |
| 186 | #ifdef _WIN32 | 196 | #ifdef _WIN32 |
| 187 | ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); | 197 | ::OutputDebugStringW(UTF8ToUTF16W(FormatLogMessage(entry).append(1, '\n')).c_str()); |
| 188 | #endif | 198 | #endif |
| 189 | } | 199 | } |
| 190 | 200 | ||
| 191 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. | ||
| 192 | #define ALL_LOG_CLASSES() \ | ||
| 193 | CLS(Log) \ | ||
| 194 | CLS(Common) \ | ||
| 195 | SUB(Common, Filesystem) \ | ||
| 196 | SUB(Common, Memory) \ | ||
| 197 | CLS(Core) \ | ||
| 198 | SUB(Core, ARM) \ | ||
| 199 | SUB(Core, Timing) \ | ||
| 200 | CLS(Config) \ | ||
| 201 | CLS(Debug) \ | ||
| 202 | SUB(Debug, Emulated) \ | ||
| 203 | SUB(Debug, GPU) \ | ||
| 204 | SUB(Debug, Breakpoint) \ | ||
| 205 | SUB(Debug, GDBStub) \ | ||
| 206 | CLS(Kernel) \ | ||
| 207 | SUB(Kernel, SVC) \ | ||
| 208 | CLS(Service) \ | ||
| 209 | SUB(Service, ACC) \ | ||
| 210 | SUB(Service, Audio) \ | ||
| 211 | SUB(Service, AM) \ | ||
| 212 | SUB(Service, AOC) \ | ||
| 213 | SUB(Service, APM) \ | ||
| 214 | SUB(Service, ARP) \ | ||
| 215 | SUB(Service, BCAT) \ | ||
| 216 | SUB(Service, BPC) \ | ||
| 217 | SUB(Service, BGTC) \ | ||
| 218 | SUB(Service, BTDRV) \ | ||
| 219 | SUB(Service, BTM) \ | ||
| 220 | SUB(Service, Capture) \ | ||
| 221 | SUB(Service, ERPT) \ | ||
| 222 | SUB(Service, ETicket) \ | ||
| 223 | SUB(Service, EUPLD) \ | ||
| 224 | SUB(Service, Fatal) \ | ||
| 225 | SUB(Service, FGM) \ | ||
| 226 | SUB(Service, Friend) \ | ||
| 227 | SUB(Service, FS) \ | ||
| 228 | SUB(Service, GRC) \ | ||
| 229 | SUB(Service, HID) \ | ||
| 230 | SUB(Service, IRS) \ | ||
| 231 | SUB(Service, LBL) \ | ||
| 232 | SUB(Service, LDN) \ | ||
| 233 | SUB(Service, LDR) \ | ||
| 234 | SUB(Service, LM) \ | ||
| 235 | SUB(Service, Migration) \ | ||
| 236 | SUB(Service, Mii) \ | ||
| 237 | SUB(Service, MM) \ | ||
| 238 | SUB(Service, NCM) \ | ||
| 239 | SUB(Service, NFC) \ | ||
| 240 | SUB(Service, NFP) \ | ||
| 241 | SUB(Service, NIFM) \ | ||
| 242 | SUB(Service, NIM) \ | ||
| 243 | SUB(Service, NPNS) \ | ||
| 244 | SUB(Service, NS) \ | ||
| 245 | SUB(Service, NVDRV) \ | ||
| 246 | SUB(Service, OLSC) \ | ||
| 247 | SUB(Service, PCIE) \ | ||
| 248 | SUB(Service, PCTL) \ | ||
| 249 | SUB(Service, PCV) \ | ||
| 250 | SUB(Service, PM) \ | ||
| 251 | SUB(Service, PREPO) \ | ||
| 252 | SUB(Service, PSC) \ | ||
| 253 | SUB(Service, PSM) \ | ||
| 254 | SUB(Service, SET) \ | ||
| 255 | SUB(Service, SM) \ | ||
| 256 | SUB(Service, SPL) \ | ||
| 257 | SUB(Service, SSL) \ | ||
| 258 | SUB(Service, TCAP) \ | ||
| 259 | SUB(Service, Time) \ | ||
| 260 | SUB(Service, USB) \ | ||
| 261 | SUB(Service, VI) \ | ||
| 262 | SUB(Service, WLAN) \ | ||
| 263 | CLS(HW) \ | ||
| 264 | SUB(HW, Memory) \ | ||
| 265 | SUB(HW, LCD) \ | ||
| 266 | SUB(HW, GPU) \ | ||
| 267 | SUB(HW, AES) \ | ||
| 268 | CLS(IPC) \ | ||
| 269 | CLS(Frontend) \ | ||
| 270 | CLS(Render) \ | ||
| 271 | SUB(Render, Software) \ | ||
| 272 | SUB(Render, OpenGL) \ | ||
| 273 | SUB(Render, Vulkan) \ | ||
| 274 | CLS(Audio) \ | ||
| 275 | SUB(Audio, DSP) \ | ||
| 276 | SUB(Audio, Sink) \ | ||
| 277 | CLS(Input) \ | ||
| 278 | CLS(Network) \ | ||
| 279 | CLS(Loader) \ | ||
| 280 | CLS(CheatEngine) \ | ||
| 281 | CLS(Crypto) \ | ||
| 282 | CLS(WebService) | ||
| 283 | |||
| 284 | // GetClassName is a macro defined by Windows.h, grrr... | ||
| 285 | const char* GetLogClassName(Class log_class) { | ||
| 286 | switch (log_class) { | ||
| 287 | #define CLS(x) \ | ||
| 288 | case Class::x: \ | ||
| 289 | return #x; | ||
| 290 | #define SUB(x, y) \ | ||
| 291 | case Class::x##_##y: \ | ||
| 292 | return #x "." #y; | ||
| 293 | ALL_LOG_CLASSES() | ||
| 294 | #undef CLS | ||
| 295 | #undef SUB | ||
| 296 | case Class::Count: | ||
| 297 | break; | ||
| 298 | } | ||
| 299 | return "Invalid"; | ||
| 300 | } | ||
| 301 | |||
| 302 | const char* GetLevelName(Level log_level) { | ||
| 303 | #define LVL(x) \ | ||
| 304 | case Level::x: \ | ||
| 305 | return #x | ||
| 306 | switch (log_level) { | ||
| 307 | LVL(Trace); | ||
| 308 | LVL(Debug); | ||
| 309 | LVL(Info); | ||
| 310 | LVL(Warning); | ||
| 311 | LVL(Error); | ||
| 312 | LVL(Critical); | ||
| 313 | case Level::Count: | ||
| 314 | break; | ||
| 315 | } | ||
| 316 | #undef LVL | ||
| 317 | return "Invalid"; | ||
| 318 | } | ||
| 319 | |||
| 320 | void SetGlobalFilter(const Filter& filter) { | 201 | void SetGlobalFilter(const Filter& filter) { |
| 321 | Impl::Instance().SetGlobalFilter(filter); | 202 | Impl::Instance().SetGlobalFilter(filter); |
| 322 | } | 203 | } |
diff --git a/src/common/logging/backend.h b/src/common/logging/backend.h index eb629a33f..4b9a910c1 100644 --- a/src/common/logging/backend.h +++ b/src/common/logging/backend.h | |||
| @@ -1,43 +1,32 @@ | |||
| 1 | // Copyright 2014 Citra Emulator Project | 1 | // Copyright 2014 Citra Emulator Project |
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | |||
| 4 | #pragma once | 5 | #pragma once |
| 5 | 6 | ||
| 6 | #include <chrono> | ||
| 7 | #include <filesystem> | 7 | #include <filesystem> |
| 8 | #include <memory> | 8 | #include <memory> |
| 9 | #include <string> | 9 | #include <string> |
| 10 | #include <string_view> | 10 | #include <string_view> |
| 11 | #include "common/fs/file.h" | ||
| 12 | #include "common/logging/filter.h" | 11 | #include "common/logging/filter.h" |
| 13 | #include "common/logging/log.h" | 12 | #include "common/logging/log.h" |
| 14 | 13 | ||
| 14 | namespace Common::FS { | ||
| 15 | class IOFile; | ||
| 16 | } | ||
| 17 | |||
| 15 | namespace Common::Log { | 18 | namespace Common::Log { |
| 16 | 19 | ||
| 17 | class Filter; | 20 | class Filter; |
| 18 | 21 | ||
| 19 | /** | 22 | /** |
| 20 | * A log entry. Log entries are store in a structured format to permit more varied output | ||
| 21 | * formatting on different frontends, as well as facilitating filtering and aggregation. | ||
| 22 | */ | ||
| 23 | struct Entry { | ||
| 24 | std::chrono::microseconds timestamp; | ||
| 25 | Class log_class{}; | ||
| 26 | Level log_level{}; | ||
| 27 | const char* filename = nullptr; | ||
| 28 | unsigned int line_num = 0; | ||
| 29 | std::string function; | ||
| 30 | std::string message; | ||
| 31 | bool final_entry = false; | ||
| 32 | }; | ||
| 33 | |||
| 34 | /** | ||
| 35 | * Interface for logging backends. As loggers can be created and removed at runtime, this can be | 23 | * Interface for logging backends. As loggers can be created and removed at runtime, this can be |
| 36 | * used by a frontend for adding a custom logging backend as needed | 24 | * used by a frontend for adding a custom logging backend as needed |
| 37 | */ | 25 | */ |
| 38 | class Backend { | 26 | class Backend { |
| 39 | public: | 27 | public: |
| 40 | virtual ~Backend() = default; | 28 | virtual ~Backend() = default; |
| 29 | |||
| 41 | virtual void SetFilter(const Filter& new_filter) { | 30 | virtual void SetFilter(const Filter& new_filter) { |
| 42 | filter = new_filter; | 31 | filter = new_filter; |
| 43 | } | 32 | } |
| @@ -53,6 +42,8 @@ private: | |||
| 53 | */ | 42 | */ |
| 54 | class ConsoleBackend : public Backend { | 43 | class ConsoleBackend : public Backend { |
| 55 | public: | 44 | public: |
| 45 | ~ConsoleBackend() override; | ||
| 46 | |||
| 56 | static const char* Name() { | 47 | static const char* Name() { |
| 57 | return "console"; | 48 | return "console"; |
| 58 | } | 49 | } |
| @@ -67,6 +58,8 @@ public: | |||
| 67 | */ | 58 | */ |
| 68 | class ColorConsoleBackend : public Backend { | 59 | class ColorConsoleBackend : public Backend { |
| 69 | public: | 60 | public: |
| 61 | ~ColorConsoleBackend() override; | ||
| 62 | |||
| 70 | static const char* Name() { | 63 | static const char* Name() { |
| 71 | return "color_console"; | 64 | return "color_console"; |
| 72 | } | 65 | } |
| @@ -83,6 +76,7 @@ public: | |||
| 83 | class FileBackend : public Backend { | 76 | class FileBackend : public Backend { |
| 84 | public: | 77 | public: |
| 85 | explicit FileBackend(const std::filesystem::path& filename); | 78 | explicit FileBackend(const std::filesystem::path& filename); |
| 79 | ~FileBackend() override; | ||
| 86 | 80 | ||
| 87 | static const char* Name() { | 81 | static const char* Name() { |
| 88 | return "file"; | 82 | return "file"; |
| @@ -95,7 +89,7 @@ public: | |||
| 95 | void Write(const Entry& entry) override; | 89 | void Write(const Entry& entry) override; |
| 96 | 90 | ||
| 97 | private: | 91 | private: |
| 98 | FS::IOFile file; | 92 | std::unique_ptr<FS::IOFile> file; |
| 99 | std::size_t bytes_written = 0; | 93 | std::size_t bytes_written = 0; |
| 100 | }; | 94 | }; |
| 101 | 95 | ||
| @@ -104,6 +98,8 @@ private: | |||
| 104 | */ | 98 | */ |
| 105 | class DebuggerBackend : public Backend { | 99 | class DebuggerBackend : public Backend { |
| 106 | public: | 100 | public: |
| 101 | ~DebuggerBackend() override; | ||
| 102 | |||
| 107 | static const char* Name() { | 103 | static const char* Name() { |
| 108 | return "debugger"; | 104 | return "debugger"; |
| 109 | } | 105 | } |
| @@ -120,17 +116,6 @@ void RemoveBackend(std::string_view backend_name); | |||
| 120 | Backend* GetBackend(std::string_view backend_name); | 116 | Backend* GetBackend(std::string_view backend_name); |
| 121 | 117 | ||
| 122 | /** | 118 | /** |
| 123 | * Returns the name of the passed log class as a C-string. Subclasses are separated by periods | ||
| 124 | * instead of underscores as in the enumeration. | ||
| 125 | */ | ||
| 126 | const char* GetLogClassName(Class log_class); | ||
| 127 | |||
| 128 | /** | ||
| 129 | * Returns the name of the passed log level as a C-string. | ||
| 130 | */ | ||
| 131 | const char* GetLevelName(Level log_level); | ||
| 132 | |||
| 133 | /** | ||
| 134 | * The global filter will prevent any messages from even being processed if they are filtered. Each | 119 | * The global filter will prevent any messages from even being processed if they are filtered. Each |
| 135 | * backend can have a filter, but if the level is lower than the global filter, the backend will | 120 | * backend can have a filter, but if the level is lower than the global filter, the backend will |
| 136 | * never get the message | 121 | * never get the message |
diff --git a/src/common/logging/filter.cpp b/src/common/logging/filter.cpp index 20a2dd106..4f2cc29e1 100644 --- a/src/common/logging/filter.cpp +++ b/src/common/logging/filter.cpp | |||
| @@ -3,7 +3,6 @@ | |||
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <algorithm> | 5 | #include <algorithm> |
| 6 | #include "common/logging/backend.h" | ||
| 7 | #include "common/logging/filter.h" | 6 | #include "common/logging/filter.h" |
| 8 | #include "common/string_util.h" | 7 | #include "common/string_util.h" |
| 9 | 8 | ||
| @@ -22,7 +21,7 @@ Level GetLevelByName(const It begin, const It end) { | |||
| 22 | 21 | ||
| 23 | template <typename It> | 22 | template <typename It> |
| 24 | Class GetClassByName(const It begin, const It end) { | 23 | Class GetClassByName(const It begin, const It end) { |
| 25 | for (ClassType i = 0; i < static_cast<ClassType>(Class::Count); ++i) { | 24 | for (u8 i = 0; i < static_cast<u8>(Class::Count); ++i) { |
| 26 | const char* level_name = GetLogClassName(static_cast<Class>(i)); | 25 | const char* level_name = GetLogClassName(static_cast<Class>(i)); |
| 27 | if (Common::ComparePartialString(begin, end, level_name)) { | 26 | if (Common::ComparePartialString(begin, end, level_name)) { |
| 28 | return static_cast<Class>(i); | 27 | return static_cast<Class>(i); |
| @@ -62,6 +61,135 @@ bool ParseFilterRule(Filter& instance, Iterator begin, Iterator end) { | |||
| 62 | } | 61 | } |
| 63 | } // Anonymous namespace | 62 | } // Anonymous namespace |
| 64 | 63 | ||
| 64 | /// Macro listing all log classes. Code should define CLS and SUB as desired before invoking this. | ||
| 65 | #define ALL_LOG_CLASSES() \ | ||
| 66 | CLS(Log) \ | ||
| 67 | CLS(Common) \ | ||
| 68 | SUB(Common, Filesystem) \ | ||
| 69 | SUB(Common, Memory) \ | ||
| 70 | CLS(Core) \ | ||
| 71 | SUB(Core, ARM) \ | ||
| 72 | SUB(Core, Timing) \ | ||
| 73 | CLS(Config) \ | ||
| 74 | CLS(Debug) \ | ||
| 75 | SUB(Debug, Emulated) \ | ||
| 76 | SUB(Debug, GPU) \ | ||
| 77 | SUB(Debug, Breakpoint) \ | ||
| 78 | SUB(Debug, GDBStub) \ | ||
| 79 | CLS(Kernel) \ | ||
| 80 | SUB(Kernel, SVC) \ | ||
| 81 | CLS(Service) \ | ||
| 82 | SUB(Service, ACC) \ | ||
| 83 | SUB(Service, Audio) \ | ||
| 84 | SUB(Service, AM) \ | ||
| 85 | SUB(Service, AOC) \ | ||
| 86 | SUB(Service, APM) \ | ||
| 87 | SUB(Service, ARP) \ | ||
| 88 | SUB(Service, BCAT) \ | ||
| 89 | SUB(Service, BPC) \ | ||
| 90 | SUB(Service, BGTC) \ | ||
| 91 | SUB(Service, BTDRV) \ | ||
| 92 | SUB(Service, BTM) \ | ||
| 93 | SUB(Service, Capture) \ | ||
| 94 | SUB(Service, ERPT) \ | ||
| 95 | SUB(Service, ETicket) \ | ||
| 96 | SUB(Service, EUPLD) \ | ||
| 97 | SUB(Service, Fatal) \ | ||
| 98 | SUB(Service, FGM) \ | ||
| 99 | SUB(Service, Friend) \ | ||
| 100 | SUB(Service, FS) \ | ||
| 101 | SUB(Service, GRC) \ | ||
| 102 | SUB(Service, HID) \ | ||
| 103 | SUB(Service, IRS) \ | ||
| 104 | SUB(Service, LBL) \ | ||
| 105 | SUB(Service, LDN) \ | ||
| 106 | SUB(Service, LDR) \ | ||
| 107 | SUB(Service, LM) \ | ||
| 108 | SUB(Service, Migration) \ | ||
| 109 | SUB(Service, Mii) \ | ||
| 110 | SUB(Service, MM) \ | ||
| 111 | SUB(Service, NCM) \ | ||
| 112 | SUB(Service, NFC) \ | ||
| 113 | SUB(Service, NFP) \ | ||
| 114 | SUB(Service, NIFM) \ | ||
| 115 | SUB(Service, NIM) \ | ||
| 116 | SUB(Service, NPNS) \ | ||
| 117 | SUB(Service, NS) \ | ||
| 118 | SUB(Service, NVDRV) \ | ||
| 119 | SUB(Service, OLSC) \ | ||
| 120 | SUB(Service, PCIE) \ | ||
| 121 | SUB(Service, PCTL) \ | ||
| 122 | SUB(Service, PCV) \ | ||
| 123 | SUB(Service, PM) \ | ||
| 124 | SUB(Service, PREPO) \ | ||
| 125 | SUB(Service, PSC) \ | ||
| 126 | SUB(Service, PSM) \ | ||
| 127 | SUB(Service, SET) \ | ||
| 128 | SUB(Service, SM) \ | ||
| 129 | SUB(Service, SPL) \ | ||
| 130 | SUB(Service, SSL) \ | ||
| 131 | SUB(Service, TCAP) \ | ||
| 132 | SUB(Service, Time) \ | ||
| 133 | SUB(Service, USB) \ | ||
| 134 | SUB(Service, VI) \ | ||
| 135 | SUB(Service, WLAN) \ | ||
| 136 | CLS(HW) \ | ||
| 137 | SUB(HW, Memory) \ | ||
| 138 | SUB(HW, LCD) \ | ||
| 139 | SUB(HW, GPU) \ | ||
| 140 | SUB(HW, AES) \ | ||
| 141 | CLS(IPC) \ | ||
| 142 | CLS(Frontend) \ | ||
| 143 | CLS(Render) \ | ||
| 144 | SUB(Render, Software) \ | ||
| 145 | SUB(Render, OpenGL) \ | ||
| 146 | SUB(Render, Vulkan) \ | ||
| 147 | CLS(Audio) \ | ||
| 148 | SUB(Audio, DSP) \ | ||
| 149 | SUB(Audio, Sink) \ | ||
| 150 | CLS(Input) \ | ||
| 151 | CLS(Network) \ | ||
| 152 | CLS(Loader) \ | ||
| 153 | CLS(CheatEngine) \ | ||
| 154 | CLS(Crypto) \ | ||
| 155 | CLS(WebService) | ||
| 156 | |||
| 157 | // GetClassName is a macro defined by Windows.h, grrr... | ||
| 158 | const char* GetLogClassName(Class log_class) { | ||
| 159 | switch (log_class) { | ||
| 160 | #define CLS(x) \ | ||
| 161 | case Class::x: \ | ||
| 162 | return #x; | ||
| 163 | #define SUB(x, y) \ | ||
| 164 | case Class::x##_##y: \ | ||
| 165 | return #x "." #y; | ||
| 166 | ALL_LOG_CLASSES() | ||
| 167 | #undef CLS | ||
| 168 | #undef SUB | ||
| 169 | case Class::Count: | ||
| 170 | break; | ||
| 171 | } | ||
| 172 | return "Invalid"; | ||
| 173 | } | ||
| 174 | |||
| 175 | const char* GetLevelName(Level log_level) { | ||
| 176 | #define LVL(x) \ | ||
| 177 | case Level::x: \ | ||
| 178 | return #x | ||
| 179 | switch (log_level) { | ||
| 180 | LVL(Trace); | ||
| 181 | LVL(Debug); | ||
| 182 | LVL(Info); | ||
| 183 | LVL(Warning); | ||
| 184 | LVL(Error); | ||
| 185 | LVL(Critical); | ||
| 186 | case Level::Count: | ||
| 187 | break; | ||
| 188 | } | ||
| 189 | #undef LVL | ||
| 190 | return "Invalid"; | ||
| 191 | } | ||
| 192 | |||
| 65 | Filter::Filter(Level default_level) { | 193 | Filter::Filter(Level default_level) { |
| 66 | ResetAll(default_level); | 194 | ResetAll(default_level); |
| 67 | } | 195 | } |
diff --git a/src/common/logging/filter.h b/src/common/logging/filter.h index f5673a9f6..1a3074e04 100644 --- a/src/common/logging/filter.h +++ b/src/common/logging/filter.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <array> | 7 | #include <array> |
| 8 | #include <chrono> | ||
| 8 | #include <cstddef> | 9 | #include <cstddef> |
| 9 | #include <string_view> | 10 | #include <string_view> |
| 10 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| @@ -12,6 +13,17 @@ | |||
| 12 | namespace Common::Log { | 13 | namespace Common::Log { |
| 13 | 14 | ||
| 14 | /** | 15 | /** |
| 16 | * Returns the name of the passed log class as a C-string. Subclasses are separated by periods | ||
| 17 | * instead of underscores as in the enumeration. | ||
| 18 | */ | ||
| 19 | const char* GetLogClassName(Class log_class); | ||
| 20 | |||
| 21 | /** | ||
| 22 | * Returns the name of the passed log level as a C-string. | ||
| 23 | */ | ||
| 24 | const char* GetLevelName(Level log_level); | ||
| 25 | |||
| 26 | /** | ||
| 15 | * Implements a log message filter which allows different log classes to have different minimum | 27 | * Implements a log message filter which allows different log classes to have different minimum |
| 16 | * severity levels. The filter can be changed at runtime and can be parsed from a string to allow | 28 | * severity levels. The filter can be changed at runtime and can be parsed from a string to allow |
| 17 | * editing via the interface or loading from a configuration file. | 29 | * editing via the interface or loading from a configuration file. |
diff --git a/src/common/logging/log.h b/src/common/logging/log.h index 1f0f8db52..8d43eddc7 100644 --- a/src/common/logging/log.h +++ b/src/common/logging/log.h | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <fmt/format.h> | 7 | #include <fmt/format.h> |
| 8 | #include "common/common_types.h" | 8 | #include "common/logging/types.h" |
| 9 | 9 | ||
| 10 | namespace Common::Log { | 10 | namespace Common::Log { |
| 11 | 11 | ||
| @@ -18,124 +18,6 @@ constexpr const char* TrimSourcePath(std::string_view source) { | |||
| 18 | return source.data() + idx; | 18 | return source.data() + idx; |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | /// Specifies the severity or level of detail of the log message. | ||
| 22 | enum class Level : u8 { | ||
| 23 | Trace, ///< Extremely detailed and repetitive debugging information that is likely to | ||
| 24 | ///< pollute logs. | ||
| 25 | Debug, ///< Less detailed debugging information. | ||
| 26 | Info, ///< Status information from important points during execution. | ||
| 27 | Warning, ///< Minor or potential problems found during execution of a task. | ||
| 28 | Error, ///< Major problems found during execution of a task that prevent it from being | ||
| 29 | ///< completed. | ||
| 30 | Critical, ///< Major problems during execution that threaten the stability of the entire | ||
| 31 | ///< application. | ||
| 32 | |||
| 33 | Count ///< Total number of logging levels | ||
| 34 | }; | ||
| 35 | |||
| 36 | typedef u8 ClassType; | ||
| 37 | |||
| 38 | /** | ||
| 39 | * Specifies the sub-system that generated the log message. | ||
| 40 | * | ||
| 41 | * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in | ||
| 42 | * backend.cpp. | ||
| 43 | */ | ||
| 44 | enum class Class : ClassType { | ||
| 45 | Log, ///< Messages about the log system itself | ||
| 46 | Common, ///< Library routines | ||
| 47 | Common_Filesystem, ///< Filesystem interface library | ||
| 48 | Common_Memory, ///< Memory mapping and management functions | ||
| 49 | Core, ///< LLE emulation core | ||
| 50 | Core_ARM, ///< ARM CPU core | ||
| 51 | Core_Timing, ///< CoreTiming functions | ||
| 52 | Config, ///< Emulator configuration (including commandline) | ||
| 53 | Debug, ///< Debugging tools | ||
| 54 | Debug_Emulated, ///< Debug messages from the emulated programs | ||
| 55 | Debug_GPU, ///< GPU debugging tools | ||
| 56 | Debug_Breakpoint, ///< Logging breakpoints and watchpoints | ||
| 57 | Debug_GDBStub, ///< GDB Stub | ||
| 58 | Kernel, ///< The HLE implementation of the CTR kernel | ||
| 59 | Kernel_SVC, ///< Kernel system calls | ||
| 60 | Service, ///< HLE implementation of system services. Each major service | ||
| 61 | ///< should have its own subclass. | ||
| 62 | Service_ACC, ///< The ACC (Accounts) service | ||
| 63 | Service_AM, ///< The AM (Applet manager) service | ||
| 64 | Service_AOC, ///< The AOC (AddOn Content) service | ||
| 65 | Service_APM, ///< The APM (Performance) service | ||
| 66 | Service_ARP, ///< The ARP service | ||
| 67 | Service_Audio, ///< The Audio (Audio control) service | ||
| 68 | Service_BCAT, ///< The BCAT service | ||
| 69 | Service_BGTC, ///< The BGTC (Background Task Controller) service | ||
| 70 | Service_BPC, ///< The BPC service | ||
| 71 | Service_BTDRV, ///< The Bluetooth driver service | ||
| 72 | Service_BTM, ///< The BTM service | ||
| 73 | Service_Capture, ///< The capture service | ||
| 74 | Service_ERPT, ///< The error reporting service | ||
| 75 | Service_ETicket, ///< The ETicket service | ||
| 76 | Service_EUPLD, ///< The error upload service | ||
| 77 | Service_Fatal, ///< The Fatal service | ||
| 78 | Service_FGM, ///< The FGM service | ||
| 79 | Service_Friend, ///< The friend service | ||
| 80 | Service_FS, ///< The FS (Filesystem) service | ||
| 81 | Service_GRC, ///< The game recording service | ||
| 82 | Service_HID, ///< The HID (Human interface device) service | ||
| 83 | Service_IRS, ///< The IRS service | ||
| 84 | Service_LBL, ///< The LBL (LCD backlight) service | ||
| 85 | Service_LDN, ///< The LDN (Local domain network) service | ||
| 86 | Service_LDR, ///< The loader service | ||
| 87 | Service_LM, ///< The LM (Logger) service | ||
| 88 | Service_Migration, ///< The migration service | ||
| 89 | Service_Mii, ///< The Mii service | ||
| 90 | Service_MM, ///< The MM (Multimedia) service | ||
| 91 | Service_NCM, ///< The NCM service | ||
| 92 | Service_NFC, ///< The NFC (Near-field communication) service | ||
| 93 | Service_NFP, ///< The NFP service | ||
| 94 | Service_NIFM, ///< The NIFM (Network interface) service | ||
| 95 | Service_NIM, ///< The NIM service | ||
| 96 | Service_NPNS, ///< The NPNS service | ||
| 97 | Service_NS, ///< The NS services | ||
| 98 | Service_NVDRV, ///< The NVDRV (Nvidia driver) service | ||
| 99 | Service_OLSC, ///< The OLSC service | ||
| 100 | Service_PCIE, ///< The PCIe service | ||
| 101 | Service_PCTL, ///< The PCTL (Parental control) service | ||
| 102 | Service_PCV, ///< The PCV service | ||
| 103 | Service_PM, ///< The PM service | ||
| 104 | Service_PREPO, ///< The PREPO (Play report) service | ||
| 105 | Service_PSC, ///< The PSC service | ||
| 106 | Service_PSM, ///< The PSM service | ||
| 107 | Service_SET, ///< The SET (Settings) service | ||
| 108 | Service_SM, ///< The SM (Service manager) service | ||
| 109 | Service_SPL, ///< The SPL service | ||
| 110 | Service_SSL, ///< The SSL service | ||
| 111 | Service_TCAP, ///< The TCAP service. | ||
| 112 | Service_Time, ///< The time service | ||
| 113 | Service_USB, ///< The USB (Universal Serial Bus) service | ||
| 114 | Service_VI, ///< The VI (Video interface) service | ||
| 115 | Service_WLAN, ///< The WLAN (Wireless local area network) service | ||
| 116 | HW, ///< Low-level hardware emulation | ||
| 117 | HW_Memory, ///< Memory-map and address translation | ||
| 118 | HW_LCD, ///< LCD register emulation | ||
| 119 | HW_GPU, ///< GPU control emulation | ||
| 120 | HW_AES, ///< AES engine emulation | ||
| 121 | IPC, ///< IPC interface | ||
| 122 | Frontend, ///< Emulator UI | ||
| 123 | Render, ///< Emulator video output and hardware acceleration | ||
| 124 | Render_Software, ///< Software renderer backend | ||
| 125 | Render_OpenGL, ///< OpenGL backend | ||
| 126 | Render_Vulkan, ///< Vulkan backend | ||
| 127 | Audio, ///< Audio emulation | ||
| 128 | Audio_DSP, ///< The HLE implementation of the DSP | ||
| 129 | Audio_Sink, ///< Emulator audio output backend | ||
| 130 | Loader, ///< ROM loader | ||
| 131 | CheatEngine, ///< Memory manipulation and engine VM functions | ||
| 132 | Crypto, ///< Cryptographic engine/functions | ||
| 133 | Input, ///< Input emulation | ||
| 134 | Network, ///< Network emulation | ||
| 135 | WebService, ///< Interface to yuzu Web Services | ||
| 136 | Count ///< Total number of logging classes | ||
| 137 | }; | ||
| 138 | |||
| 139 | /// Logs a message to the global logger, using fmt | 21 | /// Logs a message to the global logger, using fmt |
| 140 | void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, | 22 | void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, |
| 141 | unsigned int line_num, const char* function, const char* format, | 23 | unsigned int line_num, const char* function, const char* format, |
diff --git a/src/common/logging/text_formatter.cpp b/src/common/logging/text_formatter.cpp index 80ee2cca1..cfc0d5846 100644 --- a/src/common/logging/text_formatter.cpp +++ b/src/common/logging/text_formatter.cpp | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | 11 | ||
| 12 | #include "common/assert.h" | 12 | #include "common/assert.h" |
| 13 | #include "common/common_funcs.h" | 13 | #include "common/common_funcs.h" |
| 14 | #include "common/logging/backend.h" | 14 | #include "common/logging/filter.h" |
| 15 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 16 | #include "common/logging/text_formatter.h" | 16 | #include "common/logging/text_formatter.h" |
| 17 | #include "common/string_util.h" | 17 | #include "common/string_util.h" |
diff --git a/src/common/logging/types.h b/src/common/logging/types.h new file mode 100644 index 000000000..ee9a1ed84 --- /dev/null +++ b/src/common/logging/types.h | |||
| @@ -0,0 +1,142 @@ | |||
| 1 | // Copyright 2021 yuzu Emulator Project | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <chrono> | ||
| 6 | |||
| 7 | #include "common/common_types.h" | ||
| 8 | |||
| 9 | namespace Common::Log { | ||
| 10 | |||
| 11 | /// Specifies the severity or level of detail of the log message. | ||
| 12 | enum class Level : u8 { | ||
| 13 | Trace, ///< Extremely detailed and repetitive debugging information that is likely to | ||
| 14 | ///< pollute logs. | ||
| 15 | Debug, ///< Less detailed debugging information. | ||
| 16 | Info, ///< Status information from important points during execution. | ||
| 17 | Warning, ///< Minor or potential problems found during execution of a task. | ||
| 18 | Error, ///< Major problems found during execution of a task that prevent it from being | ||
| 19 | ///< completed. | ||
| 20 | Critical, ///< Major problems during execution that threaten the stability of the entire | ||
| 21 | ///< application. | ||
| 22 | |||
| 23 | Count ///< Total number of logging levels | ||
| 24 | }; | ||
| 25 | |||
| 26 | /** | ||
| 27 | * Specifies the sub-system that generated the log message. | ||
| 28 | * | ||
| 29 | * @note If you add a new entry here, also add a corresponding one to `ALL_LOG_CLASSES` in | ||
| 30 | * filter.cpp. | ||
| 31 | */ | ||
| 32 | enum class Class : u8 { | ||
| 33 | Log, ///< Messages about the log system itself | ||
| 34 | Common, ///< Library routines | ||
| 35 | Common_Filesystem, ///< Filesystem interface library | ||
| 36 | Common_Memory, ///< Memory mapping and management functions | ||
| 37 | Core, ///< LLE emulation core | ||
| 38 | Core_ARM, ///< ARM CPU core | ||
| 39 | Core_Timing, ///< CoreTiming functions | ||
| 40 | Config, ///< Emulator configuration (including commandline) | ||
| 41 | Debug, ///< Debugging tools | ||
| 42 | Debug_Emulated, ///< Debug messages from the emulated programs | ||
| 43 | Debug_GPU, ///< GPU debugging tools | ||
| 44 | Debug_Breakpoint, ///< Logging breakpoints and watchpoints | ||
| 45 | Debug_GDBStub, ///< GDB Stub | ||
| 46 | Kernel, ///< The HLE implementation of the CTR kernel | ||
| 47 | Kernel_SVC, ///< Kernel system calls | ||
| 48 | Service, ///< HLE implementation of system services. Each major service | ||
| 49 | ///< should have its own subclass. | ||
| 50 | Service_ACC, ///< The ACC (Accounts) service | ||
| 51 | Service_AM, ///< The AM (Applet manager) service | ||
| 52 | Service_AOC, ///< The AOC (AddOn Content) service | ||
| 53 | Service_APM, ///< The APM (Performance) service | ||
| 54 | Service_ARP, ///< The ARP service | ||
| 55 | Service_Audio, ///< The Audio (Audio control) service | ||
| 56 | Service_BCAT, ///< The BCAT service | ||
| 57 | Service_BGTC, ///< The BGTC (Background Task Controller) service | ||
| 58 | Service_BPC, ///< The BPC service | ||
| 59 | Service_BTDRV, ///< The Bluetooth driver service | ||
| 60 | Service_BTM, ///< The BTM service | ||
| 61 | Service_Capture, ///< The capture service | ||
| 62 | Service_ERPT, ///< The error reporting service | ||
| 63 | Service_ETicket, ///< The ETicket service | ||
| 64 | Service_EUPLD, ///< The error upload service | ||
| 65 | Service_Fatal, ///< The Fatal service | ||
| 66 | Service_FGM, ///< The FGM service | ||
| 67 | Service_Friend, ///< The friend service | ||
| 68 | Service_FS, ///< The FS (Filesystem) service | ||
| 69 | Service_GRC, ///< The game recording service | ||
| 70 | Service_HID, ///< The HID (Human interface device) service | ||
| 71 | Service_IRS, ///< The IRS service | ||
| 72 | Service_LBL, ///< The LBL (LCD backlight) service | ||
| 73 | Service_LDN, ///< The LDN (Local domain network) service | ||
| 74 | Service_LDR, ///< The loader service | ||
| 75 | Service_LM, ///< The LM (Logger) service | ||
| 76 | Service_Migration, ///< The migration service | ||
| 77 | Service_Mii, ///< The Mii service | ||
| 78 | Service_MM, ///< The MM (Multimedia) service | ||
| 79 | Service_NCM, ///< The NCM service | ||
| 80 | Service_NFC, ///< The NFC (Near-field communication) service | ||
| 81 | Service_NFP, ///< The NFP service | ||
| 82 | Service_NIFM, ///< The NIFM (Network interface) service | ||
| 83 | Service_NIM, ///< The NIM service | ||
| 84 | Service_NPNS, ///< The NPNS service | ||
| 85 | Service_NS, ///< The NS services | ||
| 86 | Service_NVDRV, ///< The NVDRV (Nvidia driver) service | ||
| 87 | Service_OLSC, ///< The OLSC service | ||
| 88 | Service_PCIE, ///< The PCIe service | ||
| 89 | Service_PCTL, ///< The PCTL (Parental control) service | ||
| 90 | Service_PCV, ///< The PCV service | ||
| 91 | Service_PM, ///< The PM service | ||
| 92 | Service_PREPO, ///< The PREPO (Play report) service | ||
| 93 | Service_PSC, ///< The PSC service | ||
| 94 | Service_PSM, ///< The PSM service | ||
| 95 | Service_SET, ///< The SET (Settings) service | ||
| 96 | Service_SM, ///< The SM (Service manager) service | ||
| 97 | Service_SPL, ///< The SPL service | ||
| 98 | Service_SSL, ///< The SSL service | ||
| 99 | Service_TCAP, ///< The TCAP service. | ||
| 100 | Service_Time, ///< The time service | ||
| 101 | Service_USB, ///< The USB (Universal Serial Bus) service | ||
| 102 | Service_VI, ///< The VI (Video interface) service | ||
| 103 | Service_WLAN, ///< The WLAN (Wireless local area network) service | ||
| 104 | HW, ///< Low-level hardware emulation | ||
| 105 | HW_Memory, ///< Memory-map and address translation | ||
| 106 | HW_LCD, ///< LCD register emulation | ||
| 107 | HW_GPU, ///< GPU control emulation | ||
| 108 | HW_AES, ///< AES engine emulation | ||
| 109 | IPC, ///< IPC interface | ||
| 110 | Frontend, ///< Emulator UI | ||
| 111 | Render, ///< Emulator video output and hardware acceleration | ||
| 112 | Render_Software, ///< Software renderer backend | ||
| 113 | Render_OpenGL, ///< OpenGL backend | ||
| 114 | Render_Vulkan, ///< Vulkan backend | ||
| 115 | Audio, ///< Audio emulation | ||
| 116 | Audio_DSP, ///< The HLE implementation of the DSP | ||
| 117 | Audio_Sink, ///< Emulator audio output backend | ||
| 118 | Loader, ///< ROM loader | ||
| 119 | CheatEngine, ///< Memory manipulation and engine VM functions | ||
| 120 | Crypto, ///< Cryptographic engine/functions | ||
| 121 | Input, ///< Input emulation | ||
| 122 | Network, ///< Network emulation | ||
| 123 | WebService, ///< Interface to yuzu Web Services | ||
| 124 | Count ///< Total number of logging classes | ||
| 125 | }; | ||
| 126 | |||
| 127 | /** | ||
| 128 | * A log entry. Log entries are store in a structured format to permit more varied output | ||
| 129 | * formatting on different frontends, as well as facilitating filtering and aggregation. | ||
| 130 | */ | ||
| 131 | struct Entry { | ||
| 132 | std::chrono::microseconds timestamp; | ||
| 133 | Class log_class{}; | ||
| 134 | Level log_level{}; | ||
| 135 | const char* filename = nullptr; | ||
| 136 | unsigned int line_num = 0; | ||
| 137 | std::string function; | ||
| 138 | std::string message; | ||
| 139 | bool final_entry = false; | ||
| 140 | }; | ||
| 141 | |||
| 142 | } // namespace Common::Log | ||
diff --git a/src/common/page_table.h b/src/common/page_table.h index e92b66b2b..8267e8b4d 100644 --- a/src/common/page_table.h +++ b/src/common/page_table.h | |||
| @@ -111,6 +111,8 @@ struct PageTable { | |||
| 111 | VirtualBuffer<u64> backing_addr; | 111 | VirtualBuffer<u64> backing_addr; |
| 112 | 112 | ||
| 113 | size_t current_address_space_width_in_bits; | 113 | size_t current_address_space_width_in_bits; |
| 114 | |||
| 115 | u8* fastmem_arena; | ||
| 114 | }; | 116 | }; |
| 115 | 117 | ||
| 116 | } // namespace Common | 118 | } // namespace Common |
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index bcb4e4be1..9ec71eced 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -55,6 +55,7 @@ void LogSettings() { | |||
| 55 | log_setting("Renderer_UseAsynchronousGpuEmulation", | 55 | log_setting("Renderer_UseAsynchronousGpuEmulation", |
| 56 | values.use_asynchronous_gpu_emulation.GetValue()); | 56 | values.use_asynchronous_gpu_emulation.GetValue()); |
| 57 | log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue()); | 57 | log_setting("Renderer_UseNvdecEmulation", values.use_nvdec_emulation.GetValue()); |
| 58 | log_setting("Renderer_AccelerateASTC", values.accelerate_astc.GetValue()); | ||
| 58 | log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); | 59 | log_setting("Renderer_UseVsync", values.use_vsync.GetValue()); |
| 59 | log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue()); | 60 | log_setting("Renderer_UseAssemblyShaders", values.use_assembly_shaders.GetValue()); |
| 60 | log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); | 61 | log_setting("Renderer_UseAsynchronousShaders", values.use_asynchronous_shaders.GetValue()); |
| @@ -90,6 +91,13 @@ bool IsGPULevelHigh() { | |||
| 90 | values.gpu_accuracy.GetValue() == GPUAccuracy::High; | 91 | values.gpu_accuracy.GetValue() == GPUAccuracy::High; |
| 91 | } | 92 | } |
| 92 | 93 | ||
| 94 | bool IsFastmemEnabled() { | ||
| 95 | if (values.cpu_accuracy.GetValue() == CPUAccuracy::DebugMode) { | ||
| 96 | return values.cpuopt_fastmem; | ||
| 97 | } | ||
| 98 | return true; | ||
| 99 | } | ||
| 100 | |||
| 93 | float Volume() { | 101 | float Volume() { |
| 94 | if (values.audio_muted) { | 102 | if (values.audio_muted) { |
| 95 | return 0.0f; | 103 | return 0.0f; |
| @@ -115,6 +123,7 @@ void RestoreGlobalState(bool is_powered_on) { | |||
| 115 | values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); | 123 | values.cpuopt_unsafe_unfuse_fma.SetGlobal(true); |
| 116 | values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); | 124 | values.cpuopt_unsafe_reduce_fp_error.SetGlobal(true); |
| 117 | values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); | 125 | values.cpuopt_unsafe_inaccurate_nan.SetGlobal(true); |
| 126 | values.cpuopt_unsafe_fastmem_check.SetGlobal(true); | ||
| 118 | 127 | ||
| 119 | // Renderer | 128 | // Renderer |
| 120 | values.renderer_backend.SetGlobal(true); | 129 | values.renderer_backend.SetGlobal(true); |
| @@ -127,6 +136,7 @@ void RestoreGlobalState(bool is_powered_on) { | |||
| 127 | values.gpu_accuracy.SetGlobal(true); | 136 | values.gpu_accuracy.SetGlobal(true); |
| 128 | values.use_asynchronous_gpu_emulation.SetGlobal(true); | 137 | values.use_asynchronous_gpu_emulation.SetGlobal(true); |
| 129 | values.use_nvdec_emulation.SetGlobal(true); | 138 | values.use_nvdec_emulation.SetGlobal(true); |
| 139 | values.accelerate_astc.SetGlobal(true); | ||
| 130 | values.use_vsync.SetGlobal(true); | 140 | values.use_vsync.SetGlobal(true); |
| 131 | values.use_assembly_shaders.SetGlobal(true); | 141 | values.use_assembly_shaders.SetGlobal(true); |
| 132 | values.use_asynchronous_shaders.SetGlobal(true); | 142 | values.use_asynchronous_shaders.SetGlobal(true); |
diff --git a/src/common/settings.h b/src/common/settings.h index 48085b9a9..6198f2d9f 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -125,10 +125,12 @@ struct Values { | |||
| 125 | bool cpuopt_const_prop; | 125 | bool cpuopt_const_prop; |
| 126 | bool cpuopt_misc_ir; | 126 | bool cpuopt_misc_ir; |
| 127 | bool cpuopt_reduce_misalign_checks; | 127 | bool cpuopt_reduce_misalign_checks; |
| 128 | bool cpuopt_fastmem; | ||
| 128 | 129 | ||
| 129 | Setting<bool> cpuopt_unsafe_unfuse_fma; | 130 | Setting<bool> cpuopt_unsafe_unfuse_fma; |
| 130 | Setting<bool> cpuopt_unsafe_reduce_fp_error; | 131 | Setting<bool> cpuopt_unsafe_reduce_fp_error; |
| 131 | Setting<bool> cpuopt_unsafe_inaccurate_nan; | 132 | Setting<bool> cpuopt_unsafe_inaccurate_nan; |
| 133 | Setting<bool> cpuopt_unsafe_fastmem_check; | ||
| 132 | 134 | ||
| 133 | // Renderer | 135 | // Renderer |
| 134 | Setting<RendererBackend> renderer_backend; | 136 | Setting<RendererBackend> renderer_backend; |
| @@ -145,6 +147,7 @@ struct Values { | |||
| 145 | Setting<GPUAccuracy> gpu_accuracy; | 147 | Setting<GPUAccuracy> gpu_accuracy; |
| 146 | Setting<bool> use_asynchronous_gpu_emulation; | 148 | Setting<bool> use_asynchronous_gpu_emulation; |
| 147 | Setting<bool> use_nvdec_emulation; | 149 | Setting<bool> use_nvdec_emulation; |
| 150 | Setting<bool> accelerate_astc; | ||
| 148 | Setting<bool> use_vsync; | 151 | Setting<bool> use_vsync; |
| 149 | Setting<bool> use_assembly_shaders; | 152 | Setting<bool> use_assembly_shaders; |
| 150 | Setting<bool> use_asynchronous_shaders; | 153 | Setting<bool> use_asynchronous_shaders; |
| @@ -216,6 +219,7 @@ struct Values { | |||
| 216 | std::string program_args; | 219 | std::string program_args; |
| 217 | bool dump_exefs; | 220 | bool dump_exefs; |
| 218 | bool dump_nso; | 221 | bool dump_nso; |
| 222 | bool enable_fs_access_log; | ||
| 219 | bool reporting_services; | 223 | bool reporting_services; |
| 220 | bool quest_flag; | 224 | bool quest_flag; |
| 221 | bool disable_macro_jit; | 225 | bool disable_macro_jit; |
| @@ -249,6 +253,8 @@ void SetConfiguringGlobal(bool is_global); | |||
| 249 | bool IsGPULevelExtreme(); | 253 | bool IsGPULevelExtreme(); |
| 250 | bool IsGPULevelHigh(); | 254 | bool IsGPULevelHigh(); |
| 251 | 255 | ||
| 256 | bool IsFastmemEnabled(); | ||
| 257 | |||
| 252 | float Volume(); | 258 | float Volume(); |
| 253 | 259 | ||
| 254 | std::string GetTimeZoneString(); | 260 | std::string GetTimeZoneString(); |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_32.cpp b/src/core/arm/dynarmic/arm_dynarmic_32.cpp index cea7f0fb1..c8f6dc765 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_32.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_32.cpp | |||
| @@ -128,6 +128,7 @@ std::shared_ptr<Dynarmic::A32::Jit> ARM_Dynarmic_32::MakeJit(Common::PageTable* | |||
| 128 | if (page_table) { | 128 | if (page_table) { |
| 129 | config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>( | 129 | config.page_table = reinterpret_cast<std::array<std::uint8_t*, NUM_PAGE_TABLE_ENTRIES>*>( |
| 130 | page_table->pointers.data()); | 130 | page_table->pointers.data()); |
| 131 | config.fastmem_pointer = page_table->fastmem_arena; | ||
| 131 | } | 132 | } |
| 132 | config.absolute_offset_page_table = true; | 133 | config.absolute_offset_page_table = true; |
| 133 | config.page_table_pointer_mask_bits = Common::PageTable::ATTRIBUTE_BITS; | 134 | 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* | |||
| 143 | 144 | ||
| 144 | // Code cache size | 145 | // Code cache size |
| 145 | config.code_cache_size = 512 * 1024 * 1024; | 146 | config.code_cache_size = 512 * 1024 * 1024; |
| 146 | config.far_code_offset = 256 * 1024 * 1024; | 147 | config.far_code_offset = 400 * 1024 * 1024; |
| 147 | 148 | ||
| 148 | // Safe optimizations | 149 | // Safe optimizations |
| 149 | if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { | 150 | 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* | |||
| 171 | if (!Settings::values.cpuopt_reduce_misalign_checks) { | 172 | if (!Settings::values.cpuopt_reduce_misalign_checks) { |
| 172 | config.only_detect_misalignment_via_page_table_on_page_boundary = false; | 173 | config.only_detect_misalignment_via_page_table_on_page_boundary = false; |
| 173 | } | 174 | } |
| 175 | if (!Settings::values.cpuopt_fastmem) { | ||
| 176 | config.fastmem_pointer = nullptr; | ||
| 177 | } | ||
| 174 | } | 178 | } |
| 175 | 179 | ||
| 176 | // Unsafe optimizations | 180 | // Unsafe optimizations |
diff --git a/src/core/arm/dynarmic/arm_dynarmic_64.cpp b/src/core/arm/dynarmic/arm_dynarmic_64.cpp index 63193dcb1..ba524cd05 100644 --- a/src/core/arm/dynarmic/arm_dynarmic_64.cpp +++ b/src/core/arm/dynarmic/arm_dynarmic_64.cpp | |||
| @@ -160,6 +160,10 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* | |||
| 160 | config.absolute_offset_page_table = true; | 160 | config.absolute_offset_page_table = true; |
| 161 | config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; | 161 | config.detect_misaligned_access_via_page_table = 16 | 32 | 64 | 128; |
| 162 | config.only_detect_misalignment_via_page_table_on_page_boundary = true; | 162 | config.only_detect_misalignment_via_page_table_on_page_boundary = true; |
| 163 | |||
| 164 | config.fastmem_pointer = page_table->fastmem_arena; | ||
| 165 | config.fastmem_address_space_bits = address_space_bits; | ||
| 166 | config.silently_mirror_fastmem = false; | ||
| 163 | } | 167 | } |
| 164 | 168 | ||
| 165 | // Multi-process state | 169 | // Multi-process state |
| @@ -181,7 +185,7 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* | |||
| 181 | 185 | ||
| 182 | // Code cache size | 186 | // Code cache size |
| 183 | config.code_cache_size = 512 * 1024 * 1024; | 187 | config.code_cache_size = 512 * 1024 * 1024; |
| 184 | config.far_code_offset = 256 * 1024 * 1024; | 188 | config.far_code_offset = 400 * 1024 * 1024; |
| 185 | 189 | ||
| 186 | // Safe optimizations | 190 | // Safe optimizations |
| 187 | if (Settings::values.cpu_accuracy.GetValue() == Settings::CPUAccuracy::DebugMode) { | 191 | 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* | |||
| 209 | if (!Settings::values.cpuopt_reduce_misalign_checks) { | 213 | if (!Settings::values.cpuopt_reduce_misalign_checks) { |
| 210 | config.only_detect_misalignment_via_page_table_on_page_boundary = false; | 214 | config.only_detect_misalignment_via_page_table_on_page_boundary = false; |
| 211 | } | 215 | } |
| 216 | if (!Settings::values.cpuopt_fastmem) { | ||
| 217 | config.fastmem_pointer = nullptr; | ||
| 218 | } | ||
| 212 | } | 219 | } |
| 213 | 220 | ||
| 214 | // Unsafe optimizations | 221 | // Unsafe optimizations |
| @@ -223,6 +230,9 @@ std::shared_ptr<Dynarmic::A64::Jit> ARM_Dynarmic_64::MakeJit(Common::PageTable* | |||
| 223 | if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { | 230 | if (Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()) { |
| 224 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; | 231 | config.optimizations |= Dynarmic::OptimizationFlag::Unsafe_InaccurateNaN; |
| 225 | } | 232 | } |
| 233 | if (Settings::values.cpuopt_unsafe_fastmem_check.GetValue()) { | ||
| 234 | config.fastmem_address_space_bits = 64; | ||
| 235 | } | ||
| 226 | } | 236 | } |
| 227 | 237 | ||
| 228 | return std::make_shared<Dynarmic::A64::Jit>(config); | 238 | return std::make_shared<Dynarmic::A64::Jit>(config); |
diff --git a/src/core/device_memory.cpp b/src/core/device_memory.cpp index 0c4b440ed..f19c0515f 100644 --- a/src/core/device_memory.cpp +++ b/src/core/device_memory.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | 6 | ||
| 7 | namespace Core { | 7 | namespace Core { |
| 8 | 8 | ||
| 9 | DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size} {} | 9 | DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size, 1ULL << 39} {} |
| 10 | DeviceMemory::~DeviceMemory() = default; | 10 | DeviceMemory::~DeviceMemory() = default; |
| 11 | 11 | ||
| 12 | } // namespace Core | 12 | } // namespace Core |
diff --git a/src/core/device_memory.h b/src/core/device_memory.h index 5b1ae28f3..c4d17705f 100644 --- a/src/core/device_memory.h +++ b/src/core/device_memory.h | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include "common/common_types.h" | 7 | #include "common/common_types.h" |
| 8 | #include "common/virtual_buffer.h" | 8 | #include "common/host_memory.h" |
| 9 | 9 | ||
| 10 | namespace Core { | 10 | namespace Core { |
| 11 | 11 | ||
| @@ -21,27 +21,30 @@ enum : u64 { | |||
| 21 | }; | 21 | }; |
| 22 | }; // namespace DramMemoryMap | 22 | }; // namespace DramMemoryMap |
| 23 | 23 | ||
| 24 | class DeviceMemory : NonCopyable { | 24 | class DeviceMemory { |
| 25 | public: | 25 | public: |
| 26 | explicit DeviceMemory(); | 26 | explicit DeviceMemory(); |
| 27 | ~DeviceMemory(); | 27 | ~DeviceMemory(); |
| 28 | 28 | ||
| 29 | DeviceMemory& operator=(const DeviceMemory&) = delete; | ||
| 30 | DeviceMemory(const DeviceMemory&) = delete; | ||
| 31 | |||
| 29 | template <typename T> | 32 | template <typename T> |
| 30 | PAddr GetPhysicalAddr(const T* ptr) const { | 33 | PAddr GetPhysicalAddr(const T* ptr) const { |
| 31 | return (reinterpret_cast<uintptr_t>(ptr) - reinterpret_cast<uintptr_t>(buffer.data())) + | 34 | return (reinterpret_cast<uintptr_t>(ptr) - |
| 35 | reinterpret_cast<uintptr_t>(buffer.BackingBasePointer())) + | ||
| 32 | DramMemoryMap::Base; | 36 | DramMemoryMap::Base; |
| 33 | } | 37 | } |
| 34 | 38 | ||
| 35 | u8* GetPointer(PAddr addr) { | 39 | u8* GetPointer(PAddr addr) { |
| 36 | return buffer.data() + (addr - DramMemoryMap::Base); | 40 | return buffer.BackingBasePointer() + (addr - DramMemoryMap::Base); |
| 37 | } | 41 | } |
| 38 | 42 | ||
| 39 | const u8* GetPointer(PAddr addr) const { | 43 | const u8* GetPointer(PAddr addr) const { |
| 40 | return buffer.data() + (addr - DramMemoryMap::Base); | 44 | return buffer.BackingBasePointer() + (addr - DramMemoryMap::Base); |
| 41 | } | 45 | } |
| 42 | 46 | ||
| 43 | private: | 47 | Common::HostMemory buffer; |
| 44 | Common::VirtualBuffer<u8> buffer; | ||
| 45 | }; | 48 | }; |
| 46 | 49 | ||
| 47 | } // namespace Core | 50 | } // namespace Core |
diff --git a/src/core/file_sys/program_metadata.cpp b/src/core/file_sys/program_metadata.cpp index 83b83a044..01ae1a567 100644 --- a/src/core/file_sys/program_metadata.cpp +++ b/src/core/file_sys/program_metadata.cpp | |||
| @@ -150,7 +150,9 @@ void ProgramMetadata::Print() const { | |||
| 150 | LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO"); | 150 | LOG_DEBUG(Service_FS, " > Is Retail: {}", acid_header.is_retail ? "YES" : "NO"); |
| 151 | LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min); | 151 | LOG_DEBUG(Service_FS, "Title ID Min: 0x{:016X}", acid_header.title_id_min); |
| 152 | LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max); | 152 | LOG_DEBUG(Service_FS, "Title ID Max: 0x{:016X}", acid_header.title_id_max); |
| 153 | LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", acid_file_access.permissions); | 153 | u64_le permissions_l; // local copy to fix alignment error |
| 154 | std::memcpy(&permissions_l, &acid_file_access.permissions, sizeof(permissions_l)); | ||
| 155 | LOG_DEBUG(Service_FS, "Filesystem Access: 0x{:016X}\n", permissions_l); | ||
| 154 | 156 | ||
| 155 | // Begin ACI0 printing (actual perms, unsigned) | 157 | // Begin ACI0 printing (actual perms, unsigned) |
| 156 | LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data()); | 158 | LOG_DEBUG(Service_FS, "Magic: {:.4}", aci_header.magic.data()); |
diff --git a/src/core/file_sys/vfs.cpp b/src/core/file_sys/vfs.cpp index 215e1cb1a..368419eca 100644 --- a/src/core/file_sys/vfs.cpp +++ b/src/core/file_sys/vfs.cpp | |||
| @@ -6,7 +6,6 @@ | |||
| 6 | #include <numeric> | 6 | #include <numeric> |
| 7 | #include <string> | 7 | #include <string> |
| 8 | #include "common/fs/path_util.h" | 8 | #include "common/fs/path_util.h" |
| 9 | #include "common/logging/backend.h" | ||
| 10 | #include "core/file_sys/mode.h" | 9 | #include "core/file_sys/mode.h" |
| 11 | #include "core/file_sys/vfs.h" | 10 | #include "core/file_sys/vfs.h" |
| 12 | 11 | ||
diff --git a/src/core/file_sys/vfs_libzip.cpp b/src/core/file_sys/vfs_libzip.cpp index cd162c0c3..00e256779 100644 --- a/src/core/file_sys/vfs_libzip.cpp +++ b/src/core/file_sys/vfs_libzip.cpp | |||
| @@ -14,7 +14,6 @@ | |||
| 14 | #endif | 14 | #endif |
| 15 | 15 | ||
| 16 | #include "common/fs/path_util.h" | 16 | #include "common/fs/path_util.h" |
| 17 | #include "common/logging/backend.h" | ||
| 18 | #include "core/file_sys/vfs.h" | 17 | #include "core/file_sys/vfs.h" |
| 19 | #include "core/file_sys/vfs_libzip.h" | 18 | #include "core/file_sys/vfs_libzip.h" |
| 20 | #include "core/file_sys/vfs_vector.h" | 19 | #include "core/file_sys/vfs_vector.h" |
diff --git a/src/core/frontend/input.h b/src/core/frontend/input.h index 0c5d2b3b0..7a047803e 100644 --- a/src/core/frontend/input.h +++ b/src/core/frontend/input.h | |||
| @@ -27,6 +27,10 @@ struct AnalogProperties { | |||
| 27 | float range; | 27 | float range; |
| 28 | float threshold; | 28 | float threshold; |
| 29 | }; | 29 | }; |
| 30 | template <typename StatusType> | ||
| 31 | struct InputCallback { | ||
| 32 | std::function<void(StatusType)> on_change; | ||
| 33 | }; | ||
| 30 | 34 | ||
| 31 | /// An abstract class template for an input device (a button, an analog input, etc.). | 35 | /// An abstract class template for an input device (a button, an analog input, etc.). |
| 32 | template <typename StatusType> | 36 | template <typename StatusType> |
| @@ -50,6 +54,17 @@ public: | |||
| 50 | [[maybe_unused]] f32 freq_high) const { | 54 | [[maybe_unused]] f32 freq_high) const { |
| 51 | return {}; | 55 | return {}; |
| 52 | } | 56 | } |
| 57 | void SetCallback(InputCallback<StatusType> callback_) { | ||
| 58 | callback = std::move(callback_); | ||
| 59 | } | ||
| 60 | void TriggerOnChange() { | ||
| 61 | if (callback.on_change) { | ||
| 62 | callback.on_change(GetStatus()); | ||
| 63 | } | ||
| 64 | } | ||
| 65 | |||
| 66 | private: | ||
| 67 | InputCallback<StatusType> callback; | ||
| 53 | }; | 68 | }; |
| 54 | 69 | ||
| 55 | /// An abstract class template for a factory that can create input devices. | 70 | /// An abstract class template for a factory that can create input devices. |
diff --git a/src/core/hle/kernel/hle_ipc.cpp b/src/core/hle/kernel/hle_ipc.cpp index 2b5c30f7a..28ed6265a 100644 --- a/src/core/hle/kernel/hle_ipc.cpp +++ b/src/core/hle/kernel/hle_ipc.cpp | |||
| @@ -30,16 +30,38 @@ | |||
| 30 | 30 | ||
| 31 | namespace Kernel { | 31 | namespace Kernel { |
| 32 | 32 | ||
| 33 | SessionRequestHandler::SessionRequestHandler() = default; | 33 | SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* service_name_) |
| 34 | : kernel{kernel_}, service_thread{kernel.CreateServiceThread(service_name_)} {} | ||
| 34 | 35 | ||
| 35 | SessionRequestHandler::~SessionRequestHandler() = default; | 36 | SessionRequestHandler::~SessionRequestHandler() { |
| 37 | kernel.ReleaseServiceThread(service_thread); | ||
| 38 | } | ||
| 39 | |||
| 40 | SessionRequestManager::SessionRequestManager(KernelCore& kernel_) : kernel{kernel_} {} | ||
| 41 | |||
| 42 | SessionRequestManager::~SessionRequestManager() = default; | ||
| 43 | |||
| 44 | bool SessionRequestManager::HasSessionRequestHandler(const HLERequestContext& context) const { | ||
| 45 | if (IsDomain() && context.HasDomainMessageHeader()) { | ||
| 46 | const auto& message_header = context.GetDomainMessageHeader(); | ||
| 47 | const auto object_id = message_header.object_id; | ||
| 48 | |||
| 49 | if (object_id > DomainHandlerCount()) { | ||
| 50 | LOG_CRITICAL(IPC, "object_id {} is too big!", object_id); | ||
| 51 | return false; | ||
| 52 | } | ||
| 53 | return DomainHandler(object_id - 1) != nullptr; | ||
| 54 | } else { | ||
| 55 | return session_handler != nullptr; | ||
| 56 | } | ||
| 57 | } | ||
| 36 | 58 | ||
| 37 | void SessionRequestHandler::ClientConnected(KServerSession* session) { | 59 | void SessionRequestHandler::ClientConnected(KServerSession* session) { |
| 38 | session->SetSessionHandler(shared_from_this()); | 60 | session->ClientConnected(shared_from_this()); |
| 39 | } | 61 | } |
| 40 | 62 | ||
| 41 | void SessionRequestHandler::ClientDisconnected(KServerSession* session) { | 63 | void SessionRequestHandler::ClientDisconnected(KServerSession* session) { |
| 42 | session->SetSessionHandler(nullptr); | 64 | session->ClientDisconnected(); |
| 43 | } | 65 | } |
| 44 | 66 | ||
| 45 | HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, | 67 | HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, |
diff --git a/src/core/hle/kernel/hle_ipc.h b/src/core/hle/kernel/hle_ipc.h index b47e363cc..a61870f8b 100644 --- a/src/core/hle/kernel/hle_ipc.h +++ b/src/core/hle/kernel/hle_ipc.h | |||
| @@ -46,6 +46,7 @@ class KThread; | |||
| 46 | class KReadableEvent; | 46 | class KReadableEvent; |
| 47 | class KSession; | 47 | class KSession; |
| 48 | class KWritableEvent; | 48 | class KWritableEvent; |
| 49 | class ServiceThread; | ||
| 49 | 50 | ||
| 50 | enum class ThreadWakeupReason; | 51 | enum class ThreadWakeupReason; |
| 51 | 52 | ||
| @@ -56,7 +57,7 @@ enum class ThreadWakeupReason; | |||
| 56 | */ | 57 | */ |
| 57 | class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> { | 58 | class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> { |
| 58 | public: | 59 | public: |
| 59 | SessionRequestHandler(); | 60 | SessionRequestHandler(KernelCore& kernel, const char* service_name_); |
| 60 | virtual ~SessionRequestHandler(); | 61 | virtual ~SessionRequestHandler(); |
| 61 | 62 | ||
| 62 | /** | 63 | /** |
| @@ -83,6 +84,14 @@ public: | |||
| 83 | * @param server_session ServerSession associated with the connection. | 84 | * @param server_session ServerSession associated with the connection. |
| 84 | */ | 85 | */ |
| 85 | void ClientDisconnected(KServerSession* session); | 86 | void ClientDisconnected(KServerSession* session); |
| 87 | |||
| 88 | std::weak_ptr<ServiceThread> GetServiceThread() const { | ||
| 89 | return service_thread; | ||
| 90 | } | ||
| 91 | |||
| 92 | protected: | ||
| 93 | KernelCore& kernel; | ||
| 94 | std::weak_ptr<ServiceThread> service_thread; | ||
| 86 | }; | 95 | }; |
| 87 | 96 | ||
| 88 | using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; | 97 | using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; |
| @@ -94,7 +103,8 @@ using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; | |||
| 94 | */ | 103 | */ |
| 95 | class SessionRequestManager final { | 104 | class SessionRequestManager final { |
| 96 | public: | 105 | public: |
| 97 | SessionRequestManager() = default; | 106 | explicit SessionRequestManager(KernelCore& kernel); |
| 107 | ~SessionRequestManager(); | ||
| 98 | 108 | ||
| 99 | bool IsDomain() const { | 109 | bool IsDomain() const { |
| 100 | return is_domain; | 110 | return is_domain; |
| @@ -142,10 +152,19 @@ public: | |||
| 142 | session_handler = std::move(handler); | 152 | session_handler = std::move(handler); |
| 143 | } | 153 | } |
| 144 | 154 | ||
| 155 | std::weak_ptr<ServiceThread> GetServiceThread() const { | ||
| 156 | return session_handler->GetServiceThread(); | ||
| 157 | } | ||
| 158 | |||
| 159 | bool HasSessionRequestHandler(const HLERequestContext& context) const; | ||
| 160 | |||
| 145 | private: | 161 | private: |
| 146 | bool is_domain{}; | 162 | bool is_domain{}; |
| 147 | SessionRequestHandlerPtr session_handler; | 163 | SessionRequestHandlerPtr session_handler; |
| 148 | std::vector<SessionRequestHandlerPtr> domain_handlers; | 164 | std::vector<SessionRequestHandlerPtr> domain_handlers; |
| 165 | |||
| 166 | private: | ||
| 167 | KernelCore& kernel; | ||
| 149 | }; | 168 | }; |
| 150 | 169 | ||
| 151 | /** | 170 | /** |
diff --git a/src/core/hle/kernel/k_auto_object.h b/src/core/hle/kernel/k_auto_object.h index bc18582be..88a052f65 100644 --- a/src/core/hle/kernel/k_auto_object.h +++ b/src/core/hle/kernel/k_auto_object.h | |||
| @@ -7,10 +7,11 @@ | |||
| 7 | #include <atomic> | 7 | #include <atomic> |
| 8 | #include <string> | 8 | #include <string> |
| 9 | 9 | ||
| 10 | #include <boost/intrusive/rbtree.hpp> | ||
| 11 | |||
| 10 | #include "common/assert.h" | 12 | #include "common/assert.h" |
| 11 | #include "common/common_funcs.h" | 13 | #include "common/common_funcs.h" |
| 12 | #include "common/common_types.h" | 14 | #include "common/common_types.h" |
| 13 | #include "common/intrusive_red_black_tree.h" | ||
| 14 | #include "core/hle/kernel/k_class_token.h" | 15 | #include "core/hle/kernel/k_class_token.h" |
| 15 | 16 | ||
| 16 | namespace Kernel { | 17 | namespace Kernel { |
| @@ -175,7 +176,7 @@ private: | |||
| 175 | 176 | ||
| 176 | class KAutoObjectWithListContainer; | 177 | class KAutoObjectWithListContainer; |
| 177 | 178 | ||
| 178 | class KAutoObjectWithList : public KAutoObject { | 179 | class KAutoObjectWithList : public KAutoObject, public boost::intrusive::set_base_hook<> { |
| 179 | public: | 180 | public: |
| 180 | explicit KAutoObjectWithList(KernelCore& kernel_) : KAutoObject(kernel_) {} | 181 | explicit KAutoObjectWithList(KernelCore& kernel_) : KAutoObject(kernel_) {} |
| 181 | 182 | ||
| @@ -192,6 +193,10 @@ public: | |||
| 192 | } | 193 | } |
| 193 | } | 194 | } |
| 194 | 195 | ||
| 196 | friend bool operator<(const KAutoObjectWithList& left, const KAutoObjectWithList& right) { | ||
| 197 | return &left < &right; | ||
| 198 | } | ||
| 199 | |||
| 195 | public: | 200 | public: |
| 196 | virtual u64 GetId() const { | 201 | virtual u64 GetId() const { |
| 197 | return reinterpret_cast<u64>(this); | 202 | return reinterpret_cast<u64>(this); |
| @@ -203,8 +208,6 @@ public: | |||
| 203 | 208 | ||
| 204 | private: | 209 | private: |
| 205 | friend class KAutoObjectWithListContainer; | 210 | friend class KAutoObjectWithListContainer; |
| 206 | |||
| 207 | Common::IntrusiveRedBlackTreeNode list_node; | ||
| 208 | }; | 211 | }; |
| 209 | 212 | ||
| 210 | template <typename T> | 213 | template <typename T> |
diff --git a/src/core/hle/kernel/k_auto_object_container.cpp b/src/core/hle/kernel/k_auto_object_container.cpp index fc0c28874..010006bb7 100644 --- a/src/core/hle/kernel/k_auto_object_container.cpp +++ b/src/core/hle/kernel/k_auto_object_container.cpp | |||
| @@ -9,13 +9,13 @@ namespace Kernel { | |||
| 9 | void KAutoObjectWithListContainer::Register(KAutoObjectWithList* obj) { | 9 | void KAutoObjectWithListContainer::Register(KAutoObjectWithList* obj) { |
| 10 | KScopedLightLock lk(m_lock); | 10 | KScopedLightLock lk(m_lock); |
| 11 | 11 | ||
| 12 | m_object_list.insert(*obj); | 12 | m_object_list.insert_unique(*obj); |
| 13 | } | 13 | } |
| 14 | 14 | ||
| 15 | void KAutoObjectWithListContainer::Unregister(KAutoObjectWithList* obj) { | 15 | void KAutoObjectWithListContainer::Unregister(KAutoObjectWithList* obj) { |
| 16 | KScopedLightLock lk(m_lock); | 16 | KScopedLightLock lk(m_lock); |
| 17 | 17 | ||
| 18 | m_object_list.erase(m_object_list.iterator_to(*obj)); | 18 | m_object_list.erase(*obj); |
| 19 | } | 19 | } |
| 20 | 20 | ||
| 21 | size_t KAutoObjectWithListContainer::GetOwnedCount(KProcess* owner) { | 21 | size_t KAutoObjectWithListContainer::GetOwnedCount(KProcess* owner) { |
diff --git a/src/core/hle/kernel/k_auto_object_container.h b/src/core/hle/kernel/k_auto_object_container.h index ff40cf5a7..459953450 100644 --- a/src/core/hle/kernel/k_auto_object_container.h +++ b/src/core/hle/kernel/k_auto_object_container.h | |||
| @@ -6,6 +6,8 @@ | |||
| 6 | 6 | ||
| 7 | #include <atomic> | 7 | #include <atomic> |
| 8 | 8 | ||
| 9 | #include <boost/intrusive/rbtree.hpp> | ||
| 10 | |||
| 9 | #include "common/assert.h" | 11 | #include "common/assert.h" |
| 10 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 11 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| @@ -23,8 +25,7 @@ class KAutoObjectWithListContainer { | |||
| 23 | YUZU_NON_MOVEABLE(KAutoObjectWithListContainer); | 25 | YUZU_NON_MOVEABLE(KAutoObjectWithListContainer); |
| 24 | 26 | ||
| 25 | public: | 27 | public: |
| 26 | using ListType = Common::IntrusiveRedBlackTreeMemberTraits< | 28 | using ListType = boost::intrusive::rbtree<KAutoObjectWithList>; |
| 27 | &KAutoObjectWithList::list_node>::TreeType<KAutoObjectWithList>; | ||
| 28 | 29 | ||
| 29 | public: | 30 | public: |
| 30 | class ListAccessor : public KScopedLightLock { | 31 | class ListAccessor : public KScopedLightLock { |
diff --git a/src/core/hle/kernel/k_client_port.cpp b/src/core/hle/kernel/k_client_port.cpp index 23d830d1f..ef168fe87 100644 --- a/src/core/hle/kernel/k_client_port.cpp +++ b/src/core/hle/kernel/k_client_port.cpp | |||
| @@ -16,11 +16,11 @@ namespace Kernel { | |||
| 16 | KClientPort::KClientPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} | 16 | KClientPort::KClientPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} |
| 17 | KClientPort::~KClientPort() = default; | 17 | KClientPort::~KClientPort() = default; |
| 18 | 18 | ||
| 19 | void KClientPort::Initialize(KPort* parent_, s32 max_sessions_, std::string&& name_) { | 19 | void KClientPort::Initialize(KPort* parent_port_, s32 max_sessions_, std::string&& name_) { |
| 20 | // Set member variables. | 20 | // Set member variables. |
| 21 | num_sessions = 0; | 21 | num_sessions = 0; |
| 22 | peak_sessions = 0; | 22 | peak_sessions = 0; |
| 23 | parent = parent_; | 23 | parent = parent_port_; |
| 24 | max_sessions = max_sessions_; | 24 | max_sessions = max_sessions_; |
| 25 | name = std::move(name_); | 25 | name = std::move(name_); |
| 26 | } | 26 | } |
| @@ -28,6 +28,9 @@ void KClientPort::Initialize(KPort* parent_, s32 max_sessions_, std::string&& na | |||
| 28 | void KClientPort::OnSessionFinalized() { | 28 | void KClientPort::OnSessionFinalized() { |
| 29 | KScopedSchedulerLock sl{kernel}; | 29 | KScopedSchedulerLock sl{kernel}; |
| 30 | 30 | ||
| 31 | // This might happen if a session was improperly used with this port. | ||
| 32 | ASSERT_MSG(num_sessions > 0, "num_sessions is invalid"); | ||
| 33 | |||
| 31 | const auto prev = num_sessions--; | 34 | const auto prev = num_sessions--; |
| 32 | if (prev == max_sessions) { | 35 | if (prev == max_sessions) { |
| 33 | this->NotifyAvailable(); | 36 | this->NotifyAvailable(); |
| @@ -56,7 +59,8 @@ bool KClientPort::IsSignaled() const { | |||
| 56 | return num_sessions < max_sessions; | 59 | return num_sessions < max_sessions; |
| 57 | } | 60 | } |
| 58 | 61 | ||
| 59 | ResultCode KClientPort::CreateSession(KClientSession** out) { | 62 | ResultCode KClientPort::CreateSession(KClientSession** out, |
| 63 | std::shared_ptr<SessionRequestManager> session_manager) { | ||
| 60 | // Reserve a new session from the resource limit. | 64 | // Reserve a new session from the resource limit. |
| 61 | KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), | 65 | KScopedResourceReservation session_reservation(kernel.CurrentProcess()->GetResourceLimit(), |
| 62 | LimitableResource::Sessions); | 66 | LimitableResource::Sessions); |
| @@ -65,7 +69,7 @@ ResultCode KClientPort::CreateSession(KClientSession** out) { | |||
| 65 | // Update the session counts. | 69 | // Update the session counts. |
| 66 | { | 70 | { |
| 67 | // Atomically increment the number of sessions. | 71 | // Atomically increment the number of sessions. |
| 68 | s32 new_sessions; | 72 | s32 new_sessions{}; |
| 69 | { | 73 | { |
| 70 | const auto max = max_sessions; | 74 | const auto max = max_sessions; |
| 71 | auto cur_sessions = num_sessions.load(std::memory_order_acquire); | 75 | auto cur_sessions = num_sessions.load(std::memory_order_acquire); |
| @@ -101,7 +105,7 @@ ResultCode KClientPort::CreateSession(KClientSession** out) { | |||
| 101 | } | 105 | } |
| 102 | 106 | ||
| 103 | // Initialize the session. | 107 | // Initialize the session. |
| 104 | session->Initialize(this, parent->GetName()); | 108 | session->Initialize(this, parent->GetName(), session_manager); |
| 105 | 109 | ||
| 106 | // Commit the session reservation. | 110 | // Commit the session reservation. |
| 107 | session_reservation.Commit(); | 111 | session_reservation.Commit(); |
diff --git a/src/core/hle/kernel/k_client_port.h b/src/core/hle/kernel/k_client_port.h index f2fff3b01..54bb05e20 100644 --- a/src/core/hle/kernel/k_client_port.h +++ b/src/core/hle/kernel/k_client_port.h | |||
| @@ -16,6 +16,7 @@ namespace Kernel { | |||
| 16 | class KClientSession; | 16 | class KClientSession; |
| 17 | class KernelCore; | 17 | class KernelCore; |
| 18 | class KPort; | 18 | class KPort; |
| 19 | class SessionRequestManager; | ||
| 19 | 20 | ||
| 20 | class KClientPort final : public KSynchronizationObject { | 21 | class KClientPort final : public KSynchronizationObject { |
| 21 | KERNEL_AUTOOBJECT_TRAITS(KClientPort, KSynchronizationObject); | 22 | KERNEL_AUTOOBJECT_TRAITS(KClientPort, KSynchronizationObject); |
| @@ -52,7 +53,8 @@ public: | |||
| 52 | void Destroy() override; | 53 | void Destroy() override; |
| 53 | bool IsSignaled() const override; | 54 | bool IsSignaled() const override; |
| 54 | 55 | ||
| 55 | ResultCode CreateSession(KClientSession** out); | 56 | ResultCode CreateSession(KClientSession** out, |
| 57 | std::shared_ptr<SessionRequestManager> session_manager = nullptr); | ||
| 56 | 58 | ||
| 57 | private: | 59 | private: |
| 58 | std::atomic<s32> num_sessions{}; | 60 | std::atomic<s32> num_sessions{}; |
diff --git a/src/core/hle/kernel/k_client_session.h b/src/core/hle/kernel/k_client_session.h index b11d5b4e3..230e3b6b8 100644 --- a/src/core/hle/kernel/k_client_session.h +++ b/src/core/hle/kernel/k_client_session.h | |||
| @@ -36,9 +36,9 @@ public: | |||
| 36 | explicit KClientSession(KernelCore& kernel_); | 36 | explicit KClientSession(KernelCore& kernel_); |
| 37 | ~KClientSession() override; | 37 | ~KClientSession() override; |
| 38 | 38 | ||
| 39 | void Initialize(KSession* parent_, std::string&& name_) { | 39 | void Initialize(KSession* parent_session_, std::string&& name_) { |
| 40 | // Set member variables. | 40 | // Set member variables. |
| 41 | parent = parent_; | 41 | parent = parent_session_; |
| 42 | name = std::move(name_); | 42 | name = std::move(name_); |
| 43 | } | 43 | } |
| 44 | 44 | ||
diff --git a/src/core/hle/kernel/k_light_condition_variable.h b/src/core/hle/kernel/k_light_condition_variable.h index ca2e539a7..a95fa41f3 100644 --- a/src/core/hle/kernel/k_light_condition_variable.h +++ b/src/core/hle/kernel/k_light_condition_variable.h | |||
| @@ -18,41 +18,58 @@ class KernelCore; | |||
| 18 | 18 | ||
| 19 | class KLightConditionVariable { | 19 | class KLightConditionVariable { |
| 20 | public: | 20 | public: |
| 21 | explicit KLightConditionVariable(KernelCore& kernel_) | 21 | explicit KLightConditionVariable(KernelCore& kernel_) : kernel{kernel_} {} |
| 22 | : thread_queue(kernel_), kernel(kernel_) {} | ||
| 23 | 22 | ||
| 24 | void Wait(KLightLock* lock, s64 timeout = -1) { | 23 | void Wait(KLightLock* lock, s64 timeout = -1, bool allow_terminating_thread = true) { |
| 25 | WaitImpl(lock, timeout); | 24 | WaitImpl(lock, timeout, allow_terminating_thread); |
| 26 | lock->Lock(); | ||
| 27 | } | 25 | } |
| 28 | 26 | ||
| 29 | void Broadcast() { | 27 | void Broadcast() { |
| 30 | KScopedSchedulerLock lk{kernel}; | 28 | KScopedSchedulerLock lk{kernel}; |
| 31 | while (thread_queue.WakeupFrontThread() != nullptr) { | 29 | |
| 32 | // We want to signal all threads, and so should continue waking up until there's nothing | 30 | // Signal all threads. |
| 33 | // to wake. | 31 | for (auto& thread : wait_list) { |
| 32 | thread.SetState(ThreadState::Runnable); | ||
| 34 | } | 33 | } |
| 35 | } | 34 | } |
| 36 | 35 | ||
| 37 | private: | 36 | private: |
| 38 | void WaitImpl(KLightLock* lock, s64 timeout) { | 37 | void WaitImpl(KLightLock* lock, s64 timeout, bool allow_terminating_thread) { |
| 39 | KThread* owner = GetCurrentThreadPointer(kernel); | 38 | KThread* owner = GetCurrentThreadPointer(kernel); |
| 40 | 39 | ||
| 41 | // Sleep the thread. | 40 | // Sleep the thread. |
| 42 | { | 41 | { |
| 43 | KScopedSchedulerLockAndSleep lk(kernel, owner, timeout); | 42 | KScopedSchedulerLockAndSleep lk{kernel, owner, timeout}; |
| 44 | lock->Unlock(); | ||
| 45 | 43 | ||
| 46 | if (!thread_queue.SleepThread(owner)) { | 44 | if (!allow_terminating_thread && owner->IsTerminationRequested()) { |
| 47 | lk.CancelSleep(); | 45 | lk.CancelSleep(); |
| 48 | return; | 46 | return; |
| 49 | } | 47 | } |
| 48 | |||
| 49 | lock->Unlock(); | ||
| 50 | |||
| 51 | // Set the thread as waiting. | ||
| 52 | GetCurrentThread(kernel).SetState(ThreadState::Waiting); | ||
| 53 | |||
| 54 | // Add the thread to the queue. | ||
| 55 | wait_list.push_back(GetCurrentThread(kernel)); | ||
| 56 | } | ||
| 57 | |||
| 58 | // Remove the thread from the wait list. | ||
| 59 | { | ||
| 60 | KScopedSchedulerLock sl{kernel}; | ||
| 61 | |||
| 62 | wait_list.erase(wait_list.iterator_to(GetCurrentThread(kernel))); | ||
| 50 | } | 63 | } |
| 51 | 64 | ||
| 52 | // Cancel the task that the sleep setup. | 65 | // Cancel the task that the sleep setup. |
| 53 | kernel.TimeManager().UnscheduleTimeEvent(owner); | 66 | kernel.TimeManager().UnscheduleTimeEvent(owner); |
| 67 | |||
| 68 | // Re-acquire the lock. | ||
| 69 | lock->Lock(); | ||
| 54 | } | 70 | } |
| 55 | KThreadQueue thread_queue; | 71 | |
| 56 | KernelCore& kernel; | 72 | KernelCore& kernel; |
| 73 | KThread::WaiterList wait_list{}; | ||
| 57 | }; | 74 | }; |
| 58 | } // namespace Kernel | 75 | } // namespace Kernel |
diff --git a/src/core/hle/kernel/k_light_lock.cpp b/src/core/hle/kernel/k_light_lock.cpp index f974022e8..0896e705f 100644 --- a/src/core/hle/kernel/k_light_lock.cpp +++ b/src/core/hle/kernel/k_light_lock.cpp | |||
| @@ -59,11 +59,7 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { | |||
| 59 | owner_thread->AddWaiter(cur_thread); | 59 | owner_thread->AddWaiter(cur_thread); |
| 60 | 60 | ||
| 61 | // Set thread states. | 61 | // Set thread states. |
| 62 | if (cur_thread->GetState() == ThreadState::Runnable) { | 62 | cur_thread->SetState(ThreadState::Waiting); |
| 63 | cur_thread->SetState(ThreadState::Waiting); | ||
| 64 | } else { | ||
| 65 | KScheduler::SetSchedulerUpdateNeeded(kernel); | ||
| 66 | } | ||
| 67 | 63 | ||
| 68 | if (owner_thread->IsSuspended()) { | 64 | if (owner_thread->IsSuspended()) { |
| 69 | owner_thread->ContinueIfHasKernelWaiters(); | 65 | owner_thread->ContinueIfHasKernelWaiters(); |
| @@ -73,10 +69,9 @@ void KLightLock::LockSlowPath(uintptr_t _owner, uintptr_t _cur_thread) { | |||
| 73 | // We're no longer waiting on the lock owner. | 69 | // We're no longer waiting on the lock owner. |
| 74 | { | 70 | { |
| 75 | KScopedSchedulerLock sl{kernel}; | 71 | KScopedSchedulerLock sl{kernel}; |
| 76 | KThread* owner_thread = cur_thread->GetLockOwner(); | 72 | |
| 77 | if (owner_thread) { | 73 | if (KThread* owner_thread = cur_thread->GetLockOwner(); owner_thread != nullptr) { |
| 78 | owner_thread->RemoveWaiter(cur_thread); | 74 | owner_thread->RemoveWaiter(cur_thread); |
| 79 | KScheduler::SetSchedulerUpdateNeeded(kernel); | ||
| 80 | } | 75 | } |
| 81 | } | 76 | } |
| 82 | } | 77 | } |
| @@ -95,17 +90,13 @@ void KLightLock::UnlockSlowPath(uintptr_t _cur_thread) { | |||
| 95 | 90 | ||
| 96 | // Pass the lock to the next owner. | 91 | // Pass the lock to the next owner. |
| 97 | uintptr_t next_tag = 0; | 92 | uintptr_t next_tag = 0; |
| 98 | if (next_owner) { | 93 | if (next_owner != nullptr) { |
| 99 | next_tag = reinterpret_cast<uintptr_t>(next_owner); | 94 | next_tag = reinterpret_cast<uintptr_t>(next_owner); |
| 100 | if (num_waiters > 1) { | 95 | if (num_waiters > 1) { |
| 101 | next_tag |= 0x1; | 96 | next_tag |= 0x1; |
| 102 | } | 97 | } |
| 103 | 98 | ||
| 104 | if (next_owner->GetState() == ThreadState::Waiting) { | 99 | next_owner->SetState(ThreadState::Runnable); |
| 105 | next_owner->SetState(ThreadState::Runnable); | ||
| 106 | } else { | ||
| 107 | KScheduler::SetSchedulerUpdateNeeded(kernel); | ||
| 108 | } | ||
| 109 | 100 | ||
| 110 | if (next_owner->IsSuspended()) { | 101 | if (next_owner->IsSuspended()) { |
| 111 | next_owner->ContinueIfHasKernelWaiters(); | 102 | next_owner->ContinueIfHasKernelWaiters(); |
diff --git a/src/core/hle/kernel/k_process.cpp b/src/core/hle/kernel/k_process.cpp index 06b8ce151..d1bd98051 100644 --- a/src/core/hle/kernel/k_process.cpp +++ b/src/core/hle/kernel/k_process.cpp | |||
| @@ -201,17 +201,15 @@ bool KProcess::ReleaseUserException(KThread* thread) { | |||
| 201 | 201 | ||
| 202 | // Remove waiter thread. | 202 | // Remove waiter thread. |
| 203 | s32 num_waiters{}; | 203 | s32 num_waiters{}; |
| 204 | KThread* next = thread->RemoveWaiterByKey( | 204 | if (KThread* next = thread->RemoveWaiterByKey( |
| 205 | std::addressof(num_waiters), | 205 | std::addressof(num_waiters), |
| 206 | reinterpret_cast<uintptr_t>(std::addressof(exception_thread))); | 206 | reinterpret_cast<uintptr_t>(std::addressof(exception_thread))); |
| 207 | if (next != nullptr) { | 207 | next != nullptr) { |
| 208 | if (next->GetState() == ThreadState::Waiting) { | 208 | next->SetState(ThreadState::Runnable); |
| 209 | next->SetState(ThreadState::Runnable); | ||
| 210 | } else { | ||
| 211 | KScheduler::SetSchedulerUpdateNeeded(kernel); | ||
| 212 | } | ||
| 213 | } | 209 | } |
| 214 | 210 | ||
| 211 | KScheduler::SetSchedulerUpdateNeeded(kernel); | ||
| 212 | |||
| 215 | return true; | 213 | return true; |
| 216 | } else { | 214 | } else { |
| 217 | return false; | 215 | return false; |
diff --git a/src/core/hle/kernel/k_readable_event.h b/src/core/hle/kernel/k_readable_event.h index b2850ac7b..149fa78dd 100644 --- a/src/core/hle/kernel/k_readable_event.h +++ b/src/core/hle/kernel/k_readable_event.h | |||
| @@ -21,9 +21,9 @@ public: | |||
| 21 | explicit KReadableEvent(KernelCore& kernel_); | 21 | explicit KReadableEvent(KernelCore& kernel_); |
| 22 | ~KReadableEvent() override; | 22 | ~KReadableEvent() override; |
| 23 | 23 | ||
| 24 | void Initialize(KEvent* parent_, std::string&& name_) { | 24 | void Initialize(KEvent* parent_event_, std::string&& name_) { |
| 25 | is_signaled = false; | 25 | is_signaled = false; |
| 26 | parent = parent_; | 26 | parent = parent_event_; |
| 27 | name = std::move(name_); | 27 | name = std::move(name_); |
| 28 | } | 28 | } |
| 29 | 29 | ||
diff --git a/src/core/hle/kernel/k_resource_limit.cpp b/src/core/hle/kernel/k_resource_limit.cpp index f91cb65dc..da88f35bc 100644 --- a/src/core/hle/kernel/k_resource_limit.cpp +++ b/src/core/hle/kernel/k_resource_limit.cpp | |||
| @@ -117,7 +117,7 @@ bool KResourceLimit::Reserve(LimitableResource which, s64 value, s64 timeout) { | |||
| 117 | if (current_hints[index] + value <= limit_values[index] && | 117 | if (current_hints[index] + value <= limit_values[index] && |
| 118 | (timeout < 0 || core_timing->GetGlobalTimeNs().count() < timeout)) { | 118 | (timeout < 0 || core_timing->GetGlobalTimeNs().count() < timeout)) { |
| 119 | waiter_count++; | 119 | waiter_count++; |
| 120 | cond_var.Wait(&lock, timeout); | 120 | cond_var.Wait(&lock, timeout, false); |
| 121 | waiter_count--; | 121 | waiter_count--; |
| 122 | } else { | 122 | } else { |
| 123 | break; | 123 | break; |
diff --git a/src/core/hle/kernel/k_server_port.cpp b/src/core/hle/kernel/k_server_port.cpp index 8cbde177a..c5dc58387 100644 --- a/src/core/hle/kernel/k_server_port.cpp +++ b/src/core/hle/kernel/k_server_port.cpp | |||
| @@ -17,9 +17,9 @@ namespace Kernel { | |||
| 17 | KServerPort::KServerPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} | 17 | KServerPort::KServerPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} |
| 18 | KServerPort::~KServerPort() = default; | 18 | KServerPort::~KServerPort() = default; |
| 19 | 19 | ||
| 20 | void KServerPort::Initialize(KPort* parent_, std::string&& name_) { | 20 | void KServerPort::Initialize(KPort* parent_port_, std::string&& name_) { |
| 21 | // Set member variables. | 21 | // Set member variables. |
| 22 | parent = parent_; | 22 | parent = parent_port_; |
| 23 | name = std::move(name_); | 23 | name = std::move(name_); |
| 24 | } | 24 | } |
| 25 | 25 | ||
diff --git a/src/core/hle/kernel/k_server_port.h b/src/core/hle/kernel/k_server_port.h index 55481d63f..67a36da40 100644 --- a/src/core/hle/kernel/k_server_port.h +++ b/src/core/hle/kernel/k_server_port.h | |||
| @@ -29,7 +29,7 @@ public: | |||
| 29 | explicit KServerPort(KernelCore& kernel_); | 29 | explicit KServerPort(KernelCore& kernel_); |
| 30 | ~KServerPort() override; | 30 | ~KServerPort() override; |
| 31 | 31 | ||
| 32 | void Initialize(KPort* parent_, std::string&& name_); | 32 | void Initialize(KPort* parent_port_, std::string&& name_); |
| 33 | 33 | ||
| 34 | /// Whether or not this server port has an HLE handler available. | 34 | /// Whether or not this server port has an HLE handler available. |
| 35 | bool HasSessionRequestHandler() const { | 35 | bool HasSessionRequestHandler() const { |
diff --git a/src/core/hle/kernel/k_server_session.cpp b/src/core/hle/kernel/k_server_session.cpp index dbf03b462..5c3c13ce6 100644 --- a/src/core/hle/kernel/k_server_session.cpp +++ b/src/core/hle/kernel/k_server_session.cpp | |||
| @@ -8,13 +8,16 @@ | |||
| 8 | #include "common/assert.h" | 8 | #include "common/assert.h" |
| 9 | #include "common/common_types.h" | 9 | #include "common/common_types.h" |
| 10 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 11 | #include "common/scope_exit.h" | ||
| 11 | #include "core/core_timing.h" | 12 | #include "core/core_timing.h" |
| 12 | #include "core/hle/ipc_helpers.h" | 13 | #include "core/hle/ipc_helpers.h" |
| 13 | #include "core/hle/kernel/hle_ipc.h" | 14 | #include "core/hle/kernel/hle_ipc.h" |
| 14 | #include "core/hle/kernel/k_client_port.h" | 15 | #include "core/hle/kernel/k_client_port.h" |
| 15 | #include "core/hle/kernel/k_handle_table.h" | 16 | #include "core/hle/kernel/k_handle_table.h" |
| 17 | #include "core/hle/kernel/k_port.h" | ||
| 16 | #include "core/hle/kernel/k_process.h" | 18 | #include "core/hle/kernel/k_process.h" |
| 17 | #include "core/hle/kernel/k_scheduler.h" | 19 | #include "core/hle/kernel/k_scheduler.h" |
| 20 | #include "core/hle/kernel/k_server_port.h" | ||
| 18 | #include "core/hle/kernel/k_server_session.h" | 21 | #include "core/hle/kernel/k_server_session.h" |
| 19 | #include "core/hle/kernel/k_session.h" | 22 | #include "core/hle/kernel/k_session.h" |
| 20 | #include "core/hle/kernel/k_thread.h" | 23 | #include "core/hle/kernel/k_thread.h" |
| @@ -23,18 +26,21 @@ | |||
| 23 | 26 | ||
| 24 | namespace Kernel { | 27 | namespace Kernel { |
| 25 | 28 | ||
| 26 | KServerSession::KServerSession(KernelCore& kernel_) | 29 | KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} |
| 27 | : KSynchronizationObject{kernel_}, manager{std::make_shared<SessionRequestManager>()} {} | ||
| 28 | 30 | ||
| 29 | KServerSession::~KServerSession() { | 31 | KServerSession::~KServerSession() {} |
| 30 | kernel.ReleaseServiceThread(service_thread); | ||
| 31 | } | ||
| 32 | 32 | ||
| 33 | void KServerSession::Initialize(KSession* parent_, std::string&& name_) { | 33 | void KServerSession::Initialize(KSession* parent_session_, std::string&& name_, |
| 34 | std::shared_ptr<SessionRequestManager> manager_) { | ||
| 34 | // Set member variables. | 35 | // Set member variables. |
| 35 | parent = parent_; | 36 | parent = parent_session_; |
| 36 | name = std::move(name_); | 37 | name = std::move(name_); |
| 37 | service_thread = kernel.CreateServiceThread(name); | 38 | |
| 39 | if (manager_) { | ||
| 40 | manager = manager_; | ||
| 41 | } else { | ||
| 42 | manager = std::make_shared<SessionRequestManager>(kernel); | ||
| 43 | } | ||
| 38 | } | 44 | } |
| 39 | 45 | ||
| 40 | void KServerSession::Destroy() { | 46 | void KServerSession::Destroy() { |
| @@ -114,9 +120,25 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor | |||
| 114 | 120 | ||
| 115 | context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); | 121 | context->PopulateFromIncomingCommandBuffer(kernel.CurrentProcess()->GetHandleTable(), cmd_buf); |
| 116 | 122 | ||
| 117 | if (auto strong_ptr = service_thread.lock()) { | 123 | // In the event that something fails here, stub a result to prevent the game from crashing. |
| 118 | strong_ptr->QueueSyncRequest(*parent, std::move(context)); | 124 | // This is a work-around in the event that somehow we process a service request after the |
| 119 | return ResultSuccess; | 125 | // session has been closed by the game. This has been observed to happen rarely in Pokemon |
| 126 | // Sword/Shield and is likely a result of us using host threads/scheduling for services. | ||
| 127 | // TODO(bunnei): Find a better solution here. | ||
| 128 | auto error_guard = SCOPE_GUARD({ CompleteSyncRequest(*context); }); | ||
| 129 | |||
| 130 | // Ensure we have a session request handler | ||
| 131 | if (manager->HasSessionRequestHandler(*context)) { | ||
| 132 | if (auto strong_ptr = manager->GetServiceThread().lock()) { | ||
| 133 | strong_ptr->QueueSyncRequest(*parent, std::move(context)); | ||
| 134 | |||
| 135 | // We succeeded. | ||
| 136 | error_guard.Cancel(); | ||
| 137 | } else { | ||
| 138 | ASSERT_MSG(false, "strong_ptr is nullptr!"); | ||
| 139 | } | ||
| 140 | } else { | ||
| 141 | ASSERT_MSG(false, "handler is invalid!"); | ||
| 120 | } | 142 | } |
| 121 | 143 | ||
| 122 | return ResultSuccess; | 144 | return ResultSuccess; |
| @@ -124,13 +146,20 @@ ResultCode KServerSession::QueueSyncRequest(KThread* thread, Core::Memory::Memor | |||
| 124 | 146 | ||
| 125 | ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) { | 147 | ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) { |
| 126 | ResultCode result = ResultSuccess; | 148 | ResultCode result = ResultSuccess; |
| 149 | |||
| 127 | // If the session has been converted to a domain, handle the domain request | 150 | // If the session has been converted to a domain, handle the domain request |
| 128 | if (IsDomain() && context.HasDomainMessageHeader()) { | 151 | if (manager->HasSessionRequestHandler(context)) { |
| 129 | result = HandleDomainSyncRequest(context); | 152 | if (IsDomain() && context.HasDomainMessageHeader()) { |
| 130 | // If there is no domain header, the regular session handler is used | 153 | result = HandleDomainSyncRequest(context); |
| 131 | } else if (manager->HasSessionHandler()) { | 154 | // If there is no domain header, the regular session handler is used |
| 132 | // If this ServerSession has an associated HLE handler, forward the request to it. | 155 | } else if (manager->HasSessionHandler()) { |
| 133 | result = manager->SessionHandler().HandleSyncRequest(*this, context); | 156 | // If this ServerSession has an associated HLE handler, forward the request to it. |
| 157 | result = manager->SessionHandler().HandleSyncRequest(*this, context); | ||
| 158 | } | ||
| 159 | } else { | ||
| 160 | ASSERT_MSG(false, "Session handler is invalid, stubbing response!"); | ||
| 161 | IPC::ResponseBuilder rb(context, 2); | ||
| 162 | rb.Push(ResultSuccess); | ||
| 134 | } | 163 | } |
| 135 | 164 | ||
| 136 | if (convert_to_domain) { | 165 | if (convert_to_domain) { |
diff --git a/src/core/hle/kernel/k_server_session.h b/src/core/hle/kernel/k_server_session.h index 27b757ad2..d44bc9d4f 100644 --- a/src/core/hle/kernel/k_server_session.h +++ b/src/core/hle/kernel/k_server_session.h | |||
| @@ -32,6 +32,7 @@ class HLERequestContext; | |||
| 32 | class KernelCore; | 32 | class KernelCore; |
| 33 | class KSession; | 33 | class KSession; |
| 34 | class SessionRequestHandler; | 34 | class SessionRequestHandler; |
| 35 | class SessionRequestManager; | ||
| 35 | class KThread; | 36 | class KThread; |
| 36 | 37 | ||
| 37 | class KServerSession final : public KSynchronizationObject, | 38 | class KServerSession final : public KSynchronizationObject, |
| @@ -46,7 +47,8 @@ public: | |||
| 46 | 47 | ||
| 47 | void Destroy() override; | 48 | void Destroy() override; |
| 48 | 49 | ||
| 49 | void Initialize(KSession* parent_, std::string&& name_); | 50 | void Initialize(KSession* parent_session_, std::string&& name_, |
| 51 | std::shared_ptr<SessionRequestManager> manager_); | ||
| 50 | 52 | ||
| 51 | KSession* GetParent() { | 53 | KSession* GetParent() { |
| 52 | return parent; | 54 | return parent; |
| @@ -60,15 +62,14 @@ public: | |||
| 60 | 62 | ||
| 61 | void OnClientClosed(); | 63 | void OnClientClosed(); |
| 62 | 64 | ||
| 63 | /** | 65 | void ClientConnected(SessionRequestHandlerPtr handler) { |
| 64 | * Sets the HLE handler for the session. This handler will be called to service IPC requests | ||
| 65 | * instead of the regular IPC machinery. (The regular IPC machinery is currently not | ||
| 66 | * implemented.) | ||
| 67 | */ | ||
| 68 | void SetSessionHandler(SessionRequestHandlerPtr handler) { | ||
| 69 | manager->SetSessionHandler(std::move(handler)); | 66 | manager->SetSessionHandler(std::move(handler)); |
| 70 | } | 67 | } |
| 71 | 68 | ||
| 69 | void ClientDisconnected() { | ||
| 70 | manager = nullptr; | ||
| 71 | } | ||
| 72 | |||
| 72 | /** | 73 | /** |
| 73 | * Handle a sync request from the emulated application. | 74 | * Handle a sync request from the emulated application. |
| 74 | * | 75 | * |
| @@ -104,16 +105,6 @@ public: | |||
| 104 | return manager; | 105 | return manager; |
| 105 | } | 106 | } |
| 106 | 107 | ||
| 107 | /// Gets the session request manager, which forwards requests to the underlying service | ||
| 108 | const std::shared_ptr<SessionRequestManager>& GetSessionRequestManager() const { | ||
| 109 | return manager; | ||
| 110 | } | ||
| 111 | |||
| 112 | /// Sets the session request manager, which forwards requests to the underlying service | ||
| 113 | void SetSessionRequestManager(std::shared_ptr<SessionRequestManager> manager_) { | ||
| 114 | manager = std::move(manager_); | ||
| 115 | } | ||
| 116 | |||
| 117 | private: | 108 | private: |
| 118 | /// Queues a sync request from the emulated application. | 109 | /// Queues a sync request from the emulated application. |
| 119 | ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory); | 110 | ResultCode QueueSyncRequest(KThread* thread, Core::Memory::Memory& memory); |
| @@ -131,9 +122,6 @@ private: | |||
| 131 | /// When set to True, converts the session to a domain at the end of the command | 122 | /// When set to True, converts the session to a domain at the end of the command |
| 132 | bool convert_to_domain{}; | 123 | bool convert_to_domain{}; |
| 133 | 124 | ||
| 134 | /// Thread to dispatch service requests | ||
| 135 | std::weak_ptr<ServiceThread> service_thread; | ||
| 136 | |||
| 137 | /// KSession that owns this KServerSession | 125 | /// KSession that owns this KServerSession |
| 138 | KSession* parent{}; | 126 | KSession* parent{}; |
| 139 | }; | 127 | }; |
diff --git a/src/core/hle/kernel/k_session.cpp b/src/core/hle/kernel/k_session.cpp index 025b8b555..940878e03 100644 --- a/src/core/hle/kernel/k_session.cpp +++ b/src/core/hle/kernel/k_session.cpp | |||
| @@ -15,7 +15,8 @@ KSession::KSession(KernelCore& kernel_) | |||
| 15 | : KAutoObjectWithSlabHeapAndContainer{kernel_}, server{kernel_}, client{kernel_} {} | 15 | : KAutoObjectWithSlabHeapAndContainer{kernel_}, server{kernel_}, client{kernel_} {} |
| 16 | KSession::~KSession() = default; | 16 | KSession::~KSession() = default; |
| 17 | 17 | ||
| 18 | void KSession::Initialize(KClientPort* port_, const std::string& name_) { | 18 | void KSession::Initialize(KClientPort* port_, const std::string& name_, |
| 19 | std::shared_ptr<SessionRequestManager> manager_) { | ||
| 19 | // Increment reference count. | 20 | // Increment reference count. |
| 20 | // Because reference count is one on creation, this will result | 21 | // Because reference count is one on creation, this will result |
| 21 | // in a reference count of two. Thus, when both server and client are closed | 22 | // in a reference count of two. Thus, when both server and client are closed |
| @@ -27,7 +28,7 @@ void KSession::Initialize(KClientPort* port_, const std::string& name_) { | |||
| 27 | KAutoObject::Create(std::addressof(client)); | 28 | KAutoObject::Create(std::addressof(client)); |
| 28 | 29 | ||
| 29 | // Initialize our sub sessions. | 30 | // Initialize our sub sessions. |
| 30 | server.Initialize(this, name_ + ":Server"); | 31 | server.Initialize(this, name_ + ":Server", manager_); |
| 31 | client.Initialize(this, name_ + ":Client"); | 32 | client.Initialize(this, name_ + ":Client"); |
| 32 | 33 | ||
| 33 | // Set state and name. | 34 | // Set state and name. |
diff --git a/src/core/hle/kernel/k_session.h b/src/core/hle/kernel/k_session.h index 4ddd080d2..62c328a68 100644 --- a/src/core/hle/kernel/k_session.h +++ b/src/core/hle/kernel/k_session.h | |||
| @@ -13,6 +13,8 @@ | |||
| 13 | 13 | ||
| 14 | namespace Kernel { | 14 | namespace Kernel { |
| 15 | 15 | ||
| 16 | class SessionRequestManager; | ||
| 17 | |||
| 16 | class KSession final : public KAutoObjectWithSlabHeapAndContainer<KSession, KAutoObjectWithList> { | 18 | class KSession final : public KAutoObjectWithSlabHeapAndContainer<KSession, KAutoObjectWithList> { |
| 17 | KERNEL_AUTOOBJECT_TRAITS(KSession, KAutoObject); | 19 | KERNEL_AUTOOBJECT_TRAITS(KSession, KAutoObject); |
| 18 | 20 | ||
| @@ -20,7 +22,8 @@ public: | |||
| 20 | explicit KSession(KernelCore& kernel_); | 22 | explicit KSession(KernelCore& kernel_); |
| 21 | ~KSession() override; | 23 | ~KSession() override; |
| 22 | 24 | ||
| 23 | void Initialize(KClientPort* port_, const std::string& name_); | 25 | void Initialize(KClientPort* port_, const std::string& name_, |
| 26 | std::shared_ptr<SessionRequestManager> manager_ = nullptr); | ||
| 24 | 27 | ||
| 25 | void Finalize() override; | 28 | void Finalize() override; |
| 26 | 29 | ||
diff --git a/src/core/hle/kernel/k_writable_event.cpp b/src/core/hle/kernel/k_writable_event.cpp index b7b83c151..bdb1db6d5 100644 --- a/src/core/hle/kernel/k_writable_event.cpp +++ b/src/core/hle/kernel/k_writable_event.cpp | |||
| @@ -13,8 +13,8 @@ KWritableEvent::KWritableEvent(KernelCore& kernel_) | |||
| 13 | 13 | ||
| 14 | KWritableEvent::~KWritableEvent() = default; | 14 | KWritableEvent::~KWritableEvent() = default; |
| 15 | 15 | ||
| 16 | void KWritableEvent::Initialize(KEvent* parent_, std::string&& name_) { | 16 | void KWritableEvent::Initialize(KEvent* parent_event_, std::string&& name_) { |
| 17 | parent = parent_; | 17 | parent = parent_event_; |
| 18 | name = std::move(name_); | 18 | name = std::move(name_); |
| 19 | parent->GetReadableEvent().Open(); | 19 | parent->GetReadableEvent().Open(); |
| 20 | } | 20 | } |
diff --git a/src/core/hle/kernel/kernel.cpp b/src/core/hle/kernel/kernel.cpp index 0ffb78d51..2ceeaeb5f 100644 --- a/src/core/hle/kernel/kernel.cpp +++ b/src/core/hle/kernel/kernel.cpp | |||
| @@ -63,8 +63,6 @@ struct KernelCore::Impl { | |||
| 63 | global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel); | 63 | global_scheduler_context = std::make_unique<Kernel::GlobalSchedulerContext>(kernel); |
| 64 | global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel); | 64 | global_handle_table = std::make_unique<Kernel::KHandleTable>(kernel); |
| 65 | 65 | ||
| 66 | service_thread_manager = | ||
| 67 | std::make_unique<Common::ThreadWorker>(1, "yuzu:ServiceThreadManager"); | ||
| 68 | is_phantom_mode_for_singlecore = false; | 66 | is_phantom_mode_for_singlecore = false; |
| 69 | 67 | ||
| 70 | InitializePhysicalCores(); | 68 | InitializePhysicalCores(); |
| @@ -96,7 +94,6 @@ struct KernelCore::Impl { | |||
| 96 | process_list.clear(); | 94 | process_list.clear(); |
| 97 | 95 | ||
| 98 | // Ensures all service threads gracefully shutdown | 96 | // Ensures all service threads gracefully shutdown |
| 99 | service_thread_manager.reset(); | ||
| 100 | service_threads.clear(); | 97 | service_threads.clear(); |
| 101 | 98 | ||
| 102 | next_object_id = 0; | 99 | next_object_id = 0; |
| @@ -680,10 +677,6 @@ struct KernelCore::Impl { | |||
| 680 | // Threads used for services | 677 | // Threads used for services |
| 681 | std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads; | 678 | std::unordered_set<std::shared_ptr<Kernel::ServiceThread>> service_threads; |
| 682 | 679 | ||
| 683 | // Service threads are managed by a worker thread, so that a calling service thread can queue up | ||
| 684 | // the release of itself | ||
| 685 | std::unique_ptr<Common::ThreadWorker> service_thread_manager; | ||
| 686 | |||
| 687 | std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads; | 680 | std::array<KThread*, Core::Hardware::NUM_CPU_CORES> suspend_threads; |
| 688 | std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{}; | 681 | std::array<Core::CPUInterruptHandler, Core::Hardware::NUM_CPU_CORES> interrupts{}; |
| 689 | std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; | 682 | std::array<std::unique_ptr<Kernel::KScheduler>, Core::Hardware::NUM_CPU_CORES> schedulers{}; |
| @@ -986,17 +979,14 @@ void KernelCore::ExitSVCProfile() { | |||
| 986 | 979 | ||
| 987 | std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) { | 980 | std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) { |
| 988 | auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name); | 981 | auto service_thread = std::make_shared<Kernel::ServiceThread>(*this, 1, name); |
| 989 | impl->service_thread_manager->QueueWork( | 982 | impl->service_threads.emplace(service_thread); |
| 990 | [this, service_thread] { impl->service_threads.emplace(service_thread); }); | ||
| 991 | return service_thread; | 983 | return service_thread; |
| 992 | } | 984 | } |
| 993 | 985 | ||
| 994 | void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) { | 986 | void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) { |
| 995 | impl->service_thread_manager->QueueWork([this, service_thread] { | 987 | if (auto strong_ptr = service_thread.lock()) { |
| 996 | if (auto strong_ptr = service_thread.lock()) { | 988 | impl->service_threads.erase(strong_ptr); |
| 997 | impl->service_threads.erase(strong_ptr); | 989 | } |
| 998 | } | ||
| 999 | }); | ||
| 1000 | } | 990 | } |
| 1001 | 991 | ||
| 1002 | Init::KSlabResourceCounts& KernelCore::SlabResourceCounts() { | 992 | Init::KSlabResourceCounts& KernelCore::SlabResourceCounts() { |
diff --git a/src/core/hle/kernel/svc.cpp b/src/core/hle/kernel/svc.cpp index 28bcae6e7..8339e11a0 100644 --- a/src/core/hle/kernel/svc.cpp +++ b/src/core/hle/kernel/svc.cpp | |||
| @@ -449,8 +449,8 @@ static ResultCode CancelSynchronization(Core::System& system, Handle handle) { | |||
| 449 | 449 | ||
| 450 | // Get the thread from its handle. | 450 | // Get the thread from its handle. |
| 451 | KScopedAutoObject thread = | 451 | KScopedAutoObject thread = |
| 452 | system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>( | 452 | system.Kernel().CurrentProcess()->GetHandleTable().GetObject<KThread>(handle); |
| 453 | static_cast<Handle>(handle)); | 453 | R_UNLESS(thread.IsNotNull(), ResultInvalidHandle); |
| 454 | 454 | ||
| 455 | // Cancel the thread's wait. | 455 | // Cancel the thread's wait. |
| 456 | thread->WaitCancel(); | 456 | thread->WaitCancel(); |
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 605236552..a755008d5 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -124,21 +124,21 @@ union ResultCode { | |||
| 124 | constexpr ResultCode(ErrorModule module_, u32 description_) | 124 | constexpr ResultCode(ErrorModule module_, u32 description_) |
| 125 | : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} | 125 | : raw(module.FormatValue(module_) | description.FormatValue(description_)) {} |
| 126 | 126 | ||
| 127 | constexpr bool IsSuccess() const { | 127 | [[nodiscard]] constexpr bool IsSuccess() const { |
| 128 | return raw == 0; | 128 | return raw == 0; |
| 129 | } | 129 | } |
| 130 | 130 | ||
| 131 | constexpr bool IsError() const { | 131 | [[nodiscard]] constexpr bool IsError() const { |
| 132 | return raw != 0; | 132 | return !IsSuccess(); |
| 133 | } | 133 | } |
| 134 | }; | 134 | }; |
| 135 | 135 | ||
| 136 | constexpr bool operator==(const ResultCode& a, const ResultCode& b) { | 136 | [[nodiscard]] constexpr bool operator==(const ResultCode& a, const ResultCode& b) { |
| 137 | return a.raw == b.raw; | 137 | return a.raw == b.raw; |
| 138 | } | 138 | } |
| 139 | 139 | ||
| 140 | constexpr bool operator!=(const ResultCode& a, const ResultCode& b) { | 140 | [[nodiscard]] constexpr bool operator!=(const ResultCode& a, const ResultCode& b) { |
| 141 | return a.raw != b.raw; | 141 | return !operator==(a, b); |
| 142 | } | 142 | } |
| 143 | 143 | ||
| 144 | // Convenience functions for creating some common kinds of errors: | 144 | // Convenience functions for creating some common kinds of errors: |
| @@ -200,7 +200,7 @@ public: | |||
| 200 | * specify the success code. `success_code` must not be an error code. | 200 | * specify the success code. `success_code` must not be an error code. |
| 201 | */ | 201 | */ |
| 202 | template <typename... Args> | 202 | template <typename... Args> |
| 203 | static ResultVal WithCode(ResultCode success_code, Args&&... args) { | 203 | [[nodiscard]] static ResultVal WithCode(ResultCode success_code, Args&&... args) { |
| 204 | ResultVal<T> result; | 204 | ResultVal<T> result; |
| 205 | result.emplace(success_code, std::forward<Args>(args)...); | 205 | result.emplace(success_code, std::forward<Args>(args)...); |
| 206 | return result; | 206 | return result; |
| @@ -259,49 +259,49 @@ public: | |||
| 259 | } | 259 | } |
| 260 | 260 | ||
| 261 | /// Returns true if the `ResultVal` contains an error code and no value. | 261 | /// Returns true if the `ResultVal` contains an error code and no value. |
| 262 | bool empty() const { | 262 | [[nodiscard]] bool empty() const { |
| 263 | return result_code.IsError(); | 263 | return result_code.IsError(); |
| 264 | } | 264 | } |
| 265 | 265 | ||
| 266 | /// Returns true if the `ResultVal` contains a return value. | 266 | /// Returns true if the `ResultVal` contains a return value. |
| 267 | bool Succeeded() const { | 267 | [[nodiscard]] bool Succeeded() const { |
| 268 | return result_code.IsSuccess(); | 268 | return result_code.IsSuccess(); |
| 269 | } | 269 | } |
| 270 | /// Returns true if the `ResultVal` contains an error code and no value. | 270 | /// Returns true if the `ResultVal` contains an error code and no value. |
| 271 | bool Failed() const { | 271 | [[nodiscard]] bool Failed() const { |
| 272 | return empty(); | 272 | return empty(); |
| 273 | } | 273 | } |
| 274 | 274 | ||
| 275 | ResultCode Code() const { | 275 | [[nodiscard]] ResultCode Code() const { |
| 276 | return result_code; | 276 | return result_code; |
| 277 | } | 277 | } |
| 278 | 278 | ||
| 279 | const T& operator*() const { | 279 | [[nodiscard]] const T& operator*() const { |
| 280 | return object; | 280 | return object; |
| 281 | } | 281 | } |
| 282 | T& operator*() { | 282 | [[nodiscard]] T& operator*() { |
| 283 | return object; | 283 | return object; |
| 284 | } | 284 | } |
| 285 | const T* operator->() const { | 285 | [[nodiscard]] const T* operator->() const { |
| 286 | return &object; | 286 | return &object; |
| 287 | } | 287 | } |
| 288 | T* operator->() { | 288 | [[nodiscard]] T* operator->() { |
| 289 | return &object; | 289 | return &object; |
| 290 | } | 290 | } |
| 291 | 291 | ||
| 292 | /// Returns the value contained in this `ResultVal`, or the supplied default if it is missing. | 292 | /// Returns the value contained in this `ResultVal`, or the supplied default if it is missing. |
| 293 | template <typename U> | 293 | template <typename U> |
| 294 | T ValueOr(U&& value) const { | 294 | [[nodiscard]] T ValueOr(U&& value) const { |
| 295 | return !empty() ? object : std::move(value); | 295 | return !empty() ? object : std::move(value); |
| 296 | } | 296 | } |
| 297 | 297 | ||
| 298 | /// Asserts that the result succeeded and returns a reference to it. | 298 | /// Asserts that the result succeeded and returns a reference to it. |
| 299 | T& Unwrap() & { | 299 | [[nodiscard]] T& Unwrap() & { |
| 300 | ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); | 300 | ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); |
| 301 | return **this; | 301 | return **this; |
| 302 | } | 302 | } |
| 303 | 303 | ||
| 304 | T&& Unwrap() && { | 304 | [[nodiscard]] T&& Unwrap() && { |
| 305 | ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); | 305 | ASSERT_MSG(Succeeded(), "Tried to Unwrap empty ResultVal"); |
| 306 | return std::move(**this); | 306 | return std::move(**this); |
| 307 | } | 307 | } |
| @@ -320,7 +320,7 @@ private: | |||
| 320 | * `T` with and creates a success `ResultVal` contained the constructed value. | 320 | * `T` with and creates a success `ResultVal` contained the constructed value. |
| 321 | */ | 321 | */ |
| 322 | template <typename T, typename... Args> | 322 | template <typename T, typename... Args> |
| 323 | ResultVal<T> MakeResult(Args&&... args) { | 323 | [[nodiscard]] ResultVal<T> MakeResult(Args&&... args) { |
| 324 | return ResultVal<T>::WithCode(ResultSuccess, std::forward<Args>(args)...); | 324 | return ResultVal<T>::WithCode(ResultSuccess, std::forward<Args>(args)...); |
| 325 | } | 325 | } |
| 326 | 326 | ||
| @@ -329,7 +329,7 @@ ResultVal<T> MakeResult(Args&&... args) { | |||
| 329 | * copy or move constructing. | 329 | * copy or move constructing. |
| 330 | */ | 330 | */ |
| 331 | template <typename Arg> | 331 | template <typename Arg> |
| 332 | ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) { | 332 | [[nodiscard]] ResultVal<std::remove_reference_t<Arg>> MakeResult(Arg&& arg) { |
| 333 | return ResultVal<std::remove_reference_t<Arg>>::WithCode(ResultSuccess, std::forward<Arg>(arg)); | 333 | return ResultVal<std::remove_reference_t<Arg>>::WithCode(ResultSuccess, std::forward<Arg>(arg)); |
| 334 | } | 334 | } |
| 335 | 335 | ||
diff --git a/src/core/hle/service/bcat/backend/boxcat.cpp b/src/core/hle/service/bcat/backend/boxcat.cpp index d9fdc2dca..a2844ea8c 100644 --- a/src/core/hle/service/bcat/backend/boxcat.cpp +++ b/src/core/hle/service/bcat/backend/boxcat.cpp | |||
| @@ -19,7 +19,6 @@ | |||
| 19 | #include "common/fs/fs.h" | 19 | #include "common/fs/fs.h" |
| 20 | #include "common/fs/path_util.h" | 20 | #include "common/fs/path_util.h" |
| 21 | #include "common/hex_util.h" | 21 | #include "common/hex_util.h" |
| 22 | #include "common/logging/backend.h" | ||
| 23 | #include "common/logging/log.h" | 22 | #include "common/logging/log.h" |
| 24 | #include "common/settings.h" | 23 | #include "common/settings.h" |
| 25 | #include "core/core.h" | 24 | #include "core/core.h" |
diff --git a/src/core/hle/service/filesystem/fsp_srv.cpp b/src/core/hle/service/filesystem/fsp_srv.cpp index 3af9881c2..db4d44c12 100644 --- a/src/core/hle/service/filesystem/fsp_srv.cpp +++ b/src/core/hle/service/filesystem/fsp_srv.cpp | |||
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "common/hex_util.h" | 14 | #include "common/hex_util.h" |
| 15 | #include "common/logging/log.h" | 15 | #include "common/logging/log.h" |
| 16 | #include "common/settings.h" | ||
| 16 | #include "common/string_util.h" | 17 | #include "common/string_util.h" |
| 17 | #include "core/core.h" | 18 | #include "core/core.h" |
| 18 | #include "core/file_sys/directory.h" | 19 | #include "core/file_sys/directory.h" |
| @@ -785,6 +786,10 @@ FSP_SRV::FSP_SRV(Core::System& system_) | |||
| 785 | }; | 786 | }; |
| 786 | // clang-format on | 787 | // clang-format on |
| 787 | RegisterHandlers(functions); | 788 | RegisterHandlers(functions); |
| 789 | |||
| 790 | if (Settings::values.enable_fs_access_log) { | ||
| 791 | access_log_mode = AccessLogMode::SdCard; | ||
| 792 | } | ||
| 788 | } | 793 | } |
| 789 | 794 | ||
| 790 | FSP_SRV::~FSP_SRV() = default; | 795 | FSP_SRV::~FSP_SRV() = default; |
| @@ -1041,9 +1046,9 @@ void FSP_SRV::DisableAutoSaveDataCreation(Kernel::HLERequestContext& ctx) { | |||
| 1041 | 1046 | ||
| 1042 | void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { | 1047 | void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { |
| 1043 | IPC::RequestParser rp{ctx}; | 1048 | IPC::RequestParser rp{ctx}; |
| 1044 | log_mode = rp.PopEnum<LogMode>(); | 1049 | access_log_mode = rp.PopEnum<AccessLogMode>(); |
| 1045 | 1050 | ||
| 1046 | LOG_DEBUG(Service_FS, "called, log_mode={:08X}", log_mode); | 1051 | LOG_DEBUG(Service_FS, "called, access_log_mode={}", access_log_mode); |
| 1047 | 1052 | ||
| 1048 | IPC::ResponseBuilder rb{ctx, 2}; | 1053 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1049 | rb.Push(ResultSuccess); | 1054 | rb.Push(ResultSuccess); |
| @@ -1054,7 +1059,7 @@ void FSP_SRV::GetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { | |||
| 1054 | 1059 | ||
| 1055 | IPC::ResponseBuilder rb{ctx, 3}; | 1060 | IPC::ResponseBuilder rb{ctx, 3}; |
| 1056 | rb.Push(ResultSuccess); | 1061 | rb.Push(ResultSuccess); |
| 1057 | rb.PushEnum(log_mode); | 1062 | rb.PushEnum(access_log_mode); |
| 1058 | } | 1063 | } |
| 1059 | 1064 | ||
| 1060 | void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { | 1065 | void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { |
| @@ -1062,9 +1067,9 @@ void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { | |||
| 1062 | auto log = Common::StringFromFixedZeroTerminatedBuffer( | 1067 | auto log = Common::StringFromFixedZeroTerminatedBuffer( |
| 1063 | reinterpret_cast<const char*>(raw.data()), raw.size()); | 1068 | reinterpret_cast<const char*>(raw.data()), raw.size()); |
| 1064 | 1069 | ||
| 1065 | LOG_DEBUG(Service_FS, "called, log='{}'", log); | 1070 | LOG_DEBUG(Service_FS, "called"); |
| 1066 | 1071 | ||
| 1067 | reporter.SaveFilesystemAccessReport(log_mode, std::move(log)); | 1072 | reporter.SaveFSAccessLog(log); |
| 1068 | 1073 | ||
| 1069 | IPC::ResponseBuilder rb{ctx, 2}; | 1074 | IPC::ResponseBuilder rb{ctx, 2}; |
| 1070 | rb.Push(ResultSuccess); | 1075 | rb.Push(ResultSuccess); |
diff --git a/src/core/hle/service/filesystem/fsp_srv.h b/src/core/hle/service/filesystem/fsp_srv.h index ff7455a20..556708284 100644 --- a/src/core/hle/service/filesystem/fsp_srv.h +++ b/src/core/hle/service/filesystem/fsp_srv.h | |||
| @@ -24,11 +24,10 @@ enum class AccessLogVersion : u32 { | |||
| 24 | Latest = V7_0_0, | 24 | Latest = V7_0_0, |
| 25 | }; | 25 | }; |
| 26 | 26 | ||
| 27 | enum class LogMode : u32 { | 27 | enum class AccessLogMode : u32 { |
| 28 | Off, | 28 | None, |
| 29 | Log, | 29 | Log, |
| 30 | RedirectToSdCard, | 30 | SdCard, |
| 31 | LogToSdCard = Log | RedirectToSdCard, | ||
| 32 | }; | 31 | }; |
| 33 | 32 | ||
| 34 | class FSP_SRV final : public ServiceFramework<FSP_SRV> { | 33 | class FSP_SRV final : public ServiceFramework<FSP_SRV> { |
| @@ -59,13 +58,12 @@ private: | |||
| 59 | 58 | ||
| 60 | FileSystemController& fsc; | 59 | FileSystemController& fsc; |
| 61 | const FileSys::ContentProvider& content_provider; | 60 | const FileSys::ContentProvider& content_provider; |
| 61 | const Core::Reporter& reporter; | ||
| 62 | 62 | ||
| 63 | FileSys::VirtualFile romfs; | 63 | FileSys::VirtualFile romfs; |
| 64 | u64 current_process_id = 0; | 64 | u64 current_process_id = 0; |
| 65 | u32 access_log_program_index = 0; | 65 | u32 access_log_program_index = 0; |
| 66 | LogMode log_mode = LogMode::LogToSdCard; | 66 | AccessLogMode access_log_mode = AccessLogMode::None; |
| 67 | |||
| 68 | const Core::Reporter& reporter; | ||
| 69 | }; | 67 | }; |
| 70 | 68 | ||
| 71 | } // namespace Service::FileSystem | 69 | } // namespace Service::FileSystem |
diff --git a/src/core/hle/service/hid/hid.cpp b/src/core/hle/service/hid/hid.cpp index fa6213d3c..d68b023d0 100644 --- a/src/core/hle/service/hid/hid.cpp +++ b/src/core/hle/service/hid/hid.cpp | |||
| @@ -236,7 +236,7 @@ Hid::Hid(Core::System& system_) : ServiceFramework{system_, "hid"} { | |||
| 236 | {80, &Hid::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"}, | 236 | {80, &Hid::GetGyroscopeZeroDriftMode, "GetGyroscopeZeroDriftMode"}, |
| 237 | {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"}, | 237 | {81, &Hid::ResetGyroscopeZeroDriftMode, "ResetGyroscopeZeroDriftMode"}, |
| 238 | {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, | 238 | {82, &Hid::IsSixAxisSensorAtRest, "IsSixAxisSensorAtRest"}, |
| 239 | {83, nullptr, "IsFirmwareUpdateAvailableForSixAxisSensor"}, | 239 | {83, &Hid::IsFirmwareUpdateAvailableForSixAxisSensor, "IsFirmwareUpdateAvailableForSixAxisSensor"}, |
| 240 | {91, &Hid::ActivateGesture, "ActivateGesture"}, | 240 | {91, &Hid::ActivateGesture, "ActivateGesture"}, |
| 241 | {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, | 241 | {100, &Hid::SetSupportedNpadStyleSet, "SetSupportedNpadStyleSet"}, |
| 242 | {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, | 242 | {101, &Hid::GetSupportedNpadStyleSet, "GetSupportedNpadStyleSet"}, |
| @@ -710,6 +710,27 @@ void Hid::IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx) { | |||
| 710 | .IsSixAxisSensorAtRest()); | 710 | .IsSixAxisSensorAtRest()); |
| 711 | } | 711 | } |
| 712 | 712 | ||
| 713 | void Hid::IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx) { | ||
| 714 | IPC::RequestParser rp{ctx}; | ||
| 715 | struct Parameters { | ||
| 716 | Controller_NPad::DeviceHandle sixaxis_handle; | ||
| 717 | INSERT_PADDING_WORDS_NOINIT(1); | ||
| 718 | u64 applet_resource_user_id; | ||
| 719 | }; | ||
| 720 | |||
| 721 | const auto parameters{rp.PopRaw<Parameters>()}; | ||
| 722 | |||
| 723 | LOG_WARNING( | ||
| 724 | Service_HID, | ||
| 725 | "(STUBBED) called, npad_type={}, npad_id={}, device_index={}, applet_resource_user_id={}", | ||
| 726 | parameters.sixaxis_handle.npad_type, parameters.sixaxis_handle.npad_id, | ||
| 727 | parameters.sixaxis_handle.device_index, parameters.applet_resource_user_id); | ||
| 728 | |||
| 729 | IPC::ResponseBuilder rb{ctx, 3}; | ||
| 730 | rb.Push(ResultSuccess); | ||
| 731 | rb.Push(false); | ||
| 732 | } | ||
| 733 | |||
| 713 | void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { | 734 | void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { |
| 714 | IPC::RequestParser rp{ctx}; | 735 | IPC::RequestParser rp{ctx}; |
| 715 | struct Parameters { | 736 | struct Parameters { |
diff --git a/src/core/hle/service/hid/hid.h b/src/core/hle/service/hid/hid.h index aa3307955..83fc2ea1d 100644 --- a/src/core/hle/service/hid/hid.h +++ b/src/core/hle/service/hid/hid.h | |||
| @@ -100,6 +100,7 @@ private: | |||
| 100 | void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); | 100 | void GetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); |
| 101 | void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); | 101 | void ResetGyroscopeZeroDriftMode(Kernel::HLERequestContext& ctx); |
| 102 | void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); | 102 | void IsSixAxisSensorAtRest(Kernel::HLERequestContext& ctx); |
| 103 | void IsFirmwareUpdateAvailableForSixAxisSensor(Kernel::HLERequestContext& ctx); | ||
| 103 | void ActivateGesture(Kernel::HLERequestContext& ctx); | 104 | void ActivateGesture(Kernel::HLERequestContext& ctx); |
| 104 | void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); | 105 | void SetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); |
| 105 | void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); | 106 | void GetSupportedNpadStyleSet(Kernel::HLERequestContext& ctx); |
diff --git a/src/core/hle/service/lm/lm.cpp b/src/core/hle/service/lm/lm.cpp index 311e4fb2d..794504314 100644 --- a/src/core/hle/service/lm/lm.cpp +++ b/src/core/hle/service/lm/lm.cpp | |||
| @@ -51,6 +51,24 @@ struct hash<Service::LM::LogPacketHeaderEntry> { | |||
| 51 | } // namespace std | 51 | } // namespace std |
| 52 | 52 | ||
| 53 | namespace Service::LM { | 53 | namespace Service::LM { |
| 54 | namespace { | ||
| 55 | std::string_view NameOf(LogSeverity severity) { | ||
| 56 | switch (severity) { | ||
| 57 | case LogSeverity::Trace: | ||
| 58 | return "TRACE"; | ||
| 59 | case LogSeverity::Info: | ||
| 60 | return "INFO"; | ||
| 61 | case LogSeverity::Warning: | ||
| 62 | return "WARNING"; | ||
| 63 | case LogSeverity::Error: | ||
| 64 | return "ERROR"; | ||
| 65 | case LogSeverity::Fatal: | ||
| 66 | return "FATAL"; | ||
| 67 | default: | ||
| 68 | return "UNKNOWN"; | ||
| 69 | } | ||
| 70 | } | ||
| 71 | } // Anonymous namespace | ||
| 54 | 72 | ||
| 55 | enum class LogDestination : u32 { | 73 | enum class LogDestination : u32 { |
| 56 | TargetManager = 1 << 0, | 74 | TargetManager = 1 << 0, |
| @@ -262,33 +280,8 @@ private: | |||
| 262 | if (text_log) { | 280 | if (text_log) { |
| 263 | output_log += fmt::format("Log Text: {}\n", *text_log); | 281 | output_log += fmt::format("Log Text: {}\n", *text_log); |
| 264 | } | 282 | } |
| 265 | 283 | LOG_DEBUG(Service_LM, "LogManager {} ({}):\n{}", NameOf(entry.severity), | |
| 266 | switch (entry.severity) { | 284 | DestinationToString(destination), output_log); |
| 267 | case LogSeverity::Trace: | ||
| 268 | LOG_DEBUG(Service_LM, "LogManager TRACE ({}):\n{}", DestinationToString(destination), | ||
| 269 | output_log); | ||
| 270 | break; | ||
| 271 | case LogSeverity::Info: | ||
| 272 | LOG_INFO(Service_LM, "LogManager INFO ({}):\n{}", DestinationToString(destination), | ||
| 273 | output_log); | ||
| 274 | break; | ||
| 275 | case LogSeverity::Warning: | ||
| 276 | LOG_WARNING(Service_LM, "LogManager WARNING ({}):\n{}", | ||
| 277 | DestinationToString(destination), output_log); | ||
| 278 | break; | ||
| 279 | case LogSeverity::Error: | ||
| 280 | LOG_ERROR(Service_LM, "LogManager ERROR ({}):\n{}", DestinationToString(destination), | ||
| 281 | output_log); | ||
| 282 | break; | ||
| 283 | case LogSeverity::Fatal: | ||
| 284 | LOG_CRITICAL(Service_LM, "LogManager FATAL ({}):\n{}", DestinationToString(destination), | ||
| 285 | output_log); | ||
| 286 | break; | ||
| 287 | default: | ||
| 288 | LOG_CRITICAL(Service_LM, "LogManager UNKNOWN ({}):\n{}", | ||
| 289 | DestinationToString(destination), output_log); | ||
| 290 | break; | ||
| 291 | } | ||
| 292 | } | 285 | } |
| 293 | 286 | ||
| 294 | static std::string DestinationToString(LogDestination destination) { | 287 | static std::string DestinationToString(LogDestination destination) { |
diff --git a/src/core/hle/service/ns/pl_u.cpp b/src/core/hle/service/ns/pl_u.cpp index 6e5ba26a3..74cc45f1e 100644 --- a/src/core/hle/service/ns/pl_u.cpp +++ b/src/core/hle/service/ns/pl_u.cpp | |||
| @@ -254,8 +254,6 @@ void PL_U::GetSharedMemoryNativeHandle(Kernel::HLERequestContext& ctx) { | |||
| 254 | LOG_DEBUG(Service_NS, "called"); | 254 | LOG_DEBUG(Service_NS, "called"); |
| 255 | 255 | ||
| 256 | // Create shared font memory object | 256 | // Create shared font memory object |
| 257 | auto& kernel = system.Kernel(); | ||
| 258 | |||
| 259 | std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(), | 257 | std::memcpy(kernel.GetFontSharedMem().GetPointer(), impl->shared_font->data(), |
| 260 | impl->shared_font->size()); | 258 | impl->shared_font->size()); |
| 261 | 259 | ||
diff --git a/src/core/hle/service/service.cpp b/src/core/hle/service/service.cpp index 7a15eeba0..4e1541630 100644 --- a/src/core/hle/service/service.cpp +++ b/src/core/hle/service/service.cpp | |||
| @@ -93,8 +93,8 @@ namespace Service { | |||
| 93 | 93 | ||
| 94 | ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, | 94 | ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, |
| 95 | u32 max_sessions_, InvokerFn* handler_invoker_) | 95 | u32 max_sessions_, InvokerFn* handler_invoker_) |
| 96 | : system{system_}, service_name{service_name_}, max_sessions{max_sessions_}, | 96 | : SessionRequestHandler(system_.Kernel(), service_name_), system{system_}, |
| 97 | handler_invoker{handler_invoker_} {} | 97 | service_name{service_name_}, max_sessions{max_sessions_}, handler_invoker{handler_invoker_} {} |
| 98 | 98 | ||
| 99 | ServiceFrameworkBase::~ServiceFrameworkBase() { | 99 | ServiceFrameworkBase::~ServiceFrameworkBase() { |
| 100 | // Wait for other threads to release access before destroying | 100 | // Wait for other threads to release access before destroying |
| @@ -111,7 +111,7 @@ void ServiceFrameworkBase::InstallAsService(SM::ServiceManager& service_manager) | |||
| 111 | port_installed = true; | 111 | port_installed = true; |
| 112 | } | 112 | } |
| 113 | 113 | ||
| 114 | Kernel::KClientPort& ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) { | 114 | Kernel::KClientPort& ServiceFrameworkBase::CreatePort() { |
| 115 | const auto guard = LockService(); | 115 | const auto guard = LockService(); |
| 116 | 116 | ||
| 117 | ASSERT(!port_installed); | 117 | ASSERT(!port_installed); |
diff --git a/src/core/hle/service/service.h b/src/core/hle/service/service.h index 4c048173b..e078ac176 100644 --- a/src/core/hle/service/service.h +++ b/src/core/hle/service/service.h | |||
| @@ -23,6 +23,7 @@ namespace Kernel { | |||
| 23 | class HLERequestContext; | 23 | class HLERequestContext; |
| 24 | class KClientPort; | 24 | class KClientPort; |
| 25 | class KServerSession; | 25 | class KServerSession; |
| 26 | class ServiceThread; | ||
| 26 | } // namespace Kernel | 27 | } // namespace Kernel |
| 27 | 28 | ||
| 28 | namespace Service { | 29 | namespace Service { |
| @@ -39,9 +40,11 @@ namespace SM { | |||
| 39 | class ServiceManager; | 40 | class ServiceManager; |
| 40 | } | 41 | } |
| 41 | 42 | ||
| 42 | static const int kMaxPortSize = 8; ///< Maximum size of a port name (8 characters) | 43 | /// Default number of maximum connections to a server session. |
| 43 | /// Arbitrary default number of maximum connections to an HLE service. | 44 | static constexpr u32 ServerSessionCountMax = 0x40; |
| 44 | static const u32 DefaultMaxSessions = 10; | 45 | static_assert(ServerSessionCountMax == 0x40, |
| 46 | "ServerSessionCountMax isn't 0x40 somehow, this assert is a reminder that this will " | ||
| 47 | "break lots of things"); | ||
| 45 | 48 | ||
| 46 | /** | 49 | /** |
| 47 | * This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it | 50 | * This is an non-templated base of ServiceFramework to reduce code bloat and compilation times, it |
| @@ -74,7 +77,7 @@ public: | |||
| 74 | void InvokeRequestTipc(Kernel::HLERequestContext& ctx); | 77 | void InvokeRequestTipc(Kernel::HLERequestContext& ctx); |
| 75 | 78 | ||
| 76 | /// Creates a port pair and registers it on the kernel's global port registry. | 79 | /// Creates a port pair and registers it on the kernel's global port registry. |
| 77 | Kernel::KClientPort& CreatePort(Kernel::KernelCore& kernel); | 80 | Kernel::KClientPort& CreatePort(); |
| 78 | 81 | ||
| 79 | /// Handles a synchronization request for the service. | 82 | /// Handles a synchronization request for the service. |
| 80 | ResultCode HandleSyncRequest(Kernel::KServerSession& session, | 83 | ResultCode HandleSyncRequest(Kernel::KServerSession& session, |
| @@ -177,7 +180,7 @@ protected: | |||
| 177 | * connected to this service at the same time. | 180 | * connected to this service at the same time. |
| 178 | */ | 181 | */ |
| 179 | explicit ServiceFramework(Core::System& system_, const char* service_name_, | 182 | explicit ServiceFramework(Core::System& system_, const char* service_name_, |
| 180 | u32 max_sessions_ = DefaultMaxSessions) | 183 | u32 max_sessions_ = ServerSessionCountMax) |
| 181 | : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {} | 184 | : ServiceFrameworkBase(system_, service_name_, max_sessions_, Invoker) {} |
| 182 | 185 | ||
| 183 | /// Registers handlers in the service. | 186 | /// Registers handlers in the service. |
diff --git a/src/core/hle/service/sm/controller.cpp b/src/core/hle/service/sm/controller.cpp index 5fa5e0512..8b9418e0f 100644 --- a/src/core/hle/service/sm/controller.cpp +++ b/src/core/hle/service/sm/controller.cpp | |||
| @@ -28,42 +28,25 @@ void Controller::ConvertCurrentObjectToDomain(Kernel::HLERequestContext& ctx) { | |||
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { | 30 | void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { |
| 31 | // TODO(bunnei): This is just creating a new handle to the same Session. I assume this is wrong | ||
| 32 | // and that we probably want to actually make an entirely new Session, but we still need to | ||
| 33 | // verify this on hardware. | ||
| 34 | |||
| 35 | LOG_DEBUG(Service, "called"); | 31 | LOG_DEBUG(Service, "called"); |
| 36 | 32 | ||
| 37 | auto& kernel = system.Kernel(); | 33 | auto& parent_session = *ctx.Session()->GetParent(); |
| 38 | auto* session = ctx.Session()->GetParent(); | 34 | auto& parent_port = parent_session.GetParent()->GetParent()->GetClientPort(); |
| 39 | auto* port = session->GetParent()->GetParent(); | 35 | auto& session_manager = parent_session.GetServerSession().GetSessionRequestManager(); |
| 40 | 36 | ||
| 41 | // Reserve a new session from the process resource limit. | 37 | // Create a session. |
| 42 | Kernel::KScopedResourceReservation session_reservation( | 38 | Kernel::KClientSession* session{}; |
| 43 | kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); | 39 | const ResultCode result = parent_port.CreateSession(std::addressof(session), session_manager); |
| 44 | if (!session_reservation.Succeeded()) { | 40 | if (result.IsError()) { |
| 41 | LOG_CRITICAL(Service, "CreateSession failed with error 0x{:08X}", result.raw); | ||
| 45 | IPC::ResponseBuilder rb{ctx, 2}; | 42 | IPC::ResponseBuilder rb{ctx, 2}; |
| 46 | rb.Push(Kernel::ResultLimitReached); | 43 | rb.Push(result); |
| 47 | } | 44 | } |
| 48 | 45 | ||
| 49 | // Create a new session. | ||
| 50 | auto* clone = Kernel::KSession::Create(kernel); | ||
| 51 | clone->Initialize(&port->GetClientPort(), session->GetName()); | ||
| 52 | |||
| 53 | // Commit the session reservation. | ||
| 54 | session_reservation.Commit(); | ||
| 55 | |||
| 56 | // Enqueue the session with the named port. | ||
| 57 | port->EnqueueSession(&clone->GetServerSession()); | ||
| 58 | |||
| 59 | // Set the session request manager. | ||
| 60 | clone->GetServerSession().SetSessionRequestManager( | ||
| 61 | session->GetServerSession().GetSessionRequestManager()); | ||
| 62 | |||
| 63 | // We succeeded. | 46 | // We succeeded. |
| 64 | IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; | 47 | IPC::ResponseBuilder rb{ctx, 2, 0, 1, IPC::ResponseBuilder::Flags::AlwaysMoveHandles}; |
| 65 | rb.Push(ResultSuccess); | 48 | rb.Push(ResultSuccess); |
| 66 | rb.PushMoveObjects(clone->GetClientSession()); | 49 | rb.PushMoveObjects(session); |
| 67 | } | 50 | } |
| 68 | 51 | ||
| 69 | void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { | 52 | void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { |
diff --git a/src/core/hle/service/sm/sm.cpp b/src/core/hle/service/sm/sm.cpp index d8b20a3f2..c7828c3bd 100644 --- a/src/core/hle/service/sm/sm.cpp +++ b/src/core/hle/service/sm/sm.cpp | |||
| @@ -46,7 +46,7 @@ Kernel::KClientPort& ServiceManager::InterfaceFactory(ServiceManager& self, Core | |||
| 46 | self.sm_interface = sm; | 46 | self.sm_interface = sm; |
| 47 | self.controller_interface = std::make_unique<Controller>(system); | 47 | self.controller_interface = std::make_unique<Controller>(system); |
| 48 | 48 | ||
| 49 | return sm->CreatePort(system.Kernel()); | 49 | return sm->CreatePort(); |
| 50 | } | 50 | } |
| 51 | 51 | ||
| 52 | ResultVal<Kernel::KServerPort*> ServiceManager::RegisterService(std::string name, | 52 | ResultVal<Kernel::KServerPort*> ServiceManager::RegisterService(std::string name, |
| @@ -151,31 +151,23 @@ ResultVal<Kernel::KClientSession*> SM::GetServiceImpl(Kernel::HLERequestContext& | |||
| 151 | std::string name(PopServiceName(rp)); | 151 | std::string name(PopServiceName(rp)); |
| 152 | 152 | ||
| 153 | // Find the named port. | 153 | // Find the named port. |
| 154 | auto result = service_manager.GetServicePort(name); | 154 | auto port_result = service_manager.GetServicePort(name); |
| 155 | if (result.Failed()) { | 155 | if (port_result.Failed()) { |
| 156 | LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.Code().raw); | 156 | LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, port_result.Code().raw); |
| 157 | return result.Code(); | 157 | return port_result.Code(); |
| 158 | } | 158 | } |
| 159 | auto* port = result.Unwrap(); | 159 | auto& port = port_result.Unwrap()->GetClientPort(); |
| 160 | |||
| 161 | // Reserve a new session from the process resource limit. | ||
| 162 | Kernel::KScopedResourceReservation session_reservation( | ||
| 163 | kernel.CurrentProcess()->GetResourceLimit(), Kernel::LimitableResource::Sessions); | ||
| 164 | R_UNLESS(session_reservation.Succeeded(), Kernel::ResultLimitReached); | ||
| 165 | 160 | ||
| 166 | // Create a new session. | 161 | // Create a new session. |
| 167 | auto* session = Kernel::KSession::Create(kernel); | 162 | Kernel::KClientSession* session{}; |
| 168 | session->Initialize(&port->GetClientPort(), std::move(name)); | 163 | if (const auto result = port.CreateSession(std::addressof(session)); result.IsError()) { |
| 169 | 164 | LOG_ERROR(Service_SM, "called service={} -> error 0x{:08X}", name, result.raw); | |
| 170 | // Commit the session reservation. | 165 | return result; |
| 171 | session_reservation.Commit(); | 166 | } |
| 172 | |||
| 173 | // Enqueue the session with the named port. | ||
| 174 | port->EnqueueSession(&session->GetServerSession()); | ||
| 175 | 167 | ||
| 176 | LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); | 168 | LOG_DEBUG(Service_SM, "called service={} -> session={}", name, session->GetId()); |
| 177 | 169 | ||
| 178 | return MakeResult(&session->GetClientSession()); | 170 | return MakeResult(session); |
| 179 | } | 171 | } |
| 180 | 172 | ||
| 181 | void SM::RegisterService(Kernel::HLERequestContext& ctx) { | 173 | void SM::RegisterService(Kernel::HLERequestContext& ctx) { |
diff --git a/src/core/memory.cpp b/src/core/memory.cpp index 9857278f6..f285c6f63 100644 --- a/src/core/memory.cpp +++ b/src/core/memory.cpp | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "common/common_types.h" | 12 | #include "common/common_types.h" |
| 13 | #include "common/logging/log.h" | 13 | #include "common/logging/log.h" |
| 14 | #include "common/page_table.h" | 14 | #include "common/page_table.h" |
| 15 | #include "common/settings.h" | ||
| 15 | #include "common/swap.h" | 16 | #include "common/swap.h" |
| 16 | #include "core/arm/arm_interface.h" | 17 | #include "core/arm/arm_interface.h" |
| 17 | #include "core/core.h" | 18 | #include "core/core.h" |
| @@ -32,6 +33,7 @@ struct Memory::Impl { | |||
| 32 | 33 | ||
| 33 | void SetCurrentPageTable(Kernel::KProcess& process, u32 core_id) { | 34 | void SetCurrentPageTable(Kernel::KProcess& process, u32 core_id) { |
| 34 | current_page_table = &process.PageTable().PageTableImpl(); | 35 | current_page_table = &process.PageTable().PageTableImpl(); |
| 36 | current_page_table->fastmem_arena = system.DeviceMemory().buffer.VirtualBasePointer(); | ||
| 35 | 37 | ||
| 36 | const std::size_t address_space_width = process.PageTable().GetAddressSpaceWidth(); | 38 | const std::size_t address_space_width = process.PageTable().GetAddressSpaceWidth(); |
| 37 | 39 | ||
| @@ -41,13 +43,23 @@ struct Memory::Impl { | |||
| 41 | void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) { | 43 | void MapMemoryRegion(Common::PageTable& page_table, VAddr base, u64 size, PAddr target) { |
| 42 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); | 44 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); |
| 43 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); | 45 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); |
| 46 | ASSERT_MSG(target >= DramMemoryMap::Base && target < DramMemoryMap::End, | ||
| 47 | "Out of bounds target: {:016X}", target); | ||
| 44 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); | 48 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, target, Common::PageType::Memory); |
| 49 | |||
| 50 | if (Settings::IsFastmemEnabled()) { | ||
| 51 | system.DeviceMemory().buffer.Map(base, target - DramMemoryMap::Base, size); | ||
| 52 | } | ||
| 45 | } | 53 | } |
| 46 | 54 | ||
| 47 | void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { | 55 | void UnmapRegion(Common::PageTable& page_table, VAddr base, u64 size) { |
| 48 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); | 56 | ASSERT_MSG((size & PAGE_MASK) == 0, "non-page aligned size: {:016X}", size); |
| 49 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); | 57 | ASSERT_MSG((base & PAGE_MASK) == 0, "non-page aligned base: {:016X}", base); |
| 50 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); | 58 | MapPages(page_table, base / PAGE_SIZE, size / PAGE_SIZE, 0, Common::PageType::Unmapped); |
| 59 | |||
| 60 | if (Settings::IsFastmemEnabled()) { | ||
| 61 | system.DeviceMemory().buffer.Unmap(base, size); | ||
| 62 | } | ||
| 51 | } | 63 | } |
| 52 | 64 | ||
| 53 | bool IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const { | 65 | bool IsValidVirtualAddress(const Kernel::KProcess& process, const VAddr vaddr) const { |
| @@ -466,6 +478,12 @@ struct Memory::Impl { | |||
| 466 | if (vaddr == 0) { | 478 | if (vaddr == 0) { |
| 467 | return; | 479 | return; |
| 468 | } | 480 | } |
| 481 | |||
| 482 | if (Settings::IsFastmemEnabled()) { | ||
| 483 | const bool is_read_enable = Settings::IsGPULevelHigh() || !cached; | ||
| 484 | system.DeviceMemory().buffer.Protect(vaddr, size, is_read_enable, !cached); | ||
| 485 | } | ||
| 486 | |||
| 469 | // Iterate over a contiguous CPU address space, which corresponds to the specified GPU | 487 | // Iterate over a contiguous CPU address space, which corresponds to the specified GPU |
| 470 | // address space, marking the region as un/cached. The region is marked un/cached at a | 488 | // address space, marking the region as un/cached. The region is marked un/cached at a |
| 471 | // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size | 489 | // granularity of CPU pages, hence why we iterate on a CPU page basis (note: GPU page size |
diff --git a/src/core/reporter.cpp b/src/core/reporter.cpp index ec2a16e62..82b0f535a 100644 --- a/src/core/reporter.cpp +++ b/src/core/reporter.cpp | |||
| @@ -195,7 +195,9 @@ json GetHLERequestContextData(Kernel::HLERequestContext& ctx, Core::Memory::Memo | |||
| 195 | 195 | ||
| 196 | namespace Core { | 196 | namespace Core { |
| 197 | 197 | ||
| 198 | Reporter::Reporter(System& system_) : system(system_) {} | 198 | Reporter::Reporter(System& system_) : system(system_) { |
| 199 | ClearFSAccessLog(); | ||
| 200 | } | ||
| 199 | 201 | ||
| 200 | Reporter::~Reporter() = default; | 202 | Reporter::~Reporter() = default; |
| 201 | 203 | ||
| @@ -362,22 +364,12 @@ void Reporter::SaveErrorReport(u64 title_id, ResultCode result, | |||
| 362 | SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); | 364 | SaveToFile(std::move(out), GetPath("error_report", title_id, timestamp)); |
| 363 | } | 365 | } |
| 364 | 366 | ||
| 365 | void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, | 367 | void Reporter::SaveFSAccessLog(std::string_view log_message) const { |
| 366 | std::string log_message) const { | 368 | const auto access_log_path = |
| 367 | if (!IsReportingEnabled()) | 369 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt"; |
| 368 | return; | ||
| 369 | |||
| 370 | const auto timestamp = GetTimestamp(); | ||
| 371 | const auto title_id = system.CurrentProcess()->GetTitleID(); | ||
| 372 | json out; | ||
| 373 | 370 | ||
| 374 | out["yuzu_version"] = GetYuzuVersionData(); | 371 | void(Common::FS::AppendStringToFile(access_log_path, Common::FS::FileType::TextFile, |
| 375 | out["report_common"] = GetReportCommonData(title_id, ResultSuccess, timestamp); | 372 | log_message)); |
| 376 | |||
| 377 | out["log_mode"] = fmt::format("{:08X}", static_cast<u32>(log_mode)); | ||
| 378 | out["log_message"] = std::move(log_message); | ||
| 379 | |||
| 380 | SaveToFile(std::move(out), GetPath("filesystem_access_report", title_id, timestamp)); | ||
| 381 | } | 373 | } |
| 382 | 374 | ||
| 383 | void Reporter::SaveUserReport() const { | 375 | void Reporter::SaveUserReport() const { |
| @@ -392,6 +384,18 @@ void Reporter::SaveUserReport() const { | |||
| 392 | GetPath("user_report", title_id, timestamp)); | 384 | GetPath("user_report", title_id, timestamp)); |
| 393 | } | 385 | } |
| 394 | 386 | ||
| 387 | void Reporter::ClearFSAccessLog() const { | ||
| 388 | const auto access_log_path = | ||
| 389 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::SDMCDir) / "FsAccessLog.txt"; | ||
| 390 | |||
| 391 | Common::FS::IOFile access_log_file{access_log_path, Common::FS::FileAccessMode::Write, | ||
| 392 | Common::FS::FileType::TextFile}; | ||
| 393 | |||
| 394 | if (!access_log_file.IsOpen()) { | ||
| 395 | LOG_ERROR(Common_Filesystem, "Failed to clear the filesystem access log."); | ||
| 396 | } | ||
| 397 | } | ||
| 398 | |||
| 395 | bool Reporter::IsReportingEnabled() const { | 399 | bool Reporter::IsReportingEnabled() const { |
| 396 | return Settings::values.reporting_services; | 400 | return Settings::values.reporting_services; |
| 397 | } | 401 | } |
diff --git a/src/core/reporter.h b/src/core/reporter.h index 6fb6ebffa..6e9edeea3 100644 --- a/src/core/reporter.h +++ b/src/core/reporter.h | |||
| @@ -16,10 +16,6 @@ namespace Kernel { | |||
| 16 | class HLERequestContext; | 16 | class HLERequestContext; |
| 17 | } // namespace Kernel | 17 | } // namespace Kernel |
| 18 | 18 | ||
| 19 | namespace Service::FileSystem { | ||
| 20 | enum class LogMode : u32; | ||
| 21 | } | ||
| 22 | |||
| 23 | namespace Service::LM { | 19 | namespace Service::LM { |
| 24 | struct LogMessage; | 20 | struct LogMessage; |
| 25 | } // namespace Service::LM | 21 | } // namespace Service::LM |
| @@ -69,14 +65,15 @@ public: | |||
| 69 | std::optional<std::string> custom_text_main = {}, | 65 | std::optional<std::string> custom_text_main = {}, |
| 70 | std::optional<std::string> custom_text_detail = {}) const; | 66 | std::optional<std::string> custom_text_detail = {}) const; |
| 71 | 67 | ||
| 72 | void SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, | 68 | void SaveFSAccessLog(std::string_view log_message) const; |
| 73 | std::string log_message) const; | ||
| 74 | 69 | ||
| 75 | // Can be used anywhere to generate a backtrace and general info report at any point during | 70 | // Can be used anywhere to generate a backtrace and general info report at any point during |
| 76 | // execution. Not intended to be used for anything other than debugging or testing. | 71 | // execution. Not intended to be used for anything other than debugging or testing. |
| 77 | void SaveUserReport() const; | 72 | void SaveUserReport() const; |
| 78 | 73 | ||
| 79 | private: | 74 | private: |
| 75 | void ClearFSAccessLog() const; | ||
| 76 | |||
| 80 | bool IsReportingEnabled() const; | 77 | bool IsReportingEnabled() const; |
| 81 | 78 | ||
| 82 | System& system; | 79 | System& system; |
diff --git a/src/core/telemetry_session.cpp b/src/core/telemetry_session.cpp index ad1a9ffb4..d4c23ced2 100644 --- a/src/core/telemetry_session.cpp +++ b/src/core/telemetry_session.cpp | |||
| @@ -230,6 +230,7 @@ void TelemetrySession::AddInitialInfo(Loader::AppLoader& app_loader, | |||
| 230 | Settings::values.use_asynchronous_gpu_emulation.GetValue()); | 230 | Settings::values.use_asynchronous_gpu_emulation.GetValue()); |
| 231 | AddField(field_type, "Renderer_UseNvdecEmulation", | 231 | AddField(field_type, "Renderer_UseNvdecEmulation", |
| 232 | Settings::values.use_nvdec_emulation.GetValue()); | 232 | Settings::values.use_nvdec_emulation.GetValue()); |
| 233 | AddField(field_type, "Renderer_AccelerateASTC", Settings::values.accelerate_astc.GetValue()); | ||
| 233 | AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue()); | 234 | AddField(field_type, "Renderer_UseVsync", Settings::values.use_vsync.GetValue()); |
| 234 | AddField(field_type, "Renderer_UseAssemblyShaders", | 235 | AddField(field_type, "Renderer_UseAssemblyShaders", |
| 235 | Settings::values.use_assembly_shaders.GetValue()); | 236 | Settings::values.use_assembly_shaders.GetValue()); |
diff --git a/src/input_common/CMakeLists.txt b/src/input_common/CMakeLists.txt index de53e1fda..7c5763f9c 100644 --- a/src/input_common/CMakeLists.txt +++ b/src/input_common/CMakeLists.txt | |||
| @@ -71,8 +71,7 @@ if (ENABLE_SDL2) | |||
| 71 | target_compile_definitions(input_common PRIVATE HAVE_SDL2) | 71 | target_compile_definitions(input_common PRIVATE HAVE_SDL2) |
| 72 | endif() | 72 | endif() |
| 73 | 73 | ||
| 74 | target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR}) | 74 | target_link_libraries(input_common PRIVATE usb) |
| 75 | target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES}) | ||
| 76 | 75 | ||
| 77 | create_target_directory_groups(input_common) | 76 | create_target_directory_groups(input_common) |
| 78 | target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) | 77 | target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) |
diff --git a/src/input_common/analog_from_button.cpp b/src/input_common/analog_from_button.cpp index f8ec179d0..100138d11 100755 --- a/src/input_common/analog_from_button.cpp +++ b/src/input_common/analog_from_button.cpp | |||
| @@ -21,104 +21,153 @@ public: | |||
| 21 | : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), | 21 | : up(std::move(up_)), down(std::move(down_)), left(std::move(left_)), |
| 22 | right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), | 22 | right(std::move(right_)), modifier(std::move(modifier_)), modifier_scale(modifier_scale_), |
| 23 | modifier_angle(modifier_angle_) { | 23 | modifier_angle(modifier_angle_) { |
| 24 | update_thread_running.store(true); | 24 | Input::InputCallback<bool> callbacks{ |
| 25 | update_thread = std::thread(&Analog::UpdateStatus, this); | 25 | [this]([[maybe_unused]] bool status) { UpdateStatus(); }}; |
| 26 | up->SetCallback(callbacks); | ||
| 27 | down->SetCallback(callbacks); | ||
| 28 | left->SetCallback(callbacks); | ||
| 29 | right->SetCallback(callbacks); | ||
| 26 | } | 30 | } |
| 27 | 31 | ||
| 28 | ~Analog() override { | 32 | bool IsAngleGreater(float old_angle, float new_angle) const { |
| 29 | if (update_thread_running.load()) { | 33 | constexpr float TAU = Common::PI * 2.0f; |
| 30 | update_thread_running.store(false); | 34 | // Use wider angle to ease the transition. |
| 31 | if (update_thread.joinable()) { | 35 | constexpr float aperture = TAU * 0.15f; |
| 32 | update_thread.join(); | 36 | const float top_limit = new_angle + aperture; |
| 33 | } | 37 | return (old_angle > new_angle && old_angle <= top_limit) || |
| 34 | } | 38 | (old_angle + TAU > new_angle && old_angle + TAU <= top_limit); |
| 35 | } | 39 | } |
| 36 | 40 | ||
| 37 | void MoveToDirection(bool enable, float to_angle) { | 41 | bool IsAngleSmaller(float old_angle, float new_angle) const { |
| 38 | if (!enable) { | ||
| 39 | return; | ||
| 40 | } | ||
| 41 | constexpr float TAU = Common::PI * 2.0f; | 42 | constexpr float TAU = Common::PI * 2.0f; |
| 42 | // Use wider angle to ease the transition. | 43 | // Use wider angle to ease the transition. |
| 43 | constexpr float aperture = TAU * 0.15f; | 44 | constexpr float aperture = TAU * 0.15f; |
| 44 | const float top_limit = to_angle + aperture; | 45 | const float bottom_limit = new_angle - aperture; |
| 45 | const float bottom_limit = to_angle - aperture; | 46 | return (old_angle >= bottom_limit && old_angle < new_angle) || |
| 46 | 47 | (old_angle - TAU >= bottom_limit && old_angle - TAU < new_angle); | |
| 47 | if ((angle > to_angle && angle <= top_limit) || | 48 | } |
| 48 | (angle + TAU > to_angle && angle + TAU <= top_limit)) { | 49 | |
| 49 | angle -= modifier_angle; | 50 | float GetAngle(std::chrono::time_point<std::chrono::steady_clock> now) const { |
| 50 | if (angle < 0) { | 51 | constexpr float TAU = Common::PI * 2.0f; |
| 51 | angle += TAU; | 52 | float new_angle = angle; |
| 53 | |||
| 54 | auto time_difference = static_cast<float>( | ||
| 55 | std::chrono::duration_cast<std::chrono::microseconds>(now - last_update).count()); | ||
| 56 | time_difference /= 1000.0f * 1000.0f; | ||
| 57 | if (time_difference > 0.5f) { | ||
| 58 | time_difference = 0.5f; | ||
| 59 | } | ||
| 60 | |||
| 61 | if (IsAngleGreater(new_angle, goal_angle)) { | ||
| 62 | new_angle -= modifier_angle * time_difference; | ||
| 63 | if (new_angle < 0) { | ||
| 64 | new_angle += TAU; | ||
| 65 | } | ||
| 66 | if (!IsAngleGreater(new_angle, goal_angle)) { | ||
| 67 | return goal_angle; | ||
| 52 | } | 68 | } |
| 53 | } else if ((angle >= bottom_limit && angle < to_angle) || | 69 | } else if (IsAngleSmaller(new_angle, goal_angle)) { |
| 54 | (angle - TAU >= bottom_limit && angle - TAU < to_angle)) { | 70 | new_angle += modifier_angle * time_difference; |
| 55 | angle += modifier_angle; | 71 | if (new_angle >= TAU) { |
| 56 | if (angle >= TAU) { | 72 | new_angle -= TAU; |
| 57 | angle -= TAU; | 73 | } |
| 74 | if (!IsAngleSmaller(new_angle, goal_angle)) { | ||
| 75 | return goal_angle; | ||
| 58 | } | 76 | } |
| 59 | } else { | 77 | } else { |
| 60 | angle = to_angle; | 78 | return goal_angle; |
| 61 | } | 79 | } |
| 80 | return new_angle; | ||
| 62 | } | 81 | } |
| 63 | 82 | ||
| 64 | void UpdateStatus() { | 83 | void SetGoalAngle(bool r, bool l, bool u, bool d) { |
| 65 | while (update_thread_running.load()) { | 84 | // Move to the right |
| 66 | const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; | 85 | if (r && !u && !d) { |
| 67 | 86 | goal_angle = 0.0f; | |
| 68 | bool r = right->GetStatus(); | 87 | } |
| 69 | bool l = left->GetStatus(); | 88 | |
| 70 | bool u = up->GetStatus(); | 89 | // Move to the upper right |
| 71 | bool d = down->GetStatus(); | 90 | if (r && u && !d) { |
| 72 | 91 | goal_angle = Common::PI * 0.25f; | |
| 73 | // Eliminate contradictory movements | 92 | } |
| 74 | if (r && l) { | ||
| 75 | r = false; | ||
| 76 | l = false; | ||
| 77 | } | ||
| 78 | if (u && d) { | ||
| 79 | u = false; | ||
| 80 | d = false; | ||
| 81 | } | ||
| 82 | 93 | ||
| 83 | // Move to the right | 94 | // Move up |
| 84 | MoveToDirection(r && !u && !d, 0.0f); | 95 | if (u && !l && !r) { |
| 96 | goal_angle = Common::PI * 0.5f; | ||
| 97 | } | ||
| 85 | 98 | ||
| 86 | // Move to the upper right | 99 | // Move to the upper left |
| 87 | MoveToDirection(r && u && !d, Common::PI * 0.25f); | 100 | if (l && u && !d) { |
| 101 | goal_angle = Common::PI * 0.75f; | ||
| 102 | } | ||
| 88 | 103 | ||
| 89 | // Move up | 104 | // Move to the left |
| 90 | MoveToDirection(u && !l && !r, Common::PI * 0.5f); | 105 | if (l && !u && !d) { |
| 106 | goal_angle = Common::PI; | ||
| 107 | } | ||
| 91 | 108 | ||
| 92 | // Move to the upper left | 109 | // Move to the bottom left |
| 93 | MoveToDirection(l && u && !d, Common::PI * 0.75f); | 110 | if (l && !u && d) { |
| 111 | goal_angle = Common::PI * 1.25f; | ||
| 112 | } | ||
| 94 | 113 | ||
| 95 | // Move to the left | 114 | // Move down |
| 96 | MoveToDirection(l && !u && !d, Common::PI); | 115 | if (d && !l && !r) { |
| 116 | goal_angle = Common::PI * 1.5f; | ||
| 117 | } | ||
| 97 | 118 | ||
| 98 | // Move to the bottom left | 119 | // Move to the bottom right |
| 99 | MoveToDirection(l && !u && d, Common::PI * 1.25f); | 120 | if (r && !u && d) { |
| 121 | goal_angle = Common::PI * 1.75f; | ||
| 122 | } | ||
| 123 | } | ||
| 100 | 124 | ||
| 101 | // Move down | 125 | void UpdateStatus() { |
| 102 | MoveToDirection(d && !l && !r, Common::PI * 1.5f); | 126 | const float coef = modifier->GetStatus() ? modifier_scale : 1.0f; |
| 103 | 127 | ||
| 104 | // Move to the bottom right | 128 | bool r = right->GetStatus(); |
| 105 | MoveToDirection(r && !u && d, Common::PI * 1.75f); | 129 | bool l = left->GetStatus(); |
| 130 | bool u = up->GetStatus(); | ||
| 131 | bool d = down->GetStatus(); | ||
| 106 | 132 | ||
| 107 | // Move if a key is pressed | 133 | // Eliminate contradictory movements |
| 108 | if (r || l || u || d) { | 134 | if (r && l) { |
| 109 | amplitude = coef; | 135 | r = false; |
| 110 | } else { | 136 | l = false; |
| 111 | amplitude = 0; | 137 | } |
| 112 | } | 138 | if (u && d) { |
| 139 | u = false; | ||
| 140 | d = false; | ||
| 141 | } | ||
| 113 | 142 | ||
| 114 | // Delay the update rate to 100hz | 143 | // Move if a key is pressed |
| 115 | std::this_thread::sleep_for(std::chrono::milliseconds(10)); | 144 | if (r || l || u || d) { |
| 145 | amplitude = coef; | ||
| 146 | } else { | ||
| 147 | amplitude = 0; | ||
| 116 | } | 148 | } |
| 149 | |||
| 150 | const auto now = std::chrono::steady_clock::now(); | ||
| 151 | const auto time_difference = static_cast<u64>( | ||
| 152 | std::chrono::duration_cast<std::chrono::milliseconds>(now - last_update).count()); | ||
| 153 | |||
| 154 | if (time_difference < 10) { | ||
| 155 | // Disable analog mode if inputs are too fast | ||
| 156 | SetGoalAngle(r, l, u, d); | ||
| 157 | angle = goal_angle; | ||
| 158 | } else { | ||
| 159 | angle = GetAngle(now); | ||
| 160 | SetGoalAngle(r, l, u, d); | ||
| 161 | } | ||
| 162 | |||
| 163 | last_update = now; | ||
| 117 | } | 164 | } |
| 118 | 165 | ||
| 119 | std::tuple<float, float> GetStatus() const override { | 166 | std::tuple<float, float> GetStatus() const override { |
| 120 | if (Settings::values.emulate_analog_keyboard) { | 167 | if (Settings::values.emulate_analog_keyboard) { |
| 121 | return std::make_tuple(std::cos(angle) * amplitude, std::sin(angle) * amplitude); | 168 | const auto now = std::chrono::steady_clock::now(); |
| 169 | float angle_ = GetAngle(now); | ||
| 170 | return std::make_tuple(std::cos(angle_) * amplitude, std::sin(angle_) * amplitude); | ||
| 122 | } | 171 | } |
| 123 | constexpr float SQRT_HALF = 0.707106781f; | 172 | constexpr float SQRT_HALF = 0.707106781f; |
| 124 | int x = 0, y = 0; | 173 | int x = 0, y = 0; |
| @@ -166,9 +215,9 @@ private: | |||
| 166 | float modifier_scale; | 215 | float modifier_scale; |
| 167 | float modifier_angle; | 216 | float modifier_angle; |
| 168 | float angle{}; | 217 | float angle{}; |
| 218 | float goal_angle{}; | ||
| 169 | float amplitude{}; | 219 | float amplitude{}; |
| 170 | std::thread update_thread; | 220 | std::chrono::time_point<std::chrono::steady_clock> last_update; |
| 171 | std::atomic<bool> update_thread_running{}; | ||
| 172 | }; | 221 | }; |
| 173 | 222 | ||
| 174 | std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { | 223 | std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { |
| @@ -179,7 +228,7 @@ std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::Para | |||
| 179 | auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine)); | 228 | auto right = Input::CreateDevice<Input::ButtonDevice>(params.Get("right", null_engine)); |
| 180 | auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine)); | 229 | auto modifier = Input::CreateDevice<Input::ButtonDevice>(params.Get("modifier", null_engine)); |
| 181 | auto modifier_scale = params.Get("modifier_scale", 0.5f); | 230 | auto modifier_scale = params.Get("modifier_scale", 0.5f); |
| 182 | auto modifier_angle = params.Get("modifier_angle", 0.035f); | 231 | auto modifier_angle = params.Get("modifier_angle", 5.5f); |
| 183 | return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left), | 232 | return std::make_unique<Analog>(std::move(up), std::move(down), std::move(left), |
| 184 | std::move(right), std::move(modifier), modifier_scale, | 233 | std::move(right), std::move(modifier), modifier_scale, |
| 185 | modifier_angle); | 234 | modifier_angle); |
diff --git a/src/input_common/keyboard.cpp b/src/input_common/keyboard.cpp index c467ff4c5..8261e76fd 100644 --- a/src/input_common/keyboard.cpp +++ b/src/input_common/keyboard.cpp | |||
| @@ -75,6 +75,7 @@ public: | |||
| 75 | } else { | 75 | } else { |
| 76 | pair.key_button->UnlockButton(); | 76 | pair.key_button->UnlockButton(); |
| 77 | } | 77 | } |
| 78 | pair.key_button->TriggerOnChange(); | ||
| 78 | } | 79 | } |
| 79 | } | 80 | } |
| 80 | } | 81 | } |
diff --git a/src/tests/CMakeLists.txt b/src/tests/CMakeLists.txt index d875c4fee..96bc30cac 100644 --- a/src/tests/CMakeLists.txt +++ b/src/tests/CMakeLists.txt | |||
| @@ -2,6 +2,7 @@ add_executable(tests | |||
| 2 | common/bit_field.cpp | 2 | common/bit_field.cpp |
| 3 | common/cityhash.cpp | 3 | common/cityhash.cpp |
| 4 | common/fibers.cpp | 4 | common/fibers.cpp |
| 5 | common/host_memory.cpp | ||
| 5 | common/param_package.cpp | 6 | common/param_package.cpp |
| 6 | common/ring_buffer.cpp | 7 | common/ring_buffer.cpp |
| 7 | core/core_timing.cpp | 8 | core/core_timing.cpp |
diff --git a/src/tests/common/host_memory.cpp b/src/tests/common/host_memory.cpp new file mode 100644 index 000000000..e241f8be5 --- /dev/null +++ b/src/tests/common/host_memory.cpp | |||
| @@ -0,0 +1,183 @@ | |||
| 1 | // Copyright 2021 yuzu emulator team | ||
| 2 | // Licensed under GPLv2 or any later version | ||
| 3 | // Refer to the license.txt file included. | ||
| 4 | |||
| 5 | #include <catch2/catch.hpp> | ||
| 6 | |||
| 7 | #include "common/host_memory.h" | ||
| 8 | |||
| 9 | using Common::HostMemory; | ||
| 10 | |||
| 11 | static constexpr size_t VIRTUAL_SIZE = 1ULL << 39; | ||
| 12 | static constexpr size_t BACKING_SIZE = 4ULL * 1024 * 1024 * 1024; | ||
| 13 | |||
| 14 | TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") { | ||
| 15 | { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } | ||
| 16 | { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); } | ||
| 17 | } | ||
| 18 | |||
| 19 | TEST_CASE("HostMemory: Simple map", "[common]") { | ||
| 20 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 21 | mem.Map(0x5000, 0x8000, 0x1000); | ||
| 22 | |||
| 23 | volatile u8* const data = mem.VirtualBasePointer() + 0x5000; | ||
| 24 | data[0] = 50; | ||
| 25 | REQUIRE(data[0] == 50); | ||
| 26 | } | ||
| 27 | |||
| 28 | TEST_CASE("HostMemory: Simple mirror map", "[common]") { | ||
| 29 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 30 | mem.Map(0x5000, 0x3000, 0x2000); | ||
| 31 | mem.Map(0x8000, 0x4000, 0x1000); | ||
| 32 | |||
| 33 | volatile u8* const mirror_a = mem.VirtualBasePointer() + 0x5000; | ||
| 34 | volatile u8* const mirror_b = mem.VirtualBasePointer() + 0x8000; | ||
| 35 | mirror_b[0] = 76; | ||
| 36 | REQUIRE(mirror_a[0x1000] == 76); | ||
| 37 | } | ||
| 38 | |||
| 39 | TEST_CASE("HostMemory: Simple unmap", "[common]") { | ||
| 40 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 41 | mem.Map(0x5000, 0x3000, 0x2000); | ||
| 42 | |||
| 43 | volatile u8* const data = mem.VirtualBasePointer() + 0x5000; | ||
| 44 | data[75] = 50; | ||
| 45 | REQUIRE(data[75] == 50); | ||
| 46 | |||
| 47 | mem.Unmap(0x5000, 0x2000); | ||
| 48 | } | ||
| 49 | |||
| 50 | TEST_CASE("HostMemory: Simple unmap and remap", "[common]") { | ||
| 51 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 52 | mem.Map(0x5000, 0x3000, 0x2000); | ||
| 53 | |||
| 54 | volatile u8* const data = mem.VirtualBasePointer() + 0x5000; | ||
| 55 | data[0] = 50; | ||
| 56 | REQUIRE(data[0] == 50); | ||
| 57 | |||
| 58 | mem.Unmap(0x5000, 0x2000); | ||
| 59 | |||
| 60 | mem.Map(0x5000, 0x3000, 0x2000); | ||
| 61 | REQUIRE(data[0] == 50); | ||
| 62 | |||
| 63 | mem.Map(0x7000, 0x2000, 0x5000); | ||
| 64 | REQUIRE(data[0x3000] == 50); | ||
| 65 | } | ||
| 66 | |||
| 67 | TEST_CASE("HostMemory: Nieche allocation", "[common]") { | ||
| 68 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 69 | mem.Map(0x0000, 0, 0x20000); | ||
| 70 | mem.Unmap(0x0000, 0x4000); | ||
| 71 | mem.Map(0x1000, 0, 0x2000); | ||
| 72 | mem.Map(0x3000, 0, 0x1000); | ||
| 73 | mem.Map(0, 0, 0x1000); | ||
| 74 | } | ||
| 75 | |||
| 76 | TEST_CASE("HostMemory: Full unmap", "[common]") { | ||
| 77 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 78 | mem.Map(0x8000, 0, 0x4000); | ||
| 79 | mem.Unmap(0x8000, 0x4000); | ||
| 80 | mem.Map(0x6000, 0, 0x16000); | ||
| 81 | } | ||
| 82 | |||
| 83 | TEST_CASE("HostMemory: Right out of bounds unmap", "[common]") { | ||
| 84 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 85 | mem.Map(0x0000, 0, 0x4000); | ||
| 86 | mem.Unmap(0x2000, 0x4000); | ||
| 87 | mem.Map(0x2000, 0x80000, 0x4000); | ||
| 88 | } | ||
| 89 | |||
| 90 | TEST_CASE("HostMemory: Left out of bounds unmap", "[common]") { | ||
| 91 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 92 | mem.Map(0x8000, 0, 0x4000); | ||
| 93 | mem.Unmap(0x6000, 0x4000); | ||
| 94 | mem.Map(0x8000, 0, 0x2000); | ||
| 95 | } | ||
| 96 | |||
| 97 | TEST_CASE("HostMemory: Multiple placeholder unmap", "[common]") { | ||
| 98 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 99 | mem.Map(0x0000, 0, 0x4000); | ||
| 100 | mem.Map(0x4000, 0, 0x1b000); | ||
| 101 | mem.Unmap(0x3000, 0x1c000); | ||
| 102 | mem.Map(0x3000, 0, 0x20000); | ||
| 103 | } | ||
| 104 | |||
| 105 | TEST_CASE("HostMemory: Unmap between placeholders", "[common]") { | ||
| 106 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 107 | mem.Map(0x0000, 0, 0x4000); | ||
| 108 | mem.Map(0x4000, 0, 0x4000); | ||
| 109 | mem.Unmap(0x2000, 0x4000); | ||
| 110 | mem.Map(0x2000, 0, 0x4000); | ||
| 111 | } | ||
| 112 | |||
| 113 | TEST_CASE("HostMemory: Unmap to origin", "[common]") { | ||
| 114 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 115 | mem.Map(0x4000, 0, 0x4000); | ||
| 116 | mem.Map(0x8000, 0, 0x4000); | ||
| 117 | mem.Unmap(0x4000, 0x4000); | ||
| 118 | mem.Map(0, 0, 0x4000); | ||
| 119 | mem.Map(0x4000, 0, 0x4000); | ||
| 120 | } | ||
| 121 | |||
| 122 | TEST_CASE("HostMemory: Unmap to right", "[common]") { | ||
| 123 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 124 | mem.Map(0x4000, 0, 0x4000); | ||
| 125 | mem.Map(0x8000, 0, 0x4000); | ||
| 126 | mem.Unmap(0x8000, 0x4000); | ||
| 127 | mem.Map(0x8000, 0, 0x4000); | ||
| 128 | } | ||
| 129 | |||
| 130 | TEST_CASE("HostMemory: Partial right unmap check bindings", "[common]") { | ||
| 131 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 132 | mem.Map(0x4000, 0x10000, 0x4000); | ||
| 133 | |||
| 134 | volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; | ||
| 135 | ptr[0x1000] = 17; | ||
| 136 | |||
| 137 | mem.Unmap(0x6000, 0x2000); | ||
| 138 | |||
| 139 | REQUIRE(ptr[0x1000] == 17); | ||
| 140 | } | ||
| 141 | |||
| 142 | TEST_CASE("HostMemory: Partial left unmap check bindings", "[common]") { | ||
| 143 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 144 | mem.Map(0x4000, 0x10000, 0x4000); | ||
| 145 | |||
| 146 | volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; | ||
| 147 | ptr[0x3000] = 19; | ||
| 148 | ptr[0x3fff] = 12; | ||
| 149 | |||
| 150 | mem.Unmap(0x4000, 0x2000); | ||
| 151 | |||
| 152 | REQUIRE(ptr[0x3000] == 19); | ||
| 153 | REQUIRE(ptr[0x3fff] == 12); | ||
| 154 | } | ||
| 155 | |||
| 156 | TEST_CASE("HostMemory: Partial middle unmap check bindings", "[common]") { | ||
| 157 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 158 | mem.Map(0x4000, 0x10000, 0x4000); | ||
| 159 | |||
| 160 | volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; | ||
| 161 | ptr[0x0000] = 19; | ||
| 162 | ptr[0x3fff] = 12; | ||
| 163 | |||
| 164 | mem.Unmap(0x1000, 0x2000); | ||
| 165 | |||
| 166 | REQUIRE(ptr[0x0000] == 19); | ||
| 167 | REQUIRE(ptr[0x3fff] == 12); | ||
| 168 | } | ||
| 169 | |||
| 170 | TEST_CASE("HostMemory: Partial sparse middle unmap and check bindings", "[common]") { | ||
| 171 | HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); | ||
| 172 | mem.Map(0x4000, 0x10000, 0x2000); | ||
| 173 | mem.Map(0x6000, 0x20000, 0x2000); | ||
| 174 | |||
| 175 | volatile u8* const ptr = mem.VirtualBasePointer() + 0x4000; | ||
| 176 | ptr[0x0000] = 19; | ||
| 177 | ptr[0x3fff] = 12; | ||
| 178 | |||
| 179 | mem.Unmap(0x5000, 0x2000); | ||
| 180 | |||
| 181 | REQUIRE(ptr[0x0000] == 19); | ||
| 182 | REQUIRE(ptr[0x3fff] == 12); | ||
| 183 | } | ||
diff --git a/src/video_core/CMakeLists.txt b/src/video_core/CMakeLists.txt index 47190c464..f9454bbaa 100644 --- a/src/video_core/CMakeLists.txt +++ b/src/video_core/CMakeLists.txt | |||
| @@ -237,6 +237,7 @@ add_library(video_core STATIC | |||
| 237 | texture_cache/util.cpp | 237 | texture_cache/util.cpp |
| 238 | texture_cache/util.h | 238 | texture_cache/util.h |
| 239 | textures/astc.h | 239 | textures/astc.h |
| 240 | textures/astc.cpp | ||
| 240 | textures/decoders.cpp | 241 | textures/decoders.cpp |
| 241 | textures/decoders.h | 242 | textures/decoders.h |
| 242 | textures/texture.cpp | 243 | textures/texture.cpp |
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 0c00ae280..a39505903 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h | |||
| @@ -476,6 +476,9 @@ private: | |||
| 476 | current_size = 0; | 476 | current_size = 0; |
| 477 | on_going = false; | 477 | on_going = false; |
| 478 | } | 478 | } |
| 479 | if (empty_bits == PAGES_PER_WORD) { | ||
| 480 | break; | ||
| 481 | } | ||
| 479 | page += empty_bits; | 482 | page += empty_bits; |
| 480 | 483 | ||
| 481 | const int continuous_bits = std::countr_one(word >> page); | 484 | const int continuous_bits = std::countr_one(word >> page); |
diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 9e6b87960..d371b842f 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h | |||
| @@ -110,6 +110,8 @@ public: | |||
| 110 | 110 | ||
| 111 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size); | 111 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size); |
| 112 | 112 | ||
| 113 | void DisableGraphicsUniformBuffer(size_t stage, u32 index); | ||
| 114 | |||
| 113 | void UpdateGraphicsBuffers(bool is_indexed); | 115 | void UpdateGraphicsBuffers(bool is_indexed); |
| 114 | 116 | ||
| 115 | void UpdateComputeBuffers(); | 117 | void UpdateComputeBuffers(); |
| @@ -419,10 +421,6 @@ template <class P> | |||
| 419 | void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, | 421 | void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, |
| 420 | u32 size) { | 422 | u32 size) { |
| 421 | const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); | 423 | const std::optional<VAddr> cpu_addr = gpu_memory.GpuToCpuAddress(gpu_addr); |
| 422 | if (!cpu_addr) { | ||
| 423 | uniform_buffers[stage][index] = NULL_BINDING; | ||
| 424 | return; | ||
| 425 | } | ||
| 426 | const Binding binding{ | 424 | const Binding binding{ |
| 427 | .cpu_addr = *cpu_addr, | 425 | .cpu_addr = *cpu_addr, |
| 428 | .size = size, | 426 | .size = size, |
| @@ -432,6 +430,11 @@ void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr | |||
| 432 | } | 430 | } |
| 433 | 431 | ||
| 434 | template <class P> | 432 | template <class P> |
| 433 | void BufferCache<P>::DisableGraphicsUniformBuffer(size_t stage, u32 index) { | ||
| 434 | uniform_buffers[stage][index] = NULL_BINDING; | ||
| 435 | } | ||
| 436 | |||
| 437 | template <class P> | ||
| 435 | void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) { | 438 | void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) { |
| 436 | MICROPROFILE_SCOPE(GPU_PrepareBuffers); | 439 | MICROPROFILE_SCOPE(GPU_PrepareBuffers); |
| 437 | do { | 440 | do { |
diff --git a/src/video_core/engines/maxwell_3d.cpp b/src/video_core/engines/maxwell_3d.cpp index 75517a4f7..aab6b8f7a 100644 --- a/src/video_core/engines/maxwell_3d.cpp +++ b/src/video_core/engines/maxwell_3d.cpp | |||
| @@ -578,8 +578,12 @@ void Maxwell3D::ProcessCBBind(size_t stage_index) { | |||
| 578 | buffer.size = regs.const_buffer.cb_size; | 578 | buffer.size = regs.const_buffer.cb_size; |
| 579 | 579 | ||
| 580 | const bool is_enabled = bind_data.valid.Value() != 0; | 580 | const bool is_enabled = bind_data.valid.Value() != 0; |
| 581 | const GPUVAddr gpu_addr = is_enabled ? regs.const_buffer.BufferAddress() : 0; | 581 | if (!is_enabled) { |
| 582 | const u32 size = is_enabled ? regs.const_buffer.cb_size : 0; | 582 | rasterizer->DisableGraphicsUniformBuffer(stage_index, bind_data.index); |
| 583 | return; | ||
| 584 | } | ||
| 585 | const GPUVAddr gpu_addr = regs.const_buffer.BufferAddress(); | ||
| 586 | const u32 size = regs.const_buffer.cb_size; | ||
| 583 | rasterizer->BindGraphicsUniformBuffer(stage_index, bind_data.index, gpu_addr, size); | 587 | rasterizer->BindGraphicsUniformBuffer(stage_index, bind_data.index, gpu_addr, size); |
| 584 | } | 588 | } |
| 585 | 589 | ||
diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index cd1fbb9bf..46f642b19 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp | |||
| @@ -99,25 +99,13 @@ void ThreadManager::FlushRegion(VAddr addr, u64 size) { | |||
| 99 | PushCommand(FlushRegionCommand(addr, size)); | 99 | PushCommand(FlushRegionCommand(addr, size)); |
| 100 | return; | 100 | return; |
| 101 | } | 101 | } |
| 102 | 102 | if (!Settings::IsGPULevelExtreme()) { | |
| 103 | // Asynchronous GPU mode | 103 | return; |
| 104 | switch (Settings::values.gpu_accuracy.GetValue()) { | ||
| 105 | case Settings::GPUAccuracy::Normal: | ||
| 106 | PushCommand(FlushRegionCommand(addr, size)); | ||
| 107 | break; | ||
| 108 | case Settings::GPUAccuracy::High: | ||
| 109 | // TODO(bunnei): Is this right? Preserving existing behavior for now | ||
| 110 | break; | ||
| 111 | case Settings::GPUAccuracy::Extreme: { | ||
| 112 | auto& gpu = system.GPU(); | ||
| 113 | u64 fence = gpu.RequestFlush(addr, size); | ||
| 114 | PushCommand(GPUTickCommand(), true); | ||
| 115 | ASSERT(fence <= gpu.CurrentFlushRequestFence()); | ||
| 116 | break; | ||
| 117 | } | ||
| 118 | default: | ||
| 119 | UNIMPLEMENTED_MSG("Unsupported gpu_accuracy {}", Settings::values.gpu_accuracy.GetValue()); | ||
| 120 | } | 104 | } |
| 105 | auto& gpu = system.GPU(); | ||
| 106 | u64 fence = gpu.RequestFlush(addr, size); | ||
| 107 | PushCommand(GPUTickCommand(), true); | ||
| 108 | ASSERT(fence <= gpu.CurrentFlushRequestFence()); | ||
| 121 | } | 109 | } |
| 122 | 110 | ||
| 123 | void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { | 111 | void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { |
diff --git a/src/video_core/host_shaders/astc_decoder.comp b/src/video_core/host_shaders/astc_decoder.comp index 703e34587..eaba1b103 100644 --- a/src/video_core/host_shaders/astc_decoder.comp +++ b/src/video_core/host_shaders/astc_decoder.comp | |||
| @@ -763,7 +763,7 @@ void ComputeEndpoints(out uvec4 ep1, out uvec4 ep2, uint color_endpoint_mode) { | |||
| 763 | case 1: { | 763 | case 1: { |
| 764 | READ_UINT_VALUES(2) | 764 | READ_UINT_VALUES(2) |
| 765 | uint L0 = (v[0] >> 2) | (v[1] & 0xC0); | 765 | uint L0 = (v[0] >> 2) | (v[1] & 0xC0); |
| 766 | uint L1 = max(L0 + (v[1] & 0x3F), 0xFFU); | 766 | uint L1 = min(L0 + (v[1] & 0x3F), 0xFFU); |
| 767 | ep1 = uvec4(0xFF, L0, L0, L0); | 767 | ep1 = uvec4(0xFF, L0, L0, L0); |
| 768 | ep2 = uvec4(0xFF, L1, L1, L1); | 768 | ep2 = uvec4(0xFF, L1, L1, L1); |
| 769 | break; | 769 | break; |
diff --git a/src/video_core/memory_manager.cpp b/src/video_core/memory_manager.cpp index eb58ac6b6..7124c755c 100644 --- a/src/video_core/memory_manager.cpp +++ b/src/video_core/memory_manager.cpp | |||
| @@ -163,6 +163,9 @@ std::optional<GPUVAddr> MemoryManager::FindFreeRange(std::size_t size, std::size | |||
| 163 | } | 163 | } |
| 164 | 164 | ||
| 165 | std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { | 165 | std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { |
| 166 | if (gpu_addr == 0) { | ||
| 167 | return std::nullopt; | ||
| 168 | } | ||
| 166 | const auto page_entry{GetPageEntry(gpu_addr)}; | 169 | const auto page_entry{GetPageEntry(gpu_addr)}; |
| 167 | if (!page_entry.IsValid()) { | 170 | if (!page_entry.IsValid()) { |
| 168 | return std::nullopt; | 171 | return std::nullopt; |
diff --git a/src/video_core/rasterizer_accelerated.cpp b/src/video_core/rasterizer_accelerated.cpp index 6decd2546..4c9524702 100644 --- a/src/video_core/rasterizer_accelerated.cpp +++ b/src/video_core/rasterizer_accelerated.cpp | |||
| @@ -2,6 +2,8 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <atomic> | ||
| 6 | |||
| 5 | #include "common/assert.h" | 7 | #include "common/assert.h" |
| 6 | #include "common/common_types.h" | 8 | #include "common/common_types.h" |
| 7 | #include "common/div_ceil.h" | 9 | #include "common/div_ceil.h" |
| @@ -10,35 +12,59 @@ | |||
| 10 | 12 | ||
| 11 | namespace VideoCore { | 13 | namespace VideoCore { |
| 12 | 14 | ||
| 13 | RasterizerAccelerated::RasterizerAccelerated(Core::Memory::Memory& cpu_memory_) | 15 | using namespace Core::Memory; |
| 14 | : cpu_memory{cpu_memory_} {} | 16 | |
| 17 | RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) : cpu_memory{cpu_memory_} {} | ||
| 15 | 18 | ||
| 16 | RasterizerAccelerated::~RasterizerAccelerated() = default; | 19 | RasterizerAccelerated::~RasterizerAccelerated() = default; |
| 17 | 20 | ||
| 18 | void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { | 21 | void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { |
| 19 | const auto page_end = Common::DivCeil(addr + size, Core::Memory::PAGE_SIZE); | 22 | u64 uncache_begin = 0; |
| 20 | for (auto page = addr >> Core::Memory::PAGE_BITS; page != page_end; ++page) { | 23 | u64 cache_begin = 0; |
| 21 | auto& count = cached_pages.at(page >> 2).Count(page); | 24 | u64 uncache_bytes = 0; |
| 25 | u64 cache_bytes = 0; | ||
| 26 | |||
| 27 | std::atomic_thread_fence(std::memory_order_acquire); | ||
| 28 | const u64 page_end = Common::DivCeil(addr + size, PAGE_SIZE); | ||
| 29 | for (u64 page = addr >> PAGE_BITS; page != page_end; ++page) { | ||
| 30 | std::atomic_uint16_t& count = cached_pages.at(page >> 2).Count(page); | ||
| 22 | 31 | ||
| 23 | if (delta > 0) { | 32 | if (delta > 0) { |
| 24 | ASSERT_MSG(count < UINT16_MAX, "Count may overflow!"); | 33 | ASSERT_MSG(count.load(std::memory_order::relaxed) < UINT16_MAX, "Count may overflow!"); |
| 25 | } else if (delta < 0) { | 34 | } else if (delta < 0) { |
| 26 | ASSERT_MSG(count > 0, "Count may underflow!"); | 35 | ASSERT_MSG(count.load(std::memory_order::relaxed) > 0, "Count may underflow!"); |
| 27 | } else { | 36 | } else { |
| 28 | ASSERT_MSG(true, "Delta must be non-zero!"); | 37 | ASSERT_MSG(false, "Delta must be non-zero!"); |
| 29 | } | 38 | } |
| 30 | 39 | ||
| 31 | // Adds or subtracts 1, as count is a unsigned 8-bit value | 40 | // Adds or subtracts 1, as count is a unsigned 8-bit value |
| 32 | count += static_cast<u16>(delta); | 41 | count.fetch_add(static_cast<u16>(delta), std::memory_order_release); |
| 33 | 42 | ||
| 34 | // Assume delta is either -1 or 1 | 43 | // Assume delta is either -1 or 1 |
| 35 | if (count == 0) { | 44 | if (count.load(std::memory_order::relaxed) == 0) { |
| 36 | cpu_memory.RasterizerMarkRegionCached(page << Core::Memory::PAGE_BITS, | 45 | if (uncache_bytes == 0) { |
| 37 | Core::Memory::PAGE_SIZE, false); | 46 | uncache_begin = page; |
| 38 | } else if (count == 1 && delta > 0) { | 47 | } |
| 39 | cpu_memory.RasterizerMarkRegionCached(page << Core::Memory::PAGE_BITS, | 48 | uncache_bytes += PAGE_SIZE; |
| 40 | Core::Memory::PAGE_SIZE, true); | 49 | } else if (uncache_bytes > 0) { |
| 50 | cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false); | ||
| 51 | uncache_bytes = 0; | ||
| 41 | } | 52 | } |
| 53 | if (count.load(std::memory_order::relaxed) == 1 && delta > 0) { | ||
| 54 | if (cache_bytes == 0) { | ||
| 55 | cache_begin = page; | ||
| 56 | } | ||
| 57 | cache_bytes += PAGE_SIZE; | ||
| 58 | } else if (cache_bytes > 0) { | ||
| 59 | cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true); | ||
| 60 | cache_bytes = 0; | ||
| 61 | } | ||
| 62 | } | ||
| 63 | if (uncache_bytes > 0) { | ||
| 64 | cpu_memory.RasterizerMarkRegionCached(uncache_begin << PAGE_BITS, uncache_bytes, false); | ||
| 65 | } | ||
| 66 | if (cache_bytes > 0) { | ||
| 67 | cpu_memory.RasterizerMarkRegionCached(cache_begin << PAGE_BITS, cache_bytes, true); | ||
| 42 | } | 68 | } |
| 43 | } | 69 | } |
| 44 | 70 | ||
diff --git a/src/video_core/rasterizer_interface.h b/src/video_core/rasterizer_interface.h index 50491b758..f968b5b16 100644 --- a/src/video_core/rasterizer_interface.h +++ b/src/video_core/rasterizer_interface.h | |||
| @@ -54,6 +54,9 @@ public: | |||
| 54 | virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, | 54 | virtual void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, |
| 55 | u32 size) = 0; | 55 | u32 size) = 0; |
| 56 | 56 | ||
| 57 | /// Signal disabling of a uniform buffer | ||
| 58 | virtual void DisableGraphicsUniformBuffer(size_t stage, u32 index) = 0; | ||
| 59 | |||
| 57 | /// Signal a GPU based semaphore as a fence | 60 | /// Signal a GPU based semaphore as a fence |
| 58 | virtual void SignalSemaphore(GPUVAddr addr, u32 value) = 0; | 61 | virtual void SignalSemaphore(GPUVAddr addr, u32 value) = 0; |
| 59 | 62 | ||
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.cpp b/src/video_core/renderer_opengl/gl_rasterizer.cpp index a5dbb9adf..f87bb269b 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.cpp +++ b/src/video_core/renderer_opengl/gl_rasterizer.cpp | |||
| @@ -526,6 +526,10 @@ void RasterizerOpenGL::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAd | |||
| 526 | buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size); | 526 | buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size); |
| 527 | } | 527 | } |
| 528 | 528 | ||
| 529 | void RasterizerOpenGL::DisableGraphicsUniformBuffer(size_t stage, u32 index) { | ||
| 530 | buffer_cache.DisableGraphicsUniformBuffer(stage, index); | ||
| 531 | } | ||
| 532 | |||
| 529 | void RasterizerOpenGL::FlushAll() {} | 533 | void RasterizerOpenGL::FlushAll() {} |
| 530 | 534 | ||
| 531 | void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { | 535 | void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { |
diff --git a/src/video_core/renderer_opengl/gl_rasterizer.h b/src/video_core/renderer_opengl/gl_rasterizer.h index 3745cf637..76298517f 100644 --- a/src/video_core/renderer_opengl/gl_rasterizer.h +++ b/src/video_core/renderer_opengl/gl_rasterizer.h | |||
| @@ -72,6 +72,7 @@ public: | |||
| 72 | void ResetCounter(VideoCore::QueryType type) override; | 72 | void ResetCounter(VideoCore::QueryType type) override; |
| 73 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; | 73 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; |
| 74 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; | 74 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; |
| 75 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; | ||
| 75 | void FlushAll() override; | 76 | void FlushAll() override; |
| 76 | void FlushRegion(VAddr addr, u64 size) override; | 77 | void FlushRegion(VAddr addr, u64 size) override; |
| 77 | bool MustFlushRegion(VAddr addr, u64 size) override; | 78 | bool MustFlushRegion(VAddr addr, u64 size) override; |
diff --git a/src/video_core/renderer_opengl/gl_texture_cache.cpp b/src/video_core/renderer_opengl/gl_texture_cache.cpp index ffe9edc1b..9b4038615 100644 --- a/src/video_core/renderer_opengl/gl_texture_cache.cpp +++ b/src/video_core/renderer_opengl/gl_texture_cache.cpp | |||
| @@ -9,6 +9,8 @@ | |||
| 9 | 9 | ||
| 10 | #include <glad/glad.h> | 10 | #include <glad/glad.h> |
| 11 | 11 | ||
| 12 | #include "common/settings.h" | ||
| 13 | |||
| 12 | #include "video_core/renderer_opengl/gl_device.h" | 14 | #include "video_core/renderer_opengl/gl_device.h" |
| 13 | #include "video_core/renderer_opengl/gl_shader_manager.h" | 15 | #include "video_core/renderer_opengl/gl_shader_manager.h" |
| 14 | #include "video_core/renderer_opengl/gl_state_tracker.h" | 16 | #include "video_core/renderer_opengl/gl_state_tracker.h" |
| @@ -307,7 +309,9 @@ void ApplySwizzle(GLuint handle, PixelFormat format, std::array<SwizzleSource, 4 | |||
| 307 | 309 | ||
| 308 | [[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime, | 310 | [[nodiscard]] bool CanBeAccelerated(const TextureCacheRuntime& runtime, |
| 309 | const VideoCommon::ImageInfo& info) { | 311 | const VideoCommon::ImageInfo& info) { |
| 310 | return !runtime.HasNativeASTC() && IsPixelFormatASTC(info.format); | 312 | if (IsPixelFormatASTC(info.format)) { |
| 313 | return !runtime.HasNativeASTC() && Settings::values.accelerate_astc.GetValue(); | ||
| 314 | } | ||
| 311 | // Disable other accelerated uploads for now as they don't implement swizzled uploads | 315 | // Disable other accelerated uploads for now as they don't implement swizzled uploads |
| 312 | return false; | 316 | return false; |
| 313 | switch (info.type) { | 317 | switch (info.type) { |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.cpp b/src/video_core/renderer_vulkan/vk_rasterizer.cpp index e9a0e7811..1c9120170 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.cpp +++ b/src/video_core/renderer_vulkan/vk_rasterizer.cpp | |||
| @@ -476,6 +476,10 @@ void RasterizerVulkan::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAd | |||
| 476 | buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size); | 476 | buffer_cache.BindGraphicsUniformBuffer(stage, index, gpu_addr, size); |
| 477 | } | 477 | } |
| 478 | 478 | ||
| 479 | void Vulkan::RasterizerVulkan::DisableGraphicsUniformBuffer(size_t stage, u32 index) { | ||
| 480 | buffer_cache.DisableGraphicsUniformBuffer(stage, index); | ||
| 481 | } | ||
| 482 | |||
| 479 | void RasterizerVulkan::FlushAll() {} | 483 | void RasterizerVulkan::FlushAll() {} |
| 480 | 484 | ||
| 481 | void RasterizerVulkan::FlushRegion(VAddr addr, u64 size) { | 485 | void RasterizerVulkan::FlushRegion(VAddr addr, u64 size) { |
diff --git a/src/video_core/renderer_vulkan/vk_rasterizer.h b/src/video_core/renderer_vulkan/vk_rasterizer.h index 235afc6f3..cb8c5c279 100644 --- a/src/video_core/renderer_vulkan/vk_rasterizer.h +++ b/src/video_core/renderer_vulkan/vk_rasterizer.h | |||
| @@ -64,6 +64,7 @@ public: | |||
| 64 | void ResetCounter(VideoCore::QueryType type) override; | 64 | void ResetCounter(VideoCore::QueryType type) override; |
| 65 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; | 65 | void Query(GPUVAddr gpu_addr, VideoCore::QueryType type, std::optional<u64> timestamp) override; |
| 66 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; | 66 | void BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, u32 size) override; |
| 67 | void DisableGraphicsUniformBuffer(size_t stage, u32 index) override; | ||
| 67 | void FlushAll() override; | 68 | void FlushAll() override; |
| 68 | void FlushRegion(VAddr addr, u64 size) override; | 69 | void FlushRegion(VAddr addr, u64 size) override; |
| 69 | bool MustFlushRegion(VAddr addr, u64 size) override; | 70 | bool MustFlushRegion(VAddr addr, u64 size) override; |
diff --git a/src/video_core/renderer_vulkan/vk_texture_cache.cpp b/src/video_core/renderer_vulkan/vk_texture_cache.cpp index bdd0ce8bc..52860b4cf 100644 --- a/src/video_core/renderer_vulkan/vk_texture_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_texture_cache.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <vector> | 8 | #include <vector> |
| 9 | 9 | ||
| 10 | #include "common/bit_cast.h" | 10 | #include "common/bit_cast.h" |
| 11 | #include "common/settings.h" | ||
| 11 | 12 | ||
| 12 | #include "video_core/engines/fermi_2d.h" | 13 | #include "video_core/engines/fermi_2d.h" |
| 13 | #include "video_core/renderer_vulkan/blit_image.h" | 14 | #include "video_core/renderer_vulkan/blit_image.h" |
| @@ -828,7 +829,11 @@ Image::Image(TextureCacheRuntime& runtime, const ImageInfo& info_, GPUVAddr gpu_ | |||
| 828 | commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal); | 829 | commit = runtime.memory_allocator.Commit(buffer, MemoryUsage::DeviceLocal); |
| 829 | } | 830 | } |
| 830 | if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) { | 831 | if (IsPixelFormatASTC(info.format) && !runtime.device.IsOptimalAstcSupported()) { |
| 831 | flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; | 832 | if (Settings::values.accelerate_astc.GetValue()) { |
| 833 | flags |= VideoCommon::ImageFlagBits::AcceleratedUpload; | ||
| 834 | } else { | ||
| 835 | flags |= VideoCommon::ImageFlagBits::Converted; | ||
| 836 | } | ||
| 832 | } | 837 | } |
| 833 | if (runtime.device.HasDebuggingToolAttached()) { | 838 | if (runtime.device.HasDebuggingToolAttached()) { |
| 834 | if (image) { | 839 | if (image) { |
diff --git a/src/video_core/texture_cache/util.cpp b/src/video_core/texture_cache/util.cpp index 8c4a5523b..6835fd747 100644 --- a/src/video_core/texture_cache/util.cpp +++ b/src/video_core/texture_cache/util.cpp | |||
| @@ -47,6 +47,7 @@ | |||
| 47 | #include "video_core/texture_cache/formatter.h" | 47 | #include "video_core/texture_cache/formatter.h" |
| 48 | #include "video_core/texture_cache/samples_helper.h" | 48 | #include "video_core/texture_cache/samples_helper.h" |
| 49 | #include "video_core/texture_cache/util.h" | 49 | #include "video_core/texture_cache/util.h" |
| 50 | #include "video_core/textures/astc.h" | ||
| 50 | #include "video_core/textures/decoders.h" | 51 | #include "video_core/textures/decoders.h" |
| 51 | 52 | ||
| 52 | namespace VideoCommon { | 53 | namespace VideoCommon { |
| @@ -647,6 +648,9 @@ u32 CalculateLayerSize(const ImageInfo& info) noexcept { | |||
| 647 | } | 648 | } |
| 648 | 649 | ||
| 649 | LevelArray CalculateMipLevelOffsets(const ImageInfo& info) noexcept { | 650 | LevelArray CalculateMipLevelOffsets(const ImageInfo& info) noexcept { |
| 651 | if (info.type == ImageType::Linear) { | ||
| 652 | return {}; | ||
| 653 | } | ||
| 650 | ASSERT(info.resources.levels <= static_cast<s32>(MAX_MIP_LEVELS)); | 654 | ASSERT(info.resources.levels <= static_cast<s32>(MAX_MIP_LEVELS)); |
| 651 | const LevelInfo level_info = MakeLevelInfo(info); | 655 | const LevelInfo level_info = MakeLevelInfo(info); |
| 652 | LevelArray offsets{}; | 656 | LevelArray offsets{}; |
| @@ -881,8 +885,16 @@ void ConvertImage(std::span<const u8> input, const ImageInfo& info, std::span<u8 | |||
| 881 | ASSERT(copy.image_extent == mip_size); | 885 | ASSERT(copy.image_extent == mip_size); |
| 882 | ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width)); | 886 | ASSERT(copy.buffer_row_length == Common::AlignUp(mip_size.width, tile_size.width)); |
| 883 | ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); | 887 | ASSERT(copy.buffer_image_height == Common::AlignUp(mip_size.height, tile_size.height)); |
| 884 | DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, | 888 | if (IsPixelFormatASTC(info.format)) { |
| 885 | output.subspan(output_offset)); | 889 | ASSERT(copy.image_extent.depth == 1); |
| 890 | Tegra::Texture::ASTC::Decompress(input.subspan(copy.buffer_offset), | ||
| 891 | copy.image_extent.width, copy.image_extent.height, | ||
| 892 | copy.image_subresource.num_layers, tile_size.width, | ||
| 893 | tile_size.height, output.subspan(output_offset)); | ||
| 894 | } else { | ||
| 895 | DecompressBC4(input.subspan(copy.buffer_offset), copy.image_extent, | ||
| 896 | output.subspan(output_offset)); | ||
| 897 | } | ||
| 886 | copy.buffer_offset = output_offset; | 898 | copy.buffer_offset = output_offset; |
| 887 | copy.buffer_row_length = mip_size.width; | 899 | copy.buffer_row_length = mip_size.width; |
| 888 | copy.buffer_image_height = mip_size.height; | 900 | copy.buffer_image_height = mip_size.height; |
| @@ -1084,7 +1096,15 @@ std::optional<SubresourceBase> FindSubresource(const ImageInfo& candidate, const | |||
| 1084 | return std::nullopt; | 1096 | return std::nullopt; |
| 1085 | } | 1097 | } |
| 1086 | const ImageInfo& existing = image.info; | 1098 | const ImageInfo& existing = image.info; |
| 1087 | if (False(options & RelaxedOptions::Format)) { | 1099 | if (True(options & RelaxedOptions::Format)) { |
| 1100 | // Format checking is relaxed, but we still have to check for matching bytes per block. | ||
| 1101 | // This avoids creating a view for blits on UE4 titles where formats with different bytes | ||
| 1102 | // per block are aliased. | ||
| 1103 | if (BytesPerBlock(existing.format) != BytesPerBlock(candidate.format)) { | ||
| 1104 | return std::nullopt; | ||
| 1105 | } | ||
| 1106 | } else { | ||
| 1107 | // Format comaptibility is not relaxed, ensure we are creating a view on a compatible format | ||
| 1088 | if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) { | 1108 | if (!IsViewCompatible(existing.format, candidate.format, broken_views, native_bgr)) { |
| 1089 | return std::nullopt; | 1109 | return std::nullopt; |
| 1090 | } | 1110 | } |
diff --git a/src/video_core/textures/astc.cpp b/src/video_core/textures/astc.cpp new file mode 100644 index 000000000..9b2177ebd --- /dev/null +++ b/src/video_core/textures/astc.cpp | |||
| @@ -0,0 +1,1577 @@ | |||
| 1 | // Copyright 2016 The University of North Carolina at Chapel Hill | ||
| 2 | // | ||
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | // you may not use this file except in compliance with the License. | ||
| 5 | // You may obtain a copy of the License at | ||
| 6 | // | ||
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | // | ||
| 9 | // Unless required by applicable law or agreed to in writing, software | ||
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | // See the License for the specific language governing permissions and | ||
| 13 | // limitations under the License. | ||
| 14 | // | ||
| 15 | // Please send all BUG REPORTS to <pavel@cs.unc.edu>. | ||
| 16 | // <http://gamma.cs.unc.edu/FasTC/> | ||
| 17 | |||
| 18 | #include <algorithm> | ||
| 19 | #include <cassert> | ||
| 20 | #include <cstring> | ||
| 21 | #include <span> | ||
| 22 | #include <vector> | ||
| 23 | |||
| 24 | #include <boost/container/static_vector.hpp> | ||
| 25 | |||
| 26 | #include "common/common_types.h" | ||
| 27 | #include "video_core/textures/astc.h" | ||
| 28 | |||
| 29 | class InputBitStream { | ||
| 30 | public: | ||
| 31 | constexpr explicit InputBitStream(std::span<const u8> data, size_t start_offset = 0) | ||
| 32 | : cur_byte{data.data()}, total_bits{data.size()}, next_bit{start_offset % 8} {} | ||
| 33 | |||
| 34 | constexpr size_t GetBitsRead() const { | ||
| 35 | return bits_read; | ||
| 36 | } | ||
| 37 | |||
| 38 | constexpr bool ReadBit() { | ||
| 39 | if (bits_read >= total_bits * 8) { | ||
| 40 | return 0; | ||
| 41 | } | ||
| 42 | const bool bit = ((*cur_byte >> next_bit) & 1) != 0; | ||
| 43 | ++next_bit; | ||
| 44 | while (next_bit >= 8) { | ||
| 45 | next_bit -= 8; | ||
| 46 | ++cur_byte; | ||
| 47 | } | ||
| 48 | ++bits_read; | ||
| 49 | return bit; | ||
| 50 | } | ||
| 51 | |||
| 52 | constexpr u32 ReadBits(std::size_t nBits) { | ||
| 53 | u32 ret = 0; | ||
| 54 | for (std::size_t i = 0; i < nBits; ++i) { | ||
| 55 | ret |= (ReadBit() & 1) << i; | ||
| 56 | } | ||
| 57 | return ret; | ||
| 58 | } | ||
| 59 | |||
| 60 | template <std::size_t nBits> | ||
| 61 | constexpr u32 ReadBits() { | ||
| 62 | u32 ret = 0; | ||
| 63 | for (std::size_t i = 0; i < nBits; ++i) { | ||
| 64 | ret |= (ReadBit() & 1) << i; | ||
| 65 | } | ||
| 66 | return ret; | ||
| 67 | } | ||
| 68 | |||
| 69 | private: | ||
| 70 | const u8* cur_byte; | ||
| 71 | size_t total_bits = 0; | ||
| 72 | size_t next_bit = 0; | ||
| 73 | size_t bits_read = 0; | ||
| 74 | }; | ||
| 75 | |||
| 76 | class OutputBitStream { | ||
| 77 | public: | ||
| 78 | constexpr explicit OutputBitStream(u8* ptr, std::size_t bits = 0, std::size_t start_offset = 0) | ||
| 79 | : cur_byte{ptr}, num_bits{bits}, next_bit{start_offset % 8} {} | ||
| 80 | |||
| 81 | constexpr std::size_t GetBitsWritten() const { | ||
| 82 | return bits_written; | ||
| 83 | } | ||
| 84 | |||
| 85 | constexpr void WriteBitsR(u32 val, u32 nBits) { | ||
| 86 | for (u32 i = 0; i < nBits; i++) { | ||
| 87 | WriteBit((val >> (nBits - i - 1)) & 1); | ||
| 88 | } | ||
| 89 | } | ||
| 90 | |||
| 91 | constexpr void WriteBits(u32 val, u32 nBits) { | ||
| 92 | for (u32 i = 0; i < nBits; i++) { | ||
| 93 | WriteBit((val >> i) & 1); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | private: | ||
| 98 | constexpr void WriteBit(bool b) { | ||
| 99 | if (bits_written >= num_bits) { | ||
| 100 | return; | ||
| 101 | } | ||
| 102 | |||
| 103 | const u32 mask = 1 << next_bit++; | ||
| 104 | |||
| 105 | // clear the bit | ||
| 106 | *cur_byte &= static_cast<u8>(~mask); | ||
| 107 | |||
| 108 | // Write the bit, if necessary | ||
| 109 | if (b) | ||
| 110 | *cur_byte |= static_cast<u8>(mask); | ||
| 111 | |||
| 112 | // Next byte? | ||
| 113 | if (next_bit >= 8) { | ||
| 114 | cur_byte += 1; | ||
| 115 | next_bit = 0; | ||
| 116 | } | ||
| 117 | } | ||
| 118 | |||
| 119 | u8* cur_byte; | ||
| 120 | std::size_t num_bits; | ||
| 121 | std::size_t bits_written = 0; | ||
| 122 | std::size_t next_bit = 0; | ||
| 123 | }; | ||
| 124 | |||
| 125 | template <typename IntType> | ||
| 126 | class Bits { | ||
| 127 | public: | ||
| 128 | explicit Bits(const IntType& v) : m_Bits(v) {} | ||
| 129 | |||
| 130 | Bits(const Bits&) = delete; | ||
| 131 | Bits& operator=(const Bits&) = delete; | ||
| 132 | |||
| 133 | u8 operator[](u32 bitPos) const { | ||
| 134 | return static_cast<u8>((m_Bits >> bitPos) & 1); | ||
| 135 | } | ||
| 136 | |||
| 137 | IntType operator()(u32 start, u32 end) const { | ||
| 138 | if (start == end) { | ||
| 139 | return (*this)[start]; | ||
| 140 | } else if (start > end) { | ||
| 141 | u32 t = start; | ||
| 142 | start = end; | ||
| 143 | end = t; | ||
| 144 | } | ||
| 145 | |||
| 146 | u64 mask = (1 << (end - start + 1)) - 1; | ||
| 147 | return (m_Bits >> start) & static_cast<IntType>(mask); | ||
| 148 | } | ||
| 149 | |||
| 150 | private: | ||
| 151 | const IntType& m_Bits; | ||
| 152 | }; | ||
| 153 | |||
| 154 | namespace Tegra::Texture::ASTC { | ||
| 155 | using IntegerEncodedVector = boost::container::static_vector< | ||
| 156 | IntegerEncodedValue, 256, | ||
| 157 | boost::container::static_vector_options< | ||
| 158 | boost::container::inplace_alignment<alignof(IntegerEncodedValue)>, | ||
| 159 | boost::container::throw_on_overflow<false>>::type>; | ||
| 160 | |||
| 161 | static void DecodeTritBlock(InputBitStream& bits, IntegerEncodedVector& result, u32 nBitsPerValue) { | ||
| 162 | // Implement the algorithm in section C.2.12 | ||
| 163 | std::array<u32, 5> m; | ||
| 164 | std::array<u32, 5> t; | ||
| 165 | u32 T; | ||
| 166 | |||
| 167 | // Read the trit encoded block according to | ||
| 168 | // table C.2.14 | ||
| 169 | m[0] = bits.ReadBits(nBitsPerValue); | ||
| 170 | T = bits.ReadBits<2>(); | ||
| 171 | m[1] = bits.ReadBits(nBitsPerValue); | ||
| 172 | T |= bits.ReadBits<2>() << 2; | ||
| 173 | m[2] = bits.ReadBits(nBitsPerValue); | ||
| 174 | T |= bits.ReadBit() << 4; | ||
| 175 | m[3] = bits.ReadBits(nBitsPerValue); | ||
| 176 | T |= bits.ReadBits<2>() << 5; | ||
| 177 | m[4] = bits.ReadBits(nBitsPerValue); | ||
| 178 | T |= bits.ReadBit() << 7; | ||
| 179 | |||
| 180 | u32 C = 0; | ||
| 181 | |||
| 182 | Bits<u32> Tb(T); | ||
| 183 | if (Tb(2, 4) == 7) { | ||
| 184 | C = (Tb(5, 7) << 2) | Tb(0, 1); | ||
| 185 | t[4] = t[3] = 2; | ||
| 186 | } else { | ||
| 187 | C = Tb(0, 4); | ||
| 188 | if (Tb(5, 6) == 3) { | ||
| 189 | t[4] = 2; | ||
| 190 | t[3] = Tb[7]; | ||
| 191 | } else { | ||
| 192 | t[4] = Tb[7]; | ||
| 193 | t[3] = Tb(5, 6); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | Bits<u32> Cb(C); | ||
| 198 | if (Cb(0, 1) == 3) { | ||
| 199 | t[2] = 2; | ||
| 200 | t[1] = Cb[4]; | ||
| 201 | t[0] = (Cb[3] << 1) | (Cb[2] & ~Cb[3]); | ||
| 202 | } else if (Cb(2, 3) == 3) { | ||
| 203 | t[2] = 2; | ||
| 204 | t[1] = 2; | ||
| 205 | t[0] = Cb(0, 1); | ||
| 206 | } else { | ||
| 207 | t[2] = Cb[4]; | ||
| 208 | t[1] = Cb(2, 3); | ||
| 209 | t[0] = (Cb[1] << 1) | (Cb[0] & ~Cb[1]); | ||
| 210 | } | ||
| 211 | |||
| 212 | for (std::size_t i = 0; i < 5; ++i) { | ||
| 213 | IntegerEncodedValue& val = result.emplace_back(IntegerEncoding::Trit, nBitsPerValue); | ||
| 214 | val.bit_value = m[i]; | ||
| 215 | val.trit_value = t[i]; | ||
| 216 | } | ||
| 217 | } | ||
| 218 | |||
| 219 | static void DecodeQuintBlock(InputBitStream& bits, IntegerEncodedVector& result, | ||
| 220 | u32 nBitsPerValue) { | ||
| 221 | // Implement the algorithm in section C.2.12 | ||
| 222 | u32 m[3]; | ||
| 223 | u32 q[3]; | ||
| 224 | u32 Q; | ||
| 225 | |||
| 226 | // Read the trit encoded block according to | ||
| 227 | // table C.2.15 | ||
| 228 | m[0] = bits.ReadBits(nBitsPerValue); | ||
| 229 | Q = bits.ReadBits<3>(); | ||
| 230 | m[1] = bits.ReadBits(nBitsPerValue); | ||
| 231 | Q |= bits.ReadBits<2>() << 3; | ||
| 232 | m[2] = bits.ReadBits(nBitsPerValue); | ||
| 233 | Q |= bits.ReadBits<2>() << 5; | ||
| 234 | |||
| 235 | Bits<u32> Qb(Q); | ||
| 236 | if (Qb(1, 2) == 3 && Qb(5, 6) == 0) { | ||
| 237 | q[0] = q[1] = 4; | ||
| 238 | q[2] = (Qb[0] << 2) | ((Qb[4] & ~Qb[0]) << 1) | (Qb[3] & ~Qb[0]); | ||
| 239 | } else { | ||
| 240 | u32 C = 0; | ||
| 241 | if (Qb(1, 2) == 3) { | ||
| 242 | q[2] = 4; | ||
| 243 | C = (Qb(3, 4) << 3) | ((~Qb(5, 6) & 3) << 1) | Qb[0]; | ||
| 244 | } else { | ||
| 245 | q[2] = Qb(5, 6); | ||
| 246 | C = Qb(0, 4); | ||
| 247 | } | ||
| 248 | |||
| 249 | Bits<u32> Cb(C); | ||
| 250 | if (Cb(0, 2) == 5) { | ||
| 251 | q[1] = 4; | ||
| 252 | q[0] = Cb(3, 4); | ||
| 253 | } else { | ||
| 254 | q[1] = Cb(3, 4); | ||
| 255 | q[0] = Cb(0, 2); | ||
| 256 | } | ||
| 257 | } | ||
| 258 | |||
| 259 | for (std::size_t i = 0; i < 3; ++i) { | ||
| 260 | IntegerEncodedValue& val = result.emplace_back(IntegerEncoding::Quint, nBitsPerValue); | ||
| 261 | val.bit_value = m[i]; | ||
| 262 | val.quint_value = q[i]; | ||
| 263 | } | ||
| 264 | } | ||
| 265 | |||
| 266 | // Fills result with the values that are encoded in the given | ||
| 267 | // bitstream. We must know beforehand what the maximum possible | ||
| 268 | // value is, and how many values we're decoding. | ||
| 269 | static void DecodeIntegerSequence(IntegerEncodedVector& result, InputBitStream& bits, u32 maxRange, | ||
| 270 | u32 nValues) { | ||
| 271 | // Determine encoding parameters | ||
| 272 | IntegerEncodedValue val = EncodingsValues[maxRange]; | ||
| 273 | |||
| 274 | // Start decoding | ||
| 275 | u32 nValsDecoded = 0; | ||
| 276 | while (nValsDecoded < nValues) { | ||
| 277 | switch (val.encoding) { | ||
| 278 | case IntegerEncoding::Quint: | ||
| 279 | DecodeQuintBlock(bits, result, val.num_bits); | ||
| 280 | nValsDecoded += 3; | ||
| 281 | break; | ||
| 282 | |||
| 283 | case IntegerEncoding::Trit: | ||
| 284 | DecodeTritBlock(bits, result, val.num_bits); | ||
| 285 | nValsDecoded += 5; | ||
| 286 | break; | ||
| 287 | |||
| 288 | case IntegerEncoding::JustBits: | ||
| 289 | val.bit_value = bits.ReadBits(val.num_bits); | ||
| 290 | result.push_back(val); | ||
| 291 | nValsDecoded++; | ||
| 292 | break; | ||
| 293 | } | ||
| 294 | } | ||
| 295 | } | ||
| 296 | |||
| 297 | struct TexelWeightParams { | ||
| 298 | u32 m_Width = 0; | ||
| 299 | u32 m_Height = 0; | ||
| 300 | bool m_bDualPlane = false; | ||
| 301 | u32 m_MaxWeight = 0; | ||
| 302 | bool m_bError = false; | ||
| 303 | bool m_bVoidExtentLDR = false; | ||
| 304 | bool m_bVoidExtentHDR = false; | ||
| 305 | |||
| 306 | u32 GetPackedBitSize() const { | ||
| 307 | // How many indices do we have? | ||
| 308 | u32 nIdxs = m_Height * m_Width; | ||
| 309 | if (m_bDualPlane) { | ||
| 310 | nIdxs *= 2; | ||
| 311 | } | ||
| 312 | |||
| 313 | return EncodingsValues[m_MaxWeight].GetBitLength(nIdxs); | ||
| 314 | } | ||
| 315 | |||
| 316 | u32 GetNumWeightValues() const { | ||
| 317 | u32 ret = m_Width * m_Height; | ||
| 318 | if (m_bDualPlane) { | ||
| 319 | ret *= 2; | ||
| 320 | } | ||
| 321 | return ret; | ||
| 322 | } | ||
| 323 | }; | ||
| 324 | |||
| 325 | static TexelWeightParams DecodeBlockInfo(InputBitStream& strm) { | ||
| 326 | TexelWeightParams params; | ||
| 327 | |||
| 328 | // Read the entire block mode all at once | ||
| 329 | u16 modeBits = static_cast<u16>(strm.ReadBits<11>()); | ||
| 330 | |||
| 331 | // Does this match the void extent block mode? | ||
| 332 | if ((modeBits & 0x01FF) == 0x1FC) { | ||
| 333 | if (modeBits & 0x200) { | ||
| 334 | params.m_bVoidExtentHDR = true; | ||
| 335 | } else { | ||
| 336 | params.m_bVoidExtentLDR = true; | ||
| 337 | } | ||
| 338 | |||
| 339 | // Next two bits must be one. | ||
| 340 | if (!(modeBits & 0x400) || !strm.ReadBit()) { | ||
| 341 | params.m_bError = true; | ||
| 342 | } | ||
| 343 | |||
| 344 | return params; | ||
| 345 | } | ||
| 346 | |||
| 347 | // First check if the last four bits are zero | ||
| 348 | if ((modeBits & 0xF) == 0) { | ||
| 349 | params.m_bError = true; | ||
| 350 | return params; | ||
| 351 | } | ||
| 352 | |||
| 353 | // If the last two bits are zero, then if bits | ||
| 354 | // [6-8] are all ones, this is also reserved. | ||
| 355 | if ((modeBits & 0x3) == 0 && (modeBits & 0x1C0) == 0x1C0) { | ||
| 356 | params.m_bError = true; | ||
| 357 | return params; | ||
| 358 | } | ||
| 359 | |||
| 360 | // Otherwise, there is no error... Figure out the layout | ||
| 361 | // of the block mode. Layout is determined by a number | ||
| 362 | // between 0 and 9 corresponding to table C.2.8 of the | ||
| 363 | // ASTC spec. | ||
| 364 | u32 layout = 0; | ||
| 365 | |||
| 366 | if ((modeBits & 0x1) || (modeBits & 0x2)) { | ||
| 367 | // layout is in [0-4] | ||
| 368 | if (modeBits & 0x8) { | ||
| 369 | // layout is in [2-4] | ||
| 370 | if (modeBits & 0x4) { | ||
| 371 | // layout is in [3-4] | ||
| 372 | if (modeBits & 0x100) { | ||
| 373 | layout = 4; | ||
| 374 | } else { | ||
| 375 | layout = 3; | ||
| 376 | } | ||
| 377 | } else { | ||
| 378 | layout = 2; | ||
| 379 | } | ||
| 380 | } else { | ||
| 381 | // layout is in [0-1] | ||
| 382 | if (modeBits & 0x4) { | ||
| 383 | layout = 1; | ||
| 384 | } else { | ||
| 385 | layout = 0; | ||
| 386 | } | ||
| 387 | } | ||
| 388 | } else { | ||
| 389 | // layout is in [5-9] | ||
| 390 | if (modeBits & 0x100) { | ||
| 391 | // layout is in [7-9] | ||
| 392 | if (modeBits & 0x80) { | ||
| 393 | // layout is in [7-8] | ||
| 394 | assert((modeBits & 0x40) == 0U); | ||
| 395 | if (modeBits & 0x20) { | ||
| 396 | layout = 8; | ||
| 397 | } else { | ||
| 398 | layout = 7; | ||
| 399 | } | ||
| 400 | } else { | ||
| 401 | layout = 9; | ||
| 402 | } | ||
| 403 | } else { | ||
| 404 | // layout is in [5-6] | ||
| 405 | if (modeBits & 0x80) { | ||
| 406 | layout = 6; | ||
| 407 | } else { | ||
| 408 | layout = 5; | ||
| 409 | } | ||
| 410 | } | ||
| 411 | } | ||
| 412 | |||
| 413 | assert(layout < 10); | ||
| 414 | |||
| 415 | // Determine R | ||
| 416 | u32 R = !!(modeBits & 0x10); | ||
| 417 | if (layout < 5) { | ||
| 418 | R |= (modeBits & 0x3) << 1; | ||
| 419 | } else { | ||
| 420 | R |= (modeBits & 0xC) >> 1; | ||
| 421 | } | ||
| 422 | assert(2 <= R && R <= 7); | ||
| 423 | |||
| 424 | // Determine width & height | ||
| 425 | switch (layout) { | ||
| 426 | case 0: { | ||
| 427 | u32 A = (modeBits >> 5) & 0x3; | ||
| 428 | u32 B = (modeBits >> 7) & 0x3; | ||
| 429 | params.m_Width = B + 4; | ||
| 430 | params.m_Height = A + 2; | ||
| 431 | break; | ||
| 432 | } | ||
| 433 | |||
| 434 | case 1: { | ||
| 435 | u32 A = (modeBits >> 5) & 0x3; | ||
| 436 | u32 B = (modeBits >> 7) & 0x3; | ||
| 437 | params.m_Width = B + 8; | ||
| 438 | params.m_Height = A + 2; | ||
| 439 | break; | ||
| 440 | } | ||
| 441 | |||
| 442 | case 2: { | ||
| 443 | u32 A = (modeBits >> 5) & 0x3; | ||
| 444 | u32 B = (modeBits >> 7) & 0x3; | ||
| 445 | params.m_Width = A + 2; | ||
| 446 | params.m_Height = B + 8; | ||
| 447 | break; | ||
| 448 | } | ||
| 449 | |||
| 450 | case 3: { | ||
| 451 | u32 A = (modeBits >> 5) & 0x3; | ||
| 452 | u32 B = (modeBits >> 7) & 0x1; | ||
| 453 | params.m_Width = A + 2; | ||
| 454 | params.m_Height = B + 6; | ||
| 455 | break; | ||
| 456 | } | ||
| 457 | |||
| 458 | case 4: { | ||
| 459 | u32 A = (modeBits >> 5) & 0x3; | ||
| 460 | u32 B = (modeBits >> 7) & 0x1; | ||
| 461 | params.m_Width = B + 2; | ||
| 462 | params.m_Height = A + 2; | ||
| 463 | break; | ||
| 464 | } | ||
| 465 | |||
| 466 | case 5: { | ||
| 467 | u32 A = (modeBits >> 5) & 0x3; | ||
| 468 | params.m_Width = 12; | ||
| 469 | params.m_Height = A + 2; | ||
| 470 | break; | ||
| 471 | } | ||
| 472 | |||
| 473 | case 6: { | ||
| 474 | u32 A = (modeBits >> 5) & 0x3; | ||
| 475 | params.m_Width = A + 2; | ||
| 476 | params.m_Height = 12; | ||
| 477 | break; | ||
| 478 | } | ||
| 479 | |||
| 480 | case 7: { | ||
| 481 | params.m_Width = 6; | ||
| 482 | params.m_Height = 10; | ||
| 483 | break; | ||
| 484 | } | ||
| 485 | |||
| 486 | case 8: { | ||
| 487 | params.m_Width = 10; | ||
| 488 | params.m_Height = 6; | ||
| 489 | break; | ||
| 490 | } | ||
| 491 | |||
| 492 | case 9: { | ||
| 493 | u32 A = (modeBits >> 5) & 0x3; | ||
| 494 | u32 B = (modeBits >> 9) & 0x3; | ||
| 495 | params.m_Width = A + 6; | ||
| 496 | params.m_Height = B + 6; | ||
| 497 | break; | ||
| 498 | } | ||
| 499 | |||
| 500 | default: | ||
| 501 | assert(false && "Don't know this layout..."); | ||
| 502 | params.m_bError = true; | ||
| 503 | break; | ||
| 504 | } | ||
| 505 | |||
| 506 | // Determine whether or not we're using dual planes | ||
| 507 | // and/or high precision layouts. | ||
| 508 | bool D = (layout != 9) && (modeBits & 0x400); | ||
| 509 | bool H = (layout != 9) && (modeBits & 0x200); | ||
| 510 | |||
| 511 | if (H) { | ||
| 512 | const u32 maxWeights[6] = {9, 11, 15, 19, 23, 31}; | ||
| 513 | params.m_MaxWeight = maxWeights[R - 2]; | ||
| 514 | } else { | ||
| 515 | const u32 maxWeights[6] = {1, 2, 3, 4, 5, 7}; | ||
| 516 | params.m_MaxWeight = maxWeights[R - 2]; | ||
| 517 | } | ||
| 518 | |||
| 519 | params.m_bDualPlane = D; | ||
| 520 | |||
| 521 | return params; | ||
| 522 | } | ||
| 523 | |||
| 524 | static void FillVoidExtentLDR(InputBitStream& strm, std::span<u32> outBuf, u32 blockWidth, | ||
| 525 | u32 blockHeight) { | ||
| 526 | // Don't actually care about the void extent, just read the bits... | ||
| 527 | for (s32 i = 0; i < 4; ++i) { | ||
| 528 | strm.ReadBits<13>(); | ||
| 529 | } | ||
| 530 | |||
| 531 | // Decode the RGBA components and renormalize them to the range [0, 255] | ||
| 532 | u16 r = static_cast<u16>(strm.ReadBits<16>()); | ||
| 533 | u16 g = static_cast<u16>(strm.ReadBits<16>()); | ||
| 534 | u16 b = static_cast<u16>(strm.ReadBits<16>()); | ||
| 535 | u16 a = static_cast<u16>(strm.ReadBits<16>()); | ||
| 536 | |||
| 537 | u32 rgba = (r >> 8) | (g & 0xFF00) | (static_cast<u32>(b) & 0xFF00) << 8 | | ||
| 538 | (static_cast<u32>(a) & 0xFF00) << 16; | ||
| 539 | |||
| 540 | for (u32 j = 0; j < blockHeight; j++) { | ||
| 541 | for (u32 i = 0; i < blockWidth; i++) { | ||
| 542 | outBuf[j * blockWidth + i] = rgba; | ||
| 543 | } | ||
| 544 | } | ||
| 545 | } | ||
| 546 | |||
| 547 | static void FillError(std::span<u32> outBuf, u32 blockWidth, u32 blockHeight) { | ||
| 548 | for (u32 j = 0; j < blockHeight; j++) { | ||
| 549 | for (u32 i = 0; i < blockWidth; i++) { | ||
| 550 | outBuf[j * blockWidth + i] = 0xFFFF00FF; | ||
| 551 | } | ||
| 552 | } | ||
| 553 | } | ||
| 554 | static constexpr u32 ReplicateByteTo16(std::size_t value) { | ||
| 555 | return REPLICATE_BYTE_TO_16_TABLE[value]; | ||
| 556 | } | ||
| 557 | |||
| 558 | static constexpr auto REPLICATE_BIT_TO_7_TABLE = MakeReplicateTable<u32, 1, 7>(); | ||
| 559 | static constexpr u32 ReplicateBitTo7(std::size_t value) { | ||
| 560 | return REPLICATE_BIT_TO_7_TABLE[value]; | ||
| 561 | } | ||
| 562 | |||
| 563 | static constexpr auto REPLICATE_BIT_TO_9_TABLE = MakeReplicateTable<u32, 1, 9>(); | ||
| 564 | static constexpr u32 ReplicateBitTo9(std::size_t value) { | ||
| 565 | return REPLICATE_BIT_TO_9_TABLE[value]; | ||
| 566 | } | ||
| 567 | |||
| 568 | static constexpr auto REPLICATE_1_BIT_TO_8_TABLE = MakeReplicateTable<u32, 1, 8>(); | ||
| 569 | static constexpr auto REPLICATE_2_BIT_TO_8_TABLE = MakeReplicateTable<u32, 2, 8>(); | ||
| 570 | static constexpr auto REPLICATE_3_BIT_TO_8_TABLE = MakeReplicateTable<u32, 3, 8>(); | ||
| 571 | static constexpr auto REPLICATE_4_BIT_TO_8_TABLE = MakeReplicateTable<u32, 4, 8>(); | ||
| 572 | static constexpr auto REPLICATE_5_BIT_TO_8_TABLE = MakeReplicateTable<u32, 5, 8>(); | ||
| 573 | /// Use a precompiled table with the most common usages, if it's not in the expected range, fallback | ||
| 574 | /// to the runtime implementation | ||
| 575 | static constexpr u32 FastReplicateTo8(u32 value, u32 num_bits) { | ||
| 576 | switch (num_bits) { | ||
| 577 | case 1: | ||
| 578 | return REPLICATE_1_BIT_TO_8_TABLE[value]; | ||
| 579 | case 2: | ||
| 580 | return REPLICATE_2_BIT_TO_8_TABLE[value]; | ||
| 581 | case 3: | ||
| 582 | return REPLICATE_3_BIT_TO_8_TABLE[value]; | ||
| 583 | case 4: | ||
| 584 | return REPLICATE_4_BIT_TO_8_TABLE[value]; | ||
| 585 | case 5: | ||
| 586 | return REPLICATE_5_BIT_TO_8_TABLE[value]; | ||
| 587 | case 6: | ||
| 588 | return REPLICATE_6_BIT_TO_8_TABLE[value]; | ||
| 589 | case 7: | ||
| 590 | return REPLICATE_7_BIT_TO_8_TABLE[value]; | ||
| 591 | case 8: | ||
| 592 | return REPLICATE_8_BIT_TO_8_TABLE[value]; | ||
| 593 | default: | ||
| 594 | return Replicate(value, num_bits, 8); | ||
| 595 | } | ||
| 596 | } | ||
| 597 | |||
| 598 | static constexpr auto REPLICATE_1_BIT_TO_6_TABLE = MakeReplicateTable<u32, 1, 6>(); | ||
| 599 | static constexpr auto REPLICATE_2_BIT_TO_6_TABLE = MakeReplicateTable<u32, 2, 6>(); | ||
| 600 | static constexpr auto REPLICATE_3_BIT_TO_6_TABLE = MakeReplicateTable<u32, 3, 6>(); | ||
| 601 | static constexpr auto REPLICATE_4_BIT_TO_6_TABLE = MakeReplicateTable<u32, 4, 6>(); | ||
| 602 | static constexpr auto REPLICATE_5_BIT_TO_6_TABLE = MakeReplicateTable<u32, 5, 6>(); | ||
| 603 | static constexpr u32 FastReplicateTo6(u32 value, u32 num_bits) { | ||
| 604 | switch (num_bits) { | ||
| 605 | case 1: | ||
| 606 | return REPLICATE_1_BIT_TO_6_TABLE[value]; | ||
| 607 | case 2: | ||
| 608 | return REPLICATE_2_BIT_TO_6_TABLE[value]; | ||
| 609 | case 3: | ||
| 610 | return REPLICATE_3_BIT_TO_6_TABLE[value]; | ||
| 611 | case 4: | ||
| 612 | return REPLICATE_4_BIT_TO_6_TABLE[value]; | ||
| 613 | case 5: | ||
| 614 | return REPLICATE_5_BIT_TO_6_TABLE[value]; | ||
| 615 | default: | ||
| 616 | return Replicate(value, num_bits, 6); | ||
| 617 | } | ||
| 618 | } | ||
| 619 | |||
| 620 | class Pixel { | ||
| 621 | protected: | ||
| 622 | using ChannelType = s16; | ||
| 623 | u8 m_BitDepth[4] = {8, 8, 8, 8}; | ||
| 624 | s16 color[4] = {}; | ||
| 625 | |||
| 626 | public: | ||
| 627 | Pixel() = default; | ||
| 628 | Pixel(u32 a, u32 r, u32 g, u32 b, u32 bitDepth = 8) | ||
| 629 | : m_BitDepth{u8(bitDepth), u8(bitDepth), u8(bitDepth), u8(bitDepth)}, | ||
| 630 | color{static_cast<ChannelType>(a), static_cast<ChannelType>(r), | ||
| 631 | static_cast<ChannelType>(g), static_cast<ChannelType>(b)} {} | ||
| 632 | |||
| 633 | // Changes the depth of each pixel. This scales the values to | ||
| 634 | // the appropriate bit depth by either truncating the least | ||
| 635 | // significant bits when going from larger to smaller bit depth | ||
| 636 | // or by repeating the most significant bits when going from | ||
| 637 | // smaller to larger bit depths. | ||
| 638 | void ChangeBitDepth() { | ||
| 639 | for (u32 i = 0; i < 4; i++) { | ||
| 640 | Component(i) = ChangeBitDepth(Component(i), m_BitDepth[i]); | ||
| 641 | m_BitDepth[i] = 8; | ||
| 642 | } | ||
| 643 | } | ||
| 644 | |||
| 645 | template <typename IntType> | ||
| 646 | static float ConvertChannelToFloat(IntType channel, u8 bitDepth) { | ||
| 647 | float denominator = static_cast<float>((1 << bitDepth) - 1); | ||
| 648 | return static_cast<float>(channel) / denominator; | ||
| 649 | } | ||
| 650 | |||
| 651 | // Changes the bit depth of a single component. See the comment | ||
| 652 | // above for how we do this. | ||
| 653 | static ChannelType ChangeBitDepth(Pixel::ChannelType val, u8 oldDepth) { | ||
| 654 | assert(oldDepth <= 8); | ||
| 655 | |||
| 656 | if (oldDepth == 8) { | ||
| 657 | // Do nothing | ||
| 658 | return val; | ||
| 659 | } else if (oldDepth == 0) { | ||
| 660 | return static_cast<ChannelType>((1 << 8) - 1); | ||
| 661 | } else if (8 > oldDepth) { | ||
| 662 | return static_cast<ChannelType>(FastReplicateTo8(static_cast<u32>(val), oldDepth)); | ||
| 663 | } else { | ||
| 664 | // oldDepth > newDepth | ||
| 665 | const u8 bitsWasted = static_cast<u8>(oldDepth - 8); | ||
| 666 | u16 v = static_cast<u16>(val); | ||
| 667 | v = static_cast<u16>((v + (1 << (bitsWasted - 1))) >> bitsWasted); | ||
| 668 | v = ::std::min<u16>(::std::max<u16>(0, v), static_cast<u16>((1 << 8) - 1)); | ||
| 669 | return static_cast<u8>(v); | ||
| 670 | } | ||
| 671 | |||
| 672 | assert(false && "We shouldn't get here."); | ||
| 673 | return 0; | ||
| 674 | } | ||
| 675 | |||
| 676 | const ChannelType& A() const { | ||
| 677 | return color[0]; | ||
| 678 | } | ||
| 679 | ChannelType& A() { | ||
| 680 | return color[0]; | ||
| 681 | } | ||
| 682 | const ChannelType& R() const { | ||
| 683 | return color[1]; | ||
| 684 | } | ||
| 685 | ChannelType& R() { | ||
| 686 | return color[1]; | ||
| 687 | } | ||
| 688 | const ChannelType& G() const { | ||
| 689 | return color[2]; | ||
| 690 | } | ||
| 691 | ChannelType& G() { | ||
| 692 | return color[2]; | ||
| 693 | } | ||
| 694 | const ChannelType& B() const { | ||
| 695 | return color[3]; | ||
| 696 | } | ||
| 697 | ChannelType& B() { | ||
| 698 | return color[3]; | ||
| 699 | } | ||
| 700 | const ChannelType& Component(u32 idx) const { | ||
| 701 | return color[idx]; | ||
| 702 | } | ||
| 703 | ChannelType& Component(u32 idx) { | ||
| 704 | return color[idx]; | ||
| 705 | } | ||
| 706 | |||
| 707 | void GetBitDepth(u8 (&outDepth)[4]) const { | ||
| 708 | for (s32 i = 0; i < 4; i++) { | ||
| 709 | outDepth[i] = m_BitDepth[i]; | ||
| 710 | } | ||
| 711 | } | ||
| 712 | |||
| 713 | // Take all of the components, transform them to their 8-bit variants, | ||
| 714 | // and then pack each channel into an R8G8B8A8 32-bit integer. We assume | ||
| 715 | // that the architecture is little-endian, so the alpha channel will end | ||
| 716 | // up in the most-significant byte. | ||
| 717 | u32 Pack() const { | ||
| 718 | Pixel eightBit(*this); | ||
| 719 | eightBit.ChangeBitDepth(); | ||
| 720 | |||
| 721 | u32 r = 0; | ||
| 722 | r |= eightBit.A(); | ||
| 723 | r <<= 8; | ||
| 724 | r |= eightBit.B(); | ||
| 725 | r <<= 8; | ||
| 726 | r |= eightBit.G(); | ||
| 727 | r <<= 8; | ||
| 728 | r |= eightBit.R(); | ||
| 729 | return r; | ||
| 730 | } | ||
| 731 | |||
| 732 | // Clamps the pixel to the range [0,255] | ||
| 733 | void ClampByte() { | ||
| 734 | for (u32 i = 0; i < 4; i++) { | ||
| 735 | color[i] = (color[i] < 0) ? 0 : ((color[i] > 255) ? 255 : color[i]); | ||
| 736 | } | ||
| 737 | } | ||
| 738 | |||
| 739 | void MakeOpaque() { | ||
| 740 | A() = 255; | ||
| 741 | } | ||
| 742 | }; | ||
| 743 | |||
| 744 | static void DecodeColorValues(u32* out, std::span<u8> data, const u32* modes, const u32 nPartitions, | ||
| 745 | const u32 nBitsForColorData) { | ||
| 746 | // First figure out how many color values we have | ||
| 747 | u32 nValues = 0; | ||
| 748 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 749 | nValues += ((modes[i] >> 2) + 1) << 1; | ||
| 750 | } | ||
| 751 | |||
| 752 | // Then based on the number of values and the remaining number of bits, | ||
| 753 | // figure out the max value for each of them... | ||
| 754 | u32 range = 256; | ||
| 755 | while (--range > 0) { | ||
| 756 | IntegerEncodedValue val = EncodingsValues[range]; | ||
| 757 | u32 bitLength = val.GetBitLength(nValues); | ||
| 758 | if (bitLength <= nBitsForColorData) { | ||
| 759 | // Find the smallest possible range that matches the given encoding | ||
| 760 | while (--range > 0) { | ||
| 761 | IntegerEncodedValue newval = EncodingsValues[range]; | ||
| 762 | if (!newval.MatchesEncoding(val)) { | ||
| 763 | break; | ||
| 764 | } | ||
| 765 | } | ||
| 766 | |||
| 767 | // Return to last matching range. | ||
| 768 | range++; | ||
| 769 | break; | ||
| 770 | } | ||
| 771 | } | ||
| 772 | |||
| 773 | // We now have enough to decode our integer sequence. | ||
| 774 | IntegerEncodedVector decodedColorValues; | ||
| 775 | |||
| 776 | InputBitStream colorStream(data, 0); | ||
| 777 | DecodeIntegerSequence(decodedColorValues, colorStream, range, nValues); | ||
| 778 | |||
| 779 | // Once we have the decoded values, we need to dequantize them to the 0-255 range | ||
| 780 | // This procedure is outlined in ASTC spec C.2.13 | ||
| 781 | u32 outIdx = 0; | ||
| 782 | for (auto itr = decodedColorValues.begin(); itr != decodedColorValues.end(); ++itr) { | ||
| 783 | // Have we already decoded all that we need? | ||
| 784 | if (outIdx >= nValues) { | ||
| 785 | break; | ||
| 786 | } | ||
| 787 | |||
| 788 | const IntegerEncodedValue& val = *itr; | ||
| 789 | u32 bitlen = val.num_bits; | ||
| 790 | u32 bitval = val.bit_value; | ||
| 791 | |||
| 792 | assert(bitlen >= 1); | ||
| 793 | |||
| 794 | u32 A = 0, B = 0, C = 0, D = 0; | ||
| 795 | // A is just the lsb replicated 9 times. | ||
| 796 | A = ReplicateBitTo9(bitval & 1); | ||
| 797 | |||
| 798 | switch (val.encoding) { | ||
| 799 | // Replicate bits | ||
| 800 | case IntegerEncoding::JustBits: | ||
| 801 | out[outIdx++] = FastReplicateTo8(bitval, bitlen); | ||
| 802 | break; | ||
| 803 | |||
| 804 | // Use algorithm in C.2.13 | ||
| 805 | case IntegerEncoding::Trit: { | ||
| 806 | |||
| 807 | D = val.trit_value; | ||
| 808 | |||
| 809 | switch (bitlen) { | ||
| 810 | case 1: { | ||
| 811 | C = 204; | ||
| 812 | } break; | ||
| 813 | |||
| 814 | case 2: { | ||
| 815 | C = 93; | ||
| 816 | // B = b000b0bb0 | ||
| 817 | u32 b = (bitval >> 1) & 1; | ||
| 818 | B = (b << 8) | (b << 4) | (b << 2) | (b << 1); | ||
| 819 | } break; | ||
| 820 | |||
| 821 | case 3: { | ||
| 822 | C = 44; | ||
| 823 | // B = cb000cbcb | ||
| 824 | u32 cb = (bitval >> 1) & 3; | ||
| 825 | B = (cb << 7) | (cb << 2) | cb; | ||
| 826 | } break; | ||
| 827 | |||
| 828 | case 4: { | ||
| 829 | C = 22; | ||
| 830 | // B = dcb000dcb | ||
| 831 | u32 dcb = (bitval >> 1) & 7; | ||
| 832 | B = (dcb << 6) | dcb; | ||
| 833 | } break; | ||
| 834 | |||
| 835 | case 5: { | ||
| 836 | C = 11; | ||
| 837 | // B = edcb000ed | ||
| 838 | u32 edcb = (bitval >> 1) & 0xF; | ||
| 839 | B = (edcb << 5) | (edcb >> 2); | ||
| 840 | } break; | ||
| 841 | |||
| 842 | case 6: { | ||
| 843 | C = 5; | ||
| 844 | // B = fedcb000f | ||
| 845 | u32 fedcb = (bitval >> 1) & 0x1F; | ||
| 846 | B = (fedcb << 4) | (fedcb >> 4); | ||
| 847 | } break; | ||
| 848 | |||
| 849 | default: | ||
| 850 | assert(false && "Unsupported trit encoding for color values!"); | ||
| 851 | break; | ||
| 852 | } // switch(bitlen) | ||
| 853 | } // case IntegerEncoding::Trit | ||
| 854 | break; | ||
| 855 | |||
| 856 | case IntegerEncoding::Quint: { | ||
| 857 | |||
| 858 | D = val.quint_value; | ||
| 859 | |||
| 860 | switch (bitlen) { | ||
| 861 | case 1: { | ||
| 862 | C = 113; | ||
| 863 | } break; | ||
| 864 | |||
| 865 | case 2: { | ||
| 866 | C = 54; | ||
| 867 | // B = b0000bb00 | ||
| 868 | u32 b = (bitval >> 1) & 1; | ||
| 869 | B = (b << 8) | (b << 3) | (b << 2); | ||
| 870 | } break; | ||
| 871 | |||
| 872 | case 3: { | ||
| 873 | C = 26; | ||
| 874 | // B = cb0000cbc | ||
| 875 | u32 cb = (bitval >> 1) & 3; | ||
| 876 | B = (cb << 7) | (cb << 1) | (cb >> 1); | ||
| 877 | } break; | ||
| 878 | |||
| 879 | case 4: { | ||
| 880 | C = 13; | ||
| 881 | // B = dcb0000dc | ||
| 882 | u32 dcb = (bitval >> 1) & 7; | ||
| 883 | B = (dcb << 6) | (dcb >> 1); | ||
| 884 | } break; | ||
| 885 | |||
| 886 | case 5: { | ||
| 887 | C = 6; | ||
| 888 | // B = edcb0000e | ||
| 889 | u32 edcb = (bitval >> 1) & 0xF; | ||
| 890 | B = (edcb << 5) | (edcb >> 3); | ||
| 891 | } break; | ||
| 892 | |||
| 893 | default: | ||
| 894 | assert(false && "Unsupported quint encoding for color values!"); | ||
| 895 | break; | ||
| 896 | } // switch(bitlen) | ||
| 897 | } // case IntegerEncoding::Quint | ||
| 898 | break; | ||
| 899 | } // switch(val.encoding) | ||
| 900 | |||
| 901 | if (val.encoding != IntegerEncoding::JustBits) { | ||
| 902 | u32 T = D * C + B; | ||
| 903 | T ^= A; | ||
| 904 | T = (A & 0x80) | (T >> 2); | ||
| 905 | out[outIdx++] = T; | ||
| 906 | } | ||
| 907 | } | ||
| 908 | |||
| 909 | // Make sure that each of our values is in the proper range... | ||
| 910 | for (u32 i = 0; i < nValues; i++) { | ||
| 911 | assert(out[i] <= 255); | ||
| 912 | } | ||
| 913 | } | ||
| 914 | |||
| 915 | static u32 UnquantizeTexelWeight(const IntegerEncodedValue& val) { | ||
| 916 | u32 bitval = val.bit_value; | ||
| 917 | u32 bitlen = val.num_bits; | ||
| 918 | |||
| 919 | u32 A = ReplicateBitTo7(bitval & 1); | ||
| 920 | u32 B = 0, C = 0, D = 0; | ||
| 921 | |||
| 922 | u32 result = 0; | ||
| 923 | switch (val.encoding) { | ||
| 924 | case IntegerEncoding::JustBits: | ||
| 925 | result = FastReplicateTo6(bitval, bitlen); | ||
| 926 | break; | ||
| 927 | |||
| 928 | case IntegerEncoding::Trit: { | ||
| 929 | D = val.trit_value; | ||
| 930 | assert(D < 3); | ||
| 931 | |||
| 932 | switch (bitlen) { | ||
| 933 | case 0: { | ||
| 934 | u32 results[3] = {0, 32, 63}; | ||
| 935 | result = results[D]; | ||
| 936 | } break; | ||
| 937 | |||
| 938 | case 1: { | ||
| 939 | C = 50; | ||
| 940 | } break; | ||
| 941 | |||
| 942 | case 2: { | ||
| 943 | C = 23; | ||
| 944 | u32 b = (bitval >> 1) & 1; | ||
| 945 | B = (b << 6) | (b << 2) | b; | ||
| 946 | } break; | ||
| 947 | |||
| 948 | case 3: { | ||
| 949 | C = 11; | ||
| 950 | u32 cb = (bitval >> 1) & 3; | ||
| 951 | B = (cb << 5) | cb; | ||
| 952 | } break; | ||
| 953 | |||
| 954 | default: | ||
| 955 | assert(false && "Invalid trit encoding for texel weight"); | ||
| 956 | break; | ||
| 957 | } | ||
| 958 | } break; | ||
| 959 | |||
| 960 | case IntegerEncoding::Quint: { | ||
| 961 | D = val.quint_value; | ||
| 962 | assert(D < 5); | ||
| 963 | |||
| 964 | switch (bitlen) { | ||
| 965 | case 0: { | ||
| 966 | u32 results[5] = {0, 16, 32, 47, 63}; | ||
| 967 | result = results[D]; | ||
| 968 | } break; | ||
| 969 | |||
| 970 | case 1: { | ||
| 971 | C = 28; | ||
| 972 | } break; | ||
| 973 | |||
| 974 | case 2: { | ||
| 975 | C = 13; | ||
| 976 | u32 b = (bitval >> 1) & 1; | ||
| 977 | B = (b << 6) | (b << 1); | ||
| 978 | } break; | ||
| 979 | |||
| 980 | default: | ||
| 981 | assert(false && "Invalid quint encoding for texel weight"); | ||
| 982 | break; | ||
| 983 | } | ||
| 984 | } break; | ||
| 985 | } | ||
| 986 | |||
| 987 | if (val.encoding != IntegerEncoding::JustBits && bitlen > 0) { | ||
| 988 | // Decode the value... | ||
| 989 | result = D * C + B; | ||
| 990 | result ^= A; | ||
| 991 | result = (A & 0x20) | (result >> 2); | ||
| 992 | } | ||
| 993 | |||
| 994 | assert(result < 64); | ||
| 995 | |||
| 996 | // Change from [0,63] to [0,64] | ||
| 997 | if (result > 32) { | ||
| 998 | result += 1; | ||
| 999 | } | ||
| 1000 | |||
| 1001 | return result; | ||
| 1002 | } | ||
| 1003 | |||
| 1004 | static void UnquantizeTexelWeights(u32 out[2][144], const IntegerEncodedVector& weights, | ||
| 1005 | const TexelWeightParams& params, const u32 blockWidth, | ||
| 1006 | const u32 blockHeight) { | ||
| 1007 | u32 weightIdx = 0; | ||
| 1008 | u32 unquantized[2][144]; | ||
| 1009 | |||
| 1010 | for (auto itr = weights.begin(); itr != weights.end(); ++itr) { | ||
| 1011 | unquantized[0][weightIdx] = UnquantizeTexelWeight(*itr); | ||
| 1012 | |||
| 1013 | if (params.m_bDualPlane) { | ||
| 1014 | ++itr; | ||
| 1015 | unquantized[1][weightIdx] = UnquantizeTexelWeight(*itr); | ||
| 1016 | if (itr == weights.end()) { | ||
| 1017 | break; | ||
| 1018 | } | ||
| 1019 | } | ||
| 1020 | |||
| 1021 | if (++weightIdx >= (params.m_Width * params.m_Height)) | ||
| 1022 | break; | ||
| 1023 | } | ||
| 1024 | |||
| 1025 | // Do infill if necessary (Section C.2.18) ... | ||
| 1026 | u32 Ds = (1024 + (blockWidth / 2)) / (blockWidth - 1); | ||
| 1027 | u32 Dt = (1024 + (blockHeight / 2)) / (blockHeight - 1); | ||
| 1028 | |||
| 1029 | const u32 kPlaneScale = params.m_bDualPlane ? 2U : 1U; | ||
| 1030 | for (u32 plane = 0; plane < kPlaneScale; plane++) | ||
| 1031 | for (u32 t = 0; t < blockHeight; t++) | ||
| 1032 | for (u32 s = 0; s < blockWidth; s++) { | ||
| 1033 | u32 cs = Ds * s; | ||
| 1034 | u32 ct = Dt * t; | ||
| 1035 | |||
| 1036 | u32 gs = (cs * (params.m_Width - 1) + 32) >> 6; | ||
| 1037 | u32 gt = (ct * (params.m_Height - 1) + 32) >> 6; | ||
| 1038 | |||
| 1039 | u32 js = gs >> 4; | ||
| 1040 | u32 fs = gs & 0xF; | ||
| 1041 | |||
| 1042 | u32 jt = gt >> 4; | ||
| 1043 | u32 ft = gt & 0x0F; | ||
| 1044 | |||
| 1045 | u32 w11 = (fs * ft + 8) >> 4; | ||
| 1046 | u32 w10 = ft - w11; | ||
| 1047 | u32 w01 = fs - w11; | ||
| 1048 | u32 w00 = 16 - fs - ft + w11; | ||
| 1049 | |||
| 1050 | u32 v0 = js + jt * params.m_Width; | ||
| 1051 | |||
| 1052 | #define FIND_TEXEL(tidx, bidx) \ | ||
| 1053 | u32 p##bidx = 0; \ | ||
| 1054 | do { \ | ||
| 1055 | if ((tidx) < (params.m_Width * params.m_Height)) { \ | ||
| 1056 | p##bidx = unquantized[plane][(tidx)]; \ | ||
| 1057 | } \ | ||
| 1058 | } while (0) | ||
| 1059 | |||
| 1060 | FIND_TEXEL(v0, 00); | ||
| 1061 | FIND_TEXEL(v0 + 1, 01); | ||
| 1062 | FIND_TEXEL(v0 + params.m_Width, 10); | ||
| 1063 | FIND_TEXEL(v0 + params.m_Width + 1, 11); | ||
| 1064 | |||
| 1065 | #undef FIND_TEXEL | ||
| 1066 | |||
| 1067 | out[plane][t * blockWidth + s] = | ||
| 1068 | (p00 * w00 + p01 * w01 + p10 * w10 + p11 * w11 + 8) >> 4; | ||
| 1069 | } | ||
| 1070 | } | ||
| 1071 | |||
| 1072 | // Transfers a bit as described in C.2.14 | ||
| 1073 | static inline void BitTransferSigned(int& a, int& b) { | ||
| 1074 | b >>= 1; | ||
| 1075 | b |= a & 0x80; | ||
| 1076 | a >>= 1; | ||
| 1077 | a &= 0x3F; | ||
| 1078 | if (a & 0x20) | ||
| 1079 | a -= 0x40; | ||
| 1080 | } | ||
| 1081 | |||
| 1082 | // Adds more precision to the blue channel as described | ||
| 1083 | // in C.2.14 | ||
| 1084 | static inline Pixel BlueContract(s32 a, s32 r, s32 g, s32 b) { | ||
| 1085 | return Pixel(static_cast<s16>(a), static_cast<s16>((r + b) >> 1), | ||
| 1086 | static_cast<s16>((g + b) >> 1), static_cast<s16>(b)); | ||
| 1087 | } | ||
| 1088 | |||
| 1089 | // Partition selection functions as specified in | ||
| 1090 | // C.2.21 | ||
| 1091 | static inline u32 hash52(u32 p) { | ||
| 1092 | p ^= p >> 15; | ||
| 1093 | p -= p << 17; | ||
| 1094 | p += p << 7; | ||
| 1095 | p += p << 4; | ||
| 1096 | p ^= p >> 5; | ||
| 1097 | p += p << 16; | ||
| 1098 | p ^= p >> 7; | ||
| 1099 | p ^= p >> 3; | ||
| 1100 | p ^= p << 6; | ||
| 1101 | p ^= p >> 17; | ||
| 1102 | return p; | ||
| 1103 | } | ||
| 1104 | |||
| 1105 | static u32 SelectPartition(s32 seed, s32 x, s32 y, s32 z, s32 partitionCount, s32 smallBlock) { | ||
| 1106 | if (1 == partitionCount) | ||
| 1107 | return 0; | ||
| 1108 | |||
| 1109 | if (smallBlock) { | ||
| 1110 | x <<= 1; | ||
| 1111 | y <<= 1; | ||
| 1112 | z <<= 1; | ||
| 1113 | } | ||
| 1114 | |||
| 1115 | seed += (partitionCount - 1) * 1024; | ||
| 1116 | |||
| 1117 | u32 rnum = hash52(static_cast<u32>(seed)); | ||
| 1118 | u8 seed1 = static_cast<u8>(rnum & 0xF); | ||
| 1119 | u8 seed2 = static_cast<u8>((rnum >> 4) & 0xF); | ||
| 1120 | u8 seed3 = static_cast<u8>((rnum >> 8) & 0xF); | ||
| 1121 | u8 seed4 = static_cast<u8>((rnum >> 12) & 0xF); | ||
| 1122 | u8 seed5 = static_cast<u8>((rnum >> 16) & 0xF); | ||
| 1123 | u8 seed6 = static_cast<u8>((rnum >> 20) & 0xF); | ||
| 1124 | u8 seed7 = static_cast<u8>((rnum >> 24) & 0xF); | ||
| 1125 | u8 seed8 = static_cast<u8>((rnum >> 28) & 0xF); | ||
| 1126 | u8 seed9 = static_cast<u8>((rnum >> 18) & 0xF); | ||
| 1127 | u8 seed10 = static_cast<u8>((rnum >> 22) & 0xF); | ||
| 1128 | u8 seed11 = static_cast<u8>((rnum >> 26) & 0xF); | ||
| 1129 | u8 seed12 = static_cast<u8>(((rnum >> 30) | (rnum << 2)) & 0xF); | ||
| 1130 | |||
| 1131 | seed1 = static_cast<u8>(seed1 * seed1); | ||
| 1132 | seed2 = static_cast<u8>(seed2 * seed2); | ||
| 1133 | seed3 = static_cast<u8>(seed3 * seed3); | ||
| 1134 | seed4 = static_cast<u8>(seed4 * seed4); | ||
| 1135 | seed5 = static_cast<u8>(seed5 * seed5); | ||
| 1136 | seed6 = static_cast<u8>(seed6 * seed6); | ||
| 1137 | seed7 = static_cast<u8>(seed7 * seed7); | ||
| 1138 | seed8 = static_cast<u8>(seed8 * seed8); | ||
| 1139 | seed9 = static_cast<u8>(seed9 * seed9); | ||
| 1140 | seed10 = static_cast<u8>(seed10 * seed10); | ||
| 1141 | seed11 = static_cast<u8>(seed11 * seed11); | ||
| 1142 | seed12 = static_cast<u8>(seed12 * seed12); | ||
| 1143 | |||
| 1144 | s32 sh1, sh2, sh3; | ||
| 1145 | if (seed & 1) { | ||
| 1146 | sh1 = (seed & 2) ? 4 : 5; | ||
| 1147 | sh2 = (partitionCount == 3) ? 6 : 5; | ||
| 1148 | } else { | ||
| 1149 | sh1 = (partitionCount == 3) ? 6 : 5; | ||
| 1150 | sh2 = (seed & 2) ? 4 : 5; | ||
| 1151 | } | ||
| 1152 | sh3 = (seed & 0x10) ? sh1 : sh2; | ||
| 1153 | |||
| 1154 | seed1 = static_cast<u8>(seed1 >> sh1); | ||
| 1155 | seed2 = static_cast<u8>(seed2 >> sh2); | ||
| 1156 | seed3 = static_cast<u8>(seed3 >> sh1); | ||
| 1157 | seed4 = static_cast<u8>(seed4 >> sh2); | ||
| 1158 | seed5 = static_cast<u8>(seed5 >> sh1); | ||
| 1159 | seed6 = static_cast<u8>(seed6 >> sh2); | ||
| 1160 | seed7 = static_cast<u8>(seed7 >> sh1); | ||
| 1161 | seed8 = static_cast<u8>(seed8 >> sh2); | ||
| 1162 | seed9 = static_cast<u8>(seed9 >> sh3); | ||
| 1163 | seed10 = static_cast<u8>(seed10 >> sh3); | ||
| 1164 | seed11 = static_cast<u8>(seed11 >> sh3); | ||
| 1165 | seed12 = static_cast<u8>(seed12 >> sh3); | ||
| 1166 | |||
| 1167 | s32 a = seed1 * x + seed2 * y + seed11 * z + (rnum >> 14); | ||
| 1168 | s32 b = seed3 * x + seed4 * y + seed12 * z + (rnum >> 10); | ||
| 1169 | s32 c = seed5 * x + seed6 * y + seed9 * z + (rnum >> 6); | ||
| 1170 | s32 d = seed7 * x + seed8 * y + seed10 * z + (rnum >> 2); | ||
| 1171 | |||
| 1172 | a &= 0x3F; | ||
| 1173 | b &= 0x3F; | ||
| 1174 | c &= 0x3F; | ||
| 1175 | d &= 0x3F; | ||
| 1176 | |||
| 1177 | if (partitionCount < 4) | ||
| 1178 | d = 0; | ||
| 1179 | if (partitionCount < 3) | ||
| 1180 | c = 0; | ||
| 1181 | |||
| 1182 | if (a >= b && a >= c && a >= d) | ||
| 1183 | return 0; | ||
| 1184 | else if (b >= c && b >= d) | ||
| 1185 | return 1; | ||
| 1186 | else if (c >= d) | ||
| 1187 | return 2; | ||
| 1188 | return 3; | ||
| 1189 | } | ||
| 1190 | |||
| 1191 | static inline u32 Select2DPartition(s32 seed, s32 x, s32 y, s32 partitionCount, s32 smallBlock) { | ||
| 1192 | return SelectPartition(seed, x, y, 0, partitionCount, smallBlock); | ||
| 1193 | } | ||
| 1194 | |||
| 1195 | // Section C.2.14 | ||
| 1196 | static void ComputeEndpoints(Pixel& ep1, Pixel& ep2, const u32*& colorValues, | ||
| 1197 | u32 colorEndpointMode) { | ||
| 1198 | #define READ_UINT_VALUES(N) \ | ||
| 1199 | u32 v[N]; \ | ||
| 1200 | for (u32 i = 0; i < N; i++) { \ | ||
| 1201 | v[i] = *(colorValues++); \ | ||
| 1202 | } | ||
| 1203 | |||
| 1204 | #define READ_INT_VALUES(N) \ | ||
| 1205 | s32 v[N]; \ | ||
| 1206 | for (u32 i = 0; i < N; i++) { \ | ||
| 1207 | v[i] = static_cast<int>(*(colorValues++)); \ | ||
| 1208 | } | ||
| 1209 | |||
| 1210 | switch (colorEndpointMode) { | ||
| 1211 | case 0: { | ||
| 1212 | READ_UINT_VALUES(2) | ||
| 1213 | ep1 = Pixel(0xFF, v[0], v[0], v[0]); | ||
| 1214 | ep2 = Pixel(0xFF, v[1], v[1], v[1]); | ||
| 1215 | } break; | ||
| 1216 | |||
| 1217 | case 1: { | ||
| 1218 | READ_UINT_VALUES(2) | ||
| 1219 | u32 L0 = (v[0] >> 2) | (v[1] & 0xC0); | ||
| 1220 | u32 L1 = std::min(L0 + (v[1] & 0x3F), 0xFFU); | ||
| 1221 | ep1 = Pixel(0xFF, L0, L0, L0); | ||
| 1222 | ep2 = Pixel(0xFF, L1, L1, L1); | ||
| 1223 | } break; | ||
| 1224 | |||
| 1225 | case 4: { | ||
| 1226 | READ_UINT_VALUES(4) | ||
| 1227 | ep1 = Pixel(v[2], v[0], v[0], v[0]); | ||
| 1228 | ep2 = Pixel(v[3], v[1], v[1], v[1]); | ||
| 1229 | } break; | ||
| 1230 | |||
| 1231 | case 5: { | ||
| 1232 | READ_INT_VALUES(4) | ||
| 1233 | BitTransferSigned(v[1], v[0]); | ||
| 1234 | BitTransferSigned(v[3], v[2]); | ||
| 1235 | ep1 = Pixel(v[2], v[0], v[0], v[0]); | ||
| 1236 | ep2 = Pixel(v[2] + v[3], v[0] + v[1], v[0] + v[1], v[0] + v[1]); | ||
| 1237 | ep1.ClampByte(); | ||
| 1238 | ep2.ClampByte(); | ||
| 1239 | } break; | ||
| 1240 | |||
| 1241 | case 6: { | ||
| 1242 | READ_UINT_VALUES(4) | ||
| 1243 | ep1 = Pixel(0xFF, v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8); | ||
| 1244 | ep2 = Pixel(0xFF, v[0], v[1], v[2]); | ||
| 1245 | } break; | ||
| 1246 | |||
| 1247 | case 8: { | ||
| 1248 | READ_UINT_VALUES(6) | ||
| 1249 | if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4]) { | ||
| 1250 | ep1 = Pixel(0xFF, v[0], v[2], v[4]); | ||
| 1251 | ep2 = Pixel(0xFF, v[1], v[3], v[5]); | ||
| 1252 | } else { | ||
| 1253 | ep1 = BlueContract(0xFF, v[1], v[3], v[5]); | ||
| 1254 | ep2 = BlueContract(0xFF, v[0], v[2], v[4]); | ||
| 1255 | } | ||
| 1256 | } break; | ||
| 1257 | |||
| 1258 | case 9: { | ||
| 1259 | READ_INT_VALUES(6) | ||
| 1260 | BitTransferSigned(v[1], v[0]); | ||
| 1261 | BitTransferSigned(v[3], v[2]); | ||
| 1262 | BitTransferSigned(v[5], v[4]); | ||
| 1263 | if (v[1] + v[3] + v[5] >= 0) { | ||
| 1264 | ep1 = Pixel(0xFF, v[0], v[2], v[4]); | ||
| 1265 | ep2 = Pixel(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]); | ||
| 1266 | } else { | ||
| 1267 | ep1 = BlueContract(0xFF, v[0] + v[1], v[2] + v[3], v[4] + v[5]); | ||
| 1268 | ep2 = BlueContract(0xFF, v[0], v[2], v[4]); | ||
| 1269 | } | ||
| 1270 | ep1.ClampByte(); | ||
| 1271 | ep2.ClampByte(); | ||
| 1272 | } break; | ||
| 1273 | |||
| 1274 | case 10: { | ||
| 1275 | READ_UINT_VALUES(6) | ||
| 1276 | ep1 = Pixel(v[4], v[0] * v[3] >> 8, v[1] * v[3] >> 8, v[2] * v[3] >> 8); | ||
| 1277 | ep2 = Pixel(v[5], v[0], v[1], v[2]); | ||
| 1278 | } break; | ||
| 1279 | |||
| 1280 | case 12: { | ||
| 1281 | READ_UINT_VALUES(8) | ||
| 1282 | if (v[1] + v[3] + v[5] >= v[0] + v[2] + v[4]) { | ||
| 1283 | ep1 = Pixel(v[6], v[0], v[2], v[4]); | ||
| 1284 | ep2 = Pixel(v[7], v[1], v[3], v[5]); | ||
| 1285 | } else { | ||
| 1286 | ep1 = BlueContract(v[7], v[1], v[3], v[5]); | ||
| 1287 | ep2 = BlueContract(v[6], v[0], v[2], v[4]); | ||
| 1288 | } | ||
| 1289 | } break; | ||
| 1290 | |||
| 1291 | case 13: { | ||
| 1292 | READ_INT_VALUES(8) | ||
| 1293 | BitTransferSigned(v[1], v[0]); | ||
| 1294 | BitTransferSigned(v[3], v[2]); | ||
| 1295 | BitTransferSigned(v[5], v[4]); | ||
| 1296 | BitTransferSigned(v[7], v[6]); | ||
| 1297 | if (v[1] + v[3] + v[5] >= 0) { | ||
| 1298 | ep1 = Pixel(v[6], v[0], v[2], v[4]); | ||
| 1299 | ep2 = Pixel(v[7] + v[6], v[0] + v[1], v[2] + v[3], v[4] + v[5]); | ||
| 1300 | } else { | ||
| 1301 | ep1 = BlueContract(v[6] + v[7], v[0] + v[1], v[2] + v[3], v[4] + v[5]); | ||
| 1302 | ep2 = BlueContract(v[6], v[0], v[2], v[4]); | ||
| 1303 | } | ||
| 1304 | ep1.ClampByte(); | ||
| 1305 | ep2.ClampByte(); | ||
| 1306 | } break; | ||
| 1307 | |||
| 1308 | default: | ||
| 1309 | assert(false && "Unsupported color endpoint mode (is it HDR?)"); | ||
| 1310 | break; | ||
| 1311 | } | ||
| 1312 | |||
| 1313 | #undef READ_UINT_VALUES | ||
| 1314 | #undef READ_INT_VALUES | ||
| 1315 | } | ||
| 1316 | |||
| 1317 | static void DecompressBlock(std::span<const u8, 16> inBuf, const u32 blockWidth, | ||
| 1318 | const u32 blockHeight, std::span<u32, 12 * 12> outBuf) { | ||
| 1319 | InputBitStream strm(inBuf); | ||
| 1320 | TexelWeightParams weightParams = DecodeBlockInfo(strm); | ||
| 1321 | |||
| 1322 | // Was there an error? | ||
| 1323 | if (weightParams.m_bError) { | ||
| 1324 | assert(false && "Invalid block mode"); | ||
| 1325 | FillError(outBuf, blockWidth, blockHeight); | ||
| 1326 | return; | ||
| 1327 | } | ||
| 1328 | |||
| 1329 | if (weightParams.m_bVoidExtentLDR) { | ||
| 1330 | FillVoidExtentLDR(strm, outBuf, blockWidth, blockHeight); | ||
| 1331 | return; | ||
| 1332 | } | ||
| 1333 | |||
| 1334 | if (weightParams.m_bVoidExtentHDR) { | ||
| 1335 | assert(false && "HDR void extent blocks are unsupported!"); | ||
| 1336 | FillError(outBuf, blockWidth, blockHeight); | ||
| 1337 | return; | ||
| 1338 | } | ||
| 1339 | |||
| 1340 | if (weightParams.m_Width > blockWidth) { | ||
| 1341 | assert(false && "Texel weight grid width should be smaller than block width"); | ||
| 1342 | FillError(outBuf, blockWidth, blockHeight); | ||
| 1343 | return; | ||
| 1344 | } | ||
| 1345 | |||
| 1346 | if (weightParams.m_Height > blockHeight) { | ||
| 1347 | assert(false && "Texel weight grid height should be smaller than block height"); | ||
| 1348 | FillError(outBuf, blockWidth, blockHeight); | ||
| 1349 | return; | ||
| 1350 | } | ||
| 1351 | |||
| 1352 | // Read num partitions | ||
| 1353 | u32 nPartitions = strm.ReadBits<2>() + 1; | ||
| 1354 | assert(nPartitions <= 4); | ||
| 1355 | |||
| 1356 | if (nPartitions == 4 && weightParams.m_bDualPlane) { | ||
| 1357 | assert(false && "Dual plane mode is incompatible with four partition blocks"); | ||
| 1358 | FillError(outBuf, blockWidth, blockHeight); | ||
| 1359 | return; | ||
| 1360 | } | ||
| 1361 | |||
| 1362 | // Based on the number of partitions, read the color endpoint mode for | ||
| 1363 | // each partition. | ||
| 1364 | |||
| 1365 | // Determine partitions, partition index, and color endpoint modes | ||
| 1366 | s32 planeIdx = -1; | ||
| 1367 | u32 partitionIndex; | ||
| 1368 | u32 colorEndpointMode[4] = {0, 0, 0, 0}; | ||
| 1369 | |||
| 1370 | // Define color data. | ||
| 1371 | u8 colorEndpointData[16]; | ||
| 1372 | memset(colorEndpointData, 0, sizeof(colorEndpointData)); | ||
| 1373 | OutputBitStream colorEndpointStream(colorEndpointData, 16 * 8, 0); | ||
| 1374 | |||
| 1375 | // Read extra config data... | ||
| 1376 | u32 baseCEM = 0; | ||
| 1377 | if (nPartitions == 1) { | ||
| 1378 | colorEndpointMode[0] = strm.ReadBits<4>(); | ||
| 1379 | partitionIndex = 0; | ||
| 1380 | } else { | ||
| 1381 | partitionIndex = strm.ReadBits<10>(); | ||
| 1382 | baseCEM = strm.ReadBits<6>(); | ||
| 1383 | } | ||
| 1384 | u32 baseMode = (baseCEM & 3); | ||
| 1385 | |||
| 1386 | // Remaining bits are color endpoint data... | ||
| 1387 | u32 nWeightBits = weightParams.GetPackedBitSize(); | ||
| 1388 | s32 remainingBits = 128 - nWeightBits - static_cast<int>(strm.GetBitsRead()); | ||
| 1389 | |||
| 1390 | // Consider extra bits prior to texel data... | ||
| 1391 | u32 extraCEMbits = 0; | ||
| 1392 | if (baseMode) { | ||
| 1393 | switch (nPartitions) { | ||
| 1394 | case 2: | ||
| 1395 | extraCEMbits += 2; | ||
| 1396 | break; | ||
| 1397 | case 3: | ||
| 1398 | extraCEMbits += 5; | ||
| 1399 | break; | ||
| 1400 | case 4: | ||
| 1401 | extraCEMbits += 8; | ||
| 1402 | break; | ||
| 1403 | default: | ||
| 1404 | assert(false); | ||
| 1405 | break; | ||
| 1406 | } | ||
| 1407 | } | ||
| 1408 | remainingBits -= extraCEMbits; | ||
| 1409 | |||
| 1410 | // Do we have a dual plane situation? | ||
| 1411 | u32 planeSelectorBits = 0; | ||
| 1412 | if (weightParams.m_bDualPlane) { | ||
| 1413 | planeSelectorBits = 2; | ||
| 1414 | } | ||
| 1415 | remainingBits -= planeSelectorBits; | ||
| 1416 | |||
| 1417 | // Read color data... | ||
| 1418 | u32 colorDataBits = remainingBits; | ||
| 1419 | while (remainingBits > 0) { | ||
| 1420 | u32 nb = std::min(remainingBits, 8); | ||
| 1421 | u32 b = strm.ReadBits(nb); | ||
| 1422 | colorEndpointStream.WriteBits(b, nb); | ||
| 1423 | remainingBits -= 8; | ||
| 1424 | } | ||
| 1425 | |||
| 1426 | // Read the plane selection bits | ||
| 1427 | planeIdx = strm.ReadBits(planeSelectorBits); | ||
| 1428 | |||
| 1429 | // Read the rest of the CEM | ||
| 1430 | if (baseMode) { | ||
| 1431 | u32 extraCEM = strm.ReadBits(extraCEMbits); | ||
| 1432 | u32 CEM = (extraCEM << 6) | baseCEM; | ||
| 1433 | CEM >>= 2; | ||
| 1434 | |||
| 1435 | bool C[4] = {0}; | ||
| 1436 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 1437 | C[i] = CEM & 1; | ||
| 1438 | CEM >>= 1; | ||
| 1439 | } | ||
| 1440 | |||
| 1441 | u8 M[4] = {0}; | ||
| 1442 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 1443 | M[i] = CEM & 3; | ||
| 1444 | CEM >>= 2; | ||
| 1445 | assert(M[i] <= 3); | ||
| 1446 | } | ||
| 1447 | |||
| 1448 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 1449 | colorEndpointMode[i] = baseMode; | ||
| 1450 | if (!(C[i])) | ||
| 1451 | colorEndpointMode[i] -= 1; | ||
| 1452 | colorEndpointMode[i] <<= 2; | ||
| 1453 | colorEndpointMode[i] |= M[i]; | ||
| 1454 | } | ||
| 1455 | } else if (nPartitions > 1) { | ||
| 1456 | u32 CEM = baseCEM >> 2; | ||
| 1457 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 1458 | colorEndpointMode[i] = CEM; | ||
| 1459 | } | ||
| 1460 | } | ||
| 1461 | |||
| 1462 | // Make sure everything up till here is sane. | ||
| 1463 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 1464 | assert(colorEndpointMode[i] < 16); | ||
| 1465 | } | ||
| 1466 | assert(strm.GetBitsRead() + weightParams.GetPackedBitSize() == 128); | ||
| 1467 | |||
| 1468 | // Decode both color data and texel weight data | ||
| 1469 | u32 colorValues[32]; // Four values, two endpoints, four maximum paritions | ||
| 1470 | DecodeColorValues(colorValues, colorEndpointData, colorEndpointMode, nPartitions, | ||
| 1471 | colorDataBits); | ||
| 1472 | |||
| 1473 | Pixel endpoints[4][2]; | ||
| 1474 | const u32* colorValuesPtr = colorValues; | ||
| 1475 | for (u32 i = 0; i < nPartitions; i++) { | ||
| 1476 | ComputeEndpoints(endpoints[i][0], endpoints[i][1], colorValuesPtr, colorEndpointMode[i]); | ||
| 1477 | } | ||
| 1478 | |||
| 1479 | // Read the texel weight data.. | ||
| 1480 | std::array<u8, 16> texelWeightData; | ||
| 1481 | std::ranges::copy(inBuf, texelWeightData.begin()); | ||
| 1482 | |||
| 1483 | // Reverse everything | ||
| 1484 | for (u32 i = 0; i < 8; i++) { | ||
| 1485 | // Taken from http://graphics.stanford.edu/~seander/bithacks.html#ReverseByteWith64Bits | ||
| 1486 | #define REVERSE_BYTE(b) (((b)*0x80200802ULL) & 0x0884422110ULL) * 0x0101010101ULL >> 32 | ||
| 1487 | u8 a = static_cast<u8>(REVERSE_BYTE(texelWeightData[i])); | ||
| 1488 | u8 b = static_cast<u8>(REVERSE_BYTE(texelWeightData[15 - i])); | ||
| 1489 | #undef REVERSE_BYTE | ||
| 1490 | |||
| 1491 | texelWeightData[i] = b; | ||
| 1492 | texelWeightData[15 - i] = a; | ||
| 1493 | } | ||
| 1494 | |||
| 1495 | // Make sure that higher non-texel bits are set to zero | ||
| 1496 | const u32 clearByteStart = (weightParams.GetPackedBitSize() >> 3) + 1; | ||
| 1497 | if (clearByteStart > 0 && clearByteStart <= texelWeightData.size()) { | ||
| 1498 | texelWeightData[clearByteStart - 1] &= | ||
| 1499 | static_cast<u8>((1 << (weightParams.GetPackedBitSize() % 8)) - 1); | ||
| 1500 | std::memset(texelWeightData.data() + clearByteStart, 0, | ||
| 1501 | std::min(16U - clearByteStart, 16U)); | ||
| 1502 | } | ||
| 1503 | |||
| 1504 | IntegerEncodedVector texelWeightValues; | ||
| 1505 | |||
| 1506 | InputBitStream weightStream(texelWeightData); | ||
| 1507 | |||
| 1508 | DecodeIntegerSequence(texelWeightValues, weightStream, weightParams.m_MaxWeight, | ||
| 1509 | weightParams.GetNumWeightValues()); | ||
| 1510 | |||
| 1511 | // Blocks can be at most 12x12, so we can have as many as 144 weights | ||
| 1512 | u32 weights[2][144]; | ||
| 1513 | UnquantizeTexelWeights(weights, texelWeightValues, weightParams, blockWidth, blockHeight); | ||
| 1514 | |||
| 1515 | // Now that we have endpoints and weights, we can interpolate and generate | ||
| 1516 | // the proper decoding... | ||
| 1517 | for (u32 j = 0; j < blockHeight; j++) | ||
| 1518 | for (u32 i = 0; i < blockWidth; i++) { | ||
| 1519 | u32 partition = Select2DPartition(partitionIndex, i, j, nPartitions, | ||
| 1520 | (blockHeight * blockWidth) < 32); | ||
| 1521 | assert(partition < nPartitions); | ||
| 1522 | |||
| 1523 | Pixel p; | ||
| 1524 | for (u32 c = 0; c < 4; c++) { | ||
| 1525 | u32 C0 = endpoints[partition][0].Component(c); | ||
| 1526 | C0 = ReplicateByteTo16(C0); | ||
| 1527 | u32 C1 = endpoints[partition][1].Component(c); | ||
| 1528 | C1 = ReplicateByteTo16(C1); | ||
| 1529 | |||
| 1530 | u32 plane = 0; | ||
| 1531 | if (weightParams.m_bDualPlane && (((planeIdx + 1) & 3) == c)) { | ||
| 1532 | plane = 1; | ||
| 1533 | } | ||
| 1534 | |||
| 1535 | u32 weight = weights[plane][j * blockWidth + i]; | ||
| 1536 | u32 C = (C0 * (64 - weight) + C1 * weight + 32) / 64; | ||
| 1537 | if (C == 65535) { | ||
| 1538 | p.Component(c) = 255; | ||
| 1539 | } else { | ||
| 1540 | double Cf = static_cast<double>(C); | ||
| 1541 | p.Component(c) = static_cast<u16>(255.0 * (Cf / 65536.0) + 0.5); | ||
| 1542 | } | ||
| 1543 | } | ||
| 1544 | |||
| 1545 | outBuf[j * blockWidth + i] = p.Pack(); | ||
| 1546 | } | ||
| 1547 | } | ||
| 1548 | |||
| 1549 | void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 1550 | uint32_t block_width, uint32_t block_height, std::span<uint8_t> output) { | ||
| 1551 | u32 block_index = 0; | ||
| 1552 | std::size_t depth_offset = 0; | ||
| 1553 | for (u32 z = 0; z < depth; z++) { | ||
| 1554 | for (u32 y = 0; y < height; y += block_height) { | ||
| 1555 | for (u32 x = 0; x < width; x += block_width) { | ||
| 1556 | const std::span<const u8, 16> blockPtr{data.subspan(block_index * 16, 16)}; | ||
| 1557 | |||
| 1558 | // Blocks can be at most 12x12 | ||
| 1559 | std::array<u32, 12 * 12> uncompData; | ||
| 1560 | DecompressBlock(blockPtr, block_width, block_height, uncompData); | ||
| 1561 | |||
| 1562 | u32 decompWidth = std::min(block_width, width - x); | ||
| 1563 | u32 decompHeight = std::min(block_height, height - y); | ||
| 1564 | |||
| 1565 | const std::span<u8> outRow = output.subspan(depth_offset + (y * width + x) * 4); | ||
| 1566 | for (u32 jj = 0; jj < decompHeight; jj++) { | ||
| 1567 | std::memcpy(outRow.data() + jj * width * 4, | ||
| 1568 | uncompData.data() + jj * block_width, decompWidth * 4); | ||
| 1569 | } | ||
| 1570 | ++block_index; | ||
| 1571 | } | ||
| 1572 | } | ||
| 1573 | depth_offset += height * width * 4; | ||
| 1574 | } | ||
| 1575 | } | ||
| 1576 | |||
| 1577 | } // namespace Tegra::Texture::ASTC | ||
diff --git a/src/video_core/textures/astc.h b/src/video_core/textures/astc.h index c1c73fda5..c1c37dfe7 100644 --- a/src/video_core/textures/astc.h +++ b/src/video_core/textures/astc.h | |||
| @@ -129,4 +129,7 @@ struct AstcBufferData { | |||
| 129 | decltype(REPLICATE_BYTE_TO_16_TABLE) replicate_byte_to_16 = REPLICATE_BYTE_TO_16_TABLE; | 129 | decltype(REPLICATE_BYTE_TO_16_TABLE) replicate_byte_to_16 = REPLICATE_BYTE_TO_16_TABLE; |
| 130 | } constexpr ASTC_BUFFER_DATA; | 130 | } constexpr ASTC_BUFFER_DATA; |
| 131 | 131 | ||
| 132 | void Decompress(std::span<const uint8_t> data, uint32_t width, uint32_t height, uint32_t depth, | ||
| 133 | uint32_t block_width, uint32_t block_height, std::span<uint8_t> output); | ||
| 134 | |||
| 132 | } // namespace Tegra::Texture::ASTC | 135 | } // namespace Tegra::Texture::ASTC |
diff --git a/src/video_core/textures/decoders.cpp b/src/video_core/textures/decoders.cpp index 3a463d5db..f1f523ad1 100644 --- a/src/video_core/textures/decoders.cpp +++ b/src/video_core/textures/decoders.cpp | |||
| @@ -63,6 +63,14 @@ void Swizzle(std::span<u8> output, std::span<const u8> input, u32 bytes_per_pixe | |||
| 63 | const u32 unswizzled_offset = | 63 | const u32 unswizzled_offset = |
| 64 | slice * pitch * height + line * pitch + column * bytes_per_pixel; | 64 | slice * pitch * height + line * pitch + column * bytes_per_pixel; |
| 65 | 65 | ||
| 66 | if (const auto offset = (TO_LINEAR ? unswizzled_offset : swizzled_offset); | ||
| 67 | offset >= input.size()) { | ||
| 68 | // TODO(Rodrigo): This is an out of bounds access that should never happen. To | ||
| 69 | // avoid crashing the emulator, break. | ||
| 70 | ASSERT_MSG(false, "offset {} exceeds input size {}!", offset, input.size()); | ||
| 71 | break; | ||
| 72 | } | ||
| 73 | |||
| 66 | u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset]; | 74 | u8* const dst = &output[TO_LINEAR ? swizzled_offset : unswizzled_offset]; |
| 67 | const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset]; | 75 | const u8* const src = &input[TO_LINEAR ? unswizzled_offset : swizzled_offset]; |
| 68 | std::memcpy(dst, src, bytes_per_pixel); | 76 | std::memcpy(dst, src, bytes_per_pixel); |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index e9d4bef60..916a22724 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -647,6 +647,8 @@ void Config::ReadDebuggingValues() { | |||
| 647 | ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString(); | 647 | ReadSetting(QStringLiteral("program_args"), QString{}).toString().toStdString(); |
| 648 | Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); | 648 | Settings::values.dump_exefs = ReadSetting(QStringLiteral("dump_exefs"), false).toBool(); |
| 649 | Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); | 649 | Settings::values.dump_nso = ReadSetting(QStringLiteral("dump_nso"), false).toBool(); |
| 650 | Settings::values.enable_fs_access_log = | ||
| 651 | ReadSetting(QStringLiteral("enable_fs_access_log"), false).toBool(); | ||
| 650 | Settings::values.reporting_services = | 652 | Settings::values.reporting_services = |
| 651 | ReadSetting(QStringLiteral("reporting_services"), false).toBool(); | 653 | ReadSetting(QStringLiteral("reporting_services"), false).toBool(); |
| 652 | Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); | 654 | Settings::values.quest_flag = ReadSetting(QStringLiteral("quest_flag"), false).toBool(); |
| @@ -756,6 +758,8 @@ void Config::ReadCpuValues() { | |||
| 756 | QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); | 758 | QStringLiteral("cpuopt_unsafe_reduce_fp_error"), true); |
| 757 | ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan, | 759 | ReadSettingGlobal(Settings::values.cpuopt_unsafe_inaccurate_nan, |
| 758 | QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true); | 760 | QStringLiteral("cpuopt_unsafe_inaccurate_nan"), true); |
| 761 | ReadSettingGlobal(Settings::values.cpuopt_unsafe_fastmem_check, | ||
| 762 | QStringLiteral("cpuopt_unsafe_fastmem_check"), true); | ||
| 759 | 763 | ||
| 760 | if (global) { | 764 | if (global) { |
| 761 | Settings::values.cpuopt_page_tables = | 765 | Settings::values.cpuopt_page_tables = |
| @@ -774,6 +778,8 @@ void Config::ReadCpuValues() { | |||
| 774 | ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); | 778 | ReadSetting(QStringLiteral("cpuopt_misc_ir"), true).toBool(); |
| 775 | Settings::values.cpuopt_reduce_misalign_checks = | 779 | Settings::values.cpuopt_reduce_misalign_checks = |
| 776 | ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); | 780 | ReadSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), true).toBool(); |
| 781 | Settings::values.cpuopt_fastmem = | ||
| 782 | ReadSetting(QStringLiteral("cpuopt_fastmem"), true).toBool(); | ||
| 777 | } | 783 | } |
| 778 | 784 | ||
| 779 | qt_config->endGroup(); | 785 | qt_config->endGroup(); |
| @@ -803,6 +809,7 @@ void Config::ReadRendererValues() { | |||
| 803 | QStringLiteral("use_asynchronous_gpu_emulation"), true); | 809 | QStringLiteral("use_asynchronous_gpu_emulation"), true); |
| 804 | ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"), | 810 | ReadSettingGlobal(Settings::values.use_nvdec_emulation, QStringLiteral("use_nvdec_emulation"), |
| 805 | true); | 811 | true); |
| 812 | ReadSettingGlobal(Settings::values.accelerate_astc, QStringLiteral("accelerate_astc"), true); | ||
| 806 | ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); | 813 | ReadSettingGlobal(Settings::values.use_vsync, QStringLiteral("use_vsync"), true); |
| 807 | ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), | 814 | ReadSettingGlobal(Settings::values.use_assembly_shaders, QStringLiteral("use_assembly_shaders"), |
| 808 | false); | 815 | false); |
| @@ -1254,6 +1261,8 @@ void Config::SaveDebuggingValues() { | |||
| 1254 | QString::fromStdString(Settings::values.program_args), QString{}); | 1261 | QString::fromStdString(Settings::values.program_args), QString{}); |
| 1255 | WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); | 1262 | WriteSetting(QStringLiteral("dump_exefs"), Settings::values.dump_exefs, false); |
| 1256 | WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); | 1263 | WriteSetting(QStringLiteral("dump_nso"), Settings::values.dump_nso, false); |
| 1264 | WriteSetting(QStringLiteral("enable_fs_access_log"), Settings::values.enable_fs_access_log, | ||
| 1265 | false); | ||
| 1257 | WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false); | 1266 | WriteSetting(QStringLiteral("quest_flag"), Settings::values.quest_flag, false); |
| 1258 | WriteSetting(QStringLiteral("use_debug_asserts"), Settings::values.use_debug_asserts, false); | 1267 | WriteSetting(QStringLiteral("use_debug_asserts"), Settings::values.use_debug_asserts, false); |
| 1259 | WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false); | 1268 | WriteSetting(QStringLiteral("disable_macro_jit"), Settings::values.disable_macro_jit, false); |
| @@ -1332,6 +1341,8 @@ void Config::SaveCpuValues() { | |||
| 1332 | Settings::values.cpuopt_unsafe_reduce_fp_error, true); | 1341 | Settings::values.cpuopt_unsafe_reduce_fp_error, true); |
| 1333 | WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), | 1342 | WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_inaccurate_nan"), |
| 1334 | Settings::values.cpuopt_unsafe_inaccurate_nan, true); | 1343 | Settings::values.cpuopt_unsafe_inaccurate_nan, true); |
| 1344 | WriteSettingGlobal(QStringLiteral("cpuopt_unsafe_fastmem_check"), | ||
| 1345 | Settings::values.cpuopt_unsafe_fastmem_check, true); | ||
| 1335 | 1346 | ||
| 1336 | if (global) { | 1347 | if (global) { |
| 1337 | WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables, | 1348 | WriteSetting(QStringLiteral("cpuopt_page_tables"), Settings::values.cpuopt_page_tables, |
| @@ -1348,6 +1359,7 @@ void Config::SaveCpuValues() { | |||
| 1348 | WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); | 1359 | WriteSetting(QStringLiteral("cpuopt_misc_ir"), Settings::values.cpuopt_misc_ir, true); |
| 1349 | WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), | 1360 | WriteSetting(QStringLiteral("cpuopt_reduce_misalign_checks"), |
| 1350 | Settings::values.cpuopt_reduce_misalign_checks, true); | 1361 | Settings::values.cpuopt_reduce_misalign_checks, true); |
| 1362 | WriteSetting(QStringLiteral("cpuopt_fastmem"), Settings::values.cpuopt_fastmem, true); | ||
| 1351 | } | 1363 | } |
| 1352 | 1364 | ||
| 1353 | qt_config->endGroup(); | 1365 | qt_config->endGroup(); |
| @@ -1381,6 +1393,7 @@ void Config::SaveRendererValues() { | |||
| 1381 | Settings::values.use_asynchronous_gpu_emulation, true); | 1393 | Settings::values.use_asynchronous_gpu_emulation, true); |
| 1382 | WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation, | 1394 | WriteSettingGlobal(QStringLiteral("use_nvdec_emulation"), Settings::values.use_nvdec_emulation, |
| 1383 | true); | 1395 | true); |
| 1396 | WriteSettingGlobal(QStringLiteral("accelerate_astc"), Settings::values.accelerate_astc, true); | ||
| 1384 | WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); | 1397 | WriteSettingGlobal(QStringLiteral("use_vsync"), Settings::values.use_vsync, true); |
| 1385 | WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), | 1398 | WriteSettingGlobal(QStringLiteral("use_assembly_shaders"), |
| 1386 | Settings::values.use_assembly_shaders, false); | 1399 | Settings::values.use_assembly_shaders, false); |
diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 525c42ff0..22219cbad 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp | |||
| @@ -35,12 +35,15 @@ void ConfigureCpu::SetConfiguration() { | |||
| 35 | ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); | 35 | ui->cpuopt_unsafe_unfuse_fma->setEnabled(runtime_lock); |
| 36 | ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); | 36 | ui->cpuopt_unsafe_reduce_fp_error->setEnabled(runtime_lock); |
| 37 | ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); | 37 | ui->cpuopt_unsafe_inaccurate_nan->setEnabled(runtime_lock); |
| 38 | ui->cpuopt_unsafe_fastmem_check->setEnabled(runtime_lock); | ||
| 38 | 39 | ||
| 39 | ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()); | 40 | ui->cpuopt_unsafe_unfuse_fma->setChecked(Settings::values.cpuopt_unsafe_unfuse_fma.GetValue()); |
| 40 | ui->cpuopt_unsafe_reduce_fp_error->setChecked( | 41 | ui->cpuopt_unsafe_reduce_fp_error->setChecked( |
| 41 | Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); | 42 | Settings::values.cpuopt_unsafe_reduce_fp_error.GetValue()); |
| 42 | ui->cpuopt_unsafe_inaccurate_nan->setChecked( | 43 | ui->cpuopt_unsafe_inaccurate_nan->setChecked( |
| 43 | Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()); | 44 | Settings::values.cpuopt_unsafe_inaccurate_nan.GetValue()); |
| 45 | ui->cpuopt_unsafe_fastmem_check->setChecked( | ||
| 46 | Settings::values.cpuopt_unsafe_fastmem_check.GetValue()); | ||
| 44 | 47 | ||
| 45 | if (Settings::IsConfiguringGlobal()) { | 48 | if (Settings::IsConfiguringGlobal()) { |
| 46 | ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue())); | 49 | ui->accuracy->setCurrentIndex(static_cast<int>(Settings::values.cpu_accuracy.GetValue())); |
| @@ -84,6 +87,9 @@ void ConfigureCpu::ApplyConfiguration() { | |||
| 84 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan, | 87 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_inaccurate_nan, |
| 85 | ui->cpuopt_unsafe_inaccurate_nan, | 88 | ui->cpuopt_unsafe_inaccurate_nan, |
| 86 | cpuopt_unsafe_inaccurate_nan); | 89 | cpuopt_unsafe_inaccurate_nan); |
| 90 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.cpuopt_unsafe_fastmem_check, | ||
| 91 | ui->cpuopt_unsafe_fastmem_check, | ||
| 92 | cpuopt_unsafe_fastmem_check); | ||
| 87 | 93 | ||
| 88 | if (Settings::IsConfiguringGlobal()) { | 94 | if (Settings::IsConfiguringGlobal()) { |
| 89 | // Guard if during game and set to game-specific value | 95 | // Guard if during game and set to game-specific value |
| @@ -134,4 +140,7 @@ void ConfigureCpu::SetupPerGameUI() { | |||
| 134 | ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan, | 140 | ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_inaccurate_nan, |
| 135 | Settings::values.cpuopt_unsafe_inaccurate_nan, | 141 | Settings::values.cpuopt_unsafe_inaccurate_nan, |
| 136 | cpuopt_unsafe_inaccurate_nan); | 142 | cpuopt_unsafe_inaccurate_nan); |
| 143 | ConfigurationShared::SetColoredTristate(ui->cpuopt_unsafe_fastmem_check, | ||
| 144 | Settings::values.cpuopt_unsafe_fastmem_check, | ||
| 145 | cpuopt_unsafe_fastmem_check); | ||
| 137 | } | 146 | } |
diff --git a/src/yuzu/configuration/configure_cpu.h b/src/yuzu/configuration/configure_cpu.h index 8e2eeb7a6..57ff2772a 100644 --- a/src/yuzu/configuration/configure_cpu.h +++ b/src/yuzu/configuration/configure_cpu.h | |||
| @@ -41,4 +41,5 @@ private: | |||
| 41 | ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma; | 41 | ConfigurationShared::CheckState cpuopt_unsafe_unfuse_fma; |
| 42 | ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; | 42 | ConfigurationShared::CheckState cpuopt_unsafe_reduce_fp_error; |
| 43 | ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan; | 43 | ConfigurationShared::CheckState cpuopt_unsafe_inaccurate_nan; |
| 44 | ConfigurationShared::CheckState cpuopt_unsafe_fastmem_check; | ||
| 44 | }; | 45 | }; |
diff --git a/src/yuzu/configuration/configure_cpu.ui b/src/yuzu/configuration/configure_cpu.ui index 99b573640..31ef9e3f5 100644 --- a/src/yuzu/configuration/configure_cpu.ui +++ b/src/yuzu/configuration/configure_cpu.ui | |||
| @@ -123,6 +123,18 @@ | |||
| 123 | </property> | 123 | </property> |
| 124 | </widget> | 124 | </widget> |
| 125 | </item> | 125 | </item> |
| 126 | <item> | ||
| 127 | <widget class="QCheckBox" name="cpuopt_unsafe_fastmem_check"> | ||
| 128 | <property name="toolTip"> | ||
| 129 | <string> | ||
| 130 | <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> | ||
| 131 | </string> | ||
| 132 | </property> | ||
| 133 | <property name="text"> | ||
| 134 | <string>Disable address space checks</string> | ||
| 135 | </property> | ||
| 136 | </widget> | ||
| 137 | </item> | ||
| 126 | </layout> | 138 | </layout> |
| 127 | </widget> | 139 | </widget> |
| 128 | </item> | 140 | </item> |
diff --git a/src/yuzu/configuration/configure_cpu_debug.cpp b/src/yuzu/configuration/configure_cpu_debug.cpp index c925c023c..e25c52baf 100644 --- a/src/yuzu/configuration/configure_cpu_debug.cpp +++ b/src/yuzu/configuration/configure_cpu_debug.cpp | |||
| @@ -39,6 +39,8 @@ void ConfigureCpuDebug::SetConfiguration() { | |||
| 39 | ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir); | 39 | ui->cpuopt_misc_ir->setChecked(Settings::values.cpuopt_misc_ir); |
| 40 | ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock); | 40 | ui->cpuopt_reduce_misalign_checks->setEnabled(runtime_lock); |
| 41 | ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks); | 41 | ui->cpuopt_reduce_misalign_checks->setChecked(Settings::values.cpuopt_reduce_misalign_checks); |
| 42 | ui->cpuopt_fastmem->setEnabled(runtime_lock); | ||
| 43 | ui->cpuopt_fastmem->setChecked(Settings::values.cpuopt_fastmem); | ||
| 42 | } | 44 | } |
| 43 | 45 | ||
| 44 | void ConfigureCpuDebug::ApplyConfiguration() { | 46 | void ConfigureCpuDebug::ApplyConfiguration() { |
| @@ -50,6 +52,7 @@ void ConfigureCpuDebug::ApplyConfiguration() { | |||
| 50 | Settings::values.cpuopt_const_prop = ui->cpuopt_const_prop->isChecked(); | 52 | Settings::values.cpuopt_const_prop = ui->cpuopt_const_prop->isChecked(); |
| 51 | Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked(); | 53 | Settings::values.cpuopt_misc_ir = ui->cpuopt_misc_ir->isChecked(); |
| 52 | Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked(); | 54 | Settings::values.cpuopt_reduce_misalign_checks = ui->cpuopt_reduce_misalign_checks->isChecked(); |
| 55 | Settings::values.cpuopt_fastmem = ui->cpuopt_fastmem->isChecked(); | ||
| 53 | } | 56 | } |
| 54 | 57 | ||
| 55 | void ConfigureCpuDebug::changeEvent(QEvent* event) { | 58 | void ConfigureCpuDebug::changeEvent(QEvent* event) { |
diff --git a/src/yuzu/configuration/configure_cpu_debug.ui b/src/yuzu/configuration/configure_cpu_debug.ui index a90dc64fe..c43f89a5a 100644 --- a/src/yuzu/configuration/configure_cpu_debug.ui +++ b/src/yuzu/configuration/configure_cpu_debug.ui | |||
| @@ -34,7 +34,7 @@ | |||
| 34 | <br> | 34 | <br> |
| 35 | If you're not sure what these do, keep all of these enabled. | 35 | If you're not sure what these do, keep all of these enabled. |
| 36 | <br> | 36 | <br> |
| 37 | These settings only take effect when CPU Accuracy is "Debug Mode". | 37 | These settings, when disabled, only take effect when CPU Accuracy is "Debug Mode". |
| 38 | </div> | 38 | </div> |
| 39 | </string> | 39 | </string> |
| 40 | </property> | 40 | </property> |
| @@ -139,6 +139,20 @@ | |||
| 139 | </property> | 139 | </property> |
| 140 | </widget> | 140 | </widget> |
| 141 | </item> | 141 | </item> |
| 142 | <item> | ||
| 143 | <widget class="QCheckBox" name="cpuopt_fastmem"> | ||
| 144 | <property name="text"> | ||
| 145 | <string>Enable Host MMU Emulation</string> | ||
| 146 | </property> | ||
| 147 | <property name="toolTip"> | ||
| 148 | <string> | ||
| 149 | <div style="white-space: nowrap">This optimization speeds up memory accesses by the guest program.</div> | ||
| 150 | <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> | ||
| 151 | <div style="white-space: nowrap">Disabling this forces all memory accesses to use Software MMU Emulation.</div> | ||
| 152 | </string> | ||
| 153 | </property> | ||
| 154 | </widget> | ||
| 155 | </item> | ||
| 142 | </layout> | 156 | </layout> |
| 143 | </widget> | 157 | </widget> |
| 144 | </item> | 158 | </item> |
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index b207e07cb..15d6a5ad7 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp | |||
| @@ -28,17 +28,21 @@ ConfigureDebug::ConfigureDebug(QWidget* parent) : QWidget(parent), ui(new Ui::Co | |||
| 28 | ConfigureDebug::~ConfigureDebug() = default; | 28 | ConfigureDebug::~ConfigureDebug() = default; |
| 29 | 29 | ||
| 30 | void ConfigureDebug::SetConfiguration() { | 30 | void ConfigureDebug::SetConfiguration() { |
| 31 | ui->toggle_console->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | 31 | const bool runtime_lock = !Core::System::GetInstance().IsPoweredOn(); |
| 32 | |||
| 33 | ui->toggle_console->setEnabled(runtime_lock); | ||
| 32 | ui->toggle_console->setChecked(UISettings::values.show_console); | 34 | ui->toggle_console->setChecked(UISettings::values.show_console); |
| 33 | ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); | 35 | ui->log_filter_edit->setText(QString::fromStdString(Settings::values.log_filter)); |
| 34 | ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); | 36 | ui->homebrew_args_edit->setText(QString::fromStdString(Settings::values.program_args)); |
| 37 | ui->fs_access_log->setEnabled(runtime_lock); | ||
| 38 | ui->fs_access_log->setChecked(Settings::values.enable_fs_access_log); | ||
| 35 | ui->reporting_services->setChecked(Settings::values.reporting_services); | 39 | ui->reporting_services->setChecked(Settings::values.reporting_services); |
| 36 | ui->quest_flag->setChecked(Settings::values.quest_flag); | 40 | ui->quest_flag->setChecked(Settings::values.quest_flag); |
| 37 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts); | 41 | ui->use_debug_asserts->setChecked(Settings::values.use_debug_asserts); |
| 38 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub); | 42 | ui->use_auto_stub->setChecked(Settings::values.use_auto_stub); |
| 39 | ui->enable_graphics_debugging->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | 43 | ui->enable_graphics_debugging->setEnabled(runtime_lock); |
| 40 | ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); | 44 | ui->enable_graphics_debugging->setChecked(Settings::values.renderer_debug); |
| 41 | ui->disable_macro_jit->setEnabled(!Core::System::GetInstance().IsPoweredOn()); | 45 | ui->disable_macro_jit->setEnabled(runtime_lock); |
| 42 | ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit); | 46 | ui->disable_macro_jit->setChecked(Settings::values.disable_macro_jit); |
| 43 | ui->extended_logging->setChecked(Settings::values.extended_logging); | 47 | ui->extended_logging->setChecked(Settings::values.extended_logging); |
| 44 | } | 48 | } |
| @@ -47,6 +51,7 @@ void ConfigureDebug::ApplyConfiguration() { | |||
| 47 | UISettings::values.show_console = ui->toggle_console->isChecked(); | 51 | UISettings::values.show_console = ui->toggle_console->isChecked(); |
| 48 | Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); | 52 | Settings::values.log_filter = ui->log_filter_edit->text().toStdString(); |
| 49 | Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); | 53 | Settings::values.program_args = ui->homebrew_args_edit->text().toStdString(); |
| 54 | Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); | ||
| 50 | Settings::values.reporting_services = ui->reporting_services->isChecked(); | 55 | Settings::values.reporting_services = ui->reporting_services->isChecked(); |
| 51 | Settings::values.quest_flag = ui->quest_flag->isChecked(); | 56 | Settings::values.quest_flag = ui->quest_flag->isChecked(); |
| 52 | Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); | 57 | Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); |
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index c9e60ee08..c8087542f 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui | |||
| @@ -144,10 +144,17 @@ | |||
| 144 | <item> | 144 | <item> |
| 145 | <widget class="QGroupBox" name="groupBox_5"> | 145 | <widget class="QGroupBox" name="groupBox_5"> |
| 146 | <property name="title"> | 146 | <property name="title"> |
| 147 | <string>Dump</string> | 147 | <string>Debugging</string> |
| 148 | </property> | 148 | </property> |
| 149 | <layout class="QVBoxLayout" name="verticalLayout_7"> | 149 | <layout class="QVBoxLayout" name="verticalLayout_7"> |
| 150 | <item> | 150 | <item> |
| 151 | <widget class="QCheckBox" name="fs_access_log"> | ||
| 152 | <property name="text"> | ||
| 153 | <string>Enable FS Access Log</string> | ||
| 154 | </property> | ||
| 155 | </widget> | ||
| 156 | </item> | ||
| 157 | <item> | ||
| 151 | <widget class="QCheckBox" name="reporting_services"> | 158 | <widget class="QCheckBox" name="reporting_services"> |
| 152 | <property name="text"> | 159 | <property name="text"> |
| 153 | <string>Enable Verbose Reporting Services</string> | 160 | <string>Enable Verbose Reporting Services</string> |
diff --git a/src/yuzu/configuration/configure_dialog.cpp b/src/yuzu/configuration/configure_dialog.cpp index 6028135c5..371bc01b1 100644 --- a/src/yuzu/configuration/configure_dialog.cpp +++ b/src/yuzu/configuration/configure_dialog.cpp | |||
| @@ -27,6 +27,8 @@ ConfigureDialog::ConfigureDialog(QWidget* parent, HotkeyRegistry& registry, | |||
| 27 | 27 | ||
| 28 | ui->inputTab->Initialize(input_subsystem); | 28 | ui->inputTab->Initialize(input_subsystem); |
| 29 | 29 | ||
| 30 | ui->generalTab->SetResetCallback([&] { this->close(); }); | ||
| 31 | |||
| 30 | SetConfiguration(); | 32 | SetConfiguration(); |
| 31 | PopulateSelectionList(); | 33 | PopulateSelectionList(); |
| 32 | 34 | ||
diff --git a/src/yuzu/configuration/configure_general.cpp b/src/yuzu/configuration/configure_general.cpp index 55a6a37bd..38edb4d8d 100644 --- a/src/yuzu/configuration/configure_general.cpp +++ b/src/yuzu/configuration/configure_general.cpp | |||
| @@ -2,11 +2,15 @@ | |||
| 2 | // Licensed under GPLv2 or any later version | 2 | // Licensed under GPLv2 or any later version |
| 3 | // Refer to the license.txt file included. | 3 | // Refer to the license.txt file included. |
| 4 | 4 | ||
| 5 | #include <functional> | ||
| 6 | #include <utility> | ||
| 5 | #include <QCheckBox> | 7 | #include <QCheckBox> |
| 8 | #include <QMessageBox> | ||
| 6 | #include <QSpinBox> | 9 | #include <QSpinBox> |
| 7 | #include "common/settings.h" | 10 | #include "common/settings.h" |
| 8 | #include "core/core.h" | 11 | #include "core/core.h" |
| 9 | #include "ui_configure_general.h" | 12 | #include "ui_configure_general.h" |
| 13 | #include "yuzu/configuration/config.h" | ||
| 10 | #include "yuzu/configuration/configuration_shared.h" | 14 | #include "yuzu/configuration/configuration_shared.h" |
| 11 | #include "yuzu/configuration/configure_general.h" | 15 | #include "yuzu/configuration/configure_general.h" |
| 12 | #include "yuzu/uisettings.h" | 16 | #include "yuzu/uisettings.h" |
| @@ -23,6 +27,9 @@ ConfigureGeneral::ConfigureGeneral(QWidget* parent) | |||
| 23 | connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, | 27 | connect(ui->toggle_frame_limit, &QCheckBox::clicked, ui->frame_limit, |
| 24 | [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); }); | 28 | [this]() { ui->frame_limit->setEnabled(ui->toggle_frame_limit->isChecked()); }); |
| 25 | } | 29 | } |
| 30 | |||
| 31 | connect(ui->button_reset_defaults, &QPushButton::clicked, this, | ||
| 32 | &ConfigureGeneral::ResetDefaults); | ||
| 26 | } | 33 | } |
| 27 | 34 | ||
| 28 | ConfigureGeneral::~ConfigureGeneral() = default; | 35 | ConfigureGeneral::~ConfigureGeneral() = default; |
| @@ -41,6 +48,8 @@ void ConfigureGeneral::SetConfiguration() { | |||
| 41 | ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); | 48 | ui->toggle_frame_limit->setChecked(Settings::values.use_frame_limit.GetValue()); |
| 42 | ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); | 49 | ui->frame_limit->setValue(Settings::values.frame_limit.GetValue()); |
| 43 | 50 | ||
| 51 | ui->button_reset_defaults->setEnabled(runtime_lock); | ||
| 52 | |||
| 44 | if (Settings::IsConfiguringGlobal()) { | 53 | if (Settings::IsConfiguringGlobal()) { |
| 45 | ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue()); | 54 | ui->frame_limit->setEnabled(Settings::values.use_frame_limit.GetValue()); |
| 46 | } else { | 55 | } else { |
| @@ -49,6 +58,25 @@ void ConfigureGeneral::SetConfiguration() { | |||
| 49 | } | 58 | } |
| 50 | } | 59 | } |
| 51 | 60 | ||
| 61 | // Called to set the callback when resetting settings to defaults | ||
| 62 | void ConfigureGeneral::SetResetCallback(std::function<void()> callback) { | ||
| 63 | reset_callback = std::move(callback); | ||
| 64 | } | ||
| 65 | |||
| 66 | void ConfigureGeneral::ResetDefaults() { | ||
| 67 | QMessageBox::StandardButton answer = QMessageBox::question( | ||
| 68 | this, tr("yuzu"), | ||
| 69 | tr("This reset all settings and remove all per-game configurations. This will not delete " | ||
| 70 | "game directories, profiles, or input profiles. Proceed?"), | ||
| 71 | QMessageBox::Yes | QMessageBox::No, QMessageBox::No); | ||
| 72 | if (answer == QMessageBox::No) { | ||
| 73 | return; | ||
| 74 | } | ||
| 75 | UISettings::values.reset_to_defaults = true; | ||
| 76 | UISettings::values.is_game_list_reload_pending.exchange(true); | ||
| 77 | reset_callback(); | ||
| 78 | } | ||
| 79 | |||
| 52 | void ConfigureGeneral::ApplyConfiguration() { | 80 | void ConfigureGeneral::ApplyConfiguration() { |
| 53 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core, | 81 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_multi_core, ui->use_multi_core, |
| 54 | use_multi_core); | 82 | use_multi_core); |
| @@ -105,6 +133,8 @@ void ConfigureGeneral::SetupPerGameUI() { | |||
| 105 | ui->toggle_background_pause->setVisible(false); | 133 | ui->toggle_background_pause->setVisible(false); |
| 106 | ui->toggle_hide_mouse->setVisible(false); | 134 | ui->toggle_hide_mouse->setVisible(false); |
| 107 | 135 | ||
| 136 | ui->button_reset_defaults->setVisible(false); | ||
| 137 | |||
| 108 | ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit, | 138 | ConfigurationShared::SetColoredTristate(ui->toggle_frame_limit, |
| 109 | Settings::values.use_frame_limit, use_frame_limit); | 139 | Settings::values.use_frame_limit, use_frame_limit); |
| 110 | ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core, | 140 | ConfigurationShared::SetColoredTristate(ui->use_multi_core, Settings::values.use_multi_core, |
diff --git a/src/yuzu/configuration/configure_general.h b/src/yuzu/configuration/configure_general.h index 323ffbd8f..a0fd52492 100644 --- a/src/yuzu/configuration/configure_general.h +++ b/src/yuzu/configuration/configure_general.h | |||
| @@ -4,9 +4,12 @@ | |||
| 4 | 4 | ||
| 5 | #pragma once | 5 | #pragma once |
| 6 | 6 | ||
| 7 | #include <functional> | ||
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <QWidget> | 9 | #include <QWidget> |
| 9 | 10 | ||
| 11 | class ConfigureDialog; | ||
| 12 | |||
| 10 | namespace ConfigurationShared { | 13 | namespace ConfigurationShared { |
| 11 | enum class CheckState; | 14 | enum class CheckState; |
| 12 | } | 15 | } |
| @@ -24,6 +27,8 @@ public: | |||
| 24 | explicit ConfigureGeneral(QWidget* parent = nullptr); | 27 | explicit ConfigureGeneral(QWidget* parent = nullptr); |
| 25 | ~ConfigureGeneral() override; | 28 | ~ConfigureGeneral() override; |
| 26 | 29 | ||
| 30 | void SetResetCallback(std::function<void()> callback); | ||
| 31 | void ResetDefaults(); | ||
| 27 | void ApplyConfiguration(); | 32 | void ApplyConfiguration(); |
| 28 | 33 | ||
| 29 | private: | 34 | private: |
| @@ -34,6 +39,8 @@ private: | |||
| 34 | 39 | ||
| 35 | void SetupPerGameUI(); | 40 | void SetupPerGameUI(); |
| 36 | 41 | ||
| 42 | std::function<void()> reset_callback; | ||
| 43 | |||
| 37 | std::unique_ptr<Ui::ConfigureGeneral> ui; | 44 | std::unique_ptr<Ui::ConfigureGeneral> ui; |
| 38 | 45 | ||
| 39 | ConfigurationShared::CheckState use_frame_limit; | 46 | ConfigurationShared::CheckState use_frame_limit; |
diff --git a/src/yuzu/configuration/configure_general.ui b/src/yuzu/configuration/configure_general.ui index 2711116a2..bc7041090 100644 --- a/src/yuzu/configuration/configure_general.ui +++ b/src/yuzu/configuration/configure_general.ui | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | <rect> | 6 | <rect> |
| 7 | <x>0</x> | 7 | <x>0</x> |
| 8 | <y>0</y> | 8 | <y>0</y> |
| 9 | <width>300</width> | 9 | <width>329</width> |
| 10 | <height>407</height> | 10 | <height>407</height> |
| 11 | </rect> | 11 | </rect> |
| 12 | </property> | 12 | </property> |
| @@ -104,6 +104,45 @@ | |||
| 104 | </property> | 104 | </property> |
| 105 | </spacer> | 105 | </spacer> |
| 106 | </item> | 106 | </item> |
| 107 | <item> | ||
| 108 | <layout class="QHBoxLayout" name="layout_reset"> | ||
| 109 | <property name="spacing"> | ||
| 110 | <number>6</number> | ||
| 111 | </property> | ||
| 112 | <property name="leftMargin"> | ||
| 113 | <number>5</number> | ||
| 114 | </property> | ||
| 115 | <property name="topMargin"> | ||
| 116 | <number>5</number> | ||
| 117 | </property> | ||
| 118 | <property name="rightMargin"> | ||
| 119 | <number>5</number> | ||
| 120 | </property> | ||
| 121 | <property name="bottomMargin"> | ||
| 122 | <number>5</number> | ||
| 123 | </property> | ||
| 124 | <item> | ||
| 125 | <widget class="QPushButton" name="button_reset_defaults"> | ||
| 126 | <property name="text"> | ||
| 127 | <string>Reset All Settings</string> | ||
| 128 | </property> | ||
| 129 | </widget> | ||
| 130 | </item> | ||
| 131 | <item> | ||
| 132 | <spacer name="spacer_reset"> | ||
| 133 | <property name="orientation"> | ||
| 134 | <enum>Qt::Horizontal</enum> | ||
| 135 | </property> | ||
| 136 | <property name="sizeHint" stdset="0"> | ||
| 137 | <size> | ||
| 138 | <width>40</width> | ||
| 139 | <height>20</height> | ||
| 140 | </size> | ||
| 141 | </property> | ||
| 142 | </spacer> | ||
| 143 | </item> | ||
| 144 | </layout> | ||
| 145 | </item> | ||
| 107 | </layout> | 146 | </layout> |
| 108 | </item> | 147 | </item> |
| 109 | </layout> | 148 | </layout> |
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp index fb9ec093c..41a69d9b8 100644 --- a/src/yuzu/configuration/configure_graphics.cpp +++ b/src/yuzu/configuration/configure_graphics.cpp | |||
| @@ -70,10 +70,12 @@ void ConfigureGraphics::SetConfiguration() { | |||
| 70 | ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); | 70 | ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); |
| 71 | ui->use_disk_shader_cache->setEnabled(runtime_lock); | 71 | ui->use_disk_shader_cache->setEnabled(runtime_lock); |
| 72 | ui->use_nvdec_emulation->setEnabled(runtime_lock); | 72 | ui->use_nvdec_emulation->setEnabled(runtime_lock); |
| 73 | ui->accelerate_astc->setEnabled(runtime_lock); | ||
| 73 | ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); | 74 | ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); |
| 74 | ui->use_asynchronous_gpu_emulation->setChecked( | 75 | ui->use_asynchronous_gpu_emulation->setChecked( |
| 75 | Settings::values.use_asynchronous_gpu_emulation.GetValue()); | 76 | Settings::values.use_asynchronous_gpu_emulation.GetValue()); |
| 76 | ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); | 77 | ui->use_nvdec_emulation->setChecked(Settings::values.use_nvdec_emulation.GetValue()); |
| 78 | ui->accelerate_astc->setChecked(Settings::values.accelerate_astc.GetValue()); | ||
| 77 | 79 | ||
| 78 | if (Settings::IsConfiguringGlobal()) { | 80 | if (Settings::IsConfiguringGlobal()) { |
| 79 | ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); | 81 | ui->api->setCurrentIndex(static_cast<int>(Settings::values.renderer_backend.GetValue())); |
| @@ -118,6 +120,8 @@ void ConfigureGraphics::ApplyConfiguration() { | |||
| 118 | use_asynchronous_gpu_emulation); | 120 | use_asynchronous_gpu_emulation); |
| 119 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, | 121 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_nvdec_emulation, |
| 120 | ui->use_nvdec_emulation, use_nvdec_emulation); | 122 | ui->use_nvdec_emulation, use_nvdec_emulation); |
| 123 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.accelerate_astc, ui->accelerate_astc, | ||
| 124 | accelerate_astc); | ||
| 121 | 125 | ||
| 122 | if (Settings::IsConfiguringGlobal()) { | 126 | if (Settings::IsConfiguringGlobal()) { |
| 123 | // Guard if during game and set to game-specific value | 127 | // Guard if during game and set to game-specific value |
| @@ -254,6 +258,7 @@ void ConfigureGraphics::SetupPerGameUI() { | |||
| 254 | ui->use_asynchronous_gpu_emulation->setEnabled( | 258 | ui->use_asynchronous_gpu_emulation->setEnabled( |
| 255 | Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); | 259 | Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); |
| 256 | ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); | 260 | ui->use_nvdec_emulation->setEnabled(Settings::values.use_nvdec_emulation.UsingGlobal()); |
| 261 | ui->accelerate_astc->setEnabled(Settings::values.accelerate_astc.UsingGlobal()); | ||
| 257 | ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); | 262 | ui->use_disk_shader_cache->setEnabled(Settings::values.use_disk_shader_cache.UsingGlobal()); |
| 258 | ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); | 263 | ui->bg_button->setEnabled(Settings::values.bg_red.UsingGlobal()); |
| 259 | 264 | ||
| @@ -269,6 +274,8 @@ void ConfigureGraphics::SetupPerGameUI() { | |||
| 269 | ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); | 274 | ui->use_disk_shader_cache, Settings::values.use_disk_shader_cache, use_disk_shader_cache); |
| 270 | ConfigurationShared::SetColoredTristate( | 275 | ConfigurationShared::SetColoredTristate( |
| 271 | ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); | 276 | ui->use_nvdec_emulation, Settings::values.use_nvdec_emulation, use_nvdec_emulation); |
| 277 | ConfigurationShared::SetColoredTristate(ui->accelerate_astc, Settings::values.accelerate_astc, | ||
| 278 | accelerate_astc); | ||
| 272 | ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, | 279 | ConfigurationShared::SetColoredTristate(ui->use_asynchronous_gpu_emulation, |
| 273 | Settings::values.use_asynchronous_gpu_emulation, | 280 | Settings::values.use_asynchronous_gpu_emulation, |
| 274 | use_asynchronous_gpu_emulation); | 281 | use_asynchronous_gpu_emulation); |
diff --git a/src/yuzu/configuration/configure_graphics.h b/src/yuzu/configuration/configure_graphics.h index c162048a2..6418115cf 100644 --- a/src/yuzu/configuration/configure_graphics.h +++ b/src/yuzu/configuration/configure_graphics.h | |||
| @@ -47,6 +47,7 @@ private: | |||
| 47 | QColor bg_color; | 47 | QColor bg_color; |
| 48 | 48 | ||
| 49 | ConfigurationShared::CheckState use_nvdec_emulation; | 49 | ConfigurationShared::CheckState use_nvdec_emulation; |
| 50 | ConfigurationShared::CheckState accelerate_astc; | ||
| 50 | ConfigurationShared::CheckState use_disk_shader_cache; | 51 | ConfigurationShared::CheckState use_disk_shader_cache; |
| 51 | ConfigurationShared::CheckState use_asynchronous_gpu_emulation; | 52 | ConfigurationShared::CheckState use_asynchronous_gpu_emulation; |
| 52 | 53 | ||
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui index ab0bd4d77..5b999d84d 100644 --- a/src/yuzu/configuration/configure_graphics.ui +++ b/src/yuzu/configuration/configure_graphics.ui | |||
| @@ -105,6 +105,13 @@ | |||
| 105 | </widget> | 105 | </widget> |
| 106 | </item> | 106 | </item> |
| 107 | <item> | 107 | <item> |
| 108 | <widget class="QCheckBox" name="accelerate_astc"> | ||
| 109 | <property name="text"> | ||
| 110 | <string>Accelerate ASTC texture decoding</string> | ||
| 111 | </property> | ||
| 112 | </widget> | ||
| 113 | </item> | ||
| 114 | <item> | ||
| 108 | <widget class="QWidget" name="fullscreen_mode_layout" native="true"> | 115 | <widget class="QWidget" name="fullscreen_mode_layout" native="true"> |
| 109 | <layout class="QHBoxLayout" name="horizontalLayout_1"> | 116 | <layout class="QHBoxLayout" name="horizontalLayout_1"> |
| 110 | <property name="leftMargin"> | 117 | <property name="leftMargin"> |
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index ab3512810..d5d624b96 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp | |||
| @@ -1395,7 +1395,8 @@ void ConfigureInputPlayer::keyPressEvent(QKeyEvent* event) { | |||
| 1395 | 1395 | ||
| 1396 | void ConfigureInputPlayer::CreateProfile() { | 1396 | void ConfigureInputPlayer::CreateProfile() { |
| 1397 | const auto profile_name = | 1397 | const auto profile_name = |
| 1398 | LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20); | 1398 | LimitableInputDialog::GetText(this, tr("New Profile"), tr("Enter a profile name:"), 1, 20, |
| 1399 | LimitableInputDialog::InputLimiter::Filesystem); | ||
| 1399 | 1400 | ||
| 1400 | if (profile_name.isEmpty()) { | 1401 | if (profile_name.isEmpty()) { |
| 1401 | return; | 1402 | return; |
diff --git a/src/yuzu/configuration/configure_input_player_widget.cpp b/src/yuzu/configuration/configure_input_player_widget.cpp index 61ba91cef..f50cda2f3 100644 --- a/src/yuzu/configuration/configure_input_player_widget.cpp +++ b/src/yuzu/configuration/configure_input_player_widget.cpp | |||
| @@ -85,6 +85,8 @@ void PlayerControlPreview::SetConnectedStatus(bool checked) { | |||
| 85 | led_color[1] = led_pattern.position2 ? colors.led_on : colors.led_off; | 85 | led_color[1] = led_pattern.position2 ? colors.led_on : colors.led_off; |
| 86 | led_color[2] = led_pattern.position3 ? colors.led_on : colors.led_off; | 86 | led_color[2] = led_pattern.position3 ? colors.led_on : colors.led_off; |
| 87 | led_color[3] = led_pattern.position4 ? colors.led_on : colors.led_off; | 87 | led_color[3] = led_pattern.position4 ? colors.led_on : colors.led_off; |
| 88 | is_enabled = checked; | ||
| 89 | ResetInputs(); | ||
| 88 | } | 90 | } |
| 89 | 91 | ||
| 90 | void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) { | 92 | void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) { |
| @@ -108,6 +110,7 @@ void PlayerControlPreview::EndMapping() { | |||
| 108 | analog_mapping_index = Settings::NativeAnalog::NumAnalogs; | 110 | analog_mapping_index = Settings::NativeAnalog::NumAnalogs; |
| 109 | mapping_active = false; | 111 | mapping_active = false; |
| 110 | blink_counter = 0; | 112 | blink_counter = 0; |
| 113 | ResetInputs(); | ||
| 111 | } | 114 | } |
| 112 | 115 | ||
| 113 | void PlayerControlPreview::UpdateColors() { | 116 | void PlayerControlPreview::UpdateColors() { |
| @@ -156,7 +159,23 @@ void PlayerControlPreview::UpdateColors() { | |||
| 156 | // colors.right = QColor(Settings::values.players.GetValue()[player_index].body_color_right); | 159 | // colors.right = QColor(Settings::values.players.GetValue()[player_index].body_color_right); |
| 157 | } | 160 | } |
| 158 | 161 | ||
| 162 | void PlayerControlPreview::ResetInputs() { | ||
| 163 | for (std::size_t index = 0; index < button_values.size(); ++index) { | ||
| 164 | button_values[index] = false; | ||
| 165 | } | ||
| 166 | |||
| 167 | for (std::size_t index = 0; index < axis_values.size(); ++index) { | ||
| 168 | axis_values[index].properties = {0, 1, 0}; | ||
| 169 | axis_values[index].value = {0, 0}; | ||
| 170 | axis_values[index].raw_value = {0, 0}; | ||
| 171 | } | ||
| 172 | update(); | ||
| 173 | } | ||
| 174 | |||
| 159 | void PlayerControlPreview::UpdateInput() { | 175 | void PlayerControlPreview::UpdateInput() { |
| 176 | if (!is_enabled && !mapping_active) { | ||
| 177 | return; | ||
| 178 | } | ||
| 160 | bool input_changed = false; | 179 | bool input_changed = false; |
| 161 | const auto& button_state = buttons; | 180 | const auto& button_state = buttons; |
| 162 | for (std::size_t index = 0; index < button_values.size(); ++index) { | 181 | for (std::size_t index = 0; index < button_values.size(); ++index) { |
diff --git a/src/yuzu/configuration/configure_input_player_widget.h b/src/yuzu/configuration/configure_input_player_widget.h index 51bb84eb6..5fc16d8af 100644 --- a/src/yuzu/configuration/configure_input_player_widget.h +++ b/src/yuzu/configuration/configure_input_player_widget.h | |||
| @@ -100,6 +100,7 @@ private: | |||
| 100 | 100 | ||
| 101 | static LedPattern GetColorPattern(std::size_t index, bool player_on); | 101 | static LedPattern GetColorPattern(std::size_t index, bool player_on); |
| 102 | void UpdateColors(); | 102 | void UpdateColors(); |
| 103 | void ResetInputs(); | ||
| 103 | 104 | ||
| 104 | // Draw controller functions | 105 | // Draw controller functions |
| 105 | void DrawHandheldController(QPainter& p, QPointF center); | 106 | void DrawHandheldController(QPainter& p, QPointF center); |
| @@ -176,6 +177,7 @@ private: | |||
| 176 | using StickArray = | 177 | using StickArray = |
| 177 | std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>; | 178 | std::array<std::unique_ptr<Input::AnalogDevice>, Settings::NativeAnalog::NUM_STICKS_HID>; |
| 178 | 179 | ||
| 180 | bool is_enabled{}; | ||
| 179 | bool mapping_active{}; | 181 | bool mapping_active{}; |
| 180 | int blink_counter{}; | 182 | int blink_counter{}; |
| 181 | QColor button_color{}; | 183 | QColor button_color{}; |
diff --git a/src/yuzu/configuration/configure_ui.cpp b/src/yuzu/configuration/configure_ui.cpp index 0a28c87c0..9674119e1 100644 --- a/src/yuzu/configuration/configure_ui.cpp +++ b/src/yuzu/configuration/configure_ui.cpp | |||
| @@ -17,17 +17,30 @@ | |||
| 17 | 17 | ||
| 18 | namespace { | 18 | namespace { |
| 19 | constexpr std::array default_icon_sizes{ | 19 | constexpr std::array default_icon_sizes{ |
| 20 | std::make_pair(0, QT_TR_NOOP("None")), | 20 | std::make_pair(0, QT_TRANSLATE_NOOP("ConfigureUI", "None")), |
| 21 | std::make_pair(32, QT_TR_NOOP("Small (32x32)")), | 21 | std::make_pair(32, QT_TRANSLATE_NOOP("ConfigureUI", "Small (32x32)")), |
| 22 | std::make_pair(64, QT_TR_NOOP("Standard (64x64)")), | 22 | std::make_pair(64, QT_TRANSLATE_NOOP("ConfigureUI", "Standard (64x64)")), |
| 23 | std::make_pair(128, QT_TR_NOOP("Large (128x128)")), | 23 | std::make_pair(128, QT_TRANSLATE_NOOP("ConfigureUI", "Large (128x128)")), |
| 24 | std::make_pair(256, QT_TR_NOOP("Full Size (256x256)")), | 24 | std::make_pair(256, QT_TRANSLATE_NOOP("ConfigureUI", "Full Size (256x256)")), |
| 25 | }; | 25 | }; |
| 26 | 26 | ||
| 27 | // clang-format off | ||
| 27 | constexpr std::array row_text_names{ | 28 | constexpr std::array row_text_names{ |
| 28 | QT_TR_NOOP("Filename"), QT_TR_NOOP("Filetype"), QT_TR_NOOP("Title ID"), | 29 | QT_TRANSLATE_NOOP("ConfigureUI", "Filename"), |
| 29 | QT_TR_NOOP("Title Name"), QT_TR_NOOP("None"), | 30 | QT_TRANSLATE_NOOP("ConfigureUI", "Filetype"), |
| 31 | QT_TRANSLATE_NOOP("ConfigureUI", "Title ID"), | ||
| 32 | QT_TRANSLATE_NOOP("ConfigureUI", "Title Name"), | ||
| 33 | QT_TRANSLATE_NOOP("ConfigureUI", "None"), | ||
| 30 | }; | 34 | }; |
| 35 | // clang-format on | ||
| 36 | |||
| 37 | QString GetTranslatedIconSize(size_t index) { | ||
| 38 | return QCoreApplication::translate("ConfigureUI", default_icon_sizes[index].second); | ||
| 39 | } | ||
| 40 | |||
| 41 | QString GetTranslatedRowTextName(size_t index) { | ||
| 42 | return QCoreApplication::translate("ConfigureUI", row_text_names[index]); | ||
| 43 | } | ||
| 31 | } // Anonymous namespace | 44 | } // Anonymous namespace |
| 32 | 45 | ||
| 33 | ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) { | 46 | ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) { |
| @@ -121,11 +134,11 @@ void ConfigureUi::RetranslateUI() { | |||
| 121 | ui->retranslateUi(this); | 134 | ui->retranslateUi(this); |
| 122 | 135 | ||
| 123 | for (int i = 0; i < ui->icon_size_combobox->count(); i++) { | 136 | for (int i = 0; i < ui->icon_size_combobox->count(); i++) { |
| 124 | ui->icon_size_combobox->setItemText(i, tr(default_icon_sizes[i].second)); | 137 | ui->icon_size_combobox->setItemText(i, GetTranslatedIconSize(static_cast<size_t>(i))); |
| 125 | } | 138 | } |
| 126 | 139 | ||
| 127 | for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { | 140 | for (int i = 0; i < ui->row_1_text_combobox->count(); i++) { |
| 128 | const QString name = tr(row_text_names[i]); | 141 | const QString name = GetTranslatedRowTextName(static_cast<size_t>(i)); |
| 129 | 142 | ||
| 130 | ui->row_1_text_combobox->setItemText(i, name); | 143 | ui->row_1_text_combobox->setItemText(i, name); |
| 131 | ui->row_2_text_combobox->setItemText(i, name); | 144 | ui->row_2_text_combobox->setItemText(i, name); |
| @@ -152,8 +165,9 @@ void ConfigureUi::InitializeLanguageComboBox() { | |||
| 152 | } | 165 | } |
| 153 | 166 | ||
| 154 | void ConfigureUi::InitializeIconSizeComboBox() { | 167 | void ConfigureUi::InitializeIconSizeComboBox() { |
| 155 | for (const auto& size : default_icon_sizes) { | 168 | for (size_t i = 0; i < default_icon_sizes.size(); i++) { |
| 156 | ui->icon_size_combobox->addItem(QString::fromUtf8(size.second), size.first); | 169 | const auto size = default_icon_sizes[i].first; |
| 170 | ui->icon_size_combobox->addItem(GetTranslatedIconSize(i), size); | ||
| 157 | } | 171 | } |
| 158 | } | 172 | } |
| 159 | 173 | ||
| @@ -170,7 +184,7 @@ void ConfigureUi::UpdateFirstRowComboBox(bool init) { | |||
| 170 | ui->row_1_text_combobox->clear(); | 184 | ui->row_1_text_combobox->clear(); |
| 171 | 185 | ||
| 172 | for (std::size_t i = 0; i < row_text_names.size(); i++) { | 186 | for (std::size_t i = 0; i < row_text_names.size(); i++) { |
| 173 | const QString row_text_name = QString::fromUtf8(row_text_names[i]); | 187 | const QString row_text_name = GetTranslatedRowTextName(i); |
| 174 | ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); | 188 | ui->row_1_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); |
| 175 | } | 189 | } |
| 176 | 190 | ||
| @@ -189,7 +203,7 @@ void ConfigureUi::UpdateSecondRowComboBox(bool init) { | |||
| 189 | ui->row_2_text_combobox->clear(); | 203 | ui->row_2_text_combobox->clear(); |
| 190 | 204 | ||
| 191 | for (std::size_t i = 0; i < row_text_names.size(); ++i) { | 205 | for (std::size_t i = 0; i < row_text_names.size(); ++i) { |
| 192 | const QString row_text_name = QString::fromUtf8(row_text_names[i]); | 206 | const QString row_text_name = GetTranslatedRowTextName(i); |
| 193 | ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); | 207 | ui->row_2_text_combobox->addItem(row_text_name, QVariant::fromValue(i)); |
| 194 | } | 208 | } |
| 195 | 209 | ||
diff --git a/src/yuzu/debugger/controller.cpp b/src/yuzu/debugger/controller.cpp index d85408ac6..c1fc69578 100644 --- a/src/yuzu/debugger/controller.cpp +++ b/src/yuzu/debugger/controller.cpp | |||
| @@ -28,6 +28,7 @@ ControllerDialog::ControllerDialog(QWidget* parent) : QWidget(parent, Qt::Dialog | |||
| 28 | // Configure focus so that widget is focusable and the dialog automatically forwards focus to | 28 | // Configure focus so that widget is focusable and the dialog automatically forwards focus to |
| 29 | // it. | 29 | // it. |
| 30 | setFocusProxy(widget); | 30 | setFocusProxy(widget); |
| 31 | widget->SetConnectedStatus(false); | ||
| 31 | widget->setFocusPolicy(Qt::StrongFocus); | 32 | widget->setFocusPolicy(Qt::StrongFocus); |
| 32 | widget->setFocus(); | 33 | widget->setFocus(); |
| 33 | } | 34 | } |
| @@ -36,9 +37,8 @@ void ControllerDialog::refreshConfiguration() { | |||
| 36 | const auto& players = Settings::values.players.GetValue(); | 37 | const auto& players = Settings::values.players.GetValue(); |
| 37 | constexpr std::size_t player = 0; | 38 | constexpr std::size_t player = 0; |
| 38 | widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); | 39 | widget->SetPlayerInputRaw(player, players[player].buttons, players[player].analogs); |
| 39 | widget->SetConnectedStatus(players[player].connected); | ||
| 40 | widget->SetControllerType(players[player].controller_type); | 40 | widget->SetControllerType(players[player].controller_type); |
| 41 | widget->repaint(); | 41 | widget->SetConnectedStatus(players[player].connected); |
| 42 | } | 42 | } |
| 43 | 43 | ||
| 44 | QAction* ControllerDialog::toggleViewAction() { | 44 | QAction* ControllerDialog::toggleViewAction() { |
| @@ -56,6 +56,7 @@ void ControllerDialog::showEvent(QShowEvent* ev) { | |||
| 56 | if (toggle_view_action) { | 56 | if (toggle_view_action) { |
| 57 | toggle_view_action->setChecked(isVisible()); | 57 | toggle_view_action->setChecked(isVisible()); |
| 58 | } | 58 | } |
| 59 | refreshConfiguration(); | ||
| 59 | QWidget::showEvent(ev); | 60 | QWidget::showEvent(ev); |
| 60 | } | 61 | } |
| 61 | 62 | ||
| @@ -63,5 +64,6 @@ void ControllerDialog::hideEvent(QHideEvent* ev) { | |||
| 63 | if (toggle_view_action) { | 64 | if (toggle_view_action) { |
| 64 | toggle_view_action->setChecked(isVisible()); | 65 | toggle_view_action->setChecked(isVisible()); |
| 65 | } | 66 | } |
| 67 | widget->SetConnectedStatus(false); | ||
| 66 | QWidget::hideEvent(ev); | 68 | QWidget::hideEvent(ev); |
| 67 | } | 69 | } |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index c2e84ef79..da956c99b 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -341,11 +341,7 @@ GameList::GameList(FileSys::VirtualFilesystem vfs, FileSys::ManualContentProvide | |||
| 341 | connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); | 341 | connect(tree_view, &QTreeView::customContextMenuRequested, this, &GameList::PopupContextMenu); |
| 342 | connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded); | 342 | connect(tree_view, &QTreeView::expanded, this, &GameList::OnItemExpanded); |
| 343 | connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded); | 343 | connect(tree_view, &QTreeView::collapsed, this, &GameList::OnItemExpanded); |
| 344 | connect(tree_view->header(), &QHeaderView::sectionResized, this, | 344 | |
| 345 | &GameList::SaveInterfaceLayout); | ||
| 346 | connect(tree_view->header(), &QHeaderView::sectionMoved, this, &GameList::SaveInterfaceLayout); | ||
| 347 | connect(tree_view->header(), &QHeaderView::sortIndicatorChanged, this, | ||
| 348 | &GameList::SaveInterfaceLayout); | ||
| 349 | // We must register all custom types with the Qt Automoc system so that we are able to use | 345 | // We must register all custom types with the Qt Automoc system so that we are able to use |
| 350 | // it with signals/slots. In this case, QList falls under the umbrells of custom types. | 346 | // it with signals/slots. In this case, QList falls under the umbrells of custom types. |
| 351 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); | 347 | qRegisterMetaType<QList<QStandardItem*>>("QList<QStandardItem*>"); |
| @@ -509,6 +505,10 @@ void GameList::PopupContextMenu(const QPoint& menu_location) { | |||
| 509 | void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) { | 505 | void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) { |
| 510 | QAction* favorite = context_menu.addAction(tr("Favorite")); | 506 | QAction* favorite = context_menu.addAction(tr("Favorite")); |
| 511 | context_menu.addSeparator(); | 507 | context_menu.addSeparator(); |
| 508 | QAction* start_game = context_menu.addAction(tr("Start Game")); | ||
| 509 | QAction* start_game_global = | ||
| 510 | context_menu.addAction(tr("Start Game without Custom Configuration")); | ||
| 511 | context_menu.addSeparator(); | ||
| 512 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); | 512 | QAction* open_save_location = context_menu.addAction(tr("Open Save Data Location")); |
| 513 | QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); | 513 | QAction* open_mod_location = context_menu.addAction(tr("Open Mod Data Location")); |
| 514 | QAction* open_transferable_shader_cache = | 514 | QAction* open_transferable_shader_cache = |
| @@ -544,6 +544,12 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 544 | connect(open_save_location, &QAction::triggered, [this, program_id, path]() { | 544 | connect(open_save_location, &QAction::triggered, [this, program_id, path]() { |
| 545 | emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); | 545 | emit OpenFolderRequested(program_id, GameListOpenTarget::SaveData, path); |
| 546 | }); | 546 | }); |
| 547 | connect(start_game, &QAction::triggered, [this, path]() { | ||
| 548 | emit BootGame(QString::fromStdString(path), 0, StartGameType::Normal); | ||
| 549 | }); | ||
| 550 | connect(start_game_global, &QAction::triggered, [this, path]() { | ||
| 551 | emit BootGame(QString::fromStdString(path), 0, StartGameType::Global); | ||
| 552 | }); | ||
| 547 | connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { | 553 | connect(open_mod_location, &QAction::triggered, [this, program_id, path]() { |
| 548 | emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); | 554 | emit OpenFolderRequested(program_id, GameListOpenTarget::ModData, path); |
| 549 | }); | 555 | }); |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index ab6866735..b630e34ff 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -28,6 +28,7 @@ class GameListWorker; | |||
| 28 | class GameListSearchField; | 28 | class GameListSearchField; |
| 29 | class GameListDir; | 29 | class GameListDir; |
| 30 | class GMainWindow; | 30 | class GMainWindow; |
| 31 | enum class StartGameType; | ||
| 31 | 32 | ||
| 32 | namespace FileSys { | 33 | namespace FileSys { |
| 33 | class ManualContentProvider; | 34 | class ManualContentProvider; |
| @@ -82,6 +83,7 @@ public: | |||
| 82 | static const QStringList supported_file_extensions; | 83 | static const QStringList supported_file_extensions; |
| 83 | 84 | ||
| 84 | signals: | 85 | signals: |
| 86 | void BootGame(const QString& game_path, std::size_t program_index, StartGameType type); | ||
| 85 | void GameChosen(const QString& game_path); | 87 | void GameChosen(const QString& game_path); |
| 86 | void ShouldCancelWorker(); | 88 | void ShouldCancelWorker(); |
| 87 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target, | 89 | void OpenFolderRequested(u64 program_id, GameListOpenTarget target, |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 237e26829..be8933c5c 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -1094,6 +1094,7 @@ void GMainWindow::OnAppFocusStateChanged(Qt::ApplicationState state) { | |||
| 1094 | } | 1094 | } |
| 1095 | 1095 | ||
| 1096 | void GMainWindow::ConnectWidgetEvents() { | 1096 | void GMainWindow::ConnectWidgetEvents() { |
| 1097 | connect(game_list, &GameList::BootGame, this, &GMainWindow::BootGame); | ||
| 1097 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); | 1098 | connect(game_list, &GameList::GameChosen, this, &GMainWindow::OnGameListLoadFile); |
| 1098 | connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); | 1099 | connect(game_list, &GameList::OpenDirectory, this, &GMainWindow::OnGameListOpenDirectory); |
| 1099 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); | 1100 | connect(game_list, &GameList::OpenFolderRequested, this, &GMainWindow::OnGameListOpenFolder); |
| @@ -1320,7 +1321,7 @@ void GMainWindow::SelectAndSetCurrentUser() { | |||
| 1320 | Settings::values.current_user = dialog.GetIndex(); | 1321 | Settings::values.current_user = dialog.GetIndex(); |
| 1321 | } | 1322 | } |
| 1322 | 1323 | ||
| 1323 | void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { | 1324 | void GMainWindow::BootGame(const QString& filename, std::size_t program_index, StartGameType type) { |
| 1324 | LOG_INFO(Frontend, "yuzu starting..."); | 1325 | LOG_INFO(Frontend, "yuzu starting..."); |
| 1325 | StoreRecentFile(filename); // Put the filename on top of the list | 1326 | StoreRecentFile(filename); // Put the filename on top of the list |
| 1326 | 1327 | ||
| @@ -1332,7 +1333,8 @@ void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { | |||
| 1332 | const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); | 1333 | const auto v_file = Core::GetGameFileFromPath(vfs, filename.toUtf8().constData()); |
| 1333 | const auto loader = Loader::GetLoader(system, v_file, program_index); | 1334 | const auto loader = Loader::GetLoader(system, v_file, program_index); |
| 1334 | 1335 | ||
| 1335 | if (!(loader == nullptr || loader->ReadProgramId(title_id) != Loader::ResultStatus::Success)) { | 1336 | if (loader != nullptr && loader->ReadProgramId(title_id) == Loader::ResultStatus::Success && |
| 1337 | type == StartGameType::Normal) { | ||
| 1336 | // Load per game settings | 1338 | // Load per game settings |
| 1337 | const auto file_path = std::filesystem::path{filename.toStdU16String()}; | 1339 | const auto file_path = std::filesystem::path{filename.toStdU16String()}; |
| 1338 | const auto config_file_name = title_id == 0 | 1340 | const auto config_file_name = title_id == 0 |
| @@ -1944,6 +1946,18 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 1944 | const auto full = res == selections.constFirst(); | 1946 | const auto full = res == selections.constFirst(); |
| 1945 | const auto entry_size = CalculateRomFSEntrySize(extracted, full); | 1947 | const auto entry_size = CalculateRomFSEntrySize(extracted, full); |
| 1946 | 1948 | ||
| 1949 | // The minimum required space is the size of the extracted RomFS + 1 GiB | ||
| 1950 | const auto minimum_free_space = extracted->GetSize() + 0x40000000; | ||
| 1951 | |||
| 1952 | if (full && Common::FS::GetFreeSpaceSize(path) < minimum_free_space) { | ||
| 1953 | QMessageBox::warning(this, tr("RomFS Extraction Failed!"), | ||
| 1954 | tr("There is not enough free space at %1 to extract the RomFS. Please " | ||
| 1955 | "free up space or select a different dump directory at " | ||
| 1956 | "Emulation > Configure > System > Filesystem > Dump Root") | ||
| 1957 | .arg(QString::fromStdString(path))); | ||
| 1958 | return; | ||
| 1959 | } | ||
| 1960 | |||
| 1947 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, | 1961 | QProgressDialog progress(tr("Extracting RomFS..."), tr("Cancel"), 0, |
| 1948 | static_cast<s32>(entry_size), this); | 1962 | static_cast<s32>(entry_size), this); |
| 1949 | progress.setWindowModality(Qt::WindowModal); | 1963 | progress.setWindowModality(Qt::WindowModal); |
| @@ -2596,13 +2610,53 @@ void GMainWindow::OnConfigure() { | |||
| 2596 | &GMainWindow::OnLanguageChanged); | 2610 | &GMainWindow::OnLanguageChanged); |
| 2597 | 2611 | ||
| 2598 | const auto result = configure_dialog.exec(); | 2612 | const auto result = configure_dialog.exec(); |
| 2599 | if (result != QDialog::Accepted && !UISettings::values.configuration_applied) { | 2613 | if (result != QDialog::Accepted && !UISettings::values.configuration_applied && |
| 2614 | !UISettings::values.reset_to_defaults) { | ||
| 2615 | // Runs if the user hit Cancel or closed the window, and did not ever press the Apply button | ||
| 2616 | // or `Reset to Defaults` button | ||
| 2600 | return; | 2617 | return; |
| 2601 | } else if (result == QDialog::Accepted) { | 2618 | } else if (result == QDialog::Accepted) { |
| 2619 | // Only apply new changes if user hit Okay | ||
| 2620 | // This is here to avoid applying changes if the user hit Apply, made some changes, then hit | ||
| 2621 | // Cancel | ||
| 2602 | configure_dialog.ApplyConfiguration(); | 2622 | configure_dialog.ApplyConfiguration(); |
| 2603 | controller_dialog->refreshConfiguration(); | 2623 | } else if (UISettings::values.reset_to_defaults) { |
| 2624 | LOG_INFO(Frontend, "Resetting all settings to defaults"); | ||
| 2625 | if (!Common::FS::RemoveFile(config->GetConfigFilePath())) { | ||
| 2626 | LOG_WARNING(Frontend, "Failed to remove configuration file"); | ||
| 2627 | } | ||
| 2628 | if (!Common::FS::RemoveDirContentsRecursively( | ||
| 2629 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::ConfigDir) / "custom")) { | ||
| 2630 | LOG_WARNING(Frontend, "Failed to remove custom configuration files"); | ||
| 2631 | } | ||
| 2632 | if (!Common::FS::RemoveDirRecursively( | ||
| 2633 | Common::FS::GetYuzuPath(Common::FS::YuzuPath::CacheDir) / "game_list")) { | ||
| 2634 | LOG_WARNING(Frontend, "Failed to remove game metadata cache files"); | ||
| 2635 | } | ||
| 2636 | |||
| 2637 | // Explicitly save the game directories, since reinitializing config does not explicitly do | ||
| 2638 | // so. | ||
| 2639 | QVector<UISettings::GameDir> old_game_dirs = std::move(UISettings::values.game_dirs); | ||
| 2640 | QVector<u64> old_favorited_ids = std::move(UISettings::values.favorited_ids); | ||
| 2641 | |||
| 2642 | Settings::values.disabled_addons.clear(); | ||
| 2643 | |||
| 2644 | config = std::make_unique<Config>(); | ||
| 2645 | UISettings::values.reset_to_defaults = false; | ||
| 2646 | |||
| 2647 | UISettings::values.game_dirs = std::move(old_game_dirs); | ||
| 2648 | UISettings::values.favorited_ids = std::move(old_favorited_ids); | ||
| 2649 | |||
| 2650 | InitializeRecentFileMenuActions(); | ||
| 2651 | |||
| 2652 | SetDefaultUIGeometry(); | ||
| 2653 | RestoreUIState(); | ||
| 2654 | |||
| 2655 | ShowTelemetryCallout(); | ||
| 2604 | } | 2656 | } |
| 2657 | controller_dialog->refreshConfiguration(); | ||
| 2605 | InitializeHotkeys(); | 2658 | InitializeHotkeys(); |
| 2659 | |||
| 2606 | if (UISettings::values.theme != old_theme) { | 2660 | if (UISettings::values.theme != old_theme) { |
| 2607 | UpdateUITheme(); | 2661 | UpdateUITheme(); |
| 2608 | } | 2662 | } |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 490b6889f..11f152cbe 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -39,6 +39,11 @@ class GameListPlaceholder; | |||
| 39 | 39 | ||
| 40 | class QtSoftwareKeyboardDialog; | 40 | class QtSoftwareKeyboardDialog; |
| 41 | 41 | ||
| 42 | enum class StartGameType { | ||
| 43 | Normal, // Can use custom configuration | ||
| 44 | Global, // Only uses global configuration | ||
| 45 | }; | ||
| 46 | |||
| 42 | namespace Core::Frontend { | 47 | namespace Core::Frontend { |
| 43 | struct ControllerParameters; | 48 | struct ControllerParameters; |
| 44 | struct InlineAppearParameters; | 49 | struct InlineAppearParameters; |
| @@ -181,7 +186,8 @@ private: | |||
| 181 | void AllowOSSleep(); | 186 | void AllowOSSleep(); |
| 182 | 187 | ||
| 183 | bool LoadROM(const QString& filename, std::size_t program_index); | 188 | bool LoadROM(const QString& filename, std::size_t program_index); |
| 184 | void BootGame(const QString& filename, std::size_t program_index = 0); | 189 | void BootGame(const QString& filename, std::size_t program_index = 0, |
| 190 | StartGameType with_config = StartGameType::Normal); | ||
| 185 | void ShutdownGame(); | 191 | void ShutdownGame(); |
| 186 | 192 | ||
| 187 | void ShowTelemetryCallout(); | 193 | void ShowTelemetryCallout(); |
diff --git a/src/yuzu/uisettings.h b/src/yuzu/uisettings.h index 49122ec32..cdcb83f9f 100644 --- a/src/yuzu/uisettings.h +++ b/src/yuzu/uisettings.h | |||
| @@ -97,6 +97,7 @@ struct Values { | |||
| 97 | bool cache_game_list; | 97 | bool cache_game_list; |
| 98 | 98 | ||
| 99 | bool configuration_applied; | 99 | bool configuration_applied; |
| 100 | bool reset_to_defaults; | ||
| 100 | }; | 101 | }; |
| 101 | 102 | ||
| 102 | extern Values values; | 103 | extern Values values; |
diff --git a/src/yuzu/util/limitable_input_dialog.cpp b/src/yuzu/util/limitable_input_dialog.cpp index edd78e579..6fea41f95 100644 --- a/src/yuzu/util/limitable_input_dialog.cpp +++ b/src/yuzu/util/limitable_input_dialog.cpp | |||
| @@ -21,11 +21,13 @@ void LimitableInputDialog::CreateUI() { | |||
| 21 | 21 | ||
| 22 | text_label = new QLabel(this); | 22 | text_label = new QLabel(this); |
| 23 | text_entry = new QLineEdit(this); | 23 | text_entry = new QLineEdit(this); |
| 24 | text_label_invalid = new QLabel(this); | ||
| 24 | buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); | 25 | buttons = new QDialogButtonBox(QDialogButtonBox::Ok | QDialogButtonBox::Cancel, this); |
| 25 | 26 | ||
| 26 | auto* const layout = new QVBoxLayout; | 27 | auto* const layout = new QVBoxLayout; |
| 27 | layout->addWidget(text_label); | 28 | layout->addWidget(text_label); |
| 28 | layout->addWidget(text_entry); | 29 | layout->addWidget(text_entry); |
| 30 | layout->addWidget(text_label_invalid); | ||
| 29 | layout->addWidget(buttons); | 31 | layout->addWidget(buttons); |
| 30 | 32 | ||
| 31 | setLayout(layout); | 33 | setLayout(layout); |
| @@ -37,18 +39,36 @@ void LimitableInputDialog::ConnectEvents() { | |||
| 37 | } | 39 | } |
| 38 | 40 | ||
| 39 | QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, const QString& text, | 41 | QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, const QString& text, |
| 40 | int min_character_limit, int max_character_limit) { | 42 | int min_character_limit, int max_character_limit, |
| 43 | InputLimiter limit_type) { | ||
| 41 | Q_ASSERT(min_character_limit <= max_character_limit); | 44 | Q_ASSERT(min_character_limit <= max_character_limit); |
| 42 | 45 | ||
| 43 | LimitableInputDialog dialog{parent}; | 46 | LimitableInputDialog dialog{parent}; |
| 44 | dialog.setWindowTitle(title); | 47 | dialog.setWindowTitle(title); |
| 45 | dialog.text_label->setText(text); | 48 | dialog.text_label->setText(text); |
| 46 | dialog.text_entry->setMaxLength(max_character_limit); | 49 | dialog.text_entry->setMaxLength(max_character_limit); |
| 50 | dialog.text_label_invalid->show(); | ||
| 51 | |||
| 52 | switch (limit_type) { | ||
| 53 | case InputLimiter::Filesystem: | ||
| 54 | dialog.invalid_characters = QStringLiteral("<>:;\"/\\|,.!?*"); | ||
| 55 | break; | ||
| 56 | default: | ||
| 57 | dialog.invalid_characters.clear(); | ||
| 58 | dialog.text_label_invalid->hide(); | ||
| 59 | break; | ||
| 60 | } | ||
| 61 | dialog.text_label_invalid->setText( | ||
| 62 | tr("The text can't contain any of the following characters:\n%1") | ||
| 63 | .arg(dialog.invalid_characters)); | ||
| 47 | 64 | ||
| 48 | auto* const ok_button = dialog.buttons->button(QDialogButtonBox::Ok); | 65 | auto* const ok_button = dialog.buttons->button(QDialogButtonBox::Ok); |
| 49 | ok_button->setEnabled(false); | 66 | ok_button->setEnabled(false); |
| 50 | connect(dialog.text_entry, &QLineEdit::textEdited, [&](const QString& new_text) { | 67 | connect(dialog.text_entry, &QLineEdit::textEdited, [&] { |
| 51 | ok_button->setEnabled(new_text.length() >= min_character_limit); | 68 | if (!dialog.invalid_characters.isEmpty()) { |
| 69 | dialog.RemoveInvalidCharacters(); | ||
| 70 | } | ||
| 71 | ok_button->setEnabled(dialog.text_entry->text().length() >= min_character_limit); | ||
| 52 | }); | 72 | }); |
| 53 | 73 | ||
| 54 | if (dialog.exec() != QDialog::Accepted) { | 74 | if (dialog.exec() != QDialog::Accepted) { |
| @@ -57,3 +77,15 @@ QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, con | |||
| 57 | 77 | ||
| 58 | return dialog.text_entry->text(); | 78 | return dialog.text_entry->text(); |
| 59 | } | 79 | } |
| 80 | |||
| 81 | void LimitableInputDialog::RemoveInvalidCharacters() { | ||
| 82 | auto cpos = text_entry->cursorPosition(); | ||
| 83 | for (int i = 0; i < text_entry->text().length(); i++) { | ||
| 84 | if (invalid_characters.contains(text_entry->text().at(i))) { | ||
| 85 | text_entry->setText(text_entry->text().remove(i, 1)); | ||
| 86 | i--; | ||
| 87 | cpos--; | ||
| 88 | } | ||
| 89 | } | ||
| 90 | text_entry->setCursorPosition(cpos); | ||
| 91 | } | ||
diff --git a/src/yuzu/util/limitable_input_dialog.h b/src/yuzu/util/limitable_input_dialog.h index 164ad7301..a8e31098b 100644 --- a/src/yuzu/util/limitable_input_dialog.h +++ b/src/yuzu/util/limitable_input_dialog.h | |||
| @@ -18,14 +18,24 @@ public: | |||
| 18 | explicit LimitableInputDialog(QWidget* parent = nullptr); | 18 | explicit LimitableInputDialog(QWidget* parent = nullptr); |
| 19 | ~LimitableInputDialog() override; | 19 | ~LimitableInputDialog() override; |
| 20 | 20 | ||
| 21 | enum class InputLimiter { | ||
| 22 | None, | ||
| 23 | Filesystem, | ||
| 24 | }; | ||
| 25 | |||
| 21 | static QString GetText(QWidget* parent, const QString& title, const QString& text, | 26 | static QString GetText(QWidget* parent, const QString& title, const QString& text, |
| 22 | int min_character_limit, int max_character_limit); | 27 | int min_character_limit, int max_character_limit, |
| 28 | InputLimiter limit_type = InputLimiter::None); | ||
| 23 | 29 | ||
| 24 | private: | 30 | private: |
| 25 | void CreateUI(); | 31 | void CreateUI(); |
| 26 | void ConnectEvents(); | 32 | void ConnectEvents(); |
| 27 | 33 | ||
| 34 | void RemoveInvalidCharacters(); | ||
| 35 | QString invalid_characters; | ||
| 36 | |||
| 28 | QLabel* text_label; | 37 | QLabel* text_label; |
| 29 | QLineEdit* text_entry; | 38 | QLineEdit* text_entry; |
| 39 | QLabel* text_label_invalid; | ||
| 30 | QDialogButtonBox* buttons; | 40 | QDialogButtonBox* buttons; |
| 31 | }; | 41 | }; |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index a2ab69cdd..621b31571 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -317,6 +317,43 @@ void Config::ReadValues() { | |||
| 317 | sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); | 317 | sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_x", 15); |
| 318 | Settings::values.touchscreen.diameter_y = | 318 | Settings::values.touchscreen.diameter_y = |
| 319 | sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); | 319 | sdl2_config->GetInteger("ControlsGeneral", "touch_diameter_y", 15); |
| 320 | |||
| 321 | int num_touch_from_button_maps = | ||
| 322 | sdl2_config->GetInteger("ControlsGeneral", "touch_from_button_map", 0); | ||
| 323 | if (num_touch_from_button_maps > 0) { | ||
| 324 | for (int i = 0; i < num_touch_from_button_maps; ++i) { | ||
| 325 | Settings::TouchFromButtonMap map; | ||
| 326 | map.name = sdl2_config->Get("ControlsGeneral", | ||
| 327 | std::string("touch_from_button_maps_") + std::to_string(i) + | ||
| 328 | std::string("_name"), | ||
| 329 | "default"); | ||
| 330 | const int num_touch_maps = sdl2_config->GetInteger( | ||
| 331 | "ControlsGeneral", | ||
| 332 | std::string("touch_from_button_maps_") + std::to_string(i) + std::string("_count"), | ||
| 333 | 0); | ||
| 334 | map.buttons.reserve(num_touch_maps); | ||
| 335 | |||
| 336 | for (int j = 0; j < num_touch_maps; ++j) { | ||
| 337 | std::string touch_mapping = | ||
| 338 | sdl2_config->Get("ControlsGeneral", | ||
| 339 | std::string("touch_from_button_maps_") + std::to_string(i) + | ||
| 340 | std::string("_bind_") + std::to_string(j), | ||
| 341 | ""); | ||
| 342 | map.buttons.emplace_back(std::move(touch_mapping)); | ||
| 343 | } | ||
| 344 | |||
| 345 | Settings::values.touch_from_button_maps.emplace_back(std::move(map)); | ||
| 346 | } | ||
| 347 | } else { | ||
| 348 | Settings::values.touch_from_button_maps.emplace_back( | ||
| 349 | Settings::TouchFromButtonMap{"default", {}}); | ||
| 350 | num_touch_from_button_maps = 1; | ||
| 351 | } | ||
| 352 | Settings::values.use_touch_from_button = | ||
| 353 | sdl2_config->GetBoolean("ControlsGeneral", "use_touch_from_button", false); | ||
| 354 | Settings::values.touch_from_button_map_index = | ||
| 355 | std::clamp(Settings::values.touch_from_button_map_index, 0, num_touch_from_button_maps - 1); | ||
| 356 | |||
| 320 | Settings::values.udp_input_servers = | 357 | Settings::values.udp_input_servers = |
| 321 | sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_SRV); | 358 | sdl2_config->Get("Controls", "udp_input_address", InputCommon::CemuhookUDP::DEFAULT_SRV); |
| 322 | 359 | ||
| @@ -410,8 +447,10 @@ void Config::ReadValues() { | |||
| 410 | sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true)); | 447 | sdl2_config->GetBoolean("Renderer", "use_assembly_shaders", true)); |
| 411 | Settings::values.use_asynchronous_shaders.SetValue( | 448 | Settings::values.use_asynchronous_shaders.SetValue( |
| 412 | sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); | 449 | sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); |
| 413 | Settings::values.use_asynchronous_shaders.SetValue( | 450 | Settings::values.use_nvdec_emulation.SetValue( |
| 414 | sdl2_config->GetBoolean("Renderer", "use_asynchronous_shaders", false)); | 451 | sdl2_config->GetBoolean("Renderer", "use_nvdec_emulation", true)); |
| 452 | Settings::values.accelerate_astc.SetValue( | ||
| 453 | sdl2_config->GetBoolean("Renderer", "accelerate_astc", true)); | ||
| 415 | Settings::values.use_fast_gpu_time.SetValue( | 454 | Settings::values.use_fast_gpu_time.SetValue( |
| 416 | sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); | 455 | sdl2_config->GetBoolean("Renderer", "use_fast_gpu_time", true)); |
| 417 | 456 | ||
| @@ -440,6 +479,8 @@ void Config::ReadValues() { | |||
| 440 | Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); | 479 | Settings::values.program_args = sdl2_config->Get("Debugging", "program_args", ""); |
| 441 | Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); | 480 | Settings::values.dump_exefs = sdl2_config->GetBoolean("Debugging", "dump_exefs", false); |
| 442 | Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); | 481 | Settings::values.dump_nso = sdl2_config->GetBoolean("Debugging", "dump_nso", false); |
| 482 | Settings::values.enable_fs_access_log = | ||
| 483 | sdl2_config->GetBoolean("Debugging", "enable_fs_access_log", false); | ||
| 443 | Settings::values.reporting_services = | 484 | Settings::values.reporting_services = |
| 444 | sdl2_config->GetBoolean("Debugging", "reporting_services", false); | 485 | sdl2_config->GetBoolean("Debugging", "reporting_services", false); |
| 445 | Settings::values.quest_flag = sdl2_config->GetBoolean("Debugging", "quest_flag", false); | 486 | Settings::values.quest_flag = sdl2_config->GetBoolean("Debugging", "quest_flag", false); |
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 6b673b935..37d895ebd 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h | |||
| @@ -7,7 +7,7 @@ | |||
| 7 | namespace DefaultINI { | 7 | namespace DefaultINI { |
| 8 | 8 | ||
| 9 | const char* sdl2_config_file = R"( | 9 | const char* sdl2_config_file = R"( |
| 10 | [Controls] | 10 | [ControlsGeneral] |
| 11 | # The input devices and parameters for each Switch native input | 11 | # The input devices and parameters for each Switch native input |
| 12 | # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." | 12 | # It should be in the format of "engine:[engine_name],[param1]:[value1],[param2]:[value2]..." |
| 13 | # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values | 13 | # Escape characters $0 (for ':'), $1 (for ',') and $2 (for '$') can be used in values |
| @@ -86,6 +86,18 @@ motion_device= | |||
| 86 | # - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system | 86 | # - "min_x", "min_y", "max_x", "max_y": defines the udp device's touch screen coordinate system |
| 87 | touch_device= | 87 | touch_device= |
| 88 | 88 | ||
| 89 | # Whether to enable or disable touch input from button | ||
| 90 | # 0 (default): Disabled, 1: Enabled | ||
| 91 | use_touch_from_button= | ||
| 92 | |||
| 93 | # for mapping buttons to touch inputs. | ||
| 94 | #touch_from_button_map=1 | ||
| 95 | #touch_from_button_maps_0_name=default | ||
| 96 | #touch_from_button_maps_0_count=2 | ||
| 97 | #touch_from_button_maps_0_bind_0=foo | ||
| 98 | #touch_from_button_maps_0_bind_1=bar | ||
| 99 | # etc. | ||
| 100 | |||
| 89 | # Most desktop operating systems do not expose a way to poll the motion state of the controllers | 101 | # Most desktop operating systems do not expose a way to poll the motion state of the controllers |
| 90 | # so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly | 102 | # so as a way around it, cemuhook created a udp client/server protocol to broadcast the data directly |
| 91 | # from a controller device to the client program. Citra has a client that can connect and read | 103 | # from a controller device to the client program. Citra has a client that can connect and read |
| @@ -138,6 +150,10 @@ cpuopt_misc_ir = | |||
| 138 | # 0: Disabled, 1 (default): Enabled | 150 | # 0: Disabled, 1 (default): Enabled |
| 139 | cpuopt_reduce_misalign_checks = | 151 | cpuopt_reduce_misalign_checks = |
| 140 | 152 | ||
| 153 | # Enable Host MMU Emulation (faster guest memory access) | ||
| 154 | # 0: Disabled, 1 (default): Enabled | ||
| 155 | cpuopt_fastmem = | ||
| 156 | |||
| 141 | [Renderer] | 157 | [Renderer] |
| 142 | # Which backend API to use. | 158 | # Which backend API to use. |
| 143 | # 0 (default): OpenGL, 1: Vulkan | 159 | # 0 (default): OpenGL, 1: Vulkan |
| @@ -178,6 +194,14 @@ use_assembly_shaders = | |||
| 178 | # 0 (default): Off, 1: On | 194 | # 0 (default): Off, 1: On |
| 179 | use_asynchronous_shaders = | 195 | use_asynchronous_shaders = |
| 180 | 196 | ||
| 197 | # Enable NVDEC emulation. | ||
| 198 | # 0: Off, 1 (default): On | ||
| 199 | use_nvdec_emulation = | ||
| 200 | |||
| 201 | # Accelerate ASTC texture decoding. | ||
| 202 | # 0: Off, 1 (default): On | ||
| 203 | accelerate_astc = | ||
| 204 | |||
| 181 | # Turns on the frame limiter, which will limit frames output to the target game speed | 205 | # Turns on the frame limiter, which will limit frames output to the target game speed |
| 182 | # 0: Off, 1: On (default) | 206 | # 0: Off, 1: On (default) |
| 183 | use_frame_limit = | 207 | use_frame_limit = |
| @@ -325,6 +349,8 @@ record_frame_times = | |||
| 325 | dump_exefs=false | 349 | dump_exefs=false |
| 326 | # Determines whether or not yuzu will dump all NSOs it attempts to load while loading them | 350 | # Determines whether or not yuzu will dump all NSOs it attempts to load while loading them |
| 327 | dump_nso=false | 351 | dump_nso=false |
| 352 | # Determines whether or not yuzu will save the filesystem access log. | ||
| 353 | enable_fs_access_log=false | ||
| 328 | # Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode | 354 | # Determines whether or not yuzu will report to the game that the emulated console is in Kiosk Mode |
| 329 | # false: Retail/Normal Mode (default), true: Kiosk Mode | 355 | # false: Retail/Normal Mode (default), true: Kiosk Mode |
| 330 | quest_flag = | 356 | quest_flag = |
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp index 3c49a300b..837a44be7 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp | |||
| @@ -32,17 +32,17 @@ | |||
| 32 | 32 | ||
| 33 | class SDLGLContext : public Core::Frontend::GraphicsContext { | 33 | class SDLGLContext : public Core::Frontend::GraphicsContext { |
| 34 | public: | 34 | public: |
| 35 | explicit SDLGLContext() { | 35 | explicit SDLGLContext(SDL_Window* window_) : window{window_} { |
| 36 | // create a hidden window to make the shared context against | ||
| 37 | window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, | ||
| 38 | SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); | ||
| 39 | context = SDL_GL_CreateContext(window); | 36 | context = SDL_GL_CreateContext(window); |
| 40 | } | 37 | } |
| 41 | 38 | ||
| 42 | ~SDLGLContext() { | 39 | ~SDLGLContext() { |
| 43 | DoneCurrent(); | 40 | DoneCurrent(); |
| 44 | SDL_GL_DeleteContext(context); | 41 | SDL_GL_DeleteContext(context); |
| 45 | SDL_DestroyWindow(window); | 42 | } |
| 43 | |||
| 44 | void SwapBuffers() override { | ||
| 45 | SDL_GL_SwapWindow(window); | ||
| 46 | } | 46 | } |
| 47 | 47 | ||
| 48 | void MakeCurrent() override { | 48 | void MakeCurrent() override { |
| @@ -114,9 +114,6 @@ EmuWindow_SDL2_GL::EmuWindow_SDL2_GL(InputCommon::InputSubsystem* input_subsyste | |||
| 114 | exit(1); | 114 | exit(1); |
| 115 | } | 115 | } |
| 116 | 116 | ||
| 117 | dummy_window = SDL_CreateWindow(NULL, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, 0, 0, | ||
| 118 | SDL_WINDOW_HIDDEN | SDL_WINDOW_OPENGL); | ||
| 119 | |||
| 120 | SetWindowIcon(); | 117 | SetWindowIcon(); |
| 121 | 118 | ||
| 122 | if (fullscreen) { | 119 | if (fullscreen) { |
| @@ -159,5 +156,5 @@ EmuWindow_SDL2_GL::~EmuWindow_SDL2_GL() { | |||
| 159 | } | 156 | } |
| 160 | 157 | ||
| 161 | std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { | 158 | std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { |
| 162 | return std::make_unique<SDLGLContext>(); | 159 | return std::make_unique<SDLGLContext>(render_window); |
| 163 | } | 160 | } |
diff --git a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h index dba5c293c..9e694d985 100644 --- a/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h +++ b/src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h | |||
| @@ -20,9 +20,6 @@ public: | |||
| 20 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; | 20 | std::unique_ptr<Core::Frontend::GraphicsContext> CreateSharedContext() const override; |
| 21 | 21 | ||
| 22 | private: | 22 | private: |
| 23 | /// Fake hidden window for the core context | ||
| 24 | SDL_Window* dummy_window{}; | ||
| 25 | |||
| 26 | /// Whether the GPU and driver supports the OpenGL extension required | 23 | /// Whether the GPU and driver supports the OpenGL extension required |
| 27 | bool SupportsRequiredGLExtensions(); | 24 | bool SupportsRequiredGLExtensions(); |
| 28 | 25 | ||