From 451cd4c74aff2faff9ab11f746f9e454ab10bd2e Mon Sep 17 00:00:00 2001
From: Frank <91616163+softhack007@users.noreply.github.com>
Date: Fri, 1 Nov 2024 23:19:38 +0100
Subject: [PATCH 1/9] Improved framerate control in strip.show(),
strip.service()
* separate fps calculation (strip.show) from framerate control (strio.service)
* improved condition for early exit in strip.show
* make MIN_SHOW_DELAY depend on target fps
* strip.show consideres complete time for effect calculation + show; old code wrongly used the time between completion of last show and start of next effect drawing, causing unexpected slowdown
* add "unlimited FPS mode" for testing
* increase warning limits for "slow strip" and "slow effects"
---
wled00/FX.h | 22 +++++++++++++++++-----
wled00/FX_fcn.cpp | 37 +++++++++++++++++++++++++++----------
2 files changed, 44 insertions(+), 15 deletions(-)
diff --git a/wled00/FX.h b/wled00/FX.h
index ad39a7c06..acdb62c81 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -44,8 +44,20 @@
/* Not used in all effects yet */
#define WLED_FPS 42
-#define FRAMETIME_FIXED (1000/WLED_FPS)
#define FRAMETIME strip.getFrameTime()
+#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested)
+ #define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) // allow faster FRAMETIME_FIXED when target FPS >= 100
+ #define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62%
+ // used to initialize for strip attributes:
+ #define WLED_FPS_SLOW 42
+ #define FRAMETIME_FIXED_SLOW (24) // 1000/42 = 24ms
+#else
+ #define FRAMETIME_FIXED (1000/WLED_FPS)
+ #define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates
+ #define WLED_FPS_SLOW WLED_FPS
+ #define FRAMETIME_FIXED_SLOW FRAMETIME_FIXED
+#endif
+#define FPS_UNLIMITED 119
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
@@ -68,8 +80,6 @@
assuming each segment uses the same amount of data. 256 for ESP8266, 640 for ESP32. */
#define FAIR_DATA_PER_SEG (MAX_SEGMENT_DATA / strip.getMaxSegments())
-#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15)
-
#define NUM_COLORS 3 /* number of colors per segment */
#define SEGMENT strip._segments[strip.getCurrSegmentId()]
#define SEGENV strip._segments[strip.getCurrSegmentId()]
@@ -727,8 +737,8 @@ class WS2812FX { // 96 bytes
_length(DEFAULT_LED_COUNT),
_brightness(DEFAULT_BRIGHTNESS),
_transitionDur(750),
- _targetFps(WLED_FPS),
- _frametime(FRAMETIME_FIXED),
+ _targetFps(WLED_FPS_SLOW),
+ _frametime(FRAMETIME_FIXED_SLOW),
_cumulativeFps(2),
_isServicing(false),
_isOffRefreshRequired(false),
@@ -739,6 +749,7 @@ class WS2812FX { // 96 bytes
customMappingTable(nullptr),
customMappingSize(0),
_lastShow(0),
+ _lastServiceShow(0),
_segment_index(0),
_mainSegment(0)
{
@@ -949,6 +960,7 @@ class WS2812FX { // 96 bytes
uint16_t customMappingSize;
unsigned long _lastShow;
+ unsigned long _lastServiceShow;
uint8_t _segment_index;
uint8_t _mainSegment;
diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp
index 949b6a932..5856ad786 100644
--- a/wled00/FX_fcn.cpp
+++ b/wled00/FX_fcn.cpp
@@ -1309,11 +1309,23 @@ void WS2812FX::finalizeInit() {
void WS2812FX::service() {
unsigned long nowUp = millis(); // Be aware, millis() rolls over every 49 days
now = nowUp + timebase;
- if (nowUp - _lastShow < MIN_SHOW_DELAY || _suspend) return;
+ if (_suspend) return;
+ unsigned long elapsed = nowUp - _lastServiceShow;
+
+ #if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
+ if (elapsed < 2) return; // keep wifi alive
+ if ( !_triggered && (_targetFps < FPS_UNLIMITED) && (_targetFps > 0)) {
+ if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service
+ }
+ #else // legacy
+ if (nowUp - _lastShow < MIN_SHOW_DELAY) return;
+ #endif
+
bool doShow = false;
_isServicing = true;
_segment_index = 0;
+ unsigned speedLimit = (_targetFps < FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime
for (segment &seg : _segments) {
if (_suspend) return; // immediately stop processing segments if suspend requested during service()
@@ -1326,10 +1338,10 @@ void WS2812FX::service() {
if (!seg.isActive()) continue;
// 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 (nowUp >= seg.next_time || _triggered || (doShow && seg.mode == FX_MODE_STATIC))
{
doShow = true;
- unsigned delay = FRAMETIME;
+ unsigned frameDelay = FRAMETIME;
if (!seg.freeze) { //only run effect function if not frozen
int oldCCT = BusManager::getSegmentCCT(); // store original CCT value (actually it is not Segment based)
@@ -1349,7 +1361,8 @@ void WS2812FX::service() {
// 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.
[[maybe_unused]] uint8_t tmpMode = seg.currentMode(); // this will return old mode while in transition
- delay = (*_mode[seg.mode])(); // run new/current mode
+ frameDelay = (*_mode[seg.mode])(); // run new/current mode
+ if (frameDelay < speedLimit) frameDelay = FRAMETIME; // limit effects that want to go faster than target FPS
#ifndef WLED_DISABLE_MODE_BLEND
if (modeBlending && seg.mode != tmpMode) {
Segment::tmpsegd_t _tmpSegData;
@@ -1358,16 +1371,16 @@ void WS2812FX::service() {
_virtualSegmentLength = seg.virtualLength(); // update SEGLEN (mapping may have changed)
unsigned d2 = (*_mode[tmpMode])(); // run old mode
seg.restoreSegenv(_tmpSegData); // restore mode state (will also update transitional state)
- delay = MIN(delay,d2); // use shortest delay
+ frameDelay = min(frameDelay,d2); // use shortest delay
Segment::modeBlend(false); // unset semaphore
}
#endif
seg.call++;
- if (seg.isInTransition() && delay > FRAMETIME) delay = FRAMETIME; // force faster updates during transition
+ if (seg.isInTransition() && frameDelay > FRAMETIME) frameDelay = FRAMETIME; // force faster updates during transition
BusManager::setSegmentCCT(oldCCT); // restore old CCT for ABL adjustments
}
- seg.next_time = nowUp + delay;
+ seg.next_time = nowUp + frameDelay;
}
_segment_index++;
}
@@ -1376,15 +1389,16 @@ void WS2812FX::service() {
_triggered = false;
#ifdef WLED_DEBUG
- if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
+ if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
if (doShow) {
yield();
Segment::handleRandomPalette(); // slowly transition random palette; move it into for loop when each segment has individual random palette
show();
+ _lastServiceShow = nowUp; // correct timestamp, for better FPS control
}
#ifdef WLED_DEBUG
- if (millis() - nowUp > _frametime) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
+ if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
}
@@ -1404,18 +1418,19 @@ void WS2812FX::show() {
// avoid race condition, capture _callback value
show_callback callback = _callback;
if (callback) callback();
+ unsigned long showNow = millis();
// some buses send asynchronously and this method will return before
// all of the data has been sent.
// See https://github.com/Makuna/NeoPixelBus/wiki/ESP32-NeoMethods#neoesp32rmt-methods
BusManager::show();
- unsigned long showNow = millis();
size_t diff = showNow - _lastShow;
size_t fpsCurr = 200;
if (diff > 0) fpsCurr = 1000 / diff;
_cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5)
_lastShow = showNow;
+ _lastServiceShow = showNow;
}
/**
@@ -1438,6 +1453,8 @@ uint16_t WS2812FX::getFps() const {
void WS2812FX::setTargetFps(uint8_t fps) {
if (fps > 0 && fps <= 120) _targetFps = fps;
_frametime = 1000 / _targetFps;
+ if (_frametime < 1) _frametime = 1; // better safe than sorry
+ if (fps >= FPS_UNLIMITED) _frametime = 3; // unlimited mode
}
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
From 50934e6840995154109ab7d532dbb61af0377f90 Mon Sep 17 00:00:00 2001
From: Frank <91616163+softhack007@users.noreply.github.com>
Date: Sat, 2 Nov 2024 18:16:51 +0100
Subject: [PATCH 2/9] adressing some review comments
* keep FRAMETIME_FIXED as a fixed value
* remove WLED_FPS_SLOW and FRAMETIME_FIXED_SLOW
* explicit test "(_targetFps != FPS_UNLIMITED)" for debug messages
* don't modify _lastServiceShow in show()
* test for "fps == FPS_UNLIMITED" explicitly, so we could pick a different
magic number later
---
wled00/FX.h | 13 ++++---------
wled00/FX_fcn.cpp | 12 +++++-------
2 files changed, 9 insertions(+), 16 deletions(-)
diff --git a/wled00/FX.h b/wled00/FX.h
index acdb62c81..1f0df9da3 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -44,20 +44,15 @@
/* Not used in all effects yet */
#define WLED_FPS 42
+#define FRAMETIME_FIXED (1000/WLED_FPS)
#define FRAMETIME strip.getFrameTime()
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3) // all ESP32 except -C3(very slow, untested)
- #define FRAMETIME_FIXED (strip.getFrameTime() < 10 ? 12 : 24) // allow faster FRAMETIME_FIXED when target FPS >= 100
#define MIN_SHOW_DELAY (max(2, (_frametime*5)/8)) // supports higher framerates and better animation control -- 5/8 = 62%
// used to initialize for strip attributes:
- #define WLED_FPS_SLOW 42
- #define FRAMETIME_FIXED_SLOW (24) // 1000/42 = 24ms
#else
- #define FRAMETIME_FIXED (1000/WLED_FPS)
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates
- #define WLED_FPS_SLOW WLED_FPS
- #define FRAMETIME_FIXED_SLOW FRAMETIME_FIXED
#endif
-#define FPS_UNLIMITED 119
+#define FPS_UNLIMITED 120
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
@@ -737,8 +732,8 @@ class WS2812FX { // 96 bytes
_length(DEFAULT_LED_COUNT),
_brightness(DEFAULT_BRIGHTNESS),
_transitionDur(750),
- _targetFps(WLED_FPS_SLOW),
- _frametime(FRAMETIME_FIXED_SLOW),
+ _targetFps(WLED_FPS),
+ _frametime(FRAMETIME_FIXED),
_cumulativeFps(2),
_isServicing(false),
_isOffRefreshRequired(false),
diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp
index 5856ad786..3972dad2c 100644
--- a/wled00/FX_fcn.cpp
+++ b/wled00/FX_fcn.cpp
@@ -1314,7 +1314,7 @@ void WS2812FX::service() {
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
if (elapsed < 2) return; // keep wifi alive
- if ( !_triggered && (_targetFps < FPS_UNLIMITED) && (_targetFps > 0)) {
+ if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps > 0)) {
if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service
}
#else // legacy
@@ -1325,7 +1325,7 @@ void WS2812FX::service() {
_isServicing = true;
_segment_index = 0;
- unsigned speedLimit = (_targetFps < FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime
+ unsigned speedLimit = (_targetFps != FPS_UNLIMITED) ? (0.85f * FRAMETIME) : 1; // lower limit for effect frametime
for (segment &seg : _segments) {
if (_suspend) return; // immediately stop processing segments if suspend requested during service()
@@ -1389,7 +1389,7 @@ void WS2812FX::service() {
_triggered = false;
#ifdef WLED_DEBUG
- if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
+ if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow effects %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
if (doShow) {
yield();
@@ -1398,7 +1398,7 @@ void WS2812FX::service() {
_lastServiceShow = nowUp; // correct timestamp, for better FPS control
}
#ifdef WLED_DEBUG
- if (millis() - nowUp > _frametime*2) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
+ if ((_targetFps != FPS_UNLIMITED) && (millis() - nowUp > _frametime)) DEBUG_PRINTF_P(PSTR("Slow strip %u/%d.\n"), (unsigned)(millis()-nowUp), (int)_frametime);
#endif
}
@@ -1430,7 +1430,6 @@ void WS2812FX::show() {
if (diff > 0) fpsCurr = 1000 / diff;
_cumulativeFps = (3 * _cumulativeFps + fpsCurr +2) >> 2; // "+2" for proper rounding (2/4 = 0.5)
_lastShow = showNow;
- _lastServiceShow = showNow;
}
/**
@@ -1453,8 +1452,7 @@ uint16_t WS2812FX::getFps() const {
void WS2812FX::setTargetFps(uint8_t fps) {
if (fps > 0 && fps <= 120) _targetFps = fps;
_frametime = 1000 / _targetFps;
- if (_frametime < 1) _frametime = 1; // better safe than sorry
- if (fps >= FPS_UNLIMITED) _frametime = 3; // unlimited mode
+ if (fps == FPS_UNLIMITED) _frametime = 3; // unlimited mode
}
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
From cf1630a94a3de7b59e2dd21562a607627c9fa2d2 Mon Sep 17 00:00:00 2001
From: Frank <91616163+softhack007@users.noreply.github.com>
Date: Mon, 4 Nov 2024 19:49:43 +0100
Subject: [PATCH 3/9] 0 FPS = unlimited
---
wled00/FX.h | 2 +-
wled00/FX_fcn.cpp | 8 ++++----
wled00/data/settings_leds.htm | 8 +++++++-
3 files changed, 12 insertions(+), 6 deletions(-)
diff --git a/wled00/FX.h b/wled00/FX.h
index 1f0df9da3..5c80686d6 100644
--- a/wled00/FX.h
+++ b/wled00/FX.h
@@ -52,7 +52,7 @@
#else
#define MIN_SHOW_DELAY (_frametime < 16 ? 8 : 15) // legacy MIN_SHOW_DELAY - creates more idle loops, but reduces framerates
#endif
-#define FPS_UNLIMITED 120
+#define FPS_UNLIMITED 0
/* each segment uses 82 bytes of SRAM memory, so if you're application fails because of
insufficient memory, decreasing MAX_NUM_SEGMENTS may help */
diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp
index 3972dad2c..30882c4e7 100644
--- a/wled00/FX_fcn.cpp
+++ b/wled00/FX_fcn.cpp
@@ -1314,7 +1314,7 @@ void WS2812FX::service() {
#if defined(ARDUINO_ARCH_ESP32) && !defined(CONFIG_IDF_TARGET_ESP32C3)
if (elapsed < 2) return; // keep wifi alive
- if ( !_triggered && (_targetFps != FPS_UNLIMITED) && (_targetFps > 0)) {
+ if ( !_triggered && (_targetFps != FPS_UNLIMITED)) {
if (elapsed < MIN_SHOW_DELAY) return; // WLEDMM too early for service
}
#else // legacy
@@ -1450,9 +1450,9 @@ uint16_t WS2812FX::getFps() const {
}
void WS2812FX::setTargetFps(uint8_t fps) {
- if (fps > 0 && fps <= 120) _targetFps = fps;
- _frametime = 1000 / _targetFps;
- if (fps == FPS_UNLIMITED) _frametime = 3; // unlimited mode
+ if (fps <= 120) _targetFps = fps;
+ if (_targetFps > 0) _frametime = 1000 / _targetFps;
+ else _frametime = 3; // unlimited mode
}
void WS2812FX::setMode(uint8_t segid, uint8_t m) {
diff --git a/wled00/data/settings_leds.htm b/wled00/data/settings_leds.htm
index 6be5becd1..35bbbd312 100644
--- a/wled00/data/settings_leds.htm
+++ b/wled00/data/settings_leds.htm
@@ -380,6 +380,10 @@
gId('psu').innerHTML = s;
gId('psu2').innerHTML = s2;
gId("json").style.display = d.Sf.IT.value==8 ? "" : "none";
+
+ // show/hide unlimited FPS message
+ gId('fpsNone').style.display = (d.Sf.FR.value == 0) ? 'block':'none';
+ gId('fpsHelp').style.display = (d.Sf.FR.value == 0)? 'none':'block';
}
function lastEnd(i) {
if (i-- < 1) return 0;
@@ -870,7 +874,9 @@ Swap:
- Target refresh rate: FPS
+ Target refresh rate: FPS
+