Compare commits

..

7 Commits

Author SHA1 Message Date
lizzie a298ed65c1 2026-09-14 09:32:00
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-14 21:33:58 +02:00
lizzie 02dccd61ed 2026-09-14 09:23:00
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-14 21:33:58 +02:00
lizzie 9bee2c2dc1 2026-09-14 09:22:19
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-14 21:33:58 +02:00
lizzie 1602864ff6 2026-09-14 08:48:05
Signed-off-by: lizzie <lizzie@eden-emu.dev>
2026-09-14 21:33:58 +02:00
lizzie de0ba75509 Reapply "[audio] Replace libopus with FFmpeg (#4169)" (#4419)
This reverts commit 2b4184dd2b.
2026-09-14 21:33:58 +02:00
lizzie d16ed2073f Trigger build 2026-09-14 21:33:58 +02:00
Exverge 415df0cfe4 [dynarmic] fix sign extension logic on x64 2026-09-14 21:33:58 +02:00
82 changed files with 1437 additions and 5242 deletions
@@ -1,28 +0,0 @@
From cc15da16e533b2a801934eab2dfeaf3c3949a1dc Mon Sep 17 00:00:00 2001
From: crueter <crueter@eden-emu.dev>
Date: Mon, 8 Sep 2025 12:28:55 -0400
Subject: [PATCH] [cmake] disable NEON runtime check on clang-cl
When enabling runtime NEON checking for clang-cl, the linker would error out with `undefined symbol: __emit`, since clang doesn't actually implement this instruction. Therefore it makes sense to disable the runtime check by default on this platform, until either this is fixed or a clang-cl compatible intrinsic check is added (I don't have enough knowledge of MSVC to do this)
---
cmake/OpusConfig.cmake | 7 ++++++-
1 file changed, 6 insertions(+), 1 deletion(-)
diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake
index e9319fbad..d0f459e88 100644
--- a/cmake/OpusConfig.cmake
+++ b/cmake/OpusConfig.cmake
@@ -71,7 +71,12 @@ elseif(OPUS_CPU_ARM AND NOT OPUS_DISABLE_INTRINSICS)
opus_detect_neon(COMPILER_SUPPORT_NEON)
if(COMPILER_SUPPORT_NEON)
option(OPUS_USE_NEON "Option to enable NEON" ON)
- option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ON)
+ if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
+ set(NEON_RUNTIME_CHECK_DEFAULT OFF)
+ else()
+ set(NEON_RUNTIME_CHECK_DEFAULT ON)
+ endif()
+ option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ${NEON_RUNTIME_CHECK_DEFAULT})
option(OPUS_PRESUME_NEON "Assume target CPU has NEON support" OFF)
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
set(OPUS_PRESUME_NEON ON)
-153
View File
@@ -1,153 +0,0 @@
From bf455b67b4eaa446ffae5d25410b141b7b1b1082 Mon Sep 17 00:00:00 2001
From: crueter <crueter@eden-emu.dev>
Date: Mon, 8 Sep 2025 12:08:20 -0400
Subject: [PATCH] [cmake] `OPUS_INSTALL` option; only default install if root
project
Signed-off-by: crueter <crueter@eden-emu.dev>
---
CMakeLists.txt | 112 ++++++++++++++++++++++++++++---------------------
1 file changed, 64 insertions(+), 48 deletions(-)
diff --git a/CMakeLists.txt b/CMakeLists.txt
index fcf034b19..08b5e16f8 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -4,6 +4,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include(OpusPackageVersion)
get_package_version(PACKAGE_VERSION PROJECT_VERSION)
+# root project detection
+if(DEFINED PROJECT_NAME)
+ set(root_project OFF)
+else()
+ set(root_project ON)
+endif()
+
project(Opus LANGUAGES C VERSION ${PROJECT_VERSION})
include(OpusFunctions)
@@ -83,12 +90,16 @@ set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging p
option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
+set(OPUS_INSTALL_HELP_STR "Install Opus targets")
+option(OPUS_INSTALL ${OPUS_INSTALL_HELP_STR} ${root_project})
+add_feature_info(OPUS_INSTALL OPUS_INSTALL ${OPUS_INSTALL_HELP_STR})
+
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
-option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
+option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.")
-option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
+option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
set(OPUS_DRED_HELP_STR "enable DRED.")
@@ -613,53 +624,58 @@ if(OPUS_BUILD_FRAMEWORK)
OUTPUT_NAME Opus)
endif()
-install(TARGETS opus
- EXPORT OpusTargets
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
- FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
-
-if(OPUS_INSTALL_PKG_CONFIG_MODULE)
- set(prefix ${CMAKE_INSTALL_PREFIX})
- set(exec_prefix ${CMAKE_INSTALL_PREFIX})
- set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
- set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
- set(VERSION ${PACKAGE_VERSION})
- if(HAVE_LIBM)
- set(LIBM "-lm")
+if (OPUS_INSTALL)
+ install(TARGETS opus
+ EXPORT OpusTargets
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
+ FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
+
+ if(OPUS_INSTALL_PKG_CONFIG_MODULE)
+ set(prefix ${CMAKE_INSTALL_PREFIX})
+ set(exec_prefix ${CMAKE_INSTALL_PREFIX})
+ set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
+ set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
+ set(VERSION ${PACKAGE_VERSION})
+ if(HAVE_LIBM)
+ set(LIBM "-lm")
+ endif()
+ configure_file(opus.pc.in opus.pc)
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
+ endif()
+
+ if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
+ set(CPACK_GENERATOR TGZ)
+ include(CPack)
+ set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
+ install(EXPORT OpusTargets
+ NAMESPACE Opus::
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
+
+ include(CMakePackageConfigHelpers)
+
+ set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
+ configure_package_config_file(
+ ${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
+ OpusConfig.cmake
+ INSTALL_DESTINATION
+ ${CMAKE_INSTALL_PACKAGEDIR}
+ PATH_VARS
+ INCLUDE_INSTALL_DIR
+ INSTALL_PREFIX
+ ${CMAKE_INSTALL_PREFIX})
+
+ write_basic_package_version_file(OpusConfigVersion.cmake
+ VERSION ${PROJECT_VERSION}
+ COMPATIBILITY SameMajorVersion)
+
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
+ ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
endif()
- configure_file(opus.pc.in opus.pc)
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
-endif()
-
-if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
- set(CPACK_GENERATOR TGZ)
- include(CPack)
- set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
- install(EXPORT OpusTargets
- NAMESPACE Opus::
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
-
- include(CMakePackageConfigHelpers)
-
- set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
- configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
- OpusConfig.cmake
- INSTALL_DESTINATION
- ${CMAKE_INSTALL_PACKAGEDIR}
- PATH_VARS
- INCLUDE_INSTALL_DIR
- INSTALL_PREFIX
- ${CMAKE_INSTALL_PREFIX})
- write_basic_package_version_file(OpusConfigVersion.cmake
- VERSION ${PROJECT_VERSION}
- COMPATIBILITY SameMajorVersion)
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
- ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
endif()
if(OPUS_BUILD_PROGRAMS)
-15
View File
@@ -464,21 +464,6 @@ if (NOT YUZU_STATIC_ROOM)
if (ZLIB_ADDED)
add_library(ZLIB::ZLIB ALIAS zlibstatic)
endif()
# Opus
AddJsonPackage(opus)
if (Opus_ADDED)
if (MSVC AND CXX_CLANG)
target_compile_options(opus PRIVATE
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-implicit-function-declaration>
)
endif()
endif()
if (NOT TARGET Opus::opus)
add_library(Opus::opus ALIAS opus)
endif()
endif()
if(NOT TARGET Boost::headers)
-22
View File
@@ -1,22 +0,0 @@
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
# SPDX-License-Identifier: GPL-3.0-or-later
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
# SPDX-License-Identifier: GPL-2.0-or-later
find_package(PkgConfig QUIET)
pkg_search_module(OPUS QUIET IMPORTED_TARGET opus)
include(FindPackageHandleStandardArgs)
find_package_handle_standard_args(Opus
REQUIRED_VARS OPUS_LINK_LIBRARIES
VERSION_VAR OPUS_VERSION
)
if (MSYS2)
FixMsysPath(PkgConfig::OPUS)
endif()
if (Opus_FOUND AND NOT TARGET Opus::opus)
add_library(Opus::opus ALIAS PkgConfig::OPUS)
endif()
+1 -16
View File
@@ -82,7 +82,7 @@
"name": "ffmpeg",
"package": "FFmpeg",
"repo": "crueter-ci/FFmpeg",
"version": "9.0.1-1788120736-bf1b838f2a"
"version": "9.0.1-1788303113-bf1b838f2a"
},
"fmt": {
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
@@ -210,21 +210,6 @@
"repo": "jimmy-park/openssl-cmake",
"version": "3.6.2"
},
"opus": {
"find_args": "MODULE",
"hash": "9506147b0de35befda8633ff272981cc2575c860874791bd455b752f797fd7dbd1079f0ba42ccdd7bb1fe6773fa5e84b3d75667c2883dd1fb2d0e4a5fa4f8387",
"min_version": "1.3",
"options": [
"OPUS_PRESUME_NEON ON"
],
"package": "Opus",
"patches": [
"0001-disable-clang-runtime-neon.patch",
"0002-no-install.patch"
],
"repo": "xiph/opus",
"version": "a3f0ec02b3"
},
"quazip": {
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
"min_version": "1.3",
+9 -10
View File
@@ -60,7 +60,6 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
* [ZLIB](https://www.zlib.net/) 1.2+
* [zstd](https://facebook.github.io/zstd/) 1.5+
* [enet](http://enet.bespin.org/) 1.3+
* [Opus](https://opus-codec.org/) 1.3+
Vulkan 1.3.274+ is also needed:
@@ -121,7 +120,7 @@ sudo emerge -a \
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
dev-util/vulkan-utility-libraries dev-util/glslang \
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
media-gfx/renderdoc media-libs/libva media-video/ffmpeg \
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
net-libs/enet \
sys-libs/zlib \
@@ -153,7 +152,7 @@ Required USE flags:
<summary>Arch Linux</summary>
```sh
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
```
* Building with QT Web Engine requires `qt6-webengine` as well.
@@ -166,7 +165,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
<summary>Ubuntu, Debian, Mint Linux</summary>
```sh
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libasound2t64 vulkan-utility-libraries-dev
```
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
@@ -213,7 +212,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
# Enable the community repository
setup-apkrepos -c
# Install
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev jq patch
```
</details>
@@ -261,7 +260,7 @@ brew install molten-vk
As root run:
```sh
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
```
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
@@ -275,7 +274,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
For NetBSD +10.1:
```sh
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
```
[Caveats](./Caveats.md#netbsd).
@@ -306,7 +305,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
<summary>OpenIndiana</summary>
```sh
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
```
[Caveats](./Caveats.md#openindiana).
@@ -330,7 +329,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
```sh
BASE="git make autoconf libtool automake-wrapper jq patch"
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3"
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet libusb openssl SDL3"
# Either x86_64 or clang-aarch64 (Windows on ARM)
packages="$BASE"
for pkg in $MINGW; do
@@ -356,7 +355,7 @@ pacman -Syuu --needed --noconfirm $packages
<summary>HaikuOS</summary>
```sh
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
```
[Caveats](./Caveats.md#haikuos).
+1 -1
View File
@@ -12,7 +12,7 @@ pkgs.mkShellNoCC {
git cmake clang gnumake patch jq pkg-config
# libraries
openssl boost fmt nlohmann_json lz4 zlib zstd
enet libopus vulkan-headers vulkan-utility-libraries
enet vulkan-headers vulkan-utility-libraries
spirv-tools spirv-headers vulkan-loader unzip
glslang python3 httplib cpp-jwt ffmpeg-headless
libusb1 cubeb
@@ -27,7 +27,6 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
RENDERER_ASYNCHRONOUS_GPU_EMULATION("use_asynchronous_gpu_emulation"),
RENDERER_ASYNC_PRESENTATION("async_presentation"),
RENDERER_ASYNCHRONOUS_SHADERS("use_asynchronous_shaders"),
RENDERER_UNIFIED_MEMORY("use_unified_memory"),
RENDERER_REACTIVE_FLUSHING("use_reactive_flushing"),
ENABLE_BUFFER_HISTORY("enable_buffer_history"),
USE_OPTIMIZED_VERTEX_BUFFERS("use_optimized_vertex_buffers"),
@@ -785,13 +785,6 @@ abstract class SettingsItem(
descriptionId = R.string.renderer_asynchronous_shaders_description
)
)
put(
SwitchSetting(
BooleanSetting.RENDERER_UNIFIED_MEMORY,
titleId = R.string.renderer_unified_memory,
descriptionId = R.string.renderer_unified_memory_description
)
)
put(
SingleChoiceSetting(
IntSetting.FAST_GPU_TIME,
@@ -558,7 +558,6 @@ class SettingsFragmentPresenter(
add(BooleanSetting.FIX_BLOOM_EFFECTS.key)
add(BooleanSetting.EMULATE_BGR565.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_SHADERS.key)
add(BooleanSetting.RENDERER_UNIFIED_MEMORY.key)
add(IntSetting.ANDROID_PIPELINE_WORKERS.key)
add(BooleanSetting.RENDERER_ASYNCHRONOUS_GPU_EMULATION.key)
add(BooleanSetting.RENDERER_ASYNC_PRESENTATION.key)
@@ -79,13 +79,6 @@ class LicensesFragment : Fragment() {
R.string.license_ffmpeg_copyright,
R.string.license_ffmpeg_text
),
License(
R.string.license_opus,
R.string.license_opus_description,
R.string.license_opus_link,
R.string.license_opus_copyright,
R.string.license_opus_text
),
License(
R.string.license_sirit,
R.string.license_sirit_description,
@@ -605,8 +605,6 @@
<string name="rescale_hack_description">Enables a legacy handling for the rescale configuration pass for games by using a quick rescale path</string>
<string name="renderer_asynchronous_shaders">Use asynchronous shaders</string>
<string name="renderer_asynchronous_shaders_description">Compiles shaders asynchronously. This may reduce stutters but may also introduce glitches.</string>
<string name="renderer_unified_memory">Unified memory access</string>
<string name="renderer_unified_memory_description">Allows GPU write buffer readbacks directly into guest memory, skipping the CPU staging copy.</string>
<string name="gpu_unswizzle_settings">GPU Unswizzle Settings</string>
<string name="gpu_unswizzle_settings_description">Configure GPU-based texture unswizzling parameters or disable it entirely. Adjust these settings to balance performance and texture loading quality.</string>
<string name="gpu_unswizzle_enable">Enable GPU Unswizzle</string>
@@ -1786,51 +1784,6 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
DAMAGES.
</string>
<string name="license_opus" translatable="false">Opus</string>
<string name="license_opus_description" translatable="false">Modern audio compression for the internet</string>
<string name="license_opus_link" translatable="false">https://github.com/xiph/opus</string>
<string name="license_opus_copyright" translatable="false">Copyright 20012011 Xiph.Org, Skype Limited, Octasic, Jean-Marc Valin, Timothy B. Terriberry, CSIRO, Gregory Maxwell, Mark Borgerding, Erik de Castro Lopo</string>
<string name="license_opus_text" translatable="false">
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:\n\n
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.\n\n
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.\n\n
- Neither the name of Internet Society, IETF or IETF Trust, nor the
names of specific contributors, may be used to endorse or promote
products derived from this software without specific prior written
permission.\n\n
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS\'\' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.\n\n
Opus is subject to the royalty-free patent licenses which are
specified at:\n\n
Xiph.Org Foundation:
https://datatracker.ietf.org/ipr/1524/ \n\n
Microsoft Corporation:
https://datatracker.ietf.org/ipr/1914/ \n\n
Broadcom Corporation:
https://datatracker.ietf.org/ipr/1526/
</string>
<string name="license_sirit" translatable="false">Sirit</string>
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
+8 -6
View File
@@ -15,10 +15,6 @@ add_library(audio_core STATIC
adsp/apps/audio_renderer/command_list_processor.h
adsp/apps/opus/opus_decoder.cpp
adsp/apps/opus/opus_decoder.h
adsp/apps/opus/opus_decode_object.cpp
adsp/apps/opus/opus_decode_object.h
adsp/apps/opus/opus_multistream_decode_object.cpp
adsp/apps/opus/opus_multistream_decode_object.h
adsp/apps/opus/shared_memory.h
audio_core.cpp
audio_core.h
@@ -226,8 +222,14 @@ else()
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
endif()
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(audio_core PUBLIC common core Opus::opus)
if (YUZU_USE_EXTERNAL_FFMPEG)
add_dependencies(audio_core ffmpeg-build)
endif()
target_include_directories(audio_core PUBLIC ${FFmpeg_INCLUDE_DIR})
target_link_libraries(audio_core PRIVATE ${FFmpeg_LIBRARIES})
target_link_options(audio_core PRIVATE ${FFmpeg_LDFLAGS})
target_link_libraries(audio_core PUBLIC common core)
if (ENABLE_CUBEB)
target_sources(audio_core PRIVATE
@@ -1,110 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
#include "common/assert.h"
namespace AudioCore::ADSP::OpusDecoder {
namespace {
bool IsValidChannelCount(u32 channel_count) {
return channel_count == 1 || channel_count == 2;
}
} // namespace
u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
if (!IsValidChannelCount(channel_count)) {
return 0;
}
return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
}
OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
if (new_decoder->magic == DecodeObjectMagic) {
if (!new_decoder->initialized ||
(new_decoder->initialized && new_decoder->self == comparison)) {
new_decoder->state_valid = true;
}
} else {
new_decoder->initialized = false;
new_decoder->state_valid = true;
}
return *new_decoder;
}
s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
if (!state_valid) {
return OPUS_INVALID_STATE;
}
if (initialized) {
return OPUS_OK;
}
// Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
// it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
// provided.
// We could use _create and have libopus allocate it for us, but then we have to separately
// track which decoder is being used between this and multistream in order to call the correct
// destroy from the host side.
// This is a bit cringe, but is safe as these objects are only ever initialized inside the given
// workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
decoder = (LibOpusDecoder*)(this + 1);
s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
if (ret == OPUS_OK) {
magic = DecodeObjectMagic;
initialized = true;
state_valid = true;
self = this;
final_range = 0;
}
return ret;
}
s32 OpusDecodeObject::Shutdown() {
if (!state_valid) {
return OPUS_INVALID_STATE;
}
if (initialized) {
magic = 0x0;
initialized = false;
state_valid = false;
self = nullptr;
final_range = 0;
decoder = nullptr;
}
return OPUS_OK;
}
s32 OpusDecodeObject::ResetDecoder() {
return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
}
s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
u64 input_data, u64 input_data_size) {
ASSERT(initialized);
out_sample_count = 0;
if (!state_valid) {
return OPUS_INVALID_STATE;
}
auto ret_code_or_samples = opus_decode(
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
if (ret_code_or_samples < OPUS_OK) {
return ret_code_or_samples;
}
out_sample_count = ret_code_or_samples;
return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
}
} // namespace AudioCore::ADSP::OpusDecoder
@@ -1,41 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <opus.h>
#include "common/common_types.h"
namespace AudioCore::ADSP::OpusDecoder {
using LibOpusDecoder = ::OpusDecoder;
static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
class OpusDecodeObject {
public:
static u32 GetWorkBufferSize(u32 channel_count);
static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
s32 Shutdown();
s32 ResetDecoder();
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
u64 input_data_size);
u32 GetFinalRange() const noexcept {
return final_range;
}
private:
u32 magic;
bool initialized;
bool state_valid;
OpusDecodeObject* self;
u32 final_range;
LibOpusDecoder* decoder;
};
static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
} // namespace AudioCore::ADSP::OpusDecoder
+368 -228
View File
@@ -5,55 +5,397 @@
// SPDX-License-Identifier: GPL-2.0-or-later
#include <array>
#include <chrono>
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
extern "C" {
#include <libswresample/swresample.h>
#include <libavcodec/avcodec.h>
#include <libavcodec/codec.h>
#include <libavcodec/packet.h>
#include <libavutil/channel_layout.h>
#include <libavutil/frame.h>
#include <libavutil/opt.h>
#include <libavutil/samplefmt.h>
}
#include "audio_core/adsp/apps/opus/shared_memory.h"
#include "audio_core/audio_core.h"
#include "audio_core/common/common.h"
#include "common/logging.h"
#include "common/thread.h"
#include "core/core.h"
#include "core/core_timing.h"
#include "core/hle/service/audio/errors.h"
namespace AudioCore::ADSP::OpusDecoder {
namespace {
constexpr size_t OpusStreamCountMax = 255;
constexpr u32 OPUS_STREAM_COUNT_MAX = 255;
// https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/libavcodec/libopusdec.c
constexpr u32 OPUS_HEAD_SIZE = 19;
constexpr u32 OPUS_MAX_CHANNELS = 2;
bool IsValidChannelCount(u32 channel_count) {
return channel_count == 1 || channel_count == 2;
return channel_count >= 1 || channel_count <= OPUS_MAX_CHANNELS;
}
bool IsValidMultiStreamChannelCount(u32 channel_count) {
return channel_count <= OpusStreamCountMax;
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
return total_stream_count > 0 && total_stream_count <= OPUS_STREAM_COUNT_MAX
&& s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count;
}
bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 stereo_stream_count) {
return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
stereo_stream_count >= 0 && stereo_stream_count <= total_stream_count;
}
struct OpusGenericDecodeParams {
AVPacket* pkt = nullptr;
AVFrame* frame = nullptr;
u8* tmp_buf[8] = {nullptr};
};
struct OpusGenericDecodeObject {
static u32 GetWorkBufferSizeMultistream(u32 total_stream_count, u32 stereo_stream_count) {
if (IsValidStreamCounts(total_stream_count, stereo_stream_count))
return 32 + 2556 * (total_stream_count * stereo_stream_count);
return 0;
}
static u32 GetWorkBufferSize(u32 channel_count) {
if (channel_count == 1 || channel_count == 2)
return 32 + 2556 * channel_count;
return 0;
}
/// idempotency of initialize is guaranteed
Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8 const* mappings) {
LOG_DEBUG(Audio_DSP, "sample_rate={}, total_stream_count={}, channel_count={}, stereo_stream_count={}, mappings={}", sample_rate, total_stream_count, channel_count, stereo_stream_count, fmt::ptr(mappings));
ASSERT(channel_count >= 1 && channel_count <= 2);
// prefer libopus, ffmpeg docs say to use libopus **if** available
// However, native opus can also work with swrescale:
// it uses planarfloat, we can resample to s16
AVCodec const* codec = avcodec_find_decoder_by_name("libopus");
bool is_libopus = codec != nullptr;
if (!codec) {
LOG_WARNING(Audio_DSP, "using ffmpeg native opus decoder");
codec = avcodec_find_decoder(AV_CODEC_ID_OPUS);
}
ASSERT(!(avc && avcodec_is_open(avc)));
if ((avc = avcodec_alloc_context3(codec)) != nullptr) {
if (is_libopus) {
const std::array<u8, 2> mapping_arr{0, 1};
mappings = mappings ? mappings : mapping_arr.data();
// freed by avcodec_context_free()
u8 *edata = reinterpret_cast<u8*>(av_mallocz(OPUS_HEAD_SIZE + 2 * OPUS_MAX_CHANNELS + AV_INPUT_BUFFER_PADDING_SIZE));
ASSERT(edata);
edata[9] = u8(channel_count); //channels
edata[10] = u8(0); //opus->pre_skip
edata[16] = u8(0); //gain_db
edata[18] = u8(0); //channel_map
edata[OPUS_HEAD_SIZE + 0] = u8(total_stream_count);
edata[OPUS_HEAD_SIZE + 1] = u8(stereo_stream_count);
if (channel_count >= 1) edata[OPUS_HEAD_SIZE + 2] = mappings[0];
if (channel_count >= 2) edata[OPUS_HEAD_SIZE + 3] = mappings[1];
avc->extradata = edata;
avc->extradata_size = OPUS_HEAD_SIZE + 2 * channel_count;
}
// FFmpeg hardcodes sample rate
avc->sample_rate = sample_rate;
avc->request_sample_fmt = AV_SAMPLE_FMT_S16;
avc->max_samples = 0x10000; //64K
av_channel_layout_default(&avc->ch_layout, channel_count);
if (avcodec_open2(avc, codec, nullptr) >= 0) {
ASSERT(avc->request_sample_fmt == AV_SAMPLE_FMT_S16);
// opus fltp -> libopus s16
if (swr_alloc_set_opts2(
&swr,
&avc->ch_layout, AV_SAMPLE_FMT_S16, avc->sample_rate,
&avc->ch_layout, (enum AVSampleFormat)avc->sample_fmt, avc->sample_rate,
0, nullptr) >= 0) {
if (swr_init(swr) >= 0) {
return ResultSuccess;
}
}
}
}
Shutdown();
return Service::Audio::ResultLibOpusInternalError;
}
Result Shutdown() {
LOG_DEBUG(Audio_DSP, "called avc={}", fmt::ptr(avc));
swr_free(&swr);
avcodec_free_context(&avc);
return ResultSuccess;
}
Result ResetDecoder() {
LOG_DEBUG(Audio_DSP, "called avc={}", fmt::ptr(avc));
if (avc) {
if (avcodec_is_open(avc)) avcodec_flush_buffers(avc);
return ResultSuccess;
}
return Service::Audio::ResultLibOpusInvalidState;
}
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size, OpusGenericDecodeParams params) {
LOG_DEBUG(Audio_DSP, "called out_sample_count={},output_data={:#x},output_data_size={},input_data={:#x},input_data_size={}", fmt::ptr(&out_sample_count), output_data, output_data_size, input_data, input_data_size);
ASSERT(avc && avcodec_is_open(avc));
out_sample_count = 0;
int r;
LOG_DEBUG(Audio_DSP, "old packet data={}", fmt::ptr(params.pkt->data));
if ((r = av_new_packet(params.pkt, int(input_data_size))) >= 0) {
LOG_DEBUG(Audio_DSP, "new packet data={}", fmt::ptr(params.pkt->data));
std::memcpy(params.pkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
r = avcodec_send_packet(avc, params.pkt);
av_packet_unref(params.pkt);
if (r >= 0) {
while ((r = avcodec_receive_frame(avc, params.frame)) >= 0) {
LOG_DEBUG(Audio_DSP, "frame data={},data[0]={},nb_samples={}", fmt::ptr(params.frame->data), fmt::ptr(params.frame->data[0]), params.frame->nb_samples);
ASSERT(std::in_range<u16>(params.frame->nb_samples));
u8 *dst_arr[2] = {reinterpret_cast<u8*>(output_data), nullptr};
if (params.frame->format == avc->request_sample_fmt) {
// input_bsize == output_bsize
av_samples_copy(dst_arr, params.frame->data, out_sample_count, 0, params.frame->nb_samples, params.frame->ch_layout.nb_channels, (enum AVSampleFormat)params.frame->format);
out_sample_count += params.frame->nb_samples;
} else {
int out_samples = int(av_rescale_rnd(swr_get_delay(swr, params.frame->sample_rate) + params.frame->nb_samples, params.frame->sample_rate, params.frame->sample_rate, AV_ROUND_UP));
out_samples = swr_convert(swr, params.tmp_buf, out_samples, (const u8 **)params.frame->data, params.frame->nb_samples);
av_samples_copy(dst_arr, params.tmp_buf, out_sample_count, 0, out_samples, params.frame->ch_layout.nb_channels, avc->request_sample_fmt);
out_sample_count += out_samples;
}
av_frame_unref(params.frame);
}
if (r == AVERROR(EAGAIN) || r == AVERROR_EOF) {
LOG_DEBUG(Audio_DSP, "{}", r); // non-errors
} else if (r < 0) {
LOG_ERROR(Audio_DSP, "{}", r); // errors
}
return ResultSuccess;
}
}
LOG_ERROR(Audio_DSP, "{}", r);
return Service::Audio::ResultLibOpusInvalidState;
}
AVCodecContext* avc = nullptr;
SwrContext* swr = nullptr;
};
} // namespace
OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
OpusDecoder::OpusDecoder(Core::System& system) {
dsp_thread = std::jthread([this, &system](std::stop_token stop_token) {
Common::SetCurrentThreadName("DSP_OpusDecoder");
if (Receive(Direction::DSP, stop_token) != Message::Start) {
LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
return;
}
Send(Direction::Host, Message::StartOK);
// Main OpusDecoder thread, responsible for processing the incoming Opus packets.
::Common::unordered_map<u64, OpusGenericDecodeObject> decode_objects;
// Staging buffers used by various decoders
// 1 <= channels <= 2, then, UPB channels => 2
// 64K is enough for most
OpusGenericDecodeParams params{};
av_samples_alloc(params.tmp_buf, nullptr, 2, (int)0x10000, AV_SAMPLE_FMT_S16, 0);
params.pkt = av_packet_alloc();
params.frame = av_frame_alloc();
while (!stop_token.stop_requested()) {
auto msg = Receive(Direction::DSP, stop_token);
LOG_DEBUG(Audio_DSP, "msg={}, buffer={}", msg, shared_memory->host_send_data[0]);
switch (msg) {
case Shutdown:
Send(Direction::Host, Message::ShutdownOK);
return;
case GetWorkBufferSize: {
auto channel_count = s32(shared_memory->host_send_data[0]);
ASSERT(IsValidChannelCount(channel_count));
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSize(channel_count);
Send(Direction::Host, Message::GetWorkBufferSizeOK);
break;
}
case InitializeDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = s32(shared_memory->host_send_data[2]);
auto channel_count = s32(shared_memory->host_send_data[3]);
ASSERT(sample_rate >= 0);
ASSERT(IsValidChannelCount(channel_count));
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSize(channel_count));
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
it->second.Shutdown();
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw;
} else {
OpusGenericDecodeObject obj{};
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw;
decode_objects.insert_or_assign(buffer, obj);
}
Send(Direction::Host, Message::InitializeDecodeObjectOK);
break;
}
case ShutdownDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
} else {
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
}
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
break;
}
case DecodeInterleaved: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0];
auto input_data = shared_memory->host_send_data[1];
auto input_data_size = shared_memory->host_send_data[2];
auto output_data = shared_memory->host_send_data[3];
auto output_data_size = shared_memory->host_send_data[4];
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
auto reset_requested = shared_memory->host_send_data[6];
u32 decoded_samples{0};
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
auto res = ResultSuccess;
if (reset_requested)
res = it->second.ResetDecoder();
if (res == ResultSuccess)
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size, params);
auto end_time = system.CoreTiming().GetGlobalTimeUs();
shared_memory->dsp_return_data[0] = res.raw;
shared_memory->dsp_return_data[1] = decoded_samples;
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
} else {
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
}
Send(Direction::Host, Message::DecodeInterleavedOK);
break;
}
case MapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::MapMemoryOK);
break;
}
case UnmapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::UnmapMemoryOK);
break;
}
case GetWorkBufferSizeForMultiStream: {
auto total_stream_count = s32(shared_memory->host_send_data[0]);
auto stereo_stream_count = s32(shared_memory->host_send_data[1]);
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count);
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
break;
}
case InitializeMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = s32(shared_memory->host_send_data[2]);
auto channel_count = s32(shared_memory->host_send_data[3]);
auto total_stream_count = s32(shared_memory->host_send_data[4]);
auto stereo_stream_count = s32(shared_memory->host_send_data[5]);
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
// mappings, but [6] is never set, and there is not enough room in the argument data for
// more than 40 channels, when 255 are possible.
// It also means the mapping values are undefined, though likely always 0,
// and the mappings given by the game are ignored. The mappings are copied to this
// dedicated buffer host side, so let's do as intended.
auto mappings = shared_memory->channel_mapping.data();
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
ASSERT(sample_rate >= 0);
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count));
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
it->second.Shutdown();
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
} else {
OpusGenericDecodeObject obj{};
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
decode_objects.insert_or_assign(buffer, obj);
}
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
break;
}
case ShutdownMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
} else {
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
}
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
break;
}
case DecodeInterleavedForMultiStream: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0];
auto input_data = shared_memory->host_send_data[1];
auto input_data_size = shared_memory->host_send_data[2];
auto output_data = shared_memory->host_send_data[3];
auto output_data_size = shared_memory->host_send_data[4];
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
auto reset_requested = shared_memory->host_send_data[6];
u32 decoded_samples{0};
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
auto res = ResultSuccess;
if (reset_requested)
res = it->second.ResetDecoder();
if (res == ResultSuccess)
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size, params);
auto end_time = system.CoreTiming().GetGlobalTimeUs();
shared_memory->dsp_return_data[0] = res.raw;
shared_memory->dsp_return_data[1] = decoded_samples;
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
} else {
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
}
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
break;
}
default:
LOG_ERROR(Audio_DSP, "Invalid OpusDecoder command {}", msg);
continue;
}
}
for (auto e : decode_objects)
e.second.Shutdown();
av_freep(params.tmp_buf);
av_frame_free(&params.frame);
av_packet_free(&params.pkt);
});
}
OpusDecoder::~OpusDecoder() {
if (!running) {
init_thread.request_stop();
return;
if (dsp_thread.joinable()) {
// Shutdown the thread
auto const stop_token = dsp_thread.get_stop_token();
Send(Direction::DSP, Message::Shutdown);
auto msg = Receive(Direction::Host, stop_token);
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
dsp_thread.request_stop();
dsp_thread.join();
}
// Shutdown the thread
Send(Direction::DSP, Message::Shutdown);
auto msg = Receive(Direction::Host);
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
Message::ShutdownOK, msg);
main_thread.request_stop();
main_thread.join();
running = false;
}
void OpusDecoder::Send(Direction dir, u32 message) {
@@ -64,206 +406,4 @@ u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
return mailbox.Receive(dir, stop_token);
}
void OpusDecoder::Init(std::stop_token stop_token) {
Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
if (Receive(Direction::DSP, stop_token) != Message::Start) {
LOG_ERROR(Service_Audio,
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
return;
}
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
running = true;
Send(Direction::Host, Message::StartOK);
}
void OpusDecoder::Main(std::stop_token stop_token) {
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
while (!stop_token.stop_requested()) {
auto msg = Receive(Direction::DSP, stop_token);
switch (msg) {
case Shutdown:
Send(Direction::Host, Message::ShutdownOK);
return;
case GetWorkBufferSize: {
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
ASSERT(IsValidChannelCount(channel_count));
shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
Send(Direction::Host, Message::GetWorkBufferSizeOK);
} break;
case InitializeDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
ASSERT(sample_rate >= 0);
ASSERT(IsValidChannelCount(channel_count));
ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
shared_memory->dsp_return_data[0] =
decoder_object.InitializeDecoder(sample_rate, channel_count);
Send(Direction::Host, Message::InitializeDecodeObjectOK);
} break;
case ShutdownDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
} break;
case DecodeInterleaved: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0];
auto input_data = shared_memory->host_send_data[1];
auto input_data_size = shared_memory->host_send_data[2];
auto output_data = shared_memory->host_send_data[3];
auto output_data_size = shared_memory->host_send_data[4];
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
auto reset_requested = shared_memory->host_send_data[6];
u32 decoded_samples{0};
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
s32 error_code{OPUS_OK};
if (reset_requested) {
error_code = decoder_object.ResetDecoder();
}
if (error_code == OPUS_OK) {
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
input_data, input_data_size);
}
if (error_code == OPUS_OK) {
if (final_range && decoder_object.GetFinalRange() != final_range) {
error_code = OPUS_INVALID_PACKET;
}
}
auto end_time = system.CoreTiming().GetGlobalTimeUs();
shared_memory->dsp_return_data[0] = error_code;
shared_memory->dsp_return_data[1] = decoded_samples;
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
Send(Direction::Host, Message::DecodeInterleavedOK);
} break;
case MapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::MapMemoryOK);
} break;
case UnmapMemory: {
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
Send(Direction::Host, Message::UnmapMemoryOK);
} break;
case GetWorkBufferSizeForMultiStream: {
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
total_stream_count, stereo_stream_count);
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
} break;
case InitializeMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
auto buffer_size = shared_memory->host_send_data[1];
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
// mappings, but [6] is never set, and there is not enough room in the argument data for
// more than 40 channels, when 255 are possible.
// It also means the mapping values are undefined, though likely always 0,
// and the mappings given by the game are ignored. The mappings are copied to this
// dedicated buffer host side, so let's do as intended.
auto mappings = shared_memory->channel_mapping.data();
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
ASSERT(sample_rate >= 0);
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
total_stream_count, stereo_stream_count));
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
} break;
case ShutdownMultiStreamDecodeObject: {
auto buffer = shared_memory->host_send_data[0];
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
} break;
case DecodeInterleavedForMultiStream: {
auto start_time = system.CoreTiming().GetGlobalTimeUs();
auto buffer = shared_memory->host_send_data[0];
auto input_data = shared_memory->host_send_data[1];
auto input_data_size = shared_memory->host_send_data[2];
auto output_data = shared_memory->host_send_data[3];
auto output_data_size = shared_memory->host_send_data[4];
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
auto reset_requested = shared_memory->host_send_data[6];
u32 decoded_samples{0};
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
s32 error_code{OPUS_OK};
if (reset_requested) {
error_code = decoder_object.ResetDecoder();
}
if (error_code == OPUS_OK) {
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
input_data, input_data_size);
}
if (error_code == OPUS_OK) {
if (final_range && decoder_object.GetFinalRange() != final_range) {
error_code = OPUS_INVALID_PACKET;
}
}
auto end_time = system.CoreTiming().GetGlobalTimeUs();
shared_memory->dsp_return_data[0] = error_code;
shared_memory->dsp_return_data[1] = decoded_samples;
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
} break;
default:
LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
continue;
}
}
}
} // namespace AudioCore::ADSP::OpusDecoder
+5 -22
View File
@@ -6,12 +6,13 @@
#pragma once
#include <memory>
#include <thread>
#include "common/container/unordered_map.h"
#include "audio_core/adsp/apps/opus/shared_memory.h"
#include "audio_core/adsp/mailbox.h"
#include "common/common_types.h"
#include "core/hle/result.h"
namespace Core {
class System;
@@ -48,16 +49,14 @@ enum Message : u32 {
DecodeInterleavedForMultiStreamOK = 50,
};
/**
* The AudioRenderer application running on the ADSP.
*/
/// @brief The AudioRenderer application running on the ADSP.
class OpusDecoder {
public:
explicit OpusDecoder(Core::System& system);
~OpusDecoder();
bool IsRunning() const noexcept {
return running;
return dsp_thread.joinable();
}
void Send(Direction dir, u32 message);
@@ -68,28 +67,12 @@ public:
}
private:
/**
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
*/
void Init(std::stop_token stop_token);
/**
* Main OpusDecoder thread, responsible for processing the incoming Opus packets.
*/
void Main(std::stop_token stop_token);
/// Core system
Core::System& system;
/// Mailbox to communicate messages with the host, drives the main thread
Mailbox mailbox;
/// Init thread
std::jthread init_thread{};
/// Main thread
std::jthread main_thread{};
/// The current state
bool running{};
/// Structure shared with the host, input data set by the host before sending a mailbox message,
/// and the responses are written back by the OpusDecoder.
SharedMemory* shared_memory{};
std::jthread dsp_thread{};
};
} // namespace AudioCore::ADSP::OpusDecoder
@@ -1,113 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
#include "common/assert.h"
namespace AudioCore::ADSP::OpusDecoder {
namespace {
constexpr u32 OpusStreamCountMax = 255;
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
static_cast<s32>(stereo_stream_count) >= 0 &&
stereo_stream_count <= total_stream_count;
}
} // namespace
u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
u32 stereo_stream_count) {
if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
}
return 0;
}
OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
if (!new_decoder->initialized ||
(new_decoder->initialized && new_decoder->self == comparison)) {
new_decoder->state_valid = true;
}
} else {
new_decoder->initialized = false;
new_decoder->state_valid = true;
}
return *new_decoder;
}
s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
u32 channel_count, u32 stereo_stream_count,
u8* mappings) {
if (!state_valid) {
return OPUS_INVALID_STATE;
}
if (initialized) {
return OPUS_OK;
}
// See OpusDecodeObject::InitializeDecoder for an explanation of this
decoder = (LibOpusMSDecoder*)(this + 1);
s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
stereo_stream_count, mappings);
if (ret == OPUS_OK) {
magic = DecodeMultiStreamObjectMagic;
initialized = true;
state_valid = true;
self = this;
final_range = 0;
}
return ret;
}
s32 OpusMultiStreamDecodeObject::Shutdown() {
if (!state_valid) {
return OPUS_INVALID_STATE;
}
if (initialized) {
magic = 0x0;
initialized = false;
state_valid = false;
self = nullptr;
final_range = 0;
decoder = nullptr;
}
return OPUS_OK;
}
s32 OpusMultiStreamDecodeObject::ResetDecoder() {
return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
}
s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
u64 output_data_size, u64 input_data, u64 input_data_size) {
ASSERT(initialized);
out_sample_count = 0;
if (!state_valid) {
return OPUS_INVALID_STATE;
}
auto ret_code_or_samples = opus_multistream_decode(
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
if (ret_code_or_samples < OPUS_OK) {
return ret_code_or_samples;
}
out_sample_count = ret_code_or_samples;
return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
}
} // namespace AudioCore::ADSP::OpusDecoder
@@ -1,42 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#pragma once
#include <opus_multistream.h>
#include "common/common_types.h"
namespace AudioCore::ADSP::OpusDecoder {
using LibOpusMSDecoder = ::OpusMSDecoder;
static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
class OpusMultiStreamDecodeObject {
public:
static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
u32 stereo_stream_count, u8* mappings);
s32 Shutdown();
s32 ResetDecoder();
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
u64 input_data_size);
u32 GetFinalRange() const noexcept {
return final_range;
}
private:
u32 magic;
bool initialized;
bool state_valid;
OpusMultiStreamDecodeObject* self;
u32 final_range;
LibOpusMSDecoder* decoder;
};
static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
} // namespace AudioCore::ADSP::OpusDecoder
@@ -0,0 +1,32 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#pragma once
#include "common/common_types.h"
namespace AudioCore::ADSP {
static constexpr u32 DECODE_OBJECT_MAGIC = 0xDEADBEEF;
struct LibOpusDecoder {
u32 magic;
bool initialized;
bool state_valid;
LibOpusDecoder* self;
u32 final_range;
void* decoder;
};
static_assert(sizeof(LibOpusDecoder) == 32);
static constexpr u32 DECODE_MULTISTREAM_OBJECT_MAGIC = 0xDEADBEEF;
struct LibOpusMultistreamDecoder {
u32 magic;
bool initialized;
bool state_valid;
LibOpusMultistreamDecoder* self;
u32 final_range;
void* decoder;
};
static_assert(sizeof(LibOpusMultistreamDecoder) == 32);
}
@@ -6,7 +6,6 @@
#pragma once
#include "common/common_funcs.h"
#include "common/common_types.h"
namespace AudioCore::ADSP::OpusDecoder {
+23 -48
View File
@@ -10,39 +10,18 @@
#include "audio_core/audio_core.h"
#include "audio_core/opus/hardware_opus.h"
#include "core/core.h"
#include "core/hle/result.h"
namespace AudioCore::OpusDecoder {
namespace {
using namespace Service::Audio;
static constexpr Result ResultCodeFromLibOpusErrorCode(u64 error_code) {
s32 error{static_cast<s32>(error_code)};
ASSERT(error <= OPUS_OK);
switch (error) {
case OPUS_ALLOC_FAIL:
R_THROW(ResultLibOpusAllocFail);
case OPUS_INVALID_STATE:
R_THROW(ResultLibOpusInvalidState);
case OPUS_UNIMPLEMENTED:
R_THROW(ResultLibOpusUnimplemented);
case OPUS_INVALID_PACKET:
R_THROW(ResultLibOpusInvalidPacket);
case OPUS_INTERNAL_ERROR:
R_THROW(ResultLibOpusInternalError);
case OPUS_BUFFER_TOO_SMALL:
R_THROW(ResultBufferTooSmall);
case OPUS_BAD_ARG:
R_THROW(ResultLibOpusBadArg);
case OPUS_OK:
R_RETURN(ResultSuccess);
}
UNREACHABLE();
}
} // namespace
HardwareOpus::HardwareOpus(Core::System& system_)
: system{system_}, opus_decoder{system.AudioCore().ADSP().OpusDecoder()} {
: system{system_}
, opus_decoder{system.AudioCore().ADSP().OpusDecoder()}
{
opus_decoder.SetSharedMemory(shared_memory);
}
@@ -112,7 +91,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count,
R_THROW(ResultInvalidOpusDSPReturnCode);
}
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
}
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
@@ -140,7 +119,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
R_THROW(ResultInvalidOpusDSPReturnCode);
}
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
}
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
@@ -154,7 +133,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
"Expected Opus shutdown code {}, got {}",
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
}
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
@@ -169,7 +148,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si
"Expected Opus shutdown code {}, got {}",
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
}
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
@@ -193,12 +172,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
R_THROW(ResultInvalidOpusDSPReturnCode);
}
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
if (error_code == OPUS_OK) {
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
auto error_code = s32(shared_memory.dsp_return_data[0]);
if (error_code == ResultSuccess.raw) {
out_sample_count = u32(shared_memory.dsp_return_data[1]);
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
}
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
R_RETURN(Result(u32(error_code)));
}
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
@@ -207,29 +186,27 @@ Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void
void* buffer, u64& out_time_taken,
bool reset) {
std::scoped_lock l{mutex};
shared_memory.host_send_data[0] = (u64)buffer;
shared_memory.host_send_data[1] = (u64)input_data;
shared_memory.host_send_data[0] = u64(buffer);
shared_memory.host_send_data[1] = u64(input_data);
shared_memory.host_send_data[2] = input_data_size;
shared_memory.host_send_data[3] = (u64)output_data;
shared_memory.host_send_data[3] = u64(output_data);
shared_memory.host_send_data[4] = output_data_size;
shared_memory.host_send_data[5] = 0;
shared_memory.host_send_data[6] = reset;
opus_decoder.Send(ADSP::Direction::DSP,
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
R_THROW(ResultInvalidOpusDSPReturnCode);
}
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
if (error_code == OPUS_OK) {
auto const error_code = shared_memory.dsp_return_data[0];
if (error_code == ResultSuccess.raw) {
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
}
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
}
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
@@ -240,8 +217,7 @@ Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
ADSP::OpusDecoder::Message::MapMemoryOK, msg);
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::MapMemoryOK, msg);
R_THROW(ResultInvalidOpusDSPReturnCode);
}
R_SUCCEED();
@@ -255,8 +231,7 @@ Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
R_THROW(ResultInvalidOpusDSPReturnCode);
}
R_SUCCEED();
-2
View File
@@ -8,8 +8,6 @@
#include <array>
#include <mutex>
#include <opus.h>
#include "audio_core/adsp/apps/opus/opus_decoder.h"
#include "audio_core/adsp/apps/opus/shared_memory.h"
#include "audio_core/adsp/mailbox.h"
+28 -22
View File
@@ -4,17 +4,34 @@
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <fstream>
#include "common/heap_tracker.h"
#include "common/logging.h"
#include "common/memory_detect.h"
#include "common/assert.h"
namespace Common {
namespace {
s64 GetMaxPermissibleResidentMapCount() {
// Default value.
s64 value = 65530;
// Try to read how many mappings we can make.
std::ifstream s("/proc/sys/vm/max_map_count");
s >> value;
// Print, for debug.
LOG_INFO(HW_Memory, "Current maximum map count: {}", value);
// Allow 20000 maps for other code and to account for split inaccuracy.
return std::max<s64>(value - 20000, 0);
}
} // namespace
HeapTracker::HeapTracker(Common::HostMemory& buffer)
: m_buffer(buffer),
m_has_hardware_buffer_backing(!buffer.BackingHardwareBuffers().empty()),
m_max_resident_map_count(static_cast<s64>(GetPermissibleMapCount())) {}
: m_buffer(buffer), m_max_resident_map_count(GetMaxPermissibleResidentMapCount()) {}
HeapTracker::~HeapTracker() = default;
void HeapTracker::Map(size_t virtual_offset, size_t host_offset, size_t length,
@@ -68,8 +85,7 @@ void HeapTracker::Unmap(size_t virtual_offset, size_t size, bool is_separate_hea
// If resident, erase from resident map.
if (item->is_resident) {
m_resident_map_count -= this->HostMapCount(item->paddr, item->size);
ASSERT(m_resident_map_count >= 0);
ASSERT(--m_resident_map_count >= 0);
m_resident_mappings.erase(m_resident_mappings.iterator_to(*item));
}
@@ -175,7 +191,7 @@ bool HeapTracker::DeferredMapSeparateHeap(size_t virtual_offset) {
// This map is now resident.
it->is_resident = true;
m_resident_map_count += this->HostMapCount(it->paddr, it->size);
m_resident_map_count++;
m_resident_mappings.insert(*it);
}
@@ -197,17 +213,17 @@ void HeapTracker::RebuildSeparateHeapAddressSpace() {
// Despite being worse in theory, this has proven to be better in practice than more
// regularly dumping a smaller amount, because it significantly reduces average case
// lock contention.
s64 const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
std::size_t const desired_count = (std::min)(m_resident_map_count, m_max_resident_map_count) / 2;
std::size_t const evict_count = m_resident_map_count - desired_count;
auto it = m_resident_mappings.begin();
while (m_resident_map_count > desired_count && it != m_resident_mappings.end()) {
for (size_t i = 0; i < evict_count && it != m_resident_mappings.end(); i++) {
// Unmark and unmap.
it->is_resident = false;
m_buffer.Unmap(it->vaddr, it->size, false);
// Advance.
m_resident_map_count -= this->HostMapCount(it->paddr, it->size);
ASSERT(m_resident_map_count >= 0);
ASSERT(--m_resident_map_count >= 0);
it = m_resident_mappings.erase(it);
}
}
@@ -229,7 +245,6 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// Cache the original values.
auto* const left = std::addressof(*it);
const size_t orig_size = left->size;
const s64 orig_host_map_count = this->HostMapCount(left->paddr, orig_size);
// Adjust the left map.
const size_t left_size = offset - left->vaddr;
@@ -251,20 +266,11 @@ void HeapTracker::SplitHeapMapLocked(VAddr offset) {
// If resident, also insert into resident map.
if (right->is_resident) {
m_resident_map_count += this->HostMapCount(left->paddr, left->size) +
this->HostMapCount(right->paddr, right->size) -
orig_host_map_count;
m_resident_map_count++;
m_resident_mappings.insert(*right);
}
}
s64 HeapTracker::HostMapCount(PAddr paddr, size_t size) const {
if (!m_has_hardware_buffer_backing) {
return size != 0 ? 1 : 0;
}
return static_cast<s64>(m_buffer.BackingMapCount(paddr, size));
}
HeapTracker::AddrTree::iterator HeapTracker::GetNearestHeapMapLocked(VAddr offset) {
const SeparateHeapMap key{
.vaddr = offset,
-6
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -85,13 +82,10 @@ private:
AddrTree::iterator GetNearestHeapMapLocked(VAddr offset);
s64 HostMapCount(PAddr paddr, size_t size) const;
void RebuildSeparateHeapAddressSpace();
private:
Common::HostMemory& m_buffer;
const bool m_has_hardware_buffer_backing;
const s64 m_max_resident_map_count;
std::shared_mutex m_rebuild_lock{};
+6 -393
View File
@@ -51,65 +51,14 @@
#endif // ^^^ POSIX ^^^
#include <atomic>
#include <mutex>
#include <random>
#include <vector>
#include "common/alignment.h"
#include "common/assert.h"
#include "common/free_region_manager.h"
#include "common/host_memory.h"
#include "common/logging.h"
#include "common/memory_detect.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <cerrno>
#include <dlfcn.h>
#include <sys/ioctl.h>
#include <android/hardware_buffer.h>
namespace {
struct NativeHandle {
int version;
int numFds;
int numInts;
int data[1];
};
using PFN_AHardwareBuffer_getNativeHandle = const NativeHandle* (*)(const AHardwareBuffer*);
PFN_AHardwareBuffer_getNativeHandle ResolveGetNativeHandle() {
void* const lib = dlopen("libnativewindow.so", RTLD_NOW);
if (lib == nullptr) {
return nullptr;
}
return reinterpret_cast<PFN_AHardwareBuffer_getNativeHandle>(
dlsym(lib, "AHardwareBuffer_getNativeHandle"));
}
struct DmaBufSync {
u64 flags;
};
constexpr u64 DmaBufSyncRead = 1ULL << 0;
constexpr u64 DmaBufSyncWrite = 1ULL << 1;
constexpr u64 DmaBufSyncStart = 0ULL << 2;
constexpr u64 DmaBufSyncEnd = 1ULL << 2;
void SyncDmaBufCpuAccess(int fd, u64 phase) {
DmaBufSync sync{.flags = phase | DmaBufSyncRead | DmaBufSyncWrite};
while (ioctl(fd, _IOW('b', 0, DmaBufSync), &sync) != 0) {
if (errno != EINTR) {
return;
}
}
}
} // namespace
#endif
#if defined(__ANDROID__) && __ANDROID_API__ < 30
#include <sys/syscall.h>
@@ -126,12 +75,6 @@ namespace Common {
[[maybe_unused]] constexpr size_t PageAlignment = 0x1000;
[[maybe_unused]] constexpr size_t HugePageSize = 0x200000;
static std::atomic<u64> committed_backing_size{};
u64 GetCommittedBackingSize() noexcept {
return committed_backing_size.load(std::memory_order_relaxed);
}
#ifdef _WIN32
// Manually imported for MinGW compatibility
@@ -179,7 +122,7 @@ static void GetFuncAddress(Common::DynamicLibrary& dll, const char* name, T& pfn
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t)
explicit Impl(size_t backing_size_, size_t virtual_size_)
: backing_size{backing_size_}
, virtual_size{virtual_size_}
, process{GetCurrentProcess()}
@@ -293,10 +236,6 @@ public:
UNREACHABLE();
}
bool IsBackingShared() const noexcept {
return true;
}
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
@@ -573,10 +512,9 @@ static int shm_open_anon(int flags, mode_t mode) {
class HostMemory::Impl {
public:
explicit Impl(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
explicit Impl(size_t backing_size_, size_t virtual_size_)
: backing_size{backing_size_}
, virtual_size{virtual_size_}
, preferred_offset{preferred_offset_}
{}
bool Init() {
@@ -616,15 +554,10 @@ public:
LOG_WARNING(Common_Memory, "Using private mappings instead of shared ones");
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_ANONYMOUS | MAP_PRIVATE | MAP_NOCORE, -1, 0));
if (fd > 0) {
fd = -1;
close(fd);
}
fd = -1;
} else {
#ifdef __ANDROID__
if (InitAhbBacking()) {
return InitVirtual();
}
#endif
backing_base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_NOCORE, fd, 0));
}
if (backing_base == MAP_FAILED) {
@@ -632,10 +565,7 @@ public:
return false;
}
return InitVirtual();
}
bool InitVirtual() {
// Virtual memory initialization
virtual_base = virtual_map_base = static_cast<u8*>(ChooseVirtualBase(virtual_size));
if (virtual_base == MAP_FAILED) {
LOG_CRITICAL(HW_Memory, "mmap failed: {}", strerror(errno));
@@ -648,244 +578,6 @@ public:
return true;
}
#ifdef __ANDROID__
static AHardwareBuffer_Desc MakeBlobDesc(size_t len) {
return AHardwareBuffer_Desc{
.width = static_cast<u32>(len),
.height = 1,
.layers = 1,
.format = AHARDWAREBUFFER_FORMAT_BLOB,
.usage = AHARDWAREBUFFER_USAGE_CPU_READ_OFTEN |
AHARDWAREBUFFER_USAGE_CPU_WRITE_OFTEN |
AHARDWAREBUFFER_USAGE_GPU_DATA_BUFFER,
.stride = 0,
.rfu0 = 0,
.rfu1 = 0,
};
}
static bool ProbeAhbBacking(PFN_AHardwareBuffer_getNativeHandle get_native_handle) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(PageAlignment * 2);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
return false;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
return false;
}
const int probe_fd = handle->data[0];
bool ok = true;
const auto try_map = [&](int prot, off_t offset) {
if (!ok) {
return;
}
void* const ptr = mmap(nullptr, PageAlignment, prot, MAP_SHARED, probe_fd, offset);
if (ptr == MAP_FAILED) {
ok = false;
return;
}
munmap(ptr, PageAlignment);
};
try_map(PROT_READ | PROT_WRITE, 0);
try_map(PROT_READ | PROT_WRITE, static_cast<off_t>(PageAlignment));
#ifdef ARCHITECTURE_arm64
try_map(PROT_READ | PROT_EXEC, 0);
#endif
AHardwareBuffer_release(buffer);
return ok;
}
size_t ComputeAhbBudget(size_t window_size) const {
const u64 total_physical = Common::GetMemInfo().TotalPhysicalMemory;
constexpr u64 BaselineFootprint = 6ULL << 30;
if (total_physical <= BaselineFootprint) {
return 0;
}
const u64 permissible_maps = Common::GetPermissibleMapCount();
if (permissible_maps == 0) {
return 0;
}
u64 budget = (total_physical - BaselineFootprint) / 2;
constexpr u64 MapSlotsPerWindow = 64;
const u64 affordable_windows = permissible_maps / MapSlotsPerWindow;
budget = (std::min)(budget, affordable_windows * window_size);
const u64 available = Common::GetAvailablePhysicalMemory();
if (available != 0) {
budget = (std::min)(budget, available / 2);
}
budget = (std::min)(budget, static_cast<u64>(backing_size));
budget = Common::AlignDown(budget, window_size);
constexpr u64 MinimumBudget = 256ULL << 20;
if (budget < MinimumBudget) {
return 0;
}
return static_cast<size_t>(budget);
}
bool InitAhbBacking() {
if (!Settings::values.use_unified_memory.GetValue()) {
return false;
}
static const PFN_AHardwareBuffer_getNativeHandle get_native_handle =
ResolveGetNativeHandle();
if (get_native_handle == nullptr) {
return false;
}
constexpr size_t window_size = 256ULL << 20;
const size_t budget = ComputeAhbBudget(window_size);
if (budget == 0) {
return false;
}
if (!ProbeAhbBacking(get_native_handle)) {
return false;
}
const size_t aligned_backing = Common::AlignDown(backing_size, window_size);
const size_t max_windows = (std::min)(budget, aligned_backing) / window_size;
std::vector<AHardwareBuffer*> buffers;
std::vector<int> buffer_fds;
const auto cleanup = [&] {
for (AHardwareBuffer* buffer : buffers) {
AHardwareBuffer_release(buffer);
}
buffers.clear();
buffer_fds.clear();
};
for (size_t i = 0; i < max_windows; ++i) {
const AHardwareBuffer_Desc desc = MakeBlobDesc(window_size);
AHardwareBuffer* buffer{};
if (AHardwareBuffer_allocate(&desc, &buffer) != 0 || buffer == nullptr) {
break;
}
const NativeHandle* const handle = get_native_handle(buffer);
if (handle == nullptr || handle->numFds < 1) {
AHardwareBuffer_release(buffer);
break;
}
const int buffer_fd = handle->data[0];
const off_t buffer_len = lseek(buffer_fd, 0, SEEK_END);
if (buffer_len < static_cast<off_t>(window_size)) {
AHardwareBuffer_release(buffer);
break;
}
buffers.push_back(buffer);
buffer_fds.push_back(buffer_fd);
}
const size_t num_windows = buffers.size();
if (num_windows == 0) {
return false;
}
const size_t region_size = num_windows * window_size;
const size_t region_base = Common::AlignDown(
(std::min)(preferred_offset, aligned_backing - region_size), window_size);
u8* const base = static_cast<u8*>(mmap(nullptr, backing_size, PROT_NONE,
MAP_PRIVATE | MAP_ANONYMOUS | MAP_NORESERVE, -1, 0));
if (base == MAP_FAILED) {
cleanup();
return false;
}
const auto map_over_reservation = [&](size_t offset, size_t len, int map_fd,
off_t map_offset) {
if (len == 0) {
return true;
}
if (mmap(base + offset, len, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FIXED, map_fd,
map_offset) == MAP_FAILED) {
munmap(base, backing_size);
cleanup();
return false;
}
return true;
};
if (!map_over_reservation(0, region_base, fd, 0)) {
return false;
}
for (size_t i = 0; i < num_windows; ++i) {
if (!map_over_reservation(region_base + i * window_size, window_size, buffer_fds[i],
0)) {
return false;
}
}
const size_t tail_offset = region_base + region_size;
if (!map_over_reservation(tail_offset, backing_size - tail_offset, fd,
static_cast<off_t>(tail_offset))) {
return false;
}
backing_base = base;
ahb_windows = std::move(buffers);
ahb_fds = std::move(buffer_fds);
ahb_window_size = window_size;
ahb_base = region_base;
ahb_bytes = region_size;
committed_backing_size.store(region_size, std::memory_order_relaxed);
for (const int window_fd : ahb_fds) {
SyncDmaBufCpuAccess(window_fd, DmaBufSyncStart);
}
return true;
}
void MapBackingRange(size_t virtual_offset, size_t host_offset, size_t length, int prot_flags) {
while (length > 0) {
int map_fd = fd;
off_t map_offset = static_cast<off_t>(host_offset);
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t relative = host_offset - ahb_base;
const size_t window = relative / ahb_window_size;
const size_t local = relative % ahb_window_size;
map_fd = ahb_fds[window];
map_offset = static_cast<off_t>(local);
chunk = (std::min)(chunk, ahb_window_size - local);
}
void* const ret = mmap(virtual_base + virtual_offset, chunk, prot_flags,
MAP_SHARED | MAP_FIXED, map_fd, map_offset);
ASSERT_MSG(ret != MAP_FAILED, "mmap: {}", strerror(errno));
virtual_offset += chunk;
host_offset += chunk;
length -= chunk;
}
}
size_t BackingMapCount(size_t host_offset, size_t length) const noexcept {
if (length == 0) {
return 0;
}
if (ahb_bytes == 0) {
return 1;
}
size_t count = 0;
while (length > 0) {
size_t chunk = length;
if (host_offset < ahb_base) {
chunk = (std::min)(chunk, ahb_base - host_offset);
} else if (host_offset < ahb_base + ahb_bytes) {
const size_t local = (host_offset - ahb_base) % ahb_window_size;
chunk = (std::min)(chunk, ahb_window_size - local);
}
host_offset += chunk;
length -= chunk;
++count;
}
return count;
}
std::span<AHardwareBuffer* const> AhbWindows() const noexcept {
return ahb_windows;
}
size_t AhbWindowSize() const noexcept {
return ahb_bytes != 0 ? ahb_window_size : 0;
}
size_t AhbBase() const noexcept {
return ahb_base;
}
#endif
~Impl() {
Release();
}
@@ -914,12 +606,6 @@ public:
#ifdef ARCHITECTURE_arm64
if (True(perms & MemoryPermission::Execute))
prot_flags |= PROT_EXEC;
#endif
#ifdef __ANDROID__
if (ahb_bytes != 0) {
MapBackingRange(virtual_offset, host_offset, length, prot_flags);
return;
}
#endif
int flags = (fd >= 0 ? MAP_SHARED : MAP_PRIVATE) | MAP_FIXED;
void* ret = mmap(virtual_base + virtual_offset, length, prot_flags, flags, fd, host_offset);
@@ -965,18 +651,8 @@ public:
virtual_base = nullptr;
}
bool IsBackingShared() const noexcept {
#ifdef __ANDROID__
if (ahb_bytes != 0) {
return true;
}
#endif
return fd >= 0;
}
const size_t backing_size; ///< Size of the backing memory in bytes
const size_t virtual_size; ///< Size of the virtual address placeholder in bytes
const size_t preferred_offset;
u8* backing_base{reinterpret_cast<u8*>(MAP_FAILED)};
u8* virtual_base{reinterpret_cast<u8*>(MAP_FAILED)};
@@ -999,21 +675,6 @@ private:
int ret = close(fd);
ASSERT_MSG(ret == 0, "close failed: {}", strerror(errno));
}
#ifdef __ANDROID__
for (const int window_fd : ahb_fds) {
SyncDmaBufCpuAccess(window_fd, DmaBufSyncEnd);
}
for (AHardwareBuffer* buffer : ahb_windows) {
AHardwareBuffer_release(buffer);
}
ahb_windows.clear();
ahb_fds.clear();
if (ahb_bytes != 0) {
committed_backing_size.store(0, std::memory_order_relaxed);
ahb_bytes = 0;
}
#endif
}
void AdjustMap(size_t* virtual_offset, size_t* length) {
@@ -1039,19 +700,11 @@ private:
int fd{-1}; // memfd file descriptor, -1 is the error value of memfd_create
FreeRegionManager free_manager{};
#ifdef __ANDROID__
std::vector<AHardwareBuffer*> ahb_windows;
std::vector<int> ahb_fds;
size_t ahb_window_size{};
size_t ahb_base{};
size_t ahb_bytes{};
#endif
};
#endif // ^^^ POSIX ^^^
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_)
HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_)
: backing_size(backing_size_)
, virtual_size(virtual_size_)
{
@@ -1061,7 +714,7 @@ HostMemory::HostMemory(size_t backing_size_, size_t virtual_size_, size_t prefer
virtual_base = nullptr;
#else
// Try to allocate a fastmem arena.
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize, preferred_offset_);
impl = std::make_unique<HostMemory::Impl>(AlignUp(backing_size, PageAlignment), AlignUp(virtual_size, PageAlignment) + HugePageSize);
if (impl->Init()) {
backing_base = impl->backing_base;
virtual_base = impl->virtual_base;
@@ -1141,46 +794,6 @@ void HostMemory::ClearBackingRegion(size_t physical_offset, size_t length, u32 f
std::memset(backing_base + physical_offset, fill_value, length);
}
std::span<AHardwareBuffer* const> HostMemory::BackingHardwareBuffers() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindows() : std::span<AHardwareBuffer* const>{};
#else
return {};
#endif
}
size_t HostMemory::BackingMapCount(size_t host_offset, size_t length) const noexcept {
#ifdef __ANDROID__
return impl ? impl->BackingMapCount(host_offset, length) : (length != 0 ? 1 : 0);
#else
return length != 0 ? 1 : 0;
#endif
}
size_t HostMemory::BackingHardwareBufferWindowSize() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbWindowSize() : 0;
#else
return 0;
#endif
}
bool HostMemory::IsBackingShared() const noexcept {
#if defined(__OPENORBIS__) || defined(__managarm__)
return false;
#else
return impl && impl->IsBackingShared();
#endif
}
size_t HostMemory::BackingHardwareBufferBase() const noexcept {
#ifdef __ANDROID__
return impl ? impl->AhbBase() : 0;
#else
return 0;
#endif
}
void HostMemory::EnableDirectMappedAddress() {
#if !(defined(__OPENORBIS__) || defined(__managarm__))
if (impl) {
+1 -20
View File
@@ -8,16 +8,11 @@
#include <memory>
#include <optional>
#include <span>
#include "common/common_funcs.h"
#include "common/common_types.h"
struct AHardwareBuffer;
namespace Common {
[[nodiscard]] u64 GetCommittedBackingSize() noexcept;
enum class MemoryPermission : u32 {
Read = 1 << 0,
Write = 1 << 1,
@@ -32,7 +27,7 @@ DECLARE_ENUM_FLAG_OPERATORS(MemoryPermission)
*/
class HostMemory {
public:
explicit HostMemory(size_t backing_size_, size_t virtual_size_, size_t preferred_offset_ = 0);
explicit HostMemory(size_t backing_size_, size_t virtual_size_);
~HostMemory();
/**
@@ -66,20 +61,6 @@ public:
return backing_base;
}
[[nodiscard]] size_t BackingSize() const noexcept {
return backing_size;
}
[[nodiscard]] size_t BackingMapCount(size_t host_offset, size_t length) const noexcept;
[[nodiscard]] std::span<AHardwareBuffer* const> BackingHardwareBuffers() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferWindowSize() const noexcept;
[[nodiscard]] size_t BackingHardwareBufferBase() const noexcept;
[[nodiscard]] bool IsBackingShared() const noexcept;
[[nodiscard]] u8* VirtualBasePointer() noexcept {
return virtual_base;
}
-64
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -20,10 +17,6 @@
#endif
#endif
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include "common/memory_detect.h"
namespace Common {
@@ -76,61 +69,4 @@ const MemoryInfo& GetMemInfo() {
return mem_info;
}
u64 GetPermissibleMapCount() {
constexpr u64 DefaultMapCount = 65530;
constexpr u64 ReservedMaps = 20000;
u64 count = DefaultMapCount;
#ifdef __linux__
if (std::FILE* const file = std::fopen("/proc/sys/vm/max_map_count", "re")) {
char line[32];
if (std::fgets(line, sizeof(line), file) != nullptr) {
const u64 parsed = std::strtoull(line, nullptr, 10);
if (parsed != 0) {
count = parsed;
}
}
std::fclose(file);
}
#endif
if (count <= ReservedMaps) {
return 0;
}
return count - ReservedMaps;
}
u64 GetAvailablePhysicalMemory() {
#ifdef _WIN32
MEMORYSTATUSEX memorystatus;
memorystatus.dwLength = sizeof(memorystatus);
if (GlobalMemoryStatusEx(&memorystatus) == 0) {
return 0;
}
return memorystatus.ullAvailPhys;
#elif defined(__linux__)
static constexpr char AvailableKey[] = "MemAvailable:";
if (std::FILE* const file = std::fopen("/proc/meminfo", "re")) {
char line[256];
u64 available = 0;
while (std::fgets(line, sizeof(line), file) != nullptr) {
if (std::strncmp(line, AvailableKey, sizeof(AvailableKey) - 1) != 0) {
continue;
}
available = std::strtoull(line + sizeof(AvailableKey) - 1, nullptr, 10) * 1024;
break;
}
std::fclose(file);
if (available != 0) {
return available;
}
}
struct sysinfo meminfo;
if (sysinfo(&meminfo) != 0) {
return 0;
}
return static_cast<u64>(meminfo.freeram) * static_cast<u64>(meminfo.mem_unit);
#else
return 0;
#endif
}
} // namespace Common
-7
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -21,8 +18,4 @@ struct MemoryInfo {
*/
[[nodiscard]] const MemoryInfo& GetMemInfo();
[[nodiscard]] u64 GetPermissibleMapCount();
[[nodiscard]] u64 GetAvailablePhysicalMemory();
} // namespace Common
-3
View File
@@ -666,9 +666,6 @@ struct Values {
SwitchableSetting<bool> use_asynchronous_shaders{linkage, false, "use_asynchronous_shaders",
Category::RendererHacks};
SwitchableSetting<bool> use_unified_memory{linkage, false, "use_unified_memory",
Category::RendererHacks};
SwitchableSetting<GpuUnswizzleSize> gpu_unswizzle_texture_size{linkage,
GpuUnswizzleSize::Large,
"gpu_unswizzle_texture_size",
+18 -7
View File
@@ -1199,7 +1199,6 @@ else()
endif()
endif()
target_include_directories(core PRIVATE ${OPUS_INCLUDE_DIRS})
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
if (BOOST_NO_HEADERS)
@@ -1261,11 +1260,23 @@ if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64 OR ARCHITECTURE_riscv64 OR ARCHITE
target_link_libraries(core PRIVATE dynarmic::dynarmic)
endif()
if (TARGET OpenSSL::SSL)
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
else()
target_sources(core PRIVATE hle/service/ssl/ssl_backend_none.cpp)
endif()
target_sources(core PRIVATE hle/service/ssl/ssl_backend_openssl.cpp)
target_link_libraries(core PRIVATE OpenSSL::SSL OpenSSL::Crypto)
# TODO
# elseif (APPLE)
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_securetransport.cpp)
# target_link_libraries(core PRIVATE "-framework Security")
# elseif (WIN32)
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_schannel.cpp)
# target_link_libraries(core PRIVATE crypt32 secur32)
# else()
# target_sources(core PRIVATE
# hle/service/ssl/ssl_backend_none.cpp)
# endif()
create_target_directory_groups(core)
+1 -5
View File
@@ -120,7 +120,6 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout = Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
core_timing.SetMulticore(is_multicore);
core_timing.Initialize([&system]() { system.RegisterHostThread(); });
@@ -148,8 +147,7 @@ struct System::Impl {
!device_memory.has_value() ||
is_multicore != Settings::values.use_multi_core.GetValue() ||
extended_memory_layout != (Settings::values.memory_layout_mode.GetValue() !=
Settings::MemoryLayout::Memory_4Gb) ||
unified_memory != Settings::values.use_unified_memory.GetValue();
Settings::MemoryLayout::Memory_4Gb);
if (!must_reinitialize) {
return;
@@ -160,7 +158,6 @@ struct System::Impl {
is_multicore = Settings::values.use_multi_core.GetValue();
extended_memory_layout =
Settings::values.memory_layout_mode.GetValue() != Settings::MemoryLayout::Memory_4Gb;
unified_memory = Settings::values.use_unified_memory.GetValue();
Initialize(system);
}
@@ -538,7 +535,6 @@ struct System::Impl {
std::atomic_bool is_powered_on{};
bool is_multicore : 1 = false;
bool extended_memory_layout : 1 = false;
bool unified_memory : 1 = false;
bool exit_locked : 1 = false;
bool exit_requested : 1 = false;
+1 -13
View File
@@ -1,6 +1,3 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
@@ -15,18 +12,9 @@ constexpr size_t VirtualReserveSize = 1ULL << 38;
constexpr size_t VirtualReserveSize = 1ULL << 39;
#endif
namespace {
size_t ApplicationPoolOffset() {
using Init = Kernel::Board::Nintendo::Nx::KSystemControl::Init;
const size_t dram_size = Init::GetIntendedMemorySize();
const size_t application_pool_size = Init::GetApplicationPoolSize();
return dram_size > application_pool_size ? dram_size - application_pool_size : 0;
}
}
DeviceMemory::DeviceMemory()
: buffer{Kernel::Board::Nintendo::Nx::KSystemControl::Init::GetIntendedMemorySize(),
VirtualReserveSize, ApplicationPoolOffset()} {}
VirtualReserveSize} {}
DeviceMemory::~DeviceMemory() = default;
-35
View File
@@ -20,8 +20,6 @@
#include "common/scratch_buffer.h"
#include "common/sparse_large_vector.h"
struct AHardwareBuffer;
namespace Core {
constexpr size_t DEVICE_PAGEBITS = 12ULL;
@@ -97,34 +95,6 @@ public:
ApplyOpOnPAddr(address, buffer, operation);
}
u8* GetPhysicalBase() noexcept {
return reinterpret_cast<u8*>(physical_base);
}
const u8* GetPhysicalBase() const noexcept {
return reinterpret_cast<const u8*>(physical_base);
}
size_t GetPhysicalSize() const noexcept {
return physical_size;
}
std::span<AHardwareBuffer* const> GetBackingHardwareBuffers() const noexcept {
return ahb_windows;
}
size_t GetBackingHardwareBufferWindowSize() const noexcept {
return ahb_window_size;
}
size_t GetBackingHardwareBufferBase() const noexcept {
return ahb_base;
}
bool IsBackingShared() const noexcept {
return backing_is_shared;
}
PAddr GetPhysicalRawAddressFromDAddr(DAddr address) const {
PAddr subbits = PAddr(address & page_mask);
auto paddr = tracked_entries[(address >> page_bits)].compressed_physical_ptr;
@@ -201,11 +171,6 @@ private:
std::unique_ptr<DeviceMemoryManagerAllocator<Traits>> impl;
const uintptr_t physical_base;
const size_t physical_size;
const std::span<AHardwareBuffer* const> ahb_windows;
const size_t ahb_window_size;
const size_t ahb_base;
const bool backing_is_shared;
DeviceInterface* device_inter;
struct TrackedEntry {
-5
View File
@@ -171,11 +171,6 @@ struct DeviceMemoryManagerAllocator {
template <typename Traits>
DeviceMemoryManager<Traits>::DeviceMemoryManager(const DeviceMemory& device_memory_)
: physical_base{uintptr_t(device_memory_.buffer.BackingBasePointer())}
, physical_size{device_memory_.buffer.BackingSize()}
, ahb_windows{device_memory_.buffer.BackingHardwareBuffers()}
, ahb_window_size{device_memory_.buffer.BackingHardwareBufferWindowSize()}
, ahb_base{device_memory_.buffer.BackingHardwareBufferBase()}
, backing_is_shared{device_memory_.buffer.IsBackingShared()}
, device_inter{nullptr}
, compressed_device_addr(1ULL << ((Settings::values.memory_layout_mode.GetValue() == Settings::MemoryLayout::Memory_4Gb ? physical_min_bits : physical_max_bits) - Memory::YUZU_PAGEBITS))
, tracked_entries(device_as_size >> Memory::YUZU_PAGEBITS)
-3
View File
@@ -42,7 +42,6 @@ public:
{22, &IUser::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IUser::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IUser::RecreateApplicationArea, "RecreateApplicationArea"},
{25, &IUser::StartDetection, "StartDetectionWithFilter"},
};
// clang-format on
@@ -75,7 +74,6 @@ public:
{20, &ISystem::GetDeviceState, "GetDeviceState"},
{21, &ISystem::GetNpadId, "GetNpadId"},
{23, &ISystem::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{25, &ISystem::StartDetection, "StartDetectionWithFilter"},
{100, &ISystem::Format, "Format"},
{101, &ISystem::GetAdminInfo, "GetAdminInfo"},
{102, &ISystem::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
@@ -120,7 +118,6 @@ public:
{22, &IDebug::GetApplicationAreaSize, "GetApplicationAreaSize"},
{23, &IDebug::AttachAvailabilityChangeEvent, "AttachAvailabilityChangeEvent"},
{24, &IDebug::RecreateApplicationArea, "RecreateApplicationArea"},
{25, &IDebug::StartDetection, "StartDetectionWithFilter"},
{100, &IDebug::Format, "Format"},
{101, &IDebug::GetAdminInfo, "GetAdminInfo"},
{102, &IDebug::GetRegisterInfoPrivate, "GetRegisterInfoPrivate"},
@@ -476,7 +476,6 @@ IApplicationManagerInterface::IApplicationManagerInterface(Core::System& system_
{4096, nullptr, "Unknown4096"}, //20.0.0+
{4097, nullptr, "Unknown4097"}, //20.0.0+
{4099, nullptr, "Unknown4099"}, //21.0.0+
{4105, D<&IApplicationManagerInterface::Unknown4105>, "Unknown4105"}, //23.0.0+
{5000, nullptr, "Unknown5000"}, //18.0.0+
{5001, nullptr, "Unknown5001"}, //18.0.0+
{9999, nullptr, "GetApplicationCertificate"}, //10.0.0-10.2.0
@@ -867,11 +866,6 @@ Result IApplicationManagerInterface::Unknown4053() {
R_SUCCEED();
}
Result IApplicationManagerInterface::Unknown4105() {
LOG_WARNING(Service_NS, "(STUBBED) called.");
R_SUCCEED();
}
void IApplicationManagerInterface::PushApplicationRecord(HLERequestContext& ctx) {
const auto record = ctx.ReadBuffer();
u64 application_id{};
@@ -72,7 +72,6 @@ public:
Result Unknown4022(OutCopyHandle<Kernel::KReadableEvent> out_event);
Result Unknown4023(Out<u64> out_result);
Result Unknown4053();
Result Unknown4105();
Result RequestDownloadApplicationControlDataInBackground(u64 control_source,
u64 application_id);
@@ -142,7 +142,7 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
{10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"},
{13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"},
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
{23, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"}, //23.0.0+
{23, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
};
// clang-format on
@@ -36,7 +36,7 @@ ITransferTaskListController::ITransferTaskListController(Core::System& system_)
{18, nullptr, "ListTransferTaskInfo"},
{19, nullptr, "DeleteTransferTask"},
{20, nullptr, "RaiseTransferTaskPriority"},
{21, D<&ITransferTaskListController::GetTransferTaskProgress>, "GetTransferTaskProgress"}, //10.1.0+
{21, nullptr, "GetTransferTaskProgress"},
{22, nullptr, "GetTransferTaskLastResult"},
{23, nullptr, "SuspendTransferTask"},
{24, D<&ITransferTaskListController::GetCurrentTransferTaskInfo>, "GetCurrentTransferTaskInfo"},
@@ -82,11 +82,6 @@ Result ITransferTaskListController::GetCurrentTransferTaskInfo(Out<std::array<u8
R_SUCCEED();
}
Result ITransferTaskListController::GetTransferTaskProgress() {
LOG_WARNING(Service_OLSC, "(STUBBED) called.");
R_SUCCEED();
}
Result ITransferTaskListController::FindTransferTaskInfo(Out<std::array<u8, 0x30>> out_info,
InBuffer<BufferAttr_HipcAutoSelect> in) {
LOG_WARNING(Service_OLSC, "(STUBBED) called, in_size={}", in.size());
@@ -24,7 +24,6 @@ private:
Result GetTransferTaskStartEventNativeHandleHolder(Out<SharedPointer<INativeHandleHolder>> out_holder);
Result StopNextTransferTaskExecution(Out<SharedPointer<IStopperObject>> out_stopper);
Result GetTransferTaskCount(Out<u32> out_count, u8 unknown);
Result GetTransferTaskProgress();
Result GetCurrentTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, u8 unknown);
Result FindTransferTaskInfo(Out<std::array<u8, 0x30>> out_info, InBuffer<BufferAttr_HipcAutoSelect> in);
};
@@ -0,0 +1,563 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
#include "common/error.h"
#include "common/fs/file.h"
#include "common/hex_util.h"
#include "common/string_util.h"
#include "core/hle/service/ssl/ssl_backend.h"
#include "core/internal_network/network.h"
#include "core/internal_network/sockets.h"
namespace {
// These includes are inside the namespace to avoid a conflict on MinGW where
// the headers define an enum containing Network and Service as enumerators
// (which clash with the correspondingly named namespaces).
#define SECURITY_WIN32
#include <schnlsp.h>
#include <security.h>
#include <wincrypt.h>
std::once_flag one_time_init_flag;
bool one_time_init_success = false;
SCHANNEL_CRED schannel_cred{};
CredHandle cred_handle;
static void OneTimeInit() {
schannel_cred.dwVersion = SCHANNEL_CRED_VERSION;
schannel_cred.dwFlags =
SCH_USE_STRONG_CRYPTO | // don't allow insecure protocols
SCH_CRED_NO_SERVERNAME_CHECK | // don't validate server names
SCH_CRED_NO_DEFAULT_CREDS; // don't automatically present a client certificate
// ^ I'm assuming that nobody would want to connect Yuzu to a
// service that requires some OS-provided corporate client
// certificate, and presenting one to some arbitrary server
// might be a privacy concern? Who knows, though.
const SECURITY_STATUS ret =
AcquireCredentialsHandle(nullptr, const_cast<LPTSTR>(UNISP_NAME), SECPKG_CRED_OUTBOUND,
nullptr, &schannel_cred, nullptr, nullptr, &cred_handle, nullptr);
if (ret != SEC_E_OK) {
// SECURITY_STATUS codes are a type of HRESULT and can be used with NativeErrorToString.
LOG_ERROR(Service_SSL, "AcquireCredentialsHandle failed: {}",
Common::NativeErrorToString(ret));
return;
}
if (getenv("SSLKEYLOGFILE")) {
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but Schannel does not support exporting "
"keys; not logging keys!");
// Not fatal.
}
one_time_init_success = true;
}
} // namespace
namespace Service::SSL {
class SSLConnectionBackendSchannel final : public SSLConnectionBackend {
public:
Result Init() {
std::call_once(one_time_init_flag, OneTimeInit);
if (!one_time_init_success) {
LOG_ERROR(
Service_SSL,
"Can't create SSL connection because Schannel one-time initialization failed");
return ResultInternalError;
}
return ResultSuccess;
}
void SetSocket(std::shared_ptr<Network::SocketBase> socket_in) override {
socket = std::move(socket_in);
}
Result SetHostName(const std::string& hostname_in) override {
hostname = hostname_in;
return ResultSuccess;
}
void SetVerifyOption(u32 option) override {
skip_cert_verification = (option == 0);
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
skip_cert_verification);
}
Result DoHandshake() override {
while (1) {
Result r;
switch (handshake_state) {
case HandshakeState::Initial:
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
(r = CallInitializeSecurityContext()) != ResultSuccess) {
return r;
}
// CallInitializeSecurityContext updated `handshake_state`.
continue;
case HandshakeState::ContinueNeeded:
case HandshakeState::IncompleteMessage:
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess ||
(r = FillCiphertextReadBuf()) != ResultSuccess) {
return r;
}
if (ciphertext_read_buf.empty()) {
LOG_ERROR(Service_SSL, "SSL handshake failed because server hung up");
return ResultInternalError;
}
if ((r = CallInitializeSecurityContext()) != ResultSuccess) {
return r;
}
// CallInitializeSecurityContext updated `handshake_state`.
continue;
case HandshakeState::DoneAfterFlush:
if ((r = FlushCiphertextWriteBuf()) != ResultSuccess) {
return r;
}
handshake_state = HandshakeState::Connected;
return ResultSuccess;
case HandshakeState::Connected:
LOG_ERROR(Service_SSL, "Called DoHandshake but we already handshook");
return ResultInternalError;
case HandshakeState::Error:
return ResultInternalError;
}
}
}
Result FillCiphertextReadBuf() {
const size_t fill_size = read_buf_fill_size ? read_buf_fill_size : 4096;
read_buf_fill_size = 0;
// This unnecessarily zeroes the buffer; oh well.
const size_t offset = ciphertext_read_buf.size();
ASSERT_OR_EXECUTE(offset + fill_size >= offset, { return ResultInternalError; });
ciphertext_read_buf.resize(offset + fill_size, 0);
const auto read_span = std::span(ciphertext_read_buf).subspan(offset, fill_size);
const auto [actual, err] = socket->Recv(0, read_span);
switch (err) {
case Network::Errno::SUCCESS:
ASSERT(static_cast<size_t>(actual) <= fill_size);
ciphertext_read_buf.resize(offset + actual);
return ResultSuccess;
case Network::Errno::AGAIN:
ciphertext_read_buf.resize(offset);
return ResultWouldBlock;
default:
ciphertext_read_buf.resize(offset);
LOG_ERROR(Service_SSL, "Socket recv returned Network::Errno {}", err);
return ResultInternalError;
}
}
// Returns success if the write buffer has been completely emptied.
Result FlushCiphertextWriteBuf() {
while (!ciphertext_write_buf.empty()) {
const auto [actual, err] = socket->Send(ciphertext_write_buf, 0);
switch (err) {
case Network::Errno::SUCCESS:
ASSERT(static_cast<size_t>(actual) <= ciphertext_write_buf.size());
ciphertext_write_buf.erase(ciphertext_write_buf.begin(),
ciphertext_write_buf.begin() + actual);
break;
case Network::Errno::AGAIN:
return ResultWouldBlock;
default:
LOG_ERROR(Service_SSL, "Socket send returned Network::Errno {}", err);
return ResultInternalError;
}
}
return ResultSuccess;
}
Result CallInitializeSecurityContext() {
unsigned long req = ISC_REQ_ALLOCATE_MEMORY | ISC_REQ_CONFIDENTIALITY |
ISC_REQ_INTEGRITY | ISC_REQ_REPLAY_DETECT |
ISC_REQ_SEQUENCE_DETECT | ISC_REQ_STREAM |
ISC_REQ_USE_SUPPLIED_CREDS;
if (skip_cert_verification) {
req |= ISC_REQ_MANUAL_CRED_VALIDATION;
}
unsigned long attr;
// https://learn.microsoft.com/en-us/windows/win32/secauthn/initializesecuritycontext--schannel
std::array<SecBuffer, 2> input_buffers{{
// only used if `initial_call_done`
{
// [0]
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
.BufferType = SECBUFFER_TOKEN,
.pvBuffer = ciphertext_read_buf.data(),
},
{
// [1] (will be replaced by SECBUFFER_MISSING when SEC_E_INCOMPLETE_MESSAGE is
// returned, or SECBUFFER_EXTRA when SEC_E_CONTINUE_NEEDED is returned if the
// whole buffer wasn't used)
.cbBuffer = 0,
.BufferType = SECBUFFER_EMPTY,
.pvBuffer = nullptr,
},
}};
std::array<SecBuffer, 2> output_buffers{{
{
.cbBuffer = 0,
.BufferType = SECBUFFER_TOKEN,
.pvBuffer = nullptr,
}, // [0]
{
.cbBuffer = 0,
.BufferType = SECBUFFER_ALERT,
.pvBuffer = nullptr,
}, // [1]
}};
SecBufferDesc input_desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(input_buffers.size()),
.pBuffers = input_buffers.data(),
};
SecBufferDesc output_desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(output_buffers.size()),
.pBuffers = output_buffers.data(),
};
ASSERT_OR_EXECUTE_MSG(
input_buffers[0].cbBuffer == ciphertext_read_buf.size(),
{ return ResultInternalError; }, "read buffer too large");
bool initial_call_done = handshake_state != HandshakeState::Initial;
if (initial_call_done) {
LOG_DEBUG(Service_SSL, "Passing {} bytes into InitializeSecurityContext",
ciphertext_read_buf.size());
}
char* hostname_ptr = hostname ? const_cast<char*>(hostname->c_str()) : nullptr;
const SECURITY_STATUS ret = InitializeSecurityContextA(
&cred_handle, initial_call_done ? &ctxt : nullptr, hostname_ptr, req,
0, // Reserved1
0, // TargetDataRep not used with Schannel
initial_call_done ? &input_desc : nullptr,
0, // Reserved2
initial_call_done ? nullptr : &ctxt, &output_desc, &attr,
nullptr); // ptsExpiry
if (output_buffers[0].pvBuffer) {
const std::span span(static_cast<u8*>(output_buffers[0].pvBuffer),
output_buffers[0].cbBuffer);
ciphertext_write_buf.insert(ciphertext_write_buf.end(), span.begin(), span.end());
FreeContextBuffer(output_buffers[0].pvBuffer);
}
if (output_buffers[1].pvBuffer) {
const std::span span(static_cast<u8*>(output_buffers[1].pvBuffer),
output_buffers[1].cbBuffer);
// The documentation doesn't explain what format this data is in.
LOG_DEBUG(Service_SSL, "Got a {}-byte alert buffer: {}", span.size(),
Common::HexToString(span));
}
switch (ret) {
case SEC_I_CONTINUE_NEEDED:
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_I_CONTINUE_NEEDED");
if (input_buffers[1].BufferType == SECBUFFER_EXTRA) {
LOG_DEBUG(Service_SSL, "EXTRA of size {}", input_buffers[1].cbBuffer);
ASSERT(input_buffers[1].cbBuffer <= ciphertext_read_buf.size());
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
ciphertext_read_buf.end() - input_buffers[1].cbBuffer);
} else {
ASSERT(input_buffers[1].BufferType == SECBUFFER_EMPTY);
ciphertext_read_buf.clear();
}
handshake_state = HandshakeState::ContinueNeeded;
return ResultSuccess;
case SEC_E_INCOMPLETE_MESSAGE:
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_INCOMPLETE_MESSAGE");
ASSERT(input_buffers[1].BufferType == SECBUFFER_MISSING);
read_buf_fill_size = input_buffers[1].cbBuffer;
handshake_state = HandshakeState::IncompleteMessage;
return ResultSuccess;
case SEC_E_OK:
LOG_DEBUG(Service_SSL, "InitializeSecurityContext => SEC_E_OK");
ciphertext_read_buf.clear();
handshake_state = HandshakeState::DoneAfterFlush;
return GrabStreamSizes();
default:
LOG_ERROR(Service_SSL,
"InitializeSecurityContext failed (probably certificate/protocol issue): {}",
Common::NativeErrorToString(ret));
handshake_state = HandshakeState::Error;
return ResultInternalError;
}
}
Result GrabStreamSizes() {
const SECURITY_STATUS ret =
QueryContextAttributes(&ctxt, SECPKG_ATTR_STREAM_SIZES, &stream_sizes);
if (ret != SEC_E_OK) {
LOG_ERROR(Service_SSL, "QueryContextAttributes(SECPKG_ATTR_STREAM_SIZES) failed: {}",
Common::NativeErrorToString(ret));
handshake_state = HandshakeState::Error;
return ResultInternalError;
}
return ResultSuccess;
}
Result Read(size_t* out_size, std::span<u8> data) override {
*out_size = 0;
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Read but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0 || got_read_eof) {
return ResultSuccess;
}
while (1) {
if (!cleartext_read_buf.empty()) {
*out_size = (std::min)(cleartext_read_buf.size(), data.size());
std::memcpy(data.data(), cleartext_read_buf.data(), *out_size);
cleartext_read_buf.erase(cleartext_read_buf.begin(),
cleartext_read_buf.begin() + *out_size);
return ResultSuccess;
}
if (!ciphertext_read_buf.empty()) {
SecBuffer empty{
.cbBuffer = 0,
.BufferType = SECBUFFER_EMPTY,
.pvBuffer = nullptr,
};
std::array<SecBuffer, 5> buffers{{
{
.cbBuffer = static_cast<unsigned long>(ciphertext_read_buf.size()),
.BufferType = SECBUFFER_DATA,
.pvBuffer = ciphertext_read_buf.data(),
},
empty,
empty,
empty,
}};
ASSERT_OR_EXECUTE_MSG(
buffers[0].cbBuffer == ciphertext_read_buf.size(),
{ return ResultInternalError; }, "read buffer too large");
SecBufferDesc desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(buffers.size()),
.pBuffers = buffers.data(),
};
SECURITY_STATUS ret =
DecryptMessage(&ctxt, &desc, /*MessageSeqNo*/ 0, /*pfQOP*/ nullptr);
switch (ret) {
case SEC_E_OK:
ASSERT_OR_EXECUTE(buffers[0].BufferType == SECBUFFER_STREAM_HEADER,
{ return ResultInternalError; });
ASSERT_OR_EXECUTE(buffers[1].BufferType == SECBUFFER_DATA,
{ return ResultInternalError; });
ASSERT_OR_EXECUTE(buffers[2].BufferType == SECBUFFER_STREAM_TRAILER,
{ return ResultInternalError; });
cleartext_read_buf.assign(static_cast<u8*>(buffers[1].pvBuffer),
static_cast<u8*>(buffers[1].pvBuffer) +
buffers[1].cbBuffer);
if (buffers[3].BufferType == SECBUFFER_EXTRA) {
ASSERT(buffers[3].cbBuffer <= ciphertext_read_buf.size());
ciphertext_read_buf.erase(ciphertext_read_buf.begin(),
ciphertext_read_buf.end() - buffers[3].cbBuffer);
} else {
ASSERT(buffers[3].BufferType == SECBUFFER_EMPTY);
ciphertext_read_buf.clear();
}
continue;
case SEC_E_INCOMPLETE_MESSAGE:
break;
case SEC_I_CONTEXT_EXPIRED:
// Server hung up by sending close_notify.
got_read_eof = true;
*out_size = 0;
return ResultSuccess;
default:
LOG_ERROR(Service_SSL, "DecryptMessage failed: {}",
Common::NativeErrorToString(ret));
return ResultInternalError;
}
}
const Result r = FillCiphertextReadBuf();
if (r != ResultSuccess) {
return r;
}
if (ciphertext_read_buf.empty()) {
got_read_eof = true;
*out_size = 0;
return ResultSuccess;
}
}
}
Result Write(size_t* out_size, std::span<const u8> data) override {
*out_size = 0;
if (handshake_state != HandshakeState::Connected) {
LOG_ERROR(Service_SSL, "Called Write but we did not successfully handshake");
return ResultInternalError;
}
if (data.size() == 0) {
return ResultSuccess;
}
data = data.subspan(0, std::min<size_t>(data.size(), stream_sizes.cbMaximumMessage));
if (!cleartext_write_buf.empty()) {
// Already in the middle of a write. It wouldn't make sense to not
// finish sending the entire buffer since TLS has
// header/MAC/padding/etc.
if (data.size() != cleartext_write_buf.size() ||
std::memcmp(data.data(), cleartext_write_buf.data(), data.size())) {
LOG_ERROR(Service_SSL, "Called Write but buffer does not match previous buffer");
return ResultInternalError;
}
return WriteAlreadyEncryptedData(out_size);
} else {
cleartext_write_buf.assign(data.begin(), data.end());
}
std::vector<u8> header_buf(stream_sizes.cbHeader, 0);
std::vector<u8> tmp_data_buf = cleartext_write_buf;
std::vector<u8> trailer_buf(stream_sizes.cbTrailer, 0);
std::array<SecBuffer, 3> buffers{{
{
.cbBuffer = stream_sizes.cbHeader,
.BufferType = SECBUFFER_STREAM_HEADER,
.pvBuffer = header_buf.data(),
},
{
.cbBuffer = static_cast<unsigned long>(tmp_data_buf.size()),
.BufferType = SECBUFFER_DATA,
.pvBuffer = tmp_data_buf.data(),
},
{
.cbBuffer = stream_sizes.cbTrailer,
.BufferType = SECBUFFER_STREAM_TRAILER,
.pvBuffer = trailer_buf.data(),
},
}};
ASSERT_OR_EXECUTE_MSG(
buffers[1].cbBuffer == tmp_data_buf.size(), { return ResultInternalError; },
"temp buffer too large");
SecBufferDesc desc{
.ulVersion = SECBUFFER_VERSION,
.cBuffers = static_cast<unsigned long>(buffers.size()),
.pBuffers = buffers.data(),
};
const SECURITY_STATUS ret = EncryptMessage(&ctxt, /*fQOP*/ 0, &desc, /*MessageSeqNo*/ 0);
if (ret != SEC_E_OK) {
LOG_ERROR(Service_SSL, "EncryptMessage failed: {}", Common::NativeErrorToString(ret));
return ResultInternalError;
}
ciphertext_write_buf.insert(ciphertext_write_buf.end(), header_buf.begin(),
header_buf.end());
ciphertext_write_buf.insert(ciphertext_write_buf.end(), tmp_data_buf.begin(),
tmp_data_buf.end());
ciphertext_write_buf.insert(ciphertext_write_buf.end(), trailer_buf.begin(),
trailer_buf.end());
return WriteAlreadyEncryptedData(out_size);
}
Result WriteAlreadyEncryptedData(size_t* out_size) {
const Result r = FlushCiphertextWriteBuf();
if (r != ResultSuccess) {
return r;
}
// write buf is empty
*out_size = cleartext_write_buf.size();
cleartext_write_buf.clear();
return ResultSuccess;
}
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
PCCERT_CONTEXT returned_cert = nullptr;
const SECURITY_STATUS ret =
QueryContextAttributes(&ctxt, SECPKG_ATTR_REMOTE_CERT_CONTEXT, &returned_cert);
if (ret != SEC_E_OK) {
LOG_ERROR(Service_SSL,
"QueryContextAttributes(SECPKG_ATTR_REMOTE_CERT_CONTEXT) failed: {}",
Common::NativeErrorToString(ret));
return ResultInternalError;
}
PCCERT_CONTEXT some_cert = nullptr;
while ((some_cert = CertEnumCertificatesInStore(returned_cert->hCertStore, some_cert)) !=
nullptr) {
out_certs->emplace_back(static_cast<u8*>(some_cert->pbCertEncoded),
static_cast<u8*>(some_cert->pbCertEncoded) +
some_cert->cbCertEncoded);
}
std::reverse(out_certs->begin(),
out_certs->end()); // Windows returns certs in reverse order from what we want
CertFreeCertificateContext(returned_cert);
return ResultSuccess;
}
~SSLConnectionBackendSchannel() {
if (handshake_state != HandshakeState::Initial) {
DeleteSecurityContext(&ctxt);
}
}
enum class HandshakeState {
// Haven't called anything yet.
Initial,
// `SEC_I_CONTINUE_NEEDED` was returned by
// `InitializeSecurityContext`; must finish sending data (if any) in
// the write buffer, then read at least one byte before calling
// `InitializeSecurityContext` again.
ContinueNeeded,
// `SEC_E_INCOMPLETE_MESSAGE` was returned by
// `InitializeSecurityContext`; hopefully the write buffer is empty;
// must read at least one byte before calling
// `InitializeSecurityContext` again.
IncompleteMessage,
// `SEC_E_OK` was returned by `InitializeSecurityContext`; must
// finish sending data in the write buffer before having `DoHandshake`
// report success.
DoneAfterFlush,
// We finished the above and are now connected. At this point, writing
// and reading are separate 'state machines' represented by the
// nonemptiness of the ciphertext and cleartext read and write buffers.
Connected,
// Another error was returned and we shouldn't allow initialization
// to continue.
Error,
} handshake_state = HandshakeState::Initial;
CtxtHandle ctxt;
SecPkgContext_StreamSizes stream_sizes;
std::shared_ptr<Network::SocketBase> socket;
std::optional<std::string> hostname;
std::vector<u8> ciphertext_read_buf;
std::vector<u8> ciphertext_write_buf;
std::vector<u8> cleartext_read_buf;
std::vector<u8> cleartext_write_buf;
bool got_read_eof = false;
bool skip_cert_verification = false;
size_t read_buf_fill_size = 0;
};
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSchannel>();
R_TRY(conn->Init());
*out_backend = std::move(conn);
return ResultSuccess;
}
} // namespace Service::SSL
@@ -0,0 +1,236 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <mutex>
// SecureTransport has been deprecated in its entirety in favor of
// Network.framework, but that does not allow layering TLS on top of an
// arbitrary socket.
#if defined(__GNUC__) || defined(__clang__)
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include <Security/SecureTransport.h>
#pragma GCC diagnostic pop
#endif
#include "core/hle/service/ssl/ssl_backend.h"
#include "core/internal_network/network.h"
#include "core/internal_network/sockets.h"
namespace {
template <typename T>
struct CFReleaser {
T ptr;
YUZU_NON_COPYABLE(CFReleaser);
constexpr CFReleaser() : ptr(nullptr) {}
constexpr CFReleaser(T ptr) : ptr(ptr) {}
constexpr operator T() {
return ptr;
}
~CFReleaser() {
if (ptr) {
CFRelease(ptr);
}
}
};
std::string CFStringToString(CFStringRef cfstr) {
CFReleaser<CFDataRef> cfdata(
CFStringCreateExternalRepresentation(nullptr, cfstr, kCFStringEncodingUTF8, 0));
ASSERT_OR_EXECUTE(cfdata, { return "???"; });
return std::string(reinterpret_cast<const char*>(CFDataGetBytePtr(cfdata)),
CFDataGetLength(cfdata));
}
std::string OSStatusToString(OSStatus status) {
CFReleaser<CFStringRef> cfstr(SecCopyErrorMessageString(status, nullptr));
if (!cfstr) {
return "[unknown error]";
}
return CFStringToString(cfstr);
}
} // namespace
namespace Service::SSL {
class SSLConnectionBackendSecureTransport final : public SSLConnectionBackend {
public:
Result Init() {
static std::once_flag once_flag;
std::call_once(once_flag, []() {
if (getenv("SSLKEYLOGFILE")) {
LOG_CRITICAL(Service_SSL, "SSLKEYLOGFILE was set but SecureTransport does not "
"support exporting keys; not logging keys!");
// Not fatal.
}
});
context.ptr = SSLCreateContext(nullptr, kSSLClientSide, kSSLStreamType);
if (!context) {
LOG_ERROR(Service_SSL, "SSLCreateContext failed");
return ResultInternalError;
}
OSStatus status;
if ((status = SSLSetIOFuncs(context, ReadCallback, WriteCallback)) ||
(status = SSLSetConnection(context, this))) {
LOG_ERROR(Service_SSL, "SSLContext initialization failed: {}",
OSStatusToString(status));
return ResultInternalError;
}
return ResultSuccess;
}
void SetSocket(std::shared_ptr<Network::SocketBase> in_socket) override {
socket = std::move(in_socket);
}
Result SetHostName(const std::string& hostname) override {
OSStatus status = SSLSetPeerDomainName(context, hostname.c_str(), hostname.size());
if (status) {
LOG_ERROR(Service_SSL, "SSLSetPeerDomainName failed: {}", OSStatusToString(status));
return ResultInternalError;
}
return ResultSuccess;
}
void SetVerifyOption(u32 option) override {
skip_cert_verification = (option == 0);
LOG_WARNING(Service_SSL, "option={} skip_verification={}", option,
skip_cert_verification);
if (skip_cert_verification) {
SSLSetSessionOption(context, kSSLSessionOptionBreakOnServerAuth, true);
}
}
Result DoHandshake() override {
OSStatus status = SSLHandshake(context);
if (skip_cert_verification && status == errSSLServerAuthCompleted) {
LOG_DEBUG(Service_SSL, "Skipping certificate verification as requested");
status = SSLHandshake(context);
}
return HandleReturn("SSLHandshake", 0, status);
}
Result Read(size_t* out_size, std::span<u8> data) override {
OSStatus status = SSLRead(context, data.data(), data.size(), out_size);
return HandleReturn("SSLRead", out_size, status);
}
Result Write(size_t* out_size, std::span<const u8> data) override {
OSStatus status = SSLWrite(context, data.data(), data.size(), out_size);
return HandleReturn("SSLWrite", out_size, status);
}
Result HandleReturn(const char* what, size_t* actual, OSStatus status) {
switch (status) {
case 0:
return ResultSuccess;
case errSSLWouldBlock:
return ResultWouldBlock;
default: {
std::string reason;
if (got_read_eof) {
reason = "server hung up";
} else {
reason = OSStatusToString(status);
}
LOG_ERROR(Service_SSL, "{} failed: {}", what, reason);
return ResultInternalError;
}
}
}
Result GetServerCerts(std::vector<std::vector<u8>>* out_certs) override {
CFReleaser<SecTrustRef> trust;
OSStatus status = SSLCopyPeerTrust(context, &trust.ptr);
if (status) {
LOG_ERROR(Service_SSL, "SSLCopyPeerTrust failed: {}", OSStatusToString(status));
return ResultInternalError;
}
for (CFIndex i = 0, count = SecTrustGetCertificateCount(trust); i < count; i++) {
SecCertificateRef cert = SecTrustGetCertificateAtIndex(trust, i);
CFReleaser<CFDataRef> data(SecCertificateCopyData(cert));
ASSERT_OR_EXECUTE(data, { return ResultInternalError; });
const u8* ptr = CFDataGetBytePtr(data);
out_certs->emplace_back(ptr, ptr + CFDataGetLength(data));
}
return ResultSuccess;
}
static OSStatus ReadCallback(SSLConnectionRef connection, void* data, size_t* dataLength) {
return ReadOrWriteCallback(connection, data, dataLength, true);
}
static OSStatus WriteCallback(SSLConnectionRef connection, const void* data,
size_t* dataLength) {
return ReadOrWriteCallback(connection, const_cast<void*>(data), dataLength, false);
}
static OSStatus ReadOrWriteCallback(SSLConnectionRef connection, void* data, size_t* dataLength,
bool is_read) {
auto self =
static_cast<SSLConnectionBackendSecureTransport*>(const_cast<void*>(connection));
ASSERT_OR_EXECUTE_MSG(
self->socket, { return 0; }, "SecureTransport asked to {} but we have no socket",
is_read ? "read" : "write");
// SecureTransport callbacks (unlike OpenSSL BIO callbacks) are
// expected to read/write the full requested dataLength or return an
// error, so we have to add a loop ourselves.
size_t requested_len = *dataLength;
size_t offset = 0;
while (offset < requested_len) {
std::span cur(reinterpret_cast<u8*>(data) + offset, requested_len - offset);
auto [actual, err] = is_read ? self->socket->Recv(0, cur) : self->socket->Send(cur, 0);
LOG_CRITICAL(Service_SSL, "op={}, offset={} actual={}/{} err={}", is_read, offset,
actual, cur.size(), static_cast<s32>(err));
switch (err) {
case Network::Errno::SUCCESS:
offset += actual;
if (actual == 0) {
ASSERT(is_read);
self->got_read_eof = true;
return errSecEndOfData;
}
break;
case Network::Errno::AGAIN:
*dataLength = offset;
return errSSLWouldBlock;
default:
LOG_ERROR(Service_SSL, "Socket {} returned Network::Errno {}",
is_read ? "recv" : "send", err);
return errSecIO;
}
}
ASSERT(offset == requested_len);
return 0;
}
private:
CFReleaser<SSLContextRef> context = nullptr;
bool got_read_eof = false;
bool skip_cert_verification = false;
std::shared_ptr<Network::SocketBase> socket;
};
Result CreateSSLConnectionBackend(std::unique_ptr<SSLConnectionBackend>* out_backend) {
auto conn = std::make_unique<SSLConnectionBackendSecureTransport>();
R_TRY(conn->Init());
*out_backend = std::move(conn);
return ResultSuccess;
}
} // namespace Service::SSL
@@ -228,8 +228,6 @@ std::unique_ptr<TranslationMap> InitializeTranslations(QObject* parent) {
tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly."));
INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"),
tr("May reduce shader stutter."));
INSERT(Settings, use_unified_memory, tr("Enable unified memory access"),
tr("Lets the GPU write buffer readbacks directly into guest memory."));
INSERT(Settings, gpu_clock, tr("GPU Clocks"),
tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering "
"resolution and render distance to fit the Switch's clocks."));
@@ -13,7 +13,6 @@
namespace Shader::Backend::SPIRV {
namespace {
Id SharedPointer(EmitContext& ctx, Id offset, u32 index_offset = 0) {
offset = ctx.BoundSharedOffset(offset, 4 + index_offset * 4);
const Id shift_id{ctx.Const(2U)};
Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
if (index_offset > 0) {
@@ -161,8 +160,7 @@ Id EmitSharedAtomicExchange32(EmitContext& ctx, Id offset, Id value) {
Id EmitSharedAtomicExchange64(EmitContext& ctx, Id offset, Id value) {
if (ctx.profile.support_shared_int64_atomics && ctx.uses_explicit_workgroup_layout) {
const Id shift_id{ctx.Const(3U)};
const Id index{
ctx.OpShiftRightArithmetic(ctx.U32[1], ctx.BoundSharedOffset(offset, 8), shift_id)};
const Id index{ctx.OpShiftRightArithmetic(ctx.U32[1], offset, shift_id)};
const Id pointer{
ctx.OpAccessChain(ctx.shared_u64, ctx.shared_memory_u64, ctx.u32_zero_value, index)};
const auto [scope, semantics]{AtomicArgs(ctx)};
@@ -31,7 +31,6 @@ std::pair<Id, Id> ExtractArgs(EmitContext& ctx, Id offset, u32 mask, u32 count)
} // Anonymous namespace
Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -43,7 +42,6 @@ Id EmitLoadSharedU8(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -55,7 +53,6 @@ Id EmitLoadSharedS8(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpUConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
@@ -66,7 +63,6 @@ Id EmitLoadSharedU16(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
return ctx.OpSConvert(ctx.U32[1], ctx.OpLoad(ctx.U16, pointer));
@@ -77,7 +73,6 @@ Id EmitLoadSharedS16(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 4);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2)};
return ctx.OpLoad(ctx.U32[1], pointer);
@@ -87,7 +82,6 @@ Id EmitLoadSharedU32(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 8);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
return ctx.OpLoad(ctx.U32[2], pointer);
@@ -103,7 +97,6 @@ Id EmitLoadSharedU64(EmitContext& ctx, Id offset) {
}
Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
offset = ctx.BoundSharedOffset(offset, 16);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
return ctx.OpLoad(ctx.U32[4], pointer);
@@ -120,7 +113,6 @@ Id EmitLoadSharedU128(EmitContext& ctx, Id offset) {
}
void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 1);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{
ctx.OpAccessChain(ctx.shared_u8, ctx.shared_memory_u8, ctx.u32_zero_value, offset)};
@@ -131,7 +123,6 @@ void EmitWriteSharedU8(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 2);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u16, ctx.shared_memory_u16, offset, 1)};
ctx.OpStore(pointer, ctx.OpUConvert(ctx.U16, value));
@@ -141,7 +132,6 @@ void EmitWriteSharedU16(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 4);
Id pointer{};
if (ctx.uses_explicit_workgroup_layout) {
pointer = Pointer(ctx, ctx.shared_u32, ctx.shared_memory_u32, offset, 2);
@@ -154,7 +144,6 @@ void EmitWriteSharedU32(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 8);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x2, ctx.shared_memory_u32x2, offset, 3)};
ctx.OpStore(pointer, value);
@@ -170,7 +159,6 @@ void EmitWriteSharedU64(EmitContext& ctx, Id offset, Id value) {
}
void EmitWriteSharedU128(EmitContext& ctx, Id offset, Id value) {
offset = ctx.BoundSharedOffset(offset, 16);
if (ctx.uses_explicit_workgroup_layout) {
const Id pointer{Pointer(ctx, ctx.shared_u32x4, ctx.shared_memory_u32x4, offset, 4)};
ctx.OpStore(pointer, value);
@@ -600,16 +600,6 @@ void EmitContext::DefineLocalMemory(const IR::Program& program) {
}
}
Id EmitContext::BoundSharedOffset(Id offset, u32 access_bytes) {
if (shared_memory_declared_bytes == 0) {
return offset;
}
const u32 last_valid{shared_memory_declared_bytes > access_bytes
? shared_memory_declared_bytes - access_bytes
: 0U};
return OpUMin(U32[1], offset, Const(last_valid));
}
void EmitContext::DefineSharedMemory(const IR::Program& program) {
uses_explicit_workgroup_layout =
profile.support_explicit_workgroup_layout &&
@@ -618,15 +608,8 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
if (program.shared_memory_size == 0) {
return;
}
const u32 device_limit{profile.max_shared_memory_size};
const u32 shared_memory_size{device_limit != 0 && program.shared_memory_size > device_limit
? device_limit
: program.shared_memory_size};
if (shared_memory_size != program.shared_memory_size) {
shared_memory_declared_bytes = shared_memory_size;
}
const auto make{[&](Id element_type, u32 element_size) {
const u32 num_elements{Common::DivCeil(shared_memory_size, element_size)};
const u32 num_elements{Common::DivCeil(program.shared_memory_size, element_size)};
const Id array_type{TypeArray(element_type, Const(num_elements))};
Decorate(array_type, spv::Decoration::ArrayStride, element_size);
@@ -661,7 +644,7 @@ void EmitContext::DefineSharedMemory(const IR::Program& program) {
std::tie(shared_memory_u32x4, shared_u32x4, std::ignore) = make(U32[4], 16);
return;
}
const u32 num_elements{Common::DivCeil(shared_memory_size, 4U)};
const u32 num_elements{Common::DivCeil(program.shared_memory_size, 4U)};
const Id type{TypeArray(U32[1], Const(num_elements))};
shared_memory_u32_type = TypePointer(spv::StorageClass::Workgroup, type);
@@ -312,8 +312,6 @@ public:
Id local_memory{};
bool uses_explicit_workgroup_layout{};
u32 shared_memory_declared_bytes{};
[[nodiscard]] Id BoundSharedOffset(Id offset, u32 access_bytes);
Id shared_memory_u8{};
Id shared_memory_u16{};
Id shared_memory_u32{};
-3
View File
@@ -105,9 +105,6 @@ struct Profile {
u32 gl_max_compute_smem_size{};
/// Largest workgroup shared memory allocation the device accepts, 0 when unconstrained
u32 max_shared_memory_size{};
/// Maxwell and earlier nVidia architectures have broken robust support
bool has_broken_robust{};
-2
View File
@@ -414,9 +414,7 @@ endif()
if (YUZU_USE_EXTERNAL_FFMPEG)
add_dependencies(video_core ffmpeg-build)
endif()
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
+39 -600
View File
@@ -78,11 +78,6 @@ void BufferCache<P>::TickFrame() {
return;
}
runtime.TickFrame(slot_buffers);
if constexpr (USE_UNIFIED_MEMORY) {
if (!unified_written_ranges.Empty() && runtime.KnownGpuTick() >= unified_write_tick) {
unified_written_ranges.Clear();
}
}
// Calculate hits and shots and move hit bits to the right
const u32 hits = std::reduce(channel_state->uniform_cache_hits.begin(),
@@ -570,8 +565,7 @@ void BufferCache<P>::FlushCachedWrites() {
template <class P>
bool BufferCache<P>::HasUncommittedFlushes() const noexcept {
return !uncommitted_gpu_modified_ranges.Empty() || !committed_gpu_modified_ranges.empty() ||
uncommitted_unified_writes;
return !uncommitted_gpu_modified_ranges.Empty() || !committed_gpu_modified_ranges.empty();
}
template <class P>
@@ -584,22 +578,14 @@ void BufferCache<P>::AccumulateFlushes() {
template <class P>
bool BufferCache<P>::ShouldWaitAsyncFlushes() const noexcept {
if (async_buffers.empty()) {
return false;
}
return async_buffers.front().has_value() ||
!pending_downloads.front().unified_copies.empty() ||
pending_downloads.front().unified_writes;
return (!async_buffers.empty() && async_buffers.front().has_value());
}
template <class P>
void BufferCache<P>::CommitAsyncFlushesHigh() {
AccumulateFlushes();
const bool unified_writes = uncommitted_unified_writes;
uncommitted_unified_writes = false;
if (committed_gpu_modified_ranges.empty()) {
pending_downloads.emplace_back(AsyncDownloadBatch{.unified_writes = unified_writes});
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
@@ -659,85 +645,27 @@ void BufferCache<P>::CommitAsyncFlushesHigh() {
}
committed_gpu_modified_ranges.clear();
if (downloads.empty()) {
pending_downloads.emplace_back(AsyncDownloadBatch{.unified_writes = unified_writes});
async_buffers.emplace_back(std::optional<Async_Buffer>{});
return;
}
struct QueuedUnifiedCopy {
u64 window;
BufferId buffer_id;
boost::container::small_vector<BufferCopy, 16> copies;
};
AsyncDownloadBatch batch;
boost::container::small_vector<std::pair<BufferCopy, BufferId>, 16> staging_downloads;
boost::container::small_vector<QueuedUnifiedCopy, 4> unified_copy_queue;
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
u64 staging_size_bytes = 0;
for (auto& [copy, buffer_id] : downloads) {
Buffer& buffer = slot_buffers[buffer_id];
const DAddr orig_device_addr = buffer.CpuAddr() + copy.src_offset;
bool unified = false;
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory()) {
window_ids.clear();
groups.clear();
unified = ResolveUnifiedWindows(orig_device_addr, copy.src_offset, copy.size,
window_ids, groups);
}
}
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(orig_device_addr);
if (unified) {
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
for (size_t i = 0; i < window_ids.size(); ++i) {
unified_copy_queue.push_back(
QueuedUnifiedCopy{window_ids[i], buffer_id, std::move(groups[i])});
}
batch.unified_copies.push_back(record);
continue;
}
copy.dst_offset = staging_size_bytes;
constexpr u64 align = 64ULL;
staging_size_bytes += (copy.size + align - 1) & ~(align - 1ULL);
staging_downloads.push_back({copy, buffer_id});
}
std::optional<Async_Buffer> download_staging;
if (!staging_downloads.empty()) {
download_staging = runtime.DownloadStagingBuffer(staging_size_bytes, true);
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes, true);
boost::container::small_vector<BufferCopy, 4> normalized_copies;
runtime.PreCopyBarrier();
for (auto& [copy, buffer_id] : staging_downloads) {
copy.dst_offset += download_staging->offset;
for (auto& [copy, buffer_id] : downloads) {
copy.dst_offset += download_staging.offset;
const std::array copies{copy};
BufferCopy second_copy{copy};
Buffer& buffer = slot_buffers[buffer_id];
BufferCopy record{copy};
record.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(record.src_offset);
second_copy.src_offset = static_cast<size_t>(buffer.CpuAddr()) + copy.src_offset;
const DAddr orig_device_addr = static_cast<DAddr>(second_copy.src_offset);
async_downloads.Add(orig_device_addr, copy.size);
buffer.MarkUsage(copy.src_offset, copy.size);
runtime.CopyBuffer(download_staging->buffer, buffer, copies, false);
batch.staging_copies.push_back(record);
}
if constexpr (USE_UNIFIED_MEMORY) {
for (const auto& queued : unified_copy_queue) {
const std::span<const BufferCopy> group_span(queued.copies.data(),
queued.copies.size());
runtime.CopyToUnifiedMemory(queued.window, slot_buffers[queued.buffer_id], group_span);
}
if (!unified_copy_queue.empty()) {
runtime.FlushUnifiedMemoryCopies();
runtime.UnifiedMemoryHostBarrier();
}
runtime.CopyBuffer(download_staging.buffer, buffer, copies, false);
normalized_copies.push_back(second_copy);
}
runtime.PostCopyBarrier();
batch.unified_writes = unified_writes;
pending_downloads.emplace_back(std::move(batch));
async_buffers.emplace_back(std::move(download_staging));
pending_downloads.emplace_back(std::move(normalized_copies));
async_buffers.emplace_back(download_staging);
}
template <class P>
@@ -752,49 +680,32 @@ void BufferCache<P>::PopAsyncFlushes() {
template <class P>
void BufferCache<P>::PopAsyncBuffers() {
struct Writeback {
DAddr addr;
const u8* src;
u64 size;
};
boost::container::small_vector<Writeback, 8> writebacks;
{
std::scoped_lock lock{mutex};
if (async_buffers.empty()) {
return;
}
auto& batch = pending_downloads.front();
if (!async_buffers.front().has_value()) {
async_buffers.pop_front();
return;
}
auto& downloads = pending_downloads.front();
auto& async_buffer = async_buffers.front();
if (async_buffer.has_value()) {
const u8* base = async_buffer->mapped_span.data();
u8* base = async_buffer->mapped_span.data();
const size_t base_offset = async_buffer->offset;
for (const auto& copy : batch.staging_copies) {
for (const auto& copy : downloads) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
const u64 dst_offset = copy.dst_offset - base_offset;
const u8* read_mapped_memory = base + dst_offset;
async_downloads.ForEachInRange(
device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
writebacks.push_back(
{start, &read_mapped_memory[start - device_addr], end - start});
async_downloads.ForEachInRange(device_addr, copy.size, [&](DAddr start, DAddr end, s32) {
device_memory.WriteBlockUnsafe(start, &read_mapped_memory[start - device_addr],
end - start);
});
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers_death_ring.emplace_back(*async_buffer);
}
for (const auto& copy : batch.unified_copies) {
const DAddr device_addr = static_cast<DAddr>(copy.src_offset);
async_downloads.Subtract(device_addr, copy.size, [&](DAddr start, DAddr end) {
gpu_modified_ranges.Subtract(start, end - start);
});
}
async_buffers.pop_front();
pending_downloads.pop_front();
}
for (const auto& wb : writebacks) {
device_memory.WriteBlockUnsafe(wb.addr, wb.src, wb.size);
}
}
template <class P>
@@ -838,9 +749,6 @@ void BufferCache<P>::BindHostIndexBuffer() {
const u32 size = channel_state->index_buffer.size;
const auto& draw_state = maxwell3d->draw_manager.draw_state;
if (draw_state.inline_index_draw_indexes.empty()) {
if (BindVirtualIndexBuffer()) {
return;
}
SynchronizeBuffer(buffer, channel_state->index_buffer.device_addr, size);
} else {
if constexpr (USE_MEMORY_MAPS_FOR_UPLOADS) {
@@ -894,7 +802,6 @@ void BufferCache<P>::UpdateVertexBufferSlot(u32 index, const Binding& binding) {
enabled_vertex_buffers_mask |= (1u << index);
} else {
enabled_vertex_buffers_mask &= ~(1u << index);
virtual_vertex_buffers_mask &= ~(1u << index);
}
}
@@ -927,16 +834,8 @@ void BufferCache<P>::BindHostVertexBuffers() {
const Binding& binding = VertexBufferSlot(index);
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
if (StageVirtualVertexBuffer(index, binding, false)) {
continue;
}
bool needs_bind = flags[Dirty::VertexBuffer0 + index];
if (((virtual_vertex_buffers_mask >> index) & 1) != 0) {
virtual_vertex_buffers_mask &= ~(1u << index);
needs_bind = true;
}
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
if (!needs_bind) {
if (!flags[Dirty::VertexBuffer0 + index]) {
flush_bindings();
continue;
}
@@ -957,7 +856,6 @@ void BufferCache<P>::BindHostVertexBuffers() {
last_index = index;
}
flush_bindings();
BindStagedVertexBuffers();
} else {
HostBindings<typename P::Buffer> host_bindings;
bool any_valid{false};
@@ -966,17 +864,8 @@ void BufferCache<P>::BindHostVertexBuffers() {
const Binding& binding = channel_state->vertex_buffers[index];
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
if (((enabled_vertex_buffers_mask >> index) & 1) != 0 &&
StageVirtualVertexBuffer(index, binding, true)) {
continue;
}
bool needs_bind = flags[Dirty::VertexBuffer0 + index];
if (((virtual_vertex_buffers_mask >> index) & 1) != 0) {
virtual_vertex_buffers_mask &= ~(1u << index);
needs_bind = true;
}
SynchronizeBuffer(buffer, binding.device_addr, binding.size);
if (!needs_bind) {
if (!flags[Dirty::VertexBuffer0 + index]) {
continue;
}
flags[Dirty::VertexBuffer0 + index] = false;
@@ -1005,7 +894,6 @@ void BufferCache<P>::BindHostVertexBuffers() {
}
runtime.BindVertexBuffers(host_bindings);
}
BindStagedVertexBuffers();
}
}
@@ -1061,12 +949,8 @@ void BufferCache<P>::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32
}();
const bool use_fast_buffer = needs_alignment_stream
|| (has_host_buffer && size <= channel_state->uniform_buffer_skip_cache_size
&& !memory_tracker.IsRegionGpuModified(device_addr, size)
&& !HasPendingUnifiedWrites(device_addr, size));
&& !memory_tracker.IsRegionGpuModified(device_addr, size));
if (use_fast_buffer) {
if (needs_alignment_stream) {
WaitForUnifiedWrites(device_addr, size);
}
if constexpr (IS_OPENGL) {
if (runtime.HasFastBufferSubData()) {
// Fast path for Nvidia
@@ -1162,34 +1046,6 @@ void BufferCache<P>::ResolveMultiRangeStorage(Binding& binding, bool is_written,
template <class P>
bool BufferCache<P>::BindMultiRangeStorage(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool) {
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
const u64 key = GeometryKey(binding.gpu_addr, 0);
if (!PushMultiRangeSources(binding, is_written, pool, key)) {
return false;
}
return runtime.BindMultiRangeStorageBuffer(key, is_written);
} else {
return false;
}
}
template <class P>
u64 BufferCache<P>::GeometryKey(GPUVAddr gpu_addr, u64 salt) const {
return ((static_cast<u64>(gpu_memory->GetID()) << 48) ^ gpu_addr) ^ salt;
}
template <class P>
u32 BufferCache<P>::ClampToMappedRange(GPUVAddr gpu_addr, u32 size) const {
const size_t mapped = gpu_memory->MaxMappedRange(gpu_addr, size);
if (mapped == 0 || mapped >= size) {
return size;
}
return static_cast<u32>(mapped);
}
template <class P>
bool BufferCache<P>::PushMultiRangeSources(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool, u64 key) {
if constexpr (requires { runtime.BindMultiRangeStorageBuffer(u64{}, bool{}); }) {
if (binding.segment_count < 2) {
return false;
@@ -1197,273 +1053,23 @@ bool BufferCache<P>::PushMultiRangeSources(const Binding& binding, bool is_writt
if (binding.segment_first + binding.segment_count > pool.size()) {
return false;
}
const std::span<const MultiRangeSegment> segments =
pool.subspan(binding.segment_first, binding.segment_count);
boost::container::small_vector<BufferId, 8> buffer_ids;
for (const MultiRangeSegment& segment : segments) {
BufferId buffer_id = segment.buffer_id;
if (!buffer_id) {
buffer_id = page_table[segment.device_addr >> CACHING_PAGEBITS];
}
if (!buffer_id ||
!slot_buffers[buffer_id].IsInBounds(segment.device_addr, segment.size)) {
return false;
}
buffer_ids.push_back(buffer_id);
}
const u64 key = (static_cast<u64>(gpu_memory->GetID()) << 48) ^ binding.gpu_addr;
runtime.ResetMultiRange();
for (u32 index = 0; index < binding.segment_count; ++index) {
const MultiRangeSegment& segment = segments[index];
const BufferId buffer_id = buffer_ids[index];
Buffer& buffer = slot_buffers[buffer_id];
TouchBuffer(buffer, buffer_id);
if (!SynchronizeBuffer(buffer, segment.device_addr, segment.size)) {
const MultiRangeSegment& segment = pool[binding.segment_first + index];
Buffer& buffer = slot_buffers[segment.buffer_id];
TouchBuffer(buffer, segment.buffer_id);
if (SynchronizeBuffer(buffer, segment.device_addr, segment.size)) {
runtime.InvalidateMultiRange(key);
}
const u32 offset = buffer.Offset(segment.device_addr);
buffer.MarkUsage(offset, segment.size);
if (is_written) {
MarkWrittenBuffer(buffer_id, segment.device_addr, segment.size);
MarkWrittenBuffer(segment.buffer_id, segment.device_addr, segment.size);
}
runtime.PushMultiRangeSource(buffer, offset, segment.size);
}
return true;
} else {
return false;
}
}
template <class P>
bool BufferCache<P>::TryResolveUnifiedSegments(
[[maybe_unused]] const Binding& binding,
[[maybe_unused]] std::span<const MultiRangeSegment> pool,
[[maybe_unused]] UnifiedExtents& extents) {
if constexpr (USE_UNIFIED_MEMORY) {
extents.clear();
const auto push = [&](DAddr device_addr, u64 size) {
const std::optional<u64> relative = TryResolveUnifiedRange(device_addr, size);
if (!relative) {
return false;
}
if (!extents.empty() && extents.back().relative + extents.back().size == *relative) {
extents.back().size += size;
return true;
}
extents.push_back(UnifiedExtent{.relative = *relative, .size = size});
return true;
};
if (binding.segment_count < 2) {
return push(binding.device_addr, binding.size);
}
if (binding.segment_first + binding.segment_count > pool.size()) {
return false;
}
for (const MultiRangeSegment& segment :
pool.subspan(binding.segment_first, binding.segment_count)) {
if (!push(segment.device_addr, segment.size)) {
return false;
}
}
return true;
} else {
return false;
}
}
template <class P>
void BufferCache<P>::ResolveGeometrySegments([[maybe_unused]] bool is_indexed) {
if constexpr (requires { runtime.BindStagedVertexBuffers(); }) {
const auto& draw_state = maxwell3d->draw_manager.draw_state;
if (is_indexed && draw_state.inline_index_draw_indexes.empty()) {
ResolveMultiRangeStorage(channel_state->index_buffer, false, graphics_segments);
}
u32 enabled_mask = enabled_vertex_buffers_mask;
while (enabled_mask != 0) {
const u32 index = std::countr_zero(enabled_mask);
enabled_mask &= enabled_mask - 1;
Binding& slot = VertexBufferSlot(index);
ResolveMultiRangeStorage(slot, false, graphics_segments);
Binding& channel_binding = channel_state->vertex_buffers[index];
channel_binding.segment_first = slot.segment_first;
channel_binding.segment_count = slot.segment_count;
}
}
}
template <class P>
bool BufferCache<P>::StageVirtualVertexBuffer([[maybe_unused]] u32 index,
[[maybe_unused]] const Binding& binding,
[[maybe_unused]] bool force) {
if constexpr (requires { runtime.BindStagedVertexBuffers(); }) {
auto& flags = maxwell3d->dirty.flags;
const bool rebind = force || flags[Dirty::VertexBuffer0 + index] ||
((virtual_vertex_buffers_mask >> index) & 1) == 0;
const u32 stride = maxwell3d->regs.vertex_streams[index].stride;
const u64 salt = VERTEX_GEOMETRY_SALT + (u64{index} << 40);
bool staged = false;
if constexpr (USE_UNIFIED_MEMORY) {
UnifiedExtents extents;
if (TryResolveUnifiedSegments(binding, graphics_segments, extents)) {
const u64 view_key = GeometryKey(binding.gpu_addr, salt + VIEW_GEOMETRY_SALT);
staged = runtime.StageUnifiedVertexBuffer(index, view_key, extents, binding.size,
stride, rebind);
}
}
if (!staged) {
const u64 key = GeometryKey(binding.gpu_addr, salt);
if (PushMultiRangeSources(binding, false, graphics_segments, key)) {
staged = runtime.StageMultiRangeVertexBuffer(index, key, binding.size, stride,
rebind);
}
}
if (!staged) {
return false;
}
flags[Dirty::VertexBuffer0 + index] = false;
virtual_vertex_buffers_mask |= 1u << index;
return true;
} else {
return false;
}
}
template <class P>
bool BufferCache<P>::BindVirtualIndexBuffer() {
if constexpr (!HAS_FULL_INDEX_AND_PRIMITIVE_SUPPORT &&
requires { runtime.BindStagedVertexBuffers(); }) {
const Binding& binding = channel_state->index_buffer;
const auto& draw_state = maxwell3d->draw_manager.draw_state;
const auto& index_ref = draw_state.index_buffer;
if constexpr (USE_UNIFIED_MEMORY) {
UnifiedExtents extents;
const u64 view_key =
GeometryKey(binding.gpu_addr, INDEX_GEOMETRY_SALT + VIEW_GEOMETRY_SALT);
if (TryResolveUnifiedSegments(binding, graphics_segments, extents) &&
runtime.BindUnifiedIndexBuffer(draw_state.topology, index_ref.format,
index_ref.first, index_ref.count, view_key,
extents, binding.size)) {
return true;
}
}
const u64 key = GeometryKey(binding.gpu_addr, INDEX_GEOMETRY_SALT);
if (!PushMultiRangeSources(binding, false, graphics_segments, key)) {
return false;
}
return runtime.BindMultiRangeIndexBuffer(draw_state.topology, index_ref.format,
index_ref.first, index_ref.count, key,
binding.size);
} else {
return false;
}
}
template <class P>
void BufferCache<P>::BindStagedVertexBuffers() {
if constexpr (requires { runtime.BindStagedVertexBuffers(); }) {
runtime.BindStagedVertexBuffers();
}
}
template <class P>
bool BufferCache<P>::HasPendingUnifiedWrites([[maybe_unused]] DAddr device_addr,
[[maybe_unused]] u64 size) {
if constexpr (USE_UNIFIED_MEMORY) {
if (unified_written_ranges.Empty()) {
return false;
}
if (runtime.KnownGpuTick() >= unified_write_tick) {
unified_written_ranges.Clear();
return false;
}
bool overlaps = false;
unified_written_ranges.ForEachInRange(device_addr, size,
[&overlaps](DAddr, DAddr) { overlaps = true; });
return overlaps;
} else {
return false;
}
}
template <class P>
void BufferCache<P>::WaitForUnifiedWrites([[maybe_unused]] DAddr device_addr,
[[maybe_unused]] u64 size) {
if constexpr (USE_UNIFIED_MEMORY) {
if (!HasPendingUnifiedWrites(device_addr, size)) {
return;
}
runtime.Wait(unified_write_tick);
unified_written_ranges.Clear();
}
}
template <class P>
template <typename Func>
bool BufferCache<P>::CopyUnifiedWrites([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] DAddr device_addr,
[[maybe_unused]] u64 size,
[[maybe_unused]] Func&& add_upload) {
if constexpr (USE_UNIFIED_MEMORY) {
if (!HasPendingUnifiedWrites(device_addr, size)) {
return false;
}
boost::container::small_vector<std::pair<DAddr, DAddr>, 4> overlaps;
unified_written_ranges.ForEachInRange(device_addr, size,
[&overlaps](DAddr start, DAddr end) {
overlaps.emplace_back(start, end);
});
const DAddr buffer_start = buffer.CpuAddr();
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
for (const auto& [start, end] : overlaps) {
if (!ResolveUnifiedWindows(start, start - buffer_start, end - start, window_ids,
groups)) {
runtime.Wait(unified_write_tick);
unified_written_ranges.Clear();
return false;
}
}
for (size_t i = 0; i < window_ids.size(); ++i) {
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
runtime.CopyFromUnifiedMemory(window_ids[i], buffer, group_span);
}
DAddr cursor = device_addr;
for (const auto& [start, end] : overlaps) {
buffer.MarkUsage(start - buffer_start, end - start);
if (start > cursor) {
add_upload(cursor, start - cursor);
}
cursor = end;
}
if (cursor < device_addr + size) {
add_upload(cursor, device_addr + size - cursor);
}
return true;
} else {
return false;
}
}
template <class P>
bool BufferCache<P>::BindUnifiedStorage([[maybe_unused]] const Binding& binding,
[[maybe_unused]] bool is_written) {
if constexpr (USE_UNIFIED_MEMORY) {
const auto relative = TryResolveUnifiedRange(binding.device_addr, binding.size);
if (!relative) {
return false;
}
const auto range = runtime.ResolveUnifiedStorage(*relative, binding.size);
if (!range) {
return false;
}
if (is_written) {
memory_tracker.MarkRegionAsCpuModified(binding.device_addr, binding.size);
unified_written_ranges.Add(binding.device_addr, binding.size);
unified_write_tick = runtime.CurrentTick();
uncommitted_unified_writes = true;
}
runtime.BindStorageBuffer(range->buffer, range->address, range->offset,
binding.size, is_written);
return true;
return runtime.BindMultiRangeStorageBuffer(key, is_written);
} else {
return false;
}
@@ -1480,11 +1086,7 @@ void BufferCache<P>::BindHostGraphicsStorageBuffers(size_t stage) {
}
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
if (BindUnifiedStorage(binding, is_written)) {
return;
}
const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.device_addr, size);
const u32 offset = buffer.Offset(binding.device_addr);
@@ -1593,7 +1195,6 @@ void BufferCache<P>::BindHostComputeUniformBuffers() {
}();
if constexpr (!IS_OPENGL) {
if (needs_alignment_stream) {
WaitForUnifiedWrites(binding.device_addr, size);
const std::span<u8> span =
runtime.BindMappedUniformBuffer(0, binding_index, size);
device_memory.ReadBlockUnsafe(binding.device_addr, span.data(), size);
@@ -1625,9 +1226,6 @@ void BufferCache<P>::BindHostComputeStorageBuffers() {
}
Buffer& buffer = slot_buffers[binding.buffer_id];
TouchBuffer(buffer, binding.buffer_id);
if (BindUnifiedStorage(binding, is_written)) {
return;
}
const u32 size = binding.size;
SynchronizeBuffer(buffer, binding.device_addr, size);
@@ -1684,7 +1282,6 @@ void BufferCache<P>::DoUpdateGraphicsBuffers(bool is_indexed) {
UpdateIndexBuffer();
}
UpdateVertexBuffers();
ResolveGeometrySegments(is_indexed);
UpdateTransformFeedbackBuffers();
for (size_t stage = 0; stage < NUM_STAGES; ++stage) {
UpdateUniformBuffers(stage);
@@ -1741,17 +1338,13 @@ void BufferCache<P>::UpdateIndexBuffer() {
const std::optional<DAddr> device_addr = gpu_memory->GpuToCpuAddress(gpu_addr_begin);
const u32 address_size = static_cast<u32>(gpu_addr_end - gpu_addr_begin);
const u32 draw_size = (index_buffer_ref.count + index_buffer_ref.first) * u32(index_buffer_ref.FormatSizeInBytes());
u32 size = (std::min)(address_size, draw_size);
const u32 size = (std::min)(address_size, draw_size);
if (size == 0 || !device_addr) {
channel_state->index_buffer = NULL_BINDING;
return;
}
if constexpr (requires { runtime.BindStagedVertexBuffers(); }) {
size = ClampToMappedRange(gpu_addr_begin, size);
}
channel_state->index_buffer = Binding{
.device_addr = *device_addr,
.gpu_addr = gpu_addr_begin,
.size = size,
.buffer_id = FindBuffer(*device_addr, size, false),
};
@@ -1789,13 +1382,10 @@ void BufferCache<P>::UpdateVertexBuffer(u32 index) {
}
if (!gpu_memory->IsWithinGPUAddressRange(gpu_addr_end) || size >= 64_MiB) {
size = static_cast<u32>(gpu_memory->MaxContinuousRange(gpu_addr_begin, size));
} else if constexpr (requires { runtime.BindStagedVertexBuffers(); }) {
size = ClampToMappedRange(gpu_addr_begin, size);
}
const BufferId buffer_id = FindBuffer(*device_addr, size, false);
const Binding binding{
.device_addr = *device_addr,
.gpu_addr = gpu_addr_begin,
.size = size,
.buffer_id = buffer_id,
};
@@ -2152,28 +1742,16 @@ bool BufferCache<P>::SynchronizeBuffer(Buffer& buffer, DAddr device_addr, u32 si
u64 total_size_bytes = 0;
u64 largest_copy = 0;
const DAddr buffer_start = buffer.cpu_addr_cached;
const auto add_upload = [&](u64 upload_addr, u64 upload_size) {
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
upload_copies.push_back(BufferCopy{
.src_offset = total_size_bytes,
.dst_offset = upload_addr - buffer_start,
.size = upload_size,
.dst_offset = device_addr_out - buffer_start,
.size = range_size,
});
total_size_bytes += upload_size;
largest_copy = (std::max)(largest_copy, upload_size);
};
bool copied_from_windows = false;
memory_tracker.ForEachUploadRange(device_addr, size, [&](u64 device_addr_out, u64 range_size) {
if (CopyUnifiedWrites(buffer, device_addr_out, range_size, add_upload)) {
copied_from_windows = true;
return;
}
add_upload(device_addr_out, range_size);
total_size_bytes += range_size;
largest_copy = (std::max)(largest_copy, range_size);
});
if (total_size_bytes == 0) {
if (copied_from_windows) {
any_buffer_uploaded = true;
return false;
}
return true;
}
const std::span<BufferCopy> copies_span(upload_copies.data(), upload_copies.size());
@@ -2221,130 +1799,6 @@ void BufferCache<P>::ImmediateUploadMemory([[maybe_unused]] Buffer& buffer,
}
}
template <class P>
bool BufferCache<P>::ResolveUnifiedWindows(
[[maybe_unused]] DAddr device_addr, [[maybe_unused]] u64 buffer_offset,
[[maybe_unused]] u64 size, [[maybe_unused]] boost::container::small_vector<u64, 4>& window_ids,
[[maybe_unused]] UnifiedWindowGroups& groups) {
if constexpr (USE_UNIFIED_MEMORY) {
const u8* const physical_base = device_memory.GetPhysicalBase();
const u64 unified_base = runtime.UnifiedMemoryBase();
const u64 unified_size = runtime.UnifiedMemorySize();
const u64 window_size = runtime.UnifiedMemoryWindowSize();
if (window_size == 0) {
return false;
}
const auto group_for = [&](u64 window) -> boost::container::small_vector<BufferCopy, 16>& {
for (size_t i = 0; i < window_ids.size(); ++i) {
if (window_ids[i] == window) {
return groups[i];
}
}
window_ids.push_back(window);
groups.emplace_back();
return groups.back();
};
u64 downloaded = 0;
while (downloaded < size) {
const DAddr page_addr = device_addr + downloaded;
const u8* const ptr = device_memory.GetPointer<u8>(page_addr);
if (ptr == nullptr) {
return false;
}
const u64 page_offset = page_addr & Core::DEVICE_PAGEMASK;
u64 chunk = (std::min)(size - downloaded,
static_cast<u64>(Core::DEVICE_PAGESIZE) - page_offset);
const u64 phys_offset = static_cast<u64>(ptr - physical_base);
if (phys_offset < unified_base || phys_offset - unified_base + chunk > unified_size) {
return false;
}
const u64 relative = phys_offset - unified_base;
const u64 window = relative / window_size;
const u64 local_offset = relative % window_size;
chunk = (std::min)(chunk, window_size - local_offset);
auto& group = group_for(window);
if (!group.empty()) {
BufferCopy& last = group.back();
if (last.src_offset + last.size == buffer_offset + downloaded &&
last.dst_offset + last.size == local_offset) {
last.size += chunk;
downloaded += chunk;
continue;
}
}
group.push_back(BufferCopy{
.src_offset = buffer_offset + downloaded,
.dst_offset = local_offset,
.size = chunk,
});
downloaded += chunk;
}
return true;
} else {
return false;
}
}
template <class P>
std::optional<u64> BufferCache<P>::TryResolveUnifiedRange([[maybe_unused]] DAddr device_addr,
[[maybe_unused]] u64 size) {
if constexpr (USE_UNIFIED_MEMORY) {
if (size == 0 || !runtime.IsUnifiedMemoryBindable()) {
return std::nullopt;
}
const u8* const first = device_memory.GetSpan(device_addr, size);
if (first == nullptr) {
return std::nullopt;
}
const u64 phys_offset = static_cast<u64>(first - device_memory.GetPhysicalBase());
const u64 unified_base = runtime.UnifiedMemoryBase();
if (phys_offset < unified_base) {
return std::nullopt;
}
const u64 relative = phys_offset - unified_base;
const u64 unified_size = runtime.UnifiedMemorySize();
if (relative >= unified_size || unified_size - relative < size) {
return std::nullopt;
}
if (memory_tracker.IsRegionGpuModified(device_addr, size) ||
IsRegionGpuModified(device_addr, size)) {
return std::nullopt;
}
return relative;
} else {
return std::nullopt;
}
}
template <class P>
bool BufferCache<P>::TryUnifiedDownloadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] std::span<BufferCopy> copies) {
if constexpr (USE_UNIFIED_MEMORY) {
boost::container::small_vector<u64, 4> window_ids;
UnifiedWindowGroups groups;
for (const BufferCopy& copy : copies) {
if (!ResolveUnifiedWindows(buffer.CpuAddr() + copy.src_offset, copy.src_offset,
copy.size, window_ids, groups)) {
return false;
}
}
for (const BufferCopy& copy : copies) {
buffer.MarkUsage(copy.src_offset, copy.size);
}
runtime.PreCopyBarrier();
for (size_t i = 0; i < window_ids.size(); ++i) {
const std::span<const BufferCopy> group_span(groups[i].data(), groups[i].size());
runtime.CopyToUnifiedMemory(window_ids[i], buffer, group_span);
}
runtime.FlushUnifiedMemoryCopies();
runtime.UnifiedMemoryHostBarrier();
runtime.Finish();
return true;
} else {
return false;
}
}
template <class P>
void BufferCache<P>::MappedUploadMemory([[maybe_unused]] Buffer& buffer,
[[maybe_unused]] u64 total_size_bytes,
@@ -2448,12 +1902,6 @@ void BufferCache<P>::DownloadBufferMemory(Buffer& buffer, DAddr device_addr, u64
}
if constexpr (USE_MEMORY_MAPS) {
if constexpr (USE_UNIFIED_MEMORY) {
if (runtime.HasUnifiedMemory() &&
TryUnifiedDownloadMemory(buffer, std::span(copies.data(), copies.size()))) {
return;
}
}
auto download_staging = runtime.DownloadStagingBuffer(total_size_bytes);
const u8* const mapped_memory = download_staging.mapped_span.data();
const std::span<BufferCopy> copies_span(copies.data(), copies.data() + copies.size());
@@ -2515,15 +1963,6 @@ void BufferCache<P>::DeleteBuffer(BufferId buffer_id, bool do_not_mark) {
replace(channel_state->transform_feedback_buffers);
replace(channel_state->compute_uniform_buffers);
replace(channel_state->compute_storage_buffers);
const auto drop_segments = [buffer_id](std::vector<MultiRangeSegment>& pool) {
for (MultiRangeSegment& segment : pool) {
if (segment.buffer_id == buffer_id) {
segment.buffer_id = BufferId{};
}
}
};
drop_segments(graphics_segments);
drop_segments(compute_segments);
// Mark the whole buffer as CPU written to stop tracking CPU writes
if (!do_not_mark) {
@@ -12,7 +12,6 @@
#include <functional>
#include <memory>
#include <mutex>
#include <optional>
#include <numeric>
#include <span>
#include <vector>
@@ -96,11 +95,6 @@ struct MultiRangeSegment {
u32 size{};
};
struct UnifiedExtent {
u64 relative{};
u64 size{};
};
struct TextureBufferBinding : Binding {
PixelFormat format;
};
@@ -196,10 +190,6 @@ class BufferCache : public VideoCommon::ChannelSetupCaches<BufferCacheChannelInf
static constexpr bool USE_MEMORY_MAPS = P::USE_MEMORY_MAPS;
static constexpr bool SEPARATE_IMAGE_BUFFERS_BINDINGS = P::SEPARATE_IMAGE_BUFFER_BINDINGS;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = P::USE_MEMORY_MAPS_FOR_UPLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
static constexpr u64 VERTEX_GEOMETRY_SALT = u64{1} << 47;
static constexpr u64 INDEX_GEOMETRY_SALT = u64{1} << 46;
static constexpr u64 VIEW_GEOMETRY_SALT = u64{1} << 45;
#ifdef YUZU_LEGACY
static constexpr s64 TARGET_THRESHOLD = 3_GiB;
@@ -238,11 +228,6 @@ public:
bool BindMultiRangeStorage(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool);
bool BindUnifiedStorage(const Binding& binding, bool is_written);
bool PushMultiRangeSources(const Binding& binding, bool is_written,
std::span<const MultiRangeSegment> pool, u64 key);
void ResolveMultiRangeStorage(Binding& binding, bool is_written,
std::vector<MultiRangeSegment>& pool);
@@ -477,41 +462,6 @@ private:
void MappedUploadMemory(Buffer& buffer, u64 total_size_bytes, std::span<BufferCopy> copies);
bool TryUnifiedDownloadMemory(Buffer& buffer, std::span<BufferCopy> copies);
std::optional<u64> TryResolveUnifiedRange(DAddr device_addr, u64 size);
using UnifiedExtents = boost::container::small_vector<UnifiedExtent, 8>;
bool TryResolveUnifiedSegments(const Binding& binding, std::span<const MultiRangeSegment> pool,
UnifiedExtents& extents);
void ResolveGeometrySegments(bool is_indexed);
bool StageVirtualVertexBuffer(u32 index, const Binding& binding, bool force);
bool BindVirtualIndexBuffer();
void BindStagedVertexBuffers();
[[nodiscard]] u64 GeometryKey(GPUVAddr gpu_addr, u64 salt) const;
[[nodiscard]] u32 ClampToMappedRange(GPUVAddr gpu_addr, u32 size) const;
bool HasPendingUnifiedWrites(DAddr device_addr, u64 size);
void WaitForUnifiedWrites(DAddr device_addr, u64 size);
template <typename Func>
bool CopyUnifiedWrites(Buffer& buffer, DAddr device_addr, u64 size, Func&& add_upload);
using UnifiedWindowGroups =
boost::container::small_vector<boost::container::small_vector<BufferCopy, 16>, 4>;
bool ResolveUnifiedWindows(DAddr device_addr, u64 buffer_offset, u64 size,
boost::container::small_vector<u64, 4>& window_ids,
UnifiedWindowGroups& groups);
void DownloadBufferMemory(Buffer& buffer_id);
void DownloadBufferMemory(Buffer& buffer_id, DAddr device_addr, u64 size);
@@ -556,7 +506,6 @@ private:
u32 last_index_count = 0;
u32 enabled_vertex_buffers_mask = 0;
u32 virtual_vertex_buffers_mask = 0;
u64 vertex_buffers_serial = 0;
std::array<Binding, 32> v_buffer{};
@@ -566,20 +515,11 @@ private:
Common::RangeSet<DAddr> uncommitted_gpu_modified_ranges;
Common::RangeSet<DAddr> gpu_modified_ranges;
std::deque<Common::RangeSet<DAddr>> committed_gpu_modified_ranges;
Common::RangeSet<DAddr> unified_written_ranges;
u64 unified_write_tick = 0;
bool uncommitted_unified_writes = false;
// Async Buffers
struct AsyncDownloadBatch {
boost::container::small_vector<BufferCopy, 4> staging_copies;
boost::container::small_vector<BufferCopy, 4> unified_copies;
bool unified_writes = false;
};
Common::OverlapRangeSet<DAddr> async_downloads;
std::deque<std::optional<Async_Buffer>> async_buffers;
std::deque<AsyncDownloadBatch> pending_downloads;
std::deque<boost::container::small_vector<BufferCopy, 4>> pending_downloads;
std::optional<Async_Buffer> current_buffer;
std::deque<Async_Buffer> async_buffers_death_ring;
@@ -16,16 +16,13 @@ set(GLSL_INCLUDES
set(SHADER_FILES
${CMAKE_CURRENT_SOURCE_DIR}/astc_decoder.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_float.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_swizzle_2d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_2d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/blit_color_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/blit_depth_stencil_msaa.frag
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_bcn.comp
${CMAKE_CURRENT_SOURCE_DIR}/block_linear_unswizzle_3d_buffer.comp
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d24s8.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_abgr8_to_d32f.frag
${CMAKE_CURRENT_SOURCE_DIR}/convert_d32f_to_abgr8.frag
@@ -1,83 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint layer_stride;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) readonly buffer InputBuffer {
uint in_u32[];
};
layout(binding = BINDING_OUTPUT_BUFFER, std430) buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += pos.z * pc.layer_stride;
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint in_idx = linear_index * words;
uint out_idx = offset >> 2u;
for (uint word = 0u; word < words; ++word) {
out_u32[out_idx + word] = in_u32[in_idx + word];
}
}
@@ -1,104 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#extension GL_EXT_shader_16bit_storage : require
#extension GL_EXT_shader_8bit_storage : require
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint layer_stride;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 16, local_size_y = 8, local_size_z = 1) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += pos.z * pc.layer_stride;
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -1,105 +0,0 @@
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
// SPDX-License-Identifier: GPL-3.0-or-later
#version 430
#define BINDING_INPUT_BUFFER 0
#define BINDING_OUTPUT_BUFFER 1
layout(push_constant) uniform PushConstants {
uvec3 dim;
uint bytes_per_block_log2;
uvec3 origin;
uint slice_size;
uint block_size;
uint x_shift;
uint block_height;
uint block_height_mask;
uint block_depth;
uint block_depth_mask;
} pc;
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU32 { uint u32data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU64 { uvec2 u64data[]; };
layout(binding = BINDING_INPUT_BUFFER, std430) buffer InputBufferU128 { uvec4 u128data[]; };
layout(binding = BINDING_OUTPUT_BUFFER, std430) writeonly buffer OutputBuffer {
uint out_u32[];
};
layout(local_size_x = 8, local_size_y = 8, local_size_z = 4) in;
const uint GOB_SIZE_X = 64;
const uint GOB_SIZE_Y = 8;
const uint GOB_SIZE_X_SHIFT = 6;
const uint GOB_SIZE_Y_SHIFT = 3;
const uint GOB_SIZE_SHIFT = GOB_SIZE_X_SHIFT + GOB_SIZE_Y_SHIFT;
const uvec2 SWIZZLE_MASK = uvec2(GOB_SIZE_X - 1u, GOB_SIZE_Y - 1u);
uint SwizzleTable(uint pos) {
const uint t[8] = uint[](
0x12100200, 0x13110301, 0x16140604, 0x17150705,
0x1a180a08, 0x1b190b09, 0x1e1c0e0c, 0x1f1d0f0d
);
const uint i = pos >> 4;
const uint h = (t[i / 4] >> ((i % 4) * 8)) & 0xff;
return (h << 4) | (pos & 0xf);
}
uint SwizzleOffset(uvec2 pos) {
pos = pos & SWIZZLE_MASK;
return SwizzleTable(pos.y * 64u + pos.x);
}
uvec4 ReadTexel(uint offset) {
switch (pc.bytes_per_block_log2) {
case 2u:
return uvec4(u32data[offset / 4u], 0u, 0u, 0u);
case 3u:
return uvec4(u64data[offset / 8u], 0u, 0u);
case 4u:
return u128data[offset / 16u];
}
return uvec4(0u);
}
void main() {
uvec3 coord = gl_GlobalInvocationID;
if (coord.x >= pc.dim.x || coord.y >= pc.dim.y || coord.z >= pc.dim.z) {
return;
}
uvec3 pos = coord + pc.origin;
pos.x <<= pc.bytes_per_block_log2;
uint swizzle = SwizzleOffset(pos.xy);
uint block_y = pos.y >> GOB_SIZE_Y_SHIFT;
uint offset = 0u;
offset += (pos.z >> pc.block_depth) * pc.slice_size;
offset += (pos.z & pc.block_depth_mask) << (GOB_SIZE_SHIFT + pc.block_height);
offset += (block_y >> pc.block_height) * pc.block_size;
offset += (block_y & pc.block_height_mask) << GOB_SIZE_SHIFT;
offset += (pos.x >> GOB_SIZE_X_SHIFT) << pc.x_shift;
offset += swizzle;
uvec4 texel = ReadTexel(offset);
uint words = 1u << (pc.bytes_per_block_log2 - 2u);
uint linear_index = coord.x + coord.y * pc.dim.x + coord.z * pc.dim.x * pc.dim.y;
uint out_idx = linear_index * words;
out_u32[out_idx] = texel.x;
if (words > 1u) {
out_u32[out_idx + 1u] = texel.y;
}
if (words > 2u) {
out_u32[out_idx + 2u] = texel.z;
out_u32[out_idx + 3u] = texel.w;
}
}
@@ -6,9 +6,9 @@
precision highp float;
precision highp int;
// Operation modes: RGBA -> 1, RGBY -> 3, LERP -> 4
#define OPERATION_MODE 1
#define EDGE_THRESHOLD (8.0 / 255.0)
#define DIRECTION_EPSILON 6.5e-05
#define DEVIATION_FLOOR 6.0e-02
layout(push_constant) uniform constants {
vec2 scale;
@@ -21,37 +21,20 @@ layout(set = 0, binding = 0) uniform sampler2D sampler0;
layout(location=0) in vec2 texcoord;
layout(location=0) out vec4 frag_color;
mediump vec4 fastLanczos2(mediump vec4 x) {
mediump vec4 wA = x - 4.0f;
mediump vec4 wB = x * wA - wA;
wA *= wA;
return wB * wA;
}
mediump vec2 edgeDirection(mediump vec4 left, mediump vec4 right) {
mediump float RxLz = right.x - left.z;
mediump float RwLy = right.w - left.y;
mediump vec2 delta = vec2(RxLz + RwLy, RxLz - RwLy);
mediump float length_inv =
inversesqrt((delta.x * delta.x + DIRECTION_EPSILON) + delta.y * delta.y);
return delta * length_inv;
}
mediump vec4 weightY(mediump vec4 dx, mediump vec4 dy, mediump vec4 c, mediump float std,
mediump vec2 dir) {
mediump vec4 edge_dis = dx * dir.y + dy * dir.x;
mediump vec4 x = (dx * dx + dy * dy) +
(edge_dis * edge_dis) * (clamp((c * c) * std, 0.0f, 1.0f) * 0.7f - 1.0f);
return fastLanczos2(x);
vec4 weightY(vec4 dx, vec4 dy, vec4 std) {
vec4 x = ((dx * dx) + (dy * dy)) * 0.55f + std;
return (x - 1.f) * (x - 4.f) * 3.8125f; // approx. of (x - 1) * (x - 4)^3
}
void main() {
mediump vec4 color = textureLod(sampler0, texcoord.xy, 0.0f);
highp vec2 icoord = (texcoord * size + vec2(-0.5f, 0.5f));
highp vec2 icoord_pixel = floor(icoord);
highp vec2 coord = icoord_pixel * scale;
mediump vec2 pl = icoord - icoord_pixel;
mediump mat3x4 dg = mat3x4(
vec4 color = textureLod(sampler0, texcoord.xy, 0.0f);
// image coord
vec2 icoord = (texcoord * size + vec2(-0.5f, 0.5f));
vec2 icoord_pixel = floor(icoord);
vec2 coord = icoord_pixel * scale;
vec2 pl = icoord - icoord_pixel;
// left: 0, right: 1, upDown: 2
mat3x4 dg = mat3x4(
textureGather(sampler0, coord, 1),
textureGather(sampler0, coord + vec2(2.f * scale.x, 0.0f), 1),
vec4(
@@ -59,40 +42,42 @@ void main() {
textureGather(sampler0, coord + vec2(scale.x, +scale.y), 1).yx
)
);
mediump float edgeVote =
abs(dg[0].z - dg[0].y) + abs(color.y - dg[0].y) + abs(color.y - dg[0].z);
float edgeVote = abs(dg[0].z - dg[0].y) + abs(color.y - dg[0].y) + abs(color.y - dg[0].z);
if (edgeVote > EDGE_THRESHOLD) {
mediump float mean = (dg[0].y + dg[0].z + dg[1].x + dg[1].w) * 0.25f;
float mean = (dg[0].y + dg[0].z + dg[1].x + dg[1].w) * 0.25f;
dg = dg - mean;
mediump float sum = dot(abs(dg[0]) + abs(dg[1]) + abs(dg[2]), vec4(1.0f));
mediump float sum_mean = 1.014185e+01f / max(sum, DEVIATION_FLOOR);
mediump float std = sum_mean * sum_mean;
mediump vec2 dir = edgeDirection(dg[0], dg[1]);
mediump vec4 w0 = weightY(
pl.xxxx + vec4(+1.0f, +0.0f, +0.0f, +1.0f),
pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
dg[0], std, dir
vec4 sum = abs(dg[0]) + abs(dg[1]) + abs(dg[2]);
float std = 2.181818f / (sum.x + sum.y + sum.z + sum.w);
mat2x4 w = mat2x4(
weightY(
pl.xxxx + vec4(+1.0f, +0.0f, +0.0f, +1.0f),
pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
clamp(abs(dg[0]) * std, 0.0f, 1.0f)
) + weightY(
pl.xxxx + vec4(-1.0f, -2.0f, -2.0f, -1.0f),
pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
clamp(abs(dg[1]) * std, 0.0f, 1.0f)
) + weightY(
pl.xxxx + vec4(+0.0f, -1.0f, -1.0f, +0.0f),
pl.yyyy + vec4(+1.0f, +1.0f, -2.0f, -2.0f),
clamp(abs(dg[2]) * std, 0.0f, 1.0f)
),
dg[0] + dg[1] + dg[2]
);
mediump vec4 w1 = weightY(
pl.xxxx + vec4(-1.0f, -2.0f, -2.0f, -1.0f),
pl.yyyy + vec4(-1.0f, -1.0f, +0.0f, +0.0f),
dg[1], std, dir
// compute final y with bounds
vec2 yb = vec2(
min(min(dg[0].y, dg[0].z), min(dg[1].x, dg[1].w)), // min
max(max(dg[0].y, dg[0].z), max(dg[1].x, dg[1].w)) // max
);
mediump vec4 w2 = weightY(
pl.xxxx + vec4(+0.0f, -1.0f, -1.0f, +0.0f),
pl.yyyy + vec4(+1.0f, +1.0f, -2.0f, -2.0f),
dg[2], std, dir
vec2 fvy = vec2(
w[0].x + w[0].y + w[0].z + w[0].w,
w[1].x + w[1].y + w[1].z + w[1].w
);
mediump float sum_w = dot(w0 + w1 + w2, vec4(1.0f));
mediump float sum_wc = dot(w0 * dg[0] + w1 * dg[1] + w2 * dg[2], vec4(1.0f));
mediump vec2 yb = vec2(
min(min(dg[0].y, dg[0].z), min(dg[1].x, dg[1].w)),
max(max(dg[0].y, dg[0].z), max(dg[1].x, dg[1].w))
);
mediump float fy = clamp((sum_wc / sum_w) * edge_sharpness, yb[0], yb[1]);
mediump float dy = clamp(fy - color.y + mean, -23.0f / 255.0f, 23.0f / 255.0f);
float fy = clamp((fvy.y / fvy.x) * edge_sharpness, yb[0], yb[1]);
// Smooth high contrast input
float dy = clamp(fy - color.y + mean, -23.0f / 255.0f, 23.0f / 255.0f);
color = clamp(color + dy, 0.0f, 1.0f);
}
color.w = 1.0f;
color.w = 1.0f; //assume alpha channel is not used
frag_color.xyzw = color;
}
}
-23
View File
@@ -529,29 +529,6 @@ size_t MemoryManager::MaxContinuousRange(GPUVAddr gpu_addr, size_t size) const {
return range_so_far;
}
size_t MemoryManager::MaxMappedRange(GPUVAddr gpu_addr, size_t size) const {
size_t range_so_far = 0;
bool stopped{false};
auto stop = [&]([[maybe_unused]] std::size_t page_index, [[maybe_unused]] std::size_t offset,
[[maybe_unused]] std::size_t copy_amount) {
stopped = true;
return true;
};
auto accumulate = [&]([[maybe_unused]] std::size_t page_index,
[[maybe_unused]] std::size_t offset, std::size_t copy_amount) {
range_so_far += copy_amount;
return false;
};
auto check_short_pages = [&](std::size_t page_index, std::size_t offset,
std::size_t copy_amount) {
GPUVAddr base = (page_index << big_page_bits) + offset;
MemoryOperation(base, copy_amount, false, accumulate, stop, stop);
return stopped;
};
MemoryOperation(gpu_addr, size, true, accumulate, stop, check_short_pages);
return range_so_far;
}
size_t MemoryManager::GetMemoryLayoutSize(GPUVAddr gpu_addr, size_t max_size) const {
std::unique_lock<std::mutex> lock(guard);
return kind_map.GetContinuousSizeFrom(gpu_addr);
-2
View File
@@ -141,8 +141,6 @@ public:
size_t MaxContinuousRange(GPUVAddr gpu_addr, size_t size) const;
size_t MaxMappedRange(GPUVAddr gpu_addr, size_t size) const;
bool IsWithinGPUAddressRange(GPUVAddr gpu_addr) const {
return gpu_addr < address_space_size;
}
@@ -262,7 +262,6 @@ struct BufferCacheParams {
// TODO: Investigate why OpenGL seems to perform worse with persistently mapped buffer uploads
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = false;
static constexpr bool USE_UNIFIED_MEMORY = false;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -238,7 +238,6 @@ ShaderCache::ShaderCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_gl_bool_ref_bug = device.HasBoolRefBug(),
.ignore_nan_fp_comparisons = true,
.gl_max_compute_smem_size = device.GetMaxComputeSharedMemorySize(),
.max_shared_memory_size = device.GetMaxComputeSharedMemorySize(),
.min_ssbo_alignment = device.GetShaderStorageBufferAlignment(),
// Use the host limit, but never more than the guest can produce. Maxwell exposes 8 clip
// distances and the SPIR-V output array is sized for at most 8, so clamping here keeps a
@@ -373,7 +373,6 @@ struct TextureCacheParams {
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
static constexpr bool HAS_MSAA_DOWNLOADS = false;
static constexpr bool USE_UNIFIED_MEMORY = false;
using Runtime = OpenGL::TextureCacheRuntime;
using Image = OpenGL::Image;
@@ -7,12 +7,9 @@
#include <algorithm>
#include <array>
#include <cstring>
#include <limits>
#include <span>
#include <utility>
#include <vector>
#include "common/alignment.h"
#include "video_core/buffer_cache/buffer_cache_base.h"
#include "video_core/renderer_vulkan/vk_buffer_cache.h"
@@ -35,32 +32,6 @@ VkBufferCopy MakeBufferCopy(const VideoCommon::BufferCopy& copy) {
};
}
constexpr size_t MAX_WINDOW_BARRIER_RANGES = 8;
using WindowRange = std::pair<VkDeviceSize, VkDeviceSize>;
using WindowRanges = boost::container::small_vector<WindowRange, MAX_WINDOW_BARRIER_RANGES>;
void CoalesceWindowRanges(WindowRanges& ranges) {
if (ranges.size() < 2) {
return;
}
std::sort(ranges.begin(), ranges.end());
size_t merged = 0;
for (size_t index = 1; index < ranges.size(); ++index) {
if (ranges[index].first <= ranges[merged].second) {
ranges[merged].second = (std::max)(ranges[merged].second, ranges[index].second);
} else {
ranges[++merged] = ranges[index];
}
}
ranges.resize(merged + 1);
if (ranges.size() > MAX_WINDOW_BARRIER_RANGES) {
const WindowRange bounding{ranges.front().first, ranges.back().second};
ranges.clear();
ranges.push_back(bounding);
}
}
VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) {
if (num_elements <= 0xff && device.IsExtIndexTypeUint8Supported()) {
return VK_INDEX_TYPE_UINT8_EXT;
@@ -71,16 +42,6 @@ VkIndexType IndexTypeFromNumElements(const Device& device, u32 num_elements) {
return VK_INDEX_TYPE_UINT32;
}
u32 GrowIndexCount(u32 current, u32 requested) {
constexpr u32 MinimumIndices = 4096;
constexpr u32 GrowthLimit = (std::numeric_limits<u32>::max)() / 2;
u32 grown = (std::max)(requested, MinimumIndices);
if (current <= GrowthLimit) {
grown = (std::max)(grown, current * 2);
}
return grown;
}
size_t BytesPerIndex(VkIndexType index_type) {
switch (index_type) {
case VK_INDEX_TYPE_UINT8_EXT:
@@ -205,12 +166,13 @@ public:
virtual ~QuadIndexBuffer() = default;
void UpdateBuffer(u32 num_indices_) {
ReleaseRetiredBuffers();
if (num_indices_ <= num_indices) {
return;
}
num_indices = GrowIndexCount(num_indices, num_indices_);
scheduler.Finish();
num_indices = num_indices_;
index_type = IndexTypeFromNumElements(device, num_indices);
const u32 num_quads = GetQuadsNum(num_indices);
@@ -228,12 +190,6 @@ public:
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
if (buffer) {
retired_buffers.push_back(RetiredBuffer{
.buffer = std::move(buffer),
.tick = scheduler.CurrentTick(),
});
}
buffer = memory_allocator.CreateBuffer(buffer_ci, MemoryUsage::DeviceLocal);
if (device.HasDebuggingToolAttached()) {
buffer.SetObjectNameEXT("Quad LUT");
@@ -301,17 +257,6 @@ protected:
virtual void MakeAndUpdateIndices(u8* staging_data, size_t quad_size, u32 quad, u32 first) = 0;
struct RetiredBuffer {
vk::Buffer buffer;
u64 tick;
};
void ReleaseRetiredBuffers() {
std::erase_if(retired_buffers, [this](const RetiredBuffer& entry) {
return scheduler.IsFree(entry.tick);
});
}
const Device& device;
MemoryAllocator& memory_allocator;
Scheduler& scheduler;
@@ -319,7 +264,6 @@ protected:
vk::Buffer buffer{};
MemoryCommit memory_commit{};
std::vector<RetiredBuffer> retired_buffers;
VkIndexType index_type{};
u32 num_indices = 0;
};
@@ -430,211 +374,6 @@ BufferCacheRuntime::BufferCacheRuntime(const Device& device_, MemoryAllocator& m
scheduler_, staging_pool_);
}
void BufferCacheRuntime::TryEnableUnifiedMemory(void* base, size_t size,
std::span<AHardwareBuffer* const> hardware_buffers,
size_t hardware_buffer_window,
size_t hardware_buffer_base) {
unified_memory = memory_allocator.CreateHostMemoryImport(
base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
if (unified_memory) {
unified_memory->CreateMirror(scheduler.submit_mutex,
device.GetSparseAddressSpaceSize() / 2);
multi_range_buffers.ReserveSparseAddressSpace(unified_memory->GetMirrorSize());
}
}
void BufferCacheRuntime::CopyToUnifiedMemory(
size_t window_index, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies) {
if (!unified_memory || src_buffer == VK_NULL_HANDLE || copies.empty() ||
window_index >= unified_memory->GetWindowCount() ||
unified_memory->GetWindowBuffer(window_index) == VK_NULL_HANDLE) {
return;
}
PendingUnifiedCopy& pending = pending_unified_copies.emplace_back();
pending.window = window_index;
pending.buffer = src_buffer;
pending.copies.resize(copies.size());
std::ranges::transform(copies, pending.copies.begin(), MakeBufferCopy);
}
void BufferCacheRuntime::CopyFromUnifiedMemory(
size_t window_index, VkBuffer dst_buffer, std::span<const VideoCommon::BufferCopy> copies) {
if (!unified_memory || dst_buffer == VK_NULL_HANDLE || copies.empty() ||
window_index >= unified_memory->GetWindowCount()) {
return;
}
const VkBuffer window_buffer = unified_memory->GetWindowBuffer(window_index);
if (window_buffer == VK_NULL_HANDLE) {
return;
}
const bool foreign = unified_memory->NeedsForeignOwnershipTransfer();
const u32 queue_family = device.GetGraphicsFamily();
boost::container::small_vector<VkBufferCopy, 8> vk_copies;
WindowRanges ranges;
for (const VideoCommon::BufferCopy& copy : copies) {
vk_copies.push_back(VkBufferCopy{
.srcOffset = static_cast<VkDeviceSize>(copy.dst_offset),
.dstOffset = static_cast<VkDeviceSize>(copy.src_offset),
.size = static_cast<VkDeviceSize>(copy.size),
});
ranges.emplace_back(copy.dst_offset, copy.dst_offset + copy.size);
}
CoalesceWindowRanges(ranges);
boost::container::small_vector<VkBufferMemoryBarrier, MAX_WINDOW_BARRIER_RANGES> acquire;
boost::container::small_vector<VkBufferMemoryBarrier, MAX_WINDOW_BARRIER_RANGES> release;
if (foreign) {
for (const WindowRange& range : ranges) {
acquire.push_back(VkBufferMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = window_buffer,
.offset = range.first,
.size = range.second - range.first,
});
release.push_back(VkBufferMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = queue_family,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.buffer = window_buffer,
.offset = range.first,
.size = range.second - range.first,
});
}
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([window_buffer, dst_buffer, vk_copies, acquire = std::move(acquire),
release = std::move(release)](vk::CommandBuffer cmdbuf) {
if (!acquire.empty()) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {},
VideoCommon::FixSmallVectorADL(acquire), {});
}
cmdbuf.CopyBuffer(window_buffer, dst_buffer, VideoCommon::FixSmallVectorADL(vk_copies));
if (!release.empty()) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, {},
VideoCommon::FixSmallVectorADL(release), {});
}
});
}
void BufferCacheRuntime::FlushUnifiedMemoryCopies() {
if (pending_unified_copies.empty()) {
return;
}
struct UnifiedCopyCommand {
VkBuffer buffer;
boost::container::small_vector<VkBufferCopy, 8> copies;
};
std::stable_sort(pending_unified_copies.begin(), pending_unified_copies.end(),
[](const PendingUnifiedCopy& lhs, const PendingUnifiedCopy& rhs) {
return lhs.window < rhs.window;
});
const bool foreign = unified_memory->NeedsForeignOwnershipTransfer();
const u32 queue_family = device.GetGraphicsFamily();
size_t group_begin = 0;
while (group_begin < pending_unified_copies.size()) {
const size_t window = pending_unified_copies[group_begin].window;
size_t group_end = group_begin;
while (group_end < pending_unified_copies.size() &&
pending_unified_copies[group_end].window == window) {
++group_end;
}
const VkBuffer window_buffer = unified_memory->GetWindowBuffer(window);
WindowRanges ranges;
for (size_t index = group_begin; index < group_end; ++index) {
for (const VkBufferCopy& copy : pending_unified_copies[index].copies) {
ranges.emplace_back(copy.dstOffset, copy.dstOffset + copy.size);
}
}
CoalesceWindowRanges(ranges);
boost::container::small_vector<VkBufferMemoryBarrier, MAX_WINDOW_BARRIER_RANGES> acquire;
boost::container::small_vector<VkBufferMemoryBarrier, MAX_WINDOW_BARRIER_RANGES> release;
if (foreign) {
for (const WindowRange& range : ranges) {
acquire.push_back(VkBufferMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = window_buffer,
.offset = range.first,
.size = range.second - range.first,
});
release.push_back(VkBufferMemoryBarrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = queue_family,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.buffer = window_buffer,
.offset = range.first,
.size = range.second - range.first,
});
}
}
boost::container::small_vector<UnifiedCopyCommand, 4> commands;
commands.reserve(group_end - group_begin);
for (size_t index = group_begin; index < group_end; ++index) {
PendingUnifiedCopy& pending = pending_unified_copies[index];
commands.push_back(UnifiedCopyCommand{pending.buffer, std::move(pending.copies)});
}
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([window_buffer, acquire = std::move(acquire), release = std::move(release),
commands = std::move(commands)](vk::CommandBuffer cmdbuf) {
if (!acquire.empty()) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {},
VideoCommon::FixSmallVectorADL(acquire), {});
}
for (const UnifiedCopyCommand& command : commands) {
cmdbuf.CopyBuffer(command.buffer, window_buffer,
VideoCommon::FixSmallVectorADL(command.copies));
}
if (!release.empty()) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, {},
VideoCommon::FixSmallVectorADL(release), {});
}
});
group_begin = group_end;
}
pending_unified_copies.clear();
}
void BufferCacheRuntime::UnifiedMemoryHostBarrier() {
static constexpr VkMemoryBarrier HOST_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
};
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([](vk::CommandBuffer cmdbuf) {
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_HOST_BIT, 0,
HOST_BARRIER);
});
}
StagingBufferRef BufferCacheRuntime::UploadStagingBuffer(size_t size) {
return staging_pool.Request(size, MemoryUsage::Upload);
}
@@ -672,7 +411,6 @@ u32 BufferCacheRuntime::GetStorageBufferAlignment() const {
}
void BufferCacheRuntime::TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept {
FlushUnifiedMemoryCopies();
for (auto it = slot_buffers.begin(); it != slot_buffers.end(); it++) {
if (scheduler.IsFree(it->LastUsageTick())) {
it->ResetUsageTracking();
@@ -807,17 +545,17 @@ void BufferCacheRuntime::ClearBuffer(VkBuffer dest_buffer, u32 offset, size_t si
});
}
MultiRangeRef BufferCacheRuntime::AcquireMultiRange(u64 key, bool require_sparse) {
bool BufferCacheRuntime::BindMultiRangeStorageBuffer(u64 key, bool is_written) {
if (multi_range_sources.empty() || multi_range_total == 0) {
return MultiRangeRef{};
return false;
}
const MultiRangeRef ref = multi_range_buffers.Get(device, scheduler, memory_allocator, key,
multi_range_sources, multi_range_total);
if (ref.handle == VK_NULL_HANDLE) {
return MultiRangeRef{};
return false;
}
if (require_sparse && !ref.sparse) {
return MultiRangeRef{};
if (is_written && !ref.sparse) {
return false;
}
if (ref.needs_gather) {
PreCopyBarrier();
@@ -834,220 +572,10 @@ MultiRangeRef BufferCacheRuntime::AcquireMultiRange(u64 key, bool require_sparse
PostCopyBarrier();
multi_range_buffers.MarkGathered(key);
}
return ref;
}
bool BufferCacheRuntime::BindMultiRangeStorageBuffer(u64 key, bool is_written) {
const MultiRangeRef ref = AcquireMultiRange(key, is_written);
if (ref.handle == VK_NULL_HANDLE) {
return false;
}
guest_descriptor_queue.AddBuffer(ref.handle, ref.address, 0, ref.size);
return true;
}
std::optional<HostMemoryImport::Range> BufferCacheRuntime::ResolveUnifiedExtents(
u64 key, std::span<const VideoCommon::UnifiedExtent> extents, u32 size) {
if (extents.size() == 1) {
return unified_memory->ResolveRange(extents.front().relative, size);
}
return AcquireUnifiedView(key, extents);
}
std::optional<HostMemoryImport::Range> BufferCacheRuntime::AcquireUnifiedView(
u64 key, std::span<const VideoCommon::UnifiedExtent> extents) {
const VkBufferUsageFlags usage = unified_memory->GetViewUsage();
if (usage == 0 || !multi_range_buffers.use_sparse || extents.size() < 2) {
return std::nullopt;
}
const VkDeviceSize block = multi_range_buffers.block_size;
boost::container::small_vector<MultiRangeSource, 16> sources;
VkDeviceSize total = 0;
VkDeviceSize padding = 0;
for (size_t index = 0; index < extents.size(); ++index) {
VkDeviceSize begin = extents[index].relative;
VkDeviceSize end = begin + extents[index].size;
if (index == 0) {
padding = begin % block;
begin -= padding;
} else if ((begin % block) != 0) {
return std::nullopt;
}
if (index + 1 == extents.size()) {
end = Common::AlignUp(end, block);
} else if ((end % block) != 0) {
return std::nullopt;
}
while (begin < end) {
const auto memory = unified_memory->ResolveViewMemory(begin);
if (!memory || (memory->offset % block) != 0) {
return std::nullopt;
}
const VkDeviceSize length = (std::min)(end - begin, memory->available);
if ((length % block) != 0) {
return std::nullopt;
}
sources.push_back(MultiRangeSource{
.handle = memory->buffer,
.memory = memory->memory,
.memory_offset = 0,
.offset = memory->offset,
.size = length,
.write_tick = 0,
.memory_type = memory->memory_type,
});
total += length;
begin += length;
}
}
const MultiRangeRef ref = multi_range_buffers.GetView(
device, scheduler, key, sources, total, usage,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID);
if (ref.handle == VK_NULL_HANDLE) {
return std::nullopt;
}
return HostMemoryImport::Range{
.buffer = ref.handle,
.address = ref.address,
.offset = padding,
};
}
bool BufferCacheRuntime::StageUnifiedVertexBuffer(
u32 index, u64 key, std::span<const VideoCommon::UnifiedExtent> extents, u32 size, u32 stride,
bool force) {
if (!unified_memory ||
(unified_memory->GetUsage() & VK_BUFFER_USAGE_VERTEX_BUFFER_BIT) == 0) {
return false;
}
const auto range = ResolveUnifiedExtents(key, extents, size);
if (!range) {
return false;
}
StageVertexBuffer(
StagedVertexBuffer{
.index = index,
.buffer = range->buffer,
.offset = range->offset,
.size = size,
.stride = stride,
},
force);
return true;
}
bool BufferCacheRuntime::StageMultiRangeVertexBuffer(u32 index, u64 key, u32 size, u32 stride,
bool force) {
const MultiRangeRef ref = AcquireMultiRange(key, false);
if (ref.handle == VK_NULL_HANDLE) {
return false;
}
StageVertexBuffer(
StagedVertexBuffer{
.index = index,
.buffer = ref.handle,
.offset = 0,
.size = size,
.stride = stride,
},
force);
return true;
}
void BufferCacheRuntime::StageVertexBuffer(const StagedVertexBuffer& target, bool force) {
StagedVertexBuffer& bound = bound_vertex_buffers[target.index];
if (!force && bound == target) {
return;
}
bound = target;
if (target.index < device.GetMaxVertexInputBindings()) {
staged_vertex_buffers.push_back(target);
}
}
void BufferCacheRuntime::BindStagedVertexBuffers() {
if (staged_vertex_buffers.empty()) {
return;
}
scheduler.Record([staged = staged_vertex_buffers,
extended = device.IsExtExtendedDynamicStateSupported()](
vk::CommandBuffer cmdbuf) {
std::array<VkBuffer, VideoCommon::NUM_VERTEX_BUFFERS> buffers{};
std::array<VkDeviceSize, VideoCommon::NUM_VERTEX_BUFFERS> offsets{};
std::array<VkDeviceSize, VideoCommon::NUM_VERTEX_BUFFERS> sizes{};
std::array<VkDeviceSize, VideoCommon::NUM_VERTEX_BUFFERS> strides{};
size_t begin = 0;
while (begin < staged.size()) {
const u32 first = staged[begin].index;
u32 count = 0;
while (begin + count < staged.size() && staged[begin + count].index == first + count) {
const StagedVertexBuffer& entry = staged[begin + count];
buffers[count] = entry.buffer;
offsets[count] = entry.offset;
sizes[count] = entry.size;
strides[count] = entry.stride;
++count;
}
if (extended) {
cmdbuf.BindVertexBuffers2EXT(first, count, buffers.data(), offsets.data(),
sizes.data(), strides.data());
} else {
cmdbuf.BindVertexBuffers(first, count, buffers.data(), offsets.data());
}
begin += count;
}
});
staged_vertex_buffers.clear();
}
bool BufferCacheRuntime::IsIndexRangeUsable(PrimitiveTopology topology, IndexFormat index_format,
VkDeviceSize offset, u32 size) const {
const VkIndexType vk_index_type = MaxwellToVK::IndexFormat(index_format);
const bool is_quad =
topology == PrimitiveTopology::Quads || topology == PrimitiveTopology::QuadStrip;
const bool is_emulated_uint8 =
vk_index_type == VK_INDEX_TYPE_UINT8_EXT && !device.IsExtIndexTypeUint8Supported();
if (is_quad || is_emulated_uint8) {
return size <= device.GetMaxStorageBufferRange() &&
(offset % device.GetStorageBufferAlignment()) == 0;
}
return (offset % BytesPerIndex(vk_index_type)) == 0;
}
bool BufferCacheRuntime::BindUnifiedIndexBuffer(PrimitiveTopology topology,
IndexFormat index_format, u32 base_vertex,
u32 num_indices, u64 key,
std::span<const VideoCommon::UnifiedExtent> extents,
u32 size) {
constexpr VkBufferUsageFlags GeometryUsage =
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (!unified_memory || (unified_memory->GetUsage() & GeometryUsage) != GeometryUsage) {
return false;
}
const auto range = ResolveUnifiedExtents(key, extents, size);
if (!range || range->offset > (std::numeric_limits<u32>::max)() - size ||
!IsIndexRangeUsable(topology, index_format, range->offset, size)) {
return false;
}
BindIndexBuffer(topology, index_format, base_vertex, num_indices, range->buffer,
static_cast<u32>(range->offset), size);
return true;
}
bool BufferCacheRuntime::BindMultiRangeIndexBuffer(PrimitiveTopology topology,
IndexFormat index_format, u32 base_vertex,
u32 num_indices, u64 key, u32 size) {
if (!IsIndexRangeUsable(topology, index_format, 0, size)) {
return false;
}
const MultiRangeRef ref = AcquireMultiRange(key, false);
if (ref.handle == VK_NULL_HANDLE) {
return false;
}
BindIndexBuffer(topology, index_format, base_vertex, num_indices, ref.handle, 0, size);
return true;
}
void BufferCacheRuntime::BindIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, VkBuffer buffer,
u32 offset, [[maybe_unused]] u32 size) {
@@ -7,8 +7,6 @@
#pragma once
#include <limits>
#include <memory>
#include <span>
#include <boost/container/small_vector.hpp>
@@ -112,36 +110,6 @@ public:
void TickFrame(Common::SlotVector<Buffer>& slot_buffers) noexcept;
void TryEnableUnifiedMemory(void* base, size_t size,
std::span<AHardwareBuffer* const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base);
[[nodiscard]] bool HasUnifiedMemory() const noexcept {
return unified_memory != nullptr && unified_memory->IsValid();
}
[[nodiscard]] u64 UnifiedMemorySize() const noexcept {
return unified_memory ? unified_memory->GetSize() : 0;
}
[[nodiscard]] u64 UnifiedMemoryBase() const noexcept {
return unified_memory ? unified_memory->GetBaseOffset() : 0;
}
[[nodiscard]] u64 UnifiedMemoryWindowSize() const noexcept {
return unified_memory ? unified_memory->GetWindowSize() : 0;
}
void CopyToUnifiedMemory(size_t window_index, VkBuffer src_buffer,
std::span<const VideoCommon::BufferCopy> copies);
void CopyFromUnifiedMemory(size_t window_index, VkBuffer dst_buffer,
std::span<const VideoCommon::BufferCopy> copies);
void FlushUnifiedMemoryCopies();
void UnifiedMemoryHostBarrier();
u64 CurrentTick();
u64 KnownGpuTick();
@@ -232,21 +200,6 @@ public:
bool BindMultiRangeStorageBuffer(u64 key, bool is_written);
bool StageUnifiedVertexBuffer(u32 index, u64 key,
std::span<const VideoCommon::UnifiedExtent> extents, u32 size,
u32 stride, bool force);
bool StageMultiRangeVertexBuffer(u32 index, u64 key, u32 size, u32 stride, bool force);
void BindStagedVertexBuffers();
bool BindUnifiedIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, u64 key,
std::span<const VideoCommon::UnifiedExtent> extents, u32 size);
bool BindMultiRangeIndexBuffer(PrimitiveTopology topology, IndexFormat index_format,
u32 base_vertex, u32 num_indices, u64 key, u32 size);
void InvalidateMultiRange(u64 key) {
multi_range_buffers.Invalidate(key);
}
@@ -264,28 +217,6 @@ public:
BindBuffer(buffer, offset, size);
}
void BindStorageBuffer(VkBuffer buffer, VkDeviceAddress address, VkDeviceSize offset, u32 size,
[[maybe_unused]] bool is_written) {
guest_descriptor_queue.AddBuffer(buffer, address, offset, size);
}
[[nodiscard]] bool IsUnifiedMemoryBindable() const noexcept {
return unified_memory != nullptr && unified_memory->IsValid() &&
unified_memory->IsBindable();
}
[[nodiscard]] std::optional<HostMemoryImport::Range> ResolveUnifiedStorage(u64 relative,
u32 size) const {
if (size > device.GetMaxStorageBufferRange()) {
return std::nullopt;
}
const auto range = unified_memory->ResolveRange(relative, size);
if (!range || (range->offset % device.GetStorageBufferAlignment()) != 0) {
return std::nullopt;
}
return range;
}
void BindTextureBuffer(Buffer& buffer, u32 offset, u32 size,
VideoCore::Surface::PixelFormat format) {
guest_descriptor_queue.AddTexelBuffer(buffer.View(offset, size, format),
@@ -302,35 +233,6 @@ public:
}
private:
struct PendingUnifiedCopy {
size_t window;
VkBuffer buffer;
boost::container::small_vector<VkBufferCopy, 8> copies;
};
struct StagedVertexBuffer {
u32 index;
VkBuffer buffer;
VkDeviceSize offset;
u32 size;
u32 stride;
bool operator==(const StagedVertexBuffer&) const = default;
};
[[nodiscard]] MultiRangeRef AcquireMultiRange(u64 key, bool require_sparse);
[[nodiscard]] std::optional<HostMemoryImport::Range> ResolveUnifiedExtents(
u64 key, std::span<const VideoCommon::UnifiedExtent> extents, u32 size);
[[nodiscard]] std::optional<HostMemoryImport::Range> AcquireUnifiedView(
u64 key, std::span<const VideoCommon::UnifiedExtent> extents);
void StageVertexBuffer(const StagedVertexBuffer& target, bool force);
[[nodiscard]] bool IsIndexRangeUsable(PrimitiveTopology topology, IndexFormat index_format,
VkDeviceSize offset, u32 size) const;
void BindBuffer(const Buffer& buffer, u32 offset, u32 size) {
const VkBuffer handle = buffer.Handle();
if (handle == VK_NULL_HANDLE) {
@@ -355,8 +257,6 @@ private:
std::shared_ptr<QuadStripIndexBuffer> quad_strip_index_buffer;
vk::Buffer null_buffer;
HostMemoryImport* unified_memory{};
boost::container::small_vector<PendingUnifiedCopy, 8> pending_unified_copies;
std::unique_ptr<Uint8Pass> uint8_pass;
QuadIndexedPass quad_index_pass;
@@ -364,9 +264,6 @@ private:
MultiRangeBufferCache multi_range_buffers;
boost::container::small_vector<MultiRangeSource, 16> multi_range_sources;
VkDeviceSize multi_range_total{};
boost::container::static_vector<StagedVertexBuffer, VideoCommon::NUM_VERTEX_BUFFERS>
staged_vertex_buffers;
std::array<StagedVertexBuffer, VideoCommon::NUM_VERTEX_BUFFERS> bound_vertex_buffers{};
bool limit_dynamic_storage_buffers = false;
u32 max_dynamic_storage_buffers = (std::numeric_limits<u32>::max)();
@@ -386,7 +283,6 @@ struct BufferCacheParams {
static constexpr bool USE_MEMORY_MAPS = true;
static constexpr bool SEPARATE_IMAGE_BUFFER_BINDINGS = false;
static constexpr bool USE_MEMORY_MAPS_FOR_UPLOADS = true;
static constexpr bool USE_UNIFIED_MEMORY = true;
};
using BufferCache = VideoCommon::BufferCache<BufferCacheParams>;
@@ -4,7 +4,6 @@
// SPDX-FileCopyrightText: Copyright 2019 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#include <algorithm>
#include <array>
#include <memory>
#include <numeric>
@@ -13,7 +12,6 @@
#include "video_core/renderer_vulkan/vk_texture_cache.h"
#include "common/alignment.h"
#include "common/assert.h"
#include "common/common_types.h"
#include "common/div_ceil.h"
@@ -24,10 +22,7 @@
#include "video_core/host_shaders/resolve_conditional_render_comp_spv.h"
#include "video_core/host_shaders/vulkan_quad_indexed_comp_spv.h"
#include "video_core/host_shaders/vulkan_uint8_comp_spv.h"
#include "video_core/host_shaders/block_linear_swizzle_2d_buffer_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_2d_buffer_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_bcn_comp_spv.h"
#include "video_core/host_shaders/block_linear_unswizzle_3d_buffer_comp_spv.h"
#include "video_core/renderer_vulkan/vk_compute_pass.h"
#include "video_core/surface.h"
#include "video_core/renderer_vulkan/vk_descriptor_pool.h"
@@ -36,7 +31,6 @@
#include "video_core/renderer_vulkan/vk_update_descriptor.h"
#include "video_core/texture_cache/accelerated_swizzle.h"
#include "video_core/texture_cache/types.h"
#include "video_core/texture_cache/util.h"
#include "video_core/textures/decoders.h"
#include "video_core/vulkan_common/vulkan_device.h"
#include "video_core/vulkan_common/vulkan_wrapper.h"
@@ -878,672 +872,4 @@ void BlockLinearUnswizzle3DPass::UnswizzleChunk(
});
}
namespace {
constexpr u32 BL2D_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL2D_BINDING_OUTPUT_BUFFER = 1;
struct alignas(16) BlockLinearUnswizzle2DPushConstants {
std::array<u32, 3> dim;
u32 bytes_per_block_log2;
std::array<u32, 3> origin;
u32 layer_stride;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
};
static_assert(sizeof(BlockLinearUnswizzle2DPushConstants) <= 128);
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL2D_BINDINGS{{
{
.binding = BL2D_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL2D_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL2D_TEMPLATE{{
{
.dstBinding = BL2D_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL2D_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = BL2D_BINDING_OUTPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL2D_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
}};
constexpr DescriptorBankInfo BL2D_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 2,
};
} // Anonymous namespace
BlockLinearUnswizzle2DPass::BlockLinearUnswizzle2DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, BL2D_BINDINGS, BL2D_TEMPLATE,
BL2D_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DPushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_2D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle2DPass::~BlockLinearUnswizzle2DPass() = default;
bool BlockLinearUnswizzle2DPass::IsSupported(const Device& device,
const VideoCommon::ImageInfo& info) {
if (info.type != VideoCommon::ImageType::e2D) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (device.GetStorageBufferAlignment() > Tegra::Texture::GOB_SIZE) {
return false;
}
if (VideoCore::Surface::GetFormatType(info.format) !=
VideoCore::Surface::SurfaceType::ColorTexture) {
return false;
}
if (VideoCore::Surface::IsPixelFormatASTC(info.format) && !device.IsOptimalAstcSupported()) {
return false;
}
if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16;
}
void BlockLinearUnswizzle2DPass::Unswizzle(
Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
UnswizzleFrom(image, swizzled.buffer, swizzled.offset, swizzles);
}
void BlockLinearUnswizzle2DPass::UnswizzleFrom(
Image& image, VkBuffer source_buffer, VkDeviceSize source_offset,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
const VkImage dst_image = image.Handle();
if (swizzles.empty() || source_buffer == VK_NULL_HANDLE || dst_image == VK_NULL_HANDLE) {
return;
}
const u32 layers = image.info.resources.layers;
const VkImageAspectFlags aspect = image.AspectMask();
const VkDeviceSize output_alignment =
(std::max)(device.GetStorageBufferAlignment(), VkDeviceSize{16});
VkDeviceSize total_output = 0;
for (const VideoCommon::SwizzleParameters& sw : swizzles) {
const auto params =
VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
const VkDeviceSize level_size = static_cast<VkDeviceSize>(sw.num_tiles.width) *
sw.num_tiles.height * layers *
(1ULL << params.bytes_per_block_log2);
if (level_size == 0) {
continue;
}
total_output = Common::AlignUp(total_output, output_alignment) + level_size;
}
if (total_output == 0) {
return;
}
const StagingBufferRef output =
staging_buffer_pool.Request(static_cast<size_t>(total_output), MemoryUsage::DeviceLocal);
const VkBuffer out_buffer = output.buffer;
if (out_buffer == VK_NULL_HANDLE) {
return;
}
VkDeviceSize level_offset = 0;
scheduler.RequestOutsideRenderPassOperationContext();
VkAccessFlags pre_access = VK_ACCESS_NONE;
VkImageLayout pre_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkPipelineStageFlags pre_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (image.ExchangeInitialization()) {
pre_access = VK_ACCESS_SHADER_READ_BIT;
pre_layout = VK_IMAGE_LAYOUT_GENERAL;
pre_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE;
}
scheduler.Record([dst_image, aspect, pre_access, pre_layout,
pre_stage](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = pre_access,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = pre_layout,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(pre_stage, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, {}, barrier);
});
for (const VideoCommon::SwizzleParameters& sw : swizzles) {
const auto params =
VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
const u32 width = sw.num_tiles.width;
const u32 height = sw.num_tiles.height;
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
const VkDeviceSize output_size =
static_cast<VkDeviceSize>(width) * height * layers * bytes_per_block;
if (output_size == 0) {
continue;
}
const u32 level = static_cast<u32>(sw.level);
const u32 texel_width = (std::max)(1u, image.info.size.width >> level);
const u32 texel_height = (std::max)(1u, image.info.size.height >> level);
level_offset = Common::AlignUp(level_offset, output_alignment);
const VkDeviceSize out_offset = output.offset + level_offset;
level_offset += output_size;
BlockLinearUnswizzle2DPushConstants pc{};
pc.dim = {width, height, layers};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.layer_stride = params.layer_stride;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(source_buffer, sw.buffer_offset + source_offset,
image.guest_size_bytes - sw.buffer_offset);
compute_pass_descriptor_queue.AddBuffer(out_buffer, out_offset, output_size);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(width, 16u);
const u32 gy = Common::DivCeil(height, 8u);
scheduler.Record([this, set, descriptor_data, pc, gx, gy, layers, output_size, out_buffer,
out_offset, dst_image, aspect, texel_width, texel_height,
level](vk::CommandBuffer cmdbuf) {
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(gx, gy, layers);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = out_offset,
.size = output_size,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, {});
const VkBufferImageCopy copy{
.bufferOffset = out_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = aspect,
.mipLevel = level,
.baseArrayLayer = 0,
.layerCount = layers,
},
.imageOffset = {0, 0, 0},
.imageExtent = {texel_width, texel_height, 1},
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
copy);
});
}
scheduler.Record([dst_image, aspect](vk::CommandBuffer cmdbuf) {
const VkImageMemoryBarrier barrier{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
vk::PIPELINE_STAGE_GRAPHICS_COMPUTE, 0, {}, {}, barrier);
});
}
BlockLinearSwizzle2DPass::BlockLinearSwizzle2DPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, BL2D_BINDINGS, BL2D_TEMPLATE,
BL2D_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle2DPushConstants)>,
BLOCK_LINEAR_SWIZZLE_2D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearSwizzle2DPass::~BlockLinearSwizzle2DPass() = default;
void BlockLinearSwizzle2DPass::SwizzleInto(Image& image, VkBuffer dst_buffer,
VkDeviceSize dst_offset, bool foreign_ownership) {
const u32 layers = image.info.resources.layers;
const VkDeviceSize guest_size = image.guest_size_bytes;
const VkDeviceSize input_alignment =
(std::max)(device.GetStorageBufferAlignment(), VkDeviceSize{16});
auto copies = VideoCommon::FullDownloadCopies(image.info);
const auto swizzles = VideoCommon::FullUploadSwizzles(image.info);
VkDeviceSize total_size = 0;
for (VideoCommon::BufferImageCopy& copy : copies) {
total_size = Common::AlignUp(total_size, input_alignment);
copy.buffer_offset = static_cast<size_t>(total_size);
total_size += copy.buffer_size;
}
const StagingBufferRef scratch =
staging_buffer_pool.Request(static_cast<size_t>(total_size), MemoryUsage::DeviceLocal);
const VkBuffer scratch_buffer = scratch.buffer;
const VkDeviceSize scratch_offset = scratch.offset;
image.DownloadMemory(scratch_buffer, static_cast<size_t>(scratch_offset),
std::span<const VideoCommon::BufferImageCopy>(copies.data(),
copies.size()));
const u32 queue_family = device.GetGraphicsFamily();
scheduler.RequestOutsideRenderPassOperationContext();
scheduler.Record([scratch_buffer, scratch_offset, total_size, dst_buffer, dst_offset,
guest_size, queue_family, foreign_ownership](vk::CommandBuffer cmdbuf) {
const VkBufferMemoryBarrier scratch_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = scratch_buffer,
.offset = scratch_offset,
.size = total_size,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, scratch_barrier);
if (foreign_ownership) {
const VkBufferMemoryBarrier acquire{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = 0,
.dstAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.dstQueueFamilyIndex = queue_family,
.buffer = dst_buffer,
.offset = dst_offset,
.size = guest_size,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT,
VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, 0, acquire);
}
});
for (size_t level = 0; level < copies.size(); ++level) {
const VideoCommon::SwizzleParameters& sw = swizzles[level];
const VideoCommon::BufferImageCopy& copy = copies[level];
const auto params =
VideoCommon::Accelerated::MakeBlockLinearSwizzle2DParams(sw, image.info);
BlockLinearUnswizzle2DPushConstants pc{};
pc.dim = {sw.num_tiles.width, sw.num_tiles.height, layers};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.layer_stride = params.layer_stride;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(scratch_buffer, scratch_offset + copy.buffer_offset,
copy.buffer_size);
compute_pass_descriptor_queue.AddBuffer(dst_buffer, dst_offset + sw.buffer_offset,
guest_size - sw.buffer_offset);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(sw.num_tiles.width, 16u);
const u32 gy = Common::DivCeil(sw.num_tiles.height, 8u);
scheduler.Record(
[this, set, descriptor_data, pc, gx, gy, layers](vk::CommandBuffer cmdbuf) {
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template,
descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(gx, gy, layers);
});
}
scheduler.Record([dst_buffer, dst_offset, guest_size, queue_family,
foreign_ownership](vk::CommandBuffer cmdbuf) {
if (foreign_ownership) {
const VkBufferMemoryBarrier release{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = 0,
.srcQueueFamilyIndex = queue_family,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_FOREIGN_EXT,
.buffer = dst_buffer,
.offset = dst_offset,
.size = guest_size,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, release);
}
static constexpr VkMemoryBarrier HOST_BARRIER{
.sType = VK_STRUCTURE_TYPE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_HOST_READ_BIT,
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT, VK_PIPELINE_STAGE_HOST_BIT,
0, HOST_BARRIER);
});
}
namespace {
constexpr u32 BL3DB_BINDING_INPUT_BUFFER = 0;
constexpr u32 BL3DB_BINDING_OUTPUT_BUFFER = 1;
struct alignas(16) BlockLinearUnswizzle3DBufferPushConstants {
std::array<u32, 3> dim;
u32 bytes_per_block_log2;
std::array<u32, 3> origin;
u32 slice_size;
u32 block_size;
u32 x_shift;
u32 block_height;
u32 block_height_mask;
u32 block_depth;
u32 block_depth_mask;
};
static_assert(sizeof(BlockLinearUnswizzle3DBufferPushConstants) <= 128);
constexpr std::array<VkDescriptorSetLayoutBinding, 2> BL3DB_BINDINGS{{
{
.binding = BL3DB_BINDING_INPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
{
.binding = BL3DB_BINDING_OUTPUT_BUFFER,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_COMPUTE_BIT,
.pImmutableSamplers = nullptr,
},
}};
constexpr std::array<VkDescriptorUpdateTemplateEntry, 2> BL3DB_TEMPLATE{{
{
.dstBinding = BL3DB_BINDING_INPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3DB_BINDING_INPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
{
.dstBinding = BL3DB_BINDING_OUTPUT_BUFFER,
.dstArrayElement = 0,
.descriptorCount = 1,
.descriptorType = VK_DESCRIPTOR_TYPE_STORAGE_BUFFER,
.offset = BL3DB_BINDING_OUTPUT_BUFFER * sizeof(DescriptorUpdateEntry),
.stride = sizeof(DescriptorUpdateEntry),
},
}};
constexpr DescriptorBankInfo BL3DB_BANK_INFO{
.uniform_buffers = 0,
.storage_buffers = 2,
.texture_buffers = 0,
.image_buffers = 0,
.textures = 0,
.images = 0,
.score = 2,
};
} // Anonymous namespace
BlockLinearUnswizzle3DBufferPass::BlockLinearUnswizzle3DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_)
: ComputePass(device_, scheduler_, descriptor_pool_, BL3DB_BINDINGS, BL3DB_TEMPLATE,
BL3DB_BANK_INFO,
COMPUTE_PUSH_CONSTANT_RANGE<sizeof(BlockLinearUnswizzle3DBufferPushConstants)>,
BLOCK_LINEAR_UNSWIZZLE_3D_BUFFER_COMP_SPV),
scheduler{scheduler_}, staging_buffer_pool{staging_buffer_pool_},
compute_pass_descriptor_queue{compute_pass_descriptor_queue_} {}
BlockLinearUnswizzle3DBufferPass::~BlockLinearUnswizzle3DBufferPass() = default;
bool BlockLinearUnswizzle3DBufferPass::IsSupported(const Device& device,
const VideoCommon::ImageInfo& info) {
if (info.type != VideoCommon::ImageType::e3D) {
return false;
}
if (info.resources.levels != 1 || info.resources.layers != 1) {
return false;
}
if (info.num_samples > 1) {
return false;
}
if (info.size.depth <= 1) {
return false;
}
if (VideoCore::Surface::GetFormatType(info.format) !=
VideoCore::Surface::SurfaceType::ColorTexture) {
return false;
}
if (VideoCore::Surface::IsPixelFormatASTC(info.format)) {
return false;
}
if (VideoCore::Surface::IsPixelFormatBCn(info.format) && !device.IsOptimalBcnSupported()) {
return false;
}
const u32 bytes_per_block = VideoCore::Surface::BytesPerBlock(info.format);
return bytes_per_block == 4 || bytes_per_block == 8 || bytes_per_block == 16;
}
void BlockLinearUnswizzle3DBufferPass::Unswizzle(
Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles) {
if (swizzles.empty()) {
return;
}
const VideoCommon::SwizzleParameters& sw = swizzles.front();
const auto params = VideoCommon::Accelerated::MakeBlockLinearSwizzle3DParams(sw, image.info);
const u32 blocks_x = sw.num_tiles.width;
const u32 blocks_y = sw.num_tiles.height;
const u32 blocks_z = sw.num_tiles.depth;
const u32 bytes_per_block = 1u << params.bytes_per_block_log2;
const VkDeviceSize output_size =
static_cast<VkDeviceSize>(blocks_x) * blocks_y * blocks_z * bytes_per_block;
const StagingBufferRef output =
staging_buffer_pool.Request(static_cast<size_t>(output_size), MemoryUsage::DeviceLocal);
BlockLinearUnswizzle3DBufferPushConstants pc{};
pc.dim = {blocks_x, blocks_y, blocks_z};
pc.bytes_per_block_log2 = params.bytes_per_block_log2;
pc.origin = params.origin;
pc.slice_size = params.slice_size;
pc.block_size = params.block_size;
pc.x_shift = params.x_shift;
pc.block_height = params.block_height;
pc.block_height_mask = params.block_height_mask;
pc.block_depth = params.block_depth;
pc.block_depth_mask = params.block_depth_mask;
scheduler.RequestOutsideRenderPassOperationContext();
compute_pass_descriptor_queue.Acquire(scheduler, 2);
compute_pass_descriptor_queue.AddBuffer(swizzled.buffer, sw.buffer_offset + swizzled.offset,
image.guest_size_bytes - sw.buffer_offset);
compute_pass_descriptor_queue.AddBuffer(output.buffer, output.offset, output_size);
const void* descriptor_data = compute_pass_descriptor_queue.UpdateData();
const VkDescriptorSet set = descriptor_allocator.Commit();
const u32 gx = Common::DivCeil(blocks_x, 8u);
const u32 gy = Common::DivCeil(blocks_y, 8u);
const u32 gz = Common::DivCeil(blocks_z, 4u);
const bool is_initialized = image.ExchangeInitialization();
const VkBuffer out_buffer = output.buffer;
const VkDeviceSize out_offset = output.offset;
const VkImage dst_image = image.Handle();
const VkImageAspectFlags aspect = image.AspectMask();
const VkExtent3D extent{
.width = image.info.size.width,
.height = image.info.size.height,
.depth = image.info.size.depth,
};
scheduler.Record([this, set, descriptor_data, pc, gx, gy, gz, output_size, out_buffer,
out_offset, dst_image, aspect, extent,
is_initialized](vk::CommandBuffer cmdbuf) {
if (dst_image == VK_NULL_HANDLE || out_buffer == VK_NULL_HANDLE) {
return;
}
device.GetLogical().UpdateDescriptorSet(set, *descriptor_template, descriptor_data);
cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_COMPUTE, *pipeline);
cmdbuf.BindDescriptorSets(VK_PIPELINE_BIND_POINT_COMPUTE, *layout, 0, set, {});
cmdbuf.PushConstants(*layout, VK_SHADER_STAGE_COMPUTE_BIT, 0, sizeof(pc), &pc);
cmdbuf.Dispatch(gx, gy, gz);
const VkBufferMemoryBarrier buffer_barrier{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_SHADER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.buffer = out_buffer,
.offset = out_offset,
.size = output_size,
};
VkAccessFlags pre_copy_access = VK_ACCESS_NONE;
VkImageLayout pre_copy_layout = VK_IMAGE_LAYOUT_UNDEFINED;
VkPipelineStageFlags pre_copy_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT;
if (is_initialized) {
pre_copy_access = VK_ACCESS_SHADER_READ_BIT;
pre_copy_layout = VK_IMAGE_LAYOUT_GENERAL;
pre_copy_stage = vk::PIPELINE_STAGE_GRAPHICS_COMPUTE;
}
const VkImageMemoryBarrier pre_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = pre_copy_access,
.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.oldLayout = pre_copy_layout,
.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT | pre_copy_stage,
VK_PIPELINE_STAGE_TRANSFER_BIT, 0, {}, buffer_barrier, pre_copy);
const VkBufferImageCopy copy{
.bufferOffset = out_offset,
.bufferRowLength = 0,
.bufferImageHeight = 0,
.imageSubresource{
.aspectMask = aspect,
.mipLevel = 0,
.baseArrayLayer = 0,
.layerCount = 1,
},
.imageOffset = {0, 0, 0},
.imageExtent = extent,
};
cmdbuf.CopyBufferToImage(out_buffer, dst_image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, copy);
const VkImageMemoryBarrier post_copy{
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.pNext = nullptr,
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
.dstAccessMask = VK_ACCESS_SHADER_READ_BIT,
.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_GENERAL,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.image = dst_image,
.subresourceRange{
.aspectMask = aspect,
.baseMipLevel = 0,
.levelCount = VK_REMAINING_MIP_LEVELS,
.baseArrayLayer = 0,
.layerCount = VK_REMAINING_ARRAY_LAYERS,
},
};
cmdbuf.PipelineBarrier(VK_PIPELINE_STAGE_TRANSFER_BIT, vk::PIPELINE_STAGE_GRAPHICS_COMPUTE,
0, {}, {}, post_copy);
});
}
} // namespace Vulkan
@@ -164,63 +164,4 @@ private:
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzle2DPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle2DPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle2DPass();
[[nodiscard]] static bool IsSupported(const Device& device,
const VideoCommon::ImageInfo& info);
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles);
void UnswizzleFrom(Image& image, VkBuffer source_buffer, VkDeviceSize source_offset,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearSwizzle2DPass final : public ComputePass {
public:
explicit BlockLinearSwizzle2DPass(const Device& device_, Scheduler& scheduler_,
DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearSwizzle2DPass();
void SwizzleInto(Image& image, VkBuffer dst_buffer, VkDeviceSize dst_offset,
bool foreign_ownership);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
class BlockLinearUnswizzle3DBufferPass final : public ComputePass {
public:
explicit BlockLinearUnswizzle3DBufferPass(
const Device& device_, Scheduler& scheduler_, DescriptorPool& descriptor_pool_,
StagingBufferPool& staging_buffer_pool_,
ComputePassDescriptorQueue& compute_pass_descriptor_queue_);
~BlockLinearUnswizzle3DBufferPass();
[[nodiscard]] static bool IsSupported(const Device& device, const VideoCommon::ImageInfo& info);
void Unswizzle(Image& image, const StagingBufferRef& swizzled,
std::span<const VideoCommon::SwizzleParameters> swizzles);
private:
Scheduler& scheduler;
StagingBufferPool& staging_buffer_pool;
ComputePassDescriptorQueue& compute_pass_descriptor_queue;
};
} // namespace Vulkan
@@ -13,8 +13,7 @@ namespace Vulkan {
MultiRangeBufferCache::MultiRangeBufferCache(const Device& device) {
sparse_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsBufferDeviceAddressSupported()) {
sparse_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
@@ -28,14 +27,9 @@ MultiRangeBufferCache::MultiRangeBufferCache(const Device& device) {
}
block_size = queried;
sparse_memory_type_bits = memory_type_bits;
sparse_budget = device.GetSparseAddressSpaceSize();
use_sparse = true;
}
void MultiRangeBufferCache::ReserveSparseAddressSpace(VkDeviceSize size) noexcept {
sparse_budget -= (std::min)(sparse_budget, size);
}
VkDeviceSize MultiRangeBufferCache::QueryBlockSize(const Device& device,
u32& memory_type_bits) const {
const VkDevice logical = *device.GetLogical();
@@ -106,26 +100,15 @@ bool MultiRangeBufferCache::CanBindSparse(std::span<const MultiRangeSource> sour
SparseBuffer MultiRangeBufferCache::CreateSparse(const Device& device, Scheduler& scheduler,
std::span<const MultiRangeSource> sources,
VkDeviceSize total, VkBufferCreateFlags flags,
VkBufferUsageFlags usage,
VkExternalMemoryHandleTypeFlags handle_types) {
VkDeviceSize total) {
const VkDevice logical = *device.GetLogical();
const auto& dld = device.GetDispatchLoader();
const VkExternalMemoryBufferCreateInfo external_info{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.pNext = nullptr,
.handleTypes = handle_types,
};
const void* next = nullptr;
if (handle_types != 0) {
next = &external_info;
}
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = next,
.flags = flags,
.pNext = nullptr,
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT,
.size = total,
.usage = usage,
.usage = sparse_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
@@ -135,27 +118,6 @@ SparseBuffer MultiRangeBufferCache::CreateSparse(const Device& device, Scheduler
return SparseBuffer{};
}
SparseBuffer handle{raw, logical, dld};
const VkBufferMemoryRequirementsInfo2 reqs_info{
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2,
.pNext = nullptr,
.buffer = raw,
};
VkMemoryRequirements2 reqs2{
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
.pNext = nullptr,
.memoryRequirements = {},
};
dld.vkGetBufferMemoryRequirements2(logical, &reqs_info, &reqs2);
const VkMemoryRequirements& requirements = reqs2.memoryRequirements;
if (requirements.alignment == 0 || (block_size % requirements.alignment) != 0) {
return SparseBuffer{};
}
for (const MultiRangeSource& source : sources) {
if (source.memory_type >= 32 ||
((requirements.memoryTypeBits >> source.memory_type) & 1) == 0) {
return SparseBuffer{};
}
}
std::vector<VkSparseMemoryBind> binds;
binds.reserve(sources.size());
VkDeviceSize resource_offset = 0;
@@ -223,16 +185,10 @@ void MultiRangeBufferCache::RetireEntry(Scheduler& scheduler, Entry& entry) {
scheduler.Wait(oldest);
DrainRetired(scheduler);
}
VkDeviceSize sparse_size = 0;
if (entry.sparse_handle) {
sparse_size = entry.size;
}
sparse_retiring += sparse_size;
retired.push_back(Retired{
.handle = std::move(entry.sparse_handle),
.gathered = std::move(entry.gathered),
.tick = scheduler.CurrentTick(),
.sparse_size = sparse_size,
});
}
@@ -240,8 +196,6 @@ void MultiRangeBufferCache::DrainRetired(Scheduler& scheduler) {
size_t index = 0;
while (index < retired.size()) {
if (scheduler.IsFree(retired[index].tick)) {
sparse_in_use -= (std::min)(sparse_in_use, retired[index].sparse_size);
sparse_retiring -= (std::min)(sparse_retiring, retired[index].sparse_size);
if (index + 1 != retired.size()) {
retired[index] = std::move(retired.back());
}
@@ -267,7 +221,6 @@ MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& schedu
const auto it = entries.find(key);
if (it != entries.end() && it->second.geometry == geometry && it->second.size == total) {
Entry& entry = it->second;
entry.last_use = scheduler.CurrentTick();
if (entry.content != content) {
entry.content = content;
entry.dirty = true;
@@ -295,14 +248,9 @@ MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& schedu
entry.geometry = geometry;
entry.content = content;
entry.size = total;
entry.last_use = scheduler.CurrentTick();
if (CanBindSparse(sources) && FitsSparse(scheduler, total)) {
entry.sparse_handle = CreateSparse(
device, scheduler, sources, total,
VK_BUFFER_CREATE_SPARSE_BINDING_BIT | VK_BUFFER_CREATE_SPARSE_ALIASED_BIT,
sparse_usage, 0);
if (CanBindSparse(sources)) {
entry.sparse_handle = CreateSparse(device, scheduler, sources, total);
if (entry.sparse_handle) {
sparse_in_use += total;
entry.owners.reserve(sources.size());
for (const MultiRangeSource& source : sources) {
entry.owners.push_back(source.handle);
@@ -312,9 +260,7 @@ MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& schedu
if (!entry.sparse_handle) {
VkBufferUsageFlags flags = VK_BUFFER_USAGE_TRANSFER_SRC_BIT |
VK_BUFFER_USAGE_TRANSFER_DST_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT |
VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (device.IsBufferDeviceAddressSupported()) {
flags |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
@@ -355,86 +301,6 @@ MultiRangeRef MultiRangeBufferCache::Get(const Device& device, Scheduler& schedu
return ref;
}
MultiRangeRef MultiRangeBufferCache::GetView(const Device& device, Scheduler& scheduler, u64 key,
std::span<const MultiRangeSource> sources,
VkDeviceSize total, VkBufferUsageFlags usage,
VkExternalMemoryHandleTypeFlags handle_types) {
if (!use_sparse || !views_supported || sources.empty() || total == 0) {
return MultiRangeRef{};
}
if (!retired.empty()) {
DrainRetired(scheduler);
}
const u64 geometry = HashSources(sources);
const auto it = entries.find(key);
if (it != entries.end() && it->second.sparse_handle && it->second.geometry == geometry &&
it->second.size == total) {
it->second.last_use = scheduler.CurrentTick();
return MultiRangeRef{
.handle = *it->second.sparse_handle,
.address = it->second.address,
.size = total,
.sparse = true,
.needs_gather = false,
};
}
if (it != entries.end()) {
RetireEntry(scheduler, it->second);
entries.erase(it);
}
if (!FitsSparse(scheduler, total)) {
return MultiRangeRef{};
}
Entry entry{};
entry.sparse_handle = CreateSparse(device, scheduler, sources, total,
VK_BUFFER_CREATE_SPARSE_BINDING_BIT, usage, handle_types);
if (!entry.sparse_handle) {
views_supported = false;
return MultiRangeRef{};
}
sparse_in_use += total;
entry.geometry = geometry;
entry.size = total;
entry.last_use = scheduler.CurrentTick();
entry.dirty = false;
if ((usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
entry.address = device.GetLogical().GetBufferDeviceAddress(*entry.sparse_handle);
}
const MultiRangeRef ref{
.handle = *entry.sparse_handle,
.address = entry.address,
.size = total,
.sparse = true,
.needs_gather = false,
};
entries.emplace(key, std::move(entry));
return ref;
}
bool MultiRangeBufferCache::FitsSparse(Scheduler& scheduler, VkDeviceSize total) {
if (total > sparse_budget) {
return false;
}
const u64 current_tick = scheduler.CurrentTick();
while (sparse_in_use - sparse_retiring > sparse_budget - total) {
auto victim = entries.end();
for (auto it = entries.begin(); it != entries.end(); ++it) {
if (!it->second.sparse_handle || it->second.last_use >= current_tick) {
continue;
}
if (victim == entries.end() || it->second.last_use < victim->second.last_use) {
victim = it;
}
}
if (victim == entries.end()) {
break;
}
RetireEntry(scheduler, victim->second);
entries.erase(victim);
}
return sparse_in_use <= sparse_budget - total;
}
void MultiRangeBufferCache::MarkGathered(u64 key) {
if (auto const it = entries.find(key); it != entries.end()) {
it->second.dirty = false;
@@ -53,19 +53,12 @@ public:
std::span<const MultiRangeSource> sources,
VkDeviceSize total);
[[nodiscard]] MultiRangeRef GetView(const Device& device, Scheduler& scheduler, u64 key,
std::span<const MultiRangeSource> sources,
VkDeviceSize total, VkBufferUsageFlags usage,
VkExternalMemoryHandleTypeFlags handle_types);
void MarkGathered(u64 key);
void Invalidate(u64 key);
void DropOwner(Scheduler& scheduler, VkBuffer owner);
void ReserveSparseAddressSpace(VkDeviceSize size) noexcept;
VkDeviceSize block_size{DEFAULT_BLOCK_SIZE};
bool use_sparse{};
@@ -74,7 +67,6 @@ private:
SparseBuffer handle;
vk::Buffer gathered;
u64 tick{};
VkDeviceSize sparse_size{};
};
struct Entry {
@@ -85,7 +77,6 @@ private:
VkDeviceSize size{};
u64 geometry{};
u64 content{};
u64 last_use{};
bool dirty{true};
};
@@ -97,11 +88,7 @@ private:
[[nodiscard]] SparseBuffer CreateSparse(const Device& device, Scheduler& scheduler,
std::span<const MultiRangeSource> sources,
VkDeviceSize total, VkBufferCreateFlags flags,
VkBufferUsageFlags usage,
VkExternalMemoryHandleTypeFlags handle_types);
[[nodiscard]] bool FitsSparse(Scheduler& scheduler, VkDeviceSize total);
VkDeviceSize total);
[[nodiscard]] VkDeviceSize QueryBlockSize(const Device& device, u32& memory_type_bits) const;
@@ -113,10 +100,6 @@ private:
boost::container::static_vector<Retired, MAX_RETIRED> retired;
u32 sparse_memory_type_bits{};
VkBufferUsageFlags sparse_usage{};
VkDeviceSize sparse_budget{};
VkDeviceSize sparse_in_use{};
VkDeviceSize sparse_retiring{};
bool views_supported{true};
};
} // namespace Vulkan
@@ -451,7 +451,6 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_,
.has_broken_fp32_denorm_flush = driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY,
.ignore_nan_fp_comparisons = false,
.has_broken_spirv_subgroup_mask_vector_extract_dynamic = false,
.max_shared_memory_size = device.GetMaxComputeSharedMemorySize(),
.has_broken_robust =
device.IsNvidia() && device.GetNvidiaArch() <= NvidiaArchitecture::Arch_Pascal,
.min_ssbo_alignment = device.GetStorageBufferAlignment(),
@@ -970,6 +969,19 @@ std::unique_ptr<ComputePipeline> PipelineCache::CreateComputePipeline(
}
auto program{TranslateProgram(pools.inst, pools.block, env, cfg, host_info)};
const VkDriverIdKHR driver_id = device.GetDriverID();
const bool needs_shared_mem_clamp =
driver_id == VK_DRIVER_ID_QUALCOMM_PROPRIETARY ||
driver_id == VK_DRIVER_ID_ARM_PROPRIETARY;
const u32 max_shared_memory = device.GetMaxComputeSharedMemorySize();
if (needs_shared_mem_clamp && program.shared_memory_size > max_shared_memory) {
LOG_WARNING(Render_Vulkan,
"Compute shader {:#016x} requests {}KB shared memory but device max is {}KB - clamping",
key.unique_hash,
program.shared_memory_size / 1024,
max_shared_memory / 1024);
program.shared_memory_size = max_shared_memory;
}
const std::vector<u32> code{EmitSPIRV(profile, program)};
device.SaveShader(code);
vk::ShaderModule spv_module{BuildShader(device, code)};
@@ -225,13 +225,6 @@ RasterizerVulkan::RasterizerVulkan(Core::Frontend::EmuWindow& emu_window_, Tegra
fence_manager(*this, gpu, texture_cache, buffer_cache, query_cache, device, scheduler),
wfi_event(device.GetLogical().CreateEvent()) {
scheduler.SetQueryCache(query_cache);
if (Settings::values.use_unified_memory.GetValue() && device_memory.IsBackingShared()) {
buffer_cache_runtime.TryEnableUnifiedMemory(
device_memory.GetPhysicalBase(), device_memory.GetPhysicalSize(),
device_memory.GetBackingHardwareBuffers(),
device_memory.GetBackingHardwareBufferWindowSize(),
device_memory.GetBackingHardwareBufferBase());
}
}
RasterizerVulkan::~RasterizerVulkan() {
@@ -129,10 +129,6 @@ public:
return master_semaphore->IsFree(tick);
}
void RefreshTick() {
master_semaphore->Refresh();
}
/// Waits for the given GPU tick, optionally pacing frames.
void Wait(u64 tick, double target_fps = 0.0) {
if (tick > 0) {
@@ -968,12 +968,6 @@ TextureCacheRuntime::TextureCacheRuntime(const Device& device_, Scheduler& sched
bl3d_unswizzle_pass.emplace(device, scheduler, descriptor_pool,
staging_buffer_pool, compute_pass_descriptor_queue);
}
bl2d_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue);
bl3db_unswizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue);
bl2d_swizzle_pass.emplace(device, scheduler, descriptor_pool, staging_buffer_pool,
compute_pass_descriptor_queue);
}
void TextureCacheRuntime::Finish() {
@@ -1900,12 +1894,6 @@ Image::Image(TextureCacheRuntime& runtime_, const ImageInfo& info_, GPUVAddr gpu
}
flags |= VideoCommon::ImageFlagBits::Converted;
flags |= VideoCommon::ImageFlagBits::CostlyLoad;
} else if (runtime->bl2d_unswizzle_pass &&
BlockLinearUnswizzle2DPass::IsSupported(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
} else if (runtime->bl3db_unswizzle_pass &&
BlockLinearUnswizzle3DBufferPass::IsSupported(runtime->device, info)) {
flags |= VideoCommon::ImageFlagBits::AcceleratedUpload;
}
if (IsPixelFormatBCn(info.format) && !runtime->device.IsOptimalBcnSupported()) {
flags |= VideoCommon::ImageFlagBits::Converted;
@@ -3149,19 +3137,10 @@ void TextureCacheRuntime::AccelerateImageUpload(
std::span<const VideoCommon::SwizzleParameters> swizzles,
u32 z_start, u32 z_count) {
if (astc_decoder_pass && WillUseAcceleratedAstcDecode(device, image.info)) {
if (IsPixelFormatASTC(image.info.format)) {
return astc_decoder_pass->Assemble(image, map, swizzles);
}
if (bl2d_unswizzle_pass && BlockLinearUnswizzle2DPass::IsSupported(device, image.info)) {
return bl2d_unswizzle_pass->Unswizzle(image, map, swizzles);
}
if (bl3db_unswizzle_pass && z_count == 0 &&
BlockLinearUnswizzle3DBufferPass::IsSupported(device, image.info)) {
return bl3db_unswizzle_pass->Unswizzle(image, map, swizzles);
}
if (!Settings::values.gpu_unswizzle_enabled.GetValue() || !bl3d_unswizzle_pass) {
if (IsPixelFormatBCn(image.info.format) && image.info.type == ImageType::e3D) {
ASSERT(false && "GPU unswizzle is disabled for BCn 3D texture");
@@ -3177,89 +3156,6 @@ void TextureCacheRuntime::AccelerateImageUpload(
ASSERT(false);
}
bool TextureCacheRuntime::IsUnifiedMemoryBindable() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
return import != nullptr && import->IsValid() && import->IsBindable();
}
u64 TextureCacheRuntime::UnifiedMemoryBase() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr) {
return 0;
}
return import->GetBaseOffset();
}
u64 TextureCacheRuntime::UnifiedMemorySize() const noexcept {
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr) {
return 0;
}
return import->GetSize();
}
bool TextureCacheRuntime::CanUploadImageDirectly(const VideoCommon::ImageInfo& info) const {
return bl2d_unswizzle_pass.has_value() &&
BlockLinearUnswizzle2DPass::IsSupported(device, info);
}
std::optional<HostMemoryImport::Range> TextureCacheRuntime::ResolveDirectRange(u64 relative,
u64 size) const {
if (size > device.GetMaxStorageBufferRange()) {
return std::nullopt;
}
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
if (import == nullptr) {
return std::nullopt;
}
const auto range = import->ResolveRange(relative, size);
if (!range || range->buffer == VK_NULL_HANDLE ||
(range->offset % device.GetStorageBufferAlignment()) != 0) {
return std::nullopt;
}
return range;
}
bool TextureCacheRuntime::UploadImageDirectly(
Image& image, u64 relative, std::span<const VideoCommon::SwizzleParameters> swizzles) {
const auto range = ResolveDirectRange(relative, image.guest_size_bytes);
if (!range) {
return false;
}
bl2d_unswizzle_pass->UnswizzleFrom(image, range->buffer, range->offset, swizzles);
return true;
}
bool TextureCacheRuntime::CanDownloadImageDirectly(const VideoCommon::ImageInfo& info) const {
return bl2d_swizzle_pass.has_value() && BlockLinearUnswizzle2DPass::IsSupported(device, info);
}
bool TextureCacheRuntime::DownloadImageDirectly(Image& image, u64 relative) {
if (image.unswizzled_size_bytes > device.GetMaxStorageBufferRange()) {
return false;
}
const auto range = ResolveDirectRange(relative, image.guest_size_bytes);
if (!range) {
return false;
}
const HostMemoryImport* const import = memory_allocator.GetHostMemoryImport();
bl2d_swizzle_pass->SwizzleInto(image, range->buffer, range->offset,
import->NeedsForeignOwnershipTransfer());
return true;
}
u64 TextureCacheRuntime::CurrentTick() const noexcept {
return scheduler.CurrentTick();
}
bool TextureCacheRuntime::IsTickRetired(u64 tick) {
if (scheduler.IsFree(tick)) {
return true;
}
scheduler.RefreshTick();
return scheduler.IsFree(tick);
}
void TextureCacheRuntime::TransitionImageLayout(Image& image) {
if (!image.ExchangeInitialization()) {
VkImageMemoryBarrier barrier{
@@ -101,28 +101,6 @@ public:
std::span<const VideoCommon::SwizzleParameters>,
u32 z_start, u32 z_count);
[[nodiscard]] bool IsUnifiedMemoryBindable() const noexcept;
[[nodiscard]] u64 UnifiedMemoryBase() const noexcept;
[[nodiscard]] u64 UnifiedMemorySize() const noexcept;
[[nodiscard]] bool CanUploadImageDirectly(const VideoCommon::ImageInfo& info) const;
bool UploadImageDirectly(Image& image, u64 relative,
std::span<const VideoCommon::SwizzleParameters> swizzles);
[[nodiscard]] bool CanDownloadImageDirectly(const VideoCommon::ImageInfo& info) const;
bool DownloadImageDirectly(Image& image, u64 relative);
[[nodiscard]] std::optional<HostMemoryImport::Range> ResolveDirectRange(u64 relative,
u64 size) const;
[[nodiscard]] u64 CurrentTick() const noexcept;
[[nodiscard]] bool IsTickRetired(u64 tick);
void InsertUploadMemoryBarrier() {}
void TransitionImageLayout(Image& image);
@@ -181,9 +159,6 @@ public:
std::optional<ASTCDecoderPass> astc_decoder_pass;
std::optional<BlockLinearUnswizzle3DPass> bl3d_unswizzle_pass;
std::optional<BlockLinearUnswizzle2DPass> bl2d_unswizzle_pass;
std::optional<BlockLinearUnswizzle3DBufferPass> bl3db_unswizzle_pass;
std::optional<BlockLinearSwizzle2DPass> bl2d_swizzle_pass;
const Settings::ResolutionScalingInfo& resolution;
std::array<std::vector<VkFormat>, VideoCore::Surface::MaxPixelFormat> view_formats;
@@ -611,7 +586,6 @@ struct TextureCacheParams {
static constexpr bool HAS_DEVICE_MEMORY_INFO = true;
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = true;
static constexpr bool HAS_MSAA_DOWNLOADS = true;
static constexpr bool USE_UNIFIED_MEMORY = true;
using Runtime = Vulkan::TextureCacheRuntime;
using Image = Vulkan::Image;
@@ -95,11 +95,6 @@ struct ImageBase {
u32 scale_rating = 0;
u64 scale_tick = 0;
bool has_scaled = false;
u64 direct_upload_tick = 0;
bool direct_upload_blocked = false;
bool eviction_pending = false;
u64 eviction_tick = 0;
u64 eviction_modification_tick = 0;
size_t channel = 0;
+1 -171
View File
@@ -119,16 +119,13 @@ void TextureCache<P>::RunGarbageCollector() {
bool aggressive_mode = false;
u64 ticks_to_destroy = 0;
size_t num_iterations = 0;
size_t num_downloads = 0;
const auto Configure = [&](bool allow_aggressive) {
high_priority_mode = total_used_memory >= expected_memory;
aggressive_mode = allow_aggressive && total_used_memory >= critical_memory;
ticks_to_destroy = aggressive_mode ? 10ULL : high_priority_mode ? 25ULL : 50ULL;
num_iterations = aggressive_mode ? 40 : (high_priority_mode ? 20 : 10);
num_downloads = MAX_GC_DOWNLOADS_PER_PASS;
};
const auto Cleanup = [this, &num_iterations, &num_downloads, &high_priority_mode,
&aggressive_mode](ImageId image_id) {
const auto Cleanup = [this, &num_iterations, &high_priority_mode, &aggressive_mode](ImageId image_id) {
if (num_iterations == 0) {
return true;
}
@@ -137,21 +134,11 @@ void TextureCache<P>::RunGarbageCollector() {
if (True(image.flags & ImageFlagBits::IsDecoding)) {
return false;
}
if (image.eviction_pending) {
return false;
}
const bool must_download = IsDownloadable(image) && False(image.flags & ImageFlagBits::BadOverlap);
if ((!aggressive_mode && True(image.flags & ImageFlagBits::CostlyLoad)) || (!high_priority_mode && must_download)) {
return false;
}
if (must_download) {
if (num_downloads == 0) {
return false;
}
--num_downloads;
if (StartEviction(image_id, image)) {
return false;
}
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(map, copies);
@@ -182,7 +169,6 @@ void TextureCache<P>::RunGarbageCollector() {
template <class P>
void TextureCache<P>::TickFrame() {
FinishEvictions();
// If we can obtain the memory info, use it instead of the estimate.
if (runtime.CanReportMemoryUsage()) {
total_used_memory = runtime.GetDeviceMemoryUsage();
@@ -599,15 +585,6 @@ FramebufferId TextureCache<P>::GetFramebufferId(const RenderTargets& key) {
template <class P>
void TextureCache<P>::WriteMemory(DAddr cpu_addr, size_t size) {
ForEachImageInRegion(cpu_addr, size, [this](ImageId image_id, Image& image) {
if constexpr (USE_UNIFIED_MEMORY) {
if (image.direct_upload_tick != 0) {
const u64 upload_tick = image.direct_upload_tick;
image.direct_upload_tick = 0;
if (!runtime.IsTickRetired(upload_tick)) {
image.direct_upload_blocked = true;
}
}
}
if (True(image.flags & ImageFlagBits::CpuModified)) {
return;
}
@@ -649,24 +626,15 @@ void TextureCache<P>::DownloadMemory(DAddr cpu_addr, size_t size) {
std::ranges::sort(images, [this](ImageId lhs, ImageId rhs) {
return slot_images[lhs].modification_tick < slot_images[rhs].modification_tick;
});
bool pending_unified = false;
for (const ImageId image_id : images) {
Image& image = slot_images[image_id];
if (TryDownloadToUnifiedMemory(image)) {
pending_unified = true;
continue;
}
auto map = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(map, copies);
runtime.Finish();
pending_unified = false;
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies, map.mapped_span,
swizzle_data_buffer);
}
if (pending_unified) {
runtime.Finish();
}
}
template <class P>
@@ -1177,143 +1145,11 @@ void TextureCache<P>::RefreshContents(Image& image, ImageId image_id) {
QueueAsyncUnswizzle(image, image_id);
return;
}
if (True(image.flags & ImageFlagBits::AcceleratedUpload) &&
TryUploadFromUnifiedMemory(image)) {
runtime.InsertUploadMemoryBarrier();
return;
}
auto staging = runtime.UploadStagingBuffer(MapSizeBytes(image));
UploadImageContents(image, staging);
runtime.InsertUploadMemoryBarrier();
}
template <class P>
std::optional<u64> TextureCache<P>::ResolveUnifiedImageOffset(
[[maybe_unused]] const ImageBase& image) {
if constexpr (USE_UNIFIED_MEMORY) {
if (image.guest_size_bytes == 0 || !runtime.IsUnifiedMemoryBindable()) {
return std::nullopt;
}
const u8* const first = gpu_memory->GetSpan(image.gpu_addr, image.guest_size_bytes);
if (first == nullptr) {
return std::nullopt;
}
const u64 phys_offset = static_cast<u64>(first - device_memory.GetPhysicalBase());
const u64 unified_base = runtime.UnifiedMemoryBase();
if (phys_offset < unified_base) {
return std::nullopt;
}
const u64 relative = phys_offset - unified_base;
const u64 unified_size = runtime.UnifiedMemorySize();
if (relative >= unified_size || unified_size - relative < image.guest_size_bytes) {
return std::nullopt;
}
return relative;
} else {
return std::nullopt;
}
}
template <class P>
bool TextureCache<P>::TryUploadFromUnifiedMemory([[maybe_unused]] Image& image) {
if constexpr (USE_UNIFIED_MEMORY) {
if (image.direct_upload_blocked || !runtime.CanUploadImageDirectly(image.info)) {
return false;
}
const auto relative = ResolveUnifiedImageOffset(image);
if (!relative) {
return false;
}
const auto swizzles = FullUploadSwizzles(image.info);
if (!runtime.UploadImageDirectly(image, *relative, FixSmallVectorADL(swizzles))) {
return false;
}
image.direct_upload_tick = runtime.CurrentTick();
return true;
} else {
return false;
}
}
template <class P>
bool TextureCache<P>::TryDownloadToUnifiedMemory([[maybe_unused]] Image& image) {
if constexpr (USE_UNIFIED_MEMORY) {
if (!runtime.CanDownloadImageDirectly(image.info)) {
return false;
}
if (image.info.resources.layers > 1 &&
image.info.layer_stride != CalculateLayerStride(image.info)) {
return false;
}
const auto relative = ResolveUnifiedImageOffset(image);
if (!relative) {
return false;
}
return runtime.DownloadImageDirectly(image, *relative);
} else {
return false;
}
}
template <class P>
bool TextureCache<P>::StartEviction([[maybe_unused]] ImageId image_id,
[[maybe_unused]] Image& image) {
if constexpr (requires { runtime.IsTickRetired(u64{}); }) {
auto staging = runtime.DownloadStagingBuffer(image.unswizzled_size_bytes, true);
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
image.DownloadMemory(staging, copies);
eviction_staging.emplace_back(image_id, staging);
image.eviction_pending = true;
image.eviction_tick = runtime.CurrentTick();
image.eviction_modification_tick = image.modification_tick;
return true;
} else {
return false;
}
}
template <class P>
void TextureCache<P>::FinishEvictions() {
if constexpr (requires { runtime.IsTickRetired(u64{}); }) {
size_t index = 0;
while (index < eviction_staging.size()) {
const ImageId image_id = eviction_staging[index].first;
Image& image = slot_images[image_id];
if (!runtime.IsTickRetired(image.eviction_tick)) {
++index;
continue;
}
const bool unchanged = image.modification_tick == image.eviction_modification_tick;
if (unchanged && False(image.flags & ImageFlagBits::CpuModified)) {
const auto copies = FixSmallVectorADL(FullDownloadCopies(image.info));
SwizzleImage(*gpu_memory, image.gpu_addr, image.info, copies,
eviction_staging[index].second.mapped_span, swizzle_data_buffer);
}
CancelEviction(image_id);
if (!unchanged) {
continue;
}
if (True(image.flags & ImageFlagBits::Tracked)) {
UntrackImage(image, image_id);
}
UnregisterImage(image_id);
DeleteImage(image_id, image.scale_tick > frame_tick + 5);
}
}
}
template <class P>
void TextureCache<P>::CancelEviction(ImageId image_id) {
slot_images[image_id].eviction_pending = false;
const auto it = std::ranges::find_if(
eviction_staging, [image_id](const auto& entry) { return entry.first == image_id; });
if (it == eviction_staging.end()) {
return;
}
async_buffers_death_ring.emplace_back(std::move(it->second));
eviction_staging.erase(it);
}
template <class P>
template <typename StagingBuffer>
void TextureCache<P>::UploadImageContents(Image& image, StagingBuffer& staging) {
@@ -2480,9 +2316,6 @@ void TextureCache<P>::UntrackImage(ImageBase& image, ImageId image_id) {
template <class P>
void TextureCache<P>::DeleteImage(ImageId image_id, bool immediate_delete) {
ImageBase& image = slot_images[image_id];
if (image.eviction_pending) {
CancelEviction(image_id);
}
if (image.HasScaled()) {
total_used_memory -= GetScaledImageSizeBytes(image);
}
@@ -2670,9 +2503,6 @@ void TextureCache<P>::SynchronizeAliases(ImageId image_id) {
template <class P>
void TextureCache<P>::PrepareImage(ImageId image_id, bool is_modification, bool invalidate) {
Image& image = slot_images[image_id];
if (image.eviction_pending) {
CancelEviction(image_id);
}
if (invalidate) {
image.flags &= ~(ImageFlagBits::CpuModified | ImageFlagBits::GpuModified);
if (False(image.flags & ImageFlagBits::Tracked)) {
@@ -109,7 +109,6 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
static constexpr bool HAS_DEVICE_MEMORY_INFO = P::HAS_DEVICE_MEMORY_INFO;
/// True when the API can do asynchronous texture downloads.
static constexpr bool IMPLEMENTS_ASYNC_DOWNLOADS = P::IMPLEMENTS_ASYNC_DOWNLOADS;
static constexpr bool USE_UNIFIED_MEMORY = P::USE_UNIFIED_MEMORY;
static constexpr size_t UNSET_CHANNEL{(std::numeric_limits<size_t>::max)()};
@@ -122,7 +121,6 @@ class TextureCache : public VideoCommon::ChannelSetupCaches<TextureCacheChannelI
static constexpr s64 DEFAULT_EXPECTED_MEMORY = 1_GiB + 125_MiB;
static constexpr s64 DEFAULT_CRITICAL_MEMORY = 1_GiB + 625_MiB;
static constexpr size_t GC_EMERGENCY_COUNTS = 2;
static constexpr size_t MAX_GC_DOWNLOADS_PER_PASS = 4;
using Runtime = typename P::Runtime;
using Image = typename P::Image;
@@ -310,18 +308,6 @@ private:
void RefreshContents(Image& image, ImageId image_id);
[[nodiscard]] std::optional<u64> ResolveUnifiedImageOffset(const ImageBase& image);
bool TryUploadFromUnifiedMemory(Image& image);
bool TryDownloadToUnifiedMemory(Image& image);
bool StartEviction(ImageId image_id, Image& image);
void FinishEvictions();
void CancelEviction(ImageId image_id);
/// Upload data from guest to an image
template <typename StagingBuffer>
void UploadImageContents(Image& image, StagingBuffer& staging_buffer);
@@ -498,7 +484,6 @@ private:
std::vector<AsyncBuffer> uncommitted_async_buffers;
std::deque<std::vector<AsyncBuffer>> async_buffers;
std::deque<AsyncBuffer> async_buffers_death_ring;
std::vector<std::pair<ImageId, AsyncBuffer>> eviction_staging;
struct LRUItemParams {
using ObjectType = ImageId;
@@ -21,7 +21,6 @@
#include "common/assert.h"
#include "common/fs/fs.h"
#include "common/fs/path_util.h"
#include "common/host_memory.h"
#include "common/literals.h"
#include <ranges>
#include "common/settings.h"
@@ -1046,7 +1045,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
FOR_EACH_VK_FEATURE_EXT(FEATURE_EXTENSION);
FOR_EACH_VK_EXTENSION(EXTENSION);
FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION);
extensions.depth_stencil_resolve =
extensions.depth_stencil_resolve &&
@@ -1065,13 +1063,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
extensions.robustness_2 = false;
}
#ifdef __ANDROID__
if (extensions.external_memory_ahb && !extensions.queue_family_foreign) {
loaded_extensions.erase(VK_ANDROID_EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER_EXTENSION_NAME);
extensions.external_memory_ahb = false;
}
#endif
#undef FEATURE_EXTENSION
#undef EXTENSION
@@ -1238,21 +1229,6 @@ bool Device::GetSuitability(bool requires_swapchain) {
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_CUSTOM_BORDER_COLOR_PROPERTIES_EXT;
SetNext(next, properties.custom_border_color);
}
if (extensions.external_memory_host) {
properties.external_memory_host.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_MEMORY_HOST_PROPERTIES_EXT;
SetNext(next, properties.external_memory_host);
}
if (extensions.maintenance3 || instance_version >= VK_API_VERSION_1_1) {
properties.maintenance3.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_3_PROPERTIES;
SetNext(next, properties.maintenance3);
}
if (extensions.maintenance4 || features.maintenance4.maintenance4) {
properties.maintenance4.sType =
VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MAINTENANCE_4_PROPERTIES;
SetNext(next, properties.maintenance4);
}
// Perform the property fetch.
physical.GetProperties2(properties2);
@@ -1647,27 +1623,12 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory = 0;
u64 device_initial_usage = 0;
u64 local_memory = 0;
const auto heap_has_usable_type = [&mem_properties](size_t heap) {
for (u32 index = 0; index < mem_properties.memoryTypeCount; ++index) {
if (mem_properties.memoryTypes[index].heapIndex != heap) {
continue;
}
if ((mem_properties.memoryTypes[index].propertyFlags &
VK_MEMORY_PROPERTY_PROTECTED_BIT) == 0) {
return true;
}
}
return false;
};
for (size_t element = 0; element < num_properties; ++element) {
const bool is_heap_local =
(mem_properties.memoryHeaps[element].flags & VK_MEMORY_HEAP_DEVICE_LOCAL_BIT) != 0;
if (!is_integrated && !is_heap_local) {
continue;
}
if (!heap_has_usable_type(element)) {
continue;
}
valid_heap_memory.push_back(element);
if (is_heap_local) {
local_memory += mem_properties.memoryHeaps[element].size;
@@ -1679,12 +1640,6 @@ void Device::CollectPhysicalMemoryInfo() {
}
device_access_memory += mem_properties.memoryHeaps[element].size;
}
const u64 committed_backing = Common::GetCommittedBackingSize();
if (committed_backing != 0) {
LOG_INFO(Render_Vulkan, "Discounting {} MiB of guest memory committed by the host",
committed_backing >> 20);
local_memory -= (std::min)(local_memory, committed_backing);
}
if (is_integrated) {
const s64 available_memory = static_cast<s64>(device_access_memory - device_initial_usage);
const u64 memory_size = Settings::values.vram_usage_mode.GetValue() == Settings::VramUsageMode::Aggressive ? 6_GiB : 4_GiB;
@@ -1699,7 +1654,6 @@ void Device::CollectPhysicalMemoryInfo() {
device_access_memory = std::min<u64>(device_access_memory, normal_memory + scaler_memory);
}
}
device_access_memory -= (std::min)(device_access_memory, committed_backing);
}
void Device::CollectToolingInfo() {
@@ -86,7 +86,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(EXT, CONDITIONAL_RENDERING, conditional_rendering) \
EXTENSION(EXT, CONSERVATIVE_RASTERIZATION, conservative_rasterization) \
EXTENSION(EXT, DEPTH_RANGE_UNRESTRICTED, depth_range_unrestricted) \
EXTENSION(EXT, EXTERNAL_MEMORY_HOST, external_memory_host) \
EXTENSION(EXT, MEMORY_BUDGET, memory_budget) \
EXTENSION(EXT, ROBUSTNESS_2, robustness_2) \
EXTENSION(EXT, SAMPLER_FILTER_MINMAX, sampler_filter_minmax) \
@@ -118,14 +117,6 @@ VK_DEFINE_HANDLE(VmaAllocator)
EXTENSION(IMG, FILTER_CUBIC, filter_cubic_img) \
EXTENSION(QCOM, FILTER_CUBIC_WEIGHTS, filter_cubic_weights)
#ifdef __ANDROID__
#define FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION) \
EXTENSION(EXT, QUEUE_FAMILY_FOREIGN, queue_family_foreign) \
EXTENSION(ANDROID, EXTERNAL_MEMORY_ANDROID_HARDWARE_BUFFER, external_memory_ahb)
#else
#define FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION)
#endif
// Define extensions which must be supported.
#define FOR_EACH_VK_MANDATORY_EXTENSION(EXTENSION_NAME) \
EXTENSION_NAME(VK_EXT_VERTEX_ATTRIBUTE_DIVISOR_EXTENSION_NAME) \
@@ -900,34 +891,6 @@ FN_MAX_LIMIT_LIST
return extensions.conditional_rendering;
}
bool IsExtExternalMemoryHostSupported() const {
return extensions.external_memory_host;
}
bool IsExtExternalMemoryAhbSupported() const {
#ifdef __ANDROID__
return extensions.external_memory_ahb && extensions.queue_family_foreign;
#else
return false;
#endif
}
u64 GetMinImportedHostPointerAlignment() const {
return properties.external_memory_host.minImportedHostPointerAlignment;
}
u64 GetMaxBufferSize() const {
return properties.maintenance4.maxBufferSize;
}
u64 GetSparseAddressSpaceSize() const {
return properties.properties.limits.sparseAddressSpaceSize;
}
u64 GetMaxMemoryAllocationSize() const {
return properties.maintenance3.maxMemoryAllocationSize;
}
bool IsExtAstcDecodeModeSupported() const {
return extensions.astc_decode_mode;
}
@@ -1200,7 +1163,6 @@ private:
FOR_EACH_VK_FEATURE_1_4(FEATURE);
FOR_EACH_VK_FEATURE_EXT(FEATURE);
FOR_EACH_VK_EXTENSION(EXTENSION);
FOR_EACH_VK_PLATFORM_EXTENSION(EXTENSION);
#undef EXTENSION
#undef FEATURE
@@ -1232,12 +1194,9 @@ private:
VkPhysicalDeviceDescriptorBufferPropertiesEXT descriptor_buffer{};
VkPhysicalDeviceSubgroupSizeControlProperties subgroup_size_control{};
VkPhysicalDeviceTransformFeedbackPropertiesEXT transform_feedback{};
VkPhysicalDeviceMaintenance3Properties maintenance3{};
VkPhysicalDeviceMaintenance4Properties maintenance4{};
VkPhysicalDeviceMaintenance5PropertiesKHR maintenance5{};
VkPhysicalDeviceDepthStencilResolveProperties depth_stencil_resolve{};
VkPhysicalDeviceCustomBorderColorPropertiesEXT custom_border_color{};
VkPhysicalDeviceExternalMemoryHostPropertiesEXT external_memory_host{};
VkPhysicalDeviceProperties properties{};
};
@@ -25,34 +25,9 @@
#include "video_core/gpu_logging/gpu_logging.h"
#include "common/settings.h"
#ifdef __ANDROID__
#include <android/hardware_buffer.h>
#endif
namespace Vulkan {
namespace {
[[nodiscard]] std::optional<u32> FindImportMemoryType(
const VkPhysicalDeviceMemoryProperties &props, u32 type_mask) {
const auto find = [&](VkMemoryPropertyFlags wanted) -> std::optional<u32> {
for (u32 i = 0; i < props.memoryTypeCount; ++i) {
if (((type_mask >> i) & 1u) != 0 &&
(props.memoryTypes[i].propertyFlags & wanted) == wanted) {
return i;
}
}
return std::nullopt;
};
auto type_index = find(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT |
VK_MEMORY_PROPERTY_HOST_CACHED_BIT);
if (!type_index) {
type_index = find(VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT |
VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
}
return type_index;
}
// Helpers translating MemoryUsage to flags/usage
[[maybe_unused]] VkMemoryPropertyFlags MemoryUsagePropertyFlags(MemoryUsage usage) {
@@ -206,512 +181,6 @@ void MemoryCommit::Release() {
size = 0;
}
HostMemoryImport::HostMemoryImport(const Device &device_, void *base, size_t size,
std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base)
: device{device_} {
if (ImportHardwareBuffers(hardware_buffers, hardware_buffer_window, hardware_buffer_base,
size)) {
return;
}
if (device.IsTiler()) {
return;
}
if (!hardware_buffers.empty()) {
return;
}
if (ImportHostPointer(base, size)) {
return;
}
LOG_INFO(Render_Vulkan, "Unified memory disabled, no host memory import path");
}
bool HostMemoryImport::ImportHostPointer(void *base, size_t size) {
if (!device.IsExtExternalMemoryHostSupported()) {
return false;
}
const u64 alignment = device.GetMinImportedHostPointerAlignment();
if (alignment == 0 || !Common::IsAligned(reinterpret_cast<uintptr_t>(base), alignment) ||
!Common::IsAligned(size, alignment)) {
return false;
}
using namespace Common::Literals;
constexpr VkDeviceSize DesktopWindowSize = 4_GiB;
VkDeviceSize candidate_window = DesktopWindowSize;
const u64 max_buffer_size = device.GetMaxBufferSize();
if (max_buffer_size != 0 && max_buffer_size < candidate_window) {
candidate_window = max_buffer_size;
}
const u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
if (max_allocation_size != 0 && max_allocation_size < candidate_window) {
candidate_window = max_allocation_size;
}
candidate_window = Common::AlignDown(candidate_window, alignment);
if (candidate_window == 0) {
return false;
}
window_size = candidate_window;
buffer_usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
const auto &logical = device.GetLogical();
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
for (size_t offset = 0; offset < size; offset += window_size) {
u8 *const window_base = static_cast<u8 *>(base) + offset;
const VkDeviceSize window_len =
(std::min)(static_cast<VkDeviceSize>(size - offset), window_size);
VkMemoryHostPointerPropertiesEXT host_props{
.sType = VK_STRUCTURE_TYPE_MEMORY_HOST_POINTER_PROPERTIES_EXT,
.pNext = nullptr,
.memoryTypeBits = 0,
};
if (logical.GetMemoryHostPointerPropertiesEXT(
VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT, window_base,
&host_props) != VK_SUCCESS ||
host_props.memoryTypeBits == 0) {
break;
}
const VkExternalMemoryBufferCreateInfo external_info{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.pNext = nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT,
};
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &external_info,
.flags = 0,
.size = window_len,
.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer new_buffer{};
if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) {
break;
}
const VkMemoryRequirements requirements =
logical.GetBufferMemoryRequirements(new_buffer);
const u32 type_mask = requirements.memoryTypeBits & host_props.memoryTypeBits;
if (type_mask == 0 || requirements.size > window_len) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const auto type_index = FindImportMemoryType(memory_props, type_mask);
if (!type_index) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const u32 heap_index = memory_props.memoryTypes[*type_index].heapIndex;
const VkDeviceSize heap_size = memory_props.memoryHeaps[heap_index].size;
if (imported_size + window_len > heap_size / 2) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const VkImportMemoryHostPointerInfoEXT import_info{
.sType = VK_STRUCTURE_TYPE_IMPORT_MEMORY_HOST_POINTER_INFO_EXT,
.pNext = nullptr,
.handleType = VK_EXTERNAL_MEMORY_HANDLE_TYPE_HOST_ALLOCATION_BIT_EXT,
.pHostPointer = window_base,
};
const VkMemoryAllocateInfo alloc_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &import_info,
.allocationSize = window_len,
.memoryTypeIndex = *type_index,
};
vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info);
if (!memory) {
logical.DestroyBufferRaw(new_buffer);
break;
}
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
logical.DestroyBufferRaw(new_buffer);
break;
}
windows.push_back(Window{
.memory = std::move(memory),
.buffer = new_buffer,
.address = 0,
.size = window_len,
.memory_type = *type_index,
});
imported_size += static_cast<size_t>(window_len);
}
if (windows.empty()) {
return false;
}
return true;
}
bool HostMemoryImport::ImportHardwareBuffers(
[[maybe_unused]] std::span<AHardwareBuffer *const> hardware_buffers,
[[maybe_unused]] size_t hardware_buffer_window,
[[maybe_unused]] size_t hardware_buffer_base, [[maybe_unused]] size_t size) {
#ifdef __ANDROID__
if (hardware_buffers.empty() || hardware_buffer_window == 0 ||
!device.IsExtExternalMemoryAhbSupported()) {
return false;
}
const u64 max_allocation_size = device.GetMaxMemoryAllocationSize();
if (max_allocation_size != 0 && hardware_buffer_window > max_allocation_size) {
return false;
}
if (hardware_buffer_base >= size) {
return false;
}
const auto &logical = device.GetLogical();
const auto memory_props = device.GetPhysical().GetMemoryProperties().memoryProperties;
window_size = hardware_buffer_window;
base_offset = hardware_buffer_base;
const auto import_all = [&](VkBufferUsageFlags usage, bool want_address, bool dedicated) {
for (size_t i = 0; i < hardware_buffers.size(); ++i) {
const size_t offset = hardware_buffer_base + i * hardware_buffer_window;
if (offset >= size) {
break;
}
const VkDeviceSize window_len = (std::min)(
static_cast<VkDeviceSize>(size - offset),
static_cast<VkDeviceSize>(hardware_buffer_window));
VkAndroidHardwareBufferPropertiesANDROID ahb_props{
.sType = VK_STRUCTURE_TYPE_ANDROID_HARDWARE_BUFFER_PROPERTIES_ANDROID,
.pNext = nullptr,
.allocationSize = 0,
.memoryTypeBits = 0,
};
if (logical.GetAndroidHardwareBufferPropertiesANDROID(hardware_buffers[i],
&ahb_props) != VK_SUCCESS ||
ahb_props.memoryTypeBits == 0 || ahb_props.allocationSize < window_len) {
break;
}
const VkExternalMemoryBufferCreateInfo external_info{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.pNext = nullptr,
.handleTypes =
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &external_info,
.flags = 0,
.size = window_len,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer new_buffer{};
if (logical.CreateBufferRaw(buffer_ci, &new_buffer) != VK_SUCCESS) {
break;
}
const VkMemoryRequirements requirements =
logical.GetBufferMemoryRequirements(new_buffer);
const u32 type_mask = requirements.memoryTypeBits & ahb_props.memoryTypeBits;
if (type_mask == 0 || requirements.size > ahb_props.allocationSize) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const auto type_index = FindImportMemoryType(memory_props, type_mask);
if (!type_index) {
logical.DestroyBufferRaw(new_buffer);
break;
}
const VkImportAndroidHardwareBufferInfoANDROID import_info{
.sType = VK_STRUCTURE_TYPE_IMPORT_ANDROID_HARDWARE_BUFFER_INFO_ANDROID,
.pNext = nullptr,
.buffer = hardware_buffers[i],
};
const VkMemoryDedicatedAllocateInfo dedicated_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_DEDICATED_ALLOCATE_INFO,
.pNext = &import_info,
.image = VK_NULL_HANDLE,
.buffer = new_buffer,
};
const void *memory_next = &import_info;
if (dedicated) {
memory_next = &dedicated_info;
}
const VkMemoryAllocateFlagsInfo flags_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_FLAGS_INFO,
.pNext = memory_next,
.flags = VK_MEMORY_ALLOCATE_DEVICE_ADDRESS_BIT,
.deviceMask = 0,
};
const void *alloc_next = memory_next;
if (want_address) {
alloc_next = &flags_info;
}
const VkMemoryAllocateInfo alloc_info{
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = alloc_next,
.allocationSize = ahb_props.allocationSize,
.memoryTypeIndex = *type_index,
};
vk::DeviceMemory memory = logical.TryAllocateMemory(alloc_info);
if (!memory) {
logical.DestroyBufferRaw(new_buffer);
break;
}
if (logical.BindBufferMemory(new_buffer, *memory, 0) != VK_SUCCESS) {
logical.DestroyBufferRaw(new_buffer);
break;
}
VkDeviceAddress address = 0;
if (want_address) {
address = logical.GetBufferDeviceAddress(new_buffer);
}
windows.push_back(Window{
.memory = std::move(memory),
.buffer = new_buffer,
.address = address,
.size = window_len,
.memory_type = *type_index,
});
imported_size += static_cast<size_t>(window_len);
}
return !windows.empty();
};
constexpr VkBufferUsageFlags TransferUsage =
VK_BUFFER_USAGE_TRANSFER_SRC_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT;
VkBufferUsageFlags shader_usage = TransferUsage |
VK_BUFFER_USAGE_UNIFORM_TEXEL_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_TEXEL_BUFFER_BIT | VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT |
VK_BUFFER_USAGE_STORAGE_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT |
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDIRECT_BUFFER_BIT;
const bool want_address = device.IsBufferDeviceAddressSupported();
VkBufferUsageFlags minimal_usage = TransferUsage | VK_BUFFER_USAGE_STORAGE_BUFFER_BIT;
if (want_address) {
shader_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
minimal_usage |= VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT;
}
const auto reset_windows = [&] {
for (Window &window : windows) {
if (window.buffer != VK_NULL_HANDLE) {
logical.DestroyBufferRaw(window.buffer);
}
}
windows.clear();
imported_size = 0;
};
mirror_capable = SupportsMirror(shader_usage);
buffer_usage = shader_usage;
bindable = import_all(shader_usage, want_address, !mirror_capable);
if (!bindable && mirror_capable) {
mirror_capable = false;
reset_windows();
bindable = import_all(shader_usage, want_address, true);
}
if (bindable) {
mirror_usage = shader_usage;
}
if (!bindable) {
reset_windows();
buffer_usage = minimal_usage;
bindable = import_all(minimal_usage, want_address, true);
}
if (!bindable) {
reset_windows();
buffer_usage = TransferUsage;
import_all(TransferUsage, false, true);
}
if (windows.empty()) {
window_size = 0;
base_offset = 0;
return false;
}
foreign_ownership = true;
return true;
#else
return false;
#endif
}
bool HostMemoryImport::SupportsMirror([[maybe_unused]] VkBufferUsageFlags usage) const {
#ifdef __ANDROID__
if (!device.IsSparseBindingSupported()) {
return false;
}
const auto supports = [&](VkBufferCreateFlags flags) {
const VkExternalMemoryProperties properties =
device.GetPhysical().GetExternalBufferProperties(
flags, usage,
VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID);
const VkExternalMemoryFeatureFlags features = properties.externalMemoryFeatures;
return (features & VK_EXTERNAL_MEMORY_FEATURE_IMPORTABLE_BIT) != 0 &&
(features & VK_EXTERNAL_MEMORY_FEATURE_DEDICATED_ONLY_BIT) == 0;
};
return supports(0) && supports(VK_BUFFER_CREATE_SPARSE_BINDING_BIT);
#else
return false;
#endif
}
std::optional<HostMemoryImport::Range> HostMemoryImport::ResolveRange(
VkDeviceSize relative, VkDeviceSize size) const noexcept {
if (window_size == 0 || size == 0 || relative >= imported_size ||
imported_size - relative < size) {
return std::nullopt;
}
const size_t index = static_cast<size_t>(relative / window_size);
const VkDeviceSize local_offset = relative % window_size;
if (index < windows.size() && windows[index].buffer != VK_NULL_HANDLE &&
local_offset < windows[index].size && windows[index].size - local_offset >= size) {
return Range{
.buffer = windows[index].buffer,
.address = windows[index].address,
.offset = local_offset,
};
}
if (mirror_buffer != VK_NULL_HANDLE && relative < mirror_size &&
mirror_size - relative >= size) {
return Range{
.buffer = mirror_buffer,
.address = mirror_address,
.offset = relative,
};
}
return std::nullopt;
}
std::optional<HostMemoryImport::ViewMemory> HostMemoryImport::ResolveViewMemory(
VkDeviceSize relative) const noexcept {
if (!mirror_capable || !bindable || window_size == 0) {
return std::nullopt;
}
const size_t index = static_cast<size_t>(relative / window_size);
if (index >= windows.size()) {
return std::nullopt;
}
const Window &window = windows[index];
const VkDeviceSize local_offset = relative % window_size;
if (window.buffer == VK_NULL_HANDLE || local_offset >= window.size) {
return std::nullopt;
}
return ViewMemory{
.buffer = window.buffer,
.memory = *window.memory,
.offset = local_offset,
.available = window.size - local_offset,
.memory_type = window.memory_type,
};
}
void HostMemoryImport::CreateMirror(std::mutex &submit_mutex, VkDeviceSize max_size) {
if (!mirror_capable || mirror_usage == 0 || !bindable || window_size == 0) {
return;
}
size_t mirror_windows = 0;
while (mirror_windows < windows.size() && windows[mirror_windows].size == window_size) {
++mirror_windows;
}
mirror_windows = (std::min)(mirror_windows, static_cast<size_t>(max_size / window_size));
const u64 max_buffer_size = device.GetMaxBufferSize();
if (max_buffer_size != 0) {
const size_t max_windows = static_cast<size_t>(max_buffer_size / window_size);
mirror_windows = (std::min)(mirror_windows, max_windows);
}
if (mirror_windows < 2) {
return;
}
const auto &logical = device.GetLogical();
const VkDeviceSize total_size = static_cast<VkDeviceSize>(mirror_windows) * window_size;
const VkExternalMemoryBufferCreateInfo external_info{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_MEMORY_BUFFER_CREATE_INFO,
.pNext = nullptr,
.handleTypes = VK_EXTERNAL_MEMORY_HANDLE_TYPE_ANDROID_HARDWARE_BUFFER_BIT_ANDROID,
};
const VkBufferCreateInfo buffer_ci{
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.pNext = &external_info,
.flags = VK_BUFFER_CREATE_SPARSE_BINDING_BIT,
.size = total_size,
.usage = mirror_usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 0,
.pQueueFamilyIndices = nullptr,
};
VkBuffer buffer{};
if (logical.CreateBufferRaw(buffer_ci, &buffer) != VK_SUCCESS) {
return;
}
const VkMemoryRequirements requirements = logical.GetBufferMemoryRequirements(buffer);
bool compatible = requirements.alignment != 0 &&
(window_size % requirements.alignment) == 0 &&
requirements.size <= total_size;
for (size_t index = 0; index < mirror_windows && compatible; ++index) {
compatible = ((requirements.memoryTypeBits >> windows[index].memory_type) & 1u) != 0;
}
if (!compatible) {
logical.DestroyBufferRaw(buffer);
return;
}
std::vector<VkSparseMemoryBind> binds;
binds.reserve(mirror_windows);
for (size_t index = 0; index < mirror_windows; ++index) {
binds.push_back(VkSparseMemoryBind{
.resourceOffset = static_cast<VkDeviceSize>(index) * window_size,
.size = window_size,
.memory = *windows[index].memory,
.memoryOffset = 0,
.flags = 0,
});
}
const VkSparseBufferMemoryBindInfo buffer_bind{
.buffer = buffer,
.bindCount = static_cast<u32>(binds.size()),
.pBinds = binds.data(),
};
const VkBindSparseInfo bind_info{
.sType = VK_STRUCTURE_TYPE_BIND_SPARSE_INFO,
.pNext = nullptr,
.waitSemaphoreCount = 0,
.pWaitSemaphores = nullptr,
.bufferBindCount = 1,
.pBufferBinds = &buffer_bind,
.imageOpaqueBindCount = 0,
.pImageOpaqueBinds = nullptr,
.imageBindCount = 0,
.pImageBinds = nullptr,
.signalSemaphoreCount = 0,
.pSignalSemaphores = nullptr,
};
vk::Fence fence = logical.CreateFence(VkFenceCreateInfo{
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.pNext = nullptr,
.flags = 0,
});
VkResult result = VK_ERROR_UNKNOWN;
{
std::scoped_lock lock{submit_mutex};
result = device.GetGraphicsQueue().BindSparse(bind_info, *fence);
}
if (result != VK_SUCCESS) {
logical.DestroyBufferRaw(buffer);
return;
}
fence.Wait();
mirror_buffer = buffer;
mirror_size = total_size;
if ((mirror_usage & VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT) != 0) {
mirror_address = logical.GetBufferDeviceAddress(buffer);
}
}
HostMemoryImport::~HostMemoryImport() {
if (mirror_buffer != VK_NULL_HANDLE) {
device.GetLogical().DestroyBufferRaw(mirror_buffer);
}
for (Window &window : windows) {
if (window.buffer != VK_NULL_HANDLE) {
device.GetLogical().DestroyBufferRaw(window.buffer);
}
}
}
MemoryAllocator::MemoryAllocator(const Device &device_)
: device{device_}, allocator{device.GetAllocator()},
properties{device_.GetPhysical().GetMemoryProperties().memoryProperties},
@@ -898,17 +367,6 @@ MemoryCommit MemoryAllocator::Commit(const VkMemoryRequirements &reqs, MemoryUsa
return MemoryCommit(allocator, a, info);
}
HostMemoryImport *MemoryAllocator::CreateHostMemoryImport(
void *base, size_t size, std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base) {
unified_memory = std::make_unique<HostMemoryImport>(
device, base, size, hardware_buffers, hardware_buffer_window, hardware_buffer_base);
if (!unified_memory->IsValid()) {
unified_memory.reset();
}
return unified_memory.get();
}
MemoryCommit MemoryAllocator::Commit(const vk::Buffer &buffer, MemoryUsage usage) {
// Allocate memory appropriate for this buffer automatically
const auto vma_usage = MemoryUsageVma(usage);
@@ -7,8 +7,6 @@
#pragma once
#include <memory>
#include <mutex>
#include <optional>
#include <span>
#include <vector>
@@ -17,8 +15,6 @@
#include "video_core/vulkan_common/vulkan_wrapper.h"
#include "video_core/vulkan_common/vma.h"
struct AHardwareBuffer;
namespace Vulkan {
class Device;
@@ -88,123 +84,6 @@ namespace Vulkan {
void *mapped_ptr{}; ///< Optional persistent mapped pointer
};
class HostMemoryImport {
public:
explicit HostMemoryImport(const Device &device_, void *base, size_t size,
std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base);
~HostMemoryImport();
HostMemoryImport(const HostMemoryImport &) = delete;
HostMemoryImport &operator=(const HostMemoryImport &) = delete;
[[nodiscard]] bool IsValid() const noexcept {
return !windows.empty();
}
[[nodiscard]] size_t GetSize() const noexcept {
return imported_size;
}
[[nodiscard]] size_t GetBaseOffset() const noexcept {
return base_offset;
}
[[nodiscard]] bool NeedsForeignOwnershipTransfer() const noexcept {
return foreign_ownership;
}
[[nodiscard]] VkDeviceSize GetWindowSize() const noexcept {
return window_size;
}
[[nodiscard]] VkBuffer GetWindowBuffer(size_t index) const noexcept {
return windows[index].buffer;
}
[[nodiscard]] VkDeviceAddress GetWindowAddress(size_t index) const noexcept {
return windows[index].address;
}
[[nodiscard]] size_t GetWindowCount() const noexcept {
return windows.size();
}
[[nodiscard]] bool IsBindable() const noexcept {
return bindable;
}
[[nodiscard]] VkBufferUsageFlags GetUsage() const noexcept {
return buffer_usage;
}
struct Range {
VkBuffer buffer{};
VkDeviceAddress address{};
VkDeviceSize offset{};
};
[[nodiscard]] std::optional<Range> ResolveRange(VkDeviceSize relative,
VkDeviceSize size) const noexcept;
struct ViewMemory {
VkBuffer buffer{};
VkDeviceMemory memory{};
VkDeviceSize offset{};
VkDeviceSize available{};
u32 memory_type{};
};
[[nodiscard]] std::optional<ViewMemory> ResolveViewMemory(
VkDeviceSize relative) const noexcept;
[[nodiscard]] VkBufferUsageFlags GetViewUsage() const noexcept {
if (!mirror_capable || !bindable) {
return 0;
}
return mirror_usage;
}
void CreateMirror(std::mutex &submit_mutex, VkDeviceSize max_size);
[[nodiscard]] VkDeviceSize GetMirrorSize() const noexcept {
return mirror_size;
}
private:
struct Window {
vk::DeviceMemory memory;
VkBuffer buffer{};
VkDeviceAddress address{};
VkDeviceSize size{};
u32 memory_type{};
};
[[nodiscard]] bool SupportsMirror(VkBufferUsageFlags usage) const;
bool ImportHostPointer(void *base, size_t size);
bool ImportHardwareBuffers(std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window, size_t hardware_buffer_base,
size_t size);
const Device &device;
std::vector<Window> windows;
VkDeviceSize window_size{};
size_t imported_size{};
size_t base_offset{};
bool foreign_ownership{};
bool bindable{};
bool mirror_capable{};
VkBufferUsageFlags buffer_usage{};
VkBufferUsageFlags mirror_usage{};
VkBuffer mirror_buffer{};
VkDeviceAddress mirror_address{};
VkDeviceSize mirror_size{};
};
/// Memory allocator container.
/// Allocates and releases memory allocations on demand.
class MemoryAllocator {
@@ -244,15 +123,6 @@ namespace Vulkan {
/// Commits memory required by the buffer and binds it (for buffers created outside VMA).
MemoryCommit Commit(const vk::Buffer &buffer, MemoryUsage usage);
HostMemoryImport *CreateHostMemoryImport(void *base, size_t size,
std::span<AHardwareBuffer *const> hardware_buffers,
size_t hardware_buffer_window,
size_t hardware_buffer_base);
[[nodiscard]] HostMemoryImport *GetHostMemoryImport() const noexcept {
return unified_memory.get();
}
private:
static bool IsAutoUsage(VmaMemoryUsage u) noexcept {
switch (u) {
@@ -270,7 +140,6 @@ namespace Vulkan {
const VkPhysicalDeviceMemoryProperties properties; ///< Physical device memory properties.
VkDeviceSize buffer_image_granularity; ///< Adjacent buffer/image granularity
u32 valid_memory_types{~0u};
std::unique_ptr<HostMemoryImport> unified_memory;
};
} // namespace Vulkan
+15 -29
View File
@@ -217,16 +217,12 @@ void Load(VkDevice device, DeviceDispatch& dld) noexcept {
X(vkGetBufferMemoryRequirements2);
X(vkGetDeviceQueue);
X(vkGetEventStatus);
X(vkGetMemoryHostPointerPropertiesEXT);
X(vkGetFenceStatus);
X(vkGetImageMemoryRequirements);
X(vkGetPipelineCacheData);
X(vkGetMemoryFdKHR);
#ifdef _WIN32
X(vkGetMemoryWin32HandleKHR);
#endif
#ifdef __ANDROID__
X(vkGetAndroidHardwareBufferPropertiesANDROID);
#endif
X(vkGetQueryPoolResults);
X(vkGetPipelineExecutablePropertiesKHR);
@@ -316,7 +312,6 @@ bool Load(VkInstance instance, InstanceDispatch& dld) noexcept {
X(vkDestroyDebugUtilsMessengerEXT);
X(vkDestroyDebugReportCallbackEXT);
X(vkDestroySurfaceKHR);
X(vkGetPhysicalDeviceExternalBufferProperties);
X(vkGetPhysicalDeviceFeatures2);
X(vkGetPhysicalDeviceFormatProperties2);
X(vkGetPhysicalDeviceProperties2);
@@ -465,8 +460,20 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
#else
constexpr VkFlags ci_flags{};
#endif
// DO NOT TOUCH, breaks RNDA3!!
// Don't know why, but gloom + yellow line glitch appears
// DO NOT TOUCH OR CHANGE THE ENGINE NAME/APPLICATION NAME, breaks RNDA3!!
// AMD drivers have fixes for Yuzu
// if remove => gloom + yellow line glitch appears
#ifdef __ANDROID__
const VkApplicationInfo application_info{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
.pApplicationName = "PUBGMobile",
.applicationVersion = VK_MAKE_VERSION(1, 7, 0),
.pEngineName = "UnrealEngine",
.engineVersion = VK_MAKE_VERSION(4, 23, 0),
.apiVersion = VK_API_VERSION_1_3,
};
#else
const VkApplicationInfo application_info{
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = nullptr,
@@ -476,6 +483,7 @@ Instance Instance::Create(u32 version, Span<const char*> layers, Span<const char
.engineVersion = VK_MAKE_VERSION(1, 3, 0),
.apiVersion = VK_API_VERSION_1_3,
};
#endif
const VkInstanceCreateInfo ci{
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pNext = nullptr,
@@ -1026,28 +1034,6 @@ VkPhysicalDeviceMemoryProperties2 PhysicalDevice::GetMemoryProperties(
return properties;
}
VkExternalMemoryProperties PhysicalDevice::GetExternalBufferProperties(
VkBufferCreateFlags flags, VkBufferUsageFlags usage,
VkExternalMemoryHandleTypeFlagBits handle_type) const noexcept {
VkExternalBufferProperties properties{
.sType = VK_STRUCTURE_TYPE_EXTERNAL_BUFFER_PROPERTIES,
.pNext = nullptr,
.externalMemoryProperties = {},
};
if (!dld->vkGetPhysicalDeviceExternalBufferProperties) {
return properties.externalMemoryProperties;
}
const VkPhysicalDeviceExternalBufferInfo info{
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_EXTERNAL_BUFFER_INFO,
.pNext = nullptr,
.flags = flags,
.usage = usage,
.handleType = handle_type,
};
dld->vkGetPhysicalDeviceExternalBufferProperties(physical_device, &info, &properties);
return properties.externalMemoryProperties;
}
u32 AvailableVersion(const InstanceDispatch& dld) noexcept {
PFN_vkEnumerateInstanceVersion vkEnumerateInstanceVersion;
if (!Proc(vkEnumerateInstanceVersion, dld, "vkEnumerateInstanceVersion")) {
@@ -178,7 +178,6 @@ struct InstanceDispatch {
PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties{};
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices{};
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr{};
PFN_vkGetPhysicalDeviceExternalBufferProperties vkGetPhysicalDeviceExternalBufferProperties{};
PFN_vkGetPhysicalDeviceFeatures2 vkGetPhysicalDeviceFeatures2{};
PFN_vkGetPhysicalDeviceFormatProperties vkGetPhysicalDeviceFormatProperties{};
PFN_vkGetPhysicalDeviceFormatProperties2 vkGetPhysicalDeviceFormatProperties2{};
@@ -334,16 +333,12 @@ struct DeviceDispatch : InstanceDispatch {
PFN_vkGetBufferMemoryRequirements2 vkGetBufferMemoryRequirements2{};
PFN_vkGetDeviceQueue vkGetDeviceQueue{};
PFN_vkGetEventStatus vkGetEventStatus{};
PFN_vkGetMemoryHostPointerPropertiesEXT vkGetMemoryHostPointerPropertiesEXT{};
PFN_vkGetFenceStatus vkGetFenceStatus{};
PFN_vkGetImageMemoryRequirements vkGetImageMemoryRequirements{};
PFN_vkGetPipelineCacheData vkGetPipelineCacheData{};
PFN_vkGetMemoryFdKHR vkGetMemoryFdKHR{};
#ifdef _WIN32
PFN_vkGetMemoryWin32HandleKHR vkGetMemoryWin32HandleKHR{};
#endif
#ifdef __ANDROID__
PFN_vkGetAndroidHardwareBufferPropertiesANDROID vkGetAndroidHardwareBufferPropertiesANDROID{};
#endif
PFN_vkGetPipelineExecutablePropertiesKHR vkGetPipelineExecutablePropertiesKHR{};
PFN_vkGetPipelineExecutableStatisticsKHR vkGetPipelineExecutableStatisticsKHR{};
@@ -1109,34 +1104,6 @@ public:
VkMemoryRequirements GetBufferMemoryRequirements(VkBuffer buffer,
void* pnext = nullptr) const noexcept;
VkResult GetMemoryHostPointerPropertiesEXT(
VkExternalMemoryHandleTypeFlagBits handle_type, const void* host_pointer,
VkMemoryHostPointerPropertiesEXT* out_properties) const noexcept {
return dld->vkGetMemoryHostPointerPropertiesEXT(handle, handle_type, host_pointer,
out_properties);
}
#ifdef __ANDROID__
VkResult GetAndroidHardwareBufferPropertiesANDROID(
const struct AHardwareBuffer* buffer,
VkAndroidHardwareBufferPropertiesANDROID* out_properties) const noexcept {
return dld->vkGetAndroidHardwareBufferPropertiesANDROID(handle, buffer, out_properties);
}
#endif
VkResult CreateBufferRaw(const VkBufferCreateInfo& ci, VkBuffer* out_buffer) const noexcept {
return dld->vkCreateBuffer(handle, &ci, nullptr, out_buffer);
}
void DestroyBufferRaw(VkBuffer buffer) const noexcept {
dld->vkDestroyBuffer(handle, buffer, nullptr);
}
VkResult BindBufferMemory(VkBuffer buffer, VkDeviceMemory memory,
VkDeviceSize offset) const noexcept {
return dld->vkBindBufferMemory(handle, buffer, memory, offset);
}
VkMemoryRequirements GetImageMemoryRequirements(VkImage image) const noexcept;
std::vector<VkPipelineExecutablePropertiesKHR> GetPipelineExecutablePropertiesKHR(
@@ -1244,10 +1211,6 @@ public:
VkPhysicalDeviceMemoryProperties2 GetMemoryProperties(
void* next_structures = nullptr) const noexcept;
VkExternalMemoryProperties GetExternalBufferProperties(
VkBufferCreateFlags flags, VkBufferUsageFlags usage,
VkExternalMemoryHandleTypeFlagBits handle_type) const noexcept;
private:
VkPhysicalDevice physical_device = nullptr;
const InstanceDispatch* dld = nullptr;