import { OrthographicCamera } from "./math"; import { Pattern } from "./pattern"; import { buildPatternData } from "./patternData"; export interface WebGLState { gl: WebGLRenderingContext | WebGL2RenderingContext; program: WebGLProgram; buffers: { position: WebGLBuffer; normal: WebGLBuffer; uv: WebGLBuffer; index: WebGLBuffer; wireframeIndex: WebGLBuffer; }; locations: { attributes: Record; uniforms: Record; }; camera: OrthographicCamera; indexCount: number; wireframeIndexCount: number; indexType: number; } /** A program whose compile and link have been issued but not read back. See `_startProgram`. */ interface PendingProgram { program: WebGLProgram; vertex: WebGLShader; fragment: WebGLShader; vertexSource: string; fragmentSource: string; } import { NeatConfig, NeatColor, NeatController } from "./types"; export interface NeatGradient { speed: number; horizontalPressure: number; verticalPressure: number; waveFrequencyX: number; waveFrequencyY: number; waveAmplitude: number; secondaryWaveEnabled: boolean; secondaryWaveFrequencyX: number; secondaryWaveFrequencyY: number; secondaryWaveAmplitude: number; secondaryWaveSpeed: number; secondaryWaveAngle: number; highlights: number; shadows: number; colorSaturation: number; colorBlending: number; yOffsetWaveMultiplier: number; yOffsetColorMultiplier: number; yOffsetFlowMultiplier: number; colorBrightness: number; grainIntensity: number; grainSparsity: number; grainSpeed: number; wireframe: boolean; backgroundAlpha: number; flowDistortionA: number; flowDistortionB: number; flowScale: number; flowEase: number; flowEnabled: boolean; textureEase: number; silhouetteFade: number; cylinderFade: number; ribbonFade: number; flatShading: boolean; domainWarpEnabled: boolean; domainWarpIntensity: number; domainWarpScale: number; vignetteIntensity: number; vignetteRadius: number; fresnelEnabled: boolean; fresnelPower: number; fresnelIntensity: number; iridescenceEnabled: boolean; iridescenceIntensity: number; iridescenceSpeed: number; prismEdgeEnabled: boolean; prismEdgeIntensity: number; prismEdgeThinness: number; prismEdgeSpread: number; prismEdgeSpeed: number; prismEdgeRipple: number; bloomIntensity: number; bloomThreshold: number; chromaticAberration: number; shapeRotationX: number; shapeRotationY: number; shapeRotationZ: number; shapeAutoRotateSpeedX: number; shapeAutoRotateSpeedY: number; cameraX: number; cameraY: number; cameraZ: number; cameraRotationX: number; cameraRotationY: number; cameraRotationZ: number; textureVoidLikelihood: number; textureVoidWidthMin: number; textureVoidWidthMax: number; textureBandDensity: number; textureColorBlending: number; textureSeed: number; transparentTextureVoid: boolean; proceduralBackgroundColor: string; textureShapeTriangles: number; textureShapeCircles: number; textureShapeBars: number; textureShapeSquiggles: number; sphereRadius: number; torusRadius: number; torusTube: number; cylinderRadius: number; cylinderHeight: number; planeBend: number; planeTwist: number; } export declare class NeatGradient implements NeatController { private _ref; private _licensed; private _antialias; private _speed; private _horizontalPressure; private _verticalPressure; private _waveFrequencyX; private _waveFrequencyY; private _waveAmplitude; private _secondaryWaveEnabled; private _secondaryWaveFrequencyX; private _secondaryWaveFrequencyY; private _secondaryWaveAmplitude; private _secondaryWaveSpeed; private _secondaryWaveAngle; private _shadows; private _highlights; private _saturation; private _brightness; private _grainScale; private _grainIntensity; private _grainSparsity; private _grainSpeed; private _colorBlending; private _resolution; private _colors; private _wireframe; private _backgroundColor; private _backgroundColorRgb; private _backgroundAlpha; private _flowDistortionA; private _flowDistortionB; private _flowScale; private _flowEase; private _flowEnabled; private glState; private _enableProceduralTexture; private _textureVoidLikelihood; private _textureVoidWidthMin; private _textureVoidWidthMax; private _textureBandDensity; private _textureColorBlending; private _textureSeed; private _textureEase; private _transparentTextureVoid; private _domainWarpEnabled; private _domainWarpIntensity; private _domainWarpScale; private _vignetteIntensity; private _vignetteRadius; private _fresnelEnabled; private _fresnelPower; private _fresnelIntensity; private _fresnelColor; private _fresnelColorRgb; private _iridescenceEnabled; private _iridescenceIntensity; private _iridescenceSpeed; private _prismEdgeEnabled; private _prismEdgeIntensity; private _prismEdgeThinness; private _prismEdgeSpread; private _prismEdgeSpeed; private _prismEdgeRipple; private _bloomIntensity; private _bloomThreshold; private _chromaticAberration; private _silhouetteFade; private _cylinderFade; private _ribbonFade; private _flatShading; private _shapeType; private _shapeRotationX; private _shapeRotationY; private _shapeRotationZ; private _shapeAutoRotateSpeedX; private _shapeAutoRotateSpeedY; private _sphereRadius; private _torusRadius; private _torusTube; private _cylinderRadius; private _cylinderHeight; private _planeBend; private _planeTwist; private _cameraLock; private _cameraX; private _cameraY; private _cameraZ; private _cameraRotationX; private _cameraRotationY; private _cameraRotationZ; private _cameraZoom; private _proceduralTexture; private _proceduralBackgroundColor; private _textureMode; /** Resolved mode. Falls back to bitmap when the context cannot bake. */ private _activeTextureMode; private _isWebGL2; private _derivativesSupported; private _bakeWarned; private _bakeEdgeSoftness; private _bakeSeamBlend; /** 0 = derive from the canvas; otherwise an explicit square resolution. */ private _textureBakeResolution; private _shapeTexture; private _auxTexture; private _bakeProgram; private _bakeQuad; private _bakeFbo; private _bakeVao; private _bakeUniforms; private _textureShapeTriangles; private _textureShapeCircles; private _textureShapeBars; private _textureShapeSquiggles; private requestRef; private sizeObserver; private _currentCursor; /** False until the programs issued in the constructor have linked and been wired up. */ private _initialized; private _destroyed; /** KHR_parallel_shader_compile, when the context has it. See `_linked`. */ private _parallelCompile; /** The gradient program while it links: the first one, and a variant after a feature toggle. */ private _pendingGradient; /** The bake program while it links. */ private _pendingBake; private _cachedColorRgb; private _yOffset; private _yOffsetWaveMultiplier; private _yOffsetColorMultiplier; private _yOffsetFlowMultiplier; private _sourceCanvas; private _sourceCtx; private _maskedCanvas; private _maskedCtx; private _resizeTimeoutId; private _colorsChanged; private _textureDirty; private _modelViewMatrix; private _isVisible; private __uniformsDirty; private __yOffsetDirty; private __textureNeedsUpdate; /** True while the loop is stopped because the next frame would be identical. */ private _parked; private _renderFrame; private get _uniformsDirty(); private set _uniformsDirty(value); private get _yOffsetDirty(); private set _yOffsetDirty(value); private get _textureNeedsUpdate(); private set _textureNeedsUpdate(value); /** Segment count the current vertex buffers were built with. */ private _segmentsInUse; /** Feature set the current program was compiled for (see _shaderFeatureKey). */ private _shaderKey; /** Drawing buffer size relative to the canvas' CSS size. */ private _renderScale; /** Last CSS size seen by the resize observer, so renderScale can be re-applied. */ private _cssWidth; private _cssHeight; private _applySize; private get _meshBase(); /** * Segment count for the displacement mesh. * * The grid used to be a flat 240×240 (≈58k vertices) whatever the canvas, so a * 320px card paid exactly what a 4K hero did — and the vertex shader is the * expensive half of this renderer, running Perlin noise, the flow field and the * colour mix per vertex. One segment per ~6 canvas pixels is visually identical * (it is what a 1440px-wide canvas was already getting) and costs a phone about * a fifteenth of the vertices. `resolution` still scales it, so the control keeps * working in both directions. * * The floor of 24 applies to the canvas-derived count only, so a small canvas * is not coarsened past it. It never raises an explicit low `resolution`: that is * how a deliberately faceted look is asked for (0.05 gives 12 segments), and * flooring it at 24 silently re-drew every such config with twice the facets. */ private _segmentsFor; /** Schedule a frame if the loop parked itself. No-op while it is running. */ private _wake; private _visibilityObserver; private _visibilityHandler; private _watermarkProgram; private _watermarkTexture; private _watermarkBuffer; private _watermarkTexCoordBuffer; private _watermarkWidth; private _watermarkHeight; private _watermarkMargin; private _wmLocPos; private _wmLocTc; private _wmLocTex; /** Whether the watermark program has linked and its uniform been resolved. */ private _wmLinked; private _wmPosData; private _wmClickHandler; private _wmMoveHandler; private _wmMoveRafPending; private _wmCachedRect; private _wmRectCacheTime; private _gradientVAO; private _watermarkVAO; constructor(config: NeatConfig & { ref: HTMLCanvasElement; resolution?: number; seed?: number; preserveDrawingBuffer?: boolean; antialias?: boolean; }); destroy(): void; get colors(): NeatColor[]; set colors(colors: NeatColor[]); get grainScale(): number; set grainScale(grainScale: number); get renderScale(): number; set renderScale(value: number); get resolution(): number; set resolution(resolution: number); get antialias(): boolean; set antialias(antialias: boolean); get backgroundColor(): string; set backgroundColor(backgroundColor: string); get yOffset(): number; set yOffset(yOffset: number); /** * How the procedural texture is produced. * * `bitmap` (default) draws the shapes through Canvas2D at a fixed 1024px. * `baked` rasterizes them analytically on the GPU instead, at a resolution * derived from the canvas, so edges get exact coverage rather than landing * on a coarse grid. Both end up as an ordinary mipmapped texture, so the * runtime cost is identical — the difference is how sharp it is, and how * long generation takes (the GPU bake is the faster of the two). * * `baked` needs WebGL2; it falls back to `bitmap` otherwise. Squiggles are * not supported when baking. Read `activeTextureMode` for what is in use. */ get textureMode(): 'bitmap' | 'baked'; set textureMode(value: 'bitmap' | 'baked'); /** Resolution of the baked texture. 0 derives it from the canvas size. */ get textureBakeResolution(): number; set textureBakeResolution(value: number); /** * Multiplier on the antialiasing filter width used while baking, in output * texels. 1 gives exact single-texel coverage; raise it to soften. */ get bakeEdgeSoftness(): number; set bakeEdgeSoftness(value: number); /** The mode actually in use, which falls back to `bitmap` if baking is unsupported. */ get activeTextureMode(): 'bitmap' | 'baked'; get enableProceduralTexture(): boolean; set enableProceduralTexture(value: boolean); _updateGeometry(): void; /** * Uploads the camera's projection to the gradient program. Before the first * program has linked there is nothing to upload to, and touching it would wait * for the link; `_wireProgram` uploads the camera as it stands by then. */ _uploadProjection(): void; _hexToRgb(hex: string): [number, number, number]; _initScene(resolution: number, preserveDrawingBuffer?: boolean): WebGLState; /** Uniform locations belong to a program, so they are re-resolved on every recompile. */ _resolveProgramLocations(gl: WebGLRenderingContext | WebGL2RenderingContext, program: WebGLProgram, locations: WebGLState["locations"]): void; /** Compiles and links the gradient program. */ /** * Identifies the shader variant the current config needs. Compared each time a * property changes; a different key means a recompile. */ _shaderFeatureKey(): string; /** * Feature flags as compile-time constants rather than uniforms. * * Every effect used to be a runtime branch on a uniform, so a plain two-colour * gradient still carried the domain-warp fbm calls, the triplanar texture taps, * fresnel, iridescence, bloom and the 3D shading path in its instruction stream * — costing register pressure and occupancy on mobile GPUs even when skipped. * Feeding the flags in as constants lets the compiler fold the conditions and * drop the dead half outright. Toggling a feature recompiles, which is fine for * something that changes on a click rather than per frame. */ _buildShaderDefines(): string; /** * Starts building a program for the current feature set. The render loop swaps * it in once it has linked and draws with the current one until then, so a * toggle does not stall the page while the driver compiles. Attribute * locations are pinned (GRADIENT_ATTRIBUTES), so the vertex buffers and the VAO * stay valid across the swap. */ _rebuildProgram(): void; /** Issues the gradient program for the current feature set. See _startProgram. */ _compileProgram(gl: WebGLRenderingContext | WebGL2RenderingContext): PendingProgram; /** * Issues a program's compile and link without reading anything back. * * `compileShader` and `linkProgram` only queue work for the driver. The first * read after them — a compile or link status, a uniform or attribute location — * is what makes the main thread wait for the driver to finish, and on a cold * shader cache that was one uninterrupted task: ~300ms for the gradient and bake * programs on an M2 Pro, and several times that on a phone. So nothing is read * here. Attribute slots are bound before the link instead of looked up after it. * `_linked` asks whether the program is done without waiting for it. */ _startProgram(gl: WebGLRenderingContext | WebGL2RenderingContext, vertexSource: string, fragmentSource: string, attributes: string[]): PendingProgram; /** * Whether a program has finished compiling and linking, asked without waiting. * * With KHR_parallel_shader_compile the driver does that work off the main thread * and this polls it. Without the extension it always answers yes, and the first * read afterwards waits for the driver exactly as it always did. */ _linked(gl: WebGLRenderingContext | WebGL2RenderingContext, program: WebGLProgram): boolean; /** * Deletes a program, but never while it is still linking. * * Chrome frees a deleted program's name straight away and hands it to the * next `createProgram` on the context while the driver is still finishing the * old link under it; the new program's queries then fail with * GL_INVALID_VALUE "Program object expected". Destroying a gradient mid-link * and building another on the same canvas does exactly that — React * StrictMode's mount, unmount, mount — and so does toggling a feature again * before the previous variant has linked. A program still linking is deleted * from a later task instead, once it has finished. Works after destroy(). */ _deleteProgram(gl: WebGLRenderingContext | WebGL2RenderingContext, program: WebGLProgram): void; /** * Finishes what the constructor started, once every program it issued has * linked: the gradient's and, for a baked texture, the bake's. False while * either is still linking. The watermark is not waited for: it is only built * when it will be shown, and draws from the first frame its program is ready. */ _completeStartup(): boolean; /** * Makes a linked gradient program current: reports what its compile and link * said, uploads the uniforms that never change, and resolves its locations, * which belong to a program and so are needed again after every recompile. */ _wireProgram(gl: WebGLRenderingContext | WebGL2RenderingContext, pending: PendingProgram): void; /** * Builds the pattern description from the current config. * * Both render modes go through this, so a given `textureSeed` describes the * same artwork whether it ends up rasterized into a bitmap or compiled into * the shader. */ _buildPattern(size: number): Pattern | null; /** * Chooses the bake resolution. * * The old Canvas2D path used a hardcoded 1024 whether it was backing a * 600px divider or a 5K hero, which is most of why the texture looked soft * when magnified. Scaling with the canvas costs nothing at generation time * and is what actually buys the sharpness. */ _bakeResolution(): number; /** Issues the bake program's compile and link, once per context. See _startProgram. */ _startBakeProgram(gl: WebGL2RenderingContext): void; /** Whether the next texture is a bake whose program has not linked yet. */ _bakeProgramLinking(gl: WebGLRenderingContext | WebGL2RenderingContext): boolean; /** * Readies the bake program, its quad and its framebuffer, once per context. * "linking" while the program has not linked yet: the caller keeps the texture * it has and asks again on the next frame. */ _ensureBakePipeline(gl: WebGL2RenderingContext): "ready" | "linking" | "failed"; /** Uploads the packed pattern into the two data textures the bake reads. */ _uploadPatternData(gl: WebGL2RenderingContext, data: ReturnType): void; /** * Renders the pattern analytically into a texture. * * Returns null if anything is unavailable, which puts the caller back on * the Canvas2D path. */ _bakePatternTexture(gl: WebGL2RenderingContext): WebGLTexture | null; /** Resolves the requested mode against what this context can actually do. */ _resolveTextureMode(): 'bitmap' | 'baked'; _createProceduralTexture(gl: WebGLRenderingContext | WebGL2RenderingContext): WebGLTexture | null; get fresnelColor(): string; set fresnelColor(fresnelColor: string); get shapeType(): 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon'; set shapeType(val: 'plane' | 'sphere' | 'torus' | 'cylinder' | 'ribbon'); get cameraLock(): boolean; set cameraLock(val: boolean); get cameraZoom(): number; set cameraZoom(val: number); _updateCameraFrustum(): void; /** * Issues the watermark shader, creates the text texture, and sets up the * screen-space quad buffers. Uses VAOs on WebGL2 to minimise per-frame state * switching (~2 calls instead of ~20). * * Only called once the watermark will be shown: at construction without a * license key, or when a key fails to verify. Once per instance. */ private _startWatermark; /** Returns true if the mouse event is inside the watermark's pixel bounds. */ private _isOverWatermark; /** * Draws the watermark quad as a second pass after the main gradient. * Uses VAO switching on WebGL2 (~2 GL calls) or manual restore on WebGL1. * * Returns false only while the watermark program is still linking, so the * loop keeps running until it can draw. */ private _renderWatermark; } export {};