fabric
Version:
Object model for HTML5 canvas, and SVG-to-canvas parser. Backed by jsdom and node-canvas.
1 lines • 18.3 kB
Source Map (JSON)
{"version":3,"file":"WebGLFilterBackend.min.mjs","sources":["../../../src/filters/WebGLFilterBackend.ts"],"sourcesContent":["import { config } from '../config';\nimport { createCanvasElementFor } from '../util/misc/dom';\nimport type {\n TWebGLPipelineState,\n TProgramCache,\n TTextureCache,\n TPipelineResources,\n} from './typedefs';\nimport type { BaseFilter } from './BaseFilter';\n\nexport class WebGLFilterBackend {\n declare tileSize: number;\n\n /**\n * Define ...\n **/\n aPosition: Float32Array = new Float32Array([0, 0, 0, 1, 1, 0, 1, 1]);\n\n /**\n * If GLPut data is the fastest operation, or if forced, this buffer will be used\n * to transfer the data back in the 2d logic\n **/\n declare imageBuffer?: ArrayBuffer;\n\n declare canvas: HTMLCanvasElement;\n\n /**\n * The Webgl context that will execute the operations for filtering\n **/\n declare gl: WebGLRenderingContext;\n\n /**\n * Keyed map for shader cache\n **/\n declare programCache: TProgramCache;\n\n /**\n * Keyed map for texture cache\n **/\n declare textureCache: TTextureCache;\n\n /**\n * Contains GPU info for debug\n **/\n declare gpuInfo: any;\n\n /**\n * Experimental. This object is a sort of repository of help layers used to avoid\n * of recreating them during frequent filtering. If you are previewing a filter with\n * a slider you probably do not want to create help layers every filter step.\n * in this object there will be appended some canvases, created once, resized sometimes\n * cleared never. Clearing is left to the developer.\n **/\n resources: TPipelineResources = {};\n\n constructor({ tileSize = config.textureSize } = {}) {\n this.tileSize = tileSize;\n this.setupGLContext(tileSize, tileSize);\n this.captureGPUInfo();\n }\n\n /**\n * Setup a WebGL context suitable for filtering, and bind any needed event handlers.\n */\n setupGLContext(width: number, height: number): void {\n this.dispose();\n this.createWebGLCanvas(width, height);\n }\n\n /**\n * Create a canvas element and associated WebGL context and attaches them as\n * class properties to the GLFilterBackend class.\n */\n createWebGLCanvas(width: number, height: number): void {\n const canvas = createCanvasElementFor({ width, height });\n const glOptions = {\n alpha: true,\n premultipliedAlpha: false,\n depth: false,\n stencil: false,\n antialias: false,\n },\n gl = canvas.getContext('webgl', glOptions) as WebGLRenderingContext;\n\n if (!gl) {\n return;\n }\n gl.clearColor(0, 0, 0, 0);\n // this canvas can fire webglcontextlost and webglcontextrestored\n this.canvas = canvas;\n this.gl = gl;\n }\n\n /**\n * Attempts to apply the requested filters to the source provided, drawing the filtered output\n * to the provided target canvas.\n *\n * @param {Array} filters The filters to apply.\n * @param {TexImageSource} source The source to be filtered.\n * @param {Number} width The width of the source input.\n * @param {Number} height The height of the source input.\n * @param {HTMLCanvasElement} targetCanvas The destination for filtered output to be drawn.\n * @param {String|undefined} cacheKey A key used to cache resources related to the source. If\n * omitted, caching will be skipped.\n */\n applyFilters(\n filters: BaseFilter<string, Record<string, any>>[],\n source: TexImageSource,\n width: number,\n height: number,\n targetCanvas: HTMLCanvasElement,\n cacheKey?: string,\n ): TWebGLPipelineState | undefined {\n const gl = this.gl;\n const ctx = targetCanvas.getContext('2d');\n if (!gl || !ctx) {\n return;\n }\n let cachedTexture;\n if (cacheKey) {\n cachedTexture = this.getCachedTexture(cacheKey, source);\n }\n const pipelineState: TWebGLPipelineState = {\n originalWidth:\n (source as HTMLImageElement).width ||\n (source as HTMLImageElement).naturalWidth ||\n 0,\n originalHeight:\n (source as HTMLImageElement).height ||\n (source as HTMLImageElement).naturalHeight ||\n 0,\n sourceWidth: width,\n sourceHeight: height,\n destinationWidth: width,\n destinationHeight: height,\n context: gl,\n sourceTexture: this.createTexture(\n gl,\n width,\n height,\n !cachedTexture ? source : undefined,\n ),\n targetTexture: this.createTexture(gl, width, height),\n originalTexture:\n cachedTexture ||\n this.createTexture(\n gl,\n width,\n height,\n !cachedTexture ? source : undefined,\n )!,\n passes: filters.length,\n webgl: true,\n aPosition: this.aPosition,\n programCache: this.programCache,\n pass: 0,\n filterBackend: this,\n targetCanvas: targetCanvas,\n };\n const tempFbo = gl.createFramebuffer();\n gl.bindFramebuffer(gl.FRAMEBUFFER, tempFbo);\n filters.forEach((filter: any) => {\n filter && filter.applyTo(pipelineState);\n });\n resizeCanvasIfNeeded(pipelineState);\n this.copyGLTo2D(gl, pipelineState);\n gl.bindTexture(gl.TEXTURE_2D, null);\n gl.deleteTexture(pipelineState.sourceTexture);\n gl.deleteTexture(pipelineState.targetTexture);\n gl.deleteFramebuffer(tempFbo);\n ctx.setTransform(1, 0, 0, 1, 0, 0);\n return pipelineState;\n }\n\n /**\n * Detach event listeners, remove references, and clean up caches.\n */\n dispose() {\n if (this.canvas) {\n // we are disposing, we don't care about the fact\n // that the canvas shouldn't be null.\n // @ts-expect-error disposing\n this.canvas = null;\n // @ts-expect-error disposing\n this.gl = null;\n }\n this.clearWebGLCaches();\n }\n\n /**\n * Wipe out WebGL-related caches.\n */\n clearWebGLCaches() {\n this.programCache = {};\n this.textureCache = {};\n }\n\n /**\n * Create a WebGL texture object.\n *\n * Accepts specific dimensions to initialize the texture to or a source image.\n *\n * @param {WebGLRenderingContext} gl The GL context to use for creating the texture.\n * @param {number} width The width to initialize the texture at.\n * @param {number} height The height to initialize the texture.\n * @param {TexImageSource} textureImageSource A source for the texture data.\n * @param {number} filter gl.NEAREST default or gl.LINEAR filters for the texture.\n * This filter is very useful for LUTs filters. If you need interpolation use gl.LINEAR\n * @returns {WebGLTexture}\n */\n createTexture(\n gl: WebGLRenderingContext,\n width: number,\n height: number,\n textureImageSource?: TexImageSource,\n filter?:\n | WebGLRenderingContextBase['NEAREST']\n | WebGLRenderingContextBase['LINEAR'],\n ) {\n const {\n NEAREST,\n TEXTURE_2D,\n RGBA,\n UNSIGNED_BYTE,\n CLAMP_TO_EDGE,\n TEXTURE_MAG_FILTER,\n TEXTURE_MIN_FILTER,\n TEXTURE_WRAP_S,\n TEXTURE_WRAP_T,\n } = gl;\n const texture = gl.createTexture();\n gl.bindTexture(TEXTURE_2D, texture);\n gl.texParameteri(TEXTURE_2D, TEXTURE_MAG_FILTER, filter || NEAREST);\n gl.texParameteri(TEXTURE_2D, TEXTURE_MIN_FILTER, filter || NEAREST);\n gl.texParameteri(TEXTURE_2D, TEXTURE_WRAP_S, CLAMP_TO_EDGE);\n gl.texParameteri(TEXTURE_2D, TEXTURE_WRAP_T, CLAMP_TO_EDGE);\n if (textureImageSource) {\n gl.texImage2D(\n TEXTURE_2D,\n 0,\n RGBA,\n RGBA,\n UNSIGNED_BYTE,\n textureImageSource,\n );\n } else {\n gl.texImage2D(\n TEXTURE_2D,\n 0,\n RGBA,\n width,\n height,\n 0,\n RGBA,\n UNSIGNED_BYTE,\n null,\n );\n }\n return texture;\n }\n\n /**\n * Can be optionally used to get a texture from the cache array\n *\n * If an existing texture is not found, a new texture is created and cached.\n *\n * @param {String} uniqueId A cache key to use to find an existing texture.\n * @param {HTMLImageElement|HTMLCanvasElement} textureImageSource A source to use to create the\n * texture cache entry if one does not already exist.\n */\n getCachedTexture(\n uniqueId: string,\n textureImageSource: TexImageSource,\n filter?:\n | WebGLRenderingContextBase['NEAREST']\n | WebGLRenderingContextBase['LINEAR'],\n ): WebGLTexture | null {\n const { textureCache } = this;\n if (textureCache[uniqueId]) {\n return textureCache[uniqueId];\n } else {\n const texture = this.createTexture(\n this.gl,\n (textureImageSource as HTMLImageElement).width,\n (textureImageSource as HTMLImageElement).height,\n textureImageSource,\n filter,\n );\n if (texture) {\n textureCache[uniqueId] = texture;\n }\n return texture;\n }\n }\n\n /**\n * Clear out cached resources related to a source image that has been\n * filtered previously.\n *\n * @param {String} cacheKey The cache key provided when the source image was filtered.\n */\n evictCachesForKey(cacheKey: string) {\n if (this.textureCache[cacheKey]) {\n this.gl.deleteTexture(this.textureCache[cacheKey]);\n delete this.textureCache[cacheKey];\n }\n }\n\n /**\n * Copy an input WebGL canvas on to an output 2D canvas.\n *\n * The WebGL canvas is assumed to be upside down, with the top-left pixel of the\n * desired output image appearing in the bottom-left corner of the WebGL canvas.\n *\n * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from.\n * @param {Object} pipelineState The 2D target canvas to copy on to.\n */\n copyGLTo2D(gl: WebGLRenderingContext, pipelineState: TWebGLPipelineState) {\n const glCanvas = gl.canvas,\n targetCanvas = pipelineState.targetCanvas,\n ctx = targetCanvas.getContext('2d');\n if (!ctx) {\n return;\n }\n ctx.translate(0, targetCanvas.height); // move it down again\n ctx.scale(1, -1); // vertical flip\n // where is my image on the big glcanvas?\n const sourceY = glCanvas.height - targetCanvas.height;\n ctx.drawImage(\n glCanvas,\n 0,\n sourceY,\n targetCanvas.width,\n targetCanvas.height,\n 0,\n 0,\n targetCanvas.width,\n targetCanvas.height,\n );\n }\n\n /**\n * Copy an input WebGL canvas on to an output 2D canvas using 2d canvas' putImageData\n * API. Measurably faster than using ctx.drawImage in Firefox (version 54 on OSX Sierra).\n *\n * @param {WebGLRenderingContext} sourceContext The WebGL context to copy from.\n * @param {HTMLCanvasElement} targetCanvas The 2D target canvas to copy on to.\n * @param {Object} pipelineState The 2D target canvas to copy on to.\n */\n copyGLTo2DPutImageData(\n this: Required<WebGLFilterBackend>,\n gl: WebGLRenderingContext,\n pipelineState: TWebGLPipelineState,\n ) {\n const targetCanvas = pipelineState.targetCanvas,\n ctx = targetCanvas.getContext('2d'),\n dWidth = pipelineState.destinationWidth,\n dHeight = pipelineState.destinationHeight,\n numBytes = dWidth * dHeight * 4;\n if (!ctx) {\n return;\n }\n const u8 = new Uint8Array(this.imageBuffer, 0, numBytes);\n const u8Clamped = new Uint8ClampedArray(this.imageBuffer, 0, numBytes);\n\n gl.readPixels(0, 0, dWidth, dHeight, gl.RGBA, gl.UNSIGNED_BYTE, u8);\n const imgData = new ImageData(u8Clamped, dWidth, dHeight);\n ctx.putImageData(imgData, 0, 0);\n }\n\n /**\n * Attempt to extract GPU information strings from a WebGL context.\n *\n * Useful information when debugging or blacklisting specific GPUs.\n *\n * @returns {Object} A GPU info object with renderer and vendor strings.\n */\n captureGPUInfo() {\n if (this.gpuInfo) {\n return this.gpuInfo;\n }\n const gl = this.gl,\n gpuInfo = { renderer: '', vendor: '' };\n if (!gl) {\n return gpuInfo;\n }\n const ext = gl.getExtension('WEBGL_debug_renderer_info');\n if (ext) {\n const renderer = gl.getParameter(ext.UNMASKED_RENDERER_WEBGL);\n const vendor = gl.getParameter(ext.UNMASKED_VENDOR_WEBGL);\n if (renderer) {\n gpuInfo.renderer = renderer.toLowerCase();\n }\n if (vendor) {\n gpuInfo.vendor = vendor.toLowerCase();\n }\n }\n this.gpuInfo = gpuInfo;\n return gpuInfo;\n }\n}\n\nfunction resizeCanvasIfNeeded(pipelineState: TWebGLPipelineState): void {\n const targetCanvas = pipelineState.targetCanvas,\n width = targetCanvas.width,\n height = targetCanvas.height,\n dWidth = pipelineState.destinationWidth,\n dHeight = pipelineState.destinationHeight;\n\n if (width !== dWidth || height !== dHeight) {\n targetCanvas.width = dWidth;\n targetCanvas.height = dHeight;\n }\n}\n"],"names":["WebGLFilterBackend","constructor","tileSize","config","textureSize","arguments","length","undefined","_defineProperty","this","Float32Array","setupGLContext","captureGPUInfo","width","height","dispose","createWebGLCanvas","canvas","createCanvasElementFor","gl","getContext","alpha","premultipliedAlpha","depth","stencil","antialias","clearColor","applyFilters","filters","source","targetCanvas","cacheKey","ctx","cachedTexture","getCachedTexture","pipelineState","originalWidth","naturalWidth","originalHeight","naturalHeight","sourceWidth","sourceHeight","destinationWidth","destinationHeight","context","sourceTexture","createTexture","targetTexture","originalTexture","passes","webgl","aPosition","programCache","pass","filterBackend","tempFbo","createFramebuffer","bindFramebuffer","FRAMEBUFFER","forEach","filter","applyTo","dWidth","dHeight","resizeCanvasIfNeeded","copyGLTo2D","bindTexture","TEXTURE_2D","deleteTexture","deleteFramebuffer","setTransform","clearWebGLCaches","textureCache","textureImageSource","NEAREST","RGBA","UNSIGNED_BYTE","CLAMP_TO_EDGE","TEXTURE_MAG_FILTER","TEXTURE_MIN_FILTER","TEXTURE_WRAP_S","TEXTURE_WRAP_T","texture","texParameteri","texImage2D","uniqueId","evictCachesForKey","glCanvas","translate","scale","sourceY","drawImage","copyGLTo2DPutImageData","numBytes","u8","Uint8Array","imageBuffer","u8Clamped","Uint8ClampedArray","readPixels","imgData","ImageData","putImageData","gpuInfo","renderer","vendor","ext","getExtension","getParameter","UNMASKED_RENDERER_WEBGL","UNMASKED_VENDOR_WEBGL","toLowerCase"],"mappings":"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"}