FabGL
ESP32 Display Controller and Graphics Library
vgapalettedcontroller.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#include "esp_spi_flash.h"
41#include "esp_heap_caps.h"
42
43#include "fabutils.h"
46
47
48
49#pragma GCC optimize ("O2")
50
51
52
53namespace fabgl {
54
55
56
57
58
59/*************************************************************************************/
60/* VGAPalettedController definitions */
61
62
63volatile uint8_t * * VGAPalettedController::s_viewPort;
64volatile uint8_t * * VGAPalettedController::s_viewPortVisible;
65lldesc_t volatile * VGAPalettedController::s_frameResetDesc;
66volatile int VGAPalettedController::s_scanLine;
67
68
69
70
71VGAPalettedController::VGAPalettedController(int linesCount, int columnsQuantum, NativePixelFormat nativePixelFormat, int viewPortRatioDiv, int viewPortRatioMul, intr_handler_t isrHandler)
72 : m_linesCount(linesCount),
73 m_columnsQuantum(columnsQuantum),
74 m_nativePixelFormat(nativePixelFormat),
75 m_viewPortRatioDiv(viewPortRatioDiv),
76 m_viewPortRatioMul(viewPortRatioMul),
77 m_isrHandler(isrHandler)
78{
79 m_lines = (volatile uint8_t**) heap_caps_malloc(sizeof(uint8_t*) * m_linesCount, MALLOC_CAP_32BIT | MALLOC_CAP_INTERNAL);
80 m_palette = (RGB222*) heap_caps_malloc(sizeof(RGB222) * getPaletteSize(), MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL);
81}
82
83
84VGAPalettedController::~VGAPalettedController()
85{
86 heap_caps_free(m_palette);
87 heap_caps_free(m_lines);
88}
89
90
91void VGAPalettedController::init()
92{
93 VGABaseController::init();
94
95 m_doubleBufferOverDMA = false;
96 m_taskProcessingPrimitives = false;
97 m_processPrimitivesOnBlank = false;
98 m_primitiveExecTask = nullptr;
99}
100
101
102void VGAPalettedController::end()
103{
104 if (m_primitiveExecTask) {
105 vTaskDelete(m_primitiveExecTask);
106 m_primitiveExecTask = nullptr;
107 m_taskProcessingPrimitives = false;
108 }
109 VGABaseController::end();
110}
111
112
113void VGAPalettedController::suspendBackgroundPrimitiveExecution()
114{
115 VGABaseController::suspendBackgroundPrimitiveExecution();
116 while (m_taskProcessingPrimitives)
117 ;
118}
119
120// make sure view port height is divisible by m_linesCount, view port width is divisible by m_columnsQuantum
121void VGAPalettedController::checkViewPortSize()
122{
123 m_viewPortHeight &= ~(m_linesCount - 1);
124 m_viewPortWidth &= ~(m_columnsQuantum - 1);
125}
126
127
128void VGAPalettedController::allocateViewPort()
129{
130 VGABaseController::allocateViewPort(MALLOC_CAP_8BIT | MALLOC_CAP_INTERNAL, m_viewPortWidth / m_viewPortRatioDiv * m_viewPortRatioMul);
131
132 for (int i = 0; i < m_linesCount; ++i)
133 m_lines[i] = (uint8_t*) heap_caps_malloc(m_viewPortWidth, MALLOC_CAP_DMA);
134}
135
136
137void VGAPalettedController::freeViewPort()
138{
139 VGABaseController::freeViewPort();
140
141 for (int i = 0; i < m_linesCount; ++i) {
142 heap_caps_free((void*)m_lines[i]);
143 m_lines[i] = nullptr;
144 }
145}
146
147
148void VGAPalettedController::setResolution(VGATimings const& timings, int viewPortWidth, int viewPortHeight, bool doubleBuffered)
149{
150 VGABaseController::setResolution(timings, viewPortWidth, viewPortHeight, doubleBuffered);
151
152 s_viewPort = m_viewPort;
153 s_viewPortVisible = m_viewPortVisible;
154
155 // fill view port
156 for (int i = 0; i < m_viewPortHeight; ++i)
157 memset((void*)(m_viewPort[i]), 0, m_viewPortWidth / m_viewPortRatioDiv * m_viewPortRatioMul);
158
159 setupDefaultPalette();
160 updateRGB2PaletteLUT();
161
162 calculateAvailableCyclesForDrawings();
163
164 // must be started before interrupt alloc
165 startGPIOStream();
166
167 // ESP_INTR_FLAG_LEVEL1: should be less than PS2Controller interrupt level, necessary when running on the same core
168 if (m_isr_handle == nullptr) {
169 CoreUsage::setBusiestCore(FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
170 esp_intr_alloc_pinnedToCore(ETS_I2S1_INTR_SOURCE, ESP_INTR_FLAG_LEVEL1 | ESP_INTR_FLAG_IRAM, m_isrHandler, this, &m_isr_handle, FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE);
171 I2S1.int_clr.val = 0xFFFFFFFF;
172 I2S1.int_ena.out_eof = 1;
173 }
174
175 if (m_primitiveExecTask == nullptr) {
176 xTaskCreatePinnedToCore(primitiveExecTask, "" , FABGLIB_VGAPALETTEDCONTROLLER_PRIMTASK_STACK_SIZE, this, FABGLIB_VGAPALETTEDCONTROLLER_PRIMTASK_PRIORITY, &m_primitiveExecTask, CoreUsage::quietCore());
177 }
178
179 resumeBackgroundPrimitiveExecution();
180}
181
182
183void VGAPalettedController::onSetupDMABuffer(lldesc_t volatile * buffer, bool isStartOfVertFrontPorch, int scan, bool isVisible, int visibleRow)
184{
185 if (isVisible) {
186 buffer->buf = (uint8_t *) m_lines[visibleRow % m_linesCount];
187
188 // generate interrupt every half m_linesCount
189 if ((scan == 0 && (visibleRow % (m_linesCount / 2)) == 0)) {
190 if (visibleRow == 0)
191 s_frameResetDesc = buffer;
192 buffer->eof = 1;
193 }
194 }
195}
196
197
198int VGAPalettedController::getPaletteSize()
199{
200 switch (nativePixelFormat()) {
201 case NativePixelFormat::PALETTE2:
202 return 2;
203 case NativePixelFormat::PALETTE4:
204 return 4;
205 case NativePixelFormat::PALETTE8:
206 return 8;
207 case NativePixelFormat::PALETTE16:
208 return 16;
209 default:
210 return 0;
211 }
212}
213
214
215// rebuild m_packedRGB222_to_PaletteIndex
216void VGAPalettedController::updateRGB2PaletteLUT()
217{
218 auto paletteSize = getPaletteSize();
219 for (int r = 0; r < 4; ++r)
220 for (int g = 0; g < 4; ++g)
221 for (int b = 0; b < 4; ++b) {
222 double H1, S1, V1;
223 rgb222_to_hsv(r, g, b, &H1, &S1, &V1);
224 int bestIdx = 0;
225 int bestDst = 1000000000;
226 for (int i = 0; i < paletteSize; ++i) {
227 double H2, S2, V2;
228 rgb222_to_hsv(m_palette[i].R, m_palette[i].G, m_palette[i].B, &H2, &S2, &V2);
229 double AH = H1 - H2;
230 double AS = S1 - S2;
231 double AV = V1 - V2;
232 int dst = AH * AH + AS * AS + AV * AV;
233 if (dst <= bestDst) { // "<=" to prioritize higher indexes
234 bestIdx = i;
235 bestDst = dst;
236 if (bestDst == 0)
237 break;
238 }
239 }
240 m_packedRGB222_to_PaletteIndex[r | (g << 2) | (b << 4)] = bestIdx;
241 }
242}
243
244
245// calculates number of CPU cycles usable to draw primitives
246void VGAPalettedController::calculateAvailableCyclesForDrawings()
247{
248 int availtime_us;
249
250 if (m_processPrimitivesOnBlank) {
251 // allowed time to process primitives is limited to the vertical blank. Slow, but avoid flickering
252 availtime_us = ceil(1000000.0 / m_timings.frequency * m_timings.scanCount * m_HLineSize * (m_linesCount / 2 + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch + m_viewPortRow));
253 } else {
254 // allowed time is the half of an entire frame. Fast, but may flick
255 availtime_us = ceil(1000000.0 / m_timings.frequency * m_timings.scanCount * m_HLineSize * (m_timings.VVisibleArea + m_timings.VFrontPorch + m_timings.VSyncPulse + m_timings.VBackPorch));
256 availtime_us /= 2;
257 }
258
259 m_primitiveExecTimeoutCycles = getCPUFrequencyMHz() * availtime_us; // at 240Mhz, there are 240 cycles every microsecond
260}
261
262
263// we can use getCycleCount here because primitiveExecTask is pinned to a specific core (so cycle counter is the same)
264// getCycleCount() requires 0.07us, while esp_timer_get_time() requires 0.78us
265void VGAPalettedController::primitiveExecTask(void * arg)
266{
267 auto ctrl = (VGAPalettedController *) arg;
268
269 while (true) {
270 if (!ctrl->m_primitiveProcessingSuspended) {
271 auto startCycle = ctrl->backgroundPrimitiveTimeoutEnabled() ? getCycleCount() : 0;
272 Rect updateRect = Rect(SHRT_MAX, SHRT_MAX, SHRT_MIN, SHRT_MIN);
273 ctrl->m_taskProcessingPrimitives = true;
274 do {
275 Primitive prim;
276 if (ctrl->getPrimitive(&prim, 0) == false)
277 break;
278 ctrl->execPrimitive(prim, updateRect, false);
279 if (ctrl->m_primitiveProcessingSuspended)
280 break;
281 } while (!ctrl->backgroundPrimitiveTimeoutEnabled() || (startCycle + ctrl->m_primitiveExecTimeoutCycles > getCycleCount()));
282 ctrl->showSprites(updateRect);
283 ctrl->m_taskProcessingPrimitives = false;
284 }
285
286 // wait for vertical sync
287 ulTaskNotifyTake(pdTRUE, portMAX_DELAY);
288 }
289
290}
291
292
293void VGAPalettedController::swapBuffers()
294{
295 VGABaseController::swapBuffers();
296 s_viewPort = m_viewPort;
297 s_viewPortVisible = m_viewPortVisible;
298}
299
300
301
302} // end of namespace
303
uint8_t B
uint8_t G
uint8_t R
#define FABGLIB_VGAPALETTEDCONTROLLER_PRIMTASK_PRIORITY
Definition: fabglconf.h:150
#define FABGLIB_VIDEO_CPUINTENSIVE_TASKS_CORE
Definition: fabglconf.h:142
#define FABGLIB_VGAPALETTEDCONTROLLER_PRIMTASK_STACK_SIZE
Definition: fabglconf.h:146
This file contains some utility classes and functions.
NativePixelFormat
This enum defines the display controller native pixel format.
This file contains fabgl::GPIOStream definition.
This file contains fabgl::VGAPalettedController definition.