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