Skip to content

@typegpu/kitfinish · bloom · tone · exposure

Final pass

The last pass of a frame, mapping linear light to the screen through a tone curve, bloom and your own grade.

kit.finish

module

Creates the final pass of a frame.

finish.create(root, { format, source, region?, glow?, exposure?, … }) → Finish

rootborrows
the root
sourcetakes
the picture, rendered or a texture, read at every draw
region?takes
the part of the source that holds the picture, read at every draw
glow?takes
a bloom, or a glow of your own, added before tone mapping
exposure?needs
an adapted exposure, read on the GPU
Finishreturns
a final pass you draw every frame and destroy
GPU functions
glowpicture
schemas
Settings
constants
defaults
types
FloatTextureRenderedFilterFinishViewportRecordOptions
Optionscreate · 11 fields
format

GPUTextureFormat

Sets the output format, such as the canvas format.

source

(() => FloatTexture) | Rendered

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr.

region?

SizeSource= the region of a rendered source, else the whole texture

Selects the source region from its top-left corner, such as regions.hdr when drawing below full resolution.

glow?

Rendered= none

Adds a bloom, or a glow of your own such as an emission target, before tone mapping.

exposure?

Pick<Exposure, 'inject'>= none

Multiplies settings.exposure by an exposure adapted to the picture on the GPU, such as one from exposure.create recorded before this pass.

tone?

Tone | null= neutral

Maps linear source colours to display range with aces, neutral, reinhard or a custom curve.

filter?

Filter= linear

Selects linear filtering or sharp filtering for fractionally scaled pixel art.

encode?

boolean= true unless format ends in -srgb

Applies the sRGB transfer function in the shader.

grade?

(color: d.v3f, uv: d.v2f) => d.v3f= none

Grades or tints the colour after tone mapping and before the vignette, encoding and dither.

alpha?

'opaque' | 'premultiplied'= opaque, which writes alpha 1

Selects output alpha handling.

settings?

Partial<d.InferInput<typeof Settings>>= defaults, with exposure 1, vignette 0.35, dither 1 and glowIntensity 0.12

Sets initial values over defaults.

records
  • drawrender
  • recordencoder

record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

owns

2 buffers ·a sampler ·a pipeline ·2 bind groups

The settings, the regions it reads, and a group each for the source and the glow.

allocates

on resize

A bind group for a new source or glow.

lifecycle

initAsync()destroy()[Symbol.dispose]

needs
  • exposure

    As options.exposure; fills accessors in its pipelines.

Raise the threshold to stop dimmer discs glowing, or reduce levels to narrow the glow. Each disc is twice as bright as the previous one.

Linear light can exceed 1. Blockfall’s cleared blocks flash brighter than white. Clipping to the screen range of 0 to 1 turns bright colours yellow, bands dark gradients and loses glow. Use finish.create for a final pass that maps the image to the screen with one full-screen triangle. Add bloom, FXAA, TAA or depth of field around it.

import {
import bloom
bloom
,
import finish
finish
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>

Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

@param ― source the size it follows, such as a surface

@param ― specs a format and options per target, by name

@param ― options.resolution the fraction of each side drawn, which changes without allocating

@returns ― a set of targets you fit every frame and destroy

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true } });
const
const source: () => targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
source
= () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
;
const
const glow: bloom.Bloom
glow
=
import bloom
bloom
.
function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

@allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

@owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

@records ― record after whatever wrote the source; then output holds the glow.

@param ― options.source the HDR picture, read at every record

@param ― options.region the part of the source that holds the picture, read at every record

@returns ― a bloom you record every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

source
});
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
,
Options.glow?: finish.Rendered | undefined

Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

glow
});
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.fit(): boolean

Rebuilds all textures if the source size changed, destroying the old textures. Updates regions for the current resolution. Returns true after a rebuild so callers can update bind groups that hold old textures. Call before recording passes that use the targets. Throws after destroy.

fit
();
const
const scene: TgpuRenderPass
scene
=
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
.
TgpuCommandEncoder.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

GPUTextureView

s.

beginRenderPass
({
TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
colorAttachments
: {
ColorAttachment.view: TgpuTextureRenderView | (ColorTextureConstraint & RenderFlag) | GPUTextureView | TgpuTextureView<WgslTexture<WgslTextureProps>> | GPUCanvasContext

A

GPUTextureView

describing the texture subresource that will be output to for this color attachment.

view
:
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.views: {
readonly hdr: GPUTextureView;
}

Provides attachment views of mip 0, created once per rebuild. Throws after destroy.

views
.
hdr: GPUTextureView
hdr
} });
// the scene, in linear colour
const scene: TgpuRenderPass
scene
.
TgpuRenderPass.end(): void

Completes the recording of this render pass

end
();
const glow: bloom.Bloom
glow
.
Bloom.record(target: frames.Frame | TgpuCommandEncoder, options?: Timed): void

Records bloom passes after the source is written. Optional timer writes measure the passes together.

record
(
frame: frames.Frame
frame
);
const final: finish.Finish
final
.
Finish.record(target: frames.Frame | TgpuCommandEncoder, view: GPUTextureView, options?: finish.RecordOptions): void

Opens a pass on view, such as surface.view(frame), draws into it and ends it. A viewport draws the picture into that rectangle and clears the rest, transparent with alpha: 'premultiplied' and black otherwise. Optional timer writes measure the pass.

