import { noop } from "../../common/utils/noop";
import { Utils } from "../core/Utils";
import { BlendMode } from "../rendering/BlendMode";
import { Framebuffer } from "../textures/Framebuffer";
import { BaseRenderer } from "./BaseRenderer";
// Uniform data of a no-op pass: mix 0 keeps the source, size and transition stay finite.
const _PASSTHROUGH_DATA = new Float32Array([0, 0, 0, 0, 0.5, 0.5, 0, 1, 1, 1]);
/**
* @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;
// Flips only after a pass is drawn into an intermediate framebuffer, so consecutive drawn
// passes always alternate fb0 and fb1 and never write the texture they read.
let fbIndex = 0;
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[fbIndex];
fbIndex ^= 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);
} else if (isLast) {
// A disabled last filter must not reuse the uniforms of the previous pass.
$gl.uniform1i($locations.uI, -1);
$gl.uniform1fv($locations.uJ, _PASSTHROUGH_DATA);
}
(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 `${Utils.GLSL.BASE_VERTEX_SHADER_INITIALIZATION}` +
`out vec4 ` +
`v0;` +
`void main(){` +
`${Utils.GLSL.BASE_VERTEX_SHADER}` +
`v0=vec4(pos.xy,${Utils.GLSL.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=pow(pow(d.x,p)+pow(d.y,p),1./p),` +
`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]);` +
`}`;
}
}