From bedc5db78f354974759472871369b0559d936009 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 13:27:30 +0000 Subject: [PATCH 01/14] [common] use libc++ provided jthread instead of in-house one (which deadlocks on FBSD 14) Signed-off-by: lizzie --- CMakeLists.txt | 10 + src/audio_core/sink/sink_stream.cpp | 3 +- src/common/bounded_threadsafe_queue.h | 5 +- src/common/polyfill_thread.h | 351 +----------------- src/common/thread.h | 5 +- src/common/thread_worker.h | 7 +- src/common/threadsafe_queue.h | 5 +- src/video_core/cdma_pusher.cpp | 4 +- src/video_core/gpu_thread.cpp | 2 +- .../renderer_vulkan/vk_master_semaphore.cpp | 2 +- .../renderer_vulkan/vk_present_manager.cpp | 2 +- .../renderer_vulkan/vk_scheduler.cpp | 2 +- .../renderer_vulkan/vk_turbo_mode.cpp | 5 +- src/yuzu/bootmanager.cpp | 4 +- 14 files changed, 43 insertions(+), 364 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 411de4620b..1c47f0d7af 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -346,6 +346,16 @@ if (PLATFORM_LINUX OR CXX_CLANG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv9-a -mtune=generic") endif() endif() +elseif (ANDROID OR PLATFORM_FREEBSD OR PLATFORM_OPENBSD OR PLATFORM_SUN) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # libc++ has stop_token and jthread as experimental + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library") + else() + # Uses glibc, mostly? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + endif() endif() # Other presets, e.g. steamdeck diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 4d7f0c1d5d..b75d743494 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -293,8 +293,7 @@ void SinkStream::WaitFreeSpace(std::stop_token stop_token) { release_cv.wait_for(lk, std::chrono::milliseconds(5), [this]() { return paused || queued_buffers < max_queue_size; }); if (queued_buffers > max_queue_size + 3) { - Common::CondvarWait(release_cv, lk, stop_token, - [this] { return paused || queued_buffers < max_queue_size; }); + release_cv.wait(lk, stop_token, [this] { return paused || queued_buffers < max_queue_size; }); } } diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index b36fc1de96..80f32e2553 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -123,7 +126,7 @@ private: } else if constexpr (Mode == PopMode::WaitWithStopToken) { // Wait until the queue is not empty. std::unique_lock lock{consumer_cv_mutex}; - Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] { + consumer_cv.wait(lock, stop_token, [this, read_index] { return read_index != m_write_index.load(std::memory_order::acquire); }); if (stop_token.stop_requested()) { diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h index 12e59a8939..0d3c963d25 100644 --- a/src/common/polyfill_thread.h +++ b/src/common/polyfill_thread.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,23 +10,13 @@ #pragma once -#include - -#ifdef __cpp_lib_jthread - #include #include -#include #include #include namespace Common { -template -void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred&& pred) { - cv.wait(lk, token, std::forward(pred)); -} - template bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) { std::condition_variable_any cv; @@ -35,341 +28,3 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace std { -namespace polyfill { - -using stop_state_callback = size_t; - -class stop_state { -public: - stop_state() = default; - ~stop_state() = default; - - bool request_stop() { - unique_lock lk{m_lock}; - - if (m_stop_requested) { - // Already set, nothing to do. - return false; - } - - // Mark stop requested. - m_stop_requested = true; - - while (!m_callbacks.empty()) { - // Get an iterator to the first element. - const auto it = m_callbacks.begin(); - - // Move the callback function out of the map. - function f; - swap(it->second, f); - - // Erase the now-empty map element. - m_callbacks.erase(it); - - // Run the callback. - if (f) { - f(); - } - } - - return true; - } - - bool stop_requested() const { - unique_lock lk{m_lock}; - return m_stop_requested; - } - - stop_state_callback insert_callback(function f) { - unique_lock lk{m_lock}; - - if (m_stop_requested) { - // Stop already requested. Don't insert anything, - // just run the callback synchronously. - if (f) { - f(); - } - return 0; - } - - // Insert the callback. - stop_state_callback ret = ++m_next_callback; - m_callbacks.emplace(ret, std::move(f)); - return ret; - } - - void remove_callback(stop_state_callback cb) { - unique_lock lk{m_lock}; - m_callbacks.erase(cb); - } - -private: - mutable recursive_mutex m_lock; - map> m_callbacks; - stop_state_callback m_next_callback{0}; - bool m_stop_requested{false}; -}; - -} // namespace polyfill - -class stop_token; -class stop_source; -struct nostopstate_t { - explicit nostopstate_t() = default; -}; -inline constexpr nostopstate_t nostopstate{}; - -template -class stop_callback; - -class stop_token { -public: - stop_token() noexcept = default; - - stop_token(const stop_token&) noexcept = default; - stop_token(stop_token&&) noexcept = default; - stop_token& operator=(const stop_token&) noexcept = default; - stop_token& operator=(stop_token&&) noexcept = default; - ~stop_token() = default; - - void swap(stop_token& other) noexcept { - m_stop_state.swap(other.m_stop_state); - } - - [[nodiscard]] bool stop_requested() const noexcept { - return m_stop_state && m_stop_state->stop_requested(); - } - [[nodiscard]] bool stop_possible() const noexcept { - return m_stop_state != nullptr; - } - -private: - friend class stop_source; - template - friend class stop_callback; - stop_token(shared_ptr stop_state) : m_stop_state(std::move(stop_state)) {} - -private: - shared_ptr m_stop_state; -}; - -class stop_source { -public: - stop_source() : m_stop_state(make_shared()) {} - explicit stop_source(nostopstate_t) noexcept {} - - stop_source(const stop_source&) noexcept = default; - stop_source(stop_source&&) noexcept = default; - stop_source& operator=(const stop_source&) noexcept = default; - stop_source& operator=(stop_source&&) noexcept = default; - ~stop_source() = default; - void swap(stop_source& other) noexcept { - m_stop_state.swap(other.m_stop_state); - } - - [[nodiscard]] stop_token get_token() const noexcept { - return stop_token(m_stop_state); - } - [[nodiscard]] bool stop_possible() const noexcept { - return m_stop_state != nullptr; - } - [[nodiscard]] bool stop_requested() const noexcept { - return m_stop_state && m_stop_state->stop_requested(); - } - bool request_stop() noexcept { - return m_stop_state && m_stop_state->request_stop(); - } - -private: - friend class jthread; - explicit stop_source(shared_ptr stop_state) - : m_stop_state(std::move(stop_state)) {} - -private: - shared_ptr m_stop_state; -}; - -template -class stop_callback { - static_assert(is_nothrow_destructible_v); - static_assert(is_invocable_v); - -public: - using callback_type = Callback; - - template - requires constructible_from - explicit stop_callback(const stop_token& st, - C&& cb) noexcept(is_nothrow_constructible_v) - : m_stop_state(st.m_stop_state) { - if (m_stop_state) { - m_callback = m_stop_state->insert_callback(std::move(cb)); - } - } - template - requires constructible_from - explicit stop_callback(stop_token&& st, - C&& cb) noexcept(is_nothrow_constructible_v) - : m_stop_state(std::move(st.m_stop_state)) { - if (m_stop_state) { - m_callback = m_stop_state->insert_callback(std::move(cb)); - } - } - ~stop_callback() { - if (m_stop_state && m_callback) { - m_stop_state->remove_callback(m_callback); - } - } - - stop_callback(const stop_callback&) = delete; - stop_callback(stop_callback&&) = delete; - stop_callback& operator=(const stop_callback&) = delete; - stop_callback& operator=(stop_callback&&) = delete; - -private: - shared_ptr m_stop_state; - polyfill::stop_state_callback m_callback; -}; - -template -stop_callback(stop_token, Callback) -> stop_callback; - -class jthread { -public: - using id = thread::id; - using native_handle_type = thread::native_handle_type; - - jthread() noexcept = default; - - template , jthread>>> - explicit jthread(F&& f, Args&&... args) - : m_stop_state(make_shared()), - m_thread(make_thread(std::forward(f), std::forward(args)...)) {} - - ~jthread() { - if (joinable()) { - request_stop(); - join(); - } - } - - jthread(const jthread&) = delete; - jthread(jthread&&) noexcept = default; - jthread& operator=(const jthread&) = delete; - - jthread& operator=(jthread&& other) noexcept { - m_thread.swap(other.m_thread); - m_stop_state.swap(other.m_stop_state); - return *this; - } - - void swap(jthread& other) noexcept { - m_thread.swap(other.m_thread); - m_stop_state.swap(other.m_stop_state); - } - [[nodiscard]] bool joinable() const noexcept { - return m_thread.joinable(); - } - void join() { - m_thread.join(); - } - void detach() { - m_thread.detach(); - m_stop_state.reset(); - } - - [[nodiscard]] id get_id() const noexcept { - return m_thread.get_id(); - } - [[nodiscard]] native_handle_type native_handle() { - return m_thread.native_handle(); - } - [[nodiscard]] stop_source get_stop_source() noexcept { - return stop_source(m_stop_state); - } - [[nodiscard]] stop_token get_stop_token() const noexcept { - return stop_source(m_stop_state).get_token(); - } - bool request_stop() noexcept { - return get_stop_source().request_stop(); - } - [[nodiscard]] static unsigned int hardware_concurrency() noexcept { - return thread::hardware_concurrency(); - } - -private: - template - thread make_thread(F&& f, Args&&... args) { - if constexpr (is_invocable_v, stop_token, decay_t...>) { - return thread(std::forward(f), get_stop_token(), std::forward(args)...); - } else { - return thread(std::forward(f), std::forward(args)...); - } - } - - shared_ptr m_stop_state; - thread m_thread; -}; - -} // namespace std - -namespace Common { - -template -void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred pred) { - if (token.stop_requested()) { - return; - } - - std::stop_callback callback(token, [&] { - { std::scoped_lock lk2{*lk.mutex()}; } - cv.notify_all(); - }); - - cv.wait(lk, [&] { return pred() || token.stop_requested(); }); -} - -template -bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) { - if (token.stop_requested()) { - return false; - } - - bool stop_requested = false; - std::condition_variable cv; - std::mutex m; - - std::stop_callback cb(token, [&] { - // Wake up the waiting thread. - { - std::scoped_lock lk{m}; - stop_requested = true; - } - cv.notify_one(); - }); - - // Perform the timed wait. - std::unique_lock lk{m}; - return !cv.wait_for(lk, rel_time, [&] { return stop_requested; }); -} - -} // namespace Common - -#endif diff --git a/src/common/thread.h b/src/common/thread.h index c6976fb6cd..5ab495baaa 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -80,7 +83,7 @@ public: condvar.notify_all(); return true; } else { - CondvarWait(condvar, lk, token, + condvar.wait(lk, token, [this, current_generation] { return current_generation != generation; }); return !token.stop_requested(); } diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index 260ad44e45..6ec9d6a2bd 100644 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -47,8 +50,8 @@ public: if (requests.empty()) { wait_condition.notify_all(); } - Common::CondvarWait(condition, lock, stop_token, - [this] { return !requests.empty(); }); + condition.wait(lock, stop_token, + [this] { return !requests.empty(); }); if (stop_token.stop_requested()) { break; } diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 2ef1da0644..28bb46722b 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -99,7 +102,7 @@ public: T PopWait(std::stop_token stop_token) { if (Empty()) { std::unique_lock lock{cv_mutex}; - Common::CondvarWait(cv, lock, stop_token, [this] { return !Empty(); }); + cv.wait(lock, stop_token, [this] { return !Empty(); }); } if (stop_token.stop_requested()) { return T{}; diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp index 1b6b4c6d45..b9140d9335 100644 --- a/src/video_core/cdma_pusher.cpp +++ b/src/video_core/cdma_pusher.cpp @@ -39,8 +39,8 @@ void CDmaPusher::ProcessEntries(std::stop_token stop_token) { while (!stop_token.stop_requested()) { { std::unique_lock l{command_mutex}; - Common::CondvarWait(command_cv, l, stop_token, - [this]() { return command_lists.size() > 0; }); + command_cv.wait(l, stop_token, + [this]() { return command_lists.size() > 0; }); if (stop_token.stop_requested()) { return; } diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 9392534e7d..8739905d99 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -116,7 +116,7 @@ u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { state.queue.EmplaceWait(std::move(command_data), fence, block); if (block) { - Common::CondvarWait(state.cv, lk, thread.get_stop_token(), [this, fence] { + state.cv.wait(lk, thread.get_stop_token(), [this, fence] { return fence <= state.signaled_fence.load(std::memory_order_relaxed); }); } diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index 6761127582..e65755de64 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -208,7 +208,7 @@ void MasterSemaphore::WaitThread(std::stop_token token) { vk::Fence fence; { std::unique_lock lock{wait_mutex}; - Common::CondvarWait(wait_cv, lock, token, [this] { return !wait_queue.empty(); }); + wait_cv.wait(lock, token, [this] { return !wait_queue.empty(); }); if (token.stop_requested()) { return; } diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 23279e49b9..0b29ad1389 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -279,7 +279,7 @@ void PresentManager::PresentThread(std::stop_token token) { std::unique_lock lock{queue_mutex}; // Wait for presentation frames - Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); }); + frame_cv.wait(lock, token, [this] { return !present_queue.empty(); }); if (token.stop_requested()) { return; } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 530d161dfe..d109d22cab 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -166,7 +166,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) { std::unique_lock lk{queue_mutex}; // Wait for work. - Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); }); + event_cv.wait(lk, stop_token, [&] { return TryPopQueue(work); }); // If we've been asked to stop, we're done. if (stop_token.stop_requested()) { diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp index 04a51f2d1e..54183be12c 100644 --- a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp +++ b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -224,7 +227,7 @@ void TurboMode::Run(std::stop_token stop_token) { #endif // Wait for the next graphics queue submission if necessary. std::unique_lock lk{m_submission_lock}; - Common::CondvarWait(m_submission_cv, lk, stop_token, [this] { + m_submission_cv.wait(lk, stop_token, [this] { return (std::chrono::steady_clock::now() - m_submission_time) <= std::chrono::milliseconds{100}; }); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 7b5f2a314f..cdc4e4024a 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -108,13 +108,13 @@ void EmuThread::run() { m_system.Run(); m_stopped.Reset(); - Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return !m_should_run; }); + m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); } else { m_system.Pause(); m_stopped.Set(); EmulationPaused(lk); - Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return m_should_run; }); + m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); EmulationResumed(lk); } } From cc2e708cfae03b75980b4b01d4c809153875cc49 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 19 Sep 2025 06:58:48 +0000 Subject: [PATCH 02/14] [cmake] fix android? Signed-off-by: lizzie --- CMakeLists.txt | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 1c47f0d7af..e089886617 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -316,6 +316,18 @@ if (YUZU_ROOM) add_compile_definitions(YUZU_ROOM) endif() +if (ANDROID OR PLATFORM_FREEBSD OR PLATFORM_OPENBSD OR PLATFORM_SUN) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # libc++ has stop_token and jthread as experimental + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library") + else() + # Uses glibc, mostly? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + endif() +endif() + # Build/optimization presets if (PLATFORM_LINUX OR CXX_CLANG) if (ARCHITECTURE_x86_64) @@ -346,16 +358,6 @@ if (PLATFORM_LINUX OR CXX_CLANG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv9-a -mtune=generic") endif() endif() -elseif (ANDROID OR PLATFORM_FREEBSD OR PLATFORM_OPENBSD OR PLATFORM_SUN) - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # libc++ has stop_token and jthread as experimental - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library") - else() - # Uses glibc, mostly? - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") - endif() endif() # Other presets, e.g. steamdeck From 672d6268f15ee8f77a1db496ad30eff1bbbcbc02 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 19 Sep 2025 07:35:53 +0000 Subject: [PATCH 03/14] [cmake] fix android once for all Signed-off-by: lizzie --- src/common/threadsafe_queue.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 28bb46722b..f9fc0ca171 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -102,7 +102,11 @@ public: T PopWait(std::stop_token stop_token) { if (Empty()) { std::unique_lock lock{cv_mutex}; - cv.wait(lock, stop_token, [this] { return !Empty(); }); + if constexpr (with_stop_token) { + cv.wait(lock, stop_token, [this] { return !Empty(); }); + } else { + cv.wait(lock, [this] { return !Empty(); }); + } } if (stop_token.stop_requested()) { return T{}; From 10aca2f90cbfcceb7f2886d5fd0f97dea6c1cf41 Mon Sep 17 00:00:00 2001 From: wildcard Date: Fri, 26 Sep 2025 04:58:09 +0200 Subject: [PATCH 04/14] [Vulkan] Descriptor Pool bug fix (#2564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit a bank could be (incorrectly) considered a superset if it had enough image buffer descriptors but not enough storage image descriptors, causing the allocator to pick a bank that can’t actually satisfy VK_DESCRIPTOR_TYPE_STORAGE_IMAGE demand resulting in sham allocations and creation of new pools. Note to testers, please look for any regressions in terms of visuals and most importantly please test the performance and ram usage. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2564 Reviewed-by: MaranBr Reviewed-by: Shinmegumi Reviewed-by: Lizzie Reviewed-by: crueter Co-authored-by: wildcard Co-committed-by: wildcard --- src/video_core/renderer_vulkan/vk_descriptor_pool.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp index 600003953d..3af9758a31 100644 --- a/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp +++ b/src/video_core/renderer_vulkan/vk_descriptor_pool.cpp @@ -31,7 +31,7 @@ struct DescriptorBank { bool DescriptorBankInfo::IsSuperset(const DescriptorBankInfo& subset) const noexcept { return uniform_buffers >= subset.uniform_buffers && storage_buffers >= subset.storage_buffers && texture_buffers >= subset.texture_buffers && image_buffers >= subset.image_buffers && - textures >= subset.textures && images >= subset.image_buffers; + textures >= subset.textures && images >= subset.images; } template From 86ddb51a87879cf5df49d7acf8212b9c4e980acb Mon Sep 17 00:00:00 2001 From: nyx Date: Fri, 26 Sep 2025 05:01:33 +0200 Subject: [PATCH 05/14] [android] Implement foreground notification service (#480) A notification is shown when emulation is active. This, in theory should help preventing Eden for getting destroyed in the background. It is also a nice Q.O.L feature to have. Credits go to t895 for the initial implementation. This was back-ported from older official Citra builds, although with crashes which i fixed myself. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/480 Reviewed-by: crueter Co-authored-by: nyx Co-committed-by: nyx --- src/android/app/src/main/AndroidManifest.xml | 6 ++ .../java/org/yuzu/yuzu_emu/YuzuApplication.kt | 12 +++ .../yuzu_emu/activities/EmulationActivity.kt | 19 +++++ .../org/yuzu/yuzu_emu/ui/main/MainActivity.kt | 9 +++ .../yuzu/yuzu_emu/utils/ForegroundService.kt | 79 +++++++++++++++++++ .../app/src/main/res/values/strings.xml | 5 ++ 6 files changed, 130 insertions(+) create mode 100644 src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt diff --git a/src/android/app/src/main/AndroidManifest.xml b/src/android/app/src/main/AndroidManifest.xml index d31deaa355..45c5dfef8c 100644 --- a/src/android/app/src/main/AndroidManifest.xml +++ b/src/android/app/src/main/AndroidManifest.xml @@ -27,6 +27,8 @@ SPDX-License-Identifier: GPL-3.0-or-later + + @@ -93,6 +95,10 @@ SPDX-License-Identifier: GPL-3.0-or-later + + + + if (result != null) { diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt new file mode 100644 index 0000000000..c181656d99 --- /dev/null +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/utils/ForegroundService.kt @@ -0,0 +1,79 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +// Copyright 2023 Citra Emulator Project +// Licensed under GPLv2 or any later version +// Refer to the license.txt file included. + +package org.yuzu.yuzu_emu.utils + +import android.app.PendingIntent +import android.app.Service +import android.content.Intent +import android.os.IBinder +import androidx.core.app.NotificationCompat +import androidx.core.app.NotificationManagerCompat +import org.yuzu.yuzu_emu.R +import org.yuzu.yuzu_emu.activities.EmulationActivity + +/** + * A service that shows a permanent notification in the background to avoid the app getting + * cleared from memory by the system. + */ +class ForegroundService : Service() { + companion object { + const val EMULATION_RUNNING_NOTIFICATION = 0x1000 + + const val ACTION_STOP = "stop" + } + + private fun showRunningNotification() { + // Intent is used to resume emulation if the notification is clicked + val contentIntent = PendingIntent.getActivity( + this, + 0, + Intent(this, EmulationActivity::class.java), + PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT + ) + val builder = + NotificationCompat.Builder(this, getString(R.string.app_notification_channel_id)) + .setSmallIcon(R.drawable.ic_stat_notification_logo) + .setContentTitle(getString(R.string.app_name)) + .setContentText(getString(R.string.app_notification_running)) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) + .setOngoing(true) + .setVibrate(null) + .setSound(null) + .setContentIntent(contentIntent) + startForeground(EMULATION_RUNNING_NOTIFICATION, builder.build()) + } + + override fun onBind(intent: Intent): IBinder? { + return null + } + + override fun onCreate() { + showRunningNotification() + } + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + if (intent?.action == ACTION_STOP) { + try { + NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) + stopForeground(STOP_FOREGROUND_REMOVE) + } catch (e: Exception) { + Log.error("Failed to stop foreground service") + } + stopSelfResult(startId) + return START_NOT_STICKY + } + + if (intent != null) { + showRunningNotification() + } + return START_STICKY + } + + override fun onDestroy() = + NotificationManagerCompat.from(this).cancel(EMULATION_RUNNING_NOTIFICATION) +} diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 1545576ea8..48ea3ed99b 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -8,6 +8,11 @@ noticesAndErrors Shows notifications when something goes wrong. Notification permission not granted! + Eden + Eden + Eden Switch emulator notifications + Eden is Running + (Enhanced) From 19eb8272b1d818547538e6c9fb63724a8f7bb961 Mon Sep 17 00:00:00 2001 From: MaranBr Date: Fri, 26 Sep 2025 05:13:08 +0200 Subject: [PATCH 06/14] [video_core] Fix a bug in buffer cache that caused flickering in some games when using fast buffering (#2584) This fixes a bug in the buffer cache that caused flickering in some games when using fast buffering. This fixes Kirby Star Allies, Yoshi's Crafted World, and possibly many others. Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2584 Reviewed-by: Lizzie Co-authored-by: MaranBr Co-committed-by: MaranBr --- src/video_core/buffer_cache/buffer_cache.h | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/video_core/buffer_cache/buffer_cache.h b/src/video_core/buffer_cache/buffer_cache.h index 94ef1a48df..eb18a4bd66 100644 --- a/src/video_core/buffer_cache/buffer_cache.h +++ b/src/video_core/buffer_cache/buffer_cache.h @@ -792,6 +792,11 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 const u32 size = (std::min)(binding.size, (*channel_state->uniform_buffer_sizes)[stage][index]); Buffer& buffer = slot_buffers[binding.buffer_id]; TouchBuffer(buffer, binding.buffer_id); + const bool sync_buffer = SynchronizeBuffer(buffer, device_addr, size); + if (sync_buffer) { + ++channel_state->uniform_cache_hits[0]; + } + ++channel_state->uniform_cache_shots[0]; const bool use_fast_buffer = binding.buffer_id != NULL_BUFFER_ID && size <= channel_state->uniform_buffer_skip_cache_size && !memory_tracker.IsRegionGpuModified(device_addr, size); @@ -822,12 +827,6 @@ void BufferCache