record
(
frame: frames.Frame
frame
,
const surface: web.Surface
surface
.
Surface.view(frame: frames.Frame): GPUTextureView

Returns a view of the current canvas texture, created once per texture. Applies pending size changes first, which a pass using context would skip.

view
(
frame: frames.Frame
frame
));
}),
);
  • final.record(frame, view) opens a pass on view, draws and ends it. It also takes an encoder in place of the frame. final.draw(pass) draws into a pass you opened, such as one a HUD draws into afterwards. record(frame, view, { timestampWrites }) times the pass under one profiler timer.
  • record(frame, view, { viewport }) draws the picture into a rectangle of the view, such as a letterbox, and clears the rest to black, or to transparent with alpha: 'premultiplied'.
  • The source is a function returning a single-sampled float target with sampled: true, or a picture another module rendered, such as aa, blur or glow. Other sources fail to type-check. A 32-bit float format requires float32-filterable. Each draw or record reads current source, region and texture values, so target resizing through fit() is supported.
  • A rendered picture brings its region. Give region yourself only with a function source drawn below full resolution, such as screen.regions.hdr.
  • The final pass adds glow to its own source. Bloom can therefore read a separate image, as in What glows. Any object with output, and optionally region, can supply glow.
  • Each pixel applies these stages in order: glow, exposure, tone, grade, vignette, sRGB encoding and dither.
  • Uniform values of zero do not remove shader work. Vignette, dither and glowIntensity still run their calculations, and glow still samples. Omit stages with no glow, tone: null, no grade or encode: false. Dither always runs.
  • Convert screen-picked sRGB colours once with srgbToLinear from @typegpu/color. It runs on the CPU too.

Falling sand renders to a small target and upscales it with filter: 'sharp'.

kit.tone

shader

These 'use gpu' functions apply tone curves and dither.

kind
shader
GPU functions
luminanceacesneutralreinhardagxdither
types
Tone
Raise exposure to see where each colour strip turns white. The plot shows grey light through each curve, and the strips show one colour at increasing brightness.
tone
tone.neutral (default) Khronos PBR Neutral. Keeps hue until the highlights.
tone.aces Narkowicz’s fit of ACES. Bright colours go white.
tone.reinhard On luminance. Soft, and never reaches white.
tone.agx Blender’s AgX. Bright colours fade to white evenly.
null No curve, for sources already in display range.

kit.bloom

module

Creates bloom from a chain of half-size textures.

bloom.create(root, { source, region?, … }) → Bloom

rootborrows
the root
sourcetakes
the HDR picture, read at every record
region?takes
the part of the source that holds the picture, read at every record
Bloomreturns
a bloom you record every frame and destroy
GPU functions
threshold
schemas
Settings
constants
defaults
types
Bloom
Optionscreate · 5 fields
source

(() => FloatTexture) | Rendered

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr.

region?

SizeSource= the region of a rendered source, else the whole texture

Selects the source region from its top-left corner, such as regions.hdr when drawing below full resolution.

levels?

number= 6

Sets the number of levels, each half the size of the preceding level.

bright?

(color: d.v3f) => d.v3f= threshold

Selects the linear source colour that contributes glow.

settings?

Partial<d.InferInput<typeof Settings>>= defaults, with threshold 1, knee 0.5 and spread 1

Sets initial values over defaults.

records
  • recordencoder

record after whatever wrote the source; then output holds the glow.

owns

targets ·a sampler ·3 pipelines ·2 bind groups and a buffer per level

The chain of glow levels, the settings, a group for each pass, and the region each pass reads.

allocates

on resize

A new chain and the bind groups of its passes; so does a new source.

lifecycle

initAsync()destroy()[Symbol.dispose]

A wide full-resolution blur needs hundreds of samples per pixel. Bloom reduces that work by blurring progressively smaller levels.

Choose a small source to see the chain stop when its dimensions run out. Each pass draws one triangle.
  • On the way down, each level halves the dimensions and samples 13 taps. Jimenez’s filter combines five overlapping four-tap boxes, weighted one half for the centre and one eighth per corner.
  • The first downsample keeps bright values through bright. It uses a Karis average, weighting boxes by 1 / (1 + luminance), to prevent flicker from isolated bright pixels.
  • The default bright is bloom.threshold. It keeps the excess of the brightest channel above threshold. A quadratic softens the transition starting knee × threshold below it.
  • On the way up, each level samples the smaller level through a 3 × 3 tent, with weights 1, 2 and 4 divided by 16. Taps are spread texels apart. Add the filtered result to the next larger level.
  • Level 0 of the rgba16float output holds the final glow at half the source dimensions. The final pass multiplies it by glowIntensity before exposure and tone mapping.
  • A region restricts bloom to the source area drawn at lower resolution. Each level reads and draws its corresponding region. glow.region gives the glow’s region, which the final pass reads through glow.
  • record takes the frame or an encoder. record(frame, { timestampWrites }) times all its passes under one profiler timer.
  • The default 6 levels require 11 render passes, one downsample per level and one upsample for all but the smallest. Level 0 does about three quarters of the pixel work with 13 taps down and 9 up. Each following level has one quarter as many pixels. Removing a level saves little GPU work but removes two CPU-recorded passes, each requiring begin, viewport, draw and end calls.

By default, everything above the brightness threshold glows, including white sky. To bloom only emitters, draw them into a separate target and bloom that target. Keep the full scene as the final pass’s source. Use bright to select or modify emission:

import {
import bloom
bloom
,
import finish
finish
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly emission: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly emission: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly emission: {
readonly format: "rgba16float";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly emission: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>

Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

@param ― source the size it follows, such as a surface

@param ― specs a format and options per target, by name

@param ― options.resolution the fraction of each side drawn, which changes without allocating

@returns ― a set of targets you fit every frame and destroy

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
emission: {
readonly format: "rgba16float";
readonly sampled: true;
}
emission
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
});
// Bloom the full emission target with a warmer tint
const
const warm: (color: d.v3f) => d.v3f
warm
= (
color: d.v3f
color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
) => {
'use gpu';
return
color: d.v3f
color
*
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(1, 0.7, 0.4);
};
const
const glow: bloom.Bloom
glow
=
import bloom
bloom
.
function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

@allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

@owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

@records ― record after whatever wrote the source; then output holds the glow.

@param ― options.source the HDR picture, read at every record

@param ― options.region the part of the source that holds the picture, read at every record

@returns ― a bloom you record every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly emission: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly emission: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly emission: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
emission: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
emission
,
Options.bright?: ((color: d.v3f) => d.v3f) | undefined

Selects the linear source colour that contributes glow. This 'use gpu' function runs once per texel of the first level. It can call threshold and carry fills with pipe. Default threshold.

