@ifc-lite/renderer 1.38.1 → 1.40.0
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/deviation/deviation-pipeline.d.ts +8 -0
- package/dist/deviation/deviation-pipeline.d.ts.map +1 -1
- package/dist/deviation/deviation-pipeline.js +20 -9
- package/dist/deviation/deviation-pipeline.js.map +1 -1
- package/dist/deviation/deviation-shader.wgsl.d.ts +1 -1
- package/dist/deviation/deviation-shader.wgsl.d.ts.map +1 -1
- package/dist/deviation/deviation-shader.wgsl.js +11 -3
- package/dist/deviation/deviation-shader.wgsl.js.map +1 -1
- package/dist/index.d.ts +45 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +78 -3
- package/dist/index.js.map +1 -1
- package/dist/picker.d.ts +9 -0
- package/dist/picker.d.ts.map +1 -1
- package/dist/picker.js +84 -2
- package/dist/picker.js.map +1 -1
- package/dist/pointcloud/point-cloud-node.d.ts +46 -0
- package/dist/pointcloud/point-cloud-node.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-node.js +71 -0
- package/dist/pointcloud/point-cloud-node.js.map +1 -1
- package/dist/pointcloud/point-cloud-ray-transform.d.ts +66 -0
- package/dist/pointcloud/point-cloud-ray-transform.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-ray-transform.js +172 -0
- package/dist/pointcloud/point-cloud-ray-transform.js.map +1 -0
- package/dist/pointcloud/point-cloud-renderer.d.ts +39 -6
- package/dist/pointcloud/point-cloud-renderer.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-renderer.js +53 -17
- package/dist/pointcloud/point-cloud-renderer.js.map +1 -1
- package/dist/pointcloud/point-cloud-spatial-index.d.ts +188 -0
- package/dist/pointcloud/point-cloud-spatial-index.d.ts.map +1 -0
- package/dist/pointcloud/point-cloud-spatial-index.js +536 -0
- package/dist/pointcloud/point-cloud-spatial-index.js.map +1 -0
- package/dist/pointcloud/point-cloud-uniforms.d.ts +14 -2
- package/dist/pointcloud/point-cloud-uniforms.d.ts.map +1 -1
- package/dist/pointcloud/point-cloud-uniforms.js +49 -8
- package/dist/pointcloud/point-cloud-uniforms.js.map +1 -1
- package/dist/pointcloud/point-pipeline.d.ts +3 -2
- package/dist/pointcloud/point-pipeline.d.ts.map +1 -1
- package/dist/pointcloud/point-pipeline.js +8 -5
- package/dist/pointcloud/point-pipeline.js.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.d.ts +1 -1
- package/dist/pointcloud/point-shader.wgsl.d.ts.map +1 -1
- package/dist/pointcloud/point-shader.wgsl.js +20 -17
- package/dist/pointcloud/point-shader.wgsl.js.map +1 -1
- package/dist/raycast-engine.d.ts +8 -0
- package/dist/raycast-engine.d.ts.map +1 -1
- package/dist/raycast-engine.js +68 -23
- package/dist/raycast-engine.js.map +1 -1
- package/dist/raycast-point-cloud-query.d.ts +122 -0
- package/dist/raycast-point-cloud-query.d.ts.map +1 -0
- package/dist/raycast-point-cloud-query.js +145 -0
- package/dist/raycast-point-cloud-query.js.map +1 -0
- package/dist/scene.d.ts +18 -0
- package/dist/scene.d.ts.map +1 -1
- package/dist/scene.js +166 -63
- package/dist/scene.js.map +1 -1
- package/dist/snap-detector.d.ts +12 -1
- package/dist/snap-detector.d.ts.map +1 -1
- package/dist/snap-detector.js +9 -1
- package/dist/snap-detector.js.map +1 -1
- package/package.json +2 -2
package/dist/scene.js
CHANGED
|
@@ -30,6 +30,10 @@ export class Scene {
|
|
|
30
30
|
meshDataMap = new Map(); // Map expressId -> MeshData[] (for lazy buffer creation, accumulates multiple pieces)
|
|
31
31
|
boundingBoxes = new Map(); // Map expressId -> bounding box (computed lazily)
|
|
32
32
|
texturedMeshes = []; // #961: IFC surface-textured meshes (own buffers/texture/bindGroup)
|
|
33
|
+
/** #1781: GPU textures shared across meshes, keyed by `MeshTextureRef.textureId`
|
|
34
|
+
* (one `IfcImageTexture` → one upload, sampled by every face set mapping it).
|
|
35
|
+
* Refcounted: entries die when the last referencing mesh is removed / on clear(). */
|
|
36
|
+
sharedTextures = new Map();
|
|
33
37
|
texturedDevice; // #961: cached for textured-mesh re-upload on translate
|
|
34
38
|
instancedTemplates = []; // GPU-instancing: unique templates + per-occurrence buffers (fed by addInstancedShard)
|
|
35
39
|
instancedVisible = true; // GPU-instancing: hidden in Types view mode (instanced geometry is class-0 occurrences)
|
|
@@ -964,10 +968,10 @@ export class Scene {
|
|
|
964
968
|
// otherwise a flat-colour copy would be drawn over the texture. Still
|
|
965
969
|
// register them in meshDataMap (addMeshData) so CPU picking/bbox/frame work.
|
|
966
970
|
let renderable = meshDataArray;
|
|
967
|
-
if (meshDataArray.some((m) =>
|
|
971
|
+
if (meshDataArray.some((m) => Scene.hasRenderableTexture(m))) {
|
|
968
972
|
renderable = [];
|
|
969
973
|
for (const meshData of meshDataArray) {
|
|
970
|
-
if (
|
|
974
|
+
if (Scene.hasRenderableTexture(meshData)) {
|
|
971
975
|
this.createTexturedMesh(meshData, device, pipeline);
|
|
972
976
|
this.addMeshData(meshData);
|
|
973
977
|
}
|
|
@@ -1135,7 +1139,7 @@ export class Scene {
|
|
|
1135
1139
|
tm.vertexBuffer.destroy();
|
|
1136
1140
|
tm.indexBuffer.destroy();
|
|
1137
1141
|
tm.uniformBuffer.destroy();
|
|
1138
|
-
|
|
1142
|
+
this.releaseTexturedMeshTexture(tm);
|
|
1139
1143
|
this.texturedMeshes.splice(i, 1);
|
|
1140
1144
|
removedDedicated = true;
|
|
1141
1145
|
}
|
|
@@ -1321,7 +1325,7 @@ export class Scene {
|
|
|
1321
1325
|
// re-interleave + re-upload the moved textured parts (paired by expressId,
|
|
1322
1326
|
// in creation order). Without this a moved textured entity renders stale.
|
|
1323
1327
|
if (this.texturedDevice && this.texturedMeshes.length > 0) {
|
|
1324
|
-
const texturedData = meshDataList.filter((md) =>
|
|
1328
|
+
const texturedData = meshDataList.filter((md) => Scene.hasRenderableTexture(md));
|
|
1325
1329
|
if (texturedData.length > 0) {
|
|
1326
1330
|
const entries = this.texturedMeshes.filter((tm) => tm.expressId === expressId);
|
|
1327
1331
|
for (let i = 0; i < entries.length && i < texturedData.length; i++) {
|
|
@@ -1519,7 +1523,7 @@ export class Scene {
|
|
|
1519
1523
|
// #961: textured meshes render from their own GPU vertex buffer — re-upload
|
|
1520
1524
|
// the rotated parts so they don't render stale (mirrors the translate path).
|
|
1521
1525
|
if (this.texturedDevice && this.texturedMeshes.length > 0) {
|
|
1522
|
-
const texturedData = meshDataList.filter((md) =>
|
|
1526
|
+
const texturedData = meshDataList.filter((md) => Scene.hasRenderableTexture(md));
|
|
1523
1527
|
if (texturedData.length > 0) {
|
|
1524
1528
|
const entries = this.texturedMeshes.filter((tm) => tm.expressId === expressId);
|
|
1525
1529
|
for (let i = 0; i < entries.length && i < texturedData.length; i++) {
|
|
@@ -1757,56 +1761,90 @@ export class Scene {
|
|
|
1757
1761
|
const oldFragments = this.streamingFragments;
|
|
1758
1762
|
const oldBatches = this.batchedMeshes;
|
|
1759
1763
|
const fragmentSet = new Set(oldFragments);
|
|
1760
|
-
|
|
1761
|
-
// 1
|
|
1762
|
-
//
|
|
1763
|
-
//
|
|
1764
|
-
//
|
|
1765
|
-
//
|
|
1766
|
-
|
|
1767
|
-
|
|
1768
|
-
|
|
1769
|
-
|
|
1770
|
-
|
|
1771
|
-
|
|
1764
|
+
const oldBatchSet = new Set(oldBatches);
|
|
1765
|
+
// Steps 1-4 detach the old drawables (streamingFragments = [],
|
|
1766
|
+
// batchedMeshes = []) BEFORE the replacement GPU buffers exist. If a
|
|
1767
|
+
// createBuffer fails part-way through, callers that CONTAIN the throw to
|
|
1768
|
+
// keep the canvas alive would otherwise be left rendering a half-built —
|
|
1769
|
+
// often empty — scene, turning a crash into a silently blank model.
|
|
1770
|
+
let rebuilt = false;
|
|
1771
|
+
try {
|
|
1772
|
+
this.streamingFragments = [];
|
|
1773
|
+
// 1. Collect ALL accumulated meshData before clearing state.
|
|
1774
|
+
// Cold buckets (issue #1682 phase 3b) hold NO meshData — their
|
|
1775
|
+
// geometry lives on disk — so they are carried through the rebuild as
|
|
1776
|
+
// sealed shells instead of being re-grouped (re-grouping would
|
|
1777
|
+
// silently drop them).
|
|
1778
|
+
const allMeshData = [];
|
|
1779
|
+
const carriedCold = [];
|
|
1780
|
+
for (const [key, bucket] of this.buckets) {
|
|
1781
|
+
if (this.coldBuckets.has(key) && bucket.meshData.length === 0 && bucket.batchedMesh) {
|
|
1782
|
+
carriedCold.push([key, bucket]);
|
|
1783
|
+
continue;
|
|
1784
|
+
}
|
|
1785
|
+
for (const md of bucket.meshData)
|
|
1786
|
+
allMeshData.push(md);
|
|
1787
|
+
}
|
|
1788
|
+
// 2. Clear all bucket/batch state for a clean rebuild
|
|
1789
|
+
// NOTE: batchedMeshes keeps the OLD array reference — the renderer
|
|
1790
|
+
// continues to draw from it until we swap in the new array below.
|
|
1791
|
+
this.buckets.clear();
|
|
1792
|
+
this.meshDataBucket = new Map();
|
|
1793
|
+
this.activeBucketKey.clear();
|
|
1794
|
+
this.lastDrawnFrame.clear();
|
|
1795
|
+
this.residencyRestoreQueue.clear();
|
|
1796
|
+
this.pendingBatchKeys.clear();
|
|
1797
|
+
// Re-seat the carried cold shells in the fresh bucket map (their GPU
|
|
1798
|
+
// shells re-enter the flat array via rebuildPendingBatches below).
|
|
1799
|
+
for (const [key, bucket] of carriedCold)
|
|
1800
|
+
this.buckets.set(key, bucket);
|
|
1801
|
+
// 3. Re-group ALL meshData by their CURRENT color (and grid cell).
|
|
1802
|
+
// meshData.color may have been mutated in-place since the mesh was
|
|
1803
|
+
// first bucketed, so the original bucket key is stale. Re-grouping
|
|
1804
|
+
// by current color ensures batches render with correct colors.
|
|
1805
|
+
for (const meshData of allMeshData) {
|
|
1806
|
+
const baseKey = this.bucketBaseKey(meshData);
|
|
1807
|
+
const bucketKey = this.resolveActiveBucket(baseKey, meshData);
|
|
1808
|
+
let bucket = this.buckets.get(bucketKey);
|
|
1809
|
+
if (!bucket) {
|
|
1810
|
+
bucket = { key: bucketKey, meshData: [], batchedMesh: null, vertexBytes: 0 };
|
|
1811
|
+
this.buckets.set(bucketKey, bucket);
|
|
1812
|
+
}
|
|
1813
|
+
bucket.meshData.push(meshData);
|
|
1814
|
+
this.meshDataBucket.set(meshData, bucket);
|
|
1815
|
+
this.pendingBatchKeys.add(bucketKey);
|
|
1772
1816
|
}
|
|
1773
|
-
|
|
1774
|
-
|
|
1817
|
+
// 4. Build new proper batches into a fresh array
|
|
1818
|
+
this.batchedMeshes = [];
|
|
1819
|
+
this.rebuildPendingBatches(device, pipeline);
|
|
1820
|
+
rebuilt = true;
|
|
1821
|
+
// Cached partial (filtered-visibility) batches are keyed by their SOURCE
|
|
1822
|
+
// batch, so they only go stale once the replacement batches are live.
|
|
1823
|
+
// Dropping them up in step 2 destroyed their GPU resources BEFORE the
|
|
1824
|
+
// rebuild could fail, and the rollback cannot bring them back — an active
|
|
1825
|
+
// hide/isolate view lost its visible subset and had to recreate it
|
|
1826
|
+
// against the very device that just failed. On the failure path they now
|
|
1827
|
+
// survive, still matching the restored batches.
|
|
1828
|
+
this.dropAllPartialCaches();
|
|
1775
1829
|
}
|
|
1776
|
-
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1780
|
-
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
// meshData.color may have been mutated in-place since the mesh was
|
|
1793
|
-
// first bucketed, so the original bucket key is stale. Re-grouping
|
|
1794
|
-
// by current color ensures batches render with correct colors.
|
|
1795
|
-
for (const meshData of allMeshData) {
|
|
1796
|
-
const baseKey = this.bucketBaseKey(meshData);
|
|
1797
|
-
const bucketKey = this.resolveActiveBucket(baseKey, meshData);
|
|
1798
|
-
let bucket = this.buckets.get(bucketKey);
|
|
1799
|
-
if (!bucket) {
|
|
1800
|
-
bucket = { key: bucketKey, meshData: [], batchedMesh: null, vertexBytes: 0 };
|
|
1801
|
-
this.buckets.set(bucketKey, bucket);
|
|
1830
|
+
finally {
|
|
1831
|
+
if (!rebuilt) {
|
|
1832
|
+
// Free ONLY what this attempt created. Carried cold shells are aliased
|
|
1833
|
+
// into BOTH the old and the new array, so anything that was already
|
|
1834
|
+
// live before the rebuild must be left alone — destroying it would
|
|
1835
|
+
// leave the restored arrays pointing at dead buffers.
|
|
1836
|
+
for (const created of this.batchedMeshes) {
|
|
1837
|
+
if (!oldBatchSet.has(created) && !fragmentSet.has(created)) {
|
|
1838
|
+
destroyGpuResources(created);
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
// Step 5 never ran, so every old fragment/batch is still a live GPU
|
|
1842
|
+
// resource: putting the arrays back restores exactly what was on
|
|
1843
|
+
// screen.
|
|
1844
|
+
this.streamingFragments = oldFragments;
|
|
1845
|
+
this.batchedMeshes = oldBatches;
|
|
1802
1846
|
}
|
|
1803
|
-
bucket.meshData.push(meshData);
|
|
1804
|
-
this.meshDataBucket.set(meshData, bucket);
|
|
1805
|
-
this.pendingBatchKeys.add(bucketKey);
|
|
1806
1847
|
}
|
|
1807
|
-
// 4. Build new proper batches into a fresh array
|
|
1808
|
-
this.batchedMeshes = [];
|
|
1809
|
-
this.rebuildPendingBatches(device, pipeline);
|
|
1810
1848
|
// 5. NOW destroy old fragment/batch GPU resources (new batches are live)
|
|
1811
1849
|
for (const fragment of oldFragments)
|
|
1812
1850
|
destroyGpuResources(fragment);
|
|
@@ -3098,6 +3136,15 @@ export class Scene {
|
|
|
3098
3136
|
* The per-frame uniform (viewProj/section/flags + colour tint) is written by
|
|
3099
3137
|
* the renderer each frame, mirroring how colour batches are driven.
|
|
3100
3138
|
*/
|
|
3139
|
+
/** True when the mesh can render through the textured pipeline: UVs plus
|
|
3140
|
+
* either a Rust-decoded image (#961) or a viewer-resolved ImageBitmap for
|
|
3141
|
+
* an external `IfcImageTexture` reference (#1781). A `textureRef` whose
|
|
3142
|
+
* image was NOT resolved (missing zip sibling) renders as ordinary
|
|
3143
|
+
* flat-colour geometry instead. */
|
|
3144
|
+
static hasRenderableTexture(meshData) {
|
|
3145
|
+
return Boolean(meshData.uvs) &&
|
|
3146
|
+
Boolean(meshData.texture || (meshData.textureRef && meshData.textureBitmap));
|
|
3147
|
+
}
|
|
3101
3148
|
/**
|
|
3102
3149
|
* Interleave a textured mesh's vertices into the stride-36 layout
|
|
3103
3150
|
* `[px,py,pz, nx,ny,nz, entityId(u32), u,v]`. Shared by initial upload and
|
|
@@ -3106,7 +3153,7 @@ export class Scene {
|
|
|
3106
3153
|
*/
|
|
3107
3154
|
interleaveTexturedVertices(meshData) {
|
|
3108
3155
|
const uvs = meshData.uvs;
|
|
3109
|
-
if (!meshData
|
|
3156
|
+
if (!Scene.hasRenderableTexture(meshData) || !uvs)
|
|
3110
3157
|
return null;
|
|
3111
3158
|
const positions = meshData.positions;
|
|
3112
3159
|
const normals = meshData.normals;
|
|
@@ -3137,8 +3184,10 @@ export class Scene {
|
|
|
3137
3184
|
}
|
|
3138
3185
|
createTexturedMesh(meshData, device, pipeline) {
|
|
3139
3186
|
const tex = meshData.texture;
|
|
3187
|
+
const ref = meshData.textureRef;
|
|
3188
|
+
const bitmap = meshData.textureBitmap;
|
|
3140
3189
|
const interleaved = this.interleaveTexturedVertices(meshData);
|
|
3141
|
-
if (!tex ||
|
|
3190
|
+
if (!interleaved || !(tex || (ref && bitmap)))
|
|
3142
3191
|
return;
|
|
3143
3192
|
this.texturedDevice = device; // reused by translateMeshesForEntity re-upload
|
|
3144
3193
|
const vertexBuffer = device.createBuffer({
|
|
@@ -3151,17 +3200,48 @@ export class Scene {
|
|
|
3151
3200
|
usage: GPUBufferUsage.INDEX | GPUBufferUsage.COPY_DST,
|
|
3152
3201
|
});
|
|
3153
3202
|
device.queue.writeBuffer(indexBuffer, 0, meshData.indices);
|
|
3154
|
-
|
|
3155
|
-
|
|
3156
|
-
|
|
3157
|
-
|
|
3158
|
-
|
|
3159
|
-
|
|
3160
|
-
|
|
3203
|
+
let texture;
|
|
3204
|
+
let sharedTextureKey;
|
|
3205
|
+
if (tex) {
|
|
3206
|
+
// #961: upload the Rust-decoded RGBA8 verbatim — no image decoding in JS.
|
|
3207
|
+
texture = device.createTexture({
|
|
3208
|
+
size: { width: tex.width, height: tex.height },
|
|
3209
|
+
format: 'rgba8unorm',
|
|
3210
|
+
usage: GPUTextureUsage.TEXTURE_BINDING | GPUTextureUsage.COPY_DST,
|
|
3211
|
+
});
|
|
3212
|
+
device.queue.writeTexture({ texture }, tex.rgba, { bytesPerRow: tex.width * 4, rowsPerImage: tex.height }, { width: tex.width, height: tex.height });
|
|
3213
|
+
}
|
|
3214
|
+
else {
|
|
3215
|
+
// #1781: external image texture — the viewer decoded the `.ifcZIP`
|
|
3216
|
+
// sibling to an ImageBitmap once per textureId; upload it ONCE and share
|
|
3217
|
+
// the GPU texture across every mesh sampling it (real files map one
|
|
3218
|
+
// 4096² image from dozens of face sets — per-mesh copies would be GBs).
|
|
3219
|
+
const refKey = ref.textureId;
|
|
3220
|
+
const bmp = bitmap;
|
|
3221
|
+
let entry = this.sharedTextures.get(refKey);
|
|
3222
|
+
if (!entry) {
|
|
3223
|
+
const gpuTex = device.createTexture({
|
|
3224
|
+
size: { width: bmp.width, height: bmp.height },
|
|
3225
|
+
format: 'rgba8unorm',
|
|
3226
|
+
// RENDER_ATTACHMENT is required by copyExternalImageToTexture.
|
|
3227
|
+
usage: GPUTextureUsage.TEXTURE_BINDING |
|
|
3228
|
+
GPUTextureUsage.COPY_DST |
|
|
3229
|
+
GPUTextureUsage.RENDER_ATTACHMENT,
|
|
3230
|
+
});
|
|
3231
|
+
device.queue.copyExternalImageToTexture({ source: bmp }, { texture: gpuTex }, { width: bmp.width, height: bmp.height });
|
|
3232
|
+
entry = { texture: gpuTex, refs: 0 };
|
|
3233
|
+
this.sharedTextures.set(refKey, entry);
|
|
3234
|
+
}
|
|
3235
|
+
entry.refs++;
|
|
3236
|
+
texture = entry.texture;
|
|
3237
|
+
sharedTextureKey = refKey;
|
|
3238
|
+
}
|
|
3239
|
+
const repeatS = tex ? tex.repeatS : ref.repeatS;
|
|
3240
|
+
const repeatT = tex ? tex.repeatT : ref.repeatT;
|
|
3161
3241
|
const wrap = (repeat) => (repeat ? 'repeat' : 'clamp-to-edge');
|
|
3162
3242
|
const sampler = device.createSampler({
|
|
3163
|
-
addressModeU: wrap(
|
|
3164
|
-
addressModeV: wrap(
|
|
3243
|
+
addressModeU: wrap(repeatS),
|
|
3244
|
+
addressModeV: wrap(repeatT),
|
|
3165
3245
|
magFilter: 'linear',
|
|
3166
3246
|
minFilter: 'linear',
|
|
3167
3247
|
mipmapFilter: 'linear',
|
|
@@ -3181,8 +3261,26 @@ export class Scene {
|
|
|
3181
3261
|
sampler,
|
|
3182
3262
|
bindGroup,
|
|
3183
3263
|
color: meshData.color,
|
|
3264
|
+
...(sharedTextureKey !== undefined ? { sharedTextureKey } : {}),
|
|
3184
3265
|
});
|
|
3185
3266
|
}
|
|
3267
|
+
/** Release a textured mesh's GPU texture: shared (#1781) entries decrement
|
|
3268
|
+
* the registry refcount and die with their LAST reference; per-mesh (#961)
|
|
3269
|
+
* uploads are destroyed outright. */
|
|
3270
|
+
releaseTexturedMeshTexture(tm) {
|
|
3271
|
+
if (tm.sharedTextureKey === undefined) {
|
|
3272
|
+
tm.texture.destroy();
|
|
3273
|
+
return;
|
|
3274
|
+
}
|
|
3275
|
+
const entry = this.sharedTextures.get(tm.sharedTextureKey);
|
|
3276
|
+
if (!entry)
|
|
3277
|
+
return;
|
|
3278
|
+
entry.refs--;
|
|
3279
|
+
if (entry.refs <= 0) {
|
|
3280
|
+
entry.texture.destroy();
|
|
3281
|
+
this.sharedTextures.delete(tm.sharedTextureKey);
|
|
3282
|
+
}
|
|
3283
|
+
}
|
|
3186
3284
|
clear() {
|
|
3187
3285
|
for (const mesh of this.meshes)
|
|
3188
3286
|
destroyGpuResources(mesh);
|
|
@@ -3192,9 +3290,14 @@ export class Scene {
|
|
|
3192
3290
|
tm.vertexBuffer.destroy();
|
|
3193
3291
|
tm.indexBuffer.destroy();
|
|
3194
3292
|
tm.uniformBuffer.destroy();
|
|
3195
|
-
|
|
3293
|
+
this.releaseTexturedMeshTexture(tm);
|
|
3196
3294
|
}
|
|
3197
3295
|
this.texturedMeshes = [];
|
|
3296
|
+
// Belt-and-braces: refcounting above should have emptied the registry;
|
|
3297
|
+
// destroy any straggler so clear() can never leak a shared GPU texture.
|
|
3298
|
+
for (const entry of this.sharedTextures.values())
|
|
3299
|
+
entry.texture.destroy();
|
|
3300
|
+
this.sharedTextures.clear();
|
|
3198
3301
|
// GPU-instancing templates own their vertex/index/instance buffers.
|
|
3199
3302
|
for (const it of this.instancedTemplates) {
|
|
3200
3303
|
it.vertexBuffer.destroy();
|