Browse Source

grid3d-compute util code

David Sehnal 4 years ago
parent
commit
1ce6641eb3

+ 2 - 2
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-compute';
 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).`

+ 4 - 3
src/extensions/alpha-orbitals/density.ts

@@ -5,10 +5,11 @@
  */
 
 import { sortArray } from '../../mol-data/util';
+import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d-compute';
 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.');

+ 60 - 199
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-compute';
+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);
 }

+ 7 - 193
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);
-    }
-}
 `;

+ 6 - 3
src/extensions/alpha-orbitals/orbitals.ts

@@ -7,11 +7,14 @@
  */
 
 import { sortArray } from '../../mol-data/util';
+import { canComputeGrid3dOnGPU } from '../../mol-gl/compute/grid3d-compute';
 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');

+ 50 - 14
src/mol-gl/compute/grid3d-compute.ts

@@ -4,7 +4,7 @@
  * @author David Sehnal <david.sehnal@gmail.com>
  */
 
-import { RenderableSchema, Values, KindValue, UniformSpec, TextureSpec, DefineSpec, RenderableValues } from '../renderable/schema';
+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';
@@ -19,6 +19,10 @@ 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
@@ -27,16 +31,14 @@ export interface Grid3DComputeRenderableSpec<S extends RenderableSchema, P, CS>
     mainCode: string,
     returnCode: string,
 
-    values(params: P, grid: RegularGrid3d): SchemaValues<S>,
+    values(params: P, grid: RegularGrid3d): UnboxedValues<S>,
 
     cumulative?: {
-        init(params: P, values: SchemaValues<S>): CS,
-        next(params: P, state: CS, values: Values<S>): boolean
+        states(params: P): CS[],
+        update(params: P, state: CS, values: Values<S>): void
     }
 }
 
-type SchemaValues<S extends RenderableSchema> = { readonly [k in keyof S]: KindValue[S[k]['kind']] }
-
 const FrameBufferName = 'grid3d-computable' as const;
 const Texture0Name = 'grid3d-computable-0' as const;
 const Texture1Name = 'grid3d-computable-1' as const;
@@ -54,7 +56,7 @@ const CumulativeSumSchema = {
     tCumulativeSum: TextureSpec('texture', 'rgba', 'ubyte', 'nearest')
 };
 
-export function createGrid3dComputable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
+export function createGrid3dComputeRenderable<S extends RenderableSchema, P, CS>(spec: Grid3DComputeRenderableSpec<S, P, CS>) {
     const id = UUID.create22();
 
     const uniforms: string[] = [];
@@ -64,7 +66,8 @@ export function createGrid3dComputable<S extends RenderableSchema, P, CS>(spec:
         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}`);
-        uniforms.push(`uniform ${getUniformGlslType(u.kind as any)} ${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`);
     });
 
@@ -113,8 +116,7 @@ export function createGrid3dComputable<S extends RenderableSchema, P, CS>(spec:
         const [nx, ny, nz] = grid.dimensions;
         const uWidth = Math.ceil(Math.sqrt(nx * ny * nz));
 
-        const values: SchemaValues<S & typeof SchemaBase> = {
-            ...QuadValues,
+        const values: UnboxedValues<S & typeof SchemaBase> = {
             uDimensions: grid.dimensions,
             uMin: grid.box.min,
             uDelta: getRegularGrid3dDelta(grid),
@@ -132,8 +134,9 @@ export function createGrid3dComputable<S extends RenderableSchema, P, CS>(spec:
         }
 
         let renderable = webgl.namedComputeRenderables[id];
+        let cells: RenderableValues;
         if (renderable) {
-            const cells = renderable.values as RenderableValues;
+            cells = renderable.values as RenderableValues;
             objectForEach(values, (c, k) => {
                 const s = schema[k];
                 if (s?.type === 'value' || s?.type === 'attribute') return;
@@ -145,17 +148,50 @@ export function createGrid3dComputable<S extends RenderableSchema, P, CS>(spec:
                 }
             });
         } else {
-            const cells = {} as any;
-            objectForEach(values, (v, k) => cells[k] = ValueCell.create(v));
+            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);
         }
 
+
         if (spec.cumulative) {
-            throw new Error('nyi');
+            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);
+
+            await ctx.update({ message: 'Computing...', isIndeterminate: false, current: 0, max: states.length });
+
+            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 (i !== states.length - 1 && 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();
         }
 

+ 2 - 0
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 = {