@vgai/engine 0.5.8 → 0.5.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/dist/adapter/ingest/contract-debug-adapter.d.ts +35 -0
- package/dist/adapter/ingest/contract-debug-adapter.d.ts.map +1 -0
- package/dist/adapter/ingest/contract-debug-adapter.js +90 -0
- package/dist/adapter/ingest/game-contract.d.ts +60 -1
- package/dist/adapter/ingest/game-contract.d.ts.map +1 -1
- package/dist/adapter/ingest/game-contract.js +1 -1
- package/dist/adapter/setup-three-root-adapter.d.ts.map +1 -1
- package/dist/adapter/setup-three-root-adapter.js +105 -2
- package/dist/dev/performance-profiler.d.ts +13 -7
- package/dist/dev/performance-profiler.d.ts.map +1 -1
- package/dist/dev/performance-profiler.js +31 -3
- package/dist/dev/register-render-vitals.d.ts +95 -0
- package/dist/dev/register-render-vitals.d.ts.map +1 -0
- package/dist/dev/register-render-vitals.js +182 -0
- package/dist/dev/render-census.d.ts +135 -0
- package/dist/dev/render-census.d.ts.map +1 -0
- package/dist/dev/render-census.js +257 -0
- package/dist/dev/render-vitals.d.ts +181 -0
- package/dist/dev/render-vitals.d.ts.map +1 -0
- package/dist/dev/render-vitals.js +232 -0
- package/dist/dev/static-batch-advisor.d.ts +106 -0
- package/dist/dev/static-batch-advisor.d.ts.map +1 -0
- package/dist/dev/static-batch-advisor.js +141 -0
- package/dist/render/render-batch-system.d.ts.map +1 -1
- package/dist/render/render-batch-system.js +7 -18
- package/dist/render/structural-signature.d.ts +148 -0
- package/dist/render/structural-signature.d.ts.map +1 -0
- package/dist/render/structural-signature.js +193 -0
- package/dist/runtime/dev-layers.d.ts.map +1 -1
- package/dist/runtime/dev-layers.js +6 -0
- package/package.json +1 -1
- package/schemas/engine-capabilities.json +5 -5
- package/src/adapter/ingest/contract-debug-adapter.ts +110 -0
- package/src/adapter/ingest/game-contract.ts +63 -1
- package/src/adapter/setup-three-root-adapter.ts +105 -2
- package/src/dev/performance-profiler.ts +47 -12
- package/src/dev/register-render-vitals.ts +249 -0
- package/src/dev/render-census.ts +351 -0
- package/src/dev/render-vitals.ts +338 -0
- package/src/dev/static-batch-advisor.ts +186 -0
- package/src/render/render-batch-system.ts +16 -19
- package/src/render/structural-signature.ts +231 -0
- package/src/runtime/dev-layers.ts +7 -1
|
@@ -0,0 +1,231 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* STRUCTURAL IDENTITY AND ELIGIBILITY for static render batching — the one
|
|
3
|
+
* place that answers "may this draw be collapsed at all?" and "are these two
|
|
4
|
+
* meshes the same thing drawn twice?".
|
|
5
|
+
*
|
|
6
|
+
* Three consumers, which is the whole reason it is a module rather than three
|
|
7
|
+
* private helpers: `render/render-batch-system.ts` (the engine's own
|
|
8
|
+
* lab-driven instancer), `dev/render-census.ts`'s structural scan (what the
|
|
9
|
+
* advisor and `render.families` measure), and the `static-batch` capability's
|
|
10
|
+
* `<Frozen>` (what a game actually installs). A batcher and the advisor that
|
|
11
|
+
* routes people to it MUST agree about what is batchable, or the advisor
|
|
12
|
+
* promises a win the wrapper then declines to take.
|
|
13
|
+
*
|
|
14
|
+
* ── THE UUID TRAP, WHICH IS WHY THIS MODULE EXISTS ──────────────────────────
|
|
15
|
+
* The obvious key for "can these two meshes be drawn as one" is object
|
|
16
|
+
* identity: same `geometry.uuid`, same `material.uuid`. It is also the key
|
|
17
|
+
* that silently batches NOTHING in the shape that matters most. A freshly
|
|
18
|
+
* scaffolded TSX/R3F world writes its materials INLINE —
|
|
19
|
+
*
|
|
20
|
+
* {crates.map((c) => (
|
|
21
|
+
* <mesh key={c.id} position={c.at}>
|
|
22
|
+
* <boxGeometry args={[1, 1, 1]} />
|
|
23
|
+
* <meshStandardMaterial color="#8a6a44" />
|
|
24
|
+
* </mesh>
|
|
25
|
+
* ))}
|
|
26
|
+
*
|
|
27
|
+
* — and every one of those `<meshStandardMaterial>` elements constructs its
|
|
28
|
+
* OWN `THREE.MeshStandardMaterial`. Five hundred crates are five hundred
|
|
29
|
+
* distinct uuids describing one identical appearance. A uuid-keyed grouper
|
|
30
|
+
* reports five hundred families of one, finds nothing to do, and is indistin-
|
|
31
|
+
* guishable from a correct batcher over an unbatchable scene. The engine's own
|
|
32
|
+
* scene construction has the same property, which is why
|
|
33
|
+
* `render/render-batch-system.ts` has keyed on VALUE since it was written.
|
|
34
|
+
*
|
|
35
|
+
* So the key here is the STRUCTURE: geometry `type` + its construction
|
|
36
|
+
* `parameters`, material `type` + the props that decide what the draw looks
|
|
37
|
+
* like. Two independently constructed `BoxGeometry(1,1,1)` +
|
|
38
|
+
* `MeshStandardMaterial({color:'#8a6a44'})` pairs are the same draw, and this
|
|
39
|
+
* module says so.
|
|
40
|
+
*
|
|
41
|
+
* ── WHERE VALUE CANNOT ANSWER, IDENTITY IS THE SAFE FALLBACK ────────────────
|
|
42
|
+
* Twice below, a structural comparison would be a guess, and the answer is
|
|
43
|
+
* `uuid:` — a key nothing else can equal, so the members simply do not group.
|
|
44
|
+
* Not grouping costs a draw call; grouping two things that only LOOK alike
|
|
45
|
+
* renders the wrong picture.
|
|
46
|
+
* - geometry with no `.parameters` (a loaded GLTF mesh, a hand-built
|
|
47
|
+
* `BufferGeometry`): its vertices are its identity and comparing them is
|
|
48
|
+
* not a signature, it is a diff.
|
|
49
|
+
* - a shader material ({@link materialMergeSignature} only): its appearance
|
|
50
|
+
* lives in shader source and uniforms that no fixed prop list can read.
|
|
51
|
+
*
|
|
52
|
+
* ── TWO KEYS, DELIBERATELY ──────────────────────────────────────────────────
|
|
53
|
+
* {@link staticBatchSignature} is `RenderBatchSystem`'s key, extracted here
|
|
54
|
+
* unchanged so there is one owner of the answer rather than two that drift.
|
|
55
|
+
* {@link materialMergeSignature} is STRICTER, and it is what the `static-batch`
|
|
56
|
+
* capability's `<Frozen>` groups by: that path hands ONE material instance to
|
|
57
|
+
* a merged/instanced product, so its members must be interchangeable, not
|
|
58
|
+
* merely similar. The looser key predates it and is kept exactly as it was —
|
|
59
|
+
* see that constant's own note.
|
|
60
|
+
*/
|
|
61
|
+
|
|
62
|
+
import type * as THREE from 'three';
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* The `userData` key a node opts OUT of static batching with — the one
|
|
66
|
+
* declaration that beats every measurement:
|
|
67
|
+
*
|
|
68
|
+
* <group name="Beacons" userData={{ staticBatch: false }}>
|
|
69
|
+
*
|
|
70
|
+
* Set on a node, it covers that node's whole subtree (a walker stops there).
|
|
71
|
+
* It is deliberately `userData` and not a component prop: the thing being
|
|
72
|
+
* excluded is a three node, and every authoring lane — TSX, a loaded GLTF, a
|
|
73
|
+
* hand-built graph — can set `userData` on one.
|
|
74
|
+
*/
|
|
75
|
+
export const STATIC_BATCH_OPT_OUT_KEY = 'staticBatch';
|
|
76
|
+
|
|
77
|
+
/**
|
|
78
|
+
* Why one node may not be collapsed into a batch. `null` from
|
|
79
|
+
* {@link staticBatchSkipReason} means it may.
|
|
80
|
+
*
|
|
81
|
+
* Each of these is a case where a batched draw would render something DIFFERENT
|
|
82
|
+
* from the originals, not merely a case that is awkward to implement:
|
|
83
|
+
* - `instanced` / `batched` — already one draw; swallowing it would flatten
|
|
84
|
+
* per-instance transforms the batch does not carry.
|
|
85
|
+
* - `skinned` — its vertices are posed by a bone matrix palette every frame;
|
|
86
|
+
* baking one pose freezes the character mid-stride.
|
|
87
|
+
* - `morph-targets` — same, driven by influences instead of bones.
|
|
88
|
+
* - `multi-material` — the geometry's `groups` select a material per range;
|
|
89
|
+
* a batch carries one material.
|
|
90
|
+
* - `transparent` — blending is order-dependent and three sorts TRANSPARENT
|
|
91
|
+
* OBJECTS, not triangles. Collapsing them fixes their relative order to
|
|
92
|
+
* whatever the merge happened to write.
|
|
93
|
+
* - `opted-out` — see {@link STATIC_BATCH_OPT_OUT_KEY}.
|
|
94
|
+
*/
|
|
95
|
+
export type StaticBatchSkipReason =
|
|
96
|
+
| 'not-a-mesh'
|
|
97
|
+
| 'instanced'
|
|
98
|
+
| 'batched'
|
|
99
|
+
| 'skinned'
|
|
100
|
+
| 'no-geometry'
|
|
101
|
+
| 'multi-material'
|
|
102
|
+
| 'morph-targets'
|
|
103
|
+
| 'transparent'
|
|
104
|
+
| 'opted-out';
|
|
105
|
+
|
|
106
|
+
interface MeshKinds {
|
|
107
|
+
isMesh?: boolean;
|
|
108
|
+
isInstancedMesh?: boolean;
|
|
109
|
+
isBatchedMesh?: boolean;
|
|
110
|
+
isSkinnedMesh?: boolean;
|
|
111
|
+
geometry?: THREE.BufferGeometry;
|
|
112
|
+
material?: THREE.Material | THREE.Material[];
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* Why `node` may not join a static batch, or `null` when it may. Pure, cheap,
|
|
117
|
+
* and the SINGLE owner of that answer — see the module note for why the
|
|
118
|
+
* batcher and the advisor cannot each keep their own copy.
|
|
119
|
+
*
|
|
120
|
+
* Checks the node itself only. Subtree exclusion (an opted-out ancestor) is
|
|
121
|
+
* the caller's walk, because the walkers that need this already prune.
|
|
122
|
+
*/
|
|
123
|
+
export function staticBatchSkipReason(node: THREE.Object3D): StaticBatchSkipReason | null {
|
|
124
|
+
if (node.userData?.[STATIC_BATCH_OPT_OUT_KEY] === false) return 'opted-out';
|
|
125
|
+
const mesh = node as THREE.Object3D & MeshKinds;
|
|
126
|
+
if (mesh.isInstancedMesh) return 'instanced';
|
|
127
|
+
if (mesh.isBatchedMesh) return 'batched';
|
|
128
|
+
if (mesh.isSkinnedMesh) return 'skinned';
|
|
129
|
+
if (!mesh.isMesh) return 'not-a-mesh';
|
|
130
|
+
if (!mesh.geometry || !mesh.material) return 'no-geometry';
|
|
131
|
+
if (Array.isArray(mesh.material)) return 'multi-material';
|
|
132
|
+
if (Object.keys(mesh.geometry.morphAttributes).length > 0) return 'morph-targets';
|
|
133
|
+
if (mesh.material.transparent) return 'transparent';
|
|
134
|
+
return null;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Geometry identity: construction `type` + `parameters` for the parametric
|
|
139
|
+
* primitives, object identity for everything else. See the module note.
|
|
140
|
+
*/
|
|
141
|
+
export function geometrySignature(geometry: THREE.BufferGeometry): string {
|
|
142
|
+
const params = (geometry as unknown as { parameters?: object }).parameters;
|
|
143
|
+
return params ? `${geometry.type}:${JSON.stringify(params)}` : `uuid:${geometry.uuid}`;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Material identity as `RenderBatchSystem` has always computed it: type plus
|
|
148
|
+
* the standard-material props that change the draw.
|
|
149
|
+
*
|
|
150
|
+
* FROZEN ON PURPOSE. This is one half of {@link staticBatchSignature}, which
|
|
151
|
+
* is a live batcher's grouping key; widening or narrowing it silently
|
|
152
|
+
* regroups that batcher's scenes. New discrimination goes in
|
|
153
|
+
* {@link materialMergeSignature}, which is free to be stricter because
|
|
154
|
+
* stricter only ever means "batches less".
|
|
155
|
+
*/
|
|
156
|
+
export function materialSignature(material: THREE.Material): string {
|
|
157
|
+
const standard = material as THREE.MeshStandardMaterial;
|
|
158
|
+
const color = standard.color?.getHexString?.() ?? '';
|
|
159
|
+
const map = standard.map?.uuid ?? '';
|
|
160
|
+
return `${material.type}:${color}:${standard.roughness ?? ''}:${standard.metalness ?? ''}:${map}:${material.side}:${material.transparent}:${material.vertexColors}`;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
/**
|
|
164
|
+
* One mesh's batch key: geometry, material, and the shadow flags.
|
|
165
|
+
*
|
|
166
|
+
* The shadow flags are part of the key because a batched product carries ONE
|
|
167
|
+
* `castShadow`/`receiveShadow` pair for every member it swallowed — mixing a
|
|
168
|
+
* caster and a non-caster into one draw changes the picture.
|
|
169
|
+
*/
|
|
170
|
+
export function staticBatchSignature(mesh: THREE.Mesh): string {
|
|
171
|
+
const geometry = mesh.geometry as THREE.BufferGeometry;
|
|
172
|
+
const material = mesh.material as THREE.Material;
|
|
173
|
+
return `${geometrySignature(geometry)}|${materialSignature(material)}|${mesh.castShadow ? 1 : 0}${mesh.receiveShadow ? 1 : 0}`;
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
/**
|
|
177
|
+
* Which vertex attributes a geometry carries, and whether it is indexed —
|
|
178
|
+
* the compatibility precondition for `mergeGeometries`, which refuses a batch
|
|
179
|
+
* whose members disagree.
|
|
180
|
+
*
|
|
181
|
+
* Item size is in the key too: two geometries can both have `uv` and disagree
|
|
182
|
+
* about whether it is 2- or 3-component, which merges into silent garbage
|
|
183
|
+
* rather than a refusal.
|
|
184
|
+
*/
|
|
185
|
+
export function geometryLayoutSignature(geometry: THREE.BufferGeometry): string {
|
|
186
|
+
const attributes = Object.keys(geometry.attributes)
|
|
187
|
+
.sort()
|
|
188
|
+
.map((name) => `${name}:${geometry.attributes[name]?.itemSize ?? '?'}`)
|
|
189
|
+
.join(',');
|
|
190
|
+
return `${attributes}${geometry.getIndex() ? '|i' : ''}`;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* Material identity for a path that will SHARE one material instance between
|
|
195
|
+
* every member it collapses — stricter than {@link materialSignature}.
|
|
196
|
+
*
|
|
197
|
+
* Everything the looser key reads, plus the props that decide the picture
|
|
198
|
+
* without touching colour/roughness/metalness: opacity and the depth/blend
|
|
199
|
+
* state, emissive, the remaining standard maps, and the flags that change the
|
|
200
|
+
* compiled program. A shader material answers with its uuid instead (see the
|
|
201
|
+
* module note): its appearance is source and uniforms, and there is no honest
|
|
202
|
+
* fixed-prop reading of it.
|
|
203
|
+
*/
|
|
204
|
+
export function materialMergeSignature(material: THREE.Material): string {
|
|
205
|
+
const shader = material as THREE.ShaderMaterial & { isRawShaderMaterial?: boolean };
|
|
206
|
+
if (shader.isShaderMaterial || shader.isRawShaderMaterial) return `uuid:${material.uuid}`;
|
|
207
|
+
|
|
208
|
+
const rich = material as THREE.MeshPhysicalMaterial;
|
|
209
|
+
const maps = [rich.normalMap, rich.aoMap, rich.emissiveMap, rich.roughnessMap, rich.metalnessMap]
|
|
210
|
+
.map((map) => map?.uuid ?? '')
|
|
211
|
+
.join(',');
|
|
212
|
+
// A material with a patched `onBeforeCompile` declares its variant through
|
|
213
|
+
// this hook (that is what three itself keys its program cache on), so a
|
|
214
|
+
// non-empty value discriminates here too.
|
|
215
|
+
const program = material.customProgramCacheKey?.() ?? '';
|
|
216
|
+
return [
|
|
217
|
+
materialSignature(material),
|
|
218
|
+
material.opacity,
|
|
219
|
+
material.depthWrite,
|
|
220
|
+
material.depthTest,
|
|
221
|
+
material.alphaTest,
|
|
222
|
+
material.blending,
|
|
223
|
+
material.toneMapped,
|
|
224
|
+
material.visible,
|
|
225
|
+
rich.emissive?.getHexString?.() ?? '',
|
|
226
|
+
rich.flatShading ?? '',
|
|
227
|
+
rich.wireframe ?? '',
|
|
228
|
+
maps,
|
|
229
|
+
program,
|
|
230
|
+
].join('|');
|
|
231
|
+
}
|
|
@@ -32,7 +32,13 @@
|
|
|
32
32
|
*/
|
|
33
33
|
export function devLayersEnabled(override?: boolean | undefined): boolean {
|
|
34
34
|
if (override !== undefined) return override;
|
|
35
|
-
|
|
35
|
+
// `import.meta` is cast whole, not just its `.env`: this module is reachable
|
|
36
|
+
// from programs whose tsconfig does not pull in `vite/client` (the session
|
|
37
|
+
// client's, for one, which reaches the three adapter transitively), and there
|
|
38
|
+
// `ImportMeta` has no declared `env` at all. The cast keeps the single owner
|
|
39
|
+
// of the dev answer importable from ANY program rather than forcing every
|
|
40
|
+
// downstream tsconfig to adopt Vite's ambient types.
|
|
41
|
+
const env = (import.meta as unknown as { env?: unknown }).env as
|
|
36
42
|
| { DEV?: boolean | undefined; VITE_VGAI_DEV_LAYERS?: string | undefined }
|
|
37
43
|
| undefined;
|
|
38
44
|
if (env?.DEV === true) return true;
|