1# Custom Rendering (XComponent) 2 3## Overview 4 5The **XComponent** is a rendering component that can be used for EGL/OpenGL ES and media data output. It uses the held [NativeWindow](../graphics/native-window-guidelines.md) to render graphics and is typically employed to meet complex custom rendering needs, such as displaying camera preview streams and rendering game graphics. You can specify different rendering methods through the **type** field, which are [XComponentType](../reference/apis-arkui/arkui-ts/ts-appendix-enums.md#xcomponenttype10).SURFACE and XComponentType.TEXTURE. For the SURFACE type, you display the custom drawing content on the screen separately. For the TEXTURE type, you combine custom drawing content with the content of the **XComponent** and display it on the screen. 6 7The **XComponent** is mainly used in three scenarios: 81. Managing the surface lifecycle with **XComponentController**: In this scenario, the **SurfaceId** is obtained on the ArkTS side; lifecycle callbacks and event callbacks (such as touch, mouse, and key event callbacks) are all triggered on the ArkTS side. 92. Managing the surface lifecycle with **OH_ArkUI_SurfaceHolder**: In this scenario, the **OH_ArkUI_SurfaceHolder** instance is created based on the **ArkUI_NodeHandle** corresponding to the **XComponent**; lifecycle callbacks, event callbacks, accessibility and variable frame rate callbacks are all triggered on the native side. 103. Managing the surface lifecycle with **NativeXComponent**: In this scenario, the native **XComponent** instance is obtained at the native layer, and the lifecycle callbacks of **XComponent**, as well as touch, mouse, key and other event callbacks are registered on the native side. 11 12## How Custom Drawing Works 13 14The **XComponent** provides a surface for custom drawing. To draw custom content on this surface, you can use the [NativeWindow](../graphics/native-window-guidelines.md) API to allocate and submit graphics buffers. This process pushes your custom content to the surface, and the **XComponent** then integrates the surface into the UI and displays the result. By default, the surface matches the size and position of the **XComponent**. Yet, you can adjust its position and size using the [setXComponentSurfaceRect](../reference/apis-arkui/arkui-ts/ts-basic-components-xcomponent.md#setxcomponentsurfacerect12) API if needed. 15 16The **XComponent** is responsible for creating the surface and notifying the application of surface-related information through callbacks. Applications can set the properties of the surface through dedicated APIs. It should be note that the component itself has no awareness of the actual drawn content and does not provide direct rendering APIs. 17 18> **NOTE** 19> 20> If your custom drawn content includes transparent elements, they will blend with the content below the surface. For example, if your content is fully transparent, the background of the **XComponent** is black, and the surface maintains its default size and position, the final display will be a black area. 21 22## Managing the Surface Lifecycle with XComponentController 23 24This scenario involves obtaining the **SurfaceId** on the ArkTS side, with layout information, lifecycle callbacks, and event callbacks (such as touch, mouse, and key event callbacks) all triggered on the ArkTS side before being optionally passed to the native side for processing. The development mainly involves the following use cases: 25- Use the **SurfaceId** obtained on the ArkTS side to call the **OH_NativeWindow_CreateNativeWindowFromSurfaceId** API on the native side to create a **NativeWindow** instance. 26- Use the **NativeWindow** instance with EGL APIs to develop custom drawing content, and allocate and submit buffers to the graphics queue. 27- Obtain lifecycle and event information on the ArkTS side and pass it to the native side for processing. 28 29> **NOTE** 30> 31> 1. **NativeWindow** instances are cached in a dictionary on the native side. Their keys must be globally unique, and they must be promptly removed from the dictionary when the corresponding **XComponent** component is destroyed. 32> 33> 2. For the **XComponent** components of the TEXTURE or SURFACE type created using [typeNode](../reference/apis-arkui/js-apis-arkui-frameNode.md#typenode12), due to their lifecycle differences from declarative components, the buffer size remains unset after component creation. Therefore, before starting to draw content, call the [OH_NativeWindow_NativeWindowHandleOpt](../reference/apis-arkgraphics2d/capi-external-window-h.md#oh_nativewindow_nativewindowhandleopt) API to set the buffer size. 34> 35> 3. During development with multiple **XComponent** components, make sure the keys used to cache resources on the Native side are unique. The two recommended key formats are as follows: **Id** + random number; **surfaceId**. 36 37**lifecycle Callbacks** 38 39- onSurfaceCreated 40 41 Triggered when the surface of the **XComponent** component is ready. 42 43 ArkTS-side sequence 44 45  46 47- onSurfaceChanged 48 49 Triggered after surface size changes trigger re-layout. 50 51 ArkTS-side sequence 52 53  54 55- onSurfaceDestroyed 56 57 Triggered when the **XComponent** component is destroyed, which is consistent with the destruction timing of common ArkUI components. 58 59 ArkTS-side sequence 60 61  62 63**Available APIs** 64 65XComponentController on the ArkTS side 66 67| API | Description | 68| ------------------------------------------------------------ | ------------------------------------------------------------ | 69| getXComponentSurfaceId(): string | Obtains the ID of the surface associated with the **XComponent**. | 70| onSurfaceCreated(surfaceId: string): void | Called when the surface held by the **XComponent** is created. | 71| onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void | Called when the size of the surface held by the **XComponent** changes (including the initial size change upon creation).| 72| onSurfaceDestroyed(surfaceId: string): void | Called when the surface held by the **XComponent** is destroyed. | 73 74Native side 75 76| API | Description | 77| ------------------------------------------------------------ | ------------------------------------------------------------ | 78| int32_t OH_NativeWindow_CreateNativeWindowFromSurfaceId (uint64_t surfaceId, OHNativeWindow **window ) | Creates an **OHNativeWindow** instance based on a surface ID. | 79| void OH_NativeWindow_DestroyNativeWindow (OHNativeWindow* window) | Decreases the reference count of an **OHNativeWindow** instance by 1 and when the reference count reaches 0, destroys the instance.| 80 81**How to Develop** 82 83The following uses the SURFACE type as an example to describe how to use the **XComponent** to pass in **SurfaceId** on the ArkTS side, create a **NativeWindow** instance on the native side, then create an EGL/GLES environment, implement drawing graphics on the main page, and change the graphics color. 84 851. Define the **XComponent** on the UI. 86 87 ```javascript 88 // Function declarations defined in cpp/types/libnativerender/Index.d.ts 89 type XComponentContextStatus = { 90 hasDraw: boolean, 91 hasChangeColor: boolean, 92 }; 93 export const SetSurfaceId: (id: BigInt) => any; 94 export const ChangeSurface: (id: BigInt, w: number, h: number) =>any; 95 export const DrawPattern: (id: BigInt) => any; 96 export const GetXComponentStatus: (id: BigInt) => XComponentContextStatus 97 export const ChangeColor: (id: BigInt) => any; 98 export const DestroySurface: (id: BigInt) => any; 99 ``` 100 101 ```typescript 102 import nativeRender from 'libnativerender.so'; 103 104 // Override XComponentController to set lifecycle callbacks. 105 class MyXComponentController extends XComponentController { 106 onSurfaceCreated(surfaceId: string): void { 107 console.log(`onSurfaceCreated surfaceId: ${surfaceId}`) 108 nativeRender.SetSurfaceId(BigInt(surfaceId)); 109 } 110 111 onSurfaceChanged(surfaceId: string, rect: SurfaceRect): void { 112 console.log(`onSurfaceChanged surfaceId: ${surfaceId}, rect: ${JSON.stringify(rect)}}`) 113 // Call ChangeSurface to draw content in onSurfaceChanged. 114 nativeRender.ChangeSurface(BigInt(surfaceId), rect.surfaceWidth, rect.surfaceHeight) 115 } 116 117 onSurfaceDestroyed(surfaceId: string): void { 118 console.log(`onSurfaceDestroyed surfaceId: ${surfaceId}`) 119 nativeRender.DestroySurface(BigInt(surfaceId)) 120 } 121 } 122 123 @Entry 124 @Component 125 struct Index { 126 @State currentStatus: string = "index"; 127 xComponentController: XComponentController = new MyXComponentController(); 128 129 build() { 130 Column() { 131 //... 132 // Define XComponent in an .ets file. 133 Column({ space: 10 }) { 134 XComponent({ 135 type: XComponentType.SURFACE, 136 controller: this.xComponentController 137 }) 138 Text(this.currentStatus) 139 .fontSize('24fp') 140 .fontWeight(500) 141 } 142 .onClick(() => { 143 let surfaceId = this.xComponentController.getXComponentSurfaceId() 144 nativeRender.ChangeColor(BigInt(surfaceId)) 145 let hasChangeColor: boolean = false; 146 if (nativeRender.GetXComponentStatus(BigInt(surfaceId))) { 147 hasChangeColor = nativeRender.GetXComponentStatus(BigInt(surfaceId)).hasChangeColor; 148 } 149 if (hasChangeColor) { 150 this.currentStatus = "change color"; 151 } 152 }) 153 154 //... 155 Row() { 156 Button('Draw Star') 157 .fontSize('16fp') 158 .fontWeight(500) 159 .margin({ bottom: 24 }) 160 .onClick(() => { 161 let surfaceId = this.xComponentController.getXComponentSurfaceId() 162 nativeRender.DrawPattern(BigInt(surfaceId)) 163 let hasDraw: boolean = false; 164 if (nativeRender.GetXComponentStatus(BigInt(surfaceId))) { 165 hasDraw = nativeRender.GetXComponentStatus(BigInt(surfaceId)).hasDraw; 166 } 167 if (hasDraw) { 168 this.currentStatus = "draw star" 169 } 170 }) 171 .width('53.6%') 172 .height(40) 173 } 174 .width('100%') 175 .justifyContent(FlexAlign.Center) 176 .alignItems(VerticalAlign.Bottom) 177 .layoutWeight(1) 178 } 179 .width('100%') 180 .height('100%') 181 } 182 } 183 ``` 184 1852. Register the Node-API module. For details, see [Node-API Development Specifications](../napi/napi-guidelines.md). 186 187 ```typescript 188 #include <hilog/log.h> 189 #include "common/common.h" 190 #include "manager/plugin_manager.h" 191 namespace NativeXComponentSample { 192 // In the napi_init.cpp file, use the Init method to register the target function to pass in the encapsulated C++ methods for the ArkTS method to call. 193 EXTERN_C_START 194 static napi_value Init(napi_env env, napi_value exports) { 195 // ... 196 // Expose the SetSurfaceId(), ChangeSurface(), and DestroySurface() APIs to the ArkTS side. 197 // DrawPattern(),ChangeColor(),GetXComponentStatus() 198 napi_property_descriptor desc[] = { 199 {"SetSurfaceId", nullptr, PluginManager::SetSurfaceId, nullptr, nullptr, nullptr, napi_default, nullptr}, 200 {"ChangeSurface", nullptr, PluginManager::ChangeSurface, nullptr, nullptr, nullptr, napi_default, nullptr}, 201 {"DestroySurface", nullptr, PluginManager::DestroySurface, nullptr, nullptr, nullptr, napi_default, nullptr}, 202 {"DrawPattern", nullptr, PluginManager::DrawPattern, nullptr, nullptr, nullptr, napi_default, nullptr}, 203 {"ChangeColor", nullptr, PluginManager::ChangeColor, nullptr, nullptr, nullptr, napi_default, nullptr}, 204 {"GetXComponentStatus", nullptr, PluginManager::GetXComponentStatus, nullptr, nullptr, nullptr, napi_default, 205 nullptr}}; 206 if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { 207 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed"); 208 return nullptr; 209 } 210 return exports; 211 } 212 EXTERN_C_END 213 // Provide module descriptor configuration. You can modify parameters as needed. 214 static napi_module nativerenderModule = {.nm_version = 1, 215 .nm_flags = 0, 216 .nm_filename = nullptr, 217 // Entry point function 218 .nm_register_func = Init, 219 // Module name 220 .nm_modname = "nativerender", 221 .nm_priv = ((void *)0), 222 .reserved = {0}}; 223 } // namespace NativeXComponentSample 224 // The method decorated by __attribute__((constructor)) is automatically called by the system. The Node-API napi_module_register() is used to pass in the module descriptor configuration for module registration. 225 extern "C" __attribute__((constructor)) void RegisterModule(void) { 226 napi_module_register(&NativeXComponentSample::nativerenderModule); 227 } 228 ``` 229 2303. Implement the preceding six registered functions on the native side. 231 232 ```cpp 233 // Define the PluginManager class. 234 class PluginManager { 235 public: 236 ~PluginManager(); 237 static PluginRender *GetPluginRender(int64_t &id); 238 static napi_value ChangeColor(napi_env env, napi_callback_info info); 239 static napi_value DrawPattern(napi_env env, napi_callback_info info); 240 static napi_value SetSurfaceId(napi_env env, napi_callback_info info); 241 static napi_value ChangeSurface(napi_env env, napi_callback_info info); 242 static napi_value DestroySurface(napi_env env, napi_callback_info info); 243 static napi_value GetXComponentStatus(napi_env env, napi_callback_info info); 244 245 public: 246 static std::unordered_map<int64_t, PluginRender *> pluginRenderMap_; 247 static std::unordered_map<int64_t, OHNativeWindow *> windowMap_; 248 }; 249 250 // Parse the surfaceId passed from ArkTS. Here, surfaceId is a 64-bit integer value. 251 int64_t ParseId(napi_env env, napi_callback_info info) { 252 if ((env == nullptr) || (info == nullptr)) { 253 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "env or info is null"); 254 return -1; 255 } 256 size_t argc = 1; 257 napi_value args[1] = {nullptr}; 258 if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { 259 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "GetContext napi_get_cb_info failed"); 260 return -1; 261 } 262 int64_t value = 0; 263 bool lossless = true; 264 if (napi_ok != napi_get_value_bigint_int64(env, args[0], &value, &lossless)) { 265 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "ParseId", "Get value failed"); 266 return -1; 267 } 268 return value; 269 } 270 271 // Set SurfaceId and initialize NativeWindow based on SurfaceId. 272 napi_value PluginManager::SetSurfaceId(napi_env env, napi_callback_info info) { 273 int64_t surfaceId = ParseId(env, info); 274 OHNativeWindow *nativeWindow; 275 PluginRender *pluginRender; 276 if (windowMap_.find(surfaceId) == windowMap_.end()) { 277 OH_NativeWindow_CreateNativeWindowFromSurfaceId(surfaceId, &nativeWindow); 278 windowMap_[surfaceId] = nativeWindow; 279 } 280 if (pluginRenderMap_.find(surfaceId) == pluginRenderMap_.end()) { 281 pluginRender = new PluginRender(surfaceId); 282 pluginRenderMap_[surfaceId] = pluginRender; 283 } 284 pluginRender->InitNativeWindow(nativeWindow); 285 return nullptr; 286 } 287 288 void PluginRender::InitNativeWindow(OHNativeWindow *window) { 289 eglCore_->EglContextInit(window); // For details about the EglContextInit implementation, see the "Native XComponent Scenario" section. 290 } 291 292 // Implement surface size changes based on the passed surfaceId, width, and height. 293 napi_value PluginManager::ChangeSurface(napi_env env, napi_callback_info info) { 294 if ((env == nullptr) || (info == nullptr)) { 295 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 296 "ChangeSurface: OnLoad env or info is null"); 297 return nullptr; 298 } 299 int64_t surfaceId = 0; 300 size_t argc = 3; 301 napi_value args[3] = {nullptr}; 302 303 if (napi_ok != napi_get_cb_info(env, info, &argc, args, nullptr, nullptr)) { 304 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 305 "ChangeSurface: GetContext napi_get_cb_info failed"); 306 return nullptr; 307 } 308 bool lossless = true; 309 int index = 0; 310 if (napi_ok != napi_get_value_bigint_int64(env, args[index++], &surfaceId, &lossless)) { 311 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "ChangeSurface: Get value failed"); 312 return nullptr; 313 } 314 double width; 315 if (napi_ok != napi_get_value_double(env, args[index++], &width)) { 316 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "ChangeSurface: Get width failed"); 317 return nullptr; 318 } 319 double height; 320 if (napi_ok != napi_get_value_double(env, args[index++], &height)) { 321 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "ChangeSurface: Get height failed"); 322 return nullptr; 323 } 324 auto pluginRender = GetPluginRender(surfaceId); 325 if (pluginRender == nullptr) { 326 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "ChangeSurface: Get pluginRender failed"); 327 return nullptr; 328 } 329 pluginRender->UpdateNativeWindowSize(width, height); 330 return nullptr; 331 } 332 333 void PluginRender::UpdateNativeWindowSize(int width, int height) { 334 eglCore_->UpdateSize(width, height); // For details about the UpdateSize implementation, see the "Native XComponent Scenario" section. 335 if (!hasChangeColor_ && !hasDraw_) { 336 eglCore_->Background(); // For details about the Background implementation, see the "Native XComponent Scenario" section. 337 } 338 } 339 340 // Destroy the surface. 341 napi_value PluginManager::DestroySurface(napi_env env, napi_callback_info info) { 342 int64_t surfaceId = ParseId(env, info); 343 auto pluginRenderMapIter = pluginRenderMap_.find(surfaceId); 344 if (pluginRenderMapIter != pluginRenderMap_.end()) { 345 delete pluginRenderMapIter->second; 346 pluginRenderMap_.erase(pluginRenderMapIter); 347 } 348 auto windowMapIter = windowMap_.find(surfaceId); 349 if (windowMapIter != windowMap_.end()) { 350 OH_NativeWindow_DestroyNativeWindow(windowMapIter->second); 351 windowMap_.erase(windowMapIter); 352 } 353 return nullptr; 354 } 355 356 // Implement the EGL drawing logic. 357 napi_value PluginManager::DrawPattern(napi_env env, napi_callback_info info) { 358 int64_t surfaceId = ParseId(env, info); 359 auto pluginRender = GetPluginRender(surfaceId); 360 if (pluginRender == nullptr) { 361 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "DrawPattern: Get pluginRender failed"); 362 return nullptr; 363 } 364 pluginRender->DrawPattern(); 365 return nullptr; 366 } 367 368 PluginRender *PluginManager::GetPluginRender(int64_t &id) { 369 if (pluginRenderMap_.find(id) != pluginRenderMap_.end()) { 370 return pluginRenderMap_[id]; 371 } 372 return nullptr; 373 } 374 375 void PluginRender::DrawPattern() { 376 eglCore_->Draw(hasDraw_); // For details about the Draw implementation, see the "Native XComponent Scenario" section. 377 } 378 379 // Implement the feature of changing the color of the drawn graphics. 380 napi_value PluginManager::ChangeColor(napi_env env, napi_callback_info info) { 381 int64_t surfaceId = ParseId(env, info); 382 auto pluginRender = GetPluginRender(surfaceId); 383 if (pluginRender == nullptr) { 384 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "ChangeColor: Get pluginRender failed"); 385 return nullptr; 386 } 387 pluginRender->ChangeColor(); // For details about the ChangeColor implementation, see the "Native XComponent Scenario" section. 388 return nullptr; 389 } 390 391 void PluginRender::ChangeColor() { eglCore_->ChangeColor(hasChangeColor_); } 392 393 // Obtain the XComponent status and return it to the ArkTS side. 394 napi_value PluginManager::GetXComponentStatus(napi_env env, napi_callback_info info) { 395 int64_t surfaceId = ParseId(env, info); 396 auto pluginRender = GetPluginRender(surfaceId); 397 if (pluginRender == nullptr) { 398 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 399 "GetXComponentStatus: Get pluginRender failed"); 400 return nullptr; 401 } 402 napi_value hasDraw; 403 napi_value hasChangeColor; 404 napi_status ret = napi_create_int32(env, pluginRender->HasDraw(), &(hasDraw)); 405 if (ret != napi_ok) { 406 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 407 "GetXComponentStatus: napi_create_int32 hasDraw_ error"); 408 return nullptr; 409 } 410 ret = napi_create_int32(env, pluginRender->HasChangedColor(), &(hasChangeColor)); 411 if (ret != napi_ok) { 412 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 413 "GetXComponentStatus: napi_create_int32 hasChangeColor_ error"); 414 return nullptr; 415 } 416 napi_value obj; 417 ret = napi_create_object(env, &obj); 418 if (ret != napi_ok) { 419 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 420 "GetXComponentStatus: napi_create_object error"); 421 return nullptr; 422 } 423 ret = napi_set_named_property(env, obj, "hasDraw", hasDraw); 424 if (ret != napi_ok) { 425 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 426 "GetXComponentStatus: napi_set_named_property hasDraw error"); 427 return nullptr; 428 } 429 ret = napi_set_named_property(env, obj, "hasChangeColor", hasChangeColor); 430 if (ret != napi_ok) { 431 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 432 "GetXComponentStatus: napi_set_named_property hasChangeColor error"); 433 return nullptr; 434 } 435 return obj; 436 } 437 438 int32_t PluginRender::HasDraw() { return hasDraw_; } 439 440 int32_t PluginRender::HasChangedColor() { return hasChangeColor_; } 441 ``` 442 4434. Configure the specific CMakeLists to use the CMake toolchain to compile the C++ source code into a dynamic link library file. 444 445 ```cmake 446 # Set the minimum CMake version. 447 cmake_minimum_required(VERSION 3.4.1) 448 # Project name 449 project(XComponent) 450 451 set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 452 add_definitions(-DOHOS_PLATFORM) 453 # Set the header file search directory. 454 include_directories( 455 ${NATIVERENDER_ROOT_PATH} 456 ${NATIVERENDER_ROOT_PATH}/include 457 ) 458 # Add the **nativerender** dynamic library, with the **libnativerender.so** library file. Add the .cpp files. 459 add_library(nativerender SHARED 460 render/egl_core.cpp 461 render/plugin_render.cpp 462 manager/plugin_manager.cpp 463 napi_init.cpp 464 ) 465 466 find_library( 467 # Set the name of the path variable. 468 EGL-lib 469 # Set the name of the NDK library to search for. 470 EGL 471 ) 472 473 find_library( 474 # Set the name of the path variable. 475 GLES-lib 476 # Set the name of the NDK library to search for. 477 GLESv3 478 ) 479 480 find_library( 481 # Set the name of the path variable. 482 hilog-lib 483 # Set the name of the NDK library to search for. 484 hilog_ndk.z 485 ) 486 487 find_library( 488 # Set the name of the path variable. 489 libace-lib 490 # Set the name of the NDK library to search for. 491 ace_ndk.z 492 ) 493 494 find_library( 495 # Set the name of the path variable. 496 libnapi-lib 497 # Set the name of the NDK library to search for. 498 ace_napi.z 499 ) 500 501 find_library( 502 # Set the name of the path variable. 503 libuv-lib 504 # Set the name of the NDK library to search for. 505 uv 506 ) 507 # Add the libraries to be linked. 508 target_link_libraries(nativerender PUBLIC 509 ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libnative_window.so) 510 ``` 511 512## Managing the Surface Lifecycle with OH_ArkUI_SurfaceHolder 513 514Unlike the scenario where the surface lifecycle is managed with **XComponentController**, this scenario allows applications to create an **OH_ArkUI_SurfaceHolder** object based on the **ArkUI_NodeHandle** corresponding to the **XComponent** component. Through relevant APIs on the **OH_ArkUI_SurfaceHolder**, you can register surface lifecycle callbacks and implement **XComponent** capabilities such as accessibility and variable frame rate. In addition, listening for basic and gesture events on the **XComponent** component can be achieved using ArkUI NDK APIs through the **ArkUI_NodeHandle** object. For details, see [Listening for Component Events](./ndk-listen-to-component-events.md). The development mainly involves the following use cases: 515- For **XComponent** components created on the ArkTS side, you can pass the corresponding FrameNode to the native side to obtain an **ArkUI_NodeHandle** object. For **XComponent** components created on the native side, you can directly obtain the **ArkUI_NodeHandle** object. Then, call the **OH_ArkUI_SurfaceHolder_Create** API to create an **OH_ArkUI_SurfaceHolder** instance. 516- Register lifecycle and event callbacks using the **OH_ArkUI_SurfaceHolder** instance and obtain a **NativeWindow** instance. 517- Use the **NativeWindow** instance with EGL APIs to develop custom drawing content, and allocate and submit buffers to the graphics queue. 518 519**lifecycle Callbacks** 520 521- OnSurfaceCreated 522 523 Triggered when the surface of the XComponent component is ready and either of the following conditions is met: 524 1. The component is attached to the component tree with **autoInitialize = true**. 525 2. The **OH_ArkUI_XComponent_Initialize** API is called. 526 527 ArkTS-side sequence 528 529  530- OnSurfaceChanged 531 532 Triggered when surface size changes trigger re-layout, after the **OnSurfaceCreated** callback is successfully triggered. 533 534 ArkTS-side sequence 535 536  537 538- OnSurfaceDestroyed 539 540 Triggered when the component is detached from the component tree with **autoInitialize=true** or the **OH_ArkUI_XComponent_Finalize** API is called. 541 542 ArkTS-side sequence 543 544  545 546 547**Available APIs** 548 549| API | Description | 550| ------------------------------------------------------------ | ------------------------------------------------------------ | 551| OH_ArkUI_QueryModuleInterfaceByName(ArkUI_NativeAPIVariantKind type, const char* structName) | Obtains the native API set of a specified type. | 552| OH_ArkUI_XComponent_GetNativeWindow(OH_ArkUI_SurfaceHolder* surfaceHolder) | Obtains the **NativeWindow** instance associated with an **OH_ArkUI_SurfaceHolder** instance. | 553| OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(OH_ArkUI_SurfaceHolder* surfaceHolder, OH_ArkUI_SurfaceCallback* callback) | Removes the previously added surface lifecycle callback from an **OH_ArkUI_SurfaceHolder** instance. | 554| OH_ArkUI_SurfaceCallback_Dispose(OH_ArkUI_SurfaceCallback* callback) | Disposes of an **OH_ArkUI_SurfaceCallback** object. | 555| OH_ArkUI_SurfaceHolder_Dispose(OH_ArkUI_SurfaceHolder* surfaceHolder) | Disposes of an **OH_ArkUI_SurfaceHolder** object. | 556| OH_ArkUI_NodeEvent_GetEventType(ArkUI_NodeEvent* event) | Obtains the event type from a component event. | 557| OH_ArkUI_NodeEvent_GetNodeHandle(ArkUI_NodeEvent* event) | Obtains the component object that triggers a component event. | 558| OH_ArkUI_GetNodeHandleFromNapiValue(napi_env env, napi_value frameNode, ArkUI_NodeHandle* handle) | Obtains an **ArkUI_NodeHandle** object on the native side mapped from the **FrameNode** object created on the ArkTS side. | 559| OH_ArkUI_SurfaceHolder_Create(ArkUI_NodeHandle node) | Creates an **OH_ArkUI_SurfaceHolder** object from the **XComponent** node. | 560| OH_ArkUI_SurfaceCallback_Create() | Creates an **OH_ArkUI_SurfaceCallback** object. | 561| OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceCreated)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | Registers the **onSurfaceCreated** callback with an **OH_ArkUI_SurfaceCallback** object. | 562| OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceChanged)(OH_ArkUI_SurfaceHolder* surfaceHolder, uint64_t width, uint64_t height)) | Registers the **onSurfaceChanged** callback with an **OH_ArkUI_SurfaceCallback** object. | 563| OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceDestroyed)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | Registers the **onSurfaceDestroyed** callback with an **OH_ArkUI_SurfaceCallback** object. | 564| OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceShow)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | Registers the **onSurfaceShow** callback with an **OH_ArkUI_SurfaceCallback** object. | 565| OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(OH_ArkUI_SurfaceCallback* callback, void (\*onSurfaceHide)(OH_ArkUI_SurfaceHolder* surfaceHolder)) | Registers the **onSurfaceHide** callback with an **OH_ArkUI_SurfaceCallback** object. | 566| OH_ArkUI_XComponent_RegisterOnFrameCallback(ArkUI_NodeHandle node, void (*callback)(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp)) | Registers the **onFrame** callback for the **XComponent** node. | 567| OH_ArkUI_SurfaceHolder_AddSurfaceCallback(OH_ArkUI_SurfaceHolder* surfaceHolder, OH_ArkUI_SurfaceCallback* callback) | Registers the **OH_ArkUI_SurfaceCallback** object with an **OH_ArkUI_SurfaceHolder** instance. | 568| OH_ArkUI_AccessibilityProvider_Create(ArkUI_NodeHandle node) | Creates an **ArkUI_AccessibilityProvider** object from the **XComponent** node. | 569| OH_ArkUI_XComponent_UnregisterOnFrameCallback(ArkUI_NodeHandle node) | Unregisters the **onFrame callback** of the **XComponent** node. | 570| OH_ArkUI_AccessibilityProvider_Dispose(ArkUI_AccessibilityProvider* provider) | Disposes of an **ArkUI_AccessibilityProvider** object. | 571| OH_ArkUI_XComponent_SetExpectedFrameRateRange(ArkUI_NodeHandle node, OH_NativeXComponent_ExpectedRateRange range) | Sets the expected frame rate range for the **XComponent** node. | 572| OH_ArkUI_XComponent_SetNeedSoftKeyboard(ArkUI_NodeHandle node, bool needSoftKeyboard) | Sets whether the **XComponent** node needs to display the soft keyboard when it gains focus. | 573 574**How to Develop** 575 576This example shows how to create an **XComponent** of the SURFACE type on the ArkTS side (for details about how to create an **ArkUI_NodeHandle** object corresponding to the **XComponent** on the native side, see [ArkUI_NativeNodeAPI_1](../reference/apis-arkui/capi-arkui-nativemodule-arkui-nativenodeapi-1.md)). It describes how to use the **XComponent** component to call APIs related to **OH_ArkUI_SurfaceHolder** for managing the surface lifecycle, create an EGL/OpenGL ES environment on the native side to draw graphics on the main page, and change the color of the graphics. 577 5781. Define the **XComponent** on the UI. 579 580 ```typescript 581 import native from 'libnativerender.so'; 582 583 @Entry 584 @Component 585 struct Index { 586 xcomponentId: string = 'xcp' + (new Date().getTime()); 587 @State isShow: boolean = true; 588 @State minRate: number = 0; 589 @State maxRate: number = 120; 590 @State expected: number = 60; 591 needSoftKeyboard: boolean = false; 592 @State needSoftKeyboardState: string = 'needSoftKeyboard=' + this.needSoftKeyboard; 593 @State text: string = 'Tap XComponent to hide the soft keyboard' 594 controller: TextInputController = new TextInputController() 595 596 build() { 597 Column() { 598 TextInput({ text: this.text, placeholder: 'please input ...', controller: this.controller }) 599 .placeholderColor(Color.Grey) 600 .placeholderFont({ size: 14, weight: 400 }) 601 .caretColor(Color.Blue) 602 .width(400) 603 .height(40) 604 .margin(20) 605 .fontSize(14) 606 .fontColor(Color.Black) 607 .onChange((value: string) => { 608 this.text = value 609 }) 610 Column() { 611 if (this.isShow) { 612 XComponent({ 613 type: XComponentType.SURFACE 614 }) 615 .id(this.xcomponentId) 616 .onAttach(() => { 617 let node = this.getUIContext().getFrameNodeById(this.xcomponentId) 618 native.bindNode(this.xcomponentId, node) 619 }) 620 .onDetach(() => { 621 native.unbindNode(this.xcomponentId) 622 }) 623 .width(200) 624 .height(200) 625 .focusable(true) 626 .focusOnTouch(true) 627 .defaultFocus(true) 628 } 629 }.height(200) 630 631 Button('Create/Destroy').onClick(() => { 632 this.isShow = !this.isShow; 633 }) 634 635 Column() { 636 Text('Expected frame rate') 637 .textAlign(TextAlign.Start) 638 .fontSize(15) 639 .border({ width: 1 }) 640 .padding(10) 641 .width('100%') 642 .margin(5) 643 Text('min: ' + this.minRate) 644 Slider({ 645 value: this.minRate, 646 min: 0, 647 max: 240, 648 step: 1 649 }).onChange((value: number, mode: SliderChangeMode) => { 650 this.minRate = value; 651 native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected) 652 }).width('100%') 653 Text('max: ' + this.maxRate) 654 Slider({ 655 value: this.maxRate, 656 min: 0, 657 max: 240, 658 step: 1 659 }).onChange((value: number, mode: SliderChangeMode) => { 660 this.maxRate = value; 661 native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected) 662 }).width('100%') 663 Text('expected: ' + this.expected) 664 Slider({ 665 value: this.expected, 666 min: 0, 667 max: 240, 668 step: 1 669 }).onChange((value: number, mode: SliderChangeMode) => { 670 this.expected = value; 671 native.setFrameRate(this.xcomponentId, this.minRate, this.maxRate, this.expected) 672 }).width('100%') 673 }.backgroundColor("#F0FAFF") 674 675 Button(this.needSoftKeyboardState) 676 .onClick(() => { 677 this.needSoftKeyboard = !this.needSoftKeyboard; 678 this.needSoftKeyboardState = 'needSoftKeyboard=' + this.needSoftKeyboard; 679 native.setNeedSoftKeyboard(this.xcomponentId, this.needSoftKeyboard); 680 this.text = this.needSoftKeyboard ? 'Tapping XComponent will not hide the keyboard' : 'Tap XComponent to hide the keyboard' 681 }) 682 } 683 .width('100%') 684 } 685 } 686 ``` 687 6882. Register the Node-API module. For details, see [Node-API Development Specifications](../napi/napi-guidelines.md). 689 690 ```c++ 691 #include "manager/plugin_manager.h" 692 693 namespace NativeXComponentSample { 694 695 // In the napi_init.cpp file, use the Init method to register the target function to pass in the encapsulated C++ methods for the ArkTS method to call. 696 EXTERN_C_START 697 static napi_value Init(napi_env env, napi_value exports) 698 { 699 // Expose APIs to the ArkTS side. 700 napi_property_descriptor desc[] = { 701 {"bindNode", nullptr, PluginManager::BindNode, 702 nullptr, nullptr, nullptr, napi_default, nullptr}, 703 {"unbindNode", nullptr, PluginManager::UnbindNode, 704 nullptr, nullptr, nullptr, napi_default, nullptr}, 705 {"setFrameRate", nullptr, PluginManager::SetFrameRate, 706 nullptr, nullptr, nullptr, napi_default, nullptr}, 707 {"setNeedSoftKeyboard", nullptr, PluginManager::SetNeedSoftKeyboard, 708 nullptr, nullptr, nullptr, napi_default, nullptr}, 709 }; 710 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 711 return exports; 712 } 713 EXTERN_C_END 714 715 // Provide module descriptor configuration. You can modify parameters as needed. 716 static napi_module demoModule = { 717 .nm_version = 1, 718 .nm_flags = 0, 719 .nm_filename = nullptr, 720 // Entry point function 721 .nm_register_func = Init, // Specify the callback for when the corresponding module is loaded. 722 // Module name 723 .nm_modname = "nativerender", // Specify the module name, which must be consistent with the value of libraryname in the XComponent declaration on the ArkTS side. 724 .nm_priv = ((void*)0), 725 .reserved = { 0 }, 726 }; 727 } 728 729 // The method decorated by __attribute__((constructor)) is automatically called by the system. The Node-API napi_module_register() is used to pass in the module descriptor configuration for module registration. 730 extern "C" __attribute__((constructor)) void RegisterEntryModule(void) 731 { 732 napi_module_register(&NativeXComponentSample::demoModule); 733 } 734 ``` 735 7363. Register the XComponent lifecycle, event, accessibility, and variable frame rate callbacks using C APIs. 737 738 (1) Define the **BindNode**, **UnbindNode**, **SetFrameRate**, and **SetNeedSoftKeyboard** APIs, which will be executed by the corresponding **bindNode**, **unbindNode**, **setFrameRate**, and **setNeedSoftKeyboard** APIs exposed to the ArkTS side. 739 740 ```c++ 741 // plugin_manager.h 742 namespace NativeXComponentSample { 743 class PluginManager { 744 public: 745 ~PluginManager(); 746 static napi_value BindNode(napi_env env, napi_callback_info info); 747 static napi_value UnbindNode(napi_env env, napi_callback_info info); 748 static napi_value SetFrameRate(napi_env env, napi_callback_info info); 749 static napi_value SetNeedSoftKeyboard(napi_env env, napi_callback_info info); 750 751 public: 752 static std::unordered_map<std::string, ArkUI_NodeHandle> nodeHandleMap_; 753 static std::unordered_map<void *, EGLRender *> renderMap_; 754 static std::unordered_map<void *, OH_ArkUI_SurfaceCallback *> callbackMap_; 755 static std::unordered_map<void *, OH_ArkUI_SurfaceHolder *> surfaceHolderMap_; 756 static ArkUI_AccessibilityProvider* provider_; 757 }; 758 } 759 ``` 760 761 ```c++ 762 // plugin_manager.cpp 763 std::unordered_map<std::string, ArkUI_NodeHandle> PluginManager::nodeHandleMap_; 764 std::unordered_map<void *, EGLRender *> PluginManager::renderMap_; 765 std::unordered_map<void *, OH_ArkUI_SurfaceCallback *> PluginManager::callbackMap_; 766 std::unordered_map<void *, OH_ArkUI_SurfaceHolder *> PluginManager::surfaceHolderMap_; 767 ArkUI_NativeNodeAPI_1 *nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1 *>( 768 OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1")); 769 770 std::string value2String (napi_env env, napi_value value) { // Convert napi_value to a string variable. 771 size_t stringSize = 0; 772 napi_get_value_string_utf8(env, value, nullptr, 0, &stringSize); 773 std::string valueString; 774 valueString.resize(stringSize); 775 napi_get_value_string_utf8(env, value, &valueString[0], stringSize + 1, &stringSize); 776 return valueString; 777 } 778 779 napi_value PluginManager::BindNode(napi_env env, napi_callback_info info) { 780 size_t argc = 2; 781 napi_value args[2] = {nullptr}; 782 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 783 std::string nodeId = value2String(env, args[0]); 784 ArkUI_NodeHandle handle; 785 OH_ArkUI_GetNodeHandleFromNapiValue(env, args[1], &handle); // Obtain the nodeHandle object. 786 OH_ArkUI_SurfaceHolder *holder = OH_ArkUI_SurfaceHolder_Create (handle); // Obtain a SurfaceHolder object. 787 nodeHandleMap_[nodeId] = handle; 788 surfaceHolderMap_[handle] = holder; 789 auto callback = OH_ArkUI_SurfaceCallback_Create(); // Create a SurfaceCallback object. 790 callbackMap_[holder] = callback; 791 OH_ArkUI_SurfaceCallback_SetSurfaceCreatedEvent(callback, OnSurfaceCreated); // Register the OnSurfaceCreated callback. 792 OH_ArkUI_SurfaceCallback_SetSurfaceChangedEvent(callback, OnSurfaceChanged); // Register the OnSurfaceChanged callback. 793 OH_ArkUI_SurfaceCallback_SetSurfaceDestroyedEvent(callback, OnSurfaceDestroyed); // Register the OnSurfaceDestroyed callback. 794 OH_ArkUI_SurfaceCallback_SetSurfaceShowEvent(callback, OnSurfaceShow); // Register the OnSurfaceShow callback. 795 OH_ArkUI_SurfaceCallback_SetSurfaceHideEvent(callback, OnSurfaceHide); // Register the OnSurfaceHide callback. 796 OH_ArkUI_XComponent_RegisterOnFrameCallback(handle, OnFrameCallback); // Register the OnFrameCallback callback. 797 OH_ArkUI_SurfaceHolder_AddSurfaceCallback(holder, callback); // Register the SurfaceCallback callback. 798 if (!nodeAPI->addNodeEventReceiver(handle, onEvent)) { // Add an event listener. The return code 0 indicates success. 799 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "addNodeEventReceiver error"); 800 } 801 if (!nodeAPI->registerNodeEvent(handle, NODE_TOUCH_EVENT, 0, nullptr)) { // Register the touch event using the C API. The return code 0 indicates success. 802 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "registerTouchEvent error"); 803 } 804 provider_ = OH_ArkUI_AccessibilityProvider_Create(handle); // Create an object of the ArkUI_AccessibilityProvider type. 805 /** 806 * After obtaining the ArkUI_AccessibilityProvider object, you can register accessibility callbacks. For details, see: 807 * https://gitee.com/openharmony/docs/blob/OpenHarmony-5.1.0-Release/en/application-dev/ui/ndk-accessibility-xcomponent.md 808 * **/ 809 return nullptr; 810 } 811 812 napi_value PluginManager::UnbindNode(napi_env env, napi_callback_info info) 813 { 814 size_t argc = 1; 815 napi_value args[1] = {nullptr}; 816 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 817 std::string nodeId = value2String(env, args[0]); 818 auto node = nodeHandleMap_[nodeId]; 819 OH_ArkUI_XComponent_UnregisterOnFrameCallback(node); // Unregister the frame callback. 820 OH_ArkUI_AccessibilityProvider_Dispose(provider_); // Dispose of the ArkUI_AccessibilityProvider object. 821 nodeAPI->disposeNode (node); // Destroy the nodeHandle object. 822 nodeHandleMap_.erase(nodeId); 823 return nullptr; 824 } 825 826 napi_value PluginManager::SetFrameRate(napi_env env, napi_callback_info info) 827 { 828 size_t argc = 4; 829 napi_value args[4] = {nullptr}; 830 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 831 std::string nodeId = value2String(env, args[0]); 832 auto node = nodeHandleMap_[nodeId]; 833 834 int32_t min = 0; 835 napi_get_value_int32( env, args[1], &min); 836 837 int32_t max = 0; 838 napi_get_value_int32(env, args[2], &max); 839 840 int32_t expected = 0; 841 napi_get_value_int32(env, args[3], &expected); 842 OH_NativeXComponent_ExpectedRateRange range = { 843 .min = min, 844 .max = max, 845 .expected = expected 846 }; 847 OH_ArkUI_XComponent_SetExpectedFrameRateRange(node, range); // Set the expected frame rate range. 848 return nullptr; 849 } 850 851 napi_value PluginManager::SetNeedSoftKeyboard(napi_env env, napi_callback_info info) 852 { 853 size_t argc = 2; 854 napi_value args[2] = {nullptr}; 855 napi_get_cb_info(env, info, &argc, args, nullptr, nullptr); 856 std::string nodeId = value2String(env, args[0]); 857 auto node = nodeHandleMap_[nodeId]; 858 859 bool needSoftKeyboard = false; 860 napi_get_value_bool( env, args[1], &needSoftKeyboard); 861 OH_ArkUI_XComponent_SetNeedSoftKeyboard(node, needSoftKeyboard); // Set whether the soft keyboard is required. 862 return nullptr; 863 } 864 ``` 865 866 (2) Define callbacks for surface creation, changes, destruction, and events, as well as variable frame rate callbacks. 867 868 ```c++ 869 void OnSurfaceCreated(OH_ArkUI_SurfaceHolder *holder) { 870 auto window = OH_ArkUI_XComponent_GetNativeWindow(holder); // Obtain the **NativeWindow** instance. 871 auto render = new EGLRender(); 872 PluginManager::renderMap_[holder] = render; 873 render->SetUpEGLContext(window); 874 } 875 876 void OnSurfaceChanged(OH_ArkUI_SurfaceHolder *holder, uint64_t width, uint64_t height) { 877 if (PluginManager::renderMap_.count(holder)) { 878 auto render = PluginManager::renderMap_[holder]; 879 render->SetEGLWindowSize(width, height); // Set the size of the drawing area. 880 render->DrawStar(true); // Draw a five-pointed star. 881 } 882 } 883 884 void OnSurfaceDestroyed(OH_ArkUI_SurfaceHolder *holder) { 885 OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "onBind", "on destroyed"); 886 if (PluginManager::renderMap_.count(holder)) { // Destroy the render object. 887 auto render = PluginManager::renderMap_[holder]; 888 delete render; 889 PluginManager::renderMap_.erase(holder); 890 } 891 if (PluginManager::callbackMap_.count(holder)) { 892 auto callback = PluginManager::callbackMap_[holder]; 893 OH_ArkUI_SurfaceHolder_RemoveSurfaceCallback(holder, callback); // Remove the SurfaceCallback object. 894 OH_ArkUI_SurfaceCallback_Dispose(callback); // Dispose of the surfaceCallback object. 895 PluginManager::callbackMap_.erase(holder); 896 } 897 OH_ArkUI_SurfaceHolder_Dispose(holder); // Dispose of the surfaceHolder object. 898 } 899 900 void OnSurfaceShow(OH_ArkUI_SurfaceHolder* holder) 901 { 902 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface show"); 903 } 904 905 void OnSurfaceHide(OH_ArkUI_SurfaceHolder* holder) 906 { 907 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on surface hide"); 908 } 909 910 void OnFrameCallback(ArkUI_NodeHandle node, uint64_t timestamp, uint64_t targetTimestamp) 911 { 912 if (!PluginManager::surfaceHolderMap_.count(node)) { 913 return; 914 } 915 static uint64_t count = 0; 916 count++; 917 if (count % 50 == 0) { 918 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "OnFrameCallback count = %{public}ld", count); 919 } 920 } 921 922 void onEvent(ArkUI_NodeEvent *event) { 923 auto eventType = OH_ArkUI_NodeEvent_GetEventType(event); // Obtain the component event type. 924 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on event"); 925 if (eventType == NODE_TOUCH_EVENT) { 926 ArkUI_NodeHandle handle = OH_ArkUI_NodeEvent_GetNodeHandle(event); // Obtain the component that triggered the event. 927 auto holder = PluginManager::surfaceHolderMap_[handle]; 928 if (PluginManager::renderMap_.count(holder)) { 929 auto render = PluginManager::renderMap_[holder]; 930 render->DrawStar(false); // Draw a five-pointed star. 931 } 932 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "onBind", "on touch"); 933 } 934 } 935 ``` 936 9374. Initialize the environment, including initializing the available EGLDisplay, determining the available surface configuration, creating the rendering surface, and creating and associating the context. 938 939 ```c++ 940 // EGLConst.h 941 #include <EGL/egl.h> 942 #include <EGL/eglext.h> 943 #include <GLES3/gl3.h> 944 945 const unsigned int LOG_PRINT_DOMAIN = 0xFF00; 946 947 /** 948 * Program error. 949 */ 950 const GLuint PROGRAM_ERROR = 0; 951 952 /** 953 * Position error. 954 */ 955 const GLint POSITION_ERROR = -1; 956 957 /** 958 * Default x coordinate. 959 */ 960 const int DEFAULT_X_POSITION = 0; 961 962 /** 963 * Default y coordinate. 964 */ 965 const int DEFAULT_Y_POSITION = 0; 966 967 /** 968 * Default GL red value. 969 */ 970 const GLfloat GL_RED_DEFAULT = 0.0; 971 972 /** 973 * Default GL green value. 974 */ 975 const GLfloat GL_GREEN_DEFAULT = 0.0; 976 977 /** 978 * Default GL blue value. 979 */ 980 const GLfloat GL_BLUE_DEFAULT = 0.0; 981 982 /** 983 * GL alpha value. 984 */ 985 const GLfloat GL_ALPHA_DEFAULT = 1.0; 986 987 /** 988 * Pointer count. 989 */ 990 const GLint POINTER_SIZE = 2; 991 992 /** 993 * Triangle fan size. 994 */ 995 const GLsizei TRIANGLE_FAN_SIZE = 4; 996 997 /** 998 * 50%. 999 */ 1000 const float FIFTY_PERCENT = 0.5; 1001 1002 /** 1003 * Position handle name. 1004 */ 1005 const char POSITION_NAME[] = "a_position"; 1006 1007 /** 1008 * Background color #f4f4f4. 1009 */ 1010 const GLfloat BACKGROUND_COLOR[] = {244.0f / 255, 244.0f / 255, 244.0f / 255, 1.0f}; 1011 1012 /** 1013 * Draw color #7E8FFB. 1014 */ 1015 const GLfloat DRAW_COLOR[] = {126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f}; 1016 1017 /** 1018 * Change color #92D6CC. 1019 */ 1020 const GLfloat CHANGE_COLOR[] = {146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f}; 1021 1022 /** 1023 * Background area. 1024 */ 1025 const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = {-1.0f, 1.0f, 1.0f, 1.0f, 1.0f, -1.0f, -1.0f, -1.0f}; 1026 1027 const EGLint ATTRIB_LIST[] = { 1028 // Key, value. 1029 EGL_SURFACE_TYPE, EGL_WINDOW_BIT, EGL_RED_SIZE, 8, EGL_GREEN_SIZE, 8, EGL_BLUE_SIZE, 8, EGL_ALPHA_SIZE, 8, 1030 EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT, 1031 // End. 1032 EGL_NONE}; 1033 1034 const EGLint CONTEXT_ATTRIBS[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; 1035 1036 /** 1037 * Vertex shader. 1038 */ 1039 const char VERTEX_SHADER[] = "#version 300 es\n" 1040 "layout(location = 0) in vec4 a_position;\n" 1041 "layout(location = 1) in vec4 a_color; \n" 1042 "out vec4 v_color; \n" 1043 "void main() \n" 1044 "{ \n" 1045 " gl_Position = a_position; \n" 1046 " v_color = a_color; \n" 1047 "} \n"; 1048 1049 /** 1050 * Fragment shader. 1051 */ 1052 const char FRAGMENT_SHADER[] = "#version 300 es\n" 1053 "precision mediump float; \n" 1054 "in vec4 v_color; \n" 1055 "out vec4 fragColor; \n" 1056 "void main() \n" 1057 "{ \n" 1058 " fragColor = v_color; \n" 1059 "} \n"; 1060 ``` 1061 1062 ```c++ 1063 // EGLRender.h 1064 #include "EGLConst.h" 1065 #include <EGL/egl.h> 1066 #include <EGL/eglext.h> 1067 #include <EGL/eglplatform.h> 1068 #include <GLES3/gl3.h> 1069 #include <string> 1070 1071 class EGLRender { 1072 public: 1073 bool SetUpEGLContext(void *window); 1074 void SetEGLWindowSize(int width, int height); 1075 void DrawStar(bool drawColor); 1076 1077 std::string xcomponentId; 1078 EGLNativeWindowType eglWindow_; 1079 1080 EGLDisplay eglDisplay_ = EGL_NO_DISPLAY; 1081 EGLConfig eglConfig_ = EGL_NO_CONFIG_KHR; 1082 EGLSurface eglSurface_ = EGL_NO_SURFACE; 1083 EGLContext eglContext_ = EGL_NO_CONTEXT; 1084 GLuint program_; 1085 int width_ = 0; 1086 int height_ = 0; 1087 ~EGLRender(); 1088 1089 private: 1090 GLint PrepareDraw(); 1091 bool ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[]); 1092 }; 1093 ``` 1094 1095 ```c++ 1096 // EGLRender.cpp 1097 #include "EGLRender.h" 1098 #include "EGLConst.h" 1099 #include <EGL/egl.h> 1100 #include <EGL/eglext.h> 1101 #include <GLES3/gl3.h> 1102 #include <cmath> 1103 #include <cstdio> 1104 #include <algorithm> 1105 #include <hilog/log.h> 1106 #include <iostream> 1107 1108 namespace { 1109 void Rotate2d(GLfloat centerX, GLfloat centerY, GLfloat *rotateX, GLfloat *rotateY, GLfloat theta) { 1110 GLfloat tempX = cos(theta) * (*rotateX - centerX) - sin(theta) * (*rotateY - centerY); 1111 GLfloat tempY = sin(theta) * (*rotateX - centerX) + cos(theta) * (*rotateY - centerY); 1112 *rotateX = tempX + centerX; 1113 *rotateY = tempY + centerY; 1114 } 1115 1116 GLuint LoadShader(GLenum type, const char *shaderSrc) { 1117 if ((type <= 0) || (shaderSrc == nullptr)) { 1118 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader type or shaderSrc error"); 1119 return PROGRAM_ERROR; 1120 } 1121 1122 GLuint shader = glCreateShader(type); 1123 if (shader == 0) { 1124 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCreateShader unable to load shader"); 1125 return PROGRAM_ERROR; 1126 } 1127 1128 // The gl function has no return value. 1129 glShaderSource(shader, 1, &shaderSrc, nullptr); 1130 glCompileShader(shader); 1131 1132 GLint compiled; 1133 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 1134 if (compiled != 0) { 1135 return shader; 1136 } 1137 1138 GLint infoLen = 0; 1139 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); 1140 if (infoLen <= 1) { 1141 glDeleteShader(shader); 1142 return PROGRAM_ERROR; 1143 } 1144 1145 char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1)); 1146 if (infoLog != nullptr) { 1147 memset(infoLog, 0, infoLen + 1); 1148 glGetShaderInfoLog(shader, infoLen, nullptr, infoLog); 1149 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glCompileShader error = %s", infoLog); 1150 free(infoLog); 1151 infoLog = nullptr; 1152 } 1153 glDeleteShader(shader); 1154 return PROGRAM_ERROR; 1155 } 1156 1157 // Create a program. 1158 GLuint CreateProgram(const char *vertexShader, const char *fragShader) { 1159 if ((vertexShader == nullptr) || (fragShader == nullptr)) { 1160 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", 1161 "createProgram: vertexShader or fragShader is null"); 1162 return PROGRAM_ERROR; 1163 } 1164 1165 GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader); 1166 if (vertex == PROGRAM_ERROR) { 1167 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram vertex error"); 1168 return PROGRAM_ERROR; 1169 } 1170 1171 GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader); 1172 if (fragment == PROGRAM_ERROR) { 1173 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram fragment error"); 1174 return PROGRAM_ERROR; 1175 } 1176 1177 GLuint program = glCreateProgram(); 1178 if (program == PROGRAM_ERROR) { 1179 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram program error"); 1180 glDeleteShader(vertex); 1181 glDeleteShader(fragment); 1182 return PROGRAM_ERROR; 1183 } 1184 1185 // The gl function has no return value. 1186 glAttachShader(program, vertex); 1187 glAttachShader(program, fragment); 1188 glLinkProgram(program); 1189 1190 GLint linked; 1191 glGetProgramiv(program, GL_LINK_STATUS, &linked); 1192 if (linked != 0) { 1193 glDeleteShader(vertex); 1194 glDeleteShader(fragment); 1195 return program; 1196 } 1197 1198 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "createProgram linked error"); 1199 GLint infoLen = 0; 1200 glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); 1201 if (infoLen > 1) { 1202 char *infoLog = (char *)malloc(sizeof(char) * (infoLen + 1)); 1203 memset(infoLog, 0, infoLen + 1); 1204 glGetProgramInfoLog(program, infoLen, nullptr, infoLog); 1205 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "glLinkProgram error = %s", infoLog); 1206 free(infoLog); 1207 infoLog = nullptr; 1208 } 1209 glDeleteShader(vertex); 1210 glDeleteShader(fragment); 1211 glDeleteProgram(program); 1212 return PROGRAM_ERROR; 1213 } 1214 } // namespace 1215 1216 bool EGLRender::SetUpEGLContext(void *window) { 1217 OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "EglContextInit execute"); 1218 eglWindow_ = (EGLNativeWindowType)(window); 1219 // Initialize the display. 1220 eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); 1221 if (eglDisplay_ == EGL_NO_DISPLAY) { 1222 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglGetDisplay: unable to get EGL display"); 1223 return false; 1224 } 1225 EGLint majorVersion; 1226 EGLint minorVersion; 1227 if (!eglInitialize(eglDisplay_, &majorVersion, &minorVersion)) { 1228 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", 1229 "eglInitialize: unable to get initialize EGL display"); 1230 return false; 1231 }; 1232 // Select the configuration. 1233 const EGLint maxConfigSize = 1; 1234 EGLint numConfigs; 1235 if (!eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs)) { 1236 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglChooseConfig: unable to choose configs"); 1237 return false; 1238 }; 1239 // Create an environment. 1240 // Create a surface. 1241 eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL); 1242 if (eglSurface_ == nullptr) { 1243 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", 1244 "eglCreateWindowSurface: unable to create Surface"); 1245 return false; 1246 } 1247 if (eglSurface_ == nullptr) { 1248 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", 1249 "eglCreateWindowSurface: unable to create Surface"); 1250 return false; 1251 } 1252 // Create a context. 1253 eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS); 1254 if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) { 1255 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "eglMakeCurrent failed"); 1256 return false; 1257 } 1258 // Create a program. 1259 program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER); 1260 if (program_ == PROGRAM_ERROR) { 1261 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "CreateProgram: unable to create program"); 1262 return false; 1263 } 1264 return true; 1265 } 1266 1267 GLint EGLRender::PrepareDraw() { 1268 if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (eglContext_ == nullptr) || 1269 (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_))) { 1270 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "PrepareDraw: param error"); 1271 return POSITION_ERROR; 1272 } 1273 1274 // These gl functions have no return value. 1275 glViewport(DEFAULT_X_POSITION, DEFAULT_Y_POSITION, width_, height_); 1276 glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT); 1277 glClear(GL_COLOR_BUFFER_BIT); 1278 glUseProgram(program_); 1279 1280 return glGetAttribLocation(program_, POSITION_NAME); 1281 } 1282 1283 // Draw a five-pointed star. 1284 void EGLRender::DrawStar(bool drawColor) { 1285 OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLRender", "Draw"); 1286 GLint position = PrepareDraw(); 1287 if (position == POSITION_ERROR) { 1288 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw get position failed"); 1289 return; 1290 } 1291 1292 // Draw the background. 1293 if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES)) { 1294 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw background failed"); 1295 return; 1296 } 1297 1298 // Divide it into five quadrilaterals and calculate the vertices for one of the quadrilaterals. 1299 GLfloat rotateX = 0; 1300 GLfloat rotateY = FIFTY_PERCENT * height_; 1301 GLfloat centerX = 0; 1302 // Convert angles 54° and 18° to radians. 1303 GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18); 1304 // Convert angle 18° to radians. 1305 GLfloat leftX = -rotateY * (M_PI / 180 * 18); 1306 GLfloat leftY = 0; 1307 // Convert angle 18° to radians. 1308 GLfloat rightX = rotateY * (M_PI / 180 * 18); 1309 GLfloat rightY = 0; 1310 1311 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 1312 const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_, 1313 rotateX / width_, rotateY / height_, rightX / width_, rightY / height_}; 1314 auto color = drawColor ? DRAW_COLOR : CHANGE_COLOR; 1315 if (!ExecuteDraw(position, color, shapeVertices)) { 1316 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed"); 1317 return; 1318 } 1319 1320 // Convert angle 72° to radians. 1321 GLfloat rad = M_PI / 180 * 72; 1322 // Rotate four times. 1323 for (int i = 0; i < 4; ++i) { 1324 // Obtain the vertices for the other four quadrilaterals through rotation. 1325 Rotate2d(centerX, centerY, &rotateX, &rotateY, rad); 1326 Rotate2d(centerX, centerY, &leftX, &leftY, rad); 1327 Rotate2d(centerX, centerY, &rightX, &rightY, rad); 1328 1329 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 1330 const GLfloat shapeVertices[] = {centerX / width_, centerY / height_, leftX / width_, leftY / height_, 1331 rotateX / width_, rotateY / height_, rightX / width_, rightY / height_}; 1332 1333 // Draw the shape. 1334 if (!ExecuteDraw(position, color, shapeVertices)) { 1335 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw execute draw shape failed"); 1336 return; 1337 } 1338 } 1339 // Submit the drawing command to the GPU. After the GPU completes the execution, the rendering result is displayed on the screen. 1340 glFlush(); 1341 glFinish(); 1342 if (!eglSwapBuffers(eglDisplay_, eglSurface_)) { 1343 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "Draw FinishDraw failed"); 1344 return; 1345 } 1346 } 1347 1348 bool EGLRender::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[]) { 1349 if ((position > 0) || (color == nullptr)) { 1350 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLRender", "ExecuteDraw: param error"); 1351 return false; 1352 } 1353 1354 // These gl functions have no return value. 1355 glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, shapeVertices); 1356 glEnableVertexAttribArray(position); 1357 glVertexAttrib4fv(1, color); 1358 glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE); 1359 glDisableVertexAttribArray(position); 1360 1361 return true; 1362 } 1363 1364 void EGLRender::SetEGLWindowSize(int width, int height) { 1365 width_ = width; 1366 height_ = height; 1367 } 1368 1369 // Release resources. 1370 EGLRender::~EGLRender() { 1371 if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) { 1372 OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed"); 1373 } 1374 1375 if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) { 1376 OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed"); 1377 } 1378 1379 if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) { 1380 OH_LOG_Print(LOG_APP, LOG_ERROR, 0xff00, "EGLRender", "Release eglDestroySurface failed"); 1381 } 1382 } 1383 ``` 1384 13855. Configure the specific CMakeLists to use the CMake toolchain to compile the C++ source code into a dynamic link library file. 1386 1387 ```CMake 1388 # the minimum version of CMake. 1389 cmake_minimum_required(VERSION 3.5.0) 1390 project(LCNXComponent2) 1391 1392 set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 1393 1394 if(DEFINED PACKAGE_FIND_FILE) 1395 include(${PACKAGE_FIND_FILE}) 1396 endif() 1397 1398 include_directories(${NATIVERENDER_ROOT_PATH} 1399 ${NATIVERENDER_ROOT_PATH}/render 1400 ${NATIVERENDER_ROOT_PATH}/manager) 1401 1402 add_library(nativerender SHARED 1403 render/EGLRender.cpp 1404 manager/plugin_manager.cpp 1405 napi_init.cpp) 1406 find_library( 1407 # Set the name of the path variable. 1408 EGL-lib 1409 # Set the name of the NDK library to search for. 1410 EGL 1411 ) 1412 1413 find_library( 1414 # Set the name of the path variable. 1415 GLES-lib 1416 # Set the name of the NDK library to search for. 1417 GLESv3 1418 ) 1419 1420 find_library( 1421 # Set the name of the path variable. 1422 hilog-lib 1423 # Set the name of the NDK library to search for. 1424 hilog_ndk.z 1425 ) 1426 1427 find_library( 1428 # Set the name of the path variable. 1429 libace-lib 1430 # Set the name of the NDK library to search for. 1431 ace_ndk.z 1432 ) 1433 1434 find_library( 1435 # Set the name of the path variable. 1436 libnapi-lib 1437 # Set the name of the NDK library to search for. 1438 ace_napi.z 1439 ) 1440 1441 find_library( 1442 # Set the name of the path variable. 1443 libuv-lib 1444 # Set the name of the NDK library to search for. 1445 uv 1446 ) 1447 1448 target_link_libraries(nativerender PUBLIC ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib} libnative_window.so) 1449 ``` 1450 1451 1452 1453## Managing the Surface Lifecycle with NativeXComponent 1454 1455Unlike the previous scenarios, this scenario involves using ArkUI NDK APIs on the native side to create XComponent components for custom rendering. The key steps include: creating components, obtaining **NativeXComponent** instances, registering lifecycle callbacks for the **XComponent** along with touch, mouse, and key event callbacks, obtaining **NativeWindow** instances through these callbacks, performing graphic rendering on **XComponent** components using OpenGL ES/EGL APIs, and finally mounting the display using **ContentSlot** placeholder components in the ArkTS layer. The development mainly involves the following use cases: 1456 1457- Register the lifecycle and event callbacks of the **XComponent**. 1458- Initialize the environment, obtain the current state, and respond to various events via these callbacks. 1459- Use the **NativeWindow** instance with EGL APIs to develop custom drawing content, and allocate and submit buffers to the graphics queue. 1460 1461**Constraints** 1462 1463When constructing **XComponent** components, be sure to select the appropriate node type that meets your service requirements. 1464 1465> **NOTE** 1466> 1467> 1. **OH_NativeXComponent** instances are cached in a dictionary on the native side. Their keys must be globally unique, and they must be promptly removed from the dictionary when the corresponding **XComponent** component is destroyed. 1468> 1469> 2. During development with multiple **XComponent** components, make sure the keys used to cache resources on the Native side are unique. The two recommended key formats are as follows: **Id** + random number; **surfaceId**. 1470 1471**lifecycle Callbacks** 1472 1473- OnSurfaceCreated 1474 1475 Triggered when the surface of the **XComponent** component is ready. 1476 1477 ArkTS-side sequence 1478 1479  1480 1481- OnSurfaceChanged 1482 1483 Triggered after surface size changes trigger re-layout. 1484 1485 ArkTS-side sequence 1486 1487  1488 1489- OnSurfaceDestroyed 1490 1491 Triggered when the **XComponent** component is destroyed, which is consistent with the destruction timing of common ArkUI components. 1492 1493 ArkTS-side sequence 1494 1495  1496 1497**Available APIs** 1498 1499| API | Description | 1500| ------------------------------------------------------------ | ------------------------------------------------------------ | 1501| OH_NativeXComponent_GetXComponentId(OH_NativeXComponent* component, char* id, uint64_t* size) | Obtains the ID of an **XComponent**. | 1502| OH_NativeXComponent_GetXComponentSize(OH_NativeXComponent* component, const void* window, uint64_t* width, uint64_t* height) | Obtains the size of the surface held by an **XComponent**. | 1503| OH_NativeXComponent_GetXComponentOffset(OH_NativeXComponent* component, const void* window, double* x, double* y) | Obtains the offset of the surface held by the **XComponent** relative to the upper left corner of its parent component. | 1504| OH_NativeXComponent_GetTouchEvent(OH_NativeXComponent* component, const void* window, OH_NativeXComponent_TouchEvent* touchEvent) | Obtains the touch event triggered by an **XComponent**. For details about the attribute values in **touchEvent**, see [OH_NativeXComponent_TouchEvent](../reference/apis-arkui/capi-oh-nativexcomponent-native-xcomponent-oh-nativexcomponent-touchevent.md).| 1505| OH_NativeXComponent_GetTouchPointToolType(OH_NativeXComponent* component, uint32_t pointIndex, OH_NativeXComponent_TouchPointToolType* toolType) | Obtains the tool type of an **XComponent** touch point. | 1506| OH_NativeXComponent_GetTouchPointTiltX(OH_NativeXComponent* component, uint32_t pointIndex, float* tiltX) | Obtains the tilt of an **XComponent** touch point relative to the x-axis. | 1507| OH_NativeXComponent_GetTouchPointTiltY(OH_NativeXComponent* component, uint32_t pointIndex, float* tiltY) | Obtains the tilt of an **XComponent** touch point relative to the y-axis. | 1508| OH_NativeXComponent_GetMouseEvent(OH_NativeXComponent* component, const void* window, OH_NativeXComponent_MouseEvent* mouseEvent) | Obtains the mouse event triggered by an **XComponent**. | 1509| OH_NativeXComponent_RegisterCallback(OH_NativeXComponent* component, OH_NativeXComponent_Callback* callback) | Registers a lifecycle or touch event callback for an **OH_NativeXComponent** instance. | 1510| OH_NativeXComponent_RegisterMouseEventCallback(OH_NativeXComponent* component, OH_NativeXComponent_MouseEvent_Callback* callback) | Registers the mouse event callback for an **OH_NativeXComponent** instance. | 1511| OH_NativeXComponent_RegisterFocusEventCallback(OH_NativeXComponent* component, void (\*callback)(OH_NativeXComponent* component, void* window)) | Registers the focus obtaining event callback for an **OH_NativeXComponent** instance. | 1512| OH_NativeXComponent_RegisterKeyEventCallback(OH_NativeXComponent* component, void (\*callback)(OH_NativeXComponent* component, void* window)) | Registers the key event callback for an **OH_NativeXComponent** instance. | 1513| OH_NativeXComponent_RegisterBlurEventCallback(OH_NativeXComponent* component, void (\*callback)(OH_NativeXComponent* component, void* window)) | Registers the focus loss event callback for an **OH_NativeXComponent** instance. | 1514| OH_NativeXComponent_GetKeyEvent(OH_NativeXComponent* component, OH_NativeXComponent_KeyEvent\** keyEvent) | Obtains the key event triggered by an **XComponent**. | 1515| OH_NativeXComponent_GetKeyEventAction(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_KeyAction* action) | Obtains the action of a key event. | 1516| OH_NativeXComponent_GetKeyEventCode(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_KeyCode* code) | Obtains the key code value of a key event. | 1517| OH_NativeXComponent_GetKeyEventSourceType(OH_NativeXComponent_KeyEvent* keyEvent, OH_NativeXComponent_EventSourceType* sourceType) | Obtains the input source type of a key event. | 1518| OH_NativeXComponent_GetKeyEventDeviceId(OH_NativeXComponent_KeyEvent* keyEvent, int64_t* deviceId) | Obtains the device ID of a key event. | 1519| OH_NativeXComponent_GetKeyEventTimestamp(OH_NativeXComponent_KeyEvent* keyEvent, int64_t* timestamp) | Obtains the timestamp of a key event. | 1520| OH_ArkUI_QueryModuleInterfaceByName(ArkUI_NativeAPIVariantKind type, const char* structName) | Obtains the native API set of a specified type. | 1521| OH_ArkUI_GetNodeContentFromNapiValue(napi_env env, napi_value value, ArkUI_NodeContentHandle* content) | Obtains a **NodeContent** object on the ArkTS side and maps it to an **ArkUI_NodeContentHandle** object on the native side.| 1522| OH_ArkUI_NodeContent_SetUserData(ArkUI_NodeContentHandle content, void* userData) | Saves custom data to the specified **NodeContent** object. | 1523| OH_ArkUI_NodeContentEvent_GetNodeContentHandle(ArkUI_NodeContentEvent* event) | Obtains the object that triggers the specified **NodeContent** event. | 1524| OH_ArkUI_NodeContent_GetUserData(ArkUI_NodeContentHandle content) | Obtains the custom data saved on the specified **NodeContent** object. | 1525| OH_ArkUI_NodeContentEvent_GetEventType(ArkUI_NodeContentEvent* event) | Obtains the type of the specified **NodeContent** event. | 1526| OH_ArkUI_NodeContent_AddNode(ArkUI_NodeContentHandle content, ArkUI_NodeHandle node) | Adds an ArkUI component node to the specified **NodeContent** object. | 1527| OH_ArkUI_NodeContent_RegisterCallback(ArkUI_NodeContentHandle content, ArkUI_NodeContentCallback callback) | Registers an event callback for the **NodeContent**. | 1528| OH_NativeXComponent_GetNativeXComponent(ArkUI_NodeHandle node) | Obtains a pointer of the **OH_NativeXComponent** type based on the specified component instance created by the native API.| 1529| OH_NativeXComponent_GetHistoricalPoints(OH_NativeXComponent* component, const void* window, int32_t* size, OH_NativeXComponent_HistoricalPoint** historicalPoints ) | Obtains the historical touch point data for the touch event of an **OH_NativeXComponent** instance. Some input devices report touch points at very high frequencies (up to 1 ms intervals). However, since UI updates typically do not require such high-frequency updates, the system consolidates touch events and reports them once per frame. All touch points collected during the current frame are preserved as historical touch points for applications that need direct access to this raw data. For details about the specifications of historical touch points, see [Resampling and Historical Points](arkts-interaction-development-guide-touch-screen.md#resampling-and-historical-points).| 1530 1531> **NOTE** 1532> 1533> The preceding APIs do not support cross-thread access. 1534> 1535> When the XComponent is destroyed (after the **onSurfaceDestroyed** callback is triggered), the **OH_NativeXComponent** and window objects obtained through the preceding APIs will be released. If these object are used again, crashes may occur due to the use of dangling or null pointers. 1536 1537**How to Develop** 1538 1539The following uses the SURFACE type as an example to describe how to use the **XComponent** to call the Node-API to create an EGL/GLES environment, implement drawing graphics on the main page, and change the graphics color. 1540 15411. Define the **XComponent** on the UI. 1542 1543 ```typescript 1544 import nativeNode from 'libnativenode.so'; 1545 import {NodeContent} from '@kit.ArkUI'; 1546 1547 @Entry 1548 @Component 1549 struct Index { 1550 @State currentStatus: string = "init"; 1551 private nodeContent: NodeContent = new NodeContent(); 1552 aboutToAppear():void{ 1553 // Create a node through the C API and add it to the nodeContent manager. 1554 nativeNode.createNativeNode(this.nodeContent); 1555 } 1556 1557 build() { 1558 Column() { 1559 Row() { 1560 Text('Native XComponent Sample') 1561 .fontSize('24fp') 1562 .fontWeight(500) 1563 .margin({ 1564 left: 24, 1565 top: 12 1566 }) 1567 } 1568 .margin({ top: 24 }) 1569 .width('100%') 1570 .height(56) 1571 1572 Column({ space: 10 }) { 1573 // Display the native components stored in the nodeContent manager. 1574 ContentSlot(this.nodeContent); 1575 1576 Text(this.currentStatus) 1577 .fontSize('24fp') 1578 .fontWeight(500) 1579 } 1580 .onClick(() => { 1581 let hasChangeColor: boolean = false; 1582 // Obtain the current drawing state. 1583 if (nativeNode.getStatus()) { 1584 hasChangeColor = nativeNode.getStatus().hasChangeColor; 1585 } 1586 if (hasChangeColor) { 1587 this.currentStatus = "change color"; 1588 } 1589 }) 1590 .margin({ 1591 top: 27, 1592 left: 12, 1593 right: 12 1594 }) 1595 .height('40%') 1596 .width('90%') 1597 1598 Row() { 1599 Button('Draw Star') 1600 .fontSize('16fp') 1601 .fontWeight(500) 1602 .margin({ bottom: 24 }) 1603 .onClick(() => { 1604 // Call drawPattern to draw content. 1605 nativeNode.drawPattern(); 1606 let hasDraw: boolean = false; 1607 // Obtain the current drawing state. 1608 if (nativeNode.getStatus()) { 1609 hasDraw = nativeNode.getStatus().hasDraw; 1610 } 1611 if (hasDraw) { 1612 this.currentStatus = "draw star"; 1613 } 1614 }) 1615 .width('53.6%') 1616 .height(40) 1617 } 1618 .width('100%') 1619 .justifyContent(FlexAlign.Center) 1620 .alignItems(VerticalAlign.Bottom) 1621 .layoutWeight(1) 1622 } 1623 .width('100%') 1624 .height('100%') 1625 } 1626 } 1627 ``` 1628 16292. Register the Node-API module. For details, see [Node-API Development Specifications](../napi/napi-guidelines.md). 1630 1631 ```c++ 1632 #include <hilog/log.h> 1633 #include "common/common.h" 1634 #include "manager/plugin_manager.h" 1635 1636 // In the napi_init.cpp file, use the Init method to register the target function to pass in the encapsulated C++ methods for the ArkTS method to call. 1637 EXTERN_C_START 1638 static napi_value Init(napi_env env, napi_value exports) { 1639 // ... 1640 // Expose APIs to the ArkTS side. 1641 napi_property_descriptor desc[] = { 1642 {"createNativeNode", nullptr, PluginManager::createNativeNode, nullptr, nullptr, nullptr, 1643 napi_default, nullptr }, 1644 {"getStatus", nullptr, PluginManager::GetXComponentStatus, nullptr, nullptr, 1645 nullptr, napi_default, nullptr}, 1646 {"drawPattern", nullptr, PluginManager::NapiDrawPattern, nullptr, nullptr, 1647 nullptr, napi_default, nullptr} 1648 }; 1649 if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { 1650 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "Init", "napi_define_properties failed"); 1651 return nullptr; 1652 } 1653 return exports; 1654 } 1655 EXTERN_C_END 1656 1657 // Provide module descriptor configuration. You can modify parameters as needed. 1658 static napi_module nativerenderModule = { 1659 .nm_version = 1, 1660 .nm_flags = 0, 1661 .nm_filename = nullptr, 1662 // Entry point function 1663 .nm_register_func = Init,// Specify the callback for when the corresponding module is loaded. 1664 // Module name 1665 .nm_modname = 1666 "nativerender", // Specify the module name. The name must be the same as the value of libraryname in the XComponent on the ArkTS side. 1667 .nm_priv = ((void *)0), 1668 .reserved = {0}}; 1669 1670 // The method decorated by __attribute__((constructor)) is automatically called by the system. The Node-API napi_module_register() is used to pass in the module descriptor configuration for module registration. 1671 extern "C" __attribute__((constructor)) void RegisterModule(void) { napi_module_register(&nativerenderModule); } 1672 ``` 1673 16743. Register the **XComponent** event callback and use the Node-API to implement it. 1675 1676 (1) Define the callbacks for the touch event of the **XComponent** component and for when a surface is successfully created, changed, or destroyed. 1677 1678 ```c++ 1679 // Define the PluginManager class in the header file. 1680 class PluginManager { 1681 public: 1682 static OH_NativeXComponent_Callback callback_; 1683 PluginManager(); 1684 ~PluginManager(); 1685 static PluginManager* GetInstance() 1686 { 1687 return &PluginManager::pluginManager_; 1688 } 1689 1690 static napi_value createNativeNode(napi_env env, napi_callback_info info); 1691 static napi_value GetXComponentStatus(napi_env env, napi_callback_info info); 1692 static napi_value NapiDrawPattern(napi_env env, napi_callback_info info); 1693 1694 // CAPI XComponent 1695 void OnSurfaceChanged(OH_NativeXComponent* component, void* window); 1696 void OnSurfaceDestroyed(OH_NativeXComponent* component, void* window); 1697 void DispatchTouchEvent(OH_NativeXComponent* component, void* window); 1698 void OnSurfaceCreated(OH_NativeXComponent* component, void* window); 1699 1700 public: 1701 EGLCore *eglcore_; 1702 uint64_t width_; 1703 uint64_t height_; 1704 OH_NativeXComponent_TouchEvent touchEvent_; 1705 static int32_t hasDraw_; 1706 static int32_t hasChangeColor_; 1707 1708 private: 1709 static PluginManager pluginManager_; 1710 std::unordered_map<std::string, OH_NativeXComponent*> nativeXComponentMap_; 1711 std::unordered_map<std::string, PluginManager*> pluginManagerMap_; 1712 }; 1713 ``` 1714 1715 ```c++ 1716 // Define the OnSurfaceCreatedCB() function to encapsulate the initialization environment and drawing background. 1717 void OnSurfaceCreatedCB(OH_NativeXComponent *component, void *window) { 1718 // ... 1719 // Initialize the environment and draw the background. 1720 auto *pluginManger = PluginManager::GetInstance(); 1721 pluginManger->OnSurfaceCreated(component, window); 1722 } 1723 1724 // Define the OnSurfaceChangedCB() function. 1725 void OnSurfaceChangedCB(OH_NativeXComponent *component, void *window) { 1726 // ... 1727 auto *pluginManger = PluginManager::GetInstance(); 1728 // Encapsulate the OnSurfaceChanged method. 1729 pluginManger->OnSurfaceChanged(component, window); 1730 } 1731 1732 // Define the OnSurfaceDestroyedCB() function and encapsulate in it the Release() method in the PluginRender class for releasing resources. 1733 void OnSurfaceDestroyedCB(OH_NativeXComponent *component, void *window) { 1734 // ... 1735 auto *pluginManger = PluginManager::GetInstance(); 1736 pluginManger->OnSurfaceDestroyed(component, window); 1737 } 1738 1739 // Define the DispatchTouchEventCB() function, which is triggered to respond to a touch event. 1740 void DispatchTouchEventCB(OH_NativeXComponent *component, void *window) { 1741 // ... 1742 auto *pluginManger = PluginManager::GetInstance(); 1743 pluginManger->DispatchTouchEvent(component, window); 1744 } 1745 ``` 1746 1747 (2) Define the **createNativeNode** API, which will be called by the **createNativeNode()** API exposed to the ArkTS side. 1748 1749 ```c++ 1750 napi_value PluginManager::createNativeNode(napi_env env, napi_callback_info info) 1751 { 1752 if ((env == nullptr) || (info == nullptr)) { 1753 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "CreateNativeNode env or info is null"); 1754 return nullptr; 1755 } 1756 size_t argCnt = 2; 1757 napi_value args[2] = { nullptr, nullptr }; 1758 if (napi_get_cb_info(env, info, &argCnt, args, nullptr, nullptr) != napi_ok) { 1759 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "CreateNativeNode napi_get_cb_info failed"); 1760 } 1761 if (argCnt != ARG_CNT) { 1762 napi_throw_type_error(env, NULL, "Wrong number of arguments"); 1763 return nullptr; 1764 } 1765 ArkUI_NodeContentHandle nodeContentHandle_ = nullptr; 1766 OH_ArkUI_GetNodeContentFromNapiValue(env, args[0], &nodeContentHandle_); 1767 nodeAPI = reinterpret_cast<ArkUI_NativeNodeAPI_1*>( 1768 OH_ArkUI_QueryModuleInterfaceByName(ARKUI_NATIVE_NODE, "ArkUI_NativeNodeAPI_1") 1769 ); 1770 std::string tag = value2String(env, args[1]); 1771 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "tag=%{public}s", tag.c_str()); 1772 int32_t ret = OH_ArkUI_NodeContent_SetUserData(nodeContentHandle_, new std::string(tag)); 1773 if (ret != ARKUI_ERROR_CODE_NO_ERROR) { 1774 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "setUserData failed error=%{public}d", ret); 1775 } 1776 if (nodeAPI != nullptr && nodeAPI->createNode != nullptr && nodeAPI->addChild != nullptr) { 1777 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", 1778 "CreateNativeNode tag=%{public}s", tag.c_str()); 1779 auto nodeContentEvent = [](ArkUI_NodeContentEvent *event) { 1780 ArkUI_NodeContentHandle handle = OH_ArkUI_NodeContentEvent_GetNodeContentHandle(event); 1781 std::string *userDate = reinterpret_cast<std::string*>(OH_ArkUI_NodeContent_GetUserData(handle)); 1782 if (OH_ArkUI_NodeContentEvent_GetEventType(event) == NODE_CONTENT_EVENT_ON_ATTACH_TO_WINDOW) { 1783 ArkUI_NodeHandle testNode; 1784 if (userDate) { 1785 testNode = CreateNodeHandle(*userDate); 1786 delete userDate; 1787 userDate = nullptr; 1788 } else { 1789 testNode = CreateNodeHandle("noUserData"); 1790 } 1791 OH_ArkUI_NodeContent_AddNode(handle, testNode); 1792 } 1793 }; 1794 OH_ArkUI_NodeContent_RegisterCallback(nodeContentHandle_, nodeContentEvent); 1795 } 1796 return nullptr; 1797 } 1798 1799 ArkUI_NodeHandle CreateNodeHandle(const std::string &tag) 1800 { 1801 ArkUI_NodeHandle column = nodeAPI->createNode(ARKUI_NODE_COLUMN); 1802 ArkUI_NumberValue value[] = {480}; 1803 ArkUI_NumberValue value1[] = {{.u32 = 15}, {.f32 = 15}}; 1804 ArkUI_AttributeItem item = {value, 1, "changeSize"}; 1805 ArkUI_AttributeItem item1 = {value1, 2}; 1806 nodeAPI->setAttribute(column, NODE_WIDTH, &item); 1807 value[0].f32 = COLUMN_MARGIN; 1808 nodeAPI->setAttribute(column, NODE_MARGIN, &item); 1809 // Create a XComponent component. 1810 xc = nodeAPI->createNode(ARKUI_NODE_XCOMPONENT); 1811 // Set XComponent attributes. 1812 value[0].u32 = ARKUI_XCOMPONENT_TYPE_SURFACE; 1813 nodeAPI->setAttribute(xc, NODE_XCOMPONENT_TYPE, &item); 1814 nodeAPI->setAttribute(xc, NODE_XCOMPONENT_ID, &item); 1815 nodeAPI->setAttribute(xc, NODE_XCOMPONENT_SURFACE_SIZE, &item1); 1816 ArkUI_NumberValue focusable[] = {1}; 1817 focusable[0].i32 = 1; 1818 ArkUI_AttributeItem focusableItem = {focusable, 1}; 1819 nodeAPI->setAttribute(xc, NODE_FOCUSABLE, &focusableItem); 1820 ArkUI_NumberValue valueSize[] = {480}; 1821 ArkUI_AttributeItem itemSize = {valueSize, 1}; 1822 valueSize[0].f32 = XC_WIDTH; 1823 nodeAPI->setAttribute(xc, NODE_WIDTH, &itemSize); 1824 valueSize[0].f32 = XC_HEIGHT; 1825 nodeAPI->setAttribute(xc, NODE_HEIGHT, &itemSize); 1826 ArkUI_AttributeItem item2 = {value, 1, "ndkxcomponent"}; 1827 nodeAPI->setAttribute(xc, NODE_ID, &item2); 1828 1829 auto *nativeXComponent = OH_NativeXComponent_GetNativeXComponent(xc); 1830 if (!nativeXComponent) { 1831 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "GetNativeXComponent error"); 1832 return column; 1833 } 1834 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "GetNativeXComponent success"); 1835 // Register XComponent callbacks. 1836 OH_NativeXComponent_RegisterCallback(nativeXComponent, &PluginManager::callback_); 1837 auto typeRet = nodeAPI->getAttribute(xc, NODE_XCOMPONENT_TYPE); 1838 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "xcomponent type: %{public}d", 1839 typeRet->value[0].i32); 1840 auto idRet = nodeAPI->getAttribute(xc, NODE_XCOMPONENT_ID); 1841 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "xcomponent id: %{public}s", 1842 idRet->string); 1843 nodeAPI->addChild(column, xc); 1844 return column; 1845 } 1846 ``` 1847 1848 (3) Define the **NapiDrawPattern** API, which will be called by the **drawPattern()** API exposed to the ArkTS side. 1849 1850 ```c++ 1851 napi_value PluginManager::NapiDrawPattern(napi_env env, napi_callback_info info) { 1852 // ... 1853 // Obtain environment variables. 1854 napi_value thisArg; 1855 if (napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr) != napi_ok) { 1856 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "PluginManager", "NapiDrawPattern: napi_get_cb_info fail"); 1857 return nullptr; 1858 } 1859 1860 auto *pluginManger = PluginManager::GetInstance(); 1861 // Call the drawing API. 1862 pluginManger->eglcore_->Draw(hasDraw_); 1863 OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "PluginManager", "render->eglCore_->Draw() executed"); 1864 1865 return nullptr; 1866 } 1867 ``` 1868 18694. Initialize the environment, including initializing the available EGLDisplay, determining the available surface configuration, creating the rendering surface, and creating and associating the context. 1870 1871 ```c++ 1872 void EGLCore::UpdateSize(int width, int height) { 1873 // width_ and height_ are defined in the header file. 1874 width_ = width; 1875 height_ = height; 1876 if (width_ > 0) { 1877 widthPercent_ = FIFTY_PERCENT * height_ / width_; 1878 } 1879 } 1880 1881 bool EGLCore::EglContextInit(void *window, int width, int height) { 1882 // ... 1883 UpdateSize(width, height); 1884 eglWindow_ = static_cast<EGLNativeWindowType>(window); 1885 1886 // Initialize the display. 1887 eglDisplay_ = eglGetDisplay(EGL_DEFAULT_DISPLAY); 1888 if (eglDisplay_ == EGL_NO_DISPLAY) { 1889 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglGetDisplay: unable to get EGL display"); 1890 return false; 1891 } 1892 1893 // Initialize the EGL. 1894 EGLint majorVersion; 1895 EGLint minorVersion; 1896 if (!eglInitialize(eglDisplay_, &majorVersion, &minorVersion)) { 1897 OH_LOG_Print( 1898 LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglInitialize: unable to get initialize EGL display"); 1899 return false; 1900 } 1901 1902 // Select the configuration. 1903 const EGLint maxConfigSize = 1; 1904 EGLint numConfigs; 1905 if (!eglChooseConfig(eglDisplay_, ATTRIB_LIST, &eglConfig_, maxConfigSize, &numConfigs)) { 1906 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglChooseConfig: unable to choose configs"); 1907 return false; 1908 } 1909 // Create an environment. 1910 return CreateEnvironment(); 1911 } 1912 ``` 1913 1914 ```c++ 1915 bool EGLCore::CreateEnvironment() { 1916 // Create a surface. 1917 if (eglWindow_ == nullptr) { 1918 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglWindow_ is null"); 1919 return false; 1920 } 1921 eglSurface_ = eglCreateWindowSurface(eglDisplay_, eglConfig_, eglWindow_, NULL); 1922 if (eglSurface_ == nullptr) { 1923 OH_LOG_Print( 1924 LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglCreateWindowSurface: unable to create surface"); 1925 return false; 1926 } 1927 1928 // Create a context. 1929 eglContext_ = eglCreateContext(eglDisplay_, eglConfig_, EGL_NO_CONTEXT, CONTEXT_ATTRIBS); 1930 if (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_)) { 1931 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "eglMakeCurrent failed"); 1932 return false; 1933 } 1934 1935 // Create a program. 1936 program_ = CreateProgram(VERTEX_SHADER, FRAGMENT_SHADER); 1937 if (program_ == PROGRAM_ERROR) { 1938 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "CreateProgram: unable to create program"); 1939 return false; 1940 } 1941 return true; 1942 } 1943 1944 GLuint EGLCore::CreateProgram(const char* vertexShader, const char* fragShader) { 1945 if ((vertexShader == nullptr) || (fragShader == nullptr)) { 1946 OH_LOG_Print( 1947 LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram: vertexShader or fragShader is null"); 1948 return PROGRAM_ERROR; 1949 } 1950 1951 GLuint vertex = LoadShader(GL_VERTEX_SHADER, vertexShader); 1952 if (vertex == PROGRAM_ERROR) { 1953 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram vertex error"); 1954 return PROGRAM_ERROR; 1955 } 1956 1957 GLuint fragment = LoadShader(GL_FRAGMENT_SHADER, fragShader); 1958 if (fragment == PROGRAM_ERROR) { 1959 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram fragment error"); 1960 return PROGRAM_ERROR; 1961 } 1962 1963 GLuint program = glCreateProgram(); 1964 if (program == PROGRAM_ERROR) { 1965 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram program error"); 1966 glDeleteShader(vertex); 1967 glDeleteShader(fragment); 1968 return PROGRAM_ERROR; 1969 } 1970 1971 // These gl functions have no return value. 1972 glAttachShader(program, vertex); 1973 glAttachShader(program, fragment); 1974 glLinkProgram(program); 1975 1976 GLint linked; 1977 glGetProgramiv(program, GL_LINK_STATUS, &linked); 1978 if (linked != 0) { 1979 glDeleteShader(vertex); 1980 glDeleteShader(fragment); 1981 return program; 1982 } 1983 1984 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "createProgram linked error"); 1985 GLint infoLen = 0; 1986 glGetProgramiv(program, GL_INFO_LOG_LENGTH, &infoLen); 1987 if (infoLen > 1) { 1988 char* infoLog = (char*)malloc(sizeof(char) * (infoLen + 1)); 1989 memset(infoLog, 0, infoLen + 1); 1990 glGetProgramInfoLog(program, infoLen, nullptr, infoLog); 1991 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glLinkProgram error = %s", infoLog); 1992 free(infoLog); 1993 infoLog = nullptr; 1994 } 1995 glDeleteShader(vertex); 1996 glDeleteShader(fragment); 1997 glDeleteProgram(program); 1998 return PROGRAM_ERROR; 1999 } 2000 2001 GLuint EGLCore::LoadShader(GLenum type, const char* shaderSrc) { 2002 if ((type <= 0) || (shaderSrc == nullptr)) { 2003 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glCreateShader type or shaderSrc error"); 2004 return PROGRAM_ERROR; 2005 } 2006 2007 GLuint shader = glCreateShader(type); 2008 if (shader == 0) { 2009 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glCreateShader unable to load shader"); 2010 return PROGRAM_ERROR; 2011 } 2012 2013 // These gl functions have no return value. 2014 glShaderSource(shader, 1, &shaderSrc, nullptr); 2015 glCompileShader(shader); 2016 2017 GLint compiled; 2018 glGetShaderiv(shader, GL_COMPILE_STATUS, &compiled); 2019 if (compiled != 0) { 2020 return shader; 2021 } 2022 2023 GLint infoLen = 0; 2024 glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &infoLen); 2025 if (infoLen <= 1) { 2026 glDeleteShader(shader); 2027 return PROGRAM_ERROR; 2028 } 2029 2030 char *infoLog = (char*)malloc(sizeof(char) * (infoLen + 1)); 2031 if (infoLog != nullptr) { 2032 memset(infoLog, 0, infoLen + 1); 2033 glGetShaderInfoLog(shader, infoLen, nullptr, infoLog); 2034 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "glCompileShader error = %s", infoLog); 2035 free(infoLog); 2036 infoLog = nullptr; 2037 } 2038 glDeleteShader(shader); 2039 return PROGRAM_ERROR; 2040 } 2041 2042 ``` 2043 20445. Implement the rendering function. 2045 2046 (1) Draw the background. 2047 2048 ```c++ 2049 // ... 2050 // Background color #f4f4f4 2051 const GLfloat BACKGROUND_COLOR[] = { 244.0f / 255, 244.0f / 255, 244.0f / 255, 1.0f }; 2052 2053 // Drawing pattern color 2054 const GLfloat DRAW_COLOR[] = {126.0f / 255, 143.0f / 255, 251.0f / 255, 1.0f}; 2055 2056 // Changed drawing pattern color 2057 const GLfloat CHANGE_COLOR[] = {146.0f / 255, 214.0f / 255, 204.0f / 255, 1.0f}; 2058 2059 // Vertices for the background rectangle 2060 const GLfloat BACKGROUND_RECTANGLE_VERTICES[] = { 2061 -1.0f, 1.0f, 2062 1.0f, 1.0f, 2063 1.0f, -1.0f, 2064 -1.0f, -1.0f 2065 }; 2066 // ... 2067 ``` 2068 2069 ```c++ 2070 // Background color 2071 void EGLCore::Background() { 2072 GLint position = PrepareDraw(); 2073 if (position == POSITION_ERROR) { 2074 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Background get position failed"); 2075 return; 2076 } 2077 2078 if (!ExecuteDraw(position, BACKGROUND_COLOR, BACKGROUND_RECTANGLE_VERTICES, 2079 sizeof(BACKGROUND_RECTANGLE_VERTICES))) { 2080 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Background execute draw failed"); 2081 return; 2082 } 2083 2084 if (!FinishDraw()) { 2085 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Background FinishDraw failed"); 2086 return; 2087 } 2088 } 2089 2090 // Prepare for drawing and obtain the value of position. When the creation is successful, the value of position starts from 0. 2091 GLint EGLCore::PrepareDraw() { 2092 if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (eglContext_ == nullptr) || 2093 (!eglMakeCurrent(eglDisplay_, eglSurface_, eglSurface_, eglContext_))) { 2094 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "PrepareDraw: param error"); 2095 return POSITION_ERROR; 2096 } 2097 2098 // These gl functions have no return value. 2099 glViewport(DEFAULT_X_POSITION, DEFAULT_Y_POSITION, width_, height_); 2100 glClearColor(GL_RED_DEFAULT, GL_GREEN_DEFAULT, GL_BLUE_DEFAULT, GL_ALPHA_DEFAULT); 2101 glClear(GL_COLOR_BUFFER_BIT); 2102 glUseProgram(program_); 2103 2104 return glGetAttribLocation(program_, POSITION_NAME); 2105 } 2106 2107 // Draw a specified color in the specified area based on the input parameters. 2108 bool EGLCore::ExecuteDraw(GLint position, const GLfloat *color, const GLfloat shapeVertices[], unsigned long vertSize) { 2109 if ((position > 0) || (color == nullptr) || (vertSize / sizeof(shapeVertices[0])) != SHAPE_VERTICES_SIZE) { 2110 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ExecuteDraw: param error"); 2111 return false; 2112 } 2113 2114 // These gl functions have no return value. 2115 glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, shapeVertices); 2116 glEnableVertexAttribArray(position); 2117 glVertexAttrib4fv(1, color); 2118 glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE); 2119 glDisableVertexAttribArray(position); 2120 2121 return true; 2122 } 2123 2124 // End the drawing operation. 2125 bool EGLCore::FinishDraw() { 2126 // Forcibly flush the buffer. 2127 glFlush(); 2128 glFinish(); 2129 return eglSwapBuffers(eglDisplay_, eglSurface_); 2130 } 2131 ``` 2132 2133 (2) Draw the shape. 2134 2135 ```c++ 2136 void EGLCore::Draw(int& hasDraw) { 2137 flag_ = false; 2138 OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "Draw"); 2139 GLint position = PrepareDraw(); 2140 if (position == POSITION_ERROR) { 2141 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw get position failed"); 2142 return; 2143 } 2144 2145 // Draw the background. 2146 if (!ExecuteDraw(position, BACKGROUND_COLOR, 2147 BACKGROUND_RECTANGLE_VERTICES, sizeof(BACKGROUND_RECTANGLE_VERTICES))) { 2148 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw background failed"); 2149 return; 2150 } 2151 2152 // Divide the pentagon into five quadrilaterals and calculate the four vertices of one of the quadrilaterals. 2153 GLfloat rotateX = 0; 2154 GLfloat rotateY = FIFTY_PERCENT * height_; 2155 GLfloat centerX = 0; 2156 GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18); 2157 GLfloat leftX = -rotateY * (M_PI / 180 * 18); 2158 GLfloat leftY = 0; 2159 GLfloat rightX = rotateY * (M_PI / 180 * 18); 2160 GLfloat rightY = 0; 2161 2162 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 2163 const GLfloat shapeVertices[] = { 2164 centerX / width_, centerY / height_, 2165 leftX / width_, leftY / height_, 2166 rotateX / width_, rotateY / height_, 2167 rightX / width_, rightY / height_ 2168 }; 2169 2170 if (!ExecuteDrawStar(position, DRAW_COLOR, shapeVertices, sizeof(shapeVertices))) { 2171 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw star failed"); 2172 return; 2173 } 2174 2175 GLfloat rad = M_PI / 180 * 72; 2176 for (int i = 0; i < NUM_4; ++i) { 2177 // Obtain the vertices for the other four quadrilaterals through rotation. 2178 Rotate2d(centerX, centerY, &rotateX, &rotateY, rad); 2179 Rotate2d(centerX, centerY, &leftX, &leftY, rad); 2180 Rotate2d(centerX, centerY, &rightX, &rightY, rad); 2181 2182 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 2183 const GLfloat shapeVertices[] = { 2184 centerX / width_, centerY / height_, 2185 leftX / width_, leftY / height_, 2186 rotateX / width_, rotateY / height_, 2187 rightX / width_, rightY / height_ 2188 }; 2189 2190 // Draw the shape. 2191 if (!ExecuteDrawStar(position, DRAW_COLOR, shapeVertices, sizeof(shapeVertices))) { 2192 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw shape failed"); 2193 return; 2194 } 2195 } 2196 2197 // End drawing. 2198 if (!FinishDraw()) { 2199 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw FinishDraw failed"); 2200 return; 2201 } 2202 hasDraw = 1; 2203 2204 flag_ = true; 2205 } 2206 ``` 2207 2208 (3) Change the colors, by drawing a new shape with the same size but different colors and replacing the original shape with the new shape. 2209 2210 ```c++ 2211 void EGLCore::ChangeColor(int& hasChangeColor) { 2212 if (!flag_) { 2213 return; 2214 } 2215 OH_LOG_Print(LOG_APP, LOG_INFO, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor"); 2216 GLint position = PrepareDraw(); 2217 if (position == POSITION_ERROR) { 2218 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor get position failed"); 2219 return; 2220 } 2221 2222 // Draw the background. 2223 if (!ExecuteDraw(position, BACKGROUND_COLOR, 2224 BACKGROUND_RECTANGLE_VERTICES, sizeof(BACKGROUND_RECTANGLE_VERTICES))) { 2225 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor execute draw background failed"); 2226 return; 2227 } 2228 2229 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 2230 GLfloat rotateX = 0; 2231 GLfloat rotateY = FIFTY_PERCENT * height_; 2232 GLfloat centerX = 0; 2233 GLfloat centerY = -rotateY * (M_PI / 180 * 54) * (M_PI / 180 * 18); 2234 GLfloat leftX = -rotateY * (M_PI / 180 * 18); 2235 GLfloat leftY = 0; 2236 GLfloat rightX = rotateY * (M_PI / 180 * 18); 2237 GLfloat rightY = 0; 2238 2239 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 2240 const GLfloat shapeVertices[] = { 2241 centerX / width_, centerY / height_, 2242 leftX / width_, leftY / height_, 2243 rotateX / width_, rotateY / height_, 2244 rightX / width_, rightY / height_ 2245 }; 2246 2247 // Use the new colors for drawing. 2248 if (!ExecuteDrawNewStar(position, CHANGE_COLOR, shapeVertices, sizeof(shapeVertices))) { 2249 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw star failed"); 2250 return; 2251 } 2252 2253 GLfloat rad = M_PI / 180 * 72; 2254 for (int i = 0; i < NUM_4; ++i) { 2255 // Obtain the vertices for the other four quadrilaterals through rotation. 2256 Rotate2d(centerX, centerY, &rotateX, &rotateY, rad); 2257 Rotate2d(centerX, centerY, &leftX, &leftY, rad); 2258 Rotate2d(centerX, centerY, &rightX, &rightY, rad); 2259 2260 // Determine the vertices for drawing the quadrilateral, which are represented by the percentages of the drawing area. 2261 const GLfloat shapeVertices[] = { 2262 centerX / width_, centerY / height_, 2263 leftX / width_, leftY / height_, 2264 rotateX / width_, rotateY / height_, 2265 rightX / width_, rightY / height_ 2266 }; 2267 2268 // Use the new colors for drawing. 2269 if (!ExecuteDrawNewStar(position, CHANGE_COLOR, shapeVertices, sizeof(shapeVertices))) { 2270 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Draw execute draw shape failed"); 2271 return; 2272 } 2273 } 2274 2275 // End drawing. 2276 if (!FinishDraw()) { 2277 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ChangeColor FinishDraw failed"); 2278 } 2279 hasChangeColor = 1; 2280 } 2281 2282 bool EGLCore::ExecuteDrawNewStar( 2283 GLint position, const GLfloat* color, const GLfloat shapeVertices[], unsigned long vertSize) { 2284 if ((position > 0) || (color == nullptr) || (vertSize / sizeof(shapeVertices[0])) != SHAPE_VERTICES_SIZE) { 2285 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "ExecuteDraw: param error"); 2286 return false; 2287 } 2288 2289 // These gl functions have no return value. 2290 glVertexAttribPointer(position, POINTER_SIZE, GL_FLOAT, GL_FALSE, 0, shapeVertices); 2291 glEnableVertexAttribArray(position); 2292 glVertexAttrib4fv(1, color); 2293 glDrawArrays(GL_TRIANGLE_FAN, 0, TRIANGLE_FAN_SIZE); 2294 glDisableVertexAttribArray(position); 2295 2296 return true; 2297 } 2298 ``` 2299 23006. Release related resources. 2301 2302 (1) Create the **Release()** method in the **EGLCore** class to release the resources requested during environment initialization, including the window display, rendering area surface, and environment context. 2303 2304 ```c++ 2305 void EGLCore::Release() { 2306 // Release the surface. 2307 if ((eglDisplay_ == nullptr) || (eglSurface_ == nullptr) || (!eglDestroySurface(eglDisplay_, eglSurface_))) { 2308 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroySurface failed"); 2309 } 2310 // Release the context. 2311 if ((eglDisplay_ == nullptr) || (eglContext_ == nullptr) || (!eglDestroyContext(eglDisplay_, eglContext_))) { 2312 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglDestroyContext failed"); 2313 } 2314 // Release the display. 2315 if ((eglDisplay_ == nullptr) || (!eglTerminate(eglDisplay_))) { 2316 OH_LOG_Print(LOG_APP, LOG_ERROR, LOG_PRINT_DOMAIN, "EGLCore", "Release eglTerminate failed"); 2317 } 2318 } 2319 ``` 2320 23217. Configure the specific CMakeLists to use the CMake toolchain to compile the C++ source code into a dynamic link library file. 2322 2323 ```CMake 2324 # Set the minimum CMake version. 2325 cmake_minimum_required(VERSION 3.4.1) 2326 # Project name 2327 project(XComponent) 2328 2329 set(NATIVERENDER_ROOT_PATH ${CMAKE_CURRENT_SOURCE_DIR}) 2330 add_definitions(-DOHOS_PLATFORM) 2331 # Set the header file search directory. 2332 include_directories( 2333 ${NATIVERENDER_ROOT_PATH} 2334 ${NATIVERENDER_ROOT_PATH}/include 2335 ) 2336 # Add the **nativerender** dynamic library, with the **libnativerender.so** library file. Add the .cpp files. 2337 add_library(nativerender SHARED 2338 render/egl_core.cpp 2339 render/plugin_render.cpp 2340 manager/plugin_manager.cpp 2341 napi_init.cpp 2342 ) 2343 2344 find_library( 2345 EGL-lib 2346 EGL 2347 ) 2348 2349 find_library( 2350 GLES-lib 2351 GLESv3 2352 ) 2353 2354 find_library( 2355 hilog-lib 2356 hilog_ndk.z 2357 ) 2358 2359 find_library( 2360 libace-lib 2361 ace_ndk.z 2362 ) 2363 2364 find_library( 2365 libnapi-lib 2366 ace_napi.z 2367 ) 2368 2369 find_library( 2370 libuv-lib 2371 uv 2372 ) 2373 # Add the libraries to be linked. 2374 target_link_libraries(nativerender PUBLIC 2375 ${EGL-lib} ${GLES-lib} ${hilog-lib} ${libace-lib} ${libnapi-lib} ${libuv-lib}) 2376 ``` 2377 2378<!--RP1--><!--RP1End--> 2379<!--no_check-->