Skip to content

@typegpu/kit, @typegpu/kit/geometrylines · polylines

Lines

Polylines with joins, caps and dashes, a whole set in one draw.

kit.lines

module

Draws polylines as a drawing for pass descriptions, in one draw for a whole set.

lines.create(root, { capacity, length, point?, … }) → Lines

rootborrows
the root
point?takes
a 'use gpu' function; it, count and shade carry the fills of what they read, such as tgpu.fn(curve).pipe(noise.inject())
Linesreturns
a set of lines you destroy
GPU functions
dash
schemas
PointFragmentCornerSeen
helpers
joinscaps
types
JoinBlendLinesWriteOptions
Optionscreate · 14 fields
capacity

number

Sets the number of lines to an integer of at least 1.

length

number

Sets the most points a line holds to an integer of at least 2.

units?

'world' | 'points'= world

Selects world for widths in world units, or points for widths that keep their size on screen, in points of the pass's passes.pixels.

closed?

boolean= false

Joins each line's last point to its first.

point?

(line: number, i: number) => d.Infer<typeof Point>= the point buffer

Returns point i of line in a 'use gpu' function, evaluated as the lines draw, in place of the set's point buffer.

count?

(line: number) => number= the count buffer, or length with point

Returns how many points line holds in a 'use gpu' function, up to length.

join?

Join= joins.round

Selects the join between segments, from lines.joins.

miterLimit?

number= 2

Limits a miter join's length, relative to the line's half width, before it bends back towards a bevel.

startCap?

Join= caps.round

Selects the cap at each line's first point, from lines.caps.

endCap?

Join= caps.round

Selects the cap at each line's last point, from lines.caps.

shade?

(fragment: Fragment) => d.v4f= the point colour times the pixel's coverage

Returns premultiplied colour for a pixel.

blend?

Blend= over

Selects over or add to blend in draw order, opaque to write depth, translucent for the passes' translucency targets, or a blend state.

minWidth?

number | { readonly pixels: number }= 1 point

Draws lines narrower than this many points, or { pixels } pixels, at this width, so distant lines stay whole.

label?

string= lines

Sets the drawing pipeline label.

records
  • drawing.drawrender

drawing, added to passes, after the kernels that write the points.

owns

1 to 3 buffers

The segment's indices, the points unless point, and the counts unless point or count.

lifecycle

destroy()[Symbol.dispose]

injects
  • per-instance

    setPoint, setCount, pointAt and held, for kernels piped with it.

binds
  • 2 storage · 1 uniform

    The drawing, the points and the counts, and passes.pixels.

  • 2 storage

    setPoint, setCount, pointAt or held, the points and the counts.

One set draws the four rows. In world units the far rows narrow with distance; in points every row keeps the width the middle row has, as hairlines and labels would. Changing the join, caps or units rebuilds the set.

WebGPU’s line primitive is one pixel wide and aliased, so paths, contours, graphs and outlines need triangles. Building those triangles on the CPU means new vertices whenever a point moves, and corners that crack at sharp turns. A set of lines stores points in a buffer and shapes every segment on the GPU, with joins and caps. One draw renders the whole set, and each pixel’s coverage comes from its distance to the line, so blended edges stay smooth with or without MSAA.

import {
import lines
lines
} from '@typegpu/kit';
const
const paths: lines.Lines
paths
=
import lines
lines
.
function create(root: TgpuRoot, options: lines.Options): lines.Lines

Creates a set of polylines drawn in one draw. They are shaped for the camera of the pass description they are drawn in, which point, count and shade read too. Throws unless capacity is an integer of at least 1 and length an integer of at least 2.

@param ― options.point a 'use gpu' function; it, count and shade carry the fills of what they read, such as tgpu.fn(curve).pipe(noise.inject())

@returns ― a set of lines you destroy

create
(
const root: TgpuRoot
root
, {
Options.capacity: number

Sets the number of lines to an integer of at least 1.

capacity
: 2,
Options.length: number

Sets the most points a line holds to an integer of at least 2.

length
: 64 });
const
const canvas: passes.Passes<{
readonly color: typeof passes.color;
}>
canvas
=
import passes
passes
.
function create(root: TgpuRoot, screen: passes.Screen, options?: passes.Options): passes.Passes<{
readonly color: typeof passes.color;
}> (+1 overload)