bright
:
const warm: (color: d.v3f) => d.v3f
warm
});
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly emission: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly emission: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly emission: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.glow?: finish.Rendered | undefined

Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

glow
,
});
  • Call bloom.threshold(color) within bright to tint values above the threshold or weight them by tone.luminance. It reads bloom settings and is valid only inside bright.
  • A bright that reads another module carries its fill, as bright: tgpu.fn(masked).pipe(mask.inject()) does.

kit.exposure

module

Creates an exposure that adapts to the picture on the GPU.

exposure.create(root, { source, region?, … }) → Exposure

rootborrows
the root
sourcetakes
the linear picture, read at every record
region?takes
the part of the source that holds the picture, read at every record
Exposurereturns
an exposure you record every frame and destroy
accessors
current
schemas
Settings
constants
defaults
types
ExposureRecordOptions
Optionscreate · 3 fields
source

(() => FloatTexture) | Rendered

Provides the linear picture to measure, usually the final pass's source: a rendered picture, such as aa, or a function returning a texture.

region?

SizeSource= the region of a rendered source, else the whole texture

Selects the source region from its top-left corner, such as regions.hdr when drawing below full resolution.

settings?

Partial<d.InferInput<typeof Settings>>= defaults, with key 0.18, lowest 1/16, highest 16, rise 1 and fall 3

Sets initial values over defaults.

records
  • recordencoder

record after whatever wrote the source, before the final pass that reads it.

owns

3 buffers ·a sampler ·a pipeline and a bind group

The settings, the frame's region and time, the exposure, and a group for the source.

allocates

on resize

A bind group for a new source.

lifecycle

initAsync()destroy()[Symbol.dispose]

injects
  • per-instance

    exposure.current, for the final pass or shaders of your own.

binds
  • 1 storage

    exposure.current, read-only.

exposure.create measures the picture each frame and eases the exposure towards a target on the GPU, as an eye adapts to light and dark:

import {
import exposure
exposure
,
import finish
finish
} from '@typegpu/kit';
const
const source: () => targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
source
= () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
;
const
const eye: exposure.Exposure
eye
=
import exposure
exposure
.
function create(root: TgpuRoot, options: exposure.Options): exposure.Exposure

Creates an exposure that adapts to the picture on the GPU. Each record averages the log luminance of 4096 filtered samples over the source in one workgroup. It then eases the exposure towards key divided by that average, within lowest and highest. The exposure stays in a GPU buffer, and nothing is read back.

@allocates ― on resize: a bind group for a new source

@owns ― 3 buffers, a sampler, a pipeline and a bind group: the settings, the frame's region and time, the exposure, and a group for the source

@injects ― per-instance exposure.current, for the final pass or shaders of your own

@binds ― 1 storage: exposure.current, read-only

@records ― record after whatever wrote the source, before the final pass that reads it

@param ― options.source the linear picture, read at every record

@param ― options.region the part of the source that holds the picture, read at every record

@returns ― an exposure you record every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.source: (() => finish.FloatTexture) | finish.Rendered

Provides the linear picture to measure, usually the final pass's source: a rendered picture, such as aa, or a function returning a texture. record reads it on every call, so the texture can be rebuilt.

source
,
Options.settings?: Partial<InferInput<WgslStruct<{
key: F32;
lowest: F32;
highest: F32;
rise: F32;
fall: F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with key 0.18, lowest 1/16, highest 16, rise 1 and fall 3.

settings
: {
highest: number
highest
: 8 } });
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
,
Options.exposure?: Pick<exposure.Exposure, "inject"> | undefined

Multiplies settings.exposure by an exposure adapted to the picture on the GPU, such as one from exposure.create recorded before this pass. Default none.

exposure
:
const eye: exposure.Exposure
eye
});
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.fit(): boolean

Rebuilds all textures if the source size changed, destroying the old textures. Updates regions for the current resolution. Returns true after a rebuild so callers can update bind groups that hold old textures. Call before recording passes that use the targets. Throws after destroy.

fit
();
// the scene, into screen.views.hdr
const eye: exposure.Exposure
eye
.
Exposure.record(target: frames.Frame | TgpuCommandEncoder, options?: exposure.RecordOptions): void

Measures the source and moves the exposure towards its target by dt seconds of adaptation, the frame's dt by default. Record it after whatever wrote the source and before the final pass. The first record, the first after reset, and one given an encoder without dt take the target at once. Optional timer writes measure the pass.

record
(
frame: frames.Frame
frame
);
const final: finish.Finish
final
.
Finish.record(target: frames.Frame | TgpuCommandEncoder, view: GPUTextureView, options?: finish.RecordOptions): void

Opens a pass on view, such as surface.view(frame), draws into it and ends it. A viewport draws the picture into that rectangle and clears the rest, transparent with alpha: 'premultiplied' and black otherwise. Optional timer writes measure the pass.

record
(
frame: frames.Frame
frame
,
const surface: web.Surface
surface
.
Surface.view(frame: frames.Frame): GPUTextureView

Returns a view of the current canvas texture, created once per texture. Applies pending size changes first, which a pass using context would skip.

view
(
frame: frames.Frame
frame
));
}),
);
  • record(frame) averages the log luminance of 4096 samples over the source and moves the exposure towards key divided by that average, within lowest and highest. The exposure stays on the GPU, so the final pass of the same frame reads it.
  • The exposure rises at rise and falls at fall, per second, over the frame’s dt: each second closes 1 - e^-rise of the remaining stops, as clock.ease does. By default it falls three times as fast as it rises, so a flash dims the picture quickly and a dark scene brightens slowly. The first record takes the target at once, and so does the first after eye.reset(), such as after a camera cut.
  • With an encoder in place of the frame, give the seconds yourself, eye.record(encoder, { dt }). Without dt, it takes the target at once.
  • final.settings.exposure multiplies the adapted exposure, so it trims the look as before.
  • A second view, such as a picture-in-picture camera, takes its own exposure.create and final pass.
  • Shaders of your own read the value through exposure.current with root.pipe(eye.inject()).
  • eye.settings patches like the other settings. Its fields don’t overlap the final pass’s or the bloom’s.
