From 7e88eec1b14478863327a103ddb7e5014f023afb Mon Sep 17 00:00:00 2001 From: Alexander Rose <alex.rose@rcsb.org> Date: Tue, 9 Oct 2018 18:10:23 -0700 Subject: [PATCH] wip, direct volume --- src/apps/structure-info/volume.ts | 2 +- src/mol-geo/geometry/color-data.ts | 4 +- .../geometry/direct-volume/direct-volume.ts | 15 ++--- .../direct-volume/transfer-function.ts | 2 +- src/mol-geo/geometry/marker-data.ts | 2 +- src/mol-geo/geometry/size-data.ts | 4 +- .../representation/volume/direct-volume.ts | 33 +++++------ .../representation/volume/isosurface-mesh.ts | 23 +++++--- src/mol-gl/render-object.ts | 8 +-- src/mol-gl/renderable/direct-volume.ts | 28 ++++++--- src/mol-gl/renderable/schema.ts | 19 +++--- src/mol-gl/renderable/util.ts | 13 +++- src/mol-gl/shader/direct-volume.frag | 5 +- src/mol-gl/webgl/compat.ts | 16 ++++- src/mol-gl/webgl/context.ts | 23 +++++++- src/mol-gl/webgl/render-target.ts | 4 +- src/mol-gl/webgl/texture.ts | 59 +++++++++++++------ src/mol-gl/webgl/uniform.ts | 3 +- src/mol-model/volume/data.ts | 16 +++-- 19 files changed, 181 insertions(+), 98 deletions(-) diff --git a/src/apps/structure-info/volume.ts b/src/apps/structure-info/volume.ts index d59034bca..790d4f8a9 100644 --- a/src/apps/structure-info/volume.ts +++ b/src/apps/structure-info/volume.ts @@ -38,7 +38,7 @@ function print(data: Volume) { } async function doMesh(data: Volume, filename: string) { - const mesh = await Task.create('', ctx => createVolumeSurface(ctx, data.volume, VolumeIsoValue.relative(data.volume.dataStats, 1.5))).run(); + const mesh = await Task.create('', ctx => createVolumeSurface(ctx, data.volume, VolumeIsoValue.calcAbsolute(data.volume.dataStats, 1.5))).run(); console.log({ vc: mesh.vertexCount, tc: mesh.triangleCount }); // Export the mesh in OBJ format. diff --git a/src/mol-geo/geometry/color-data.ts b/src/mol-geo/geometry/color-data.ts index 7ac3f24cd..b02d20440 100644 --- a/src/mol-geo/geometry/color-data.ts +++ b/src/mol-geo/geometry/color-data.ts @@ -20,7 +20,7 @@ export type ColorType = 'uniform' | 'instance' | 'group' | 'groupInstance' export type ColorData = { uColor: ValueCell<Vec3>, aColor: ValueCell<Float32Array>, - tColor: ValueCell<TextureImage>, + tColor: ValueCell<TextureImage<Uint8Array>>, uColorTexDim: ValueCell<Vec2>, dColorType: ValueCell<string>, } @@ -77,7 +77,7 @@ export async function createUniformColor(ctx: RuntimeContext, locationIt: Locati return createValueColor(color(NullLocation, false), colorData) } -export function createTextureColor(colors: TextureImage, type: ColorType, colorData?: ColorData): ColorData { +export function createTextureColor(colors: TextureImage<Uint8Array>, type: ColorType, colorData?: ColorData): ColorData { if (colorData) { ValueCell.update(colorData.tColor, colors) ValueCell.update(colorData.uColorTexDim, Vec2.create(colors.width, colors.height)) diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index 63cde8d1f..d89d13979 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -8,7 +8,7 @@ import { RuntimeContext } from 'mol-task' import { ValueCell } from 'mol-util' import { Sphere3D } from 'mol-math/geometry' import { paramDefaultValues, RangeParam, BooleanParam, SelectParam, TextParam } from 'mol-view/parameter'; -import { DirectVolumeValues } from 'mol-gl/renderable/direct-volume'; +import { DirectVolume2dValues } from 'mol-gl/renderable/direct-volume'; import { TextureImage } from 'mol-gl/renderable/util'; import { Vec3, Vec2, Mat4 } from 'mol-math/linear-algebra'; import { Box } from '../../primitive/box'; @@ -18,7 +18,7 @@ export interface DirectVolume { readonly kind: 'direct-volume', readonly gridDimension: ValueCell<Vec3>, - readonly gridTexture: ValueCell<TextureImage>, + readonly gridTexture: ValueCell<TextureImage<any>>, readonly gridTextureDim: ValueCell<Vec2>, readonly bboxSize: ValueCell<Vec3> readonly bboxMin: ValueCell<Vec3> @@ -48,14 +48,15 @@ export namespace DirectVolume { visible: BooleanParam('Visible', '', true), depthMask: BooleanParam('Depth Mask', '', true), useFog: BooleanParam('Use Fog', '', false), - isoValue: RangeParam('Iso Value', '', 0.22, 0, 1, 0.01), + isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01), + isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1), renderMode: SelectParam('Render Mode', '', 'volume', RenderModeOptions), controlPoints: TextParam('Control Points', '', '0.19:0.1, 0.2:0.5, 0.21:0.1, 0.4:0.3'), } export const DefaultProps = paramDefaultValues(Params) export type Props = typeof DefaultProps - export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, props: Props): Promise<DirectVolumeValues> { + export async function createValues(ctx: RuntimeContext, directVolume: DirectVolume, props: Props): Promise<DirectVolume2dValues> { const { bboxSize, bboxMin, bboxMax, gridDimension, gridTexture, gridTextureDim, transform } = directVolume const controlPoints = getControlPointsFromString(props.controlPoints) @@ -71,7 +72,7 @@ export namespace DirectVolume { uAlpha: ValueCell.create(props.alpha), dUseFog: ValueCell.create(props.useFog), - uIsoValue: ValueCell.create(props.isoValue), + uIsoValue: ValueCell.create(props.isoValueAbsolute), uBboxMin: bboxMin, uBboxMax: bboxMax, uBboxSize: bboxSize, @@ -84,9 +85,9 @@ export namespace DirectVolume { } } - export function updateValues(values: DirectVolumeValues, props: Props) { + export function updateValues(values: DirectVolume2dValues, props: Props) { console.log('DirectVolumeValues', props, values) - ValueCell.updateIfChanged(values.uIsoValue, props.isoValue) + ValueCell.updateIfChanged(values.uIsoValue, props.isoValueAbsolute) ValueCell.updateIfChanged(values.uAlpha, props.alpha) ValueCell.updateIfChanged(values.dUseFog, props.useFog) ValueCell.updateIfChanged(values.dRenderMode, props.renderMode) diff --git a/src/mol-geo/geometry/direct-volume/transfer-function.ts b/src/mol-geo/geometry/direct-volume/transfer-function.ts index f236bc9f6..9c6cd1424 100644 --- a/src/mol-geo/geometry/direct-volume/transfer-function.ts +++ b/src/mol-geo/geometry/direct-volume/transfer-function.ts @@ -19,7 +19,7 @@ export function getControlPointsFromString(s: string): ControlPoint[] { }) } // TODO move core function to mol-view/color -export function createTransferFunctionTexture(controlPoints: ControlPoint[], texture?: ValueCell<TextureImage>): ValueCell<TextureImage> { +export function createTransferFunctionTexture(controlPoints: ControlPoint[], texture?: ValueCell<TextureImage<Uint8Array>>): ValueCell<TextureImage<Uint8Array>> { const cp = [ { x: 0, alpha: 0 }, { x: 0, alpha: 0 }, diff --git a/src/mol-geo/geometry/marker-data.ts b/src/mol-geo/geometry/marker-data.ts index ed813f2fa..56e920c33 100644 --- a/src/mol-geo/geometry/marker-data.ts +++ b/src/mol-geo/geometry/marker-data.ts @@ -9,7 +9,7 @@ import { Vec2 } from 'mol-math/linear-algebra' import { TextureImage, createTextureImage } from 'mol-gl/renderable/util'; export type MarkerData = { - tMarker: ValueCell<TextureImage> + tMarker: ValueCell<TextureImage<Uint8Array>> uMarkerTexDim: ValueCell<Vec2> } diff --git a/src/mol-geo/geometry/size-data.ts b/src/mol-geo/geometry/size-data.ts index c9520dc3d..81373ef58 100644 --- a/src/mol-geo/geometry/size-data.ts +++ b/src/mol-geo/geometry/size-data.ts @@ -19,7 +19,7 @@ export type SizeType = 'uniform' | 'instance' | 'group' | 'groupInstance' export type SizeData = { uSize: ValueCell<number>, aSize: ValueCell<Float32Array>, - tSize: ValueCell<TextureImage>, + tSize: ValueCell<TextureImage<Uint8Array>>, uSizeTexDim: ValueCell<Vec2>, dSizeType: ValueCell<string>, } @@ -82,7 +82,7 @@ export async function createUniformSize(ctx: RuntimeContext, locationIt: Locatio return createValueSize(sizeFn(NullLocation), sizeData) } -export function createTextureSize(sizes: TextureImage, type: SizeType, sizeData?: SizeData): SizeData { +export function createTextureSize(sizes: TextureImage<Uint8Array>, type: SizeType, sizeData?: SizeData): SizeData { if (sizeData) { ValueCell.update(sizeData.tSize, sizes) ValueCell.update(sizeData.uSizeTexDim, Vec2.create(sizes.width, sizes.height)) diff --git a/src/mol-geo/representation/volume/direct-volume.ts b/src/mol-geo/representation/volume/direct-volume.ts index 860a7eaf2..4f13ee8cc 100644 --- a/src/mol-geo/representation/volume/direct-volume.ts +++ b/src/mol-geo/representation/volume/direct-volume.ts @@ -12,10 +12,10 @@ import { PickingId } from '../../geometry/picking'; import { MarkerAction } from '../../geometry/marker-data'; import { Loci, EmptyLoci } from 'mol-model/loci'; import { createRenderableState, updateRenderableState, Geometry } from '../../geometry/geometry'; -import { paramDefaultValues, RangeParam } from 'mol-view/parameter'; +import { paramDefaultValues } from 'mol-view/parameter'; import { ValueCell } from 'mol-util'; import { DirectVolume } from '../../geometry/direct-volume/direct-volume'; -import { Vec2, Vec3, Tensor } from 'mol-math/linear-algebra'; +import { Vec2, Vec3 } from 'mol-math/linear-algebra'; import { Box3D } from 'mol-math/geometry'; import { createImageData } from 'mol-gl/webgl/context'; import { debugTexture } from 'mol-gl/util'; @@ -38,15 +38,14 @@ function getFlattedVolumeLayout(dim: Vec3, maxTextureSize = 4096) { return { width, height, columns, rows } } -// let foo = 0 - -function createFlattendVolumeTexture(tensor: Tensor, itemSize = 4) { +function createFlattendVolumeTexture(volume: VolumeData) { + const { data: tensor, dataStats: stats } = volume const { space, data } = tensor const dim = space.dimensions as Vec3 const { get } = space const { width, height, columns, rows } = getFlattedVolumeLayout(dim) - const array = new Uint8Array(width * height * itemSize) + const array = new Uint8Array(width * height * 4) const textureImage = { array, width, height } const [ xl, yl, zl ] = dim @@ -57,15 +56,8 @@ function createFlattendVolumeTexture(tensor: Tensor, itemSize = 4) { const column = Math.floor(((z * xlp) % width) / xlp) const row = Math.floor((z * xlp) / width) const px = column * xlp + x - // const py = row * ylp + y - const index = itemSize * ((row * ylp * width) + (y * width) + px); - array[index] = value * 255; - array[index + 1] = value * 255; - array[index + 3] = value; - // if (foo % 1000 === 0) { - // console.log(value * 255, x, y, z, index, '|', column, row); - // } - // ++foo; + const index = 4 * ((row * ylp * width) + (y * width) + px) + array[index + 3] = ((value - stats.min) / (stats.max - stats.min)) * 255 } console.log('dim', dim) @@ -85,7 +77,7 @@ function createFlattendVolumeTexture(tensor: Tensor, itemSize = 4) { export function createDirectVolume(ctx: RuntimeContext, volume: VolumeData, directVolume?: DirectVolume) { const gridDimension = volume.data.space.dimensions as Vec3 // const textureImage = createTextureImage(1, 4) - const textureImage = createFlattendVolumeTexture(volume.data) + const textureImage = createFlattendVolumeTexture(volume) const transform = VolumeData.getGridToCartesianTransform(volume) console.log('textureImage', textureImage) @@ -128,8 +120,7 @@ export function createDirectVolume(ctx: RuntimeContext, volume: VolumeData, dire export const DirectVolumeParams = { ...Geometry.Params, - ...DirectVolume.Params, - isoValue: RangeParam('Iso Value', '', 2, -15, 15, 0.01), + ...DirectVolume.Params } export const DefaultDirectVolumeProps = paramDefaultValues(DirectVolumeParams) export type DirectVolumeProps = typeof DefaultDirectVolumeProps @@ -142,6 +133,9 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> { async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<DirectVolumeProps> = {}) { currentProps = { ...DefaultDirectVolumeProps, ...props } + if (props.isoValueRelative) { + // currentProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative) + } directVolume = await createDirectVolume(ctx, volume, directVolume) @@ -154,6 +148,9 @@ export function DirectVolumeVisual(): VolumeVisual<DirectVolumeProps> { async function update(ctx: RuntimeContext, props: Partial<DirectVolumeProps> = {}) { console.log('props', props) const newProps = { ...currentProps, ...props } + if (props.isoValueRelative) { + // newProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative) + } DirectVolume.updateValues(renderObject.values, newProps) updateRenderableState(renderObject.state, newProps) diff --git a/src/mol-geo/representation/volume/isosurface-mesh.ts b/src/mol-geo/representation/volume/isosurface-mesh.ts index 982dc3261..e6c951840 100644 --- a/src/mol-geo/representation/volume/isosurface-mesh.ts +++ b/src/mol-geo/representation/volume/isosurface-mesh.ts @@ -18,14 +18,14 @@ import { LocationIterator } from '../../util/location-iterator'; import { NullLocation } from 'mol-model/location'; import { createIdentityTransform } from '../../geometry/transform-data'; import { createRenderableState, updateRenderableState } from '../../geometry/geometry'; -import { paramDefaultValues, NumberParam } from 'mol-view/parameter'; +import { paramDefaultValues, RangeParam } from 'mol-view/parameter'; import { ValueCell } from 'mol-util'; -export async function createVolumeSurface(ctx: RuntimeContext, volume: VolumeData, isoValue: VolumeIsoValue, mesh?: Mesh) { +export async function createVolumeSurface(ctx: RuntimeContext, volume: VolumeData, isoValueAbsolute: number, mesh?: Mesh) { ctx.update({ message: 'Marching cubes...' }); const surface = await computeMarchingCubesMesh({ - isoLevel: VolumeIsoValue.toAbsolute(isoValue).absoluteValue, + isoLevel: isoValueAbsolute, scalarField: volume.data }, mesh).runAsChild(ctx); @@ -39,7 +39,8 @@ export async function createVolumeSurface(ctx: RuntimeContext, volume: VolumeDat export const IsosurfaceParams = { ...Mesh.Params, - isoValue: NumberParam('Iso Value', '', 2, -15, 15, 0.01), + isoValueAbsolute: RangeParam('Iso Value Absolute', '', 0.22, -1, 1, 0.01), + isoValueRelative: RangeParam('Iso Value Relative', '', 2, -10, 10, 0.1), } export const DefaultIsosurfaceProps = paramDefaultValues(IsosurfaceParams) export type IsosurfaceProps = typeof DefaultIsosurfaceProps @@ -52,8 +53,12 @@ export function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> { async function create(ctx: RuntimeContext, volume: VolumeData, props: Partial<IsosurfaceProps> = {}) { currentProps = { ...DefaultIsosurfaceProps, ...props } + if (props.isoValueRelative) { + currentProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative) + console.log('create props.isoValueRelative', props.isoValueRelative, currentProps.isoValueAbsolute, currentVolume.dataStats) + } - mesh = await createVolumeSurface(ctx, volume, VolumeIsoValue.relative(volume.dataStats, currentProps.isoValue)) + mesh = await createVolumeSurface(ctx, volume, currentProps.isoValueAbsolute) const locationIt = LocationIterator(1, 1, () => NullLocation) const transform = createIdentityTransform() @@ -66,13 +71,17 @@ export function IsosurfaceVisual(): VolumeVisual<IsosurfaceProps> { async function update(ctx: RuntimeContext, props: Partial<IsosurfaceProps> = {}) { const newProps = { ...currentProps, ...props } + if (props.isoValueRelative) { + newProps.isoValueAbsolute = VolumeIsoValue.calcAbsolute(currentVolume.dataStats, props.isoValueRelative) + console.log('update props.isoValueRelative', props.isoValueRelative, newProps.isoValueAbsolute, currentVolume.dataStats) + } let createMesh = false - if (newProps.isoValue !== currentProps.isoValue) createMesh = true + if (newProps.isoValueAbsolute !== currentProps.isoValueAbsolute) createMesh = true if (createMesh) { - mesh = await createVolumeSurface(ctx, currentVolume, VolumeIsoValue.relative(currentVolume.dataStats, currentProps.isoValue), mesh) + mesh = await createVolumeSurface(ctx, currentVolume, newProps.isoValueAbsolute, mesh) ValueCell.update(renderObject.values.drawCount, mesh.triangleCount * 3) } diff --git a/src/mol-gl/render-object.ts b/src/mol-gl/render-object.ts index 939f05c81..51c8fb902 100644 --- a/src/mol-gl/render-object.ts +++ b/src/mol-gl/render-object.ts @@ -9,7 +9,7 @@ import { RenderableValues } from './renderable/schema'; import { idFactory } from 'mol-util/id-factory'; import { Context } from './webgl/context'; import { GaussianDensityValues, GaussianDensityRenderable } from './renderable/gaussian-density'; -import { DirectVolumeValues, DirectVolumeRenderable } from './renderable/direct-volume'; +import { DirectVolume2dValues, DirectVolume2dRenderable } from './renderable/direct-volume'; const getNextId = idFactory(0, 0x7FFFFFFF) @@ -18,7 +18,7 @@ export interface MeshRenderObject extends BaseRenderObject { type: 'mesh', value export interface PointsRenderObject extends BaseRenderObject { type: 'points', values: PointsValues } export interface LinesRenderObject extends BaseRenderObject { type: 'lines', values: LinesValues } export interface GaussianDensityRenderObject extends BaseRenderObject { type: 'gaussian-density', values: GaussianDensityValues } -export interface DirectVolumeRenderObject extends BaseRenderObject { type: 'direct-volume', values: DirectVolumeValues } +export interface DirectVolumeRenderObject extends BaseRenderObject { type: 'direct-volume', values: DirectVolume2dValues } export type RenderObject = MeshRenderObject | PointsRenderObject | LinesRenderObject | GaussianDensityRenderObject | DirectVolumeRenderObject export function createMeshRenderObject(values: MeshValues, state: RenderableState): MeshRenderObject { @@ -33,7 +33,7 @@ export function createLinesRenderObject(values: LinesValues, state: RenderableSt export function createGaussianDensityRenderObject(values: GaussianDensityValues, state: RenderableState): GaussianDensityRenderObject { return { id: getNextId(), type: 'gaussian-density', values, state } } -export function createDirectVolumeRenderObject(values: DirectVolumeValues, state: RenderableState): DirectVolumeRenderObject { +export function createDirectVolumeRenderObject(values: DirectVolume2dValues, state: RenderableState): DirectVolumeRenderObject { return { id: getNextId(), type: 'direct-volume', values, state } } @@ -43,6 +43,6 @@ export function createRenderable(ctx: Context, o: RenderObject): Renderable<any> case 'points': return PointsRenderable(ctx, o.id, o.values, o.state) case 'lines': return LinesRenderable(ctx, o.id, o.values, o.state) case 'gaussian-density': return GaussianDensityRenderable(ctx, o.id, o.values, o.state) - case 'direct-volume': return DirectVolumeRenderable(ctx, o.id, o.values, o.state) + case 'direct-volume': return DirectVolume2dRenderable(ctx, o.id, o.values, o.state) } } \ No newline at end of file diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index a19d26f32..5464f4195 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -11,7 +11,7 @@ import { AttributeSpec, Values, UniformSpec, GlobalUniformSchema, InternalSchema import { DirectVolumeShaderCode } from '../shader-code'; import { ValueCell } from 'mol-util'; -export const DirectVolumeSchema = { +const DirectVolumeBaseSchema = { drawCount: ValueSpec('number'), instanceCount: ValueSpec('number'), @@ -27,20 +27,30 @@ export const DirectVolumeSchema = { uBboxSize: UniformSpec('v3'), uTransform: UniformSpec('m4'), uGridDim: UniformSpec('v3'), - uGridTexDim: UniformSpec('v2'), - tGridTex: TextureSpec('rgba', 'ubyte', 'linear'), dRenderMode: DefineSpec('string', ['isosurface', 'volume']), - tTransferTex: TextureSpec('rgba', 'ubyte', 'linear'), + tTransferTex: TextureSpec('image-uint8', 'rgba', 'ubyte', 'linear'), } -export type DirectVolumeSchema = typeof DirectVolumeSchema -export type DirectVolumeValues = Values<DirectVolumeSchema> -export function DirectVolumeRenderable(ctx: Context, id: number, values: DirectVolumeValues, state: RenderableState): Renderable<DirectVolumeValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolumeSchema } - const internalValues: InternalValues = { +function getInternalValues(ctx: Context, id: number): InternalValues { + return { dWebGL2: ValueCell.create(ctx.isWebGL2), uObjectId: ValueCell.create(id) } +} + +// + +export const DirectVolume2dSchema = { + ...DirectVolumeBaseSchema, + uGridTexDim: UniformSpec('v2'), + tGridTex: TextureSpec('image-uint8', '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> { + const schema = { ...GlobalUniformSchema, ...InternalSchema, ...DirectVolume2dSchema } + const internalValues = getInternalValues(ctx, id) const shaderCode = DirectVolumeShaderCode const renderItem = createRenderItem(ctx, 'triangles', shaderCode, schema, { ...values, ...internalValues }) const renderable = createRenderable(renderItem, values, state); diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index c29f51f09..01ff19633 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 } from './util'; -import { TextureValues, TextureType, TextureFormat, TextureFilter } from '../webgl/texture'; +import { TextureValues, TextureType, TextureFormat, TextureFilter, TextureKind } from '../webgl/texture'; export type ValueKindType = { 'number': number @@ -39,7 +39,8 @@ export type KindValue = { 'int32': Int32Array 'float32': Float32Array - 'image': TextureImage + 'image-uint8': TextureImage<Uint8Array> + 'image-float32': TextureImage<Float32Array> 'number': number 'string': string @@ -84,9 +85,9 @@ export function UniformSpec<K extends UniformKind>(kind: K): UniformSpec<K> { return { type: 'uniform', kind } } -export type TextureSpec = { type: 'texture', kind: 'image', format: TextureFormat, dataType: TextureType, filter: TextureFilter } -export function TextureSpec(format: TextureFormat, dataType: TextureType, filter: TextureFilter): TextureSpec { - return { type: 'texture', kind: 'image', format, dataType, filter } +export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter } +export function TextureSpec<K extends TextureKind>(kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter): TextureSpec<K> { + return { type: 'texture', kind, format, dataType, filter } } export type ElementsSpec<K extends ElementsKind> = { type: 'elements', kind: K } @@ -108,7 +109,7 @@ export function ValueSpec<K extends ValueKind>(kind: K): ValueSpec<K> { export type RenderableSchema = { [k: string]: ( - AttributeSpec<ArrayKind> | UniformSpec<UniformKind> | TextureSpec | + AttributeSpec<ArrayKind> | UniformSpec<UniformKind> | TextureSpec<TextureKind> | ValueSpec<ValueKind> | DefineSpec<DefineKind> | ElementsSpec<ElementsKind> ) } @@ -151,7 +152,7 @@ export const ColorSchema = { aColor: AttributeSpec('float32', 3, 0), uColor: UniformSpec('v3'), uColorTexDim: UniformSpec('v2'), - tColor: TextureSpec('rgb', 'ubyte', 'nearest'), + tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'), dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']), } export type ColorSchema = typeof ColorSchema @@ -161,7 +162,7 @@ export const SizeSchema = { aSize: AttributeSpec('float32', 1, 0), uSize: UniformSpec('f'), uSizeTexDim: UniformSpec('v2'), - tSize: TextureSpec('alpha', 'ubyte', 'nearest'), + tSize: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'group_instance']), } export type SizeSchema = typeof SizeSchema @@ -179,7 +180,7 @@ export const BaseSchema = { uGroupCount: UniformSpec('i'), uMarkerTexDim: UniformSpec('v2'), - tMarker: TextureSpec('alpha', 'ubyte', 'nearest'), + tMarker: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), drawCount: ValueSpec('number'), instanceCount: ValueSpec('number'), diff --git a/src/mol-gl/renderable/util.ts b/src/mol-gl/renderable/util.ts index 86f3a8565..aa77abc80 100644 --- a/src/mol-gl/renderable/util.ts +++ b/src/mol-gl/renderable/util.ts @@ -16,13 +16,20 @@ export function calculateTextureInfo (n: number, itemSize: number) { return { width, height, length: width * height * itemSize } } -export interface TextureImage { - readonly array: Uint8Array +export interface TextureImage<T extends Uint8Array | Float32Array> { + readonly array: T readonly width: number readonly height: number } -export function createTextureImage (n: number, itemSize: number): TextureImage { +export interface TextureVolume<T extends Uint8Array | Float32Array> { + readonly array: T + readonly width: number + readonly height: number + readonly depth: number +} + +export function createTextureImage(n: number, itemSize: number): TextureImage<Uint8Array> { const { length, width, height } = calculateTextureInfo(n, itemSize) return { array: new Uint8Array(length), width, height } } diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag index adf44b420..ac72c81a0 100644 --- a/src/mol-gl/shader/direct-volume.frag +++ b/src/mol-gl/shader/direct-volume.frag @@ -134,7 +134,7 @@ vec3 color = vec3(0.45, 0.55, 0.8); vec4 raymarch(vec3 cameraPos) { vec3 pos = unitCoord; - float prevValue = -1.0; + float prevValue = -127.0; float value = 0.0; float MAX_STEPS_F = max(max(uGridDim.x, uGridDim.y), uGridDim.z); // int MAX_STEPS = 2 * int(length(vec3(imgresx, imgresy, imgresz))); @@ -196,8 +196,9 @@ vec4 raymarch(vec3 cameraPos) { src.a = uAlpha; // draw interior darker - if( (prevValue - uIsoValue) < 0.0 ) + if( (prevValue - uIsoValue) > 0.0 ) { src.rgb *= 0.5; + } src.rgb *= src.a; dst = (1.0 - dst.a) * src + dst; // standard blending diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts index 2ff0e6bb6..f0bad2f5f 100644 --- a/src/mol-gl/webgl/compat.ts +++ b/src/mol-gl/webgl/compat.ts @@ -61,7 +61,7 @@ export interface COMPAT_element_index_uint { } export function getElementIndexUint(gl: GLRenderingContext): COMPAT_element_index_uint | null { - return isWebGL2(gl) ? {} : gl.getExtension('OES_standard_derivatives') + return isWebGL2(gl) ? {} : gl.getExtension('OES_element_index_uint') } export interface COMPAT_vertex_array_object { @@ -92,4 +92,18 @@ export function getVertexArrayObject(gl: GLRenderingContext): COMPAT_vertex_arra isVertexArray: ext.isVertexArrayOES.bind(ext) } } +} + +export interface COMPAT_texture_float { +} + +export function getTextureFloat(gl: GLRenderingContext): COMPAT_texture_float | null { + return isWebGL2(gl) ? {} : gl.getExtension('OES_texture_float') +} + +export interface COMPAT_texture_float_linear { +} + +export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_float_linear | null { + return gl.getExtension('OES_texture_float_linear') } \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 0ba44529a..254b3a13a 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -6,7 +6,7 @@ import { createProgramCache, ProgramCache } from './program' import { createShaderCache, ShaderCache } from './shader' -import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint } from './compat'; +import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear } from './compat'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -51,7 +51,7 @@ function unbindFramebuffer(gl: GLRenderingContext) { gl.bindFramebuffer(gl.FRAMEBUFFER, null) } -export function createImageData(buffer: Uint8Array, width: number, height: number) { +export function createImageData(buffer: ArrayLike<number>, width: number, height: number) { const w = width * 4 const h = height const data = new Uint8ClampedArray(width * height * 4) @@ -69,6 +69,8 @@ export function createImageData(buffer: Uint8Array, width: number, height: numbe type Extensions = { instancedArrays: COMPAT_instanced_arrays standardDerivatives: COMPAT_standard_derivatives + textureFloat: COMPAT_texture_float, + textureFloatLinear: COMPAT_texture_float_linear, elementIndexUint: COMPAT_element_index_uint | null vertexArrayObject: COMPAT_vertex_array_object | null } @@ -111,6 +113,14 @@ export function createContext(gl: GLRenderingContext): Context { if (standardDerivatives === null) { throw new Error('Could not find support for "standard_derivatives"') } + const textureFloat = getTextureFloat(gl) + if (textureFloat === null) { + throw new Error('Could not find support for "texture_float"') + } + const textureFloatLinear = getTextureFloatLinear(gl) + if (textureFloatLinear === null) { + throw new Error('Could not find support for "texture_float_linear"') + } const elementIndexUint = getElementIndexUint(gl) if (elementIndexUint === null) { console.warn('Could not find support for "element_index_uint"') @@ -130,7 +140,14 @@ export function createContext(gl: GLRenderingContext): Context { return { gl, isWebGL2: isWebGL2(gl), - extensions: { instancedArrays, standardDerivatives, elementIndexUint, vertexArrayObject }, + extensions: { + instancedArrays, + standardDerivatives, + textureFloat, + textureFloatLinear, + elementIndexUint, + vertexArrayObject + }, pixelRatio: getPixelRatio(), shaderCache, diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts index bce6528dd..81ddd9fb8 100644 --- a/src/mol-gl/webgl/render-target.ts +++ b/src/mol-gl/webgl/render-target.ts @@ -17,7 +17,7 @@ export interface RenderTarget { readonly id: number readonly width: number readonly height: number - readonly image: TextureImage + readonly image: TextureImage<any> readonly texture: Texture bind: () => void @@ -31,7 +31,7 @@ export interface RenderTarget { export function createRenderTarget (ctx: Context, _width: number, _height: number): RenderTarget { const { gl } = ctx - const image: Helpers.Mutable<TextureImage> = { + const image: Helpers.Mutable<TextureImage<Uint8Array>> = { array: new Uint8Array(_width * _height * 4), width: _width, height: _height diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index d7e94cc07..c090c1aca 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -13,8 +13,13 @@ import { Framebuffer } from './framebuffer'; const getNextTextureId = idFactory() +export type TextureKindValue = { + 'image-uint8': TextureImage<Uint8Array> + 'image-float32': TextureImage<Float32Array> +} +export type TextureKind = keyof TextureKindValue +export type TextureType = 'ubyte' | 'float' export type TextureFormat = 'alpha' | 'rgb' | 'rgba' -export type TextureType = 'ubyte' | 'uint' export type TextureAttachment = 'depth' | 'stencil' | 'color0' export type TextureFilter = 'nearest' | 'linear' @@ -27,11 +32,35 @@ export function getFormat(ctx: Context, format: TextureFormat) { } } +export function getInternalFormat(ctx: Context, format: TextureFormat, type: TextureType) { + const { gl, isWebGL2 } = ctx + if (isWebGL2) { + switch (format) { + case 'alpha': + switch (type) { + case 'ubyte': return gl.ALPHA + case 'float': throw new Error('invalid format/type combination alpha/float') + } + case 'rgb': + switch (type) { + case 'ubyte': return gl.RGB + case 'float': return (gl as WebGL2RenderingContext).RGB32F + } + case 'rgba': + switch (type) { + case 'ubyte': return gl.RGBA + case 'float': return (gl as WebGL2RenderingContext).RGBA32F + } + } + } + return getFormat(ctx, format) +} + export function getType(ctx: Context, type: TextureType) { const { gl } = ctx switch (type) { case 'ubyte': return gl.UNSIGNED_BYTE - case 'uint': return gl.UNSIGNED_INT + case 'float': return gl.FLOAT } } @@ -55,12 +84,10 @@ export function getAttachment(ctx: Context, attachment: TextureAttachment) { export interface Texture { readonly id: number readonly format: number + readonly internalFormat: number readonly type: number - readonly width: number - readonly height: number - - load: (image: TextureImage) => void + load: (image: TextureImage<any>) => void bind: (id: TextureId) => void unbind: (id: TextureId) => void attachFramebuffer: (framebuffer: Framebuffer, attachment: TextureAttachment) => void @@ -69,7 +96,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> } +export type TextureValues = { [k: string]: ValueCell<TextureImage<any>> } export type Textures = { [k: string]: Texture } export function createTexture(ctx: Context, _format: TextureFormat, _type: TextureType, _filter: TextureFilter): Texture { @@ -80,33 +107,29 @@ export function createTexture(ctx: Context, _format: TextureFormat, _type: Textu throw new Error('Could not create WebGL texture') } + + const filter = getFilter(ctx, _filter) const format = getFormat(ctx, _format) + const internalFormat = getInternalFormat(ctx, _format, _type) const type = getType(ctx, _type) - let _width = 0 - let _height = 0 - let destroyed = false ctx.textureCount += 1 return { id, format, + internalFormat, type, - get width () { return _width }, - get height () { return _height }, - - load: (image: TextureImage) => { + load: (image: TextureImage<any>) => { const { array, width, height } = image gl.bindTexture(gl.TEXTURE_2D, texture) // unpack alignment of 1 since we use textures only for data gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1); - // gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, true); - (gl as WebGLRenderingContext).texImage2D(gl.TEXTURE_2D, 0, format, width, height, 0, format, type, array) // TODO remove cast when webgl2 types are fixed - _width = width - _height = height + gl.pixelStorei(gl.UNPACK_COLORSPACE_CONVERSION_WEBGL, gl.NONE); + (gl as WebGLRenderingContext).texImage2D(gl.TEXTURE_2D, 0, internalFormat, width, height, 0, format, type, array) // TODO remove cast when webgl2 types are fixed gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, filter) gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, filter) // clamp-to-edge needed for non-power-of-two textures diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index a8a92ad23..9fc22194e 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -6,7 +6,6 @@ import { Mat3, Mat4, Vec2, Vec3, Vec4 } from 'mol-math/linear-algebra' import { Context } from './context'; -import { TextureImage } from '../renderable/util'; import { ValueCell } from 'mol-util'; import { RenderableSchema } from '../renderable/schema'; @@ -21,7 +20,7 @@ export type UniformKindValue = { 't2': number } export type UniformKind = keyof UniformKindValue -export type UniformType = number | Vec2 | Vec3 | Vec4 | Mat3 | Mat4 | TextureImage +export type UniformType = number | Vec2 | Vec3 | Vec4 | Mat3 | Mat4 export interface UniformUpdater { set: (value: UniformType) => void, clear: () => void diff --git a/src/mol-model/volume/data.ts b/src/mol-model/volume/data.ts index 592a2d936..05e85eba3 100644 --- a/src/mol-model/volume/data.ts +++ b/src/mol-model/volume/data.ts @@ -39,25 +39,29 @@ namespace VolumeIsoValue { export function absolute(stats: VolumeData['dataStats'], value: number): Absolute { return { kind: 'absolute', stats, absoluteValue: value }; } export function relative(stats: VolumeData['dataStats'], value: number): Relative { return { kind: 'relative', stats, relativeValue: value }; } + export function calcAbsolute(stats: VolumeData['dataStats'], relativeValue: number): number { + return relativeValue * stats.sigma + stats.mean + } + + export function calcRelative(stats: VolumeData['dataStats'], absoluteValue: number): number { + return (stats.mean - absoluteValue) / stats.sigma + } + export function toAbsolute(value: VolumeIsoValue): Absolute { if (value.kind === 'absolute') return value; - - const { mean, sigma } = value.stats return { kind: 'absolute', stats: value.stats, - absoluteValue: value.relativeValue * sigma + mean + absoluteValue: calcAbsolute(value.stats, value.relativeValue) } } export function toRelative(value: VolumeIsoValue): Relative { if (value.kind === 'relative') return value; - - const { mean, sigma } = value.stats return { kind: 'relative', stats: value.stats, - relativeValue: (mean - value.absoluteValue) / sigma + relativeValue: calcRelative(value.stats, value.absoluteValue) } } } -- GitLab