Multiple updates:
- additional debug timings - removed local leds[] buffer - async segment bounds change (crashes seen otherwise) - added isActive() check to Segment drawing methods - ABL simplification - palette option for Black hole (FX) - (possible) crash mitigation is Segment handling (rapid preset changes)
This commit is contained in:
parent
6267d11e51
commit
fa6070c680
@ -606,7 +606,6 @@ static const char _data_FX_MODE_TWINKLE[] PROGMEM = "Twinkle@!,!;!,!;!;;m12=0";
|
||||
uint16_t dissolve(uint32_t color) {
|
||||
//bool wa = (SEGCOLOR(1) != 0 && strip.getBrightness() < 255); //workaround, can't compare getPixel to color if not full brightness
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds(); //lossless getPixelColor()
|
||||
SEGMENT.fill(SEGCOLOR(1));
|
||||
}
|
||||
|
||||
@ -1205,7 +1204,6 @@ uint16_t mode_fireworks() {
|
||||
const uint16_t height = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds(); //lossless getPixelColor()
|
||||
SEGMENT.fill(SEGCOLOR(1));
|
||||
SEGENV.aux0 = UINT16_MAX;
|
||||
SEGENV.aux1 = UINT16_MAX;
|
||||
@ -1904,7 +1902,6 @@ static const char _data_FX_MODE_PRIDE_2015[] PROGMEM = "Pride 2015@!;;";
|
||||
uint16_t mode_juggle(void) {
|
||||
if (SEGLEN == 1) return mode_static();
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds(); //lossless getPixelColor()
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4585,7 +4582,6 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma
|
||||
|
||||
// initialize on first call
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4595,22 +4591,22 @@ uint16_t mode_2DBlackHole(void) { // By: Stepko https://editor.soulma
|
||||
for (size_t i = 0; i < 8; i++) {
|
||||
x = beatsin8(SEGMENT.custom1>>3, 0, cols - 1, 0, ((i % 2) ? 128 : 0) + t * i);
|
||||
y = beatsin8(SEGMENT.intensity>>3, 0, rows - 1, 0, ((i % 2) ? 192 : 64) + t * i);
|
||||
SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255));
|
||||
SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(i*32, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255));
|
||||
}
|
||||
// inner stars
|
||||
for (size_t i = 0; i < 4; i++) {
|
||||
x = beatsin8(SEGMENT.custom2>>3, cols/4, cols - 1 - cols/4, 0, ((i % 2) ? 128 : 0) + t * i);
|
||||
y = beatsin8(SEGMENT.custom3 , rows/4, rows - 1 - rows/4, 0, ((i % 2) ? 192 : 64) + t * i);
|
||||
SEGMENT.addPixelColorXY(x, y, CHSV(i*32, 255, 255));
|
||||
SEGMENT.addPixelColorXY(x, y, SEGMENT.color_from_palette(255-i*64, false, PALETTE_SOLID_WRAP, SEGMENT.check1?0:255));
|
||||
}
|
||||
// central white dot
|
||||
SEGMENT.setPixelColorXY(cols/2, rows/2, CHSV(0, 0, 255));
|
||||
SEGMENT.setPixelColorXY(cols/2, rows/2, WHITE);
|
||||
// blur everything a bit
|
||||
SEGMENT.blur(16);
|
||||
|
||||
return FRAMETIME;
|
||||
} // mode_2DBlackHole()
|
||||
static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.;;;2";
|
||||
static const char _data_FX_MODE_2DBLACKHOLE[] PROGMEM = "Black Hole@Fade rate,Outer Y freq.,Outer X freq.,Inner X freq.,Inner Y freq.,Solid;!;!;2;pal=11";
|
||||
|
||||
|
||||
////////////////////////////
|
||||
@ -4623,7 +4619,6 @@ uint16_t mode_2DColoredBursts() { // By: ldirko https://editor.so
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
SEGENV.aux0 = 0; // start with red hue
|
||||
}
|
||||
@ -4677,7 +4672,6 @@ uint16_t mode_2Ddna(void) { // dna originally by by ldirko at https://pa
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4704,7 +4698,6 @@ uint16_t mode_2DDNASpiral() { // By: ldirko https://editor.soulma
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4750,7 +4743,6 @@ uint16_t mode_2DDrift() { // By: Stepko https://editor.soulmateli
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4781,7 +4773,6 @@ uint16_t mode_2Dfirenoise(void) { // firenoise2d. By Andrew Tuline
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4816,7 +4807,6 @@ uint16_t mode_2DFrizzles(void) { // By: Stepko https://editor.so
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -4855,8 +4845,6 @@ uint16_t mode_2Dgameoflife(void) { // Written by Ewoud Wijma, inspired by https:
|
||||
|
||||
CRGB backgroundColor = SEGCOLOR(1);
|
||||
|
||||
if (SEGENV.call == 0) SEGMENT.setUpLeds();
|
||||
|
||||
if (SEGENV.call == 0 || strip.now - SEGMENT.step > 3000) {
|
||||
SEGENV.step = strip.now;
|
||||
SEGENV.aux0 = 0;
|
||||
@ -5122,7 +5110,6 @@ uint16_t mode_2Dmatrix(void) { // Matrix2D. By Jeremy Williams.
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5267,7 +5254,6 @@ uint16_t mode_2DPlasmaball(void) { // By: Stepko https://edito
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5315,7 +5301,6 @@ uint16_t mode_2DPolarLights(void) { // By: Kostyantyn Matviyevskyy https
|
||||
CRGBPalette16 auroraPalette = {0x000000, 0x003300, 0x006600, 0x009900, 0x00cc00, 0x00ff00, 0x33ff00, 0x66ff00, 0x99ff00, 0xccff00, 0xffff00, 0xffcc00, 0xff9900, 0xff6600, 0xff3300, 0xff0000};
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
SEGENV.step = 0;
|
||||
}
|
||||
@ -5365,7 +5350,6 @@ uint16_t mode_2DPulser(void) { // By: ldirko https://edi
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5393,7 +5377,6 @@ uint16_t mode_2DSindots(void) { // By: ldirko http
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5425,7 +5408,6 @@ uint16_t mode_2Dsquaredswirl(void) { // By: Mark Kriegsman. https://g
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5468,7 +5450,6 @@ uint16_t mode_2DSunradiation(void) { // By: ldirko https://edi
|
||||
byte *bump = reinterpret_cast<byte*>(SEGENV.data);
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5516,7 +5497,6 @@ uint16_t mode_2Dtartan(void) { // By: Elliott Kember https://editor.so
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5556,7 +5536,6 @@ uint16_t mode_2Dspaceships(void) { //// Space ships by stepko (c)05.02.21 [ht
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -5625,7 +5604,6 @@ uint16_t mode_2Dcrazybees(void) {
|
||||
bee_t *bee = reinterpret_cast<bee_t*>(SEGENV.data);
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
for (size_t i = 0; i < n; i++) {
|
||||
bee[i].posX = random8(0, cols);
|
||||
@ -5695,7 +5673,6 @@ uint16_t mode_2Dghostrider(void) {
|
||||
|
||||
const size_t maxLighters = min(cols + rows, LIGHTERS_AM);
|
||||
|
||||
if (SEGENV.call == 0) SEGMENT.setUpLeds();
|
||||
if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) {
|
||||
SEGENV.aux0 = cols;
|
||||
SEGENV.aux1 = rows;
|
||||
@ -5780,7 +5757,6 @@ uint16_t mode_2Dfloatingblobs(void) {
|
||||
if (!SEGENV.allocateData(sizeof(blob_t))) return mode_static(); //allocation failed
|
||||
blob_t *blob = reinterpret_cast<blob_t*>(SEGENV.data);
|
||||
|
||||
if (SEGENV.call == 0) SEGMENT.setUpLeds();
|
||||
if (SEGENV.aux0 != cols || SEGENV.aux1 != rows) {
|
||||
SEGENV.aux0 = cols; // re-initialise if virtual size changes
|
||||
SEGENV.aux1 = rows;
|
||||
@ -5946,7 +5922,6 @@ uint16_t mode_2Ddriftrose(void) {
|
||||
const float L = min(cols, rows) / 2.f;
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6101,7 +6076,6 @@ uint16_t mode_2DSwirl(void) {
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6148,7 +6122,6 @@ uint16_t mode_2DWaverly(void) {
|
||||
const uint16_t rows = SEGMENT.virtualHeight();
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6377,7 +6350,6 @@ uint16_t mode_matripix(void) { // Matripix. By Andrew Tuline.
|
||||
int16_t volumeRaw = *(int16_t*)um_data->u_data[1];
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6505,7 +6477,6 @@ uint16_t mode_pixelwave(void) { // Pixelwave. By Andrew Tuline.
|
||||
// even with 1D effect we have to take logic for 2D segments for allocation as fill_solid() fills whole segment
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6731,7 +6702,6 @@ uint16_t mode_DJLight(void) { // Written by ??? Adapted by Wil
|
||||
uint8_t *fftResult = (uint8_t*)um_data->u_data[2];
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6800,7 +6770,6 @@ uint16_t mode_freqmatrix(void) { // Freqmatrix. By Andreas Plesch
|
||||
float volumeSmth = *(float*)um_data->u_data[0];
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -6902,7 +6871,6 @@ uint16_t mode_freqwave(void) { // Freqwave. By Andreas Pleschun
|
||||
float volumeSmth = *(float*)um_data->u_data[0];
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -7087,7 +7055,6 @@ uint16_t mode_waterfall(void) { // Waterfall. By: Andrew Tulin
|
||||
if (FFT_MajorPeak < 1) FFT_MajorPeak = 1; // log10(0) is "forbidden" (throws exception)
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
SEGENV.aux0 = 255;
|
||||
SEGMENT.custom1 = *binNum;
|
||||
@ -7204,7 +7171,6 @@ uint16_t mode_2DFunkyPlank(void) { // Written by ??? Adapted by Wil
|
||||
uint8_t *fftResult = (uint8_t*)um_data->u_data[2];
|
||||
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
SEGMENT.fill(BLACK);
|
||||
}
|
||||
|
||||
@ -7417,7 +7383,6 @@ uint16_t mode_2Dsoap() {
|
||||
|
||||
// init
|
||||
if (SEGENV.call == 0) {
|
||||
SEGMENT.setUpLeds();
|
||||
*noise32_x = random16();
|
||||
*noise32_y = random16();
|
||||
*noise32_z = random16();
|
||||
@ -7535,12 +7500,12 @@ uint16_t mode_2Doctopus() {
|
||||
SEGENV.aux1 = rows;
|
||||
*offsX = SEGMENT.custom1;
|
||||
*offsY = SEGMENT.custom2;
|
||||
const uint8_t C_X = cols / 2 + (SEGMENT.custom1 - 128)*cols/255;
|
||||
const uint8_t C_Y = rows / 2 + (SEGMENT.custom2 - 128)*rows/255;
|
||||
const int C_X = (cols / 2) + ((SEGMENT.custom1 - 128)*cols)/255;
|
||||
const int C_Y = (rows / 2) + ((SEGMENT.custom2 - 128)*rows)/255;
|
||||
for (int x = 0; x < cols; x++) {
|
||||
for (int y = 0; y < rows; y++) {
|
||||
rMap[XY(x, y)].angle = 40.7436f * atan2f(y - C_Y, x - C_X); // avoid 128*atan2()/PI
|
||||
rMap[XY(x, y)].radius = hypotf(x - C_X, y - C_Y) * mapp; //thanks Sutaburosu
|
||||
rMap[XY(x, y)].angle = 40.7436f * atan2f((y - C_Y), (x - C_X)); // avoid 128*atan2()/PI
|
||||
rMap[XY(x, y)].radius = hypotf((x - C_X), (y - C_Y)) * mapp; //thanks Sutaburosu
|
||||
}
|
||||
}
|
||||
}
|
||||
|
30
wled00/FX.h
30
wled00/FX.h
@ -329,7 +329,7 @@ typedef enum mapping1D2D {
|
||||
M12_pCorner = 3
|
||||
} mapping1D2D_t;
|
||||
|
||||
// segment, 72 bytes
|
||||
// segment, 80 bytes
|
||||
typedef struct Segment {
|
||||
public:
|
||||
uint16_t start; // start index / start X coordinate 2D (left)
|
||||
@ -378,8 +378,7 @@ typedef struct Segment {
|
||||
uint32_t call; // call counter
|
||||
uint16_t aux0; // custom var
|
||||
uint16_t aux1; // custom var
|
||||
byte* data; // effect data pointer
|
||||
uint32_t* leds; // local leds[] array (may be a pointer to global)
|
||||
byte *data; // effect data pointer
|
||||
static uint16_t maxWidth, maxHeight; // these define matrix width & height (max. segment dimensions)
|
||||
|
||||
private:
|
||||
@ -396,7 +395,10 @@ typedef struct Segment {
|
||||
uint16_t _dataLen;
|
||||
static uint16_t _usedSegmentData;
|
||||
|
||||
// transition data, valid only if transitional==true, holds values during transition
|
||||
uint16_t _qStart, _qStop, _qStartY, _qStopY;
|
||||
bool _queuedChanges;
|
||||
|
||||
// transition data, valid only if transitional==true, holds values during transition (72 bytes)
|
||||
struct Transition {
|
||||
uint32_t _colorT[NUM_COLORS];
|
||||
uint8_t _briT; // temporary brightness
|
||||
@ -407,7 +409,7 @@ typedef struct Segment {
|
||||
//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
|
||||
uint32_t _start;
|
||||
unsigned long _start; // must accommodate millis()
|
||||
uint16_t _dur;
|
||||
Transition(uint16_t dur=750)
|
||||
: _briT(255)
|
||||
@ -462,9 +464,9 @@ typedef struct Segment {
|
||||
aux0(0),
|
||||
aux1(0),
|
||||
data(nullptr),
|
||||
leds(nullptr),
|
||||
_capabilities(0),
|
||||
_dataLen(0),
|
||||
_queuedChanges(false),
|
||||
_t(nullptr)
|
||||
{
|
||||
//refreshLightCapabilities();
|
||||
@ -485,9 +487,8 @@ typedef struct Segment {
|
||||
//if (data) Serial.printf(" %d (%p)", (int)_dataLen, data);
|
||||
//Serial.println();
|
||||
//#endif
|
||||
if (name) delete[] name;
|
||||
if (_t) delete _t;
|
||||
if (leds) free(leds);
|
||||
if (name) { delete[] name; name = nullptr; }
|
||||
if (_t) { transitional = false; delete _t; _t = nullptr; }
|
||||
deallocateData();
|
||||
}
|
||||
|
||||
@ -495,7 +496,7 @@ typedef struct Segment {
|
||||
Segment& operator= (Segment &&orig) noexcept; // move assignment
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0) + (leds?sizeof(CRGB)*length():0); }
|
||||
size_t getSize() const { return sizeof(Segment) + (data?_dataLen:0) + (name?strlen(name):0) + (_t?sizeof(Transition):0); }
|
||||
#endif
|
||||
|
||||
inline bool getOption(uint8_t n) const { return ((options >> n) & 0x01); }
|
||||
@ -505,8 +506,8 @@ typedef struct Segment {
|
||||
inline bool hasRGB(void) const { return _isRGB; }
|
||||
inline bool hasWhite(void) const { return _hasW; }
|
||||
inline bool isCCT(void) const { return _isCCT; }
|
||||
inline uint16_t width(void) const { return (stop > start) ? (stop - start) : 0; } // segment width in physical pixels (length if 1D)
|
||||
inline uint16_t height(void) const { return (stopY > startY) ? (stopY - startY) : 0; } // segment height (if 2D) in physical pixels // softhack007: make sure its always > 0
|
||||
inline uint16_t width(void) const { return isActive() ? (stop - start) : 0; } // segment width in physical pixels (length if 1D)
|
||||
inline uint16_t height(void) const { return stopY - startY; } // segment height (if 2D) in physical pixels (it *is* always >=1)
|
||||
inline uint16_t length(void) const { return width() * height(); } // segment length (count) in physical pixels
|
||||
inline uint16_t groupLength(void) const { return grouping + spacing; }
|
||||
inline uint8_t getLightCapabilities(void) const { return _capabilities; }
|
||||
@ -514,7 +515,7 @@ typedef struct Segment {
|
||||
static uint16_t getUsedSegmentData(void) { return _usedSegmentData; }
|
||||
static void addUsedSegmentData(int len) { _usedSegmentData += len; }
|
||||
|
||||
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1);
|
||||
void setUp(uint16_t i1, uint16_t i2, uint8_t grp=1, uint8_t spc=0, uint16_t ofs=UINT16_MAX, uint16_t i1Y=0, uint16_t i2Y=1, bool immediate=false);
|
||||
bool setColor(uint8_t slot, uint32_t c); //returns true if changed
|
||||
void setCCT(uint16_t k);
|
||||
void setOpacity(uint8_t o);
|
||||
@ -536,7 +537,6 @@ typedef struct Segment {
|
||||
* Safe to call from interrupts and network requests.
|
||||
*/
|
||||
inline void markForReset(void) { reset = true; } // setOption(SEG_OPTION_RESET, true)
|
||||
void setUpLeds(void); // local double buffer/lossless getPixelColor()
|
||||
|
||||
// transition functions
|
||||
void startTransition(uint16_t dur); // transition has to start before actual segment values change
|
||||
@ -896,7 +896,7 @@ class WS2812FX { // 96 bytes
|
||||
uint16_t* customMappingTable;
|
||||
uint16_t customMappingSize;
|
||||
|
||||
uint32_t _lastShow;
|
||||
unsigned long _lastShow;
|
||||
|
||||
uint8_t _segment_index;
|
||||
uint8_t _mainSegment;
|
||||
|
@ -189,20 +189,16 @@ uint32_t WS2812FX::getPixelColorXY(uint16_t x, uint16_t y) {
|
||||
|
||||
// XY(x,y) - gets pixel index within current segment (often used to reference leds[] array element)
|
||||
uint16_t /*IRAM_ATTR*/ Segment::XY(uint16_t x, uint16_t y) {
|
||||
uint16_t width = virtualWidth(); // segment width in logical pixels
|
||||
uint16_t height = virtualHeight(); // segment height in logical pixels
|
||||
if (width == 0) return 0; // softhack007 avoid div/0
|
||||
if (height == 0) return (x%width); // softhack007 avoid div/0
|
||||
return (x%width) + (y%height) * width;
|
||||
uint16_t width = virtualWidth(); // segment width in logical pixels (can be 0 if segment is inactive)
|
||||
uint16_t height = virtualHeight(); // segment height in logical pixels (is always >= 1)
|
||||
return isActive() ? (x%width) + (y%height) * width : 0;
|
||||
}
|
||||
|
||||
void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col)
|
||||
{
|
||||
if (Segment::maxHeight==1) return; // not a matrix set-up
|
||||
if (!isActive()) return; // not active
|
||||
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit
|
||||
|
||||
if (leds) leds[XY(x,y)] = col;
|
||||
|
||||
uint8_t _bri_t = currentBri(on ? opacity : 0);
|
||||
if (_bri_t < 255) {
|
||||
byte r = scale8(R(col), _bri_t);
|
||||
@ -245,7 +241,7 @@ void /*IRAM_ATTR*/ Segment::setPixelColorXY(int x, int y, uint32_t col)
|
||||
// anti-aliased version of setPixelColorXY()
|
||||
void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
|
||||
{
|
||||
if (Segment::maxHeight==1) return; // not a matrix set-up
|
||||
if (!isActive()) return; // not active
|
||||
if (x<0.0f || x>1.0f || y<0.0f || y>1.0f) return; // not normalized
|
||||
|
||||
const uint16_t cols = virtualWidth();
|
||||
@ -288,7 +284,8 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa)
|
||||
|
||||
// returns RGBW values of pixel
|
||||
uint32_t Segment::getPixelColorXY(uint16_t x, uint16_t y) {
|
||||
if (leds) return leds[XY(x,y)];
|
||||
if (!isActive()) return 0; // not active
|
||||
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit
|
||||
if (reverse ) x = virtualWidth() - x - 1;
|
||||
if (reverse_y) y = virtualHeight() - y - 1;
|
||||
if (transpose) { uint16_t t = x; x = y; y = t; } // swap X & Y if segment transposed
|
||||
@ -305,6 +302,7 @@ void Segment::blendPixelColorXY(uint16_t x, uint16_t y, uint32_t color, uint8_t
|
||||
|
||||
// Adds the specified color with the existing pixel color perserving color balance.
|
||||
void Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) {
|
||||
if (!isActive()) return; // not active
|
||||
if (x >= virtualWidth() || y >= virtualHeight() || x<0 || y<0) return; // if pixel would fall out of virtual segment just exit
|
||||
uint32_t col = getPixelColorXY(x,y);
|
||||
uint8_t r = R(col);
|
||||
@ -324,12 +322,14 @@ void Segment::addPixelColorXY(int x, int y, uint32_t color, bool fast) {
|
||||
}
|
||||
|
||||
void Segment::fadePixelColorXY(uint16_t x, uint16_t y, uint8_t fade) {
|
||||
if (!isActive()) return; // not active
|
||||
CRGB pix = CRGB(getPixelColorXY(x,y)).nscale8_video(fade);
|
||||
setPixelColorXY(x, y, pix);
|
||||
}
|
||||
|
||||
// blurRow: perform a blur on a row of a rectangular matrix
|
||||
void Segment::blurRow(uint16_t row, fract8 blur_amount) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint_fast16_t cols = virtualWidth();
|
||||
const uint_fast16_t rows = virtualHeight();
|
||||
|
||||
@ -357,6 +357,7 @@ void Segment::blurRow(uint16_t row, fract8 blur_amount) {
|
||||
|
||||
// blurCol: perform a blur on a column of a rectangular matrix
|
||||
void Segment::blurCol(uint16_t col, fract8 blur_amount) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint_fast16_t cols = virtualWidth();
|
||||
const uint_fast16_t rows = virtualHeight();
|
||||
|
||||
@ -384,6 +385,7 @@ void Segment::blurCol(uint16_t col, fract8 blur_amount) {
|
||||
|
||||
// 1D Box blur (with added weight - blur_amount: [0=no blur, 255=max blur])
|
||||
void Segment::box_blur(uint16_t i, bool vertical, fract8 blur_amount) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = virtualWidth();
|
||||
const uint16_t rows = virtualHeight();
|
||||
const uint16_t dim1 = vertical ? rows : cols;
|
||||
@ -436,6 +438,7 @@ void Segment::blur1d(fract8 blur_amount) {
|
||||
}
|
||||
|
||||
void Segment::moveX(int8_t delta, bool wrap) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = virtualWidth();
|
||||
const uint16_t rows = virtualHeight();
|
||||
if (!delta || abs(delta) >= cols) return;
|
||||
@ -453,6 +456,7 @@ void Segment::moveX(int8_t delta, bool wrap) {
|
||||
}
|
||||
|
||||
void Segment::moveY(int8_t delta, bool wrap) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = virtualWidth();
|
||||
const uint16_t rows = virtualHeight();
|
||||
if (!delta || abs(delta) >= rows) return;
|
||||
@ -488,6 +492,7 @@ void Segment::move(uint8_t dir, uint8_t delta, bool wrap) {
|
||||
}
|
||||
|
||||
void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
||||
if (!isActive()) return; // not active
|
||||
// Bresenham’s Algorithm
|
||||
int d = 3 - (2*radius);
|
||||
int y = radius, x = 0;
|
||||
@ -512,6 +517,7 @@ void Segment::draw_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
||||
|
||||
// by stepko, taken from https://editor.soulmatelights.com/gallery/573-blobs
|
||||
void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = virtualWidth();
|
||||
const uint16_t rows = virtualHeight();
|
||||
for (int16_t y = -radius; y <= radius; y++) {
|
||||
@ -525,6 +531,7 @@ void Segment::fill_circle(uint16_t cx, uint16_t cy, uint8_t radius, CRGB col) {
|
||||
}
|
||||
|
||||
void Segment::nscale8(uint8_t scale) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = virtualWidth();
|
||||
const uint16_t rows = virtualHeight();
|
||||
for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) {
|
||||
@ -534,6 +541,7 @@ void Segment::nscale8(uint8_t scale) {
|
||||
|
||||
//line function
|
||||
void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint32_t c) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = virtualWidth();
|
||||
const uint16_t rows = virtualHeight();
|
||||
if (x0 >= cols || x1 >= cols || y0 >= rows || y1 >= rows) return;
|
||||
@ -558,6 +566,7 @@ void Segment::drawLine(uint16_t x0, uint16_t y0, uint16_t x1, uint16_t y1, uint3
|
||||
// draws a raster font character on canvas
|
||||
// only supports: 4x6=24, 5x8=40, 5x12=60, 6x8=48 and 7x9=63 fonts ATM
|
||||
void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w, uint8_t h, uint32_t color, uint32_t col2) {
|
||||
if (!isActive()) return; // not active
|
||||
if (chr < 32 || chr > 126) return; // only ASCII 32-126 supported
|
||||
chr -= 32; // align with font table entries
|
||||
const uint16_t cols = virtualWidth();
|
||||
@ -593,6 +602,7 @@ void Segment::drawCharacter(unsigned char chr, int16_t x, int16_t y, uint8_t w,
|
||||
|
||||
#define WU_WEIGHT(a,b) ((uint8_t) (((a)*(b)+(a)+(b))>>8))
|
||||
void Segment::wu_pixel(uint32_t x, uint32_t y, CRGB c) { //awesome wu_pixel procedure by reddit u/sutaburosu
|
||||
if (!isActive()) return; // not active
|
||||
// extract the fractional parts and derive their inverses
|
||||
uint8_t xx = x & 0xff, yy = y & 0xff, ix = 255 - xx, iy = 255 - yy;
|
||||
// calculate the intensities for each affected pixel
|
||||
|
@ -81,22 +81,21 @@ uint16_t Segment::maxHeight = 1;
|
||||
Segment::Segment(const Segment &orig) {
|
||||
//DEBUG_PRINTLN(F("-- Copy segment constructor --"));
|
||||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||||
transitional = false; // copied segment cannot be in transition
|
||||
name = nullptr;
|
||||
data = nullptr;
|
||||
_dataLen = 0;
|
||||
_t = nullptr;
|
||||
leds = nullptr;
|
||||
if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
|
||||
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
|
||||
if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); }
|
||||
if (orig.leds) { leds = (uint32_t*)malloc(sizeof(uint32_t)*length()); if (leds) memcpy(leds, orig.leds, sizeof(uint32_t)*length()); }
|
||||
//if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); }
|
||||
}
|
||||
|
||||
// move constructor
|
||||
Segment::Segment(Segment &&orig) noexcept {
|
||||
//DEBUG_PRINTLN(F("-- Move segment constructor --"));
|
||||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||||
orig.leds = nullptr;
|
||||
orig.transitional = false; // old segment cannot be in transition any more
|
||||
orig.name = nullptr;
|
||||
orig.data = nullptr;
|
||||
orig._dataLen = 0;
|
||||
@ -108,23 +107,22 @@ Segment& Segment::operator= (const Segment &orig) {
|
||||
//DEBUG_PRINTLN(F("-- Copying segment --"));
|
||||
if (this != &orig) {
|
||||
// clean destination
|
||||
transitional = false; // copied segment cannot be in transition
|
||||
if (name) delete[] name;
|
||||
if (_t) delete _t;
|
||||
if (leds) free(leds);
|
||||
deallocateData();
|
||||
// copy source
|
||||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||||
transitional = false;
|
||||
// erase pointers to allocated data
|
||||
name = nullptr;
|
||||
data = nullptr;
|
||||
_dataLen = 0;
|
||||
_t = nullptr;
|
||||
leds = nullptr;
|
||||
// copy source data
|
||||
if (orig.name) { name = new char[strlen(orig.name)+1]; if (name) strcpy(name, orig.name); }
|
||||
if (orig.data) { if (allocateData(orig._dataLen)) memcpy(data, orig.data, orig._dataLen); }
|
||||
if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); }
|
||||
if (orig.leds) { leds = (uint32_t*)malloc(sizeof(uint32_t)*length()); if (leds) memcpy(leds, orig.leds, sizeof(uint32_t)*length()); }
|
||||
//if (orig._t) { _t = new Transition(orig._t->_dur, orig._t->_briT, orig._t->_cctT, orig._t->_colorT); }
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@ -133,16 +131,16 @@ Segment& Segment::operator= (const Segment &orig) {
|
||||
Segment& Segment::operator= (Segment &&orig) noexcept {
|
||||
//DEBUG_PRINTLN(F("-- Moving segment --"));
|
||||
if (this != &orig) {
|
||||
if (name) delete[] name; // free old name
|
||||
if (leds) free(leds);
|
||||
transitional = false; // just temporary
|
||||
if (name) { delete[] name; name = nullptr; } // free old name
|
||||
deallocateData(); // free old runtime data
|
||||
if (_t) delete _t;
|
||||
if (_t) { delete _t; _t = nullptr; }
|
||||
memcpy((void*)this, (void*)&orig, sizeof(Segment));
|
||||
orig.transitional = false; // old segment cannot be in transition
|
||||
orig.name = nullptr;
|
||||
orig.data = nullptr;
|
||||
orig._dataLen = 0;
|
||||
orig._t = nullptr;
|
||||
orig.leds = nullptr;
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
@ -152,11 +150,6 @@ bool Segment::allocateData(size_t len) {
|
||||
deallocateData();
|
||||
if (Segment::getUsedSegmentData() + len > MAX_SEGMENT_DATA) return false; //not enough memory
|
||||
// do not use SPI RAM on ESP32 since it is slow
|
||||
//#if defined(ARDUINO_ARCH_ESP32) && defined(BOARD_HAS_PSRAM) && defined(WLED_USE_PSRAM)
|
||||
//if (psramFound())
|
||||
// data = (byte*) ps_malloc(len);
|
||||
//else
|
||||
//#endif
|
||||
data = (byte*) malloc(len);
|
||||
if (!data) return false; //allocation failed
|
||||
Segment::addUsedSegmentData(len);
|
||||
@ -182,28 +175,13 @@ void Segment::deallocateData() {
|
||||
*/
|
||||
void Segment::resetIfRequired() {
|
||||
if (reset) {
|
||||
if (leds) { free(leds); leds = nullptr; }
|
||||
deallocateData();
|
||||
next_time = 0; step = 0; call = 0; aux0 = 0; aux1 = 0;
|
||||
if (_queuedChanges) setUp(_qStart, _qStop, grouping, spacing, offset, _qStartY, _qStopY, true);
|
||||
reset = false; // setOption(SEG_OPTION_RESET, false);
|
||||
}
|
||||
}
|
||||
|
||||
void Segment::setUpLeds() {
|
||||
// deallocation happens in resetIfRequired() as it is called when segment changes or in destructor
|
||||
if (useGlobalLedBuffer) return; // TODO optional seg buffer for FX without lossy restore due to opacity
|
||||
|
||||
// no global buffer
|
||||
if (leds == nullptr && length() > 0) { //softhack007 quickfix - avoid malloc(0) which is undefined behaviour (should not happen, but i've seen it)
|
||||
//#if defined(ARDUINO_ARCH_ESP32) && defined(WLED_USE_PSRAM)
|
||||
//if (psramFound())
|
||||
// leds = (CRGB*)ps_malloc(sizeof(CRGB)*length()); // softhack007 disabled; putting leds into psram leads to horrible slowdown on WROVER boards
|
||||
//else
|
||||
//#endif
|
||||
leds = (uint32_t *)calloc(length(), sizeof(uint32_t));
|
||||
}
|
||||
}
|
||||
|
||||
CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) {
|
||||
static unsigned long _lastPaletteChange = 0; // perhaps it should be per segment
|
||||
static CRGBPalette16 randomPalette = CRGBPalette16(DEFAULT_COLOR);
|
||||
@ -326,7 +304,7 @@ void Segment::startTransition(uint16_t dur) {
|
||||
// transition progression between 0-65535
|
||||
uint16_t Segment::progress() {
|
||||
if (!transitional || !_t) return 0xFFFFU;
|
||||
uint32_t timeNow = millis();
|
||||
unsigned long timeNow = millis();
|
||||
if (timeNow - _t->_start > _t->_dur || _t->_dur == 0) return 0xFFFFU;
|
||||
return (timeNow - _t->_start) * 0xFFFFU / _t->_dur;
|
||||
}
|
||||
@ -355,7 +333,7 @@ CRGBPalette16 &Segment::currentPalette(CRGBPalette16 &targetPalette, uint8_t pal
|
||||
// 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
|
||||
uint32_t timeMS = millis() - _t->_start;
|
||||
unsigned long timeMS = millis() - _t->_start;
|
||||
uint16_t noOfBlends = (255U * timeMS / _t->_dur) - _t->_prevPaletteBlends;
|
||||
for (int i=0; i<noOfBlends; i++, _t->_prevPaletteBlends++) nblendPaletteTowardPalette(_t->_palT, targetPalette, 48);
|
||||
targetPalette = _t->_palT; // copy transitioning/temporary palette
|
||||
@ -376,8 +354,9 @@ void Segment::handleTransition() {
|
||||
}
|
||||
}
|
||||
|
||||
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y) {
|
||||
//return if neither bounds nor grouping have changed
|
||||
void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t ofs, uint16_t i1Y, uint16_t i2Y, bool immediate) {
|
||||
_queuedChanges = false; // cancel anything queued
|
||||
// return if neither bounds nor grouping have changed
|
||||
bool boundsUnchanged = (start == i1 && stop == i2);
|
||||
#ifndef WLED_DISABLE_2D
|
||||
if (Segment::maxHeight>1) boundsUnchanged &= (startY == i1Y && stopY == i2Y); // 2D
|
||||
@ -386,10 +365,21 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
|
||||
&& (!grp || (grouping == grp && spacing == spc))
|
||||
&& (ofs == UINT16_MAX || ofs == offset)) return;
|
||||
|
||||
if (stop) fill(BLACK); //turn old segment range off
|
||||
if (stop) fill(BLACK); // turn old segment range off (clears pixels if changing spacing)
|
||||
if (grp) { // prevent assignment of 0
|
||||
grouping = grp;
|
||||
spacing = spc;
|
||||
} else {
|
||||
grouping = 1;
|
||||
spacing = 0;
|
||||
}
|
||||
if (ofs < UINT16_MAX) offset = ofs;
|
||||
|
||||
markForReset();
|
||||
if (!boundsUnchanged) {
|
||||
if (immediate) {
|
||||
if (i2 <= i1) { //disable segment
|
||||
stop = 0;
|
||||
markForReset();
|
||||
return;
|
||||
}
|
||||
if (i1 < Segment::maxWidth || (i1 >= Segment::maxWidth*Segment::maxHeight && i1 < strip.getLengthTotal())) start = i1; // Segment::maxWidth equals strip.getLengthTotal() for 1D
|
||||
@ -402,13 +392,20 @@ void Segment::setUp(uint16_t i1, uint16_t i2, uint8_t grp, uint8_t spc, uint16_t
|
||||
stopY = i2Y > Segment::maxHeight ? Segment::maxHeight : MAX(1,i2Y);
|
||||
}
|
||||
#endif
|
||||
if (grp) {
|
||||
grouping = grp;
|
||||
spacing = spc;
|
||||
// safety check
|
||||
if (start >= stop || startY >= stopY) {
|
||||
stop = 0;
|
||||
return;
|
||||
}
|
||||
if (ofs < UINT16_MAX) offset = ofs;
|
||||
markForReset();
|
||||
if (!boundsUnchanged) refreshLightCapabilities();
|
||||
} else {
|
||||
_qStart = i1;
|
||||
_qStop = i2;
|
||||
_qStartY = i1Y;
|
||||
_qStopY = i2Y;
|
||||
_queuedChanges = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -455,8 +452,7 @@ void Segment::setMode(uint8_t fx, bool loadDefaults) {
|
||||
// if we have a valid mode & is not reserved
|
||||
if (fx < strip.getModeCount() && strncmp_P("RSVD", strip.getModeData(fx), 4)) {
|
||||
if (fx != mode) {
|
||||
startTransition(strip.getTransition()); // set effect transitions
|
||||
//markForReset(); // transition will handle this
|
||||
if (fadeTransition) startTransition(strip.getTransition()); // set effect transitions
|
||||
mode = fx;
|
||||
|
||||
// load default values from effect string
|
||||
@ -541,8 +537,7 @@ uint16_t Segment::virtualLength() const {
|
||||
return vLen;
|
||||
}
|
||||
#endif
|
||||
uint16_t groupLen = groupLength();
|
||||
if (groupLen < 1) groupLen = 1; // prevent division by zero - better safe than sorry ...
|
||||
uint16_t groupLen = groupLength(); // is always >= 1
|
||||
uint16_t vLength = (length() + groupLen - 1) / groupLen;
|
||||
if (mirror) vLength = (vLength + 1) /2; // divide by 2 if mirror, leave at least a single LED
|
||||
return vLength;
|
||||
@ -550,6 +545,7 @@ uint16_t Segment::virtualLength() const {
|
||||
|
||||
void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
|
||||
{
|
||||
if (!isActive()) return; // not active
|
||||
#ifndef WLED_DISABLE_2D
|
||||
int vStrip = i>>16; // hack to allow running on virtual strips (2D segment columns/rows)
|
||||
#endif
|
||||
@ -617,8 +613,6 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (leds) leds[i] = col;
|
||||
|
||||
uint16_t len = length();
|
||||
uint8_t _bri_t = currentBri(on ? opacity : 0);
|
||||
if (_bri_t < 255) {
|
||||
@ -660,6 +654,7 @@ void IRAM_ATTR Segment::setPixelColor(int i, uint32_t col)
|
||||
// anti-aliased normalized version of setPixelColor()
|
||||
void Segment::setPixelColor(float i, uint32_t col, bool aa)
|
||||
{
|
||||
if (!isActive()) return; // not active
|
||||
int vStrip = int(i/10.0f); // hack to allow running on virtual strips (2D segment columns/rows)
|
||||
i -= int(i);
|
||||
|
||||
@ -691,6 +686,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa)
|
||||
|
||||
uint32_t Segment::getPixelColor(int i)
|
||||
{
|
||||
if (!isActive()) return 0; // not active
|
||||
#ifndef WLED_DISABLE_2D
|
||||
int vStrip = i>>16;
|
||||
#endif
|
||||
@ -718,8 +714,6 @@ uint32_t Segment::getPixelColor(int i)
|
||||
}
|
||||
#endif
|
||||
|
||||
if (leds) return leds[i];
|
||||
|
||||
if (reverse) i = virtualLength() - i - 1;
|
||||
i *= groupLength();
|
||||
i += start;
|
||||
@ -760,6 +754,11 @@ void Segment::refreshLightCapabilities() {
|
||||
uint16_t segStartIdx = 0xFFFFU;
|
||||
uint16_t segStopIdx = 0;
|
||||
|
||||
if (!isActive()) {
|
||||
_capabilities = 0;
|
||||
return;
|
||||
}
|
||||
|
||||
if (start < Segment::maxWidth * Segment::maxHeight) {
|
||||
// we are withing 2D matrix (includes 1D segments)
|
||||
for (int y = startY; y < stopY; y++) for (int x = start; x < stop; x++) {
|
||||
@ -804,6 +803,7 @@ void Segment::refreshLightCapabilities() {
|
||||
* Fills segment with color
|
||||
*/
|
||||
void Segment::fill(uint32_t c) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = is2D() ? virtualWidth() : virtualLength();
|
||||
const uint16_t rows = virtualHeight(); // will be 1 for 1D
|
||||
for(uint16_t y = 0; y < rows; y++) for (uint16_t x = 0; x < cols; x++) {
|
||||
@ -819,6 +819,7 @@ void Segment::blendPixelColor(int n, uint32_t color, uint8_t blend) {
|
||||
|
||||
// Adds the specified color with the existing pixel color perserving color balance.
|
||||
void Segment::addPixelColor(int n, uint32_t color, bool fast) {
|
||||
if (!isActive()) return; // not active
|
||||
uint32_t col = getPixelColor(n);
|
||||
uint8_t r = R(col);
|
||||
uint8_t g = G(col);
|
||||
@ -837,6 +838,7 @@ void Segment::addPixelColor(int n, uint32_t color, bool fast) {
|
||||
}
|
||||
|
||||
void Segment::fadePixelColor(uint16_t n, uint8_t fade) {
|
||||
if (!isActive()) return; // not active
|
||||
CRGB pix = CRGB(getPixelColor(n)).nscale8_video(fade);
|
||||
setPixelColor(n, pix);
|
||||
}
|
||||
@ -845,6 +847,7 @@ void Segment::fadePixelColor(uint16_t n, uint8_t fade) {
|
||||
* fade out function, higher rate = quicker fade
|
||||
*/
|
||||
void Segment::fade_out(uint8_t rate) {
|
||||
if (!isActive()) return; // not active
|
||||
const uint16_t cols = is2D() ? virtualWidth() : virtualLength();
|
||||
const uint16_t rows = virtualHeight(); // will be 1 for 1D
|
||||
|
||||
@ -882,7 +885,7 @@ void Segment::fade_out(uint8_t rate) {
|
||||
|
||||
// fades all pixels to black using nscale8()
|
||||
void Segment::fadeToBlackBy(uint8_t fadeBy) {
|
||||
if (fadeBy == 0) return; // optimization - no scaling to apply
|
||||
if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply
|
||||
const uint16_t cols = is2D() ? virtualWidth() : virtualLength();
|
||||
const uint16_t rows = virtualHeight(); // will be 1 for 1D
|
||||
|
||||
@ -897,7 +900,7 @@ void Segment::fadeToBlackBy(uint8_t fadeBy) {
|
||||
*/
|
||||
void Segment::blur(uint8_t blur_amount)
|
||||
{
|
||||
if (blur_amount == 0) return; // optimization: 0 means "don't blur"
|
||||
if (!isActive() || blur_amount == 0) return; // optimization: 0 means "don't blur"
|
||||
#ifndef WLED_DISABLE_2D
|
||||
if (is2D()) {
|
||||
// compatibility with 2D
|
||||
@ -1071,7 +1074,7 @@ void WS2812FX::finalizeInit(void)
|
||||
}
|
||||
|
||||
void WS2812FX::service() {
|
||||
uint32_t nowUp = millis(); // Be aware, millis() rolls over every 49 days
|
||||
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
|
||||
now = nowUp + timebase;
|
||||
if (nowUp - _lastShow < MIN_SHOW_DELAY) return;
|
||||
bool doShow = false;
|
||||
@ -1089,7 +1092,6 @@ void WS2812FX::service() {
|
||||
// last condition ensures all solid segments are updated at the same time
|
||||
if(nowUp > seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
|
||||
{
|
||||
if (seg.grouping == 0) seg.grouping = 1; // sanity check
|
||||
doShow = true;
|
||||
uint16_t delay = FRAMETIME;
|
||||
|
||||
@ -1117,12 +1119,19 @@ void WS2812FX::service() {
|
||||
}
|
||||
_virtualSegmentLength = 0;
|
||||
busses.setSegmentCCT(-1);
|
||||
if(doShow) {
|
||||
_isServicing = false;
|
||||
_triggered = false;
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
if (millis() - nowUp > _frametime) DEBUG_PRINTLN(F("Slow effects."));
|
||||
#endif
|
||||
if (doShow) {
|
||||
yield();
|
||||
show();
|
||||
}
|
||||
_triggered = false;
|
||||
_isServicing = false;
|
||||
#ifdef WLED_DEBUG
|
||||
if (millis() - nowUp > _frametime) DEBUG_PRINTLN(F("Slow strip."));
|
||||
#endif
|
||||
}
|
||||
|
||||
void IRAM_ATTR WS2812FX::setPixelColor(int i, uint32_t col)
|
||||
@ -1159,31 +1168,25 @@ uint8_t WS2812FX::estimateCurrentAndLimitBri() {
|
||||
bool useWackyWS2815PowerModel = false;
|
||||
byte actualMilliampsPerLed = milliampsPerLed;
|
||||
|
||||
if(milliampsPerLed == 255) {
|
||||
useWackyWS2815PowerModel = true;
|
||||
actualMilliampsPerLed = 12; // from testing an actual strip
|
||||
}
|
||||
|
||||
if (ablMilliampsMax < 150 || actualMilliampsPerLed == 0) { //0 mA per LED and too low numbers turn off calculation
|
||||
currentMilliamps = 0;
|
||||
return _brightness;
|
||||
}
|
||||
|
||||
uint16_t pLen = getLengthPhysical();
|
||||
uint32_t puPerMilliamp = 195075 / actualMilliampsPerLed;
|
||||
uint32_t powerBudget = (ablMilliampsMax - MA_FOR_ESP) * puPerMilliamp; //100mA for ESP power
|
||||
if (powerBudget > puPerMilliamp * pLen) { //each LED uses about 1mA in standby, exclude that from power budget
|
||||
powerBudget -= puPerMilliamp * pLen;
|
||||
} else {
|
||||
powerBudget = 0;
|
||||
if (milliampsPerLed == 255) {
|
||||
useWackyWS2815PowerModel = true;
|
||||
actualMilliampsPerLed = 12; // from testing an actual strip
|
||||
}
|
||||
|
||||
uint32_t powerSum = 0; // could overflow if more than 22K LEDs (uint32_t MAX / 195075 PU per LED)
|
||||
size_t powerBudget = (ablMilliampsMax - MA_FOR_ESP); //100mA for ESP power
|
||||
|
||||
size_t pLen = 0; //getLengthPhysical();
|
||||
size_t powerSum = 0;
|
||||
for (uint_fast8_t bNum = 0; bNum < busses.getNumBusses(); bNum++) {
|
||||
Bus *bus = busses.getBus(bNum);
|
||||
if (bus->getType() >= TYPE_NET_DDP_RGB) continue; //exclude non-physical network busses
|
||||
if (!IS_DIGITAL(bus->getType())) continue; //exclude non-digital network busses
|
||||
uint16_t len = bus->getLength();
|
||||
pLen += len;
|
||||
uint32_t busPowerSum = 0;
|
||||
for (uint_fast16_t i = 0; i < len; i++) { //sum up the usage of each LED
|
||||
uint32_t c = bus->getPixelColor(i); // always returns original or restored color without brightness scaling
|
||||
@ -1198,38 +1201,44 @@ uint8_t WS2812FX::estimateCurrentAndLimitBri() {
|
||||
|
||||
if (bus->hasWhite()) { //RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
||||
busPowerSum *= 3;
|
||||
busPowerSum = busPowerSum >> 2; //same as /= 4
|
||||
busPowerSum >>= 2; //same as /= 4
|
||||
}
|
||||
powerSum += busPowerSum;
|
||||
}
|
||||
|
||||
uint8_t newBri = _brightness;
|
||||
uint32_t powerSumUnscaled = powerSum;
|
||||
powerSum *= _brightness;
|
||||
if (powerBudget > pLen) { //each LED uses about 1mA in standby, exclude that from power budget
|
||||
powerBudget -= pLen;
|
||||
} else {
|
||||
powerBudget = 0;
|
||||
}
|
||||
|
||||
if (powerSum > powerBudget) { //scale brightness down to stay in current limit
|
||||
// powerSum has all the values of channels summed (max would be pLen*765 as white is excluded) so convert to milliAmps
|
||||
powerSum = (powerSum * actualMilliampsPerLed) / 765;
|
||||
|
||||
uint8_t newBri = _brightness;
|
||||
if (powerSum * _brightness / 255 > powerBudget) { //scale brightness down to stay in current limit
|
||||
float scale = (float)powerBudget / (float)powerSum;
|
||||
uint16_t scaleI = scale * 255;
|
||||
uint8_t scaleB = (scaleI > 255) ? 255 : scaleI;
|
||||
newBri = scale8(_brightness, scaleB);
|
||||
}
|
||||
currentMilliamps = (powerSumUnscaled * newBri) / puPerMilliamp;
|
||||
currentMilliamps = (powerSum * newBri) / 255;
|
||||
currentMilliamps += MA_FOR_ESP; //add power of ESP back to estimate
|
||||
currentMilliamps += pLen; //add standby power back to estimate
|
||||
currentMilliamps += pLen; //add standby power (1mA/LED) back to estimate
|
||||
return newBri;
|
||||
}
|
||||
|
||||
void WS2812FX::show(void) {
|
||||
// avoid race condition, caputre _callback value
|
||||
show_callback callback = _callback;
|
||||
if (callback) callback();
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
static unsigned long sumMicros = 0, sumCurrent = 0;
|
||||
static size_t calls = 0;
|
||||
unsigned long microsStart = micros();
|
||||
#endif
|
||||
|
||||
// avoid race condition, caputre _callback value
|
||||
show_callback callback = _callback;
|
||||
if (callback) callback();
|
||||
|
||||
uint8_t busBrightness = estimateCurrentAndLimitBri();
|
||||
busses.setBrightness(busBrightness);
|
||||
#ifdef WLED_DEBUG
|
||||
@ -1240,21 +1249,22 @@ void WS2812FX::show(void) {
|
||||
// all of the data has been sent.
|
||||
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
||||
busses.show();
|
||||
unsigned long now = millis();
|
||||
unsigned long diff = now - _lastShow;
|
||||
uint16_t fpsCurr = 200;
|
||||
if (diff > 0) fpsCurr = 1000 / diff;
|
||||
_cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2;
|
||||
_lastShow = now;
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
sumMicros += micros() - microsStart;
|
||||
if (++calls == 100) {
|
||||
DEBUG_PRINTF("show calls: %d micros: %lu avg: %lu (current: %lu avg: %lu)\n", calls, sumMicros, sumMicros/calls, sumCurrent, sumCurrent/calls);
|
||||
DEBUG_PRINTF("%d show calls: %lu[us] avg: %lu[us] (current: %lu[us] avg: %lu[us])\n", calls, sumMicros, sumMicros/calls, sumCurrent, sumCurrent/calls);
|
||||
sumMicros = sumCurrent = 0;
|
||||
calls = 0;
|
||||
}
|
||||
#endif
|
||||
|
||||
unsigned long now = millis();
|
||||
size_t diff = now - _lastShow;
|
||||
size_t fpsCurr = 200;
|
||||
if (diff > 0) fpsCurr = 1000 / diff;
|
||||
_cumulativeFps = (3 * _cumulativeFps + fpsCurr) >> 2;
|
||||
_lastShow = now;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -1431,6 +1441,7 @@ Segment& WS2812FX::getSegment(uint8_t id) {
|
||||
return _segments[id >= _segments.size() ? getMainSegmentId() : id]; // vectors
|
||||
}
|
||||
|
||||
// compatibility method (deprecated)
|
||||
void WS2812FX::setSegment(uint8_t n, uint16_t i1, uint16_t i2, uint8_t grouping, uint8_t spacing, uint16_t offset, uint16_t startY, uint16_t stopY) {
|
||||
if (n >= _segments.size()) return;
|
||||
_segments[n].setUp(i1, i2, grouping, spacing, offset, startY, stopY);
|
||||
|
@ -128,11 +128,6 @@ BusDigital::BusDigital(BusConfig &bc, uint8_t nr, const ColorOrderMap &com)
|
||||
|
||||
void BusDigital::show() {
|
||||
if (!_valid) return;
|
||||
#ifdef WLED_DEBUG
|
||||
static unsigned long sumMicros = 0;
|
||||
static size_t calls = 0;
|
||||
unsigned long microsStart = micros();
|
||||
#endif
|
||||
PolyBus::setBrightness(_busPtr, _iType, _bri);
|
||||
if (buffering) { // should be _data != nullptr, but that causes ~20% FPS drop
|
||||
size_t channels = Bus::hasWhite(_type) + 3*Bus::hasRGB(_type);
|
||||
@ -159,14 +154,6 @@ void BusDigital::show() {
|
||||
}
|
||||
PolyBus::show(_busPtr, _iType);
|
||||
PolyBus::setBrightness(_busPtr, _iType, 255); // restore full brightness at bus level (setting unscaled pixel color)
|
||||
#ifdef WLED_DEBUG
|
||||
sumMicros += micros() - microsStart;
|
||||
if (++calls == 100) {
|
||||
DEBUG_PRINTF("Bus calls: %d micros: %lu avg: %lu\n", calls, sumMicros, sumMicros/calls);
|
||||
sumMicros = 0;
|
||||
calls = 0;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
bool BusDigital::canShow() {
|
||||
|
@ -22,7 +22,7 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
||||
|
||||
int stop = elem["stop"] | -1;
|
||||
|
||||
// if using vectors use this code to append segment
|
||||
// append segment
|
||||
if (id >= strip.getSegmentsNum()) {
|
||||
if (stop <= 0) return false; // ignore empty/inactive segments
|
||||
strip.appendSegment(Segment(0, strip.getLengthTotal()));
|
||||
@ -110,7 +110,10 @@ bool deserializeSegment(JsonObject elem, byte it, byte presetId)
|
||||
of = offsetAbs;
|
||||
}
|
||||
if (stop > start && of > len -1) of = len -1;
|
||||
seg.setUp(start, stop, grp, spc, of, startY, stopY);
|
||||
|
||||
// update segment (delete if necessary)
|
||||
// we must not change segment dimensions during drawing of effects as that may produce undesired behaviour (crash)
|
||||
seg.setUp(start, stop, grp, spc, of, startY, stopY, !strip.isServicing());
|
||||
|
||||
if (seg.reset && seg.stop == 0) return true; // segment was deleted & is marked for reset, no need to change anything else
|
||||
|
||||
@ -468,12 +471,14 @@ void serializeSegment(JsonObject& root, Segment& seg, byte id, bool forPreset, b
|
||||
if (segmentBounds) {
|
||||
root["start"] = seg.start;
|
||||
root["stop"] = seg.stop;
|
||||
#ifndef WLED_DISABLE_2D
|
||||
if (strip.isMatrix) {
|
||||
root[F("startY")] = seg.startY;
|
||||
root[F("stopY")] = seg.stopY;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
if (!forPreset) root["len"] = (seg.stop >= seg.start) ? (seg.stop - seg.start) : 0;
|
||||
if (!forPreset) root["len"] = seg.stop - seg.start;
|
||||
root["grp"] = seg.grouping;
|
||||
root[F("spc")] = seg.spacing;
|
||||
root[F("of")] = seg.offset;
|
||||
|
@ -23,7 +23,7 @@ void WLED::reset()
|
||||
#ifdef WLED_ENABLE_WEBSOCKETS
|
||||
ws.closeAll(1012);
|
||||
#endif
|
||||
long dly = millis();
|
||||
unsigned long dly = millis();
|
||||
while (millis() - dly < 450) {
|
||||
yield(); // enough time to send response to client
|
||||
}
|
||||
@ -36,13 +36,17 @@ void WLED::loop()
|
||||
{
|
||||
#ifdef WLED_DEBUG
|
||||
static unsigned long lastRun = 0;
|
||||
size_t loopDelay = (millis() - lastRun);
|
||||
unsigned long loopMillis = millis();
|
||||
size_t loopDelay = loopMillis - lastRun;
|
||||
if (lastRun == 0) loopDelay=0; // startup - don't have valid data from last run.
|
||||
if (loopDelay > 2) DEBUG_PRINTF("Loop delayed more than %dms.\n", loopDelay);
|
||||
if (loopDelay > 2) DEBUG_PRINTF("Loop delayed more than %ums.\n", loopDelay);
|
||||
static unsigned long maxLoopMillis = 0;
|
||||
static size_t avgLoopMillis = 0;
|
||||
static unsigned long maxUsermodMillis = 0;
|
||||
static size_t avgUsermodMillis = 0;
|
||||
static unsigned long maxStripMillis = 0;
|
||||
static size_t avgStripMillis = 0;
|
||||
unsigned long stripMillis;
|
||||
#endif
|
||||
|
||||
handleTime();
|
||||
@ -84,6 +88,9 @@ void WLED::loop()
|
||||
yield();
|
||||
}
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
stripMillis = millis();
|
||||
#endif
|
||||
if (!realtimeMode || realtimeOverride || (realtimeMode && useMainSegmentOnly)) // block stuff if WARLS/Adalight is enabled
|
||||
{
|
||||
if (apActive) dnsServer.processNextRequest();
|
||||
@ -102,22 +109,18 @@ void WLED::loop()
|
||||
handlePresets();
|
||||
yield();
|
||||
|
||||
#ifdef WLED_DEBUG
|
||||
unsigned long stripMillis = millis();
|
||||
#endif
|
||||
if (!offMode || strip.isOffRefreshRequired())
|
||||
strip.service();
|
||||
#ifdef ESP8266
|
||||
else if (!noWifiSleep)
|
||||
delay(1); //required to make sure ESP enters modem sleep (see #1184)
|
||||
#endif
|
||||
}
|
||||
#ifdef WLED_DEBUG
|
||||
stripMillis = millis() - stripMillis;
|
||||
if (stripMillis > 50) DEBUG_PRINTLN("Slow strip.");
|
||||
avgStripMillis += stripMillis;
|
||||
if (stripMillis > maxStripMillis) maxStripMillis = stripMillis;
|
||||
#endif
|
||||
}
|
||||
|
||||
yield();
|
||||
#ifdef ESP8266
|
||||
@ -186,8 +189,31 @@ void WLED::loop()
|
||||
handleWs();
|
||||
handleStatusLED();
|
||||
|
||||
toki.resetTick();
|
||||
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
// we finished our mainloop, reset the watchdog timer
|
||||
if (!strip.isUpdating())
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
esp_task_wdt_reset();
|
||||
#else
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration
|
||||
reset();
|
||||
|
||||
// DEBUG serial logging (every 30s)
|
||||
#ifdef WLED_DEBUG
|
||||
loopMillis = millis() - loopMillis;
|
||||
if (loopMillis > 30) {
|
||||
DEBUG_PRINTF("Loop took %lums.\n", loopMillis);
|
||||
DEBUG_PRINTF("Usermods took %lums.\n", usermodMillis);
|
||||
DEBUG_PRINTF("Strip took %lums.\n", stripMillis);
|
||||
}
|
||||
avgLoopMillis += loopMillis;
|
||||
if (loopMillis > maxLoopMillis) maxLoopMillis = loopMillis;
|
||||
if (millis() - debugTime > 29999) {
|
||||
DEBUG_PRINTLN(F("---DEBUG INFO---"));
|
||||
DEBUG_PRINT(F("Runtime: ")); DEBUG_PRINTLN(millis());
|
||||
@ -210,11 +236,13 @@ void WLED::loop()
|
||||
DEBUG_PRINT(F("Client IP: ")); DEBUG_PRINTLN(Network.localIP());
|
||||
if (loops > 0) { // avoid division by zero
|
||||
DEBUG_PRINT(F("Loops/sec: ")); DEBUG_PRINTLN(loops / 30);
|
||||
DEBUG_PRINT(F("Loop time[ms]: ")); DEBUG_PRINT(avgLoopMillis/loops); DEBUG_PRINT("/");DEBUG_PRINTLN(maxLoopMillis);
|
||||
DEBUG_PRINT(F("UM time[ms]: ")); DEBUG_PRINT(avgUsermodMillis/loops); DEBUG_PRINT("/");DEBUG_PRINTLN(maxUsermodMillis);
|
||||
DEBUG_PRINT(F("Strip time[ms]: ")); DEBUG_PRINT(avgStripMillis/loops); DEBUG_PRINT("/"); DEBUG_PRINTLN(maxStripMillis);
|
||||
}
|
||||
strip.printSize();
|
||||
loops = 0;
|
||||
maxLoopMillis = 0;
|
||||
maxUsermodMillis = 0;
|
||||
maxStripMillis = 0;
|
||||
avgUsermodMillis = 0;
|
||||
@ -224,20 +252,6 @@ void WLED::loop()
|
||||
loops++;
|
||||
lastRun = millis();
|
||||
#endif // WLED_DEBUG
|
||||
toki.resetTick();
|
||||
|
||||
#if WLED_WATCHDOG_TIMEOUT > 0
|
||||
// we finished our mainloop, reset the watchdog timer
|
||||
if (!strip.isUpdating())
|
||||
#ifdef ARDUINO_ARCH_ESP32
|
||||
esp_task_wdt_reset();
|
||||
#else
|
||||
ESP.wdtFeed();
|
||||
#endif
|
||||
#endif
|
||||
|
||||
if (doReboot && (!doInitBusses || !doSerializeConfig)) // if busses have to be inited & saved, wait until next iteration
|
||||
reset();
|
||||
}
|
||||
|
||||
void WLED::enableWatchdog() {
|
||||
|
@ -8,7 +8,7 @@
|
||||
*/
|
||||
|
||||
// version code in format yymmddb (b = daily build)
|
||||
#define VERSION 2307090
|
||||
#define VERSION 2307120
|
||||
|
||||
//uncomment this if you have a "my_config.h" file you'd like to use
|
||||
//#define WLED_USE_MY_CONFIG
|
||||
@ -513,7 +513,7 @@ WLED_GLOBAL uint16_t userVar0 _INIT(0), userVar1 _INIT(0); //available for use i
|
||||
// wifi
|
||||
WLED_GLOBAL bool apActive _INIT(false);
|
||||
WLED_GLOBAL bool forceReconnect _INIT(false);
|
||||
WLED_GLOBAL uint32_t lastReconnectAttempt _INIT(0);
|
||||
WLED_GLOBAL unsigned long lastReconnectAttempt _INIT(0);
|
||||
WLED_GLOBAL bool interfacesInited _INIT(false);
|
||||
WLED_GLOBAL bool wasConnected _INIT(false);
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user