From 937e3d0b9408d7d4e6695e9f1b34a36c4530255a Mon Sep 17 00:00:00 2001 From: Blaz Kristan Date: Sat, 5 Aug 2023 13:50:08 +0200 Subject: [PATCH] FX blending POC --- wled00/FX.h | 50 ++++++++++----- wled00/FX_fcn.cpp | 155 ++++++++++++++++++++++++++++++++++++---------- wled00/led.cpp | 8 +-- 3 files changed, 161 insertions(+), 52 deletions(-) diff --git a/wled00/FX.h b/wled00/FX.h index fc8f16b9..bca67a7e 100644 --- a/wled00/FX.h +++ b/wled00/FX.h @@ -381,6 +381,28 @@ typedef struct Segment { byte *data; // effect data pointer static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions) + typedef struct TemporarySegmentData { + uint32_t _colorT[NUM_COLORS]; + //uint8_t _opacityT; + //uint8_t _cctT; // temporary CCT + //uint16_t _optionsT; + uint8_t _speedT; + uint8_t _intensityT; + uint8_t _custom1T, _custom2T; // custom FX parameters/sliders + struct { + uint8_t _custom3T : 5; // reduced range slider (0-31) + bool _check1T : 1; // checkmark 1 + bool _check2T : 1; // checkmark 2 + bool _check3T : 1; // checkmark 3 + }; + uint16_t _aux0T; + uint16_t _aux1T; + uint32_t _stepT; + uint32_t _callT; + uint8_t *_dataT; + uint16_t _dataLenT; + } tmpsegd_t; + private: union { uint8_t _capabilities; @@ -399,41 +421,35 @@ typedef struct Segment { static CRGBPalette16 _randomPalette; static CRGBPalette16 _newRandomPalette; static unsigned long _lastPaletteChange; - static uint8_t _noOfBlendsRemaining; // transition data, valid only if transitional==true, holds values during transition (72 bytes) struct Transition { - uint32_t _colorT[NUM_COLORS]; + tmpsegd_t _tmpSeg; uint8_t _briT; // temporary brightness uint8_t _cctT; // temporary CCT + uint8_t _modeT; // previous mode/effect CRGBPalette16 _palT; // temporary palette uint8_t _prevPaletteBlends; // number of previous palette blends (there are max 255 belnds possible) - uint8_t _modeP; // previous mode/effect - //uint16_t _aux0, _aux1; // previous mode/effect runtime data - //uint32_t _step, _call; // previous mode/effect runtime data - //byte *_data; // previous mode/effect runtime data - unsigned long _start; // must accommodate millis() + unsigned long _start; // must accommodate millis() uint16_t _dur; Transition(uint16_t dur=750) - : _briT(255) - , _cctT(127) - , _palT(CRGBPalette16(CRGB::Black)) + : _palT(CRGBPalette16(CRGB::Black)) , _prevPaletteBlends(0) - , _modeP(FX_MODE_STATIC) , _start(millis()) , _dur(dur) {} + /* Transition(uint16_t d, uint8_t b, uint8_t c, const uint32_t *o) - : _briT(b) - , _cctT(c) - , _palT(CRGBPalette16(CRGB::Black)) + : _palT(CRGBPalette16(CRGB::Black)) , _prevPaletteBlends(0) - , _modeP(FX_MODE_STATIC) , _start(millis()) , _dur(d) { - for (size_t i=0; i_tmpSeg._dataT && _t->_tmpSeg._dataLenT > 0) { + free(_t->_tmpSeg._dataT); + _t->_tmpSeg._dataT = nullptr; + } delete _t; _t = nullptr; } @@ -284,44 +288,111 @@ void Segment::startTransition(uint16_t dur) { _t = new Transition(dur); // no previous transition running if (!_t) return; // failed to allocate data + DEBUG_PRINT(F("-- Saving transition environment. ")); + DEBUG_PRINTLN(on ? opacity : 0); + saveSegenv(&(_t->_tmpSeg)); CRGBPalette16 _palT = CRGBPalette16(DEFAULT_COLOR); loadPalette(_palT, palette); - _t->_briT = on ? opacity : 0; - _t->_cctT = cct; - _t->_palT = _palT; - _t->_modeP = mode; - for (size_t i=0; i_colorT[i] = colors[i]; + _t->_palT = _palT; + _t->_modeT = mode; + _t->_briT = on ? opacity : 0; + _t->_cctT = cct; + _t->_tmpSeg._dataLenT = 0; + _t->_tmpSeg._dataT = nullptr; + if (_dataLen > 0 && data) { + _t->_tmpSeg._dataT = (byte *)malloc(_dataLen); + if (_t->_tmpSeg._dataT) { + DEBUG_PRINTLN(F("-- Allocated duplicate data.")); + memcpy(_t->_tmpSeg._dataT, data, _dataLen); + _t->_tmpSeg._dataLenT = _dataLen; + } + } transitional = true; // setOption(SEG_OPTION_TRANSITIONAL, true); } // transition progression between 0-65535 uint16_t Segment::progress() { - if (!transitional || !_t) return 0xFFFFU; - unsigned long timeNow = millis(); - if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU; - return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; + if (transitional && _t) { + unsigned long timeNow = millis(); + if (_t->_dur > 0 && timeNow - _t->_start < _t->_dur) return (timeNow - _t->_start) * 0xFFFFU / _t->_dur; + } + return 0xFFFFU; } uint8_t Segment::currentBri(uint8_t briNew, bool useCct) { uint32_t prog = progress(); - if (transitional && _t && prog < 0xFFFFU) { + if (prog < 0xFFFFU) { if (useCct) return ((briNew * prog) + _t->_cctT * (0xFFFFU - prog)) >> 16; else return ((briNew * prog) + _t->_briT * (0xFFFFU - prog)) >> 16; - } else { - return briNew; } + return briNew; } uint8_t Segment::currentMode(uint8_t newMode) { - return (progress()>32767U) ? newMode : _t->_modeP; // change effect in the middle of transition + uint16_t prog = progress(); + if (prog < 0xFFFFU) { // implicit check for transitional & _t in progress() + restoreSegenv(&(_t->_tmpSeg)); + opacity -= (uint32_t)opacity * prog / 0xFFFFU; + return _t->_modeT; + } + return newMode; +} + +void Segment::saveSegenv(tmpsegd_t *tmpSeg) { + //tmpSeg._opacityT = on ? opacity : 0; + //tmpSeg._optionsT = options; + for (size_t i=0; i_colorT[i] = colors[i]; + tmpSeg->_speedT = speed; + tmpSeg->_intensityT = intensity; + tmpSeg->_custom1T = custom1; + tmpSeg->_custom2T = custom2; + tmpSeg->_custom3T = custom3; + tmpSeg->_check1T = check1; + tmpSeg->_check2T = check2; + tmpSeg->_check3T = check3; + tmpSeg->_aux0T = aux0; + tmpSeg->_aux1T = aux1; + tmpSeg->_stepT = step; + tmpSeg->_callT = call; + tmpSeg->_dataT = data; + tmpSeg->_dataLenT = _dataLen; +} + +void Segment::restoreSegenv(tmpsegd_t *tmpSeg) { + if (&(_t->_tmpSeg) != tmpSeg) { + DEBUG_PRINTF("Temp: %p != %p\n", &(_t->_tmpSeg), tmpSeg); + DEBUG_PRINTLN(F("-- Restoring OLD environment.")); + // update possibly changed variables to keep old effect running correctly + _t->_tmpSeg._aux0T = aux0; + _t->_tmpSeg._aux1T = aux1; + _t->_tmpSeg._stepT = step; + _t->_tmpSeg._callT = call; + } + //opacity = tmpSeg._opacityT; + //options = tmpSeg._optionsT; + for (size_t i=0; i_colorT[i]; + speed = tmpSeg->_speedT; + intensity = tmpSeg->_intensityT; + custom1 = tmpSeg->_custom1T; + custom2 = tmpSeg->_custom2T; + custom3 = tmpSeg->_custom3T; + check1 = tmpSeg->_check1T; + check2 = tmpSeg->_check2T; + check3 = tmpSeg->_check3T; + aux0 = tmpSeg->_aux0T; + aux1 = tmpSeg->_aux1T; + step = tmpSeg->_stepT; + call = tmpSeg->_callT; + data = tmpSeg->_dataT; + _dataLen = tmpSeg->_dataLenT; } uint32_t Segment::currentColor(uint8_t slot, uint32_t colorNew) { - return transitional && _t ? color_blend(_t->_colorT[slot], colorNew, progress(), true) : colorNew; + return transitional && _t ? color_blend(_t->_tmpSeg._colorT[slot], colorNew, progress(), true) : colorNew; } CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal) { loadPalette(targetPalette, pal); - if (transitional && _t && progress() < 0xFFFFU) { + if (progress() < 0xFFFFU) { // blend palettes // there are about 255 blend passes of 48 "blends" to completely blend two palettes (in _dur time) // minimum blend time is 100ms maximum is 65535ms @@ -338,8 +409,13 @@ void Segment::handleTransition() { uint16_t _progress = progress(); if (_progress == 0xFFFFU) transitional = false; // finish transitioning segment if (_t) { // thanks to @nXm AKA https://github.com/NMeirer - if (_progress >= 32767U && _t->_modeP != mode) markForReset(); + //if (_progress >= 32767U && _t->_modeP != mode) markForReset(); if (_progress == 0xFFFFU) { + DEBUG_PRINTLN(F("-- Stopping transition.")); + if (_t->_tmpSeg._dataT && _t->_tmpSeg._dataLenT > 0) { + free(_t->_tmpSeg._dataT); + _t->_tmpSeg._dataT = nullptr; + } delete _t; _t = nullptr; } @@ -348,13 +424,9 @@ void Segment::handleTransition() { // relies on WS2812FX::service() to call it max every 8ms or more (MIN_SHOW_DELAY) void Segment::handleRandomPalette() { - if (_noOfBlendsRemaining > 0) { - // there needs to be 255 palette blends (48) for full blend - size_t noOfBlends = 3; // blending time ~850ms when MIN_SHOW_DELAY>10 - if (noOfBlends > _noOfBlendsRemaining) noOfBlends = _noOfBlendsRemaining; - for (size_t i=0; i seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))) + if (nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC)) { doShow = true; uint16_t delay = FRAMETIME; @@ -1100,10 +1175,26 @@ void WS2812FX::service() { if (!cctFromRgb || correctWB) busses.setSegmentCCT(seg.currentBri(seg.cct, true), correctWB); for (uint8_t c = 0; c < NUM_COLORS; c++) _colors_t[c] = gamma32(_colors_t[c]); - // effect blending (execute previous effect) - // actual code may be a bit more involved as effects have runtime data including allocated memory - //if (seg.transitional && seg._modeP) (*_mode[seg._modeP])(progress()); - delay = (*_mode[seg.currentMode(seg.mode)])(); + // Effect blending (execute previous effect then new effect while in transition) + // WARNING: seg.currentMode(mode) (while in transition) will overwrite SEGENV variables!!! + // so they need to be saved first and then restored before running new mode. + // The blending will largely depend on the effect behaviour since actual output (LEDs) may be + // overwritten by later effect. To enable seamless blending for every effect, additional LED buffer + // would need to be allocated for each effect and then blended together for each pixel. + Segment::tmpsegd_t _tmpSegData; + seg.saveSegenv(&_tmpSegData); + uint8_t newMode = seg.mode; + uint8_t newOpacity = seg.opacity; + uint8_t tmpMode = seg.currentMode(seg.mode); + delay = (*_mode[tmpMode])(); // run old mode + if (newMode != tmpMode) { + if (tmpMode != FX_MODE_HALLOWEEN_EYES) seg.call++; + seg.restoreSegenv(&_tmpSegData); // restore mode state + seg.opacity = (uint32_t)newOpacity * seg.progress() / 0xFFFFU; + delay += (*_mode[seg.mode])(); // run new mode + delay /= 2; // average the delay + seg.opacity = newOpacity; + } if (seg.mode != FX_MODE_HALLOWEEN_EYES) seg.call++; if (seg.transitional && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition } diff --git a/wled00/led.cpp b/wled00/led.cpp index 97499e76..db49ec2a 100644 --- a/wled00/led.cpp +++ b/wled00/led.cpp @@ -37,12 +37,12 @@ void applyValuesToSelectedSegs() if (effectSpeed != selsegPrev.speed) {seg.speed = effectSpeed; stateChanged = true;} if (effectIntensity != selsegPrev.intensity) {seg.intensity = effectIntensity; stateChanged = true;} - if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette); stateChanged = true;} - if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent); stateChanged = true;} + if (effectPalette != selsegPrev.palette) {seg.setPalette(effectPalette);} + if (effectCurrent != selsegPrev.mode) {seg.setMode(effectCurrent);} uint32_t col0 = RGBW32( col[0], col[1], col[2], col[3]); uint32_t col1 = RGBW32(colSec[0], colSec[1], colSec[2], colSec[3]); - if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0); stateChanged = true;} - if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1); stateChanged = true;} + if (col0 != selsegPrev.colors[0]) {seg.setColor(0, col0);} + if (col1 != selsegPrev.colors[1]) {seg.setColor(1, col1);} } }