Skip to content

Commit 8fc561e

Browse files
committed
draw directly to screen only changes and mark updated rows once (speed improvement)
1 parent 35f8346 commit 8fc561e

1 file changed

Lines changed: 168 additions & 38 deletions

File tree

Arduboy2Playdate/Arduboy2Core.cpp

Lines changed: 168 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -87,18 +87,54 @@ const PROGMEM uint8_t Arduboy2Core::lcdBootProgram[] = {
8787
// 0x22, 0x00, PAGE_ADDRESS_END
8888
};
8989

90+
// prevBuffer tracks last painted state for dirty detection (markUpdatedRows only)
91+
static uint8_t prevBuffer[(HEIGHT * WIDTH) / 8] = {0};
92+
93+
// Fullscreen src->dst range LUTs: for each src pixel, first and last dst pixel it maps to
94+
#define FS_DST_W 398
95+
#define FS_DST_H 236
96+
#define FS_X_OFF 1
97+
#define FS_Y_OFF 2
98+
static uint16_t fsColDstStart[WIDTH];
99+
static uint16_t fsColDstEnd[WIDTH];
100+
static uint8_t fsRowDstStart[HEIGHT];
101+
static uint8_t fsRowDstEnd[HEIGHT];
102+
static bool fsLutBuilt = false;
103+
static bool forceFullRedraw = false;
104+
105+
static void buildFullscreenLut()
106+
{
107+
for (int sc = 0; sc < WIDTH; sc++) {
108+
int start = (int)(sc * 3.11f);
109+
int end = (int)((sc + 1) * 3.11f) - 1;
110+
if (end >= FS_DST_W) end = FS_DST_W - 1;
111+
fsColDstStart[sc] = (uint16_t)start;
112+
fsColDstEnd[sc] = (uint16_t)end;
113+
}
114+
for (int sr = 0; sr < HEIGHT; sr++) {
115+
int start = (int)(sr * 3.7f);
116+
int end = (int)((sr + 1) * 3.7f) - 1;
117+
if (end >= FS_DST_H) end = FS_DST_H - 1;
118+
fsRowDstStart[sr] = (uint8_t)start;
119+
fsRowDstEnd[sr] = (uint8_t)end;
120+
}
121+
fsLutBuilt = true;
122+
}
123+
90124
void toggleFullscreen(void *userdata) {
91125
arduboyFullscreenEnabled = pd->system->getMenuItemValue(arduboyFullScreenMenuItem);
92-
//clear left over stuff from going to fullscreen to none fullscreen
93126
if(!arduboyFullscreenEnabled)
94127
pd->graphics->clear(kColorBlack);
128+
// Force full redraw after mode switch
129+
forceFullRedraw = true;
95130
}
96131

97132
void toggleFps(void *userdata) {
98133
arduboyFpsEnabled = pd->system->getMenuItemValue(arduboyFpsMenuItem);
99134
//clear left over fps display
100135
if(!arduboyFpsEnabled)
101136
pd->graphics->clear(kColorBlack);
137+
forceFullRedraw = true;
102138
}
103139

104140

@@ -237,60 +273,154 @@ void Arduboy2Core::paintScreen(const uint8_t *image)
237273
// The following assembly code runs "open loop". It relies on instruction
238274
// execution times to allow time for each byte of data to be clocked out.
239275
// It is specifically tuned for a 16MHz CPU clock and SPI clocking at 8MHz.
240-
static uint8_t prevBuffer[(HEIGHT * WIDTH) / 8] = {0};
241276

