Skip to content

Commit 3fad7de

Browse files
committed
Rework the pixel computation to use areas
This changes the displayio pixel computation from per-pixel to per-area. This is precursor work to updating portions of the screen (adafruit#1169). It should provide mild speedups because bounds checks are done once per area rather than once per pixel. Filling by area also allows TileGrid to maintain a row-associative fill pattern even when the display's refresh is orthogonal to it.
1 parent a888fe4 commit 3fad7de

File tree

11 files changed

+353
-153
lines changed

11 files changed

+353
-153
lines changed

ports/atmel-samd/Makefile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,6 @@ endif
9999

100100
#Debugging/Optimization
101101
ifeq ($(DEBUG), 1)
102-
# Turn on Python modules useful for debugging (e.g. uheap, ustack).
103102
CFLAGS += -ggdb
104103
# You may want to disable -flto if it interferes with debugging.
105104
CFLAGS += -flto

shared-bindings/displayio/Display.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ void common_hal_displayio_display_refresh_soon(displayio_display_obj_t* self);
5353
bool displayio_display_begin_transaction(displayio_display_obj_t* self);
5454
void displayio_display_end_transaction(displayio_display_obj_t* self);
5555

56+
// The second point of the region is exclusive.
5657
void displayio_display_set_region_to_update(displayio_display_obj_t* self, uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1);
5758
bool displayio_display_frame_queued(displayio_display_obj_t* self);
5859

shared-module/displayio/Group.c

Lines changed: 14 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -134,32 +134,28 @@ void displayio_group_construct(displayio_group_t* self, displayio_group_child_t*
134134
self->scale = scale;
135135
}
136136

137-
bool displayio_group_get_pixel(displayio_group_t *self, int16_t x, int16_t y, uint16_t* pixel) {
138-
x -= self->x;
139-
y -= self->y;
140-
// When we are scaled we need to substract all but one to ensure -scale to 0 divide down to -1.
141-
// Normally -scale to scale both divide down to 0 because 0 is unsigned.
142-
if (x < 0) {
143-
x -= self->scale - 1;
144-
}
145-
if (y < 0) {
146-
y -= self->scale - 1;
147-
}
148-
x /= self->scale;
149-
y /= self->scale;
137+
bool displayio_group_get_area(displayio_group_t *self, displayio_buffer_transform_t* transform, displayio_area_t* area, uint32_t* mask, uint32_t* buffer) {
138+
displayio_area_shift(area, -self->x * transform->scale, -self->y * transform->scale);
139+
transform->scale *= self->scale;
140+
141+
bool full_coverage = false;
150142
for (int32_t i = self->size - 1; i >= 0 ; i--) {
151143
mp_obj_t layer = self->children[i].native;
152144
if (MP_OBJ_IS_TYPE(layer, &displayio_tilegrid_type)) {
153-
if (displayio_tilegrid_get_pixel(layer, x, y, pixel)) {
154-
return true;
145+
if (displayio_tilegrid_get_area(layer, transform, area, mask, buffer)) {
146+
full_coverage = true;
147+
break;
155148
}
156149
} else if (MP_OBJ_IS_TYPE(layer, &displayio_group_type)) {
157-
if (displayio_group_get_pixel(layer, x, y, pixel)) {
158-
return true;
150+
if (displayio_group_get_area(layer, transform, area, mask, buffer)) {
151+
full_coverage = true;
152+
break;
159153
}
160154
}
161155
}
162-
return false;
156+
transform->scale /= self->scale;
157+
displayio_area_shift(area, self->x * transform->scale, self->y * transform->scale);
158+
return full_coverage;
163159
}
164160

165161
bool displayio_group_needs_refresh(displayio_group_t *self) {

shared-module/displayio/Group.h

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
#include <stdint.h>
3232

3333
#include "py/obj.h"
34+
#include "shared-module/displayio/area.h"
3435

3536
typedef struct {
3637
mp_obj_t native;
@@ -49,7 +50,7 @@ typedef struct {
4950
} displayio_group_t;
5051

5152
void displayio_group_construct(displayio_group_t* self, displayio_group_child_t* child_array, uint32_t max_size, uint32_t scale, mp_int_t x, mp_int_t y);
52-
bool displayio_group_get_pixel(displayio_group_t *group, int16_t x, int16_t y, uint16_t *pixel);
53+
bool displayio_group_get_area(displayio_group_t *group, displayio_buffer_transform_t* transform, displayio_area_t* area, uint32_t* mask, uint32_t *buffer);
5354
bool displayio_group_needs_refresh(displayio_group_t *self);
5455
void displayio_group_finish_refresh(displayio_group_t *self);
5556

shared-module/displayio/TileGrid.c

Lines changed: 114 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -56,31 +56,40 @@ void common_hal_displayio_tilegrid_construct(displayio_tilegrid_t *self, mp_obj_
5656
self->bitmap_width_in_tiles = bitmap_width_in_tiles;
5757
self->width_in_tiles = width;
5858
self->height_in_tiles = height;
59-
self->total_width = width * tile_width;
60-
self->total_height = height * tile_height;
59+
self->area.x1 = x;
60+
self->area.y1 = y;
61+
// -1 because areas are inclusive
62+
self->area.x2 = x + width * tile_width - 1;
63+
self->area.y2 = y + height * tile_height - 1;
6164
self->tile_width = tile_width;
6265
self->tile_height = tile_height;
6366
self->bitmap = bitmap;
6467
self->pixel_shader = pixel_shader;
65-
self->x = x;
66-
self->y = y;
6768
}
6869

6970

7071
mp_int_t common_hal_displayio_tilegrid_get_x(displayio_tilegrid_t *self) {
71-
return self->x;
72+
return self->area.x1;
7273
}
7374
void common_hal_displayio_tilegrid_set_x(displayio_tilegrid_t *self, mp_int_t x) {
74-
self->needs_refresh = self->x != x;
75-
self->x = x;
75+
if (self->area.x1 == x) {
76+
return;
77+
}
78+
self->needs_refresh = true;
79+
self->area.x2 += (self->area.x1 - x);
80+
self->area.x1 = x;
7681
}
7782
mp_int_t common_hal_displayio_tilegrid_get_y(displayio_tilegrid_t *self) {
78-
return self->y;
83+
return self->area.y1;
7984
}
8085

8186
void common_hal_displayio_tilegrid_set_y(displayio_tilegrid_t *self, mp_int_t y) {
82-
self->needs_refresh = self->y != y;
83-
self->y = y;
87+
if (self->area.y1 == y) {
88+
return;
89+
}
90+
self->needs_refresh = true;
91+
self->area.y2 += (self->area.y1 - y);
92+
self->area.y1 = y;
8493
}
8594

8695
mp_obj_t common_hal_displayio_tilegrid_get_pixel_shader(displayio_tilegrid_t *self) {
@@ -128,45 +137,115 @@ void common_hal_displayio_tilegrid_set_tile(displayio_tilegrid_t *self, uint16_t
128137
void common_hal_displayio_tilegrid_set_top_left(displayio_tilegrid_t *self, uint16_t x, uint16_t y) {
129138
self->top_left_x = x;
130139
self->top_left_y = y;
140+
self->needs_refresh = true;
131141
}
132-
bool displayio_tilegrid_get_pixel(displayio_tilegrid_t *self, int16_t x, int16_t y, uint16_t* pixel) {
133-
x -= self->x;
134-
y -= self->y;
135-
if (y < 0 || y >= self->total_height || x >= self->total_width || x < 0) {
136-
return false;
137-
}
138142

143+
bool displayio_tilegrid_get_area(displayio_tilegrid_t *self, displayio_buffer_transform_t* transform, displayio_area_t* area, uint32_t* mask, uint32_t *buffer) {
144+
// If no tiles are present we have no impact.
139145
uint8_t* tiles = self->tiles;
140146
if (self->inline_tiles) {
141147
tiles = (uint8_t*) &self->tiles;
142148
}
143149
if (tiles == NULL) {
144150
return false;
145151
}
146-
uint16_t tile_location = ((y / self->tile_height + self->top_left_y) % self->height_in_tiles) * self->width_in_tiles + (x / self->tile_width + self->top_left_x) % self->width_in_tiles;
147-
uint8_t tile = tiles[tile_location];
148-
uint16_t tile_x = tile_x = (tile % self->bitmap_width_in_tiles) * self->tile_width + x % self->tile_width;
149-
uint16_t tile_y = tile_y = (tile / self->bitmap_width_in_tiles) * self->tile_height + y % self->tile_height;
150152

151-
uint32_t value = 0;
152-
if (MP_OBJ_IS_TYPE(self->bitmap, &displayio_bitmap_type)) {
153-
value = common_hal_displayio_bitmap_get_pixel(self->bitmap, tile_x, tile_y);
154-
} else if (MP_OBJ_IS_TYPE(self->bitmap, &displayio_shape_type)) {
155-
value = common_hal_displayio_shape_get_pixel(self->bitmap, tile_x, tile_y);
156-
} else if (MP_OBJ_IS_TYPE(self->bitmap, &displayio_ondiskbitmap_type)) {
157-
value = common_hal_displayio_ondiskbitmap_get_pixel(self->bitmap, tile_x, tile_y);
153+
displayio_area_t overlap;
154+
displayio_area_t scaled_area = {
155+
.x1 = self->area.x1 * transform->scale,
156+
.y1 = self->area.y1 * transform->scale,
157+
.x2 = (self->area.x2 + 1) * transform->scale - 1, // Second point is inclusive.
158+
.y2 = (self->area.y2 + 1) * transform->scale - 1
159+
};
160+
if (!displayio_area_compute_overlap(area, &scaled_area, &overlap)) {
161+
return false;
158162
}
159163

160-
if (self->pixel_shader == mp_const_none) {
161-
*pixel = value;
162-
return true;
163-
} else if (MP_OBJ_IS_TYPE(self->pixel_shader, &displayio_palette_type) && displayio_palette_get_color(self->pixel_shader, value, pixel)) {
164-
return true;
165-
} else if (MP_OBJ_IS_TYPE(self->pixel_shader, &displayio_colorconverter_type) && common_hal_displayio_colorconverter_convert(self->pixel_shader, value, pixel)) {
166-
return true;
164+
int16_t x_stride = 1;
165+
int16_t y_stride = displayio_area_width(area);
166+
if (transform->transpose_xy) {
167+
x_stride = displayio_area_height(area);
168+
y_stride = 1;
169+
}
170+
uint16_t start = 0;
171+
if (transform->mirror_x) {
172+
start += (area->x2 - area->x1) * x_stride;
173+
x_stride *= -1;
174+
}
175+
if (transform->mirror_y) {
176+
start += (area->y2 - area->y1) * y_stride;
177+
y_stride *= -1;
167178
}
168179

169-
return false;
180+
bool full_coverage = displayio_area_equal(area, &overlap);
181+
182+
// TODO(tannewt): Set full coverage to true if all pixels outside the overlap have already been
183+
// set as well.
184+
bool always_full_coverage = false;
185+
186+
// TODO(tannewt): Check to see if the pixel_shader has any transparency. If it doesn't then we
187+
// can either return full coverage or bulk update the mask.
188+
int16_t y = overlap.y1 - scaled_area.y1;
189+
if (y < 0) {
190+
y = 0;
191+
}
192+
int16_t x_shift = area->x1 - scaled_area.x1;
193+
int16_t y_shift = area->y1 - scaled_area.y1;
194+
for (; y <= overlap.y2 - scaled_area.y1; y++) {
195+
int16_t x = overlap.x1 - scaled_area.x1;
196+
if (x < 0) {
197+
x = 0;
198+
}
199+
int16_t row_start = start + (y - y_shift) * y_stride;
200+
int16_t local_y = y / transform->scale;
201+
for (; x <= overlap.x2 - scaled_area.x1; x++) {
202+
// Compute the destination pixel in the buffer and mask based on the transformations.
203+
uint16_t offset = row_start + (x - x_shift) * x_stride;
204+
205+
// Check the mask first to see if the pixel has already been set.
206+
if ((mask[offset / 32] & (1 << (offset % 32))) != 0) {
207+
continue;
208+
}
209+
int16_t local_x = x / transform->scale;
210+
uint16_t tile_location = ((local_y / self->tile_height + self->top_left_y) % self->height_in_tiles) * self->width_in_tiles + (local_x / self->tile_width + self->top_left_x) % self->width_in_tiles;
211+
uint8_t tile = tiles[tile_location];
212+
uint16_t tile_x = (tile % self->bitmap_width_in_tiles) * self->tile_width + local_x % self->tile_width;
213+
uint16_t tile_y = (tile / self->bitmap_width_in_tiles) * self->tile_height + local_y % self->tile_height;
214+
215+
uint32_t value = 0;
216+
// We always want to read bitmap pixels by row first and then transpose into the destination
217+
// buffer because most bitmaps are row associated.
218+
if (MP_OBJ_IS_TYPE(self->bitmap, &displayio_bitmap_type)) {
219+
value = common_hal_displayio_bitmap_get_pixel(self->bitmap, tile_x, tile_y);
220+
} else if (MP_OBJ_IS_TYPE(self->bitmap, &displayio_shape_type)) {
221+
value = common_hal_displayio_shape_get_pixel(self->bitmap, tile_x, tile_y);
222+
} else if (MP_OBJ_IS_TYPE(self->bitmap, &displayio_ondiskbitmap_type)) {
223+
value = common_hal_displayio_ondiskbitmap_get_pixel(self->bitmap, tile_x, tile_y);
224+
}
225+
226+
uint16_t* pixel = ((uint16_t*) buffer) + offset;
227+
if (self->pixel_shader == mp_const_none) {
228+
*pixel = value;
229+
return true;
230+
} else if (MP_OBJ_IS_TYPE(self->pixel_shader, &displayio_palette_type)) {
231+
if (!displayio_palette_get_color(self->pixel_shader, value, pixel)) {
232+
// mark the pixel as transparent
233+
full_coverage = false;
234+
} else if (!always_full_coverage) {
235+
mask[offset / 32] |= 1 << (offset % 32);
236+
}
237+
} else if (MP_OBJ_IS_TYPE(self->pixel_shader, &displayio_colorconverter_type)) {
238+
if (!common_hal_displayio_colorconverter_convert(self->pixel_shader, value, pixel)) {
239+
// mark the pixel as transparent
240+
full_coverage = false;
241+
} else if (!always_full_coverage) {
242+
mask[offset / 32] |= 1 << (offset % 32);
243+
}
244+
}
245+
}
246+
}
247+
248+
return full_coverage;
170249
}
171250

172251
bool displayio_tilegrid_needs_refresh(displayio_tilegrid_t *self) {

shared-module/displayio/TileGrid.h

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -31,18 +31,16 @@
3131
#include <stdint.h>
3232

3333
#include "py/obj.h"
34+
#include "shared-module/displayio/area.h"
3435

3536
typedef struct {
3637
mp_obj_base_t base;
3738
mp_obj_t bitmap;
3839
mp_obj_t pixel_shader;
39-
uint16_t x;
40-
uint16_t y;
40+
displayio_area_t area;
4141
uint16_t bitmap_width_in_tiles;
4242
uint16_t width_in_tiles;
4343
uint16_t height_in_tiles;
44-
uint16_t total_width;
45-
uint16_t total_height;
4644
uint16_t tile_width;
4745
uint16_t tile_height;
4846
uint16_t top_left_x;
@@ -52,7 +50,7 @@ typedef struct {
5250
bool inline_tiles;
5351
} displayio_tilegrid_t;
5452

55-
bool displayio_tilegrid_get_pixel(displayio_tilegrid_t *self, int16_t x, int16_t y, uint16_t *pixel);
53+
bool displayio_tilegrid_get_area(displayio_tilegrid_t *self, displayio_buffer_transform_t* transform, displayio_area_t* area, uint32_t* mask, uint32_t *buffer);
5654
bool displayio_tilegrid_needs_refresh(displayio_tilegrid_t *self);
5755
void displayio_tilegrid_finish_refresh(displayio_tilegrid_t *self);
5856

0 commit comments

Comments
 (0)