@woosh/meep-engine 3.14.3 → 3.14.5
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/geom/3d/mat4/m4_rigidity_defect.d.ts +30 -0
- package/src/core/geom/3d/mat4/m4_rigidity_defect.d.ts.map +1 -0
- package/src/core/geom/3d/mat4/m4_rigidity_defect.js +71 -0
- package/src/engine/Engine.d.ts.map +1 -1
- package/src/engine/Engine.js +733 -726
- package/src/engine/network/NetworkSession.d.ts +24 -4
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +28 -4
- package/src/engine/network/orchestrator/NetworkPeer.d.ts +96 -7
- package/src/engine/network/orchestrator/NetworkPeer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/NetworkPeer.js +392 -68
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts +6 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeClient.js +6 -0
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts +105 -13
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.d.ts.map +1 -1
- package/src/engine/network/orchestrator/ServerAuthoritativeServer.js +306 -59
- package/src/engine/network/replication/Replicator.d.ts +43 -7
- package/src/engine/network/replication/Replicator.d.ts.map +1 -1
- package/src/engine/network/replication/Replicator.js +309 -111
- package/src/engine/network/sim/ActionLog.d.ts +23 -3
- package/src/engine/network/sim/ActionLog.d.ts.map +1 -1
- package/src/engine/network/sim/ActionLog.js +26 -3
- package/src/engine/network/sim/ActionRecordCursor.d.ts +82 -0
- package/src/engine/network/sim/ActionRecordCursor.d.ts.map +1 -0
- package/src/engine/network/sim/ActionRecordCursor.js +120 -0
- package/src/engine/network/sim/RewindEngine.d.ts.map +1 -1
- package/src/engine/network/sim/RewindEngine.js +13 -29
- package/src/engine/network/sim/SimAction.d.ts +15 -0
- package/src/engine/network/sim/SimAction.d.ts.map +1 -1
- package/src/engine/network/sim/SimAction.js +15 -0
- package/src/engine/network/sim/SimActionExecutor.d.ts +51 -2
- package/src/engine/network/sim/SimActionExecutor.d.ts.map +1 -1
- package/src/engine/network/sim/SimActionExecutor.js +74 -7
- package/src/engine/network/transport/Channel.d.ts +1 -1
- package/src/engine/network/transport/Channel.js +1 -1
- package/src/engine/physics/fluid/ecs/FluidObstacleSystem.d.ts +4 -4
- package/src/engine/simulation/Ticker.d.ts.map +1 -1
- package/src/engine/simulation/Ticker.js +4 -1
- package/src/engine/ui/GUIEngine.d.ts.map +1 -1
- package/src/engine/ui/GUIEngine.js +6 -0
- package/src/shade/playground/particle_system/README.md +98 -0
- package/src/shade/playground/particle_system/index.html +124 -0
- package/src/shade/playground/particle_system/main.d.ts +2 -0
- package/src/shade/playground/particle_system/main.d.ts.map +1 -0
- package/src/shade/playground/particle_system/main.js +180 -0
- package/src/shade/playground/particle_system/particle_scene.d.ts +52 -0
- package/src/shade/playground/particle_system/particle_scene.d.ts.map +1 -0
- package/src/shade/playground/particle_system/particle_scene.js +553 -0
- package/src/shade/playground/skinned_mesh_soup/README.md +143 -0
- package/src/shade/playground/skinned_mesh_soup/index.html +202 -0
- package/src/shade/playground/skinned_mesh_soup/main.d.ts +7 -0
- package/src/shade/playground/skinned_mesh_soup/main.d.ts.map +1 -0
- package/src/shade/playground/skinned_mesh_soup/main.js +567 -0
- package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts +29 -0
- package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.d.ts.map +1 -0
- package/src/shade/playground/skinned_mesh_soup/verify_clone_vertices.js +428 -0
- package/src/shade/renderer/animation/GPUAnimationManager.d.ts.map +1 -1
- package/src/shade/renderer/animation/GPUAnimationManager.js +1958 -1796
- package/src/shade/renderer/animation/SKINNING_FRAME_PLAN.md +357 -0
- package/src/shade/renderer/animation/skin_bind_transform.d.ts +25 -0
- package/src/shade/renderer/animation/skin_bind_transform.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_bind_transform.js +33 -0
- package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts +67 -0
- package/src/shade/renderer/animation/skin_clip_scale_conflicts.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_clip_scale_conflicts.js +199 -0
- package/src/shade/renderer/animation/skin_frame_defect.d.ts +34 -0
- package/src/shade/renderer/animation/skin_frame_defect.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_frame_defect.js +132 -0
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts +24 -0
- package/src/shade/renderer/animation/skin_joint_matrix.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_joint_matrix.js +46 -0
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts +70 -0
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_normalize_mesh_frame.js +216 -0
- package/src/shade/renderer/animation/skin_test_fixtures.d.ts +86 -0
- package/src/shade/renderer/animation/skin_test_fixtures.d.ts.map +1 -0
- package/src/shade/renderer/animation/skin_test_fixtures.js +250 -0
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts +18 -4
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.d.ts.map +1 -1
- package/src/shade/renderer/animation/skinning/chunk_skin_blend_mesh_local.js +18 -4
- package/src/shade/renderer/particles/DESIGN.md +5 -5
- package/src/shade/renderer/particles/cull/chunk_particle_cull.d.ts.map +1 -1
- package/src/shade/renderer/particles/cull/chunk_particle_cull.js +3 -1
- package/src/shade/renderer/particles/data/chunk_particle_emitter.js +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts +43 -0
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.d.ts.map +1 -0
- package/src/shade/renderer/particles/graph/ParticleNodeDescription.js +44 -0
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts +33 -59
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/ParticleNodeRegistry.js +185 -118
- package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts +15 -10
- package/src/shade/renderer/particles/graph/compile_particle_graph.d.ts.map +1 -1
- package/src/shade/renderer/particles/graph/compile_particle_graph.js +205 -170
- package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts +44 -0
- package/src/shade/renderer/particles/graph/particle_graph_authoring.d.ts.map +1 -0
- package/src/shade/renderer/particles/graph/particle_graph_authoring.js +108 -0
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts +89 -0
- package/src/shade/renderer/particles/isa/InstructionStream.d.ts.map +1 -0
- package/src/shade/renderer/particles/isa/InstructionStream.js +162 -0
- package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts +1 -48
- package/src/shade/renderer/particles/isa/ParticleAssembler.d.ts.map +1 -1
- package/src/shade/renderer/particles/isa/ParticleAssembler.js +25 -114
- package/src/shade/renderer/particles/particle_prototype.d.ts.map +1 -1
- package/src/shade/renderer/particles/particle_prototype.js +181 -180
- package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts +4 -4
- package/src/shade/renderer/particles/runtime/create_particle_effect.d.ts.map +1 -1
- package/src/shade/renderer/particles/runtime/create_particle_effect.js +19 -19
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_curve_animation.js +4 -2
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts +4 -4
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_lighting.js +7 -5
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/chunk_particle_render_math.js +3 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_emit.js +3 -1
- package/src/shade/renderer/particles/shaders/shader_particle_finalize.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_finalize.js +5 -2
- package/src/shade/renderer/particles/shaders/shader_particle_render.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_render.js +9 -2
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.d.ts.map +1 -1
- package/src/shade/renderer/particles/shaders/shader_particle_simulate.js +3 -1
- package/src/shade/renderer/particles/vm/chunk_particle_vm.js +1 -1
- package/src/shade/renderer/scene/SkinnedMesh.d.ts +11 -0
- package/src/shade/renderer/scene/SkinnedMesh.d.ts.map +1 -1
- package/src/shade/renderer/scene/SkinnedMesh.js +11 -0
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts +33 -3
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.d.ts.map +1 -1
- package/src/shade/renderer/shader/chunk/geometry/quaternion/dual/chunk_dual_quat_from_m4.js +58 -37
- package/src/shade/renderer/particles/graph/ParticleGraph.d.ts +0 -54
- package/src/shade/renderer/particles/graph/ParticleGraph.d.ts.map +0 -1
- package/src/shade/renderer/particles/graph/ParticleGraph.js +0 -99
- package/src/shade/renderer/particles/prototypeParticleSystem.d.ts +0 -2
- package/src/shade/renderer/particles/prototypeParticleSystem.d.ts.map +0 -1
- package/src/shade/renderer/particles/prototypeParticleSystem.js +0 -448
|
@@ -0,0 +1,428 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read a clone's deformed vertex region back and hold it against an answer the CPU knows.
|
|
3
|
+
*
|
|
4
|
+
* The page can be looked at, and soup is not subtle. What looking at it cannot say is *which*
|
|
5
|
+
* vertices went wrong and in what pattern, and that is the whole difference between "skinning is
|
|
6
|
+
* broken" and a bug you can name: every vertex past the 64th of its meshlet is the workgroup stride
|
|
7
|
+
* loop, every vertex of some meshlets and none of others is the per-meshlet addressing, a scatter
|
|
8
|
+
* with no pattern is the joint palette.
|
|
9
|
+
*
|
|
10
|
+
* This runs in the **bind pose** — `?skin=bind`, no clip registered — because that is the one pose
|
|
11
|
+
* whose answer needs no GPU. At bind, `joint.global × inverse_bind` is the identity for every joint
|
|
12
|
+
* (verified on the CPU for Michelle: every one of her 19 106 vertices lands back on its rest
|
|
13
|
+
* position to the bit), so the blend collapses whatever the weights are and the shader's output is
|
|
14
|
+
* just its input under the mesh-local fold:
|
|
15
|
+
*
|
|
16
|
+
* ```
|
|
17
|
+
* clone_position = inverse(mesh.transform_global) × source_position
|
|
18
|
+
* ```
|
|
19
|
+
*
|
|
20
|
+
* — the leftmost factor of the glTF skinning formula, which `chunk_skin_blend_mesh_local` folds
|
|
21
|
+
* into each joint matrix so the rasterizer can re-apply `mesh.global` afterwards. Nothing about
|
|
22
|
+
* that expression involves the pose, so a disagreement here is the skinning *machinery* — the
|
|
23
|
+
* addressing, the layout, the matrix range — and not the animation.
|
|
24
|
+
*
|
|
25
|
+
* @author Alex Goldring
|
|
26
|
+
* @copyright Company Named Limited (c) 2026
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import { assert } from "../../../core/assert.js";
|
|
30
|
+
import {
|
|
31
|
+
MESHLET_METADATA_STRUCT
|
|
32
|
+
} from "../../renderer/geometry/meshlet/encoding/MESHLET_METADATA_STRUCT.js";
|
|
33
|
+
import {
|
|
34
|
+
GEOMETRY_METADATA_STRUCT
|
|
35
|
+
} from "../../renderer/geometry/GEOMETRY_METADATA_STRUCT.js";
|
|
36
|
+
import {
|
|
37
|
+
SKINNING_WORKGROUP_SIZE
|
|
38
|
+
} from "../../renderer/animation/skinning/shader_meshlet_apply_skinning.js";
|
|
39
|
+
|
|
40
|
+
/**
|
|
41
|
+
* Copy a slice of a GPU buffer back to the CPU.
|
|
42
|
+
*
|
|
43
|
+
* @param {GPUDevice} device
|
|
44
|
+
* @param {GPUBuffer} buffer
|
|
45
|
+
* @param {number} offset in bytes, a multiple of 4
|
|
46
|
+
* @param {number} size in bytes, a multiple of 4
|
|
47
|
+
* @returns {Promise<ArrayBuffer>}
|
|
48
|
+
*/
|
|
49
|
+
async function read_back(device, buffer, offset, size) {
|
|
50
|
+
const staging = device.createBuffer({
|
|
51
|
+
label: 'soup/readback',
|
|
52
|
+
size,
|
|
53
|
+
usage: GPUBufferUsage.COPY_DST | GPUBufferUsage.MAP_READ,
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
const encoder = device.createCommandEncoder({ label: 'soup/readback' });
|
|
57
|
+
|
|
58
|
+
encoder.copyBufferToBuffer(buffer, offset, staging, 0, size);
|
|
59
|
+
|
|
60
|
+
device.queue.submit([encoder.finish()]);
|
|
61
|
+
|
|
62
|
+
await staging.mapAsync(GPUMapMode.READ);
|
|
63
|
+
|
|
64
|
+
const copy = staging.getMappedRange().slice(0);
|
|
65
|
+
|
|
66
|
+
staging.unmap();
|
|
67
|
+
staging.destroy();
|
|
68
|
+
|
|
69
|
+
return copy;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Transform a point by a column-major 4×4.
|
|
74
|
+
*
|
|
75
|
+
* @param {ArrayLike<number>} m
|
|
76
|
+
* @param {number} x
|
|
77
|
+
* @param {number} y
|
|
78
|
+
* @param {number} z
|
|
79
|
+
* @returns {number[]}
|
|
80
|
+
*/
|
|
81
|
+
function transform_point(m, x, y, z) {
|
|
82
|
+
return [
|
|
83
|
+
m[0] * x + m[4] * y + m[8] * z + m[12],
|
|
84
|
+
m[1] * x + m[5] * y + m[9] * z + m[13],
|
|
85
|
+
m[2] * x + m[6] * y + m[10] * z + m[14],
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Invert a column-major 4×4 that is an affine transform — which every node transform in this engine
|
|
91
|
+
* is. General enough for a `T · R · S` with non-uniform scale, and not for a projection.
|
|
92
|
+
*
|
|
93
|
+
* @param {ArrayLike<number>} m
|
|
94
|
+
* @returns {Float64Array}
|
|
95
|
+
*/
|
|
96
|
+
function affine_inverse(m) {
|
|
97
|
+
const a = m[0], b = m[4], c = m[8];
|
|
98
|
+
const d = m[1], e = m[5], f = m[9];
|
|
99
|
+
const g = m[2], h = m[6], i = m[10];
|
|
100
|
+
|
|
101
|
+
const det = a * (e * i - f * h) - b * (d * i - f * g) + c * (d * h - e * g);
|
|
102
|
+
|
|
103
|
+
assert.notEqual(det, 0, 'transform is singular');
|
|
104
|
+
|
|
105
|
+
const s = 1 / det;
|
|
106
|
+
|
|
107
|
+
const r = new Float64Array(16);
|
|
108
|
+
|
|
109
|
+
r[0] = (e * i - f * h) * s;
|
|
110
|
+
r[1] = (f * g - d * i) * s;
|
|
111
|
+
r[2] = (d * h - e * g) * s;
|
|
112
|
+
|
|
113
|
+
r[4] = (c * h - b * i) * s;
|
|
114
|
+
r[5] = (a * i - c * g) * s;
|
|
115
|
+
r[6] = (b * g - a * h) * s;
|
|
116
|
+
|
|
117
|
+
r[8] = (b * f - c * e) * s;
|
|
118
|
+
r[9] = (c * d - a * f) * s;
|
|
119
|
+
r[10] = (a * e - b * d) * s;
|
|
120
|
+
|
|
121
|
+
const tx = m[12], ty = m[13], tz = m[14];
|
|
122
|
+
|
|
123
|
+
r[12] = -(r[0] * tx + r[4] * ty + r[8] * tz);
|
|
124
|
+
r[13] = -(r[1] * tx + r[5] * ty + r[9] * tz);
|
|
125
|
+
r[14] = -(r[2] * tx + r[6] * ty + r[10] * tz);
|
|
126
|
+
|
|
127
|
+
r[15] = 1;
|
|
128
|
+
|
|
129
|
+
return r;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Contiguous runs of a sorted list of integers, as `[first, last]` pairs.
|
|
134
|
+
*
|
|
135
|
+
* @param {number[]} values
|
|
136
|
+
* @returns {number[][]}
|
|
137
|
+
*/
|
|
138
|
+
function runs_of(values) {
|
|
139
|
+
const runs = [];
|
|
140
|
+
|
|
141
|
+
for (const value of values) {
|
|
142
|
+
const last = runs[runs.length - 1];
|
|
143
|
+
|
|
144
|
+
if (last !== undefined && last[1] === value - 1) {
|
|
145
|
+
last[1] = value;
|
|
146
|
+
} else {
|
|
147
|
+
runs.push([value, value]);
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
return runs;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
/**
|
|
155
|
+
* The clone's deformed vertices, against `inverse(mesh.global) × rest`.
|
|
156
|
+
*
|
|
157
|
+
* Reads the GPU's own answer to "where does this geometry id's data live" rather than reconstructing
|
|
158
|
+
* it from the host records: the geometry metadata row, then the clone's meshlet metadata rows, then
|
|
159
|
+
* the data those rows address. That is the same chain the rasterizer and the intersector walk, so a
|
|
160
|
+
* clone whose *addressing* is wrong is caught here rather than being read around.
|
|
161
|
+
*
|
|
162
|
+
* @param {object} options
|
|
163
|
+
* @param {Renderer} options.renderer
|
|
164
|
+
* @param {Scene} options.scene
|
|
165
|
+
* @param {SkinnedMesh} options.mesh
|
|
166
|
+
* @param {number} [options.tolerance] in mesh-local units. Since `register_skin` puts the mesh on
|
|
167
|
+
* the skin's bind transform, mesh-local *is* world scale — a metre is a metre here, and at
|
|
168
|
+
* bind the clone should hold the source's own positions. `1e-4` is f32 headroom over a
|
|
169
|
+
* chain — a matrix product, a quaternion conversion and a screw motion, all at single
|
|
170
|
+
* precision — against a smallest real violation of `1.6`, so there are four orders between
|
|
171
|
+
* the two. It was `5e-3` while `dual_quat_from_m4` amplified a near-identity matrix's error
|
|
172
|
+
* by its square root; `SKINNING_FRAME_PLAN.md` §9 took that out, and the same measurement on
|
|
173
|
+
* the CPU now reads zero at bind.
|
|
174
|
+
* @returns {Promise<object>}
|
|
175
|
+
*/
|
|
176
|
+
export async function verify_clone_vertices({ renderer, scene, mesh, tolerance = 1e-4 }) {
|
|
177
|
+
assert.defined(mesh, 'mesh');
|
|
178
|
+
assert.equal(mesh.isSkinnedMesh, true, 'mesh.isSkinnedMesh !== true');
|
|
179
|
+
|
|
180
|
+
const graphics = renderer.graphics;
|
|
181
|
+
const device = graphics.device;
|
|
182
|
+
const geometries = graphics.geometries;
|
|
183
|
+
|
|
184
|
+
const scene_context = renderer.scenes.obtain(scene);
|
|
185
|
+
|
|
186
|
+
const geometry_index = scene_context.skinning.obtain_geometry_index(mesh);
|
|
187
|
+
|
|
188
|
+
// ---- where the clone's meshlets are, as the GPU has it -------------------------------------
|
|
189
|
+
|
|
190
|
+
const geometry_row = new DataView(await read_back(
|
|
191
|
+
device,
|
|
192
|
+
geometries.buffer_metadata,
|
|
193
|
+
geometry_index * GEOMETRY_METADATA_STRUCT.size,
|
|
194
|
+
GEOMETRY_METADATA_STRUCT.size
|
|
195
|
+
));
|
|
196
|
+
|
|
197
|
+
const meshlets_address = geometry_row.getUint32(
|
|
198
|
+
GEOMETRY_METADATA_STRUCT.get('meshlets_address').offset, true
|
|
199
|
+
);
|
|
200
|
+
const meshlets_count = geometry_row.getUint32(
|
|
201
|
+
GEOMETRY_METADATA_STRUCT.get('meshlets_count').offset, true
|
|
202
|
+
);
|
|
203
|
+
|
|
204
|
+
const source_batch = mesh.geometry.meshlets;
|
|
205
|
+
|
|
206
|
+
if (meshlets_count !== source_batch.count) {
|
|
207
|
+
return {
|
|
208
|
+
geometry_index,
|
|
209
|
+
verdict: `FAIL — the clone's geometry row claims ${meshlets_count} meshlets, `
|
|
210
|
+
+ `its source has ${source_batch.count}`,
|
|
211
|
+
};
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
const meshlet_stride = MESHLET_METADATA_STRUCT.aligned_size;
|
|
215
|
+
|
|
216
|
+
const clone_metadata = new DataView(await read_back(
|
|
217
|
+
device,
|
|
218
|
+
geometries.meshlets.buffer_metadata,
|
|
219
|
+
meshlets_address * meshlet_stride,
|
|
220
|
+
meshlets_count * meshlet_stride
|
|
221
|
+
));
|
|
222
|
+
|
|
223
|
+
const ADDRESS_OFFSET = MESHLET_METADATA_STRUCT.get('address').offset;
|
|
224
|
+
const VERTEX_COUNT_OFFSET = MESHLET_METADATA_STRUCT.get('vertex_count').offset;
|
|
225
|
+
const PRIMITIVE_COUNT_OFFSET = MESHLET_METADATA_STRUCT.get('primitive_count').offset;
|
|
226
|
+
|
|
227
|
+
// ---- and where the source's are, to say the clone is somewhere else --------------------------
|
|
228
|
+
|
|
229
|
+
/*
|
|
230
|
+
A clone is a fresh vertex region plus a patch pass that adds `clone.data.offset -
|
|
231
|
+
source.data.offset` to every one of its meshlet records. If that patch does not land, the
|
|
232
|
+
clone's records still address the *source's* data: the skinning pass then deforms the shared
|
|
233
|
+
rest-pose vertices in place, every instance of the model deforms on top of every other, and the
|
|
234
|
+
source no rasterizer ever wanted deformed goes with them. The addresses differing by one
|
|
235
|
+
constant is the whole contract, and it is checkable before a single vertex is compared.
|
|
236
|
+
*/
|
|
237
|
+
const source_record = geometries.get_metadata(mesh.geometry);
|
|
238
|
+
|
|
239
|
+
if (source_record === undefined) {
|
|
240
|
+
return {
|
|
241
|
+
geometry_index,
|
|
242
|
+
verdict: `FAIL — the mesh's source geometry is not registered, so there is nothing for `
|
|
243
|
+
+ `its clone to be a clone of. The scene has not been built with this mesh in it.`,
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
const source_metadata = new DataView(await read_back(
|
|
248
|
+
device,
|
|
249
|
+
geometries.meshlets.buffer_metadata,
|
|
250
|
+
source_record.meshlets.metadata.offset * meshlet_stride,
|
|
251
|
+
meshlets_count * meshlet_stride
|
|
252
|
+
));
|
|
253
|
+
|
|
254
|
+
// ---- the clone's vertex data, in one read ---------------------------------------------------
|
|
255
|
+
|
|
256
|
+
/*
|
|
257
|
+
Position is the first attribute of the section and is never collapsed to a shared slot (see
|
|
258
|
+
ENCODED_ORDERED_ATTRIBUTES and MESHLET_ATTRIBUTE_COMPRESSION_FLAGS), so a meshlet's positions
|
|
259
|
+
start where its attribute section does and the furthest word this needs is the last position
|
|
260
|
+
word of the furthest meshlet. That is a span, not a set of holes, so it comes back in one copy
|
|
261
|
+
rather than in `meshlets_count` of them — two hundred round trips through `mapAsync` is a
|
|
262
|
+
diagnostic nobody runs twice.
|
|
263
|
+
*/
|
|
264
|
+
const position_word_of = new Uint32Array(meshlets_count);
|
|
265
|
+
|
|
266
|
+
let data_low = Infinity;
|
|
267
|
+
let data_high = 0;
|
|
268
|
+
|
|
269
|
+
/** every distinct `clone address − source address`; one entry is the contract */
|
|
270
|
+
const address_deltas = new Set();
|
|
271
|
+
|
|
272
|
+
for (let m = 0; m < meshlets_count; m++) {
|
|
273
|
+
const row = m * meshlet_stride;
|
|
274
|
+
|
|
275
|
+
const address = clone_metadata.getUint32(row + ADDRESS_OFFSET, true);
|
|
276
|
+
const primitive_count = clone_metadata.getUint32(row + PRIMITIVE_COUNT_OFFSET, true);
|
|
277
|
+
const vertex_count = clone_metadata.getUint32(row + VERTEX_COUNT_OFFSET, true);
|
|
278
|
+
|
|
279
|
+
address_deltas.add(address - source_metadata.getUint32(row + ADDRESS_OFFSET, true));
|
|
280
|
+
|
|
281
|
+
const position_word = address + ((primitive_count * 3 + 3) >> 2);
|
|
282
|
+
|
|
283
|
+
position_word_of[m] = position_word;
|
|
284
|
+
|
|
285
|
+
data_low = Math.min(data_low, position_word);
|
|
286
|
+
data_high = Math.max(data_high, position_word + vertex_count * 3);
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
if (address_deltas.size !== 1 || address_deltas.has(0)) {
|
|
290
|
+
return {
|
|
291
|
+
geometry_index,
|
|
292
|
+
address_deltas: [...address_deltas].slice(0, 8),
|
|
293
|
+
verdict: address_deltas.has(0) && address_deltas.size === 1
|
|
294
|
+
? `FAIL — every one of the clone's ${meshlets_count} meshlet records addresses the `
|
|
295
|
+
+ `source's vertex data. The address patch did not run, so the skinning pass is `
|
|
296
|
+
+ `deforming the shared rest-pose region in place.`
|
|
297
|
+
: `FAIL — the clone's meshlet records sit at ${address_deltas.size} different `
|
|
298
|
+
+ `offsets from the source's; a clone is one contiguous region and its patch is `
|
|
299
|
+
+ `one delta.`,
|
|
300
|
+
};
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const data = new Float32Array(await read_back(
|
|
304
|
+
device,
|
|
305
|
+
geometries.meshlets.buffer_data,
|
|
306
|
+
data_low * 4,
|
|
307
|
+
(data_high - data_low) * 4
|
|
308
|
+
));
|
|
309
|
+
|
|
310
|
+
// ---- the answer the CPU knows ---------------------------------------------------------------
|
|
311
|
+
|
|
312
|
+
const to_mesh_local = affine_inverse(mesh.transform_global.matrix);
|
|
313
|
+
|
|
314
|
+
// ---- and the comparison ---------------------------------------------------------------------
|
|
315
|
+
|
|
316
|
+
let vertices = 0;
|
|
317
|
+
let wrong = 0;
|
|
318
|
+
let worst = 0;
|
|
319
|
+
let worst_where = null;
|
|
320
|
+
|
|
321
|
+
/** meshlets with at least one wrong vertex */
|
|
322
|
+
const wrong_meshlets = [];
|
|
323
|
+
|
|
324
|
+
/** per-vertex-slot tally: how many meshlets got slot `v` wrong */
|
|
325
|
+
const wrong_by_slot = new Map();
|
|
326
|
+
|
|
327
|
+
const examples = [];
|
|
328
|
+
|
|
329
|
+
for (let m = 0; m < meshlets_count; m++) {
|
|
330
|
+
const source_header = source_batch.read_header(m);
|
|
331
|
+
const source = source_batch.read_meshlet(m);
|
|
332
|
+
|
|
333
|
+
const row = m * meshlet_stride;
|
|
334
|
+
|
|
335
|
+
const clone_vertex_count = clone_metadata.getUint32(row + VERTEX_COUNT_OFFSET, true);
|
|
336
|
+
const clone_primitive_count = clone_metadata.getUint32(row + PRIMITIVE_COUNT_OFFSET, true);
|
|
337
|
+
|
|
338
|
+
if (clone_vertex_count !== source_header.vertex_count
|
|
339
|
+
|| clone_primitive_count !== source_header.primitive_count) {
|
|
340
|
+
return {
|
|
341
|
+
geometry_index,
|
|
342
|
+
verdict: `FAIL — clone meshlet ${m} says `
|
|
343
|
+
+ `${clone_vertex_count} vertices / ${clone_primitive_count} triangles, `
|
|
344
|
+
+ `its source says ${source_header.vertex_count} / ${source_header.primitive_count}. `
|
|
345
|
+
+ `The metadata copy or its address patch is wrong, before any skinning ran.`,
|
|
346
|
+
};
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const positions = data.subarray(
|
|
350
|
+
position_word_of[m] - data_low,
|
|
351
|
+
position_word_of[m] - data_low + clone_vertex_count * 3
|
|
352
|
+
);
|
|
353
|
+
|
|
354
|
+
let meshlet_wrong = 0;
|
|
355
|
+
|
|
356
|
+
for (let v = 0; v < clone_vertex_count; v++) {
|
|
357
|
+
vertices++;
|
|
358
|
+
|
|
359
|
+
const expected = transform_point(
|
|
360
|
+
to_mesh_local,
|
|
361
|
+
source.attribute_position[v * 3],
|
|
362
|
+
source.attribute_position[v * 3 + 1],
|
|
363
|
+
source.attribute_position[v * 3 + 2],
|
|
364
|
+
);
|
|
365
|
+
|
|
366
|
+
const error = Math.max(
|
|
367
|
+
Math.abs(positions[v * 3] - expected[0]),
|
|
368
|
+
Math.abs(positions[v * 3 + 1] - expected[1]),
|
|
369
|
+
Math.abs(positions[v * 3 + 2] - expected[2]),
|
|
370
|
+
);
|
|
371
|
+
|
|
372
|
+
if (error > worst) {
|
|
373
|
+
worst = error;
|
|
374
|
+
worst_where = { meshlet: m, vertex: v };
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (error > tolerance) {
|
|
378
|
+
wrong++;
|
|
379
|
+
meshlet_wrong++;
|
|
380
|
+
|
|
381
|
+
wrong_by_slot.set(v, (wrong_by_slot.get(v) ?? 0) + 1);
|
|
382
|
+
|
|
383
|
+
if (examples.length < 8) {
|
|
384
|
+
examples.push({
|
|
385
|
+
meshlet: m,
|
|
386
|
+
vertex: v,
|
|
387
|
+
got: [positions[v * 3], positions[v * 3 + 1], positions[v * 3 + 2]],
|
|
388
|
+
expected,
|
|
389
|
+
});
|
|
390
|
+
}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
|
|
394
|
+
if (meshlet_wrong > 0) {
|
|
395
|
+
wrong_meshlets.push(m);
|
|
396
|
+
}
|
|
397
|
+
}
|
|
398
|
+
|
|
399
|
+
// The two shapes worth telling apart, named rather than left to be read out of the runs: a
|
|
400
|
+
// stride-loop bug wrongs every slot at or past the workgroup size and nothing below it, and an
|
|
401
|
+
// addressing bug wrongs whole meshlets.
|
|
402
|
+
const slots = [...wrong_by_slot.keys()].sort((a, b) => a - b);
|
|
403
|
+
|
|
404
|
+
const lowest_wrong_slot = slots.length > 0 ? slots[0] : null;
|
|
405
|
+
|
|
406
|
+
return {
|
|
407
|
+
geometry_index,
|
|
408
|
+
meshlets: meshlets_count,
|
|
409
|
+
address_delta: [...address_deltas][0],
|
|
410
|
+
vertices,
|
|
411
|
+
wrong,
|
|
412
|
+
worst,
|
|
413
|
+
worst_where,
|
|
414
|
+
wrong_meshlets: wrong_meshlets.length,
|
|
415
|
+
wrong_meshlet_runs: runs_of(wrong_meshlets).slice(0, 20),
|
|
416
|
+
lowest_wrong_slot,
|
|
417
|
+
highest_correct_slot: slots.length > 0 ? lowest_wrong_slot - 1 : null,
|
|
418
|
+
wrong_slot_runs: runs_of(slots).slice(0, 20),
|
|
419
|
+
examples,
|
|
420
|
+
verdict: wrong === 0
|
|
421
|
+
? `pass — all ${vertices} of the clone's vertices are the bind pose, worst error ${worst}`
|
|
422
|
+
: `FAIL — ${wrong} of ${vertices} vertices in ${wrong_meshlets.length} of `
|
|
423
|
+
+ `${meshlets_count} meshlets are not the bind pose (worst ${worst}); `
|
|
424
|
+
+ `lowest wrong vertex slot ${lowest_wrong_slot}. `
|
|
425
|
+
+ `A lowest slot of ${SKINNING_WORKGROUP_SIZE} is the stride loop; whole meshlets `
|
|
426
|
+
+ `wrong is addressing; a uniform factor is the frame.`,
|
|
427
|
+
};
|
|
428
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"GPUAnimationManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/GPUAnimationManager.js"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"GPUAnimationManager.d.ts","sourceRoot":"","sources":["../../../../../src/shade/renderer/animation/GPUAnimationManager.js"],"names":[],"mappings":"AAiIA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AACH;IA6OI;;;;OAIG;IACH,sCAHW,MAAM,kCAgBhB;IAED;;OAEG;IACH,4BAEC;IAED;;;;;;;;;;;;OAYG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,qCAEC;IAED;;;;;;OAMG;IACH,gCAEC;IAED;;;;;;;;;;OAUG;IACH,mCAEC;IAED;;;;;;;;;;;;OAYG;IACH,4CAFW,MAAM,QA6BhB;IAED;;;;;OAKG;IACH,kBAHW,MAAM,GACJ;QAAC,gBAAgB,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAC,CAK3D;IAED;;;;;;;;;;;;OAYG;IACH,gCAHW,MAAM,GACJ,MAAM,CAOlB;IAED;;;;;;;;;;;OAWG;IACH,kCAFa,MAAM,CA+BlB;IAED;;;;;;;;;;;;;;OAcG;IACH,oBAJW;QAAE,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAC;QAAC,CAAC,CAAC,EAAE,MAAM,CAAA;KAAE,GAEhD,MAAM,CA2BlB;IAED;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH;eAPc,MAAM;qBACA,MAAM;mBACR,MAAM;kBACP,MAAM;YAAE,KAAK,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAC;YAAC,QAAQ,EAAE,MAAM,CAAA;SAAE,CAAC;QAEhE,MAAM,CAkElB;IAgCD;;;;;;;;;;;;;;;;;OAiBG;IACH,kBAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;OAQG;IACH,2BAHW,MAAM,QACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;OAcG;IACH,6BAHW,MAAM,UACN,MAAM,QAKhB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,mBAHW,MAAM,SACN,MAAM,QAShB;IAED;;;;;;;OAOG;IACH,qBAHW,MAAM,SACN,MAAM,QAShB;IAED;;;;;;;;;;;OAWG;IACH,eAFW,MAAM,QAShB;IAED;;;;;;;;;OASG;IACH,cAFW,MAAM,QAShB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA4BG;IACH;gBAJe,MAAM;YAAE,IAAI,EAAE,MAAM,CAAC;YAAC,YAAY,EAAE,YAAY,GAAG,MAAM,EAAE,CAAA;SAAE,CAAC;QAEhE,MAAM,CAoElB;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,2BAFa,MAAM,CA0FlB;IAyLD;;;;;;;;;;;;;;;;;;;;;;;;OAwBG;IACH,yCAFa,MAAM,CAsElB;IAwDD;;;;;;;;;;;;;;;OAeG;IACH,yBAFW,MAAM,QAoChB;IAED;;;;;;;;;;;;;;;;OAgBG;IACH,yBAFW,MAAM,QA6ChB;IAqHD;;;;;;;;;;;;;;;;;;;OAmBG;IACH,8CAIC;IAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;OA0BG;IACH,iEA2CC;IAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;OA6BG;IACH,kDAFW,MAAM,QAgChB;IA8FD,gBAYC;;CACJ;4BAn6D2B,gCAAgC"}
|