diff --git a/docs/api-reference/shadertools/shader-module.md b/docs/api-reference/shadertools/shader-module.md index e0ff0c857..6c4dc45de 100644 --- a/docs/api-reference/shadertools/shader-module.md +++ b/docs/api-reference/shadertools/shader-module.md @@ -10,14 +10,17 @@ For more information see [Shader Module System Guide](/docs/api-guide/shaders/sh To define a new shader module, you create a descriptor object that brings together all the necessary pieces: ```typescript -export const MY_SHADER_MODULE = { +type ModuleProps = { + radius: number; +} + +export const MY_SHADER_MODULE: ShaderModule = { name: 'my-shader-module', vs: '...', fs: '...', inject: {}, dependencies: [], deprecations: [], - getUniforms }; ``` @@ -36,7 +39,15 @@ export const MY_SHADER_MODULE = { - `fs` - (string | null) -#### `uniforms` (_Object_) +#### `uniformTypes` (_Object_) - Uniform shader types (note: both order and types MUST match uniform block declarations in shader) + +#### `uniformPropTypes` (_Object_) - Uniform JS prop types + +#### `defaultUniforms` (_Object_) - Default uniform values + +#### `getUniforms` (_function_) - Function that maps props to uniforms & bindings. When not provided it is assumed that the names of the uniforms and bindings match those of the provided props + +#### `defines` (_Object_) - Constant defines to be injected into shader #### `inject` (_Object_) - injections the module will make into shader hooks, see below @@ -52,33 +63,36 @@ If `deprecations` is supplied, `assembleShaders` will scan shader source code fo - `deprecated`: whether the old API is still supported. -### Defining Uniforms +### Statically defining Uniforms -If the uniforms of this module can be directly pulled from user settings, they may declaratively defined by a `uniforms` object: +If the uniforms of this module can be directly pulled from user props, they may declaratively defined by a `defaultUniforms` object: ```typescript { name: 'my-shader-module', - uniforms: { - strength: {type: 'number', value: 1, min: 0, max: 1}, - center: [0.5, 0.5] - } + defaultUniforms: {center: [0.5, 0.5], strength: 0.9}, + uniformTypes: {center: 'vec2' strength: 'f32'} } ``` -At runtime, this map will be used to generate the uniforms needed by the shaders. If either `strength` or `center` is present in the user's module settings, then the user's value will be used; otherwise, the default value in the original definition will be used. - -Each uniform definition may contain the following fields: +At runtime, this map will be used to generate the uniforms needed by the shaders. If either `strength` or `center` is present in the user's module props, then the user's value will be used; otherwise, the default value in the original definition will be used. -- `type` (string `number`, `boolean`, `array` or `object` -- `value` - the default value of this uniform +### Dynamically defining Uniforms -With `type: 'number'`, the following additional fields may be added for validation: +The shader module may want to perform more complex logic when mapping the user' module props to uniforms. This can be achieved using `getUniforms()`: -- `min` (_Number_) -- `max` (_Number_) - -Note: `uniforms` is ignored if `getUniforms` is provided. +```typescript +{ + name: 'my-shader-module', + uniformTypes: {center: 'vec2' strength: 'f32'} + getUniforms(({intensity}) => { + return { + strength: Math.sqrt(intensity), + center: intensity > 0 ? [0.5, 0.5] : [0, 0] + } + } +} +``` ## Defining Injections @@ -111,7 +125,7 @@ getShaderModuleUniforms(module: ShaderModule) Each shader module provides a method to get a map of uniforms for the shader. This function will be called with two arguments: -- `opts` - the module settings to update. This argument may not be provided when `getUniforms` is called to generate a set of default uniform values. +- `opts` - the module props to update. This argument may not be provided when `getUniforms` is called to generate a set of default uniform values. - `context` - the uniforms generated by this module's dependencies. The function should return a JavaScript object with keys representing uniform names and values representing uniform values. diff --git a/modules/engine/src/model/model.ts b/modules/engine/src/model/model.ts index f256f599f..1b981853a 100644 --- a/modules/engine/src/model/model.ts +++ b/modules/engine/src/model/model.ts @@ -524,6 +524,7 @@ export class Model { /** Update uniform buffers from the model's shader inputs */ updateShaderInputs(): void { this._uniformStore.setUniforms(this.shaderInputs.getUniformValues()); + this.setBindings(this.shaderInputs.getBindings()); // TODO - this is already tracked through buffer/texture update times? this.setNeedsRedraw('shaderInputs'); } diff --git a/modules/engine/src/shader-inputs.ts b/modules/engine/src/shader-inputs.ts index 18cb4869a..d16f65a87 100644 --- a/modules/engine/src/shader-inputs.ts +++ b/modules/engine/src/shader-inputs.ts @@ -6,6 +6,7 @@ import type {UniformValue, Texture, Sampler} from '@luma.gl/core'; import {log} from '@luma.gl/core'; // import type {ShaderUniformType, UniformValue, UniformFormat, UniformInfoDevice, Texture, Sampler} from '@luma.gl/core'; import {getShaderModuleDependencies, ShaderModule} from '@luma.gl/shadertools'; +import {splitUniformsAndBindings} from './model/split-uniforms-and-bindings'; /** Minimal ShaderModule subset, we don't need shader code etc */ export type ShaderModuleInputs< @@ -103,17 +104,16 @@ export class ShaderInputs< } const oldUniforms = this.moduleUniforms[moduleName]; - const uniforms = + const oldBindings = this.moduleBindings[moduleName]; + const uniformsAndBindings = module.getUniforms?.(moduleProps, this.moduleUniforms[moduleName]) || (moduleProps as any); - // console.error(uniforms) + + const {uniforms, bindings} = splitUniformsAndBindings(uniformsAndBindings); this.moduleUniforms[moduleName] = {...oldUniforms, ...uniforms}; + this.moduleBindings[moduleName] = {...oldBindings, ...bindings}; // // this.moduleUniformsChanged ||= moduleName; // console.log(`setProps(${String(moduleName)}`, moduleName, this.moduleUniforms[moduleName]) - - // TODO - Get Module bindings - // const bindings = module.getBindings?.(moduleProps); - // this.moduleUniforms[moduleName] = bindings; } } diff --git a/modules/engine/test/shader-inputs.spec.ts b/modules/engine/test/shader-inputs.spec.ts index 58f9e85bc..ca41fd5a2 100644 --- a/modules/engine/test/shader-inputs.spec.ts +++ b/modules/engine/test/shader-inputs.spec.ts @@ -3,7 +3,8 @@ // Copyright (c) vis.gl contributors import test from 'tape-promise/tape'; -import {picking} from '../../shadertools/src/index'; +import {Texture} from '../../core/src/index'; +import {picking, ShaderModule} from '../../shadertools/src/index'; // import {_ShaderInputs as ShaderInputs} from '@luma.gl/engine'; import {ShaderInputs} from '../src/shader-inputs'; @@ -62,3 +63,36 @@ test('ShaderInputs#picking prop merge', t => { t.end(); }); + +test('ShaderInputs#bindings', t => { + [true, false].map(callback => { + t.comment(`custom module created ${callback ? 'with' : 'without'} getUniforms()`); + type CustomProps = {color: number[]; colorTexture: Texture}; + const custom: ShaderModule = { + name: 'custom', + uniformTypes: {color: 'vec3'}, + uniformPropTypes: {color: {value: [0, 0, 0]}} + }; + if (callback) { + custom.getUniforms = ({color, colorTexture}) => ({color, colorTexture}); + } + + const shaderInputs = new ShaderInputs<{ + custom: CustomProps; + }>({custom}); + + const MOCK_TEXTURE = 'MOCK_TEXTURE' as unknown as Texture; + shaderInputs.setProps({ + custom: {color: [255, 0, 0], colorTexture: MOCK_TEXTURE} + }); + t.deepEqual(shaderInputs.moduleUniforms.custom.color, [255, 0, 0], 'custom color updated'); + t.equal(shaderInputs.moduleBindings.custom.colorTexture, MOCK_TEXTURE, 'colorTexture updated'); + + const uniformValues = shaderInputs.getUniformValues(); + const bindings = shaderInputs.getBindings(); + t.deepEqual(uniformValues, {custom: {color: [255, 0, 0]}}, 'uniformValues correct'); + t.deepEqual(bindings, {colorTexture: 'MOCK_TEXTURE'}, 'bindings correct'); + + t.end(); + }); +}); diff --git a/modules/shadertools/src/lib/shader-module/shader-module.ts b/modules/shadertools/src/lib/shader-module/shader-module.ts index f361417ad..366e59fec 100644 --- a/modules/shadertools/src/lib/shader-module/shader-module.ts +++ b/modules/shadertools/src/lib/shader-module/shader-module.ts @@ -3,6 +3,7 @@ // Copyright (c) vis.gl contributors import type {NumberArray} from '@math.gl/types'; +import {Sampler, Texture} from '@luma.gl/core'; import type {UniformFormat} from '../../types'; import {PropType, PropValidator} from '../filters/prop-types'; import {ShaderInjection} from '../shader-assembly/shader-injections'; @@ -17,12 +18,13 @@ export type UniformInfo = { /** * A shader module definition object + * * @note Needs to be initialized with `initializeShaderModules` */ export type ShaderModule< PropsT extends Record = Record, UniformsT extends Record = Record, - BindingsT extends Record = {} + BindingsT extends Record = {} > = { /** Used for type inference not for values */ props?: PropsT; @@ -45,9 +47,8 @@ export type ShaderModule< /** Default uniform values */ defaultUniforms?: Required; // Record; - /** Function that maps settings to uniforms */ - // getUniforms?: (settings?: Partial, prevUniforms?: any /* UniformsT */) => UniformsT; - getUniforms?: (settings?: any, prevUniforms?: any) => Record; + /** Function that maps props to uniforms & bindings */ + getUniforms?: (props?: any, oldProps?: any) => Record; /** uniform buffers, textures, samplers, storage, ... */ bindings?: Record;