Преглед на файлове

dispose of volume & repr associated textures

Alexander Rose преди 4 години
родител
ревизия
f79f1507f7

+ 8 - 4
src/mol-model/custom-property.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author David Sehnal <david.sehnal@gmail.com>
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
@@ -9,7 +9,6 @@ import { CifWriter } from '../mol-io/writer/cif';
 import { CifExportContext } from './structure/export/mmcif';
 import { QuerySymbolRuntime } from '../mol-script/runtime/query/compiler';
 import { UUID } from '../mol-util';
-import { Asset } from '../mol-util/assets';
 
 export { CustomPropertyDescriptor, CustomProperties };
 
@@ -40,11 +39,16 @@ namespace CustomPropertyDescriptor {
     }
 }
 
+/**
+ * Anything with a dispose method, used to despose of data assets or webgl resources
+ */
+type Asset = { dispose: () => void }
+
 class CustomProperties {
     private _list: CustomPropertyDescriptor[] = [];
     private _set = new Set<CustomPropertyDescriptor>();
     private _refs = new Map<CustomPropertyDescriptor, number>();
-    private _assets = new Map<CustomPropertyDescriptor, Asset.Wrapper[]>();
+    private _assets = new Map<CustomPropertyDescriptor, Asset[]>();
 
     get all(): ReadonlyArray<CustomPropertyDescriptor> {
         return this._list;
@@ -72,7 +76,7 @@ class CustomProperties {
     }
 
     /** Sets assets for a prop, disposes of existing assets for that prop */
-    assets(desc: CustomPropertyDescriptor<any>, assets?: Asset.Wrapper[]) {
+    assets(desc: CustomPropertyDescriptor<any>, assets?: Asset[]) {
         const prevAssets = this._assets.get(desc);
         if (prevAssets) {
             for (const a of prevAssets) a.dispose();

+ 15 - 0
src/mol-plugin-state/transforms/volume.ts

@@ -46,6 +46,9 @@ const VolumeFromCcp4 = PluginStateTransform.BuiltIn({
             const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.NX}\u00D7${a.data.header.NX}\u00D7${a.data.header.NX}` };
             return new SO.Volume.Data(volume, props);
         });
+    },
+    dispose({ b }) {
+        b?.data.customProperties.dispose();
     }
 });
 
@@ -68,6 +71,9 @@ const VolumeFromDsn6 = PluginStateTransform.BuiltIn({
             const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.xExtent}\u00D7${a.data.header.yExtent}\u00D7${a.data.header.zExtent}` };
             return new SO.Volume.Data(volume, props);
         });
+    },
+    dispose({ b }) {
+        b?.data.customProperties.dispose();
     }
 });
 
@@ -91,6 +97,9 @@ const VolumeFromCube = PluginStateTransform.BuiltIn({
             const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
             return new SO.Volume.Data(volume, props);
         });
+    },
+    dispose({ b }) {
+        b?.data.customProperties.dispose();
     }
 });
 
@@ -107,6 +116,9 @@ const VolumeFromDx = PluginStateTransform.BuiltIn({
             const props = { label: volume.label || 'Volume', description: `Volume ${a.data.header.dim[0]}\u00D7${a.data.header.dim[1]}\u00D7${a.data.header.dim[2]}` };
             return new SO.Volume.Data(volume, props);
         });
+    },
+    dispose({ b }) {
+        b?.data.customProperties.dispose();
     }
 });
 
@@ -142,6 +154,9 @@ const VolumeFromDensityServerCif = PluginStateTransform.BuiltIn({
             const props = { label: densityServerCif.volume_data_3d_info.name.value(0), description: `Volume ${x}\u00D7${y}\u00D7${z}` };
             return new SO.Volume.Data(volume, props);
         });
+    },
+    dispose({ b }) {
+        b?.data.customProperties.dispose();
     }
 });
 

+ 3 - 2
src/mol-repr/structure/complex-visual.ts

@@ -53,6 +53,7 @@ interface ComplexVisualBuilder<P extends StructureParams, G extends Geometry> {
     eachLocation(loci: Loci, structure: Structure, apply: (interval: Interval) => boolean, isMarking: boolean): boolean,
     setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructure: Structure, currentStructure: Structure): void
     mustRecreate?: (props: PD.Values<P>) => boolean
+    dispose?: (geometry: G) => void
 }
 
 interface ComplexVisualGeometryBuilder<P extends StructureParams, G extends Geometry> extends ComplexVisualBuilder<P, G> {
@@ -60,7 +61,7 @@ interface ComplexVisualGeometryBuilder<P extends StructureParams, G extends Geom
 }
 
 export function ComplexVisual<G extends Geometry, P extends StructureParams & Geometry.Params<G>>(builder: ComplexVisualGeometryBuilder<P, G>, materialId: number): ComplexVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate } = builder;
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, dispose } = builder;
     const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
