UNPKG

@luma.gl/shadertools

Version:

Shader module system for luma.gl

153 lines (138 loc) 4.4 kB
// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import type {ShaderModule} from './shader-module/shader-module'; import {initializeShaderModules} from './shader-module/shader-module'; import { AssembleShaderProps, GetUniformsFunc, assembleWGSLShader, assembleGLSLShaderPair } from './shader-assembly/assemble-shaders'; import {preprocess} from './preprocessor/preprocessor'; /** * A stateful version of `assembleShaders` that can be used to assemble shaders. * Supports setting of default modules and hooks. */ export class ShaderAssembler { /** Default ShaderAssembler instance */ static defaultShaderAssembler: ShaderAssembler; /** Hook functions */ private readonly _hookFunctions: any[] = []; /** Shader modules */ private _defaultModules: ShaderModule[] = []; /** * A default shader assembler instance - the natural place to register default modules and hooks * @returns */ static getDefaultShaderAssembler(): ShaderAssembler { ShaderAssembler.defaultShaderAssembler = ShaderAssembler.defaultShaderAssembler || new ShaderAssembler(); return ShaderAssembler.defaultShaderAssembler; } /** * Add a default module that does not have to be provided with every call to assembleShaders() */ addDefaultModule(module: ShaderModule): void { if ( !this._defaultModules.find( m => m.name === (typeof module === 'string' ? module : module.name) ) ) { this._defaultModules.push(module); } } /** * Remove a default module */ removeDefaultModule(module: ShaderModule): void { const moduleName = typeof module === 'string' ? module : module.name; this._defaultModules = this._defaultModules.filter(m => m.name !== moduleName); } /** * Register a shader hook * @param hook * @param opts */ addShaderHook(hook: string, opts?: any): void { if (opts) { hook = Object.assign(opts, {hook}); } this._hookFunctions.push(hook); } /** * Assemble a WGSL unified shader * @param platformInfo * @param props * @returns */ assembleWGSLShader(props: AssembleShaderProps): { source: string; getUniforms: GetUniformsFunc; modules: ShaderModule[]; } { const modules = this._getModuleList(props.modules); // Combine with default modules const hookFunctions = this._hookFunctions; // TODO - combine with default hook functions const {source, getUniforms} = assembleWGSLShader({ ...props, // @ts-expect-error source: props.source, modules, hookFunctions }); // WGSL does not have built-in preprocessing support (just compile time constants) const preprocessedSource = props.platformInfo.shaderLanguage === 'wgsl' ? preprocess(source) : source; return {source: preprocessedSource, getUniforms, modules}; } /** * Assemble a pair of shaders into a single shader program * @param platformInfo * @param props * @returns */ assembleGLSLShaderPair(props: AssembleShaderProps): { vs: string; fs: string; getUniforms: GetUniformsFunc; modules: ShaderModule[]; } { const modules = this._getModuleList(props.modules); // Combine with default modules const hookFunctions = this._hookFunctions; // TODO - combine with default hook functions const assembled = assembleGLSLShaderPair({ ...props, // @ts-expect-error vs: props.vs, // @ts-expect-error fs: props.fs, modules, hookFunctions }); return {...assembled, modules}; } /** * Dedupe and combine with default modules */ _getModuleList(appModules: ShaderModule[] = []): ShaderModule[] { const modules = new Array<ShaderModule>(this._defaultModules.length + appModules.length); const seen: Record<string, boolean> = {}; let count = 0; for (let i = 0, len = this._defaultModules.length; i < len; ++i) { const module = this._defaultModules[i]; const name = module.name; modules[count++] = module; seen[name] = true; } for (let i = 0, len = appModules.length; i < len; ++i) { const module = appModules[i]; const name = module.name; if (!seen[name]) { modules[count++] = module; seen[name] = true; } } modules.length = count; initializeShaderModules(modules); return modules; } }