[core/ threads] Initial refactor for Multithreading

This commit is contained in:
CamilleLaVey
2026-08-05 15:18:39 -04:00
committed by crueter
parent a43664c0fd
commit c41cadf3f4
23 changed files with 922 additions and 54 deletions
+3
View File
@@ -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"
@@ -218,6 +218,8 @@ object NativeLibrary {
external fun logSettings()
external fun refreshThreadPolicies()
external fun getDebugKnobAt(index: Int): Boolean
/**
@@ -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"
)
@@ -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())
+5
View File
@@ -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<jboolean>(Settings::getDebugKnobAt(static_cast<u8>(index)));
}
+2
View File
@@ -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
+307
View File
@@ -0,0 +1,307 @@
// 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;
std::atomic<s64> g_target_ns{DEFAULT_TARGET.count()};
thread_local std::chrono::steady_clock::time_point t_work_begin{};
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;
}
}
bool OpenLocked(Session session, SessionState& state) {
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(state.threads.size());
for (const pid_t tid : state.threads) {
ids.push_back(static_cast<s32>(tid));
}
state.handle = api.create_session(api.manager, ids.data(), ids.size(), target);
if (state.handle == nullptr && target == 0) {
state.handle =
api.create_session(api.manager, ids.data(), ids.size(), DEFAULT_TARGET.count());
}
if (state.handle == nullptr) {
state.unsupported = true;
LOG_WARNING(Common, "Could not open a performance hint session, falling back");
return false;
}
if (session == Session::Background) {
api.set_power_efficiency(state.handle, true);
}
return true;
}
bool SyncLocked(Session session, SessionState& state) {
if (state.threads.empty()) {
CloseLocked(state);
return false;
}
if (state.handle == nullptr) {
return OpenLocked(session, state);
}
const Api& api = Resolve();
if (api.set_threads != nullptr) {
std::vector<pid_t> ids = state.threads;
if (api.set_threads(state.handle, ids.data(), ids.size()) == 0) {
return true;
}
}
CloseLocked(state);
return OpenLocked(session, state);
}
} // 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);
if (std::find(state.threads.begin(), state.threads.end(), tid) == state.threads.end()) {
state.threads.push_back(tid);
}
if (!SyncLocked(session, state)) {
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 BeginFrameWork() {
if (!Resolve().usable) {
return;
}
t_work_begin = std::chrono::steady_clock::now();
}
void EndFrameWork() {
const Api& api = Resolve();
if (!api.usable || t_work_begin.time_since_epoch().count() == 0) {
return;
}
const auto elapsed = std::chrono::steady_clock::now() - t_work_begin;
t_work_begin = {};
const s64 actual = std::chrono::duration_cast<std::chrono::nanoseconds>(elapsed).count();
if (actual <= 0) {
return;
}
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 BeginFrameWork() {}
void EndFrameWork() {}
void Shutdown() {}
} // namespace Common::ADPF
#endif
+26
View File
@@ -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 BeginFrameWork();
void EndFrameWork();
void Shutdown();
} // namespace Common::ADPF
+514 -36
View File
@@ -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 <string>
#include <thread>
#include "common/adpf.h"
#include "common/error.h"
#include "common/logging.h"
#include "common/assert.h"
@@ -39,6 +41,414 @@
#include <unistd.h>
#endif
#ifdef __linux__
#include <sys/resource.h>
#include <algorithm>
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 == RLIM_INFINITY) {
return -20;
}
return 20 - static_cast<int>(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, LowestAllowedNice());
}
} // Anonymous namespace
#endif
#ifdef __ANDROID__
#include <sys/utsname.h>
#include <atomic>
#include <cerrno>
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <fstream>
#include <mutex>
#include <vector>
namespace {
constexpr size_t ANDROID_MINIMUM_PERFORMANCE_CORES = 4;
constexpr std::chrono::nanoseconds ANDROID_POLICY_POLL_INTERVAL = std::chrono::milliseconds{500};
enum class CoreGroup {
Unrestricted,
Performance,
Efficiency,
};
struct CoreTopology {
cpu_set_t allowed;
cpu_set_t performance;
cpu_set_t efficiency;
bool separated;
bool initialized;
};
struct ThreadPolicy {
pid_t tid;
CoreGroup group;
int nice_value;
bool has_nice;
};
struct CoreInfo {
long weight;
u64 midr;
int cpu;
};
std::mutex g_topology_mutex;
CoreTopology g_topology{};
pid_t g_canary_tid = 0;
cpu_set_t g_canary_mask;
bool g_canary_valid = false;
std::atomic<s64> g_next_poll_ns{0};
std::mutex g_policy_mutex;
std::vector<ThreadPolicy>& Policies() {
static auto* const policies = new std::vector<ThreadPolicy>();
return *policies;
}
struct PolicyRegistration {
~PolicyRegistration() {
const pid_t tid = gettid();
::Common::ADPF::RemoveCurrentThread();
std::scoped_lock lock{g_policy_mutex};
std::erase_if(Policies(), [tid](const ThreadPolicy& policy) { return policy.tid == tid; });
}
};
thread_local PolicyRegistration t_policy_registration;
int PossibleCpuCount() {
std::ifstream file("/sys/devices/system/cpu/possible");
std::string list;
if (file && std::getline(file, list) && !list.empty()) {
int highest = -1;
const char* cursor = list.c_str();
while (*cursor != '\0') {
char* end = nullptr;
const long value = std::strtol(cursor, &end, 10);
if (end == cursor) {
break;
}
highest = (std::max)(highest, static_cast<int>(value));
cursor = end;
while (*cursor == '-' || *cursor == ',') {
++cursor;
}
}
if (highest >= 0) {
return (std::min)(highest + 1, CPU_SETSIZE);
}
}
const long configured = sysconf(_SC_NPROCESSORS_CONF);
if (configured > 0) {
return static_cast<int>((std::min<long>)(configured, CPU_SETSIZE));
}
return static_cast<int>((std::min<unsigned>)(std::thread::hardware_concurrency(), CPU_SETSIZE));
}
long ReadCpuScalar(int cpu, const char* node) {
long 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(int 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<CoreInfo> CollectCores(const cpu_set_t& allowed, int total, const char* node,
bool require_all) {
std::vector<CoreInfo> cores;
for (int cpu = 0; cpu < total; ++cpu) {
if (!CPU_ISSET(cpu, &allowed)) {
continue;
}
const long 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<CoreInfo>& cores) {
return std::all_of(cores.begin(), cores.end(),
[&](const CoreInfo& core) { return core.weight == cores.front().weight; });
}
bool MidrsAreDistinct(const std::vector<CoreInfo>& cores) {
const auto unknown = [](const CoreInfo& core) { return core.midr == 0; };
if (std::any_of(cores.begin(), cores.end(), unknown)) {
return false;
}
return std::any_of(cores.begin(), cores.end(),
[&](const CoreInfo& core) { return core.midr != cores.front().midr; });
}
void ComputeTopologyLocked() {
g_topology.initialized = true;
g_topology.separated = false;
CPU_ZERO(&g_topology.allowed);
CPU_ZERO(&g_topology.performance);
CPU_ZERO(&g_topology.efficiency);
if (sched_getaffinity(getpid(), sizeof(g_topology.allowed), &g_topology.allowed) != 0) {
LOG_WARNING(Common, "Could not query process CPU affinity: {}",
::Common::GetLastErrorMsg());
return;
}
const int total = PossibleCpuCount();
auto cores = CollectCores(g_topology.allowed, total, "cpu_capacity", true);
if (cores.empty() || (WeightsAreUniform(cores) && MidrsAreDistinct(cores))) {
auto by_frequency =
CollectCores(g_topology.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 bool midr_known = std::none_of(cores.begin(), cores.end(),
[](const CoreInfo& core) { return core.midr == 0; });
const size_t allowed_count = static_cast<size_t>(CPU_COUNT(&g_topology.allowed));
const size_t maximum =
allowed_count > 2 * ANDROID_MINIMUM_PERFORMANCE_CORES
? allowed_count - ANDROID_MINIMUM_PERFORMANCE_CORES
: ANDROID_MINIMUM_PERFORMANCE_CORES;
size_t taken = 0;
long cluster_weight = cores.front().weight;
u64 cluster_midr = cores.front().midr;
for (const auto& core : cores) {
if (core.weight != cluster_weight || (midr_known && core.midr != cluster_midr)) {
if (taken >= ANDROID_MINIMUM_PERFORMANCE_CORES) {
break;
}
cluster_weight = core.weight;
cluster_midr = core.midr;
}
if (taken >= maximum) {
break;
}
CPU_SET(core.cpu, &g_topology.performance);
++taken;
}
if (taken == 0) {
return;
}
for (int cpu = 0; cpu < total; ++cpu) {
if (CPU_ISSET(cpu, &g_topology.allowed) && !CPU_ISSET(cpu, &g_topology.performance)) {
CPU_SET(cpu, &g_topology.efficiency);
}
}
g_topology.separated = CPU_COUNT(&g_topology.efficiency) > 0;
LOG_INFO(Common, "CPU topology: {} performance cores, {} efficiency cores, separation {}",
CPU_COUNT(&g_topology.performance), CPU_COUNT(&g_topology.efficiency),
g_topology.separated ? "enabled" : "unavailable");
}
void EnsureTopologyLocked() {
if (!g_topology.initialized) {
ComputeTopologyLocked();
}
}
void RefreshTopologyLocked() {
if (!g_topology.initialized) {
ComputeTopologyLocked();
return;
}
cpu_set_t current;
CPU_ZERO(&current);
if (sched_getaffinity(getpid(), sizeof(current), &current) != 0) {
return;
}
if (std::memcmp(&current, &g_topology.allowed, sizeof(current)) != 0) {
ComputeTopologyLocked();
}
}
bool ApplyCoreGroupLocked(pid_t tid, CoreGroup group, bool* gone = nullptr) {
const bool restrict_group = group != CoreGroup::Unrestricted && g_topology.separated;
const cpu_set_t* mask = &g_topology.allowed;
if (restrict_group) {
mask = group == CoreGroup::Performance ? &g_topology.performance : &g_topology.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;
}
int major = 0;
int minor = 0;
if (std::sscanf(info.release, "%d.%d", &major, &minor) != 2) {
return false;
}
return major > 6 || (major == 6 && minor >= 2);
}
void SnapshotCanaryLocked() {
g_canary_valid = false;
if (!g_topology.separated) {
return;
}
for (const auto& policy : Policies()) {
if (policy.group == CoreGroup::Unrestricted) {
continue;
}
cpu_set_t mask;
CPU_ZERO(&mask);
if (sched_getaffinity(policy.tid, sizeof(mask), &mask) != 0) {
continue;
}
g_canary_tid = policy.tid;
g_canary_mask = mask;
g_canary_valid = true;
return;
}
}
bool DueForPoll() {
const auto now = std::chrono::steady_clock::now().time_since_epoch();
const s64 now_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(now).count();
s64 next = g_next_poll_ns.load(std::memory_order_relaxed);
if (now_ns < next) {
return false;
}
return g_next_poll_ns.compare_exchange_strong(next, now_ns + ANDROID_POLICY_POLL_INTERVAL.count(),
std::memory_order_relaxed);
}
ThreadPolicy& AcquirePolicyLocked(pid_t tid) {
auto& policies = Policies();
for (auto& policy : policies) {
if (policy.tid == tid) {
return policy;
}
}
return 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;
}
std::scoped_lock topology_lock{g_topology_mutex};
EnsureTopologyLocked();
ApplyCoreGroupLocked(tid, effective);
std::scoped_lock policy_lock{g_policy_mutex};
AcquirePolicyLocked(tid).group = effective;
if (!g_canary_valid) {
SnapshotCanaryLocked();
}
}
void RememberCurrentThreadNice(pid_t tid, int nice_value) {
(void)&t_policy_registration;
std::scoped_lock lock{g_policy_mutex};
ThreadPolicy& policy = AcquirePolicyLocked(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 +458,6 @@
#endif
#include "common/x64/rdtsc.h"
#endif
#include "core/core_timing.h"
namespace Common {
@@ -78,21 +487,31 @@ void SetCurrentThreadPriority(ThreadPriority new_priority) {
}
}();
set_thread_priority(find_thread(NULL), priority);
#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;
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;
#elif defined(__ANDROID__)
const int nice_value = NiceValueForPriority(new_priority);
const pid_t tid = gettid();
if (setpriority(PRIO_PROCESS, static_cast<id_t>(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
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) {
const u32 level = (std::min)(static_cast<u32>(new_priority), 4U);
sched_param params{};
params.sched_priority =
min_prio + static_cast<s32>(static_cast<u32>(max_prio - min_prio) * level) / 4;
pthread_setschedparam(pthread_self(), SCHED_OTHER, &params);
}
pthread_setschedparam(this_thread, scheduling_type, &params);
#endif
}
@@ -132,29 +551,88 @@ 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
#endif
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__)
std::scoped_lock topology_lock{g_topology_mutex};
RefreshTopologyLocked();
std::scoped_lock policy_lock{g_policy_mutex};
std::erase_if(Policies(), [](const ThreadPolicy& policy) {
bool gone = false;
if (policy.has_nice &&
setpriority(PRIO_PROCESS, static_cast<id_t>(policy.tid), policy.nice_value) != 0 &&
errno == ESRCH) {
gone = true;
}
if (!gone) {
ApplyCoreGroupLocked(policy.tid, policy.group, &gone);
}
return gone;
});
SnapshotCanaryLocked();
#endif
}
void PollThreadPolicies() {
#if defined(__ANDROID__)
static const bool needed = !KernelPreservesRequestedAffinity();
if (!needed || !DueForPoll()) {
return;
}
pid_t tid;
cpu_set_t expected;
{
std::scoped_lock lock{g_topology_mutex};
if (!g_canary_valid) {
return;
}
tid = g_canary_tid;
expected = g_canary_mask;
}
cpu_set_t current;
CPU_ZERO(&current);
if (sched_getaffinity(tid, sizeof(current), &current) == 0 &&
std::memcmp(&current, &expected, sizeof(current)) == 0) {
return;
}
RefreshThreadPolicies();
#endif
}
#ifdef ARCHITECTURE_x86_64
+13 -1
View File
@@ -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
+18 -3
View File
@@ -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<with_state, std::function<StateType()>, 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<with_state, StateType, int> state{func()};
while (!stop_token.stop_requested()) {
+2
View File
@@ -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;
+2 -1
View File
@@ -58,7 +58,8 @@ void CoreTiming::Initialize(std::function<void()>&& 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;
+1 -6
View File
@@ -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();
@@ -6,6 +6,7 @@
#include <boost/container/small_vector.hpp>
#include "common/adpf.h"
#include "common/assert.h"
#include "common/logging.h"
#include "core/core.h"
@@ -86,9 +87,11 @@ void nvdisp_disp0::Composite(std::span<const Nvnflinger::HwcLayer> sorted_layers
}
system.GPU().RequestComposite(std::move(output_layers), std::move(output_fences));
Common::ADPF::EndFrameWork();
system.SpeedLimiter().DoSpeedLimiting(system.CoreTiming().GetGlobalTimeUs());
system.GetPerfStats().EndSystemFrame();
system.GetPerfStats().BeginSystemFrame();
Common::ADPF::BeginFrameWork();
}
Kernel::KEvent* nvdisp_disp0::QueryEvent(u32 event_id) {
+7
View File
@@ -4,7 +4,9 @@
// SPDX-FileCopyrightText: Copyright 2024 yuzu Emulator Project
// SPDX-License-Identifier: GPL-2.0-or-later
#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"
@@ -68,6 +70,9 @@ void Conductor::UnlinkVsyncEvent(u64 display_id, Event* event) {
}
void Conductor::ProcessVsync() {
Common::PollThreadPolicies();
Common::ADPF::SetTargetWorkDuration(std::chrono::nanoseconds{this->GetNextTicks()});
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 +81,8 @@ void Conductor::ProcessVsync() {
void Conductor::VsyncThread(std::stop_token token) {
Common::SetCurrentThreadName("VSyncThread");
Common::SetCurrentThreadPriority(Common::ThreadPriority::High);
Common::SetCurrentThreadToPerformanceCores();
while (!token.stop_requested()) {
m_signal.Wait();
+1
View File
@@ -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<std::function<void()>> current_operations;
+1
View File
@@ -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();
@@ -305,7 +305,7 @@ size_t GetTotalPipelineWorkers() {
std::max<size_t>(static_cast<size_t>(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<size_t>(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()};
@@ -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
@@ -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::VeryHigh);
Common::SetCurrentThreadToPerformanceCores();
const auto TryPopQueue{[this](auto& work) -> bool {
if (work_queue.empty()) {
@@ -509,7 +509,8 @@ private:
u64 frame_tick = 0;
u64 last_sampler_gc_frame = (std::numeric_limits<u64>::max)();
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder"};
Common::ThreadWorker texture_decode_worker{1, "TextureDecoder", {},
Common::ThreadPlacement::Efficiency};
std::vector<std::unique_ptr<AsyncDecodeContext>> async_decodes;
std::deque<PendingUnswizzle> unswizzle_queue;
+3 -2
View File
@@ -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
@@ -10,7 +10,8 @@ namespace Tegra::Texture {
Common::ThreadWorker& GetThreadWorkers() {
static Common::ThreadWorker workers{(std::max)(std::thread::hardware_concurrency(), 2U) / 2,
"ImageTranscode"};
"ImageTranscode", {},
Common::ThreadPlacement::Efficiency};
return workers;
}