@lightningjs/renderer
Version:
Lightning 3 Renderer
298 lines (297 loc) • 14.7 kB
TypeScript
import { CoreRenderer, type BufferInfo } from '../CoreRenderer.js';
import type { CoreContextTexture } from '../CoreContextTexture.js';
import { type CoreWebGlParameters, type CoreWebGlExtensions, type WebGlColor } from './internal/RendererUtils.js';
import { WebGlCtxTexture } from './WebGlCtxTexture.js';
import { Texture, type TextureCoords } from '../../textures/Texture.js';
import { BufferCollection } from './internal/BufferCollection.js';
import { type RectWithValid } from '../../lib/utils.js';
import { SdfBuffer } from './SdfBuffer.js';
import { SdfRenderOp } from './SdfRenderOp.js';
import { WebGlShaderProgram } from './WebGlShaderProgram.js';
import { CoreNode } from '../../CoreNode.js';
import type { WebGlShaderType } from './WebGlShaderNode.js';
import { WebGlShaderNode } from './WebGlShaderNode.js';
import type { Dimensions } from '../../../common/CommonTypes.js';
import type { GlContextWrapper } from '../../platforms/GlContextWrapper.js';
import type { Stage } from '../../Stage.js';
import type { CoreTextNode } from '../../CoreTextNode.js';
interface CoreWebGlSystem {
parameters: CoreWebGlParameters;
extensions: CoreWebGlExtensions;
}
/**
* Pre-allocated sentinel op inserted into the renderOps array to bracket the
* child quads of a node that uses rounded-corner stencil clipping.
*
* `kind === 0` = begin stencil write pass (before children)
* `kind === 1` = end stencil region (after children)
*
* Objects are reused from a pool on WebGlRenderer — never heap-allocated per frame.
*/
export declare class StencilClipRenderOp {
kind: 0 | 1;
x: number;
y: number;
w: number;
h: number;
clipRadius: number;
pixelRatio: number;
canvasHeight: number;
parentHasRenderTexture: boolean;
parentFramebufferH: number;
stencilRef: number;
}
export type WebGlNodeRenderOp = CoreNode | CoreTextNode;
export type WebGlRenderOp = WebGlNodeRenderOp | StencilClipRenderOp | SdfRenderOp;
export declare class WebGlRenderer extends CoreRenderer {
glw: GlContextWrapper;
system: CoreWebGlSystem;
quadBuffer: ArrayBuffer;
fQuadBuffer: Float32Array;
uiQuadBuffer: Uint32Array;
renderOps: WebGlRenderOp[];
curBufferIdx: number;
curRenderOp: WebGlRenderOp | null;
rttNodes: CoreNode[];
activeRttNode: CoreNode | null;
needsFullUpload: boolean;
lastUploadedBufferSize: number;
dirtyQuadCount: number;
rttQuadBuffer: ArrayBuffer | null;
fRttQuadBuffer: Float32Array | null;
uiRttQuadBuffer: Uint32Array | null;
private readonly _quadScratchBuffer;
private readonly _quadScratchF;
/**
* Shared SDF vertex buffers — one per GPU layout.
*
* All SDF text of a given layout writes into a single pre-allocated CPU
* buffer that is uploaded to the GPU in one `bufferData` per frame. Compatible
* consecutive text nodes are merged into a single SdfRenderOp, producing one
* draw call for many strings.
*
* The two layouts have different strides (6 floats plain / 7 floats rich) and
* can therefore never share a draw call; each gets its own buffer, and each
* SdfRenderOp carries the SdfBuffer it draws from.
*/
sdfBufferPlain: SdfBuffer;
sdfBufferRich: SdfBuffer;
/**
* Current SDF render op being extended by `finalizeSdfBatch`. Null when the
* last op is not extendable (different atlas, clipping rect, or RTT state).
*/
curSdfRenderOp: SdfRenderOp | null;
defaultTextureCoords: TextureCoords;
defaultShaderNode: WebGlShaderNode | null;
quadBufferCollection: BufferCollection;
stencilClipProgram: WebGlShaderProgram | null;
stencilDepth: number;
private stencilQuadBufferCollection;
private stencilOpPool;
private stencilOpPoolIdx;
private readonly _stencilScratchBuffer;
private readonly _stencilScratchF;
private readonly _stencilScratchU;
clearColor: WebGlColor;
/**
* White pixel texture used by default when no texture is specified.
*/
quadBufferUsage: number;
numQuadsRendered: number;
/**
* Whether the renderer is currently rendering to a texture.
*/
renderToTextureActive: boolean;
constructor(stage: Stage);
reset(): void;
createShaderProgram(shaderType: WebGlShaderType, props: Record<string, unknown>): WebGlShaderProgram;
createShaderNode(shaderKey: string, shaderType: WebGlShaderType, props?: Record<string, unknown>, program?: WebGlShaderProgram): WebGlShaderNode<Record<string, unknown>>;
supportsShaderType(shaderType: Readonly<WebGlShaderType>): boolean;
createCtxTexture(textureSource: Texture): CoreContextTexture;
/**
* This function adds a quad (a rectangle composed of two triangles) to the WebGL rendering pipeline.
*
* It takes a set of options that define the quad's properties, such as its dimensions, colors, texture, shader, and transformation matrix.
* The function first updates the shader properties with the current dimensions if necessary, then sets the default texture if none is provided.
* It then checks if a new render operation is needed, based on the current shader and clipping rectangle.
* If a new render operation is needed, it creates one and updates the current render operation.
* The function then adjusts the texture coordinates based on the texture options and adds the texture to the texture manager.
*
* Finally, it calculates the vertices for the quad, taking into account any transformations, and adds them to the quad buffer.
* The function updates the length and number of quads in the current render operation, and updates the current buffer index.
*/
addQuad(node: CoreNode): void;
/**
* Replace the existing RenderOp with a new one that uses the specified Shader
* and starts at the specified buffer index.
*
* @param shader
* @param bufferIdx
*/
private newRenderOp;
/**
* Test if the current Render operation can be reused for the specified parameters.
* @param params
* @returns
*/
reuseRenderOp(node: CoreNode): boolean;
/**
* add RenderOp to the render pipeline
*/
addRenderOp(renderable: WebGlRenderOp): void;
/**
* Append pre-transformed SDF glyph vertices to the given shared SDF buffer
* and manage SDF render op batching.
*
* @remarks
* This method pre-transforms glyph positions from design units to world
* pixel space on the CPU, packs per-vertex color and distanceRange, and
* writes them into the shared SDF buffer of the given layout. Compatible
* consecutive calls (same layout, atlas, clipping, RTT state) are merged
* into a single SdfRenderOp, resulting in one draw call for many text nodes.
*
* The design-unit glyph records are `SDF_PLAIN_GLYPH_STRIDE` (8) or
* `SDF_RICH_GLYPH_STRIDE` (12) floats per glyph depending on the layout:
* plain: x, y, w, h, u, v, uw, vh
* rich: x, y, w, h, u, v, uw, vh, shearTop, shearBot, packed_span_color, style
* where packed_span_color is RGBA bytes written via a Uint32 view of the
* same ArrayBuffer (bit-identical read via `uGlyphs` below), shearTop/shearBot
* are the per-corner x-deltas of the italic lean, and the decorated quads use
* `u = -1.0` as a solid-fill sentinel.
*/
addSdfQuads(sdfBuffer: SdfBuffer, glyphs: Float32Array, glyphCount: number, fontScale: number, transform: Float32Array, color: number, worldAlpha: number, distanceRange: number, atlasTexture: WebGlCtxTexture, clippingRect: RectWithValid, width: number, height: number, parentHasRenderTexture: boolean, framebufferDimensions: Dimensions | null, sdfShader: WebGlShaderNode): void;
/**
* Fast path: copy pre-computed cached SDF vertex data into the shared
* buffer and create/extend an SdfRenderOp.
*
* @remarks
* When a text node hasn't changed (same layout, transform, color, alpha),
* the per-glyph matrix multiplication is skipped entirely. The cached
* Float32Array is written via a single `Float32Array.set()` (memcpy), which
* is orders of magnitude faster than the per-glyph computation path.
*
* The cached data is already in the target SdfBuffer's GPU layout, so the
* mem-copy must stay a typed-array `set` (bit-exact): packed RGBA colors
* live in the same Float32Array and some bit patterns are float32 NaNs,
* which element-wise float reads/writes may canonicalize and corrupt.
*
* Exact cache hits write byte-identical data at identical offsets, so this
* path deliberately does NOT set `sdfBuffer.changed`.
*/
addSdfCachedQuads(sdfBuffer: SdfBuffer, cachedVertices: Float32Array, numGlyphs: number, atlasTexture: WebGlCtxTexture, clippingRect: RectWithValid, worldAlpha: number, width: number, height: number, parentHasRenderTexture: boolean, framebufferDimensions: Dimensions | null, sdfShader: WebGlShaderNode): void;
/**
* Append cached SDF vertices translated by (dx, dy) to the shared buffer.
*
* @remarks
* The scroll fast path: a text node whose transform changed by pure
* translation reuses its world-space vertex cache — one mem-copy plus two
* adds per vertex instead of full per-glyph matrix math, and the cache
* keeps its original base so nothing is re-snapshotted per frame.
*
* The copy MUST stay a typed-array `set` (bit-exact memcpy): packed RGBA
* colors live in the same Float32Array and some bit patterns are float32
* NaNs, which element-wise float reads/writes may canonicalize and corrupt.
* Only the two position floats of each vertex are touched after the copy.
*/
addSdfTranslatedQuads(sdfBuffer: SdfBuffer, cachedVertices: Float32Array, numGlyphs: number, dx: number, dy: number, atlasTexture: WebGlCtxTexture, clippingRect: RectWithValid, worldAlpha: number, width: number, height: number, parentHasRenderTexture: boolean, framebufferDimensions: Dimensions | null, sdfShader: WebGlShaderNode): void;
/**
* Shared batching logic for SDF render ops.
* Called by all `addSdf*` write paths.
*/
private finalizeSdfBatch;
/**
* Upload the shared SDF buffers for the main pass, skipping the driver-side
* `bufferData` copy per layout when its bytes provably match what the GPU
* already holds: every write this frame was an exact cache-hit mem-copy
* (`changed` false) and the total size matches the previous upload.
*
* The skip is only sound because a cache hit that is NOT byte-identical to
* the current GPU contents always raises `changed`:
* - cache-miss recompute (`addSdfQuads`) and translated copies
* (`addSdfTranslatedQuads`) write fresh bytes;
* - `renderQuads` marks the buffer dirty when a static cache hit would land
* at a shifted offset (a render-list reorder moves the node's quad range)
* or when the last write at that range was a translated copy;
* - backing-store growth swaps the ArrayBuffer, and RTT partial uploads
* have their own dirty path.
*/
private uploadSdfBuffer;
private uploadSdfBufferLayout;
/**
* Render the current set of RenderOps to render to the specified surface.
*
* TODO: 'screen' is the only supported surface at the moment.
*
* @param surface
*/
render(_surface?: 'screen' | CoreContextTexture): void;
getQuadCount(): number;
renderToTexture(node: CoreNode): void;
/**
* Inserts an RTT node into `this.rttNodes` while maintaining the correct rendering order based on hierarchy.
*
* Rendering order for RTT nodes is critical when nested RTT nodes exist in a parent-child relationship.
* Specifically:
* - Child RTT nodes must be rendered before their RTT-enabled parents to ensure proper texture composition.
* - If an RTT node is added and it has existing RTT children, it should be rendered after those children.
*
* This function addresses both cases by:
* 1. **Checking Upwards**: It traverses the node's hierarchy upwards to identify any RTT parent
* already in `rttNodes`. If an RTT parent is found, the new node is placed before this parent.
* 2. **Checking Downwards**: It traverses the node’s children recursively to find any RTT-enabled
* children that are already in `rttNodes`. If such children are found, the new node is inserted
* after the last (highest index) RTT child node.
*
* The final calculated insertion index ensures the new node is positioned in `rttNodes` to respect
* both parent-before-child and child-before-parent rendering rules, preserving the correct order
* for the WebGL renderer.
*
* @param node - The RTT-enabled CoreNode to be added to `rttNodes` in the appropriate hierarchical position.
*/
private insertRTTNodeInOrder;
private findMaxChildRTTIndex;
renderRTTNodes(): void;
private renderRTT;
updateViewport(): void;
removeRTTNode(node: CoreNode): void;
invalidateQuadBuffer(): void;
getBufferInfo(): BufferInfo | null;
getDefaultShaderNode(): WebGlShaderNode;
getTextureCoords(node: CoreNode): TextureCoords | undefined;
/**
* Sets the glClearColor to the specified color. *
* @param color - The color to set as the clear color, represented as a 32-bit integer.
*/
updateClearColor(color: number): void;
/**
* Lazily compiles the StencilClip shader program (once per renderer lifetime).
*/
private getStencilClipProgram;
/**
* Returns a pre-allocated StencilClipRenderOp from the pool, growing the pool
* if necessary. Pool objects are never GC'd between frames.
*/
private allocStencilOp;
/**
* Inserts a "begin rounded clip" sentinel into renderOps for the given node.
* Called by Stage.addQuads before processing a rounded-clip node's children.
*/
beginRoundedClip(node: CoreNode): void;
/**
* Inserts an "end rounded clip" sentinel into renderOps for the given node.
* Called by Stage.addSubtreeQuads after processing a rounded-clip node's children.
*/
endRoundedClip(_node: CoreNode): void;
/**
* Executes the stencil write pass for a begin-rounded-clip sentinel.
* Sets stencil test so subsequent draws are masked to the rounded region.
*/
private drawStencilBegin;
/**
* Tears down the stencil state after the rounded-clip subtree has been drawn.
*/
private drawStencilEnd;
destroy(): void;
deleteBuffer(buffer: WebGLBuffer): void;
}
export {};