{"version":3,"file":"WebGLFilterBackend.min.mjs","names":[],"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>[],\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  ): WebGLTexture {\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    // disabled because website and issues with different typescript version\n    // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion\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"],"mappings":"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"}