Skip to content

Use global Buffer for HUB75#5744

Open
DedeHai wants to merge 3 commits into
wled:mainfrom
DedeHai:HUB75_nopixelbuffer
Open

Use global Buffer for HUB75#5744
DedeHai wants to merge 3 commits into
wled:mainfrom
DedeHai:HUB75_nopixelbuffer

Conversation

@DedeHai

@DedeHai DedeHai commented Jul 18, 2026

Copy link
Copy Markdown
Collaborator

Saves on RAM and is faster.
Dirty buffer is kept as it is faster if there are black pixels and not much slower if there are not.
Also changed PSRAM allocation rule to use PSRAM for large buffers which allows larger setups to run. Tested this part on S3 only using 128x128. 64x64 still uses DRAM mostly. Did not check what is possible on ESP32 with PSRAM.
Also fixed a bug perventing to actually use MAX_LEDS (off by 1)

Summary by CodeRabbit

  • Bug Fixes

    • Improved segment data handling to prevent stale values when memory is reused.
    • HUB75 matrix displays now update pixels immediately and more reliably.
    • Improved color-state reporting for HUB75 matrix pixels.
    • Added safer handling for large LED configurations on supported hardware.
  • Performance

    • Improved memory allocation behavior for large buffers, helping maintain stability when PSRAM is available.
  • Documentation

    • Clarified supported LED limits for ESP32-S3 devices.

@coderabbitai

coderabbitai Bot commented Jul 18, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Walkthrough

Changes

The PR clears reused segment data, simplifies segment memory accounting, changes HUB75 matrix rendering to direct DMA-backed writes without _ledBuffer, permits the maximum LED count, and expands PSRAM selection for very large allocations.

Memory and rendering changes

Layer / File(s) Summary
Segment data buffer lifecycle
wled00/FX_fcn.cpp
Reused segment buffers are zeroed for the requested length, and deallocation directly subtracts _dataLen from usage accounting.
HUB75 direct pixel rendering
wled00/bus_manager.cpp, wled00/const.h
The HUB75 path removes _ledBuffer, writes RGB888 directly, derives pixel reads from dirty state, makes show() a no-op, frees only _ledsDirty, and allows _len == MAX_LEDS.
Large buffer allocation policy
wled00/util.cpp
PSRAM allocation is selected for buffers larger than twice PSRAM_THRESHOLD as well as under the existing low-DRAM condition.

Estimated code review effort: 3 (Moderate) | ~20 minutes

Possibly related PRs

  • wled/WLED#4783: Changes the same segment data allocation and accounting logic.
  • wled/WLED#4950: Modifies the HUB75 pixel writing and display flow.
  • wled/WLED#5026: Modifies the HUB75 DMA rendering path and _ledBuffer handling.

Suggested labels: hardware, optimization, enhancement

Suggested reviewers: netmindz, softhack007, willmmiles

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly matches the main change: HUB75 now uses the global Buffer instead of a local pixel buffer.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (2)
wled00/bus_manager.cpp (2)

1097-1104: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove redundant null check.

Since this block is already guarded by if (_ledsDirty != nullptr), the ternary operator checking _ledsDirty again is redundant and can be simplified.

