Skip to main content

ShaderAssembler

ShaderAssembler is the abstract base for stateful shader assembly. Its concrete subclasses combine application shader source with shader modules, callable injection targets, and injections before luma.gl creates shader resources:

  • GLSLShaderAssembler assembles GLSL vertex and fragment shaders for WebGL.
  • WGSLShaderAssembler assembles unified WGSL shaders for WebGPU.

Applications construct and pass language-specific assemblers. Models and computations create owned assemblers when none is supplied; share an instance explicitly when several consumers should use the same injection targets, modules, or transpilers.

For the assembly model, see Shader Assembly. For extension design, see Shader Injection. For WGSL binding relocation and conditionals, see WGSL Support.

:::warning Common mistake Registering targets on an assembler does not automatically configure other models. Pass the configured instance through shaderAssembler to each model or computation that should use it. :::

Language entry points​

Import GLSLShaderAssembler from @luma.gl/shaders/glsl and WGSLShaderAssembler from @luma.gl/shaders/wgsl. The shared ShaderAssembler contract can be imported as a type from the root or either language entry. Each language entry loads its own assembler and assembly backend; shared modules, injection targets, uniforms and transpiler contracts retain their existing behavior. The WGSL entry also retains scanWGSLInterface and exports WGSL binding diagnostics.

Root assembler exports remain available and share constructor identity with the subpaths, including for CommonJS. Construct and pass a language-specific assembler; there is no global default factory.

Usage​

import {resolveShaderPlugins, type ShaderPlugin} from '@luma.gl/shaders';
import {GLSLShaderAssembler} from '@luma.gl/shaders/glsl';
import {WGSLShaderAssembler} from '@luma.gl/shaders/wgsl';

const offsetLeftPlugin: ShaderPlugin = {
name: 'offset-left',
injections: [{
stage: 'vertex',
target: 'OFFSET_POSITION',
glsl: 'position.x -= 0.5;',
wgsl: '(*position).x -= 0.5;'
}]
};

const glslShaderAssembler = new GLSLShaderAssembler();
glslShaderAssembler.addShaderInjectionTarget({name: 'OFFSET_POSITION', stage: 'vertex', glsl: {parameters: 'inout vec4 position'}});
const glslPlugin = resolveShaderPlugins([offsetLeftPlugin], 'glsl');
const assembledShaders = glslShaderAssembler.assembleGLSLShaderPair({
platformInfo: glslPlatformInfo,
vs: vertexShaderSource,
fs: fragmentShaderSource,
modules: glslPlugin.modules,
pluginInjections: glslPlugin.injections
});

const wgslShaderAssembler = new WGSLShaderAssembler();
wgslShaderAssembler.addShaderInjectionTarget({name: 'OFFSET_POSITION', stage: 'vertex', wgsl: {parameters: 'position: ptr<function, vec4<f32>>'}});
const wgslPlugin = resolveShaderPlugins([offsetLeftPlugin], 'wgsl');
const assembledWGSLShader = wgslShaderAssembler.assembleWGSLShader({
platformInfo: wgslPlatformInfo,
source: wgslShaderSource,
modules: wgslPlugin.modules,
pluginInjections: wgslPlugin.injections
});

const isolatedGLSLShaderAssembler = new GLSLShaderAssembler();
const isolatedWGSLShaderAssembler = new WGSLShaderAssembler();

Types​

AssembleShaderProps​

Common assembly props:

PropertyDescription
platformInfo: PlatformInfoCurrent backend, shader language, device limits, and features.
source?: stringUnified WGSL source for assembleWGSLShader().
vs?: stringGLSL vertex shader source for assembleGLSLShaderPair().
fs?: stringGLSL fragment shader source for assembleGLSLShaderPair().
modules?: ShaderModule[]Shader modules to resolve and add to the assembled source.
defines?: Record<string, boolean | number>Boolean or numeric preprocessor defines used while assembling shader source.
injectionTargets?: readonly ShaderInjectionTarget[]Callable targets to emit into assembled source.
pluginInjectionTargets?: readonly ShaderInjectionTarget[]Callable targets resolved from plugins.
inject?: ShaderInjection[] | Record<string, string | ResolvedShaderInjection>Named target or standard injection source.
pluginInjections?: Record<string, ResolvedShaderInjection[]>Ordered named injections already resolved from ShaderPlugin descriptors.
pluginVertexInputs?: Record<string, AttributeShaderType>Render-shader vertex inputs already resolved from ShaderPlugin descriptors.
vertexEntryPoint?: stringWGSL vertex entry point selected by the render pipeline. Defaults to vertexMain.
fragmentEntryPoint?: stringWGSL fragment entry point selected by the render pipeline. Defaults to fragmentMain.
prologue?: booleanGLSL only: whether to emit the luma.gl shader prologue.

