diff --git a/.github/workflows/pr-merge.yaml b/.github/workflows/pr-merge.yaml index 6b74d8820b..1efc366cc5 100644 --- a/.github/workflows/pr-merge.yaml +++ b/.github/workflows/pr-merge.yaml @@ -33,6 +33,6 @@ run: | jq -n \ --arg content "Pull Request #${PR_NUMBER} \"${PR_TITLE}\" merged by ${ACTOR} - ${PR_URL}. It will be included in the next nightly builds, please test" \ + ${PR_URL} . It will be included in the next nightly builds, please test" \ '{content: $content}' \ | curl -H "Content-Type: application/json" -d @- ${{ secrets.DISCORD_WEBHOOK_BETA_TESTERS }} diff --git a/platformio_override.sample.ini b/platformio_override.sample.ini index 597f487383..0caa6eafba 100644 --- a/platformio_override.sample.ini +++ b/platformio_override.sample.ini @@ -549,8 +549,9 @@ platform_packages = build_unflags = ${common.build_unflags} build_flags = ${common.build_flags} -D WLED_RELEASE_NAME=\"ESP32_hub75\" - -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 - ; -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX + -D LED_TYPES=65 -D DATA_PINS=64,64,1 + -D WLED_DEBUG_BUS ; -D WLED_DEBUG lib_deps = ${esp32_idf_V4.lib_deps} https://github.com/mrfaptastic/ESP32-HUB75-MatrixPanel-DMA.git#3.0.11 @@ -558,16 +559,20 @@ lib_deps = ${esp32_idf_V4.lib_deps} monitor_filters = esp32_exception_decoder board_build.partitions = ${esp32.default_partitions} board_build.flash_mode = dio +custom_usermods = audioreactive [env:esp32dev_hub75_forum_pinout] extends = env:esp32dev_hub75 build_flags = ${common.build_flags} -D WLED_RELEASE_NAME=\"ESP32_hub75_forum_pinout\" - -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D ESP32_FORUM_PINOUT ;; enable for SmartMatrix default pins + -D LED_TYPES=65 -D DATA_PINS=64,64,1 + -D WLED_DEBUG_BUS ; -D WLED_DEBUG + [env:adafruit_matrixportal_esp32s3] ; ESP32-S3 processor, 8 MB flash, 2 MB of PSRAM, dedicated driver pins for HUB75 board = adafruit_matrixportal_esp32s3 @@ -581,9 +586,12 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 ${esp32.AR_build_flags} - -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips -D ARDUINO_ADAFRUIT_MATRIXPORTAL_ESP32S3 + -D LED_TYPES=65 -D DATA_PINS=64,64,1 + -D WLED_DEBUG_BUS + lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} @@ -593,6 +601,7 @@ board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +custom_usermods = audioreactive [env:esp32S3_PSRAM_HUB75] ;; MOONHUB HUB75 adapter board @@ -607,9 +616,11 @@ build_flags = ${common.build_flags} ${esp32s3.build_flags} -D WLED_RELEASE_NAME= -DLOLIN_WIFI_FIX ; seems to work much better with this -D WLED_WATCHDOG_TIMEOUT=0 ${esp32.AR_build_flags} - -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D NO_CIE1931 + -D WLED_ENABLE_HUB75MATRIX -D NO_GFX -D S3_LCD_DIV_NUM=20 ;; Attempt to fix wifi performance issue when panel active with S3 chips -D MOONHUB_S3_PINOUT ;; HUB75 pinout + -D LED_TYPES=65 -D DATA_PINS=64,64,1 + -D WLED_DEBUG_BUS lib_deps = ${esp32s3.lib_deps} ${esp32.AR_lib_deps} @@ -619,3 +630,4 @@ board_build.partitions = ${esp32.default_partitions} board_build.f_flash = 80000000L board_build.flash_mode = qio monitor_filters = esp32_exception_decoder +custom_usermods = audioreactive diff --git a/tools/cdata.js b/tools/cdata.js index e02047e8b3..c05b28e522 100644 --- a/tools/cdata.js +++ b/tools/cdata.js @@ -143,7 +143,7 @@ async function writeHtmlGzipped(sourceFile, resultFile, page) { console.info("Minified and compressed " + sourceFile + " from " + originalLength + " to " + result.length + " bytes"); const array = hexdump(result); let src = singleHeader; - src += `const uint16_t PAGE_${page}_L = ${result.length};\n`; + src += `const uint16_t PAGE_${page}_length = ${result.length};\n`; src += `const uint8_t PAGE_${page}[] PROGMEM = {\n${array}\n};\n\n`; console.info("Writing " + resultFile); fs.writeFileSync(resultFile, src); @@ -244,9 +244,22 @@ if (isAlreadyBuilt("wled00/data") && process.argv[2] !== '--force' && process.ar writeHtmlGzipped("wled00/data/index.htm", "wled00/html_ui.h", 'index'); writeHtmlGzipped("wled00/data/pixart/pixart.htm", "wled00/html_pixart.h", 'pixart'); -writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); +//writeHtmlGzipped("wled00/data/cpal/cpal.htm", "wled00/html_cpal.h", 'cpal'); writeHtmlGzipped("wled00/data/pxmagic/pxmagic.htm", "wled00/html_pxmagic.h", 'pxmagic'); +writeChunks( + "wled00/data/cpal", + [ + { + file: "cpal.htm", + name: "PAGE_cpal", + method: "gzip", + filter: "html-minify" + } + ], + "wled00/html_cpal.h" +); + writeChunks( "wled00/data", [ diff --git a/tools/wled-tools b/tools/wled-tools index 9d196526f7..34fb6370df 100755 --- a/tools/wled-tools +++ b/tools/wled-tools @@ -28,28 +28,78 @@ log() { fi } -# Generic curl handler function -curl_handler() { - local command="$1" - local hostname="$2" - - response=$($command -w "%{http_code}" -o /dev/null) - curl_exit_code=$? - - if [ "$response" -ge 200 ] && [ "$response" -lt 300 ]; then - return 0 - elif [ $curl_exit_code -ne 0 ]; then - log "ERROR" "$RED" "Connection error during request to $hostname (curl exit code: $curl_exit_code)." - return 1 - elif [ "$response" -ge 400 ]; then - log "ERROR" "$RED" "Server error during request to $hostname (HTTP status code: $response)." - return 2 +# Fetch a URL to a destination file, validating status codes. +# Usage: fetch "" "" "200 404" +fetch() { + local url="$1" + local dest="$2" + local accepted="${3:-200}" + + # If no dest given, just discard body + local out + if [ -n "$dest" ]; then + # Write to ".tmp" files first, then move when success, to ensure we don't write partial files + out="${dest}.tmp" else - log "ERROR" "$RED" "Unexpected response from $hostname (HTTP status code: $response)." - return 3 + out="/dev/null" + fi + + response=$(curl --connect-timeout 5 --max-time 30 -s -w "%{http_code}" -o "$out" "$url") + local curl_exit_code=$? + + if [ $curl_exit_code -ne 0 ]; then + [ -n "$dest" ] && rm -f "$out" + log "ERROR" "$RED" "Connection error during request to $url (curl exit code: $curl_exit_code)." + return 1 + fi + + for code in $accepted; do + if [ "$response" = "$code" ]; then + # Accepted; only persist body for 2xx responses + if [ -n "$dest" ]; then + if [[ "$response" =~ ^2 ]]; then + mv "$out" "$dest" + else + rm -f "$out" + fi + fi + return 0 + fi + done + + # not accepted + [ -n "$dest" ] && rm -f "$out" + log "ERROR" "$RED" "Unexpected response from $url (HTTP $response)." + return 2 +} + + +# POST a file to a URL, validating status codes. +# Usage: post_file "" "" "200" +post_file() { + local url="$1" + local file="$2" + local accepted="${3:-200}" + + response=$(curl --connect-timeout 5 --max-time 300 -s -w "%{http_code}" -o /dev/null -X POST -F "file=@$file" "$url") + local curl_exit_code=$? + + if [ $curl_exit_code -ne 0 ]; then + log "ERROR" "$RED" "Connection error during POST to $url (curl exit code: $curl_exit_code)." + return 1 fi + + for code in $accepted; do + if [ "$response" -eq "$code" ]; then + return 0 + fi + done + + log "ERROR" "$RED" "Unexpected response from $url (HTTP $response)." + return 2 } + # Print help message show_help() { cat << EOF @@ -109,33 +159,27 @@ backup_one() { local address="$2" local port="$3" - log "INFO" "$YELLOW" "Backing up device config/presets: $hostname ($address:$port)" + log "INFO" "$YELLOW" "Backing up device config/presets/ir: $hostname ($address:$port)" mkdir -p "$backup_dir" - local cfg_url="http://$address:$port/cfg.json" - local presets_url="http://$address:$port/presets.json" - local cfg_dest="${backup_dir}/${hostname}.cfg.json" - local presets_dest="${backup_dir}/${hostname}.presets.json" - - # Write to ".tmp" files first, then move when success, to ensure we don't write partial files - local curl_command_cfg="curl -s "$cfg_url" -o "$cfg_dest.tmp"" - local curl_command_presets="curl -s "$presets_url" -o "$presets_dest.tmp"" + local file_prefix="${backup_dir}/${hostname}" - if ! curl_handler "$curl_command_cfg" "$hostname"; then + if ! fetch "http://$address:$port/cfg.json" "${file_prefix}.cfg.json"; then log "ERROR" "$RED" "Failed to backup configuration for $hostname" - rm -f "$cfg_dest.tmp" return 1 fi - if ! curl_handler "$curl_command_presets" "$hostname"; then + if ! fetch "http://$address:$port/presets.json" "${file_prefix}.presets.json"; then log "ERROR" "$RED" "Failed to backup presets for $hostname" - rm -f "$presets_dest.tmp" return 1 - fi + fi + + # ir.json is optional + if ! fetch "http://$address:$port/ir.json" "${file_prefix}.ir.json" "200 404"; then + log "ERROR" "$RED" "Failed to backup ir configs for $hostname" + fi - mv "$cfg_dest.tmp" "$cfg_dest" - mv "$presets_dest.tmp" "$presets_dest" log "INFO" "$GREEN" "Successfully backed up config and presets for $hostname" return 0 } @@ -150,9 +194,8 @@ update_one() { log "INFO" "$YELLOW" "Starting firmware update for device: $hostname ($address:$port)" local url="http://$address:$port/update" - local curl_command="curl -s -X POST -F "file=@$firmware" "$url"" - if ! curl_handler "$curl_command" "$hostname"; then + if ! post_file "$url" "$firmware" "200"; then log "ERROR" "$RED" "Failed to update firmware for $hostname" return 1 fi diff --git a/usermods/audioreactive/audio_reactive.cpp b/usermods/audioreactive/audio_reactive.cpp index 25b8135209..b41517258f 100644 --- a/usermods/audioreactive/audio_reactive.cpp +++ b/usermods/audioreactive/audio_reactive.cpp @@ -1981,7 +1981,7 @@ void AudioReactive::createAudioPalettes(void) { if (palettes) return; DEBUG_PRINTLN(F("Adding audio palettes.")); for (int i=0; i stop the clipping range is inverted -bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const { +bool Segment::isPixelXYClipped(int x, int y) const { if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { const bool invertX = _clipStart > _clipStop; const bool invertY = _clipStartY > _clipStopY; @@ -186,7 +185,7 @@ bool IRAM_ATTR_YN Segment::isPixelXYClipped(int x, int y) const { void IRAM_ATTR_YN Segment::setPixelColorXY(int x, int y, uint32_t col) const { if (!isActive()) return; // not active - if (x >= (int)vWidth() || y >= (int)vHeight() || x < 0 || y < 0) return; // if pixel would fall out of virtual segment just exit + if ((unsigned)x >= vWidth() || (unsigned)y >= vHeight()) return; // if pixel would fall out of virtual segment just exit setPixelColorXYRaw(x, y, col); } @@ -236,7 +235,7 @@ void Segment::setPixelColorXY(float x, float y, uint32_t col, bool aa) const // returns RGBW values of pixel uint32_t IRAM_ATTR_YN Segment::getPixelColorXY(int x, int y) const { if (!isActive()) return 0; // not active - if (x >= (int)vWidth() || y >= (int)vHeight() || x<0 || y<0) return 0; // if pixel would fall out of virtual segment just exit + if ((unsigned)x >= vWidth() || (unsigned)y >= vHeight()) return 0; // if pixel would fall out of virtual segment just exit return getPixelColorXYRaw(x,y); } @@ -246,52 +245,42 @@ void Segment::blur2D(uint8_t blur_x, uint8_t blur_y, bool smear) const { const unsigned cols = vWidth(); const unsigned rows = vHeight(); const auto XY = [&](unsigned x, unsigned y){ return x + y*cols; }; - uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration - uint32_t last; if (blur_x) { const uint8_t keepx = smear ? 255 : 255 - blur_x; const uint8_t seepx = blur_x >> 1; for (unsigned row = 0; row < rows; row++) { // blur rows (x direction) - uint32_t carryover = BLACK; - uint32_t curnew = BLACK; - for (unsigned x = 0; x < cols; x++) { - uint32_t cur = getPixelColorRaw(XY(x, row)); - uint32_t part = color_fade(cur, seepx); - curnew = color_fade(cur, keepx); - if (x > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorRaw(XY(x - 1, row), prev); - } else setPixelColorRaw(XY(x, row), curnew); // first pixel - lastnew = curnew; - last = cur; // save original value for comparison on next iteration + // handle first pixel in row to avoid conditional in loop (faster) + uint32_t cur = getPixelColorRaw(XY(0, row)); + uint32_t carryover = fast_color_scale(cur, seepx); + setPixelColorRaw(XY(0, row), fast_color_scale(cur, keepx)); + for (unsigned x = 1; x < cols; x++) { + cur = getPixelColorRaw(XY(x, row)); + uint32_t part = fast_color_scale(cur, seepx); + cur = fast_color_scale(cur, keepx); + cur = color_add(cur, carryover); + setPixelColorRaw(XY(x - 1, row), color_add(getPixelColorRaw(XY(x-1, row)), part)); // previous pixel + setPixelColorRaw(XY(x, row), cur); // current pixel carryover = part; } - setPixelColorRaw(XY(cols-1, row), curnew); // set last pixel } } if (blur_y) { const uint8_t keepy = smear ? 255 : 255 - blur_y; const uint8_t seepy = blur_y >> 1; for (unsigned col = 0; col < cols; col++) { - uint32_t carryover = BLACK; - uint32_t curnew = BLACK; - for (unsigned y = 0; y < rows; y++) { - uint32_t cur = getPixelColorRaw(XY(col, y)); - uint32_t part = color_fade(cur, seepy); - curnew = color_fade(cur, keepy); - if (y > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorRaw(XY(col, y - 1), prev); - } else setPixelColorRaw(XY(col, y), curnew); // first pixel - lastnew = curnew; - last = cur; //save original value for comparison on next iteration + // handle first pixel in column + uint32_t cur = getPixelColorRaw(XY(col, 0)); + uint32_t carryover = fast_color_scale(cur, seepy); + setPixelColorRaw(XY(col, 0), fast_color_scale(cur, keepy)); + for (unsigned y = 1; y < rows; y++) { + cur = getPixelColorRaw(XY(col, y)); + uint32_t part = fast_color_scale(cur, seepy); + cur = fast_color_scale(cur, keepy); + cur = color_add(cur, carryover); + setPixelColorRaw(XY(col, y - 1), color_add(getPixelColorRaw(XY(col, y-1)), part)); // previous pixel + setPixelColorRaw(XY(col, y), cur); // current pixel carryover = part; } - setPixelColorRaw(XY(col, rows - 1), curnew); } } } diff --git a/wled00/FX_fcn.cpp b/wled00/FX_fcn.cpp old mode 100755 new mode 100644 index 95a347b66d..9ef6463140 --- a/wled00/FX_fcn.cpp +++ b/wled00/FX_fcn.cpp @@ -11,7 +11,6 @@ */ #include "wled.h" #include "FXparticleSystem.h" // TODO: better define the required function (mem service) in FX.h? -#include "palettes.h" /* Custom per-LED mapping has moved! @@ -226,8 +225,12 @@ void Segment::resetIfRequired() { } CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { - if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; - if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; + // there is one randomy generated palette (1) followed by 4 palettes created from segment colors (2-5) + // those are followed by 7 fastled palettes (6-12) and 59 gradient palettes (13-71) + // then come the custom palettes (255,254,...) growing downwards from 255 (255 being 1st custom palette) + // palette 0 is a varying palette depending on effect and may be replaced by segment's color if so + // instructed in color_from_palette() + if (pal > FIXED_PALETTE_COUNT && pal <= 255-customPalettes.size()) pal = 0; // out of bounds palette //default palette. Differs depending on effect if (pal == 0) pal = _default_palette; // _default_palette is set in setMode() switch (pal) { @@ -263,13 +266,13 @@ CRGBPalette16 &Segment::loadPalette(CRGBPalette16 &targetPalette, uint8_t pal) { } break;} default: //progmem palettes - if (pal>245) { + if (pal > 255 - customPalettes.size()) { targetPalette = customPalettes[255-pal]; // we checked bounds above - } else if (pal < 13) { // palette 6 - 12, fastled palettes - targetPalette = *fastledPalettes[pal-6]; + } else if (pal < DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT+1) { // palette 6 - 12, fastled palettes + targetPalette = *fastledPalettes[pal-DYNAMIC_PALETTE_COUNT-1]; } else { byte tcp[72]; - memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-13])), 72); + memcpy_P(tcp, (byte*)pgm_read_dword(&(gGradientPalettes[pal-(DYNAMIC_PALETTE_COUNT+FASTLED_PALETTE_COUNT)-1])), 72); targetPalette.loadDynamicGradientPalette(tcp); } break; @@ -573,8 +576,7 @@ Segment &Segment::setMode(uint8_t fx, bool loadDefaults) { } Segment &Segment::setPalette(uint8_t pal) { - if (pal < 245 && pal > GRADIENT_PALETTE_COUNT+13) pal = 0; // built in palettes - if (pal > 245 && (customPalettes.size() == 0 || 255U-pal > customPalettes.size()-1)) pal = 0; // custom palettes + if (pal <= 255-customPalettes.size() && pal > FIXED_PALETTE_COUNT) pal = 0; // not built in palette or custom palette if (pal != palette) { //DEBUG_PRINTF_P(PSTR("- Starting palette transition: %d\n"), pal); startTransition(strip.getTransition(), blendingStyle != BLEND_STYLE_FADE); // start transition prior to change (no need to copy segment) @@ -680,7 +682,7 @@ uint16_t Segment::maxMappingLength() const { #endif // pixel is clipped if it falls outside clipping range // if clipping start > stop the clipping range is inverted -bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const { +bool Segment::isPixelClipped(int i) const { if (blendingStyle != BLEND_STYLE_FADE && isInTransition() && _clipStart != _clipStop) { bool invert = _clipStart > _clipStop; // ineverted start & stop int start = invert ? _clipStop : _clipStart; @@ -698,7 +700,7 @@ bool IRAM_ATTR_YN Segment::isPixelClipped(int i) const { return false; } -void IRAM_ATTR_YN Segment::setPixelColor(int i, uint32_t col) const +void WLED_O2_ATTR Segment::setPixelColor(int i, uint32_t col) const { if (!isActive() || i < 0) return; // not active or invalid index #ifndef WLED_DISABLE_2D @@ -911,7 +913,7 @@ void Segment::setPixelColor(float i, uint32_t col, bool aa) const } #endif -uint32_t IRAM_ATTR_YN Segment::getPixelColor(int i) const +uint32_t WLED_O2_ATTR Segment::getPixelColor(int i) const { if (!isActive() || i < 0) return 0; // not active or invalid index @@ -1050,7 +1052,7 @@ void Segment::fadeToSecondaryBy(uint8_t fadeBy) const { void Segment::fadeToBlackBy(uint8_t fadeBy) const { if (!isActive() || fadeBy == 0) return; // optimization - no scaling to apply const size_t rlength = rawLength(); // calculate only once - for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, color_fade(getPixelColorRaw(i), 255-fadeBy)); + for (unsigned i = 0; i < rlength; i++) setPixelColorRaw(i, fast_color_scale(getPixelColorRaw(i), 255-fadeBy)); } /* @@ -1070,25 +1072,19 @@ void Segment::blur(uint8_t blur_amount, bool smear) const { uint8_t keep = smear ? 255 : 255 - blur_amount; uint8_t seep = blur_amount >> 1; unsigned vlength = vLength(); - uint32_t carryover = BLACK; - uint32_t lastnew; // not necessary to initialize lastnew and last, as both will be initialized by the first loop iteration - uint32_t last; - uint32_t curnew = BLACK; - for (unsigned i = 0; i < vlength; i++) { - uint32_t cur = getPixelColorRaw(i); - uint32_t part = color_fade(cur, seep); - curnew = color_fade(cur, keep); - if (i > 0) { - if (carryover) curnew = color_add(curnew, carryover); - uint32_t prev = color_add(lastnew, part); - // optimization: only set pixel if color has changed - if (last != prev) setPixelColorRaw(i - 1, prev); - } else setPixelColorRaw(i, curnew); // first pixel - lastnew = curnew; - last = cur; // save original value for comparison on next iteration + // handle first pixel to avoid conditional in loop (faster) + uint32_t cur = getPixelColorRaw(0); + uint32_t carryover = fast_color_scale(cur, seep); + setPixelColorRaw(0, fast_color_scale(cur, keep)); + for (unsigned i = 1; i < vlength; i++) { + cur = getPixelColorRaw(i); + uint32_t part = fast_color_scale(cur, seep); + cur = fast_color_scale(cur, keep); + cur = color_add(cur, carryover); + setPixelColorRaw(i - 1, color_add(getPixelColorRaw(i - 1), part)); // previous pixel + setPixelColorRaw(i, cur); // current pixel carryover = part; } - setPixelColorRaw(vlength - 1, curnew); } /* @@ -1178,17 +1174,42 @@ void WS2812FX::finalizeInit() { digitalCount = 0; #endif + DEBUG_PRINTF_P(PSTR("Heap before buses: %d\n"), getFreeHeapSize()); // create buses/outputs unsigned mem = 0; + unsigned maxI2S = 0; for (const auto &bus : busConfigs) { - mem += bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // includes global buffer - if (mem <= MAX_LED_MEMORY) { - if (BusManager::add(bus) == -1) break; + unsigned memB = bus.memUsage(Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) ? digitalCount++ : 0); // does not include DMA/RMT buffer + mem += memB; + // estimate maximum I2S memory usage (only relevant for digital non-2pin busses) + #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) + #if defined(CONFIG_IDF_TARGET_ESP32) || defined(CONFIG_IDF_TARGET_ESP32S3) + const bool usesI2S = ((useParallelI2S && digitalCount <= 8) || (!useParallelI2S && digitalCount == 1)); + #elif defined(CONFIG_IDF_TARGET_ESP32S2) + const bool usesI2S = (useParallelI2S && digitalCount <= 8); + #else + const bool usesI2S = false; + #endif + if (Bus::isDigital(bus.type) && !Bus::is2Pin(bus.type) && usesI2S) { + #ifdef NPB_CONF_4STEP_CADENCE + constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit) + #else + constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit) + #endif + unsigned i2sCommonSize = stepFactor * bus.count * (3*Bus::hasRGB(bus.type)+Bus::hasWhite(bus.type)+Bus::hasCCT(bus.type)) * (Bus::is16bit(bus.type)+1); + if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; + } + #endif + if (mem + maxI2S <= MAX_LED_MEMORY) { + BusManager::add(bus); + DEBUG_PRINTF_P(PSTR("Bus memory: %uB\n"), memB); } else { errorFlag = ERR_NORAM_PX; // alert UI DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)bus.type, (int)bus.count, digitalCount); + break; } } + DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem + maxI2S, BusManager::memUsage()); busConfigs.clear(); busConfigs.shrink_to_fit(); diff --git a/wled00/FXparticleSystem.cpp b/wled00/FXparticleSystem.cpp index 8b684a5f69..1a1ed08850 100644 --- a/wled00/FXparticleSystem.cpp +++ b/wled00/FXparticleSystem.cpp @@ -17,8 +17,7 @@ // local shared functions (used both in 1D and 2D system) static int32_t calcForce_dv(const int8_t force, uint8_t &counter); static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32_t particleradius, const bool wrap); // returns false if out of bounds by more than particleradius -static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) -static uint32_t fast_color_scale(CRGBW c, const uint8_t scale); // fast scaling function using 32bit variable and pointer. note: keep 'scale' within 0-255 +static uint32_t fast_color_scaleAdd(const uint32_t c1, const uint32_t c2, uint8_t scale = 255); // fast and accurate color adding with scaling (scales c2 before adding) #endif #ifndef WLED_DISABLE_PARTICLESYSTEM2D @@ -625,7 +624,7 @@ void ParticleSystem2D::render() { } // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer -__attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY) { +void WLED_O2_ATTR ParticleSystem2D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW& color, const bool wrapX, const bool wrapY) { uint32_t size = particlesize; if (advPartProps && advPartProps[particleindex].size > 0) // use advanced size properties (0 means use global size including single pixel rendering) size = advPartProps[particleindex].size; @@ -635,7 +634,7 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint uint32_t y = particles[particleindex].y >> PS_P_RADIUS_SHIFT; if (x <= (uint32_t)maxXpixel && y <= (uint32_t)maxYpixel) { uint32_t index = x + (maxYpixel - y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer) - framebuffer[index] = fast_color_add(framebuffer[index], color, brightness); + framebuffer[index] = fast_color_scaleAdd(framebuffer[index], color, brightness); } return; } @@ -687,10 +686,10 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint memset(renderbuffer, 0, sizeof(renderbuffer)); // clear buffer //particle size to pixels: < 64 is 4x4, < 128 is 6x6, < 192 is 8x8, bigger is 10x10 //first, render the pixel to the center of the renderbuffer, then apply 2D blurring - renderbuffer[4 + (4 * 10)] = fast_color_add(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left - renderbuffer[5 + (4 * 10)] = fast_color_add(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]); - renderbuffer[5 + (5 * 10)] = fast_color_add(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]); - renderbuffer[4 + (5 * 10)] = fast_color_add(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]); + renderbuffer[4 + (4 * 10)] = fast_color_scaleAdd(renderbuffer[4 + (4 * 10)], color, pxlbrightness[0]); // order is: bottom left, bottom right, top right, top left + renderbuffer[5 + (4 * 10)] = fast_color_scaleAdd(renderbuffer[5 + (4 * 10)], color, pxlbrightness[1]); + renderbuffer[5 + (5 * 10)] = fast_color_scaleAdd(renderbuffer[5 + (5 * 10)], color, pxlbrightness[2]); + renderbuffer[4 + (5 * 10)] = fast_color_scaleAdd(renderbuffer[4 + (5 * 10)], color, pxlbrightness[3]); uint32_t rendersize = 2; // initialize render size, minimum is 4x4 pixels, it is incremented int he loop below to start with 4 uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) uint32_t maxsize = advPartProps[particleindex].size; @@ -748,7 +747,7 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint continue; } uint32_t idx = xfb + (maxYpixel - yfb) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer) - framebuffer[idx] = fast_color_add(framebuffer[idx], renderbuffer[xrb + yrb * 10]); + framebuffer[idx] = fast_color_scaleAdd(framebuffer[idx], renderbuffer[xrb + yrb * 10]); } } } else { // standard rendering (2x2 pixels) @@ -785,7 +784,7 @@ __attribute__((optimize("O2"))) void ParticleSystem2D::renderParticle(const uint for (uint32_t i = 0; i < 4; i++) { if (pixelvalid[i]) { uint32_t idx = pixco[i].x + (maxYpixel - pixco[i].y) * (maxXpixel + 1); // flip y coordinate (0,0 is bottom left in PS but top left in framebuffer) - framebuffer[idx] = fast_color_add(framebuffer[idx], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left + framebuffer[idx] = fast_color_scaleAdd(framebuffer[idx], color, pxlbrightness[i]); // order is: bottom left, bottom right, top right, top left } } } @@ -857,7 +856,7 @@ void ParticleSystem2D::handleCollisions() { // handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS // takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) -__attribute__((optimize("O2"))) void ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy, const uint32_t collDistSq) { +void WLED_O2_ATTR ParticleSystem2D::collideParticles(PSparticle &particle1, PSparticle &particle2, int32_t dx, int32_t dy, const uint32_t collDistSq) { int32_t distanceSquared = dx * dx + dy * dy; // Calculate relative velocity note: could zero check but that does not improve overall speed but deminish it as that is rarely the case and pushing is still required int32_t relativeVx = (int32_t)particle2.vx - (int32_t)particle1.vx; @@ -1028,9 +1027,8 @@ void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblu for (uint32_t x = xstart; x < xstart + xsize; x++) { seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours if (x > 0) { - colorbuffer[indexXY - 1] = fast_color_add(colorbuffer[indexXY - 1], seeppart); - if (carryover.color32) // note: check adds overhead but is faster on average - colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover); + colorbuffer[indexXY - 1] = fast_color_scaleAdd(colorbuffer[indexXY - 1], seeppart); + colorbuffer[indexXY] = fast_color_scaleAdd(colorbuffer[indexXY], carryover); } carryover = seeppart; indexXY++; // next pixel in x direction @@ -1049,9 +1047,8 @@ void blur2D(uint32_t *colorbuffer, uint32_t xsize, uint32_t ysize, uint32_t xblu for (uint32_t y = ystart; y < ystart + ysize; y++) { seeppart = fast_color_scale(colorbuffer[indexXY], seep); // scale it and seep to neighbours if (y > 0) { - colorbuffer[indexXY - width] = fast_color_add(colorbuffer[indexXY - width], seeppart); - if (carryover.color32) // note: check adds overhead but is faster on average - colorbuffer[indexXY] = fast_color_add(colorbuffer[indexXY], carryover); + colorbuffer[indexXY - width] = fast_color_scaleAdd(colorbuffer[indexXY - width], seeppart); + colorbuffer[indexXY] = fast_color_scaleAdd(colorbuffer[indexXY], carryover); } carryover = seeppart; indexXY += width; // next pixel in y direction @@ -1470,7 +1467,7 @@ void ParticleSystem1D::render() { CRGBW bg_color = SEGCOLOR(1); if (bg_color > 0) { //if not black for (int32_t i = 0; i <= maxXpixel; i++) { - framebuffer[i] = fast_color_add(framebuffer[i], bg_color); + framebuffer[i] = fast_color_scaleAdd(framebuffer[i], bg_color); } } #ifndef WLED_DISABLE_2D @@ -1485,7 +1482,7 @@ void ParticleSystem1D::render() { } // calculate pixel positions and brightness distribution and render the particle to local buffer or global buffer -__attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap) { +void WLED_O2_ATTR ParticleSystem1D::renderParticle(const uint32_t particleindex, const uint8_t brightness, const CRGBW &color, const bool wrap) { uint32_t size = particlesize; if (advPartProps) // use advanced size properties (1D system has no large size global rendering TODO: add large global rendering?) size = advPartProps[particleindex].size; @@ -1493,7 +1490,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint if (size == 0) { //single pixel particle, can be out of bounds as oob checking is made for 2-pixel particles (and updating it uses more code) uint32_t x = particles[particleindex].x >> PS_P_RADIUS_SHIFT_1D; if (x <= (uint32_t)maxXpixel) { //by making x unsigned there is no need to check < 0 as it will overflow - framebuffer[x] = fast_color_add(framebuffer[x], color, brightness); + framebuffer[x] = fast_color_scaleAdd(framebuffer[x], color, brightness); } return; } @@ -1530,8 +1527,8 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint //render particle to a bigger size //particle size to pixels: 2 - 63 is 4 pixels, < 128 is 6pixels, < 192 is 8 pixels, bigger is 10 pixels //first, render the pixel to the center of the renderbuffer, then apply 1D blurring - renderbuffer[4] = fast_color_add(renderbuffer[4], color, pxlbrightness[0]); - renderbuffer[5] = fast_color_add(renderbuffer[5], color, pxlbrightness[1]); + renderbuffer[4] = fast_color_scaleAdd(renderbuffer[4], color, pxlbrightness[0]); + renderbuffer[5] = fast_color_scaleAdd(renderbuffer[5], color, pxlbrightness[1]); uint32_t rendersize = 2; // initialize render size, minimum is 4 pixels, it is incremented int he loop below to start with 4 uint32_t offset = 4; // offset to zero coordinate to write/read data in renderbuffer (actually needs to be 3, is decremented in the loop below) uint32_t blurpasses = size/64 + 1; // number of blur passes depends on size, four passes max @@ -1565,7 +1562,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint #ifdef ESP8266 // no local buffer on ESP8266 SEGMENT.addPixelColor(xfb, renderbuffer[xrb], true); #else - framebuffer[xfb] = fast_color_add(framebuffer[xfb], renderbuffer[xrb]); + framebuffer[xfb] = fast_color_scaleAdd(framebuffer[xfb], renderbuffer[xrb]); #endif } } @@ -1585,7 +1582,7 @@ __attribute__((optimize("O2"))) void ParticleSystem1D::renderParticle(const uint } for (uint32_t i = 0; i < 2; i++) { if (pxlisinframe[i]) { - framebuffer[pixco[i]] = fast_color_add(framebuffer[pixco[i]], color, pxlbrightness[i]); + framebuffer[pixco[i]] = fast_color_scaleAdd(framebuffer[pixco[i]], color, pxlbrightness[i]); } } } @@ -1648,7 +1645,7 @@ void ParticleSystem1D::handleCollisions() { } // handle a collision if close proximity is detected, i.e. dx and/or dy smaller than 2*PS_P_RADIUS // takes two pointers to the particles to collide and the particle hardness (softer means more energy lost in collision, 255 means full hard) -__attribute__((optimize("O2"))) void ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const uint32_t collisiondistance) { +void WLED_O2_ATTR ParticleSystem1D::collideParticles(PSparticle1D &particle1, const PSparticleFlags1D &particle1flags, PSparticle1D &particle2, const PSparticleFlags1D &particle2flags, const int32_t dx, const uint32_t dx_abs, const uint32_t collisiondistance) { int32_t dv = particle2.vx - particle1.vx; int32_t dotProduct = (dx * dv); // is always negative if moving towards each other @@ -1837,9 +1834,8 @@ void blur1D(uint32_t *colorbuffer, uint32_t size, uint32_t blur, uint32_t start) for (uint32_t x = start; x < start + size; x++) { seeppart = fast_color_scale(colorbuffer[x], seep); // scale it and seep to neighbours if (x > 0) { - colorbuffer[x-1] = fast_color_add(colorbuffer[x-1], seeppart); - if (carryover.color32) // note: check adds overhead but is faster on average - colorbuffer[x] = fast_color_add(colorbuffer[x], carryover); // is black on first pass + colorbuffer[x-1] = fast_color_scaleAdd(colorbuffer[x-1], seeppart); + colorbuffer[x] = fast_color_scaleAdd(colorbuffer[x], carryover); // is black on first pass } carryover = seeppart; } @@ -1888,36 +1884,34 @@ static bool checkBoundsAndWrap(int32_t &position, const int32_t max, const int32 return true; // particle is in bounds } -// this is a fast version for CRGBW color adding ignoring white channel (PS does not handle white) including scaling of second color +// this is a fast version for RGB color adding ignoring white channel (PS does not handle white) including scaling of second color // note: function is mainly used to add scaled colors, so checking if one color is black is slower -// note2: returning CRGBW value is slightly slower as the return value gets written to uint32_t framebuffer - __attribute__((optimize("O2"))) static uint32_t fast_color_add(CRGBW c1, const CRGBW c2, const uint8_t scale) { - uint32_t r, g, b; - r = c1.r + ((c2.r * scale) >> 8); - g = c1.g + ((c2.g * scale) >> 8); - b = c1.b + ((c2.b * scale) >> 8); - - // note: this chained comparison is the fastest method for max of 3 values (faster than std:max() or using xor) - uint32_t max = (r > g) ? ((r > b) ? r : b) : ((g > b) ? g : b); - if (max <= 255) { - c1.r = r; // save result to c1 - c1.g = g; - c1.b = b; - } else { - uint32_t newscale = (255U << 16) / max; - c1.r = (r * newscale) >> 16; - c1.g = (g * newscale) >> 16; - c1.b = (b * newscale) >> 16; - } - return c1.color32; -} - -// fast CRGBW color scaling ignoring white channel (PS does not handle white) - __attribute__((optimize("O2"))) static uint32_t fast_color_scale(CRGBW c, const uint8_t scale) { - c.r = ((c.r * scale) >> 8); - c.g = ((c.g * scale) >> 8); - c.b = ((c.b * scale) >> 8); - return c.color32; +static uint32_t fast_color_scaleAdd(const uint32_t c1, const uint32_t c2, const uint8_t scale) { + constexpr uint32_t MASK_RB = 0x00FF00FF; // red and blue mask + constexpr uint32_t MASK_G = 0x0000FF00; // green mask + + uint32_t rb = c2 & MASK_RB; // 0x00RR00BB + uint32_t g = c2 & MASK_G; // 0x0000GG00 + // scale second color + rb = ((rb * scale) >> 8) & MASK_RB; + g = ((g * scale) >> 8) & MASK_G; + // add colors + rb = (c1 & MASK_RB) + rb; + g = ((c1 & MASK_G) + g); + + // check for overflow by looking at the 9th bit of each channel + if ((rb | (g >> 8)) & 0x01000100) { + // find max among the three 16-bit values + g = g >> 8; // shift to get 0x000000GG + uint32_t max_val = (rb >> 16); // red + max_val = ((rb & 0xFFFF) > max_val) ? rb & 0xFFFF : max_val; // blue + max_val = (g > max_val) ? g : max_val; // green + // scale down to avoid saturation + uint32_t scale_factor = (255 << 8) / max_val; + rb = ((rb * scale_factor) >> 8) & MASK_RB; + g = (g * scale_factor) & MASK_G; + } + return rb | g; } #endif // !(defined(WLED_DISABLE_PARTICLESYSTEM2D) && defined(WLED_DISABLE_PARTICLESYSTEM1D)) diff --git a/wled00/bus_manager.cpp b/wled00/bus_manager.cpp index 40014e2c6d..4fa5c40a57 100644 --- a/wled00/bus_manager.cpp +++ b/wled00/bus_manager.cpp @@ -233,6 +233,7 @@ void BusDigital::estimateCurrent() { void BusDigital::applyBriLimit(uint8_t newBri) { // a newBri of 0 means calculate per-bus brightness limit + _NPBbri = 255; // reset, intermediate value is set below, final value is calculated in bus::show() if (newBri == 0) { if (_milliAmpsLimit == 0 || _milliAmpsTotal == 0) return; // ABL not used for this bus newBri = 255; @@ -250,6 +251,7 @@ void BusDigital::applyBriLimit(uint8_t newBri) { } if (newBri < 255) { + _NPBbri = newBri; // store value so it can be updated in show() (must be updated even if ABL is not used) uint8_t cctWW = 0, cctCW = 0; unsigned hwLen = _len; if (_type == TYPE_WS2812_1CH_X3) hwLen = NUM_ICS_WS2812_1CH_3X(_len); // only needs a third of "RGB" LEDs for NeoPixelBus @@ -267,6 +269,7 @@ void BusDigital::applyBriLimit(uint8_t newBri) { void BusDigital::show() { if (!_valid) return; + _NPBbri = (_NPBbri * _bri) / 255; // total applied brightness for use in restoreColorLossy (see applyBriLimit()) PolyBus::show(_busPtr, _iType, _skip); // faster if buffer consistency is not important (no skipped LEDs) } @@ -329,7 +332,7 @@ uint32_t IRAM_ATTR BusDigital::getPixelColor(unsigned pix) const { if (_reversed) pix = _len - pix -1; pix += _skip; const uint8_t co = _colorOrderMap.getPixelColorOrder(pix+_start, _colorOrder); - uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_bri); + uint32_t c = restoreColorLossy(PolyBus::getPixelColor(_busPtr, _iType, (_type==TYPE_WS2812_1CH_X3) ? IC_INDEX_WS2812_1CH_3X(pix) : pix, co),_NPBbri); if (_type == TYPE_WS2812_1CH_X3) { // map to correct IC, each controls 3 LEDs uint8_t r = R(c); uint8_t g = _reversed ? B(c) : G(c); // should G and B be switched if _reversed? @@ -354,7 +357,7 @@ size_t BusDigital::getPins(uint8_t* pinArray) const { } size_t BusDigital::getBusSize() const { - return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); + return sizeof(BusDigital) + (isOk() ? PolyBus::getDataSize(_busPtr, _iType) : 0); // does not include common I2S DMA buffer } void BusDigital::setColorOrder(uint8_t colorOrder) { @@ -812,14 +815,15 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc. virtualDisp = nullptr; if (bc.type == TYPE_HUB75MATRIX_HS) { - mxconfig.mx_width = min((u_int8_t) 64, bc.pins[0]); - mxconfig.mx_height = min((u_int8_t) 64, bc.pins[1]); - if(bc.pins[2] > 1 && bc.pins[3] > 0 && bc.pins[4]) { - virtualDisp = new VirtualMatrixPanel((*display), bc.pins[3], bc.pins[4], mxconfig.mx_width, mxconfig.mx_height, CHAIN_BOTTOM_LEFT_UP); - } + mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]); + mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]); + // Disable chains of panels for now, incomplete UI changes + // if(bc.pins[2] > 1 && bc.pins[3] != 0 && bc.pins[4] != 0 && bc.pins[3] != 255 && bc.pins[4] != 255) { + // virtualDisp = new VirtualMatrixPanel((*display), bc.pins[3], bc.pins[4], mxconfig.mx_width, mxconfig.mx_height, CHAIN_BOTTOM_LEFT_UP); + // } } else if (bc.type == TYPE_HUB75MATRIX_QS) { - mxconfig.mx_width = min((u_int8_t) 64, bc.pins[0]) * 2; - mxconfig.mx_height = min((u_int8_t) 64, bc.pins[1]) / 2; + mxconfig.mx_width = min((uint8_t) 64, bc.pins[0]) * 2; + mxconfig.mx_height = min((uint8_t) 64, bc.pins[1]) / 2; virtualDisp = new VirtualMatrixPanel((*display), 1, 1, bc.pins[0], bc.pins[1]); virtualDisp->setRotation(0); switch(bc.pins[1]) { @@ -849,7 +853,7 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc. } else mxconfig.setPixelColorDepthBits(8); #endif - mxconfig.chain_length = max((u_int8_t) 1, min(bc.pins[2], (u_int8_t) 4)); // prevent bad data preventing boot due to low memory + mxconfig.chain_length = max((uint8_t) 1, min(bc.pins[2], (uint8_t) 4)); // prevent bad data preventing boot due to low memory if(mxconfig.mx_height >= 64 && (mxconfig.chain_length > 1)) { DEBUGBUS_PRINTLN("WARNING, only single panel can be used of 64 pixel boards due to memory"); @@ -996,7 +1000,6 @@ BusHub75Matrix::BusHub75Matrix(const BusConfig &bc) : Bus(bc.type, bc.start, bc. DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA ")); DEBUGBUS_PRINTF("%sstarted, width=%u, %u pixels.\n", _valid? "":"not ", _panelWidth, _len); - if (mxconfig.double_buff == true) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA driver native double-buffering enabled.")); if (_ledBuffer != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS buffer enabled.")); if (_ledsDirty != nullptr) DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled.")); if ((_ledBuffer != nullptr) || (_ledsDirty != nullptr)) { @@ -1021,9 +1024,6 @@ void __attribute__((hot)) BusHub75Matrix::setPixelColor(unsigned pix, uint32_t c if ((c == IS_BLACK) && (getBitFromArray(_ledsDirty, pix) == false)) return; // ignore black if pixel is already black setBitInArray(_ledsDirty, pix, c != IS_BLACK); // dirty = true means "color is not BLACK" - #ifndef NO_CIE1931 - c = unGamma24(c); // to use the driver linear brightness feature, we first need to undo WLED gamma correction - #endif uint8_t r = R(c); uint8_t g = G(c); uint8_t b = B(c); @@ -1069,9 +1069,6 @@ void BusHub75Matrix::show(void) { for (int y=0; yflipDMABuffer(); // Show the back buffer, set current output buffer to the back (i.e. no longer being sent to LED panels) - // while(!previousBufferFree) delay(1); // experimental - Wait before we allow any writing to the buffer. Stop flicker. - display->clearScreen(); // Now clear the back-buffer - setBitArray(_ledsDirty, _len, false); // dislay buffer is blank - reset all dirty bits - } } void BusHub75Matrix::cleanup() { @@ -1136,7 +1126,8 @@ size_t BusConfig::memUsage(unsigned nr) const { if (Bus::isVirtual(type)) { return sizeof(BusNetwork) + (count * Bus::getNumberOfChannels(type)); } else if (Bus::isDigital(type)) { - return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)) /*+ doubleBuffer * (count + skipAmount) * Bus::getNumberOfChannels(type)*/; + // if any of digital buses uses I2S, there is additional common I2S DMA buffer not accounted for here + return sizeof(BusDigital) + PolyBus::memUsage(count + skipAmount, PolyBus::getI(type, pins, nr)); } else if (Bus::isOnOff(type)) { return sizeof(BusOnOff); } else { @@ -1152,23 +1143,23 @@ size_t BusManager::memUsage() { unsigned maxI2S = 0; #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) unsigned digitalCount = 0; - #if defined(CONFIG_IDF_TARGET_ESP32S2) || defined(CONFIG_IDF_TARGET_ESP32S3) - #define MAX_RMT 4 - #else - #define MAX_RMT 8 - #endif #endif for (const auto &bus : busses) { - unsigned busSize = bus->getBusSize(); + size += bus->getBusSize(); #if !defined(CONFIG_IDF_TARGET_ESP32C3) && !defined(ESP8266) - if (bus->isDigital() && !bus->is2Pin()) digitalCount++; - if (PolyBus::isParallelI2S1Output() && digitalCount > MAX_RMT) { - unsigned i2sCommonSize = 3 * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1); - if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; - busSize -= i2sCommonSize; + if (bus->isDigital() && !bus->is2Pin()) { + digitalCount++; + if ((PolyBus::isParallelI2S1Output() && digitalCount <= 8) || (!PolyBus::isParallelI2S1Output() && digitalCount == 1)) { + #ifdef NPB_CONF_4STEP_CADENCE + constexpr unsigned stepFactor = 4; // 4 step cadence (4 bits per pixel bit) + #else + constexpr unsigned stepFactor = 3; // 3 step cadence (3 bits per pixel bit) + #endif + unsigned i2sCommonSize = stepFactor * bus->getLength() * bus->getNumberOfChannels() * (bus->is16bit()+1); + if (i2sCommonSize > maxI2S) maxI2S = i2sCommonSize; + } } #endif - size += busSize; } return size + maxI2S; } diff --git a/wled00/bus_manager.h b/wled00/bus_manager.h index 31df680ae4..95772a443f 100644 --- a/wled00/bus_manager.h +++ b/wled00/bus_manager.h @@ -112,6 +112,7 @@ class Bus { Bus(uint8_t type, uint16_t start, uint8_t aw, uint16_t len = 1, bool reversed = false, bool refresh = false) : _type(type) , _bri(255) + , _NPBbri(255) , _start(start) , _len(std::max(len,(uint16_t)1)) , _reversed(reversed) @@ -165,7 +166,7 @@ class Bus { inline bool containsPixel(uint16_t pix) const { return pix >= _start && pix < _start + _len; } static inline std::vector getLEDTypes() { return {{TYPE_NONE, "", PSTR("None")}}; } // not used. just for reference for derived classes - static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : is2Pin(type) + 1; } // credit @PaoloTK + static constexpr size_t getNumberOfPins(uint8_t type) { return isVirtual(type) ? 4 : isPWM(type) ? numPWMPins(type) : isHub75(type) ? 3 : is2Pin(type) + 1; } // credit @PaoloTK static constexpr size_t getNumberOfChannels(uint8_t type) { return hasWhite(type) + 3*hasRGB(type) + hasCCT(type); } static constexpr bool hasRGB(uint8_t type) { return !((type >= TYPE_WS2812_1CH && type <= TYPE_WS2812_WWA) || type == TYPE_ANALOG_1CH || type == TYPE_ANALOG_2CH || type == TYPE_ONOFF); @@ -210,7 +211,9 @@ class Bus { protected: uint8_t _type; - uint8_t _bri; + uint8_t _bri; // bus brightness + uint8_t _NPBbri; // total brightness applied to colors in NPB buffer (_bri + ABL) + uint8_t _autoWhiteMode; // global Auto White Calculation override uint16_t _start; uint16_t _len; //struct { //using bitfield struct adds abour 250 bytes to binary size @@ -221,8 +224,6 @@ class Bus { bool _hasWhite;// : 1; bool _hasCCT;// : 1; //} __attribute__ ((packed)); - uint8_t _autoWhiteMode; - // global Auto White Calculation override static uint8_t _gAWM; // _cct has the following meanings (see calculateCCT() & BusManager::setSegmentCCT()): // -1 means to extract approximate CCT value in K from RGB (in calcualteCCT()) diff --git a/wled00/bus_wrapper.h b/wled00/bus_wrapper.h index d64ead8d83..b2ff947418 100644 --- a/wled00/bus_wrapper.h +++ b/wled00/bus_wrapper.h @@ -1121,54 +1121,54 @@ class PolyBus { switch (busType) { case I_NONE: break; #ifdef ESP8266 - case I_8266_U0_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_U0_NEO_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_NEO_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_NEO_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_NEO_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_NEO_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_NEO_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_NEO_4: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_NEO_4: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_NEO_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_NEO_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_400_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_400_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_400_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_400_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_400_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_TM1_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_TM1_4: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_TM1_4: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_TM1_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_TM1_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_TM2_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_TM2_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_TM2_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_TM2_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_TM2_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_UCS_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_UCS_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_UCS_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_UCS_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_UCS_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_UCS_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_UCS_4: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_UCS_4: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_UCS_4: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_UCS_4: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_APA106_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_APA106_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_APA106_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_APA106_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_APA106_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_FW6_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_FW6_5: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_FW6_5: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_FW6_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_FW6_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_2805_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_2805_5: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_2805_5: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_2805_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_2805_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_TM1914_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_TM1914_3: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_TM1914_3: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U0_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; - case I_8266_U1_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_TM1914_3: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U0_SM16825_5: size = (static_cast(busPtr))->PixelsSize(); break; + case I_8266_U1_SM16825_5: size = (static_cast(busPtr))->PixelsSize(); break; case I_8266_DM_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*5; break; - case I_8266_BB_SM16825_5: size = (static_cast(busPtr))->PixelsSize()*2; break; + case I_8266_BB_SM16825_5: size = (static_cast(busPtr))->PixelsSize(); break; #endif #ifdef ARDUINO_ARCH_ESP32 // RMT buses (front + back + small system managed RMT) @@ -1220,68 +1220,65 @@ class PolyBus { case I_NONE: size = 0; break; #ifdef ESP8266 // UART methods have front + back buffers + small UART - case I_8266_U0_NEO_4: size = (size + count)*2; break; // 4 channels - case I_8266_U1_NEO_4: size = (size + count)*2; break; // 4 channels - case I_8266_BB_NEO_4: size = (size + count)*2; break; // 4 channels - case I_8266_U0_TM1_4: size = (size + count)*2; break; // 4 channels - case I_8266_U1_TM1_4: size = (size + count)*2; break; // 4 channels - case I_8266_BB_TM1_4: size = (size + count)*2; break; // 4 channels - case I_8266_U0_UCS_3: size *= 4; break; // 16 bit - case I_8266_U1_UCS_3: size *= 4; break; // 16 bit - case I_8266_BB_UCS_3: size *= 4; break; // 16 bit - case I_8266_U0_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_U1_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_BB_UCS_4: size = (size + count)*2*2; break; // 16 bit 4 channels - case I_8266_U0_FW6_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U1_FW6_5: size = (size + 2*count)*2; break; // 5channels - case I_8266_BB_FW6_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U0_2805_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U1_2805_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_BB_2805_5: size = (size + 2*count)*2; break; // 5 channels - case I_8266_U0_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels - case I_8266_U1_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels - case I_8266_BB_SM16825_5: size = (size + 2*count)*2*2; break; // 16 bit 5 channels - // DMA methods have front + DMA buffer = ((1+(3+1)) * channels) - case I_8266_DM_NEO_3: size *= 5; break; - case I_8266_DM_NEO_4: size = (size + count)*5; break; - case I_8266_DM_400_3: size *= 5; break; - case I_8266_DM_TM1_4: size = (size + count)*5; break; - case I_8266_DM_TM2_3: size *= 5; break; - case I_8266_DM_UCS_3: size *= 2*5; break; - case I_8266_DM_UCS_4: size = (size + count)*2*5; break; - case I_8266_DM_APA106_3: size *= 5; break; - case I_8266_DM_FW6_5: size = (size + 2*count)*5; break; - case I_8266_DM_2805_5: size = (size + 2*count)*5; break; - case I_8266_DM_TM1914_3: size *= 5; break; + case I_8266_U0_NEO_4 : // fallthrough + case I_8266_U1_NEO_4 : // fallthrough + case I_8266_BB_NEO_4 : // fallthrough + case I_8266_U0_TM1_4 : // fallthrough + case I_8266_U1_TM1_4 : // fallthrough + case I_8266_BB_TM1_4 : size = (size + count); break; // 4 channels + case I_8266_U0_UCS_3 : // fallthrough + case I_8266_U1_UCS_3 : // fallthrough + case I_8266_BB_UCS_3 : size *= 2; break; // 16 bit + case I_8266_U0_UCS_4 : // fallthrough + case I_8266_U1_UCS_4 : // fallthrough + case I_8266_BB_UCS_4 : size = (size + count)*2; break; // 16 bit 4 channels + case I_8266_U0_FW6_5 : // fallthrough + case I_8266_U1_FW6_5 : // fallthrough + case I_8266_BB_FW6_5 : // fallthrough + case I_8266_U0_2805_5 : // fallthrough + case I_8266_U1_2805_5 : // fallthrough + case I_8266_BB_2805_5 : size = (size + 2*count); break; // 5 channels + case I_8266_U0_SM16825_5: // fallthrough + case I_8266_U1_SM16825_5: // fallthrough + case I_8266_BB_SM16825_5: size = (size + 2*count)*2; break; // 16 bit 5 channels + // DMA methods have front + DMA buffer = ((1+(3+1)) * channels; exact value is a bit of mistery - needs a dig into NPB) + case I_8266_DM_NEO_3 : // fallthrough + case I_8266_DM_400_3 : // fallthrough + case I_8266_DM_TM2_3 : // fallthrough + case I_8266_DM_APA106_3 : // fallthrough + case I_8266_DM_TM1914_3 : size *= 5; break; + case I_8266_DM_NEO_4 : // fallthrough + case I_8266_DM_TM1_4 : size = (size + count)*5; break; + case I_8266_DM_UCS_3 : size *= 2*5; break; + case I_8266_DM_UCS_4 : size = (size + count)*2*5; break; + case I_8266_DM_FW6_5 : // fallthrough + case I_8266_DM_2805_5 : size = (size + 2*count)*5; break; case I_8266_DM_SM16825_5: size = (size + 2*count)*2*5; break; - #endif - #ifdef ARDUINO_ARCH_ESP32 - // RMT buses (1x front and 1x back buffer) - case I_32_RN_NEO_4: size = (size + count)*2; break; - case I_32_RN_TM1_4: size = (size + count)*2; break; - case I_32_RN_UCS_3: size *= 2*2; break; - case I_32_RN_UCS_4: size = (size + count)*2*2; break; - case I_32_RN_FW6_5: size = (size + 2*count)*2; break; - case I_32_RN_2805_5: size = (size + 2*count)*2; break; - case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; - // I2S1 bus or paralell buses (individual 1x front and 1 DMA (3x or 4x pixel count) or common back DMA buffers) + #else + // RMT buses (1x front and 1x back buffer, does not include small RMT buffer) + case I_32_RN_NEO_4 : // fallthrough + case I_32_RN_TM1_4 : size = (size + count)*2; break; // 4 channels + case I_32_RN_UCS_3 : size *= 2*2; break; // 16bit + case I_32_RN_UCS_4 : size = (size + count)*2*2; break; // 16bit, 4 channels + case I_32_RN_FW6_5 : // fallthrough + case I_32_RN_2805_5 : size = (size + 2*count)*2; break; // 5 channels + case I_32_RN_SM16825_5: size = (size + 2*count)*2*2; break; // 16bit, 5 channels + // I2S1 bus or paralell I2S1 buses (1x front, does not include DMA buffer which is front*cadence, a bit(?) more for LCD) #ifndef CONFIG_IDF_TARGET_ESP32C3 - case I_32_I2_NEO_3: size *= 4; break; - case I_32_I2_NEO_4: size = (size + count)*4; break; - case I_32_I2_400_3: size *= 4; break; - case I_32_I2_TM1_4: size = (size + count)*4; break; - case I_32_I2_TM2_3: size *= 4; break; - case I_32_I2_UCS_3: size *= 2*4; break; - case I_32_I2_UCS_4: size = (size + count)*2*4; break; - case I_32_I2_APA106_3: size *= 4; break; - case I_32_I2_FW6_5: size = (size + 2*count)*4; break; - case I_32_I2_2805_5: size = (size + 2*count)*4; break; - case I_32_I2_TM1914_3: size *= 4; break; - case I_32_I2_SM16825_5: size = (size + 2*count)*2*4; break; + case I_32_I2_NEO_3 : // fallthrough + case I_32_I2_400_3 : // fallthrough + case I_32_I2_TM2_3 : // fallthrough + case I_32_I2_APA106_3 : break; // do nothing, I2S uses single buffer + DMA buffer + case I_32_I2_NEO_4 : // fallthrough + case I_32_I2_TM1_4 : size = (size + count); break; // 4 channels + case I_32_I2_UCS_3 : size *= 2; break; // 16 bit + case I_32_I2_UCS_4 : size = (size + count)*2; break; // 16 bit, 4 channels + case I_32_I2_FW6_5 : // fallthrough + case I_32_I2_2805_5 : size = (size + 2*count); break; // 5 channels + case I_32_I2_SM16825_5: size = (size + 2*count)*2; break; // 16 bit, 5 channels #endif + default : size *= 2; break; // everything else uses 2 buffers #endif - // everything else uses 2 buffers - default: size *= 2; break; } return size; } diff --git a/wled00/cfg.cpp b/wled00/cfg.cpp index 6a11b60b2c..840afb51ba 100644 --- a/wled00/cfg.cpp +++ b/wled00/cfg.cpp @@ -256,9 +256,7 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { static_assert(validatePinsAndTypes(defDataTypes, defNumTypes, defNumPins), "The default pin list defined in DATA_PINS does not match the pin requirements for the default buses defined in LED_TYPES"); - unsigned mem = 0; unsigned pinsIndex = 0; - unsigned digitalCount = 0; for (unsigned i = 0; i < WLED_MAX_BUSSES; i++) { uint8_t defPin[OUTPUT_MAX_PINS]; // if we have less types than requested outputs and they do not align, use last known type to set current type @@ -321,16 +319,9 @@ bool deserializeConfig(JsonObject doc, bool fromFS) { unsigned start = 0; // analog always has length 1 if (Bus::isPWM(dataType) || Bus::isOnOff(dataType)) count = 1; - BusConfig defCfg = BusConfig(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); - mem += defCfg.memUsage(Bus::isDigital(dataType) && !Bus::is2Pin(dataType) ? digitalCount++ : 0); - if (mem > MAX_LED_MEMORY) { - DEBUG_PRINTF_P(PSTR("Out of LED memory! Bus %d (%d) #%u not created."), (int)dataType, (int)count, digitalCount); - break; - } - busConfigs.push_back(defCfg); // use push_back for simplification as we needed defCfg to calculate memory usage + busConfigs.emplace_back(dataType, defPin, start, count, DEFAULT_LED_COLOR_ORDER, false, 0, RGBW_MODE_MANUAL_ONLY, 0); doInitBusses = true; // finalization done in beginStrip() } - DEBUG_PRINTF_P(PSTR("LED buffer size: %uB/%uB\n"), mem, BusManager::memUsage()); } if (hw_led["rev"] && BusManager::getNumBusses()) BusManager::getBus(0)->setReversed(true); //set 0.11 global reversed setting for first bus diff --git a/wled00/colors.cpp b/wled00/colors.cpp index bf2b69d73a..612b33108b 100644 --- a/wled00/colors.cpp +++ b/wled00/colors.cpp @@ -8,7 +8,7 @@ * color blend function, based on FastLED blend function * the calculation for each color is: result = (A*(amountOfA) + A + B*(amountOfB) + B) / 256 with amountOfA = 255 - amountOfB */ -uint32_t IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { +uint32_t WLED_O2_ATTR IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) { // min / max blend checking is omitted: calls with 0 or 255 are rare, checking lowers overall performance const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated (poorman's SIMD; https://github.com/wled/WLED/pull/4568#discussion_r1986587221) uint32_t rb1 = color1 & TWO_CHANNEL_MASK; // extract R & B channels from color1 @@ -25,39 +25,37 @@ uint32_t IRAM_ATTR color_blend(uint32_t color1, uint32_t color2, uint8_t blend) * original idea: https://github.com/wled-dev/WLED/pull/2465 by https://github.com/Proto-molecule * speed optimisations by @dedehai */ -uint32_t color_add(uint32_t c1, uint32_t c2, bool preserveCR) +uint32_t WLED_O2_ATTR color_add(uint32_t c1, uint32_t c2, bool preserveCR) //1212558 | 1212598 | 1212576 | 1212530 { if (c1 == BLACK) return c2; if (c2 == BLACK) return c1; const uint32_t TWO_CHANNEL_MASK = 0x00FF00FF; // mask for R and B channels or W and G if negated uint32_t rb = ( c1 & TWO_CHANNEL_MASK) + ( c2 & TWO_CHANNEL_MASK); // mask and add two colors at once uint32_t wg = ((c1>>8) & TWO_CHANNEL_MASK) + ((c2>>8) & TWO_CHANNEL_MASK); - uint32_t r = rb >> 16; // extract single color values - uint32_t b = rb & 0xFFFF; - uint32_t w = wg >> 16; - uint32_t g = wg & 0xFFFF; if (preserveCR) { // preserve color ratios - uint32_t max = std::max(r,g); // check for overflow note - max = std::max(max,b); - max = std::max(max,w); - //unsigned max = r; // check for overflow note - //max = g > max ? g : max; - //max = b > max ? b : max; - //max = w > max ? w : max; - if (max > 255) { + uint32_t overflow = (rb | wg) & 0x01000100; // detect overflow by checking 9th bit + if (overflow) { + uint32_t r = rb >> 16; // extract single color values + uint32_t b = rb & 0xFFFF; + uint32_t w = wg >> 16; + uint32_t g = wg & 0xFFFF; + uint32_t max = std::max(r,g); + max = std::max(max,b); + max = std::max(max,w); const uint32_t scale = (uint32_t(255)<<8) / max; // division of two 8bit (shifted) values does not work -> use bit shifts and multiplaction instead rb = ((rb * scale) >> 8) & TWO_CHANNEL_MASK; wg = (wg * scale) & ~TWO_CHANNEL_MASK; } else wg <<= 8; //shift white and green back to correct position - return rb | wg; } else { - r = r > 255 ? 255 : r; - g = g > 255 ? 255 : g; - b = b > 255 ? 255 : b; - w = w > 255 ? 255 : w; - return RGBW32(r,g,b,w); + // branchless per-channel saturation to 255 (extract 9th bit, subtract 1 if it is set, mask with 0xFF, input is 0xFF+0xFF=0x1EF max) + // example with overflow: input: 0x01EF01EF -> (0x0100100 - 0x00010001) = 0x00FF00FF -> input|0x00FF00FF = 0x00FF00FF (saturate) + // example without overflow: input: 0x007F007F -> (0x00000000 - 0x00000000) = 0x00000000 -> input|0x00000000 = input (no change) + rb |= ((rb & 0x01000100) - ((rb >> 8) & 0x00010001)) & 0x00FF00FF; + wg |= ((wg & 0x01000100) - ((wg >> 8) & 0x00010001)) & 0x00FF00FF; + wg <<= 8; // restore WG position } + return rb | wg; } /* @@ -92,15 +90,15 @@ uint32_t IRAM_ATTR color_fade(uint32_t c1, uint8_t amount, bool video) { note: inputs are 32bit to speed up the function, useful input value ranges are 0-255 */ uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten) { - if (rgb == 0 | hueShift + lighten + brighten == 0) return rgb; // black or no change - CHSV32 hsv; - rgb2hsv(rgb, hsv); //convert to HSV - hsv.h += (hueShift << 8); // shift hue (hue is 16 bits) - hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate - hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness - uint32_t rgb_adjusted; - hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion - return rgb_adjusted; + if (rgb == 0 || hueShift + lighten + brighten == 0) return rgb; // black or no change + CHSV32 hsv; + rgb2hsv(rgb, hsv); //convert to HSV + hsv.h += (hueShift << 8); // shift hue (hue is 16 bits) + hsv.s = max((int32_t)0, (int32_t)hsv.s - (int32_t)lighten); // desaturate + hsv.v = min((uint32_t)255, (uint32_t)hsv.v + brighten); // increase brightness + uint32_t rgb_adjusted; + hsv2rgb(hsv, rgb_adjusted); // convert back to RGB TODO: make this into 16 bit conversion + return rgb_adjusted; } // 1:1 replacement of fastled function optimized for ESP, slightly faster, more accurate and uses less flash (~ -200bytes) @@ -252,7 +250,7 @@ void loadCustomPalettes() { byte tcp[72]; //support gradient palettes with up to 18 entries CRGBPalette16 targetPalette; customPalettes.clear(); // start fresh - for (int index = 0; index<10; index++) { + for (int index = 0; index < WLED_MAX_CUSTOM_PALETTES; index++) { char fileName[32]; sprintf_P(fileName, PSTR("/palette%d.json"), index); @@ -597,13 +595,13 @@ void NeoGammaWLEDMethod::calcGammaTable(float gamma) gammaT_inv[0] = 0; } -uint8_t IRAM_ATTR_YN NeoGammaWLEDMethod::Correct(uint8_t value) +uint8_t NeoGammaWLEDMethod::Correct(uint8_t value) { if (!gammaCorrectCol) return value; return gammaT[value]; } -uint32_t IRAM_ATTR_YN NeoGammaWLEDMethod::inverseGamma32(uint32_t color) +uint32_t NeoGammaWLEDMethod::inverseGamma32(uint32_t color) { if (!gammaCorrectCol) return color; uint8_t w = W(color); diff --git a/wled00/colors.h b/wled00/colors.h index 376959fd65..eb5767de7e 100644 --- a/wled00/colors.h +++ b/wled00/colors.h @@ -117,13 +117,14 @@ class NeoGammaWLEDMethod { [[gnu::hot, gnu::pure]] uint32_t color_blend(uint32_t c1, uint32_t c2 , uint8_t blend); inline uint32_t color_blend16(uint32_t c1, uint32_t c2, uint16_t b) { return color_blend(c1, c2, b >> 8); }; [[gnu::hot, gnu::pure]] uint32_t color_add(uint32_t, uint32_t, bool preserveCR = false); +[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false); [[gnu::hot, gnu::pure]] uint32_t adjust_color(uint32_t rgb, uint32_t hueShift, uint32_t lighten, uint32_t brighten); [[gnu::hot, gnu::pure]] uint32_t ColorFromPaletteWLED(const CRGBPalette16 &pal, unsigned index, uint8_t brightness = (uint8_t)255U, TBlendType blendType = LINEARBLEND); CRGBPalette16 generateHarmonicRandomPalette(const CRGBPalette16 &basepalette); CRGBPalette16 generateRandomPalette(); void loadCustomPalettes(); extern std::vector customPalettes; -inline size_t getPaletteCount() { return 13 + GRADIENT_PALETTE_COUNT + customPalettes.size(); } +inline size_t getPaletteCount() { return FIXED_PALETTE_COUNT + customPalettes.size(); } inline uint32_t colorFromRgbw(byte* rgbw) { return uint32_t((byte(rgbw[3]) << 24) | (byte(rgbw[0]) << 16) | (byte(rgbw[1]) << 8) | (byte(rgbw[2]))); } void hsv2rgb(const CHSV32& hsv, uint32_t& rgb); void colorHStoRGB(uint16_t hue, byte sat, byte* rgb); @@ -139,6 +140,16 @@ uint32_t colorBalanceFromKelvin(uint16_t kelvin, uint32_t rgb); uint16_t approximateKelvinFromRGB(uint32_t rgb); void setRandomColor(byte* rgb); -[[gnu::hot, gnu::pure]] uint32_t color_fade(uint32_t c1, uint8_t amount, bool video = false); - +// fast scaling function for colors, performs color*scale/256 for all four channels, speed over accuracy +// note: inlining uses less code than actual function calls +static inline uint32_t fast_color_scale(const uint32_t c, const uint8_t scale) { + uint32_t rb = (((c & 0x00FF00FF) * scale) >> 8) & 0x00FF00FF; + uint32_t wg = (((c>>8) & 0x00FF00FF) * scale) & ~0x00FF00FF; + return rb | wg; +} + +// palettes +extern const TProgmemRGBPalette16* const fastledPalettes[]; +extern const uint8_t* const gGradientPalettes[]; #endif + diff --git a/wled00/const.h b/wled00/const.h index c95c19b76d..8891dfcaee 100644 --- a/wled00/const.h +++ b/wled00/const.h @@ -6,7 +6,15 @@ * Readability defines and their associated numerical values + compile-time constants */ -#define GRADIENT_PALETTE_COUNT 59 +constexpr size_t FASTLED_PALETTE_COUNT = 7; // = sizeof(fastledPalettes) / sizeof(fastledPalettes[0]); +constexpr size_t GRADIENT_PALETTE_COUNT = 59; // = sizeof(gGradientPalettes) / sizeof(gGradientPalettes[0]); +constexpr size_t DYNAMIC_PALETTE_COUNT = 5; // 1-5 are dynamic palettes (1=random,2=primary,3=primary+secondary,4=primary+secondary+tertiary,5=primary+secondary(+tertiary if not black) +constexpr size_t FIXED_PALETTE_COUNT = DYNAMIC_PALETTE_COUNT + FASTLED_PALETTE_COUNT + GRADIENT_PALETTE_COUNT; // total number of fixed palettes +#ifndef ESP8266 + #define WLED_MAX_CUSTOM_PALETTES (255 - FIXED_PALETTE_COUNT) // allow up to 255 total palettes, user is warned about stability issues when adding more than 10 +#else + #define WLED_MAX_CUSTOM_PALETTES 10 // ESP8266: limit custom palettes to 10 +#endif // You can define custom product info from build flags. // This is useful to allow API consumer to identify what type of WLED version @@ -674,4 +682,6 @@ static_assert(WLED_MAX_BUSSES <= 32, "WLED_MAX_BUSSES exceeds hard limit"); #define IRAM_ATTR_YN IRAM_ATTR #endif +#define WLED_O2_ATTR __attribute__((optimize("O2"))) + #endif diff --git a/wled00/data/cpal/cpal.htm b/wled00/data/cpal/cpal.htm index 8fa715bc8d..2c09029b5f 100644 --- a/wled00/data/cpal/cpal.htm +++ b/wled00/data/cpal/cpal.htm @@ -11,7 +11,8 @@ function gId(e) {return d.getElementById(e);} function cE(e) {return d.createElement(e);} - + +

- - - - - + + + + + - WLED Custom Palette Editor + WLED Palette Editor

-
-
-
+
-
-
-
- Currently in use custom palettes -
+
+
+ +
Custom palettes
-
- Click on the gradient editor to add new color slider, then the colored box below the slider to change its color. - Click the red box below indicator (and confirm) to delete. - Once finished, click the arrow icon to upload into the desired slot. - To edit existing palette, click the pencil icon. -
+
Click gradient to add. Box = color. Red = delete. Arrow = upload. Pencil = edit.
-
-
- Available static palettes -
+
+
Static palettes
- + + + + + + + + + + +