@lightningjs/renderer
Version:
Lightning 3 Renderer
408 lines (389 loc) • 15.3 kB
JavaScript
import { WebGlCoreShader, } from '../WebGlCoreShader.js';
import {} from '../internal/ShaderUtils.js';
import { ShaderEffect, } from './effects/ShaderEffect.js';
import { assertTruthy } from '../../../../utils.js';
const effectCache = new Map();
const getResolvedEffect = (effects, effectContructors) => {
const key = JSON.stringify(effects);
if (effectCache.has(key)) {
return effectCache.get(key);
}
effects = effects ?? [];
const resolvedEffects = [];
const effectsLength = effects.length;
let i = 0;
for (; i < effectsLength; i++) {
const { name, type, props } = effects[i];
const resolvedEffect = {
name,
type,
props: {},
};
const effectConstructor = effectContructors[type];
const defaultPropValues = effectConstructor.resolveDefaults(props);
const uniforms = effectConstructor.uniforms;
const uniformKeys = Object.keys(uniforms);
const uniformsLength = uniformKeys.length;
let j = 0;
for (; j < uniformsLength; j++) {
const key = uniformKeys[j];
const uniform = uniforms[key];
const result = {
value: defaultPropValues[key],
programValue: undefined,
method: uniform.method,
updateOnBind: uniform.updateOnBind || false,
hasValidator: uniform.validator !== undefined,
hasProgramValueUpdater: uniform.updateProgramValue !== undefined,
};
const validatedValue = (result.hasValidator &&
uniform.validator(defaultPropValues[key], defaultPropValues)) ||
defaultPropValues[key];
if (defaultPropValues[key] !== validatedValue) {
result.validatedValue = validatedValue;
}
if (result.hasProgramValueUpdater) {
uniform.updateProgramValue(result);
}
if (result.programValue === undefined) {
result.programValue = result.value;
}
resolvedEffect.props[key] = result;
}
resolvedEffects.push(resolvedEffect);
}
effectCache.set(key, resolvedEffects);
return resolvedEffects;
};
export class DynamicShader extends WebGlCoreShader {
effects = [];
constructor(renderer, props, effectContructors) {
const shader = DynamicShader.createShader(props, effectContructors);
super({
renderer,
shaderSources: {
vertex: shader.vertex,
fragment: shader.fragment,
},
});
this.effects = shader.effects;
}
bindTextures(textures) {
const { glw } = this;
glw.activeTexture(0);
glw.bindTexture(textures[0].ctxTexture);
}
bindUniformMethods(props) {
const glw = this.glw;
const effects = props.effects;
const effectsL = effects.length;
for (let i = 0; i < effectsL; i++) {
const uniformInfo = this.effects[i].uniformInfo;
const effect = effects[i];
const propKeys = Object.keys(effect.props);
const propsLength = propKeys.length;
for (let j = 0; j < propsLength; j++) {
const key = propKeys[j];
const method = effect.props[key].method;
const location = this.getUniformLocation(uniformInfo[key].name);
if (method === 'uniform2fv' ||
method === 'uniform2iv' ||
//uniform === 'uniform3fv ' || <--- check why this isnt recognized
method === 'uniform3iv' ||
method === 'uniform4fv' ||
method === 'uniform4iv' ||
method === 'uniformMatrix2fv' ||
method === 'uniformMatrix3fv' ||
method === 'uniformMatrix4fv' ||
method === 'uniform1f' ||
method === 'uniform1fv' ||
method === 'uniform1i' ||
method === 'uniform1iv') {
effect.props[key].setUniformValue = function () {
glw[method](location, this.programValue);
};
continue;
}
if (method === 'uniform2f' || method === 'uniform2i') {
effect.props[key].setUniformValue = function () {
glw[method](location, this.programValue[0], this.programValue[1]);
};
continue;
}
if (method === 'uniform3f' || method === 'uniform3i') {
effect.props[key].setUniformValue = function () {
glw[method](location, this.programValue[0], this.programValue[1], this.programValue[2]);
};
continue;
}
if (method === 'uniform4f' || method === 'uniform4i') {
effect.props[key].setUniformValue = function () {
glw[method](location, this.programValue[0], this.programValue[1], this.programValue[2], this.programValue[3]);
};
continue;
}
}
}
}
bindProps(props) {
const effects = props.effects;
const effectsL = effects.length;
let i = 0;
for (; i < effectsL; i++) {
const effect = effects[i];
const propKeys = Object.keys(effect.props);
const propsLength = propKeys.length;
let j = 0;
for (; j < propsLength; j++) {
const key = propKeys[j];
const prop = effect.props[key];
if (prop.updateOnBind === true) {
const uniform = this.renderer.shManager.getRegisteredEffects()[effect.type]?.uniforms[key];
uniform?.updateProgramValue(effect.props[key], props);
}
prop.setUniformValue();
}
}
}
canBatchShaderProps(propsA, propsB) {
if (propsA.$alpha !== propsB.$alpha ||
propsA.$dimensions.width !== propsB.$dimensions.width ||
propsA.$dimensions.height !== propsB.$dimensions.height ||
propsA.effects.length !== propsB.effects.length) {
return false;
}
const propsEffectsLen = propsA.effects.length;
let i = 0;
for (; i < propsEffectsLen; i++) {
const effectA = propsA.effects[i];
const effectB = propsB.effects[i];
if (effectA.type !== effectB.type) {
return false;
}
for (const key in effectA.props) {
if ((effectB.props && !effectB.props[key]) ||
effectA.props[key].value !==
effectB.props[key].value) {
return false;
}
}
}
return true;
}
static createShader(props, effectContructors) {
//counts duplicate effects
const effectNameCount = {};
const methods = {};
let declareUniforms = '';
const uniforms = [];
const uFx = [];
const effects = props.effects.map((effect) => {
const baseClass = effectContructors[effect.type];
const key = baseClass.getEffectKey(effect.props || {});
effectNameCount[key] = effectNameCount[key] ? ++effectNameCount[key] : 1;
const nr = effectNameCount[key];
if (nr === 1) {
uFx.push({ key, type: effect.type, props: effect.props });
}
//initialize new effect class;
const fxClass = new baseClass({
ref: `${key}${nr === 1 ? '' : nr}`,
target: key,
props: effect.props,
});
declareUniforms += fxClass.declaredUniforms;
uniforms.push(...Object.values(fxClass.uniformInfo));
return fxClass;
});
//build source
let effectMethods = '';
uFx?.forEach((fx) => {
const fxClass = effectContructors[fx.type];
const fxProps = fxClass.resolveDefaults((fx.props ?? {}));
const remap = [];
for (const m in fxClass.methods) {
let cm = m;
const fxMethod = fxClass.methods[m];
if (methods[m] && methods[m] !== fxMethod) {
cm = DynamicShader.resolveMethodDuplicate(m, fxMethod, methods);
}
methods[cm] = fxMethod.replace('function', cm);
remap.push({ m, cm });
}
let onShaderMask = fxClass.onShaderMask instanceof Function
? fxClass.onShaderMask(fxProps)
: fxClass.onShaderMask;
let onColorize = fxClass.onColorize instanceof Function
? fxClass.onColorize(fxProps)
: fxClass.onColorize;
let onEffectMask = fxClass.onEffectMask instanceof Function
? fxClass.onEffectMask(fxProps)
: fxClass.onEffectMask;
remap.forEach((r) => {
const { m, cm } = r;
const reg = new RegExp(`\\$${m}`, 'g');
if (onShaderMask) {
onShaderMask = onShaderMask.replace(reg, cm);
}
if (onColorize) {
onColorize = onColorize.replace(reg, cm);
}
if (onEffectMask) {
onEffectMask = onEffectMask.replace(reg, cm);
}
});
const methodParameters = fxClass.getMethodParameters(fxClass.uniforms, fxProps);
const pm = methodParameters.length > 0 ? `, ${methodParameters}` : '';
if (onShaderMask) {
effectMethods += `
float fx_${fx.key}_onShaderMask(float shaderMask ${pm}) {
${onShaderMask}
}
`;
}
if (onColorize) {
effectMethods += `
vec4 fx_${fx.key}_onColorize(float shaderMask, vec4 maskColor, vec4 shaderColor${pm}) {
${onColorize}
}
`;
}
if (onEffectMask) {
effectMethods += `
vec4 fx_${fx.key}_onEffectMask(float shaderMask, vec4 maskColor, vec4 shaderColor${pm}) {
${onEffectMask}
}
`;
}
});
let sharedMethods = '';
for (const m in methods) {
sharedMethods += methods[m];
}
//fill main functions
let currentMask = `mix(shaderColor, maskColor, clamp(-(lng_DefaultMask), 0.0, 1.0))`;
let drawEffects = `
`;
for (let i = 0; i < effects.length; i++) {
const current = effects[i];
const pm = current.passParameters.length > 0 ? `, ${current.passParameters}` : '';
const currentClass = effectContructors[current.name];
if (currentClass.onShaderMask) {
drawEffects += `
shaderMask = fx_${current.target}_onShaderMask(shaderMask ${pm});
`;
}
if (currentClass.onColorize) {
drawEffects += `
maskColor = fx_${current.target}_onColorize(shaderMask, maskColor, shaderColor${pm});
`;
}
if (currentClass.onEffectMask) {
currentMask = `fx_${current.target}_onEffectMask(shaderMask, maskColor, shaderColor${pm})`;
}
const next = effects[i + 1];
if (next === undefined ||
effectContructors[next.name].onEffectMask) {
drawEffects += `
shaderColor = ${currentMask};
`;
}
}
return {
effects,
uniforms,
fragment: DynamicShader.fragment(declareUniforms, sharedMethods, effectMethods, drawEffects),
vertex: DynamicShader.vertex(),
};
}
static resolveMethodDuplicate(key, effectMethod, methodCollection, increment = 0) {
const m = key + (increment > 0 ? increment : '');
if (methodCollection[m] && methodCollection[m] !== effectMethod) {
return this.resolveMethodDuplicate(key, effectMethod, methodCollection, ++increment);
}
return m;
}
static resolveDefaults(props, effectContructors) {
assertTruthy(effectContructors);
return {
effects: getResolvedEffect(props.effects ?? [], effectContructors),
$dimensions: {
width: 0,
height: 0,
},
$alpha: 0,
};
}
static makeCacheKey(props, effectContructors) {
let fx = '';
props.effects?.forEach((effect) => {
const baseClass = effectContructors[effect.type];
const key = baseClass.getEffectKey(effect.props || {});
fx += `,${key}`;
});
return `DynamicShader${fx}`;
}
static z$__type__Props;
static vertex = () => `
# ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
# else
precision mediump float;
# endif
attribute vec2 a_textureCoordinate;
attribute vec2 a_nodeCoordinate;
attribute vec2 a_position;
attribute vec4 a_color;
attribute float a_textureIndex;
uniform vec2 u_resolution;
uniform float u_pixelRatio;
varying vec4 v_color;
varying vec2 v_textureCoordinate;
varying float v_textureIndex;
varying vec2 v_nodeCoordinate;
void main(){
vec2 normalized = a_position * u_pixelRatio / u_resolution;
vec2 zero_two = normalized * 2.0;
vec2 clip_space = zero_two - 1.0;
// pass to fragment
v_color = a_color;
v_textureCoordinate = a_textureCoordinate;
v_textureIndex = a_textureIndex;
v_nodeCoordinate = a_nodeCoordinate;
// flip y
gl_Position = vec4(clip_space * vec2(1.0, -1.0), 0, 1);
}
`;
static fragment = (uniforms, methods, effectMethods, drawEffects) => `
# ifdef GL_FRAGMENT_PRECISION_HIGH
precision highp float;
# else
precision mediump float;
# endif
#define PI 3.14159265359
uniform vec2 u_resolution;
uniform vec2 u_dimensions;
uniform float u_alpha;
uniform float u_radius;
uniform sampler2D u_texture;
uniform float u_pixelRatio;
${uniforms}
varying vec4 v_color;
varying vec2 v_textureCoordinate;
varying vec2 v_nodeCoordinate;
${methods}
${effectMethods}
void main() {
vec2 p = v_nodeCoordinate.xy * u_dimensions - u_dimensions * 0.5;
vec2 d = abs(p) - (u_dimensions) * 0.5;
float lng_DefaultMask = min(max(d.x, d.y), 0.0) + length(max(d, 0.0));
vec4 shaderColor = vec4(0.0);
float shaderMask = lng_DefaultMask;
vec4 maskColor = texture2D(u_texture, v_textureCoordinate) * v_color;
shaderColor = mix(shaderColor, maskColor, clamp(-(lng_DefaultMask + 0.5), 0.0, 1.0));
${drawEffects}
gl_FragColor = shaderColor * u_alpha;
}
`;
}
//# sourceMappingURL=DynamicShader.js.map