@@ -240,7 +241,7 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
             Visual.setClipping(renderObject, clipping, lociApply, true);
         },
         destroy() {
-            // TODO
+            dispose?.(geometry);
             renderObject = undefined;
         },
         mustRecreate

+ 3 - 2
src/mol-repr/structure/units-visual.ts

@@ -59,6 +59,7 @@ interface UnitsVisualBuilder<P extends StructureParams, G extends Geometry> {
     eachLocation(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean, isMarking: boolean): boolean
     setUpdateState(state: VisualUpdateState, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme, newStructureGroup: StructureGroup, currentStructureGroup: StructureGroup): void
     mustRecreate?: (props: PD.Values<P>) => boolean
+    dispose?: (geometry: G) => void
 }
 
 interface UnitsVisualGeometryBuilder<P extends StructureParams, G extends Geometry> extends UnitsVisualBuilder<P, G> {
@@ -66,7 +67,7 @@ interface UnitsVisualGeometryBuilder<P extends StructureParams, G extends Geomet
 }
 
 export function UnitsVisual<G extends Geometry, P extends StructureParams & Geometry.Params<G>>(builder: UnitsVisualGeometryBuilder<P, G>, materialId: number): UnitsVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate } = builder;
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, dispose } = builder;
     const { createEmpty: createEmptyGeometry, updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
@@ -292,7 +293,7 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
             Visual.setClipping(renderObject, clipping, lociApply, true);
         },
         destroy() {
-            // TODO
+            dispose?.(geometry);
             renderObject = undefined;
         },
         mustRecreate

+ 6 - 0
src/mol-repr/structure/visual/gaussian-density-volume.ts

@@ -60,6 +60,9 @@ export function GaussianDensityVolumeVisual(materialId: number): ComplexVisual<G
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
+        },
+        dispose: (geometry: DirectVolume) => {
+            geometry.gridTexture.ref.value.destroy();
         }
     }, materialId);
 }
@@ -108,6 +111,9 @@ export function UnitsGaussianDensityVolumeVisual(materialId: number): UnitsVisua
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
+        },
+        dispose: (geometry: DirectVolume) => {
+            geometry.gridTexture.ref.value.destroy();
         }
     }, materialId);
 }

+ 8 - 0
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -212,6 +212,10 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
         },
         mustRecreate: (props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
             return !props.useGpu || !webgl;
+        },
+        dispose: (geometry: TextureMesh) => {
+            geometry.normalTexture.ref.value.destroy();
+            geometry.vertexGroupTexture.ref.value.destroy();
         }
     }, materialId);
 }
@@ -281,6 +285,10 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
         },
         mustRecreate: (props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
             return !props.useGpu || !webgl;
+        },
+        dispose: (geometry: TextureMesh) => {
+            geometry.normalTexture.ref.value.destroy();
+            geometry.vertexGroupTexture.ref.value.destroy();
         }
     }, materialId);
 }

+ 4 - 3
src/mol-repr/volume/direct-volume.ts

@@ -39,7 +39,6 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
     const transform = Grid.getGridToCartesianTransform(volume.grid);
     const bbox = getBoundingBox(gridDimension, transform);
 
-    // TODO: handle disposal
     const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.load(textureImage);
 
@@ -77,7 +76,6 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
     const transform = Grid.getGridToCartesianTransform(volume.grid);
     const bbox = getBoundingBox(gridDimension, transform);
 
-    // TODO: handle disposal
     const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
     texture.load(textureVolume);
 
@@ -139,7 +137,10 @@ export function DirectVolumeVisual(materialId: number): VolumeVisual<DirectVolum
         eachLocation: eachDirectVolume,
         setUpdateState: (state: VisualUpdateState, volume: Volume, newProps: PD.Values<DirectVolumeParams>, currentProps: PD.Values<DirectVolumeParams>) => {
         },
-        geometryUtils: DirectVolume.Utils
+        geometryUtils: DirectVolume.Utils,
+        dispose: (geometry: DirectVolume) => {
+            geometry.gridTexture.ref.value.destroy();
+        }
     }, materialId);
 }
 

+ 43 - 23
src/mol-repr/volume/isosurface.ts

@@ -28,6 +28,8 @@ import { calcActiveVoxels } from '../../mol-gl/compute/marching-cubes/active-vox
 import { createHistogramPyramid } from '../../mol-gl/compute/histogram-pyramid/reduction';
 import { createIsosurfaceBuffers } from '../../mol-gl/compute/marching-cubes/isosurface';
 import { WebGLContext } from '../../mol-gl/webgl/context';
