Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pixel-Based Effect Classes #4549

Draft
wants to merge 12 commits into
base: main
Choose a base branch
from
51 changes: 30 additions & 21 deletions wled00/FX.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,11 @@
#include "wled.h"
#include "FX.h"
#include "fcn_declare.h"
#include "effects/BouncingBallsEffect.h"
#include "effects/PaletteEffect.h"
#include "effects/StaticEffect.h"
#include "effects/TetrixEffect.h"
#include <memory>

#if !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D))
#include "FXparticleSystem.h"
Expand Down Expand Up @@ -863,7 +868,7 @@ static uint16_t chase(uint32_t color1, uint32_t color2, uint32_t color3, bool do
uint16_t counter = strip.now * ((SEGMENT.speed >> 2) + 1);
uint16_t a = (counter * SEGLEN) >> 16;

bool chase_random = (SEGMENT.mode == FX_MODE_CHASE_RANDOM);
bool chase_random = (SEGMENT.getEffectId() == FX_MODE_CHASE_RANDOM);
if (chase_random) {
if (a < SEGENV.step) //we hit the start again, choose new color for Chase random
{
Expand Down Expand Up @@ -10127,36 +10132,39 @@ static const char _data_RESERVED[] PROGMEM = "RSVD";
// use id==255 to find unallocated gaps (with "Reserved" data string)
// if vector size() is smaller than id (single) data is appended at the end (regardless of id)
// return the actual id used for the effect or 255 if the add failed.
uint8_t WS2812FX::addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name) {
if (id == 255) { // find empty slot
for (size_t i=1; i<_mode.size(); i++) if (_modeData[i] == _data_RESERVED) { id = i; break; }
}
if (id < _mode.size()) {
if (_modeData[id] != _data_RESERVED) return 255; // do not overwrite an already added effect
_mode[id] = mode_fn;
_modeData[id] = mode_name;
uint8_t WS2812FX::addEffect(std::unique_ptr<EffectFactory>&& factory) {
uint8_t id = factory->getEffectId();
if (id == 255u) { // find empty slot
for (size_t i=1; i<_effectFactories.size(); i++) if (_effectFactories[i] == nullptr) { id = i; break; }
}
for (size_t i = _effectFactories.size(); i < id; ++i) {
_effectFactories.push_back(nullptr);
}
if (id < _effectFactories.size()) {
if (_effectFactories[id] != nullptr) return 255; // do not overwrite an already added effect
_effectFactories[id] = std::move(factory);
return id;
} else if (_mode.size() < 255) { // 255 is reserved for indicating the effect wasn't added
_mode.push_back(mode_fn);
_modeData.push_back(mode_name);
if (_modeCount < _mode.size()) _modeCount++;
return _mode.size() - 1;
} else if (_effectFactories.size() < 255) { // 255 is reserved for indicating the effect wasn't added
_effectFactories.push_back(std::move(factory));
return _effectFactories.size() - 1;
} else {
return 255; // The vector is full so return 255
return 255u; // The vector is full so return 255
}
}

void WS2812FX::setupEffectData() {
void WS2812FX::setupEffectData(size_t modeCount) {
// Solid must be first! (assuming vector is empty upon call to setup)
_mode.push_back(&mode_static);
_modeData.push_back(_data_FX_MODE_STATIC);
addEffect(std::make_unique<EffectFactory>(StaticEffect::effectInformation));
addEffect(std::make_unique<EffectFactory>(PaletteEffect::effectInformation));
addEffect(std::make_unique<EffectFactory>(TetrixEffect::effectInformation));
addEffect(std::make_unique<EffectFactory>(BouncingBallsEffect::effectInformation));
// fill reserved word in case there will be any gaps in the array
for (size_t i=1; i<_modeCount; i++) {
_mode.push_back(&mode_static);
_modeData.push_back(_data_RESERVED);
for (size_t i=1; i<modeCount; i++) {
_effectFactories.push_back(nullptr);
}
// now replace all pre-allocated effects
// --- 1D non-audio effects ---
/*
addEffect(FX_MODE_BLINK, &mode_blink, _data_FX_MODE_BLINK);
addEffect(FX_MODE_BREATH, &mode_breath, _data_FX_MODE_BREATH);
addEffect(FX_MODE_COLOR_WIPE, &mode_color_wipe, _data_FX_MODE_COLOR_WIPE);
Expand Down Expand Up @@ -10387,5 +10395,6 @@ addEffect(FX_MODE_PS1DGEQ, &mode_particle1DGEQ, _data_FX_MODE_PS_1D_GEQ);
addEffect(FX_MODE_PSFIRE1D, &mode_particleFire1D, _data_FX_MODE_PS_FIRE1D);
addEffect(FX_MODE_PS1DSONICSTREAM, &mode_particle1Dsonicstream, _data_FX_MODE_PS_SONICSTREAM);
#endif // WLED_DISABLE_PARTICLESYSTEM1D
*/

}
75 changes: 49 additions & 26 deletions wled00/FX.h
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@

#include "const.h"
#include "bus_manager.h"
#include "effects/Effect.h"

#define FASTLED_INTERNAL //remove annoying pragma messages
#define USE_GET_MILLISECOND_TIMER
Expand Down Expand Up @@ -394,7 +395,6 @@ typedef struct Segment {
uint8_t speed;
uint8_t intensity;
uint8_t palette;
uint8_t mode;
union {
uint16_t options; //bit pattern: msb first: [transposed mirrorY reverseY] transitional (tbd) paused needspixelstate mirrored on reverse selected
struct {
Expand Down Expand Up @@ -462,6 +462,7 @@ typedef struct Segment {
} tmpsegd_t;

private:
std::unique_ptr<Effect> effect;
union {
uint8_t _capabilities;
struct {
Expand Down Expand Up @@ -497,7 +498,7 @@ typedef struct Segment {
struct Transition {
#ifndef WLED_DISABLE_MODE_BLEND
tmpsegd_t _segT; // previous segment environment
uint8_t _modeT; // previous mode/effect
std::unique_ptr<Effect> _effectT; // previous mode/effect
#else
uint32_t _colorT[NUM_COLORS];
#endif
Expand All @@ -515,7 +516,8 @@ typedef struct Segment {
, _start(millis())
, _dur(dur)
{}
} *_t;
};
std::unique_ptr<Transition> _t;

[[gnu::hot]] void _setPixelColorXY_raw(const int& x, const int& y, uint32_t& col) const; // set pixel without mapping (internal use only)

Expand All @@ -528,7 +530,6 @@ typedef struct Segment {
speed(DEFAULT_SPEED),
intensity(DEFAULT_INTENSITY),
palette(0),
mode(DEFAULT_MODE),
options(SELECTED | SEGMENT_ON),
grouping(1),
spacing(0),
Expand All @@ -544,7 +545,6 @@ typedef struct Segment {
startY(0),
stopY(1),
name(nullptr),
next_time(0),
step(0),
call(0),
aux0(0),
Expand Down Expand Up @@ -578,6 +578,7 @@ typedef struct Segment {
if (name) { free(name); name = nullptr; }
stopTransition();
deallocateData();
effect = nullptr;
}

Segment& operator= (const Segment &orig); // copy assignment
Expand All @@ -587,6 +588,7 @@ typedef struct Segment {
size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0); }
#endif

inline uint8_t getEffectId() const { return (effect != nullptr) ? effect->getEffectId() : 0u; }
inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); }
inline bool isSelected() const { return selected; }
inline bool isInTransition() const { return _t != nullptr; }
Expand Down Expand Up @@ -626,7 +628,6 @@ typedef struct Segment {
Segment &setMode(uint8_t fx, bool loadDefaults = false);
Segment &setPalette(uint8_t pal);
Segment &setName(const char* name);
uint8_t differs(const Segment& b) const;
void refreshLightCapabilities();

// runtime data functions
Expand All @@ -643,7 +644,7 @@ typedef struct Segment {
inline Segment &markForReset() { reset = true; return *this; } // setOption(SEG_OPTION_RESET, true)

// transition functions
void startTransition(uint16_t dur); // transition has to start before actual segment values change
void startTransition(uint16_t dur, std::unique_ptr<Effect>&& oldEffect = nullptr); // transition has to start before actual segment values change
void stopTransition(); // ends transition mode by destroying transition structure (does nothing if not in transition)
inline void handleTransition() { updateTransitionProgress(); if (progress() == 0xFFFFU) stopTransition(); }
#ifndef WLED_DISABLE_MODE_BLEND
Expand All @@ -653,8 +654,9 @@ typedef struct Segment {
[[gnu::hot]] void updateTransitionProgress(); // set current progression of transition
inline uint16_t progress() const { return Segment::_transitionprogress; } // transition progression between 0-65535
[[gnu::hot]] uint8_t currentBri(bool useCct = false) const; // current segment brightness/CCT (blended while in transition)
uint8_t currentMode() const; // currently active effect/mode (while in transition)
[[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
Effect* getTransitionEffect() const; // while in transition: Old mode, nullptr otherwise
Effect* getCurrentEffect() const; // Currently active effect/mode. While in transition: New mode.
[[gnu::hot]] uint32_t currentColor(uint8_t slot) const; // currently active segment color (blended while in transition)
CRGBPalette16 &loadPalette(CRGBPalette16 &tgt, uint8_t pal);
void loadOldPalette(); // loads old FX palette into _currentPalette

Expand Down Expand Up @@ -826,7 +828,6 @@ class WS2812FX { // 96 bytes
_isOffRefreshRequired(false),
_hasWhiteChannel(false),
_triggered(false),
_modeCount(MODE_COUNT),
_callback(nullptr),
customMappingTable(nullptr),
customMappingSize(0),
Expand All @@ -836,16 +837,13 @@ class WS2812FX { // 96 bytes
_mainSegment(0)
{
WS2812FX::instance = this;
_mode.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size())
_modeData.reserve(_modeCount); // allocate memory to prevent initial fragmentation (does not increase size())
if (_mode.capacity() <= 1 || _modeData.capacity() <= 1) _modeCount = 1; // memory allocation failed only show Solid
else setupEffectData();
_effectFactories.reserve(MODE_COUNT); // allocate memory to prevent initial fragmentation (does not increase size())
setupEffectData(std::max(size_t{1}, _effectFactories.capacity()));
}

~WS2812FX() {
if (customMappingTable) free(customMappingTable);
_mode.clear();
_modeData.clear();
_effectFactories.clear();
_segments.clear();
#ifndef WLED_DISABLE_2D
panel.clear();
Expand All @@ -872,7 +870,7 @@ class WS2812FX { // 96 bytes
setPixelColor(unsigned i, uint32_t c) const, // paints absolute strip pixel with index n and color c
show(), // initiates LED output
setTargetFps(unsigned fps),
setupEffectData(); // add default effects to the list; defined in FX.cpp
setupEffectData(size_t modeCount); // add default effects to the list; defined in FX.cpp

inline void resetTimebase() { timebase = 0UL - millis(); }
inline void restartRuntime() { for (Segment &seg : _segments) { seg.markForReset().resetIfRequired(); } }
Expand All @@ -883,7 +881,7 @@ class WS2812FX { // 96 bytes
inline void trigger() { _triggered = true; } // Forces the next frame to be computed on all active segments.
inline void setShowCallback(show_callback cb) { _callback = cb; }
inline void setTransition(uint16_t t) { _transitionDur = t; } // sets transition time (in ms)
inline void appendSegment(const Segment &seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(seg); }
inline void appendSegment(Segment &&seg = Segment()) { if (_segments.size() < getMaxSegments()) _segments.push_back(std::move(seg)); }
inline void suspend() { _suspend = true; } // will suspend (and canacel) strip.service() execution
inline void resume() { _suspend = false; } // will resume strip.service() execution

Expand All @@ -906,7 +904,7 @@ class WS2812FX { // 96 bytes
getFirstSelectedSegId() const,
getLastActiveSegmentId() const,
getActiveSegsLightCapabilities(bool selectedOnly = false) const,
addEffect(uint8_t id, mode_ptr mode_fn, const char *mode_name); // add effect to the list; defined in FX.cpp;
addEffect(std::unique_ptr<EffectFactory>&& factory); // add effect to the list; defined in FX.cpp;

inline uint8_t getBrightness() const { return _brightness; } // returns current strip brightness
inline static constexpr unsigned getMaxSegments() { return MAX_NUM_SEGMENTS; } // returns maximum number of supported segments (fixed value)
Expand All @@ -915,7 +913,7 @@ class WS2812FX { // 96 bytes
inline uint8_t getMainSegmentId() const { return _mainSegment; } // returns main segment index
inline uint8_t getPaletteCount() const { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); }
inline uint8_t getTargetFps() const { return _targetFps; } // returns rough FPS value for las 2s interval
inline uint8_t getModeCount() const { return _modeCount; } // returns number of registered modes/effects
inline size_t getModeCount() const { return _effectFactories.size(); } // returns number of registered modes/effects

uint16_t
getLengthPhysical() const,
Expand All @@ -936,8 +934,9 @@ class WS2812FX { // 96 bytes

inline uint32_t getLastShow() const { return _lastShow; } // returns millis() timestamp of last strip.show() call

const char *getModeData(unsigned id = 0) const { return (id && id < _modeCount) ? _modeData[id] : PSTR("Solid"); }
inline const char **getModeDataSrc() { return &(_modeData[0]); } // vectors use arrays for underlying data
inline EffectFactory* getEffectFactory(uint8_t effectId) const { return _effectFactories[effectId].get(); }
inline EffectFactory* safeGetEffectFactory(uint8_t effectId) const { return (effectId < getModeCount()) ? _effectFactories[effectId].get() : nullptr; }
const char *getModeData(unsigned id = 0) const { const EffectFactory* const factory = safeGetEffectFactory(id); return (factory != nullptr) ? factory->getMetaData() : PSTR("Solid"); }

Segment& getSegment(unsigned id);
inline Segment& getFirstSelectedSeg() { return _segments[getFirstSelectedSegId()]; } // returns reference to first segment that is "selected"
Expand Down Expand Up @@ -1020,9 +1019,7 @@ class WS2812FX { // 96 bytes
bool _triggered : 1;
};

uint8_t _modeCount;
std::vector<mode_ptr> _mode; // SRAM footprint: 4 bytes per element
std::vector<const char*> _modeData; // mode (effect) name and its slider control data array
std::vector<std::unique_ptr<EffectFactory>> _effectFactories;

show_callback _callback;

Expand All @@ -1039,4 +1036,30 @@ class WS2812FX { // 96 bytes
extern const char JSON_mode_names[];
extern const char JSON_palette_names[];

#endif
class LazyColor {
public:
explicit constexpr LazyColor(Segment& seg, int x, int y)
: seg(seg),
x(x),
y(y)
{
}

uint32_t getColor(int x, int y) const {
if (!loaded) {
color = seg.getPixelColorXY(x, y);
loaded = true;
}
return color;
}

private:
Segment& seg;
int x = 0;
int y = 0;
mutable bool loaded = false;
mutable uint32_t color = 0;
};


#endif
Loading
Loading