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