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