diff --git a/src/mol-model/custom-property.ts b/src/mol-model/custom-property.ts index 0bf52fa2cfc7a015742774d7cd857562ae19dee2..04c9aad0fb31a51e13ccbc6fd68c3b2352ada585 100644 --- a/src/mol-model/custom-property.ts +++ b/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(); diff --git a/src/mol-plugin-state/transforms/volume.ts b/src/mol-plugin-state/transforms/volume.ts index 34682eeab79a06ed856b8cc8500c69d324390702..a74e276422be6bc4887c37a1c4e6f3d5f720de7c 100644 --- a/src/mol-plugin-state/transforms/volume.ts +++ b/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(); } }); diff --git a/src/mol-repr/structure/complex-visual.ts b/src/mol-repr/structure/complex-visual.ts index 4a4df2e8268931e8b14a6b22b9cf309915d23197..dcda03a22468527885fa793d9f2266d903dd1267 100644 --- a/src/mol-repr/structure/complex-visual.ts +++ b/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 diff --git a/src/mol-repr/structure/units-visual.ts b/src/mol-repr/structure/units-visual.ts index 55552264cc1eba4b66911340d8e2a142fe4cae20..c270d7c7cb8b600f8866964fbd2b83bfdacc13b9 100644 --- a/src/mol-repr/structure/units-visual.ts +++ b/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 diff --git a/src/mol-repr/structure/visual/gaussian-density-volume.ts b/src/mol-repr/structure/visual/gaussian-density-volume.ts index 7b8405d3d1cbe3559d1a9141056e0fccef8444b6..925f8fe2539f3a41efc088659c1e28cef8f06c8a 100644 --- a/src/mol-repr/structure/visual/gaussian-density-volume.ts +++ b/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); } \ No newline at end of file diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts index 8daac0e0fc221cf2f2167ca98cdf4469ddb0ac58..546d98e3a8c817228209d2eadd93ff3df5f173dd 100644 --- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts +++ b/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); } \ No newline at end of file diff --git a/src/mol-repr/volume/direct-volume.ts b/src/mol-repr/volume/direct-volume.ts index 89af5d7bcd2f5e03394bbd75fb8c7e961d6c0530..88d1bf36f68b75a05296e19fa245c7e00b14e3ec 100644 --- a/src/mol-repr/volume/direct-volume.ts +++ b/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); } diff --git a/src/mol-repr/volume/isosurface.ts b/src/mol-repr/volume/isosurface.ts index a71b7dc29d5e8da510b2ee9453e251082cd54219..7ffac46b5aa5db42b22551c52702d836e90b7a79 100644 --- a/src/mol-repr/volume/isosurface.ts +++ b/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); } diff --git a/src/mol-repr/volume/representation.ts b/src/mol-repr/volume/representation.ts index 2b8e32b9955bd82ee7e1f530fe7412873ed21f71..67e11b2b26ced42d0d652bbdacb69af9bf6ddfe9 100644 --- a/src/mol-repr/volume/representation.ts +++ b/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