microui  4.1.0
microui
ui_display_brs_predraw.c
1 /*
2  * Copyright 2023-2024 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_DISPLAY_BRS_PREDRAW comment
11  * @author MicroEJ Developer Team
12  * @version 4.1.0
13  */
14 
15 #include "ui_display_brs.h"
16 #if defined UI_DISPLAY_BRS && UI_DISPLAY_BRS == UI_DISPLAY_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_DISPLAY_BRS_PREDRAW.
32  */
33 #ifndef UI_DISPLAY_BRS_DRAWING_BUFFER_COUNT
34 #error "Require the available number of display buffers (back and front buffers)."
35 #elif UI_DISPLAY_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_DISPLAY_BRS_DRAWING_BUFFER_COUNT > 1u
47 static ui_rect_collection_t dirty_regions[UI_DISPLAY_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_DISPLAY_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_DISPLAY_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_DISPLAY_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  LOG_REGION(UI_LOG_BRS_RemoveRegion, r);
93  UI_RECT_mark_empty(r);
94  }
95  r++;
96  }
97  }
98 }
99 
100 /*
101  * This function adds the region to the regions to restore.
102  */
103 static void _add_drawing_region(MICROUI_GraphicsContext *gc, ui_rect_t *region) {
104  for (uint32_t i = 0u; i < (UI_DISPLAY_BRS_DRAWING_BUFFER_COUNT - 1u); i++) {
105  ui_rect_t *previous = UI_RECT_COLLECTION_get_last(&dirty_regions[i]);
106  if ((NULL == previous) || !UI_RECT_contains_rect(previous, region)) {
107  // add the dirty region if and only if previous dirty region does not
108  // include the new dirty region
109  ui_rect_t new_region;
110  if (!UI_RECT_COLLECTION_is_full(&dirty_regions[i])) {
111  new_region = *region;
112  } else {
113  // too many rectangles: replace them by only one whose fits the full display
114  LLTRACE_record_event_void(LLUI_EVENT_group, LLUI_EVENT_offset + UI_LOG_BRS_ClearList);
115  UI_RECT_COLLECTION_clear(&dirty_regions[i]);
116  new_region = UI_RECT_new_xyxy(0, 0, gc->image.width - 1, gc->image.height - 1);
117  }
118  LOG_REGION(UI_LOG_BRS_AddRegion, &new_region);
119  UI_RECT_COLLECTION_add_rect(&dirty_regions[i], new_region);
120  }
121  }
122 
123 #ifdef UI_DISPLAY_BRS_FLUSH_SINGLE_RECTANGLE
124  flush_bounds.x1 = MIN(flush_bounds.x1, region->x1);
125  flush_bounds.y1 = MIN(flush_bounds.y1, region->y1);
126  flush_bounds.x2 = MAX(flush_bounds.x2, region->x2);
127  flush_bounds.y2 = MAX(flush_bounds.y2, region->y2);
128 #endif // UI_DISPLAY_BRS_FLUSH_SINGLE_RECTANGLE
129 }
130 
131 /*
132  * @brief Checks if the past has to be restored and if the collection that represents
133  * the past has to be cleared.
134  *
135  * The past may be not restored if the new drawing region includes the past (no need to
136  * restore) or if the current back buffer is the same buffer than before last flush()
137  * (because it already contains the past).
138  *
139  * @param[in] gc the MicroUI GraphicsContext that targets the current back buffer
140  * @param[in] dirty_region the region of the next drawing.
141  * @param[out] clear_past true if the caller has to clear the past collection.
142  * @return true if the past has to be restored.
143  */
144 static bool _check_restore(MICROUI_GraphicsContext *gc, const ui_rect_t *dirty_region, bool *clear_past) {
145  bool restore;
146  *clear_past = false;
147 
148  if (!UI_RECT_COLLECTION_is_empty(&dirty_regions[index_dirty_region_to_restore])) {
149  // remains something to restore
150  restore = true;
151 
152  if (!UI_RECT_is_empty(dirty_regions[index_dirty_region_to_restore].data)) {
153  // very first call after a flush (not a SNI callback)
154 
155  if (LLUI_DISPLAY_getBufferAddress(LLUI_DISPLAY_getSourceImage(&gc->image)) !=
156  LLUI_DISPLAY_getBufferAddress(&gc->image)) {
157  // target a new buffer
158 
159  if ((dirty_region->x1 == 0) && (dirty_region->y1 == 0) && (dirty_region->x2 == (gc->image.width - 1)) &&
160  (dirty_region->y2 == (gc->image.height - 1))) {
161  // new dirty region fits the full screen; the "past" is useless
162  *clear_past = true;
163  restore = false;
164  }
165  } else {
166  // target the same buffer: restore is useless and just add the new dirty region (do not clear the past)
167  restore = false;
168  }
169  }
170  // else: continue the restore
171  } else {
172  // nothing to restore
173  restore = false;
174  }
175  return restore;
176 }
177 
178 /*
179  * @brief Restores the rectangles stored in the array of 4 rectangles (a region previously splitted in four
180  * rectangles).
181  *
182  * @param[in] gc the MicroUI GraphicsContext that targets the current back buffer
183  * @param[in] previous_buffer the MicroUI Image that targets the current front buffer
184  * @return DRAWING_RUNNING if the a restoration is step is running or DRAWING_DONE if the restoration
185  * is fully completed.
186  */
187 static DRAWING_Status _restore_sub_rect(MICROUI_GraphicsContext *gc, MICROUI_Image *previous_buffer) {
188  DRAWING_Status restore_status = DRAWING_DONE;
189  int ri = 0;
190  do {
191  ui_rect_t *r = &diff[ri];
192  if (!UI_RECT_is_empty(r)) {
193  LOG_REGION(UI_LOG_BRS_RestoreRegion, r);
194  restore_status = UI_DISPLAY_BRS_restore(gc, previous_buffer, r);
195  UI_RECT_mark_empty(r);
196  }
197  ri++;
198  } while ((DRAWING_DONE == restore_status) && (ri < 4));
199  return restore_status;
200 }
201 
202 /*
203  * @brief Prepares the back buffer before the very first drawing, which consists to
204  * restore the past if it is required (see _check_restore()).
205  *
206  * The restoration steps are symbolized by a collection of rectangles. The restoration
207  * of one rectangle might be asynchronous. In that case, this function launches a
208  * restoration step and returns false. The complete restoration will be completed
209  * until this function returns true.
210  *
211  * @param[in] gc the MicroUI GraphicsContext that targets the current back buffer
212  * @param[in] dirty_region the region of the next drawing.
213  * @return DRAWING_RUNNING if the a restoration is step is running or DRAWING_DONE if the restoration
214  * is fully completed and the region added as region to restore
215  */
216 static DRAWING_Status _prepare_back_buffer(MICROUI_GraphicsContext *gc, ui_rect_t *dirty_region) {
217  DRAWING_Status restore_status = DRAWING_DONE;
218  bool clear_past = false;
219 
220  if (_check_restore(gc, dirty_region, &clear_past)) {
221  MICROUI_Image *previous_buffer = LLUI_DISPLAY_getSourceImage(&gc->image);
222  LLUI_DISPLAY_configureClip(gc, false); // regions to restore can be fully out of clip
223 
224  // restore sub parts of previous restoration (if any)
225  restore_status = _restore_sub_rect(gc, previous_buffer);
226 
227  if (DRAWING_DONE == restore_status) {
228  ui_rect_t *r = dirty_regions[index_dirty_region_to_restore].data;
229  while ((DRAWING_DONE == restore_status) &&
230  (r != UI_RECT_COLLECTION_get_end(&dirty_regions[index_dirty_region_to_restore]))) {
231  if (!UI_RECT_is_empty(r)) {
232  // split (if required) the current region in sub-parts
233  if (0u < UI_RECT_subtract(diff, r, dirty_region)) {
234  // restore sub-part(s)
235  restore_status = _restore_sub_rect(gc, previous_buffer);
236  }
237 
238  // current region is now restored or is currently in restore (thanks to array of sub-parts)
239  UI_RECT_mark_empty(r);
240  }
241  r++;
242  }
243 
244  // do not clear the past if a restoration step is running
245  clear_past = DRAWING_DONE == restore_status;
246  }
247  // else: the restoration of a sub-part is running
248  }
249 
250  if (clear_past) {
251  UI_RECT_COLLECTION_clear(&dirty_regions[index_dirty_region_to_restore]);
252 #if UI_DISPLAY_BRS_DRAWING_BUFFER_COUNT > 1
253  index_dirty_region_to_restore++;
254  index_dirty_region_to_restore %= (uint32_t)UI_DISPLAY_BRS_DRAWING_BUFFER_COUNT - 1u;
255 #endif
256  }
257 
258  if (DRAWING_DONE == restore_status) {
259  // back buffer is considered as ready when it is fully restored
260  backbuffer_ready = true;
261 
262  // add the new region as a region to restore
263  _add_drawing_region(gc, dirty_region);
264  }
265 
266  return restore_status;
267 }
268 
269 // --------------------------------------------------------------------------------
270 // LLUI_DISPLAY_impl.h API
271 // --------------------------------------------------------------------------------
272 /*
273  * @brief See the header file for the function documentation.
274  *
275  * This function restores the back buffer if required and store the new region as new
276  * region to restore after next call to flush().
277  */
278 DRAWING_Status LLUI_DISPLAY_IMPL_newDrawingRegion(MICROUI_GraphicsContext *gc, ui_rect_t *region, bool drawing_now) {
279  LOG_REGION(UI_LOG_BRS_NewDrawing, region);
280 
281  DRAWING_Status ret = DRAWING_DONE;
282 
283  if (!backbuffer_ready) {
284  if (!drawing_now) {
285  _remove_drawing_regions(gc, region);
286  } else {
287  ret = _prepare_back_buffer(gc, region);
288  }
289  } else {
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_DISPLAY_BRS_FLUSH_SINGLE_RECTANGLE
303 
304  LLTRACE_record_event_u32x6(LLUI_EVENT_group, LLUI_EVENT_offset + UI_LOG_BRS_FlushSingle, flushIdentifier,
305  (uint32_t)LLUI_DISPLAY_getBufferAddress(&gc->image), flush_bounds.x1, flush_bounds.y1,
306  flush_bounds.x2, flush_bounds.y2);
307 
308  // refresh the LCD; use the flush_bounds as dirty region (includes all dirty regions)
309  LLUI_DISPLAY_IMPL_flush(gc, flushIdentifier, &flush_bounds, 1);
310 
311  // reset flush bounds to no flush again if there is no drawing until next flush
312  flush_bounds.x1 = INT16_MAX;
313  flush_bounds.y1 = INT16_MAX;
314  flush_bounds.x2 = 0;
315  flush_bounds.y2 = 0;
316 
317 #else // UI_DISPLAY_BRS_FLUSH_SINGLE_RECTANGLE
318 
319  size_t size = UI_RECT_COLLECTION_get_length(&dirty_regions[index_dirty_region_to_restore]);
320  if (1u == size) {
321  ui_rect_t *rect = &dirty_regions[index_dirty_region_to_restore].data[0];
322  LLTRACE_record_event_u32x6(LLUI_EVENT_group, LLUI_EVENT_offset + UI_LOG_BRS_FlushSingle, flushIdentifier,
323  (uint32_t)LLUI_DISPLAY_getBufferAddress(&gc->image), rect->x1, rect->y1, rect->x2,
324  rect->y2);
325  } else {
326  LLTRACE_record_event_u32x3(LLUI_EVENT_group, LLUI_EVENT_offset + UI_LOG_BRS_FlushMulti, flushIdentifier,
327  (uint32_t)LLUI_DISPLAY_getBufferAddress(&gc->image), size);
328  }
329 
330  LLUI_DISPLAY_IMPL_flush(gc, flushIdentifier, dirty_regions[index_dirty_region_to_restore].data,
331  UI_RECT_COLLECTION_get_length(&dirty_regions[index_dirty_region_to_restore]));
332 
333 #endif // UI_DISPLAY_BRS_FLUSH_SINGLE_RECTANGLE
334 
335  backbuffer_ready = false;
336  return DRAWING_DONE;
337 }
338 
339 #endif // UI_DISPLAY_BRS_PREDRAW
340 
341 // --------------------------------------------------------------------------------
342 // EOF
343 // --------------------------------------------------------------------------------