mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-15 15:58:23 +00:00
Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a298ed65c1 | |||
| 02dccd61ed | |||
| 9bee2c2dc1 | |||
| 1602864ff6 | |||
| de0ba75509 | |||
| d16ed2073f | |||
| 415df0cfe4 |
@@ -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)
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
@@ -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
@@ -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
@@ -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).
|
||||
|
||||
@@ -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
|
||||
|
||||
-1
@@ -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"),
|
||||
|
||||
-7
@@ -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,
|
||||
|
||||
-1
@@ -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 2001–2011 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>
|
||||
|
||||
@@ -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
|
||||
@@ -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(¶ms.frame);
|
||||
av_packet_free(¶ms.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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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
@@ -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,
|
||||
|
||||
@@ -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
@@ -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) {
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
@@ -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
@@ -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,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;
|
||||
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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{};
|
||||
|
||||
@@ -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{};
|
||||
|
||||
|
||||
@@ -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})
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user