FabGL
ESP32 Display Controller and Graphics Library
vga8controller.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
28#include <alloca.h>
29#include <stdarg.h>
30#include <math.h>
31#include <string.h>
32
33#include "freertos/FreeRTOS.h"
34#include "freertos/task.h"
35
36#include "soc/i2s_struct.h"
37#include "soc/i2s_reg.h"
38#include "driver/periph_ctrl.h"
39#include "soc/rtc.h"
40#include "esp_spi_flash.h"
41#include "esp_heap_caps.h"
42
43#include "fabutils.h"
44#include "vga8controller.h"
46
47
48
49
50#pragma GCC optimize ("O2")
51
52
53
54namespace fabgl {
55
56/*
57To improve drawing and rendering speed pixels order is a bit oddy because we want to pack pixels (3 bits) into a uint32_t and ESP32 is little-endian.
588 pixels (24 bits) are packed in 3 bytes:
59bytes: 0 1 2 ...
60bits: 76543210 76543210 76543210 ...
61pixels: 55666777 23334445 00011122 ...
62bits24: 0 1...
63*/
64
65static inline __attribute__((always_inline)) void VGA8_SETPIXELINROW(uint8_t * row, int x, int value) {
66 uint32_t * bits24 = (uint32_t*)(row + (x >> 3) * 3); // x / 8 * 3
67 int shift = 21 - (x & 7) * 3;
68 *bits24 ^= ((value << shift) ^ *bits24) & (7 << shift);
69}
70
71static inline __attribute__((always_inline)) int VGA8_GETPIXELINROW(uint8_t * row, int x) {
72 uint32_t * bits24 = (uint32_t*)(row + (x >> 3) * 3); // x / 8 * 3
73 int shift = 21 - (x & 7) * 3;
74 return (*bits24 >> shift) & 7;
75}
76
77#define VGA8_INVERTPIXELINROW(row, x) *((uint32_t*)(row + ((x) >> 3) * 3)) ^= 7 << (21 - ((x) & 7) * 3)
78
79static inline __attribute__((always_inline)) void VGA8_SETPIXEL(int x, int y, int value) {
80 auto row = (uint8_t*) VGA8Controller::sgetScanline(y);
81 uint32_t * bits24 = (uint32_t*)(row + (x >> 3) * 3); // x / 8 * 3
82 int shift = 21 - (x & 7) * 3;
83 *bits24 ^= ((value << shift) ^ *bits24) & (7 << shift);
84}
85
86#define VGA8_GETPIXEL(x, y) VGA8_GETPIXELINROW((uint8_t*)VGA8Controller::s_viewPort[(y)], (x))
87
88#define VGA8_INVERT_PIXEL(x, y) VGA8_INVERTPIXELINROW((uint8_t*)VGA8Controller::s_viewPort[(y)], (x))
89
90
91#define VGA8_COLUMNSQUANTUM 16
92
93
94
95/*************************************************************************************/
96/* VGA8Controller definitions */
97
98
99VGA8Controller * VGA8Controller::s_instance = nullptr;
100
101
102
103
104VGA8Controller::VGA8Controller()
105 : VGAPalettedController(VGA8_LinesCount, VGA8_COLUMNSQUANTUM, NativePixelFormat::PALETTE8, 8, 3, ISRHandler)
106{
107 s_instance = this;
108 m_packedPaletteIndexPair_to_signals = (uint16_t *) heap_caps_malloc(256 * sizeof(uint16_t), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
109}
110
111
112VGA8Controller::~VGA8Controller()
113{
114 heap_caps_free((void *)m_packedPaletteIndexPair_to_signals);
115}
116
117
118void VGA8Controller::setupDefaultPalette()
119{
120 setPaletteItem(0, RGB888(0, 0, 0)); // 0: black
121 setPaletteItem(1, RGB888(128, 0, 0)); // 1: red
122 setPaletteItem(2, RGB888(0, 128, 0)); // 2: green
123 setPaletteItem(3, RGB888(0, 0, 128)); // 3: blue
124 setPaletteItem(4, RGB888(255, 0, 0)); // 4: bright red
125 setPaletteItem(5, RGB888(0, 255, 0)); // 5: bright green
126 setPaletteItem(6, RGB888(0, 0, 255)); // 6: bright blue
127 setPaletteItem(7, RGB888(255, 255, 255)); // 7: white
128}
129
130
131void VGA8Controller::setPaletteItem(int index, RGB888 const & color)
132{
133 index %= 8;
134 m_palette[index] = color;
135 auto packed222 = RGB888toPackedRGB222(color);
136 for (int i = 0; i < 8; ++i) {
137 m_packedPaletteIndexPair_to_signals[(index << 3) | i] &= 0xFF00;
138 m_packedPaletteIndexPair_to_signals[(index << 3) | i] |= (m_HVSync | packed222);
139 m_packedPaletteIndexPair_to_signals[(i << 3) | index] &= 0x00FF;
140 m_packedPaletteIndexPair_to_signals[(i << 3) | index] |= (m_HVSync | packed222) << 8;
141 }
142}
143
144
145void VGA8Controller::setPixelAt(PixelDesc const & pixelDesc, Rect & updateRect)
146{
147 genericSetPixelAt(pixelDesc, updateRect,
148 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
149 VGA8_SETPIXEL
150 );
151}
152
153
154// coordinates are absolute values (not relative to origin)
155// line clipped on current absolute clipping rectangle
156void VGA8Controller::absDrawLine(int X1, int Y1, int X2, int Y2, RGB888 color)
157{
158 genericAbsDrawLine(X1, Y1, X2, Y2, color,
159 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
160 [&] (int Y, int X1, int X2, uint8_t colorIndex) { rawFillRow(Y, X1, X2, colorIndex); },
161 [&] (int Y, int X1, int X2) { rawInvertRow(Y, X1, X2); },
162 VGA8_SETPIXEL,
163 [&] (int X, int Y) { VGA8_INVERT_PIXEL(X, Y); }
164 );
165}
166
167
168// parameters not checked
169void VGA8Controller::rawFillRow(int y, int x1, int x2, RGB888 color)
170{
171 rawFillRow(y, x1, x2, RGB888toPaletteIndex(color));
172}
173
174
175// parameters not checked
176void VGA8Controller::rawFillRow(int y, int x1, int x2, uint8_t colorIndex)
177{
178 uint8_t * row = (uint8_t*) m_viewPort[y];
179 for (; x1 <= x2; ++x1)
180 VGA8_SETPIXELINROW(row, x1, colorIndex);
181}
182
183
184// parameters not checked
185void VGA8Controller::rawInvertRow(int y, int x1, int x2)
186{
187 auto row = m_viewPort[y];
188 for (int x = x1; x <= x2; ++x)
189 VGA8_INVERTPIXELINROW(row, x);
190}
191
192
193void VGA8Controller::rawCopyRow(int x1, int x2, int srcY, int dstY)
194{
195 auto srcRow = (uint8_t*) m_viewPort[srcY];
196 auto dstRow = (uint8_t*) m_viewPort[dstY];
197 for (; x1 <= x2; ++x1)
198 VGA8_SETPIXELINROW(dstRow, x1, VGA8_GETPIXELINROW(srcRow, x1));
199}
200
201
202void VGA8Controller::swapRows(int yA, int yB, int x1, int x2)
203{
204 auto rowA = (uint8_t*) m_viewPort[yA];
205 auto rowB = (uint8_t*) m_viewPort[yB];
206 for (; x1 <= x2; ++x1) {
207 uint8_t a = VGA8_GETPIXELINROW(rowA, x1);
208 uint8_t b = VGA8_GETPIXELINROW(rowB, x1);
209 VGA8_SETPIXELINROW(rowA, x1, b);
210 VGA8_SETPIXELINROW(rowB, x1, a);
211 }
212}
213
214
215void VGA8Controller::drawEllipse(Size const & size, Rect & updateRect)
216{
217 genericDrawEllipse(size, updateRect,
218 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
219 VGA8_SETPIXEL
220 );
221}
222
223
224void VGA8Controller::clear(Rect & updateRect)
225{
226 hideSprites(updateRect);
227 uint8_t paletteIndex = RGB888toPaletteIndex(getActualBrushColor());
228 uint32_t pattern8 = (paletteIndex) | (paletteIndex << 3) | (paletteIndex << 6) | (paletteIndex << 9) | (paletteIndex << 12) | (paletteIndex << 15) | (paletteIndex << 18) | (paletteIndex << 21);
229 for (int y = 0; y < m_viewPortHeight; ++y) {
230 auto dest = (uint8_t*) m_viewPort[y];
231 for (int x = 0; x < m_viewPortWidth; x += 8, dest += 3)
232 *((uint32_t*)dest) = (*((uint32_t*)dest) & 0xFF000000) | pattern8;
233 }
234}
235
236
237// scroll < 0 -> scroll UP
238// scroll > 0 -> scroll DOWN
239void VGA8Controller::VScroll(int scroll, Rect & updateRect)
240{
241 genericVScroll(scroll, updateRect,
242 [&] (int yA, int yB, int x1, int x2) { swapRows(yA, yB, x1, x2); }, // swapRowsCopying
243 [&] (int yA, int yB) { tswap(m_viewPort[yA], m_viewPort[yB]); }, // swapRowsPointers
244 [&] (int y, int x1, int x2, RGB888 color) { rawFillRow(y, x1, x2, color); } // rawFillRow
245 );
246}
247
248
249// todo: optimize!
250void VGA8Controller::HScroll(int scroll, Rect & updateRect)
251{
252 hideSprites(updateRect);
253 uint8_t back = RGB888toPaletteIndex(getActualBrushColor());
254
255 int Y1 = paintState().scrollingRegion.Y1;
256 int Y2 = paintState().scrollingRegion.Y2;
257 int X1 = paintState().scrollingRegion.X1;
258 int X2 = paintState().scrollingRegion.X2;
259
260 int width = X2 - X1 + 1;
261 bool HScrolllingRegionAligned = ((X1 & 7) == 0 && (width & 7) == 0); // 8 pixels aligned
262
263 if (scroll < 0) {
264 // scroll left
265 for (int y = Y1; y <= Y2; ++y) {
266 for (int s = -scroll; s > 0;) {
267 if (HScrolllingRegionAligned && s >= 8) {
268 // scroll left by multiplies of 8
269 uint8_t * row = (uint8_t*) (m_viewPort[y]) + X1 / 8 * 3;
270 auto sc = s & ~7;
271 auto sz = width & ~7;
272 memmove(row, row + sc / 8 * 3, (sz - sc) / 8 * 3);
273 rawFillRow(y, X2 - sc + 1, X2, back);
274 s -= sc;
275 } else {
276 // unaligned horizontal scrolling region, fallback to slow version
277 auto row = (uint8_t*) m_viewPort[y];
278 for (int x = X1; x <= X2 - s; ++x)
279 VGA8_SETPIXELINROW(row, x, VGA8_GETPIXELINROW(row, x + s));
280 // fill right area with brush color
281 rawFillRow(y, X2 - s + 1, X2, back);
282 s = 0;
283 }
284 }
285 }
286 } else if (scroll > 0) {
287 // scroll right
288 for (int y = Y1; y <= Y2; ++y) {
289 for (int s = scroll; s > 0;) {
290 if (HScrolllingRegionAligned && s >= 8) {
291 // scroll right by multiplies of 8
292 uint8_t * row = (uint8_t*) (m_viewPort[y]) + X1 / 8 * 3;
293 auto sc = s & ~7;
294 auto sz = width & ~7;
295 memmove(row + sc / 8 * 3, row, (sz - sc) / 8 * 3);
296 rawFillRow(y, X1, X1 + sc - 1, back);
297 s -= sc;
298 } else {
299 // unaligned horizontal scrolling region, fallback to slow version
300 auto row = (uint8_t*) m_viewPort[y];
301 for (int x = X2 - s; x >= X1; --x)
302 VGA8_SETPIXELINROW(row, x + s, VGA8_GETPIXELINROW(row, x));
303 // fill left area with brush color
304 rawFillRow(y, X1, X1 + s - 1, back);
305 s = 0;
306 }
307 }
308 }
309 }
310}
311
312
313void VGA8Controller::drawGlyph(Glyph const & glyph, GlyphOptions glyphOptions, RGB888 penColor, RGB888 brushColor, Rect & updateRect)
314{
315 genericDrawGlyph(glyph, glyphOptions, penColor, brushColor, updateRect,
316 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
317 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
318 VGA8_SETPIXELINROW
319 );
320}
321
322
323void VGA8Controller::invertRect(Rect const & rect, Rect & updateRect)
324{
325 genericInvertRect(rect, updateRect,
326 [&] (int Y, int X1, int X2) { rawInvertRow(Y, X1, X2); }
327 );
328}
329
330
331void VGA8Controller::swapFGBG(Rect const & rect, Rect & updateRect)
332{
333 genericSwapFGBG(rect, updateRect,
334 [&] (RGB888 const & color) { return RGB888toPaletteIndex(color); },
335 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
336 VGA8_GETPIXELINROW,
337 VGA8_SETPIXELINROW
338 );
339}
340
341
342// Slow operation!
343// supports overlapping of source and dest rectangles
344void VGA8Controller::copyRect(Rect const & source, Rect & updateRect)
345{
346 genericCopyRect(source, updateRect,
347 [&] (int y) { return (uint8_t*) m_viewPort[y]; },
348 VGA8_GETPIXELINROW,
349 VGA8_SETPIXELINROW
350 );
351}
352
353
354// no bounds check is done!
355void VGA8Controller::readScreen(Rect const & rect, RGB888 * destBuf)
356{
357 for (int y = rect.Y1; y <= rect.Y2; ++y) {
358 auto row = (uint8_t*) m_viewPort[y];
359 for (int x = rect.X1; x <= rect.X2; ++x, ++destBuf) {
360 const RGB222 v = m_palette[VGA8_GETPIXELINROW(row, x)];
361 *destBuf = RGB888(v.R * 85, v.G * 85, v.B * 85); // 85 x 3 = 255
362 }
363 }
364}
365
366
367void VGA8Controller::rawDrawBitmap_Native(int destX, int destY, Bitmap const * bitmap, int X1, int Y1, int XCount, int YCount)
368{
369 genericRawDrawBitmap_Native(destX, destY, (uint8_t*) bitmap->data, bitmap->width, X1, Y1, XCount, YCount,
370 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
371 VGA8_SETPIXELINROW
372 );
373}
374
375
376void VGA8Controller::rawDrawBitmap_Mask(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
377{
378 auto foregroundColorIndex = RGB888toPaletteIndex(bitmap->foregroundColor);
379 genericRawDrawBitmap_Mask(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
380 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
381 VGA8_GETPIXELINROW,
382 [&] (uint8_t * row, int x) { VGA8_SETPIXELINROW(row, x, foregroundColorIndex); } // rawSetPixelInRow
383 );
384}
385
386
387void VGA8Controller::rawDrawBitmap_RGBA2222(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
388{
389 genericRawDrawBitmap_RGBA2222(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
390 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
391 VGA8_GETPIXELINROW,
392 [&] (uint8_t * row, int x, uint8_t src) { VGA8_SETPIXELINROW(row, x, RGB2222toPaletteIndex(src)); } // rawSetPixelInRow
393 );
394}
395
396
397void VGA8Controller::rawDrawBitmap_RGBA8888(int destX, int destY, Bitmap const * bitmap, void * saveBackground, int X1, int Y1, int XCount, int YCount)
398{
399 genericRawDrawBitmap_RGBA8888(destX, destY, bitmap, (uint8_t*)saveBackground, X1, Y1, XCount, YCount,
400 [&] (int y) { return (uint8_t*) m_viewPort[y]; }, // rawGetRow
401 [&] (uint8_t * row, int x) { return VGA8_GETPIXELINROW(row, x); }, // rawGetPixelInRow
402 [&] (uint8_t * row, int x, RGBA8888 const & src) { VGA8_SETPIXELINROW(row, x, RGB8888toPaletteIndex(src)); } // rawSetPixelInRow
403 );
404}
405
406
407void VGA8Controller::directSetPixel(int x, int y, int value)
408{
409 VGA8_SETPIXEL(x, y, value);
410}
411
412
413void IRAM_ATTR VGA8Controller::ISRHandler(void * arg)
414{
415 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
416 auto s1 = getCycleCount();
417 #endif
418
419 auto ctrl = (VGA8Controller *) arg;
420
421 if (I2S1.int_st.out_eof) {
422
423 auto const desc = (lldesc_t*) I2S1.out_eof_des_addr;
424
425 if (desc == s_frameResetDesc)
426 s_scanLine = 0;
427
428 auto const width = ctrl->m_viewPortWidth;
429 auto const height = ctrl->m_viewPortHeight;
430 auto const packedPaletteIndexPair_to_signals = (uint16_t const *) ctrl->m_packedPaletteIndexPair_to_signals;
431 auto const lines = ctrl->m_lines;
432
433 int scanLine = (s_scanLine + VGA8_LinesCount / 2) % height;
434
435 auto lineIndex = scanLine & (VGA8_LinesCount - 1);
436
437 for (int i = 0; i < VGA8_LinesCount / 2; ++i) {
438
439 auto src = (uint8_t const *) s_viewPortVisible[scanLine];
440 auto dest = (uint16_t*) lines[lineIndex];
441
442 // optimization warn: horizontal resolution must be a multiple of 16!
443 for (int col = 0; col < width; col += 16) {
444
445 auto w1 = *((uint16_t*)(src )); // hi A:23334445, lo A:55666777
446 auto w2 = *((uint16_t*)(src + 2)); // hi B:55666777, lo A:00011122
447 auto w3 = *((uint16_t*)(src + 4)); // hi B:00011122, lo B:23334445
448
449 PSRAM_HACK;
450
451 auto src1 = w1 | (w2 << 16);
452 auto src2 = (w2 >> 8) | (w3 << 8);
453
454 auto v1 = packedPaletteIndexPair_to_signals[(src1 ) & 0x3f]; // pixels 0, 1
455 auto v2 = packedPaletteIndexPair_to_signals[(src1 >> 6) & 0x3f]; // pixels 2, 3
456 auto v3 = packedPaletteIndexPair_to_signals[(src1 >> 12) & 0x3f]; // pixels 4, 5
457 auto v4 = packedPaletteIndexPair_to_signals[(src1 >> 18) & 0x3f]; // pixels 6, 7
458 auto v5 = packedPaletteIndexPair_to_signals[(src2 ) & 0x3f]; // pixels 8, 9
459 auto v6 = packedPaletteIndexPair_to_signals[(src2 >> 6) & 0x3f]; // pixels 10, 11
460 auto v7 = packedPaletteIndexPair_to_signals[(src2 >> 12) & 0x3f]; // pixels 12, 13
461 auto v8 = packedPaletteIndexPair_to_signals[(src2 >> 18) & 0x3f]; // pixels 14, 15
462
463 *(dest + 2) = v1;
464 *(dest + 3) = v2;
465 *(dest + 0) = v3;
466 *(dest + 1) = v4;
467 *(dest + 6) = v5;
468 *(dest + 7) = v6;
469 *(dest + 4) = v7;
470 *(dest + 5) = v8;
471
472 dest += 8;
473 src += 6;
474
475 }
476
477 ++lineIndex;
478 ++scanLine;
479 }
480
481 s_scanLine += VGA8_LinesCount / 2;
482
483 if (scanLine >= height && !ctrl->m_primitiveProcessingSuspended && spi_flash_cache_enabled() && ctrl->m_primitiveExecTask) {
484 // vertical sync, unlock primitive execution task
485 // warn: don't use vTaskSuspendAll() in primitive drawing, otherwise vTaskNotifyGiveFromISR may be blocked and screen will flick!
486 vTaskNotifyGiveFromISR(ctrl->m_primitiveExecTask, NULL);
487 }
488
489 }
490
491 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
492 s_vgapalctrlcycles += getCycleCount() - s1;
493 #endif
494
495 I2S1.int_clr.val = I2S1.int_st.val;
496}
497
498
499
500
501
502} // 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.
Represents a 24 bit RGB color.
Represents a rectangle.
Definition: fabutils.h:248
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGA8Controller definition.