• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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