Arduboy2 Library  6.0.0
Sprites.cpp
Go to the documentation of this file.
1 
7 #include "Sprites.h"
8 
9 void Sprites::drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap,
10  const uint8_t *mask, uint8_t frame, uint8_t mask_frame)
11 {
12  draw(x, y, bitmap, frame, mask, mask_frame, SPRITE_MASKED);
13 }
14 
15 void Sprites::drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
16 {
17  draw(x, y, bitmap, frame, NULL, 0, SPRITE_OVERWRITE);
18 }
19 
20 void Sprites::drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
21 {
22  draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK_ERASE);
23 }
24 
25 void Sprites::drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
26 {
27  draw(x, y, bitmap, frame, NULL, 0, SPRITE_IS_MASK);
28 }
29 
30 void Sprites::drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
31 {
32  draw(x, y, bitmap, frame, NULL, 0, SPRITE_PLUS_MASK);
33 }
34 
35 
36 //common functions
37 void Sprites::draw(int16_t x, int16_t y,
38  const uint8_t *bitmap, uint8_t frame,
39  const uint8_t *mask, uint8_t sprite_frame,
40  uint8_t drawMode)
41 {
42  unsigned int frame_offset;
43 
44  if (bitmap == NULL)
45  return;
46 
47  uint8_t width = pgm_read_byte(bitmap);
48  uint8_t height = pgm_read_byte(++bitmap);
49  bitmap++;
50  if (frame > 0 || sprite_frame > 0) {
51  frame_offset = (width * ( height / 8 + ( height % 8 == 0 ? 0 : 1)));
52  // sprite plus mask uses twice as much space for each frame
53  if (drawMode == SPRITE_PLUS_MASK) {
54  frame_offset *= 2;
55  } else if (mask != NULL) {
56  mask += sprite_frame * frame_offset;
57  }
58  bitmap += frame * frame_offset;
59  }
60 
61  // if we're detecting the draw mode then base it on whether a mask
62  // was passed as a separate object
63  if (drawMode == SPRITE_AUTO_MODE) {
64  drawMode = mask == NULL ? SPRITE_UNMASKED : SPRITE_MASKED;
65  }
66 
67  drawBitmap(x, y, bitmap, mask, width, height, drawMode);
68 }
69 
70 void Sprites::drawBitmap(int16_t x, int16_t y,
71  const uint8_t *bitmap, const uint8_t *mask,
72  uint8_t w, uint8_t h, uint8_t draw_mode)
73 {
74  // no need to draw at all of we're offscreen
75  if (x + w <= 0 || x > WIDTH - 1 || y + h <= 0 || y > HEIGHT - 1)
76  return;
77 
78  if (bitmap == NULL)
79  return;
80 
81  // xOffset technically doesn't need to be 16 bit but the math operations
82  // are measurably faster if it is
83  uint16_t xOffset, ofs;
84  int8_t yOffset = y & 7;
85  int8_t sRow = y / 8;
86  uint8_t loop_h, start_h, rendered_width;
87 
88  if (y < 0 && yOffset > 0) {
89  sRow--;
90  }
91 
92  // if the left side of the render is offscreen skip those loops
93  if (x < 0) {
94  xOffset = abs(x);
95  } else {
96  xOffset = 0;
97  }
98 
99  // if the right side of the render is offscreen skip those loops
100  if (x + w > WIDTH - 1) {
101  rendered_width = ((WIDTH - x) - xOffset);
102  } else {
103  rendered_width = (w - xOffset);
104  }
105 
106  // if the top side of the render is offscreen skip those loops
107  if (sRow < -1) {
108  start_h = abs(sRow) - 1;
109  } else {
110  start_h = 0;
111  }
112 
113  loop_h = h / 8 + (h % 8 > 0 ? 1 : 0); // divide, then round up
114 
115  // if (sRow + loop_h - 1 > (HEIGHT/8)-1)
116  if (sRow + loop_h > (HEIGHT / 8)) {
117  loop_h = (HEIGHT / 8) - sRow;
118  }
119 
120  // prepare variables for loops later so we can compare with 0
121  // instead of comparing two variables
122  loop_h -= start_h;
123 
124  sRow += start_h;
125  ofs = (sRow * WIDTH) + x + xOffset;
126  uint8_t *bofs = (uint8_t *)bitmap + (start_h * w) + xOffset;
127  uint8_t data;
128 
129  uint8_t mul_amt = 1 << yOffset;
130  uint16_t mask_data;
131  uint16_t bitmap_data;
132 
133  switch (draw_mode) {
134  case SPRITE_UNMASKED:
135  // we only want to mask the 8 bits of our own sprite, so we can
136  // calculate the mask before the start of the loop
137  mask_data = ~(0xFF * mul_amt);
138  // really if yOffset = 0 you have a faster case here that could be
139  // optimized
140  for (uint8_t a = 0; a < loop_h; a++) {
141  for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
142  bitmap_data = pgm_read_byte(bofs) * mul_amt;
143 
144  if (sRow >= 0) {
145  data = Arduboy2Base::sBuffer[ofs];
146  data &= (uint8_t)(mask_data);
147  data |= (uint8_t)(bitmap_data);
148  Arduboy2Base::sBuffer[ofs] = data;
149  }
150  if (yOffset != 0 && sRow < 7) {
151  const size_t index = static_cast<uint16_t>(ofs + WIDTH);
152  data = Arduboy2Base::sBuffer[index];
153  data &= (uint8_t)(mask_data >> 8);
154  data |= (uint8_t)(bitmap_data >> 8);
155  Arduboy2Base::sBuffer[index] = data;
156  }
157  ofs++;
158  bofs++;
159  }
160  sRow++;
161  bofs += w - rendered_width;
162  ofs += WIDTH - rendered_width;
163  }
164  break;
165 
166  case SPRITE_IS_MASK:
167  for (uint8_t a = 0; a < loop_h; a++) {
168  for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
169  bitmap_data = pgm_read_byte(bofs) * mul_amt;
170  if (sRow >= 0) {
171  Arduboy2Base::sBuffer[ofs] |= (uint8_t)(bitmap_data);
172  }
173  if (yOffset != 0 && sRow < 7) {
174  const size_t index = static_cast<uint16_t>(ofs + WIDTH);
175  Arduboy2Base::sBuffer[index] |= (uint8_t)(bitmap_data >> 8);
176  }
177  ofs++;
178  bofs++;
179  }
180  sRow++;
181  bofs += w - rendered_width;
182  ofs += WIDTH - rendered_width;
183  }
184  break;
185 
186  case SPRITE_IS_MASK_ERASE:
187  for (uint8_t a = 0; a < loop_h; a++) {
188  for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
189  bitmap_data = pgm_read_byte(bofs) * mul_amt;
190  if (sRow >= 0) {
191  Arduboy2Base::sBuffer[ofs] &= ~(uint8_t)(bitmap_data);
192  }
193  if (yOffset != 0 && sRow < 7) {
194  const size_t index = static_cast<uint16_t>(ofs + WIDTH);
195  Arduboy2Base::sBuffer[index] &= ~(uint8_t)(bitmap_data >> 8);
196  }
197  ofs++;
198  bofs++;
199  }
200  sRow++;
201  bofs += w - rendered_width;
202  ofs += WIDTH - rendered_width;
203  }
204  break;
205 
206  case SPRITE_MASKED:
207  uint8_t *mask_ofs;
208  mask_ofs = (uint8_t *)mask + (start_h * w) + xOffset;
209  for (uint8_t a = 0; a < loop_h; a++) {
210  for (uint8_t iCol = 0; iCol < rendered_width; iCol++) {
211  // NOTE: you might think in the yOffset==0 case that this results
212  // in more effort, but in all my testing the compiler was forcing
213  // 16-bit math to happen here anyways, so this isn't actually
214  // compiling to more code than it otherwise would. If the offset
215  // is 0 the high part of the word will just never be used.
216 
217  // load data and bit shift
218  // mask needs to be bit flipped
219  mask_data = ~(pgm_read_byte(mask_ofs) * mul_amt);
220  bitmap_data = pgm_read_byte(bofs) * mul_amt;
221 
222  if (sRow >= 0) {
223  data = Arduboy2Base::sBuffer[ofs];
224  data &= (uint8_t)(mask_data);
225  data |= (uint8_t)(bitmap_data);
226  Arduboy2Base::sBuffer[ofs] = data;
227  }
228  if (yOffset != 0 && sRow < 7) {
229  const size_t index = static_cast<uint16_t>(ofs + WIDTH);
230  data = Arduboy2Base::sBuffer[index];
231  data &= (uint8_t)(mask_data >> 8);
232  data |= (uint8_t)(bitmap_data >> 8);
233  Arduboy2Base::sBuffer[index] = data;
234  }
235  ofs++;
236  mask_ofs++;
237  bofs++;
238  }
239  sRow++;
240  bofs += w - rendered_width;
241  mask_ofs += w - rendered_width;
242  ofs += WIDTH - rendered_width;
243  }
244  break;
245 
246 
247  case SPRITE_PLUS_MASK:
248  // *2 because we use double the bits (mask + bitmap)
249  bofs = (uint8_t *)(bitmap + ((start_h * w) + xOffset) * 2);
250 
251  uint8_t xi = rendered_width; // counter for x loop below
252 
253  asm volatile(
254  "push r28\n" // save Y
255  "push r29\n"
256  "movw r28, %[buffer_ofs]\n" // Y = buffer_ofs_2
257  "adiw r28, 63\n" // buffer_ofs_2 = buffer_ofs + 128
258  "adiw r28, 63\n"
259  "adiw r28, 2\n"
260  "loop_y:\n"
261  "loop_x:\n"
262  // load bitmap and mask data
263  "lpm %A[bitmap_data], Z+\n"
264  "lpm %A[mask_data], Z+\n"
265 
266  // shift mask and buffer data
267  "tst %[yOffset]\n"
268  "breq skip_shifting\n"
269  "mul %A[bitmap_data], %[mul_amt]\n"
270  "movw %[bitmap_data], r0\n"
271  "mul %A[mask_data], %[mul_amt]\n"
272  "movw %[mask_data], r0\n"
273 
274  // SECOND PAGE
275  // if yOffset != 0 && sRow < 7
276  "cpi %[sRow], 7\n"
277  "brge end_second_page\n"
278  // then
279  "ld %[data], Y\n"
280  "com %B[mask_data]\n" // invert high byte of mask
281  "and %[data], %B[mask_data]\n"
282  "or %[data], %B[bitmap_data]\n"
283  // update buffer, increment
284  "st Y+, %[data]\n"
285 
286  "end_second_page:\n"
287  "skip_shifting:\n"
288 
289  // FIRST PAGE
290  // if sRow >= 0
291  "tst %[sRow]\n"
292  "brmi skip_first_page\n"
293  "ld %[data], %a[buffer_ofs]\n"
294  // then
295  "com %A[mask_data]\n"
296  "and %[data], %A[mask_data]\n"
297  "or %[data], %A[bitmap_data]\n"
298  // update buffer, increment
299  "st %a[buffer_ofs]+, %[data]\n"
300  "jmp end_first_page\n"
301 
302  "skip_first_page:\n"
303  // since no ST Z+ when skipped we need to do this manually
304  "adiw %[buffer_ofs], 1\n"
305 
306  "end_first_page:\n"
307 
308  // "x_loop_next:\n"
309  "dec %[xi]\n"
310  "brne loop_x\n"
311 
312  // increment y
313  "next_loop_y:\n"
314  "dec %[yi]\n"
315  "breq finished\n"
316  "mov %[xi], %[x_count]\n" // reset x counter
317  // sRow++;
318  "inc %[sRow]\n"
319  "clr __zero_reg__\n"
320  // sprite_ofs += (w - rendered_width) * 2;
321  "add %A[sprite_ofs], %A[sprite_ofs_jump]\n"
322  "adc %B[sprite_ofs], __zero_reg__\n"
323  // buffer_ofs += WIDTH - rendered_width;
324  "add %A[buffer_ofs], %A[buffer_ofs_jump]\n"
325  "adc %B[buffer_ofs], __zero_reg__\n"
326  // buffer_ofs_page_2 += WIDTH - rendered_width;
327  "add r28, %A[buffer_ofs_jump]\n"
328  "adc r29, __zero_reg__\n"
329 
330  "rjmp loop_y\n"
331  "finished:\n"
332  // put the Y register back in place
333  "pop r29\n"
334  "pop r28\n"
335  "clr __zero_reg__\n" // just in case
336  : [xi] "+&a" (xi),
337  [yi] "+&a" (loop_h),
338  [sRow] "+&a" (sRow), // CPI requires an upper register (r16-r23)
339  [data] "=&l" (data),
340  [mask_data] "=&l" (mask_data),
341  [bitmap_data] "=&l" (bitmap_data)
342  :
343  [screen_width] "M" (WIDTH),
344  [x_count] "l" (rendered_width), // lower register
345  [sprite_ofs] "z" (bofs),
346  [buffer_ofs] "x" (Arduboy2Base::sBuffer+ofs),
347  [buffer_ofs_jump] "a" (WIDTH-rendered_width), // upper reg (r16-r23)
348  [sprite_ofs_jump] "a" ((w-rendered_width)*2), // upper reg (r16-r23)
349 
350  // [sprite_ofs_jump] "r" (0),
351  [yOffset] "l" (yOffset), // lower register
352  [mul_amt] "l" (mul_amt) // lower register
353  // NOTE: We also clobber r28 and r29 (y) but sometimes the compiler
354  // won't allow us, so in order to make this work we don't tell it
355  // that we clobber them. Instead, we push/pop to preserve them.
356  // Then we need to guarantee that the the compiler doesn't put one of
357  // our own variables into r28/r29.
358  // We do that by specifying all the inputs and outputs use either
359  // lower registers (l) or simple (r16-r23) upper registers (a).
360  : // pushes/clobbers/pops r28 and r29 (y)
361  );
362  break;
363  }
364 }
Sprites.h
A class for drawing animated sprites from image and mask bitmaps.
Arduboy2Base::sBuffer
static uint8_t sBuffer[(HEIGHT *WIDTH)/8]
The display buffer array in RAM.
Definition: Arduboy2.h:1465
Sprites::drawPlusMask
static void drawPlusMask(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
Draw a sprite using an array containing both image and mask values.
Definition: Sprites.cpp:30
WIDTH
#define WIDTH
Definition: Arduboy2Core.h:242
Sprites::drawErase
static void drawErase(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
"Erase" a sprite.
Definition: Sprites.cpp:20
Sprites::drawExternalMask
static void drawExternalMask(int16_t x, int16_t y, const uint8_t *bitmap, const uint8_t *mask, uint8_t frame, uint8_t mask_frame)
Draw a sprite using a separate image and mask array.
Definition: Sprites.cpp:9
Sprites::drawOverwrite
static void drawOverwrite(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
Draw a sprite by replacing the existing content completely.
Definition: Sprites.cpp:15
Sprites::drawSelfMasked
static void drawSelfMasked(int16_t x, int16_t y, const uint8_t *bitmap, uint8_t frame)
Draw a sprite using only the bits set to 1.
Definition: Sprites.cpp:25
HEIGHT
#define HEIGHT
Definition: Arduboy2Core.h:243