Describes the targets and outputs for scene passes. With a surface such as the canvas, writes only each renderer's colour. With a target set, writes colour into the target marked color, depth into the target marked depth, and other outputs into their named targets. Multisampled targets resolve according to resolve. Targets with layers draw one pass per layer, and every target the description draws into needs the same layers. add builds a renderer's drawing. begin starts a pass. pipe supplies fills such as a camera.

@param ― screen the surface, or the targets, which give the formats; each one the description names is drawn into

@param ― writes what fills each target, by its key in screen

@returns ― a description you destroy, which holds the pipelines it builds

create
(
const root: TgpuRoot
root
,
const surface: web.Surface
surface
).
Passes<{ readonly color: typeof color; }>.pipe(fill: Inject): passes.Passes<{
readonly color: typeof passes.color;
}>

Adds fill after existing fills and returns a description of the same passes. Piped fills take precedence over a drawing's own. For example, passes.create(…).pipe(view.inject()) uses the camera from view.

pipe
(
const view: cameras.OrbitCamera
view
.
CameraBase.inject(): Inject

Fills cameras.current for the pipelines created from the configuration it is piped into.

inject
());
const
const drawn: passes.Drawn
drawn
=
const canvas: passes.Passes<{
readonly color: typeof passes.color;
}>
canvas
.
Passes<{ readonly color: typeof color; }>.add<WgslStruct<{
position: Vec4f;
world: Vec3f;
normal: Vec3f;
color: Vec4f;
along: F32;
segment: Vec3f;
radii: Vec2f;
rim: Vec4f;
ends: U32;
line: U32;
}>, {
world: v3f;
normal: v3f;
color: v4f;
}>(this: passes.Passes<...>, drawing: passes.Drawing<WgslStruct<{
position: Vec4f;
world: Vec3f;
normal: Vec3f;
color: Vec4f;
along: F32;
segment: Vec3f;
radii: Vec2f;
rim: Vec4f;
ends: U32;
line: U32;
}>, {
world: v3f;
normal: v3f;
color: v4f;
}>): passes.Drawn (+1 overload)

Builds a pipeline for drawing that writes every described output and returns its draw methods. If the drawing cannot supply the fields an output needs, type checking fails with that output's name. Use without to leave it out or instead to replace it.

add
(
const paths: lines.Lines
paths
.
Lines.drawing: passes.Drawing<WgslStruct<{
position: Vec4f;
world: Vec3f;
normal: Vec3f;
color: Vec4f;
along: F32;
segment: Vec3f;
radii: Vec2f;
rim: Vec4f;
ends: U32;
line: U32;
}>, {
world: v3f;
normal: v3f;
color: v4f;
}> & {
inject(): Inject;
}

Draws the shown lines in one draw when added to a pass description.

drawing
);
// A spiral, wider towards its end
const
const spiral: Float32Array<ArrayBuffer>
spiral
= new
var Float32Array: Float32ArrayConstructor
new (length: number) => Float32Array<ArrayBuffer> (+6 overloads)
Float32Array
(64 * 3);
const
const widths: Float32Array<ArrayBuffer>
widths
= new
var Float32Array: Float32ArrayConstructor
new (length: number) => Float32Array<ArrayBuffer> (+6 overloads)
Float32Array
(64);
for (let
let i: number
i
= 0;
let i: number
i
< 64;
let i: number
i
++) {
const
const angle: number
angle
=
let i: number
i
* 0.3;
const spiral: Float32Array<ArrayBuffer>
spiral
.
Float32Array<ArrayBuffer>.set(array: ArrayLike<number>, offset?: number): void

Sets a value or an array of values.

@param ― array A typed or untyped array of values to set.

@param ― offset The index in the current array at which the values are to be written.

set
([
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.cos(x: number): number

Returns the cosine of a number.

@param ― x A numeric expression that contains an angle measured in radians.

cos
(
const angle: number
angle
) *
let i: number
i
* 0.04,
let i: number
i
* 0.02,
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.sin(x: number): number

Returns the sine of a number.

@param ― x A numeric expression that contains an angle measured in radians.

sin
(
const angle: number
angle
) *
let i: number
i
* 0.04],
let i: number
i
* 3);
const widths: Float32Array<ArrayBuffer>
widths
[
let i: number
i
] = 0.02 +
let i: number
i
* 0.002;
}
const paths: lines.Lines
paths
.
Lines.write(line: number, positions: ArrayLike<number>, options?: lines.WriteOptions): void

