From 3f52bf4b4b34922f9991e4696d27a8f432fe6ea2 Mon Sep 17 00:00:00 2001 From: CamilleLaVey Date: Sat, 8 Aug 2026 04:39:47 +0200 Subject: [PATCH] [core, gpu, threads] Multithreading refactor (#4254) This insufferable work tries to cover some holes on previous threading implementation from yuzu's team, starting with Windows and Linux reordering of priorities (NICE), reworks previous Android's threading and cpu affinity with adpf, adjust emulated clocks/gpu for better "accuracy" with their work, bumps android minSDK for all flavors, legacy will now work with AP 29 to cover A10 - A12, standard will reach A13 as base and finally the optimized build will come with API 35, mostly targeted on devices with A15 support and newer, NDK and AGP wasn't upgraded yet. The performance cost efficiency have been improved based on device power configuration; preventing overheating if certain devices tended to fall into NICE0 (not allocated threads priority, all task ran with higher priority, 11 tasks running within the limited 2 - 7 threads available on the most common configuration 1x3x4 or 1x4x3). Reviewed-on: https://git.eden-emu.dev/eden-emu/eden/pulls/4254 --- src/android/app/build.gradle.kts | 3 + .../java/org/yuzu/yuzu_emu/NativeLibrary.kt | 2 + .../settings/model/view/SettingsItem.kt | 2 +- .../settings/ui/SettingsFragmentPresenter.kt | 4 +- .../yuzu_emu/fragments/EmulationFragment.kt | 1 + src/android/app/src/main/jni/native.cpp | 5 + .../app/src/main/res/values-ar/strings.xml | 1 - .../app/src/main/res/values-de/strings.xml | 1 - .../app/src/main/res/values-es/strings.xml | 1 - .../app/src/main/res/values-fr/strings.xml | 1 - .../app/src/main/res/values-pl/strings.xml | 1 - .../app/src/main/res/values-ru/strings.xml | 1 - .../app/src/main/res/values-uk/strings.xml | 1 - .../src/main/res/values-zh-rCN/strings.xml | 1 - .../app/src/main/res/values/arrays.xml | 4 +- .../app/src/main/res/values/strings.xml | 24 +- .../app/src/main/res/xml/game_mode_config.xml | 2 +- src/common/CMakeLists.txt | 2 + src/common/adpf.cpp | 317 +++++++++++ src/common/adpf.h | 26 + src/common/settings.h | 20 +- src/common/settings_enums.h | 4 +- src/common/thread.cpp | 538 ++++++++++++++++-- src/common/thread.h | 14 +- src/common/thread_worker.h | 21 +- src/core/core.cpp | 2 + src/core/core_timing.cpp | 23 +- src/core/cpu_manager.cpp | 7 +- .../service/nvdrv/devices/nvdisp_disp0.cpp | 6 + .../hle/service/nvdrv/devices/nvdisp_disp0.h | 5 + .../service/nvnflinger/hardware_composer.cpp | 63 +- .../service/nvnflinger/hardware_composer.h | 3 +- src/core/hle/service/vi/conductor.cpp | 32 ++ src/core/hle/service/vi/conductor.h | 1 + src/qt_common/config/shared_translation.cpp | 29 +- src/video_core/fence_manager.h | 1 + src/video_core/gpu.cpp | 86 ++- src/video_core/gpu.h | 4 +- src/video_core/gpu_thread.cpp | 1 + .../renderer_vulkan/vk_pipeline_cache.cpp | 7 +- .../renderer_vulkan/vk_present_manager.cpp | 2 + .../renderer_vulkan/vk_scheduler.cpp | 2 + .../texture_cache/texture_cache_base.h | 3 +- src/video_core/textures/workers.cpp | 3 +- src/yuzu/configuration/configure_cpu.cpp | 3 +- 45 files changed, 1093 insertions(+), 187 deletions(-) create mode 100644 src/common/adpf.cpp create mode 100644 src/common/adpf.h diff --git a/src/android/app/build.gradle.kts b/src/android/app/build.gradle.kts index a4076978a1..0cef7e7196 100644 --- a/src/android/app/build.gradle.kts +++ b/src/android/app/build.gradle.kts @@ -188,6 +188,7 @@ android { create("mainline") { dimension = "version" isDefault = true + minSdk = 33 manifestPlaceholders += mapOf("appNameBase" to "Eden") resValue("string", "app_name_suffixed", "Eden") @@ -199,6 +200,7 @@ android { create("genshinSpoof") { dimension = "version" + minSdk = 35 manifestPlaceholders += mapOf("appNameBase" to "Eden Optimized") resValue("string", "app_name_suffixed", "Eden Optimized") applicationId = "com.miHoYo.Yuanshen" @@ -216,6 +218,7 @@ android { create("legacy") { dimension = "version" + minSdk = 29 manifestPlaceholders += mapOf("appNameBase" to "Eden Legacy") resValue("string", "app_name_suffixed", "Eden Legacy") applicationId = "dev.legacy.eden_emulator" diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt index a8d6eaf6e1..797ac803eb 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt @@ -218,6 +218,8 @@ object NativeLibrary { external fun logSettings() + external fun refreshThreadPolicies() + external fun getDebugKnobAt(index: Int): Boolean /** diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt index 7c6063988f..63ac7b0630 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt @@ -594,7 +594,7 @@ abstract class SettingsItem( IntSetting.ANDROID_PIPELINE_WORKERS, titleId = R.string.pipeline_worker_cores, descriptionId = R.string.pipeline_worker_cores_description, - min = 4, + min = 2, max = 8, units = "cores" ) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt index 81702d7110..594099f159 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt @@ -250,8 +250,9 @@ class SettingsFragmentPresenter( add(BooleanSetting.USE_CUSTOM_RTC.key) add(LongSetting.CUSTOM_RTC.key) - add(HeaderSetting(R.string.cpu)) + add(HeaderSetting(R.string.clocks)) add(IntSetting.FAST_CPU_TIME.key) + add(IntSetting.FAST_GPU_TIME.key) add(BooleanSetting.CORE_SYNC_CORE_SPEED.key) add(IntSetting.MEMORY_LAYOUT.key) @@ -298,7 +299,6 @@ class SettingsFragmentPresenter( add(HeaderSetting(R.string.hacks)) - add(IntSetting.FAST_GPU_TIME.key) add(BooleanSetting.SKIP_CPU_INNER_INVALIDATION.key) add(BooleanSetting.FIX_BLOOM_EFFECTS.key) add(BooleanSetting.EMULATE_BGR565.key) diff --git a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt index 0069e169b3..5b93c88161 100644 --- a/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt +++ b/src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt @@ -1451,6 +1451,7 @@ class EmulationFragment : Fragment(), SurfaceHolder.Callback { override fun onResume() { super.onResume() + NativeLibrary.refreshThreadPolicies() val b = _binding ?: return updateStatsPosition(IntSetting.PERF_OVERLAY_POSITION.getInt()) updateSocPosition(IntSetting.SOC_OVERLAY_POSITION.getInt()) diff --git a/src/android/app/src/main/jni/native.cpp b/src/android/app/src/main/jni/native.cpp index 41ba1b2019..c367a06dfd 100644 --- a/src/android/app/src/main/jni/native.cpp +++ b/src/android/app/src/main/jni/native.cpp @@ -50,6 +50,7 @@ extern "C" { #include "common/scope_exit.h" #include "common/settings.h" #include "common/string_util.h" +#include "common/thread.h" #include "frontend_common/play_time_manager.h" #include "core/constants.h" #include "core/core.h" @@ -1182,6 +1183,10 @@ void Java_org_yuzu_yuzu_1emu_NativeLibrary_logSettings(JNIEnv* env, jobject jobj Settings::LogSettings(); } +void Java_org_yuzu_yuzu_1emu_NativeLibrary_refreshThreadPolicies(JNIEnv* env, jobject jobj) { + Common::RefreshThreadPolicies(); +} + jboolean Java_org_yuzu_yuzu_1emu_NativeLibrary_getDebugKnobAt(JNIEnv* env, jobject jobj, jint index) { return static_cast(Settings::getDebugKnobAt(static_cast(index))); } diff --git a/src/android/app/src/main/res/values-ar/strings.xml b/src/android/app/src/main/res/values-ar/strings.xml index 616f160fd1..dd03ec453f 100644 --- a/src/android/app/src/main/res/values-ar/strings.xml +++ b/src/android/app/src/main/res/values-ar/strings.xml @@ -958,7 +958,6 @@ سريع (2000MHz) - تعطيل متوسط (256) مرتفع (512) diff --git a/src/android/app/src/main/res/values-de/strings.xml b/src/android/app/src/main/res/values-de/strings.xml index 182247580c..1c3b6c148f 100644 --- a/src/android/app/src/main/res/values-de/strings.xml +++ b/src/android/app/src/main/res/values-de/strings.xml @@ -884,7 +884,6 @@ Wirklich fortfahren? Schnell (2000MHz) - Aus Mittel (256) Hoch (512) diff --git a/src/android/app/src/main/res/values-es/strings.xml b/src/android/app/src/main/res/values-es/strings.xml index 37bd14b2d1..a5092c4b2f 100644 --- a/src/android/app/src/main/res/values-es/strings.xml +++ b/src/android/app/src/main/res/values-es/strings.xml @@ -950,7 +950,6 @@ Rápido (2000MHz) - Desactivado Medio (256) Alto (512) diff --git a/src/android/app/src/main/res/values-fr/strings.xml b/src/android/app/src/main/res/values-fr/strings.xml index 2cf6a1da91..602a542c5f 100644 --- a/src/android/app/src/main/res/values-fr/strings.xml +++ b/src/android/app/src/main/res/values-fr/strings.xml @@ -897,7 +897,6 @@ Rapide (2000MHz) - Désactivé Moyen (256) Élevé (512) diff --git a/src/android/app/src/main/res/values-pl/strings.xml b/src/android/app/src/main/res/values-pl/strings.xml index d96d420a98..4bcebd5a8c 100644 --- a/src/android/app/src/main/res/values-pl/strings.xml +++ b/src/android/app/src/main/res/values-pl/strings.xml @@ -871,7 +871,6 @@ Szybkie (2000MHz) - Wyłączone Średnie (256) Wysokie (512) diff --git a/src/android/app/src/main/res/values-ru/strings.xml b/src/android/app/src/main/res/values-ru/strings.xml index 6e85084b77..3dbeb6ffdb 100644 --- a/src/android/app/src/main/res/values-ru/strings.xml +++ b/src/android/app/src/main/res/values-ru/strings.xml @@ -954,7 +954,6 @@ Быстрая (2000MHz) - Выкл. Среднее (256) Высокое (512) diff --git a/src/android/app/src/main/res/values-uk/strings.xml b/src/android/app/src/main/res/values-uk/strings.xml index 86ea6ba787..83893210bd 100644 --- a/src/android/app/src/main/res/values-uk/strings.xml +++ b/src/android/app/src/main/res/values-uk/strings.xml @@ -943,7 +943,6 @@ Швидко (2000 МГц) - Вимкнено Середньо (256) Високо (512) diff --git a/src/android/app/src/main/res/values-zh-rCN/strings.xml b/src/android/app/src/main/res/values-zh-rCN/strings.xml index ff2db086e3..1e6ba26041 100644 --- a/src/android/app/src/main/res/values-zh-rCN/strings.xml +++ b/src/android/app/src/main/res/values-zh-rCN/strings.xml @@ -948,7 +948,6 @@ 快速 (2000MHz) - 关闭 中 (256) 高 (512) diff --git a/src/android/app/src/main/res/values/arrays.xml b/src/android/app/src/main/res/values/arrays.xml index 7a69d5b48b..1bad110d14 100644 --- a/src/android/app/src/main/res/values/arrays.xml +++ b/src/android/app/src/main/res/values/arrays.xml @@ -34,7 +34,7 @@ - @string/off + @string/clock_normal @string/clock_boost @string/clock_fast @@ -547,7 +547,7 @@ - @string/off + @string/fast_gpu_normal @string/fast_gpu_medium @string/fast_gpu_high diff --git a/src/android/app/src/main/res/values/strings.xml b/src/android/app/src/main/res/values/strings.xml index 3023327f14..bb1d85db82 100644 --- a/src/android/app/src/main/res/values/strings.xml +++ b/src/android/app/src/main/res/values/strings.xml @@ -450,8 +450,8 @@ Set custom RTC - CPU Overclock - Forces the emulated CPU to run at a higher clock, reducing certain FPS limiters. Use Boost (1700MHz) to run at the Switch\'s highest native clock, or Fast (2000MHz) to run at 2x clock. + CPU Clocks + Raises the clock the emulated CPU reports, which removes some FPS limiters. Weaker CPUs may see reduced performance, and certain games may behave improperly. Custom CPU Ticks Set a custom value of CPU ticks. Higher values can increase performance, but may also cause the game to freeze. A range of 77–21000 is recommended. Ticks @@ -512,8 +512,8 @@ Hacks - Fast GPU Time - Forces most games to run at their highest native resolution. Use 256 for maximal performance and 512 for maximal graphics fidelity. + GPU Clocks + Makes the game believe GPU work finishes faster than it does, so it stops lowering resolution and render distance to fit the Switch\'s clocks. Skip CPU Inner Invalidation Skips certain CPU-side cache invalidations during memory updates, reducing CPU usage and improving it\'s performance. This may cause glitches or crashes on some games. Fix Bloom Effects @@ -560,6 +560,7 @@ CPU + Clocks Use Auto Stub Automatically stub missing services and functions. This may improve compatibility but can cause crashes and stability issues. @@ -968,14 +969,15 @@ 6GB (Unsafe) 8GB (Unsafe) - - Boost (1700MHz) - Fast (2000MHz) + + Normal + Boost + Overclock - - Off - Medium (256) - High (512) + + Normal + Boost + Overclock Very Small (16 MB) diff --git a/src/android/app/src/main/res/xml/game_mode_config.xml b/src/android/app/src/main/res/xml/game_mode_config.xml index 8f6e90d0d2..69e038cb5e 100644 --- a/src/android/app/src/main/res/xml/game_mode_config.xml +++ b/src/android/app/src/main/res/xml/game_mode_config.xml @@ -1,7 +1,7 @@ \ No newline at end of file diff --git a/src/common/CMakeLists.txt b/src/common/CMakeLists.txt index 87c4642f04..923d001442 100644 --- a/src/common/CMakeLists.txt +++ b/src/common/CMakeLists.txt @@ -121,6 +121,8 @@ add_library( swap.h thread.cpp thread.h + adpf.cpp + adpf.h thread_queue_list.h thread_worker.h threadsafe_queue.h diff --git a/src/common/adpf.cpp b/src/common/adpf.cpp new file mode 100644 index 0000000000..8fa2996173 --- /dev/null +++ b/src/common/adpf.cpp @@ -0,0 +1,317 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#include "common/adpf.h" + +#ifdef __ANDROID__ + +#include +#include +#include +#include +#include +#include + +#include +#include + +#include "common/logging.h" + +namespace Common::ADPF { + +namespace { + +constexpr std::chrono::nanoseconds DEFAULT_TARGET = std::chrono::nanoseconds{16'666'667}; + +struct AHintManager; +struct AHintSession; + +using PFN_GetManager = AHintManager* (*)(); +using PFN_CreateSession = AHintSession* (*)(AHintManager*, const s32*, size_t, s64); +using PFN_CloseSession = void (*)(AHintSession*); +using PFN_UpdateTarget = int (*)(AHintSession*, s64); +using PFN_ReportActual = int (*)(AHintSession*, s64); +using PFN_SetThreads = int (*)(AHintSession*, const pid_t*, size_t); +using PFN_SetPowerEfficiency = int (*)(AHintSession*, bool); + +struct Api { + PFN_GetManager get_manager = nullptr; + PFN_CreateSession create_session = nullptr; + PFN_CloseSession close_session = nullptr; + PFN_UpdateTarget update_target = nullptr; + PFN_ReportActual report_actual = nullptr; + PFN_SetThreads set_threads = nullptr; + PFN_SetPowerEfficiency set_power_efficiency = nullptr; + AHintManager* manager = nullptr; + bool usable = false; +}; + +const Api& Resolve() { + static const Api api = [] { + Api resolved; + void* library = dlopen("libandroid.so", RTLD_NOW); + if (library == nullptr) { + LOG_INFO(Common, "libandroid.so unavailable, ADPF is disabled"); + return resolved; + } + const auto load = [library](const char* name) { return dlsym(library, name); }; + + resolved.get_manager = reinterpret_cast(load("APerformanceHint_getManager")); + resolved.create_session = + reinterpret_cast(load("APerformanceHint_createSession")); + resolved.close_session = + reinterpret_cast(load("APerformanceHint_closeSession")); + resolved.update_target = + reinterpret_cast(load("APerformanceHint_updateTargetWorkDuration")); + resolved.report_actual = + reinterpret_cast(load("APerformanceHint_reportActualWorkDuration")); + resolved.set_threads = + reinterpret_cast(load("APerformanceHint_setThreads")); + resolved.set_power_efficiency = reinterpret_cast( + load("APerformanceHint_setPreferPowerEfficiency")); + + if (resolved.get_manager == nullptr || resolved.create_session == nullptr || + resolved.close_session == nullptr || resolved.update_target == nullptr || + resolved.report_actual == nullptr) { + LOG_INFO(Common, "Performance hint API not exported, ADPF is disabled"); + return resolved; + } + + resolved.manager = resolved.get_manager(); + if (resolved.manager == nullptr) { + LOG_INFO(Common, "Device does not provide a performance hint manager"); + return resolved; + } + + resolved.usable = true; + LOG_INFO(Common, "ADPF available, setThreads {}, power efficiency {}", + resolved.set_threads != nullptr ? "yes" : "no", + resolved.set_power_efficiency != nullptr ? "yes" : "no"); + return resolved; + }(); + return api; +} + +struct SessionState { + AHintSession* handle = nullptr; + std::vector threads; + bool unsupported = false; +}; + +std::mutex g_mutex; +std::array g_sessions; + +constexpr s64 MAX_REPORTED_TARGETS = 4; + +std::atomic g_target_ns{DEFAULT_TARGET.count()}; +thread_local std::chrono::steady_clock::time_point t_last_frame{}; + +SessionState& StateOf(Session session) { + return g_sessions[static_cast(session)]; +} + +bool IsBackgroundUsable(const Api& api) { + return api.set_power_efficiency != nullptr; +} + +void CloseLocked(SessionState& state) { + if (state.handle != nullptr) { + Resolve().close_session(state.handle); + state.handle = nullptr; + } +} + +AHintSession* CreateSessionFor(Session session, const std::vector& threads) { + const Api& api = Resolve(); + const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0; + + std::vector ids; + ids.reserve(threads.size()); + for (const pid_t tid : threads) { + ids.push_back(static_cast(tid)); + } + + AHintSession* handle = api.create_session(api.manager, ids.data(), ids.size(), target); + if (handle == nullptr && target == 0) { + handle = api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count()); + } + if (handle == nullptr) { + return nullptr; + } + if (session == Session::Background && api.set_power_efficiency != nullptr) { + api.set_power_efficiency(handle, true); + } + return handle; +} + +bool SyncLocked(Session session, SessionState& state) { + if (state.threads.empty()) { + CloseLocked(state); + return false; + } + + const Api& api = Resolve(); + if (state.handle != nullptr && api.set_threads != nullptr) { + std::vector ids = state.threads; + if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) { + return true; + } + } + + AHintSession* const replacement = CreateSessionFor(session, state.threads); + if (replacement == nullptr) { + if (state.handle == nullptr) { + state.unsupported = true; + } + LOG_WARNING(Common, "Could not open a performance hint session for {} threads, falling back", + state.threads.size()); + return false; + } + CloseLocked(state); + state.handle = replacement; + return true; +} + +} // Anonymous namespace + +bool IsSessionSupported(Session session) { + const Api& api = Resolve(); + if (!api.usable) { + return false; + } + if (session == Session::Background && !IsBackgroundUsable(api)) { + return false; + } + std::scoped_lock lock{g_mutex}; + return !StateOf(session).unsupported; +} + +bool AddCurrentThread(Session session) { + if (!IsSessionSupported(session)) { + return false; + } + + const pid_t tid = gettid(); + std::scoped_lock lock{g_mutex}; + + for (size_t i = 0; i < g_sessions.size(); ++i) { + SessionState& state = g_sessions[i]; + if (static_cast(session) == i) { + continue; + } + const auto it = std::find(state.threads.begin(), state.threads.end(), tid); + if (it != state.threads.end()) { + state.threads.erase(it); + SyncLocked(static_cast(i), state); + } + } + + SessionState& state = StateOf(session); + const bool added = + std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end(); + if (added) { + state.threads.push_back(tid); + } + if (!SyncLocked(session, state)) { + if (added) { + std::erase(state.threads, tid); + } + return false; + } + return true; +} + +void RemoveCurrentThread() { + if (!Resolve().usable) { + return; + } + const pid_t tid = gettid(); + std::scoped_lock lock{g_mutex}; + for (size_t i = 0; i < g_sessions.size(); ++i) { + SessionState& state = g_sessions[i]; + if (std::erase(state.threads, tid) != 0) { + SyncLocked(static_cast(i), state); + } + } +} + +void SetTargetWorkDuration(std::chrono::nanoseconds target) { + const Api& api = Resolve(); + if (!api.usable || target.count() <= 0) { + return; + } + if (g_target_ns.exchange(target.count(), std::memory_order_relaxed) == target.count()) { + return; + } + std::scoped_lock lock{g_mutex}; + SessionState& state = StateOf(Session::Render); + if (state.handle != nullptr) { + api.update_target(state.handle, target.count()); + } +} + +void ReportFrameInterval() { + const Api& api = Resolve(); + if (!api.usable) { + return; + } + + const auto now = std::chrono::steady_clock::now(); + const auto previous = t_last_frame; + t_last_frame = now; + if (previous.time_since_epoch().count() == 0) { + return; + } + + s64 actual = std::chrono::duration_cast(now - previous).count(); + if (actual <= 0) { + return; + } + + const s64 ceiling = g_target_ns.load(std::memory_order_relaxed) * MAX_REPORTED_TARGETS; + actual = (std::min)(actual, ceiling); + + std::scoped_lock lock{g_mutex}; + SessionState& state = StateOf(Session::Render); + if (state.handle != nullptr) { + api.report_actual(state.handle, actual); + } +} + +void Shutdown() { + if (!Resolve().usable) { + return; + } + std::scoped_lock lock{g_mutex}; + for (SessionState& state : g_sessions) { + CloseLocked(state); + state.threads.clear(); + state.unsupported = false; + } +} + +} // namespace Common::ADPF + +#else + +namespace Common::ADPF { + +bool IsSessionSupported(Session) { + return false; +} + +bool AddCurrentThread(Session) { + return false; +} + +void RemoveCurrentThread() {} + +void SetTargetWorkDuration(std::chrono::nanoseconds) {} + +void ReportFrameInterval() {} + +void Shutdown() {} + +} // namespace Common::ADPF + +#endif diff --git a/src/common/adpf.h b/src/common/adpf.h new file mode 100644 index 0000000000..6f14a0bee4 --- /dev/null +++ b/src/common/adpf.h @@ -0,0 +1,26 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + +#pragma once + +#include + +namespace Common::ADPF { + +enum class Session { + Render, + Background, +}; + +bool IsSessionSupported(Session session); + +bool AddCurrentThread(Session session); +void RemoveCurrentThread(); + +void SetTargetWorkDuration(std::chrono::nanoseconds target); + +void ReportFrameInterval(); + +void Shutdown(); + +} // namespace Common::ADPF diff --git a/src/common/settings.h b/src/common/settings.h index 00ba7dddc9..d25d5f690b 100644 --- a/src/common/settings.h +++ b/src/common/settings.h @@ -260,10 +260,10 @@ struct Values { Category::Cpu}; SwitchableSetting cpu_accuracy{linkage, CpuAccuracy::Auto, "cpu_accuracy", Category::Cpu}; - SwitchableSetting fast_cpu_time{linkage, - CpuClock::Off, + SwitchableSetting cpu_clock{linkage, + CpuClock::Normal, "fast_cpu_time", - Category::Cpu, + Category::System, Specialization::Default, true, true}; @@ -540,13 +540,13 @@ struct Values { #endif // Renderer Hacks // - SwitchableSetting fast_gpu_time{linkage, - GpuOverclock::Medium, - "fast_gpu_time", - Category::RendererHacks, - Specialization::Default, - true, - true}; + SwitchableSetting gpu_clock{linkage, + GpuClock::Boost, + "fast_gpu_time", + Category::System, + Specialization::Default, + true, + true}; SwitchableSetting skip_cpu_inner_invalidation{linkage, false, diff --git a/src/common/settings_enums.h b/src/common/settings_enums.h index 2db0e02a5f..3d0131de70 100644 --- a/src/common/settings_enums.h +++ b/src/common/settings_enums.h @@ -140,7 +140,7 @@ ENUM(DmaAccuracy, Default, Unsafe, Safe); ENUM(GpuFenceBehavior, Default, Immediate, Balanced, Accurate, Strict); ENUM(CpuBackend, Dynarmic, Nce); ENUM(CpuAccuracy, Auto, Accurate, Unsafe, Paranoid, Debugging); -ENUM(CpuClock, Off, Boost, Fast) +ENUM(CpuClock, Normal, Boost, Overclock) ENUM(MemoryLayout, Memory_4Gb, Memory_6Gb, Memory_8Gb, Memory_10Gb, Memory_12Gb); ENUM(ConfirmStop, Ask_Always, Ask_Based_On_Game, Ask_Never); ENUM(FullscreenMode, Borderless, Exclusive); @@ -152,7 +152,7 @@ ENUM(AspectRatio, R16_9, R4_3, R21_9, R16_10, Stretch); ENUM(ConsoleMode, Handheld, Docked); ENUM(AppletMode, HLE, LLE); ENUM(SpirvOptimizeMode, Never, OnLoad, Always); -ENUM(GpuOverclock, Normal, Medium, High) +ENUM(GpuClock, Normal, Boost, Overclock) ENUM(GpuUnswizzleSize, VerySmall, Small, Normal, Large, VeryLarge) ENUM(GpuUnswizzle, VeryLow, Low, Normal, Medium, High) ENUM(GpuUnswizzleChunk, VeryLow, Low, Normal, Medium, High) diff --git a/src/common/thread.cpp b/src/common/thread.cpp index c947c88373..7b4dec3dce 100644 --- a/src/common/thread.cpp +++ b/src/common/thread.cpp @@ -1,5 +1,6 @@ // SPDX-FileCopyrightText: Copyright 2026 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 @@ -9,6 +10,7 @@ #include #include +#include "common/adpf.h" #include "common/error.h" #include "common/logging.h" #include "common/assert.h" @@ -39,6 +41,402 @@ #include #endif +#ifdef __linux__ +#include +#include + +namespace { +constexpr int NICE_AUDIO = -16; +constexpr int NICE_URGENT_DISPLAY = -8; +constexpr int NICE_DISPLAY = -4; +constexpr int NICE_DEFAULT = 0; +constexpr int NICE_BACKGROUND = 10; + +int LowestAllowedNice() { + static const int lowest = [] { + rlimit limit{}; + if (getrlimit(RLIMIT_NICE, &limit) != 0) { + return 0; + } + if (limit.rlim_cur >= 40) { + return -20; + } + return 20 - static_cast(limit.rlim_cur); + }(); + return lowest; +} + +int NiceValueForPriority(Common::ThreadPriority priority) { + const int wanted = [priority] { + switch (priority) { + case Common::ThreadPriority::Low: return NICE_BACKGROUND; + case Common::ThreadPriority::Normal: return NICE_DEFAULT; + case Common::ThreadPriority::High: return NICE_DISPLAY; + case Common::ThreadPriority::VeryHigh: return NICE_URGENT_DISPLAY; + case Common::ThreadPriority::Critical: return NICE_AUDIO; + default: return NICE_DEFAULT; + } + }(); + return (std::max)(wanted, (std::min)(NICE_DEFAULT, LowestAllowedNice())); +} +} // Anonymous namespace +#endif + +#ifdef __ANDROID__ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace { +constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4; + +// A core counts as a performance core while it is within this much of the fastest one. +constexpr s64 ANDROID_PERFORMANCE_CAPACITY_PERCENT = 50; + +constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500}; + +enum class CoreGroup { + Unrestricted, + Performance, + Efficiency, +}; + +struct ThreadPolicy { + pid_t tid; + CoreGroup group; + s32 nice_value; + bool has_nice; +}; + +struct CoreInfo { + s64 weight; + u64 midr; + s32 cpu; +}; + +struct CpuTopologyState { + std::mutex topology_mutex; + cpu_set_t allowed{}; + cpu_set_t performance{}; + cpu_set_t efficiency{}; + bool separated = false; + bool initialized = false; + + pid_t canary_tid = 0; + cpu_set_t canary_mask{}; + bool canary_valid = false; + + std::atomic next_poll_ns{0}; + + std::mutex policy_mutex; + std::vector policies; +}; + +CpuTopologyState& State() { + static CpuTopologyState* const state = new CpuTopologyState(); + return *state; +} + +struct PolicyRegistration { + ~PolicyRegistration() { + const pid_t tid = gettid(); + ::Common::ADPF::RemoveCurrentThread(); + CpuTopologyState& state = State(); + std::scoped_lock lock{state.policy_mutex}; + std::erase_if(state.policies, + [tid](const ThreadPolicy& policy) { return policy.tid == tid; }); + } +}; + +thread_local PolicyRegistration t_policy_registration; + +s32 PossibleCpuCount() { + std::ifstream file("/sys/devices/system/cpu/possible"); + std::string list; + if (file && std::getline(file, list) && !list.empty()) { + s64 highest = -1; + const char* cursor = list.c_str(); + while (*cursor != '\0') { + char* end = nullptr; + const s64 value = std::strtol(cursor, &end, 10); + if (end == cursor) { + break; + } + highest = (std::max)(highest, value); + cursor = end; + while (*cursor == '-' || *cursor == ',') { + ++cursor; + } + } + if (highest >= 0) { + return static_cast((std::min)(highest + 1, CPU_SETSIZE)); + } + } + const s64 configured = sysconf(_SC_NPROCESSORS_CONF); + if (configured > 0) { + return static_cast((std::min)(configured, CPU_SETSIZE)); + } + return static_cast((std::min)(std::thread::hardware_concurrency(), CPU_SETSIZE)); +} + +s64 ReadCpuScalar(s32 cpu, const char* node) { + s64 value = 0; + std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + "/" + node); + if (!file || !(file >> value) || value <= 0) { + return 0; + } + return value; +} + +u64 ReadCpuMidr(s32 cpu) { + u64 midr = 0; + std::ifstream file("/sys/devices/system/cpu/cpu" + std::to_string(cpu) + + "/regs/identification/midr_el1"); + if (!file || !(file >> std::hex >> midr)) { + return 0; + } + return midr; +} + +std::vector CollectCores(const cpu_set_t& allowed, s32 total, const char* node, + bool require_all) { + std::vector cores; + for (s32 cpu = 0; cpu < total; ++cpu) { + if (!CPU_ISSET(cpu, &allowed)) { + continue; + } + const s64 weight = ReadCpuScalar(cpu, node); + if (weight <= 0) { + if (require_all) { + return {}; + } + LOG_WARNING(Common, "Could not read {} for CPU {}, treating it as an efficiency core", + node, cpu); + continue; + } + cores.push_back(CoreInfo{weight, ReadCpuMidr(cpu), cpu}); + } + return cores; +} + +bool WeightsAreUniform(const std::vector& cores) { + return std::all_of(cores.begin(), cores.end(), + [&](const CoreInfo& core) { return core.weight == cores.front().weight; }); +} + +bool MidrsAreDistinct(const std::vector& cores) { + return std::none_of(cores.begin(), cores.end(), + [](const CoreInfo& core) { return core.midr == 0; }) && + std::any_of(cores.begin(), cores.end(), + [&](const CoreInfo& core) { return core.midr != cores.front().midr; }); +} + +void ComputeTopologyLocked(CpuTopologyState& state) { + state.initialized = true; + state.separated = false; + CPU_ZERO(&state.allowed); + CPU_ZERO(&state.performance); + CPU_ZERO(&state.efficiency); + + if (sched_getaffinity(getpid(), sizeof(state.allowed), &state.allowed) != 0) { + LOG_WARNING(Common, "Could not query process CPU affinity: {}", + ::Common::GetLastErrorMsg()); + return; + } + + const s32 total = PossibleCpuCount(); + auto cores = CollectCores(state.allowed, total, "cpu_capacity", true); + if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) { + auto by_frequency = CollectCores(state.allowed, total, "cpufreq/cpuinfo_max_freq", false); + if (!by_frequency.empty()) { + cores = std::move(by_frequency); + } + } + if (cores.empty()) { + LOG_WARNING(Common, "Could not determine CPU topology, thread placement is disabled"); + return; + } + + if (WeightsAreUniform(cores)) { + if (MidrsAreDistinct(cores)) { + LOG_WARNING(Common, "CPU clusters differ but rank identically, thread placement is " + "disabled"); + } else { + LOG_INFO(Common, "CPU cores are symmetric, thread placement is disabled"); + } + return; + } + + std::sort(cores.begin(), cores.end(), [](const CoreInfo& lhs, const CoreInfo& rhs) { + if (lhs.weight != rhs.weight) { + return lhs.weight > rhs.weight; + } + return lhs.cpu < rhs.cpu; + }); + + const s64 fastest = cores.front().weight; + size_t taken = 0; + for (const auto& core : cores) { + const bool fast_enough = + core.weight * 100 >= fastest * ANDROID_PERFORMANCE_CAPACITY_PERCENT; + if (!fast_enough && taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) { + break; + } + CPU_SET(core.cpu, &state.performance); + ++taken; + } + if (taken == 0) { + return; + } + + for (s32 cpu = 0; cpu < total; ++cpu) { + if (CPU_ISSET(cpu, &state.allowed) && !CPU_ISSET(cpu, &state.performance)) { + CPU_SET(cpu, &state.efficiency); + } + } + + state.separated = CPU_COUNT(&state.efficiency) > 0; + LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}", + CPU_COUNT(&state.performance), CPU_COUNT(&state.efficiency), + state.separated ? "enabled" : "unavailable"); +} + +void EnsureTopologyLocked(CpuTopologyState& state) { + if (!state.initialized) { + ComputeTopologyLocked(state); + } +} + +void RefreshTopologyLocked(CpuTopologyState& state) { + if (!state.initialized) { + ComputeTopologyLocked(state); + return; + } + cpu_set_t current; + CPU_ZERO(¤t); + if (sched_getaffinity(getpid(), sizeof(current), ¤t) != 0) { + return; + } + if (std::memcmp(¤t, &state.allowed, sizeof(current)) != 0) { + ComputeTopologyLocked(state); + } +} + +bool ApplyCoreGroupLocked(CpuTopologyState& state, pid_t tid, CoreGroup group, + bool* gone = nullptr) { + const bool restrict_group = group != CoreGroup::Unrestricted && state.separated; + const cpu_set_t* mask = &state.allowed; + if (restrict_group) { + mask = group == CoreGroup::Performance ? &state.performance : &state.efficiency; + } + if (CPU_COUNT(mask) == 0) { + return false; + } + if (sched_setaffinity(tid, sizeof(*mask), mask) != 0) { + if (gone != nullptr && errno == ESRCH) { + *gone = true; + return false; + } + LOG_WARNING(Common, "Could not restrict thread {} to its core group: {}", tid, + ::Common::GetLastErrorMsg()); + return false; + } + return true; +} + +bool KernelPreservesRequestedAffinity() { + utsname info{}; + if (uname(&info) != 0) { + return false; + } + s32 major = 0; + s32 minor = 0; + if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) { + return false; + } + return major > 6 || (major == 6 && minor >= 2); +} + +void SnapshotCanaryLocked(CpuTopologyState& state) { + state.canary_valid = false; + if (!state.separated) { + return; + } + for (const auto& policy : state.policies) { + if (policy.group == CoreGroup::Unrestricted) { + continue; + } + cpu_set_t mask; + CPU_ZERO(&mask); + if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) { + continue; + } + state.canary_tid = policy.tid; + state.canary_mask = mask; + state.canary_valid = true; + return; + } +} + +bool DueForPoll(CpuTopologyState& state) { + const auto now = std::chrono::steady_clock::now().time_since_epoch(); + const s64 now_ns = std::chrono::duration_cast(now).count(); + s64 next = state.next_poll_ns.load(std::memory_order_relaxed); + if (now_ns < next) { + return false; + } + return state.next_poll_ns.compare_exchange_strong( + next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(), std::memory_order_relaxed); +} + +ThreadPolicy& AcquirePolicyLocked(CpuTopologyState& state, pid_t tid) { + for (auto& policy : state.policies) { + if (policy.tid == tid) { + return policy; + } + } + return state.policies.emplace_back(ThreadPolicy{tid, CoreGroup::Unrestricted, 0, false}); +} + +void SetCurrentThreadCoreGroup(CoreGroup group) { + const pid_t tid = gettid(); + (void)&t_policy_registration; + + CoreGroup effective = group; + if (tid == getpid() && group != CoreGroup::Unrestricted) { + LOG_WARNING(Common, "Refusing to place the main thread: the CPU topology is read from it"); + effective = CoreGroup::Unrestricted; + } + + CpuTopologyState& state = State(); + std::scoped_lock topology_lock{state.topology_mutex}; + EnsureTopologyLocked(state); + ApplyCoreGroupLocked(state, tid, effective); + + std::scoped_lock policy_lock{state.policy_mutex}; + AcquirePolicyLocked(state, tid).group = effective; + if (!state.canary_valid) { + SnapshotCanaryLocked(state); + } +} + +void RememberCurrentThreadNice(pid_t tid, s32 nice_value) { + (void)&t_policy_registration; + CpuTopologyState& state = State(); + std::scoped_lock lock{state.policy_mutex}; + ThreadPolicy& policy = AcquirePolicyLocked(state, tid); + policy.nice_value = nice_value; + policy.has_nice = true; +} +} // Anonymous namespace +#endif + #include "common/cpu_features.h" #ifdef ARCHITECTURE_x86_64 #ifdef _MSC_VER @@ -48,7 +446,6 @@ #endif #include "common/x64/rdtsc.h" #endif -#include "core/core_timing.h" namespace Common { @@ -78,21 +475,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) { } }(); set_thread_priority(find_thread(NULL), priority); +#elif defined(__ANDROID__) + const int nice_value = NiceValueForPriority(new_priority); + const pid_t tid = gettid(); + if (setpriority(PRIO_PROCESS, static_cast(tid), nice_value) != 0) { + LOG_WARNING(Common, "Could not set thread nice value to {}: {}", nice_value, + GetLastErrorMsg()); + return; + } + RememberCurrentThreadNice(tid, nice_value); +#elif defined(__linux__) + const int nice_value = NiceValueForPriority(new_priority); + if (setpriority(PRIO_PROCESS, 0, nice_value) != 0) { + LOG_DEBUG(Common, "Could not set thread nice value to {}: {}", nice_value, + GetLastErrorMsg()); + } #else - pthread_t this_thread = pthread_self(); - const auto scheduling_type = SCHED_OTHER; - s32 max_prio = sched_get_priority_max(scheduling_type); - s32 min_prio = sched_get_priority_min(scheduling_type); - u32 level = (std::max)(u32(new_priority) + 1, 4U); - - struct sched_param params; + const s32 max_prio = sched_get_priority_max(SCHED_OTHER); + const s32 min_prio = sched_get_priority_min(SCHED_OTHER); if (max_prio > min_prio) { - params.sched_priority = min_prio + ((max_prio - min_prio) * level) / 4; - } else { - params.sched_priority = min_prio - ((min_prio - max_prio) * level) / 4; + const u32 level = (std::min)(static_cast(new_priority), 4U); + sched_param params{}; + params.sched_priority = + min_prio + static_cast(static_cast(max_prio - min_prio) * level) / 4; + pthread_setschedparam(pthread_self(), SCHED_OTHER, ¶ms); } - - pthread_setschedparam(this_thread, scheduling_type, ¶ms); #endif } @@ -132,29 +539,94 @@ void SetCurrentThreadName(const char* name) { #endif } -void PinCurrentThreadToPerformanceCore(size_t core_id) { - ASSERT(core_id < 4); - // If we set a flag for a CPU that doesn't exist, the thread may not be allowed to - // run in ANY processor! - auto const total_cores = std::thread::hardware_concurrency(); - if (core_id < total_cores) { +void SetCurrentThreadToPerformanceCores() { #if defined(__ANDROID__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(core_id, &set); - sched_setaffinity(pthread_self(), sizeof(set), &set); -#elif defined(__linux__) || defined(__FreeBSD__) - cpu_set_t set; - CPU_ZERO(&set); - CPU_SET(core_id, &set); - pthread_setaffinity_np(pthread_self(), sizeof(set), &set); -#elif defined(_WIN32) - DWORD set = 1UL << core_id; - SetThreadAffinityMask(GetCurrentThread(), set); -#else - // No pin functionality implemented + if (ADPF::AddCurrentThread(ADPF::Session::Render)) { + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); + return; + } + SetCurrentThreadCoreGroup(CoreGroup::Performance); +#endif +} + +void SetCurrentThreadToEfficiencyCores() { +#if defined(__ANDROID__) + if (ADPF::AddCurrentThread(ADPF::Session::Background)) { + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); + return; + } + SetCurrentThreadCoreGroup(CoreGroup::Efficiency); +#endif +} + +void SetCurrentThreadToBackgroundWork() { +#if defined(__ANDROID__) + ADPF::AddCurrentThread(ADPF::Session::Background); + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); +#endif +} + +void SetCurrentThreadToAllCores() { +#if defined(__ANDROID__) + ADPF::RemoveCurrentThread(); + SetCurrentThreadCoreGroup(CoreGroup::Unrestricted); +#endif +} + +void RefreshThreadPolicies() { +#if defined(__ANDROID__) + CpuTopologyState& state = State(); + std::scoped_lock topology_lock{state.topology_mutex}; + RefreshTopologyLocked(state); + + std::scoped_lock policy_lock{state.policy_mutex}; + std::erase_if(state.policies, [&state](const ThreadPolicy& policy) { + bool gone = false; + if (policy.has_nice && + setpriority(PRIO_PROCESS, static_cast(policy.tid), policy.nice_value) != 0 && + errno == ESRCH) { + gone = true; + } + if (!gone) { + ApplyCoreGroupLocked(state, policy.tid, policy.group, &gone); + } + return gone; + }); + SnapshotCanaryLocked(state); #endif +} + +void PollThreadPolicies() { +#if defined(__ANDROID__) + static const bool needed = !KernelPreservesRequestedAffinity(); + if (!needed) { + return; + } + + CpuTopologyState& state = State(); + if (!DueForPoll(state)) { + return; + } + + pid_t tid; + cpu_set_t expected; + { + std::scoped_lock lock{state.topology_mutex}; + if (!state.canary_valid) { + return; + } + tid = state.canary_tid; + expected = state.canary_mask; } + + cpu_set_t current; + CPU_ZERO(¤t); + if (sched_getaffinity(tid, sizeof(current), ¤t) == 0 && + std::memcmp(¤t, &expected, sizeof(current)) == 0) { + return; + } + RefreshThreadPolicies(); +#endif } #ifdef ARCHITECTURE_x86_64 diff --git a/src/common/thread.h b/src/common/thread.h index a75e342802..4a36295931 100644 --- a/src/common/thread.h +++ b/src/common/thread.h @@ -99,8 +99,20 @@ enum class ThreadPriority : u32 { Critical = 4, }; +enum class ThreadPlacement : u32 { + Default = 0, + Background = 1, + Efficiency = 2, +}; + void SetCurrentThreadPriority(ThreadPriority new_priority); void SetCurrentThreadName(const char* name); -void PinCurrentThreadToPerformanceCore(size_t core_id); +void SetCurrentThreadToPerformanceCores(); +void SetCurrentThreadToEfficiencyCores(); +void SetCurrentThreadToBackgroundWork(); +void SetCurrentThreadToAllCores(); + +void RefreshThreadPolicies(); +void PollThreadPolicies(); } // namespace Common diff --git a/src/common/thread_worker.h b/src/common/thread_worker.h index 6ec9d6a2bd..ec3ca0af49 100644 --- a/src/common/thread_worker.h +++ b/src/common/thread_worker.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2020 yuzu Emulator Project @@ -37,10 +37,25 @@ class StatefulThreadWorker { using StateMaker = std::conditional_t, DummyCallable>; public: - explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}) + explicit StatefulThreadWorker(size_t num_workers, std::string name, StateMaker func = {}, + ThreadPlacement placement = ThreadPlacement::Default) : workers_queued{num_workers}, thread_name{std::move(name)} { - const auto lambda = [this, func](std::stop_token stop_token) { + const auto lambda = [this, func, placement](std::stop_token stop_token) { Common::SetCurrentThreadName(thread_name.c_str()); + if (placement != ThreadPlacement::Default) { + Common::SetCurrentThreadPriority(ThreadPriority::Low); + } + switch (placement) { + case ThreadPlacement::Efficiency: + Common::SetCurrentThreadToEfficiencyCores(); + break; + case ThreadPlacement::Background: + Common::SetCurrentThreadToBackgroundWork(); + break; + default: + Common::SetCurrentThreadToAllCores(); + break; + } { [[maybe_unused]] std::conditional_t state{func()}; while (!stop_token.stop_requested()) { diff --git a/src/core/core.cpp b/src/core/core.cpp index 3ff42cf6f5..d110aa5f8b 100644 --- a/src/core/core.cpp +++ b/src/core/core.cpp @@ -10,6 +10,7 @@ #include "audio_core/audio_core.h" #include "common/fs/fs.h" #include "common/logging.h" +#include "common/adpf.h" #include "common/settings.h" #include "common/settings_enums.h" #include "common/string_util.h" @@ -385,6 +386,7 @@ struct System::Impl { void ShutdownMainProcess() { SetShuttingDown(true); + Common::ADPF::Shutdown(); // Reset per-game flags Settings::values.use_squashed_iterated_blend = false; diff --git a/src/core/core_timing.cpp b/src/core/core_timing.cpp index 3e4e7bdc68..d2ee6769cc 100644 --- a/src/core/core_timing.cpp +++ b/src/core/core_timing.cpp @@ -23,6 +23,21 @@ namespace Core::Timing { constexpr s64 MAX_SLICE_LENGTH = 10000; +constexpr u32 CPU_CLOCK_BASE_MHZ = 1020; +constexpr u32 CPU_CLOCK_BOOST_MHZ = 1734; +constexpr u32 CPU_CLOCK_OVERCLOCK_MHZ = 2040; + +constexpr u32 CpuClockTargetMhz(Settings::CpuClock clock) { + switch (clock) { + case Settings::CpuClock::Boost: + return CPU_CLOCK_BOOST_MHZ; + case Settings::CpuClock::Overclock: + return CPU_CLOCK_OVERCLOCK_MHZ; + default: + return CPU_CLOCK_BASE_MHZ; + } +} + std::shared_ptr CreateEvent(std::string name, TimedCallback&& callback) { return std::make_shared(std::move(callback), std::move(name)); } @@ -58,7 +73,8 @@ void CoreTiming::Initialize(std::function&& on_thread_init_) { if (is_multicore) { timer_thread = std::jthread([this](std::stop_token stop_token) { Common::SetCurrentThreadName("HostTiming"); - Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh); + Common::SetCurrentThreadToPerformanceCores(); on_thread_init(); has_started = true; @@ -209,8 +225,9 @@ void CoreTiming::ResetTicks() { u64 CoreTiming::GetClockTicks() const { u64 fres = is_multicore ? Common::g_wall_clock.GetCNTPCT() : Common::WallClock::CPUTickToCNTPCT(cpu_ticks); - if (auto const overclock = Settings::values.fast_cpu_time.GetValue(); overclock != Settings::CpuClock::Off) { - fres = u64(f64(fres) * (1.7 + 0.3 * u32(overclock))); + if (const u32 target = CpuClockTargetMhz(Settings::values.cpu_clock.GetValue()); + target != CPU_CLOCK_BASE_MHZ) { + fres = fres * target / CPU_CLOCK_BASE_MHZ; } if (::Settings::values.sync_core_speed.GetValue()) { auto const ticks = f64(fres); diff --git a/src/core/cpu_manager.cpp b/src/core/cpu_manager.cpp index 7194fa5022..23d5ac3dfd 100644 --- a/src/core/cpu_manager.cpp +++ b/src/core/cpu_manager.cpp @@ -174,12 +174,7 @@ void CpuManager::RunThread(std::stop_token token, std::size_t core) { std::string name = is_multicore ? ("CPUCore_" + std::to_string(core)) : std::string{"CPUThread"}; Common::SetCurrentThreadName(name.c_str()); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); -#ifdef __ANDROID__ - // Aimed specifically for Snapdragon 8 Elite devices - // This kills performance on desktop, but boosts perf for UMA devices - // like the S8E. Mediatek and Mali likely won't suffer. - Common::PinCurrentThreadToPerformanceCore(core); -#endif + Common::SetCurrentThreadToPerformanceCores(); auto& data = core_data[core]; data.host_context = Common::Fiber::ThreadToFiber(); diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp index bdbb16ed04..6e9fe9ec07 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp @@ -6,6 +6,7 @@ #include +#include "common/adpf.h" #include "common/assert.h" #include "common/logging.h" #include "core/core.h" @@ -86,11 +87,16 @@ void nvdisp_disp0::Composite(std::span sorted_layers } system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences)); + Common::ADPF::ReportFrameInterval(); system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs()); system.GetPerfStats().EndSystemFrame(); system.GetPerfStats().BeginSystemFrame(); } +void nvdisp_disp0::WaitForComposite() { + system.GPU().WaitForComposite(); +} + Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) { LOG_CRITICAL(Service_NVDRV, "Unknown DISP Event {}", event_id); return nullptr; diff --git a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h index 1082b85c2b..ff0bfb2ec9 100644 --- a/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h +++ b/src/core/hle/service/nvdrv/devices/nvdisp_disp0.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -37,6 +40,8 @@ public: /// Performs a screen flip, compositing each buffer. void Composite(std::span sorted_layers); + void WaitForComposite(); + Kernel::KEvent* QueryEvent(u32 event_id) override; private: diff --git a/src/core/hle/service/nvnflinger/hardware_composer.cpp b/src/core/hle/service/nvnflinger/hardware_composer.cpp index cfd999497b..e491b24a8f 100644 --- a/src/core/hle/service/nvnflinger/hardware_composer.cpp +++ b/src/core/hle/service/nvnflinger/hardware_composer.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -57,18 +57,8 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, // Set default speed limit to 100%. *out_speed_scale = 1.0f; - // If no layers are available, skip the logic. - bool any_visible = false; - for (auto& layer : display.stack.layers) { - if (layer->visible) { - any_visible = true; - break; - } - } - if (!any_visible) { - *out_speed_scale = 1.0f; - return 1; - } + nvdisp.WaitForComposite(); + this->ReleaseFramebuffersLocked(display); // Determine the number of vsync periods to wait before composing again. std::optional swap_interval{}; @@ -158,55 +148,30 @@ u32 HardwareComposer::ComposeLocked(f32* out_speed_scale, Display& display, nvdisp.Composite(composition_stack); } - // Batch framebuffer releases, instead of one-into-one. - std::vector> to_release; - for (auto& [layer_id, framebuffer] : m_framebuffers) { - if (!framebuffer.is_acquired) - continue; - - auto layer = display.stack.FindLayer(layer_id); - if (!layer) - continue; - - // Overlay layers always release after every compose - // Non-overlay layers release based on their swap interval - if (layer->is_overlay || framebuffer.release_frame_number <= m_frame_number) { - to_release.emplace_back(layer.get(), &framebuffer); - } - } - for (auto& [layer, framebuffer] : to_release) { - layer->buffer_item_consumer->ReleaseBuffer(framebuffer->item, android::Fence::NoFence()); - framebuffer->is_acquired = false; - } - // Advance by 1 frame (60 FPS compositing) m_frame_number += 1; - // Release any necessary framebuffers (non-overlay layers only, as overlays are already released above). + return 1; +} + +void HardwareComposer::ReleaseFramebuffersLocked(Display& display) { for (auto& [layer_id, framebuffer] : m_framebuffers) { if (!framebuffer.is_acquired) { - // Already released. continue; } - if (framebuffer.release_frame_number > m_frame_number) { + const auto layer = display.stack.FindLayer(layer_id); + if (!layer) { continue; } - if (const auto layer = display.stack.FindLayer(layer_id); layer != nullptr) { - // Skip overlay layers as they were already released above - if (layer->is_overlay) { - continue; - } - - // TODO: support release fence - // This is needed to prevent screen tearing - layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence()); - framebuffer.is_acquired = false; + if (!layer->is_overlay && framebuffer.release_frame_number > m_frame_number) { + continue; } - } - return 1; + layer->buffer_item_consumer->ReleaseBuffer(framebuffer.item, android::Fence::NoFence()); + framebuffer.is_acquired = false; + } } void HardwareComposer::RemoveLayerLocked(Display& display, ConsumerId consumer_id) { diff --git a/src/core/hle/service/nvnflinger/hardware_composer.h b/src/core/hle/service/nvnflinger/hardware_composer.h index e9b7194612..9702e0584b 100644 --- a/src/core/hle/service/nvnflinger/hardware_composer.h +++ b/src/core/hle/service/nvnflinger/hardware_composer.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project @@ -52,6 +52,7 @@ private: private: bool TryAcquireFramebufferLocked(Layer& layer, Framebuffer& framebuffer); CacheStatus CacheFramebufferLocked(Layer& layer, ConsumerId consumer_id); + void ReleaseFramebuffersLocked(Display& display); }; } // namespace Service::Nvnflinger diff --git a/src/core/hle/service/vi/conductor.cpp b/src/core/hle/service/vi/conductor.cpp index 002c653b8c..fd7233cf93 100644 --- a/src/core/hle/service/vi/conductor.cpp +++ b/src/core/hle/service/vi/conductor.cpp @@ -4,7 +4,11 @@ // SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later +#include + +#include "common/adpf.h" #include "common/settings.h" +#include "common/thread.h" #include "core/core.h" #include "core/core_timing.h" #include "core/hle/service/vi/conductor.h" @@ -14,6 +18,8 @@ constexpr auto FrameNs = std::chrono::nanoseconds{1000000000 / 60}; +constexpr s64 UNLOCKED_TARGET_DIVISOR = 4; + namespace Service::VI { Conductor::Conductor(Core::System& system, Container& container, DisplayList& displays) @@ -68,6 +74,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) { } void Conductor::ProcessVsync() { + Common::PollThreadPolicies(); + Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetFramePeriodNs()}); + for (auto& [display_id, manager] : m_vsync_managers) { m_container.ComposeOnDisplay(&m_swap_interval, &m_compose_speed_scale, display_id); manager.SignalVsync(m_system.Kernel()); @@ -76,6 +85,8 @@ void Conductor::ProcessVsync() { void Conductor::VsyncThread(std::stop_token token) { Common::SetCurrentThreadName("VSyncThread"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::VeryHigh); + Common::SetCurrentThreadToPerformanceCores(); while (!token.stop_requested()) { m_signal.Wait(); @@ -114,4 +125,25 @@ s64 Conductor::GetNextTicks() const { return static_cast(speed_scale * (1000000000.f / effective_fps)); } +s64 Conductor::GetFramePeriodNs() const { + const auto& settings = Settings::values; + f32 speed_scale = 1.f; + bool unlocked = false; + if (settings.use_multi_core.GetValue()) { + if (settings.use_speed_limit.GetValue()) { + speed_scale = 100.f / Settings::SpeedLimit(); + } else { + unlocked = true; + } + } + speed_scale /= m_compose_speed_scale; + + const f32 effective_fps = 60.f / static_cast(m_swap_interval); + s64 period = static_cast(speed_scale * (1000000000.f / effective_fps)); + if (unlocked) { + period /= UNLOCKED_TARGET_DIVISOR; + } + return std::clamp(period, 1'000'000, 100'000'000); +} + } // namespace Service::VI diff --git a/src/core/hle/service/vi/conductor.h b/src/core/hle/service/vi/conductor.h index 59eb2fd022..7c93c230f7 100644 --- a/src/core/hle/service/vi/conductor.h +++ b/src/core/hle/service/vi/conductor.h @@ -44,6 +44,7 @@ private: void ProcessVsync(); void VsyncThread(std::stop_token token); s64 GetNextTicks() const; + s64 GetFramePeriodNs() const; private: Core::System& m_system; diff --git a/src/qt_common/config/shared_translation.cpp b/src/qt_common/config/shared_translation.cpp index 893e4a0854..26597d412c 100644 --- a/src/qt_common/config/shared_translation.cpp +++ b/src/qt_common/config/shared_translation.cpp @@ -92,12 +92,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("Change the accuracy of the emulated CPU (for debugging only).")); INSERT(Settings, cpu_backend, tr("Backend:"), QString()); - INSERT(Settings, fast_cpu_time, tr("CPU Overclock"), - tr("Overclocks the emulated CPU to remove some FPS limiters. Weaker CPUs may see " - "reduced performance, " - "and certain games may behave improperly.\nUse Boost (1700MHz) to run at the " - "Switch's highest native " - "clock, or Fast (2000MHz) to run at 2x clock.")); + INSERT(Settings, cpu_clock, tr("CPU Clocks"), + tr("Raises the clock the emulated CPU reports, which removes some FPS limiters.\n" + "Weaker CPUs may see reduced performance, and certain games may behave improperly.")); INSERT(Settings, use_custom_cpu_ticks, QString(), QString()); INSERT(Settings, cpu_ticks, tr("Custom CPU Ticks"), @@ -230,9 +227,9 @@ std::unique_ptr InitializeTranslations(QObject* parent) { tr("Preserves GPU-modified data by reading it back before uploading.\nSome games require this to render certain effects properly.")); INSERT(Settings, use_asynchronous_shaders, tr("Enable asynchronous shader compilation"), tr("May reduce shader stutter.")); - INSERT(Settings, fast_gpu_time, tr("Fast GPU Time"), - tr("Overclocks the emulated GPU to increase dynamic resolution and render " - "distance.\nUse 256 for maximal performance and 512 for maximal graphics fidelity.")); + INSERT(Settings, gpu_clock, tr("GPU Clocks"), + tr("Makes the game believe GPU work finishes faster than it does, so it stops lowering " + "resolution and render distance to fit the Switch's clocks.")); INSERT(Settings, gpu_unswizzle_enabled, tr("GPU Unswizzle"), tr("Accelerates BCn 3D texture decoding using GPU compute.\n" "Disable if experiencing crashes or graphical glitches.")); @@ -639,9 +636,9 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { }}); translations->insert({Settings::EnumMetadata::Index(), { - PAIR(CpuClock, Off, tr("Off")), - PAIR(CpuClock, Boost, tr("Boost (1700MHz)")), - PAIR(CpuClock, Fast, tr("Fast (2000MHz)")), + PAIR(CpuClock, Normal, tr("Normal")), + PAIR(CpuClock, Boost, tr("Boost")), + PAIR(CpuClock, Overclock, tr("Overclock")), }}); translations->insert( {Settings::EnumMetadata::Index(), @@ -650,11 +647,11 @@ std::unique_ptr ComboboxEnumeration(QObject* parent) { PAIR(ConfirmStop, Ask_Based_On_Game, tr("Only if game specifies not to stop")), PAIR(ConfirmStop, Ask_Never, tr("Never ask")), }}); - translations->insert({Settings::EnumMetadata::Index(), + translations->insert({Settings::EnumMetadata::Index(), { - PAIR(GpuOverclock, Normal, tr("Off")), - PAIR(GpuOverclock, Medium, tr("Medium (256)")), - PAIR(GpuOverclock, High, tr("High (512)")), + PAIR(GpuClock, Normal, tr("Normal")), + PAIR(GpuClock, Boost, tr("Boost")), + PAIR(GpuClock, Overclock, tr("Overclock")), }}); translations->insert({Settings::EnumMetadata::Index(), { diff --git a/src/video_core/fence_manager.h b/src/video_core/fence_manager.h index 16f19bf009..885c714025 100644 --- a/src/video_core/fence_manager.h +++ b/src/video_core/fence_manager.h @@ -195,6 +195,7 @@ private: void ReleaseThreadFunc(std::stop_token stop_token) { Common::SetCurrentThreadName("GPUFencingThread"); Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + Common::SetCurrentThreadToPerformanceCores(); TFence current_fence; std::deque> current_operations; diff --git a/src/video_core/gpu.cpp b/src/video_core/gpu.cpp index 548a8bf809..a44015c0dd 100644 --- a/src/video_core/gpu.cpp +++ b/src/video_core/gpu.cpp @@ -10,6 +10,7 @@ #include #include #include +#include #include "common/assert.h" #include "common/settings.h" @@ -39,6 +40,19 @@ namespace Tegra { +namespace { +constexpr u64 GpuClockMultiplier(Settings::GpuClock clock) { + switch (clock) { + case Settings::GpuClock::Boost: + return 256; + case Settings::GpuClock::Overclock: + return 512; + default: + return 1; + } +} +} // Anonymous namespace + struct GPU::Impl { explicit Impl(Core::System& system_, bool is_async_, bool use_nvdec_) : system{system_} @@ -116,7 +130,7 @@ struct GPU::Impl { [[nodiscard]] u64 RequestSyncOperation(Func&& action) { std::unique_lock lck{sync_request_mutex}; const u64 fence = ++last_sync_fence; - sync_requests.emplace_back(action); + sync_requests.emplace_back(std::forward(action)); return fence; } @@ -145,14 +159,8 @@ struct GPU::Impl { } [[nodiscard]] u64 GetTicks() const { - u64 gpu_tick = system.CoreTiming().GetGPUTicks(); - Settings::GpuOverclock overclock = Settings::values.fast_gpu_time.GetValue(); - - if (overclock != Settings::GpuOverclock::Normal) { - gpu_tick /= 256 * u64(overclock); - } - - return gpu_tick; + const u64 gpu_tick = system.CoreTiming().GetGPUTicks(); + return gpu_tick / GpuClockMultiplier(Settings::values.gpu_clock.GetValue()); } void RendererFrameEndNotify() { @@ -225,9 +233,9 @@ struct GPU::Impl { } void RequestComposite(std::vector&& layers, std::vector&& fences) { - size_t num_fences{fences.size()}; + const size_t num_fences{fences.size()}; size_t current_request_counter{}; - { + if (num_fences != 0) { std::unique_lock lk(request_swap_mutex); if (free_swap_counters.empty()) { current_request_counter = request_swap_counters.size(); @@ -238,27 +246,42 @@ struct GPU::Impl { free_swap_counters.pop_front(); } } - const auto wait_fence = RequestSyncOperation([this, current_request_counter, &layers, &fences, num_fences] { - auto& syncpoint_manager = system.Host1x().GetSyncpointManager(); - if (num_fences == 0) { - renderer->Composite(layers); - } - const auto executer = [this, current_request_counter, layers_copy = layers]() { - { - std::unique_lock lk(request_swap_mutex); - if (--request_swap_counters[current_request_counter] != 0) { - return; + pending_composite_fence = RequestSyncOperation( + [this, current_request_counter, num_fences, composite_layers = std::move(layers), + composite_fences = std::move(fences)] { + if (num_fences == 0) { + renderer->Composite(composite_layers); + return; + } + auto& syncpoint_manager = system.Host1x().GetSyncpointManager(); + const auto executer = [this, current_request_counter, composite_layers]() { + { + std::unique_lock lk(request_swap_mutex); + if (--request_swap_counters[current_request_counter] != 0) { + return; + } + free_swap_counters.push_back(current_request_counter); } - free_swap_counters.push_back(current_request_counter); + renderer->Composite(composite_layers); + }; + for (size_t i = 0; i < num_fences; i++) { + syncpoint_manager.RegisterGuestAction(composite_fences[i].id, + composite_fences[i].value, executer); } - renderer->Composite(layers_copy); - }; - for (size_t i = 0; i < num_fences; i++) { - syncpoint_manager.RegisterGuestAction(fences[i].id, fences[i].value, executer); - } - }); + }); gpu_thread.TickGPU(is_async); - WaitForSyncOperation(wait_fence); + } + + void WaitForComposite() { + const u64 fence = pending_composite_fence; + if (fence == 0) { + return; + } + pending_composite_fence = 0; + if (shutting_down.load(std::memory_order_relaxed)) { + return; + } + WaitForSyncOperation(fence); } std::vector GetAppletCaptureBuffer() { @@ -311,6 +334,7 @@ struct GPU::Impl { std::deque free_swap_counters; std::deque request_swap_counters; std::mutex request_swap_mutex; + u64 pending_composite_fence{}; }; GPU::GPU(Core::System& system, bool is_async, bool use_nvdec) @@ -428,6 +452,10 @@ void GPU::RequestComposite(std::vector&& layers, impl->RequestComposite(std::move(layers), std::move(fences)); } +void GPU::WaitForComposite() { + impl->WaitForComposite(); +} + std::vector GPU::GetAppletCaptureBuffer() { return impl->GetAppletCaptureBuffer(); } diff --git a/src/video_core/gpu.h b/src/video_core/gpu.h index 538c4da85a..8d4a101fd4 100644 --- a/src/video_core/gpu.h +++ b/src/video_core/gpu.h @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project @@ -218,6 +218,8 @@ public: void RequestComposite(std::vector&& layers, std::vector&& fences); + void WaitForComposite(); + std::vector GetAppletCaptureBuffer(); /// Performs any additional setup necessary in order to begin GPU emulation. diff --git a/src/video_core/gpu_thread.cpp b/src/video_core/gpu_thread.cpp index 50570b596d..2acf9ac458 100644 --- a/src/video_core/gpu_thread.cpp +++ b/src/video_core/gpu_thread.cpp @@ -30,6 +30,7 @@ void ThreadManager::StartThread(VideoCore::RendererBase& renderer, Core::Fronten thread = std::jthread([&](std::stop_token stop_token) { Common::SetCurrentThreadName("GPU"); Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); + Common::SetCurrentThreadToPerformanceCores(); system.RegisterHostThread(); auto current_context = context.Acquire(); diff --git a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp index 2772a68d7a..785602b33a 100644 --- a/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp +++ b/src/video_core/renderer_vulkan/vk_pipeline_cache.cpp @@ -305,7 +305,7 @@ size_t GetTotalPipelineWorkers() { std::max(static_cast(std::thread::hardware_concurrency()), 2ULL) - 1ULL; #ifdef __ANDROID__ const int configured = AndroidSettings::values.pipeline_worker_count.GetValue(); - const int clamped = std::clamp(configured, 4, 8); + const int clamped = std::clamp(configured, 2, 8); const size_t desired = static_cast(clamped); if (desired == 0) { return 1ULL; @@ -351,8 +351,9 @@ PipelineCache::PipelineCache(Tegra::MaxwellDeviceMemoryManager& device_memory_, use_asynchronous_shaders{Settings::values.use_asynchronous_shaders.GetValue()}, use_vulkan_pipeline_cache{Settings::values.use_vulkan_driver_pipeline_cache.GetValue()}, workers(device.HasBrokenParallelShaderCompiling() ? 1ULL : GetTotalPipelineWorkers(), - "VkPipelineBuilder"), - serialization_thread(1, "VkPipelineSerialization") { + "VkPipelineBuilder", {}, Common::ThreadPlacement::Background), + serialization_thread(1, "VkPipelineSerialization", {}, + Common::ThreadPlacement::Background) { const auto& float_control{device.FloatControlProperties()}; const VkDriverId driver_id{device.GetDriverID()}; const VkShaderStageFlags subgroup_stages{device.GetSubgroupSupportedStages()}; diff --git a/src/video_core/renderer_vulkan/vk_present_manager.cpp b/src/video_core/renderer_vulkan/vk_present_manager.cpp index 53c3f6759f..90ebb55d38 100644 --- a/src/video_core/renderer_vulkan/vk_present_manager.cpp +++ b/src/video_core/renderer_vulkan/vk_present_manager.cpp @@ -266,6 +266,8 @@ void PresentManager::WaitPresent() { void PresentManager::PresentThread(std::stop_token token) { Common::SetCurrentThreadName("VulkanPresent"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::High); + Common::SetCurrentThreadToPerformanceCores(); while (!token.stop_requested()) { std::unique_lock lock{queue_mutex}; // Wait for presentation frames diff --git a/src/video_core/renderer_vulkan/vk_scheduler.cpp b/src/video_core/renderer_vulkan/vk_scheduler.cpp index 7432c94cfc..3601a3e891 100644 --- a/src/video_core/renderer_vulkan/vk_scheduler.cpp +++ b/src/video_core/renderer_vulkan/vk_scheduler.cpp @@ -258,6 +258,8 @@ bool Scheduler::UpdateDescriptorBufferChunk(u32 descriptor_chunk) { void Scheduler::WorkerThread(std::stop_token stop_token) { Common::SetCurrentThreadName("VulkanWorker"); + Common::SetCurrentThreadPriority(Common::ThreadPriority::Critical); + Common::SetCurrentThreadToPerformanceCores(); const auto TryPopQueue{[this](auto& work) -> bool { if (work_queue.empty()) { diff --git a/src/video_core/texture_cache/texture_cache_base.h b/src/video_core/texture_cache/texture_cache_base.h index fbc2bb4cf7..fafc331ef7 100644 --- a/src/video_core/texture_cache/texture_cache_base.h +++ b/src/video_core/texture_cache/texture_cache_base.h @@ -509,7 +509,8 @@ private: u64 frame_tick = 0; u64 last_sampler_gc_frame = (std::numeric_limits::max)(); - Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"}; + Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {}, + Common::ThreadPlacement::Efficiency}; std::vector> async_decodes; std::deque unswizzle_queue; diff --git a/src/video_core/textures/workers.cpp b/src/video_core/textures/workers.cpp index 780e0c4da9..a1953c4bf4 100644 --- a/src/video_core/textures/workers.cpp +++ b/src/video_core/textures/workers.cpp @@ -1,4 +1,4 @@ -// SPDX-FileCopyrightText: Copyright 2025 Eden Emulator Project +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project // SPDX-License-Identifier: GPL-3.0-or-later // SPDX-FileCopyrightText: Copyright 2023 yuzu Emulator Project @@ -11,7 +11,6 @@ namespace Tegra::Texture { Common::ThreadWorker& GetThreadWorkers() { static Common::ThreadWorker workers{(std::max)(std::thread::hardware_concurrency(), 2U) / 2, "ImageTranscode"}; - return workers; } diff --git a/src/yuzu/configuration/configure_cpu.cpp b/src/yuzu/configuration/configure_cpu.cpp index 0511465ee6..8c7b15e4e0 100644 --- a/src/yuzu/configuration/configure_cpu.cpp +++ b/src/yuzu/configuration/configure_cpu.cpp @@ -76,8 +76,7 @@ void ConfigureCpu::Setup(const ConfigurationShared::Builder& builder) { } else if (setting->Id() == Settings::values.cpu_backend.Id()) { backend_layout->addWidget(widget); backend_combobox = widget->combobox; - } else if (setting->Id() == Settings::values.fast_cpu_time.Id() || - setting->Id() == Settings::values.cpu_ticks.Id()) { + } else if (setting->Id() == Settings::values.cpu_ticks.Id()) { ui->general_layout->addWidget(widget); } else { // Presently, all other settings here are unsafe checkboxes