diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts index aa7a8ff5d933c61659139aa35aaa3c7c3360bfa6..45b617685d07779fb6dc3fac469d0117e0396319 100644 --- a/src/apps/canvas/index.ts +++ b/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') \ No newline at end of file +// app.loadCcp4Url('http://localhost:8091/ngl/data/betaGal.mrc') \ No newline at end of file diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts index 4224874470d1465db4880d83f58f226d5ee3fe25..3b4658514a82a258c73edc8f7f15dacfef6b3dd1 100644 --- a/src/apps/canvas/structure-view.ts +++ b/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]) diff --git a/src/apps/canvas/volume-view.ts b/src/apps/canvas/volume-view.ts index 422934fdf4e1db5ee695d3d1524f869027a823cb..1ef40f6dda5e97284b971906a8fa556215f64976 100644 --- a/src/apps/canvas/volume-view.ts +++ b/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]) diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index de1aa2f03b5f72340389534ed9a92c39eb2762e3..cdfdccc303b35c54a3c064985ef842b5703264b1 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/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 } diff --git a/src/mol-geo/geometry/geometry.ts b/src/mol-geo/geometry/geometry.ts index 7a3879effdba26117a00537759c8df5f32a88633..b8920b17a75cd1b065fe7d909d9cf592c6a955d9 100644 --- a/src/mol-geo/geometry/geometry.ts +++ b/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 diff --git a/src/mol-geo/representation/structure/index.ts b/src/mol-geo/representation/structure/index.ts index 5c1cae6f6840b9ee845365deeb5f5ade64586b51..f1b8d592bac06a5b8e403e4b82be4e2410c53467 100644 --- a/src/mol-geo/representation/structure/index.ts +++ b/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) diff --git a/src/mol-geo/representation/structure/units-visual.ts b/src/mol-geo/representation/structure/units-visual.ts index dd1eaafad5d9fdaa35bf2e193764f2714bcbec31..353404ea7f7cca159122fc3810b2ffa0c4789054 100644 --- a/src/mol-geo/representation/structure/units-visual.ts +++ b/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 } diff --git a/src/mol-geo/representation/structure/visual/gaussian-density-point.ts b/src/mol-geo/representation/structure/visual/gaussian-density-point.ts index ed7254ae3adebbe7fcb8eeade9bdfe3af2bfbfdb..9a26d96019acfd67fbfe01967205328cf3bc0fc1 100644 --- a/src/mol-geo/representation/structure/visual/gaussian-density-point.ts +++ b/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 } }) diff --git a/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts b/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts index 5f59467b42aaa9ae7c8a48e690a2402893795240..932cdad4e138e4a19f03360ecd814e456179ee92 100644 --- a/src/mol-geo/representation/structure/visual/gaussian-density-volume.ts +++ b/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 } }) diff --git a/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts b/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts index 99ef1b785901dafffd99f7a3a953b1ac7963cb53..8036e70041e7c44d851850bfe2bf6c88e2f515b2 100644 --- a/src/mol-geo/representation/structure/visual/gaussian-surface-mesh.ts +++ b/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 } }) diff --git a/src/mol-geo/representation/structure/visual/gaussian-surface-wireframe.ts b/src/mol-geo/representation/structure/visual/gaussian-surface-wireframe.ts index f14369d6f7aace9d77f1fd021d8d53fd4e0e25a9..3d1da6e79ce63dee34a04cda3b393b5d0c22fcf1 100644 --- a/src/mol-geo/representation/structure/visual/gaussian-surface-wireframe.ts +++ b/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 } }) diff --git a/src/mol-geo/representation/structure/visual/util/common.ts b/src/mol-geo/representation/structure/visual/util/common.ts index 7c95a0300faaa2f271f576bc8cc37bda52178c4a..ef59fe0f370c855cd150a25d3b9c35592f00c4ba 100644 --- a/src/mol-geo/representation/structure/visual/util/common.ts +++ b/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) } \ No newline at end of file diff --git a/src/mol-geo/representation/volume/direct-volume.ts b/src/mol-geo/representation/volume/direct-volume.ts index 8574b8671b8150f6c636a76929a20f3307b540ab..8e5eaecdf20e2df0264bdd0430037c73a5d835b9 100644 --- a/src/mol-geo/representation/volume/direct-volume.ts +++ b/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) diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index 55d3143f07fa1780a59c7ec0a22eb6ff63ef6653..356ad888e9bb07ecd801d3c5a066805b24daf08a 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/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) } \ No newline at end of file diff --git a/src/mol-gl/renderable/gaussian-density.ts b/src/mol-gl/renderable/gaussian-density.ts index 2b4b51e696c1a8c6441f8a05835206aa05ef6354..1fe68a5aae45fe6a59550402226b1f9f31798425 100644 --- a/src/mol-gl/renderable/gaussian-density.ts +++ b/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> diff --git a/src/mol-gl/renderable/lines.ts b/src/mol-gl/renderable/lines.ts index 186772e2ea67115abd1339b24d79dd7db754cdb2..7baaa55855911ddfdfa850039b4049a818744803 100644 --- a/src/mol-gl/renderable/lines.ts +++ b/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 diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index fd16901cc765c7ff23b52c21e5e2b044346a9f53..3d5d7d23c3dff7eb3e2fc3d274ab1118688d87fd 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/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 diff --git a/src/mol-gl/renderable/points.ts b/src/mol-gl/renderable/points.ts index f92bc00a5956f8a4e0627fb083b99fb895230624..9b5e09e2a067808f76f00f5cc549c0e3095d17c8 100644 --- a/src/mol-gl/renderable/points.ts +++ b/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 diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 93b419807a9fe3cd7ea298a5c3d00a2da58a7365..1c25d2ab4258f5b6a94d944d29056a39654047aa 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/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 diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index 93bfcab696c6e7e7e1a010aac1db3d1f36b93d89..26d8011827fadb8641fa83f2e1a7badeeaa47f6f 100644 --- a/src/mol-gl/shader-code.ts +++ b/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}`, diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index 86754cb969f73a4e407312b77f7de68eb57603f0..82e6c5e40257235f6c990e09bd1e2eeeb3efe6c7 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/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); diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag index ed44a36b8db701b96aa45be54544c127e1c7e139..546267b47df69a4640019cd537ffad4ba96b4272 100644 --- a/src/mol-gl/shader/gaussian-density.frag +++ b/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 } \ No newline at end of file diff --git a/src/mol-gl/shader/gaussian-density.vert b/src/mol-gl/shader/gaussian-density.vert index 2bbc651750d6a67283d8a8ea0126ed4a867bc1d0..217f0085a8a5d87aa2f6341e87f49bf122f54861 100644 --- a/src/mol-gl/shader/gaussian-density.vert +++ b/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; diff --git a/src/mol-gl/shader/mesh.frag b/src/mol-gl/shader/mesh.frag index 5b6a6e7218aa4d896bed9a19df9692d36bf9f366..ce0233e3b80d9e30bd2aebaac2708bc7c84408ef 100644 --- a/src/mol-gl/shader/mesh.frag +++ b/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; diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 254b3a13a417b1edad839ab39f2d44310f3ed5de..869526c5fe3c76e8364ccb09c8a9c312502b1f3b 100644 --- a/src/mol-gl/webgl/context.ts +++ b/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) diff --git a/src/mol-gl/webgl/program.ts b/src/mol-gl/webgl/program.ts index 34fa9501e2ca78895a1789634ccb70b5702dcd46..551f42abe0befb5070cd8d992d9fd4ff47b2e59b 100644 --- a/src/mol-gl/webgl/program.ts +++ b/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() }, diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 7c37fb60991798c495cadda0707299055d60f00b..ec3d687d23fba19bbe89a1d09ed2e00ce6b87a60 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/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 diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts index 92c840867abf0c7c0b5d4f41c5e82f78a4dd3792..a40fc4522b46502fc85e72b552e42365187da20d 100644 --- a/src/mol-gl/webgl/render-target.ts +++ b/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 diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 26966378896c86d6ffc3cd018381387775d237ec..e51497592f8d242be50a2ab3b594afdbac5229c5 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/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 diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index 9fc22194e19ca8fdd6b8f488093bceb88297110a..7ef816a1d335ba63bdc65377f71222cde8598c8e 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/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 diff --git a/src/mol-math/geometry/common.ts b/src/mol-math/geometry/common.ts index ccb246258fcab53b4a4deabb433a9c2728749124..6eab9cfa2492ef2a0cc1b95145ab8fa646805dd3 100644 --- a/src/mol-math/geometry/common.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-math/geometry/gaussian-density.ts b/src/mol-math/geometry/gaussian-density.ts index 16577af6b9305216a661bd28cf7e917a9913d20d..f7e39348e622e8ff2429dc66e57d7df9765c8c79 100644 --- a/src/mol-math/geometry/gaussian-density.ts +++ b/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 diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index cb779e1ebc04d31bc40eedd421ec77aedee79ca9..259d3ff576a1acd61ff554f271dfa287bca332b7 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/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 } \ No newline at end of file diff --git a/src/mol-model/structure/structure/unit/gaussian-density.ts b/src/mol-model/structure/structure/unit/gaussian-density.ts index 821a828d9333bb5556d62074ff3b4ff052a47ee8..a5d4d0681972dc13e3a20449cd8712a594489975 100644 --- a/src/mol-model/structure/structure/unit/gaussian-density.ts +++ b/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) diff --git a/src/mol-view/parameter.ts b/src/mol-view/parameter.ts index eec8b7a9c08d9503ec60741b16b714a24c752620..2110b3db36ba6eab84764a4210e1e88e636b4667 100644 --- a/src/mol-view/parameter.ts +++ b/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 } diff --git a/src/mol-view/viewer.ts b/src/mol-view/viewer.ts index 91a42cdacca0dd60d7cf26622b78c9af09c60e0c..7d092a8370f2cd7520dad135573b5a99713dc140 100644 --- a/src/mol-view/viewer.ts +++ b/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]) },