From e98b88ccceafe494b218b130818f22db667b7a8f Mon Sep 17 00:00:00 2001 From: Ib Green Date: Fri, 1 Mar 2024 11:58:53 -0500 Subject: [PATCH 1/4] feat(shadertools): ShaderAssembler.assembleSingleShader/assembleShaderPair --- modules/engine/src/model/model.ts | 2 +- modules/shadertools/src/index.ts | 2 +- .../shadertools/src/lib/shader-assembler.ts | 38 +++++- .../lib/shader-assembly/assemble-shaders.ts | 112 +++++++++++------- .../src/lib/shader-assembly/select-shaders.ts | 2 + .../test/lib/shader-assembler.spec.ts | 26 ++-- .../shader-assembly/assemble-shaders.spec.ts | 48 ++++---- .../lib/shader-assembly/inject-shader.spec.ts | 6 +- 8 files changed, 146 insertions(+), 90 deletions(-) diff --git a/modules/engine/src/model/model.ts b/modules/engine/src/model/model.ts index 961726c508..ce9280dd7c 100644 --- a/modules/engine/src/model/model.ts +++ b/modules/engine/src/model/model.ts @@ -208,7 +208,7 @@ export class Model { const modules = (this.props.modules?.length > 0 ? this.props.modules : this.shaderInputs?.getModules()) || []; - const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleShaders({ + const {vs, fs, getUniforms} = this.props.shaderAssembler.assembleShaderPair({ platformInfo, ...this.props, modules diff --git a/modules/shadertools/src/index.ts b/modules/shadertools/src/index.ts index f1e5c1d269..34ccd9be8b 100644 --- a/modules/shadertools/src/index.ts +++ b/modules/shadertools/src/index.ts @@ -41,7 +41,7 @@ export {generateShaderForModule} from './lib/shader-generator/generate-shader'; export {capitalize} from './lib/shader-generator/utils/capitalize'; // TEST EXPORTS - Do not use in production applications -export {assembleShaders} from './lib/shader-assembly/assemble-shaders'; +export {assembleShaderPairGLSL} from './lib/shader-assembly/assemble-shaders'; export {ShaderModuleInstance as _ShaderModuleInstance} from './lib/shader-module/shader-module-instance'; export {combineInjects} from './lib/shader-assembly/shader-injections'; export {resolveModules as _resolveModules} from './lib/shader-assembly/resolve-modules'; diff --git a/modules/shadertools/src/lib/shader-assembler.ts b/modules/shadertools/src/lib/shader-assembler.ts index 6cbafe3481..6d37c19cfe 100644 --- a/modules/shadertools/src/lib/shader-assembler.ts +++ b/modules/shadertools/src/lib/shader-assembler.ts @@ -4,8 +4,13 @@ import type {ShaderModule} from './shader-module/shader-module'; import {ShaderModuleInstance} from './shader-module/shader-module-instance'; -import {GetUniformsFunc, assembleShaders} from './shader-assembly/assemble-shaders'; import {selectShaders, AssembleShaderProps} from './shader-assembly/select-shaders'; +import { + GetUniformsFunc, + assembleSingleShaderWGSL, + assembleShaderPairWGSL, + assembleShaderPairGLSL +} from './shader-assembly/assemble-shaders'; /** * A stateful version of `assembleShaders` that can be used to assemble shaders. @@ -68,16 +73,15 @@ export class ShaderAssembler { * @param props * @returns */ - assembleShaders(props: AssembleShaderProps): { - vs: string; - fs: string; + assembleSingleShader(props: AssembleShaderProps): { + source: string; getUniforms: GetUniformsFunc; modules: ShaderModuleInstance[]; } { const modules = this._getModuleList(props.modules); // Combine with default modules const hookFunctions = this._hookFunctions; // TODO - combine with default hook functions const options = selectShaders(props); - const assembled = assembleShaders({ + const assembled = assembleSingleShaderWGSL({ platformInfo: props.platformInfo, ...options, modules, @@ -86,6 +90,30 @@ export class ShaderAssembler { return {...assembled, modules}; } + /** + * Assemble a pair of shaders into a single shader program + * @param platformInfo + * @param props + * @returns + */ + assembleShaderPair(props: AssembleShaderProps): { + vs: string; + fs: string; + getUniforms: GetUniformsFunc; + modules: ShaderModuleInstance[]; + } { + const options = selectShaders(props); + const modules = this._getModuleList(props.modules); // Combine with default modules + const hookFunctions = this._hookFunctions; // TODO - combine with default hook functions + const {platformInfo} = props; + const isWGSL = props.platformInfo.shaderLanguage === 'wgsl'; + const assembled = isWGSL + ? assembleShaderPairWGSL({platformInfo, ...options, modules, hookFunctions}) + : assembleShaderPairGLSL({platformInfo, ...options, modules, hookFunctions}); + + return {...assembled, modules}; + } + /** * Dedupe and combine with default modules */ diff --git a/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts b/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts index 110d1d51ed..c2f85ffb06 100644 --- a/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts +++ b/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts @@ -35,12 +35,12 @@ export type AssembleShaderOptions = { platformInfo: PlatformInfo; /** Inject shader id #defines */ id?: string; + /** Single WGSL shader */ + source?: string; /** Vertex shader */ - vs: string; + vs?: string; /** Fragment shader */ - fs: string; - /** Shader type @deprecated do we still need this */ - // type?: any; + fs?: string; /** Modules to be injected */ modules?: (ShaderModule | ShaderModuleInstance)[]; /** Defines to be injected */ @@ -81,51 +81,77 @@ type AssembleStageOptions = { export type GetUniformsFunc = (opts: Record) => Record; /** - * Inject a list of shader modules into shader sources + * Inject a list of shader modules into a single shader source for WGSL + */ +export function assembleSingleShaderWGSL(options: AssembleShaderOptions): { + source: string; + getUniforms: GetUniformsFunc; +} { + const modules = resolveModules(options.modules || []); + return { + source: assembleWGSLShader(options.platformInfo, { + ...options, + source: options.source, + stage: 'vertex', + modules + }), + getUniforms: assembleGetUniforms(modules) + }; +} + +/** + * Injects dependent shader module sources into pair of main vertex/fragment shader sources for WGSL */ -export function assembleShaders(options: AssembleShaderOptions): { +export function assembleShaderPairWGSL(options: AssembleShaderOptions): { vs: string; fs: string; getUniforms: GetUniformsFunc; } { - const {vs, fs} = options; const modules = resolveModules(options.modules || []); - switch (options.platformInfo.shaderLanguage) { - case 'glsl': - return { - vs: assembleGLSLShader(options.platformInfo, { - ...options, - source: vs, - stage: 'vertex', - modules - }), - fs: assembleGLSLShader(options.platformInfo, { - ...options, - source: fs, - stage: 'fragment', - modules - }), - getUniforms: assembleGetUniforms(modules) - }; + return { + vs: assembleWGSLShader(options.platformInfo, { + ...options, + source: options.vs, + stage: 'vertex', + modules + }), + fs: assembleWGSLShader(options.platformInfo, { + ...options, + source: options.fs, + stage: 'fragment', + modules + }), + getUniforms: assembleGetUniforms(modules) + }; +} - case 'wgsl': - return { - vs: assembleWGSLShader(options.platformInfo, { - ...options, - source: vs, - stage: 'vertex', - modules - }), - fs: assembleWGSLShader(options.platformInfo, { - ...options, - source: fs, - stage: 'fragment', - modules - }), - getUniforms: assembleGetUniforms(modules) - }; - } +/** + * Injects dependent shader module sources into pair of main vertex/fragment shader sources for GLSL + */ +export function assembleShaderPairGLSL(options: AssembleShaderOptions): { + vs: string; + fs: string; + getUniforms: GetUniformsFunc; +} { + const {vs, fs} = options; + const modules = resolveModules(options.modules || []); + + return { + vs: assembleGLSLShader(options.platformInfo, { + ...options, + source: vs, + stage: 'vertex', + modules + }), + fs: assembleGLSLShader(options.platformInfo, { + ...options, + source: fs, + stage: 'fragment', + modules + }), + getUniforms: assembleGetUniforms(modules) + }; } /** @@ -135,7 +161,7 @@ export function assembleShaders(options: AssembleShaderOptions): { * @param options * @returns */ -function assembleWGSLShader(platformInfo: PlatformInfo, options: AssembleStageOptions) { +export function assembleWGSLShader(platformInfo: PlatformInfo, options: AssembleStageOptions) { const { // id, source, @@ -412,7 +438,7 @@ ${getApplicationDefines(allDefines)} * @param modules * @returns */ -function assembleGetUniforms(modules: ShaderModuleInstance[]) { +export function assembleGetUniforms(modules: ShaderModuleInstance[]) { return function getUniforms(opts: Record): Record { const uniforms = {}; for (const module of modules) { diff --git a/modules/shadertools/src/lib/shader-assembly/select-shaders.ts b/modules/shadertools/src/lib/shader-assembly/select-shaders.ts index 380f6666c7..e3ddcfee54 100644 --- a/modules/shadertools/src/lib/shader-assembly/select-shaders.ts +++ b/modules/shadertools/src/lib/shader-assembly/select-shaders.ts @@ -10,6 +10,8 @@ import type {AssembleShaderOptions} from './assemble-shaders'; */ export type AssembleShaderProps = Omit & { platformInfo: PlatformInfo; + /** Single shader source. Always WGSL */ + source?: string | null; /** Vertex shader source. Can be GLSL or WGSL or both */ vs?: {glsl?: string; wgsl?: string} | string | null; /** Fragment shader source. Can be GLSL or WGSL or both */ diff --git a/modules/shadertools/test/lib/shader-assembler.spec.ts b/modules/shadertools/test/lib/shader-assembler.spec.ts index ac12fb2c4c..c04160a1c0 100644 --- a/modules/shadertools/test/lib/shader-assembler.spec.ts +++ b/modules/shadertools/test/lib/shader-assembler.spec.ts @@ -59,7 +59,7 @@ const FS_300 = glsl`\ test('ShaderAssembler#hooks', t => { const shaderAssembler = new ShaderAssembler(); - const preHookShaders = shaderAssembler.assembleShaders({platformInfo, vs, fs}); + const preHookShaders = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); shaderAssembler.addShaderHook('vs:LUMAGL_pickColor(inout vec4 color)'); shaderAssembler.addShaderHook('fs:LUMAGL_fragmentColor(inout vec4 color)', { @@ -67,7 +67,7 @@ test('ShaderAssembler#hooks', t => { footer: 'color.a *= 1.2;\n' }); - const assemblyResults = shaderAssembler.assembleShaders({platformInfo, vs, fs}); + const assemblyResults = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); t.ok(preHookShaders !== assemblyResults, 'Adding hooks changes hash'); @@ -84,7 +84,7 @@ test('ShaderAssembler#hooks', t => { picking ); - const noModuleProgram = shaderAssembler.assembleShaders({platformInfo, vs, fs}); + const noModuleProgram = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); t.ok(preHookShaders !== noModuleProgram, 'Adding hooks changes hash'); @@ -106,7 +106,7 @@ test('ShaderAssembler#hooks', t => { 'injection code not included in fragment shader without module' ); - const modulesProgram = shaderAssembler.assembleShaders({ + const modulesProgram = shaderAssembler.assembleShaderPair({ platformInfo, vs, fs, @@ -138,7 +138,7 @@ test('ShaderAssembler#hooks', t => { 'hook footer injected after injection code' ); - const injectedShaders = shaderAssembler.assembleShaders({ + const injectedShaders = shaderAssembler.assembleShaderPair({ platformInfo, vs, fs, @@ -153,7 +153,7 @@ test('ShaderAssembler#hooks', t => { t.ok(injectVs.indexOf('color *= 0.1') > -1, 'argument injection code included in shader hook'); t.ok(injectFs.indexOf('color += 0.1') > -1, 'argument injection code included in shader hook'); - const injectDefineProgram1 = shaderAssembler.assembleShaders({ + const injectDefineProgram1 = shaderAssembler.assembleShaderPair({ platformInfo, vs, fs, @@ -162,7 +162,7 @@ test('ShaderAssembler#hooks', t => { } }); - const injectDefineProgram2 = shaderAssembler.assembleShaders({ + const injectDefineProgram2 = shaderAssembler.assembleShaderPair({ platformInfo, vs, fs, @@ -179,9 +179,9 @@ test('ShaderAssembler#hooks', t => { test('ShaderAssembler#defaultModules', t => { const shaderAssembler = new ShaderAssembler(); - const program = shaderAssembler.assembleShaders({platformInfo, vs, fs}); + const program = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); - const preDefaultModuleProgram = shaderAssembler.assembleShaders({ + const preDefaultModuleProgram = shaderAssembler.assembleShaderPair({ platformInfo, vs, fs, @@ -192,8 +192,8 @@ test('ShaderAssembler#defaultModules', t => { shaderAssembler.addDefaultModule(dirlight); - const defaultModuleProgram = shaderAssembler.assembleShaders({platformInfo, vs, fs}); - const moduleProgram = shaderAssembler.assembleShaders({ + const defaultModuleProgram = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); + const moduleProgram = shaderAssembler.assembleShaderPair({ platformInfo, vs, fs, @@ -211,7 +211,7 @@ test('ShaderAssembler#defaultModules', t => { shaderAssembler.removeDefaultModule(dirlight); - const noDefaultModuleProgram = shaderAssembler.assembleShaders({platformInfo, vs, fs}); + const noDefaultModuleProgram = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); t.ok(program.fs === noDefaultModuleProgram.fs, 'Default module was removed'); t.ok(moduleProgram.fs !== noDefaultModuleProgram.fs, 'Default module was removed'); @@ -219,7 +219,7 @@ test('ShaderAssembler#defaultModules', t => { // Reset program manager shaderAssembler.addDefaultModule(dirlight); - const uncachedProgram = shaderAssembler.assembleShaders({platformInfo, vs, fs}); + const uncachedProgram = shaderAssembler.assembleShaderPair({platformInfo, vs, fs}); const defaultModuleSource = uncachedProgram.fs; // TODO - this deep equal thing doesn't make sense due to getUniforms diff --git a/modules/shadertools/test/lib/shader-assembly/assemble-shaders.spec.ts b/modules/shadertools/test/lib/shader-assembly/assemble-shaders.spec.ts index 0589ea64da..ee4caee0d1 100644 --- a/modules/shadertools/test/lib/shader-assembly/assemble-shaders.spec.ts +++ b/modules/shadertools/test/lib/shader-assembly/assemble-shaders.spec.ts @@ -1,7 +1,7 @@ import test from 'tape-promise/tape'; import {Device} from '@luma.gl/core'; import {webglDevice} from '@luma.gl/test-utils'; -import {assembleShaders, picking, fp64, pbr, glsl, PlatformInfo} from '@luma.gl/shadertools'; +import {assembleShaderPairGLSL, picking, fp64, pbr, glsl, PlatformInfo} from '@luma.gl/shadertools'; import type {WebGLDevice} from '@luma.gl/webgl'; import {isBrowser} from '@probe.gl/env'; @@ -259,13 +259,13 @@ void main(void) { } `; -test('assembleShaders#import', t => { - t.ok(assembleShaders !== undefined, 'assembleShaders import successful'); +test('assembleShaderPairGLSL#import', t => { + t.ok(assembleShaderPairGLSL !== undefined, 'assembleShaderPairGLSL import successful'); t.end(); }); -test('assembleShaders#version_directive', t => { - const assembleResult = assembleShaders({ +test('assembleShaderPairGLSL#version_directive', t => { + const assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -285,7 +285,7 @@ test('assembleShaders#version_directive', t => { t.end(); }); -test('assembleShaders#getUniforms', t => { +test('assembleShaderPairGLSL#getUniforms', t => { // inject spy into the picking module's getUniforms // const module = getShaderModule(picking); // const getUniformsSpy = makeSpy(module, 'getUniforms'); @@ -293,7 +293,7 @@ test('assembleShaders#getUniforms', t => { let assembleResult; // Without shader modules - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300 @@ -314,7 +314,7 @@ test('assembleShaders#getUniforms', t => { dependencies: [picking] }; - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -327,8 +327,8 @@ test('assembleShaders#getUniforms', t => { t.end(); }); -test('assembleShaders#defines', t => { - const assembleResult = assembleShaders({ +test('assembleShaderPairGLSL#defines', t => { + const assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -341,7 +341,7 @@ test('assembleShaders#defines', t => { t.end(); }); -test('assembleShaders#shaderhooks', t => { +test('assembleShaderPairGLSL#shaderhooks', t => { const hookFunctions = [ 'vs:LUMAGL_pickColor(inout vec4 color)', { @@ -372,7 +372,7 @@ test('assembleShaders#shaderhooks', t => { } }; - let assembleResult = assembleShaders({ + let assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -405,7 +405,7 @@ test('assembleShaders#shaderhooks', t => { 'regex injection code not included in fragment shader without module' ); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -440,7 +440,7 @@ test('assembleShaders#shaderhooks', t => { 'regex injection code included in fragment shader with module' ); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -466,7 +466,7 @@ test('assembleShaders#shaderhooks', t => { 'argument injection code injected in the correct order' ); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -484,7 +484,7 @@ test('assembleShaders#shaderhooks', t => { 'module injection code injected in the correct order' ); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -502,8 +502,8 @@ test('assembleShaders#shaderhooks', t => { t.end(); }); -test('assembleShaders#injection order', t => { - let assembleResult = assembleShaders({ +test('assembleShaderPairGLSL#injection order', t => { + let assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300_MODULES, fs: FS_GLSL_300_MODULES, @@ -524,7 +524,7 @@ test('assembleShaders#injection order', t => { 'Hook functions have access to injected variables.' ); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300_MODULES, fs: FS_GLSL_300_MODULES, @@ -541,8 +541,8 @@ test('assembleShaders#injection order', t => { }); // TODO - restore if we ever support transpilation of uniform blocks -test.skip('assembleShaders#transpilation', t => { - let assembleResult = assembleShaders({ +test.skip('assembleShaderPairGLSL#transpilation', t => { + let assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300, fs: FS_GLSL_300, @@ -560,7 +560,7 @@ test.skip('assembleShaders#transpilation', t => { 'assemble GLSL300 + picking and transpile to GLSL100' ); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300_2, fs: FS_GLSL_300_2, @@ -576,7 +576,7 @@ test.skip('assembleShaders#transpilation', t => { // TODO - this doesn't work in headless gl if (isBrowser() && extension) { t.comment(JSON.stringify(extension)); - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300_DECK, fs: FS_GLSL_300_DECK @@ -588,7 +588,7 @@ test.skip('assembleShaders#transpilation', t => { ); } - assembleResult = assembleShaders({ + assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_300_GLTF, fs: FS_GLSL_300_GLTF, diff --git a/modules/shadertools/test/lib/shader-assembly/inject-shader.spec.ts b/modules/shadertools/test/lib/shader-assembly/inject-shader.spec.ts index bd4636c7f8..ad9285b7b4 100644 --- a/modules/shadertools/test/lib/shader-assembly/inject-shader.spec.ts +++ b/modules/shadertools/test/lib/shader-assembly/inject-shader.spec.ts @@ -2,7 +2,7 @@ import test from 'tape-promise/tape'; import {Device} from '@luma.gl/core'; import {webglDevice} from '@luma.gl/test-utils'; -import {assembleShaders, glsl, PlatformInfo} from '@luma.gl/shadertools'; +import {assembleShaderPairGLSL, glsl, PlatformInfo} from '@luma.gl/shadertools'; import { injectShader, combineInjects, @@ -136,8 +136,8 @@ test('injectShader#injectShader', t => { t.end(); }); -test('injectShader#assembleShaders', t => { - const assembleResult = assembleShaders({ +test('injectShader#assembleShaderPairGLSL', t => { + const assembleResult = assembleShaderPairGLSL({ platformInfo: getInfo(webglDevice), vs: VS_GLSL_TEMPLATE, fs: FS_GLSL_TEMPLATE, From 7cb8821ade176c9b622d4f04fcdac55a6c19c487 Mon Sep 17 00:00:00 2001 From: Ib Green Date: Fri, 1 Mar 2024 12:10:07 -0500 Subject: [PATCH 2/4] docs --- .../shadertools/shader-assembler.md | 74 ++++++++++++------- .../shadertools/src/lib/shader-assembler.ts | 6 +- .../lib/shader-assembly/assemble-shaders.ts | 2 +- 3 files changed, 53 insertions(+), 29 deletions(-) diff --git a/docs/api-reference/shadertools/shader-assembler.md b/docs/api-reference/shadertools/shader-assembler.md index d221e38326..712fc04d82 100644 --- a/docs/api-reference/shadertools/shader-assembler.md +++ b/docs/api-reference/shadertools/shader-assembler.md @@ -9,13 +9,43 @@ via the `ShaderAssembler` class. - and injections to generate the final vertex and fragment shader source that can be used to create a program. +## Types + +## `AssembleShaderOptions` + +For single shader compilation +- `source` - single shader source (always WGSL) + +For shader pair compilation +- `vs` - vertex shader source +- `fs` - fragment shader source code + +Common options +- `prologue`=`true` (Boolean) - Will inject platform prologue (see below) +- `defines`=`{}` (Object) - a map of key/value pairs representing custom `#define`s to be injected into the shader source +- `modules`=`[]` (Array) - list of shader modules (either objects defining the module, or names of previously registered modules) +- `inject`=`{}` (Object) - map of substituions, +- `hookFunctions`=`[]` Array of hook functions descriptions. Descriptions can simply be the hook function signature (with a prefix `vs` for vertex shader, or `fs` for fragment shader) or an object with the hook signature, and a header and footer that will always appear in the hook function. + +Example of hook function + +```typescript +[ + 'vs:MY_HOOK_FUNCTION1(inout vec4 color)', + { + hook: 'fs:MY_HOOK_FUNCTION2(inout vec4 color)', + header: 'if (color.a == 0.0) discard;\n', + footer: 'color.a *= 1.2;\n' + } +]; +``` + ## Static Methods ### `getDefaultShaderAssembler()` Most applications that register default modules and hooks will want to use a single `Shader` - ## Methods ### `addDefaultModule(module: ShaderModule)` @@ -35,41 +65,34 @@ Creates a shader hook function that shader modules can injection code into. Shad - `opts.header` (optional): code always included at the beginning of a hook function - `opts.footer` (optional): code always included at the end of a hook function -### `assembleShaders` +### `assembleShader(options: AssembleShaderOptions)` -`ahaderAssebler.assembleShaders()` composes base vertex and fragment shader source with shader modules, -hook functions and injections to generate the final vertex and -fragment shader source that can be used to create a program. +generate the shader source that can be used to create a shader and then a pipeline. +- composes a single shader source (compute or unified vertex/fragment WGSL shader) with source from shader modules, +- resolving hook functions and injections to -Takes the source code of a vertex shader and a fragment shader, and a list of modules, defines, etc. Outputs resolved source code for both shaders, after adding prologue, adding defines, importing and transpiling modules, and injecting any shader fragments). -- `vs` - vertex shader source -- `fs` - fragment shader source code -- `id` - `id` for the shader, will be used to inject shader names (using `#define SHADER_NAME`) if not already present in the source. -- `prologue`=`true` (Boolean) - Will inject platform prologue (see below) -- `defines`=`{}` (Object) - a map of key/value pairs representing custom `#define`s to be injected into the shader source -- `modules`=`[]` (Array) - list of shader modules (either objects defining the module, or names of previously registered modules) -- `inject`=`{}` (Object) - map of substituions, -- `hookFunctions`=`[]` Array of hook functions descriptions. Descriptions can simply be the hook function signature (with a prefix `vs` for vertex shader, or `fs` for fragment shader) or an object with the hook signature, and a header and footer that will always appear in the hook function. For example: +Returns: -```typescript -[ - 'vs:MY_HOOK_FUNCTION1(inout vec4 color)', - { - hook: 'fs:MY_HOOK_FUNCTION2(inout vec4 color)', - header: 'if (color.a == 0.0) discard;\n', - footer: 'color.a *= 1.2;\n' - } -]; -``` +- `vs` - the resolved vertex shader +- `fs` - the resolved fragment shader +- `getUniforms` - a combined `getUniforms` function covering all modules. + +### `assembleShaderPair(options: AssembleShaderOptions)` + +Generate the final vertex and fragment shader source that can be compiled to create two shaders and then link them into a pipeline. + +- composes base vertex and fragment shader source with source from shader modules +- resolves hook functions and injections + +Takes the source code of a vertex shader and a fragment shader, and a list of modules, defines, etc. Outputs resolved source code for both shaders, after adding prologue, adding defines, importing and transpiling modules, and injecting any shader fragments). Returns: - `vs` - the resolved vertex shader - `fs` - the resolved fragment shader - `getUniforms` - a combined `getUniforms` function covering all modules. -- `moduleMap` - a map with all resolved modules, keyed by name ## Shader Module Assembly @@ -116,6 +139,7 @@ void MY_HOOK_FUNCTION(inout vec4 color) { The hook function now changes the color from white to red. + ## Constants and Values ### Predefined Injection Hooks diff --git a/modules/shadertools/src/lib/shader-assembler.ts b/modules/shadertools/src/lib/shader-assembler.ts index 6d37c19cfe..f633c47e7f 100644 --- a/modules/shadertools/src/lib/shader-assembler.ts +++ b/modules/shadertools/src/lib/shader-assembler.ts @@ -7,7 +7,7 @@ import {ShaderModuleInstance} from './shader-module/shader-module-instance'; import {selectShaders, AssembleShaderProps} from './shader-assembly/select-shaders'; import { GetUniformsFunc, - assembleSingleShaderWGSL, + assembleShaderWGSL, assembleShaderPairWGSL, assembleShaderPairGLSL } from './shader-assembly/assemble-shaders'; @@ -73,7 +73,7 @@ export class ShaderAssembler { * @param props * @returns */ - assembleSingleShader(props: AssembleShaderProps): { + assembleShader(props: AssembleShaderProps): { source: string; getUniforms: GetUniformsFunc; modules: ShaderModuleInstance[]; @@ -81,7 +81,7 @@ export class ShaderAssembler { const modules = this._getModuleList(props.modules); // Combine with default modules const hookFunctions = this._hookFunctions; // TODO - combine with default hook functions const options = selectShaders(props); - const assembled = assembleSingleShaderWGSL({ + const assembled = assembleShaderWGSL({ platformInfo: props.platformInfo, ...options, modules, diff --git a/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts b/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts index c2f85ffb06..0c8c6f8507 100644 --- a/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts +++ b/modules/shadertools/src/lib/shader-assembly/assemble-shaders.ts @@ -83,7 +83,7 @@ export type GetUniformsFunc = (opts: Record) => Record /** * Inject a list of shader modules into a single shader source for WGSL */ -export function assembleSingleShaderWGSL(options: AssembleShaderOptions): { +export function assembleShaderWGSL(options: AssembleShaderOptions): { source: string; getUniforms: GetUniformsFunc; } { From 259bddf17005429f461ff9236d89e247cc467fdd Mon Sep 17 00:00:00 2001 From: Ib Green Date: Fri, 1 Mar 2024 12:12:17 -0500 Subject: [PATCH 3/4] docs --- docs/whats-new.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/whats-new.md b/docs/whats-new.md index 6cf75cb474..804afc6c1d 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -70,8 +70,9 @@ To accelerate WebGPU development, luma.gl v9 drops support for legacy functional **`@luma.gl/shadertools`** - All shader modules now use uniform buffers. -- NEW: `ShaderAssembler` class that provides a clean entry point to the shader module system. +- New `ShaderAssembler` class that provides a clean entry point to the shader module system. - New `CompilerMessage` type and `formatCompilerLog` function for portable shader log handling. +- Shader assembly now supports WGSL and single shader source (compute or single vertex+fragment WGSL shaders) **`@luma.gl/webgl`** From 7ce9e7b788441f81e8b95056f9667ed3d4fa23c6 Mon Sep 17 00:00:00 2001 From: Ib Green Date: Fri, 1 Mar 2024 12:14:22 -0500 Subject: [PATCH 4/4] whats-new --- docs/whats-new.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/docs/whats-new.md b/docs/whats-new.md index 804afc6c1d..e6a9dabb57 100644 --- a/docs/whats-new.md +++ b/docs/whats-new.md @@ -79,10 +79,16 @@ To accelerate WebGPU development, luma.gl v9 drops support for legacy functional WebGL is not dead yet! Browsers (Chrome in particular) are still adding extensions to WebGL 2, and luma.gl is adding support for many of the new features through the [`DeviceFeatures`](/docs/api-reference/core/device-features) API. -New `Device.features` that improve WebGL application performance: +New `Device.features` that improve application performance in WebGL: - `compilation-status-async-webgl`: Asynchronous shader compilation and linking is used automatically when available and speeds up applications that create many `RenderPipelines`. -New `Device.features` that expose new WebGL GPU parameters: +New `Device.features` that enable additional color format support in WebGL: +- `rgb9e5ufloat-renderable-webgl`: `rgb9e5ufloat` is renderable. +- `snorm8-renderable-webgl`: `r,rg,rgba8snorm` are renderable. +- `norm16-renderable-webgl`: `r,rg,rgba16norm` are renderable. +- `snorm16-renderable-webgl`: `r,rg,rgba16snorm` are renderable. + +New `Device.features` that expose new GPU parameters in WebGL: - `depth-clip-control`: `parameters.unclippedDepth` - depth clipping can now be disabled if the feature is available. - `provoking-vertex-webgl`: `parameters.provokingVertex` - controls which primitive vertex is used for flat shading. - `polygon-mode-webgl`: `parameters.polygonMode` - enables wire frame rendering of polygons. Check the feature. @@ -93,8 +99,3 @@ New `Device.features` that enable new GLSL syntax - `shader-noperspective-interpolation-webgl`: GLSL vertex outputs and fragment inputs may be declared with a `noperspective` interpolation qualifier. - `shader-conservative-depth-webgl`: GLSL `gl_FragDepth` qualifiers `depth_any` `depth_greater` `depth_less` `depth_unchanged` can enable early depth test optimizations. -New `Device.features` that enable additional WebGL color format support: -- `rgb9e5ufloat-renderable-webgl`: `rgb9e5ufloat` are renderable. -- `snorm8-renderable-webgl`: `r,rg,rgba8snorm` are renderable. -- `norm16-renderable-webgl`: `r,rg,rgba16norm` are renderable. -- `snorm16-renderable-webgl`: `r,rg,rgba16snorm` are renderable.