From d6ffbae8c56a0103dcfc257e1e6344414bf70fb7 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alex.rose@rcsb.org>
Date: Fri, 19 Oct 2018 14:37:21 -0700
Subject: [PATCH] gaussian density groupid calc for via 2d textures

---
 src/mol-gl/renderable/gaussian-density.ts     |   4 +-
 src/mol-gl/shader/direct-volume.frag          |   7 +-
 src/mol-gl/shader/gaussian-density.frag       |  29 +-
 src/mol-gl/shader/utils/encode-id-rgba.glsl   |   6 +
 src/mol-gl/shader/utils/my-div.glsl           |  13 +
 src/mol-gl/shader/utils/my-mod.glsl           |  13 +
 .../utils/texture3d-from-2d-linear.glsl       |  28 ++
 .../utils/texture3d-from-2d-nearest.glsl      |  19 ++
 src/mol-gl/webgl/compat.ts                    |  15 +
 src/mol-gl/webgl/context.ts                   |  12 +-
 src/mol-math/geometry/gaussian-density/gpu.ts | 279 +++++++++---------
 11 files changed, 279 insertions(+), 146 deletions(-)
 create mode 100644 src/mol-gl/shader/utils/my-div.glsl
 create mode 100644 src/mol-gl/shader/utils/my-mod.glsl
 create mode 100644 src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl
 create mode 100644 src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl

diff --git a/src/mol-gl/renderable/gaussian-density.ts b/src/mol-gl/renderable/gaussian-density.ts
index abb6b8b80..5d2a4f5c5 100644
--- a/src/mol-gl/renderable/gaussian-density.ts
+++ b/src/mol-gl/renderable/gaussian-density.ts
@@ -25,9 +25,11 @@ export const GaussianDensitySchema = {
     uBboxMax: UniformSpec('v3'),
     uBboxSize: UniformSpec('v3'),
     uGridDim: UniformSpec('v3'),
+    uGridTexDim: UniformSpec('v2'),
     uAlpha: UniformSpec('f'),
-    tMinDistanceTex: TextureSpec('texture2d', 'rgba', 'ubyte', 'nearest'),
+    tMinDistanceTex: TextureSpec('texture3d', 'rgba', 'ubyte', 'nearest'),
 
+    dGridTexType: DefineSpec('string', ['2d', '3d']),
     dCalcType: DefineSpec('string', ['density', 'minDistance', 'groupId']),
 }
 export type GaussianDensitySchema = typeof GaussianDensitySchema
diff --git a/src/mol-gl/shader/direct-volume.frag b/src/mol-gl/shader/direct-volume.frag
index 3082b5ccf..96df2263e 100644
--- a/src/mol-gl/shader/direct-volume.frag
+++ b/src/mol-gl/shader/direct-volume.frag
@@ -5,11 +5,6 @@
  * @author Michael Krone <michael.krone@uni-tuebingen.de>
  */
 
-#if defined(dGridTexType_2d)
-    precision mediump sampler2D;
-#elif defined(dGridTexType_3d)
-    precision mediump sampler3D;
-#endif
 precision highp float;
 
 varying vec3 unitCoord;
@@ -22,9 +17,11 @@ uniform vec3 uGridDim;
 uniform sampler2D tTransferTex;
 
 #if defined(dGridTexType_2d)
+    precision mediump sampler2D;
     uniform sampler2D tGridTex;
     uniform vec2 uGridTexDim;
 #elif defined(dGridTexType_3d)
+    precision mediump sampler3D;
     uniform sampler3D tGridTex;
 #endif
 
diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag
index f9558f866..dbb5e67b1 100644
--- a/src/mol-gl/shader/gaussian-density.frag
+++ b/src/mol-gl/shader/gaussian-density.frag
@@ -10,12 +10,19 @@ precision highp float;
 varying vec3 vPosition;
 varying float vRadius;
 #if defined(dCalcType_groupId)
-    precision highp sampler3D;
-    uniform sampler3D tMinDistanceTex;
+    #if defined(dGridTexType_2d)
+        precision mediump sampler2D;
+        uniform sampler2D tMinDistanceTex;
+        uniform vec2 uGridTexDim;
+    #elif defined(dGridTexType_3d)
+        precision highp sampler3D;
+        uniform sampler3D tMinDistanceTex;
+    #endif
     varying float vGroup;
 #endif
 
 #pragma glslify: encodeIdRGBA = require(./utils/encode-id-rgba.glsl)
