浏览代码

improved gpu support for representations

- enabled in volume isosurface & structure gaussian surface
- only if suitable (check memory requirements and resolution)
- falls back to cpu code
Alexander Rose 4 年之前
父节点
当前提交
16f4524bdb

+ 3 - 0
src/mol-gl/webgl/context.ts

@@ -188,6 +188,7 @@ export interface WebGLContext {
     readonly resources: WebGLResources
 
     readonly maxTextureSize: number
+    readonly max3dTextureSize: number
     readonly maxRenderbufferSize: number
     readonly maxDrawBuffers: number
     readonly maxTextureImageUnits: number
@@ -223,6 +224,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
 
     const parameters = {
         maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number,
+        max3dTextureSize: isWebGL2(gl) ? gl.getParameter(gl.MAX_3D_TEXTURE_SIZE) as number : 0,
         maxRenderbufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number,
         maxDrawBuffers: extensions.drawBuffers ? gl.getParameter(extensions.drawBuffers.MAX_DRAW_BUFFERS) as number : 0,
         maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number,
@@ -289,6 +291,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal
         resources,
 
         get maxTextureSize () { return parameters.maxTextureSize; },
+        get max3dTextureSize () { return parameters.max3dTextureSize; },
         get maxRenderbufferSize () { return parameters.maxRenderbufferSize; },
         get maxDrawBuffers () { return parameters.maxDrawBuffers; },
         get maxTextureImageUnits () { return parameters.maxTextureImageUnits; },

+ 4 - 4
src/mol-repr/structure/complex-representation.ts

@@ -22,7 +22,7 @@ import { Clipping } from '../../mol-theme/clipping';
 import { Transparency } from '../../mol-theme/transparency';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 
-export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, props?: PD.Values<P>, webgl?: WebGLContext) => ComplexVisual<P>): StructureRepresentation<P> {
+export function ComplexRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => ComplexVisual<P>): StructureRepresentation<P> {
     let version = 0;
     const { webgl } = ctx;
     const updated = new Subject<number>();
@@ -47,11 +47,11 @@ export function ComplexRepresentation<P extends StructureParams>(label: string,
         return Task.create('Creating or updating ComplexRepresentation', async runtime => {
             let newVisual = false;
             if (!visual) {
-                visual = visualCtor(materialId, _props, webgl);
+                visual = visualCtor(materialId, _structure, _props, webgl);
                 newVisual = true;
-            } else if (visual.mustRecreate?.(_props, webgl)) {
+            } else if (visual.mustRecreate?.(_structure, _props, webgl)) {
                 visual.destroy();
-                visual = visualCtor(materialId, _props, webgl);
+                visual = visualCtor(materialId, _structure, _props, webgl);
                 newVisual = true;
             }
             const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, structure);

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

@@ -52,7 +52,7 @@ interface ComplexVisualBuilder<P extends StructureParams, G extends Geometry> {
     getLoci(pickingId: PickingId, structure: Structure, id: number): Loci
     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
+    mustRecreate?: (structure: Structure, props: PD.Values<P>) => boolean
     dispose?: (geometry: G) => void
 }
 

+ 9 - 9
src/mol-repr/structure/units-representation.ts

@@ -28,7 +28,7 @@ import { WebGLContext } from '../../mol-gl/webgl/context';
 
 export interface UnitsVisual<P extends StructureParams> extends Visual<StructureGroup, P> { }
 
-export function UnitsRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, props?: PD.Values<P>, webgl?: WebGLContext) => UnitsVisual<P>): StructureRepresentation<P> {
+export function UnitsRepresentation<P extends StructureParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Structure, P>, visualCtor: (materialId: number, structure: Structure, props: PD.Values<P>, webgl?: WebGLContext) => UnitsVisual<P>): StructureRepresentation<P> {
     let version = 0;
     const { webgl } = ctx;
     const updated = new Subject<number>();
@@ -59,7 +59,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                 _groups = structure.unitSymmetryGroups;
                 for (let i = 0; i < _groups.length; i++) {
                     const group = _groups[i];
-                    const visual = visualCtor(materialId, _props, webgl);
+                    const visual = visualCtor(materialId, structure, _props, webgl);
                     const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
                     if (promise) await promise;
                     setVisualState(visual, group, _state); // current state for new visual
@@ -82,9 +82,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                         // console.log('old', visualGroup.group)
                         // console.log('new', group)
                         let { visual } = visualGroup;
-                        if (visual.mustRecreate?.(_props, webgl)) {
+                        if (visual.mustRecreate?.({ group, structure }, _props, webgl)) {
                             visual.destroy();
-                            visual = visualCtor(materialId, _props, webgl);
+                            visual = visualCtor(materialId, structure, _props, webgl);
                             const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
                             if (promise) await promise;
                             setVisualState(visual, group, _state); // current state for new visual
@@ -104,7 +104,7 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                     } else {
                         // console.log(label, 'did not find visualGroup to reuse, creating new');
                         // newGroups.push(group)
-                        const visual = visualCtor(materialId, _props, webgl);
+                        const visual = visualCtor(materialId, structure, _props, webgl);
                         const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
                         if (promise) await promise;
                         setVisualState(visual, group, _state); // current state for new visual
@@ -129,9 +129,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                     const visualGroup = visuals.get(group.hashCode);
                     if (visualGroup) {
                         let { visual } = visualGroup;
-                        if (visual.mustRecreate?.(_props, ctx.webgl)) {
+                        if (visual.mustRecreate?.({ group, structure }, _props, ctx.webgl)) {
                             visual.destroy();
-                            visual = visualCtor(materialId, _props, ctx.webgl);
+                            visual = visualCtor(materialId, structure, _props, ctx.webgl);
                             visualGroup.visual = visual;
                             const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure });
                             if (promise) await promise;
@@ -153,9 +153,9 @@ export function UnitsRepresentation<P extends StructureParams>(label: string, ct
                 visuals.forEach(vg => visualsList.push(vg));
                 for (let i = 0, il = visualsList.length; i < il; ++i) {
                     let { visual, group } = visualsList[i];
-                    if (visual.mustRecreate?.(_props, ctx.webgl)) {
+                    if (visual.mustRecreate?.({ group, structure: _structure }, _props, ctx.webgl)) {
                         visual.destroy();
-                        visual = visualCtor(materialId, _props, webgl);
+                        visual = visualCtor(materialId, _structure, _props, webgl);
                         visualsList[i].visual = visual;
                         const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, { group, structure: _structure });
                         if (promise) await promise;

+ 1 - 1
src/mol-repr/structure/units-visual.ts

@@ -58,7 +58,7 @@ interface UnitsVisualBuilder<P extends StructureParams, G extends Geometry> {
     getLoci(pickingId: PickingId, structureGroup: StructureGroup, id: number): Loci
     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
+    mustRecreate?: (structureGroup: StructureGroup, props: PD.Values<P>) => boolean
     dispose?: (geometry: G) => void
 }
 

+ 4 - 4
src/mol-repr/structure/visual/bond-inter-unit-cylinder.ts

@@ -161,8 +161,8 @@ export const InterUnitBondCylinderParams = {
 };
 export type InterUnitBondCylinderParams = typeof InterUnitBondCylinderParams
 
-export function InterUnitBondCylinderVisual(materialId: number, props?: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) {
-    return props?.useImpostor && webgl && webgl.extensions.fragDepth
+export function InterUnitBondCylinderVisual(materialId: number, structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) {
+    return props.useImpostor && webgl && webgl.extensions.fragDepth
         ? InterUnitBondCylinderImpostorVisual(materialId)
         : InterUnitBondCylinderMeshVisual(materialId);
 }
@@ -187,7 +187,7 @@ export function InterUnitBondCylinderImpostorVisual(materialId: number): Complex
                 !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
             );
         },
-        mustRecreate: (props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
+        mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
             return !props.useImpostor || !webgl;
         }
     }, materialId);
@@ -216,7 +216,7 @@ export function InterUnitBondCylinderMeshVisual(materialId: number): ComplexVisu
                 !arrayEqual(newProps.excludeTypes, currentProps.excludeTypes)
             );
         },
-        mustRecreate: (props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
+        mustRecreate: (structure: Structure, props: PD.Values<InterUnitBondCylinderParams>, webgl?: WebGLContext) => {
             return props.useImpostor && !!webgl;
         }
     }, materialId);

+ 4 - 4
src/mol-repr/structure/visual/bond-intra-unit-cylinder.ts

@@ -145,8 +145,8 @@ export const IntraUnitBondCylinderParams = {
 };
 export type IntraUnitBondCylinderParams = typeof IntraUnitBondCylinderParams
 
-export function IntraUnitBondCylinderVisual(materialId: number, props?: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) {
-    return props?.useImpostor && webgl && webgl.extensions.fragDepth
+export function IntraUnitBondCylinderVisual(materialId: number, structure: Structure, props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) {
+    return props.useImpostor && webgl && webgl.extensions.fragDepth
         ? IntraUnitBondCylinderImpostorVisual(materialId)
         : IntraUnitBondCylinderMeshVisual(materialId);
 }
@@ -182,7 +182,7 @@ export function IntraUnitBondCylinderImpostorVisual(materialId: number): UnitsVi
                 }
             }
         },
-        mustRecreate: (props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
+        mustRecreate: (structureGroup: StructureGroup, props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
             return !props.useImpostor || !webgl;
         }
     }, materialId);