Field Default
key 0.18 Average luminance the exposed picture aims for, mid grey.
lowest 1/16 Lowest exposure, for the brightest pictures.
highest 16 Highest exposure, for the darkest pictures.
rise 1 How fast exposure rises after the picture darkens, per second.
fall 3 How fast exposure falls after the picture brightens, per second.

final.settings and glow.settings are uniforms. Their initial values combine defaults with the settings option. Use TypeGPU’s patch when settings change. It uploads supplied fields and skips fields absent from that uniform. Since the fields do not overlap, one object can patch both:

import {
import bloom
bloom
,
import finish
finish
} from '@typegpu/kit';
const
const source: () => targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
source
= () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
;
const
const glow: bloom.Bloom
glow
=
import bloom
bloom
.
function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

@allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

@owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

@records ― record after whatever wrote the source; then output holds the glow.

@param ― options.source the HDR picture, read at every record

@param ― options.region the part of the source that holds the picture, read at every record

@returns ― a bloom you record every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

source
,
Options.settings?: Partial<InferInput<WgslStruct<{
threshold: F32;
knee: F32;
spread: F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with threshold 1, knee 0.5 and spread 1.

settings
: {
threshold: number
threshold
: 0.6 } });
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
,
Options.settings?: Partial<InferInput<WgslStruct<{
exposure: F32;
vignette: F32;
dither: F32;
glowIntensity: F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with exposure 1, vignette 0.35, dither 1 and glowIntensity 0.12.

settings
: {
exposure: number
exposure
: 1.4 },
Options.glow?: finish.Rendered | undefined

Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

glow
,
});
// When settings change
const
const look: {
exposure: number;
threshold: number;
}
look
= {
exposure: number
exposure
: 1.1,
threshold: number
threshold
: 0.8 };
const final: finish.Finish
final
.
Finish.settings: TgpuUniform<WgslStruct<{
exposure: F32;
vignette: F32;
dither: F32;
glowIntensity: F32;
}>>

Holds defaults with overrides from settings. Use patch to upload only the fields you supply.

settings
.
TgpuBufferBindingBase<WgslStruct<{ exposure: F32; vignette: F32; dither: F32; glowIntensity: F32; }>>.patch(data: {
exposure?: number | undefined;
vignette?: number | undefined;
dither?: number | undefined;
glowIntensity?: number | undefined;
} | undefined): void
patch
(
const look: {
exposure: number;
threshold: number;
}
look
);
const glow: bloom.Bloom
glow
.
Bloom.settings: TgpuUniform<WgslStruct<{
threshold: F32;
knee: F32;
spread: F32;
}>>

Holds defaults with overrides from settings. Use patch to upload only the fields you supply.

settings
.
TgpuBufferBindingBase<WgslStruct<{ threshold: F32; knee: F32; spread: F32; }>>.patch(data: {
threshold?: number | undefined;
knee?: number | undefined;
spread?: number | undefined;
} | undefined): void
patch
(
const look: {
exposure: number;
threshold: number;
}
look
);
Field Default
exposure 1 Multiplies the source before the tone curve.
vignette 0.35 Darkens the corners, or fades them out over a page: 0 not at all, 1 fully.
dither 1 Noise against banding, in 8-bit steps of what the output stores. 0 is off.
glowIntensity 0.12 How much glow the final pass adds.
threshold 1 Brightness where the glow starts.
knee 0.5 How gradually it starts, as a fraction of the threshold.
spread 1 Distance between taps on each step up, in texels of the smaller level.

The first four fields belong to the final pass and the last three to bloom. FXAA’s settings patch the same way.

grade(color, uv) runs after the tone curve, on colours in display range, and before the vignette. This one cools the shadows:

import {
import finish
finish
,
import tone
tone
} from '@typegpu/kit';
const
const coolShadows: (color: d.v3f, _uv: d.v2f) => d.v3f
coolShadows
= (
color: d.v3f
color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
,
_uv: d.v2f
_uv
:
import d
d
.
export v2f

Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

v2f
) => {
'use gpu';
const
const dark: number
dark
= 1 -
import std
std
.
function saturate(value: number): number (+1 overload)
export saturate
saturate
(
import tone
tone
.
const luminance: (color: d.v3f) => number

Returns the relative luminance of a linear colour.

luminance
(
color: d.v3f
color
) * 3);
return
import std
std
.
mix<d.v3f>(e1: d.v3f, e2: d.v3f, e3: number): d.v3f (+2 overloads)
export mix
mix
(
color: d.v3f
color
,
color: d.v3f
color
*
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(0.85, 0.95, 1.15),
const dark: number
dark
);
};
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
: 'bgra8unorm',
Options.source: (() => finish.FloatTexture) | finish.Rendered

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.grade?: ((color: d.v3f, uv: d.v2f) => d.v3f) | undefined

Grades or tints the colour after tone mapping and before the vignette, encoding and dither. It receives the colour and output UV coordinates from 0 to 1. Read other pixels with finish.picture(uv) and glow with finish.glow(uv). Default none.

grade
:
const coolShadows: (color: d.v3f, _uv: d.v2f) => d.v3f
coolShadows
,
});

Use finish.picture(uv) inside grade to read a neighbouring source pixel with glow, exposure and tone mapping applied. finish.glow(uv) reads glow before glowIntensity scales it and returns zero if absent. This grade adds red and blue fringes like lens chromatic aberration:

