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