Writes line from x, y and z coordinates, keeping at most length points, and sets its count when the set has a count buffer. Fills along with the distance from the first point. width and color give one value for the line or one per point. Use write(line, []) to empty a line.

write
(0,
const spiral: Float32Array<ArrayBuffer>
spiral
, {
WriteOptions.width?: number | ArrayLike<number> | undefined

Sets the width, or one width per kept point. Default 1.

width
:
const widths: Float32Array<ArrayBuffer>
widths
,
WriteOptions.color?: ArrayLike<number> | undefined

Sets r, g, b and a for the line, or four values per point. Default opaque white.

color
: [0.3, 0.8, 1, 1] });
const paths: lines.Lines
paths
.
Lines.write(line: number, positions: ArrayLike<number>, options?: lines.WriteOptions): void

Writes line from x, y and z coordinates, keeping at most length points, and sets its count when the set has a count buffer. Fills along with the distance from the first point. width and color give one value for the line or one per point. Use write(line, []) to empty a line.

write
(1, [-2, 0, 0, 2, 0, 0], {
WriteOptions.width?: number | ArrayLike<number> | undefined

Sets the width, or one width per kept point. Default 1.

width
: 0.05 });
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 canvas: passes.Passes<{
readonly color: typeof passes.color;
}>
canvas
.
Passes<{ readonly color: typeof color; }>.record(frame: frames.Frame | TgpuCommandEncoder, drawn: passes.Drawn | readonly passes.Drawn[], options?: passes.BeginOptions<{
readonly color: typeof passes.color;
}> | undefined): void

Begins a pass, draws drawn or each drawing in a list in order, then ends the pass. Reuse the list to avoid allocating each frame. Call begin yourself when you need to choose drawings per frame. Records nothing while enabled is false.

record
(
frame: frames.Frame
frame
,
const drawn: passes.Drawn
drawn
)));
  • write(line, positions, options) replaces a line with x, y and z values and keeps at most length points. width and color take one value for the line or one per point. write(line, []) empties it.
  • canvas.add(paths.drawing) builds the set’s pipeline into a pass description, shaped for the camera the description injects. Lines with fewer than two points draw nothing.
  • Each set’s options fix its pipeline, so lines with other joins, caps, units or blending go in another set.
  • paths.shown sets how many lines the draw covers, lines 0 to shown - 1. Size capacity for the most lines a scene can hold, and set shown as their number changes, so the lines past it cost no vertices.

Pipe paths.inject() into a kernel to write points with setPoint(line, i, point) and counts with setCount(line, count). write fills each point’s along with its distance from the first point; a kernel fills it itself, or leaves it 0 when nothing reads it. The survey model traces 41 contour rings in a kernel this way and draws them in one draw.

const
const waves: lines.Lines
waves
=
import lines
lines
.
function create(root: TgpuRoot, options: lines.Options): lines.Lines

Creates a set of polylines drawn in one draw. They are shaped for the camera of the pass description they are drawn in, which point, count and shade read too. Throws unless capacity is an integer of at least 1 and length an integer of at least 2.

@param ― options.point a 'use gpu' function; it, count and shade carry the fills of what they read, such as tgpu.fn(curve).pipe(noise.inject())

@returns ― a set of lines you destroy