import {
import finish
finish
} from '@typegpu/kit';
const
const fringe: (color: d.v3f, uv: d.v2f) => d.v3f
fringe
= (
color: d.v3f
color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
,
uv: d.v2f
uv
:
import d
d
.
export v2f

Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

v2f
) => {
'use gpu';
const
const shift: d.v2f
shift
= (
uv: d.v2f
uv
- 0.5) * 0.008;
return
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(
import finish
finish
.
const picture: (uv: d.v2f) => d.v3f

Samples the source with glow, exposure and tone mapping at output UV coordinates from 0 to 1. This is the colour passed to grade. Call it only from the final pass grade function.

picture
(
uv: d.v2f
uv
+
const shift: d.v2f
shift
).
v3f.x: number
x
,
color: d.v3f
color
.
v3f.y: number
y
,
import finish
finish
.
const picture: (uv: d.v2f) => d.v3f

Samples the source with glow, exposure and tone mapping at output UV coordinates from 0 to 1. This is the colour passed to grade. Call it only from the final pass grade function.

picture
(
uv: d.v2f
uv
-
const shift: d.v2f
shift
).
v3f.z: number
z
);
};
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
: 'bgra8unorm',
Options.source: (() => finish.FloatTexture) | finish.Rendered

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.grade?: ((color: d.v3f, uv: d.v2f) => d.v3f) | undefined

Grades or tints the colour after tone mapping and before the vignette, encoding and dither. It receives the colour and output UV coordinates from 0 to 1. Read other pixels with finish.picture(uv) and glow with finish.glow(uv). Default none.

grade
:
const fringe: (color: d.v3f, uv: d.v2f) => d.v3f
fringe
,
});
  • Each finish.picture call repeats source and glow reads and tone mapping. The fringe example triples that work. For sharpening, sample neighbours std.fwidth(uv) apart, one texel.
  • These functions work only inside the final pass’s grade. Other uses fail shader compilation.
  • For a lookup table, capture a 3D texture view and sampler in the grade. If the table can change, read it through a filled accessor, for example grade: tgpu.fn(looked).pipe((cfg) => cfg.with(table, () => view.$)).

A grade can show any sampled target instead of the picture, such as depth while you tune fog. Read a target that follows the canvas through a bindings.layout part, and set the shared group on the pass before draw:

import {
import d
d
,
import std
std
} from 'typegpu';
import {
import bindings
bindings
,
import finish
finish
,
import targets
targets
} from '@typegpu/kit';
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>

Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

@param ― source the size it follows, such as a surface

@param ― specs a format and options per target, by name

@param ― options.resolution the fraction of each side drawn, which changes without allocating

@returns ― a set of targets you fit every frame and destroy

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true },
depth: {
readonly format: "depth24plus";
readonly sampled: true;
}
depth
: {
format: "depth24plus"
format
: 'depth24plus',
sampled: true
sampled
: true },
});
const
const shown: bindings.Layout<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
shown
=
import bindings
bindings
.
function layout<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>(entries: {
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}): bindings.Layout<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>

Declares bindings for module shaders from the entries that tgpu.bindGroupLayout takes. Define the layout once beside the shaders. A part made with bind can bind its resources directly. Call share before creating pipelines to put the part in a shared layout.

@param ― entries the bindings, as tgpu.bindGroupLayout takes them

@returns ― a layout whose bind makes a part from resources

layout
({
depth: {
readonly texture: d.WgslTextureDepth2d;
}
depth
: {
texture: d.WgslTextureDepth2d
texture
:
import d
d
.
function textureDepth2d(): d.WgslTextureDepth2d
export textureDepth2d
textureDepth2d
() } });
const
const part: bindings.Bound<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
part
=
const shown: bindings.Layout<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
shown
.
Layout<{ readonly depth: { readonly texture: WgslTextureDepth2d; }; }>.bind(resources: bindings.Resources<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>, options?: bindings.BindOptions): bindings.Bound<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>

Binds one set of resources, using the same inputs as root.createBindGroup.

bind
({
depth: GPUTextureView | GPUTexture | (SampledFlag & TgpuTexture<{
size: readonly number[];
format: GPUTextureFormat;
dimension?: "2d" | undefined;
sampleCount?: 1 | undefined;
}>) | TgpuTextureView<d.WgslTexture<WgslTextureProps>>
depth
:
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>
depth
});
const
const shared: bindings.Shared
shared
=
import bindings
bindings
.
function share(root: TgpuRoot, parts: Record<string, bindings.Part>): bindings.Shared

Combines parts from modules or your own code in one layout. Call it before creating pipelines. Set its group on each pass with pass.setBindGroup(shared.layout, shared.group()), or give it to passes.create as shared, which sets it in every pass it begins. Every pipeline in that pass that reads a part then uses the shared group.

@param ― parts parts from any modules and your own, by key

@returns ― a layout you set on each pass, with the group for the parts' resources

share
(
const root: TgpuRoot
root
, {
shown: bindings.Bound<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
shown
:
const part: bindings.Bound<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
part
}); // before `finish.create`
// Depth as grey, white at the eye
const
const depthOf: (_color: d.v3f, uv: d.v2f) => d.v3f
depthOf
= (
_color: d.v3f
_color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
,
uv: d.v2f
uv
:
import d
d
.
export v2f

Interface representing its WGSL vector type counterpart: vec2f or vec2. A vector with 2 elements of type f32

v2f
) => {
'use gpu';
const
const texel: d.v2i
texel
=
import d
d
.
const vec2i: d.Vec2i
(v: AnyNumericVec2Instance) => d.v2i (+3 overloads)
vec2i
(
uv: d.v2f
uv
*
import d
d
.
const vec2f: d.Vec2f
(v: AnyNumericVec2Instance) => d.v2f (+3 overloads)
vec2f
(
import std
std
.
textureDimensions<d.textureDepth2d>(texture: d.textureDepth2d): d.v2u (+5 overloads)
export textureDimensions
textureDimensions
(
const shown: bindings.Layout<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
shown
.
Layout<{ readonly depth: { readonly texture: WgslTextureDepth2d; }; }>.$: {
readonly depth: d.textureDepth2d;
}

Provides shader access to the entries, as a tgpu.bindGroupLayout's $ does.

$
.
depth: d.textureDepth2d
depth
)));
return
import d
d
.
const vec3f: d.Vec3f
(xyz: number) => d.v3f (+5 overloads)
vec3f
(1 -
import std
std
.
textureLoad<d.textureDepth2d>(texture: d.textureDepth2d, coords: d.v2i | d.v2u, level: number): number (+12 overloads)
export textureLoad
textureLoad
(
const shown: bindings.Layout<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
shown
.
Layout<{ readonly depth: { readonly texture: WgslTextureDepth2d; }; }>.$: {
readonly depth: d.textureDepth2d;
}

Provides shader access to the entries, as a tgpu.bindGroupLayout's $ does.

$
.
depth: d.textureDepth2d
depth
,
const texel: d.v2i
texel
, 0));
};
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: (() => finish.FloatTexture) | finish.Rendered

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.grade?: ((color: d.v3f, uv: d.v2f) => d.v3f) | undefined

