Browse Source

wip, gaussian surface & mc

- fix iso-level
- reuse gpu resources for mc (patched many memory leaks)
Alexander Rose 4 years ago
parent
commit
146022dc12

+ 54 - 30
src/mol-gl/compute/histogram-pyramid/reduction.ts

@@ -1,14 +1,14 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { createComputeRenderable, ComputeRenderable } from '../../renderable';
+import { createComputeRenderable } from '../../renderable';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
-import { Texture } from '../../../mol-gl/webgl/texture';
+import { Texture, TextureFilter, TextureFormat, TextureKind, TextureType } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
 import { QuadSchema, QuadValues } from '../util';
@@ -26,37 +26,44 @@ const HistopyramidReductionSchema = {
     uTexSize: UniformSpec('f'),
 };
 
-let HistopyramidReductionRenderable: ComputeRenderable<Values<typeof HistopyramidReductionSchema>>;
+const HistogramPyramidName = 'histogram-pyramid';
+
 function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
-    if (HistopyramidReductionRenderable) {
-        ValueCell.update(HistopyramidReductionRenderable.values.tPreviousLevel, initialTexture);
-        HistopyramidReductionRenderable.update();
-        return HistopyramidReductionRenderable;
+    if (ctx.namedComputeRenderables[HistogramPyramidName]) {
+        const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
+
+        ValueCell.update(v.tPreviousLevel, initialTexture);
+
+        ctx.namedComputeRenderables[HistogramPyramidName].update();
     } else {
-        const values: Values<typeof HistopyramidReductionSchema> = {
-            ...QuadValues,
-            tPreviousLevel: ValueCell.create(initialTexture),
-            uSize: ValueCell.create(0),
-            uTexSize: ValueCell.create(0),
-        };
-
-        const schema = { ...HistopyramidReductionSchema };
-        const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
-        const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
-
-        HistopyramidReductionRenderable = createComputeRenderable(renderItem, values);
-        return HistopyramidReductionRenderable;
+        ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, initialTexture);
     }
+    return ctx.namedComputeRenderables[HistogramPyramidName];
+}
+
+function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
+    const values: Values<typeof HistopyramidReductionSchema> = {
+        ...QuadValues,
+        tPreviousLevel: ValueCell.create(initialTexture),
+        uSize: ValueCell.create(0),
+        uTexSize: ValueCell.create(0),
+    };
+
+    const schema = { ...HistopyramidReductionSchema };
+    const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
+    const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
 }
 
 type TextureFramebuffer = { texture: Texture, framebuffer: Framebuffer }
 const LevelTexturesFramebuffers: TextureFramebuffer[] = [];
 function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