@@ -222,7 +222,7 @@ export function IntraUnitBondCylinderMeshVisual(materialId: number): UnitsVisual
                 }
             }
         },
-        mustRecreate: (props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
+        mustRecreate: (structureGroup: StructureGroup, props: PD.Values<IntraUnitBondCylinderParams>, webgl?: WebGLContext) => {
             return props.useImpostor && !!webgl;
         }
     }, materialId);

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

@@ -6,11 +6,12 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual } from '../units-visual';
+import { UnitsMeshParams, UnitsSpheresParams, UnitsVisual, UnitsSpheresVisual, UnitsMeshVisual, StructureGroup } from '../units-visual';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { createElementSphereImpostor, ElementIterator, getElementLoci, eachElement, createElementSphereMesh } from './util/element';
 import { VisualUpdateState } from '../../util';
 import { BaseGeometry } from '../../../mol-geo/geometry/base';
+import { Structure } from '../../../mol-model/structure';
 
 export const ElementSphereParams = {
     ...UnitsMeshParams,
@@ -23,8 +24,8 @@ export const ElementSphereParams = {
 };
 export type ElementSphereParams = typeof ElementSphereParams
 
-export function ElementSphereVisual(materialId: number, props?: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
-    return props?.useImpostor && webgl && webgl.extensions.fragDepth
+export function ElementSphereVisual(materialId: number, structure: Structure, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) {
+    return props.useImpostor && webgl && webgl.extensions.fragDepth
         ? ElementSphereImpostorVisual(materialId)
         : ElementSphereMeshVisual(materialId);
 }
@@ -42,7 +43,7 @@ export function ElementSphereImpostorVisual(materialId: number): UnitsVisual<Ele
                 newProps.traceOnly !== currentProps.traceOnly
             );
         },
