UNPKG

@lightningjs/renderer

Version:
408 lines (389 loc) 15.3 kB
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