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