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