+#pragma glslify: texture3dFrom2dNearest = require(./utils/texture3d-from-2d-nearest.glsl)
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -26,6 +33,18 @@ uniform float uCurrentX;
 uniform float uCurrentY;
 uniform float uAlpha;
 
+#if defined(dCalcType_groupId)
+    #if defined(dGridTexType_2d)
+        vec4 textureMinDist(vec3 pos) {
+            return texture3dFrom2dNearest(tMinDistanceTex, pos, uGridDim, uGridTexDim);
+        }
+    #elif defined(dGridTexType_3d)
+        vec4 textureMinDist(vec3 pos) {
+            return texture(tMinDistanceTex, pos);
+        }
+    #endif
+#endif
+
 // encode distance logarithmically with given maxDistance
 const float maxDistance = 10000.0;
 const float distLogFactor = log(maxDistance + 1.0);
@@ -42,10 +61,10 @@ void main() {
         float density = exp(-uAlpha * ((dist * dist) / radiusSq));
         gl_FragColor = vec4(density);
     #elif defined(dCalcType_minDistance)
-        gl_FragColor.r = 1.0 - encodeDistLog(dist);
+        gl_FragColor.a = 1.0 - encodeDistLog(dist);
     #elif defined(dCalcType_groupId)
-        float minDistance = decodeDistLog(1.0 - texture(tMinDistanceTex, fragPos).r);
-        if (dist > minDistance + log(minDistance) / 2.0)
+        float minDistance = decodeDistLog(1.0 - textureMinDist(fragPos).a);
+        if (dist > minDistance + length(uBboxSize / uGridDim) / 1.5)
             discard;
         gl_FragColor = encodeIdRGBA(vGroup);
     #endif
diff --git a/src/mol-gl/shader/utils/encode-id-rgba.glsl b/src/mol-gl/shader/utils/encode-id-rgba.glsl
index 9a4703965..b487be72f 100644
--- a/src/mol-gl/shader/utils/encode-id-rgba.glsl
+++ b/src/mol-gl/shader/utils/encode-id-rgba.glsl
@@ -1,3 +1,9 @@
+/**
+ * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ */
+
 #pragma glslify: encodeFloatRGBA = require(../utils/encode-float-rgba.glsl)
 
 vec4 encodeIdRGBA(const in float v) {
diff --git a/src/mol-gl/shader/utils/my-div.glsl b/src/mol-gl/shader/utils/my-div.glsl
new file mode 100644
index 000000000..49c461894
--- /dev/null
+++ b/src/mol-gl/shader/utils/my-div.glsl
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Michael Krone <michael.krone@uni-tuebingen.de>
+ */
+
+// TODO workaround due to some kind of GPU quirk
+float myDiv(float a, float b) {
+    return float(int(a) / int(b));
+}
+
+#pragma glslify: export(myDiv)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/my-mod.glsl b/src/mol-gl/shader/utils/my-mod.glsl
new file mode 100644
index 000000000..3030ff3f1
--- /dev/null
+++ b/src/mol-gl/shader/utils/my-mod.glsl
@@ -0,0 +1,13 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Michael Krone <michael.krone@uni-tuebingen.de>
+ */
+
+// TODO workaround due to some kind of GPU quirk
+float myMod(float a, float b) {
+    return a - b * float(int(a) / int(b));
+}
+
+#pragma glslify: export(myMod)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl b/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl
new file mode 100644
index 000000000..5b8fca4f0
--- /dev/null
+++ b/src/mol-gl/shader/utils/texture3d-from-2d-linear.glsl
@@ -0,0 +1,28 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Michael Krone <michael.krone@uni-tuebingen.de>
+ */
+
+#pragma glslify: myMod = require(./my-mod.glsl)
+#pragma glslify: myDiv = require(./my-div.glsl)
+
+vec4 texture3dFrom2dLinear(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
+    float zSlice0 = floor(pos.z * gridDim.z);
+    float column0 = myMod(zSlice0 * gridDim.x, texDim.x) / gridDim.x;
+    float row0 = floor(myDiv(zSlice0 * gridDim.x, texDim.x));
+    vec2 coord0 = (vec2(column0 * gridDim.x, row0 * gridDim.y) + (pos.xy * gridDim.xy)) / texDim;
+    vec4 color0 = texture2D(tex, coord0);
+
+    float zSlice1 = zSlice0 + 1.0;
+    float column1 = myMod(zSlice1 * gridDim.x, texDim.x) / gridDim.x;
+    float row1 = floor(myDiv(zSlice1 * gridDim.x, texDim.x));
+    vec2 coord1 = (vec2(column1 * gridDim.x, row1 * gridDim.y) + (pos.xy * gridDim.xy)) / texDim;
+    vec4 color1 = texture2D(tex, coord1);
+
+    float delta0 = abs((pos.z * gridDim.z) - zSlice0);
+    return mix(color0, color1, delta0);
+}
+
+#pragma glslify: export(texture3dFrom2dLinear)
\ No newline at end of file
diff --git a/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl b/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl
new file mode 100644
index 000000000..9cf714cd6
--- /dev/null
+++ b/src/mol-gl/shader/utils/texture3d-from-2d-nearest.glsl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017-2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ *
+ * @author Alexander Rose <alexander.rose@weirdbyte.de>
+ * @author Michael Krone <michael.krone@uni-tuebingen.de>
+ */
+
+#pragma glslify: myMod = require(./my-mod.glsl)
+#pragma glslify: myDiv = require(./my-div.glsl)
+
+vec4 texture3dFrom2dNearest(sampler2D tex, vec3 pos, vec3 gridDim, vec2 texDim) {
+    float zSlice = floor(pos.z * gridDim.z + 0.5); // round to nearest z-slice
+    float column = myMod(zSlice * gridDim.x, texDim.x) / gridDim.x;
+    float row = floor(myDiv(zSlice * gridDim.x, texDim.x));
+    vec2 coord = (vec2(column * gridDim.x, row * gridDim.y) + (pos.xy * gridDim.xy)) / texDim;
+    return texture2D(tex, coord);
+}
+
+#pragma glslify: export(texture3dFrom2dNearest)
\ No newline at end of file
diff --git a/src/mol-gl/webgl/compat.ts b/src/mol-gl/webgl/compat.ts
index f0bad2f5f..9fbaba595 100644
--- a/src/mol-gl/webgl/compat.ts
+++ b/src/mol-gl/webgl/compat.ts
@@ -106,4 +106,19 @@ export interface COMPAT_texture_float_linear {
 
 export function getTextureFloatLinear(gl: GLRenderingContext): COMPAT_texture_float_linear | null {
     return gl.getExtension('OES_texture_float_linear')
+}
+
+export interface COMPAT_blend_minmax {
+    readonly MIN: number
+    readonly MAX: number
+}
+
+export function getBlendMinMax(gl: GLRenderingContext): COMPAT_blend_minmax | null {
+    if (isWebGL2(gl)) {
+        return { MIN: gl.MIN, MAX: gl.MAX }
+    } else {
+        const ext = gl.getExtension('EXT_blend_minmax')
+        if (ext === null) return null
+        return { MIN: ext.MIN_EXT, MAX: ext.MAX_EXT }
+    }
 }
\ No newline at end of file
diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts
index 869526c5f..3fd9a2e3f 100644
--- a/src/mol-gl/webgl/context.ts
+++ b/src/mol-gl/webgl/context.ts
@@ -6,7 +6,7 @@
 
 import { createProgramCache, ProgramCache } from './program'
 import { createShaderCache, ShaderCache } from './shader'
-import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear } from './compat';
+import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax } from './compat';
 
 export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null {
     function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') {
@@ -100,8 +100,9 @@ export function createImageData(buffer: ArrayLike<number>, width: number, height
 type Extensions = {
     instancedArrays: COMPAT_instanced_arrays
     standardDerivatives: COMPAT_standard_derivatives
-    textureFloat: COMPAT_texture_float,
-    textureFloatLinear: COMPAT_texture_float_linear,
+    blendMinMax: COMPAT_blend_minmax
+    textureFloat: COMPAT_texture_float
+    textureFloatLinear: COMPAT_texture_float_linear
     elementIndexUint: COMPAT_element_index_uint | null
     vertexArrayObject: COMPAT_vertex_array_object | null
 }
@@ -144,6 +145,10 @@ export function createContext(gl: GLRenderingContext): Context {
     if (standardDerivatives === null) {
         throw new Error('Could not find support for "standard_derivatives"')
     }
+    const blendMinMax = getBlendMinMax(gl)
+    if (blendMinMax === null) {
+        throw new Error('Could not find support for "blend_minmax"')
+    }
     const textureFloat = getTextureFloat(gl)
     if (textureFloat === null) {
         throw new Error('Could not find support for "texture_float"')
@@ -175,6 +180,7 @@ export function createContext(gl: GLRenderingContext): Context {
         extensions: {
             instancedArrays,
             standardDerivatives,
+            blendMinMax,
             textureFloat,
             textureFloatLinear,
             elementIndexUint,
diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts
index d3fbaa40f..233ce9aa6 100644
--- a/src/mol-math/geometry/gaussian-density/gpu.ts
+++ b/src/mol-math/geometry/gaussian-density/gpu.ts
@@ -10,10 +10,10 @@ import { PositionData, DensityData, DensityTextureData } from '../common'
 import { Box3D } from '../../geometry'
 import { GaussianDensityProps, getDelta } from '../gaussian-density'
 import { OrderedSet } from 'mol-data/int'
-import { Vec3, Tensor, Mat4 } from '../../linear-algebra'
+import { Vec3, Tensor, Mat4, Vec2 } from '../../linear-algebra'
 import { GaussianDensityValues } from 'mol-gl/renderable/gaussian-density'
 import { ValueCell, defaults } from 'mol-util'
-import { RenderableState } from 'mol-gl/renderable'
+import { RenderableState, Renderable } from 'mol-gl/renderable'
 import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object'
 import { Context, createContext, getGLContext } from 'mol-gl/webgl/context';
 import { createFramebuffer } from 'mol-gl/webgl/framebuffer';
@@ -23,13 +23,16 @@ import { decodeIdRGBA } from 'mol-geo/geometry/picking';
 
 export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
     const webgl = defaults(props.webgl, getWebGLContext())
+    // always use texture2d when the gaussian density needs to be downloaded from the GPU,
+    // it's faster than texture3d
+    console.time('GaussianDensityTexture2d')
+    const { scale, bbox, texture, dim } = await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props)
+    console.timeEnd('GaussianDensityTexture2d')
+    const { field, idField } = fieldFromTexture2d(webgl, texture, dim)
 
-    const { transform, texture, gridDimension } = await GaussianDensityTexture(ctx, webgl, position, box, radius, props)
-
-    const { field, idField } = webgl.isWebGL2 ?
-        fieldFromTexture3d(webgl, texture, gridDimension) :
-        fieldFromTexture2d(webgl, texture, gridDimension)
-
+    const transform = Mat4.identity()
+    Mat4.fromScaling(transform, scale)
+    Mat4.setTranslation(transform, bbox.min)
     console.log(idField)
 
     return { field, idField, transform }
@@ -56,63 +59,56 @@ async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: Context, pos
 
     const { drawCount, positions, radii, groups, delta, expandedBox, dim } = await prepareGaussianDensityData(ctx, position, box, radius, props)
     const [ dx, dy, dz ] = dim
-    const minDistanceTexture = getMinDistanceTexture(webgl, dx, dy)
+    const { texDimX, texDimY, texCols } = getTexture2dSize(webgl.maxTextureSize, dim)
+
+    const minDistanceTexture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest')
+    minDistanceTexture.define(texDimX, texDimY)
 
     const renderObject = getGaussianDensityRenderObject(webgl, drawCount, positions, radii, groups, minDistanceTexture, expandedBox, dim, smoothness)
     const renderable = createRenderable(webgl, renderObject)
 
     //
 
-    const maxTexSize = webgl.maxTextureSize
-    let fboTexDimX = 0
-    let fboTexDimY = dim[1]
-    let fboTexRows = 1
-    let fboTexCols = dim[0]
-    if (maxTexSize < dim[0] * dim[2]) {
-        fboTexCols =  Math.floor(maxTexSize / dim[0])
-        fboTexRows = Math.ceil(dim[2] / fboTexCols)
-        fboTexDimX = fboTexCols * dim[0]
-        fboTexDimY *= fboTexRows
-    } else {
-        fboTexDimX = dim[0] * dim[2]
-    }
-
-    //
-
     const { gl } = webgl
     const { uCurrentSlice, uCurrentX, uCurrentY } = renderObject.values
 
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
-
-    if (!texture) {
-        texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
-    }
-    texture.define(fboTexDimX, fboTexDimY)
-
-    const program = renderable.getProgram('draw')
-    program.use()
     setRenderingDefaults(gl)
-    texture.attachFramebuffer(framebuffer, 0)
 
-    let currCol = 0
-    let currY = 0
-    let currX = 0
-    for (let i = 0; i < dz; ++i) {
-        if (currCol >= fboTexCols) {
-            currCol -= fboTexCols
-            currY += dy
-            currX = 0
+    if (!texture) texture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'linear')
+    texture.define(texDimX, texDimY)
+
+    function render(fbTex: Texture) {
+        fbTex.attachFramebuffer(framebuffer, 0)
+        let currCol = 0
+        let currY = 0
+        let currX = 0
+        for (let i = 0; i < dz; ++i) {
+            if (currCol >= texCols) {
+                currCol -= texCols
+                currY += dy
+                currX = 0
+            }
+            gl.viewport(currX, currY, dx, dy)
+            ValueCell.update(uCurrentSlice, i)
+            ValueCell.update(uCurrentX, currX)
+            ValueCell.update(uCurrentY, currY)
+            renderable.render('draw')
+            ++currCol
+            currX += dx
         }
-        gl.viewport(currX, currY, dx, dy)
-        ValueCell.update(uCurrentSlice, i)
-        ValueCell.update(uCurrentX, currX)
-        ValueCell.update(uCurrentY, currY)
-        renderable.render('draw')
-        ++currCol
-        currX += dx
     }
 
+    setupMinDistanceRendering(webgl, renderable)
+    render(minDistanceTexture)
+
+    setupDensityRendering(webgl, renderable)
+    render(texture)
+
+    setupGroupIdRendering(webgl, renderable)
+    render(texture)
+
     framebuffer.destroy() // clean up
 
     await ctx.update({ message: 'gpu gaussian density calculation' });
@@ -134,55 +130,33 @@ async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: Context, pos
 
     //
 
-    const gl = webgl.gl as WebGL2RenderingContext
+    const { gl } = webgl
     const { uCurrentSlice } = renderObject.values
 
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
-
-    gl.viewport(0, 0, dx, dy)
     setRenderingDefaults(gl)
+    gl.viewport(0, 0, dx, dy)
 
-    if (!texture) {
-        texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
-    }
+    if (!texture) texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
     texture.define(dx, dy, dz)
 
-    ValueCell.update(renderable.values.dCalcType, 'minDistance')
-    renderable.update()
-    const programMinDistance = renderable.getProgram('draw')
-    programMinDistance.use()
-    gl.blendFunc(gl.ONE, gl.ONE)
-    gl.blendEquation(gl.MAX)
-    for (let i = 0; i < dz; ++i) {
-        ValueCell.update(uCurrentSlice, i)
-        minDistanceTexture.attachFramebuffer(framebuffer, 0, i)
-        renderable.render('draw')
+    function render(fbTex: Texture) {
+        for (let i = 0; i < dz; ++i) {
+            ValueCell.update(uCurrentSlice, i)
+            fbTex.attachFramebuffer(framebuffer, 0, i)
+            renderable.render('draw')
+        }
     }
 
-    ValueCell.update(renderable.values.dCalcType, 'density')
-    renderable.update()
-    const programDensity = renderable.getProgram('draw')
-    programDensity.use()
-    gl.blendFunc(gl.ONE, gl.ONE)
-    gl.blendEquation(gl.FUNC_ADD)
-    for (let i = 0; i < dz; ++i) {
-        ValueCell.update(uCurrentSlice, i)
-        texture.attachFramebuffer(framebuffer, 0, i)
-        renderable.render('draw')
-    }
+    setupMinDistanceRendering(webgl, renderable)
+    render(minDistanceTexture)
 
-    ValueCell.update(renderable.values.dCalcType, 'groupId')
-    renderable.update()
-    const programGroupId = renderable.getProgram('draw')
-    programGroupId.use()
-    gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ZERO, gl.ONE)
-    gl.blendEquation(gl.FUNC_ADD)
-    for (let i = 0; i < dz; ++i) {
-        ValueCell.update(uCurrentSlice, i)
-        texture.attachFramebuffer(framebuffer, 0, i)
-        renderable.render('draw')
-    }
+    setupDensityRendering(webgl, renderable)
+    render(texture)
+
+    setupGroupIdRendering(webgl, renderable)
+    render(texture)
 
     framebuffer.destroy() // clean up
 
@@ -249,14 +223,9 @@ async function prepareGaussianDensityData(ctx: RuntimeContext, position: Positio
     return { drawCount: n, positions, radii, groups, delta, expandedBox, dim }
 }
 
-function getMinDistanceTexture(webgl: Context, width: number, height: number) {
-    const minDistanceTexture = createTexture(webgl, 'image-uint8', 'rgba', 'ubyte', 'nearest')
-    minDistanceTexture.define(width, height)
-    return minDistanceTexture
-}
-
 function getGaussianDensityRenderObject(webgl: Context, drawCount: number, positions: Float32Array, radii: Float32Array, groups: Float32Array, minDistanceTexture: Texture, box: Box3D, dimensions: Vec3, smoothness: number) {
     const extent = Vec3.sub(Vec3.zero(), box.max, box.min)
+    const { texDimX, texDimY } = getTexture2dSize(webgl.maxTextureSize, dimensions)
 
     const values: GaussianDensityValues = {
         drawCount: ValueCell.create(drawCount),
@@ -273,9 +242,11 @@ function getGaussianDensityRenderObject(webgl: Context, drawCount: number, posit
         uBboxMax: ValueCell.create(box.max),
         uBboxSize: ValueCell.create(extent),
         uGridDim: ValueCell.create(dimensions),
+        uGridTexDim: ValueCell.create(Vec2.create(texDimX, texDimY)),
         uAlpha: ValueCell.create(smoothness),
         tMinDistanceTex: ValueCell.create(minDistanceTexture),
 
+        dGridTexType: ValueCell.create(minDistanceTexture.depth > 0 ? '3d' : '2d'),
         dCalcType: ValueCell.create('density'),
     }
     const state: RenderableState = {
@@ -292,10 +263,52 @@ function setRenderingDefaults(gl: GLRenderingContext) {
     gl.disable(gl.CULL_FACE)
     gl.frontFace(gl.CCW)
     gl.cullFace(gl.BACK)
+    gl.enable(gl.BLEND)
+}
 
+function setupMinDistanceRendering(webgl: Context, renderable: Renderable<any>) {
+    const { gl } = webgl
+    ValueCell.update(renderable.values.dCalcType, 'minDistance')
+    renderable.update()
+    renderable.getProgram('draw').use()
+    gl.blendFunc(gl.ONE, gl.ONE)
+    // the shader writes 1 - dist so we set blending to MAX
+    gl.blendEquation(webgl.extensions.blendMinMax.MAX)
+}
+
+function setupDensityRendering(webgl: Context, renderable: Renderable<any>) {
+    const { gl } = webgl
+    ValueCell.update(renderable.values.dCalcType, 'density')
+    renderable.update()
+    renderable.getProgram('draw').use()
     gl.blendFunc(gl.ONE, gl.ONE)
     gl.blendEquation(gl.FUNC_ADD)
-    gl.enable(gl.BLEND)
+}
+
+function setupGroupIdRendering(webgl: Context, renderable: Renderable<any>) {
+    const { gl } = webgl
+    ValueCell.update(renderable.values.dCalcType, 'groupId')
+    renderable.update()
+    renderable.getProgram('draw').use()
+    // overwrite color, don't change alpha
+    gl.blendFuncSeparate(gl.ONE, gl.ZERO, gl.ZERO, gl.ONE)
+    gl.blendEquation(gl.FUNC_ADD)
+}
+
+function getTexture2dSize(maxTexSize: number, gridDim: Vec3) {
+    let texDimX = 0
+    let texDimY = gridDim[1]
+    let texRows = 1
+    let texCols = gridDim[0]
+    if (maxTexSize < gridDim[0] * gridDim[2]) {
+        texCols =  Math.floor(maxTexSize / gridDim[0])
+        texRows = Math.ceil(gridDim[2] / texCols)
+        texDimX = texCols * gridDim[0]
+        texDimY *= texRows
+    } else {
+        texDimX = gridDim[0] * gridDim[2]
+    }
+    return { texDimX, texDimY, texRows, texCols }
 }
 
 function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
@@ -319,7 +332,7 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
     texture.attachFramebuffer(framebuffer, 0)
     gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image)
 
-    let idx = 0
+    let j = 0
     let tmpCol = 0
     let tmpRow = 0
     for (let iz = 0; iz < dz; ++iz) {
@@ -329,8 +342,10 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
         }
         for (let iy = 0; iy < dy; ++iy) {
             for (let ix = 0; ix < dx; ++ix) {
-                data[idx] = image[4 * (tmpCol * dx + (iy + tmpRow) * width + ix) + 3] / 255
-                idx++
+                const idx = 4 * (tmpCol * dx + (iy + tmpRow) * width + ix)
+                data[j] = image[idx + 3] / 255
+                idData[j] = decodeIdRGBA(image[idx], image[idx + 1], image[idx + 2])
+                j++
             }
         }
         tmpCol++
@@ -342,38 +357,38 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
     return { field, idField }
 }
 
-function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) {
-    console.time('fieldFromTexture3d')
-    const { gl } = ctx
-    const { width, height, depth } = texture
-
-    const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
-    const data = space.create()
-    const field = Tensor.create(space, data)
-    const idData = space.create()
-    const idField = Tensor.create(space, idData)
-
-    const slice = new Uint8Array(width * height * 4)
-
-    const framebuffer = createFramebuffer(ctx)
-    framebuffer.bind()
-
-    let j = 0
-    for (let i = 0; i < depth; ++i) {
-        texture.attachFramebuffer(framebuffer, 0, i)
-        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice)
-        for (let iy = 0; iy < height; ++iy) {
-            for (let ix = 0; ix < width; ++ix) {
-                const idx = 4 * (iy * width + ix)
-                data[j] = slice[idx + 3] / 255
-                idData[j] = decodeIdRGBA(slice[idx], slice[idx + 1], slice[idx + 2])
-                ++j
-            }
-        }
-    }
-
-    framebuffer.destroy()
-    console.timeEnd('fieldFromTexture3d')
-
-    return { field, idField }
-}
\ No newline at end of file
+// function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) {
+//     console.time('fieldFromTexture3d')
+//     const { gl } = ctx
+//     const { width, height, depth } = texture
+
+//     const space = Tensor.Space(dim, [2, 1, 0], Float32Array)
+//     const data = space.create()
+//     const field = Tensor.create(space, data)
+//     const idData = space.create()
+//     const idField = Tensor.create(space, idData)
+
+//     const slice = new Uint8Array(width * height * 4)
+
+//     const framebuffer = createFramebuffer(ctx)
+//     framebuffer.bind()
+
+//     let j = 0
+//     for (let i = 0; i < depth; ++i) {
+//         texture.attachFramebuffer(framebuffer, 0, i)
+//         gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice)
+//         for (let iy = 0; iy < height; ++iy) {
+//             for (let ix = 0; ix < width; ++ix) {
+//                 const idx = 4 * (iy * width + ix)
+//                 data[j] = slice[idx + 3] / 255
+//                 idData[j] = decodeIdRGBA(slice[idx], slice[idx + 1], slice[idx + 2])
+//                 ++j
+//             }
+//         }
+//     }
+
+//     framebuffer.destroy()
+//     console.timeEnd('fieldFromTexture3d')
+
+//     return { field, idField }
+// }
\ No newline at end of file
-- 
GitLab