Skip to content

Layer

Source: Layer.h

Layer

class Layer
src/light/layers/Layer.h:35

Inherits: MoonModule

A [Layer]MoonModule (role ModuleRole::Layer, child of the Layers container) owns a buffer, a mapping LUT, an ordered effect list, and an ordered modifier list, and references the shared Layouts that describes the physical topology.

Ownership: a Buffer (logical light data, sized to the logical box); a MappingLUT (logical lights to physical positions); effects (write lights into the buffer, dynamic heap-grown list, no fixed max); modifiers (transform the LUT or light values, same dynamic list).

Composition: two controls, blendMode and opacity, govern how this [Layer] composites onto the layers below it. They are inert on the [Layer] — it never reads them; a [Layer] can't know its position in the stack or what's beneath it. The Drivers container reads each enabled [Layer]'s two values plus the container child order and does the compositing (bottom layer overwrites, each layer above blends per its mode and opacity). The value lives here so it travels with the [Layer] through add / delete / reorder — no separate sync-prone blend list on Drivers. The blend math itself lives in BlendMap.

Buffer persistence: the buffer persists frame-to-frame — the [Layer] does NOT clear it. This is the FastLED / WLED / MoonLight convention: the buffer holds the previous frame so an effect can fade it for trails (fadeToBlackBy) or read prior pixels (a scroll, Game-of-Life). Each effect owns its background. rebuildLUT clears once on the cold path so a freshly added effect starts black.

rebuildLUT (cold path): called when a layout or modifier control changes. Reads physical dimensions from Layouts, folds the box through each enabled static modifier to compute the logical box, allocates the buffer and LUT, and for the common case (no modifier, dense grid in natural order) skips the table entirely with an identity mapping. The general path folds each physical light through the static chain to its logical cell via a textbook counting-sort CSR build.

render (hot path): runs each enabled effect in order (all write the same buffer), calls extrude after each effect to duplicate its written slice across the axes it doesn't iterate, then ticks each enabled modifier. A live (animated) modifier triggers the per-frame applyLivePass backward gather; a beat-driven one asks for a single coalesced rebuild.

extrude: lets a low-dimensional effect work on a higher-dimensional layer without per-effect changes — a D2 effect on a 3D layer has its z=0 slice copied across z, a D1 effect its x=0 column copied across x, then that row across the rest. Cost is zero for D3 effects (the default, an early return) and zero when the layer's unused axes are size 1 (a D2 effect on a 2D layer). Real memcpy work only happens when the layer has more dimensions than the effect writes.

Status: the status slot shows the LOGICAL box the effects render into (<w>×<h>×<d>), which can differ from the physical box shown on Layouts (a Mirror-XY modifier folds a 128×128 physical layout into a 64×64 logical box). The same slot carries memory-degradation warnings when a build can't fit (modifier mapping skipped, buffer reduced, buffer allocation failed, all — not enough memory), and a warning wins over the neutral box line.

Prior art: MoonLight's VirtualLayeroneToOneMapping fast-path flag, virtualChannels per-layer buffer, effectDimension, a nodes vector for effects/modifiers, and forEachLight per-logical-light iteration that asks the modifier for physical destinations (https://github.com/ewowi/MoonLight/blob/main/src/MoonLight/Layers/VirtualLayer.h).

Layer card

Public Attributes

uint8_t blendMode = 0

uint8_t opacity = 255

Public Methods

virtual inline ModuleRole role() const override : Role for type identification (no RTTI needed).

virtual inline constchar * acceptsChildRoles() const override : Comma-separated role names this module accepts as user-added children ("effect,modifier").

virtual inline void defineControls() override : defineControls MUST be idempotent and pure: only controls_.clear() + controls_.addX().

inline BlendOp blendOp() const : How this [Layer] composites when stacked above another (read by Drivers).

inline void setLayouts(Layouts * lg)

inline Layouts * layouts() const

inline void setChannelsPerLight(uint8_t cpl)

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 void tick() override

inline void ensureLiveScratch()

inline void applyLivePass()

inline void extrude(Dim effectDim) : Copy the effect's written slice to fill the unused axes.

inline Buffer & buffer()

inline constBuffer & buffer() const

inline constMappingLUT & lut() const

inline lengthType width() const

inline lengthType height() const

inline lengthType depth() const

inline uint8_t channelsPerLight() const

inline uint32_t elapsed() const

inline void fadeToBlackBy(uint8_t amt)

inline nrOfLightsType physicalLightCount() const

inline lengthType physicalWidth() const

inline lengthType physicalHeight() const

inline lengthType physicalDepth() const

inline bool lutSkipped() const

inline void rebuildLUT() : Cold path, called from prepare after physical dimensions are known.

inline bool isNaturalOrder() const

inline bool buildFoldedLUT(const Coord3D & logical, nrOfLightsType logicalCount, nrOfLightsType driverCount)

inline void clampLogical(Coord3D & logical) const

Public Static Attributes

constexpr nrOfLightsType kNoDriver = <nrOfLightsType>(-1)

Public Static Methods

static inline nrOfLightsType cellCount(const Coord3D & box)