Skip to content

Conversation

@blazoncek
Copy link

RMT, RP2040x4 and 8266AsyncUart methods covered.
However I have also made 8322fce if that would be a better fit.

Resolves #904

@blazoncek
Copy link
Author

After some more testing all malloc() calls should be replaced with calloc().
The other method (ClearTo()) works fine.

- sometimes buffers were swapped before 1st Show()/Update()
@blazoncek blazoncek marked this pull request as draft January 25, 2026 18:30
@robertlipe
Copy link

If there aren't c++ constructors initializing every member, and there obviously isn't or you wouldn't be using malloc(), this is just good taste.

Either this or a move to actual construction is surely necessary for robust performance in real world systems.

If you need help with "money where your mouth is", I'll help wire up a build. I'll provide an interposed malloc that always returns buffers of 0xfd (or something else really annoying) and will help debug it if that's necessary and welcome.

I'm not just running my mouth. This is a good idea and if that's challenged (maybe the buffers are otherwise initialized - great!; maybe there's a belief that PODs have initializers; they don't. I'll help pull out iso specs and/or help test/debug if there isn't overwhelming consensus that this is the Way To Go.

@blazoncek
Copy link
Author

@robertlipe the main issue I've been facing was "random" flashing when using Show(false) which doesn't copy pixel data between 2 buffers (if bus uses two buffers, i.e. RMT and some others).

This "pixel data", although originally RgbColor (or similar) class, is just POD that is supposed to be overwritten by client for each Show(). But when using Show(false) unmodified previous frame's values are displayed if client doesn't modify every pixel.

This allows some freedom and creativity but with uninitialised second buffer and consistently skipping the same pixels in each frame produced unwanted "random" flashing of those unpainted pixels due to one of original buffers not being initialised.

As mentioned there are two ways to initialise both buffers. I'm now convinced that it is better to use higher level clearing (in NeoPixelBus::Begin()) so I've marked this PR as draft and do not intend to pursue it further except on explicit request by @Makuna.

BTW, I am not a developer by trade so may have misinterpreted the calloc() specification but as I read it it has the advantage of taking care of memory alignment. I do not know if this is actual advantage in case of NPB as data is moved byte by byte.

@robertlipe
Copy link

I understand. I didn't analyze every call trace all the way to the top, but this seemed quite reasonable to me. (We've "met" before...) Your symptoms and solutions are spot-on.

I won't say that a memset per alloc/frame is free, even on an ESP32 vs. a VAX. Unless it's absolutely, positively known that someone else (like C++ initializers) is going to DTRT behind the scenes, that memset/bzero/mmap from /dev/zero (clearly not on ESP32) is absolutely worth the water carried in the name of predictability and stability. Then again, if you're doing an alloc per frame, you've already lost some battles.

I'm just saying that I pencil-whipped this, and it seems really sane, though I don't know every path through this code or if it could be assured that it's never doing a bzero twice. I'm saying that it's a good idea, and if the lead devs need help proving that, I'm willing to invest some brain and machine cycles to make it so, though it might be on a flaky schedule. That said, this code looks to be C++, so it MIGHT be the case that _dataEditing/_sizeData (which might be a std::span in modern C++) should instead just be a smarter (non-POD, non size_t/void* tuple) type that initializes itself to be a smarter/smaller type, potentially saving the bus terrorization of a memset/0.

I can't test all the cases that you've touched, but I'm willing to wire up a similarly failing case (assuming I can match the hardware), try to profile/study the failing case, and help validate the maxmially awesome case because I think you're onto a good lead here.

If the project/SOC has the bandwidth to just accept the change to calloc without blinking an eye, it's hard to argue that's not the right solution.

IMO, you're onto something here that's an actual problem. I'm just hoping to help you get the attention of the right people and, in case you can't, offer some time to help pan for gold in the profiles.

Blink on!

@blazoncek
Copy link
Author

To add some context (that I gathered during studying code): memory allocations only happen upon object construction (or during Begin() call as I propose in #906 ) and not later in the flow.

@robertlipe
Copy link

robertlipe commented Jan 31, 2026 via email

@blazoncek
Copy link
Author

Correctness trumps saving a microsecond that usually works.

I know some people that will object to that. Sadly now in charge of a well known OSS. 🤷‍♂️

@robertlipe
Copy link

robertlipe commented Jan 31, 2026 via email

@Makuna
Copy link
Owner

Makuna commented Feb 3, 2026

Not specifically opposed to this, but it doesn't really solve the problem in all cases.

NOW, consider if your LEDs use 255 for black (yes, there is one LED strip model that inverts the values for some reason). There are some that have per pixel config that must be a specific bit pattern. Now you just initialized to all ON rather than all off or a corrupt set per pixel config data. This is what ClearTo() was created for. You can't assume the color values or format. And you can't assume this per pixel config data didn't change by calling SetSettings (either the feature or the method, both can expose it).

Now the case of the per pixel config data, that is set latter after construction so calling ClearTo() in the constructor won't fix the per pixel config corruption.

This "old" model was either you write every pixel every frame and call Show(false), or you call Show(true).

The "new" model is the Shader branch, where there is a single output buffer that is composed when you call Show(), so these per pixel config get recreated every time as the front buffer is translated into the hardware sending buffer all at once, rather than on the SetPixelColor().

@blazoncek
Copy link
Author

doesn't really solve the problem

Well, it sort of does. LEDs don't blink. However, not everyone will like "on" LEDs in case 0 is not black.

This is what ClearTo() was created for.

Figured out as much and hence reverted this PR to draft (to serve as a discussion).

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.

ESP32's RMT method does not clear edit and transmit buffers

3 participants