diff --git a/interfaces/ets/BUILD.gn b/interfaces/ets/BUILD.gn index e458c08e3e82d25f5214354483ffe261c568eda4..0b2e343f84715af273f9a646ca92193042aa6e38 100644 --- a/interfaces/ets/BUILD.gn +++ b/interfaces/ets/BUILD.gn @@ -26,6 +26,8 @@ group("ace_ani_package") { "${ace_root}/interfaces/ets/ani/componentUtils:componentUtils_etc", "${ace_root}/interfaces/ets/ani/curves:curves_ani", "${ace_root}/interfaces/ets/ani/curves:curves_etc", + "${ace_root}/interfaces/ets/ani/displaySync:display_sync_ani", + "${ace_root}/interfaces/ets/ani/displaySync:display_sync_ani_abc_etc", "${ace_root}/interfaces/ets/ani/imagecache:imagecache_ani", "${ace_root}/interfaces/ets/ani/imagecache:imagecache_etc", "${ace_root}/interfaces/ets/ani/inspector:ani_inspector_group", diff --git a/interfaces/ets/ani/displaySync/BUILD.gn b/interfaces/ets/ani/displaySync/BUILD.gn new file mode 100644 index 0000000000000000000000000000000000000000..801338062f90e46584bd9e9e19d060c59166748c --- /dev/null +++ b/interfaces/ets/ani/displaySync/BUILD.gn @@ -0,0 +1,56 @@ +# Copyright (c) 2025 Huawei Device Co., Ltd. +# 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 +# +# http://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. + +import("//build/config/components/ets_frontend/ets2abc_config.gni") +import("//build/ohos.gni") +import("//foundation/arkui/ace_engine/ace_config.gni") + +config("ani_config") { + include_dirs = [ + "${ace_root}/frameworks", + "${ace_root}/interfaces", + "${ace_root}", + ] +} + +ohos_shared_library("display_sync_ani") { + public_configs = [ ":ani_config" ] + sources = [ "./src/display_sync.cpp" ] + deps = [ "$ace_root/build:libace_compatible" ] + external_deps = [ "runtime_core:ani" ] + part_name = "ace_engine" + subsystem_name = "arkui" + output_extension = "so" +} + +generate_static_abc("display_sync_ani_abc") { + base_url = "./ets" + files = [ + "./ets/@ohos.graphics.displaySync.ets", + "./ets/arkui.component.common.ets", + ] + paths_keys = [ "@ohos.graphics.displaySync" ] + paths_values = [ "./ets/@ohos.graphics.displaySync.ets" ] + dst_file = target_out_dir + "/display_sync_ani.abc" + out_puts = [ target_out_dir + "/display_sync_ani.abc" ] + is_boot_abc = "True" + device_dst_file = "/system/framework/display_sync_ani.abc" +} + +ohos_prebuilt_etc("display_sync_ani_abc_etc") { + source = target_out_dir + "/display_sync_ani.abc" + deps = [ ":display_sync_ani_abc" ] + module_install_dir = "framework" + subsystem_name = "arkui" + part_name = "ace_engine" +} diff --git a/interfaces/ets/ani/displaySync/ets/@ohos.graphics.displaySync.ets b/interfaces/ets/ani/displaySync/ets/@ohos.graphics.displaySync.ets new file mode 100644 index 0000000000000000000000000000000000000000..7e4ae5956e804648010f355d766175b21682f73c --- /dev/null +++ b/interfaces/ets/ani/displaySync/ets/@ohos.graphics.displaySync.ets @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 + * + * http://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. + */ + +import { ExpectedFrameRateRange } from './arkui.component.common' + +export namespace displaySync { + loadLibrary("display_sync_ani") + + export class IntervalInfo { + _timestamp: long; + _targetTimestamp: long; + + public get timestamp(): long { + return this._timestamp; + } + + public get targetTimestamp(): long { + return this._targetTimestamp; + } + + public set timestamp(value: long) { + this._timestamp = value; + } + + public set targetTimestamp(value: long) { + this._targetTimestamp = value; + } + + constructor(timestamp: long, targetTimestamp: long) { + this._timestamp = timestamp; + this._targetTimestamp = targetTimestamp; + } + } + + export native function create(): DisplaySync; + + class Cleaner { + private ptr: long = 0; + constructor(ptr: long) { + this.ptr = ptr; + } + native clean(): void; + } + + export function callback(cleaner: Cleaner): void { + cleaner.clean() + } + + let destroyRegister = new FinalizationRegistry(callback) + let unregisterToken = new object() + + class DisplaySync { + displaySync: long = 0; + private cleaner: Cleaner | null = null; + constructor(result: long) { + if (this.displaySync == 0) { + this.displaySync = result; + } + this.registerCleaner(this.displaySync) + } + + registerCleaner(ptr: long): void { + this.cleaner = new Cleaner(ptr) + destroyRegister.register(this, this.cleaner!, unregisterToken); + } + unregisterCleaner(): void { + destroyRegister.unregister(unregisterToken); + } + + public native setExpectedFrameRateRange(rateRange: ExpectedFrameRateRange) : void; + public native on(callbackType: string, callback: (intervalInfo: IntervalInfo) => void): void; + public native off(callbackType: string, callback?: (intervalInfo: IntervalInfo) => void): void; + public native start(): void; + public native stop(): void; + public native clean(): void; + } +} + diff --git a/interfaces/ets/ani/displaySync/ets/arkui.component.common.ets b/interfaces/ets/ani/displaySync/ets/arkui.component.common.ets new file mode 100644 index 0000000000000000000000000000000000000000..b563d7dc6d3b86c6624ecc9542f0a8bc92c379f0 --- /dev/null +++ b/interfaces/ets/ani/displaySync/ets/arkui.component.common.ets @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 + * + * http://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. + */ + +export interface ExpectedFrameRateRange { + + min: number, + + max: number, + + expected: number, +} diff --git a/interfaces/ets/ani/displaySync/src/display_sync.cpp b/interfaces/ets/ani/displaySync/src/display_sync.cpp new file mode 100644 index 0000000000000000000000000000000000000000..32305b78c5159e1181c019f98a573732dcbe9d33 --- /dev/null +++ b/interfaces/ets/ani/displaySync/src/display_sync.cpp @@ -0,0 +1,308 @@ +/* + * Copyright (c) 2025 Huawei Device Co., Ltd. + * 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 + * + * http://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 +#include "core/components_ng/manager/display_sync/ui_display_sync.h" + +namespace OHOS::Ace::Ani { + +class DisplaySync final { +public: + DisplaySync() = delete; + explicit DisplaySync(RefPtr& uiDisplaySync) + : uiDisplaySync_(uiDisplaySync) {} + + ~DisplaySync() + { + if (uiDisplaySync_) { + uiDisplaySync_->DelFromPipelineOnContainer(); + } + } + + RefPtr GetUIDisplaySync() const + { + return uiDisplaySync_; + } + + ani_ref GetOnframeRef() const + { + return onFrameRef_; + } + + void SetOnframeRef(const ani_ref& onframe) + { + onFrameRef_ = onframe; + } + + ani_ref onFrameRef_ = nullptr; + +private: + RefPtr uiDisplaySync_; +}; + +std::string ANIUtils_ANIStringToStdString(ani_env *env, ani_string ani_str) +{ + ani_size strSize; + env->String_GetUTF8Size(ani_str, &strSize); + + std::vector buffer(strSize + 1); + char *utf8Buffer = buffer.data(); + + ani_size bytesWritten = 0; + env->String_GetUTF8(ani_str, utf8Buffer, strSize + 1, &bytesWritten); + utf8Buffer[bytesWritten] = '\0'; + std::string content = std::string(utf8Buffer); + return content; +} + +void ParseExpectedFrameRateRange(ani_env *env, ani_object objOption, + FrameRateRange& frameRateRange) +{ + static const char *className = "Larkui/component/common/ExpectedFrameRateRange;"; + ani_class cls; + if (ANI_OK != env->FindClass(className, &cls)) { + return; + } + + ani_boolean isInstance; + if (ANI_OK != env->Object_InstanceOf(objOption, cls, &isInstance)) { + return; + } + + int32_t minFPS = 0; + int32_t maxFPS = 0; + int32_t expectedFPS = 0; + + ani_double minAni; + ani_double maxAni; + ani_double expectedAni; + env->Object_GetPropertyByName_Double(objOption, "min", &minAni); + env->Object_GetPropertyByName_Double(objOption, "max", &maxAni); + env->Object_GetPropertyByName_Double(objOption, "expected", &expectedAni); + + minFPS = static_cast(minAni); + maxFPS = static_cast(maxAni); + expectedFPS = static_cast(expectedAni); + frameRateRange.Set(minFPS, maxFPS, expectedFPS); +} + +static DisplaySync *GetDisplaySync(ani_env *env, ani_object obj) +{ + ani_long displaySync; + if (ANI_OK != env->Object_GetFieldByName_Long(obj, "displaySync", &displaySync)) { + return nullptr; + } + return reinterpret_cast(displaySync); +} + +static RefPtr GetUIDisplaySync(ani_env *env, ani_object obj) +{ + auto displaySync = GetDisplaySync(env, obj); + if (displaySync == nullptr) { + return nullptr; + } + auto uiDisplaySync = displaySync->GetUIDisplaySync(); + if (uiDisplaySync == nullptr) { + return nullptr; + } + return uiDisplaySync; +} + +ani_object createIntervalInfo(ani_env *env, int64_t timestamp, int64_t targetTimestamp) +{ + static const char *className = "L@ohos/graphics/displaySync/displaySync/IntervalInfo;"; + ani_class intervalInfo_cls; + + if (ANI_OK != env->FindClass(className, &intervalInfo_cls)) + { + return nullptr; + } + ani_method intervalInfoCtor; + env->Class_FindMethod(intervalInfo_cls, "", "JJ:V", &intervalInfoCtor); + ani_object intervalInfoObj; + env->Object_New( + intervalInfo_cls, intervalInfoCtor, &intervalInfoObj, ani_long(timestamp), ani_long(targetTimestamp)); + return intervalInfoObj; +} + +static void JSOnFrame_On(ani_env *env, ani_object obj, ani_string callbackType, ani_object callbackObj) +{ + if (ANIUtils_ANIStringToStdString(env, callbackType) != "frame") { + return; + } + auto displaySync = GetDisplaySync(env, obj); + if (displaySync == nullptr) { + return; + } + if (displaySync->GetOnframeRef()) { + return; + } + auto uiDisplaySync = displaySync->GetUIDisplaySync(); + if (uiDisplaySync == nullptr) { + return; + } + + ani_ref onFrameRef = reinterpret_cast(callbackObj); + ani_ref onFrameGlobalRef; + env->GlobalReference_Create(onFrameRef, &onFrameGlobalRef); + displaySync->SetOnframeRef(onFrameGlobalRef); + uiDisplaySync->RegisterOnFrameWithData([env, onFrameGlobalRef] (RefPtr displaySyncData) { + auto fnObj = reinterpret_cast(onFrameGlobalRef); + ani_ref result; + auto intervalInfo = createIntervalInfo(env, displaySyncData->timestamp_, displaySyncData->targetTimestamp_); + std::vector tmp = { reinterpret_cast(intervalInfo) }; + env->FunctionalObject_Call(fnObj, tmp.size(), tmp.data(), &result); + }); +} + +static void JSOnFrame_Off(ani_env *env, ani_object obj, ani_string callbackType, ani_object callbackObj) +{ + if (ANIUtils_ANIStringToStdString(env, callbackType) != "frame") { + return; + } + auto displaySync = GetDisplaySync(env, obj); + if (displaySync == nullptr) { + return; + } + auto uiDisplaySync = displaySync->GetUIDisplaySync(); + if (uiDisplaySync == nullptr) { + return; + } + displaySync->SetOnframeRef(nullptr); + uiDisplaySync->UnregisterOnFrame(); +} + +static void JSStart(ani_env *env, ani_object obj) { + if (auto uiDisplaySync = GetUIDisplaySync(env, obj); uiDisplaySync != nullptr) { + uiDisplaySync->AddToPipelineOnContainer(); + } +} + +static void JSStop(ani_env *env, ani_object obj) { + if (auto uiDisplaySync = GetUIDisplaySync(env, obj); uiDisplaySync != nullptr) { + uiDisplaySync->DelFromPipelineOnContainer(); + } +} + +static void JSSetExpectedFrameRateRange(ani_env *env, ani_object obj, ani_object objOption) +{ + if (auto uiDisplaySync = GetUIDisplaySync(env, obj); uiDisplaySync != nullptr) { + FrameRateRange frameRateRange; + ParseExpectedFrameRateRange(env, objOption, frameRateRange); + uiDisplaySync->SetExpectedFrameRateRange(frameRateRange); + } +} + +ani_object ANICreate(ani_env *env, [[maybe_unused]] ani_object object, [[maybe_unused]] ani_object aniOption) +{ + ani_object displaySync_obj = {}; + static const char *className = "L@ohos/graphics/displaySync/displaySync/DisplaySync;"; + ani_class cls; + if (ANI_OK != env->FindClass(className, &cls)) { + TAG_LOGE(AceLogTag::ACE_DISPLAY_SYNC, "[ANI] find class fail"); + return displaySync_obj; + } + + ani_method ctor; + if (ANI_OK != env->Class_FindMethod(cls, "", nullptr, &ctor)) { + TAG_LOGE(AceLogTag::ACE_DISPLAY_SYNC, "[ANI] find method fail"); + return displaySync_obj; + } + + auto uiDisplaySync = AceType::MakeRefPtr(); + DisplaySync* displaySync = new DisplaySync(uiDisplaySync); + if (ANI_OK != env->Object_New(cls, ctor, &displaySync_obj, reinterpret_cast(displaySync))) { + TAG_LOGE(AceLogTag::ACE_DISPLAY_SYNC, "[ANI] create displaySync fail"); + delete displaySync; + return displaySync_obj; + } + return displaySync_obj; +} + +static void clean([[maybe_unused]] ani_env *env, [[maybe_unused]] ani_object object) { + ani_long ptr; + if (ANI_OK != env->Object_GetFieldByName_Long(object, "ptr", &ptr)) { + return; + } + delete reinterpret_cast(ptr); +} + + +ani_status BindDisplaySync(ani_env *env) +{ + static const char *className = "L@ohos/graphics/displaySync/displaySync/DisplaySync;"; + ani_class cls; + if (ANI_OK != env->FindClass(className, &cls)) { + TAG_LOGE(AceLogTag::ACE_DISPLAY_SYNC, "[ANI] bind DisplaySync result fail"); + return ANI_ERROR; + } + + std::array methods = { + ani_native_function{"on", nullptr, reinterpret_cast(JSOnFrame_On)}, + ani_native_function{"off", nullptr, reinterpret_cast(JSOnFrame_Off)}, + ani_native_function{"start", ":V", reinterpret_cast(JSStart)}, + ani_native_function{"stop", ":V", reinterpret_cast(JSStop)}, + ani_native_function{"setExpectedFrameRateRange", nullptr, + reinterpret_cast(JSSetExpectedFrameRateRange)}, + }; + if (ANI_OK != env->Class_BindNativeMethods(cls, methods.data(), methods.size())) { + TAG_LOGE(AceLogTag::ACE_DISPLAY_SYNC, "[ANI] bind native method fail"); + return ANI_ERROR; + }; + + static const char *cleanerName = "L@ohos/graphics/displaySync/displaySync/Cleaner;"; + ani_class cleanerCls; + if (ANI_OK != env->FindClass(cleanerName, &cleanerCls)) { + return (ani_status)ANI_ERROR; + } + + std::array cleanerMethods = { + ani_native_function {"clean", nullptr, reinterpret_cast(clean) }, + }; + + if (ANI_OK != env->Class_BindNativeMethods(cleanerCls, cleanerMethods.data(), cleanerMethods.size())) { + return (ani_status)ANI_ERROR; + }; + return ANI_OK; +} +} // namespace OHOS::Ace::Ani + +ANI_EXPORT ani_status ANI_Constructor(ani_vm *vm, uint32_t *result) +{ + ani_env *env; + if (ANI_OK != vm->GetEnv(ANI_VERSION_1, &env)) { + return ANI_ERROR; + } + ani_namespace displaySyncNamespace; + if (ANI_OK != env->FindNamespace("L@ohos/graphics/displaySync/displaySync;", &displaySyncNamespace)) { + return ANI_ERROR; + } + std::array staticMethods = { + ani_native_function {"create", nullptr, + reinterpret_cast(OHOS::Ace::Ani::ANICreate)}, + }; + if (ANI_OK != env->Namespace_BindNativeFunctions( + displaySyncNamespace, staticMethods.data(), staticMethods.size())) { + return ANI_ERROR; + }; + + ani_status retBindResult = OHOS::Ace::Ani::BindDisplaySync(env); + if (retBindResult != ANI_OK) { + TAG_LOGE(OHOS::Ace::AceLogTag::ACE_DISPLAY_SYNC, "[ANI] BindDisplaySyncResult fail"); + return retBindResult; + } + *result = ANI_VERSION_1; + return ANI_OK; +}