-    let textureFramebuffer  = LevelTexturesFramebuffers[level];
+    let textureFramebuffer = LevelTexturesFramebuffers[level];
     const size = Math.pow(2, level);
     if (textureFramebuffer === undefined) {
-        const texture = ctx.resources.texture('image-float32', 'rgba', 'float', 'nearest');
-        const framebuffer = ctx.resources.framebuffer();
+        const texture = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
+        const framebuffer = getFramebuffer(`level${level}`, ctx);
         texture.attachFramebuffer(framebuffer, 0);
         textureFramebuffer = { texture, framebuffer };
         textureFramebuffer.texture.define(size, size);
@@ -76,6 +83,22 @@ function setRenderingDefaults(ctx: WebGLContext) {
     state.clearColor(0, 0, 0, 0);
 }
 
+function getFramebuffer(name: string, webgl: WebGLContext): Framebuffer {
+    const _name = `${HistogramPyramidName}-${name}`;
+    if (!webgl.namedFramebuffers[_name]) {
+        webgl.namedFramebuffers[_name] = webgl.resources.framebuffer();
+    }
+    return webgl.namedFramebuffers[_name];
+}
+
+function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format: TextureFormat, type: TextureType, filter: TextureFilter): Texture {
+    const _name = `${HistogramPyramidName}-${name}`;
+    if (!webgl.namedTextures[_name]) {
+        webgl.namedTextures[_name] = webgl.resources.texture(kind, format, type, filter);
+    }
+    return webgl.namedTextures[_name];
+}
+
 export interface HistogramPyramid {
     pyramidTex: Texture
     count: number
@@ -85,7 +108,7 @@ export interface HistogramPyramid {
 }
 
 export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture, scale: Vec2): HistogramPyramid {
-    const { gl, resources } = ctx;
+    const { gl } = ctx;
 
     // printTexture(ctx, inputTexture, 2)
     if (inputTexture.getWidth() !== inputTexture.getHeight() || !isPowerOfTwo(inputTexture.getWidth())) {
@@ -95,13 +118,14 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
     // This part set the levels
     const levels = Math.ceil(Math.log(inputTexture.getWidth()) / Math.log(2));
     const maxSize = Math.pow(2, levels);
-    // console.log('levels', levels, 'maxSize', maxSize)
+    // console.log('levels', levels, 'maxSize', maxSize, 'input', inputTexture.getWidth());
 
-    const pyramidTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    const pyramidTexture = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
     pyramidTexture.define(maxSize, maxSize);
 
-    const framebuffer = resources.framebuffer();
+    const framebuffer = getFramebuffer('pyramid', ctx);
     pyramidTexture.attachFramebuffer(framebuffer, 0);
+    gl.viewport(0, 0, maxSize, maxSize);
     gl.clear(gl.COLOR_BUFFER_BIT);
 
     const levelTexturesFramebuffers: TextureFramebuffer[] = [];
@@ -120,8 +144,6 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
 
         const size = Math.pow(2, currLevel);
         // console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
-        gl.clear(gl.COLOR_BUFFER_BIT);
-        gl.viewport(0, 0, size, size);
 
         ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
         ValueCell.update(renderable.values.uTexSize, size);
@@ -130,6 +152,8 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
             renderable.update();
         }
         ctx.state.currentRenderItemId = -1;
+        gl.viewport(0, 0, size, size);
+        gl.clear(gl.COLOR_BUFFER_BIT);
         renderable.render();
 
         pyramidTexture.bind(0);

+ 32 - 25
src/mol-gl/compute/histogram-pyramid/sum.ts

@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { createComputeRenderable, ComputeRenderable } from '../../renderable';
+import { createComputeRenderable } from '../../renderable';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec } from '../../renderable/schema';
@@ -21,33 +21,32 @@ const HistopyramidSumSchema = {
     tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 };
 
-let HistopyramidSumRenderable: ComputeRenderable<Values<typeof HistopyramidSumSchema>>;
+const HistopyramidSumName = 'histopyramid-sum';
+
 function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
-    if (HistopyramidSumRenderable) {
-        ValueCell.update(HistopyramidSumRenderable.values.tTexture, texture);
-        HistopyramidSumRenderable.update();
-        return HistopyramidSumRenderable;
-    } else {
-        const values: Values<typeof HistopyramidSumSchema> = {
-            ...QuadValues,
-            tTexture: ValueCell.create(texture),
-        };
+    if (ctx.namedComputeRenderables[HistopyramidSumName]) {
+        const v = ctx.namedComputeRenderables[HistopyramidSumName].values;
 
-        const schema = { ...HistopyramidSumSchema };
-        const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
-        const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
+        ValueCell.update(v.tTexture, texture);
 
-        HistopyramidSumRenderable = createComputeRenderable(renderItem, values);
-        return HistopyramidSumRenderable;
+        ctx.namedComputeRenderables[HistopyramidSumName].update();
+    } else {
+        ctx.namedComputeRenderables[HistopyramidSumName] = createHistopyramidSumRenderable(ctx, texture);
     }
+    return ctx.namedComputeRenderables[HistopyramidSumName];
 }
 
