@doki-land/live2d-renderer 0.0.19 → 0.0.21
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/index.d.ts +51 -5
- package/dist/index.js +475 -203
- package/package.json +2 -2
- package/src/backends/canvas2d.ts +6 -7
- package/src/backends/webgl2.ts +54 -33
- package/src/backends/webgpu-mesh-cache.ts +206 -0
- package/src/backends/webgpu.ts +158 -186
- package/src/index.ts +9 -0
- package/src/render/clipping-plan.ts +140 -0
- package/src/render/clipping.ts +22 -6
- package/src/render/mesh-interleave.ts +28 -0
package/dist/index.js
CHANGED
|
@@ -322,12 +322,112 @@ function fitClippingContexts(contexts, byIndex, margin = 0.05) {
|
|
|
322
322
|
)
|
|
323
323
|
}));
|
|
324
324
|
}
|
|
325
|
-
function maskLayoutVec4(layout) {
|
|
326
|
-
|
|
325
|
+
function maskLayoutVec4(layout, into) {
|
|
326
|
+
const out = into && into.length >= 4 ? into : new Float32Array(4);
|
|
327
|
+
out[0] = layout.x;
|
|
328
|
+
out[1] = layout.y;
|
|
329
|
+
out[2] = layout.width;
|
|
330
|
+
out[3] = layout.height;
|
|
331
|
+
return out;
|
|
332
|
+
}
|
|
333
|
+
function maskChannelVec4(flag, into) {
|
|
334
|
+
const out = into && into.length >= 4 ? into : new Float32Array(4);
|
|
335
|
+
out[0] = flag[0];
|
|
336
|
+
out[1] = flag[1];
|
|
337
|
+
out[2] = flag[2];
|
|
338
|
+
out[3] = flag[3];
|
|
339
|
+
return out;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/render/clipping-plan.ts
|
|
343
|
+
function clippingTopologyKey(drawables) {
|
|
344
|
+
let key = `n:${drawables.length}`;
|
|
345
|
+
for (let i = 0; i < drawables.length; i++) {
|
|
346
|
+
const d = drawables[i];
|
|
347
|
+
key += `|${d.index}:${d.invertedMask ? 1 : 0}:`;
|
|
348
|
+
const masks = d.maskIndices;
|
|
349
|
+
for (let j = 0; j < masks.length; j++) {
|
|
350
|
+
key += `${masks[j]},`;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return key;
|
|
354
|
+
}
|
|
355
|
+
function atlasOptionsKey(options) {
|
|
356
|
+
return `${options.mode ?? "rgba"}:${options.inset ?? ""}:${options.renderTextureCount ?? ""}`;
|
|
327
357
|
}
|
|
328
|
-
function
|
|
329
|
-
|
|
358
|
+
function fitClippingContextsInPlace(contexts, byIndex, margin = 0.05) {
|
|
359
|
+
for (let i = 0; i < contexts.length; i++) {
|
|
360
|
+
const ctx = contexts[i];
|
|
361
|
+
ctx.modelBounds = calcClippedDrawableBounds(
|
|
362
|
+
ctx.clippedIndices,
|
|
363
|
+
byIndex,
|
|
364
|
+
margin
|
|
365
|
+
);
|
|
366
|
+
}
|
|
330
367
|
}
|
|
368
|
+
var ResidentClippingPlan = class {
|
|
369
|
+
#cacheKey = "";
|
|
370
|
+
#contexts = [];
|
|
371
|
+
#clipped = /* @__PURE__ */ new Set();
|
|
372
|
+
#maskOnly = /* @__PURE__ */ new Set();
|
|
373
|
+
#byIndex = /* @__PURE__ */ new Map();
|
|
374
|
+
/**
|
|
375
|
+
* Returns a partition whose `contexts` array identity is stable across
|
|
376
|
+
* frames with the same topology. `modelBounds` are refreshed in place.
|
|
377
|
+
*/
|
|
378
|
+
resolve(drawables, options = {}) {
|
|
379
|
+
const nextKey = `${clippingTopologyKey(drawables)}|${atlasOptionsKey(options)}`;
|
|
380
|
+
if (nextKey !== this.#cacheKey) {
|
|
381
|
+
const partitioned = partitionForClipping(drawables, options);
|
|
382
|
+
this.#contexts = partitioned.contexts.map((ctx) => ({
|
|
383
|
+
key: ctx.key,
|
|
384
|
+
maskIndices: ctx.maskIndices,
|
|
385
|
+
clippedIndices: ctx.clippedIndices,
|
|
386
|
+
invertedMask: ctx.invertedMask,
|
|
387
|
+
layout: ctx.layout,
|
|
388
|
+
channelIndex: ctx.channelIndex,
|
|
389
|
+
channelFlag: ctx.channelFlag,
|
|
390
|
+
bufferIndex: ctx.bufferIndex,
|
|
391
|
+
modelBounds: { ...ctx.modelBounds }
|
|
392
|
+
}));
|
|
393
|
+
this.#clipped = new Set(partitioned.clipped);
|
|
394
|
+
this.#maskOnly = new Set(partitioned.maskOnly);
|
|
395
|
+
this.#cacheKey = nextKey;
|
|
396
|
+
}
|
|
397
|
+
this.#byIndex.clear();
|
|
398
|
+
for (let i = 0; i < drawables.length; i++) {
|
|
399
|
+
const d = drawables[i];
|
|
400
|
+
if ("vertexPositions" in d) {
|
|
401
|
+
this.#byIndex.set(d.index, d);
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
fitClippingContextsInPlace(this.#contexts, this.#byIndex);
|
|
405
|
+
return {
|
|
406
|
+
contexts: this.#contexts,
|
|
407
|
+
clipped: this.#clipped,
|
|
408
|
+
maskOnly: this.#maskOnly
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
/** Convenience for full drawable meshes (GPU backends). */
|
|
412
|
+
resolveMeshes(drawables, options = {}) {
|
|
413
|
+
return this.resolve(drawables, options);
|
|
414
|
+
}
|
|
415
|
+
/** True when the last {@link resolve} reused the cached atlas layout. */
|
|
416
|
+
get hasPlan() {
|
|
417
|
+
return this.#cacheKey.length > 0;
|
|
418
|
+
}
|
|
419
|
+
/** Exposed for tests — context array identity while topology holds. */
|
|
420
|
+
get residentContexts() {
|
|
421
|
+
return this.#contexts;
|
|
422
|
+
}
|
|
423
|
+
clear() {
|
|
424
|
+
this.#cacheKey = "";
|
|
425
|
+
this.#contexts = [];
|
|
426
|
+
this.#clipped = /* @__PURE__ */ new Set();
|
|
427
|
+
this.#maskOnly = /* @__PURE__ */ new Set();
|
|
428
|
+
this.#byIndex.clear();
|
|
429
|
+
}
|
|
430
|
+
};
|
|
331
431
|
|
|
332
432
|
// src/render/coords.ts
|
|
333
433
|
function modelYUpToCanvasPixelY(modelY, canvasHeight) {
|
|
@@ -443,6 +543,7 @@ var Canvas2DModelDrawPass = class {
|
|
|
443
543
|
#ctx;
|
|
444
544
|
#options;
|
|
445
545
|
#textures = [];
|
|
546
|
+
#clipPlan = new ResidentClippingPlan();
|
|
446
547
|
constructor(ctx, options) {
|
|
447
548
|
this.#ctx = ctx;
|
|
448
549
|
this.#options = options;
|
|
@@ -459,10 +560,10 @@ var Canvas2DModelDrawPass = class {
|
|
|
459
560
|
const h = ctx.canvas.height;
|
|
460
561
|
const byIndex = /* @__PURE__ */ new Map();
|
|
461
562
|
for (const d of drawables) byIndex.set(d.index, d);
|
|
462
|
-
const partitioned =
|
|
563
|
+
const partitioned = this.#clipPlan.resolveMeshes(drawables, {
|
|
463
564
|
mode: "uv-grid"
|
|
464
565
|
});
|
|
465
|
-
const contexts =
|
|
566
|
+
const contexts = partitioned.contexts;
|
|
466
567
|
const { maskOnly } = partitioned;
|
|
467
568
|
if (contexts.length === 0) {
|
|
468
569
|
for (const d of drawables) {
|
|
@@ -627,6 +728,7 @@ var Canvas2DModelDrawPass = class {
|
|
|
627
728
|
}
|
|
628
729
|
destroy() {
|
|
629
730
|
this.#textures = [];
|
|
731
|
+
this.#clipPlan.clear();
|
|
630
732
|
}
|
|
631
733
|
};
|
|
632
734
|
var Canvas2DRendererImpl = class {
|
|
@@ -683,6 +785,25 @@ function isCanvas2DAvailable() {
|
|
|
683
785
|
return !!canvas.getContext("2d");
|
|
684
786
|
}
|
|
685
787
|
|
|
788
|
+
// src/render/mesh-interleave.ts
|
|
789
|
+
function interleavePosUv(positions, uvs, into) {
|
|
790
|
+
const n = Math.floor(positions.length / 2);
|
|
791
|
+
const need = n * 4;
|
|
792
|
+
const out = into && into.length >= need ? into : new Float32Array(need);
|
|
793
|
+
for (let i = 0; i < n; i++) {
|
|
794
|
+
const o = i * 4;
|
|
795
|
+
const p = i * 2;
|
|
796
|
+
out[o] = positions[p];
|
|
797
|
+
out[o + 1] = positions[p + 1];
|
|
798
|
+
out[o + 2] = uvs[p] ?? 0;
|
|
799
|
+
out[o + 3] = uvs[p + 1] ?? 0;
|
|
800
|
+
}
|
|
801
|
+
return out;
|
|
802
|
+
}
|
|
803
|
+
function interleaveByteLength(positions) {
|
|
804
|
+
return Math.floor(positions.length / 2) * 16;
|
|
805
|
+
}
|
|
806
|
+
|
|
686
807
|
// src/backends/webgl2.ts
|
|
687
808
|
var VS = `#version 300 es
|
|
688
809
|
layout(location = 0) in vec2 a_pos;
|
|
@@ -791,19 +912,6 @@ function linkProgram(gl, vsSource, fsSource) {
|
|
|
791
912
|
}
|
|
792
913
|
return program;
|
|
793
914
|
}
|
|
794
|
-
function interleavePosUv(positions, uvs) {
|
|
795
|
-
const n = Math.floor(positions.length / 2);
|
|
796
|
-
const out = new Float32Array(n * 4);
|
|
797
|
-
for (let i = 0; i < n; i++) {
|
|
798
|
-
const o = i * 4;
|
|
799
|
-
const p = i * 2;
|
|
800
|
-
out[o] = positions[p];
|
|
801
|
-
out[o + 1] = positions[p + 1];
|
|
802
|
-
out[o + 2] = uvs[p] ?? 0;
|
|
803
|
-
out[o + 3] = uvs[p + 1] ?? 0;
|
|
804
|
-
}
|
|
805
|
-
return out;
|
|
806
|
-
}
|
|
807
915
|
function requireUniform(gl, program, name) {
|
|
808
916
|
const loc = gl.getUniformLocation(program, name);
|
|
809
917
|
if (!loc) {
|
|
@@ -841,6 +949,13 @@ var WebGl2ModelDrawPass = class {
|
|
|
841
949
|
#maskTex = null;
|
|
842
950
|
#maskW = 0;
|
|
843
951
|
#maskH = 0;
|
|
952
|
+
#clipPlan = new ResidentClippingPlan();
|
|
953
|
+
#interleaved = new Float32Array(64);
|
|
954
|
+
#indicesRef = null;
|
|
955
|
+
#layoutScratch = new Float32Array(4);
|
|
956
|
+
#boundsScratch = new Float32Array(4);
|
|
957
|
+
#channelScratch = new Float32Array(4);
|
|
958
|
+
#defaultChannel = new Float32Array([0, 0, 0, 1]);
|
|
844
959
|
constructor(gl) {
|
|
845
960
|
this.#gl = gl;
|
|
846
961
|
this.#program = linkProgram(gl, VS, FS);
|
|
@@ -1012,13 +1127,25 @@ var WebGl2ModelDrawPass = class {
|
|
|
1012
1127
|
}
|
|
1013
1128
|
gl.bindTexture(gl.TEXTURE_2D, null);
|
|
1014
1129
|
}
|
|
1015
|
-
#uploadMesh(d) {
|
|
1130
|
+
#uploadMesh(d, uploadIndices = true) {
|
|
1016
1131
|
const gl = this.#gl;
|
|
1017
|
-
|
|
1132
|
+
this.#interleaved = interleavePosUv(
|
|
1133
|
+
d.vertexPositions,
|
|
1134
|
+
d.uvs,
|
|
1135
|
+
this.#interleaved
|
|
1136
|
+
);
|
|
1137
|
+
const vBytes = interleaveByteLength(d.vertexPositions);
|
|
1018
1138
|
gl.bindBuffer(gl.ARRAY_BUFFER, this.#vbo);
|
|
1019
|
-
gl.bufferData(
|
|
1139
|
+
gl.bufferData(
|
|
1140
|
+
gl.ARRAY_BUFFER,
|
|
1141
|
+
this.#interleaved.subarray(0, vBytes / 4),
|
|
1142
|
+
gl.DYNAMIC_DRAW
|
|
1143
|
+
);
|
|
1020
1144
|
gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, this.#ibo);
|
|
1021
|
-
|
|
1145
|
+
if (uploadIndices && this.#indicesRef !== d.indices) {
|
|
1146
|
+
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, d.indices, gl.STATIC_DRAW);
|
|
1147
|
+
this.#indicesRef = d.indices;
|
|
1148
|
+
}
|
|
1022
1149
|
}
|
|
1023
1150
|
#drawMeshColor(d, useMask, invertMask, layout, modelBounds, channelFlag) {
|
|
1024
1151
|
const gl = this.#gl;
|
|
@@ -1041,19 +1168,23 @@ var WebGl2ModelDrawPass = class {
|
|
|
1041
1168
|
);
|
|
1042
1169
|
gl.uniform1f(this.#useMaskLoc, useMask ? 1 : 0);
|
|
1043
1170
|
gl.uniform1f(this.#invertMaskLoc, invertMask ? 1 : 0);
|
|
1044
|
-
|
|
1045
|
-
|
|
1171
|
+
gl.uniform4fv(
|
|
1172
|
+
this.#maskLayoutLoc,
|
|
1173
|
+
maskLayoutVec4(
|
|
1174
|
+
layout ?? { x: 0, y: 0, width: 1, height: 1 },
|
|
1175
|
+
this.#layoutScratch
|
|
1176
|
+
)
|
|
1046
1177
|
);
|
|
1047
|
-
gl.uniform4fv(this.#maskLayoutLoc, layoutVec);
|
|
1048
1178
|
gl.uniform4fv(
|
|
1049
1179
|
this.#maskBoundsLoc,
|
|
1050
1180
|
maskLayoutVec4(
|
|
1051
|
-
modelBounds ?? { x: -1, y: -1, width: 2, height: 2 }
|
|
1181
|
+
modelBounds ?? { x: -1, y: -1, width: 2, height: 2 },
|
|
1182
|
+
this.#boundsScratch
|
|
1052
1183
|
)
|
|
1053
1184
|
);
|
|
1054
1185
|
gl.uniform4fv(
|
|
1055
1186
|
this.#channelFlagLoc,
|
|
1056
|
-
channelFlag ??
|
|
1187
|
+
channelFlag ?? this.#defaultChannel
|
|
1057
1188
|
);
|
|
1058
1189
|
if (useMask && this.#maskTex) {
|
|
1059
1190
|
gl.activeTexture(gl.TEXTURE1);
|
|
@@ -1072,6 +1203,7 @@ var WebGl2ModelDrawPass = class {
|
|
|
1072
1203
|
PREVIEW_STROKE.a * d.opacity
|
|
1073
1204
|
);
|
|
1074
1205
|
gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, lines, gl.DYNAMIC_DRAW);
|
|
1206
|
+
this.#indicesRef = null;
|
|
1075
1207
|
gl.drawElements(gl.LINES, lines.length, gl.UNSIGNED_SHORT, 0);
|
|
1076
1208
|
}
|
|
1077
1209
|
}
|
|
@@ -1085,8 +1217,14 @@ var WebGl2ModelDrawPass = class {
|
|
|
1085
1217
|
gl.uniform1i(this.#maskTexLoc, 0);
|
|
1086
1218
|
gl.uniform1f(this.#maskUseTexLoc, gpuTex ? 1 : 0);
|
|
1087
1219
|
gl.uniform1f(this.#maskOpacityLoc, Math.max(d.opacity, 1));
|
|
1088
|
-
gl.uniform4fv(
|
|
1089
|
-
|
|
1220
|
+
gl.uniform4fv(
|
|
1221
|
+
this.#maskWriteLayoutLoc,
|
|
1222
|
+
maskLayoutVec4(layout, this.#layoutScratch)
|
|
1223
|
+
);
|
|
1224
|
+
gl.uniform4fv(
|
|
1225
|
+
this.#maskWriteBoundsLoc,
|
|
1226
|
+
maskLayoutVec4(modelBounds, this.#boundsScratch)
|
|
1227
|
+
);
|
|
1090
1228
|
gl.uniform4fv(this.#maskWriteChannelLoc, channelFlag);
|
|
1091
1229
|
gl.drawElements(gl.TRIANGLES, d.indices.length, gl.UNSIGNED_SHORT, 0);
|
|
1092
1230
|
}
|
|
@@ -1097,8 +1235,8 @@ var WebGl2ModelDrawPass = class {
|
|
|
1097
1235
|
const height = canvas instanceof HTMLCanvasElement ? canvas.height : canvas.height;
|
|
1098
1236
|
const byIndex = /* @__PURE__ */ new Map();
|
|
1099
1237
|
for (const d of drawables) byIndex.set(d.index, d);
|
|
1100
|
-
const partitioned =
|
|
1101
|
-
const contexts =
|
|
1238
|
+
const partitioned = this.#clipPlan.resolveMeshes(drawables);
|
|
1239
|
+
const contexts = partitioned.contexts;
|
|
1102
1240
|
const { maskOnly } = partitioned;
|
|
1103
1241
|
gl.bindVertexArray(this.#vao);
|
|
1104
1242
|
if (contexts.length > 0) {
|
|
@@ -1113,7 +1251,10 @@ var WebGl2ModelDrawPass = class {
|
|
|
1113
1251
|
gl.enable(gl.BLEND);
|
|
1114
1252
|
gl.blendFuncSeparate(gl.ONE, gl.ONE, gl.ONE, gl.ONE);
|
|
1115
1253
|
for (const ctx of contexts) {
|
|
1116
|
-
const flag = maskChannelVec4(
|
|
1254
|
+
const flag = maskChannelVec4(
|
|
1255
|
+
ctx.channelFlag,
|
|
1256
|
+
this.#channelScratch
|
|
1257
|
+
);
|
|
1117
1258
|
for (const mi of ctx.maskIndices) {
|
|
1118
1259
|
const maskMesh = byIndex.get(mi);
|
|
1119
1260
|
if (maskMesh) {
|
|
@@ -1152,7 +1293,7 @@ var WebGl2ModelDrawPass = class {
|
|
|
1152
1293
|
clip.invertedMask,
|
|
1153
1294
|
clip.layout,
|
|
1154
1295
|
clip.modelBounds,
|
|
1155
|
-
maskChannelVec4(clip.channelFlag)
|
|
1296
|
+
maskChannelVec4(clip.channelFlag, this.#channelScratch)
|
|
1156
1297
|
);
|
|
1157
1298
|
} else {
|
|
1158
1299
|
this.#drawMeshColor(d, false, false, null, null, null);
|
|
@@ -1167,6 +1308,8 @@ var WebGl2ModelDrawPass = class {
|
|
|
1167
1308
|
destroy() {
|
|
1168
1309
|
const gl = this.#gl;
|
|
1169
1310
|
this.#clearGpuTextures();
|
|
1311
|
+
this.#clipPlan.clear();
|
|
1312
|
+
this.#indicesRef = null;
|
|
1170
1313
|
if (this.#maskTex) gl.deleteTexture(this.#maskTex);
|
|
1171
1314
|
if (this.#maskFbo) gl.deleteFramebuffer(this.#maskFbo);
|
|
1172
1315
|
gl.deleteBuffer(this.#vbo);
|
|
@@ -1240,6 +1383,155 @@ function isWebGl2Available() {
|
|
|
1240
1383
|
return !!gl;
|
|
1241
1384
|
}
|
|
1242
1385
|
|
|
1386
|
+
// src/backends/webgpu-mesh-cache.ts
|
|
1387
|
+
function indexBufferByteSize(byteLength) {
|
|
1388
|
+
return byteLength + 3 & ~3;
|
|
1389
|
+
}
|
|
1390
|
+
function createOrGrowBuffer(device, prev, prevBytes, needBytes, usage) {
|
|
1391
|
+
const bytes = Math.max(4, needBytes);
|
|
1392
|
+
if (prev && prevBytes >= bytes) return { buffer: prev, bytes: prevBytes };
|
|
1393
|
+
prev?.destroy();
|
|
1394
|
+
return {
|
|
1395
|
+
buffer: device.createBuffer({
|
|
1396
|
+
size: bytes,
|
|
1397
|
+
usage
|
|
1398
|
+
}),
|
|
1399
|
+
bytes
|
|
1400
|
+
};
|
|
1401
|
+
}
|
|
1402
|
+
var WebGpuMeshCache = class {
|
|
1403
|
+
#slots = /* @__PURE__ */ new Map();
|
|
1404
|
+
#maskGeneration = 0;
|
|
1405
|
+
bumpMaskGeneration() {
|
|
1406
|
+
this.#maskGeneration += 1;
|
|
1407
|
+
}
|
|
1408
|
+
get maskGeneration() {
|
|
1409
|
+
return this.#maskGeneration;
|
|
1410
|
+
}
|
|
1411
|
+
clear() {
|
|
1412
|
+
for (const slot of this.#slots.values()) {
|
|
1413
|
+
slot.vbo.destroy();
|
|
1414
|
+
slot.ibo.destroy();
|
|
1415
|
+
slot.ubo.destroy();
|
|
1416
|
+
}
|
|
1417
|
+
this.#slots.clear();
|
|
1418
|
+
}
|
|
1419
|
+
/** Drop bind groups that reference model textures (after setTextures). */
|
|
1420
|
+
invalidateTextureBindGroups() {
|
|
1421
|
+
for (const slot of this.#slots.values()) {
|
|
1422
|
+
slot.bgMask = null;
|
|
1423
|
+
slot.bgTextured = null;
|
|
1424
|
+
slot.bgClippedTextured = null;
|
|
1425
|
+
slot.bgSolid = null;
|
|
1426
|
+
slot.bgClippedSolid = null;
|
|
1427
|
+
slot.texIndex = -1;
|
|
1428
|
+
}
|
|
1429
|
+
}
|
|
1430
|
+
ensureSlot(device, drawableIndex) {
|
|
1431
|
+
let slot = this.#slots.get(drawableIndex);
|
|
1432
|
+
if (slot) return slot;
|
|
1433
|
+
const vbo = device.createBuffer({
|
|
1434
|
+
size: 64,
|
|
1435
|
+
usage: GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
1436
|
+
});
|
|
1437
|
+
const ibo = device.createBuffer({
|
|
1438
|
+
size: 4,
|
|
1439
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
1440
|
+
});
|
|
1441
|
+
const ubo = device.createBuffer({
|
|
1442
|
+
size: 80,
|
|
1443
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST
|
|
1444
|
+
});
|
|
1445
|
+
slot = {
|
|
1446
|
+
index: drawableIndex,
|
|
1447
|
+
vbo,
|
|
1448
|
+
vboBytes: 64,
|
|
1449
|
+
interleaved: new Float32Array(16),
|
|
1450
|
+
ibo,
|
|
1451
|
+
iboBytes: 4,
|
|
1452
|
+
indicesRef: null,
|
|
1453
|
+
uvsRef: null,
|
|
1454
|
+
ubo,
|
|
1455
|
+
uboFloats: new Float32Array(20),
|
|
1456
|
+
bgMask: null,
|
|
1457
|
+
bgTextured: null,
|
|
1458
|
+
bgClippedTextured: null,
|
|
1459
|
+
bgSolid: null,
|
|
1460
|
+
bgClippedSolid: null,
|
|
1461
|
+
texIndex: -1,
|
|
1462
|
+
useWhite: false,
|
|
1463
|
+
maskGeneration: -1
|
|
1464
|
+
};
|
|
1465
|
+
this.#slots.set(drawableIndex, slot);
|
|
1466
|
+
return slot;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Upload interleaved pos/uv when positions change every frame; refresh
|
|
1470
|
+
* index buffer only when the `indices` TypedArray identity changes.
|
|
1471
|
+
*/
|
|
1472
|
+
uploadMesh(device, slot, positions, uvs, indices) {
|
|
1473
|
+
slot.interleaved = interleavePosUv(positions, uvs, slot.interleaved);
|
|
1474
|
+
const vBytes = interleaveByteLength(positions);
|
|
1475
|
+
const grownV = createOrGrowBuffer(
|
|
1476
|
+
device,
|
|
1477
|
+
slot.vbo,
|
|
1478
|
+
slot.vboBytes,
|
|
1479
|
+
vBytes,
|
|
1480
|
+
GPUBufferUsage.VERTEX | GPUBufferUsage.COPY_DST
|
|
1481
|
+
);
|
|
1482
|
+
slot.vbo = grownV.buffer;
|
|
1483
|
+
slot.vboBytes = grownV.bytes;
|
|
1484
|
+
device.queue.writeBuffer(
|
|
1485
|
+
slot.vbo,
|
|
1486
|
+
0,
|
|
1487
|
+
slot.interleaved.buffer,
|
|
1488
|
+
slot.interleaved.byteOffset,
|
|
1489
|
+
vBytes
|
|
1490
|
+
);
|
|
1491
|
+
const iBytes = indexBufferByteSize(indices.byteLength);
|
|
1492
|
+
const grownI = createOrGrowBuffer(
|
|
1493
|
+
device,
|
|
1494
|
+
slot.ibo,
|
|
1495
|
+
slot.iboBytes,
|
|
1496
|
+
iBytes,
|
|
1497
|
+
GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST
|
|
1498
|
+
);
|
|
1499
|
+
if (grownI.buffer !== slot.ibo) {
|
|
1500
|
+
slot.indicesRef = null;
|
|
1501
|
+
}
|
|
1502
|
+
slot.ibo = grownI.buffer;
|
|
1503
|
+
slot.iboBytes = grownI.bytes;
|
|
1504
|
+
if (slot.indicesRef !== indices || slot.uvsRef !== uvs) {
|
|
1505
|
+
if (iBytes === indices.byteLength) {
|
|
1506
|
+
device.queue.writeBuffer(
|
|
1507
|
+
slot.ibo,
|
|
1508
|
+
0,
|
|
1509
|
+
indices.buffer,
|
|
1510
|
+
indices.byteOffset,
|
|
1511
|
+
indices.byteLength
|
|
1512
|
+
);
|
|
1513
|
+
} else {
|
|
1514
|
+
const padded = new Uint16Array(iBytes / 2);
|
|
1515
|
+
padded.set(indices);
|
|
1516
|
+
device.queue.writeBuffer(slot.ibo, 0, padded);
|
|
1517
|
+
}
|
|
1518
|
+
slot.indicesRef = indices;
|
|
1519
|
+
slot.uvsRef = uvs;
|
|
1520
|
+
}
|
|
1521
|
+
}
|
|
1522
|
+
writeUbo(device, slot, floats) {
|
|
1523
|
+
const n = floats.length;
|
|
1524
|
+
for (let i = 0; i < n; i++) slot.uboFloats[i] = floats[i];
|
|
1525
|
+
device.queue.writeBuffer(
|
|
1526
|
+
slot.ubo,
|
|
1527
|
+
0,
|
|
1528
|
+
slot.uboFloats.buffer,
|
|
1529
|
+
slot.uboFloats.byteOffset,
|
|
1530
|
+
Math.max(16, n * 4 + 15 & ~15)
|
|
1531
|
+
);
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
|
|
1243
1535
|
// src/backends/webgpu.ts
|
|
1244
1536
|
var SOLID_WGSL = (
|
|
1245
1537
|
/* wgsl */
|
|
@@ -1432,33 +1724,6 @@ var BLEND_MODES = [
|
|
|
1432
1724
|
BlendMode.Additive,
|
|
1433
1725
|
BlendMode.Multiplicative
|
|
1434
1726
|
];
|
|
1435
|
-
function indexBufferSize(byteLength) {
|
|
1436
|
-
return byteLength + 3 & ~3;
|
|
1437
|
-
}
|
|
1438
|
-
function writeIndexBuffer(device, indices) {
|
|
1439
|
-
const size = indexBufferSize(indices.byteLength);
|
|
1440
|
-
const ibo = device.createBuffer({
|
|
1441
|
-
size: Math.max(4, size),
|
|
1442
|
-
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
1443
|
-
mappedAtCreation: true
|
|
1444
|
-
});
|
|
1445
|
-
new Uint16Array(ibo.getMappedRange()).set(indices);
|
|
1446
|
-
ibo.unmap();
|
|
1447
|
-
return ibo;
|
|
1448
|
-
}
|
|
1449
|
-
function interleavePosUv2(positions, uvs) {
|
|
1450
|
-
const n = Math.floor(positions.length / 2);
|
|
1451
|
-
const out = new Float32Array(n * 4);
|
|
1452
|
-
for (let i = 0; i < n; i++) {
|
|
1453
|
-
const o = i * 4;
|
|
1454
|
-
const p = i * 2;
|
|
1455
|
-
out[o] = positions[p];
|
|
1456
|
-
out[o + 1] = positions[p + 1];
|
|
1457
|
-
out[o + 2] = uvs[p] ?? 0;
|
|
1458
|
-
out[o + 3] = uvs[p + 1] ?? 0;
|
|
1459
|
-
}
|
|
1460
|
-
return out;
|
|
1461
|
-
}
|
|
1462
1727
|
function createWhiteTexture(device) {
|
|
1463
1728
|
const tex = device.createTexture({
|
|
1464
1729
|
size: [1, 1],
|
|
@@ -1514,8 +1779,17 @@ var WebGpuRendererImpl = class {
|
|
|
1514
1779
|
#maskW = 0;
|
|
1515
1780
|
#maskH = 0;
|
|
1516
1781
|
#whiteTexture = null;
|
|
1517
|
-
#
|
|
1782
|
+
#whiteView = null;
|
|
1783
|
+
#maskView = null;
|
|
1518
1784
|
#gpuTextures = [];
|
|
1785
|
+
#gpuTextureViews = [];
|
|
1786
|
+
#meshCache = new WebGpuMeshCache();
|
|
1787
|
+
#clipPlan = new ResidentClippingPlan();
|
|
1788
|
+
#layoutScratch = new Float32Array(4);
|
|
1789
|
+
#channelScratch = new Float32Array(4);
|
|
1790
|
+
#boundsScratch = new Float32Array(4);
|
|
1791
|
+
/** Preview-only line index buffers destroyed at endFrame. */
|
|
1792
|
+
#transient = [];
|
|
1519
1793
|
#options;
|
|
1520
1794
|
constructor(options = {}) {
|
|
1521
1795
|
this.#options = options;
|
|
@@ -1885,6 +2159,7 @@ var WebGpuRendererImpl = class {
|
|
|
1885
2159
|
addressModeV: "clamp-to-edge"
|
|
1886
2160
|
});
|
|
1887
2161
|
this.#whiteTexture = createWhiteTexture(device);
|
|
2162
|
+
this.#whiteView = this.#whiteTexture.createView();
|
|
1888
2163
|
this.#ensureMsaa();
|
|
1889
2164
|
}
|
|
1890
2165
|
#ensureMsaa() {
|
|
@@ -1923,11 +2198,15 @@ var WebGpuRendererImpl = class {
|
|
|
1923
2198
|
});
|
|
1924
2199
|
this.#maskW = w;
|
|
1925
2200
|
this.#maskH = h;
|
|
2201
|
+
this.#maskView = this.#maskTexture.createView();
|
|
2202
|
+
this.#meshCache.bumpMaskGeneration();
|
|
1926
2203
|
return this.#maskTexture;
|
|
1927
2204
|
}
|
|
1928
2205
|
#clearGpuTextures() {
|
|
1929
2206
|
for (const t of this.#gpuTextures) t?.destroy();
|
|
1930
2207
|
this.#gpuTextures = [];
|
|
2208
|
+
this.#gpuTextureViews = [];
|
|
2209
|
+
this.#meshCache.invalidateTextureBindGroups();
|
|
1931
2210
|
}
|
|
1932
2211
|
setTextures(textures) {
|
|
1933
2212
|
const device = this.#device;
|
|
@@ -1936,6 +2215,7 @@ var WebGpuRendererImpl = class {
|
|
|
1936
2215
|
let maxIndex = -1;
|
|
1937
2216
|
for (const t of textures) maxIndex = Math.max(maxIndex, t.index);
|
|
1938
2217
|
this.#gpuTextures = new Array(Math.max(0, maxIndex + 1)).fill(null);
|
|
2218
|
+
this.#gpuTextureViews = new Array(Math.max(0, maxIndex + 1)).fill(null);
|
|
1939
2219
|
for (const t of textures) {
|
|
1940
2220
|
const gpuTex = device.createTexture({
|
|
1941
2221
|
size: [t.width, t.height],
|
|
@@ -1948,6 +2228,7 @@ var WebGpuRendererImpl = class {
|
|
|
1948
2228
|
[t.width, t.height]
|
|
1949
2229
|
);
|
|
1950
2230
|
this.#gpuTextures[t.index] = gpuTex;
|
|
2231
|
+
this.#gpuTextureViews[t.index] = gpuTex.createView();
|
|
1951
2232
|
}
|
|
1952
2233
|
}
|
|
1953
2234
|
createModelDrawPass() {
|
|
@@ -2018,8 +2299,8 @@ var WebGpuRendererImpl = class {
|
|
|
2018
2299
|
}
|
|
2019
2300
|
const byIndex = /* @__PURE__ */ new Map();
|
|
2020
2301
|
for (const d of drawables) byIndex.set(d.index, d);
|
|
2021
|
-
const partitioned =
|
|
2022
|
-
const contexts =
|
|
2302
|
+
const partitioned = this.#clipPlan.resolveMeshes(drawables);
|
|
2303
|
+
const contexts = partitioned.contexts;
|
|
2023
2304
|
const { maskOnly } = partitioned;
|
|
2024
2305
|
let maskTex = null;
|
|
2025
2306
|
if (contexts.length > 0) {
|
|
@@ -2090,27 +2371,23 @@ var WebGpuRendererImpl = class {
|
|
|
2090
2371
|
}
|
|
2091
2372
|
#drawMaskMesh(d, pass, layout, sampler, whiteTex, atlasLayout, modelBounds, channelFlag) {
|
|
2092
2373
|
const device = this.#device;
|
|
2093
|
-
|
|
2094
|
-
|
|
2095
|
-
const
|
|
2096
|
-
|
|
2097
|
-
|
|
2098
|
-
|
|
2099
|
-
|
|
2100
|
-
|
|
2101
|
-
|
|
2102
|
-
|
|
2103
|
-
const
|
|
2104
|
-
|
|
2105
|
-
|
|
2106
|
-
|
|
2107
|
-
|
|
2108
|
-
const layoutVec = maskLayoutVec4(atlasLayout);
|
|
2109
|
-
const ch = maskChannelVec4(channelFlag);
|
|
2110
|
-
const boundsVec = maskLayoutVec4(modelBounds);
|
|
2111
|
-
new Float32Array(ubo.getMappedRange()).set([
|
|
2374
|
+
const whiteView = this.#whiteView;
|
|
2375
|
+
if (!device || !whiteView || d.opacity <= 0) return;
|
|
2376
|
+
const slot = this.#meshCache.ensureSlot(device, d.index);
|
|
2377
|
+
this.#meshCache.uploadMesh(
|
|
2378
|
+
device,
|
|
2379
|
+
slot,
|
|
2380
|
+
d.vertexPositions,
|
|
2381
|
+
d.uvs,
|
|
2382
|
+
d.indices
|
|
2383
|
+
);
|
|
2384
|
+
const layoutVec = maskLayoutVec4(atlasLayout, this.#layoutScratch);
|
|
2385
|
+
const ch = maskChannelVec4(channelFlag, this.#channelScratch);
|
|
2386
|
+
const boundsVec = maskLayoutVec4(modelBounds, this.#boundsScratch);
|
|
2387
|
+
const useWhite = !this.#gpuTextures[d.textureIndex];
|
|
2388
|
+
this.#meshCache.writeUbo(device, slot, [
|
|
2112
2389
|
Math.max(d.opacity, 1),
|
|
2113
|
-
|
|
2390
|
+
useWhite ? 0 : 1,
|
|
2114
2391
|
0,
|
|
2115
2392
|
0,
|
|
2116
2393
|
layoutVec[0],
|
|
@@ -2126,23 +2403,24 @@ var WebGpuRendererImpl = class {
|
|
|
2126
2403
|
boundsVec[2],
|
|
2127
2404
|
boundsVec[3]
|
|
2128
2405
|
]);
|
|
2129
|
-
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
0,
|
|
2133
|
-
device.createBindGroup({
|
|
2406
|
+
const texView = this.#gpuTextureViews[d.textureIndex] ?? whiteView;
|
|
2407
|
+
if (!slot.bgMask || slot.texIndex !== d.textureIndex || slot.useWhite !== useWhite) {
|
|
2408
|
+
slot.bgMask = device.createBindGroup({
|
|
2134
2409
|
layout,
|
|
2135
2410
|
entries: [
|
|
2136
|
-
{ binding: 0, resource: { buffer: ubo } },
|
|
2411
|
+
{ binding: 0, resource: { buffer: slot.ubo } },
|
|
2137
2412
|
{ binding: 1, resource: sampler },
|
|
2138
|
-
{ binding: 2, resource:
|
|
2413
|
+
{ binding: 2, resource: texView }
|
|
2139
2414
|
]
|
|
2140
|
-
})
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2415
|
+
});
|
|
2416
|
+
slot.texIndex = d.textureIndex;
|
|
2417
|
+
slot.useWhite = useWhite;
|
|
2418
|
+
}
|
|
2419
|
+
pass.setBindGroup(0, slot.bgMask);
|
|
2420
|
+
pass.setVertexBuffer(0, slot.vbo);
|
|
2421
|
+
pass.setIndexBuffer(slot.ibo, "uint16");
|
|
2144
2422
|
pass.drawIndexed(d.indices.length);
|
|
2145
|
-
|
|
2423
|
+
void whiteTex;
|
|
2146
2424
|
}
|
|
2147
2425
|
#drawColorMesh(d, pass, useMask, invertMask, maskTex, atlasLayout, channelFlag, modelBounds) {
|
|
2148
2426
|
const device = this.#device;
|
|
@@ -2151,6 +2429,7 @@ var WebGpuRendererImpl = class {
|
|
|
2151
2429
|
const texturedLayout = this.#texturedBindGroupLayout;
|
|
2152
2430
|
const clippedSolidLayout = this.#clippedSolidBindGroupLayout;
|
|
2153
2431
|
const clippedTexturedLayout = this.#clippedTexturedBindGroupLayout;
|
|
2432
|
+
const maskView = this.#maskView;
|
|
2154
2433
|
if (!device || !sampler || !solidLayout || !texturedLayout || !clippedSolidLayout || !clippedTexturedLayout) {
|
|
2155
2434
|
return;
|
|
2156
2435
|
}
|
|
@@ -2158,30 +2437,30 @@ var WebGpuRendererImpl = class {
|
|
|
2158
2437
|
const set = this.#pipelines[d.blendMode] ?? this.#pipelines[BlendMode.Normal];
|
|
2159
2438
|
if (!set) return;
|
|
2160
2439
|
const layoutVec = maskLayoutVec4(
|
|
2161
|
-
atlasLayout ?? { x: 0, y: 0, width: 1, height: 1 }
|
|
2440
|
+
atlasLayout ?? { x: 0, y: 0, width: 1, height: 1 },
|
|
2441
|
+
this.#layoutScratch
|
|
2442
|
+
);
|
|
2443
|
+
const ch = maskChannelVec4(
|
|
2444
|
+
channelFlag ?? [0, 0, 0, 1],
|
|
2445
|
+
this.#channelScratch
|
|
2162
2446
|
);
|
|
2163
|
-
const ch = maskChannelVec4(channelFlag ?? [0, 0, 0, 1]);
|
|
2164
2447
|
const boundsVec = maskLayoutVec4(
|
|
2165
|
-
modelBounds ?? { x: -1, y: -1, width: 2, height: 2 }
|
|
2448
|
+
modelBounds ?? { x: -1, y: -1, width: 2, height: 2 },
|
|
2449
|
+
this.#boundsScratch
|
|
2166
2450
|
);
|
|
2451
|
+
const slot = this.#meshCache.ensureSlot(device, d.index);
|
|
2167
2452
|
const gpuTex = this.#gpuTextures[d.textureIndex] ?? null;
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
size: uboSize,
|
|
2180
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2181
|
-
mappedAtCreation: true
|
|
2182
|
-
});
|
|
2183
|
-
if (useMask && maskTex) {
|
|
2184
|
-
new Float32Array(ubo.getMappedRange()).set([
|
|
2453
|
+
const texView = this.#gpuTextureViews[d.textureIndex] ?? null;
|
|
2454
|
+
if (gpuTex && texView) {
|
|
2455
|
+
this.#meshCache.uploadMesh(
|
|
2456
|
+
device,
|
|
2457
|
+
slot,
|
|
2458
|
+
d.vertexPositions,
|
|
2459
|
+
d.uvs,
|
|
2460
|
+
d.indices
|
|
2461
|
+
);
|
|
2462
|
+
if (useMask && maskTex && maskView) {
|
|
2463
|
+
this.#meshCache.writeUbo(device, slot, [
|
|
2185
2464
|
d.opacity,
|
|
2186
2465
|
invertMask ? 1 : 0,
|
|
2187
2466
|
0,
|
|
@@ -2199,64 +2478,51 @@ var WebGpuRendererImpl = class {
|
|
|
2199
2478
|
boundsVec[2],
|
|
2200
2479
|
boundsVec[3]
|
|
2201
2480
|
]);
|
|
2202
|
-
|
|
2203
|
-
|
|
2204
|
-
d.opacity,
|
|
2205
|
-
0,
|
|
2206
|
-
0,
|
|
2207
|
-
0
|
|
2208
|
-
]);
|
|
2209
|
-
}
|
|
2210
|
-
ubo.unmap();
|
|
2211
|
-
const ibo = writeIndexBuffer(device, d.indices);
|
|
2212
|
-
if (useMask && maskTex) {
|
|
2213
|
-
pass.setPipeline(set.clippedTextured);
|
|
2214
|
-
pass.setBindGroup(
|
|
2215
|
-
0,
|
|
2216
|
-
device.createBindGroup({
|
|
2481
|
+
if (!slot.bgClippedTextured || slot.texIndex !== d.textureIndex || slot.maskGeneration !== this.#meshCache.maskGeneration) {
|
|
2482
|
+
slot.bgClippedTextured = device.createBindGroup({
|
|
2217
2483
|
layout: clippedTexturedLayout,
|
|
2218
2484
|
entries: [
|
|
2219
|
-
{ binding: 0, resource: { buffer: ubo } },
|
|
2485
|
+
{ binding: 0, resource: { buffer: slot.ubo } },
|
|
2220
2486
|
{ binding: 1, resource: sampler },
|
|
2221
|
-
{ binding: 2, resource:
|
|
2222
|
-
{ binding: 3, resource:
|
|
2487
|
+
{ binding: 2, resource: texView },
|
|
2488
|
+
{ binding: 3, resource: maskView }
|
|
2223
2489
|
]
|
|
2224
|
-
})
|
|
2225
|
-
|
|
2490
|
+
});
|
|
2491
|
+
slot.texIndex = d.textureIndex;
|
|
2492
|
+
slot.maskGeneration = this.#meshCache.maskGeneration;
|
|
2493
|
+
}
|
|
2494
|
+
pass.setPipeline(set.clippedTextured);
|
|
2495
|
+
pass.setBindGroup(0, slot.bgClippedTextured);
|
|
2226
2496
|
} else {
|
|
2227
|
-
|
|
2228
|
-
|
|
2229
|
-
|
|
2230
|
-
device.createBindGroup({
|
|
2497
|
+
this.#meshCache.writeUbo(device, slot, [d.opacity, 0, 0, 0]);
|
|
2498
|
+
if (!slot.bgTextured || slot.texIndex !== d.textureIndex) {
|
|
2499
|
+
slot.bgTextured = device.createBindGroup({
|
|
2231
2500
|
layout: texturedLayout,
|
|
2232
2501
|
entries: [
|
|
2233
|
-
{ binding: 0, resource: { buffer: ubo } },
|
|
2502
|
+
{ binding: 0, resource: { buffer: slot.ubo } },
|
|
2234
2503
|
{ binding: 1, resource: sampler },
|
|
2235
|
-
{ binding: 2, resource:
|
|
2504
|
+
{ binding: 2, resource: texView }
|
|
2236
2505
|
]
|
|
2237
|
-
})
|
|
2238
|
-
|
|
2506
|
+
});
|
|
2507
|
+
slot.texIndex = d.textureIndex;
|
|
2508
|
+
}
|
|
2509
|
+
pass.setPipeline(set.textured);
|
|
2510
|
+
pass.setBindGroup(0, slot.bgTextured);
|
|
2239
2511
|
}
|
|
2240
|
-
pass.setVertexBuffer(0,
|
|
2241
|
-
pass.setIndexBuffer(ibo, "uint16");
|
|
2512
|
+
pass.setVertexBuffer(0, slot.vbo);
|
|
2513
|
+
pass.setIndexBuffer(slot.ibo, "uint16");
|
|
2242
2514
|
pass.drawIndexed(d.indices.length);
|
|
2243
|
-
this.#transient.push(vbo2, ubo, ibo);
|
|
2244
2515
|
return;
|
|
2245
2516
|
}
|
|
2246
|
-
|
|
2247
|
-
|
|
2248
|
-
|
|
2249
|
-
|
|
2250
|
-
|
|
2251
|
-
|
|
2252
|
-
|
|
2253
|
-
if (useMask && maskTex) {
|
|
2254
|
-
|
|
2255
|
-
size: 80,
|
|
2256
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2257
|
-
mappedAtCreation: true
|
|
2258
|
-
});
|
|
2259
|
-
new Float32Array(ubo.getMappedRange()).set([
|
|
2517
|
+
this.#meshCache.uploadMesh(
|
|
2518
|
+
device,
|
|
2519
|
+
slot,
|
|
2520
|
+
d.vertexPositions,
|
|
2521
|
+
d.uvs,
|
|
2522
|
+
d.indices
|
|
2523
|
+
);
|
|
2524
|
+
if (useMask && maskTex && maskView) {
|
|
2525
|
+
this.#meshCache.writeUbo(device, slot, [
|
|
2260
2526
|
PREVIEW_FILL.r,
|
|
2261
2527
|
PREVIEW_FILL.g,
|
|
2262
2528
|
PREVIEW_FILL.b,
|
|
@@ -2278,54 +2544,61 @@ var WebGpuRendererImpl = class {
|
|
|
2278
2544
|
boundsVec[2],
|
|
2279
2545
|
boundsVec[3]
|
|
2280
2546
|
]);
|
|
2281
|
-
|
|
2282
|
-
|
|
2283
|
-
pass.setPipeline(set.clippedFill);
|
|
2284
|
-
pass.setBindGroup(
|
|
2285
|
-
0,
|
|
2286
|
-
device.createBindGroup({
|
|
2547
|
+
if (!slot.bgClippedSolid || slot.maskGeneration !== this.#meshCache.maskGeneration) {
|
|
2548
|
+
slot.bgClippedSolid = device.createBindGroup({
|
|
2287
2549
|
layout: clippedSolidLayout,
|
|
2288
2550
|
entries: [
|
|
2289
|
-
{ binding: 0, resource: { buffer: ubo } },
|
|
2551
|
+
{ binding: 0, resource: { buffer: slot.ubo } },
|
|
2290
2552
|
{ binding: 1, resource: sampler },
|
|
2291
|
-
{ binding: 2, resource:
|
|
2553
|
+
{ binding: 2, resource: maskView }
|
|
2292
2554
|
]
|
|
2293
|
-
})
|
|
2294
|
-
|
|
2295
|
-
|
|
2296
|
-
pass.
|
|
2555
|
+
});
|
|
2556
|
+
slot.maskGeneration = this.#meshCache.maskGeneration;
|
|
2557
|
+
}
|
|
2558
|
+
pass.setPipeline(set.clippedFill);
|
|
2559
|
+
pass.setBindGroup(0, slot.bgClippedSolid);
|
|
2560
|
+
pass.setVertexBuffer(0, slot.vbo);
|
|
2561
|
+
pass.setIndexBuffer(slot.ibo, "uint16");
|
|
2297
2562
|
pass.drawIndexed(d.indices.length);
|
|
2298
|
-
this.#transient.push(vbo, ubo, ibo);
|
|
2299
2563
|
return;
|
|
2300
2564
|
}
|
|
2301
|
-
|
|
2302
|
-
device,
|
|
2565
|
+
this.#meshCache.writeUbo(device, slot, [
|
|
2303
2566
|
PREVIEW_FILL.r,
|
|
2304
2567
|
PREVIEW_FILL.g,
|
|
2305
2568
|
PREVIEW_FILL.b,
|
|
2306
2569
|
PREVIEW_FILL.a * d.opacity
|
|
2307
|
-
);
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
pass.setBindGroup(
|
|
2311
|
-
0,
|
|
2312
|
-
device.createBindGroup({
|
|
2570
|
+
]);
|
|
2571
|
+
if (!slot.bgSolid) {
|
|
2572
|
+
slot.bgSolid = device.createBindGroup({
|
|
2313
2573
|
layout: solidLayout,
|
|
2314
|
-
entries: [{ binding: 0, resource: { buffer:
|
|
2315
|
-
})
|
|
2316
|
-
|
|
2317
|
-
pass.
|
|
2318
|
-
pass.
|
|
2574
|
+
entries: [{ binding: 0, resource: { buffer: slot.ubo } }]
|
|
2575
|
+
});
|
|
2576
|
+
}
|
|
2577
|
+
pass.setPipeline(set.fill);
|
|
2578
|
+
pass.setBindGroup(0, slot.bgSolid);
|
|
2579
|
+
pass.setVertexBuffer(0, slot.vbo);
|
|
2580
|
+
pass.setIndexBuffer(slot.ibo, "uint16");
|
|
2319
2581
|
pass.drawIndexed(d.indices.length);
|
|
2320
2582
|
const lines = triangleEdgesToLineList(d.indices);
|
|
2321
|
-
const lineUbo =
|
|
2322
|
-
|
|
2583
|
+
const lineUbo = device.createBuffer({
|
|
2584
|
+
size: 16,
|
|
2585
|
+
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2586
|
+
mappedAtCreation: true
|
|
2587
|
+
});
|
|
2588
|
+
new Float32Array(lineUbo.getMappedRange()).set([
|
|
2323
2589
|
PREVIEW_STROKE.r,
|
|
2324
2590
|
PREVIEW_STROKE.g,
|
|
2325
2591
|
PREVIEW_STROKE.b,
|
|
2326
2592
|
PREVIEW_STROKE.a * d.opacity
|
|
2327
|
-
);
|
|
2328
|
-
|
|
2593
|
+
]);
|
|
2594
|
+
lineUbo.unmap();
|
|
2595
|
+
const lineIbo = device.createBuffer({
|
|
2596
|
+
size: Math.max(4, lines.byteLength + 3 & ~3),
|
|
2597
|
+
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
2598
|
+
mappedAtCreation: true
|
|
2599
|
+
});
|
|
2600
|
+
new Uint16Array(lineIbo.getMappedRange()).set(lines);
|
|
2601
|
+
lineIbo.unmap();
|
|
2329
2602
|
pass.setPipeline(set.line);
|
|
2330
2603
|
pass.setBindGroup(
|
|
2331
2604
|
0,
|
|
@@ -2334,20 +2607,10 @@ var WebGpuRendererImpl = class {
|
|
|
2334
2607
|
entries: [{ binding: 0, resource: { buffer: lineUbo } }]
|
|
2335
2608
|
})
|
|
2336
2609
|
);
|
|
2337
|
-
pass.setVertexBuffer(0, vbo);
|
|
2610
|
+
pass.setVertexBuffer(0, slot.vbo);
|
|
2338
2611
|
pass.setIndexBuffer(lineIbo, "uint16");
|
|
2339
2612
|
pass.drawIndexed(lines.length);
|
|
2340
|
-
this.#transient.push(
|
|
2341
|
-
}
|
|
2342
|
-
#makeColorUbo(device, r, g, b, a) {
|
|
2343
|
-
const ubo = device.createBuffer({
|
|
2344
|
-
size: 16,
|
|
2345
|
-
usage: GPUBufferUsage.UNIFORM | GPUBufferUsage.COPY_DST,
|
|
2346
|
-
mappedAtCreation: true
|
|
2347
|
-
});
|
|
2348
|
-
new Float32Array(ubo.getMappedRange()).set([r, g, b, a]);
|
|
2349
|
-
ubo.unmap();
|
|
2350
|
-
return ubo;
|
|
2613
|
+
this.#transient.push(lineUbo, lineIbo);
|
|
2351
2614
|
}
|
|
2352
2615
|
endFrame() {
|
|
2353
2616
|
const device = this.#device;
|
|
@@ -2376,13 +2639,17 @@ var WebGpuRendererImpl = class {
|
|
|
2376
2639
|
destroy() {
|
|
2377
2640
|
for (const b of this.#transient) b.destroy();
|
|
2378
2641
|
this.#transient = [];
|
|
2642
|
+
this.#meshCache.clear();
|
|
2643
|
+
this.#clipPlan.clear();
|
|
2379
2644
|
this.#clearGpuTextures();
|
|
2380
2645
|
this.#msaaTexture?.destroy();
|
|
2381
2646
|
this.#msaaTexture = null;
|
|
2382
2647
|
this.#maskTexture?.destroy();
|
|
2383
2648
|
this.#maskTexture = null;
|
|
2649
|
+
this.#maskView = null;
|
|
2384
2650
|
this.#whiteTexture?.destroy();
|
|
2385
2651
|
this.#whiteTexture = null;
|
|
2652
|
+
this.#whiteView = null;
|
|
2386
2653
|
this.#device?.destroy();
|
|
2387
2654
|
this.#device = null;
|
|
2388
2655
|
this.#context = null;
|
|
@@ -5927,6 +6194,7 @@ export {
|
|
|
5927
6194
|
Moc3DrawableFlag,
|
|
5928
6195
|
PREVIEW_FILL,
|
|
5929
6196
|
PREVIEW_STROKE,
|
|
6197
|
+
ResidentClippingPlan,
|
|
5930
6198
|
WebGl2RendererImpl,
|
|
5931
6199
|
WebGpuRendererImpl,
|
|
5932
6200
|
applyMoc3Glues,
|
|
@@ -5935,6 +6203,7 @@ export {
|
|
|
5935
6203
|
calcClippedDrawableBounds,
|
|
5936
6204
|
calcVertexBounds,
|
|
5937
6205
|
canvasCompositeForBlendMode,
|
|
6206
|
+
clippingTopologyKey,
|
|
5938
6207
|
compileSharedModelCompile,
|
|
5939
6208
|
createCanvas2DRenderer,
|
|
5940
6209
|
createMoc2Backend,
|
|
@@ -5954,6 +6223,9 @@ export {
|
|
|
5954
6223
|
expandBounds,
|
|
5955
6224
|
fingerprintSnapshot,
|
|
5956
6225
|
fitClippingContexts,
|
|
6226
|
+
fitClippingContextsInPlace,
|
|
6227
|
+
interleaveByteLength,
|
|
6228
|
+
interleavePosUv,
|
|
5957
6229
|
isCanvas2DAvailable,
|
|
5958
6230
|
isCpuProgramBytes,
|
|
5959
6231
|
isWebGl2Available,
|