microui  14.5.1
microui
ui_display_brs_predraw.c
1 /*
2  * Copyright 2023-2025 MicroEJ Corp. All rights reserved.
3  * MicroEJ Corp. PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
4  */
5 
6 /*
7  * @file
8  * @brief This file implements all the LLUI_DISPLAY_impl.h functions relating to the
9  * display buffer strategy (BRS) "pre draw"
10  * @see UI_FEATURE_BRS_PREDRAW comment
11  * @author MicroEJ Developer Team
12  * @version 14.5.1
13  */
14 
15 #include "ui_display_brs.h"
16 #if defined UI_FEATURE_BRS && UI_FEATURE_BRS == UI_FEATURE_BRS_PREDRAW
17 
18 // --------------------------------------------------------------------------------
19 // Includes
20 // --------------------------------------------------------------------------------
21 
22 #include "ui_rect_collection.h"
23 #include "ui_rect_util.h"
24 
25 // --------------------------------------------------------------------------------
26 // Defines
27 // --------------------------------------------------------------------------------
28 
29 /*
30  * @brief This strategy requires the available number of back buffers.
31  * @see the comment of UI_FEATURE_BRS_PREDRAW.
32  */
33 #ifndef UI_FEATURE_BRS_DRAWING_BUFFER_COUNT
34 #error "Require the available number of display buffers (back and front buffers)."
35 #elif UI_FEATURE_BRS_DRAWING_BUFFER_COUNT < 2
36 #warning "This strategy is not optimized for less than two buffers."
37 #endif
38 
39 // --------------------------------------------------------------------------------
40 // Private fields
41 // --------------------------------------------------------------------------------
42 
43 /*
44  * @brief A collection of dirty regions is kept for each back buffers.
45  */
46 #if UI_FEATURE_BRS_DRAWING_BUFFER_COUNT > 1u
47 static ui_rect_collection_t dirty_regions[UI_FEATURE_BRS_DRAWING_BUFFER_COUNT - 1u] = { 0 };
48 #else
49 static ui_rect_collection_t dirty_regions[1u] = { 0 };
50 #endif
51 
52 /*
53  * @brief Index of the back buffer's collection to restore in the new back buffer.
54  */
55 static uint32_t index_dirty_region_to_restore = 0;
56 
57 static bool backbuffer_ready = true;
58 
59 ui_rect_t diff[4] = { UI_RECT_EMPTY, UI_RECT_EMPTY, UI_RECT_EMPTY, UI_RECT_EMPTY };
60 
61 #if defined UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE && (UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE == 1)
62 
63 /*
64  * @brief Rectangle given to LLUI_DISPLAY_IMPL_flush(): it includes all dirty regions since
65  * last call to flush().
66  */
67 static ui_rect_t flush_bounds = {
68  .x1 = INT16_MAX,
69  .y1 = INT16_MAX,
70  .x2 = 0,
71  .y2 = 0,
72 };
73 
74 #endif // UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
75 
76 // --------------------------------------------------------------------------------
77 // Private functions
78 // --------------------------------------------------------------------------------
79 
80 /*
81  * This function removes all regions saved in the next collection to restore in the
82  * new back buffer if they are included in the new region.
83  */
84 static void _remove_drawing_regions(MICROUI_GraphicsContext *gc, ui_rect_t *region) {
85  (void)gc;
86 
87  for (uint32_t i = 0u; i < (UI_FEATURE_BRS_DRAWING_BUFFER_COUNT - 1u); i++) {
88  ui_rect_t *r = dirty_regions[i].data;
89  while (r != UI_RECT_COLLECTION_get_end(&dirty_regions[i])) {
90  if (!UI_RECT_is_empty(r) && UI_RECT_contains_rect(region, r)) {
91  // this region will be re-drawn: remove it from the restoration array
92  UI_RECT_mark_empty(r);
93  }
94  r++;
95  }
96  }
97 }
98 
99 /*
100  * This function adds the region to the regions to restore.
101  */
102 static void _add_drawing_region(MICROUI_GraphicsContext *gc, ui_rect_t *region) {
103  for (uint32_t i = 0u; i < (UI_FEATURE_BRS_DRAWING_BUFFER_COUNT - 1u); i++) {
104  ui_rect_t *previous = UI_RECT_COLLECTION_get_last(&dirty_regions[i]);
105  if ((NULL == previous) || !UI_RECT_contains_rect(previous, region)) {
106  // add the dirty region if and only if previous dirty region does not
107  // include the new dirty region
108  ui_rect_t new_region;
109  if (!UI_RECT_COLLECTION_is_full(&dirty_regions[i])) {
110  new_region = *region;
111  } else {
112  // too many rectangles: replace them by only one whose fits the full display
113  UI_RECT_COLLECTION_clear(&dirty_regions[i]);
114  new_region = UI_RECT_new_xyxy(0, 0, gc->image.width - 1, gc->image.height - 1);
115  }
116  UI_RECT_COLLECTION_add_rect(&dirty_regions[i], new_region);
117  }
118  }
119 
120 #if defined UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE && (UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE == 1)
121  flush_bounds.x1 = MIN(flush_bounds.x1, region->x1);
122  flush_bounds.y1 = MIN(flush_bounds.y1, region->y1);
123  flush_bounds.x2 = MAX(flush_bounds.x2, region->x2);
124  flush_bounds.y2 = MAX(flush_bounds.y2, region->y2);
125 #endif // UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
126 }
127 
128 /*
129  * @brief Checks if the past has to be restored and if the collection that represents
130  * the past has to be cleared.
131  *
132  * The past may be not restored if the new drawing region includes the past (no need to
133  * restore) or if the current back buffer is the same buffer than before last flush()
134  * (because it already contains the past).
135  *
136  * @param[in] gc the MicroUI GraphicsContext that targets the current back buffer
137  * @param[in] dirty_region the region of the next drawing.
138  * @param[out] clear_past true if the caller has to clear the past collection.
139  * @return true if the past has to be restored.
140  */
141 static bool _check_restore(MICROUI_GraphicsContext *gc, const ui_rect_t *dirty_region, bool *clear_past) {
142  bool restore;
143  *clear_past = false;
144 
145  if (!UI_RECT_COLLECTION_is_empty(&dirty_regions[index_dirty_region_to_restore])) {
146  // remains something to restore
147  restore = true;
148 
149  if (!UI_RECT_is_empty(dirty_regions[index_dirty_region_to_restore].data)) {
150  // very first call after a flush (not a SNI callback)
151 
152  if (LLUI_DISPLAY_getBufferAddress(LLUI_DISPLAY_getSourceImage(&gc->image)) !=
153  LLUI_DISPLAY_getBufferAddress(&gc->image)) {
154  // target a new buffer
155 
156  if ((dirty_region->x1 == 0) && (dirty_region->y1 == 0) && (dirty_region->x2 == (gc->image.width - 1)) &&
157  (dirty_region->y2 == (gc->image.height - 1))) {
158  // new dirty region fits the full screen; the "past" is useless
159  *clear_past = true;
160  restore = false;
161  }
162  } else {
163  // target the same buffer: restore is useless and just add the new dirty region (do not clear the past)
164  restore = false;
165  }
166  }
167  // else: continue the restore
168  } else {
169  // nothing to restore
170  restore = false;
171  }
172  return restore;
173 }
174 
175 /*
176  * @brief Restores the rectangles stored in the array of 4 rectangles (a region previously splitted in four
177  * rectangles).
178  *
179  * @param[in] gc the MicroUI GraphicsContext that targets the current back buffer
180  * @param[in] previous_buffer the MicroUI Image that targets the current front buffer
181  * @return DRAWING_RUNNING if the a restoration is step is running or DRAWING_DONE if the restoration
182  * is fully completed.
183  */
184 static DRAWING_Status _restore_sub_rect(MICROUI_GraphicsContext *gc, MICROUI_Image *previous_buffer) {
185  DRAWING_Status restore_status = DRAWING_DONE;
186  int ri = 0;
187  do {
188  ui_rect_t *r = &diff[ri];
189  if (!UI_RECT_is_empty(r)) {
190  UI_LOG_START(BRS_RestoreRegion, r->x1, r->y1, UI_RECT_get_width(r), UI_RECT_get_height(r));
191  restore_status = UI_DISPLAY_BRS_restore(gc, previous_buffer, r);
192  UI_RECT_mark_empty(r);
193  }
194  ri++;
195  } while ((DRAWING_DONE == restore_status) && (ri < 4));
196  return restore_status;
197 }
198 
199 /*
200  * @brief Prepares the back buffer before the very first drawing, which consists to
201  * restore the past if it is required (see _check_restore()).
202  *
203  * The restoration steps are symbolized by a collection of rectangles. The restoration
204  * of one rectangle might be asynchronous. In that case, this function launches a
205  * restoration step and returns false. The complete restoration will be completed
206  * until this function returns true.
207  *
208  * @param[in] gc the MicroUI GraphicsContext that targets the current back buffer
209  * @param[in] dirty_region the region of the next drawing.
210  * @return DRAWING_RUNNING if the a restoration is step is running or DRAWING_DONE if the restoration
211  * is fully completed and the region added as region to restore
212  */
213 static DRAWING_Status _prepare_back_buffer(MICROUI_GraphicsContext *gc, ui_rect_t *dirty_region) {
214  DRAWING_Status restore_status = DRAWING_DONE;
215  bool clear_past = false;
216 
217  if (_check_restore(gc, dirty_region, &clear_past)) {
218  MICROUI_Image *previous_buffer = LLUI_DISPLAY_getSourceImage(&gc->image);
219  LLUI_DISPLAY_configureClip(gc, false); // regions to restore can be fully out of clip
220 
221  // restore sub parts of previous restoration (if any)
222  restore_status = _restore_sub_rect(gc, previous_buffer);
223 
224  if (DRAWING_DONE == restore_status) {
225  ui_rect_t *r = dirty_regions[index_dirty_region_to_restore].data;
226  while ((DRAWING_DONE == restore_status) &&
227  (r != UI_RECT_COLLECTION_get_end(&dirty_regions[index_dirty_region_to_restore]))) {
228  if (!UI_RECT_is_empty(r)) {
229  // split (if required) the current region in sub-parts
230  if (0u < UI_RECT_subtract(diff, r, dirty_region)) {
231  // restore sub-part(s)
232  restore_status = _restore_sub_rect(gc, previous_buffer);
233  }
234 
235  // current region is now restored or is currently in restore (thanks to array of sub-parts)
236  UI_RECT_mark_empty(r);
237  }
238  r++;
239  }
240 
241  // do not clear the past if a restoration step is running
242  clear_past = DRAWING_DONE == restore_status;
243  }
244  // else: the restoration of a sub-part is running
245  }
246 
247  if (clear_past) {
248  UI_RECT_COLLECTION_clear(&dirty_regions[index_dirty_region_to_restore]);
249 #if UI_FEATURE_BRS_DRAWING_BUFFER_COUNT > 1
250  index_dirty_region_to_restore++;
251  index_dirty_region_to_restore %= (uint32_t)UI_FEATURE_BRS_DRAWING_BUFFER_COUNT - 1u;
252 #endif
253  }
254 
255  return restore_status;
256 }
257 
258 // --------------------------------------------------------------------------------
259 // LLUI_DISPLAY_impl.h API
260 // --------------------------------------------------------------------------------
261 /*
262  * @brief See the header file for the function documentation.
263  *
264  * This function restores the back buffer if required and store the new region as new
265  * region to restore after next call to flush().
266  */
267 DRAWING_Status LLUI_DISPLAY_IMPL_newDrawingRegion(MICROUI_GraphicsContext *gc, ui_rect_t *region, bool drawing_now) {
268  DRAWING_Status ret = DRAWING_DONE;
269 
270  if (!backbuffer_ready) {
271  // first region after a flush
272  if (!drawing_now) {
273  // region without drawing: no need to restore its content (it will be erased later)
274  _remove_drawing_regions(gc, region);
275  } else {
276  // region with drawing: have to restore
277  ret = _prepare_back_buffer(gc, region);
278 
279  if (DRAWING_DONE == ret) {
280  // back buffer is considered as ready when it is fully restored
281  backbuffer_ready = true;
282 
283  // add the new region as a region to restore
284  _add_drawing_region(gc, region);
285  }
286  }
287  } else {
288  // new region: add it to the pool of regions to restore after next flush
289  _add_drawing_region(gc, region); // don't care if drawing now or not
290  }
291 
292  return ret;
293 }
294 
295 /*
296  * @brief See the header file for the function documentation.
297  *
298  * This function calls LLUI_DISPLAY_IMPL_flush() with the rectangle that includes all dirty regions.
299  */
300 DRAWING_Status LLUI_DISPLAY_IMPL_refresh(MICROUI_GraphicsContext *gc, uint8_t flushIdentifier) {
301 #if defined UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE && (UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE == 1)
302 
303  UI_LOG_START(BRS_FlushSingle, flushIdentifier, UI_LOG_BUFFER(&gc->image), flush_bounds.x1, flush_bounds.y1,
304  UI_RECT_get_width(&flush_bounds), UI_RECT_get_height(&flush_bounds));
305 
306  // refresh the LCD; use the flush_bounds as dirty region (includes all dirty regions)
307  LLUI_DISPLAY_IMPL_flush(gc, flushIdentifier, &flush_bounds, 1);
308 
309  // reset flush bounds to no flush again if there is no drawing until next flush
310  flush_bounds.x1 = INT16_MAX;
311  flush_bounds.y1 = INT16_MAX;
312  flush_bounds.x2 = 0;
313  flush_bounds.y2 = 0;
314 
315 #else // UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
316 
317  size_t size = UI_RECT_COLLECTION_get_length(&dirty_regions[index_dirty_region_to_restore]);
318  if (1u == size) {
319  ui_rect_t *rect = &dirty_regions[index_dirty_region_to_restore].data[0];
320  UI_LOG_START(BRS_FlushSingle, flushIdentifier, UI_LOG_BUFFER(&gc->image), rect->x1, rect->y1,
321  UI_RECT_get_width(rect), UI_RECT_get_height(rect));
322  } else {
323  UI_LOG_START(BRS_FlushMulti, flushIdentifier, UI_LOG_BUFFER(&gc->image), size);
324  }
325 
326  LLUI_DISPLAY_IMPL_flush(gc, flushIdentifier, dirty_regions[index_dirty_region_to_restore].data,
327  UI_RECT_COLLECTION_get_length(&dirty_regions[index_dirty_region_to_restore]));
328 
329 #endif // UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
330 
331  backbuffer_ready = false;
332  return DRAWING_DONE;
333 }
334 
335 #endif // UI_FEATURE_BRS_PREDRAW
336 
337 // --------------------------------------------------------------------------------
338 // EOF
339 // --------------------------------------------------------------------------------
#define UI_FEATURE_BRS_DRAWING_BUFFER_COUNT
Defines the available number of drawing buffers; in other words, the number of buffers the Graphics E...