Browse Source

reduce spheres geo memory usage

- derive mapping from VertexID
- pull position and group from texture
Alexander Rose 1 year ago
parent
commit
aa25874775

+ 3 - 0
CHANGELOG.md

@@ -8,6 +8,9 @@ Note that since we don't clearly distinguish between a public and private interf
 
 - Fix display issue with SIFTS mapping
 - Properly switch-off fog
+- Reduce `Spheres` memory usage
+    - Derive mapping from VertexID
+    - Pull position and group from texture
 
 ## [v3.37.1] - 2023-06-20
 

+ 4 - 4
src/extensions/geo-export/mesh-exporter.ts

@@ -465,13 +465,13 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
     private async addSpheres(values: SpheresValues, webgl: WebGLContext, ctx: RuntimeContext) {
         const center = Vec3();
 
-        const aPosition = values.aPosition.ref.value;
-        const aGroup = values.aGroup.ref.value;
+        const aPosition = values.centerBuffer.ref.value;
+        const aGroup = values.groupBuffer.ref.value;
         const instanceCount = values.instanceCount.ref.value;
         const vertexCount = values.uVertexCount.ref.value;
         const meshes: Mesh[] = [];
 
-        const sphereCount = vertexCount / 4 * instanceCount;
+        const sphereCount = vertexCount / 6 * instanceCount;
         let detail: number;
         switch (this.options.primitivesQuality) {
             case 'auto':
@@ -495,7 +495,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         for (let instanceIndex = 0; instanceIndex < instanceCount; ++instanceIndex) {
             const state = MeshBuilder.createState(512, 256);
 
-            for (let i = 0; i < vertexCount; i += 4) {
+            for (let i = 0; i < sphereCount; ++i) {
                 v3fromArray(center, aPosition, i * 3);
 
                 const group = aGroup[i];

+ 4 - 29
src/mol-geo/geometry/spheres/spheres-builder.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,21 +7,8 @@
 import { ChunkedArray } from '../../../mol-data/util';
 import { Spheres } from './spheres';
 
-const quadMapping = new Float32Array([
-    -1.0, 1.0,
-    -1.0, -1.0,
-    1.0, 1.0,
-    1.0, -1.0
-]);
-
-const quadIndices = new Uint16Array([
-    0, 1, 2,
-    1, 3, 2
-]);
-
 // avoiding namespace lookup improved performance in Chrome (Aug 2020)
 const caAdd3 = ChunkedArray.add3;
-const caAdd2 = ChunkedArray.add2;
 const caAdd = ChunkedArray.add;
 
 export interface SpheresBuilder {
@@ -31,30 +18,18 @@ export interface SpheresBuilder {
 
 export namespace SpheresBuilder {
     export function create(initialCount = 2048, chunkSize = 1024, spheres?: Spheres): SpheresBuilder {
-        initialCount *= 4;
-        chunkSize *= 4;
         const centers = ChunkedArray.create(Float32Array, 3, chunkSize, spheres ? spheres.centerBuffer.ref.value : initialCount);
-        const mappings = ChunkedArray.create(Float32Array, 2, chunkSize, spheres ? spheres.mappingBuffer.ref.value : initialCount);
-        const indices = ChunkedArray.create(Uint32Array, 3, chunkSize / 2, spheres ? spheres.indexBuffer.ref.value : initialCount / 2);
         const groups = ChunkedArray.create(Float32Array, 1, chunkSize, spheres ? spheres.groupBuffer.ref.value : initialCount);
 
         return {
             add: (x: number, y: number, z: number, group: number) => {
-                const offset = centers.elementCount;
-                for (let i = 0; i < 4; ++i) {
-                    caAdd3(centers, x, y, z);
-                    caAdd2(mappings, quadMapping[i * 2], quadMapping[i * 2 + 1]);
-                    caAdd(groups, group);
-                }
-                caAdd3(indices, offset + quadIndices[0], offset + quadIndices[1], offset + quadIndices[2]);
-                caAdd3(indices, offset + quadIndices[3], offset + quadIndices[4], offset + quadIndices[5]);
+                caAdd3(centers, x, y, z);
+                caAdd(groups, group);
             },
             getSpheres: () => {
                 const cb = ChunkedArray.compact(centers, true) as Float32Array;
-                const mb = ChunkedArray.compact(mappings, true) as Float32Array;
-                const ib = ChunkedArray.compact(indices, true) as Uint32Array;
                 const gb = ChunkedArray.compact(groups, true) as Float32Array;
-                return Spheres.create(cb, mb, ib, gb, centers.elementCount / 4, spheres);
+                return Spheres.create(cb, gb, centers.elementCount, spheres);
             }
         };
     }

+ 32 - 29
src/mol-geo/geometry/spheres/spheres.ts

@@ -13,7 +13,7 @@ import { Theme } from '../../../mol-theme/theme';
 import { SpheresValues } from '../../../mol-gl/renderable/spheres';
 import { createColors } from '../color-data';
 import { createMarkers } from '../marker-data';
-import { calculateInvariantBoundingSphere, calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
+import { TextureImage, calculateInvariantBoundingSphere, calculateTransformBoundingSphere, createTextureImage } from '../../../mol-gl/renderable/util';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { createSizes, getMaxSize } from '../size-data';
 import { Color } from '../../../mol-util/color';
@@ -23,7 +23,7 @@ import { createEmptyTransparency } from '../transparency-data';
 import { hashFnv32a } from '../../../mol-data/util';
 import { GroupMapping, createGroupMapping } from '../../util';
 import { createEmptyClipping } from '../clipping-data';
-import { Vec3, Vec4 } from '../../../mol-math/linear-algebra';
+import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { RenderableState } from '../../../mol-gl/renderable';
 import { createEmptySubstance } from '../substance-data';
 
@@ -35,10 +35,6 @@ export interface Spheres {
 
     /** Center buffer as array of xyz values wrapped in a value cell */
     readonly centerBuffer: ValueCell<Float32Array>,
-    /** Mapping buffer as array of xy values wrapped in a value cell */
-    readonly mappingBuffer: ValueCell<Float32Array>,
-    /** Index buffer as array of center index triplets wrapped in a value cell */
-    readonly indexBuffer: ValueCell<Uint32Array>,
     /** Group buffer as array of group ids for each vertex wrapped in a value cell */
     readonly groupBuffer: ValueCell<Float32Array>,
 
@@ -51,29 +47,27 @@ export interface Spheres {
 }
 
 export namespace Spheres {
-    export function create(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
+    export function create(centers: Float32Array, groups: Float32Array, sphereCount: number, spheres?: Spheres): Spheres {
         return spheres ?
-            update(centers, mappings, indices, groups, sphereCount, spheres) :
-            fromArrays(centers, mappings, indices, groups, sphereCount);
+            update(centers, groups, sphereCount, spheres) :
+            fromArrays(centers, groups, sphereCount);
     }
 
     export function createEmpty(spheres?: Spheres): Spheres {
         const cb = spheres ? spheres.centerBuffer.ref.value : new Float32Array(0);
-        const mb = spheres ? spheres.mappingBuffer.ref.value : new Float32Array(0);
-        const ib = spheres ? spheres.indexBuffer.ref.value : new Uint32Array(0);
         const gb = spheres ? spheres.groupBuffer.ref.value : new Float32Array(0);
-        return create(cb, mb, ib, gb, 0, spheres);
+        return create(cb, gb, 0, spheres);
     }
 
     function hashCode(spheres: Spheres) {
         return hashFnv32a([
             spheres.sphereCount,
-            spheres.centerBuffer.ref.version, spheres.mappingBuffer.ref.version,
-            spheres.indexBuffer.ref.version, spheres.groupBuffer.ref.version
+            spheres.centerBuffer.ref.version,
+            spheres.groupBuffer.ref.version
         ]);
     }
 
-    function fromArrays(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number): Spheres {
+    function fromArrays(centers: Float32Array, groups: Float32Array, sphereCount: number): Spheres {
 
         const boundingSphere = Sphere3D();
         let groupMapping: GroupMapping;
@@ -85,8 +79,6 @@ export namespace Spheres {
             kind: 'spheres' as const,
             sphereCount,
             centerBuffer: ValueCell.create(centers),
-            mappingBuffer: ValueCell.create(mappings),
-            indexBuffer: ValueCell.create(indices),
             groupBuffer: ValueCell.create(groups),
             get boundingSphere() {
                 const newHash = hashCode(spheres);
@@ -112,17 +104,23 @@ export namespace Spheres {
         return spheres;
     }
 
-    function update(centers: Float32Array, mappings: Float32Array, indices: Uint32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
-        if (sphereCount > spheres.sphereCount) {
-            ValueCell.update(spheres.mappingBuffer, mappings);
-            ValueCell.update(spheres.indexBuffer, indices);
-        }
+    function update(centers: Float32Array, groups: Float32Array, sphereCount: number, spheres: Spheres) {
         spheres.sphereCount = sphereCount;
         ValueCell.update(spheres.centerBuffer, centers);
         ValueCell.update(spheres.groupBuffer, groups);
         return spheres;
     }
 
+    function setPositionGroup(out: TextureImage<Float32Array>, centers: Float32Array, groups: Float32Array, count: number) {
+        const { array } = out;
+        for (let i = 0; i < count; ++i) {
+            array[i * 4 + 0] = centers[i * 3 + 0];
+            array[i * 4 + 1] = centers[i * 3 + 1];
+            array[i * 4 + 2] = centers[i * 3 + 2];
+            array[i * 4 + 3] = groups[i];
+        }
+    }
+
     export const Params = {
         ...BaseGeometry.Params,
         sizeFactor: PD.Numeric(1, { min: 0, max: 10, step: 0.1 }),
@@ -149,7 +147,7 @@ export namespace Spheres {
     };
 
     function createPositionIterator(spheres: Spheres, transform: TransformData): LocationIterator {
-        const groupCount = spheres.sphereCount * 4;
+        const groupCount = spheres.sphereCount;
         const instanceCount = transform.instanceCount.ref.value;
         const location = PositionLocation();
         const p = location.position;
@@ -163,7 +161,7 @@ export namespace Spheres {
             }
             return location;
         };
-        return LocationIterator(groupCount, instanceCount, 4, getLocation);
+        return LocationIterator(groupCount, instanceCount, 1, getLocation);
     }
 
     function createValues(spheres: Spheres, transform: TransformData, locationIt: LocationIterator, theme: Theme, props: PD.Values<Params>): SpheresValues {
@@ -180,19 +178,21 @@ export namespace Spheres {
         const material = createEmptySubstance();
         const clipping = createEmptyClipping();
 
-        const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 4, groupCount, instanceCount };
+        const counts = { drawCount: spheres.sphereCount * 2 * 3, vertexCount: spheres.sphereCount * 6, groupCount, instanceCount };
 
         const padding = spheres.boundingSphere.radius ? getMaxSize(size) * props.sizeFactor : 0;
         const invariantBoundingSphere = Sphere3D.expand(Sphere3D(), spheres.boundingSphere, padding);
         const boundingSphere = calculateTransformBoundingSphere(invariantBoundingSphere, transform.aTransform.ref.value, instanceCount, 0);
 
+        const positionGroupTexture = createTextureImage(spheres.sphereCount, 4, Float32Array);
+        setPositionGroup(positionGroupTexture, spheres.centerBuffer.ref.value, spheres.groupBuffer.ref.value, spheres.sphereCount);
+
         return {
             dGeometryType: ValueCell.create('spheres'),
 
-            aPosition: spheres.centerBuffer,
-            aMapping: spheres.mappingBuffer,
-            aGroup: spheres.groupBuffer,
-            elements: spheres.indexBuffer,
+            uTexDim: ValueCell.create(Vec2.create(positionGroupTexture.width, positionGroupTexture.height)),
+            tPositionGroup: ValueCell.create(positionGroupTexture),
+
             boundingSphere: ValueCell.create(boundingSphere),
             invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
             uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
@@ -216,6 +216,9 @@ export namespace Spheres {
             dSolidInterior: ValueCell.create(props.solidInterior),
             uBumpFrequency: ValueCell.create(props.bumpFrequency),
             uBumpAmplitude: ValueCell.create(props.bumpAmplitude),
+
+            centerBuffer: spheres.centerBuffer,
+            groupBuffer: spheres.groupBuffer,
         };
     }
 

+ 3 - 2
src/mol-gl/_spec/gl.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -17,10 +17,11 @@ export function getGLContext(width: number, height: number) {
     return createContext(gl);
 }
 
-export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean }) {
+export function tryGetGLContext(width: number, height: number, requiredExtensions?: { fragDepth?: boolean, textureFloat?: boolean }) {
     try {
         const ctx = getGLContext(width, height);
         if (requiredExtensions?.fragDepth && !ctx.extensions.fragDepth) return;
+        if (requiredExtensions?.textureFloat && !ctx.extensions.textureFloat) return;
         return ctx;
     } catch (e) {
         return;

+ 2 - 2
src/mol-gl/_spec/spheres.spec.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -21,7 +21,7 @@ export function createSpheres() {
 }
 
 describe('spheres', () => {
-    const ctx = tryGetGLContext(32, 32, { fragDepth: true });
+    const ctx = tryGetGLContext(32, 32, { fragDepth: true, textureFloat: true });
 
     (ctx ? it : it.skip)('basic', async () => {
         const ctx = getGLContext(32, 32);

+ 7 - 5
src/mol-gl/renderable/spheres.ts

@@ -7,17 +7,16 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem, GraphicsRenderVariant } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec } from './schema';
+import { GlobalUniformSchema, BaseSchema, Values, InternalSchema, SizeSchema, InternalValues, ValueSpec, DefineSpec, GlobalTextureSchema, UniformSpec, TextureSpec } from './schema';
 import { SpheresShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
 export const SpheresSchema = {
     ...BaseSchema,
     ...SizeSchema,
-    aGroup: AttributeSpec('float32', 1, 0),
-    aPosition: AttributeSpec('float32', 3, 0),
-    aMapping: AttributeSpec('float32', 2, 0),
-    elements: ElementsSpec('uint32'),
+
+    uTexDim: UniformSpec('v2'),
+    tPositionGroup: TextureSpec('image-float32', 'rgba', 'float', 'nearest'),
 
     padding: ValueSpec('number'),
     uDoubleSided: UniformSpec('b', 'material'),
@@ -27,6 +26,9 @@ export const SpheresSchema = {
     dSolidInterior: DefineSpec('boolean'),
     uBumpFrequency: UniformSpec('f', 'material'),
     uBumpAmplitude: UniformSpec('f', 'material'),
+
+    centerBuffer: ValueSpec('float32'),
+    groupBuffer: ValueSpec('float32'),
 };
 export type SpheresSchema = typeof SpheresSchema
 export type SpheresValues = Values<SpheresSchema>

+ 25 - 9
src/mol-gl/shader/spheres.vert.ts

@@ -18,11 +18,11 @@ precision highp int;
 uniform mat4 uModelView;
 uniform mat4 uInvProjection;
 
-attribute vec3 aPosition;
-attribute vec2 aMapping;
+uniform vec2 uTexDim;
+uniform sampler2D tPositionGroup;
+
 attribute mat4 aTransform;
 attribute float aInstance;
-attribute float aGroup;
 
 varying float vRadius;
 varying float vRadiusSq;
@@ -43,7 +43,7 @@ const mat4 D = mat4(
  * "GPU-Based Ray-Casting of Quadratic Surfaces" http://dl.acm.org/citation.cfm?id=2386396
  * by Christian Sigg, Tim Weyrich, Mario Botsch, Markus Gross.
  */
-void quadraticProjection(const in float radius, const in vec3 position){
+void quadraticProjection(const in float radius, const in vec3 position, const in vec2 mapping){
     vec2 xbc, ybc;
 
     mat4 T = mat4(
@@ -69,13 +69,29 @@ void quadraticProjection(const in float radius, const in vec3 position){
     float sy = abs(ybc[0] - ybc[1]) * 0.5;
 
     gl_Position.xy = vec2(0.5 * (xbc.x + xbc.y), 0.5 * (ybc.x + ybc.y));
-    gl_Position.xy -= aMapping * vec2(sx, sy);
+    gl_Position.xy -= mapping * vec2(sx, sy);
     gl_Position.xy *= gl_Position.w;
 }
 
-
 void main(void){
-    #include assign_group
+    vec2 mapping = vec2(1.0, 1.0); // vertices 2 and 5
+    #if __VERSION__ == 100
+        int m = imod(VertexID, 6);
+    #else
+        int m = VertexID % 6;
+    #endif
+    if (m == 0) {
+        mapping = vec2(-1.0, 1.0);
+    } else if (m == 1 || m == 3) {
+        mapping = vec2(-1.0, -1.0);
+    } else if (m == 4) {
+        mapping = vec2(1.0, -1.0);
+    }
+
+    vec4 positionGroup = readFromTexture(tPositionGroup, VertexID / 6, uTexDim);
+    vec3 position = positionGroup.rgb;
+    float group = positionGroup.a;
+
     #include assign_color_varying
     #include assign_marker_varying
     #include assign_clipping_varying
@@ -83,11 +99,11 @@ void main(void){
 
     vRadius = size * matrixScale(uModelView);
 
-    vec4 position4 = vec4(aPosition, 1.0);
+    vec4 position4 = vec4(position, 1.0);
     vec4 mvPosition = uModelView * aTransform * position4;
 
     gl_Position = uProjection * vec4(mvPosition.xyz, 1.0);
-    quadraticProjection(size, aPosition);
+    quadraticProjection(size, position, mapping);
 
     vRadiusSq = vRadius * vRadius;
     vec4 vPoint4 = uInvProjection * gl_Position;

+ 2 - 2
src/mol-repr/structure/visual/element-sphere.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  * @author David Sehnal <david.sehnal@gmail.com>
@@ -27,7 +27,7 @@ export const ElementSphereParams = {
 export type ElementSphereParams = typeof ElementSphereParams
 
 export function ElementSphereVisual(materialId: number, structure: Structure, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
-    return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
+    return props.tryUseImpostor && webgl && webgl.extensions.fragDepth && webgl.extensions.textureFloat
         ? ElementSphereImpostorVisual(materialId)
         : ElementSphereMeshVisual(materialId);
 }

+ 2 - 2
src/mol-repr/structure/visual/polymer-backbone-sphere.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2021-2023 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -34,7 +34,7 @@ export const PolymerBackboneSphereParams = {
 export type PolymerBackboneSphereParams = typeof PolymerBackboneSphereParams
 
 export function PolymerBackboneSphereVisual(materialId: number, structure: Structure, props: PD.Values<PolymerBackboneSphereParams>, webgl?: WebGLContext) {
-    return props.tryUseImpostor && webgl && webgl.extensions.fragDepth
+    return props.tryUseImpostor && webgl && webgl.extensions.fragDepth && webgl.extensions.textureFloat
         ? PolymerBackboneSphereImpostorVisual(materialId)
         : PolymerBackboneSphereMeshVisual(materialId);
 }