create
(
const root: TgpuRoot
root
, {
Options.capacity: number

Sets the number of lines to an integer of at least 1.

capacity
: 16,
Options.length: number

Sets the most points a line holds to an integer of at least 2.

length
: 128 });
const
const trace: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
trace
=
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
.
computeFn: <{
gid: d.BuiltinGlobalInvocationId;
}>(options: {
in: {
gid: d.BuiltinGlobalInvocationId;
};
workgroupSize: number[];
}) => TgpuComputeFnShell<{
gid: d.BuiltinGlobalInvocationId;
}> (+1 overload)
computeFn
({
workgroupSize: number[]
workgroupSize
: [64],
in: {
gid: d.BuiltinGlobalInvocationId;
}
in
: {
gid: d.BuiltinGlobalInvocationId
gid
:
import d
d
.
const builtin: {
readonly vertexIndex: d.BuiltinVertexIndex;
readonly instanceIndex: d.BuiltinInstanceIndex;
readonly clipDistances: d.BuiltinClipDistances;
readonly position: d.BuiltinPosition;
readonly frontFacing: d.BuiltinFrontFacing;
readonly fragDepth: d.BuiltinFragDepth;
readonly primitiveIndex: d.BuiltinPrimitiveIndex;
readonly sampleIndex: d.BuiltinSampleIndex;
... 11 more ...;
readonly numSubgroups: d.BuiltinNumSubgroups;
}
export builtin
builtin
.
globalInvocationId: d.BuiltinGlobalInvocationId
globalInvocationId
},
})(({
gid: d.v3u
gid
}) => {
'use gpu';
if (
gid: d.v3u
gid
.
v3u.x: number
x
>= 16 * 128) return;
const
const line: number
line
=
import std
std
.
function intdiv(lhs: number, rhs: number): number
export intdiv

Performs integer division on the passed in scalars. Equivalent to trunc(trunc(lhs) / trunc(rhs)). Coerces both arguments to integers if they're floating point.

intdiv
(
gid: d.v3u
gid
.
v3u.x: number
x
,
import d
d
.
function u32(v?: number | boolean): number
export u32

A schema that represents an unsigned 32-bit integer value. (equivalent to u32 in WGSL)

Can also be called to cast a value to an u32 in accordance with WGSL casting rules.

@example const value = u32(); // 0

@example const value = u32(7); // 7

@example const value = u32(3.14); // 3

@example const value = u32(-1); // 4294967295

@example const value = u32(-3.1); // 0

u32
(128));
const
const i: number
i
=
gid: d.v3u
gid
.
v3u.x: number
x
% 128;
const
const x: number
x
=
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
const i: number
i
) / 127 - 0.5;
const
const position: d.v3f
position
=
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(
const x: number
x
* 8,
import std
std
.
function sin(value: number): number (+1 overload)
export sin
sin
(
const x: number
x
* 12 +
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
const line: number
line
)) * 0.4,
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
const line: number
line
) * 0.3);
const waves: lines.Lines
waves
.
Lines.setPoint: (line: number, i: number, point: d.Infer<typeof lines.Point>) => void

Writes point i of line from a kernel created from root.pipe(paths.inject()). Requires the point buffer.

setPoint
(
const line: number
line
,
const i: number
i
,
import lines
lines
.
const Point: d.WgslStruct
(props?: {
position: d.v3f;
width: number;
color: d.v4f;
along: number;
} | undefined) => {
position: d.v3f;
width: number;
color: d.v4f;
along: number;
}

This schema holds a point of a line.

Point
({
position: d.v3f

Holds the point in world space, or in a flat camera's plane with z as depth.

position
,
width: number

Sets the full width at this point, in the set's units.

width
: 0.04,
color: d.v4f

Sets linear colour and opacity, not premultiplied.

color
:
import d
d
.
const vec4f: d.Vec4f
(xyzw: number) => d.v4f (+9 overloads)
vec4f
(1),
along: number

Measures distance from the line's start, for dashes and patterns. write fills it. A closed line's last segment continues it by the segment's length in world units.

along
:
const x: number
x
}));
if (
const i: number
i
=== 0)
const waves: lines.Lines
waves
.
Lines.setCount: (line: number, count: number) => void

Sets how many points line holds from a kernel. Requires the count buffer.

setCount
(
const line: number
line
, 128);
});
const
const tracing: TgpuComputePipeline
tracing
=
const root: TgpuRoot
root
.
WithBinding.pipe(transform: (cfg: Configurable) => Configurable): WithBinding
pipe
(
const waves: lines.Lines
waves
.
Lines.inject(): Inject

Fills the set's accessors for pipelines created from the configuration you pipe it into.

inject
()).
WithBinding.createComputePipeline<{
gid: d.BuiltinGlobalInvocationId;
}>(descriptor: TgpuComputePipeline.Descriptor<{
gid: d.BuiltinGlobalInvocationId;
}>): TgpuComputePipeline
createComputePipeline
({
compute: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
compute
:
const trace: TgpuComputeFn<{
gid: d.BuiltinGlobalInvocationId;
}>
trace
});

