diff --git a/src/cli/state-docs/pd-to-md.ts b/src/cli/state-docs/pd-to-md.ts index 1e0d4ef4b5b6d05f602b010bdc029fe4385bcac8..0e0fa7af5d7cb90ea0707a0f076f97707842bedb 100644 --- a/src/cli/state-docs/pd-to-md.ts +++ b/src/cli/state-docs/pd-to-md.ts @@ -26,7 +26,8 @@ function paramInfo(param: PD.Any, offset: number): string { case 'file': return `JavaScript File Handle`; case 'file-list': return `JavaScript FileList Handle`; case 'select': return `One of ${oToS(param.options)}`; - case 'value-ref': return `Reference to a state object.`; + case 'value-ref': return `Reference to a runtime defined value.`; + case 'data-ref': return `Reference to a computed data value.`; case 'text': return 'String'; case 'interval': return `Interval [min, max]`; case 'group': return `Object with:\n${getParams(param.params, offset + 2)}`; diff --git a/src/examples/alpha-orbitals/index.ts b/src/examples/alpha-orbitals/index.ts index 6387b915ddb895ce73dfd317574e8391896d5c4b..612a835a06732b47f4d3df9257442fe0f8ab0294 100644 --- a/src/examples/alpha-orbitals/index.ts +++ b/src/examples/alpha-orbitals/index.ts @@ -20,8 +20,8 @@ import { BehaviorSubject } from 'rxjs'; import { debounceTime, skip } from 'rxjs/operators'; import './index.html'; import { Basis, AlphaOrbital } from '../../extensions/alpha-orbitals/data-model'; -import { canComputeAlphaOrbitalsOnGPU } from '../../extensions/alpha-orbitals/gpu/compute'; import { PluginCommands } from '../../mol-plugin/commands'; +import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d'; require('mol-plugin-ui/skin/light.scss'); interface DemoInput { @@ -71,7 +71,7 @@ export class AlphaOrbitalsExample { this.plugin.managers.interactivity.setProps({ granularity: 'element' }); - if (!canComputeAlphaOrbitalsOnGPU(this.plugin.canvas3d?.webgl)) { + if (!canComputeGrid3dOnGPU(this.plugin.canvas3d?.webgl)) { PluginCommands.Toast.Show(this.plugin, { title: 'Error', message: `Browser/device does not support required WebGL extension (OES_texture_float).` diff --git a/src/extensions/alpha-orbitals/data-model.ts b/src/extensions/alpha-orbitals/data-model.ts index 4c7c1edc909eed748e2e355926e4a60550c64bea..9141f0f569009a758f55fbaf5ad9c759c45adb5e 100644 --- a/src/extensions/alpha-orbitals/data-model.ts +++ b/src/extensions/alpha-orbitals/data-model.ts @@ -7,8 +7,8 @@ import { Mat4, Tensor, Vec3 } from '../../mol-math/linear-algebra'; import { Grid } from '../../mol-model/volume'; import { SphericalBasisOrder } from './spherical-functions'; -import { Box3D } from '../../mol-math/geometry'; -import { arrayMin, arrayMax, arrayRms } from '../../mol-util/array'; +import { Box3D, RegularGrid3d } from '../../mol-math/geometry'; +import { arrayMin, arrayMax, arrayRms, arrayMean } from '../../mol-util/array'; // Note: generally contracted gaussians are currently not supported. export interface SphericalElectronShell { @@ -95,7 +95,7 @@ export function initCubeGrid(params: CubeGridComputationParams): CubeGridInfo { const BohrToAngstromFactor = 0.529177210859; -export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrder: number[]) { +export function createGrid(gridInfo: RegularGrid3d, values: Float32Array, axisOrder: number[]) { const boxSize = Box3D.size(Vec3(), gridInfo.box); const boxOrigin = Vec3.clone(gridInfo.box.min); @@ -122,7 +122,7 @@ export function createGrid(gridInfo: CubeGridInfo, values: Float32Array, axisOrd stats: { min: arrayMin(values), max: arrayMax(values), - mean: arrayMax(values), + mean: arrayMean(values), sigma: arrayRms(values), }, }; diff --git a/src/extensions/alpha-orbitals/density.ts b/src/extensions/alpha-orbitals/density.ts index 0b39e5928707362fecdcfa0a0e830764f47404c2..73b00ed07924ac5d7f487377eb727d24cdc449cc 100644 --- a/src/extensions/alpha-orbitals/density.ts +++ b/src/extensions/alpha-orbitals/density.ts @@ -5,10 +5,11 @@ */ import { sortArray } from '../../mol-data/util'; +import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d'; import { WebGLContext } from '../../mol-gl/webgl/context'; import { Task } from '../../mol-task'; import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model'; -import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute'; +import { gpuComputeAlphaOrbitalsDensityGridValues } from './gpu/compute'; export function createSphericalCollocationDensityGrid( params: CubeGridComputationParams, orbitals: AlphaOrbital[], webgl?: WebGLContext @@ -17,9 +18,9 @@ export function createSphericalCollocationDensityGrid( const cubeGrid = initCubeGrid(params); let matrix: Float32Array; - if (canComputeAlphaOrbitalsOnGPU(webgl)) { + if (canComputeGrid3dOnGPU(webgl)) { // console.time('gpu'); - matrix = await gpuComputeAlphaOrbitalsDensityGridValues(webgl!, cubeGrid, orbitals, ctx); + matrix = await gpuComputeAlphaOrbitalsDensityGridValues(ctx, webgl!, cubeGrid, orbitals); // console.timeEnd('gpu'); } else { throw new Error('Missing OES_texture_float WebGL extension.'); diff --git a/src/extensions/alpha-orbitals/gpu/compute.ts b/src/extensions/alpha-orbitals/gpu/compute.ts index 0cc25a6ccda30fba294ec44fe0bdf3bbe22f431d..c142590f0d3258298e40aeb4b4074f11b4775e05 100644 --- a/src/extensions/alpha-orbitals/gpu/compute.ts +++ b/src/extensions/alpha-orbitals/gpu/compute.ts @@ -4,46 +4,72 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { QuadSchema, QuadValues } from '../../../mol-gl/compute/util'; -import { ComputeRenderable, createComputeRenderable } from '../../../mol-gl/renderable'; -import { DefineSpec, TextureSpec, UniformSpec, Values } from '../../../mol-gl/renderable/schema'; -import { ShaderCode } from '../../../mol-gl/shader-code'; -import quad_vert from '../../../mol-gl/shader/quad.vert'; +import { createGrid3dComputeRenderable } from '../../../mol-gl/compute/grid3d'; +import { TextureSpec, UnboxedValues, UniformSpec } from '../../../mol-gl/renderable/schema'; import { WebGLContext } from '../../../mol-gl/webgl/context'; -import { createComputeRenderItem } from '../../../mol-gl/webgl/render-item'; import { RuntimeContext } from '../../../mol-task'; import { ValueCell } from '../../../mol-util'; import { arrayMin } from '../../../mol-util/array'; -import { isLittleEndian } from '../../../mol-util/is-little-endian'; import { AlphaOrbital, Basis, CubeGridInfo } from '../data-model'; import { normalizeBasicOrder, SphericalBasisOrder } from '../spherical-functions'; -import shader_frag from './shader.frag'; +import { MAIN, UTILS } from './shader.frag'; -const AlphaOrbitalsSchema = { - ...QuadSchema, - uDimensions: UniformSpec('v3'), - uMin: UniformSpec('v3'), - uDelta: UniformSpec('v3'), +const Schema = { tCenters: TextureSpec('image-float32', 'rgba', 'float', 'nearest'), tInfo: TextureSpec('image-float32', 'rgba', 'float', 'nearest'), tCoeff: TextureSpec('image-float32', 'rgb', 'float', 'nearest'), tAlpha: TextureSpec('image-float32', 'alpha', 'float', 'nearest'), - uWidth: UniformSpec('f'), uNCenters: UniformSpec('i'), uNAlpha: UniformSpec('i'), uNCoeff: UniformSpec('i'), uMaxCoeffs: UniformSpec('i'), - uLittleEndian: UniformSpec('b'), - uDensity: UniformSpec('b'), - uOccupancy: UniformSpec('f'), - tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest') }; -type AlphaOrbitalsSchema = Values<typeof AlphaOrbitalsSchema> -const AlphaOrbitalsName = 'alpha-orbitals'; -const AlphaOrbitalsTex0 = 'alpha-orbitals-0'; -const AlphaOrbitalsTex1 = 'alpha-orbitals-1'; -const AlphaOrbitalsShaderCode = ShaderCode(AlphaOrbitalsName, quad_vert, shader_frag); -type AlphaOrbitalsRenderable = ComputeRenderable<AlphaOrbitalsSchema> + +const Orbitals = createGrid3dComputeRenderable({ + schema: Schema, + loopBounds: ['uNCenters', 'uMaxCoeffs'], + mainCode: MAIN, + utilCode: UTILS, + returnCode: 'v', + values(params: { grid: CubeGridInfo, orbital: AlphaOrbital }) { + return createTextureData(params.grid, params.orbital); + } +}); + +const Density = createGrid3dComputeRenderable({ + schema: { + ...Schema, + uOccupancy: UniformSpec('f'), + }, + loopBounds: ['uNCenters', 'uMaxCoeffs'], + mainCode: MAIN, + utilCode: UTILS, + returnCode: 'current + uOccupancy * v * v', + values(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) { + return { + ...createTextureData(params.grid, params.orbitals[0]), + uOccupancy: 0 + }; + }, + cumulative: { + states(params: { grid: CubeGridInfo, orbitals: AlphaOrbital[] }) { + return params.orbitals.filter(o => o.occupancy !== 0); + }, + update({ grid }, state: AlphaOrbital, values) { + const alpha = getNormalizedAlpha(grid.params.basis, state.alpha, grid.params.sphericalOrder); + ValueCell.updateIfChanged(values.uOccupancy, state.occupancy); + ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha }); + } + } +}); + +export function gpuComputeAlphaOrbitalsGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) { + return Orbitals(ctx, webgl, grid, { grid, orbital }); +} + +export function gpuComputeAlphaOrbitalsDensityGridValues(ctx: RuntimeContext, webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[]) { + return Density(ctx, webgl, grid, { grid, orbitals }); +} function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrder: SphericalBasisOrder) { const alpha = new Float32Array(alphaOrbitals.length); @@ -62,7 +88,7 @@ function getNormalizedAlpha(basis: Basis, alphaOrbitals: number[], sphericalOrde return alpha; } -function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) { +function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital): UnboxedValues<typeof Schema> { const { basis, sphericalOrder, cutoffThreshold } = grid.params; let centerCount = 0; @@ -131,179 +157,14 @@ function createTextureData(grid: CubeGridInfo, orbital: AlphaOrbital) { } } - return { nCenters: centerCount, nAlpha: baseCount, nCoeff: coeffCount, maxCoeffs, centers, info, alpha, coeff }; -} - -function createAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable { - const data = createTextureData(grid, orbital); - - const [nx, ny, nz] = grid.dimensions; - const width = Math.ceil(Math.sqrt(nx * ny * nz)); - - if (!ctx.namedFramebuffers[AlphaOrbitalsName]) { - ctx.namedFramebuffers[AlphaOrbitalsName] = ctx.resources.framebuffer(); - } - if (!ctx.namedTextures[AlphaOrbitalsTex0]) { - ctx.namedTextures[AlphaOrbitalsTex0] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); - } - if (!ctx.namedTextures[AlphaOrbitalsTex1]) { - ctx.namedTextures[AlphaOrbitalsTex1] = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); - } - - const values: AlphaOrbitalsSchema = { - ...QuadValues, - uDimensions: ValueCell.create(grid.dimensions), - uMin: ValueCell.create(grid.box.min), - uDelta: ValueCell.create(grid.delta), - uWidth: ValueCell.create(width), - uNCenters: ValueCell.create(data.nCenters), - uNAlpha: ValueCell.create(data.nAlpha), - uNCoeff: ValueCell.create(data.nCoeff), - uMaxCoeffs: ValueCell.create(data.maxCoeffs), - tCenters: ValueCell.create({ width: data.nCenters, height: 1, array: data.centers }), - tInfo: ValueCell.create({ width: data.nCenters, height: 1, array: data.info }), - tCoeff: ValueCell.create({ width: data.nCoeff, height: 1, array: data.coeff }), - tAlpha: ValueCell.create({ width: data.nAlpha, height: 1, array: data.alpha }), - uLittleEndian: ValueCell.create(isLittleEndian()), - uDensity: ValueCell.create(false), - uOccupancy: ValueCell.create(0), - tCumulativeSum: ValueCell.create(ctx.namedTextures[AlphaOrbitalsTex1]) + return { + uNCenters: centerCount, + uNAlpha: baseCount, + uNCoeff: coeffCount, + uMaxCoeffs: maxCoeffs, + tCenters: { width: centerCount, height: 1, array: centers }, + tInfo: { width: centerCount, height: 1, array: info }, + tCoeff: { width: coeffCount, height: 1, array: coeff }, + tAlpha: { width: baseCount, height: 1, array: alpha }, }; - - const schema = { ...AlphaOrbitalsSchema }; - if (!ctx.isWebGL2) { - // workaround for webgl1 limitation that loop counters need to be `const` - (schema.uNCenters as any) = DefineSpec('number'); - (schema.uMaxCoeffs as any) = DefineSpec('number'); - } - - const renderItem = createComputeRenderItem(ctx, 'triangles', AlphaOrbitalsShaderCode, schema, values); - - return createComputeRenderable(renderItem, values); -} - -function getAlphaOrbitalsRenderable(ctx: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital): AlphaOrbitalsRenderable { - if (ctx.namedComputeRenderables[AlphaOrbitalsName]) { - const v = ctx.namedComputeRenderables[AlphaOrbitalsName].values as AlphaOrbitalsSchema; - - const data = createTextureData(grid, orbital); - - const [nx, ny, nz] = grid.dimensions; - const width = Math.ceil(Math.sqrt(nx * ny * nz)); - - ValueCell.update(v.uDimensions, grid.dimensions); - ValueCell.update(v.uMin, grid.box.min); - ValueCell.update(v.uDelta, grid.delta); - ValueCell.updateIfChanged(v.uWidth, width); - ValueCell.updateIfChanged(v.uNCenters, data.nCenters); - ValueCell.updateIfChanged(v.uNAlpha, data.nAlpha); - ValueCell.updateIfChanged(v.uNCoeff, data.nCoeff); - ValueCell.updateIfChanged(v.uMaxCoeffs, data.maxCoeffs); - ValueCell.update(v.tCenters, { width: data.nCenters, height: 1, array: data.centers }); - ValueCell.update(v.tInfo, { width: data.nCenters, height: 1, array: data.info }); - ValueCell.update(v.tCoeff, { width: data.nCoeff, height: 1, array: data.coeff }); - ValueCell.update(v.tAlpha, { width: data.nAlpha, height: 1, array: data.alpha }); - ValueCell.updateIfChanged(v.uLittleEndian, isLittleEndian()); - ValueCell.updateIfChanged(v.uDensity, false); - ValueCell.updateIfChanged(v.uOccupancy, 0); - ValueCell.updateIfChanged(v.tCumulativeSum, ctx.namedTextures[AlphaOrbitalsTex1]); - - ctx.namedComputeRenderables[AlphaOrbitalsName].update(); - } else { - ctx.namedComputeRenderables[AlphaOrbitalsName] = createAlphaOrbitalsRenderable(ctx, grid, orbital); - } - return ctx.namedComputeRenderables[AlphaOrbitalsName]; -} - -export function gpuComputeAlphaOrbitalsGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbital: AlphaOrbital) { - const [nx, ny, nz] = grid.dimensions; - const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbital); - const width = renderable.values.uWidth.ref.value; - - const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName]; - webgl.namedTextures[AlphaOrbitalsTex0].define(width, width); - webgl.namedTextures[AlphaOrbitalsTex0].attachFramebuffer(framebuffer, 'color0'); - - const { gl, state } = webgl; - framebuffer.bind(); - gl.viewport(0, 0, width, width); - gl.scissor(0, 0, width, width); - state.disable(gl.SCISSOR_TEST); - state.disable(gl.BLEND); - state.disable(gl.DEPTH_TEST); - state.depthMask(false); - renderable.render(); - - const array = new Uint8Array(width * width * 4); - webgl.readPixels(0, 0, width, width, array); - return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz); -} - -export function canComputeAlphaOrbitalsOnGPU(webgl?: WebGLContext) { - return !!webgl?.extensions.textureFloat; -} - -export async function gpuComputeAlphaOrbitalsDensityGridValues(webgl: WebGLContext, grid: CubeGridInfo, orbitals: AlphaOrbital[], ctx: RuntimeContext) { - await ctx.update({ message: 'Initializing...', isIndeterminate: true }); - - const [nx, ny, nz] = grid.dimensions; - const renderable = getAlphaOrbitalsRenderable(webgl, grid, orbitals[0]); - const width = renderable.values.uWidth.ref.value; - - if (!webgl.namedFramebuffers[AlphaOrbitalsName]) { - webgl.namedFramebuffers[AlphaOrbitalsName] = webgl.resources.framebuffer(); - } - const framebuffer = webgl.namedFramebuffers[AlphaOrbitalsName]; - const tex = [webgl.namedTextures[AlphaOrbitalsTex0], webgl.namedTextures[AlphaOrbitalsTex1]]; - - tex[0].define(width, width); - tex[1].define(width, width); - - const values = renderable.values as AlphaOrbitalsSchema; - const { gl, state } = webgl; - - gl.viewport(0, 0, width, width); - gl.scissor(0, 0, width, width); - state.disable(gl.SCISSOR_TEST); - state.disable(gl.BLEND); - state.disable(gl.DEPTH_TEST); - state.depthMask(false); - - gl.clearColor(0, 0, 0, 0); - - tex[0].attachFramebuffer(framebuffer, 'color0'); - gl.clear(gl.COLOR_BUFFER_BIT); - - tex[1].attachFramebuffer(framebuffer, 'color0'); - gl.clear(gl.COLOR_BUFFER_BIT); - - ValueCell.update(values.uDensity, true); - - const nonZero = orbitals.filter(o => o.occupancy !== 0); - await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: nonZero.length }); - for (let i = 0; i < nonZero.length; i++) { - const alpha = getNormalizedAlpha(grid.params.basis, nonZero[i].alpha, grid.params.sphericalOrder); - - ValueCell.update(values.uOccupancy, nonZero[i].occupancy); - ValueCell.update(values.tCumulativeSum, tex[(i + 1) % 2]); - ValueCell.update(values.tAlpha, { width: alpha.length, height: 1, array: alpha }); - tex[i % 2].attachFramebuffer(framebuffer, 'color0'); - gl.viewport(0, 0, width, width); - gl.scissor(0, 0, width, width); - state.disable(gl.SCISSOR_TEST); - state.disable(gl.BLEND); - state.disable(gl.DEPTH_TEST); - state.depthMask(false); - renderable.update(); - renderable.render(); - - if (i !== nonZero.length - 1 && ctx.shouldUpdate) { - await ctx.update({ current: i + 1 }); - } - } - - const array = new Uint8Array(width * width * 4); - webgl.readPixels(0, 0, width, width, array); - - return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz); } \ No newline at end of file diff --git a/src/extensions/alpha-orbitals/gpu/shader.frag.ts b/src/extensions/alpha-orbitals/gpu/shader.frag.ts index 11e63673af632d879dbbbbdb3de9c675281dc3ad..e3c53bf68bcf85a3d0f859af86a6cab80863c507 100644 --- a/src/extensions/alpha-orbitals/gpu/shader.frag.ts +++ b/src/extensions/alpha-orbitals/gpu/shader.frag.ts @@ -4,165 +4,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -export default ` -precision highp float; -precision highp int; -precision highp sampler2D; - -uniform vec2 uQuadShift; -uniform vec3 uDimensions; -uniform vec3 uMin; -uniform vec3 uDelta; - -uniform sampler2D tCenters; -uniform sampler2D tInfo; -uniform sampler2D tCoeff; -uniform sampler2D tAlpha; - -uniform float uWidth; - -#ifndef uNCenters - uniform int uNCenters; -#endif - -uniform int uNCoeff; -uniform int uNAlpha; - -uniform bool uDensity; -uniform float uOccupancy; -uniform sampler2D tCumulativeSum; - -uniform bool uLittleEndian; - -////////////////////////////////////////////////////////// - -// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba -// MIT License, Copyright (c) 2020 Equinor - -float shiftRight (float v, float amt) { - v = floor(v) + 0.5; - return floor(v / exp2(amt)); -} -float shiftLeft (float v, float amt) { - return floor(v * exp2(amt) + 0.5); -} -float maskLast (float v, float bits) { - return mod(v, shiftLeft(1.0, bits)); -} -float extractBits (float num, float from, float to) { - from = floor(from + 0.5); to = floor(to + 0.5); - return maskLast(shiftRight(num, from), to - from); -} - -vec4 floatToRgba(float texelFloat) { - if (texelFloat == 0.0) return vec4(0, 0, 0, 0); - float sign = texelFloat > 0.0 ? 0.0 : 1.0; - texelFloat = abs(texelFloat); - float exponent = floor(log2(texelFloat)); - float biased_exponent = exponent + 127.0; - float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0; - float t = biased_exponent / 2.0; - float last_bit_of_biased_exponent = fract(t) * 2.0; - float remaining_bits_of_biased_exponent = floor(t); - float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0; - float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0; - float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0; - float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0; - return ( - uLittleEndian - ? vec4(byte4, byte3, byte2, byte1) - : vec4(byte1, byte2, byte3, byte4) - ); -} - -/////////////////////////////////////////////////////// - -// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float -// BSD 3-Clause License -// -// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved. -// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. -// -// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, -// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. -// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, -// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, -// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, -// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED -// OF THE POSSIBILITY OF SUCH DAMAGE. - -ivec4 floatsToBytes(vec4 inputFloats) { - ivec4 bytes = ivec4(inputFloats * 255.0); - return ( - uLittleEndian - ? bytes.abgr - : bytes - ); -} - -// Break the four bytes down into an array of 32 bits. -void bytesToBits(const in ivec4 bytes, out bool bits[32]) { - for (int channelIndex = 0; channelIndex < 4; ++channelIndex) { - float acc = float(bytes[channelIndex]); - for (int indexInByte = 7; indexInByte >= 0; --indexInByte) { - float powerOfTwo = exp2(float(indexInByte)); - bool bit = acc >= powerOfTwo; - bits[channelIndex * 8 + (7 - indexInByte)] = bit; - acc = mod(acc, powerOfTwo); - } - } -} - -// Compute the exponent of the 32-bit float. -float getExponent(bool bits[32]) { - const int startIndex = 1; - const int bitStringLength = 8; - const int endBeforeIndex = startIndex + bitStringLength; - float acc = 0.0; - int pow2 = bitStringLength - 1; - for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) { - acc += float(bits[bitIndex]) * exp2(float(pow2--)); - } - return acc; -} - -// Compute the mantissa of the 32-bit float. -float getMantissa(bool bits[32], bool subnormal) { - const int startIndex = 9; - const int bitStringLength = 23; - const int endBeforeIndex = startIndex + bitStringLength; - // Leading/implicit/hidden bit convention: - // If the number is not subnormal (with exponent 0), we add a leading 1 digit. - float acc = float(!subnormal) * exp2(float(bitStringLength)); - int pow2 = bitStringLength - 1; - for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) { - acc += float(bits[bitIndex]) * exp2(float(pow2--)); - } - return acc; -} - -// Parse the float from its 32 bits. -float bitsToFloat(bool bits[32]) { - float signBit = float(bits[0]) * -2.0 + 1.0; - float exponent = getExponent(bits); - bool subnormal = abs(exponent - 0.0) < 0.01; - float mantissa = getMantissa(bits, subnormal); - float exponentBias = 127.0; - return signBit * mantissa * exp2(exponent - exponentBias - 23.0); -} - -float rgbaToFloat(vec4 texelRGBA) { - ivec4 rgbaBytes = floatsToBytes(texelRGBA); - bool bits[32]; - bytesToBits(rgbaBytes, bits); - return bitsToFloat(bits); -} - -/////////////////////////////////////////////////////// - +export const UTILS = ` float L1(vec3 p, float a0, float a1, float a2) { return a0 * p.z + a1 * p.x + a2 * p.y; } @@ -213,12 +55,10 @@ float L4(vec3 p, float a0, float a1, float a2, float a3, float a4, float a5, flo } float alpha(float offset, float f) { - #ifdef uMaxCoeffs + #ifdef WEBGL1 // in webgl1, the value is in the alpha channel! return texture2D(tAlpha, vec2(offset * f, 0.5)).a; - #endif - - #ifndef uMaxCoeffs + #else return texture2D(tAlpha, vec2(offset * f, 0.5)).x; #endif } @@ -249,7 +89,7 @@ float Y(int L, vec3 X, float aO, float fA) { return 0.0; } -#ifndef uMaxCoeffs +#ifndef WEBGL1 float R(float R2, int start, int end, float fCoeff) { float gauss = 0.0; for (int i = start; i < end; i++) { @@ -258,9 +98,7 @@ float Y(int L, vec3 X, float aO, float fA) { } return gauss; } -#endif - -#ifdef uMaxCoeffs +#else float R(float R2, int start, int end, float fCoeff) { float gauss = 0.0; int o = start; @@ -274,28 +112,13 @@ float Y(int L, vec3 X, float aO, float fA) { return gauss; } #endif +`; -float intDiv(float a, float b) { return float(int(a) / int(b)); } -float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } - -void main(void) { - float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth; - - // axis order fast to slow Z, Y, X - // TODO: support arbitrary axis orders? - float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z); - float j = intMod(kk, uDimensions.y); - float i = intDiv(kk, uDimensions.y); - - vec3 xyz = uMin + uDelta * vec3(i, j, k); - +export const MAIN = ` float fCenter = 1.0 / float(uNCenters - 1); float fCoeff = 1.0 / float(uNCoeff - 1); float fA = 1.0 / float(uNAlpha - 1); - // gl_FragColor = floatToRgba(offset); - // return; - float v = 0.0; for (int i = 0; i < uNCenters; i++) { @@ -319,13 +142,4 @@ void main(void) { v += R(R2, coeffStart, coeffEnd, fCoeff) * Y(L, X, aO, fA); } - - - if (uDensity) { - float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth))); - gl_FragColor = floatToRgba(current + uOccupancy * v * v); - } else { - gl_FragColor = floatToRgba(v); - } -} `; \ No newline at end of file diff --git a/src/extensions/alpha-orbitals/orbitals.ts b/src/extensions/alpha-orbitals/orbitals.ts index a55a5e190b914fe422db6250422d440bde3054bb..4235852da5f5e5339cbeb831c05271a20505c620 100644 --- a/src/extensions/alpha-orbitals/orbitals.ts +++ b/src/extensions/alpha-orbitals/orbitals.ts @@ -7,11 +7,14 @@ */ import { sortArray } from '../../mol-data/util'; +import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d'; import { WebGLContext } from '../../mol-gl/webgl/context'; import { Task } from '../../mol-task'; import { sphericalCollocation } from './collocation'; import { AlphaOrbital, createGrid, CubeGrid, CubeGridComputationParams, initCubeGrid } from './data-model'; -import { canComputeAlphaOrbitalsOnGPU, gpuComputeAlphaOrbitalsGridValues } from './gpu/compute'; +import { gpuComputeAlphaOrbitalsGridValues } from './gpu/compute'; + +// setDebugMode(true); export function createSphericalCollocationGrid( params: CubeGridComputationParams, orbital: AlphaOrbital, webgl?: WebGLContext @@ -20,9 +23,9 @@ export function createSphericalCollocationGrid( const cubeGrid = initCubeGrid(params); let matrix: Float32Array; - if (canComputeAlphaOrbitalsOnGPU(webgl)) { + if (canComputeGrid3dOnGPU(webgl)) { // console.time('gpu'); - matrix = gpuComputeAlphaOrbitalsGridValues(webgl!, cubeGrid, orbital); + matrix = await gpuComputeAlphaOrbitalsGridValues(ctx, webgl!, cubeGrid, orbital); // console.timeEnd('gpu'); } else { // console.time('cpu'); diff --git a/src/mol-gl/compute/grid3d.ts b/src/mol-gl/compute/grid3d.ts new file mode 100644 index 0000000000000000000000000000000000000000..818786babd624bdbcfea04bd0257a449062522e9 --- /dev/null +++ b/src/mol-gl/compute/grid3d.ts @@ -0,0 +1,223 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +import { RenderableSchema, Values, UnboxedValues, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema'; +import { WebGLContext } from '../webgl/context'; +import { getRegularGrid3dDelta, RegularGrid3d } from '../../mol-math/geometry/common'; +import shader_template from '../shader/util/grid3d-template.frag'; +import quad_vert from '../shader/quad.vert'; +import { ShaderCode } from '../shader-code'; +import { UUID, ValueCell } from '../../mol-util'; +import { objectForEach } from '../../mol-util/object'; +import { getUniformGlslType, isUniformValueScalar } from '../webgl/uniform'; +import { QuadSchema, QuadValues } from './util'; +import { createComputeRenderItem } from '../webgl/render-item'; +import { createComputeRenderable } from '../renderable'; +import { isLittleEndian } from '../../mol-util/is-little-endian'; +import { RuntimeContext } from '../../mol-task'; + +export function canComputeGrid3dOnGPU(webgl?: WebGLContext) { + return !!webgl?.extensions.textureFloat; +} + +export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS> { + schema: S, + // indicate which params are loop bounds for WebGL1 compat + loopBounds?: (keyof S)[] + utilCode?: string, + mainCode: string, + returnCode: string, + + values(params: P, grid: RegularGrid3d): UnboxedValues<S>, + + cumulative?: { + states(params: P): CS[], + update(params: P, state: CS, values: Values<S>): void, + // call gl.readPixes every 'yieldPeriod' states to split the computation + // into multiple parts, if not set, the computation will be synchronous + yieldPeriod?: number + } +} + +const FrameBufferName = 'grid3d-computable' as const; +const Texture0Name = 'grid3d-computable-0' as const; +const Texture1Name = 'grid3d-computable-1' as const; + +const SchemaBase = { + ...QuadSchema, + uDimensions: UniformSpec('v3'), + uMin: UniformSpec('v3'), + uDelta: UniformSpec('v3'), + uWidth: UniformSpec('f'), + uLittleEndian: UniformSpec('b'), +}; + +const CumulativeSumSchema = { + tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest') +}; + +export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) { + const id = UUID.create22(); + + const uniforms: string[] = []; + + objectForEach(spec.schema, (u, k) => { + if (u.type === 'define') return; + if (u.kind.indexOf('[]') >= 0) throw new Error('array uniforms are not supported'); + const isBound = (spec.loopBounds?.indexOf(k) ?? -1) >= 0; + if (isBound) uniforms.push(`#ifndef ${k}`); + if (u.type === 'uniform') uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${k};`); + else if (u.type === 'texture') uniforms.push(`uniform sampler2D ${k};`); + if (isBound) uniforms.push(`#endif`); + }); + + const code = shader_template + .replace('{UNIFORMS}', uniforms.join('\n')) + .replace('{UTILS}', spec.utilCode ?? '') + .replace('{MAIN}', spec.mainCode) + .replace('{RETURN}', spec.returnCode); + + const shader = ShaderCode(id, quad_vert, code); + + return async (ctx: RuntimeContext, webgl: WebGLContext, grid: RegularGrid3d, params: P) => { + const schema: RenderableSchema = { + ...SchemaBase, + ...(spec.cumulative ? CumulativeSumSchema : {}), + ...spec.schema, + }; + + if (!webgl.isWebGL2) { + if (spec.loopBounds) { + for (const b of spec.loopBounds) { + (schema as any)[b] = DefineSpec('number'); + } + } + (schema as any)['WEBGL1'] = DefineSpec('boolean'); + } + + if (spec.cumulative) { + (schema as any)['CUMULATIVE'] = DefineSpec('boolean'); + } + + if (!webgl.namedFramebuffers[FrameBufferName]) { + webgl.namedFramebuffers[FrameBufferName] = webgl.resources.framebuffer(); + } + const framebuffer = webgl.namedFramebuffers[FrameBufferName]; + + if (!webgl.namedTextures[Texture0Name]) { + webgl.namedTextures[Texture0Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); + } + if (spec.cumulative && !webgl.namedTextures[Texture1Name]) { + webgl.namedTextures[Texture1Name] = webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest'); + } + + const tex = [webgl.namedTextures[Texture0Name], webgl.namedTextures[Texture1Name]]; + + const [nx, ny, nz] = grid.dimensions; + const uWidth = Math.ceil(Math.sqrt(nx * ny * nz)); + + const values: UnboxedValues<S & typeof SchemaBase> = { + uDimensions: grid.dimensions, + uMin: grid.box.min, + uDelta: getRegularGrid3dDelta(grid), + uWidth, + uLittleEndian: isLittleEndian(), + ...spec.values(params, grid) + } as any; + + if (!webgl.isWebGL2) { + (values as any).WEBGL1 = true; + } + if (spec.cumulative) { + (values as any).tCumulativeSum = tex[0]; + (values as any).CUMULATIVE = true; + } + + let renderable = webgl.namedComputeRenderables[id]; + let cells: RenderableValues; + if (renderable) { + cells = renderable.values as RenderableValues; + objectForEach(values, (c, k) => { + const s = schema[k]; + if (s?.type === 'value' || s?.type === 'attribute') return; + + if (!s || !isUniformValueScalar(s.kind as any)) { + ValueCell.update(cells[k], c); + } else { + ValueCell.updateIfChanged(cells[k], c); + } + }); + } else { + cells = {} as any; + objectForEach(QuadValues, (v, k) => (cells as any)[k] = v); + objectForEach(values, (v, k) => (cells as any)[k] = ValueCell.create(v)); + renderable = createComputeRenderable(createComputeRenderItem(webgl, 'triangles', shader, schema, cells), cells); + } + + const array = new Uint8Array(uWidth * uWidth * 4); + if (spec.cumulative) { + const { gl } = webgl; + + const states = spec.cumulative.states(params); + + tex[0].define(uWidth, uWidth); + tex[1].define(uWidth, uWidth); + + resetGl(webgl, uWidth); + gl.clearColor(0, 0, 0, 0); + + tex[0].attachFramebuffer(framebuffer, 'color0'); + gl.clear(gl.COLOR_BUFFER_BIT); + + tex[1].attachFramebuffer(framebuffer, 'color0'); + gl.clear(gl.COLOR_BUFFER_BIT); + + if (spec.cumulative.yieldPeriod) { + await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length }); + } + + const yieldPeriod = Math.max(1, spec.cumulative.yieldPeriod ?? 1 | 0); + + for (let i = 0; i < states.length; i++) { + ValueCell.update(cells.tCumulativeSum, tex[(i + 1) % 2]); + tex[i % 2].attachFramebuffer(framebuffer, 'color0'); + resetGl(webgl, uWidth); + spec.cumulative.update(params, states[i], cells as any); + renderable.update(); + renderable.render(); + + if (spec.cumulative.yieldPeriod && i !== states.length - 1) { + if (i % yieldPeriod === yieldPeriod - 1) { + webgl.readPixels(0, 0, 1, 1, array); + } + if (ctx.shouldUpdate) { + await ctx.update({ current: i + 1 }); + } + } + } + } else { + tex[0].define(uWidth, uWidth); + tex[0].attachFramebuffer(framebuffer, 'color0'); + framebuffer.bind(); + resetGl(webgl, uWidth); + renderable.update(); + renderable.render(); + } + + webgl.readPixels(0, 0, uWidth, uWidth, array); + return new Float32Array(array.buffer, array.byteOffset, nx * ny * nz); + }; +} + +function resetGl(webgl: WebGLContext, w: number) { + const { gl, state } = webgl; + gl.viewport(0, 0, w, w); + gl.scissor(0, 0, w, w); + state.disable(gl.SCISSOR_TEST); + state.disable(gl.BLEND); + state.disable(gl.DEPTH_TEST); + state.depthMask(false); +} \ No newline at end of file diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 98ab67b678f88473079161f21daae152e2462044..abcab8569ed6891bcc3439e4811fa80f1377e306 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -29,6 +29,7 @@ export type ValueKind = keyof ValueKindType export type KindValue = UniformKindValue & DataTypeArrayType & TextureKindValue & ValueKindType export type Values<S extends RenderableSchema> = { readonly [k in keyof S]: ValueCell<KindValue[S[k]['kind']]> } +export type UnboxedValues<S extends RenderableSchema> = { readonly [k in keyof S]: KindValue[S[k]['kind']] } export function splitValues(schema: RenderableSchema, values: RenderableValues) { const attributeValues: AttributeValues = {}; @@ -102,6 +103,7 @@ export type RenderableSchema = { } export type RenderableValues = { readonly [k: string]: ValueCell<any> } + // export const GlobalUniformSchema = { diff --git a/src/mol-gl/shader/util/grid3d-template.frag.ts b/src/mol-gl/shader/util/grid3d-template.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..336586cbbd015ba985fbac174e3b254ac2bc3b0f --- /dev/null +++ b/src/mol-gl/shader/util/grid3d-template.frag.ts @@ -0,0 +1,181 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author David Sehnal <david.sehnal@gmail.com> + */ + +export default ` +precision highp float; +precision highp int; +precision highp sampler2D; + +uniform vec2 uQuadShift; +uniform vec3 uDimensions; +uniform vec3 uMin; +uniform vec3 uDelta; +uniform bool uLittleEndian; +uniform float uWidth; + +#ifdef CUMULATIVE +uniform sampler2D tCumulativeSum; +#endif + +{UNIFORMS} + +{UTILS} + +////////////////////////////////////////////////////////// + +// floatToRgba adapted from https://github.com/equinor/glsl-float-to-rgba +// MIT License, Copyright (c) 2020 Equinor + +float shiftRight (float v, float amt) { + v = floor(v) + 0.5; + return floor(v / exp2(amt)); +} +float shiftLeft (float v, float amt) { + return floor(v * exp2(amt) + 0.5); +} +float maskLast (float v, float bits) { + return mod(v, shiftLeft(1.0, bits)); +} +float extractBits (float num, float from, float to) { + from = floor(from + 0.5); to = floor(to + 0.5); + return maskLast(shiftRight(num, from), to - from); +} + +vec4 floatToRgba(float texelFloat) { + if (texelFloat == 0.0) return vec4(0, 0, 0, 0); + float sign = texelFloat > 0.0 ? 0.0 : 1.0; + texelFloat = abs(texelFloat); + float exponent = floor(log2(texelFloat)); + float biased_exponent = exponent + 127.0; + float fraction = ((texelFloat / exp2(exponent)) - 1.0) * 8388608.0; + float t = biased_exponent / 2.0; + float last_bit_of_biased_exponent = fract(t) * 2.0; + float remaining_bits_of_biased_exponent = floor(t); + float byte4 = extractBits(fraction, 0.0, 8.0) / 255.0; + float byte3 = extractBits(fraction, 8.0, 16.0) / 255.0; + float byte2 = (last_bit_of_biased_exponent * 128.0 + extractBits(fraction, 16.0, 23.0)) / 255.0; + float byte1 = (sign * 128.0 + remaining_bits_of_biased_exponent) / 255.0; + return ( + uLittleEndian + ? vec4(byte4, byte3, byte2, byte1) + : vec4(byte1, byte2, byte3, byte4) + ); +} + +/////////////////////////////////////////////////////// + +// rgbaToFloat adapted from https://github.com/ihmeuw/glsl-rgba-to-float +// BSD 3-Clause License +// +// Copyright (c) 2019, Institute for Health Metrics and Evaluation All rights reserved. +// Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: +// - Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. +// - Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. +// - Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, +// INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +// IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, +// OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, +// OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +// OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED +// OF THE POSSIBILITY OF SUCH DAMAGE. + +#ifdef CUMULATIVE + +ivec4 floatsToBytes(vec4 inputFloats) { + ivec4 bytes = ivec4(inputFloats * 255.0); + return ( + uLittleEndian + ? bytes.abgr + : bytes + ); +} + +// Break the four bytes down into an array of 32 bits. +void bytesToBits(const in ivec4 bytes, out bool bits[32]) { + for (int channelIndex = 0; channelIndex < 4; ++channelIndex) { + float acc = float(bytes[channelIndex]); + for (int indexInByte = 7; indexInByte >= 0; --indexInByte) { + float powerOfTwo = exp2(float(indexInByte)); + bool bit = acc >= powerOfTwo; + bits[channelIndex * 8 + (7 - indexInByte)] = bit; + acc = mod(acc, powerOfTwo); + } + } +} + +// Compute the exponent of the 32-bit float. +float getExponent(bool bits[32]) { + const int startIndex = 1; + const int bitStringLength = 8; + const int endBeforeIndex = startIndex + bitStringLength; + float acc = 0.0; + int pow2 = bitStringLength - 1; + for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) { + acc += float(bits[bitIndex]) * exp2(float(pow2--)); + } + return acc; +} + +// Compute the mantissa of the 32-bit float. +float getMantissa(bool bits[32], bool subnormal) { + const int startIndex = 9; + const int bitStringLength = 23; + const int endBeforeIndex = startIndex + bitStringLength; + // Leading/implicit/hidden bit convention: + // If the number is not subnormal (with exponent 0), we add a leading 1 digit. + float acc = float(!subnormal) * exp2(float(bitStringLength)); + int pow2 = bitStringLength - 1; + for (int bitIndex = startIndex; bitIndex < endBeforeIndex; ++bitIndex) { + acc += float(bits[bitIndex]) * exp2(float(pow2--)); + } + return acc; +} + +// Parse the float from its 32 bits. +float bitsToFloat(bool bits[32]) { + float signBit = float(bits[0]) * -2.0 + 1.0; + float exponent = getExponent(bits); + bool subnormal = abs(exponent - 0.0) < 0.01; + float mantissa = getMantissa(bits, subnormal); + float exponentBias = 127.0; + return signBit * mantissa * exp2(exponent - exponentBias - 23.0); +} + +float rgbaToFloat(vec4 texelRGBA) { + ivec4 rgbaBytes = floatsToBytes(texelRGBA); + bool bits[32]; + bytesToBits(rgbaBytes, bits); + return bitsToFloat(bits); +} + +#endif + +/////////////////////////////////////////////////////// + +float intDiv(float a, float b) { return float(int(a) / int(b)); } +float intMod(float a, float b) { return a - b * float(int(a) / int(b)); } + +void main(void) { + float offset = floor(gl_FragCoord.x) + floor(gl_FragCoord.y) * uWidth; + + // axis order fast to slow Z, Y, X + // TODO: support arbitrary axis orders? + float k = intMod(offset, uDimensions.z), kk = intDiv(offset, uDimensions.z); + float j = intMod(kk, uDimensions.y); + float i = intDiv(kk, uDimensions.y); + + vec3 xyz = uMin + uDelta * vec3(i, j, k); + + {MAIN} + + #ifdef CUMULATIVE + float current = rgbaToFloat(texture2D(tCumulativeSum, gl_FragCoord.xy / vec2(uWidth, uWidth))); + #endif + gl_FragColor = floatToRgba({RETURN}); +} +`; \ No newline at end of file diff --git a/src/mol-gl/webgl/uniform.ts b/src/mol-gl/webgl/uniform.ts index d333fd13b88965d8f12c8915a73c1f63ac4644f4..aa1e43b62c4c141c35530318b96d7450d72aaa51 100644 --- a/src/mol-gl/webgl/uniform.ts +++ b/src/mol-gl/webgl/uniform.ts @@ -79,4 +79,30 @@ export function getUniformSetters(schema: RenderableSchema) { } }); return setters; +} + +export function getUniformGlslType(kind: UniformKind): string { + switch (kind) { + case 'f': return 'float'; + case 'i': return 'int'; + case 't': return 'sampler2D'; + case 'b': return 'bool'; + case 'v2': return 'vec2'; + case 'v3': return 'vec3'; + case 'v4': return 'vec4'; + case 'm3': return 'mat3'; + case 'm4': return 'mat4'; + } + throw new Error(`${kind} has no primitive GLSL type.`); +} + +export function isUniformValueScalar(kind: UniformKind): boolean { + switch (kind) { + case 'f': + case 'i': + case 'b': + return true; + default: + return false; + } } \ No newline at end of file diff --git a/src/mol-math/geometry/common.ts b/src/mol-math/geometry/common.ts index 16a09e84d2dbb8419bef27a166b9067b56da0d80..ac298f38d706486b9504785534bebe2c76c35926 100644 --- a/src/mol-math/geometry/common.ts +++ b/src/mol-math/geometry/common.ts @@ -37,6 +37,15 @@ export type DensityTextureData = { gridTexScale: Vec2 } +export interface RegularGrid3d { + box: Box3D, + dimensions: Vec3 +} + +export function getRegularGrid3dDelta({ box, dimensions }: RegularGrid3d) { + return Vec3.div(Vec3(), Box3D.size(Vec3(), box), Vec3.subScalar(Vec3(), dimensions, 1)); +} + export function fillGridDim(length: number, start: number, step: number) { const a = new Float32Array(length); for (let i = 0; i < a.length; i++) { diff --git a/src/mol-plugin-ui/controls/parameters.tsx b/src/mol-plugin-ui/controls/parameters.tsx index c21ac30f1d7e65a4abd617df1bae3898976ae49e..885e96d8045f16d69612e18f6ec29cb04dd1db63 100644 --- a/src/mol-plugin-ui/controls/parameters.tsx +++ b/src/mol-plugin-ui/controls/parameters.tsx @@ -186,6 +186,7 @@ function controlFor(param: PD.Any): ParamControl | undefined { case 'file-list': return FileListControl; case 'select': return SelectControl; case 'value-ref': return ValueRefControl; + case 'data-ref': return void 0; case 'text': return TextControl; case 'interval': return typeof param.min !== 'undefined' && typeof param.max !== 'undefined' ? BoundedIntervalControl : IntervalControl; diff --git a/src/mol-state/state.ts b/src/mol-state/state.ts index 3e65f8a033d048aab9b1bcfcaee2ff1c9142a9f5..5412004ec782bad22477a5315b2d246e0b9862a9 100644 --- a/src/mol-state/state.ts +++ b/src/mol-state/state.ts @@ -328,6 +328,8 @@ class State { const oldTree = this._tree; this._tree = _tree; + const cells = this.cells; + const ctx: UpdateContext = { parent: this, editInfo: StateBuilder.is(tree) ? tree.editInfo : void 0, @@ -346,7 +348,9 @@ class State { changed: false, hadError: false, wasAborted: false, - newCurrent: void 0 + newCurrent: void 0, + + getCellData: ref => cells.get(ref)!.obj?.data }; this.errorFree = true; @@ -454,7 +458,9 @@ interface UpdateContext { changed: boolean, hadError: boolean, wasAborted: boolean, - newCurrent?: Ref + newCurrent?: Ref, + + getCellData: (ref: string) => any } async function update(ctx: UpdateContext) { @@ -841,7 +847,7 @@ function resolveParams(ctx: UpdateContext, transform: StateTransform, src: State (transform.params as any) = transform.params ? assignIfUndefined(transform.params, defaultValues) : defaultValues; - ParamDefinition.resolveValueRefs(definition, transform.params); + ParamDefinition.resolveRefs(definition, transform.params, ctx.getCellData); return { definition, values: transform.params }; } diff --git a/src/mol-util/param-definition.ts b/src/mol-util/param-definition.ts index 1a441a9d8120c7ddb39518b0ea2258f1ea514e75..7d66032de13e696e3d090a09386144e6a6e5d569 100644 --- a/src/mol-util/param-definition.ts +++ b/src/mol-util/param-definition.ts @@ -296,6 +296,13 @@ export namespace ParamDefinition { return setInfo<ValueRef<T>>({ type: 'value-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any }, getOptions, resolveRef }, info); } + export interface DataRef<T = any> extends Base<{ ref: string, getValue: () => T }> { + type: 'data-ref' + } + export function DataRef<T>(info?: Info & { defaultRef?: string }) { + return setInfo<DataRef<T>>({ type: 'data-ref', defaultValue: { ref: info?.defaultRef ?? '', getValue: unsetGetValue as any } }, info); + } + export interface Converted<T, C> extends Base<T> { type: 'converted', converted: Any, @@ -329,7 +336,7 @@ export namespace ParamDefinition { export type Any = | Value<any> | Select<any> | MultiSelect<any> | BooleanParam | Text | Color | Vec3 | Mat4 | Numeric | FileParam | UrlParam | FileListParam | Interval | LineGraph - | ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef + | ColorList | Group<any> | Mapped<any> | Converted<any, any> | Conditioned<any, any, any> | Script | ObjectList | ValueRef | DataRef export type Params = { [k: string]: Any } export type Values<T extends Params> = { [k in keyof T]: T[k]['defaultValue'] } @@ -360,29 +367,33 @@ export namespace ParamDefinition { return () => resolve(ref); } - function resolveRefValue(p: Any, value: any) { + function resolveRefValue(p: Any, value: any, getData: (ref: string) => any) { if (!value) return; if (p.type === 'value-ref') { const v = value as ValueRef['defaultValue']; if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); }; else v.getValue = _resolveRef(p.resolveRef, v.ref); + } else if (p.type === 'data-ref') { + const v = value as ValueRef['defaultValue']; + if (!v.ref) v.getValue = () => { throw new Error('Unset ref in ValueRef value.'); }; + else v.getValue = _resolveRef(getData, v.ref); } else if (p.type === 'group') { - resolveValueRefs(p.params, value); + resolveRefs(p.params, value, getData); } else if (p.type === 'mapped') { const v = value as NamedParams; const param = p.map(v.name); - resolveRefValue(param, v.params); + resolveRefValue(param, v.params, getData); } else if (p.type === 'object-list') { if (!hasValueRef(p.element)) return; for (const e of value) { - resolveValueRefs(p.element, e); + resolveRefs(p.element, e, getData); } } } function hasParamValueRef(p: Any) { - if (p.type === 'value-ref') { + if (p.type === 'value-ref' || p.type === 'data-ref') { return true; } else if (p.type === 'group') { if (hasValueRef(p.params)) return true; @@ -403,9 +414,9 @@ export namespace ParamDefinition { return false; } - export function resolveValueRefs(params: Params, values: any) { + export function resolveRefs(params: Params, values: any, getData: (ref: string) => any) { for (const n of Object.keys(params)) { - resolveRefValue(params[n], values?.[n]); + resolveRefValue(params[n], values?.[n], getData); } }