diff options
85 files changed, 887 insertions, 1159 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/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/.reuse/dep5 b/.reuse/dep5 index fe4fa2f07..5ba017494 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 |
| @@ -66,9 +67,7 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png | |||
| 66 | Copyright: Ionic (http://ionic.io/) | 67 | Copyright: Ionic (http://ionic.io/) |
| 67 | License: MIT | 68 | License: MIT |
| 68 | 69 | ||
| 69 | 70 | Files: dist/qt_themes/colorful/icons/48x48/star.png | |
| 70 | Files: dist/qt_themes/*/icons/48x48/sd_card.png | ||
| 71 | dist/qt_themes/colorful/icons/48x48/star.png | ||
| 72 | dist/qt_themes/default/icons/16x16/checked.png | 71 | dist/qt_themes/default/icons/16x16/checked.png |
| 73 | dist/qt_themes/default/icons/16x16/failed.png | 72 | dist/qt_themes/default/icons/16x16/failed.png |
| 74 | Copyright: SVG Repo | 73 | Copyright: SVG Repo |
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/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/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/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.h b/src/audio_core/audio_render_manager.h index 6a508ec56..7119e1b99 100644 --- a/src/audio_core/audio_render_manager.h +++ b/src/audio_core/audio_render_manager.h | |||
| @@ -64,10 +64,10 @@ public: | |||
| 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 | ||
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..3ecbbb63f 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h | |||
| @@ -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,7 +88,9 @@ 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(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { |
| @@ -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..c71c3a376 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,20 +59,21 @@ 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<AudioBuffer> buffers) const { |
| 65 | auto& memory{system.Memory()}; | ||
| 66 | |||
| 67 | for (size_t i = 0; i < buffers.size(); i++) { | 77 | 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 = buffers[i].size / (channel_count * sizeof(s16)), |
| @@ -77,7 +87,7 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | |||
| 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(buffers[i].size / sizeof(s16)); |
| 80 | memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); | 90 | system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); |
| 81 | stream->AppendBuffer(new_buffer, samples); | 91 | stream->AppendBuffer(new_buffer, samples); |
| 82 | } | 92 | } |
| 83 | } | 93 | } |
| @@ -85,17 +95,13 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { | |||
| 85 | 95 | ||
| 86 | void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { | 96 | void DeviceSession::ReleaseBuffer(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(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..53b649c61 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; |
| @@ -67,10 +74,11 @@ public: | |||
| 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(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_system.cpp b/src/audio_core/in/audio_in_system.cpp index ec5d37ed4..7e80ba03c 100644 --- a/src/audio_core/in/audio_in_system.cpp +++ b/src/audio_core/in/audio_in_system.cpp | |||
| @@ -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,13 @@ 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 | AudioBuffer new_buffer{.start_timestamp = timestamp, |
| 118 | .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), | ||
| 119 | .played_timestamp = 0, | ||
| 120 | .samples = buffer.samples, | ||
| 121 | .tag = tag, | ||
| 122 | .size = buffer.size}; | ||
| 117 | 123 | ||
| 118 | buffers.AppendBuffer(new_buffer); | 124 | buffers.AppendBuffer(new_buffer); |
| 119 | RegisterBuffers(); | 125 | RegisterBuffers(); |
diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h index 165e35d83..9ddc8daae 100644 --- a/src/audio_core/in/audio_in_system.h +++ b/src/audio_core/in/audio_in_system.h | |||
| @@ -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 | ||
diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index 35afddf06..8941b09a0 100644 --- a/src/audio_core/out/audio_out_system.cpp +++ b/src/audio_core/out/audio_out_system.cpp | |||
| @@ -92,6 +92,7 @@ Result System::Start() { | |||
| 92 | std::vector<AudioBuffer> buffers_to_flush{}; | 92 | std::vector<AudioBuffer> buffers_to_flush{}; |
| 93 | buffers.RegisterBuffers(buffers_to_flush); | 93 | buffers.RegisterBuffers(buffers_to_flush); |
| 94 | session->AppendBuffers(buffers_to_flush); | 94 | session->AppendBuffers(buffers_to_flush); |
| 95 | session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); | ||
| 95 | 96 | ||
| 96 | return ResultSuccess; | 97 | return ResultSuccess; |
| 97 | } | 98 | } |
| @@ -111,8 +112,13 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { | |||
| 111 | return false; | 112 | return false; |
| 112 | } | 113 | } |
| 113 | 114 | ||
| 114 | AudioBuffer new_buffer{ | 115 | const auto timestamp{buffers.GetNextTimestamp()}; |
| 115 | .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; | 116 | AudioBuffer new_buffer{.start_timestamp = timestamp, |
| 117 | .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), | ||
| 118 | .played_timestamp = 0, | ||
| 119 | .samples = buffer.samples, | ||
| 120 | .tag = tag, | ||
| 121 | .size = buffer.size}; | ||
| 116 | 122 | ||
| 117 | buffers.AppendBuffer(new_buffer); | 123 | buffers.AppendBuffer(new_buffer); |
| 118 | RegisterBuffers(); | 124 | RegisterBuffers(); |
diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h index 4ca2f3417..205ead861 100644 --- a/src/audio_core/out/audio_out_system.h +++ b/src/audio_core/out/audio_out_system.h | |||
| @@ -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 | ||
diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h index 4dfcef4a5..523184dc2 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 | ||
diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index 3967ccfe6..bcd889ecb 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.cpp +++ b/src/audio_core/renderer/adsp/audio_renderer.cpp | |||
| @@ -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..49f66f21c 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 | ||
| @@ -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..92140aaea 100644 --- a/src/audio_core/renderer/behavior/behavior_info.cpp +++ b/src/audio_core/renderer/behavior/behavior_info.cpp | |||
| @@ -43,13 +43,15 @@ void BehaviorInfo::AppendError(ErrorInfo& error) { | |||
| 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) { |
| 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)); | 47 | |
| 48 | 48 | for (size_t i = 0; i < MaxErrors; i++) { | |
| 49 | for (size_t i = 0; i < error_count_; i++) { | 49 | if (i < out_count) { |
| 50 | out_errors[i] = errors[i]; | 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/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/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..c0fced56f 100644 --- a/src/audio_core/renderer/nodes/node_states.h +++ b/src/audio_core/renderer/nodes/node_states.h | |||
| @@ -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..9366ebbd3 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. |
| @@ -200,23 +155,93 @@ public: | |||
| 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..b1e0ba6cc 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt | |||
| @@ -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 8354fdba7..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"}; |
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..5375a5d59 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 { |
| @@ -219,11 +210,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { | |||
| 219 | } | 210 | } |
| 220 | } | 211 | } |
| 221 | 212 | ||
| 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() { | 213 | std::optional<s64> CoreTiming::Advance() { |
| 228 | std::scoped_lock lock{advance_lock, basic_lock}; | 214 | std::scoped_lock lock{advance_lock, basic_lock}; |
| 229 | global_timer = GetGlobalTimeNs().count(); | 215 | global_timer = GetGlobalTimeNs().count(); |
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/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/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index de388ec4c..5cc1ccbd9 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp | |||
| @@ -40,13 +40,13 @@ public: | |||
| 40 | void EnableMotion() { | 40 | void EnableMotion() { |
| 41 | if (sdl_controller) { | 41 | if (sdl_controller) { |
| 42 | SDL_GameController* controller = sdl_controller.get(); | 42 | SDL_GameController* controller = sdl_controller.get(); |
| 43 | if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { | 43 | has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL); |
| 44 | has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO); | ||
| 45 | if (has_accel) { | ||
| 44 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); | 46 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); |
| 45 | has_accel = true; | ||
| 46 | } | 47 | } |
| 47 | if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { | 48 | if (has_gyro) { |
| 48 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); | 49 | SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); |
| 49 | has_gyro = true; | ||
| 50 | } | 50 | } |
| 51 | } | 51 | } |
| 52 | } | 52 | } |
| @@ -305,6 +305,7 @@ void SDLDriver::InitJoystick(int joystick_index) { | |||
| 305 | auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); | 305 | auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); |
| 306 | PreSetController(joystick->GetPadIdentifier()); | 306 | PreSetController(joystick->GetPadIdentifier()); |
| 307 | SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); | 307 | SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); |
| 308 | joystick->EnableMotion(); | ||
| 308 | joystick_map[guid].emplace_back(std::move(joystick)); | 309 | joystick_map[guid].emplace_back(std::move(joystick)); |
| 309 | return; | 310 | return; |
| 310 | } | 311 | } |
| @@ -316,6 +317,7 @@ void SDLDriver::InitJoystick(int joystick_index) { | |||
| 316 | 317 | ||
| 317 | if (joystick_it != joystick_guid_list.end()) { | 318 | if (joystick_it != joystick_guid_list.end()) { |
| 318 | (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); | 319 | (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); |
| 320 | (*joystick_it)->EnableMotion(); | ||
| 319 | return; | 321 | return; |
| 320 | } | 322 | } |
| 321 | 323 | ||
| @@ -323,6 +325,7 @@ void SDLDriver::InitJoystick(int joystick_index) { | |||
| 323 | auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); | 325 | auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); |
| 324 | PreSetController(joystick->GetPadIdentifier()); | 326 | PreSetController(joystick->GetPadIdentifier()); |
| 325 | SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); | 327 | SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); |
| 328 | joystick->EnableMotion(); | ||
| 326 | joystick_guid_list.emplace_back(std::move(joystick)); | 329 | joystick_guid_list.emplace_back(std::move(joystick)); |
| 327 | } | 330 | } |
| 328 | 331 | ||
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/shader_recompiler/ir_opt/texture_pass.cpp b/src/shader_recompiler/ir_opt/texture_pass.cpp index 5cead5135..597112ba4 100644 --- a/src/shader_recompiler/ir_opt/texture_pass.cpp +++ b/src/shader_recompiler/ir_opt/texture_pass.cpp | |||
| @@ -415,11 +415,11 @@ void TexturePass(Environment& env, IR::Program& program) { | |||
| 415 | inst->SetFlags(flags); | 415 | inst->SetFlags(flags); |
| 416 | break; | 416 | break; |
| 417 | case IR::Opcode::ImageSampleImplicitLod: | 417 | case IR::Opcode::ImageSampleImplicitLod: |
| 418 | if (flags.type == TextureType::Color2D) { | 418 | if (flags.type != TextureType::Color2D) { |
| 419 | auto texture_type = ReadTextureType(env, cbuf); | 419 | break; |
| 420 | if (texture_type == TextureType::Color2DRect) { | 420 | } |
| 421 | PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); | 421 | if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) { |
| 422 | } | 422 | PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); |
| 423 | } | 423 | } |
| 424 | break; | 424 | break; |
| 425 | case IR::Opcode::ImageFetch: | 425 | case IR::Opcode::ImageFetch: |
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/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 1ad56d9e7..ddb70934c 100644 --- a/src/video_core/renderer_opengl/gl_shader_cache.cpp +++ b/src/video_core/renderer_opengl/gl_shader_cache.cpp | |||
| @@ -49,7 +49,7 @@ using VideoCommon::LoadPipelines; | |||
| 49 | using VideoCommon::SerializePipeline; | 49 | using VideoCommon::SerializePipeline; |
| 50 | using Context = ShaderContext::Context; | 50 | using Context = ShaderContext::Context; |
| 51 | 51 | ||
| 52 | constexpr u32 CACHE_VERSION = 5; | 52 | constexpr u32 CACHE_VERSION = 6; |
| 53 | 53 | ||
| 54 | template <typename Container> | 54 | template <typename Container> |
| 55 | auto MakeSpan(Container& container) { | 55 | auto MakeSpan(Container& container) { |
diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 3adad5af4..9708dc45e 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp | |||
| @@ -53,7 +53,7 @@ using VideoCommon::FileEnvironment; | |||
| 53 | using VideoCommon::GenericEnvironment; | 53 | using VideoCommon::GenericEnvironment; |
| 54 | using VideoCommon::GraphicsEnvironment; | 54 | using VideoCommon::GraphicsEnvironment; |
| 55 | 55 | ||
| 56 | constexpr u32 CACHE_VERSION = 5; | 56 | constexpr u32 CACHE_VERSION = 6; |
| 57 | 57 | ||
| 58 | template <typename Container> | 58 | template <typename Container> |
| 59 | auto MakeSpan(Container& container) { | 59 | auto MakeSpan(Container& container) { |
diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 808d88eec..5f7625947 100644 --- a/src/video_core/shader_environment.cpp +++ b/src/video_core/shader_environment.cpp | |||
| @@ -39,11 +39,8 @@ static Shader::TextureType ConvertType(const Tegra::Texture::TICEntry& entry) { | |||
| 39 | return Shader::TextureType::Color1D; | 39 | return Shader::TextureType::Color1D; |
| 40 | case Tegra::Texture::TextureType::Texture2D: | 40 | case Tegra::Texture::TextureType::Texture2D: |
| 41 | case Tegra::Texture::TextureType::Texture2DNoMipmap: | 41 | case Tegra::Texture::TextureType::Texture2DNoMipmap: |
| 42 | if (entry.normalized_coords) { | 42 | return entry.normalized_coords ? Shader::TextureType::Color2D |
| 43 | return Shader::TextureType::Color2D; | 43 | : Shader::TextureType::Color2DRect; |
| 44 | } else { | ||
| 45 | return Shader::TextureType::Color2DRect; | ||
| 46 | } | ||
| 47 | case Tegra::Texture::TextureType::Texture3D: | 44 | case Tegra::Texture::TextureType::Texture3D: |
| 48 | return Shader::TextureType::Color3D; | 45 | return Shader::TextureType::Color3D; |
| 49 | case Tegra::Texture::TextureType::TextureCubemap: | 46 | case Tegra::Texture::TextureType::TextureCubemap: |
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index e44759856..a4ed68422 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp | |||
| @@ -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); |
| @@ -1301,6 +1302,7 @@ void Config::SaveRendererValues() { | |||
| 1301 | Settings::values.shader_backend.UsingGlobal()); | 1302 | Settings::values.shader_backend.UsingGlobal()); |
| 1302 | WriteGlobalSetting(Settings::values.use_asynchronous_shaders); | 1303 | WriteGlobalSetting(Settings::values.use_asynchronous_shaders); |
| 1303 | WriteGlobalSetting(Settings::values.use_fast_gpu_time); | 1304 | WriteGlobalSetting(Settings::values.use_fast_gpu_time); |
| 1305 | WriteGlobalSetting(Settings::values.use_pessimistic_flushes); | ||
| 1304 | WriteGlobalSetting(Settings::values.bg_red); | 1306 | WriteGlobalSetting(Settings::values.bg_red); |
| 1305 | WriteGlobalSetting(Settings::values.bg_green); | 1307 | WriteGlobalSetting(Settings::values.bg_green); |
| 1306 | WriteGlobalSetting(Settings::values.bg_blue); | 1308 | WriteGlobalSetting(Settings::values.bg_blue); |
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_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_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_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 = |