import { EffectSchemaValue, MaterialEffect, SchemaToNodeType } from "./MaterialEffect.js"; import { InstanceAttributeConfig } from "../pipeline/types.js"; import { Texture } from "three"; import { MeshBasicNodeMaterial } from "three/webgpu"; import Node from "three/src/nodes/core/Node.js"; //#region src/materials/EffectMaterial.d.ts /** * Context passed to colorTransform callbacks. */ interface ColorTransformContext { /** Base sampled + tinted color (vec4) */ color: Node<'vec4'>; /** UV after flip + atlas remap */ atlasUV: Node<'vec2'>; /** World position XY (works with instancing via positionWorld) */ worldPosition: Node<'vec2'>; } /** * A function that transforms the base sprite color. * Receives the base color and context, returns modified color (vec4). */ type ColorTransformFn = (ctx: ColorTransformContext) => Node<'vec4'>; /** * Compute the buffer tier for a given float count. * Tiers: 0, 4, 8, 16, and multiples of 4 beyond 16. */ declare function computeTier(neededFloats: number): number; /** * Get a TSL component accessor from a packed vec4 buffer array. * Maps an absolute float offset to the correct bufNode[n].xyzw component. */ declare function getPackedComponent(bufNodes: Node<'vec4'>[], absoluteOffset: number): Node<'float'>; /** Options for EffectMaterial constructor. */ interface EffectMaterialOptions { /** * Effect buffer tier size in floats. * Buffers are allocated in tiers: 0, 4, 8, 16, then multiples of 4 up * to the 24-float cap ({@link EffectMaterial.MAX_EFFECT_FLOATS}). * Default is 8 (2 vec4 buffers), covering most effect combinations. * Set to 0 for fully effect-free materials (no effect buffer overhead). */ effectTier?: number; } /** * Base material class with composable packed-buffer effect system. * * Extends MeshBasicNodeMaterial and adds: * - Packed vec4 effect buffers with bitmask enable flags * - Effect registration with automatic slot assignment * - Shader rebuilding with effect chain composition * * Subclasses override `_buildBaseColor()` to provide their own base color/UV * (e.g., Sprite2DMaterial provides sprite-specific UV flip, atlas, tint). * * This base class can be extended for non-sprite materials that also need effects. */ declare class EffectMaterial extends MeshBasicNodeMaterial { /** * Instance attribute schema for SpriteBatch to read. * Contains effectBuf0, effectBuf1, ... for packed effect data. * @internal */ _instanceAttributes: Map; /** * Registered effect classes on this material. * @internal */ _effects: (typeof MaterialEffect)[]; /** * Stored constants per effect (keyed by effect name). * Used by _rebuildColorNode to pass constants to channelNode and _node. * @internal */ _effectConstants: Map>; /** * Maps `effectName_fieldName` to its packed buffer offset and size. * Offsets are absolute within `effectBuf*` (pure effect data — system * flags and enable bits moved to `instanceSystem` in the interleaved * core, so effect buffers start at offset 0). * @internal */ _effectSlots: Map; /** * Maximum total effect-data floats allowed across all registered * effects on this material. WebGPU allows 8 vertex-buffer bindings * per pipeline; SpriteBatch uses 2 fixed bindings (instanceMatrix + * interleaved core — the synth-quad geometry's `position`/`uv` * attributes exist for user TSL but cost a binding only when a * material's nodes actually read them), leaving 6 for * `effectBuf0..5` × 4 floats = 24 floats. Exceeding this would force * a 7th effectBuf binding which WebGPU rejects at pipeline creation * with a cryptic "vertex buffer count exceeds maximum" error. * `registerEffect` throws clearly when the cap would be exceeded. * * A material whose custom TSL nodes call `uv()`/`positionGeometry()` * consumes one or two additional vertex-buffer bindings beyond the 2 * above, reducing headroom below 24 floats for that material. */ static readonly MAX_EFFECT_FLOATS = 24; /** * Maps effect name to its bit position in the enable flags bitmask. * @internal */ _effectBitIndex: Map; /** * Total floats needed across all registered effects (1 flags + data floats). * 0 when no effects are registered. * @internal */ _effectTotalFloats: number; /** * Current buffer tier (0, 4, 8, 16, ...). * Determines the actual buffer allocation size. * @internal */ _effectTier: number; /** * Configured default tier (constructor option). * When effects are registered, the tier is at least this value. * @internal */ _defaultEffectTier: number; /** * Version counter for effect schema changes (tier upgrades). * Incremented when the tier changes, used by SpriteGroup to detect * when batches need rebuilding. * @internal */ _effectSchemaVersion: number; /** * Return the maximum number of per-instance effect floats this * material type supports. See {@link MAX_EFFECT_FLOATS}. */ static getMaxEffectFloats(): number; /** * Per-instance effect-float cap for THIS material. Strategy-dependent * in subclasses: tight-mesh geometry spends bindings on position/uv, * shrinking the effect budget (see Sprite2DMaterial). */ get maxEffectFloats(): number; /** * Current per-instance effect-float usage (sum across all registered * effects). Complements {@link getMaxEffectFloats}. */ getUsedEffectFloats(): number; /** * Color transform function (e.g., lighting). * @internal */ protected _colorTransform: ColorTransformFn | null; /** * Set of per-fragment channel names required by the active colorTransform. * @internal */ protected _requiredChannels: ReadonlySet; constructor(options?: EffectMaterialOptions); /** * Get the color transform function. */ get colorTransform(): ColorTransformFn | null; /** * Set the color transform function. * Triggers shader rebuild. */ set colorTransform(value: ColorTransformFn | null); /** * Get the required channels set. */ get requiredChannels(): ReadonlySet; /** * Set the required channels. * Triggers shader rebuild when channels change. */ set requiredChannels(value: ReadonlySet); /** * Effective per-instance float cap for a PROSPECTIVE effect total, * queried before `registerEffect` mutates any state. A subclass whose * cap depends on a geometry strategy it can demote (Sprite2DMaterial's * tight-mesh → synth-quad, 16 → 24) reports the post-demotion cap here, * so an effect that would force a demotion is measured against the * ceiling it will actually run under. Pure — no side effects, safe to * call on the throw path. Base class: the fixed cap. * @internal */ protected _effectFloatCap(_prospectiveTotal: number): number; /** * Reconcile a geometry-strategy-dependent cap AFTER an effect is * committed and `_effectTotalFloats` reflects it (e.g. Sprite2DMaterial * demoting out of tight-mesh). Runs only on `registerEffect`'s success * path — a rejected over-cap registration never reaches it, so it can't * leave a demoted strategy behind. No-op in the base class. * @internal */ protected _applyEffectGeometryStrategy(): void; /** * Register an effect class on this material. * Assigns a bit index and packed buffer slots, then rebuilds the shader. * If the effect is already registered, this is a no-op. * * @param effectClass - The MaterialEffect subclass to register * @param constants - Optional constants from the effect instance (for provider effects) * @returns Whether the buffer tier changed (requiring batch rebuild). */ registerEffect(effectClass: typeof MaterialEffect, constants?: Record): boolean; /** * Check if an effect class is registered on this material. */ hasEffect(effectClass: typeof MaterialEffect): boolean; /** * Get the list of registered effect classes. */ getEffects(): readonly (typeof MaterialEffect)[]; /** * Rebuild effect buffer attributes in `_instanceAttributes` for the current tier. * @internal */ _rebuildEffectBufferAttributes(): void; /** * Hook for subclasses to provide base color and UV nodes. * Returns `{ color, uv }` — the base color node and UV node that effects can read. * Default returns null (no base color — effects cannot be applied). * @internal */ protected _buildBaseColor(): { color: Node<'vec4'>; uv: Node<'vec2'>; } | null; /** * Check if _buildBaseColor has prerequisites (e.g. texture set). * Subclasses override this to gate _rebuildColorNode() calls. * @internal */ protected _canBuildColor(): boolean; /** * Get the base texture for channel providers (e.g., auto-normal from diffuse alpha). * Returns null by default. Sprite2DMaterial overrides to return the sprite texture. * @internal */ protected _getBaseTexture(): Texture | null; /** * Build TSL attribute nodes for an effect from packed buffer data. * @internal */ protected _buildEffectAttrs(effectClass: typeof MaterialEffect, bufNodes: Node<'vec4'>[]): Record>; /** * Rebuild the colorNode from scratch using the 4-phase pipeline: * * Phase 0: Base color via _buildBaseColor() (no lighting) * Phase 1: Resolve channels from provider effects * Phase 2: Apply colorTransform (lighting) — only if all required channels resolved * Phase 3: Chain color-transforming MaterialEffects (non-providers) * * Called when texture is set, effects are registered, or colorTransform/channels change. * @internal */ _rebuildColorNode(): void; /** * Check if an instance attribute exists. * @internal */ hasInstanceAttribute(name: string): boolean; /** * Get an instance attribute configuration. * @internal */ getInstanceAttribute(name: string): InstanceAttributeConfig | undefined; /** * Get all instance attribute configurations. * Used by SpriteBatch to create InstancedBufferAttributes. * @internal */ getInstanceAttributeSchema(): Map; /** * Get the number of floats needed per instance for custom attributes. * @internal */ getInstanceAttributeStride(): number; /** * Locate an effect's schema field within the packed instance buffers. * Returns the `effectBufN` attribute that contains this field, the * float offset within a single instance's 4-float slice of that * buffer (0-3), and the field's float width. Returns `undefined` * if no registered effect declares the given field. * * Used by instance-writing code (e.g. TileLayer, SpriteBatch) that * needs to poke per-instance effect values directly without going * through a MaterialEffect instance's property setter. */ getEffectFieldLocation(effectName: string, fieldName: string): { bufferName: string; componentIndex: number; size: number; } | undefined; } //#endregion export { ColorTransformContext, ColorTransformFn, EffectMaterial, EffectMaterialOptions, computeTier, getPackedComponent }; //# sourceMappingURL=EffectMaterial.d.ts.map