// ┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓ // ┃ ██████ ██████ ██████ █ █ █ █ █ █▄ ▀███ █ ┃ // ┃ ▄▄▄▄▄█ █▄▄▄▄▄ ▄▄▄▄▄█ ▀▀▀▀▀█▀▀▀▀▀ █ ▀▀▀▀▀█ ████████▌▐███ ███▄ ▀█ █ ▀▀▀▀▀ ┃ // ┃ █▀▀▀▀▀ █▀▀▀▀▀ █▀██▀▀ ▄▄▄▄▄ █ ▄▄▄▄▄█ ▄▄▄▄▄█ ████████▌▐███ █████▄ █ ▄▄▄▄▄ ┃ // ┃ █ ██████ █ ▀█▄ █ ██████ █ ███▌▐███ ███████▄ █ ┃ // ┣━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┫ // ┃ Copyright (c) 2017, the Perspective Authors. ┃ // ┃ ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌ ┃ // ┃ This file is part of the Perspective library, distributed under the terms ┃ // ┃ of the [Apache License 2.0](https://www.apache.org/licenses/LICENSE-2.0). ┃ // ┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛ import { buildGradientLUT, type GradientStop } from "../theme/gradient"; import type { WebGLContextManager } from "./context-manager"; const LUT_SIZE = 256; export interface GradientTextureCache { texture: WebGLTexture; // The `GradientStop[]` reference last uploaded. `resolveTheme` returns a // fresh object per render, so comparing the array reference is enough to // detect a theme change and skip the upload otherwise. lastStops: GradientStop[] | null; } /** * Allocate a 256×1 RGBA8 texture (once) and re-upload the LUT only when * `stops` has changed since the last call. Typical render path: zero * GPU work beyond binding an already-uploaded texture. */ export function ensureGradientTexture( glManager: WebGLContextManager, cache: GradientTextureCache | null, stops: GradientStop[], ): GradientTextureCache { const gl = glManager.gl; let texture: WebGLTexture; if (cache?.texture) { texture = cache.texture; if (cache.lastStops === stops) { return cache; } // no-op fast path } else { texture = gl.createTexture()!; } const lut = buildGradientLUT(stops, LUT_SIZE); gl.bindTexture(gl.TEXTURE_2D, texture); gl.texImage2D( gl.TEXTURE_2D, 0, gl.RGBA, LUT_SIZE, 1, 0, gl.RGBA, gl.UNSIGNED_BYTE, lut, ); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE); gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE); return { texture, lastStops: stops }; } /** * Bind `texture` to a texture unit and set the sampler uniform. Call after * `useProgram`. */ export function bindGradientTexture( glManager: WebGLContextManager, texture: WebGLTexture, samplerLoc: WebGLUniformLocation | null, unit: number = 0, ): void { const gl = glManager.gl; gl.activeTexture(gl.TEXTURE0 + unit); gl.bindTexture(gl.TEXTURE_2D, texture); gl.uniform1i(samplerLoc, unit); }