Methods​

addDefaultModule(module: ShaderModule): void​

Registers a module that is included in later assembly calls unless that module is already present.

removeDefaultModule(module: ShaderModule): void​

Removes a previously registered default module.

addShaderInjectionTarget(target: ShaderInjectionTarget): void​

Registers one callable injection target with language-specific source:

const target: ShaderInjectionTarget = {
name: 'OFFSET_POSITION',
stage: 'vertex',
glsl: {parameters: 'inout vec4 position'},
wgsl: {parameters: 'position: ptr<function, vec4<f32>>'}
};
glslShaderAssembler.addShaderInjectionTarget(target);
wgslShaderAssembler.addShaderInjectionTarget(target);

Import ShaderInjectionTarget from @luma.gl/shaders. stage is vertex or fragment. Each language implementation supplies parameters without parentheses and optional header and footer source around injected code. Set returnType to a GLSL or WGSL type to return a value. Omit it (or use void) for a target with no return value. A value-returning target must provide a default return path in its header/footer. Vertex and fragment injection targets may share a public name, including in WGSL. The assembler emits private stage-specific function names and resolves calls from each entry point. Helpers reached by both stages are specialized when they call a shared-name target. A helper containing such a call must be reachable from a render entry point or target, so its stage can be determined. Injection keys still select the stage (vs: or fs:). Missing language implementations are skipped. Registration and assembly do not modify descriptors.

Injection targets remain vs:OFFSET_POSITION and fs:TARGET_NAME. Use root-level injections on ShaderPlugin, with glsl and wgsl source on each entry, for language-specific bodies. Shared target names do not translate injection source. Shaders still call the target explicitly, using GLSL inout arguments or WGSL pointers as appropriate.

resetShaderInjectionTargets(): void​

Removes every target registered with addShaderInjectionTarget(). Default modules are not affected. Use this instead of mutating assembler internals when an application needs to re-register its injection targets.

GLSLShaderAssembler.assembleGLSLShaderPair(props: AssembleShaderProps)​

Assembles a GLSL vertex/fragment pair and returns {vs, fs, getUniforms, modules}.

WGSLShaderAssembler.assembleWGSLShader(props: AssembleShaderProps)​

Assembles one unified WGSL source string and returns {source, getUniforms, modules, bindingAssignments, bindingTable}. Module WGSL source is prepended to application WGSL, inactive conditional branches are removed, and @binding(auto) declarations are assigned concrete binding numbers before WebGPU reflection.

Injection Targets And Injections​

Callable injection targets are deliberate extension points called by application shader code. The base shader registers a stage-prefixed injection target signature, calls the generated target function, and lets plugin contributions add ordered source by targeting the registered target name and stage.

glslShaderAssembler.addShaderInjectionTarget({name: 'OFFSET_POSITION', stage: 'vertex', glsl: {parameters: 'inout vec4 position'}});

const offsetLeftPlugin: ShaderPlugin = {
name: 'offsetLeft',
injections: [{stage: 'vertex', target: 'OFFSET_POSITION', glsl: 'position.x -= 0.5;'}]
};
void main() {
gl_Position = vec4(position, 0.0, 1.0);
OFFSET_POSITION(gl_Position);
}

If no plugin injects into a callable target, the generated target function is a no-op. WGSL uses pointer arguments for mutable values.

Named target injections must target a callable target registered for the shader stage. Plugin injections targeting undeclared targets fail during assembly.

Standard named injections are also available:

KeyShaderDescription
vs:#declVertexInject declarations near the top of the vertex shader.
vs:#main-startVertexInject at the beginning of the vertex main function.
vs:#main-endVertexInject at the end of the vertex main function.
fs:#declFragmentInject declarations near the top of the fragment shader.
fs:#main-startFragmentInject at the beginning of the fragment main function.
fs:#main-endFragmentInject at the end of the fragment main function.

For assembled WGSL, these keys target the matching @vertex or @fragment entry point in the unified source. ShaderPlugin accepts only named target and built-in targets; lower-level assembler inject also preserves the legacy arbitrary text-replacement escape hatch.