+import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
+import { Texture } from '../../mol-gl/webgl/texture';
 
 export const VolumeIsosurfaceParams = {
     isoValue: Volume.IsoValueParam
@@ -111,38 +113,52 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
 
 //
 
-async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, theme: Theme, props: VolumeIsosurfaceProps, textureMesh?: TextureMesh) {
-    if (!ctx.webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
+namespace VolumeIsosurfaceTexture {
+    const name = 'volume-isosurface-texture';
+    export const descriptor = CustomPropertyDescriptor({ name });
+    export function get(volume: Volume, webgl: WebGLContext) {
+        const { resources } = webgl;
+
+        const padding = 1;
+        const transform = Grid.getGridToCartesianTransform(volume.grid);
+        const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
+        const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, padding);
+        const gridTexDim = Vec3.create(width, height, 0);
+        const gridTexScale = Vec2.create(width / texDim, height / texDim);
+        // console.log({ texDim, width, height, gridDimension });
+
+        if (!volume._propertyData[name]) {
+            volume._propertyData[name] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
+            const texture = volume._propertyData[name] as Texture;
+            texture.define(texDim, texDim);
+            // load volume into sub-section of texture
+            texture.load(createVolumeTexture2d(volume, 'groups', padding), true);
+            volume.customProperties.add(descriptor);
+            volume.customProperties.assets(descriptor, [{ dispose: () => texture.destroy() }]);
+        }
 
-    const { resources } = ctx.webgl;
-    if (!volume._propertyData['texture2d']) {
-        // TODO: handle disposal
-        volume._propertyData['texture2d'] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
-    }
-    const texture = volume._propertyData['texture2d'];
+        gridDimension[0] += padding;
+        gridDimension[1] += padding;
 
-    const padding = 1;
-    const transform = Grid.getGridToCartesianTransform(volume.grid);
-    const gridDimension = Vec3.clone(volume.grid.cells.space.dimensions as Vec3);
-    const { width, height, powerOfTwoSize: texDim } = getVolumeTexture2dLayout(gridDimension, padding);
-    const gridTexDim = Vec3.create(width, height, 0);
-    const gridTexScale = Vec2.create(width / texDim, height / texDim);
-    // console.log({ texDim, width, height, gridDimension });
-
-    if (!textureMesh) {
-        // set to power-of-two size required for histopyramid calculation
-        texture.define(texDim, texDim);
-        // load volume into sub-section of texture
-        texture.load(createVolumeTexture2d(volume, 'groups', padding), true);
+        return {
+            texture: volume._propertyData[name] as Texture,
+            transform,
+            gridDimension,
+            gridTexDim,
+            gridTexScale
+        };
     }
+}
+
+async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Volume, theme: Theme, props: VolumeIsosurfaceProps, textureMesh?: TextureMesh) {
+    if (!ctx.webgl) throw new Error('webgl context required to create volume isosurface texture-mesh');
 
     const { max, min } = volume.grid.stats;
     const diff = max - min;
     const value = Volume.IsoValue.toAbsolute(props.isoValue, volume.grid.stats).absoluteValue;
     const isoLevel = ((value - min) / diff);
 
-    gridDimension[0] += padding;
-    gridDimension[1] += padding;
+    const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
 
     // console.time('calcActiveVoxels');
     const activeVoxelsTex = calcActiveVoxels(ctx.webgl, texture, gridDimension, gridTexDim, isoLevel, gridTexScale);
@@ -182,6 +198,10 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
         geometryUtils: TextureMesh.Utils,
         mustRecreate: (props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
             return !props.useGpu || !webgl;
+        },
+        dispose: (geometry: TextureMesh) => {
+            geometry.normalTexture.ref.value.destroy();
+            geometry.vertexGroupTexture.ref.value.destroy();
         }
     }, materialId);
 }

+ 3 - 2
src/mol-repr/volume/representation.ts

@@ -50,6 +50,7 @@ interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
     eachLocation(loci: Loci, volume: Volume, props: PD.Values<P>, apply: (interval: Interval) => boolean): boolean
     setUpdateState(state: VisualUpdateState, volume: Volume, newProps: PD.Values<P>, currentProps: PD.Values<P>, newTheme: Theme, currentTheme: Theme): void
     mustRecreate?: (props: PD.Values<P>) => boolean
+    dispose?: (geometry: G) => void
 }
 
 interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry> extends VolumeVisualBuilder<P, G> {
@@ -57,7 +58,7 @@ interface VolumeVisualGeometryBuilder<P extends VolumeParams, G extends Geometry
 }
 
 export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geometry.Params<G>>(builder: VolumeVisualGeometryBuilder<P, G>, materialId: number): VolumeVisual<P> {
-    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate } = builder;
+    const { defaultProps, createGeometry, createLocationIterator, getLoci, eachLocation, setUpdateState, mustRecreate, dispose } = builder;
     const { updateValues, updateBoundingSphere, updateRenderableState, createPositionIterator } = builder.geometryUtils;
     const updateState = VisualUpdateState.create();
 
@@ -208,7 +209,7 @@ export function VolumeVisual<G extends Geometry, P extends VolumeParams & Geomet
             return Visual.setClipping(renderObject, clipping, lociApply, true);
         },
         destroy() {
-            // TODO
+            dispose?.(geometry);
             renderObject = undefined;
         },
         mustRecreate