diff options
132 files changed, 1456 insertions, 1379 deletions
diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh index 8173c5728..e0f336427 100755 --- a/.ci/scripts/linux/upload.sh +++ b/.ci/scripts/linux/upload.sh | |||
| @@ -5,21 +5,24 @@ | |||
| 5 | 5 | ||
| 6 | . .ci/scripts/common/pre-upload.sh | 6 | . .ci/scripts/common/pre-upload.sh |
| 7 | 7 | ||
| 8 | APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage" | 8 | APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage" |
| 9 | REV_NAME="yuzu-linux-${GITDATE}-${GITREV}" | 9 | BASE_NAME="yuzu-linux" |
| 10 | REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}" | ||
| 10 | ARCHIVE_NAME="${REV_NAME}.tar.xz" | 11 | ARCHIVE_NAME="${REV_NAME}.tar.xz" |
| 11 | COMPRESSION_FLAGS="-cJvf" | 12 | COMPRESSION_FLAGS="-cJvf" |
| 12 | 13 | ||
| 13 | if [ "${RELEASE_NAME}" = "mainline" ]; then | 14 | if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then |
| 14 | DIR_NAME="${REV_NAME}" | 15 | DIR_NAME="${BASE_NAME}-${RELEASE_NAME}" |
| 15 | else | 16 | else |
| 16 | DIR_NAME="${REV_NAME}_${RELEASE_NAME}" | 17 | DIR_NAME="${REV_NAME}-${RELEASE_NAME}" |
| 17 | fi | 18 | fi |
| 18 | 19 | ||
| 19 | mkdir "$DIR_NAME" | 20 | mkdir "$DIR_NAME" |
| 20 | 21 | ||
| 21 | cp build/bin/yuzu-cmd "$DIR_NAME" | 22 | cp build/bin/yuzu-cmd "$DIR_NAME" |
| 22 | cp build/bin/yuzu "$DIR_NAME" | 23 | if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then |
| 24 | cp build/bin/yuzu "$DIR_NAME" | ||
| 25 | fi | ||
| 23 | 26 | ||
| 24 | # Build an AppImage | 27 | # Build an AppImage |
| 25 | cd build | 28 | cd build |
| @@ -32,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then | |||
| 32 | export APPIMAGE_EXTRACT_AND_RUN=1 | 35 | export APPIMAGE_EXTRACT_AND_RUN=1 |
| 33 | fi | 36 | fi |
| 34 | 37 | ||
| 38 | # Don't let AppImageLauncher ask to integrate EA | ||
| 39 | if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then | ||
| 40 | echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop | ||
| 41 | fi | ||
| 42 | |||
| 35 | if [ "${RELEASE_NAME}" = "mainline" ]; then | 43 | if [ "${RELEASE_NAME}" = "mainline" ]; then |
| 36 | # Generate update information if releasing to mainline | 44 | # Generate update information if releasing to mainline |
| 37 | ./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}" | 45 | ./appimagetool-x86_64.AppImage -u "gh-releases-zsync|yuzu-emu|yuzu-${RELEASE_NAME}|latest|yuzu-*.AppImage.zsync" AppDir "${APPIMAGE_NAME}" |
| @@ -46,4 +54,9 @@ if [ -f "build/${APPIMAGE_NAME}.zsync" ]; then | |||
| 46 | cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/" | 54 | cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/" |
| 47 | fi | 55 | fi |
| 48 | 56 | ||
| 57 | # Copy the AppImage to the general release directory and remove git revision info | ||
| 58 | if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then | ||
| 59 | cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage" | ||
| 60 | fi | ||
| 61 | |||
| 49 | . .ci/scripts/common/post-upload.sh | 62 | . .ci/scripts/common/post-upload.sh |
diff --git a/.ci/scripts/windows/docker.sh b/.ci/scripts/windows/docker.sh index 790ba8218..6f522feed 100755 --- a/.ci/scripts/windows/docker.sh +++ b/.ci/scripts/windows/docker.sh | |||
| @@ -21,6 +21,7 @@ cmake .. \ | |||
| 21 | -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ | 21 | -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON \ |
| 22 | -DENABLE_QT_TRANSLATION=ON \ | 22 | -DENABLE_QT_TRANSLATION=ON \ |
| 23 | -DUSE_CCACHE=ON \ | 23 | -DUSE_CCACHE=ON \ |
| 24 | -DYUZU_CRASH_DUMPS=ON \ | ||
| 24 | -DYUZU_USE_BUNDLED_SDL2=OFF \ | 25 | -DYUZU_USE_BUNDLED_SDL2=OFF \ |
| 25 | -DYUZU_USE_EXTERNAL_SDL2=OFF \ | 26 | -DYUZU_USE_EXTERNAL_SDL2=OFF \ |
| 26 | -DYUZU_TESTS=OFF \ | 27 | -DYUZU_TESTS=OFF \ |
diff --git a/.ci/scripts/windows/upload.ps1 b/.ci/scripts/windows/upload.ps1 index d463281de..21abcd752 100644 --- a/.ci/scripts/windows/upload.ps1 +++ b/.ci/scripts/windows/upload.ps1 | |||
| @@ -65,8 +65,8 @@ if ("$env:GITHUB_ACTIONS" -eq "true") { | |||
| 65 | # None of the other GHA builds are including source, so commenting out today | 65 | # None of the other GHA builds are including source, so commenting out today |
| 66 | #Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts" | 66 | #Copy-Item $MSVC_SOURCE_TARXZ -Destination "artifacts" |
| 67 | 67 | ||
| 68 | # Are debug symbols important? | 68 | # Debugging symbols |
| 69 | # cp .\build\bin\yuzu*.pdb .\pdb\ | 69 | cp .\build\bin\yuzu*.pdb .\artifacts\ |
| 70 | 70 | ||
| 71 | # Write out a tag BUILD_TAG to environment for the Upload step | 71 | # Write out a tag BUILD_TAG to environment for the Upload step |
| 72 | # We're getting ${{ github.event.number }} as $env:PR_NUMBER" | 72 | # We're getting ${{ github.event.number }} as $env:PR_NUMBER" |
diff --git a/.ci/templates/build-msvc.yml b/.ci/templates/build-msvc.yml index a2ee71bd8..ea405e5dc 100644 --- a/.ci/templates/build-msvc.yml +++ b/.ci/templates/build-msvc.yml | |||
| @@ -9,7 +9,7 @@ parameters: | |||
| 9 | steps: | 9 | steps: |
| 10 | - script: choco install vulkan-sdk | 10 | - script: choco install vulkan-sdk |
| 11 | displayName: 'Install vulkan-sdk' | 11 | displayName: 'Install vulkan-sdk' |
| 12 | - script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release .. && cd .. | 12 | - script: refreshenv && mkdir build && cd build && cmake -G "Visual Studio 17 2022" -A x64 -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=${COMPAT} -DYUZU_TESTS=OFF -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DDISPLAY_VERSION=${{ parameters['version'] }} -DCMAKE_BUILD_TYPE=Release -DYUZU_CRASH_DUMPS=ON .. && cd .. |
| 13 | displayName: 'Configure CMake' | 13 | displayName: 'Configure CMake' |
| 14 | - task: MSBuild@1 | 14 | - task: MSBuild@1 |
| 15 | displayName: 'Build' | 15 | displayName: 'Build' |
diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml index 5d5b140fd..71a23ebe6 100644 --- a/.ci/yuzu-patreon-step2.yml +++ b/.ci/yuzu-patreon-step2.yml | |||
| @@ -11,9 +11,30 @@ stages: | |||
| 11 | - stage: build | 11 | - stage: build |
| 12 | displayName: 'build' | 12 | displayName: 'build' |
| 13 | jobs: | 13 | jobs: |
| 14 | - job: build | 14 | - job: linux |
| 15 | timeoutInMinutes: 120 | 15 | timeoutInMinutes: 120 |
| 16 | displayName: 'windows-msvc' | 16 | displayName: 'linux' |
| 17 | pool: | ||
| 18 | vmImage: ubuntu-latest | ||
| 19 | strategy: | ||
| 20 | maxParallel: 10 | ||
| 21 | matrix: | ||
| 22 | linux: | ||
| 23 | BuildSuffix: 'linux' | ||
| 24 | ScriptFolder: 'linux' | ||
| 25 | steps: | ||
| 26 | - template: ./templates/sync-source.yml | ||
| 27 | parameters: | ||
| 28 | artifactSource: $(parameters.artifactSource) | ||
| 29 | needSubmodules: 'true' | ||
| 30 | - template: ./templates/build-single.yml | ||
| 31 | parameters: | ||
| 32 | artifactSource: 'false' | ||
| 33 | cache: $(parameters.cache) | ||
| 34 | version: $(DisplayVersion) | ||
| 35 | - job: msvc | ||
| 36 | timeoutInMinutes: 120 | ||
| 37 | displayName: 'windows' | ||
| 17 | pool: | 38 | pool: |
| 18 | vmImage: windows-2022 | 39 | vmImage: windows-2022 |
| 19 | steps: | 40 | steps: |
diff --git a/.github/workflows/verify.yml b/.github/workflows/verify.yml index 733a52764..7cde8380b 100644 --- a/.github/workflows/verify.yml +++ b/.github/workflows/verify.yml | |||
| @@ -104,7 +104,7 @@ jobs: | |||
| 104 | run: | | 104 | run: | |
| 105 | glslangValidator --version | 105 | glslangValidator --version |
| 106 | mkdir build | 106 | mkdir build |
| 107 | cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify | 107 | cmake . -B build -GNinja -DCMAKE_TOOLCHAIN_FILE="CMakeModules/MSVCCache.cmake" -DUSE_CCACHE=ON -DYUZU_USE_BUNDLED_QT=1 -DYUZU_USE_BUNDLED_SDL2=1 -DYUZU_USE_QT_WEB_ENGINE=ON -DENABLE_COMPATIBILITY_LIST_DOWNLOAD=ON -DYUZU_ENABLE_COMPATIBILITY_REPORTING=ON -DUSE_DISCORD_PRESENCE=ON -DENABLE_QT_TRANSLATION=ON -DCMAKE_BUILD_TYPE=Release -DGIT_BRANCH=pr-verify -DYUZU_CRASH_DUMPS=ON |
| 108 | - name: Build | 108 | - name: Build |
| 109 | run: cmake --build build | 109 | run: cmake --build build |
| 110 | - name: Cache Summary | 110 | - name: Cache Summary |
diff --git a/.gitignore b/.gitignore index cdf37962a..a5f7248c7 100644 --- a/.gitignore +++ b/.gitignore | |||
| @@ -2,7 +2,7 @@ | |||
| 2 | # SPDX-License-Identifier: GPL-2.0-or-later | 2 | # SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | # Build directory | 4 | # Build directory |
| 5 | [Bb]uild/ | 5 | [Bb]uild*/ |
| 6 | doc-build/ | 6 | doc-build/ |
| 7 | 7 | ||
| 8 | # Generated source files | 8 | # Generated source files |
diff --git a/.reuse/dep5 b/.reuse/dep5 index fe4fa2f07..9a90f9eb6 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 | |||
| @@ -6,6 +6,7 @@ Files: dist/english_plurals/* | |||
| 6 | dist/icons/controller/*.png | 6 | dist/icons/controller/*.png |
| 7 | dist/icons/overlay/*.png | 7 | dist/icons/overlay/*.png |
| 8 | dist/languages/* | 8 | dist/languages/* |
| 9 | dist/qt_themes/*/icons/48x48/sd_card.png | ||
| 9 | dist/qt_themes/*/icons/index.theme | 10 | dist/qt_themes/*/icons/index.theme |
| 10 | dist/qt_themes/default/style.qss | 11 | dist/qt_themes/default/style.qss |
| 11 | Copyright: yuzu Emulator Project | 12 | Copyright: yuzu Emulator Project |
| @@ -51,6 +52,8 @@ Files: dist/qt_themes/colorful/icons/16x16/lock.png | |||
| 51 | dist/qt_themes/colorful/icons/48x48/chip.png | 52 | dist/qt_themes/colorful/icons/48x48/chip.png |
| 52 | dist/qt_themes/colorful/icons/48x48/folder.png | 53 | dist/qt_themes/colorful/icons/48x48/folder.png |
| 53 | dist/qt_themes/colorful_dark/icons/16x16/lock.png | 54 | dist/qt_themes/colorful_dark/icons/16x16/lock.png |
| 55 | dist/qt_themes/colorful/icons/16x16/info.png | ||
| 56 | dist/qt_themes/colorful/icons/16x16/sync.png | ||
| 54 | Copyright: Icons8 | 57 | Copyright: Icons8 |
| 55 | License: MIT | 58 | License: MIT |
| 56 | Comment: https://github.com/icons8/flat-color-icons | 59 | Comment: https://github.com/icons8/flat-color-icons |
| @@ -66,11 +69,9 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png | |||
| 66 | Copyright: Ionic (http://ionic.io/) | 69 | Copyright: Ionic (http://ionic.io/) |
| 67 | License: MIT | 70 | License: MIT |
| 68 | 71 | ||
| 69 | 72 | Files: dist/qt_themes/colorful/icons/48x48/star.png | |
| 70 | Files: dist/qt_themes/*/icons/48x48/sd_card.png | 73 | dist/qt_themes/colorful/icons/16x16/checked.png |
| 71 | dist/qt_themes/colorful/icons/48x48/star.png | 74 | dist/qt_themes/colorful/icons/16x16/failed.png |
| 72 | dist/qt_themes/default/icons/16x16/checked.png | ||
| 73 | dist/qt_themes/default/icons/16x16/failed.png | ||
| 74 | Copyright: SVG Repo | 75 | Copyright: SVG Repo |
| 75 | License: CC0-1.0 | 76 | License: CC0-1.0 |
| 76 | 77 | ||
diff --git a/CMakeLists.txt b/CMakeLists.txt index 2ab0ea589..20dd1383f 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt | |||
| @@ -38,6 +38,8 @@ option(YUZU_USE_BUNDLED_OPUS "Compile bundled opus" ON) | |||
| 38 | 38 | ||
| 39 | option(YUZU_TESTS "Compile tests" ON) | 39 | option(YUZU_TESTS "Compile tests" ON) |
| 40 | 40 | ||
| 41 | CMAKE_DEPENDENT_OPTION(YUZU_CRASH_DUMPS "Compile Windows crash dump (Minidump) support" OFF "WIN32" OFF) | ||
| 42 | |||
| 41 | option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") | 43 | option(YUZU_USE_BUNDLED_VCPKG "Use vcpkg for yuzu dependencies" "${MSVC}") |
| 42 | 44 | ||
| 43 | option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON) | 45 | option(YUZU_CHECK_SUBMODULES "Check if submodules are present" ON) |
| @@ -46,6 +48,9 @@ if (YUZU_USE_BUNDLED_VCPKG) | |||
| 46 | if (YUZU_TESTS) | 48 | if (YUZU_TESTS) |
| 47 | list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") | 49 | list(APPEND VCPKG_MANIFEST_FEATURES "yuzu-tests") |
| 48 | endif() | 50 | endif() |
| 51 | if (YUZU_CRASH_DUMPS) | ||
| 52 | list(APPEND VCPKG_MANIFEST_FEATURES "dbghelp") | ||
| 53 | endif() | ||
| 49 | 54 | ||
| 50 | include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake) | 55 | include(${CMAKE_SOURCE_DIR}/externals/vcpkg/scripts/buildsystems/vcpkg.cmake) |
| 51 | elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "") | 56 | elseif(NOT "$ENV{VCPKG_TOOLCHAIN_FILE}" STREQUAL "") |
| @@ -447,6 +452,13 @@ elseif (WIN32) | |||
| 447 | # PSAPI is the Process Status API | 452 | # PSAPI is the Process Status API |
| 448 | set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) | 453 | set(PLATFORM_LIBRARIES ${PLATFORM_LIBRARIES} psapi imm32 version) |
| 449 | endif() | 454 | endif() |
| 455 | |||
| 456 | if (YUZU_CRASH_DUMPS) | ||
| 457 | find_library(DBGHELP_LIBRARY dbghelp) | ||
| 458 | if ("${DBGHELP_LIBRARY}" STREQUAL "DBGHELP_LIBRARY-NOTFOUND") | ||
| 459 | message(FATAL_ERROR "YUZU_CRASH_DUMPS enabled but dbghelp library not found") | ||
| 460 | endif() | ||
| 461 | endif() | ||
| 450 | elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") | 462 | elseif (CMAKE_SYSTEM_NAME MATCHES "^(Linux|kFreeBSD|GNU|SunOS)$") |
| 451 | set(PLATFORM_LIBRARIES rt) | 463 | set(PLATFORM_LIBRARIES rt) |
| 452 | endif() | 464 | endif() |
diff --git a/dist/qt_themes/default/icons/16x16/checked.png b/dist/qt_themes/colorful/icons/16x16/checked.png index b9e64e9e0..b9e64e9e0 100644 --- a/dist/qt_themes/default/icons/16x16/checked.png +++ b/dist/qt_themes/colorful/icons/16x16/checked.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/default/icons/16x16/failed.png b/dist/qt_themes/colorful/icons/16x16/failed.png index a1872835d..a1872835d 100644 --- a/dist/qt_themes/default/icons/16x16/failed.png +++ b/dist/qt_themes/colorful/icons/16x16/failed.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/colorful/icons/16x16/info.png b/dist/qt_themes/colorful/icons/16x16/info.png new file mode 100644 index 000000000..8b9330f4c --- /dev/null +++ b/dist/qt_themes/colorful/icons/16x16/info.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/colorful/icons/16x16/sync.png b/dist/qt_themes/colorful/icons/16x16/sync.png new file mode 100644 index 000000000..0d57789c3 --- /dev/null +++ b/dist/qt_themes/colorful/icons/16x16/sync.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/default/icons/16x16/view-refresh.png b/dist/qt_themes/colorful/icons/16x16/view-refresh.png index 69f9474ac..69f9474ac 100644 --- a/dist/qt_themes/default/icons/16x16/view-refresh.png +++ b/dist/qt_themes/colorful/icons/16x16/view-refresh.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/default/icons/48x48/no_avatar.png b/dist/qt_themes/colorful/icons/48x48/no_avatar.png index 76f812349..76f812349 100644 --- a/dist/qt_themes/default/icons/48x48/no_avatar.png +++ b/dist/qt_themes/colorful/icons/48x48/no_avatar.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/colorful/icons/48x48/sd_card.png b/dist/qt_themes/colorful/icons/48x48/sd_card.png index 47e491d32..652d61bc3 100644 --- a/dist/qt_themes/colorful/icons/48x48/sd_card.png +++ b/dist/qt_themes/colorful/icons/48x48/sd_card.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/colorful/icons/index.theme b/dist/qt_themes/colorful/icons/index.theme index b452aca16..6eb3c6949 100644 --- a/dist/qt_themes/colorful/icons/index.theme +++ b/dist/qt_themes/colorful/icons/index.theme | |||
| @@ -1,7 +1,6 @@ | |||
| 1 | [Icon Theme] | 1 | [Icon Theme] |
| 2 | Name=colorful | 2 | Name=colorful |
| 3 | Comment=Colorful theme | 3 | Comment=Colorful theme |
| 4 | Inherits=default | ||
| 5 | Directories=16x16,48x48,256x256 | 4 | Directories=16x16,48x48,256x256 |
| 6 | 5 | ||
| 7 | [16x16] | 6 | [16x16] |
diff --git a/dist/qt_themes/colorful/style.qrc b/dist/qt_themes/colorful/style.qrc index 507e0e58b..82cd367be 100644 --- a/dist/qt_themes/colorful/style.qrc +++ b/dist/qt_themes/colorful/style.qrc | |||
| @@ -6,14 +6,20 @@ SPDX-License-Identifier: GPL-2.0-or-later | |||
| 6 | <RCC> | 6 | <RCC> |
| 7 | <qresource prefix="icons/colorful"> | 7 | <qresource prefix="icons/colorful"> |
| 8 | <file alias="index.theme">icons/index.theme</file> | 8 | <file alias="index.theme">icons/index.theme</file> |
| 9 | <file alias="16x16/checked.png">icons/16x16/checked.png</file> | ||
| 9 | <file alias="16x16/connected.png">icons/16x16/connected.png</file> | 10 | <file alias="16x16/connected.png">icons/16x16/connected.png</file> |
| 10 | <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file> | 11 | <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file> |
| 11 | <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file> | 12 | <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file> |
| 13 | <file alias="16x16/failed.png">icons/16x16/failed.png</file> | ||
| 14 | <file alias="16x16/info.png">icons/16x16/info.png</file> | ||
| 12 | <file alias="16x16/lock.png">icons/16x16/lock.png</file> | 15 | <file alias="16x16/lock.png">icons/16x16/lock.png</file> |
| 16 | <file alias="16x16/sync.png">icons/16x16/sync.png</file> | ||
| 17 | <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file> | ||
| 13 | <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> | 18 | <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> |
| 14 | <file alias="48x48/chip.png">icons/48x48/chip.png</file> | 19 | <file alias="48x48/chip.png">icons/48x48/chip.png</file> |
| 15 | <file alias="48x48/folder.png">icons/48x48/folder.png</file> | 20 | <file alias="48x48/folder.png">icons/48x48/folder.png</file> |
| 16 | <file alias="48x48/list-add.png">icons/48x48/list-add.png</file> | 21 | <file alias="48x48/list-add.png">icons/48x48/list-add.png</file> |
| 22 | <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file> | ||
| 17 | <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> | 23 | <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> |
| 18 | <file alias="48x48/star.png">icons/48x48/star.png</file> | 24 | <file alias="48x48/star.png">icons/48x48/star.png</file> |
| 19 | <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> | 25 | <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> |
diff --git a/dist/qt_themes/colorful_dark/style.qrc b/dist/qt_themes/colorful_dark/style.qrc index 9853fd438..72451ef02 100644 --- a/dist/qt_themes/colorful_dark/style.qrc +++ b/dist/qt_themes/colorful_dark/style.qrc | |||
| @@ -5,19 +5,9 @@ SPDX-License-Identifier: GPL-2.0-or-later | |||
| 5 | 5 | ||
| 6 | <RCC> | 6 | <RCC> |
| 7 | <qresource prefix="icons/colorful_dark"> | 7 | <qresource prefix="icons/colorful_dark"> |
| 8 | <file alias="16x16/connected.png">../colorful/icons/16x16/connected.png</file> | ||
| 9 | <file alias="16x16/connected_notification.png">../colorful/icons/16x16/connected_notification.png</file> | ||
| 10 | <file alias="16x16/disconnected.png">../colorful/icons/16x16/disconnected.png</file> | ||
| 11 | <file alias="index.theme">icons/index.theme</file> | 8 | <file alias="index.theme">icons/index.theme</file> |
| 12 | <file alias="16x16/lock.png">icons/16x16/lock.png</file> | 9 | <file alias="16x16/lock.png">icons/16x16/lock.png</file> |
| 13 | <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file> | 10 | <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file> |
| 14 | <file alias="48x48/bad_folder.png">../colorful/icons/48x48/bad_folder.png</file> | ||
| 15 | <file alias="48x48/chip.png">../colorful/icons/48x48/chip.png</file> | ||
| 16 | <file alias="48x48/folder.png">../colorful/icons/48x48/folder.png</file> | ||
| 17 | <file alias="48x48/no_avatar.png">../qdarkstyle/icons/48x48/no_avatar.png</file> | ||
| 18 | <file alias="48x48/list-add.png">../colorful/icons/48x48/list-add.png</file> | ||
| 19 | <file alias="48x48/sd_card.png">../colorful/icons/48x48/sd_card.png</file> | ||
| 20 | <file alias="256x256/plus_folder.png">../colorful/icons/256x256/plus_folder.png</file> | ||
| 21 | </qresource> | 11 | </qresource> |
| 22 | 12 | ||
| 23 | <qresource prefix="qss_icons"> | 13 | <qresource prefix="qss_icons"> |
diff --git a/dist/qt_themes/default/default.qrc b/dist/qt_themes/default/default.qrc index a07f2a9c1..2e01a3434 100644 --- a/dist/qt_themes/default/default.qrc +++ b/dist/qt_themes/default/default.qrc | |||
| @@ -5,23 +5,20 @@ SPDX-License-Identifier: GPL-2.0-or-later | |||
| 5 | 5 | ||
| 6 | <RCC> | 6 | <RCC> |
| 7 | <qresource prefix="icons/default"> | 7 | <qresource prefix="icons/default"> |
| 8 | <!-- "colorful" is now the default theme, add new icons there --> | ||
| 8 | <file alias="index.theme">icons/index.theme</file> | 9 | <file alias="index.theme">icons/index.theme</file> |
| 9 | <file alias="16x16/checked.png">icons/16x16/checked.png</file> | ||
| 10 | <file alias="16x16/failed.png">icons/16x16/failed.png</file> | ||
| 11 | <file alias="16x16/lock.png">icons/16x16/lock.png</file> | ||
| 12 | <file alias="16x16/connected.png">icons/16x16/connected.png</file> | 10 | <file alias="16x16/connected.png">icons/16x16/connected.png</file> |
| 13 | <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file> | ||
| 14 | <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file> | 11 | <file alias="16x16/connected_notification.png">icons/16x16/connected_notification.png</file> |
| 15 | <file alias="16x16/view-refresh.png">icons/16x16/view-refresh.png</file> | 12 | <file alias="16x16/disconnected.png">icons/16x16/disconnected.png</file> |
| 13 | <file alias="16x16/lock.png">icons/16x16/lock.png</file> | ||
| 16 | <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> | 14 | <file alias="48x48/bad_folder.png">icons/48x48/bad_folder.png</file> |
| 17 | <file alias="48x48/chip.png">icons/48x48/chip.png</file> | 15 | <file alias="48x48/chip.png">icons/48x48/chip.png</file> |
| 18 | <file alias="48x48/folder.png">icons/48x48/folder.png</file> | 16 | <file alias="48x48/folder.png">icons/48x48/folder.png</file> |
| 19 | <file alias="48x48/no_avatar.png">icons/48x48/no_avatar.png</file> | ||
| 20 | <file alias="48x48/list-add.png">icons/48x48/list-add.png</file> | 17 | <file alias="48x48/list-add.png">icons/48x48/list-add.png</file> |
| 21 | <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> | 18 | <file alias="48x48/sd_card.png">icons/48x48/sd_card.png</file> |
| 22 | <file alias="48x48/star.png">icons/48x48/star.png</file> | 19 | <file alias="48x48/star.png">icons/48x48/star.png</file> |
| 23 | <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> | ||
| 24 | <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> | 20 | <file alias="256x256/plus_folder.png">icons/256x256/plus_folder.png</file> |
| 21 | <file alias="256x256/yuzu.png">icons/256x256/yuzu.png</file> | ||
| 25 | </qresource> | 22 | </qresource> |
| 26 | <qresource prefix="default"> | 23 | <qresource prefix="default"> |
| 27 | <file>style.qss</file> | 24 | <file>style.qss</file> |
diff --git a/dist/qt_themes/default/icons/48x48/sd_card.png b/dist/qt_themes/default/icons/48x48/sd_card.png index 60dfba269..6bcb7f6b1 100644 --- a/dist/qt_themes/default/icons/48x48/sd_card.png +++ b/dist/qt_themes/default/icons/48x48/sd_card.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/default/icons/index.theme b/dist/qt_themes/default/icons/index.theme index 1edbe6408..21b35e3e3 100644 --- a/dist/qt_themes/default/icons/index.theme +++ b/dist/qt_themes/default/icons/index.theme | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | [Icon Theme] | 1 | [Icon Theme] |
| 2 | Name=default | 2 | Name=default |
| 3 | Comment=default theme | 3 | Comment=default theme |
| 4 | Inherits=colorful | ||
| 4 | Directories=16x16,48x48,256x256 | 5 | Directories=16x16,48x48,256x256 |
| 5 | 6 | ||
| 6 | [16x16] | 7 | [16x16] |
| @@ -10,4 +11,4 @@ Size=16 | |||
| 10 | Size=48 | 11 | Size=48 |
| 11 | 12 | ||
| 12 | [256x256] | 13 | [256x256] |
| 13 | Size=256 \ No newline at end of file | 14 | Size=256 |
diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png index 87ae5186d..15e5e4024 100644 --- a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png +++ b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png | |||
| Binary files differ | |||
diff --git a/dist/qt_themes/qdarkstyle/icons/index.theme b/dist/qt_themes/qdarkstyle/icons/index.theme index d1e12f3ef..502717617 100644 --- a/dist/qt_themes/qdarkstyle/icons/index.theme +++ b/dist/qt_themes/qdarkstyle/icons/index.theme | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | [Icon Theme] | 1 | [Icon Theme] |
| 2 | Name=qdarkstyle | 2 | Name=qdarkstyle |
| 3 | Comment=dark theme | 3 | Comment=dark theme |
| 4 | Inherits=default | 4 | Inherits=colorful |
| 5 | Directories=16x16,48x48,256x256 | 5 | Directories=16x16,48x48,256x256 |
| 6 | 6 | ||
| 7 | [16x16] | 7 | [16x16] |
| @@ -11,4 +11,4 @@ Size=16 | |||
| 11 | Size=48 | 11 | Size=48 |
| 12 | 12 | ||
| 13 | [256x256] | 13 | [256x256] |
| 14 | Size=256 \ No newline at end of file | 14 | Size=256 |
diff --git a/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme b/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme index 447a6c8be..20f9f6d63 100644 --- a/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme +++ b/dist/qt_themes/qdarkstyle_midnight_blue/icons/index.theme | |||
| @@ -1,7 +1,7 @@ | |||
| 1 | [Icon Theme] | 1 | [Icon Theme] |
| 2 | Name=qdarkstyle_midnight_blue | 2 | Name=qdarkstyle_midnight_blue |
| 3 | Comment=dark theme | 3 | Comment=dark theme |
| 4 | Inherits=default | 4 | Inherits=colorful |
| 5 | Directories=16x16,48x48,256x256 | 5 | Directories=16x16,48x48,256x256 |
| 6 | 6 | ||
| 7 | [16x16] | 7 | [16x16] |
diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index eea70fc27..e80fd124e 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt | |||
| @@ -16,7 +16,6 @@ endif() | |||
| 16 | 16 | ||
| 17 | # Dynarmic | 17 | # Dynarmic |
| 18 | if (ARCHITECTURE_x86_64) | 18 | if (ARCHITECTURE_x86_64) |
| 19 | set(DYNARMIC_TESTS OFF) | ||
| 20 | set(DYNARMIC_NO_BUNDLED_FMT ON) | 19 | set(DYNARMIC_NO_BUNDLED_FMT ON) |
| 21 | set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE) | 20 | set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE) |
| 22 | add_subdirectory(dynarmic) | 21 | add_subdirectory(dynarmic) |
diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 5fe1d5fa5..144f1bab2 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt | |||
| @@ -194,6 +194,7 @@ add_library(audio_core STATIC | |||
| 194 | sink/sink.h | 194 | sink/sink.h |
| 195 | sink/sink_details.cpp | 195 | sink/sink_details.cpp |
| 196 | sink/sink_details.h | 196 | sink/sink_details.h |
| 197 | sink/sink_stream.cpp | ||
| 197 | sink/sink_stream.h | 198 | sink/sink_stream.h |
| 198 | ) | 199 | ) |
| 199 | 200 | ||
diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 78e615a10..c845330cd 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp | |||
| @@ -47,22 +47,12 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { | |||
| 47 | return *adsp; | 47 | return *adsp; |
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | void AudioCore::PauseSinks(const bool pausing) const { | 50 | void AudioCore::SetNVDECActive(bool active) { |
| 51 | if (pausing) { | 51 | nvdec_active = active; |
| 52 | output_sink->PauseStreams(); | ||
| 53 | input_sink->PauseStreams(); | ||
| 54 | } else { | ||
| 55 | output_sink->UnpauseStreams(); | ||
| 56 | input_sink->UnpauseStreams(); | ||
| 57 | } | ||
| 58 | } | 52 | } |
| 59 | 53 | ||
| 60 | u32 AudioCore::GetStreamQueue() const { | 54 | bool AudioCore::IsNVDECActive() const { |
| 61 | return estimated_queue.load(); | 55 | return nvdec_active; |
| 62 | } | ||
| 63 | |||
| 64 | void AudioCore::SetStreamQueue(u32 size) { | ||
| 65 | estimated_queue.store(size); | ||
| 66 | } | 56 | } |
| 67 | 57 | ||
| 68 | } // namespace AudioCore | 58 | } // namespace AudioCore |
diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index 0f7d61ee4..e33e00a3e 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h | |||
| @@ -17,7 +17,7 @@ namespace AudioCore { | |||
| 17 | 17 | ||
| 18 | class AudioManager; | 18 | class AudioManager; |
| 19 | /** | 19 | /** |
| 20 | * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. | 20 | * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP. |
| 21 | */ | 21 | */ |
| 22 | class AudioCore { | 22 | class AudioCore { |
| 23 | public: | 23 | public: |
| @@ -58,26 +58,16 @@ public: | |||
| 58 | AudioRenderer::ADSP::ADSP& GetADSP(); | 58 | AudioRenderer::ADSP::ADSP& GetADSP(); |
| 59 | 59 | ||
| 60 | /** | 60 | /** |
| 61 | * Pause the sink. Called from the core. | 61 | * Toggle NVDEC state, used to avoid stall in playback. |
| 62 | * | 62 | * |
| 63 | * @param pausing - Is this pause due to an actual pause, or shutdown? | 63 | * @param active - Set true if nvdec is active, otherwise false. |
| 64 | * Unfortunately, shutdown also pauses streams, which can cause issues. | ||
| 65 | */ | 64 | */ |
| 66 | void PauseSinks(bool pausing) const; | 65 | void SetNVDECActive(bool active); |
| 67 | 66 | ||
| 68 | /** | 67 | /** |
| 69 | * Get the size of the current stream queue. | 68 | * Get NVDEC state. |
| 70 | * | ||
| 71 | * @return Current stream queue size. | ||
| 72 | */ | ||
| 73 | u32 GetStreamQueue() const; | ||
| 74 | |||
| 75 | /** | ||
| 76 | * Get the size of the current stream queue. | ||
| 77 | * | ||
| 78 | * @param size - New stream size. | ||
| 79 | */ | 69 | */ |
| 80 | void SetStreamQueue(u32 size); | 70 | bool IsNVDECActive() const; |
| 81 | 71 | ||
| 82 | private: | 72 | private: |
| 83 | /** | 73 | /** |
| @@ -93,8 +83,8 @@ private: | |||
| 93 | std::unique_ptr<Sink::Sink> input_sink; | 83 | std::unique_ptr<Sink::Sink> input_sink; |
| 94 | /// The ADSP in the sysmodule | 84 | /// The ADSP in the sysmodule |
| 95 | std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; | 85 | std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; |
| 96 | /// Current size of the stream queue | 86 | /// Is NVDec currently active? |
| 97 | std::atomic<u32> estimated_queue{0}; | 87 | bool nvdec_active{false}; |
| 98 | }; | 88 | }; |
| 99 | 89 | ||
| 100 | } // namespace AudioCore | 90 | } // namespace AudioCore |
diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h index 82dd32dca..012d2ed70 100644 --- a/src/audio_core/audio_event.h +++ b/src/audio_core/audio_event.h | |||
| @@ -14,7 +14,7 @@ namespace AudioCore { | |||
| 14 | * Responsible for the input/output events, set by the stream backend when buffers are consumed, and | 14 | * Responsible for the input/output events, set by the stream backend when buffers are consumed, and |
| 15 | * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer | 15 | * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer |
| 16 | * recycling going. | 16 | * recycling going. |
| 17 | * In a real Switch this is not a seprate class, and exists entirely within the audio manager. | 17 | * In a real Switch this is not a separate class, and exists entirely within the audio manager. |
| 18 | * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can | 18 | * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can |
| 19 | * wait on multiple events at once, and the events are not needed by the backend. | 19 | * wait on multiple events at once, and the events are not needed by the backend. |
| 20 | */ | 20 | */ |
| @@ -81,7 +81,7 @@ public: | |||
| 81 | void ClearEvents(); | 81 | void ClearEvents(); |
| 82 | 82 | ||
| 83 | private: | 83 | private: |
| 84 | /// Lock, used bythe audio manager | 84 | /// Lock, used by the audio manager |
| 85 | std::mutex event_lock; | 85 | std::mutex event_lock; |
| 86 | /// Array of events, one per system type (see Type), last event is used to terminate | 86 | /// Array of events, one per system type (see Type), last event is used to terminate |
| 87 | std::array<std::atomic<bool>, 4> events_signalled; | 87 | std::array<std::atomic<bool>, 4> events_signalled; |
diff --git a/src/audio_core/audio_in_manager.cpp b/src/audio_core/audio_in_manager.cpp index 4aadb7fd6..f39fb4002 100644 --- a/src/audio_core/audio_in_manager.cpp +++ b/src/audio_core/audio_in_manager.cpp | |||
| @@ -82,7 +82,7 @@ u32 Manager::GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceN | |||
| 82 | 82 | ||
| 83 | auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; | 83 | auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; |
| 84 | if (input_devices.size() > 1) { | 84 | if (input_devices.size() > 1) { |
| 85 | names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); | 85 | names.emplace_back("Uac"); |
| 86 | return 1; | 86 | return 1; |
| 87 | } | 87 | } |
| 88 | return 0; | 88 | return 0; |
diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h index 75b73a0b6..8a519df99 100644 --- a/src/audio_core/audio_in_manager.h +++ b/src/audio_core/audio_in_manager.h | |||
| @@ -59,9 +59,10 @@ public: | |||
| 59 | /** | 59 | /** |
| 60 | * Get a list of audio in device names. | 60 | * Get a list of audio in device names. |
| 61 | * | 61 | * |
| 62 | * @oaram names - Output container to write names to. | 62 | * @param names - Output container to write names to. |
| 63 | * @param max_count - Maximum numebr of deivce names to write. Unused | 63 | * @param max_count - Maximum number of device names to write. Unused |
| 64 | * @param filter - Should the list be filtered? Unused. | 64 | * @param filter - Should the list be filtered? Unused. |
| 65 | * | ||
| 65 | * @return Number of names written. | 66 | * @return Number of names written. |
| 66 | */ | 67 | */ |
| 67 | u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, | 68 | u32 GetDeviceNames(std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names, |
diff --git a/src/audio_core/audio_manager.h b/src/audio_core/audio_manager.h index 70316e9cb..8cbd95e22 100644 --- a/src/audio_core/audio_manager.h +++ b/src/audio_core/audio_manager.h | |||
| @@ -76,7 +76,7 @@ public: | |||
| 76 | 76 | ||
| 77 | private: | 77 | private: |
| 78 | /** | 78 | /** |
| 79 | * Main thread, waiting on a manager signal and calling the registered fucntion. | 79 | * Main thread, waiting on a manager signal and calling the registered function. |
| 80 | */ | 80 | */ |
| 81 | void ThreadFunc(); | 81 | void ThreadFunc(); |
| 82 | 82 | ||
diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp index 71d67de64..1766efde1 100644 --- a/src/audio_core/audio_out_manager.cpp +++ b/src/audio_core/audio_out_manager.cpp | |||
| @@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() { | |||
| 74 | 74 | ||
| 75 | u32 Manager::GetAudioOutDeviceNames( | 75 | u32 Manager::GetAudioOutDeviceNames( |
| 76 | std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { | 76 | std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { |
| 77 | names.push_back({"DeviceOut"}); | 77 | names.emplace_back("DeviceOut"); |
| 78 | return 1; | 78 | return 1; |
| 79 | } | 79 | } |
| 80 | 80 | ||
diff --git a/src/audio_core/audio_render_manager.cpp b/src/audio_core/audio_render_manager.cpp index 7a846835b..7aba2b423 100644 --- a/src/audio_core/audio_render_manager.cpp +++ b/src/audio_core/audio_render_manager.cpp | |||
| @@ -25,8 +25,8 @@ SystemManager& Manager::GetSystemManager() { | |||
| 25 | return *system_manager; | 25 | return *system_manager; |
| 26 | } | 26 | } |
| 27 | 27 | ||
| 28 | auto Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) | 28 | Result Manager::GetWorkBufferSize(const AudioRendererParameterInternal& params, |
| 29 | -> Result { | 29 | u64& out_count) const { |
| 30 | if (!CheckValidRevision(params.revision)) { | 30 | if (!CheckValidRevision(params.revision)) { |
| 31 | return Service::Audio::ERR_INVALID_REVISION; | 31 | return Service::Audio::ERR_INVALID_REVISION; |
| 32 | } | 32 | } |
| @@ -54,7 +54,7 @@ void Manager::ReleaseSessionId(const s32 session_id) { | |||
| 54 | session_ids[--session_count] = session_id; | 54 | session_ids[--session_count] = session_id; |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | u32 Manager::GetSessionCount() { | 57 | u32 Manager::GetSessionCount() const { |
| 58 | std::scoped_lock l{session_lock}; | 58 | std::scoped_lock l{session_lock}; |
| 59 | return session_count; | 59 | return session_count; |
| 60 | } | 60 | } |
diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h index 6a508ec56..bf4837190 100644 --- a/src/audio_core/audio_render_manager.h +++ b/src/audio_core/audio_render_manager.h | |||
| @@ -46,7 +46,7 @@ public: | |||
| 46 | * @param out_count - Output size of the required workbuffer. | 46 | * @param out_count - Output size of the required workbuffer. |
| 47 | * @return Result code. | 47 | * @return Result code. |
| 48 | */ | 48 | */ |
| 49 | Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count); | 49 | Result GetWorkBufferSize(const AudioRendererParameterInternal& params, u64& out_count) const; |
| 50 | 50 | ||
| 51 | /** | 51 | /** |
| 52 | * Get a new session id. | 52 | * Get a new session id. |
| @@ -60,14 +60,14 @@ public: | |||
| 60 | * | 60 | * |
| 61 | * @return The number of active sessions. | 61 | * @return The number of active sessions. |
| 62 | */ | 62 | */ |
| 63 | u32 GetSessionCount(); | 63 | u32 GetSessionCount() const; |
| 64 | 64 | ||
| 65 | /** | 65 | /** |
| 66 | * Add a renderer system to the manager. | 66 | * Add a renderer system to the manager. |
| 67 | * The system will be reguarly called to generate commands for the AudioRenderer. | 67 | * The system will be regularly called to generate commands for the AudioRenderer. |
| 68 | * | 68 | * |
| 69 | * @param system - The system to add. | 69 | * @param system - The system to add. |
| 70 | * @return True if the system was sucessfully added, otherwise false. | 70 | * @return True if the system was successfully added, otherwise false. |
| 71 | */ | 71 | */ |
| 72 | bool AddSystem(System& system); | 72 | bool AddSystem(System& system); |
| 73 | 73 | ||
| @@ -75,7 +75,7 @@ public: | |||
| 75 | * Remove a renderer system from the manager. | 75 | * Remove a renderer system from the manager. |
| 76 | * | 76 | * |
| 77 | * @param system - The system to remove. | 77 | * @param system - The system to remove. |
| 78 | * @return True if the system was sucessfully removed, otherwise false. | 78 | * @return True if the system was successfully removed, otherwise false. |
| 79 | */ | 79 | */ |
| 80 | bool RemoveSystem(System& system); | 80 | bool RemoveSystem(System& system); |
| 81 | 81 | ||
| @@ -94,7 +94,7 @@ private: | |||
| 94 | /// Number of active renderers | 94 | /// Number of active renderers |
| 95 | u32 session_count{}; | 95 | u32 session_count{}; |
| 96 | /// Lock for interacting with the sessions | 96 | /// Lock for interacting with the sessions |
| 97 | std::mutex session_lock{}; | 97 | mutable std::mutex session_lock{}; |
| 98 | /// Regularly generates commands from the registered systems for the AudioRenderer | 98 | /// Regularly generates commands from the registered systems for the AudioRenderer |
| 99 | std::unique_ptr<SystemManager> system_manager{}; | 99 | std::unique_ptr<SystemManager> system_manager{}; |
| 100 | }; | 100 | }; |
diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h index cae7fa970..7128ef72a 100644 --- a/src/audio_core/device/audio_buffer.h +++ b/src/audio_core/device/audio_buffer.h | |||
| @@ -8,6 +8,10 @@ | |||
| 8 | namespace AudioCore { | 8 | namespace AudioCore { |
| 9 | 9 | ||
| 10 | struct AudioBuffer { | 10 | struct AudioBuffer { |
| 11 | /// Timestamp this buffer started playing. | ||
| 12 | u64 start_timestamp; | ||
| 13 | /// Timestamp this buffer should finish playing. | ||
| 14 | u64 end_timestamp; | ||
| 11 | /// Timestamp this buffer completed playing. | 15 | /// Timestamp this buffer completed playing. |
| 12 | s64 played_timestamp; | 16 | s64 played_timestamp; |
| 13 | /// Game memory address for these samples. | 17 | /// Game memory address for these samples. |
diff --git a/src/audio_core/device/audio_buffers.h b/src/audio_core/device/audio_buffers.h index 5d1979ea0..3dae1a3b7 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h | |||
| @@ -36,7 +36,7 @@ public: | |||
| 36 | * | 36 | * |
| 37 | * @param buffer - The new buffer. | 37 | * @param buffer - The new buffer. |
| 38 | */ | 38 | */ |
| 39 | void AppendBuffer(AudioBuffer& buffer) { | 39 | void AppendBuffer(const AudioBuffer& buffer) { |
| 40 | std::scoped_lock l{lock}; | 40 | std::scoped_lock l{lock}; |
| 41 | buffers[appended_index] = buffer; | 41 | buffers[appended_index] = buffer; |
| 42 | appended_count++; | 42 | appended_count++; |
| @@ -58,6 +58,7 @@ public: | |||
| 58 | if (index < 0) { | 58 | if (index < 0) { |
| 59 | index += N; | 59 | index += N; |
| 60 | } | 60 | } |
| 61 | |||
| 61 | out_buffers.push_back(buffers[index]); | 62 | out_buffers.push_back(buffers[index]); |
| 62 | registered_count++; | 63 | registered_count++; |
| 63 | registered_index = (registered_index + 1) % append_limit; | 64 | registered_index = (registered_index + 1) % append_limit; |
| @@ -87,10 +88,12 @@ public: | |||
| 87 | /** | 88 | /** |
| 88 | * Release all registered buffers. | 89 | * Release all registered buffers. |
| 89 | * | 90 | * |
| 90 | * @param timestamp - The released timestamp for this buffer. | 91 | * @param core_timing - The CoreTiming instance |
| 92 | * @param session - The device session | ||
| 93 | * | ||
| 91 | * @return Is the buffer was released. | 94 | * @return Is the buffer was released. |
| 92 | */ | 95 | */ |
| 93 | bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { | 96 | bool ReleaseBuffers(const Core::Timing::CoreTiming& core_timing, const DeviceSession& session) { |
| 94 | std::scoped_lock l{lock}; | 97 | std::scoped_lock l{lock}; |
| 95 | bool buffer_released{false}; | 98 | bool buffer_released{false}; |
| 96 | while (registered_count > 0) { | 99 | while (registered_count > 0) { |
| @@ -100,7 +103,7 @@ public: | |||
| 100 | } | 103 | } |
| 101 | 104 | ||
| 102 | // Check with the backend if this buffer can be released yet. | 105 | // Check with the backend if this buffer can be released yet. |
| 103 | if (!session.IsBufferConsumed(buffers[index].tag)) { | 106 | if (!session.IsBufferConsumed(buffers[index])) { |
| 104 | break; | 107 | break; |
| 105 | } | 108 | } |
| 106 | 109 | ||
| @@ -280,6 +283,16 @@ public: | |||
| 280 | return true; | 283 | return true; |
| 281 | } | 284 | } |
| 282 | 285 | ||
| 286 | u64 GetNextTimestamp() const { | ||
| 287 | // Iterate backwards through the buffer queue, and take the most recent buffer's end | ||
| 288 | std::scoped_lock l{lock}; | ||
| 289 | auto index{appended_index - 1}; | ||
| 290 | if (index < 0) { | ||
| 291 | index += append_limit; | ||
| 292 | } | ||
| 293 | return buffers[index].end_timestamp; | ||
| 294 | } | ||
| 295 | |||
| 283 | private: | 296 | private: |
| 284 | /// Buffer lock | 297 | /// Buffer lock |
| 285 | mutable std::recursive_mutex lock{}; | 298 | mutable std::recursive_mutex lock{}; |
diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp index 095fc96ce..995060414 100644 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp | |||
| @@ -7,11 +7,20 @@ | |||
| 7 | #include "audio_core/device/device_session.h" | 7 | #include "audio_core/device/device_session.h" |
| 8 | #include "audio_core/sink/sink_stream.h" | 8 | #include "audio_core/sink/sink_stream.h" |
| 9 | #include "core/core.h" | 9 | #include "core/core.h" |
| 10 | #include "core/core_timing.h" | ||
| 10 | #include "core/memory.h" | 11 | #include "core/memory.h" |
| 11 | 12 | ||
| 12 | namespace AudioCore { | 13 | namespace AudioCore { |
| 13 | 14 | ||
| 14 | DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} | 15 | using namespace std::literals; |
| 16 | constexpr auto INCREMENT_TIME{5ms}; | ||
| 17 | |||
| 18 | DeviceSession::DeviceSession(Core::System& system_) | ||
| 19 | : system{system_}, thread_event{Core::Timing::CreateEvent( | ||
| 20 | "AudioOutSampleTick", | ||
| 21 | [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { | ||
| 22 | return ThreadFunc(); | ||
| 23 | })} {} | ||
| 15 | 24 | ||
| 16 | DeviceSession::~DeviceSession() { | 25 | DeviceSession::~DeviceSession() { |
| 17 | Finalize(); | 26 | Finalize(); |
| @@ -50,25 +59,26 @@ void DeviceSession::Finalize() { | |||
| 50 | } | 59 | } |
| 51 | 60 | ||
| 52 | void DeviceSession::Start() { | 61 | void DeviceSession::Start() { |
| 53 | stream->SetPlayedSampleCount(played_sample_count); | 62 | if (stream) { |
| 54 | stream->Start(); | 63 | stream->Start(); |
| 64 | system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME, | ||
| 65 | thread_event); | ||
| 66 | } | ||
| 55 | } | 67 | } |
| 56 | 68 | ||
| 57 | void DeviceSession::Stop() { | 69 | void DeviceSession::Stop() { |
| 58 | if (stream) { | 70 | if (stream) { |
| 59 | played_sample_count = stream->GetPlayedSampleCount(); | ||
| 60 | stream->Stop(); | 71 | stream->Stop(); |
| 72 | system.CoreTiming().UnscheduleEvent(thread_event, {}); | ||
| 61 | } | 73 | } |
| 62 | } | 74 | } |
| 63 | 75 | ||
| 64 | void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | 76 | void DeviceSession::AppendBuffers(std::span<const AudioBuffer> buffers) const { |
| 65 | auto& memory{system.Memory()}; | 77 | for (const auto& buffer : buffers) { |
| 66 | |||
| 67 | for (size_t i = 0; i < buffers.size(); i++) { | ||
| 68 | Sink::SinkBuffer new_buffer{ | 78 | Sink::SinkBuffer new_buffer{ |
| 69 | .frames = buffers[i].size / (channel_count * sizeof(s16)), | 79 | .frames = buffer.size / (channel_count * sizeof(s16)), |
| 70 | .frames_played = 0, | 80 | .frames_played = 0, |
| 71 | .tag = buffers[i].tag, | 81 | .tag = buffer.tag, |
| 72 | .consumed = false, | 82 | .consumed = false, |
| 73 | }; | 83 | }; |
| 74 | 84 | ||
| @@ -76,26 +86,22 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | |||
| 76 | std::vector<s16> samples{}; | 86 | std::vector<s16> samples{}; |
| 77 | stream->AppendBuffer(new_buffer, samples); | 87 | stream->AppendBuffer(new_buffer, samples); |
| 78 | } else { | 88 | } else { |
| 79 | std::vector<s16> samples(buffers[i].size / sizeof(s16)); | 89 | std::vector<s16> samples(buffer.size / sizeof(s16)); |
| 80 | memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); | 90 | system.Memory().ReadBlockUnsafe(buffer.samples, samples.data(), buffer.size); |
| 81 | stream->AppendBuffer(new_buffer, samples); | 91 | stream->AppendBuffer(new_buffer, samples); |
| 82 | } | 92 | } |
| 83 | } | 93 | } |
| 84 | } | 94 | } |
| 85 | 95 | ||
| 86 | void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { | 96 | void DeviceSession::ReleaseBuffer(const AudioBuffer& buffer) const { |
| 87 | if (type == Sink::StreamType::In) { | 97 | if (type == Sink::StreamType::In) { |
| 88 | auto& memory{system.Memory()}; | ||
| 89 | auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; | 98 | auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; |
| 90 | memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); | 99 | system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); |
| 91 | } | 100 | } |
| 92 | } | 101 | } |
| 93 | 102 | ||
| 94 | bool DeviceSession::IsBufferConsumed(u64 tag) const { | 103 | bool DeviceSession::IsBufferConsumed(const AudioBuffer& buffer) const { |
| 95 | if (stream) { | 104 | return played_sample_count >= buffer.end_timestamp; |
| 96 | return stream->IsBufferConsumed(tag); | ||
| 97 | } | ||
| 98 | return true; | ||
| 99 | } | 105 | } |
| 100 | 106 | ||
| 101 | void DeviceSession::SetVolume(f32 volume) const { | 107 | void DeviceSession::SetVolume(f32 volume) const { |
| @@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const { | |||
| 105 | } | 111 | } |
| 106 | 112 | ||
| 107 | u64 DeviceSession::GetPlayedSampleCount() const { | 113 | u64 DeviceSession::GetPlayedSampleCount() const { |
| 108 | if (stream) { | 114 | return played_sample_count; |
| 109 | return stream->GetPlayedSampleCount(); | 115 | } |
| 116 | |||
| 117 | std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() { | ||
| 118 | // Add 5ms of samples at a 48K sample rate. | ||
| 119 | played_sample_count += 48'000 * INCREMENT_TIME / 1s; | ||
| 120 | if (type == Sink::StreamType::Out) { | ||
| 121 | system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true); | ||
| 122 | } else { | ||
| 123 | system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true); | ||
| 110 | } | 124 | } |
| 111 | return 0; | 125 | return std::nullopt; |
| 126 | } | ||
| 127 | |||
| 128 | void DeviceSession::SetRingSize(u32 ring_size) { | ||
| 129 | stream->SetRingSize(ring_size); | ||
| 112 | } | 130 | } |
| 113 | 131 | ||
| 114 | } // namespace AudioCore | 132 | } // namespace AudioCore |
diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h index 4a031b765..74f4dc085 100644 --- a/src/audio_core/device/device_session.h +++ b/src/audio_core/device/device_session.h | |||
| @@ -3,6 +3,9 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <chrono> | ||
| 7 | #include <memory> | ||
| 8 | #include <optional> | ||
| 6 | #include <span> | 9 | #include <span> |
| 7 | 10 | ||
| 8 | #include "audio_core/common/common.h" | 11 | #include "audio_core/common/common.h" |
| @@ -11,9 +14,13 @@ | |||
| 11 | 14 | ||
| 12 | namespace Core { | 15 | namespace Core { |
| 13 | class System; | 16 | class System; |
| 14 | } | 17 | namespace Timing { |
| 18 | struct EventType; | ||
| 19 | } // namespace Timing | ||
| 20 | } // namespace Core | ||
| 15 | 21 | ||
| 16 | namespace AudioCore { | 22 | namespace AudioCore { |
| 23 | |||
| 17 | namespace Sink { | 24 | namespace Sink { |
| 18 | class SinkStream; | 25 | class SinkStream; |
| 19 | struct SinkBuffer; | 26 | struct SinkBuffer; |
| @@ -55,22 +62,23 @@ public: | |||
| 55 | * | 62 | * |
| 56 | * @param buffers - The buffers to play. | 63 | * @param buffers - The buffers to play. |
| 57 | */ | 64 | */ |
| 58 | void AppendBuffers(std::span<AudioBuffer> buffers) const; | 65 | void AppendBuffers(std::span<const AudioBuffer> buffers) const; |
| 59 | 66 | ||
| 60 | /** | 67 | /** |
| 61 | * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. | 68 | * (Audio In only) Pop samples from the backend, and write them back to this buffer's address. |
| 62 | * | 69 | * |
| 63 | * @param buffer - The buffer to write to. | 70 | * @param buffer - The buffer to write to. |
| 64 | */ | 71 | */ |
| 65 | void ReleaseBuffer(AudioBuffer& buffer) const; | 72 | void ReleaseBuffer(const AudioBuffer& buffer) const; |
| 66 | 73 | ||
| 67 | /** | 74 | /** |
| 68 | * Check if the buffer for the given tag has been consumed by the backend. | 75 | * Check if the buffer for the given tag has been consumed by the backend. |
| 69 | * | 76 | * |
| 70 | * @param tag - Unqiue tag of the buffer to check. | 77 | * @param buffer - the buffer to check. |
| 78 | * | ||
| 71 | * @return true if the buffer has been consumed, otherwise false. | 79 | * @return true if the buffer has been consumed, otherwise false. |
| 72 | */ | 80 | */ |
| 73 | bool IsBufferConsumed(u64 tag) const; | 81 | bool IsBufferConsumed(const AudioBuffer& buffer) const; |
| 74 | 82 | ||
| 75 | /** | 83 | /** |
| 76 | * Start this device session, starting the backend stream. | 84 | * Start this device session, starting the backend stream. |
| @@ -96,6 +104,16 @@ public: | |||
| 96 | */ | 104 | */ |
| 97 | u64 GetPlayedSampleCount() const; | 105 | u64 GetPlayedSampleCount() const; |
| 98 | 106 | ||
| 107 | /* | ||
| 108 | * CoreTiming callback to increment played_sample_count over time. | ||
| 109 | */ | ||
| 110 | std::optional<std::chrono::nanoseconds> ThreadFunc(); | ||
| 111 | |||
| 112 | /* | ||
| 113 | * Set the size of the ring buffer. | ||
| 114 | */ | ||
| 115 | void SetRingSize(u32 ring_size); | ||
| 116 | |||
| 99 | private: | 117 | private: |
| 100 | /// System | 118 | /// System |
| 101 | Core::System& system; | 119 | Core::System& system; |
| @@ -118,9 +136,13 @@ private: | |||
| 118 | /// Applet resource user id of this device session | 136 | /// Applet resource user id of this device session |
| 119 | u64 applet_resource_user_id{}; | 137 | u64 applet_resource_user_id{}; |
| 120 | /// Total number of samples played by this device session | 138 | /// Total number of samples played by this device session |
| 121 | u64 played_sample_count{}; | 139 | std::atomic<u64> played_sample_count{}; |
| 140 | /// Event increasing the played sample count every 5ms | ||
| 141 | std::shared_ptr<Core::Timing::EventType> thread_event; | ||
| 122 | /// Is this session initialised? | 142 | /// Is this session initialised? |
| 123 | bool initialized{}; | 143 | bool initialized{}; |
| 144 | /// Buffer queue | ||
| 145 | std::vector<AudioBuffer> buffer_queue{}; | ||
| 124 | }; | 146 | }; |
| 125 | 147 | ||
| 126 | } // namespace AudioCore | 148 | } // namespace AudioCore |
diff --git a/src/audio_core/in/audio_in.cpp b/src/audio_core/in/audio_in.cpp index c946895d6..91ccd5ad7 100644 --- a/src/audio_core/in/audio_in.cpp +++ b/src/audio_core/in/audio_in.cpp | |||
| @@ -72,7 +72,7 @@ Kernel::KReadableEvent& In::GetBufferEvent() { | |||
| 72 | return event->GetReadableEvent(); | 72 | return event->GetReadableEvent(); |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | f32 In::GetVolume() { | 75 | f32 In::GetVolume() const { |
| 76 | std::scoped_lock l{parent_mutex}; | 76 | std::scoped_lock l{parent_mutex}; |
| 77 | return system.GetVolume(); | 77 | return system.GetVolume(); |
| 78 | } | 78 | } |
| @@ -82,17 +82,17 @@ void In::SetVolume(f32 volume) { | |||
| 82 | system.SetVolume(volume); | 82 | system.SetVolume(volume); |
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | bool In::ContainsAudioBuffer(u64 tag) { | 85 | bool In::ContainsAudioBuffer(u64 tag) const { |
| 86 | std::scoped_lock l{parent_mutex}; | 86 | std::scoped_lock l{parent_mutex}; |
| 87 | return system.ContainsAudioBuffer(tag); | 87 | return system.ContainsAudioBuffer(tag); |
| 88 | } | 88 | } |
| 89 | 89 | ||
| 90 | u32 In::GetBufferCount() { | 90 | u32 In::GetBufferCount() const { |
| 91 | std::scoped_lock l{parent_mutex}; | 91 | std::scoped_lock l{parent_mutex}; |
| 92 | return system.GetBufferCount(); | 92 | return system.GetBufferCount(); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | u64 In::GetPlayedSampleCount() { | 95 | u64 In::GetPlayedSampleCount() const { |
| 96 | std::scoped_lock l{parent_mutex}; | 96 | std::scoped_lock l{parent_mutex}; |
| 97 | return system.GetPlayedSampleCount(); | 97 | return system.GetPlayedSampleCount(); |
| 98 | } | 98 | } |
diff --git a/src/audio_core/in/audio_in.h b/src/audio_core/in/audio_in.h index 6253891d5..092ab7236 100644 --- a/src/audio_core/in/audio_in.h +++ b/src/audio_core/in/audio_in.h | |||
| @@ -102,7 +102,7 @@ public: | |||
| 102 | * | 102 | * |
| 103 | * @return The current volume. | 103 | * @return The current volume. |
| 104 | */ | 104 | */ |
| 105 | f32 GetVolume(); | 105 | f32 GetVolume() const; |
| 106 | 106 | ||
| 107 | /** | 107 | /** |
| 108 | * Set the system volume. | 108 | * Set the system volume. |
| @@ -117,21 +117,21 @@ public: | |||
| 117 | * @param tag - The tag to search for. | 117 | * @param tag - The tag to search for. |
| 118 | * @return True if the buffer is in the system, otherwise false. | 118 | * @return True if the buffer is in the system, otherwise false. |
| 119 | */ | 119 | */ |
| 120 | bool ContainsAudioBuffer(u64 tag); | 120 | bool ContainsAudioBuffer(u64 tag) const; |
| 121 | 121 | ||
| 122 | /** | 122 | /** |
| 123 | * Get the maximum number of buffers. | 123 | * Get the maximum number of buffers. |
| 124 | * | 124 | * |
| 125 | * @return The maximum number of buffers. | 125 | * @return The maximum number of buffers. |
| 126 | */ | 126 | */ |
| 127 | u32 GetBufferCount(); | 127 | u32 GetBufferCount() const; |
| 128 | 128 | ||
| 129 | /** | 129 | /** |
| 130 | * Get the total played sample count for this audio in. | 130 | * Get the total played sample count for this audio in. |
| 131 | * | 131 | * |
| 132 | * @return The played sample count. | 132 | * @return The played sample count. |
| 133 | */ | 133 | */ |
| 134 | u64 GetPlayedSampleCount(); | 134 | u64 GetPlayedSampleCount() const; |
| 135 | 135 | ||
| 136 | private: | 136 | private: |
| 137 | /// The AudioIn::Manager this audio in is registered with | 137 | /// The AudioIn::Manager this audio in is registered with |
diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index ec5d37ed4..e7f918a47 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp | |||
| @@ -34,16 +34,16 @@ size_t System::GetSessionId() const { | |||
| 34 | return session_id; | 34 | return session_id; |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | std::string_view System::GetDefaultDeviceName() { | 37 | std::string_view System::GetDefaultDeviceName() const { |
| 38 | return "BuiltInHeadset"; | 38 | return "BuiltInHeadset"; |
| 39 | } | 39 | } |
| 40 | 40 | ||
| 41 | std::string_view System::GetDefaultUacDeviceName() { | 41 | std::string_view System::GetDefaultUacDeviceName() const { |
| 42 | return "Uac"; | 42 | return "Uac"; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | Result System::IsConfigValid(const std::string_view device_name, | 45 | Result System::IsConfigValid(const std::string_view device_name, |
| 46 | const AudioInParameter& in_params) { | 46 | const AudioInParameter& in_params) const { |
| 47 | if ((device_name.size() > 0) && | 47 | if ((device_name.size() > 0) && |
| 48 | (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { | 48 | (device_name != GetDefaultDeviceName() && device_name != GetDefaultUacDeviceName())) { |
| 49 | return Service::Audio::ERR_INVALID_DEVICE_NAME; | 49 | return Service::Audio::ERR_INVALID_DEVICE_NAME; |
| @@ -93,6 +93,7 @@ Result System::Start() { | |||
| 93 | std::vector<AudioBuffer> buffers_to_flush{}; | 93 | std::vector<AudioBuffer> buffers_to_flush{}; |
| 94 | buffers.RegisterBuffers(buffers_to_flush); | 94 | buffers.RegisterBuffers(buffers_to_flush); |
| 95 | session->AppendBuffers(buffers_to_flush); | 95 | session->AppendBuffers(buffers_to_flush); |
| 96 | session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); | ||
| 96 | 97 | ||
| 97 | return ResultSuccess; | 98 | return ResultSuccess; |
| 98 | } | 99 | } |
| @@ -112,8 +113,15 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { | |||
| 112 | return false; | 113 | return false; |
| 113 | } | 114 | } |
| 114 | 115 | ||
| 115 | AudioBuffer new_buffer{ | 116 | const auto timestamp{buffers.GetNextTimestamp()}; |
| 116 | .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | 117 | const AudioBuffer new_buffer{ |
| 118 | .start_timestamp = timestamp, | ||
| 119 | .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), | ||
| 120 | .played_timestamp = 0, | ||
| 121 | .samples = buffer.samples, | ||
| 122 | .tag = tag, | ||
| 123 | .size = buffer.size, | ||
| 124 | }; | ||
| 117 | 125 | ||
| 118 | buffers.AppendBuffer(new_buffer); | 126 | buffers.AppendBuffer(new_buffer); |
| 119 | RegisterBuffers(); | 127 | RegisterBuffers(); |
| @@ -194,11 +202,11 @@ void System::SetVolume(const f32 volume_) { | |||
| 194 | session->SetVolume(volume_); | 202 | session->SetVolume(volume_); |
| 195 | } | 203 | } |
| 196 | 204 | ||
| 197 | bool System::ContainsAudioBuffer(const u64 tag) { | 205 | bool System::ContainsAudioBuffer(const u64 tag) const { |
| 198 | return buffers.ContainsBuffer(tag); | 206 | return buffers.ContainsBuffer(tag); |
| 199 | } | 207 | } |
| 200 | 208 | ||
| 201 | u32 System::GetBufferCount() { | 209 | u32 System::GetBufferCount() const { |
| 202 | return buffers.GetAppendedRegisteredCount(); | 210 | return buffers.GetAppendedRegisteredCount(); |
| 203 | } | 211 | } |
| 204 | 212 | ||
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h index 165e35d83..b9dc0e60f 100644 --- a/src/audio_core/in/audio_in_system.h +++ b/src/audio_core/in/audio_in_system.h | |||
| @@ -68,7 +68,7 @@ public: | |||
| 68 | * | 68 | * |
| 69 | * @return The default audio input device name. | 69 | * @return The default audio input device name. |
| 70 | */ | 70 | */ |
| 71 | std::string_view GetDefaultDeviceName(); | 71 | std::string_view GetDefaultDeviceName() const; |
| 72 | 72 | ||
| 73 | /** | 73 | /** |
| 74 | * Get the default USB audio input device name. | 74 | * Get the default USB audio input device name. |
| @@ -77,7 +77,7 @@ public: | |||
| 77 | * | 77 | * |
| 78 | * @return The default USB audio input device name. | 78 | * @return The default USB audio input device name. |
| 79 | */ | 79 | */ |
| 80 | std::string_view GetDefaultUacDeviceName(); | 80 | std::string_view GetDefaultUacDeviceName() const; |
| 81 | 81 | ||
| 82 | /** | 82 | /** |
| 83 | * Is the given initialize config valid? | 83 | * Is the given initialize config valid? |
| @@ -86,7 +86,7 @@ public: | |||
| 86 | * @param in_params - Input parameters, see AudioInParameter. | 86 | * @param in_params - Input parameters, see AudioInParameter. |
| 87 | * @return Result code. | 87 | * @return Result code. |
| 88 | */ | 88 | */ |
| 89 | Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params); | 89 | Result IsConfigValid(std::string_view device_name, const AudioInParameter& in_params) const; |
| 90 | 90 | ||
| 91 | /** | 91 | /** |
| 92 | * Initialize this system. | 92 | * Initialize this system. |
| @@ -208,7 +208,7 @@ public: | |||
| 208 | /** | 208 | /** |
| 209 | * Set this system's current volume. | 209 | * Set this system's current volume. |
| 210 | * | 210 | * |
| 211 | * @param The new volume. | 211 | * @param volume The new volume. |
| 212 | */ | 212 | */ |
| 213 | void SetVolume(f32 volume); | 213 | void SetVolume(f32 volume); |
| 214 | 214 | ||
| @@ -218,14 +218,14 @@ public: | |||
| 218 | * @param tag - Unique tag to search for. | 218 | * @param tag - Unique tag to search for. |
| 219 | * @return True if the buffer is in the system, otherwise false. | 219 | * @return True if the buffer is in the system, otherwise false. |
| 220 | */ | 220 | */ |
| 221 | bool ContainsAudioBuffer(u64 tag); | 221 | bool ContainsAudioBuffer(u64 tag) const; |
| 222 | 222 | ||
| 223 | /** | 223 | /** |
| 224 | * Get the maximum number of usable buffers (default 32). | 224 | * Get the maximum number of usable buffers (default 32). |
| 225 | * | 225 | * |
| 226 | * @return The number of buffers. | 226 | * @return The number of buffers. |
| 227 | */ | 227 | */ |
| 228 | u32 GetBufferCount(); | 228 | u32 GetBufferCount() const; |
| 229 | 229 | ||
| 230 | /** | 230 | /** |
| 231 | * Get the total number of samples played by this system. | 231 | * Get the total number of samples played by this system. |
diff --git a/src/audio_core/out/audio_out.cpp b/src/audio_core/out/audio_out.cpp index 9a8d8a742..d3ee4f0eb 100644 --- a/src/audio_core/out/audio_out.cpp +++ b/src/audio_core/out/audio_out.cpp | |||
| @@ -72,7 +72,7 @@ Kernel::KReadableEvent& Out::GetBufferEvent() { | |||
| 72 | return event->GetReadableEvent(); | 72 | return event->GetReadableEvent(); |
| 73 | } | 73 | } |
| 74 | 74 | ||
| 75 | f32 Out::GetVolume() { | 75 | f32 Out::GetVolume() const { |
| 76 | std::scoped_lock l{parent_mutex}; | 76 | std::scoped_lock l{parent_mutex}; |
| 77 | return system.GetVolume(); | 77 | return system.GetVolume(); |
| 78 | } | 78 | } |
| @@ -82,17 +82,17 @@ void Out::SetVolume(const f32 volume) { | |||
| 82 | system.SetVolume(volume); | 82 | system.SetVolume(volume); |
| 83 | } | 83 | } |
| 84 | 84 | ||
| 85 | bool Out::ContainsAudioBuffer(const u64 tag) { | 85 | bool Out::ContainsAudioBuffer(const u64 tag) const { |
| 86 | std::scoped_lock l{parent_mutex}; | 86 | std::scoped_lock l{parent_mutex}; |
| 87 | return system.ContainsAudioBuffer(tag); | 87 | return system.ContainsAudioBuffer(tag); |
| 88 | } | 88 | } |
| 89 | 89 | ||
| 90 | u32 Out::GetBufferCount() { | 90 | u32 Out::GetBufferCount() const { |
| 91 | std::scoped_lock l{parent_mutex}; | 91 | std::scoped_lock l{parent_mutex}; |
| 92 | return system.GetBufferCount(); | 92 | return system.GetBufferCount(); |
| 93 | } | 93 | } |
| 94 | 94 | ||
| 95 | u64 Out::GetPlayedSampleCount() { | 95 | u64 Out::GetPlayedSampleCount() const { |
| 96 | std::scoped_lock l{parent_mutex}; | 96 | std::scoped_lock l{parent_mutex}; |
| 97 | return system.GetPlayedSampleCount(); | 97 | return system.GetPlayedSampleCount(); |
| 98 | } | 98 | } |
diff --git a/src/audio_core/out/audio_out.h b/src/audio_core/out/audio_out.h index f6b921645..946f345c6 100644 --- a/src/audio_core/out/audio_out.h +++ b/src/audio_core/out/audio_out.h | |||
| @@ -102,7 +102,7 @@ public: | |||
| 102 | * | 102 | * |
| 103 | * @return The current volume. | 103 | * @return The current volume. |
| 104 | */ | 104 | */ |
| 105 | f32 GetVolume(); | 105 | f32 GetVolume() const; |
| 106 | 106 | ||
| 107 | /** | 107 | /** |
| 108 | * Set the system volume. | 108 | * Set the system volume. |
| @@ -117,21 +117,21 @@ public: | |||
| 117 | * @param tag - The tag to search for. | 117 | * @param tag - The tag to search for. |
| 118 | * @return True if the buffer is in the system, otherwise false. | 118 | * @return True if the buffer is in the system, otherwise false. |
| 119 | */ | 119 | */ |
| 120 | bool ContainsAudioBuffer(u64 tag); | 120 | bool ContainsAudioBuffer(u64 tag) const; |
| 121 | 121 | ||
| 122 | /** | 122 | /** |
| 123 | * Get the maximum number of buffers. | 123 | * Get the maximum number of buffers. |
| 124 | * | 124 | * |
| 125 | * @return The maximum number of buffers. | 125 | * @return The maximum number of buffers. |
| 126 | */ | 126 | */ |
| 127 | u32 GetBufferCount(); | 127 | u32 GetBufferCount() const; |
| 128 | 128 | ||
| 129 | /** | 129 | /** |
| 130 | * Get the total played sample count for this audio out. | 130 | * Get the total played sample count for this audio out. |
| 131 | * | 131 | * |
| 132 | * @return The played sample count. | 132 | * @return The played sample count. |
| 133 | */ | 133 | */ |
| 134 | u64 GetPlayedSampleCount(); | 134 | u64 GetPlayedSampleCount() const; |
| 135 | 135 | ||
| 136 | private: | 136 | private: |
| 137 | /// The AudioOut::Manager this audio out is registered with | 137 | /// The AudioOut::Manager this audio out is registered with |
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index 35afddf06..8b907590a 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp | |||
| @@ -27,11 +27,12 @@ void System::Finalize() { | |||
| 27 | buffer_event->GetWritableEvent().Signal(); | 27 | buffer_event->GetWritableEvent().Signal(); |
| 28 | } | 28 | } |
| 29 | 29 | ||
| 30 | std::string_view System::GetDefaultOutputDeviceName() { | 30 | std::string_view System::GetDefaultOutputDeviceName() const { |
| 31 | return "DeviceOut"; | 31 | return "DeviceOut"; |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | Result System::IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) { | 34 | Result System::IsConfigValid(std::string_view device_name, |
| 35 | const AudioOutParameter& in_params) const { | ||
| 35 | if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { | 36 | if ((device_name.size() > 0) && (device_name != GetDefaultOutputDeviceName())) { |
| 36 | return Service::Audio::ERR_INVALID_DEVICE_NAME; | 37 | return Service::Audio::ERR_INVALID_DEVICE_NAME; |
| 37 | } | 38 | } |
| @@ -92,6 +93,7 @@ Result System::Start() { | |||
| 92 | std::vector<AudioBuffer> buffers_to_flush{}; | 93 | std::vector<AudioBuffer> buffers_to_flush{}; |
| 93 | buffers.RegisterBuffers(buffers_to_flush); | 94 | buffers.RegisterBuffers(buffers_to_flush); |
| 94 | session->AppendBuffers(buffers_to_flush); | 95 | session->AppendBuffers(buffers_to_flush); |
| 96 | session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); | ||
| 95 | 97 | ||
| 96 | return ResultSuccess; | 98 | return ResultSuccess; |
| 97 | } | 99 | } |
| @@ -111,8 +113,15 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { | |||
| 111 | return false; | 113 | return false; |
| 112 | } | 114 | } |
| 113 | 115 | ||
| 114 | AudioBuffer new_buffer{ | 116 | const auto timestamp{buffers.GetNextTimestamp()}; |
| 115 | .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | 117 | const AudioBuffer new_buffer{ |
| 118 | .start_timestamp = timestamp, | ||
| 119 | .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), | ||
| 120 | .played_timestamp = 0, | ||
| 121 | .samples = buffer.samples, | ||
| 122 | .tag = tag, | ||
| 123 | .size = buffer.size, | ||
| 124 | }; | ||
| 116 | 125 | ||
| 117 | buffers.AppendBuffer(new_buffer); | 126 | buffers.AppendBuffer(new_buffer); |
| 118 | RegisterBuffers(); | 127 | RegisterBuffers(); |
| @@ -192,11 +201,11 @@ void System::SetVolume(const f32 volume_) { | |||
| 192 | session->SetVolume(volume_); | 201 | session->SetVolume(volume_); |
| 193 | } | 202 | } |
| 194 | 203 | ||
| 195 | bool System::ContainsAudioBuffer(const u64 tag) { | 204 | bool System::ContainsAudioBuffer(const u64 tag) const { |
| 196 | return buffers.ContainsBuffer(tag); | 205 | return buffers.ContainsBuffer(tag); |
| 197 | } | 206 | } |
| 198 | 207 | ||
| 199 | u32 System::GetBufferCount() { | 208 | u32 System::GetBufferCount() const { |
| 200 | return buffers.GetAppendedRegisteredCount(); | 209 | return buffers.GetAppendedRegisteredCount(); |
| 201 | } | 210 | } |
| 202 | 211 | ||
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h index 4ca2f3417..0817b2f37 100644 --- a/src/audio_core/out/audio_out_system.h +++ b/src/audio_core/out/audio_out_system.h | |||
| @@ -68,7 +68,7 @@ public: | |||
| 68 | * | 68 | * |
| 69 | * @return The default audio output device name. | 69 | * @return The default audio output device name. |
| 70 | */ | 70 | */ |
| 71 | std::string_view GetDefaultOutputDeviceName(); | 71 | std::string_view GetDefaultOutputDeviceName() const; |
| 72 | 72 | ||
| 73 | /** | 73 | /** |
| 74 | * Is the given initialize config valid? | 74 | * Is the given initialize config valid? |
| @@ -77,7 +77,7 @@ public: | |||
| 77 | * @param in_params - Input parameters, see AudioOutParameter. | 77 | * @param in_params - Input parameters, see AudioOutParameter. |
| 78 | * @return Result code. | 78 | * @return Result code. |
| 79 | */ | 79 | */ |
| 80 | Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params); | 80 | Result IsConfigValid(std::string_view device_name, const AudioOutParameter& in_params) const; |
| 81 | 81 | ||
| 82 | /** | 82 | /** |
| 83 | * Initialize this system. | 83 | * Initialize this system. |
| @@ -199,7 +199,7 @@ public: | |||
| 199 | /** | 199 | /** |
| 200 | * Set this system's current volume. | 200 | * Set this system's current volume. |
| 201 | * | 201 | * |
| 202 | * @param The new volume. | 202 | * @param volume The new volume. |
| 203 | */ | 203 | */ |
| 204 | void SetVolume(f32 volume); | 204 | void SetVolume(f32 volume); |
| 205 | 205 | ||
| @@ -209,14 +209,14 @@ public: | |||
| 209 | * @param tag - Unique tag to search for. | 209 | * @param tag - Unique tag to search for. |
| 210 | * @return True if the buffer is in the system, otherwise false. | 210 | * @return True if the buffer is in the system, otherwise false. |
| 211 | */ | 211 | */ |
| 212 | bool ContainsAudioBuffer(u64 tag); | 212 | bool ContainsAudioBuffer(u64 tag) const; |
| 213 | 213 | ||
| 214 | /** | 214 | /** |
| 215 | * Get the maximum number of usable buffers (default 32). | 215 | * Get the maximum number of usable buffers (default 32). |
| 216 | * | 216 | * |
| 217 | * @return The number of buffers. | 217 | * @return The number of buffers. |
| 218 | */ | 218 | */ |
| 219 | u32 GetBufferCount(); | 219 | u32 GetBufferCount() const; |
| 220 | 220 | ||
| 221 | /** | 221 | /** |
| 222 | * Get the total number of samples played by this system. | 222 | * Get the total number of samples played by this system. |
diff --git a/src/audio_core/renderer/adsp/adsp.cpp b/src/audio_core/renderer/adsp/adsp.cpp index e05a22d86..a28395663 100644 --- a/src/audio_core/renderer/adsp/adsp.cpp +++ b/src/audio_core/renderer/adsp/adsp.cpp | |||
| @@ -50,7 +50,7 @@ u32 ADSP::GetRemainCommandCount(const u32 session_id) const { | |||
| 50 | return render_mailbox.GetRemainCommandCount(session_id); | 50 | return render_mailbox.GetRemainCommandCount(session_id); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | void ADSP::SendCommandBuffer(const u32 session_id, CommandBuffer& command_buffer) { | 53 | void ADSP::SendCommandBuffer(const u32 session_id, const CommandBuffer& command_buffer) { |
| 54 | render_mailbox.SetCommandBuffer(session_id, command_buffer); | 54 | render_mailbox.SetCommandBuffer(session_id, command_buffer); |
| 55 | } | 55 | } |
| 56 | 56 | ||
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h index 4dfcef4a5..f7a2f25e4 100644 --- a/src/audio_core/renderer/adsp/adsp.h +++ b/src/audio_core/renderer/adsp/adsp.h | |||
| @@ -63,8 +63,6 @@ public: | |||
| 63 | 63 | ||
| 64 | /** | 64 | /** |
| 65 | * Stop the ADSP. | 65 | * Stop the ADSP. |
| 66 | * | ||
| 67 | * @return True if started or already running, otherwise false. | ||
| 68 | */ | 66 | */ |
| 69 | void Stop(); | 67 | void Stop(); |
| 70 | 68 | ||
| @@ -133,7 +131,7 @@ public: | |||
| 133 | * @param session_id - The session id to check (0 or 1). | 131 | * @param session_id - The session id to check (0 or 1). |
| 134 | * @param command_buffer - The command buffer to process. | 132 | * @param command_buffer - The command buffer to process. |
| 135 | */ | 133 | */ |
| 136 | void SendCommandBuffer(u32 session_id, CommandBuffer& command_buffer); | 134 | void SendCommandBuffer(u32 session_id, const CommandBuffer& command_buffer); |
| 137 | 135 | ||
| 138 | /** | 136 | /** |
| 139 | * Clear the command buffers (does not clear the time taken or the remaining command count) | 137 | * Clear the command buffers (does not clear the time taken or the remaining command count) |
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index 3967ccfe6..bafe4822a 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp | |||
| @@ -51,7 +51,7 @@ CommandBuffer& AudioRenderer_Mailbox::GetCommandBuffer(const s32 session_id) { | |||
| 51 | return command_buffers[session_id]; | 51 | return command_buffers[session_id]; |
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, CommandBuffer& buffer) { | 54 | void AudioRenderer_Mailbox::SetCommandBuffer(const u32 session_id, const CommandBuffer& buffer) { |
| 55 | command_buffers[session_id] = buffer; | 55 | command_buffers[session_id] = buffer; |
| 56 | } | 56 | } |
| 57 | 57 | ||
| @@ -106,9 +106,6 @@ void AudioRenderer::Start(AudioRenderer_Mailbox* mailbox_) { | |||
| 106 | 106 | ||
| 107 | mailbox = mailbox_; | 107 | mailbox = mailbox_; |
| 108 | thread = std::thread(&AudioRenderer::ThreadFunc, this); | 108 | thread = std::thread(&AudioRenderer::ThreadFunc, this); |
| 109 | for (auto& stream : streams) { | ||
| 110 | stream->Start(); | ||
| 111 | } | ||
| 112 | running = true; | 109 | running = true; |
| 113 | } | 110 | } |
| 114 | 111 | ||
| @@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() { | |||
| 130 | std::string name{fmt::format("ADSP_RenderStream-{}", i)}; | 127 | std::string name{fmt::format("ADSP_RenderStream-{}", i)}; |
| 131 | streams[i] = | 128 | streams[i] = |
| 132 | sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); | 129 | sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); |
| 130 | streams[i]->SetRingSize(4); | ||
| 133 | } | 131 | } |
| 134 | } | 132 | } |
| 135 | 133 | ||
| @@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() { | |||
| 198 | command_list_processor.Process(index) - start_time; | 196 | command_list_processor.Process(index) - start_time; |
| 199 | } | 197 | } |
| 200 | 198 | ||
| 201 | if (index == 0) { | ||
| 202 | auto stream{command_list_processor.GetOutputSinkStream()}; | ||
| 203 | system.AudioCore().SetStreamQueue(stream->GetQueueSize()); | ||
| 204 | } | ||
| 205 | |||
| 206 | const auto end_time{system.CoreTiming().GetClockTicks()}; | 199 | const auto end_time{system.CoreTiming().GetClockTicks()}; |
| 207 | 200 | ||
| 208 | command_buffer.remaining_command_count = | 201 | command_buffer.remaining_command_count = |
diff --git a/src/audio_core/renderer/adsp/audio_renderer.h b/src/audio_core/renderer/adsp/audio_renderer.h index b6ced9d2b..02e923c84 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ b/src/audio_core/renderer/adsp/audio_renderer.h | |||
| @@ -52,7 +52,7 @@ public: | |||
| 52 | /** | 52 | /** |
| 53 | * Send a message from the host to the AudioRenderer. | 53 | * Send a message from the host to the AudioRenderer. |
| 54 | * | 54 | * |
| 55 | * @param message_ - The message to send to the AudioRenderer. | 55 | * @param message - The message to send to the AudioRenderer. |
| 56 | */ | 56 | */ |
| 57 | void HostSendMessage(RenderMessage message); | 57 | void HostSendMessage(RenderMessage message); |
| 58 | 58 | ||
| @@ -66,7 +66,7 @@ public: | |||
| 66 | /** | 66 | /** |
| 67 | * Send a message from the AudioRenderer to the host. | 67 | * Send a message from the AudioRenderer to the host. |
| 68 | * | 68 | * |
| 69 | * @param message_ - The message to send to the host. | 69 | * @param message - The message to send to the host. |
| 70 | */ | 70 | */ |
| 71 | void ADSPSendMessage(RenderMessage message); | 71 | void ADSPSendMessage(RenderMessage message); |
| 72 | 72 | ||
| @@ -91,7 +91,7 @@ public: | |||
| 91 | * @param session_id - The session id to get (0 or 1). | 91 | * @param session_id - The session id to get (0 or 1). |
| 92 | * @param buffer - The command buffer to set. | 92 | * @param buffer - The command buffer to set. |
| 93 | */ | 93 | */ |
| 94 | void SetCommandBuffer(u32 session_id, CommandBuffer& buffer); | 94 | void SetCommandBuffer(u32 session_id, const CommandBuffer& buffer); |
| 95 | 95 | ||
| 96 | /** | 96 | /** |
| 97 | * Get the total render time taken for the last command lists sent. | 97 | * Get the total render time taken for the last command lists sent. |
| @@ -163,7 +163,7 @@ public: | |||
| 163 | /** | 163 | /** |
| 164 | * Start the AudioRenderer. | 164 | * Start the AudioRenderer. |
| 165 | * | 165 | * |
| 166 | * @param The mailbox to use for this session. | 166 | * @param mailbox The mailbox to use for this session. |
| 167 | */ | 167 | */ |
| 168 | void Start(AudioRenderer_Mailbox* mailbox); | 168 | void Start(AudioRenderer_Mailbox* mailbox); |
| 169 | 169 | ||
diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h index 3f99173e3..d78269e1d 100644 --- a/src/audio_core/renderer/adsp/command_list_processor.h +++ b/src/audio_core/renderer/adsp/command_list_processor.h | |||
| @@ -33,10 +33,10 @@ public: | |||
| 33 | /** | 33 | /** |
| 34 | * Initialize the processor. | 34 | * Initialize the processor. |
| 35 | * | 35 | * |
| 36 | * @param system_ - The core system. | 36 | * @param system - The core system. |
| 37 | * @param buffer - The command buffer to process. | 37 | * @param buffer - The command buffer to process. |
| 38 | * @param size - The size of the buffer. | 38 | * @param size - The size of the buffer. |
| 39 | * @param stream_ - The stream to be used for sending the samples. | 39 | * @param stream - The stream to be used for sending the samples. |
| 40 | */ | 40 | */ |
| 41 | void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); | 41 | void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); |
| 42 | 42 | ||
| @@ -72,7 +72,8 @@ public: | |||
| 72 | /** | 72 | /** |
| 73 | * Process the command list. | 73 | * Process the command list. |
| 74 | * | 74 | * |
| 75 | * @param index - Index of the current command list. | 75 | * @param session_id - Session ID for the commands being processed. |
| 76 | * | ||
| 76 | * @return The time taken to process. | 77 | * @return The time taken to process. |
| 77 | */ | 78 | */ |
| 78 | u64 Process(u32 session_id); | 79 | u64 Process(u32 session_id); |
| @@ -89,7 +90,7 @@ public: | |||
| 89 | u8* commands{}; | 90 | u8* commands{}; |
| 90 | /// The command buffer size | 91 | /// The command buffer size |
| 91 | u64 commands_buffer_size{}; | 92 | u64 commands_buffer_size{}; |
| 92 | /// The maximum processing time alloted | 93 | /// The maximum processing time allotted |
| 93 | u64 max_process_time{}; | 94 | u64 max_process_time{}; |
| 94 | /// The number of commands in the buffer | 95 | /// The number of commands in the buffer |
| 95 | u32 command_count{}; | 96 | u32 command_count{}; |
diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index d5886e55e..0d9d8f6ce 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp | |||
| @@ -1,6 +1,9 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <array> | ||
| 5 | #include <span> | ||
| 6 | |||
| 4 | #include "audio_core/audio_core.h" | 7 | #include "audio_core/audio_core.h" |
| 5 | #include "audio_core/common/feature_support.h" | 8 | #include "audio_core/common/feature_support.h" |
| 6 | #include "audio_core/renderer/audio_device.h" | 9 | #include "audio_core/renderer/audio_device.h" |
| @@ -9,14 +12,33 @@ | |||
| 9 | 12 | ||
| 10 | namespace AudioCore::AudioRenderer { | 13 | namespace AudioCore::AudioRenderer { |
| 11 | 14 | ||
| 15 | constexpr std::array usb_device_names{ | ||
| 16 | AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, | ||
| 17 | AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, | ||
| 18 | AudioDevice::AudioDeviceName{"AudioTvOutput"}, | ||
| 19 | AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"}, | ||
| 20 | }; | ||
| 21 | |||
| 22 | constexpr std::array device_names{ | ||
| 23 | AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, | ||
| 24 | AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, | ||
| 25 | AudioDevice::AudioDeviceName{"AudioTvOutput"}, | ||
| 26 | }; | ||
| 27 | |||
| 28 | constexpr std::array output_device_names{ | ||
| 29 | AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, | ||
| 30 | AudioDevice::AudioDeviceName{"AudioTvOutput"}, | ||
| 31 | AudioDevice::AudioDeviceName{"AudioExternalOutput"}, | ||
| 32 | }; | ||
| 33 | |||
| 12 | AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, | 34 | AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, |
| 13 | const u32 revision) | 35 | const u32 revision) |
| 14 | : output_sink{system.AudioCore().GetOutputSink()}, | 36 | : output_sink{system.AudioCore().GetOutputSink()}, |
| 15 | applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} | 37 | applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} |
| 16 | 38 | ||
| 17 | u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, | 39 | u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, |
| 18 | const size_t max_count) { | 40 | const size_t max_count) const { |
| 19 | std::span<AudioDeviceName> names{}; | 41 | std::span<const AudioDeviceName> names{}; |
| 20 | 42 | ||
| 21 | if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { | 43 | if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { |
| 22 | names = usb_device_names; | 44 | names = usb_device_names; |
| @@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, | |||
| 24 | names = device_names; | 46 | names = device_names; |
| 25 | } | 47 | } |
| 26 | 48 | ||
| 27 | u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; | 49 | const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; |
| 28 | for (u32 i = 0; i < out_count; i++) { | 50 | for (u32 i = 0; i < out_count; i++) { |
| 29 | out_buffer.push_back(names[i]); | 51 | out_buffer.push_back(names[i]); |
| 30 | } | 52 | } |
| @@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, | |||
| 32 | } | 54 | } |
| 33 | 55 | ||
| 34 | u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, | 56 | u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, |
| 35 | const size_t max_count) { | 57 | const size_t max_count) const { |
| 36 | u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; | 58 | const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; |
| 37 | 59 | ||
| 38 | for (u32 i = 0; i < out_count; i++) { | 60 | for (u32 i = 0; i < out_count; i++) { |
| 39 | out_buffer.push_back(output_device_names[i]); | 61 | out_buffer.push_back(output_device_names[i]); |
| @@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) { | |||
| 45 | output_sink.SetDeviceVolume(volume); | 67 | output_sink.SetDeviceVolume(volume); |
| 46 | } | 68 | } |
| 47 | 69 | ||
| 48 | f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { | 70 | f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const { |
| 49 | return output_sink.GetDeviceVolume(); | 71 | return output_sink.GetDeviceVolume(); |
| 50 | } | 72 | } |
| 51 | 73 | ||
diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h index 1f449f261..dd6be70ee 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h | |||
| @@ -3,7 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <span> | 6 | #include <string_view> |
| 7 | 7 | ||
| 8 | #include "audio_core/audio_render_manager.h" | 8 | #include "audio_core/audio_render_manager.h" |
| 9 | 9 | ||
| @@ -23,21 +23,13 @@ namespace AudioRenderer { | |||
| 23 | class AudioDevice { | 23 | class AudioDevice { |
| 24 | public: | 24 | public: |
| 25 | struct AudioDeviceName { | 25 | struct AudioDeviceName { |
| 26 | std::array<char, 0x100> name; | 26 | std::array<char, 0x100> name{}; |
| 27 | 27 | ||
| 28 | AudioDeviceName(const char* name_) { | 28 | constexpr AudioDeviceName(std::string_view name_) { |
| 29 | std::strncpy(name.data(), name_, name.size()); | 29 | name_.copy(name.data(), name.size() - 1); |
| 30 | } | 30 | } |
| 31 | }; | 31 | }; |
| 32 | 32 | ||
| 33 | std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput", | ||
| 34 | "AudioBuiltInSpeakerOutput", "AudioTvOutput", | ||
| 35 | "AudioUsbDeviceOutput"}; | ||
| 36 | std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput", | ||
| 37 | "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; | ||
| 38 | std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", | ||
| 39 | "AudioExternalOutput"}; | ||
| 40 | |||
| 41 | explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); | 33 | explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); |
| 42 | 34 | ||
| 43 | /** | 35 | /** |
| @@ -47,7 +39,7 @@ public: | |||
| 47 | * @param max_count - Maximum number of devices to write (count of out_buffer). | 39 | * @param max_count - Maximum number of devices to write (count of out_buffer). |
| 48 | * @return Number of device names written. | 40 | * @return Number of device names written. |
| 49 | */ | 41 | */ |
| 50 | u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); | 42 | u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const; |
| 51 | 43 | ||
| 52 | /** | 44 | /** |
| 53 | * Get a list of the available output devices. | 45 | * Get a list of the available output devices. |
| @@ -57,7 +49,7 @@ public: | |||
| 57 | * @param max_count - Maximum number of devices to write (count of out_buffer). | 49 | * @param max_count - Maximum number of devices to write (count of out_buffer). |
| 58 | * @return Number of device names written. | 50 | * @return Number of device names written. |
| 59 | */ | 51 | */ |
| 60 | u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); | 52 | u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const; |
| 61 | 53 | ||
| 62 | /** | 54 | /** |
| 63 | * Set the volume of all streams in the backend sink. | 55 | * Set the volume of all streams in the backend sink. |
| @@ -73,7 +65,7 @@ public: | |||
| 73 | * @param name - Name of the device to check. Unused. | 65 | * @param name - Name of the device to check. Unused. |
| 74 | * @return Volume of the device. | 66 | * @return Volume of the device. |
| 75 | */ | 67 | */ |
| 76 | f32 GetDeviceVolume(std::string_view name); | 68 | f32 GetDeviceVolume(std::string_view name) const; |
| 77 | 69 | ||
| 78 | private: | 70 | private: |
| 79 | /// Backend output sink for the device | 71 | /// Backend output sink for the device |
diff --git a/src/audio_core/renderer/behavior/behavior_info.cpp b/src/audio_core/renderer/behavior/behavior_info.cpp index c5d4d66d8..3d2a91312 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp | |||
| @@ -34,7 +34,7 @@ void BehaviorInfo::ClearError() { | |||
| 34 | error_count = 0; | 34 | error_count = 0; |
| 35 | } | 35 | } |
| 36 | 36 | ||
| 37 | void BehaviorInfo::AppendError(ErrorInfo& error) { | 37 | void BehaviorInfo::AppendError(const ErrorInfo& error) { |
| 38 | LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", | 38 | LOG_ERROR(Service_Audio, "Error during RequestUpdate, reporting code {:04X} address {:08X}", |
| 39 | error.error_code.raw, error.address); | 39 | error.error_code.raw, error.address); |
| 40 | if (error_count < MaxErrors) { | 40 | if (error_count < MaxErrors) { |
| @@ -42,14 +42,16 @@ void BehaviorInfo::AppendError(ErrorInfo& error) { | |||
| 42 | } | 42 | } |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { | 45 | void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const { |
| 46 | auto error_count_{std::min(error_count, MaxErrors)}; | 46 | out_count = std::min(error_count, MaxErrors); |
| 47 | std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); | ||
| 48 | 47 | ||
| 49 | for (size_t i = 0; i < error_count_; i++) { | 48 | for (size_t i = 0; i < MaxErrors; i++) { |
| 50 | out_errors[i] = errors[i]; | 49 | if (i < out_count) { |
| 50 | out_errors[i] = errors[i]; | ||
| 51 | } else { | ||
| 52 | out_errors[i] = {}; | ||
| 53 | } | ||
| 51 | } | 54 | } |
| 52 | out_count = error_count_; | ||
| 53 | } | 55 | } |
| 54 | 56 | ||
| 55 | void BehaviorInfo::UpdateFlags(const Flags flags_) { | 57 | void BehaviorInfo::UpdateFlags(const Flags flags_) { |
diff --git a/src/audio_core/renderer/behavior/behavior_info.h b/src/audio_core/renderer/behavior/behavior_info.h index 7333c297f..15c948344 100644 --- a/src/audio_core/renderer/behavior/behavior_info.h +++ b/src/audio_core/renderer/behavior/behavior_info.h | |||
| @@ -94,7 +94,7 @@ public: | |||
| 94 | * | 94 | * |
| 95 | * @param error - The new error. | 95 | * @param error - The new error. |
| 96 | */ | 96 | */ |
| 97 | void AppendError(ErrorInfo& error); | 97 | void AppendError(const ErrorInfo& error); |
| 98 | 98 | ||
| 99 | /** | 99 | /** |
| 100 | * Copy errors to the given output container. | 100 | * Copy errors to the given output container. |
| @@ -102,7 +102,7 @@ public: | |||
| 102 | * @param out_errors - Output container to receive the errors. | 102 | * @param out_errors - Output container to receive the errors. |
| 103 | * @param out_count - The number of errors written. | 103 | * @param out_count - The number of errors written. |
| 104 | */ | 104 | */ |
| 105 | void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count); | 105 | void CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) const; |
| 106 | 106 | ||
| 107 | /** | 107 | /** |
| 108 | * Update the behaviour flags. | 108 | * Update the behaviour flags. |
diff --git a/src/audio_core/renderer/behavior/info_updater.cpp b/src/audio_core/renderer/behavior/info_updater.cpp index 06a37e1a6..c0a307b89 100644 --- a/src/audio_core/renderer/behavior/info_updater.cpp +++ b/src/audio_core/renderer/behavior/info_updater.cpp | |||
| @@ -485,7 +485,7 @@ Result InfoUpdater::UpdateBehaviorInfo(BehaviorInfo& behaviour_) { | |||
| 485 | return ResultSuccess; | 485 | return ResultSuccess; |
| 486 | } | 486 | } |
| 487 | 487 | ||
| 488 | Result InfoUpdater::UpdateErrorInfo(BehaviorInfo& behaviour_) { | 488 | Result InfoUpdater::UpdateErrorInfo(const BehaviorInfo& behaviour_) { |
| 489 | auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; | 489 | auto out_params{reinterpret_cast<BehaviorInfo::OutStatus*>(output)}; |
| 490 | behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); | 490 | behaviour_.CopyErrorInfo(out_params->errors, out_params->error_count); |
| 491 | 491 | ||
diff --git a/src/audio_core/renderer/behavior/info_updater.h b/src/audio_core/renderer/behavior/info_updater.h index f0b445d9c..c817d8d8d 100644 --- a/src/audio_core/renderer/behavior/info_updater.h +++ b/src/audio_core/renderer/behavior/info_updater.h | |||
| @@ -130,7 +130,7 @@ public: | |||
| 130 | * @param behaviour - Behaviour to update. | 130 | * @param behaviour - Behaviour to update. |
| 131 | * @return Result code. | 131 | * @return Result code. |
| 132 | */ | 132 | */ |
| 133 | Result UpdateErrorInfo(BehaviorInfo& behaviour); | 133 | Result UpdateErrorInfo(const BehaviorInfo& behaviour); |
| 134 | 134 | ||
| 135 | /** | 135 | /** |
| 136 | * Update splitter. | 136 | * Update splitter. |
diff --git a/src/audio_core/renderer/command/command_buffer.h b/src/audio_core/renderer/command/command_buffer.h index 496b0e50a..162170846 100644 --- a/src/audio_core/renderer/command/command_buffer.h +++ b/src/audio_core/renderer/command/command_buffer.h | |||
| @@ -191,6 +191,7 @@ public: | |||
| 191 | * @param volume - Current mix volume used for calculating the ramp. | 191 | * @param volume - Current mix volume used for calculating the ramp. |
| 192 | * @param prev_volume - Previous mix volume, used for calculating the ramp, | 192 | * @param prev_volume - Previous mix volume, used for calculating the ramp, |
| 193 | * also applied to the input. | 193 | * also applied to the input. |
| 194 | * @param prev_samples - Previous sample buffer. Used for depopping. | ||
| 194 | * @param precision - Number of decimal bits for fixed point operations. | 195 | * @param precision - Number of decimal bits for fixed point operations. |
| 195 | */ | 196 | */ |
| 196 | void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, | 197 | void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, |
| @@ -208,6 +209,7 @@ public: | |||
| 208 | * @param volumes - Current mix volumes used for calculating the ramp. | 209 | * @param volumes - Current mix volumes used for calculating the ramp. |
| 209 | * @param prev_volumes - Previous mix volumes, used for calculating the ramp, | 210 | * @param prev_volumes - Previous mix volumes, used for calculating the ramp, |
| 210 | * also applied to the input. | 211 | * also applied to the input. |
| 212 | * @param prev_samples - Previous sample buffer. Used for depopping. | ||
| 211 | * @param precision - Number of decimal bits for fixed point operations. | 213 | * @param precision - Number of decimal bits for fixed point operations. |
| 212 | */ | 214 | */ |
| 213 | void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, | 215 | void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, |
| @@ -297,11 +299,11 @@ public: | |||
| 297 | /** | 299 | /** |
| 298 | * Generate a device sink command, adding it to the command list. | 300 | * Generate a device sink command, adding it to the command list. |
| 299 | * | 301 | * |
| 300 | * @param node_id - Node id of the voice this command is generated for. | 302 | * @param node_id - Node id of the voice this command is generated for. |
| 301 | * @param buffer_offset - Base mix buffer offset to use. | 303 | * @param buffer_offset - Base mix buffer offset to use. |
| 302 | * @param sink_info - The sink_info to generate this command from. | 304 | * @param sink_info - The sink_info to generate this command from. |
| 303 | * @session_id - System session id this command is generated from. | 305 | * @param session_id - System session id this command is generated from. |
| 304 | * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. | 306 | * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used. |
| 305 | */ | 307 | */ |
| 306 | void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, | 308 | void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, |
| 307 | u32 session_id, std::span<s32> samples_buffer); | 309 | u32 session_id, std::span<s32> samples_buffer); |
diff --git a/src/audio_core/renderer/command/command_generator.h b/src/audio_core/renderer/command/command_generator.h index d80d9b0d8..b3cd7b408 100644 --- a/src/audio_core/renderer/command/command_generator.h +++ b/src/audio_core/renderer/command/command_generator.h | |||
| @@ -197,9 +197,9 @@ public: | |||
| 197 | /** | 197 | /** |
| 198 | * Generate an I3DL2 reverb effect command. | 198 | * Generate an I3DL2 reverb effect command. |
| 199 | * | 199 | * |
| 200 | * @param buffer_offset - Base mix buffer offset to use. | 200 | * @param buffer_offset - Base mix buffer offset to use. |
| 201 | * @param effect_info_base - I3DL2Reverb effect info. | 201 | * @param effect_info - I3DL2Reverb effect info. |
| 202 | * @param node_id - Node id of the mix this command is generated for. | 202 | * @param node_id - Node id of the mix this command is generated for. |
| 203 | */ | 203 | */ |
| 204 | void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | 204 | void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
| 205 | s32 node_id); | 205 | s32 node_id); |
| @@ -207,18 +207,18 @@ public: | |||
| 207 | /** | 207 | /** |
| 208 | * Generate an aux effect command. | 208 | * Generate an aux effect command. |
| 209 | * | 209 | * |
| 210 | * @param buffer_offset - Base mix buffer offset to use. | 210 | * @param buffer_offset - Base mix buffer offset to use. |
| 211 | * @param effect_info_base - Aux effect info. | 211 | * @param effect_info - Aux effect info. |
| 212 | * @param node_id - Node id of the mix this command is generated for. | 212 | * @param node_id - Node id of the mix this command is generated for. |
| 213 | */ | 213 | */ |
| 214 | void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | 214 | void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); |
| 215 | 215 | ||
| 216 | /** | 216 | /** |
| 217 | * Generate a biquad filter effect command. | 217 | * Generate a biquad filter effect command. |
| 218 | * | 218 | * |
| 219 | * @param buffer_offset - Base mix buffer offset to use. | 219 | * @param buffer_offset - Base mix buffer offset to use. |
| 220 | * @param effect_info_base - Aux effect info. | 220 | * @param effect_info - Aux effect info. |
| 221 | * @param node_id - Node id of the mix this command is generated for. | 221 | * @param node_id - Node id of the mix this command is generated for. |
| 222 | */ | 222 | */ |
| 223 | void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | 223 | void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
| 224 | s32 node_id); | 224 | s32 node_id); |
| @@ -226,10 +226,10 @@ public: | |||
| 226 | /** | 226 | /** |
| 227 | * Generate a light limiter effect command. | 227 | * Generate a light limiter effect command. |
| 228 | * | 228 | * |
| 229 | * @param buffer_offset - Base mix buffer offset to use. | 229 | * @param buffer_offset - Base mix buffer offset to use. |
| 230 | * @param effect_info_base - Limiter effect info. | 230 | * @param effect_info - Limiter effect info. |
| 231 | * @param node_id - Node id of the mix this command is generated for. | 231 | * @param node_id - Node id of the mix this command is generated for. |
| 232 | * @param effect_index - Index for the statistics state. | 232 | * @param effect_index - Index for the statistics state. |
| 233 | */ | 233 | */ |
| 234 | void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, | 234 | void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, |
| 235 | s32 node_id, u32 effect_index); | 235 | s32 node_id, u32 effect_index); |
| @@ -238,21 +238,20 @@ public: | |||
| 238 | * Generate a capture effect command. | 238 | * Generate a capture effect command. |
| 239 | * Writes a mix buffer back to game memory. | 239 | * Writes a mix buffer back to game memory. |
| 240 | * | 240 | * |
| 241 | * @param buffer_offset - Base mix buffer offset to use. | 241 | * @param buffer_offset - Base mix buffer offset to use. |
| 242 | * @param effect_info_base - Capture effect info. | 242 | * @param effect_info - Capture effect info. |
| 243 | * @param node_id - Node id of the mix this command is generated for. | 243 | * @param node_id - Node id of the mix this command is generated for. |
| 244 | */ | 244 | */ |
| 245 | void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); | 245 | void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); |
| 246 | 246 | ||
| 247 | /** | 247 | /** |
| 248 | * Generate a compressor effect command. | 248 | * Generate a compressor effect command. |
| 249 | * | 249 | * |
| 250 | * @param buffer_offset - Base mix buffer offset to use. | 250 | * @param buffer_offset - Base mix buffer offset to use. |
| 251 | * @param effect_info_base - Compressor effect info. | 251 | * @param effect_info - Compressor effect info. |
| 252 | * @param node_id - Node id of the mix this command is generated for. | 252 | * @param node_id - Node id of the mix this command is generated for. |
| 253 | */ | 253 | */ |
| 254 | void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, | 254 | void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); |
| 255 | const s32 node_id); | ||
| 256 | 255 | ||
| 257 | /** | 256 | /** |
| 258 | * Generate all effect commands for a mix. | 257 | * Generate all effect commands for a mix. |
| @@ -318,8 +317,9 @@ public: | |||
| 318 | * Generate a performance command. | 317 | * Generate a performance command. |
| 319 | * Used to report performance metrics of the AudioRenderer back to the game. | 318 | * Used to report performance metrics of the AudioRenderer back to the game. |
| 320 | * | 319 | * |
| 321 | * @param buffer_offset - Base mix buffer offset to use. | 320 | * @param node_id - Node ID of the mix this command is generated for |
| 322 | * @param sink_info - Sink info to generate the commands from. | 321 | * @param state - Output state of the generated performance command |
| 322 | * @param entry_addresses - Addresses to be written | ||
| 323 | */ | 323 | */ |
| 324 | void GeneratePerformanceCommand(s32 node_id, PerformanceState state, | 324 | void GeneratePerformanceCommand(s32 node_id, PerformanceState state, |
| 325 | const PerformanceEntryAddresses& entry_addresses); | 325 | const PerformanceEntryAddresses& entry_addresses); |
diff --git a/src/audio_core/renderer/command/effect/compressor.cpp b/src/audio_core/renderer/command/effect/compressor.cpp index 2ebc140f1..7229618e8 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp | |||
| @@ -11,7 +11,7 @@ | |||
| 11 | 11 | ||
| 12 | namespace AudioCore::AudioRenderer { | 12 | namespace AudioCore::AudioRenderer { |
| 13 | 13 | ||
| 14 | static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, | 14 | static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params, |
| 15 | CompressorInfo::State& state) { | 15 | CompressorInfo::State& state) { |
| 16 | const auto ratio{1.0f / params.compressor_ratio}; | 16 | const auto ratio{1.0f / params.compressor_ratio}; |
| 17 | auto makeup_gain{0.0f}; | 17 | auto makeup_gain{0.0f}; |
| @@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para | |||
| 31 | state.unk_20 = c; | 31 | state.unk_20 = c; |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, | 34 | static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params, |
| 35 | CompressorInfo::State& state) { | 35 | CompressorInfo::State& state) { |
| 36 | std::memset(&state, 0, sizeof(CompressorInfo::State)); | 36 | state = {}; |
| 37 | 37 | ||
| 38 | state.unk_00 = 0; | 38 | state.unk_00 = 0; |
| 39 | state.unk_04 = 1.0f; | 39 | state.unk_04 = 1.0f; |
| @@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params | |||
| 42 | SetCompressorEffectParameter(params, state); | 42 | SetCompressorEffectParameter(params, state); |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, | 45 | static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params, |
| 46 | CompressorInfo::State& state, bool enabled, | 46 | CompressorInfo::State& state, bool enabled, |
| 47 | std::vector<std::span<const s32>> input_buffers, | 47 | std::vector<std::span<const s32>> input_buffers, |
| 48 | std::vector<std::span<s32>> output_buffers, u32 sample_count) { | 48 | std::vector<std::span<s32>> output_buffers, u32 sample_count) { |
| @@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, | |||
| 103 | } else { | 103 | } else { |
| 104 | for (s16 channel = 0; channel < params.channel_count; channel++) { | 104 | for (s16 channel = 0; channel < params.channel_count; channel++) { |
| 105 | if (params.inputs[channel] != params.outputs[channel]) { | 105 | if (params.inputs[channel] != params.outputs[channel]) { |
| 106 | std::memcpy((char*)output_buffers[channel].data(), | 106 | std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(), |
| 107 | (char*)input_buffers[channel].data(), | ||
| 108 | output_buffers[channel].size_bytes()); | 107 | output_buffers[channel].size_bytes()); |
| 109 | } | 108 | } |
| 110 | } | 109 | } |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp index ffdafa1c8..d67123cd8 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp.cpp +++ b/src/audio_core/renderer/command/mix/mix_ramp.cpp | |||
| @@ -7,17 +7,7 @@ | |||
| 7 | #include "common/logging/log.h" | 7 | #include "common/logging/log.h" |
| 8 | 8 | ||
| 9 | namespace AudioCore::AudioRenderer { | 9 | namespace AudioCore::AudioRenderer { |
| 10 | /** | 10 | |
| 11 | * Mix input mix buffer into output mix buffer, with volume applied to the input. | ||
| 12 | * | ||
| 13 | * @tparam Q - Number of bits for fixed point operations. | ||
| 14 | * @param output - Output mix buffer. | ||
| 15 | * @param input - Input mix buffer. | ||
| 16 | * @param volume - Volume applied to the input. | ||
| 17 | * @param ramp - Ramp applied to volume every sample. | ||
| 18 | * @param sample_count - Number of samples to process. | ||
| 19 | * @return The final gained input sample, used for depopping. | ||
| 20 | */ | ||
| 21 | template <size_t Q> | 11 | template <size_t Q> |
| 22 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | 12 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, |
| 23 | const f32 ramp_, const u32 sample_count) { | 13 | const f32 ramp_, const u32 sample_count) { |
| @@ -40,10 +30,8 @@ s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 vo | |||
| 40 | return sample.to_int(); | 30 | return sample.to_int(); |
| 41 | } | 31 | } |
| 42 | 32 | ||
| 43 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, | 33 | template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32); |
| 44 | const u32); | 34 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32); |
| 45 | template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, | ||
| 46 | const u32); | ||
| 47 | 35 | ||
| 48 | void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { | 36 | void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { |
| 49 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; | 37 | const auto ramp{(volume - prev_volume) / static_cast<f32>(processor.sample_count)}; |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp.h b/src/audio_core/renderer/command/mix/mix_ramp.h index 770f57e80..52f74a273 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp.h +++ b/src/audio_core/renderer/command/mix/mix_ramp.h | |||
| @@ -61,13 +61,13 @@ struct MixRampCommand : ICommand { | |||
| 61 | * @tparam Q - Number of bits for fixed point operations. | 61 | * @tparam Q - Number of bits for fixed point operations. |
| 62 | * @param output - Output mix buffer. | 62 | * @param output - Output mix buffer. |
| 63 | * @param input - Input mix buffer. | 63 | * @param input - Input mix buffer. |
| 64 | * @param volume - Volume applied to the input. | 64 | * @param volume_ - Volume applied to the input. |
| 65 | * @param ramp - Ramp applied to volume every sample. | 65 | * @param ramp_ - Ramp applied to volume every sample. |
| 66 | * @param sample_count - Number of samples to process. | 66 | * @param sample_count - Number of samples to process. |
| 67 | * @return The final gained input sample, used for depopping. | 67 | * @return The final gained input sample, used for depopping. |
| 68 | */ | 68 | */ |
| 69 | template <size_t Q> | 69 | template <size_t Q> |
| 70 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, | 70 | s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_, |
| 71 | const f32 ramp_, const u32 sample_count); | 71 | u32 sample_count); |
| 72 | 72 | ||
| 73 | } // namespace AudioCore::AudioRenderer | 73 | } // namespace AudioCore::AudioRenderer |
diff --git a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h index 027276e5a..3b0ce67ef 100644 --- a/src/audio_core/renderer/command/mix/mix_ramp_grouped.h +++ b/src/audio_core/renderer/command/mix/mix_ramp_grouped.h | |||
| @@ -50,9 +50,9 @@ struct MixRampGroupedCommand : ICommand { | |||
| 50 | std::array<s16, MaxMixBuffers> inputs; | 50 | std::array<s16, MaxMixBuffers> inputs; |
| 51 | /// Output mix buffer indexes for each mix buffer | 51 | /// Output mix buffer indexes for each mix buffer |
| 52 | std::array<s16, MaxMixBuffers> outputs; | 52 | std::array<s16, MaxMixBuffers> outputs; |
| 53 | /// Previous mix vloumes for each mix buffer | 53 | /// Previous mix volumes for each mix buffer |
| 54 | std::array<f32, MaxMixBuffers> prev_volumes; | 54 | std::array<f32, MaxMixBuffers> prev_volumes; |
| 55 | /// Current mix vloumes for each mix buffer | 55 | /// Current mix volumes for each mix buffer |
| 56 | std::array<f32, MaxMixBuffers> volumes; | 56 | std::array<f32, MaxMixBuffers> volumes; |
| 57 | /// Pointer to the previous sample buffer, used for depop | 57 | /// Pointer to the previous sample buffer, used for depop |
| 58 | CpuAddr previous_samples; | 58 | CpuAddr previous_samples; |
diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp index 47e0c6722..e88372a75 100644 --- a/src/audio_core/renderer/command/sink/device.cpp +++ b/src/audio_core/renderer/command/sink/device.cpp | |||
| @@ -46,6 +46,10 @@ void DeviceSinkCommand::Process(const ADSP::CommandListProcessor& processor) { | |||
| 46 | 46 | ||
| 47 | out_buffer.tag = reinterpret_cast<u64>(samples.data()); | 47 | out_buffer.tag = reinterpret_cast<u64>(samples.data()); |
| 48 | stream->AppendBuffer(out_buffer, samples); | 48 | stream->AppendBuffer(out_buffer, samples); |
| 49 | |||
| 50 | if (stream->IsPaused()) { | ||
| 51 | stream->Start(); | ||
| 52 | } | ||
| 49 | } | 53 | } |
| 50 | 54 | ||
| 51 | bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { | 55 | bool DeviceSinkCommand::Verify(const ADSP::CommandListProcessor& processor) { |
diff --git a/src/audio_core/renderer/effect/effect_context.h b/src/audio_core/renderer/effect/effect_context.h index 85955bd9c..8f6d6e7d8 100644 --- a/src/audio_core/renderer/effect/effect_context.h +++ b/src/audio_core/renderer/effect/effect_context.h | |||
| @@ -15,15 +15,15 @@ class EffectContext { | |||
| 15 | public: | 15 | public: |
| 16 | /** | 16 | /** |
| 17 | * Initialize the effect context | 17 | * Initialize the effect context |
| 18 | * @param effect_infos List of effect infos for this context | 18 | * @param effect_infos_ - List of effect infos for this context |
| 19 | * @param effect_count The number of effects in the list | 19 | * @param effect_count_ - The number of effects in the list |
| 20 | * @param result_states_cpu The workbuffer of result states for the CPU for this context | 20 | * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context |
| 21 | * @param result_states_dsp The workbuffer of result states for the DSP for this context | 21 | * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context |
| 22 | * @param state_count The number of result states | 22 | * @param dsp_state_count - The number of result states |
| 23 | */ | 23 | */ |
| 24 | void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, | 24 | void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_, |
| 25 | std::span<EffectResultState> result_states_cpu_, | 25 | std::span<EffectResultState> result_states_cpu_, |
| 26 | std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); | 26 | std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count); |
| 27 | 27 | ||
| 28 | /** | 28 | /** |
| 29 | * Get the EffectInfo for a given index | 29 | * Get the EffectInfo for a given index |
diff --git a/src/audio_core/renderer/effect/effect_info_base.h b/src/audio_core/renderer/effect/effect_info_base.h index 8c9583878..8525fde05 100644 --- a/src/audio_core/renderer/effect/effect_info_base.h +++ b/src/audio_core/renderer/effect/effect_info_base.h | |||
| @@ -291,7 +291,7 @@ public: | |||
| 291 | * Update the info with new parameters, version 1. | 291 | * Update the info with new parameters, version 1. |
| 292 | * | 292 | * |
| 293 | * @param error_info - Used to write call result code. | 293 | * @param error_info - Used to write call result code. |
| 294 | * @param in_params - New parameters to update the info with. | 294 | * @param params - New parameters to update the info with. |
| 295 | * @param pool_mapper - Pool for mapping buffers. | 295 | * @param pool_mapper - Pool for mapping buffers. |
| 296 | */ | 296 | */ |
| 297 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, | 297 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, |
| @@ -305,7 +305,7 @@ public: | |||
| 305 | * Update the info with new parameters, version 2. | 305 | * Update the info with new parameters, version 2. |
| 306 | * | 306 | * |
| 307 | * @param error_info - Used to write call result code. | 307 | * @param error_info - Used to write call result code. |
| 308 | * @param in_params - New parameters to update the info with. | 308 | * @param params - New parameters to update the info with. |
| 309 | * @param pool_mapper - Pool for mapping buffers. | 309 | * @param pool_mapper - Pool for mapping buffers. |
| 310 | */ | 310 | */ |
| 311 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, | 311 | virtual void Update(BehaviorInfo::ErrorInfo& error_info, |
diff --git a/src/audio_core/renderer/effect/i3dl2.h b/src/audio_core/renderer/effect/i3dl2.h index 7a088a627..1ebbc5c4c 100644 --- a/src/audio_core/renderer/effect/i3dl2.h +++ b/src/audio_core/renderer/effect/i3dl2.h | |||
| @@ -99,7 +99,7 @@ public: | |||
| 99 | return out_sample; | 99 | return out_sample; |
| 100 | } | 100 | } |
| 101 | 101 | ||
| 102 | Common::FixedPoint<50, 14> Read() { | 102 | Common::FixedPoint<50, 14> Read() const { |
| 103 | return *output; | 103 | return *output; |
| 104 | } | 104 | } |
| 105 | 105 | ||
| @@ -110,7 +110,7 @@ public: | |||
| 110 | } | 110 | } |
| 111 | } | 111 | } |
| 112 | 112 | ||
| 113 | Common::FixedPoint<50, 14> TapOut(const s32 index) { | 113 | Common::FixedPoint<50, 14> TapOut(const s32 index) const { |
| 114 | auto out{input - (index + 1)}; | 114 | auto out{input - (index + 1)}; |
| 115 | if (out < buffer.data()) { | 115 | if (out < buffer.data()) { |
| 116 | out += max_delay + 1; | 116 | out += max_delay + 1; |
diff --git a/src/audio_core/renderer/effect/reverb.h b/src/audio_core/renderer/effect/reverb.h index b4df9f6ef..a72475c3c 100644 --- a/src/audio_core/renderer/effect/reverb.h +++ b/src/audio_core/renderer/effect/reverb.h | |||
| @@ -95,7 +95,7 @@ public: | |||
| 95 | return out_sample; | 95 | return out_sample; |
| 96 | } | 96 | } |
| 97 | 97 | ||
| 98 | Common::FixedPoint<50, 14> Read() { | 98 | Common::FixedPoint<50, 14> Read() const { |
| 99 | return *output; | 99 | return *output; |
| 100 | } | 100 | } |
| 101 | 101 | ||
| @@ -106,7 +106,7 @@ public: | |||
| 106 | } | 106 | } |
| 107 | } | 107 | } |
| 108 | 108 | ||
| 109 | Common::FixedPoint<50, 14> TapOut(const s32 index) { | 109 | Common::FixedPoint<50, 14> TapOut(const s32 index) const { |
| 110 | auto out{input - (index + 1)}; | 110 | auto out{input - (index + 1)}; |
| 111 | if (out < buffer.data()) { | 111 | if (out < buffer.data()) { |
| 112 | out += sample_count; | 112 | out += sample_count; |
diff --git a/src/audio_core/renderer/memory/address_info.h b/src/audio_core/renderer/memory/address_info.h index 4cfefea8e..bb5c930e1 100644 --- a/src/audio_core/renderer/memory/address_info.h +++ b/src/audio_core/renderer/memory/address_info.h | |||
| @@ -19,8 +19,8 @@ public: | |||
| 19 | /** | 19 | /** |
| 20 | * Setup a new AddressInfo. | 20 | * Setup a new AddressInfo. |
| 21 | * | 21 | * |
| 22 | * @param cpu_address - The CPU address of this region. | 22 | * @param cpu_address_ - The CPU address of this region. |
| 23 | * @param size - The size of this region. | 23 | * @param size_ - The size of this region. |
| 24 | */ | 24 | */ |
| 25 | void Setup(CpuAddr cpu_address_, u64 size_) { | 25 | void Setup(CpuAddr cpu_address_, u64 size_) { |
| 26 | cpu_address = cpu_address_; | 26 | cpu_address = cpu_address_; |
| @@ -42,7 +42,6 @@ public: | |||
| 42 | * Assign this region to a memory pool. | 42 | * Assign this region to a memory pool. |
| 43 | * | 43 | * |
| 44 | * @param memory_pool_ - Memory pool to assign. | 44 | * @param memory_pool_ - Memory pool to assign. |
| 45 | * @return The CpuAddr address of this region. | ||
| 46 | */ | 45 | */ |
| 47 | void SetPool(MemoryPoolInfo* memory_pool_) { | 46 | void SetPool(MemoryPoolInfo* memory_pool_) { |
| 48 | memory_pool = memory_pool_; | 47 | memory_pool = memory_pool_; |
diff --git a/src/audio_core/renderer/nodes/node_states.h b/src/audio_core/renderer/nodes/node_states.h index a1e0958a2..94b1d1254 100644 --- a/src/audio_core/renderer/nodes/node_states.h +++ b/src/audio_core/renderer/nodes/node_states.h | |||
| @@ -56,7 +56,7 @@ class NodeStates { | |||
| 56 | * | 56 | * |
| 57 | * @return The current stack position. | 57 | * @return The current stack position. |
| 58 | */ | 58 | */ |
| 59 | u32 Count() { | 59 | u32 Count() const { |
| 60 | return pos; | 60 | return pos; |
| 61 | } | 61 | } |
| 62 | 62 | ||
| @@ -83,7 +83,7 @@ class NodeStates { | |||
| 83 | * | 83 | * |
| 84 | * @return The node on the top of the stack. | 84 | * @return The node on the top of the stack. |
| 85 | */ | 85 | */ |
| 86 | u32 top() { | 86 | u32 top() const { |
| 87 | return stack[pos - 1]; | 87 | return stack[pos - 1]; |
| 88 | } | 88 | } |
| 89 | 89 | ||
| @@ -112,11 +112,11 @@ public: | |||
| 112 | /** | 112 | /** |
| 113 | * Initialize the node states. | 113 | * Initialize the node states. |
| 114 | * | 114 | * |
| 115 | * @param buffer - The workbuffer to use. Unused. | 115 | * @param buffer_ - The workbuffer to use. Unused. |
| 116 | * @param node_buffer_size - The size of the workbuffer. Unused. | 116 | * @param node_buffer_size - The size of the workbuffer. Unused. |
| 117 | * @param count - The number of nodes in the graph. | 117 | * @param count - The number of nodes in the graph. |
| 118 | */ | 118 | */ |
| 119 | void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); | 119 | void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count); |
| 120 | 120 | ||
| 121 | /** | 121 | /** |
| 122 | * Sort the graph. Only calls DepthFirstSearch. | 122 | * Sort the graph. Only calls DepthFirstSearch. |
diff --git a/src/audio_core/renderer/performance/performance_manager.h b/src/audio_core/renderer/performance/performance_manager.h index b82176bef..b65caa9b6 100644 --- a/src/audio_core/renderer/performance/performance_manager.h +++ b/src/audio_core/renderer/performance/performance_manager.h | |||
| @@ -73,7 +73,8 @@ public: | |||
| 73 | * Calculate the required size for the performance workbuffer. | 73 | * Calculate the required size for the performance workbuffer. |
| 74 | * | 74 | * |
| 75 | * @param behavior - Check which version is supported. | 75 | * @param behavior - Check which version is supported. |
| 76 | * @param params - Input parameters. | 76 | * @param params - Input parameters. |
| 77 | * | ||
| 77 | * @return Required workbuffer size. | 78 | * @return Required workbuffer size. |
| 78 | */ | 79 | */ |
| 79 | static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( | 80 | static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( |
| @@ -104,7 +105,7 @@ public: | |||
| 104 | * @param workbuffer - Workbuffer to use for performance frames. | 105 | * @param workbuffer - Workbuffer to use for performance frames. |
| 105 | * @param workbuffer_size - Size of the workbuffer. | 106 | * @param workbuffer_size - Size of the workbuffer. |
| 106 | * @param params - Input parameters. | 107 | * @param params - Input parameters. |
| 107 | * @param behavior - Behaviour to check version and data format. | 108 | * @param behavior - Behaviour to check version and data format. |
| 108 | * @param memory_pool - Used to translate the workbuffer address for the DSP. | 109 | * @param memory_pool - Used to translate the workbuffer address for the DSP. |
| 109 | */ | 110 | */ |
| 110 | virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, | 111 | virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, |
| @@ -160,7 +161,8 @@ public: | |||
| 160 | * workbuffer, to be written by the AudioRenderer. | 161 | * workbuffer, to be written by the AudioRenderer. |
| 161 | * | 162 | * |
| 162 | * @param addresses - Filled with pointers to the new detail, which should be passed | 163 | * @param addresses - Filled with pointers to the new detail, which should be passed |
| 163 | * to the AudioRenderer with Performance commands to be written. | 164 | * to the AudioRenderer with Performance commands to be written. |
| 165 | * @param detail_type - Performance detail type. | ||
| 164 | * @param entry_type - The type of this detail. See PerformanceEntryType | 166 | * @param entry_type - The type of this detail. See PerformanceEntryType |
| 165 | * @param node_id - Node id for this detail. | 167 | * @param node_id - Node id for this detail. |
| 166 | * @return True if a new detail was created and the offsets are valid, otherwise false. | 168 | * @return True if a new detail was created and the offsets are valid, otherwise false. |
diff --git a/src/audio_core/renderer/system_manager.cpp b/src/audio_core/renderer/system_manager.cpp index b326819ed..9c1331e19 100644 --- a/src/audio_core/renderer/system_manager.cpp +++ b/src/audio_core/renderer/system_manager.cpp | |||
| @@ -15,17 +15,14 @@ MICROPROFILE_DEFINE(Audio_RenderSystemManager, "Audio", "Render System Manager", | |||
| 15 | MP_RGB(60, 19, 97)); | 15 | MP_RGB(60, 19, 97)); |
| 16 | 16 | ||
| 17 | namespace AudioCore::AudioRenderer { | 17 | namespace AudioCore::AudioRenderer { |
| 18 | constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; | 18 | constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL}; |
| 19 | constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; | ||
| 20 | 19 | ||
| 21 | SystemManager::SystemManager(Core::System& core_) | 20 | SystemManager::SystemManager(Core::System& core_) |
| 22 | : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, | 21 | : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, |
| 23 | thread_event{Core::Timing::CreateEvent( | 22 | thread_event{Core::Timing::CreateEvent( |
| 24 | "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { | 23 | "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { |
| 25 | return ThreadFunc2(time); | 24 | return ThreadFunc2(time); |
| 26 | })} { | 25 | })} {} |
| 27 | core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); }); | ||
| 28 | } | ||
| 29 | 26 | ||
| 30 | SystemManager::~SystemManager() { | 27 | SystemManager::~SystemManager() { |
| 31 | Stop(); | 28 | Stop(); |
| @@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() { | |||
| 36 | if (adsp.Start()) { | 33 | if (adsp.Start()) { |
| 37 | active = true; | 34 | active = true; |
| 38 | thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); | 35 | thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); |
| 39 | core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), | 36 | core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME, |
| 40 | BaseRenderTime - RenderTimeOffset, thread_event); | 37 | thread_event); |
| 41 | } | 38 | } |
| 42 | } | 39 | } |
| 43 | 40 | ||
| @@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() { | |||
| 121 | } | 118 | } |
| 122 | 119 | ||
| 123 | std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { | 120 | std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { |
| 124 | std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt}; | ||
| 125 | const auto queue_size{core.AudioCore().GetStreamQueue()}; | ||
| 126 | switch (state) { | ||
| 127 | case StreamState::Filling: | ||
| 128 | if (queue_size >= 5) { | ||
| 129 | new_schedule_time = BaseRenderTime; | ||
| 130 | state = StreamState::Steady; | ||
| 131 | } | ||
| 132 | break; | ||
| 133 | case StreamState::Steady: | ||
| 134 | if (queue_size <= 2) { | ||
| 135 | new_schedule_time = BaseRenderTime - RenderTimeOffset; | ||
| 136 | state = StreamState::Filling; | ||
| 137 | } else if (queue_size > 5) { | ||
| 138 | new_schedule_time = BaseRenderTime + RenderTimeOffset; | ||
| 139 | state = StreamState::Draining; | ||
| 140 | } | ||
| 141 | break; | ||
| 142 | case StreamState::Draining: | ||
| 143 | if (queue_size <= 5) { | ||
| 144 | new_schedule_time = BaseRenderTime; | ||
| 145 | state = StreamState::Steady; | ||
| 146 | } | ||
| 147 | break; | ||
| 148 | } | ||
| 149 | |||
| 150 | update.store(true); | 121 | update.store(true); |
| 151 | update.notify_all(); | 122 | update.notify_all(); |
| 152 | return new_schedule_time; | 123 | return std::nullopt; |
| 153 | } | ||
| 154 | |||
| 155 | void SystemManager::PauseCallback(bool paused) { | ||
| 156 | if (paused && core.IsPoweredOn() && core.IsShuttingDown()) { | ||
| 157 | update.store(true); | ||
| 158 | update.notify_all(); | ||
| 159 | } | ||
| 160 | } | 124 | } |
| 161 | 125 | ||
| 162 | } // namespace AudioCore::AudioRenderer | 126 | } // namespace AudioCore::AudioRenderer |
diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h index 1291e9e0e..81457a3a1 100644 --- a/src/audio_core/renderer/system_manager.h +++ b/src/audio_core/renderer/system_manager.h | |||
| @@ -73,13 +73,6 @@ private: | |||
| 73 | */ | 73 | */ |
| 74 | std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); | 74 | std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); |
| 75 | 75 | ||
| 76 | /** | ||
| 77 | * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc. | ||
| 78 | * | ||
| 79 | * @param paused - Are we pausing or resuming? | ||
| 80 | */ | ||
| 81 | void PauseCallback(bool paused); | ||
| 82 | |||
| 83 | enum class StreamState { | 76 | enum class StreamState { |
| 84 | Filling, | 77 | Filling, |
| 85 | Steady, | 78 | Steady, |
| @@ -106,8 +99,6 @@ private: | |||
| 106 | std::shared_ptr<Core::Timing::EventType> thread_event; | 99 | std::shared_ptr<Core::Timing::EventType> thread_event; |
| 107 | /// Atomic for main thread to wait on | 100 | /// Atomic for main thread to wait on |
| 108 | std::atomic<bool> update{}; | 101 | std::atomic<bool> update{}; |
| 109 | /// Current state of the streams | ||
| 110 | StreamState state{StreamState::Filling}; | ||
| 111 | }; | 102 | }; |
| 112 | 103 | ||
| 113 | } // namespace AudioCore::AudioRenderer | 104 | } // namespace AudioCore::AudioRenderer |
diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h index 70cd42b08..83c697c0c 100644 --- a/src/audio_core/renderer/upsampler/upsampler_manager.h +++ b/src/audio_core/renderer/upsampler/upsampler_manager.h | |||
| @@ -27,7 +27,7 @@ public: | |||
| 27 | /** | 27 | /** |
| 28 | * Free the given upsampler. | 28 | * Free the given upsampler. |
| 29 | * | 29 | * |
| 30 | * @param The upsampler to be freed. | 30 | * @param info The upsampler to be freed. |
| 31 | */ | 31 | */ |
| 32 | void Free(UpsamplerInfo* info); | 32 | void Free(UpsamplerInfo* info); |
| 33 | 33 | ||
diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 896723e0c..930180895 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.h | |||
| @@ -185,7 +185,8 @@ public: | |||
| 185 | /** | 185 | /** |
| 186 | * Does this voice ned an update? | 186 | * Does this voice ned an update? |
| 187 | * | 187 | * |
| 188 | * @param params - Input parametetrs to check matching. | 188 | * @param params - Input parameters to check matching. |
| 189 | * | ||
| 189 | * @return True if this voice needs an update, otherwise false. | 190 | * @return True if this voice needs an update, otherwise false. |
| 190 | */ | 191 | */ |
| 191 | bool ShouldUpdateParameters(const InParameter& params) const; | 192 | bool ShouldUpdateParameters(const InParameter& params) const; |
| @@ -194,9 +195,9 @@ public: | |||
| 194 | * Update the parameters of this voice. | 195 | * Update the parameters of this voice. |
| 195 | * | 196 | * |
| 196 | * @param error_info - Output error code. | 197 | * @param error_info - Output error code. |
| 197 | * @param params - Input parametters to udpate from. | 198 | * @param params - Input parameters to update from. |
| 198 | * @param pool_mapper - Used to map buffers. | 199 | * @param pool_mapper - Used to map buffers. |
| 199 | * @param behavior - behavior to check supported features. | 200 | * @param behavior - behavior to check supported features. |
| 200 | */ | 201 | */ |
| 201 | void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, | 202 | void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, |
| 202 | const PoolMapper& pool_mapper, const BehaviorInfo& behavior); | 203 | const PoolMapper& pool_mapper, const BehaviorInfo& behavior); |
| @@ -218,12 +219,12 @@ public: | |||
| 218 | /** | 219 | /** |
| 219 | * Update all wavebuffers. | 220 | * Update all wavebuffers. |
| 220 | * | 221 | * |
| 221 | * @param error_infos - Output 2D array of errors, 2 per wavebuffer. | 222 | * @param error_infos - Output 2D array of errors, 2 per wavebuffer. |
| 222 | * @param error_count - Number of errors provided. Unused. | 223 | * @param error_count - Number of errors provided. Unused. |
| 223 | * @param params - Input parametters to be used for the update. | 224 | * @param params - Input parameters to be used for the update. |
| 224 | * @param voice_states - The voice states for each channel in this voice to be updated. | 225 | * @param voice_states - The voice states for each channel in this voice to be updated. |
| 225 | * @param pool_mapper - Used to map the wavebuffers. | 226 | * @param pool_mapper - Used to map the wavebuffers. |
| 226 | * @param behavior - Used to check for supported features. | 227 | * @param behavior - Used to check for supported features. |
| 227 | */ | 228 | */ |
| 228 | void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, | 229 | void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, |
| 229 | u32 error_count, const InParameter& params, | 230 | u32 error_count, const InParameter& params, |
| @@ -233,13 +234,13 @@ public: | |||
| 233 | /** | 234 | /** |
| 234 | * Update a wavebuffer. | 235 | * Update a wavebuffer. |
| 235 | * | 236 | * |
| 236 | * @param error_infos - Output array of errors. | 237 | * @param error_info - Output array of errors. |
| 237 | * @param wave_buffer - The wavebuffer to be updated. | 238 | * @param wave_buffer - The wavebuffer to be updated. |
| 238 | * @param wave_buffer_internal - Input parametters to be used for the update. | 239 | * @param wave_buffer_internal - Input parametters to be used for the update. |
| 239 | * @param sample_format - Sample format of the wavebuffer. | 240 | * @param sample_format - Sample format of the wavebuffer. |
| 240 | * @param valid - Is this wavebuffer valid? | 241 | * @param valid - Is this wavebuffer valid? |
| 241 | * @param pool_mapper - Used to map the wavebuffers. | 242 | * @param pool_mapper - Used to map the wavebuffers. |
| 242 | * @param behavior - Used to check for supported features. | 243 | * @param behavior - Used to check for supported features. |
| 243 | */ | 244 | */ |
| 244 | void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, | 245 | void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, |
| 245 | const WaveBufferInternal& wave_buffer_internal, | 246 | const WaveBufferInternal& wave_buffer_internal, |
| @@ -276,7 +277,7 @@ public: | |||
| 276 | /** | 277 | /** |
| 277 | * Check if this voice has any mixing connections. | 278 | * Check if this voice has any mixing connections. |
| 278 | * | 279 | * |
| 279 | * @return True if this voice participes in mixing, otherwise false. | 280 | * @return True if this voice participates in mixing, otherwise false. |
| 280 | */ | 281 | */ |
| 281 | bool HasAnyConnection() const; | 282 | bool HasAnyConnection() const; |
| 282 | 283 | ||
| @@ -301,7 +302,8 @@ public: | |||
| 301 | /** | 302 | /** |
| 302 | * Update this voice on command generation. | 303 | * Update this voice on command generation. |
| 303 | * | 304 | * |
| 304 | * @param voice_states - Voice states for these wavebuffers. | 305 | * @param voice_context - Voice context for these wavebuffers. |
| 306 | * | ||
| 305 | * @return True if this voice should be generated, otherwise false. | 307 | * @return True if this voice should be generated, otherwise false. |
| 306 | */ | 308 | */ |
| 307 | bool UpdateForCommandGeneration(VoiceContext& voice_context); | 309 | bool UpdateForCommandGeneration(VoiceContext& voice_context); |
diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 90d049e8e..36b115ad6 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp | |||
| @@ -1,21 +1,13 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <algorithm> | ||
| 5 | #include <atomic> | ||
| 6 | #include <span> | 4 | #include <span> |
| 5 | #include <vector> | ||
| 7 | 6 | ||
| 8 | #include "audio_core/audio_core.h" | 7 | #include "audio_core/common/common.h" |
| 9 | #include "audio_core/audio_event.h" | ||
| 10 | #include "audio_core/audio_manager.h" | ||
| 11 | #include "audio_core/sink/cubeb_sink.h" | 8 | #include "audio_core/sink/cubeb_sink.h" |
| 12 | #include "audio_core/sink/sink_stream.h" | 9 | #include "audio_core/sink/sink_stream.h" |
| 13 | #include "common/assert.h" | ||
| 14 | #include "common/fixed_point.h" | ||
| 15 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 16 | #include "common/reader_writer_queue.h" | ||
| 17 | #include "common/ring_buffer.h" | ||
| 18 | #include "common/settings.h" | ||
| 19 | #include "core/core.h" | 11 | #include "core/core.h" |
| 20 | 12 | ||
| 21 | #ifdef _WIN32 | 13 | #ifdef _WIN32 |
| @@ -42,10 +34,10 @@ public: | |||
| 42 | * @param system_ - Core system. | 34 | * @param system_ - Core system. |
| 43 | * @param event - Event used only for audio renderer, signalled on buffer consume. | 35 | * @param event - Event used only for audio renderer, signalled on buffer consume. |
| 44 | */ | 36 | */ |
| 45 | CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, | 37 | CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_, |
| 46 | cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, | 38 | cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, |
| 47 | const StreamType type_, Core::System& system_) | 39 | StreamType type_, Core::System& system_) |
| 48 | : ctx{ctx_}, type{type_}, system{system_} { | 40 | : SinkStream(system_, type_), ctx{ctx_} { |
| 49 | #ifdef _WIN32 | 41 | #ifdef _WIN32 |
| 50 | CoInitializeEx(nullptr, COINIT_MULTITHREADED); | 42 | CoInitializeEx(nullptr, COINIT_MULTITHREADED); |
| 51 | #endif | 43 | #endif |
| @@ -79,12 +71,10 @@ public: | |||
| 79 | 71 | ||
| 80 | minimum_latency = std::max(minimum_latency, 256u); | 72 | minimum_latency = std::max(minimum_latency, 256u); |
| 81 | 73 | ||
| 82 | playing_buffer.consumed = true; | 74 | LOG_INFO(Service_Audio, |
| 83 | 75 | "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " | |
| 84 | LOG_DEBUG(Service_Audio, | 76 | "latency {}", |
| 85 | "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " | 77 | name, type, params.rate, params.channels, system_channels, minimum_latency); |
| 86 | "latency {}", | ||
| 87 | name, type, params.rate, params.channels, system_channels, minimum_latency); | ||
| 88 | 78 | ||
| 89 | auto init_error{0}; | 79 | auto init_error{0}; |
| 90 | if (type == StreamType::In) { | 80 | if (type == StreamType::In) { |
| @@ -111,6 +101,8 @@ public: | |||
| 111 | ~CubebSinkStream() override { | 101 | ~CubebSinkStream() override { |
| 112 | LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); | 102 | LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); |
| 113 | 103 | ||
| 104 | Unstall(); | ||
| 105 | |||
| 114 | if (!ctx) { | 106 | if (!ctx) { |
| 115 | return; | 107 | return; |
| 116 | } | 108 | } |
| @@ -136,21 +128,14 @@ public: | |||
| 136 | * @param resume - Set to true if this is resuming the stream a previously-active stream. | 128 | * @param resume - Set to true if this is resuming the stream a previously-active stream. |
| 137 | * Default false. | 129 | * Default false. |
| 138 | */ | 130 | */ |
| 139 | void Start(const bool resume = false) override { | 131 | void Start(bool resume = false) override { |
| 140 | if (!ctx) { | 132 | if (!ctx || !paused) { |
| 141 | return; | 133 | return; |
| 142 | } | 134 | } |
| 143 | 135 | ||
| 144 | if (resume && was_playing) { | 136 | paused = false; |
| 145 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | 137 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { |
| 146 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | 138 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); |
| 147 | } | ||
| 148 | paused = false; | ||
| 149 | } else if (!resume) { | ||
| 150 | if (cubeb_stream_start(stream_backend) != CUBEB_OK) { | ||
| 151 | LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); | ||
| 152 | } | ||
| 153 | paused = false; | ||
| 154 | } | 139 | } |
| 155 | } | 140 | } |
| 156 | 141 | ||
| @@ -158,207 +143,20 @@ public: | |||
| 158 | * Stop the sink stream. | 143 | * Stop the sink stream. |
| 159 | */ | 144 | */ |
| 160 | void Stop() override { | 145 | void Stop() override { |
| 161 | if (!ctx) { | 146 | Unstall(); |
| 147 | |||
| 148 | if (!ctx || paused) { | ||
| 162 | return; | 149 | return; |
| 163 | } | 150 | } |
| 164 | 151 | ||
| 152 | paused = true; | ||
| 165 | if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { | 153 | if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { |
| 166 | LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); | 154 | LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); |
| 167 | } | 155 | } |
| 168 | |||
| 169 | was_playing.store(!paused); | ||
| 170 | paused = true; | ||
| 171 | } | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 175 | * | ||
| 176 | * @param buffer - Audio buffer information to be queued. | ||
| 177 | * @param samples - The s16 samples to be queue for playback. | ||
| 178 | */ | ||
| 179 | void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { | ||
| 180 | if (type == StreamType::In) { | ||
| 181 | queue.enqueue(buffer); | ||
| 182 | queued_buffers++; | ||
| 183 | } else { | ||
| 184 | constexpr s32 min{std::numeric_limits<s16>::min()}; | ||
| 185 | constexpr s32 max{std::numeric_limits<s16>::max()}; | ||
| 186 | |||
| 187 | auto yuzu_volume{Settings::Volume()}; | ||
| 188 | if (yuzu_volume > 1.0f) { | ||
| 189 | yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); | ||
| 190 | } | ||
| 191 | auto volume{system_volume * device_volume * yuzu_volume}; | ||
| 192 | |||
| 193 | if (system_channels == 6 && device_channels == 2) { | ||
| 194 | // We're given 6 channels, but our device only outputs 2, so downmix. | ||
| 195 | constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; | ||
| 196 | |||
| 197 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 198 | read_index += system_channels, write_index += device_channels) { | ||
| 199 | const auto left_sample{ | ||
| 200 | ((Common::FixedPoint<49, 15>( | ||
| 201 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 202 | down_mix_coeff[0] + | ||
| 203 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 204 | down_mix_coeff[1] + | ||
| 205 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 206 | down_mix_coeff[2] + | ||
| 207 | samples[read_index + static_cast<u32>(Channels::BackLeft)] * | ||
| 208 | down_mix_coeff[3]) * | ||
| 209 | volume) | ||
| 210 | .to_int()}; | ||
| 211 | |||
| 212 | const auto right_sample{ | ||
| 213 | ((Common::FixedPoint<49, 15>( | ||
| 214 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 215 | down_mix_coeff[0] + | ||
| 216 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 217 | down_mix_coeff[1] + | ||
| 218 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 219 | down_mix_coeff[2] + | ||
| 220 | samples[read_index + static_cast<u32>(Channels::BackRight)] * | ||
| 221 | down_mix_coeff[3]) * | ||
| 222 | volume) | ||
| 223 | .to_int()}; | ||
| 224 | |||
| 225 | samples[write_index + static_cast<u32>(Channels::FrontLeft)] = | ||
| 226 | static_cast<s16>(std::clamp(left_sample, min, max)); | ||
| 227 | samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 228 | static_cast<s16>(std::clamp(right_sample, min, max)); | ||
| 229 | } | ||
| 230 | |||
| 231 | samples.resize(samples.size() / system_channels * device_channels); | ||
| 232 | |||
| 233 | } else if (system_channels == 2 && device_channels == 6) { | ||
| 234 | // We need moar samples! Not all games will provide 6 channel audio. | ||
| 235 | // TODO: Implement some upmixing here. Currently just passthrough, with other | ||
| 236 | // channels left as silence. | ||
| 237 | std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); | ||
| 238 | |||
| 239 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 240 | read_index += system_channels, write_index += device_channels) { | ||
| 241 | const auto left_sample{static_cast<s16>(std::clamp( | ||
| 242 | static_cast<s32>( | ||
| 243 | static_cast<f32>( | ||
| 244 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 245 | volume), | ||
| 246 | min, max))}; | ||
| 247 | |||
| 248 | new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; | ||
| 249 | |||
| 250 | const auto right_sample{static_cast<s16>(std::clamp( | ||
| 251 | static_cast<s32>( | ||
| 252 | static_cast<f32>( | ||
| 253 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 254 | volume), | ||
| 255 | min, max))}; | ||
| 256 | |||
| 257 | new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 258 | right_sample; | ||
| 259 | } | ||
| 260 | samples = std::move(new_samples); | ||
| 261 | |||
| 262 | } else if (volume != 1.0f) { | ||
| 263 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 264 | samples[i] = static_cast<s16>(std::clamp( | ||
| 265 | static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 266 | } | ||
| 267 | } | ||
| 268 | |||
| 269 | samples_buffer.Push(samples); | ||
| 270 | queue.enqueue(buffer); | ||
| 271 | queued_buffers++; | ||
| 272 | } | ||
| 273 | } | ||
| 274 | |||
| 275 | /** | ||
| 276 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 277 | * | ||
| 278 | * @param num_samples - Maximum number of samples to receive. | ||
| 279 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 280 | */ | ||
| 281 | std::vector<s16> ReleaseBuffer(const u64 num_samples) override { | ||
| 282 | static constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 283 | static constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 284 | |||
| 285 | auto samples{samples_buffer.Pop(num_samples)}; | ||
| 286 | |||
| 287 | // TODO: Up-mix to 6 channels if the game expects it. | ||
| 288 | // For audio input this is unlikely to ever be the case though. | ||
| 289 | |||
| 290 | // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. | ||
| 291 | // TODO: Play with this and find something that works better. | ||
| 292 | auto volume{system_volume * device_volume * 8}; | ||
| 293 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 294 | samples[i] = static_cast<s16>( | ||
| 295 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 296 | } | ||
| 297 | |||
| 298 | if (samples.size() < num_samples) { | ||
| 299 | samples.resize(num_samples, 0); | ||
| 300 | } | ||
| 301 | return samples; | ||
| 302 | } | ||
| 303 | |||
| 304 | /** | ||
| 305 | * Check if a certain buffer has been consumed (fully played). | ||
| 306 | * | ||
| 307 | * @param tag - Unique tag of a buffer to check for. | ||
| 308 | * @return True if the buffer has been played, otherwise false. | ||
| 309 | */ | ||
| 310 | bool IsBufferConsumed(const u64 tag) override { | ||
| 311 | if (released_buffer.tag == 0) { | ||
| 312 | if (!released_buffers.try_dequeue(released_buffer)) { | ||
| 313 | return false; | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | if (released_buffer.tag == tag) { | ||
| 318 | released_buffer.tag = 0; | ||
| 319 | return true; | ||
| 320 | } | ||
| 321 | return false; | ||
| 322 | } | ||
| 323 | |||
| 324 | /** | ||
| 325 | * Empty out the buffer queue. | ||
| 326 | */ | ||
| 327 | void ClearQueue() override { | ||
| 328 | samples_buffer.Pop(); | ||
| 329 | while (queue.pop()) { | ||
| 330 | } | ||
| 331 | while (released_buffers.pop()) { | ||
| 332 | } | ||
| 333 | queued_buffers = 0; | ||
| 334 | released_buffer = {}; | ||
| 335 | playing_buffer = {}; | ||
| 336 | playing_buffer.consumed = true; | ||
| 337 | } | 156 | } |
| 338 | 157 | ||
| 339 | private: | 158 | private: |
| 340 | /** | 159 | /** |
| 341 | * Signal events back to the audio system that a buffer was played/can be filled. | ||
| 342 | * | ||
| 343 | * @param buffer - Consumed audio buffer to be released. | ||
| 344 | */ | ||
| 345 | void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { | ||
| 346 | auto& manager{system.AudioCore().GetAudioManager()}; | ||
| 347 | switch (type) { | ||
| 348 | case StreamType::Out: | ||
| 349 | released_buffers.enqueue(buffer); | ||
| 350 | manager.SetEvent(Event::Type::AudioOutManager, true); | ||
| 351 | break; | ||
| 352 | case StreamType::In: | ||
| 353 | released_buffers.enqueue(buffer); | ||
| 354 | manager.SetEvent(Event::Type::AudioInManager, true); | ||
| 355 | break; | ||
| 356 | case StreamType::Render: | ||
| 357 | break; | ||
| 358 | } | ||
| 359 | } | ||
| 360 | |||
| 361 | /** | ||
| 362 | * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will | 160 | * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will |
| 363 | * provide samples to be copied (audio in). | 161 | * provide samples to be copied (audio in). |
| 364 | * | 162 | * |
| @@ -378,106 +176,15 @@ private: | |||
| 378 | 176 | ||
| 379 | const std::size_t num_channels = impl->GetDeviceChannels(); | 177 | const std::size_t num_channels = impl->GetDeviceChannels(); |
| 380 | const std::size_t frame_size = num_channels; | 178 | const std::size_t frame_size = num_channels; |
| 381 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 382 | const std::size_t num_frames{static_cast<size_t>(num_frames_)}; | 179 | const std::size_t num_frames{static_cast<size_t>(num_frames_)}; |
| 383 | size_t frames_written{0}; | ||
| 384 | [[maybe_unused]] bool underrun{false}; | ||
| 385 | 180 | ||
| 386 | if (impl->type == StreamType::In) { | 181 | if (impl->type == StreamType::In) { |
| 387 | // INPUT | ||
| 388 | std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), | 182 | std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), |
| 389 | num_frames * frame_size}; | 183 | num_frames * frame_size}; |
| 390 | 184 | impl->ProcessAudioIn(input_buffer, num_frames); | |
| 391 | while (frames_written < num_frames) { | ||
| 392 | auto& playing_buffer{impl->playing_buffer}; | ||
| 393 | |||
| 394 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 395 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 396 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 397 | // If no buffer was available we've underrun, just push the samples and | ||
| 398 | // continue. | ||
| 399 | underrun = true; | ||
| 400 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 401 | (num_frames - frames_written) * frame_size); | ||
| 402 | frames_written = num_frames; | ||
| 403 | continue; | ||
| 404 | } else { | ||
| 405 | // Successfully got a new buffer, mark the old one as consumed and signal. | ||
| 406 | impl->queued_buffers--; | ||
| 407 | impl->SignalEvent(impl->playing_buffer); | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 412 | // amount we have left to fill | ||
| 413 | size_t frames_available{ | ||
| 414 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 415 | num_frames - frames_written)}; | ||
| 416 | |||
| 417 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 418 | frames_available * frame_size); | ||
| 419 | |||
| 420 | frames_written += frames_available; | ||
| 421 | playing_buffer.frames_played += frames_available; | ||
| 422 | |||
| 423 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 424 | // consumed | ||
| 425 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 426 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 427 | impl->playing_buffer.consumed = true; | ||
| 428 | } | ||
| 429 | } | ||
| 430 | |||
| 431 | std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], | ||
| 432 | frame_size_bytes); | ||
| 433 | } else { | 185 | } else { |
| 434 | // OUTPUT | ||
| 435 | std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; | 186 | std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; |
| 436 | 187 | impl->ProcessAudioOutAndRender(output_buffer, num_frames); | |
| 437 | while (frames_written < num_frames) { | ||
| 438 | auto& playing_buffer{impl->playing_buffer}; | ||
| 439 | |||
| 440 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 441 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 442 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 443 | // If no buffer was available we've underrun, fill the remaining buffer with | ||
| 444 | // the last written frame and continue. | ||
| 445 | underrun = true; | ||
| 446 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 447 | std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], | ||
| 448 | frame_size_bytes); | ||
| 449 | } | ||
| 450 | frames_written = num_frames; | ||
| 451 | continue; | ||
| 452 | } else { | ||
| 453 | // Successfully got a new buffer, mark the old one as consumed and signal. | ||
| 454 | impl->queued_buffers--; | ||
| 455 | impl->SignalEvent(impl->playing_buffer); | ||
| 456 | } | ||
| 457 | } | ||
| 458 | |||
| 459 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 460 | // amount we have left to fill | ||
| 461 | size_t frames_available{ | ||
| 462 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 463 | num_frames - frames_written)}; | ||
| 464 | |||
| 465 | impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], | ||
| 466 | frames_available * frame_size); | ||
| 467 | |||
| 468 | frames_written += frames_available; | ||
| 469 | playing_buffer.frames_played += frames_available; | ||
| 470 | |||
| 471 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 472 | // consumed | ||
| 473 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 474 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 475 | impl->playing_buffer.consumed = true; | ||
| 476 | } | ||
| 477 | } | ||
| 478 | |||
| 479 | std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], | ||
| 480 | frame_size_bytes); | ||
| 481 | } | 188 | } |
| 482 | 189 | ||
| 483 | return num_frames_; | 190 | return num_frames_; |
| @@ -490,32 +197,12 @@ private: | |||
| 490 | * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. | 197 | * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. |
| 491 | * @param state - New state of the device. | 198 | * @param state - New state of the device. |
| 492 | */ | 199 | */ |
| 493 | static void StateCallback([[maybe_unused]] cubeb_stream* stream, | 200 | static void StateCallback(cubeb_stream*, void*, cubeb_state) {} |
| 494 | [[maybe_unused]] void* user_data, | ||
| 495 | [[maybe_unused]] cubeb_state state) {} | ||
| 496 | 201 | ||
| 497 | /// Main Cubeb context | 202 | /// Main Cubeb context |
| 498 | cubeb* ctx{}; | 203 | cubeb* ctx{}; |
| 499 | /// Cubeb stream backend | 204 | /// Cubeb stream backend |
| 500 | cubeb_stream* stream_backend{}; | 205 | cubeb_stream* stream_backend{}; |
| 501 | /// Name of this stream | ||
| 502 | std::string name{}; | ||
| 503 | /// Type of this stream | ||
| 504 | StreamType type; | ||
| 505 | /// Core system | ||
| 506 | Core::System& system; | ||
| 507 | /// Ring buffer of the samples waiting to be played or consumed | ||
| 508 | Common::RingBuffer<s16, 0x10000> samples_buffer; | ||
| 509 | /// Audio buffers queued and waiting to play | ||
| 510 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; | ||
| 511 | /// The currently-playing audio buffer | ||
| 512 | ::AudioCore::Sink::SinkBuffer playing_buffer{}; | ||
| 513 | /// Audio buffers which have been played and are in queue to be released by the audio system | ||
| 514 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; | ||
| 515 | /// Currently released buffer waiting to be taken by the audio system | ||
| 516 | ::AudioCore::Sink::SinkBuffer released_buffer{}; | ||
| 517 | /// The last played (or received) frame of audio, used when the callback underruns | ||
| 518 | std::array<s16, MaxChannels> last_frame{}; | ||
| 519 | }; | 206 | }; |
| 520 | 207 | ||
| 521 | CubebSink::CubebSink(std::string_view target_device_name) { | 208 | CubebSink::CubebSink(std::string_view target_device_name) { |
| @@ -569,15 +256,15 @@ CubebSink::~CubebSink() { | |||
| 569 | #endif | 256 | #endif |
| 570 | } | 257 | } |
| 571 | 258 | ||
| 572 | SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | 259 | SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels, |
| 573 | const std::string& name, const StreamType type) { | 260 | const std::string& name, StreamType type) { |
| 574 | SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( | 261 | SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( |
| 575 | ctx, device_channels, system_channels, output_device, input_device, name, type, system)); | 262 | ctx, device_channels, system_channels, output_device, input_device, name, type, system)); |
| 576 | 263 | ||
| 577 | return stream.get(); | 264 | return stream.get(); |
| 578 | } | 265 | } |
| 579 | 266 | ||
| 580 | void CubebSink::CloseStream(const SinkStream* stream) { | 267 | void CubebSink::CloseStream(SinkStream* stream) { |
| 581 | for (size_t i = 0; i < sink_streams.size(); i++) { | 268 | for (size_t i = 0; i < sink_streams.size(); i++) { |
| 582 | if (sink_streams[i].get() == stream) { | 269 | if (sink_streams[i].get() == stream) { |
| 583 | sink_streams[i].reset(); | 270 | sink_streams[i].reset(); |
| @@ -591,18 +278,6 @@ void CubebSink::CloseStreams() { | |||
| 591 | sink_streams.clear(); | 278 | sink_streams.clear(); |
| 592 | } | 279 | } |
| 593 | 280 | ||
| 594 | void CubebSink::PauseStreams() { | ||
| 595 | for (auto& stream : sink_streams) { | ||
| 596 | stream->Stop(); | ||
| 597 | } | ||
| 598 | } | ||
| 599 | |||
| 600 | void CubebSink::UnpauseStreams() { | ||
| 601 | for (auto& stream : sink_streams) { | ||
| 602 | stream->Start(true); | ||
| 603 | } | ||
| 604 | } | ||
| 605 | |||
| 606 | f32 CubebSink::GetDeviceVolume() const { | 281 | f32 CubebSink::GetDeviceVolume() const { |
| 607 | if (sink_streams.empty()) { | 282 | if (sink_streams.empty()) { |
| 608 | return 1.0f; | 283 | return 1.0f; |
| @@ -611,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const { | |||
| 611 | return sink_streams[0]->GetDeviceVolume(); | 286 | return sink_streams[0]->GetDeviceVolume(); |
| 612 | } | 287 | } |
| 613 | 288 | ||
| 614 | void CubebSink::SetDeviceVolume(const f32 volume) { | 289 | void CubebSink::SetDeviceVolume(f32 volume) { |
| 615 | for (auto& stream : sink_streams) { | 290 | for (auto& stream : sink_streams) { |
| 616 | stream->SetDeviceVolume(volume); | 291 | stream->SetDeviceVolume(volume); |
| 617 | } | 292 | } |
| 618 | } | 293 | } |
| 619 | 294 | ||
| 620 | void CubebSink::SetSystemVolume(const f32 volume) { | 295 | void CubebSink::SetSystemVolume(f32 volume) { |
| 621 | for (auto& stream : sink_streams) { | 296 | for (auto& stream : sink_streams) { |
| 622 | stream->SetSystemVolume(volume); | 297 | stream->SetSystemVolume(volume); |
| 623 | } | 298 | } |
| 624 | } | 299 | } |
| 625 | 300 | ||
| 626 | std::vector<std::string> ListCubebSinkDevices(const bool capture) { | 301 | std::vector<std::string> ListCubebSinkDevices(bool capture) { |
| 627 | std::vector<std::string> device_list; | 302 | std::vector<std::string> device_list; |
| 628 | cubeb* ctx; | 303 | cubeb* ctx; |
| 629 | 304 | ||
diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index f0f43dfa1..4b0cb160d 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h | |||
| @@ -34,8 +34,7 @@ public: | |||
| 34 | * May differ from the device's channel count. | 34 | * May differ from the device's channel count. |
| 35 | * @param name - Name of this stream. | 35 | * @param name - Name of this stream. |
| 36 | * @param type - Type of this stream, render/in/out. | 36 | * @param type - Type of this stream, render/in/out. |
| 37 | * @param event - Audio render only, a signal used to prevent the renderer running too | 37 | * |
| 38 | * fast. | ||
| 39 | * @return A pointer to the created SinkStream | 38 | * @return A pointer to the created SinkStream |
| 40 | */ | 39 | */ |
| 41 | SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, | 40 | SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, |
| @@ -46,7 +45,7 @@ public: | |||
| 46 | * | 45 | * |
| 47 | * @param stream - The stream to close. | 46 | * @param stream - The stream to close. |
| 48 | */ | 47 | */ |
| 49 | void CloseStream(const SinkStream* stream) override; | 48 | void CloseStream(SinkStream* stream) override; |
| 50 | 49 | ||
| 51 | /** | 50 | /** |
| 52 | * Close all streams. | 51 | * Close all streams. |
| @@ -54,16 +53,6 @@ public: | |||
| 54 | void CloseStreams() override; | 53 | void CloseStreams() override; |
| 55 | 54 | ||
| 56 | /** | 55 | /** |
| 57 | * Pause all streams. | ||
| 58 | */ | ||
| 59 | void PauseStreams() override; | ||
| 60 | |||
| 61 | /** | ||
| 62 | * Unpause all streams. | ||
| 63 | */ | ||
| 64 | void UnpauseStreams() override; | ||
| 65 | |||
| 66 | /** | ||
| 67 | * Get the device volume. Set from calls to the IAudioDevice service. | 56 | * Get the device volume. Set from calls to the IAudioDevice service. |
| 68 | * | 57 | * |
| 69 | * @return Volume of the device. | 58 | * @return Volume of the device. |
| @@ -101,7 +90,7 @@ private: | |||
| 101 | }; | 90 | }; |
| 102 | 91 | ||
| 103 | /** | 92 | /** |
| 104 | * Get a list of conencted devices from Cubeb. | 93 | * Get a list of connected devices from Cubeb. |
| 105 | * | 94 | * |
| 106 | * @param capture - Return input (capture) devices if true, otherwise output devices. | 95 | * @param capture - Return input (capture) devices if true, otherwise output devices. |
| 107 | */ | 96 | */ |
diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h index 47a342171..1215d3cd2 100644 --- a/src/audio_core/sink/null_sink.h +++ b/src/audio_core/sink/null_sink.h | |||
| @@ -3,10 +3,29 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <string> | ||
| 7 | #include <string_view> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 6 | #include "audio_core/sink/sink.h" | 10 | #include "audio_core/sink/sink.h" |
| 7 | #include "audio_core/sink/sink_stream.h" | 11 | #include "audio_core/sink/sink_stream.h" |
| 8 | 12 | ||
| 13 | namespace Core { | ||
| 14 | class System; | ||
| 15 | } // namespace Core | ||
| 16 | |||
| 9 | namespace AudioCore::Sink { | 17 | namespace AudioCore::Sink { |
| 18 | class NullSinkStreamImpl final : public SinkStream { | ||
| 19 | public: | ||
| 20 | explicit NullSinkStreamImpl(Core::System& system_, StreamType type_) | ||
| 21 | : SinkStream{system_, type_} {} | ||
| 22 | ~NullSinkStreamImpl() override {} | ||
| 23 | void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {} | ||
| 24 | std::vector<s16> ReleaseBuffer(u64) override { | ||
| 25 | return {}; | ||
| 26 | } | ||
| 27 | }; | ||
| 28 | |||
| 10 | /** | 29 | /** |
| 11 | * A no-op sink for when no audio out is wanted. | 30 | * A no-op sink for when no audio out is wanted. |
| 12 | */ | 31 | */ |
| @@ -15,17 +34,16 @@ public: | |||
| 15 | explicit NullSink(std::string_view) {} | 34 | explicit NullSink(std::string_view) {} |
| 16 | ~NullSink() override = default; | 35 | ~NullSink() override = default; |
| 17 | 36 | ||
| 18 | SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, | 37 | SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&, |
| 19 | [[maybe_unused]] u32 system_channels, | 38 | StreamType type) override { |
| 20 | [[maybe_unused]] const std::string& name, | 39 | if (null_sink == nullptr) { |
| 21 | [[maybe_unused]] StreamType type) override { | 40 | null_sink = std::make_unique<NullSinkStreamImpl>(system, type); |
| 22 | return &null_sink_stream; | 41 | } |
| 42 | return null_sink.get(); | ||
| 23 | } | 43 | } |
| 24 | 44 | ||
| 25 | void CloseStream([[maybe_unused]] const SinkStream* stream) override {} | 45 | void CloseStream(SinkStream*) override {} |
| 26 | void CloseStreams() override {} | 46 | void CloseStreams() override {} |
| 27 | void PauseStreams() override {} | ||
| 28 | void UnpauseStreams() override {} | ||
| 29 | f32 GetDeviceVolume() const override { | 47 | f32 GetDeviceVolume() const override { |
| 30 | return 1.0f; | 48 | return 1.0f; |
| 31 | } | 49 | } |
| @@ -33,20 +51,7 @@ public: | |||
| 33 | void SetSystemVolume(f32 volume) override {} | 51 | void SetSystemVolume(f32 volume) override {} |
| 34 | 52 | ||
| 35 | private: | 53 | private: |
| 36 | struct NullSinkStreamImpl final : SinkStream { | 54 | SinkStreamPtr null_sink{}; |
| 37 | void Finalize() override {} | ||
| 38 | void Start(bool resume = false) override {} | ||
| 39 | void Stop() override {} | ||
| 40 | void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer, | ||
| 41 | [[maybe_unused]] std::vector<s16>& samples) override {} | ||
| 42 | std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override { | ||
| 43 | return {}; | ||
| 44 | } | ||
| 45 | bool IsBufferConsumed([[maybe_unused]] const u64 tag) { | ||
| 46 | return true; | ||
| 47 | } | ||
| 48 | void ClearQueue() override {} | ||
| 49 | } null_sink_stream; | ||
| 50 | }; | 55 | }; |
| 51 | 56 | ||
| 52 | } // namespace AudioCore::Sink | 57 | } // namespace AudioCore::Sink |
diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index d6c9ec90d..1bd001b94 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp | |||
| @@ -1,20 +1,13 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <algorithm> | 4 | #include <span> |
| 5 | #include <atomic> | 5 | #include <vector> |
| 6 | 6 | ||
| 7 | #include "audio_core/audio_core.h" | 7 | #include "audio_core/common/common.h" |
| 8 | #include "audio_core/audio_event.h" | ||
| 9 | #include "audio_core/audio_manager.h" | ||
| 10 | #include "audio_core/sink/sdl2_sink.h" | 8 | #include "audio_core/sink/sdl2_sink.h" |
| 11 | #include "audio_core/sink/sink_stream.h" | 9 | #include "audio_core/sink/sink_stream.h" |
| 12 | #include "common/assert.h" | ||
| 13 | #include "common/fixed_point.h" | ||
| 14 | #include "common/logging/log.h" | 10 | #include "common/logging/log.h" |
| 15 | #include "common/reader_writer_queue.h" | ||
| 16 | #include "common/ring_buffer.h" | ||
| 17 | #include "common/settings.h" | ||
| 18 | #include "core/core.h" | 11 | #include "core/core.h" |
| 19 | 12 | ||
| 20 | // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 | 13 | // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 |
| @@ -44,10 +37,9 @@ public: | |||
| 44 | * @param system_ - Core system. | 37 | * @param system_ - Core system. |
| 45 | * @param event - Event used only for audio renderer, signalled on buffer consume. | 38 | * @param event - Event used only for audio renderer, signalled on buffer consume. |
| 46 | */ | 39 | */ |
| 47 | SDLSinkStream(u32 device_channels_, const u32 system_channels_, | 40 | SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device, |
| 48 | const std::string& output_device, const std::string& input_device, | 41 | const std::string& input_device, StreamType type_, Core::System& system_) |
| 49 | const StreamType type_, Core::System& system_) | 42 | : SinkStream{system_, type_} { |
| 50 | : type{type_}, system{system_} { | ||
| 51 | system_channels = system_channels_; | 43 | system_channels = system_channels_; |
| 52 | device_channels = device_channels_; | 44 | device_channels = device_channels_; |
| 53 | 45 | ||
| @@ -63,8 +55,6 @@ public: | |||
| 63 | spec.callback = &SDLSinkStream::DataCallback; | 55 | spec.callback = &SDLSinkStream::DataCallback; |
| 64 | spec.userdata = this; | 56 | spec.userdata = this; |
| 65 | 57 | ||
| 66 | playing_buffer.consumed = true; | ||
| 67 | |||
| 68 | std::string device_name{output_device}; | 58 | std::string device_name{output_device}; |
| 69 | bool capture{false}; | 59 | bool capture{false}; |
| 70 | if (type == StreamType::In) { | 60 | if (type == StreamType::In) { |
| @@ -84,31 +74,30 @@ public: | |||
| 84 | return; | 74 | return; |
| 85 | } | 75 | } |
| 86 | 76 | ||
| 87 | LOG_DEBUG(Service_Audio, | 77 | LOG_INFO(Service_Audio, |
| 88 | "Opening sdl stream {} with: rate {} channels {} (system channels {}) " | 78 | "Opening SDL stream {} with: rate {} channels {} (system channels {}) " |
| 89 | " samples {}", | 79 | " samples {}", |
| 90 | device, obtained.freq, obtained.channels, system_channels, obtained.samples); | 80 | device, obtained.freq, obtained.channels, system_channels, obtained.samples); |
| 91 | } | 81 | } |
| 92 | 82 | ||
| 93 | /** | 83 | /** |
| 94 | * Destroy the sink stream. | 84 | * Destroy the sink stream. |
| 95 | */ | 85 | */ |
| 96 | ~SDLSinkStream() override { | 86 | ~SDLSinkStream() override { |
| 97 | if (device == 0) { | 87 | LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name); |
| 98 | return; | 88 | Finalize(); |
| 99 | } | ||
| 100 | |||
| 101 | SDL_CloseAudioDevice(device); | ||
| 102 | } | 89 | } |
| 103 | 90 | ||
| 104 | /** | 91 | /** |
| 105 | * Finalize the sink stream. | 92 | * Finalize the sink stream. |
| 106 | */ | 93 | */ |
| 107 | void Finalize() override { | 94 | void Finalize() override { |
| 95 | Unstall(); | ||
| 108 | if (device == 0) { | 96 | if (device == 0) { |
| 109 | return; | 97 | return; |
| 110 | } | 98 | } |
| 111 | 99 | ||
| 100 | Stop(); | ||
| 112 | SDL_CloseAudioDevice(device); | 101 | SDL_CloseAudioDevice(device); |
| 113 | } | 102 | } |
| 114 | 103 | ||
| @@ -118,217 +107,29 @@ public: | |||
| 118 | * @param resume - Set to true if this is resuming the stream a previously-active stream. | 107 | * @param resume - Set to true if this is resuming the stream a previously-active stream. |
| 119 | * Default false. | 108 | * Default false. |
| 120 | */ | 109 | */ |
| 121 | void Start(const bool resume = false) override { | 110 | void Start(bool resume = false) override { |
| 122 | if (device == 0) { | 111 | if (device == 0 || !paused) { |
| 123 | return; | 112 | return; |
| 124 | } | 113 | } |
| 125 | 114 | ||
| 126 | if (resume && was_playing) { | 115 | paused = false; |
| 127 | SDL_PauseAudioDevice(device, 0); | 116 | SDL_PauseAudioDevice(device, 0); |
| 128 | paused = false; | ||
| 129 | } else if (!resume) { | ||
| 130 | SDL_PauseAudioDevice(device, 0); | ||
| 131 | paused = false; | ||
| 132 | } | ||
| 133 | } | 117 | } |
| 134 | 118 | ||
| 135 | /** | 119 | /** |
| 136 | * Stop the sink stream. | 120 | * Stop the sink stream. |
| 137 | */ | 121 | */ |
| 138 | void Stop() { | 122 | void Stop() override { |
| 139 | if (device == 0) { | 123 | Unstall(); |
| 124 | if (device == 0 || paused) { | ||
| 140 | return; | 125 | return; |
| 141 | } | 126 | } |
| 142 | SDL_PauseAudioDevice(device, 1); | ||
| 143 | paused = true; | 127 | paused = true; |
| 144 | } | 128 | SDL_PauseAudioDevice(device, 1); |
| 145 | |||
| 146 | /** | ||
| 147 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 148 | * | ||
| 149 | * @param buffer - Audio buffer information to be queued. | ||
| 150 | * @param samples - The s16 samples to be queue for playback. | ||
| 151 | */ | ||
| 152 | void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { | ||
| 153 | if (type == StreamType::In) { | ||
| 154 | queue.enqueue(buffer); | ||
| 155 | queued_buffers++; | ||
| 156 | } else { | ||
| 157 | constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 158 | constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 159 | |||
| 160 | auto yuzu_volume{Settings::Volume()}; | ||
| 161 | auto volume{system_volume * device_volume * yuzu_volume}; | ||
| 162 | |||
| 163 | if (system_channels == 6 && device_channels == 2) { | ||
| 164 | // We're given 6 channels, but our device only outputs 2, so downmix. | ||
| 165 | constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; | ||
| 166 | |||
| 167 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 168 | read_index += system_channels, write_index += device_channels) { | ||
| 169 | const auto left_sample{ | ||
| 170 | ((Common::FixedPoint<49, 15>( | ||
| 171 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 172 | down_mix_coeff[0] + | ||
| 173 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 174 | down_mix_coeff[1] + | ||
| 175 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 176 | down_mix_coeff[2] + | ||
| 177 | samples[read_index + static_cast<u32>(Channels::BackLeft)] * | ||
| 178 | down_mix_coeff[3]) * | ||
| 179 | volume) | ||
| 180 | .to_int()}; | ||
| 181 | |||
| 182 | const auto right_sample{ | ||
| 183 | ((Common::FixedPoint<49, 15>( | ||
| 184 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 185 | down_mix_coeff[0] + | ||
| 186 | samples[read_index + static_cast<u32>(Channels::Center)] * | ||
| 187 | down_mix_coeff[1] + | ||
| 188 | samples[read_index + static_cast<u32>(Channels::LFE)] * | ||
| 189 | down_mix_coeff[2] + | ||
| 190 | samples[read_index + static_cast<u32>(Channels::BackRight)] * | ||
| 191 | down_mix_coeff[3]) * | ||
| 192 | volume) | ||
| 193 | .to_int()}; | ||
| 194 | |||
| 195 | samples[write_index + static_cast<u32>(Channels::FrontLeft)] = | ||
| 196 | static_cast<s16>(std::clamp(left_sample, min, max)); | ||
| 197 | samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 198 | static_cast<s16>(std::clamp(right_sample, min, max)); | ||
| 199 | } | ||
| 200 | |||
| 201 | samples.resize(samples.size() / system_channels * device_channels); | ||
| 202 | |||
| 203 | } else if (system_channels == 2 && device_channels == 6) { | ||
| 204 | // We need moar samples! Not all games will provide 6 channel audio. | ||
| 205 | // TODO: Implement some upmixing here. Currently just passthrough, with other | ||
| 206 | // channels left as silence. | ||
| 207 | std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); | ||
| 208 | |||
| 209 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 210 | read_index += system_channels, write_index += device_channels) { | ||
| 211 | const auto left_sample{static_cast<s16>(std::clamp( | ||
| 212 | static_cast<s32>( | ||
| 213 | static_cast<f32>( | ||
| 214 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 215 | volume), | ||
| 216 | min, max))}; | ||
| 217 | |||
| 218 | new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; | ||
| 219 | |||
| 220 | const auto right_sample{static_cast<s16>(std::clamp( | ||
| 221 | static_cast<s32>( | ||
| 222 | static_cast<f32>( | ||
| 223 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 224 | volume), | ||
| 225 | min, max))}; | ||
| 226 | |||
| 227 | new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 228 | right_sample; | ||
| 229 | } | ||
| 230 | samples = std::move(new_samples); | ||
| 231 | |||
| 232 | } else if (volume != 1.0f) { | ||
| 233 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 234 | samples[i] = static_cast<s16>(std::clamp( | ||
| 235 | static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 236 | } | ||
| 237 | } | ||
| 238 | |||
| 239 | samples_buffer.Push(samples); | ||
| 240 | queue.enqueue(buffer); | ||
| 241 | queued_buffers++; | ||
| 242 | } | ||
| 243 | } | ||
| 244 | |||
| 245 | /** | ||
| 246 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 247 | * | ||
| 248 | * @param num_samples - Maximum number of samples to receive. | ||
| 249 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 250 | */ | ||
| 251 | std::vector<s16> ReleaseBuffer(const u64 num_samples) override { | ||
| 252 | static constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 253 | static constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 254 | |||
| 255 | auto samples{samples_buffer.Pop(num_samples)}; | ||
| 256 | |||
| 257 | // TODO: Up-mix to 6 channels if the game expects it. | ||
| 258 | // For audio input this is unlikely to ever be the case though. | ||
| 259 | |||
| 260 | // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. | ||
| 261 | // TODO: Play with this and find something that works better. | ||
| 262 | auto volume{system_volume * device_volume * 8}; | ||
| 263 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 264 | samples[i] = static_cast<s16>( | ||
| 265 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 266 | } | ||
| 267 | |||
| 268 | if (samples.size() < num_samples) { | ||
| 269 | samples.resize(num_samples, 0); | ||
| 270 | } | ||
| 271 | return samples; | ||
| 272 | } | ||
| 273 | |||
| 274 | /** | ||
| 275 | * Check if a certain buffer has been consumed (fully played). | ||
| 276 | * | ||
| 277 | * @param tag - Unique tag of a buffer to check for. | ||
| 278 | * @return True if the buffer has been played, otherwise false. | ||
| 279 | */ | ||
| 280 | bool IsBufferConsumed(const u64 tag) override { | ||
| 281 | if (released_buffer.tag == 0) { | ||
| 282 | if (!released_buffers.try_dequeue(released_buffer)) { | ||
| 283 | return false; | ||
| 284 | } | ||
| 285 | } | ||
| 286 | |||
| 287 | if (released_buffer.tag == tag) { | ||
| 288 | released_buffer.tag = 0; | ||
| 289 | return true; | ||
| 290 | } | ||
| 291 | return false; | ||
| 292 | } | ||
| 293 | |||
| 294 | /** | ||
| 295 | * Empty out the buffer queue. | ||
| 296 | */ | ||
| 297 | void ClearQueue() override { | ||
| 298 | samples_buffer.Pop(); | ||
| 299 | while (queue.pop()) { | ||
| 300 | } | ||
| 301 | while (released_buffers.pop()) { | ||
| 302 | } | ||
| 303 | released_buffer = {}; | ||
| 304 | playing_buffer = {}; | ||
| 305 | playing_buffer.consumed = true; | ||
| 306 | queued_buffers = 0; | ||
| 307 | } | 129 | } |
| 308 | 130 | ||
| 309 | private: | 131 | private: |
| 310 | /** | 132 | /** |
| 311 | * Signal events back to the audio system that a buffer was played/can be filled. | ||
| 312 | * | ||
| 313 | * @param buffer - Consumed audio buffer to be released. | ||
| 314 | */ | ||
| 315 | void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { | ||
| 316 | auto& manager{system.AudioCore().GetAudioManager()}; | ||
| 317 | switch (type) { | ||
| 318 | case StreamType::Out: | ||
| 319 | released_buffers.enqueue(buffer); | ||
| 320 | manager.SetEvent(Event::Type::AudioOutManager, true); | ||
| 321 | break; | ||
| 322 | case StreamType::In: | ||
| 323 | released_buffers.enqueue(buffer); | ||
| 324 | manager.SetEvent(Event::Type::AudioInManager, true); | ||
| 325 | break; | ||
| 326 | case StreamType::Render: | ||
| 327 | break; | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | /** | ||
| 332 | * Main callback from SDL. Either expects samples from us (audio render/audio out), or will | 133 | * Main callback from SDL. Either expects samples from us (audio render/audio out), or will |
| 333 | * provide samples to be copied (audio in). | 134 | * provide samples to be copied (audio in). |
| 334 | * | 135 | * |
| @@ -345,122 +146,20 @@ private: | |||
| 345 | 146 | ||
| 346 | const std::size_t num_channels = impl->GetDeviceChannels(); | 147 | const std::size_t num_channels = impl->GetDeviceChannels(); |
| 347 | const std::size_t frame_size = num_channels; | 148 | const std::size_t frame_size = num_channels; |
| 348 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 349 | const std::size_t num_frames{len / num_channels / sizeof(s16)}; | 149 | const std::size_t num_frames{len / num_channels / sizeof(s16)}; |
| 350 | size_t frames_written{0}; | ||
| 351 | [[maybe_unused]] bool underrun{false}; | ||
| 352 | 150 | ||
| 353 | if (impl->type == StreamType::In) { | 151 | if (impl->type == StreamType::In) { |
| 354 | std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; | 152 | std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream), |
| 355 | 153 | num_frames * frame_size}; | |
| 356 | while (frames_written < num_frames) { | 154 | impl->ProcessAudioIn(input_buffer, num_frames); |
| 357 | auto& playing_buffer{impl->playing_buffer}; | ||
| 358 | |||
| 359 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 360 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 361 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 362 | // If no buffer was available we've underrun, just push the samples and | ||
| 363 | // continue. | ||
| 364 | underrun = true; | ||
| 365 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 366 | (num_frames - frames_written) * frame_size); | ||
| 367 | frames_written = num_frames; | ||
| 368 | continue; | ||
| 369 | } else { | ||
| 370 | impl->queued_buffers--; | ||
| 371 | impl->SignalEvent(impl->playing_buffer); | ||
| 372 | } | ||
| 373 | } | ||
| 374 | |||
| 375 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 376 | // amount we have left to fill | ||
| 377 | size_t frames_available{ | ||
| 378 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 379 | num_frames - frames_written)}; | ||
| 380 | |||
| 381 | impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 382 | frames_available * frame_size); | ||
| 383 | |||
| 384 | frames_written += frames_available; | ||
| 385 | playing_buffer.frames_played += frames_available; | ||
| 386 | |||
| 387 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 388 | // consumed | ||
| 389 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 390 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 391 | impl->playing_buffer.consumed = true; | ||
| 392 | } | ||
| 393 | } | ||
| 394 | |||
| 395 | std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], | ||
| 396 | frame_size_bytes); | ||
| 397 | } else { | 155 | } else { |
| 398 | std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; | 156 | std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; |
| 399 | 157 | impl->ProcessAudioOutAndRender(output_buffer, num_frames); | |
| 400 | while (frames_written < num_frames) { | ||
| 401 | auto& playing_buffer{impl->playing_buffer}; | ||
| 402 | |||
| 403 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 404 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 405 | if (!impl->queue.try_dequeue(impl->playing_buffer)) { | ||
| 406 | // If no buffer was available we've underrun, fill the remaining buffer with | ||
| 407 | // the last written frame and continue. | ||
| 408 | underrun = true; | ||
| 409 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 410 | std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], | ||
| 411 | frame_size_bytes); | ||
| 412 | } | ||
| 413 | frames_written = num_frames; | ||
| 414 | continue; | ||
| 415 | } else { | ||
| 416 | impl->queued_buffers--; | ||
| 417 | impl->SignalEvent(impl->playing_buffer); | ||
| 418 | } | ||
| 419 | } | ||
| 420 | |||
| 421 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 422 | // amount we have left to fill | ||
| 423 | size_t frames_available{ | ||
| 424 | std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 425 | num_frames - frames_written)}; | ||
| 426 | |||
| 427 | impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], | ||
| 428 | frames_available * frame_size); | ||
| 429 | |||
| 430 | frames_written += frames_available; | ||
| 431 | playing_buffer.frames_played += frames_available; | ||
| 432 | |||
| 433 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 434 | // consumed | ||
| 435 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 436 | impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); | ||
| 437 | impl->playing_buffer.consumed = true; | ||
| 438 | } | ||
| 439 | } | ||
| 440 | |||
| 441 | std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], | ||
| 442 | frame_size_bytes); | ||
| 443 | } | 158 | } |
| 444 | } | 159 | } |
| 445 | 160 | ||
| 446 | /// SDL device id of the opened input/output device | 161 | /// SDL device id of the opened input/output device |
| 447 | SDL_AudioDeviceID device{}; | 162 | SDL_AudioDeviceID device{}; |
| 448 | /// Type of this stream | ||
| 449 | StreamType type; | ||
| 450 | /// Core system | ||
| 451 | Core::System& system; | ||
| 452 | /// Ring buffer of the samples waiting to be played or consumed | ||
| 453 | Common::RingBuffer<s16, 0x10000> samples_buffer; | ||
| 454 | /// Audio buffers queued and waiting to play | ||
| 455 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; | ||
| 456 | /// The currently-playing audio buffer | ||
| 457 | ::AudioCore::Sink::SinkBuffer playing_buffer{}; | ||
| 458 | /// Audio buffers which have been played and are in queue to be released by the audio system | ||
| 459 | Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; | ||
| 460 | /// Currently released buffer waiting to be taken by the audio system | ||
| 461 | ::AudioCore::Sink::SinkBuffer released_buffer{}; | ||
| 462 | /// The last played (or received) frame of audio, used when the callback underruns | ||
| 463 | std::array<s16, MaxChannels> last_frame{}; | ||
| 464 | }; | 163 | }; |
| 465 | 164 | ||
| 466 | SDLSink::SDLSink(std::string_view target_device_name) { | 165 | SDLSink::SDLSink(std::string_view target_device_name) { |
| @@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) { | |||
| 482 | 181 | ||
| 483 | SDLSink::~SDLSink() = default; | 182 | SDLSink::~SDLSink() = default; |
| 484 | 183 | ||
| 485 | SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, | 184 | SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels, |
| 486 | const std::string&, const StreamType type) { | 185 | const std::string&, StreamType type) { |
| 487 | SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( | 186 | SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( |
| 488 | device_channels, system_channels, output_device, input_device, type, system)); | 187 | device_channels, system_channels, output_device, input_device, type, system)); |
| 489 | return stream.get(); | 188 | return stream.get(); |
| 490 | } | 189 | } |
| 491 | 190 | ||
| 492 | void SDLSink::CloseStream(const SinkStream* stream) { | 191 | void SDLSink::CloseStream(SinkStream* stream) { |
| 493 | for (size_t i = 0; i < sink_streams.size(); i++) { | 192 | for (size_t i = 0; i < sink_streams.size(); i++) { |
| 494 | if (sink_streams[i].get() == stream) { | 193 | if (sink_streams[i].get() == stream) { |
| 495 | sink_streams[i].reset(); | 194 | sink_streams[i].reset(); |
| @@ -503,18 +202,6 @@ void SDLSink::CloseStreams() { | |||
| 503 | sink_streams.clear(); | 202 | sink_streams.clear(); |
| 504 | } | 203 | } |
| 505 | 204 | ||
| 506 | void SDLSink::PauseStreams() { | ||
| 507 | for (auto& stream : sink_streams) { | ||
| 508 | stream->Stop(); | ||
| 509 | } | ||
| 510 | } | ||
| 511 | |||
| 512 | void SDLSink::UnpauseStreams() { | ||
| 513 | for (auto& stream : sink_streams) { | ||
| 514 | stream->Start(); | ||
| 515 | } | ||
| 516 | } | ||
| 517 | |||
| 518 | f32 SDLSink::GetDeviceVolume() const { | 205 | f32 SDLSink::GetDeviceVolume() const { |
| 519 | if (sink_streams.empty()) { | 206 | if (sink_streams.empty()) { |
| 520 | return 1.0f; | 207 | return 1.0f; |
| @@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const { | |||
| 523 | return sink_streams[0]->GetDeviceVolume(); | 210 | return sink_streams[0]->GetDeviceVolume(); |
| 524 | } | 211 | } |
| 525 | 212 | ||
| 526 | void SDLSink::SetDeviceVolume(const f32 volume) { | 213 | void SDLSink::SetDeviceVolume(f32 volume) { |
| 527 | for (auto& stream : sink_streams) { | 214 | for (auto& stream : sink_streams) { |
| 528 | stream->SetDeviceVolume(volume); | 215 | stream->SetDeviceVolume(volume); |
| 529 | } | 216 | } |
| 530 | } | 217 | } |
| 531 | 218 | ||
| 532 | void SDLSink::SetSystemVolume(const f32 volume) { | 219 | void SDLSink::SetSystemVolume(f32 volume) { |
| 533 | for (auto& stream : sink_streams) { | 220 | for (auto& stream : sink_streams) { |
| 534 | stream->SetSystemVolume(volume); | 221 | stream->SetSystemVolume(volume); |
| 535 | } | 222 | } |
| 536 | } | 223 | } |
| 537 | 224 | ||
| 538 | std::vector<std::string> ListSDLSinkDevices(const bool capture) { | 225 | std::vector<std::string> ListSDLSinkDevices(bool capture) { |
| 539 | std::vector<std::string> device_list; | 226 | std::vector<std::string> device_list; |
| 540 | 227 | ||
| 541 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { | 228 | if (!SDL_WasInit(SDL_INIT_AUDIO)) { |
diff --git a/src/audio_core/sink/sdl2_sink.h b/src/audio_core/sink/sdl2_sink.h index 186bc2fa3..f01eddc1b 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h | |||
| @@ -32,8 +32,7 @@ public: | |||
| 32 | * May differ from the device's channel count. | 32 | * May differ from the device's channel count. |
| 33 | * @param name - Name of this stream. | 33 | * @param name - Name of this stream. |
| 34 | * @param type - Type of this stream, render/in/out. | 34 | * @param type - Type of this stream, render/in/out. |
| 35 | * @param event - Audio render only, a signal used to prevent the renderer running too | 35 | * |
| 36 | * fast. | ||
| 37 | * @return A pointer to the created SinkStream | 36 | * @return A pointer to the created SinkStream |
| 38 | */ | 37 | */ |
| 39 | SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, | 38 | SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, |
| @@ -44,7 +43,7 @@ public: | |||
| 44 | * | 43 | * |
| 45 | * @param stream - The stream to close. | 44 | * @param stream - The stream to close. |
| 46 | */ | 45 | */ |
| 47 | void CloseStream(const SinkStream* stream) override; | 46 | void CloseStream(SinkStream* stream) override; |
| 48 | 47 | ||
| 49 | /** | 48 | /** |
| 50 | * Close all streams. | 49 | * Close all streams. |
| @@ -52,16 +51,6 @@ public: | |||
| 52 | void CloseStreams() override; | 51 | void CloseStreams() override; |
| 53 | 52 | ||
| 54 | /** | 53 | /** |
| 55 | * Pause all streams. | ||
| 56 | */ | ||
| 57 | void PauseStreams() override; | ||
| 58 | |||
| 59 | /** | ||
| 60 | * Unpause all streams. | ||
| 61 | */ | ||
| 62 | void UnpauseStreams() override; | ||
| 63 | |||
| 64 | /** | ||
| 65 | * Get the device volume. Set from calls to the IAudioDevice service. | 54 | * Get the device volume. Set from calls to the IAudioDevice service. |
| 66 | * | 55 | * |
| 67 | * @return Volume of the device. | 56 | * @return Volume of the device. |
| @@ -92,7 +81,7 @@ private: | |||
| 92 | }; | 81 | }; |
| 93 | 82 | ||
| 94 | /** | 83 | /** |
| 95 | * Get a list of conencted devices from Cubeb. | 84 | * Get a list of connected devices from SDL. |
| 96 | * | 85 | * |
| 97 | * @param capture - Return input (capture) devices if true, otherwise output devices. | 86 | * @param capture - Return input (capture) devices if true, otherwise output devices. |
| 98 | */ | 87 | */ |
diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index 91fe455e4..f28c6d126 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h | |||
| @@ -32,7 +32,7 @@ public: | |||
| 32 | * | 32 | * |
| 33 | * @param stream - The stream to close. | 33 | * @param stream - The stream to close. |
| 34 | */ | 34 | */ |
| 35 | virtual void CloseStream(const SinkStream* stream) = 0; | 35 | virtual void CloseStream(SinkStream* stream) = 0; |
| 36 | 36 | ||
| 37 | /** | 37 | /** |
| 38 | * Close all streams. | 38 | * Close all streams. |
| @@ -40,16 +40,6 @@ public: | |||
| 40 | virtual void CloseStreams() = 0; | 40 | virtual void CloseStreams() = 0; |
| 41 | 41 | ||
| 42 | /** | 42 | /** |
| 43 | * Pause all streams. | ||
| 44 | */ | ||
| 45 | virtual void PauseStreams() = 0; | ||
| 46 | |||
| 47 | /** | ||
| 48 | * Unpause all streams. | ||
| 49 | */ | ||
| 50 | virtual void UnpauseStreams() = 0; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Create a new sink stream, kept within this sink, with a pointer returned for use. | 43 | * Create a new sink stream, kept within this sink, with a pointer returned for use. |
| 54 | * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. | 44 | * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. |
| 55 | * | 45 | * |
| @@ -58,8 +48,7 @@ public: | |||
| 58 | * May differ from the device's channel count. | 48 | * May differ from the device's channel count. |
| 59 | * @param name - Name of this stream. | 49 | * @param name - Name of this stream. |
| 60 | * @param type - Type of this stream, render/in/out. | 50 | * @param type - Type of this stream, render/in/out. |
| 61 | * @param event - Audio render only, a signal used to prevent the renderer running too | 51 | * |
| 62 | * fast. | ||
| 63 | * @return A pointer to the created SinkStream | 52 | * @return A pointer to the created SinkStream |
| 64 | */ | 53 | */ |
| 65 | virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, | 54 | virtual SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, |
diff --git a/src/audio_core/sink/sink_details.cpp b/src/audio_core/sink/sink_details.cpp index 253c0fd1e..67bdab779 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp | |||
| @@ -5,7 +5,7 @@ | |||
| 5 | #include <memory> | 5 | #include <memory> |
| 6 | #include <string> | 6 | #include <string> |
| 7 | #include <vector> | 7 | #include <vector> |
| 8 | #include "audio_core/sink/null_sink.h" | 8 | |
| 9 | #include "audio_core/sink/sink_details.h" | 9 | #include "audio_core/sink/sink_details.h" |
| 10 | #ifdef HAVE_CUBEB | 10 | #ifdef HAVE_CUBEB |
| 11 | #include "audio_core/sink/cubeb_sink.h" | 11 | #include "audio_core/sink/cubeb_sink.h" |
| @@ -13,6 +13,7 @@ | |||
| 13 | #ifdef HAVE_SDL2 | 13 | #ifdef HAVE_SDL2 |
| 14 | #include "audio_core/sink/sdl2_sink.h" | 14 | #include "audio_core/sink/sdl2_sink.h" |
| 15 | #endif | 15 | #endif |
| 16 | #include "audio_core/sink/null_sink.h" | ||
| 16 | #include "common/logging/log.h" | 17 | #include "common/logging/log.h" |
| 17 | 18 | ||
| 18 | namespace AudioCore::Sink { | 19 | namespace AudioCore::Sink { |
| @@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { | |||
| 59 | 60 | ||
| 60 | if (sink_id == "auto" || iter == std::end(sink_details)) { | 61 | if (sink_id == "auto" || iter == std::end(sink_details)) { |
| 61 | if (sink_id != "auto") { | 62 | if (sink_id != "auto") { |
| 62 | LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", | 63 | LOG_ERROR(Audio, "Invalid sink_id {}", sink_id); |
| 63 | sink_id); | ||
| 64 | } | 64 | } |
| 65 | // Auto-select. | 65 | // Auto-select. |
| 66 | // sink_details is ordered in terms of desirability, with the best choice at the front. | 66 | // sink_details is ordered in terms of desirability, with the best choice at the front. |
diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp new file mode 100644 index 000000000..37fe725e4 --- /dev/null +++ b/src/audio_core/sink/sink_stream.cpp | |||
| @@ -0,0 +1,279 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <array> | ||
| 5 | #include <atomic> | ||
| 6 | #include <memory> | ||
| 7 | #include <span> | ||
| 8 | #include <vector> | ||
| 9 | |||
| 10 | #include "audio_core/audio_core.h" | ||
| 11 | #include "audio_core/common/common.h" | ||
| 12 | #include "audio_core/sink/sink_stream.h" | ||
| 13 | #include "common/common_types.h" | ||
| 14 | #include "common/fixed_point.h" | ||
| 15 | #include "common/settings.h" | ||
| 16 | #include "core/core.h" | ||
| 17 | |||
| 18 | namespace AudioCore::Sink { | ||
| 19 | |||
| 20 | void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) { | ||
| 21 | if (type == StreamType::In) { | ||
| 22 | queue.enqueue(buffer); | ||
| 23 | queued_buffers++; | ||
| 24 | return; | ||
| 25 | } | ||
| 26 | |||
| 27 | constexpr s32 min{std::numeric_limits<s16>::min()}; | ||
| 28 | constexpr s32 max{std::numeric_limits<s16>::max()}; | ||
| 29 | |||
| 30 | auto yuzu_volume{Settings::Volume()}; | ||
| 31 | if (yuzu_volume > 1.0f) { | ||
| 32 | yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); | ||
| 33 | } | ||
| 34 | auto volume{system_volume * device_volume * yuzu_volume}; | ||
| 35 | |||
| 36 | if (system_channels == 6 && device_channels == 2) { | ||
| 37 | // We're given 6 channels, but our device only outputs 2, so downmix. | ||
| 38 | constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; | ||
| 39 | |||
| 40 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 41 | read_index += system_channels, write_index += device_channels) { | ||
| 42 | const auto left_sample{ | ||
| 43 | ((Common::FixedPoint<49, 15>( | ||
| 44 | samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 45 | down_mix_coeff[0] + | ||
| 46 | samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + | ||
| 47 | samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + | ||
| 48 | samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) * | ||
| 49 | volume) | ||
| 50 | .to_int()}; | ||
| 51 | |||
| 52 | const auto right_sample{ | ||
| 53 | ((Common::FixedPoint<49, 15>( | ||
| 54 | samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 55 | down_mix_coeff[0] + | ||
| 56 | samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + | ||
| 57 | samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + | ||
| 58 | samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) * | ||
| 59 | volume) | ||
| 60 | .to_int()}; | ||
| 61 | |||
| 62 | samples[write_index + static_cast<u32>(Channels::FrontLeft)] = | ||
| 63 | static_cast<s16>(std::clamp(left_sample, min, max)); | ||
| 64 | samples[write_index + static_cast<u32>(Channels::FrontRight)] = | ||
| 65 | static_cast<s16>(std::clamp(right_sample, min, max)); | ||
| 66 | } | ||
| 67 | |||
| 68 | samples.resize(samples.size() / system_channels * device_channels); | ||
| 69 | |||
| 70 | } else if (system_channels == 2 && device_channels == 6) { | ||
| 71 | // We need moar samples! Not all games will provide 6 channel audio. | ||
| 72 | // TODO: Implement some upmixing here. Currently just passthrough, with other | ||
| 73 | // channels left as silence. | ||
| 74 | std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); | ||
| 75 | |||
| 76 | for (u32 read_index = 0, write_index = 0; read_index < samples.size(); | ||
| 77 | read_index += system_channels, write_index += device_channels) { | ||
| 78 | const auto left_sample{static_cast<s16>(std::clamp( | ||
| 79 | static_cast<s32>( | ||
| 80 | static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * | ||
| 81 | volume), | ||
| 82 | min, max))}; | ||
| 83 | |||
| 84 | new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; | ||
| 85 | |||
| 86 | const auto right_sample{static_cast<s16>(std::clamp( | ||
| 87 | static_cast<s32>( | ||
| 88 | static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) * | ||
| 89 | volume), | ||
| 90 | min, max))}; | ||
| 91 | |||
| 92 | new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample; | ||
| 93 | } | ||
| 94 | samples = std::move(new_samples); | ||
| 95 | |||
| 96 | } else if (volume != 1.0f) { | ||
| 97 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 98 | samples[i] = static_cast<s16>( | ||
| 99 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 100 | } | ||
| 101 | } | ||
| 102 | |||
| 103 | samples_buffer.Push(samples); | ||
| 104 | queue.enqueue(buffer); | ||
| 105 | queued_buffers++; | ||
| 106 | } | ||
| 107 | |||
| 108 | std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) { | ||
| 109 | constexpr s32 min = std::numeric_limits<s16>::min(); | ||
| 110 | constexpr s32 max = std::numeric_limits<s16>::max(); | ||
| 111 | |||
| 112 | auto samples{samples_buffer.Pop(num_samples)}; | ||
| 113 | |||
| 114 | // TODO: Up-mix to 6 channels if the game expects it. | ||
| 115 | // For audio input this is unlikely to ever be the case though. | ||
| 116 | |||
| 117 | // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. | ||
| 118 | // TODO: Play with this and find something that works better. | ||
| 119 | auto volume{system_volume * device_volume * 8}; | ||
| 120 | for (u32 i = 0; i < samples.size(); i++) { | ||
| 121 | samples[i] = static_cast<s16>( | ||
| 122 | std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); | ||
| 123 | } | ||
| 124 | |||
| 125 | if (samples.size() < num_samples) { | ||
| 126 | samples.resize(num_samples, 0); | ||
| 127 | } | ||
| 128 | return samples; | ||
| 129 | } | ||
| 130 | |||
| 131 | void SinkStream::ClearQueue() { | ||
| 132 | samples_buffer.Pop(); | ||
| 133 | while (queue.pop()) { | ||
| 134 | } | ||
| 135 | queued_buffers = 0; | ||
| 136 | playing_buffer = {}; | ||
| 137 | playing_buffer.consumed = true; | ||
| 138 | } | ||
| 139 | |||
| 140 | void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) { | ||
| 141 | const std::size_t num_channels = GetDeviceChannels(); | ||
| 142 | const std::size_t frame_size = num_channels; | ||
| 143 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 144 | size_t frames_written{0}; | ||
| 145 | |||
| 146 | // If we're paused or going to shut down, we don't want to consume buffers as coretiming is | ||
| 147 | // paused and we'll desync, so just return. | ||
| 148 | if (system.IsPaused() || system.IsShuttingDown()) { | ||
| 149 | return; | ||
| 150 | } | ||
| 151 | |||
| 152 | if (queued_buffers > max_queue_size) { | ||
| 153 | Stall(); | ||
| 154 | } | ||
| 155 | |||
| 156 | while (frames_written < num_frames) { | ||
| 157 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 158 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 159 | if (!queue.try_dequeue(playing_buffer)) { | ||
| 160 | // If no buffer was available we've underrun, just push the samples and | ||
| 161 | // continue. | ||
| 162 | samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 163 | (num_frames - frames_written) * frame_size); | ||
| 164 | frames_written = num_frames; | ||
| 165 | continue; | ||
| 166 | } | ||
| 167 | // Successfully dequeued a new buffer. | ||
| 168 | queued_buffers--; | ||
| 169 | } | ||
| 170 | |||
| 171 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 172 | // amount we have left to fill | ||
| 173 | size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 174 | num_frames - frames_written)}; | ||
| 175 | |||
| 176 | samples_buffer.Push(&input_buffer[frames_written * frame_size], | ||
| 177 | frames_available * frame_size); | ||
| 178 | |||
| 179 | frames_written += frames_available; | ||
| 180 | playing_buffer.frames_played += frames_available; | ||
| 181 | |||
| 182 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 183 | // consumed | ||
| 184 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 185 | playing_buffer.consumed = true; | ||
| 186 | } | ||
| 187 | } | ||
| 188 | |||
| 189 | std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); | ||
| 190 | |||
| 191 | if (queued_buffers <= max_queue_size) { | ||
| 192 | Unstall(); | ||
| 193 | } | ||
| 194 | } | ||
| 195 | |||
| 196 | void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) { | ||
| 197 | const std::size_t num_channels = GetDeviceChannels(); | ||
| 198 | const std::size_t frame_size = num_channels; | ||
| 199 | const std::size_t frame_size_bytes = frame_size * sizeof(s16); | ||
| 200 | size_t frames_written{0}; | ||
| 201 | |||
| 202 | // If we're paused or going to shut down, we don't want to consume buffers as coretiming is | ||
| 203 | // paused and we'll desync, so just play silence. | ||
| 204 | if (system.IsPaused() || system.IsShuttingDown()) { | ||
| 205 | constexpr std::array<s16, 6> silence{}; | ||
| 206 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 207 | std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes); | ||
| 208 | } | ||
| 209 | return; | ||
| 210 | } | ||
| 211 | |||
| 212 | // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get | ||
| 213 | // queued up (30+) but not all at once, which causes constant stalling here, so just let the | ||
| 214 | // video play out without attempting to stall. | ||
| 215 | // Can hopefully remove this later with a more complete NVDEC implementation. | ||
| 216 | const auto nvdec_active{system.AudioCore().IsNVDECActive()}; | ||
| 217 | if (!nvdec_active && queued_buffers > max_queue_size) { | ||
| 218 | Stall(); | ||
| 219 | } | ||
| 220 | |||
| 221 | while (frames_written < num_frames) { | ||
| 222 | // If the playing buffer has been consumed or has no frames, we need a new one | ||
| 223 | if (playing_buffer.consumed || playing_buffer.frames == 0) { | ||
| 224 | if (!queue.try_dequeue(playing_buffer)) { | ||
| 225 | // If no buffer was available we've underrun, fill the remaining buffer with | ||
| 226 | // the last written frame and continue. | ||
| 227 | for (size_t i = frames_written; i < num_frames; i++) { | ||
| 228 | std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes); | ||
| 229 | } | ||
| 230 | frames_written = num_frames; | ||
| 231 | continue; | ||
| 232 | } | ||
| 233 | // Successfully dequeued a new buffer. | ||
| 234 | queued_buffers--; | ||
| 235 | } | ||
| 236 | |||
| 237 | // Get the minimum frames available between the currently playing buffer, and the | ||
| 238 | // amount we have left to fill | ||
| 239 | size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, | ||
| 240 | num_frames - frames_written)}; | ||
| 241 | |||
| 242 | samples_buffer.Pop(&output_buffer[frames_written * frame_size], | ||
| 243 | frames_available * frame_size); | ||
| 244 | |||
| 245 | frames_written += frames_available; | ||
| 246 | playing_buffer.frames_played += frames_available; | ||
| 247 | |||
| 248 | // If that's all the frames in the current buffer, add its samples and mark it as | ||
| 249 | // consumed | ||
| 250 | if (playing_buffer.frames_played >= playing_buffer.frames) { | ||
| 251 | playing_buffer.consumed = true; | ||
| 252 | } | ||
| 253 | } | ||
| 254 | |||
| 255 | std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size], | ||
| 256 | frame_size_bytes); | ||
| 257 | |||
| 258 | if (stalled && queued_buffers <= max_queue_size) { | ||
| 259 | Unstall(); | ||
| 260 | } | ||
| 261 | } | ||
| 262 | |||
| 263 | void SinkStream::Stall() { | ||
| 264 | if (stalled) { | ||
| 265 | return; | ||
| 266 | } | ||
| 267 | stalled = true; | ||
| 268 | system.StallProcesses(); | ||
| 269 | } | ||
| 270 | |||
| 271 | void SinkStream::Unstall() { | ||
| 272 | if (!stalled) { | ||
| 273 | return; | ||
| 274 | } | ||
| 275 | system.UnstallProcesses(); | ||
| 276 | stalled = false; | ||
| 277 | } | ||
| 278 | |||
| 279 | } // namespace AudioCore::Sink | ||
diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h index 17ed6593f..38a4b2f51 100644 --- a/src/audio_core/sink/sink_stream.h +++ b/src/audio_core/sink/sink_stream.h | |||
| @@ -3,12 +3,20 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <array> | ||
| 6 | #include <atomic> | 7 | #include <atomic> |
| 7 | #include <memory> | 8 | #include <memory> |
| 9 | #include <span> | ||
| 8 | #include <vector> | 10 | #include <vector> |
| 9 | 11 | ||
| 10 | #include "audio_core/common/common.h" | 12 | #include "audio_core/common/common.h" |
| 11 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "common/reader_writer_queue.h" | ||
| 15 | #include "common/ring_buffer.h" | ||
| 16 | |||
| 17 | namespace Core { | ||
| 18 | class System; | ||
| 19 | } // namespace Core | ||
| 12 | 20 | ||
| 13 | namespace AudioCore::Sink { | 21 | namespace AudioCore::Sink { |
| 14 | 22 | ||
| @@ -34,20 +42,24 @@ struct SinkBuffer { | |||
| 34 | * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer | 42 | * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer |
| 35 | * has been consumed. | 43 | * has been consumed. |
| 36 | * | 44 | * |
| 37 | * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the | 45 | * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were |
| 38 | * buffers, skipping a buffer will result in all following buffers to never release. | 46 | * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to |
| 47 | * never release. | ||
| 39 | * | 48 | * |
| 40 | * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this | 49 | * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this |
| 41 | * is what games do), or call ClearQueue to flush all of the buffers without a full restart. | 50 | * is what games do), or call ClearQueue to flush all of the buffers without a full restart. |
| 42 | */ | 51 | */ |
| 43 | class SinkStream { | 52 | class SinkStream { |
| 44 | public: | 53 | public: |
| 45 | virtual ~SinkStream() = default; | 54 | explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {} |
| 55 | virtual ~SinkStream() { | ||
| 56 | Unstall(); | ||
| 57 | } | ||
| 46 | 58 | ||
| 47 | /** | 59 | /** |
| 48 | * Finalize the sink stream. | 60 | * Finalize the sink stream. |
| 49 | */ | 61 | */ |
| 50 | virtual void Finalize() = 0; | 62 | virtual void Finalize() {} |
| 51 | 63 | ||
| 52 | /** | 64 | /** |
| 53 | * Start the sink stream. | 65 | * Start the sink stream. |
| @@ -55,48 +67,19 @@ public: | |||
| 55 | * @param resume - Set to true if this is resuming the stream a previously-active stream. | 67 | * @param resume - Set to true if this is resuming the stream a previously-active stream. |
| 56 | * Default false. | 68 | * Default false. |
| 57 | */ | 69 | */ |
| 58 | virtual void Start(bool resume = false) = 0; | 70 | virtual void Start(bool resume = false) {} |
| 59 | 71 | ||
| 60 | /** | 72 | /** |
| 61 | * Stop the sink stream. | 73 | * Stop the sink stream. |
| 62 | */ | 74 | */ |
| 63 | virtual void Stop() = 0; | 75 | virtual void Stop() {} |
| 64 | |||
| 65 | /** | ||
| 66 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 67 | * | ||
| 68 | * @param buffer - Audio buffer information to be queued. | ||
| 69 | * @param samples - The s16 samples to be queue for playback. | ||
| 70 | */ | ||
| 71 | virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0; | ||
| 72 | |||
| 73 | /** | ||
| 74 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 75 | * | ||
| 76 | * @param num_samples - Maximum number of samples to receive. | ||
| 77 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 78 | */ | ||
| 79 | virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0; | ||
| 80 | |||
| 81 | /** | ||
| 82 | * Check if a certain buffer has been consumed (fully played). | ||
| 83 | * | ||
| 84 | * @param tag - Unique tag of a buffer to check for. | ||
| 85 | * @return True if the buffer has been played, otherwise false. | ||
| 86 | */ | ||
| 87 | virtual bool IsBufferConsumed(u64 tag) = 0; | ||
| 88 | |||
| 89 | /** | ||
| 90 | * Empty out the buffer queue. | ||
| 91 | */ | ||
| 92 | virtual void ClearQueue() = 0; | ||
| 93 | 76 | ||
| 94 | /** | 77 | /** |
| 95 | * Check if the stream is paused. | 78 | * Check if the stream is paused. |
| 96 | * | 79 | * |
| 97 | * @return True if paused, otherwise false. | 80 | * @return True if paused, otherwise false. |
| 98 | */ | 81 | */ |
| 99 | bool IsPaused() { | 82 | bool IsPaused() const { |
| 100 | return paused; | 83 | return paused; |
| 101 | } | 84 | } |
| 102 | 85 | ||
| @@ -128,34 +111,6 @@ public: | |||
| 128 | } | 111 | } |
| 129 | 112 | ||
| 130 | /** | 113 | /** |
| 131 | * Get the total number of samples played by this stream. | ||
| 132 | * | ||
| 133 | * @return Number of samples played. | ||
| 134 | */ | ||
| 135 | u64 GetPlayedSampleCount() const { | ||
| 136 | return played_sample_count; | ||
| 137 | } | ||
| 138 | |||
| 139 | /** | ||
| 140 | * Set the number of samples played. | ||
| 141 | * This is started and stopped on system start/stop. | ||
| 142 | * | ||
| 143 | * @param played_sample_count_ - Number of samples to set. | ||
| 144 | */ | ||
| 145 | void SetPlayedSampleCount(u64 played_sample_count_) { | ||
| 146 | played_sample_count = played_sample_count_; | ||
| 147 | } | ||
| 148 | |||
| 149 | /** | ||
| 150 | * Add to the played sample count. | ||
| 151 | * | ||
| 152 | * @param num_samples - Number of samples to add. | ||
| 153 | */ | ||
| 154 | void AddPlayedSampleCount(u64 num_samples) { | ||
| 155 | played_sample_count += num_samples; | ||
| 156 | } | ||
| 157 | |||
| 158 | /** | ||
| 159 | * Get the system volume. | 114 | * Get the system volume. |
| 160 | * | 115 | * |
| 161 | * @return The current system volume. | 116 | * @return The current system volume. |
| @@ -196,27 +151,97 @@ public: | |||
| 196 | * | 151 | * |
| 197 | * @return The number of queued buffers. | 152 | * @return The number of queued buffers. |
| 198 | */ | 153 | */ |
| 199 | u32 GetQueueSize() { | 154 | u32 GetQueueSize() const { |
| 200 | return queued_buffers.load(); | 155 | return queued_buffers.load(); |
| 201 | } | 156 | } |
| 202 | 157 | ||
| 158 | /** | ||
| 159 | * Set the maximum buffer queue size. | ||
| 160 | */ | ||
| 161 | void SetRingSize(u32 ring_size) { | ||
| 162 | max_queue_size = ring_size; | ||
| 163 | } | ||
| 164 | |||
| 165 | /** | ||
| 166 | * Append a new buffer and its samples to a waiting queue to play. | ||
| 167 | * | ||
| 168 | * @param buffer - Audio buffer information to be queued. | ||
| 169 | * @param samples - The s16 samples to be queue for playback. | ||
| 170 | */ | ||
| 171 | virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples); | ||
| 172 | |||
| 173 | /** | ||
| 174 | * Release a buffer. Audio In only, will fill a buffer with recorded samples. | ||
| 175 | * | ||
| 176 | * @param num_samples - Maximum number of samples to receive. | ||
| 177 | * @return Vector of recorded samples. May have fewer than num_samples. | ||
| 178 | */ | ||
| 179 | virtual std::vector<s16> ReleaseBuffer(u64 num_samples); | ||
| 180 | |||
| 181 | /** | ||
| 182 | * Empty out the buffer queue. | ||
| 183 | */ | ||
| 184 | void ClearQueue(); | ||
| 185 | |||
| 186 | /** | ||
| 187 | * Callback for AudioIn. | ||
| 188 | * | ||
| 189 | * @param input_buffer - Input buffer to be filled with samples. | ||
| 190 | * @param num_frames - Number of frames to be filled. | ||
| 191 | */ | ||
| 192 | void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames); | ||
| 193 | |||
| 194 | /** | ||
| 195 | * Callback for AudioOut and AudioRenderer. | ||
| 196 | * | ||
| 197 | * @param output_buffer - Output buffer to be filled with samples. | ||
| 198 | * @param num_frames - Number of frames to be filled. | ||
| 199 | */ | ||
| 200 | void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames); | ||
| 201 | |||
| 202 | /** | ||
| 203 | * Stall core processes if the audio thread falls too far behind. | ||
| 204 | */ | ||
| 205 | void Stall(); | ||
| 206 | |||
| 207 | /** | ||
| 208 | * Unstall core processes. | ||
| 209 | */ | ||
| 210 | void Unstall(); | ||
| 211 | |||
| 203 | protected: | 212 | protected: |
| 204 | /// Number of buffers waiting to be played | 213 | /// Core system |
| 205 | std::atomic<u32> queued_buffers{}; | 214 | Core::System& system; |
| 206 | /// Total samples played by this stream | 215 | /// Type of this stream |
| 207 | std::atomic<u64> played_sample_count{}; | 216 | StreamType type; |
| 208 | /// Set by the audio render/in/out system which uses this stream | 217 | /// Set by the audio render/in/out system which uses this stream |
| 209 | f32 system_volume{1.0f}; | ||
| 210 | /// Set via IAudioDevice service calls | ||
| 211 | f32 device_volume{1.0f}; | ||
| 212 | /// Set by the audio render/in/out systen which uses this stream | ||
| 213 | u32 system_channels{2}; | 218 | u32 system_channels{2}; |
| 214 | /// Channels supported by hardware | 219 | /// Channels supported by hardware |
| 215 | u32 device_channels{2}; | 220 | u32 device_channels{2}; |
| 216 | /// Is this stream currently paused? | 221 | /// Is this stream currently paused? |
| 217 | std::atomic<bool> paused{true}; | 222 | std::atomic<bool> paused{true}; |
| 218 | /// Was this stream previously playing? | 223 | /// Name of this stream |
| 219 | std::atomic<bool> was_playing{false}; | 224 | std::string name{}; |
| 225 | |||
| 226 | private: | ||
| 227 | /// Ring buffer of the samples waiting to be played or consumed | ||
| 228 | Common::RingBuffer<s16, 0x10000> samples_buffer; | ||
| 229 | /// Audio buffers queued and waiting to play | ||
| 230 | Common::ReaderWriterQueue<SinkBuffer> queue; | ||
| 231 | /// The currently-playing audio buffer | ||
| 232 | SinkBuffer playing_buffer{}; | ||
| 233 | /// The last played (or received) frame of audio, used when the callback underruns | ||
| 234 | std::array<s16, MaxChannels> last_frame{}; | ||
| 235 | /// Number of buffers waiting to be played | ||
| 236 | std::atomic<u32> queued_buffers{}; | ||
| 237 | /// The ring size for audio out buffers (usually 4, rarely 2 or 8) | ||
| 238 | u32 max_queue_size{}; | ||
| 239 | /// Set by the audio render/in/out system which uses this stream | ||
| 240 | f32 system_volume{1.0f}; | ||
| 241 | /// Set via IAudioDevice service calls | ||
| 242 | f32 device_volume{1.0f}; | ||
| 243 | /// True if coretiming has been stalled | ||
| 244 | bool stalled{false}; | ||
| 220 | }; | 245 | }; |
| 221 | 246 | ||
| 222 | using SinkStreamPtr = std::unique_ptr<SinkStream>; | 247 | using SinkStreamPtr = std::unique_ptr<SinkStream>; |
diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 635fb85c8..68436a4bc 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -19,7 +19,7 @@ find_package(Git QUIET) | |||
| 19 | 19 | ||
| 20 | add_custom_command(OUTPUT scm_rev.cpp | 20 | add_custom_command(OUTPUT scm_rev.cpp |
| 21 | COMMAND ${CMAKE_COMMAND} | 21 | COMMAND ${CMAKE_COMMAND} |
| 22 | -DSRC_DIR=${CMAKE_SOURCE_DIR} | 22 | -DSRC_DIR=${PROJECT_SOURCE_DIR} |
| 23 | -DBUILD_REPOSITORY=${BUILD_REPOSITORY} | 23 | -DBUILD_REPOSITORY=${BUILD_REPOSITORY} |
| 24 | -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} | 24 | -DTITLE_BAR_FORMAT_IDLE=${TITLE_BAR_FORMAT_IDLE} |
| 25 | -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} | 25 | -DTITLE_BAR_FORMAT_RUNNING=${TITLE_BAR_FORMAT_RUNNING} |
| @@ -31,13 +31,13 @@ add_custom_command(OUTPUT scm_rev.cpp | |||
| 31 | -DGIT_BRANCH=${GIT_BRANCH} | 31 | -DGIT_BRANCH=${GIT_BRANCH} |
| 32 | -DBUILD_FULLNAME=${BUILD_FULLNAME} | 32 | -DBUILD_FULLNAME=${BUILD_FULLNAME} |
| 33 | -DGIT_EXECUTABLE=${GIT_EXECUTABLE} | 33 | -DGIT_EXECUTABLE=${GIT_EXECUTABLE} |
| 34 | -P ${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake | 34 | -P ${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake |
| 35 | DEPENDS | 35 | DEPENDS |
| 36 | # Check that the scm_rev files haven't changed | 36 | # Check that the scm_rev files haven't changed |
| 37 | "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" | 37 | "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.cpp.in" |
| 38 | "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" | 38 | "${CMAKE_CURRENT_SOURCE_DIR}/scm_rev.h" |
| 39 | # technically we should regenerate if the git version changed, but its not worth the effort imo | 39 | # technically we should regenerate if the git version changed, but its not worth the effort imo |
| 40 | "${CMAKE_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" | 40 | "${PROJECT_SOURCE_DIR}/CMakeModules/GenerateSCMRev.cmake" |
| 41 | VERBATIM | 41 | VERBATIM |
| 42 | ) | 42 | ) |
| 43 | 43 | ||
| @@ -166,6 +166,7 @@ if(ARCHITECTURE_x86_64) | |||
| 166 | x64/xbyak_abi.h | 166 | x64/xbyak_abi.h |
| 167 | x64/xbyak_util.h | 167 | x64/xbyak_util.h |
| 168 | ) | 168 | ) |
| 169 | target_link_libraries(common PRIVATE xbyak) | ||
| 169 | endif() | 170 | endif() |
| 170 | 171 | ||
| 171 | if (MSVC) | 172 | if (MSVC) |
| @@ -189,7 +190,7 @@ endif() | |||
| 189 | create_target_directory_groups(common) | 190 | create_target_directory_groups(common) |
| 190 | 191 | ||
| 191 | target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) | 192 | target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) |
| 192 | target_link_libraries(common PRIVATE lz4::lz4 xbyak) | 193 | target_link_libraries(common PRIVATE lz4::lz4) |
| 193 | if (TARGET zstd::zstd) | 194 | if (TARGET zstd::zstd) |
| 194 | target_link_libraries(common PRIVATE zstd::zstd) | 195 | target_link_libraries(common PRIVATE zstd::zstd) |
| 195 | else() | 196 | else() |
diff --git a/src/common/input.h b/src/common/input.h index 213aa2384..825b0d650 100644 --- a/src/common/input.h +++ b/src/common/input.h | |||
| @@ -102,6 +102,8 @@ struct AnalogProperties { | |||
| 102 | float offset{}; | 102 | float offset{}; |
| 103 | // Invert direction of the sensor data | 103 | // Invert direction of the sensor data |
| 104 | bool inverted{}; | 104 | bool inverted{}; |
| 105 | // Press once to activate, press again to release | ||
| 106 | bool toggle{}; | ||
| 105 | }; | 107 | }; |
| 106 | 108 | ||
| 107 | // Single analog sensor data | 109 | // Single analog sensor data |
| @@ -115,8 +117,11 @@ struct AnalogStatus { | |||
| 115 | struct ButtonStatus { | 117 | struct ButtonStatus { |
| 116 | Common::UUID uuid{}; | 118 | Common::UUID uuid{}; |
| 117 | bool value{}; | 119 | bool value{}; |
| 120 | // Invert value of the button | ||
| 118 | bool inverted{}; | 121 | bool inverted{}; |
| 122 | // Press once to activate, press again to release | ||
| 119 | bool toggle{}; | 123 | bool toggle{}; |
| 124 | // Internal lock for the toggle status | ||
| 120 | bool locked{}; | 125 | bool locked{}; |
| 121 | }; | 126 | }; |
| 122 | 127 | ||
diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 7282a45d3..0a560ebb7 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp | |||
| @@ -195,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) { | |||
| 195 | values.shader_backend.SetGlobal(true); | 195 | values.shader_backend.SetGlobal(true); |
| 196 | values.use_asynchronous_shaders.SetGlobal(true); | 196 | values.use_asynchronous_shaders.SetGlobal(true); |
| 197 | values.use_fast_gpu_time.SetGlobal(true); | 197 | values.use_fast_gpu_time.SetGlobal(true); |
| 198 | values.use_pessimistic_flushes.SetGlobal(true); | ||
| 198 | values.bg_red.SetGlobal(true); | 199 | values.bg_red.SetGlobal(true); |
| 199 | values.bg_green.SetGlobal(true); | 200 | values.bg_green.SetGlobal(true); |
| 200 | values.bg_blue.SetGlobal(true); | 201 | values.bg_blue.SetGlobal(true); |
diff --git a/src/common/settings.h b/src/common/settings.h index 14ed9b237..851812f28 100644 --- a/src/common/settings.h +++ b/src/common/settings.h | |||
| @@ -446,6 +446,7 @@ struct Values { | |||
| 446 | ShaderBackend::SPIRV, "shader_backend"}; | 446 | ShaderBackend::SPIRV, "shader_backend"}; |
| 447 | SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; | 447 | SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; |
| 448 | SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; | 448 | SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; |
| 449 | SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"}; | ||
| 449 | 450 | ||
| 450 | SwitchableSetting<u8> bg_red{0, "bg_red"}; | 451 | SwitchableSetting<u8> bg_red{0, "bg_red"}; |
| 451 | SwitchableSetting<u8> bg_green{0, "bg_green"}; | 452 | SwitchableSetting<u8> bg_green{0, "bg_green"}; |
| @@ -529,6 +530,7 @@ struct Values { | |||
| 529 | Setting<bool> use_debug_asserts{false, "use_debug_asserts"}; | 530 | Setting<bool> use_debug_asserts{false, "use_debug_asserts"}; |
| 530 | Setting<bool> use_auto_stub{false, "use_auto_stub"}; | 531 | Setting<bool> use_auto_stub{false, "use_auto_stub"}; |
| 531 | Setting<bool> enable_all_controllers{false, "enable_all_controllers"}; | 532 | Setting<bool> enable_all_controllers{false, "enable_all_controllers"}; |
| 533 | Setting<bool> create_crash_dumps{false, "create_crash_dumps"}; | ||
| 532 | 534 | ||
| 533 | // Miscellaneous | 535 | // Miscellaneous |
| 534 | Setting<std::string> log_filter{"*:Info", "log_filter"}; | 536 | Setting<std::string> log_filter{"*:Info", "log_filter"}; |
diff --git a/src/common/thread.h b/src/common/thread.h index 1552f58e0..e17a7850f 100644 --- a/src/common/thread.h +++ b/src/common/thread.h | |||
| @@ -54,6 +54,10 @@ public: | |||
| 54 | is_set = false; | 54 | is_set = false; |
| 55 | } | 55 | } |
| 56 | 56 | ||
| 57 | [[nodiscard]] bool IsSet() { | ||
| 58 | return is_set; | ||
| 59 | } | ||
| 60 | |||
| 57 | private: | 61 | private: |
| 58 | std::condition_variable condvar; | 62 | std::condition_variable condvar; |
| 59 | std::mutex mutex; | 63 | std::mutex mutex; |
diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 22ff3d304..33cf470d5 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt | |||
| @@ -4,12 +4,6 @@ | |||
| 4 | add_library(core STATIC | 4 | add_library(core STATIC |
| 5 | arm/arm_interface.h | 5 | arm/arm_interface.h |
| 6 | arm/arm_interface.cpp | 6 | arm/arm_interface.cpp |
| 7 | arm/dynarmic/arm_dynarmic_32.cpp | ||
| 8 | arm/dynarmic/arm_dynarmic_32.h | ||
| 9 | arm/dynarmic/arm_dynarmic_64.cpp | ||
| 10 | arm/dynarmic/arm_dynarmic_64.h | ||
| 11 | arm/dynarmic/arm_dynarmic_cp15.cpp | ||
| 12 | arm/dynarmic/arm_dynarmic_cp15.h | ||
| 13 | arm/dynarmic/arm_exclusive_monitor.cpp | 7 | arm/dynarmic/arm_exclusive_monitor.cpp |
| 14 | arm/dynarmic/arm_exclusive_monitor.h | 8 | arm/dynarmic/arm_exclusive_monitor.h |
| 15 | arm/exclusive_monitor.cpp | 9 | arm/exclusive_monitor.cpp |
diff --git a/src/core/core.cpp b/src/core/core.cpp index e651ce100..121092868 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp | |||
| @@ -141,8 +141,6 @@ struct System::Impl { | |||
| 141 | core_timing.SyncPause(false); | 141 | core_timing.SyncPause(false); |
| 142 | is_paused = false; | 142 | is_paused = false; |
| 143 | 143 | ||
| 144 | audio_core->PauseSinks(false); | ||
| 145 | |||
| 146 | return status; | 144 | return status; |
| 147 | } | 145 | } |
| 148 | 146 | ||
| @@ -150,8 +148,6 @@ struct System::Impl { | |||
| 150 | std::unique_lock<std::mutex> lk(suspend_guard); | 148 | std::unique_lock<std::mutex> lk(suspend_guard); |
| 151 | status = SystemResultStatus::Success; | 149 | status = SystemResultStatus::Success; |
| 152 | 150 | ||
| 153 | audio_core->PauseSinks(true); | ||
| 154 | |||
| 155 | core_timing.SyncPause(true); | 151 | core_timing.SyncPause(true); |
| 156 | kernel.Suspend(true); | 152 | kernel.Suspend(true); |
| 157 | is_paused = true; | 153 | is_paused = true; |
diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2dbb99c8b..f6c4567ba 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp | |||
| @@ -73,7 +73,6 @@ void CoreTiming::Shutdown() { | |||
| 73 | if (timer_thread) { | 73 | if (timer_thread) { |
| 74 | timer_thread->join(); | 74 | timer_thread->join(); |
| 75 | } | 75 | } |
| 76 | pause_callbacks.clear(); | ||
| 77 | ClearPendingEvents(); | 76 | ClearPendingEvents(); |
| 78 | timer_thread.reset(); | 77 | timer_thread.reset(); |
| 79 | has_started = false; | 78 | has_started = false; |
| @@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) { | |||
| 86 | if (!is_paused) { | 85 | if (!is_paused) { |
| 87 | pause_end_time = GetGlobalTimeNs().count(); | 86 | pause_end_time = GetGlobalTimeNs().count(); |
| 88 | } | 87 | } |
| 89 | |||
| 90 | for (auto& cb : pause_callbacks) { | ||
| 91 | cb(is_paused); | ||
| 92 | } | ||
| 93 | } | 88 | } |
| 94 | 89 | ||
| 95 | void CoreTiming::SyncPause(bool is_paused) { | 90 | void CoreTiming::SyncPause(bool is_paused) { |
| @@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) { | |||
| 110 | if (!is_paused) { | 105 | if (!is_paused) { |
| 111 | pause_end_time = GetGlobalTimeNs().count(); | 106 | pause_end_time = GetGlobalTimeNs().count(); |
| 112 | } | 107 | } |
| 113 | |||
| 114 | for (auto& cb : pause_callbacks) { | ||
| 115 | cb(is_paused); | ||
| 116 | } | ||
| 117 | } | 108 | } |
| 118 | 109 | ||
| 119 | bool CoreTiming::IsRunning() const { | 110 | bool CoreTiming::IsRunning() const { |
| @@ -143,13 +134,17 @@ void CoreTiming::ScheduleLoopingEvent(std::chrono::nanoseconds start_time, | |||
| 143 | std::chrono::nanoseconds resched_time, | 134 | std::chrono::nanoseconds resched_time, |
| 144 | const std::shared_ptr<EventType>& event_type, | 135 | const std::shared_ptr<EventType>& event_type, |
| 145 | std::uintptr_t user_data, bool absolute_time) { | 136 | std::uintptr_t user_data, bool absolute_time) { |
| 146 | std::scoped_lock scope{basic_lock}; | 137 | { |
| 147 | const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; | 138 | std::scoped_lock scope{basic_lock}; |
| 139 | const auto next_time{absolute_time ? start_time : GetGlobalTimeNs() + start_time}; | ||
| 140 | |||
| 141 | event_queue.emplace_back( | ||
| 142 | Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); | ||
| 148 | 143 | ||
| 149 | event_queue.emplace_back( | 144 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |
| 150 | Event{next_time.count(), event_fifo_id++, user_data, event_type, resched_time.count()}); | 145 | } |
| 151 | 146 | ||
| 152 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | 147 | event.Set(); |
| 153 | } | 148 | } |
| 154 | 149 | ||
| 155 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, | 150 | void CoreTiming::UnscheduleEvent(const std::shared_ptr<EventType>& event_type, |
| @@ -219,11 +214,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { | |||
| 219 | } | 214 | } |
| 220 | } | 215 | } |
| 221 | 216 | ||
| 222 | void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) { | ||
| 223 | std::scoped_lock lock{basic_lock}; | ||
| 224 | pause_callbacks.emplace_back(std::move(callback)); | ||
| 225 | } | ||
| 226 | |||
| 227 | std::optional<s64> CoreTiming::Advance() { | 217 | std::optional<s64> CoreTiming::Advance() { |
| 228 | std::scoped_lock lock{advance_lock, basic_lock}; | 218 | std::scoped_lock lock{advance_lock, basic_lock}; |
| 229 | global_timer = GetGlobalTimeNs().count(); | 219 | global_timer = GetGlobalTimeNs().count(); |
| @@ -243,17 +233,17 @@ std::optional<s64> CoreTiming::Advance() { | |||
| 243 | basic_lock.lock(); | 233 | basic_lock.lock(); |
| 244 | 234 | ||
| 245 | if (evt.reschedule_time != 0) { | 235 | if (evt.reschedule_time != 0) { |
| 236 | const auto next_schedule_time{new_schedule_time.has_value() | ||
| 237 | ? new_schedule_time.value().count() | ||
| 238 | : evt.reschedule_time}; | ||
| 239 | |||
| 246 | // If this event was scheduled into a pause, its time now is going to be way behind. | 240 | // If this event was scheduled into a pause, its time now is going to be way behind. |
| 247 | // Re-set this event to continue from the end of the pause. | 241 | // Re-set this event to continue from the end of the pause. |
| 248 | auto next_time{evt.time + evt.reschedule_time}; | 242 | auto next_time{evt.time + next_schedule_time}; |
| 249 | if (evt.time < pause_end_time) { | 243 | if (evt.time < pause_end_time) { |
| 250 | next_time = pause_end_time + evt.reschedule_time; | 244 | next_time = pause_end_time + next_schedule_time; |
| 251 | } | 245 | } |
| 252 | 246 | ||
| 253 | const auto next_schedule_time{new_schedule_time.has_value() | ||
| 254 | ? new_schedule_time.value().count() | ||
| 255 | : evt.reschedule_time}; | ||
| 256 | |||
| 257 | event_queue.emplace_back( | 247 | event_queue.emplace_back( |
| 258 | Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); | 248 | Event{next_time, event_fifo_id++, evt.user_data, evt.type, next_schedule_time}); |
| 259 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); | 249 | std::push_heap(event_queue.begin(), event_queue.end(), std::greater<>()); |
| @@ -264,8 +254,7 @@ std::optional<s64> CoreTiming::Advance() { | |||
| 264 | } | 254 | } |
| 265 | 255 | ||
| 266 | if (!event_queue.empty()) { | 256 | if (!event_queue.empty()) { |
| 267 | const s64 next_time = event_queue.front().time - global_timer; | 257 | return event_queue.front().time; |
| 268 | return next_time; | ||
| 269 | } else { | 258 | } else { |
| 270 | return std::nullopt; | 259 | return std::nullopt; |
| 271 | } | 260 | } |
| @@ -278,11 +267,29 @@ void CoreTiming::ThreadLoop() { | |||
| 278 | paused_set = false; | 267 | paused_set = false; |
| 279 | const auto next_time = Advance(); | 268 | const auto next_time = Advance(); |
| 280 | if (next_time) { | 269 | if (next_time) { |
| 281 | if (*next_time > 0) { | 270 | // There are more events left in the queue, wait until the next event. |
| 282 | std::chrono::nanoseconds next_time_ns = std::chrono::nanoseconds(*next_time); | 271 | const auto wait_time = *next_time - GetGlobalTimeNs().count(); |
| 283 | event.WaitFor(next_time_ns); | 272 | if (wait_time > 0) { |
| 273 | // Assume a timer resolution of 1ms. | ||
| 274 | static constexpr s64 TimerResolutionNS = 1000000; | ||
| 275 | |||
| 276 | // Sleep in discrete intervals of the timer resolution, and spin the rest. | ||
| 277 | const auto sleep_time = wait_time - (wait_time % TimerResolutionNS); | ||
| 278 | if (sleep_time > 0) { | ||
| 279 | event.WaitFor(std::chrono::nanoseconds(sleep_time)); | ||
| 280 | } | ||
| 281 | |||
| 282 | while (!paused && !event.IsSet() && GetGlobalTimeNs().count() < *next_time) { | ||
| 283 | // Yield to reduce thread starvation. | ||
| 284 | std::this_thread::yield(); | ||
| 285 | } | ||
| 286 | |||
| 287 | if (event.IsSet()) { | ||
| 288 | event.Reset(); | ||
| 289 | } | ||
| 284 | } | 290 | } |
| 285 | } else { | 291 | } else { |
| 292 | // Queue is empty, wait until another event is scheduled and signals us to continue. | ||
| 286 | wait_set = true; | 293 | wait_set = true; |
| 287 | event.Wait(); | 294 | event.Wait(); |
| 288 | } | 295 | } |
diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 6aa3ae923..3259397b2 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h | |||
| @@ -22,7 +22,6 @@ namespace Core::Timing { | |||
| 22 | /// A callback that may be scheduled for a particular core timing event. | 22 | /// A callback that may be scheduled for a particular core timing event. |
| 23 | using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( | 23 | using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( |
| 24 | std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; | 24 | std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; |
| 25 | using PauseCallback = std::function<void(bool paused)>; | ||
| 26 | 25 | ||
| 27 | /// Contains the characteristics of a particular event. | 26 | /// Contains the characteristics of a particular event. |
| 28 | struct EventType { | 27 | struct EventType { |
| @@ -134,9 +133,6 @@ public: | |||
| 134 | /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. | 133 | /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. |
| 135 | std::optional<s64> Advance(); | 134 | std::optional<s64> Advance(); |
| 136 | 135 | ||
| 137 | /// Register a callback function to be called when coretiming pauses. | ||
| 138 | void RegisterPauseCallback(PauseCallback&& callback); | ||
| 139 | |||
| 140 | private: | 136 | private: |
| 141 | struct Event; | 137 | struct Event; |
| 142 | 138 | ||
| @@ -176,8 +172,6 @@ private: | |||
| 176 | /// Cycle timing | 172 | /// Cycle timing |
| 177 | u64 ticks{}; | 173 | u64 ticks{}; |
| 178 | s64 downcount{}; | 174 | s64 downcount{}; |
| 179 | |||
| 180 | std::vector<PauseCallback> pause_callbacks{}; | ||
| 181 | }; | 175 | }; |
| 182 | 176 | ||
| 183 | /// Creates a core timing event with the given name and callback. | 177 | /// Creates a core timing event with the given name and callback. |
diff --git a/src/core/hid/emulated_controller.cpp b/src/core/hid/emulated_controller.cpp index f9f902c2d..01c43be93 100644 --- a/src/core/hid/emulated_controller.cpp +++ b/src/core/hid/emulated_controller.cpp | |||
| @@ -562,6 +562,16 @@ void EmulatedController::SetButton(const Common::Input::CallbackStatus& callback | |||
| 562 | return; | 562 | return; |
| 563 | } | 563 | } |
| 564 | 564 | ||
| 565 | // GC controllers have triggers not buttons | ||
| 566 | if (npad_type == NpadStyleIndex::GameCube) { | ||
| 567 | if (index == Settings::NativeButton::ZR) { | ||
| 568 | return; | ||
| 569 | } | ||
| 570 | if (index == Settings::NativeButton::ZL) { | ||
| 571 | return; | ||
| 572 | } | ||
| 573 | } | ||
| 574 | |||
| 565 | switch (index) { | 575 | switch (index) { |
| 566 | case Settings::NativeButton::A: | 576 | case Settings::NativeButton::A: |
| 567 | controller.npad_button_state.a.Assign(current_status.value); | 577 | controller.npad_button_state.a.Assign(current_status.value); |
| @@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac | |||
| 738 | return; | 748 | return; |
| 739 | } | 749 | } |
| 740 | 750 | ||
| 751 | // Only GC controllers have analog triggers | ||
| 752 | if (npad_type != NpadStyleIndex::GameCube) { | ||
| 753 | return; | ||
| 754 | } | ||
| 755 | |||
| 741 | const auto& trigger = controller.trigger_values[index]; | 756 | const auto& trigger = controller.trigger_values[index]; |
| 742 | 757 | ||
| 743 | switch (index) { | 758 | switch (index) { |
diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 68d143a01..52fb69e9c 100644 --- a/src/core/hid/input_converter.cpp +++ b/src/core/hid/input_converter.cpp | |||
| @@ -52,6 +52,9 @@ Common::Input::ButtonStatus TransformToButton(const Common::Input::CallbackStatu | |||
| 52 | Common::Input::ButtonStatus status{}; | 52 | Common::Input::ButtonStatus status{}; |
| 53 | switch (callback.type) { | 53 | switch (callback.type) { |
| 54 | case Common::Input::InputType::Analog: | 54 | case Common::Input::InputType::Analog: |
| 55 | status.value = TransformToTrigger(callback).pressed.value; | ||
| 56 | status.toggle = callback.analog_status.properties.toggle; | ||
| 57 | break; | ||
| 55 | case Common::Input::InputType::Trigger: | 58 | case Common::Input::InputType::Trigger: |
| 56 | status.value = TransformToTrigger(callback).pressed.value; | 59 | status.value = TransformToTrigger(callback).pressed.value; |
| 57 | break; | 60 | break; |
diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 4de44cd06..47a1b829b 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h | |||
| @@ -117,6 +117,7 @@ union Result { | |||
| 117 | BitField<0, 9, ErrorModule> module; | 117 | BitField<0, 9, ErrorModule> module; |
| 118 | BitField<9, 13, u32> description; | 118 | BitField<9, 13, u32> description; |
| 119 | 119 | ||
| 120 | Result() = default; | ||
| 120 | constexpr explicit Result(u32 raw_) : raw(raw_) {} | 121 | constexpr explicit Result(u32 raw_) : raw(raw_) {} |
| 121 | 122 | ||
| 122 | constexpr Result(ErrorModule module_, u32 description_) | 123 | constexpr Result(ErrorModule module_, u32 description_) |
| @@ -130,6 +131,7 @@ union Result { | |||
| 130 | return !IsSuccess(); | 131 | return !IsSuccess(); |
| 131 | } | 132 | } |
| 132 | }; | 133 | }; |
| 134 | static_assert(std::is_trivial_v<Result>); | ||
| 133 | 135 | ||
| 134 | [[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { | 136 | [[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { |
| 135 | return a.raw == b.raw; | 137 | return a.raw == b.raw; |
diff --git a/src/core/hle/service/audio/audout_u.cpp b/src/core/hle/service/audio/audout_u.cpp index a44dd842a..49c092301 100644 --- a/src/core/hle/service/audio/audout_u.cpp +++ b/src/core/hle/service/audio/audout_u.cpp | |||
| @@ -246,9 +246,8 @@ void AudOutU::ListAudioOuts(Kernel::HLERequestContext& ctx) { | |||
| 246 | const auto write_count = | 246 | const auto write_count = |
| 247 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); | 247 | static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); |
| 248 | std::vector<AudioDevice::AudioDeviceName> device_names{}; | 248 | std::vector<AudioDevice::AudioDeviceName> device_names{}; |
| 249 | std::string print_names{}; | ||
| 250 | if (write_count > 0) { | 249 | if (write_count > 0) { |
| 251 | device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); | 250 | device_names.emplace_back("DeviceOut"); |
| 252 | LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); | 251 | LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); |
| 253 | } else { | 252 | } else { |
| 254 | LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); | 253 | LOG_DEBUG(Service_Audio, "called. Empty buffer passed in."); |
diff --git a/src/core/hle/service/audio/audren_u.cpp b/src/core/hle/service/audio/audren_u.cpp index bc69117c6..6fb07c37d 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp | |||
| @@ -252,7 +252,7 @@ private: | |||
| 252 | 252 | ||
| 253 | std::vector<AudioDevice::AudioDeviceName> out_names{}; | 253 | std::vector<AudioDevice::AudioDeviceName> out_names{}; |
| 254 | 254 | ||
| 255 | u32 out_count = impl->ListAudioDeviceName(out_names, in_count); | 255 | const u32 out_count = impl->ListAudioDeviceName(out_names, in_count); |
| 256 | 256 | ||
| 257 | std::string out{}; | 257 | std::string out{}; |
| 258 | for (u32 i = 0; i < out_count; i++) { | 258 | for (u32 i = 0; i < out_count; i++) { |
| @@ -365,7 +365,7 @@ private: | |||
| 365 | 365 | ||
| 366 | std::vector<AudioDevice::AudioDeviceName> out_names{}; | 366 | std::vector<AudioDevice::AudioDeviceName> out_names{}; |
| 367 | 367 | ||
| 368 | u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); | 368 | const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); |
| 369 | 369 | ||
| 370 | std::string out{}; | 370 | std::string out{}; |
| 371 | for (u32 i = 0; i < out_count; i++) { | 371 | for (u32 i = 0; i < out_count; i++) { |
diff --git a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp index 2a5128c60..a7385fce8 100644 --- a/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp +++ b/src/core/hle/service/nvdrv/devices/nvhost_nvdec.cpp | |||
| @@ -1,6 +1,7 @@ | |||
| 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project | 1 | // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project |
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include "audio_core/audio_core.h" | ||
| 4 | #include "common/assert.h" | 5 | #include "common/assert.h" |
| 5 | #include "common/logging/log.h" | 6 | #include "common/logging/log.h" |
| 6 | #include "core/core.h" | 7 | #include "core/core.h" |
| @@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& | |||
| 65 | return NvResult::NotImplemented; | 66 | return NvResult::NotImplemented; |
| 66 | } | 67 | } |
| 67 | 68 | ||
| 68 | void nvhost_nvdec::OnOpen(DeviceFD fd) {} | 69 | void nvhost_nvdec::OnOpen(DeviceFD fd) { |
| 70 | LOG_INFO(Service_NVDRV, "NVDEC video stream started"); | ||
| 71 | system.AudioCore().SetNVDECActive(true); | ||
| 72 | } | ||
| 69 | 73 | ||
| 70 | void nvhost_nvdec::OnClose(DeviceFD fd) { | 74 | void nvhost_nvdec::OnClose(DeviceFD fd) { |
| 71 | LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); | 75 | LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); |
| @@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) { | |||
| 73 | if (iter != fd_to_id.end()) { | 77 | if (iter != fd_to_id.end()) { |
| 74 | system.GPU().ClearCdmaInstance(iter->second); | 78 | system.GPU().ClearCdmaInstance(iter->second); |
| 75 | } | 79 | } |
| 80 | system.AudioCore().SetNVDECActive(false); | ||
| 76 | } | 81 | } |
| 77 | 82 | ||
| 78 | } // namespace Service::Nvidia::Devices | 83 | } // namespace Service::Nvidia::Devices |
diff --git a/src/core/hle/service/nvflinger/nvflinger.cpp b/src/core/hle/service/nvflinger/nvflinger.cpp index 5574269eb..9b382bf56 100644 --- a/src/core/hle/service/nvflinger/nvflinger.cpp +++ b/src/core/hle/service/nvflinger/nvflinger.cpp | |||
| @@ -38,20 +38,16 @@ void NVFlinger::SplitVSync(std::stop_token stop_token) { | |||
| 38 | 38 | ||
| 39 | Common::SetCurrentThreadName(name.c_str()); | 39 | Common::SetCurrentThreadName(name.c_str()); |
| 40 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); | 40 | Common::SetCurrentThreadPriority(Common::ThreadPriority::High); |
| 41 | s64 delay = 0; | 41 | |
| 42 | while (!stop_token.stop_requested()) { | 42 | while (!stop_token.stop_requested()) { |
| 43 | vsync_signal.wait(false); | ||
| 44 | vsync_signal.store(false); | ||
| 45 | |||
| 43 | guard->lock(); | 46 | guard->lock(); |
| 44 | const s64 time_start = system.CoreTiming().GetGlobalTimeNs().count(); | 47 | |
| 45 | Compose(); | 48 | Compose(); |
| 46 | const auto ticks = GetNextTicks(); | 49 | |
| 47 | const s64 time_end = system.CoreTiming().GetGlobalTimeNs().count(); | ||
| 48 | const s64 time_passed = time_end - time_start; | ||
| 49 | const s64 next_time = std::max<s64>(0, ticks - time_passed - delay); | ||
| 50 | guard->unlock(); | 50 | guard->unlock(); |
| 51 | if (next_time > 0) { | ||
| 52 | std::this_thread::sleep_for(std::chrono::nanoseconds{next_time}); | ||
| 53 | } | ||
| 54 | delay = (system.CoreTiming().GetGlobalTimeNs().count() - time_end) - next_time; | ||
| 55 | } | 51 | } |
| 56 | } | 52 | } |
| 57 | 53 | ||
| @@ -66,27 +62,41 @@ NVFlinger::NVFlinger(Core::System& system_, HosBinderDriverServer& hos_binder_dr | |||
| 66 | guard = std::make_shared<std::mutex>(); | 62 | guard = std::make_shared<std::mutex>(); |
| 67 | 63 | ||
| 68 | // Schedule the screen composition events | 64 | // Schedule the screen composition events |
| 69 | composition_event = Core::Timing::CreateEvent( | 65 | multi_composition_event = Core::Timing::CreateEvent( |
| 66 | "ScreenComposition", | ||
| 67 | [this](std::uintptr_t, s64 time, | ||
| 68 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | ||
| 69 | vsync_signal.store(true); | ||
| 70 | vsync_signal.notify_all(); | ||
| 71 | return std::chrono::nanoseconds(GetNextTicks()); | ||
| 72 | }); | ||
| 73 | |||
| 74 | single_composition_event = Core::Timing::CreateEvent( | ||
| 70 | "ScreenComposition", | 75 | "ScreenComposition", |
| 71 | [this](std::uintptr_t, s64 time, | 76 | [this](std::uintptr_t, s64 time, |
| 72 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { | 77 | std::chrono::nanoseconds ns_late) -> std::optional<std::chrono::nanoseconds> { |
| 73 | const auto lock_guard = Lock(); | 78 | const auto lock_guard = Lock(); |
| 74 | Compose(); | 79 | Compose(); |
| 75 | 80 | ||
| 76 | return std::max(std::chrono::nanoseconds::zero(), | 81 | return std::chrono::nanoseconds(GetNextTicks()); |
| 77 | std::chrono::nanoseconds(GetNextTicks()) - ns_late); | ||
| 78 | }); | 82 | }); |
| 79 | 83 | ||
| 80 | if (system.IsMulticore()) { | 84 | if (system.IsMulticore()) { |
| 85 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, multi_composition_event); | ||
| 81 | vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); | 86 | vsync_thread = std::jthread([this](std::stop_token token) { SplitVSync(token); }); |
| 82 | } else { | 87 | } else { |
| 83 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, composition_event); | 88 | system.CoreTiming().ScheduleLoopingEvent(frame_ns, frame_ns, single_composition_event); |
| 84 | } | 89 | } |
| 85 | } | 90 | } |
| 86 | 91 | ||
| 87 | NVFlinger::~NVFlinger() { | 92 | NVFlinger::~NVFlinger() { |
| 88 | if (!system.IsMulticore()) { | 93 | if (system.IsMulticore()) { |
| 89 | system.CoreTiming().UnscheduleEvent(composition_event, 0); | 94 | system.CoreTiming().UnscheduleEvent(multi_composition_event, {}); |
| 95 | vsync_thread.request_stop(); | ||
| 96 | vsync_signal.store(true); | ||
| 97 | vsync_signal.notify_all(); | ||
| 98 | } else { | ||
| 99 | system.CoreTiming().UnscheduleEvent(single_composition_event, {}); | ||
| 90 | } | 100 | } |
| 91 | 101 | ||
| 92 | for (auto& display : displays) { | 102 | for (auto& display : displays) { |
diff --git a/src/core/hle/service/nvflinger/nvflinger.h b/src/core/hle/service/nvflinger/nvflinger.h index 4775597cc..044ac6ac8 100644 --- a/src/core/hle/service/nvflinger/nvflinger.h +++ b/src/core/hle/service/nvflinger/nvflinger.h | |||
| @@ -126,12 +126,15 @@ private: | |||
| 126 | u32 swap_interval = 1; | 126 | u32 swap_interval = 1; |
| 127 | 127 | ||
| 128 | /// Event that handles screen composition. | 128 | /// Event that handles screen composition. |
| 129 | std::shared_ptr<Core::Timing::EventType> composition_event; | 129 | std::shared_ptr<Core::Timing::EventType> multi_composition_event; |
| 130 | std::shared_ptr<Core::Timing::EventType> single_composition_event; | ||
| 130 | 131 | ||
| 131 | std::shared_ptr<std::mutex> guard; | 132 | std::shared_ptr<std::mutex> guard; |
| 132 | 133 | ||
| 133 | Core::System& system; | 134 | Core::System& system; |
| 134 | 135 | ||
| 136 | std::atomic<bool> vsync_signal; | ||
| 137 | |||
| 135 | std::jthread vsync_thread; | 138 | std::jthread vsync_thread; |
| 136 | 139 | ||
| 137 | KernelHelpers::ServiceContext service_context; | 140 | KernelHelpers::ServiceContext service_context; |
diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index 737aedbe4..1efdbc1f7 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt | |||
| @@ -16,7 +16,7 @@ if (ENABLE_WEB_SERVICE) | |||
| 16 | target_link_libraries(yuzu-room PRIVATE web_service) | 16 | target_link_libraries(yuzu-room PRIVATE web_service) |
| 17 | endif() | 17 | endif() |
| 18 | 18 | ||
| 19 | target_link_libraries(yuzu-room PRIVATE mbedtls) | 19 | target_link_libraries(yuzu-room PRIVATE mbedtls mbedcrypto) |
| 20 | if (MSVC) | 20 | if (MSVC) |
| 21 | target_link_libraries(yuzu-room PRIVATE getopt) | 21 | target_link_libraries(yuzu-room PRIVATE getopt) |
| 22 | endif() | 22 | endif() |
diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 133422d5c..ffb9b945e 100644 --- a/src/input_common/input_poller.cpp +++ b/src/input_common/input_poller.cpp | |||
| @@ -824,6 +824,7 @@ std::unique_ptr<Common::Input::InputDevice> InputFactory::CreateAnalogDevice( | |||
| 824 | .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), | 824 | .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), |
| 825 | .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), | 825 | .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), |
| 826 | .inverted = params.Get("invert", "+") == "-", | 826 | .inverted = params.Get("invert", "+") == "-", |
| 827 | .toggle = static_cast<bool>(params.Get("toggle", false)), | ||
| 827 | }; | 828 | }; |
| 828 | input_engine->PreSetController(identifier); | 829 | input_engine->PreSetController(identifier); |
| 829 | input_engine->PreSetAxis(identifier, axis); | 830 | input_engine->PreSetAxis(identifier, axis); |
diff --git a/src/video_core/buffer_cache/buffer_base.h b/src/video_core/buffer_cache/buffer_base.h index 0b2bc67b1..f9a6472cf 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h | |||
| @@ -12,6 +12,7 @@ | |||
| 12 | #include "common/common_funcs.h" | 12 | #include "common/common_funcs.h" |
| 13 | #include "common/common_types.h" | 13 | #include "common/common_types.h" |
| 14 | #include "common/div_ceil.h" | 14 | #include "common/div_ceil.h" |
| 15 | #include "common/settings.h" | ||
| 15 | #include "core/memory.h" | 16 | #include "core/memory.h" |
| 16 | 17 | ||
| 17 | namespace VideoCommon { | 18 | namespace VideoCommon { |
| @@ -219,7 +220,9 @@ public: | |||
| 219 | NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits); | 220 | NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits); |
| 220 | untracked_words[word_index] |= cached_bits; | 221 | untracked_words[word_index] |= cached_bits; |
| 221 | cpu_words[word_index] |= cached_bits; | 222 | cpu_words[word_index] |= cached_bits; |
| 222 | cached_words[word_index] = 0; | 223 | if (!Settings::values.use_pessimistic_flushes) { |
| 224 | cached_words[word_index] = 0; | ||
| 225 | } | ||
| 223 | } | 226 | } |
| 224 | } | 227 | } |
| 225 | 228 | ||
diff --git a/src/yuzu/CMakeLists.txt b/src/yuzu/CMakeLists.txt index 50007338f..29d506c47 100644 --- a/src/yuzu/CMakeLists.txt +++ b/src/yuzu/CMakeLists.txt | |||
| @@ -208,6 +208,16 @@ add_executable(yuzu | |||
| 208 | yuzu.rc | 208 | yuzu.rc |
| 209 | ) | 209 | ) |
| 210 | 210 | ||
| 211 | if (WIN32 AND YUZU_CRASH_DUMPS) | ||
| 212 | target_sources(yuzu PRIVATE | ||
| 213 | mini_dump.cpp | ||
| 214 | mini_dump.h | ||
| 215 | ) | ||
| 216 | |||
| 217 | target_link_libraries(yuzu PRIVATE ${DBGHELP_LIBRARY}) | ||
| 218 | target_compile_definitions(yuzu PRIVATE -DYUZU_DBGHELP) | ||
| 219 | endif() | ||
| 220 | |||
| 211 | file(GLOB COMPAT_LIST | 221 | file(GLOB COMPAT_LIST |
| 212 | ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc | 222 | ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.qrc |
| 213 | ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) | 223 | ${PROJECT_BINARY_DIR}/dist/compatibility_list/compatibility_list.json) |
diff --git a/src/yuzu/applets/qt_controller.cpp b/src/yuzu/applets/qt_controller.cpp index 8be311fcb..1d8072243 100644 --- a/src/yuzu/applets/qt_controller.cpp +++ b/src/yuzu/applets/qt_controller.cpp | |||
| @@ -63,7 +63,7 @@ QtControllerSelectorDialog::QtControllerSelectorDialog( | |||
| 63 | InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) | 63 | InputCommon::InputSubsystem* input_subsystem_, Core::System& system_) |
| 64 | : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), | 64 | : QDialog(parent), ui(std::make_unique<Ui::QtControllerSelectorDialog>()), |
| 65 | parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, | 65 | parameters(std::move(parameters_)), input_subsystem{input_subsystem_}, |
| 66 | input_profiles(std::make_unique<InputProfiles>(system_)), system{system_} { | 66 | input_profiles(std::make_unique<InputProfiles>()), system{system_} { |
| 67 | ui->setupUi(this); | 67 | ui->setupUi(this); |
| 68 | 68 | ||
| 69 | player_widgets = { | 69 | player_widgets = { |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index da6e5aa88..a4ed68422 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -15,8 +15,7 @@ | |||
| 15 | 15 | ||
| 16 | namespace FS = Common::FS; | 16 | namespace FS = Common::FS; |
| 17 | 17 | ||
| 18 | Config::Config(Core::System& system_, const std::string& config_name, ConfigType config_type) | 18 | Config::Config(const std::string& config_name, ConfigType config_type) : type(config_type) { |
| 19 | : type(config_type), system{system_} { | ||
| 20 | global = config_type == ConfigType::GlobalConfig; | 19 | global = config_type == ConfigType::GlobalConfig; |
| 21 | 20 | ||
| 22 | Initialize(config_name); | 21 | Initialize(config_name); |
| @@ -546,6 +545,7 @@ void Config::ReadDebuggingValues() { | |||
| 546 | ReadBasicSetting(Settings::values.use_debug_asserts); | 545 | ReadBasicSetting(Settings::values.use_debug_asserts); |
| 547 | ReadBasicSetting(Settings::values.use_auto_stub); | 546 | ReadBasicSetting(Settings::values.use_auto_stub); |
| 548 | ReadBasicSetting(Settings::values.enable_all_controllers); | 547 | ReadBasicSetting(Settings::values.enable_all_controllers); |
| 548 | ReadBasicSetting(Settings::values.create_crash_dumps); | ||
| 549 | 549 | ||
| 550 | qt_config->endGroup(); | 550 | qt_config->endGroup(); |
| 551 | } | 551 | } |
| @@ -684,6 +684,7 @@ void Config::ReadRendererValues() { | |||
| 684 | ReadGlobalSetting(Settings::values.shader_backend); | 684 | ReadGlobalSetting(Settings::values.shader_backend); |
| 685 | ReadGlobalSetting(Settings::values.use_asynchronous_shaders); | 685 | ReadGlobalSetting(Settings::values.use_asynchronous_shaders); |
| 686 | ReadGlobalSetting(Settings::values.use_fast_gpu_time); | 686 | ReadGlobalSetting(Settings::values.use_fast_gpu_time); |
| 687 | ReadGlobalSetting(Settings::values.use_pessimistic_flushes); | ||
| 687 | ReadGlobalSetting(Settings::values.bg_red); | 688 | ReadGlobalSetting(Settings::values.bg_red); |
| 688 | ReadGlobalSetting(Settings::values.bg_green); | 689 | ReadGlobalSetting(Settings::values.bg_green); |
| 689 | ReadGlobalSetting(Settings::values.bg_blue); | 690 | ReadGlobalSetting(Settings::values.bg_blue); |
| @@ -1160,6 +1161,7 @@ void Config::SaveDebuggingValues() { | |||
| 1160 | WriteBasicSetting(Settings::values.use_debug_asserts); | 1161 | WriteBasicSetting(Settings::values.use_debug_asserts); |
| 1161 | WriteBasicSetting(Settings::values.disable_macro_jit); | 1162 | WriteBasicSetting(Settings::values.disable_macro_jit); |
| 1162 | WriteBasicSetting(Settings::values.enable_all_controllers); | 1163 | WriteBasicSetting(Settings::values.enable_all_controllers); |
| 1164 | WriteBasicSetting(Settings::values.create_crash_dumps); | ||
| 1163 | 1165 | ||
| 1164 | qt_config->endGroup(); | 1166 | qt_config->endGroup(); |
| 1165 | } | 1167 | } |
| @@ -1300,6 +1302,7 @@ void Config::SaveRendererValues() { | |||
| 1300 | Settings::values.shader_backend.UsingGlobal()); | 1302 | Settings::values.shader_backend.UsingGlobal()); |
| 1301 | WriteGlobalSetting(Settings::values.use_asynchronous_shaders); | 1303 | WriteGlobalSetting(Settings::values.use_asynchronous_shaders); |
| 1302 | WriteGlobalSetting(Settings::values.use_fast_gpu_time); | 1304 | WriteGlobalSetting(Settings::values.use_fast_gpu_time); |
| 1305 | WriteGlobalSetting(Settings::values.use_pessimistic_flushes); | ||
| 1303 | WriteGlobalSetting(Settings::values.bg_red); | 1306 | WriteGlobalSetting(Settings::values.bg_red); |
| 1304 | WriteGlobalSetting(Settings::values.bg_green); | 1307 | WriteGlobalSetting(Settings::values.bg_green); |
| 1305 | WriteGlobalSetting(Settings::values.bg_blue); | 1308 | WriteGlobalSetting(Settings::values.bg_blue); |
| @@ -1545,7 +1548,6 @@ void Config::Reload() { | |||
| 1545 | ReadValues(); | 1548 | ReadValues(); |
| 1546 | // To apply default value changes | 1549 | // To apply default value changes |
| 1547 | SaveValues(); | 1550 | SaveValues(); |
| 1548 | system.ApplySettings(); | ||
| 1549 | } | 1551 | } |
| 1550 | 1552 | ||
| 1551 | void Config::Save() { | 1553 | void Config::Save() { |
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h index 486ceea94..06fa7d2d0 100644 --- a/src/yuzu/configuration/config.h +++ b/src/yuzu/configuration/config.h | |||
| @@ -25,7 +25,7 @@ public: | |||
| 25 | InputProfile, | 25 | InputProfile, |
| 26 | }; | 26 | }; |
| 27 | 27 | ||
| 28 | explicit Config(Core::System& system_, const std::string& config_name = "qt-config", | 28 | explicit Config(const std::string& config_name = "qt-config", |
| 29 | ConfigType config_type = ConfigType::GlobalConfig); | 29 | ConfigType config_type = ConfigType::GlobalConfig); |
| 30 | ~Config(); | 30 | ~Config(); |
| 31 | 31 | ||
| @@ -194,8 +194,6 @@ private: | |||
| 194 | std::unique_ptr<QSettings> qt_config; | 194 | std::unique_ptr<QSettings> qt_config; |
| 195 | std::string qt_config_loc; | 195 | std::string qt_config_loc; |
| 196 | bool global; | 196 | bool global; |
| 197 | |||
| 198 | Core::System& system; | ||
| 199 | }; | 197 | }; |
| 200 | 198 | ||
| 201 | // These metatype declarations cannot be in common/settings.h because core is devoid of QT | 199 | // These metatype declarations cannot be in common/settings.h because core is devoid of QT |
diff --git a/src/yuzu/configuration/configure_debug.cpp b/src/yuzu/configuration/configure_debug.cpp index 04d397750..622808e94 100644 --- a/src/yuzu/configuration/configure_debug.cpp +++ b/src/yuzu/configuration/configure_debug.cpp | |||
| @@ -2,6 +2,7 @@ | |||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | 2 | // SPDX-License-Identifier: GPL-2.0-or-later |
| 3 | 3 | ||
| 4 | #include <QDesktopServices> | 4 | #include <QDesktopServices> |
| 5 | #include <QMessageBox> | ||
| 5 | #include <QUrl> | 6 | #include <QUrl> |
| 6 | #include "common/fs/path_util.h" | 7 | #include "common/fs/path_util.h" |
| 7 | #include "common/logging/backend.h" | 8 | #include "common/logging/backend.h" |
| @@ -26,6 +27,16 @@ ConfigureDebug::ConfigureDebug(const Core::System& system_, QWidget* parent) | |||
| 26 | 27 | ||
| 27 | connect(ui->toggle_gdbstub, &QCheckBox::toggled, | 28 | connect(ui->toggle_gdbstub, &QCheckBox::toggled, |
| 28 | [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); | 29 | [&]() { ui->gdbport_spinbox->setEnabled(ui->toggle_gdbstub->isChecked()); }); |
| 30 | |||
| 31 | connect(ui->create_crash_dumps, &QCheckBox::stateChanged, [&](int) { | ||
| 32 | if (crash_dump_warning_shown) { | ||
| 33 | return; | ||
| 34 | } | ||
| 35 | QMessageBox::warning(this, tr("Restart Required"), | ||
| 36 | tr("yuzu is required to restart in order to apply this setting."), | ||
| 37 | QMessageBox::Ok, QMessageBox::Ok); | ||
| 38 | crash_dump_warning_shown = true; | ||
| 39 | }); | ||
| 29 | } | 40 | } |
| 30 | 41 | ||
| 31 | ConfigureDebug::~ConfigureDebug() = default; | 42 | ConfigureDebug::~ConfigureDebug() = default; |
| @@ -71,7 +82,14 @@ void ConfigureDebug::SetConfiguration() { | |||
| 71 | ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); | 82 | ui->disable_web_applet->setChecked(UISettings::values.disable_web_applet.GetValue()); |
| 72 | #else | 83 | #else |
| 73 | ui->disable_web_applet->setEnabled(false); | 84 | ui->disable_web_applet->setEnabled(false); |
| 74 | ui->disable_web_applet->setText(QString::fromUtf8("Web applet not compiled")); | 85 | ui->disable_web_applet->setText(tr("Web applet not compiled")); |
| 86 | #endif | ||
| 87 | |||
| 88 | #ifdef YUZU_DBGHELP | ||
| 89 | ui->create_crash_dumps->setChecked(Settings::values.create_crash_dumps.GetValue()); | ||
| 90 | #else | ||
| 91 | ui->create_crash_dumps->setEnabled(false); | ||
| 92 | ui->create_crash_dumps->setText(tr("MiniDump creation not compiled")); | ||
| 75 | #endif | 93 | #endif |
| 76 | } | 94 | } |
| 77 | 95 | ||
| @@ -84,6 +102,7 @@ void ConfigureDebug::ApplyConfiguration() { | |||
| 84 | Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); | 102 | Settings::values.enable_fs_access_log = ui->fs_access_log->isChecked(); |
| 85 | Settings::values.reporting_services = ui->reporting_services->isChecked(); | 103 | Settings::values.reporting_services = ui->reporting_services->isChecked(); |
| 86 | Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); | 104 | Settings::values.dump_audio_commands = ui->dump_audio_commands->isChecked(); |
| 105 | Settings::values.create_crash_dumps = ui->create_crash_dumps->isChecked(); | ||
| 87 | Settings::values.quest_flag = ui->quest_flag->isChecked(); | 106 | Settings::values.quest_flag = ui->quest_flag->isChecked(); |
| 88 | Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); | 107 | Settings::values.use_debug_asserts = ui->use_debug_asserts->isChecked(); |
| 89 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); | 108 | Settings::values.use_auto_stub = ui->use_auto_stub->isChecked(); |
diff --git a/src/yuzu/configuration/configure_debug.h b/src/yuzu/configuration/configure_debug.h index 42d30f170..030a0b7f7 100644 --- a/src/yuzu/configuration/configure_debug.h +++ b/src/yuzu/configuration/configure_debug.h | |||
| @@ -32,4 +32,6 @@ private: | |||
| 32 | std::unique_ptr<Ui::ConfigureDebug> ui; | 32 | std::unique_ptr<Ui::ConfigureDebug> ui; |
| 33 | 33 | ||
| 34 | const Core::System& system; | 34 | const Core::System& system; |
| 35 | |||
| 36 | bool crash_dump_warning_shown{false}; | ||
| 35 | }; | 37 | }; |
diff --git a/src/yuzu/configuration/configure_debug.ui b/src/yuzu/configuration/configure_debug.ui index 47b8b80f1..314d47af5 100644 --- a/src/yuzu/configuration/configure_debug.ui +++ b/src/yuzu/configuration/configure_debug.ui | |||
| @@ -7,60 +7,60 @@ | |||
| 7 | </property> | 7 | </property> |
| 8 | <widget class="QWidget"> | 8 | <widget class="QWidget"> |
| 9 | <layout class="QVBoxLayout" name="verticalLayout_1"> | 9 | <layout class="QVBoxLayout" name="verticalLayout_1"> |
| 10 | <item> | 10 | <item> |
| 11 | <layout class="QVBoxLayout" name="verticalLayout_2"> | 11 | <layout class="QVBoxLayout" name="verticalLayout_2"> |
| 12 | <item> | 12 | <item> |
| 13 | <widget class="QGroupBox" name="groupBox"> | 13 | <widget class="QGroupBox" name="groupBox"> |
| 14 | <property name="title"> | 14 | <property name="title"> |
| 15 | <string>Debugger</string> | 15 | <string>Debugger</string> |
| 16 | </property> | 16 | </property> |
| 17 | <layout class="QVBoxLayout" name="verticalLayout_3"> | 17 | <layout class="QVBoxLayout" name="verticalLayout_3"> |
| 18 | <item> | ||
| 19 | <layout class="QHBoxLayout" name="horizontalLayout_11"> | ||
| 20 | <item> | ||
| 21 | <widget class="QCheckBox" name="toggle_gdbstub"> | ||
| 22 | <property name="text"> | ||
| 23 | <string>Enable GDB Stub</string> | ||
| 24 | </property> | ||
| 25 | </widget> | ||
| 26 | </item> | ||
| 27 | <item> | ||
| 28 | <spacer name="horizontalSpacer"> | ||
| 29 | <property name="orientation"> | ||
| 30 | <enum>Qt::Horizontal</enum> | ||
| 31 | </property> | ||
| 32 | <property name="sizeHint" stdset="0"> | ||
| 33 | <size> | ||
| 34 | <width>40</width> | ||
| 35 | <height>20</height> | ||
| 36 | </size> | ||
| 37 | </property> | ||
| 38 | </spacer> | ||
| 39 | </item> | ||
| 40 | <item> | ||
| 41 | <widget class="QLabel" name="label_11"> | ||
| 42 | <property name="text"> | ||
| 43 | <string>Port:</string> | ||
| 44 | </property> | ||
| 45 | </widget> | ||
| 46 | </item> | ||
| 18 | <item> | 47 | <item> |
| 19 | <layout class="QHBoxLayout" name="horizontalLayout_11"> | 48 | <widget class="QSpinBox" name="gdbport_spinbox"> |
| 20 | <item> | 49 | <property name="minimum"> |
| 21 | <widget class="QCheckBox" name="toggle_gdbstub"> | 50 | <number>1024</number> |
| 22 | <property name="text"> | 51 | </property> |
| 23 | <string>Enable GDB Stub</string> | 52 | <property name="maximum"> |
| 24 | </property> | 53 | <number>65535</number> |
| 25 | </widget> | 54 | </property> |
| 26 | </item> | 55 | </widget> |
| 27 | <item> | ||
| 28 | <spacer name="horizontalSpacer"> | ||
| 29 | <property name="orientation"> | ||
| 30 | <enum>Qt::Horizontal</enum> | ||
| 31 | </property> | ||
| 32 | <property name="sizeHint" stdset="0"> | ||
| 33 | <size> | ||
| 34 | <width>40</width> | ||
| 35 | <height>20</height> | ||
| 36 | </size> | ||
| 37 | </property> | ||
| 38 | </spacer> | ||
| 39 | </item> | ||
| 40 | <item> | ||
| 41 | <widget class="QLabel" name="label_11"> | ||
| 42 | <property name="text"> | ||
| 43 | <string>Port:</string> | ||
| 44 | </property> | ||
| 45 | </widget> | ||
| 46 | </item> | ||
| 47 | <item> | ||
| 48 | <widget class="QSpinBox" name="gdbport_spinbox"> | ||
| 49 | <property name="minimum"> | ||
| 50 | <number>1024</number> | ||
| 51 | </property> | ||
| 52 | <property name="maximum"> | ||
| 53 | <number>65535</number> | ||
| 54 | </property> | ||
| 55 | </widget> | ||
| 56 | </item> | ||
| 57 | </layout> | ||
| 58 | </item> | 56 | </item> |
| 59 | </layout> | 57 | </layout> |
| 60 | </widget> | 58 | </item> |
| 61 | </item> | 59 | </layout> |
| 62 | </layout> | 60 | </widget> |
| 63 | </item> | 61 | </item> |
| 62 | </layout> | ||
| 63 | </item> | ||
| 64 | <item> | 64 | <item> |
| 65 | <widget class="QGroupBox" name="groupBox_2"> | 65 | <widget class="QGroupBox" name="groupBox_2"> |
| 66 | <property name="title"> | 66 | <property name="title"> |
| @@ -231,6 +231,13 @@ | |||
| 231 | <string>Debugging</string> | 231 | <string>Debugging</string> |
| 232 | </property> | 232 | </property> |
| 233 | <layout class="QGridLayout" name="gridLayout_3"> | 233 | <layout class="QGridLayout" name="gridLayout_3"> |
| 234 | <item row="2" column="0"> | ||
| 235 | <widget class="QCheckBox" name="reporting_services"> | ||
| 236 | <property name="text"> | ||
| 237 | <string>Enable Verbose Reporting Services**</string> | ||
| 238 | </property> | ||
| 239 | </widget> | ||
| 240 | </item> | ||
| 234 | <item row="0" column="0"> | 241 | <item row="0" column="0"> |
| 235 | <widget class="QCheckBox" name="fs_access_log"> | 242 | <widget class="QCheckBox" name="fs_access_log"> |
| 236 | <property name="text"> | 243 | <property name="text"> |
| @@ -238,20 +245,20 @@ | |||
| 238 | </property> | 245 | </property> |
| 239 | </widget> | 246 | </widget> |
| 240 | </item> | 247 | </item> |
| 241 | <item row="1" column="0"> | 248 | <item row="0" column="1"> |
| 242 | <widget class="QCheckBox" name="dump_audio_commands"> | 249 | <widget class="QCheckBox" name="dump_audio_commands"> |
| 243 | <property name="text"> | ||
| 244 | <string>Dump Audio Commands To Console**</string> | ||
| 245 | </property> | ||
| 246 | <property name="toolTip"> | 250 | <property name="toolTip"> |
| 247 | <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> | 251 | <string>Enable this to output the latest generated audio command list to the console. Only affects games using the audio renderer.</string> |
| 248 | </property> | 252 | </property> |
| 253 | <property name="text"> | ||
| 254 | <string>Dump Audio Commands To Console**</string> | ||
| 255 | </property> | ||
| 249 | </widget> | 256 | </widget> |
| 250 | </item> | 257 | </item> |
| 251 | <item row="2" column="0"> | 258 | <item row="2" column="1"> |
| 252 | <widget class="QCheckBox" name="reporting_services"> | 259 | <widget class="QCheckBox" name="create_crash_dumps"> |
| 253 | <property name="text"> | 260 | <property name="text"> |
| 254 | <string>Enable Verbose Reporting Services**</string> | 261 | <string>Create Minidump After Crash</string> |
| 255 | </property> | 262 | </property> |
| 256 | </widget> | 263 | </widget> |
| 257 | </item> | 264 | </item> |
| @@ -340,7 +347,6 @@ | |||
| 340 | <tabstop>disable_loop_safety_checks</tabstop> | 347 | <tabstop>disable_loop_safety_checks</tabstop> |
| 341 | <tabstop>fs_access_log</tabstop> | 348 | <tabstop>fs_access_log</tabstop> |
| 342 | <tabstop>reporting_services</tabstop> | 349 | <tabstop>reporting_services</tabstop> |
| 343 | <tabstop>dump_audio_commands</tabstop> | ||
| 344 | <tabstop>quest_flag</tabstop> | 350 | <tabstop>quest_flag</tabstop> |
| 345 | <tabstop>enable_cpu_debugging</tabstop> | 351 | <tabstop>enable_cpu_debugging</tabstop> |
| 346 | <tabstop>use_debug_asserts</tabstop> | 352 | <tabstop>use_debug_asserts</tabstop> |
diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 7c3196c83..01f074699 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp | |||
| @@ -28,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { | |||
| 28 | ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); | 28 | ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); |
| 29 | ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); | 29 | ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); |
| 30 | ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); | 30 | ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); |
| 31 | ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue()); | ||
| 31 | 32 | ||
| 32 | if (Settings::IsConfiguringGlobal()) { | 33 | if (Settings::IsConfiguringGlobal()) { |
| 33 | ui->gpu_accuracy->setCurrentIndex( | 34 | ui->gpu_accuracy->setCurrentIndex( |
| @@ -55,6 +56,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { | |||
| 55 | use_asynchronous_shaders); | 56 | use_asynchronous_shaders); |
| 56 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, | 57 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, |
| 57 | ui->use_fast_gpu_time, use_fast_gpu_time); | 58 | ui->use_fast_gpu_time, use_fast_gpu_time); |
| 59 | ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes, | ||
| 60 | ui->use_pessimistic_flushes, use_pessimistic_flushes); | ||
| 58 | } | 61 | } |
| 59 | 62 | ||
| 60 | void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { | 63 | void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { |
| @@ -77,6 +80,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { | |||
| 77 | ui->use_asynchronous_shaders->setEnabled( | 80 | ui->use_asynchronous_shaders->setEnabled( |
| 78 | Settings::values.use_asynchronous_shaders.UsingGlobal()); | 81 | Settings::values.use_asynchronous_shaders.UsingGlobal()); |
| 79 | ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); | 82 | ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); |
| 83 | ui->use_pessimistic_flushes->setEnabled( | ||
| 84 | Settings::values.use_pessimistic_flushes.UsingGlobal()); | ||
| 80 | ui->anisotropic_filtering_combobox->setEnabled( | 85 | ui->anisotropic_filtering_combobox->setEnabled( |
| 81 | Settings::values.max_anisotropy.UsingGlobal()); | 86 | Settings::values.max_anisotropy.UsingGlobal()); |
| 82 | 87 | ||
| @@ -89,6 +94,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { | |||
| 89 | use_asynchronous_shaders); | 94 | use_asynchronous_shaders); |
| 90 | ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, | 95 | ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, |
| 91 | Settings::values.use_fast_gpu_time, use_fast_gpu_time); | 96 | Settings::values.use_fast_gpu_time, use_fast_gpu_time); |
| 97 | ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes, | ||
| 98 | Settings::values.use_pessimistic_flushes, | ||
| 99 | use_pessimistic_flushes); | ||
| 92 | ConfigurationShared::SetColoredComboBox( | 100 | ConfigurationShared::SetColoredComboBox( |
| 93 | ui->gpu_accuracy, ui->label_gpu_accuracy, | 101 | ui->gpu_accuracy, ui->label_gpu_accuracy, |
| 94 | static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); | 102 | static_cast<int>(Settings::values.gpu_accuracy.GetValue(true))); |
diff --git a/src/yuzu/configuration/configure_graphics_advanced.h b/src/yuzu/configuration/configure_graphics_advanced.h index 1ef7bd916..12e816905 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h | |||
| @@ -39,6 +39,7 @@ private: | |||
| 39 | ConfigurationShared::CheckState use_vsync; | 39 | ConfigurationShared::CheckState use_vsync; |
| 40 | ConfigurationShared::CheckState use_asynchronous_shaders; | 40 | ConfigurationShared::CheckState use_asynchronous_shaders; |
| 41 | ConfigurationShared::CheckState use_fast_gpu_time; | 41 | ConfigurationShared::CheckState use_fast_gpu_time; |
| 42 | ConfigurationShared::CheckState use_pessimistic_flushes; | ||
| 42 | 43 | ||
| 43 | const Core::System& system; | 44 | const Core::System& system; |
| 44 | }; | 45 | }; |
diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index d6d819364..87a121471 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui | |||
| @@ -100,6 +100,16 @@ | |||
| 100 | </widget> | 100 | </widget> |
| 101 | </item> | 101 | </item> |
| 102 | <item> | 102 | <item> |
| 103 | <widget class="QCheckBox" name="use_pessimistic_flushes"> | ||
| 104 | <property name="toolTip"> | ||
| 105 | <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string> | ||
| 106 | </property> | ||
| 107 | <property name="text"> | ||
| 108 | <string>Use pessimistic buffer flushes (Hack)</string> | ||
| 109 | </property> | ||
| 110 | </widget> | ||
| 111 | </item> | ||
| 112 | <item> | ||
| 103 | <widget class="QWidget" name="af_layout" native="true"> | 113 | <widget class="QWidget" name="af_layout" native="true"> |
| 104 | <layout class="QHBoxLayout" name="horizontalLayout_1"> | 114 | <layout class="QHBoxLayout" name="horizontalLayout_1"> |
| 105 | <property name="leftMargin"> | 115 | <property name="leftMargin"> |
diff --git a/src/yuzu/configuration/configure_input.cpp b/src/yuzu/configuration/configure_input.cpp index 16fba3deb..cb55472c9 100644 --- a/src/yuzu/configuration/configure_input.cpp +++ b/src/yuzu/configuration/configure_input.cpp | |||
| @@ -65,7 +65,7 @@ void OnDockedModeChanged(bool last_state, bool new_state, Core::System& system) | |||
| 65 | 65 | ||
| 66 | ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) | 66 | ConfigureInput::ConfigureInput(Core::System& system_, QWidget* parent) |
| 67 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), | 67 | : QWidget(parent), ui(std::make_unique<Ui::ConfigureInput>()), |
| 68 | profiles(std::make_unique<InputProfiles>(system_)), system{system_} { | 68 | profiles(std::make_unique<InputProfiles>()), system{system_} { |
| 69 | ui->setupUi(this); | 69 | ui->setupUi(this); |
| 70 | } | 70 | } |
| 71 | 71 | ||
diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 109689c88..9e5a40fe7 100644 --- a/src/yuzu/configuration/configure_input_player.cpp +++ b/src/yuzu/configuration/configure_input_player.cpp | |||
| @@ -161,6 +161,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { | |||
| 161 | 161 | ||
| 162 | const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); | 162 | const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); |
| 163 | const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); | 163 | const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); |
| 164 | const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); | ||
| 164 | const auto common_button_name = input_subsystem->GetButtonName(param); | 165 | const auto common_button_name = input_subsystem->GetButtonName(param); |
| 165 | 166 | ||
| 166 | // Retrieve the names from Qt | 167 | // Retrieve the names from Qt |
| @@ -184,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { | |||
| 184 | } | 185 | } |
| 185 | if (param.Has("axis")) { | 186 | if (param.Has("axis")) { |
| 186 | const QString axis = QString::fromStdString(param.Get("axis", "")); | 187 | const QString axis = QString::fromStdString(param.Get("axis", "")); |
| 187 | return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); | 188 | return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); |
| 188 | } | 189 | } |
| 189 | if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { | 190 | if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { |
| 190 | const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); | 191 | const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); |
| @@ -362,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 362 | button_map[button_id]->setText(tr("[not set]")); | 363 | button_map[button_id]->setText(tr("[not set]")); |
| 363 | }); | 364 | }); |
| 364 | if (param.Has("code") || param.Has("button") || param.Has("hat")) { | 365 | if (param.Has("code") || param.Has("button") || param.Has("hat")) { |
| 365 | context_menu.addAction(tr("Toggle button"), [&] { | ||
| 366 | const bool toggle_value = !param.Get("toggle", false); | ||
| 367 | param.Set("toggle", toggle_value); | ||
| 368 | button_map[button_id]->setText(ButtonToText(param)); | ||
| 369 | emulated_controller->SetButtonParam(button_id, param); | ||
| 370 | }); | ||
| 371 | context_menu.addAction(tr("Invert button"), [&] { | 366 | context_menu.addAction(tr("Invert button"), [&] { |
| 372 | const bool invert_value = !param.Get("inverted", false); | 367 | const bool invert_value = !param.Get("inverted", false); |
| 373 | param.Set("inverted", invert_value); | 368 | param.Set("inverted", invert_value); |
| 374 | button_map[button_id]->setText(ButtonToText(param)); | 369 | button_map[button_id]->setText(ButtonToText(param)); |
| 375 | emulated_controller->SetButtonParam(button_id, param); | 370 | emulated_controller->SetButtonParam(button_id, param); |
| 376 | }); | 371 | }); |
| 372 | context_menu.addAction(tr("Toggle button"), [&] { | ||
| 373 | const bool toggle_value = !param.Get("toggle", false); | ||
| 374 | param.Set("toggle", toggle_value); | ||
| 375 | button_map[button_id]->setText(ButtonToText(param)); | ||
| 376 | emulated_controller->SetButtonParam(button_id, param); | ||
| 377 | }); | ||
| 377 | } | 378 | } |
| 378 | if (param.Has("axis")) { | 379 | if (param.Has("axis")) { |
| 379 | context_menu.addAction(tr("Invert axis"), [&] { | 380 | context_menu.addAction(tr("Invert axis"), [&] { |
| @@ -398,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i | |||
| 398 | } | 399 | } |
| 399 | emulated_controller->SetButtonParam(button_id, param); | 400 | emulated_controller->SetButtonParam(button_id, param); |
| 400 | }); | 401 | }); |
| 402 | context_menu.addAction(tr("Toggle axis"), [&] { | ||
| 403 | const bool toggle_value = !param.Get("toggle", false); | ||
| 404 | param.Set("toggle", toggle_value); | ||
| 405 | button_map[button_id]->setText(ButtonToText(param)); | ||
| 406 | emulated_controller->SetButtonParam(button_id, param); | ||
| 407 | }); | ||
| 401 | } | 408 | } |
| 402 | context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); | 409 | context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); |
| 403 | }); | 410 | }); |
| @@ -1410,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick( | |||
| 1410 | ui->controllerFrame->BeginMappingAnalog(button_id); | 1417 | ui->controllerFrame->BeginMappingAnalog(button_id); |
| 1411 | } | 1418 | } |
| 1412 | 1419 | ||
| 1413 | timeout_timer->start(2500); // Cancel after 2.5 seconds | 1420 | timeout_timer->start(4000); // Cancel after 4 seconds |
| 1414 | poll_timer->start(25); // Check for new inputs every 25ms | 1421 | poll_timer->start(25); // Check for new inputs every 25ms |
| 1415 | } | 1422 | } |
| 1416 | 1423 | ||
diff --git a/src/yuzu/configuration/configure_per_game.cpp b/src/yuzu/configuration/configure_per_game.cpp index af8343b2e..c3cb8f61d 100644 --- a/src/yuzu/configuration/configure_per_game.cpp +++ b/src/yuzu/configuration/configure_per_game.cpp | |||
| @@ -42,8 +42,7 @@ ConfigurePerGame::ConfigurePerGame(QWidget* parent, u64 title_id_, const std::st | |||
| 42 | const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); | 42 | const auto file_path = std::filesystem::path(Common::FS::ToU8String(file_name)); |
| 43 | const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) | 43 | const auto config_file_name = title_id == 0 ? Common::FS::PathToUTF8String(file_path.filename()) |
| 44 | : fmt::format("{:016X}", title_id); | 44 | : fmt::format("{:016X}", title_id); |
| 45 | game_config = | 45 | game_config = std::make_unique<Config>(config_file_name, Config::ConfigType::PerGameConfig); |
| 46 | std::make_unique<Config>(system, config_file_name, Config::ConfigType::PerGameConfig); | ||
| 47 | 46 | ||
| 48 | addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); | 47 | addons_tab = std::make_unique<ConfigurePerGameAddons>(system_, this); |
| 49 | audio_tab = std::make_unique<ConfigureAudio>(system_, this); | 48 | audio_tab = std::make_unique<ConfigureAudio>(system_, this); |
diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index cf88a5bf0..625af0c89 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui | |||
| @@ -16,6 +16,9 @@ | |||
| 16 | <property name="text"> | 16 | <property name="text"> |
| 17 | <string><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html></string> | 17 | <string><html><head/><body><p>Reads controller input from scripts in the same format as TAS-nx scripts.<br/>For a more detailed explanation, please consult the <a href="https://yuzu-emu.org/help/feature/tas/"><span style=" text-decoration: underline; color:#039be5;">help page</span></a> on the yuzu website.</p></body></html></string> |
| 18 | </property> | 18 | </property> |
| 19 | <property name="openExternalLinks"> | ||
| 20 | <bool>true</bool> | ||
| 21 | </property> | ||
| 19 | </widget> | 22 | </widget> |
| 20 | </item> | 23 | </item> |
| 21 | <item row="1" column="0" colspan="4"> | 24 | <item row="1" column="0" colspan="4"> |
diff --git a/src/yuzu/configuration/configure_web.cpp b/src/yuzu/configuration/configure_web.cpp index d668c992b..ab526e4ca 100644 --- a/src/yuzu/configuration/configure_web.cpp +++ b/src/yuzu/configuration/configure_web.cpp | |||
| @@ -128,20 +128,25 @@ void ConfigureWeb::RefreshTelemetryID() { | |||
| 128 | void ConfigureWeb::OnLoginChanged() { | 128 | void ConfigureWeb::OnLoginChanged() { |
| 129 | if (ui->edit_token->text().isEmpty()) { | 129 | if (ui->edit_token->text().isEmpty()) { |
| 130 | user_verified = true; | 130 | user_verified = true; |
| 131 | 131 | // Empty = no icon | |
| 132 | const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); | 132 | ui->label_token_verified->setPixmap(QPixmap()); |
| 133 | ui->label_token_verified->setPixmap(pixmap); | 133 | ui->label_token_verified->setToolTip(QString()); |
| 134 | } else { | 134 | } else { |
| 135 | user_verified = false; | 135 | user_verified = false; |
| 136 | 136 | ||
| 137 | const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); | 137 | // Show an info icon if it's been changed, clearer than showing failure |
| 138 | const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("info")).pixmap(16); | ||
| 138 | ui->label_token_verified->setPixmap(pixmap); | 139 | ui->label_token_verified->setPixmap(pixmap); |
| 140 | ui->label_token_verified->setToolTip( | ||
| 141 | tr("Unverified, please click Verify before saving configuration", "Tooltip")); | ||
| 139 | } | 142 | } |
| 140 | } | 143 | } |
| 141 | 144 | ||
| 142 | void ConfigureWeb::VerifyLogin() { | 145 | void ConfigureWeb::VerifyLogin() { |
| 143 | ui->button_verify_login->setDisabled(true); | 146 | ui->button_verify_login->setDisabled(true); |
| 144 | ui->button_verify_login->setText(tr("Verifying...")); | 147 | ui->button_verify_login->setText(tr("Verifying...")); |
| 148 | ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("sync")).pixmap(16)); | ||
| 149 | ui->label_token_verified->setToolTip(tr("Verifying...")); | ||
| 145 | verify_watcher.setFuture(QtConcurrent::run( | 150 | verify_watcher.setFuture(QtConcurrent::run( |
| 146 | [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), | 151 | [username = UsernameFromDisplayToken(ui->edit_token->text().toStdString()), |
| 147 | token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { | 152 | token = TokenFromDisplayToken(ui->edit_token->text().toStdString())] { |
| @@ -155,13 +160,13 @@ void ConfigureWeb::OnLoginVerified() { | |||
| 155 | if (verify_watcher.result()) { | 160 | if (verify_watcher.result()) { |
| 156 | user_verified = true; | 161 | user_verified = true; |
| 157 | 162 | ||
| 158 | const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("checked")).pixmap(16); | 163 | ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("checked")).pixmap(16)); |
| 159 | ui->label_token_verified->setPixmap(pixmap); | 164 | ui->label_token_verified->setToolTip(tr("Verified", "Tooltip")); |
| 160 | ui->username->setText( | 165 | ui->username->setText( |
| 161 | QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); | 166 | QString::fromStdString(UsernameFromDisplayToken(ui->edit_token->text().toStdString()))); |
| 162 | } else { | 167 | } else { |
| 163 | const QPixmap pixmap = QIcon::fromTheme(QStringLiteral("failed")).pixmap(16); | 168 | ui->label_token_verified->setPixmap(QIcon::fromTheme(QStringLiteral("failed")).pixmap(16)); |
| 164 | ui->label_token_verified->setPixmap(pixmap); | 169 | ui->label_token_verified->setToolTip(tr("Verification failed", "Tooltip")); |
| 165 | ui->username->setText(tr("Unspecified")); | 170 | ui->username->setText(tr("Unspecified")); |
| 166 | QMessageBox::critical(this, tr("Verification failed"), | 171 | QMessageBox::critical(this, tr("Verification failed"), |
| 167 | tr("Verification failed. Check that you have entered your token " | 172 | tr("Verification failed. Check that you have entered your token " |
diff --git a/src/yuzu/configuration/input_profiles.cpp b/src/yuzu/configuration/input_profiles.cpp index 20b22e7de..807afbeb2 100644 --- a/src/yuzu/configuration/input_profiles.cpp +++ b/src/yuzu/configuration/input_profiles.cpp | |||
| @@ -27,7 +27,7 @@ std::filesystem::path GetNameWithoutExtension(std::filesystem::path filename) { | |||
| 27 | 27 | ||
| 28 | } // namespace | 28 | } // namespace |
| 29 | 29 | ||
| 30 | InputProfiles::InputProfiles(Core::System& system_) : system{system_} { | 30 | InputProfiles::InputProfiles() { |
| 31 | const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; | 31 | const auto input_profile_loc = FS::GetYuzuPath(FS::YuzuPath::ConfigDir) / "input"; |
| 32 | 32 | ||
| 33 | if (!FS::IsDir(input_profile_loc)) { | 33 | if (!FS::IsDir(input_profile_loc)) { |
| @@ -43,8 +43,8 @@ InputProfiles::InputProfiles(Core::System& system_) : system{system_} { | |||
| 43 | 43 | ||
| 44 | if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { | 44 | if (IsINI(filename) && IsProfileNameValid(name_without_ext)) { |
| 45 | map_profiles.insert_or_assign( | 45 | map_profiles.insert_or_assign( |
| 46 | name_without_ext, std::make_unique<Config>(system, name_without_ext, | 46 | name_without_ext, |
| 47 | Config::ConfigType::InputProfile)); | 47 | std::make_unique<Config>(name_without_ext, Config::ConfigType::InputProfile)); |
| 48 | } | 48 | } |
| 49 | 49 | ||
| 50 | return true; | 50 | return true; |
| @@ -80,8 +80,7 @@ bool InputProfiles::CreateProfile(const std::string& profile_name, std::size_t p | |||
| 80 | } | 80 | } |
| 81 | 81 | ||
| 82 | map_profiles.insert_or_assign( | 82 | map_profiles.insert_or_assign( |
| 83 | profile_name, | 83 | profile_name, std::make_unique<Config>(profile_name, Config::ConfigType::InputProfile)); |
| 84 | std::make_unique<Config>(system, profile_name, Config::ConfigType::InputProfile)); | ||
| 85 | 84 | ||
| 86 | return SaveProfile(profile_name, player_index); | 85 | return SaveProfile(profile_name, player_index); |
| 87 | } | 86 | } |
diff --git a/src/yuzu/configuration/input_profiles.h b/src/yuzu/configuration/input_profiles.h index 65fc9e62c..2bf3e4250 100644 --- a/src/yuzu/configuration/input_profiles.h +++ b/src/yuzu/configuration/input_profiles.h | |||
| @@ -15,7 +15,7 @@ class Config; | |||
| 15 | class InputProfiles { | 15 | class InputProfiles { |
| 16 | 16 | ||
| 17 | public: | 17 | public: |
| 18 | explicit InputProfiles(Core::System& system_); | 18 | explicit InputProfiles(); |
| 19 | virtual ~InputProfiles(); | 19 | virtual ~InputProfiles(); |
| 20 | 20 | ||
| 21 | std::vector<std::string> GetInputProfileNames(); | 21 | std::vector<std::string> GetInputProfileNames(); |
| @@ -31,6 +31,4 @@ private: | |||
| 31 | bool ProfileExistsInMap(const std::string& profile_name) const; | 31 | bool ProfileExistsInMap(const std::string& profile_name) const; |
| 32 | 32 | ||
| 33 | std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; | 33 | std::unordered_map<std::string, std::unique_ptr<Config>> map_profiles; |
| 34 | |||
| 35 | Core::System& system; | ||
| 36 | }; | 34 | }; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index fa9548fed..3c1bd19db 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -138,6 +138,10 @@ static FileSys::VirtualFile VfsDirectoryCreateFileWrapper(const FileSys::Virtual | |||
| 138 | #include "yuzu/uisettings.h" | 138 | #include "yuzu/uisettings.h" |
| 139 | #include "yuzu/util/clickable_label.h" | 139 | #include "yuzu/util/clickable_label.h" |
| 140 | 140 | ||
| 141 | #ifdef YUZU_DBGHELP | ||
| 142 | #include "yuzu/mini_dump.h" | ||
| 143 | #endif | ||
| 144 | |||
| 141 | using namespace Common::Literals; | 145 | using namespace Common::Literals; |
| 142 | 146 | ||
| 143 | #ifdef USE_DISCORD_PRESENCE | 147 | #ifdef USE_DISCORD_PRESENCE |
| @@ -269,10 +273,9 @@ bool GMainWindow::CheckDarkMode() { | |||
| 269 | #endif // __linux__ | 273 | #endif // __linux__ |
| 270 | } | 274 | } |
| 271 | 275 | ||
| 272 | GMainWindow::GMainWindow(bool has_broken_vulkan) | 276 | GMainWindow::GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan) |
| 273 | : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, | 277 | : ui{std::make_unique<Ui::MainWindow>()}, system{std::make_unique<Core::System>()}, |
| 274 | input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, | 278 | input_subsystem{std::make_shared<InputCommon::InputSubsystem>()}, config{std::move(config_)}, |
| 275 | config{std::make_unique<Config>(*system)}, | ||
| 276 | vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, | 279 | vfs{std::make_shared<FileSys::RealVfsFilesystem>()}, |
| 277 | provider{std::make_unique<FileSys::ManualContentProvider>()} { | 280 | provider{std::make_unique<FileSys::ManualContentProvider>()} { |
| 278 | #ifdef __linux__ | 281 | #ifdef __linux__ |
| @@ -1637,7 +1640,8 @@ void GMainWindow::BootGame(const QString& filename, u64 program_id, std::size_t | |||
| 1637 | const auto config_file_name = title_id == 0 | 1640 | const auto config_file_name = title_id == 0 |
| 1638 | ? Common::FS::PathToUTF8String(file_path.filename()) | 1641 | ? Common::FS::PathToUTF8String(file_path.filename()) |
| 1639 | : fmt::format("{:016X}", title_id); | 1642 | : fmt::format("{:016X}", title_id); |
| 1640 | Config per_game_config(*system, config_file_name, Config::ConfigType::PerGameConfig); | 1643 | Config per_game_config(config_file_name, Config::ConfigType::PerGameConfig); |
| 1644 | system->ApplySettings(); | ||
| 1641 | } | 1645 | } |
| 1642 | 1646 | ||
| 1643 | // Save configurations | 1647 | // Save configurations |
| @@ -2981,7 +2985,7 @@ void GMainWindow::OnConfigure() { | |||
| 2981 | 2985 | ||
| 2982 | Settings::values.disabled_addons.clear(); | 2986 | Settings::values.disabled_addons.clear(); |
| 2983 | 2987 | ||
| 2984 | config = std::make_unique<Config>(*system); | 2988 | config = std::make_unique<Config>(); |
| 2985 | UISettings::values.reset_to_defaults = false; | 2989 | UISettings::values.reset_to_defaults = false; |
| 2986 | 2990 | ||
| 2987 | UISettings::values.game_dirs = std::move(old_game_dirs); | 2991 | UISettings::values.game_dirs = std::move(old_game_dirs); |
| @@ -3042,6 +3046,7 @@ void GMainWindow::OnConfigure() { | |||
| 3042 | 3046 | ||
| 3043 | UpdateStatusButtons(); | 3047 | UpdateStatusButtons(); |
| 3044 | controller_dialog->refreshConfiguration(); | 3048 | controller_dialog->refreshConfiguration(); |
| 3049 | system->ApplySettings(); | ||
| 3045 | } | 3050 | } |
| 3046 | 3051 | ||
| 3047 | void GMainWindow::OnConfigureTas() { | 3052 | void GMainWindow::OnConfigureTas() { |
| @@ -4063,7 +4068,24 @@ void GMainWindow::changeEvent(QEvent* event) { | |||
| 4063 | #endif | 4068 | #endif |
| 4064 | 4069 | ||
| 4065 | int main(int argc, char* argv[]) { | 4070 | int main(int argc, char* argv[]) { |
| 4071 | std::unique_ptr<Config> config = std::make_unique<Config>(); | ||
| 4066 | bool has_broken_vulkan = false; | 4072 | bool has_broken_vulkan = false; |
| 4073 | bool is_child = false; | ||
| 4074 | if (CheckEnvVars(&is_child)) { | ||
| 4075 | return 0; | ||
| 4076 | } | ||
| 4077 | |||
| 4078 | #ifdef YUZU_DBGHELP | ||
| 4079 | PROCESS_INFORMATION pi; | ||
| 4080 | if (!is_child && Settings::values.create_crash_dumps.GetValue() && | ||
| 4081 | MiniDump::SpawnDebuggee(argv[0], pi)) { | ||
| 4082 | // Delete the config object so that it doesn't save when the program exits | ||
| 4083 | config.reset(nullptr); | ||
| 4084 | MiniDump::DebugDebuggee(pi); | ||
| 4085 | return 0; | ||
| 4086 | } | ||
| 4087 | #endif | ||
| 4088 | |||
| 4067 | if (StartupChecks(argv[0], &has_broken_vulkan)) { | 4089 | if (StartupChecks(argv[0], &has_broken_vulkan)) { |
| 4068 | return 0; | 4090 | return 0; |
| 4069 | } | 4091 | } |
| @@ -4116,7 +4138,7 @@ int main(int argc, char* argv[]) { | |||
| 4116 | // generating shaders | 4138 | // generating shaders |
| 4117 | setlocale(LC_ALL, "C"); | 4139 | setlocale(LC_ALL, "C"); |
| 4118 | 4140 | ||
| 4119 | GMainWindow main_window{has_broken_vulkan}; | 4141 | GMainWindow main_window{std::move(config), has_broken_vulkan}; |
| 4120 | // After settings have been loaded by GMainWindow, apply the filter | 4142 | // After settings have been loaded by GMainWindow, apply the filter |
| 4121 | main_window.show(); | 4143 | main_window.show(); |
| 4122 | 4144 | ||
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1ae2b93d9..716aef063 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -120,7 +120,7 @@ class GMainWindow : public QMainWindow { | |||
| 120 | public: | 120 | public: |
| 121 | void filterBarSetChecked(bool state); | 121 | void filterBarSetChecked(bool state); |
| 122 | void UpdateUITheme(); | 122 | void UpdateUITheme(); |
| 123 | explicit GMainWindow(bool has_broken_vulkan); | 123 | explicit GMainWindow(std::unique_ptr<Config> config_, bool has_broken_vulkan); |
| 124 | ~GMainWindow() override; | 124 | ~GMainWindow() override; |
| 125 | 125 | ||
| 126 | bool DropAction(QDropEvent* event); | 126 | bool DropAction(QDropEvent* event); |
diff --git a/src/yuzu/mini_dump.cpp b/src/yuzu/mini_dump.cpp new file mode 100644 index 000000000..a34dc6a9c --- /dev/null +++ b/src/yuzu/mini_dump.cpp | |||
| @@ -0,0 +1,202 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #include <cstdio> | ||
| 5 | #include <cstring> | ||
| 6 | #include <ctime> | ||
| 7 | #include <filesystem> | ||
| 8 | #include <fmt/format.h> | ||
| 9 | #include <windows.h> | ||
| 10 | #include "yuzu/mini_dump.h" | ||
| 11 | #include "yuzu/startup_checks.h" | ||
| 12 | |||
| 13 | // dbghelp.h must be included after windows.h | ||
| 14 | #include <dbghelp.h> | ||
| 15 | |||
| 16 | namespace MiniDump { | ||
| 17 | |||
| 18 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | ||
| 19 | EXCEPTION_POINTERS* pep) { | ||
| 20 | char file_name[255]; | ||
| 21 | const std::time_t the_time = std::time(nullptr); | ||
| 22 | std::strftime(file_name, 255, "yuzu-crash-%Y%m%d%H%M%S.dmp", std::localtime(&the_time)); | ||
| 23 | |||
| 24 | // Open the file | ||
| 25 | HANDLE file_handle = CreateFileA(file_name, GENERIC_READ | GENERIC_WRITE, 0, nullptr, | ||
| 26 | CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, nullptr); | ||
| 27 | |||
| 28 | if (file_handle == nullptr || file_handle == INVALID_HANDLE_VALUE) { | ||
| 29 | fmt::print(stderr, "CreateFileA failed. Error: {}", GetLastError()); | ||
| 30 | return; | ||
| 31 | } | ||
| 32 | |||
| 33 | // Create the minidump | ||
| 34 | const MINIDUMP_TYPE dump_type = MiniDumpNormal; | ||
| 35 | |||
| 36 | const bool write_dump_status = MiniDumpWriteDump(process_handle, process_id, file_handle, | ||
| 37 | dump_type, (pep != 0) ? info : 0, 0, 0); | ||
| 38 | |||
| 39 | if (write_dump_status) { | ||
| 40 | fmt::print(stderr, "MiniDump created: {}", file_name); | ||
| 41 | } else { | ||
| 42 | fmt::print(stderr, "MiniDumpWriteDump failed. Error: {}", GetLastError()); | ||
| 43 | } | ||
| 44 | |||
| 45 | // Close the file | ||
| 46 | CloseHandle(file_handle); | ||
| 47 | } | ||
| 48 | |||
| 49 | void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi) { | ||
| 50 | EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; | ||
| 51 | |||
| 52 | HANDLE thread_handle = OpenThread(THREAD_GET_CONTEXT, false, deb_ev.dwThreadId); | ||
| 53 | if (thread_handle == nullptr) { | ||
| 54 | fmt::print(stderr, "OpenThread failed ({})", GetLastError()); | ||
| 55 | return; | ||
| 56 | } | ||
| 57 | |||
| 58 | // Get child process context | ||
| 59 | CONTEXT context = {}; | ||
| 60 | context.ContextFlags = CONTEXT_ALL; | ||
| 61 | if (!GetThreadContext(thread_handle, &context)) { | ||
| 62 | fmt::print(stderr, "GetThreadContext failed ({})", GetLastError()); | ||
| 63 | return; | ||
| 64 | } | ||
| 65 | |||
| 66 | // Create exception pointers for minidump | ||
| 67 | EXCEPTION_POINTERS ep; | ||
| 68 | ep.ExceptionRecord = &record; | ||
| 69 | ep.ContextRecord = &context; | ||
| 70 | |||
| 71 | MINIDUMP_EXCEPTION_INFORMATION info; | ||
| 72 | info.ThreadId = deb_ev.dwThreadId; | ||
| 73 | info.ExceptionPointers = &ep; | ||
| 74 | info.ClientPointers = false; | ||
| 75 | |||
| 76 | CreateMiniDump(pi.hProcess, pi.dwProcessId, &info, &ep); | ||
| 77 | |||
| 78 | if (CloseHandle(thread_handle) == 0) { | ||
| 79 | fmt::print(stderr, "error: CloseHandle(thread_handle) failed ({})", GetLastError()); | ||
| 80 | } | ||
| 81 | } | ||
| 82 | |||
| 83 | bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi) { | ||
| 84 | std::memset(&pi, 0, sizeof(pi)); | ||
| 85 | |||
| 86 | // Don't debug if we are already being debugged | ||
| 87 | if (IsDebuggerPresent()) { | ||
| 88 | return false; | ||
| 89 | } | ||
| 90 | |||
| 91 | if (!SpawnChild(arg0, &pi, 0)) { | ||
| 92 | fmt::print(stderr, "warning: continuing without crash dumps"); | ||
| 93 | return false; | ||
| 94 | } | ||
| 95 | |||
| 96 | const bool can_debug = DebugActiveProcess(pi.dwProcessId); | ||
| 97 | if (!can_debug) { | ||
| 98 | fmt::print(stderr, | ||
| 99 | "warning: DebugActiveProcess failed ({}), continuing without crash dumps", | ||
| 100 | GetLastError()); | ||
| 101 | return false; | ||
| 102 | } | ||
| 103 | |||
| 104 | return true; | ||
| 105 | } | ||
| 106 | |||
| 107 | static const char* ExceptionName(DWORD exception) { | ||
| 108 | switch (exception) { | ||
| 109 | case EXCEPTION_ACCESS_VIOLATION: | ||
| 110 | return "EXCEPTION_ACCESS_VIOLATION"; | ||
| 111 | case EXCEPTION_DATATYPE_MISALIGNMENT: | ||
| 112 | return "EXCEPTION_DATATYPE_MISALIGNMENT"; | ||
| 113 | case EXCEPTION_BREAKPOINT: | ||
| 114 | return "EXCEPTION_BREAKPOINT"; | ||
| 115 | case EXCEPTION_SINGLE_STEP: | ||
| 116 | return "EXCEPTION_SINGLE_STEP"; | ||
| 117 | case EXCEPTION_ARRAY_BOUNDS_EXCEEDED: | ||
| 118 | return "EXCEPTION_ARRAY_BOUNDS_EXCEEDED"; | ||
| 119 | case EXCEPTION_FLT_DENORMAL_OPERAND: | ||
| 120 | return "EXCEPTION_FLT_DENORMAL_OPERAND"; | ||
| 121 | case EXCEPTION_FLT_DIVIDE_BY_ZERO: | ||
| 122 | return "EXCEPTION_FLT_DIVIDE_BY_ZERO"; | ||
| 123 | case EXCEPTION_FLT_INEXACT_RESULT: | ||
| 124 | return "EXCEPTION_FLT_INEXACT_RESULT"; | ||
| 125 | case EXCEPTION_FLT_INVALID_OPERATION: | ||
| 126 | return "EXCEPTION_FLT_INVALID_OPERATION"; | ||
| 127 | case EXCEPTION_FLT_OVERFLOW: | ||
| 128 | return "EXCEPTION_FLT_OVERFLOW"; | ||
| 129 | case EXCEPTION_FLT_STACK_CHECK: | ||
| 130 | return "EXCEPTION_FLT_STACK_CHECK"; | ||
| 131 | case EXCEPTION_FLT_UNDERFLOW: | ||
| 132 | return "EXCEPTION_FLT_UNDERFLOW"; | ||
| 133 | case EXCEPTION_INT_DIVIDE_BY_ZERO: | ||
| 134 | return "EXCEPTION_INT_DIVIDE_BY_ZERO"; | ||
| 135 | case EXCEPTION_INT_OVERFLOW: | ||
| 136 | return "EXCEPTION_INT_OVERFLOW"; | ||
| 137 | case EXCEPTION_PRIV_INSTRUCTION: | ||
| 138 | return "EXCEPTION_PRIV_INSTRUCTION"; | ||
| 139 | case EXCEPTION_IN_PAGE_ERROR: | ||
| 140 | return "EXCEPTION_IN_PAGE_ERROR"; | ||
| 141 | case EXCEPTION_ILLEGAL_INSTRUCTION: | ||
| 142 | return "EXCEPTION_ILLEGAL_INSTRUCTION"; | ||
| 143 | case EXCEPTION_NONCONTINUABLE_EXCEPTION: | ||
| 144 | return "EXCEPTION_NONCONTINUABLE_EXCEPTION"; | ||
| 145 | case EXCEPTION_STACK_OVERFLOW: | ||
| 146 | return "EXCEPTION_STACK_OVERFLOW"; | ||
| 147 | case EXCEPTION_INVALID_DISPOSITION: | ||
| 148 | return "EXCEPTION_INVALID_DISPOSITION"; | ||
| 149 | case EXCEPTION_GUARD_PAGE: | ||
| 150 | return "EXCEPTION_GUARD_PAGE"; | ||
| 151 | case EXCEPTION_INVALID_HANDLE: | ||
| 152 | return "EXCEPTION_INVALID_HANDLE"; | ||
| 153 | default: | ||
| 154 | return "unknown exception type"; | ||
| 155 | } | ||
| 156 | } | ||
| 157 | |||
| 158 | void DebugDebuggee(PROCESS_INFORMATION& pi) { | ||
| 159 | DEBUG_EVENT deb_ev = {}; | ||
| 160 | |||
| 161 | while (deb_ev.dwDebugEventCode != EXIT_PROCESS_DEBUG_EVENT) { | ||
| 162 | const bool wait_success = WaitForDebugEvent(&deb_ev, INFINITE); | ||
| 163 | if (!wait_success) { | ||
| 164 | fmt::print(stderr, "error: WaitForDebugEvent failed ({})", GetLastError()); | ||
| 165 | return; | ||
| 166 | } | ||
| 167 | |||
| 168 | switch (deb_ev.dwDebugEventCode) { | ||
| 169 | case OUTPUT_DEBUG_STRING_EVENT: | ||
| 170 | case CREATE_PROCESS_DEBUG_EVENT: | ||
| 171 | case CREATE_THREAD_DEBUG_EVENT: | ||
| 172 | case EXIT_PROCESS_DEBUG_EVENT: | ||
| 173 | case EXIT_THREAD_DEBUG_EVENT: | ||
| 174 | case LOAD_DLL_DEBUG_EVENT: | ||
| 175 | case RIP_EVENT: | ||
| 176 | case UNLOAD_DLL_DEBUG_EVENT: | ||
| 177 | // Continue on all other debug events | ||
| 178 | ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_CONTINUE); | ||
| 179 | break; | ||
| 180 | case EXCEPTION_DEBUG_EVENT: | ||
| 181 | EXCEPTION_RECORD& record = deb_ev.u.Exception.ExceptionRecord; | ||
| 182 | |||
| 183 | // We want to generate a crash dump if we are seeing the same exception again. | ||
| 184 | if (!deb_ev.u.Exception.dwFirstChance) { | ||
| 185 | fmt::print(stderr, "Creating MiniDump on ExceptionCode: 0x{:08x} {}\n", | ||
| 186 | record.ExceptionCode, ExceptionName(record.ExceptionCode)); | ||
| 187 | DumpFromDebugEvent(deb_ev, pi); | ||
| 188 | } | ||
| 189 | |||
| 190 | // Continue without handling the exception. | ||
| 191 | // Lets the debuggee use its own exception handler. | ||
| 192 | // - If one does not exist, we will see the exception once more where we make a minidump | ||
| 193 | // for. Then when it reaches here again, yuzu will probably crash. | ||
| 194 | // - DBG_CONTINUE on an exception that the debuggee does not handle can set us up for an | ||
| 195 | // infinite loop of exceptions. | ||
| 196 | ContinueDebugEvent(deb_ev.dwProcessId, deb_ev.dwThreadId, DBG_EXCEPTION_NOT_HANDLED); | ||
| 197 | break; | ||
| 198 | } | ||
| 199 | } | ||
| 200 | } | ||
| 201 | |||
| 202 | } // namespace MiniDump | ||
diff --git a/src/yuzu/mini_dump.h b/src/yuzu/mini_dump.h new file mode 100644 index 000000000..d6b6cca84 --- /dev/null +++ b/src/yuzu/mini_dump.h | |||
| @@ -0,0 +1,19 @@ | |||
| 1 | // SPDX-FileCopyrightText: 2022 yuzu Emulator Project | ||
| 2 | // SPDX-License-Identifier: GPL-2.0-or-later | ||
| 3 | |||
| 4 | #pragma once | ||
| 5 | |||
| 6 | #include <windows.h> | ||
| 7 | |||
| 8 | #include <dbghelp.h> | ||
| 9 | |||
| 10 | namespace MiniDump { | ||
| 11 | |||
| 12 | void CreateMiniDump(HANDLE process_handle, DWORD process_id, MINIDUMP_EXCEPTION_INFORMATION* info, | ||
| 13 | EXCEPTION_POINTERS* pep); | ||
| 14 | |||
| 15 | void DumpFromDebugEvent(DEBUG_EVENT& deb_ev, PROCESS_INFORMATION& pi); | ||
| 16 | bool SpawnDebuggee(const char* arg0, PROCESS_INFORMATION& pi); | ||
| 17 | void DebugDebuggee(PROCESS_INFORMATION& pi); | ||
| 18 | |||
| 19 | } // namespace MiniDump | ||
diff --git a/src/yuzu/startup_checks.cpp b/src/yuzu/startup_checks.cpp index 8421280bf..29b87da05 100644 --- a/src/yuzu/startup_checks.cpp +++ b/src/yuzu/startup_checks.cpp | |||
| @@ -31,19 +31,36 @@ void CheckVulkan() { | |||
| 31 | } | 31 | } |
| 32 | } | 32 | } |
| 33 | 33 | ||
| 34 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | 34 | bool CheckEnvVars(bool* is_child) { |
| 35 | #ifdef _WIN32 | 35 | #ifdef _WIN32 |
| 36 | // Check environment variable to see if we are the child | 36 | // Check environment variable to see if we are the child |
| 37 | char variable_contents[8]; | 37 | char variable_contents[8]; |
| 38 | const DWORD startup_check_var = | 38 | const DWORD startup_check_var = |
| 39 | GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); | 39 | GetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, variable_contents, 8); |
| 40 | if (startup_check_var > 0 && std::strncmp(variable_contents, "ON", 8) == 0) { | 40 | if (startup_check_var > 0 && std::strncmp(variable_contents, ENV_VAR_ENABLED_TEXT, 8) == 0) { |
| 41 | CheckVulkan(); | 41 | CheckVulkan(); |
| 42 | return true; | 42 | return true; |
| 43 | } | 43 | } |
| 44 | 44 | ||
| 45 | // Don't perform startup checks if we are a child process | ||
| 46 | char is_child_s[8]; | ||
| 47 | const DWORD is_child_len = GetEnvironmentVariableA(IS_CHILD_ENV_VAR, is_child_s, 8); | ||
| 48 | if (is_child_len > 0 && std::strncmp(is_child_s, ENV_VAR_ENABLED_TEXT, 8) == 0) { | ||
| 49 | *is_child = true; | ||
| 50 | return false; | ||
| 51 | } else if (!SetEnvironmentVariableA(IS_CHILD_ENV_VAR, ENV_VAR_ENABLED_TEXT)) { | ||
| 52 | std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", | ||
| 53 | IS_CHILD_ENV_VAR, GetLastError()); | ||
| 54 | return true; | ||
| 55 | } | ||
| 56 | #endif | ||
| 57 | return false; | ||
| 58 | } | ||
| 59 | |||
| 60 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | ||
| 61 | #ifdef _WIN32 | ||
| 45 | // Set the startup variable for child processes | 62 | // Set the startup variable for child processes |
| 46 | const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, "ON"); | 63 | const bool env_var_set = SetEnvironmentVariableA(STARTUP_CHECK_ENV_VAR, ENV_VAR_ENABLED_TEXT); |
| 47 | if (!env_var_set) { | 64 | if (!env_var_set) { |
| 48 | std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", | 65 | std::fprintf(stderr, "SetEnvironmentVariableA failed to set %s with error %d\n", |
| 49 | STARTUP_CHECK_ENV_VAR, GetLastError()); | 66 | STARTUP_CHECK_ENV_VAR, GetLastError()); |
| @@ -53,7 +70,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | |||
| 53 | PROCESS_INFORMATION process_info; | 70 | PROCESS_INFORMATION process_info; |
| 54 | std::memset(&process_info, '\0', sizeof(process_info)); | 71 | std::memset(&process_info, '\0', sizeof(process_info)); |
| 55 | 72 | ||
| 56 | if (!SpawnChild(arg0, &process_info)) { | 73 | if (!SpawnChild(arg0, &process_info, 0)) { |
| 57 | return false; | 74 | return false; |
| 58 | } | 75 | } |
| 59 | 76 | ||
| @@ -106,7 +123,7 @@ bool StartupChecks(const char* arg0, bool* has_broken_vulkan) { | |||
| 106 | } | 123 | } |
| 107 | 124 | ||
| 108 | #ifdef _WIN32 | 125 | #ifdef _WIN32 |
| 109 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { | 126 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags) { |
| 110 | STARTUPINFOA startup_info; | 127 | STARTUPINFOA startup_info; |
| 111 | 128 | ||
| 112 | std::memset(&startup_info, '\0', sizeof(startup_info)); | 129 | std::memset(&startup_info, '\0', sizeof(startup_info)); |
| @@ -120,7 +137,7 @@ bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi) { | |||
| 120 | nullptr, // lpProcessAttributes | 137 | nullptr, // lpProcessAttributes |
| 121 | nullptr, // lpThreadAttributes | 138 | nullptr, // lpThreadAttributes |
| 122 | false, // bInheritHandles | 139 | false, // bInheritHandles |
| 123 | 0, // dwCreationFlags | 140 | flags, // dwCreationFlags |
| 124 | nullptr, // lpEnvironment | 141 | nullptr, // lpEnvironment |
| 125 | nullptr, // lpCurrentDirectory | 142 | nullptr, // lpCurrentDirectory |
| 126 | &startup_info, // lpStartupInfo | 143 | &startup_info, // lpStartupInfo |
diff --git a/src/yuzu/startup_checks.h b/src/yuzu/startup_checks.h index 096dd54a8..f2fc2d9d4 100644 --- a/src/yuzu/startup_checks.h +++ b/src/yuzu/startup_checks.h | |||
| @@ -7,11 +7,14 @@ | |||
| 7 | #include <windows.h> | 7 | #include <windows.h> |
| 8 | #endif | 8 | #endif |
| 9 | 9 | ||
| 10 | constexpr char IS_CHILD_ENV_VAR[] = "YUZU_IS_CHILD"; | ||
| 10 | constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; | 11 | constexpr char STARTUP_CHECK_ENV_VAR[] = "YUZU_DO_STARTUP_CHECKS"; |
| 12 | constexpr char ENV_VAR_ENABLED_TEXT[] = "ON"; | ||
| 11 | 13 | ||
| 12 | void CheckVulkan(); | 14 | void CheckVulkan(); |
| 15 | bool CheckEnvVars(bool* is_child); | ||
| 13 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan); | 16 | bool StartupChecks(const char* arg0, bool* has_broken_vulkan); |
| 14 | 17 | ||
| 15 | #ifdef _WIN32 | 18 | #ifdef _WIN32 |
| 16 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi); | 19 | bool SpawnChild(const char* arg0, PROCESS_INFORMATION* pi, int flags); |
| 17 | #endif | 20 | #endif |
diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index bd0fb75f8..66dd0dc15 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp | |||
| @@ -314,6 +314,7 @@ void Config::ReadValues() { | |||
| 314 | ReadSetting("Renderer", Settings::values.nvdec_emulation); | 314 | ReadSetting("Renderer", Settings::values.nvdec_emulation); |
| 315 | ReadSetting("Renderer", Settings::values.accelerate_astc); | 315 | ReadSetting("Renderer", Settings::values.accelerate_astc); |
| 316 | ReadSetting("Renderer", Settings::values.use_fast_gpu_time); | 316 | ReadSetting("Renderer", Settings::values.use_fast_gpu_time); |
| 317 | ReadSetting("Renderer", Settings::values.use_pessimistic_flushes); | ||
| 317 | 318 | ||
| 318 | ReadSetting("Renderer", Settings::values.bg_red); | 319 | ReadSetting("Renderer", Settings::values.bg_red); |
| 319 | ReadSetting("Renderer", Settings::values.bg_green); | 320 | ReadSetting("Renderer", Settings::values.bg_green); |
diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 1168cf136..d214771b0 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h | |||
| @@ -319,6 +319,10 @@ use_asynchronous_gpu_emulation = | |||
| 319 | # 0: Off, 1 (default): On | 319 | # 0: Off, 1 (default): On |
| 320 | use_fast_gpu_time = | 320 | use_fast_gpu_time = |
| 321 | 321 | ||
| 322 | # Force unmodified buffers to be flushed, which can cost performance. | ||
| 323 | # 0: Off (default), 1: On | ||
| 324 | use_pessimistic_flushes = | ||
| 325 | |||
| 322 | # Whether to use garbage collection or not for GPU caches. | 326 | # Whether to use garbage collection or not for GPU caches. |
| 323 | # 0 (default): Off, 1: On | 327 | # 0 (default): Off, 1: On |
| 324 | use_caches_gc = | 328 | use_caches_gc = |
diff --git a/vcpkg.json b/vcpkg.json index c4413e22a..3c92510d6 100644 --- a/vcpkg.json +++ b/vcpkg.json | |||
| @@ -31,6 +31,10 @@ | |||
| 31 | "yuzu-tests": { | 31 | "yuzu-tests": { |
| 32 | "description": "Compile tests", | 32 | "description": "Compile tests", |
| 33 | "dependencies": [ "catch2" ] | 33 | "dependencies": [ "catch2" ] |
| 34 | }, | ||
| 35 | "dbghelp": { | ||
| 36 | "description": "Compile Windows crash dump (Minidump) support", | ||
| 37 | "dependencies": [ "dbghelp" ] | ||
| 34 | } | 38 | } |
| 35 | }, | 39 | }, |
| 36 | "overrides": [ | 40 | "overrides": [ |