@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
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
// Public API of @aardworx/wombat.rendering-core.
|
|
2
|
+
//
|
|
3
|
+
// Phase 0: types only. Implementations of `compileEffect`,
|
|
4
|
+
// `prepareRenderObject`, `runRenderTask`, etc. live in higher
|
|
5
|
+
// packages (`resources`, `commands`, `runtime`).
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
IBuffer,
|
|
9
|
+
} from "./buffer.js";
|
|
10
|
+
export type {
|
|
11
|
+
HostBufferSource,
|
|
12
|
+
} from "./buffer.js";
|
|
13
|
+
|
|
14
|
+
export type {
|
|
15
|
+
BufferView,
|
|
16
|
+
} from "./bufferView.js";
|
|
17
|
+
|
|
18
|
+
export type {
|
|
19
|
+
ClearColor,
|
|
20
|
+
ClearValues,
|
|
21
|
+
} from "./clear.js";
|
|
22
|
+
|
|
23
|
+
export type {
|
|
24
|
+
Command,
|
|
25
|
+
CopySpec,
|
|
26
|
+
BufferCopy,
|
|
27
|
+
TextureCopy,
|
|
28
|
+
BufferCopyRange,
|
|
29
|
+
} from "./command.js";
|
|
30
|
+
|
|
31
|
+
export type {
|
|
32
|
+
DrawCall,
|
|
33
|
+
IndexedDrawCall,
|
|
34
|
+
NonIndexedDrawCall,
|
|
35
|
+
} from "./drawCall.js";
|
|
36
|
+
|
|
37
|
+
export type {
|
|
38
|
+
IFramebuffer,
|
|
39
|
+
} from "./framebuffer.js";
|
|
40
|
+
|
|
41
|
+
export type {
|
|
42
|
+
DepthStencilAttachmentSignature,
|
|
43
|
+
FramebufferSignature,
|
|
44
|
+
} from "./framebufferSignature.js";
|
|
45
|
+
|
|
46
|
+
export type {
|
|
47
|
+
BlendComponentState,
|
|
48
|
+
BlendState,
|
|
49
|
+
CullMode,
|
|
50
|
+
DepthState,
|
|
51
|
+
FrontFace,
|
|
52
|
+
PipelineState,
|
|
53
|
+
RasterizerState,
|
|
54
|
+
StencilFaceState,
|
|
55
|
+
StencilState,
|
|
56
|
+
Topology,
|
|
57
|
+
} from "./pipelineState.js";
|
|
58
|
+
|
|
59
|
+
export type {
|
|
60
|
+
RenderObject,
|
|
61
|
+
} from "./renderObject.js";
|
|
62
|
+
|
|
63
|
+
export {
|
|
64
|
+
RenderTree,
|
|
65
|
+
} from "./renderTree.js";
|
|
66
|
+
|
|
67
|
+
export type {
|
|
68
|
+
IRenderTask,
|
|
69
|
+
} from "./renderTask.js";
|
|
70
|
+
|
|
71
|
+
export {
|
|
72
|
+
ISampler,
|
|
73
|
+
} from "./sampler.js";
|
|
74
|
+
|
|
75
|
+
export {
|
|
76
|
+
ITexture,
|
|
77
|
+
} from "./texture.js";
|
|
78
|
+
export type {
|
|
79
|
+
ExternalTextureSource,
|
|
80
|
+
HostTextureSource,
|
|
81
|
+
RawTextureSource,
|
|
82
|
+
} from "./texture.js";
|
|
83
|
+
|
|
84
|
+
export type {
|
|
85
|
+
AttributeInfo,
|
|
86
|
+
CompiledEffect,
|
|
87
|
+
CompiledStage,
|
|
88
|
+
ComputeShader,
|
|
89
|
+
Effect,
|
|
90
|
+
HoleGetter,
|
|
91
|
+
HoleGetters,
|
|
92
|
+
LooseUniformInfo,
|
|
93
|
+
OutputInfo,
|
|
94
|
+
ProgramInterface,
|
|
95
|
+
SamplerInfo,
|
|
96
|
+
Stage,
|
|
97
|
+
StageInfo,
|
|
98
|
+
StorageBufferInfo,
|
|
99
|
+
Target,
|
|
100
|
+
TextureInfo,
|
|
101
|
+
UniformBlockInfo,
|
|
102
|
+
UniformFieldInfo,
|
|
103
|
+
} from "./shader.js";
|
|
104
|
+
|
|
105
|
+
export {
|
|
106
|
+
AdaptiveResource,
|
|
107
|
+
} from "./adaptiveResource.js";
|
|
108
|
+
|
|
109
|
+
export {
|
|
110
|
+
tryAcquire,
|
|
111
|
+
tryRelease,
|
|
112
|
+
} from "./acquire.js";
|
|
113
|
+
|
|
114
|
+
export {
|
|
115
|
+
RenderContext,
|
|
116
|
+
} from "./renderContext.js";
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
// PipelineState — the fixed-function bits the user picks per
|
|
2
|
+
// RenderObject. Vertex layout, blending, depth/stencil, raster.
|
|
3
|
+
// Combined with a CompiledEffect + FramebufferSignature this is
|
|
4
|
+
// enough to bake a GPURenderPipeline.
|
|
5
|
+
|
|
6
|
+
import type { HashMap } from "@aardworx/wombat.adaptive";
|
|
7
|
+
|
|
8
|
+
export type CullMode = "none" | "front" | "back";
|
|
9
|
+
export type FrontFace = "ccw" | "cw";
|
|
10
|
+
export type Topology = GPUPrimitiveTopology;
|
|
11
|
+
|
|
12
|
+
export interface RasterizerState {
|
|
13
|
+
readonly topology: Topology;
|
|
14
|
+
readonly cullMode: CullMode;
|
|
15
|
+
readonly frontFace: FrontFace;
|
|
16
|
+
/** "none" disables polygon offset. */
|
|
17
|
+
readonly depthBias?: { readonly constant: number; readonly slopeScale: number; readonly clamp: number };
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DepthState {
|
|
21
|
+
readonly write: boolean;
|
|
22
|
+
readonly compare: GPUCompareFunction;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface StencilFaceState {
|
|
26
|
+
readonly compare: GPUCompareFunction;
|
|
27
|
+
readonly failOp: GPUStencilOperation;
|
|
28
|
+
readonly depthFailOp: GPUStencilOperation;
|
|
29
|
+
readonly passOp: GPUStencilOperation;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export interface StencilState {
|
|
33
|
+
readonly readMask: number;
|
|
34
|
+
readonly writeMask: number;
|
|
35
|
+
readonly front: StencilFaceState;
|
|
36
|
+
readonly back: StencilFaceState;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export interface BlendComponentState {
|
|
40
|
+
readonly operation: GPUBlendOperation;
|
|
41
|
+
readonly srcFactor: GPUBlendFactor;
|
|
42
|
+
readonly dstFactor: GPUBlendFactor;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface BlendState {
|
|
46
|
+
readonly color: BlendComponentState;
|
|
47
|
+
readonly alpha: BlendComponentState;
|
|
48
|
+
/** RGBA channel write mask (bitmask: R=1, G=2, B=4, A=8). */
|
|
49
|
+
readonly writeMask: number;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface PipelineState {
|
|
53
|
+
readonly rasterizer: RasterizerState;
|
|
54
|
+
readonly depth?: DepthState;
|
|
55
|
+
readonly stencil?: StencilState;
|
|
56
|
+
/**
|
|
57
|
+
* Per-color-attachment blend state, keyed by the attachment name
|
|
58
|
+
* from the `FramebufferSignature`. Missing entries default to
|
|
59
|
+
* "no blending, write all channels".
|
|
60
|
+
*/
|
|
61
|
+
readonly blends?: HashMap<string, BlendState>;
|
|
62
|
+
/** Multi-sample alpha-to-coverage. Default `false`. */
|
|
63
|
+
readonly alphaToCoverage?: boolean;
|
|
64
|
+
}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
// RenderContext — ambient state available to AdaptiveResources
|
|
2
|
+
// during evaluation. The runtime sets `encoder` before walking a
|
|
3
|
+
// task's resources so that `compute(token)` calls inside an
|
|
4
|
+
// AdaptiveResource can encode commands (uploads, compute passes,
|
|
5
|
+
// render-to-texture) onto the current frame's encoder.
|
|
6
|
+
//
|
|
7
|
+
// Outside a frame (`encoder === null`) AdaptiveResources should
|
|
8
|
+
// either return their cached handle or throw — they cannot do
|
|
9
|
+
// GPU work without an encoder.
|
|
10
|
+
|
|
11
|
+
interface RenderContextSlot {
|
|
12
|
+
encoder: GPUCommandEncoder | null;
|
|
13
|
+
withEncoder<R>(encoder: GPUCommandEncoder, fn: () => R): R;
|
|
14
|
+
requireEncoder(): GPUCommandEncoder;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export const RenderContext: RenderContextSlot = {
|
|
18
|
+
encoder: null,
|
|
19
|
+
|
|
20
|
+
/**
|
|
21
|
+
* Run `fn` with `encoder` exposed as the current frame encoder.
|
|
22
|
+
* Restores the previous value on exit. Nestable.
|
|
23
|
+
*/
|
|
24
|
+
withEncoder<R>(encoder: GPUCommandEncoder, fn: () => R): R {
|
|
25
|
+
const prev = RenderContext.encoder;
|
|
26
|
+
RenderContext.encoder = encoder;
|
|
27
|
+
try {
|
|
28
|
+
return fn();
|
|
29
|
+
} finally {
|
|
30
|
+
RenderContext.encoder = prev;
|
|
31
|
+
}
|
|
32
|
+
},
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Convenience: throws if no encoder is active. Use inside
|
|
36
|
+
* `compute(token)` of an AdaptiveResource that genuinely needs
|
|
37
|
+
* to encode work.
|
|
38
|
+
*/
|
|
39
|
+
requireEncoder(): GPUCommandEncoder {
|
|
40
|
+
if (RenderContext.encoder === null) {
|
|
41
|
+
throw new Error(
|
|
42
|
+
"RenderContext: no active encoder. AdaptiveResource.compute() " +
|
|
43
|
+
"was called outside RenderContext.withEncoder(...).",
|
|
44
|
+
);
|
|
45
|
+
}
|
|
46
|
+
return RenderContext.encoder;
|
|
47
|
+
},
|
|
48
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
// RenderObject — the user-facing primitive: an effect + a bag of
|
|
2
|
+
// named inputs + a draw call. Names are matched against the
|
|
3
|
+
// shader's ProgramInterface during preparation; extras are
|
|
4
|
+
// silently ignored, missing required bindings are an error.
|
|
5
|
+
//
|
|
6
|
+
// Every input is `aval<T>` over a *source* type (IBuffer, ITexture,
|
|
7
|
+
// ISampler, …). The runtime (resources package) wraps these into
|
|
8
|
+
// internal AdaptiveResources for upload + ref-counting; the user
|
|
9
|
+
// never instantiates an AdaptiveResource directly.
|
|
10
|
+
|
|
11
|
+
import type { HashMap, aval } from "@aardworx/wombat.adaptive";
|
|
12
|
+
import type { IBuffer } from "./buffer.js";
|
|
13
|
+
import type { BufferView } from "./bufferView.js";
|
|
14
|
+
import type { DrawCall } from "./drawCall.js";
|
|
15
|
+
import type { ISampler } from "./sampler.js";
|
|
16
|
+
import type { ITexture } from "./texture.js";
|
|
17
|
+
import type { PipelineState } from "./pipelineState.js";
|
|
18
|
+
import type { Effect } from "./shader.js";
|
|
19
|
+
|
|
20
|
+
export interface RenderObject {
|
|
21
|
+
/**
|
|
22
|
+
* A wombat.shader `Effect` — produced by the `vertex(...) /
|
|
23
|
+
* fragment(...) / effect(...)` markers (transformed at build
|
|
24
|
+
* time by `@aardworx/wombat.shader-vite`) or hand-built via
|
|
25
|
+
* `stage(module, ...)`. The runtime calls
|
|
26
|
+
* `effect.compile({ target: "wgsl" })` (Effect's own
|
|
27
|
+
* hole-value-keyed cache makes this free per frame).
|
|
28
|
+
*
|
|
29
|
+
* `Effect.id` (stable build-time hash) feeds the pipeline cache
|
|
30
|
+
* key, which means renaming a captured value invalidates only
|
|
31
|
+
* its specialization; everything else stays cached.
|
|
32
|
+
*
|
|
33
|
+
* For users with a precompiled `CompiledEffect` (e.g. from
|
|
34
|
+
* `compileShaderSource(...)`), wrap it with the test helper
|
|
35
|
+
* `fakeEffectFromCompiled` or call `stage(module)` to produce
|
|
36
|
+
* a real Effect.
|
|
37
|
+
*/
|
|
38
|
+
readonly effect: Effect;
|
|
39
|
+
readonly pipelineState: PipelineState;
|
|
40
|
+
|
|
41
|
+
/** name → vertex buffer view; e.g. "position", "normal", "uv". */
|
|
42
|
+
readonly vertexAttributes: HashMap<string, aval<BufferView>>;
|
|
43
|
+
/** name → instance buffer view; e.g. "modelMatrix", "instanceColor". */
|
|
44
|
+
readonly instanceAttributes?: HashMap<string, aval<BufferView>>;
|
|
45
|
+
/** name → uniform value; runtime packs into UBO based on shader layout. */
|
|
46
|
+
readonly uniforms: HashMap<string, aval<unknown>>;
|
|
47
|
+
/** name → texture source (CPU image or pre-built GPUTexture). */
|
|
48
|
+
readonly textures: HashMap<string, aval<ITexture>>;
|
|
49
|
+
/** name → sampler source (descriptor or pre-built GPUSampler). */
|
|
50
|
+
readonly samplers: HashMap<string, aval<ISampler>>;
|
|
51
|
+
/** name → storage buffer source. */
|
|
52
|
+
readonly storageBuffers?: HashMap<string, aval<IBuffer>>;
|
|
53
|
+
|
|
54
|
+
/** Index buffer for indexed draws. */
|
|
55
|
+
readonly indices?: aval<BufferView>;
|
|
56
|
+
readonly drawCall: aval<DrawCall>;
|
|
57
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
// RenderTask — the compiled, runnable form of an alist<Command>.
|
|
2
|
+
// The runtime returns one of these from `compile`; the user calls
|
|
3
|
+
// `run(token)` to encode + submit a frame.
|
|
4
|
+
|
|
5
|
+
import type { AdaptiveToken } from "@aardworx/wombat.adaptive";
|
|
6
|
+
|
|
7
|
+
export interface IRenderTask {
|
|
8
|
+
/**
|
|
9
|
+
* Encode all commands into the GPU queue using the current
|
|
10
|
+
* adaptive state read via `token`. Returns once submission has
|
|
11
|
+
* been queued (does not wait for GPU completion).
|
|
12
|
+
*/
|
|
13
|
+
run(token: AdaptiveToken): void;
|
|
14
|
+
|
|
15
|
+
/** Tear down resources owned by the task. Idempotent. */
|
|
16
|
+
dispose(): void;
|
|
17
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
// RenderTree — intra-pass ordering structure for RenderObjects.
|
|
2
|
+
//
|
|
3
|
+
// `Ordered` children execute left-to-right; `Unordered` children
|
|
4
|
+
// can be reordered by the backend to minimise pipeline / bind-
|
|
5
|
+
// group / vertex-buffer state changes. The Adaptive variants
|
|
6
|
+
// fold reactive containers (alist, aset, aval) directly into the
|
|
7
|
+
// tree.
|
|
8
|
+
|
|
9
|
+
import type { aval, alist, aset } from "@aardworx/wombat.adaptive";
|
|
10
|
+
import type { RenderObject } from "./renderObject.js";
|
|
11
|
+
|
|
12
|
+
export type RenderTree =
|
|
13
|
+
| { readonly kind: "Empty" }
|
|
14
|
+
| { readonly kind: "Leaf"; readonly object: RenderObject }
|
|
15
|
+
| { readonly kind: "Ordered"; readonly children: readonly RenderTree[] }
|
|
16
|
+
| { readonly kind: "Unordered"; readonly children: readonly RenderTree[] }
|
|
17
|
+
| { readonly kind: "Adaptive"; readonly tree: aval<RenderTree> }
|
|
18
|
+
| { readonly kind: "OrderedFromList"; readonly children: alist<RenderTree> }
|
|
19
|
+
| { readonly kind: "UnorderedFromSet"; readonly children: aset<RenderTree> };
|
|
20
|
+
|
|
21
|
+
export const RenderTree = {
|
|
22
|
+
empty: { kind: "Empty" } as const satisfies RenderTree,
|
|
23
|
+
leaf: (object: RenderObject): RenderTree => ({ kind: "Leaf", object }),
|
|
24
|
+
ordered: (...children: RenderTree[]): RenderTree => ({ kind: "Ordered", children }),
|
|
25
|
+
unordered: (...children: RenderTree[]): RenderTree => ({ kind: "Unordered", children }),
|
|
26
|
+
adaptive: (tree: aval<RenderTree>): RenderTree => ({ kind: "Adaptive", tree }),
|
|
27
|
+
orderedFromList: (children: alist<RenderTree>): RenderTree => ({ kind: "OrderedFromList", children }),
|
|
28
|
+
unorderedFromSet: (children: aset<RenderTree>): RenderTree => ({ kind: "UnorderedFromSet", children }),
|
|
29
|
+
} as const;
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
// ISampler — sampler source. Either a pre-built `GPUSampler` or a
|
|
2
|
+
// descriptor for the runtime to materialise (samplers are cheap
|
|
3
|
+
// and dedupable, so descriptors are the common path).
|
|
4
|
+
|
|
5
|
+
export type ISampler =
|
|
6
|
+
| { readonly kind: "gpu"; readonly sampler: GPUSampler }
|
|
7
|
+
| { readonly kind: "desc"; readonly descriptor: GPUSamplerDescriptor };
|
|
8
|
+
|
|
9
|
+
export const ISampler = {
|
|
10
|
+
fromGPU(sampler: GPUSampler): ISampler { return { kind: "gpu", sampler }; },
|
|
11
|
+
fromDescriptor(descriptor: GPUSamplerDescriptor): ISampler { return { kind: "desc", descriptor }; },
|
|
12
|
+
} as const;
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// Re-exports of the wombat.shader runtime types consumed by the
|
|
2
|
+
// rendering layer. We do NOT duplicate or re-shape these — the
|
|
3
|
+
// rendering layer is just a consumer of wombat.shader's
|
|
4
|
+
// `Effect` / `CompiledEffect` / `ProgramInterface`.
|
|
5
|
+
|
|
6
|
+
export type {
|
|
7
|
+
Effect,
|
|
8
|
+
ComputeShader,
|
|
9
|
+
Stage,
|
|
10
|
+
HoleGetter,
|
|
11
|
+
HoleGetters,
|
|
12
|
+
CompiledEffect,
|
|
13
|
+
CompiledStage,
|
|
14
|
+
Target,
|
|
15
|
+
} from "@aardworx/wombat.shader";
|
|
16
|
+
|
|
17
|
+
export type {
|
|
18
|
+
ProgramInterface,
|
|
19
|
+
StageInfo,
|
|
20
|
+
AttributeInfo,
|
|
21
|
+
OutputInfo,
|
|
22
|
+
LooseUniformInfo,
|
|
23
|
+
UniformBlockInfo,
|
|
24
|
+
UniformFieldInfo,
|
|
25
|
+
SamplerInfo,
|
|
26
|
+
TextureInfo,
|
|
27
|
+
StorageBufferInfo,
|
|
28
|
+
} from "@aardworx/wombat.shader";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// ITexture — texture source that can be either a real GPU texture
|
|
2
|
+
// or a host-side image. Mirrors the IBuffer pattern.
|
|
3
|
+
//
|
|
4
|
+
// Host sources cover everything WebGPU's `copyExternalImageToTexture`
|
|
5
|
+
// or `writeTexture` can accept:
|
|
6
|
+
// - `ImageBitmap`, `HTMLImageElement`, `HTMLVideoElement`,
|
|
7
|
+
// `HTMLCanvasElement`, `OffscreenCanvas`, `ImageData` —
|
|
8
|
+
// uploaded via `copyExternalImageToTexture`.
|
|
9
|
+
// - Raw `{ data, width, height, format }` — uploaded via
|
|
10
|
+
// `queue.writeTexture` (one mip; runtime can synthesise the
|
|
11
|
+
// rest if requested).
|
|
12
|
+
|
|
13
|
+
export interface RawTextureSource {
|
|
14
|
+
readonly kind: "raw";
|
|
15
|
+
readonly data: ArrayBuffer | ArrayBufferView;
|
|
16
|
+
readonly width: number;
|
|
17
|
+
readonly height: number;
|
|
18
|
+
readonly depthOrArrayLayers?: number;
|
|
19
|
+
readonly format: GPUTextureFormat;
|
|
20
|
+
readonly mipLevelCount?: number;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface ExternalTextureSource {
|
|
24
|
+
readonly kind: "external";
|
|
25
|
+
readonly source:
|
|
26
|
+
| ImageBitmap
|
|
27
|
+
| HTMLImageElement
|
|
28
|
+
| HTMLVideoElement
|
|
29
|
+
| HTMLCanvasElement
|
|
30
|
+
| OffscreenCanvas
|
|
31
|
+
| ImageData;
|
|
32
|
+
/** Override the format the runtime should allocate (default: "rgba8unorm"). */
|
|
33
|
+
readonly format?: GPUTextureFormat;
|
|
34
|
+
/** Generate a mip chain on upload. Default `false`. */
|
|
35
|
+
readonly generateMips?: boolean;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export type HostTextureSource = RawTextureSource | ExternalTextureSource;
|
|
39
|
+
|
|
40
|
+
export type ITexture =
|
|
41
|
+
| { readonly kind: "gpu"; readonly texture: GPUTexture }
|
|
42
|
+
| { readonly kind: "host"; readonly source: HostTextureSource };
|
|
43
|
+
|
|
44
|
+
export const ITexture = {
|
|
45
|
+
fromGPU(texture: GPUTexture): ITexture {
|
|
46
|
+
return { kind: "gpu", texture };
|
|
47
|
+
},
|
|
48
|
+
fromExternal(
|
|
49
|
+
source: ExternalTextureSource["source"],
|
|
50
|
+
opts: { format?: GPUTextureFormat; generateMips?: boolean } = {},
|
|
51
|
+
): ITexture {
|
|
52
|
+
const ext: ExternalTextureSource = {
|
|
53
|
+
kind: "external",
|
|
54
|
+
source,
|
|
55
|
+
...(opts.format !== undefined ? { format: opts.format } : {}),
|
|
56
|
+
...(opts.generateMips !== undefined ? { generateMips: opts.generateMips } : {}),
|
|
57
|
+
};
|
|
58
|
+
return { kind: "host", source: ext };
|
|
59
|
+
},
|
|
60
|
+
fromRaw(raw: Omit<RawTextureSource, "kind">): ITexture {
|
|
61
|
+
return { kind: "host", source: { kind: "raw", ...raw } };
|
|
62
|
+
},
|
|
63
|
+
} as const;
|
package/src/index.ts
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// Public API of `@aardworx/wombat.rendering`.
|
|
2
|
+
//
|
|
3
|
+
// Default entry re-exports everything: types, resource preparers,
|
|
4
|
+
// command-stream functions, runtime, and the window/canvas glue.
|
|
5
|
+
// Subpath exports give granular access for advanced consumers.
|
|
6
|
+
//
|
|
7
|
+
// import { ... } from "@aardworx/wombat.rendering"; // everything
|
|
8
|
+
// import { ... } from "@aardworx/wombat.rendering/core"; // types only
|
|
9
|
+
// import { ... } from "@aardworx/wombat.rendering/resources";
|
|
10
|
+
// import { ... } from "@aardworx/wombat.rendering/commands";
|
|
11
|
+
// import { ... } from "@aardworx/wombat.rendering/runtime";
|
|
12
|
+
// import { ... } from "@aardworx/wombat.rendering/window";
|
|
13
|
+
|
|
14
|
+
export * from "./core/index.js";
|
|
15
|
+
export * from "./resources/index.js";
|
|
16
|
+
export * from "./commands/index.js";
|
|
17
|
+
export * from "./runtime/index.js";
|
|
18
|
+
export * from "./window/index.js";
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
// prepareAdaptiveBuffer — lift `aval<IBuffer>` to
|
|
2
|
+
// `AdaptiveResource<GPUBuffer>`.
|
|
3
|
+
//
|
|
4
|
+
// Behaviour:
|
|
5
|
+
// - On `acquire` (refCount 0→1): subscribes, allocates nothing yet.
|
|
6
|
+
// - On `compute(token)`:
|
|
7
|
+
// - reads the source `aval<IBuffer>`;
|
|
8
|
+
// - if it's `gpu`: returns the user's handle directly. We
|
|
9
|
+
// don't own it; `destroy()` will not free it.
|
|
10
|
+
// - if it's `host`: ensures we own a `GPUBuffer` of sufficient
|
|
11
|
+
// size + correct usage flags, encodes a `writeBuffer` (via
|
|
12
|
+
// device.queue) to upload the bytes, returns the owned
|
|
13
|
+
// buffer. Reuses the existing buffer when capacity allows;
|
|
14
|
+
// reallocates on growth.
|
|
15
|
+
// - On `destroy`: frees the owned buffer (if any).
|
|
16
|
+
//
|
|
17
|
+
// `writeBuffer` does not require a GPUCommandEncoder — it goes via
|
|
18
|
+
// `GPUQueue.writeBuffer`, scheduled at the next submit. That keeps
|
|
19
|
+
// the upload path simple and avoids needing `RenderContext.encoder`
|
|
20
|
+
// for plain CPU→GPU buffer flow. (Compute-produced buffers will
|
|
21
|
+
// need the encoder; that's a different code path.)
|
|
22
|
+
|
|
23
|
+
import {
|
|
24
|
+
AdaptiveResource,
|
|
25
|
+
tryAcquire,
|
|
26
|
+
tryRelease,
|
|
27
|
+
type IBuffer,
|
|
28
|
+
} from "../core/index.js";
|
|
29
|
+
import {
|
|
30
|
+
type AdaptiveToken,
|
|
31
|
+
type aval,
|
|
32
|
+
} from "@aardworx/wombat.adaptive";
|
|
33
|
+
import { BufferUsage } from "./webgpuFlags.js";
|
|
34
|
+
|
|
35
|
+
export interface PrepareAdaptiveBufferOptions {
|
|
36
|
+
/** Bitwise OR of GPUBufferUsage flags. `COPY_DST` is added automatically when needed. */
|
|
37
|
+
readonly usage: GPUBufferUsageFlags;
|
|
38
|
+
/** Optional debug label forwarded to created GPUBuffers. */
|
|
39
|
+
readonly label?: string;
|
|
40
|
+
/**
|
|
41
|
+
* Reuse the existing GPUBuffer when the new host data fits in
|
|
42
|
+
* the current allocation. Default `true`. Set `false` to force
|
|
43
|
+
* a fresh buffer on every host-side change (rarely useful).
|
|
44
|
+
*/
|
|
45
|
+
readonly reuseOnFit?: boolean;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
class AdaptiveBuffer extends AdaptiveResource<GPUBuffer> {
|
|
49
|
+
private _owned: GPUBuffer | undefined = undefined;
|
|
50
|
+
private _ownedCapacity = 0;
|
|
51
|
+
|
|
52
|
+
constructor(
|
|
53
|
+
private readonly device: GPUDevice,
|
|
54
|
+
private readonly source: aval<IBuffer>,
|
|
55
|
+
private readonly opts: PrepareAdaptiveBufferOptions,
|
|
56
|
+
) {
|
|
57
|
+
super();
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
protected create(): void {
|
|
61
|
+
tryAcquire(this.source);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
protected destroy(): void {
|
|
65
|
+
if (this._owned !== undefined) {
|
|
66
|
+
this._owned.destroy();
|
|
67
|
+
this._owned = undefined;
|
|
68
|
+
this._ownedCapacity = 0;
|
|
69
|
+
}
|
|
70
|
+
tryRelease(this.source);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
override compute(token: AdaptiveToken): GPUBuffer {
|
|
74
|
+
const src = this.source.getValue(token);
|
|
75
|
+
if (src.kind === "gpu") {
|
|
76
|
+
// Caller-owned buffer; drop any owned allocation we still hold.
|
|
77
|
+
if (this._owned !== undefined) {
|
|
78
|
+
this._owned.destroy();
|
|
79
|
+
this._owned = undefined;
|
|
80
|
+
this._ownedCapacity = 0;
|
|
81
|
+
}
|
|
82
|
+
return src.buffer;
|
|
83
|
+
}
|
|
84
|
+
// host — upload via queue.writeBuffer.
|
|
85
|
+
const usage = this.opts.usage | BufferUsage.COPY_DST;
|
|
86
|
+
const reuseOnFit = this.opts.reuseOnFit ?? true;
|
|
87
|
+
const requiredSize = roundUp4(src.sizeBytes);
|
|
88
|
+
if (
|
|
89
|
+
this._owned === undefined
|
|
90
|
+
|| !reuseOnFit
|
|
91
|
+
|| requiredSize > this._ownedCapacity
|
|
92
|
+
) {
|
|
93
|
+
if (this._owned !== undefined) this._owned.destroy();
|
|
94
|
+
const desc: GPUBufferDescriptor = {
|
|
95
|
+
size: requiredSize,
|
|
96
|
+
usage,
|
|
97
|
+
...(this.opts.label !== undefined ? { label: this.opts.label } : {}),
|
|
98
|
+
};
|
|
99
|
+
this._owned = this.device.createBuffer(desc);
|
|
100
|
+
this._ownedCapacity = requiredSize;
|
|
101
|
+
}
|
|
102
|
+
// writeBuffer accepts ArrayBuffer or any ArrayBufferView.
|
|
103
|
+
const data = src.data;
|
|
104
|
+
if (data instanceof ArrayBuffer) {
|
|
105
|
+
this.device.queue.writeBuffer(this._owned, 0, data, 0, src.sizeBytes);
|
|
106
|
+
} else {
|
|
107
|
+
this.device.queue.writeBuffer(
|
|
108
|
+
this._owned,
|
|
109
|
+
0,
|
|
110
|
+
data.buffer,
|
|
111
|
+
data.byteOffset,
|
|
112
|
+
src.sizeBytes,
|
|
113
|
+
);
|
|
114
|
+
}
|
|
115
|
+
return this._owned;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
function roundUp4(n: number): number {
|
|
120
|
+
return (n + 3) & ~3;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/**
|
|
124
|
+
* Wrap an `aval<IBuffer>` as a ref-counted, adaptively-uploaded
|
|
125
|
+
* `AdaptiveResource<GPUBuffer>`. The resource is itself an
|
|
126
|
+
* `aval<GPUBuffer>` — downstream consumers can `getValue(token)`
|
|
127
|
+
* inside their own adaptive evaluation.
|
|
128
|
+
*/
|
|
129
|
+
export function prepareAdaptiveBuffer(
|
|
130
|
+
device: GPUDevice,
|
|
131
|
+
source: aval<IBuffer>,
|
|
132
|
+
opts: PrepareAdaptiveBufferOptions,
|
|
133
|
+
): AdaptiveResource<GPUBuffer> {
|
|
134
|
+
return new AdaptiveBuffer(device, source, opts);
|
|
135
|
+
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// prepareAdaptiveSampler — lift `aval<ISampler>` to
|
|
2
|
+
// `AdaptiveResource<GPUSampler>`.
|
|
3
|
+
//
|
|
4
|
+
// Samplers are cheap and structurally-identifiable — the device
|
|
5
|
+
// dedupes them via a descriptor cache. Per AdaptiveResource we
|
|
6
|
+
// just track the most recent handle; on descriptor change we
|
|
7
|
+
// fetch a fresh one from the cache.
|
|
8
|
+
|
|
9
|
+
import {
|
|
10
|
+
AdaptiveResource,
|
|
11
|
+
tryAcquire,
|
|
12
|
+
tryRelease,
|
|
13
|
+
type ISampler,
|
|
14
|
+
} from "../core/index.js";
|
|
15
|
+
import {
|
|
16
|
+
type AdaptiveToken,
|
|
17
|
+
type aval,
|
|
18
|
+
} from "@aardworx/wombat.adaptive";
|
|
19
|
+
|
|
20
|
+
class SamplerCache {
|
|
21
|
+
private readonly map = new Map<string, GPUSampler>();
|
|
22
|
+
constructor(private readonly device: GPUDevice) {}
|
|
23
|
+
get(desc: GPUSamplerDescriptor): GPUSampler {
|
|
24
|
+
const k = key(desc);
|
|
25
|
+
let s = this.map.get(k);
|
|
26
|
+
if (s === undefined) {
|
|
27
|
+
s = this.device.createSampler(desc);
|
|
28
|
+
this.map.set(k, s);
|
|
29
|
+
}
|
|
30
|
+
return s;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
function key(d: GPUSamplerDescriptor): string {
|
|
35
|
+
return [
|
|
36
|
+
d.addressModeU ?? "clamp-to-edge",
|
|
37
|
+
d.addressModeV ?? "clamp-to-edge",
|
|
38
|
+
d.addressModeW ?? "clamp-to-edge",
|
|
39
|
+
d.magFilter ?? "nearest",
|
|
40
|
+
d.minFilter ?? "nearest",
|
|
41
|
+
d.mipmapFilter ?? "nearest",
|
|
42
|
+
d.lodMinClamp ?? 0,
|
|
43
|
+
d.lodMaxClamp ?? 32,
|
|
44
|
+
d.compare ?? "",
|
|
45
|
+
d.maxAnisotropy ?? 1,
|
|
46
|
+
].join("|");
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
const caches = new WeakMap<GPUDevice, SamplerCache>();
|
|
50
|
+
function cacheFor(device: GPUDevice): SamplerCache {
|
|
51
|
+
let c = caches.get(device);
|
|
52
|
+
if (c === undefined) {
|
|
53
|
+
c = new SamplerCache(device);
|
|
54
|
+
caches.set(device, c);
|
|
55
|
+
}
|
|
56
|
+
return c;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
class AdaptiveSampler extends AdaptiveResource<GPUSampler> {
|
|
60
|
+
constructor(
|
|
61
|
+
private readonly device: GPUDevice,
|
|
62
|
+
private readonly source: aval<ISampler>,
|
|
63
|
+
) { super(); }
|
|
64
|
+
protected create(): void { tryAcquire(this.source); }
|
|
65
|
+
protected destroy(): void {
|
|
66
|
+
// We never own the GPUSampler — the cache does. Just propagate
|
|
67
|
+
// release to the source aval (in case it's an AdaptiveResource).
|
|
68
|
+
tryRelease(this.source);
|
|
69
|
+
}
|
|
70
|
+
override compute(token: AdaptiveToken): GPUSampler {
|
|
71
|
+
const s = this.source.getValue(token);
|
|
72
|
+
if (s.kind === "gpu") return s.sampler;
|
|
73
|
+
return cacheFor(this.device).get(s.descriptor);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function prepareAdaptiveSampler(
|
|
78
|
+
device: GPUDevice,
|
|
79
|
+
source: aval<ISampler>,
|
|
80
|
+
): AdaptiveResource<GPUSampler> {
|
|
81
|
+
return new AdaptiveSampler(device, source);
|
|
82
|
+
}
|