summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--.ci/scripts/common/post-upload.sh6
-rw-r--r--.ci/scripts/windows/upload.sh4
-rw-r--r--CMakeLists.txt24
-rw-r--r--externals/CMakeLists.txt5
m---------externals/SDL0
m---------externals/dynarmic0
-rw-r--r--externals/libusb/CMakeLists.txt74
-rw-r--r--src/common/CMakeLists.txt20
-rw-r--r--src/common/fs/file.cpp10
-rw-r--r--src/common/fs/file.h3
-rw-r--r--src/common/fs/fs.cpp18
-rw-r--r--src/common/fs/path_util.h2
-rw-r--r--src/common/host_memory.cpp538
-rw-r--r--src/common/host_memory.h70
-rw-r--r--src/common/logging/backend.cpp147
-rw-r--r--src/common/logging/backend.h43
-rw-r--r--src/common/logging/filter.cpp132
-rw-r--r--src/common/logging/filter.h12
-rw-r--r--src/common/logging/log.h120
-rw-r--r--src/common/logging/text_formatter.cpp2
-rw-r--r--src/common/logging/types.h142
-rw-r--r--src/common/page_table.h2
-rw-r--r--src/common/settings.cpp10
-rw-r--r--src/common/settings.h6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_32.cpp6
-rw-r--r--src/core/arm/dynarmic/arm_dynarmic_64.cpp12
-rw-r--r--src/core/device_memory.cpp2
-rw-r--r--src/core/device_memory.h17
-rw-r--r--src/core/file_sys/program_metadata.cpp4
-rw-r--r--src/core/file_sys/vfs.cpp1
-rw-r--r--src/core/file_sys/vfs_libzip.cpp1
-rw-r--r--src/core/frontend/input.h15
-rw-r--r--src/core/hle/kernel/hle_ipc.cpp30
-rw-r--r--src/core/hle/kernel/hle_ipc.h23
-rw-r--r--src/core/hle/kernel/k_auto_object.h11
-rw-r--r--src/core/hle/kernel/k_auto_object_container.cpp4
-rw-r--r--src/core/hle/kernel/k_auto_object_container.h5
-rw-r--r--src/core/hle/kernel/k_client_port.cpp14
-rw-r--r--src/core/hle/kernel/k_client_port.h4
-rw-r--r--src/core/hle/kernel/k_client_session.h4
-rw-r--r--src/core/hle/kernel/k_light_condition_variable.h43
-rw-r--r--src/core/hle/kernel/k_light_lock.cpp19
-rw-r--r--src/core/hle/kernel/k_process.cpp16
-rw-r--r--src/core/hle/kernel/k_readable_event.h4
-rw-r--r--src/core/hle/kernel/k_resource_limit.cpp2
-rw-r--r--src/core/hle/kernel/k_server_port.cpp4
-rw-r--r--src/core/hle/kernel/k_server_port.h2
-rw-r--r--src/core/hle/kernel/k_server_session.cpp63
-rw-r--r--src/core/hle/kernel/k_server_session.h28
-rw-r--r--src/core/hle/kernel/k_session.cpp5
-rw-r--r--src/core/hle/kernel/k_session.h5
-rw-r--r--src/core/hle/kernel/k_writable_event.cpp4
-rw-r--r--src/core/hle/kernel/kernel.cpp18
-rw-r--r--src/core/hle/kernel/svc.cpp4
-rw-r--r--src/core/hle/result.h40
-rw-r--r--src/core/hle/service/bcat/backend/boxcat.cpp1
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.cpp15
-rw-r--r--src/core/hle/service/filesystem/fsp_srv.h12
-rw-r--r--src/core/hle/service/hid/hid.cpp23
-rw-r--r--src/core/hle/service/hid/hid.h1
-rw-r--r--src/core/hle/service/lm/lm.cpp47
-rw-r--r--src/core/hle/service/ns/pl_u.cpp2
-rw-r--r--src/core/hle/service/service.cpp6
-rw-r--r--src/core/hle/service/service.h13
-rw-r--r--src/core/hle/service/sm/controller.cpp37
-rw-r--r--src/core/hle/service/sm/sm.cpp32
-rw-r--r--src/core/memory.cpp18
-rw-r--r--src/core/reporter.cpp36
-rw-r--r--src/core/reporter.h9
-rw-r--r--src/core/telemetry_session.cpp1
-rw-r--r--src/input_common/CMakeLists.txt3
-rwxr-xr-xsrc/input_common/analog_from_button.cpp195
-rw-r--r--src/input_common/keyboard.cpp1
-rw-r--r--src/tests/CMakeLists.txt1
-rw-r--r--src/tests/common/host_memory.cpp183
-rw-r--r--src/video_core/CMakeLists.txt1
-rw-r--r--src/video_core/buffer_cache/buffer_base.h3
-rw-r--r--src/video_core/buffer_cache/buffer_cache.h11
-rw-r--r--src/video_core/engines/maxwell_3d.cpp8
-rw-r--r--src/video_core/gpu_thread.cpp24
-rw-r--r--src/video_core/host_shaders/astc_decoder.comp2
-rw-r--r--src/video_core/memory_manager.cpp3
-rw-r--r--src/video_core/rasterizer_accelerated.cpp56
-rw-r--r--src/video_core/rasterizer_interface.h3
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.cpp4
-rw-r--r--src/video_core/renderer_opengl/gl_rasterizer.h1
-rw-r--r--src/video_core/renderer_opengl/gl_texture_cache.cpp6
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.cpp4
-rw-r--r--src/video_core/renderer_vulkan/vk_rasterizer.h1
-rw-r--r--src/video_core/renderer_vulkan/vk_texture_cache.cpp7
-rw-r--r--src/video_core/texture_cache/util.cpp26
-rw-r--r--src/video_core/textures/astc.cpp1577
-rw-r--r--src/video_core/textures/astc.h3
-rw-r--r--src/video_core/textures/decoders.cpp8
-rw-r--r--src/yuzu/configuration/config.cpp13
-rw-r--r--src/yuzu/configuration/configure_cpu.cpp9
-rw-r--r--src/yuzu/configuration/configure_cpu.h1
-rw-r--r--src/yuzu/configuration/configure_cpu.ui12
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.cpp3
-rw-r--r--src/yuzu/configuration/configure_cpu_debug.ui16
-rw-r--r--src/yuzu/configuration/configure_debug.cpp11
-rw-r--r--src/yuzu/configuration/configure_debug.ui9
-rw-r--r--src/yuzu/configuration/configure_dialog.cpp2
-rw-r--r--src/yuzu/configuration/configure_general.cpp30
-rw-r--r--src/yuzu/configuration/configure_general.h7
-rw-r--r--src/yuzu/configuration/configure_general.ui41
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp7
-rw-r--r--src/yuzu/configuration/configure_graphics.h1
-rw-r--r--src/yuzu/configuration/configure_graphics.ui7
-rw-r--r--src/yuzu/configuration/configure_input_player.cpp3
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.cpp19
-rw-r--r--src/yuzu/configuration/configure_input_player_widget.h2
-rw-r--r--src/yuzu/configuration/configure_ui.cpp40
-rw-r--r--src/yuzu/debugger/controller.cpp6
-rw-r--r--src/yuzu/game_list.cpp16
-rw-r--r--src/yuzu/game_list.h2
-rw-r--r--src/yuzu/main.cpp62
-rw-r--r--src/yuzu/main.h8
-rw-r--r--src/yuzu/uisettings.h1
-rw-r--r--src/yuzu/util/limitable_input_dialog.cpp38
-rw-r--r--src/yuzu/util/limitable_input_dialog.h12
-rw-r--r--src/yuzu_cmd/config.cpp45
-rw-r--r--src/yuzu_cmd/default_ini.h28
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.cpp15
-rw-r--r--src/yuzu_cmd/emu_window/emu_window_sdl2_gl.h3
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
10tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$DIR_NAME" 10tar $COMPRESSION_FLAGS "$ARCHIVE_NAME" "$DIR_NAME"
11 11
12mv "$DIR_NAME" $RELEASE_NAME
13mv "${REV_NAME}-source.tar.xz" $RELEASE_NAME
14
157z 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
18mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/" 13mv "$ARCHIVE_NAME" "${ARTIFACTS_DIR}/"
19mv "$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
5REV_NAME="yuzu-windows-mingw-${GITDATE}-${GITREV}" 5REV_NAME="yuzu-windows-mingw-${GITDATE}-${GITREV}"
6ARCHIVE_NAME="${REV_NAME}.tar.gz" 6ARCHIVE_NAME="${REV_NAME}.tar.xz"
7COMPRESSION_FLAGS="-czvf" 7COMPRESSION_FLAGS="-cJvf"
8 8
9if [ "${RELEASE_NAME}" = "mainline" ]; then 9if [ "${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
24option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF) 24option(YUZU_USE_BUNDLED_BOOST "Download bundled Boost" OFF)
25 25
26option(YUZU_USE_BUNDLED_LIBUSB "Compile bundled libusb" OFF)
27
26CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF) 28CMAKE_DEPENDENT_OPTION(YUZU_USE_BUNDLED_FFMPEG "Download/Build bundled FFmpeg" ON "WIN32" OFF)
27 29
28option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF) 30option(YUZU_USE_QT_WEB_ENGINE "Use QtWebEngine for web applet implementation" OFF)
@@ -420,14 +422,22 @@ elseif (TARGET Boost::boost)
420endif() 422endif()
421 423
422# Ensure libusb is properly configured (based on dolphin libusb include) 424# Ensure libusb is properly configured (based on dolphin libusb include)
423if(NOT APPLE) 425if(NOT APPLE AND NOT YUZU_USE_BUNDLED_LIBUSB)
424 include(FindPkgConfig) 426 include(FindPkgConfig)
425 find_package(LibUSB) 427 if (PKG_CONFIG_FOUND)
426endif() 428 pkg_check_modules(LIBUSB QUIET libusb-1.0>=1.0.24)
427if (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()
431endif() 441endif()
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)
45add_library(unicorn-headers INTERFACE) 45add_library(unicorn-headers INTERFACE)
46target_include_directories(unicorn-headers INTERFACE ./unicorn/include) 46target_include_directories(unicorn-headers INTERFACE ./unicorn/include)
47 47
48# libusb
49if (NOT LIBUSB_FOUND OR YUZU_USE_BUNDLED_LIBUSB)
50 add_subdirectory(libusb)
51endif()
52
48# SDL2 53# SDL2
49if (NOT SDL2_FOUND AND ENABLE_SDL2) 54if (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 @@
1if (MINGW) 1if (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)
83else() # 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()
103else() # 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)
239endif() # MINGW 259endif() # 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
22add_custom_command(OUTPUT scm_rev.cpp 22add_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
97add_library(common STATIC 98add_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
184size_t AppendStringToFile(const std::filesystem::path& path, FileType type, 184size_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
211template <typename Path> 211template <typename Path>
212[[nodiscard]] void SetYuzuPath(YuzuPath yuzu_path, const Path& new_path) { 212void 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
28namespace Common {
29
30constexpr size_t PageAlignment = 0x1000;
31constexpr 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
49using 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
56using 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
62using 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
68using PFN_UnmapViewOfFile2 = BOOL(WINAPI*)(_In_ HANDLE Process, _In_ PVOID BaseAddress,
69 _In_ ULONG UnmapFlags);
70
71template <typename T>
72static 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
79class HostMemory::Impl {
80public:
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
187private:
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
348class HostMemory::Impl {
349public:
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
430private:
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
454class HostMemory::Impl {
455public:
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
474HostMemory::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
500HostMemory::~HostMemory() = default;
501
502HostMemory::HostMemory(HostMemory&&) noexcept = default;
503
504HostMemory& HostMemory::operator=(HostMemory&&) noexcept = default;
505
506void 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
518void 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
528void 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
11namespace 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 */
17class HostMemory {
18public:
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
55private:
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
144ConsoleBackend::~ConsoleBackend() = default;
145
143void ConsoleBackend::Write(const Entry& entry) { 146void ConsoleBackend::Write(const Entry& entry) {
144 PrintMessage(entry); 147 PrintMessage(entry);
145} 148}
146 149
150ColorConsoleBackend::~ColorConsoleBackend() = default;
151
147void ColorConsoleBackend::Write(const Entry& entry) { 152void 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
169FileBackend::~FileBackend() = default;
170
163void FileBackend::Write(const Entry& entry) { 171void 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
193DebuggerBackend::~DebuggerBackend() = default;
194
185void DebuggerBackend::Write(const Entry& entry) { 195void 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...
285const 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
302const 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
320void SetGlobalFilter(const Filter& filter) { 201void 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
14namespace Common::FS {
15class IOFile;
16}
17
15namespace Common::Log { 18namespace Common::Log {
16 19
17class Filter; 20class 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 */
23struct 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 */
38class Backend { 26class Backend {
39public: 27public:
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 */
54class ConsoleBackend : public Backend { 43class ConsoleBackend : public Backend {
55public: 44public:
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 */
68class ColorConsoleBackend : public Backend { 59class ColorConsoleBackend : public Backend {
69public: 60public:
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:
83class FileBackend : public Backend { 76class FileBackend : public Backend {
84public: 77public:
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
97private: 91private:
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 */
105class DebuggerBackend : public Backend { 99class DebuggerBackend : public Backend {
106public: 100public:
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);
120Backend* GetBackend(std::string_view backend_name); 116Backend* 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 */
126const char* GetLogClassName(Class log_class);
127
128/**
129 * Returns the name of the passed log level as a C-string.
130 */
131const 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
23template <typename It> 22template <typename It>
24Class GetClassByName(const It begin, const It end) { 23Class 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...
158const 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
175const 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
65Filter::Filter(Level default_level) { 193Filter::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 @@
12namespace Common::Log { 13namespace 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 */
19const char* GetLogClassName(Class log_class);
20
21/**
22 * Returns the name of the passed log level as a C-string.
23 */
24const 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
10namespace Common::Log { 10namespace 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.
22enum 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
36typedef 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 */
44enum 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
140void FmtLogMessageImpl(Class log_class, Level log_level, const char* filename, 22void 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
9namespace Common::Log {
10
11/// Specifies the severity or level of detail of the log message.
12enum 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 */
32enum 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 */
131struct 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
94bool IsFastmemEnabled() {
95 if (values.cpu_accuracy.GetValue() == CPUAccuracy::DebugMode) {
96 return values.cpuopt_fastmem;
97 }
98 return true;
99}
100
93float Volume() { 101float 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);
249bool IsGPULevelExtreme(); 253bool IsGPULevelExtreme();
250bool IsGPULevelHigh(); 254bool IsGPULevelHigh();
251 255
256bool IsFastmemEnabled();
257
252float Volume(); 258float Volume();
253 259
254std::string GetTimeZoneString(); 260std::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
7namespace Core { 7namespace Core {
8 8
9DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size} {} 9DeviceMemory::DeviceMemory() : buffer{DramMemoryMap::Size, 1ULL << 39} {}
10DeviceMemory::~DeviceMemory() = default; 10DeviceMemory::~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
10namespace Core { 10namespace Core {
11 11
@@ -21,27 +21,30 @@ enum : u64 {
21}; 21};
22}; // namespace DramMemoryMap 22}; // namespace DramMemoryMap
23 23
24class DeviceMemory : NonCopyable { 24class DeviceMemory {
25public: 25public:
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
43private: 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};
30template <typename StatusType>
31struct 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.).
32template <typename StatusType> 36template <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
66private:
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
31namespace Kernel { 31namespace Kernel {
32 32
33SessionRequestHandler::SessionRequestHandler() = default; 33SessionRequestHandler::SessionRequestHandler(KernelCore& kernel_, const char* service_name_)
34 : kernel{kernel_}, service_thread{kernel.CreateServiceThread(service_name_)} {}
34 35
35SessionRequestHandler::~SessionRequestHandler() = default; 36SessionRequestHandler::~SessionRequestHandler() {
37 kernel.ReleaseServiceThread(service_thread);
38}
39
40SessionRequestManager::SessionRequestManager(KernelCore& kernel_) : kernel{kernel_} {}
41
42SessionRequestManager::~SessionRequestManager() = default;
43
44bool 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
37void SessionRequestHandler::ClientConnected(KServerSession* session) { 59void SessionRequestHandler::ClientConnected(KServerSession* session) {
38 session->SetSessionHandler(shared_from_this()); 60 session->ClientConnected(shared_from_this());
39} 61}
40 62
41void SessionRequestHandler::ClientDisconnected(KServerSession* session) { 63void SessionRequestHandler::ClientDisconnected(KServerSession* session) {
42 session->SetSessionHandler(nullptr); 64 session->ClientDisconnected();
43} 65}
44 66
45HLERequestContext::HLERequestContext(KernelCore& kernel_, Core::Memory::Memory& memory_, 67HLERequestContext::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;
46class KReadableEvent; 46class KReadableEvent;
47class KSession; 47class KSession;
48class KWritableEvent; 48class KWritableEvent;
49class ServiceThread;
49 50
50enum class ThreadWakeupReason; 51enum class ThreadWakeupReason;
51 52
@@ -56,7 +57,7 @@ enum class ThreadWakeupReason;
56 */ 57 */
57class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> { 58class SessionRequestHandler : public std::enable_shared_from_this<SessionRequestHandler> {
58public: 59public:
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
92protected:
93 KernelCore& kernel;
94 std::weak_ptr<ServiceThread> service_thread;
86}; 95};
87 96
88using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>; 97using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>;
@@ -94,7 +103,8 @@ using SessionRequestHandlerPtr = std::shared_ptr<SessionRequestHandler>;
94 */ 103 */
95class SessionRequestManager final { 104class SessionRequestManager final {
96public: 105public:
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
145private: 161private:
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
166private:
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
16namespace Kernel { 17namespace Kernel {
@@ -175,7 +176,7 @@ private:
175 176
176class KAutoObjectWithListContainer; 177class KAutoObjectWithListContainer;
177 178
178class KAutoObjectWithList : public KAutoObject { 179class KAutoObjectWithList : public KAutoObject, public boost::intrusive::set_base_hook<> {
179public: 180public:
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
195public: 200public:
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
204private: 209private:
205 friend class KAutoObjectWithListContainer; 210 friend class KAutoObjectWithListContainer;
206
207 Common::IntrusiveRedBlackTreeNode list_node;
208}; 211};
209 212
210template <typename T> 213template <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 {
9void KAutoObjectWithListContainer::Register(KAutoObjectWithList* obj) { 9void 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
15void KAutoObjectWithListContainer::Unregister(KAutoObjectWithList* obj) { 15void 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
21size_t KAutoObjectWithListContainer::GetOwnedCount(KProcess* owner) { 21size_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
25public: 27public:
26 using ListType = Common::IntrusiveRedBlackTreeMemberTraits< 28 using ListType = boost::intrusive::rbtree<KAutoObjectWithList>;
27 &KAutoObjectWithList::list_node>::TreeType<KAutoObjectWithList>;
28 29
29public: 30public:
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 {
16KClientPort::KClientPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} 16KClientPort::KClientPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
17KClientPort::~KClientPort() = default; 17KClientPort::~KClientPort() = default;
18 18
19void KClientPort::Initialize(KPort* parent_, s32 max_sessions_, std::string&& name_) { 19void 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
28void KClientPort::OnSessionFinalized() { 28void 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
59ResultCode KClientPort::CreateSession(KClientSession** out) { 62ResultCode 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 {
16class KClientSession; 16class KClientSession;
17class KernelCore; 17class KernelCore;
18class KPort; 18class KPort;
19class SessionRequestManager;
19 20
20class KClientPort final : public KSynchronizationObject { 21class 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
57private: 59private:
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
19class KLightConditionVariable { 19class KLightConditionVariable {
20public: 20public:
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
37private: 36private:
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 {
17KServerPort::KServerPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {} 17KServerPort::KServerPort(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
18KServerPort::~KServerPort() = default; 18KServerPort::~KServerPort() = default;
19 19
20void KServerPort::Initialize(KPort* parent_, std::string&& name_) { 20void 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
24namespace Kernel { 27namespace Kernel {
25 28
26KServerSession::KServerSession(KernelCore& kernel_) 29KServerSession::KServerSession(KernelCore& kernel_) : KSynchronizationObject{kernel_} {}
27 : KSynchronizationObject{kernel_}, manager{std::make_shared<SessionRequestManager>()} {}
28 30
29KServerSession::~KServerSession() { 31KServerSession::~KServerSession() {}
30 kernel.ReleaseServiceThread(service_thread);
31}
32 32
33void KServerSession::Initialize(KSession* parent_, std::string&& name_) { 33void 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
40void KServerSession::Destroy() { 46void 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
125ResultCode KServerSession::CompleteSyncRequest(HLERequestContext& context) { 147ResultCode 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;
32class KernelCore; 32class KernelCore;
33class KSession; 33class KSession;
34class SessionRequestHandler; 34class SessionRequestHandler;
35class SessionRequestManager;
35class KThread; 36class KThread;
36 37
37class KServerSession final : public KSynchronizationObject, 38class 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
117private: 108private:
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_} {}
16KSession::~KSession() = default; 16KSession::~KSession() = default;
17 17
18void KSession::Initialize(KClientPort* port_, const std::string& name_) { 18void 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
14namespace Kernel { 14namespace Kernel {
15 15
16class SessionRequestManager;
17
16class KSession final : public KAutoObjectWithSlabHeapAndContainer<KSession, KAutoObjectWithList> { 18class 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
14KWritableEvent::~KWritableEvent() = default; 14KWritableEvent::~KWritableEvent() = default;
15 15
16void KWritableEvent::Initialize(KEvent* parent_, std::string&& name_) { 16void 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
987std::weak_ptr<Kernel::ServiceThread> KernelCore::CreateServiceThread(const std::string& name) { 980std::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
994void KernelCore::ReleaseServiceThread(std::weak_ptr<Kernel::ServiceThread> service_thread) { 986void 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
1002Init::KSlabResourceCounts& KernelCore::SlabResourceCounts() { 992Init::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
136constexpr 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
140constexpr 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 */
322template <typename T, typename... Args> 322template <typename T, typename... Args>
323ResultVal<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 */
331template <typename Arg> 331template <typename Arg>
332ResultVal<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
790FSP_SRV::~FSP_SRV() = default; 795FSP_SRV::~FSP_SRV() = default;
@@ -1041,9 +1046,9 @@ void FSP_SRV::DisableAutoSaveDataCreation(Kernel::HLERequestContext& ctx) {
1041 1046
1042void FSP_SRV::SetGlobalAccessLogMode(Kernel::HLERequestContext& ctx) { 1047void 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
1060void FSP_SRV::OutputAccessLogToSdCard(Kernel::HLERequestContext& ctx) { 1065void 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
27enum class LogMode : u32 { 27enum class AccessLogMode : u32 {
28 Off, 28 None,
29 Log, 29 Log,
30 RedirectToSdCard, 30 SdCard,
31 LogToSdCard = Log | RedirectToSdCard,
32}; 31};
33 32
34class FSP_SRV final : public ServiceFramework<FSP_SRV> { 33class 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
713void 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
713void Hid::ActivateGesture(Kernel::HLERequestContext& ctx) { 734void 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
53namespace Service::LM { 53namespace Service::LM {
54namespace {
55std::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
55enum class LogDestination : u32 { 73enum 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
94ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* service_name_, 94ServiceFrameworkBase::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
99ServiceFrameworkBase::~ServiceFrameworkBase() { 99ServiceFrameworkBase::~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
114Kernel::KClientPort& ServiceFrameworkBase::CreatePort(Kernel::KernelCore& kernel) { 114Kernel::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 {
23class HLERequestContext; 23class HLERequestContext;
24class KClientPort; 24class KClientPort;
25class KServerSession; 25class KServerSession;
26class ServiceThread;
26} // namespace Kernel 27} // namespace Kernel
27 28
28namespace Service { 29namespace Service {
@@ -39,9 +40,11 @@ namespace SM {
39class ServiceManager; 40class ServiceManager;
40} 41}
41 42
42static 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. 44static constexpr u32 ServerSessionCountMax = 0x40;
44static const u32 DefaultMaxSessions = 10; 45static_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
30void Controller::CloneCurrentObject(Kernel::HLERequestContext& ctx) { 30void 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
69void Controller::CloneCurrentObjectEx(Kernel::HLERequestContext& ctx) { 52void 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
52ResultVal<Kernel::KServerPort*> ServiceManager::RegisterService(std::string name, 52ResultVal<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
181void SM::RegisterService(Kernel::HLERequestContext& ctx) { 173void 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
196namespace Core { 196namespace Core {
197 197
198Reporter::Reporter(System& system_) : system(system_) {} 198Reporter::Reporter(System& system_) : system(system_) {
199 ClearFSAccessLog();
200}
199 201
200Reporter::~Reporter() = default; 202Reporter::~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
365void Reporter::SaveFilesystemAccessReport(Service::FileSystem::LogMode log_mode, 367void 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
383void Reporter::SaveUserReport() const { 375void 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
387void 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
395bool Reporter::IsReportingEnabled() const { 399bool 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 {
16class HLERequestContext; 16class HLERequestContext;
17} // namespace Kernel 17} // namespace Kernel
18 18
19namespace Service::FileSystem {
20enum class LogMode : u32;
21}
22
23namespace Service::LM { 19namespace Service::LM {
24struct LogMessage; 20struct 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
79private: 74private:
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)
72endif() 72endif()
73 73
74target_include_directories(input_common SYSTEM PRIVATE ${LIBUSB_INCLUDE_DIR}) 74target_link_libraries(input_common PRIVATE usb)
75target_link_libraries(input_common PRIVATE ${LIBUSB_LIBRARIES})
76 75
77create_target_directory_groups(input_common) 76create_target_directory_groups(input_common)
78target_link_libraries(input_common PUBLIC core PRIVATE common Boost::boost) 77target_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
174std::unique_ptr<Input::AnalogDevice> AnalogFromButton::Create(const Common::ParamPackage& params) { 223std::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
9using Common::HostMemory;
10
11static constexpr size_t VIRTUAL_SIZE = 1ULL << 39;
12static constexpr size_t BACKING_SIZE = 4ULL * 1024 * 1024 * 1024;
13
14TEST_CASE("HostMemory: Initialize and deinitialize", "[common]") {
15 { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
16 { HostMemory mem(BACKING_SIZE, VIRTUAL_SIZE); }
17}
18
19TEST_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
28TEST_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
39TEST_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
50TEST_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
67TEST_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
76TEST_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
83TEST_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
90TEST_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
97TEST_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
105TEST_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
113TEST_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
122TEST_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
130TEST_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
142TEST_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
156TEST_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
170TEST_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>
419void BufferCache<P>::BindGraphicsUniformBuffer(size_t stage, u32 index, GPUVAddr gpu_addr, 421void 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
434template <class P> 432template <class P>
433void BufferCache<P>::DisableGraphicsUniformBuffer(size_t stage, u32 index) {
434 uniform_buffers[stage][index] = NULL_BINDING;
435}
436
437template <class P>
435void BufferCache<P>::UpdateGraphicsBuffers(bool is_indexed) { 438void 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
123void ThreadManager::InvalidateRegion(VAddr addr, u64 size) { 111void 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
165std::optional<VAddr> MemoryManager::GpuToCpuAddress(GPUVAddr gpu_addr) const { 165std::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
11namespace VideoCore { 13namespace VideoCore {
12 14
13RasterizerAccelerated::RasterizerAccelerated(Core::Memory::Memory& cpu_memory_) 15using namespace Core::Memory;
14 : cpu_memory{cpu_memory_} {} 16
17RasterizerAccelerated::RasterizerAccelerated(Memory& cpu_memory_) : cpu_memory{cpu_memory_} {}
15 18
16RasterizerAccelerated::~RasterizerAccelerated() = default; 19RasterizerAccelerated::~RasterizerAccelerated() = default;
17 20
18void RasterizerAccelerated::UpdatePagesCachedCount(VAddr addr, u64 size, int delta) { 21void 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
529void RasterizerOpenGL::DisableGraphicsUniformBuffer(size_t stage, u32 index) {
530 buffer_cache.DisableGraphicsUniformBuffer(stage, index);
531}
532
529void RasterizerOpenGL::FlushAll() {} 533void RasterizerOpenGL::FlushAll() {}
530 534
531void RasterizerOpenGL::FlushRegion(VAddr addr, u64 size) { 535void 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
479void Vulkan::RasterizerVulkan::DisableGraphicsUniformBuffer(size_t stage, u32 index) {
480 buffer_cache.DisableGraphicsUniformBuffer(stage, index);
481}
482
479void RasterizerVulkan::FlushAll() {} 483void RasterizerVulkan::FlushAll() {}
480 484
481void RasterizerVulkan::FlushRegion(VAddr addr, u64 size) { 485void 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
52namespace VideoCommon { 53namespace VideoCommon {
@@ -647,6 +648,9 @@ u32 CalculateLayerSize(const ImageInfo& info) noexcept {
647} 648}
648 649
649LevelArray CalculateMipLevelOffsets(const ImageInfo& info) noexcept { 650LevelArray 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
29class InputBitStream {
30public:
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
69private:
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
76class OutputBitStream {
77public:
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
97private:
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
125template <typename IntType>
126class Bits {
127public:
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
150private:
151 const IntType& m_Bits;
152};
153
154namespace Tegra::Texture::ASTC {
155using 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
161static 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
219static 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.
269static 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
297struct 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
325static 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
524static 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
547static 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}
554static constexpr u32 ReplicateByteTo16(std::size_t value) {
555 return REPLICATE_BYTE_TO_16_TABLE[value];
556}
557
558static constexpr auto REPLICATE_BIT_TO_7_TABLE = MakeReplicateTable<u32, 1, 7>();
559static constexpr u32 ReplicateBitTo7(std::size_t value) {
560 return REPLICATE_BIT_TO_7_TABLE[value];
561}
562
563static constexpr auto REPLICATE_BIT_TO_9_TABLE = MakeReplicateTable<u32, 1, 9>();
564static constexpr u32 ReplicateBitTo9(std::size_t value) {
565 return REPLICATE_BIT_TO_9_TABLE[value];
566}
567
568static constexpr auto REPLICATE_1_BIT_TO_8_TABLE = MakeReplicateTable<u32, 1, 8>();
569static constexpr auto REPLICATE_2_BIT_TO_8_TABLE = MakeReplicateTable<u32, 2, 8>();
570static constexpr auto REPLICATE_3_BIT_TO_8_TABLE = MakeReplicateTable<u32, 3, 8>();
571static constexpr auto REPLICATE_4_BIT_TO_8_TABLE = MakeReplicateTable<u32, 4, 8>();
572static 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
575static 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
598static constexpr auto REPLICATE_1_BIT_TO_6_TABLE = MakeReplicateTable<u32, 1, 6>();
599static constexpr auto REPLICATE_2_BIT_TO_6_TABLE = MakeReplicateTable<u32, 2, 6>();
600static constexpr auto REPLICATE_3_BIT_TO_6_TABLE = MakeReplicateTable<u32, 3, 6>();
601static constexpr auto REPLICATE_4_BIT_TO_6_TABLE = MakeReplicateTable<u32, 4, 6>();
602static constexpr auto REPLICATE_5_BIT_TO_6_TABLE = MakeReplicateTable<u32, 5, 6>();
603static 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
620class Pixel {
621protected:
622 using ChannelType = s16;
623 u8 m_BitDepth[4] = {8, 8, 8, 8};
624 s16 color[4] = {};
625
626public:
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
744static 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
915static 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
1004static 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
1073static 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
1084static 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
1091static 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
1105static 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
1191static 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
1196static 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
1317static 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
1549void 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
132void 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 &lt;div&gt;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.&lt;/div&gt;
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
44void ConfigureCpuDebug::ApplyConfiguration() { 46void 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
55void ConfigureCpuDebug::changeEvent(QEvent* event) { 58void 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 &lt;br&gt; 34 &lt;br&gt;
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 &lt;br&gt; 36 &lt;br&gt;
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 &lt;/div&gt; 38 &lt;/div&gt;
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 &lt;div style="white-space: nowrap"&gt;This optimization speeds up memory accesses by the guest program.&lt;/div&gt;
150 &lt;div style="white-space: nowrap"&gt;Enabling it causes guest memory reads/writes to be done directly into memory and make use of Host's MMU.&lt;/div&gt;
151 &lt;div style="white-space: nowrap"&gt;Disabling this forces all memory accesses to use Software MMU Emulation.&lt;/div&gt;
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
28ConfigureDebug::~ConfigureDebug() = default; 28ConfigureDebug::~ConfigureDebug() = default;
29 29
30void ConfigureDebug::SetConfiguration() { 30void 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
28ConfigureGeneral::~ConfigureGeneral() = default; 35ConfigureGeneral::~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
62void ConfigureGeneral::SetResetCallback(std::function<void()> callback) {
63 reset_callback = std::move(callback);
64}
65
66void 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
52void ConfigureGeneral::ApplyConfiguration() { 80void 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
11class ConfigureDialog;
12
10namespace ConfigurationShared { 13namespace ConfigurationShared {
11enum class CheckState; 14enum 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
29private: 34private:
@@ -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
1396void ConfigureInputPlayer::CreateProfile() { 1396void 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
90void PlayerControlPreview::SetControllerType(const Settings::ControllerType type) { 92void 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
113void PlayerControlPreview::UpdateColors() { 116void 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
162void 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
159void PlayerControlPreview::UpdateInput() { 175void 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
18namespace { 18namespace {
19constexpr std::array default_icon_sizes{ 19constexpr 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
27constexpr std::array row_text_names{ 28constexpr 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
37QString GetTranslatedIconSize(size_t index) {
38 return QCoreApplication::translate("ConfigureUI", default_icon_sizes[index].second);
39}
40
41QString GetTranslatedRowTextName(size_t index) {
42 return QCoreApplication::translate("ConfigureUI", row_text_names[index]);
43}
31} // Anonymous namespace 44} // Anonymous namespace
32 45
33ConfigureUi::ConfigureUi(QWidget* parent) : QWidget(parent), ui(new Ui::ConfigureUi) { 46ConfigureUi::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
154void ConfigureUi::InitializeIconSizeComboBox() { 167void 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
44QAction* ControllerDialog::toggleViewAction() { 44QAction* 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) {
509void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::string& path) { 505void 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;
28class GameListSearchField; 28class GameListSearchField;
29class GameListDir; 29class GameListDir;
30class GMainWindow; 30class GMainWindow;
31enum class StartGameType;
31 32
32namespace FileSys { 33namespace FileSys {
33class ManualContentProvider; 34class ManualContentProvider;
@@ -82,6 +83,7 @@ public:
82 static const QStringList supported_file_extensions; 83 static const QStringList supported_file_extensions;
83 84
84signals: 85signals:
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
1096void GMainWindow::ConnectWidgetEvents() { 1096void 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
1323void GMainWindow::BootGame(const QString& filename, std::size_t program_index) { 1324void 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
40class QtSoftwareKeyboardDialog; 40class QtSoftwareKeyboardDialog;
41 41
42enum class StartGameType {
43 Normal, // Can use custom configuration
44 Global, // Only uses global configuration
45};
46
42namespace Core::Frontend { 47namespace Core::Frontend {
43struct ControllerParameters; 48struct ControllerParameters;
44struct InlineAppearParameters; 49struct 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
102extern Values values; 103extern 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
39QString LimitableInputDialog::GetText(QWidget* parent, const QString& title, const QString& text, 41QString 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
81void 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
24private: 30private:
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 @@
7namespace DefaultINI { 7namespace DefaultINI {
8 8
9const char* sdl2_config_file = R"( 9const 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
87touch_device= 87touch_device=
88 88
89# Whether to enable or disable touch input from button
90# 0 (default): Disabled, 1: Enabled
91use_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
139cpuopt_reduce_misalign_checks = 151cpuopt_reduce_misalign_checks =
140 152
153# Enable Host MMU Emulation (faster guest memory access)
154# 0: Disabled, 1 (default): Enabled
155cpuopt_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
179use_asynchronous_shaders = 195use_asynchronous_shaders =
180 196
197# Enable NVDEC emulation.
198# 0: Off, 1 (default): On
199use_nvdec_emulation =
200
201# Accelerate ASTC texture decoding.
202# 0: Off, 1 (default): On
203accelerate_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)
183use_frame_limit = 207use_frame_limit =
@@ -325,6 +349,8 @@ record_frame_times =
325dump_exefs=false 349dump_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
327dump_nso=false 351dump_nso=false
352# Determines whether or not yuzu will save the filesystem access log.
353enable_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
330quest_flag = 356quest_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
33class SDLGLContext : public Core::Frontend::GraphicsContext { 33class SDLGLContext : public Core::Frontend::GraphicsContext {
34public: 34public:
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
161std::unique_ptr<Core::Frontend::GraphicsContext> EmuWindow_SDL2_GL::CreateSharedContext() const { 158std::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
22private: 22private:
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