242277
void Arduboy2Core::paintScreen(uint8_t image[], bool clear)
243278
{
244-
pd->graphics->getBitmapData(screenBitmap, &dummy, &dummy, &rowBytes, &dummy2, &buffer);
279+
uint8_t* frame = pd->graphics->getFrame();
245280

246-
int minDirtyRow = HEIGHT, maxDirtyRow = -1;
281+
LCDBitmap* dbg = NULL;
282+
if (pd->graphics->getDebugBitmap != NULL)
283+
dbg = pd->graphics->getDebugBitmap();
247284

248-
for (int page = 0; page < HEIGHT / 8; page++)
285+
if (!arduboyFullscreenEnabled)
249286
{
250-
int baseRow = page * 8;
251-
for (int col = 0; col < WIDTH; col++)
252-
{
253-
uint8_t byte = image[page * WIDTH + col];
254-
uint8_t prev = prevBuffer[page * WIDTH + col];
255-
if (byte == prev)
256-
continue; // skip unchanged column slices
287+
const int pdRowBytes = LCD_ROWSIZE;
288+
const int xOffset = 9;
289+
const int yOffset = 25;
257290

258-
prevBuffer[page * WIDTH + col] = byte;
259-
uint8_t changed = byte ^ prev; // only bits that changed
291+
int minDirtyRow = 240, maxDirtyRow = -1;
260292

261-
int dstByteCol = col / 8;
262-
uint8_t setMask = 0x80 >> (col % 8);
263-
uint8_t clrMask = ~setMask;
293+
for (int page = 0; page < HEIGHT / 8; page++)
294+
{
295+
int baseRow = page * 8;
296+
for (int col = 0; col < WIDTH; col++)
297+
{
298+
uint8_t byte = image[page * WIDTH + col];
299+
uint8_t prev = prevBuffer[page * WIDTH + col];
300+
bool dirty = (byte != prev);
301+
if (dirty)
302+
prevBuffer[page * WIDTH + col] = byte;
303+
304+
int px0 = xOffset + col * 3;
305+
int b0 = px0 / 8, b1 = (px0 + 1) / 8, b2 = (px0 + 2) / 8;
306+
uint8_t m0 = 0x80 >> (px0 % 8);
307+
uint8_t m1 = 0x80 >> ((px0 + 1) % 8);
308+
uint8_t m2 = 0x80 >> ((px0 + 2) % 8);
309+
uint8_t c0 = ~m0, c1 = ~m1, c2 = ~m2;
310+
311+
for (int bit = 0; bit < 8; bit++)
312+
{
313+
bool on = byte & (1 << bit);
314+
int srcRow = baseRow + bit;
315+
int pdY0 = yOffset + srcRow * 3;
316+
317+
for (int dy = 0; dy < 3; dy++)
318+
{
319+
int rowStart = (pdY0 + dy) * pdRowBytes;
320+
if (on) {
321+
frame[rowStart + b0] |= m0;
322+
frame[rowStart + b1] |= m1;
323+
frame[rowStart + b2] |= m2;
324+
} else {
325+
frame[rowStart + b0] &= c0;
326+
frame[rowStart + b1] &= c1;
327+
frame[rowStart + b2] &= c2;
328+
}
329+
if (dirty) {
330+
if ((pdY0 + dy) < minDirtyRow) minDirtyRow = pdY0 + dy;
331+
if ((pdY0 + dy) > maxDirtyRow) maxDirtyRow = pdY0 + dy;
332+
}
333+
}
334+
}
335+
}
336+
}
337+
338+
if (forceFullRedraw)
339+
{
340+
minDirtyRow = yOffset;
341+
maxDirtyRow = yOffset + HEIGHT * 3 - 1;
342+
}
264343

265-
for (int bit = 0; bit < 8; bit++)
344+
if (maxDirtyRow >= 0)
345+
{
346+
pd->graphics->markUpdatedRows(minDirtyRow, maxDirtyRow);
347+
if (dbg)
266348
{
267-
if (!(changed & (1 << bit)))
268-
continue; // skip unchanged pixels
269-
int row = baseRow + bit;
270-
if (row < minDirtyRow) minDirtyRow = row;
271-
if (row > maxDirtyRow) maxDirtyRow = row;
272-
uint8_t* dst = buffer + row * rowBytes + dstByteCol;
273-
if (byte & (1 << bit))
274-
*dst |= setMask;
275-
else
276-
*dst &= clrMask;
349+
pd->graphics->pushContext(dbg);
350+
pd->graphics->fillRect(0, minDirtyRow, 400, maxDirtyRow - minDirtyRow + 1, kColorWhite);
351+
pd->graphics->popContext();
277352
}
278353
}
354+
forceFullRedraw = false;
279355
}
280-
281-
if (maxDirtyRow >= 0)
356+
else
282357
{
283-
if (arduboyFullscreenEnabled)
284-
pd->graphics->drawScaledBitmap(screenBitmap, 0, 0, 3.11f, 3.7f);
285-
else
286-
pd->graphics->drawScaledBitmap(screenBitmap, 9, 25, 3.0f, 3.0f);
358+
if (!fsLutBuilt)
359+
buildFullscreenLut();
360+
361+
const int pdRowBytes = LCD_ROWSIZE;
362+
int minDirtyRow = 240, maxDirtyRow = -1;
363+
364+
for (int page = 0; page < HEIGHT / 8; page++)
365+
{
366+
int baseRow = page * 8;
367+
for (int col = 0; col < WIDTH; col++)
368+
{
369+
uint8_t byte = image[page * WIDTH + col];
370+
uint8_t prev = prevBuffer[page * WIDTH + col];
371+
bool dirty = (byte != prev);
372+
if (dirty || forceFullRedraw)
373+
prevBuffer[page * WIDTH + col] = byte;
374+
375+
uint8_t changed = (forceFullRedraw || dirty) ? (forceFullRedraw ? 0xFF : (byte ^ prev)) : 0;
376+
if (!changed)
377+
continue;
378+
379+
int dxStart = FS_X_OFF + fsColDstStart[col];
380+
int dxEnd = FS_X_OFF + fsColDstEnd[col];
381+
382+
for (int bit = 0; bit < 8; bit++)
383+
{
384+
if (!(changed & (1 << bit)))
385+
continue;
386+
387+
bool on = byte & (1 << bit);
388+
int srcRow = baseRow + bit;
389+
int dyStart = FS_Y_OFF + fsRowDstStart[srcRow];
390+
int dyEnd = FS_Y_OFF + fsRowDstEnd[srcRow];
391+
392+
for (int pdY = dyStart; pdY <= dyEnd; pdY++)
393+
{
394+
int rowStart = pdY * pdRowBytes;
395+
for (int pdX = dxStart; pdX <= dxEnd; pdX++)
396+
{
397+
uint8_t* dst = frame + rowStart + (pdX / 8);
398+
uint8_t mask = 0x80 >> (pdX % 8);
399+
if (on) *dst |= mask;
400+
else *dst &= ~mask;
401+
}
402+
if (pdY < minDirtyRow) minDirtyRow = pdY;
403+
if (pdY > maxDirtyRow) maxDirtyRow = pdY;
404+
}
405+
}
406+
}
407+
}
408+
409+
if (maxDirtyRow >= 0)
410+
{
411+
pd->graphics->markUpdatedRows(minDirtyRow, maxDirtyRow);
412+
if (dbg)
413+
{
414+
pd->graphics->pushContext(dbg);
415+
pd->graphics->fillRect(0, minDirtyRow, 400, maxDirtyRow - minDirtyRow + 1, kColorWhite);
416+
pd->graphics->popContext();
417+
}
418+
}
419+
forceFullRedraw = false;
287420
}
288421

289-
if (clear) {
290-
pd->graphics->clearBitmap(screenBitmap, kColorBlack);
422+
if (clear)
291423
memset(image, 0, (HEIGHT * WIDTH) / 8);
292-
memset(prevBuffer, 0, sizeof(prevBuffer));
293-
}
294424
}
295425

296426
void Arduboy2Core::blank()

0 commit comments

Comments
 (0)