@aardworx/wombat.rendering 0.1.0
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/package.json +67 -0
- package/src/commands/clear.ts +66 -0
- package/src/commands/index.ts +11 -0
- package/src/commands/render.ts +106 -0
- package/src/core/acquire.ts +22 -0
- package/src/core/adaptiveResource.ts +88 -0
- package/src/core/buffer.ts +45 -0
- package/src/core/bufferView.ts +26 -0
- package/src/core/clear.ts +16 -0
- package/src/core/command.ts +50 -0
- package/src/core/drawCall.ts +22 -0
- package/src/core/framebuffer.ts +37 -0
- package/src/core/framebufferSignature.ts +19 -0
- package/src/core/index.ts +116 -0
- package/src/core/pipelineState.ts +64 -0
- package/src/core/renderContext.ts +48 -0
- package/src/core/renderObject.ts +57 -0
- package/src/core/renderTask.ts +17 -0
- package/src/core/renderTree.ts +29 -0
- package/src/core/sampler.ts +12 -0
- package/src/core/shader.ts +28 -0
- package/src/core/texture.ts +63 -0
- package/src/index.ts +18 -0
- package/src/resources/adaptiveBuffer.ts +135 -0
- package/src/resources/adaptiveSampler.ts +82 -0
- package/src/resources/adaptiveTexture.ts +269 -0
- package/src/resources/framebuffer.ts +163 -0
- package/src/resources/framebufferSignature.ts +36 -0
- package/src/resources/index.ts +82 -0
- package/src/resources/mipGen.ts +148 -0
- package/src/resources/preparedComputeShader.ts +426 -0
- package/src/resources/preparedRenderObject.ts +498 -0
- package/src/resources/renderPipeline.ts +148 -0
- package/src/resources/shaderDiagnostics.ts +52 -0
- package/src/resources/sourceMapDecoder.ts +133 -0
- package/src/resources/uniformBuffer.ts +170 -0
- package/src/resources/webgpuFlags.ts +43 -0
- package/src/runtime/copy.ts +13 -0
- package/src/runtime/index.ts +24 -0
- package/src/runtime/renderTask.ts +137 -0
- package/src/runtime/renderTo.ts +154 -0
- package/src/runtime/runtime.ts +113 -0
- package/src/runtime/scenePass.ts +273 -0
- package/src/window/canvas.ts +252 -0
- package/src/window/index.ts +14 -0
- package/src/window/loop.ts +62 -0
package/package.json
ADDED
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@aardworx/wombat.rendering",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "WebGPU rendering layer for the Wombat TypeScript stack — RenderObject/RenderTask/runtime + window glue, port of Aardvark.Rendering's lower layers on top of WebGPU and wombat.shader.",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"author": "krauthaufen",
|
|
7
|
+
"repository": {
|
|
8
|
+
"type": "git",
|
|
9
|
+
"url": "git+https://github.com/krauthaufen/wombat.rendering.git"
|
|
10
|
+
},
|
|
11
|
+
"homepage": "https://github.com/krauthaufen/wombat.rendering",
|
|
12
|
+
"keywords": [
|
|
13
|
+
"wombat",
|
|
14
|
+
"aardvark",
|
|
15
|
+
"webgpu",
|
|
16
|
+
"rendering",
|
|
17
|
+
"renderobject",
|
|
18
|
+
"rendertask",
|
|
19
|
+
"scenegraph",
|
|
20
|
+
"shader"
|
|
21
|
+
],
|
|
22
|
+
"type": "module",
|
|
23
|
+
"main": "./dist/index.js",
|
|
24
|
+
"module": "./dist/index.js",
|
|
25
|
+
"types": "./dist/index.d.ts",
|
|
26
|
+
"exports": {
|
|
27
|
+
".": {
|
|
28
|
+
"types": "./dist/index.d.ts",
|
|
29
|
+
"import": "./dist/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./core": {
|
|
32
|
+
"types": "./dist/core/index.d.ts",
|
|
33
|
+
"import": "./dist/core/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./resources": {
|
|
36
|
+
"types": "./dist/resources/index.d.ts",
|
|
37
|
+
"import": "./dist/resources/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./commands": {
|
|
40
|
+
"types": "./dist/commands/index.d.ts",
|
|
41
|
+
"import": "./dist/commands/index.js"
|
|
42
|
+
},
|
|
43
|
+
"./runtime": {
|
|
44
|
+
"types": "./dist/runtime/index.d.ts",
|
|
45
|
+
"import": "./dist/runtime/index.js"
|
|
46
|
+
},
|
|
47
|
+
"./window": {
|
|
48
|
+
"types": "./dist/window/index.d.ts",
|
|
49
|
+
"import": "./dist/window/index.js"
|
|
50
|
+
}
|
|
51
|
+
},
|
|
52
|
+
"files": [
|
|
53
|
+
"dist",
|
|
54
|
+
"src"
|
|
55
|
+
],
|
|
56
|
+
"dependencies": {
|
|
57
|
+
"@aardworx/wombat.adaptive": "^0.2.1",
|
|
58
|
+
"@aardworx/wombat.base": "^0.3.0",
|
|
59
|
+
"@aardworx/wombat.shader": "^0.2.1"
|
|
60
|
+
},
|
|
61
|
+
"devDependencies": {
|
|
62
|
+
"@webgpu/types": "^0.1.69"
|
|
63
|
+
},
|
|
64
|
+
"publishConfig": {
|
|
65
|
+
"access": "public"
|
|
66
|
+
}
|
|
67
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
// clear(cmd, output, values) — open a render pass on `output`
|
|
2
|
+
// with `loadOp: "clear"` for the named attachments listed in
|
|
3
|
+
// `values`, and `loadOp: "load"` for the rest. End the pass
|
|
4
|
+
// immediately. This is the encoding for a `Clear` Command.
|
|
5
|
+
//
|
|
6
|
+
// Note that color clears are typed as `V4f | V4i | V4ui`; we
|
|
7
|
+
// translate to WebGPU's `GPUColorDict` (always `r/g/b/a` numbers)
|
|
8
|
+
// directly. WebGPU figures out the integer-vs-float interpretation
|
|
9
|
+
// from the texture's format.
|
|
10
|
+
|
|
11
|
+
import type { ClearValues, IFramebuffer } from "../core/index.js";
|
|
12
|
+
|
|
13
|
+
export function clear(
|
|
14
|
+
cmd: GPUCommandEncoder,
|
|
15
|
+
output: IFramebuffer,
|
|
16
|
+
values: ClearValues,
|
|
17
|
+
): void {
|
|
18
|
+
const colorAttachments: GPURenderPassColorAttachment[] = [];
|
|
19
|
+
for (const [name] of output.signature.colors) {
|
|
20
|
+
const view = output.colors.tryFind(name);
|
|
21
|
+
if (view === undefined) {
|
|
22
|
+
throw new Error(`clear: framebuffer is missing color attachment "${name}"`);
|
|
23
|
+
}
|
|
24
|
+
const cv = values.colors?.tryFind(name);
|
|
25
|
+
if (cv !== undefined) {
|
|
26
|
+
const d = cv as unknown as { _data: ArrayLike<number> };
|
|
27
|
+
colorAttachments.push({
|
|
28
|
+
view,
|
|
29
|
+
loadOp: "clear",
|
|
30
|
+
storeOp: "store",
|
|
31
|
+
clearValue: { r: d._data[0]!, g: d._data[1]!, b: d._data[2]!, a: d._data[3]! },
|
|
32
|
+
});
|
|
33
|
+
} else {
|
|
34
|
+
colorAttachments.push({ view, loadOp: "load", storeOp: "store" });
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
let depthStencilAttachment: GPURenderPassDepthStencilAttachment | undefined;
|
|
38
|
+
if (output.signature.depthStencil !== undefined) {
|
|
39
|
+
if (output.depthStencil === undefined) {
|
|
40
|
+
throw new Error("clear: signature has depthStencil but framebuffer.depthStencil is undefined");
|
|
41
|
+
}
|
|
42
|
+
const sig = output.signature.depthStencil;
|
|
43
|
+
const att: GPURenderPassDepthStencilAttachment = { view: output.depthStencil };
|
|
44
|
+
if (sig.hasDepth) {
|
|
45
|
+
if (values.depth !== undefined) {
|
|
46
|
+
att.depthLoadOp = "clear"; att.depthStoreOp = "store"; att.depthClearValue = values.depth;
|
|
47
|
+
} else {
|
|
48
|
+
att.depthLoadOp = "load"; att.depthStoreOp = "store";
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
if (sig.hasStencil) {
|
|
52
|
+
if (values.stencil !== undefined) {
|
|
53
|
+
att.stencilLoadOp = "clear"; att.stencilStoreOp = "store"; att.stencilClearValue = values.stencil;
|
|
54
|
+
} else {
|
|
55
|
+
att.stencilLoadOp = "load"; att.stencilStoreOp = "store";
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
depthStencilAttachment = att;
|
|
59
|
+
}
|
|
60
|
+
const desc: GPURenderPassDescriptor = {
|
|
61
|
+
colorAttachments,
|
|
62
|
+
...(depthStencilAttachment !== undefined ? { depthStencilAttachment } : {}),
|
|
63
|
+
};
|
|
64
|
+
const pass = cmd.beginRenderPass(desc);
|
|
65
|
+
pass.end();
|
|
66
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
// Public API of @aardworx/wombat.rendering-commands.
|
|
2
|
+
// Layer 3 — command-stream functions on `GPUCommandEncoder`.
|
|
3
|
+
|
|
4
|
+
export { clear } from "./clear.js";
|
|
5
|
+
|
|
6
|
+
export {
|
|
7
|
+
render,
|
|
8
|
+
renderMany,
|
|
9
|
+
beginPassDescriptor,
|
|
10
|
+
type Recordable,
|
|
11
|
+
} from "./render.js";
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
// render(cmd, prepared, output, token) — open a render pass on
|
|
2
|
+
// `output` (loadOp = "load"; clears go through the `Clear` command),
|
|
3
|
+
// record the prepared object, end the pass.
|
|
4
|
+
//
|
|
5
|
+
// Multiple prepared objects can be batched into one pass via
|
|
6
|
+
// `renderMany(...)` — same pass, multiple `record` calls — which
|
|
7
|
+
// is the path the RenderTask walker uses for an `Ordered` /
|
|
8
|
+
// `Unordered` subtree of leaves all targeting the same FBO.
|
|
9
|
+
|
|
10
|
+
import type { ClearValues, IFramebuffer } from "../core/index.js";
|
|
11
|
+
import type { AdaptiveToken } from "@aardworx/wombat.adaptive";
|
|
12
|
+
|
|
13
|
+
// Forward type — we don't import from -resources to avoid a
|
|
14
|
+
// commands→resources dependency cycle. Anyone constructing a
|
|
15
|
+
// PreparedRenderObject hands it to us; we only call `record`.
|
|
16
|
+
export interface Recordable {
|
|
17
|
+
record(pass: GPURenderPassEncoder, token: AdaptiveToken): void;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Build a render-pass descriptor for `output`. If `clear` is set,
|
|
22
|
+
* named attachments use `loadOp: "clear"` with the requested
|
|
23
|
+
* values; the rest fall back to `loadOp: "load"`. Used by
|
|
24
|
+
* `render` / `renderMany` and by the runtime walker's Clear+Render
|
|
25
|
+
* coalescing path.
|
|
26
|
+
*/
|
|
27
|
+
export function beginPassDescriptor(output: IFramebuffer, clear?: ClearValues): GPURenderPassDescriptor {
|
|
28
|
+
const colorAttachments: GPURenderPassColorAttachment[] = [];
|
|
29
|
+
const msaa = output.signature.sampleCount > 1;
|
|
30
|
+
for (const [name] of output.signature.colors) {
|
|
31
|
+
const view = output.colors.tryFind(name);
|
|
32
|
+
if (view === undefined) {
|
|
33
|
+
throw new Error(`render: framebuffer is missing color attachment "${name}"`);
|
|
34
|
+
}
|
|
35
|
+
const resolveTarget = msaa ? output.resolveColors?.tryFind(name) : undefined;
|
|
36
|
+
if (msaa && resolveTarget === undefined) {
|
|
37
|
+
throw new Error(`render: MSAA framebuffer is missing resolve target for "${name}"`);
|
|
38
|
+
}
|
|
39
|
+
const cv = clear?.colors?.tryFind(name);
|
|
40
|
+
if (cv !== undefined) {
|
|
41
|
+
const d = cv as unknown as { _data: ArrayLike<number> };
|
|
42
|
+
colorAttachments.push({
|
|
43
|
+
view, loadOp: "clear", storeOp: "store",
|
|
44
|
+
...(resolveTarget !== undefined ? { resolveTarget } : {}),
|
|
45
|
+
clearValue: { r: d._data[0]!, g: d._data[1]!, b: d._data[2]!, a: d._data[3]! },
|
|
46
|
+
});
|
|
47
|
+
} else {
|
|
48
|
+
colorAttachments.push({
|
|
49
|
+
view, loadOp: "load", storeOp: "store",
|
|
50
|
+
...(resolveTarget !== undefined ? { resolveTarget } : {}),
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
let depthStencilAttachment: GPURenderPassDepthStencilAttachment | undefined;
|
|
55
|
+
if (output.signature.depthStencil !== undefined) {
|
|
56
|
+
if (output.depthStencil === undefined) {
|
|
57
|
+
throw new Error("render: signature has depthStencil but framebuffer.depthStencil is undefined");
|
|
58
|
+
}
|
|
59
|
+
const sig = output.signature.depthStencil;
|
|
60
|
+
const att: GPURenderPassDepthStencilAttachment = { view: output.depthStencil };
|
|
61
|
+
if (sig.hasDepth) {
|
|
62
|
+
if (clear?.depth !== undefined) {
|
|
63
|
+
att.depthLoadOp = "clear"; att.depthStoreOp = "store"; att.depthClearValue = clear.depth;
|
|
64
|
+
} else {
|
|
65
|
+
att.depthLoadOp = "load"; att.depthStoreOp = "store";
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
if (sig.hasStencil) {
|
|
69
|
+
if (clear?.stencil !== undefined) {
|
|
70
|
+
att.stencilLoadOp = "clear"; att.stencilStoreOp = "store"; att.stencilClearValue = clear.stencil;
|
|
71
|
+
} else {
|
|
72
|
+
att.stencilLoadOp = "load"; att.stencilStoreOp = "store";
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
depthStencilAttachment = att;
|
|
76
|
+
}
|
|
77
|
+
return {
|
|
78
|
+
colorAttachments,
|
|
79
|
+
...(depthStencilAttachment !== undefined ? { depthStencilAttachment } : {}),
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
export function render(
|
|
84
|
+
cmd: GPUCommandEncoder,
|
|
85
|
+
prepared: Recordable,
|
|
86
|
+
output: IFramebuffer,
|
|
87
|
+
token: AdaptiveToken,
|
|
88
|
+
clear?: ClearValues,
|
|
89
|
+
): void {
|
|
90
|
+
const pass = cmd.beginRenderPass(beginPassDescriptor(output, clear));
|
|
91
|
+
prepared.record(pass, token);
|
|
92
|
+
pass.end();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export function renderMany(
|
|
96
|
+
cmd: GPUCommandEncoder,
|
|
97
|
+
prepared: readonly Recordable[],
|
|
98
|
+
output: IFramebuffer,
|
|
99
|
+
token: AdaptiveToken,
|
|
100
|
+
clear?: ClearValues,
|
|
101
|
+
): void {
|
|
102
|
+
if (prepared.length === 0) return;
|
|
103
|
+
const pass = cmd.beginRenderPass(beginPassDescriptor(output, clear));
|
|
104
|
+
for (const p of prepared) p.record(pass, token);
|
|
105
|
+
pass.end();
|
|
106
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// tryAcquire / tryRelease — propagate acquire/release through
|
|
2
|
+
// the dependency graph. If a value is an `AdaptiveResource`, drive
|
|
3
|
+
// its lifetime; otherwise no-op. Used by wrapper resources
|
|
4
|
+
// (`prepareAdaptiveBuffer`, `prepareAdaptiveTexture`, …) so that
|
|
5
|
+
// when a downstream consumer acquires them, any upstream
|
|
6
|
+
// AdaptiveResources they read also get acquired.
|
|
7
|
+
//
|
|
8
|
+
// This is what makes `renderTo` work: the returned `aval<ITexture>`
|
|
9
|
+
// is an AdaptiveResource holding an FBO + inner RenderTask. When
|
|
10
|
+
// a downstream RenderObject's prepareAdaptiveTexture acquires its
|
|
11
|
+
// source, that source is the renderTo result, and the FBO + task
|
|
12
|
+
// come live automatically.
|
|
13
|
+
|
|
14
|
+
import { AdaptiveResource } from "./adaptiveResource.js";
|
|
15
|
+
|
|
16
|
+
export function tryAcquire(av: unknown): void {
|
|
17
|
+
if (av instanceof AdaptiveResource) av.acquire();
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function tryRelease(av: unknown): void {
|
|
21
|
+
if (av instanceof AdaptiveResource) av.release();
|
|
22
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
// AdaptiveResource<T> — a ref-counted, adaptively-updated GPU
|
|
2
|
+
// handle that **is itself an `aval<T>`**. Lifted directly from
|
|
3
|
+
// Aardvark.Base's `AdaptiveResource<'a>`.
|
|
4
|
+
//
|
|
5
|
+
// The shape lets `runtime.renderTo(scene, size) → aval<ITexture>`
|
|
6
|
+
// work the way Aardvark.Rendering's `RenderTask.renderTo` works:
|
|
7
|
+
// a downstream consumer treats it like any other `aval<ITexture>`,
|
|
8
|
+
// the FBO is allocated lazily on first `acquire`, and torn down
|
|
9
|
+
// when the last consumer releases.
|
|
10
|
+
//
|
|
11
|
+
// Lifecycle:
|
|
12
|
+
// acquire() — refCount++. On 0→1 transition, calls `create()`,
|
|
13
|
+
// which is where one-time setup happens (allocate
|
|
14
|
+
// an FBO, register input subscriptions, etc.).
|
|
15
|
+
// getValue(token) — the standard aval contract. Calls
|
|
16
|
+
// `compute(token)`, which is where adaptive work
|
|
17
|
+
// lives (re-render scene if size or scene changed,
|
|
18
|
+
// return the texture handle). May encode commands
|
|
19
|
+
// on the currently-active `RenderContext.encoder`.
|
|
20
|
+
// release() — refCount--. On 1→0 transition, calls `destroy()`,
|
|
21
|
+
// which frees the GPU resources.
|
|
22
|
+
//
|
|
23
|
+
// Subscribing to value changes (`addCallback`) does **not** acquire
|
|
24
|
+
// — acquisition is an explicit lifetime concern and a subscription
|
|
25
|
+
// alone does not justify keeping GPU memory live. The runtime's
|
|
26
|
+
// RenderTask compilation walks its referenced AdaptiveResources
|
|
27
|
+
// and acquires them at task-construction time, releases them on
|
|
28
|
+
// task disposal.
|
|
29
|
+
|
|
30
|
+
import {
|
|
31
|
+
AbstractVal,
|
|
32
|
+
type AdaptiveToken,
|
|
33
|
+
} from "@aardworx/wombat.adaptive";
|
|
34
|
+
|
|
35
|
+
export abstract class AdaptiveResource<T> extends AbstractVal<T> {
|
|
36
|
+
private _refCount = 0;
|
|
37
|
+
|
|
38
|
+
/** Allocate one-time GPU resources. Called on the 0→1 acquire. */
|
|
39
|
+
protected abstract create(): void;
|
|
40
|
+
/** Free GPU resources. Called on the 1→0 release. */
|
|
41
|
+
protected abstract destroy(): void;
|
|
42
|
+
/**
|
|
43
|
+
* The standard aval contract: produce the current handle for
|
|
44
|
+
* this resource. May read other adaptive inputs via `token` and
|
|
45
|
+
* may encode work onto `RenderContext.encoder` if one is active
|
|
46
|
+
* (uploads, compute dispatches, render-to-texture, ...).
|
|
47
|
+
*/
|
|
48
|
+
abstract override compute(token: AdaptiveToken): T;
|
|
49
|
+
|
|
50
|
+
acquire(): void {
|
|
51
|
+
this._refCount++;
|
|
52
|
+
if (this._refCount === 1) this.create();
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
release(): void {
|
|
56
|
+
if (this._refCount <= 0) {
|
|
57
|
+
throw new Error("AdaptiveResource: release without matching acquire");
|
|
58
|
+
}
|
|
59
|
+
this._refCount--;
|
|
60
|
+
if (this._refCount === 0) this.destroy();
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/** True iff at least one consumer holds an acquire. */
|
|
64
|
+
get isActive(): boolean { return this._refCount > 0; }
|
|
65
|
+
get refCount(): number { return this._refCount; }
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Derive a new `AdaptiveResource<R>` whose lifetime forwards to
|
|
69
|
+
* `this`. Consumers of the derived resource transitively
|
|
70
|
+
* acquire/release `this`. Use this to expose structured outputs
|
|
71
|
+
* of a complex resource (e.g. the colour textures of a `renderTo`
|
|
72
|
+
* framebuffer) as individual `aval<ITexture>`s.
|
|
73
|
+
*/
|
|
74
|
+
derive<R>(project: (t: T) => R): AdaptiveResource<R> {
|
|
75
|
+
return new DerivedAdaptiveResource<T, R>(this, project);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
class DerivedAdaptiveResource<T, R> extends AdaptiveResource<R> {
|
|
80
|
+
constructor(private readonly parent: AdaptiveResource<T>, private readonly project: (t: T) => R) {
|
|
81
|
+
super();
|
|
82
|
+
}
|
|
83
|
+
protected override create(): void { this.parent.acquire(); }
|
|
84
|
+
protected override destroy(): void { this.parent.release(); }
|
|
85
|
+
override compute(token: AdaptiveToken): R {
|
|
86
|
+
return this.project(this.parent.getValue(token));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// IBuffer — a buffer source that can be either a real GPU buffer
|
|
2
|
+
// or host-side memory (an ArrayBuffer, a TypedArray, or a packed
|
|
3
|
+
// view from wombat.base such as `V2fArray._data` or `arr.buffer`).
|
|
4
|
+
//
|
|
5
|
+
// The user is free to mix and match: an `aval<IBuffer>` can flip
|
|
6
|
+
// between `gpu` and `host` over its lifetime, and the rendering
|
|
7
|
+
// layer transparently handles the upload when a `host` value is
|
|
8
|
+
// observed.
|
|
9
|
+
//
|
|
10
|
+
// `host.data` is typed as `BufferSource` (DOM standard) which
|
|
11
|
+
// admits `ArrayBuffer`, `SharedArrayBuffer`, and any
|
|
12
|
+
// `ArrayBufferView` — covering wombat.base's packed array views
|
|
13
|
+
// that expose a `.buffer` (ArrayBuffer) or a typed array directly.
|
|
14
|
+
|
|
15
|
+
export type HostBufferSource = ArrayBuffer | ArrayBufferView;
|
|
16
|
+
|
|
17
|
+
export type IBuffer =
|
|
18
|
+
| {
|
|
19
|
+
readonly kind: "gpu";
|
|
20
|
+
readonly buffer: GPUBuffer;
|
|
21
|
+
readonly sizeBytes: number;
|
|
22
|
+
}
|
|
23
|
+
| {
|
|
24
|
+
readonly kind: "host";
|
|
25
|
+
readonly data: HostBufferSource;
|
|
26
|
+
readonly sizeBytes: number;
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
export const IBuffer = {
|
|
30
|
+
fromGPU(buffer: GPUBuffer, sizeBytes?: number): IBuffer {
|
|
31
|
+
return { kind: "gpu", buffer, sizeBytes: sizeBytes ?? buffer.size };
|
|
32
|
+
},
|
|
33
|
+
/**
|
|
34
|
+
* Wrap a host-side `ArrayBuffer` / `ArrayBufferView`. Pass any
|
|
35
|
+
* wombat.base packed array's `.buffer` directly, or a `Float32Array`,
|
|
36
|
+
* etc.
|
|
37
|
+
*/
|
|
38
|
+
fromHost(data: HostBufferSource): IBuffer {
|
|
39
|
+
const sizeBytes =
|
|
40
|
+
data instanceof ArrayBuffer
|
|
41
|
+
? data.byteLength
|
|
42
|
+
: data.byteLength;
|
|
43
|
+
return { kind: "host", data, sizeBytes };
|
|
44
|
+
},
|
|
45
|
+
} as const;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
// BufferView — a typed slice of an `IBuffer` used as a vertex
|
|
2
|
+
// attribute, instance attribute, or index source. The buffer
|
|
3
|
+
// itself can be a real GPU buffer or host-side memory; the
|
|
4
|
+
// runtime handles upload transparently.
|
|
5
|
+
//
|
|
6
|
+
// `format` carries the WebGPU vertex format when this view is
|
|
7
|
+
// bound as a vertex / instance attribute. `indexFormat` is set
|
|
8
|
+
// when the view is bound as an index buffer; in that case
|
|
9
|
+
// `format` is ignored. Both are optional so callers don't have
|
|
10
|
+
// to fill in the irrelevant one.
|
|
11
|
+
|
|
12
|
+
import type { IBuffer } from "./buffer.js";
|
|
13
|
+
|
|
14
|
+
export interface BufferView {
|
|
15
|
+
readonly buffer: IBuffer;
|
|
16
|
+
/** Byte offset into `buffer` where the view starts. */
|
|
17
|
+
readonly offset: number;
|
|
18
|
+
/** Number of elements (not bytes) in the view. */
|
|
19
|
+
readonly count: number;
|
|
20
|
+
/** Stride between consecutive elements, in bytes. */
|
|
21
|
+
readonly stride: number;
|
|
22
|
+
/** Vertex format — used when bound as a vertex / instance attribute. */
|
|
23
|
+
readonly format: GPUVertexFormat | GPUIndexFormat;
|
|
24
|
+
/** Index format — used when bound as an index buffer. Overrides `format`. */
|
|
25
|
+
readonly indexFormat?: GPUIndexFormat;
|
|
26
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// ClearValues — per-attachment, name-keyed clear specification.
|
|
2
|
+
// Color clears are typed as V4f / V4i / V4ui to match the
|
|
3
|
+
// attachment's format; depth and stencil are plain numbers since
|
|
4
|
+
// WebGPU's GPURenderPassDepthStencilAttachment takes scalars.
|
|
5
|
+
// Attachments not named in `colors` keep their previous contents.
|
|
6
|
+
|
|
7
|
+
import type { HashMap } from "@aardworx/wombat.adaptive";
|
|
8
|
+
import type { V4f, V4i, V4ui } from "@aardworx/wombat.base";
|
|
9
|
+
|
|
10
|
+
export type ClearColor = V4f | V4i | V4ui;
|
|
11
|
+
|
|
12
|
+
export interface ClearValues {
|
|
13
|
+
readonly colors?: HashMap<string, ClearColor>;
|
|
14
|
+
readonly depth?: number;
|
|
15
|
+
readonly stencil?: number;
|
|
16
|
+
}
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
// Command — the per-frame primitive consumed by `runtime.compile`.
|
|
2
|
+
// Four kinds:
|
|
3
|
+
// Render: encode a RenderTree into an FBO.
|
|
4
|
+
// Clear: clear individual named attachments of an FBO.
|
|
5
|
+
// Copy: buffer↔buffer / texture↔texture transfers.
|
|
6
|
+
// Custom: escape hatch — receives the raw GPUCommandEncoder.
|
|
7
|
+
//
|
|
8
|
+
// Compute and Upload are NOT commands; they live inside
|
|
9
|
+
// AdaptiveResource update logic and run implicitly when a Render
|
|
10
|
+
// command needs a dirty resource.
|
|
11
|
+
|
|
12
|
+
import type { aval } from "@aardworx/wombat.adaptive";
|
|
13
|
+
import type { ClearValues } from "./clear.js";
|
|
14
|
+
import type { IFramebuffer } from "./framebuffer.js";
|
|
15
|
+
import type { RenderTree } from "./renderTree.js";
|
|
16
|
+
|
|
17
|
+
export interface BufferCopyRange {
|
|
18
|
+
readonly srcOffset: number;
|
|
19
|
+
readonly dstOffset: number;
|
|
20
|
+
readonly size: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface BufferCopy {
|
|
24
|
+
readonly kind: "buffer";
|
|
25
|
+
readonly src: GPUBuffer;
|
|
26
|
+
readonly dst: GPUBuffer;
|
|
27
|
+
readonly range?: BufferCopyRange;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface TextureCopy {
|
|
31
|
+
readonly kind: "texture";
|
|
32
|
+
readonly src: GPUImageCopyTexture;
|
|
33
|
+
readonly dst: GPUImageCopyTexture;
|
|
34
|
+
readonly size: GPUExtent3DStrict;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export type CopySpec = BufferCopy | TextureCopy;
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* `Render` and `Clear` take an `aval<IFramebuffer>` for their output
|
|
41
|
+
* so that swap-chain framebuffers (which produce a fresh
|
|
42
|
+
* `GPUTextureView` every animation frame) flow through cleanly.
|
|
43
|
+
* For static FBOs, wrap with `cval(...)` from `@aardworx/wombat.adaptive`
|
|
44
|
+
* — the runtime evaluates the aval per frame.
|
|
45
|
+
*/
|
|
46
|
+
export type Command =
|
|
47
|
+
| { readonly kind: "Render"; readonly output: aval<IFramebuffer>; readonly tree: RenderTree }
|
|
48
|
+
| { readonly kind: "Clear"; readonly output: aval<IFramebuffer>; readonly values: ClearValues }
|
|
49
|
+
| { readonly kind: "Copy"; readonly copy: CopySpec }
|
|
50
|
+
| { readonly kind: "Custom"; readonly encode: (cmd: GPUCommandEncoder) => void };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
// DrawCall — what to actually draw, once the pipeline + bindings
|
|
2
|
+
// are bound. Indexed and non-indexed variants; both can be
|
|
3
|
+
// instanced.
|
|
4
|
+
|
|
5
|
+
export interface IndexedDrawCall {
|
|
6
|
+
readonly kind: "indexed";
|
|
7
|
+
readonly indexCount: number;
|
|
8
|
+
readonly instanceCount: number;
|
|
9
|
+
readonly firstIndex: number;
|
|
10
|
+
readonly baseVertex: number;
|
|
11
|
+
readonly firstInstance: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface NonIndexedDrawCall {
|
|
15
|
+
readonly kind: "non-indexed";
|
|
16
|
+
readonly vertexCount: number;
|
|
17
|
+
readonly instanceCount: number;
|
|
18
|
+
readonly firstVertex: number;
|
|
19
|
+
readonly firstInstance: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export type DrawCall = IndexedDrawCall | NonIndexedDrawCall;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
// IFramebuffer — a concrete render target. The signature describes
|
|
2
|
+
// the *shape*; the framebuffer pairs that shape with concrete
|
|
3
|
+
// texture views. Both color and depth/stencil views are addressed
|
|
4
|
+
// by the same names declared in the signature.
|
|
5
|
+
|
|
6
|
+
import type { HashMap } from "@aardworx/wombat.adaptive";
|
|
7
|
+
import type { FramebufferSignature } from "./framebufferSignature.js";
|
|
8
|
+
|
|
9
|
+
export interface IFramebuffer {
|
|
10
|
+
readonly signature: FramebufferSignature;
|
|
11
|
+
/**
|
|
12
|
+
* name → color attachment view used inside the render pass.
|
|
13
|
+
* For `sampleCount > 1` these are views into the multisample
|
|
14
|
+
* textures; the resolve target views live in `resolveColors`.
|
|
15
|
+
*/
|
|
16
|
+
readonly colors: HashMap<string, GPUTextureView>;
|
|
17
|
+
readonly depthStencil?: GPUTextureView;
|
|
18
|
+
/**
|
|
19
|
+
* Underlying GPU textures suitable for *sampling* downstream.
|
|
20
|
+
* - `sampleCount = 1`: same textures as `colors`.
|
|
21
|
+
* - `sampleCount > 1`: the resolve textures (single-sample);
|
|
22
|
+
* the multisample textures are not sampleable.
|
|
23
|
+
* Populated by `allocateFramebuffer`; used by `renderTo` to
|
|
24
|
+
* expose results as `ITexture.fromGPU(...)`.
|
|
25
|
+
*/
|
|
26
|
+
readonly colorTextures?: HashMap<string, GPUTexture>;
|
|
27
|
+
/**
|
|
28
|
+
* name → resolve target view, only present when
|
|
29
|
+
* `signature.sampleCount > 1`. Wired into the render pass
|
|
30
|
+
* descriptor as `resolveTarget` so the GPU resolves
|
|
31
|
+
* multisample → single-sample at end of pass.
|
|
32
|
+
*/
|
|
33
|
+
readonly resolveColors?: HashMap<string, GPUTextureView>;
|
|
34
|
+
readonly depthStencilTexture?: GPUTexture;
|
|
35
|
+
readonly width: number;
|
|
36
|
+
readonly height: number;
|
|
37
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
// FramebufferSignature — the typed shape of a render target.
|
|
2
|
+
// Attachments are addressed by name (never by index). The runtime
|
|
3
|
+
// maps names → WebGPU attachment slots using a shader's
|
|
4
|
+
// ProgramInterface, so users only ever talk about names like
|
|
5
|
+
// "albedo" / "normal" / "objectId", not location indices.
|
|
6
|
+
|
|
7
|
+
import type { HashMap } from "@aardworx/wombat.adaptive";
|
|
8
|
+
|
|
9
|
+
export interface DepthStencilAttachmentSignature {
|
|
10
|
+
readonly format: GPUTextureFormat;
|
|
11
|
+
readonly hasDepth: boolean;
|
|
12
|
+
readonly hasStencil: boolean;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface FramebufferSignature {
|
|
16
|
+
readonly colors: HashMap<string, GPUTextureFormat>;
|
|
17
|
+
readonly depthStencil?: DepthStencilAttachmentSignature;
|
|
18
|
+
readonly sampleCount: number;
|
|
19
|
+
}
|