Skip to content
Merged
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
43b4724
Implement initAsync
aleksanderkatan Jun 12, 2026
e5168a6
Add tests
aleksanderkatan Jun 12, 2026
11607d8
Abstract PerformanceCollector from unwrap
aleksanderkatan Jun 12, 2026
c352876
Abstract `resolveAndCreateShaderModule`
aleksanderkatan Jun 12, 2026
8852e65
Clean up initAsync
aleksanderkatan Jun 12, 2026
fcbed03
Add jsDocs
aleksanderkatan Jun 12, 2026
fa5c706
Extract unwrap to initSync
aleksanderkatan Jun 12, 2026
3f4379d
Add initSync to the interface
aleksanderkatan Jun 12, 2026
674c6b2
Update examples
aleksanderkatan Jun 12, 2026
32c9d16
Update docs
aleksanderkatan Jun 12, 2026
e3ee140
Update examples
aleksanderkatan Jun 12, 2026
af270ae
Rename PerformanceCollector to PerformanceTracker
aleksanderkatan Jun 12, 2026
de777f1
Use true private props
aleksanderkatan Jun 12, 2026
a5f7a10
Add initAsync and initSync directly onto guarded compute pipeline
aleksanderkatan Jun 16, 2026
5dc41cb
Update docs
aleksanderkatan Jun 16, 2026
f090a7a
Await promises in examples as late as possible
aleksanderkatan Jun 16, 2026
0aa4ef3
Move cleanup to finally
aleksanderkatan Jun 16, 2026
8d56157
Update docs
aleksanderkatan Jun 24, 2026
22cffdd
Update interfaces
aleksanderkatan Jun 24, 2026
9f6bee0
Add guarded compute pipeline tests
aleksanderkatan Jun 24, 2026
d6ca5ec
Add render pipeline tests
aleksanderkatan Jun 24, 2026
f8b309d
Move performance tracker to another file
aleksanderkatan Jun 24, 2026
51ec842
Use performance tracker in render pipeline
aleksanderkatan Jun 25, 2026
abda3fa
Separate unwrap logic to resolveAndCreateShaderModule
aleksanderkatan Jun 25, 2026
aa2ad94
Add initSync and initAsync implementations
aleksanderkatan Jun 25, 2026
507064d
Add render pipeline initialization to examples
aleksanderkatan Jun 25, 2026
2e3293a
Update expected calls and snapshots
aleksanderkatan Jun 25, 2026
1caa389
Merge remote-tracking branch 'origin/main' into feat/asynchronous-pip…
aleksanderkatan Jun 25, 2026
aca8bfb
Review fixes
aleksanderkatan Jun 25, 2026
858e21a
nr fix
aleksanderkatan Jun 25, 2026
4efe6ad
Fix lines combinations expected calls
aleksanderkatan Jun 25, 2026
9b0adbb
Fix js doc
aleksanderkatan Jun 25, 2026
0a8a1e9
Add initSync and initAsync to runners, update radiance cascades example
aleksanderkatan Jun 30, 2026
39a1e14
Revert example changes
aleksanderkatan Jun 30, 2026
c1cd579
Merge remote-tracking branch 'origin/main' into feat/asynchronous-pip…
aleksanderkatan Jul 1, 2026
2f066ba
Add jsDoc
aleksanderkatan Jul 1, 2026
00d542d
Merge remote-tracking branch 'origin/main' into feat/asynchronous-pip…
aleksanderkatan Jul 1, 2026
23ae40a
Merge remote-tracking branch 'origin/main' into feat/asynchronous-pip…
aleksanderkatan Jul 2, 2026
a5fcf15
Import from 'typegpu'
aleksanderkatan Jul 2, 2026
f3384dd
Merge branch 'main' into feat/asynchronous-pipeline-creation
aleksanderkatan Jul 2, 2026
1a6ad09
Merge branch 'main' into feat/asynchronous-pipeline-creation
aleksanderkatan Jul 6, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions apps/typegpu-docs/src/content/docs/apis/pipelines.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -242,6 +242,29 @@ tgpu.resolve([innerPipeline]);
```
:::

## Initialization

Pipeline initialization involves resolving the pipeline code, creating the shader module, and creating the underlying WebGPU pipeline.
This happens automatically the first time the pipeline is executed via `.draw`, `.dispatchWorkgroups`, or a similar method.
The `initSync` and `initAsync` methods let you perform this work ahead of time, avoiding a stall on first execution.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm missing the explanation of when one should use initSync as opposed to initAsync and vice versa. On the other hand, it's non-trivial to explain it briefly 🤔

Maybe:

Suggested change
Pipeline initialization involves resolving the pipeline code, creating the shader module, and creating the underlying WebGPU pipeline.
This happens automatically the first time the pipeline is executed via `.draw`, `.dispatchWorkgroups`, or a similar method.
The `initSync` and `initAsync` methods let you perform this work ahead of time, avoiding a stall on first execution.
Pipeline initialization involves resolving the pipeline code, creating the shader module, and creating the underlying WebGPU pipeline.
This happens automatically the first time the pipeline is executed via `.draw`, `.dispatchWorkgroups`, or a similar method.
The `initSync` function lets you jump-start that initialization on the WebGPU process early. To await until the initialization actually finishes and avoid a stall on first execution, you can use the `initAsync` function instead.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I updated this paragraph and added commented snippets, tell me what you think


```ts twoslash
import tgpu from 'typegpu';
const root = await tgpu.init();
const mainCompute = tgpu.computeFn({ workgroupSize: [1] })(() => {});
const pipeline = root.createComputePipeline({
compute: mainCompute,
});
const myPipelines = [root.createComputePipeline({
compute: mainCompute,
})];
// ---cut---
pipeline.initSync();

