use video scaling instead of NPB luminance & new ABL (#4798)
* updated color scaling to preserve hue at low brightness resulting in much better colors * replace NPBlg with NPB, moved brightness scaling to bus manager * improved gamma table calculation: fixed mismatch in inverting gamma table calculation: inversion should now be as good as it gets * code cleanup, fixed gamma being applied in unnecessary places Improvements to ABL handling: - removed strip level handling, ist now all done on bus level - limiter now respects pixel mapping - proper handling of white channel - improved current estimation - current is now always correctly reported to UI - minimal FPS impact if the ABL is not limiting but slighly higher impact for global ABL limit due to double-scaling - moved brightness scaling to BusDigital - created new header file colors.h to be able to access color functions in bus-manager. - updated colo_fade() with better video scaling to preserve hue's at low brightness - added IRAM_ATTR to color_fade (negligible speed impact when compared to inline and benefits other functions) - added IRAM_ATTR to color_blend as it is used a lot throughout the code, did not test speed impact but adding it to color_fade made it almost on-par with an inlined function Additional changes: - fixes for properly handling `scaledBri()` (by @blazoncek) - also use bit-shift instead of division in blending for ESP8266 - improvements for faster "softlight" calculation in blending - changed some variables to uint8_t to maybe let the compiler optimize better, uint8_t can be faster if read, store and set are all done in uint8_t, which is the case in the ones I changed - various minor code formatting changes
This commit is contained in:
@@ -1194,8 +1194,9 @@ void WS2812FX::finalizeInit() {
|
||||
if (busEnd > _length) _length = busEnd;
|
||||
// This must be done after all buses have been created, as some kinds (parallel I2S) interact
|
||||
bus->begin();
|
||||
bus->setBrightness(bri);
|
||||
bus->setBrightness(scaledBri(bri));
|
||||
}
|
||||
BusManager::initializeABL(); // init brightness limiter
|
||||
DEBUG_PRINTF_P(PSTR("Heap after buses: %d\n"), ESP.getFreeHeap());
|
||||
|
||||
Segment::maxWidth = _length;
|
||||
@@ -1297,7 +1298,7 @@ static uint8_t _add (uint8_t a, uint8_t b) { unsigned t = a + b; return t
|
||||
static uint8_t _subtract (uint8_t a, uint8_t b) { return b > a ? (b - a) : 0; }
|
||||
static uint8_t _difference(uint8_t a, uint8_t b) { return b > a ? (b - a) : (a - b); }
|
||||
static uint8_t _average (uint8_t a, uint8_t b) { return (a + b) >> 1; }
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return ((a * b) + 255) >> 8; } // faster than division on C3 but slightly less accurate
|
||||
#else
|
||||
static uint8_t _multiply (uint8_t a, uint8_t b) { return (a * b) / 255; } // origianl uses a & b in range [0,1]
|
||||
@@ -1308,10 +1309,10 @@ static uint8_t _darken (uint8_t a, uint8_t b) { return a < b ? a : b; }
|
||||
static uint8_t _screen (uint8_t a, uint8_t b) { return 255 - _multiply(~a,~b); } // 255 - (255-a)*(255-b)/255
|
||||
static uint8_t _overlay (uint8_t a, uint8_t b) { return b < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||
static uint8_t _hardlight (uint8_t a, uint8_t b) { return a < 128 ? 2 * _multiply(a,b) : (255 - 2 * _multiply(~a,~b)); }
|
||||
#ifdef CONFIG_IDF_TARGET_ESP32C3
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a) + 255) >> 8) + 2 * a * b + 255) >> 8; } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
#if defined(ESP8266) || defined(CONFIG_IDF_TARGET_ESP32C3)
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (((b * b * (255 - 2 * a))) + ((2 * a * b + 256) << 8)) >> 16; } // Pegtop's formula (1 - 2a)b^2
|
||||
#else
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) / 255 + 2 * a * b) / 255; } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
static uint8_t _softlight (uint8_t a, uint8_t b) { return (b * b * (255 - 2 * a) + 255 * 2 * a * b) / (255 * 255); } // Pegtop's formula (1 - 2a)b^2 + 2ab
|
||||
#endif
|
||||
static uint8_t _dodge (uint8_t a, uint8_t b) { return _divide(~a,b); }
|
||||
static uint8_t _burn (uint8_t a, uint8_t b) { return ~_divide(a,~b); }
|
||||
@@ -1550,66 +1551,6 @@ void WS2812FX::blendSegment(const Segment &topSegment) const {
|
||||
Segment::setClippingRect(0, 0); // disable clipping for overlays
|
||||
}
|
||||
|
||||
// To disable brightness limiter we either set output max current to 0 or single LED current to 0
|
||||
static uint8_t estimateCurrentAndLimitBri(uint8_t brightness, uint32_t *pixels) {
|
||||
unsigned milliAmpsMax = BusManager::ablMilliampsMax();
|
||||
if (milliAmpsMax > 0) {
|
||||
unsigned milliAmpsTotal = 0;
|
||||
unsigned avgMilliAmpsPerLED = 0;
|
||||
unsigned lengthDigital = 0;
|
||||
bool useWackyWS2815PowerModel = false;
|
||||
|
||||
for (size_t i = 0; i < BusManager::getNumBusses(); i++) {
|
||||
const Bus *bus = BusManager::getBus(i);
|
||||
if (!(bus && bus->isDigital() && bus->isOk())) continue;
|
||||
unsigned maPL = bus->getLEDCurrent();
|
||||
if (maPL == 0 || bus->getMaxCurrent() > 0) continue; // skip buses with 0 mA per LED or max current per bus defined (PP-ABL)
|
||||
if (maPL == 255) {
|
||||
useWackyWS2815PowerModel = true;
|
||||
maPL = 12; // WS2815 uses 12mA per channel
|
||||
}
|
||||
avgMilliAmpsPerLED += maPL * bus->getLength();
|
||||
lengthDigital += bus->getLength();
|
||||
// sum up the usage of each LED on digital bus
|
||||
uint32_t busPowerSum = 0;
|
||||
for (unsigned j = 0; j < bus->getLength(); j++) {
|
||||
uint32_t c = pixels[j + bus->getStart()];
|
||||
byte r = R(c), g = G(c), b = B(c), w = W(c);
|
||||
if (useWackyWS2815PowerModel) { //ignore white component on WS2815 power calculation
|
||||
busPowerSum += (max(max(r,g),b)) * 3;
|
||||
} else {
|
||||
busPowerSum += (r + g + b + w);
|
||||
}
|
||||
}
|
||||
// RGBW led total output with white LEDs enabled is still 50mA, so each channel uses less
|
||||
if (bus->hasWhite()) {
|
||||
busPowerSum *= 3;
|
||||
busPowerSum >>= 2; //same as /= 4
|
||||
}
|
||||
// powerSum has all the values of channels summed (max would be getLength()*765 as white is excluded) so convert to milliAmps
|
||||
milliAmpsTotal += (busPowerSum * maPL * brightness) / (765*255);
|
||||
}
|
||||
if (lengthDigital > 0) {
|
||||
avgMilliAmpsPerLED /= lengthDigital;
|
||||
|
||||
if (milliAmpsMax > MA_FOR_ESP && avgMilliAmpsPerLED > 0) { //0 mA per LED and too low numbers turn off calculation
|
||||
unsigned powerBudget = (milliAmpsMax - MA_FOR_ESP); //80/120mA for ESP power
|
||||
if (powerBudget > lengthDigital) { //each LED uses about 1mA in standby, exclude that from power budget
|
||||
powerBudget -= lengthDigital;
|
||||
} else {
|
||||
powerBudget = 0;
|
||||
}
|
||||
if (milliAmpsTotal > powerBudget) {
|
||||
//scale brightness down to stay in current limit
|
||||
unsigned scaleB = powerBudget * 255 / milliAmpsTotal;
|
||||
brightness = ((brightness * scaleB) >> 8) + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return brightness;
|
||||
}
|
||||
|
||||
void WS2812FX::show() {
|
||||
if (!_pixels) return; // no pixels allocated, nothing to show
|
||||
|
||||
@@ -1637,10 +1578,6 @@ void WS2812FX::show() {
|
||||
show_callback callback = _callback;
|
||||
if (callback) callback(); // will call setPixelColor or setRealtimePixelColor
|
||||
|
||||
// determine ABL brightness
|
||||
uint8_t newBri = estimateCurrentAndLimitBri(_brightness, _pixels);
|
||||
if (newBri != _brightness) BusManager::setBrightness(newBri);
|
||||
|
||||
// paint actual pixels
|
||||
int oldCCT = Bus::getCCT(); // store original CCT value (since it is global)
|
||||
// when cctFromRgb is true we implicitly calculate WW and CW from RGB values (cct==-1)
|
||||
@@ -1651,7 +1588,11 @@ void WS2812FX::show() {
|
||||
if (_pixelCCT) { // cctFromRgb already exluded at allocation
|
||||
if (i == 0 || _pixelCCT[i-1] != _pixelCCT[i]) BusManager::setSegmentCCT(_pixelCCT[i], correctWB);
|
||||
}
|
||||
BusManager::setPixelColor(getMappedPixelIndex(i), realtimeMode && arlsDisableGammaCorrection ? _pixels[i] : gamma32(_pixels[i]));
|
||||
|
||||
uint32_t c = _pixels[i]; // need a copy, do not modify _pixels directly (no byte access allowed on ESP32)
|
||||
if(c > 0 && !(realtimeMode && arlsDisableGammaCorrection))
|
||||
c = gamma32(c); // apply gamma correction if enabled note: applying gamma after brightness has too much color loss
|
||||
BusManager::setPixelColor(getMappedPixelIndex(i), c);
|
||||
}
|
||||
Bus::setCCT(oldCCT); // restore old CCT for ABL adjustments
|
||||
|
||||
@@ -1663,9 +1604,6 @@ void WS2812FX::show() {
|
||||
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
|
||||
BusManager::show();
|
||||
|
||||
// restore brightness for next frame
|
||||
if (newBri != _brightness) BusManager::setBrightness(_brightness);
|
||||
|
||||
if (diff > 0) { // skip calculation if no time has passed
|
||||
size_t fpsCurr = (1000 << FPS_CALC_SHIFT) / diff; // fixed point math
|
||||
_cumulativeFps = (FPS_CALC_AVG * _cumulativeFps + fpsCurr + FPS_CALC_AVG / 2) / (FPS_CALC_AVG + 1); // "+FPS_CALC_AVG/2" for proper rounding
|
||||
@@ -1730,7 +1668,7 @@ void WS2812FX::setBrightness(uint8_t b, bool direct) {
|
||||
if (_brightness == 0) { //unfreeze all segments on power off
|
||||
for (const Segment &seg : _segments) seg.freeze = false; // freeze is mutable
|
||||
}
|
||||
BusManager::setBrightness(b);
|
||||
BusManager::setBrightness(scaledBri(b));
|
||||
if (!direct) {
|
||||
unsigned long t = millis();
|
||||
if (_segments[0].next_time > t + 22 && t - _lastShow > MIN_SHOW_DELAY) trigger(); //apply brightness change immediately if no refresh soon
|
||||
|
||||
Reference in New Issue
Block a user