-let SumTexture: Texture;
-function getSumTexture(ctx: WebGLContext) {
-    if (SumTexture) return SumTexture;
-    SumTexture = ctx.resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
-    SumTexture.define(1, 1);
-    return SumTexture;
+function createHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
+    const values: Values<typeof HistopyramidSumSchema> = {
+        ...QuadValues,
+        tTexture: ValueCell.create(texture),
+    };
+
+    const schema = { ...HistopyramidSumSchema };
+    const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
+    const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
+
+    return createComputeRenderable(renderItem, values);
 }
 
 function setRenderingDefaults(ctx: WebGLContext) {
@@ -68,8 +67,16 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
     const renderable = getHistopyramidSumRenderable(ctx, pyramidTopTexture);
     ctx.state.currentRenderItemId = -1;
 
-    const framebuffer = resources.framebuffer();
-    const sumTexture = getSumTexture(ctx);
+    if (!ctx.namedFramebuffers[HistopyramidSumName]) {
+        ctx.namedFramebuffers[HistopyramidSumName] = resources.framebuffer();
+    }
+    const framebuffer = ctx.namedFramebuffers[HistopyramidSumName];
+
+    if (!ctx.namedTextures[HistopyramidSumName]) {
+        ctx.namedTextures[HistopyramidSumName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+        ctx.namedTextures[HistopyramidSumName].define(1, 1);
+    }
+    const sumTexture = ctx.namedTextures[HistopyramidSumName];
     sumTexture.attachFramebuffer(framebuffer, 0);
 
     setRenderingDefaults(ctx);

+ 31 - 6
src/mol-gl/compute/marching-cubes/active-voxels.ts

@@ -30,18 +30,36 @@ const ActiveVoxelsSchema = {
     uScale: UniformSpec('v2'),
 };
 
+const ActiveVoxelsName = 'active-voxels';
+
 function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
+    if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
+        const v = ctx.namedComputeRenderables[ActiveVoxelsName].values;
+
+        ValueCell.update(v.uQuadScale, scale);
+        ValueCell.update(v.tVolumeData, volumeData);
+        ValueCell.updateIfChanged(v.uIsoValue, isoValue);
+        ValueCell.update(v.uGridDim, gridDim);
+        ValueCell.update(v.uGridTexDim, gridTexDim);
+        ValueCell.update(v.uScale, scale);
+
+        ctx.namedComputeRenderables[ActiveVoxelsName].update();
+    } else {
+        ctx.namedComputeRenderables[ActiveVoxelsName] = createActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, scale);
+    }
+    return ctx.namedComputeRenderables[ActiveVoxelsName];
+}
+
+function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
     const values: Values<typeof ActiveVoxelsSchema> = {
         ...QuadValues,
-        uQuadScale: ValueCell.create(scale),
-
         tTriCount: ValueCell.create(getTriCount()),
+
+        uQuadScale: ValueCell.create(scale),
         tVolumeData: ValueCell.create(volumeData),
         uIsoValue: ValueCell.create(isoValue),
-
         uGridDim: ValueCell.create(gridDim),
         uGridTexDim: ValueCell.create(gridTexDim),
-
         uScale: ValueCell.create(scale),
     };
 
@@ -68,10 +86,16 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
     const width = volumeData.getWidth();
     const height = volumeData.getHeight();
 
-    const framebuffer = resources.framebuffer();
+    if (!ctx.namedFramebuffers[ActiveVoxelsName]) {
+        ctx.namedFramebuffers[ActiveVoxelsName] = resources.framebuffer();
+    }
+    const framebuffer = ctx.namedFramebuffers[ActiveVoxelsName];
     framebuffer.bind();
 
