Browse Source

add useInstanceGranularity option

- for marker, transparency, clipping, overpaint, substance data
- saves memory
Alexander Rose 2 years ago
parent
commit
99759b5282

+ 2 - 0
CHANGELOG.md

@@ -6,8 +6,10 @@ Note that since we don't clearly distinguish between a public and private interf
 
 ## [Unreleased]
 
+- Add ``useInstanceGranularity`` option for marker, transparency, clipping, overpaint, substance data to save memory
 - CellPack extension tweaks
     - Use instancing to create DNA/RNA curves to save memory
+    - Enable ``useInstanceGranularity`` by default
 
 ## [v3.9.0] - 2022-05-30
 

+ 3 - 1
src/extensions/cellpack/preset.ts

@@ -27,7 +27,8 @@ export const CellpackPackingPreset = StructureRepresentationPresetProvider({
 
         const reprProps = {
             ignoreHydrogens: true,
-            traceOnly: params.traceOnly
+            traceOnly: params.traceOnly,
+            forceInstanceTheme: true,
         };
         const components = {
             polymer: await presetStaticComponent(plugin, structureCell, 'polymer')
@@ -71,6 +72,7 @@ export const CellpackMembranePreset = StructureRepresentationPresetProvider({
 
         const reprProps = {
             ignoreHydrogens: true,
+            forceInstanceTheme: true,
         };
         const components = {
             membrane: await presetStaticComponent(plugin, structureCell, 'all', { label: 'Membrane' })

+ 6 - 1
src/mol-geo/geometry/base.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -83,6 +83,7 @@ export namespace BaseGeometry {
         quality: PD.Select<VisualQuality>('auto', VisualQualityOptions, { isEssential: true, description: 'Visual/rendering quality of the representation.' }),
         material: Material.getParam(),
         clip: PD.Group(Clip.Params),
+        useInstanceGranularity: PD.Boolean(false, { description: 'Use instance granularity for marker, transparency, clipping, overpaint, substance data to save memory.' }),
     };
     export type Params = typeof Params
 
@@ -118,6 +119,8 @@ export namespace BaseGeometry {
             uClipObjectPosition: ValueCell.create(clip.objects.position),
             uClipObjectRotation: ValueCell.create(clip.objects.rotation),
             uClipObjectScale: ValueCell.create(clip.objects.scale),
+
+            useInstanceGranularity: ValueCell.create(props.useInstanceGranularity),
         };
     }
 
@@ -135,6 +138,8 @@ export namespace BaseGeometry {
         ValueCell.update(values.uClipObjectPosition, clip.objects.position);
         ValueCell.update(values.uClipObjectRotation, clip.objects.rotation);
         ValueCell.update(values.uClipObjectScale, clip.objects.scale);
+
+        ValueCell.updateIfChanged(values.useInstanceGranularity, props.useInstanceGranularity);
     }
 
     export function createRenderableState(props: Partial<PD.Values<Params>> = {}): RenderableState {

+ 8 - 2
src/mol-geo/geometry/clipping-data.ts

@@ -9,10 +9,13 @@ import { Vec2 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { Clipping } from '../../mol-theme/clipping';
 
+export type ClippingType = 'instance' | 'groupInstance';
+
 export type ClippingData = {
     tClipping: ValueCell<TextureImage<Uint8Array>>
     uClippingTexDim: ValueCell<Vec2>
-    dClipping: ValueCell<boolean>,
+    dClipping: ValueCell<boolean>
+    dClippingType: ValueCell<string>
 }
 
 export function applyClippingGroups(array: Uint8Array, start: number, end: number, groups: Clipping.Groups) {
@@ -24,18 +27,20 @@ export function clearClipping(array: Uint8Array, start: number, end: number) {
     array.fill(0, start, end);
 }
 
-export function createClipping(count: number, clippingData?: ClippingData): ClippingData {
+export function createClipping(count: number, type: ClippingType, clippingData?: ClippingData): ClippingData {
     const clipping = createTextureImage(Math.max(1, count), 1, Uint8Array, clippingData && clippingData.tClipping.ref.value.array);
     if (clippingData) {
         ValueCell.update(clippingData.tClipping, clipping);
         ValueCell.update(clippingData.uClippingTexDim, Vec2.create(clipping.width, clipping.height));
         ValueCell.updateIfChanged(clippingData.dClipping, count > 0);
+        ValueCell.updateIfChanged(clippingData.dClippingType, type);
         return clippingData;
     } else {
         return {
             tClipping: ValueCell.create(clipping),
             uClippingTexDim: ValueCell.create(Vec2.create(clipping.width, clipping.height)),
             dClipping: ValueCell.create(count > 0),
+            dClippingType: ValueCell.create(type),
         };
     }
 }
@@ -52,6 +57,7 @@ export function createEmptyClipping(clippingData?: ClippingData): ClippingData {
             tClipping: ValueCell.create(emptyClippingTexture),
             uClippingTexDim: ValueCell.create(Vec2.create(1, 1)),
             dClipping: ValueCell.create(false),
+            dClippingType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/cylinders/cylinders.ts

@@ -201,7 +201,9 @@ export namespace Cylinders {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -211,7 +211,9 @@ export namespace DirectVolume {
         const positionIt = Utils.createPositionIterator(directVolume, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/image/image.ts

@@ -143,7 +143,9 @@ namespace Image {
         const positionIt = Utils.createPositionIterator(image, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/lines/lines.ts

@@ -208,7 +208,9 @@ export namespace Lines {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 9 - 3
src/mol-geo/geometry/marker-data.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2022 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -8,12 +8,15 @@ import { ValueCell } from '../../mol-util/value-cell';
 import { Vec2 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 
+export type MarkerType = 'instance' | 'groupInstance';
+
 export type MarkerData = {
-    uMarker: ValueCell<number>,
+    uMarker: ValueCell<number>
     tMarker: ValueCell<TextureImage<Uint8Array>>
     uMarkerTexDim: ValueCell<Vec2>
     markerAverage: ValueCell<number>
     markerStatus: ValueCell<number>
+    dMarkerType: ValueCell<string>
 }
 
 const MarkerCountLut = new Uint8Array(0x0303 + 1);
@@ -64,7 +67,7 @@ export function getMarkersAverage(array: Uint8Array, count: number): number {
     return sum / count;
 }
 
-export function createMarkers(count: number, markerData?: MarkerData): MarkerData {
+export function createMarkers(count: number, type: MarkerType, markerData?: MarkerData): MarkerData {
     const markers = createTextureImage(Math.max(1, count), 1, Uint8Array, markerData && markerData.tMarker.ref.value.array);
     const average = getMarkersAverage(markers.array, count);
     const status = average === 0 ? 0 : -1;
@@ -74,6 +77,7 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
         ValueCell.update(markerData.uMarkerTexDim, Vec2.create(markers.width, markers.height));
         ValueCell.updateIfChanged(markerData.markerAverage, average);
         ValueCell.updateIfChanged(markerData.markerStatus, status);
+        ValueCell.updateIfChanged(markerData.dMarkerType, type);
         return markerData;
     } else {
         return {
@@ -82,6 +86,7 @@ export function createMarkers(count: number, markerData?: MarkerData): MarkerDat
             uMarkerTexDim: ValueCell.create(Vec2.create(markers.width, markers.height)),
             markerAverage: ValueCell.create(average),
             markerStatus: ValueCell.create(status),
+            dMarkerType: ValueCell.create(type),
         };
     }
 }
@@ -102,6 +107,7 @@ export function createEmptyMarkers(markerData?: MarkerData): MarkerData {
             uMarkerTexDim: ValueCell.create(Vec2.create(1, 1)),
             markerAverage: ValueCell.create(0),
             markerStatus: ValueCell.create(0),
+            dMarkerType: ValueCell.create('groupInstance'),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/mesh/mesh.ts

@@ -666,7 +666,9 @@ export namespace Mesh {
         const positionIt = createPositionIterator(mesh, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 5 - 2
src/mol-geo/geometry/overpaint-data.ts

@@ -10,6 +10,8 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { Color } from '../../mol-util/color';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
+export type OverpaintType = 'instance' | 'groupInstance' | 'volumeInstance';
+
 export type OverpaintData = {
     tOverpaint: ValueCell<TextureImage<Uint8Array>>
     uOverpaintTexDim: ValueCell<Vec2>
@@ -34,12 +36,13 @@ export function clearOverpaint(array: Uint8Array, start: number, end: number) {
     return true;
 }
 
-export function createOverpaint(count: number, overpaintData?: OverpaintData): OverpaintData {
+export function createOverpaint(count: number, type: OverpaintType, overpaintData?: OverpaintData): OverpaintData {
     const overpaint = createTextureImage(Math.max(1, count), 4, Uint8Array, overpaintData && overpaintData.tOverpaint.ref.value.array);
     if (overpaintData) {
         ValueCell.update(overpaintData.tOverpaint, overpaint);
         ValueCell.update(overpaintData.uOverpaintTexDim, Vec2.create(overpaint.width, overpaint.height));
         ValueCell.updateIfChanged(overpaintData.dOverpaint, count > 0);
+        ValueCell.updateIfChanged(overpaintData.dOverpaintType, type);
         return overpaintData;
     } else {
         return {
@@ -50,7 +53,7 @@ export function createOverpaint(count: number, overpaintData?: OverpaintData): O
             tOverpaintGrid: ValueCell.create(createNullTexture()),
             uOverpaintGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
             uOverpaintGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
-            dOverpaintType: ValueCell.create('groupInstance'),
+            dOverpaintType: ValueCell.create(type),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/points/points.ts

@@ -170,7 +170,9 @@ export namespace Points {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/spheres/spheres.ts

@@ -171,7 +171,9 @@ export namespace Spheres {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const material = createEmptySubstance();

+ 5 - 2
src/mol-geo/geometry/substance-data.ts

@@ -10,6 +10,8 @@ import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 import { Material } from '../../mol-util/material';
 
+export type SubstanceType = 'instance' | 'groupInstance' | 'volumeInstance';
+
 export type SubstanceData = {
     tSubstance: ValueCell<TextureImage<Uint8Array>>
     uSubstanceTexDim: ValueCell<Vec2>
@@ -34,12 +36,13 @@ export function clearSubstance(array: Uint8Array, start: number, end: number) {
     return true;
 }
 
-export function createSubstance(count: number, substanceData?: SubstanceData): SubstanceData {
+export function createSubstance(count: number, type: SubstanceType, substanceData?: SubstanceData): SubstanceData {
     const substance = createTextureImage(Math.max(1, count), 4, Uint8Array, substanceData && substanceData.tSubstance.ref.value.array);
     if (substanceData) {
         ValueCell.update(substanceData.tSubstance, substance);
         ValueCell.update(substanceData.uSubstanceTexDim, Vec2.create(substance.width, substance.height));
         ValueCell.updateIfChanged(substanceData.dSubstance, count > 0);
+        ValueCell.updateIfChanged(substanceData.dSubstanceType, type);
         return substanceData;
     } else {
         return {
@@ -50,7 +53,7 @@ export function createSubstance(count: number, substanceData?: SubstanceData): S
             tSubstanceGrid: ValueCell.create(createNullTexture()),
             uSubstanceGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
             uSubstanceGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
-            dSubstanceType: ValueCell.create('groupInstance'),
+            dSubstanceType: ValueCell.create(type),
         };
     }
 }

+ 3 - 1
src/mol-geo/geometry/text/text.ts

@@ -211,7 +211,9 @@ export namespace Text {
 
         const color = createColors(locationIt, positionIt, theme.color);
         const size = createSizes(locationIt, theme.size);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const substance = createEmptySubstance();

+ 3 - 1
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -137,7 +137,9 @@ export namespace TextureMesh {
         const positionIt = Utils.createPositionIterator(textureMesh, transform);
 
         const color = createColors(locationIt, positionIt, theme.color);
-        const marker = createMarkers(instanceCount * groupCount);
+        const marker = props.useInstanceGranularity
+            ? createMarkers(instanceCount, 'instance')
+            : createMarkers(instanceCount * groupCount, 'groupInstance');
         const overpaint = createEmptyOverpaint();
         const transparency = createEmptyTransparency();
         const substance = createEmptySubstance();

+ 5 - 2
src/mol-geo/geometry/transparency-data.ts

@@ -9,6 +9,8 @@ import { Vec2, Vec3, Vec4 } from '../../mol-math/linear-algebra';
 import { TextureImage, createTextureImage } from '../../mol-gl/renderable/util';
 import { createNullTexture, Texture } from '../../mol-gl/webgl/texture';
 
+export type TransparencyType = 'instance' | 'groupInstance' | 'volumeInstance';
+
 export type TransparencyData = {
     tTransparency: ValueCell<TextureImage<Uint8Array>>
     uTransparencyTexDim: ValueCell<Vec2>
@@ -41,13 +43,14 @@ export function clearTransparency(array: Uint8Array, start: number, end: number)
     array.fill(0, start, end);
 }
 
-export function createTransparency(count: number, transparencyData?: TransparencyData): TransparencyData {
+export function createTransparency(count: number, type: TransparencyType, transparencyData?: TransparencyData): TransparencyData {
     const transparency = createTextureImage(Math.max(1, count), 1, Uint8Array, transparencyData && transparencyData.tTransparency.ref.value.array);
     if (transparencyData) {
         ValueCell.update(transparencyData.tTransparency, transparency);
         ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height));
         ValueCell.updateIfChanged(transparencyData.dTransparency, count > 0);
         ValueCell.updateIfChanged(transparencyData.transparencyAverage, getTransparencyAverage(transparency.array, count));
+        ValueCell.updateIfChanged(transparencyData.dTransparencyType, type);
         return transparencyData;
     } else {
         return {
@@ -59,7 +62,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc
             tTransparencyGrid: ValueCell.create(createNullTexture()),
             uTransparencyGridDim: ValueCell.create(Vec3.create(1, 1, 1)),
             uTransparencyGridTransform: ValueCell.create(Vec4.create(0, 0, 0, 1)),
-            dTransparencyType: ValueCell.create('groupInstance'),
+            dTransparencyType: ValueCell.create(type),
         };
     }
 }

+ 7 - 3
src/mol-gl/renderable/schema.ts

@@ -205,6 +205,7 @@ export const MarkerSchema = {
     tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     markerAverage: ValueSpec('number'),
     markerStatus: ValueSpec('number'),
+    dMarkerType: DefineSpec('string', ['instance', 'groupInstance']),
 } as const;
 export type MarkerSchema = typeof MarkerSchema
 export type MarkerValues = Values<MarkerSchema>
@@ -217,7 +218,7 @@ export const OverpaintSchema = {
     uOverpaintGridDim: UniformSpec('v3'),
     uOverpaintGridTransform: UniformSpec('v4'),
     tOverpaintGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
-    dOverpaintType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
+    dOverpaintType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
 } as const;
 export type OverpaintSchema = typeof OverpaintSchema
 export type OverpaintValues = Values<OverpaintSchema>
@@ -231,7 +232,7 @@ export const TransparencySchema = {
     uTransparencyGridDim: UniformSpec('v3'),
     uTransparencyGridTransform: UniformSpec('v4'),
     tTransparencyGrid: TextureSpec('texture', 'alpha', 'ubyte', 'linear'),
-    dTransparencyType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
+    dTransparencyType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
 } as const;
 export type TransparencySchema = typeof TransparencySchema
 export type TransparencyValues = Values<TransparencySchema>
@@ -244,7 +245,7 @@ export const SubstanceSchema = {
     uSubstanceGridDim: UniformSpec('v3'),
     uSubstanceGridTransform: UniformSpec('v4'),
     tSubstanceGrid: TextureSpec('texture', 'rgba', 'ubyte', 'linear'),
-    dSubstanceType: DefineSpec('string', ['groupInstance', 'volumeInstance']),
+    dSubstanceType: DefineSpec('string', ['instance', 'groupInstance', 'volumeInstance']),
 } as const;
 export type SubstanceSchema = typeof SubstanceSchema
 export type SubstanceValues = Values<SubstanceSchema>
@@ -253,6 +254,7 @@ export const ClippingSchema = {
     uClippingTexDim: UniformSpec('v2'),
     tClipping: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'),
     dClipping: DefineSpec('boolean'),
+    dClippingType: DefineSpec('string', ['instance', 'groupInstance']),
 } as const;
 export type ClippingSchema = typeof ClippingSchema
 export type ClippingValues = Values<ClippingSchema>
@@ -311,6 +313,8 @@ export const BaseSchema = {
     extraTransform: ValueSpec('float32'),
     /** denotes reflection in transform */
     hasReflection: ValueSpec('boolean'),
+    /** use instance granularity for marker, transparency, clipping, overpaint, substance */
+    useInstanceGranularity: ValueSpec('boolean'),
 
     /** bounding sphere taking aTransform into account and encompases all instances */
     boundingSphere: ValueSpec('sphere'),

+ 5 - 1
src/mol-gl/shader/chunks/assign-clipping-varying.glsl.ts

@@ -1,5 +1,9 @@
 export const assign_clipping_varying = `
 #if dClipObjectCount != 0 && defined(dClipping)
-    vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a;
+    #if defined(dClippingType_instance)
+        vClipping = readFromTexture(tClipping, aInstance, uClippingTexDim).a;
+    #elif defined(dMarkerType_groupInstance)
+        vClipping = readFromTexture(tClipping, aInstance * float(uGroupCount) + group, uClippingTexDim).a;
+    #endif
 #endif
 `;

+ 9 - 3
src/mol-gl/shader/chunks/assign-color-varying.glsl.ts

@@ -25,7 +25,9 @@ export const assign_color_varying = `
     #endif
 
     #ifdef dOverpaint
-        #if defined(dOverpaintType_groupInstance)
+        #if defined(dOverpaintType_instance)
+            vOverpaint = readFromTexture(tOverpaint, aInstance, uOverpaintTexDim);
+        #elif defined(dOverpaintType_groupInstance)
             vOverpaint = readFromTexture(tOverpaint, aInstance * float(uGroupCount) + group, uOverpaintTexDim);
         #elif defined(dOverpaintType_vertexInstance)
             vOverpaint = readFromTexture(tOverpaint, int(aInstance) * uVertexCount + VertexID, uOverpaintTexDim);
@@ -43,7 +45,9 @@ export const assign_color_varying = `
     #endif
 
     #ifdef dSubstance
-        #if defined(dSubstanceType_groupInstance)
+        #if defined(dSubstanceType_instance)
+            vSubstance = readFromTexture(tSubstance, aInstance, uSubstanceTexDim);
+        #elif defined(dSubstanceType_groupInstance)
             vSubstance = readFromTexture(tSubstance, aInstance * float(uGroupCount) + group, uSubstanceTexDim);
         #elif defined(dSubstanceType_vertexInstance)
             vSubstance = readFromTexture(tSubstance, int(aInstance) * uVertexCount + VertexID, uSubstanceTexDim);
@@ -74,7 +78,9 @@ export const assign_color_varying = `
 #ifdef dTransparency
     vGroup = group;
 
-    #if defined(dTransparencyType_groupInstance)
+    #if defined(dTransparencyType_instance)
+        vTransparency = readFromTexture(tTransparency, aInstance, uTransparencyTexDim).a;
+    #elif defined(dTransparencyType_groupInstance)
         vTransparency = readFromTexture(tTransparency, aInstance * float(uGroupCount) + group, uTransparencyTexDim).a;
     #elif defined(dTransparencyType_vertexInstance)
         vTransparency = readFromTexture(tTransparency, int(aInstance) * uVertexCount + VertexID, uTransparencyTexDim).a;

+ 5 - 1
src/mol-gl/shader/chunks/assign-marker-varying.glsl.ts

@@ -1,5 +1,9 @@
 export const assign_marker_varying = `
 #if defined(dRenderVariant_color) || defined(dRenderVariant_marking)
-    vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+    #if defined(dMarkerType_instance)
+        vMarker = readFromTexture(tMarker, aInstance, uMarkerTexDim).a;
+    #elif defined(dMarkerType_groupInstance)
+        vMarker = readFromTexture(tMarker, aInstance * float(uGroupCount) + group, uMarkerTexDim).a;
+    #endif
 #endif
 `;

+ 5 - 1
src/mol-repr/shape/representation.ts

@@ -125,7 +125,11 @@ export function ShapeRepresentation<D, G extends Geometry, P extends Geometry.Pa
                     Shape.createTransform(_shape.transforms, _renderObject.values);
                     locationIt = Shape.groupIterator(_shape);
                     const { instanceCount, groupCount } = locationIt;
-                    createMarkers(instanceCount * groupCount, _renderObject.values);
+                    if (props.useInstanceGranularity) {
+                        createMarkers(instanceCount * groupCount, 'groupInstance', _renderObject.values);
+                    } else {
+                        createMarkers(instanceCount, 'instance', _renderObject.values);
+                    }
                 }
 
                 if (updateState.createGeometry) {

+ 5 - 1
src/mol-repr/structure/complex-visual.ts

@@ -154,7 +154,11 @@ export function ComplexVisual<G extends Geometry, P extends StructureParams & Ge
                 // console.log('update transform')
                 locationIt = createLocationIterator(newStructure);
                 const { instanceCount, groupCount } = locationIt;
-                createMarkers(instanceCount * groupCount, renderObject.values);
+                if (newProps.useInstanceGranularity) {
+                    createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values);
+                } else {
+                    createMarkers(instanceCount, 'instance', renderObject.values);
+                }
             }
 
             if (updateState.createGeometry) {

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

@@ -15,7 +15,7 @@ import { createUnitsTransform, includesUnitKind, StructureGroup } from './visual
 import { createRenderObject, GraphicsRenderObject, RenderObjectValues } from '../../mol-gl/render-object';
 import { PickingId } from '../../mol-geo/geometry/picking';
 import { Loci, isEveryLoci, EmptyLoci } from '../../mol-model/loci';
-import { Interval } from '../../mol-data/int';
+import { Interval, OrderedSet } from '../../mol-data/int';
 import { VisualUpdateState } from '../util';
 import { ColorTheme } from '../../mol-theme/color';
 import { createMarkers } from '../../mol-geo/geometry/marker-data';
@@ -126,6 +126,10 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
             updateState.createGeometry = true;
         }
 
+        if (newProps.useInstanceGranularity !== currentProps.useInstanceGranularity) {
+            updateState.updateTransform = true;
+        }
+
         if (!deepEqual(newProps.unitKinds, currentProps.unitKinds)) {
             // console.log('new unitKinds');
             updateState.createGeometry = true;
@@ -194,7 +198,11 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
                 // console.log('update transform');
                 locationIt = createLocationIterator(newStructureGroup);
                 const { instanceCount, groupCount } = locationIt;
-                createMarkers(instanceCount * groupCount, renderObject.values);
+                if (newProps.useInstanceGranularity) {
+                    createMarkers(instanceCount, 'instance', renderObject.values);
+                } else {
+                    createMarkers(instanceCount * groupCount, 'groupInstance', renderObject.values);
+                }
             }
 
             if (updateState.updateMatrix) {
@@ -258,7 +266,69 @@ export function UnitsVisual<G extends Geometry, P extends StructureParams & Geom
         return false;
     }
 
+    function structureInstanceLoci() {
+        const elements: StructureElement.Loci['elements'][0][] = [];
+        currentStructureGroup.group.units.forEach(unit => {
+            elements.push({
+                unit,
+                indices: OrderedSet.ofSingleton(0 as StructureElement.UnitIndex)
+            });
+        });
+        return StructureElement.Loci(currentStructureGroup.structure, elements);
+    }
+
+    function lociFirstElementOfInstances(loci: Loci) {
+        if (isEveryLoci(loci)) {
+            return structureInstanceLoci();
+        } else if (Structure.isLoci(loci)) {
+            if (!Structure.areRootsEquivalent(loci.structure, currentStructureGroup.structure)) {
+                return EmptyLoci;
+            }
+            return structureInstanceLoci();
+        } else if (StructureElement.Loci.is(loci)) {
+            if (!Structure.areRootsEquivalent(loci.structure, currentStructureGroup.structure)) {
+                return EmptyLoci;
+            }
+            if (StructureElement.Loci.isWholeStructure(loci)) {
+                return structureInstanceLoci();
+            }
+
+            const elements: StructureElement.Loci['elements'][0][] = [];
+            loci.elements.forEach(e => {
+                elements.push({
+                    unit: loci.elements[0].unit,
+                    indices: OrderedSet.ofSingleton(OrderedSet.start(loci.elements[0].indices))
+                });
+            });
+            return StructureElement.Loci(currentStructureGroup.structure, elements);
+        }
+
+        console.log('unexpected loci', loci);
+
+        return loci;
+    }
+
+    function eachInstance(loci: Loci, structureGroup: StructureGroup, apply: (interval: Interval) => boolean) {
+        let changed = false;
+        if (!StructureElement.Loci.is(loci)) return false;
+        const { structure, group } = structureGroup;
+        if (!Structure.areEquivalent(loci.structure, structure)) return false;
+        const { unitIndexMap } = group;
+        for (const e of loci.elements) {
+            const unitIdx = unitIndexMap.get(e.unit.id);
+            if (unitIdx !== undefined) {
+                if (apply(Interval.ofSingleton(unitIdx))) changed = true;
+            }
+        }
+        return changed;
+    }
+
     function lociApply(loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) {
+        if (currentProps.useInstanceGranularity) {
+            // TODO: change loci to include only the first element of each instance
+            loci = lociFirstElementOfInstances(loci);
+            return eachInstance(loci, currentStructureGroup, apply);
+        }
         if (lociIsSuperset(loci)) {
             return apply(Interval.ofBounds(0, locationIt.groupCount * locationIt.instanceCount));
         } else {

+ 39 - 21
src/mol-repr/visual.ts

@@ -82,8 +82,10 @@ namespace Visual {
     export function mark(renderObject: GraphicsRenderObject | undefined, loci: Loci, action: MarkerAction, lociApply: LociApply, previous?: PreviousMark) {
         if (!renderObject || isEmptyLoci(loci)) return false;
 
-        const { tMarker, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tMarker, uMarker, markerAverage, markerStatus, uGroupCount, instanceCount, useInstanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
         const { array } = tMarker.ref.value;
         const currentStatus = markerStatus.ref.value as MarkerInfo['status'];
 
@@ -158,11 +160,14 @@ namespace Visual {
     export function setOverpaint(renderObject: GraphicsRenderObject | undefined, overpaint: Overpaint, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tOverpaint, dOverpaintType, dOverpaint, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tOverpaint, dOverpaintType, dOverpaint, uGroupCount, instanceCount, useInstanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
 
-        // ensure texture has right size
-        createOverpaint(overpaint.layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createOverpaint(overpaint.layers.length ? count : 0, type, renderObject.values);
         const { array } = tOverpaint.ref.value;
 
         // clear all if requested
@@ -180,10 +185,11 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tOverpaint, tOverpaint.ref.value);
-        ValueCell.updateIfChanged(dOverpaintType, 'groupInstance');
+        ValueCell.updateIfChanged(dOverpaintType, type);
         ValueCell.updateIfChanged(dOverpaint, overpaint.layers.length > 0);
 
         if (overpaint.layers.length === 0) return;
+        if (type === 'instance') return;
 
         if (smoothing && hasColorSmoothingProp(smoothing.props)) {
             const { geometry, props, webgl } = smoothing;
@@ -208,11 +214,14 @@ namespace Visual {
     export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tTransparency, dTransparencyType, transparencyAverage, dTransparency, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tTransparency, dTransparencyType, transparencyAverage, dTransparency, uGroupCount, instanceCount, useInstanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
 
-        // ensure texture has right size and variant
-        createTransparency(transparency.layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createTransparency(transparency.layers.length ? count : 0, type, renderObject.values);
         const { array } = tTransparency.ref.value;
 
         // clear if requested
@@ -229,10 +238,11 @@ namespace Visual {
         }
         ValueCell.update(tTransparency, tTransparency.ref.value);
         ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count));
-        ValueCell.updateIfChanged(dTransparencyType, 'groupInstance');
+        ValueCell.updateIfChanged(dTransparencyType, type);
         ValueCell.updateIfChanged(dTransparency, transparency.layers.length > 0);
 
         if (transparency.layers.length === 0) return;
+        if (type === 'instance') return;
 
         if (smoothing && hasColorSmoothingProp(smoothing.props)) {
             const { geometry, props, webgl } = smoothing;
@@ -257,11 +267,14 @@ namespace Visual {
     export function setSubstance(renderObject: GraphicsRenderObject | undefined, substance: Substance, lociApply: LociApply, clear: boolean, smoothing?: SmoothingContext) {
         if (!renderObject) return;
 
-        const { tSubstance, dSubstanceType, dSubstance, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tSubstance, dSubstanceType, dSubstance, uGroupCount, instanceCount, useInstanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
 
-        // ensure texture has right size
-        createSubstance(substance.layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createSubstance(substance.layers.length ? count : 0, type, renderObject.values);
         const { array } = tSubstance.ref.value;
 
         // clear all if requested
@@ -279,10 +292,11 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tSubstance, tSubstance.ref.value);
-        ValueCell.updateIfChanged(dSubstanceType, 'groupInstance');
+        ValueCell.updateIfChanged(dSubstanceType, type);
         ValueCell.updateIfChanged(dSubstance, substance.layers.length > 0);
 
         if (substance.layers.length === 0) return;
+        if (type === 'instance') return;
 
         if (smoothing && hasColorSmoothingProp(smoothing.props)) {
             const { geometry, props, webgl } = smoothing;
@@ -307,12 +321,15 @@ namespace Visual {
     export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) {
         if (!renderObject) return;
 
-        const { tClipping, dClipping, uGroupCount, instanceCount } = renderObject.values;
-        const count = uGroupCount.ref.value * instanceCount.ref.value;
+        const { tClipping, dClippingType, dClipping, uGroupCount, instanceCount, useInstanceGranularity: instanceGranularity } = renderObject.values;
+        const count = instanceGranularity.ref.value
+            ? instanceCount.ref.value
+            : uGroupCount.ref.value * instanceCount.ref.value;
         const { layers } = clipping;
 
-        // ensure texture has right size
-        createClipping(layers.length ? count : 0, renderObject.values);
+        // ensure texture has right size and type
+        const type = instanceGranularity.ref.value ? 'instance' : 'groupInstance';
+        createClipping(layers.length ? count : 0, type, renderObject.values);
         const { array } = tClipping.ref.value;
 
         // clear if requested
@@ -328,6 +345,7 @@ namespace Visual {
             lociApply(loci, apply, false);
         }
         ValueCell.update(tClipping, tClipping.ref.value);
+        ValueCell.updateIfChanged(dClippingType, type);
         ValueCell.updateIfChanged(dClipping, clipping.layers.length > 0);
     }