aboutsummaryrefslogtreecommitdiff
path: root/src/common/adpf.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'src/common/adpf.cpp')
-rw-r--r--src/common/adpf.cpp317
1 files changed, 317 insertions, 0 deletions
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 <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