mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-08-30 18:30:51 +00:00
Compare commits
8 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 15e3acac93 | |||
| 71d8796040 | |||
| 28ab4a1a01 | |||
| ccf3de02cf | |||
| 1f03dee126 | |||
| 3df41c1e7a | |||
| f4a8f9421a | |||
| c0a85d0e53 |
@@ -0,0 +1,28 @@
|
||||
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)
|
||||
@@ -0,0 +1,153 @@
|
||||
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)
|
||||
@@ -461,6 +461,21 @@ 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)
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
# 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()
|
||||
+17
-2
@@ -218,6 +218,21 @@
|
||||
"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",
|
||||
@@ -325,10 +340,10 @@
|
||||
"version": "vulkan-sdk-%NUMERIC_VERSION%"
|
||||
},
|
||||
"xbyak": {
|
||||
"hash": "b6475276b2faaeb315734ea8f4f8bd87ededcee768961b39679bee547e7f3e98884d8b7851e176d861dab30a80a76e6ea302f8c111483607dde969b4797ea95a",
|
||||
"hash": "e0aa0a603dd3ac1a39d82213df1e73c042831aec2d6b2fe382c899651eaae4ed7e8aeb6be9c41ea0097087c9d091961ab18f313cd6a9e10d621715ffd49bfe36",
|
||||
"package": "xbyak",
|
||||
"repo": "herumi/xbyak",
|
||||
"version": "v7.35.2"
|
||||
"version": "v7.40.1"
|
||||
},
|
||||
"zlib": {
|
||||
"hash": "16fea4df307a68cf0035858abe2fd550250618a97590e202037acd18a666f57afc10f8836cbbd472d54a0e76539d0e558cb26f059d53de52ff90634bbf4f47d4",
|
||||
|
||||
+10
-9
@@ -60,6 +60,7 @@ 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 +122,7 @@ sudo emerge -a \
|
||||
dev-libs/unordered_dense 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-video/ffmpeg \
|
||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||
net-libs/enet \
|
||||
sys-libs/zlib \
|
||||
@@ -153,7 +154,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 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 opus 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 +167,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 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 libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
||||
```
|
||||
|
||||
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
||||
@@ -213,7 +214,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 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 opus-dev jq patch
|
||||
```
|
||||
|
||||
</details>
|
||||
@@ -259,7 +260,7 @@ brew install molten-vk
|
||||
<details>
|
||||
<summary>FreeBSD</summary>
|
||||
|
||||
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/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 devel/unordered-dense vulkan-headers quazip-qt6`
|
||||
As root run: `pkg install devel/cmake sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base devel/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 devel/unordered-dense vulkan-headers quazip-qt6`
|
||||
|
||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||
|
||||
@@ -272,7 +273,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 qt6 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 libopus qt6 cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#netbsd).
|
||||
@@ -303,7 +304,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 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 opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
||||
```
|
||||
|
||||
[Caveats](./Caveats.md#openindiana).
|
||||
@@ -327,7 +328,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 libusb unordered_dense 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 opus libusb unordered_dense openssl SDL3"
|
||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||
packages="$BASE"
|
||||
for pkg in $MINGW; do
|
||||
@@ -353,7 +354,7 @@ pacman -Syuu --needed --noconfirm $packages
|
||||
<summary>HaikuOS</summary>
|
||||
|
||||
```sh
|
||||
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
|
||||
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
|
||||
```
|
||||
|
||||
[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 vulkan-headers vulkan-utility-libraries
|
||||
enet libopus vulkan-headers vulkan-utility-libraries
|
||||
spirv-tools spirv-headers vulkan-loader unzip
|
||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||
libusb1 cubeb
|
||||
|
||||
@@ -79,6 +79,13 @@ 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,
|
||||
|
||||
@@ -47,7 +47,6 @@ import info.debatty.java.stringsimilarity.Jaccard
|
||||
import info.debatty.java.stringsimilarity.JaroWinkler
|
||||
import java.util.Locale
|
||||
import androidx.core.content.edit
|
||||
import androidx.core.view.doOnNextLayout
|
||||
|
||||
class GamesFragment : Fragment() {
|
||||
private var _binding: FragmentGamesBinding? = null
|
||||
@@ -59,7 +58,6 @@ class GamesFragment : Fragment() {
|
||||
private var originalHeaderLeftMargin: Int? = null
|
||||
|
||||
private var lastViewType: Int = GameAdapter.VIEW_TYPE_GRID
|
||||
private var fallbackBottomInset: Int = 0
|
||||
private var pendingPostReloadListSettle = false
|
||||
private var pendingPostReloadListSettleGeneration = 0
|
||||
private var gameListSubmitGeneration = 0
|
||||
@@ -227,12 +225,7 @@ class GamesFragment : Fragment() {
|
||||
}
|
||||
else -> throw IllegalArgumentException("Invalid view type: $savedViewType")
|
||||
}
|
||||
if (savedViewType == GameAdapter.VIEW_TYPE_CAROUSEL) {
|
||||
(binding.gridGames as? View)?.let { it -> ViewCompat.requestApplyInsets(it)}
|
||||
doOnNextLayout { //Carousel: important to avoid overlap issues
|
||||
(this as? CarouselRecyclerView)?.notifyLaidOut(fallbackBottomInset)
|
||||
}
|
||||
} else {
|
||||
if (savedViewType != GameAdapter.VIEW_TYPE_CAROUSEL) {
|
||||
(this as? CarouselRecyclerView)?.setupCarousel(false)
|
||||
}
|
||||
adapter = gameAdapter
|
||||
@@ -590,11 +583,6 @@ class GamesFragment : Fragment() {
|
||||
qlaunchButton.layoutParams = mlpQLaunch
|
||||
}
|
||||
|
||||
val navInsets = windowInsets.getInsets(WindowInsetsCompat.Type.navigationBars())
|
||||
val gestureInsets = windowInsets.getInsets(WindowInsetsCompat.Type.systemGestures())
|
||||
val bottomInset = maxOf(navInsets.bottom, gestureInsets.bottom, cutoutInsets.bottom)
|
||||
fallbackBottomInset = bottomInset
|
||||
(binding.gridGames as? CarouselRecyclerView)?.notifyInsetsReady(bottomInset)
|
||||
windowInsets
|
||||
}
|
||||
}
|
||||
|
||||
@@ -12,13 +12,16 @@ import androidx.recyclerview.widget.PagerSnapHelper
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import org.yuzu.yuzu_emu.R
|
||||
import org.yuzu.yuzu_emu.adapters.GameAdapter
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import androidx.core.view.ViewCompat
|
||||
import org.yuzu.yuzu_emu.YuzuApplication
|
||||
import androidx.preference.PreferenceManager
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import org.yuzu.yuzu_emu.utils.FullscreenHelper
|
||||
/**
|
||||
* CarouselRecyclerView encapsulates all carousel content for the games UI.
|
||||
* It manages overlapping cards, center snapping, custom drawing order,
|
||||
@@ -32,7 +35,9 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
|
||||
private var overlapFactor: Float = 0f
|
||||
private var overlapPx: Int = 0
|
||||
private var bottomInset: Int = -1
|
||||
private var bottomInset: Int = 0
|
||||
private var latestWindowInsets: WindowInsetsCompat? = null
|
||||
private var cardGeometryInitialized: Boolean = false
|
||||
private var overlapDecoration: OverlappingDecoration? = null
|
||||
private var pagerSnapHelper: PagerSnapHelper? = null
|
||||
private var scalingScrollListener: OnScrollListener? = null
|
||||
@@ -91,6 +96,38 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
|
||||
init {
|
||||
setChildrenDrawingOrderEnabled(true)
|
||||
ViewCompat.setOnApplyWindowInsetsListener(this) { _, insets ->
|
||||
latestWindowInsets = insets
|
||||
updateCardGeometry()
|
||||
applyCarouselPadding()
|
||||
insets
|
||||
}
|
||||
}
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
ViewCompat.requestApplyInsets(this)
|
||||
}
|
||||
|
||||
override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
|
||||
super.onSizeChanged(w, h, oldw, oldh)
|
||||
if (w != oldw || h != oldh) {
|
||||
updateCardGeometry()
|
||||
applyCarouselPadding()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
|
||||
super.onLayout(changed, left, top, right, bottom)
|
||||
if (isCarouselMode) updateChildScalesAndAlpha()
|
||||
}
|
||||
|
||||
override fun onWindowFocusChanged(hasFocus: Boolean) {
|
||||
super.onWindowFocusChanged(hasFocus)
|
||||
if (hasFocus) {
|
||||
ViewCompat.requestApplyInsets(this)
|
||||
post { updateCardGeometry() }
|
||||
}
|
||||
}
|
||||
|
||||
override fun setAdapter(adapter: Adapter<*>?) {
|
||||
@@ -103,6 +140,8 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
super.setAdapter(adapter)
|
||||
|
||||
(adapter as? GameAdapter)?.registerAdapterDataObserver(carouselAdapterObserver)
|
||||
updateCardGeometry()
|
||||
applyCarouselPadding()
|
||||
}
|
||||
|
||||
private fun calculateCenter(width: Int, paddingStart: Int, paddingEnd: Int): Int {
|
||||
@@ -253,40 +292,71 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
}
|
||||
|
||||
fun notifyInsetsReady(newBottomInset: Int) {
|
||||
if (bottomInset != newBottomInset) {
|
||||
bottomInset = newBottomInset
|
||||
}
|
||||
|
||||
if (isCarouselMode) {
|
||||
setupCarousel(true)
|
||||
private fun resolveBottomInset(windowInsets: WindowInsetsCompat): Int {
|
||||
val navigationBottom = if (FullscreenHelper.isFullscreenEnabled(context)) {
|
||||
0
|
||||
} else {
|
||||
setupCarousel(false)
|
||||
windowInsets.getInsetsIgnoringVisibility(WindowInsetsCompat.Type.navigationBars()).bottom
|
||||
}
|
||||
val gestureInsets = windowInsets.getInsetsIgnoringVisibility(
|
||||
WindowInsetsCompat.Type.systemGestures()
|
||||
)
|
||||
val cutoutInsets = windowInsets.getInsetsIgnoringVisibility(
|
||||
WindowInsetsCompat.Type.displayCutout()
|
||||
)
|
||||
return maxOf(navigationBottom, gestureInsets.bottom, cutoutInsets.bottom)
|
||||
}
|
||||
|
||||
fun notifyLaidOut(fallBackBottomInset: Int) {
|
||||
if (bottomInset < 0) bottomInset = fallBackBottomInset
|
||||
var gameAdapter = adapter as? GameAdapter ?: return
|
||||
var newCardSize = cardSize(bottomInset)
|
||||
if (gameAdapter.cardSize != newCardSize) {
|
||||
gameAdapter.setCardSize(newCardSize)
|
||||
}
|
||||
private fun updateCardGeometry() {
|
||||
if (!isCarouselMode || height <= 0) return
|
||||
|
||||
if (isCarouselMode) {
|
||||
setupCarousel(true)
|
||||
}
|
||||
}
|
||||
val gameAdapter = adapter as? GameAdapter ?: return
|
||||
val windowInsets = latestWindowInsets ?: ViewCompat.getRootWindowInsets(this) ?: return
|
||||
|
||||
fun cardSize(bottomInset: Int): Int {
|
||||
if (cardGeometryInitialized && !hasWindowFocus()) return
|
||||
|
||||
val newBottomInset = resolveBottomInset(windowInsets).coerceIn(0, height)
|
||||
val internalFactor = resources.getFraction(R.fraction.carousel_card_size_factor, 1, 1)
|
||||
val userFactor = preferences.getFloat(CAROUSEL_CARD_SIZE_FACTOR, internalFactor).coerceIn(
|
||||
0f,
|
||||
1f
|
||||
)
|
||||
val scaledHeight = height * userFactor
|
||||
val availableHeight = height - bottomInset
|
||||
return minOf(scaledHeight.toInt(), availableHeight.toInt())
|
||||
val screenWidth = resources.displayMetrics.widthPixels.toFloat()
|
||||
val screenHeight = resources.displayMetrics.heightPixels.toFloat()
|
||||
val aspectFactor = ((screenWidth / screenHeight) / (20f / 9f))
|
||||
.pow(0.75f)
|
||||
.coerceIn(0.5f, 1f)
|
||||
val newCardSize = minOf(
|
||||
(height * userFactor).toInt(),
|
||||
height - newBottomInset,
|
||||
(height * aspectFactor).toInt()
|
||||
)
|
||||
if (newCardSize <= 0) return
|
||||
val insetChanged = bottomInset != newBottomInset
|
||||
val cardSizeChanged = gameAdapter.cardSize != newCardSize
|
||||
|
||||
bottomInset = newBottomInset
|
||||
cardGeometryInitialized = true
|
||||
|
||||
if (cardSizeChanged) gameAdapter.setCardSize(newCardSize)
|
||||
if (insetChanged || cardSizeChanged) setupCarousel(true)
|
||||
}
|
||||
|
||||
private fun applyCarouselPadding() {
|
||||
if (!isCarouselMode) return
|
||||
|
||||
val gameAdapter = adapter as? GameAdapter ?: return
|
||||
val cardSize = gameAdapter.cardSize
|
||||
if (cardSize <= 0 || bottomInset < 0) return
|
||||
|
||||
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0)
|
||||
val sidePadding = (width - cardSize) / 2
|
||||
if (paddingLeft != sidePadding || paddingTop != topPadding ||
|
||||
paddingRight != sidePadding || paddingBottom != 0
|
||||
) {
|
||||
setPadding(sidePadding, topPadding, sidePadding, 0)
|
||||
}
|
||||
clipToPadding = false
|
||||
}
|
||||
|
||||
fun setupCarousel(enabled: Boolean) {
|
||||
@@ -315,9 +385,6 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
internalFlingMultiplier
|
||||
).coerceIn(1f, 5f)
|
||||
|
||||
// Detach SnapHelper during setup
|
||||
pagerSnapHelper?.attachToRecyclerView(null)
|
||||
|
||||
// Add overlap decoration if not present
|
||||
if (overlapDecoration == null) {
|
||||
overlapDecoration = OverlappingDecoration(overlapPx)
|
||||
@@ -335,12 +402,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
addOnScrollListener(scalingScrollListener!!)
|
||||
}
|
||||
|
||||
if (cardSize > 0) {
|
||||
val topPadding = ((height - bottomInset - cardSize) / 2).coerceAtLeast(0) // Center vertically
|
||||
val sidePadding = (width - cardSize) / 2 // Center first/last card
|
||||
setPadding(sidePadding, topPadding, sidePadding, 0)
|
||||
clipToPadding = false
|
||||
}
|
||||
applyCarouselPadding()
|
||||
|
||||
if (pagerSnapHelper == null) {
|
||||
pagerSnapHelper = CenterPagerSnapHelper()
|
||||
@@ -362,6 +424,7 @@ class CarouselRecyclerView @JvmOverloads constructor(
|
||||
}
|
||||
savedItemAnimator = null
|
||||
}
|
||||
cardGeometryInitialized = false
|
||||
useCustomDrawingOrder = false
|
||||
// Reset padding and fling
|
||||
setPadding(0, 0, 0, 0)
|
||||
|
||||
@@ -1760,6 +1760,51 @@ 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>
|
||||
|
||||
@@ -226,14 +226,8 @@ else()
|
||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||
endif()
|
||||
|
||||
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)
|
||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
||||
|
||||
if (ENABLE_CUBEB)
|
||||
target_sources(audio_core PRIVATE
|
||||
|
||||
@@ -1,71 +1,107 @@
|
||||
// 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
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/codec.h>
|
||||
#include <libavcodec/packet.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_types.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/service/audio/errors.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 (channel_count == 1 || channel_count == 2)
|
||||
return u32(sizeof(OpusDecodeObject)) + 16 * channel_count;
|
||||
return 0;
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
|
||||
if ((codec = avcodec_find_decoder(AV_CODEC_ID_OPUS)) == nullptr)
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
if ((avctx = avcodec_alloc_context3(codec)) == nullptr)
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
avctx->sample_rate = sample_rate;
|
||||
av_channel_layout_default(&avctx->ch_layout, channel_count);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::Shutdown() {
|
||||
if (avctx)
|
||||
avcodec_free_context(&avctx);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::ResetDecoder() {
|
||||
if (avctx) {
|
||||
avcodec_flush_buffers(avctx);
|
||||
return ResultSuccess;
|
||||
if (!IsValidChannelCount(channel_count)) {
|
||||
return 0;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
|
||||
}
|
||||
|
||||
Result OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||
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 (avctx) {
|
||||
AVPacket* avpkt = av_packet_alloc();
|
||||
av_new_packet(avpkt, int(input_data_size));
|
||||
std::memcpy(avpkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
|
||||
avcodec_send_packet(avctx, avpkt);
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
avcodec_receive_frame(avctx, frame);
|
||||
std::memcpy(reinterpret_cast<u16*>(output_data), frame->data, output_data_size);
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&avpkt);
|
||||
|
||||
out_sample_count = frame->nb_samples;
|
||||
return ResultSuccess;
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
|
||||
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,31 +1,38 @@
|
||||
// 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 "common/common_types.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include <opus.h>
|
||||
|
||||
struct AVCodec;
|
||||
struct AVCodecContext;
|
||||
#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);
|
||||
|
||||
Result InitializeDecoder(u32 sample_rate, u32 channel_count);
|
||||
Result Shutdown();
|
||||
Result ResetDecoder();
|
||||
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size);
|
||||
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;
|
||||
}
|
||||
|
||||
AVCodec const* codec = nullptr;
|
||||
AVCodecContext* avctx = nullptr;
|
||||
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
|
||||
|
||||
@@ -16,7 +16,6 @@
|
||||
#include "common/thread.h"
|
||||
#include "core/core.h"
|
||||
#include "core/core_timing.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
@@ -50,16 +49,11 @@ OpusDecoder::~OpusDecoder() {
|
||||
// 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);
|
||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
||||
Message::ShutdownOK, msg);
|
||||
main_thread.request_stop();
|
||||
main_thread.join();
|
||||
running = false;
|
||||
|
||||
// Must shutdown as there are AV allocations which are manual
|
||||
for (auto& e : decode_objects)
|
||||
e.second.Shutdown();
|
||||
for (auto& e : ms_decode_objects)
|
||||
e.second.Shutdown();
|
||||
}
|
||||
|
||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||
@@ -74,195 +68,202 @@ 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.");
|
||||
LOG_ERROR(Service_Audio,
|
||||
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||
return;
|
||||
}
|
||||
// Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||
main_thread = std::jthread([this](std::stop_token thread_stop_token) {
|
||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
||||
while (!thread_stop_token.stop_requested()) {
|
||||
auto msg = Receive(Direction::DSP, thread_stop_token);
|
||||
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] = 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 = 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 >= OpusDecodeObject::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, channel_count).raw;
|
||||
} else {
|
||||
OpusDecodeObject obj{};
|
||||
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, channel_count).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);
|
||||
|
||||
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(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 = 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(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
||||
ASSERT(sample_rate >= 0);
|
||||
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(total_stream_count, stereo_stream_count));
|
||||
|
||||
if (auto const it = ms_decode_objects.find(buffer); it != ms_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 {
|
||||
OpusMultiStreamDecodeObject obj{};
|
||||
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||
ms_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 = ms_decode_objects.find(buffer); it != ms_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 = ms_decode_objects.find(buffer); it != ms_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);
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
});
|
||||
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
|
||||
|
||||
@@ -9,9 +9,6 @@
|
||||
#include <memory>
|
||||
#include <thread>
|
||||
|
||||
#include "ankerl/unordered_dense.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||
#include "audio_core/adsp/mailbox.h"
|
||||
#include "common/common_types.h"
|
||||
@@ -71,7 +68,9 @@ public:
|
||||
}
|
||||
|
||||
private:
|
||||
/// @brief Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
||||
/**
|
||||
* 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.
|
||||
@@ -91,9 +90,6 @@ private:
|
||||
/// 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{};
|
||||
|
||||
ankerl::unordered_dense::map<u64, OpusDecodeObject> decode_objects;
|
||||
ankerl::unordered_dense::map<u64, OpusMultiStreamDecodeObject> ms_decode_objects;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::ADSP::OpusDecoder
|
||||
|
||||
@@ -4,17 +4,8 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
extern "C" {
|
||||
#include <libavcodec/avcodec.h>
|
||||
#include <libavcodec/codec.h>
|
||||
#include <libavcodec/packet.h>
|
||||
#include <libavutil/channel_layout.h>
|
||||
#include <libavutil/frame.h>
|
||||
}
|
||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
||||
#include "common/assert.h"
|
||||
#include "core/hle/result.h"
|
||||
#include "core/hle/service/audio/errors.h"
|
||||
|
||||
namespace AudioCore::ADSP::OpusDecoder {
|
||||
|
||||
@@ -22,59 +13,101 @@ namespace {
|
||||
constexpr u32 OpusStreamCountMax = 255;
|
||||
|
||||
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
||||
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax
|
||||
&& s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_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 u32(sizeof(OpusMultiStreamDecodeObject)) + 2556 * (total_stream_count * stereo_stream_count);
|
||||
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;
|
||||
}
|
||||
|
||||
Result OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8* mappings) {
|
||||
if ((codec = avcodec_find_decoder(AV_CODEC_ID_OPUS)) == nullptr)
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
if ((avctx = avcodec_alloc_context3(codec)))
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
avctx->sample_rate = sample_rate;
|
||||
av_channel_layout_default(&avctx->ch_layout, channel_count);
|
||||
return ResultSuccess;
|
||||
}
|
||||
OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
||||
auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
|
||||
auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
|
||||
|
||||
Result OpusMultiStreamDecodeObject::Shutdown() {
|
||||
if (avctx)
|
||||
avcodec_free_context(&avctx);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
Result OpusMultiStreamDecodeObject::ResetDecoder() {
|
||||
if (avctx) {
|
||||
avcodec_flush_buffers(avctx);
|
||||
return ResultSuccess;
|
||||
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 Service::Audio::ResultLibOpusInvalidState;
|
||||
return *new_decoder;
|
||||
}
|
||||
|
||||
Result OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||
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 (avctx) {
|
||||
AVPacket* avpkt = av_packet_alloc();
|
||||
av_new_packet(avpkt, int(input_data_size));
|
||||
std::memcpy(avpkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
|
||||
avcodec_send_packet(avctx, avpkt);
|
||||
|
||||
AVFrame* frame = av_frame_alloc();
|
||||
avcodec_receive_frame(avctx, frame);
|
||||
std::memcpy(reinterpret_cast<u16*>(output_data), frame->data, output_data_size);
|
||||
av_frame_free(&frame);
|
||||
av_packet_free(&avpkt);
|
||||
|
||||
out_sample_count = frame->nb_samples;
|
||||
return ResultSuccess;
|
||||
if (!state_valid) {
|
||||
return OPUS_INVALID_STATE;
|
||||
}
|
||||
return Service::Audio::ResultLibOpusInvalidState;
|
||||
|
||||
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,31 +1,39 @@
|
||||
// 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 "audio_core/adsp/apps/opus/opus_types.h"
|
||||
#include "common/common_types.h"
|
||||
#include "core/hle/result.h"
|
||||
#include <opus_multistream.h>
|
||||
|
||||
struct AVCodec;
|
||||
struct AVCodecContext;
|
||||
#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);
|
||||
|
||||
Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8* mappings);
|
||||
Result Shutdown();
|
||||
Result ResetDecoder();
|
||||
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size);
|
||||
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;
|
||||
}
|
||||
|
||||
AVCodec const* codec = nullptr;
|
||||
AVCodecContext* avctx = nullptr;
|
||||
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
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
// 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);
|
||||
|
||||
}
|
||||
@@ -12,7 +12,7 @@
|
||||
namespace AudioCore {
|
||||
|
||||
AudioCore::AudioCore(Core::System& system) {
|
||||
audio_manager.emplace();
|
||||
audio_manager.emplace(system);
|
||||
CreateSinks();
|
||||
// Must be created after the sinks
|
||||
adsp.emplace(system, *output_sink);
|
||||
|
||||
@@ -15,12 +15,12 @@
|
||||
|
||||
namespace AudioCore::AudioIn {
|
||||
|
||||
Manager::Manager(Core::System& system_) : system{system_} {
|
||||
Manager::Manager(Core::System& system) {
|
||||
std::iota(session_ids.begin(), session_ids.end(), 0);
|
||||
num_free_sessions = MaxInSessions;
|
||||
}
|
||||
|
||||
Result Manager::AcquireSessionId(size_t& session_id) {
|
||||
Result Manager::AcquireSessionId(Core::System& system, size_t& session_id) {
|
||||
if (num_free_sessions == 0) {
|
||||
LOG_ERROR(Service_Audio, "All 4 AudioIn sessions are in use, cannot create any more");
|
||||
return Service::Audio::ResultOutOfSessions;
|
||||
@@ -31,7 +31,7 @@ Result Manager::AcquireSessionId(size_t& session_id) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Manager::ReleaseSessionId(const size_t session_id) {
|
||||
void Manager::ReleaseSessionId(Core::System& system, const size_t session_id) {
|
||||
std::scoped_lock l{mutex};
|
||||
LOG_DEBUG(Service_Audio, "Freeing AudioIn session {}", session_id);
|
||||
session_ids[free_session_id] = session_id;
|
||||
@@ -41,21 +41,20 @@ void Manager::ReleaseSessionId(const size_t session_id) {
|
||||
applet_resource_user_ids[session_id] = 0;
|
||||
}
|
||||
|
||||
Result Manager::LinkToManager() {
|
||||
Result Manager::LinkToManager(Core::System& system) {
|
||||
std::scoped_lock l{mutex};
|
||||
if (!linked_to_manager) {
|
||||
system.AudioCore().GetAudioManager().SetInManager(std::bind(&Manager::BufferReleaseAndRegister, this));
|
||||
system.AudioCore().GetAudioManager().SetInManager(&Manager::BufferReleaseAndRegister);
|
||||
linked_to_manager = true;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Manager::Start() {
|
||||
void Manager::Start(Core::System& system) {
|
||||
if (sessions_started) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::scoped_lock l{mutex};
|
||||
for (auto& session : sessions) {
|
||||
if (session) {
|
||||
@@ -66,21 +65,19 @@ void Manager::Start() {
|
||||
sessions_started = true;
|
||||
}
|
||||
|
||||
void Manager::BufferReleaseAndRegister() {
|
||||
std::scoped_lock l{mutex};
|
||||
for (auto& session : sessions) {
|
||||
void Manager::BufferReleaseAndRegister(void *data, Core::System& system) noexcept {
|
||||
Manager* this_ = (Manager*)data;
|
||||
std::scoped_lock l{this_->mutex};
|
||||
for (auto& session : this_->sessions) {
|
||||
if (session != nullptr) {
|
||||
session->ReleaseAndRegisterBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 Manager::GetDeviceNames(std::span<Renderer::AudioDevice::AudioDeviceName> names,
|
||||
[[maybe_unused]] const bool filter) {
|
||||
u32 Manager::GetDeviceNames(Core::System& system, std::span<Renderer::AudioDevice::AudioDeviceName> names, [[maybe_unused]] const bool filter) {
|
||||
std::scoped_lock l{mutex};
|
||||
|
||||
LinkToManager();
|
||||
|
||||
LinkToManager(system);
|
||||
auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)};
|
||||
if (!input_devices.empty() && !names.empty()) {
|
||||
names[0] = Renderer::AudioDevice::AudioDeviceName("Uac");
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -30,31 +33,29 @@ public:
|
||||
* @param session_id - Output session_id.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result AcquireSessionId(size_t& session_id);
|
||||
Result AcquireSessionId(Core::System& system, size_t& session_id);
|
||||
|
||||
/**
|
||||
* Release a session id on close.
|
||||
*
|
||||
* @param session_id - Session id to free.
|
||||
*/
|
||||
void ReleaseSessionId(size_t session_id);
|
||||
void ReleaseSessionId(Core::System& system, const size_t session_id);
|
||||
|
||||
/**
|
||||
* Link the audio in manager to the main audio manager.
|
||||
*
|
||||
* @return Result code.
|
||||
*/
|
||||
Result LinkToManager();
|
||||
Result LinkToManager(Core::System& system);
|
||||
|
||||
/**
|
||||
* Start the audio in manager.
|
||||
*/
|
||||
void Start();
|
||||
void Start(Core::System& system);
|
||||
|
||||
/**
|
||||
* Callback function, called by the audio manager when the audio in event is signalled.
|
||||
*/
|
||||
void BufferReleaseAndRegister();
|
||||
/// @brief Callback function, called by the audio manager when the audio in event is signalled.
|
||||
static void BufferReleaseAndRegister(void *data, Core::System& system) noexcept;
|
||||
|
||||
/**
|
||||
* Get a list of audio in device names.
|
||||
@@ -64,10 +65,8 @@ public:
|
||||
*
|
||||
* @return Number of names written.
|
||||
*/
|
||||
u32 GetDeviceNames(std::span<Renderer::AudioDevice::AudioDeviceName> names, bool filter);
|
||||
u32 GetDeviceNames(Core::System& system, std::span<Renderer::AudioDevice::AudioDeviceName> names, bool filter);
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Array of session ids
|
||||
std::array<size_t, MaxInSessions> session_ids{};
|
||||
/// Array of resource user ids
|
||||
|
||||
@@ -11,8 +11,8 @@
|
||||
|
||||
namespace AudioCore {
|
||||
|
||||
AudioManager::AudioManager() {
|
||||
thread = std::jthread([this](std::stop_token stop_token) {
|
||||
AudioManager::AudioManager(Core::System& system) {
|
||||
thread = std::jthread([&](std::stop_token stop_token) {
|
||||
Common::SetCurrentThreadName("AudioManager");
|
||||
std::unique_lock l{events.GetAudioEventLock()};
|
||||
events.ClearEvents();
|
||||
@@ -25,7 +25,7 @@ AudioManager::AudioManager() {
|
||||
const auto event_type = Event::Type(i);
|
||||
if (events.CheckAudioEventSet(event_type) || timed_out) {
|
||||
if (buffer_events[i]) {
|
||||
buffer_events[i]();
|
||||
buffer_events[i](this, system);
|
||||
}
|
||||
}
|
||||
events.SetAudioEvent(event_type, false);
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
#include "audio_core/audio_event.h"
|
||||
|
||||
namespace Core {
|
||||
class System;
|
||||
}
|
||||
|
||||
union Result;
|
||||
|
||||
namespace AudioCore {
|
||||
@@ -34,10 +38,9 @@ namespace AudioCore {
|
||||
* This is only used by audio in and audio out.
|
||||
*/
|
||||
class AudioManager {
|
||||
using BufferEventFunc = std::function<void()>;
|
||||
|
||||
using BufferEventFunc = void (*)(void *data, Core::System& system) noexcept;
|
||||
public:
|
||||
explicit AudioManager();
|
||||
explicit AudioManager(Core::System& system);
|
||||
|
||||
/**
|
||||
* Shutdown the audio manager.
|
||||
|
||||
@@ -14,12 +14,12 @@
|
||||
|
||||
namespace AudioCore::AudioOut {
|
||||
|
||||
Manager::Manager(Core::System& system_) : system{system_} {
|
||||
Manager::Manager(Core::System& system) {
|
||||
std::iota(session_ids.begin(), session_ids.end(), 0);
|
||||
num_free_sessions = MaxOutSessions;
|
||||
}
|
||||
|
||||
Result Manager::AcquireSessionId(size_t& session_id) {
|
||||
Result Manager::AcquireSessionId(Core::System& system, size_t& session_id) {
|
||||
if (num_free_sessions == 0) {
|
||||
LOG_ERROR(Service_Audio, "All 12 Audio Out sessions are in use, cannot create any more");
|
||||
return Service::Audio::ResultOutOfSessions;
|
||||
@@ -30,7 +30,7 @@ Result Manager::AcquireSessionId(size_t& session_id) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Manager::ReleaseSessionId(const size_t session_id) {
|
||||
void Manager::ReleaseSessionId(Core::System& system, const size_t session_id) {
|
||||
std::scoped_lock l{mutex};
|
||||
LOG_DEBUG(Service_Audio, "Freeing AudioOut session {}", session_id);
|
||||
session_ids[free_session_id] = session_id;
|
||||
@@ -40,17 +40,17 @@ void Manager::ReleaseSessionId(const size_t session_id) {
|
||||
applet_resource_user_ids[session_id] = 0;
|
||||
}
|
||||
|
||||
Result Manager::LinkToManager() {
|
||||
Result Manager::LinkToManager(Core::System& system) {
|
||||
std::scoped_lock l{mutex};
|
||||
if (!linked_to_manager) {
|
||||
system.AudioCore().GetAudioManager().SetOutManager(std::bind(&Manager::BufferReleaseAndRegister, this));
|
||||
system.AudioCore().GetAudioManager().SetOutManager(&Manager::BufferReleaseAndRegister);
|
||||
linked_to_manager = true;
|
||||
}
|
||||
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Manager::Start() {
|
||||
void Manager::Start(Core::System& system) {
|
||||
if (sessions_started) {
|
||||
return;
|
||||
}
|
||||
@@ -65,19 +65,14 @@ void Manager::Start() {
|
||||
sessions_started = true;
|
||||
}
|
||||
|
||||
void Manager::BufferReleaseAndRegister() {
|
||||
std::scoped_lock l{mutex};
|
||||
for (auto& session : sessions) {
|
||||
void Manager::BufferReleaseAndRegister(void *data, Core::System& system) noexcept {
|
||||
Manager* this_ = (Manager*)data;
|
||||
std::scoped_lock l{this_->mutex};
|
||||
for (auto& session : this_->sessions) {
|
||||
if (session != nullptr) {
|
||||
session->ReleaseAndRegisterBuffers();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
u32 Manager::GetAudioOutDeviceNames(
|
||||
std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const {
|
||||
names.emplace_back("DeviceOut");
|
||||
return 1;
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioOut
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -29,42 +32,32 @@ public:
|
||||
* @param session_id - Output session_id.
|
||||
* @return Result code.
|
||||
*/
|
||||
Result AcquireSessionId(size_t& session_id);
|
||||
Result AcquireSessionId(Core::System& system, size_t& session_id);
|
||||
|
||||
/**
|
||||
* Release a session id on close.
|
||||
*
|
||||
* @param session_id - Session id to free.
|
||||
*/
|
||||
void ReleaseSessionId(size_t session_id);
|
||||
void ReleaseSessionId(Core::System& system, const size_t session_id);
|
||||
|
||||
/**
|
||||
* Link this manager to the main audio manager.
|
||||
*
|
||||
* @return Result code.
|
||||
*/
|
||||
Result LinkToManager();
|
||||
Result LinkToManager(Core::System& system);
|
||||
|
||||
/**
|
||||
* Start the audio out manager.
|
||||
*/
|
||||
void Start();
|
||||
void Start(Core::System& system);
|
||||
|
||||
/**
|
||||
* Callback function, called by the audio manager when the audio out event is signalled.
|
||||
*/
|
||||
void BufferReleaseAndRegister();
|
||||
static void BufferReleaseAndRegister(void* data, Core::System& system) noexcept;
|
||||
|
||||
/**
|
||||
* Get a list of audio out device names.
|
||||
*
|
||||
* @param names - Output container to write names to.
|
||||
* @return Number of names written.
|
||||
*/
|
||||
u32 GetAudioOutDeviceNames(std::vector<Renderer::AudioDevice::AudioDeviceName>& names) const;
|
||||
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Array of session ids
|
||||
std::array<size_t, MaxOutSessions> session_ids{};
|
||||
/// Array of resource user ids
|
||||
|
||||
@@ -1,15 +1,20 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include "audio_core/audio_render_manager.h"
|
||||
#include "audio_core/common/audio_renderer_parameter.h"
|
||||
#include "audio_core/renderer/system_manager.h"
|
||||
#include "audio_core/common/feature_support.h"
|
||||
#include "core/core.h"
|
||||
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
Manager::Manager(Core::System& system_)
|
||||
: system{system_}, system_manager{std::make_unique<SystemManager>(system)} {
|
||||
: system_manager{std::make_unique<SystemManager>(system_)}
|
||||
{
|
||||
std::iota(session_ids.begin(), session_ids.end(), 0);
|
||||
}
|
||||
|
||||
@@ -59,11 +64,11 @@ u32 Manager::GetSessionCount() const {
|
||||
return session_count;
|
||||
}
|
||||
|
||||
bool Manager::AddSystem(System& system_) {
|
||||
bool Manager::AddSystem(Renderer::System& system_) {
|
||||
return system_manager->Add(system_);
|
||||
}
|
||||
|
||||
bool Manager::RemoveSystem(System& system_) {
|
||||
bool Manager::RemoveSystem(Renderer::System& system_) {
|
||||
return system_manager->Remove(system_);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -71,7 +74,7 @@ public:
|
||||
* @param system - The system to add.
|
||||
* @return True if the system was successfully added, otherwise false.
|
||||
*/
|
||||
bool AddSystem(System& system);
|
||||
bool AddSystem(Renderer::System& system);
|
||||
|
||||
/**
|
||||
* Remove a renderer system from the manager.
|
||||
@@ -79,7 +82,7 @@ public:
|
||||
* @param system - The system to remove.
|
||||
* @return True if the system was successfully removed, otherwise false.
|
||||
*/
|
||||
bool RemoveSystem(System& system);
|
||||
bool RemoveSystem(Renderer::System& system);
|
||||
|
||||
/**
|
||||
* Free a session id when the system wants to shut down.
|
||||
@@ -89,8 +92,6 @@ public:
|
||||
void ReleaseSessionId(s32 session_id);
|
||||
|
||||
private:
|
||||
/// Core system
|
||||
Core::System& system;
|
||||
/// Session ids, -1 when in use
|
||||
std::array<s32, MaxRendererSessions> session_ids{};
|
||||
/// Number of active renderers
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -16,8 +19,9 @@ namespace AudioCore {
|
||||
*/
|
||||
class WorkbufferAllocator {
|
||||
public:
|
||||
explicit WorkbufferAllocator(std::span<u8> buffer_, u64 size_)
|
||||
: buffer{reinterpret_cast<u64>(buffer_.data())}, size{size_} {}
|
||||
explicit WorkbufferAllocator(std::span<u8> buffer_)
|
||||
: buffer{buffer_}
|
||||
{}
|
||||
|
||||
/**
|
||||
* Allocate the given count of T elements, aligned to alignment.
|
||||
@@ -29,36 +33,31 @@ public:
|
||||
template <typename T>
|
||||
std::span<T> Allocate(u64 count, u64 alignment) {
|
||||
u64 out{0};
|
||||
u64 byte_size{count * sizeof(T)};
|
||||
|
||||
u64 byte_size = count * sizeof(T);
|
||||
if (byte_size > 0) {
|
||||
auto current{buffer + offset};
|
||||
auto current{uintptr_t(buffer.data()) + offset};
|
||||
auto aligned_buffer{Common::AlignUp(current, alignment)};
|
||||
if (aligned_buffer + byte_size <= buffer + size) {
|
||||
if (aligned_buffer + byte_size <= uintptr_t(buffer.data()) + buffer.size()) {
|
||||
out = aligned_buffer;
|
||||
offset = byte_size - buffer + aligned_buffer;
|
||||
offset = byte_size - uintptr_t(buffer.data()) + aligned_buffer;
|
||||
} else {
|
||||
LOG_ERROR(
|
||||
Service_Audio,
|
||||
"Allocated buffer was too small to hold new alloc.\nAllocator size={:08X}, "
|
||||
"offset={:08X}.\nAttempting to allocate {:08X} with alignment={:02X}",
|
||||
size, offset, byte_size, alignment);
|
||||
buffer.size(), offset, byte_size, alignment);
|
||||
count = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return std::span<T>(reinterpret_cast<T*>(out), count);
|
||||
}
|
||||
|
||||
/**
|
||||
* Align the current offset to the given alignment.
|
||||
*
|
||||
* @param alignment - The required starting alignment.
|
||||
*/
|
||||
/// @brief Align the current offset to the given alignment.
|
||||
/// @param alignment - The required starting alignment.
|
||||
void Align(u64 alignment) {
|
||||
auto current{buffer + offset};
|
||||
auto current{uintptr_t(buffer.data()) + offset};
|
||||
auto aligned_buffer{Common::AlignUp(current, alignment)};
|
||||
offset = 0 - buffer + aligned_buffer;
|
||||
offset = 0 - uintptr_t(buffer.data()) + aligned_buffer;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -76,7 +75,7 @@ public:
|
||||
* @return The size of the current buffer.
|
||||
*/
|
||||
u64 GetSize() const {
|
||||
return size;
|
||||
return buffer.size();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -85,14 +84,11 @@ public:
|
||||
* @return The remaining size left in the buffer.
|
||||
*/
|
||||
u64 GetRemainingSize() const {
|
||||
return size - offset;
|
||||
return buffer.size() - offset;
|
||||
}
|
||||
|
||||
private:
|
||||
/// The buffer into which we are allocating.
|
||||
u64 buffer;
|
||||
/// Size of the buffer we're allocating to.
|
||||
u64 size;
|
||||
const std::span<u8> buffer;
|
||||
/// Current offset into the buffer, an error will be thrown if it exceeds size.
|
||||
u64 offset{};
|
||||
};
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -8,42 +11,43 @@
|
||||
namespace AudioCore::AudioIn {
|
||||
|
||||
In::In(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
|
||||
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
|
||||
session_id_} {}
|
||||
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}
|
||||
, audio_system{system_, event, session_id_}
|
||||
{}
|
||||
|
||||
void In::Free() {
|
||||
void In::Free(Core::System& system) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
manager.ReleaseSessionId(system.GetSessionId());
|
||||
manager.ReleaseSessionId(system, audio_system.GetSessionId());
|
||||
}
|
||||
|
||||
System& In::GetSystem() {
|
||||
return system;
|
||||
return audio_system;
|
||||
}
|
||||
|
||||
AudioIn::State In::GetState() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetState();
|
||||
return audio_system.GetState();
|
||||
}
|
||||
|
||||
Result In::StartSystem() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.Start();
|
||||
return audio_system.Start();
|
||||
}
|
||||
|
||||
void In::StartSession() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
system.StartSession();
|
||||
audio_system.StartSession();
|
||||
}
|
||||
|
||||
Result In::StopSystem() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.Stop();
|
||||
return audio_system.Stop();
|
||||
}
|
||||
|
||||
Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
|
||||
if (system.AppendBuffer(buffer, tag)) {
|
||||
if (audio_system.AppendBuffer(buffer, tag)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
return Service::Audio::ResultBufferCountReached;
|
||||
@@ -51,20 +55,20 @@ Result In::AppendBuffer(const AudioInBuffer& buffer, u64 tag) {
|
||||
|
||||
void In::ReleaseAndRegisterBuffers() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
if (system.GetState() == State::Started) {
|
||||
system.ReleaseBuffers();
|
||||
system.RegisterBuffers();
|
||||
if (audio_system.GetState() == State::Started) {
|
||||
audio_system.ReleaseBuffers();
|
||||
audio_system.RegisterBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
bool In::FlushAudioInBuffers() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.FlushAudioInBuffers();
|
||||
return audio_system.FlushAudioInBuffers();
|
||||
}
|
||||
|
||||
u32 In::GetReleasedBuffers(std::span<u64> tags) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetReleasedBuffers(tags);
|
||||
return audio_system.GetReleasedBuffers(tags);
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& In::GetBufferEvent() {
|
||||
@@ -74,27 +78,27 @@ Kernel::KReadableEvent& In::GetBufferEvent() {
|
||||
|
||||
f32 In::GetVolume() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetVolume();
|
||||
return audio_system.GetVolume();
|
||||
}
|
||||
|
||||
void In::SetVolume(f32 volume) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
system.SetVolume(volume);
|
||||
audio_system.SetVolume(volume);
|
||||
}
|
||||
|
||||
bool In::ContainsAudioBuffer(u64 tag) const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.ContainsAudioBuffer(tag);
|
||||
return audio_system.ContainsAudioBuffer(tag);
|
||||
}
|
||||
|
||||
u32 In::GetBufferCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetBufferCount();
|
||||
return audio_system.GetBufferCount();
|
||||
}
|
||||
|
||||
u64 In::GetPlayedSampleCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetPlayedSampleCount();
|
||||
return audio_system.GetPlayedSampleCount();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioIn
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -30,7 +33,7 @@ public:
|
||||
/**
|
||||
* Free this audio in from the audio in manager.
|
||||
*/
|
||||
void Free();
|
||||
void Free(Core::System& system);
|
||||
|
||||
/**
|
||||
* Get this audio in's system.
|
||||
@@ -141,7 +144,7 @@ private:
|
||||
/// Buffer event, signalled when buffers are ready to be released
|
||||
Kernel::KEvent* event;
|
||||
/// Main audio in system
|
||||
System system;
|
||||
System audio_system;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioIn
|
||||
|
||||
@@ -9,18 +9,39 @@
|
||||
#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);
|
||||
}
|
||||
|
||||
@@ -71,7 +92,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count,
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||
@@ -99,7 +120,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||
@@ -113,7 +134,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||
"Expected Opus shutdown code {}, got {}",
|
||||
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
||||
@@ -128,7 +149,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si
|
||||
"Expected Opus shutdown code {}, got {}",
|
||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
||||
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
||||
}
|
||||
|
||||
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||
@@ -152,12 +173,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||
}
|
||||
|
||||
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]);
|
||||
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]);
|
||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||
}
|
||||
R_RETURN(Result(u32(error_code)));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
||||
}
|
||||
|
||||
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||
@@ -166,27 +187,29 @@ 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 const error_code = shared_memory.dsp_return_data[0];
|
||||
if (error_code == ResultSuccess.raw) {
|
||||
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]);
|
||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||
}
|
||||
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
||||
}
|
||||
|
||||
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||
@@ -197,7 +220,8 @@ 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();
|
||||
@@ -211,7 +235,8 @@ 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();
|
||||
|
||||
@@ -1,12 +1,11 @@
|
||||
// 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 <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"
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -8,42 +11,43 @@
|
||||
namespace AudioCore::AudioOut {
|
||||
|
||||
Out::Out(Core::System& system_, Manager& manager_, Kernel::KEvent* event_, size_t session_id_)
|
||||
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}, system{system_, event,
|
||||
session_id_} {}
|
||||
: manager{manager_}, parent_mutex{manager.mutex}, event{event_}
|
||||
, audio_system{system_, event, session_id_}
|
||||
{}
|
||||
|
||||
void Out::Free() {
|
||||
void Out::Free(Core::System& system) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
manager.ReleaseSessionId(system.GetSessionId());
|
||||
manager.ReleaseSessionId(system, audio_system.GetSessionId());
|
||||
}
|
||||
|
||||
System& Out::GetSystem() {
|
||||
return system;
|
||||
return audio_system;
|
||||
}
|
||||
|
||||
AudioOut::State Out::GetState() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetState();
|
||||
return audio_system.GetState();
|
||||
}
|
||||
|
||||
Result Out::StartSystem() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.Start();
|
||||
return audio_system.Start();
|
||||
}
|
||||
|
||||
void Out::StartSession() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
system.StartSession();
|
||||
audio_system.StartSession();
|
||||
}
|
||||
|
||||
Result Out::StopSystem() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.Stop();
|
||||
return audio_system.Stop();
|
||||
}
|
||||
|
||||
Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
|
||||
if (system.AppendBuffer(buffer, tag)) {
|
||||
if (audio_system.AppendBuffer(buffer, tag)) {
|
||||
return ResultSuccess;
|
||||
}
|
||||
return Service::Audio::ResultBufferCountReached;
|
||||
@@ -51,20 +55,20 @@ Result Out::AppendBuffer(const AudioOutBuffer& buffer, const u64 tag) {
|
||||
|
||||
void Out::ReleaseAndRegisterBuffers() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
if (system.GetState() == State::Started) {
|
||||
system.ReleaseBuffers();
|
||||
system.RegisterBuffers();
|
||||
if (audio_system.GetState() == State::Started) {
|
||||
audio_system.ReleaseBuffers();
|
||||
audio_system.RegisterBuffers();
|
||||
}
|
||||
}
|
||||
|
||||
bool Out::FlushAudioOutBuffers() {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.FlushAudioOutBuffers();
|
||||
return audio_system.FlushAudioOutBuffers();
|
||||
}
|
||||
|
||||
u32 Out::GetReleasedBuffers(std::span<u64> tags) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetReleasedBuffers(tags);
|
||||
return audio_system.GetReleasedBuffers(tags);
|
||||
}
|
||||
|
||||
Kernel::KReadableEvent& Out::GetBufferEvent() {
|
||||
@@ -74,27 +78,27 @@ Kernel::KReadableEvent& Out::GetBufferEvent() {
|
||||
|
||||
f32 Out::GetVolume() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetVolume();
|
||||
return audio_system.GetVolume();
|
||||
}
|
||||
|
||||
void Out::SetVolume(const f32 volume) {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
system.SetVolume(volume);
|
||||
audio_system.SetVolume(volume);
|
||||
}
|
||||
|
||||
bool Out::ContainsAudioBuffer(const u64 tag) const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.ContainsAudioBuffer(tag);
|
||||
return audio_system.ContainsAudioBuffer(tag);
|
||||
}
|
||||
|
||||
u32 Out::GetBufferCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetBufferCount();
|
||||
return audio_system.GetBufferCount();
|
||||
}
|
||||
|
||||
u64 Out::GetPlayedSampleCount() const {
|
||||
std::scoped_lock l{parent_mutex};
|
||||
return system.GetPlayedSampleCount();
|
||||
return audio_system.GetPlayedSampleCount();
|
||||
}
|
||||
|
||||
} // namespace AudioCore::AudioOut
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -30,7 +33,7 @@ public:
|
||||
/**
|
||||
* Free this audio out from the audio out manager.
|
||||
*/
|
||||
void Free();
|
||||
void Free(Core::System& system);
|
||||
|
||||
/**
|
||||
* Get this audio out's system.
|
||||
@@ -141,7 +144,7 @@ private:
|
||||
/// Buffer event, signalled when buffers are ready to be released
|
||||
Kernel::KEvent* event;
|
||||
/// Main audio out system
|
||||
System system;
|
||||
System audio_system;
|
||||
};
|
||||
|
||||
} // namespace AudioCore::AudioOut
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -13,56 +16,48 @@
|
||||
namespace AudioCore::Renderer {
|
||||
|
||||
Renderer::Renderer(Core::System& system_, Manager& manager_, Kernel::KEvent* rendered_event)
|
||||
: core{system_}, manager{manager_}, system{system_, rendered_event} {}
|
||||
: system{system_}, manager{manager_}
|
||||
, audio_system{system_, rendered_event}
|
||||
{}
|
||||
|
||||
Result Renderer::Initialize(const AudioRendererParameterInternal& params,
|
||||
Kernel::KTransferMemory* transfer_memory,
|
||||
const u64 transfer_memory_size, Kernel::KProcess* process_handle,
|
||||
const u64 applet_resource_user_id, const s32 session_id) {
|
||||
Result Renderer::Initialize(const AudioRendererParameterInternal& params, Kernel::KTransferMemory* transfer_memory, const u64 transfer_memory_size, Kernel::KProcess* process_handle, const u64 applet_resource_user_id, const s32 session_id) {
|
||||
if (params.execution_mode == ExecutionMode::Auto) {
|
||||
if (!manager.AddSystem(system)) {
|
||||
LOG_ERROR(Service_Audio,
|
||||
"Both Audio Render sessions are in use, cannot create any more");
|
||||
if (!manager.AddSystem(audio_system)) {
|
||||
LOG_ERROR(Service_Audio, "Both Audio Render sessions are in use, cannot create any more");
|
||||
return Service::Audio::ResultOutOfSessions;
|
||||
}
|
||||
system_registered = true;
|
||||
}
|
||||
|
||||
initialized = true;
|
||||
system.Initialize(params, transfer_memory, transfer_memory_size, process_handle,
|
||||
applet_resource_user_id, session_id);
|
||||
|
||||
audio_system.Initialize(params, transfer_memory, transfer_memory_size, process_handle, applet_resource_user_id, session_id);
|
||||
return ResultSuccess;
|
||||
}
|
||||
|
||||
void Renderer::Finalize() {
|
||||
auto session_id{system.GetSessionId()};
|
||||
|
||||
system.Finalize();
|
||||
|
||||
auto const session_id{audio_system.GetSessionId()};
|
||||
audio_system.Finalize();
|
||||
if (system_registered) {
|
||||
manager.RemoveSystem(system);
|
||||
manager.RemoveSystem(audio_system);
|
||||
system_registered = false;
|
||||
}
|
||||
|
||||
manager.ReleaseSessionId(session_id);
|
||||
}
|
||||
|
||||
System& Renderer::GetSystem() {
|
||||
return system;
|
||||
return audio_system;
|
||||
}
|
||||
|
||||
void Renderer::Start() {
|
||||
system.Start();
|
||||
audio_system.Start();
|
||||
}
|
||||
|
||||
void Renderer::Stop() {
|
||||
system.Stop();
|
||||
audio_system.Stop();
|
||||
}
|
||||
|
||||
Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance,
|
||||
std::span<u8> output) {
|
||||
return system.Update(input, performance, output);
|
||||
Result Renderer::RequestUpdate(std::span<const u8> input, std::span<u8> performance, std::span<u8> output) {
|
||||
return audio_system.Update(input, performance, output);
|
||||
}
|
||||
|
||||
} // namespace AudioCore::Renderer
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -84,7 +87,7 @@ public:
|
||||
|
||||
private:
|
||||
/// System core
|
||||
Core::System& core;
|
||||
Core::System& system;
|
||||
/// Manager this renderer is registered with
|
||||
Manager& manager;
|
||||
/// Is the audio renderer initialized?
|
||||
@@ -92,7 +95,7 @@ private:
|
||||
/// Is the system registered with the manager?
|
||||
bool system_registered{};
|
||||
/// Audio render system, main driver of audio rendering
|
||||
System system;
|
||||
System audio_system;
|
||||
};
|
||||
|
||||
} // namespace Renderer
|
||||
|
||||
@@ -145,7 +145,7 @@ Result System::Initialize(const AudioRendererParameterInternal& params,
|
||||
PoolMapper pool_mapper(process_handle, false);
|
||||
pool_mapper.InitializeSystemPool(memory_pool_info, workbuffer.get(), workbuffer_size);
|
||||
|
||||
WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size}, workbuffer_size);
|
||||
WorkbufferAllocator allocator({workbuffer.get(), workbuffer_size});
|
||||
|
||||
samples_workbuffer =
|
||||
allocator.Allocate<s32>((voice_channels + mix_buffer_count) * sample_count, 0x10);
|
||||
|
||||
@@ -11,14 +11,14 @@
|
||||
|
||||
namespace Common::Net {
|
||||
|
||||
typedef struct {
|
||||
struct Asset {
|
||||
std::string name;
|
||||
std::string url;
|
||||
std::string path;
|
||||
std::string filename;
|
||||
} Asset;
|
||||
};
|
||||
|
||||
typedef struct Release {
|
||||
struct Release {
|
||||
std::string title;
|
||||
std::string body;
|
||||
std::string tag;
|
||||
@@ -39,7 +39,7 @@ typedef struct Release {
|
||||
static std::optional<Release> FromJson(const std::string_view& json, const std::string &host, const std::string& repo);
|
||||
static std::vector<Release> ListFromJson(const nlohmann::json &json, const std::string &host, const std::string &repo);
|
||||
static std::vector<Release> ListFromJson(const std::string_view &json, const std::string &host, const std::string &repo);
|
||||
} Release;
|
||||
};
|
||||
|
||||
// Make a request via httplib, and return the response body if applicable.
|
||||
std::optional<std::string> MakeRequest(const std::string &url, const std::string &path);
|
||||
|
||||
@@ -1205,6 +1205,7 @@ 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)
|
||||
|
||||
+34
-6
@@ -4,6 +4,7 @@
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
#include "game_settings.h"
|
||||
@@ -248,12 +249,30 @@ struct System::Impl {
|
||||
}
|
||||
}
|
||||
|
||||
void SetNVDECActive(bool is_nvdec_active) {
|
||||
nvdec_active = is_nvdec_active;
|
||||
void NotifyNVDECChannelOpen(u64 process_id) {
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
++nvdec_active_channels[process_id];
|
||||
}
|
||||
|
||||
void NotifyNVDECChannelClose(u64 process_id) {
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
const auto it = nvdec_active_channels.find(process_id);
|
||||
if (it == nvdec_active_channels.end()) {
|
||||
return;
|
||||
}
|
||||
if (--it->second == 0) {
|
||||
nvdec_active_channels.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
bool GetNVDECActive() {
|
||||
return nvdec_active;
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
return !nvdec_active_channels.empty();
|
||||
}
|
||||
|
||||
bool IsNVDECActiveForProcess(u64 process_id) {
|
||||
std::scoped_lock lock{nvdec_active_mutex};
|
||||
return nvdec_active_channels.contains(process_id);
|
||||
}
|
||||
|
||||
void InitializeDebugger(System& system, u16 port) {
|
||||
@@ -505,6 +524,8 @@ struct System::Impl {
|
||||
|
||||
mutable std::mutex suspend_guard;
|
||||
std::mutex general_channel_mutex;
|
||||
std::mutex nvdec_active_mutex;
|
||||
std::unordered_map<u64, u32> nvdec_active_channels;
|
||||
std::atomic_bool is_paused{};
|
||||
std::atomic_bool is_shutting_down{};
|
||||
std::atomic_bool is_powered_on{};
|
||||
@@ -512,7 +533,6 @@ struct System::Impl {
|
||||
bool extended_memory_layout : 1 = false;
|
||||
bool exit_locked : 1 = false;
|
||||
bool exit_requested : 1 = false;
|
||||
bool nvdec_active : 1 = false;
|
||||
|
||||
void EnsureGeneralChannelInitialized(System& system) {
|
||||
if (!general_channel_event) {
|
||||
@@ -576,14 +596,22 @@ void System::UnstallApplication() {
|
||||
impl->UnstallApplication();
|
||||
}
|
||||
|
||||
void System::SetNVDECActive(bool is_nvdec_active) {
|
||||
impl->SetNVDECActive(is_nvdec_active);
|
||||
void System::NotifyNVDECChannelOpen(u64 process_id) {
|
||||
impl->NotifyNVDECChannelOpen(process_id);
|
||||
}
|
||||
|
||||
void System::NotifyNVDECChannelClose(u64 process_id) {
|
||||
impl->NotifyNVDECChannelClose(process_id);
|
||||
}
|
||||
|
||||
bool System::GetNVDECActive() {
|
||||
return impl->GetNVDECActive();
|
||||
}
|
||||
|
||||
bool System::IsNVDECActiveForProcess(u64 process_id) {
|
||||
return impl->IsNVDECActiveForProcess(process_id);
|
||||
}
|
||||
|
||||
void System::InitializeDebugger() {
|
||||
impl->InitializeDebugger(*this, Settings::values.gdbstub_port.GetValue());
|
||||
}
|
||||
|
||||
+3
-1
@@ -191,8 +191,10 @@ public:
|
||||
std::unique_lock<std::mutex> StallApplication();
|
||||
void UnstallApplication();
|
||||
|
||||
void SetNVDECActive(bool is_nvdec_active);
|
||||
void NotifyNVDECChannelOpen(u64 process_id);
|
||||
void NotifyNVDECChannelClose(u64 process_id);
|
||||
[[nodiscard]] bool GetNVDECActive();
|
||||
[[nodiscard]] bool IsNVDECActiveForProcess(u64 process_id);
|
||||
|
||||
/**
|
||||
* Initialize the debugger.
|
||||
|
||||
@@ -1216,7 +1216,7 @@ Result KServerSession::ReceiveRequest(KernelCore& kernel, uintptr_t server_messa
|
||||
}
|
||||
|
||||
Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
|
||||
KPhysicalAddress server_message_paddr, bool is_hle) {
|
||||
KPhysicalAddress server_message_paddr, bool is_hle, bool session_closed) {
|
||||
// Lock the session.
|
||||
KScopedLightLock lk{m_lock};
|
||||
|
||||
@@ -1248,7 +1248,7 @@ Result KServerSession::SendReply(KernelCore& kernel, uintptr_t server_message, u
|
||||
KEvent* event = request->GetEvent();
|
||||
|
||||
// Check whether we're closed.
|
||||
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed());
|
||||
const bool closed = (client_thread == nullptr || m_parent->IsClientClosed() || session_closed);
|
||||
|
||||
Result result = ResultSuccess;
|
||||
if (!closed) {
|
||||
|
||||
@@ -54,14 +54,14 @@ public:
|
||||
|
||||
Result OnRequest(KernelCore& kernel, KSessionRequest* request);
|
||||
Result SendReply(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
|
||||
KPhysicalAddress server_message_paddr, bool is_hle = false);
|
||||
KPhysicalAddress server_message_paddr, bool is_hle = false, bool session_closed = false);
|
||||
Result ReceiveRequest(KernelCore& kernel, uintptr_t server_message, uintptr_t server_buffer_size,
|
||||
KPhysicalAddress server_message_paddr,
|
||||
std::shared_ptr<Service::HLERequestContext>* out_context = nullptr,
|
||||
std::weak_ptr<Service::SessionRequestManager> manager = {});
|
||||
|
||||
Result SendReplyHLE(KernelCore& kernel) {
|
||||
R_RETURN(this->SendReply(kernel, 0, 0, 0, true));
|
||||
Result SendReplyHLE(KernelCore& kernel, bool session_closed = false) {
|
||||
R_RETURN(this->SendReply(kernel, 0, 0, 0, true, session_closed));
|
||||
}
|
||||
|
||||
Result ReceiveRequestHLE(KernelCore& kernel, std::shared_ptr<Service::HLERequestContext>* out_context,
|
||||
|
||||
@@ -50,7 +50,7 @@ IAudioIn::IAudioIn(Core::System& system_, Manager& manager, size_t session_id,
|
||||
}
|
||||
|
||||
IAudioIn::~IAudioIn() {
|
||||
impl->Free();
|
||||
impl->Free(system);
|
||||
service_context.CloseEvent(event);
|
||||
process->Close(system.Kernel());
|
||||
}
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
@@ -65,7 +68,7 @@ Result IAudioInManager::OpenAudioInAuto(
|
||||
Result IAudioInManager::ListAudioInsAutoFiltered(
|
||||
OutArray<AudioDeviceName, BufferAttr_HipcAutoSelect> out_audio_ins, Out<u32> out_count) {
|
||||
LOG_DEBUG(Service_Audio, "called");
|
||||
*out_count = impl->GetDeviceNames(out_audio_ins, true);
|
||||
*out_count = impl->GetDeviceNames(system, out_audio_ins, true);
|
||||
R_SUCCEED();
|
||||
}
|
||||
|
||||
@@ -90,8 +93,8 @@ Result IAudioInManager::OpenAudioInProtocolSpecified(
|
||||
|
||||
size_t new_session_id{};
|
||||
|
||||
R_TRY(impl->LinkToManager());
|
||||
R_TRY(impl->AcquireSessionId(new_session_id));
|
||||
R_TRY(impl->LinkToManager(system));
|
||||
R_TRY(impl->AcquireSessionId(system, new_session_id));
|
||||
|
||||
LOG_DEBUG(Service_Audio, "Opening new AudioIn, session_id={}, free sessions={}", new_session_id,
|
||||
impl->num_free_sessions);
|
||||
|
||||
@@ -46,7 +46,7 @@ IAudioOut::IAudioOut(Core::System& system_, Manager& manager, size_t session_id,
|
||||
}
|
||||
|
||||
IAudioOut::~IAudioOut() {
|
||||
impl->Free();
|
||||
impl->Free(system);
|
||||
service_context.CloseEvent(event);
|
||||
process->Close(system.Kernel());
|
||||
}
|
||||
|
||||
@@ -77,8 +77,8 @@ Result IAudioOutManager::OpenAudioOutAuto(
|
||||
}
|
||||
|
||||
size_t new_session_id{};
|
||||
R_TRY(impl->LinkToManager());
|
||||
R_TRY(impl->AcquireSessionId(new_session_id));
|
||||
R_TRY(impl->LinkToManager(system));
|
||||
R_TRY(impl->AcquireSessionId(system, new_session_id));
|
||||
|
||||
const auto device_name = Common::StringFromBuffer(name[0].name);
|
||||
LOG_DEBUG(Service_Audio, "Opening new AudioOut, sessionid={}, free sessions={}", new_session_id,
|
||||
|
||||
@@ -227,12 +227,13 @@ Result VfsDirectoryServiceWrapper::RenameDirectory(const std::string& src_path_,
|
||||
std::string src_path(Common::FS::SanitizePath(src_path_));
|
||||
std::string dest_path(Common::FS::SanitizePath(dest_path_));
|
||||
auto src = GetDirectoryRelativeWrapped(backing, src_path);
|
||||
if (src == nullptr)
|
||||
return FileSys::ResultPathNotFound;
|
||||
|
||||
if (Common::FS::GetParentPath(src_path) == Common::FS::GetParentPath(dest_path)) {
|
||||
// Use more-optimized vfs implementation rename.
|
||||
if (src == nullptr)
|
||||
return FileSys::ResultPathNotFound;
|
||||
if (!src->Rename(Common::FS::GetFilename(dest_path))) {
|
||||
// TODO(DarkLordZach): Find a better error code for this
|
||||
std::string full_src_path = backing->GetFullPath() + "/" + src_path;
|
||||
std::string full_dest_path = backing->GetFullPath() + "/" + dest_path;
|
||||
if (!Common::FS::RenameDir(full_src_path, full_dest_path)) {
|
||||
return ResultUnknown;
|
||||
}
|
||||
return ResultSuccess;
|
||||
|
||||
@@ -24,7 +24,7 @@ IFileSystem::IFileSystem(Core::System& system_, FileSys::VirtualDir dir_, SizeGe
|
||||
{3, D<&IFileSystem::DeleteDirectory>, "DeleteDirectory"},
|
||||
{4, D<&IFileSystem::DeleteDirectoryRecursively>, "DeleteDirectoryRecursively"},
|
||||
{5, D<&IFileSystem::RenameFile>, "RenameFile"},
|
||||
{6, nullptr, "RenameDirectory"},
|
||||
{6, D<&IFileSystem::RenameDirectory>, "RenameDirectory"},
|
||||
{7, D<&IFileSystem::GetEntryType>, "GetEntryType"},
|
||||
{8, D<&IFileSystem::OpenFile>, "OpenFile"},
|
||||
{9, D<&IFileSystem::OpenDirectory>, "OpenDirectory"},
|
||||
@@ -88,6 +88,14 @@ Result IFileSystem::RenameFile(
|
||||
R_RETURN(backend->RenameFile(FileSys::Path(old_path->str), FileSys::Path(new_path->str)));
|
||||
}
|
||||
|
||||
Result IFileSystem::RenameDirectory(
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path) {
|
||||
LOG_DEBUG(Service_FS, "called. directory '{}' to directory '{}'", old_path->str, new_path->str);
|
||||
|
||||
R_RETURN(backend->RenameDirectory(FileSys::Path(old_path->str), FileSys::Path(new_path->str)));
|
||||
}
|
||||
|
||||
Result IFileSystem::OpenFile(OutInterface<IFile> out_interface,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path,
|
||||
u32 mode) {
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// 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
|
||||
|
||||
@@ -36,6 +39,8 @@ public:
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path);
|
||||
Result RenameFile(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path);
|
||||
Result RenameDirectory(const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> old_path,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> new_path);
|
||||
Result OpenFile(OutInterface<IFile> out_interface,
|
||||
const InLargeData<FileSys::Sf::Path, BufferAttr_HipcPointer> path, u32 mode);
|
||||
Result OpenDirectory(OutInterface<IDirectory> out_interface,
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/service/nvdrv/core/container.h"
|
||||
#include "core/hle/service/nvdrv/devices/ioctl_serialization.h"
|
||||
#include "core/hle/service/nvdrv/devices/nvhost_nvdec.h"
|
||||
@@ -71,17 +72,23 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, std::span<const u8> in
|
||||
|
||||
void nvhost_nvdec::OnOpen(NvCore::SessionId session_id, DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream started");
|
||||
system.SetNVDECActive(true);
|
||||
sessions[fd] = session_id;
|
||||
if (const auto* session = core.GetSession(session_id);
|
||||
session != nullptr && session->process != nullptr) {
|
||||
system.NotifyNVDECChannelOpen(session->process->GetId());
|
||||
}
|
||||
host1x.StartDevice(fd, Tegra::Host1x::ChannelType::NvDec, channel_syncpoint);
|
||||
}
|
||||
|
||||
void nvhost_nvdec::OnClose(DeviceFD fd) {
|
||||
LOG_INFO(Service_NVDRV, "NVDEC video stream ended");
|
||||
host1x.StopDevice(fd, Tegra::Host1x::ChannelType::NvDec);
|
||||
system.SetNVDECActive(false);
|
||||
auto it = sessions.find(fd);
|
||||
if (it != sessions.end()) {
|
||||
if (const auto* session = core.GetSession(it->second);
|
||||
session != nullptr && session->process != nullptr) {
|
||||
system.NotifyNVDECChannelClose(session->process->GetId());
|
||||
}
|
||||
sessions.erase(it);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -393,7 +393,7 @@ Result ServerManager::CompleteSyncRequest(Session* session) {
|
||||
}
|
||||
|
||||
// Send the reply.
|
||||
res = server_session->SendReplyHLE(m_system.Kernel());
|
||||
res = server_session->SendReplyHLE(m_system.Kernel(), service_res == IPC::ResultSessionClosed);
|
||||
|
||||
// If the session has been closed, we're done.
|
||||
if (res == Kernel::ResultSessionClosed || service_res == IPC::ResultSessionClosed) {
|
||||
|
||||
@@ -4,12 +4,16 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||
|
||||
#include <chrono>
|
||||
#include <fmt/ranges.h>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include "common/assert.h"
|
||||
#include "common/logging.h"
|
||||
#include "common/settings.h"
|
||||
#include "core/core.h"
|
||||
#include "core/hle/ipc.h"
|
||||
#include "core/hle/kernel/k_process.h"
|
||||
#include "core/hle/kernel/kernel.h"
|
||||
#include "core/hle/service/ipc_helpers.h"
|
||||
#include "core/hle/service/service.h"
|
||||
@@ -33,6 +37,7 @@ ServiceFrameworkBase::ServiceFrameworkBase(Core::System& system_, const char* se
|
||||
: SessionRequestHandler(system_.Kernel(), service_name_)
|
||||
, system{system_}
|
||||
, service_name{service_name_}
|
||||
, is_i_storage{std::string_view{service_name_} == "IStorage"}
|
||||
, handler_invoker{handler_invoker_}
|
||||
, max_sessions{max_sessions_}
|
||||
{}
|
||||
@@ -77,13 +82,22 @@ void ServiceFrameworkBase::ReportUnimplementedFunction(HLERequestContext& ctx,
|
||||
}
|
||||
|
||||
void ServiceFrameworkBase::InvokeRequest(HLERequestContext& ctx) {
|
||||
auto it = handlers.find(ctx.GetCommand());
|
||||
const auto command = ctx.GetCommand();
|
||||
auto it = handlers.find(command);
|
||||
const bool is_cmd_read = command == 0;
|
||||
FunctionInfoBase const* info = it == handlers.end() ? nullptr : &it->second;
|
||||
if (info == nullptr || info->handler_callback == nullptr)
|
||||
return ReportUnimplementedFunction(ctx, info);
|
||||
|
||||
LOG_TRACE(Service, "{}", MakeFunctionString(info->name, GetServiceName(), ctx.CommandBuffer()));
|
||||
handler_invoker(this, info->handler_callback, ctx);
|
||||
|
||||
if (is_i_storage && is_cmd_read) {
|
||||
const auto* const process = ctx.GetThread().GetOwnerProcess();
|
||||
if (process != nullptr && system.IsNVDECActiveForProcess(process->GetId())) {
|
||||
std::this_thread::sleep_for(std::chrono::microseconds{600});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void ServiceFrameworkBase::InvokeRequestTipc(HLERequestContext& ctx) {
|
||||
|
||||
@@ -107,6 +107,8 @@ protected:
|
||||
Core::System& system;
|
||||
/// Identifier string used to connect to the service.
|
||||
const char* service_name;
|
||||
/// Whether this is the IStorage service.
|
||||
const bool is_i_storage;
|
||||
/// Function used to safely up-cast pointers to the derived class before invoking a handler.
|
||||
InvokerFn* handler_invoker;
|
||||
/// Maximum number of concurrent sessions that this service can handle.
|
||||
|
||||
@@ -247,7 +247,7 @@ void A32EmitX64::GenTerminalHandlers() {
|
||||
calculate_location_descriptor();
|
||||
code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)]);
|
||||
code.sub(eax, 1);
|
||||
code.and_(eax, u32(A32JitState::RSBPtrMask));
|
||||
code.and_(eax, u32(A32JitState::RSB_PTR_MASK));
|
||||
code.mov(dword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_ptr)], eax);
|
||||
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A32JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
|
||||
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
|
||||
|
||||
@@ -37,9 +37,9 @@ using namespace Backend::X64;
|
||||
|
||||
static RunCodeCallbacks GenRunCodeCallbacks(A32::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A32::UserConfig& conf) {
|
||||
return RunCodeCallbacks{
|
||||
std::make_unique<ArgCallback>(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
ArgCallback(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
ArgCallback(Devirtualize<&A32::UserCallbacks::AddTicks>(cb)),
|
||||
ArgCallback(Devirtualize<&A32::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
conf.enable_cycle_counting,
|
||||
};
|
||||
}
|
||||
@@ -79,7 +79,7 @@ struct Jit::Impl {
|
||||
jit_interface->is_executing = true;
|
||||
const CodePtr current_codeptr = [this] {
|
||||
// RSB optimization
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSBPtrMask;
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A32JitState::RSB_PTR_MASK;
|
||||
if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
|
||||
jit_state.rsb_ptr = new_rsb_ptr;
|
||||
return reinterpret_cast<CodePtr>(jit_state.rsb_codeptrs[new_rsb_ptr]);
|
||||
|
||||
@@ -27,6 +27,9 @@ struct A32JitState {
|
||||
|
||||
A32JitState() { ResetRSB(); }
|
||||
|
||||
static constexpr std::size_t RSB_SIZE = 8; // MUST be a power of 2.
|
||||
static constexpr std::size_t RSB_PTR_MASK = RSB_SIZE - 1;
|
||||
|
||||
std::array<u32, 16> Reg{}; // Current register file.
|
||||
// TODO: Mode-specific register sets unimplemented.
|
||||
|
||||
@@ -36,8 +39,9 @@ struct A32JitState {
|
||||
u32 cpsr_q = 0;
|
||||
u32 cpsr_nzcv = 0;
|
||||
u32 cpsr_jaifm = 0;
|
||||
u32 Cpsr() const;
|
||||
void SetCpsr(u32 cpsr);
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpsr_nzcv = 0;
|
||||
|
||||
alignas(16) std::array<u32, 64> ExtReg{}; // Extension registers.
|
||||
|
||||
@@ -49,21 +53,19 @@ struct A32JitState {
|
||||
// Exclusive state
|
||||
u32 exclusive_state = 0;
|
||||
|
||||
static constexpr std::size_t RSBSize = 8; // MUST be a power of 2.
|
||||
static constexpr std::size_t RSBPtrMask = RSBSize - 1;
|
||||
u32 rsb_ptr = 0;
|
||||
std::array<u64, RSBSize> rsb_location_descriptors;
|
||||
std::array<u64, RSBSize> rsb_codeptrs;
|
||||
void ResetRSB();
|
||||
std::array<u64, RSB_SIZE> rsb_location_descriptors;
|
||||
std::array<u64, RSB_SIZE> rsb_codeptrs;
|
||||
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpsr_nzcv = 0;
|
||||
u32 Cpsr() const;
|
||||
void SetCpsr(u32 cpsr);
|
||||
|
||||
void ResetRSB();
|
||||
u32 Fpscr() const;
|
||||
void SetFpscr(u32 FPSCR);
|
||||
|
||||
u64 GetUniqueHash() const noexcept {
|
||||
return (static_cast<u64>(upper_location_descriptor) << 32) | (static_cast<u64>(Reg[15]));
|
||||
return (u64(upper_location_descriptor) << 32) | (u64(Reg[15]));
|
||||
}
|
||||
|
||||
void TransferJitState(const A32JitState& src, bool reset_rsb) {
|
||||
|
||||
@@ -208,7 +208,7 @@ void A64EmitX64::GenTerminalHandlers() {
|
||||
calculate_location_descriptor();
|
||||
code.mov(eax, dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)]);
|
||||
code.sub(eax, 1);
|
||||
code.and_(eax, u32(A64JitState::RSBPtrMask));
|
||||
code.and_(eax, u32(A64JitState::RSB_PTR_MASK));
|
||||
code.mov(dword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_ptr)], eax);
|
||||
code.cmp(rbx, qword[code.ABI_JIT_PTR + offsetof(A64JitState, rsb_location_descriptors) + rax * sizeof(u64)]);
|
||||
if (conf.HasOptimization(OptimizationFlag::FastDispatch)) {
|
||||
|
||||
@@ -33,9 +33,9 @@ using namespace Backend::X64;
|
||||
|
||||
static RunCodeCallbacks GenRunCodeCallbacks(A64::UserCallbacks* cb, CodePtr (*LookupBlock)(void* lookup_block_arg), void* arg, const A64::UserConfig& conf) {
|
||||
return RunCodeCallbacks{
|
||||
std::make_unique<ArgCallback>(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)),
|
||||
std::make_unique<ArgCallback>(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
ArgCallback(LookupBlock, reinterpret_cast<u64>(arg)),
|
||||
ArgCallback(Devirtualize<&A64::UserCallbacks::AddTicks>(cb)),
|
||||
ArgCallback(Devirtualize<&A64::UserCallbacks::GetTicksRemaining>(cb)),
|
||||
conf.enable_cycle_counting,
|
||||
};
|
||||
}
|
||||
@@ -78,7 +78,7 @@ public:
|
||||
// TODO: Check code alignment
|
||||
const CodePtr current_code_ptr = [this] {
|
||||
// RSB optimization
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSBPtrMask;
|
||||
const u32 new_rsb_ptr = (jit_state.rsb_ptr - 1) & A64JitState::RSB_PTR_MASK;
|
||||
if (jit_state.GetUniqueHash() == jit_state.rsb_location_descriptors[new_rsb_ptr]) {
|
||||
jit_state.rsb_ptr = new_rsb_ptr;
|
||||
return CodePtr(jit_state.rsb_codeptrs[new_rsb_ptr]);
|
||||
|
||||
@@ -29,18 +29,19 @@ struct A64JitState {
|
||||
|
||||
A64JitState() { ResetRSB(); }
|
||||
|
||||
// Exclusive state stuff
|
||||
static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull;
|
||||
// Return stack buffer
|
||||
static constexpr size_t RSB_SIZE = 8; // MUST be a power of 2.
|
||||
static constexpr size_t RSB_PTR_MASK = RSB_SIZE - 1;
|
||||
|
||||
std::array<u64, 31> reg{};
|
||||
u64 sp = 0;
|
||||
u64 pc = 0;
|
||||
|
||||
u32 cpsr_nzcv = 0;
|
||||
|
||||
u32 GetPstate() const {
|
||||
return NZCV::FromX64(cpsr_nzcv);
|
||||
}
|
||||
void SetPstate(u32 new_pstate) {
|
||||
cpsr_nzcv = NZCV::ToX64(new_pstate);
|
||||
}
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpcr = 0;
|
||||
|
||||
alignas(16) std::array<u64, 64> vec{}; // Extension registers.
|
||||
|
||||
@@ -50,29 +51,31 @@ struct A64JitState {
|
||||
volatile u32 halt_reason = 0;
|
||||
|
||||
// Exclusive state
|
||||
static constexpr u64 RESERVATION_GRANULE_MASK = 0xFFFF'FFFF'FFFF'FFF0ull;
|
||||
u8 exclusive_state = 0;
|
||||
|
||||
static constexpr size_t RSBSize = 8; // MUST be a power of 2.
|
||||
static constexpr size_t RSBPtrMask = RSBSize - 1;
|
||||
u32 rsb_ptr = 0;
|
||||
std::array<u64, RSBSize> rsb_location_descriptors;
|
||||
std::array<u64, RSBSize> rsb_codeptrs;
|
||||
std::array<u64, RSB_SIZE> rsb_location_descriptors;
|
||||
std::array<u64, RSB_SIZE> rsb_codeptrs;
|
||||
|
||||
u32 GetPstate() const {
|
||||
return NZCV::FromX64(cpsr_nzcv);
|
||||
}
|
||||
|
||||
void SetPstate(u32 new_pstate) {
|
||||
cpsr_nzcv = NZCV::ToX64(new_pstate);
|
||||
}
|
||||
|
||||
void ResetRSB() {
|
||||
rsb_location_descriptors.fill(0xFFFFFFFFFFFFFFFFull);
|
||||
rsb_codeptrs.fill(0);
|
||||
}
|
||||
|
||||
u32 fpsr_exc = 0;
|
||||
u32 fpsr_qc = 0;
|
||||
u32 fpcr = 0;
|
||||
u32 GetFpcr() const;
|
||||
u32 GetFpsr() const;
|
||||
void SetFpcr(u32 value);
|
||||
void SetFpsr(u32 value);
|
||||
|
||||
u64 GetUniqueHash() const noexcept {
|
||||
const u64 fpcr_u64 = static_cast<u64>(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift;
|
||||
const u64 fpcr_u64 = u64(fpcr & A64::LocationDescriptor::fpcr_mask) << A64::LocationDescriptor::fpcr_shift;
|
||||
const u64 pc_u64 = pc & A64::LocationDescriptor::pc_mask;
|
||||
return pc_u64 | fpcr_u64;
|
||||
}
|
||||
|
||||
@@ -61,73 +61,6 @@ namespace {
|
||||
constexpr size_t CONSTANT_POOL_SIZE = 2 * 1024 * 1024;
|
||||
constexpr size_t PRELUDE_COMMIT_SIZE = 16 * 1024 * 1024;
|
||||
|
||||
class CustomXbyakAllocator : public Xbyak::Allocator {
|
||||
public:
|
||||
#ifdef _WIN32
|
||||
uint8_t* alloc(size_t size) override {
|
||||
void* p = VirtualAlloc(nullptr, size, MEM_RESERVE, PAGE_READWRITE);
|
||||
if (p == nullptr) {
|
||||
using Xbyak::Error;
|
||||
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
|
||||
}
|
||||
return static_cast<uint8_t*>(p);
|
||||
}
|
||||
|
||||
void free(uint8_t* p) override {
|
||||
VirtualFree(static_cast<void*>(p), 0, MEM_RELEASE);
|
||||
}
|
||||
|
||||
bool useProtect() const override { return false; }
|
||||
#else
|
||||
static constexpr size_t DYNARMIC_PAGE_SIZE = 4096;
|
||||
|
||||
// Can't subclass Xbyak::MmapAllocator because it is not a pure interface
|
||||
// and doesn't expose its construtor
|
||||
uint8_t* alloc(size_t size) override {
|
||||
// Waste a page to store the size
|
||||
size += DYNARMIC_PAGE_SIZE;
|
||||
|
||||
int mode = MAP_PRIVATE;
|
||||
#if defined(MAP_ANONYMOUS)
|
||||
mode |= MAP_ANONYMOUS;
|
||||
#elif defined(MAP_ANON)
|
||||
mode |= MAP_ANON;
|
||||
#else
|
||||
# error "not supported"
|
||||
#endif
|
||||
#ifdef MAP_JIT
|
||||
mode |= MAP_JIT;
|
||||
#endif
|
||||
int prot = PROT_READ | PROT_WRITE;
|
||||
#ifdef PROT_MPROTECT
|
||||
// https://man.netbsd.org/mprotect.2 specifies that an mprotect() that is LESS
|
||||
// restrictive than the original mapping MUST fail
|
||||
prot |= PROT_MPROTECT(PROT_READ) | PROT_MPROTECT(PROT_WRITE) | PROT_MPROTECT(PROT_EXEC);
|
||||
#endif
|
||||
void* p = mmap(nullptr, size, prot, mode, -1, 0);
|
||||
if (p == MAP_FAILED) {
|
||||
using Xbyak::Error;
|
||||
XBYAK_THROW(Xbyak::ERR_CANT_ALLOC);
|
||||
}
|
||||
std::memcpy(p, &size, sizeof(size_t));
|
||||
return static_cast<uint8_t*>(p) + DYNARMIC_PAGE_SIZE;
|
||||
}
|
||||
|
||||
void free(uint8_t* p) override {
|
||||
size_t size;
|
||||
std::memcpy(&size, p - DYNARMIC_PAGE_SIZE, sizeof(size_t));
|
||||
munmap(p - DYNARMIC_PAGE_SIZE, size);
|
||||
}
|
||||
|
||||
# ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
bool useProtect() const override { return false; }
|
||||
# endif
|
||||
#endif
|
||||
};
|
||||
|
||||
// This is threadsafe as Xbyak::Allocator does not contain any state; it is a pure interface.
|
||||
CustomXbyakAllocator s_allocator;
|
||||
|
||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
void ProtectMemory(const void* base, size_t size, bool is_executable) {
|
||||
# ifdef _WIN32
|
||||
@@ -145,11 +78,9 @@ void ProtectMemory(const void* base, size_t size, bool is_executable) {
|
||||
|
||||
HostFeature GetHostFeatures() {
|
||||
HostFeature features = {};
|
||||
|
||||
#ifdef DYNARMIC_ENABLE_CPU_FEATURE_DETECTION
|
||||
using Cpu = Xbyak::util::Cpu;
|
||||
Xbyak::util::Cpu cpu_info;
|
||||
|
||||
Xbyak::util::Cpu cpu_info{};
|
||||
if (cpu_info.has(Cpu::tSSSE3))
|
||||
features |= HostFeature::SSSE3;
|
||||
if (cpu_info.has(Cpu::tSSE41))
|
||||
@@ -196,7 +127,6 @@ HostFeature GetHostFeatures() {
|
||||
features |= HostFeature::GFNI;
|
||||
if (cpu_info.has(Cpu::tWAITPKG))
|
||||
features |= HostFeature::WAITPKG;
|
||||
|
||||
if (cpu_info.has(Cpu::tBMI2)) {
|
||||
// BMI2 instructions such as pdep and pext have been very slow up until Zen 3.
|
||||
// Check for Zen 3 or newer by its family (0x19).
|
||||
@@ -214,7 +144,6 @@ HostFeature GetHostFeatures() {
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return features;
|
||||
}
|
||||
|
||||
@@ -233,23 +162,27 @@ bool IsUnderRosetta() {
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
static const auto default_cg_mode = Xbyak::DontSetProtectRWE;
|
||||
#else
|
||||
static const auto default_cg_mode = nullptr; //Allow RWE
|
||||
#endif
|
||||
|
||||
BlockOfCode::BlockOfCode(RunCodeCallbacks cb, JitStateInfo jsi, size_t total_code_size, std::function<void(BlockOfCode&)> rcp)
|
||||
: Xbyak::CodeGenerator(total_code_size, default_cg_mode, &s_allocator)
|
||||
, cb(std::move(cb))
|
||||
, jsi(jsi)
|
||||
, constant_pool(*this, CONSTANT_POOL_SIZE)
|
||||
, host_features(GetHostFeatures()) {
|
||||
: Xbyak::CodeGenerator(total_code_size
|
||||
#ifdef DYNARMIC_ENABLE_NO_EXECUTE_SUPPORT
|
||||
, Xbyak::DontSetProtectRWE
|
||||
#else
|
||||
, nullptr //Allow RWE
|
||||
#endif
|
||||
, nullptr)
|
||||
, constant_pool(*this, CONSTANT_POOL_SIZE)
|
||||
, jsi(jsi)
|
||||
, cb(std::move(cb))
|
||||
{
|
||||
EnableWriting();
|
||||
EnsureMemoryCommitted(PRELUDE_COMMIT_SIZE);
|
||||
GenRunCode(rcp);
|
||||
}
|
||||
|
||||
bool BlockOfCode::HasHostFeature(HostFeature feature) const noexcept {
|
||||
return (GetHostFeatures() & feature) == feature;
|
||||
}
|
||||
|
||||
void BlockOfCode::PreludeComplete() {
|
||||
prelude_complete = true;
|
||||
code_begin = getCurr();
|
||||
@@ -341,7 +274,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
mov(rbx, ABI_PARAM2); // save temporarily in non-volatile register
|
||||
|
||||
if (cb.enable_cycle_counting) {
|
||||
cb.GetTicksRemaining->EmitCall(*this);
|
||||
cb.GetTicksRemaining.EmitCall(*this);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
|
||||
}
|
||||
@@ -388,7 +321,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
cmp(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], 0);
|
||||
jng(return_to_caller);
|
||||
}
|
||||
cb.LookupBlock->EmitCall(*this);
|
||||
cb.LookupBlock.EmitCall(*this);
|
||||
jmp(ABI_RETURN);
|
||||
|
||||
align();
|
||||
@@ -401,7 +334,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
jng(return_to_caller_mxcsr_already_exited);
|
||||
}
|
||||
SwitchMxcsrOnEntry();
|
||||
cb.LookupBlock->EmitCall(*this);
|
||||
cb.LookupBlock.EmitCall(*this);
|
||||
jmp(ABI_RETURN);
|
||||
|
||||
align();
|
||||
@@ -415,7 +348,7 @@ void BlockOfCode::GenRunCode(std::function<void(BlockOfCode&)> rcp) {
|
||||
L(return_to_caller_mxcsr_already_exited);
|
||||
|
||||
if (cb.enable_cycle_counting) {
|
||||
cb.AddTicks->EmitCall(*this, [this](RegList param) {
|
||||
cb.AddTicks.EmitCall(*this, [this](RegList param) {
|
||||
mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
|
||||
sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
|
||||
});
|
||||
@@ -455,18 +388,18 @@ void BlockOfCode::UpdateTicks() {
|
||||
return;
|
||||
}
|
||||
|
||||
cb.AddTicks->EmitCall(*this, [this](RegList param) {
|
||||
cb.AddTicks.EmitCall(*this, [this](RegList param) {
|
||||
mov(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)]);
|
||||
sub(param[0], qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)]);
|
||||
});
|
||||
|
||||
cb.GetTicksRemaining->EmitCall(*this);
|
||||
cb.GetTicksRemaining.EmitCall(*this);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_to_run)], ABI_RETURN);
|
||||
mov(qword[rsp + ABI_SHADOW_SPACE + offsetof(StackLayout, cycles_remaining)], ABI_RETURN);
|
||||
}
|
||||
|
||||
void BlockOfCode::LookupBlock() {
|
||||
cb.LookupBlock->EmitCall(*this);
|
||||
cb.LookupBlock.EmitCall(*this);
|
||||
}
|
||||
|
||||
void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
|
||||
@@ -520,7 +453,7 @@ void BlockOfCode::LoadRequiredFlagsForCondFromRax(IR::Cond cond) {
|
||||
}
|
||||
|
||||
Xbyak::Address BlockOfCode::Const(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
return constant_pool.GetConstant(frame, lower, upper);
|
||||
return constant_pool.GetConstant(*this, frame, lower, upper);
|
||||
}
|
||||
|
||||
CodePtr BlockOfCode::GetCodeBegin() const {
|
||||
|
||||
@@ -31,9 +31,9 @@ namespace Dynarmic::Backend::X64 {
|
||||
using CodePtr = const void*;
|
||||
|
||||
struct RunCodeCallbacks {
|
||||
std::unique_ptr<Callback> LookupBlock;
|
||||
std::unique_ptr<Callback> AddTicks;
|
||||
std::unique_ptr<Callback> GetTicksRemaining;
|
||||
ArgCallback LookupBlock;
|
||||
ArgCallback AddTicks;
|
||||
ArgCallback GetTicksRemaining;
|
||||
bool enable_cycle_counting;
|
||||
};
|
||||
|
||||
@@ -166,27 +166,24 @@ public:
|
||||
|
||||
JitStateInfo GetJitStateInfo() const { return jsi; }
|
||||
|
||||
bool HasHostFeature(HostFeature feature) const {
|
||||
return (host_features & feature) == feature;
|
||||
}
|
||||
bool HasHostFeature(HostFeature feature) const noexcept;
|
||||
|
||||
private:
|
||||
using RunCodeFuncType = HaltReason (*)(void*, CodePtr);
|
||||
static constexpr size_t MXCSR_ALREADY_EXITED = 1 << 0;
|
||||
static constexpr size_t FORCE_RETURN = 1 << 1;
|
||||
|
||||
RunCodeCallbacks cb;
|
||||
ConstantPool constant_pool;
|
||||
JitStateInfo jsi;
|
||||
std::array<const void*, 4> return_from_run_code;
|
||||
RunCodeFuncType run_code = nullptr;
|
||||
RunCodeFuncType step_code = nullptr;
|
||||
RunCodeCallbacks cb;
|
||||
CodePtr code_begin = nullptr;
|
||||
#ifdef _WIN32
|
||||
size_t committed_size = 0;
|
||||
#endif
|
||||
ConstantPool constant_pool;
|
||||
RunCodeFuncType run_code = nullptr;
|
||||
RunCodeFuncType step_code = nullptr;
|
||||
std::array<const void*, 4> return_from_run_code;
|
||||
bool prelude_complete = false;
|
||||
const HostFeature host_features;
|
||||
|
||||
void GenRunCode(std::function<void(BlockOfCode&)> rcp);
|
||||
};
|
||||
|
||||
@@ -16,8 +16,7 @@
|
||||
namespace Dynarmic::Backend::X64 {
|
||||
|
||||
ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
|
||||
: code(code)
|
||||
, insertion_point(0)
|
||||
: insertion_point(0)
|
||||
{
|
||||
code.EnsureMemoryCommitted(align_size + size);
|
||||
code.int3();
|
||||
@@ -25,17 +24,17 @@ ConstantPool::ConstantPool(BlockOfCode& code, size_t size)
|
||||
pool = std::span<ConstantT>(reinterpret_cast<ConstantT*>(code.AllocateFromCodeSpace(size)), size / align_size);
|
||||
}
|
||||
|
||||
Xbyak::Address ConstantPool::GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
Xbyak::Address ConstantPool::GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper) {
|
||||
const auto constant = ConstantT(lower, upper);
|
||||
auto iter = constant_info.find(constant);
|
||||
if (iter == constant_info.end()) {
|
||||
auto it = constant_info.find(constant);
|
||||
if (it == constant_info.end()) {
|
||||
ASSERT(insertion_point < pool.size());
|
||||
ConstantT& target_constant = pool[insertion_point];
|
||||
target_constant = constant;
|
||||
iter = constant_info.insert({constant, &target_constant}).first;
|
||||
it = constant_info.insert({constant, &target_constant}).first;
|
||||
++insertion_point;
|
||||
}
|
||||
return frame[code.rip + iter->second];
|
||||
return frame[code.rip + it->second];
|
||||
}
|
||||
|
||||
} // namespace Dynarmic::Backend::X64
|
||||
|
||||
@@ -29,7 +29,7 @@ class ConstantPool final {
|
||||
public:
|
||||
ConstantPool(BlockOfCode& code, size_t size);
|
||||
|
||||
Xbyak::Address GetConstant(const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
|
||||
Xbyak::Address GetConstant(BlockOfCode& code, const Xbyak::AddressFrame& frame, u64 lower, u64 upper = 0);
|
||||
|
||||
private:
|
||||
static constexpr size_t align_size = 16; // bytes
|
||||
@@ -45,7 +45,6 @@ private:
|
||||
|
||||
ankerl::unordered_dense::map<ConstantT, void*, ConstantHash> constant_info;
|
||||
std::span<ConstantT> pool;
|
||||
BlockOfCode& code;
|
||||
std::size_t insertion_point;
|
||||
};
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
namespace Dynarmic::Backend::X64 {
|
||||
|
||||
enum class HostFeature : u64 {
|
||||
enum class HostFeature : u32 {
|
||||
SSSE3 = 1ULL << 0,
|
||||
SSE41 = 1ULL << 1,
|
||||
SSE42 = 1ULL << 2,
|
||||
|
||||
@@ -1,3 +1,6 @@
|
||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||
|
||||
/* This file is part of the dynarmic project.
|
||||
* Copyright (c) 2016 MerryMage
|
||||
* SPDX-License-Identifier: 0BSD
|
||||
@@ -15,7 +18,7 @@ struct JitStateInfo {
|
||||
: offsetof_guest_MXCSR(offsetof(JitStateType, guest_MXCSR))
|
||||
, offsetof_asimd_MXCSR(offsetof(JitStateType, asimd_MXCSR))
|
||||
, offsetof_rsb_ptr(offsetof(JitStateType, rsb_ptr))
|
||||
, rsb_ptr_mask(JitStateType::RSBPtrMask)
|
||||
, rsb_ptr_mask(JitStateType::RSB_PTR_MASK)
|
||||
, offsetof_rsb_location_descriptors(offsetof(JitStateType, rsb_location_descriptors))
|
||||
, offsetof_rsb_codeptrs(offsetof(JitStateType, rsb_codeptrs))
|
||||
, offsetof_cpsr_nzcv(offsetof(JitStateType, cpsr_nzcv))
|
||||
|
||||
@@ -367,7 +367,9 @@ 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})
|
||||
|
||||
|
||||
Reference in New Issue
Block a user