Browse Source
[core, gpu, threads] Multithreading refactor (#4254)
[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/4254pull/3842/head
committed by
crueter
No known key found for this signature in database
GPG Key ID: 425ACD2D4830EBC6
45 changed files with 1093 additions and 187 deletions
-
3src/android/app/build.gradle.kts
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/NativeLibrary.kt
-
2src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/model/view/SettingsItem.kt
-
4src/android/app/src/main/java/org/yuzu/yuzu_emu/features/settings/ui/SettingsFragmentPresenter.kt
-
1src/android/app/src/main/java/org/yuzu/yuzu_emu/fragments/EmulationFragment.kt
-
5src/android/app/src/main/jni/native.cpp
-
1src/android/app/src/main/res/values-ar/strings.xml
-
1src/android/app/src/main/res/values-de/strings.xml
-
1src/android/app/src/main/res/values-es/strings.xml
-
1src/android/app/src/main/res/values-fr/strings.xml
-
1src/android/app/src/main/res/values-pl/strings.xml
-
1src/android/app/src/main/res/values-ru/strings.xml
-
1src/android/app/src/main/res/values-uk/strings.xml
-
1src/android/app/src/main/res/values-zh-rCN/strings.xml
-
4src/android/app/src/main/res/values/arrays.xml
-
24src/android/app/src/main/res/values/strings.xml
-
2src/android/app/src/main/res/xml/game_mode_config.xml
-
2src/common/CMakeLists.txt
-
317src/common/adpf.cpp
-
26src/common/adpf.h
-
20src/common/settings.h
-
4src/common/settings_enums.h
-
538src/common/thread.cpp
-
14src/common/thread.h
-
21src/common/thread_worker.h
-
2src/core/core.cpp
-
23src/core/core_timing.cpp
-
7src/core/cpu_manager.cpp
-
6src/core/hle/service/nvdrv/devices/nvdisp_disp0.cpp
-
5src/core/hle/service/nvdrv/devices/nvdisp_disp0.h
-
63src/core/hle/service/nvnflinger/hardware_composer.cpp
-
3src/core/hle/service/nvnflinger/hardware_composer.h
-
32src/core/hle/service/vi/conductor.cpp
-
1src/core/hle/service/vi/conductor.h
-
29src/qt_common/config/shared_translation.cpp
-
1src/video_core/fence_manager.h
-
86src/video_core/gpu.cpp
-
4src/video_core/gpu.h
-
1src/video_core/gpu_thread.cpp
-
7src/video_core/renderer_vulkan/vk_pipeline_cache.cpp
-
2src/video_core/renderer_vulkan/vk_present_manager.cpp
-
2src/video_core/renderer_vulkan/vk_scheduler.cpp
-
3src/video_core/texture_cache/texture_cache_base.h
-
3src/video_core/textures/workers.cpp
-
3src/yuzu/configuration/configure_cpu.cpp
@ -1,7 +1,7 @@ |
|||
<?xml version="1.0" encoding="UTF-8"?> |
|||
<game-mode-config |
|||
xmlns:android="http://schemas.android.com/apk/res/android" |
|||
android:supportsBatteryGameMode="true" |
|||
android:supportsBatteryGameMode="false" |
|||
android:supportsPerformanceGameMode="true" |
|||
android:allowGameDownscaling="false" |
|||
android:allowGameFpsOverride="false"/> |
|||
@ -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 <algorithm>
|
|||
#include <array>
|
|||
#include <atomic>
|
|||
#include <cstdint>
|
|||
#include <mutex>
|
|||
#include <vector>
|
|||
|
|||
#include <dlfcn.h>
|
|||
#include <unistd.h>
|
|||
|
|||
#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<PFN_GetManager>(load("APerformanceHint_getManager")); |
|||
resolved.create_session = |
|||
reinterpret_cast<PFN_CreateSession>(load("APerformanceHint_createSession")); |
|||
resolved.close_session = |
|||
reinterpret_cast<PFN_CloseSession>(load("APerformanceHint_closeSession")); |
|||
resolved.update_target = |
|||
reinterpret_cast<PFN_UpdateTarget>(load("APerformanceHint_updateTargetWorkDuration")); |
|||
resolved.report_actual = |
|||
reinterpret_cast<PFN_ReportActual>(load("APerformanceHint_reportActualWorkDuration")); |
|||
resolved.set_threads = |
|||
reinterpret_cast<PFN_SetThreads>(load("APerformanceHint_setThreads")); |
|||
resolved.set_power_efficiency = reinterpret_cast<PFN_SetPowerEfficiency>( |
|||
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<pid_t> threads; |
|||
bool unsupported = false; |
|||
}; |
|||
|
|||
std::mutex g_mutex; |
|||
std::array<SessionState, 2> g_sessions; |
|||
|
|||
constexpr s64 MAX_REPORTED_TARGETS = 4; |
|||
|
|||
std::atomic<s64> 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<size_t>(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<pid_t>& threads) { |
|||
const Api& api = Resolve(); |
|||
const s64 target = session == Session::Render ? g_target_ns.load(std::memory_order_relaxed) : 0; |
|||
|
|||
std::vector<s32> ids; |
|||
ids.reserve(threads.size()); |
|||
for (const pid_t tid : threads) { |
|||
ids.push_back(static_cast<s32>(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<pid_t> 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<size_t>(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<Session>(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<Session>(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<std::chrono::nanoseconds>(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
|
|||
@ -0,0 +1,26 @@ |
|||
// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project |
|||
// SPDX-License-Identifier: GPL-3.0-or-later |
|||
|
|||
#pragma once |
|||
|
|||
#include <chrono> |
|||
|
|||
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 |
|||
Write
Preview
Loading…
Cancel
Save
Reference in new issue