@lightningjs/renderer
Version:
Lightning 3 Renderer
293 lines (269 loc) • 10.1 kB
text/typescript
/*
* If not stated otherwise in this file or this component's LICENSE file the
* following copyright and licenses apply:
*
* Copyright 2023 Comcast Cable Communications Management, LLC.
*
* Licensed under the Apache License, Version 2.0 (the License);
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import type { ExtractProps } from './CoreTextureManager.js';
import type { CoreRenderer } from './renderers/CoreRenderer.js';
import type { CoreShader } from './renderers/CoreShader.js';
import { DefaultShader } from './renderers/webgl/shaders/DefaultShader.js';
import { DefaultShaderBatched } from './renderers/webgl/shaders/DefaultShaderBatched.js';
import {
DynamicShader,
type DynamicShaderProps,
} from './renderers/webgl/shaders/DynamicShader.js';
import { RoundedRectangle } from './renderers/webgl/shaders/RoundedRectangle.js';
import { SdfShader } from './renderers/webgl/shaders/SdfShader.js';
import { RadiusEffect } from './renderers/webgl/shaders/effects/RadiusEffect.js';
import { BorderEffect } from './renderers/webgl/shaders/effects/BorderEffect.js';
import {
LinearGradientEffect,
type LinearGradientEffectProps,
} from './renderers/webgl/shaders/effects/LinearGradientEffect.js';
import {
GrayscaleEffect,
type GrayscaleEffectProps,
} from './renderers/webgl/shaders/effects/GrayscaleEffect.js';
import { BorderRightEffect } from './renderers/webgl/shaders/effects/BorderRightEffect.js';
import { BorderTopEffect } from './renderers/webgl/shaders/effects/BorderTopEffect.js';
import { BorderBottomEffect } from './renderers/webgl/shaders/effects/BorderBottomEffect.js';
import { BorderLeftEffect } from './renderers/webgl/shaders/effects/BorderLeftEffect.js';
import {
GlitchEffect,
type GlitchEffectProps,
} from './renderers/webgl/shaders/effects/GlitchEffect.js';
import {
FadeOutEffect,
type FadeOutEffectProps,
} from './renderers/webgl/shaders/effects/FadeOutEffect.js';
import {
RadialGradientEffect,
type RadialGradientEffectProps,
} from './renderers/webgl/shaders/effects/RadialGradientEffect.js';
import type { WebGlCoreRenderer } from './renderers/webgl/WebGlCoreRenderer.js';
import {
RadialProgressEffect,
type RadialProgressEffectProps,
} from './renderers/webgl/shaders/effects/RadialProgressEffect.js';
import {
HolePunchEffect,
type HolePunchEffectProps,
} from './renderers/webgl/shaders/effects/HolePunchEffect.js';
import { WebGlCoreShader } from './renderers/webgl/WebGlCoreShader.js';
import { UnsupportedShader } from './renderers/canvas/shaders/UnsupportedShader.js';
import { ShaderController } from '../main-api/ShaderController.js';
import {
DynamicShaderController,
type DynamicEffects,
} from '../main-api/DynamicShaderController.js';
export type { FadeOutEffectProps };
export type { LinearGradientEffectProps };
export type { RadialGradientEffectProps };
export type { GrayscaleEffectProps };
export type { GlitchEffectProps };
export type { RadialProgressEffectProps };
export type { HolePunchEffectProps };
export interface ShaderMap {
DefaultShader: typeof DefaultShader;
DefaultShaderBatched: typeof DefaultShaderBatched;
RoundedRectangle: typeof RoundedRectangle;
DynamicShader: typeof DynamicShader;
SdfShader: typeof SdfShader;
UnsupportedShader: typeof UnsupportedShader;
}
export interface EffectMap {
radius: typeof RadiusEffect;
border: typeof BorderEffect;
borderBottom: typeof BorderBottomEffect;
borderLeft: typeof BorderLeftEffect;
borderRight: typeof BorderRightEffect;
borderTop: typeof BorderTopEffect;
fadeOut: typeof FadeOutEffect;
linearGradient: typeof LinearGradientEffect;
radialGradient: typeof RadialGradientEffect;
grayscale: typeof GrayscaleEffect;
glitch: typeof GlitchEffect;
radialProgress: typeof RadialProgressEffect;
holePunch: typeof HolePunchEffect;
}
export type EffectProps =
| FadeOutEffectProps
| LinearGradientEffectProps
| RadialGradientEffectProps
| GrayscaleEffectProps
| GlitchEffectProps
| RadialProgressEffectProps
| HolePunchEffectProps;
export class CoreShaderManager {
protected shCache: Map<string, InstanceType<ShaderMap[keyof ShaderMap]>> =
new Map();
protected shConstructors: Partial<ShaderMap> = {};
protected attachedShader: CoreShader | null = null;
protected effectConstructors: Partial<EffectMap> = {};
renderer!: CoreRenderer;
constructor() {
this.registerShaderType('DefaultShader', DefaultShader);
this.registerShaderType('DefaultShaderBatched', DefaultShaderBatched);
this.registerShaderType('RoundedRectangle', RoundedRectangle);
this.registerShaderType('DynamicShader', DynamicShader);
this.registerShaderType('SdfShader', SdfShader);
this.registerEffectType('border', BorderEffect);
this.registerEffectType('borderBottom', BorderBottomEffect);
this.registerEffectType('borderLeft', BorderLeftEffect);
this.registerEffectType('borderRight', BorderRightEffect);
this.registerEffectType('borderTop', BorderTopEffect);
this.registerEffectType('fadeOut', FadeOutEffect);
this.registerEffectType('linearGradient', LinearGradientEffect);
this.registerEffectType('radialGradient', RadialGradientEffect);
this.registerEffectType('grayscale', GrayscaleEffect);
this.registerEffectType('glitch', GlitchEffect);
this.registerEffectType('radius', RadiusEffect);
this.registerEffectType('radialProgress', RadialProgressEffect);
this.registerEffectType('holePunch', HolePunchEffect);
}
registerShaderType<Type extends keyof ShaderMap>(
shType: Type,
shClass: ShaderMap[Type],
): void {
this.shConstructors[shType] = shClass;
}
registerEffectType<Type extends keyof EffectMap>(
effectType: Type,
effectClass: EffectMap[Type],
): void {
this.effectConstructors[effectType] = effectClass;
}
getRegisteredEffects(): Partial<EffectMap> {
return this.effectConstructors;
}
getRegisteredShaders(): Partial<ShaderMap> {
return this.shConstructors;
}
/**
* Loads a shader (if not already loaded) and returns a controller for it.
*
* @param shType
* @param props
* @returns
*/
loadShader<Type extends keyof ShaderMap>(
shType: Type,
props?: ExtractProps<ShaderMap[Type]>,
): ShaderController<Type> {
if (!this.renderer) {
throw new Error(`Renderer is not been defined`);
}
const ShaderClass = this.shConstructors[shType];
if (!ShaderClass) {
throw new Error(`Shader type "${shType as string}" is not registered`);
}
if (
this.renderer.mode === 'canvas' &&
ShaderClass.prototype instanceof WebGlCoreShader
) {
return this._createShaderCtr(
shType,
new UnsupportedShader(shType) as InstanceType<ShaderMap[Type]>,
props as ExtractProps<ShaderMap[Type]>,
);
}
if (shType === 'DynamicShader') {
return this.loadDynamicShader(
props as DynamicShaderProps,
) as unknown as ShaderController<Type>;
}
const resolvedProps = ShaderClass.resolveDefaults(
props as Record<string, unknown>,
);
const cacheKey =
ShaderClass.makeCacheKey(resolvedProps) || ShaderClass.name;
if (cacheKey && this.shCache.has(cacheKey)) {
return this._createShaderCtr(
shType,
this.shCache.get(cacheKey) as InstanceType<ShaderMap[Type]>,
resolvedProps as ExtractProps<ShaderMap[Type]>,
);
}
// @ts-expect-error ShaderClass WILL accept a Renderer
const shader = new ShaderClass(this.renderer, props) as InstanceType<
ShaderMap[Type]
>;
if (cacheKey) {
this.shCache.set(cacheKey, shader);
}
return this._createShaderCtr(
shType,
shader,
resolvedProps as ExtractProps<ShaderMap[Type]>,
);
}
loadDynamicShader<
T extends DynamicEffects<[...{ name?: string; type: keyof EffectMap }[]]>,
>(props: DynamicShaderProps): DynamicShaderController<T> {
if (!this.renderer) {
throw new Error(`Renderer is not been defined`);
}
const resolvedProps = DynamicShader.resolveDefaults(
props as Record<string, unknown>,
this.effectConstructors,
);
const cacheKey = DynamicShader.makeCacheKey(
resolvedProps,
this.effectConstructors,
);
if (cacheKey && this.shCache.has(cacheKey)) {
return this._createDynShaderCtr(
this.shCache.get(cacheKey) as InstanceType<ShaderMap['DynamicShader']>,
resolvedProps,
);
}
const shader = new DynamicShader(
this.renderer as WebGlCoreRenderer,
props,
this.effectConstructors,
);
if (cacheKey) {
this.shCache.set(cacheKey, shader);
}
return this._createDynShaderCtr(shader, resolvedProps);
}
private _createShaderCtr<Type extends keyof ShaderMap>(
type: Type,
shader: InstanceType<ShaderMap[Type]>,
props: ExtractProps<ShaderMap[Type]>,
): ShaderController<Type> {
return new ShaderController(type, shader, props, this.renderer.stage);
}
private _createDynShaderCtr<
T extends DynamicEffects<[...{ name?: string; type: keyof EffectMap }[]]>,
>(
shader: InstanceType<ShaderMap['DynamicShader']>,
props: ExtractProps<ShaderMap['DynamicShader']>,
): DynamicShaderController<T> {
shader.bindUniformMethods(props);
return new DynamicShaderController(shader, props, this);
}
useShader(shader: CoreShader): void {
if (this.attachedShader === shader) {
return;
}
if (this.attachedShader) {
this.attachedShader.detach();
}
shader.attach();
this.attachedShader = shader;
}
}