-        mustRecreate: (props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
+        mustRecreate: (structureGroup: StructureGroup, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
             return !props.useImpostor || !webgl;
         }
     }, materialId);
@@ -63,7 +64,7 @@ export function ElementSphereMeshVisual(materialId: number): UnitsVisual<Element
                 newProps.traceOnly !== currentProps.traceOnly
             );
         },
-        mustRecreate: (props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
+        mustRecreate: (structureGroup: StructureGroup, props: PD.Values<ElementSphereParams>, webgl?: WebGLContext) => {
             return props.useImpostor && !!webgl;
         }
     }, materialId);

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

@@ -20,7 +20,9 @@ import { getStructureExtraRadius, getUnitExtraRadius } from './util/common';
 
 async function createGaussianDensityVolume(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
     const { runtime, webgl } = ctx;
-    if (webgl === undefined) throw new Error('createGaussianDensityVolume requires `webgl` object in VisualContext');
+    if (!webgl || !webgl.extensions.blendMinMax) {
+        throw new Error('GaussianDensityVolume requires `webgl` and `blendMinMax` extension');
+    }
 
     const p = { ...props, useGpu: true };
     const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;
@@ -71,7 +73,9 @@ export function GaussianDensityVolumeVisual(materialId: number): ComplexVisual<G
 
 async function createUnitsGaussianDensityVolume(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, directVolume?: DirectVolume): Promise<DirectVolume> {
     const { runtime, webgl } = ctx;
-    if (webgl === undefined) throw new Error('createUnitGaussianDensityVolume requires `webgl` object in VisualContext');
+    if (!webgl || !webgl.extensions.blendMinMax) {
+        throw new Error('GaussianDensityVolume requires `webgl` and `blendMinMax` extension');
+    }
 
     const p = { ...props, useGpu: true };
     const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined;

+ 35 - 21
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -5,7 +5,7 @@
  */
 
 import { ParamDefinition as PD } from '../../../mol-util/param-definition';
-import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual } from '../units-visual';
+import { UnitsMeshParams, UnitsTextureMeshParams, UnitsVisual, UnitsMeshVisual, UnitsTextureMeshVisual, StructureGroup } from '../units-visual';
 import { GaussianDensityParams, computeUnitGaussianDensity, computeUnitGaussianDensityTexture2d, GaussianDensityProps, computeStructureGaussianDensity, computeStructureGaussianDensityTexture2d } from './util/gaussian';
 import { VisualContext } from '../../visual';
 import { Unit, Structure } from '../../../mol-model/structure';
@@ -18,14 +18,15 @@ import { TextureMesh } from '../../../mol-geo/geometry/texture-mesh/texture-mesh
 import { extractIsosurface } from '../../../mol-gl/compute/marching-cubes/isosurface';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { ComplexVisual, ComplexMeshParams, ComplexMeshVisual, ComplexTextureMeshVisual, ComplexTextureMeshParams } from '../complex-visual';
-import { getUnitExtraRadius, getStructureExtraRadius } from './util/common';
+import { getUnitExtraRadius, getStructureExtraRadius, getVolumeSliceInfo } from './util/common';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 
 const SharedParams = {
     ...GaussianDensityParams,
     ignoreHydrogens: PD.Boolean(false),
-    useGpu: PD.Boolean(false),
+    useGpu: PD.Boolean(true, { label: 'Try Use GPU' }),
 };
+type SharedParams = typeof SharedParams
 
 export const GaussianSurfaceMeshParams = {
     ...UnitsMeshParams,
@@ -45,23 +46,36 @@ function gpuSupport(webgl: WebGLContext) {
     return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.blendMinMax && webgl.extensions.drawBuffers;
 }
 
-export function GaussianSurfaceVisual(materialId: number, props?: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) {
-    return props?.useGpu && webgl && gpuSupport(webgl)
-        ? GaussianSurfaceTextureMeshVisual(materialId)
-        : GaussianSurfaceMeshVisual(materialId);
+function suitableForGpu(structure: Structure, props: PD.Values<SharedParams>, webgl: WebGLContext) {
+    // lower resolutions are about as fast on CPU vs integrated GPU,
+    // very low resolutions have artifacts when calculated on GPU
+    if (props.resolution > 1) return false;
+    // the GPU is much more memory contraint, especially true for integrated GPUs,
+    // being conservative here still allows for small and medium sized assemblies
+    const d = webgl.maxTextureSize / 3;
+    const { areaCells, maxAreaCells } = getVolumeSliceInfo(structure.boundary.box, props.resolution, d * d);
+    return areaCells < maxAreaCells;
 }
 
-export function StructureGaussianSurfaceVisual(materialId: number, props?: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) {
-    return props?.useGpu && webgl && gpuSupport(webgl)
-        ? StructureGaussianSurfaceTextureMeshVisual(materialId)
-        : StructureGaussianSurfaceMeshVisual(materialId);
+export function GaussianSurfaceVisual(materialId: number, structure: Structure, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) {
+    if (props.useGpu && webgl && gpuSupport(webgl) && suitableForGpu(structure, props, webgl)) {
+        return GaussianSurfaceTextureMeshVisual(materialId);
+    }
+    return GaussianSurfaceMeshVisual(materialId);
+}
+
+export function StructureGaussianSurfaceVisual(materialId: number, structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) {
+    if (props.useGpu && webgl && gpuSupport(webgl) && suitableForGpu(structure, props, webgl)) {
+        return StructureGaussianSurfaceTextureMeshVisual(materialId);
+    }
+    return StructureGaussianSurfaceMeshVisual(materialId);
 }
 
 //
 
 async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -94,8 +108,8 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
         },
-        mustRecreate: (props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
-            return props.useGpu && !!webgl;
+        mustRecreate: (structureGroup: StructureGroup, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            return props.useGpu && !!webgl && suitableForGpu(structureGroup.structure, props, webgl);
         }
     }, materialId);
 }
@@ -104,7 +118,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
 
 async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness) / radiusFactor,
