@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.
Files changed (46) hide show
  1. package/package.json +67 -0
  2. package/src/commands/clear.ts +66 -0
  3. package/src/commands/index.ts +11 -0
  4. package/src/commands/render.ts +106 -0
  5. package/src/core/acquire.ts +22 -0
  6. package/src/core/adaptiveResource.ts +88 -0
  7. package/src/core/buffer.ts +45 -0
  8. package/src/core/bufferView.ts +26 -0
  9. package/src/core/clear.ts +16 -0
  10. package/src/core/command.ts +50 -0
  11. package/src/core/drawCall.ts +22 -0
  12. package/src/core/framebuffer.ts +37 -0
  13. package/src/core/framebufferSignature.ts +19 -0
  14. package/src/core/index.ts +116 -0
  15. package/src/core/pipelineState.ts +64 -0
  16. package/src/core/renderContext.ts +48 -0
  17. package/src/core/renderObject.ts +57 -0
  18. package/src/core/renderTask.ts +17 -0
  19. package/src/core/renderTree.ts +29 -0
  20. package/src/core/sampler.ts +12 -0
  21. package/src/core/shader.ts +28 -0
  22. package/src/core/texture.ts +63 -0
  23. package/src/index.ts +18 -0
  24. package/src/resources/adaptiveBuffer.ts +135 -0
  25. package/src/resources/adaptiveSampler.ts +82 -0
  26. package/src/resources/adaptiveTexture.ts +269 -0
  27. package/src/resources/framebuffer.ts +163 -0
  28. package/src/resources/framebufferSignature.ts +36 -0
  29. package/src/resources/index.ts +82 -0
  30. package/src/resources/mipGen.ts +148 -0
  31. package/src/resources/preparedComputeShader.ts +426 -0
  32. package/src/resources/preparedRenderObject.ts +498 -0
  33. package/src/resources/renderPipeline.ts +148 -0
  34. package/src/resources/shaderDiagnostics.ts +52 -0
  35. package/src/resources/sourceMapDecoder.ts +133 -0
  36. package/src/resources/uniformBuffer.ts +170 -0
  37. package/src/resources/webgpuFlags.ts +43 -0
  38. package/src/runtime/copy.ts +13 -0
  39. package/src/runtime/index.ts +24 -0
  40. package/src/runtime/renderTask.ts +137 -0
  41. package/src/runtime/renderTo.ts +154 -0
  42. package/src/runtime/runtime.ts +113 -0
  43. package/src/runtime/scenePass.ts +273 -0
  44. package/src/window/canvas.ts +252 -0
  45. package/src/window/index.ts +14 -0
  46. 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
+ }