Drivers
Source:
Drivers.h
Drivers¶
src/light/drivers/Drivers.h:73Inherits:
MoonModule
Top-level container for one or more drivers — the consumer side of the pipeline.
Owns the shared output buffer (when memory allows) and performs blend+map from every layer's buffer into it each frame.
Naming convention. Capital Drivers is the container class; lowercase "driver"/"drivers" is the English singular/plural for individual DriverBase children. Capitalisation disambiguates "the Drivers container" from "two drivers
running" (same rule for Layouts/layout and Layers/layer).
Shared output buffer. Necessary because blend+map writes to arbitrary physical positions via LUT — the output is not filled sequentially, so a driver cannot read chunk-by-chunk until the full buffer is populated. It uses the same Buffer type a Layer does, sized by the Layouts container. Exception: when exactly one layer is enabled AND its mapping is 1:1 unshuffled (no LUT — grid layout, no serpentine), Drivers skips its own buffer and lets drivers read directly from the layer's buffer (the zero-copy fast path, at the cost of parallelism).
Multi-layer composition. When two or more layers are enabled, Drivers composites them into the shared output buffer each frame in Layers container order (bottom→top, via forEachEnabledLayer). The bottom layer clears and overwrites the buffer; each layer above blends onto the accumulated frame per its own blendMode and opacity (the inert per-Layer controls). Drivers owns the orchestration because only it sees the stack order and the output buffer; the layers carry only the parameters. The per-pixel blend math lives in blendMap (integer-only, per the hot-path rule). A full-opacity overwrite/additive layer pays no alpha arithmetic, so per-frame cost scales with the enabled-layer count. With a single enabled layer there is no composite: the fast path applies (no-LUT → zero-copy; with a LUT → one blend+map pass into the output buffer).
Output correction. Each physical driver owns its own Correction (channel-order table, output channel count, white synthesis, and a brightness LUT), applied per-light as it reads the source buffer; Preview ignores it (shows the raw logical buffer). Drivers owns only the GLOBAL brightness (plus on and palette); on a power/brightness change it calls each child's rebuildCorrection(globalBrightness), which bakes global × that driver's local brightness into its LUT. So outputs on one board can differ — a GRB strip and an RGBW panel, each at its own brightness — while every driver still sees the same composited RGB source. Palette model + names follow FastLED's, credited as prior art; implementation in src/light/Palette.h.
Per-driver source window (start / count). A window-aware output driver reads the shared source buffer and outputs a contiguous slice of it — its window — making light distribution explicit and order-independent: each driver names its own slice, so reordering drivers does not change which lights each outputs (only tick order). The motivating case: an onboard status LED with window [0, 1) and a main strip with window [1, …) as two driver instances on the same buffer, neither stealing the other's lights. DriverBase::addWindowControls() opts a driver in (see there); a driver that outputs the whole buffer (such as PreviewDriver) simply doesn't call it.
Prior art: MoonLight's PhysicalLayer — owns channelsD (display buffer), compositeLayers() maps virtualChannels → channelsD, parallelism via a semaphore (driver signals completion, compositor writes) (https://github.com/ewowi/MoonLight/blob/main/src/MoonLight/Layers/PhysicalLayer.h).

Public Attributes¶
uint8_t brightness = 20
: Global brightness (0–255).
bool on =
: Master power.
bool multicore =
: Multicore render↔encode split (Step 2a): run the drivers' encode+transmit on the second core while the render loop draws the next frame on core 0, so a frame costs max(render, encode) instead of render + encode.
uint8_t palette = 0
: The global active colour palette (index into mm::palettes::kBuiltins; Rainbow, Party, Lava, Ocean, …).
Public Methods¶
virtual inline constchar * acceptsChildRoles() const override
: Comma-separated role names this module accepts as user-added children ("effect,modifier").
virtual inline void release() override
inline ~Drivers() override
: Stop the core-1 task on destruction too, not only on release().
inline void setLayers(Layers * layers)
inline void setLayer(Layer * layer)
inline uint8_t effectiveBrightness() const
virtual inline void defineControls() override
: defineControls MUST be idempotent and pure: only controls_.clear() + controls_.addX().
virtual inline void onControlChanged(constchar *) override
: Cheap per-control reaction, tier 1 of the three-tier control-change split (mirrors MoonLight's onUpdate / requestMappings / onSizeChanged; see architecture.md § Rebuild propagation).
virtual inline bool affectsPrepare(constchar * name) const override
: multicore is the one Drivers control that is STRUCTURAL: it decides whether the cross-core handoff buffer is allocated and the core-1 encode task runs, both of which live in [prepare()].
virtual inline void tick1s() override
: Refresh the read-only renderWait KPI once a second (off the hot path, same tier as the driver's frameTime).
inline void rebuildAllCorrections()
: Re-resolve every driver's correction (preset roles + brightness LUT) into its flat Correction, WITHOUT re-preparing the tree.
virtual inline void setup() override
: Default lifecycle propagates to children.
virtual inline void prepare() override
: Tier-3 of the control-change split (see onControlChanged above): the module (re)allocates / recomputes whatever derived state it owns — an effect's heap, a Layer's mapping LUT, the Drivers output buffer.
virtual inline bool firstOutputRgb(uint8_t) const override
: Read this module's first output light as RGB into out[3], returning true if it has one.
virtual inline void tick() override
inline void tickNonDriverChildren()
virtual inline void quiesce() override
: Stop any async worker this module owns that could be reading its child array or its children's state, and return only once that worker is idle.
inline bool renderSplitActive() const
: True while the render↔encode split is engaged (multicore on, a driver exists, AND outputBuffer_ allocated AND the core-1 task is live).
inline uint32_t renderWaitPeakUs() const
: The WORST core-0 wait at the frame boundary in the current 1 s window (µs) — time given up waiting for core 1 to finish the output stage.
inline bool quiesceEncodeForTest()
: Test-only: the frame-boundary wait in isolation (false = it timed out and disengaged the split).
Public Static Methods¶
static inline constLightSummary * latestSummary()
: The live light-pipeline summary (light count, channels), for the domain-neutral core consumers (WLED /json shim, MQTT).