zincjs 2.4.1-beta.5 → 2.4.1-beta.7
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/build/zinc.js +5 -5
- package/build/zinc.js.map +1 -1
- package/package.json +1 -1
- package/src/primitives/glyphset.js +15 -4
- package/src/renderer.js +8 -4
- package/src/tsl/glyphTransform.js +107 -55
package/package.json
CHANGED
|
@@ -3,7 +3,8 @@ import { Glyph } from './glyph';
|
|
|
3
3
|
import { ZincObject } from './zincObject';
|
|
4
4
|
import { updateWorldMatrixFromAncestors } from '../utilities';
|
|
5
5
|
import { JSONLoader } from '../loaders/JSONLoader';
|
|
6
|
-
import { createGlyphTransformCompute, dispatchAndReadbackGlyphTransform,
|
|
6
|
+
import { createGlyphTransformCompute, dispatchAndReadbackGlyphTransform, isGlyphComputeSupported,
|
|
7
|
+
readbackGlyphTransform } from '../tsl/glyphTransform';
|
|
7
8
|
import { createGlyphInstancedMaterial } from '../tsl/glyphRenderMaterial';
|
|
8
9
|
|
|
9
10
|
/**
|
|
@@ -176,7 +177,9 @@ const Glyphset = function () {
|
|
|
176
177
|
offset = glyphsetData.metadata.offset;
|
|
177
178
|
scaleFactors = glyphsetData.metadata.scale_factors;
|
|
178
179
|
glyphCompute = undefined;
|
|
179
|
-
|
|
180
|
+
//The CPU path is used when the backend does not support GPU compute
|
|
181
|
+
if (morphVertices && numberOfTimeSteps > 1 && positions && positions["0"] &&
|
|
182
|
+
isGlyphComputeSupported()) {
|
|
180
183
|
const baseCount = positions["0"].length / 3;
|
|
181
184
|
try {
|
|
182
185
|
glyphCompute = createGlyphTransformCompute({
|
|
@@ -699,6 +702,15 @@ const Glyphset = function () {
|
|
|
699
702
|
}
|
|
700
703
|
};
|
|
701
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Check if the glyph transforms are animated on the GPU.
|
|
707
|
+
*
|
|
708
|
+
* @return {Boolean}
|
|
709
|
+
*/
|
|
710
|
+
this.isUsingGPUCompute = () => {
|
|
711
|
+
return glyphCompute !== undefined;
|
|
712
|
+
}
|
|
713
|
+
|
|
702
714
|
/**
|
|
703
715
|
* Get the assigned label for instance at index
|
|
704
716
|
*/
|
|
@@ -1042,8 +1054,7 @@ const Glyphset = function () {
|
|
|
1042
1054
|
const renderer = this.region?.getScene?.()?.getRenderer?.();
|
|
1043
1055
|
if (!renderer) return;
|
|
1044
1056
|
renderer.compute(glyphCompute.compute);
|
|
1045
|
-
const colorResult =
|
|
1046
|
-
await renderer.getArrayBufferAsync(glyphCompute.outputs.color.value));
|
|
1057
|
+
const colorResult = (await readbackGlyphTransform(renderer, glyphCompute)).color;
|
|
1047
1058
|
// setColorAt(0, ...) is only here to lazily allocate instanceColor the
|
|
1048
1059
|
// same way the stock InstancedMesh API would; the loop below overwrites
|
|
1049
1060
|
// every instance (including 0) with the real snapshot right after.
|
package/src/renderer.js
CHANGED
|
@@ -3,6 +3,7 @@ import { ResizeSensor } from 'css-element-queries';
|
|
|
3
3
|
import { Scene } from './scene';
|
|
4
4
|
import { onRenderRequest, offRenderRequest } from './renderRequests';
|
|
5
5
|
import { setTextPixelsPerUnit } from './textSprite';
|
|
6
|
+
import { setGlyphComputeSupported } from './tsl/glyphTransform';
|
|
6
7
|
/**
|
|
7
8
|
* Create a Zinc 3D renderer in the container provided.
|
|
8
9
|
* The primary function of a Zinc 3D renderer is to display the current
|
|
@@ -152,10 +153,10 @@ const Renderer = function (containerIn) {
|
|
|
152
153
|
//WebGPURenderer otherwise requests a device with the default
|
|
153
154
|
//maxTextureArrayLayers (256 on many adapters), which is too low for
|
|
154
155
|
//some currnetly sypported scaffold, and the default
|
|
155
|
-
//maxStorageBuffersPerShaderStage (8 on many adapters)
|
|
156
|
-
//
|
|
157
|
-
//
|
|
158
|
-
//for both.
|
|
156
|
+
//maxStorageBuffersPerShaderStage (8 on many adapters). The glyph
|
|
157
|
+
//transform compute pass only needs two storage buffers, the higher
|
|
158
|
+
//limit is kept as headroom. We will request the highest possible on
|
|
159
|
+
//the device for both.
|
|
159
160
|
if (parameters.device === undefined && parameters.requiredLimits === undefined &&
|
|
160
161
|
typeof navigator !== 'undefined' && navigator.gpu) {
|
|
161
162
|
try {
|
|
@@ -173,6 +174,9 @@ const Renderer = function (containerIn) {
|
|
|
173
174
|
}
|
|
174
175
|
renderer = new THREE.WebGPURenderer(parameters);
|
|
175
176
|
await renderer.init();
|
|
177
|
+
//GPU glyph animation requires the WebGPU backend, the WebGL fallback
|
|
178
|
+
//cannot index storage buffers freely.
|
|
179
|
+
setGlyphComputeSupported(renderer.backend?.isWebGPUBackend === true);
|
|
176
180
|
|
|
177
181
|
//renderer = new THREE.WebGLRenderer(parameters);
|
|
178
182
|
if (container !== undefined) {
|
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
int,
|
|
8
8
|
float,
|
|
9
9
|
vec3,
|
|
10
|
+
vec4,
|
|
10
11
|
uniform,
|
|
11
12
|
mix,
|
|
12
13
|
select,
|
|
@@ -28,6 +29,9 @@ const repeatModeToInt = (repeat_mode) => {
|
|
|
28
29
|
}
|
|
29
30
|
}
|
|
30
31
|
|
|
32
|
+
//Order of the vec4 fields of each glyph instance in the output buffer
|
|
33
|
+
const OUTPUT_FIELDS = { position: 0, axis1: 1, axis2: 2, axis3: 3, color: 4 };
|
|
34
|
+
|
|
31
35
|
const multiplierForRepeatMode = (repeat_mode) => {
|
|
32
36
|
if (repeat_mode == "AXES_2D" || repeat_mode == "MIRROR") return 2;
|
|
33
37
|
if (repeat_mode == "AXES_3D") return 3;
|
|
@@ -66,9 +70,10 @@ const multiplierForRepeatMode = (repeat_mode) => {
|
|
|
66
70
|
* @param {Array<number>} params.scaleFactors
|
|
67
71
|
* @returns {Object} { uniforms, compute, outputs } - update
|
|
68
72
|
* uniforms.bottomFrame/topFrame/proportion.value then pass `compute` to
|
|
69
|
-
* renderer.compute()/computeAsync(); read results back
|
|
70
|
-
* outputs.
|
|
71
|
-
*
|
|
73
|
+
* renderer.compute()/computeAsync(); read results back with
|
|
74
|
+
* readbackGlyphTransform(). outputs.buffer is the interleaved storage
|
|
75
|
+
* buffer (outputs.stride vec4 per instance), outputs.position/axis1/
|
|
76
|
+
* axis2/axis3/color provide element(index) accessors for materials.
|
|
72
77
|
*/
|
|
73
78
|
function createGlyphTransformCompute({
|
|
74
79
|
baseCount,
|
|
@@ -87,18 +92,38 @@ function createGlyphTransformCompute({
|
|
|
87
92
|
}) {
|
|
88
93
|
const multiplier = multiplierForRepeatMode(repeat_mode);
|
|
89
94
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
const
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
const
|
|
99
|
-
const
|
|
100
|
-
const
|
|
101
|
-
const
|
|
95
|
+
// All inputs are interleaved into a single storage buffer and all outputs
|
|
96
|
+
// into another, one vec4 per field. Separate buffers per field needed up
|
|
97
|
+
// to 11 storage buffers in the compute stage, more than some devices
|
|
98
|
+
// support (e.g. 10 on iOS). vec4 also avoids three.js padding vec3
|
|
99
|
+
// storage data into an extra copy.
|
|
100
|
+
const hasColor = !!colorData;
|
|
101
|
+
const inputSources = [positionsData, axis1Data, axis2Data, axis3Data, scaleData];
|
|
102
|
+
if (hasColor) inputSources.push(colorData);
|
|
103
|
+
const inputStride = inputSources.length;
|
|
104
|
+
const outputStride = hasColor ? OUTPUT_FIELDS.color + 1 : OUTPUT_FIELDS.axis3 + 1;
|
|
105
|
+
const recordCount = positionsData.length / 3;
|
|
106
|
+
const inputData = new Float32Array(recordCount * inputStride * 4);
|
|
107
|
+
for (let record = 0; record < recordCount; record++) {
|
|
108
|
+
for (let field = 0; field < inputStride; field++) {
|
|
109
|
+
const source = inputSources[field];
|
|
110
|
+
const target = (record * inputStride + field) * 4;
|
|
111
|
+
inputData[target] = source[record * 3];
|
|
112
|
+
inputData[target + 1] = source[record * 3 + 1];
|
|
113
|
+
inputData[target + 2] = source[record * 3 + 2];
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
const inputBuffer = attributeArray(inputData, 'vec4');
|
|
117
|
+
const outputBuffer = attributeArray(outputCount * outputStride, 'vec4');
|
|
118
|
+
|
|
119
|
+
const readInput = (recordIdx, field) =>
|
|
120
|
+
inputBuffer.element(recordIdx.mul(int(inputStride)).add(int(field))).xyz;
|
|
121
|
+
const writeOutput = (outIdx, field, value) =>
|
|
122
|
+
outputBuffer.element(outIdx.mul(int(outputStride)).add(int(field))).assign(vec4(value, 0));
|
|
123
|
+
//Accessor used by the render material to read an output field
|
|
124
|
+
const outputField = (field) => ({
|
|
125
|
+
element: (idx) => outputBuffer.element(int(idx).mul(int(outputStride)).add(int(field))).xyz,
|
|
126
|
+
});
|
|
102
127
|
|
|
103
128
|
const uniforms = {
|
|
104
129
|
bottomFrame: uniform(0, 'int'),
|
|
@@ -121,11 +146,11 @@ function createGlyphTransformCompute({
|
|
|
121
146
|
const topIdx = uniforms.topFrame.mul(int(baseCount)).add(inputIdx);
|
|
122
147
|
|
|
123
148
|
// Matches updateMorphGlyphsets(): bottom*proportion + top*(1-proportion).
|
|
124
|
-
const point = mix(
|
|
125
|
-
const axis1 = mix(
|
|
126
|
-
const axis2 = mix(
|
|
127
|
-
const axis3 = mix(
|
|
128
|
-
const scale = mix(
|
|
149
|
+
const point = mix(readInput(topIdx, 0), readInput(bottomIdx, 0), uniforms.proportion);
|
|
150
|
+
const axis1 = mix(readInput(topIdx, 1), readInput(bottomIdx, 1), uniforms.proportion);
|
|
151
|
+
const axis2 = mix(readInput(topIdx, 2), readInput(bottomIdx, 2), uniforms.proportion);
|
|
152
|
+
const axis3 = mix(readInput(topIdx, 3), readInput(bottomIdx, 3), uniforms.proportion);
|
|
153
|
+
const scale = mix(readInput(topIdx, 4), readInput(bottomIdx, 4), uniforms.proportion);
|
|
129
154
|
|
|
130
155
|
// NOTE: each branch below assigns straight into the output storage
|
|
131
156
|
// buffer elements (outPosition.element(outIdx).assign(...) etc.),
|
|
@@ -175,10 +200,10 @@ function createGlyphTransformCompute({
|
|
|
175
200
|
a3.assign(a3.negate());
|
|
176
201
|
});
|
|
177
202
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
203
|
+
writeOutput(outIdx, OUTPUT_FIELDS.position, p);
|
|
204
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis1, a1);
|
|
205
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis2, a2);
|
|
206
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis3, a3);
|
|
182
207
|
|
|
183
208
|
}).Else(() => {
|
|
184
209
|
|
|
@@ -220,16 +245,16 @@ function createGlyphTransformCompute({
|
|
|
220
245
|
.div(max(mag2, 1e-8));
|
|
221
246
|
const finalAxis2 = select(mag2.greaterThan(0), axis2Raw.mul(scaling2), axis2Raw);
|
|
222
247
|
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
248
|
+
writeOutput(outIdx, OUTPUT_FIELDS.position, finalPoint);
|
|
249
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis1, finalAxis1);
|
|
250
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis2, finalAxis2);
|
|
251
|
+
writeOutput(outIdx, OUTPUT_FIELDS.axis3, finalAxis3);
|
|
227
252
|
|
|
228
253
|
});
|
|
229
254
|
|
|
230
|
-
if (
|
|
231
|
-
const color = mix(
|
|
232
|
-
|
|
255
|
+
if (hasColor) {
|
|
256
|
+
const color = mix(readInput(topIdx, 5), readInput(bottomIdx, 5), uniforms.proportion);
|
|
257
|
+
writeOutput(outIdx, OUTPUT_FIELDS.color, color);
|
|
233
258
|
}
|
|
234
259
|
|
|
235
260
|
} )().compute( outputCount );
|
|
@@ -238,11 +263,14 @@ function createGlyphTransformCompute({
|
|
|
238
263
|
uniforms,
|
|
239
264
|
compute: computeFn,
|
|
240
265
|
outputs: {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
266
|
+
buffer: outputBuffer,
|
|
267
|
+
stride: outputStride,
|
|
268
|
+
count: outputCount,
|
|
269
|
+
position: outputField(OUTPUT_FIELDS.position),
|
|
270
|
+
axis1: outputField(OUTPUT_FIELDS.axis1),
|
|
271
|
+
axis2: outputField(OUTPUT_FIELDS.axis2),
|
|
272
|
+
axis3: outputField(OUTPUT_FIELDS.axis3),
|
|
273
|
+
color: hasColor ? outputField(OUTPUT_FIELDS.color) : undefined,
|
|
246
274
|
},
|
|
247
275
|
};
|
|
248
276
|
}
|
|
@@ -264,27 +292,30 @@ function createGlyphTransformCompute({
|
|
|
264
292
|
* @param {THREE.WebGPURenderer} renderer
|
|
265
293
|
* @param {Object} glyphCompute - return value of createGlyphTransformCompute().
|
|
266
294
|
* @returns {Promise<Object>} { position, axis1, axis2, axis3, color } -
|
|
267
|
-
* plain Float32Arrays
|
|
268
|
-
* present when the glyphset has colours).
|
|
295
|
+
* plain Float32Arrays with a 4 float stride per output glyph instance
|
|
296
|
+
* (color only present when the glyphset has colours).
|
|
269
297
|
*/
|
|
270
298
|
async function readbackGlyphTransform(renderer, glyphCompute) {
|
|
271
|
-
const {
|
|
272
|
-
//
|
|
273
|
-
|
|
274
|
-
//
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
299
|
+
const { buffer, stride, count, color } = glyphCompute.outputs;
|
|
300
|
+
//A single readback of the interleaved output buffer
|
|
301
|
+
const data = new Float32Array(await renderer.getArrayBufferAsync(buffer.value));
|
|
302
|
+
//Split into one array per field, each with a 4 float stride per instance
|
|
303
|
+
const extract = (field) => {
|
|
304
|
+
const result = new Float32Array(count * 4);
|
|
305
|
+
for (let i = 0; i < count; i++) {
|
|
306
|
+
const source = (i * stride + field) * 4;
|
|
307
|
+
result[i * 4] = data[source];
|
|
308
|
+
result[i * 4 + 1] = data[source + 1];
|
|
309
|
+
result[i * 4 + 2] = data[source + 2];
|
|
310
|
+
}
|
|
311
|
+
return result;
|
|
312
|
+
};
|
|
282
313
|
return {
|
|
283
|
-
position:
|
|
284
|
-
axis1:
|
|
285
|
-
axis2:
|
|
286
|
-
axis3:
|
|
287
|
-
color:
|
|
314
|
+
position: extract(OUTPUT_FIELDS.position),
|
|
315
|
+
axis1: extract(OUTPUT_FIELDS.axis1),
|
|
316
|
+
axis2: extract(OUTPUT_FIELDS.axis2),
|
|
317
|
+
axis3: extract(OUTPUT_FIELDS.axis3),
|
|
318
|
+
color: color ? extract(OUTPUT_FIELDS.color) : undefined,
|
|
288
319
|
};
|
|
289
320
|
}
|
|
290
321
|
|
|
@@ -303,4 +334,25 @@ async function dispatchAndReadbackGlyphTransform(renderer, glyphCompute) {
|
|
|
303
334
|
return readbackGlyphTransform(renderer, glyphCompute);
|
|
304
335
|
}
|
|
305
336
|
|
|
306
|
-
|
|
337
|
+
/*
|
|
338
|
+
* The GPU glyph transforms read storage buffers at arbitrary indices, which
|
|
339
|
+
* the WebGL fallback backend of WebGPURenderer does not support (it turns
|
|
340
|
+
* them into plain vertex attributes). The renderer sets this when it is
|
|
341
|
+
* initialised, glyphsets use the CPU animation path when it is false.
|
|
342
|
+
*/
|
|
343
|
+
let glyphComputeSupported = true;
|
|
344
|
+
|
|
345
|
+
const setGlyphComputeSupported = (flag) => {
|
|
346
|
+
glyphComputeSupported = flag ? true : false;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
const isGlyphComputeSupported = () => glyphComputeSupported;
|
|
350
|
+
|
|
351
|
+
export {
|
|
352
|
+
createGlyphTransformCompute,
|
|
353
|
+
dispatchAndReadbackGlyphTransform,
|
|
354
|
+
isGlyphComputeSupported,
|
|
355
|
+
readbackGlyphTransform,
|
|
356
|
+
setGlyphComputeSupported,
|
|
357
|
+
REPEAT_MODE,
|
|
358
|
+
};
|