FabGL
ESP32 Display Controller and Graphics Library
vga16controller.cpp
1/*
2 Created by Fabrizio Di Vittorio (fdivitto2013@gmail.com) - <http://www.fabgl.com>
3 Copyright (c) 2019-2022 Fabrizio Di Vittorio.
4 All rights reserved.
5
6
7* Please contact fdivitto2013@gmail.com if you need a commercial license.
8
9
10* This library and related software is available under GPL v3.
11
12 FabGL is free software: you can redistribute it and/or modify
13 it under the terms of the GNU General Public License as published by
14 the Free Software Foundation, either version 3 of the License, or
15 (at your option) any later version.
16
17 FabGL is distributed in the hope that it will be useful,
18 but WITHOUT ANY WARRANTY; without even the implied warranty of
19 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
20 GNU General Public License for more details.
21
22 You should have received a copy of the GNU General Public License
23 along with FabGL. If not, see <http://www.gnu.org/licenses/>.
24 */
25
26
27#include <alloca.h>
28#include <stdarg.h>
29#include <math.h>
30#include <string.h>
31
32#include "freertos/FreeRTOS.h"
33#include "freertos/task.h"
34
35#include "soc/i2s_struct.h"
36#include "soc/i2s_reg.h"
37#include "driver/periph_ctrl.h"
38#include "soc/rtc.h"
39#include "esp_spi_flash.h"
40#include "esp_heap_caps.h"
41
42#include "fabutils.h"
43#include "vga16controller.h"
45
46
47
48#pragma GCC optimize ("O2")
49
50
51
52
53namespace fabgl {
54
55
56
57// high nibble is pixel 0, low nibble is pixel 1
58
59static inline __attribute__((always_inline)) void VGA16_SETPIXELINROW(uint8_t * row, int x, int value) {
60 int brow = x >> 1;
61 row[brow] = (x & 1) ? ((row[brow] & 0xf0) | value) : ((row[brow] & 0x0f) | (value << 4));
62}
63
64static inline __attribute__((always_inline)) int VGA16_GETPIXELINROW(uint8_t * row, int x) {
65 int brow = x >> 1;
66 return ((x & 1) ? (row[brow] & 0x0f) : ((row[brow] & 0xf0) >> 4));
67}
68
69#define VGA16_INVERTPIXELINROW(row, x) (row)[(x) >> 1] ^= (0xf0 >> (((x) & 1) << 2))
70
71static inline __attribute__((always_inline)) void VGA16_SETPIXEL(int x, int y, int value) {
72 auto row = (uint8_t*) VGA16Controller::sgetScanline(y);
73 int brow = x >> 1;
74 row[brow] = (x & 1) ? ((row[brow] & 0xf0) | value) : ((row[brow] & 0x0f) | (value << 4));
75}
76
77#define VGA16_GETPIXEL(x, y) VGA16_GETPIXELINROW((uint8_t*)VGA16Controller::s_viewPort[(y)], (x))
78
79#define VGA16_INVERT_PIXEL(x, y) VGA16_INVERTPIXELINROW((uint8_t*)VGA16Controller::s_viewPort[(y)], (x))
80
81
82#define VGA16_COLUMNSQUANTUM 16
83
84
85/*************************************************************************************/
86/* VGA16Controller definitions */
87
88
89VGA16Controller * VGA16Controller::s_instance = nullptr;
90
91
92
93VGA16Controller::VGA16Controller()
94 : VGAPalettedController(VGA16_LinesCount, VGA16_COLUMNSQUANTUM, NativePixelFormat::PALETTE16, 2, 1, ISRHandler)
95{
96 s_instance = this;
97}
98
99
100void VGA16Controller::setupDefaultPalette()
101{
102 for (int colorIndex = 0; colorIndex < 16; ++colorIndex) {
103 RGB888 rgb888((Color)colorIndex);
104 setPaletteItem(colorIndex, rgb888);
105 }
106}
107
108
109void VGA16Controller::setPaletteItem(int index, RGB888 const & color)
110{
111 index %= 16;
112 m_palette[index] = color;
113 auto packed222 = RGB888toPackedRGB222(color);
114 for (int i = 0; i < 16; ++i) {
115 m_packedPaletteIndexPair_to_signals[(index << 4) | i] &= 0xFF00;
116 m_packedPaletteIndexPair_to_signals[(index << 4) | i] |= (m_HVSync | packed222);
117 m_packedPaletteIndexPair_to_signals[(i << 4) | index] &= 0x00FF;
118 m_packedPaletteIndexPair_to_signals[(i << 4) | index] |= (m_HVSync | packed222) << 8;
119 }
120}
121
122
123void VGA16Controller::setPixelAt(PixelDesc const & pixelDesc, Rect & updateRect)
124{
125 genericSetPixelAt(pixelDesc, updateRect,
126 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
127 VGA16_SETPIXEL
128 );
129}
130
131
132// coordinates are absolute values (not relative to origin)
133// line clipped on current absolute clipping rectangle
134void VGA16Controller::absDrawLine(int X1, int Y1, int X2, int Y2, RGB888 color)
135{
136 genericAbsDrawLine(X1, Y1, X2, Y2, color,
137 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
138 [&] (int Y, int X1, int X2, uint8_t colorIndex) { rawFillRow(Y, X1, X2, colorIndex); },
139 [&] (int Y, int X1, int X2) { rawInvertRow(Y, X1, X2); },
140 VGA16_SETPIXEL,
141 [&] (int X, int Y) { VGA16_INVERT_PIXEL(X, Y); }
142 );
143}
144
145
146// parameters not checked
147void VGA16Controller::rawFillRow(int y, int x1, int x2, RGB888 color)
148{
149 rawFillRow(y, x1, x2, RGB888toPaletteIndex(color));
150}
151
152
153// parameters not checked
154void VGA16Controller::rawFillRow(int y, int x1, int x2, uint8_t colorIndex)
155{
156 uint8_t * row = (uint8_t*) m_viewPort[y];
157 // fill first pixels before full 16 bits word
158 int x = x1;
159 for (; x <= x2 && (x & 3) != 0; ++x) {
160 VGA16_SETPIXELINROW(row, x, colorIndex);
161 }
162 // fill whole 16 bits words (4 pixels)
163 if (x <= x2) {
164 int sz = (x2 & ~3) - x;
165 memset((void*)(row + x / 2), colorIndex | (colorIndex << 4), sz / 2);
166 x += sz;
167 }
168 // fill last unaligned pixels
169 for (; x <= x2; ++x) {
170 VGA16_SETPIXELINROW(row, x, colorIndex);
171 }
172}
173
174
175// parameters not checked
176void VGA16Controller::rawInvertRow(int y, int x1, int x2)
177{
178 auto row = m_viewPort[y];
179 for (int x = x1; x <= x2; ++x)
180 VGA16_INVERTPIXELINROW(row, x);
181}
182
183
184void VGA16Controller::rawCopyRow(int x1, int x2, int srcY, int dstY)
185{
186 auto srcRow = (uint8_t*) m_viewPort[srcY];
187 auto dstRow = (uint8_t*) m_viewPort[dstY];
188 // copy first pixels before full 16 bits word
189 int x = x1;
190 for (; x <= x2 && (x & 3) != 0; ++x) {
191 VGA16_SETPIXELINROW(dstRow, x, VGA16_GETPIXELINROW(srcRow, x));
192 }
193 // copy whole 16 bits words (4 pixels)
194 auto src = (uint16_t*)(srcRow + x / 2);
195 auto dst = (uint16_t*)(dstRow + x / 2);
196 for (int right = (x2 & ~3); x < right; x += 4)
197 *dst++ = *src++;
198 // copy last unaligned pixels
199 for (x = (x2 & ~3); x <= x2; ++x) {
200 VGA16_SETPIXELINROW(dstRow, x, VGA16_GETPIXELINROW(srcRow, x));
201 }
202}
203
204
205void VGA16Controller::swapRows(int yA, int yB, int x1, int x2)
206{
207 auto rowA = (uint8_t*) m_viewPort[yA];
208 auto rowB = (uint8_t*) m_viewPort[yB];
209 // swap first pixels before full 16 bits word
210 int x = x1;
211 for (; x <= x2 && (x & 3) != 0; ++x) {
212 uint8_t a = VGA16_GETPIXELINROW(rowA, x);
213 uint8_t b = VGA16_GETPIXELINROW(rowB, x);
214 VGA16_SETPIXELINROW(rowA, x, b);
215 VGA16_SETPIXELINROW(rowB, x, a);
216 }
217 // swap whole 16 bits words (4 pixels)
218 auto a = (uint16_t*)(rowA + x / 2);
219 auto b = (uint16_t*)(rowB + x / 2);
220 for (int right = (x2 & ~3); x < right; x += 4)
221 tswap(*a++, *b++);
222 // swap last unaligned pixels
223 for (x = (x2 & ~3); x <= x2; ++x) {
224 uint8_t a = VGA16_GETPIXELINROW(rowA, x);
225 uint8_t b = VGA16_GETPIXELINROW(rowB, x);
226 VGA16_SETPIXELINROW(rowA, x, b);
227 VGA16_SETPIXELINROW(rowB, x, a);
228 }
229}
230
231
232void VGA16Controller::drawEllipse(Size const & size, Rect & updateRect)
233{
234 genericDrawEllipse(size, updateRect,
235 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
236 VGA16_SETPIXEL
237 );
238}
239
240
241void VGA16Controller::clear(Rect & updateRect)
242{
243 hideSprites(updateRect);
244 uint8_t paletteIndex = RGB888toPaletteIndex(getActualBrushColor());
245 uint8_t pattern = paletteIndex | (paletteIndex << 4);
246 for (int y = 0; y < m_viewPortHeight; ++y)
247 memset((uint8_t*) m_viewPort[y], pattern, m_viewPortWidth / 2);
248}
249
250
251// scroll < 0 -> scroll UP
252// scroll > 0 -> scroll DOWN
253void VGA16Controller::VScroll(int scroll, Rect & updateRect)
254{
255 genericVScroll(scroll, updateRect,
256 [&] (int yA, int yB, int x1, int x2) { swapRows(yA, yB, x1, x2); }, // swapRowsCopying
257 [&] (int yA, int yB) { tswap(m_viewPort[yA], m_viewPort[yB]); }, // swapRowsPointers
258 [&] (int y, int x1, int x2, RGB888 color) { rawFillRow(y, x1, x2, color); } // rawFillRow
259 );
260}
261
262
263void VGA16Controller::HScroll(int scroll, Rect & updateRect)
264{
265 hideSprites(updateRect);
266 uint8_t back4 = RGB888toPaletteIndex(getActualBrushColor());
267
268 int Y1 = paintState().scrollingRegion.Y1;
269 int Y2 = paintState().scrollingRegion.Y2;
270 int X1 = paintState().scrollingRegion.X1;
271 int X2 = paintState().scrollingRegion.X2;
272
273 int width = X2 - X1 + 1;
274 bool HScrolllingRegionAligned = ((X1 & 3) == 0 && (width & 3) == 0); // 4 pixels aligned
275
276 if (scroll < 0) {
277 // scroll left
278 for (int y = Y1; y <= Y2; ++y) {
279 if (HScrolllingRegionAligned) {
280 // aligned horizontal scrolling region, fast version
281 uint8_t * row = (uint8_t*) (m_viewPort[y]) + X1 / 2;
282 for (int s = -scroll; s > 0;) {
283 if (s > 1) {
284 // scroll left by 2, 4, 6, ... moving bytes
285 auto sc = s & ~1;
286 auto sz = width & ~1;
287 memmove(row, row + sc / 2, (sz - sc) / 2);
288 rawFillRow(y, X2 - sc + 1, X2, back4);
289 s -= sc;
290 } else if (s & 1) {
291 // scroll left 1 pixel (uint16_t at the time = 4 pixels)
292 // nibbles 0,1,2... P is prev or background
293 // byte : 01 23 45 67 -> 12 34 56 7P
294 // word (little endian CPU) : 2301 6745 -> 3412 7P56
295 auto prev = back4;
296 auto w = (uint16_t *) (row + width / 2) - 1;
297 for (int i = 0; i < width; i += 4) {
298 const uint16_t p4 = *w; // four pixels
299 *w-- = (p4 << 4 & 0xf000) | (prev << 8 & 0x0f00) | (p4 << 4 & 0x00f0) | (p4 >> 12 & 0x000f);
300 prev = p4 >> 4 & 0x000f;
301 }
302 --s;
303 }
304 }
305 } else {
306 // unaligned horizontal scrolling region, fallback to slow version
307 auto row = (uint8_t*) m_viewPort[y];
308 for (int x = X1; x <= X2 + scroll; ++x)
309 VGA16_SETPIXELINROW(row, x, VGA16_GETPIXELINROW(row, x - scroll));
310 // fill right area with brush color
311 rawFillRow(y, X2 + 1 + scroll, X2, back4);
312 }
313 }
314 } else if (scroll > 0) {
315 // scroll right
316 for (int y = Y1; y <= Y2; ++y) {
317 if (HScrolllingRegionAligned) {
318 // aligned horizontal scrolling region, fast version
319 uint8_t * row = (uint8_t*) (m_viewPort[y]) + X1 / 2;
320 for (int s = scroll; s > 0;) {
321 if (s > 1) {
322 // scroll right by 2, 4, 6, ... moving bytes
323 auto sc = s & ~1;
324 auto sz = width & ~1;
325 memmove(row + sc / 2, row, (sz - sc) / 2);
326 rawFillRow(y, X1, X1 + sc - 1, back4);
327 s -= sc;
328 } else if (s & 1) {
329 // scroll right 1 pixel (uint16_t at the time = 4 pixels)
330 // nibbles 0,1,2... P is prev or background
331 // byte : 01 23 45 67 -> P0 12 34 56 7...
332 // word (little endian CPU) : 2301 6745 -> 12P0 5634 ...
333 auto prev = back4;
334 auto w = (uint16_t *) row;
335 for (int i = 0; i < width; i += 4) {
336 const uint16_t p4 = *w; // four pixels
337 *w++ = (p4 << 12 & 0xf000) | (p4 >> 4 & 0x0f00) | (prev << 4) | (p4 >> 4 & 0x000f);
338 prev = p4 >> 8 & 0x000f;
339 }
340 --s;
341 }
342 }
343 } else {
344 // unaligned horizontal scrolling region, fallback to slow version
345 auto row = (uint8_t*) m_viewPort[y];
346 for (int x = X2 - scroll; x >= X1; --x)
347 VGA16_SETPIXELINROW(row, x + scroll, VGA16_GETPIXELINROW(row, x));
348 // fill left area with brush color
349 rawFillRow(y, X1, X1 + scroll - 1, back4);
350 }
351 }
352
353 }
354}
355
356
357void VGA16Controller::drawGlyph(Glyph const & glyph, GlyphOptions glyphOptions, RGB888 penColor, RGB888 brushColor, Rect & updateRect)
358{
359 genericDrawGlyph(glyph, glyphOptions, penColor, brushColor, updateRect,
360 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
361 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
362 VGA16_SETPIXELINROW
363 );
364}
365
366
367void VGA16Controller::invertRect(Rect const & rect, Rect & updateRect)
368{
369 genericInvertRect(rect, updateRect,
370 [&] (int Y, int X1, int X2) { rawInvertRow(Y, X1, X2); }
371 );
372}
373
374
375void VGA16Controller::swapFGBG(Rect const & rect, Rect & updateRect)
376{
377 genericSwapFGBG(rect, updateRect,
378 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
379 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
380 VGA16_GETPIXELINROW,
381 VGA16_SETPIXELINROW
382 );
383}
384
385
386// Slow operation!
387// supports overlapping of source and dest rectangles
388void VGA16Controller::copyRect(Rect const & source, Rect & updateRect)
389{
390 genericCopyRect(source, updateRect,
391 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
392 VGA16_GETPIXELINROW,
393 VGA16_SETPIXELINROW
394 );
395}
396
397
398// no bounds check is done!
399void VGA16Controller::readScreen(Rect const & rect, RGB888 * destBuf)
400{
401 for (int y = rect.Y1; y <= rect.Y2; ++y) {
402 auto row = (uint8_t*) m_viewPort[y];
403 for (int x = rect.X1; x <= rect.X2; ++x, ++destBuf) {
404 const RGB222 v = m_palette[VGA16_GETPIXELINROW(row, x)];
405 *destBuf = RGB888(v.R * 85, v.G * 85, v.B * 85); // 85 x 3 = 255
406 }
407 }
408}
409
410
411void VGA16Controller::rawDrawBitmap_Native(int destX, int destY, Bitmap const * bitmap, int X1, int Y1, int XCount, int YCount)
412{
413 genericRawDrawBitmap_Native(destX, destY, (uint8_t*) bitmap->data, bitmap->width, X1, Y1, XCount, YCount,
414 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
415 VGA16_SETPIXELINROW
416 );
417}
418
419
420void VGA16Controller::rawDrawBitmap_Mask(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
421{
422 auto foregroundColorIndex = RGB888toPaletteIndex(bitmap->foregroundColor);
423 genericRawDrawBitmap_Mask(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
424 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
425 VGA16_GETPIXELINROW,
426 [&] (uint8_t * row, int x) { VGA16_SETPIXELINROW(row, x, foregroundColorIndex); } // rawSetPixelInRow
427 );
428}
429
430
431void VGA16Controller::rawDrawBitmap_RGBA2222(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
432{
433 genericRawDrawBitmap_RGBA2222(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
434 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
435 VGA16_GETPIXELINROW,
436 [&] (uint8_t * row, int x, uint8_t src) { VGA16_SETPIXELINROW(row, x, RGB2222toPaletteIndex(src)); } // rawSetPixelInRow
437 );
438}
439
440
441void VGA16Controller::rawDrawBitmap_RGBA8888(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
442{
443 genericRawDrawBitmap_RGBA8888(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
444 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
445 [&] (uint8_t * row, int x) { return VGA16_GETPIXELINROW(row, x); }, // rawGetPixelInRow
446 [&] (uint8_t * row, int x, RGBA8888 const & src) { VGA16_SETPIXELINROW(row, x, RGB8888toPaletteIndex(src)); } // rawSetPixelInRow
447 );
448}
449
450
451void IRAM_ATTR VGA16Controller::ISRHandler(void * arg)
452{
453 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
454 auto s1 = getCycleCount();
455 #endif
456
457 auto ctrl = (VGA16Controller *) arg;
458
459 if (I2S1.int_st.out_eof) {
460
461 auto const desc = (lldesc_t*) I2S1.out_eof_des_addr;
462
463 if (desc == s_frameResetDesc)
464 s_scanLine = 0;
465
466 auto const width = ctrl->m_viewPortWidth;
467 auto const height = ctrl->m_viewPortHeight;
468 auto const packedPaletteIndexPair_to_signals = (uint16_t const *) ctrl->m_packedPaletteIndexPair_to_signals;
469 auto const lines = ctrl->m_lines;
470
471 int scanLine = (s_scanLine + VGA16_LinesCount / 2) % height;
472
473 auto lineIndex = scanLine & (VGA16_LinesCount - 1);
474
475 for (int i = 0; i < VGA16_LinesCount / 2; ++i) {
476
477 auto src = (uint8_t const *) s_viewPortVisible[scanLine];
478 auto dest = (uint16_t*) lines[lineIndex];
479
480 // optimization warn: horizontal resolution must be a multiple of 16!
481 for (int col = 0; col < width; col += 16) {
482
483 auto src1 = *(src + 0);
484 auto src2 = *(src + 1);
485 auto src3 = *(src + 2);
486 auto src4 = *(src + 3);
487 auto src5 = *(src + 4);
488 auto src6 = *(src + 5);
489 auto src7 = *(src + 6);
490 auto src8 = *(src + 7);
491
492 PSRAM_HACK;
493
494 auto v1 = packedPaletteIndexPair_to_signals[src1];
495 auto v2 = packedPaletteIndexPair_to_signals[src2];
496 auto v3 = packedPaletteIndexPair_to_signals[src3];
497 auto v4 = packedPaletteIndexPair_to_signals[src4];
498 auto v5 = packedPaletteIndexPair_to_signals[src5];
499 auto v6 = packedPaletteIndexPair_to_signals[src6];
500 auto v7 = packedPaletteIndexPair_to_signals[src7];
501 auto v8 = packedPaletteIndexPair_to_signals[src8];
502
503 *(dest + 1) = v1;
504 *(dest ) = v2;
505 *(dest + 3) = v3;
506 *(dest + 2) = v4;
507 *(dest + 5) = v5;
508 *(dest + 4) = v6;
509 *(dest + 7) = v7;
510 *(dest + 6) = v8;
511
512 dest += 8;
513 src += 8;
514
515 }
516
517 ++lineIndex;
518 ++scanLine;
519 }
520
521 s_scanLine += VGA16_LinesCount / 2;
522
523 if (scanLine >= height && !ctrl->m_primitiveProcessingSuspended && spi_flash_cache_enabled() && ctrl->m_primitiveExecTask) {
524 // vertical sync, unlock primitive execution task
525 // warn: don't use vTaskSuspendAll() in primitive drawing, otherwise vTaskNotifyGiveFromISR may be blocked and screen will flick!
526 vTaskNotifyGiveFromISR(ctrl->m_primitiveExecTask, NULL);
527 }
528
529 }
530
531 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
532 s_vgapalctrlcycles += getCycleCount() - s1;
533 #endif
534
535 I2S1.int_clr.val = I2S1.int_st.val;
536}
537
538
539
540} // end of namespace
uint8_t width
int16_t X
uint8_t swapFGBG
int16_t Y
uint8_t height
int16_t X1
Definition: fabutils.h:0
int16_t Y2
Definition: fabutils.h:3
int16_t X2
Definition: fabutils.h:2
int16_t Y1
Definition: fabutils.h:1
This file contains some utility classes and functions.
NativePixelFormat
This enum defines the display controller native pixel format.
Color
This enum defines named colors.
Represents a 24 bit RGB color.
Represents a rectangle.
Definition: fabutils.h:248
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGA16Controller definition.