@vgpu/render 0.0.5 → 0.0.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +94 -5
- package/dist/frame.d.ts +19 -0
- package/dist/frame.d.ts.map +1 -0
- package/dist/frame.js +50 -0
- package/dist/frame.js.map +1 -0
- package/dist/index.d.ts +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +5 -1
- package/dist/index.js.map +1 -1
- package/dist/perf/frame-time-measure.d.ts +22 -0
- package/dist/perf/frame-time-measure.d.ts.map +1 -0
- package/dist/perf/frame-time-measure.js +91 -0
- package/dist/perf/frame-time-measure.js.map +1 -0
- package/dist/perf/gpu-frame-time.d.ts +49 -0
- package/dist/perf/gpu-frame-time.d.ts.map +1 -0
- package/dist/perf/gpu-frame-time.js +50 -0
- package/dist/perf/gpu-frame-time.js.map +1 -0
- package/dist/perf/index.d.ts +5 -0
- package/dist/perf/index.d.ts.map +1 -0
- package/dist/perf/index.js +3 -0
- package/dist/perf/index.js.map +1 -0
- package/dist/perf/pixel-diff.d.ts +27 -0
- package/dist/perf/pixel-diff.d.ts.map +1 -0
- package/dist/perf/pixel-diff.js +40 -0
- package/dist/perf/pixel-diff.js.map +1 -0
- package/dist/pipeline-descriptor.d.ts +35 -0
- package/dist/pipeline-descriptor.d.ts.map +1 -0
- package/dist/pipeline-descriptor.js +44 -0
- package/dist/pipeline-descriptor.js.map +1 -0
- package/dist/pipeline.d.ts +24 -14
- package/dist/pipeline.d.ts.map +1 -1
- package/dist/pipeline.js +58 -7
- package/dist/pipeline.js.map +1 -1
- package/dist/render-bundle.d.ts +24 -0
- package/dist/render-bundle.d.ts.map +1 -0
- package/dist/render-bundle.js +39 -0
- package/dist/render-bundle.js.map +1 -0
- package/dist/render-pass.d.ts +16 -1
- package/dist/render-pass.d.ts.map +1 -1
- package/dist/render-pass.js +44 -7
- package/dist/render-pass.js.map +1 -1
- package/dist/storage-buffer.d.ts +81 -0
- package/dist/storage-buffer.d.ts.map +1 -0
- package/dist/storage-buffer.js +91 -0
- package/dist/storage-buffer.js.map +1 -0
- package/dist/uniform.d.ts +55 -0
- package/dist/uniform.d.ts.map +1 -0
- package/dist/uniform.js +64 -0
- package/dist/uniform.js.map +1 -0
- package/package.json +8 -3
- package/src/Frame.docs.md +79 -0
- package/src/RenderPass.docs.md +6 -3
- package/src/createRenderPipeline.docs.md +113 -19
- package/src/perf/perf.docs.md +45 -0
- package/src/rapid-renderer.docs.md +15 -1
- package/src/render-bundle.docs.md +46 -0
- package/src/render-target/render-target-canvas.docs.md +15 -0
- package/src/storage-buffer.docs.md +102 -0
- package/src/uniform.docs.md +70 -0
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
import { Buffer, type Device } from "@vgpu/core";
|
|
2
|
+
export interface UniformOptions {
|
|
3
|
+
/** Byte size of the uniform buffer. Must be a positive number. */
|
|
4
|
+
readonly size: number;
|
|
5
|
+
/** Optional debug label forwarded to the buffer, bind group layout, and bind group. */
|
|
6
|
+
readonly label?: string;
|
|
7
|
+
/**
|
|
8
|
+
* Shader stages that read binding 0. Defaults to `VERTEX | FRAGMENT`.
|
|
9
|
+
* Ignored when {@link UniformOptions.bindGroupLayout} is provided.
|
|
10
|
+
*/
|
|
11
|
+
readonly visibility?: GPUShaderStageFlags;
|
|
12
|
+
/**
|
|
13
|
+
* Reuse a pipeline-owned bind group layout instead of creating one.
|
|
14
|
+
* The layout's binding 0 must be a uniform buffer compatible with `size`.
|
|
15
|
+
*/
|
|
16
|
+
readonly bindGroupLayout?: GPUBindGroupLayout;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* A single stable uniform buffer for one render pass, rewritten per frame.
|
|
20
|
+
*
|
|
21
|
+
* @remarks
|
|
22
|
+
* Unlike {@link UniformPool} (a dynamic-offset ring allocator built for many
|
|
23
|
+
* per-draw uniforms), `Uniform` owns exactly one uniform buffer at binding 0 with
|
|
24
|
+
* a fixed (non-dynamic) offset. The caller decides when to {@link Uniform.write}
|
|
25
|
+
* — typically gating on real changes — and the bind group never moves. This fits
|
|
26
|
+
* the "one camera/globals buffer per pass" case where a dynamic-offset binding
|
|
27
|
+
* would needlessly mark the layout `hasDynamicOffset` and re-upload every frame.
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```ts
|
|
31
|
+
* const uniform = new Uniform(device, { size: 64, label: "globals" });
|
|
32
|
+
* // per frame, only when inputs changed:
|
|
33
|
+
* uniform.write(globalsBytes);
|
|
34
|
+
* pass.setBindGroup(0, uniform.bindGroup);
|
|
35
|
+
* ```
|
|
36
|
+
*/
|
|
37
|
+
export declare class Uniform {
|
|
38
|
+
readonly device: Device;
|
|
39
|
+
readonly size: number;
|
|
40
|
+
readonly buffer: Buffer;
|
|
41
|
+
readonly bindGroupLayout: GPUBindGroupLayout;
|
|
42
|
+
readonly bindGroup: GPUBindGroup;
|
|
43
|
+
private destroyed;
|
|
44
|
+
constructor(device: Device, opts: UniformOptions);
|
|
45
|
+
/** The underlying uniform `GPUBuffer`. */
|
|
46
|
+
get gpu(): GPUBuffer;
|
|
47
|
+
/**
|
|
48
|
+
* Upload `data` to the buffer via `queue.writeBuffer`. No dynamic offset; the
|
|
49
|
+
* bind group is unchanged. Call only when the uniform contents actually change.
|
|
50
|
+
*/
|
|
51
|
+
write(data: BufferSource, offset?: number): void;
|
|
52
|
+
destroy(): void;
|
|
53
|
+
dispose(): void;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=uniform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uniform.d.ts","sourceRoot":"","sources":["../src/uniform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAQ,MAAM,EAA0C,KAAK,MAAM,EAAE,MAAM,YAAY,CAAC;AAE/F,MAAM,WAAW,cAAc;IAC7B,kEAAkE;IAClE,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,uFAAuF;IACvF,QAAQ,CAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IACxB;;;OAGG;IACH,QAAQ,CAAC,UAAU,CAAC,EAAE,mBAAmB,CAAC;IAC1C;;;OAGG;IACH,QAAQ,CAAC,eAAe,CAAC,EAAE,kBAAkB,CAAC;CAC/C;AAID;;;;;;;;;;;;;;;;;;GAkBG;AACH,qBAAa,OAAO;IAON,QAAQ,CAAC,MAAM,EAAE,MAAM;IANnC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,eAAe,EAAE,kBAAkB,CAAC;IAC7C,QAAQ,CAAC,SAAS,EAAE,YAAY,CAAC;IACjC,OAAO,CAAC,SAAS,CAAS;gBAEL,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,cAAc;IAczD,0CAA0C;IAC1C,IAAI,GAAG,IAAI,SAAS,CAEnB;IAED;;;OAGG;IACH,KAAK,CAAC,IAAI,EAAE,YAAY,EAAE,MAAM,SAAI,GAAG,IAAI;IAI3C,OAAO,IAAI,IAAI;IAMf,OAAO,IAAI,IAAI;CAGhB"}
|
package/dist/uniform.js
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
import { bind, createBindGroup, createBindGroupLayout } from "@vgpu/core";
|
|
2
|
+
const defaultVisibility = ((globalThis.GPUShaderStage?.VERTEX ?? 1) | (globalThis.GPUShaderStage?.FRAGMENT ?? 2));
|
|
3
|
+
/**
|
|
4
|
+
* A single stable uniform buffer for one render pass, rewritten per frame.
|
|
5
|
+
*
|
|
6
|
+
* @remarks
|
|
7
|
+
* Unlike {@link UniformPool} (a dynamic-offset ring allocator built for many
|
|
8
|
+
* per-draw uniforms), `Uniform` owns exactly one uniform buffer at binding 0 with
|
|
9
|
+
* a fixed (non-dynamic) offset. The caller decides when to {@link Uniform.write}
|
|
10
|
+
* — typically gating on real changes — and the bind group never moves. This fits
|
|
11
|
+
* the "one camera/globals buffer per pass" case where a dynamic-offset binding
|
|
12
|
+
* would needlessly mark the layout `hasDynamicOffset` and re-upload every frame.
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* ```ts
|
|
16
|
+
* const uniform = new Uniform(device, { size: 64, label: "globals" });
|
|
17
|
+
* // per frame, only when inputs changed:
|
|
18
|
+
* uniform.write(globalsBytes);
|
|
19
|
+
* pass.setBindGroup(0, uniform.bindGroup);
|
|
20
|
+
* ```
|
|
21
|
+
*/
|
|
22
|
+
export class Uniform {
|
|
23
|
+
device;
|
|
24
|
+
size;
|
|
25
|
+
buffer;
|
|
26
|
+
bindGroupLayout;
|
|
27
|
+
bindGroup;
|
|
28
|
+
destroyed = false;
|
|
29
|
+
constructor(device, opts) {
|
|
30
|
+
this.device = device;
|
|
31
|
+
this.size = opts.size;
|
|
32
|
+
this.buffer = device.createBuffer({ size: opts.size, usage: ["uniform", "copy_dst"], label: opts.label });
|
|
33
|
+
this.bindGroupLayout = opts.bindGroupLayout ?? createBindGroupLayout(device, {
|
|
34
|
+
label: opts.label ? `${opts.label}.bgl` : undefined,
|
|
35
|
+
entries: [bind.uniform(0, opts.visibility ?? defaultVisibility, { minBindingSize: opts.size })],
|
|
36
|
+
});
|
|
37
|
+
this.bindGroup = createBindGroup(device, {
|
|
38
|
+
label: opts.label ? `${opts.label}.bg` : undefined,
|
|
39
|
+
layout: this.bindGroupLayout,
|
|
40
|
+
entries: [bind.resource(0, this.buffer)],
|
|
41
|
+
});
|
|
42
|
+
}
|
|
43
|
+
/** The underlying uniform `GPUBuffer`. */
|
|
44
|
+
get gpu() {
|
|
45
|
+
return this.buffer.gpu;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* Upload `data` to the buffer via `queue.writeBuffer`. No dynamic offset; the
|
|
49
|
+
* bind group is unchanged. Call only when the uniform contents actually change.
|
|
50
|
+
*/
|
|
51
|
+
write(data, offset = 0) {
|
|
52
|
+
this.buffer.write(data, offset);
|
|
53
|
+
}
|
|
54
|
+
destroy() {
|
|
55
|
+
if (this.destroyed)
|
|
56
|
+
return;
|
|
57
|
+
this.destroyed = true;
|
|
58
|
+
this.buffer.destroy();
|
|
59
|
+
}
|
|
60
|
+
dispose() {
|
|
61
|
+
this.destroy();
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
//# sourceMappingURL=uniform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"uniform.js","sourceRoot":"","sources":["../src/uniform.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,IAAI,EAAU,eAAe,EAAE,qBAAqB,EAAe,MAAM,YAAY,CAAC;AAmB/F,MAAM,iBAAiB,GAAG,CAAC,CAAC,UAAU,CAAC,cAAc,EAAE,MAAM,IAAI,CAAC,CAAC,GAAG,CAAC,UAAU,CAAC,cAAc,EAAE,QAAQ,IAAI,CAAC,CAAC,CAAwB,CAAC;AAEzI;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,OAAO,OAAO;IAOG;IANZ,IAAI,CAAS;IACb,MAAM,CAAS;IACf,eAAe,CAAqB;IACpC,SAAS,CAAe;IACzB,SAAS,GAAG,KAAK,CAAC;IAE1B,YAAqB,MAAc,EAAE,IAAoB;QAApC,WAAM,GAAN,MAAM,CAAQ;QACjC,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,GAAG,MAAM,CAAC,YAAY,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,KAAK,EAAE,CAAC,SAAS,EAAE,UAAU,CAAC,EAAE,KAAK,EAAE,IAAI,CAAC,KAAK,EAAE,CAAC,CAAC;QAC1G,IAAI,CAAC,eAAe,GAAG,IAAI,CAAC,eAAe,IAAI,qBAAqB,CAAC,MAAM,EAAE;YAC3E,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,SAAS;YACnD,OAAO,EAAE,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,EAAE,IAAI,CAAC,UAAU,IAAI,iBAAiB,EAAE,EAAE,cAAc,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,CAAC;SAChG,CAAC,CAAC;QACH,IAAI,CAAC,SAAS,GAAG,eAAe,CAAC,MAAM,EAAE;YACvC,KAAK,EAAE,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,GAAG,IAAI,CAAC,KAAK,KAAK,CAAC,CAAC,CAAC,SAAS;YAClD,MAAM,EAAE,IAAI,CAAC,eAAe;YAC5B,OAAO,EAAE,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,EAAE,IAAI,CAAC,MAAM,CAAC,CAAC;SACzC,CAAC,CAAC;IACL,CAAC;IAED,0CAA0C;IAC1C,IAAI,GAAG;QACL,OAAO,IAAI,CAAC,MAAM,CAAC,GAAG,CAAC;IACzB,CAAC;IAED;;;OAGG;IACH,KAAK,CAAC,IAAkB,EAAE,MAAM,GAAG,CAAC;QAClC,IAAI,CAAC,MAAM,CAAC,KAAK,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAClC,CAAC;IAED,OAAO;QACL,IAAI,IAAI,CAAC,SAAS;YAAE,OAAO;QAC3B,IAAI,CAAC,SAAS,GAAG,IAAI,CAAC;QACtB,IAAI,CAAC,MAAM,CAAC,OAAO,EAAE,CAAC;IACxB,CAAC;IAED,OAAO;QACL,IAAI,CAAC,OAAO,EAAE,CAAC;IACjB,CAAC;CACF"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vgpu/render",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.7",
|
|
4
4
|
"description": "Render pipeline + render pass abstractions for vgpu.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"webgpu",
|
|
@@ -47,6 +47,10 @@
|
|
|
47
47
|
"./passes": {
|
|
48
48
|
"types": "./dist/passes/index.d.ts",
|
|
49
49
|
"import": "./dist/passes/index.js"
|
|
50
|
+
},
|
|
51
|
+
"./perf": {
|
|
52
|
+
"types": "./dist/perf/index.d.ts",
|
|
53
|
+
"import": "./dist/perf/index.js"
|
|
50
54
|
}
|
|
51
55
|
},
|
|
52
56
|
"files": [
|
|
@@ -58,14 +62,15 @@
|
|
|
58
62
|
],
|
|
59
63
|
"dependencies": {
|
|
60
64
|
"wgpu-matrix": "^3.4.2",
|
|
61
|
-
"@vgpu/core": "0.0.
|
|
65
|
+
"@vgpu/core": "0.0.7"
|
|
62
66
|
},
|
|
63
67
|
"vgpuExportBundleBudgetsGzipBytes": {
|
|
64
68
|
".": 49152,
|
|
65
69
|
"./inspect": 4096,
|
|
66
70
|
"./utils": 2048,
|
|
67
71
|
"./edit": 25600,
|
|
68
|
-
"./passes": 3584
|
|
72
|
+
"./passes": 3584,
|
|
73
|
+
"./perf": 3072
|
|
69
74
|
},
|
|
70
75
|
"scripts": {
|
|
71
76
|
"build": "tsc -b",
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# Frame
|
|
2
|
+
|
|
3
|
+
`Frame` owns one `GPUCommandEncoder` for an explicit sequence of commands. Use
|
|
4
|
+
`beginFrame(device)` when one render frame needs multiple render passes, query
|
|
5
|
+
commands, or copies in a user-authored order, then call `submit()` once.
|
|
6
|
+
|
|
7
|
+
VGPU does not infer pass order, bind group layouts, or pipeline layouts. The
|
|
8
|
+
helpers keep the native WebGPU lifecycle visible while removing repeated
|
|
9
|
+
boilerplate around pass begin/end and final submission.
|
|
10
|
+
|
|
11
|
+
## Native WebGPU baseline
|
|
12
|
+
|
|
13
|
+
Without `Frame`, a multipass render frame is one command encoder, multiple
|
|
14
|
+
passes ended in order, then one finish/submit:
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
const encoder = device.gpu.createCommandEncoder({ label: "hero.frame" });
|
|
18
|
+
|
|
19
|
+
const lightPass = encoder.beginRenderPass(lightPassDescriptor);
|
|
20
|
+
lightPass.executeBundles([lightBundle]);
|
|
21
|
+
lightPass.end();
|
|
22
|
+
|
|
23
|
+
encoder.writeTimestamp(querySet, 0);
|
|
24
|
+
encoder.copyBufferToBuffer(srcBuffer, 0, dstBuffer, 0, byteLength);
|
|
25
|
+
|
|
26
|
+
const compositePass = encoder.beginRenderPass(compositePassDescriptor);
|
|
27
|
+
compositePass.setPipeline(compositePipeline);
|
|
28
|
+
compositePass.setBindGroup(0, compositeBindGroup);
|
|
29
|
+
compositePass.draw(3);
|
|
30
|
+
compositePass.end();
|
|
31
|
+
|
|
32
|
+
device.queue.gpu.submit([encoder.finish()]);
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## With VGPU Frame
|
|
36
|
+
|
|
37
|
+
`Frame` keeps that same lifecycle: one encoder, explicit pass order, finish once,
|
|
38
|
+
submit once. This one-encoder/one-submit contract is intentional for homepage-style
|
|
39
|
+
multipass renderers: setup and warmup should create pipelines, buffers, bind
|
|
40
|
+
groups, and bundles up front; the per-frame path should only encode commands and
|
|
41
|
+
submit the finished command buffer.
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
const frame = beginFrame(device, { label: "hero.frame" });
|
|
45
|
+
|
|
46
|
+
frame.renderPass(lightPassDescriptor, (pass) => {
|
|
47
|
+
pass.executeBundles([lightBundle]);
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
frame.gpu.writeTimestamp(querySet, 0);
|
|
51
|
+
frame.copyBufferToBuffer(srcBuffer, dstBuffer, byteLength);
|
|
52
|
+
|
|
53
|
+
frame.renderPass(compositePassDescriptor, (pass) => {
|
|
54
|
+
pass.setPipeline(compositePipeline);
|
|
55
|
+
pass.setBindGroup(0, compositeBindGroup);
|
|
56
|
+
pass.draw(3);
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
frame.submit();
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
`frame.renderPass(options, callback)` begins a `RenderPass` wrapper on the
|
|
63
|
+
frame-owned encoder, runs the callback, and ends that pass in a `finally` block.
|
|
64
|
+
Unlike constructing a standalone `RenderPass`, a pass created by `Frame` does
|
|
65
|
+
**not** finish or submit the command encoder when it ends. `frame.submit()` is
|
|
66
|
+
the only submission point.
|
|
67
|
+
|
|
68
|
+
The raw command encoder is available as `frame.gpu` for WebGPU operations that
|
|
69
|
+
are intentionally not wrapped, such as `writeTimestamp()` or `resolveQuerySet()`.
|
|
70
|
+
This is an advanced, semver-protected public escape hatch. Direct raw encoder
|
|
71
|
+
calls follow native WebGPU behavior; VGPU only guards its own helper methods
|
|
72
|
+
after `submit()`.
|
|
73
|
+
|
|
74
|
+
`copyBufferToBuffer(source, destination, size, sourceOffset?, destinationOffset?)`
|
|
75
|
+
is a small typed helper that accepts either core `Buffer` instances or raw
|
|
76
|
+
`GPUBuffer`s. For all other encoder commands, use `frame.gpu` directly.
|
|
77
|
+
|
|
78
|
+
A `Frame` is single-use. Calling VGPU helper methods after `submit()` throws
|
|
79
|
+
`VGPUError` with code `VGPU-FRAME-SUBMITTED`.
|
package/src/RenderPass.docs.md
CHANGED
|
@@ -5,9 +5,11 @@ attachments. Construct it with a core `Device` and `RenderPassOptions`, encode
|
|
|
5
5
|
render commands, then call `end()` to finish the pass and submit the command
|
|
6
6
|
buffer through `Device.queue`.
|
|
7
7
|
|
|
8
|
-
`RenderPassOptions` contains `colorAttachments
|
|
9
|
-
`
|
|
10
|
-
`
|
|
8
|
+
`RenderPassOptions` contains `colorAttachments`, optional
|
|
9
|
+
`depthStencilAttachment`, and an optional `label`. `ColorAttachment.view` and
|
|
10
|
+
`DepthStencilAttachment.view` accept either a core `Texture` or a raw
|
|
11
|
+
`GPUTextureView`; load/store operations and clear values are forwarded to
|
|
12
|
+
WebGPU.
|
|
11
13
|
|
|
12
14
|
Commands mirror the WebGPU render pass encoder:
|
|
13
15
|
|
|
@@ -15,6 +17,7 @@ Commands mirror the WebGPU render pass encoder:
|
|
|
15
17
|
- `setBindGroup(index, group, dynamicOffsets?)` binds a raw `GPUBindGroup`.
|
|
16
18
|
- `setVertexBuffer(slot, buffer, offset?, size?)` binds a core `Buffer` or raw
|
|
17
19
|
`GPUBuffer` for vertex input.
|
|
20
|
+
- `executeBundles(bundles)` executes setup-time `GPURenderBundle`s in the pass.
|
|
18
21
|
- `draw({ vertexCount, instanceCount?, firstVertex?, firstInstance? })` issues a
|
|
19
22
|
non-indexed draw. The numeric WebGPU-style `draw(vertexCount, ...)` call is
|
|
20
23
|
also accepted.
|
|
@@ -1,34 +1,128 @@
|
|
|
1
|
-
# createRenderPipeline
|
|
1
|
+
# createRenderPipeline / createRenderPipelineAsync
|
|
2
2
|
|
|
3
|
-
Creates a GPU render pipeline from
|
|
4
|
-
|
|
3
|
+
Creates a GPU render pipeline from descriptor-like VGPU options. Both helpers
|
|
4
|
+
return a raw `GPURenderPipeline` with no wrapper so the result can be passed
|
|
5
|
+
directly to native WebGPU, `RenderPass.setPipeline()`, or render-bundle recording.
|
|
5
6
|
|
|
6
|
-
##
|
|
7
|
+
## Signatures
|
|
7
8
|
|
|
8
|
-
|
|
9
|
+
```ts
|
|
10
|
+
createRenderPipeline(device: Device, opts: RenderPipelineOptions): GPURenderPipeline
|
|
11
|
+
createRenderPipelineAsync(device: Device, opts: RenderPipelineOptions): Promise<GPURenderPipeline>
|
|
12
|
+
createRenderPipelineFromDescriptor(device: Device, descriptor: GPURenderPipelineDescriptor): GPURenderPipeline
|
|
13
|
+
createRenderPipelineFromDescriptorAsync(device: Device, descriptor: GPURenderPipelineDescriptor, fallback?: RenderPipelineAsyncFallback): Promise<GPURenderPipeline>
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
`createRenderPipelineAsync` calls `GPUDevice.createRenderPipelineAsync()` when it
|
|
17
|
+
exists. If the implementation does not expose the async API, the default
|
|
18
|
+
compatibility policy is `fallback: "sync"`, which emits a once-only diagnostic and
|
|
19
|
+
calls `createRenderPipeline()` instead. Performance-critical warmup can pass
|
|
20
|
+
`fallback: "throw"` to receive a structured `VGPUError` with code
|
|
21
|
+
`VGPU-RENDER-PIPELINE-ASYNC-UNAVAILABLE` instead of accidentally blocking.
|
|
22
|
+
|
|
23
|
+
## Raw descriptor entrypoints
|
|
24
|
+
|
|
25
|
+
If you already have a hand-built `GPURenderPipelineDescriptor`, pass it straight
|
|
26
|
+
through — do not reshape it into `RenderPipelineOptions` just to get the
|
|
27
|
+
async→sync fallback:
|
|
28
|
+
|
|
29
|
+
- `createRenderPipelineFromDescriptor(device, descriptor)` forwards the descriptor
|
|
30
|
+
to `GPUDevice.createRenderPipeline()` unchanged.
|
|
31
|
+
- `createRenderPipelineFromDescriptorAsync(device, descriptor, fallback?)` forwards
|
|
32
|
+
it to `GPUDevice.createRenderPipelineAsync()` with the exact same compatibility
|
|
33
|
+
fallback as `createRenderPipelineAsync` (default `"sync"`, or `"throw"` for a
|
|
34
|
+
structured `VGPUError`).
|
|
35
|
+
|
|
36
|
+
The descriptor is forwarded verbatim, so native WebGPU validation and lifecycle
|
|
37
|
+
rules remain the caller's responsibility. These are explicit, separately named
|
|
38
|
+
exports rather than an overload so a `RenderPipelineOptions` caller can never be
|
|
39
|
+
misread as passing a raw descriptor.
|
|
40
|
+
|
|
41
|
+
VGPU does not cache pipelines: one helper call equals one WebGPU device call.
|
|
42
|
+
Keep pipeline caches explicit and owned by the caller.
|
|
9
43
|
|
|
10
44
|
## Options
|
|
11
45
|
|
|
12
|
-
- `shader`:
|
|
13
|
-
|
|
14
|
-
- `
|
|
15
|
-
- `
|
|
16
|
-
- `
|
|
17
|
-
- `
|
|
46
|
+
- `shader`: optional shared `Shader` or raw `GPUShaderModule` used by stages that
|
|
47
|
+
do not provide their own module.
|
|
48
|
+
- `vertex`: vertex stage options.
|
|
49
|
+
- `shader` / `module`: optional per-stage `Shader` or raw `GPUShaderModule`.
|
|
50
|
+
- `entry` / `entryPoint`: vertex entry-point name.
|
|
51
|
+
- `buffers`: optional vertex buffer layouts and attributes.
|
|
52
|
+
- `constants`: optional pipeline constants.
|
|
53
|
+
- `fragment`: optional fragment stage options.
|
|
54
|
+
- `shader` / `module`: optional per-stage `Shader` or raw `GPUShaderModule`.
|
|
55
|
+
- `entry` / `entryPoint`: fragment entry-point name.
|
|
56
|
+
- `targets`: color target formats plus blend and write-mask state.
|
|
57
|
+
- `constants`: optional pipeline constants.
|
|
58
|
+
- `primitive`: optional WebGPU primitive state.
|
|
59
|
+
- `depthStencil`: optional depth/stencil state.
|
|
60
|
+
- `multisample`: optional multisample state.
|
|
61
|
+
- `layout`: explicit `GPUPipelineLayout`, or `"auto"`. Defaults to `"auto"`.
|
|
18
62
|
- `label`: optional debug label forwarded to WebGPU.
|
|
63
|
+
- `fallback`: async-only fallback policy, `"sync"` or `"throw"`.
|
|
64
|
+
|
|
65
|
+
## Examples
|
|
19
66
|
|
|
20
|
-
|
|
67
|
+
Shared VGPU `Shader` module:
|
|
21
68
|
|
|
22
69
|
```ts
|
|
23
|
-
const pipeline =
|
|
70
|
+
const pipeline = await createRenderPipelineAsync(device, {
|
|
71
|
+
label: "hero.pipeline",
|
|
72
|
+
fallback: "throw",
|
|
24
73
|
shader,
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
74
|
+
layout: explicitPipelineLayout,
|
|
75
|
+
vertex: {
|
|
76
|
+
entry: "vs_main",
|
|
77
|
+
buffers: [{
|
|
78
|
+
arrayStride: 16,
|
|
79
|
+
attributes: [{ shaderLocation: 0, offset: 0, format: "float32x4" }],
|
|
80
|
+
}],
|
|
81
|
+
},
|
|
82
|
+
fragment: {
|
|
83
|
+
entry: "fs_main",
|
|
84
|
+
targets: [{
|
|
85
|
+
format,
|
|
86
|
+
blend: {
|
|
87
|
+
color: { operation: "add", srcFactor: "one", dstFactor: "one-minus-src-alpha" },
|
|
88
|
+
alpha: { operation: "add", srcFactor: "one", dstFactor: "one-minus-src-alpha" },
|
|
89
|
+
},
|
|
90
|
+
writeMask: 0xf,
|
|
91
|
+
}],
|
|
92
|
+
},
|
|
93
|
+
primitive: { topology: "triangle-list", cullMode: "back" },
|
|
94
|
+
depthStencil: { format: "depth24plus", depthWriteEnabled: true, depthCompare: "less" },
|
|
95
|
+
multisample: { count: 4 },
|
|
28
96
|
});
|
|
29
97
|
```
|
|
30
98
|
|
|
31
|
-
|
|
99
|
+
Raw shader modules and per-stage constants:
|
|
100
|
+
|
|
101
|
+
```ts
|
|
102
|
+
const pipeline = createRenderPipeline(device, {
|
|
103
|
+
layout: "auto",
|
|
104
|
+
vertex: { module: vertexModule, entryPoint: "vs", constants: { scale: 2 } },
|
|
105
|
+
fragment: { module: fragmentModule, entryPoint: "fs", targets: [{ format }] },
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Existing raw `GPURenderPipelineDescriptor`, only wanting the async fallback:
|
|
110
|
+
|
|
111
|
+
```ts
|
|
112
|
+
const descriptor: GPURenderPipelineDescriptor = {
|
|
113
|
+
label: "hero.pipeline",
|
|
114
|
+
layout: pipelineLayout,
|
|
115
|
+
vertex: { module: shaderModule, entryPoint: "vs_main", buffers },
|
|
116
|
+
fragment: { module: shaderModule, entryPoint: "fs_main", targets: [{ format }] },
|
|
117
|
+
primitive: { topology: "triangle-list" },
|
|
118
|
+
};
|
|
119
|
+
|
|
120
|
+
const pipeline = await createRenderPipelineFromDescriptorAsync(device, descriptor, "throw");
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Raw escape hatch
|
|
32
124
|
|
|
33
|
-
|
|
34
|
-
|
|
125
|
+
`Shader.gpu` is an intentional advanced escape hatch to the underlying
|
|
126
|
+
`GPUShaderModule`. It is part of VGPU's public API surface and should be treated
|
|
127
|
+
as semver-protected, but direct native WebGPU usage remains responsible for
|
|
128
|
+
native WebGPU validation and lifecycle rules.
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# @vgpu/render/perf
|
|
2
|
+
|
|
3
|
+
Measurement utilities for the optimize loop: confirm a shader change is still correct and actually
|
|
4
|
+
faster. **Tooling only — never call these on a live animation-frame path.** See the `measuring`
|
|
5
|
+
guide (`vgpu docs cat /guides/measuring.docs.md`) for the workflow.
|
|
6
|
+
|
|
7
|
+
## gpuFrameTime
|
|
8
|
+
|
|
9
|
+
`gpuFrameTime(device, encode, options?)` measures median GPU time per frame for a render routine.
|
|
10
|
+
|
|
11
|
+
The `encode(frame, index)` callback records the frame's passes onto a vgpu `Frame` — the same body
|
|
12
|
+
you run in production. The harness owns warmup, the loop, submit, and timing. It uses GPU timestamp
|
|
13
|
+
queries when the device exposes `timestamp-query`, otherwise wall-clock timing via
|
|
14
|
+
`device.queue.flush()`.
|
|
15
|
+
|
|
16
|
+
```ts
|
|
17
|
+
import { gpuFrameTime } from "@vgpu/render/perf";
|
|
18
|
+
|
|
19
|
+
const { medianMs, method } = await gpuFrameTime(device, (frame, i) => {
|
|
20
|
+
frame.renderPass(scenePass, (pass) => drawScene(pass, i));
|
|
21
|
+
frame.renderPass(floorPass, (pass) => drawFloor(pass, i));
|
|
22
|
+
});
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- `options.frames` (default 120), `options.warmup` (default 30), `options.batch` (wall-clock batch
|
|
26
|
+
size, default 8), `options.forceWallClock`, `options.label`.
|
|
27
|
+
- Returns `GpuFrameTimeResult`: `{ medianMs, meanMs, minMs, p95Ms, samples, method }` where `method`
|
|
28
|
+
is `"timestamp-query"` or `"wall-clock"`.
|
|
29
|
+
- Compare medians before vs after. For absolute numbers use a real-GPU device (`@vgpu/adapter-node`
|
|
30
|
+
Dawn or a browser); software backends give relative signal only.
|
|
31
|
+
|
|
32
|
+
## pixelDiff
|
|
33
|
+
|
|
34
|
+
`pixelDiff(a, b)` compares two renders byte-for-byte. Pass two `Texture`s (read back via
|
|
35
|
+
`Texture.read()`) or two `Uint8Array`s.
|
|
36
|
+
|
|
37
|
+
```ts
|
|
38
|
+
import { pixelDiff } from "@vgpu/render/perf";
|
|
39
|
+
|
|
40
|
+
const { maxByte } = await pixelDiff(before, after); // before/after are Texture or Uint8Array
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
Returns `PixelDiffResult`: `{ maxByte, meanByte, changedBytes, totalBytes, changedFraction }`.
|
|
44
|
+
Interpret `maxByte` (max per-channel delta, 0–255): `0` bit-exact, `1–2` imperceptible
|
|
45
|
+
(driver-rounding floor), more is a real visual change. A length mismatch surfaces as `maxByte = 255`.
|
|
@@ -15,4 +15,18 @@ material has uniforms. The renderer does not write camera, transform, light, or
|
|
|
15
15
|
material parameters.
|
|
16
16
|
|
|
17
17
|
`clearValue` defaults to opaque black. `depthTarget` is optional. `renderer.gpu`
|
|
18
|
-
returns the raw `GPUDevice`. `
|
|
18
|
+
returns the raw `GPUDevice`. Like other `.gpu` fields, this is an advanced,
|
|
19
|
+
semver-protected public escape hatch. `draw()` resolves after command submission.
|
|
20
|
+
|
|
21
|
+
## Hot-path caveats
|
|
22
|
+
|
|
23
|
+
`RapidRenderer` is optimized for examples and simple draws, not for hiding all
|
|
24
|
+
homepage hot-path work. For performance-critical renderers, keep pipeline,
|
|
25
|
+
buffer, bind-group, and render-bundle creation in setup/warmup code. Similarly,
|
|
26
|
+
`material().writeUniforms()` writes CPU-side uniform data for later upload and
|
|
27
|
+
should be scheduled deliberately.
|
|
28
|
+
|
|
29
|
+
Do not resolve shaders, compile shaders, or rebuild pipelines inside the animation
|
|
30
|
+
frame path. If shader source truly changes dynamically, resolve the shader and
|
|
31
|
+
create the replacement pipeline outside the frame loop, then stage or
|
|
32
|
+
double-buffer the swap so a completed pipeline is installed at a frame boundary.
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
# createRenderBundle
|
|
2
|
+
|
|
3
|
+
`createRenderBundle(device, options)` records a WebGPU render bundle once during
|
|
4
|
+
setup and returns the finished `GPURenderBundle`.
|
|
5
|
+
|
|
6
|
+
Render bundles are reusable draw packets, not mini-passes. They do not own color
|
|
7
|
+
or depth attachments and they do not decide ordering. Execute them from an
|
|
8
|
+
explicit render pass with `pass.executeBundles([bundle])`, alongside the normal
|
|
9
|
+
WebGPU state constraints for bundles and render passes.
|
|
10
|
+
|
|
11
|
+
```ts
|
|
12
|
+
const lightBundle = createRenderBundle(device, {
|
|
13
|
+
label: "hero.light-sources.bundle",
|
|
14
|
+
colorFormats: [format],
|
|
15
|
+
depthStencilFormat: "depth24plus",
|
|
16
|
+
sampleCount: 1,
|
|
17
|
+
record(bundle) {
|
|
18
|
+
bundle.setPipeline(lightPipeline);
|
|
19
|
+
bundle.setBindGroup(0, lightBindGroup);
|
|
20
|
+
bundle.draw(lightVertexCount);
|
|
21
|
+
},
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
const frame = beginFrame(device);
|
|
25
|
+
frame.renderPass(lightPassDescriptor, (pass) => {
|
|
26
|
+
pass.executeBundles([lightBundle]);
|
|
27
|
+
});
|
|
28
|
+
frame.submit();
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Options mirror the required parts of `GPURenderBundleEncoderDescriptor`:
|
|
32
|
+
`colorFormats`, optional `depthStencilFormat`, optional `sampleCount`, and the
|
|
33
|
+
optional depth/stencil read-only flags. The `record` callback receives a
|
|
34
|
+
`RenderBundleRecorder` wrapper with `gpu` escape-hatch access plus convenience
|
|
35
|
+
methods for `setPipeline`, `setBindGroup`, `setVertexBuffer`, and `draw`. The raw
|
|
36
|
+
`bundle.gpu` recorder is an advanced, semver-protected public escape hatch to the
|
|
37
|
+
underlying `GPURenderBundleEncoder`.
|
|
38
|
+
|
|
39
|
+
Create bundles during setup or resize, not in the per-frame hot path. A bundle is
|
|
40
|
+
compatible with the `colorFormats`, `depthStencilFormat`, `sampleCount`,
|
|
41
|
+
`depthReadOnly`, and `stencilReadOnly` values it was recorded with. If resize or
|
|
42
|
+
render-target reconfiguration changes any of those bundle-compatible fields,
|
|
43
|
+
re-record the affected bundles before the next frame.
|
|
44
|
+
|
|
45
|
+
VGPU preserves explicit pipeline, layout, and bind-group control. Bundle helpers
|
|
46
|
+
never infer layouts and do not switch to `layout: "auto"` for performance.
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# renderTargetForCanvas
|
|
2
|
+
|
|
3
|
+
`renderTargetForCanvas(context, options?)` adapts the current texture from a
|
|
4
|
+
`GPUCanvasContext` into a render target that can be passed to `pass()` and other
|
|
5
|
+
`@vgpu/render/passes` helpers.
|
|
6
|
+
|
|
7
|
+
The returned target resolves its `color` view lazily each time it is read, so it
|
|
8
|
+
tracks `context.getCurrentTexture()` across frames. Use `label` and `clearColor`
|
|
9
|
+
in the options object to configure the generated attachment metadata and default
|
|
10
|
+
clear color.
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
const target = renderTargetForCanvas(canvasContext, { label: "screen" });
|
|
14
|
+
pass({ mesh, material, target });
|
|
15
|
+
```
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# StorageBuffer
|
|
2
|
+
|
|
3
|
+
`StorageBuffer` is one stable storage buffer for a single render (or compute)
|
|
4
|
+
pass, rewritten as needed and change-gated by the caller. It is the
|
|
5
|
+
storage-buffer counterpart to `Uniform`: it creates a `storage | copy_dst`
|
|
6
|
+
buffer, a bind group layout with a single storage binding at binding 0, and a
|
|
7
|
+
bind group wired to that buffer. `write()` uploads bytes with
|
|
8
|
+
`queue.writeBuffer` at a fixed offset — there is no dynamic offset and the bind
|
|
9
|
+
group never changes.
|
|
10
|
+
|
|
11
|
+
## When to use `StorageBuffer` vs `Uniform` vs `UniformPool`
|
|
12
|
+
|
|
13
|
+
Use `StorageBuffer` when the data is too large or too dynamic for a uniform:
|
|
14
|
+
arrays, runtime-sized data, particle/instance state, lookup tables. Storage
|
|
15
|
+
buffers can be much larger than uniforms — up to the adapter's
|
|
16
|
+
`maxStorageBufferBindingSize` (typically 128 MiB) versus a uniform's 64 KiB —
|
|
17
|
+
and the shader can write them with `access: "read-write"`.
|
|
18
|
+
|
|
19
|
+
Use `Uniform` for the common "globals/camera per pass" case: a small, fixed,
|
|
20
|
+
read-only buffer (`var<uniform>`).
|
|
21
|
+
|
|
22
|
+
Use `UniformPool` only when you have **many** small per-draw uniforms. It is a
|
|
23
|
+
dynamic-offset ring allocator: every slot binding is marked
|
|
24
|
+
`hasDynamicOffset: true` and the whole CPU mirror is re-uploaded each frame.
|
|
25
|
+
|
|
26
|
+
## `access`: read vs read-write
|
|
27
|
+
|
|
28
|
+
`access` controls the bind group layout entry type. Both modes default to
|
|
29
|
+
`FRAGMENT | COMPUTE` visibility:
|
|
30
|
+
|
|
31
|
+
- `"read"` (default) → WGSL `var<storage, read>`, layout type
|
|
32
|
+
`"read-only-storage"`.
|
|
33
|
+
- `"read-write"` → WGSL `var<storage, read_write>`, layout type `"storage"`.
|
|
34
|
+
|
|
35
|
+
> Neither default includes `VERTEX`. Read-write storage is forbidden in the
|
|
36
|
+
> vertex stage outright. Read-only storage is _allowed_ in the vertex stage, but
|
|
37
|
+
> `maxStorageBuffersInVertexStage` is **0** on many adapters (software/CI Vulkan,
|
|
38
|
+
> some mobile GPUs) — a `VERTEX`-visible default silently invalidates the layout
|
|
39
|
+
> there and the draw no-ops. For vertex-stage read-only storage, pass
|
|
40
|
+
> `visibility: VERTEX | …` explicitly **and** raise `maxStorageBuffersInVertexStage`
|
|
41
|
+
> via `requiredLimits` when requesting the device.
|
|
42
|
+
|
|
43
|
+
## Constructor
|
|
44
|
+
|
|
45
|
+
```ts
|
|
46
|
+
new StorageBuffer(device, {
|
|
47
|
+
size, // byte size of the storage buffer (required)
|
|
48
|
+
label, // optional debug label for buffer, layout, and bind group
|
|
49
|
+
access, // "read" (default) | "read-write"
|
|
50
|
+
visibility, // GPUShaderStageFlags; defaults by access (see above)
|
|
51
|
+
bindGroupLayout, // optional pipeline-owned layout to reuse instead of creating one
|
|
52
|
+
});
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
- `size`: byte size of the storage buffer. May be far larger than a uniform.
|
|
56
|
+
- `label`: optional debug label; suffixed `.bgl` / `.bg` for the layout and bind
|
|
57
|
+
group.
|
|
58
|
+
- `access`: `"read"` (default, read-only storage) or `"read-write"` (writable
|
|
59
|
+
storage). Selects the layout entry type and the default visibility.
|
|
60
|
+
- `visibility`: shader stages that access binding 0. Defaults to
|
|
61
|
+
`FRAGMENT | COMPUTE` for both access modes (no `VERTEX` — see the note above).
|
|
62
|
+
Ignored when `bindGroupLayout` is provided.
|
|
63
|
+
- `bindGroupLayout`: reuse a pipeline-owned bind group layout instead of creating
|
|
64
|
+
one. Its binding 0 must be a storage buffer compatible with `size` and
|
|
65
|
+
`access`.
|
|
66
|
+
|
|
67
|
+
## Members
|
|
68
|
+
|
|
69
|
+
- `storageBuffer.gpu`: the underlying storage `GPUBuffer`.
|
|
70
|
+
- `storageBuffer.buffer`: the VGPU `Buffer` wrapper around `gpu`.
|
|
71
|
+
- `storageBuffer.bindGroup`: the `GPUBindGroup` to bind for the pass.
|
|
72
|
+
- `storageBuffer.bindGroupLayout`: the `GPUBindGroupLayout` (created or reused).
|
|
73
|
+
- `storageBuffer.size`: the buffer byte size.
|
|
74
|
+
- `storageBuffer.access`: the resolved access mode (`"read"` or `"read-write"`).
|
|
75
|
+
- `storageBuffer.write(data, offset = 0)`: uploads `data` via
|
|
76
|
+
`device.queue.writeBuffer(gpu, offset, data)`. No dynamic offset; the bind
|
|
77
|
+
group is unchanged. Call only when the contents actually change.
|
|
78
|
+
- `storageBuffer.destroy()` / `storageBuffer.dispose()`: releases the backing
|
|
79
|
+
buffer once; idempotent.
|
|
80
|
+
|
|
81
|
+
## Example
|
|
82
|
+
|
|
83
|
+
```ts
|
|
84
|
+
// A read-only array the fragment shader samples:
|
|
85
|
+
// @group(0) @binding(0) var<storage, read> values: array<f32>;
|
|
86
|
+
const storage = new StorageBuffer(device, { size: 4 * count, label: "values" });
|
|
87
|
+
storage.write(new Float32Array(values));
|
|
88
|
+
|
|
89
|
+
const pipeline = createRenderPipelineFromDescriptor(device, {
|
|
90
|
+
layout: device.gpu.createPipelineLayout({ bindGroupLayouts: [storage.bindGroupLayout] }),
|
|
91
|
+
vertex: { module, entryPoint: "vs_main" },
|
|
92
|
+
fragment: { module, entryPoint: "fs_main", targets: [{ format }] },
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
pass.setPipeline(pipeline);
|
|
96
|
+
pass.setBindGroup(0, storage.bindGroup);
|
|
97
|
+
pass.draw(3);
|
|
98
|
+
|
|
99
|
+
// A compute-written scratch buffer:
|
|
100
|
+
// @group(0) @binding(0) var<storage, read_write> out: array<u32>;
|
|
101
|
+
const scratch = new StorageBuffer(device, { size: 4 * count, access: "read-write" });
|
|
102
|
+
```
|