Grades or tints the colour after tone mapping and before the vignette, encoding and dither. It receives the colour and output UV coordinates from 0 to 1. Read other pixels with finish.picture(uv) and glow with finish.glow(uv). Default none.

grade
:
const tgpu: {
const: typeof import("node_modules/typegpu/core/constant/tgpuConstant").constant;
fn: typeof import("node_modules/typegpu/core/function/tgpuFn").fn;
comptime: typeof import("node_modules/typegpu/core/function/comptime").comptime;
resolve: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolve;
resolveWithContext: typeof import("node_modules/typegpu/core/resolve/tgpuResolve").resolveWithContext;
init: typeof import("node_modules/typegpu/core/root/init").init;
initFromDevice: typeof import("node_modules/typegpu/core/root/init").initFromDevice;
slot: typeof import("node_modules/typegpu/core/slot/slot").slot;
lazy: typeof import("node_modules/typegpu/core/slot/lazy").lazy;
... 10 more ...;
'~unstable': typeof import("node_modules/typegpu/tgpuUnstable");
}

@module ― typegpu

tgpu
.
fn: <(_color: d.v3f, uv: d.v2f) => d.v3f>(inner: (_color: d.v3f, uv: d.v2f) => d.v3f) => TgpuGenericFn<(_color: d.v3f, uv: d.v2f) => d.v3f> (+2 overloads)
fn
(
const depthOf: (_color: d.v3f, uv: d.v2f) => d.v3f
depthOf
).
TgpuGenericFn<(_color: d.v3f, uv: d.v2f) => v3f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(_color: d.v3f, uv: d.v2f) => d.v3f>

Applies a configuration transform to the slots and accessors this function reads, as root.pipe does to a pipeline's.

pipe
(
const part: bindings.Bound<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
part
.
Bound<{ readonly depth: { readonly texture: WgslTextureDepth2d; }; }>.inject(values?: {
readonly depth: d.textureDepth2d;
} | undefined): Inject

Fills shader accessors from the shared layout after share takes this part. Otherwise, it binds the resources in each pipeline's automatic group.

inject
()),
});
import frames
frames
.
function animate(loop: Pick<frames.Frames, "run"> & Partial<Pick<frames.Frames, "seconds">>, options?: frames.AnimateOptions): frames.Animation
export animate

Runs the loop on each scheduled frame. Time advances by at most maxDt per frame, never steps back, and stops while the surface is paused. Destroying the animation keeps the loop alive. Another animation on the same loop continues from its recorded time. A frame error stops the animation.

@param ― loop the loop to run on each unpaused frame. Continues from its seconds if it ran before

@returns ― an animation you destroy to stop the frames