-    const activeVoxelsTex = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    if (!ctx.namedTextures[ActiveVoxelsName]) {
+        ctx.namedTextures[ActiveVoxelsName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    }
+    const activeVoxelsTex = ctx.namedTextures[ActiveVoxelsName];
     activeVoxelsTex.define(width, height);
 
     const renderable = getActiveVoxelsRenderable(ctx, volumeData, gridDim, gridTexDim, isoValue, gridScale);
@@ -80,6 +104,7 @@ export function calcActiveVoxels(ctx: WebGLContext, volumeData: Texture, gridDim
     activeVoxelsTex.attachFramebuffer(framebuffer, 0);
     setRenderingDefaults(ctx);
     gl.viewport(0, 0, width, height);
+    gl.clear(gl.COLOR_BUFFER_BIT);
     renderable.render();
 
     // console.log('gridScale', gridScale, 'gridTexDim', gridTexDim, 'gridDim', gridDim)

+ 42 - 19
src/mol-gl/compute/marching-cubes/isosurface.ts

@@ -38,18 +38,46 @@ const IsosurfaceSchema = {
     uScale: UniformSpec('v2'),
 };
 
+const IsosurfaceName = 'isosurface';
+
 function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
+    if (ctx.namedComputeRenderables[IsosurfaceName]) {
+        const v = ctx.namedComputeRenderables[IsosurfaceName].values;
+
+        ValueCell.update(v.uQuadScale, Vec2.create(1, height / Math.pow(2, levels)));
+        ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
+        ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
+        ValueCell.update(v.tVolumeData, volumeData);
+
+        ValueCell.updateIfChanged(v.uIsoValue, isoValue);
+        ValueCell.updateIfChanged(v.uSize, Math.pow(2, levels));
+        ValueCell.updateIfChanged(v.uLevels, levels);
+        ValueCell.updateIfChanged(v.uCount, count);
+
+        ValueCell.update(v.uGridDim, gridDim);
+        ValueCell.update(v.uGridTexDim, gridTexDim);
+        ValueCell.update(v.uGridTransform, transform);
+        ValueCell.update(v.uScale, scale);
+
+        ctx.namedComputeRenderables[IsosurfaceName].update();
+    } else {
+        ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
+    }
+    return ctx.namedComputeRenderables[IsosurfaceName];
+}
+
+function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, height: number) {
     // console.log('uSize', Math.pow(2, levels))
     const values: Values<typeof IsosurfaceSchema> = {
         ...QuadValues,
-        uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
-
         tTriIndices: ValueCell.create(getTriIndices()),
+
+        uQuadScale: ValueCell.create(Vec2.create(1, height / Math.pow(2, levels))),
         tActiveVoxelsPyramid: ValueCell.create(activeVoxelsPyramid),
         tActiveVoxelsBase: ValueCell.create(activeVoxelsBase),
         tVolumeData: ValueCell.create(volumeData),
-        uIsoValue: ValueCell.create(isoValue),
 
+        uIsoValue: ValueCell.create(isoValue),
         uSize: ValueCell.create(Math.pow(2, levels)),
         uLevels: ValueCell.create(levels),
         uCount: ValueCell.create(count),
@@ -57,7 +85,6 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
         uGridDim: ValueCell.create(gridDim),
         uGridTexDim: ValueCell.create(gridTexDim),
         uGridTransform: ValueCell.create(transform),
-
         uScale: ValueCell.create(scale),
     };
 
@@ -86,27 +113,23 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     // console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
     // console.log('iso volumeData', volumeData)
 
-    const framebuffer = resources.framebuffer();
+    if (!ctx.namedFramebuffers[IsosurfaceName]) {
+        ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
+    }
+    const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
 
-    let needsClear = false;
+    const w = pyramidTex.getWidth();
+    const h = pyramidTex.getHeight();
 
     if (!vertexGroupTexture) {
         vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
-        vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
-    } else if (vertexGroupTexture.getWidth() !== pyramidTex.getWidth() || vertexGroupTexture.getHeight() !== pyramidTex.getHeight()) {
-        vertexGroupTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
-    } else {
-        needsClear = true;
     }
+    vertexGroupTexture.define(w, h);
 
     if (!normalTexture) {
         normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
-        normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
-    } else if (normalTexture.getWidth() !== pyramidTex.getWidth() || normalTexture.getHeight() !== pyramidTex.getHeight()) {
-        normalTexture.define(pyramidTex.getWidth(), pyramidTex.getHeight());
-    } else {
-        needsClear = true;
     }
+    normalTexture.define(w, h);
 
     // const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
     // infoTex.define(pyramidTex.width, pyramidTex.height)
@@ -147,11 +170,11 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
     ]);
 
     setRenderingDefaults(ctx);
-    gl.viewport(0, 0, pyramidTex.getWidth(), pyramidTex.getHeight());
-    if (needsClear) gl.clear(gl.COLOR_BUFFER_BIT);
+    gl.viewport(0, 0, w, h);
+    gl.clear(gl.COLOR_BUFFER_BIT);
     renderable.render();
 
-    gl.finish();
+    gl.flush();
 
     // const vgt = readTexture(ctx, vertexGroupTexture, pyramidTex.width, height)
     // console.log('vertexGroupTexture', vgt.array.subarray(0, 4 * count))

+ 11 - 3
src/mol-math/geometry/gaussian-density.ts