::BindHostGraphicsUniformBuffer(size_t stage, u32 index, u32 device_memory.ReadBlockUnsafe(device_addr, span.data(), size); return; } - // Classic cached path - const bool sync_cached = SynchronizeBuffer(buffer, device_addr, size); - if (sync_cached) { - ++channel_state->uniform_cache_hits[0]; - } - ++channel_state->uniform_cache_shots[0]; // Skip binding if it's not needed and if the bound buffer is not the fast version // This exists to avoid instances where the fast buffer is bound and a GPU write happens From f088f028f3e018a10552fbe67376d93030ebc3ec Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Fri, 26 Sep 2025 21:46:56 +0200 Subject: [PATCH 07/14] [cmake] Fix building on aarch64-linux (#2591) Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2591 Reviewed-by: Lizzie Reviewed-by: crueter Co-authored-by: Marcin Serwin Co-committed-by: Marcin Serwin --- CMakeLists.txt | 1 + src/core/CMakeLists.txt | 2 +- src/core/arm/nce/patcher.h | 6 +++--- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 411de4620b..994bc184fa 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -310,6 +310,7 @@ endif() if (ARCHITECTURE_arm64 AND (ANDROID OR PLATFORM_LINUX)) set(HAS_NCE 1) add_compile_definitions(HAS_NCE=1) + find_package(oaknut 2.0.1) endif() if (YUZU_ROOM) diff --git a/src/core/CMakeLists.txt b/src/core/CMakeLists.txt index 6b64ab7820..11c217fce6 100644 --- a/src/core/CMakeLists.txt +++ b/src/core/CMakeLists.txt @@ -1224,7 +1224,7 @@ if (HAS_NCE) arm/nce/patcher.h arm/nce/visitor_base.h ) - target_link_libraries(core PRIVATE merry::mcl merry::oaknut) + target_link_libraries(core PRIVATE merry::oaknut) endif() if (ARCHITECTURE_x86_64 OR ARCHITECTURE_arm64) diff --git a/src/core/arm/nce/patcher.h b/src/core/arm/nce/patcher.h index 7f54608e3f..31b122477f 100644 --- a/src/core/arm/nce/patcher.h +++ b/src/core/arm/nce/patcher.h @@ -27,11 +27,11 @@ template <> struct std::hash { size_t operator()(const PatchCacheKey& key) const { // Simple XOR hash of first few bytes - size_t hash = 0; + size_t hash_ = 0; for (size_t i = 0; i < key.module_id.size(); ++i) { - hash ^= static_cast(key.module_id[i]) << ((i % sizeof(size_t)) * 8); + hash_ ^= static_cast(key.module_id[i]) << ((i % sizeof(size_t)) * 8); } - return hash ^ std::hash{}(key.offset); + return hash_ ^ std::hash{}(key.offset); } }; From 677148bdca260de138723ba998261e0f95d50f32 Mon Sep 17 00:00:00 2001 From: crueter Date: Sat, 27 Sep 2025 01:02:34 +0200 Subject: [PATCH 08/14] [cmake] PUBLIC link to mcl for dynarmic (#2595) fixes comp error in core/arm Signed-off-by: crueter Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2595 --- src/dynarmic/src/dynarmic/CMakeLists.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dynarmic/src/dynarmic/CMakeLists.txt b/src/dynarmic/src/dynarmic/CMakeLists.txt index 9e6bd25913..58efcac747 100644 --- a/src/dynarmic/src/dynarmic/CMakeLists.txt +++ b/src/dynarmic/src/dynarmic/CMakeLists.txt @@ -374,7 +374,7 @@ endif() target_compile_options(dynarmic PRIVATE ${DYNARMIC_CXX_FLAGS}) target_link_libraries(dynarmic - PRIVATE + PUBLIC fmt::fmt merry::mcl ) From 4982dcfaa54ef236e1a3e0a753c8d5e80407a881 Mon Sep 17 00:00:00 2001 From: Marcin Serwin Date: Sat, 27 Sep 2025 01:02:49 +0200 Subject: [PATCH 09/14] [cmake] Use siritConfig instead of the module (#2593) Tested together with https://github.com/eden-emulator/sirit/pull/2 Signed-off-by: Marcin Serwin Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2593 Reviewed-by: crueter Reviewed-by: MaranBr Co-authored-by: Marcin Serwin Co-committed-by: Marcin Serwin --- CMakeModules/Findsirit.cmake | 11 ----------- externals/cpmfile.json | 2 +- 2 files changed, 1 insertion(+), 12 deletions(-) delete mode 100644 CMakeModules/Findsirit.cmake diff --git a/CMakeModules/Findsirit.cmake b/CMakeModules/Findsirit.cmake deleted file mode 100644 index 83b81b09c5..0000000000 --- a/CMakeModules/Findsirit.cmake +++ /dev/null @@ -1,11 +0,0 @@ -# SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project -# SPDX-License-Identifier: GPL-3.0-or-later - -include(FindPackageHandleStandardArgs) - -find_package(PkgConfig QUIET) -pkg_search_module(sirit QUIET IMPORTED_TARGET sirit) -find_package_handle_standard_args(sirit - REQUIRED_VARS sirit_LINK_LIBRARIES - VERSION_VAR sirit_VERSION -) diff --git a/externals/cpmfile.json b/externals/cpmfile.json index 283da76743..dcafc8f97d 100644 --- a/externals/cpmfile.json +++ b/externals/cpmfile.json @@ -10,7 +10,7 @@ "repo": "eden-emulator/sirit", "sha": "db1f1e8ab5", "hash": "73eb3a042848c63a10656545797e85f40d142009dfb7827384548a385e1e28e1ac72f42b25924ce530d58275f8638554281e884d72f9c7aaf4ed08690a414b05", - "find_args": "MODULE", + "find_args": "CONFIG", "options": [ "SIRIT_USE_SYSTEM_SPIRV_HEADERS ON" ] From 020ad29a8c929d18d551ea7cb62d3db7d8460f8f Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 27 Sep 2025 01:21:14 +0200 Subject: [PATCH 10/14] [common] replace Common::BitSet with std::bitset (#2576) Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2576 Reviewed-by: crueter Co-authored-by: lizzie Co-committed-by: lizzie --- src/common/CMakeLists.txt | 1 - src/common/bit_set.h | 86 -------------------------- src/core/hle/kernel/k_priority_queue.h | 29 +++++---- 3 files changed, 18 insertions(+), 98 deletions(-) delete mode 100644 src/common/bit_set.h diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 7759ea6a21..96ea429e5a 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -32,7 +32,6 @@ add_library( atomic_ops.h bit_cast.h bit_field.h - bit_set.h bit_util.h bounded_threadsafe_queue.h cityhash.cpp diff --git a/src/common/bit_set.h b/src/common/bit_set.h deleted file mode 100644 index 74754504be..0000000000 --- a/src/common/bit_set.h +++ /dev/null @@ -1,86 +0,0 @@ -// SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project -// SPDX-License-Identifier: GPL-2.0-or-later - -#pragma once - -#include -#include - -#include "common/alignment.h" -#include "common/bit_util.h" -#include "common/common_types.h" - -namespace Common { - -namespace impl { - -template -class BitSet { - -public: - constexpr BitSet() = default; - - constexpr void SetBit(size_t i) { - this->words[i / FlagsPerWord] |= GetBitMask(i % FlagsPerWord); - } - - constexpr void ClearBit(size_t i) { - this->words[i / FlagsPerWord] &= ~GetBitMask(i % FlagsPerWord); - } - - constexpr size_t CountLeadingZero() const { - for (size_t i = 0; i < NumWords; i++) { - if (this->words[i]) { - return FlagsPerWord * i + CountLeadingZeroImpl(this->words[i]); - } - } - return FlagsPerWord * NumWords; - } - - constexpr size_t GetNextSet(size_t n) const { - for (size_t i = (n + 1) / FlagsPerWord; i < NumWords; i++) { - Storage word = this->words[i]; - if (!IsAligned(n + 1, FlagsPerWord)) { - word &= GetBitMask(n % FlagsPerWord) - 1; - } - if (word) { - return FlagsPerWord * i + CountLeadingZeroImpl(word); - } - } - return FlagsPerWord * NumWords; - } - -private: - static_assert(std::is_unsigned_v); - static_assert(sizeof(Storage) <= sizeof(u64)); - - static constexpr size_t FlagsPerWord = BitSize(); - static constexpr size_t NumWords = AlignUp(N, FlagsPerWord) / FlagsPerWord; - - static constexpr auto CountLeadingZeroImpl(Storage word) { - return std::countl_zero(static_cast(word)) - - (BitSize() - FlagsPerWord); - } - - static constexpr Storage GetBitMask(size_t bit) { - return Storage(1) << (FlagsPerWord - 1 - bit); - } - - std::array words{}; -}; - -} // namespace impl - -template -using BitSet8 = impl::BitSet; - -template -using BitSet16 = impl::BitSet; - -template -using BitSet32 = impl::BitSet; - -template -using BitSet64 = impl::BitSet; - -} // namespace Common diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h index 26677ec65c..0ef6bcc32c 100644 --- a/src/core/hle/kernel/k_priority_queue.h +++ b/src/core/hle/kernel/k_priority_queue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -5,10 +8,11 @@ #include #include +#include #include +#include #include "common/assert.h" -#include "common/bit_set.h" #include "common/common_types.h" #include "common/concepts.h" @@ -159,7 +163,7 @@ public: } if (m_queues[priority].PushBack(core, member)) { - m_available_priorities[core].SetBit(priority); + m_available_priorities[core].set(std::size_t(priority)); } } @@ -172,7 +176,7 @@ public: } if (m_queues[priority].PushFront(core, member)) { - m_available_priorities[core].SetBit(priority); + m_available_priorities[core].set(std::size_t(priority)); } } @@ -185,14 +189,14 @@ public: } if (m_queues[priority].Remove(core, member)) { - m_available_priorities[core].ClearBit(priority); + m_available_priorities[core].reset(std::size_t(priority)); } } constexpr Member* GetFront(s32 core) const { ASSERT(IsValidCore(core)); - const s32 priority = static_cast(m_available_priorities[core].CountLeadingZero()); + const s32 priority = s32((~m_available_priorities[core]).count()); if (priority <= LowestPriority) { return m_queues[priority].GetFront(core); } else { @@ -216,11 +220,14 @@ public: Member* next = member->GetPriorityQueueEntry(core).GetNext(); if (next == nullptr) { - const s32 priority = static_cast( - m_available_priorities[core].GetNextSet(member->GetPriority())); - if (priority <= LowestPriority) { - next = m_queues[priority].GetFront(core); - } + s32 priority = member->GetPriority() + 1; + do { + if (m_available_priorities[core][priority]) { + next = m_queues[priority].GetFront(core); + break; + } + ++priority; + } while (priority <= LowestPriority && priority < s32(m_available_priorities[core].size())); } return next; } @@ -250,7 +257,7 @@ public: private: std::array m_queues{}; - std::array, NumCores> m_available_priorities{}; + std::array, NumCores> m_available_priorities{}; }; private: From ba20e5c2f52f75ce1a396b0b93a9c5b362b6dff3 Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 27 Sep 2025 14:51:37 +0200 Subject: [PATCH 11/14] [common] fix extraneous error wrt. priority queues (#2598) This fixes an error that is reproducible (seemingly everywhere?) but on Linux. BitSet<> PR did not yield errors at the time of testing and this issue only cropped up after merge. Signed-off-by: lizzie Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/2598 Co-authored-by: lizzie Co-committed-by: lizzie --- src/core/hle/kernel/k_priority_queue.h | 27 +++++++++++++++++--------- 1 file changed, 18 insertions(+), 9 deletions(-) diff --git a/src/core/hle/kernel/k_priority_queue.h b/src/core/hle/kernel/k_priority_queue.h index 0ef6bcc32c..99347b5aef 100644 --- a/src/core/hle/kernel/k_priority_queue.h +++ b/src/core/hle/kernel/k_priority_queue.h @@ -12,6 +12,7 @@ #include #include +#include "common/alignment.h" #include "common/assert.h" #include "common/common_types.h" #include "common/concepts.h" @@ -196,7 +197,12 @@ public: constexpr Member* GetFront(s32 core) const { ASSERT(IsValidCore(core)); - const s32 priority = s32((~m_available_priorities[core]).count()); + const s32 priority = s32([](auto const& e) { + for (size_t i = 0; i < e.size(); ++i) + if (e[i]) + return i; + return e.size(); + }(m_available_priorities[core])); if (priority <= LowestPriority) { return m_queues[priority].GetFront(core); } else { @@ -215,19 +221,22 @@ public: } } + template + constexpr size_t GetNextSet(std::bitset const& bit, size_t n) const { + for (size_t i = n + 1; i < bit.size(); i++) + if (bit[i]) + return i; + return bit.size(); + } + constexpr Member* GetNext(s32 core, const Member* member) const { ASSERT(IsValidCore(core)); Member* next = member->GetPriorityQueueEntry(core).GetNext(); if (next == nullptr) { - s32 priority = member->GetPriority() + 1; - do { - if (m_available_priorities[core][priority]) { - next = m_queues[priority].GetFront(core); - break; - } - ++priority; - } while (priority <= LowestPriority && priority < s32(m_available_priorities[core].size())); + s32 priority = s32(GetNextSet(m_available_priorities[core], member->GetPriority())); + if (priority <= LowestPriority) + next = m_queues[priority].GetFront(core); } return next; } From cefd7a780ad1f80b828fb3857340a5a3507a97ed Mon Sep 17 00:00:00 2001 From: lizzie Date: Sat, 30 Aug 2025 13:27:30 +0000 Subject: [PATCH 12/14] [common] use libc++ provided jthread instead of in-house one (which deadlocks on FBSD 14) Signed-off-by: lizzie --- CMakeLists.txt | 10 + src/audio_core/sink/sink_stream.cpp | 3 +- src/common/bounded_threadsafe_queue.h | 5 +- src/common/polyfill_thread.h | 351 +----------------- src/common/thread.h | 5 +- src/common/thread_worker.h | 7 +- src/common/threadsafe_queue.h | 5 +- src/video_core/cdma_pusher.cpp | 4 +- src/video_core/gpu_thread.cpp | 2 +- .../renderer_vulkan/vk_master_semaphore.cpp | 2 +- .../renderer_vulkan/vk_present_manager.cpp | 2 +- .../renderer_vulkan/vk_scheduler.cpp | 2 +- .../renderer_vulkan/vk_turbo_mode.cpp | 5 +- src/yuzu/bootmanager.cpp | 4 +- 14 files changed, 43 insertions(+), 364 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index 994bc184fa..b7a2f2aa9b 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -347,6 +347,16 @@ if (PLATFORM_LINUX OR CXX_CLANG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv9-a -mtune=generic") endif() endif() +elseif (ANDROID OR PLATFORM_FREEBSD OR PLATFORM_OPENBSD OR PLATFORM_SUN) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # libc++ has stop_token and jthread as experimental + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library") + else() + # Uses glibc, mostly? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + endif() endif() # Other presets, e.g. steamdeck diff --git a/src/audio_core/sink/sink_stream.cpp b/src/audio_core/sink/sink_stream.cpp index 4d7f0c1d5d..b75d743494 100644 --- a/src/audio_core/sink/sink_stream.cpp +++ b/src/audio_core/sink/sink_stream.cpp @@ -293,8 +293,7 @@ void SinkStream::WaitFreeSpace(std::stop_token stop_token) { release_cv.wait_for(lk, std::chrono::milliseconds(5), [this]() { return paused || queued_buffers < max_queue_size; }); if (queued_buffers > max_queue_size + 3) { - Common::CondvarWait(release_cv, lk, stop_token, - [this] { return paused || queued_buffers < max_queue_size; }); + release_cv.wait(lk, stop_token, [this] { return paused || queued_buffers < max_queue_size; }); } } diff --git a/src/common/bounded_threadsafe_queue.h b/src/common/bounded_threadsafe_queue.h index b36fc1de96..80f32e2553 100644 --- a/src/common/bounded_threadsafe_queue.h +++ b/src/common/bounded_threadsafe_queue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -123,7 +126,7 @@ private: } else if constexpr (Mode == PopMode::WaitWithStopToken) { // Wait until the queue is not empty. std::unique_lock lock{consumer_cv_mutex}; - Common::CondvarWait(consumer_cv, lock, stop_token, [this, read_index] { + consumer_cv.wait(lock, stop_token, [this, read_index] { return read_index != m_write_index.load(std::memory_order::acquire); }); if (stop_token.stop_requested()) { diff --git a/src/common/polyfill_thread.h b/src/common/polyfill_thread.h index 12e59a8939..0d3c963d25 100644 --- a/src/common/polyfill_thread.h +++ b/src/common/polyfill_thread.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -7,23 +10,13 @@ #pragma once -#include - -#ifdef __cpp_lib_jthread - #include #include -#include #include #include namespace Common { -template -void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred&& pred) { - cv.wait(lk, token, std::forward(pred)); -} - template bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) { std::condition_variable_any cv; @@ -35,341 +28,3 @@ bool StoppableTimedWait(std::stop_token token, const std::chrono::duration -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace std { -namespace polyfill { - -using stop_state_callback = size_t; - -class stop_state { -public: - stop_state() = default; - ~stop_state() = default; - - bool request_stop() { - unique_lock lk{m_lock}; - - if (m_stop_requested) { - // Already set, nothing to do. - return false; - } - - // Mark stop requested. - m_stop_requested = true; - - while (!m_callbacks.empty()) { - // Get an iterator to the first element. - const auto it = m_callbacks.begin(); - - // Move the callback function out of the map. - function f; - swap(it->second, f); - - // Erase the now-empty map element. - m_callbacks.erase(it); - - // Run the callback. - if (f) { - f(); - } - } - - return true; - } - - bool stop_requested() const { - unique_lock lk{m_lock}; - return m_stop_requested; - } - - stop_state_callback insert_callback(function f) { - unique_lock lk{m_lock}; - - if (m_stop_requested) { - // Stop already requested. Don't insert anything, - // just run the callback synchronously. - if (f) { - f(); - } - return 0; - } - - // Insert the callback. - stop_state_callback ret = ++m_next_callback; - m_callbacks.emplace(ret, std::move(f)); - return ret; - } - - void remove_callback(stop_state_callback cb) { - unique_lock lk{m_lock}; - m_callbacks.erase(cb); - } - -private: - mutable recursive_mutex m_lock; - map> m_callbacks; - stop_state_callback m_next_callback{0}; - bool m_stop_requested{false}; -}; - -} // namespace polyfill - -class stop_token; -class stop_source; -struct nostopstate_t { - explicit nostopstate_t() = default; -}; -inline constexpr nostopstate_t nostopstate{}; - -template -class stop_callback; - -class stop_token { -public: - stop_token() noexcept = default; - - stop_token(const stop_token&) noexcept = default; - stop_token(stop_token&&) noexcept = default; - stop_token& operator=(const stop_token&) noexcept = default; - stop_token& operator=(stop_token&&) noexcept = default; - ~stop_token() = default; - - void swap(stop_token& other) noexcept { - m_stop_state.swap(other.m_stop_state); - } - - [[nodiscard]] bool stop_requested() const noexcept { - return m_stop_state && m_stop_state->stop_requested(); - } - [[nodiscard]] bool stop_possible() const noexcept { - return m_stop_state != nullptr; - } - -private: - friend class stop_source; - template - friend class stop_callback; - stop_token(shared_ptr stop_state) : m_stop_state(std::move(stop_state)) {} - -private: - shared_ptr m_stop_state; -}; - -class stop_source { -public: - stop_source() : m_stop_state(make_shared()) {} - explicit stop_source(nostopstate_t) noexcept {} - - stop_source(const stop_source&) noexcept = default; - stop_source(stop_source&&) noexcept = default; - stop_source& operator=(const stop_source&) noexcept = default; - stop_source& operator=(stop_source&&) noexcept = default; - ~stop_source() = default; - void swap(stop_source& other) noexcept { - m_stop_state.swap(other.m_stop_state); - } - - [[nodiscard]] stop_token get_token() const noexcept { - return stop_token(m_stop_state); - } - [[nodiscard]] bool stop_possible() const noexcept { - return m_stop_state != nullptr; - } - [[nodiscard]] bool stop_requested() const noexcept { - return m_stop_state && m_stop_state->stop_requested(); - } - bool request_stop() noexcept { - return m_stop_state && m_stop_state->request_stop(); - } - -private: - friend class jthread; - explicit stop_source(shared_ptr stop_state) - : m_stop_state(std::move(stop_state)) {} - -private: - shared_ptr m_stop_state; -}; - -template -class stop_callback { - static_assert(is_nothrow_destructible_v); - static_assert(is_invocable_v); - -public: - using callback_type = Callback; - - template - requires constructible_from - explicit stop_callback(const stop_token& st, - C&& cb) noexcept(is_nothrow_constructible_v) - : m_stop_state(st.m_stop_state) { - if (m_stop_state) { - m_callback = m_stop_state->insert_callback(std::move(cb)); - } - } - template - requires constructible_from - explicit stop_callback(stop_token&& st, - C&& cb) noexcept(is_nothrow_constructible_v) - : m_stop_state(std::move(st.m_stop_state)) { - if (m_stop_state) { - m_callback = m_stop_state->insert_callback(std::move(cb)); - } - } - ~stop_callback() { - if (m_stop_state && m_callback) { - m_stop_state->remove_callback(m_callback); - } - } - - stop_callback(const stop_callback&) = delete; - stop_callback(stop_callback&&) = delete; - stop_callback& operator=(const stop_callback&) = delete; - stop_callback& operator=(stop_callback&&) = delete; - -private: - shared_ptr m_stop_state; - polyfill::stop_state_callback m_callback; -}; - -template -stop_callback(stop_token, Callback) -> stop_callback; - -class jthread { -public: - using id = thread::id; - using native_handle_type = thread::native_handle_type; - - jthread() noexcept = default; - - template , jthread>>> - explicit jthread(F&& f, Args&&... args) - : m_stop_state(make_shared()), - m_thread(make_thread(std::forward(f), std::forward(args)...)) {} - - ~jthread() { - if (joinable()) { - request_stop(); - join(); - } - } - - jthread(const jthread&) = delete; - jthread(jthread&&) noexcept = default; - jthread& operator=(const jthread&) = delete; - - jthread& operator=(jthread&& other) noexcept { - m_thread.swap(other.m_thread); - m_stop_state.swap(other.m_stop_state); - return *this; - } - - void swap(jthread& other) noexcept { - m_thread.swap(other.m_thread); - m_stop_state.swap(other.m_stop_state); - } - [[nodiscard]] bool joinable() const noexcept { - return m_thread.joinable(); - } - void join() { - m_thread.join(); - } - void detach() { - m_thread.detach(); - m_stop_state.reset(); - } - - [[nodiscard]] id get_id() const noexcept { - return m_thread.get_id(); - } - [[nodiscard]] native_handle_type native_handle() { - return m_thread.native_handle(); - } - [[nodiscard]] stop_source get_stop_source() noexcept { - return stop_source(m_stop_state); - } - [[nodiscard]] stop_token get_stop_token() const noexcept { - return stop_source(m_stop_state).get_token(); - } - bool request_stop() noexcept { - return get_stop_source().request_stop(); - } - [[nodiscard]] static unsigned int hardware_concurrency() noexcept { - return thread::hardware_concurrency(); - } - -private: - template - thread make_thread(F&& f, Args&&... args) { - if constexpr (is_invocable_v, stop_token, decay_t...>) { - return thread(std::forward(f), get_stop_token(), std::forward(args)...); - } else { - return thread(std::forward(f), std::forward(args)...); - } - } - - shared_ptr m_stop_state; - thread m_thread; -}; - -} // namespace std - -namespace Common { - -template -void CondvarWait(Condvar& cv, std::unique_lock& lk, std::stop_token token, Pred pred) { - if (token.stop_requested()) { - return; - } - - std::stop_callback callback(token, [&] { - { std::scoped_lock lk2{*lk.mutex()}; } - cv.notify_all(); - }); - - cv.wait(lk, [&] { return pred() || token.stop_requested(); }); -} - -template -bool StoppableTimedWait(std::stop_token token, const std::chrono::duration& rel_time) { - if (token.stop_requested()) { - return false; - } - - bool stop_requested = false; - std::condition_variable cv; - std::mutex m; - - std::stop_callback cb(token, [&] { - // Wake up the waiting thread. - { - std::scoped_lock lk{m}; - stop_requested = true; - } - cv.notify_one(); - }); - - // Perform the timed wait. - std::unique_lock lk{m}; - return !cv.wait_for(lk, rel_time, [&] { return stop_requested; }); -} - -} // namespace Common - -#endif diff --git a/src/common/thread.h b/src/common/thread.h index c6976fb6cd..5ab495baaa 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2013 Dolphin Emulator Project // SPDX-FileCopyrightText: 2014 Citra Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -80,7 +83,7 @@ public: condvar.notify_all(); return true; } else { - CondvarWait(condvar, lk, token, + condvar.wait(lk, token, [this, current_generation] { return current_generation != generation; }); return !token.stop_requested(); } diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index 260ad44e45..6ec9d6a2bd 100644 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -47,8 +50,8 @@ public: if (requests.empty()) { wait_condition.notify_all(); } - Common::CondvarWait(condition, lock, stop_token, - [this] { return !requests.empty(); }); + condition.wait(lock, stop_token, + [this] { return !requests.empty(); }); if (stop_token.stop_requested()) { break; } diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 2ef1da0644..28bb46722b 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: 2010 Dolphin Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -99,7 +102,7 @@ public: T PopWait(std::stop_token stop_token) { if (Empty()) { std::unique_lock lock{cv_mutex}; - Common::CondvarWait(cv, lock, stop_token, [this] { return !Empty(); }); + cv.wait(lock, stop_token, [this] { return !Empty(); }); } if (stop_token.stop_requested()) { return T{}; diff --git a/src/video_core/cdma_pusher.cpp b/src/video_core/cdma_pusher.cpp index 1b6b4c6d45..b9140d9335 100644 --- a/src/video_core/cdma_pusher.cpp +++ b/src/video_core/cdma_pusher.cpp @@ -39,8 +39,8 @@ void CDmaPusher::ProcessEntries(std::stop_token stop_token) { while (!stop_token.stop_requested()) { { std::unique_lock l{command_mutex}; - Common::CondvarWait(command_cv, l, stop_token, - [this]() { return command_lists.size() > 0; }); + command_cv.wait(l, stop_token, + [this]() { return command_lists.size() > 0; }); if (stop_token.stop_requested()) { return; } diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 9392534e7d..8739905d99 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -116,7 +116,7 @@ u64 ThreadManager::PushCommand(CommandData&& command_data, bool block) { state.queue.EmplaceWait(std::move(command_data), fence, block); if (block) { - Common::CondvarWait(state.cv, lk, thread.get_stop_token(), [this, fence] { + state.cv.wait(lk, thread.get_stop_token(), [this, fence] { return fence <= state.signaled_fence.load(std::memory_order_relaxed); }); } diff --git a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp index 6761127582..e65755de64 100644 --- a/src/video_core/renderer_vulkan/vk_master_semaphore.cpp +++ b/src/video_core/renderer_vulkan/vk_master_semaphore.cpp @@ -208,7 +208,7 @@ void MasterSemaphore::WaitThread(std::stop_token token) { vk::Fence fence; { std::unique_lock lock{wait_mutex}; - Common::CondvarWait(wait_cv, lock, token, [this] { return !wait_queue.empty(); }); + wait_cv.wait(lock, token, [this] { return !wait_queue.empty(); }); if (token.stop_requested()) { return; } diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 23279e49b9..0b29ad1389 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -279,7 +279,7 @@ void PresentManager::PresentThread(std::stop_token token) { std::unique_lock lock{queue_mutex}; // Wait for presentation frames - Common::CondvarWait(frame_cv, lock, token, [this] { return !present_queue.empty(); }); + frame_cv.wait(lock, token, [this] { return !present_queue.empty(); }); if (token.stop_requested()) { return; } diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 530d161dfe..d109d22cab 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -166,7 +166,7 @@ void Scheduler::WorkerThread(std::stop_token stop_token) { std::unique_lock lk{queue_mutex}; // Wait for work. - Common::CondvarWait(event_cv, lk, stop_token, [&] { return TryPopQueue(work); }); + event_cv.wait(lk, stop_token, [&] { return TryPopQueue(work); }); // If we've been asked to stop, we're done. if (stop_token.stop_requested()) { diff --git a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp index 04a51f2d1e..54183be12c 100644 --- a/src/video_core/renderer_vulkan/vk_turbo_mode.cpp +++ b/src/video_core/renderer_vulkan/vk_turbo_mode.cpp @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2022 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -224,7 +227,7 @@ void TurboMode::Run(std::stop_token stop_token) { #endif // Wait for the next graphics queue submission if necessary. std::unique_lock lk{m_submission_lock}; - Common::CondvarWait(m_submission_cv, lk, stop_token, [this] { + m_submission_cv.wait(lk, stop_token, [this] { return (std::chrono::steady_clock::now() - m_submission_time) <= std::chrono::milliseconds{100}; }); diff --git a/src/yuzu/bootmanager.cpp b/src/yuzu/bootmanager.cpp index 7b5f2a314f..cdc4e4024a 100644 --- a/src/yuzu/bootmanager.cpp +++ b/src/yuzu/bootmanager.cpp @@ -108,13 +108,13 @@ void EmuThread::run() { m_system.Run(); m_stopped.Reset(); - Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return !m_should_run; }); + m_should_run_cv.wait(lk, stop_token, [&] { return !m_should_run; }); } else { m_system.Pause(); m_stopped.Set(); EmulationPaused(lk); - Common::CondvarWait(m_should_run_cv, lk, stop_token, [&] { return m_should_run; }); + m_should_run_cv.wait(lk, stop_token, [&] { return m_should_run; }); EmulationResumed(lk); } } From 96133c24649119120a634e9ad9d588c24e6d9055 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 19 Sep 2025 06:58:48 +0000 Subject: [PATCH 13/14] [cmake] fix android? Signed-off-by: lizzie --- CMakeLists.txt | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/CMakeLists.txt b/CMakeLists.txt index b7a2f2aa9b..b0cedbf33e 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -317,6 +317,18 @@ if (YUZU_ROOM) add_compile_definitions(YUZU_ROOM) endif() +if (ANDROID OR PLATFORM_FREEBSD OR PLATFORM_OPENBSD OR PLATFORM_SUN) + if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") + # libc++ has stop_token and jthread as experimental + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library") + else() + # Uses glibc, mostly? + set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") + endif() +endif() + # Build/optimization presets if (PLATFORM_LINUX OR CXX_CLANG) if (ARCHITECTURE_x86_64) @@ -347,16 +359,6 @@ if (PLATFORM_LINUX OR CXX_CLANG) set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -march=armv9-a -mtune=generic") endif() endif() -elseif (ANDROID OR PLATFORM_FREEBSD OR PLATFORM_OPENBSD OR PLATFORM_SUN) - if(CMAKE_CXX_COMPILER_ID MATCHES "Clang") - # libc++ has stop_token and jthread as experimental - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -fexperimental-library") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -fexperimental-library") - else() - # Uses glibc, mostly? - set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") - set(CMAKE_C_FLAGS "${CMAKE_C_FLAGS} -D_LIBCPP_ENABLE_EXPERIMENTAL=1") - endif() endif() # Other presets, e.g. steamdeck From 91697b11ea8b84204feb6dd3b8933f8f351768c9 Mon Sep 17 00:00:00 2001 From: lizzie Date: Fri, 19 Sep 2025 07:35:53 +0000 Subject: [PATCH 14/14] [cmake] fix android once for all Signed-off-by: lizzie --- src/common/threadsafe_queue.h | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/common/threadsafe_queue.h b/src/common/threadsafe_queue.h index 28bb46722b..f9fc0ca171 100644 --- a/src/common/threadsafe_queue.h +++ b/src/common/threadsafe_queue.h @@ -102,7 +102,11 @@ public: T PopWait(std::stop_token stop_token) { if (Empty()) { std::unique_lock lock{cv_mutex}; - cv.wait(lock, stop_token, [this] { return !Empty(); }); + if constexpr (with_stop_token) { + cv.wait(lock, stop_token, [this] { return !Empty(); }); + } else { + cv.wait(lock, [this] { return !Empty(); }); + } } if (stop_token.stop_requested()) { return T{};