Преглед на файлове

Merge branch 'master' into state_representation

David Sehnal преди 6 години
родител
ревизия
2c252ac798
променени са 36 файла, в които са добавени 687 реда и са изтрити 373 реда
  1. 1 1
      src/apps/canvas/index.ts
  2. 3 2
      src/apps/canvas/structure-view.ts
  3. 2 1
      src/apps/canvas/volume-view.ts
  4. 48 4
      src/mol-geo/geometry/direct-volume/direct-volume.ts
  5. 3 1
      src/mol-geo/geometry/geometry.ts
  6. 2 1
      src/mol-geo/representation/structure/index.ts
  7. 26 11
      src/mol-geo/representation/structure/units-visual.ts
  8. 0 1
      src/mol-geo/representation/structure/visual/gaussian-density-point.ts
  9. 16 34
      src/mol-geo/representation/structure/visual/gaussian-density-volume.ts
  10. 0 1
      src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts
  11. 0 1
      src/mol-geo/representation/structure/visual/gaussian-surface-wireframe.ts
  12. 7 6
      src/mol-geo/representation/structure/visual/util/common.ts
  13. 42 85
      src/mol-geo/representation/volume/direct-volume.ts
  14. 7 9
      src/mol-gl/renderable/direct-volume.ts
  15. 2 2
      src/mol-gl/renderable/gaussian-density.ts
  16. 0 2
      src/mol-gl/renderable/lines.ts
  17. 0 2
      src/mol-gl/renderable/mesh.ts
  18. 0 2
      src/mol-gl/renderable/points.ts
  19. 4 4
      src/mol-gl/renderable/schema.ts
  20. 11 7
      src/mol-gl/shader-code.ts
  21. 1 1
      src/mol-gl/shader/direct-volume.frag
  22. 36 12
      src/mol-gl/shader/gaussian-density.frag
  23. 1 1
      src/mol-gl/shader/gaussian-density.vert
  24. 0 4
      src/mol-gl/shader/mesh.frag
  25. 37 3
      src/mol-gl/webgl/context.ts
  26. 7 2
      src/mol-gl/webgl/program.ts
  27. 14 5
      src/mol-gl/webgl/render-item.ts
  28. 1 1
      src/mol-gl/webgl/render-target.ts
  29. 91 20
      src/mol-gl/webgl/texture.ts
  30. 3 3
      src/mol-gl/webgl/uniform.ts
  31. 7 4
      src/mol-math/geometry/common.ts
  32. 2 1
      src/mol-math/geometry/gaussian-density.ts
  33. 280 134
      src/mol-math/geometry/gaussian-density/gpu.ts
  34. 20 3
      src/mol-model/structure/structure/unit/gaussian-density.ts
  35. 8 1
      src/mol-view/parameter.ts
  36. 5 1
      src/mol-view/viewer.ts

+ 1 - 1
src/apps/canvas/index.ts

@@ -23,4 +23,4 @@ const assemblyId = urlQueryParameter('assembly')
 const pdbId = urlQueryParameter('pdb')
 if (pdbId) app.loadPdbIdOrMmcifUrl(pdbId, { assemblyId })
 
-app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc')
+// app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc')

+ 3 - 2
src/apps/canvas/structure-view.ts