@@ -4,9 +4,9 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { Box3D } from '../geometry';
+import { Box3D, DensityData, DensityTextureData } from '../geometry';
 import { RuntimeContext, Task } from '../../mol-task';
-import { PositionData, DensityData } from './common';
+import { PositionData } from './common';
 import { GaussianDensityCPU } from './gaussian-density/cpu';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { Texture } from '../../mol-gl/webgl/texture';
@@ -33,13 +33,21 @@ export const DefaultGaussianDensityProps = {
 };
 export type GaussianDensityProps = typeof DefaultGaussianDensityProps
 
+export type GaussianDensityData = {
+    radiusFactor: number
+} & DensityData
+
+export type GaussianDensityTextureData = {
+    radiusFactor: number
+} & DensityTextureData
+
 export function computeGaussianDensity(position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps, webgl?: WebGLContext) {
     return Task.create('Gaussian Density', async ctx => {
         return await GaussianDensity(ctx, position, box, radius, props, webgl);
     });
 }
 
-export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps, webgl?: WebGLContext): Promise<DensityData> {
+export async function GaussianDensity(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps, webgl?: WebGLContext): Promise<GaussianDensityData> {
     if (props.useGpu) {
         if (!GaussianDensityGPU) throw 'GPU computation not supported on this platform';
         if (!webgl) throw 'No WebGL context provided';

+ 4 - 4
src/mol-math/geometry/gaussian-density/cpu.ts

@@ -7,12 +7,12 @@
 import { Box3D, fillGridDim } from '../../geometry';
 import { Vec3, Mat4, Tensor } from '../../linear-algebra';
 import { RuntimeContext } from '../../../mol-task';
-import { PositionData, DensityData } from '../common';
+import { PositionData } from '../common';
 import { OrderedSet } from '../../../mol-data/int';
-import { GaussianDensityProps } from '../gaussian-density';
+import { GaussianDensityProps, GaussianDensityData } from '../gaussian-density';
 import { fasterExp } from '../../approx';
 
-export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps): Promise<DensityData> {
+export async function GaussianDensityCPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number,  props: GaussianDensityProps): Promise<GaussianDensityData> {
     const { resolution, radiusOffset, smoothness } = props;
     const scaleFactor = 1 / resolution;
 
@@ -129,5 +129,5 @@ export async function GaussianDensityCPU(ctx: RuntimeContext, position: Position
     Mat4.fromScaling(transform, Vec3.create(resolution, resolution, resolution));
     Mat4.setTranslation(transform, expandedBox.min);
 
-    return { field, idField, transform };
+    return { field, idField, transform, radiusFactor: 1 };
 }

+ 23 - 21
src/mol-math/geometry/gaussian-density/gpu.ts

@@ -5,9 +5,9 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-import { PositionData, DensityData, DensityTextureData } from '../common';
+import { PositionData } from '../common';
 import { Box3D } from '../../geometry';
-import { GaussianDensityGPUProps } from '../gaussian-density';
+import { GaussianDensityGPUProps, GaussianDensityData, GaussianDensityTextureData } from '../gaussian-density';
 import { OrderedSet } from '../../../mol-data/int';
 import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra';
 import { ValueCell } from '../../../mol-util';
@@ -68,35 +68,35 @@ function getTexture(name: string, webgl: WebGLContext, kind: TextureKind, format
     return webgl.namedTextures[_name];
 }
 
-export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): DensityData {
+export function GaussianDensityGPU(position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, webgl: WebGLContext): GaussianDensityData {
     // always use texture2d when the gaussian density needs to be downloaded from the GPU,
     // it's faster than texture3d
     // console.time('GaussianDensityTexture2d')
     const tmpTexture = getTexture('tmp', webgl, 'image-uint8', 'rgba', 'ubyte', 'linear');
-    const { scale, bbox, texture, gridDim, gridTexDim } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, tmpTexture);
+    const { scale, bbox, texture, gridDim, gridTexDim, radiusFactor } = calcGaussianDensityTexture2d(webgl, position, box, radius, props, tmpTexture);
     // webgl.waitForGpuCommandsCompleteSync()
     // console.timeEnd('GaussianDensityTexture2d')
     const { field, idField } = fieldFromTexture2d(webgl, texture, gridDim, gridTexDim);
 
-    return { field, idField, transform: getTransform(scale, bbox) };
+    return { field, idField, transform: getTransform(scale, bbox), radiusFactor };
 }
 
-export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
+export function GaussianDensityTexture(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
     return webgl.isWebGL2 ?
         GaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture) :
         GaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture);
 }
 
