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.
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(
constroot: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.
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
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(
constview:cameras.OrbitCamera
view.
CameraBase.inject(): Inject
Fills cameras.current for the pipelines created from the configuration it is piped into.
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(
constpaths: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.
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,
constspiral:Float32Array<ArrayBuffer>
spiral,{
WriteOptions.width?: number | ArrayLike<number>|undefined
Sets the width, or one width per kept point. Default 1.
width:
constwidths: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.
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.
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
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,
constdrawn: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.
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(
constroot: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.
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.
functionu32(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.
@exampleconst value = u32(); // 0
@exampleconst value = u32(7); // 7
@exampleconst value = u32(3.14); // 3
@exampleconst value = u32(-1); // 4294967295
@exampleconst value = u32(-3.1); // 0
u32(128));
const
consti:number
i=
gid:d.v3u
gid.
v3u.x: number
x%128;
const
constx:number
x=
import d
d.
functionf32(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.
@exampleconst value = f32(); // 0
@exampleconst value = f32(1.23); // 1.23
@exampleconst value = f32(true); // 1
f32(
consti:number
i)/127-0.5;
const
constposition:d.v3f
position=
import d
d.
constvec3f:d.Vec3f
(x:number,y:number,z:number)=>d.v3f(+5 overloads)
vec3f(
constx:number
x*8,
import std
std.
functionsin(value:number):number(+1overload)
export sin
sin(
constx:number
x*12+
import d
d.
functionf32(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.
@exampleconst value = f32(); // 0
@exampleconst value = f32(1.23); // 1.23
@exampleconst value = f32(true); // 1
f32(
constline:number
line))*0.4,
import d
d.
functionf32(v?:number|boolean):number
export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
Writes point i of line from a kernel created from root.pipe(paths.inject()). Requires
the point buffer.
setPoint(
constline:number
line,
consti:number
i,
import lines
lines.
constPoint: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.
constvec4f: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:
constx:number
x}));
if(
consti:number
i===0)
constwaves:lines.Lines
waves.
Lines.setCount: (line:number,count:number)=>void
Sets how many points line holds from a kernel. Requires the count buffer.
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
consttime:TgpuUniform<d.F32>
time=
constroot: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.
constf32:d.F32
export f32
A schema that represents a 32-bit float value. (equivalent to f32 in WGSL)
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(
constroot: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.
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
constenamel:(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.
typeFragment={
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
constpoint:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
}
point=
import shading
shading.
constpointAt:(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.
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.
constglossy:(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),
constpoint:{
position:d.v3f;
normal:d.v3f;
view:d.v3f;
uv:d.v2f;
uv1:d.v2f;
}
point);
const
constalpha: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.
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.
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(
constroot: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.
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.
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.
@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.
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.