animate
(
import frames
frames
.
function create(root: TgpuRoot, record: Recorder): frames.Frames

Records a frame into one encoder and submits once, with a clock. Borrows the root. The synchronous recorder borrows each frame's encoder.

@submits ― always once a frame after your recorder returns, and again at each frame.submit()

@param ― record your frame, recorded into each frame's encoder

@returns ― a loop you run with each frame's time and destroy

create
(
const root: TgpuRoot
root
, (
frame: frames.Frame
frame
) => {
if (
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.fit(): boolean

Rebuilds all textures if the source size changed, destroying the old textures. Updates regions for the current resolution. Returns true after a rebuild so callers can update bind groups that hold old textures. Call before recording passes that use the targets. Throws after destroy.

fit
())
const part: bindings.Bound<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>
part
.
Bound<{ readonly depth: { readonly texture: WgslTextureDepth2d; }; }>.replace(resources: Partial<bindings.Resources<{
readonly depth: {
readonly texture: d.WgslTextureDepth2d;
};
}>>): void

Replaces resources without creating pipelines. The next shared group uses the replacements. Throws if a pipeline already binds the resources directly, because that pipeline would keep the old resources.

replace
({
depth?: GPUTextureView | GPUTexture | (SampledFlag & TgpuTexture<{
size: readonly number[];
format: GPUTextureFormat;
dimension?: "2d" | undefined;
sampleCount?: 1 | undefined;
}>) | TgpuTextureView<d.WgslTexture<WgslTextureProps>> | undefined
depth
:
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
readonly depth: {
readonly format: "depth24plus";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; readonly depth: { readonly format: "depth24plus"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
readonly depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
depth: targets.Texture<{
readonly format: "depth24plus";
readonly sampled: true;
}>
depth
});
// the scene, into hdr and depth
const
const output: TgpuRenderPass
output
=
frame: frames.Frame
frame
.
Frame.encoder: TgpuCommandEncoder

Records commands for submission after the recorder returns. submit() replaces this encoder. Read it at each call instead of keeping it, and let the frame submit it.

encoder
.
TgpuCommandEncoder.beginRenderPass(descriptor: TgpuRenderPassDescriptor): TgpuRenderPass

Begins recording a render pass. Attachment views accept TypeGPU textures, texture views and canvas contexts, next to raw

GPUTextureView

s.

beginRenderPass
({
TgpuRenderPassDescriptor.colorAttachments?: ColorAttachment | readonly (ColorAttachment | null)[] | undefined
colorAttachments
: {
ColorAttachment.view: GPUTextureView | TgpuTextureRenderView | (ColorTextureConstraint & RenderFlag) | TgpuTextureView<d.WgslTexture<WgslTextureProps>> | GPUCanvasContext

A

GPUTextureView

describing the texture subresource that will be output to for this color attachment.

view
:
const surface: web.Surface
surface
.
Surface.view(frame: frames.Frame): GPUTextureView

Returns a view of the current canvas texture, created once per texture. Applies pending size changes first, which a pass using context would skip.

view
(
frame: frames.Frame
frame
) },
});
const output: TgpuRenderPass
output
.
TgpuRenderCommands.setBindGroup<Record<string, TgpuLayoutEntry | null>>(bindGroupLayout: TgpuBindGroupLayout<Record<string, TgpuLayoutEntry | null>>, bindGroup: GPUBindGroup | TgpuBindGroup<Record<string, TgpuLayoutEntry | null>>): void (+1 overload)

Associates a bind group with the given layout

setBindGroup
(
const shared: bindings.Shared
shared
.
Shared.layout: TgpuBindGroupLayout<Record<string, TgpuLayoutEntry | null>>

Contains every part's entries. Each entry is named key_name using the part's key in share.

layout
,
const shared: bindings.Shared
shared
.
Shared.group(): TgpuBindGroup

Returns the group for the current resources, for pass.setBindGroup(shared.layout, …). Creates a group only when a resource has changed. Otherwise, it allocates nothing.

group
());
const final: finish.Finish
final
.
Finish.draw(pass: TgpuRenderCommands): void

Draws into the caller's pass, which usually targets the canvas.

draw
(
const output: TgpuRenderPass
output
);
const output: TgpuRenderPass
output
.
TgpuRenderPass.end(): void

Completes the recording of this render pass

end
();
}),
);
  • fit() returns true after it rebuilds the targets. Replacing the part’s texture then keeps the pipeline. Without the share, replace throws at the first resize; bind the part with unshared to have finish.create’s pipeline throw as it builds instead.
  • With glow, the final pass uses 3 bind groups and the shared group is the fourth, WebGPU’s limit.
  • The final pass still encodes, vignettes and dithers what the grade returns. Pass encode: false and set vignette and dither to 0 to see the stored values unchanged.

The same grade shows what a module computes on the way to the picture. Ambient occlusion stores visibility in red, so a final pass can take its output as the source and show red as grey:

const
const grey: (color: d.v3f) => d.v3f
grey
= (
color: d.v3f
color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
) => {
'use gpu';
return
import d
d
.
const vec3f: d.Vec3f
(xyz: number) => d.v3f (+5 overloads)
vec3f
(
color: d.v3f
color
.
v3f.x: number
x
);
};
const
const occlusionView: finish.Finish
occlusionView
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
: 'bgra8unorm',
Options.source: (() => finish.FloatTexture) | finish.Rendered

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
:
const occlusion: ao.Ao
occlusion
,
Options.tone?: Tone | null | undefined

Maps linear source colours to display range with aces, neutral, reinhard or a custom curve. Use null for sources already in display range. Default neutral.

tone
: null,
Options.encode?: boolean | undefined

Applies the sRGB transfer function in the shader. Use false for encoded colours, such as pixel art painted in screen colours. Formats ending in -srgb encode on storage. Default true unless format ends in -srgb.

encode
: false,
Options.grade?: ((color: d.v3f, uv: d.v2f) => d.v3f) | undefined

Grades or tints the colour after tone mapping and before the vignette, encoding and dither. It receives the colour and output UV coordinates from 0 to 1. Read other pixels with finish.picture(uv) and glow with finish.glow(uv). Default none.

grade
:
const grey: (color: d.v3f) => d.v3f
grey
,
Options.settings?: Partial<d.InferInput<d.WgslStruct<{
exposure: d.F32;
vignette: d.F32;
dither: d.F32;
glowIntensity: d.F32;
}>>> | undefined

Sets initial values over defaults. Default defaults, with exposure 1, vignette 0.35, dither 1 and glowIntensity 0.12.

settings
: {
vignette: number
vignette
: 0,
dither: number
dither
: 0 },
});

A surface with alphaMode: 'premultiplied' shows what is behind it, such as a web page. Draw the scene in premultiplied colour into a target cleared to transparent, and give the final pass alpha: 'premultiplied':

const
const surface: web.Surface
surface
=
import web
web
.
function surface(root: TgpuRoot, canvas: HTMLCanvasElement, options?: web.SurfaceOptions): web.Surface

Configures a canvas for the root. Matches its drawing buffer to the layout size or the fixed options.size. Throws if the fixed size is invalid.

@param ― canvas the canvas it configures and sizes

@returns ― a surface you draw into and destroy

surface
(
const root: TgpuRoot
root
,
var document: Document

window.document returns a reference to the document contained in the window.

MDN Reference

document
.
ParentNode.querySelector<"canvas">(selectors: "canvas"): HTMLCanvasElement | null (+4 overloads)

Returns the first element that is a descendant of node that matches selectors.

MDN Reference

querySelector
('canvas')!, {
CanvasOptions.alphaMode?: GPUCanvasAlphaMode | undefined

Controls canvas transparency. With premultiplied, alpha below 1 reveals what is behind the canvas, and colours must be premultiplied by alpha. Default opaque.

alphaMode
: 'premultiplied',
});
const
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
=
import targets
targets
.
function create<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>(root: TgpuRoot, source: targets.SizeSource, specs: {
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}, options?: targets.Options): targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>

Creates render targets that follow a size. Replaces the whole set when the size changes. Put targets needed only sometimes in their own set and create that set when needed. Throws for transient targets with sampling, storage or mip levels, for multisampled targets with storage or mip levels, and for layered targets that are multisampled, mipped or have more layers than the device allows.

@param ― source the size it follows, such as a surface

@param ― specs a format and options per target, by name

@param ― options.resolution the fraction of each side drawn, which changes without allocating

@returns ― a set of targets you fit every frame and destroy

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
, {
hdr: {
readonly format: "rgba16float";
readonly sampled: true;
}
hdr
: {
format: "rgba16float"
format
: 'rgba16float',
sampled: true
sampled
: true } });
const
const glow: bloom.Bloom
glow
=
import bloom
bloom
.
function create(root: TgpuRoot, options: bloom.Options): bloom.Bloom

