microui  14.4.1
microui
ui_display_brs_predraw.c
1 /*
2  * Copyright 2023-2025 MicroEJ Corp. All rights reserved.
3  * Use of this source code is governed by a BSD-style license that can be found with this software.
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.4.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 #ifdef UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
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 #ifdef UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
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 
269  DRAWING_Status ret = DRAWING_DONE;
270 
271  if (!backbuffer_ready) {
272  // first region after a flush
273  if (!drawing_now) {
274  // region without drawing: no need to restore its content (it will be erased later)
275  _remove_drawing_regions(gc, region);
276  } else {
277  // region with drawing: have to restore
278  ret = _prepare_back_buffer(gc, region);
279 
280  if (DRAWING_DONE == ret) {
281  // back buffer is considered as ready when it is fully restored
282  backbuffer_ready = true;
283 
284  // add the new region as a region to restore
285  _add_drawing_region(gc, region);
286  }
287  }
288  } else {
289  // new region: add it to the pool of regions to restore after next flush
290  _add_drawing_region(gc, region); // don't care if drawing now or not
291  }
292 
293  return ret;
294 }
295 
296 /*
297  * @brief See the header file for the function documentation.
298  *
299  * This function calls LLUI_DISPLAY_IMPL_flush() with the rectangle that includes all dirty regions.
300  */
301 DRAWING_Status LLUI_DISPLAY_IMPL_refresh(MICROUI_GraphicsContext *gc, uint8_t flushIdentifier) {
302 #ifdef UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
303 
304  UI_LOG_START(BRS_FlushSingle, flushIdentifier, UI_LOG_BUFFER(&gc->image), flush_bounds.x1, flush_bounds.y1,
305  UI_RECT_get_width(&flush_bounds), UI_RECT_get_height(&flush_bounds));
306 
307  // refresh the LCD; use the flush_bounds as dirty region (includes all dirty regions)
308  LLUI_DISPLAY_IMPL_flush(gc, flushIdentifier, &flush_bounds, 1);
309 
310  // reset flush bounds to no flush again if there is no drawing until next flush
311  flush_bounds.x1 = INT16_MAX;
312  flush_bounds.y1 = INT16_MAX;
313  flush_bounds.x2 = 0;
314  flush_bounds.y2 = 0;
315 
316 #else // UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
317 
318  size_t size = UI_RECT_COLLECTION_get_length(&dirty_regions[index_dirty_region_to_restore]);
319  if (1u == size) {
320  ui_rect_t *rect = &dirty_regions[index_dirty_region_to_restore].data[0];
321  UI_LOG_START(BRS_FlushSingle, flushIdentifier, UI_LOG_BUFFER(&gc->image), rect->x1, rect->y1, UI_RECT_get_width(rect),
322  UI_RECT_get_height(rect));
323  } else {
324  UI_LOG_START(BRS_FlushMulti, flushIdentifier, UI_LOG_BUFFER(&gc->image), size);
325  }
326 
327  LLUI_DISPLAY_IMPL_flush(gc, flushIdentifier, dirty_regions[index_dirty_region_to_restore].data,
328  UI_RECT_COLLECTION_get_length(&dirty_regions[index_dirty_region_to_restore]));
329 
330 #endif // UI_FEATURE_BRS_FLUSH_SINGLE_RECTANGLE
331 
332  backbuffer_ready = false;
333  return DRAWING_DONE;
334 }
335 
336 #endif // UI_FEATURE_BRS_PREDRAW
337 
338 // --------------------------------------------------------------------------------
339 // EOF
340 // --------------------------------------------------------------------------------
#define UI_FEATURE_BRS_DRAWING_BUFFER_COUNT
Defines the available number of drawing buffers; in other words, the number of buffers the Graphics E...