forked from PlummersSoftwareLLC/NightDriverStrip
-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathdrawing.cpp
More file actions
310 lines (251 loc) · 10.9 KB
/
drawing.cpp
File metadata and controls
310 lines (251 loc) · 10.9 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
//+--------------------------------------------------------------------------
//
// File: drawing.cpp
//
// NightDriverStrip - (c) 2018 Plummer's Software LLC. All Rights Reserved.
//
// This file is part of the NightDriver software project.
//
// NightDriver is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// NightDriver is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with Nightdriver. It is normally found in copying.txt
// If not, see <https://www.gnu.org/licenses/>.
//
// Description:
//
// Main draw loop and rendering code
//
// History: May-11-2021 Davepl Commented
// Nov-02-2022 Davepl Broke up into multiple functions
//
//---------------------------------------------------------------------------
#include <mutex>
#include <ArduinoOTA.h> // Over-the-air helper object so we can be flashed via WiFi
#include "globals.h"
#include "effects/matrix/spectrumeffects.h"
#include "systemcontainer.h"
static DRAM_ATTR CRGB l_SinglePixel = CRGB::Blue;
static DRAM_ATTR uint64_t l_usLastWifiDraw = 0;
// The g_buffer_mutex is a global mutex used to protect access while adding or removing frames
// from the led buffer.
extern DRAM_ATTR std::mutex g_buffer_mutex;
extern const CRGBPalette16 vuPaletteGreen;
std::shared_ptr<LEDStripEffect> GetSpectrumAnalyzer(CRGB color); // Defined in effectmanager.cpp
// WiFiDraw
//
// Draws from WiFi color data if available, returns pixels drawn this frame
uint16_t WiFiDraw()
{
std::lock_guard<std::mutex> guard(g_buffer_mutex);
uint16_t pixelsDrawn = 0;
for (auto& bufferManager : g_ptrSystem->BufferManagers())
{
timeval tv;
gettimeofday(&tv, nullptr);
// Pull buffers out of the queue.
if (false == bufferManager.IsEmpty())
{
std::shared_ptr<LEDBuffer> pBuffer;
if (NTPTimeClient::HasClockBeenSet() == false)
{
pBuffer = bufferManager.GetOldestBuffer();
}
else
{
// Using a 'while' rather than an 'if' causes it to pull frames until it's caught up
// written as 'while' it will pull frames until it gets one that is current.
// Chew through ALL frames older than now, ignoring all but the last of them
while (!bufferManager.IsEmpty() && bufferManager.PeekOldestBuffer()->IsBufferOlderThan(tv))
pBuffer = bufferManager.GetOldestBuffer();
}
if (pBuffer)
{
l_usLastWifiDraw = micros();
debugV("Calling LEDBuffer::Draw from wire with %d/%d pixels.", pixelsDrawn, NUM_LEDS);
pBuffer->DrawBuffer();
// In case we drew some pixels and then drew 0 due a failure, we want to return a positive
// number of pixels drawn so the caller knows we did in fact render.
pixelsDrawn += pBuffer->Length();
}
}
}
debugV("WifIDraw claims to have drawn %d pixels", pixelsDrawn);
return pixelsDrawn;
}
// LocalDraw
//
// Draws from effects table rather than from WiFi data. Returns the number of LEDs rendered.
uint16_t LocalDraw()
{
if (!g_ptrSystem->HasEffectManager())
{
debugW("Drawing before EffectManager is ready, so delaying...");
delay(100);
return 0;
}
else
{
auto& effectManager = g_ptrSystem->EffectManager();
if (effectManager.EffectCount() > 0)
{
// If we've never drawn from wifi before, now would also be a good time to local draw
if (l_usLastWifiDraw == 0 || (micros() - l_usLastWifiDraw > (TIME_BEFORE_LOCAL * MICROS_PER_SECOND)))
{
effectManager.Update(); // Draw the current built in effect
#if SHOW_VU_METER
static auto spectrum = std::static_pointer_cast<SpectrumAnalyzerEffect>(GetSpectrumAnalyzer(0));
if (effectManager.IsVUVisible())
spectrum->DrawVUMeter(effectManager.g(), 0, g_Analyzer.MicMode() == PeakData::PCREMOTE ? & vuPaletteBlue : &vuPaletteGreen);
#endif
debugV("LocalDraw claims to have drawn %d pixels", NUM_LEDS);
return NUM_LEDS;
}
else
{
debugV("Not drawing local effect because last wifi draw was %lf seconds ago.", (micros() - l_usLastWifiDraw) / (float)MICROS_PER_SECOND);
// It's important to return 0 when you do not draw so that the caller knows we did not
// render any pixels, and we can/should wait until the next frame. Otherwise the caller might
// draw the strip needlessly, which can take significant time.
return 0;
}
}
}
debugV("Local draw not drawing");
return 0;
}
// CalcDelayUntilNextFrame
//
// Returns the amount of time to wait patiently until it's time to draw the next frame, up to one second max
int CalcDelayUntilNextFrame(double frameStartTime, uint16_t localPixelsDrawn, uint16_t wifiPixelsDrawn)
{
constexpr auto kMinDelay = 0.001;
// Delay enough to slow down to the desired framerate
#if MILLIS_PER_FRAME == 0
if (localPixelsDrawn > 0)
{
const double minimumFrameTime = 1.0 / g_ptrSystem->EffectManager().GetCurrentEffect().DesiredFramesPerSecond();
double elapsed = g_Values.AppTime.CurrentTime() - frameStartTime;
if (elapsed < minimumFrameTime)
g_Values.FreeDrawTime = std::clamp(minimumFrameTime - elapsed, 0.0, 1.0);
}
else if (wifiPixelsDrawn > 0)
{
// Look through all the channels to see how far away the next wifi frame is times for. We can then delay
// up to the minimum value found across all buffer managers.
double t = std::numeric_limits<double>::max();
bool bFoundFrame = false;
for (auto& bufferManager : g_ptrSystem->BufferManagers())
{
auto pOldest = bufferManager.PeekOldestBuffer();
if (pOldest)
{
t = std::min(t, pOldest->TimeTillDue());
bFoundFrame = true;
}
}
g_Values.FreeDrawTime = bFoundFrame ? std::clamp(t, 0.0, 1.0) : kMinDelay;
}
else
{
debugV("Nothing drawn this pass because neither wifi nor local rendered a frame");
// Nothing drawn this pass - check back soon
g_Values.FreeDrawTime = kMinDelay;
}
return g_Values.FreeDrawTime * MILLIS_PER_SECOND;
#endif
}
// ShowOnboardLED
//
// If the board has an onboard LED, this will update it to show some activity from the draw
void ShowOnboardRGBLED()
{
// Some boards have onboard PWM RGB LEDs, so if defined, we color them here. If we're doing audio,
// the color maps to the sound level. If no audio, it shows the middle LED color from the strip.
#if ONBOARD_LED_R
#if ENABLE_AUDIO
CRGB c = ColorFromPalette(HeatColors_p, g_Analyzer._VURatioFade / 2.0 * 255);
ledcWrite(1, 255 - c.r); // write red component to channel 1, etc.
ledcWrite(2, 255 - c.g);
ledcWrite(3, 255 - c.b);
#else
int iLed = NUM_LEDS / 2;
ledcWrite(1, 255 - graphics->leds[iLed].r); // write red component to channel 1, etc.
ledcWrite(2, 255 - graphics->leds[iLed].g);
ledcWrite(3, 255 - graphics->leds[iLed].b);
#endif
#endif
}
// PrepareOnboardPixel
//
// Do any setup required for the onboard pixel, if we have one
void PrepareOnboardPixel()
{
#ifdef ONBOARD_PIXEL_POWER
FastLED.addLeds<WS2812B, ONBOARD_PIXEL_DATA, ONBOARD_PIXEL_ORDER>(&l_SinglePixel, 1);
pinMode(ONBOARD_PIXEL_POWER, OUTPUT);
digitalWrite(ONBOARD_PIXEL_POWER, HIGH);
#endif
}
void ShowOnboardPixel()
{
// Some boards have onboard PWM RGB LEDs, so if defined, we color them here. If we're doing audio,
// the color maps to the sound level. If no audio, it shows the middle LED color from the strip.
#ifdef ONBOARD_PIXEL_POWER
l_SinglePixel = FastLED[0].leds()[0];
#endif
}
// DrawLoopTaskEntry
//
// Main draw loop entry point
void IRAM_ATTR DrawLoopTaskEntry(void *)
{
debugW(">> DrawLoopTaskEntry\n");
// If this board has an onboard RGB pixel, set it up now
PrepareOnboardPixel();
// Start the effect
g_ptrSystem->EffectManager().StartEffect();
// Run the draw loop
debugW("Entering main draw loop!");
for (;;)
{
g_Values.AppTime.NewFrame();
uint16_t localPixelsDrawn = 0;
uint16_t wifiPixelsDrawn = 0;
double frameStartTime = g_Values.AppTime.FrameStartTime();
auto graphics = g_ptrSystem->EffectManager().GetBaseGraphics();
graphics->PrepareFrame();
if (WiFi.isConnected())
wifiPixelsDrawn = WiFiDraw();
// If we didn't draw now, and it's been a while since we did, and we have at least one local effect, then draw the local effect instead
if (wifiPixelsDrawn == 0)
localPixelsDrawn = LocalDraw();
// If we drew any pixels by any method, we'll call that a frame and track it for FPS purposes. We also notify the
// color data thread that a new frame is available and can be transmitted to clients
if (wifiPixelsDrawn + localPixelsDrawn > 0)
{
// If the module has onboard LEDs, we support a couple of different types, and we set it to be the same as whatever
// is on LED #0 of Channel #0.
ShowOnboardPixel();
ShowOnboardRGBLED();
g_Values.FPS = FastLED.getFPS();
g_ptrSystem->EffectManager().SetNewFrameAvailable(true);
}
graphics->PostProcessFrame(localPixelsDrawn, wifiPixelsDrawn);
// Delay at least 2ms and not more than 1s until next frame is due
constexpr auto minimumDelay = 5;
delay( std::max(minimumDelay, CalcDelayUntilNextFrame(frameStartTime, localPixelsDrawn, wifiPixelsDrawn) ));
// Once an OTA flash update has started, we don't want to hog the CPU or it goes quite slowly,
// so we'll slow down to share the CPU a bit once the update has begun
if (g_Values.UpdateStarted)
delay(500);
}
}