Application-owned transpilers​

Call addShaderTranspiler({name, sourceLanguage, transpile}) on an assembler to register a synchronous compiler for an input language. Registration is local to that assembler; registering the same language replaces its compiler. removeShaderTranspiler(sourceLanguage) removes it. Shaders neither imports nor installs a compiler. The application imports the implementation and supplies the callback, keeping unused compiler code out of its bundle.

Set sourceLanguage on assembly options, Model, or Computation. GLSL uses vs and fs as separate source-language translation units. WGSL uses source as one translation unit, including all requested render or compute entries. The callback receives source, target, stage, entryPoints, and platformInfo, and returns {code, entryPoints?}. stage is set for GLSL or compute; unified WGSL render compilation leaves it undefined. Returned entry-point names are used for WGSL interface scanning and pipeline creation. For direct WGSL compute assembly, set shaderStage: 'compute' even if the entry-point name is omitted. Computation sets this automatically. An explicit computeEntryPoint also selects compute compilation for compatibility. Omitted names remain undefined so the compiler can select its default entry point. Compiler diagnostics propagate to the caller.

Reusable modules can set {name, sourceLanguage, source}. Their dependency code is combined, once per module name, ahead of the application source before the compiler runs. Native modules, injection targets, and injections are assembled afterward in the target language. Module uniform getters and other metadata are preserved. Foreign modules must use the same language as the application translation unit; calling foreign helpers from native application source is not supported. Source-language modules should declare their uniforms and resources in their source and supply matching binding/uniform metadata and packing where needed. Automatic resource-name or uniform-layout translation is not part of this API.

For example, an application using the experimental @luma.gl/slang package (available from v10) can register an adapter. These imports belong to the application:

import {transpileSlang, transpileSlangWGSL} from '@luma.gl/slang';
import {type ShaderTranspiler} from '@luma.gl/shaders';

const slangTranspiler: ShaderTranspiler = {
name: 'slang',
sourceLanguage: 'slang',
transpile({source, target, stage, entryPoints}) {
if (target === 'glsl') {
return transpileSlang(source, {
target,
glslVersion: '300 es',
stage,
entryPoint: stage ? entryPoints[stage] : undefined
});
}
const selected = Object.values(entryPoints).filter(
(entryPoint): entryPoint is string => Boolean(entryPoint)
);
const result = transpileSlangWGSL(source, {
entryPoints: selected.length ? selected : undefined
});
return {
code: result.code,
entryPoints: Object.fromEntries(
Object.values(result.entryPoints).map(entry => [entry.stage, entry.entryPoint])
)
};
}
};

const glslShaderAssembler = new GLSLShaderAssembler();
const wgslShaderAssembler = new WGSLShaderAssembler();
glslShaderAssembler.addShaderTranspiler(slangTranspiler);
wgslShaderAssembler.addShaderTranspiler(slangTranspiler);
// Pass the configured assembler to Model or Computation via shaderAssembler.

The Slang Shaders example demonstrates this adapter with animated ray-marched geometry, lighting, and a floor reflection. Its reusable Slang distance-field and material modules run on both backends, with camera and material controls.

Slang remains a practical shader-authoring subset with documented limits. The adapter must select supported entry points and follow its resource layout rules; registering it does not add full Slang compiler compatibility.

Shared injection declarations​

ShaderInjectionTarget and ShaderInjection describe extension points and source independently of where they are registered. Register targets with addShaderInjectionTarget() or pass them as assembly injectionTargets or plugin injectionTargets. Pass the same ShaderInjection[] to application-level inject or plugin injections:

import type {ShaderInjection} from '@luma.gl/shaders';

const injections: ShaderInjection[] = [{
stage: 'fragment',
target: 'FILTER_COLOR',
glsl: 'color.r *= 0.5;',
wgsl: '(*color).r *= 0.5;',
order: 0
}];

const shaders = glslShaderAssembler.assembleGLSLShaderPair({
platformInfo, vs, fs, inject: injections
});

Declare and call FILTER_COLOR in the host shader. Missing language implementations are skipped; injections into undeclared targets throw. Repeated targets retain all injections, sorted by order with declaration order breaking ties. Application injections follow plugin injections when orders tie.

The existing application injection map also accepts native source or ResolvedShaderInjection values. ResolvedShaderInjection represents selected source with an injection order and optional plugin provenance; it has no language variants.