Extensions

Source: renderers/FilterRenderer.js

import { noop } from "../../extensions/utils/noop";
import { Utils } from "../core/Utils";
import { BlendMode } from "../rendering/BlendMode";
import { Framebuffer } from "../textures/Framebuffer";
import {
  BASE_VERTEX_SHADER,
  BASE_VERTEX_SHADER_INITIALIZATION,
  BASE_VERTEX_SHADER_POSITION,
} from "../utils/shaderUtils";
import { BaseRenderer } from "./BaseRenderer";

/**
 * @typedef {Object} FilterRendererConfig
 * @extends {RendererConfig}
 * @property {Array<BaseFilter>} filters
 * @property {TextureInfo} sourceTexture
 */

/**
 * <pre>
 *  Filter renderer
 *    - Renders filters to a source image
 * </pre>
 * @extends {BaseRenderer}
 */
export class FilterRenderer extends BaseRenderer {
  /**
   * Creates an instance of FilterRenderer.
   * @constructor
   * @param {FilterRendererConfig} config
   */
  constructor(config = {}) {
    config = Utils.initRendererConfig(config);

    // prettier-ignore
    Utils.setLocations(config, [
      "uF",
      "uH",
      "uI",
      "uJ",
      "uK",
      "uL",
    ]);

    super(config);

    this._attachFramebufferAndClearFv = this.$attachFramebufferAndClear;
    this.$attachFramebufferAndClear = noop;

    this.filters = config.filters || [];
    this.sourceTexture = config.sourceTexture;

    this._framebuffers = [new Framebuffer(), new Framebuffer()];
  }

  get filters() {
    return this._filters;
  }

  set filters(v) {
    this._filters = new Proxy(v, {
      deleteProperty: (target, property) => {
        delete target[property];
        this._rendererId++;
        return true;
      },
      set: (target, property, value) => {
        target[property] = value;
        this._rendererId++;
        return true;
      },
    });
    this._rendererId++;
  }

  /**
   * @param {Framebuffer} framebuffer
   * @ignore
   */
  $render(framebuffer) {
    const { context, $gl, $renderTime, $locations, _filters } = this;
    const l = _filters.length || 1;
    const minL = l - 2;

    let i = -1;

    context.setBlendMode(BlendMode.NORMAL);

    this.$uploadBuffers();

    this.$useTextureAt(this.sourceTexture, $locations.uB, 0);

    $gl.uniform3f($locations.uF, this.width, this.height, $renderTime % 864e5);

    while (++i < l) {
      const filter = _filters[i];
      const useFilter = filter && filter.on;
      const isLast = i > minL;
      const filterTexture = useFilter && filter.texture;

      let filterFramebuffer;

      filterTexture && this.$useTextureAt(filterTexture, $locations.uH, 1);

      if (isLast) {
        framebuffer ? this._attachFramebufferAndClearFv(framebuffer) : $gl.uniform1f($locations.uA, 1);
      } else if (useFilter) {
        filterFramebuffer = this._framebuffers[i & 1];
        this._attachFramebufferAndClearFv(filterFramebuffer);
      }

      if (useFilter) {
        $gl.uniform1i($locations.uI, filter.uniqueId);
        $gl.uniform1fv($locations.uJ, filter.data);
        $gl.uniformMatrix2x4fv($locations.uL, false, filter.customData);
        $gl.uniformMatrix3fv($locations.uK, false, filter.kernels);
      }

      (useFilter || isLast) && this.$drawInstanced(1);

      filterTexture && context.deactivateTexture(filterTexture);

      if (filterFramebuffer) {
        filterFramebuffer.unbind($gl);
        context.useTextureAt(filterFramebuffer, 0, $renderTime);
      }
    }
  }

  // prettier-ignore
  /**
   * @returns {string}
   * @ignore
   */
  $createVertexShader() {
    return `${BASE_VERTEX_SHADER_INITIALIZATION}` +

    `out vec4 ` +
      `v0;` +

    `void main(){` +
      `${BASE_VERTEX_SHADER}` +
      
      `v0=vec4(pos.xy,${BASE_VERTEX_SHADER_POSITION});` +
    `}`;
  }

  // prettier-ignore
  /**
   * @returns {string}
   * @ignore
   */
  $createFragmentShader() {
    return `${Utils.GLSL.DEFINE.Z}` +
    `${Utils.GLSL.DEFINE.RADIANS_360}` +
    
    `in vec4 ` +
      `v0;` +
      
    `uniform float ` + 
      `uJ[10];` +
    `uniform int ` +
      `uI;` +
    `uniform vec3 ` +
      `uF;` +
    `uniform mat3 ` +
      `uK;` +
    `uniform mat2x4 ` +
      `uL;` +
    `uniform sampler2D ` +
      `uB,` +
      `uH;` +

    `out vec4 ` +
      `oCl;` +

    `${Utils.GLSL.RANDOM}` +
    
    `float gs(vec3 c){` + 
      `return .3*c.r+.59*c.g+.11*c.b;` +
    `}` +

    `void main(){` +
      `oCl=texture(uB,v0.zw);` +

      `vec4 ` +
        `tmpCl=oCl;` +

      `float ` +
        `v=uJ[0];` +

      `vec2 ` + 
        `ts=floor(uF.xy),` +
        `vol=v/ts;` +
      
      `ivec2 ` +
        `f=ivec2(floor(v0.zw*ts));` +
    
      `${this.filters.reduce((acc, item) => {
        let index = acc.findIndex((record) => record.GLSL === item.GLSL);
        if (index < 0) {
          index = acc.push(item) - 1;
        }
        item.uniqueId = index;
        return acc;
      }, []).map((item) => `if(uI==${item.uniqueId}){${item.GLSL}}`).join("else ")}` + 

      `vec2 d=abs(v0.zw-vec2(uJ[4],uJ[5]));` +
      
      `float ` + 
        `p=mix(2.,16.,uJ[8]),` +
        `ds=mix(pow(pow(d.x,p)+pow(d.y,p),1./p),max(d.x,d.y),smoothstep(.7,1.,uJ[5])),` +
        `dst=mix(` +
          `1.,` +
          `clamp(` +
            `pow(` +
              `clamp(ds,0.,1.)/uJ[7],` +
              `uJ[9]` +
            `),` +
            `0.,` +
            `1.` +
          `),` +
          `uJ[3]` +
        `),` +
        `mA=mix(dst,1.-dst,uJ[6]);` +

        `oCl=mix(tmpCl,oCl,mA*uJ[2]);` +
    `}`;
  }
}