UNPKG

@lightningjs/renderer

Version:
298 lines (297 loc) 14.7 kB
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 {};