1# Native DisplaySoloist Development (C/C++) 2 3To develop a native service that controls the frame rate in an independent thread, you use **DisplaySoloist** to implement the services, such as gaming and self-drawing UI framework interconnection. 4 5A **DisplaySoloist** instance can exclusively occupy a thread or share a thread with others. 6 7## Available APIs 8 9| Name | Description | 10| ------------------------------------------------------------ | ----------------------------------------------------- | 11| OH_DisplaySoloist* OH_DisplaySoloist_Create (bool useExclusiveThread) | Creates an **OH_DisplaySoloist** instance. | 12| OH_DisplaySoloist_Destroy (OH_DisplaySoloist * displaySoloist) | Destroys an **OH_DisplaySoloist** instance. | 13| OH_DisplaySoloist_Start (OH_DisplaySoloist * displaySoloist, OH_DisplaySoloist_FrameCallback callback, void * data ) | Sets a callback function for each frame. The callback function is triggered each time a VSync signal arrives. | 14| OH_DisplaySoloist_Stop (OH_DisplaySoloist * displaySoloist) | Stops requesting the next VSync signal and triggering the callback function.| 15| OH_DisplaySoloist_SetExpectedFrameRateRange (OH_DisplaySoloist* displaySoloist, DisplaySoloist_ExpectedRateRange* range) | Sets the expected frame rate range. | 16 17## How to Develop 18 19In this example, a graphic is drawn using the native Drawing module. Specifically, an expected frame rate is set through the asynchronous thread, and the graphic is drawn based on the frame rate and displayed on the native window. For details about graphics drawing, see [Using Drawing to Draw and Display Graphics (C/C++)](graphic-drawing-overview.md). 20 21### Adding Dependencies 22 23**Adding Dynamic Link Libraries** 24 25Add the following libraries to **CMakeLists.txt**. 26 27```txt 28libace_napi.z.so 29libace_ndk.z.so 30libnative_window.so 31libnative_drawing.so 32libnative_display_soloist.so 33``` 34 35**Including Header Files** 36 37```c++ 38#include <ace/xcomponent/native_interface_xcomponent.h> 39#include "napi/native_api.h" 40#include <native_display_soloist/native_display_soloist.h> 41#include <native_drawing/drawing_bitmap.h> 42#include <native_drawing/drawing_color.h> 43#include <native_drawing/drawing_canvas.h> 44#include <native_drawing/drawing_pen.h> 45#include <native_drawing/drawing_brush.h> 46#include <native_drawing/drawing_path.h> 47#include <native_window/external_window.h> 48#include <cmath> 49#include <algorithm> 50#include <stdint.h> 51#include <sys/mman.h> 52``` 53 54### How to Develop 55 561. Define an ArkTS API file and name it **XComponentContext.ts**, which is used to connect to the native layer. 57 ```ts 58 export default interface XComponentContext { 59 register(): void; 60 unregister(): void; 61 destroy(): void; 62 }; 63 ``` 64 652. Define a demo page, which contains two **XComponents**. 66 67 ```ts 68 import XComponentContext from "../interface/XComponentContext"; 69 70 @Entry 71 @Component 72 struct Index { 73 private xComponentContext1: XComponentContext | undefined = undefined; 74 private xComponentContext2: XComponentContext | undefined = undefined; 75 76 build() { 77 Column() { 78 Row() { 79 XComponent({ id: 'xcomponentId30', type: 'surface', libraryname: 'entry' }) 80 .onLoad((xComponentContext) => { 81 this.xComponentContext1 = xComponentContext as XComponentContext; 82 }).width('640px') 83 }.height('40%') 84 85 Row() { 86 XComponent({ id: 'xcomponentId120', type: 'surface', libraryname: 'entry' }) 87 .onLoad((xComponentContext) => { 88 this.xComponentContext2 = xComponentContext as XComponentContext; 89 }).width('640px') // Multiple of 64 90 }.height('40%') 91 } 92 } 93 } 94 ``` 95 963. Obtain the native **XComponent** at the C++ layer. You are advised to save the **XComponent** in a singleton. This step must be performed during napi_init. 97 98 Create a **PluginManger** singleton to manage the native **XComponent**. 99 ```c++ 100 class PluginManager { 101 public: 102 ~PluginManager(); 103 104 static PluginManager *GetInstance(); 105 106 void SetNativeXComponent(std::string &id, OH_NativeXComponent *nativeXComponent); 107 SampleBitMap *GetRender(std::string &id); 108 void Export(napi_env env, napi_value exports); 109 private: 110 111 std::unordered_map<std::string, OH_NativeXComponent *> nativeXComponentMap_; 112 std::unordered_map<std::string, SampleXComponent *> pluginRenderMap_; 113 }; 114 ``` 115 The **SampleXComponent** class will be created in the step of drawing the graphic. 116 ```c++ 117 void PluginManager::Export(napi_env env, napi_value exports) { 118 nativeXComponentMap_.clear(); 119 pluginRenderMap_.clear(); 120 if ((env == nullptr) || (exports == nullptr)) { 121 DRAWING_LOGE("Export: env or exports is null"); 122 return; 123 } 124 125 napi_value exportInstance = nullptr; 126 if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { 127 DRAWING_LOGE("Export: napi_get_named_property fail"); 128 return; 129 } 130 131 OH_NativeXComponent *nativeXComponent = nullptr; 132 if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) { 133 DRAWING_LOGE("Export: napi_unwrap fail"); 134 return; 135 } 136 137 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 138 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 139 if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 140 DRAWING_LOGE("Export: OH_NativeXComponent_GetXComponentId fail"); 141 return; 142 } 143 144 std::string id(idStr); 145 auto context = PluginManager::GetInstance(); 146 if ((context != nullptr) && (nativeXComponent != nullptr)) { 147 context->SetNativeXComponent(id, nativeXComponent); 148 auto render = context->GetRender(id); 149 if (render != nullptr) { 150 render->RegisterCallback(nativeXComponent); 151 render->Export(env, exports); 152 } else { 153 DRAWING_LOGE("render is nullptr"); 154 } 155 } 156 } 157 ``` 158 1594. Configure the frame rate and register the callback function at the native layer. 160 161 Define the callback function for each frame. 162 163 ```c++ 164 static void TestCallback(long long timestamp, long long targetTimestamp, void *data) 165 { 166 // ... 167 // Obtain the corresponding XComponent. 168 OH_NativeXComponent *component = nullptr; 169 component = static_cast<OH_NativeXComponent *>(data); 170 if (component == nullptr) { 171 SAMPLE_LOGE("TestCallback: component is null"); 172 return; 173 } 174 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 175 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 176 if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 177 SAMPLE_LOGE("TestCallback: Unable to get XComponent id"); 178 return; 179 } 180 181 std::string id(idStr); 182 auto render = SampleXComponent::GetInstance(id); 183 OHNativeWindow *nativeWindow = render->GetNativeWindow(); 184 uint64_t width; 185 uint64_t height; 186 // Obtain the surface size of the XComponent. 187 int32_t xSize = OH_NativeXComponent_GetXComponentSize(component, nativeWindow, &width, &height); 188 if ((xSize == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) && (render != nullptr)) { 189 render->Prepare(); 190 render->Create(); 191 if (id == "xcomponentId30") { 192 // When the frame rate is 30 Hz, the frame moves by 16 pixels at a time. 193 render->ConstructPath(16, 16, render->defaultOffsetY); 194 } 195 if (id == "xcomponentId120") { 196 // When the frame rate is 120 Hz, the frame moves by 4 pixels at a time. 197 render->ConstructPath(4, 4, render->defaultOffsetY); 198 } 199 // ... 200 } 201 } 202 ``` 203 2045. Call the **DisplaySoloist** APIs to configure the frame rate and register the callback function for each frame. 205 206 > **NOTE** 207 > 208 > - After the instance calls **NapiRegister**, it must call **NapiUnregister** when it no longer needs to control the frame rate, so as to avoid memory leakage. 209 > - During page redirection, both **NapiUnregister** and **NapiDestroy** must be called to avoid memory leakage. 210 211 ```c++ 212 static std::unordered_map<std::string, OH_DisplaySoloist *> g_displaySync; 213 214 napi_value SampleXComponent::NapiRegister(napi_env env, napi_callback_info info) 215 { 216 // ... 217 // Obtain the corresponding XComponent. 218 napi_value thisArg; 219 if (napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr) != napi_ok) { 220 SAMPLE_LOGE("NapiRegister: napi_get_cb_info fail"); 221 return nullptr; 222 } 223 224 napi_value exportInstance; 225 if (napi_get_named_property(env, thisArg, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { 226 SAMPLE_LOGE("NapiRegister: napi_get_named_property fail"); 227 return nullptr; 228 } 229 230 OH_NativeXComponent *nativeXComponent = nullptr; 231 if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) { 232 SAMPLE_LOGE("NapiRegister: napi_unwrap fail"); 233 return nullptr; 234 } 235 236 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 237 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 238 if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 239 SAMPLE_LOGE("NapiRegister: Unable to get XComponent id"); 240 return nullptr; 241 } 242 SAMPLE_LOGI("RegisterID = %{public}s", idStr); 243 std::string id(idStr); 244 SampleXComponent *render = SampleXComponent().GetInstance(id); 245 if (render != nullptr) { 246 OH_DisplaySoloist *nativeDisplaySoloist = nullptr; 247 if (g_displaySync.find(id) == g_displaySync.end()) { 248 // Create an OH_DisplaySoloist instance. 249 // The value true means that the OH_DisplaySoloist instance exclusively occupies a thread, and false means that the instance shares a thread with others. 250 g_displaySync[id] = OH_DisplaySoloist_Create(true); 251 } 252 nativeDisplaySoloist = g_displaySync[id]; 253 // Set the expected frame rate range. 254 // The member variables of this struct are the minimum frame rate, maximum frame rate, and expected frame rate. 255 DisplaySoloist_ExpectedRateRange range; 256 if (id == "xcomponentId30") { 257 // The expected frame rate of the first XComponent is 30 Hz. 258 range = {30, 120, 30}; 259 } 260 if (id == "xcomponentId120") { 261 // The expected frame rate of the second XComponent is 120 Hz. 262 range = {30, 120, 120}; 263 } 264 OH_DisplaySoloist_SetExpectedFrameRateRange(nativeDisplaySoloist, &range); 265 // Register the callback function for each frame and enable it. 266 OH_DisplaySoloist_Start(nativeDisplaySoloist, TestCallback, nativeXComponent); 267 } 268 // ... 269 } 270 271 napi_value SampleXComponent::NapiUnregister(napi_env env, napi_callback_info info) 272 { 273 // ... 274 // Unregister the callback function for each frame. 275 OH_DisplaySoloist_Stop(g_displaySync[id]);; 276 // ... 277 } 278 279 napi_value SampleXComponent::NapiDestroy(napi_env env, napi_callback_info info) 280 { 281 // ... 282 // Destroy the OH_DisplaySoloist instance. 283 OH_DisplaySoloist_Destroy(g_displaySync[id]); 284 g_displaySync.erase(id); 285 // ... 286 } 287 288 // Implement the mappings between the ArkTS APIs in XComponentContext.ts and C++ APIs. 289 void SampleXComponent::Export(napi_env env, napi_value exports) { 290 if ((env == nullptr) || (exports == nullptr)) { 291 SAMPLE_LOGE("Export: env or exports is null"); 292 return; 293 } 294 napi_property_descriptor desc[] = { 295 {"register", nullptr, SampleXComponent::NapiRegister, nullptr, nullptr, nullptr, napi_default, nullptr}, 296 {"unregister", nullptr, SampleXComponent::NapiUnregister, nullptr, nullptr, nullptr, napi_default, nullptr}, 297 {"destroy", nullptr, SampleXComponent::NapiDestroy, nullptr, nullptr, nullptr, napi_default, nullptr}}; 298 299 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 300 if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { 301 SAMPLE_LOGE("Export: napi_define_properties failed"); 302 } 303 } 304 ``` 305 3066. Register and deregister the callback function for each frame at the TS layer, and destroy the OH_DisplaySoloist instance. 307 308 ```c++ 309 // When you leave the page, unregister the callback function and destroy the OH_DisplaySoloist instance. 310 aboutToDisappear(): void { 311 if (this.xComponentContext1) { 312 this.xComponentContext1.unregister(); 313 this.xComponentContext1.destroy(); 314 } 315 if (this.xComponentContext2) { 316 this.xComponentContext2.unregister(); 317 this.xComponentContext2.destroy(); 318 } 319 } 320 321 Row() { 322 Button('Start') 323 .id('Start') 324 .fontSize(14) 325 .fontWeight(500) 326 .margin({ bottom: 20, right: 6, left: 6 }) 327 .onClick(() => { 328 if (this.xComponentContext1) { 329 this.xComponentContext1.register(); 330 } 331 if (this.xComponentContext2) { 332 this.xComponentContext2.register(); 333 } 334 }) 335 .width('30%') 336 .height(40) 337 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 338 339 Button('Stop') 340 .id('Stop') 341 .fontSize(14) 342 .fontWeight(500) 343 .margin({ bottom: 20, left: 6 }) 344 .onClick(() => { 345 if (this.xComponentContext1) { 346 this.xComponentContext1.unregister(); 347 } 348 if (this.xComponentContext2) { 349 this.xComponentContext2.unregister(); 350 } 351 }) 352 .width('30%') 353 .height(40) 354 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 355 } 356 ``` 357 358