-export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
+export function GaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
     return finalizeGaussianDensityTexture(calcGaussianDensityTexture2d(webgl, position, box, radius, props, oldTexture));
 }
 
-export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): DensityTextureData {
+export function GaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, oldTexture?: Texture): GaussianDensityTextureData {
     return finalizeGaussianDensityTexture(calcGaussianDensityTexture3d(webgl, position, box, radius, props, oldTexture));
 }
 
-function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale }: GaussianDensityTextureData): DensityTextureData {
-    return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale };
+function finalizeGaussianDensityTexture({ texture, scale, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor }: _GaussianDensityTextureData): GaussianDensityTextureData {
+    return { transform: getTransform(scale, bbox), texture, bbox, gridDim, gridTexDim, gridTexScale, radiusFactor };
 }
 
 function getTransform(scale: Vec3, bbox: Box3D) {
@@ -108,30 +108,31 @@ function getTransform(scale: Vec3, bbox: Box3D) {
 
 //
 
-type GaussianDensityTextureData = {
+type _GaussianDensityTextureData = {
     texture: Texture,
     scale: Vec3,
     bbox: Box3D,
     gridDim: Vec3,
     gridTexDim: Vec3
     gridTexScale: Vec2
+    radiusFactor: number
 }
 
-function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
+function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
     // console.log('2d');
     const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
     const { smoothness, resolution } = props;
 
     const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props);
     const [ dx, dy, dz ] = dim;
-    const { texDimX, texDimY, texCols } = getTexture2dSize(dim);
-    // console.log({ texDimX, texDimY, texCols, texSize, dim });
+    const { texDimX, texDimY, texCols, powerOfTwoSize } = getTexture2dSize(dim);
+    // console.log({ texDimX, texDimY, texCols, powerOfTwoSize, dim });
     const gridTexDim = Vec3.create(texDimX, texDimY, 0);
-    const gridTexScale = Vec2.create(texDimX / texDimX, texDimY / texDimY);
+    const gridTexScale = Vec2.create(texDimX / powerOfTwoSize, texDimY / powerOfTwoSize);
     const radiusFactor = maxRadius * 2;
 
     const minDistTex = getTexture('min-dist-2d', webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest');
-    minDistTex.define(texDimX, texDimY);
+    minDistTex.define(powerOfTwoSize, powerOfTwoSize);
 
     const renderable = getGaussianDensityRenderable(webgl, drawCount, positions, radii, groups, minDistTex, expandedBox, dim, gridTexDim, gridTexScale, smoothness, resolution, radiusFactor);
 
@@ -146,7 +147,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
     if (!texture) texture = colorBufferFloat && textureFloat
         ? resources.texture('image-float32', 'rgba', 'float', 'linear')
         : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
-    texture.define(texDimX, texDimY);
+    texture.define(powerOfTwoSize, powerOfTwoSize);
 
     // console.log(renderable)
 
@@ -186,10 +187,10 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat
 
     // printTexture(webgl, texture, 1);
 
-    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale };
+    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim, gridTexScale, radiusFactor };
 }
 
-function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData {
+function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): _GaussianDensityTextureData {
     // console.log('3d');
     const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl;
     const { smoothness, resolution } = props;
@@ -239,7 +240,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat
     setupGroupIdRendering(webgl, renderable);
     render(texture, false);
 
-    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale };
+    return { texture, scale, bbox: expandedBox, gridDim: dim, gridTexDim: dim, gridTexScale, radiusFactor };
 }
 
 //
@@ -409,7 +410,8 @@ function getTexture2dSize(gridDim: Vec3) {
     } else {
         texDimX = gridDim[0] * gridDim[2];
     }
