@multiplekex/shallot 0.1.7 → 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 +2 -2
- package/src/core/component.ts +7 -17
- package/src/core/index.ts +2 -1
- package/src/core/math.ts +30 -5
- package/src/core/state.ts +4 -2
- package/src/core/types.ts +2 -2
- package/src/core/xml.ts +83 -33
- package/src/extras/arrows/index.ts +79 -96
- package/src/extras/gradient/index.ts +1050 -0
- package/src/extras/index.ts +1 -0
- package/src/extras/lines/index.ts +103 -57
- package/src/extras/orbit/index.ts +1 -1
- package/src/extras/text/index.ts +249 -82
- package/src/standard/compute/graph.ts +10 -12
- package/src/standard/compute/index.ts +27 -2
- package/src/standard/compute/inspect.ts +49 -53
- package/src/standard/compute/pass.ts +23 -0
- package/src/standard/render/camera.ts +14 -2
- package/src/standard/render/forward.ts +104 -43
- package/src/standard/render/index.ts +85 -12
- package/src/standard/render/mesh/index.ts +255 -28
- package/src/standard/render/pass.ts +63 -0
- package/src/standard/render/postprocess.ts +250 -58
- package/src/standard/render/scene.ts +98 -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 +91 -0
- package/src/standard/tween/sequence.ts +2 -2
- package/src/standard/tween/tween.ts +31 -13
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { Not } from "bitecs";
|
|
1
2
|
import type { Plugin, State, System } from "../../core";
|
|
2
3
|
import { MAX_ENTITIES, resource } from "../../core";
|
|
3
4
|
import { Compute, ComputePlugin } from "../compute";
|
|
@@ -6,33 +7,48 @@ import { Camera, Tonemap, FXAA, Vignette, uploadCamera } from "./camera";
|
|
|
6
7
|
import { AmbientLight, DirectionalLight, packLightUniforms } from "./light";
|
|
7
8
|
import {
|
|
8
9
|
Mesh,
|
|
10
|
+
MeshShapes,
|
|
9
11
|
MeshColors,
|
|
10
12
|
MeshSizes,
|
|
11
|
-
|
|
13
|
+
MeshPBR,
|
|
14
|
+
MeshEmission,
|
|
15
|
+
collectByShapeAndSurface,
|
|
12
16
|
updateBatches,
|
|
17
|
+
MAX_BATCH_SLOTS,
|
|
13
18
|
type ShapeBatch,
|
|
14
19
|
type MeshBuffers,
|
|
15
20
|
} from "./mesh";
|
|
21
|
+
import { Surface, SurfaceIds, SurfaceType } from "./surface";
|
|
16
22
|
import { createSceneBuffer, ensureRenderTextures } from "./scene";
|
|
17
23
|
import { createForwardNode, createIndirectBuffer } from "./forward";
|
|
18
24
|
import { createPostProcessNode, type PostProcessUniforms } from "./postprocess";
|
|
25
|
+
import { createTransparentNode } from "./transparent";
|
|
26
|
+
import { Draws, type DrawState } from "./pass";
|
|
19
27
|
|
|
20
28
|
export * from "./camera";
|
|
21
29
|
export * from "./light";
|
|
22
30
|
export * from "./mesh";
|
|
31
|
+
export * from "./surface";
|
|
23
32
|
export * from "./scene";
|
|
24
33
|
export * from "./forward";
|
|
25
34
|
export * from "./postprocess";
|
|
35
|
+
export * from "./transparent";
|
|
36
|
+
export * from "./pass";
|
|
26
37
|
|
|
27
38
|
export interface RenderState {
|
|
28
39
|
scene: GPUBuffer;
|
|
29
40
|
matrices: GPUBuffer;
|
|
30
41
|
colors: GPUBuffer;
|
|
31
42
|
sizes: GPUBuffer;
|
|
43
|
+
shapes: GPUBuffer;
|
|
44
|
+
pbr: GPUBuffer;
|
|
45
|
+
emission: GPUBuffer;
|
|
46
|
+
surfaceIds: GPUBuffer;
|
|
32
47
|
indirect: GPUBuffer;
|
|
33
|
-
batches: Map<
|
|
48
|
+
batches: Map<string, ShapeBatch>;
|
|
34
49
|
buffers: Map<number, MeshBuffers>;
|
|
35
50
|
postProcess: PostProcessUniforms;
|
|
51
|
+
entityCount: number;
|
|
36
52
|
}
|
|
37
53
|
|
|
38
54
|
export const Render = resource<RenderState>("render");
|
|
@@ -60,7 +76,7 @@ const RenderSystem: System = {
|
|
|
60
76
|
|
|
61
77
|
for (const eid of state.query([Camera])) {
|
|
62
78
|
if (Camera.active[eid]) {
|
|
63
|
-
uploadCamera(device, render.scene, eid, width
|
|
79
|
+
uploadCamera(device, render.scene, eid, width, height);
|
|
64
80
|
|
|
65
81
|
render.postProcess.tonemap = state.hasComponent(eid, Tonemap);
|
|
66
82
|
if (render.postProcess.tonemap) {
|
|
@@ -71,6 +87,8 @@ const RenderSystem: System = {
|
|
|
71
87
|
render.postProcess.vignetteStrength = Vignette.strength[eid];
|
|
72
88
|
render.postProcess.vignetteInner = Vignette.inner[eid];
|
|
73
89
|
render.postProcess.vignetteOuter = Vignette.outer[eid];
|
|
90
|
+
} else {
|
|
91
|
+
render.postProcess.vignetteStrength = 0;
|
|
74
92
|
}
|
|
75
93
|
break;
|
|
76
94
|
}
|
|
@@ -107,23 +125,57 @@ const RenderSystem: System = {
|
|
|
107
125
|
const lightUniforms = packLightUniforms(ambientData, directionalData);
|
|
108
126
|
device.queue.writeBuffer(render.scene, 128, lightUniforms as Float32Array<ArrayBuffer>);
|
|
109
127
|
|
|
128
|
+
render.entityCount = state.maxEid + 1;
|
|
129
|
+
const uploadCount = render.entityCount;
|
|
110
130
|
device.queue.writeBuffer(
|
|
111
131
|
render.matrices,
|
|
112
132
|
0,
|
|
113
|
-
WorldTransform.data as Float32Array<ArrayBuffer
|
|
133
|
+
WorldTransform.data as Float32Array<ArrayBuffer>,
|
|
134
|
+
0,
|
|
135
|
+
uploadCount * 64
|
|
114
136
|
);
|
|
115
137
|
|
|
116
138
|
const meshEntities = state.query([Mesh, WorldTransform]);
|
|
117
|
-
|
|
118
|
-
device.queue.writeBuffer(render.
|
|
119
|
-
device.queue.writeBuffer(render.
|
|
120
|
-
|
|
139
|
+
device.queue.writeBuffer(render.colors, 0, MeshColors.data, 0, uploadCount * 16);
|
|
140
|
+
device.queue.writeBuffer(render.sizes, 0, MeshSizes.data, 0, uploadCount * 16);
|
|
141
|
+
device.queue.writeBuffer(render.shapes, 0, MeshShapes.data, 0, uploadCount * 4);
|
|
142
|
+
device.queue.writeBuffer(render.pbr, 0, MeshPBR.data, 0, uploadCount * 16);
|
|
143
|
+
device.queue.writeBuffer(render.emission, 0, MeshEmission.data, 0, uploadCount * 16);
|
|
144
|
+
|
|
145
|
+
for (const eid of state.query([Surface])) {
|
|
146
|
+
SurfaceIds.data[eid] = Surface.type[eid];
|
|
147
|
+
}
|
|
148
|
+
device.queue.writeBuffer(render.surfaceIds, 0, SurfaceIds.data, 0, uploadCount * 4);
|
|
149
|
+
|
|
150
|
+
const byShapeAndSurface = collectByShapeAndSurface(
|
|
151
|
+
meshEntities,
|
|
152
|
+
(eid) => Surface.type[eid] ?? SurfaceType.Default
|
|
153
|
+
);
|
|
154
|
+
updateBatches(device, byShapeAndSurface, render, render.indirect);
|
|
155
|
+
},
|
|
156
|
+
};
|
|
157
|
+
|
|
158
|
+
const DefaultSurfaceSystem: System = {
|
|
159
|
+
group: "setup",
|
|
160
|
+
update(state: State) {
|
|
161
|
+
for (const eid of state.query([Mesh, Not(Surface)])) {
|
|
162
|
+
state.addComponent(eid, Surface);
|
|
163
|
+
}
|
|
121
164
|
},
|
|
122
165
|
};
|
|
123
166
|
|
|
124
167
|
export const RenderPlugin: Plugin = {
|
|
125
|
-
systems: [RenderSystem],
|
|
126
|
-
components: {
|
|
168
|
+
systems: [DefaultSurfaceSystem, RenderSystem],
|
|
169
|
+
components: {
|
|
170
|
+
Camera,
|
|
171
|
+
Mesh,
|
|
172
|
+
Surface,
|
|
173
|
+
AmbientLight,
|
|
174
|
+
DirectionalLight,
|
|
175
|
+
Tonemap,
|
|
176
|
+
FXAA,
|
|
177
|
+
Vignette,
|
|
178
|
+
},
|
|
127
179
|
dependencies: [ComputePlugin],
|
|
128
180
|
|
|
129
181
|
initialize(state: State) {
|
|
@@ -144,9 +196,14 @@ export const RenderPlugin: Plugin = {
|
|
|
144
196
|
matrices: createPropertyBuffer(MAX_ENTITIES * 64),
|
|
145
197
|
colors: createPropertyBuffer(MAX_ENTITIES * 16),
|
|
146
198
|
sizes: createPropertyBuffer(MAX_ENTITIES * 16),
|
|
147
|
-
|
|
199
|
+
shapes: createPropertyBuffer(MAX_ENTITIES * 4),
|
|
200
|
+
pbr: createPropertyBuffer(MAX_ENTITIES * 16),
|
|
201
|
+
emission: createPropertyBuffer(MAX_ENTITIES * 16),
|
|
202
|
+
surfaceIds: createPropertyBuffer(MAX_ENTITIES * 4),
|
|
203
|
+
indirect: createIndirectBuffer(device, MAX_BATCH_SLOTS),
|
|
148
204
|
batches: new Map(),
|
|
149
205
|
buffers: new Map(),
|
|
206
|
+
entityCount: 1,
|
|
150
207
|
postProcess: {
|
|
151
208
|
tonemap: false,
|
|
152
209
|
exposure: 1.0,
|
|
@@ -159,17 +216,33 @@ export const RenderPlugin: Plugin = {
|
|
|
159
216
|
|
|
160
217
|
state.setResource(Render, renderState);
|
|
161
218
|
|
|
219
|
+
const drawState: DrawState = {
|
|
220
|
+
draws: new Map(),
|
|
221
|
+
};
|
|
222
|
+
state.setResource(Draws, drawState);
|
|
223
|
+
|
|
162
224
|
compute.graph.add(
|
|
163
225
|
createForwardNode({
|
|
226
|
+
state,
|
|
164
227
|
scene: renderState.scene,
|
|
165
228
|
matrices: renderState.matrices,
|
|
166
229
|
colors: renderState.colors,
|
|
167
230
|
sizes: renderState.sizes,
|
|
231
|
+
shapes: renderState.shapes,
|
|
232
|
+
pbr: renderState.pbr,
|
|
233
|
+
emission: renderState.emission,
|
|
168
234
|
indirect: renderState.indirect,
|
|
169
235
|
batches: renderState.batches,
|
|
170
236
|
})
|
|
171
237
|
);
|
|
172
238
|
|
|
173
|
-
compute.graph.add(
|
|
239
|
+
compute.graph.add(createTransparentNode({ state }));
|
|
240
|
+
|
|
241
|
+
compute.graph.add(
|
|
242
|
+
createPostProcessNode({
|
|
243
|
+
state,
|
|
244
|
+
uniforms: renderState.postProcess,
|
|
245
|
+
})
|
|
246
|
+
);
|
|
174
247
|
},
|
|
175
248
|
};
|
|
@@ -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>;
|
|
@@ -13,23 +17,43 @@ export interface MeshData {
|
|
|
13
17
|
indexCount: number;
|
|
14
18
|
}
|
|
15
19
|
|
|
20
|
+
const meshes: MeshData[] = [];
|
|
21
|
+
|
|
22
|
+
function initBuiltIns(): void {
|
|
23
|
+
if (meshes.length === 0) {
|
|
24
|
+
meshes.push(createBox());
|
|
25
|
+
meshes.push(createSphere());
|
|
26
|
+
meshes.push(createPlane());
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
initBuiltIns();
|
|
31
|
+
|
|
16
32
|
export const MeshShape = {
|
|
17
33
|
Box: 0,
|
|
18
34
|
Sphere: 1,
|
|
19
35
|
Plane: 2,
|
|
20
36
|
} as const;
|
|
21
37
|
|
|
22
|
-
export function
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
}
|
|
38
|
+
export function mesh(data: MeshData): number {
|
|
39
|
+
const id = meshes.length;
|
|
40
|
+
meshes.push(data);
|
|
41
|
+
return id;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function getMesh(id: number): MeshData | undefined {
|
|
45
|
+
return meshes[id];
|
|
31
46
|
}
|
|
32
47
|
|
|
48
|
+
export function clearMeshes(): void {
|
|
49
|
+
meshes.length = 0;
|
|
50
|
+
initBuiltIns();
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export const MeshShapes = {
|
|
54
|
+
data: new Uint32Array(MAX_ENTITIES).fill(INVALID_SHAPE),
|
|
55
|
+
};
|
|
56
|
+
|
|
33
57
|
export const MeshColors = {
|
|
34
58
|
data: new Float32Array(MAX_ENTITIES * 4),
|
|
35
59
|
};
|
|
@@ -38,6 +62,21 @@ export const MeshSizes = {
|
|
|
38
62
|
data: new Float32Array(MAX_ENTITIES * 4),
|
|
39
63
|
};
|
|
40
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
|
+
|
|
41
80
|
interface ColorProxy extends Array<number>, FieldAccessor {}
|
|
42
81
|
|
|
43
82
|
function colorProxy(): ColorProxy {
|
|
@@ -76,6 +115,36 @@ function colorProxy(): ColorProxy {
|
|
|
76
115
|
});
|
|
77
116
|
}
|
|
78
117
|
|
|
118
|
+
interface ColorChannelProxy extends Array<number>, FieldAccessor {}
|
|
119
|
+
|
|
120
|
+
function colorChannelProxy(channelIndex: number): ColorChannelProxy {
|
|
121
|
+
const data = MeshColors.data;
|
|
122
|
+
|
|
123
|
+
function getValue(eid: number): number {
|
|
124
|
+
return data[eid * 4 + channelIndex];
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function setValue(eid: number, value: number): void {
|
|
128
|
+
data[eid * 4 + channelIndex] = value;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return new Proxy([] as unknown as ColorChannelProxy, {
|
|
132
|
+
get(_, prop) {
|
|
133
|
+
if (prop === "get") return getValue;
|
|
134
|
+
if (prop === "set") return setValue;
|
|
135
|
+
const eid = Number(prop);
|
|
136
|
+
if (Number.isNaN(eid)) return undefined;
|
|
137
|
+
return getValue(eid);
|
|
138
|
+
},
|
|
139
|
+
set(_, prop, value) {
|
|
140
|
+
const eid = Number(prop);
|
|
141
|
+
if (Number.isNaN(eid)) return false;
|
|
142
|
+
setValue(eid, value);
|
|
143
|
+
return true;
|
|
144
|
+
},
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
79
148
|
interface SizeProxy extends Array<number>, FieldAccessor {}
|
|
80
149
|
|
|
81
150
|
function sizeProxy(component: number): SizeProxy {
|
|
@@ -106,18 +175,128 @@ function sizeProxy(component: number): SizeProxy {
|
|
|
106
175
|
});
|
|
107
176
|
}
|
|
108
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
|
+
|
|
109
274
|
export const Mesh: {
|
|
110
|
-
shape:
|
|
275
|
+
shape: Uint32Array;
|
|
111
276
|
color: ColorProxy;
|
|
277
|
+
colorR: ColorChannelProxy;
|
|
278
|
+
colorG: ColorChannelProxy;
|
|
279
|
+
colorB: ColorChannelProxy;
|
|
112
280
|
sizeX: SizeProxy;
|
|
113
281
|
sizeY: SizeProxy;
|
|
114
282
|
sizeZ: SizeProxy;
|
|
283
|
+
roughness: PBRProxy;
|
|
284
|
+
metallic: PBRProxy;
|
|
285
|
+
emission: EmissionProxy;
|
|
286
|
+
emissionIntensity: PBRProxy;
|
|
115
287
|
} = {
|
|
116
|
-
shape:
|
|
288
|
+
shape: MeshShapes.data,
|
|
117
289
|
color: colorProxy(),
|
|
290
|
+
colorR: colorChannelProxy(0),
|
|
291
|
+
colorG: colorChannelProxy(1),
|
|
292
|
+
colorB: colorChannelProxy(2),
|
|
118
293
|
sizeX: sizeProxy(0),
|
|
119
294
|
sizeY: sizeProxy(1),
|
|
120
295
|
sizeZ: sizeProxy(2),
|
|
296
|
+
roughness: pbrProxy(0, 0.9),
|
|
297
|
+
metallic: pbrProxy(1, 0.0),
|
|
298
|
+
emission: emissionProxy(),
|
|
299
|
+
emissionIntensity: emissionIntensityProxy(),
|
|
121
300
|
};
|
|
122
301
|
|
|
123
302
|
setTraits(Mesh, {
|
|
@@ -127,12 +306,23 @@ setTraits(Mesh, {
|
|
|
127
306
|
sizeX: 1,
|
|
128
307
|
sizeY: 1,
|
|
129
308
|
sizeZ: 1,
|
|
309
|
+
roughness: 0.9,
|
|
310
|
+
metallic: 0.0,
|
|
311
|
+
emission: 0x000000,
|
|
312
|
+
emissionIntensity: 0.0,
|
|
130
313
|
}),
|
|
131
314
|
accessors: {
|
|
132
315
|
color: Mesh.color,
|
|
316
|
+
colorR: Mesh.colorR,
|
|
317
|
+
colorG: Mesh.colorG,
|
|
318
|
+
colorB: Mesh.colorB,
|
|
133
319
|
sizeX: Mesh.sizeX,
|
|
134
320
|
sizeY: Mesh.sizeY,
|
|
135
321
|
sizeZ: Mesh.sizeZ,
|
|
322
|
+
roughness: Mesh.roughness,
|
|
323
|
+
metallic: Mesh.metallic,
|
|
324
|
+
emission: Mesh.emission,
|
|
325
|
+
emissionIntensity: Mesh.emissionIntensity,
|
|
136
326
|
},
|
|
137
327
|
});
|
|
138
328
|
|
|
@@ -160,53 +350,84 @@ export function createMeshBuffers(device: GPUDevice, mesh: MeshData): MeshBuffer
|
|
|
160
350
|
return { vertex, index, indexCount: mesh.indexCount };
|
|
161
351
|
}
|
|
162
352
|
|
|
163
|
-
export
|
|
164
|
-
|
|
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[] }>();
|
|
165
367
|
for (const eid of entities) {
|
|
166
368
|
const shape = Mesh.shape[eid];
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
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);
|
|
171
376
|
}
|
|
172
|
-
|
|
377
|
+
entry.entities.push(eid);
|
|
173
378
|
}
|
|
174
|
-
return
|
|
379
|
+
return batches;
|
|
175
380
|
}
|
|
176
381
|
|
|
177
382
|
export interface ShapeBatch {
|
|
178
383
|
index: number;
|
|
384
|
+
shape: number;
|
|
385
|
+
surface: number;
|
|
179
386
|
buffers: MeshBuffers;
|
|
180
387
|
entityIds: GPUBuffer;
|
|
181
388
|
count: number;
|
|
182
389
|
}
|
|
183
390
|
|
|
184
391
|
export interface BatchState {
|
|
185
|
-
batches: Map<
|
|
392
|
+
batches: Map<string, ShapeBatch>;
|
|
186
393
|
buffers: Map<number, MeshBuffers>;
|
|
187
394
|
}
|
|
188
395
|
|
|
189
396
|
export function updateBatches(
|
|
190
397
|
device: GPUDevice,
|
|
191
|
-
|
|
398
|
+
byShapeAndSurface: Map<string, { key: BatchKey; entities: number[] }>,
|
|
192
399
|
state: BatchState,
|
|
193
400
|
indirect: GPUBuffer
|
|
194
401
|
): void {
|
|
195
|
-
|
|
196
|
-
|
|
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);
|
|
197
408
|
if (!batch) {
|
|
198
|
-
|
|
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);
|
|
199
417
|
if (!buffers) {
|
|
200
|
-
|
|
201
|
-
|
|
418
|
+
const data = getMesh(key.shape) ?? getMesh(MeshShape.Box)!;
|
|
419
|
+
buffers = createMeshBuffers(device, data);
|
|
420
|
+
state.buffers.set(key.shape, buffers);
|
|
202
421
|
}
|
|
203
422
|
batch = {
|
|
204
|
-
index:
|
|
423
|
+
index: batchIndex,
|
|
424
|
+
shape: key.shape,
|
|
425
|
+
surface: key.surface,
|
|
205
426
|
buffers,
|
|
206
427
|
entityIds: createEntityIdBuffer(device, MAX_ENTITIES),
|
|
207
428
|
count: 0,
|
|
208
429
|
};
|
|
209
|
-
state.batches.set(
|
|
430
|
+
state.batches.set(keyStr, batch);
|
|
210
431
|
}
|
|
211
432
|
|
|
212
433
|
device.queue.writeBuffer(batch.entityIds, 0, new Uint32Array(entities));
|
|
@@ -220,6 +441,12 @@ export function updateBatches(
|
|
|
220
441
|
firstInstance: 0,
|
|
221
442
|
});
|
|
222
443
|
}
|
|
444
|
+
|
|
445
|
+
for (const keyStr of state.batches.keys()) {
|
|
446
|
+
if (!activeBatches.has(keyStr)) {
|
|
447
|
+
state.batches.delete(keyStr);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
223
450
|
}
|
|
224
451
|
|
|
225
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
|
+
}
|