Dispose a Navara layer effect's pass when the layer detaches so engine switches stop retaining its material and textures

Layers re-create their ground effects on every attach. Navara's removePass leaves the EffectPass undisposed, so each switch kept its EffectMaterial in three's program and material caches, together with the uniforms' DataTextures and their Float32Array data (earthquakes: about 47 KB of buffers and one WebGL program per switch). engine.LayerEffectDesc now disposes the raw pass on delete, as the app's own effects do.
This commit is contained in:
German Khokhlov committed 2026-11-04 00:07:15 +04:00
1 parent 726e201314
commit ebc717df8b
3 files changed
+59 -3

No files matched your search

+9
View File
@@ -33,3 +33,12 @@ export function withPassDisposal(Base) {
} }
}; };
} }
// The base the layers' Navara effects extend (engine.LayerEffectDesc): per
// frame updates on their own callback, and the raw pass disposed when the
// effect is deleted. A layer re-creates its effect on every attach (an
// engine switch), so an undisposed pass kept its material, its shader
// program and the uniforms' data textures alive for the page's lifetime.
export function layerEffectDesc(EffectDesc) {
return withPassDisposal(withFrameUpdates(EffectDesc));
}
+48 -1
View File
@@ -1,6 +1,10 @@
import assert from 'node:assert/strict'; import assert from 'node:assert/strict';
import test from 'node:test'; import test from 'node:test';
import { withFrameUpdates, withPassDisposal } from './frameDescriptor.js'; import {
layerEffectDesc,
withFrameUpdates,
withPassDisposal,
} from './frameDescriptor.js';
test('deleting a descriptor unsubscribes only that renderer from the warm view', () => { test('deleting a descriptor unsubscribes only that renderer from the warm view', () => {
const frames = new Set(); const frames = new Set();
@@ -77,3 +81,46 @@ test('a deleted effect disposes the pass the composer no longer holds', () => {
assert.throws(() => new (withPassDisposal(Failing))().onDestroy(), /removal/); assert.throws(() => new (withPassDisposal(Failing))().onDestroy(), /removal/);
assert.deepEqual(events, ['dispose']); assert.deepEqual(events, ['dispose']);
}); });
test('a layer effect re-created on every attach leaves no pass, material or frame callback behind', () => {
const frames = new Set();
const view = {
on: (name, cb) => frames.add(cb),
off: (name, cb) => frames.delete(cb),
};
const live = new Set();
class EffectDesc {
view = view;
onCreate() {
const pass = {
material: {},
dispose: () => live.delete(pass),
};
live.add(pass);
this._instance = { rawPass: pass };
}
get raw() {
return this._instance;
}
onDestroy() {
// Navara's removePass: out of the composer, not disposed.
this._instance = undefined;
}
}
// As the earthquakes ground effect: disposes its own effect, then super.
class LayerEffect extends layerEffectDesc(EffectDesc) {
updateFrame() {}
onDestroy() {
this.effectDisposed = true;
super.onDestroy();
}
}
for (let attach = 0; attach < 50; attach++) {
const effect = new LayerEffect();
effect.onCreate();
effect.onDestroy();
assert.equal(effect.effectDisposed, true);
}
assert.equal(live.size, 0, 'every pass of a deleted effect is disposed');
assert.equal(frames.size, 0, 'no frame callback outlives its effect');
});
+2 -2
View File
@@ -2,7 +2,7 @@
// the product way (viewOptions.js) and the GlobeEngine on it. Load it with a // the product way (viewOptions.js) and the GlobeEngine on it. Load it with a
// dynamic import() so Navara stays out of the Cesium-mode bundle. // dynamic import() so Navara stays out of the Cesium-mode bundle.
import { withFrameUpdates } from './frameDescriptor.js'; import { layerEffectDesc, withFrameUpdates } from './frameDescriptor.js';
import ThreeView, { import ThreeView, {
Color as NavaraColor, Color as NavaraColor,
MeshDesc, MeshDesc,
@@ -241,7 +241,7 @@ export async function createNavaraGlobe({
NavaraColor, NavaraColor,
MeshDesc, MeshDesc,
EffectDesc, EffectDesc,
LayerEffectDesc: withFrameUpdates(EffectDesc), LayerEffectDesc: layerEffectDesc(EffectDesc),
NavaraEffect, NavaraEffect,
DrapedMesh, DrapedMesh,
setupMaterialForDrape, setupMaterialForDrape,