From 663e156eaa8c4086f5d3681ec3d69d9f38c20ea3 Mon Sep 17 00:00:00 2001 From: Patrice Jiang <397136899@qq.com> Date: Fri, 27 Oct 2023 14:38:26 +0800 Subject: [PATCH 1/4] add adpf bindings --- @types/jsb.d.ts | 176 ++++++------ cocos/native-binding/impl.ts | 1 + cocos/native-binding/index.ts | 54 +++- native/CMakeLists.txt | 2 + native/cocos/application/BaseGame.cpp | 7 + native/cocos/bindings/manual/jsb_global.cpp | 185 +++++++++--- native/cocos/engine/Engine.cpp | 8 +- native/cocos/engine/EngineEvents.h | 2 + .../cocos/platform/android/adpf_manager.cpp | 272 ++++++++++++++++++ native/cocos/platform/android/adpf_manager.h | 161 +++++++++++ 10 files changed, 743 insertions(+), 125 deletions(-) create mode 100644 native/cocos/platform/android/adpf_manager.cpp create mode 100644 native/cocos/platform/android/adpf_manager.h diff --git a/@types/jsb.d.ts b/@types/jsb.d.ts index f39aae1ecf2..d7c73d0b8f8 100644 --- a/@types/jsb.d.ts +++ b/@types/jsb.d.ts @@ -64,13 +64,13 @@ declare namespace jsb { wheelDeltaX: number, wheelDeltaY: number, } - type MouseWheelEventCallback = (mouseEvent: MouseWheelEvent) => void; + type MouseWheelEventCallback = (mouseEvent: MouseWheelEvent) => void; export let onMouseDown: MouseEventCallback | undefined; export let onMouseMove: MouseEventCallback | undefined; export let onMouseUp: MouseEventCallback | undefined; export let onMouseWheel: MouseWheelEventCallback | undefined; - type TouchEventCallback = (touchList: TouchList, windowId?: number) => void; + type TouchEventCallback = (touchList: TouchList, windowId?: number) => void; export let onTouchStart: TouchEventCallback | undefined; export let onTouchMove: TouchEventCallback | undefined; export let onTouchEnd: TouchEventCallback | undefined; @@ -129,7 +129,7 @@ declare namespace jsb { } type KeyboardEventCallback = (keyboardEvent: KeyboardEvent) => void; export let onKeyDown: KeyboardEventCallback | undefined; - export let onKeyUp: KeyboardEventCallback| undefined; + export let onKeyUp: KeyboardEventCallback | undefined; export interface WindowEvent { windowId: number; @@ -142,7 +142,7 @@ declare namespace jsb { * @zh WindowEvent.width 和 WindowEvent.height 都已乘以 DPR */ export let onResize: (event: WindowEvent) => void | undefined; - export let onOrientationChanged: (event: {orientation: number}) => void | undefined; // TODO: enum orientation type + export let onOrientationChanged: (event: { orientation: number }) => void | undefined; // TODO: enum orientation type export let onResume: () => void | undefined; export let onPause: () => void | undefined; export let onClose: () => void | undefined; @@ -167,43 +167,43 @@ declare namespace jsb { channelCount: number; } export namespace AudioEngine { - export function preload (url: string, cb: (isSuccess: boolean) => void); - - export function play2d (url: string, loop: boolean, volume: number): number; - export function pause (id: number); - export function pauseAll (); - export function resume (id: number); - export function resumeAll (); - export function stop (id: number); - export function stopAll (); - - export function getPlayingAudioCount (): number; - export function getMaxAudioInstance (): number; - export function getState (id: number): any; - export function getDuration (id: number): number; - export function getVolume (id: number): number; - export function isLoop (id: number): boolean; - export function getCurrentTime (id: number): number; - - export function setVolume (id: number, val: number); - export function setLoop (id: number, val: boolean); - export function setCurrentTime (id: number, val: number); - - export function uncache (url: string); - export function uncacheAll (); - export function setErrorCallback (id: number, cb: (err: any) => void); - export function setFinishCallback (id: number, cb: () => void); + export function preload(url: string, cb: (isSuccess: boolean) => void); + + export function play2d(url: string, loop: boolean, volume: number): number; + export function pause(id: number); + export function pauseAll(); + export function resume(id: number); + export function resumeAll(); + export function stop(id: number); + export function stopAll(); + + export function getPlayingAudioCount(): number; + export function getMaxAudioInstance(): number; + export function getState(id: number): any; + export function getDuration(id: number): number; + export function getVolume(id: number): number; + export function isLoop(id: number): boolean; + export function getCurrentTime(id: number): number; + + export function setVolume(id: number, val: number); + export function setLoop(id: number, val: boolean); + export function setCurrentTime(id: number, val: number); + + export function uncache(url: string); + export function uncacheAll(); + export function setErrorCallback(id: number, cb: (err: any) => void); + export function setFinishCallback(id: number, cb: () => void); /** * Get PCM header without pcm data. if you want to get pcm data, use getOriginalPCMBuffer instead */ - export function getPCMHeader (url: string): PCMHeader; + export function getPCMHeader(url: string): PCMHeader; /** * Get PCM Data in decode format for example Int16Array, the format information is written in PCMHeader. * @param url: file relative path, for example player._path * @param channelID: ChannelID which should smaller than channel count, start from 0 */ - export function getOriginalPCMBuffer (url: string, channelID: number): ArrayBuffer | undefined; + export function getOriginalPCMBuffer(url: string, channelID: number): ArrayBuffer | undefined; } class NativePOD { @@ -236,22 +236,22 @@ declare namespace jsb { } export class Manifest { - constructor (manifestUrl: string); - constructor (content: string, manifestRoot: string); - parseFile (manifestUrl: string): void; - parseJSONString (content: string, manifestRoot: string): void; + constructor(manifestUrl: string); + constructor(content: string, manifestRoot: string); + parseFile(manifestUrl: string): void; + parseJSONString(content: string, manifestRoot: string): void; - getManifestRoot (): string; - getManifestFileUrl (): string; - getVersionFileUrl (): string; - getSearchPaths (): [string]; - getVersion (): string; - getPackageUrl (): boolean; + getManifestRoot(): string; + getManifestFileUrl(): string; + getVersionFileUrl(): string; + getSearchPaths(): [string]; + getVersion(): string; + getPackageUrl(): boolean; - setUpdating (isUpdating: boolean): void; - isUpdating (): boolean; - isVersionLoaded (): boolean; - isLoaded (): boolean; + setUpdating(isUpdating: boolean): void; + isUpdating(): boolean; + isVersionLoaded(): boolean; + isLoaded(): boolean; } export class EventAssetsManager { @@ -268,23 +268,23 @@ declare namespace jsb { static UPDATE_FAILED: number; static ERROR_DECOMPRESS: number; - constructor (eventName: string, manager: AssetsManager, eventCode: number, + constructor(eventName: string, manager: AssetsManager, eventCode: number, assetId?: string, message?: string, curleCode?: number, curlmCode?: number); - getAssetsManagerEx (): AssetsManager; - isResuming (): boolean; + getAssetsManagerEx(): AssetsManager; + isResuming(): boolean; - getDownloadedFiles (): number; - getDownloadedBytes (): number; - getTotalFiles (): number; - getTotalBytes (): number; - getPercent (): number; - getPercentByFile (): number; + getDownloadedFiles(): number; + getDownloadedBytes(): number; + getTotalFiles(): number; + getTotalBytes(): number; + getPercent(): number; + getPercentByFile(): number; - getEventCode (): number; - getMessage (): string; - getAssetId (): string; - getCURLECode (): number; - getCURLMCode (): number; + getEventCode(): number; + getMessage(): string; + getAssetId(): string; + getCURLECode(): number; + getCURLMCode(): number; } export namespace AssetsManager { @@ -307,30 +307,30 @@ declare namespace jsb { } export class AssetsManager { - constructor (manifestUrl: string, storagePath: string, versionCompareHandle?: (versionA: string, versionB: string) => number); - static create (manifestUrl: string, storagePath: string): AssetsManager; + constructor(manifestUrl: string, storagePath: string, versionCompareHandle?: (versionA: string, versionB: string) => number); + static create(manifestUrl: string, storagePath: string): AssetsManager; - getState (): AssetsManager.State; - getStoragePath (): string - getMaxConcurrentTask (): number; + getState(): AssetsManager.State; + getStoragePath(): string + getMaxConcurrentTask(): number; // setMaxConcurrentTask (max: number): void; // actually not supported - checkUpdate (): void; - prepareUpdate (): void; - update (): void; - isResuming (): boolean; + checkUpdate(): void; + prepareUpdate(): void; + update(): void; + isResuming(): boolean; - getDownloadedFiles (): number; - getDownloadedBytes (): number; - getTotalFiles (): number; - getTotalBytes (): number; - downloadFailedAssets (): void; + getDownloadedFiles(): number; + getDownloadedBytes(): number; + getTotalFiles(): number; + getTotalBytes(): number; + downloadFailedAssets(): void; - getLocalManifest (): Manifest; - loadLocalManifest (manifestUrl: string): boolean; - loadLocalManifest (localManifest: Manifest, storagePath: string): boolean; - getRemoteManifest (): Manifest; - loadRemoteManifest (remoteManifest: Manifest): boolean; + getLocalManifest(): Manifest; + loadLocalManifest(manifestUrl: string): boolean; + loadLocalManifest(localManifest: Manifest, storagePath: string): boolean; + getRemoteManifest(): Manifest; + loadRemoteManifest(remoteManifest: Manifest): boolean; /** * Setup your own version compare handler, versionA and B is versions in string. @@ -338,13 +338,23 @@ declare namespace jsb { * if the return value equals 0, versionA equals to B, * if the return value smaller than 0, versionA is smaller than B. */ - setVersionCompareHandle (versionCompareHandle?: (versionA: string, versionB: string) => number): void; + setVersionCompareHandle(versionCompareHandle?: (versionA: string, versionB: string) => number): void; /** * Setup the verification callback, Return true if the verification passed, otherwise return false */ - setVerifyCallback (verifyCallback: (path: string, asset: ManifestAsset) => boolean): void; - setEventCallback (eventCallback: (event: EventAssetsManager) => void): void; - } + setVerifyCallback(verifyCallback: (path: string, asset: ManifestAsset) => boolean): void; + setEventCallback(eventCallback: (event: EventAssetsManager) => void): void; + } + + // Android ADPF module + const adpf: { + readonly thermalHeadroom: number; + readonly thermalStatus: number; + readonly thermalStatusMin: number; + readonly thermalStatusMax: number; + readonly thermalStatusNormalized: number; + onThermalStatusChanged?: (previousStatus: number, newStatus: number, statusMin: number, statusMax: number) => void; + } | undefined; } declare namespace ns { diff --git a/cocos/native-binding/impl.ts b/cocos/native-binding/impl.ts index 6bba0aaefe2..471aefdd038 100644 --- a/cocos/native-binding/impl.ts +++ b/cocos/native-binding/impl.ts @@ -156,4 +156,5 @@ export const native = { Manifest: globalJsb.Manifest, saveImageData: globalJsb.saveImageData, process: globalJsb.process, + adpf: globalJsb.adpf, }; diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index c48c439b698..4855a69c68b 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -381,7 +381,7 @@ export declare namespace native { * @param storagePath @en Storage path for downloaded file @zh 下载文件存储路径 * @param identifier @en identifier @zh 标识符 */ - export type DownloadTask = { requestURL: string, storagePath: string, identifier: string }; + export interface DownloadTask { requestURL: string, storagePath: string, identifier: string } /** * @en DownloaderTask @zh 下载任务对象 @@ -390,7 +390,7 @@ export declare namespace native { * @param identifier @en identifier @zh 标识符 * @deprecated since v3.7.0, please use `DownloadTask` to instead. */ - export type DownloaderTask = { requestURL: string, storagePath: string, identifier: string }; + export interface DownloaderTask { requestURL: string, storagePath: string, identifier: string } /** * @en DownloaderHints @zh 下载任务的配置接口 @@ -1401,4 +1401,54 @@ export declare namespace native { */ export const argv: Readonly; } + + /** + * @en This object provides properties related to thermal characteristics and an optional callback function to track changes in thermal status. + * It is supported only on Android platforms with an API level of 31 or higher. + * @zh 该对象提供与热特性相关的属性以及用于跟踪热状态变化的可选回调函数。仅支持 API 等级为 31 或更高的 Android 平台。 + * + * @see https://developer.android.com/ndk/reference/group/thermal#group___thermal_1ga1055f6c8d5910a1904162bea75807314 + */ + const adpf: { + /** + * @en A number indicating the current thermal headroom. The value can exceed 1. + * @zh 表示当前热头寸余量数字。该值可以超过1 + * @see https://developer.android.com/ndk/reference/group/thermal#group___thermal_1ga1055f6c8d5910a1904162bea75807314 + */ + readonly thermalHeadroom: number; + /** + * @en A number indicating the current thermal status + * @zh 表示当前热状态的数字 + */ + readonly thermalStatus: number; + /** + * @en A number indicating the minimum threshold for thermal status + * @zh 表示热状态的最大阈值的数字 + */ + readonly thermalStatusMin: number; + /** + * @en A number indicating the maximum threshold for thermal status + * @zh 表示热状态的最大阈值的数字 + */ + readonly thermalStatusMax: number; + /** + * @en A normalized value of the current thermal status. It's computed based on the formula: + * (thermalStatus - thermalStatusMax) / thermalStatusMax. + * This value ranges between 0 and 1, giving a relative measure of the current thermal status against its minimum and maximum thresholds. + * @zh 当前热状态的归一化值,范围在 0 到 1 之间. 它是基于以下公式计算的: (thermalStatus - thermalStatusMax) / thermalStatusMax. + * 提供了当前热状态相对于其最小和最大阈值的相对测量。 + */ + readonly thermalStatusNormalized: number; + /** + * @en An optional callback function that is triggered when the thermal status changes + * @zh 该对象提供与热特性相关的属性以及用于跟踪热状态变化的可选回调函数 + * + * @param previousStatus @zh 之前的热状态 @en The previous thermal status + * @param newStatus @zh 更改后的新热状态 @en The new thermal status after the change + * @param statusMin @zh 热状态的最小阈值 @en The minimum threshold for thermal status + * @param statusMax @zh 热状态的最大阈值 @en The maximum threshold for thermal status + * @returns + */ + onThermalStatusChanged?: (previousStatus: number, newStatus: number, statusMin: number, statusMax: number) => void; + } | undefined; } diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index 91155327302..fa17bf00657 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -551,6 +551,8 @@ elseif(ANDROID) cocos/platform/android/AndroidPlatform.cpp cocos/platform/android/AndroidPlatform.h cocos/platform/android/AndroidKeyCodes.cpp + cocos/platform/android/adpf_manager.h + cocos/platform/android/adpf_manager.cpp ) elseif(OPENHARMONY) cocos_source_files( diff --git a/native/cocos/application/BaseGame.cpp b/native/cocos/application/BaseGame.cpp index fa7ee208c61..ecad44b3319 100644 --- a/native/cocos/application/BaseGame.cpp +++ b/native/cocos/application/BaseGame.cpp @@ -28,6 +28,9 @@ #include "platform/interfaces/modules/ISystemWindowManager.h" #include "renderer/pipeline/GlobalDescriptorSetManager.h" +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "platform/android/adpf_manager.h" +#endif extern "C" void cc_load_all_plugins(); // NOLINT namespace cc { @@ -36,6 +39,10 @@ int BaseGame::init() { cc_load_all_plugins(); +#if (CC_PLATFORM == CC_PLATFORM_ANDROID) && CC_SUPPORT_ADPF + 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 // override default value //_windowInfo.x = _windowInfo.x == -1 ? 0 : _windowInfo.x; diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index e0e25530ef9..161b26bf40d 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -24,6 +24,7 @@ #include "jsb_global.h" #include "application/ApplicationManager.h" +#include "base/Assertf.h" #include "base/Data.h" #include "base/DeferredReleasePool.h" #include "base/Scheduler.h" @@ -42,7 +43,12 @@ #include "ui/edit-box/EditBox.h" #include "xxtea/xxtea.h" +#include +#include +#include + #if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "platform/android/adpf_manager.h" #include "platform/java/jni/JniImp.h" #endif @@ -50,10 +56,6 @@ #include "platform/openharmony/napi/NapiHelper.h" #endif -#include -#include -#include - using namespace cc; // NOLINT static LegacyThreadPool *gThreadPool = nullptr; @@ -218,7 +220,7 @@ static bool doModuleRequire(const ccstd::string &path, se::Value *ret, const ccs const ccstd::string &reletivePath = fullPath; #endif - auto se = se::ScriptEngine::getInstance(); + auto *se = se::ScriptEngine::getInstance(); bool succeed = se->evalString(scriptBuffer.c_str(), static_cast(scriptBuffer.length()), nullptr, reletivePath.c_str()); se::Value moduleVal; if (succeed && se->getGlobalObject()->getProperty("module", &moduleVal) && moduleVal.isObject()) { @@ -356,7 +358,7 @@ static bool JSBCore_os(se::State &s) { // NOLINT SE_BIND_FUNC(JSBCore_os) static bool JSBCore_getCurrentLanguage(se::State &s) { // NOLINT - ISystem *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); CC_ASSERT_NOT_NULL(systemIntf); ccstd::string languageStr = systemIntf->getCurrentLanguageToString(); s.rval().setString(languageStr); @@ -365,7 +367,7 @@ static bool JSBCore_getCurrentLanguage(se::State &s) { // NOLINT SE_BIND_FUNC(JSBCore_getCurrentLanguage) static bool JSBCore_getCurrentLanguageCode(se::State &s) { // NOLINT - ISystem *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); CC_ASSERT_NOT_NULL(systemIntf); ccstd::string language = systemIntf->getCurrentLanguageCode(); s.rval().setString(language); @@ -374,7 +376,7 @@ static bool JSBCore_getCurrentLanguageCode(se::State &s) { // NOLINT SE_BIND_FUNC(JSBCore_getCurrentLanguageCode) static bool JSB_getOSVersion(se::State &s) { // NOLINT - ISystem *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); CC_ASSERT_NOT_NULL(systemIntf); ccstd::string systemVersion = systemIntf->getSystemVersion(); s.rval().setString(systemVersion); @@ -399,13 +401,13 @@ static bool JSB_core_restartVM(se::State &s) { // NOLINT } SE_BIND_FUNC(JSB_core_restartVM) -static bool JSB_closeWindow(se::State &s) { +static bool JSB_closeWindow(se::State &s) { // NOLINT CC_CURRENT_APPLICATION()->close(); return true; } SE_BIND_FUNC(JSB_closeWindow) -static bool JSB_exit(se::State &s) { +static bool JSB_exit(se::State &s) { // NOLINT BasePlatform::getPlatform()->exit(); return true; } @@ -680,7 +682,7 @@ static bool js_loadImage(se::State &s) { // NOLINT ok &= sevalue_to_native(args[0], &path); SE_PRECONDITION2(ok, false, "Error processing arguments"); - se::Value callbackVal = args[1]; + const auto &callbackVal = args[1]; CC_ASSERT(callbackVal.isObject()); CC_ASSERT(callbackVal.toObject()->isFunction()); @@ -702,8 +704,8 @@ static bool js_saveImageData(se::State &s) { // NOLINT uint8ArrayObj->root(); uint8ArrayObj->incRef(); uint8ArrayObj->getTypedArrayData(&uint8ArrayData, &length); - uint32_t width; - uint32_t height; + int32_t width; + int32_t height; ok &= sevalue_to_native(args[1], &width); ok &= sevalue_to_native(args[2], &height); @@ -775,7 +777,7 @@ static bool JSB_openURL(se::State &s) { // NOLINT ccstd::string url; ok = sevalue_to_native(args[0], &url); SE_PRECONDITION2(ok, false, "url is invalid!"); - ISystem *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); CC_ASSERT_NOT_NULL(systemIntf); systemIntf->openURL(url); return true; @@ -794,7 +796,7 @@ static bool JSB_copyTextToClipboard(se::State &s) { // NOLINT ccstd::string text; ok = sevalue_to_native(args[0], &text); SE_PRECONDITION2(ok, false, "text is invalid!"); - ISystem *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); + auto *systemIntf = CC_GET_PLATFORM_INTERFACE(ISystem); CC_ASSERT_NOT_NULL(systemIntf); systemIntf->copyTextToClipboard(text); return true; @@ -897,13 +899,13 @@ static bool JSB_showInputBox(se::State &s) { // NOLINT if (obj->getProperty("fontSize", &tmp)) { SE_PRECONDITION2(tmp.isNumber(), false, "fontSize is invalid!"); if (!tmp.isUndefined()) { - showInfo.fontSize = tmp.toDouble(); + showInfo.fontSize = tmp.toUint32(); } } if (obj->getProperty("fontColor", &tmp)) { SE_PRECONDITION2(tmp.isNumber(), false, "fontColor is invalid!"); if (!tmp.isUndefined()) { - showInfo.fontColor = tmp.toDouble(); + showInfo.fontColor = tmp.toUint32(); } } if (obj->getProperty("isBold", &tmp)) { @@ -927,25 +929,25 @@ static bool JSB_showInputBox(se::State &s) { // NOLINT if (obj->getProperty("underlineColor", &tmp)) { SE_PRECONDITION2(tmp.isNumber(), false, "underlinrColor is invalid!"); if (!tmp.isUndefined()) { - showInfo.underlineColor = tmp.toDouble(); + showInfo.underlineColor = tmp.toUint32(); } } if (obj->getProperty("backColor", &tmp)) { SE_PRECONDITION2(tmp.isNumber(), false, "backColor is invalid!"); if (!tmp.isUndefined()) { - showInfo.backColor = tmp.toDouble(); + showInfo.backColor = tmp.toUint32(); } } if (obj->getProperty("backgroundColor", &tmp)) { SE_PRECONDITION2(tmp.isNumber(), false, "backgroundColor is invalid!"); if (!tmp.isUndefined()) { - showInfo.backgroundColor = tmp.toDouble(); + showInfo.backgroundColor = tmp.toUint32(); } } if (obj->getProperty("textAlignment", &tmp)) { SE_PRECONDITION2(tmp.isNumber(), false, "textAlignment is invalid!"); if (!tmp.isUndefined()) { - showInfo.textAlignment = tmp.toDouble(); + showInfo.textAlignment = tmp.toUint32(); } } EditBox::show(showInfo); @@ -965,7 +967,7 @@ SE_BIND_FUNC(JSB_hideInputBox) #endif -static bool jsb_createExternalArrayBuffer(se::State &s) { +static bool jsb_createExternalArrayBuffer(se::State &s) { // NOLINT const auto &args = s.args(); size_t argc = args.size(); CC_UNUSED bool ok = true; @@ -1024,7 +1026,7 @@ static bool JSB_zipUtils_inflateMemory(se::State &s) { // NOLINT } SE_PRECONDITION2(ok, false, "args[0] is not in type of string | ArrayBuffer | TypedArray"); unsigned char *arg2 = nullptr; - int32_t len = 0; + uint32_t len = 0; if (argc == 1) { len = ZipUtils::inflateMemory(arg0, static_cast(arg1), &arg2); } else if (argc == 2) { @@ -1274,9 +1276,9 @@ static bool JSB_zipUtils_setPvrEncryptionKey(se::State &s) { // NOLINT SE_BIND_FUNC(JSB_zipUtils_setPvrEncryptionKey) // TextEncoder -static se::Class *__jsb_TextEncoder_class = nullptr; +static se::Class *__jsb_TextEncoder_class = nullptr; // NOLINT -static bool js_TextEncoder_finalize(se::State &s) // NOLINT(readability-identifier-naming) +static bool js_TextEncoder_finalize(se::State &) // NOLINT { return true; } @@ -1319,9 +1321,9 @@ static bool js_TextEncoder_encode(se::State &s) // NOLINT(readability-identifier SE_BIND_FUNC(js_TextEncoder_encode) // TextDecoder -static se::Class *__jsb_TextDecoder_class = nullptr; +static se::Class *__jsb_TextDecoder_class = nullptr; // NOLINT -static bool js_TextDecoder_finalize(se::State &s) // NOLINT(readability-identifier-naming) +static bool js_TextDecoder_finalize(se::State &) // NOLINT { return true; } @@ -1368,7 +1370,7 @@ static bool js_TextDecoder_decode(se::State &s) // NOLINT(readability-identifier } SE_BIND_FUNC(js_TextDecoder_decode) -static bool jsb_register_TextEncoder(se::Object *globalObj) { +static bool jsb_register_TextEncoder(se::Object *globalObj) { // NOLINT auto *cls = se::Class::create("TextEncoder", globalObj, nullptr, _SE(js_TextEncoder_constructor)); cls->defineFunction("encode", _SE(js_TextEncoder_encode)); cls->defineFinalizeFunction(_SE(js_TextEncoder_finalize)); @@ -1380,7 +1382,7 @@ static bool jsb_register_TextEncoder(se::Object *globalObj) { return true; } -static bool jsb_register_TextDecoder(se::Object *globalObj) { +static bool jsb_register_TextDecoder(se::Object *globalObj) { // NOLINT auto *cls = se::Class::create("TextDecoder", globalObj, nullptr, _SE(js_TextDecoder_constructor)); cls->defineFunction("decode", _SE(js_TextDecoder_decode)); cls->defineFinalizeFunction(_SE(js_TextDecoder_finalize)); @@ -1392,6 +1394,110 @@ static bool jsb_register_TextDecoder(se::Object *globalObj) { return true; } +#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF + +struct VmCallback { + uint32_t vmId{0xFEFEFEFE}; + se::Value *cbFn{nullptr}; + void reset() { + vmId = 0xFEFEFEFE; + delete cbFn; + cbFn = nullptr; + } +}; +static VmCallback vmCallback; +static bool jsb_adpf_onThermalStatusChanged_set(se::State &state) { // NOLINT + + auto fn = state.args()[0]; + vmCallback.reset(); + if (fn.isNullOrUndefined()) { + return true; + } + CC_ASSERT_TRUE(fn.toObject()->isFunction()); + auto *scriptEngine = se::ScriptEngine::getInstance(); + if (vmCallback.vmId != scriptEngine->getVMId()) { + vmCallback.vmId = scriptEngine->getVMId(); + scriptEngine->addBeforeCleanupHook([]() { + vmCallback.reset(); + }); + } + + vmCallback.cbFn = new se::Value(fn.toObject(), true); + // NOLINTNEXTLINE + ADPFManager::getInstance().SetThermalListener(+[](int prevStatus, int currentStatus) { + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { + se::AutoHandleScope scope; + se::ValueArray args; + args.push_back(se::Value(prevStatus)); + args.push_back(se::Value(currentStatus)); + args.push_back(se::Value(ATHERMAL_STATUS_NONE)); + args.push_back(se::Value(ATHERMAL_STATUS_SHUTDOWN)); + CC_ASSERT_EQ(vmCallback.vmId, se::ScriptEngine::getInstance()->getVMId()); + if (vmCallback.cbFn && vmCallback.cbFn->isObject() && vmCallback.cbFn->toObject()->isFunction()) { + vmCallback.cbFn->toObject()->call(args, nullptr); + } + }); + }); + return true; +} +SE_BIND_PROP_SET(jsb_adpf_onThermalStatusChanged_set) + +static bool jsb_adpf_onThermalStatusChanged_get(se::State &state) { // NOLINT + if (!vmCallback.cbFn) { + state.rval().setUndefined(); + } else { + state.rval().setObject(vmCallback.cbFn->toObject()); + } + return true; +} +SE_BIND_PROP_GET(jsb_adpf_onThermalStatusChanged_get) + +static bool jsb_adpf_getThermalStatus(se::State &state) { // NOLINT + int statusInt = ADPFManager::getInstance().GetThermalStatus(); + state.rval().setUint32(statusInt); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatus) + +static bool jsb_adpf_getThermalStatusMin(se::State &state) { // NOLINT + state.rval().setUint32(ATHERMAL_STATUS_NONE); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMin) +static bool jsb_adpf_getThermalStatusMax(se::State &state) { // NOLINT + state.rval().setUint32(ATHERMAL_STATUS_SHUTDOWN); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMax) + +static bool jsb_adpf_getThermalStatusNormalized(se::State &state) { // NOLINT + 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(); + state.rval().setFloat(headroom); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalHeadroom) + +static void jsb_register_ADPF(se::Object *ns) { // NOLINT + se::Value adpfObj{se::Object::createPlainObject()}; + adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); + adpfObj.toObject()->defineProperty("thermalStatus", _SE(jsb_adpf_getThermalStatus), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusMin", _SE(jsb_adpf_getThermalStatusMin), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusMax", _SE(jsb_adpf_getThermalStatusMax), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusNormalized", _SE(jsb_adpf_getThermalStatusNormalized), nullptr); + adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); + adpfObj.toObject()->defineProperty("onThermalStatusChanged", _SE(jsb_adpf_onThermalStatusChanged_get), _SE(jsb_adpf_onThermalStatusChanged_set)); + ns->setProperty("adpf", adpfObj); +} + +#endif // CC_PLATFORM_ANDROID + static bool JSB_process_get_argv(se::State &s) // NOLINT(readability-identifier-naming) { const auto &args = CC_CURRENT_APPLICATION()->getArguments(); @@ -1402,14 +1508,14 @@ SE_BIND_PROP_GET(JSB_process_get_argv) #if CC_PLATFORM == CC_PLATFORM_OPENHARMONY && SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI -static bool sevalue_to_napivalue(const se::Value& seVal, Napi::Value* napiVal, Napi::Env env); +static bool sevalue_to_napivalue(const se::Value &seVal, Napi::Value *napiVal, Napi::Env env); -static bool seobject_to_napivalue(se::Object *seObj, Napi::Value* napiVal, Napi::Env env) { +static bool seobject_to_napivalue(se::Object *seObj, Napi::Value *napiVal, Napi::Env env) { auto napiObj = Napi::Object::New(env); ccstd::vector allKeys; bool ok = seObj->getAllKeys(&allKeys); if (ok && !allKeys.empty()) { - for (const auto& key : allKeys) { + for (const auto &key : allKeys) { Napi::Value napiProp; se::Value prop; ok = seObj->getProperty(key, &prop); @@ -1425,7 +1531,7 @@ static bool seobject_to_napivalue(se::Object *seObj, Napi::Value* napiVal, Napi: return true; } -static bool sevalue_to_napivalue(const se::Value& seVal, Napi::Value* napiVal, Napi::Env env) { +static bool sevalue_to_napivalue(const se::Value &seVal, Napi::Value *napiVal, Napi::Env env) { // Only supports number or {tag: number, url: string} now if (seVal.isNumber()) { *napiVal = Napi::Number::New(env, seVal.toDouble()); @@ -1507,7 +1613,7 @@ static bool JSB_openharmony_postSyncMessage(se::State &s) { // NOLINT(readabilit Napi::Value napiPromise = NapiHelper::postSyncMessageToUIThread(msgType.c_str(), napiArg1); - //TODO(cjh): Implement Promise for se + // TODO(cjh): Implement Promise for se se::HandleObject retObj(se::Object::createPlainObject()); retObj->defineFunction("then", _SE(JSB_empty_promise_then)); s.rval().setObject(retObj); @@ -1586,15 +1692,18 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT #if CC_PLATFORM == CC_PLATFORM_OPENHARMONY #if SCRIPT_ENGINE_TYPE != SCRIPT_ENGINE_NAPI - se::HandleObject ohObj(se::Object::createPlainObject()); - global->setProperty("oh", se::Value(ohObj)); - ohObj->defineFunction("postMessage", _SE(JSB_openharmony_postMessage)); - ohObj->defineFunction("postSyncMessage", _SE(JSB_openharmony_postSyncMessage)); + se::HandleObject ohObj(se::Object::createPlainObject()); + global->setProperty("oh", se::Value(ohObj)); + ohObj->defineFunction("postMessage", _SE(JSB_openharmony_postMessage)); + ohObj->defineFunction("postSyncMessage", _SE(JSB_openharmony_postSyncMessage)); #endif #endif jsb_register_TextEncoder(global); jsb_register_TextDecoder(global); +#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF + jsb_register_ADPF(__jsbObj); +#endif se::ScriptEngine::getInstance()->clearException(); diff --git a/native/cocos/engine/Engine.cpp b/native/cocos/engine/Engine.cpp index f2ae34f8fbb..7e286c70d0e 100644 --- a/native/cocos/engine/Engine.cpp +++ b/native/cocos/engine/Engine.cpp @@ -286,7 +286,7 @@ void Engine::tick() { // iOS/macOS use its own fps limitation algorithm. // Windows for Editor should not sleep,because Editor call tick function synchronously -#if (CC_PLATFORM == CC_PLATFORM_ANDROID || (CC_PLATFORM == CC_PLATFORM_WINDOWS && !CC_EDITOR) || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY || CC_PLATFORM == CC_PLATFORM_MACOS) +#if (CC_PLATFORM == CC_PLATFORM_ANDROID || (CC_PLATFORM == CC_PLATFORM_WINDOWS && !CC_EDITOR) || CC_PLATFORM == CC_PLATFORM_OHOS || CC_PLATFORM == CC_PLATFORM_OPENHARMONY || CC_PLATFORM == CC_PLATFORM_MACOS) if (dtNS < static_cast(_preferredNanosecondsPerFrame)) { CC_PROFILE(EngineSleep); std::this_thread::sleep_for( @@ -295,6 +295,8 @@ void Engine::tick() { } #endif + events::BeforeTick::broadcast(); + prevTime = std::chrono::steady_clock::now(); if (_xr) _xr->beginRenderFrame(); _scheduler->update(dt); @@ -308,6 +310,8 @@ void Engine::tick() { now = std::chrono::steady_clock::now(); dtNS = dtNS * 0.1 + 0.9 * static_cast(std::chrono::duration_cast(now - prevTime).count()); dt = static_cast(dtNS) / NANOSECONDS_PER_SECOND; + + events::AfterTick::broadcast(); } CC_PROFILER_END_FRAME; @@ -339,7 +343,7 @@ bool Engine::redirectWindowEvent(const WindowEvent &ev) { CC_ASSERT(w); w->setViewSize(ev.width, ev.height); // Because the ts layer calls the getviewsize interface in response to resize. - // So we need to set the view size when sending the message. + // So we need to set the view size when sending the message. events::Resize::broadcast(ev.width, ev.height, ev.windowId); isHandled = true; } else if (ev.type == WindowEvent::Type::HIDDEN || diff --git a/native/cocos/engine/EngineEvents.h b/native/cocos/engine/EngineEvents.h index 5e3e7e8e769..0a4b2b006c4 100644 --- a/native/cocos/engine/EngineEvents.h +++ b/native/cocos/engine/EngineEvents.h @@ -339,6 +339,8 @@ DECLARE_BUS_EVENT_ARG1(Keyboard, Engine, const cc::KeyboardEvent &) DECLARE_BUS_EVENT_ARG1(Controller, Engine, const cc::ControllerEvent &) DECLARE_BUS_EVENT_ARG1(ControllerChange, Engine, const cc::ControllerChangeEvent &) DECLARE_BUS_EVENT_ARG1(Tick, Engine, float) +DECLARE_BUS_EVENT_ARG0(BeforeTick, Engine) +DECLARE_BUS_EVENT_ARG0(AfterTick, Engine) DECLARE_BUS_EVENT_ARG3(Resize, Engine, int, int, uint32_t /* windowId*/) DECLARE_BUS_EVENT_ARG1(Orientation, Engine, int) DECLARE_BUS_EVENT_ARG1(PointerLock, Engine, bool) diff --git a/native/cocos/platform/android/adpf_manager.cpp b/native/cocos/platform/android/adpf_manager.cpp new file mode 100644 index 00000000000..2411ae01ee5 --- /dev/null +++ b/native/cocos/platform/android/adpf_manager.cpp @@ -0,0 +1,272 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "adpf_manager.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30 + + #include + #include + #include + #include + #include + #include + #include "java/jni/JniHelper.h" + + #define ALOGI(...) + #define ALOGE(...) + +// Invoke the method periodically (once a frame) to monitor +// the device's thermal throttling status. +void ADPFManager::Monitor() { + auto currentClock = std::chrono::high_resolution_clock::now(); + auto past = currentClock - last_clock_; + auto pastMS = std::chrono::duration_cast(past).count(); + + // if (current_clock - last_clock_ >= kThermalHeadroomUpdateThreshold) { + if (past > kThermalHeadroomUpdateThreshold) { + // Update thermal headroom. + // CC_LOG_INFO(" Monitor past %d ms", static_cast(pastMS)); + UpdateThermalStatusHeadRoom(); + last_clock_ = currentClock; + } +} + +float ADPFManager::GetThermalStatusNormalized() const { + if (thermal_manager_ == nullptr) { + return 0; + } + auto level = AThermal_getCurrentThermalStatus(thermal_manager_); + auto levelValue = (static_cast(level) - static_cast(ATHERMAL_STATUS_NONE)) * 1.0f / + static_cast(ATHERMAL_STATUS_SHUTDOWN); + return levelValue; +} + +// Invoke the API first to set the android_app instance. +void ADPFManager::Initialize() { + // Initialize PowerManager reference. + InitializePowerManager(); + + // Initialize PowerHintManager reference. + InitializePerformanceHintManager(); + + beforeTick.bind([&]() { + this->BeginPerfHintSession(); + this->Monitor(); + }); + + afterTick.bind([&]() { + auto frameDuration = std::chrono::duration_cast( + std::chrono::milliseconds(16)) + .count(); + this->EndPerfHintSession(frameDuration); + }); + + if (thermal_manager_) { + auto ret = AThermal_registerThermalStatusListener( + thermal_manager_, +[](void *data, AThermalStatus status) { + ADPFManager::getInstance().SetThermalStatus(status); + CC_LOG_INFO("Thermal Status :%d", static_cast(status)); + }, + nullptr); + ALOGI("Thermal Status callback registerred to:%d", ret); + } +} + +// Initialize JNI calls for the powermanager. +bool ADPFManager::InitializePowerManager() { + #if __ANDROID_API__ >= 31 + if (android_get_device_api_level() >= 31) { + // Initialize the powermanager using NDK API. + thermal_manager_ = AThermal_acquireManager(); + return true; + } + #endif + + JNIEnv *env = cc::JniHelper::getEnv(); + auto *javaGameActivity = cc::JniHelper::getActivity(); + + // Retrieve class information + jclass context = env->FindClass("android/content/Context"); + + // Get the value of a constant + jfieldID fid = + env->GetStaticFieldID(context, "POWER_SERVICE", "Ljava/lang/String;"); + jobject str_svc = env->GetStaticObjectField(context, fid); + + // Get the method 'getSystemService' and call it + jmethodID mid_getss = env->GetMethodID( + context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject obj_power_service = env->CallObjectMethod(javaGameActivity, mid_getss, str_svc); + + // Add global reference to the power service object. + obj_power_service_ = env->NewGlobalRef(obj_power_service); + + jclass cls_power_service = env->GetObjectClass(obj_power_service_); + get_thermal_headroom_ = + env->GetMethodID(cls_power_service, "getThermalHeadroom", "(I)F"); + + // Free references + env->DeleteLocalRef(cls_power_service); + env->DeleteLocalRef(obj_power_service); + env->DeleteLocalRef(str_svc); + env->DeleteLocalRef(context); + + if (get_thermal_headroom_ == 0) { + // The API is not supported in the platform version. + return false; + } + + return true; +} + +// Retrieve current thermal headroom using JNI call. +float ADPFManager::UpdateThermalStatusHeadRoom() { + #if __ANDROID_API__ >= 31 + if (android_get_device_api_level() >= 31) { + // Use NDK API to retrieve thermal status headroom. + auto seconds = kThermalHeadroomUpdateThreshold.count(); + thermal_headroom_ = AThermal_getThermalHeadroom( + thermal_manager_, seconds); + if (!std::isnan(thermal_headroom_)) { + thermal_headroom_valid_ = thermal_headroom_; + } + return thermal_headroom_; + } + #endif + + if (get_thermal_headroom_ == 0) { + return 0.f; + } + JNIEnv *env = cc::JniHelper::getEnv(); + + // Get thermal headroom! + thermal_headroom_ = + env->CallFloatMethod(obj_power_service_, get_thermal_headroom_, + kThermalHeadroomUpdateThreshold); + ALOGE("Current thermal Headroom %f", thermal_headroom_); + return thermal_headroom_; +} + +// Initialize JNI calls for the PowerHintManager. +bool ADPFManager::InitializePerformanceHintManager() { + JNIEnv *env = cc::JniHelper::getEnv(); + auto *javaGameActivity = cc::JniHelper::getActivity(); + + // Retrieve class information + jclass context = env->FindClass("android/content/Context"); + + // Get the value of a constant + jfieldID fid = env->GetStaticFieldID(context, "PERFORMANCE_HINT_SERVICE", + "Ljava/lang/String;"); + jobject str_svc = env->GetStaticObjectField(context, fid); + + // Get the method 'getSystemService' and call it + jmethodID mid_getss = env->GetMethodID( + context, "getSystemService", "(Ljava/lang/String;)Ljava/lang/Object;"); + jobject obj_perfhint_service = env->CallObjectMethod( + javaGameActivity, mid_getss, str_svc); + + // Add global reference to the power service object. + obj_perfhint_service_ = env->NewGlobalRef(obj_perfhint_service); + + // Retrieve methods IDs for the APIs. + jclass cls_perfhint_service = env->GetObjectClass(obj_perfhint_service_); + jmethodID mid_createhintsession = + 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(); + 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); + if (obj_hintsession == nullptr) { + ALOGI("Failed to create a perf hint session."); + } else { + obj_perfhint_session_ = env->NewGlobalRef(obj_hintsession); + preferred_update_rate_ = + env->CallLongMethod(obj_perfhint_service_, mid_preferedupdaterate); + + // Retrieve mid of Session APIs. + jclass cls_perfhint_session = env->GetObjectClass(obj_perfhint_session_); + report_actual_work_duration_ = env->GetMethodID( + cls_perfhint_session, "reportActualWorkDuration", "(J)V"); + update_target_work_duration_ = env->GetMethodID( + cls_perfhint_session, "updateTargetWorkDuration", "(J)V"); + } + + // Free local references + env->DeleteLocalRef(obj_hintsession); + env->DeleteLocalRef(array); + env->DeleteLocalRef(cls_perfhint_service); + env->DeleteLocalRef(obj_perfhint_service); + env->DeleteLocalRef(str_svc); + env->DeleteLocalRef(context); + + if (report_actual_work_duration_ == 0 || update_target_work_duration_ == 0) { + // The API is not supported in the platform version. + return false; + } + + return true; +} + +thermalStateChangeListener ADPFManager::thermalListener = NULL; + +void ADPFManager::SetThermalStatus(int32_t i) { + int32_t prev_status_ = thermal_status_; + int32_t current_status_ = i; + thermal_status_ = i; + if (thermalListener != NULL) { + thermalListener(prev_status_, current_status_); + } +} + +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::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(duration).count(); + if (obj_perfhint_session_) { + jlong duration_ns = std::chrono::duration_cast( + 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); + env->CallVoidMethod(obj_perfhint_session_, update_target_work_duration_, + target_duration_ns); + } +} + +#endif diff --git a/native/cocos/platform/android/adpf_manager.h b/native/cocos/platform/android/adpf_manager.h new file mode 100644 index 00000000000..9ef9bc8e23d --- /dev/null +++ b/native/cocos/platform/android/adpf_manager.h @@ -0,0 +1,161 @@ +/* + * Copyright 2022 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef ADPF_MANAGER_H_ +#define ADPF_MANAGER_H_ + +#if CC_PLATFORM == CC_PLATFORM_ANDROID && __ANDROID_API__ >= 30 + #include + #include + #include + #include + + #include + #include + #include "3d/models/SkinningModel.h" + #include "engine/EngineEvents.h" + #include "platform/java/jni/JniHelper.h" + +// Forward declarations of functions that need to be in C decl. + +typedef void (*thermalStateChangeListener)(int32_t, int32_t); + +/* + * ADPFManager class anages the ADPF APIs. + */ +class ADPFManager { +public: + // Singleton function. + static ADPFManager &getInstance() { + static ADPFManager instance; + return instance; + } + + // Dtor. + ~ADPFManager() { + // Remove global reference. + auto env = cc::JniHelper::getEnv(); + if (env != nullptr) { + if (obj_power_service_ != nullptr) { + env->DeleteGlobalRef(obj_power_service_); + } + if (obj_perfhint_service_ != nullptr) { + env->DeleteGlobalRef(obj_perfhint_service_); + } + if (obj_perfhint_session_ != nullptr) { + env->DeleteGlobalRef(obj_perfhint_session_); + } + if (thermal_manager_ != nullptr) { + AThermal_releaseManager(thermal_manager_); + } + } + } + + // Delete copy constructor since the class is used as a singleton. + ADPFManager(ADPFManager const &) = delete; + + void operator=(ADPFManager const &) = delete; + + // Invoke the method periodically (once a frame) to monitor + // the device's thermal throttling status. + void Monitor(); + + // Invoke the API first to set the android_app instance. + + // Method to set thermal status. Need to be public since the method + // is called from C native listener. + void SetThermalStatus(int32_t i); + + // Get current thermal status and headroom. + int32_t GetThermalStatus() { return thermal_status_; } + float GetThermalStatusNormalized() const; + + float GetFrameTimeMS() const { return frame_time_ns_ / 1000000.0F; } + + float GetThermalHeadroom() { return thermal_headroom_; } + + void SetThermalListener(thermalStateChangeListener 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 BeginPerfHintSession(); + + void EndPerfHintSession(jlong target_duration_ns); + + // Method to retrieve thermal manager. The API is used to register/unregister + // callbacks from C API. + AThermalManager *GetThermalManager() { return thermal_manager_; } + + void Initialize(); + +private: + // Update thermal headroom each sec. + static constexpr auto kThermalHeadroomUpdateThreshold = std::chrono::seconds(1); + + // Function pointer from the game, will be invoked when we receive state changed event from Thermal API + static thermalStateChangeListener thermalListener; + + // Ctor. It's private since the class is designed as a singleton. + ADPFManager() + : thermal_manager_(nullptr), + thermal_status_(0), + thermal_headroom_(0.f), + obj_power_service_(nullptr), + get_thermal_headroom_(0), + obj_perfhint_service_(nullptr), + obj_perfhint_session_(nullptr), + report_actual_work_duration_(0), + update_target_work_duration_(0), + preferred_update_rate_(0) { + last_clock_ = std::chrono::high_resolution_clock::now(); + perfhintsession_start_ = std::chrono::high_resolution_clock::now(); + } + + // Functions to initialize ADPF API's calls. + bool InitializePowerManager(); + + float UpdateThermalStatusHeadRoom(); + + bool InitializePerformanceHintManager(); + + AThermalManager *thermal_manager_ = nullptr; + int32_t thermal_status_; + float thermal_headroom_ = 0; + float thermal_headroom_valid_ = 0; + std::chrono::time_point last_clock_; + jobject obj_power_service_; + jmethodID get_thermal_headroom_; + + jobject obj_perfhint_service_; + jobject obj_perfhint_session_; + jmethodID report_actual_work_duration_; + jmethodID update_target_work_duration_; + jlong preferred_update_rate_; + + cc::events::BeforeTick::Listener beforeTick; + cc::events::AfterTick::Listener afterTick; + + std::chrono::time_point perfhintsession_start_; + int64_t frame_time_ns_{0}; +}; + + #define CC_SUPPORT_ADPF 1 // NOLINT +#else + #define CC_SUPPORT_ADPF 0 // NOLINT +#endif // ADPF_MANAGER_H_ + +#endif From 59352edcc7c2709794167980c9d0ee6874d03630 Mon Sep 17 00:00:00 2001 From: Patrice Jiang <397136899@qq.com> Date: Fri, 27 Oct 2023 15:22:14 +0800 Subject: [PATCH 2/4] update typo --- cocos/native-binding/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index 4855a69c68b..33ce21c3f04 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -1433,9 +1433,9 @@ export declare namespace native { readonly thermalStatusMax: number; /** * @en A normalized value of the current thermal status. It's computed based on the formula: - * (thermalStatus - thermalStatusMax) / thermalStatusMax. + * (thermalStatus - thermalStatusMin) / thermalStatusMax. * This value ranges between 0 and 1, giving a relative measure of the current thermal status against its minimum and maximum thresholds. - * @zh 当前热状态的归一化值,范围在 0 到 1 之间. 它是基于以下公式计算的: (thermalStatus - thermalStatusMax) / thermalStatusMax. + * @zh 当前热状态的归一化值,范围在 0 到 1 之间. 它是基于以下公式计算的: (thermalStatus - thermalStatusMin) / thermalStatusMax. * 提供了当前热状态相对于其最小和最大阈值的相对测量。 */ readonly thermalStatusNormalized: number; From e66635c05fe8337ce8216081345684bfcbc0836a Mon Sep 17 00:00:00 2001 From: Patrice Jiang <397136899@qq.com> Date: Wed, 1 Nov 2023 10:17:44 +0800 Subject: [PATCH 3/4] review fix --- cocos/native-binding/index.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cocos/native-binding/index.ts b/cocos/native-binding/index.ts index 33ce21c3f04..5a64c27d01a 100644 --- a/cocos/native-binding/index.ts +++ b/cocos/native-binding/index.ts @@ -1411,8 +1411,8 @@ export declare namespace native { */ const adpf: { /** - * @en A number indicating the current thermal headroom. The value can exceed 1. - * @zh 表示当前热头寸余量数字。该值可以超过1 + * @en Provides an estimate of how much thermal headroom the device currently has before hitting severe throttling. The value range is a non-negative float, where 0.0 represents a fixed distance from overheating, 1.0 indicates the device will be severely throttled, and values greater than 1.0 may imply even heavier throttling. + * @zh 提供设备在达到严重节流之前当前有多少热余量的估计值。值的范围是非负浮点数,其中0.0表示距离过热的固定距离,1.0表示设备将被严重限制,而大于1.0的值可能表示更重的限制。 * @see https://developer.android.com/ndk/reference/group/thermal#group___thermal_1ga1055f6c8d5910a1904162bea75807314 */ readonly thermalHeadroom: number; From 24bdeddedfbbc1868907754c8e5807c51138380c Mon Sep 17 00:00:00 2001 From: Patrice Jiang <397136899@qq.com> Date: Wed, 1 Nov 2023 16:18:43 +0800 Subject: [PATCH 4/4] seperate jsb_adpf register function --- native/CMakeLists.txt | 1 + native/cocos/bindings/manual/jsb_adpf.cpp | 135 ++++++++++++++++++++ native/cocos/bindings/manual/jsb_global.cpp | 111 +--------------- 3 files changed, 140 insertions(+), 107 deletions(-) create mode 100644 native/cocos/bindings/manual/jsb_adpf.cpp diff --git a/native/CMakeLists.txt b/native/CMakeLists.txt index fa17bf00657..947dcbc5599 100644 --- a/native/CMakeLists.txt +++ b/native/CMakeLists.txt @@ -2531,6 +2531,7 @@ cocos_source_files( cocos/bindings/manual/jsb_xmlhttprequest.h cocos/bindings/manual/jsb_pipeline_manual.h cocos/bindings/manual/jsb_pipeline_manual.cpp + cocos/bindings/manual/jsb_adpf.cpp ) if(USE_AUDIO) cocos_source_files( diff --git a/native/cocos/bindings/manual/jsb_adpf.cpp b/native/cocos/bindings/manual/jsb_adpf.cpp new file mode 100644 index 00000000000..108d5a7923c --- /dev/null +++ b/native/cocos/bindings/manual/jsb_adpf.cpp @@ -0,0 +1,135 @@ +/**************************************************************************** + Copyright (c) 2023 Xiamen Yaji Software Co., Ltd. + + http://www.cocos.com + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights to + use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies + of the Software, and to permit persons to whom the Software is furnished to do so, + subject to the following conditions: + + The above copyright notice and this permission notice shall be included in + all copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + THE SOFTWARE. +*****************************************************************************/ + +#include "application/ApplicationManager.h" +#include "bindings/jswrapper/SeApi.h" + +#if CC_PLATFORM == CC_PLATFORM_ANDROID + #include "platform/android/adpf_manager.h" +#endif + +#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF + +struct VmCallback { + uint32_t vmId{0xFEFEFEFE}; + se::Value *cbFn{nullptr}; + void reset() { + vmId = 0xFEFEFEFE; + delete cbFn; + cbFn = nullptr; + } +}; +static VmCallback vmCallback; +static bool jsb_adpf_onThermalStatusChanged_set(se::State &state) { // NOLINT + + auto fn = state.args()[0]; + vmCallback.reset(); + if (fn.isNullOrUndefined()) { + return true; + } + CC_ASSERT_TRUE(fn.toObject()->isFunction()); + auto *scriptEngine = se::ScriptEngine::getInstance(); + if (vmCallback.vmId != scriptEngine->getVMId()) { + vmCallback.vmId = scriptEngine->getVMId(); + scriptEngine->addBeforeCleanupHook([]() { + vmCallback.reset(); + }); + } + + vmCallback.cbFn = new se::Value(fn.toObject(), true); + // NOLINTNEXTLINE + ADPFManager::getInstance().SetThermalListener(+[](int prevStatus, int currentStatus) { + CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { + se::AutoHandleScope scope; + se::ValueArray args; + args.push_back(se::Value(prevStatus)); + args.push_back(se::Value(currentStatus)); + args.push_back(se::Value(ATHERMAL_STATUS_NONE)); + args.push_back(se::Value(ATHERMAL_STATUS_SHUTDOWN)); + CC_ASSERT_EQ(vmCallback.vmId, se::ScriptEngine::getInstance()->getVMId()); + if (vmCallback.cbFn && vmCallback.cbFn->isObject() && vmCallback.cbFn->toObject()->isFunction()) { + vmCallback.cbFn->toObject()->call(args, nullptr); + } + }); + }); + return true; +} +SE_BIND_PROP_SET(jsb_adpf_onThermalStatusChanged_set) + +static bool jsb_adpf_onThermalStatusChanged_get(se::State &state) { // NOLINT + if (!vmCallback.cbFn) { + state.rval().setUndefined(); + } else { + state.rval().setObject(vmCallback.cbFn->toObject()); + } + return true; +} +SE_BIND_PROP_GET(jsb_adpf_onThermalStatusChanged_get) + +static bool jsb_adpf_getThermalStatus(se::State &state) { // NOLINT + int statusInt = ADPFManager::getInstance().GetThermalStatus(); + state.rval().setUint32(statusInt); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatus) + +static bool jsb_adpf_getThermalStatusMin(se::State &state) { // NOLINT + state.rval().setUint32(ATHERMAL_STATUS_NONE); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMin) +static bool jsb_adpf_getThermalStatusMax(se::State &state) { // NOLINT + state.rval().setUint32(ATHERMAL_STATUS_SHUTDOWN); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMax) + +static bool jsb_adpf_getThermalStatusNormalized(se::State &state) { // NOLINT + 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(); + state.rval().setFloat(headroom); + return true; +} +SE_BIND_PROP_GET(jsb_adpf_getThermalHeadroom) + +void jsb_register_ADPF(se::Object *ns) { // NOLINT + se::Value adpfObj{se::Object::createPlainObject()}; + adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); + adpfObj.toObject()->defineProperty("thermalStatus", _SE(jsb_adpf_getThermalStatus), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusMin", _SE(jsb_adpf_getThermalStatusMin), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusMax", _SE(jsb_adpf_getThermalStatusMax), nullptr); + adpfObj.toObject()->defineProperty("thermalStatusNormalized", _SE(jsb_adpf_getThermalStatusNormalized), nullptr); + adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); + adpfObj.toObject()->defineProperty("onThermalStatusChanged", _SE(jsb_adpf_onThermalStatusChanged_get), _SE(jsb_adpf_onThermalStatusChanged_set)); + ns->setProperty("adpf", adpfObj); +} +#else +void jsb_register_ADPF(se::Object *ns) {} // NOLINT +#endif // CC_PLATFORM_ANDROID diff --git a/native/cocos/bindings/manual/jsb_global.cpp b/native/cocos/bindings/manual/jsb_global.cpp index 161b26bf40d..b282c8df932 100644 --- a/native/cocos/bindings/manual/jsb_global.cpp +++ b/native/cocos/bindings/manual/jsb_global.cpp @@ -41,6 +41,7 @@ #include "platform/interfaces/modules/ISystem.h" #include "platform/interfaces/modules/ISystemWindow.h" #include "ui/edit-box/EditBox.h" +#include "v8/Object.h" #include "xxtea/xxtea.h" #include @@ -48,7 +49,6 @@ #include #if CC_PLATFORM == CC_PLATFORM_ANDROID - #include "platform/android/adpf_manager.h" #include "platform/java/jni/JniImp.h" #endif @@ -56,6 +56,8 @@ #include "platform/openharmony/napi/NapiHelper.h" #endif +extern void jsb_register_ADPF(se::Object *); // NOLINT + using namespace cc; // NOLINT static LegacyThreadPool *gThreadPool = nullptr; @@ -1394,110 +1396,6 @@ static bool jsb_register_TextDecoder(se::Object *globalObj) { // NOLINT return true; } -#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF - -struct VmCallback { - uint32_t vmId{0xFEFEFEFE}; - se::Value *cbFn{nullptr}; - void reset() { - vmId = 0xFEFEFEFE; - delete cbFn; - cbFn = nullptr; - } -}; -static VmCallback vmCallback; -static bool jsb_adpf_onThermalStatusChanged_set(se::State &state) { // NOLINT - - auto fn = state.args()[0]; - vmCallback.reset(); - if (fn.isNullOrUndefined()) { - return true; - } - CC_ASSERT_TRUE(fn.toObject()->isFunction()); - auto *scriptEngine = se::ScriptEngine::getInstance(); - if (vmCallback.vmId != scriptEngine->getVMId()) { - vmCallback.vmId = scriptEngine->getVMId(); - scriptEngine->addBeforeCleanupHook([]() { - vmCallback.reset(); - }); - } - - vmCallback.cbFn = new se::Value(fn.toObject(), true); - // NOLINTNEXTLINE - ADPFManager::getInstance().SetThermalListener(+[](int prevStatus, int currentStatus) { - CC_CURRENT_ENGINE()->getScheduler()->performFunctionInCocosThread([=]() { - se::AutoHandleScope scope; - se::ValueArray args; - args.push_back(se::Value(prevStatus)); - args.push_back(se::Value(currentStatus)); - args.push_back(se::Value(ATHERMAL_STATUS_NONE)); - args.push_back(se::Value(ATHERMAL_STATUS_SHUTDOWN)); - CC_ASSERT_EQ(vmCallback.vmId, se::ScriptEngine::getInstance()->getVMId()); - if (vmCallback.cbFn && vmCallback.cbFn->isObject() && vmCallback.cbFn->toObject()->isFunction()) { - vmCallback.cbFn->toObject()->call(args, nullptr); - } - }); - }); - return true; -} -SE_BIND_PROP_SET(jsb_adpf_onThermalStatusChanged_set) - -static bool jsb_adpf_onThermalStatusChanged_get(se::State &state) { // NOLINT - if (!vmCallback.cbFn) { - state.rval().setUndefined(); - } else { - state.rval().setObject(vmCallback.cbFn->toObject()); - } - return true; -} -SE_BIND_PROP_GET(jsb_adpf_onThermalStatusChanged_get) - -static bool jsb_adpf_getThermalStatus(se::State &state) { // NOLINT - int statusInt = ADPFManager::getInstance().GetThermalStatus(); - state.rval().setUint32(statusInt); - return true; -} -SE_BIND_PROP_GET(jsb_adpf_getThermalStatus) - -static bool jsb_adpf_getThermalStatusMin(se::State &state) { // NOLINT - state.rval().setUint32(ATHERMAL_STATUS_NONE); - return true; -} -SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMin) -static bool jsb_adpf_getThermalStatusMax(se::State &state) { // NOLINT - state.rval().setUint32(ATHERMAL_STATUS_SHUTDOWN); - return true; -} -SE_BIND_PROP_GET(jsb_adpf_getThermalStatusMax) - -static bool jsb_adpf_getThermalStatusNormalized(se::State &state) { // NOLINT - 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(); - state.rval().setFloat(headroom); - return true; -} -SE_BIND_PROP_GET(jsb_adpf_getThermalHeadroom) - -static void jsb_register_ADPF(se::Object *ns) { // NOLINT - se::Value adpfObj{se::Object::createPlainObject()}; - adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); - adpfObj.toObject()->defineProperty("thermalStatus", _SE(jsb_adpf_getThermalStatus), nullptr); - adpfObj.toObject()->defineProperty("thermalStatusMin", _SE(jsb_adpf_getThermalStatusMin), nullptr); - adpfObj.toObject()->defineProperty("thermalStatusMax", _SE(jsb_adpf_getThermalStatusMax), nullptr); - adpfObj.toObject()->defineProperty("thermalStatusNormalized", _SE(jsb_adpf_getThermalStatusNormalized), nullptr); - adpfObj.toObject()->defineProperty("thermalHeadroom", _SE(jsb_adpf_getThermalHeadroom), nullptr); - adpfObj.toObject()->defineProperty("onThermalStatusChanged", _SE(jsb_adpf_onThermalStatusChanged_get), _SE(jsb_adpf_onThermalStatusChanged_set)); - ns->setProperty("adpf", adpfObj); -} - -#endif // CC_PLATFORM_ANDROID - static bool JSB_process_get_argv(se::State &s) // NOLINT(readability-identifier-naming) { const auto &args = CC_CURRENT_APPLICATION()->getArguments(); @@ -1701,9 +1599,8 @@ bool jsb_register_global_variables(se::Object *global) { // NOLINT jsb_register_TextEncoder(global); jsb_register_TextDecoder(global); -#if CC_PLATFORM == CC_PLATFORM_ANDROID && CC_SUPPORT_ADPF + jsb_register_ADPF(__jsbObj); -#endif se::ScriptEngine::getInstance()->clearException();