Record the kernel before the pass that draws the set. pointAt(line, i) and held(line) read the set back in other shaders.

Set point to a 'use gpu' function to evaluate each point as the lines draw, with no buffer. count returns how many points a line holds, length by default. A curve that changes with time then needs only a uniform:

const
const time: TgpuUniform<d.F32>
time
=
const root: TgpuRoot
root
.
TgpuRoot.createUniform<d.F32>(typeSchema: d.F32, initial?: number | ((buffer: TgpuBuffer<NoInfer<d.F32>>) => void) | undefined): TgpuUniform<d.F32> (+1 overload)

Allocates memory on the GPU, allows passing data between host and shader. Read-only on the GPU, optimized for small data. For a general-purpose buffer, use

TgpuRoot.createBuffer

.

@param ― typeSchema The type of data that this buffer will hold.

@param ― initial Either initial value of the buffer, or an initializer to execute on the mapped buffer. (optional)

createUniform
(
import d
d
.
const f32: d.F32
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
);
const
const rings: lines.Lines
rings
=
import lines
lines
.
function create(root: TgpuRoot, options: lines.Options): lines.Lines

Creates a set of polylines drawn in one draw. They are shaped for the camera of the pass description they are drawn in, which point, count and shade read too. Throws unless capacity is an integer of at least 1 and length an integer of at least 2.

@param ― options.point a 'use gpu' function; it, count and shade carry the fills of what they read, such as tgpu.fn(curve).pipe(noise.inject())

@returns ― a set of lines you destroy

create
(
const root: TgpuRoot
root
, {
Options.capacity: number

Sets the number of lines to an integer of at least 1.

capacity
: 8,
Options.length: number

Sets the most points a line holds to an integer of at least 2.

length
: 96,
Options.closed?: boolean | undefined

Joins each line's last point to its first. Default false.

closed
: true,
Options.point?: ((line: number, i: number) => d.Infer<typeof lines.Point>) | undefined

Returns point i of line in a 'use gpu' function, evaluated as the lines draw, in place of the set's point buffer. Default the point buffer.

point
: (
line: number
line
,
i: number
i
) => {
'use gpu';
const
const angle: number
angle
= (
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
i: number
i
) / 96) *
var Math: Math

An intrinsic object that provides basic mathematics functionality and constants.

Math
.
Math.PI: number

Pi. This is the ratio of the circumference of a circle to its diameter.

PI
* 2;
const
const radius: number
radius
= 1 +
import d
d
.
function f32(v?: number | boolean): number
export f32

A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)

Can also be called to cast a value to an f32.

@example const value = f32(); // 0

@example const value = f32(1.23); // 1.23

@example const value = f32(true); // 1

f32
(
line: number
line
) * 0.3 +
import std
std
.
function sin(value: number): number (+1 overload)
export sin
sin
(
const angle: number
angle
* 5 +
const time: TgpuUniform<d.F32>
time
.
TgpuUniform<F32>.$: number
$
) * 0.05;
return
import lines
lines
.
const Point: d.WgslStruct
(props?: {
position: d.v3f;
width: number;
color: d.v4f;
along: number;
} | undefined) => {
position: d.v3f;
width: number;
color: d.v4f;
along: number;
}

This schema holds a point of a line.

Point
({
position: d.v3f

Holds the point in world space, or in a flat camera's plane with z as depth.

position
:
import d
d
.
const vec3f: d.Vec3f
(x: number, y: number, z: number) => d.v3f (+5 overloads)
vec3f
(
import std
std
.
function cos(value: number): number (+1 overload)
export cos
cos
(
const angle: number
angle
), 0,
import std
std
.
function sin(value: number): number (+1 overload)
export sin
sin
(
const angle: number
angle
)) *
const radius: number
radius
,
width: number

Sets the full width at this point, in the set's units.

width
: 0.03,
color: d.v4f

Sets linear colour and opacity, not premultiplied.

color
:
import d
d
.
const vec4f: d.Vec4f
(x: number, y: number, z: number, w: number) => d.v4f (+9 overloads)
vec4f
(0.9, 0.7, 0.3, 1),
along: number

Measures distance from the line's start, for dashes and patterns. write fills it. A closed line's last segment continues it by the segment's length in world units.

along
: 0,
});
},
});