Creates bloom from a chain of half-size textures. Each level downsamples with 13 taps, then a tent filter adds it to the next larger level. The result is mip 0 of output, at half the source size. With region, passes draw and sample only that region; changing the region allocates nothing.

@allocates ― on resize: a new chain and the bind groups of its passes; so does a new source

@owns ― targets, a sampler, 3 pipelines, 2 bind groups and a buffer per level: the chain of glow levels, the settings, a group for each pass, and the region each pass reads

@records ― record after whatever wrote the source; then output holds the glow.

@param ― options.source the HDR picture, read at every record

@param ― options.region the part of the source that holds the picture, read at every record

@returns ― a bloom you record every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the HDR picture: a rendered picture, such as aa, or a function returning a texture, such as () => screen.textures.hdr. record reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
});
const
const final: finish.Finish
final
=
import finish
finish
.
function create(root: TgpuRoot, options: finish.Options): finish.Finish

Creates the final pass of a frame. It reads the source, adds glow, tone maps, grades, applies the vignette, encodes and dithers. It draws one triangle into the caller pass.

@allocates ― on resize: a bind group for a new source or glow

@owns ― 2 buffers, a sampler, a pipeline, 2 bind groups: the settings, the regions it reads, and a group each for the source and the glow

@records ― record into a pass of its own on the view you give, usually the canvas's, or draw into the caller's pass, after the bloom's record.

@param ― options.source the picture, rendered or a texture, read at every draw

@param ― options.region the part of the source that holds the picture, read at every draw

@param ― options.glow a bloom, or a glow of your own, added before tone mapping

@param ― options.exposure an adapted exposure, read on the GPU

@returns ― a final pass you draw every frame and destroy

create
(
const root: TgpuRoot
root
, {
Options.format: GPUTextureFormat

Sets the output format, such as the canvas format.

format
:
const surface: web.Surface
surface
.
Surface.format: GPUTextureFormat
format
,
Options.source: finish.Rendered | (() => finish.FloatTexture)

Provides the picture at any size: a rendered picture, such as aa or blur, or a function returning a texture, such as () => screen.textures.hdr. draw reads it on every call, so the texture can be rebuilt.

source
: () =>
const screen: targets.Targets<{
readonly hdr: {
readonly format: "rgba16float";
readonly sampled: true;
};
}>
screen
.
Targets<{ readonly hdr: { readonly format: "rgba16float"; readonly sampled: true; }; }>.textures: {
readonly hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>;
}

Provides the current textures. A size change replaces them. Throws after destroy.

textures
.
hdr: targets.Texture<{
readonly format: "rgba16float";
readonly sampled: true;
}>
hdr
,
Options.alpha?: "opaque" | "premultiplied" | undefined

Selects output alpha handling. premultiplied requires premultiplied source colour, preserves alpha and raises it where glow adds light. The output remains premultiplied so a surface can show its background. Default opaque, which writes alpha 1.

alpha
: 'premultiplied',
Options.glow?: finish.Rendered | undefined

Adds a bloom, or a glow of your own such as an emission target, before tone mapping. draw reads its output and region on every call. Default none.

glow
,
});
  • Divide out alpha before the curve and grade. Multiply it back after dither so clear pixels stay clear and noise does not show over the page.
  • The glow raises the alpha by its brightest channel, so it shows over the page around what casts it.
  • The vignette fades the corners out, colour and alpha together, instead of darkening them, which would tint the page.

Custom screen shaders can finish with a tone curve, linearToSrgb from @typegpu/color and tone.dither(encoded, pixel, steps) to reduce banding. Import curves, luminance and dither from @typegpu/kit/tone without creating bloom or a final pass.

import {
import tone
tone
} from '@typegpu/kit';
import {
const linearToSrgb: TgpuFn<(linear: d.Vec3f) => d.Vec3f>
linearToSrgb
} from '@typegpu/color';
const
const shown: (color: d.v3f, position: d.v4f) => d.v4f
shown
= (
color: d.v3f
color
:
import d
d
.
export v3f

Interface representing its WGSL vector type counterpart: vec3f or vec3. A vector with 3 elements of type f32

v3f
,
position: d.v4f
position
:
import d
d
.
export v4f

Interface representing its WGSL vector type counterpart: vec4f or vec4. A vector with 4 elements of type f32

v4f
) => {
'use gpu';
return
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
import tone
tone
.
const dither: (encoded: d.v3f, pixel: d.v2f, steps: number) => d.v3f

Adds triangular noise to encoded colour, seeded by pixel. steps sets the amplitude in 8-bit steps. This reduces banding in dark 8-bit gradients. The final pass applies it before storing output.

dither
(
function linearToSrgb(linear: d.v3f): d.v3f
linearToSrgb
(
import tone
tone
.
const neutral: (input: d.v3f) => d.v3f

Applies Khronos PBR Neutral. It preserves hue and saturation below the highlights, then reduces them.

neutral
(
color: d.v3f
color
)),
position: d.v4f
position
.
xy: d.v2f
xy
, 1), 1);
};
Part
final.settings Exposure, vignette, dither and glowIntensity, a uniform.
glow.settings Threshold, knee and spread, a uniform.
glow.output, glow.region Bloom chain with glow at level 0 and its region. Read each frame because resizing replaces the texture.
  • tone and grade compile into pipelines. Create another final pass to switch them at runtime.
  • Built-in curves do not use Oklab. Per-channel curves shift orange toward yellow and blue toward cyan. Supply a custom tone using conversions from @typegpu/color.
  • linearToSrgb passes negative values through; a unorm target clamps them as it stores.
  • exposure meters with one compute pass of one 256-thread workgroup, the same at any resolution. Pixels darker than 0.0001 count as 0.0001, and every sample weighs the same, with no weighting towards the centre.
  • Final-pass stage order is fixed. For a custom vignette or encoding, implement it in grade and set vignette: 0 and encode: false.