roo_display
API Documentation for roo_display
Loading...
Searching...
No Matches
background_fill_optimizer.cpp
Go to the documentation of this file.
2
3namespace roo_display {
4
5namespace {
6
7// Adapter to use writer as filler, for a single rectangle.
8struct RectFillWriter {
9 Color color;
10 BufferedRectWriter& writer;
11 void fillRect(int16_t x0, int16_t y0, int16_t x1, int16_t y1) const {
12 writer.writeRect(x0, y0, x1, y1, color);
13 }
14};
15
16inline uint8_t getIdxInPalette(Color color, const Color* palette,
17 uint8_t palette_size) {
18 for (int i = 0; i < palette_size; ++i) {
19 if (color == palette[i]) return i + 1;
20 }
21 return 0;
22}
23
24} // namespace
25
27 : FrameBuffer(width, height,
28 new roo::byte[FrameBuffer::SizeForDimensions(width, height)],
29 true) {
30 invalidate();
31}
32
34 roo::byte* buffer)
35 : FrameBuffer(width, height, buffer, false) {}
36
38 roo::byte* buffer,
39 bool owns_buffer)
40 : background_mask_(buffer, ((((width - 1) / kBlock + 1) + 1) / 2),
41 ((((height - 1) / kBlock + 1) + 1) / 2) * 2),
42 swap_xy_(false),
43 owned_buffer_(owns_buffer ? buffer : nullptr) {}
44
47 assert(palette_size <= 15);
48 std::copy(palette, palette + palette_size, palette_);
49 palette_size_ = palette_size;
50 pinned_palette_size_ = palette_size;
51 resetPendingDynamicPaletteColor();
52 resetMaskAndUsage(0);
53}
54
55void BackgroundFillOptimizer::setPalette(std::initializer_list<Color> palette) {
56 assert(palette.size() <= 15);
57 std::copy(palette.begin(), palette.end(), palette_);
58 palette_size_ = palette.size();
59 pinned_palette_size_ = palette.size();
60 resetPendingDynamicPaletteColor();
61 resetMaskAndUsage(0);
62}
63
64void BackgroundFillOptimizer::setPrefilled(Color color) {
65 uint8_t idx = getIdxInPalette(color, palette_, palette_size_);
66 if (idx == 0) {
67 idx = tryAddIdxInPaletteDynamic(color);
68 }
69 resetMaskAndUsage(idx);
70}
71
73
74void BackgroundFillOptimizer::FrameBuffer::prefilled(uint8_t idx_in_palette) {
75 background_mask_.fillRect(
76 Box(0, 0, background_mask_.width() - 1, background_mask_.height() - 1),
78}
79
81 background_mask_.fillRect(Box(rect.xMin() / kBlock, rect.yMin() / kBlock,
82 rect.xMax() / kBlock, rect.yMax() / kBlock),
83 0);
84}
85
87 if (swap == swap_xy_) return;
88 swap_xy_ = swap;
89 background_mask_ = internal::NibbleRect(background_mask_.buffer(),
90 background_mask_.height() / 2,
91 background_mask_.width());
92 invalidate();
93}
94
96 FrameBuffer& buffer)
97 : output_(output),
98 background_mask_(&buffer.background_mask_),
99 palette_size_(0),
100 address_window_(0, 0, 0, 0),
101 cursor_x_(0),
102 cursor_y_(0),
103 cursor_ord_(0),
104 pinned_palette_size_(0),
105 palette_full_hint_(false),
106 has_pending_dynamic_palette_color_(false),
107 pending_dynamic_palette_color_(color::Transparent),
108 write_scan_state_(WriteScanState::kScan),
109 passthrough_address_set_(false),
110 scan_uniform_active_(false),
111 scan_uniform_color_(color::Transparent),
112 scan_uniform_start_y_(0),
113 scan_uniform_count_(0) {
114 resetMaskAndUsage(0);
115}
116
117// void BackgroundFillOptimizer::setPrefilled(Color color) {
118// resetMaskAndUsage(getIdxInPalette(color, palette_, palette_size_));
119// }
120
121bool BackgroundFillOptimizer::isReclaimablePaletteIdx(uint8_t idx) const {
122 return idx > pinned_palette_size_ && idx <= palette_size_;
123}
124
125void BackgroundFillOptimizer::recountMaskUsage() {
126 for (int i = 0; i < 16; ++i) palette_usage_count_[i] = 0;
127 const int16_t w = background_mask_->width();
128 const int16_t h = background_mask_->height();
129 for (int16_t y = 0; y < h; ++y) {
130 for (int16_t x = 0; x < w; ++x) {
131 ++palette_usage_count_[background_mask_->get(x, y)];
132 }
133 }
134
135 if (palette_size_ < 15) {
136 palette_full_hint_ = false;
137 return;
138 }
139
140 palette_full_hint_ = true;
141 for (uint8_t idx = pinned_palette_size_ + 1; idx <= palette_size_; ++idx) {
142 if (palette_usage_count_[idx] == 0) {
143 palette_full_hint_ = false;
144 return;
145 }
146 }
147}
148
149void BackgroundFillOptimizer::decrementPaletteUsage(uint8_t old_value) {
150 DCHECK_GT(palette_usage_count_[old_value], 0);
151 --palette_usage_count_[old_value];
152 if (palette_usage_count_[old_value] == 0 &&
153 isReclaimablePaletteIdx(old_value)) {
154 // LOG(INFO) << "Palette idx " << static_cast<int>(old_value - 1)
155 // << " is now reclaimable";
156 palette_full_hint_ = false;
157 }
158}
159
160void BackgroundFillOptimizer::incrementPaletteUsage(uint8_t new_value,
161 uint16_t count) {
162 palette_usage_count_[new_value] += count;
163}
164
165void BackgroundFillOptimizer::updateMaskValue(int16_t x, int16_t y,
166 uint8_t old_value,
167 uint8_t new_value) {
168 DCHECK_NE(old_value, new_value);
169 decrementPaletteUsage(old_value);
170 incrementPaletteUsage(new_value);
171 background_mask_->set(x, y, new_value);
172}
173
174void BackgroundFillOptimizer::fillMaskRect(const Box& box, uint8_t new_value) {
175 if (box.empty()) return;
176 const int16_t x0 = std::max<int16_t>(0, box.xMin());
177 const int16_t y0 = std::max<int16_t>(0, box.yMin());
178 const int16_t x1 =
179 std::min<int16_t>(background_mask_->width() - 1, box.xMax());
180 const int16_t y1 =
181 std::min<int16_t>(background_mask_->height() - 1, box.yMax());
182 if (x1 < x0 || y1 < y0) return;
183
184 for (int16_t y = y0; y <= y1; ++y) {
185 for (int16_t x = x0; x <= x1; ++x) {
186 decrementPaletteUsage(background_mask_->get(x, y));
187 }
188 }
189
190 background_mask_->fillRect(Box(x0, y0, x1, y1), new_value);
191 incrementPaletteUsage(new_value,
192 static_cast<uint16_t>((x1 - x0 + 1) * (y1 - y0 + 1)));
193}
194
195void BackgroundFillOptimizer::resetMaskAndUsage(uint8_t mask_value) {
196 background_mask_->fillRect(
197 Box(0, 0, background_mask_->width() - 1, background_mask_->height() - 1),
198 mask_value);
199 for (int i = 0; i < 16; ++i) palette_usage_count_[i] = 0;
200 const uint16_t cell_count = static_cast<uint16_t>(background_mask_->width() *
201 background_mask_->height());
202 palette_usage_count_[mask_value] = cell_count;
203 palette_full_hint_ = false;
204}
205
206uint8_t BackgroundFillOptimizer::tryAddIdxInPaletteDynamic(Color color) {
207 if (palette_size_ < 15) {
208 uint8_t reclaim_idx = palette_size_;
209 while (reclaim_idx > pinned_palette_size_ &&
210 palette_usage_count_[reclaim_idx] == 0) {
211 --reclaim_idx;
212 }
213 const bool append = (reclaim_idx == palette_size_);
214 if (append) {
215 ++palette_size_;
216 // LOG(INFO) << "Added color " << roo_logging::hex << color.asArgb()
217 // << " to palette idx " << static_cast<int>(palette_size_);
218 } else {
219 // LOG(INFO) << "Reclaimed palette idx " << static_cast<int>(reclaim_idx)
220 // << " for color " << roo_logging::hex << color.asArgb();
221 }
222 palette_[reclaim_idx] = color;
223 palette_full_hint_ = false;
224 return reclaim_idx + 1;
225 }
226
227 if (palette_full_hint_) return 0;
228
229 for (uint8_t idx = pinned_palette_size_ + 1; idx <= palette_size_; ++idx) {
230 if (palette_usage_count_[idx] == 0) {
231 palette_[idx - 1] = color;
232 return idx;
233 }
234 }
235
236 palette_full_hint_ = true;
237 return 0;
238}
239
240uint8_t BackgroundFillOptimizer::tryAddIdxInPaletteOnSecondConsecutiveColor(
241 Color color) {
242 if (has_pending_dynamic_palette_color_ &&
243 pending_dynamic_palette_color_ == color) {
244 has_pending_dynamic_palette_color_ = false;
245 return tryAddIdxInPaletteDynamic(color);
246 }
247 pending_dynamic_palette_color_ = color;
248 has_pending_dynamic_palette_color_ = true;
249 return 0;
250}
251
252void BackgroundFillOptimizer::resetPendingDynamicPaletteColor() {
253 has_pending_dynamic_palette_color_ = false;
254}
255
257 uint16_t y1, BlendingMode mode) {
258 flushDeferredUniformRun();
259 address_window_ = Box(x0, y0, x1, y1);
260 address_window_area_ = address_window_.area();
261 bx_min_ = x0 / kBlock;
262 bx_max_ = x1 / kBlock;
263 address_window_block_haligned_ =
264 (x0 % kBlock) == 0 && (x1 % kBlock) == (kBlock - 1);
265 blending_mode_ = mode;
266 cursor_x_ = x0;
267 cursor_y_ = y0;
268 cursor_ord_ = 0;
269 write_scan_state_ = WriteScanState::kScan;
270 passthrough_address_set_ = false;
271 scan_uniform_active_ = false;
272 scan_uniform_count_ = 0;
273 resetPendingDynamicPaletteColor();
274}
275
276void BackgroundFillOptimizer::setWriteCursorOrd(uint32_t ord) {
277 cursor_ord_ = ord;
278 const int16_t aw_width = address_window_.width();
279 const uint32_t row = cursor_ord_ / static_cast<uint32_t>(aw_width);
280 const uint32_t col = cursor_ord_ % static_cast<uint32_t>(aw_width);
281 cursor_x_ = address_window_.xMin() + static_cast<int16_t>(col);
282 cursor_y_ = address_window_.yMin() + static_cast<int16_t>(row);
283}
284
285void BackgroundFillOptimizer::advanceWriteCursor(uint32_t pixel_count) {
286 setWriteCursorOrd(cursor_ord_ + pixel_count);
287}
288
289uint32_t BackgroundFillOptimizer::pixelsUntilStripeEnd() const {
290 if (cursor_ord_ >= address_window_area_) return 0;
291 const int16_t aw_width = address_window_.width();
292 const int16_t aw_y0 = address_window_.yMin();
293 const int16_t aw_y1 = address_window_.yMax();
294 const int16_t aw_x1 = address_window_.xMax();
295
296 const int16_t y = cursor_y_;
297 const int16_t mod = static_cast<int16_t>((y + 1) % kBlock);
298 const int16_t rows_to_aligned_end = (mod == 0) ? 0 : (kBlock - mod);
299 const int16_t stripe_end_y =
300 std::min<int16_t>(aw_y1, y + rows_to_aligned_end);
301
302 const uint32_t stripe_end_ord =
303 static_cast<uint32_t>(stripe_end_y - aw_y0) *
304 static_cast<uint32_t>(aw_width) +
305 static_cast<uint32_t>(aw_x1 - address_window_.xMin());
306 DCHECK_GE(stripe_end_ord, cursor_ord_);
307 return stripe_end_ord - cursor_ord_ + 1;
308}
309
310void BackgroundFillOptimizer::beginPassthroughIfNeeded() {
311 if (passthrough_address_set_) return;
312 output_.setAddress(cursor_x_, cursor_y_, address_window_.xMax(),
313 address_window_.yMax(), blending_mode_);
314 passthrough_address_set_ = true;
315}
316
317void BackgroundFillOptimizer::clearMaskForOrdRange(uint32_t start_ord,
318 uint32_t pixel_count) {
319 if (pixel_count == 0) return;
320 const int16_t aw_width = address_window_.width();
321 const int16_t aw_x0 = address_window_.xMin();
322 const int16_t aw_y0 = address_window_.yMin();
323 const uint32_t end_ord = start_ord + pixel_count - 1;
324
325 const int16_t start_y = aw_y0 + static_cast<int16_t>(start_ord / aw_width);
326 const int16_t end_y = aw_y0 + static_cast<int16_t>(end_ord / aw_width);
327 const int16_t start_x = aw_x0 + static_cast<int16_t>(start_ord % aw_width);
328 const int16_t end_x = aw_x0 + static_cast<int16_t>(end_ord % aw_width);
329
330 const int16_t start_by = start_y / kBlock;
331 const int16_t start_bx = start_x / kBlock;
332 const int16_t end_bx = end_x / kBlock;
333
334 // The stream range [start_ord, end_ord] maps to contiguous rows. Mask
335 // clearing can always be decomposed into up to 3 rectangle clears:
336 // 1) top boundary block-row (partial-X),
337 // 2) middle block-rows (full-X),
338 // 3) bottom boundary block-row (partial-X).
339 //
340 // ..****
341 // ******
342 // ******
343 // ****..
344 //
345 // Special case is when the range covers only a single row, then it's just one
346 // partial-X clear.:
347
348 if (start_y == end_y) {
349 fillMaskRect(Box(start_bx, start_by, end_bx, start_by), 0);
350 return;
351 }
352
353 const int16_t end_by = end_y / kBlock;
354
355 int16_t full_by0 = start_by;
356 int16_t full_by1 = end_by;
357
358 // If the first touched row is the last row of its 4px stripe, only that row
359 // contributes in start_by and needs partial-x clearing.
360 if (((start_y + 1) % kBlock) == 0) {
361 fillMaskRect(Box(start_bx, start_by, bx_max_, start_by), 0);
362 ++full_by0;
363 }
364
365 // If the last touched row is the first row of its 4px stripe, only that row
366 // contributes in end_by and needs partial-x clearing.
367 if ((end_y % kBlock) == 0) {
368 fillMaskRect(Box(bx_min_, end_by, end_bx, end_by), 0);
369 --full_by1;
370 }
371
372 if (full_by0 <= full_by1) {
373 fillMaskRect(Box(bx_min_, full_by0, bx_max_, full_by1), 0);
374 }
375}
376
377void BackgroundFillOptimizer::passthroughWrite(Color* color,
378 uint32_t pixel_count) {
379 if (pixel_count == 0) return;
380 beginPassthroughIfNeeded();
381 clearMaskForOrdRange(cursor_ord_, pixel_count);
382 output_.write(color, pixel_count);
383 advanceWriteCursor(pixel_count);
384}
385
386void BackgroundFillOptimizer::passthroughFill(Color color,
387 uint32_t pixel_count) {
388 if (pixel_count == 0) return;
389 beginPassthroughIfNeeded();
390 clearMaskForOrdRange(cursor_ord_, pixel_count);
391 output_.fill(color, pixel_count);
392 advanceWriteCursor(pixel_count);
393}
394
395void BackgroundFillOptimizer::flushDeferredUniformRun() {
396 if (!scan_uniform_active_ || scan_uniform_count_ == 0) return;
397
398 const int16_t aw_width = address_window_.width();
399 cursor_ord_ = (scan_uniform_start_y_ - address_window_.yMin()) * aw_width;
400 cursor_x_ = address_window_.xMin();
401 cursor_y_ = scan_uniform_start_y_;
402
403 write_scan_state_ = WriteScanState::kPassthrough;
404 passthrough_address_set_ = false;
405 passthroughFill(scan_uniform_color_, scan_uniform_count_);
406 scan_uniform_active_ = false;
407 scan_uniform_count_ = 0;
408}
409
410void BackgroundFillOptimizer::processAlignedFullStripeBlock(Color* colors,
411 int16_t bx,
412 int16_t by,
413 int16_t aw_width) {
414 // Determine if the block is uniform color.
415 bool all_same = true;
416 Color first_color = *colors;
417 {
418 Color* row_base = colors;
419 for (int16_t dy = 0; dy < kBlock && all_same; ++dy) {
420 for (int16_t dx = 0; dx < kBlock; ++dx) {
421 if (row_base[dx] != first_color) {
422 all_same = false;
423 break;
424 }
425 }
426 row_base += aw_width;
427 }
428 }
429
430 const uint8_t current_mask_value = background_mask_->get(bx, by);
431 if (!all_same) {
432 // Copy the block over.
433 resetPendingDynamicPaletteColor();
434 if (current_mask_value != 0) {
435 updateMaskValue(bx, by, current_mask_value, 0);
436 }
437 output_.setAddress(bx * kBlock, by * kBlock, bx * kBlock + kBlock - 1,
438 by * kBlock + kBlock - 1, blending_mode_);
439 if (aw_width == kBlock) {
440 output_.write(colors, kBlock * kBlock);
441 } else {
442 Color* row_base = colors;
443 for (int16_t dy = 0; dy < kBlock; ++dy) {
444 output_.write(row_base, kBlock);
445 row_base += aw_width;
446 }
447 }
448 return;
449 }
450
451 // Block is uniform color. Try to use palette index if possible.
452 uint8_t palette_idx = getIdxInPalette(first_color, palette_, palette_size_);
453 if (palette_idx == 0) {
454 palette_idx = tryAddIdxInPaletteOnSecondConsecutiveColor(first_color);
455 } else {
456 resetPendingDynamicPaletteColor();
457 }
458
459 if (palette_idx > 0 && current_mask_value == palette_idx) {
460 // Cache hit: block is already marked as filled with the same palette color.
461 return;
462 }
463
464 output_.fillRect(bx * kBlock, by * kBlock, bx * kBlock + kBlock - 1,
465 by * kBlock + kBlock - 1, first_color);
466 if (palette_idx != current_mask_value) {
467 updateMaskValue(bx, by, current_mask_value, palette_idx);
468 }
469}
470
471bool BackgroundFillOptimizer::tryProcessGridAlignedBlockStripes(
472 Color*& color, uint32_t& pixel_count) {
473 if (!address_window_block_haligned_) return false;
474 if (cursor_x_ != address_window_.xMin()) return false;
475 if ((cursor_y_ % kBlock) != 0) return false;
476 const int16_t aw_width = address_window_.width();
477
478 const uint32_t stripe_pixels = static_cast<uint32_t>(aw_width) * kBlock;
479 bool consumed_any = false;
480 while (pixel_count >= stripe_pixels &&
481 cursor_y_ + kBlock - 1 <= address_window_.yMax()) {
482 const int16_t by = cursor_y_ / kBlock;
483 Color* block_start = color;
484 for (int16_t block = bx_min_; block <= bx_max_; ++block) {
485 processAlignedFullStripeBlock(block_start, block, by, aw_width);
486 block_start += kBlock;
487 }
488 color += stripe_pixels;
489 pixel_count -= stripe_pixels;
490 cursor_ord_ += stripe_pixels;
491 cursor_y_ += kBlock;
492 consumed_any = true;
493 scan_uniform_active_ = false;
494 scan_uniform_count_ = 0;
495 }
496 return consumed_any;
497}
498
499void BackgroundFillOptimizer::emitUniformScanRun(Color color, int16_t start_y,
500 uint32_t count) {
501 if (count == 0) return;
502 const int16_t aw_width = address_window_.width();
503 DCHECK_EQ(0u, count % aw_width);
504
505 const int16_t y0 = start_y;
506 const int16_t y1 = y0 + static_cast<int16_t>(count / aw_width) - 1;
507
508 uint8_t palette_idx = getIdxInPalette(color, palette_, palette_size_);
509 const int16_t rect_h = y1 - y0 + 1;
510 if (palette_idx == 0 && aw_width >= kDynamicPaletteMinWidth &&
511 rect_h >= kDynamicPaletteMinHeight) {
512 palette_idx = tryAddIdxInPaletteDynamic(color);
513 }
514
515 // Inexplicably, using writer and adapter comes out faster in the text scroll
516 // benchmark (by more than 3%). Need further testing, but leaving as-is for
517 // now.
518 BufferedRectWriter writer(output_, blending_mode_);
519 if (palette_idx != 0) {
520 RectFillWriter adapter{.color = color, .writer = writer};
521 fillRectBg(address_window_.xMin(), y0, address_window_.xMax(), y1, adapter,
522 palette_idx);
523 } else {
524 fillMaskRect(Box(bx_min_, y0 / kBlock, bx_max_, y1 / kBlock), 0);
525 writer.writeRect(address_window_.xMin(), y0, address_window_.xMax(), y1,
526 color);
527 }
528}
529
531 while (pixel_count > 0 && !(cursor_ord_ >= address_window_area_)) {
532 if (write_scan_state_ == WriteScanState::kScan) {
533 // Strictly grid-aligned block fast path.
534 if (tryProcessGridAlignedBlockStripes(color, pixel_count)) {
535 continue;
536 }
537
538 // Uniform scanner path: works for aligned and unaligned windows.
539 const uint32_t to_stripe_end = pixelsUntilStripeEnd();
540 if (to_stripe_end == 0) break;
541 const uint32_t inspect_count =
542 std::min<uint32_t>(pixel_count, to_stripe_end);
543
544 if (!scan_uniform_active_) {
545 scan_uniform_active_ = true;
546 scan_uniform_color_ = color[0];
547 scan_uniform_start_y_ = cursor_y_;
548 scan_uniform_count_ = 0;
549 }
550
552 while (uniform_prefix < inspect_count &&
553 color[uniform_prefix] == scan_uniform_color_) {
555 }
556
558 flushDeferredUniformRun();
559 write_scan_state_ = WriteScanState::kPassthrough;
560 continue;
561 }
562
563 scan_uniform_count_ += inspect_count;
564 advanceWriteCursor(inspect_count);
567
569 emitUniformScanRun(scan_uniform_color_, scan_uniform_start_y_,
570 scan_uniform_count_);
571 scan_uniform_active_ = false;
572 scan_uniform_count_ = 0;
573 }
574 continue;
575 }
576
577 const uint32_t to_stripe_end = pixelsUntilStripeEnd();
578 if (to_stripe_end == 0) break;
580 std::min<uint32_t>(pixel_count, to_stripe_end);
581 passthroughWrite(color, passthrough_count);
584
586 write_scan_state_ = WriteScanState::kScan;
587 passthrough_address_set_ = false;
588 scan_uniform_active_ = false;
589 scan_uniform_count_ = 0;
590 }
591 }
592}
593
595 while (pixel_count > 0 && !(cursor_ord_ >= address_window_area_)) {
596 if (write_scan_state_ == WriteScanState::kScan) {
597 if (scan_uniform_active_ && scan_uniform_count_ > 0 &&
598 scan_uniform_color_ != color) {
599 flushDeferredUniformRun();
600 continue;
601 }
602
603 const uint32_t to_stripe_end = pixelsUntilStripeEnd();
604 if (to_stripe_end == 0) break;
605 const uint32_t inspect_count =
606 std::min<uint32_t>(pixel_count, to_stripe_end);
607
608 if (!scan_uniform_active_) {
609 scan_uniform_active_ = true;
610 scan_uniform_color_ = color;
611 scan_uniform_start_y_ = cursor_y_;
612 scan_uniform_count_ = 0;
613 }
614
615 scan_uniform_count_ += inspect_count;
616 advanceWriteCursor(inspect_count);
618
620 emitUniformScanRun(scan_uniform_color_, scan_uniform_start_y_,
621 scan_uniform_count_);
622 scan_uniform_active_ = false;
623 scan_uniform_count_ = 0;
624 }
625 continue;
626 }
627
628 const uint32_t to_stripe_end = pixelsUntilStripeEnd();
629 if (to_stripe_end == 0) break;
631 std::min<uint32_t>(pixel_count, to_stripe_end);
632 passthroughFill(color, passthrough_count);
634
636 write_scan_state_ = WriteScanState::kScan;
637 passthrough_address_set_ = false;
638 scan_uniform_active_ = false;
639 scan_uniform_count_ = 0;
640 }
641 }
642}
643
645 int16_t* x0, int16_t* y0, int16_t* x1,
646 int16_t* y1, uint16_t count) {
647 flushDeferredUniformRun();
648 BufferedRectWriter writer(output_, mode);
649 while (count-- > 0) {
650 const Color c = *color++;
651 const int16_t rx0 = *x0++;
652 const int16_t ry0 = *y0++;
653 const int16_t rx1 = *x1++;
654 const int16_t ry1 = *y1++;
655
656 uint8_t palette_idx = getIdxInPalette(c, palette_, palette_size_);
657 if (palette_idx == 0) {
658 const int16_t rect_w = rx1 - rx0 + 1;
659 const int16_t rect_h = ry1 - ry0 + 1;
662 palette_idx = tryAddIdxInPaletteDynamic(c);
663 }
664 }
665
666 if (palette_idx != 0) {
667 RectFillWriter adapter{
668 .color = c,
669 .writer = writer,
670 };
671 fillRectBg(rx0, ry0, rx1, ry1, adapter, palette_idx);
672 } else {
673 // Not a background palette color -> clear the nibble subrectangle
674 // corresponding to a region entirely enclosing the the drawn rectangle
675 // before writing to the underlying device.
676 fillMaskRect(Box(rx0 / kBlock, ry0 / kBlock, rx1 / kBlock, ry1 / kBlock),
677 0);
678 writer.writeRect(rx0, ry0, rx1, ry1, c);
679 }
680 }
681}
682
684 int16_t* x0, int16_t* y0, int16_t* x1,
685 int16_t* y1, uint16_t count) {
686 flushDeferredUniformRun();
687 uint8_t palette_idx = getIdxInPalette(color, palette_, palette_size_);
688 BufferedRectFiller filler(output_, color, mode);
689 BufferedRectWriter writer(output_, mode);
690
691 for (int i = 0; i < count; ++i) {
692 const int16_t rx0 = x0[i];
693 const int16_t ry0 = y0[i];
694 const int16_t rx1 = x1[i];
695 const int16_t ry1 = y1[i];
696
697 if (palette_idx == 0) {
698 const int16_t rect_w = rx1 - rx0 + 1;
699 const int16_t rect_h = ry1 - ry0 + 1;
702 palette_idx = tryAddIdxInPaletteDynamic(color);
703 }
704 }
705
706 if (palette_idx != 0) {
707 fillRectBg(rx0, ry0, rx1, ry1, filler, palette_idx);
708 } else {
709 // Not a background color -> clear the nibble subrectangle
710 // corresponding to a region entirely enclosing the drawn rectangle
711 // before writing to the underlying device.
712 fillMaskRect(Box(rx0 / kBlock, ry0 / kBlock, rx1 / kBlock, ry1 / kBlock),
713 0);
714 writer.writeRect(rx0, ry0, rx1, ry1, color);
715 }
716 }
717}
718
720 int16_t* x, int16_t* y,
722 flushDeferredUniformRun();
723 int16_t* x_out = x;
724 int16_t* y_out = y;
727 for (uint16_t i = 0; i < pixel_count; ++i) {
728 const int16_t bx = x[i] / kBlock;
729 const int16_t by = y[i] / kBlock;
730 const uint8_t old_value = background_mask_->get(bx, by);
731
732 uint8_t palette_idx = getIdxInPalette(color[i], palette_, palette_size_);
733 if (palette_idx != 0 && old_value == palette_idx) {
734 // Do not actually draw the background pixel if the corresponding
735 // bit-mask rectangle is known to be all-background already.
736 continue;
737 }
738 // Mark the corresponding bit-mask rectangle as no longer
739 // all-background.
740 if (old_value != 0) {
741 updateMaskValue(bx, by, old_value, 0);
742 }
743 *x_out++ = x[i];
744 *y_out++ = y[i];
745 *color_out++ = color[i];
747 }
748 if (new_pixel_count > 0) {
749 output_.writePixels(mode, color, x, y, new_pixel_count);
750 }
751}
752
754 int16_t* x, int16_t* y,
756 flushDeferredUniformRun();
757 uint8_t palette_idx = getIdxInPalette(color, palette_, palette_size_);
758 if (palette_idx != 0) {
759 int16_t* x_out = x;
760 int16_t* y_out = y;
762 for (uint16_t i = 0; i < pixel_count; ++i) {
763 const int16_t bx = x[i] / kBlock;
764 const int16_t by = y[i] / kBlock;
765 const uint8_t old_value = background_mask_->get(bx, by);
766
767 // Do not actually draw the background pixel if the corresponding
768 // bit-mask rectangle is known to be all-background already.
769 if (old_value == palette_idx) {
770 continue;
771 }
772 if (old_value != 0) {
773 updateMaskValue(bx, by, old_value, 0);
774 }
775 *x_out++ = x[i];
776 *y_out++ = y[i];
778 }
779 if (new_pixel_count > 0) {
780 output_.fillPixels(mode, color, x, y, new_pixel_count);
781 }
782 } else {
783 // We need to draw all the pixels, but also mark the corresponding
784 // bit-mask rectangles as not all-background.
785 for (uint16_t i = 0; i < pixel_count; ++i) {
786 const int16_t bx = x[i] / kBlock;
787 const int16_t by = y[i] / kBlock;
788 const uint8_t old_value = background_mask_->get(bx, by);
789 if (old_value != 0) {
790 updateMaskValue(bx, by, old_value, 0);
791 }
792 }
793 output_.fillPixels(mode, color, x, y, pixel_count);
794 }
795}
796
798 size_t row_width_bytes,
802 flushDeferredUniformRun();
803 if (src_x1 < src_x0 || src_y1 < src_y0) return;
804 const int16_t dst_x1 = dst_x0 + (src_x1 - src_x0);
805 const int16_t dst_y1 = dst_y0 + (src_y1 - src_y0);
806
807 const int16_t bx_min = dst_x0 / kBlock;
808 const int16_t by_min = dst_y0 / kBlock;
809 const int16_t bx_max = dst_x1 / kBlock;
810 const int16_t by_max = dst_y1 / kBlock;
811
813
814 for (int16_t by = by_min; by <= by_max; ++by) {
815 const int16_t block_y0 = by * kBlock;
816 const int16_t block_y1 = block_y0 + kBlock - 1;
817 const int16_t draw_y0 = std::max<int16_t>(block_y0, dst_y0);
818 const int16_t draw_y1 = std::min<int16_t>(block_y1, dst_y1);
819
820 bool streak_active = false;
822
823 for (int16_t bx = bx_min; bx <= bx_max + 1; ++bx) {
824 bool must_draw = false;
825
826 if (bx <= bx_max) {
827 const int16_t block_x0 = bx * kBlock;
828 const int16_t block_x1 = block_x0 + kBlock - 1;
829 const int16_t draw_x0 = std::max<int16_t>(block_x0, dst_x0);
830 const int16_t draw_x1 = std::min<int16_t>(block_x1, dst_x1);
831
833
839 const bool all_same = color_format.decodeIfUniform(
842
843 const bool fully_covered =
844 (draw_x0 == block_x0 && draw_x1 == block_x1 &&
846
848 all_same ? getIdxInPalette(uniform_color, palette_, palette_size_)
849 : 0;
850 if (all_same && fully_covered) {
851 if (palette_idx == 0) {
853 tryAddIdxInPaletteOnSecondConsecutiveColor(uniform_color);
854 } else {
855 resetPendingDynamicPaletteColor();
856 }
857 } else {
858 resetPendingDynamicPaletteColor();
859 }
860 const uint8_t current_mask_value = background_mask_->get(bx, by);
861
862 // Redundant block update: all touched pixels are a palette color and
863 // the block is already known to be entirely that color.
865
867 if (must_draw) {
869 }
870
872 updateMaskValue(bx, by, current_mask_value, new_mask_value);
873 }
874 }
875
876 if (must_draw) {
877 if (!streak_active) {
878 streak_active = true;
879 streak_bx0 = bx;
880 }
881 } else if (streak_active) {
883 const int16_t streak_block_x1 = bx * kBlock - 1;
884 const int16_t draw_x0 = std::max<int16_t>(streak_block_x0, dst_x0);
885 const int16_t draw_x1 = std::min<int16_t>(streak_block_x1, dst_x1);
886
891
895 streak_active = false;
896 }
897 }
898 }
899}
900
901template <typename Filler>
902void BackgroundFillOptimizer::fillRectBg(int16_t x0, int16_t y0, int16_t x1,
903 int16_t y1, Filler& filler,
905 // Iterate over the bit-map rectangle that encloses the requested rectangle.
906 // Skip writing sub-rectangles that are known to be already all-background.
907 Box filter_box(x0 / kBlock, y0 / kBlock, x1 / kBlock, y1 / kBlock);
908 internal::NibbleRectWindowIterator itr(background_mask_, filter_box.xMin(),
909 filter_box.yMin(), filter_box.xMax(),
910 filter_box.yMax());
911 int16_t xendl = filter_box.xMax() + 1;
912 for (int16_t y = filter_box.yMin(); y <= filter_box.yMax(); ++y) {
913 int16_t xstart = -1;
914 for (int16_t x = filter_box.xMin(); x <= xendl; ++x) {
915 if (x == xendl || itr.next() == palette_idx) {
916 // End of the streak. Let's draw the necessary rectangle.
917 if (xstart >= 0) {
918 Box box(xstart * kBlock, y * kBlock, x * kBlock - 1,
919 y * kBlock + kBlock - 1);
920 if (box.clip(Box(x0, y0, x1, y1)) != Box::ClipResult::kUnchanged) {
921 if (y0 > y * kBlock || y1 < y * kBlock + kBlock - 1) {
922 // Need to invalidate the entire line.
923 fillMaskRect(Box(xstart, y, x - 1, y), 0);
924 } else {
925 if (x0 > xstart * kBlock) {
926 const uint8_t old_value = background_mask_->get(xstart, y);
927 if (old_value != 0) {
928 updateMaskValue(xstart, y, old_value, 0);
929 }
930 }
931 if (x1 < x * kBlock - 1) {
932 const uint8_t old_value = background_mask_->get(x - 1, y);
933 if (old_value != 0) {
934 updateMaskValue(x - 1, y, old_value, 0);
935 }
936 }
937 }
938 }
939 filler.fillRect(box.xMin(), box.yMin(), box.xMax(), box.yMax());
940 }
941 xstart = -1;
942 } else {
943 // Continuation, or a new streak.
944 if (xstart < 0) {
945 // New streak.
946 xstart = x;
947 }
948 }
949 }
950 }
951 // Identify the bit-mask rectangle that is entirely covered by the
952 // requested rect. If non-empty, set all nibbles corresponding to it in
953 // the mask, indicating that the area is all-bg.
954 Box inner_filter_box((x0 + kBlock - 1) / kBlock, (y0 + kBlock - 1) / kBlock,
955 (x1 + 1) / kBlock - 1, (y1 + 1) / kBlock - 1);
956 if (!inner_filter_box.empty()) {
957 fillMaskRect(inner_filter_box, palette_idx);
958 }
959}
960
962 DisplayDevice& device)
963 : DisplayDevice(device.raw_width(), device.raw_height()),
964 device_(device),
965 buffer_(device_.raw_width(), device_.raw_height()),
966 optimizer_(device_, buffer_) {
967 setOrientation(device.orientation());
968}
969
972 Color prefilled) {
973 optimizer_.setPalette(palette, palette_size);
974 optimizer_.setPrefilled(prefilled);
975}
976
978 std::initializer_list<Color> palette, Color prefilled) {
979 uint8_t size = palette.size();
980 const Color* p = size == 0 ? nullptr : &*palette.begin();
981 setPalette(p, size, prefilled);
982}
983
984} // namespace roo_display
BufferedRectWriter & writer
BackgroundFillOptimizerDevice(DisplayDevice &device)
Create a wrapper device.
void setPalette(const Color *palette, uint8_t palette_size, Color prefilled=color::Transparent)
Set the palette and optional prefilled color.
void setSwapXY(bool swap)
Set whether the underlying display swaps x/y.
void invalidateRect(const Box &rect)
Clear the mask for the area covering rect.
FrameBuffer(int16_t width, int16_t height)
Create a frame buffer with dynamic allocation.
void setAddress(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, BlendingMode mode) override
Set a rectangular window filled by subsequent calls to write().
static constexpr const int kDynamicPaletteMinWidth
Minimum rectangle width for dynamic palette enrollment.
void write(Color *color, uint32_t pixel_count) override
Write pixels into the current address window.
void drawDirectRect(const roo::byte *data, size_t row_width_bytes, int16_t src_x0, int16_t src_y0, int16_t src_x1, int16_t src_y1, int16_t dst_x0, int16_t dst_y0) override
Draw a rectangle represented in the device's native color format.
static constexpr const int kBlock
Size of the coarse background grid in pixels.
void writePixels(BlendingMode mode, Color *color, int16_t *x, int16_t *y, uint16_t pixel_count) override
Draw the specified pixels (per-pixel colors). Invalidates the address window.
void fillRects(BlendingMode mode, Color color, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1, uint16_t count) override
Draw the specified rectangles using the same color. Invalidates the address window.
void fillPixels(BlendingMode mode, Color color, int16_t *x, int16_t *y, uint16_t pixel_count) override
Draw the specified pixels using the same color. Invalidates the address window.
void writeRects(BlendingMode mode, Color *color, int16_t *x0, int16_t *y0, int16_t *x1, int16_t *y1, uint16_t count) override
Draw the specified rectangles (per-rectangle colors). Invalidates the address window.
static constexpr const int kDynamicPaletteMinHeight
Minimum rectangle height for dynamic palette enrollment.
void fill(Color color, uint32_t pixel_count) override
Write pixel_count copies of the same color into the current address window.
BackgroundFillOptimizer(DisplayOutput &output, FrameBuffer &frame_buffer)
Create a background fill optimizer filter.
const ColorFormat & getColorFormat() const override
Return the native color format used by this device for direct drawing.
void setPalette(const Color *palette, uint8_t palette_size)
Axis-aligned integer rectangle.
Definition box.h:12
int16_t width() const
Width in pixels (inclusive coordinates).
Definition box.h:77
int16_t xMin() const
Minimum x (inclusive).
Definition box.h:65
int16_t xMax() const
Maximum x (inclusive).
Definition box.h:71
int16_t yMax() const
Maximum y (inclusive).
Definition box.h:74
int32_t area() const
Area in pixels.
Definition box.h:83
int16_t yMin() const
Minimum y (inclusive).
Definition box.h:68
ARGB8888 color stored as a 32-bit unsigned integer.
Definition color.h:16
Base class for display device drivers.
Definition device.h:223
Orientation orientation() const
Return the current orientation of the display.
Definition device.h:250
void setOrientation(Orientation orientation)
Set the orientation of the display.
Definition device.h:235
The abstraction for drawing to a display.
Definition device.h:15
virtual void write(Color *color, uint32_t pixel_count)=0
Write pixels into the current address window.
void fillRect(BlendingMode blending_mode, const Box &rect, Color color)
Fill a single rectangle. Invalidates the address window.
Definition device.h:135
virtual void drawDirectRect(const roo::byte *data, size_t row_width_bytes, int16_t src_x0, int16_t src_y0, int16_t src_x1, int16_t src_y1, int16_t dst_x0, int16_t dst_y0)
Draw a rectangle represented in the device's native color format.
Definition device.cpp:33
virtual void fill(Color color, uint32_t pixel_count)
Write pixel_count copies of the same color into the current address window.
Definition device.cpp:7
virtual void fillPixels(BlendingMode blending_mode, Color color, int16_t *x, int16_t *y, uint16_t pixel_count)=0
Draw the specified pixels using the same color. Invalidates the address window.
virtual void writePixels(BlendingMode blending_mode, Color *color, int16_t *x, int16_t *y, uint16_t pixel_count)=0
Draw the specified pixels (per-pixel colors). Invalidates the address window.
void setAddress(const Box &bounds, BlendingMode blending_mode)
Convenience overload for setAddress() using a Box.
Definition device.h:45
void fillRect(const Box &rect, uint8_t value)
Definition nibble_rect.h:42
uint8_t get(int16_t x, int16_t y) const
Definition nibble_rect.h:25
void set(int16_t x, int16_t y, uint8_t value)
Definition nibble_rect.h:30
Defines 140 opaque HTML named colors.
BlendingMode
Porter-Duff style blending modes.
Definition blending.h:17
const Palette * palette
Definition png.cpp:30