From 08a56ad6abe87da204ee1c3d4a4619ababc18777 Mon Sep 17 00:00:00 2001 From: Alexander Rose Date: Sat, 9 May 2026 08:10:58 -0700 Subject: [PATCH] Instance granularity improvements - Add `instanceGranularity: 'auto'` as a memory guard - Honor `instanceGranularity` in `Visual.getLoci` --- CHANGELOG.md | 2 ++ src/mol-geo/geometry/base.ts | 25 ++++++++++++++++--- src/mol-geo/geometry/cylinders/cylinders.ts | 4 +-- .../geometry/direct-volume/direct-volume.ts | 4 +-- src/mol-geo/geometry/image/image.ts | 4 +-- src/mol-geo/geometry/lines/lines.ts | 4 +-- src/mol-geo/geometry/mesh/mesh.ts | 4 +-- src/mol-geo/geometry/points/points.ts | 4 +-- src/mol-geo/geometry/spheres/spheres.ts | 4 +-- src/mol-geo/geometry/text/text.ts | 4 +-- .../geometry/texture-mesh/texture-mesh.ts | 4 +-- src/mol-repr/shape/representation.ts | 11 +++++--- src/mol-repr/structure/complex-visual.ts | 14 ++++++++--- src/mol-repr/structure/units-visual.ts | 14 ++++++++--- src/mol-repr/volume/visual.ts | 15 +++++++---- 15 files changed, 79 insertions(+), 38 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7dd345ac9..7a95530ad 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,8 @@ Note that since we don't clearly distinguish between a public and private interf ## [Unreleased] - Fix empty transforms default in `ShapeFromPly` +- Add `instanceGranularity: 'auto'` as a memory guard +- Honor `instanceGranularity` in `Visual.getLoci` ## [v5.9.0] - 2026-05-03 - Fix edge case when `PluginSpec.animations` is empty diff --git a/src/mol-geo/geometry/base.ts b/src/mol-geo/geometry/base.ts index f5711dbca..45cc63898 100644 --- a/src/mol-geo/geometry/base.ts +++ b/src/mol-geo/geometry/base.ts @@ -72,6 +72,25 @@ export function getColorSmoothingProps(smoothColors: PD.Values AutoInstanceGranularityThreshold; + return value; +} + +// + export namespace BaseGeometry { export const MaterialCategory: PD.Info = { category: 'Material' }; export const ShadingCategory: PD.Info = { category: 'Shading' }; @@ -88,7 +107,7 @@ export namespace BaseGeometry { clip: PD.Group(Clip.Params), emissive: PD.Numeric(0, { min: 0, max: 1, step: 0.01 }), density: PD.Numeric(0.2, { min: 0, max: 1, step: 0.01 }, { description: 'Density value to estimate object thickness.' }), - instanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }), + instanceGranularity: PD.Select('auto', InstanceGranularityOptions, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory. When set to `auto`, granularity is enabled if `groupCount * instanceCount` exceeds `AutoInstanceGranularityThreshold`.' }), lod: PD.Vec3(Vec3(), undefined, { ...CullingLodCategory, description: 'Level of detail.', fieldLabels: { x: 'Min Distance', y: 'Max Distance', z: 'Overlap (Shader)' } }), cellSize: PD.Numeric(200, { min: 0, max: 5000, step: 100 }, { ...CullingLodCategory, description: 'Instance grid cell size.' }), batchSize: PD.Numeric(2000, { min: 0, max: 50000, step: 500 }, { ...CullingLodCategory, description: 'Instance grid batch size.' }), @@ -130,7 +149,7 @@ export namespace BaseGeometry { uClipObjectScale: ValueCell.create(clip.objects.scale), uClipObjectTransform: ValueCell.create(clip.objects.transform), - instanceGranularity: ValueCell.create(props.instanceGranularity), + instanceGranularity: ValueCell.create(resolveInstanceGranularity(props.instanceGranularity, counts.groupCount, counts.instanceCount)), uLod: ValueCell.create(Vec4.create(props.lod[0], props.lod[1], props.lod[2], 0)), }; } @@ -153,7 +172,7 @@ export namespace BaseGeometry { ValueCell.update(values.uClipObjectScale, clip.objects.scale); ValueCell.update(values.uClipObjectTransform, clip.objects.transform); - ValueCell.updateIfChanged(values.instanceGranularity, props.instanceGranularity); + ValueCell.updateIfChanged(values.instanceGranularity, resolveInstanceGranularity(props.instanceGranularity, values.uGroupCount.ref.value, values.instanceCount.ref.value)); ValueCell.update(values.uLod, Vec4.set(values.uLod.ref.value, props.lod[0], props.lod[1], props.lod[2], 0)); } diff --git a/src/mol-geo/geometry/cylinders/cylinders.ts b/src/mol-geo/geometry/cylinders/cylinders.ts index 2d44e7aa6..43b6bea0b 100644 --- a/src/mol-geo/geometry/cylinders/cylinders.ts +++ b/src/mol-geo/geometry/cylinders/cylinders.ts @@ -19,7 +19,7 @@ import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } fr import { Sphere3D } from '../../../mol-math/geometry'; import { Theme } from '../../../mol-theme/theme'; import { Color } from '../../../mol-util/color'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; @@ -225,7 +225,7 @@ export namespace Cylinders { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, positionIt, theme.size); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index d9fa75c2a..e80e73bf2 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -17,7 +17,7 @@ import { ValueCell } from '../../../mol-util'; import { Color } from '../../../mol-util/color'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; import { Box } from '../../primitive/box'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createColors } from '../color-data'; import { GeometryUtils } from '../geometry'; import { createMarkers } from '../marker-data'; @@ -228,7 +228,7 @@ export namespace DirectVolume { const positionIt = createPositionIterator(directVolume, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/image/image.ts b/src/mol-geo/geometry/image/image.ts index fff42c10f..b45f38f68 100644 --- a/src/mol-geo/geometry/image/image.ts +++ b/src/mol-geo/geometry/image/image.ts @@ -14,7 +14,7 @@ import { Theme } from '../../../mol-theme/theme'; import { ValueCell } from '../../../mol-util'; import { Color } from '../../../mol-util/color'; import { ParamDefinition as PD } from '../../../mol-util/param-definition'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createColors } from '../color-data'; import { GeometryUtils } from '../geometry'; import { createMarkers } from '../marker-data'; @@ -201,7 +201,7 @@ namespace Image { const positionIt = createPositionIterator(image, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/lines/lines.ts b/src/mol-geo/geometry/lines/lines.ts index 75b52eb8f..4d744d296 100644 --- a/src/mol-geo/geometry/lines/lines.ts +++ b/src/mol-geo/geometry/lines/lines.ts @@ -21,7 +21,7 @@ import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } fr import { Sphere3D } from '../../../mol-math/geometry'; import { Theme } from '../../../mol-theme/theme'; import { Color } from '../../../mol-util/color'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; @@ -232,7 +232,7 @@ export namespace Lines { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, positionIt, theme.size); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/mesh/mesh.ts b/src/mol-geo/geometry/mesh/mesh.ts index 49f5f57cc..c6843a855 100644 --- a/src/mol-geo/geometry/mesh/mesh.ts +++ b/src/mol-geo/geometry/mesh/mesh.ts @@ -20,7 +20,7 @@ import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } fr import { Theme } from '../../../mol-theme/theme'; import { MeshValues } from '../../../mol-gl/renderable/mesh'; import { Color } from '../../../mol-util/color'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { createEmptyClipping } from '../clipping-data'; @@ -684,7 +684,7 @@ export namespace Mesh { const positionIt = createPositionIterator(mesh, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/points/points.ts b/src/mol-geo/geometry/points/points.ts index 797a04757..11f6b15b4 100644 --- a/src/mol-geo/geometry/points/points.ts +++ b/src/mol-geo/geometry/points/points.ts @@ -20,7 +20,7 @@ import { Theme } from '../../../mol-theme/theme'; import { PointsValues } from '../../../mol-gl/renderable/points'; import { RenderableState } from '../../../mol-gl/renderable'; import { Color } from '../../../mol-util/color'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; @@ -178,7 +178,7 @@ export namespace Points { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, positionIt, theme.size); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/spheres/spheres.ts b/src/mol-geo/geometry/spheres/spheres.ts index 3d99f4b9a..e6fb09c75 100644 --- a/src/mol-geo/geometry/spheres/spheres.ts +++ b/src/mol-geo/geometry/spheres/spheres.ts @@ -17,7 +17,7 @@ import { TextureImage, calculateInvariantBoundingSphere, calculateTransformBound import { Sphere3D } from '../../../mol-math/geometry'; import { createSizes, getMaxSize } from '../size-data'; import { Color } from '../../../mol-util/color'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; @@ -314,7 +314,7 @@ export namespace Spheres { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, positionIt, theme.size); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/text/text.ts b/src/mol-geo/geometry/text/text.ts index 77050d336..0abfb01c3 100644 --- a/src/mol-geo/geometry/text/text.ts +++ b/src/mol-geo/geometry/text/text.ts @@ -25,7 +25,7 @@ import { FontAtlasParams } from './font-atlas'; import { RenderableState } from '../../../mol-gl/renderable'; import { clamp } from '../../../mol-math/interpolate'; import { createRenderObject as _createRenderObject } from '../../../mol-gl/render-object'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { hashFnv32a } from '../../../mol-data/util'; @@ -219,7 +219,7 @@ export namespace Text { const color = createColors(locationIt, positionIt, theme.color); const size = createSizes(locationIt, positionIt, theme.size); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts index 3649decfe..e567b9638 100644 --- a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts +++ b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts @@ -15,7 +15,7 @@ import { createMarkers } from '../marker-data'; import { GeometryUtils } from '../geometry'; import { Theme } from '../../../mol-theme/theme'; import { Color } from '../../../mol-util/color'; -import { BaseGeometry } from '../base'; +import { BaseGeometry, resolveInstanceGranularity } from '../base'; import { createEmptyOverpaint } from '../overpaint-data'; import { createEmptyTransparency } from '../transparency-data'; import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh'; @@ -203,7 +203,7 @@ export namespace TextureMesh { const positionIt = Utils.createPositionIterator(textureMesh, transform); const color = createColors(locationIt, positionIt, theme.color); - const marker = props.instanceGranularity + const marker = resolveInstanceGranularity(props.instanceGranularity, groupCount, instanceCount) ? createMarkers(instanceCount, 'instance') : createMarkers(instanceCount * groupCount, 'groupInstance'); const overpaint = createEmptyOverpaint(); diff --git a/src/mol-repr/shape/representation.ts b/src/mol-repr/shape/representation.ts index ff326232f..a36e8dd50 100644 --- a/src/mol-repr/shape/representation.ts +++ b/src/mol-repr/shape/representation.ts @@ -5,6 +5,7 @@ */ import { Geometry, GeometryUtils } from '../../mol-geo/geometry/geometry'; +import { resolveInstanceGranularity } from '../../mol-geo/geometry/base'; import { Representation } from '../representation'; import { Shape, ShapeGroup } from '../../mol-model/shape'; import { Subject } from 'rxjs'; @@ -129,7 +130,7 @@ export function ShapeRepresentation boolean) { + const instanceGranularity = resolveInstanceGranularity(currentProps.instanceGranularity, _shape.groupCount, _shape.transforms.length); if (isEveryLoci(loci) || (Shape.isLoci(loci) && loci.shape === _shape)) { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return apply(Interval.ofBounds(0, _shape.transforms.length)); } else { return apply(Interval.ofBounds(0, _shape.groupCount * _shape.transforms.length)); } } else { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return eachInstance(loci, _shape, apply); } else { return eachShapeGroup(loci, _shape, apply); @@ -226,7 +228,8 @@ export function ShapeRepresentation boolean, isMarking: boolean) { + const instanceGranularity = resolveInstanceGranularity(currentProps.instanceGranularity, locationIt.groupCount, locationIt.instanceCount); if (lociIsSuperset(loci)) { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return apply(Interval.ofBounds(0, locationIt.instanceCount)); } else { return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); } } else { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return eachInstance(loci, currentStructure, apply); } else { return eachLocation(loci, currentStructure, apply, isMarking); @@ -279,7 +281,11 @@ export function ComplexVisual boolean, isMarking: boolean) { + const instanceGranularity = resolveInstanceGranularity(currentProps.instanceGranularity, locationIt.groupCount, locationIt.instanceCount); if (lociIsSuperset(loci)) { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return apply(Interval.ofBounds(0, locationIt.instanceCount)); } else { return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); } } else { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return eachInstance(loci, currentStructureGroup, apply); } else { return eachLocation(loci, currentStructureGroup, apply, isMarking); @@ -355,7 +357,11 @@ export function UnitsVisual boolean) { + const instanceGranularity = resolveInstanceGranularity(currentProps.instanceGranularity, locationIt.groupCount, locationIt.instanceCount); if (isEveryLoci(loci)) { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return apply(Interval.ofBounds(0, locationIt.instanceCount)); } else { return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount)); } } else { - if (currentProps.instanceGranularity) { + if (instanceGranularity) { return eachInstance(loci, currentVolume, currentKey, apply); } else { return eachLocation(loci, currentVolume, currentKey, currentProps, apply, geometry); @@ -308,7 +309,11 @@ export function VolumeVisual