UNPKG

@luma.gl/shadertools

Version:

Shader module system for luma.gl

225 lines (192 loc) 6.78 kB
// luma.gl // SPDX-License-Identifier: MIT // Copyright (c) vis.gl contributors import {UniformFormat} from '../../types'; import { PropType, PropValidator, makePropValidators, getValidatedProperties } from '../filters/prop-types'; import type {UniformTypes, UniformValue} from '../utils/uniform-types'; import {ShaderInjection, normalizeInjections} from '../shader-assembly/shader-injections'; // To avoid dependency on core module, do not import `Binding` type. // The ShaderModule is not concerned with the type of `Binding`, // it is the repsonsibility of `splitUniformsAndBindings` in // ShaderInputs to type the result of `getUniforms()` type Binding = unknown; // import type {Binding} from '@luma.gl/core'; export type UniformInfo = { format?: UniformFormat; } & PropType; // Helper types type BindingKeys<T> = {[K in keyof T]: T[K] extends UniformValue ? never : K}[keyof T]; type UniformKeys<T> = {[K in keyof T]: T[K] extends UniformValue ? K : never}[keyof T]; export type PickBindings<T> = {[K in BindingKeys<Required<T>>]: T[K]}; export type PickUniforms<T> = {[K in UniformKeys<Required<T>>]: T[K]}; /** * A shader module definition object * * @note Needs to be initialized with `initializeShaderModules` * @note `UniformsT` & `BindingsT` are deduced from `PropsT` by default. If * a custom type for `UniformsT` is used, `BindingsT` should be also be provided. */ export type ShaderModule< PropsT extends Record<string, any> = Record<string, any>, UniformsT extends Record<string, UniformValue> = PickUniforms<PropsT>, BindingsT extends Record<string, Binding> = PickBindings<PropsT> > = { /** Used for type inference not for values */ props?: PropsT; /** Used for type inference, not currently used for values */ uniforms?: UniformsT; /** Used for type inference, not currently used for values */ bindings?: BindingsT; name: string; /** WGSL code */ source?: string; /** GLSL fragment shader code */ fs?: string; /** GLSL vertex shader code */ vs?: string; /** Uniform shader types @note: Both order and types MUST match uniform block declarations in shader */ uniformTypes?: Required<UniformTypes<UniformsT>>; // Record<keyof UniformsT, UniformFormat>; /** Uniform JS prop types */ propTypes?: Record<keyof UniformsT, UniformInfo>; /** Default uniform values */ defaultUniforms?: Required<UniformsT>; // Record<keyof UniformsT, UniformValue>; /** Function that maps props to uniforms & bindings */ getUniforms?: ( props: Partial<PropsT>, prevUniforms?: UniformsT ) => Partial<UniformsT & BindingsT>; defines?: Record<string, string | number>; /** Injections */ inject?: Record<string, string | {injection: string; order: number}>; dependencies?: ShaderModule<any, any>[]; /** Information on deprecated properties */ deprecations?: ShaderModuleDeprecation[]; /** The instance field contains information that is generated at run-time */ instance?: { propValidators?: Record<string, PropValidator>; parsedDeprecations: ShaderModuleDeprecation[]; normalizedInjections: { vertex: Record<string, ShaderInjection>; fragment: Record<string, ShaderInjection>; }; }; }; /** Use to generate deprecations when shader module is used */ export type ShaderModuleDeprecation = { type: string; regex?: RegExp; new: string; old: string; deprecated?: boolean; }; // SHNDER MODULE API export function initializeShaderModules(modules: ShaderModule[]): void { modules.map((module: ShaderModule) => initializeShaderModule(module)); } export function initializeShaderModule(module: ShaderModule): void { if (module.instance) { return; } initializeShaderModules(module.dependencies || []); const { propTypes = {}, deprecations = [], // defines = {}, inject = {} } = module; const instance: Required<ShaderModule>['instance'] = { normalizedInjections: normalizeInjections(inject), parsedDeprecations: parseDeprecationDefinitions(deprecations) }; if (propTypes) { instance.propValidators = makePropValidators(propTypes); } module.instance = instance; // TODO(ib) - we need to apply the original prop types to the default uniforms let defaultProps: ShaderModule['props'] = {}; if (propTypes) { defaultProps = Object.entries(propTypes).reduce( (obj: ShaderModule['props'], [key, propType]) => { // @ts-expect-error const value = propType?.value; if (value) { // @ts-expect-error obj[key] = value; } return obj; }, {} as ShaderModule['props'] ); } module.defaultUniforms = {...module.defaultUniforms, ...defaultProps} as any; } /** Convert module props to uniforms */ export function getShaderModuleUniforms< ShaderModuleT extends ShaderModule<Record<string, unknown>, Record<string, UniformValue>> >( module: ShaderModuleT, props?: ShaderModuleT['props'], oldUniforms?: ShaderModuleT['uniforms'] ): Record<string, Binding | UniformValue> { initializeShaderModule(module); const uniforms = oldUniforms || {...module.defaultUniforms}; // If module has a getUniforms function, use it if (props && module.getUniforms) { return module.getUniforms(props, uniforms); } // Build uniforms from the uniforms array // @ts-expect-error return getValidatedProperties(props, module.instance?.propValidators, module.name); } /* TODO this looks like it was unused code _defaultGetUniforms(opts: Record<string, any> = {}): Record<string, any> { const uniforms: Record<string, any> = {}; const propTypes = this.uniforms; for (const key in propTypes) { const propDef = propTypes[key]; if (key in opts && !propDef.private) { if (propDef.validate) { assert(propDef.validate(opts[key], propDef), `${this.name}: invalid ${key}`); } uniforms[key] = opts[key]; } else { uniforms[key] = propDef.value; } } return uniforms; } } */ // Warn about deprecated uniforms or functions export function checkShaderModuleDeprecations( shaderModule: ShaderModule, shaderSource: string, log: any ): void { shaderModule.deprecations?.forEach(def => { if (def.regex?.test(shaderSource)) { if (def.deprecated) { log.deprecated(def.old, def.new)(); } else { log.removed(def.old, def.new)(); } } }); } // HELPERS function parseDeprecationDefinitions(deprecations: ShaderModuleDeprecation[]) { deprecations.forEach(def => { switch (def.type) { case 'function': def.regex = new RegExp(`\\b${def.old}\\(`); break; default: def.regex = new RegExp(`${def.type} ${def.old};`); } }); return deprecations; }