@@ -70,7 +70,7 @@ export async function StructureView(app: App, viewer: Viewer, models: ReadonlyAr
     const active: { [k: string]: boolean } = {
         cartoon: true,
         point: false,
-        surface: false,
+        surface: true,
         ballAndStick: false,
         carbohydrate: false,
         spacefill: false,
@@ -211,7 +211,8 @@ export async function StructureView(app: App, viewer: Viewer, models: ReadonlyAr
             console.log('createStructureRepr')
             for (const k in structureRepresentations) {
                 if (active[k]) {
-                    await app.runTask(structureRepresentations[k].createOrUpdate({}, structure).run(
+                    const p = { webgl: viewer.webgl }
+                    await app.runTask(structureRepresentations[k].createOrUpdate(p, structure).run(
                         progress => console.log(Progress.format(progress))
                     ), 'Create/update representation')
                     viewer.add(structureRepresentations[k])

+ 2 - 1
src/apps/canvas/volume-view.ts

@@ -55,7 +55,8 @@ export async function VolumeView(app: App, viewer: Viewer, volume: VolumeData, p
     async function createVolumeRepr() {
         for (const k in volumeRepresentations) {
             if (active[k]) {
-                await app.runTask(volumeRepresentations[k].createOrUpdate({}, volume).run(
+                const p = { webgl: viewer.webgl }
+                await app.runTask(volumeRepresentations[k].createOrUpdate(p, volume).run(
                     progress => console.log(Progress.format(progress))
                 ), 'Create/update representation')
                 viewer.add(volumeRepresentations[k])

+ 48 - 4
src/mol-geo/geometry/direct-volume/direct-volume.ts

@@ -6,13 +6,13 @@
 
 import { RuntimeContext } from 'mol-task'
 import { ValueCell } from 'mol-util'
-import { Sphere3D } from 'mol-math/geometry'
+import { Sphere3D, Box3D } from 'mol-math/geometry'
 import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, TextParam } from 'mol-view/parameter';
 import { DirectVolume2dValues, DirectVolumeBaseValues, DirectVolume3dValues } from 'mol-gl/renderable/direct-volume';
-import { TextureImage, TextureVolume } from 'mol-gl/renderable/util';
 import { Vec3, Vec2, Mat4 } from 'mol-math/linear-algebra';
 import { Box } from '../../primitive/box';
 import { getControlPointsFromString, createTransferFunctionTexture } from './transfer-function';
+import { Texture } from 'mol-gl/webgl/texture';
 
 const VolumeBox = Box()
 const RenderModeOptions = [['isosurface', 'Isosurface'], ['volume', 'Volume']] as [string, string][]
@@ -87,11 +87,34 @@ function updateBaseValues(values: DirectVolumeBaseValues, props: BaseProps) {
 
 export interface DirectVolume2d extends DirectVolumeBase {
     readonly kind: 'direct-volume-2d',
-    readonly gridTexture: ValueCell<TextureImage<any>>,
+    readonly gridTexture: ValueCell<Texture>,
     readonly gridTextureDim: ValueCell<Vec2>,
 }
 
 export namespace DirectVolume2d {
+    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume2d): DirectVolume2d {
+        if (directVolume) {
+            ValueCell.update(directVolume.gridDimension, gridDimension)
+            ValueCell.update(directVolume.gridTextureDim, Vec2.set(directVolume.gridTextureDim.ref.value, texture.width, texture.height))
+            ValueCell.update(directVolume.bboxMin, bbox.min)
+            ValueCell.update(directVolume.bboxMax, bbox.max)
+            ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
+            ValueCell.update(directVolume.transform, transform)
+            return directVolume
+        } else {
+            return {
+                kind: 'direct-volume-2d' as 'direct-volume-2d',
+                gridDimension: ValueCell.create(gridDimension),
+                gridTexture: ValueCell.create(texture),
+                gridTextureDim: ValueCell.create(Vec2.create(texture.width, texture.height)),
+                bboxMin: ValueCell.create(bbox.min),
+                bboxMax: ValueCell.create(bbox.max),
+                bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
+                transform: ValueCell.create(transform),
+            }
+        }
+    }
+
     export function createEmpty(directVolume?: DirectVolume2d): DirectVolume2d {
         return {} as DirectVolume2d // TODO
     }
@@ -120,10 +143,31 @@ export namespace DirectVolume2d {
 
 export interface DirectVolume3d extends DirectVolumeBase {
     readonly kind: 'direct-volume-3d',
-    readonly gridTexture: ValueCell<TextureVolume<any>>,
+    readonly gridTexture: ValueCell<Texture>,
 }
 
 export namespace DirectVolume3d {
+    export function create(bbox: Box3D, gridDimension: Vec3, transform: Mat4, texture: Texture, directVolume?: DirectVolume3d): DirectVolume3d {
+        if (directVolume) {
+            ValueCell.update(directVolume.gridDimension, gridDimension)
+            ValueCell.update(directVolume.bboxMin, bbox.min)
+            ValueCell.update(directVolume.bboxMax, bbox.max)
+            ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
+            ValueCell.update(directVolume.transform, transform)
+            return directVolume
+        } else {
+            return {
+                kind: 'direct-volume-3d' as 'direct-volume-3d',
+                gridDimension: ValueCell.create(gridDimension),
+                gridTexture: ValueCell.create(texture),
+                bboxMin: ValueCell.create(bbox.min),
+                bboxMax: ValueCell.create(bbox.max),
+                bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
+                transform: ValueCell.create(transform),
+            }
+        }
+    }
+
     export function createEmpty(directVolume?: DirectVolume3d): DirectVolume3d {
         return {} as DirectVolume3d // TODO
     }

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

@@ -15,9 +15,10 @@ import { LocationIterator } from '../util/location-iterator';
 import { ColorType } from './color-data';
 import { SizeType } from './size-data';
 import { Lines } from './lines/lines';
-import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, ColorParam, StructureParam } from 'mol-view/parameter'
+import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, ColorParam, StructureParam, ValueParam } from 'mol-view/parameter'
 import { Structure } from 'mol-model/structure';
 import { DirectVolume2d, DirectVolume3d } from './direct-volume/direct-volume';
+import { Context } from 'mol-gl/webgl/context';
 
 //
 
@@ -70,6 +71,7 @@ export namespace Geometry {
         colorTheme: SelectParam<ColorThemeName>('Color Theme', '', 'uniform', ColorThemeOptions),
         colorValue: ColorParam('Color Value', '', Color(0xCCCCCC)),
         structure: StructureParam('Structure', '', Structure.Empty),
+        webgl: ValueParam('WebGL Context', '', undefined as Context | undefined),
     }
     export const DefaultProps = paramDefaultValues(Params)
     export type Props = typeof DefaultProps

+ 2 - 1
src/mol-geo/representation/structure/index.ts

@@ -14,7 +14,7 @@ import { Mesh } from '../../geometry/mesh/mesh';
 import { Points } from '../../geometry/points/points';
 import { Lines } from '../../geometry/lines/lines';
 import { SelectParam, paramDefaultValues } from 'mol-view/parameter';
-import { DirectVolume2d } from '../../geometry/direct-volume/direct-volume';
+import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
 
 export interface StructureRepresentation<P extends RepresentationProps = {}> extends Representation<Structure, P> { }
 
@@ -49,6 +49,7 @@ export type StructureLinesProps = typeof DefaultStructureLinesProps
 
 export const StructureDirectVolumeParams = {
     ...DirectVolume2d.Params,
+    ...DirectVolume3d.Params,
     ...StructureParams,
 }
 export const DefaultStructureDirectVolumeProps = paramDefaultValues(StructureDirectVolumeParams)

+ 26 - 11
src/mol-geo/representation/structure/units-visual.ts

@@ -15,7 +15,7 @@ import { LocationIterator } from '../../util/location-iterator';
 import { Mesh } from '../../geometry/mesh/mesh';
 import { MarkerAction, applyMarkerAction, createMarkers } from '../../geometry/marker-data';
 import { Loci, isEveryLoci, EmptyLoci } from 'mol-model/loci';
-import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolume2dRenderObject } from 'mol-gl/render-object';
+import { MeshRenderObject, PointsRenderObject, LinesRenderObject, DirectVolume2dRenderObject, DirectVolume3dRenderObject } from 'mol-gl/render-object';
 import { createUnitsMeshRenderObject, createUnitsPointsRenderObject, createUnitsTransform, createUnitsLinesRenderObject, createUnitsDirectVolumeRenderObject } from './visual/util/common';
 import { deepEqual, ValueCell, UUID } from 'mol-util';
 import { Interval } from 'mol-data/int';
@@ -25,7 +25,7 @@ import { createColors, ColorProps } from '../../geometry/color-data';
 import { createSizes, SizeProps } from '../../geometry/size-data';
 import { Lines } from '../../geometry/lines/lines';
 import { MultiSelectParam, paramDefaultValues } from 'mol-view/parameter';
-import { DirectVolume2d } from '../../geometry/direct-volume/direct-volume';
+import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
 
 export const UnitKindInfo = {
     'atomic': {},
@@ -72,7 +72,7 @@ function colorChanged(oldProps: ColorProps, newProps: ColorProps) {
 }
 
 const UnitsParams = {
-    unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', [ 'atomic', 'spheres' ], UnitKindOptions),
+    unitKinds: MultiSelectParam<UnitKind>('Unit Kind', '', ['atomic', 'spheres'], UnitKindOptions),
 }
 
 interface UnitsVisualBuilder<P extends StructureProps, G extends Geometry> {
@@ -526,21 +526,24 @@ export const UnitsDirectVolumeParams = {
 }
 export const DefaultUnitsDirectVolumeProps = paramDefaultValues(UnitsDirectVolumeParams)
 export type UnitsDirectVolumeProps = typeof DefaultUnitsDirectVolumeProps
-export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume2d> { }
+export interface UnitsDirectVolumeVisualBuilder<P extends UnitsDirectVolumeProps> extends UnitsVisualBuilder<P, DirectVolume2d | DirectVolume3d> { }
 
 export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builder: UnitsDirectVolumeVisualBuilder<P>): UnitsVisual<P> {
     const { defaultProps, createGeometry, createLocationIterator, getLoci, setUpdateState } = builder
     const updateState = VisualUpdateState.create()
 
-    let renderObject: DirectVolume2dRenderObject | undefined
+    let renderObject: DirectVolume2dRenderObject | DirectVolume3dRenderObject | undefined
     let currentProps: P
-    let directVolume: DirectVolume2d
+    let directVolume: DirectVolume2d | DirectVolume3d
     let currentGroup: Unit.SymmetryGroup
     let currentStructure: Structure
     let locationIt: LocationIterator
     let currentConformationId: UUID
 
     async function create(ctx: RuntimeContext, group: Unit.SymmetryGroup, props: Partial<P> = {}) {
+        const { webgl } = props
+        if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props')
+
         currentProps = Object.assign({}, defaultProps, props, { structure: currentStructure })
         currentGroup = group
 
@@ -548,7 +551,11 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
         currentConformationId = Unit.conformationId(unit)
         directVolume = includesUnitKind(currentProps.unitKinds, unit)
             ? await createGeometry(ctx, unit, currentStructure, currentProps, directVolume)
-            : DirectVolume2d.createEmpty(directVolume)
+            : (webgl.isWebGL2 ?
+                DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
+                DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
+
+        console.log('directVolume', directVolume)
 
         // TODO create empty location iterator when not in unitKinds
         locationIt = createLocationIterator(group)
@@ -556,6 +563,9 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
     }
 
     async function update(ctx: RuntimeContext, props: Partial<P> = {}) {
+        const { webgl } = props
+        if (webgl === undefined) throw new Error('UnitsDirectVolumeVisual requires `webgl` in props')
+
         if (!renderObject) return
 
         const newProps = Object.assign({}, currentProps, props, { structure: currentStructure })
@@ -590,7 +600,9 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
         if (updateState.createGeometry) {
             directVolume = includesUnitKind(newProps.unitKinds, unit)
                 ? await createGeometry(ctx, unit, currentStructure, newProps, directVolume)
-                : DirectVolume2d.createEmpty(directVolume)
+                : (webgl.isWebGL2 ?
+                    DirectVolume2d.createEmpty(directVolume as DirectVolume2d) :
+                    DirectVolume3d.createEmpty(directVolume as DirectVolume3d))
             updateState.updateColor = true
         }
 
@@ -598,9 +610,12 @@ export function UnitsDirectVolumeVisual<P extends UnitsDirectVolumeProps>(builde
         //     await createColors(ctx, locationIt, newProps, renderObject.values)
         // }
 
-        // TODO why do I need to cast here?
-        DirectVolume2d.updateValues(renderObject.values, newProps as UnitsDirectVolumeProps)
-        updateRenderableState(renderObject.state, newProps as UnitsDirectVolumeProps)
+        if (renderObject.type === 'direct-volume-2d') {
+            DirectVolume2d.updateValues(renderObject.values, newProps)
+        } else {
+            DirectVolume3d.updateValues(renderObject.values, newProps)
+        }
+        updateRenderableState(renderObject.state, newProps)
 
         currentProps = newProps
     }

+ 0 - 1
src/mol-geo/representation/structure/visual/gaussian-density-point.ts

@@ -69,7 +69,6 @@ export function GaussianDensityPointVisual(): UnitsVisual<GaussianDensityPointPr
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true
             if (newProps.useGpu !== currentProps.useGpu) state.createGeometry = true
-            if (newProps.readSlices !== currentProps.readSlices) state.createGeometry = true
             if (newProps.ignoreCache !== currentProps.ignoreCache) state.createGeometry = true
         }
     })

+ 16 - 34
src/mol-geo/representation/structure/visual/gaussian-density-volume.ts

@@ -9,42 +9,22 @@ import { UnitsVisual, VisualUpdateState } from '..';
 import { RuntimeContext } from 'mol-task'
 import { UnitsDirectVolumeVisual, UnitsDirectVolumeParams } from '../units-visual';
 import { StructureElementIterator, getElementLoci, markElement } from './util/element';
-import { GaussianDensityProps, GaussianDensityParams } from 'mol-model/structure/structure/unit/gaussian-density';
+import { GaussianDensityProps, GaussianDensityParams, computeUnitGaussianDensityTexture } from 'mol-model/structure/structure/unit/gaussian-density';
 import { paramDefaultValues } from 'mol-view/parameter';
-import { DirectVolume2d } from '../../../geometry/direct-volume/direct-volume';
-import { ValueCell } from 'mol-util';
-import { Vec3, Vec2 } from 'mol-math/linear-algebra';
+import { DirectVolume2d, DirectVolume3d } from '../../../geometry/direct-volume/direct-volume';
 
-async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume2d): Promise<DirectVolume2d> {
-    const p = { ...props, useGpu: true, ignoreCache: true }
-    const { transform, renderTarget, bbox, gridDimension } = await unit.computeGaussianDensity(p, ctx)
-    if (!renderTarget || !bbox || !gridDimension) throw new Error('missing renderTarget and/or boundingBox and/or gridDimension')
+async function createGaussianDensityVolume(ctx: RuntimeContext, unit: Unit, structure: Structure, props: GaussianDensityProps, directVolume?: DirectVolume2d | DirectVolume3d): Promise<DirectVolume2d | DirectVolume3d> {
+    const { webgl } = props
+    if (webgl === undefined) throw new Error('createGaussianDensityVolume requires `webgl` in props')
 
-    if (directVolume) {
-        ValueCell.update(directVolume.gridDimension, gridDimension)
-        ValueCell.update(directVolume.gridTexture, renderTarget.image)
-        ValueCell.update(directVolume.gridTextureDim, Vec2.set(directVolume.gridTextureDim.ref.value, renderTarget.width, renderTarget.height))
-        ValueCell.update(directVolume.bboxMin, bbox.min)
-        ValueCell.update(directVolume.bboxMax, bbox.max)
-        ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
-        ValueCell.update(directVolume.transform, transform)
-    } else {
-        directVolume = {
-            kind: 'direct-volume-2d' as 'direct-volume-2d',
-            gridDimension: ValueCell.create(gridDimension),
-            gridTexture: ValueCell.create(renderTarget.image),
-            gridTextureDim: ValueCell.create(Vec2.create(renderTarget.width, renderTarget.height)),
-            bboxMin: ValueCell.create(bbox.min),
-            bboxMax: ValueCell.create(bbox.max),
-            bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
-            transform: ValueCell.create(transform),
-        }
-    }
+    const p = { ...props, useGpu: true }
+    const oldTexture = directVolume ? directVolume.gridTexture.ref.value : undefined
+    const densityTextureData = await computeUnitGaussianDensityTexture(unit, p, oldTexture).runInContext(ctx)
+    const { transform, texture, bbox, gridDimension } = densityTextureData
 
-    console.log('gridDimension', gridDimension)
-    console.log('gridTextureDim', renderTarget.width, renderTarget.height)
-    console.log('boundingBox', bbox)
-    console.log('transform', transform)
+    directVolume = texture.depth === 0 ?
+        DirectVolume2d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume2d) :
+        DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume3d)
 
     return directVolume;
 }
@@ -66,9 +46,11 @@ export function GaussianDensityVolumeVisual(): UnitsVisual<GaussianDensityVolume
         setUpdateState: (state: VisualUpdateState, newProps: GaussianDensityVolumeProps, currentProps: GaussianDensityVolumeProps) => {
             if (newProps.resolution !== currentProps.resolution) state.createGeometry = true
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
-            if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true
+            if (newProps.smoothness !== currentProps.smoothness) {
+                state.createGeometry = true
+                newProps.isoValueAbsolute = Math.exp(-newProps.smoothness)
+            }
             if (newProps.useGpu !== currentProps.useGpu) state.createGeometry = true
-            if (newProps.readSlices !== currentProps.readSlices) state.createGeometry = true
             if (newProps.ignoreCache !== currentProps.ignoreCache) state.createGeometry = true
         }
     })

+ 0 - 1
src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts

@@ -93,7 +93,6 @@ export function GaussianSurfaceVisual(): UnitsVisual<GaussianSurfaceProps> {
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true
             if (newProps.useGpu !== currentProps.useGpu) state.createGeometry = true
-            if (newProps.readSlices !== currentProps.readSlices) state.createGeometry = true
             if (newProps.ignoreCache !== currentProps.ignoreCache) state.createGeometry = true
         }
     })

+ 0 - 1
src/mol-geo/representation/structure/visual/gaussian-surface-wireframe.ts

@@ -53,7 +53,6 @@ export function GaussianWireframeVisual(): UnitsVisual<GaussianWireframeProps> {
             if (newProps.radiusOffset !== currentProps.radiusOffset) state.createGeometry = true
             if (newProps.smoothness !== currentProps.smoothness) state.createGeometry = true
             if (newProps.useGpu !== currentProps.useGpu) state.createGeometry = true
-            if (newProps.readSlices !== currentProps.readSlices) state.createGeometry = true
             if (newProps.ignoreCache !== currentProps.ignoreCache) state.createGeometry = true
         }
     })

+ 7 - 6
src/mol-geo/representation/structure/visual/util/common.ts

@@ -8,14 +8,14 @@ import { Unit, Structure } from 'mol-model/structure';
 import { LocationIterator } from '../../../../util/location-iterator';
 import { Mesh } from '../../../../geometry/mesh/mesh';
 import { StructureProps } from '../..';
-import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolume2dRenderObject } from 'mol-gl/render-object';
+import { createMeshRenderObject, createPointsRenderObject, createLinesRenderObject, createDirectVolume2dRenderObject, createDirectVolume3dRenderObject } from 'mol-gl/render-object';
 import { RuntimeContext } from 'mol-task';
 import { TransformData, createIdentityTransform, createTransform } from '../../../../geometry/transform-data';
 import { Points } from '../../../../geometry/points/points';
 import { createRenderableState } from '../../../../geometry/geometry';
 import { Mat4 } from 'mol-math/linear-algebra';
 import { Lines } from '../../../../geometry/lines/lines';
-import { DirectVolume2d } from '../../../../geometry/direct-volume/direct-volume';
+import { DirectVolume2d, DirectVolume3d } from '../../../../geometry/direct-volume/direct-volume';
 
 export function createUnitsTransform({ units }: Unit.SymmetryGroup, transformData?: TransformData) {
     const unitCount = units.length
@@ -70,12 +70,13 @@ export async function createUnitsLinesRenderObject(ctx: RuntimeContext, group: U
 
 // direct-volume
 
-type StructureDirectVolumeProps = DirectVolume2d.Props & StructureProps
+type StructureDirectVolumeProps = DirectVolume2d.Props & DirectVolume3d.Props & StructureProps
 
-export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume2d, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
+export async function createUnitsDirectVolumeRenderObject(ctx: RuntimeContext, group: Unit.SymmetryGroup, directVolume: DirectVolume2d | DirectVolume3d, locationIt: LocationIterator, props: StructureDirectVolumeProps) {
     // TODO transform support
     // const transform = createUnitsTransform(group)
-    const values = await DirectVolume2d.createValues(ctx, directVolume, props)
     const state = createRenderableState(props)
-    return createDirectVolume2dRenderObject(values, state)
+    return directVolume.kind === 'direct-volume-2d' ?
+        createDirectVolume2dRenderObject(await DirectVolume2d.createValues(ctx, directVolume, props), state) :
+        createDirectVolume3dRenderObject(await DirectVolume3d.createValues(ctx, directVolume, props), state)
 }

+ 42 - 85
src/mol-geo/representation/volume/direct-volume.ts

@@ -13,17 +13,23 @@ import { MarkerAction } from '../../geometry/marker-data';
 import { Loci, EmptyLoci } from 'mol-model/loci';
 import { createRenderableState, updateRenderableState, Geometry } from '../../geometry/geometry';
 import { paramDefaultValues } from 'mol-view/parameter';
-import { ValueCell } from 'mol-util';
 import { DirectVolume2d, DirectVolume3d } from '../../geometry/direct-volume/direct-volume';
-import { Vec2, Vec3 } from 'mol-math/linear-algebra';
+import { Vec3, Mat4 } from 'mol-math/linear-algebra';
 import { Box3D } from 'mol-math/geometry';
-import { createImageData } from 'mol-gl/webgl/context';
-import { debugTexture } from 'mol-gl/util';
+import { Context } from 'mol-gl/webgl/context';
 import { DirectVolume3dValues, DirectVolume2dValues } from 'mol-gl/renderable/direct-volume';
+import { createTexture } from 'mol-gl/webgl/texture';
+
+function getBoundingBox(gridDimension: Vec3, transform: Mat4) {
+    const bbox = Box3D.empty()
+    Box3D.add(bbox, gridDimension)
+    Box3D.transform(bbox, bbox, transform)
+    return bbox
+}
 
 // 2d volume texture
 
-function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize = 4096) {
+function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize: number) {
     let width = 0
     let height = dim[1]
     let rows = 1
@@ -41,19 +47,19 @@ function getVolumeTexture2dLayout(dim: Vec3, maxTextureSize = 4096) {
     return { width, height, columns, rows }
 }
 
-function createVolumeTexture2d(volume: VolumeData) {
+function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) {
     const { data: tensor, dataStats: stats } = volume
     const { space, data } = tensor
     const dim = space.dimensions as Vec3
     const { get } = space
-    const { width, height, columns, rows } = getVolumeTexture2dLayout(dim)
+    const { width, height, columns, rows } = getVolumeTexture2dLayout(dim, maxTextureSize)
 
     const array = new Uint8Array(width * height * 4)
     const textureImage = { array, width, height }
 
     const [ xl, yl, zl ] = dim
-    const xlp = xl + 1
-    const ylp = yl + 1
+    const xlp = xl + 1 // horizontal padding
+    const ylp = yl + 1 // vertical padding
 
     function setTex(value: number, x: number, y: number, z: number) {
         const column = Math.floor(((z * xlp) % width) / xlp)
@@ -77,48 +83,20 @@ function createVolumeTexture2d(volume: VolumeData) {
     return textureImage
 }
 
-export function createDirectVolume2d(ctx: RuntimeContext, volume: VolumeData, directVolume?: DirectVolume2d) {
+export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume2d) {
     const gridDimension = volume.data.space.dimensions as Vec3
-    // const textureImage = createTextureImage(1, 4)
-    const textureImage = createVolumeTexture2d(volume)
+    const textureImage = createVolumeTexture2d(volume, webgl.maxTextureSize)
+    // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
     const transform = VolumeData.getGridToCartesianTransform(volume)
+    const bbox = getBoundingBox(gridDimension, transform)
+    const dim = Vec3.create(gridDimension[0], gridDimension[1], gridDimension[2])
+    dim[0] += 1 // horizontal padding
+    dim[0] += 1 // vertical padding
 
-    console.log('textureImage', textureImage)
-    debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
-
-    const bbox = Box3D.empty()
-    Box3D.add(bbox, gridDimension)
-    Box3D.transform(bbox, bbox, transform)
-
-    const dim = Vec3.create(gridDimension[0] + 1, gridDimension[1] + 1, gridDimension[2])
-
-    if (directVolume) {
-        ValueCell.update(directVolume.gridDimension, dim)
-        ValueCell.update(directVolume.gridTexture, textureImage)
-        ValueCell.update(directVolume.gridTextureDim, Vec2.set(directVolume.gridTextureDim.ref.value, textureImage.width, textureImage.height))
-        ValueCell.update(directVolume.bboxMin, bbox.min)
-        ValueCell.update(directVolume.bboxMax, bbox.max)
-        ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
-        ValueCell.update(directVolume.transform, transform)
-    } else {
-        directVolume = {
-            kind: 'direct-volume-2d' as 'direct-volume-2d',
-            gridDimension: ValueCell.create(dim),
-            gridTexture: ValueCell.create(textureImage),
-            gridTextureDim: ValueCell.create(Vec2.create(textureImage.width, textureImage.height)),
-            bboxMin: ValueCell.create(bbox.min),
-            bboxMax: ValueCell.create(bbox.max),
-            bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
-            transform: ValueCell.create(transform),
-        }
-    }
-
-    console.log('gridDimension', dim)
-    console.log('gridTextureDim', textureImage.width, textureImage.height)
-    console.log('boundingBox', bbox)
-    console.log('transform', transform)
+    const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    texture.load(textureImage)
 
-    return directVolume;
+    return DirectVolume2d.create(bbox, dim, transform, texture, directVolume)
 }
 
 // 3d volume texture
@@ -135,8 +113,7 @@ function createVolumeTexture3d(volume: VolumeData) {
     let i = 0
     for (let z = 0; z < depth; ++z) {
         for (let y = 0; y < height; ++y) {
-        for (let x = 0; x < width; ++x) {
-
+            for (let x = 0; x < width; ++x) {
                 array[i + 3] = ((get(data, x, y, z) - stats.min) / (stats.max - stats.min)) * 255
                 i += 4
             }
@@ -146,45 +123,20 @@ function createVolumeTexture3d(volume: VolumeData) {
     return textureVolume
 }
 
-export function createDirectVolume3d(ctx: RuntimeContext, volume: VolumeData, directVolume?: DirectVolume3d) {
+export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume3d) {
     const gridDimension = volume.data.space.dimensions as Vec3
     const textureVolume = createVolumeTexture3d(volume)
     const transform = VolumeData.getGridToCartesianTransform(volume)
+    const bbox = getBoundingBox(gridDimension, transform)
 
-    console.log('textureVolume', textureVolume)
-
-    const bbox = Box3D.empty()
-    Box3D.add(bbox, gridDimension)
-    Box3D.transform(bbox, bbox, transform)
-
-    if (directVolume) {
-        ValueCell.update(directVolume.gridDimension, gridDimension)
-        ValueCell.update(directVolume.gridTexture, textureVolume)
-        ValueCell.update(directVolume.bboxMin, bbox.min)
-        ValueCell.update(directVolume.bboxMax, bbox.max)
-        ValueCell.update(directVolume.bboxSize, Vec3.sub(directVolume.bboxSize.ref.value, bbox.max, bbox.min))
-        ValueCell.update(directVolume.transform, transform)
-    } else {
-        directVolume = {
-            kind: 'direct-volume-3d' as 'direct-volume-3d',
-            gridDimension: ValueCell.create(gridDimension),
-            gridTexture: ValueCell.create(textureVolume),
-            bboxMin: ValueCell.create(bbox.min),
-            bboxMax: ValueCell.create(bbox.max),
-            bboxSize: ValueCell.create(Vec3.sub(Vec3.zero(), bbox.max, bbox.min)),
-            transform: ValueCell.create(transform),
-        }
-    }
+    const texture = directVolume ? directVolume.gridTexture.ref.value : createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
+    texture.load(textureVolume)
 
-    return directVolume;
+    return DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume)
 }
 
 //
 
-function hasWebGL2() {
-    return true
-}
-
 export const DirectVolumeParams = {
     ...Geometry.Params,
     ...DirectVolume2d.Params
@@ -199,6 +151,9 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
     let directVolume: DirectVolume2d | DirectVolume3d
 
     async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<DirectVolumeProps> = {}) {
+        const { webgl } = props
+        if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props')
+
         currentProps = { ...DefaultDirectVolumeProps, ...props }
         if (props.isoValueRelative) {
             // currentProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative)
@@ -206,26 +161,28 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> {
 
         const state = createRenderableState(currentProps)
 
-        if (hasWebGL2()) {
-            console.log('createing 3d volume')
-            directVolume = await createDirectVolume3d(ctx, volume, directVolume as DirectVolume3d)
+        if (webgl.isWebGL2) {
+            console.log('creating 3d volume')
+            directVolume = await createDirectVolume3d(ctx, webgl, volume, directVolume as DirectVolume3d)
             const values = await DirectVolume3d.createValues(ctx, directVolume as DirectVolume3d, currentProps)
             renderObject = createDirectVolume3dRenderObject(values, state)
         } else {
-            directVolume = await createDirectVolume2d(ctx, volume, directVolume as DirectVolume2d)
+            directVolume = await createDirectVolume2d(ctx, webgl, volume, directVolume as DirectVolume2d)
             const values = await DirectVolume2d.createValues(ctx, directVolume as DirectVolume2d, currentProps)
             renderObject = createDirectVolume2dRenderObject(values, state)
         }
     }
 
     async function update(ctx: RuntimeContext, props: Partial<DirectVolumeProps> = {}) {
-        console.log('props', props)
+        const { webgl } = props
+        if (webgl === undefined) throw new Error('DirectVolumeVisual requires `webgl` in props')
+
         const newProps = { ...currentProps, ...props }
         if (props.isoValueRelative) {
             // newProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative)
         }
 
-        if (hasWebGL2()) {
+        if (webgl.isWebGL2) {
             DirectVolume3d.updateValues(renderObject.values as DirectVolume3dValues, newProps)
         } else {
             DirectVolume2d.updateValues(renderObject.values as DirectVolume2dValues, newProps)

+ 7 - 9
src/mol-gl/renderable/direct-volume.ts

@@ -34,17 +34,15 @@ export const DirectVolumeBaseSchema = {
 export type DirectVolumeBaseSchema = typeof DirectVolumeBaseSchema
 export type DirectVolumeBaseValues = Values<DirectVolumeBaseSchema>
 
-function getInternalValues(ctx: Context, id: number, version: '100es' | '300es'): InternalValues {
+function getInternalValues(ctx: Context, id: number): InternalValues {
     return {
-        dWebGL2: ValueCell.create(ctx.isWebGL2),
-        dGlslVersion: ValueCell.create(version),
         uObjectId: ValueCell.create(id)
     }
 }
 
-function DirectVolumeRenderable<T extends DirectVolumeBaseValues, S extends DirectVolumeBaseSchema>(ctx: Context, id: number, values: T, state: RenderableState, schema: S, version: '100es' | '300es'): Renderable<T> {
+function DirectVolumeRenderable<T extends DirectVolumeBaseValues, S extends DirectVolumeBaseSchema>(ctx: Context, id: number, values: T, state: RenderableState, schema: S): Renderable<T> {
     const fullSchema = Object.assign({}, GlobalUniformSchema, InternalSchema, schema)
-    const internalValues = getInternalValues(ctx, id, version)
+    const internalValues = getInternalValues(ctx, id)
     const fullValues = Object.assign({}, values, internalValues)
     const shaderCode = DirectVolumeShaderCode
     const renderItem = createRenderItem(ctx, 'triangles', shaderCode, fullSchema, fullValues)
@@ -61,13 +59,13 @@ export const DirectVolume2dSchema = {
     ...DirectVolumeBaseSchema,
     dGridTexType: DefineSpec('string', ['2d']),
     uGridTexDim: UniformSpec('v2'),
-    tGridTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'),
+    tGridTex: TextureSpec('texture2d', 'rgba', 'ubyte', 'linear'),
 }
 export type DirectVolume2dSchema = typeof DirectVolume2dSchema
 export type DirectVolume2dValues = Values<DirectVolume2dSchema>
 
 export function DirectVolume2dRenderable(ctx: Context, id: number, values: DirectVolume2dValues, state: RenderableState): Renderable<DirectVolume2dValues> {
-    return DirectVolumeRenderable(ctx, id, values, state, DirectVolume2dSchema, '100es')
+    return DirectVolumeRenderable(ctx, id, values, state, DirectVolume2dSchema)
 }
 
 // via 3d texture
@@ -75,11 +73,11 @@ export function DirectVolume2dRenderable(ctx: Context, id: number, values: Direc
 export const DirectVolume3dSchema = {
     ...DirectVolumeBaseSchema,
     dGridTexType: DefineSpec('string', ['3d']),
-    tGridTex: TextureSpec('volume-uint8', 'rgba', 'ubyte', 'linear'),
+    tGridTex: TextureSpec('texture3d', 'rgba', 'ubyte', 'linear'),
 }
 export type DirectVolume3dSchema = typeof DirectVolume3dSchema
 export type DirectVolume3dValues = Values<DirectVolume3dSchema>
 
 export function DirectVolume3dRenderable(ctx: Context, id: number, values: DirectVolume3dValues, state: RenderableState): Renderable<DirectVolume3dValues> {
-    return DirectVolumeRenderable(ctx, id, values, state, DirectVolume3dSchema, '300es')
+    return DirectVolumeRenderable(ctx, id, values, state, DirectVolume3dSchema)
 }

+ 2 - 2
src/mol-gl/renderable/gaussian-density.ts

@@ -11,8 +11,6 @@ import { AttributeSpec, Values, UniformSpec, ValueSpec, DefineSpec } from './sch
 import { GaussianDensityShaderCode } from '../shader-code';
 
 export const GaussianDensitySchema = {
-    dWebGL2: DefineSpec('boolean'),
-
     drawCount: ValueSpec('number'),
     instanceCount: ValueSpec('number'),
 
@@ -27,6 +25,8 @@ export const GaussianDensitySchema = {
     uBboxSize: UniformSpec('v3'),
     uGridDim: UniformSpec('v3'),
     uAlpha: UniformSpec('f'),
+
+    dDrawBuffers: DefineSpec('number'),
 }
 export type GaussianDensitySchema = typeof GaussianDensitySchema
 export type GaussianDensityValues = Values<GaussianDensitySchema>

+ 0 - 2
src/mol-gl/renderable/lines.ts

@@ -28,8 +28,6 @@ export type LinesValues = Values<LinesSchema>
 export function LinesRenderable(ctx: Context, id: number, values: LinesValues, state: RenderableState): Renderable<LinesValues> {
     const schema = { ...GlobalUniformSchema, ...InternalSchema, ...LinesSchema }
     const internalValues: InternalValues = {
-        dWebGL2: ValueCell.create(ctx.isWebGL2),
-        dGlslVersion: ValueCell.create('100es'),
         uObjectId: ValueCell.create(id)
     }
     const shaderCode = LinesShaderCode

+ 0 - 2
src/mol-gl/renderable/mesh.ts

@@ -26,8 +26,6 @@ export type MeshValues = Values<MeshSchema>
 export function MeshRenderable(ctx: Context, id: number, values: MeshValues, state: RenderableState): Renderable<MeshValues> {
     const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema }
     const internalValues: InternalValues = {
-        dWebGL2: ValueCell.create(ctx.isWebGL2),
-        dGlslVersion: ValueCell.create('100es'),
         uObjectId: ValueCell.create(id)
     }
     const shaderCode = MeshShaderCode

+ 0 - 2
src/mol-gl/renderable/points.ts

@@ -25,8 +25,6 @@ export type PointsValues = Values<PointsSchema>
 export function PointsRenderable(ctx: Context, id: number, values: PointsValues, state: RenderableState): Renderable<PointsValues> {
     const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointsSchema }
     const internalValues: InternalValues = {
-        dWebGL2: ValueCell.create(ctx.isWebGL2),
-        dGlslVersion: ValueCell.create('100es'),
         uObjectId: ValueCell.create(id)
     }
     const shaderCode = PointsShaderCode

+ 4 - 4
src/mol-gl/renderable/schema.ts

@@ -10,7 +10,7 @@ import { UniformKind, UniformValues } from '../webgl/uniform';
 import { DefineKind, DefineValues } from '../shader-code';
 import { Vec2, Vec3, Vec4, Mat3, Mat4 } from 'mol-math/linear-algebra';
 import { TextureImage, TextureVolume } from './util';
-import { TextureValues, TextureType, TextureFormat, TextureFilter, TextureKind } from '../webgl/texture';
+import { TextureValues, TextureType, TextureFormat, TextureFilter, TextureKind, Texture } from '../webgl/texture';
 
 export type ValueKindType = {
     'number': number
@@ -29,7 +29,7 @@ export type KindValue = {
     'v4': Vec4
     'm3': Mat3
     'm4': Mat4
-    't2': number
+    't': number
 
     'uint8': Uint8Array
     'int8': Int8Array
@@ -43,6 +43,8 @@ export type KindValue = {
     'image-float32': TextureImage<Float32Array>
     'volume-uint8': TextureVolume<Uint8Array>
     'volume-float32': TextureVolume<Float32Array>
+    'texture2d': Texture
+    'texture3d': Texture
 
     'number': number
     'string': string
@@ -148,8 +150,6 @@ export type GlobalUniformSchema = typeof GlobalUniformSchema
 export type GlobalUniformValues = { [k in keyof GlobalUniformSchema]: ValueCell<any> }
 
 export const InternalSchema = {
-    dWebGL2: DefineSpec('boolean'),
-    dGlslVersion: DefineSpec('string', ['100es', '300es']),
     uObjectId: UniformSpec('i'),
 }
 export type InternalSchema = typeof InternalSchema

+ 11 - 7
src/mol-gl/shader-code.ts

@@ -6,6 +6,7 @@
 
 import { ValueCell } from 'mol-util';
 import { idFactory } from 'mol-util/id-factory';
+import { Context } from './webgl/context';
 
 export type DefineKind = 'boolean' | 'string' | 'number'
 export type DefineType = boolean | string
@@ -58,13 +59,13 @@ function getDefinesCode (defines: ShaderDefines) {
     for (const name in defines) {
         const define = defines[name]
         const v = define.ref.value
-        if (v) {
+        if (v !== undefined) {
             if (typeof v === 'string') {
                 lines.push(`#define ${name}_${v}`)
             } else if (typeof v === 'number') {
                 lines.push(`#define ${name} ${v}`)
             } else if (typeof v === 'boolean') {
-                lines.push(`#define ${name}`)
+                if (v) lines.push(`#define ${name}`)
             } else {
                 throw new Error('unknown define type')
             }
@@ -73,6 +74,9 @@ function getDefinesCode (defines: ShaderDefines) {
     return lines.join('\n') + '\n'
 }
 
+const glsl100FragPrefix = `#extension GL_OES_standard_derivatives : enable
+`
+
 const glsl300VertPrefix = `#version 300 es
 #define attribute in
 #define varying out
@@ -81,17 +85,17 @@ const glsl300VertPrefix = `#version 300 es
 
 const glsl300FragPrefix = `#version 300 es
 #define varying in
-out highp vec4 out_FragColor;
+layout(location = 0) out highp vec4 out_FragColor;
 #define gl_FragColor out_FragColor
 #define gl_FragDepthEXT gl_FragDepth
 #define texture2D texture
 `
 
-export function addShaderDefines(defines: ShaderDefines, shaders: ShaderCode): ShaderCode {
-    const isGlsl300es = defines.dGlslVersion && defines.dGlslVersion.ref.value === '300es'
+export function addShaderDefines(ctx: Context, defines: ShaderDefines, shaders: ShaderCode): ShaderCode {
+    const { isWebGL2 } = ctx
     const header = getDefinesCode(defines)
-    const vertPrefix = isGlsl300es ? glsl300VertPrefix : ''
-    const fragPrefix = isGlsl300es ? glsl300FragPrefix : ''
+    const vertPrefix = isWebGL2 ? glsl300VertPrefix : ''
+    const fragPrefix = isWebGL2 ? glsl300FragPrefix : glsl100FragPrefix
     return {
         id: shaderCodeId(),
         vert: `${vertPrefix}${header}${shaders.vert}`,

+ 1 - 1
src/mol-gl/shader/direct-volume.frag

@@ -69,7 +69,7 @@ const vec3 color = vec3(0.45, 0.55, 0.8);
 vec4 raymarch(vec3 startLoc, vec3 step, vec3 viewDir) {
     vec3 scaleVol = vec3(1.0) / uGridDim;
     vec3 pos = startLoc + scaleVol * 0.5;
-    float prevValue = -127.0;
+    float prevValue = -1.0;
     float value = 0.0;
     vec4 src = vec4(0.0);
     vec4 dst = vec4(0.0);

+ 36 - 12
src/mol-gl/shader/gaussian-density.frag

@@ -5,7 +5,7 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-precision mediump float;
+precision highp float;
 
 varying vec3 position;
 varying float radius;
@@ -19,16 +19,40 @@ uniform float uCurrentX;
 uniform float uCurrentY;
 uniform float uAlpha;
 
+#if dDrawBuffers >= 4
+    layout(location = 1) out vec4 out1;
+    layout(location = 2) out vec4 out2;
+    layout(location = 3) out vec4 out3;
+#endif
+#if dDrawBuffers >= 8
+    layout(location = 4) out vec4 out4;
+    layout(location = 5) out vec4 out5;
+    layout(location = 6) out vec4 out6;
+    layout(location = 7) out vec4 out7;
+#endif
+
+float calcDensity(float x, float y, float z, float radiusSq) {
+    vec3 fragPos = vec3(x, y, z) / uGridDim;
+    float dist = distance(fragPos * uBboxSize, position * uBboxSize);
+    float density = exp(-uAlpha * ((dist * dist) / radiusSq));
+    return density;
+}
+
+const vec3 color = vec3(1.0, 1.0, 1.0);
+
 void main() {
-    vec3 tmpVec = gl_FragCoord.xyz;
-    tmpVec.x = tmpVec.x - uCurrentX;
-    tmpVec.y = tmpVec.y - uCurrentY;
-    vec3 fragPos = vec3(
-        (tmpVec.x - 0.5) / uGridDim.x,
-        (tmpVec.y - 0.5) / uGridDim.y,
-        (uCurrentSlice) / uGridDim.z
-    );
-    float dist = length(fragPos * uBboxSize - position * uBboxSize);
-    float density = exp(-uAlpha * ((dist * dist) / (radius * radius)));
-    gl_FragColor = vec4(1, 1, 1, density);
+    float radiusSq = radius * radius;
+    vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
+    gl_FragColor = vec4(color, calcDensity(v.x, v.y, uCurrentSlice, radiusSq));
+    #if dDrawBuffers >= 4
+        out1 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 1.0, radiusSq));
+        out2 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 2.0, radiusSq));
+        out3 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 3.0, radiusSq));
+    #endif
+    #if dDrawBuffers >= 8
+        out4 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 4.0, radiusSq));
+        out5 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 5.0, radiusSq));
+        out6 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 6.0, radiusSq));
+        out7 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 7.0, radiusSq));
+    #endif
 }

+ 1 - 1
src/mol-gl/shader/gaussian-density.vert

@@ -5,7 +5,7 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-precision mediump float;
+precision highp float;
 
 attribute vec3 aPosition;
 attribute float aRadius;

+ 0 - 4
src/mol-gl/shader/mesh.frag

@@ -4,10 +4,6 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-#ifdef dFlatShaded
-    #extension GL_OES_standard_derivatives : enable
-#endif
-
 precision highp float;
 precision highp int;
 

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

@@ -51,6 +51,35 @@ function unbindFramebuffer(gl: GLRenderingContext) {
     gl.bindFramebuffer(gl.FRAMEBUFFER, null)
 }
 
+const tmpPixel = new Uint8Array(1 * 4);
+async function waitForGpuCommandsComplete(gl: GLRenderingContext) {
+    if (isWebGL2(gl)) {
+        const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
+        if (sync) {
+            // TODO too slow in Firefox
+            // await new Promise(resolve => {
+            //     const check = async () => {
+            //         if (gl.getSyncParameter(sync, gl.SYNC_STATUS) === gl.SIGNALED) {
+            //             gl.deleteSync(sync)
+            //             resolve();
+            //         } else {
+            //             setTimeout(check, 50)
+            //         }
+            //     };
+            //     setTimeout(check, 10)
+            // })
+            gl.deleteSync(sync)
+            gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+        } else {
+            console.warn('unable to get webgl sync object')
+            gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+        }
+    } else {
+        console.info('webgl sync object not supported in webgl 1')
+        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+    }
+}
+
 export function createImageData(buffer: ArrayLike<number>, width: number, height: number) {
     const w = width * 4
     const h = height
@@ -66,6 +95,8 @@ export function createImageData(buffer: ArrayLike<number>, width: number, height
     return new ImageData(data, width, height);
 }
 
+//
+
 type Extensions = {
     instancedArrays: COMPAT_instanced_arrays
     standardDerivatives: COMPAT_standard_derivatives
@@ -96,14 +127,14 @@ export interface Context {
     instancedDrawCount: number
 
     readonly maxTextureSize: number
+    readonly maxDrawBuffers: number
 
     unbindFramebuffer: () => void
     readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
+    waitForGpuCommandsComplete: () => Promise<void>
     destroy: () => void
 }
 
-
-
 export function createContext(gl: GLRenderingContext): Context {
     const instancedArrays = getInstancedArrays(gl)
     if (instancedArrays === null) {
@@ -134,7 +165,8 @@ export function createContext(gl: GLRenderingContext): Context {
     const programCache = createProgramCache()
 
     const parameters = {
-        maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE)
+        maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE),
+        maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) : 0,
     }
 
     return {
@@ -164,6 +196,7 @@ export function createContext(gl: GLRenderingContext): Context {
         instancedDrawCount: 0,
 
         get maxTextureSize () { return parameters.maxTextureSize },
+        get maxDrawBuffers () { return parameters.maxDrawBuffers },
 
         unbindFramebuffer: () => unbindFramebuffer(gl),
         readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
@@ -175,6 +208,7 @@ export function createContext(gl: GLRenderingContext): Context {
             //     console.error('Reading pixels failed. Framebuffer not complete.')
             // }
         },
+        waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl),
 
         destroy: () => {
             unbindResources(gl)

+ 7 - 2
src/mol-gl/webgl/program.ts

@@ -61,7 +61,7 @@ export function createProgram(ctx: Context, props: ProgramProps): Program {
         throw new Error('Could not create WebGL program')
     }
 
-    const shaderCode = addShaderDefines(defineValues, _shaderCode)
+    const shaderCode = addShaderDefines(ctx, defineValues, _shaderCode)
     const vertShaderRef = shaderCache.get(ctx, { type: 'vert', source: shaderCode.vert })
     const fragShaderRef = shaderCache.get(ctx, { type: 'frag', source: shaderCode.frag })
 
@@ -114,13 +114,18 @@ export function createProgram(ctx: Context, props: ProgramProps): Program {
 
 export type ProgramCache = ReferenceCache<Program, ProgramProps, Context>
 
+function defineValueHash(v: boolean | number | string): number {
+    return typeof v === 'boolean' ? (v ? 1 : 0) :
+        typeof v === 'number' ? v : hashString(v)
+}
+
 export function createProgramCache(): ProgramCache {
     return createReferenceCache(
         (props: ProgramProps) => {
             const array = [ props.shaderCode.id ]
             Object.keys(props.defineValues).forEach(k => {
                 const v = props.defineValues[k].ref.value
-                array.push(hashString(k), typeof v === 'boolean' ? v ? 1 : 0 : hashString(v))
+                array.push(hashString(k), defineValueHash(v))
             })
             return hashFnv32a(array).toString()
         },

+ 14 - 5
src/mol-gl/webgl/render-item.ts

@@ -14,6 +14,7 @@ import { idFactory } from 'mol-util/id-factory';
 import { deleteVertexArray, createVertexArray } from './vertex-array';
 import { ValueCell } from 'mol-util';
 import { ReferenceItem } from 'mol-util/reference-cache';
+import { TextureImage, TextureVolume } from 'mol-gl/renderable/util';
 
 const getNextRenderItemId = idFactory()
 
@@ -226,10 +227,13 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
             Object.keys(textureValues).forEach(k => {
                 const value = textureValues[k]
                 if (value.ref.version !== versions[k]) {
-                    // console.log('texture version changed, uploading image', k)
-                    textures[k].load(value.ref.value)
-                    versions[k] = value.ref.version
-                    valueChanges.textures = true
+                    // update of textures with kind 'texture2d' or 'texture3d' is done externally
+                    if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') {
+                        // console.log('texture version changed, uploading image', k)
+                        textures[k].load(value.ref.value as TextureImage<any> | TextureVolume<any>)
+                        versions[k] = value.ref.version
+                        valueChanges.textures = true
+                    }
                 }
             })
 
@@ -241,7 +245,12 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
                     programs[k].free()
                     deleteVertexArray(ctx, vertexArrays[k])
                 })
-                Object.keys(textures).forEach(k => textures[k].destroy())
+                Object.keys(textures).forEach(k => {
+                    // lifetime of textures with kind 'texture2d' or 'texture3d' is defined externally
+                    if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') {
+                        textures[k].destroy()
+                    }
+                })
                 Object.keys(attributeBuffers).forEach(k => attributeBuffers[k].destroy())
                 if (elementsBuffer) elementsBuffer.destroy()
                 destroyed = true

+ 1 - 1
src/mol-gl/webgl/render-target.ts

@@ -69,7 +69,7 @@ export function createRenderTarget (ctx: Context, _width: number, _height: numbe
 
         bind: () => {
             framebuffer.bind()
-            gl.viewport(0, 0, _width, _height);
+            gl.viewport(0, 0, _width, _height)
         },
         setSize: (width: number, height: number) => {
             _width = width

+ 91 - 20
src/mol-gl/webgl/texture.ts

@@ -10,6 +10,7 @@ import { ValueCell } from 'mol-util';
 import { RenderableSchema } from '../renderable/schema';
 import { idFactory } from 'mol-util/id-factory';
 import { Framebuffer } from './framebuffer';
+import { isWebGL2 } from './compat';
 
 const getNextTextureId = idFactory()
 
@@ -18,11 +19,15 @@ export type TextureKindValue = {
     'image-float32': TextureImage<Float32Array>
     'volume-uint8': TextureVolume<Uint8Array>
     'volume-float32': TextureVolume<Float32Array>
+    'texture2d': Texture
+    'texture3d': Texture
 }
+export type TextureValueType = Helpers.ValueOf<TextureKindValue>
 export type TextureKind = keyof TextureKindValue
 export type TextureType = 'ubyte' | 'float'
 export type TextureFormat = 'alpha' | 'rgb' | 'rgba'
-export type TextureAttachment = 'depth' | 'stencil' | 'color0'
+/** Numbers are shortcuts for color attachment */
+export type TextureAttachment = 'depth' | 'stencil' | 'color0' | 'color1' | 'color2' | 'color3' | 'color4' | 'color5' | 'color6' | 'color7' | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7
 export type TextureFilter = 'nearest' | 'linear'
 
 export function getTarget(ctx: Context, kind: TextureKind): number {
@@ -30,9 +35,16 @@ export function getTarget(ctx: Context, kind: TextureKind): number {
     switch (kind) {
         case 'image-uint8': return gl.TEXTURE_2D
         case 'image-float32': return gl.TEXTURE_2D
-        case 'volume-uint8': return (gl as WebGL2RenderingContext).TEXTURE_3D
-        case 'volume-float32': return (gl as WebGL2RenderingContext).TEXTURE_3D
+        case 'texture2d': return gl.TEXTURE_2D
     }
+    if (isWebGL2(gl)) {
+        switch (kind) {
+            case 'volume-uint8': return gl.TEXTURE_3D
+            case 'volume-float32': return gl.TEXTURE_3D
+            case 'texture3d': return gl.TEXTURE_3D
+        }
+    }
+    throw new Error('unknown texture kind')
 }
 
 export function getFormat(ctx: Context, format: TextureFormat): number {
@@ -89,8 +101,20 @@ export function getAttachment(ctx: Context, attachment: TextureAttachment): numb
     switch (attachment) {
         case 'depth': return gl.DEPTH_ATTACHMENT
         case 'stencil': return gl.STENCIL_ATTACHMENT
-        case 'color0': return gl.COLOR_ATTACHMENT0
+        case 'color0': case 0: return gl.COLOR_ATTACHMENT0
+    }
+    if (isWebGL2(gl)) {
+        switch (attachment) {
+            case 'color1': case 1: return gl.COLOR_ATTACHMENT1
+            case 'color2': case 2: return gl.COLOR_ATTACHMENT2
+            case 'color3': case 3: return gl.COLOR_ATTACHMENT3
+            case 'color4': case 4: return gl.COLOR_ATTACHMENT4
+            case 'color5': case 5: return gl.COLOR_ATTACHMENT5
+            case 'color6': case 6: return gl.COLOR_ATTACHMENT6
+            case 'color7': case 7: return gl.COLOR_ATTACHMENT7
+        }
     }
+    throw new Error('unknown texture attachment')
 }
 
 export interface Texture {
@@ -100,16 +124,23 @@ export interface Texture {
     readonly internalFormat: number
     readonly type: number
 
-    load: (image: TextureImage<any>) => void
+    readonly width: number
+    readonly height: number
+    readonly depth: number
+
+    define: (width: number, height: number, depth?: number) => void
+    load: (image: TextureImage<any> | TextureVolume<any>) => void
     bind: (id: TextureId) => void
     unbind: (id: TextureId) => void
-    attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
+    /** Use `layer` to attach a z-slice of a 3D texture */
+    attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => void
+    detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void
     destroy: () => void
 }
 
 export type TextureId = 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15
 
-export type TextureValues = { [k: string]: ValueCell<TextureImage<any>> }
+export type TextureValues = { [k: string]: ValueCell<TextureValueType> }
 export type Textures = { [k: string]: Texture }
 
 export function createTexture(ctx: Context, kind: TextureKind, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture {
@@ -126,6 +157,16 @@ export function createTexture(ctx: Context, kind: TextureKind, _format: TextureF
     const internalFormat = getInternalFormat(ctx, _format, _type)
     const type = getType(ctx, _type)
 
+    gl.bindTexture(target, texture)
+    gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter)
+    gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter)
+    // clamp-to-edge needed for non-power-of-two textures in webgl
+    gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
+    gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
+    gl.bindTexture(target, null)
+
+    let width = 0, height = 0, depth = 0
+
     let destroyed = false
     ctx.textureCount += 1
 
@@ -136,26 +177,40 @@ export function createTexture(ctx: Context, kind: TextureKind, _format: TextureF
         internalFormat,
         type,
 
+        get width () { return width },
+        get height () { return height },
+        get depth () { return depth },
+
+        define: (_width: number, _height: number, _depth?: number) => {
+            width = _width, height = _height, depth = _depth || 0
+            gl.bindTexture(target, texture)
+            if (target === gl.TEXTURE_2D) {
+                // TODO remove cast when webgl2 types are fixed
+                (gl as WebGLRenderingContext).texImage2D(target, 0, internalFormat, width, height, 0, format, type, null)
+            } else if (target === (gl as WebGL2RenderingContext).TEXTURE_3D && depth !== undefined) {
+                (gl as WebGL2RenderingContext).texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, null)
+            } else {
+                throw new Error('unknown texture target')
+            }
+        },
         load: (data: TextureImage<any> | TextureVolume<any>) => {
             gl.bindTexture(target, texture)
             // unpack alignment of 1 since we use textures only for data
             gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
             gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE);
+            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);
             if (target === gl.TEXTURE_2D) {
-                const { array, width, height } = data as TextureImage<any>;
+                const { array, width: _width, height: _height } = data as TextureImage<any>
+                width = _width, height = _height;
                 // TODO remove cast when webgl2 types are fixed
                 (gl as WebGLRenderingContext).texImage2D(target, 0, internalFormat, width, height, 0, format, type, array)
             } else if (target === (gl as WebGL2RenderingContext).TEXTURE_3D) {
-                const { array, width, height, depth } = data as TextureVolume<any>;
+                const { array, width: _width, height: _height, depth: _depth } = data as TextureVolume<any>
+                width = _width, height = _height, depth = _depth;
                 (gl as WebGL2RenderingContext).texImage3D(target, 0, internalFormat, width, height, depth, 0, format, type, array)
             } else {
                 throw new Error('unknown texture target')
             }
-            gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, filter)
-            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, filter)
-            // clamp-to-edge needed for non-power-of-two textures
-            gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
-            gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
             gl.bindTexture(target, null)
         },
         bind: (id: TextureId) => {
@@ -166,10 +221,22 @@ export function createTexture(ctx: Context, kind: TextureKind, _format: TextureF
             gl.activeTexture(gl.TEXTURE0 + id)
             gl.bindTexture(target, null)
         },
-        attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
-            if (target !== gl.TEXTURE_2D) throw new Error('framebuffer texture must be 2d')
+        attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment, layer?: number) => {
             framebuffer.bind()
-            gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, texture, 0)
+            if (target === (gl as WebGL2RenderingContext).TEXTURE_3D) {
+                if (layer === undefined) throw new Error('need `layer` to attach 3D texture');
+                (gl as WebGL2RenderingContext).framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(ctx, attachment), texture, 0, layer)
+            } else {
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, texture, 0)
+            }
+        },
+        detachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => {
+            framebuffer.bind()
+            if (target === (gl as WebGL2RenderingContext).TEXTURE_3D) {
+                (gl as WebGL2RenderingContext).framebufferTextureLayer(gl.FRAMEBUFFER, getAttachment(ctx, attachment), null, 0, 0)
+            } else {
+                gl.framebufferTexture2D(gl.FRAMEBUFFER, getAttachment(ctx, attachment), gl.TEXTURE_2D, null, 0)
+            }
         },
         destroy: () => {
             if (destroyed) return
@@ -185,9 +252,13 @@ export function createTextures(ctx: Context, schema: RenderableSchema, values: T
     Object.keys(schema).forEach((k, i) => {
         const spec = schema[k]
         if (spec.type === 'texture') {
-            const texture = createTexture(ctx, spec.kind, spec.format, spec.dataType, spec.filter)
-            texture.load(values[k].ref.value)
-            textures[k] = texture
+            if (spec.kind === 'texture2d' || spec.kind === 'texture3d') {
+                textures[k] = values[k].ref.value as Texture
+            } else {
+                const texture = createTexture(ctx, spec.kind, spec.format, spec.dataType, spec.filter)
+                texture.load(values[k].ref.value as TextureImage<any> | TextureVolume<any>)
+                textures[k] = texture
+            }
         }
     })
     return textures

+ 3 - 3
src/mol-gl/webgl/uniform.ts

@@ -17,7 +17,7 @@ export type UniformKindValue = {
     'v4': Vec4
     'm3': Mat3
     'm4': Mat4
-    't2': number
+    't': number
 }
 export type UniformKind = keyof UniformKindValue
 export type UniformType = number | Vec2 | Vec3 | Vec4 | Mat3 | Mat4
@@ -37,7 +37,7 @@ function createUniformSetter(ctx: Context, program: WebGLProgram, name: string,
     }
     switch (kind) {
         case 'f': return (value: number) => gl.uniform1f(location, value)
-        case 'i': case 't2': return (value: number) => gl.uniform1i(location, value)
+        case 'i': case 't': return (value: number) => gl.uniform1i(location, value)
         case 'v2': return (value: Vec2) => (gl as WebGLRenderingContext).uniform2fv(location, value) // TODO remove cast when webgl2 types are fixed
         case 'v3': return (value: Vec3) => (gl as WebGLRenderingContext).uniform3fv(location, value)
         case 'v4': return (value: Vec4) => (gl as WebGLRenderingContext).uniform4fv(location, value)
@@ -78,7 +78,7 @@ export function getTextureUniformUpdaters(ctx: Context, program: WebGLProgram, s
     Object.keys(schema).forEach(k => {
         const spec = schema[k]
         if (spec.type === 'texture') {
-            updaters[k] = createUniformUpdater(ctx, program, k, 't2')
+            updaters[k] = createUniformUpdater(ctx, program, k, 't')
         }
     })
     return updaters

+ 7 - 4
src/mol-math/geometry/common.ts

@@ -7,8 +7,8 @@
 
 import { OrderedSet } from 'mol-data/int'
 import { Mat4, Tensor, Vec3 } from '../linear-algebra';
-import { RenderTarget } from 'mol-gl/webgl/render-target';
 import { Box3D } from '../geometry';
+import { Texture } from 'mol-gl/webgl/texture';
 
 export interface PositionData {
     x: ArrayLike<number>,
@@ -24,8 +24,11 @@ export type DensityData = {
     transform: Mat4,
     field: Tensor,
     idField: Tensor,
+}
 
-    renderTarget?: RenderTarget,
-    bbox?: Box3D,
-    gridDimension?: Vec3
+export type DensityTextureData = {
+    transform: Mat4,
+    texture: Texture,
+    bbox: Box3D,
+    gridDimension: Vec3
 }

+ 2 - 1
src/mol-math/geometry/gaussian-density.ts

@@ -9,6 +9,7 @@ import { Vec3 } from '../linear-algebra';
 import { RuntimeContext, Task } from 'mol-task';
 import { PositionData, DensityData } from './common';
 import { GaussianDensityCPU } from './gaussian-density/cpu';
+import { Context } from 'mol-gl/webgl/context';
 
 // import { GaussianDensityGPU } from './gaussian-density/gpu';
 const GaussianDensityGPU = typeof document !== 'undefined'
@@ -19,8 +20,8 @@ export const DefaultGaussianDensityProps = {
     resolution: 1,
     radiusOffset: 0,
     smoothness: 1.5,
-    readSlices: false,
     useGpu: true,
+    webgl: undefined as Context | undefined
 }
 export type GaussianDensityProps = typeof DefaultGaussianDensityProps
 

+ 280 - 134
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -6,20 +6,202 @@
  */
 
 import { RuntimeContext } from 'mol-task'
-import { PositionData, DensityData } from '../common'
+import { PositionData, DensityData, DensityTextureData } from '../common'
 import { Box3D } from '../../geometry'
 import { GaussianDensityProps, getDelta } from '../gaussian-density'
 import { OrderedSet } from 'mol-data/int'
 import { Vec3, Tensor, Mat4 } from '../../linear-algebra'
 import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density'
-import { ValueCell } from 'mol-util'
+import { ValueCell, defaults } from 'mol-util'
 import { RenderableState } from 'mol-gl/renderable'
 import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object'
-import { createRenderTarget } from 'mol-gl/webgl/render-target'
-import { Context, createContext } from 'mol-gl/webgl/context';
+import { Context, createContext, getGLContext } from 'mol-gl/webgl/context';
+import { createFramebuffer } from 'mol-gl/webgl/framebuffer';
+import { createTexture, Texture, TextureAttachment } from 'mol-gl/webgl/texture';
+import { GLRenderingContext } from 'mol-gl/webgl/compat';
 
-export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps): Promise<DensityData> {
-    const { resolution, radiusOffset, smoothness, readSlices } = props
+export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
+    const webgl = defaults(props.webgl, getWebGLContext())
+
+    const { transform, texture, gridDimension } = await GaussianDensityTexture(ctx, webgl, position, box, radius, props)
+
+    const field = webgl.maxDrawBuffers > 0 ?
+        fieldFromTexture3d(webgl, texture, gridDimension) :
+        fieldFromTexture2d(webgl, texture, gridDimension)
+
+    const idData = field.space.create()
+    const idField = Tensor.create(field.space, idData)
+
+    return { field, idField, transform }
+}
+
+export async function GaussianDensityTexture(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, oldTexture?: Texture): Promise<DensityTextureData> {
+    console.time(`GaussianDensityTexture, ${webgl.maxDrawBuffers > 0 ? 'multi' : 'single'}`)
+    const { texture, scale, bbox, dim } = webgl.maxDrawBuffers > 0 ?
+        await GaussianDensityMultiDrawBuffer(ctx, webgl, position, box, radius, props, oldTexture) :
+        await GaussianDensitySingleDrawBuffer(ctx, webgl, position, box, radius, props, oldTexture)
+    console.timeEnd(`GaussianDensityTexture, ${webgl.maxDrawBuffers > 0 ? 'multi' : 'single'}`)
+
+    const transform = Mat4.identity()
+    Mat4.fromScaling(transform, scale)
+    Mat4.setTranslation(transform, bbox.min)
+
+    return { transform, texture, bbox, gridDimension: dim }
+}
+
+//
+
+async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
+    const { smoothness } = props
+
+    const { drawCount, positions, radii, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
+    const [ dx, dy, dz ] = dim
+    const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, expandedBox, dim, smoothness)
+    const renderable = createRenderable(webgl, renderObject)
+
+    //
+
+    const maxTexSize = webgl.maxTextureSize
+    let fboTexDimX = 0
+    let fboTexDimY = dim[1]
+    let fboTexRows = 1
+    let fboTexCols = dim[0]
+    if (maxTexSize < dim[0] * dim[2]) {
+        fboTexCols =  Math.floor(maxTexSize / dim[0])
+        fboTexRows = Math.ceil(dim[2] / fboTexCols)
+        fboTexDimX = fboTexCols * dim[0]
+        fboTexDimY *= fboTexRows
+    } else {
+        fboTexDimX = dim[0] * dim[2]
+    }
+
+    //
+
+    const { gl } = webgl
+    const { uCurrentSlice, uCurrentX, uCurrentY } = renderObject.values
+
+    const framebuffer = createFramebuffer(webgl)
+    framebuffer.bind()
+
+    if (!texture) {
+        texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    }
+    texture.define(fboTexDimX, fboTexDimY)
+
+    const program = renderable.getProgram('draw')
+    program.use()
+    setRenderingDefaults(gl)
+    texture.attachFramebuffer(framebuffer, 0)
+
+    let currCol = 0
+    let currY = 0
+    let currX = 0
+    for (let i = 0; i < dz; ++i) {
+        if (currCol >= fboTexCols) {
+            currCol -= fboTexCols
+            currY += dy
+            currX = 0
+        }
+        gl.viewport(currX, currY, dx, dy)
+        ValueCell.update(uCurrentSlice, i)
+        ValueCell.update(uCurrentX, currX)
+        ValueCell.update(uCurrentY, currY)
+        renderable.render('draw')
+        ++currCol
+        currX += dx
+    }
+
+    framebuffer.destroy() // clean up
+
+    await ctx.update({ message: 'gpu gaussian density calculation' });
+    await webgl.waitForGpuCommandsComplete()
+
+    return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
+}
+
+async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps, texture?: Texture) {
+    const { smoothness } = props
+
+    const { drawCount, positions, radii, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
+    const [ dx, dy, dz ] = dim
+    const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, expandedBox, dim, smoothness)
+    const renderable = createRenderable(webgl, renderObject)
+    const drawBuffers = Math.min(8, webgl.maxDrawBuffers)
+
+    //
+
+    const gl = webgl.gl as WebGL2RenderingContext
+    const { uCurrentSlice } = renderObject.values
+
+    const framebuffer = createFramebuffer(webgl)
+    framebuffer.bind()
+
+    setDrawBuffers(gl, drawBuffers)
+    gl.viewport(0, 0, dx, dy)
+    setRenderingDefaults(gl)
+
+    if (!texture) {
+        texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
+    }
+    texture.define(dx, dy, dz)
+
+    // z-slices to be render with multi render targets
+    const dzMulti = Math.floor(dz / drawBuffers) * drawBuffers
+
+    // render multi target
+    const programMulti = renderable.getProgram('draw')
+    programMulti.use()
+    for (let i = 0; i < dzMulti; i += drawBuffers) {
+        ValueCell.update(uCurrentSlice, i)
+        for (let k = 0; k < drawBuffers; ++k) {
+            texture.attachFramebuffer(framebuffer, k as TextureAttachment, i + k)
+        }
+        renderable.render('draw');
+    }
+
+    // render single target
+    ValueCell.updateIfChanged(renderable.values.dDrawBuffers, 1)
+    renderable.update()
+    const programSingle = renderable.getProgram('draw')
+    programSingle.use()
+    for (let i = dzMulti; i < dz; ++i) {
+        ValueCell.update(uCurrentSlice, i)
+        texture.attachFramebuffer(framebuffer, 0, i)
+        renderable.render('draw')
+    }
+
+    // must detach framebuffer attachments before reading is possible
+    for (let k = 0; k < drawBuffers; ++k) {
+        texture.detachFramebuffer(framebuffer, k as TextureAttachment)
+    }
+
+    framebuffer.destroy() // clean up
+
+    await ctx.update({ message: 'gpu gaussian density calculation' });
+    await webgl.waitForGpuCommandsComplete()
+
+    return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
+}
+
+//
+
+let webglContext: Context
+function getWebGLContext() {
+    if (webglContext) return webglContext
+    const canvas = document.createElement('canvas')
+    const gl = getGLContext(canvas, {
+        alpha: true,
+        antialias: false,
+        depth: false,
+        preserveDrawingBuffer: true
+    })
+    if (!gl) throw new Error('Could not create a WebGL rendering context')
+    webglContext = createContext(gl)
+    return webglContext
+}
+
+async function prepareGaussianDensityData(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps) {
+    const { resolution, radiusOffset } = props
 
     const { indices, x, y, z } = position
     const n = OrderedSet.size(indices)
@@ -44,29 +226,24 @@ export async function GaussianDensityGPU(ctx: RuntimeContext, position: Position
         }
     }
 
+
     const pad = maxRadius * 2 + resolution
     const expandedBox = Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad));
     const extent = Vec3.sub(Vec3.zero(), expandedBox.max, expandedBox.min)
 
-    const delta = getDelta(Box3D.expand(Box3D.empty(), box, Vec3.create(pad, pad, pad)), resolution)
+    const delta = getDelta(expandedBox, resolution)
     const dim = Vec3.zero()
     Vec3.ceil(dim, Vec3.mul(dim, extent, delta))
-    console.log('grid dim', dim)
-
-    const _r2 = maxRadius * 2
-    const _radius2 = Vec3.create(_r2, _r2, _r2)
-    Vec3.mul(_radius2, _radius2, delta)
-    const updateChunk = Math.ceil(10000 / (_radius2[0] * _radius2[1] * _radius2[2]))
+    console.log('grid dim gpu', dim)
 
-    //
+    return { drawCount: n, positions, radii, delta, expandedBox, dim }
+}
 
-    // TODO do in OffscreenCanvas (https://www.chromestatus.com/feature/5681560598609920)?
-    const webgl = getWebGLContext()
+function getGaussianDensityRenderObject(webgl: Context, drawCount: number, positions: Float32Array, radii: Float32Array, box: Box3D, dimensions: Vec3, smoothness: number) {
+    const extent = Vec3.sub(Vec3.zero(), box.max, box.min)
 
     const values: GaussianDensityValues = {
-        dWebGL2: ValueCell.create(webgl.isWebGL2),
-
-        drawCount: ValueCell.create(n),
+        drawCount: ValueCell.create(drawCount),
         instanceCount: ValueCell.create(1),
 
         aRadius: ValueCell.create(radii),
@@ -75,11 +252,13 @@ export async function GaussianDensityGPU(ctx: RuntimeContext, position: Position
         uCurrentSlice: ValueCell.create(0),
         uCurrentX: ValueCell.create(0),
         uCurrentY: ValueCell.create(0),
-        uBboxMin: ValueCell.create(expandedBox.min),
-        uBboxMax: ValueCell.create(expandedBox.max),
+        uBboxMin: ValueCell.create(box.min),
+        uBboxMax: ValueCell.create(box.max),
         uBboxSize: ValueCell.create(extent),
-        uGridDim: ValueCell.create(dim),
+        uGridDim: ValueCell.create(dimensions),
         uAlpha: ValueCell.create(smoothness),
+
+        dDrawBuffers: ValueCell.create(Math.min(8, webgl.maxDrawBuffers)),
     }
     const state: RenderableState = {
         visible: true,
@@ -87,140 +266,107 @@ export async function GaussianDensityGPU(ctx: RuntimeContext, position: Position
     }
 
     const renderObject = createGaussianDensityRenderObject(values, state)
-    const renderable = createRenderable(webgl, renderObject)
 
-    //
+    return renderObject
+}
 
-    // TODO fallback to lower resolution when texture size is not large enough
-    const maxTexSize = webgl.maxTextureSize
-    let fboTexDimX = 0
-    let fboTexDimY = dim[1]
-    let fboTexRows = 1
-    let fboTexCols = dim[0]
-    if (maxTexSize < dim[0] * dim[2]) {
-        fboTexCols =  Math.floor(maxTexSize / dim[0])
-        fboTexRows = Math.ceil(dim[2] / fboTexCols)
-        fboTexDimX = fboTexCols * dim[0]
-        fboTexDimY *= fboTexRows
-    } else {
-        fboTexDimX = dim[0] * dim[2]
-    }
+function setRenderingDefaults(gl: GLRenderingContext) {
+    gl.disable(gl.CULL_FACE)
+    gl.frontFace(gl.CCW)
+    gl.cullFace(gl.BACK)
 
-    console.log('dim', dim, 'cols', fboTexCols, 'rows', fboTexRows)
+    gl.blendFunc(gl.ONE, gl.ONE)
+    gl.blendEquation(gl.FUNC_ADD)
+    gl.enable(gl.BLEND)
+}
 
-    //
+function setDrawBuffers(gl: WebGL2RenderingContext, drawBuffers: number) {
+    if (drawBuffers === 1) {
+        gl.drawBuffers([
+            gl.COLOR_ATTACHMENT0,
+        ]);
+    } else if (drawBuffers === 4) {
+        gl.drawBuffers([
+            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
+        ]);
+    } else if (drawBuffers === 8) {
+        gl.drawBuffers([
+            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
+            gl.COLOR_ATTACHMENT4, gl.COLOR_ATTACHMENT5, gl.COLOR_ATTACHMENT6, gl.COLOR_ATTACHMENT7,
+        ]);
+    }
+}
+
+function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
+    console.time('fieldFromTexture2d')
+    const { gl } = ctx
+    const [ dx, dy, dz ] = dim
+    const { width, height } = texture
+    const fboTexCols = Math.floor(width / dx)
 
     const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
     const data = space.create()
     const field = Tensor.create(space, data)
 
-    const idData = space.create()
-    const idField = Tensor.create(space, idData)
-
-    //
+    const image = new Uint8Array(width * height * 4)
 
-    const { gl } = webgl
+    const framebuffer = createFramebuffer(ctx)
+    framebuffer.bind()
 
-    const program = renderable.getProgram('draw')
-    const renderTarget = createRenderTarget(webgl, fboTexDimX, fboTexDimY)
+    texture.attachFramebuffer(framebuffer, 0)
+    gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image)
 
-    program.use()
-    renderTarget.bind()
-
-    gl.disable(gl.CULL_FACE)
-    gl.frontFace(gl.CCW)
-    gl.cullFace(gl.BACK)
+    let idx = 0
+    let tmpCol = 0
+    let tmpRow = 0
+    for (let iz = 0; iz < dz; ++iz) {
+        if (tmpCol >= fboTexCols ) {
+            tmpCol = 0
+            tmpRow += dy
+        }
+        for (let iy = 0; iy < dy; ++iy) {
+            for (let ix = 0; ix < dx; ++ix) {
+                data[idx] = image[4 * (tmpCol * dx + (iy + tmpRow) * width + ix) + 3] / 255
+                idx++
+            }
+        }
+        tmpCol++
+    }
 
-    gl.depthMask(true)
-    gl.clearColor(0, 0, 0, 0)
-    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT)
-    gl.depthMask(false)
+    framebuffer.destroy()
+    console.timeEnd('fieldFromTexture2d')
 
-    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA)
-    gl.blendEquation(gl.FUNC_ADD)
-    gl.enable(gl.BLEND)
+    return field
+}
 
-    const slice = new Uint8Array(dim[0] * dim[1] * 4)
+function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) {
+    console.time('fieldFromTexture3d')
+    const { gl } = ctx
+    const { width, height, depth } = texture
 
-    console.time('gpu gaussian density slices')
-    let currCol = 0
-    let currY = 0
-    let currX = 0
-    let j = 0
-    for (let i = 0; i < dim[2]; ++i) {
-        if (currCol >= fboTexCols) {
-            currCol -= fboTexCols
-            currY += dim[1]
-            currX = 0
-        }
-        gl.viewport(currX, currY, dim[0], dim[1])
-        ValueCell.update(values.uCurrentSlice, i)
-        ValueCell.update(values.uCurrentX, currX)
-        ValueCell.update(values.uCurrentY, currY)
-        renderable.render('draw')
-        if (readSlices) {
-            renderTarget.readBuffer(currX, currY, dim[0], dim[1], slice)
-            for (let iy = 0; iy < dim[1]; ++iy) {
-                for (let ix = 0; ix < dim[0]; ++ix) {
-                    data[j] = slice[4 * (iy * dim[0] + ix)] / 255
-                    ++j
-                }
-            }
-        }
-        ++currCol
-        currX += dim[0]
+    const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
+    const data = space.create()
+    const field = Tensor.create(space, data)
 
-        if (i % updateChunk === 0 && ctx.shouldUpdate) {
-            await ctx.update({ message: 'filling density grid', current: i, max: n })
-        }
-    }
-    console.timeEnd('gpu gaussian density slices')
+    const slice = new Uint8Array(width * height * 4)
 
-    //
+    const framebuffer = createFramebuffer(ctx)
+    framebuffer.bind()
 
-    if (!readSlices) {
-        console.time('gpu gaussian density full')
-        renderTarget.getBuffer()
-        const { array } = renderTarget.image
-        let idx = 0
-        let tmpCol = 0
-        let tmpRow = 0
-        for (let iz = 0; iz < dim[2]; ++iz) {
-            if (tmpCol >= fboTexCols ) {
-                tmpCol = 0
-                tmpRow += dim[1]
-            }
-            for (let iy = 0; iy < dim[1]; ++iy) {
-                for (let ix = 0; ix < dim[0]; ++ix) {
-                    data[idx] = array[4 * (tmpCol * dim[0] + (iy + tmpRow) * fboTexDimX + ix)] / 255
-                    idx++
-                }
+    let j = 0
+    for (let i = 0; i < depth; ++i) {
+        texture.attachFramebuffer(framebuffer, 0, i)
+        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice)
+        for (let iy = 0; iy < height; ++iy) {
+            for (let ix = 0; ix < width; ++ix) {
+                data[j] = slice[4 * (iy * width + ix) + 3] / 255
+                ++j
             }
-            tmpCol++
         }
-        console.timeEnd('gpu gaussian density full')
     }
 
-    //
+    framebuffer.destroy()
+    console.timeEnd('fieldFromTexture3d')
 
-    const transform = Mat4.identity()
-    Mat4.fromScaling(transform, Vec3.inverse(Vec3.zero(), delta))
-    Mat4.setTranslation(transform, expandedBox.min)
-
-    return { field, idField, transform, renderTarget, bbox: expandedBox, gridDimension: dim }
-}
-
-let webglContext: Context
-function getWebGLContext() {
-    if (webglContext) return webglContext
-    const canvas = document.createElement('canvas')
-    const gl = canvas.getContext('webgl', {
-        alpha: true,
-        antialias: false,
-        depth: false,
-        preserveDrawingBuffer: true
-    })
-    if (!gl) throw new Error('Could not create a WebGL rendering context')
-    webglContext = createContext(gl)
-    return webglContext
+    return field
 }

+ 20 - 3
src/mol-model/structure/structure/unit/gaussian-density.ts

@@ -9,15 +9,18 @@ import { SizeTheme } from 'mol-view/theme/size';
 import { GaussianDensity } from 'mol-math/geometry/gaussian-density';
 import { Task, RuntimeContext } from 'mol-task';
 import { DensityData } from 'mol-math/geometry';
-import { NumberParam, paramDefaultValues, BooleanParam } from 'mol-view/parameter';
+import { NumberParam, paramDefaultValues, BooleanParam, ValueParam } from 'mol-view/parameter';
+import { Context } from 'mol-gl/webgl/context';
+import { GaussianDensityTexture } from 'mol-math/geometry/gaussian-density/gpu';
+import { Texture } from 'mol-gl/webgl/texture';
 
 export const GaussianDensityParams = {
     resolution: NumberParam('Resolution', '', 1, 0.1, 10, 0.1),
     radiusOffset: NumberParam('Radius Offset', '', 0, 0, 10, 0.1),
     smoothness: NumberParam('Smoothness', '', 1.5, 0.5, 2.5, 0.1),
     useGpu: BooleanParam('Use GPU', '', true),
-    readSlices: BooleanParam('Read Slices', '', false),
     ignoreCache: BooleanParam('Ignore Cache', '', false),
+    webgl: ValueParam('WebGL Context', '', undefined as Context | undefined),
 }
 export const DefaultGaussianDensityProps = paramDefaultValues(GaussianDensityParams)
 export type GaussianDensityProps = typeof DefaultGaussianDensityProps
@@ -30,7 +33,7 @@ function getConformation(unit: Unit) {
     }
 }
 
-export function computeUnitGaussianDensity(unit: Unit, props: GaussianDensityProps) {
+function getConformationAndRadius(unit: Unit) {
     const conformation = getConformation(unit)
     const { elements } = unit
     const position = {
@@ -47,11 +50,25 @@ export function computeUnitGaussianDensity(unit: Unit, props: GaussianDensityPro
         return sizeTheme.size(l)
     }
 
+    return { position, radius }
+}
+
+export function computeUnitGaussianDensity(unit: Unit, props: GaussianDensityProps) {
+    const { position, radius } = getConformationAndRadius(unit)
     return Task.create('Gaussian Density', async ctx => {
         return await GaussianDensity(ctx, position, unit.lookup3d.boundary.box, radius, props);
     });
 }
 
+export function computeUnitGaussianDensityTexture(unit: Unit, props: GaussianDensityProps, texture?: Texture) {
+    const webgl = props.webgl
+    if (!webgl) throw new Error('nned webgl context for computeUnitGaussianDensityTexture')
+    const { position, radius } = getConformationAndRadius(unit)
+    return Task.create('Gaussian Density', async ctx => {
+        return await GaussianDensityTexture(ctx, webgl, position, unit.lookup3d.boundary.box, radius, props, texture);
+    });
+}
+
 export async function computeUnitGaussianDensityCached(unit: Unit, props: GaussianDensityProps, cache: Map<string, DensityData>, ctx?: RuntimeContext) {
     const key = `${props.radiusOffset}|${props.resolution}|${props.smoothness}`
     let density = cache.get(key)

+ 8 - 1
src/mol-view/parameter.ts

@@ -13,6 +13,13 @@ export interface BaseParam<T> {
     defaultValue: T
 }
 
+export interface ValueParam<T> extends BaseParam<T> {
+    type: 'value'
+}
+export function ValueParam<T>(label: string, description: string, defaultValue: T): ValueParam<T> {
+    return { type: 'value', label, description, defaultValue }
+}
+
 export interface SelectParam<T extends string> extends BaseParam<T> {
     type: 'select'
     /** array of (value, label) tupels */
@@ -81,7 +88,7 @@ export function StructureParam(label: string, description: string, defaultValue:
     return { type: 'structure', label, description, defaultValue }
 }
 
-export type Param = SelectParam<any> | MultiSelectParam<any> | BooleanParam | RangeParam | TextParam | ColorParam | NumberParam | StructureParam
+export type Param = ValueParam<any> | SelectParam<any> | MultiSelectParam<any> | BooleanParam | RangeParam | TextParam | ColorParam | NumberParam | StructureParam
 
 export type Params = { [k: string]: Param }
 

+ 5 - 1
src/mol-view/viewer.ts

@@ -16,7 +16,7 @@ import TrackballControls from './controls/trackball'
 import { Viewport } from './camera/util'
 import { PerspectiveCamera } from './camera/perspective'
 import { resizeCanvas } from './util';
-import { createContext, getGLContext } from 'mol-gl/webgl/context';
+import { createContext, getGLContext, Context } from 'mol-gl/webgl/context';
 import { Representation } from 'mol-geo/representation';
 import { createRenderTarget } from 'mol-gl/webgl/render-target';
 import Scene from 'mol-gl/scene';
@@ -27,6 +27,8 @@ import { Loci, EmptyLoci, isEmptyLoci } from 'mol-model/loci';
 import { Color } from 'mol-util/color';
 
 interface Viewer {
+    webgl: Context,
+
     center: (p: Vec3) => void
 
     hide: (repr: Representation<any>) => void
@@ -247,6 +249,8 @@ namespace Viewer {
         handleResize()
 
         return {
+            webgl: ctx,
+
             center: (p: Vec3) => {
                 Vec3.set(controls.target, p[0], p[1], p[2])
             },