Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[ADPF] PerformanceHintAPI - include core threads + use NDK API #16610

Merged
merged 7 commits into from
Dec 28, 2023
2 changes: 1 addition & 1 deletion native/cocos/application/BaseGame.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ int BaseGame::init() {
cc_load_all_plugins();

#if (CC_PLATFORM == CC_PLATFORM_ANDROID) && CC_SUPPORT_ADPF
ADPFManager::getInstance().Initialize();
ADPFManager::getInstance().initialize();
#endif

#if CC_PLATFORM == CC_PLATFORM_WINDOWS || CC_PLATFORM == CC_PLATFORM_LINUX || CC_PLATFORM == CC_PLATFORM_QNX || CC_PLATFORM == CC_PLATFORM_MACOS
Expand Down
11 changes: 11 additions & 0 deletions native/cocos/base/threading/MessageQueue.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,12 @@
#include "MessageQueue.h"
#include "AutoReleasePool.h"
#include "base/Utils.h"
#include "base/Log.h"

#if CC_PLATFORM == CC_PLATFORM_ANDROID
#include <unistd.h>
#include "platform/android/adpf_manager.h"
#endif

namespace cc {

Expand Down Expand Up @@ -272,6 +278,11 @@ MessageQueue::~MessageQueue() {
}

void MessageQueue::consumerThreadLoop() noexcept {
#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF == 1
// add tid to PerformanceHintManager
int32_t tid = gettid();
ADPFManager::getInstance().addThreadIdToHintSession(tid);
#endif
while (!_reader.terminateConsumerThread) {
AutoReleasePool autoReleasePool;
flushMessages();
Expand Down
9 changes: 4 additions & 5 deletions native/cocos/bindings/manual/jsb_adpf.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ static bool jsb_adpf_onThermalStatusChanged_set(se::State &state) { // NOLINT

vmCallback.cbFn = new se::Value(fn.toObject(), true);
// NOLINTNEXTLINE
ADPFManager::getInstance().SetThermalListener(+[](int prevStatus, int currentStatus) {
ADPFManager::getInstance().setThermalListener(+[](int prevStatus, int currentStatus) {
CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() {
se::AutoHandleScope scope;
se::ValueArray args;
Expand Down Expand Up @@ -88,7 +88,7 @@ static bool jsb_adpf_onThermalStatusChanged_get(se::State &state) { // NOLINT
SE_BIND_PROP_GET(jsb_adpf_onThermalStatusChanged_get)

static bool jsb_adpf_getThermalStatus(se::State &state) { // NOLINT
int statusInt = ADPFManager::getInstance().GetThermalStatus();
int statusInt = ADPFManager::getInstance().getThermalStatus();
state.rval().setUint32(statusInt);
return true;
}
Expand All @@ -106,14 +106,13 @@ static bool jsb_adpf_getThermalStatusMax(se::State &state) { // NOLINT
SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMax)

static bool jsb_adpf_getThermalStatusNormalized(se::State &state) { // NOLINT
float statusNormalized = ADPFManager::getInstance().GetThermalStatusNormalized();
float statusNormalized = ADPFManager::getInstance().getThermalStatusNormalized();
state.rval().setFloat(statusNormalized);
return true;
}
SE_BIND_PROP_GET(jsb_adpf_getThermalStatusNormalized)

static bool jsb_adpf_getThermalHeadroom(se::State &state) { // NOLINT
float headroom = ADPFManager::getInstance().GetThermalHeadroom();
float headroom = ADPFManager::getInstance().getThermalHeadroom();
state.rval().setFloat(headroom);
return true;
}
Expand Down
144 changes: 115 additions & 29 deletions native/cocos/platform/android/adpf_manager.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@

#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30

#include <algorithm>
#include <unistd.h>
#include <chrono>
#include <cstdio>
Expand All @@ -32,7 +33,7 @@

// Invoke the method periodically (once a frame) to monitor
// the device's thermal throttling status.
void ADPFManager::Monitor() {
void ADPFManager::monitor() {
auto currentClock = std::chrono::high_resolution_clock::now();
auto past = currentClock - last_clock_;
auto pastMS = std::chrono::duration_cast<std::chrono::milliseconds>(past).count();
Expand All @@ -41,12 +42,12 @@ void ADPFManager::Monitor() {
if (past > kThermalHeadroomUpdateThreshold) {
// Update thermal headroom.
// CC_LOG_INFO(" Monitor past %d ms", static_cast<int>(pastMS));
UpdateThermalStatusHeadRoom();
updateThermalStatusHeadRoom();
last_clock_ = currentClock;
}
}

float ADPFManager::GetThermalStatusNormalized() const {
float ADPFManager::getThermalStatusNormalized() const {
if (thermal_manager_ == nullptr) {
return 0;
}
Expand All @@ -57,28 +58,28 @@ float ADPFManager::GetThermalStatusNormalized() const {
}

// Invoke the API first to set the android_app instance.
void ADPFManager::Initialize() {
void ADPFManager::initialize() {
// Initialize PowerManager reference.
InitializePowerManager();
initializePowerManager();

// Initialize PowerHintManager reference.
InitializePerformanceHintManager();
initializePerformanceHintManager();

beforeTick.bind([&]() {
this->BeginPerfHintSession();
this->Monitor();
this->beginPerfHintSession();
this->monitor();
});

afterTick.bind([&]() {
auto fps = cc::BasePlatform::getPlatform()->getFps();
auto frameDurationNS = 1000000000LL / fps;
this->EndPerfHintSession(frameDurationNS);
this->endPerfHintSession(frameDurationNS);
});

if (thermal_manager_) {
auto ret = AThermal_registerThermalStatusListener(
thermal_manager_, +[](void *data, AThermalStatus status) {
ADPFManager::getInstance().SetThermalStatus(status);
ADPFManager::getInstance().setThermalStatus(status);
CC_LOG_INFO("Thermal Status :%d", static_cast<int>(status));
},
nullptr);
Expand All @@ -87,7 +88,7 @@ void ADPFManager::Initialize() {
}

// Initialize JNI calls for the powermanager.
bool ADPFManager::InitializePowerManager() {
bool ADPFManager::initializePowerManager() {
#if __ANDROID_API__ >= 31
if (android_get_device_api_level() >= 31) {
// Initialize the powermanager using NDK API.
Expand Down Expand Up @@ -134,7 +135,7 @@ bool ADPFManager::InitializePowerManager() {
}

// Retrieve current thermal headroom using JNI call.
float ADPFManager::UpdateThermalStatusHeadRoom() {
float ADPFManager::updateThermalStatusHeadRoom() {
#if __ANDROID_API__ >= 31
if (android_get_device_api_level() >= 31) {
// Use NDK API to retrieve thermal status headroom.
Expand All @@ -157,12 +158,30 @@ float ADPFManager::UpdateThermalStatusHeadRoom() {
thermal_headroom_ =
env->CallFloatMethod(obj_power_service_, get_thermal_headroom_,
kThermalHeadroomUpdateThreshold);

if (!std::isnan(thermal_headroom_)) {
thermal_headroom_valid_ = thermal_headroom_;
}

ALOGE("Current thermal Headroom %f", thermal_headroom_);
return thermal_headroom_;
}

// Initialize JNI calls for the PowerHintManager.
bool ADPFManager::InitializePerformanceHintManager() {
bool ADPFManager::initializePerformanceHintManager() {
#if __ANDROID_API__ >= 33
if ( hint_manager_ == nullptr ) {
hint_manager_ = APerformanceHint_getManager();
}
if ( hint_session_ == nullptr && hint_manager_ != nullptr ) {
int32_t tid = gettid();
thread_ids_.push_back(tid);
int32_t tids[1];
tids[0] = tid;
hint_session_ = APerformanceHint_createSession(hint_manager_, tids, 1, last_target_);
}
return true;
#else
JNIEnv *env = cc::JniHelper::getEnv();
auto *javaGameActivity = cc::JniHelper::getActivity();

Expand All @@ -185,23 +204,25 @@ bool ADPFManager::InitializePerformanceHintManager() {

// Retrieve methods IDs for the APIs.
jclass cls_perfhint_service = env->GetObjectClass(obj_perfhint_service_);
jmethodID mid_createhintsession =
create_hint_session_ =
env->GetMethodID(cls_perfhint_service, "createHintSession",
"([IJ)Landroid/os/PerformanceHintManager$Session;");
jmethodID mid_preferedupdaterate = env->GetMethodID(
cls_perfhint_service, "getPreferredUpdateRateNanos", "()J");

// Create int array which contain current tid.
jintArray array = env->NewIntArray(1);
int32_t tid = getpid();
int32_t tid = gettid();
thread_ids_.push_back(tid);
env->SetIntArrayRegion(array, 0, 1, &tid);
const jlong DEFAULT_TARGET_NS = 16666666;

// Create Hint session for the thread.
jobject obj_hintsession = env->CallObjectMethod(
obj_perfhint_service_, mid_createhintsession, array, DEFAULT_TARGET_NS);
jobject obj_hintsession = env->CallObjectMethod(obj_perfhint_service_, create_hint_session_, array, DEFAULT_TARGET_NS);
jboolean check = env->ExceptionCheck();
CC_LOG_DEBUG("ADPFManager::initializePerformanceHintManager threadId: %ld gettid: %d getpid: %ld %d %x", std::this_thread::get_id(), gettid(), getpid(), check, obj_hintsession);
if (obj_hintsession == nullptr) {
ALOGI("Failed to create a perf hint session.");
CC_LOG_DEBUG("Failed First to create a perf hint session.");
} else {
obj_perfhint_session_ = env->NewGlobalRef(obj_hintsession);
preferred_update_rate_ =
Expand All @@ -213,6 +234,14 @@ bool ADPFManager::InitializePerformanceHintManager() {
cls_perfhint_session, "reportActualWorkDuration", "(J)V");
update_target_work_duration_ = env->GetMethodID(
cls_perfhint_session, "updateTargetWorkDuration", "(J)V");
set_threads_ = env->GetMethodID(
cls_perfhint_session, "setThreads", "([I)V");
check = env->ExceptionCheck();
if ( check ) {
env->ExceptionDescribe();
env->ExceptionClear();
set_threads_ = nullptr;
}
}

// Free local references
Expand All @@ -229,11 +258,12 @@ bool ADPFManager::InitializePerformanceHintManager() {
}

return true;
#endif
}

thermalStateChangeListener ADPFManager::thermalListener = NULL;

void ADPFManager::SetThermalStatus(int32_t i) {
void ADPFManager::setThermalStatus(int32_t i) {
int32_t prev_status_ = thermal_status_;
int32_t current_status_ = i;
thermal_status_ = i;
Expand All @@ -242,31 +272,87 @@ void ADPFManager::SetThermalStatus(int32_t i) {
}
}

void ADPFManager::SetThermalListener(thermalStateChangeListener listener) {
void ADPFManager::setThermalListener(thermalStateChangeListener listener) {
thermalListener = listener;
}

// Indicates the start and end of the performance intensive task.
// The methods call performance hint API to tell the performance
// hint to the system.
void ADPFManager::BeginPerfHintSession() { perfhintsession_start_ = std::chrono::high_resolution_clock::now(); }
void ADPFManager::beginPerfHintSession() {
perf_start_ = std::chrono::steady_clock::now();
}

void ADPFManager::EndPerfHintSession(jlong target_duration_ns) {
auto current_clock = std::chrono::high_resolution_clock::now();
auto duration = current_clock - perfhintsession_start_;
frame_time_ns_ = std::chrono::duration_cast<std::chrono::nanoseconds>(duration).count();
void ADPFManager::endPerfHintSession(jlong target_duration_ns) {
#if __ANDROID_API__ >= 33
auto perf_end = std::chrono::steady_clock::now();
auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(perf_end - perf_start_).count();
int64_t actual_duration_ns = static_cast<int64_t>(dur);
APerformanceHint_reportActualWorkDuration(hint_session_, actual_duration_ns);
APerformanceHint_updateTargetWorkDuration(hint_session_, target_duration_ns);
#else
auto perf_end = std::chrono::steady_clock::now();
auto dur = std::chrono::duration_cast<std::chrono::nanoseconds>(perf_end - perf_start_).count();
jlong actual_duration_ns = static_cast<jlong>(dur);
if (obj_perfhint_session_) {
jlong duration_ns = std::chrono::duration_cast<std::chrono::nanoseconds>(
duration * 100000000)
.count();
auto *env = cc::JniHelper::getEnv();

// Report and update the target work duration using JNI calls.
env->CallVoidMethod(obj_perfhint_session_, report_actual_work_duration_,
duration_ns);
actual_duration_ns);
env->CallVoidMethod(obj_perfhint_session_, update_target_work_duration_,
target_duration_ns);
}
#endif
}
void ADPFManager::addThreadIdToHintSession(int32_t tid) {
thread_ids_.push_back(tid);

registerThreadIdsToHintSession();
}

void ADPFManager::removeThreadIdFromHintSession(int32_t tid) {
thread_ids_.erase(std::remove(thread_ids_.begin(), thread_ids_.end(), tid), thread_ids_.end());

registerThreadIdsToHintSession();
}

void ADPFManager::registerThreadIdsToHintSession() {
#if __ANDROID_API__ >= 34
auto data = thread_ids_.data();
std::size_t size = thread_ids_.size();
APerformanceHint_setThreads(hint_session_, data, size);
#elif __ANDROID_API__ >= 33
auto data = thread_ids_.data();
std::size_t size = thread_ids_.size();
if ( hint_session_ != nullptr ) {
APerformanceHint_closeSession(hint_session_);
}
hint_session_ = APerformanceHint_createSession(hint_manager_, data, size, last_target_);
#else
JNIEnv *env = cc::JniHelper::getEnv();
std::size_t size = thread_ids_.size();
jintArray array = env->NewIntArray(size);
auto data = thread_ids_.data();
env->SetIntArrayRegion(array, 0, size, data);
if ( set_threads_ == nullptr ) {
// we have to recreate the hint session
if ( obj_perfhint_session_ ) {
env->DeleteGlobalRef(obj_perfhint_session_ );
}
const jlong DEFAULT_TARGET_NS = 16666666;
jobject obj_hintsession = env->CallObjectMethod(obj_perfhint_service_, create_hint_session_, array, DEFAULT_TARGET_NS);
obj_perfhint_session_ = env->NewGlobalRef(obj_hintsession);
} else {
// API Level 34
env->CallVoidMethod(obj_perfhint_session_, set_threads_, array);
jboolean check = env->ExceptionCheck();
if ( check ) {
env->ExceptionDescribe();
env->ExceptionClear();
}
}
#endif
}

#endif
Loading
Loading