-    return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY };
+    // console.log(texDimX, texDimY, texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2);
+    return { texDimX, texDimY, texRows, texCols, powerOfTwoSize: texDimY < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
 }
 
 export function fieldFromTexture2d(ctx: WebGLContext, texture: Texture, dim: Vec3, texDim: Vec3) {

+ 21 - 7
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -33,10 +33,10 @@ export type GaussianSurfaceMeshParams = typeof GaussianSurfaceMeshParams
 
 async function createGaussianSurfaceMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor } = await computeUnitGaussianDensity(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
 
     const params = {
-        isoLevel: Math.exp(-smoothness),
+        isoLevel: Math.exp(-smoothness) / radiusFactor,
         scalarField: field,
         idField
     };
@@ -81,10 +81,10 @@ export type StructureGaussianSurfaceMeshParams = typeof StructureGaussianSurface
 
 async function createStructureGaussianSurfaceMesh(ctx: VisualContext, structure: Structure, theme: Theme, props: GaussianDensityProps, mesh?: Mesh): Promise<Mesh> {
     const { smoothness } = props;
-    const { transform, field, idField } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
+    const { transform, field, idField, radiusFactor } = await computeStructureGaussianDensity(structure, props, ctx.webgl).runInContext(ctx.runtime);
 
     const params = {
-        isoLevel: Math.exp(-smoothness),
+        isoLevel: Math.exp(-smoothness) / radiusFactor,
         scalarField: field,
         idField
     };
@@ -119,15 +119,25 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
 
 //
 
+const GaussianSurfaceName = 'gaussian-surface';
+
 async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit, structure: Structure, theme: Theme, props: GaussianDensityTextureProps, textureMesh?: TextureMesh): Promise<TextureMesh> {
     if (!ctx.webgl) throw new Error('webgl context required to create gaussian surface texture-mesh');
-    const isoLevel = Math.exp(-props.smoothness);
 
-    const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, props, ctx.webgl).runInContext(ctx.runtime);
+    const { namedTextures, resources, extensions: { colorBufferFloat, textureFloat } } = ctx.webgl;
+    if (!namedTextures[GaussianSurfaceName]) {
+        namedTextures[GaussianSurfaceName] = colorBufferFloat && textureFloat
+            ? resources.texture('image-float32', 'rgba', 'float', 'linear')
+            : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
+    }
+
+    const densityTextureData = await computeUnitGaussianDensityTexture2d(structure, unit, props, ctx.webgl, namedTextures[GaussianSurfaceName]).runInContext(ctx.runtime);
     // console.log(densityTextureData)
     // console.log('vertexGroupTexture', readTexture(ctx.webgl, densityTextureData.texture))
     // ctx.webgl.waitForGpuCommandsCompleteSync()
 
+    const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
+
     const activeVoxelsTex = calcActiveVoxels(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, isoLevel, densityTextureData.gridTexScale);
     // ctx.webgl.waitForGpuCommandsCompleteSync()
 
@@ -141,7 +151,11 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
     // Sphere3D.addVec3(boundingSphere, boundingSphere, densityTextureData.gridDimension)
     const boundingSphere = Sphere3D.fromBox3D(Sphere3D(), densityTextureData.bbox);
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, boundingSphere, textureMesh);
-
+    // console.log({
+    //     renderables: ctx.webgl.namedComputeRenderables,
+    //     framebuffers: ctx.webgl.namedFramebuffers,
+    //     textures: ctx.webgl.namedTextures,
+    // });
     // ctx.webgl.waitForGpuCommandsCompleteSync()
     return surface;
 }

+ 2 - 0
src/mol-repr/volume/direct-volume.ts

@@ -113,6 +113,7 @@ export function createDirectVolume2d(ctx: RuntimeContext, webgl: WebGLContext, v
     const transform = Grid.getGridToCartesianTransform(volume.grid);
     const bbox = getBoundingBox(gridDimension, transform);
 
+    // TODO: handle disposal
     const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.load(textureImage);
 
@@ -195,6 +196,7 @@ export function createDirectVolume3d(ctx: RuntimeContext, webgl: WebGLContext, v
     const transform = Grid.getGridToCartesianTransform(volume.grid);
     const bbox = getBoundingBox(gridDimension, transform);
 
+    // TODO: handle disposal
     const texture = directVolume ? directVolume.gridTexture.ref.value : webgl.resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear');
     texture.load(textureVolume);