closed: true joins each line’s last point to its first. A function that reads another module carries that module’s fills, as in point: tgpu.fn(curve).pipe(noise.inject()).

Widths are in world units by default, so lines narrow with distance like the things around them. With units: 'points', widths are in points of the pass’s passes.pixels, so a 1-point hairline, a graph or an outline keeps its size at any distance and on any display.

A line narrower than minWidth points, 1 by default, is drawn that wide. A blended line fades by how much narrower it is, so a far line fades and doesn’t break into dots. minWidth: { pixels: 1 } gives the least width in pixels instead, so a hairline stays one pixel wide on a 2x display, where a point is two.

shade(fragment) returns premultiplied colour for each pixel. The lines.Fragment it receives holds the line, its colour, along, how far across the line the pixel lies, the coverage to multiply alpha by and a tube’s normal for lighting. lines.dash(fragment, on, off) returns antialiased dashes measured in the units of along:

const
const enamel: (fragment: lines.Fragment) => d.v4f
enamel
= (
fragment: {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}
fragment
:
import lines
lines
.
type Fragment = {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}

This schema describes a pixel of a line passed to shade.

Fragment
) => {
'use gpu';
const
const point: {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}
point
=
import shading
shading
.
const pointAt: (position: d.v3f, normal: d.v3f) => {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}

Creates a point at position with surface normal normal, viewed from the kit camera.

pointAt
(
fragment: {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}
fragment
.
position: d.v3f

Holds the point on the centre line in world space.

position
,
fragment: {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}
fragment
.
normal: d.v3f

Holds the unit normal of a tube along the line: towards the viewer at the centre and sideways at the edges, for lighting.

normal
);
const
const lit: d.v3f
lit
=
import shading
shading
.
const shade: (material: shading.Material, point: shading.Point) => d.v3f

Computes the final surface colour. It combines ambient light, shadowed sunlight, live point lights and emission, then applies fog. Every channel stays at or below 65 504, the largest half float. Create the pipeline from root.pipe(light.inject()) after injecting the kit camera.

shade
(
import materials
materials
.
const glossy: (color: d.v3f, roughness: number) => {
color: d.v3f;
roughness: number;
emission: d.v3f;
metallic: number;
occlusion: number;
}

Creates a dielectric with specular highlights, such as paint, plastic or wet stone.

glossy
(
fragment: {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}
fragment
.
color: d.v4f

Holds the colour of the points, blended along the segment.

color
.
xyz: d.v3f
xyz
, 0.4),
const point: {
position: d.v3f;
normal: d.v3f;
view: d.v3f;
uv: d.v2f;
uv1: d.v2f;
}
point
);
const
const alpha: number
alpha
=
fragment: {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}
fragment
.
coverage: number

Gives the share of the pixel the line covers, from 0 to 1. A blended line drawn wider than its width to meet minWidth covers less by how much narrower it is.

coverage
*
import lines
lines
.
const dash: (fragment: lines.Fragment, on: number, off: number) => number

Returns the coverage of dashes on long with gaps off long, measured along the line in the units of along, with antialiased ends. Multiply a shade's alpha by it.

dash
(
fragment: {
line: number;
position: d.v3f;
normal: d.v3f;
color: d.v4f;
along: number;
perPixel: number;
across: number;
edge: number;
coverage: number;
}
fragment
, 0.2, 0.1);
return
import d
d
.
const vec4f: d.Vec4f
(v0: AnyNumericVec3Instance, w: number) => d.v4f (+9 overloads)
vec4f
(
const lit: d.v3f
lit
*
const alpha: number
alpha
,
const alpha: number
alpha
);
};
const
const tubes: lines.Lines
tubes
=
import lines
lines
.
function create(root: TgpuRoot, options: lines.Options): lines.Lines

Creates a set of polylines drawn in one draw. They are shaped for the camera of the pass description they are drawn in, which point, count and shade read too. Throws unless capacity is an integer of at least 1 and length an integer of at least 2.

