@multiplekex/shallot 0.1.9 → 0.1.10
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 +1 -1
- package/src/core/state.ts +2 -0
- package/src/extras/arrows/index.ts +13 -8
- package/src/extras/gradient/index.ts +1050 -0
- package/src/extras/index.ts +1 -0
- package/src/extras/lines/index.ts +13 -8
- package/src/extras/text/index.ts +11 -7
- package/src/standard/compute/graph.ts +10 -12
- package/src/standard/compute/index.ts +1 -0
- package/src/standard/compute/inspect.ts +34 -52
- package/src/standard/compute/pass.ts +23 -0
- package/src/standard/render/camera.ts +7 -2
- package/src/standard/render/forward.ts +99 -89
- package/src/standard/render/index.ts +68 -44
- package/src/standard/render/mesh/index.ts +190 -19
- package/src/standard/render/pass.ts +63 -0
- package/src/standard/render/postprocess.ts +241 -57
- package/src/standard/render/scene.ts +87 -2
- package/src/standard/render/surface/compile.ts +74 -0
- package/src/standard/render/surface/index.ts +116 -0
- package/src/standard/render/surface/structs.ts +50 -0
- package/src/standard/render/transparent.ts +62 -65
- package/src/standard/render/material/index.ts +0 -92
- package/src/standard/render/opaque.ts +0 -44
|
@@ -6,6 +6,10 @@ import { createBox } from "./box";
|
|
|
6
6
|
import { createSphere } from "./sphere";
|
|
7
7
|
import { createPlane } from "./plane";
|
|
8
8
|
|
|
9
|
+
export const MAX_SURFACES = 16;
|
|
10
|
+
export const MAX_BATCH_SLOTS = 64;
|
|
11
|
+
export const INVALID_SHAPE = 0xffffffff;
|
|
12
|
+
|
|
9
13
|
export interface MeshData {
|
|
10
14
|
vertices: Float32Array<ArrayBuffer>;
|
|
11
15
|
indices: Uint16Array<ArrayBuffer>;
|
|
@@ -46,6 +50,10 @@ export function clearMeshes(): void {
|
|
|
46
50
|
initBuiltIns();
|
|
47
51
|
}
|
|
48
52
|
|
|
53
|
+
export const MeshShapes = {
|
|
54
|
+
data: new Uint32Array(MAX_ENTITIES).fill(INVALID_SHAPE),
|
|
55
|
+
};
|
|
56
|
+
|
|
49
57
|
export const MeshColors = {
|
|
50
58
|
data: new Float32Array(MAX_ENTITIES * 4),
|
|
51
59
|
};
|
|
@@ -54,6 +62,21 @@ export const MeshSizes = {
|
|
|
54
62
|
data: new Float32Array(MAX_ENTITIES * 4),
|
|
55
63
|
};
|
|
56
64
|
|
|
65
|
+
export const MeshPBR = {
|
|
66
|
+
data: new Float32Array(MAX_ENTITIES * 4),
|
|
67
|
+
};
|
|
68
|
+
|
|
69
|
+
export const MeshEmission = {
|
|
70
|
+
data: new Float32Array(MAX_ENTITIES * 4),
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
function initPBRDefaults(): void {
|
|
74
|
+
for (let i = 0; i < MAX_ENTITIES; i++) {
|
|
75
|
+
MeshPBR.data[i * 4] = 0.9;
|
|
76
|
+
MeshPBR.data[i * 4 + 1] = 0.0;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
57
80
|
interface ColorProxy extends Array<number>, FieldAccessor {}
|
|
58
81
|
|
|
59
82
|
function colorProxy(): ColorProxy {
|
|
@@ -152,8 +175,104 @@ function sizeProxy(component: number): SizeProxy {
|
|
|
152
175
|
});
|
|
153
176
|
}
|
|
154
177
|
|
|
178
|
+
interface PBRProxy extends Array<number>, FieldAccessor {}
|
|
179
|
+
|
|
180
|
+
function pbrProxy(component: number, defaultValue: number): PBRProxy {
|
|
181
|
+
const data = MeshPBR.data;
|
|
182
|
+
|
|
183
|
+
function getValue(eid: number): number {
|
|
184
|
+
const val = data[eid * 4 + component];
|
|
185
|
+
return val === 0 && component === 0 ? defaultValue : val;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
function setValue(eid: number, value: number): void {
|
|
189
|
+
data[eid * 4 + component] = value;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
return new Proxy([] as unknown as PBRProxy, {
|
|
193
|
+
get(_, prop) {
|
|
194
|
+
if (prop === "get") return getValue;
|
|
195
|
+
if (prop === "set") return setValue;
|
|
196
|
+
const eid = Number(prop);
|
|
197
|
+
if (Number.isNaN(eid)) return undefined;
|
|
198
|
+
return getValue(eid);
|
|
199
|
+
},
|
|
200
|
+
set(_, prop, value) {
|
|
201
|
+
const eid = Number(prop);
|
|
202
|
+
if (Number.isNaN(eid)) return false;
|
|
203
|
+
setValue(eid, value);
|
|
204
|
+
return true;
|
|
205
|
+
},
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
interface EmissionProxy extends Array<number>, FieldAccessor {}
|
|
210
|
+
|
|
211
|
+
function emissionProxy(): EmissionProxy {
|
|
212
|
+
const data = MeshEmission.data;
|
|
213
|
+
|
|
214
|
+
function getValue(eid: number): number {
|
|
215
|
+
const offset = eid * 4;
|
|
216
|
+
const r = Math.round(data[offset] * 255);
|
|
217
|
+
const g = Math.round(data[offset + 1] * 255);
|
|
218
|
+
const b = Math.round(data[offset + 2] * 255);
|
|
219
|
+
return (r << 16) | (g << 8) | b;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
function setValue(eid: number, value: number): void {
|
|
223
|
+
const offset = eid * 4;
|
|
224
|
+
data[offset] = ((value >> 16) & 0xff) / 255;
|
|
225
|
+
data[offset + 1] = ((value >> 8) & 0xff) / 255;
|
|
226
|
+
data[offset + 2] = (value & 0xff) / 255;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
return new Proxy([] as unknown as EmissionProxy, {
|
|
230
|
+
get(_, prop) {
|
|
231
|
+
if (prop === "get") return getValue;
|
|
232
|
+
if (prop === "set") return setValue;
|
|
233
|
+
const eid = Number(prop);
|
|
234
|
+
if (Number.isNaN(eid)) return undefined;
|
|
235
|
+
return getValue(eid);
|
|
236
|
+
},
|
|
237
|
+
set(_, prop, value) {
|
|
238
|
+
const eid = Number(prop);
|
|
239
|
+
if (Number.isNaN(eid)) return false;
|
|
240
|
+
setValue(eid, value);
|
|
241
|
+
return true;
|
|
242
|
+
},
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function emissionIntensityProxy(): PBRProxy {
|
|
247
|
+
const data = MeshEmission.data;
|
|
248
|
+
|
|
249
|
+
function getValue(eid: number): number {
|
|
250
|
+
return data[eid * 4 + 3];
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
function setValue(eid: number, value: number): void {
|
|
254
|
+
data[eid * 4 + 3] = value;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
return new Proxy([] as unknown as PBRProxy, {
|
|
258
|
+
get(_, prop) {
|
|
259
|
+
if (prop === "get") return getValue;
|
|
260
|
+
if (prop === "set") return setValue;
|
|
261
|
+
const eid = Number(prop);
|
|
262
|
+
if (Number.isNaN(eid)) return undefined;
|
|
263
|
+
return getValue(eid);
|
|
264
|
+
},
|
|
265
|
+
set(_, prop, value) {
|
|
266
|
+
const eid = Number(prop);
|
|
267
|
+
if (Number.isNaN(eid)) return false;
|
|
268
|
+
setValue(eid, value);
|
|
269
|
+
return true;
|
|
270
|
+
},
|
|
271
|
+
});
|
|
272
|
+
}
|
|
273
|
+
|
|
155
274
|
export const Mesh: {
|
|
156
|
-
shape:
|
|
275
|
+
shape: Uint32Array;
|
|
157
276
|
color: ColorProxy;
|
|
158
277
|
colorR: ColorChannelProxy;
|
|
159
278
|
colorG: ColorChannelProxy;
|
|
@@ -161,8 +280,12 @@ export const Mesh: {
|
|
|
161
280
|
sizeX: SizeProxy;
|
|
162
281
|
sizeY: SizeProxy;
|
|
163
282
|
sizeZ: SizeProxy;
|
|
283
|
+
roughness: PBRProxy;
|
|
284
|
+
metallic: PBRProxy;
|
|
285
|
+
emission: EmissionProxy;
|
|
286
|
+
emissionIntensity: PBRProxy;
|
|
164
287
|
} = {
|
|
165
|
-
shape:
|
|
288
|
+
shape: MeshShapes.data,
|
|
166
289
|
color: colorProxy(),
|
|
167
290
|
colorR: colorChannelProxy(0),
|
|
168
291
|
colorG: colorChannelProxy(1),
|
|
@@ -170,6 +293,10 @@ export const Mesh: {
|
|
|
170
293
|
sizeX: sizeProxy(0),
|
|
171
294
|
sizeY: sizeProxy(1),
|
|
172
295
|
sizeZ: sizeProxy(2),
|
|
296
|
+
roughness: pbrProxy(0, 0.9),
|
|
297
|
+
metallic: pbrProxy(1, 0.0),
|
|
298
|
+
emission: emissionProxy(),
|
|
299
|
+
emissionIntensity: emissionIntensityProxy(),
|
|
173
300
|
};
|
|
174
301
|
|
|
175
302
|
setTraits(Mesh, {
|
|
@@ -179,6 +306,10 @@ setTraits(Mesh, {
|
|
|
179
306
|
sizeX: 1,
|
|
180
307
|
sizeY: 1,
|
|
181
308
|
sizeZ: 1,
|
|
309
|
+
roughness: 0.9,
|
|
310
|
+
metallic: 0.0,
|
|
311
|
+
emission: 0x000000,
|
|
312
|
+
emissionIntensity: 0.0,
|
|
182
313
|
}),
|
|
183
314
|
accessors: {
|
|
184
315
|
color: Mesh.color,
|
|
@@ -188,6 +319,10 @@ setTraits(Mesh, {
|
|
|
188
319
|
sizeX: Mesh.sizeX,
|
|
189
320
|
sizeY: Mesh.sizeY,
|
|
190
321
|
sizeZ: Mesh.sizeZ,
|
|
322
|
+
roughness: Mesh.roughness,
|
|
323
|
+
metallic: Mesh.metallic,
|
|
324
|
+
emission: Mesh.emission,
|
|
325
|
+
emissionIntensity: Mesh.emissionIntensity,
|
|
191
326
|
},
|
|
192
327
|
});
|
|
193
328
|
|
|
@@ -215,54 +350,84 @@ export function createMeshBuffers(device: GPUDevice, mesh: MeshData): MeshBuffer
|
|
|
215
350
|
return { vertex, index, indexCount: mesh.indexCount };
|
|
216
351
|
}
|
|
217
352
|
|
|
218
|
-
export
|
|
219
|
-
|
|
353
|
+
export interface BatchKey {
|
|
354
|
+
shape: number;
|
|
355
|
+
surface: number;
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
export function batchKeyString(key: BatchKey): string {
|
|
359
|
+
return `${key.shape}:${key.surface}`;
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
export function collectByShapeAndSurface(
|
|
363
|
+
entities: Iterable<number>,
|
|
364
|
+
getSurface: (eid: number) => number
|
|
365
|
+
): Map<string, { key: BatchKey; entities: number[] }> {
|
|
366
|
+
const batches = new Map<string, { key: BatchKey; entities: number[] }>();
|
|
220
367
|
for (const eid of entities) {
|
|
221
368
|
const shape = Mesh.shape[eid];
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
369
|
+
const surface = getSurface(eid);
|
|
370
|
+
const key: BatchKey = { shape, surface };
|
|
371
|
+
const keyStr = batchKeyString(key);
|
|
372
|
+
let entry = batches.get(keyStr);
|
|
373
|
+
if (!entry) {
|
|
374
|
+
entry = { key, entities: [] };
|
|
375
|
+
batches.set(keyStr, entry);
|
|
226
376
|
}
|
|
227
|
-
|
|
377
|
+
entry.entities.push(eid);
|
|
228
378
|
}
|
|
229
|
-
return
|
|
379
|
+
return batches;
|
|
230
380
|
}
|
|
231
381
|
|
|
232
382
|
export interface ShapeBatch {
|
|
233
383
|
index: number;
|
|
384
|
+
shape: number;
|
|
385
|
+
surface: number;
|
|
234
386
|
buffers: MeshBuffers;
|
|
235
387
|
entityIds: GPUBuffer;
|
|
236
388
|
count: number;
|
|
237
389
|
}
|
|
238
390
|
|
|
239
391
|
export interface BatchState {
|
|
240
|
-
batches: Map<
|
|
392
|
+
batches: Map<string, ShapeBatch>;
|
|
241
393
|
buffers: Map<number, MeshBuffers>;
|
|
242
394
|
}
|
|
243
395
|
|
|
244
396
|
export function updateBatches(
|
|
245
397
|
device: GPUDevice,
|
|
246
|
-
|
|
398
|
+
byShapeAndSurface: Map<string, { key: BatchKey; entities: number[] }>,
|
|
247
399
|
state: BatchState,
|
|
248
400
|
indirect: GPUBuffer
|
|
249
401
|
): void {
|
|
250
|
-
|
|
251
|
-
|
|
402
|
+
const activeBatches = new Set<string>();
|
|
403
|
+
|
|
404
|
+
for (const [keyStr, { key, entities }] of byShapeAndSurface) {
|
|
405
|
+
activeBatches.add(keyStr);
|
|
406
|
+
|
|
407
|
+
let batch = state.batches.get(keyStr);
|
|
252
408
|
if (!batch) {
|
|
253
|
-
|
|
409
|
+
const batchIndex = key.shape * MAX_SURFACES + key.surface;
|
|
410
|
+
if (batchIndex >= MAX_BATCH_SLOTS) {
|
|
411
|
+
console.warn(
|
|
412
|
+
`Batch index ${batchIndex} exceeds limit (${MAX_BATCH_SLOTS}), skipping batch`
|
|
413
|
+
);
|
|
414
|
+
continue;
|
|
415
|
+
}
|
|
416
|
+
let buffers = state.buffers.get(key.shape);
|
|
254
417
|
if (!buffers) {
|
|
255
|
-
const data = getMesh(shape) ?? getMesh(MeshShape.Box)!;
|
|
418
|
+
const data = getMesh(key.shape) ?? getMesh(MeshShape.Box)!;
|
|
256
419
|
buffers = createMeshBuffers(device, data);
|
|
257
|
-
state.buffers.set(shape, buffers);
|
|
420
|
+
state.buffers.set(key.shape, buffers);
|
|
258
421
|
}
|
|
259
422
|
batch = {
|
|
260
|
-
index:
|
|
423
|
+
index: batchIndex,
|
|
424
|
+
shape: key.shape,
|
|
425
|
+
surface: key.surface,
|
|
261
426
|
buffers,
|
|
262
427
|
entityIds: createEntityIdBuffer(device, MAX_ENTITIES),
|
|
263
428
|
count: 0,
|
|
264
429
|
};
|
|
265
|
-
state.batches.set(
|
|
430
|
+
state.batches.set(keyStr, batch);
|
|
266
431
|
}
|
|
267
432
|
|
|
268
433
|
device.queue.writeBuffer(batch.entityIds, 0, new Uint32Array(entities));
|
|
@@ -276,6 +441,12 @@ export function updateBatches(
|
|
|
276
441
|
firstInstance: 0,
|
|
277
442
|
});
|
|
278
443
|
}
|
|
444
|
+
|
|
445
|
+
for (const keyStr of state.batches.keys()) {
|
|
446
|
+
if (!activeBatches.has(keyStr)) {
|
|
447
|
+
state.batches.delete(keyStr);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
279
450
|
}
|
|
280
451
|
|
|
281
452
|
export { createBox } from "./box";
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import { resource, type State } from "../../core";
|
|
2
|
+
import { Pass } from "../compute/pass";
|
|
3
|
+
|
|
4
|
+
export { Pass };
|
|
5
|
+
|
|
6
|
+
export interface DrawContext {
|
|
7
|
+
readonly device: GPUDevice;
|
|
8
|
+
readonly encoder: GPUCommandEncoder;
|
|
9
|
+
readonly format: GPUTextureFormat;
|
|
10
|
+
readonly width: number;
|
|
11
|
+
readonly height: number;
|
|
12
|
+
|
|
13
|
+
readonly sceneView: GPUTextureView;
|
|
14
|
+
readonly depthView: GPUTextureView;
|
|
15
|
+
readonly entityIdView: GPUTextureView;
|
|
16
|
+
readonly maskView: GPUTextureView;
|
|
17
|
+
readonly canvasView: GPUTextureView;
|
|
18
|
+
|
|
19
|
+
readonly inputView?: GPUTextureView;
|
|
20
|
+
readonly outputView?: GPUTextureView;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface SharedPassContext {
|
|
24
|
+
readonly device: GPUDevice;
|
|
25
|
+
readonly format: GPUTextureFormat;
|
|
26
|
+
readonly maskFormat: GPUTextureFormat;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export interface Draw {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly pass: Pass;
|
|
32
|
+
readonly order: number;
|
|
33
|
+
execute(ctx: DrawContext): void;
|
|
34
|
+
draw?(pass: GPURenderPassEncoder, ctx: SharedPassContext): void;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface DrawState {
|
|
38
|
+
draws: Map<string, Draw>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
export const Draws = resource<DrawState>("draws");
|
|
42
|
+
|
|
43
|
+
export function registerDraw(state: State, draw: Draw): void {
|
|
44
|
+
const draws = Draws.from(state);
|
|
45
|
+
if (draws) {
|
|
46
|
+
draws.draws.set(draw.id, draw);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function unregisterDraw(state: State, id: string): void {
|
|
51
|
+
const draws = Draws.from(state);
|
|
52
|
+
if (draws) {
|
|
53
|
+
draws.draws.delete(id);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export function getDrawsByPass(state: State, pass: Pass): Draw[] {
|
|
58
|
+
const draws = Draws.from(state);
|
|
59
|
+
if (!draws) return [];
|
|
60
|
+
return Array.from(draws.draws.values())
|
|
61
|
+
.filter((d) => d.pass === pass)
|
|
62
|
+
.sort((a, b) => a.order - b.order);
|
|
63
|
+
}
|