FabGL
ESP32 Display Controller and Graphics Library
vgabasecontroller.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 
41 #include "fabutils.h"
42 #include "devdrivers/swgenerator.h"
44 
45 
46 #pragma GCC optimize ("O2")
47 
48 
49 namespace fabgl {
50 
51 
52 #if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
53  volatile uint64_t s_vgapalctrlcycles = 0;
54 #endif
55 
56 
57 
58 VGABaseController::VGABaseController()
59 {
60 }
61 
62 
63 void VGABaseController::init()
64 {
65  m_DMABuffers = nullptr;
66  m_DMABuffersCount = 0;
67  m_DMABuffersHead = nullptr;
68  m_DMABuffersVisible = nullptr;
69  m_primitiveProcessingSuspended = 1; // >0 suspended
70  m_isr_handle = nullptr;
71  m_doubleBufferOverDMA = false;
72  m_viewPort = nullptr;
73  m_viewPortMemoryPool[0] = nullptr;
74 
75  m_GPIOStream.begin();
76 }
77 
78 
79 // initializer for 8 colors configuration
80 void VGABaseController::begin(gpio_num_t redGPIO, gpio_num_t greenGPIO, gpio_num_t blueGPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
81 {
82  init();
83 
84  // GPIO configuration for bit 0
85  setupGPIO(redGPIO, VGA_RED_BIT, GPIO_MODE_OUTPUT);
86  setupGPIO(greenGPIO, VGA_GREEN_BIT, GPIO_MODE_OUTPUT);
87  setupGPIO(blueGPIO, VGA_BLUE_BIT, GPIO_MODE_OUTPUT);
88 
89  // GPIO configuration for VSync and HSync
90  setupGPIO(HSyncGPIO, VGA_HSYNC_BIT, GPIO_MODE_OUTPUT);
91  setupGPIO(VSyncGPIO, VGA_VSYNC_BIT, GPIO_MODE_OUTPUT);
92 
93  RGB222::lowBitOnly = true;
94  m_bitsPerChannel = 1;
95 }
96 
97 
98 // initializer for 64 colors configuration
99 void VGABaseController::begin(gpio_num_t red1GPIO, gpio_num_t red0GPIO, gpio_num_t green1GPIO, gpio_num_t green0GPIO, gpio_num_t blue1GPIO, gpio_num_t blue0GPIO, gpio_num_t HSyncGPIO, gpio_num_t VSyncGPIO)
100 {
101  begin(red0GPIO, green0GPIO, blue0GPIO, HSyncGPIO, VSyncGPIO);
102 
103  // GPIO configuration for bit 1
104  setupGPIO(red1GPIO, VGA_RED_BIT + 1, GPIO_MODE_OUTPUT);
105  setupGPIO(green1GPIO, VGA_GREEN_BIT + 1, GPIO_MODE_OUTPUT);
106  setupGPIO(blue1GPIO, VGA_BLUE_BIT + 1, GPIO_MODE_OUTPUT);
107 
108  RGB222::lowBitOnly = false;
109  m_bitsPerChannel = 2;
110 }
111 
112 
113 // initializer for default configuration
114 void VGABaseController::begin()
115 {
116  begin(GPIO_NUM_22, GPIO_NUM_21, GPIO_NUM_19, GPIO_NUM_18, GPIO_NUM_5, GPIO_NUM_4, GPIO_NUM_23, GPIO_NUM_15);
117 }
118 
119 
120 void VGABaseController::end()
121 {
122  if (m_DMABuffers) {
123  suspendBackgroundPrimitiveExecution();
124  vTaskDelay(50 / portTICK_PERIOD_MS);
125  m_GPIOStream.stop();
126  vTaskDelay(10 / portTICK_PERIOD_MS);
127  if (m_isr_handle) {
128  esp_intr_free(m_isr_handle);
129  m_isr_handle = nullptr;
130  }
131  freeBuffers();
132  }
133 }
134 
135 
136 void VGABaseController::setupGPIO(gpio_num_t gpio, int bit, gpio_mode_t mode)
137 {
138  configureGPIO(gpio, mode);
139  gpio_matrix_out(gpio, I2S1O_DATA_OUT0_IDX + bit, false, false);
140 }
141 
142 
143 void VGABaseController::freeBuffers()
144 {
145  if (m_DMABuffersCount > 0) {
146  heap_caps_free((void*)m_HBlankLine_withVSync);
147  heap_caps_free((void*)m_HBlankLine);
148 
149  freeViewPort();
150 
151  setDMABuffersCount(0);
152  }
153 }
154 
155 
156 void VGABaseController::freeViewPort()
157 {
158  for (uint8_t * * poolPtr = m_viewPortMemoryPool; *poolPtr; ++poolPtr) {
159  heap_caps_free((void*) *poolPtr);
160  *poolPtr = nullptr;
161  }
162  if (m_viewPort) {
163  heap_caps_free(m_viewPort);
164  m_viewPort = nullptr;
165  }
166  if (isDoubleBuffered())
167  heap_caps_free(m_viewPortVisible);
168  m_viewPortVisible = nullptr;
169 }
170 
171 
172 // Can be used to change buffers count, maintainig already set pointers.
173 // If m_doubleBufferOverDMA = true, uses m_DMABuffersHead and m_DMABuffersVisible to implement
174 // double buffer on DMA level.
175 bool VGABaseController::setDMABuffersCount(int buffersCount)
176 {
177  if (buffersCount == 0) {
178  if (m_DMABuffersVisible && m_DMABuffersVisible != m_DMABuffers)
179  heap_caps_free( (void*) m_DMABuffersVisible );
180  heap_caps_free( (void*) m_DMABuffers );
181  m_DMABuffers = nullptr;
182  m_DMABuffersVisible = nullptr;
183  m_DMABuffersCount = 0;
184  return true;
185  }
186 
187  if (buffersCount != m_DMABuffersCount) {
188 
189  // buffers head
190  if (m_doubleBufferOverDMA && m_DMABuffersHead == nullptr) {
191  m_DMABuffersHead = (lldesc_t*) heap_caps_malloc(sizeof(lldesc_t), MALLOC_CAP_DMA);
192  m_DMABuffersHead->eof = m_DMABuffersHead->sosf = m_DMABuffersHead->offset = 0;
193  m_DMABuffersHead->owner = 1;
194  m_DMABuffersHead->size = 0;
195  m_DMABuffersHead->length = 0;
196  m_DMABuffersHead->buf = m_HBlankLine; // dummy valid address. Setting nullptr crashes DMA!
197  m_DMABuffersHead->qe.stqe_next = nullptr; // this will be set before the first frame
198  }
199 
200  // (re)allocate and initialize DMA descs
201  m_DMABuffers = (lldesc_t*) heap_caps_realloc((void*)m_DMABuffers, buffersCount * sizeof(lldesc_t), MALLOC_CAP_DMA);
202  if (m_doubleBufferOverDMA && isDoubleBuffered())
203  m_DMABuffersVisible = (lldesc_t*) heap_caps_realloc((void*)m_DMABuffersVisible, buffersCount * sizeof(lldesc_t), MALLOC_CAP_DMA);
204  else
205  m_DMABuffersVisible = m_DMABuffers;
206  if (!m_DMABuffers || !m_DMABuffersVisible)
207  return false;
208 
209  auto buffersHead = m_DMABuffersHead ? m_DMABuffersHead : &m_DMABuffers[0];
210 
211  for (int i = 0; i < buffersCount; ++i) {
212  m_DMABuffers[i].eof = 0;
213  m_DMABuffers[i].sosf = 0;
214  m_DMABuffers[i].offset = 0;
215  m_DMABuffers[i].owner = 1;
216  m_DMABuffers[i].qe.stqe_next = (lldesc_t*) (i == buffersCount - 1 ? buffersHead : &m_DMABuffers[i + 1]);
217  if (m_doubleBufferOverDMA && isDoubleBuffered()) {
218  m_DMABuffersVisible[i].eof = 0;
219  m_DMABuffersVisible[i].sosf = 0;
220  m_DMABuffersVisible[i].offset = 0;
221  m_DMABuffersVisible[i].owner = 1;
222  m_DMABuffersVisible[i].qe.stqe_next = (lldesc_t*) (i == buffersCount - 1 ? buffersHead : &m_DMABuffersVisible[i + 1]);
223  }
224  }
225 
226  m_DMABuffersCount = buffersCount;
227  }
228 
229  return true;
230 }
231 
232 
233 // modeline syntax:
234 // "label" clock_mhz hdisp hsyncstart hsyncend htotal vdisp vsyncstart vsyncend vtotal (+HSync | -HSync) (+VSync | -VSync) [DoubleScan | QuadScan] [FrontPorchBegins | SyncBegins | BackPorchBegins | VisibleBegins] [MultiScanBlank]
235 bool VGABaseController::convertModelineToTimings(char const * modeline, VGATimings * timings)
236 {
237  float freq;
238  int hdisp, hsyncstart, hsyncend, htotal, vdisp, vsyncstart, vsyncend, vtotal;
239  char HSyncPol = 0, VSyncPol = 0;
240  int pos = 0;
241 
242  int count = sscanf(modeline, "\"%[^\"]\" %g %d %d %d %d %d %d %d %d %n", timings->label, &freq, &hdisp, &hsyncstart, &hsyncend, &htotal, &vdisp, &vsyncstart, &vsyncend, &vtotal, &pos);
243 
244  if (count == 10 && pos > 0) {
245 
246  timings->frequency = freq * 1000000;
247  timings->HVisibleArea = hdisp;
248  timings->HFrontPorch = hsyncstart - hdisp;
249  timings->HSyncPulse = hsyncend - hsyncstart;
250  timings->HBackPorch = htotal - hsyncend;
251  timings->VVisibleArea = vdisp;
252  timings->VFrontPorch = vsyncstart - vdisp;
253  timings->VSyncPulse = vsyncend - vsyncstart;
254  timings->VBackPorch = vtotal - vsyncend;
255  timings->HSyncLogic = '-';
256  timings->VSyncLogic = '-';
257  timings->scanCount = 1;
258  timings->multiScanBlack = 0;
259  timings->HStartingBlock = VGAScanStart::FrontPorch;
260 
261  // get (+HSync | -HSync) (+VSync | -VSync)
262  char const * pc = modeline + pos;
263  for (; *pc; ++pc) {
264  if (*pc == '+' || *pc == '-') {
265  if (!HSyncPol)
266  timings->HSyncLogic = HSyncPol = *pc;
267  else if (!VSyncPol) {
268  timings->VSyncLogic = VSyncPol = *pc;
269  while (*pc && *pc != ' ')
270  ++pc;
271  break;
272  }
273  }
274  }
275 
276  // get [DoubleScan | QuadScan] [FrontPorchBegins | SyncBegins | BackPorchBegins | VisibleBegins] [MultiScanBlank]
277  // actually this gets only the first character
278  while (*pc) {
279  switch (*pc) {
280  case 'D':
281  case 'd':
282  timings->scanCount = 2;
283  break;
284  case 'Q':
285  case 'q':
286  timings->scanCount = 4;
287  break;
288  case 'F':
289  case 'f':
290  timings->HStartingBlock = VGAScanStart::FrontPorch;
291  break;
292  case 'S':
293  case 's':
294  timings->HStartingBlock = VGAScanStart::Sync;
295  break;
296  case 'B':
297  case 'b':
298  timings->HStartingBlock = VGAScanStart::BackPorch;
299  break;
300  case 'V':
301  case 'v':
302  timings->HStartingBlock = VGAScanStart::VisibleArea;
303  break;
304  case 'M':
305  case 'm':
306  timings->multiScanBlack = 1;
307  break;
308  case ' ':
309  ++pc;
310  continue;
311  }
312  ++pc;
313  while (*pc && *pc != ' ')
314  ++pc;
315  }
316 
317  return true;
318 
319  }
320  return false;
321 }
322 
323 
324 // Suspend vertical sync interrupt
325 // Warning: After call to suspendBackgroundPrimitiveExecution() adding primitives may cause a deadlock.
326 // To avoid this a call to "processPrimitives()" should be performed very often.
327 // Can be nested
328 void VGABaseController::suspendBackgroundPrimitiveExecution()
329 {
330  ++m_primitiveProcessingSuspended;
331 }
332 
333 
334 // Resume vertical sync interrupt after suspendBackgroundPrimitiveExecution()
335 // Can be nested
336 void VGABaseController::resumeBackgroundPrimitiveExecution()
337 {
338  m_primitiveProcessingSuspended = tmax(0, m_primitiveProcessingSuspended - 1);
339 }
340 
341 
342 void VGABaseController::startGPIOStream()
343 {
344  m_GPIOStream.play(m_timings.frequency, m_DMABuffers);
345 }
346 
347 
348 void VGABaseController::setResolution(char const * modeline, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
349 {
350  VGATimings timings;
351  if (convertModelineToTimings(modeline, &timings))
352  setResolution(timings, viewPortWidth, viewPortHeight, doubleBuffered);
353 }
354 
355 
356 void VGABaseController::setResolution(VGATimings const& timings, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
357 {
358  // just in case setResolution() was called before
359  end();
360 
361  m_timings = timings;
362 
363  // inform base class about screen size
364  setScreenSize(m_timings.HVisibleArea, m_timings.VVisibleArea);
365 
366  setDoubleBuffered(doubleBuffered);
367 
368  m_HVSync = packHVSync(false, false);
369 
370  m_HLineSize = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch + m_timings.HVisibleArea;
371 
372  m_HBlankLine_withVSync = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
373  m_HBlankLine = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
374 
375  m_viewPortWidth = ~3 & (viewPortWidth <= 0 || viewPortWidth >= m_timings.HVisibleArea ? m_timings.HVisibleArea : viewPortWidth); // view port width must be 32 bit aligned
376  m_viewPortHeight = viewPortHeight <= 0 || viewPortHeight >= m_timings.VVisibleArea ? m_timings.VVisibleArea : viewPortHeight;
377 
378  // adjust view port size if necessary
379  checkViewPortSize();
380 
381  // need to center viewport?
382  m_viewPortCol = (m_timings.HVisibleArea - m_viewPortWidth) / 2;
383  m_viewPortRow = (m_timings.VVisibleArea - m_viewPortHeight) / 2;
384 
385  // view port col and row must be 32 bit aligned
386  m_viewPortCol = m_viewPortCol & ~3;
387  m_viewPortRow = m_viewPortRow & ~3;
388 
389  m_rawFrameHeight = m_timings.VVisibleArea + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch;
390 
391  // allocate DMA descriptors
392  setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
393 
394  // allocate the viewport
395  allocateViewPort();
396 
397  // adjust again view port size if necessary
398  checkViewPortSize();
399 
400  // this may free space if m_viewPortHeight has been reduced
401  setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
402 
403  // fill buffers
404  fillVertBuffers(0);
405  fillHorizBuffers(0);
406 
407  resetPaintState();
408 
409  if (m_doubleBufferOverDMA)
410  m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
411 }
412 
413 
414 // this method may adjust m_viewPortHeight to the actual number of allocated rows.
415 // to reduce memory allocation overhead try to allocate the minimum number of blocks.
416 void VGABaseController::allocateViewPort(uint32_t allocCaps, int rowlen)
417 {
418  int linesCount[FABGLIB_VIEWPORT_MEMORY_POOL_COUNT]; // where store number of lines for each pool
419  int poolsCount = 0; // number of allocated pools
420  int remainingLines = m_viewPortHeight;
421  m_viewPortHeight = 0; // m_viewPortHeight needs to be recalculated
422 
423  if (isDoubleBuffered())
424  remainingLines *= 2;
425 
426  // allocate pools
427  while (remainingLines > 0 && poolsCount < FABGLIB_VIEWPORT_MEMORY_POOL_COUNT) {
428  int largestBlock = heap_caps_get_largest_free_block(allocCaps);
429  linesCount[poolsCount] = tmin(remainingLines, largestBlock / rowlen);
430  if (linesCount[poolsCount] == 0) // no more memory available for lines
431  break;
432  m_viewPortMemoryPool[poolsCount] = (uint8_t*) heap_caps_malloc(linesCount[poolsCount] * rowlen, allocCaps);
433  remainingLines -= linesCount[poolsCount];
434  m_viewPortHeight += linesCount[poolsCount];
435  ++poolsCount;
436  }
437  m_viewPortMemoryPool[poolsCount] = nullptr;
438 
439  // fill m_viewPort[] with line pointers
440  if (isDoubleBuffered()) {
441  m_viewPortHeight /= 2;
442  m_viewPortVisible = (volatile uint8_t * *) heap_caps_malloc(sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
443  }
444  m_viewPort = (volatile uint8_t * *) heap_caps_malloc(sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
445  if (!isDoubleBuffered())
446  m_viewPortVisible = m_viewPort;
447  for (int p = 0, l = 0; p < poolsCount; ++p) {
448  uint8_t * pool = m_viewPortMemoryPool[p];
449  for (int i = 0; i < linesCount[p]; ++i) {
450  if (l + i < m_viewPortHeight)
451  m_viewPort[l + i] = pool;
452  else
453  m_viewPortVisible[l + i - m_viewPortHeight] = pool; // set only when double buffered is enabled
454  pool += rowlen;
455  }
456  l += linesCount[p];
457  }
458 }
459 
460 
461 uint8_t IRAM_ATTR VGABaseController::packHVSync(bool HSync, bool VSync)
462 {
463  uint8_t hsync_value = (m_timings.HSyncLogic == '+' ? (HSync ? 1 : 0) : (HSync ? 0 : 1));
464  uint8_t vsync_value = (m_timings.VSyncLogic == '+' ? (VSync ? 1 : 0) : (VSync ? 0 : 1));
465  return (vsync_value << VGA_VSYNC_BIT) | (hsync_value << VGA_HSYNC_BIT);
466 }
467 
468 
469 uint8_t IRAM_ATTR VGABaseController::preparePixelWithSync(RGB222 rgb, bool HSync, bool VSync)
470 {
471  return packHVSync(HSync, VSync) | (rgb.B << VGA_BLUE_BIT) | (rgb.G << VGA_GREEN_BIT) | (rgb.R << VGA_RED_BIT);
472 }
473 
474 
475 int VGABaseController::calcRequiredDMABuffersCount(int viewPortHeight)
476 {
477  int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
478  int buffersCount = m_timings.scanCount * (m_rawFrameHeight + viewPortHeight);
479 
480  switch (m_timings.HStartingBlock) {
482  // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
483  buffersCount += m_timings.scanCount * (rightPadSize > 0 ? viewPortHeight : 0);
484  break;
485  case VGAScanStart::Sync:
486  // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
487  buffersCount += m_timings.scanCount * viewPortHeight;
488  break;
490  // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
491  buffersCount += m_timings.scanCount * viewPortHeight;
492  break;
494  // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
495  buffersCount += m_timings.scanCount * (m_viewPortCol > 0 ? viewPortHeight : 0);
496  break;
497  }
498 
499  return buffersCount;
500 }
501 
502 
503 // refill buffers changing Front Porch and Back Porch
504 // offsetX : (< 0 : left > 0 right)
505 void VGABaseController::fillHorizBuffers(int offsetX)
506 {
507  // fill all with no hsync
508 
509  fill(m_HBlankLine, 0, m_HLineSize, 0, 0, 0, false, false);
510  fill(m_HBlankLine_withVSync, 0, m_HLineSize, 0, 0, 0, false, true);
511 
512  // calculate hsync pos and fill it
513 
514  int16_t porchSum = m_timings.HFrontPorch + m_timings.HBackPorch;
515  m_timings.HFrontPorch = tmax(8, (int16_t)m_timings.HFrontPorch - offsetX);
516  m_timings.HBackPorch = tmax(8, porchSum - m_timings.HFrontPorch);
517  m_timings.HFrontPorch = porchSum - m_timings.HBackPorch;
518 
519  int syncPos = 0;
520 
521  switch (m_timings.HStartingBlock) {
523  // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
524  syncPos = m_timings.HFrontPorch;
525  break;
526  case VGAScanStart::Sync:
527  // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
528  syncPos = 0;
529  break;
531  // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
532  syncPos = m_timings.HBackPorch + m_timings.HVisibleArea + m_timings.HFrontPorch;
533  break;
535  // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
536  syncPos = m_timings.HVisibleArea + m_timings.HFrontPorch;
537  break;
538  }
539 
540  fill(m_HBlankLine, syncPos, m_timings.HSyncPulse, 0, 0, 0, true, false);
541  fill(m_HBlankLine_withVSync, syncPos, m_timings.HSyncPulse, 0, 0, 0, true, true);
542 }
543 
544 
545 void VGABaseController::fillVertBuffers(int offsetY)
546 {
547  int16_t porchSum = m_timings.VFrontPorch + m_timings.VBackPorch;
548  m_timings.VFrontPorch = tmax(1, (int16_t)m_timings.VFrontPorch - offsetY);
549  m_timings.VBackPorch = tmax(1, porchSum - m_timings.VFrontPorch);
550  m_timings.VFrontPorch = porchSum - m_timings.VBackPorch;
551 
552  // associate buffers pointer to DMA info buffers
553  //
554  // Vertical order:
555  // VisibleArea
556  // Front Porch
557  // Sync
558  // Back Porch
559 
560  int VVisibleArea_pos = 0;
561  int VFrontPorch_pos = VVisibleArea_pos + m_timings.VVisibleArea;
562  int VSync_pos = VFrontPorch_pos + m_timings.VFrontPorch;
563  int VBackPorch_pos = VSync_pos + m_timings.VSyncPulse;
564 
565  int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
566 
567  for (int line = 0, DMABufIdx = 0; line < m_rawFrameHeight; ++line) {
568 
569  bool isVVisibleArea = (line < VFrontPorch_pos);
570  bool isVFrontPorch = (line >= VFrontPorch_pos && line < VSync_pos);
571  bool isVSync = (line >= VSync_pos && line < VBackPorch_pos);
572  bool isVBackPorch = (line >= VBackPorch_pos);
573 
574  for (int scan = 0; scan < m_timings.scanCount; ++scan) {
575 
576  bool isStartOfVertFrontPorch = (line == VFrontPorch_pos && scan == 0);
577 
578  if (isVSync) {
579 
580  setDMABufferBlank(DMABufIdx++, m_HBlankLine_withVSync, m_HLineSize, scan, isStartOfVertFrontPorch);
581 
582  } else if (isVFrontPorch || isVBackPorch) {
583 
584  setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
585 
586  } else if (isVVisibleArea) {
587 
588  int visibleAreaLine = line - VVisibleArea_pos;
589  bool isViewport = visibleAreaLine >= m_viewPortRow && visibleAreaLine < m_viewPortRow + m_viewPortHeight;
590  int HInvisibleAreaSize = m_HLineSize - m_timings.HVisibleArea;
591 
592  if (isViewport) {
593  // visible, this is the viewport
594  switch (m_timings.HStartingBlock) {
596  // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
597  setDMABufferBlank(DMABufIdx++, m_HBlankLine, HInvisibleAreaSize + m_viewPortCol, scan, isStartOfVertFrontPorch);
598  setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
599  if (rightPadSize > 0)
600  setDMABufferBlank(DMABufIdx++, m_HBlankLine + HInvisibleAreaSize, rightPadSize, scan, isStartOfVertFrontPorch);
601  break;
602  case VGAScanStart::Sync:
603  // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
604  setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HSyncPulse + m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
605  setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
606  setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - rightPadSize, m_timings.HFrontPorch + rightPadSize, scan, isStartOfVertFrontPorch);
607  break;
609  // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
610  setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
611  setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
612  setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - m_timings.HSyncPulse - rightPadSize, m_timings.HFrontPorch + m_timings.HSyncPulse + rightPadSize, scan, isStartOfVertFrontPorch);
613  break;
615  // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
616  if (m_viewPortCol > 0)
617  setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_viewPortCol, scan, isStartOfVertFrontPorch);
618  setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
619  setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_timings.HVisibleArea - rightPadSize, HInvisibleAreaSize + rightPadSize, scan, isStartOfVertFrontPorch);
620  break;
621  }
622 
623  } else {
624  // not visible
625  setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
626  }
627 
628  }
629 
630  }
631 
632  }
633 }
634 
635 
636 // address must be allocated with MALLOC_CAP_DMA or even an address of another already allocated buffer
637 // allocated buffer length (in bytes) must be 32 bit aligned
638 // Max length is 4092 bytes
639 void VGABaseController::setDMABufferBlank(int index, void volatile * address, int length, int scan, bool isStartOfVertFrontPorch)
640 {
641  int size = (length + 3) & (~3);
642  m_DMABuffers[index].eof = 0;
643  m_DMABuffers[index].size = size;
644  m_DMABuffers[index].length = length;
645  m_DMABuffers[index].buf = (uint8_t*) address;
646  onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan, false, 0);
647  if (m_doubleBufferOverDMA && isDoubleBuffered()) {
648  m_DMABuffersVisible[index].eof = 0;
649  m_DMABuffersVisible[index].size = size;
650  m_DMABuffersVisible[index].length = length;
651  m_DMABuffersVisible[index].buf = (uint8_t*) address;
652  onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan, false, 0);
653  }
654 }
655 
656 
657 bool VGABaseController::isMultiScanBlackLine(int scan)
658 {
659  return (scan > 0 && m_timings.multiScanBlack == 1 && m_timings.HStartingBlock == FrontPorch);
660 }
661 
662 
663 // address must be allocated with MALLOC_CAP_DMA or even an address of another already allocated buffer
664 // allocated buffer length (in bytes) must be 32 bit aligned
665 // Max length is 4092 bytes
666 void VGABaseController::setDMABufferView(int index, int row, int scan, volatile uint8_t * * viewPort, bool onVisibleDMA)
667 {
668  uint8_t * bufferPtr = nullptr;
669  if (isMultiScanBlackLine(scan))
670  bufferPtr = (uint8_t *) (m_HBlankLine + m_HLineSize - m_timings.HVisibleArea); // this works only when HSYNC, FrontPorch and BackPorch are at the beginning of m_HBlankLine
671  else if (viewPort)
672  bufferPtr = (uint8_t *) viewPort[row];
673  lldesc_t volatile * DMABuffers = onVisibleDMA ? m_DMABuffersVisible : m_DMABuffers;
674  DMABuffers[index].size = (m_viewPortWidth + 3) & (~3);
675  DMABuffers[index].length = m_viewPortWidth;
676  DMABuffers[index].buf = bufferPtr;
677 }
678 
679 
680 // address must be allocated with MALLOC_CAP_DMA or even an address of another already allocated buffer
681 // allocated buffer length (in bytes) must be 32 bit aligned
682 // Max length is 4092 bytes
683 void VGABaseController::setDMABufferView(int index, int row, int scan, bool isStartOfVertFrontPorch)
684 {
685  setDMABufferView(index, row, scan, m_viewPort, false);
686  if (!isMultiScanBlackLine(scan))
687  onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan, true, row);
688  if (isDoubleBuffered()) {
689  setDMABufferView(index, row, scan, m_viewPortVisible, true);
690  if (!isMultiScanBlackLine(scan))
691  onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan, true, row);
692  }
693 }
694 
695 
696 void volatile * VGABaseController::getDMABuffer(int index, int * length)
697 {
698  *length = m_DMABuffers[index].length;
699  return m_DMABuffers[index].buf;
700 }
701 
702 
703 // buffer: buffer to fill (buffer size must be 32 bit aligned)
704 // startPos: initial position (in pixels)
705 // length: number of pixels to fill
706 //
707 // Returns next pos to fill (startPos + length)
708 int VGABaseController::fill(uint8_t volatile * buffer, int startPos, int length, uint8_t red, uint8_t green, uint8_t blue, bool HSync, bool VSync)
709 {
710  uint8_t pattern = preparePixelWithSync((RGB222){red, green, blue}, HSync, VSync);
711  for (int i = 0; i < length; ++i, ++startPos)
712  VGA_PIXELINROW(buffer, startPos) = pattern;
713  return startPos;
714 }
715 
716 
717 void VGABaseController::moveScreen(int offsetX, int offsetY)
718 {
719  suspendBackgroundPrimitiveExecution();
720  fillVertBuffers(offsetY);
721  fillHorizBuffers(offsetX);
722  resumeBackgroundPrimitiveExecution();
723 }
724 
725 
726 void VGABaseController::shrinkScreen(int shrinkX, int shrinkY)
727 {
728  VGATimings * currTimings = getResolutionTimings();
729 
730  currTimings->HBackPorch = tmax(currTimings->HBackPorch + 4 * shrinkX, 4);
731  currTimings->HFrontPorch = tmax(currTimings->HFrontPorch + 4 * shrinkX, 4);
732 
733  currTimings->VBackPorch = tmax(currTimings->VBackPorch + shrinkY, 1);
734  currTimings->VFrontPorch = tmax(currTimings->VFrontPorch + shrinkY, 1);
735 
736  setResolution(*currTimings, m_viewPortWidth, m_viewPortHeight, isDoubleBuffered());
737 }
738 
739 
740 void IRAM_ATTR VGABaseController::swapBuffers()
741 {
742  tswap(m_viewPort, m_viewPortVisible);
743  if (m_doubleBufferOverDMA) {
744  tswap(m_DMABuffers, m_DMABuffersVisible);
745  m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
746  }
747 }
748 
749 
750 
751 
752 } // end of namespace
753 
754 
This file contains fabgl::VGABaseController definition.
This file contains fabgl::GPIOStream definition.
This file contains some utility classes and functions.
#define FABGLIB_VIEWPORT_MEMORY_POOL_COUNT
Definition: fabglconf.h:126
Definition: canvas.cpp:36