@param ― options.point a 'use gpu' function; it, count and shade carry the fills of what they read, such as tgpu.fn(curve).pipe(noise.inject())

@returns ― a set of lines you destroy

create
(
const root: TgpuRoot
root
, {
Options.capacity: number

Sets the number of lines to an integer of at least 1.

capacity
: 4,
Options.length: number

Sets the most points a line holds to an integer of at least 2.

length
: 32,
Options.shade?: ((fragment: lines.Fragment) => d.v4f) | undefined

Returns premultiplied colour for a pixel. Default the point colour times the pixel's coverage.

shade
:
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: <(fragment: lines.Fragment) => d.v4f>(inner: (fragment: lines.Fragment) => d.v4f) => TgpuGenericFn<(fragment: lines.Fragment) => d.v4f> (+2 overloads)
fn
(
const enamel: (fragment: lines.Fragment) => d.v4f
enamel
).
TgpuGenericFn<(fragment: lines.Fragment) => v4f>.pipe(transform: (cfg: Configurable) => Configurable): TgpuGenericFn<(fragment: lines.Fragment) => d.v4f>

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

pipe
(
const light: lighting.Lighting
light
.
Lighting.inject(): Inject

Fills the shading hooks and resources for pipelines created from the configuration you pipe it into.

inject
()),
});

blend decides how a set reaches its targets:

  • over, the default, and add blend in draw order and test depth without writing it. Draw them after opaque drawings.
  • opaque writes depth, so lines hide one another and cast shadows. In a multisampled description, coverage turns into samples; elsewhere edges are cut at half coverage, and TAA smooths them. Into depth alone, such as a shadow map, a line keeps its own width, without minWidth or its antialiased edge.
  • translucent draws into the translucency targets, in any order.

Lines write every output the description lists. A segment that crosses the near plane, under standard or reversed depth, is cut there with no cap.

join shapes the corners between segments: lines.joins.round, .miter or .bevel. miterLimit sets how long a miter grows, in half widths, before it bends back. startCap and endCap shape each line’s first and last points: lines.caps.round, .square, .butt, .arrow, .triangle or .wedge. A round cap is six triangles, and a round join three on each side, so a segment takes 16 triangles in a set with round shapes and 12 in one without. Lines of two points never join, so only their caps count.

kit.polylines

shader

'use gpu' functions that place the vertices of lines, joins, caps and circles on the screen.

kind
shader
GPU functions
circlelineVariableWidthpolylineVariableWidth
accessors
arrowCapParamsSlotjoinSlotstartCapSlotendCapSlot
schemas
LineControlPointLineSegmentOutputJoinInput
helpers
circleVertexCountcapsjoinslineSegmentIndiceslineSegmentWireframeIndiceslineSegmentLeftIndices

@typegpu/kit/geometry shapes the lines. polylineVariableWidth(A, B, C, D, vertexIndex, triangles) places one vertex of the segment from B to C on the screen, given the points before and after it, with their radii. lineSegmentIndices(triangles) lists the triangles of one segment. triangles counts the triangles in each half of a join or cap; lines uses 3 for round shapes and 2 otherwise. A shader of your own can draw lines with them, and a join or cap is a 'use gpu' function with the signature of lines.joins.round.

Part
points A storage buffer of capacity * length points, line l in the length slots from l * length.
counts A storage buffer of each line’s point count.
indices An index buffer of one segment’s triangles, which every instance of the draw reuses.

Each point takes 48 bytes, so 41 lines of 192 points take 369 KiB. A set with point has neither buffer; one with count has no count buffer.

  • closed holds for a whole set. Mixing closed and open lines takes two sets.
  • Lines are shaped on the screen, so they always face the camera. A line lying on a surface seen at a low angle sinks into it by up to half its width; lift it by that much.
  • All lines in a set hold at most length points. Size it for the longest.
  • A repeated point ends the line there with endCap and starts it again with startCap. Unless both caps are round, write only points that moved.
  • Motion for TAA follows the camera. Points that move, written by a kernel or the CPU, carry no motion of their own.
  • point runs up to four times for each of a segment’s 14 or 18 vertices, and again for motion. Write an expensive curve into the set’s buffer from a kernel instead.