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-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
41#include "fabutils.h"
44
45
46#pragma GCC optimize ("O2")
47
48
49namespace fabgl {
50
51
52#if FABGLIB_VGAXCONTROLLER_PERFORMANCE_CHECK
53 volatile uint64_t s_vgapalctrlcycles = 0;
54#endif
55
56
57
58VGABaseController::VGABaseController()
59{
60}
61
62
63void 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
80void 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
99void 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
114void 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
120void 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
136void 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
143void 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
156void 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.
175bool 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]
235bool 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::VisibleArea;
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 } else if (*pc != ' ')
274 break;
275 }
276
277 // get [DoubleScan | QuadScan] [FrontPorchBegins | SyncBegins | BackPorchBegins | VisibleBegins] [MultiScanBlank]
278 // actually this gets only the first character
279 while (*pc) {
280 switch (*pc) {
281 case 'D':
282 case 'd':
283 timings->scanCount = 2;
284 break;
285 case 'Q':
286 case 'q':
287 timings->scanCount = 4;
288 break;
289 case 'F':
290 case 'f':
291 timings->HStartingBlock = VGAScanStart::FrontPorch;
292 break;
293 case 'S':
294 case 's':
295 timings->HStartingBlock = VGAScanStart::Sync;
296 break;
297 case 'B':
298 case 'b':
299 timings->HStartingBlock = VGAScanStart::BackPorch;
300 break;
301 case 'V':
302 case 'v':
303 timings->HStartingBlock = VGAScanStart::VisibleArea;
304 break;
305 case 'M':
306 case 'm':
307 timings->multiScanBlack = 1;
308 break;
309 case ' ':
310 ++pc;
311 continue;
312 }
313 ++pc;
314 while (*pc && *pc != ' ')
315 ++pc;
316 }
317
318 return true;
319
320 }
321 return false;
322}
323
324
325// Suspend vertical sync interrupt
326// Warning: After call to suspendBackgroundPrimitiveExecution() adding primitives may cause a deadlock.
327// To avoid this a call to "processPrimitives()" should be performed very often.
328// Can be nested
329void VGABaseController::suspendBackgroundPrimitiveExecution()
330{
331 ++m_primitiveProcessingSuspended;
332}
333
334
335// Resume vertical sync interrupt after suspendBackgroundPrimitiveExecution()
336// Can be nested
337void VGABaseController::resumeBackgroundPrimitiveExecution()
338{
339 m_primitiveProcessingSuspended = tmax(0, m_primitiveProcessingSuspended - 1);
340}
341
342
343void VGABaseController::startGPIOStream()
344{
345 m_GPIOStream.play(m_timings.frequency, m_DMABuffers);
346}
347
348
349void VGABaseController::setResolution(char const * modeline, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
350{
351 VGATimings timings;
352 if (convertModelineToTimings(modeline, &timings))
353 setResolution(timings, viewPortWidth, viewPortHeight, doubleBuffered);
354}
355
356
357void VGABaseController::setResolution(VGATimings const& timings, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
358{
359 // just in case setResolution() was called before
360 end();
361
362 m_timings = timings;
363
364 // inform base class about screen size
365 setScreenSize(m_timings.HVisibleArea, m_timings.VVisibleArea);
366
367 setDoubleBuffered(doubleBuffered);
368
369 m_HVSync = packHVSync(false, false);
370
371 m_HLineSize = m_timings.HFrontPorch + m_timings.HSyncPulse + m_timings.HBackPorch + m_timings.HVisibleArea;
372
373 m_HBlankLine_withVSync = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
374 m_HBlankLine = (uint8_t*) heap_caps_malloc(m_HLineSize, MALLOC_CAP_DMA);
375
376 m_viewPortWidth = ~3 & (viewPortWidth <= 0 || viewPortWidth >= m_timings.HVisibleArea ? m_timings.HVisibleArea : viewPortWidth); // view port width must be 32 bit aligned
377 m_viewPortHeight = viewPortHeight <= 0 || viewPortHeight >= m_timings.VVisibleArea ? m_timings.VVisibleArea : viewPortHeight;
378
379 // adjust view port size if necessary
380 checkViewPortSize();
381
382 // need to center viewport?
383 m_viewPortCol = (m_timings.HVisibleArea - m_viewPortWidth) / 2;
384 m_viewPortRow = (m_timings.VVisibleArea - m_viewPortHeight) / 2;
385
386 // view port col and row must be 32 bit aligned
387 m_viewPortCol = m_viewPortCol & ~3;
388 m_viewPortRow = m_viewPortRow & ~3;
389
390 m_rawFrameHeight = m_timings.VVisibleArea + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch;
391
392 // allocate DMA descriptors
393 setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
394
395 // allocate the viewport
396 allocateViewPort();
397
398 // adjust again view port size if necessary
399 checkViewPortSize();
400
401 // this may free space if m_viewPortHeight has been reduced
402 setDMABuffersCount(calcRequiredDMABuffersCount(m_viewPortHeight));
403
404 // fill buffers
405 fillVertBuffers(0);
406 fillHorizBuffers(0);
407
408 resetPaintState();
409
410 if (m_doubleBufferOverDMA)
411 m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
412}
413
414
415// this method may adjust m_viewPortHeight to the actual number of allocated rows.
416// to reduce memory allocation overhead try to allocate the minimum number of blocks.
417void VGABaseController::allocateViewPort(uint32_t allocCaps, int rowlen)
418{
419 int linesCount[FABGLIB_VIEWPORT_MEMORY_POOL_COUNT]; // where store number of lines for each pool
420 int poolsCount = 0; // number of allocated pools
421 int remainingLines = m_viewPortHeight;
422 m_viewPortHeight = 0; // m_viewPortHeight needs to be recalculated
423
424 if (isDoubleBuffered())
425 remainingLines *= 2;
426
427 // allocate pools
428 while (remainingLines > 0 && poolsCount < FABGLIB_VIEWPORT_MEMORY_POOL_COUNT) {
429 int largestBlock = heap_caps_get_largest_free_block(allocCaps);
430 linesCount[poolsCount] = tmin(remainingLines, largestBlock / rowlen);
431 if (linesCount[poolsCount] == 0) // no more memory available for lines
432 break;
433 m_viewPortMemoryPool[poolsCount] = (uint8_t*) heap_caps_malloc(linesCount[poolsCount] * rowlen, allocCaps);
434 remainingLines -= linesCount[poolsCount];
435 m_viewPortHeight += linesCount[poolsCount];
436 ++poolsCount;
437 }
438 m_viewPortMemoryPool[poolsCount] = nullptr;
439
440 // fill m_viewPort[] with line pointers
441 if (isDoubleBuffered()) {
442 m_viewPortHeight /= 2;
443 m_viewPortVisible = (volatile uint8_t * *) heap_caps_malloc(sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
444 }
445 m_viewPort = (volatile uint8_t * *) heap_caps_malloc(sizeof(uint8_t*) * m_viewPortHeight, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
446 if (!isDoubleBuffered())
447 m_viewPortVisible = m_viewPort;
448 for (int p = 0, l = 0; p < poolsCount; ++p) {
449 uint8_t * pool = m_viewPortMemoryPool[p];
450 for (int i = 0; i < linesCount[p]; ++i) {
451 if (l + i < m_viewPortHeight)
452 m_viewPort[l + i] = pool;
453 else
454 m_viewPortVisible[l + i - m_viewPortHeight] = pool; // set only when double buffered is enabled
455 pool += rowlen;
456 }
457 l += linesCount[p];
458 }
459}
460
461
462uint8_t IRAM_ATTR VGABaseController::packHVSync(bool HSync, bool VSync)
463{
464 uint8_t hsync_value = (m_timings.HSyncLogic == '+' ? (HSync ? 1 : 0) : (HSync ? 0 : 1));
465 uint8_t vsync_value = (m_timings.VSyncLogic == '+' ? (VSync ? 1 : 0) : (VSync ? 0 : 1));
466 return (vsync_value << VGA_VSYNC_BIT) | (hsync_value << VGA_HSYNC_BIT);
467}
468
469
470uint8_t IRAM_ATTR VGABaseController::preparePixelWithSync(RGB222 rgb, bool HSync, bool VSync)
471{
472 return packHVSync(HSync, VSync) | (rgb.B << VGA_BLUE_BIT) | (rgb.G << VGA_GREEN_BIT) | (rgb.R << VGA_RED_BIT);
473}
474
475
476int VGABaseController::calcRequiredDMABuffersCount(int viewPortHeight)
477{
478 int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
479 int buffersCount = m_timings.scanCount * (m_rawFrameHeight + viewPortHeight);
480
481 switch (m_timings.HStartingBlock) {
483 // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
484 buffersCount += m_timings.scanCount * (rightPadSize > 0 ? viewPortHeight : 0);
485 break;
487 // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
488 buffersCount += m_timings.scanCount * viewPortHeight;
489 break;
491 // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
492 buffersCount += m_timings.scanCount * viewPortHeight;
493 break;
495 // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
496 buffersCount += m_timings.scanCount * (m_viewPortCol > 0 ? viewPortHeight : 0);
497 break;
498 }
499
500 return buffersCount;
501}
502
503
504// refill buffers changing Front Porch and Back Porch
505// offsetX : (< 0 : left > 0 right)
506void VGABaseController::fillHorizBuffers(int offsetX)
507{
508 // fill all with no hsync
509
510 fill(m_HBlankLine, 0, m_HLineSize, 0, 0, 0, false, false);
511 fill(m_HBlankLine_withVSync, 0, m_HLineSize, 0, 0, 0, false, true);
512
513 // calculate hsync pos and fill it
514
515 int16_t porchSum = m_timings.HFrontPorch + m_timings.HBackPorch;
516 m_timings.HFrontPorch = tmax(8, (int16_t)m_timings.HFrontPorch - offsetX);
517 m_timings.HBackPorch = tmax(8, porchSum - m_timings.HFrontPorch);
518 m_timings.HFrontPorch = porchSum - m_timings.HBackPorch;
519
520 int syncPos = 0;
521
522 switch (m_timings.HStartingBlock) {
524 // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
525 syncPos = m_timings.HFrontPorch;
526 break;
528 // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
529 syncPos = 0;
530 break;
532 // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
533 syncPos = m_timings.HBackPorch + m_timings.HVisibleArea + m_timings.HFrontPorch;
534 break;
536 // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
537 syncPos = m_timings.HVisibleArea + m_timings.HFrontPorch;
538 break;
539 }
540
541 fill(m_HBlankLine, syncPos, m_timings.HSyncPulse, 0, 0, 0, true, false);
542 fill(m_HBlankLine_withVSync, syncPos, m_timings.HSyncPulse, 0, 0, 0, true, true);
543}
544
545
546void VGABaseController::fillVertBuffers(int offsetY)
547{
548 int16_t porchSum = m_timings.VFrontPorch + m_timings.VBackPorch;
549 m_timings.VFrontPorch = tmax(1, (int16_t)m_timings.VFrontPorch - offsetY);
550 m_timings.VBackPorch = tmax(1, porchSum - m_timings.VFrontPorch);
551 m_timings.VFrontPorch = porchSum - m_timings.VBackPorch;
552
553 // associate buffers pointer to DMA info buffers
554 //
555 // Vertical order:
556 // VisibleArea
557 // Front Porch
558 // Sync
559 // Back Porch
560
561 int VVisibleArea_pos = 0;
562 int VFrontPorch_pos = VVisibleArea_pos + m_timings.VVisibleArea;
563 int VSync_pos = VFrontPorch_pos + m_timings.VFrontPorch;
564 int VBackPorch_pos = VSync_pos + m_timings.VSyncPulse;
565
566 int rightPadSize = m_timings.HVisibleArea - m_viewPortWidth - m_viewPortCol;
567
568 for (int line = 0, DMABufIdx = 0; line < m_rawFrameHeight; ++line) {
569
570 bool isVVisibleArea = (line < VFrontPorch_pos);
571 bool isVFrontPorch = (line >= VFrontPorch_pos && line < VSync_pos);
572 bool isVSync = (line >= VSync_pos && line < VBackPorch_pos);
573 bool isVBackPorch = (line >= VBackPorch_pos);
574
575 for (int scan = 0; scan < m_timings.scanCount; ++scan) {
576
577 bool isStartOfVertFrontPorch = (line == VFrontPorch_pos && scan == 0);
578
579 if (isVSync) {
580
581 setDMABufferBlank(DMABufIdx++, m_HBlankLine_withVSync, m_HLineSize, scan, isStartOfVertFrontPorch);
582
583 } else if (isVFrontPorch || isVBackPorch) {
584
585 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
586
587 } else if (isVVisibleArea) {
588
589 int visibleAreaLine = line - VVisibleArea_pos;
590 bool isViewport = visibleAreaLine >= m_viewPortRow && visibleAreaLine < m_viewPortRow + m_viewPortHeight;
591 int HInvisibleAreaSize = m_HLineSize - m_timings.HVisibleArea;
592
593 if (isViewport) {
594 // visible, this is the viewport
595 switch (m_timings.HStartingBlock) {
597 // FRONTPORCH -> SYNC -> BACKPORCH -> VISIBLEAREA
598 setDMABufferBlank(DMABufIdx++, m_HBlankLine, HInvisibleAreaSize + m_viewPortCol, scan, isStartOfVertFrontPorch);
599 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
600 if (rightPadSize > 0)
601 setDMABufferBlank(DMABufIdx++, m_HBlankLine + HInvisibleAreaSize, rightPadSize, scan, isStartOfVertFrontPorch);
602 break;
604 // SYNC -> BACKPORCH -> VISIBLEAREA -> FRONTPORCH
605 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HSyncPulse + m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
606 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
607 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - rightPadSize, m_timings.HFrontPorch + rightPadSize, scan, isStartOfVertFrontPorch);
608 break;
610 // BACKPORCH -> VISIBLEAREA -> FRONTPORCH -> SYNC
611 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_timings.HBackPorch + m_viewPortCol, scan, isStartOfVertFrontPorch);
612 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
613 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_HLineSize - m_timings.HFrontPorch - m_timings.HSyncPulse - rightPadSize, m_timings.HFrontPorch + m_timings.HSyncPulse + rightPadSize, scan, isStartOfVertFrontPorch);
614 break;
616 // VISIBLEAREA -> FRONTPORCH -> SYNC -> BACKPORCH
617 if (m_viewPortCol > 0)
618 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_viewPortCol, scan, isStartOfVertFrontPorch);
619 setDMABufferView(DMABufIdx++, visibleAreaLine - m_viewPortRow, scan, isStartOfVertFrontPorch);
620 setDMABufferBlank(DMABufIdx++, m_HBlankLine + m_timings.HVisibleArea - rightPadSize, HInvisibleAreaSize + rightPadSize, scan, isStartOfVertFrontPorch);
621 break;
622 }
623
624 } else {
625 // not visible
626 setDMABufferBlank(DMABufIdx++, m_HBlankLine, m_HLineSize, scan, isStartOfVertFrontPorch);
627 }
628
629 }
630
631 }
632
633 }
634}
635
636
637// address must be allocated with MALLOC_CAP_DMA or even an address of another already allocated buffer
638// allocated buffer length (in bytes) must be 32 bit aligned
639// Max length is 4092 bytes
640void VGABaseController::setDMABufferBlank(int index, void volatile * address, int length, int scan, bool isStartOfVertFrontPorch)
641{
642 int size = (length + 3) & (~3);
643 m_DMABuffers[index].eof = 0;
644 m_DMABuffers[index].size = size;
645 m_DMABuffers[index].length = length;
646 m_DMABuffers[index].buf = (uint8_t*) address;
647 onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan, false, 0);
648 if (m_doubleBufferOverDMA && isDoubleBuffered()) {
649 m_DMABuffersVisible[index].eof = 0;
650 m_DMABuffersVisible[index].size = size;
651 m_DMABuffersVisible[index].length = length;
652 m_DMABuffersVisible[index].buf = (uint8_t*) address;
653 onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan, false, 0);
654 }
655}
656
657
658bool VGABaseController::isMultiScanBlackLine(int scan)
659{
660 return (scan > 0 && m_timings.multiScanBlack == 1 && m_timings.HStartingBlock == FrontPorch);
661}
662
663
664// address must be allocated with MALLOC_CAP_DMA or even an address of another already allocated buffer
665// allocated buffer length (in bytes) must be 32 bit aligned
666// Max length is 4092 bytes
667void VGABaseController::setDMABufferView(int index, int row, int scan, volatile uint8_t * * viewPort, bool onVisibleDMA)
668{
669 uint8_t * bufferPtr = nullptr;
670 if (isMultiScanBlackLine(scan))
671 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
672 else if (viewPort)
673 bufferPtr = (uint8_t *) viewPort[row];
674 lldesc_t volatile * DMABuffers = onVisibleDMA ? m_DMABuffersVisible : m_DMABuffers;
675 DMABuffers[index].size = (m_viewPortWidth + 3) & (~3);
676 DMABuffers[index].length = m_viewPortWidth;
677 DMABuffers[index].buf = bufferPtr;
678}
679
680
681// address must be allocated with MALLOC_CAP_DMA or even an address of another already allocated buffer
682// allocated buffer length (in bytes) must be 32 bit aligned
683// Max length is 4092 bytes
684void VGABaseController::setDMABufferView(int index, int row, int scan, bool isStartOfVertFrontPorch)
685{
686 setDMABufferView(index, row, scan, m_viewPort, false);
687 if (!isMultiScanBlackLine(scan))
688 onSetupDMABuffer(&m_DMABuffers[index], isStartOfVertFrontPorch, scan, true, row);
689 if (isDoubleBuffered()) {
690 setDMABufferView(index, row, scan, m_viewPortVisible, true);
691 if (!isMultiScanBlackLine(scan))
692 onSetupDMABuffer(&m_DMABuffersVisible[index], isStartOfVertFrontPorch, scan, true, row);
693 }
694}
695
696
697void volatile * VGABaseController::getDMABuffer(int index, int * length)
698{
699 *length = m_DMABuffers[index].length;
700 return m_DMABuffers[index].buf;
701}
702
703
704// buffer: buffer to fill (buffer size must be 32 bit aligned)
705// startPos: initial position (in pixels)
706// length: number of pixels to fill
707//
708// Returns next pos to fill (startPos + length)
709int VGABaseController::fill(uint8_t volatile * buffer, int startPos, int length, uint8_t red, uint8_t green, uint8_t blue, bool HSync, bool VSync)
710{
711 uint8_t pattern = preparePixelWithSync((RGB222){red, green, blue}, HSync, VSync);
712 for (int i = 0; i < length; ++i, ++startPos)
713 VGA_PIXELINROW(buffer, startPos) = pattern;
714 return startPos;
715}
716
717
718void VGABaseController::moveScreen(int offsetX, int offsetY)
719{
720 suspendBackgroundPrimitiveExecution();
721 fillVertBuffers(offsetY);
722 fillHorizBuffers(offsetX);
723 resumeBackgroundPrimitiveExecution();
724}
725
726
727void VGABaseController::shrinkScreen(int shrinkX, int shrinkY)
728{
729 VGATimings * currTimings = getResolutionTimings();
730
731 currTimings->HBackPorch = tmax(currTimings->HBackPorch + 4 * shrinkX, 4);
732 currTimings->HFrontPorch = tmax(currTimings->HFrontPorch + 4 * shrinkX, 4);
733
734 currTimings->VBackPorch = tmax(currTimings->VBackPorch + shrinkY, 1);
735 currTimings->VFrontPorch = tmax(currTimings->VFrontPorch + shrinkY, 1);
736
737 setResolution(*currTimings, m_viewPortWidth, m_viewPortHeight, isDoubleBuffered());
738}
739
740
741void IRAM_ATTR VGABaseController::swapBuffers()
742{
743 tswap(m_viewPort, m_viewPortVisible);
744 if (m_doubleBufferOverDMA) {
745 tswap(m_DMABuffers, m_DMABuffersVisible);
746 m_DMABuffersHead->qe.stqe_next = (lldesc_t*) &m_DMABuffersVisible[0];
747 }
748}
749
750
751
752
753} // end of namespace
754
755
#define FABGLIB_VIEWPORT_MEMORY_POOL_COUNT
Definition: fabglconf.h:126
This file contains some utility classes and functions.
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGABaseController definition.