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++)](drawing-guidelines.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```c++ 37#include <ace/xcomponent/native_interface_xcomponent.h> 38#include "napi/native_api.h" 39#include <native_display_soloist/native_display_soloist.h> 40#include <native_drawing/drawing_bitmap.h> 41#include <native_drawing/drawing_color.h> 42#include <native_drawing/drawing_canvas.h> 43#include <native_drawing/drawing_pen.h> 44#include <native_drawing/drawing_brush.h> 45#include <native_drawing/drawing_path.h> 46#include <native_window/external_window.h> 47#include <cmath> 48#include <algorithm> 49#include <stdint.h> 50#include <sys/mman.h> 51``` 52 531. Define an ArkTS API file and name it **XComponentContext.ts**, which is used to connect to the native layer. 54 ```ts 55 export default interface XComponentContext { 56 register(): void; 57 unregister(): void; 58 destroy(): void; 59 }; 60 ``` 61 622. Define a demo page, which contains two **XComponents**. 63 64 ```ts 65 import XComponentContext from "../interface/XComponentContext"; 66 67 @Entry 68 @Component 69 struct Index { 70 private xComponentContext1: XComponentContext | undefined = undefined; 71 private xComponentContext2: XComponentContext | undefined = undefined; 72 73 build() { 74 Column() { 75 Row() { 76 XComponent({ id: 'xcomponentId30', type: 'surface', libraryname: 'entry' }) 77 .onLoad((xComponentContext) => { 78 this.xComponentContext1 = xComponentContext as XComponentContext; 79 }).width('640px') 80 }.height('40%') 81 82 Row() { 83 XComponent({ id: 'xcomponentId120', type: 'surface', libraryname: 'entry' }) 84 .onLoad((xComponentContext) => { 85 this.xComponentContext2 = xComponentContext as XComponentContext; 86 }).width('640px') // Multiple of 64 87 }.height('40%') 88 } 89 } 90 } 91 ``` 92 933. 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. 94 95 Create a **PluginManger** singleton to manage the native **XComponent**. 96 ```c++ 97 class PluginManager { 98 public: 99 ~PluginManager(); 100 101 static PluginManager *GetInstance(); 102 103 void SetNativeXComponent(std::string &id, OH_NativeXComponent *nativeXComponent); 104 SampleBitMap *GetRender(std::string &id); 105 void Export(napi_env env, napi_value exports); 106 private: 107 108 std::unordered_map<std::string, OH_NativeXComponent *> nativeXComponentMap_; 109 std::unordered_map<std::string, SampleXComponent *> pluginRenderMap_; 110 }; 111 ``` 112 The **SampleXComponent** class will be created in the step of drawing the graphic. 113 ```c++ 114 void PluginManager::Export(napi_env env, napi_value exports) { 115 nativeXComponentMap_.clear(); 116 pluginRenderMap_.clear(); 117 if ((env == nullptr) || (exports == nullptr)) { 118 DRAWING_LOGE("Export: env or exports is null"); 119 return; 120 } 121 122 napi_value exportInstance = nullptr; 123 if (napi_get_named_property(env, exports, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { 124 DRAWING_LOGE("Export: napi_get_named_property fail"); 125 return; 126 } 127 128 OH_NativeXComponent *nativeXComponent = nullptr; 129 if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) { 130 DRAWING_LOGE("Export: napi_unwrap fail"); 131 return; 132 } 133 134 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 135 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 136 if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 137 DRAWING_LOGE("Export: OH_NativeXComponent_GetXComponentId fail"); 138 return; 139 } 140 141 std::string id(idStr); 142 auto context = PluginManager::GetInstance(); 143 if ((context != nullptr) && (nativeXComponent != nullptr)) { 144 context->SetNativeXComponent(id, nativeXComponent); 145 auto render = context->GetRender(id); 146 if (render != nullptr) { 147 render->RegisterCallback(nativeXComponent); 148 render->Export(env, exports); 149 } else { 150 DRAWING_LOGE("render is nullptr"); 151 } 152 } 153 } 154 ``` 155 1564. Configure the frame rate and register the callback function at the native layer. 157 158 Define the callback function for each frame. 159 160 ```c++ 161 static void TestCallback(long long timestamp, long long targetTimestamp, void *data) 162 { 163 // ... 164 // Obtain the corresponding XComponent. 165 OH_NativeXComponent *component = nullptr; 166 component = static_cast<OH_NativeXComponent *>(data); 167 if (component == nullptr) { 168 SAMPLE_LOGE("TestCallback: component is null"); 169 return; 170 } 171 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 172 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 173 if (OH_NativeXComponent_GetXComponentId(component, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 174 SAMPLE_LOGE("TestCallback: Unable to get XComponent id"); 175 return; 176 } 177 178 std::string id(idStr); 179 auto render = SampleXComponent::GetInstance(id); 180 OHNativeWindow *nativeWindow = render->GetNativeWindow(); 181 uint64_t width; 182 uint64_t height; 183 // Obtain the surface size of the XComponent. 184 int32_t xSize = OH_NativeXComponent_GetXComponentSize(component, nativeWindow, &width, &height); 185 if ((xSize == OH_NATIVEXCOMPONENT_RESULT_SUCCESS) && (render != nullptr)) { 186 render->Prepare(); 187 render->Create(); 188 if (id == "xcomponentId30") { 189 // When the frame rate is 30 Hz, the frame moves by 16 pixels at a time. 190 render->ConstructPath(16, 16, render->defaultOffsetY); 191 } 192 if (id == "xcomponentId120") { 193 // When the frame rate is 120 Hz, the frame moves by 4 pixels at a time. 194 render->ConstructPath(4, 4, render->defaultOffsetY); 195 } 196 // ... 197 } 198 } 199 ``` 200 2015. Call the **DisplaySoloist** APIs to configure the frame rate and register the callback function for each frame. 202 203 > **NOTE** 204 > 205 > - 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. 206 > - During page redirection, both **NapiUnregister** and **NapiDestroy** must be called to avoid memory leakage. 207 208 ```c++ 209 static std::unordered_map<std::string, OH_DisplaySoloist *> g_displaySync; 210 211 napi_value SampleXComponent::NapiRegister(napi_env env, napi_callback_info info) 212 { 213 // ... 214 // Obtain the corresponding XComponent. 215 napi_value thisArg; 216 if (napi_get_cb_info(env, info, nullptr, nullptr, &thisArg, nullptr) != napi_ok) { 217 SAMPLE_LOGE("NapiRegister: napi_get_cb_info fail"); 218 return nullptr; 219 } 220 221 napi_value exportInstance; 222 if (napi_get_named_property(env, thisArg, OH_NATIVE_XCOMPONENT_OBJ, &exportInstance) != napi_ok) { 223 SAMPLE_LOGE("NapiRegister: napi_get_named_property fail"); 224 return nullptr; 225 } 226 227 OH_NativeXComponent *nativeXComponent = nullptr; 228 if (napi_unwrap(env, exportInstance, reinterpret_cast<void **>(&nativeXComponent)) != napi_ok) { 229 SAMPLE_LOGE("NapiRegister: napi_unwrap fail"); 230 return nullptr; 231 } 232 233 char idStr[OH_XCOMPONENT_ID_LEN_MAX + 1] = {'\0'}; 234 uint64_t idSize = OH_XCOMPONENT_ID_LEN_MAX + 1; 235 if (OH_NativeXComponent_GetXComponentId(nativeXComponent, idStr, &idSize) != OH_NATIVEXCOMPONENT_RESULT_SUCCESS) { 236 SAMPLE_LOGE("NapiRegister: Unable to get XComponent id"); 237 return nullptr; 238 } 239 SAMPLE_LOGI("RegisterID = %{public}s", idStr); 240 std::string id(idStr); 241 SampleXComponent *render = SampleXComponent().GetInstance(id); 242 if (render != nullptr) { 243 OH_DisplaySoloist *nativeDisplaySoloist = nullptr; 244 if (g_displaySync.find(id) == g_displaySync.end()) { 245 // Create an OH_DisplaySoloist instance. 246 // The value true means that the OH_DisplaySoloist instance exclusively occupies a thread, and false means that the instance shares a thread with others. 247 g_displaySync[id] = OH_DisplaySoloist_Create(true); 248 } 249 nativeDisplaySoloist = g_displaySync[id]; 250 // Set the expected frame rate range. 251 // The member variables of this struct are the minimum frame rate, maximum frame rate, and expected frame rate. 252 DisplaySoloist_ExpectedRateRange range; 253 if (id == "xcomponentId30") { 254 // The expected frame rate of the first XComponent is 30 Hz. 255 range = {30, 120, 30}; 256 } 257 if (id == "xcomponentId120") { 258 // The expected frame rate of the second XComponent is 120 Hz. 259 range = {30, 120, 120}; 260 } 261 OH_DisplaySoloist_SetExpectedFrameRateRange(nativeDisplaySoloist, &range); 262 // Register the callback function for each frame and enable it. 263 OH_DisplaySoloist_Start(nativeDisplaySoloist, TestCallback, nativeXComponent); 264 } 265 // ... 266 } 267 268 napi_value SampleXComponent::NapiUnregister(napi_env env, napi_callback_info info) 269 { 270 // ... 271 // Unregister the callback function for each frame. 272 OH_DisplaySoloist_Stop(g_displaySync[id]);; 273 // ... 274 } 275 276 napi_value SampleXComponent::NapiDestroy(napi_env env, napi_callback_info info) 277 { 278 // ... 279 // Destroy the OH_DisplaySoloist instance. 280 OH_DisplaySoloist_Destroy(g_displaySync[id]); 281 g_displaySync.erase(id); 282 // ... 283 } 284 285 // Implement the mappings between the ArkTS APIs in XComponentContext.ts and C++ APIs. 286 void SampleXComponent::Export(napi_env env, napi_value exports) { 287 if ((env == nullptr) || (exports == nullptr)) { 288 SAMPLE_LOGE("Export: env or exports is null"); 289 return; 290 } 291 napi_property_descriptor desc[] = { 292 {"register", nullptr, SampleXComponent::NapiRegister, nullptr, nullptr, nullptr, napi_default, nullptr}, 293 {"unregister", nullptr, SampleXComponent::NapiUnregister, nullptr, nullptr, nullptr, napi_default, nullptr}, 294 {"destroy", nullptr, SampleXComponent::NapiDestroy, nullptr, nullptr, nullptr, napi_default, nullptr}}; 295 296 napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc); 297 if (napi_define_properties(env, exports, sizeof(desc) / sizeof(desc[0]), desc) != napi_ok) { 298 SAMPLE_LOGE("Export: napi_define_properties failed"); 299 } 300 } 301 ``` 302 3036. Register and deregister the callback function for each frame at the TS layer, and destroy the OH_DisplaySoloist instance. 304 305 ```c++ 306 // When you leave the page, unregister the callback function and destroy the OH_DisplaySoloist instance. 307 aboutToDisappear(): void { 308 if (this.xComponentContext1) { 309 this.xComponentContext1.unregister(); 310 this.xComponentContext1.destroy(); 311 } 312 if (this.xComponentContext2) { 313 this.xComponentContext2.unregister(); 314 this.xComponentContext2.destroy(); 315 } 316 } 317 318 Row() { 319 Button('Start') 320 .id('Start') 321 .fontSize(14) 322 .fontWeight(500) 323 .margin({ bottom: 20, right: 6, left: 6 }) 324 .onClick(() => { 325 if (this.xComponentContext1) { 326 this.xComponentContext1.register(); 327 } 328 if (this.xComponentContext2) { 329 this.xComponentContext2.register(); 330 } 331 }) 332 .width('30%') 333 .height(40) 334 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 335 336 Button('Stop') 337 .id('Stop') 338 .fontSize(14) 339 .fontWeight(500) 340 .margin({ bottom: 20, left: 6 }) 341 .onClick(() => { 342 if (this.xComponentContext1) { 343 this.xComponentContext1.unregister(); 344 } 345 if (this.xComponentContext2) { 346 this.xComponentContext2.unregister(); 347 } 348 }) 349 .width('30%') 350 .height(40) 351 .shadow(ShadowStyle.OUTER_DEFAULT_LG) 352 } 353 ``` 354 355