diff options
85 files changed, 887 insertions, 1159 deletions
diff --git a/.ci/scripts/linux/upload.sh b/.ci/scripts/linux/upload.sh index 8173c5728a..e0f336427b 100755 --- a/.ci/scripts/linux/upload.sh +++ b/.ci/scripts/linux/upload.sh @@ -5,21 +5,24 @@ . .ci/scripts/common/pre-upload.sh -APPIMAGE_NAME="yuzu-${GITDATE}-${GITREV}.AppImage" -REV_NAME="yuzu-linux-${GITDATE}-${GITREV}" +APPIMAGE_NAME="yuzu-${RELEASE_NAME}-${GITDATE}-${GITREV}.AppImage" +BASE_NAME="yuzu-linux" +REV_NAME="${BASE_NAME}-${GITDATE}-${GITREV}" ARCHIVE_NAME="${REV_NAME}.tar.xz" COMPRESSION_FLAGS="-cJvf" -if [ "${RELEASE_NAME}" = "mainline" ]; then - DIR_NAME="${REV_NAME}" +if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then + DIR_NAME="${BASE_NAME}-${RELEASE_NAME}" else - DIR_NAME="${REV_NAME}_${RELEASE_NAME}" + DIR_NAME="${REV_NAME}-${RELEASE_NAME}" fi mkdir "$DIR_NAME" cp build/bin/yuzu-cmd "$DIR_NAME" -cp build/bin/yuzu "$DIR_NAME" +if [ "${RELEASE_NAME}" != "early-access" ] && [ "${RELEASE_NAME}" != "mainline" ]; then + cp build/bin/yuzu "$DIR_NAME" +fi # Build an AppImage cd build @@ -32,6 +35,11 @@ if ! ./appimagetool-x86_64.AppImage --version; then export APPIMAGE_EXTRACT_AND_RUN=1 fi +# Don't let AppImageLauncher ask to integrate EA +if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then + echo "X-AppImage-Integrate=false" >> AppDir/org.yuzu_emu.yuzu.desktop +fi + if [ "${RELEASE_NAME}" = "mainline" ]; then # Generate update information if releasing to mainline ./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 cp "build/${APPIMAGE_NAME}.zsync" "${ARTIFACTS_DIR}/" fi +# Copy the AppImage to the general release directory and remove git revision info +if [ "${RELEASE_NAME}" = "mainline" ] || [ "${RELEASE_NAME}" = "early-access" ]; then + cp "build/${APPIMAGE_NAME}" "${DIR_NAME}/yuzu-${RELEASE_NAME}.AppImage" +fi + . .ci/scripts/common/post-upload.sh diff --git a/.ci/yuzu-patreon-step2.yml b/.ci/yuzu-patreon-step2.yml index 5d5b140fd0..71a23ebe62 100644 --- a/.ci/yuzu-patreon-step2.yml +++ b/.ci/yuzu-patreon-step2.yml @@ -11,9 +11,30 @@ stages: - stage: build displayName: 'build' jobs: - - job: build + - job: linux timeoutInMinutes: 120 - displayName: 'windows-msvc' + displayName: 'linux' + pool: + vmImage: ubuntu-latest + strategy: + maxParallel: 10 + matrix: + linux: + BuildSuffix: 'linux' + ScriptFolder: 'linux' + steps: + - template: ./templates/sync-source.yml + parameters: + artifactSource: $(parameters.artifactSource) + needSubmodules: 'true' + - template: ./templates/build-single.yml + parameters: + artifactSource: 'false' + cache: $(parameters.cache) + version: $(DisplayVersion) + - job: msvc + timeoutInMinutes: 120 + displayName: 'windows' pool: vmImage: windows-2022 steps: diff --git a/.reuse/dep5 b/.reuse/dep5 index fe4fa2f07f..5ba0174948 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -6,6 +6,7 @@ Files: dist/english_plurals/* dist/icons/controller/*.png dist/icons/overlay/*.png dist/languages/* + dist/qt_themes/*/icons/48x48/sd_card.png dist/qt_themes/*/icons/index.theme dist/qt_themes/default/style.qss Copyright: yuzu Emulator Project @@ -66,9 +67,7 @@ Files: dist/qt_themes/*/icons/48x48/no_avatar.png Copyright: Ionic (http://ionic.io/) License: MIT - -Files: dist/qt_themes/*/icons/48x48/sd_card.png - dist/qt_themes/colorful/icons/48x48/star.png +Files: dist/qt_themes/colorful/icons/48x48/star.png dist/qt_themes/default/icons/16x16/checked.png dist/qt_themes/default/icons/16x16/failed.png 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 Binary files differindex 47e491d32c..652d61bc32 100644 --- a/dist/qt_themes/colorful/icons/48x48/sd_card.png +++ b/dist/qt_themes/colorful/icons/48x48/sd_card.png diff --git a/dist/qt_themes/default/icons/48x48/sd_card.png b/dist/qt_themes/default/icons/48x48/sd_card.png Binary files differindex 60dfba2693..6bcb7f6b1d 100644 --- a/dist/qt_themes/default/icons/48x48/sd_card.png +++ b/dist/qt_themes/default/icons/48x48/sd_card.png diff --git a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png Binary files differindex 87ae5186d9..15e5e40245 100644 --- a/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png +++ b/dist/qt_themes/qdarkstyle/icons/48x48/sd_card.png diff --git a/externals/CMakeLists.txt b/externals/CMakeLists.txt index eea70fc27d..e80fd124eb 100644 --- a/externals/CMakeLists.txt +++ b/externals/CMakeLists.txt @@ -16,7 +16,6 @@ endif() # Dynarmic if (ARCHITECTURE_x86_64) - set(DYNARMIC_TESTS OFF) set(DYNARMIC_NO_BUNDLED_FMT ON) set(DYNARMIC_IGNORE_ASSERTS ON CACHE BOOL "" FORCE) add_subdirectory(dynarmic) diff --git a/src/audio_core/CMakeLists.txt b/src/audio_core/CMakeLists.txt index 5fe1d5fa5f..144f1bab29 100644 --- a/src/audio_core/CMakeLists.txt +++ b/src/audio_core/CMakeLists.txt @@ -194,6 +194,7 @@ add_library(audio_core STATIC sink/sink.h sink/sink_details.cpp sink/sink_details.h + sink/sink_stream.cpp sink/sink_stream.h ) diff --git a/src/audio_core/audio_core.cpp b/src/audio_core/audio_core.cpp index 78e615a10e..c845330cd4 100644 --- a/src/audio_core/audio_core.cpp +++ b/src/audio_core/audio_core.cpp @@ -47,22 +47,12 @@ AudioRenderer::ADSP::ADSP& AudioCore::GetADSP() { return *adsp; } -void AudioCore::PauseSinks(const bool pausing) const { - if (pausing) { - output_sink->PauseStreams(); - input_sink->PauseStreams(); - } else { - output_sink->UnpauseStreams(); - input_sink->UnpauseStreams(); - } +void AudioCore::SetNVDECActive(bool active) { + nvdec_active = active; } -u32 AudioCore::GetStreamQueue() const { - return estimated_queue.load(); -} - -void AudioCore::SetStreamQueue(u32 size) { - estimated_queue.store(size); +bool AudioCore::IsNVDECActive() const { + return nvdec_active; } } // namespace AudioCore diff --git a/src/audio_core/audio_core.h b/src/audio_core/audio_core.h index 0f7d61ee4b..e33e00a3e9 100644 --- a/src/audio_core/audio_core.h +++ b/src/audio_core/audio_core.h @@ -17,7 +17,7 @@ namespace AudioCore { class AudioManager; /** - * Main audio class, sotred inside the core, and holding the audio manager, all sinks, and the ADSP. + * Main audio class, stored inside the core, and holding the audio manager, all sinks, and the ADSP. */ class AudioCore { public: @@ -58,26 +58,16 @@ public: AudioRenderer::ADSP::ADSP& GetADSP(); /** - * Pause the sink. Called from the core. + * Toggle NVDEC state, used to avoid stall in playback. * - * @param pausing - Is this pause due to an actual pause, or shutdown? - * Unfortunately, shutdown also pauses streams, which can cause issues. + * @param active - Set true if nvdec is active, otherwise false. */ - void PauseSinks(bool pausing) const; + void SetNVDECActive(bool active); /** - * Get the size of the current stream queue. - * - * @return Current stream queue size. - */ - u32 GetStreamQueue() const; - - /** - * Get the size of the current stream queue. - * - * @param size - New stream size. + * Get NVDEC state. */ - void SetStreamQueue(u32 size); + bool IsNVDECActive() const; private: /** @@ -93,8 +83,8 @@ private: std::unique_ptr<Sink::Sink> input_sink; /// The ADSP in the sysmodule std::unique_ptr<AudioRenderer::ADSP::ADSP> adsp; - /// Current size of the stream queue - std::atomic<u32> estimated_queue{0}; + /// Is NVDec currently active? + bool nvdec_active{false}; }; } // namespace AudioCore diff --git a/src/audio_core/audio_event.h b/src/audio_core/audio_event.h index 82dd32dca5..012d2ed70e 100644 --- a/src/audio_core/audio_event.h +++ b/src/audio_core/audio_event.h @@ -14,7 +14,7 @@ namespace AudioCore { * Responsible for the input/output events, set by the stream backend when buffers are consumed, and * waited on by the audio manager. These callbacks signal the game's events to keep the audio buffer * recycling going. - * In a real Switch this is not a seprate class, and exists entirely within the audio manager. + * In a real Switch this is not a separate class, and exists entirely within the audio manager. * On the Switch it's implemented more simply through a MultiWaitEventHolder, where it can * wait on multiple events at once, and the events are not needed by the backend. */ @@ -81,7 +81,7 @@ public: void ClearEvents(); private: - /// Lock, used bythe audio manager + /// Lock, used by the audio manager std::mutex event_lock; /// Array of events, one per system type (see Type), last event is used to terminate 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 4aadb7fd61..f39fb40021 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 auto input_devices{Sink::GetDeviceListForSink(Settings::values.sink_id.GetValue(), true)}; if (input_devices.size() > 1) { - names.push_back(AudioRenderer::AudioDevice::AudioDeviceName("Uac")); + names.emplace_back("Uac"); return 1; } return 0; diff --git a/src/audio_core/audio_in_manager.h b/src/audio_core/audio_in_manager.h index 75b73a0b6f..8a519df999 100644 --- a/src/audio_core/audio_in_manager.h +++ b/src/audio_core/audio_in_manager.h @@ -59,9 +59,10 @@ public: /** * Get a list of audio in device names. * - * @oaram names - Output container to write names to. - * @param max_count - Maximum numebr of deivce names to write. Unused + * @param names - Output container to write names to. + * @param max_count - Maximum number of device names to write. Unused * @param filter - Should the list be filtered? Unused. + * * @return Number of names written. */ 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 70316e9cbd..8cbd95e22e 100644 --- a/src/audio_core/audio_manager.h +++ b/src/audio_core/audio_manager.h @@ -76,7 +76,7 @@ public: private: /** - * Main thread, waiting on a manager signal and calling the registered fucntion. + * Main thread, waiting on a manager signal and calling the registered function. */ void ThreadFunc(); diff --git a/src/audio_core/audio_out_manager.cpp b/src/audio_core/audio_out_manager.cpp index 71d67de647..1766efde15 100644 --- a/src/audio_core/audio_out_manager.cpp +++ b/src/audio_core/audio_out_manager.cpp @@ -74,7 +74,7 @@ void Manager::BufferReleaseAndRegister() { u32 Manager::GetAudioOutDeviceNames( std::vector<AudioRenderer::AudioDevice::AudioDeviceName>& names) const { - names.push_back({"DeviceOut"}); + names.emplace_back("DeviceOut"); return 1; } diff --git a/src/audio_core/audio_render_manager.h b/src/audio_core/audio_render_manager.h index 6a508ec560..7119e1b992 100644 --- a/src/audio_core/audio_render_manager.h +++ b/src/audio_core/audio_render_manager.h @@ -64,10 +64,10 @@ public: /** * Add a renderer system to the manager. - * The system will be reguarly called to generate commands for the AudioRenderer. + * The system will be regularly called to generate commands for the AudioRenderer. * * @param system - The system to add. - * @return True if the system was sucessfully added, otherwise false. + * @return True if the system was successfully added, otherwise false. */ bool AddSystem(System& system); @@ -75,7 +75,7 @@ public: * Remove a renderer system from the manager. * * @param system - The system to remove. - * @return True if the system was sucessfully removed, otherwise false. + * @return True if the system was successfully removed, otherwise false. */ bool RemoveSystem(System& system); diff --git a/src/audio_core/device/audio_buffer.h b/src/audio_core/device/audio_buffer.h index cae7fa9704..7128ef72a7 100644 --- a/src/audio_core/device/audio_buffer.h +++ b/src/audio_core/device/audio_buffer.h @@ -8,6 +8,10 @@ namespace AudioCore { struct AudioBuffer { + /// Timestamp this buffer started playing. + u64 start_timestamp; + /// Timestamp this buffer should finish playing. + u64 end_timestamp; /// Timestamp this buffer completed playing. s64 played_timestamp; /// 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 5d1979ea0c..3ecbbb63f0 100644 --- a/src/audio_core/device/audio_buffers.h +++ b/src/audio_core/device/audio_buffers.h @@ -58,6 +58,7 @@ public: if (index < 0) { index += N; } + out_buffers.push_back(buffers[index]); registered_count++; registered_index = (registered_index + 1) % append_limit; @@ -87,7 +88,9 @@ public: /** * Release all registered buffers. * - * @param timestamp - The released timestamp for this buffer. + * @param core_timing - The CoreTiming instance + * @param session - The device session + * * @return Is the buffer was released. */ bool ReleaseBuffers(Core::Timing::CoreTiming& core_timing, DeviceSession& session) { @@ -100,7 +103,7 @@ public: } // Check with the backend if this buffer can be released yet. - if (!session.IsBufferConsumed(buffers[index].tag)) { + if (!session.IsBufferConsumed(buffers[index])) { break; } @@ -280,6 +283,16 @@ public: return true; } + u64 GetNextTimestamp() const { + // Iterate backwards through the buffer queue, and take the most recent buffer's end + std::scoped_lock l{lock}; + auto index{appended_index - 1}; + if (index < 0) { + index += append_limit; + } + return buffers[index].end_timestamp; + } + private: /// Buffer lock mutable std::recursive_mutex lock{}; diff --git a/src/audio_core/device/device_session.cpp b/src/audio_core/device/device_session.cpp index 095fc96ce1..c71c3a376f 100644 --- a/src/audio_core/device/device_session.cpp +++ b/src/audio_core/device/device_session.cpp @@ -7,11 +7,20 @@ #include "audio_core/device/device_session.h" #include "audio_core/sink/sink_stream.h" #include "core/core.h" +#include "core/core_timing.h" #include "core/memory.h" namespace AudioCore { -DeviceSession::DeviceSession(Core::System& system_) : system{system_} {} +using namespace std::literals; +constexpr auto INCREMENT_TIME{5ms}; + +DeviceSession::DeviceSession(Core::System& system_) + : system{system_}, thread_event{Core::Timing::CreateEvent( + "AudioOutSampleTick", + [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { + return ThreadFunc(); + })} {} DeviceSession::~DeviceSession() { Finalize(); @@ -50,20 +59,21 @@ void DeviceSession::Finalize() { } void DeviceSession::Start() { - stream->SetPlayedSampleCount(played_sample_count); - stream->Start(); + if (stream) { + stream->Start(); + system.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds::zero(), INCREMENT_TIME, + thread_event); + } } void DeviceSession::Stop() { if (stream) { - played_sample_count = stream->GetPlayedSampleCount(); stream->Stop(); + system.CoreTiming().UnscheduleEvent(thread_event, {}); } } void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { - auto& memory{system.Memory()}; - for (size_t i = 0; i < buffers.size(); i++) { Sink::SinkBuffer new_buffer{ .frames = buffers[i].size / (channel_count * sizeof(s16)), @@ -77,7 +87,7 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { stream->AppendBuffer(new_buffer, samples); } else { std::vector<s16> samples(buffers[i].size / sizeof(s16)); - memory.ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); + system.Memory().ReadBlockUnsafe(buffers[i].samples, samples.data(), buffers[i].size); stream->AppendBuffer(new_buffer, samples); } } @@ -85,17 +95,13 @@ void DeviceSession::AppendBuffers(std::span<AudioBuffer> buffers) const { void DeviceSession::ReleaseBuffer(AudioBuffer& buffer) const { if (type == Sink::StreamType::In) { - auto& memory{system.Memory()}; auto samples{stream->ReleaseBuffer(buffer.size / sizeof(s16))}; - memory.WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); + system.Memory().WriteBlockUnsafe(buffer.samples, samples.data(), buffer.size); } } -bool DeviceSession::IsBufferConsumed(u64 tag) const { - if (stream) { - return stream->IsBufferConsumed(tag); - } - return true; +bool DeviceSession::IsBufferConsumed(AudioBuffer& buffer) const { + return played_sample_count >= buffer.end_timestamp; } void DeviceSession::SetVolume(f32 volume) const { @@ -105,10 +111,22 @@ void DeviceSession::SetVolume(f32 volume) const { } u64 DeviceSession::GetPlayedSampleCount() const { - if (stream) { - return stream->GetPlayedSampleCount(); + return played_sample_count; +} + +std::optional<std::chrono::nanoseconds> DeviceSession::ThreadFunc() { + // Add 5ms of samples at a 48K sample rate. + played_sample_count += 48'000 * INCREMENT_TIME / 1s; + if (type == Sink::StreamType::Out) { + system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioOutManager, true); + } else { + system.AudioCore().GetAudioManager().SetEvent(Event::Type::AudioInManager, true); } - return 0; + return std::nullopt; +} + +void DeviceSession::SetRingSize(u32 ring_size) { + stream->SetRingSize(ring_size); } } // namespace AudioCore diff --git a/src/audio_core/device/device_session.h b/src/audio_core/device/device_session.h index 4a031b7651..53b649c615 100644 --- a/src/audio_core/device/device_session.h +++ b/src/audio_core/device/device_session.h @@ -3,6 +3,9 @@ #pragma once +#include <chrono> +#include <memory> +#include <optional> #include <span> #include "audio_core/common/common.h" @@ -11,9 +14,13 @@ namespace Core { class System; -} +namespace Timing { +struct EventType; +} // namespace Timing +} // namespace Core namespace AudioCore { + namespace Sink { class SinkStream; struct SinkBuffer; @@ -67,10 +74,11 @@ public: /** * Check if the buffer for the given tag has been consumed by the backend. * - * @param tag - Unqiue tag of the buffer to check. + * @param buffer - the buffer to check. + * * @return true if the buffer has been consumed, otherwise false. */ - bool IsBufferConsumed(u64 tag) const; + bool IsBufferConsumed(AudioBuffer& buffer) const; /** * Start this device session, starting the backend stream. @@ -96,6 +104,16 @@ public: */ u64 GetPlayedSampleCount() const; + /* + * CoreTiming callback to increment played_sample_count over time. + */ + std::optional<std::chrono::nanoseconds> ThreadFunc(); + + /* + * Set the size of the ring buffer. + */ + void SetRingSize(u32 ring_size); + private: /// System Core::System& system; @@ -118,9 +136,13 @@ private: /// Applet resource user id of this device session u64 applet_resource_user_id{}; /// Total number of samples played by this device session - u64 played_sample_count{}; + std::atomic<u64> played_sample_count{}; + /// Event increasing the played sample count every 5ms + std::shared_ptr<Core::Timing::EventType> thread_event; /// Is this session initialised? bool initialized{}; + /// Buffer queue + std::vector<AudioBuffer> buffer_queue{}; }; } // namespace AudioCore diff --git a/src/audio_core/in/audio_in_system.cpp b/src/audio_core/in/audio_in_system.cpp index ec5d37ed44..7e80ba03c1 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() { std::vector<AudioBuffer> buffers_to_flush{}; buffers.RegisterBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush); + session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); return ResultSuccess; } @@ -112,8 +113,13 @@ bool System::AppendBuffer(const AudioInBuffer& buffer, const u64 tag) { return false; } - AudioBuffer new_buffer{ - .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; + const auto timestamp{buffers.GetNextTimestamp()}; + AudioBuffer new_buffer{.start_timestamp = timestamp, + .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), + .played_timestamp = 0, + .samples = buffer.samples, + .tag = tag, + .size = buffer.size}; buffers.AppendBuffer(new_buffer); RegisterBuffers(); diff --git a/src/audio_core/in/audio_in_system.h b/src/audio_core/in/audio_in_system.h index 165e35d83b..9ddc8daae1 100644 --- a/src/audio_core/in/audio_in_system.h +++ b/src/audio_core/in/audio_in_system.h @@ -208,7 +208,7 @@ public: /** * Set this system's current volume. * - * @param The new volume. + * @param volume The new volume. */ void SetVolume(f32 volume); diff --git a/src/audio_core/out/audio_out_system.cpp b/src/audio_core/out/audio_out_system.cpp index 35afddf06d..8941b09a0c 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() { std::vector<AudioBuffer> buffers_to_flush{}; buffers.RegisterBuffers(buffers_to_flush); session->AppendBuffers(buffers_to_flush); + session->SetRingSize(static_cast<u32>(buffers_to_flush.size())); return ResultSuccess; } @@ -111,8 +112,13 @@ bool System::AppendBuffer(const AudioOutBuffer& buffer, u64 tag) { return false; } - AudioBuffer new_buffer{ - .played_timestamp = 0, .samples = buffer.samples, .tag = tag, .size = buffer.size}; + const auto timestamp{buffers.GetNextTimestamp()}; + AudioBuffer new_buffer{.start_timestamp = timestamp, + .end_timestamp = timestamp + buffer.size / (channel_count * sizeof(s16)), + .played_timestamp = 0, + .samples = buffer.samples, + .tag = tag, + .size = buffer.size}; buffers.AppendBuffer(new_buffer); RegisterBuffers(); diff --git a/src/audio_core/out/audio_out_system.h b/src/audio_core/out/audio_out_system.h index 4ca2f3417f..205ead861f 100644 --- a/src/audio_core/out/audio_out_system.h +++ b/src/audio_core/out/audio_out_system.h @@ -199,7 +199,7 @@ public: /** * Set this system's current volume. * - * @param The new volume. + * @param volume The new volume. */ void SetVolume(f32 volume); diff --git a/src/audio_core/renderer/adsp/adsp.h b/src/audio_core/renderer/adsp/adsp.h index 4dfcef4a51..523184dc20 100644 --- a/src/audio_core/renderer/adsp/adsp.h +++ b/src/audio_core/renderer/adsp/adsp.h @@ -63,8 +63,6 @@ public: /** * Stop the ADSP. - * - * @return True if started or already running, otherwise false. */ void Stop(); diff --git a/src/audio_core/renderer/adsp/audio_renderer.cpp b/src/audio_core/renderer/adsp/audio_renderer.cpp index 3967ccfe69..bcd889ecb2 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_) { mailbox = mailbox_; thread = std::thread(&AudioRenderer::ThreadFunc, this); - for (auto& stream : streams) { - stream->Start(); - } running = true; } @@ -130,6 +127,7 @@ void AudioRenderer::CreateSinkStreams() { std::string name{fmt::format("ADSP_RenderStream-{}", i)}; streams[i] = sink.AcquireSinkStream(system, channels, name, ::AudioCore::Sink::StreamType::Render); + streams[i]->SetRingSize(4); } } @@ -198,11 +196,6 @@ void AudioRenderer::ThreadFunc() { command_list_processor.Process(index) - start_time; } - if (index == 0) { - auto stream{command_list_processor.GetOutputSinkStream()}; - system.AudioCore().SetStreamQueue(stream->GetQueueSize()); - } - const auto end_time{system.CoreTiming().GetClockTicks()}; 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 b6ced9d2b0..49f66f21c4 100644 --- a/src/audio_core/renderer/adsp/audio_renderer.h +++ b/src/audio_core/renderer/adsp/audio_renderer.h @@ -52,7 +52,7 @@ public: /** * Send a message from the host to the AudioRenderer. * - * @param message_ - The message to send to the AudioRenderer. + * @param message - The message to send to the AudioRenderer. */ void HostSendMessage(RenderMessage message); @@ -66,7 +66,7 @@ public: /** * Send a message from the AudioRenderer to the host. * - * @param message_ - The message to send to the host. + * @param message - The message to send to the host. */ void ADSPSendMessage(RenderMessage message); @@ -163,7 +163,7 @@ public: /** * Start the AudioRenderer. * - * @param The mailbox to use for this session. + * @param mailbox The mailbox to use for this session. */ void Start(AudioRenderer_Mailbox* mailbox); diff --git a/src/audio_core/renderer/adsp/command_list_processor.h b/src/audio_core/renderer/adsp/command_list_processor.h index 3f99173e3e..d78269e1d0 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: /** * Initialize the processor. * - * @param system_ - The core system. - * @param buffer - The command buffer to process. - * @param size - The size of the buffer. - * @param stream_ - The stream to be used for sending the samples. + * @param system - The core system. + * @param buffer - The command buffer to process. + * @param size - The size of the buffer. + * @param stream - The stream to be used for sending the samples. */ void Initialize(Core::System& system, CpuAddr buffer, u64 size, Sink::SinkStream* stream); @@ -72,7 +72,8 @@ public: /** * Process the command list. * - * @param index - Index of the current command list. + * @param session_id - Session ID for the commands being processed. + * * @return The time taken to process. */ u64 Process(u32 session_id); @@ -89,7 +90,7 @@ public: u8* commands{}; /// The command buffer size u64 commands_buffer_size{}; - /// The maximum processing time alloted + /// The maximum processing time allotted u64 max_process_time{}; /// The number of commands in the buffer u32 command_count{}; diff --git a/src/audio_core/renderer/audio_device.cpp b/src/audio_core/renderer/audio_device.cpp index d5886e55e4..0d9d8f6ce5 100644 --- a/src/audio_core/renderer/audio_device.cpp +++ b/src/audio_core/renderer/audio_device.cpp @@ -1,6 +1,9 @@ // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include <array> +#include <span> + #include "audio_core/audio_core.h" #include "audio_core/common/feature_support.h" #include "audio_core/renderer/audio_device.h" @@ -9,14 +12,33 @@ namespace AudioCore::AudioRenderer { +constexpr std::array usb_device_names{ + AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, + AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, + AudioDevice::AudioDeviceName{"AudioTvOutput"}, + AudioDevice::AudioDeviceName{"AudioUsbDeviceOutput"}, +}; + +constexpr std::array device_names{ + AudioDevice::AudioDeviceName{"AudioStereoJackOutput"}, + AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, + AudioDevice::AudioDeviceName{"AudioTvOutput"}, +}; + +constexpr std::array output_device_names{ + AudioDevice::AudioDeviceName{"AudioBuiltInSpeakerOutput"}, + AudioDevice::AudioDeviceName{"AudioTvOutput"}, + AudioDevice::AudioDeviceName{"AudioExternalOutput"}, +}; + AudioDevice::AudioDevice(Core::System& system, const u64 applet_resource_user_id_, const u32 revision) : output_sink{system.AudioCore().GetOutputSink()}, applet_resource_user_id{applet_resource_user_id_}, user_revision{revision} {} u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, - const size_t max_count) { - std::span<AudioDeviceName> names{}; + const size_t max_count) const { + std::span<const AudioDeviceName> names{}; if (CheckFeatureSupported(SupportTags::AudioUsbDeviceOutput, user_revision)) { names = usb_device_names; @@ -24,7 +46,7 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, names = device_names; } - u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; + const u32 out_count{static_cast<u32>(std::min(max_count, names.size()))}; for (u32 i = 0; i < out_count; i++) { out_buffer.push_back(names[i]); } @@ -32,8 +54,8 @@ u32 AudioDevice::ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, } u32 AudioDevice::ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, - const size_t max_count) { - u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; + const size_t max_count) const { + const u32 out_count{static_cast<u32>(std::min(max_count, output_device_names.size()))}; for (u32 i = 0; i < out_count; i++) { out_buffer.push_back(output_device_names[i]); @@ -45,7 +67,7 @@ void AudioDevice::SetDeviceVolumes(const f32 volume) { output_sink.SetDeviceVolume(volume); } -f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) { +f32 AudioDevice::GetDeviceVolume([[maybe_unused]] std::string_view name) const { return output_sink.GetDeviceVolume(); } diff --git a/src/audio_core/renderer/audio_device.h b/src/audio_core/renderer/audio_device.h index 1f449f2612..dd6be70eeb 100644 --- a/src/audio_core/renderer/audio_device.h +++ b/src/audio_core/renderer/audio_device.h @@ -3,7 +3,7 @@ #pragma once -#include <span> +#include <string_view> #include "audio_core/audio_render_manager.h" @@ -23,21 +23,13 @@ namespace AudioRenderer { class AudioDevice { public: struct AudioDeviceName { - std::array<char, 0x100> name; + std::array<char, 0x100> name{}; - AudioDeviceName(const char* name_) { - std::strncpy(name.data(), name_, name.size()); + constexpr AudioDeviceName(std::string_view name_) { + name_.copy(name.data(), name.size() - 1); } }; - std::array<AudioDeviceName, 4> usb_device_names{"AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput", "AudioTvOutput", - "AudioUsbDeviceOutput"}; - std::array<AudioDeviceName, 3> device_names{"AudioStereoJackOutput", - "AudioBuiltInSpeakerOutput", "AudioTvOutput"}; - std::array<AudioDeviceName, 3> output_device_names{"AudioBuiltInSpeakerOutput", "AudioTvOutput", - "AudioExternalOutput"}; - explicit AudioDevice(Core::System& system, u64 applet_resource_user_id, u32 revision); /** @@ -47,7 +39,7 @@ public: * @param max_count - Maximum number of devices to write (count of out_buffer). * @return Number of device names written. */ - u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); + u32 ListAudioDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const; /** * Get a list of the available output devices. @@ -57,7 +49,7 @@ public: * @param max_count - Maximum number of devices to write (count of out_buffer). * @return Number of device names written. */ - u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count); + u32 ListAudioOutputDeviceName(std::vector<AudioDeviceName>& out_buffer, size_t max_count) const; /** * Set the volume of all streams in the backend sink. @@ -73,7 +65,7 @@ public: * @param name - Name of the device to check. Unused. * @return Volume of the device. */ - f32 GetDeviceVolume(std::string_view name); + f32 GetDeviceVolume(std::string_view name) const; private: /// 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 c5d4d66d8f..92140aaea9 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) { } void BehaviorInfo::CopyErrorInfo(std::span<ErrorInfo> out_errors, u32& out_count) { - auto error_count_{std::min(error_count, MaxErrors)}; - std::memset(out_errors.data(), 0, MaxErrors * sizeof(ErrorInfo)); - - for (size_t i = 0; i < error_count_; i++) { - out_errors[i] = errors[i]; + out_count = std::min(error_count, MaxErrors); + + for (size_t i = 0; i < MaxErrors; i++) { + if (i < out_count) { + out_errors[i] = errors[i]; + } else { + out_errors[i] = {}; + } } - out_count = error_count_; } 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 496b0e50a3..1621708469 100644 --- a/src/audio_core/renderer/command/command_buffer.h +++ b/src/audio_core/renderer/command/command_buffer.h @@ -191,6 +191,7 @@ public: * @param volume - Current mix volume used for calculating the ramp. * @param prev_volume - Previous mix volume, used for calculating the ramp, * also applied to the input. + * @param prev_samples - Previous sample buffer. Used for depopping. * @param precision - Number of decimal bits for fixed point operations. */ void GenerateMixRampCommand(s32 node_id, s16 buffer_count, s16 input_index, s16 output_index, @@ -208,6 +209,7 @@ public: * @param volumes - Current mix volumes used for calculating the ramp. * @param prev_volumes - Previous mix volumes, used for calculating the ramp, * also applied to the input. + * @param prev_samples - Previous sample buffer. Used for depopping. * @param precision - Number of decimal bits for fixed point operations. */ void GenerateMixRampGroupedCommand(s32 node_id, s16 buffer_count, s16 input_index, @@ -297,11 +299,11 @@ public: /** * Generate a device sink command, adding it to the command list. * - * @param node_id - Node id of the voice this command is generated for. - * @param buffer_offset - Base mix buffer offset to use. - * @param sink_info - The sink_info to generate this command from. - * @session_id - System session id this command is generated from. - * @samples_buffer - The buffer to be sent to the sink if upsampling is not used. + * @param node_id - Node id of the voice this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param sink_info - The sink_info to generate this command from. + * @param session_id - System session id this command is generated from. + * @param samples_buffer - The buffer to be sent to the sink if upsampling is not used. */ void GenerateDeviceSinkCommand(s32 node_id, s16 buffer_offset, SinkInfoBase& sink_info, 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 d80d9b0d8b..b3cd7b408f 100644 --- a/src/audio_core/renderer/command/command_generator.h +++ b/src/audio_core/renderer/command/command_generator.h @@ -197,9 +197,9 @@ public: /** * Generate an I3DL2 reverb effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - I3DL2Reverb effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - I3DL2Reverb effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateI3dl2ReverbEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); @@ -207,18 +207,18 @@ public: /** * Generate an aux effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Aux effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateAuxCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); /** * Generate a biquad filter effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Aux effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Aux effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateBiquadFilterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); @@ -226,10 +226,10 @@ public: /** * Generate a light limiter effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Limiter effect info. - * @param node_id - Node id of the mix this command is generated for. - * @param effect_index - Index for the statistics state. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Limiter effect info. + * @param node_id - Node id of the mix this command is generated for. + * @param effect_index - Index for the statistics state. */ void GenerateLightLimiterEffectCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id, u32 effect_index); @@ -238,21 +238,20 @@ public: * Generate a capture effect command. * Writes a mix buffer back to game memory. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Capture effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Capture effect info. + * @param node_id - Node id of the mix this command is generated for. */ void GenerateCaptureCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); /** * Generate a compressor effect command. * - * @param buffer_offset - Base mix buffer offset to use. - * @param effect_info_base - Compressor effect info. - * @param node_id - Node id of the mix this command is generated for. + * @param buffer_offset - Base mix buffer offset to use. + * @param effect_info - Compressor effect info. + * @param node_id - Node id of the mix this command is generated for. */ - void GenerateCompressorCommand(const s16 buffer_offset, EffectInfoBase& effect_info, - const s32 node_id); + void GenerateCompressorCommand(s16 buffer_offset, EffectInfoBase& effect_info, s32 node_id); /** * Generate all effect commands for a mix. @@ -318,8 +317,9 @@ public: * Generate a performance command. * Used to report performance metrics of the AudioRenderer back to the game. * - * @param buffer_offset - Base mix buffer offset to use. - * @param sink_info - Sink info to generate the commands from. + * @param node_id - Node ID of the mix this command is generated for + * @param state - Output state of the generated performance command + * @param entry_addresses - Addresses to be written */ void GeneratePerformanceCommand(s32 node_id, PerformanceState state, 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 2ebc140f13..7229618e87 100644 --- a/src/audio_core/renderer/command/effect/compressor.cpp +++ b/src/audio_core/renderer/command/effect/compressor.cpp @@ -11,7 +11,7 @@ namespace AudioCore::AudioRenderer { -static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& params, +static void SetCompressorEffectParameter(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state) { const auto ratio{1.0f / params.compressor_ratio}; auto makeup_gain{0.0f}; @@ -31,9 +31,9 @@ static void SetCompressorEffectParameter(CompressorInfo::ParameterVersion2& para state.unk_20 = c; } -static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params, +static void InitializeCompressorEffect(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state) { - std::memset(&state, 0, sizeof(CompressorInfo::State)); + state = {}; state.unk_00 = 0; state.unk_04 = 1.0f; @@ -42,7 +42,7 @@ static void InitializeCompressorEffect(CompressorInfo::ParameterVersion2& params SetCompressorEffectParameter(params, state); } -static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, +static void ApplyCompressorEffect(const CompressorInfo::ParameterVersion2& params, CompressorInfo::State& state, bool enabled, std::vector<std::span<const s32>> input_buffers, std::vector<std::span<s32>> output_buffers, u32 sample_count) { @@ -103,8 +103,7 @@ static void ApplyCompressorEffect(CompressorInfo::ParameterVersion2& params, } else { for (s16 channel = 0; channel < params.channel_count; channel++) { if (params.inputs[channel] != params.outputs[channel]) { - std::memcpy((char*)output_buffers[channel].data(), - (char*)input_buffers[channel].data(), + std::memcpy(output_buffers[channel].data(), input_buffers[channel].data(), output_buffers[channel].size_bytes()); } } diff --git a/src/audio_core/renderer/command/mix/mix_ramp.cpp b/src/audio_core/renderer/command/mix/mix_ramp.cpp index ffdafa1c8d..d67123cd82 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 @@ #include "common/logging/log.h" namespace AudioCore::AudioRenderer { -/** - * Mix input mix buffer into output mix buffer, with volume applied to the input. - * - * @tparam Q - Number of bits for fixed point operations. - * @param output - Output mix buffer. - * @param input - Input mix buffer. - * @param volume - Volume applied to the input. - * @param ramp - Ramp applied to volume every sample. - * @param sample_count - Number of samples to process. - * @return The final gained input sample, used for depopping. - */ + template <size_t Q> s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, 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 return sample.to_int(); } -template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, const f32, const f32, - const u32); -template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, const f32, const f32, - const u32); +template s32 ApplyMixRamp<15>(std::span<s32>, std::span<const s32>, f32, f32, u32); +template s32 ApplyMixRamp<23>(std::span<s32>, std::span<const s32>, f32, f32, u32); void MixRampCommand::Dump(const ADSP::CommandListProcessor& processor, std::string& string) { 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 770f57e802..52f74a2738 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 { * @tparam Q - Number of bits for fixed point operations. * @param output - Output mix buffer. * @param input - Input mix buffer. - * @param volume - Volume applied to the input. - * @param ramp - Ramp applied to volume every sample. + * @param volume_ - Volume applied to the input. + * @param ramp_ - Ramp applied to volume every sample. * @param sample_count - Number of samples to process. * @return The final gained input sample, used for depopping. */ template <size_t Q> -s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, const f32 volume_, - const f32 ramp_, const u32 sample_count); +s32 ApplyMixRamp(std::span<s32> output, std::span<const s32> input, f32 volume_, f32 ramp_, + u32 sample_count); } // 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 027276e5a4..3b0ce67ef0 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 { std::array<s16, MaxMixBuffers> inputs; /// Output mix buffer indexes for each mix buffer std::array<s16, MaxMixBuffers> outputs; - /// Previous mix vloumes for each mix buffer + /// Previous mix volumes for each mix buffer std::array<f32, MaxMixBuffers> prev_volumes; - /// Current mix vloumes for each mix buffer + /// Current mix volumes for each mix buffer std::array<f32, MaxMixBuffers> volumes; /// Pointer to the previous sample buffer, used for depop CpuAddr previous_samples; diff --git a/src/audio_core/renderer/command/sink/device.cpp b/src/audio_core/renderer/command/sink/device.cpp index 47e0c67226..e88372a75e 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) { out_buffer.tag = reinterpret_cast<u64>(samples.data()); stream->AppendBuffer(out_buffer, samples); + + if (stream->IsPaused()) { + stream->Start(); + } } 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 85955bd9c9..8f6d6e7d87 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 { public: /** * Initialize the effect context - * @param effect_infos List of effect infos for this context - * @param effect_count The number of effects in the list - * @param result_states_cpu The workbuffer of result states for the CPU for this context - * @param result_states_dsp The workbuffer of result states for the DSP for this context - * @param state_count The number of result states + * @param effect_infos_ - List of effect infos for this context + * @param effect_count_ - The number of effects in the list + * @param result_states_cpu_ - The workbuffer of result states for the CPU for this context + * @param result_states_dsp_ - The workbuffer of result states for the DSP for this context + * @param dsp_state_count - The number of result states */ - void Initialize(std::span<EffectInfoBase> effect_infos_, const u32 effect_count_, + void Initialize(std::span<EffectInfoBase> effect_infos_, u32 effect_count_, std::span<EffectResultState> result_states_cpu_, - std::span<EffectResultState> result_states_dsp_, const size_t dsp_state_count); + std::span<EffectResultState> result_states_dsp_, size_t dsp_state_count); /** * 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 8c95838789..8525fde05c 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: * Update the info with new parameters, version 1. * * @param error_info - Used to write call result code. - * @param in_params - New parameters to update the info with. + * @param params - New parameters to update the info with. * @param pool_mapper - Pool for mapping buffers. */ virtual void Update(BehaviorInfo::ErrorInfo& error_info, @@ -305,7 +305,7 @@ public: * Update the info with new parameters, version 2. * * @param error_info - Used to write call result code. - * @param in_params - New parameters to update the info with. + * @param params - New parameters to update the info with. * @param pool_mapper - Pool for mapping buffers. */ 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 4cfefea8ed..bb5c930e1f 100644 --- a/src/audio_core/renderer/memory/address_info.h +++ b/src/audio_core/renderer/memory/address_info.h @@ -19,8 +19,8 @@ public: /** * Setup a new AddressInfo. * - * @param cpu_address - The CPU address of this region. - * @param size - The size of this region. + * @param cpu_address_ - The CPU address of this region. + * @param size_ - The size of this region. */ void Setup(CpuAddr cpu_address_, u64 size_) { cpu_address = cpu_address_; @@ -42,7 +42,6 @@ public: * Assign this region to a memory pool. * * @param memory_pool_ - Memory pool to assign. - * @return The CpuAddr address of this region. */ void SetPool(MemoryPoolInfo* memory_pool_) { 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 a1e0958a29..c0fced56f7 100644 --- a/src/audio_core/renderer/nodes/node_states.h +++ b/src/audio_core/renderer/nodes/node_states.h @@ -112,11 +112,11 @@ public: /** * Initialize the node states. * - * @param buffer - The workbuffer to use. Unused. + * @param buffer_ - The workbuffer to use. Unused. * @param node_buffer_size - The size of the workbuffer. Unused. * @param count - The number of nodes in the graph. */ - void Initialize(std::span<u8> nodes, u64 node_buffer_size, u32 count); + void Initialize(std::span<u8> buffer_, u64 node_buffer_size, u32 count); /** * 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 b82176bef1..b65caa9b6e 100644 --- a/src/audio_core/renderer/performance/performance_manager.h +++ b/src/audio_core/renderer/performance/performance_manager.h @@ -73,7 +73,8 @@ public: * Calculate the required size for the performance workbuffer. * * @param behavior - Check which version is supported. - * @param params - Input parameters. + * @param params - Input parameters. + * * @return Required workbuffer size. */ static u64 GetRequiredBufferSizeForPerformanceMetricsPerFrame( @@ -104,7 +105,7 @@ public: * @param workbuffer - Workbuffer to use for performance frames. * @param workbuffer_size - Size of the workbuffer. * @param params - Input parameters. - * @param behavior - Behaviour to check version and data format. + * @param behavior - Behaviour to check version and data format. * @param memory_pool - Used to translate the workbuffer address for the DSP. */ virtual void Initialize(std::span<u8> workbuffer, u64 workbuffer_size, @@ -160,7 +161,8 @@ public: * workbuffer, to be written by the AudioRenderer. * * @param addresses - Filled with pointers to the new detail, which should be passed - * to the AudioRenderer with Performance commands to be written. + * to the AudioRenderer with Performance commands to be written. + * @param detail_type - Performance detail type. * @param entry_type - The type of this detail. See PerformanceEntryType * @param node_id - Node id for this detail. * @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 b326819eda..9c1331e193 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", MP_RGB(60, 19, 97)); namespace AudioCore::AudioRenderer { -constexpr std::chrono::nanoseconds BaseRenderTime{5'000'000UL}; -constexpr std::chrono::nanoseconds RenderTimeOffset{400'000UL}; +constexpr std::chrono::nanoseconds RENDER_TIME{5'000'000UL}; SystemManager::SystemManager(Core::System& core_) : core{core_}, adsp{core.AudioCore().GetADSP()}, mailbox{adsp.GetRenderMailbox()}, thread_event{Core::Timing::CreateEvent( "AudioRendererSystemManager", [this](std::uintptr_t, s64 time, std::chrono::nanoseconds) { return ThreadFunc2(time); - })} { - core.CoreTiming().RegisterPauseCallback([this](bool paused) { PauseCallback(paused); }); -} + })} {} SystemManager::~SystemManager() { Stop(); @@ -36,8 +33,8 @@ bool SystemManager::InitializeUnsafe() { if (adsp.Start()) { active = true; thread = std::jthread([this](std::stop_token stop_token) { ThreadFunc(); }); - core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), - BaseRenderTime - RenderTimeOffset, thread_event); + core.CoreTiming().ScheduleLoopingEvent(std::chrono::nanoseconds(0), RENDER_TIME, + thread_event); } } @@ -121,42 +118,9 @@ void SystemManager::ThreadFunc() { } std::optional<std::chrono::nanoseconds> SystemManager::ThreadFunc2(s64 time) { - std::optional<std::chrono::nanoseconds> new_schedule_time{std::nullopt}; - const auto queue_size{core.AudioCore().GetStreamQueue()}; - switch (state) { - case StreamState::Filling: - if (queue_size >= 5) { - new_schedule_time = BaseRenderTime; - state = StreamState::Steady; - } - break; - case StreamState::Steady: - if (queue_size <= 2) { - new_schedule_time = BaseRenderTime - RenderTimeOffset; - state = StreamState::Filling; - } else if (queue_size > 5) { - new_schedule_time = BaseRenderTime + RenderTimeOffset; - state = StreamState::Draining; - } - break; - case StreamState::Draining: - if (queue_size <= 5) { - new_schedule_time = BaseRenderTime; - state = StreamState::Steady; - } - break; - } - update.store(true); update.notify_all(); - return new_schedule_time; -} - -void SystemManager::PauseCallback(bool paused) { - if (paused && core.IsPoweredOn() && core.IsShuttingDown()) { - update.store(true); - update.notify_all(); - } + return std::nullopt; } } // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/system_manager.h b/src/audio_core/renderer/system_manager.h index 1291e9e0ef..81457a3a18 100644 --- a/src/audio_core/renderer/system_manager.h +++ b/src/audio_core/renderer/system_manager.h @@ -73,13 +73,6 @@ private: */ std::optional<std::chrono::nanoseconds> ThreadFunc2(s64 time); - /** - * Callback from core timing when pausing, used to detect shutdowns and stop ThreadFunc. - * - * @param paused - Are we pausing or resuming? - */ - void PauseCallback(bool paused); - enum class StreamState { Filling, Steady, @@ -106,8 +99,6 @@ private: std::shared_ptr<Core::Timing::EventType> thread_event; /// Atomic for main thread to wait on std::atomic<bool> update{}; - /// Current state of the streams - StreamState state{StreamState::Filling}; }; } // namespace AudioCore::AudioRenderer diff --git a/src/audio_core/renderer/upsampler/upsampler_manager.h b/src/audio_core/renderer/upsampler/upsampler_manager.h index 70cd42b08d..83c697c0c1 100644 --- a/src/audio_core/renderer/upsampler/upsampler_manager.h +++ b/src/audio_core/renderer/upsampler/upsampler_manager.h @@ -27,7 +27,7 @@ public: /** * Free the given upsampler. * - * @param The upsampler to be freed. + * @param info The upsampler to be freed. */ void Free(UpsamplerInfo* info); diff --git a/src/audio_core/renderer/voice/voice_info.h b/src/audio_core/renderer/voice/voice_info.h index 896723e0c7..930180895b 100644 --- a/src/audio_core/renderer/voice/voice_info.h +++ b/src/audio_core/renderer/voice/voice_info.h @@ -185,7 +185,8 @@ public: /** * Does this voice ned an update? * - * @param params - Input parametetrs to check matching. + * @param params - Input parameters to check matching. + * * @return True if this voice needs an update, otherwise false. */ bool ShouldUpdateParameters(const InParameter& params) const; @@ -194,9 +195,9 @@ public: * Update the parameters of this voice. * * @param error_info - Output error code. - * @param params - Input parametters to udpate from. + * @param params - Input parameters to update from. * @param pool_mapper - Used to map buffers. - * @param behavior - behavior to check supported features. + * @param behavior - behavior to check supported features. */ void UpdateParameters(BehaviorInfo::ErrorInfo& error_info, const InParameter& params, const PoolMapper& pool_mapper, const BehaviorInfo& behavior); @@ -218,12 +219,12 @@ public: /** * Update all wavebuffers. * - * @param error_infos - Output 2D array of errors, 2 per wavebuffer. - * @param error_count - Number of errors provided. Unused. - * @param params - Input parametters to be used for the update. + * @param error_infos - Output 2D array of errors, 2 per wavebuffer. + * @param error_count - Number of errors provided. Unused. + * @param params - Input parameters to be used for the update. * @param voice_states - The voice states for each channel in this voice to be updated. - * @param pool_mapper - Used to map the wavebuffers. - * @param behavior - Used to check for supported features. + * @param pool_mapper - Used to map the wavebuffers. + * @param behavior - Used to check for supported features. */ void UpdateWaveBuffers(std::span<std::array<BehaviorInfo::ErrorInfo, 2>> error_infos, u32 error_count, const InParameter& params, @@ -233,13 +234,13 @@ public: /** * Update a wavebuffer. * - * @param error_infos - Output array of errors. + * @param error_info - Output array of errors. * @param wave_buffer - The wavebuffer to be updated. * @param wave_buffer_internal - Input parametters to be used for the update. * @param sample_format - Sample format of the wavebuffer. * @param valid - Is this wavebuffer valid? * @param pool_mapper - Used to map the wavebuffers. - * @param behavior - Used to check for supported features. + * @param behavior - Used to check for supported features. */ void UpdateWaveBuffer(std::span<BehaviorInfo::ErrorInfo> error_info, WaveBuffer& wave_buffer, const WaveBufferInternal& wave_buffer_internal, @@ -276,7 +277,7 @@ public: /** * Check if this voice has any mixing connections. * - * @return True if this voice participes in mixing, otherwise false. + * @return True if this voice participates in mixing, otherwise false. */ bool HasAnyConnection() const; @@ -301,7 +302,8 @@ public: /** * Update this voice on command generation. * - * @param voice_states - Voice states for these wavebuffers. + * @param voice_context - Voice context for these wavebuffers. + * * @return True if this voice should be generated, otherwise false. */ bool UpdateForCommandGeneration(VoiceContext& voice_context); diff --git a/src/audio_core/sink/cubeb_sink.cpp b/src/audio_core/sink/cubeb_sink.cpp index 90d049e8e4..36b115ad69 100644 --- a/src/audio_core/sink/cubeb_sink.cpp +++ b/src/audio_core/sink/cubeb_sink.cpp @@ -1,21 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <algorithm> -#include <atomic> #include <span> +#include <vector> -#include "audio_core/audio_core.h" -#include "audio_core/audio_event.h" -#include "audio_core/audio_manager.h" +#include "audio_core/common/common.h" #include "audio_core/sink/cubeb_sink.h" #include "audio_core/sink/sink_stream.h" -#include "common/assert.h" -#include "common/fixed_point.h" #include "common/logging/log.h" -#include "common/reader_writer_queue.h" -#include "common/ring_buffer.h" -#include "common/settings.h" #include "core/core.h" #ifdef _WIN32 @@ -42,10 +34,10 @@ public: * @param system_ - Core system. * @param event - Event used only for audio renderer, signalled on buffer consume. */ - CubebSinkStream(cubeb* ctx_, const u32 device_channels_, const u32 system_channels_, + CubebSinkStream(cubeb* ctx_, u32 device_channels_, u32 system_channels_, cubeb_devid output_device, cubeb_devid input_device, const std::string& name_, - const StreamType type_, Core::System& system_) - : ctx{ctx_}, type{type_}, system{system_} { + StreamType type_, Core::System& system_) + : SinkStream(system_, type_), ctx{ctx_} { #ifdef _WIN32 CoInitializeEx(nullptr, COINIT_MULTITHREADED); #endif @@ -79,12 +71,10 @@ public: minimum_latency = std::max(minimum_latency, 256u); - playing_buffer.consumed = true; - - LOG_DEBUG(Service_Audio, - "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " - "latency {}", - name, type, params.rate, params.channels, system_channels, minimum_latency); + LOG_INFO(Service_Audio, + "Opening cubeb stream {} type {} with: rate {} channels {} (system channels {}) " + "latency {}", + name, type, params.rate, params.channels, system_channels, minimum_latency); auto init_error{0}; if (type == StreamType::In) { @@ -111,6 +101,8 @@ public: ~CubebSinkStream() override { LOG_DEBUG(Service_Audio, "Destructing cubeb stream {}", name); + Unstall(); + if (!ctx) { return; } @@ -136,21 +128,14 @@ public: * @param resume - Set to true if this is resuming the stream a previously-active stream. * Default false. */ - void Start(const bool resume = false) override { - if (!ctx) { + void Start(bool resume = false) override { + if (!ctx || !paused) { return; } - if (resume && was_playing) { - if (cubeb_stream_start(stream_backend) != CUBEB_OK) { - LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); - } - paused = false; - } else if (!resume) { - if (cubeb_stream_start(stream_backend) != CUBEB_OK) { - LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); - } - paused = false; + paused = false; + if (cubeb_stream_start(stream_backend) != CUBEB_OK) { + LOG_CRITICAL(Audio_Sink, "Error starting cubeb stream"); } } @@ -158,207 +143,20 @@ public: * Stop the sink stream. */ void Stop() override { - if (!ctx) { + Unstall(); + + if (!ctx || paused) { return; } + paused = true; if (cubeb_stream_stop(stream_backend) != CUBEB_OK) { LOG_CRITICAL(Audio_Sink, "Error stopping cubeb stream"); } - - was_playing.store(!paused); - paused = true; - } - - /** - * Append a new buffer and its samples to a waiting queue to play. - * - * @param buffer - Audio buffer information to be queued. - * @param samples - The s16 samples to be queue for playback. - */ - void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { - if (type == StreamType::In) { - queue.enqueue(buffer); - queued_buffers++; - } else { - constexpr s32 min{std::numeric_limits<s16>::min()}; - constexpr s32 max{std::numeric_limits<s16>::max()}; - - auto yuzu_volume{Settings::Volume()}; - if (yuzu_volume > 1.0f) { - yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); - } - auto volume{system_volume * device_volume * yuzu_volume}; - - if (system_channels == 6 && device_channels == 2) { - // We're given 6 channels, but our device only outputs 2, so downmix. - constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * - down_mix_coeff[0] + - samples[read_index + static_cast<u32>(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast<u32>(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast<u32>(Channels::BackLeft)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - const auto right_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast<u32>(Channels::FrontRight)]) * - down_mix_coeff[0] + - samples[read_index + static_cast<u32>(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast<u32>(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast<u32>(Channels::BackRight)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - samples[write_index + static_cast<u32>(Channels::FrontLeft)] = - static_cast<s16>(std::clamp(left_sample, min, max)); - samples[write_index + static_cast<u32>(Channels::FrontRight)] = - static_cast<s16>(std::clamp(right_sample, min, max)); - } - - samples.resize(samples.size() / system_channels * device_channels); - - } else if (system_channels == 2 && device_channels == 6) { - // We need moar samples! Not all games will provide 6 channel audio. - // TODO: Implement some upmixing here. Currently just passthrough, with other - // channels left as silence. - std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{static_cast<s16>(std::clamp( - static_cast<s32>( - static_cast<f32>( - samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; - - const auto right_sample{static_cast<s16>(std::clamp( - static_cast<s32>( - static_cast<f32>( - samples[read_index + static_cast<u32>(Channels::FrontRight)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = - right_sample; - } - samples = std::move(new_samples); - - } else if (volume != 1.0f) { - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast<s16>(std::clamp( - static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); - } - } - - samples_buffer.Push(samples); - queue.enqueue(buffer); - queued_buffers++; - } - } - - /** - * Release a buffer. Audio In only, will fill a buffer with recorded samples. - * - * @param num_samples - Maximum number of samples to receive. - * @return Vector of recorded samples. May have fewer than num_samples. - */ - std::vector<s16> ReleaseBuffer(const u64 num_samples) override { - static constexpr s32 min = std::numeric_limits<s16>::min(); - static constexpr s32 max = std::numeric_limits<s16>::max(); - - auto samples{samples_buffer.Pop(num_samples)}; - - // TODO: Up-mix to 6 channels if the game expects it. - // For audio input this is unlikely to ever be the case though. - - // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. - // TODO: Play with this and find something that works better. - auto volume{system_volume * device_volume * 8}; - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast<s16>( - std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); - } - - if (samples.size() < num_samples) { - samples.resize(num_samples, 0); - } - return samples; - } - - /** - * Check if a certain buffer has been consumed (fully played). - * - * @param tag - Unique tag of a buffer to check for. - * @return True if the buffer has been played, otherwise false. - */ - bool IsBufferConsumed(const u64 tag) override { - if (released_buffer.tag == 0) { - if (!released_buffers.try_dequeue(released_buffer)) { - return false; - } - } - - if (released_buffer.tag == tag) { - released_buffer.tag = 0; - return true; - } - return false; - } - - /** - * Empty out the buffer queue. - */ - void ClearQueue() override { - samples_buffer.Pop(); - while (queue.pop()) { - } - while (released_buffers.pop()) { - } - queued_buffers = 0; - released_buffer = {}; - playing_buffer = {}; - playing_buffer.consumed = true; } private: /** - * Signal events back to the audio system that a buffer was played/can be filled. - * - * @param buffer - Consumed audio buffer to be released. - */ - void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { - auto& manager{system.AudioCore().GetAudioManager()}; - switch (type) { - case StreamType::Out: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioOutManager, true); - break; - case StreamType::In: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioInManager, true); - break; - case StreamType::Render: - break; - } - } - - /** * Main callback from Cubeb. Either expects samples from us (audio render/audio out), or will * provide samples to be copied (audio in). * @@ -378,106 +176,15 @@ private: const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t frame_size = num_channels; - const std::size_t frame_size_bytes = frame_size * sizeof(s16); const std::size_t num_frames{static_cast<size_t>(num_frames_)}; - size_t frames_written{0}; - [[maybe_unused]] bool underrun{false}; if (impl->type == StreamType::In) { - // INPUT std::span<const s16> input_buffer{reinterpret_cast<const s16*>(in_buff), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, just push the samples and - // continue. - underrun = true; - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - (num_frames - frames_written) * frame_size); - frames_written = num_frames; - continue; - } else { - // Successfully got a new buffer, mark the old one as consumed and signal. - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + impl->ProcessAudioIn(input_buffer, num_frames); } else { - // OUTPUT std::span<s16> output_buffer{reinterpret_cast<s16*>(out_buff), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, fill the remaining buffer with - // the last written frame and continue. - underrun = true; - for (size_t i = frames_written; i < num_frames; i++) { - std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], - frame_size_bytes); - } - frames_written = num_frames; - continue; - } else { - // Successfully got a new buffer, mark the old one as consumed and signal. - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + impl->ProcessAudioOutAndRender(output_buffer, num_frames); } return num_frames_; @@ -490,32 +197,12 @@ private: * @param user_data - Custom data pointer passed along, points to a CubebSinkStream. * @param state - New state of the device. */ - static void StateCallback([[maybe_unused]] cubeb_stream* stream, - [[maybe_unused]] void* user_data, - [[maybe_unused]] cubeb_state state) {} + static void StateCallback(cubeb_stream*, void*, cubeb_state) {} /// Main Cubeb context cubeb* ctx{}; /// Cubeb stream backend cubeb_stream* stream_backend{}; - /// Name of this stream - std::string name{}; - /// Type of this stream - StreamType type; - /// Core system - Core::System& system; - /// Ring buffer of the samples waiting to be played or consumed - Common::RingBuffer<s16, 0x10000> samples_buffer; - /// Audio buffers queued and waiting to play - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; - /// The currently-playing audio buffer - ::AudioCore::Sink::SinkBuffer playing_buffer{}; - /// Audio buffers which have been played and are in queue to be released by the audio system - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; - /// Currently released buffer waiting to be taken by the audio system - ::AudioCore::Sink::SinkBuffer released_buffer{}; - /// The last played (or received) frame of audio, used when the callback underruns - std::array<s16, MaxChannels> last_frame{}; }; CubebSink::CubebSink(std::string_view target_device_name) { @@ -569,15 +256,15 @@ CubebSink::~CubebSink() { #endif } -SinkStream* CubebSink::AcquireSinkStream(Core::System& system, const u32 system_channels, - const std::string& name, const StreamType type) { +SinkStream* CubebSink::AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string& name, StreamType type) { SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<CubebSinkStream>( ctx, device_channels, system_channels, output_device, input_device, name, type, system)); return stream.get(); } -void CubebSink::CloseStream(const SinkStream* stream) { +void CubebSink::CloseStream(SinkStream* stream) { for (size_t i = 0; i < sink_streams.size(); i++) { if (sink_streams[i].get() == stream) { sink_streams[i].reset(); @@ -591,18 +278,6 @@ void CubebSink::CloseStreams() { sink_streams.clear(); } -void CubebSink::PauseStreams() { - for (auto& stream : sink_streams) { - stream->Stop(); - } -} - -void CubebSink::UnpauseStreams() { - for (auto& stream : sink_streams) { - stream->Start(true); - } -} - f32 CubebSink::GetDeviceVolume() const { if (sink_streams.empty()) { return 1.0f; @@ -611,19 +286,19 @@ f32 CubebSink::GetDeviceVolume() const { return sink_streams[0]->GetDeviceVolume(); } -void CubebSink::SetDeviceVolume(const f32 volume) { +void CubebSink::SetDeviceVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetDeviceVolume(volume); } } -void CubebSink::SetSystemVolume(const f32 volume) { +void CubebSink::SetSystemVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetSystemVolume(volume); } } -std::vector<std::string> ListCubebSinkDevices(const bool capture) { +std::vector<std::string> ListCubebSinkDevices(bool capture) { std::vector<std::string> device_list; cubeb* ctx; diff --git a/src/audio_core/sink/cubeb_sink.h b/src/audio_core/sink/cubeb_sink.h index f0f43dfa12..4b0cb160d4 100644 --- a/src/audio_core/sink/cubeb_sink.h +++ b/src/audio_core/sink/cubeb_sink.h @@ -34,8 +34,7 @@ public: * May differ from the device's channel count. * @param name - Name of this stream. * @param type - Type of this stream, render/in/out. - * @param event - Audio render only, a signal used to prevent the renderer running too - * fast. + * * @return A pointer to the created SinkStream */ SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, @@ -46,7 +45,7 @@ public: * * @param stream - The stream to close. */ - void CloseStream(const SinkStream* stream) override; + void CloseStream(SinkStream* stream) override; /** * Close all streams. @@ -54,16 +53,6 @@ public: void CloseStreams() override; /** - * Pause all streams. - */ - void PauseStreams() override; - - /** - * Unpause all streams. - */ - void UnpauseStreams() override; - - /** * Get the device volume. Set from calls to the IAudioDevice service. * * @return Volume of the device. @@ -101,7 +90,7 @@ private: }; /** - * Get a list of conencted devices from Cubeb. + * Get a list of connected devices from Cubeb. * * @param capture - Return input (capture) devices if true, otherwise output devices. */ diff --git a/src/audio_core/sink/null_sink.h b/src/audio_core/sink/null_sink.h index 47a3421719..1215d3cd25 100644 --- a/src/audio_core/sink/null_sink.h +++ b/src/audio_core/sink/null_sink.h @@ -3,10 +3,29 @@ #pragma once +#include <string> +#include <string_view> +#include <vector> + #include "audio_core/sink/sink.h" #include "audio_core/sink/sink_stream.h" +namespace Core { +class System; +} // namespace Core + namespace AudioCore::Sink { +class NullSinkStreamImpl final : public SinkStream { +public: + explicit NullSinkStreamImpl(Core::System& system_, StreamType type_) + : SinkStream{system_, type_} {} + ~NullSinkStreamImpl() override {} + void AppendBuffer(SinkBuffer&, std::vector<s16>&) override {} + std::vector<s16> ReleaseBuffer(u64) override { + return {}; + } +}; + /** * A no-op sink for when no audio out is wanted. */ @@ -15,17 +34,16 @@ public: explicit NullSink(std::string_view) {} ~NullSink() override = default; - SinkStream* AcquireSinkStream([[maybe_unused]] Core::System& system, - [[maybe_unused]] u32 system_channels, - [[maybe_unused]] const std::string& name, - [[maybe_unused]] StreamType type) override { - return &null_sink_stream; + SinkStream* AcquireSinkStream(Core::System& system, u32, const std::string&, + StreamType type) override { + if (null_sink == nullptr) { + null_sink = std::make_unique<NullSinkStreamImpl>(system, type); + } + return null_sink.get(); } - void CloseStream([[maybe_unused]] const SinkStream* stream) override {} + void CloseStream(SinkStream*) override {} void CloseStreams() override {} - void PauseStreams() override {} - void UnpauseStreams() override {} f32 GetDeviceVolume() const override { return 1.0f; } @@ -33,20 +51,7 @@ public: void SetSystemVolume(f32 volume) override {} private: - struct NullSinkStreamImpl final : SinkStream { - void Finalize() override {} - void Start(bool resume = false) override {} - void Stop() override {} - void AppendBuffer([[maybe_unused]] ::AudioCore::Sink::SinkBuffer& buffer, - [[maybe_unused]] std::vector<s16>& samples) override {} - std::vector<s16> ReleaseBuffer([[maybe_unused]] u64 num_samples) override { - return {}; - } - bool IsBufferConsumed([[maybe_unused]] const u64 tag) { - return true; - } - void ClearQueue() override {} - } null_sink_stream; + SinkStreamPtr null_sink{}; }; } // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sdl2_sink.cpp b/src/audio_core/sink/sdl2_sink.cpp index d6c9ec90dd..1bd001b941 100644 --- a/src/audio_core/sink/sdl2_sink.cpp +++ b/src/audio_core/sink/sdl2_sink.cpp @@ -1,20 +1,13 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later -#include <algorithm> -#include <atomic> +#include <span> +#include <vector> -#include "audio_core/audio_core.h" -#include "audio_core/audio_event.h" -#include "audio_core/audio_manager.h" +#include "audio_core/common/common.h" #include "audio_core/sink/sdl2_sink.h" #include "audio_core/sink/sink_stream.h" -#include "common/assert.h" -#include "common/fixed_point.h" #include "common/logging/log.h" -#include "common/reader_writer_queue.h" -#include "common/ring_buffer.h" -#include "common/settings.h" #include "core/core.h" // Ignore -Wimplicit-fallthrough due to https://github.com/libsdl-org/SDL/issues/4307 @@ -44,10 +37,9 @@ public: * @param system_ - Core system. * @param event - Event used only for audio renderer, signalled on buffer consume. */ - SDLSinkStream(u32 device_channels_, const u32 system_channels_, - const std::string& output_device, const std::string& input_device, - const StreamType type_, Core::System& system_) - : type{type_}, system{system_} { + SDLSinkStream(u32 device_channels_, u32 system_channels_, const std::string& output_device, + const std::string& input_device, StreamType type_, Core::System& system_) + : SinkStream{system_, type_} { system_channels = system_channels_; device_channels = device_channels_; @@ -63,8 +55,6 @@ public: spec.callback = &SDLSinkStream::DataCallback; spec.userdata = this; - playing_buffer.consumed = true; - std::string device_name{output_device}; bool capture{false}; if (type == StreamType::In) { @@ -84,31 +74,30 @@ public: return; } - LOG_DEBUG(Service_Audio, - "Opening sdl stream {} with: rate {} channels {} (system channels {}) " - " samples {}", - device, obtained.freq, obtained.channels, system_channels, obtained.samples); + LOG_INFO(Service_Audio, + "Opening SDL stream {} with: rate {} channels {} (system channels {}) " + " samples {}", + device, obtained.freq, obtained.channels, system_channels, obtained.samples); } /** * Destroy the sink stream. */ ~SDLSinkStream() override { - if (device == 0) { - return; - } - - SDL_CloseAudioDevice(device); + LOG_DEBUG(Service_Audio, "Destructing SDL stream {}", name); + Finalize(); } /** * Finalize the sink stream. */ void Finalize() override { + Unstall(); if (device == 0) { return; } + Stop(); SDL_CloseAudioDevice(device); } @@ -118,217 +107,29 @@ public: * @param resume - Set to true if this is resuming the stream a previously-active stream. * Default false. */ - void Start(const bool resume = false) override { - if (device == 0) { + void Start(bool resume = false) override { + if (device == 0 || !paused) { return; } - if (resume && was_playing) { - SDL_PauseAudioDevice(device, 0); - paused = false; - } else if (!resume) { - SDL_PauseAudioDevice(device, 0); - paused = false; - } + paused = false; + SDL_PauseAudioDevice(device, 0); } /** * Stop the sink stream. */ - void Stop() { - if (device == 0) { + void Stop() override { + Unstall(); + if (device == 0 || paused) { return; } - SDL_PauseAudioDevice(device, 1); paused = true; - } - - /** - * Append a new buffer and its samples to a waiting queue to play. - * - * @param buffer - Audio buffer information to be queued. - * @param samples - The s16 samples to be queue for playback. - */ - void AppendBuffer(::AudioCore::Sink::SinkBuffer& buffer, std::vector<s16>& samples) override { - if (type == StreamType::In) { - queue.enqueue(buffer); - queued_buffers++; - } else { - constexpr s32 min = std::numeric_limits<s16>::min(); - constexpr s32 max = std::numeric_limits<s16>::max(); - - auto yuzu_volume{Settings::Volume()}; - auto volume{system_volume * device_volume * yuzu_volume}; - - if (system_channels == 6 && device_channels == 2) { - // We're given 6 channels, but our device only outputs 2, so downmix. - constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * - down_mix_coeff[0] + - samples[read_index + static_cast<u32>(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast<u32>(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast<u32>(Channels::BackLeft)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - const auto right_sample{ - ((Common::FixedPoint<49, 15>( - samples[read_index + static_cast<u32>(Channels::FrontRight)]) * - down_mix_coeff[0] + - samples[read_index + static_cast<u32>(Channels::Center)] * - down_mix_coeff[1] + - samples[read_index + static_cast<u32>(Channels::LFE)] * - down_mix_coeff[2] + - samples[read_index + static_cast<u32>(Channels::BackRight)] * - down_mix_coeff[3]) * - volume) - .to_int()}; - - samples[write_index + static_cast<u32>(Channels::FrontLeft)] = - static_cast<s16>(std::clamp(left_sample, min, max)); - samples[write_index + static_cast<u32>(Channels::FrontRight)] = - static_cast<s16>(std::clamp(right_sample, min, max)); - } - - samples.resize(samples.size() / system_channels * device_channels); - - } else if (system_channels == 2 && device_channels == 6) { - // We need moar samples! Not all games will provide 6 channel audio. - // TODO: Implement some upmixing here. Currently just passthrough, with other - // channels left as silence. - std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); - - for (u32 read_index = 0, write_index = 0; read_index < samples.size(); - read_index += system_channels, write_index += device_channels) { - const auto left_sample{static_cast<s16>(std::clamp( - static_cast<s32>( - static_cast<f32>( - samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; - - const auto right_sample{static_cast<s16>(std::clamp( - static_cast<s32>( - static_cast<f32>( - samples[read_index + static_cast<u32>(Channels::FrontRight)]) * - volume), - min, max))}; - - new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = - right_sample; - } - samples = std::move(new_samples); - - } else if (volume != 1.0f) { - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast<s16>(std::clamp( - static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); - } - } - - samples_buffer.Push(samples); - queue.enqueue(buffer); - queued_buffers++; - } - } - - /** - * Release a buffer. Audio In only, will fill a buffer with recorded samples. - * - * @param num_samples - Maximum number of samples to receive. - * @return Vector of recorded samples. May have fewer than num_samples. - */ - std::vector<s16> ReleaseBuffer(const u64 num_samples) override { - static constexpr s32 min = std::numeric_limits<s16>::min(); - static constexpr s32 max = std::numeric_limits<s16>::max(); - - auto samples{samples_buffer.Pop(num_samples)}; - - // TODO: Up-mix to 6 channels if the game expects it. - // For audio input this is unlikely to ever be the case though. - - // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. - // TODO: Play with this and find something that works better. - auto volume{system_volume * device_volume * 8}; - for (u32 i = 0; i < samples.size(); i++) { - samples[i] = static_cast<s16>( - std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); - } - - if (samples.size() < num_samples) { - samples.resize(num_samples, 0); - } - return samples; - } - - /** - * Check if a certain buffer has been consumed (fully played). - * - * @param tag - Unique tag of a buffer to check for. - * @return True if the buffer has been played, otherwise false. - */ - bool IsBufferConsumed(const u64 tag) override { - if (released_buffer.tag == 0) { - if (!released_buffers.try_dequeue(released_buffer)) { - return false; - } - } - - if (released_buffer.tag == tag) { - released_buffer.tag = 0; - return true; - } - return false; - } - - /** - * Empty out the buffer queue. - */ - void ClearQueue() override { - samples_buffer.Pop(); - while (queue.pop()) { - } - while (released_buffers.pop()) { - } - released_buffer = {}; - playing_buffer = {}; - playing_buffer.consumed = true; - queued_buffers = 0; + SDL_PauseAudioDevice(device, 1); } private: /** - * Signal events back to the audio system that a buffer was played/can be filled. - * - * @param buffer - Consumed audio buffer to be released. - */ - void SignalEvent(const ::AudioCore::Sink::SinkBuffer& buffer) { - auto& manager{system.AudioCore().GetAudioManager()}; - switch (type) { - case StreamType::Out: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioOutManager, true); - break; - case StreamType::In: - released_buffers.enqueue(buffer); - manager.SetEvent(Event::Type::AudioInManager, true); - break; - case StreamType::Render: - break; - } - } - - /** * Main callback from SDL. Either expects samples from us (audio render/audio out), or will * provide samples to be copied (audio in). * @@ -345,122 +146,20 @@ private: const std::size_t num_channels = impl->GetDeviceChannels(); const std::size_t frame_size = num_channels; - const std::size_t frame_size_bytes = frame_size * sizeof(s16); const std::size_t num_frames{len / num_channels / sizeof(s16)}; - size_t frames_written{0}; - [[maybe_unused]] bool underrun{false}; if (impl->type == StreamType::In) { - std::span<s16> input_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, just push the samples and - // continue. - underrun = true; - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - (num_frames - frames_written) * frame_size); - frames_written = num_frames; - continue; - } else { - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Push(&input_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &input_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + std::span<const s16> input_buffer{reinterpret_cast<const s16*>(stream), + num_frames * frame_size}; + impl->ProcessAudioIn(input_buffer, num_frames); } else { std::span<s16> output_buffer{reinterpret_cast<s16*>(stream), num_frames * frame_size}; - - while (frames_written < num_frames) { - auto& playing_buffer{impl->playing_buffer}; - - // If the playing buffer has been consumed or has no frames, we need a new one - if (playing_buffer.consumed || playing_buffer.frames == 0) { - if (!impl->queue.try_dequeue(impl->playing_buffer)) { - // If no buffer was available we've underrun, fill the remaining buffer with - // the last written frame and continue. - underrun = true; - for (size_t i = frames_written; i < num_frames; i++) { - std::memcpy(&output_buffer[i * frame_size], &impl->last_frame[0], - frame_size_bytes); - } - frames_written = num_frames; - continue; - } else { - impl->queued_buffers--; - impl->SignalEvent(impl->playing_buffer); - } - } - - // Get the minimum frames available between the currently playing buffer, and the - // amount we have left to fill - size_t frames_available{ - std::min(playing_buffer.frames - playing_buffer.frames_played, - num_frames - frames_written)}; - - impl->samples_buffer.Pop(&output_buffer[frames_written * frame_size], - frames_available * frame_size); - - frames_written += frames_available; - playing_buffer.frames_played += frames_available; - - // If that's all the frames in the current buffer, add its samples and mark it as - // consumed - if (playing_buffer.frames_played >= playing_buffer.frames) { - impl->AddPlayedSampleCount(playing_buffer.frames_played * num_channels); - impl->playing_buffer.consumed = true; - } - } - - std::memcpy(&impl->last_frame[0], &output_buffer[(frames_written - 1) * frame_size], - frame_size_bytes); + impl->ProcessAudioOutAndRender(output_buffer, num_frames); } } /// SDL device id of the opened input/output device SDL_AudioDeviceID device{}; - /// Type of this stream - StreamType type; - /// Core system - Core::System& system; - /// Ring buffer of the samples waiting to be played or consumed - Common::RingBuffer<s16, 0x10000> samples_buffer; - /// Audio buffers queued and waiting to play - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> queue; - /// The currently-playing audio buffer - ::AudioCore::Sink::SinkBuffer playing_buffer{}; - /// Audio buffers which have been played and are in queue to be released by the audio system - Common::ReaderWriterQueue<::AudioCore::Sink::SinkBuffer> released_buffers{}; - /// Currently released buffer waiting to be taken by the audio system - ::AudioCore::Sink::SinkBuffer released_buffer{}; - /// The last played (or received) frame of audio, used when the callback underruns - std::array<s16, MaxChannels> last_frame{}; }; SDLSink::SDLSink(std::string_view target_device_name) { @@ -482,14 +181,14 @@ SDLSink::SDLSink(std::string_view target_device_name) { SDLSink::~SDLSink() = default; -SinkStream* SDLSink::AcquireSinkStream(Core::System& system, const u32 system_channels, - const std::string&, const StreamType type) { +SinkStream* SDLSink::AcquireSinkStream(Core::System& system, u32 system_channels, + const std::string&, StreamType type) { SinkStreamPtr& stream = sink_streams.emplace_back(std::make_unique<SDLSinkStream>( device_channels, system_channels, output_device, input_device, type, system)); return stream.get(); } -void SDLSink::CloseStream(const SinkStream* stream) { +void SDLSink::CloseStream(SinkStream* stream) { for (size_t i = 0; i < sink_streams.size(); i++) { if (sink_streams[i].get() == stream) { sink_streams[i].reset(); @@ -503,18 +202,6 @@ void SDLSink::CloseStreams() { sink_streams.clear(); } -void SDLSink::PauseStreams() { - for (auto& stream : sink_streams) { - stream->Stop(); - } -} - -void SDLSink::UnpauseStreams() { - for (auto& stream : sink_streams) { - stream->Start(); - } -} - f32 SDLSink::GetDeviceVolume() const { if (sink_streams.empty()) { return 1.0f; @@ -523,19 +210,19 @@ f32 SDLSink::GetDeviceVolume() const { return sink_streams[0]->GetDeviceVolume(); } -void SDLSink::SetDeviceVolume(const f32 volume) { +void SDLSink::SetDeviceVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetDeviceVolume(volume); } } -void SDLSink::SetSystemVolume(const f32 volume) { +void SDLSink::SetSystemVolume(f32 volume) { for (auto& stream : sink_streams) { stream->SetSystemVolume(volume); } } -std::vector<std::string> ListSDLSinkDevices(const bool capture) { +std::vector<std::string> ListSDLSinkDevices(bool capture) { std::vector<std::string> device_list; 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 186bc2fa3a..f01eddc1b4 100644 --- a/src/audio_core/sink/sdl2_sink.h +++ b/src/audio_core/sink/sdl2_sink.h @@ -32,8 +32,7 @@ public: * May differ from the device's channel count. * @param name - Name of this stream. * @param type - Type of this stream, render/in/out. - * @param event - Audio render only, a signal used to prevent the renderer running too - * fast. + * * @return A pointer to the created SinkStream */ SinkStream* AcquireSinkStream(Core::System& system, u32 system_channels, @@ -44,7 +43,7 @@ public: * * @param stream - The stream to close. */ - void CloseStream(const SinkStream* stream) override; + void CloseStream(SinkStream* stream) override; /** * Close all streams. @@ -52,16 +51,6 @@ public: void CloseStreams() override; /** - * Pause all streams. - */ - void PauseStreams() override; - - /** - * Unpause all streams. - */ - void UnpauseStreams() override; - - /** * Get the device volume. Set from calls to the IAudioDevice service. * * @return Volume of the device. @@ -92,7 +81,7 @@ private: }; /** - * Get a list of conencted devices from Cubeb. + * Get a list of connected devices from SDL. * * @param capture - Return input (capture) devices if true, otherwise output devices. */ diff --git a/src/audio_core/sink/sink.h b/src/audio_core/sink/sink.h index 91fe455e48..f28c6d1268 100644 --- a/src/audio_core/sink/sink.h +++ b/src/audio_core/sink/sink.h @@ -32,7 +32,7 @@ public: * * @param stream - The stream to close. */ - virtual void CloseStream(const SinkStream* stream) = 0; + virtual void CloseStream(SinkStream* stream) = 0; /** * Close all streams. @@ -40,16 +40,6 @@ public: virtual void CloseStreams() = 0; /** - * Pause all streams. - */ - virtual void PauseStreams() = 0; - - /** - * Unpause all streams. - */ - virtual void UnpauseStreams() = 0; - - /** * Create a new sink stream, kept within this sink, with a pointer returned for use. * Do not free the returned pointer. When done with the stream, call CloseStream on the sink. * @@ -58,8 +48,7 @@ public: * May differ from the device's channel count. * @param name - Name of this stream. * @param type - Type of this stream, render/in/out. - * @param event - Audio render only, a signal used to prevent the renderer running too - * fast. + * * @return A pointer to the created SinkStream */ 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 253c0fd1e5..67bdab7799 100644 --- a/src/audio_core/sink/sink_details.cpp +++ b/src/audio_core/sink/sink_details.cpp @@ -5,7 +5,7 @@ #include <memory> #include <string> #include <vector> -#include "audio_core/sink/null_sink.h" + #include "audio_core/sink/sink_details.h" #ifdef HAVE_CUBEB #include "audio_core/sink/cubeb_sink.h" @@ -13,6 +13,7 @@ #ifdef HAVE_SDL2 #include "audio_core/sink/sdl2_sink.h" #endif +#include "audio_core/sink/null_sink.h" #include "common/logging/log.h" namespace AudioCore::Sink { @@ -59,8 +60,7 @@ const SinkDetails& GetOutputSinkDetails(std::string_view sink_id) { if (sink_id == "auto" || iter == std::end(sink_details)) { if (sink_id != "auto") { - LOG_ERROR(Audio, "AudioCore::Sink::GetOutputSinkDetails given invalid sink_id {}", - sink_id); + LOG_ERROR(Audio, "Invalid sink_id {}", sink_id); } // Auto-select. // 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 0000000000..37fe725e41 --- /dev/null +++ b/src/audio_core/sink/sink_stream.cpp @@ -0,0 +1,279 @@ +// SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project +// SPDX-License-Identifier: GPL-2.0-or-later + +#include <array> +#include <atomic> +#include <memory> +#include <span> +#include <vector> + +#include "audio_core/audio_core.h" +#include "audio_core/common/common.h" +#include "audio_core/sink/sink_stream.h" +#include "common/common_types.h" +#include "common/fixed_point.h" +#include "common/settings.h" +#include "core/core.h" + +namespace AudioCore::Sink { + +void SinkStream::AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) { + if (type == StreamType::In) { + queue.enqueue(buffer); + queued_buffers++; + return; + } + + constexpr s32 min{std::numeric_limits<s16>::min()}; + constexpr s32 max{std::numeric_limits<s16>::max()}; + + auto yuzu_volume{Settings::Volume()}; + if (yuzu_volume > 1.0f) { + yuzu_volume = 0.6f + 20 * std::log10(yuzu_volume); + } + auto volume{system_volume * device_volume * yuzu_volume}; + + if (system_channels == 6 && device_channels == 2) { + // We're given 6 channels, but our device only outputs 2, so downmix. + constexpr std::array<f32, 4> down_mix_coeff{1.0f, 0.707f, 0.251f, 0.707f}; + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * + down_mix_coeff[0] + + samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + + samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + + samples[read_index + static_cast<u32>(Channels::BackLeft)] * down_mix_coeff[3]) * + volume) + .to_int()}; + + const auto right_sample{ + ((Common::FixedPoint<49, 15>( + samples[read_index + static_cast<u32>(Channels::FrontRight)]) * + down_mix_coeff[0] + + samples[read_index + static_cast<u32>(Channels::Center)] * down_mix_coeff[1] + + samples[read_index + static_cast<u32>(Channels::LFE)] * down_mix_coeff[2] + + samples[read_index + static_cast<u32>(Channels::BackRight)] * down_mix_coeff[3]) * + volume) + .to_int()}; + + samples[write_index + static_cast<u32>(Channels::FrontLeft)] = + static_cast<s16>(std::clamp(left_sample, min, max)); + samples[write_index + static_cast<u32>(Channels::FrontRight)] = + static_cast<s16>(std::clamp(right_sample, min, max)); + } + + samples.resize(samples.size() / system_channels * device_channels); + + } else if (system_channels == 2 && device_channels == 6) { + // We need moar samples! Not all games will provide 6 channel audio. + // TODO: Implement some upmixing here. Currently just passthrough, with other + // channels left as silence. + std::vector<s16> new_samples(samples.size() / system_channels * device_channels, 0); + + for (u32 read_index = 0, write_index = 0; read_index < samples.size(); + read_index += system_channels, write_index += device_channels) { + const auto left_sample{static_cast<s16>(std::clamp( + static_cast<s32>( + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontLeft)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast<u32>(Channels::FrontLeft)] = left_sample; + + const auto right_sample{static_cast<s16>(std::clamp( + static_cast<s32>( + static_cast<f32>(samples[read_index + static_cast<u32>(Channels::FrontRight)]) * + volume), + min, max))}; + + new_samples[write_index + static_cast<u32>(Channels::FrontRight)] = right_sample; + } + samples = std::move(new_samples); + + } else if (volume != 1.0f) { + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast<s16>( + std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); + } + } + + samples_buffer.Push(samples); + queue.enqueue(buffer); + queued_buffers++; +} + +std::vector<s16> SinkStream::ReleaseBuffer(u64 num_samples) { + constexpr s32 min = std::numeric_limits<s16>::min(); + constexpr s32 max = std::numeric_limits<s16>::max(); + + auto samples{samples_buffer.Pop(num_samples)}; + + // TODO: Up-mix to 6 channels if the game expects it. + // For audio input this is unlikely to ever be the case though. + + // Incoming mic volume seems to always be very quiet, so multiply by an additional 8 here. + // TODO: Play with this and find something that works better. + auto volume{system_volume * device_volume * 8}; + for (u32 i = 0; i < samples.size(); i++) { + samples[i] = static_cast<s16>( + std::clamp(static_cast<s32>(static_cast<f32>(samples[i]) * volume), min, max)); + } + + if (samples.size() < num_samples) { + samples.resize(num_samples, 0); + } + return samples; +} + +void SinkStream::ClearQueue() { + samples_buffer.Pop(); + while (queue.pop()) { + } + queued_buffers = 0; + playing_buffer = {}; + playing_buffer.consumed = true; +} + +void SinkStream::ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames) { + const std::size_t num_channels = GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + size_t frames_written{0}; + + // If we're paused or going to shut down, we don't want to consume buffers as coretiming is + // paused and we'll desync, so just return. + if (system.IsPaused() || system.IsShuttingDown()) { + return; + } + + if (queued_buffers > max_queue_size) { + Stall(); + } + + while (frames_written < num_frames) { + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!queue.try_dequeue(playing_buffer)) { + // If no buffer was available we've underrun, just push the samples and + // continue. + samples_buffer.Push(&input_buffer[frames_written * frame_size], + (num_frames - frames_written) * frame_size); + frames_written = num_frames; + continue; + } + // Successfully dequeued a new buffer. + queued_buffers--; + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + samples_buffer.Push(&input_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + playing_buffer.consumed = true; + } + } + + std::memcpy(&last_frame[0], &input_buffer[(frames_written - 1) * frame_size], frame_size_bytes); + + if (queued_buffers <= max_queue_size) { + Unstall(); + } +} + +void SinkStream::ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames) { + const std::size_t num_channels = GetDeviceChannels(); + const std::size_t frame_size = num_channels; + const std::size_t frame_size_bytes = frame_size * sizeof(s16); + size_t frames_written{0}; + + // If we're paused or going to shut down, we don't want to consume buffers as coretiming is + // paused and we'll desync, so just play silence. + if (system.IsPaused() || system.IsShuttingDown()) { + constexpr std::array<s16, 6> silence{}; + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &silence[0], frame_size_bytes); + } + return; + } + + // Due to many frames being queued up with nvdec (5 frames or so?), a lot of buffers also get + // queued up (30+) but not all at once, which causes constant stalling here, so just let the + // video play out without attempting to stall. + // Can hopefully remove this later with a more complete NVDEC implementation. + const auto nvdec_active{system.AudioCore().IsNVDECActive()}; + if (!nvdec_active && queued_buffers > max_queue_size) { + Stall(); + } + + while (frames_written < num_frames) { + // If the playing buffer has been consumed or has no frames, we need a new one + if (playing_buffer.consumed || playing_buffer.frames == 0) { + if (!queue.try_dequeue(playing_buffer)) { + // If no buffer was available we've underrun, fill the remaining buffer with + // the last written frame and continue. + for (size_t i = frames_written; i < num_frames; i++) { + std::memcpy(&output_buffer[i * frame_size], &last_frame[0], frame_size_bytes); + } + frames_written = num_frames; + continue; + } + // Successfully dequeued a new buffer. + queued_buffers--; + } + + // Get the minimum frames available between the currently playing buffer, and the + // amount we have left to fill + size_t frames_available{std::min(playing_buffer.frames - playing_buffer.frames_played, + num_frames - frames_written)}; + + samples_buffer.Pop(&output_buffer[frames_written * frame_size], + frames_available * frame_size); + + frames_written += frames_available; + playing_buffer.frames_played += frames_available; + + // If that's all the frames in the current buffer, add its samples and mark it as + // consumed + if (playing_buffer.frames_played >= playing_buffer.frames) { + playing_buffer.consumed = true; + } + } + + std::memcpy(&last_frame[0], &output_buffer[(frames_written - 1) * frame_size], + frame_size_bytes); + + if (stalled && queued_buffers <= max_queue_size) { + Unstall(); + } +} + +void SinkStream::Stall() { + if (stalled) { + return; + } + stalled = true; + system.StallProcesses(); +} + +void SinkStream::Unstall() { + if (!stalled) { + return; + } + system.UnstallProcesses(); + stalled = false; +} + +} // namespace AudioCore::Sink diff --git a/src/audio_core/sink/sink_stream.h b/src/audio_core/sink/sink_stream.h index 17ed6593fb..9366ebbd37 100644 --- a/src/audio_core/sink/sink_stream.h +++ b/src/audio_core/sink/sink_stream.h @@ -3,12 +3,20 @@ #pragma once +#include <array> #include <atomic> #include <memory> +#include <span> #include <vector> #include "audio_core/common/common.h" #include "common/common_types.h" +#include "common/reader_writer_queue.h" +#include "common/ring_buffer.h" + +namespace Core { +class System; +} // namespace Core namespace AudioCore::Sink { @@ -34,20 +42,24 @@ struct SinkBuffer { * You should regularly call IsBufferConsumed with the unique SinkBuffer tag to check if the buffer * has been consumed. * - * Since these are a FIFO queue, always check IsBufferConsumed in the same order you appended the - * buffers, skipping a buffer will result in all following buffers to never release. + * Since these are a FIFO queue, IsBufferConsumed must be checked in the same order buffers were + * appended, skipping a buffer will result in the queue getting stuck, and all following buffers to + * never release. * * If the buffers appear to be stuck, you can stop and re-open an IAudioIn/IAudioOut service (this * is what games do), or call ClearQueue to flush all of the buffers without a full restart. */ class SinkStream { public: - virtual ~SinkStream() = default; + explicit SinkStream(Core::System& system_, StreamType type_) : system{system_}, type{type_} {} + virtual ~SinkStream() { + Unstall(); + } /** * Finalize the sink stream. */ - virtual void Finalize() = 0; + virtual void Finalize() {} /** * Start the sink stream. @@ -55,48 +67,19 @@ public: * @param resume - Set to true if this is resuming the stream a previously-active stream. * Default false. */ - virtual void Start(bool resume = false) = 0; + virtual void Start(bool resume = false) {} /** * Stop the sink stream. */ - virtual void Stop() = 0; - - /** - * Append a new buffer and its samples to a waiting queue to play. - * - * @param buffer - Audio buffer information to be queued. - * @param samples - The s16 samples to be queue for playback. - */ - virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples) = 0; - - /** - * Release a buffer. Audio In only, will fill a buffer with recorded samples. - * - * @param num_samples - Maximum number of samples to receive. - * @return Vector of recorded samples. May have fewer than num_samples. - */ - virtual std::vector<s16> ReleaseBuffer(u64 num_samples) = 0; - - /** - * Check if a certain buffer has been consumed (fully played). - * - * @param tag - Unique tag of a buffer to check for. - * @return True if the buffer has been played, otherwise false. - */ - virtual bool IsBufferConsumed(u64 tag) = 0; - - /** - * Empty out the buffer queue. - */ - virtual void ClearQueue() = 0; + virtual void Stop() {} /** * Check if the stream is paused. * * @return True if paused, otherwise false. */ - bool IsPaused() { + bool IsPaused() const { return paused; } @@ -128,34 +111,6 @@ public: } /** - * Get the total number of samples played by this stream. - * - * @return Number of samples played. - */ - u64 GetPlayedSampleCount() const { - return played_sample_count; - } - - /** - * Set the number of samples played. - * This is started and stopped on system start/stop. - * - * @param played_sample_count_ - Number of samples to set. - */ - void SetPlayedSampleCount(u64 played_sample_count_) { - played_sample_count = played_sample_count_; - } - - /** - * Add to the played sample count. - * - * @param num_samples - Number of samples to add. - */ - void AddPlayedSampleCount(u64 num_samples) { - played_sample_count += num_samples; - } - - /** * Get the system volume. * * @return The current system volume. @@ -200,23 +155,93 @@ public: return queued_buffers.load(); } + /** + * Set the maximum buffer queue size. + */ + void SetRingSize(u32 ring_size) { + max_queue_size = ring_size; + } + + /** + * Append a new buffer and its samples to a waiting queue to play. + * + * @param buffer - Audio buffer information to be queued. + * @param samples - The s16 samples to be queue for playback. + */ + virtual void AppendBuffer(SinkBuffer& buffer, std::vector<s16>& samples); + + /** + * Release a buffer. Audio In only, will fill a buffer with recorded samples. + * + * @param num_samples - Maximum number of samples to receive. + * @return Vector of recorded samples. May have fewer than num_samples. + */ + virtual std::vector<s16> ReleaseBuffer(u64 num_samples); + + /** + * Empty out the buffer queue. + */ + void ClearQueue(); + + /** + * Callback for AudioIn. + * + * @param input_buffer - Input buffer to be filled with samples. + * @param num_frames - Number of frames to be filled. + */ + void ProcessAudioIn(std::span<const s16> input_buffer, std::size_t num_frames); + + /** + * Callback for AudioOut and AudioRenderer. + * + * @param output_buffer - Output buffer to be filled with samples. + * @param num_frames - Number of frames to be filled. + */ + void ProcessAudioOutAndRender(std::span<s16> output_buffer, std::size_t num_frames); + + /** + * Stall core processes if the audio thread falls too far behind. + */ + void Stall(); + + /** + * Unstall core processes. + */ + void Unstall(); + protected: - /// Number of buffers waiting to be played - std::atomic<u32> queued_buffers{}; - /// Total samples played by this stream - std::atomic<u64> played_sample_count{}; + /// Core system + Core::System& system; + /// Type of this stream + StreamType type; /// Set by the audio render/in/out system which uses this stream - f32 system_volume{1.0f}; - /// Set via IAudioDevice service calls - f32 device_volume{1.0f}; - /// Set by the audio render/in/out systen which uses this stream u32 system_channels{2}; /// Channels supported by hardware u32 device_channels{2}; /// Is this stream currently paused? std::atomic<bool> paused{true}; - /// Was this stream previously playing? - std::atomic<bool> was_playing{false}; + /// Name of this stream + std::string name{}; + +private: + /// Ring buffer of the samples waiting to be played or consumed + Common::RingBuffer<s16, 0x10000> samples_buffer; + /// Audio buffers queued and waiting to play + Common::ReaderWriterQueue<SinkBuffer> queue; + /// The currently-playing audio buffer + SinkBuffer playing_buffer{}; + /// The last played (or received) frame of audio, used when the callback underruns + std::array<s16, MaxChannels> last_frame{}; + /// Number of buffers waiting to be played + std::atomic<u32> queued_buffers{}; + /// The ring size for audio out buffers (usually 4, rarely 2 or 8) + u32 max_queue_size{}; + /// Set by the audio render/in/out system which uses this stream + f32 system_volume{1.0f}; + /// Set via IAudioDevice service calls + f32 device_volume{1.0f}; + /// True if coretiming has been stalled + bool stalled{false}; }; using SinkStreamPtr = std::unique_ptr<SinkStream>; diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 635fb85c89..b1e0ba6cc7 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -166,6 +166,7 @@ if(ARCHITECTURE_x86_64) x64/xbyak_abi.h x64/xbyak_util.h ) + target_link_libraries(common PRIVATE xbyak) endif() if (MSVC) @@ -189,7 +190,7 @@ endif() create_target_directory_groups(common) target_link_libraries(common PUBLIC ${Boost_LIBRARIES} fmt::fmt microprofile Threads::Threads) -target_link_libraries(common PRIVATE lz4::lz4 xbyak) +target_link_libraries(common PRIVATE lz4::lz4) if (TARGET zstd::zstd) target_link_libraries(common PRIVATE zstd::zstd) else() diff --git a/src/common/input.h b/src/common/input.h index 213aa2384d..825b0d650d 100644 --- a/src/common/input.h +++ b/src/common/input.h @@ -102,6 +102,8 @@ struct AnalogProperties { float offset{}; // Invert direction of the sensor data bool inverted{}; + // Press once to activate, press again to release + bool toggle{}; }; // Single analog sensor data @@ -115,8 +117,11 @@ struct AnalogStatus { struct ButtonStatus { Common::UUID uuid{}; bool value{}; + // Invert value of the button bool inverted{}; + // Press once to activate, press again to release bool toggle{}; + // Internal lock for the toggle status bool locked{}; }; diff --git a/src/common/settings.cpp b/src/common/settings.cpp index 7282a45d32..0a560ebb72 100644 --- a/src/common/settings.cpp +++ b/src/common/settings.cpp @@ -195,6 +195,7 @@ void RestoreGlobalState(bool is_powered_on) { values.shader_backend.SetGlobal(true); values.use_asynchronous_shaders.SetGlobal(true); values.use_fast_gpu_time.SetGlobal(true); + values.use_pessimistic_flushes.SetGlobal(true); values.bg_red.SetGlobal(true); values.bg_green.SetGlobal(true); values.bg_blue.SetGlobal(true); diff --git a/src/common/settings.h b/src/common/settings.h index 8354fdba76..851812f28b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -446,6 +446,7 @@ struct Values { ShaderBackend::SPIRV, "shader_backend"}; SwitchableSetting<bool> use_asynchronous_shaders{false, "use_asynchronous_shaders"}; SwitchableSetting<bool> use_fast_gpu_time{true, "use_fast_gpu_time"}; + SwitchableSetting<bool> use_pessimistic_flushes{false, "use_pessimistic_flushes"}; SwitchableSetting<u8> bg_red{0, "bg_red"}; SwitchableSetting<u8> bg_green{0, "bg_green"}; diff --git a/src/core/core.cpp b/src/core/core.cpp index e651ce100e..1210928682 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -141,8 +141,6 @@ struct System::Impl { core_timing.SyncPause(false); is_paused = false; - audio_core->PauseSinks(false); - return status; } @@ -150,8 +148,6 @@ struct System::Impl { std::unique_lock<std::mutex> lk(suspend_guard); status = SystemResultStatus::Success; - audio_core->PauseSinks(true); - core_timing.SyncPause(true); kernel.Suspend(true); is_paused = true; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 2dbb99c8b0..5375a5d592 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -73,7 +73,6 @@ void CoreTiming::Shutdown() { if (timer_thread) { timer_thread->join(); } - pause_callbacks.clear(); ClearPendingEvents(); timer_thread.reset(); has_started = false; @@ -86,10 +85,6 @@ void CoreTiming::Pause(bool is_paused) { if (!is_paused) { pause_end_time = GetGlobalTimeNs().count(); } - - for (auto& cb : pause_callbacks) { - cb(is_paused); - } } void CoreTiming::SyncPause(bool is_paused) { @@ -110,10 +105,6 @@ void CoreTiming::SyncPause(bool is_paused) { if (!is_paused) { pause_end_time = GetGlobalTimeNs().count(); } - - for (auto& cb : pause_callbacks) { - cb(is_paused); - } } bool CoreTiming::IsRunning() const { @@ -219,11 +210,6 @@ void CoreTiming::RemoveEvent(const std::shared_ptr<EventType>& event_type) { } } -void CoreTiming::RegisterPauseCallback(PauseCallback&& callback) { - std::scoped_lock lock{basic_lock}; - pause_callbacks.emplace_back(std::move(callback)); -} - std::optional<s64> CoreTiming::Advance() { std::scoped_lock lock{advance_lock, basic_lock}; global_timer = GetGlobalTimeNs().count(); diff --git a/src/core/core_timing.h b/src/core/core_timing.h index 6aa3ae923d..3259397b28 100644 --- a/src/core/core_timing.h +++ b/src/core/core_timing.h @@ -22,7 +22,6 @@ namespace Core::Timing { /// A callback that may be scheduled for a particular core timing event. using TimedCallback = std::function<std::optional<std::chrono::nanoseconds>( std::uintptr_t user_data, s64 time, std::chrono::nanoseconds ns_late)>; -using PauseCallback = std::function<void(bool paused)>; /// Contains the characteristics of a particular event. struct EventType { @@ -134,9 +133,6 @@ public: /// Checks for events manually and returns time in nanoseconds for next event, threadsafe. std::optional<s64> Advance(); - /// Register a callback function to be called when coretiming pauses. - void RegisterPauseCallback(PauseCallback&& callback); - private: struct Event; @@ -176,8 +172,6 @@ private: /// Cycle timing u64 ticks{}; s64 downcount{}; - - std::vector<PauseCallback> pause_callbacks{}; }; /// 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 f9f902c2dd..01c43be934 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 return; } + // GC controllers have triggers not buttons + if (npad_type == NpadStyleIndex::GameCube) { + if (index == Settings::NativeButton::ZR) { + return; + } + if (index == Settings::NativeButton::ZL) { + return; + } + } + switch (index) { case Settings::NativeButton::A: controller.npad_button_state.a.Assign(current_status.value); @@ -738,6 +748,11 @@ void EmulatedController::SetTrigger(const Common::Input::CallbackStatus& callbac return; } + // Only GC controllers have analog triggers + if (npad_type != NpadStyleIndex::GameCube) { + return; + } + const auto& trigger = controller.trigger_values[index]; switch (index) { diff --git a/src/core/hid/input_converter.cpp b/src/core/hid/input_converter.cpp index 68d143a013..52fb69e9c8 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 Common::Input::ButtonStatus status{}; switch (callback.type) { case Common::Input::InputType::Analog: + status.value = TransformToTrigger(callback).pressed.value; + status.toggle = callback.analog_status.properties.toggle; + break; case Common::Input::InputType::Trigger: status.value = TransformToTrigger(callback).pressed.value; break; diff --git a/src/core/hle/result.h b/src/core/hle/result.h index 4de44cd061..47a1b829b2 100644 --- a/src/core/hle/result.h +++ b/src/core/hle/result.h @@ -117,6 +117,7 @@ union Result { BitField<0, 9, ErrorModule> module; BitField<9, 13, u32> description; + Result() = default; constexpr explicit Result(u32 raw_) : raw(raw_) {} constexpr Result(ErrorModule module_, u32 description_) @@ -130,6 +131,7 @@ union Result { return !IsSuccess(); } }; +static_assert(std::is_trivial_v<Result>); [[nodiscard]] constexpr bool operator==(const Result& a, const Result& b) { 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 a44dd842a0..49c0923016 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) { const auto write_count = static_cast<u32>(ctx.GetWriteBufferSize() / sizeof(AudioDevice::AudioDeviceName)); std::vector<AudioDevice::AudioDeviceName> device_names{}; - std::string print_names{}; if (write_count > 0) { - device_names.push_back(AudioDevice::AudioDeviceName("DeviceOut")); + device_names.emplace_back("DeviceOut"); LOG_DEBUG(Service_Audio, "called. \nName=DeviceOut"); } else { 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 bc69117c62..6fb07c37dc 100644 --- a/src/core/hle/service/audio/audren_u.cpp +++ b/src/core/hle/service/audio/audren_u.cpp @@ -252,7 +252,7 @@ private: std::vector<AudioDevice::AudioDeviceName> out_names{}; - u32 out_count = impl->ListAudioDeviceName(out_names, in_count); + const u32 out_count = impl->ListAudioDeviceName(out_names, in_count); std::string out{}; for (u32 i = 0; i < out_count; i++) { @@ -365,7 +365,7 @@ private: std::vector<AudioDevice::AudioDeviceName> out_names{}; - u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); + const u32 out_count = impl->ListAudioOutputDeviceName(out_names, in_count); std::string out{}; 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 2a5128c602..a7385fce81 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 @@ // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include "audio_core/audio_core.h" #include "common/assert.h" #include "common/logging/log.h" #include "core/core.h" @@ -65,7 +66,10 @@ NvResult nvhost_nvdec::Ioctl3(DeviceFD fd, Ioctl command, const std::vector<u8>& return NvResult::NotImplemented; } -void nvhost_nvdec::OnOpen(DeviceFD fd) {} +void nvhost_nvdec::OnOpen(DeviceFD fd) { + LOG_INFO(Service_NVDRV, "NVDEC video stream started"); + system.AudioCore().SetNVDECActive(true); +} void nvhost_nvdec::OnClose(DeviceFD fd) { LOG_INFO(Service_NVDRV, "NVDEC video stream ended"); @@ -73,6 +77,7 @@ void nvhost_nvdec::OnClose(DeviceFD fd) { if (iter != fd_to_id.end()) { system.GPU().ClearCdmaInstance(iter->second); } + system.AudioCore().SetNVDECActive(false); } } // namespace Service::Nvidia::Devices diff --git a/src/dedicated_room/CMakeLists.txt b/src/dedicated_room/CMakeLists.txt index 737aedbe47..1efdbc1f76 100644 --- a/src/dedicated_room/CMakeLists.txt +++ b/src/dedicated_room/CMakeLists.txt @@ -16,7 +16,7 @@ if (ENABLE_WEB_SERVICE) target_link_libraries(yuzu-room PRIVATE web_service) endif() -target_link_libraries(yuzu-room PRIVATE mbedtls) +target_link_libraries(yuzu-room PRIVATE mbedtls mbedcrypto) if (MSVC) target_link_libraries(yuzu-room PRIVATE getopt) endif() diff --git a/src/input_common/drivers/sdl_driver.cpp b/src/input_common/drivers/sdl_driver.cpp index de388ec4cf..5cc1ccbd97 100644 --- a/src/input_common/drivers/sdl_driver.cpp +++ b/src/input_common/drivers/sdl_driver.cpp @@ -40,13 +40,13 @@ public: void EnableMotion() { if (sdl_controller) { SDL_GameController* controller = sdl_controller.get(); - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL) && !has_accel) { + has_accel = SDL_GameControllerHasSensor(controller, SDL_SENSOR_ACCEL); + has_gyro = SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO); + if (has_accel) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_ACCEL, SDL_TRUE); - has_accel = true; } - if (SDL_GameControllerHasSensor(controller, SDL_SENSOR_GYRO) && !has_gyro) { + if (has_gyro) { SDL_GameControllerSetSensorEnabled(controller, SDL_SENSOR_GYRO, SDL_TRUE); - has_gyro = true; } } } @@ -305,6 +305,7 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared<SDLJoystick>(guid, 0, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick->EnableMotion(); joystick_map[guid].emplace_back(std::move(joystick)); return; } @@ -316,6 +317,7 @@ void SDLDriver::InitJoystick(int joystick_index) { if (joystick_it != joystick_guid_list.end()) { (*joystick_it)->SetSDLJoystick(sdl_joystick, sdl_gamecontroller); + (*joystick_it)->EnableMotion(); return; } @@ -323,6 +325,7 @@ void SDLDriver::InitJoystick(int joystick_index) { auto joystick = std::make_shared<SDLJoystick>(guid, port, sdl_joystick, sdl_gamecontroller); PreSetController(joystick->GetPadIdentifier()); SetBattery(joystick->GetPadIdentifier(), joystick->GetBatteryLevel()); + joystick->EnableMotion(); joystick_guid_list.emplace_back(std::move(joystick)); } diff --git a/src/input_common/input_poller.cpp b/src/input_common/input_poller.cpp index 133422d5cd..ffb9b945e5 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( .threshold = std::clamp(params.Get("threshold", 0.5f), 0.0f, 1.0f), .offset = std::clamp(params.Get("offset", 0.0f), -1.0f, 1.0f), .inverted = params.Get("invert", "+") == "-", + .toggle = static_cast<bool>(params.Get("toggle", false)), }; input_engine->PreSetController(identifier); 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 5cead5135e..597112ba47 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) { inst->SetFlags(flags); break; case IR::Opcode::ImageSampleImplicitLod: - if (flags.type == TextureType::Color2D) { - auto texture_type = ReadTextureType(env, cbuf); - if (texture_type == TextureType::Color2DRect) { - PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); - } + if (flags.type != TextureType::Color2D) { + break; + } + if (ReadTextureType(env, cbuf) == TextureType::Color2DRect) { + PatchImageSampleImplicitLod(*texture_inst.block, *texture_inst.inst); } break; 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 0b2bc67b1f..f9a6472cfd 100644 --- a/src/video_core/buffer_cache/buffer_base.h +++ b/src/video_core/buffer_cache/buffer_base.h @@ -12,6 +12,7 @@ #include "common/common_funcs.h" #include "common/common_types.h" #include "common/div_ceil.h" +#include "common/settings.h" #include "core/memory.h" namespace VideoCommon { @@ -219,7 +220,9 @@ public: NotifyRasterizer<false>(word_index, untracked_words[word_index], cached_bits); untracked_words[word_index] |= cached_bits; cpu_words[word_index] |= cached_bits; - cached_words[word_index] = 0; + if (!Settings::values.use_pessimistic_flushes) { + cached_words[word_index] = 0; + } } } diff --git a/src/video_core/renderer_opengl/gl_shader_cache.cpp b/src/video_core/renderer_opengl/gl_shader_cache.cpp index 1ad56d9e7a..ddb70934c1 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; using VideoCommon::SerializePipeline; using Context = ShaderContext::Context; -constexpr u32 CACHE_VERSION = 5; +constexpr u32 CACHE_VERSION = 6; template <typename Container> 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 3adad5af4c..9708dc45e4 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; using VideoCommon::GenericEnvironment; using VideoCommon::GraphicsEnvironment; -constexpr u32 CACHE_VERSION = 5; +constexpr u32 CACHE_VERSION = 6; template <typename Container> auto MakeSpan(Container& container) { diff --git a/src/video_core/shader_environment.cpp b/src/video_core/shader_environment.cpp index 808d88eec8..5f76259470 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) { return Shader::TextureType::Color1D; case Tegra::Texture::TextureType::Texture2D: case Tegra::Texture::TextureType::Texture2DNoMipmap: - if (entry.normalized_coords) { - return Shader::TextureType::Color2D; - } else { - return Shader::TextureType::Color2DRect; - } + return entry.normalized_coords ? Shader::TextureType::Color2D + : Shader::TextureType::Color2DRect; case Tegra::Texture::TextureType::Texture3D: return Shader::TextureType::Color3D; case Tegra::Texture::TextureType::TextureCubemap: diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp index e447598566..a4ed684229 100644 --- a/src/yuzu/configuration/config.cpp +++ b/src/yuzu/configuration/config.cpp @@ -684,6 +684,7 @@ void Config::ReadRendererValues() { ReadGlobalSetting(Settings::values.shader_backend); ReadGlobalSetting(Settings::values.use_asynchronous_shaders); ReadGlobalSetting(Settings::values.use_fast_gpu_time); + ReadGlobalSetting(Settings::values.use_pessimistic_flushes); ReadGlobalSetting(Settings::values.bg_red); ReadGlobalSetting(Settings::values.bg_green); ReadGlobalSetting(Settings::values.bg_blue); @@ -1301,6 +1302,7 @@ void Config::SaveRendererValues() { Settings::values.shader_backend.UsingGlobal()); WriteGlobalSetting(Settings::values.use_asynchronous_shaders); WriteGlobalSetting(Settings::values.use_fast_gpu_time); + WriteGlobalSetting(Settings::values.use_pessimistic_flushes); WriteGlobalSetting(Settings::values.bg_red); WriteGlobalSetting(Settings::values.bg_green); WriteGlobalSetting(Settings::values.bg_blue); diff --git a/src/yuzu/configuration/configure_graphics_advanced.cpp b/src/yuzu/configuration/configure_graphics_advanced.cpp index 7c3196c83a..01f074699c 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.cpp +++ b/src/yuzu/configuration/configure_graphics_advanced.cpp @@ -28,6 +28,7 @@ void ConfigureGraphicsAdvanced::SetConfiguration() { ui->use_vsync->setChecked(Settings::values.use_vsync.GetValue()); ui->use_asynchronous_shaders->setChecked(Settings::values.use_asynchronous_shaders.GetValue()); ui->use_fast_gpu_time->setChecked(Settings::values.use_fast_gpu_time.GetValue()); + ui->use_pessimistic_flushes->setChecked(Settings::values.use_pessimistic_flushes.GetValue()); if (Settings::IsConfiguringGlobal()) { ui->gpu_accuracy->setCurrentIndex( @@ -55,6 +56,8 @@ void ConfigureGraphicsAdvanced::ApplyConfiguration() { use_asynchronous_shaders); ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_fast_gpu_time, ui->use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::ApplyPerGameSetting(&Settings::values.use_pessimistic_flushes, + ui->use_pessimistic_flushes, use_pessimistic_flushes); } void ConfigureGraphicsAdvanced::changeEvent(QEvent* event) { @@ -77,6 +80,8 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { ui->use_asynchronous_shaders->setEnabled( Settings::values.use_asynchronous_shaders.UsingGlobal()); ui->use_fast_gpu_time->setEnabled(Settings::values.use_fast_gpu_time.UsingGlobal()); + ui->use_pessimistic_flushes->setEnabled( + Settings::values.use_pessimistic_flushes.UsingGlobal()); ui->anisotropic_filtering_combobox->setEnabled( Settings::values.max_anisotropy.UsingGlobal()); @@ -89,6 +94,9 @@ void ConfigureGraphicsAdvanced::SetupPerGameUI() { use_asynchronous_shaders); ConfigurationShared::SetColoredTristate(ui->use_fast_gpu_time, Settings::values.use_fast_gpu_time, use_fast_gpu_time); + ConfigurationShared::SetColoredTristate(ui->use_pessimistic_flushes, + Settings::values.use_pessimistic_flushes, + use_pessimistic_flushes); ConfigurationShared::SetColoredComboBox( ui->gpu_accuracy, ui->label_gpu_accuracy, 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 1ef7bd9161..12e816905a 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.h +++ b/src/yuzu/configuration/configure_graphics_advanced.h @@ -39,6 +39,7 @@ private: ConfigurationShared::CheckState use_vsync; ConfigurationShared::CheckState use_asynchronous_shaders; ConfigurationShared::CheckState use_fast_gpu_time; + ConfigurationShared::CheckState use_pessimistic_flushes; const Core::System& system; }; diff --git a/src/yuzu/configuration/configure_graphics_advanced.ui b/src/yuzu/configuration/configure_graphics_advanced.ui index d6d8193647..87a1214710 100644 --- a/src/yuzu/configuration/configure_graphics_advanced.ui +++ b/src/yuzu/configuration/configure_graphics_advanced.ui @@ -100,6 +100,16 @@ </widget> </item> <item> + <widget class="QCheckBox" name="use_pessimistic_flushes"> + <property name="toolTip"> + <string>Enables pessimistic buffer flushes. This option will force unmodified buffers to be flushed, which can cost performance.</string> + </property> + <property name="text"> + <string>Use pessimistic buffer flushes (Hack)</string> + </property> + </widget> + </item> + <item> <widget class="QWidget" name="af_layout" native="true"> <layout class="QHBoxLayout" name="horizontalLayout_1"> <property name="leftMargin"> diff --git a/src/yuzu/configuration/configure_input_player.cpp b/src/yuzu/configuration/configure_input_player.cpp index 109689c88d..9e5a40fe74 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) { const QString toggle = QString::fromStdString(param.Get("toggle", false) ? "~" : ""); const QString inverted = QString::fromStdString(param.Get("inverted", false) ? "!" : ""); + const QString invert = QString::fromStdString(param.Get("invert", "+") == "-" ? "-" : ""); const auto common_button_name = input_subsystem->GetButtonName(param); // Retrieve the names from Qt @@ -184,7 +185,7 @@ QString ConfigureInputPlayer::ButtonToText(const Common::ParamPackage& param) { } if (param.Has("axis")) { const QString axis = QString::fromStdString(param.Get("axis", "")); - return QObject::tr("%1%2Axis %3").arg(toggle, inverted, axis); + return QObject::tr("%1%2Axis %3").arg(toggle, invert, axis); } if (param.Has("axis_x") && param.Has("axis_y") && param.Has("axis_z")) { const QString axis_x = QString::fromStdString(param.Get("axis_x", "")); @@ -362,18 +363,18 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i button_map[button_id]->setText(tr("[not set]")); }); if (param.Has("code") || param.Has("button") || param.Has("hat")) { - context_menu.addAction(tr("Toggle button"), [&] { - const bool toggle_value = !param.Get("toggle", false); - param.Set("toggle", toggle_value); - button_map[button_id]->setText(ButtonToText(param)); - emulated_controller->SetButtonParam(button_id, param); - }); context_menu.addAction(tr("Invert button"), [&] { const bool invert_value = !param.Get("inverted", false); param.Set("inverted", invert_value); button_map[button_id]->setText(ButtonToText(param)); emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Toggle button"), [&] { + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); } if (param.Has("axis")) { context_menu.addAction(tr("Invert axis"), [&] { @@ -398,6 +399,12 @@ ConfigureInputPlayer::ConfigureInputPlayer(QWidget* parent, std::size_t player_i } emulated_controller->SetButtonParam(button_id, param); }); + context_menu.addAction(tr("Toggle axis"), [&] { + const bool toggle_value = !param.Get("toggle", false); + param.Set("toggle", toggle_value); + button_map[button_id]->setText(ButtonToText(param)); + emulated_controller->SetButtonParam(button_id, param); + }); } context_menu.exec(button_map[button_id]->mapToGlobal(menu_location)); }); @@ -1410,7 +1417,7 @@ void ConfigureInputPlayer::HandleClick( ui->controllerFrame->BeginMappingAnalog(button_id); } - timeout_timer->start(2500); // Cancel after 2.5 seconds + timeout_timer->start(4000); // Cancel after 4 seconds poll_timer->start(25); // Check for new inputs every 25ms } diff --git a/src/yuzu/configuration/configure_tas.ui b/src/yuzu/configuration/configure_tas.ui index cf88a5bf08..625af0c891 100644 --- a/src/yuzu/configuration/configure_tas.ui +++ b/src/yuzu/configuration/configure_tas.ui @@ -16,6 +16,9 @@ <property name="text"> <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> </property> + <property name="openExternalLinks"> + <bool>true</bool> + </property> </widget> </item> <item row="1" column="0" colspan="4"> diff --git a/src/yuzu_cmd/config.cpp b/src/yuzu_cmd/config.cpp index bd0fb75f8d..66dd0dc154 100644 --- a/src/yuzu_cmd/config.cpp +++ b/src/yuzu_cmd/config.cpp @@ -314,6 +314,7 @@ void Config::ReadValues() { ReadSetting("Renderer", Settings::values.nvdec_emulation); ReadSetting("Renderer", Settings::values.accelerate_astc); ReadSetting("Renderer", Settings::values.use_fast_gpu_time); + ReadSetting("Renderer", Settings::values.use_pessimistic_flushes); ReadSetting("Renderer", Settings::values.bg_red); ReadSetting("Renderer", Settings::values.bg_green); diff --git a/src/yuzu_cmd/default_ini.h b/src/yuzu_cmd/default_ini.h index 1168cf1362..d214771b0e 100644 --- a/src/yuzu_cmd/default_ini.h +++ b/src/yuzu_cmd/default_ini.h @@ -319,6 +319,10 @@ use_asynchronous_gpu_emulation = # 0: Off, 1 (default): On use_fast_gpu_time = +# Force unmodified buffers to be flushed, which can cost performance. +# 0: Off (default), 1: On +use_pessimistic_flushes = + # Whether to use garbage collection or not for GPU caches. # 0 (default): Off, 1: On use_caches_gc = |