@@ -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+
90124void 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
97132void 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
242277void 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
296426void Arduboy2Core::blank ()
0 commit comments