@@ -136,8 +150,8 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
         },
-        mustRecreate: (props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
-            return props.useGpu && !!webgl;
+        mustRecreate: (structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            return props.useGpu && !!webgl && suitableForGpu(structure, props, webgl);
         }
     }, materialId);
 }
@@ -190,8 +204,8 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
             if (newProps.includeParent !== currentProps.includeParent) state.createGeometry = true;
         },
-        mustRecreate: (props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
-            return !props.useGpu || !webgl;
+        mustRecreate: (structureGroup: StructureGroup, props: PD.Values<GaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            return !props.useGpu || !webgl || !suitableForGpu(structureGroup.structure, props, webgl);
         },
         dispose: (geometry: TextureMesh) => {
             geometry.vertexTexture.ref.value.destroy();
@@ -246,8 +260,8 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
             if (newProps.ignoreHydrogens !== currentProps.ignoreHydrogens) state.createGeometry = true;
             if (newProps.traceOnly !== currentProps.traceOnly) state.createGeometry = true;
         },
-        mustRecreate: (props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
-            return !props.useGpu || !webgl;
+        mustRecreate: (structure: Structure, props: PD.Values<StructureGaussianSurfaceMeshParams>, webgl?: WebGLContext) => {
+            return !props.useGpu || !webgl || !suitableForGpu(structure, props, webgl);
         },
         dispose: (geometry: TextureMesh) => {
             geometry.vertexTexture.ref.value.destroy();

+ 1 - 1
src/mol-repr/structure/visual/gaussian-surface-wireframe.ts

@@ -19,7 +19,7 @@ import { getUnitExtraRadius } from './util/common';
 
 async function createGaussianWireframe(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, lines?: Lines): Promise<Lines> {
     const { smoothness } = props;
-    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
+    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props).runInContext(ctx.runtime);
 
     const params = {
         isoLevel: Math.exp(-smoothness),

+ 16 - 5
src/mol-repr/structure/visual/util/common.ts

@@ -102,13 +102,24 @@ export function includesUnitKind(unitKinds: UnitKind[], unit: Unit) {
 
 const DefaultMaxCells = 500_000_000;
 
-/** guard against overly high resolution for the given box size */
-export function ensureReasonableResolution<T>(box: Box3D, props: { resolution: number } & T, maxCells = DefaultMaxCells) {
+export function getVolumeSliceInfo(box: Box3D, resolution: number, maxCells = DefaultMaxCells) {
     const size = Box3D.size(Vec3(), box);
-    const maxArea = Math.floor(Math.cbrt(maxCells) * Math.cbrt(maxCells));
+    Vec3.ceil(size, size);
+    size.sort((a, b) => b - a); // descending
+    const maxAreaCells = Math.floor(Math.cbrt(maxCells) * Math.cbrt(maxCells));
     const area = size[0] * size[1];
-    const maxAreaCells = Math.ceil(area / props.resolution);
-    const resolution = maxAreaCells > maxArea ? area / maxArea : props.resolution;
+    const areaCells = Math.ceil(area / (resolution * resolution));
+    return { area, areaCells, maxAreaCells };
+}
+
+/**
+ * Guard against overly high resolution for the given box size.
+ * Internally it uses the largest 2d slice of the box to determine the
+ * maximum resolution to account for the 2d texture layout on the GPU.
+ */
+export function ensureReasonableResolution<T>(box: Box3D, props: { resolution: number } & T, maxCells = DefaultMaxCells) {
+    const { area, areaCells, maxAreaCells } = getVolumeSliceInfo(box, props.resolution, maxCells);
+    const resolution = areaCells > maxAreaCells ? Math.sqrt(area / maxAreaCells) : props.resolution;
     return { ...props, resolution };
 }
 

+ 8 - 13
src/mol-repr/structure/visual/util/gaussian.ts

@@ -25,21 +25,16 @@ export type GaussianDensityProps = typeof DefaultGaussianDensityProps
 
 //
 
-function getTextureMaxCells(webgl: WebGLContext) {
-    const d = Math.min(webgl.maxTextureSize / 2, 4096);
-    return d * d;
-}
-
-function largestUnitBox(structure: Structure) {
-    const units = structure.unitsSortedByVolume;
-    return units[units.length - 1].lookup3d.boundary.box;
+export function getTextureMaxCells(webgl: WebGLContext, structure?: Structure) {
+    const d = webgl.maxTextureSize / 3;
+    return (d * d) / Math.max(1, (structure ? structure.units.length / 16 : 1));
 }
 
 //
 
-export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl?: WebGLContext) {
+export function computeUnitGaussianDensity(structure: Structure, unit: Unit, props: GaussianDensityProps) {
     const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(largestUnitBox(structure), props);
+    const p = ensureReasonableResolution(box, props);
     const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
     return Task.create('Gaussian Density', async ctx => {
         return await GaussianDensityCPU(ctx, position, box, radius, p);
@@ -48,7 +43,7 @@ export function computeUnitGaussianDensity(structure: Structure, unit: Unit, pro
 
 export function computeUnitGaussianDensityTexture(structure: Structure, unit: Unit, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(largestUnitBox(structure), props, getTextureMaxCells(webgl));
+    const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
     const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture(webgl, position, box, radius, p, texture);
@@ -57,7 +52,7 @@ export function computeUnitGaussianDensityTexture(structure: Structure, unit: Un
 
 export function computeUnitGaussianDensityTexture2d(structure: Structure, unit: Unit, powerOfTwo: boolean, props: GaussianDensityProps, webgl: WebGLContext, texture?: Texture) {
     const { box } = unit.lookup3d.boundary;
-    const p = ensureReasonableResolution(largestUnitBox(structure), props, getTextureMaxCells(webgl));
+    const p = ensureReasonableResolution(box, props, getTextureMaxCells(webgl, structure));
     const { position, radius } = getUnitConformationAndRadius(structure, unit, p);
     return Task.create('Gaussian Density', async ctx => {
         return GaussianDensityTexture2d(webgl, position, box, radius, powerOfTwo, p, texture);
@@ -66,7 +61,7 @@ export function computeUnitGaussianDensityTexture2d(structure: Structure, unit:
 
 //
 
-export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps, webgl?: WebGLContext) {
+export function computeStructureGaussianDensity(structure: Structure, props: GaussianDensityProps) {
     const { box } = structure.lookup3d.boundary;
     const p = ensureReasonableResolution(box, props);
     const { position, radius } = getStructureConformationAndRadius(structure, props.ignoreHydrogens, props.traceOnly);

+ 1 - 1
src/mol-repr/visual.ts

@@ -47,7 +47,7 @@ interface Visual<D, P extends PD.Params> {
     setTransparency: (transparency: Transparency) => void
     setClipping: (clipping: Clipping) => void
     destroy: () => void
-    mustRecreate?: (props: PD.Values<P>, webgl?: WebGLContext) => boolean
+    mustRecreate?: (data: D, props: PD.Values<P>, webgl?: WebGLContext) => boolean
 }
 namespace Visual {
     export type LociApply = (loci: Loci, apply: (interval: Interval) => boolean, isMarking: boolean) => boolean

+ 10 - 1
src/mol-repr/volume/direct-volume.ts

@@ -21,7 +21,7 @@ import { RepresentationContext, RepresentationParamsGetter } from '../representa
 import { Interval } from '../../mol-data/int';
 import { Loci, EmptyLoci } from '../../mol-model/loci';
 import { PickingId } from '../../mol-geo/geometry/picking';
-import { createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci } from './util';
+import { createVolumeTexture2d, createVolumeTexture3d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
 
 function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
     const bbox = Box3D();
@@ -34,6 +34,11 @@ function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
 
 export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
     const gridDimension = volume.grid.cells.space.dimensions as Vec3;
+    const { width, height } = getVolumeTexture2dLayout(gridDimension);
+    if(Math.max(width, height) > webgl.maxTextureSize / 2) {
+        throw new Error('volume too large for direct-volume rendering');
+    }
+
     const textureImage = createVolumeTexture2d(volume, 'normals');
     // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
     const transform = Grid.getGridToCartesianTransform(volume.grid);
@@ -72,6 +77,10 @@ function getUnitToCartn(grid: Grid) {
 
 export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, volume: Volume, directVolume?: DirectVolume) {
     const gridDimension = volume.grid.cells.space.dimensions as Vec3;
+    if(Math.max(...gridDimension) > webgl.max3dTextureSize / 2) {
+        throw new Error('volume too large for direct-volume rendering');
+    }
+
     const textureVolume = createVolumeTexture3d(volume);
     const transform = Grid.getGridToCartesianTransform(volume.grid);
     const bbox = getBoundingBox(gridDimension, transform);

+ 31 - 14
src/mol-repr/volume/isosurface.ts

@@ -39,10 +39,19 @@ function gpuSupport(webgl: WebGLContext) {
     return webgl.extensions.colorBufferFloat && webgl.extensions.textureFloat && webgl.extensions.drawBuffers;
 }
 
-export function IsosurfaceVisual(materialId: number, props?: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) {
-    return props?.useGpu && webgl && gpuSupport(webgl)
-        ? IsosurfaceTextureMeshVisual(materialId)
-        : IsosurfaceMeshVisual(materialId);
+const Padding = 1;
+
+function suitableForGpu(volume: Volume, webgl: WebGLContext) {
+    const gridDim = volume.grid.cells.space.dimensions as Vec3;
+    const { powerOfTwoSize } = getVolumeTexture2dLayout(gridDim, Padding);
+    return powerOfTwoSize <= webgl.maxTextureSize / 2;
+}
+
+export function IsosurfaceVisual(materialId: number, volume: Volume, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) {
+    if (props.useGpu && webgl && gpuSupport(webgl) && suitableForGpu(volume, webgl)) {
+        return IsosurfaceTextureMeshVisual(materialId);
+    }
+    return IsosurfaceMeshVisual(materialId);
 }
 
 function getLoci(volume: Volume, props: VolumeIsosurfaceProps) {
@@ -76,7 +85,11 @@ export async function createVolumeIsosurfaceMesh(ctx: VisualContext, volume: Vol
 
     const transform = Grid.getGridToCartesianTransform(volume.grid);
     Mesh.transform(surface, transform);
-    if (ctx.webgl && !ctx.webgl.isWebGL2) Mesh.uniformTriangleGroup(surface);
+    if (ctx.webgl && !ctx.webgl.isWebGL2) {
+        // 2nd arg means not to split triangles based on group id. Splitting triangles
+        // is too expensive if each cell has its own group id as is the case here.
+        Mesh.uniformTriangleGroup(surface, false);
+    }
 
     surface.setBoundingSphere(Volume.getBoundingSphere(volume));
 
@@ -88,7 +101,7 @@ export const IsosurfaceMeshParams = {
     ...TextureMesh.Params,
     ...VolumeIsosurfaceParams,
     quality: { ...Mesh.Params.quality, isEssential: false },
-    useGpu: PD.Boolean(false),
+    useGpu: PD.Boolean(true, { label: 'Try Use GPU' }),
 };
 export type IsosurfaceMeshParams = typeof IsosurfaceMeshParams
 
@@ -103,8 +116,8 @@ export function IsosurfaceMeshVisual(materialId: number): VolumeVisual<Isosurfac
             if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
         },
         geometryUtils: Mesh.Utils,
-        mustRecreate: (props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
-            return props.useGpu && !!webgl;
+        mustRecreate: (volume: Volume, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
+            return props.useGpu && !!webgl && suitableForGpu(volume, webgl);
         }
     }, materialId);
 }
@@ -117,14 +130,18 @@ namespace VolumeIsosurfaceTexture {
     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 { 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 (texDim > webgl.maxTextureSize / 2) {
+            throw new Error('volume too large for gpu isosurface extraction');
+        }
+
         if (!volume._propertyData[name]) {
             volume._propertyData[name] = resources.texture('image-uint8', 'alpha', 'ubyte', 'linear');
             const texture = volume._propertyData[name] as Texture;
@@ -135,8 +152,8 @@ namespace VolumeIsosurfaceTexture {
             volume.customProperties.assets(descriptor, [{ dispose: () => texture.destroy() }]);
         }
 
-        gridDimension[0] += padding;
-        gridDimension[1] += padding;
+        gridDimension[0] += Padding;
+        gridDimension[1] += Padding;
 
         return {
             texture: volume._propertyData[name] as Texture,
@@ -176,8 +193,8 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
             if (!Volume.IsoValue.areSame(newProps.isoValue, currentProps.isoValue, volume.grid.stats)) state.createGeometry = true;
         },
         geometryUtils: TextureMesh.Utils,
-        mustRecreate: (props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
-            return !props.useGpu || !webgl;
+        mustRecreate: (volume: Volume, props: PD.Values<IsosurfaceMeshParams>, webgl?: WebGLContext) => {
+            return !props.useGpu || !webgl || !suitableForGpu(volume, webgl);
         },
         dispose: (geometry: TextureMesh) => {
             geometry.vertexTexture.ref.value.destroy();

+ 5 - 5
src/mol-repr/volume/representation.ts

@@ -49,7 +49,7 @@ interface VolumeVisualBuilder<P extends VolumeParams, G extends Geometry> {
     getLoci(pickingId: PickingId, volume: Volume, props: PD.Values<P>, id: number): Loci
     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
+    mustRecreate?: (volume: Volume, props: PD.Values<P>) => boolean
     dispose?: (geometry: G) => void
 }
 
@@ -231,7 +231,7 @@ export const VolumeParams = {
 };
 export type VolumeParams = typeof VolumeParams
 
-export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, P>, visualCtor: (materialId: number, props?: PD.Values<P>, webgl?: WebGLContext) => VolumeVisual<P>, getLoci: (volume: Volume, props: PD.Values<P>) => Loci): VolumeRepresentation<P> {
+export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx: RepresentationContext, getParams: RepresentationParamsGetter<Volume, P>, visualCtor: (materialId: number, volume: Volume, props: PD.Values<P>, webgl?: WebGLContext) => VolumeVisual<P>, getLoci: (volume: Volume, props: PD.Values<P>) => Loci): VolumeRepresentation<P> {
     let version = 0;
     const { webgl } = ctx;
     const updated = new Subject<number>();
@@ -256,10 +256,10 @@ export function VolumeRepresentation<P extends VolumeParams>(label: string, ctx:
 
         return Task.create('Creating or updating VolumeRepresentation', async runtime => {
             if (!visual) {
-                visual = visualCtor(materialId, _props, webgl);
-            } else if (visual.mustRecreate?.(_props, webgl)) {
+                visual = visualCtor(materialId, _volume, _props, webgl);
+            } else if (visual.mustRecreate?.(_volume, _props, webgl)) {
                 visual.destroy();
-                visual = visualCtor(materialId, _props, webgl);
+                visual = visualCtor(materialId, _volume, _props, webgl);
             }
             const promise = visual.createOrUpdate({ webgl, runtime }, _theme, _props, volume);
             if (promise) await promise;