await Promise.all(myPipelines.map((pipeline) => pipeline.initAsync()));
Comment thread
aleksanderkatan marked this conversation as resolved.
Outdated
```


## Execution

```ts
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ const buffers = {
let bindGroup = createBindGroup();

const pipelines = createPipelines();
await Promise.all([pipelines['gpu-optimized'].initAsync(), pipelines['gpu-simple'].initAsync()]);

function createBindGroup() {
return root.createBindGroup(computeLayout, {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@ const pipelines = {
? root.createComputePipeline({ compute: subgroupCompute })
: null,
};
await Promise.all([pipelines.default.initAsync(), pipelines.subgroup?.initAsync()]);

// Definitions for the network

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,7 @@ let blurBindGroups: TgpuBindGroup<typeof blurLayout.entries>[];
const prepareModelInputPipeline = root
.with(paramsAccess, paramsUniform)
.createGuardedComputePipeline(prepareModelInput);
prepareModelInputPipeline.pipeline.initSync();
Comment thread
aleksanderkatan marked this conversation as resolved.
Outdated

let currentModelIndex = 0;
let session = await prepareSession(
Expand All @@ -138,6 +139,7 @@ async function switchModel(modelIndex: number) {
}

const generateMaskFromOutputPipeline = root.createGuardedComputePipeline(generateMaskFromOutput);
generateMaskFromOutputPipeline.pipeline.initSync();

const blurPipelines = [false, true].map((flip) =>
root.with(flipAccess, flip).createComputePipeline({ compute: computeFn }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const createComputePipeline = (exprCode: string) => {
const computePipelines: Array<TgpuGuardedComputePipeline> = initialFunctions.map(
(functionData, _) => createComputePipeline(functionData.code),
);
await Promise.all(computePipelines.map((guarded) => guarded.pipeline.initAsync()));

// Render background shader

Expand Down
2 changes: 2 additions & 0 deletions apps/typegpu-docs/src/examples/simulation/gravity/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,8 @@ const renderPipeline = root
},
});

await Promise.all([computeCollisionsPipeline.initAsync(), computeGravityPipeline.initAsync()]);
Comment thread
aleksanderkatan marked this conversation as resolved.
Outdated

let depthTexture = root.device.createTexture({
size: [canvas.width, canvas.height, 1],
format: 'depth24plus',
Expand Down
13 changes: 13 additions & 0 deletions apps/typegpu-docs/src/examples/simulation/stable-fluid/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,19 @@ const projectPipeline = createComputePipeline(c.projectFn);
const advectInkPipeline = createComputePipeline(c.advectInkFn);
const addInkPipeline = createComputePipeline(c.addInkFn);

// Eagerly initialize all pipelines
await Promise.all([
brushPipeline.initAsync(),
addForcePipeline.initAsync(),
advectPipeline.initAsync(),
diffusionPipeline.initAsync(),
divergencePipeline.initAsync(),
pressurePipeline.initAsync(),
projectPipeline.initAsync(),
advectInkPipeline.initAsync(),
addInkPipeline.initAsync(),
]);

// Create render pipelines
function createRenderPipeline(fragmentFn: TgpuFragmentFn<{ uv: d.Vec2f }, d.Vec4f>) {
return root.createRenderPipeline({
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ describe('matrix(next) example', () => {
category: 'algorithms',
name: 'matrix-next',
controlTriggers: ['Compute'],
expectedCalls: 1,
expectedCalls: 2,
},
device,
);
Expand Down Expand Up @@ -84,6 +84,39 @@ describe('matrix(next) example', () => {
let outputIndex = getIndex(globalRow, globalCol, (*dimensions).secondColumnCount);
resultMatrix[outputIndex] = accumulatedResult;
}
}

struct MatrixInfo {
firstRowCount: u32,
firstColumnCount: u32,
secondColumnCount: u32,
}

@group(0) @binding(3) var<uniform> dimensions: MatrixInfo;

fn getIndex(row: u32, col: u32, columns: u32) -> u32 {
return (col + (row * columns));
}

@group(0) @binding(0) var<storage, read> firstMatrix: array<i32>;

@group(0) @binding(1) var<storage, read> secondMatrix: array<i32>;

@group(0) @binding(2) var<storage, read_write> resultMatrix: array<i32>;

@compute @workgroup_size(16, 16) fn computeSimple(@builtin(global_invocation_id) gid: vec3u) {
let row = gid.x;
let col = gid.y;
if (((row >= dimensions.firstRowCount) || (col >= dimensions.secondColumnCount))) {
return;
}
var result = 0;
for (var k = 0u; (k < dimensions.firstColumnCount); k++) {
let aValue = firstMatrix[getIndex(row, k, dimensions.firstColumnCount)];
let bValue = secondMatrix[getIndex(k, col, dimensions.secondColumnCount)];
result += (aValue * bValue);
}
resultMatrix[getIndex(row, col, dimensions.secondColumnCount)] = result;
}"
`);
});
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,31 @@ describe('mnist inference example', () => {
);

expect(shaderCodes).toMatchInlineSnapshot(`
"enable subgroups;
"@group(0) @binding(0) var<storage, read> input: array<f32>;

@group(1) @binding(0) var<storage, read> weights: array<f32>;

@group(1) @binding(1) var<storage, read> biases: array<f32>;

@group(0) @binding(1) var<storage, read_write> output: array<f32>;

fn relu(x: f32) -> f32 {
return max(0f, x);
}

@compute @workgroup_size(1) fn defaultCompute(@builtin(global_invocation_id) gid: vec3u) {
let inputSize = arrayLength(&input);
let i = gid.x;
let weightsOffset = (i * inputSize);
var sum = 0f;
for (var j = 0u; (j < inputSize); j++) {
sum = fma(input[j], weights[(weightsOffset + j)], sum);
}
let total = (sum + biases[i]);
output[i] = relu(total);
}

enable subgroups;

@group(0) @binding(0) var<storage, read> input: array<f32>;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,67 @@ describe('stable-fluid example', () => {
mockImageLoading();
mockCreateImageBitmap();
},
expectedCalls: 7,
expectedCalls: 9,
},
device,
);

expect(shaderCodes).toMatchInlineSnapshot(`
"@group(0) @binding(0) var src: texture_2d<f32>;
"struct BrushParams {
pos: vec2i,
delta: vec2f,
radius: f32,
forceScale: f32,
inkAmount: f32,
}

@group(0) @binding(0) var<uniform> brushParams: BrushParams;

@group(0) @binding(1) var forceDst: texture_storage_2d<rgba16float, write>;

@group(0) @binding(2) var inkDst: texture_storage_2d<rgba16float, write>;

@compute @workgroup_size(16, 16) fn brushFn(@builtin(global_invocation_id) gid: vec3u) {
let pixelPos = gid.xy;
let brushSettings = (&brushParams);
var forceVec = vec2f();
var inkAmount = 0f;
let deltaX = (f32(pixelPos.x) - f32((*brushSettings).pos.x));
let deltaY = (f32(pixelPos.y) - f32((*brushSettings).pos.y));
let distSquared = ((deltaX * deltaX) + (deltaY * deltaY));
let radiusSquared = ((*brushSettings).radius * (*brushSettings).radius);
if ((distSquared < radiusSquared)) {
let brushWeight = exp((-(distSquared) / radiusSquared));
forceVec = (((*brushSettings).forceScale * brushWeight) * (*brushSettings).delta);
inkAmount = ((*brushSettings).inkAmount * brushWeight);
}
textureStore(forceDst, pixelPos, vec4f(forceVec, 0f, 1f));
textureStore(inkDst, pixelPos, vec4f(inkAmount, 0f, 0f, 1f));
}

@group(0) @binding(0) var src: texture_2d<f32>;

@group(0) @binding(2) var force: texture_2d<f32>;

struct ShaderParams {
dt: f32,
viscosity: f32,
}

@group(0) @binding(3) var<uniform> simParams: ShaderParams;

@group(0) @binding(1) var dst: texture_storage_2d<rgba16float, write>;

@compute @workgroup_size(16, 16) fn addForcesFn(@builtin(global_invocation_id) gid: vec3u) {
let pixelPos = gid.xy;
let currentVel = textureLoad(src, pixelPos, 0).xy;
let forceVec = textureLoad(force, pixelPos, 0).xy;
let timeStep = simParams.dt;
let newVel = (currentVel + (timeStep * forceVec));
textureStore(dst, pixelPos, vec4f(newVel, 0f, 1f));
}

@group(0) @binding(0) var src: texture_2d<f32>;

@group(0) @binding(1) var dst: texture_storage_2d<rgba16float, write>;

Expand Down Expand Up @@ -248,6 +302,19 @@ describe('stable-fluid example', () => {
textureStore(dst, pixelPos, inkVal);
}

@group(0) @binding(2) var add: texture_2d<f32>;

@group(0) @binding(0) var src: texture_2d<f32>;

@group(0) @binding(1) var dst: texture_storage_2d<rgba16float, write>;

@compute @workgroup_size(16, 16) fn addInkFn(@builtin(global_invocation_id) gid: vec3u) {
let pixelPos = gid.xy;
let addVal = textureLoad(add, pixelPos, 0).x;
let srcVal = textureLoad(src, pixelPos, 0).x;
textureStore(dst, pixelPos, vec4f((addVal + srcVal), 0f, 0f, 1f));
}

struct renderFn_Output {
@builtin(position) pos: vec4f,
@location(0) uv: vec2f,
Expand Down
1 change: 1 addition & 0 deletions packages/typegpu-testing-utility/src/extendedIt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ export const it = base
return commandEncoder;
}),
createComputePipeline: vi.fn(() => mockComputePipeline),
createComputePipelineAsync: vi.fn(async () => mockComputePipeline),
createPipelineLayout: vi.fn(() => 'mockPipelineLayout'),
createQuerySet: vi.fn(({ type, count }: GPUQuerySetDescriptor) => {
const querySet = Object.create(mockQuerySet);
Expand Down
Loading
Loading