@aardworx/wombat.rendering 0.7.0 → 0.7.2
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/dist/core/bufferView.d.ts +18 -17
- package/dist/core/bufferView.d.ts.map +1 -1
- package/dist/core/bufferView.js +56 -94
- package/dist/core/bufferView.js.map +1 -1
- package/dist/core/elementType.d.ts +78 -13
- package/dist/core/elementType.d.ts.map +1 -1
- package/dist/core/elementType.js +147 -76
- package/dist/core/elementType.js.map +1 -1
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.d.ts.map +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/index.js.map +1 -1
- package/dist/resources/preparedRenderObject.d.ts.map +1 -1
- package/dist/resources/preparedRenderObject.js +10 -2
- package/dist/resources/preparedRenderObject.js.map +1 -1
- package/package.json +2 -2
- package/src/core/bufferView.ts +64 -105
- package/src/core/elementType.ts +174 -80
- package/src/core/index.ts +1 -0
- package/src/resources/preparedRenderObject.ts +10 -2
package/src/core/bufferView.ts
CHANGED
|
@@ -4,33 +4,33 @@
|
|
|
4
4
|
//
|
|
5
5
|
// - `buffer` is `aval<IBuffer>` so per-frame content changes flow
|
|
6
6
|
// through normal aval marking without reshaping the view.
|
|
7
|
-
// - `elementType`
|
|
8
|
-
//
|
|
9
|
-
//
|
|
7
|
+
// - `elementType` is a singleton token from the `ElementType`
|
|
8
|
+
// namespace (e.g. `ElementType.V3f`, `ElementType.U16`); the
|
|
9
|
+
// backend reads `byteSize` / `vertexFormat` / `indexFormat`
|
|
10
|
+
// directly off it.
|
|
10
11
|
// - `singleValue` is set when the view was constructed from a
|
|
11
12
|
// single adaptive value broadcast to all instances/vertices —
|
|
12
13
|
// the runtime can lower it to a uniform binding instead of a
|
|
13
14
|
// per-vertex/per-instance attribute.
|
|
14
15
|
//
|
|
15
16
|
// The view itself is plain (not wrapped in `aval`). Structural
|
|
16
|
-
// fields
|
|
17
|
-
//
|
|
18
|
-
// without forcing.
|
|
17
|
+
// fields are eager so the binding plan / pipeline layout can be
|
|
18
|
+
// computed without forcing.
|
|
19
19
|
|
|
20
20
|
import type { aval } from "@aardworx/wombat.adaptive";
|
|
21
21
|
import { AVal } from "@aardworx/wombat.adaptive";
|
|
22
22
|
import { IBuffer } from "./buffer.js";
|
|
23
|
-
import
|
|
24
|
-
import {
|
|
23
|
+
import { ElementType } from "./elementType.js";
|
|
24
|
+
import type { ElementKind } from "./elementType.js";
|
|
25
25
|
|
|
26
26
|
export interface BufferView {
|
|
27
27
|
/** Adaptive buffer source — content changes flow through this aval. */
|
|
28
28
|
readonly buffer: aval<IBuffer>;
|
|
29
|
-
/** Element kind
|
|
29
|
+
/** Element kind (singleton from the `ElementType` namespace). */
|
|
30
30
|
readonly elementType: ElementType;
|
|
31
31
|
/** Byte offset into `buffer` where the view starts. Default 0. */
|
|
32
32
|
readonly offset?: number;
|
|
33
|
-
/** Stride between consecutive elements in bytes. 0 = tight (= `byteSize
|
|
33
|
+
/** Stride between consecutive elements in bytes. 0 = tight (= `elementType.byteSize`). */
|
|
34
34
|
readonly stride?: number;
|
|
35
35
|
/** Integer attributes treated as normalized fixed-point floats. Default false. */
|
|
36
36
|
readonly normalized?: boolean;
|
|
@@ -43,58 +43,6 @@ export interface BufferView {
|
|
|
43
43
|
readonly singleValue?: aval<unknown>;
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
// ---------------------------------------------------------------------------
|
|
47
|
-
// Inference helpers — discover ElementType from a wombat.base packed
|
|
48
|
-
// array or a TypedArray. Kept as a duck-type lookup so wombat.base
|
|
49
|
-
// doesn't need to expose explicit brands.
|
|
50
|
-
// ---------------------------------------------------------------------------
|
|
51
|
-
|
|
52
|
-
interface WombatArrayLike {
|
|
53
|
-
readonly buffer: ArrayBufferLike;
|
|
54
|
-
readonly length: number;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
function inferElementType(arr: unknown): ElementType {
|
|
58
|
-
// wombat.base packed types — discover via class name. These all
|
|
59
|
-
// have a distinct `constructor.name` and a `buffer: ArrayBuffer`
|
|
60
|
-
// backing field.
|
|
61
|
-
const ctor = (arr as { constructor?: { name?: string } }).constructor;
|
|
62
|
-
const name = ctor?.name;
|
|
63
|
-
switch (name) {
|
|
64
|
-
case "V2fArray": return "v2f";
|
|
65
|
-
case "V3fArray": return "v3f";
|
|
66
|
-
case "V4fArray": return "v4f";
|
|
67
|
-
case "V2iArray": return "v2i";
|
|
68
|
-
case "V3iArray": return "v3i";
|
|
69
|
-
case "V4iArray": return "v4i";
|
|
70
|
-
case "V2uiArray": return "v2u";
|
|
71
|
-
case "V3uiArray": return "v3u";
|
|
72
|
-
case "V4uiArray": return "v4u";
|
|
73
|
-
// Native typed arrays.
|
|
74
|
-
case "Float32Array": return "f32";
|
|
75
|
-
case "Uint32Array": return "u32";
|
|
76
|
-
case "Int32Array": return "i32";
|
|
77
|
-
case "Uint16Array": return "u16";
|
|
78
|
-
case "Int16Array": return "i16";
|
|
79
|
-
case "Uint8Array": return "u8";
|
|
80
|
-
case "Int8Array": return "i8";
|
|
81
|
-
}
|
|
82
|
-
throw new Error(
|
|
83
|
-
`BufferView.ofArray: cannot infer ElementType from ${name ?? "unknown type"}; ` +
|
|
84
|
-
`pass elementType explicitly`,
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
function arrayToHostBytes(arr: unknown): ArrayBuffer | ArrayBufferView {
|
|
89
|
-
// wombat.base packed types expose `.buffer: ArrayBuffer` covering
|
|
90
|
-
// exactly the packed bytes.
|
|
91
|
-
const a = arr as { buffer?: unknown };
|
|
92
|
-
if (a.buffer instanceof ArrayBuffer) return a.buffer;
|
|
93
|
-
// Native typed array — pass through.
|
|
94
|
-
if (ArrayBuffer.isView(arr as ArrayBufferView)) return arr as ArrayBufferView;
|
|
95
|
-
throw new Error("BufferView.ofArray: unsupported array source");
|
|
96
|
-
}
|
|
97
|
-
|
|
98
46
|
// ---------------------------------------------------------------------------
|
|
99
47
|
// Constructors
|
|
100
48
|
// ---------------------------------------------------------------------------
|
|
@@ -106,7 +54,7 @@ interface OfArrayOptions {
|
|
|
106
54
|
readonly normalized?: boolean;
|
|
107
55
|
}
|
|
108
56
|
|
|
109
|
-
interface
|
|
57
|
+
interface OfBufferOptions {
|
|
110
58
|
readonly offset?: number;
|
|
111
59
|
readonly stride?: number;
|
|
112
60
|
readonly normalized?: boolean;
|
|
@@ -117,23 +65,33 @@ function isAval<T>(v: unknown): v is aval<T> {
|
|
|
117
65
|
&& typeof (v as { getValue?: unknown }).getValue === "function";
|
|
118
66
|
}
|
|
119
67
|
|
|
68
|
+
function arrayToHostBytes(arr: unknown): ArrayBuffer | ArrayBufferView {
|
|
69
|
+
// wombat.base packed array types expose `.buffer: ArrayBuffer`.
|
|
70
|
+
const a = arr as { buffer?: unknown };
|
|
71
|
+
if (a.buffer instanceof ArrayBuffer) return a.buffer;
|
|
72
|
+
// Native TypedArray.
|
|
73
|
+
if (ArrayBuffer.isView(arr as ArrayBufferView)) return arr as ArrayBufferView;
|
|
74
|
+
throw new Error("BufferView.ofArray: unsupported array source");
|
|
75
|
+
}
|
|
76
|
+
|
|
120
77
|
export const BufferView = {
|
|
121
78
|
/**
|
|
122
79
|
* Build a view from a wombat.base packed array (`V3fArray`,
|
|
123
|
-
* `Uint16Array`, …) or an `aval<…>` of one. The element
|
|
124
|
-
* inferred from the array constructor; pass it
|
|
125
|
-
* `opts.elementType` for unusual cases.
|
|
80
|
+
* `Uint16Array`, …) or an `aval<…>` of one. The element kind is
|
|
81
|
+
* inferred from the array's constructor identity; pass it
|
|
82
|
+
* explicitly via `opts.elementType` for unusual cases (e.g. a
|
|
83
|
+
* `Float32Array` interpreted as packed `V4f` with `ElementType.V4f`).
|
|
126
84
|
*/
|
|
127
|
-
ofArray<T
|
|
85
|
+
ofArray<T>(
|
|
128
86
|
a: aval<T> | T,
|
|
129
87
|
opts: OfArrayOptions = {},
|
|
130
88
|
): BufferView {
|
|
131
89
|
const adaptive = isAval<T>(a) ? a : AVal.constant(a);
|
|
132
|
-
//
|
|
133
|
-
//
|
|
134
|
-
//
|
|
90
|
+
// One-time element-kind discovery from the source's constructor.
|
|
91
|
+
// Marked allow-force: structural-eager, runs at construction only;
|
|
92
|
+
// per-frame content changes flow through `buffer` without re-running.
|
|
135
93
|
const sample = adaptive.force(/* allow-force */);
|
|
136
|
-
const elementType = opts.elementType ??
|
|
94
|
+
const elementType = opts.elementType ?? ElementType.fromArray(sample);
|
|
137
95
|
const buffer = adaptive.map((arr): IBuffer =>
|
|
138
96
|
IBuffer.fromHost(arrayToHostBytes(arr)),
|
|
139
97
|
);
|
|
@@ -152,14 +110,17 @@ export const BufferView = {
|
|
|
152
110
|
* and lowers it to a uniform binding instead of a per-element
|
|
153
111
|
* attribute. The `buffer` aval still carries packed bytes for
|
|
154
112
|
* backends that don't recognize the fast path.
|
|
113
|
+
*
|
|
114
|
+
* The `T` parameter on `ElementKind<T>` flows through to the
|
|
115
|
+
* value type — passing `ElementType.V4f` requires `T = V4f` here.
|
|
155
116
|
*/
|
|
156
117
|
ofValue<T>(
|
|
157
118
|
v: aval<T> | T,
|
|
158
|
-
elementType:
|
|
119
|
+
elementType: ElementKind<T>,
|
|
159
120
|
): BufferView {
|
|
160
121
|
const adaptive = isAval<T>(v) ? v : AVal.constant(v);
|
|
161
122
|
const buffer = adaptive.map((value): IBuffer => {
|
|
162
|
-
const bytes =
|
|
123
|
+
const bytes = elementType.byteSize;
|
|
163
124
|
const ab = new ArrayBuffer(bytes);
|
|
164
125
|
packSingle(value, elementType, ab);
|
|
165
126
|
return IBuffer.fromHost(ab);
|
|
@@ -178,7 +139,7 @@ export const BufferView = {
|
|
|
178
139
|
ofGPU(
|
|
179
140
|
buffer: aval<GPUBuffer> | GPUBuffer,
|
|
180
141
|
elementType: ElementType,
|
|
181
|
-
opts:
|
|
142
|
+
opts: OfBufferOptions = {},
|
|
182
143
|
): BufferView {
|
|
183
144
|
const adaptive = isAval<GPUBuffer>(buffer) ? buffer : AVal.constant(buffer);
|
|
184
145
|
const ibuffer = adaptive.map((b): IBuffer => IBuffer.fromGPU(b));
|
|
@@ -193,13 +154,13 @@ export const BufferView = {
|
|
|
193
154
|
|
|
194
155
|
/**
|
|
195
156
|
* Wrap an already-built `aval<IBuffer>` with explicit element
|
|
196
|
-
*
|
|
157
|
+
* kind. Used by callers that compute their own `IBuffer` (e.g.
|
|
197
158
|
* compositing multiple attributes into one packed buffer).
|
|
198
159
|
*/
|
|
199
160
|
ofBuffer(
|
|
200
161
|
buffer: aval<IBuffer>,
|
|
201
162
|
elementType: ElementType,
|
|
202
|
-
opts:
|
|
163
|
+
opts: OfBufferOptions = {},
|
|
203
164
|
): BufferView {
|
|
204
165
|
return {
|
|
205
166
|
buffer,
|
|
@@ -216,12 +177,12 @@ export const BufferView = {
|
|
|
216
177
|
offsetOf(v: BufferView): number { return v.offset ?? 0; },
|
|
217
178
|
/**
|
|
218
179
|
* Stride in bytes — `v.stride` if set and non-zero, otherwise
|
|
219
|
-
* tight from `elementType`.
|
|
180
|
+
* tight from `elementType.byteSize`.
|
|
220
181
|
*/
|
|
221
182
|
strideOf(v: BufferView): number {
|
|
222
183
|
return v.stride !== undefined && v.stride > 0
|
|
223
184
|
? v.stride
|
|
224
|
-
:
|
|
185
|
+
: v.elementType.byteSize;
|
|
225
186
|
},
|
|
226
187
|
/** Whether this view is a single-value broadcast. */
|
|
227
188
|
isSingleValue(v: BufferView): boolean {
|
|
@@ -230,41 +191,39 @@ export const BufferView = {
|
|
|
230
191
|
} as const;
|
|
231
192
|
|
|
232
193
|
// ---------------------------------------------------------------------------
|
|
233
|
-
// Single-value packing — write one element of `
|
|
234
|
-
//
|
|
235
|
-
//
|
|
236
|
-
//
|
|
194
|
+
// Single-value packing — write one element of `t` into a fresh
|
|
195
|
+
// ArrayBuffer. The runtime usually shortcuts this via `singleValue`
|
|
196
|
+
// (lifted to a uniform); this path runs only for backends that
|
|
197
|
+
// don't recognise the broadcast.
|
|
237
198
|
// ---------------------------------------------------------------------------
|
|
238
199
|
|
|
239
200
|
function packSingle(value: unknown, t: ElementType, dst: ArrayBuffer): void {
|
|
240
|
-
// Lazy import to avoid a hard dep on wombat.base shapes here. We
|
|
241
|
-
// peek at `_data` (the typed-array view exposed by every wombat
|
|
242
|
-
// math type) and copy its bytes into the destination.
|
|
243
201
|
const v = value as { _data?: ArrayLike<number> };
|
|
244
202
|
if (v._data !== undefined) {
|
|
245
203
|
const src = v._data;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
204
|
+
// Match by name on the singleton — concise and minifier-safe
|
|
205
|
+
// since the names are baked at the singleton's construction.
|
|
206
|
+
const name = t.name;
|
|
207
|
+
if (name === "f32"
|
|
208
|
+
|| name.startsWith("v") && name.endsWith("f")
|
|
209
|
+
|| name.startsWith("m") && name.endsWith("f")) {
|
|
210
|
+
const out = new Float32Array(dst);
|
|
211
|
+
for (let i = 0; i < src.length; i++) out[i] = src[i]!;
|
|
212
|
+
return;
|
|
213
|
+
}
|
|
214
|
+
if (name === "i32" || (name.startsWith("v") && name.endsWith("i"))) {
|
|
215
|
+
const out = new Int32Array(dst);
|
|
216
|
+
for (let i = 0; i < src.length; i++) out[i] = src[i]!;
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
if (name === "u32" || (name.startsWith("v") && name.endsWith("u"))) {
|
|
220
|
+
const out = new Uint32Array(dst);
|
|
221
|
+
for (let i = 0; i < src.length; i++) out[i] = src[i]!;
|
|
222
|
+
return;
|
|
264
223
|
}
|
|
265
224
|
}
|
|
266
225
|
// Scalars and unbranded values.
|
|
267
|
-
switch (t) {
|
|
226
|
+
switch (t.name) {
|
|
268
227
|
case "f32": new Float32Array(dst)[0] = value as number; return;
|
|
269
228
|
case "u32": new Uint32Array(dst)[0] = value as number; return;
|
|
270
229
|
case "i32": new Int32Array(dst)[0] = value as number; return;
|
|
@@ -273,5 +232,5 @@ function packSingle(value: unknown, t: ElementType, dst: ArrayBuffer): void {
|
|
|
273
232
|
case "u8": new Uint8Array(dst)[0] = value as number; return;
|
|
274
233
|
case "i8": new Int8Array(dst)[0] = value as number; return;
|
|
275
234
|
}
|
|
276
|
-
throw new Error(`BufferView.ofValue: cannot pack ${typeof value} as ${t}`);
|
|
235
|
+
throw new Error(`BufferView.ofValue: cannot pack ${typeof value} as ${t.name}`);
|
|
277
236
|
}
|
package/src/core/elementType.ts
CHANGED
|
@@ -1,84 +1,178 @@
|
|
|
1
|
-
// ElementType —
|
|
1
|
+
// ElementType — runtime token + structural metadata for the data
|
|
2
|
+
// type of a single element in a `BufferView`. The TypeScript-side
|
|
3
|
+
// equivalent of F#'s `typeof<T>` / `System.Type`: a value (not a
|
|
4
|
+
// string tag) carrying byte size, vertex / index format, and a
|
|
5
|
+
// phantom type parameter that flows through to `BufferView<T>`-
|
|
6
|
+
// generic call sites.
|
|
2
7
|
//
|
|
3
|
-
//
|
|
4
|
-
//
|
|
5
|
-
//
|
|
6
|
-
//
|
|
7
|
-
//
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
8
|
+
// All entries are exposed as singleton properties on `ElementType`:
|
|
9
|
+
//
|
|
10
|
+
// ElementType.V3f // ElementKind<V3f>
|
|
11
|
+
// ElementType.U16 // ElementKind<number>
|
|
12
|
+
// ElementType.M44f // ElementKind<M44f>
|
|
13
|
+
//
|
|
14
|
+
// `BufferView.ofArray` infers the kind from the source array's
|
|
15
|
+
// constructor identity (V3fArray → ElementType.V3f, Float32Array →
|
|
16
|
+
// ElementType.F32, etc.) so the explicit second argument is rarely
|
|
17
|
+
// needed.
|
|
18
|
+
|
|
19
|
+
import type {
|
|
20
|
+
V2f, V3f, V4f, M22f, M33f, M44f, V2i, V3i, V4i,
|
|
21
|
+
} from "@aardworx/wombat.base";
|
|
22
|
+
import {
|
|
23
|
+
V2fArray, V3fArray, V4fArray,
|
|
24
|
+
V2iArray, V3iArray, V4iArray,
|
|
25
|
+
V2uiArray, V3uiArray, V4uiArray,
|
|
26
|
+
} from "@aardworx/wombat.base";
|
|
27
|
+
|
|
28
|
+
// ---------------------------------------------------------------------------
|
|
29
|
+
// ElementKind<T>
|
|
30
|
+
// ---------------------------------------------------------------------------
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* Phantom-typed token describing a single element's data type. The
|
|
34
|
+
* `T` parameter is a brand only — it carries the source-side type
|
|
35
|
+
* (V3f, M44f, number, …) into the type system at zero runtime cost.
|
|
36
|
+
*
|
|
37
|
+
* Identity equality is the equality used everywhere in the renderer
|
|
38
|
+
* (group keys, bind plans). All instances are singletons under the
|
|
39
|
+
* `ElementType` namespace below.
|
|
40
|
+
*/
|
|
41
|
+
export class ElementKind<T> {
|
|
42
|
+
constructor(
|
|
43
|
+
/** Short human-readable tag (e.g. `"v3f"`, `"u16"`). */
|
|
44
|
+
readonly name: string,
|
|
45
|
+
/** Size of one element in bytes. */
|
|
46
|
+
readonly byteSize: number,
|
|
47
|
+
/** GPUVertexFormat for use as a vertex attribute. `undefined` for matrix and non-vertex types. */
|
|
48
|
+
readonly vertexFormat: GPUVertexFormat | undefined,
|
|
49
|
+
/** GPUIndexFormat when used as an index buffer. `undefined` unless the type is `u16` / `u32`. */
|
|
50
|
+
readonly indexFormat: GPUIndexFormat | undefined,
|
|
51
|
+
) {}
|
|
52
|
+
|
|
53
|
+
/** Phantom — never read at runtime. Forces TS to track T. */
|
|
54
|
+
declare readonly _brand: T;
|
|
55
|
+
|
|
56
|
+
toString(): string { return this.name; }
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
/**
|
|
60
|
+
* `ElementType` doubles as a type alias (the structural shape) and
|
|
61
|
+
* a value namespace (the singleton tokens). `ElementType` in type
|
|
62
|
+
* position is `ElementKind<unknown>`; in value position it's the
|
|
63
|
+
* collection of singletons below.
|
|
64
|
+
*/
|
|
65
|
+
export type ElementType = ElementKind<unknown>;
|
|
66
|
+
|
|
67
|
+
// ---------------------------------------------------------------------------
|
|
68
|
+
// Singleton tokens
|
|
69
|
+
// ---------------------------------------------------------------------------
|
|
24
70
|
|
|
71
|
+
const _V2f = new ElementKind<V2f>("v2f", 8, "float32x2", undefined);
|
|
72
|
+
const _V3f = new ElementKind<V3f>("v3f", 12, "float32x3", undefined);
|
|
73
|
+
const _V4f = new ElementKind<V4f>("v4f", 16, "float32x4", undefined);
|
|
74
|
+
const _M22f = new ElementKind<M22f>("m22f", 16, undefined, undefined);
|
|
75
|
+
const _M33f = new ElementKind<M33f>("m33f", 36, undefined, undefined);
|
|
76
|
+
const _M44f = new ElementKind<M44f>("m44f", 64, undefined, undefined);
|
|
77
|
+
const _V2i = new ElementKind<V2i>("v2i", 8, "sint32x2", undefined);
|
|
78
|
+
const _V3i = new ElementKind<V3i>("v3i", 12, "sint32x3", undefined);
|
|
79
|
+
const _V4i = new ElementKind<V4i>("v4i", 16, "sint32x4", undefined);
|
|
80
|
+
const _V2u = new ElementKind<V2i>("v2u", 8, "uint32x2", undefined);
|
|
81
|
+
const _V3u = new ElementKind<V3i>("v3u", 12, "uint32x3", undefined);
|
|
82
|
+
const _V4u = new ElementKind<V4i>("v4u", 16, "uint32x4", undefined);
|
|
83
|
+
const _F32 = new ElementKind<number>("f32", 4, "float32", undefined);
|
|
84
|
+
const _U32 = new ElementKind<number>("u32", 4, "uint32", "uint32");
|
|
85
|
+
const _I32 = new ElementKind<number>("i32", 4, "sint32", undefined);
|
|
86
|
+
const _U16 = new ElementKind<number>("u16", 2, undefined, "uint16");
|
|
87
|
+
const _I16 = new ElementKind<number>("i16", 2, undefined, undefined);
|
|
88
|
+
const _U8 = new ElementKind<number>("u8", 1, undefined, undefined);
|
|
89
|
+
const _I8 = new ElementKind<number>("i8", 1, undefined, undefined);
|
|
90
|
+
|
|
91
|
+
/**
|
|
92
|
+
* Construct a vertex view; throws if the kind has no vertex format
|
|
93
|
+
* (matrix / unsupported type).
|
|
94
|
+
*/
|
|
95
|
+
function asVertexFormat(t: ElementType): GPUVertexFormat {
|
|
96
|
+
if (t.vertexFormat === undefined) {
|
|
97
|
+
throw new Error(
|
|
98
|
+
`ElementType.${t.name}: no GPUVertexFormat (matrices must be split into column attributes; ` +
|
|
99
|
+
`unsupported types must be repacked first)`,
|
|
100
|
+
);
|
|
101
|
+
}
|
|
102
|
+
return t.vertexFormat;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function asIndexFormat(t: ElementType): GPUIndexFormat {
|
|
106
|
+
if (t.indexFormat === undefined) {
|
|
107
|
+
throw new Error(`ElementType.${t.name}: not a valid index type (use ElementType.U16 or ElementType.U32)`);
|
|
108
|
+
}
|
|
109
|
+
return t.indexFormat;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// ---------------------------------------------------------------------------
|
|
113
|
+
// Constructor → ElementType registry — drives `BufferView.ofArray`
|
|
114
|
+
// auto-inference. Keyed on class identity (V3fArray, Float32Array,
|
|
115
|
+
// …) so it survives minification.
|
|
116
|
+
// ---------------------------------------------------------------------------
|
|
117
|
+
|
|
118
|
+
const FROM_CTOR: ReadonlyMap<unknown, ElementType> = new Map<unknown, ElementType>([
|
|
119
|
+
// wombat.base packed arrays
|
|
120
|
+
[V2fArray, _V2f], [V3fArray, _V3f], [V4fArray, _V4f],
|
|
121
|
+
[V2iArray, _V2i], [V3iArray, _V3i], [V4iArray, _V4i],
|
|
122
|
+
[V2uiArray, _V2u], [V3uiArray, _V3u], [V4uiArray, _V4u],
|
|
123
|
+
// native typed arrays (interpreted as scalars)
|
|
124
|
+
[Float32Array, _F32],
|
|
125
|
+
[Uint32Array, _U32],
|
|
126
|
+
[Int32Array, _I32],
|
|
127
|
+
[Uint16Array, _U16],
|
|
128
|
+
[Int16Array, _I16],
|
|
129
|
+
[Uint8Array, _U8],
|
|
130
|
+
[Int8Array, _I8],
|
|
131
|
+
]);
|
|
132
|
+
|
|
133
|
+
function fromArray(arr: unknown): ElementType {
|
|
134
|
+
const ctor = (arr as { constructor?: unknown }).constructor;
|
|
135
|
+
if (ctor === undefined) {
|
|
136
|
+
throw new Error("ElementType.fromArray: input has no constructor");
|
|
137
|
+
}
|
|
138
|
+
const et = FROM_CTOR.get(ctor);
|
|
139
|
+
if (et === undefined) {
|
|
140
|
+
const name = (ctor as { name?: string }).name ?? "<anonymous>";
|
|
141
|
+
throw new Error(
|
|
142
|
+
`ElementType.fromArray: cannot infer ElementType from ${name}; ` +
|
|
143
|
+
`pass the kind explicitly (e.g. ElementType.V3f)`,
|
|
144
|
+
);
|
|
145
|
+
}
|
|
146
|
+
return et;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// ---------------------------------------------------------------------------
|
|
150
|
+
// Public namespace
|
|
151
|
+
// ---------------------------------------------------------------------------
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Singleton tokens + helpers. Most call sites use the singleton
|
|
155
|
+
* directly (`ElementType.V3f`); the helpers are kept on the
|
|
156
|
+
* namespace so `import { ElementType }` is the only import you
|
|
157
|
+
* need.
|
|
158
|
+
*/
|
|
25
159
|
export const ElementType = {
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
* GPUVertexFormat for use as a vertex / instance attribute.
|
|
45
|
-
* `normalized=true` only affects integer types — picks the `unorm`
|
|
46
|
-
* / `snorm` form. Matrix types are not directly supported as
|
|
47
|
-
* vertex formats; the auto-instancing pass splits them into
|
|
48
|
-
* column attributes upstream.
|
|
49
|
-
*/
|
|
50
|
-
toVertexFormat(t: ElementType, normalized = false): GPUVertexFormat {
|
|
51
|
-
switch (t) {
|
|
52
|
-
case "f32": return "float32";
|
|
53
|
-
case "u32": return "uint32";
|
|
54
|
-
case "i32": return "sint32";
|
|
55
|
-
case "v2f": return "float32x2";
|
|
56
|
-
case "v3f": return "float32x3";
|
|
57
|
-
case "v4f": return "float32x4";
|
|
58
|
-
case "v2i": return "sint32x2";
|
|
59
|
-
case "v3i": return "sint32x3";
|
|
60
|
-
case "v4i": return "sint32x4";
|
|
61
|
-
case "v2u": return "uint32x2";
|
|
62
|
-
case "v3u": return "uint32x3";
|
|
63
|
-
case "v4u": return "uint32x4";
|
|
64
|
-
case "u16": return normalized ? "unorm16x2" as GPUVertexFormat : "uint16";
|
|
65
|
-
case "i16": return normalized ? "snorm16x2" as GPUVertexFormat : "sint16";
|
|
66
|
-
case "u8": return normalized ? "unorm8x4" as GPUVertexFormat : "uint8";
|
|
67
|
-
case "i8": return normalized ? "snorm8x4" as GPUVertexFormat : "sint8";
|
|
68
|
-
case "c4b": return normalized ? "unorm8x4" : "uint8x4";
|
|
69
|
-
case "c3b":
|
|
70
|
-
// No 3-component byte format in WebGPU; promoted to x4 with
|
|
71
|
-
// a dummy fourth byte by the upload path.
|
|
72
|
-
throw new Error(`ElementType.toVertexFormat: c3b cannot be a vertex format directly; pad to c4b`);
|
|
73
|
-
case "m22f": case "m33f": case "m44f":
|
|
74
|
-
throw new Error(`ElementType.toVertexFormat: ${t} is a matrix; split into column attributes first`);
|
|
75
|
-
}
|
|
76
|
-
},
|
|
77
|
-
|
|
78
|
-
/** Index format. Throws unless the type is `u16` or `u32`. */
|
|
79
|
-
toIndexFormat(t: ElementType): GPUIndexFormat {
|
|
80
|
-
if (t === "u16") return "uint16";
|
|
81
|
-
if (t === "u32") return "uint32";
|
|
82
|
-
throw new Error(`ElementType.toIndexFormat: ${t} is not a valid index type (use u16 or u32)`);
|
|
83
|
-
},
|
|
160
|
+
// Float vectors
|
|
161
|
+
V2f: _V2f, V3f: _V3f, V4f: _V4f,
|
|
162
|
+
// Float matrices (square)
|
|
163
|
+
M22f: _M22f, M33f: _M33f, M44f: _M44f,
|
|
164
|
+
// Signed-int vectors
|
|
165
|
+
V2i: _V2i, V3i: _V3i, V4i: _V4i,
|
|
166
|
+
// Unsigned-int vectors
|
|
167
|
+
V2u: _V2u, V3u: _V3u, V4u: _V4u,
|
|
168
|
+
// Scalars
|
|
169
|
+
F32: _F32, U32: _U32, I32: _I32,
|
|
170
|
+
U16: _U16, I16: _I16, U8: _U8, I8: _I8,
|
|
171
|
+
|
|
172
|
+
/** Infer from a wombat.base packed array or a native TypedArray. */
|
|
173
|
+
fromArray,
|
|
174
|
+
/** GPUVertexFormat for `t`; throws if `t` has no vertex form. */
|
|
175
|
+
toVertexFormat: asVertexFormat,
|
|
176
|
+
/** GPUIndexFormat for `t`; throws if `t` is not `u16` / `u32`. */
|
|
177
|
+
toIndexFormat: asIndexFormat,
|
|
84
178
|
} as const;
|
package/src/core/index.ts
CHANGED
|
@@ -543,8 +543,16 @@ export function prepareRenderObject(
|
|
|
543
543
|
const stepMode: GPUVertexStepMode = isInstance ? "instance" : "vertex";
|
|
544
544
|
|
|
545
545
|
const view = (fromVertex ?? fromInstance)!;
|
|
546
|
-
const format = ElementType.toVertexFormat(view.elementType
|
|
547
|
-
|
|
546
|
+
const format = ElementType.toVertexFormat(view.elementType);
|
|
547
|
+
// Single-value broadcast — `BufferView.ofValue(...)` allocates a
|
|
548
|
+
// tight one-element buffer and asks every vertex/instance to read
|
|
549
|
+
// it. WebGPU reads the same element when arrayStride is 0
|
|
550
|
+
// (regardless of stepMode), so override the default tight stride
|
|
551
|
+
// here. Without this the second vertex would read past the end of
|
|
552
|
+
// the 16-byte buffer and pick up zeros — colorAval would tint
|
|
553
|
+
// only the first vertex of a draw.
|
|
554
|
+
const isBroadcast = view.singleValue !== undefined;
|
|
555
|
+
const stride = isBroadcast ? 0 : BufferView.strideOf(view);
|
|
548
556
|
const offset = BufferView.offsetOf(view);
|
|
549
557
|
resolved.push({
|
|
550
558
|
name: vb.name,
|