mirror of
https://git.eden-emu.dev/eden-emu/eden.git
synced 2026-09-14 15:33:06 +00:00
Compare commits
1 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 092a68e198 |
@@ -1,28 +0,0 @@
|
|||||||
From cc15da16e533b2a801934eab2dfeaf3c3949a1dc Mon Sep 17 00:00:00 2001
|
|
||||||
From: crueter <crueter@eden-emu.dev>
|
|
||||||
Date: Mon, 8 Sep 2025 12:28:55 -0400
|
|
||||||
Subject: [PATCH] [cmake] disable NEON runtime check on clang-cl
|
|
||||||
|
|
||||||
When enabling runtime NEON checking for clang-cl, the linker would error out with `undefined symbol: __emit`, since clang doesn't actually implement this instruction. Therefore it makes sense to disable the runtime check by default on this platform, until either this is fixed or a clang-cl compatible intrinsic check is added (I don't have enough knowledge of MSVC to do this)
|
|
||||||
---
|
|
||||||
cmake/OpusConfig.cmake | 7 ++++++-
|
|
||||||
1 file changed, 6 insertions(+), 1 deletion(-)
|
|
||||||
|
|
||||||
diff --git a/cmake/OpusConfig.cmake b/cmake/OpusConfig.cmake
|
|
||||||
index e9319fbad..d0f459e88 100644
|
|
||||||
--- a/cmake/OpusConfig.cmake
|
|
||||||
+++ b/cmake/OpusConfig.cmake
|
|
||||||
@@ -71,7 +71,12 @@ elseif(OPUS_CPU_ARM AND NOT OPUS_DISABLE_INTRINSICS)
|
|
||||||
opus_detect_neon(COMPILER_SUPPORT_NEON)
|
|
||||||
if(COMPILER_SUPPORT_NEON)
|
|
||||||
option(OPUS_USE_NEON "Option to enable NEON" ON)
|
|
||||||
- option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ON)
|
|
||||||
+ if (MSVC AND CMAKE_CXX_COMPILER_ID STREQUAL "Clang")
|
|
||||||
+ set(NEON_RUNTIME_CHECK_DEFAULT OFF)
|
|
||||||
+ else()
|
|
||||||
+ set(NEON_RUNTIME_CHECK_DEFAULT ON)
|
|
||||||
+ endif()
|
|
||||||
+ option(OPUS_MAY_HAVE_NEON "Does runtime check for neon support" ${NEON_RUNTIME_CHECK_DEFAULT})
|
|
||||||
option(OPUS_PRESUME_NEON "Assume target CPU has NEON support" OFF)
|
|
||||||
if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64")
|
|
||||||
set(OPUS_PRESUME_NEON ON)
|
|
||||||
@@ -1,153 +0,0 @@
|
|||||||
From bf455b67b4eaa446ffae5d25410b141b7b1b1082 Mon Sep 17 00:00:00 2001
|
|
||||||
From: crueter <crueter@eden-emu.dev>
|
|
||||||
Date: Mon, 8 Sep 2025 12:08:20 -0400
|
|
||||||
Subject: [PATCH] [cmake] `OPUS_INSTALL` option; only default install if root
|
|
||||||
project
|
|
||||||
|
|
||||||
Signed-off-by: crueter <crueter@eden-emu.dev>
|
|
||||||
---
|
|
||||||
CMakeLists.txt | 112 ++++++++++++++++++++++++++++---------------------
|
|
||||||
1 file changed, 64 insertions(+), 48 deletions(-)
|
|
||||||
|
|
||||||
diff --git a/CMakeLists.txt b/CMakeLists.txt
|
|
||||||
index fcf034b19..08b5e16f8 100644
|
|
||||||
--- a/CMakeLists.txt
|
|
||||||
+++ b/CMakeLists.txt
|
|
||||||
@@ -4,6 +4,13 @@ list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
|
|
||||||
include(OpusPackageVersion)
|
|
||||||
get_package_version(PACKAGE_VERSION PROJECT_VERSION)
|
|
||||||
|
|
||||||
+# root project detection
|
|
||||||
+if(DEFINED PROJECT_NAME)
|
|
||||||
+ set(root_project OFF)
|
|
||||||
+else()
|
|
||||||
+ set(root_project ON)
|
|
||||||
+endif()
|
|
||||||
+
|
|
||||||
project(Opus LANGUAGES C VERSION ${PROJECT_VERSION})
|
|
||||||
|
|
||||||
include(OpusFunctions)
|
|
||||||
@@ -83,12 +90,16 @@ set(OPUS_DNN_FLOAT_DEBUG_HELP_STR "Run DNN computations as float for debugging p
|
|
||||||
option(OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR} OFF)
|
|
||||||
add_feature_info(OPUS_DNN_FLOAT_DEBUG OPUS_DNN_FLOAT_DEBUG ${OPUS_DNN_FLOAT_DEBUG_HELP_STR})
|
|
||||||
|
|
||||||
+set(OPUS_INSTALL_HELP_STR "Install Opus targets")
|
|
||||||
+option(OPUS_INSTALL ${OPUS_INSTALL_HELP_STR} ${root_project})
|
|
||||||
+add_feature_info(OPUS_INSTALL OPUS_INSTALL ${OPUS_INSTALL_HELP_STR})
|
|
||||||
+
|
|
||||||
set(OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR "install pkg-config module.")
|
|
||||||
-option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ON)
|
|
||||||
+option(OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
|
||||||
add_feature_info(OPUS_INSTALL_PKG_CONFIG_MODULE OPUS_INSTALL_PKG_CONFIG_MODULE ${OPUS_INSTALL_PKG_CONFIG_MODULE_HELP_STR})
|
|
||||||
|
|
||||||
set(OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR "install CMake package config module.")
|
|
||||||
-option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ON)
|
|
||||||
+option(OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR} ${OPUS_INSTALL})
|
|
||||||
add_feature_info(OPUS_INSTALL_CMAKE_CONFIG_MODULE OPUS_INSTALL_CMAKE_CONFIG_MODULE ${OPUS_INSTALL_CMAKE_CONFIG_MODULE_HELP_STR})
|
|
||||||
|
|
||||||
set(OPUS_DRED_HELP_STR "enable DRED.")
|
|
||||||
@@ -613,53 +624,58 @@ if(OPUS_BUILD_FRAMEWORK)
|
|
||||||
OUTPUT_NAME Opus)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
-install(TARGETS opus
|
|
||||||
- EXPORT OpusTargets
|
|
||||||
- ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
- LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
- RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
- FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
|
||||||
- PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
|
||||||
-
|
|
||||||
-if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
|
||||||
- set(prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
- set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
- set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
|
||||||
- set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
|
||||||
- set(VERSION ${PACKAGE_VERSION})
|
|
||||||
- if(HAVE_LIBM)
|
|
||||||
- set(LIBM "-lm")
|
|
||||||
+if (OPUS_INSTALL)
|
|
||||||
+ install(TARGETS opus
|
|
||||||
+ EXPORT OpusTargets
|
|
||||||
+ ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
+ LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}
|
|
||||||
+ RUNTIME DESTINATION ${CMAKE_INSTALL_BINDIR}
|
|
||||||
+ FRAMEWORK DESTINATION ${CMAKE_INSTALL_PREFIX}
|
|
||||||
+ PUBLIC_HEADER DESTINATION ${CMAKE_INSTALL_INCLUDEDIR}/opus)
|
|
||||||
+
|
|
||||||
+ if(OPUS_INSTALL_PKG_CONFIG_MODULE)
|
|
||||||
+ set(prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
+ set(exec_prefix ${CMAKE_INSTALL_PREFIX})
|
|
||||||
+ set(libdir ${CMAKE_INSTALL_FULL_LIBDIR})
|
|
||||||
+ set(includedir ${CMAKE_INSTALL_FULL_INCLUDEDIR})
|
|
||||||
+ set(VERSION ${PACKAGE_VERSION})
|
|
||||||
+ if(HAVE_LIBM)
|
|
||||||
+ set(LIBM "-lm")
|
|
||||||
+ endif()
|
|
||||||
+ configure_file(opus.pc.in opus.pc)
|
|
||||||
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
|
||||||
+ DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
|
||||||
+ endif()
|
|
||||||
+
|
|
||||||
+ if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
|
||||||
+ set(CPACK_GENERATOR TGZ)
|
|
||||||
+ include(CPack)
|
|
||||||
+ set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
|
||||||
+ install(EXPORT OpusTargets
|
|
||||||
+ NAMESPACE Opus::
|
|
||||||
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
+
|
|
||||||
+ include(CMakePackageConfigHelpers)
|
|
||||||
+
|
|
||||||
+ set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
|
||||||
+ configure_package_config_file(
|
|
||||||
+ ${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
|
||||||
+ OpusConfig.cmake
|
|
||||||
+ INSTALL_DESTINATION
|
|
||||||
+ ${CMAKE_INSTALL_PACKAGEDIR}
|
|
||||||
+ PATH_VARS
|
|
||||||
+ INCLUDE_INSTALL_DIR
|
|
||||||
+ INSTALL_PREFIX
|
|
||||||
+ ${CMAKE_INSTALL_PREFIX})
|
|
||||||
+
|
|
||||||
+ write_basic_package_version_file(OpusConfigVersion.cmake
|
|
||||||
+ VERSION ${PROJECT_VERSION}
|
|
||||||
+ COMPATIBILITY SameMajorVersion)
|
|
||||||
+
|
|
||||||
+ install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
|
||||||
+ ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
|
||||||
+ DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
endif()
|
|
||||||
- configure_file(opus.pc.in opus.pc)
|
|
||||||
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/opus.pc
|
|
||||||
- DESTINATION ${CMAKE_INSTALL_LIBDIR}/pkgconfig)
|
|
||||||
-endif()
|
|
||||||
-
|
|
||||||
-if(OPUS_INSTALL_CMAKE_CONFIG_MODULE)
|
|
||||||
- set(CPACK_GENERATOR TGZ)
|
|
||||||
- include(CPack)
|
|
||||||
- set(CMAKE_INSTALL_PACKAGEDIR ${CMAKE_INSTALL_LIBDIR}/cmake/${PROJECT_NAME})
|
|
||||||
- install(EXPORT OpusTargets
|
|
||||||
- NAMESPACE Opus::
|
|
||||||
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
-
|
|
||||||
- include(CMakePackageConfigHelpers)
|
|
||||||
-
|
|
||||||
- set(INCLUDE_INSTALL_DIR ${CMAKE_INSTALL_INCLUDEDIR})
|
|
||||||
- configure_package_config_file(${PROJECT_SOURCE_DIR}/cmake/OpusConfig.cmake.in
|
|
||||||
- OpusConfig.cmake
|
|
||||||
- INSTALL_DESTINATION
|
|
||||||
- ${CMAKE_INSTALL_PACKAGEDIR}
|
|
||||||
- PATH_VARS
|
|
||||||
- INCLUDE_INSTALL_DIR
|
|
||||||
- INSTALL_PREFIX
|
|
||||||
- ${CMAKE_INSTALL_PREFIX})
|
|
||||||
- write_basic_package_version_file(OpusConfigVersion.cmake
|
|
||||||
- VERSION ${PROJECT_VERSION}
|
|
||||||
- COMPATIBILITY SameMajorVersion)
|
|
||||||
- install(FILES ${CMAKE_CURRENT_BINARY_DIR}/OpusConfig.cmake
|
|
||||||
- ${CMAKE_CURRENT_BINARY_DIR}/OpusConfigVersion.cmake
|
|
||||||
- DESTINATION ${CMAKE_INSTALL_PACKAGEDIR})
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if(OPUS_BUILD_PROGRAMS)
|
|
||||||
@@ -464,21 +464,6 @@ if (NOT YUZU_STATIC_ROOM)
|
|||||||
if (ZLIB_ADDED)
|
if (ZLIB_ADDED)
|
||||||
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
add_library(ZLIB::ZLIB ALIAS zlibstatic)
|
||||||
endif()
|
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()
|
endif()
|
||||||
|
|
||||||
if(NOT TARGET Boost::headers)
|
if(NOT TARGET Boost::headers)
|
||||||
|
|||||||
@@ -1,22 +0,0 @@
|
|||||||
# SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
# SPDX-FileCopyrightText: 2022 yuzu Emulator Project
|
|
||||||
# SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
find_package(PkgConfig QUIET)
|
|
||||||
pkg_search_module(OPUS QUIET IMPORTED_TARGET opus)
|
|
||||||
|
|
||||||
include(FindPackageHandleStandardArgs)
|
|
||||||
find_package_handle_standard_args(Opus
|
|
||||||
REQUIRED_VARS OPUS_LINK_LIBRARIES
|
|
||||||
VERSION_VAR OPUS_VERSION
|
|
||||||
)
|
|
||||||
|
|
||||||
if (MSYS2)
|
|
||||||
FixMsysPath(PkgConfig::OPUS)
|
|
||||||
endif()
|
|
||||||
|
|
||||||
if (Opus_FOUND AND NOT TARGET Opus::opus)
|
|
||||||
add_library(Opus::opus ALIAS PkgConfig::OPUS)
|
|
||||||
endif()
|
|
||||||
+1
-16
@@ -82,7 +82,7 @@
|
|||||||
"name": "ffmpeg",
|
"name": "ffmpeg",
|
||||||
"package": "FFmpeg",
|
"package": "FFmpeg",
|
||||||
"repo": "crueter-ci/FFmpeg",
|
"repo": "crueter-ci/FFmpeg",
|
||||||
"version": "9.0.1-1788120736-bf1b838f2a"
|
"version": "9.0.1-1788303113-bf1b838f2a"
|
||||||
},
|
},
|
||||||
"fmt": {
|
"fmt": {
|
||||||
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
"hash": "f0da82c545b01692e9fd30fdfb613dbb8dd9716983dcd0ff19ac2a8d36f74beb5540ef38072fdecc1e34191b3682a8542ecbf3a61ef287dbba0a2679d4e023f2",
|
||||||
@@ -210,21 +210,6 @@
|
|||||||
"repo": "jimmy-park/openssl-cmake",
|
"repo": "jimmy-park/openssl-cmake",
|
||||||
"version": "3.6.2"
|
"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": {
|
"quazip": {
|
||||||
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
"hash": "609c240c7f029ac26a37d8fbab51bc16284e05e128b78b9b9c0e95d083538c36047a67d682759ac990e4adb0eeb90f04f1ea7fe2253bbda7e7e3bcce32e53dd8",
|
||||||
"min_version": "1.3",
|
"min_version": "1.3",
|
||||||
|
|||||||
+9
-10
@@ -60,7 +60,6 @@ All other dependencies will be downloaded and built by [CPM](https://github.com/
|
|||||||
* [ZLIB](https://www.zlib.net/) 1.2+
|
* [ZLIB](https://www.zlib.net/) 1.2+
|
||||||
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
* [zstd](https://facebook.github.io/zstd/) 1.5+
|
||||||
* [enet](http://enet.bespin.org/) 1.3+
|
* [enet](http://enet.bespin.org/) 1.3+
|
||||||
* [Opus](https://opus-codec.org/) 1.3+
|
|
||||||
|
|
||||||
Vulkan 1.3.274+ is also needed:
|
Vulkan 1.3.274+ is also needed:
|
||||||
|
|
||||||
@@ -121,7 +120,7 @@ sudo emerge -a \
|
|||||||
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
dev-libs/boost dev-libs/openssl dev-libs/discord-rpc \
|
||||||
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
dev-util/spirv-tools dev-util/spirv-headers dev-util/vulkan-headers \
|
||||||
dev-util/vulkan-utility-libraries dev-util/glslang \
|
dev-util/vulkan-utility-libraries dev-util/glslang \
|
||||||
media-gfx/renderdoc media-libs/libva media-libs/opus media-video/ffmpeg \
|
media-gfx/renderdoc media-libs/libva media-video/ffmpeg \
|
||||||
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
media-libs/VulkanMemoryAllocator media-libs/libsdl3 media-libs/cubeb \
|
||||||
net-libs/enet \
|
net-libs/enet \
|
||||||
sys-libs/zlib \
|
sys-libs/zlib \
|
||||||
@@ -153,7 +152,7 @@ Required USE flags:
|
|||||||
<summary>Arch Linux</summary>
|
<summary>Arch Linux</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl opus qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glslang libzip lz4 ninja nlohmann-json openssl qt6-base qt6-multimedia qt6-charts sdl3 zlib zstd zip unzip vulkan-headers vulkan-utility-libraries libusb spirv-tools spirv-headers
|
||||||
```
|
```
|
||||||
|
|
||||||
* Building with QT Web Engine requires `qt6-webengine` as well.
|
* Building with QT Web Engine requires `qt6-webengine` as well.
|
||||||
@@ -166,7 +165,7 @@ sudo pacman -Syu --needed base-devel boost catch2 cmake enet ffmpeg fmt git glsl
|
|||||||
<summary>Ubuntu, Debian, Mint Linux</summary>
|
<summary>Ubuntu, Debian, Mint Linux</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libopus-dev libasound2t64 vulkan-utility-libraries-dev
|
sudo apt-get install autoconf cmake g++ gcc git glslang-tools libglu1-mesa-dev libhidapi-dev libpulse-dev libtool libudev-dev libxcb-icccm4 libxcb-image0 libxcb-keysyms1 libxcb-render-util0 libxcb-xinerama0 libxcb-xkb1 libxext-dev libxkbcommon-x11-0 mesa-common-dev nasm ninja-build qt6-base-private-dev catch2 libfmt-dev liblz4-dev nlohmann-json3-dev libzstd-dev libssl-dev libavfilter-dev libavcodec-dev libswscale-dev pkg-config zlib1g-dev libva-dev libvdpau-dev qt6-tools-dev qt6-charts-dev libvulkan-dev spirv-tools spirv-headers libusb-1.0-0-dev libxbyak-dev libboost-dev libboost-fiber-dev libboost-context-dev libsdl3-dev libasound2t64 vulkan-utility-libraries-dev
|
||||||
```
|
```
|
||||||
|
|
||||||
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
* Ubuntu 26.04, Linux Mint 22.3, or Debian 13 or later is required.
|
||||||
@@ -213,7 +212,7 @@ First, enable the community repository; [see here](https://wiki.alpinelinux.org/
|
|||||||
# Enable the community repository
|
# Enable the community repository
|
||||||
setup-apkrepos -c
|
setup-apkrepos -c
|
||||||
# Install
|
# Install
|
||||||
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev opus-dev jq patch
|
apk add g++ git cmake make mesa-dev qt6-qtbase-dev qt6-qtbase-private-dev libquazip1-qt6 ffmpeg-dev qt6-charts-dev libusb-dev libtool boost-dev sdl3-dev zstd-dev vulkan-utility-libraries spirv-tools-dev openssl-dev nlohmann-json lz4-dev jq patch
|
||||||
```
|
```
|
||||||
|
|
||||||
</details>
|
</details>
|
||||||
@@ -261,7 +260,7 @@ brew install molten-vk
|
|||||||
|
|
||||||
As root run:
|
As root run:
|
||||||
```sh
|
```sh
|
||||||
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg audio/opus archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
|
pkg install devel/cmake devel/sdl3 devel/boost-libs devel/catch2 devel/libfmt devel/nlohmann-json devel/ninja devel/nasm devel/autoconf devel/pkgconf devel/qt6-base x11-toolkits/qt6-charts devel/simpleini net/enet multimedia/ffnvcodec-headers multimedia/ffmpeg archivers/liblz4 lang/gcc12 graphics/glslang graphics/vulkan-utility-libraries graphics/spirv-tools www/cpp-httplib graphics/vulkan-utility-libraries graphics/vulkan-headers graphics/spirv-headers quazip-qt6
|
||||||
```
|
```
|
||||||
|
|
||||||
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
||||||
@@ -275,7 +274,7 @@ If using FreeBSD 12 or prior, use `devel/pkg-config` instead.
|
|||||||
For NetBSD +10.1:
|
For NetBSD +10.1:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq libopus qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
pkgin install git cmake boost fmtlib SDL3 catch2 libjwt spirv-headers spirv-tools ffmpeg7 libva nlohmann-json jq qt6-qtbase qt6-qtcharts qt6-qtmultimedia qt6-qttools cpp-httplib lz4 vulkan-headers nasm autoconf enet pkg-config libusb1 libcxx frozen
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#netbsd).
|
[Caveats](./Caveats.md#netbsd).
|
||||||
@@ -306,7 +305,7 @@ pkg install gcc14 git cmake unzip nasm autoconf bash pkgconf ffmpeg glslang gmak
|
|||||||
<summary>OpenIndiana</summary>
|
<summary>OpenIndiana</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl opus sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
sudo pkg install git cmake qt6 boost glslang libzip library/lz4 libusb-1 nlohmann-json openssl sdl3 zlib compress/zstd unzip pkg-config nasm autoconf mesa library/libdrm header-drm developer/fmt
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#openindiana).
|
[Caveats](./Caveats.md#openindiana).
|
||||||
@@ -330,7 +329,7 @@ sudo pkgin install git cmake autoconf build-essential libusb-1 nasm gcc13
|
|||||||
|
|
||||||
```sh
|
```sh
|
||||||
BASE="git make autoconf libtool automake-wrapper jq patch"
|
BASE="git make autoconf libtool automake-wrapper jq patch"
|
||||||
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet opus libusb openssl SDL3"
|
MINGW="qt6-base qt6-charts qt6-tools qt6-translations qt6-svg cmake toolchain clang python-pip openssl vulkan-memory-allocator vulkan-devel glslang boost fmt lz4 nlohmann-json zlib zstd enet libusb openssl SDL3"
|
||||||
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
# Either x86_64 or clang-aarch64 (Windows on ARM)
|
||||||
packages="$BASE"
|
packages="$BASE"
|
||||||
for pkg in $MINGW; do
|
for pkg in $MINGW; do
|
||||||
@@ -356,7 +355,7 @@ pacman -Syuu --needed --noconfirm $packages
|
|||||||
<summary>HaikuOS</summary>
|
<summary>HaikuOS</summary>
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel opus_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
pkgman install git cmake patch libfmt_devel nlohmann_json lz4_devel boost1.90_devel vulkan_devel qt6_base_devel qt6_declarative_devel libsdl3_devel ffmpeg7_devel libx11_devel enet_devel catch2_devel quazip1_qt5_devel qt6_5compat_devel glslang qt6_devel qt6_charts_devel cubeb_devel simpleini quazip_qt6_devel
|
||||||
```
|
```
|
||||||
|
|
||||||
[Caveats](./Caveats.md#haikuos).
|
[Caveats](./Caveats.md#haikuos).
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ pkgs.mkShellNoCC {
|
|||||||
git cmake clang gnumake patch jq pkg-config
|
git cmake clang gnumake patch jq pkg-config
|
||||||
# libraries
|
# libraries
|
||||||
openssl boost fmt nlohmann_json lz4 zlib zstd
|
openssl boost fmt nlohmann_json lz4 zlib zstd
|
||||||
enet libopus vulkan-headers vulkan-utility-libraries
|
enet vulkan-headers vulkan-utility-libraries
|
||||||
spirv-tools spirv-headers vulkan-loader unzip
|
spirv-tools spirv-headers vulkan-loader unzip
|
||||||
glslang python3 httplib cpp-jwt ffmpeg-headless
|
glslang python3 httplib cpp-jwt ffmpeg-headless
|
||||||
libusb1 cubeb
|
libusb1 cubeb
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ import android.view.LayoutInflater
|
|||||||
import android.view.MotionEvent
|
import android.view.MotionEvent
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.LinearLayout
|
|
||||||
import android.widget.RadioGroup
|
import android.widget.RadioGroup
|
||||||
import android.widget.TextView
|
import android.widget.TextView
|
||||||
import androidx.drawerlayout.widget.DrawerLayout
|
import androidx.drawerlayout.widget.DrawerLayout
|
||||||
@@ -674,167 +673,6 @@ class QuickSettings(val emulationFragment: EmulationFragment) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun addFrameGenCell(inflater: LayoutInflater, row: ViewGroup): TextView {
|
|
||||||
val cell = inflater.inflate(R.layout.item_quick_settings_frame_gen_cell, row, false)
|
|
||||||
as TextView
|
|
||||||
row.addView(cell)
|
|
||||||
return cell
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addFrameGen(container: ViewGroup) {
|
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
|
||||||
val itemView = inflater.inflate(R.layout.item_quick_settings_frame_gen, container, false)
|
|
||||||
|
|
||||||
val multiplierRow = itemView.findViewById<LinearLayout>(R.id.frame_gen_multipliers)
|
|
||||||
val targetRow = itemView.findViewById<LinearLayout>(R.id.frame_gen_targets)
|
|
||||||
val targetSection = itemView.findViewById<ViewGroup>(R.id.frame_gen_target_section)
|
|
||||||
val flowRow = itemView.findViewById<LinearLayout>(R.id.frame_gen_flow)
|
|
||||||
val flowSection = itemView.findViewById<ViewGroup>(R.id.frame_gen_flow_section)
|
|
||||||
|
|
||||||
val multiplierNames =
|
|
||||||
emulationFragment.resources.getStringArray(R.array.frameGenMultiplierNames)
|
|
||||||
val multiplierValues =
|
|
||||||
emulationFragment.resources.getIntArray(R.array.frameGenMultiplierValues)
|
|
||||||
val targetValues =
|
|
||||||
emulationFragment.resources.getIntArray(R.array.frameGenTargetRateValues)
|
|
||||||
val flowValues =
|
|
||||||
emulationFragment.resources.getIntArray(R.array.frameGenFlowScaleValues)
|
|
||||||
|
|
||||||
val columns = maxOf(
|
|
||||||
multiplierValues.size + 1,
|
|
||||||
targetValues.size,
|
|
||||||
flowValues.size + 1
|
|
||||||
).toFloat()
|
|
||||||
multiplierRow.weightSum = columns
|
|
||||||
targetRow.weightSum = columns
|
|
||||||
flowRow.weightSum = columns
|
|
||||||
|
|
||||||
val powerCell = addFrameGenCell(inflater, multiplierRow)
|
|
||||||
|
|
||||||
val multiplierCells = mutableListOf<TextView>()
|
|
||||||
for (name in multiplierNames) {
|
|
||||||
val cell = addFrameGenCell(inflater, multiplierRow)
|
|
||||||
cell.text = name
|
|
||||||
multiplierCells.add(cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
val targetCells = mutableListOf<TextView>()
|
|
||||||
for (value in targetValues) {
|
|
||||||
val cell = addFrameGenCell(inflater, targetRow)
|
|
||||||
if (value == 0) {
|
|
||||||
cell.setText(R.string.frame_gen_fixed)
|
|
||||||
} else {
|
|
||||||
cell.text = value.toString()
|
|
||||||
}
|
|
||||||
targetCells.add(cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
val autoCell = addFrameGenCell(inflater, flowRow)
|
|
||||||
autoCell.setText(R.string.frame_gen_flow_auto)
|
|
||||||
|
|
||||||
val flowCells = mutableListOf<TextView>()
|
|
||||||
for (value in flowValues) {
|
|
||||||
val cell = addFrameGenCell(inflater, flowRow)
|
|
||||||
cell.text = "$value%"
|
|
||||||
flowCells.add(cell)
|
|
||||||
}
|
|
||||||
|
|
||||||
val inactiveAlpha = 0.38f
|
|
||||||
|
|
||||||
fun refresh() {
|
|
||||||
val enabled = BooleanSetting.RENDERER_FRAME_GEN.getBoolean(needsGlobal = false)
|
|
||||||
val multiplier = IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.getInt(needsGlobal = false)
|
|
||||||
val target = IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.getInt(needsGlobal = false)
|
|
||||||
val fixed = target == 0
|
|
||||||
val flowAuto =
|
|
||||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.getBoolean(needsGlobal = false)
|
|
||||||
val flowScale = IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.getInt(needsGlobal = false)
|
|
||||||
|
|
||||||
var powerLabel = R.string.frame_gen_off
|
|
||||||
var rowAlpha = inactiveAlpha
|
|
||||||
if (enabled) {
|
|
||||||
powerLabel = R.string.frame_gen_on
|
|
||||||
rowAlpha = 1.0f
|
|
||||||
}
|
|
||||||
|
|
||||||
var multiplierAlpha = inactiveAlpha
|
|
||||||
if (enabled && fixed) {
|
|
||||||
multiplierAlpha = 1.0f
|
|
||||||
}
|
|
||||||
|
|
||||||
powerCell.setText(powerLabel)
|
|
||||||
powerCell.isSelected = enabled
|
|
||||||
|
|
||||||
multiplierCells.forEachIndexed { index, cell ->
|
|
||||||
cell.isEnabled = enabled
|
|
||||||
cell.isSelected = enabled && fixed && multiplierValues[index] == multiplier
|
|
||||||
cell.alpha = multiplierAlpha
|
|
||||||
}
|
|
||||||
|
|
||||||
targetSection.alpha = rowAlpha
|
|
||||||
targetCells.forEachIndexed { index, cell ->
|
|
||||||
cell.isEnabled = enabled
|
|
||||||
cell.isSelected = enabled && targetValues[index] == target
|
|
||||||
}
|
|
||||||
|
|
||||||
flowSection.alpha = rowAlpha
|
|
||||||
autoCell.isEnabled = enabled
|
|
||||||
autoCell.isSelected = enabled && flowAuto
|
|
||||||
|
|
||||||
flowCells.forEachIndexed { index, cell ->
|
|
||||||
cell.isEnabled = enabled
|
|
||||||
cell.isSelected = enabled && !flowAuto && flowValues[index] == flowScale
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
powerCell.setOnClickListener {
|
|
||||||
if (BooleanSetting.RENDERER_FRAME_GEN.getBoolean(needsGlobal = false)) {
|
|
||||||
BooleanSetting.RENDERER_FRAME_GEN.setBoolean(false)
|
|
||||||
} else {
|
|
||||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.setInt(multiplierValues.first())
|
|
||||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(0)
|
|
||||||
BooleanSetting.RENDERER_FRAME_GEN.setBoolean(true)
|
|
||||||
}
|
|
||||||
saveSettings()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
multiplierCells.forEachIndexed { index, cell ->
|
|
||||||
cell.setOnClickListener {
|
|
||||||
IntSetting.RENDERER_FRAME_GEN_MULTIPLIER.setInt(multiplierValues[index])
|
|
||||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(0)
|
|
||||||
saveSettings()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
targetCells.forEachIndexed { index, cell ->
|
|
||||||
cell.setOnClickListener {
|
|
||||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.setInt(targetValues[index])
|
|
||||||
saveSettings()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
autoCell.setOnClickListener {
|
|
||||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.setBoolean(true)
|
|
||||||
saveSettings()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
|
|
||||||
flowCells.forEachIndexed { index, cell ->
|
|
||||||
cell.setOnClickListener {
|
|
||||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.setInt(flowValues[index])
|
|
||||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.setBoolean(false)
|
|
||||||
saveSettings()
|
|
||||||
refresh()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
refresh()
|
|
||||||
container.addView(itemView)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun addDivider(container: ViewGroup) {
|
fun addDivider(container: ViewGroup) {
|
||||||
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
val inflater = LayoutInflater.from(emulationFragment.requireContext())
|
||||||
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
|
val dividerView = inflater.inflate(R.layout.item_quick_settings_divider, container, false)
|
||||||
|
|||||||
+2
@@ -38,7 +38,9 @@ enum class BooleanSetting(override val key: String) : AbstractBooleanSetting {
|
|||||||
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
RENDERER_VERTEX_INPUT_DYNAMIC_STATE("vertex_input_dynamic_state"),
|
||||||
RENDERER_SAMPLE_SHADING("sample_shading"),
|
RENDERER_SAMPLE_SHADING("sample_shading"),
|
||||||
RENDERER_FRAME_GEN("frame_gen"),
|
RENDERER_FRAME_GEN("frame_gen"),
|
||||||
|
RENDERER_FRAME_GEN_FP16("frame_gen_fp16"),
|
||||||
RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"),
|
RENDERER_FRAME_GEN_FLOW_SCALE_AUTO("frame_gen_flow_scale_auto"),
|
||||||
|
RENDERER_FRAME_GEN_DUMP_FLOW("frame_gen_dump_flow"),
|
||||||
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
GPU_UNSWIZZLE_ENABLED("gpu_unswizzle_enabled"),
|
||||||
PICTURE_IN_PICTURE("picture_in_picture"),
|
PICTURE_IN_PICTURE("picture_in_picture"),
|
||||||
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
USE_CUSTOM_RTC("custom_rtc_enabled"),
|
||||||
|
|||||||
+17
-1
@@ -123,7 +123,9 @@ abstract class SettingsItem(
|
|||||||
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key,
|
IntSetting.RENDERER_FRAME_GEN_TARGET_RATE.key,
|
||||||
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key,
|
IntSetting.RENDERER_FRAME_GEN_QUEUE_TARGET.key,
|
||||||
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key,
|
BooleanSetting.RENDERER_FRAME_GEN_FLOW_SCALE_AUTO.key,
|
||||||
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key
|
IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key,
|
||||||
|
BooleanSetting.RENDERER_FRAME_GEN_FP16.key,
|
||||||
|
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key
|
||||||
)
|
)
|
||||||
|
|
||||||
const val TYPE_HEADER = 0
|
const val TYPE_HEADER = 0
|
||||||
@@ -707,6 +709,20 @@ abstract class SettingsItem(
|
|||||||
units = "%"
|
units = "%"
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_FRAME_GEN_FP16,
|
||||||
|
titleId = R.string.frame_gen_fp16,
|
||||||
|
descriptionId = R.string.frame_gen_fp16_description
|
||||||
|
)
|
||||||
|
)
|
||||||
|
put(
|
||||||
|
SwitchSetting(
|
||||||
|
BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW,
|
||||||
|
titleId = R.string.frame_gen_dump_flow,
|
||||||
|
descriptionId = R.string.frame_gen_dump_flow_description
|
||||||
|
)
|
||||||
|
)
|
||||||
put(
|
put(
|
||||||
SingleChoiceSetting(
|
SingleChoiceSetting(
|
||||||
IntSetting.RENDERER_SCREEN_LAYOUT,
|
IntSetting.RENDERER_SCREEN_LAYOUT,
|
||||||
|
|||||||
+2
@@ -124,6 +124,7 @@ class SettingsFragmentPresenter(
|
|||||||
) {
|
) {
|
||||||
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
add(IntSetting.RENDERER_FRAME_GEN_FLOW_SCALE.key)
|
||||||
}
|
}
|
||||||
|
add(BooleanSetting.RENDERER_FRAME_GEN_FP16.key)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1552,6 +1553,7 @@ class SettingsFragmentPresenter(
|
|||||||
add(BooleanSetting.DUMP_GUEST_SHADERS.key)
|
add(BooleanSetting.DUMP_GUEST_SHADERS.key)
|
||||||
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
add(BooleanSetting.GPU_LOG_SHADER_DUMPS.key)
|
||||||
add(BooleanSetting.DUMP_MACROS.key)
|
add(BooleanSetting.DUMP_MACROS.key)
|
||||||
|
add(BooleanSetting.RENDERER_FRAME_GEN_DUMP_FLOW.key)
|
||||||
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
add(BooleanSetting.GPU_LOG_MEMORY_TRACKING.key)
|
||||||
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
add(BooleanSetting.GPU_LOG_DRIVER_DEBUG.key)
|
||||||
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
add(IntSetting.GPU_LOG_RING_BUFFER_SIZE.key)
|
||||||
|
|||||||
@@ -92,7 +92,6 @@ import org.yuzu.yuzu_emu.utils.GameIconUtils
|
|||||||
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
import org.yuzu.yuzu_emu.utils.GpuDriverHelper
|
||||||
import org.yuzu.yuzu_emu.utils.InputHandler
|
import org.yuzu.yuzu_emu.utils.InputHandler
|
||||||
import org.yuzu.yuzu_emu.utils.Log
|
import org.yuzu.yuzu_emu.utils.Log
|
||||||
import org.yuzu.yuzu_emu.utils.LosslessScalingHelper
|
|
||||||
import org.yuzu.yuzu_emu.utils.NativeConfig
|
import org.yuzu.yuzu_emu.utils.NativeConfig
|
||||||
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
import org.yuzu.yuzu_emu.utils.NativeFreedrenoConfig
|
||||||
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
import org.yuzu.yuzu_emu.utils.NativePostProcessing
|
||||||
@@ -1189,11 +1188,6 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback {
|
|||||||
|
|
||||||
quickSettings.addDivider(container)
|
quickSettings.addDivider(container)
|
||||||
|
|
||||||
if (LosslessScalingHelper.isInstalled() && LosslessScalingHelper.isSupportedByGpu()) {
|
|
||||||
quickSettings.addFrameGen(container)
|
|
||||||
quickSettings.addDivider(container)
|
|
||||||
}
|
|
||||||
|
|
||||||
quickSettings.addIntSetting(
|
quickSettings.addIntSetting(
|
||||||
R.string.renderer_accuracy,
|
R.string.renderer_accuracy,
|
||||||
container,
|
container,
|
||||||
|
|||||||
@@ -79,13 +79,6 @@ class LicensesFragment : Fragment() {
|
|||||||
R.string.license_ffmpeg_copyright,
|
R.string.license_ffmpeg_copyright,
|
||||||
R.string.license_ffmpeg_text
|
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(
|
License(
|
||||||
R.string.license_sirit,
|
R.string.license_sirit,
|
||||||
R.string.license_sirit_description,
|
R.string.license_sirit_description,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
// SPDX-FileCopyrightText: 2023 yuzu Emulator Project
|
||||||
@@ -37,7 +37,7 @@ class Game(
|
|||||||
|
|
||||||
val settingsName: String
|
val settingsName: String
|
||||||
get() {
|
get() {
|
||||||
val programIdLong = programId.toLongOrNull() ?: 0L
|
val programIdLong = programId.toLong()
|
||||||
return if (programIdLong == 0L) {
|
return if (programIdLong == 0L) {
|
||||||
FileUtil.getFilename(Uri.parse(path))
|
FileUtil.getFilename(Uri.parse(path))
|
||||||
} else {
|
} else {
|
||||||
@@ -47,7 +47,7 @@ class Game(
|
|||||||
|
|
||||||
val programIdHex: String
|
val programIdHex: String
|
||||||
get() {
|
get() {
|
||||||
val programIdLong = programId.toLongOrNull() ?: 0L
|
val programIdLong = programId.toLong()
|
||||||
return if (programIdLong == 0L) {
|
return if (programIdLong == 0L) {
|
||||||
"0"
|
"0"
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -1168,7 +1168,7 @@ VkPhysicalDeviceProperties GetVulkanDeviceProperties() {
|
|||||||
return physical_device.GetProperties();
|
return physical_device.GetProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
bool GetFrameGenerationSupport() {
|
bool GetVulkanMemoryModelSupport() {
|
||||||
Common::DynamicLibrary library;
|
Common::DynamicLibrary library;
|
||||||
if (!library.Open("libvulkan.so")) {
|
if (!library.Open("libvulkan.so")) {
|
||||||
return false;
|
return false;
|
||||||
@@ -1183,13 +1183,9 @@ bool GetFrameGenerationSupport() {
|
|||||||
|
|
||||||
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
const Vulkan::vk::PhysicalDevice physical_device(physical_devices[0], dld);
|
||||||
|
|
||||||
VkPhysicalDeviceShaderFloat16Int8Features float16_int8{
|
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_FLOAT16_INT8_FEATURES,
|
|
||||||
.pNext = nullptr,
|
|
||||||
};
|
|
||||||
VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{
|
VkPhysicalDeviceVulkanMemoryModelFeatures memory_model{
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_MEMORY_MODEL_FEATURES,
|
||||||
.pNext = &float16_int8,
|
.pNext = nullptr,
|
||||||
};
|
};
|
||||||
VkPhysicalDeviceFeatures2 features{
|
VkPhysicalDeviceFeatures2 features{
|
||||||
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
|
||||||
@@ -1197,7 +1193,7 @@ bool GetFrameGenerationSupport() {
|
|||||||
};
|
};
|
||||||
physical_device.GetFeatures2(features);
|
physical_device.GetFeatures2(features);
|
||||||
|
|
||||||
return memory_model.vulkanMemoryModel == VK_TRUE && float16_int8.shaderFloat16 == VK_TRUE;
|
return memory_model.vulkanMemoryModel == VK_TRUE;
|
||||||
}
|
}
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
@@ -1276,7 +1272,7 @@ jstring Java_org_yuzu_yuzu_1emu_NativeLibrary_getVulkanApiVersion(JNIEnv* env, j
|
|||||||
|
|
||||||
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) {
|
jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_supportsFrameGeneration(JNIEnv* env, jobject jobj) {
|
||||||
try {
|
try {
|
||||||
return static_cast<jboolean>(GetFrameGenerationSupport());
|
return static_cast<jboolean>(GetVulkanMemoryModelSupport());
|
||||||
} catch (...) {
|
} catch (...) {
|
||||||
return static_cast<jboolean>(false);
|
return static_cast<jboolean>(false);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:color="?attr/colorOnPrimary" android:state_selected="true" />
|
|
||||||
<item android:color="?attr/colorOnSurface" />
|
|
||||||
</selector>
|
|
||||||
@@ -1,31 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<ripple xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:color="?attr/colorControlHighlight">
|
|
||||||
|
|
||||||
<item android:id="@android:id/mask">
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<solid android:color="@android:color/white" />
|
|
||||||
<corners android:radius="8dp" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
<item>
|
|
||||||
<selector>
|
|
||||||
<item android:state_selected="true">
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<solid android:color="?attr/colorPrimary" />
|
|
||||||
<corners android:radius="8dp" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
<item>
|
|
||||||
<shape android:shape="rectangle">
|
|
||||||
<corners android:radius="8dp" />
|
|
||||||
<stroke
|
|
||||||
android:width="1dp"
|
|
||||||
android:color="?attr/colorOutline" />
|
|
||||||
</shape>
|
|
||||||
</item>
|
|
||||||
</selector>
|
|
||||||
</item>
|
|
||||||
|
|
||||||
</ripple>
|
|
||||||
@@ -1,111 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical">
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="18dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
style="@style/TextAppearance.Material3.TitleSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/frame_gen" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/frame_gen_quick_description"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/frame_gen_multipliers"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="horizontal" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/frame_gen_target_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="18dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
style="@style/TextAppearance.Material3.TitleSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/frame_gen_target_rate" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/frame_gen_target_rate_quick_description"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/frame_gen_targets"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="horizontal" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/frame_gen_flow_section"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:paddingStart="24dp"
|
|
||||||
android:paddingEnd="18dp"
|
|
||||||
android:paddingTop="12dp"
|
|
||||||
android:paddingBottom="8dp">
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
style="@style/TextAppearance.Material3.TitleSmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/frame_gen_flow_scale" />
|
|
||||||
|
|
||||||
<com.google.android.material.textview.MaterialTextView
|
|
||||||
style="@style/TextAppearance.Material3.BodySmall"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="4dp"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:text="@string/frame_gen_flow_scale_quick_description"
|
|
||||||
android:textColor="?attr/colorOnSurfaceVariant" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:id="@+id/frame_gen_flow"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginTop="10dp"
|
|
||||||
android:orientation="horizontal" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<com.google.android.material.textview.MaterialTextView xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
style="@style/TextAppearance.Material3.LabelLarge"
|
|
||||||
android:layout_width="0dp"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:layout_marginEnd="6dp"
|
|
||||||
android:background="@drawable/frame_gen_cell_background"
|
|
||||||
android:clickable="true"
|
|
||||||
android:focusable="true"
|
|
||||||
android:gravity="center"
|
|
||||||
android:maxLines="1"
|
|
||||||
android:textColor="@color/frame_gen_cell_text" />
|
|
||||||
@@ -305,6 +305,8 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 إطارًا في الثانية</string>
|
<string name="frame_gen_target_rate_60">60 إطارًا في الثانية</string>
|
||||||
<string name="frame_gen_target_rate_90">90 إطارًا في الثانية</string>
|
<string name="frame_gen_target_rate_90">90 إطارًا في الثانية</string>
|
||||||
<string name="frame_gen_target_rate_120">120 إطارًا في الثانية</string>
|
<string name="frame_gen_target_rate_120">120 إطارًا في الثانية</string>
|
||||||
|
<string name="frame_gen_target_rate_144">144 إطارًا في الثانية</string>
|
||||||
|
<string name="frame_gen_target_rate_165">165 إطارًا في الثانية</string>
|
||||||
<string name="frame_gen_queue_target">هدف قائمة انتظار الإطارات</string>
|
<string name="frame_gen_queue_target">هدف قائمة انتظار الإطارات</string>
|
||||||
<string name="frame_gen_queue_target_description">كم عدد الإطارات المكتملة التي قد تنتظر قبل عرضها؟ تعمل قوائم الانتظار الأكبر حجمًا على امتصاص الارتفاعات المفاجئة في حمل وحدة معالجة الرسومات على حساب زمن انتقال الإدخال.</string>
|
<string name="frame_gen_queue_target_description">كم عدد الإطارات المكتملة التي قد تنتظر قبل عرضها؟ تعمل قوائم الانتظار الأكبر حجمًا على امتصاص الارتفاعات المفاجئة في حمل وحدة معالجة الرسومات على حساب زمن انتقال الإدخال.</string>
|
||||||
<string name="frame_gen_queue_target_0">أقل زمن انتقال (بدون تخزين مؤقت)</string>
|
<string name="frame_gen_queue_target_0">أقل زمن انتقال (بدون تخزين مؤقت)</string>
|
||||||
@@ -314,6 +316,10 @@
|
|||||||
<string name="frame_gen_flow_scale_auto_description">قم بتقدير الحركة بناءً على الدقة التي تعرضها اللعبة فعليًّا، بدلاً من الإخراج الذي تم رفع دقته. ولا يؤثر ذلك على الدقة بأي شكل، لأن رفع الدقة لا يضيف أي تفاصيل تتعلق بالحركة.</string>
|
<string name="frame_gen_flow_scale_auto_description">قم بتقدير الحركة بناءً على الدقة التي تعرضها اللعبة فعليًّا، بدلاً من الإخراج الذي تم رفع دقته. ولا يؤثر ذلك على الدقة بأي شكل، لأن رفع الدقة لا يضيف أي تفاصيل تتعلق بالحركة.</string>
|
||||||
<string name="frame_gen_flow_scale">دقة تقدير الحركة</string>
|
<string name="frame_gen_flow_scale">دقة تقدير الحركة</string>
|
||||||
<string name="frame_gen_flow_scale_description">دقة مسار التدفق البصري، كجزء من الناتج. ويُعد خفض هذه القيمة أرخص طريقة لاستعادة الأداء.</string>
|
<string name="frame_gen_flow_scale_description">دقة مسار التدفق البصري، كجزء من الناتج. ويُعد خفض هذه القيمة أرخص طريقة لاستعادة الأداء.</string>
|
||||||
|
<string name="frame_gen_fp16">مُظلِّلات نصف الدقة</string>
|
||||||
|
<string name="frame_gen_fp16_description">استخدم نسخة التظليل 16 بت. يتم التراجع تلقائيًا إلى الخيار البديل في حالة عدم توفرها في برنامج التشغيل أو الملف.</string>
|
||||||
|
<string name="frame_gen_dump_flow">إفراغ الإطار الذي تم إنشاؤه</string>
|
||||||
|
<string name="frame_gen_dump_flow_description">قم بكتابة مستويات MIP للتدفق البصري والإطار المُستكمل إلى مجلد lossless/debug مرة واحدة، لغرض استكشاف الأخطاء وإصلاحها</string>
|
||||||
<string name="frame_gen_unsupported">توليد الإطار غير متاح</string>
|
<string name="frame_gen_unsupported">توليد الإطار غير متاح</string>
|
||||||
<string name="frame_gen_unsupported_description">لا يدعم برنامج تشغيل وحدة معالجة الرسومات هذا نموذج ذاكرة Vulkan، الذي تتطلبه برامج التظليل الخاصة بـ«Lossless Scaling».</string>
|
<string name="frame_gen_unsupported_description">لا يدعم برنامج تشغيل وحدة معالجة الرسومات هذا نموذج ذاكرة Vulkan، الذي تتطلبه برامج التظليل الخاصة بـ«Lossless Scaling».</string>
|
||||||
<string name="lossless_scaling_setup_description">اختياري. قم بتوفير ملف Lossless.dll الخاص بك لتمكين إنشاء الإطارات لاحقًا</string>
|
<string name="lossless_scaling_setup_description">اختياري. قم بتوفير ملف Lossless.dll الخاص بك لتمكين إنشاء الإطارات لاحقًا</string>
|
||||||
|
|||||||
@@ -304,6 +304,8 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||||
<string name="frame_gen_queue_target_1">Ausbalanciert (1 Bild)</string>
|
<string name="frame_gen_queue_target_1">Ausbalanciert (1 Bild)</string>
|
||||||
<string name="frame_gen_queue_target_2">Flüssigste (2 Bilder)</string>
|
<string name="frame_gen_queue_target_2">Flüssigste (2 Bilder)</string>
|
||||||
<string name="frame_gen_unsupported">Frame-Generation nicht verfügbar</string>
|
<string name="frame_gen_unsupported">Frame-Generation nicht verfügbar</string>
|
||||||
|
|||||||
@@ -301,9 +301,12 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||||
<string name="frame_gen_queue_target_0">Latencia más baja (Sin búfer)</string>
|
<string name="frame_gen_queue_target_0">Latencia más baja (Sin búfer)</string>
|
||||||
<string name="frame_gen_queue_target_1">Equilibrado (1 fotograma)</string>
|
<string name="frame_gen_queue_target_1">Equilibrado (1 fotograma)</string>
|
||||||
<string name="frame_gen_queue_target_2">Más suave (2 fotogramas)</string>
|
<string name="frame_gen_queue_target_2">Más suave (2 fotogramas)</string>
|
||||||
|
<string name="frame_gen_fp16">Sombreadores de media precisión</string>
|
||||||
<string name="frame_gen_unsupported">Generación de fotogramas no disponbile</string>
|
<string name="frame_gen_unsupported">Generación de fotogramas no disponbile</string>
|
||||||
<string name="lossless_scaling_install">Instalar Lossless.dll</string>
|
<string name="lossless_scaling_install">Instalar Lossless.dll</string>
|
||||||
<string name="lossless_scaling_replace_description">Seleccionar una copia diferente de Lossless.dll</string>
|
<string name="lossless_scaling_replace_description">Seleccionar una copia diferente de Lossless.dll</string>
|
||||||
|
|||||||
@@ -308,6 +308,10 @@
|
|||||||
<string name="frame_gen_flow_scale_auto_description">Оценивать движение в разрешении, которое игра действительно рендерит, вместо масштабированного вывода. Ничего не стоит в точности, так как масштабирование не добавляет деталей движения.</string>
|
<string name="frame_gen_flow_scale_auto_description">Оценивать движение в разрешении, которое игра действительно рендерит, вместо масштабированного вывода. Ничего не стоит в точности, так как масштабирование не добавляет деталей движения.</string>
|
||||||
<string name="frame_gen_flow_scale">Разрешение оценки движения</string>
|
<string name="frame_gen_flow_scale">Разрешение оценки движения</string>
|
||||||
<string name="frame_gen_flow_scale_description">Разрешение прохода оптического потока в долях от выходного разрешения. Его понижение — самый дешёвый способ вернуть производительность.</string>
|
<string name="frame_gen_flow_scale_description">Разрешение прохода оптического потока в долях от выходного разрешения. Его понижение — самый дешёвый способ вернуть производительность.</string>
|
||||||
|
<string name="frame_gen_fp16">Шейдеры половинной точности</string>
|
||||||
|
<string name="frame_gen_fp16_description">Использовать 16-битную версию шейдеров. Автоматически переключается на обычную, если драйвер или файл не поддерживают её.</string>
|
||||||
|
<string name="frame_gen_dump_flow">Сохранить сгенерированный кадр</string>
|
||||||
|
<string name="frame_gen_dump_flow_description">Однократно записать уровни мип-карт оптического потока и интерполированный кадр в папку lossless/debug для диагностики.</string>
|
||||||
<string name="frame_gen_unsupported">Генерация кадров недоступна</string>
|
<string name="frame_gen_unsupported">Генерация кадров недоступна</string>
|
||||||
<string name="frame_gen_unsupported_description">Драйвер ГПУ не поддерживает модель памяти Vulkan, требуемую шейдерами Lossless Scaling.</string>
|
<string name="frame_gen_unsupported_description">Драйвер ГПУ не поддерживает модель памяти Vulkan, требуемую шейдерами Lossless Scaling.</string>
|
||||||
<string name="lossless_scaling_setup_description">Опционально. Укажите свой Lossless.dll для включения генерации кадров позже.</string>
|
<string name="lossless_scaling_setup_description">Опционально. Укажите свой Lossless.dll для включения генерации кадров позже.</string>
|
||||||
|
|||||||
@@ -305,6 +305,8 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||||
<string name="frame_gen_queue_target">帧队列目标</string>
|
<string name="frame_gen_queue_target">帧队列目标</string>
|
||||||
<string name="frame_gen_queue_target_description">在显示之前有多少已完成渲染的帧正在等待。较大的队列可以缓解 GPU 突发的压力,但会增加输入延迟。</string>
|
<string name="frame_gen_queue_target_description">在显示之前有多少已完成渲染的帧正在等待。较大的队列可以缓解 GPU 突发的压力,但会增加输入延迟。</string>
|
||||||
<string name="frame_gen_queue_target_0">最低延迟 (无缓冲)</string>
|
<string name="frame_gen_queue_target_0">最低延迟 (无缓冲)</string>
|
||||||
@@ -314,6 +316,10 @@
|
|||||||
<string name="frame_gen_flow_scale_auto_description">在游戏实际渲染的分辨率下估算运动,而不是在放大后的输出上。不会影响精确性,因为放大并不会增加运动细节。</string>
|
<string name="frame_gen_flow_scale_auto_description">在游戏实际渲染的分辨率下估算运动,而不是在放大后的输出上。不会影响精确性,因为放大并不会增加运动细节。</string>
|
||||||
<string name="frame_gen_flow_scale">运动预估分辨率</string>
|
<string name="frame_gen_flow_scale">运动预估分辨率</string>
|
||||||
<string name="frame_gen_flow_scale_description">光流通道的分辨率,以输出的比例表示。降低它是提升性能最经济的做法。</string>
|
<string name="frame_gen_flow_scale_description">光流通道的分辨率,以输出的比例表示。降低它是提升性能最经济的做法。</string>
|
||||||
|
<string name="frame_gen_fp16">半精度着色器</string>
|
||||||
|
<string name="frame_gen_fp16_description">使用 16 位着色器变体。如果驱动或文件不支持会自动回退。</string>
|
||||||
|
<string name="frame_gen_dump_flow">转储已生成的帧</string>
|
||||||
|
<string name="frame_gen_dump_flow_description">为了排查问题,把光流 mip 级别和插值帧写入 lossless/debug 文件夹一次</string>
|
||||||
<string name="frame_gen_unsupported">帧生成不可用</string>
|
<string name="frame_gen_unsupported">帧生成不可用</string>
|
||||||
<string name="frame_gen_unsupported_description">这个 GPU 驱动不支持无损缩放着色器所需的 Vulkan 内存模型。</string>
|
<string name="frame_gen_unsupported_description">这个 GPU 驱动不支持无损缩放着色器所需的 Vulkan 内存模型。</string>
|
||||||
<string name="lossless_scaling_setup_description">作为可选项。请提供您自己的 Lossless.dll 以便在之后可以启用帧生成。</string>
|
<string name="lossless_scaling_setup_description">作为可选项。请提供您自己的 Lossless.dll 以便在之后可以启用帧生成。</string>
|
||||||
|
|||||||
@@ -305,6 +305,8 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||||
|
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||||
<string name="frame_gen_queue_target">影格佇列目標</string>
|
<string name="frame_gen_queue_target">影格佇列目標</string>
|
||||||
<string name="frame_gen_queue_target_description">最多允許多少個已完成的影格在顯示器前等待,較大的佇列可以吸收 GPU 突發負載,但會增加輸入延遲</string>
|
<string name="frame_gen_queue_target_description">最多允許多少個已完成的影格在顯示器前等待,較大的佇列可以吸收 GPU 突發負載,但會增加輸入延遲</string>
|
||||||
<string name="frame_gen_queue_target_0">最低延遲(無緩衝)</string>
|
<string name="frame_gen_queue_target_0">最低延遲(無緩衝)</string>
|
||||||
@@ -314,6 +316,10 @@
|
|||||||
<string name="frame_gen_flow_scale_auto_description">以遊戲實際渲染的解析度而非升頻後的輸出進行運動預測。由於升頻後不會增加任何動態細節,因此不會影響準確度</string>
|
<string name="frame_gen_flow_scale_auto_description">以遊戲實際渲染的解析度而非升頻後的輸出進行運動預測。由於升頻後不會增加任何動態細節,因此不會影響準確度</string>
|
||||||
<string name="frame_gen_flow_scale">運動預測解析度</string>
|
<string name="frame_gen_flow_scale">運動預測解析度</string>
|
||||||
<string name="frame_gen_flow_scale_description">光流處理的解析度,以輸出解析度的比例表示。降低此設定值是減少效能負載最有效的方法</string>
|
<string name="frame_gen_flow_scale_description">光流處理的解析度,以輸出解析度的比例表示。降低此設定值是減少效能負載最有效的方法</string>
|
||||||
|
<string name="frame_gen_fp16">半準確著色器</string>
|
||||||
|
<string name="frame_gen_fp16_description">使用16位元著色器。如果驅動程式或著色器檔案不支援則會自動切換成其它版本</string>
|
||||||
|
<string name="frame_gen_dump_flow">傾印生成的著色器</string>
|
||||||
|
<string name="frame_gen_dump_flow_description">將光流的 MIP 層級與補間影格寫入 Eden 資料夾中的 lossless\debug 資料夾以便進行疑難排解</string>
|
||||||
<string name="frame_gen_unsupported">無法使用影格生成</string>
|
<string name="frame_gen_unsupported">無法使用影格生成</string>
|
||||||
<string name="frame_gen_unsupported_description">Lossless Scaling 的著色器需要 Vulkan 記憶體模型,所選的驅動程式不支援該功能</string>
|
<string name="frame_gen_unsupported_description">Lossless Scaling 的著色器需要 Vulkan 記憶體模型,所選的驅動程式不支援該功能</string>
|
||||||
<string name="lossless_scaling_setup_description">可選擇安裝自己擁有的 Lossless.dll 以在之後啟用影格生成功能</string>
|
<string name="lossless_scaling_setup_description">可選擇安裝自己擁有的 Lossless.dll 以在之後啟用影格生成功能</string>
|
||||||
|
|||||||
@@ -174,6 +174,8 @@
|
|||||||
<item>@string/frame_gen_target_rate_60</item>
|
<item>@string/frame_gen_target_rate_60</item>
|
||||||
<item>@string/frame_gen_target_rate_90</item>
|
<item>@string/frame_gen_target_rate_90</item>
|
||||||
<item>@string/frame_gen_target_rate_120</item>
|
<item>@string/frame_gen_target_rate_120</item>
|
||||||
|
<item>@string/frame_gen_target_rate_144</item>
|
||||||
|
<item>@string/frame_gen_target_rate_165</item>
|
||||||
</string-array>
|
</string-array>
|
||||||
|
|
||||||
<integer-array name="frameGenTargetRateValues">
|
<integer-array name="frameGenTargetRateValues">
|
||||||
@@ -181,12 +183,8 @@
|
|||||||
<item>60</item>
|
<item>60</item>
|
||||||
<item>90</item>
|
<item>90</item>
|
||||||
<item>120</item>
|
<item>120</item>
|
||||||
</integer-array>
|
<item>144</item>
|
||||||
|
<item>165</item>
|
||||||
<integer-array name="frameGenFlowScaleValues">
|
|
||||||
<item>50</item>
|
|
||||||
<item>75</item>
|
|
||||||
<item>100</item>
|
|
||||||
</integer-array>
|
</integer-array>
|
||||||
|
|
||||||
<string-array name="frameGenQueueTargetNames">
|
<string-array name="frameGenQueueTargetNames">
|
||||||
|
|||||||
@@ -331,13 +331,8 @@
|
|||||||
<string name="frame_gen_target_rate_60">60 FPS</string>
|
<string name="frame_gen_target_rate_60">60 FPS</string>
|
||||||
<string name="frame_gen_target_rate_90">90 FPS</string>
|
<string name="frame_gen_target_rate_90">90 FPS</string>
|
||||||
<string name="frame_gen_target_rate_120">120 FPS</string>
|
<string name="frame_gen_target_rate_120">120 FPS</string>
|
||||||
<string name="frame_gen_on">On</string>
|
<string name="frame_gen_target_rate_144">144 FPS</string>
|
||||||
<string name="frame_gen_off">Off</string>
|
<string name="frame_gen_target_rate_165">165 FPS</string>
|
||||||
<string name="frame_gen_fixed">Fixed</string>
|
|
||||||
<string name="frame_gen_flow_auto">Auto</string>
|
|
||||||
<string name="frame_gen_quick_description">Turn frame generation on or off and choose the frame multiplier.</string>
|
|
||||||
<string name="frame_gen_target_rate_quick_description">Generate frames up to a target frame rate. The multiplier adjusts on its own.</string>
|
|
||||||
<string name="frame_gen_flow_scale_quick_description">Resolution used to estimate motion between frames. Lower values save GPU time.</string>
|
|
||||||
<string name="frame_gen_queue_target">Frame queue target</string>
|
<string name="frame_gen_queue_target">Frame queue target</string>
|
||||||
<string name="frame_gen_queue_target_description">How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency.</string>
|
<string name="frame_gen_queue_target_description">How many finished frames may wait ahead of the display. Larger queues absorb GPU spikes at the cost of input latency.</string>
|
||||||
<string name="frame_gen_queue_target_0">Lowest latency (Unbuffered)</string>
|
<string name="frame_gen_queue_target_0">Lowest latency (Unbuffered)</string>
|
||||||
@@ -347,15 +342,19 @@
|
|||||||
<string name="frame_gen_flow_scale_auto_description">Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.</string>
|
<string name="frame_gen_flow_scale_auto_description">Estimate motion at the resolution the game actually renders instead of the upscaled output. Costs nothing in accuracy, since upscaling adds no motion detail.</string>
|
||||||
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
|
<string name="frame_gen_flow_scale">Motion estimation resolution</string>
|
||||||
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
|
<string name="frame_gen_flow_scale_description">Resolution of the optical flow pass, as a fraction of the output. Lowering it is the cheapest way to reclaim performance.</string>
|
||||||
|
<string name="frame_gen_fp16">Half precision shaders</string>
|
||||||
|
<string name="frame_gen_fp16_description">Use the 16-bit shader variant. Falls back automatically if the driver or the file lacks it.</string>
|
||||||
|
<string name="frame_gen_dump_flow">Dump generated frame</string>
|
||||||
|
<string name="frame_gen_dump_flow_description">Write the optical flow mip levels and the interpolated frame to the lossless/debug folder once, for troubleshooting</string>
|
||||||
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
<string name="frame_gen_unsupported">Frame generation unavailable</string>
|
||||||
<string name="frame_gen_unsupported_description">This GPU driver lacks the Vulkan memory model or half precision (float16) support that the Lossless Scaling shaders require.</string>
|
<string name="frame_gen_unsupported_description">This GPU driver does not support the Vulkan memory model, which the Lossless Scaling shaders require.</string>
|
||||||
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
|
<string name="lossless_scaling_setup_description">Optional. Provide your own Lossless.dll to enable frame generation later</string>
|
||||||
<string name="lossless_scaling_install">Install Lossless.dll</string>
|
<string name="lossless_scaling_install">Install Lossless.dll</string>
|
||||||
<string name="lossless_scaling_install_description">Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling</string>
|
<string name="lossless_scaling_install_description">Frame generation needs your own legal copy of Lossless.dll from Lossless Scaling</string>
|
||||||
<string name="lossless_scaling_replace_description">Select a different copy of Lossless.dll</string>
|
<string name="lossless_scaling_replace_description">Select a different copy of Lossless.dll</string>
|
||||||
<string name="frame_generation_support">Frame generation</string>
|
<string name="frame_generation_support">Frame generation</string>
|
||||||
<string name="frame_generation_supported">Supported</string>
|
<string name="frame_generation_supported">Supported</string>
|
||||||
<string name="frame_generation_unsupported">Unsupported (no Vulkan memory model or float16)</string>
|
<string name="frame_generation_unsupported">Unsupported (no Vulkan memory model)</string>
|
||||||
<string name="lossless_scaling">Lossless Scaling</string>
|
<string name="lossless_scaling">Lossless Scaling</string>
|
||||||
<string name="lossless_scaling_description">Provide your own copy of Lossless.dll to enable frame generation</string>
|
<string name="lossless_scaling_description">Provide your own copy of Lossless.dll to enable frame generation</string>
|
||||||
<string name="lossless_scaling_installed">Installed</string>
|
<string name="lossless_scaling_installed">Installed</string>
|
||||||
@@ -1784,51 +1783,6 @@ RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A
|
|||||||
FAILURE OF THE LIBRARY TO OPERATE WITH ANY OTHER SOFTWARE), EVEN IF
|
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
|
SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH
|
||||||
DAMAGES.
|
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>
|
||||||
<string name="license_sirit" translatable="false">Sirit</string>
|
<string name="license_sirit" translatable="false">Sirit</string>
|
||||||
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
|
<string name="license_sirit_description" translatable="false">A runtime SPIR-V assembler</string>
|
||||||
|
|||||||
@@ -15,10 +15,6 @@ add_library(audio_core STATIC
|
|||||||
adsp/apps/audio_renderer/command_list_processor.h
|
adsp/apps/audio_renderer/command_list_processor.h
|
||||||
adsp/apps/opus/opus_decoder.cpp
|
adsp/apps/opus/opus_decoder.cpp
|
||||||
adsp/apps/opus/opus_decoder.h
|
adsp/apps/opus/opus_decoder.h
|
||||||
adsp/apps/opus/opus_decode_object.cpp
|
|
||||||
adsp/apps/opus/opus_decode_object.h
|
|
||||||
adsp/apps/opus/opus_multistream_decode_object.cpp
|
|
||||||
adsp/apps/opus/opus_multistream_decode_object.h
|
|
||||||
adsp/apps/opus/shared_memory.h
|
adsp/apps/opus/shared_memory.h
|
||||||
audio_core.cpp
|
audio_core.cpp
|
||||||
audio_core.h
|
audio_core.h
|
||||||
@@ -226,8 +222,14 @@ else()
|
|||||||
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
$<$<COMPILE_LANGUAGE:C,CXX>:-Wno-sign-conversion>)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(audio_core PRIVATE ${OPUS_INCLUDE_DIRS})
|
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||||
target_link_libraries(audio_core PUBLIC common core Opus::opus)
|
add_dependencies(audio_core ffmpeg-build)
|
||||||
|
endif()
|
||||||
|
target_include_directories(audio_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||||
|
target_link_libraries(audio_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||||
|
target_link_options(audio_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||||
|
|
||||||
|
target_link_libraries(audio_core PUBLIC common core)
|
||||||
|
|
||||||
if (ENABLE_CUBEB)
|
if (ENABLE_CUBEB)
|
||||||
target_sources(audio_core PRIVATE
|
target_sources(audio_core PRIVATE
|
||||||
|
|||||||
@@ -1,110 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
namespace {
|
|
||||||
bool IsValidChannelCount(u32 channel_count) {
|
|
||||||
return channel_count == 1 || channel_count == 2;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
u32 OpusDecodeObject::GetWorkBufferSize(u32 channel_count) {
|
|
||||||
if (!IsValidChannelCount(channel_count)) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
return static_cast<u32>(sizeof(OpusDecodeObject)) + opus_decoder_get_size(channel_count);
|
|
||||||
}
|
|
||||||
|
|
||||||
OpusDecodeObject& OpusDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
|
||||||
auto* new_decoder = reinterpret_cast<OpusDecodeObject*>(buffer);
|
|
||||||
auto* comparison = reinterpret_cast<OpusDecodeObject*>(buffer2);
|
|
||||||
|
|
||||||
if (new_decoder->magic == DecodeObjectMagic) {
|
|
||||||
if (!new_decoder->initialized ||
|
|
||||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_decoder->initialized = false;
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
return *new_decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::InitializeDecoder(u32 sample_rate, u32 channel_count) {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unfortunately libopus does not expose the OpusDecoder struct publicly, so we can't include
|
|
||||||
// it in this class. Nintendo does not allocate memory, which is why we have a workbuffer
|
|
||||||
// provided.
|
|
||||||
// We could use _create and have libopus allocate it for us, but then we have to separately
|
|
||||||
// track which decoder is being used between this and multistream in order to call the correct
|
|
||||||
// destroy from the host side.
|
|
||||||
// This is a bit cringe, but is safe as these objects are only ever initialized inside the given
|
|
||||||
// workbuffer, and GetWorkBufferSize will guarantee there's enough space to follow.
|
|
||||||
decoder = (LibOpusDecoder*)(this + 1);
|
|
||||||
s32 ret = opus_decoder_init(decoder, sample_rate, channel_count);
|
|
||||||
if (ret == OPUS_OK) {
|
|
||||||
magic = DecodeObjectMagic;
|
|
||||||
initialized = true;
|
|
||||||
state_valid = true;
|
|
||||||
self = this;
|
|
||||||
final_range = 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::Shutdown() {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
magic = 0x0;
|
|
||||||
initialized = false;
|
|
||||||
state_valid = false;
|
|
||||||
self = nullptr;
|
|
||||||
final_range = 0;
|
|
||||||
decoder = nullptr;
|
|
||||||
}
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::ResetDecoder() {
|
|
||||||
return opus_decoder_ctl(decoder, OPUS_RESET_STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusDecodeObject::Decode(u32& out_sample_count, u64 output_data, u64 output_data_size,
|
|
||||||
u64 input_data, u64 input_data_size) {
|
|
||||||
ASSERT(initialized);
|
|
||||||
out_sample_count = 0;
|
|
||||||
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ret_code_or_samples = opus_decode(
|
|
||||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
|
||||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
|
||||||
|
|
||||||
if (ret_code_or_samples < OPUS_OK) {
|
|
||||||
return ret_code_or_samples;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_sample_count = ret_code_or_samples;
|
|
||||||
return opus_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -1,41 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <opus.h>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
using LibOpusDecoder = ::OpusDecoder;
|
|
||||||
static constexpr u32 DecodeObjectMagic = 0xDEADBEEF;
|
|
||||||
|
|
||||||
class OpusDecodeObject {
|
|
||||||
public:
|
|
||||||
static u32 GetWorkBufferSize(u32 channel_count);
|
|
||||||
static OpusDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
|
||||||
|
|
||||||
s32 InitializeDecoder(u32 sample_rate, u32 channel_count);
|
|
||||||
s32 Shutdown();
|
|
||||||
s32 ResetDecoder();
|
|
||||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
|
||||||
u64 input_data_size);
|
|
||||||
u32 GetFinalRange() const noexcept {
|
|
||||||
return final_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
u32 magic;
|
|
||||||
bool initialized;
|
|
||||||
bool state_valid;
|
|
||||||
OpusDecodeObject* self;
|
|
||||||
u32 final_range;
|
|
||||||
LibOpusDecoder* decoder;
|
|
||||||
};
|
|
||||||
static_assert(std::is_trivially_constructible_v<OpusDecodeObject>);
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -5,55 +5,388 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decode_object.h"
|
extern "C" {
|
||||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
#include <libswresample/swresample.h>
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavcodec/codec.h>
|
||||||
|
#include <libavcodec/packet.h>
|
||||||
|
#include <libavutil/channel_layout.h>
|
||||||
|
#include <libavutil/frame.h>
|
||||||
|
#include <libavutil/opt.h>
|
||||||
|
#include <libavutil/samplefmt.h>
|
||||||
|
}
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/common/common.h"
|
|
||||||
#include "common/logging.h"
|
#include "common/logging.h"
|
||||||
#include "common/thread.h"
|
#include "common/thread.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
#include "core/core_timing.h"
|
#include "core/core_timing.h"
|
||||||
|
#include "core/hle/service/audio/errors.h"
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
namespace AudioCore::ADSP::OpusDecoder {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
constexpr size_t OpusStreamCountMax = 255;
|
constexpr u32 OPUS_STREAM_COUNT_MAX = 255;
|
||||||
|
// https://git.ffmpeg.org/gitweb/ffmpeg.git/blob_plain/HEAD:/libavcodec/libopusdec.c
|
||||||
|
constexpr u32 OPUS_HEAD_SIZE = 19;
|
||||||
|
constexpr u32 OPUS_MAX_CHANNELS = 2;
|
||||||
|
|
||||||
bool IsValidChannelCount(u32 channel_count) {
|
bool IsValidChannelCount(u32 channel_count) {
|
||||||
return channel_count == 1 || channel_count == 2;
|
return channel_count >= 1 || channel_count <= OPUS_MAX_CHANNELS;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidMultiStreamChannelCount(u32 channel_count) {
|
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
||||||
return channel_count <= OpusStreamCountMax;
|
return total_stream_count > 0 && total_stream_count <= OPUS_STREAM_COUNT_MAX
|
||||||
|
&& s32(stereo_stream_count) >= 0 && stereo_stream_count <= total_stream_count;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool IsValidMultiStreamStreamCounts(s32 total_stream_count, s32 stereo_stream_count) {
|
class OpusGenericDecodeObject {
|
||||||
return IsValidMultiStreamChannelCount(total_stream_count) && total_stream_count > 0 &&
|
public:
|
||||||
stereo_stream_count >= 0 && stereo_stream_count <= total_stream_count;
|
static u32 GetWorkBufferSizeMultistream(u32 total_stream_count, u32 stereo_stream_count) {
|
||||||
}
|
if (IsValidStreamCounts(total_stream_count, stereo_stream_count))
|
||||||
|
return 48 + 2556 * (total_stream_count * stereo_stream_count);
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
static u32 GetWorkBufferSize(u32 channel_count) {
|
||||||
|
if (channel_count == 1 || channel_count == 2)
|
||||||
|
return 48 + 16 * channel_count;
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// idempotency of initialize is guaranteed
|
||||||
|
Result InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count, u32 stereo_stream_count, u8 const* mappings) {
|
||||||
|
// prefer libopus, ffmpeg docs say to use libopus **if** available
|
||||||
|
// However, native opus can also work with swrescale:
|
||||||
|
// it uses planarfloat, we can resample to s16
|
||||||
|
AVCodec const* codec = avcodec_find_decoder_by_name("libopus");
|
||||||
|
bool is_libopus = codec != nullptr;
|
||||||
|
if (!codec) {
|
||||||
|
LOG_WARNING(Audio_DSP, "using ffmpeg native opus decoder");
|
||||||
|
codec = avcodec_find_decoder(AV_CODEC_ID_OPUS);
|
||||||
|
}
|
||||||
|
if (codec) {
|
||||||
|
if ((avc = avc ? avc : avcodec_alloc_context3(codec))) {
|
||||||
|
if (is_libopus) {
|
||||||
|
const std::array<u8, 2> mapping_arr{0, 1};
|
||||||
|
mappings = mappings ? mappings : mapping_arr.data();
|
||||||
|
|
||||||
|
// freed by avcodec_context_free()
|
||||||
|
u8 *edata = reinterpret_cast<u8*>(av_mallocz(OPUS_HEAD_SIZE + 2 * OPUS_MAX_CHANNELS + AV_INPUT_BUFFER_PADDING_SIZE));
|
||||||
|
ASSERT(edata);
|
||||||
|
edata[9] = u8(channel_count); //channels
|
||||||
|
edata[10] = u8(0); //opus->pre_skip
|
||||||
|
edata[16] = u8(0); //gain_db
|
||||||
|
edata[18] = u8(0); //channel_map
|
||||||
|
edata[OPUS_HEAD_SIZE + 0] = u8(total_stream_count);
|
||||||
|
edata[OPUS_HEAD_SIZE + 1] = u8(stereo_stream_count);
|
||||||
|
if (channel_count >= 1) edata[OPUS_HEAD_SIZE + 2] = mappings[0];
|
||||||
|
if (channel_count >= 2) edata[OPUS_HEAD_SIZE + 3] = mappings[1];
|
||||||
|
avc->extradata = edata;
|
||||||
|
avc->extradata_size = OPUS_HEAD_SIZE + 2 * channel_count;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
// FFmpeg hardcodes sample rate
|
||||||
|
avc->sample_rate = sample_rate;
|
||||||
|
avc->request_sample_fmt = AV_SAMPLE_FMT_S16;
|
||||||
|
av_channel_layout_default(&avc->ch_layout, channel_count);
|
||||||
|
if (avcodec_open2(avc, codec, nullptr) >= 0) {
|
||||||
|
avpkt = av_packet_alloc();
|
||||||
|
frame = av_frame_alloc();
|
||||||
|
return ResultSuccess;
|
||||||
|
} else {
|
||||||
|
avcodec_free_context(&avc);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInternalError;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Shutdown() {
|
||||||
|
avcodec_free_context(&avc);
|
||||||
|
av_frame_free(&frame);
|
||||||
|
av_packet_free(&avpkt);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result ResetDecoder() {
|
||||||
|
if (avc) {
|
||||||
|
if (avcodec_is_open(avc)) avcodec_flush_buffers(avc);
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data, u64 input_data_size) {
|
||||||
|
out_sample_count = 0;
|
||||||
|
if (avc) {
|
||||||
|
int rem_output_bytes = int(output_data_size);
|
||||||
|
while (rem_output_bytes > 0) {
|
||||||
|
int r = avcodec_receive_frame(avc, frame);
|
||||||
|
if (r == AVERROR(EAGAIN)) {
|
||||||
|
av_packet_unref(avpkt);
|
||||||
|
av_new_packet(avpkt, int(input_data_size));
|
||||||
|
std::memcpy(avpkt->data, reinterpret_cast<const u8*>(input_data), input_data_size);
|
||||||
|
r = avcodec_send_packet(avc, avpkt);
|
||||||
|
ASSERT(r >= 0);
|
||||||
|
} else if (r == AVERROR_EOF) {
|
||||||
|
break;
|
||||||
|
} else if (r >= 0) {
|
||||||
|
auto const bsize = av_samples_get_buffer_size(nullptr, frame->ch_layout.nb_channels, frame->nb_samples, AV_SAMPLE_FMT_S16, 1);
|
||||||
|
if (frame->format == AV_SAMPLE_FMT_S16) {
|
||||||
|
std::memcpy(reinterpret_cast<s16*>(output_data) + (int(output_data_size) - rem_output_bytes), frame->data[0], size_t(bsize));
|
||||||
|
} else {
|
||||||
|
SwrContext *swr = nullptr;
|
||||||
|
if (swr_alloc_set_opts2(
|
||||||
|
&swr,
|
||||||
|
&avc->ch_layout,
|
||||||
|
AV_SAMPLE_FMT_S16,
|
||||||
|
48000,
|
||||||
|
&avc->ch_layout,
|
||||||
|
(enum AVSampleFormat)frame->format,
|
||||||
|
48000,
|
||||||
|
0,
|
||||||
|
nullptr
|
||||||
|
) >= 0) {
|
||||||
|
if (swr_init(swr) >= 0) {
|
||||||
|
AVFrame *s16_frame = av_frame_alloc();
|
||||||
|
s16_frame->format = AV_SAMPLE_FMT_S16;
|
||||||
|
s16_frame->sample_rate = frame->sample_rate;
|
||||||
|
av_channel_layout_copy(&s16_frame->ch_layout, &frame->ch_layout);
|
||||||
|
s16_frame->nb_samples = frame->nb_samples;
|
||||||
|
av_frame_get_buffer(s16_frame, 0);
|
||||||
|
swr_convert(swr, s16_frame->data, s16_frame->nb_samples, (const uint8_t **)frame->data, frame->nb_samples);
|
||||||
|
std::memcpy(reinterpret_cast<s16*>(output_data) + (int(output_data_size) - rem_output_bytes), s16_frame->data[0], size_t(bsize));
|
||||||
|
swr_free(&swr);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out_sample_count += frame->nb_samples;
|
||||||
|
rem_output_bytes -= bsize;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "{}", r);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ASSERT(rem_output_bytes == 0 && "remaining bytes!");
|
||||||
|
return ResultSuccess;
|
||||||
|
}
|
||||||
|
return Service::Audio::ResultLibOpusInvalidState;
|
||||||
|
}
|
||||||
|
|
||||||
|
AVCodecContext* avc = nullptr;
|
||||||
|
AVPacket* avpkt = nullptr;
|
||||||
|
AVFrame* frame = nullptr;
|
||||||
|
};
|
||||||
} // namespace
|
} // namespace
|
||||||
|
|
||||||
OpusDecoder::OpusDecoder(Core::System& system_) : system{system_} {
|
OpusDecoder::OpusDecoder(Core::System& system) {
|
||||||
init_thread = std::jthread([this](std::stop_token stop_token) { Init(stop_token); });
|
dsp_thread = std::jthread([this, &system](std::stop_token stop_token) {
|
||||||
|
Common::SetCurrentThreadName("DSP_OpusDecoder");
|
||||||
|
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
||||||
|
LOG_ERROR(Service_Audio, "DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::StartOK);
|
||||||
|
|
||||||
|
// Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
||||||
|
::Common::unordered_map<u64, OpusGenericDecodeObject> decode_objects;
|
||||||
|
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 = s32(shared_memory->host_send_data[0]);
|
||||||
|
|
||||||
|
ASSERT(IsValidChannelCount(channel_count));
|
||||||
|
|
||||||
|
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSize(channel_count);
|
||||||
|
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InitializeDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||||
|
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||||
|
|
||||||
|
ASSERT(sample_rate >= 0);
|
||||||
|
ASSERT(IsValidChannelCount(channel_count));
|
||||||
|
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSize(channel_count));
|
||||||
|
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
it->second.Shutdown();
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw;
|
||||||
|
} else {
|
||||||
|
OpusGenericDecodeObject obj{};
|
||||||
|
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, 1, channel_count, channel_count == 2 ? 1 : 0, nullptr).raw;
|
||||||
|
decode_objects.insert_or_assign(buffer, obj);
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShutdownDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DecodeInterleaved: {
|
||||||
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto input_data = shared_memory->host_send_data[1];
|
||||||
|
auto input_data_size = shared_memory->host_send_data[2];
|
||||||
|
auto output_data = shared_memory->host_send_data[3];
|
||||||
|
auto output_data_size = shared_memory->host_send_data[4];
|
||||||
|
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||||
|
auto reset_requested = shared_memory->host_send_data[6];
|
||||||
|
|
||||||
|
u32 decoded_samples{0};
|
||||||
|
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
auto res = ResultSuccess;
|
||||||
|
if (reset_requested)
|
||||||
|
res = it->second.ResetDecoder();
|
||||||
|
if (res == ResultSuccess)
|
||||||
|
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size);
|
||||||
|
|
||||||
|
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
shared_memory->dsp_return_data[0] = res.raw;
|
||||||
|
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||||
|
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::DecodeInterleavedOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case MapMemory: {
|
||||||
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
Send(Direction::Host, Message::MapMemoryOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case UnmapMemory: {
|
||||||
|
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
||||||
|
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
Send(Direction::Host, Message::UnmapMemoryOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case GetWorkBufferSizeForMultiStream: {
|
||||||
|
auto total_stream_count = s32(shared_memory->host_send_data[0]);
|
||||||
|
auto stereo_stream_count = s32(shared_memory->host_send_data[1]);
|
||||||
|
|
||||||
|
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
|
||||||
|
|
||||||
|
shared_memory->dsp_return_data[0] = OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count);
|
||||||
|
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case InitializeMultiStreamDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
auto sample_rate = s32(shared_memory->host_send_data[2]);
|
||||||
|
auto channel_count = s32(shared_memory->host_send_data[3]);
|
||||||
|
auto total_stream_count = s32(shared_memory->host_send_data[4]);
|
||||||
|
auto stereo_stream_count = s32(shared_memory->host_send_data[5]);
|
||||||
|
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
||||||
|
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
||||||
|
// more than 40 channels, when 255 are possible.
|
||||||
|
// It also means the mapping values are undefined, though likely always 0,
|
||||||
|
// and the mappings given by the game are ignored. The mappings are copied to this
|
||||||
|
// dedicated buffer host side, so let's do as intended.
|
||||||
|
auto mappings = shared_memory->channel_mapping.data();
|
||||||
|
|
||||||
|
ASSERT(IsValidStreamCounts(total_stream_count, stereo_stream_count));
|
||||||
|
ASSERT(sample_rate >= 0);
|
||||||
|
ASSERT(buffer_size >= OpusGenericDecodeObject::GetWorkBufferSizeMultistream(total_stream_count, stereo_stream_count));
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
it->second.Shutdown();
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||||
|
} else {
|
||||||
|
OpusGenericDecodeObject obj{};
|
||||||
|
shared_memory->dsp_return_data[0] = obj.InitializeDecoder(sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings).raw;
|
||||||
|
decode_objects.insert_or_assign(buffer, obj);
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case ShutdownMultiStreamDecodeObject: {
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
//[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
shared_memory->dsp_return_data[0] = it->second.Shutdown().raw;
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case DecodeInterleavedForMultiStream: {
|
||||||
|
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
|
||||||
|
auto buffer = shared_memory->host_send_data[0];
|
||||||
|
auto input_data = shared_memory->host_send_data[1];
|
||||||
|
auto input_data_size = shared_memory->host_send_data[2];
|
||||||
|
auto output_data = shared_memory->host_send_data[3];
|
||||||
|
auto output_data_size = shared_memory->host_send_data[4];
|
||||||
|
//auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
||||||
|
auto reset_requested = shared_memory->host_send_data[6];
|
||||||
|
|
||||||
|
u32 decoded_samples{0};
|
||||||
|
|
||||||
|
if (auto const it = decode_objects.find(buffer); it != decode_objects.end()) {
|
||||||
|
auto res = ResultSuccess;
|
||||||
|
if (reset_requested)
|
||||||
|
res = it->second.ResetDecoder();
|
||||||
|
if (res == ResultSuccess)
|
||||||
|
res = it->second.Decode(decoded_samples, output_data, output_data_size, input_data, input_data_size);
|
||||||
|
|
||||||
|
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
||||||
|
shared_memory->dsp_return_data[0] = res.raw;
|
||||||
|
shared_memory->dsp_return_data[1] = decoded_samples;
|
||||||
|
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
||||||
|
} else {
|
||||||
|
LOG_ERROR(Audio_DSP, "operating unregistered buffer {}", buffer);
|
||||||
|
shared_memory->dsp_return_data[0] = Service::Audio::ResultLibOpusInvalidState.raw;
|
||||||
|
}
|
||||||
|
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
LOG_ERROR(Audio_DSP, "Invalid OpusDecoder command {}", msg);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (auto e : decode_objects)
|
||||||
|
e.second.Shutdown();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
OpusDecoder::~OpusDecoder() {
|
OpusDecoder::~OpusDecoder() {
|
||||||
if (!running) {
|
if (dsp_thread.joinable()) {
|
||||||
init_thread.request_stop();
|
// Shutdown the thread
|
||||||
return;
|
auto const stop_token = dsp_thread.get_stop_token();
|
||||||
|
Send(Direction::DSP, Message::Shutdown);
|
||||||
|
auto msg = Receive(Direction::Host, stop_token);
|
||||||
|
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}", Message::ShutdownOK, msg);
|
||||||
|
dsp_thread.request_stop();
|
||||||
|
dsp_thread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Shutdown the thread
|
|
||||||
Send(Direction::DSP, Message::Shutdown);
|
|
||||||
auto msg = Receive(Direction::Host);
|
|
||||||
ASSERT_MSG(msg == Message::ShutdownOK, "Expected Opus shutdown code {}, got {}",
|
|
||||||
Message::ShutdownOK, msg);
|
|
||||||
main_thread.request_stop();
|
|
||||||
main_thread.join();
|
|
||||||
running = false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Send(Direction dir, u32 message) {
|
void OpusDecoder::Send(Direction dir, u32 message) {
|
||||||
@@ -64,206 +397,4 @@ u32 OpusDecoder::Receive(Direction dir, std::stop_token stop_token) {
|
|||||||
return mailbox.Receive(dir, stop_token);
|
return mailbox.Receive(dir, stop_token);
|
||||||
}
|
}
|
||||||
|
|
||||||
void OpusDecoder::Init(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Init");
|
|
||||||
|
|
||||||
if (Receive(Direction::DSP, stop_token) != Message::Start) {
|
|
||||||
LOG_ERROR(Service_Audio,
|
|
||||||
"DSP OpusDecoder failed to receive Start message. Opus initialization failed.");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
main_thread = std::jthread([this](std::stop_token st) { Main(st); });
|
|
||||||
running = true;
|
|
||||||
Send(Direction::Host, Message::StartOK);
|
|
||||||
}
|
|
||||||
|
|
||||||
void OpusDecoder::Main(std::stop_token stop_token) {
|
|
||||||
Common::SetCurrentThreadName("DSP_OpusDecoder_Main");
|
|
||||||
|
|
||||||
while (!stop_token.stop_requested()) {
|
|
||||||
auto msg = Receive(Direction::DSP, stop_token);
|
|
||||||
switch (msg) {
|
|
||||||
case Shutdown:
|
|
||||||
Send(Direction::Host, Message::ShutdownOK);
|
|
||||||
return;
|
|
||||||
|
|
||||||
case GetWorkBufferSize: {
|
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
|
||||||
|
|
||||||
ASSERT(IsValidChannelCount(channel_count));
|
|
||||||
|
|
||||||
shared_memory->dsp_return_data[0] = OpusDecodeObject::GetWorkBufferSize(channel_count);
|
|
||||||
Send(Direction::Host, Message::GetWorkBufferSizeOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case InitializeDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
|
||||||
|
|
||||||
ASSERT(sample_rate >= 0);
|
|
||||||
ASSERT(IsValidChannelCount(channel_count));
|
|
||||||
ASSERT(buffer_size >= OpusDecodeObject::GetWorkBufferSize(channel_count));
|
|
||||||
|
|
||||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] =
|
|
||||||
decoder_object.InitializeDecoder(sample_rate, channel_count);
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::InitializeDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ShutdownDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
|
|
||||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::ShutdownDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DecodeInterleaved: {
|
|
||||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto input_data = shared_memory->host_send_data[1];
|
|
||||||
auto input_data_size = shared_memory->host_send_data[2];
|
|
||||||
auto output_data = shared_memory->host_send_data[3];
|
|
||||||
auto output_data_size = shared_memory->host_send_data[4];
|
|
||||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
|
||||||
auto reset_requested = shared_memory->host_send_data[6];
|
|
||||||
|
|
||||||
u32 decoded_samples{0};
|
|
||||||
|
|
||||||
auto& decoder_object = OpusDecodeObject::Initialize(buffer, buffer);
|
|
||||||
s32 error_code{OPUS_OK};
|
|
||||||
if (reset_requested) {
|
|
||||||
error_code = decoder_object.ResetDecoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
|
||||||
input_data, input_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
|
||||||
error_code = OPUS_INVALID_PACKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
shared_memory->dsp_return_data[0] = error_code;
|
|
||||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
|
||||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::DecodeInterleavedOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case MapMemory: {
|
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
Send(Direction::Host, Message::MapMemoryOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case UnmapMemory: {
|
|
||||||
[[maybe_unused]] auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
Send(Direction::Host, Message::UnmapMemoryOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case GetWorkBufferSizeForMultiStream: {
|
|
||||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[0]);
|
|
||||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[1]);
|
|
||||||
|
|
||||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
|
||||||
|
|
||||||
shared_memory->dsp_return_data[0] = OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
|
||||||
total_stream_count, stereo_stream_count);
|
|
||||||
Send(Direction::Host, Message::GetWorkBufferSizeForMultiStreamOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case InitializeMultiStreamDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
auto sample_rate = static_cast<s32>(shared_memory->host_send_data[2]);
|
|
||||||
auto channel_count = static_cast<s32>(shared_memory->host_send_data[3]);
|
|
||||||
auto total_stream_count = static_cast<s32>(shared_memory->host_send_data[4]);
|
|
||||||
auto stereo_stream_count = static_cast<s32>(shared_memory->host_send_data[5]);
|
|
||||||
// Nintendo seem to have a bug here, they try to use &host_send_data[6] for the channel
|
|
||||||
// mappings, but [6] is never set, and there is not enough room in the argument data for
|
|
||||||
// more than 40 channels, when 255 are possible.
|
|
||||||
// It also means the mapping values are undefined, though likely always 0,
|
|
||||||
// and the mappings given by the game are ignored. The mappings are copied to this
|
|
||||||
// dedicated buffer host side, so let's do as intended.
|
|
||||||
auto mappings = shared_memory->channel_mapping.data();
|
|
||||||
|
|
||||||
ASSERT(IsValidMultiStreamStreamCounts(total_stream_count, stereo_stream_count));
|
|
||||||
ASSERT(sample_rate >= 0);
|
|
||||||
ASSERT(buffer_size >= OpusMultiStreamDecodeObject::GetWorkBufferSize(
|
|
||||||
total_stream_count, stereo_stream_count));
|
|
||||||
|
|
||||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] = decoder_object.InitializeDecoder(
|
|
||||||
sample_rate, total_stream_count, channel_count, stereo_stream_count, mappings);
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::InitializeMultiStreamDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case ShutdownMultiStreamDecodeObject: {
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
[[maybe_unused]] auto buffer_size = shared_memory->host_send_data[1];
|
|
||||||
|
|
||||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
|
||||||
shared_memory->dsp_return_data[0] = decoder_object.Shutdown();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::ShutdownMultiStreamDecodeObjectOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
case DecodeInterleavedForMultiStream: {
|
|
||||||
auto start_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
|
|
||||||
auto buffer = shared_memory->host_send_data[0];
|
|
||||||
auto input_data = shared_memory->host_send_data[1];
|
|
||||||
auto input_data_size = shared_memory->host_send_data[2];
|
|
||||||
auto output_data = shared_memory->host_send_data[3];
|
|
||||||
auto output_data_size = shared_memory->host_send_data[4];
|
|
||||||
auto final_range = static_cast<u32>(shared_memory->host_send_data[5]);
|
|
||||||
auto reset_requested = shared_memory->host_send_data[6];
|
|
||||||
|
|
||||||
u32 decoded_samples{0};
|
|
||||||
|
|
||||||
auto& decoder_object = OpusMultiStreamDecodeObject::Initialize(buffer, buffer);
|
|
||||||
s32 error_code{OPUS_OK};
|
|
||||||
if (reset_requested) {
|
|
||||||
error_code = decoder_object.ResetDecoder();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
error_code = decoder_object.Decode(decoded_samples, output_data, output_data_size,
|
|
||||||
input_data, input_data_size);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (error_code == OPUS_OK) {
|
|
||||||
if (final_range && decoder_object.GetFinalRange() != final_range) {
|
|
||||||
error_code = OPUS_INVALID_PACKET;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
auto end_time = system.CoreTiming().GetGlobalTimeUs();
|
|
||||||
shared_memory->dsp_return_data[0] = error_code;
|
|
||||||
shared_memory->dsp_return_data[1] = decoded_samples;
|
|
||||||
shared_memory->dsp_return_data[2] = (end_time - start_time).count();
|
|
||||||
|
|
||||||
Send(Direction::Host, Message::DecodeInterleavedForMultiStreamOK);
|
|
||||||
} break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
LOG_ERROR(Service_Audio, "Invalid OpusDecoder command {}", msg);
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -6,12 +6,13 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <memory>
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
|
|
||||||
|
#include "common/container/unordered_map.h"
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/adsp/mailbox.h"
|
#include "audio_core/adsp/mailbox.h"
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
namespace Core {
|
namespace Core {
|
||||||
class System;
|
class System;
|
||||||
@@ -48,16 +49,14 @@ enum Message : u32 {
|
|||||||
DecodeInterleavedForMultiStreamOK = 50,
|
DecodeInterleavedForMultiStreamOK = 50,
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/// @brief The AudioRenderer application running on the ADSP.
|
||||||
* The AudioRenderer application running on the ADSP.
|
|
||||||
*/
|
|
||||||
class OpusDecoder {
|
class OpusDecoder {
|
||||||
public:
|
public:
|
||||||
explicit OpusDecoder(Core::System& system);
|
explicit OpusDecoder(Core::System& system);
|
||||||
~OpusDecoder();
|
~OpusDecoder();
|
||||||
|
|
||||||
bool IsRunning() const noexcept {
|
bool IsRunning() const noexcept {
|
||||||
return running;
|
return dsp_thread.joinable();
|
||||||
}
|
}
|
||||||
|
|
||||||
void Send(Direction dir, u32 message);
|
void Send(Direction dir, u32 message);
|
||||||
@@ -68,28 +67,12 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
/**
|
|
||||||
* Initializing thread, launched at audio_core boot to avoid blocking the main emu boot thread.
|
|
||||||
*/
|
|
||||||
void Init(std::stop_token stop_token);
|
|
||||||
/**
|
|
||||||
* Main OpusDecoder thread, responsible for processing the incoming Opus packets.
|
|
||||||
*/
|
|
||||||
void Main(std::stop_token stop_token);
|
|
||||||
|
|
||||||
/// Core system
|
|
||||||
Core::System& system;
|
|
||||||
/// Mailbox to communicate messages with the host, drives the main thread
|
/// Mailbox to communicate messages with the host, drives the main thread
|
||||||
Mailbox mailbox;
|
Mailbox mailbox;
|
||||||
/// Init thread
|
|
||||||
std::jthread init_thread{};
|
|
||||||
/// Main thread
|
|
||||||
std::jthread main_thread{};
|
|
||||||
/// The current state
|
|
||||||
bool running{};
|
|
||||||
/// Structure shared with the host, input data set by the host before sending a mailbox message,
|
/// 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.
|
/// and the responses are written back by the OpusDecoder.
|
||||||
SharedMemory* shared_memory{};
|
SharedMemory* shared_memory{};
|
||||||
|
std::jthread dsp_thread{};
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
} // namespace AudioCore::ADSP::OpusDecoder
|
||||||
|
|||||||
@@ -1,113 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_multistream_decode_object.h"
|
|
||||||
#include "common/assert.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
|
|
||||||
namespace {
|
|
||||||
constexpr u32 OpusStreamCountMax = 255;
|
|
||||||
|
|
||||||
bool IsValidStreamCounts(u32 total_stream_count, u32 stereo_stream_count) {
|
|
||||||
return total_stream_count > 0 && total_stream_count <= OpusStreamCountMax &&
|
|
||||||
static_cast<s32>(stereo_stream_count) >= 0 &&
|
|
||||||
stereo_stream_count <= total_stream_count;
|
|
||||||
}
|
|
||||||
} // namespace
|
|
||||||
|
|
||||||
u32 OpusMultiStreamDecodeObject::GetWorkBufferSize(u32 total_stream_count,
|
|
||||||
u32 stereo_stream_count) {
|
|
||||||
if (IsValidStreamCounts(total_stream_count, stereo_stream_count)) {
|
|
||||||
return static_cast<u32>(sizeof(OpusMultiStreamDecodeObject)) +
|
|
||||||
opus_multistream_decoder_get_size(total_stream_count, stereo_stream_count);
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
OpusMultiStreamDecodeObject& OpusMultiStreamDecodeObject::Initialize(u64 buffer, u64 buffer2) {
|
|
||||||
auto* new_decoder = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer);
|
|
||||||
auto* comparison = reinterpret_cast<OpusMultiStreamDecodeObject*>(buffer2);
|
|
||||||
|
|
||||||
if (new_decoder->magic == DecodeMultiStreamObjectMagic) {
|
|
||||||
if (!new_decoder->initialized ||
|
|
||||||
(new_decoder->initialized && new_decoder->self == comparison)) {
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
new_decoder->initialized = false;
|
|
||||||
new_decoder->state_valid = true;
|
|
||||||
}
|
|
||||||
return *new_decoder;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::InitializeDecoder(u32 sample_rate, u32 total_stream_count,
|
|
||||||
u32 channel_count, u32 stereo_stream_count,
|
|
||||||
u8* mappings) {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
// See OpusDecodeObject::InitializeDecoder for an explanation of this
|
|
||||||
decoder = (LibOpusMSDecoder*)(this + 1);
|
|
||||||
s32 ret = opus_multistream_decoder_init(decoder, sample_rate, channel_count, total_stream_count,
|
|
||||||
stereo_stream_count, mappings);
|
|
||||||
if (ret == OPUS_OK) {
|
|
||||||
magic = DecodeMultiStreamObjectMagic;
|
|
||||||
initialized = true;
|
|
||||||
state_valid = true;
|
|
||||||
self = this;
|
|
||||||
final_range = 0;
|
|
||||||
}
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::Shutdown() {
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (initialized) {
|
|
||||||
magic = 0x0;
|
|
||||||
initialized = false;
|
|
||||||
state_valid = false;
|
|
||||||
self = nullptr;
|
|
||||||
final_range = 0;
|
|
||||||
decoder = nullptr;
|
|
||||||
}
|
|
||||||
return OPUS_OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::ResetDecoder() {
|
|
||||||
return opus_multistream_decoder_ctl(decoder, OPUS_RESET_STATE);
|
|
||||||
}
|
|
||||||
|
|
||||||
s32 OpusMultiStreamDecodeObject::Decode(u32& out_sample_count, u64 output_data,
|
|
||||||
u64 output_data_size, u64 input_data, u64 input_data_size) {
|
|
||||||
ASSERT(initialized);
|
|
||||||
out_sample_count = 0;
|
|
||||||
|
|
||||||
if (!state_valid) {
|
|
||||||
return OPUS_INVALID_STATE;
|
|
||||||
}
|
|
||||||
|
|
||||||
auto ret_code_or_samples = opus_multistream_decode(
|
|
||||||
decoder, reinterpret_cast<const u8*>(input_data), static_cast<opus_int32>(input_data_size),
|
|
||||||
reinterpret_cast<opus_int16*>(output_data), static_cast<opus_int32>(output_data_size), 0);
|
|
||||||
|
|
||||||
if (ret_code_or_samples < OPUS_OK) {
|
|
||||||
return ret_code_or_samples;
|
|
||||||
}
|
|
||||||
|
|
||||||
out_sample_count = ret_code_or_samples;
|
|
||||||
return opus_multistream_decoder_ctl(decoder, OPUS_GET_FINAL_RANGE_REQUEST, &final_range);
|
|
||||||
}
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -1,42 +0,0 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
|
||||||
|
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <opus_multistream.h>
|
|
||||||
|
|
||||||
#include "common/common_types.h"
|
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
|
||||||
using LibOpusMSDecoder = ::OpusMSDecoder;
|
|
||||||
static constexpr u32 DecodeMultiStreamObjectMagic = 0xDEADBEEF;
|
|
||||||
|
|
||||||
class OpusMultiStreamDecodeObject {
|
|
||||||
public:
|
|
||||||
static u32 GetWorkBufferSize(u32 total_stream_count, u32 stereo_stream_count);
|
|
||||||
static OpusMultiStreamDecodeObject& Initialize(u64 buffer, u64 buffer2);
|
|
||||||
|
|
||||||
s32 InitializeDecoder(u32 sample_rate, u32 total_stream_count, u32 channel_count,
|
|
||||||
u32 stereo_stream_count, u8* mappings);
|
|
||||||
s32 Shutdown();
|
|
||||||
s32 ResetDecoder();
|
|
||||||
s32 Decode(u32& out_sample_count, u64 output_data, u64 output_data_size, u64 input_data,
|
|
||||||
u64 input_data_size);
|
|
||||||
u32 GetFinalRange() const noexcept {
|
|
||||||
return final_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
private:
|
|
||||||
u32 magic;
|
|
||||||
bool initialized;
|
|
||||||
bool state_valid;
|
|
||||||
OpusMultiStreamDecodeObject* self;
|
|
||||||
u32 final_range;
|
|
||||||
LibOpusMSDecoder* decoder;
|
|
||||||
};
|
|
||||||
static_assert(std::is_trivially_constructible_v<OpusMultiStreamDecodeObject>);
|
|
||||||
|
|
||||||
} // namespace AudioCore::ADSP::OpusDecoder
|
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "common/common_types.h"
|
||||||
|
|
||||||
|
namespace AudioCore::ADSP {
|
||||||
|
|
||||||
|
static constexpr u32 DECODE_OBJECT_MAGIC = 0xDEADBEEF;
|
||||||
|
struct LibOpusDecoder {
|
||||||
|
u32 magic;
|
||||||
|
bool initialized;
|
||||||
|
bool state_valid;
|
||||||
|
LibOpusDecoder* self;
|
||||||
|
u32 final_range;
|
||||||
|
void* decoder;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LibOpusDecoder) == 32);
|
||||||
|
|
||||||
|
static constexpr u32 DECODE_MULTISTREAM_OBJECT_MAGIC = 0xDEADBEEF;
|
||||||
|
struct LibOpusMultistreamDecoder {
|
||||||
|
u32 magic;
|
||||||
|
bool initialized;
|
||||||
|
bool state_valid;
|
||||||
|
LibOpusMultistreamDecoder* self;
|
||||||
|
u32 final_range;
|
||||||
|
void* decoder;
|
||||||
|
};
|
||||||
|
static_assert(sizeof(LibOpusMultistreamDecoder) == 32);
|
||||||
|
|
||||||
|
}
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
|
|
||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include "common/common_funcs.h"
|
|
||||||
#include "common/common_types.h"
|
#include "common/common_types.h"
|
||||||
|
|
||||||
namespace AudioCore::ADSP::OpusDecoder {
|
namespace AudioCore::ADSP::OpusDecoder {
|
||||||
|
|||||||
@@ -10,39 +10,18 @@
|
|||||||
#include "audio_core/audio_core.h"
|
#include "audio_core/audio_core.h"
|
||||||
#include "audio_core/opus/hardware_opus.h"
|
#include "audio_core/opus/hardware_opus.h"
|
||||||
#include "core/core.h"
|
#include "core/core.h"
|
||||||
|
#include "core/hle/result.h"
|
||||||
|
|
||||||
namespace AudioCore::OpusDecoder {
|
namespace AudioCore::OpusDecoder {
|
||||||
|
|
||||||
namespace {
|
namespace {
|
||||||
using namespace Service::Audio;
|
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
|
} // namespace
|
||||||
|
|
||||||
HardwareOpus::HardwareOpus(Core::System& system_)
|
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);
|
opus_decoder.SetSharedMemory(shared_memory);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -112,7 +91,7 @@ Result HardwareOpus::InitializeDecodeObject(u32 sample_rate, u32 channel_count,
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 channel_count,
|
||||||
@@ -140,7 +119,7 @@ Result HardwareOpus::InitializeMultiStreamDecodeObject(u32 sample_rate, u32 chan
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
||||||
@@ -154,7 +133,7 @@ Result HardwareOpus::ShutdownDecodeObject(void* buffer, u64 buffer_size) {
|
|||||||
"Expected Opus shutdown code {}, got {}",
|
"Expected Opus shutdown code {}, got {}",
|
||||||
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
ADSP::OpusDecoder::Message::ShutdownDecodeObjectOK, msg);
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_size) {
|
||||||
@@ -169,7 +148,7 @@ Result HardwareOpus::ShutdownMultiStreamDecodeObject(void* buffer, u64 buffer_si
|
|||||||
"Expected Opus shutdown code {}, got {}",
|
"Expected Opus shutdown code {}, got {}",
|
||||||
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
ADSP::OpusDecoder::Message::ShutdownMultiStreamDecodeObjectOK, msg);
|
||||||
|
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(shared_memory.dsp_return_data[0]));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
||||||
@@ -193,12 +172,12 @@ Result HardwareOpus::DecodeInterleaved(u32& out_sample_count, void* output_data,
|
|||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
auto error_code = s32(shared_memory.dsp_return_data[0]);
|
||||||
if (error_code == OPUS_OK) {
|
if (error_code == ResultSuccess.raw) {
|
||||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
out_sample_count = u32(shared_memory.dsp_return_data[1]);
|
||||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||||
}
|
}
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
R_RETURN(Result(u32(error_code)));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void* output_data,
|
||||||
@@ -207,29 +186,27 @@ Result HardwareOpus::DecodeInterleavedForMultiStream(u32& out_sample_count, void
|
|||||||
void* buffer, u64& out_time_taken,
|
void* buffer, u64& out_time_taken,
|
||||||
bool reset) {
|
bool reset) {
|
||||||
std::scoped_lock l{mutex};
|
std::scoped_lock l{mutex};
|
||||||
shared_memory.host_send_data[0] = (u64)buffer;
|
shared_memory.host_send_data[0] = u64(buffer);
|
||||||
shared_memory.host_send_data[1] = (u64)input_data;
|
shared_memory.host_send_data[1] = u64(input_data);
|
||||||
shared_memory.host_send_data[2] = input_data_size;
|
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[4] = output_data_size;
|
||||||
shared_memory.host_send_data[5] = 0;
|
shared_memory.host_send_data[5] = 0;
|
||||||
shared_memory.host_send_data[6] = reset;
|
shared_memory.host_send_data[6] = reset;
|
||||||
|
|
||||||
opus_decoder.Send(ADSP::Direction::DSP,
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
||||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStream);
|
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
if (msg != ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
||||||
ADSP::OpusDecoder::Message::DecodeInterleavedForMultiStreamOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
|
|
||||||
auto error_code{static_cast<s32>(shared_memory.dsp_return_data[0])};
|
auto const error_code = shared_memory.dsp_return_data[0];
|
||||||
if (error_code == OPUS_OK) {
|
if (error_code == ResultSuccess.raw) {
|
||||||
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
out_sample_count = static_cast<u32>(shared_memory.dsp_return_data[1]);
|
||||||
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
out_time_taken = 1000 * shared_memory.dsp_return_data[2];
|
||||||
}
|
}
|
||||||
R_RETURN(ResultCodeFromLibOpusErrorCode(error_code));
|
R_RETURN(Result(u32(shared_memory.dsp_return_data[0])));
|
||||||
}
|
}
|
||||||
|
|
||||||
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
||||||
@@ -240,8 +217,7 @@ Result HardwareOpus::MapMemory(void* buffer, u64 buffer_size) {
|
|||||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::MapMemory);
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
if (msg != ADSP::OpusDecoder::Message::MapMemoryOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
||||||
ADSP::OpusDecoder::Message::MapMemoryOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@@ -255,8 +231,7 @@ Result HardwareOpus::UnmapMemory(void* buffer, u64 buffer_size) {
|
|||||||
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
opus_decoder.Send(ADSP::Direction::DSP, ADSP::OpusDecoder::Message::UnmapMemory);
|
||||||
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
auto msg = opus_decoder.Receive(ADSP::Direction::Host);
|
||||||
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
if (msg != ADSP::OpusDecoder::Message::UnmapMemoryOK) {
|
||||||
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}",
|
LOG_ERROR(Service_Audio, "OpusDecoder returned invalid message. Expected {} got {}", ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
||||||
ADSP::OpusDecoder::Message::UnmapMemoryOK, msg);
|
|
||||||
R_THROW(ResultInvalidOpusDSPReturnCode);
|
R_THROW(ResultInvalidOpusDSPReturnCode);
|
||||||
}
|
}
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
|
|||||||
@@ -8,8 +8,6 @@
|
|||||||
|
|
||||||
#include <array>
|
#include <array>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <opus.h>
|
|
||||||
|
|
||||||
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
#include "audio_core/adsp/apps/opus/opus_decoder.h"
|
||||||
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
#include "audio_core/adsp/apps/opus/shared_memory.h"
|
||||||
#include "audio_core/adsp/mailbox.h"
|
#include "audio_core/adsp/mailbox.h"
|
||||||
|
|||||||
@@ -261,7 +261,6 @@ SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channe
|
|||||||
system_channels = system_channels_;
|
system_channels = system_channels_;
|
||||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>(
|
||||||
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
ctx, device_channels, system_channels, output_device, input_device, name, type, system));
|
||||||
stream->SetDeviceVolume(device_volume);
|
|
||||||
|
|
||||||
return stream.get();
|
return stream.get();
|
||||||
}
|
}
|
||||||
@@ -281,11 +280,14 @@ void CubebSink::CloseStreams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f32 CubebSink::GetDeviceVolume() const {
|
f32 CubebSink::GetDeviceVolume() const {
|
||||||
return device_volume;
|
if (sink_streams.empty()) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sink_streams[0]->GetDeviceVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void CubebSink::SetDeviceVolume(f32 volume) {
|
void CubebSink::SetDeviceVolume(f32 volume) {
|
||||||
device_volume = volume;
|
|
||||||
for (auto& stream : sink_streams) {
|
for (auto& stream : sink_streams) {
|
||||||
stream->SetDeviceVolume(volume);
|
stream->SetDeviceVolume(volume);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -41,7 +38,6 @@ public:
|
|||||||
StreamType type) override {
|
StreamType type) override {
|
||||||
if (null_sink == nullptr) {
|
if (null_sink == nullptr) {
|
||||||
null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
|
null_sink = std::make_unique<NullSinkStreamImpl>(system, type);
|
||||||
null_sink->SetDeviceVolume(device_volume);
|
|
||||||
}
|
}
|
||||||
return null_sink.get();
|
return null_sink.get();
|
||||||
}
|
}
|
||||||
@@ -49,14 +45,9 @@ public:
|
|||||||
void CloseStream(SinkStream*) override {}
|
void CloseStream(SinkStream*) override {}
|
||||||
void CloseStreams() override {}
|
void CloseStreams() override {}
|
||||||
f32 GetDeviceVolume() const override {
|
f32 GetDeviceVolume() const override {
|
||||||
return device_volume;
|
return 1.0f;
|
||||||
}
|
|
||||||
void SetDeviceVolume(f32 volume) override {
|
|
||||||
device_volume = volume;
|
|
||||||
if (null_sink != nullptr) {
|
|
||||||
null_sink->SetDeviceVolume(volume);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
void SetDeviceVolume(f32 volume) override {}
|
||||||
void SetSystemVolume(f32 volume) override {}
|
void SetSystemVolume(f32 volume) override {}
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|||||||
@@ -246,7 +246,6 @@ SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels
|
|||||||
system_channels = system_channels_;
|
system_channels = system_channels_;
|
||||||
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>(
|
||||||
device_channels, system_channels, output_device, input_device, type, system));
|
device_channels, system_channels, output_device, input_device, type, system));
|
||||||
stream->SetDeviceVolume(device_volume);
|
|
||||||
return stream.get();
|
return stream.get();
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,11 +264,14 @@ void SDLSink::CloseStreams() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
f32 SDLSink::GetDeviceVolume() const {
|
f32 SDLSink::GetDeviceVolume() const {
|
||||||
return device_volume;
|
if (sink_streams.empty()) {
|
||||||
|
return 1.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
return sink_streams[0]->GetDeviceVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
void SDLSink::SetDeviceVolume(f32 volume) {
|
void SDLSink::SetDeviceVolume(f32 volume) {
|
||||||
device_volume = volume;
|
|
||||||
for (auto& stream : sink_streams) {
|
for (auto& stream : sink_streams) {
|
||||||
stream->SetDeviceVolume(volume);
|
stream->SetDeviceVolume(volume);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,3 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
||||||
|
|
||||||
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
@@ -99,8 +96,6 @@ public:
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/// Master volume, persists stream lifetimes
|
|
||||||
f32 device_volume{1.0f};
|
|
||||||
/// Number of device channels supported by the hardware
|
/// Number of device channels supported by the hardware
|
||||||
u32 device_channels{2};
|
u32 device_channels{2};
|
||||||
/// Number of channels the game is sending
|
/// Number of channels the game is sending
|
||||||
|
|||||||
@@ -459,7 +459,7 @@ struct Values {
|
|||||||
&frame_gen};
|
&frame_gen};
|
||||||
|
|
||||||
SwitchableSetting<u32, true> frame_gen_queue_target{linkage,
|
SwitchableSetting<u32, true> frame_gen_queue_target{linkage,
|
||||||
0,
|
1,
|
||||||
0,
|
0,
|
||||||
2,
|
2,
|
||||||
"frame_gen_queue_target",
|
"frame_gen_queue_target",
|
||||||
@@ -469,6 +469,9 @@ struct Values {
|
|||||||
false,
|
false,
|
||||||
&frame_gen};
|
&frame_gen};
|
||||||
|
|
||||||
|
SwitchableSetting<bool> frame_gen_fp16{linkage, true, "frame_gen_fp16", Category::Renderer,
|
||||||
|
Specialization::Default, true, false, &frame_gen};
|
||||||
|
|
||||||
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
|
SwitchableSetting<bool> frame_gen_dump_flow{linkage, false, "frame_gen_dump_flow",
|
||||||
Category::Renderer};
|
Category::Renderer};
|
||||||
|
|
||||||
|
|||||||
@@ -1199,7 +1199,6 @@ else()
|
|||||||
endif()
|
endif()
|
||||||
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)
|
target_link_libraries(core PUBLIC common PRIVATE audio_core hid_core network video_core nx_tzdb tz)
|
||||||
|
|
||||||
if (BOOST_NO_HEADERS)
|
if (BOOST_NO_HEADERS)
|
||||||
|
|||||||
@@ -73,20 +73,16 @@ Result DisplayLayerManager::CreateManagedDisplayLayer(u64* out_layer_id) {
|
|||||||
R_TRY(m_manager_display_service->CreateManagedLayer(
|
R_TRY(m_manager_display_service->CreateManagedLayer(
|
||||||
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
|
out_layer_id, 0, display_id, Service::AppletResourceUserId{m_process->GetProcessId()}));
|
||||||
|
|
||||||
m_manager_display_service->SetLayerVisibility(m_visible, *out_layer_id);
|
|
||||||
(void)m_display_service->GetContainer()->SetLayerStackMask(*out_layer_id,
|
|
||||||
this->GetLayerStackMask());
|
|
||||||
|
|
||||||
if (m_applet_id != AppletId::Application) {
|
if (m_applet_id != AppletId::Application) {
|
||||||
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, *out_layer_id);
|
||||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||||
(void)m_manager_display_service->SetLayerZIndex(Overlay, *out_layer_id);
|
(void)m_manager_display_service->SetLayerZIndex(-1, *out_layer_id);
|
||||||
|
(void)m_display_service->GetContainer()->SetLayerIsOverlay(*out_layer_id, true);
|
||||||
} else {
|
} else {
|
||||||
(void)m_manager_display_service->SetLayerZIndex(Foreground, *out_layer_id);
|
(void)m_manager_display_service->SetLayerZIndex(1, *out_layer_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
(void)m_display_service->GetContainer()->SetLayerZIndex(*out_layer_id, true);
|
||||||
m_display_service->GetContainer()->SetLayerZIndex(*out_layer_id, true);
|
|
||||||
m_managed_display_layers.emplace(*out_layer_id);
|
m_managed_display_layers.emplace(*out_layer_id);
|
||||||
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
@@ -126,16 +122,14 @@ Result DisplayLayerManager::IsSystemBufferSharingEnabled() {
|
|||||||
|
|
||||||
// Ensure the overlay layer is visible
|
// Ensure the overlay layer is visible
|
||||||
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
|
m_manager_display_service->SetLayerVisibility(m_visible, m_system_shared_layer_id);
|
||||||
(void)m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
|
m_manager_display_service->SetLayerBlending(m_blending_enabled, m_system_shared_layer_id);
|
||||||
s32 initial_z = Foreground;
|
s32 initial_z = 1;
|
||||||
|
(void)m_display_service->GetContainer()->SetLayerZIndex(m_system_shared_layer_id, true);
|
||||||
if (m_applet_id == AppletId::OverlayDisplay) {
|
if (m_applet_id == AppletId::OverlayDisplay) {
|
||||||
initial_z = Overlay;
|
initial_z = -1;
|
||||||
(void)m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
|
||||||
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
(void)m_display_service->GetContainer()->SetLayerIsOverlay(m_system_shared_layer_id, true);
|
||||||
}
|
}
|
||||||
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
m_manager_display_service->SetLayerZIndex(initial_z, m_system_shared_layer_id);
|
||||||
m_display_service->GetContainer()->SetLayerZIndex(m_system_shared_layer_id, true);
|
|
||||||
m_managed_display_layers.emplace(m_system_shared_layer_id);
|
|
||||||
R_SUCCEED();
|
R_SUCCEED();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -142,7 +142,6 @@ IReadOnlyApplicationControlDataInterface::IReadOnlyApplicationControlDataInterfa
|
|||||||
{10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"},
|
{10, &IReadOnlyApplicationControlDataInterface::ListApplicationIcon, "ListApplicationIcon"},
|
||||||
{13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"},
|
{13, &IReadOnlyApplicationControlDataInterface::ListApplicationTitle, "ListApplicationTitle"},
|
||||||
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
|
{19, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
|
||||||
{23, D<&IReadOnlyApplicationControlDataInterface::GetApplicationControlData3>, "GetApplicationControlData"},
|
|
||||||
};
|
};
|
||||||
// clang-format on
|
// clang-format on
|
||||||
|
|
||||||
|
|||||||
@@ -143,7 +143,7 @@ protected:
|
|||||||
/// @param expected_header_ request header in the command buffer which will trigger dispatch to this handler
|
/// @param expected_header_ request header in the command buffer which will trigger dispatch to this handler
|
||||||
/// @param handler_callback_ member function in this service which will be called to handle the request
|
/// @param handler_callback_ member function in this service which will be called to handle the request
|
||||||
/// @param name_ human-friendly name for the request. Used mostly for logging purposes.
|
/// @param name_ human-friendly name for the request. Used mostly for logging purposes.
|
||||||
FunctionInfoTyped(u32 expected_header_, HandlerFnP<T> handler_callback_, const char* name_)
|
constexpr FunctionInfoTyped(u32 expected_header_, HandlerFnP<T> handler_callback_, const char* name_)
|
||||||
: FunctionInfoBase{expected_header_, HandlerFnP<ServiceFrameworkBase>(handler_callback_), name_} {}
|
: FunctionInfoBase{expected_header_, HandlerFnP<ServiceFrameworkBase>(handler_callback_), name_} {}
|
||||||
};
|
};
|
||||||
using FunctionInfo = FunctionInfoTyped<Self>;
|
using FunctionInfo = FunctionInfoTyped<Self>;
|
||||||
|
|||||||
@@ -274,8 +274,8 @@ std::pair<oaknut::XReg, oaknut::XReg> InlinePageTableEmitVAddrLookup(oaknut::Cod
|
|||||||
code.LDR(Xscratch0, Xpagetable, Xscratch0);
|
code.LDR(Xscratch0, Xpagetable, Xscratch0);
|
||||||
|
|
||||||
if (ctx.conf.page_table_marked_bit) {
|
if (ctx.conf.page_table_marked_bit) {
|
||||||
code.TST(Xscratch0, 1ULL << *ctx.conf.page_table_marked_bit);
|
// check for marked bit
|
||||||
code.B(NE, *fallback);
|
code.TBNZ(Xscratch0, *ctx.conf.page_table_marked_bit, *fallback);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ctx.conf.page_table_pointer_mask != 0) {
|
if (ctx.conf.page_table_pointer_mask != 0) {
|
||||||
|
|||||||
@@ -4,16 +4,17 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2021 yuzu Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <chrono>
|
#include <thread>
|
||||||
#include <fmt/ranges.h>
|
#include <fmt/ranges.h>
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
|
||||||
#include "common/param_package.h"
|
#include "common/param_package.h"
|
||||||
#include "common/settings.h"
|
#include "common/settings.h"
|
||||||
#include "common/steady_clock.h"
|
#include "common/thread.h"
|
||||||
#include "input_common/drivers/mouse.h"
|
#include "input_common/drivers/mouse.h"
|
||||||
|
|
||||||
namespace InputCommon {
|
namespace InputCommon {
|
||||||
|
constexpr int update_time = 10;
|
||||||
constexpr float default_panning_sensitivity = 0.0010f;
|
constexpr float default_panning_sensitivity = 0.0010f;
|
||||||
constexpr float default_stick_sensitivity = 0.0006f;
|
constexpr float default_stick_sensitivity = 0.0006f;
|
||||||
constexpr float default_deadzone_counterweight = 0.01f;
|
constexpr float default_deadzone_counterweight = 0.01f;
|
||||||
@@ -73,31 +74,33 @@ Mouse::Mouse(std::string input_engine_) : InputEngine(std::move(input_engine_))
|
|||||||
last_motion_change = {};
|
last_motion_change = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mouse::UpdateStickInput(Common::SteadyClock::time_point timestamp) {
|
void Mouse::UpdateStickInput() {
|
||||||
if (IsMousePanningEnabled()) {
|
if (!IsMousePanningEnabled()) {
|
||||||
const float length = last_mouse_change.Length();
|
return;
|
||||||
|
|
||||||
// Prevent input from exceeding the max range (1.0f) too much,
|
|
||||||
// but allow some room to make it easier to sustain
|
|
||||||
if (length > maximum_stick_range) {
|
|
||||||
last_mouse_change /= length;
|
|
||||||
last_mouse_change *= maximum_stick_range;
|
|
||||||
}
|
|
||||||
|
|
||||||
SetAxis(identifier, mouse_axis_x, last_mouse_change[0]);
|
|
||||||
SetAxis(identifier, mouse_axis_y, -last_mouse_change[1]);
|
|
||||||
|
|
||||||
// Decay input over time
|
|
||||||
const float clamped_length = (std::min)(1.0f, length);
|
|
||||||
const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue();
|
|
||||||
const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f;
|
|
||||||
const float min_decay = Settings::values.mouse_panning_min_decay.GetValue();
|
|
||||||
const float clamped_decay = (std::min)(1 - min_decay / 100.0f, decay);
|
|
||||||
last_mouse_change *= clamped_decay;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const float length = last_mouse_change.Length();
|
||||||
|
|
||||||
|
// Prevent input from exceeding the max range (1.0f) too much,
|
||||||
|
// but allow some room to make it easier to sustain
|
||||||
|
if (length > maximum_stick_range) {
|
||||||
|
last_mouse_change /= length;
|
||||||
|
last_mouse_change *= maximum_stick_range;
|
||||||
|
}
|
||||||
|
|
||||||
|
SetAxis(identifier, mouse_axis_x, last_mouse_change[0]);
|
||||||
|
SetAxis(identifier, mouse_axis_y, -last_mouse_change[1]);
|
||||||
|
|
||||||
|
// Decay input over time
|
||||||
|
const float clamped_length = (std::min)(1.0f, length);
|
||||||
|
const float decay_strength = Settings::values.mouse_panning_decay_strength.GetValue();
|
||||||
|
const float decay = 1 - clamped_length * clamped_length * decay_strength * 0.01f;
|
||||||
|
const float min_decay = Settings::values.mouse_panning_min_decay.GetValue();
|
||||||
|
const float clamped_decay = (std::min)(1 - min_decay / 100.0f, decay);
|
||||||
|
last_mouse_change *= clamped_decay;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mouse::UpdateMotionInput(Common::SteadyClock::time_point timestamp) {
|
void Mouse::UpdateMotionInput() {
|
||||||
const float sensitivity =
|
const float sensitivity =
|
||||||
IsMousePanningEnabled() ? default_motion_panning_sensitivity : default_motion_sensitivity;
|
IsMousePanningEnabled() ? default_motion_panning_sensitivity : default_motion_sensitivity;
|
||||||
|
|
||||||
@@ -111,21 +114,23 @@ void Mouse::UpdateMotionInput(Common::SteadyClock::time_point timestamp) {
|
|||||||
last_motion_change[1] = last_motion_change[1] * multiplier;
|
last_motion_change[1] = last_motion_change[1] * multiplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (IsMousePanningEnabled()) {
|
const BasicMotion motion_data{
|
||||||
last_motion_change[0] = 0;
|
|
||||||
last_motion_change[1] = 0;
|
|
||||||
}
|
|
||||||
last_motion_change[2] = 0;
|
|
||||||
|
|
||||||
SetMotion(motion_identifier, 0, BasicMotion{
|
|
||||||
.gyro_x = last_motion_change[0] * sensitivity,
|
.gyro_x = last_motion_change[0] * sensitivity,
|
||||||
.gyro_y = last_motion_change[1] * sensitivity,
|
.gyro_y = last_motion_change[1] * sensitivity,
|
||||||
.gyro_z = last_motion_change[2] * sensitivity,
|
.gyro_z = last_motion_change[2] * sensitivity,
|
||||||
.accel_x = 0,
|
.accel_x = 0,
|
||||||
.accel_y = 0,
|
.accel_y = 0,
|
||||||
.accel_z = 0,
|
.accel_z = 0,
|
||||||
.delta_timestamp = u64(std::chrono::duration_cast<std::chrono::microseconds>(timestamp - last_notify_timestamp).count()),
|
.delta_timestamp = update_time * 1000,
|
||||||
});
|
};
|
||||||
|
|
||||||
|
if (IsMousePanningEnabled()) {
|
||||||
|
last_motion_change[0] = 0;
|
||||||
|
last_motion_change[1] = 0;
|
||||||
|
}
|
||||||
|
last_motion_change[2] = 0;
|
||||||
|
|
||||||
|
SetMotion(motion_identifier, 0, motion_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mouse::Move(int x, int y, int center_x, int center_y) {
|
void Mouse::Move(int x, int y, int center_x, int center_y) {
|
||||||
@@ -144,27 +149,29 @@ void Mouse::Move(int x, int y, int center_x, int center_y) {
|
|||||||
last_mouse_change /= length;
|
last_mouse_change /= length;
|
||||||
last_mouse_change *= deadzone_cw;
|
last_mouse_change *= deadzone_cw;
|
||||||
}
|
}
|
||||||
} else {
|
return;
|
||||||
if (button_pressed) {
|
}
|
||||||
const auto mouse_move = Common::Vec<int, 2>(x, y) - mouse_origin;
|
|
||||||
const float x_sensitivity = Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity;
|
if (button_pressed) {
|
||||||
const float y_sensitivity = Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity;
|
const auto mouse_move = Common::Vec<int, 2>(x, y) - mouse_origin;
|
||||||
SetAxis(identifier, mouse_axis_x, float(mouse_move[0]) * x_sensitivity);
|
const float x_sensitivity =
|
||||||
SetAxis(identifier, mouse_axis_y, float(-mouse_move[1]) * y_sensitivity);
|
Settings::values.mouse_panning_x_sensitivity.GetValue() * default_stick_sensitivity;
|
||||||
last_motion_change = {
|
const float y_sensitivity =
|
||||||
float(-mouse_move[1]) * x_sensitivity,
|
Settings::values.mouse_panning_y_sensitivity.GetValue() * default_stick_sensitivity;
|
||||||
float(-mouse_move[0]) * y_sensitivity,
|
SetAxis(identifier, mouse_axis_x, float(mouse_move[0]) * x_sensitivity);
|
||||||
last_motion_change[2],
|
SetAxis(identifier, mouse_axis_y, float(-mouse_move[1]) * y_sensitivity);
|
||||||
};
|
|
||||||
}
|
last_motion_change = {
|
||||||
|
float(-mouse_move[1]) * x_sensitivity,
|
||||||
|
float(-mouse_move[0]) * y_sensitivity,
|
||||||
|
last_motion_change[2],
|
||||||
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mouse::NotifyChanged() {
|
void Mouse::NotifyChanged() {
|
||||||
auto const timestamp = Common::SteadyClock::Now();
|
UpdateStickInput();
|
||||||
UpdateStickInput(timestamp);
|
UpdateMotionInput();
|
||||||
UpdateMotionInput(timestamp);
|
|
||||||
last_notify_timestamp = timestamp;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mouse::MouseMove(f32 touch_x, f32 touch_y) {
|
void Mouse::MouseMove(f32 touch_x, f32 touch_y) {
|
||||||
@@ -216,8 +223,8 @@ void Mouse::MouseWheelChange(int x, int y) {
|
|||||||
wheel_position[0] += x;
|
wheel_position[0] += x;
|
||||||
wheel_position[1] += y;
|
wheel_position[1] += y;
|
||||||
last_motion_change[2] += static_cast<f32>(y);
|
last_motion_change[2] += static_cast<f32>(y);
|
||||||
SetAxis(identifier, wheel_axis_x, f32(wheel_position[0]));
|
SetAxis(identifier, wheel_axis_x, static_cast<f32>(wheel_position[0]));
|
||||||
SetAxis(identifier, wheel_axis_y, f32(wheel_position[1]));
|
SetAxis(identifier, wheel_axis_y, static_cast<f32>(wheel_position[1]));
|
||||||
}
|
}
|
||||||
|
|
||||||
void Mouse::ReleaseAllButtons() {
|
void Mouse::ReleaseAllButtons() {
|
||||||
|
|||||||
@@ -7,9 +7,7 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
#include <thread>
|
#include <thread>
|
||||||
#include <chrono>
|
|
||||||
|
|
||||||
#include "common/steady_clock.h"
|
|
||||||
#include "common/polyfill_thread.h"
|
#include "common/polyfill_thread.h"
|
||||||
#include "common/vector_math.h"
|
#include "common/vector_math.h"
|
||||||
#include "input_common/input_engine.h"
|
#include "input_common/input_engine.h"
|
||||||
@@ -103,8 +101,8 @@ public:
|
|||||||
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
Common::Input::ButtonNames GetUIName(const Common::ParamPackage& params) const override;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
void UpdateStickInput(Common::SteadyClock::time_point timestamp);
|
void UpdateStickInput();
|
||||||
void UpdateMotionInput(Common::SteadyClock::time_point timestamp);
|
void UpdateMotionInput();
|
||||||
bool IsMousePanningEnabled();
|
bool IsMousePanningEnabled();
|
||||||
|
|
||||||
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
Common::Input::ButtonNames GetUIButtonName(const Common::ParamPackage& params) const;
|
||||||
@@ -114,7 +112,6 @@ private:
|
|||||||
Common::Vec<float, 2> last_mouse_change;
|
Common::Vec<float, 2> last_mouse_change;
|
||||||
Common::Vec<float, 3> last_motion_change;
|
Common::Vec<float, 3> last_motion_change;
|
||||||
Common::Vec<int, 2> wheel_position;
|
Common::Vec<int, 2> wheel_position;
|
||||||
Common::SteadyClock::time_point last_notify_timestamp{};
|
|
||||||
bool button_pressed = false;
|
bool button_pressed = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -414,9 +414,7 @@ endif()
|
|||||||
if (YUZU_USE_EXTERNAL_FFMPEG)
|
if (YUZU_USE_EXTERNAL_FFMPEG)
|
||||||
add_dependencies(video_core ffmpeg-build)
|
add_dependencies(video_core ffmpeg-build)
|
||||||
endif()
|
endif()
|
||||||
|
|
||||||
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
target_include_directories(video_core PUBLIC ${FFmpeg_INCLUDE_DIR})
|
||||||
|
|
||||||
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
|
target_link_libraries(video_core PRIVATE ${FFmpeg_LIBRARIES})
|
||||||
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
|
target_link_options(video_core PRIVATE ${FFmpeg_LDFLAGS})
|
||||||
|
|
||||||
|
|||||||
@@ -78,7 +78,7 @@ public:
|
|||||||
protected:
|
protected:
|
||||||
static constexpr size_t UNSET_CHANNEL{(std::numeric_limits<size_t>::max)()};
|
static constexpr size_t UNSET_CHANNEL{(std::numeric_limits<size_t>::max)()};
|
||||||
|
|
||||||
P* channel_state = nullptr;
|
P* channel_state;
|
||||||
size_t current_channel_id{UNSET_CHANNEL};
|
size_t current_channel_id{UNSET_CHANNEL};
|
||||||
size_t current_address_space{};
|
size_t current_address_space{};
|
||||||
Tegra::Engines::Maxwell3D* maxwell3d{};
|
Tegra::Engines::Maxwell3D* maxwell3d{};
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ constexpr u32 PERFORMANCE_SHADER_ID_FIRST = 280;
|
|||||||
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
|
constexpr u32 PERFORMANCE_SHADER_ID_LAST = 302;
|
||||||
|
|
||||||
constexpr u32 CACHE_MAGIC = 0x4746534C;
|
constexpr u32 CACHE_MAGIC = 0x4746534C;
|
||||||
constexpr u32 CACHE_VERSION = 3;
|
constexpr u32 CACHE_VERSION = 2;
|
||||||
|
|
||||||
struct CacheHeader {
|
struct CacheHeader {
|
||||||
u32 magic;
|
u32 magic;
|
||||||
@@ -53,6 +53,7 @@ struct CacheHeader {
|
|||||||
u64 source_size;
|
u64 source_size;
|
||||||
u64 source_hash;
|
u64 source_hash;
|
||||||
u32 module_count;
|
u32 module_count;
|
||||||
|
u32 variant;
|
||||||
};
|
};
|
||||||
|
|
||||||
struct Section {
|
struct Section {
|
||||||
@@ -276,19 +277,41 @@ template <typename Map>
|
|||||||
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
|
return std::ranges::all_of(ids, [&](u32 id) { return resources.contains(id); });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] u32 VariantOffset(ShaderVariant variant) {
|
||||||
|
return variant == ShaderVariant::NativeFp16 ? PerformanceShader::NATIVE_FP16_OFFSET
|
||||||
|
: PerformanceShader::NATIVE_FP32_OFFSET;
|
||||||
|
}
|
||||||
|
|
||||||
template <typename Map>
|
template <typename Map>
|
||||||
[[nodiscard]] bool HasNativeShaders(const Map& resources) {
|
[[nodiscard]] bool HasNativeVariant(const Map& resources, ShaderVariant variant) {
|
||||||
|
const u32 offset = VariantOffset(variant);
|
||||||
return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) {
|
return std::ranges::all_of(PerformanceShaderIds(), [&](u32 id) {
|
||||||
const auto hit = resources.find(id + PerformanceShader::NATIVE_FP16_OFFSET);
|
const auto hit = resources.find(id + offset);
|
||||||
return hit != resources.end() && IsSpirvModule(hit->second);
|
return hit != resources.end() && IsSpirvModule(hit->second);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] std::optional<ShaderVariant> SelectVariant(const ResourceSpans& resources,
|
||||||
|
bool allow_fp16, bool prefer_fp16) {
|
||||||
|
if (prefer_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
|
||||||
|
return ShaderVariant::NativeFp16;
|
||||||
|
}
|
||||||
|
if (HasNativeVariant(resources, ShaderVariant::NativeFp32)) {
|
||||||
|
return ShaderVariant::NativeFp32;
|
||||||
|
}
|
||||||
|
if (allow_fp16 && HasNativeVariant(resources, ShaderVariant::NativeFp16)) {
|
||||||
|
return ShaderVariant::NativeFp16;
|
||||||
|
}
|
||||||
|
return std::nullopt;
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
|
[[nodiscard]] LosslessStatus TranslateAll(const ResourceSpans& resources,
|
||||||
ShaderModules& out_modules) {
|
ShaderModules& out_modules,
|
||||||
|
ShaderVariant variant) {
|
||||||
|
const u32 offset = VariantOffset(variant);
|
||||||
out_modules.clear();
|
out_modules.clear();
|
||||||
for (const u32 id : PerformanceShaderIds()) {
|
for (const u32 id : PerformanceShaderIds()) {
|
||||||
const auto hit = resources.find(id + PerformanceShader::NATIVE_FP16_OFFSET);
|
const auto hit = resources.find(id + offset);
|
||||||
if (hit == resources.end()) {
|
if (hit == resources.end()) {
|
||||||
return LosslessStatus::MissingShaders;
|
return LosslessStatus::MissingShaders;
|
||||||
}
|
}
|
||||||
@@ -320,7 +343,7 @@ template <typename Map>
|
|||||||
}
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
|
[[nodiscard]] bool ReadShaderCache(const std::filesystem::path& path, u64 source_size,
|
||||||
u64 source_hash, ShaderModules& out_modules) {
|
u64 source_hash, u32 variant, ShaderModules& out_modules) {
|
||||||
if (!Common::FS::Exists(path)) {
|
if (!Common::FS::Exists(path)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -332,7 +355,8 @@ template <typename Map>
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
|
if (header.magic != CACHE_MAGIC || header.version != CACHE_VERSION ||
|
||||||
header.source_size != source_size || header.source_hash != source_hash) {
|
header.source_size != source_size || header.source_hash != source_hash ||
|
||||||
|
header.variant != variant) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -409,10 +433,8 @@ template <typename Map>
|
|||||||
return LosslessStatus::MissingShaders;
|
return LosslessStatus::MissingShaders;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!HasNativeShaders(out_resources)) {
|
return HasPerformanceShaders(out_resources) ? LosslessStatus::Ok
|
||||||
return LosslessStatus::MissingShaders;
|
: LosslessStatus::MissingShaders;
|
||||||
}
|
|
||||||
return LosslessStatus::Ok;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // Anonymous namespace
|
} // Anonymous namespace
|
||||||
@@ -461,7 +483,7 @@ LosslessStatus GetInstalledLosslessStatus() {
|
|||||||
return ValidateLosslessDll(GetLosslessDllPath());
|
return ValidateLosslessDll(GetLosslessDllPath());
|
||||||
}
|
}
|
||||||
|
|
||||||
LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
LosslessStatus LoadShaderModules(ShaderModules& out_modules, bool allow_fp16, bool prefer_fp16) {
|
||||||
std::vector<u8> image;
|
std::vector<u8> image;
|
||||||
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
|
const LosslessStatus read_status = ReadImageFile(GetLosslessDllPath(), image);
|
||||||
if (read_status != LosslessStatus::Ok) {
|
if (read_status != LosslessStatus::Ok) {
|
||||||
@@ -479,11 +501,17 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
|||||||
return parse_status;
|
return parse_status;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ReadShaderCache(cache_path, source_size, source_hash, out_modules)) {
|
const std::optional<ShaderVariant> variant = SelectVariant(spans, allow_fp16, prefer_fp16);
|
||||||
|
if (!variant) {
|
||||||
|
return LosslessStatus::MissingShaders;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (ReadShaderCache(cache_path, source_size, source_hash, static_cast<u32>(*variant),
|
||||||
|
out_modules)) {
|
||||||
return LosslessStatus::Ok;
|
return LosslessStatus::Ok;
|
||||||
}
|
}
|
||||||
|
|
||||||
const LosslessStatus translate_status = TranslateAll(spans, out_modules);
|
const LosslessStatus translate_status = TranslateAll(spans, out_modules, *variant);
|
||||||
if (translate_status != LosslessStatus::Ok) {
|
if (translate_status != LosslessStatus::Ok) {
|
||||||
return translate_status;
|
return translate_status;
|
||||||
}
|
}
|
||||||
@@ -494,6 +522,7 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
|||||||
.source_size = source_size,
|
.source_size = source_size,
|
||||||
.source_hash = source_hash,
|
.source_hash = source_hash,
|
||||||
.module_count = static_cast<u32>(out_modules.size()),
|
.module_count = static_cast<u32>(out_modules.size()),
|
||||||
|
.variant = static_cast<u32>(*variant),
|
||||||
};
|
};
|
||||||
if (!WriteShaderCache(cache_path, header, out_modules)) {
|
if (!WriteShaderCache(cache_path, header, out_modules)) {
|
||||||
void(Common::FS::RemoveFile(cache_path));
|
void(Common::FS::RemoveFile(cache_path));
|
||||||
@@ -505,7 +534,7 @@ LosslessStatus LoadShaderModules(ShaderModules& out_modules) {
|
|||||||
|
|
||||||
LosslessStatus BuildShaderCache() {
|
LosslessStatus BuildShaderCache() {
|
||||||
ShaderModules modules;
|
ShaderModules modules;
|
||||||
return LoadShaderModules(modules);
|
return LoadShaderModules(modules, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool RemoveInstalledLosslessDll() {
|
bool RemoveInstalledLosslessDll() {
|
||||||
|
|||||||
@@ -28,6 +28,11 @@ enum class LosslessStatus : u32 {
|
|||||||
using ShaderResources = std::map<u32, std::vector<u8>>;
|
using ShaderResources = std::map<u32, std::vector<u8>>;
|
||||||
using ShaderModules = std::map<u32, std::vector<u32>>;
|
using ShaderModules = std::map<u32, std::vector<u32>>;
|
||||||
|
|
||||||
|
enum class ShaderVariant : u32 {
|
||||||
|
NativeFp32 = 1,
|
||||||
|
NativeFp16 = 2,
|
||||||
|
};
|
||||||
|
|
||||||
namespace PerformanceShader {
|
namespace PerformanceShader {
|
||||||
constexpr u32 MIPMAPS = 255;
|
constexpr u32 MIPMAPS = 255;
|
||||||
constexpr u32 GENERATE = 256;
|
constexpr u32 GENERATE = 256;
|
||||||
@@ -37,6 +42,7 @@ constexpr std::array<u32, 5> GAMMA{280, 282, 283, 284, 285};
|
|||||||
constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297};
|
constexpr std::array<u32, 10> DELTA{280, 286, 287, 288, 289, 281, 294, 295, 296, 297};
|
||||||
|
|
||||||
constexpr u32 NATIVE_FP16_OFFSET = 49;
|
constexpr u32 NATIVE_FP16_OFFSET = 49;
|
||||||
|
constexpr u32 NATIVE_FP32_OFFSET = 98;
|
||||||
} // namespace PerformanceShader
|
} // namespace PerformanceShader
|
||||||
|
|
||||||
[[nodiscard]] std::filesystem::path GetLosslessDllPath();
|
[[nodiscard]] std::filesystem::path GetLosslessDllPath();
|
||||||
@@ -52,7 +58,9 @@ constexpr u32 NATIVE_FP16_OFFSET = 49;
|
|||||||
|
|
||||||
[[nodiscard]] LosslessStatus BuildShaderCache();
|
[[nodiscard]] LosslessStatus BuildShaderCache();
|
||||||
|
|
||||||
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules);
|
[[nodiscard]] LosslessStatus LoadShaderModules(ShaderModules& out_modules,
|
||||||
|
bool allow_fp16 = false,
|
||||||
|
bool prefer_fp16 = false);
|
||||||
|
|
||||||
bool RemoveInstalledLosslessDll();
|
bool RemoveInstalledLosslessDll();
|
||||||
|
|
||||||
|
|||||||
@@ -198,10 +198,6 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
|
|||||||
VkExtent2D guest_extent) {
|
VkExtent2D guest_extent) {
|
||||||
generated = false;
|
generated = false;
|
||||||
|
|
||||||
if (!shaders) {
|
|
||||||
shaders.emplace(device);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
if (unavailable || !Settings::values.frame_gen.GetValue()) {
|
||||||
if (chain) {
|
if (chain) {
|
||||||
scheduler.Finish();
|
scheduler.Finish();
|
||||||
@@ -211,14 +207,17 @@ void FrameGen::Process(const Device& device, Frame* frame, VkFormat format,
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!shaders->IsValid()) {
|
if (!frame->storage_view) {
|
||||||
unavailable = true;
|
unavailable = true;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!frame->storage_view) {
|
if (!shaders) {
|
||||||
warm_streak = 0;
|
shaders.emplace(device);
|
||||||
return;
|
if (!shaders->IsValid()) {
|
||||||
|
unavailable = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width);
|
peak_guest_extent.width = std::max(peak_guest_extent.width, guest_extent.width);
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project
|
||||||
// SPDX-License-Identifier: GPL-3.0-or-later
|
// SPDX-License-Identifier: GPL-3.0-or-later
|
||||||
|
|
||||||
|
#include "common/settings.h"
|
||||||
#include "video_core/frame_gen/lossless_dll.h"
|
#include "video_core/frame_gen/lossless_dll.h"
|
||||||
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
#include "video_core/renderer_vulkan/present/lsfg_shaders.h"
|
||||||
#include "video_core/renderer_vulkan/present/util.h"
|
#include "video_core/renderer_vulkan/present/util.h"
|
||||||
@@ -9,13 +10,16 @@
|
|||||||
namespace Vulkan {
|
namespace Vulkan {
|
||||||
|
|
||||||
LsfgShaders::LsfgShaders(const Device& device) {
|
LsfgShaders::LsfgShaders(const Device& device) {
|
||||||
if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor() ||
|
if (!device.IsVulkanMemoryModelSupported() || !device.HasNullDescriptor()) {
|
||||||
!device.IsFloat16Supported()) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const bool allow_fp16 = device.IsFloat16Supported();
|
||||||
|
const bool prefer_fp16 = allow_fp16 && Settings::values.frame_gen_fp16.GetValue();
|
||||||
|
|
||||||
VideoCore::FrameGen::ShaderModules code;
|
VideoCore::FrameGen::ShaderModules code;
|
||||||
if (VideoCore::FrameGen::LoadShaderModules(code) != VideoCore::FrameGen::LosslessStatus::Ok) {
|
if (VideoCore::FrameGen::LoadShaderModules(code, allow_fp16, prefer_fp16) !=
|
||||||
|
VideoCore::FrameGen::LosslessStatus::Ok) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,6 @@
|
|||||||
// SPDX-License-Identifier: GPL-2.0-or-later
|
// SPDX-License-Identifier: GPL-2.0-or-later
|
||||||
|
|
||||||
#include <vulkan/vulkan_core.h>
|
#include <vulkan/vulkan_core.h>
|
||||||
#include "common/settings.h"
|
|
||||||
#include "video_core/framebuffer_config.h"
|
#include "video_core/framebuffer_config.h"
|
||||||
#include "video_core/present.h"
|
#include "video_core/present.h"
|
||||||
#include "video_core/renderer_vulkan/present/filters.h"
|
#include "video_core/renderer_vulkan/present/filters.h"
|
||||||
@@ -88,18 +87,13 @@ void BlitScreen::SetWindowAdaptPass(const Device& device) {
|
|||||||
|
|
||||||
void BlitScreen::PrepareFrame(const Device& device, Frame* frame,
|
void BlitScreen::PrepareFrame(const Device& device, Frame* frame,
|
||||||
const Layout::FramebufferLayout& layout) {
|
const Layout::FramebufferLayout& layout) {
|
||||||
if (!window_adapt) {
|
if (!window_adapt || (frame->width == layout.width && frame->height == layout.height)) {
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (frame->width != layout.width || frame->height != layout.height) {
|
|
||||||
WaitIdle(device);
|
|
||||||
} else if (!present_manager.NeedsStorage(frame, true)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
WaitIdle(device);
|
||||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
||||||
window_adapt->GetRenderPass(), true);
|
window_adapt->GetRenderPass());
|
||||||
}
|
}
|
||||||
|
|
||||||
void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame,
|
void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer, Frame* frame,
|
||||||
@@ -126,20 +120,16 @@ void BlitScreen::DrawToFrame(const Device& device, RasterizerVulkan& rasterizer,
|
|||||||
swapchain_view_format = current_swapchain_view_format;
|
swapchain_view_format = current_swapchain_view_format;
|
||||||
}
|
}
|
||||||
|
|
||||||
const bool storage_required = Settings::values.frame_gen.GetValue();
|
|
||||||
if (resource_update_required) {
|
if (resource_update_required) {
|
||||||
WaitIdle(device);
|
WaitIdle(device);
|
||||||
SetWindowAdaptPass(device);
|
SetWindowAdaptPass(device);
|
||||||
|
|
||||||
if (presentation_recreate_required) {
|
if (presentation_recreate_required) {
|
||||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
||||||
window_adapt->GetRenderPass(), storage_required);
|
window_adapt->GetRenderPass());
|
||||||
}
|
}
|
||||||
|
|
||||||
image_index = 0;
|
image_index = 0;
|
||||||
} else if (present_manager.NeedsStorage(frame, storage_required)) {
|
|
||||||
present_manager.RecreateFrame(frame, layout.width, layout.height, swapchain_view_format,
|
|
||||||
window_adapt->GetRenderPass(), true);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const VkExtent2D window_size{
|
const VkExtent2D window_size{
|
||||||
|
|||||||
@@ -29,6 +29,9 @@ static_assert(MAX_FRAMES_IN_FLIGHT <= LSFG_MAX_TARGETS);
|
|||||||
|
|
||||||
bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) {
|
bool CanStoreToFrame(const vk::PhysicalDevice& physical_device, VkFormat format) {
|
||||||
#ifdef HAS_LSFG
|
#ifdef HAS_LSFG
|
||||||
|
if (!Settings::values.frame_gen.GetValue()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
|
const VkFormatProperties props{physical_device.GetFormatProperties(format)};
|
||||||
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
return (props.optimalTilingFeatures & VK_FORMAT_FEATURE_STORAGE_IMAGE_BIT) != 0;
|
||||||
#else
|
#else
|
||||||
@@ -157,7 +160,6 @@ PresentManager::PresentManager(const vk::Instance& instance_,
|
|||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
|
||||||
});
|
});
|
||||||
frame.storage_capable = storage_supported;
|
|
||||||
free_queue.push_back(&frame);
|
free_queue.push_back(&frame);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -203,22 +205,15 @@ size_t PresentManager::MaxExtraFrames() const {
|
|||||||
return image_count - 1;
|
return image_count - 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool PresentManager::NeedsStorage(const Frame* frame, bool required) const {
|
|
||||||
return required && frame->storage_capable && !frame->storage_view;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
||||||
VkRenderPass rd, bool storage) {
|
VkRenderPass rd) {
|
||||||
auto& dld = device.GetLogical();
|
auto& dld = device.GetLogical();
|
||||||
|
|
||||||
frame->width = width;
|
frame->width = width;
|
||||||
frame->height = height;
|
frame->height = height;
|
||||||
|
|
||||||
const bool with_storage = storage && frame->storage_capable;
|
const VkImageUsageFlags storage_usage =
|
||||||
VkImageUsageFlags storage_usage = 0;
|
storage_supported ? static_cast<VkImageUsageFlags>(VK_IMAGE_USAGE_STORAGE_BIT) : 0;
|
||||||
if (with_storage) {
|
|
||||||
storage_usage = VK_IMAGE_USAGE_STORAGE_BIT;
|
|
||||||
}
|
|
||||||
|
|
||||||
frame->image = memory_allocator.CreateImage({
|
frame->image = memory_allocator.CreateImage({
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
|
||||||
@@ -269,7 +264,7 @@ void PresentManager::RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat
|
|||||||
});
|
});
|
||||||
|
|
||||||
frame->storage_view = vk::ImageView{};
|
frame->storage_view = vk::ImageView{};
|
||||||
if (with_storage) {
|
if (storage_supported) {
|
||||||
frame->storage_view = dld.CreateImageView({
|
frame->storage_view = dld.CreateImageView({
|
||||||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
|
||||||
.pNext = nullptr,
|
.pNext = nullptr,
|
||||||
|
|||||||
@@ -36,7 +36,6 @@ struct Frame {
|
|||||||
vk::CommandBuffer cmdbuf;
|
vk::CommandBuffer cmdbuf;
|
||||||
vk::Semaphore render_ready;
|
vk::Semaphore render_ready;
|
||||||
vk::Fence present_done;
|
vk::Fence present_done;
|
||||||
bool storage_capable{};
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PresentManager {
|
class PresentManager {
|
||||||
@@ -58,9 +57,7 @@ public:
|
|||||||
|
|
||||||
/// Recreates the present frame to match the provided parameters
|
/// Recreates the present frame to match the provided parameters
|
||||||
void RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
void RecreateFrame(Frame* frame, u32 width, u32 height, VkFormat image_view_format,
|
||||||
VkRenderPass rd, bool storage);
|
VkRenderPass rd);
|
||||||
|
|
||||||
[[nodiscard]] bool NeedsStorage(const Frame* frame, bool required) const;
|
|
||||||
|
|
||||||
/// Waits for the present thread to finish presenting all queued frames.
|
/// Waits for the present thread to finish presenting all queued frames.
|
||||||
void WaitPresent();
|
void WaitPresent();
|
||||||
|
|||||||
@@ -2850,8 +2850,6 @@ Sampler::VariantKey Sampler::MakeKey(const ImageView& image_view, bool is_depth)
|
|||||||
VariantKey key{};
|
VariantKey key{};
|
||||||
key.reduce_anisotropy = has_added_anisotropy && !image_view.SupportsAnisotropy();
|
key.reduce_anisotropy = has_added_anisotropy && !image_view.SupportsAnisotropy();
|
||||||
key.force_nearest = has_linear_filtering && IsPixelFormatInteger(image_view.format);
|
key.force_nearest = has_linear_filtering && IsPixelFormatInteger(image_view.format);
|
||||||
key.drop_depth_comparison =
|
|
||||||
is_depth && has_depth_comparison && !image_view.SupportsDepthComparison();
|
|
||||||
key.drop_reduction = has_minmax_reduction && !image_view.SupportsMinmaxFilter();
|
key.drop_reduction = has_minmax_reduction && !image_view.SupportsMinmaxFilter();
|
||||||
key.drop_custom_border = has_custom_border_colors && image_view.RequiresBorderColorFormat();
|
key.drop_custom_border = has_custom_border_colors && image_view.RequiresBorderColorFormat();
|
||||||
key.srgb_border = has_srgb_border_color && IsPixelFormatSRGB(image_view.format);
|
key.srgb_border = has_srgb_border_color && IsPixelFormatSRGB(image_view.format);
|
||||||
@@ -2929,9 +2927,6 @@ VkSampler Sampler::Emplace(VariantKey key) {
|
|||||||
create_info.anisotropyEnable = static_cast<VkBool32>(default_anisotropy > 1.0f);
|
create_info.anisotropyEnable = static_cast<VkBool32>(default_anisotropy > 1.0f);
|
||||||
create_info.maxAnisotropy = default_anisotropy;
|
create_info.maxAnisotropy = default_anisotropy;
|
||||||
}
|
}
|
||||||
if (key.drop_depth_comparison) {
|
|
||||||
create_info.compareEnable = VK_FALSE;
|
|
||||||
}
|
|
||||||
if (!custom_border) {
|
if (!custom_border) {
|
||||||
create_info.borderColor = ConvertBorderColor(color);
|
create_info.borderColor = ConvertBorderColor(color);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -536,7 +536,6 @@ private:
|
|||||||
struct VariantKey {
|
struct VariantKey {
|
||||||
bool reduce_anisotropy;
|
bool reduce_anisotropy;
|
||||||
bool force_nearest;
|
bool force_nearest;
|
||||||
bool drop_depth_comparison;
|
|
||||||
bool drop_reduction;
|
bool drop_reduction;
|
||||||
bool drop_custom_border;
|
bool drop_custom_border;
|
||||||
bool srgb_border;
|
bool srgb_border;
|
||||||
|
|||||||
+21
-16
@@ -73,7 +73,6 @@ class QPaintEngine;
|
|||||||
class QSurface;
|
class QSurface;
|
||||||
|
|
||||||
constexpr int default_mouse_constrain_timeout = 10;
|
constexpr int default_mouse_constrain_timeout = 10;
|
||||||
constexpr int default_mouse_update_timeout = 5;
|
|
||||||
|
|
||||||
class RenderWidget : public QWidget {
|
class RenderWidget : public QWidget {
|
||||||
public:
|
public:
|
||||||
@@ -139,10 +138,6 @@ GRenderWindow::GRenderWindow(MainWindow* parent,
|
|||||||
|
|
||||||
mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
|
mouse_constrain_timer.setInterval(default_mouse_constrain_timeout);
|
||||||
connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
|
connect(&mouse_constrain_timer, &QTimer::timeout, this, &GRenderWindow::ConstrainMouse);
|
||||||
|
|
||||||
mouse_update_timer.setInterval(default_mouse_update_timeout);
|
|
||||||
connect(&mouse_update_timer, &QTimer::timeout, this, &GRenderWindow::UpdateMouse);
|
|
||||||
mouse_update_timer.start();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::ExecuteProgram(std::size_t program_index) {
|
void GRenderWindow::ExecuteProgram(std::size_t program_index) {
|
||||||
@@ -492,6 +487,7 @@ void GRenderWindow::mousePressEvent(QMouseEvent* event) {
|
|||||||
input_subsystem->GetMouse()->PressMouseButton(button);
|
input_subsystem->GetMouse()->PressMouseButton(button);
|
||||||
input_subsystem->GetMouse()->PressButton(pos.x(), pos.y(), button);
|
input_subsystem->GetMouse()->PressButton(pos.x(), pos.y(), button);
|
||||||
input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, button);
|
input_subsystem->GetMouse()->PressTouchButton(touch_x, touch_y, button);
|
||||||
|
input_subsystem->GetMouse()->NotifyChanged();
|
||||||
|
|
||||||
emit MouseActivity();
|
emit MouseActivity();
|
||||||
}
|
}
|
||||||
@@ -512,6 +508,7 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
|||||||
input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
|
input_subsystem->GetMouse()->MouseMove(touch_x, touch_y);
|
||||||
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
|
input_subsystem->GetMouse()->TouchMove(touch_x, touch_y);
|
||||||
input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
|
input_subsystem->GetMouse()->Move(pos.x(), pos.y(), center_x, center_y);
|
||||||
|
input_subsystem->GetMouse()->NotifyChanged();
|
||||||
|
|
||||||
// Center mouse for mouse panning
|
// Center mouse for mouse panning
|
||||||
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
|
if (Settings::values.mouse_panning && !Settings::values.mouse_enabled) {
|
||||||
@@ -521,7 +518,8 @@ void GRenderWindow::mouseMoveEvent(QMouseEvent* event) {
|
|||||||
// Constrain mouse for mouse emulation with mouse panning
|
// Constrain mouse for mouse emulation with mouse panning
|
||||||
if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
|
if (Settings::values.mouse_panning && Settings::values.mouse_enabled) {
|
||||||
const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
|
const auto [clamped_mouse_x, clamped_mouse_y] = ClipToTouchScreen(x, y);
|
||||||
QCursor::setPos(mapToGlobal(QPoint{int(clamped_mouse_x), int(clamped_mouse_y)}));
|
QCursor::setPos(mapToGlobal(
|
||||||
|
QPoint{static_cast<int>(clamped_mouse_x), static_cast<int>(clamped_mouse_y)}));
|
||||||
}
|
}
|
||||||
|
|
||||||
mouse_constrain_timer.stop();
|
mouse_constrain_timer.stop();
|
||||||
@@ -536,10 +534,16 @@ void GRenderWindow::mouseReleaseEvent(QMouseEvent* event) {
|
|||||||
|
|
||||||
const auto button = QtButtonToMouseButton(event->button());
|
const auto button = QtButtonToMouseButton(event->button());
|
||||||
input_subsystem->GetMouse()->ReleaseButton(button);
|
input_subsystem->GetMouse()->ReleaseButton(button);
|
||||||
|
input_subsystem->GetMouse()->NotifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::ConstrainMouse() {
|
void GRenderWindow::ConstrainMouse() {
|
||||||
if (QtCommon::emu_thread == nullptr || Settings::values.mouse_panning || !this->isActiveWindow()) {
|
if (QtCommon::emu_thread == nullptr || !Settings::values.mouse_panning) {
|
||||||
|
mouse_constrain_timer.stop();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this->isActiveWindow()) {
|
||||||
mouse_constrain_timer.stop();
|
mouse_constrain_timer.stop();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -548,22 +552,22 @@ void GRenderWindow::ConstrainMouse() {
|
|||||||
const auto pos = mapFromGlobal(QCursor::pos());
|
const auto pos = mapFromGlobal(QCursor::pos());
|
||||||
const int new_pos_x = std::clamp(pos.x(), 0, width());
|
const int new_pos_x = std::clamp(pos.x(), 0, width());
|
||||||
const int new_pos_y = std::clamp(pos.y(), 0, height());
|
const int new_pos_y = std::clamp(pos.y(), 0, height());
|
||||||
QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
|
|
||||||
} else {
|
|
||||||
const int center_x = width() / 2;
|
|
||||||
const int center_y = height() / 2;
|
|
||||||
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void GRenderWindow::UpdateMouse() {
|
QCursor::setPos(mapToGlobal(QPoint{new_pos_x, new_pos_y}));
|
||||||
input_subsystem->GetMouse()->NotifyChanged(); // required to reset mouse once it's no longer moved
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const int center_x = width() / 2;
|
||||||
|
const int center_y = height() / 2;
|
||||||
|
|
||||||
|
QCursor::setPos(mapToGlobal(QPoint{center_x, center_y}));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::wheelEvent(QWheelEvent* event) {
|
void GRenderWindow::wheelEvent(QWheelEvent* event) {
|
||||||
const int x = event->angleDelta().x();
|
const int x = event->angleDelta().x();
|
||||||
const int y = event->angleDelta().y();
|
const int y = event->angleDelta().y();
|
||||||
input_subsystem->GetMouse()->MouseWheelChange(x, y);
|
input_subsystem->GetMouse()->MouseWheelChange(x, y);
|
||||||
|
input_subsystem->GetMouse()->NotifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
void GRenderWindow::TouchBeginEvent(const QTouchEvent* event) {
|
||||||
@@ -712,6 +716,7 @@ void GRenderWindow::focusOutEvent(QFocusEvent* event) {
|
|||||||
input_subsystem->GetKeyboard()->ReleaseAllKeys();
|
input_subsystem->GetKeyboard()->ReleaseAllKeys();
|
||||||
input_subsystem->GetTouchScreen()->ReleaseAllTouch();
|
input_subsystem->GetTouchScreen()->ReleaseAllTouch();
|
||||||
input_subsystem->GetMouse()->ReleaseAllButtons();
|
input_subsystem->GetMouse()->ReleaseAllButtons();
|
||||||
|
input_subsystem->GetMouse()->NotifyChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
void GRenderWindow::resizeEvent(QResizeEvent* event) {
|
||||||
|
|||||||
@@ -144,7 +144,6 @@ private:
|
|||||||
void TouchUpdateEvent(const QTouchEvent* event);
|
void TouchUpdateEvent(const QTouchEvent* event);
|
||||||
void TouchEndEvent();
|
void TouchEndEvent();
|
||||||
void ConstrainMouse();
|
void ConstrainMouse();
|
||||||
void UpdateMouse();
|
|
||||||
|
|
||||||
void RequestCameraCapture();
|
void RequestCameraCapture();
|
||||||
void OnCameraCapture(int requestId, const QImage& img);
|
void OnCameraCapture(int requestId, const QImage& img);
|
||||||
@@ -185,7 +184,6 @@ private:
|
|||||||
#endif
|
#endif
|
||||||
|
|
||||||
QTimer mouse_constrain_timer;
|
QTimer mouse_constrain_timer;
|
||||||
QTimer mouse_update_timer;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void showEvent(QShowEvent* event) override;
|
void showEvent(QShowEvent* event) override;
|
||||||
|
|||||||
@@ -37,16 +37,10 @@ EmuWindow_SDL3::EmuWindow_SDL3(InputCommon::InputSubsystem* input_subsystem_, Co
|
|||||||
SDL_SetWindowTitle(this_->render_window, title.c_str());
|
SDL_SetWindowTitle(this_->render_window, title.c_str());
|
||||||
return 2000;
|
return 2000;
|
||||||
}, this);
|
}, this);
|
||||||
mouse_timer = SDL_AddTimer(100, [](void *userdata, SDL_TimerID, Uint32) -> Uint32 {
|
|
||||||
auto* this_ = (EmuWindow_SDL3*)userdata;
|
|
||||||
this_->input_subsystem->GetMouse()->NotifyChanged();
|
|
||||||
return 100;
|
|
||||||
}, this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
EmuWindow_SDL3::~EmuWindow_SDL3() {
|
EmuWindow_SDL3::~EmuWindow_SDL3() {
|
||||||
SDL_RemoveTimer(titlebar_timer);
|
SDL_RemoveTimer(titlebar_timer);
|
||||||
SDL_RemoveTimer(mouse_timer);
|
|
||||||
system.HIDCore().UnloadInputDevices();
|
system.HIDCore().UnloadInputDevices();
|
||||||
input_subsystem->Shutdown();
|
input_subsystem->Shutdown();
|
||||||
SDL_Quit();
|
SDL_Quit();
|
||||||
|
|||||||
@@ -84,9 +84,6 @@ protected:
|
|||||||
/// Periodic changer of titlebar (independent of event loop)
|
/// Periodic changer of titlebar (independent of event loop)
|
||||||
SDL_TimerID titlebar_timer;
|
SDL_TimerID titlebar_timer;
|
||||||
|
|
||||||
// Mouse resetter once it
|
|
||||||
SDL_TimerID mouse_timer;
|
|
||||||
|
|
||||||
/// Is the window still open?
|
/// Is the window still open?
|
||||||
bool is_open = true;
|
bool is_open = true;
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user