1 /**
2  * @file lv_draw_pxp.c
3  *
4  */
5 
6 /**
7  * Copyright 2022-2024 NXP
8  *
9  * SPDX-License-Identifier: MIT
10  */
11 
12 /*********************
13  *      INCLUDES
14  *********************/
15 
16 #include "lv_draw_pxp.h"
17 
18 #if LV_USE_PXP
19 #if LV_USE_DRAW_PXP || LV_USE_ROTATE_PXP
20 #include "lv_pxp_cfg.h"
21 #include "lv_pxp_utils.h"
22 
23 #if LV_USE_PARALLEL_DRAW_DEBUG
24     #include "../../../core/lv_global.h"
25 #endif
26 
27 /*********************
28  *      DEFINES
29  *********************/
30 
31 #define DRAW_UNIT_ID_PXP 3
32 
33 /**********************
34  *      TYPEDEFS
35  **********************/
36 
37 /**********************
38  *  STATIC PROTOTYPES
39  **********************/
40 
41 /*
42  * Evaluate a task and set the score and preferred PXP unit.
43  * Return 1 if task is preferred, 0 otherwise (task is not supported).
44  */
45 static int32_t _pxp_evaluate(lv_draw_unit_t * draw_unit, lv_draw_task_t * task);
46 
47 /*
48  * Dispatch a task to the PXP unit.
49  * Return 1 if task was dispatched, 0 otherwise (task not supported).
50  */
51 static int32_t _pxp_dispatch(lv_draw_unit_t * draw_unit, lv_layer_t * layer);
52 
53 /*
54  * Delete the PXP draw unit.
55  */
56 static int32_t _pxp_delete(lv_draw_unit_t * draw_unit);
57 
58 #if LV_USE_PXP_DRAW_THREAD
59     static void _pxp_render_thread_cb(void * ptr);
60 #endif
61 
62 static void _pxp_execute_drawing(lv_draw_pxp_unit_t * u);
63 
64 /**********************
65  *  STATIC PROTOTYPES
66  **********************/
67 
68 /**********************
69  *  STATIC VARIABLES
70  **********************/
71 
72 #if LV_USE_PARALLEL_DRAW_DEBUG
73     #define _draw_info LV_GLOBAL_DEFAULT()->draw_info
74 #endif
75 
76 /**********************
77  *      MACROS
78  **********************/
79 
80 /**********************
81  *   GLOBAL FUNCTIONS
82  **********************/
83 
lv_draw_pxp_init(void)84 void lv_draw_pxp_init(void)
85 {
86     lv_pxp_init();
87 
88 #if LV_USE_DRAW_PXP
89     lv_draw_buf_pxp_init_handlers();
90 
91     lv_draw_pxp_unit_t * draw_pxp_unit = lv_draw_create_unit(sizeof(lv_draw_pxp_unit_t));
92     draw_pxp_unit->base_unit.evaluate_cb = _pxp_evaluate;
93     draw_pxp_unit->base_unit.dispatch_cb = _pxp_dispatch;
94     draw_pxp_unit->base_unit.delete_cb = _pxp_delete;
95     draw_pxp_unit->base_unit.name = "NXP_PXP";
96 
97 #if LV_USE_PXP_DRAW_THREAD
98     lv_thread_init(&draw_pxp_unit->thread, "pxpdraw", LV_THREAD_PRIO_HIGH, _pxp_render_thread_cb, 2 * 1024, draw_pxp_unit);
99 #endif
100 #endif /*LV_USE_DRAW_PXP*/
101 }
102 
lv_draw_pxp_deinit(void)103 void lv_draw_pxp_deinit(void)
104 {
105     lv_pxp_deinit();
106 }
107 
lv_draw_pxp_rotate(const void * src_buf,void * dest_buf,int32_t src_width,int32_t src_height,int32_t src_stride,int32_t dest_stride,lv_display_rotation_t rotation,lv_color_format_t cf)108 void lv_draw_pxp_rotate(const void * src_buf, void * dest_buf, int32_t src_width, int32_t src_height,
109                         int32_t src_stride, int32_t dest_stride, lv_display_rotation_t rotation,
110                         lv_color_format_t cf)
111 {
112     lv_pxp_reset();
113 
114     /* convert rotation angle */
115     pxp_rotate_degree_t pxp_rotation;
116     switch(rotation) {
117         case LV_DISPLAY_ROTATION_0:
118             pxp_rotation = kPXP_Rotate0;
119             break;
120         case LV_DISPLAY_ROTATION_90:
121             pxp_rotation = kPXP_Rotate90;
122             break;
123         case LV_DISPLAY_ROTATION_180:
124             pxp_rotation = kPXP_Rotate180;
125             break;
126         case LV_DISPLAY_ROTATION_270:
127             pxp_rotation = kPXP_Rotate270;
128             break;
129         default:
130             pxp_rotation = kPXP_Rotate0;
131             break;
132     }
133     PXP_SetRotateConfig(PXP_ID, kPXP_RotateOutputBuffer, pxp_rotation, kPXP_FlipDisable);
134 
135     /*Simple blit, no effect - Disable PS buffer*/
136     PXP_SetProcessSurfacePosition(PXP_ID, 0xFFFFU, 0xFFFFU, 0U, 0U);
137 
138     /*AS buffer - source image*/
139     pxp_as_buffer_config_t asBufferConfig = {
140         .pixelFormat = pxp_get_as_px_format(cf),
141         .bufferAddr = (uint32_t)src_buf,
142         .pitchBytes = src_stride
143     };
144     PXP_SetAlphaSurfaceBufferConfig(PXP_ID, &asBufferConfig);
145     PXP_SetAlphaSurfacePosition(PXP_ID, 0U, 0U, src_width - 1U, src_height - 1U);
146     PXP_EnableAlphaSurfaceOverlayColorKey(PXP_ID, false);
147 
148     /*Output buffer.*/
149     pxp_output_buffer_config_t outputBufferConfig = {
150         .pixelFormat = pxp_get_out_px_format(cf),
151         .interlacedMode = kPXP_OutputProgressive,
152         .buffer0Addr = (uint32_t)dest_buf,
153         .buffer1Addr = (uint32_t)0U,
154         .pitchBytes = dest_stride,
155         .width = src_width,
156         .height = src_height
157     };
158     PXP_SetOutputBufferConfig(PXP_ID, &outputBufferConfig);
159 
160     lv_pxp_run();
161 }
162 
163 /**********************
164  *   STATIC FUNCTIONS
165  **********************/
166 #if LV_USE_DRAW_PXP
_pxp_src_cf_supported(lv_color_format_t cf)167 static inline bool _pxp_src_cf_supported(lv_color_format_t cf)
168 {
169     bool is_cf_supported = false;
170 
171     switch(cf) {
172         case LV_COLOR_FORMAT_RGB565:
173         case LV_COLOR_FORMAT_ARGB8888:
174         case LV_COLOR_FORMAT_XRGB8888:
175             is_cf_supported = true;
176             break;
177         default:
178             break;
179     }
180 
181     return is_cf_supported;
182 }
183 
_pxp_dest_cf_supported(lv_color_format_t cf)184 static inline bool _pxp_dest_cf_supported(lv_color_format_t cf)
185 {
186     bool is_cf_supported = false;
187 
188     switch(cf) {
189         case LV_COLOR_FORMAT_RGB565:
190         case LV_COLOR_FORMAT_RGB888:
191         case LV_COLOR_FORMAT_ARGB8888:
192         case LV_COLOR_FORMAT_XRGB8888:
193             is_cf_supported = true;
194             break;
195         default:
196             break;
197     }
198 
199     return is_cf_supported;
200 }
201 
_pxp_draw_img_supported(const lv_draw_image_dsc_t * draw_dsc)202 static bool _pxp_draw_img_supported(const lv_draw_image_dsc_t * draw_dsc)
203 {
204     const lv_image_dsc_t * img_dsc = draw_dsc->src;
205 
206     bool has_recolor = (draw_dsc->recolor_opa > LV_OPA_MIN);
207     bool has_transform = (draw_dsc->rotation != 0 || draw_dsc->scale_x != LV_SCALE_NONE ||
208                           draw_dsc->scale_y != LV_SCALE_NONE);
209 
210     /* Recolor and transformation are not supported at the same time. */
211     if(has_recolor && has_transform)
212         return false;
213 
214     bool has_opa = (draw_dsc->opa < (lv_opa_t)LV_OPA_MAX);
215     bool src_has_alpha = (img_dsc->header.cf == LV_COLOR_FORMAT_ARGB8888);
216 
217     /*
218      * Recolor or transformation for images w/ opa or alpha channel can't
219      * be obtained in a single PXP configuration. Two steps are required.
220      */
221     if((has_recolor || has_transform) && (has_opa || src_has_alpha))
222         return false;
223 
224     /* PXP can only rotate at 90x angles. */
225     if(draw_dsc->rotation % 900)
226         return false;
227 
228     /*
229      * PXP is set to process 16x16 blocks to optimize the system for memory
230      * bandwidth and image processing time.
231      * The output engine essentially truncates any output pixels after the
232      * desired number of pixels has been written.
233      * When rotating a source image and the output is not divisible by the block
234      * size, the incorrect pixels could be truncated and the final output image
235      * can look shifted.
236      *
237      * No combination of rotate with flip, scaling or decimation is possible
238      * if buffer is unaligned.
239      */
240     if(has_transform && (img_dsc->header.w % 16 || img_dsc->header.h % 16))
241         return false;
242 
243     return true;
244 }
245 
_pxp_evaluate(lv_draw_unit_t * u,lv_draw_task_t * t)246 static int32_t _pxp_evaluate(lv_draw_unit_t * u, lv_draw_task_t * t)
247 {
248     LV_UNUSED(u);
249 
250     const lv_draw_dsc_base_t * draw_dsc_base = (lv_draw_dsc_base_t *) t->draw_dsc;
251 
252     if(!_pxp_dest_cf_supported(draw_dsc_base->layer->color_format))
253         return 0;
254 
255     switch(t->type) {
256         case LV_DRAW_TASK_TYPE_FILL: {
257                 const lv_draw_fill_dsc_t * draw_dsc = (lv_draw_fill_dsc_t *) t->draw_dsc;
258 
259                 /* Most simple case: just a plain rectangle (no radius, no gradient). */
260                 if((draw_dsc->radius != 0) || (draw_dsc->grad.dir != (lv_grad_dir_t)LV_GRAD_DIR_NONE))
261                     return 0;
262 
263                 if(t->preference_score > 70) {
264                     t->preference_score = 70;
265                     t->preferred_draw_unit_id = DRAW_UNIT_ID_PXP;
266                 }
267                 return 1;
268             }
269 
270         case LV_DRAW_TASK_TYPE_LAYER: {
271                 const lv_draw_image_dsc_t * draw_dsc = (lv_draw_image_dsc_t *) t->draw_dsc;
272                 lv_layer_t * layer_to_draw = (lv_layer_t *)draw_dsc->src;
273 
274                 if(!_pxp_src_cf_supported(layer_to_draw->color_format))
275                     return 0;
276 
277                 if(!_pxp_draw_img_supported(draw_dsc))
278                     return 0;
279 
280                 if(t->preference_score > 70) {
281                     t->preference_score = 70;
282                     t->preferred_draw_unit_id = DRAW_UNIT_ID_PXP;
283                 }
284                 return 1;
285             }
286 
287         case LV_DRAW_TASK_TYPE_IMAGE: {
288                 lv_draw_image_dsc_t * draw_dsc = (lv_draw_image_dsc_t *) t->draw_dsc;
289                 const lv_image_dsc_t * img_dsc = draw_dsc->src;
290 
291                 if(img_dsc->header.cf >= LV_COLOR_FORMAT_PROPRIETARY_START)
292                     return 0;
293 
294                 if(draw_dsc->tile)
295                     return 0;
296 
297                 if((!_pxp_src_cf_supported(img_dsc->header.cf)) ||
298                    (!pxp_buf_aligned(img_dsc->data, img_dsc->header.stride)))
299                     return 0;
300 
301                 if(!_pxp_draw_img_supported(draw_dsc))
302                     return 0;
303 
304                 if(t->preference_score > 70) {
305                     t->preference_score = 70;
306                     t->preferred_draw_unit_id = DRAW_UNIT_ID_PXP;
307                 }
308                 return 1;
309             }
310         default:
311             return 0;
312     }
313 
314     return 0;
315 }
316 
_pxp_dispatch(lv_draw_unit_t * draw_unit,lv_layer_t * layer)317 static int32_t _pxp_dispatch(lv_draw_unit_t * draw_unit, lv_layer_t * layer)
318 {
319     lv_draw_pxp_unit_t * draw_pxp_unit = (lv_draw_pxp_unit_t *) draw_unit;
320 
321     /* Return immediately if it's busy with draw task. */
322     if(draw_pxp_unit->task_act)
323         return 0;
324 
325     /* Try to get an ready to draw. */
326     lv_draw_task_t * t = lv_draw_get_next_available_task(layer, NULL, DRAW_UNIT_ID_PXP);
327 
328     if(t == NULL || t->preferred_draw_unit_id != DRAW_UNIT_ID_PXP)
329         return LV_DRAW_UNIT_IDLE;
330 
331     if(lv_draw_layer_alloc_buf(layer) == NULL)
332         return LV_DRAW_UNIT_IDLE;
333 
334     t->state = LV_DRAW_TASK_STATE_IN_PROGRESS;
335     draw_pxp_unit->base_unit.target_layer = layer;
336     draw_pxp_unit->base_unit.clip_area = &t->clip_area;
337     draw_pxp_unit->task_act = t;
338 
339 #if LV_USE_PXP_DRAW_THREAD
340     /* Let the render thread work. */
341     if(draw_pxp_unit->inited)
342         lv_thread_sync_signal(&draw_pxp_unit->sync);
343 #else
344     _pxp_execute_drawing(draw_pxp_unit);
345 
346     draw_pxp_unit->task_act->state = LV_DRAW_TASK_STATE_READY;
347     draw_pxp_unit->task_act = NULL;
348 
349     /* The draw unit is free now. Request a new dispatching as it can get a new task. */
350     lv_draw_dispatch_request();
351 #endif
352 
353     return 1;
354 }
355 
_pxp_delete(lv_draw_unit_t * draw_unit)356 static int32_t _pxp_delete(lv_draw_unit_t * draw_unit)
357 {
358 #if LV_USE_PXP_DRAW_THREAD
359     lv_draw_pxp_unit_t * draw_pxp_unit = (lv_draw_pxp_unit_t *) draw_unit;
360 
361     LV_LOG_INFO("Cancel PXP draw thread.");
362     draw_pxp_unit->exit_status = true;
363 
364     if(draw_pxp_unit->inited)
365         lv_thread_sync_signal(&draw_pxp_unit->sync);
366 
367     lv_result_t res = lv_thread_delete(&draw_pxp_unit->thread);
368 
369     return res;
370 #else
371     LV_UNUSED(draw_unit);
372 
373     return 0;
374 #endif
375 }
376 
_pxp_execute_drawing(lv_draw_pxp_unit_t * u)377 static void _pxp_execute_drawing(lv_draw_pxp_unit_t * u)
378 {
379     lv_draw_task_t * t = u->task_act;
380     lv_draw_unit_t * draw_unit = (lv_draw_unit_t *)u;
381     lv_layer_t * layer = draw_unit->target_layer;
382     lv_draw_buf_t * draw_buf = layer->draw_buf;
383 
384     lv_area_t draw_area;
385     if(!lv_area_intersect(&draw_area, &t->area, draw_unit->clip_area))
386         return; /*Fully clipped, nothing to do*/
387 
388     /* Make area relative to the buffer */
389     lv_area_move(&draw_area, -layer->buf_area.x1, -layer->buf_area.y1);
390 
391     /* Invalidate only the drawing area */
392     lv_draw_buf_invalidate_cache(draw_buf, &draw_area);
393 
394     switch(t->type) {
395         case LV_DRAW_TASK_TYPE_FILL:
396             lv_draw_pxp_fill(draw_unit, t->draw_dsc, &t->area);
397             break;
398         case LV_DRAW_TASK_TYPE_IMAGE:
399             lv_draw_pxp_img(draw_unit, t->draw_dsc, &t->area);
400             break;
401         case LV_DRAW_TASK_TYPE_LAYER:
402             lv_draw_pxp_layer(draw_unit, t->draw_dsc, &t->area);
403             break;
404         default:
405             break;
406     }
407 
408 #if LV_USE_PARALLEL_DRAW_DEBUG
409     /*Layers manage it for themselves*/
410     if(t->type != LV_DRAW_TASK_TYPE_LAYER) {
411         lv_area_t draw_area;
412         if(!lv_area_intersect(&draw_area, &t->area, u->base_unit.clip_area))
413             return;
414 
415         int32_t idx = 0;
416         lv_draw_unit_t * draw_unit_tmp = _draw_info.unit_head;
417         while(draw_unit_tmp != (lv_draw_unit_t *)u) {
418             draw_unit_tmp = draw_unit_tmp->next;
419             idx++;
420         }
421         lv_draw_rect_dsc_t rect_dsc;
422         lv_draw_rect_dsc_init(&rect_dsc);
423         rect_dsc.bg_color = lv_palette_main(idx % LV_PALETTE_LAST);
424         rect_dsc.border_color = rect_dsc.bg_color;
425         rect_dsc.bg_opa = LV_OPA_10;
426         rect_dsc.border_opa = LV_OPA_80;
427         rect_dsc.border_width = 1;
428         lv_draw_sw_fill((lv_draw_unit_t *)u, &rect_dsc, &draw_area);
429 
430         lv_point_t txt_size;
431         lv_text_get_size(&txt_size, "W", LV_FONT_DEFAULT, 0, 0, 100, LV_TEXT_FLAG_NONE);
432 
433         lv_area_t txt_area;
434         txt_area.x1 = draw_area.x1;
435         txt_area.y1 = draw_area.y1;
436         txt_area.x2 = draw_area.x1 + txt_size.x - 1;
437         txt_area.y2 = draw_area.y1 + txt_size.y - 1;
438 
439         lv_draw_rect_dsc_init(&rect_dsc);
440         rect_dsc.bg_color = lv_color_white();
441         lv_draw_sw_fill((lv_draw_unit_t *)u, &rect_dsc, &txt_area);
442 
443         char buf[8];
444         lv_snprintf(buf, sizeof(buf), "%d", idx);
445         lv_draw_label_dsc_t label_dsc;
446         lv_draw_label_dsc_init(&label_dsc);
447         label_dsc.color = lv_color_black();
448         label_dsc.text = buf;
449         lv_draw_sw_label((lv_draw_unit_t *)u, &label_dsc, &txt_area);
450     }
451 #endif
452 }
453 
454 #if LV_USE_PXP_DRAW_THREAD
_pxp_render_thread_cb(void * ptr)455 static void _pxp_render_thread_cb(void * ptr)
456 {
457     lv_draw_pxp_unit_t * u = ptr;
458 
459     lv_thread_sync_init(&u->sync);
460     u->inited = true;
461 
462     while(1) {
463         /* Wait for sync if there is no task set. */
464         while(u->task_act == NULL) {
465             if(u->exit_status)
466                 break;
467 
468             lv_thread_sync_wait(&u->sync);
469         }
470 
471         if(u->exit_status) {
472             LV_LOG_INFO("Ready to exit PXP draw thread.");
473             break;
474         }
475 
476         _pxp_execute_drawing(u);
477 
478         /* Signal the ready state to dispatcher. */
479         u->task_act->state = LV_DRAW_TASK_STATE_READY;
480 
481         /* Cleanup. */
482         u->task_act = NULL;
483 
484         /* The draw unit is free now. Request a new dispatching as it can get a new task. */
485         lv_draw_dispatch_request();
486     }
487 
488     u->inited = false;
489     lv_thread_sync_delete(&u->sync);
490     LV_LOG_INFO("Exit PXP draw thread.");
491 }
492 #endif
493 #endif /*LV_USE_DRAW_PXP*/
494 #endif /*LV_USE_DRAW_PXP || LV_USE_ROTATE_PXP*/
495 #endif /*LV_USE_PXP*/
496