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