♻️ Proposed refactor
   if (_ledsDirty != nullptr) {
     DEBUGBUS_PRINTLN(F("MatrixPanel_I2S_DMA LEDS dirty bit optimization enabled."));
     DEBUGBUS_PRINT(F("MatrixPanel_I2S_DMA LEDS dirty buffer uses "));
-    DEBUGBUS_PRINT((_ledsDirty? getBitArrayBytes(_len) :0));
+    DEBUGBUS_PRINT(getBitArrayBytes(_len));
     DEBUGBUS_PRINTLN(F(" bytes."));
   }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/bus_manager.cpp` around lines 1097 - 1104, In the _ledsDirty
initialization/logging block, remove the redundant _ledsDirty ternary inside the
DEBUGBUS_PRINT call and directly pass getBitArrayBytes(_len), preserving the
existing outer null guard and logging behavior.

1115-1124: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consolidate pixel coordinate calculations and use fast types.

To remove code duplication and adhere to the hot-path optimization guidelines, you can extract the x and y calculations outside the conditional and use uint_fast16_t instead of int. As per coding guidelines, use uint_fast16_t in hot-path code like pixel set operations.

♻️ Proposed refactor
-  if (virtualDisp != nullptr) {
-    int x = pix % _panelWidth; // TODO: check if using & and shift would be faster here, it limits to power-of-2 widths though
-    int y = pix / _panelWidth;
-    virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
-  } else {
-    int x = pix % _panelWidth;
-    int y = pix / _panelWidth;
-    display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
-  }
+  uint_fast16_t x = pix % _panelWidth; // TODO: check if using & and shift would be faster here, it limits to power-of-2 widths though
+  uint_fast16_t y = pix / _panelWidth;
+
+  if (virtualDisp != nullptr) {
+    virtualDisp->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
+  } else {
+    display->drawPixelRGB888(int16_t(x), int16_t(y), r, g, b);
+  }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@wled00/bus_manager.cpp` around lines 1115 - 1124, In the pixel-setting
routine containing the virtualDisp/display branch, compute the pixel coordinates
once before the conditional and reuse them in both drawPixelRGB888 calls.
Declare the coordinates as uint_fast16_t instead of int, while preserving the
existing modulo/division calculations and rendering behavior.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@wled00/bus_manager.cpp`:
- Around line 1097-1104: In the _ledsDirty initialization/logging block, remove
the redundant _ledsDirty ternary inside the DEBUGBUS_PRINT call and directly
pass getBitArrayBytes(_len), preserving the existing outer null guard and
logging behavior.
- Around line 1115-1124: In the pixel-setting routine containing the
virtualDisp/display branch, compute the pixel coordinates once before the
conditional and reuse them in both drawPixelRGB888 calls. Declare the
coordinates as uint_fast16_t instead of int, while preserving the existing
modulo/division calculations and rendering behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 84cb121d-e06f-4a21-846b-ded9648d8baa

📥 Commits

Reviewing files that changed from the base of the PR and between a962116 and 9c157b9.

📒 Files selected for processing (4)
  • wled00/FX_fcn.cpp
  • wled00/bus_manager.cpp
  • wled00/const.h
  • wled00/util.cpp

@DedeHai

DedeHai commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

@softhack007 is there a reson we do not use the NO_FAST_FUNCTIONS buildflag? see https://github.com/tidbyt/ESP32-HUB75-MatrixPanel-I2S-DMA/blob/master/doc/BuildOptions.md

@softhack007

Copy link
Copy Markdown
Member

@softhack007 is there a reson we do not use the NO_FAST_FUNCTIONS buildflag? see https://github.com/tidbyt/ESP32-HUB75-MatrixPanel-I2S-DMA/blob/master/doc/BuildOptions.md

@DedeHai I think we can enabled that, WLED-MM builds already use the NO_FAST_FUNCTIONS buildflag.

@softhack007

Copy link
Copy Markdown
Member

@DedeHai I'll take a closer look in the next days.

Question: did you test with a larger panel, like 128x64 or 64x64?

One reason for the HUB75 driver-level buffer was to prevent redraw flickering - if the show() loop ist faster than the screen draw "beam", we can be sure that tearing/flickering will only happen rarely. Maybe it's only visible with effects that have lots of motion and color changes.

The other reason - and we surely don't need that any more - was to support getPixelColor on the driver level.

@DedeHai

DedeHai commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

I did test on 64x64 and saw no tearing, but I also did not push it. Wheter the DMA driver is fed from global buffer or a local buffer should not make any difference should it? at the point where the (old) local buffer was pushed, the contents of that buffer and global buffer is identical and not changing. Or what am I missing that gets "out of sync"?

@softhack007

softhack007 commented Jul 19, 2026

Copy link
Copy Markdown
Member

Wheter the DMA driver is fed from global buffer or a local buffer should not make any difference should it?

True, that's not the difference. My concern was more on how fast can we update the DMA driver data? A local loop (in hub75.show()) that operates only on the local buffer is perfect for CPU caches, that's why this should be the fastest possible option. With wled v16 we already push all pixels down to the driver in one loop, so the difference is becoming very small - the remaining overhead may just be some function calls and the "find the right bus" loop in busmanager that runs on each pixel. Maybe the impact is not that severe any more. It was a huge problem in V0.14.x.

@DedeHai

DedeHai commented Jul 19, 2026

Copy link
Copy Markdown
Collaborator Author

valid point, I see the difference now. Could maybe grant show() access to the _pixels[] buffer (if it does not already have it) and push pixels from there. That would break ledmaps though.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants