Przeglądaj źródła

direct volume rendering improvements

Alexander Rose 6 lat temu
rodzic
commit
a2eb5b95c7

+ 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
     }

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

@@ -18,7 +18,6 @@ import { Lines } from './lines/lines';
 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 { GLRenderingContext } from 'mol-gl/webgl/compat';
 import { Context } from 'mol-gl/webgl/context';
 
 //

+ 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)

+ 17 - 10
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': {},
@@ -526,15 +526,15 @@ 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
@@ -551,7 +551,9 @@ 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)
 
@@ -598,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
         }
 
@@ -606,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
     }

+ 33 - 32
src/mol-geo/representation/structure/visual/gaussian-density-volume.ts

@@ -9,42 +9,43 @@ 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, texture, bbox, gridDimension } = await unit.computeGaussianDensity(p, ctx)
-    if (!texture || !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
+
+    directVolume = texture.depth == 0 ?
+        DirectVolume2d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume2d) :    
+        DirectVolume3d.create(bbox, gridDimension, transform, texture, directVolume as DirectVolume3d)
+        
 
-    console.log('gridDimension', gridDimension)
-    console.log('gridTextureDim', renderTarget.width, renderTarget.height)
-    console.log('boundingBox', bbox)
-    console.log('transform', transform)
+    // 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)
+    // } else {
+    //     directVolume = {
+    //         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),
+    //     }
+    // }
 
     return directVolume;
 }

+ 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)
 }

+ 19 - 63
src/mol-geo/representation/volume/direct-volume.ts

@@ -13,13 +13,19 @@ 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, Context } 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
 
@@ -80,46 +86,17 @@ function createVolumeTexture2d(volume: VolumeData, maxTextureSize: number) {
 export function createDirectVolume2d(ctx: RuntimeContext, webgl: Context, volume: VolumeData, directVolume?: DirectVolume2d) {
     const gridDimension = volume.data.space.dimensions as Vec3
     const textureImage = createVolumeTexture2d(volume, webgl.maxTextureSize)
+    // debugTexture(createImageData(textureImage.array, textureImage.width, textureImage.height), 1/3)
     const transform = VolumeData.getGridToCartesianTransform(volume)
-
-    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 bbox = getBoundingBox(gridDimension, transform)
     const dim = Vec3.create(gridDimension[0], gridDimension[1], gridDimension[2])
     dim[0] += 1 // horizontal padding
     dim[0] += 1 // vertical padding
 
-    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
@@ -150,33 +127,12 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: Context, volume
     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)
 }
 
 //

+ 2 - 2
src/mol-gl/renderable/direct-volume.ts

@@ -59,7 +59,7 @@ 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>
@@ -73,7 +73,7 @@ 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>

+ 4 - 2
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

+ 6 - 3
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()
 
@@ -227,9 +228,11 @@ export function createRenderItem(ctx: Context, drawMode: DrawMode, shaderCode: S
                 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
+                    if (schema[k].kind !== 'texture2d' && schema[k].kind !== 'texture3d') {
+                        textures[k].load(value.ref.value as TextureImage<any> | TextureVolume<any>)
+                        versions[k] = value.ref.version
+                        valueChanges.textures = true
+                    }
                 }
             })
 

+ 21 - 7
src/mol-gl/webgl/texture.ts

@@ -19,7 +19,10 @@ 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'
@@ -32,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 {
@@ -119,7 +129,7 @@ export interface Texture {
     readonly depth: number
 
     define: (width: number, height: number, depth?: number) => void
-    load: (image: TextureImage<any>) => void
+    load: (image: TextureImage<any> | TextureVolume<any>) => void
     bind: (id: TextureId) => void
     unbind: (id: TextureId) => void
     /** Use `layer` to attach a z-slice of a 3D texture */
@@ -130,7 +140,7 @@ export interface Texture {
 
 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 {
@@ -241,9 +251,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

+ 6 - 3
src/mol-math/geometry/common.ts

@@ -24,8 +24,11 @@ export type DensityData = {
     transform: Mat4,
     field: Tensor,
     idField: Tensor,
+}
 
-    texture?: Texture,
-    bbox?: Box3D,
-    gridDimension?: Vec3
+export type DensityTextureData = {
+    transform: Mat4,
+    texture: Texture,
+    bbox: Box3D,
+    gridDimension: Vec3
 }

+ 2 - 0
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'
@@ -20,6 +21,7 @@ export const DefaultGaussianDensityProps = {
     radiusOffset: 0,
     smoothness: 1.5,
     useGpu: true,
+    webgl: undefined as Context | undefined
 }
 export type GaussianDensityProps = typeof DefaultGaussianDensityProps
 

+ 26 - 22
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -6,13 +6,13 @@
  */
 
 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 { Context, createContext, getGLContext } from 'mol-gl/webgl/context';
@@ -20,38 +20,38 @@ 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> {
-    // TODO allow passing a context via props
-    const webgl = getWebGLContext()
+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 useMultiDraw = webgl.maxDrawBuffers > 0
-    console.log('useMultiDraw', useMultiDraw)
-
-    console.time('gpu gaussian density render')
-    const { texture, scale, bbox, dim } = useMultiDraw ?
-        await GaussianDensityMultiDrawBuffer(ctx, webgl, position, box, radius, props) :
-        await GaussianDensitySingleDrawBuffer(ctx, webgl, position, box, radius, props)
-    console.timeEnd('gpu gaussian density render')
+    const { transform, texture, gridDimension } = await GaussianDensityTexture(ctx, webgl, position, box, radius, props)
 
     console.time('gpu gaussian density read')
-    const field = useMultiDraw ?
-        fieldFromTexture3d(webgl, texture, dim) :
-        fieldFromTexture2d(webgl, texture, dim)
+    const field = webgl.maxDrawBuffers > 0 ?
+        fieldFromTexture3d(webgl, texture, gridDimension) :
+        fieldFromTexture2d(webgl, texture, gridDimension)
     console.timeEnd('gpu gaussian density read')
 
     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> {
+    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)
+
     const transform = Mat4.identity()
     Mat4.fromScaling(transform, scale)
     Mat4.setTranslation(transform, bbox.min)
 
-    return { field, idField, transform, texture, bbox, gridDimension: dim }
+    return { transform, texture, bbox, gridDimension: dim }
 }
 
 //
 
-async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Context, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps) {
+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)
@@ -83,7 +83,9 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
 
-    const texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    if (!texture) {
+        texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    }
     texture.define(fboTexDimX, fboTexDimY)
 
     const program = renderable.getProgram('draw')
@@ -114,7 +116,7 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte
     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) {
+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)
@@ -131,7 +133,9 @@ async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Contex
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
 
-    const texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
+    if (!texture) {
+        texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
+    }
     texture.define(dx, dy, dz)
 
     if (drawBuffers === 1) {
@@ -204,7 +208,7 @@ function getWebGLContext() {
     return webglContext
 }
 
-async function prepareGaussianDensityData(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps) {
+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

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

@@ -9,7 +9,10 @@ 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),
@@ -17,6 +20,7 @@ export const GaussianDensityParams = {
     smoothness: NumberParam('Smoothness', '', 1.5, 0.5, 2.5, 0.1),
     useGpu: BooleanParam('Use GPU', '', true),
     ignoreCache: BooleanParam('Ignore Cache', '', false),
+    webgl: ValueParam('WebGL Context', '', undefined as Context | undefined),
 }
 export const DefaultGaussianDensityProps = paramDefaultValues(GaussianDensityParams)
 export type GaussianDensityProps = typeof DefaultGaussianDensityProps
@@ -29,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 = {
@@ -46,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)