diff --git a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts
index cd8c359fd67ef137d8154f74a2674347e70e7cf8..2cd745e3551f30ef10df78e455d8983ed201de69 100644
--- a/src/mol-geo/geometry/texture-mesh/texture-mesh.ts
+++ b/src/mol-geo/geometry/texture-mesh/texture-mesh.ts
@@ -21,7 +21,6 @@ import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 import { calculateTransformBoundingSphere } from '../../../mol-gl/renderable/util';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { Vec2, Vec4 } from '../../../mol-math/linear-algebra';
-import { fillSerial } from '../../../mol-util/array';
 import { createEmptyClipping } from '../clipping-data';
 import { NullLocation } from '../../../mol-model/location';
 
@@ -34,22 +33,22 @@ export interface TextureMesh {
     groupCount: number,
 
     readonly geoTextureDim: ValueCell<Vec2>,
-    /** texture has vertex positions in XYZ and group id in W */
-    readonly vertexGroupTexture: ValueCell<Texture>,
+    readonly vertexTexture: ValueCell<Texture>,
+    readonly groupTexture: ValueCell<Texture>,
     readonly normalTexture: ValueCell<Texture>,
 
     readonly boundingSphere: Sphere3D
 }
 
 export namespace TextureMesh {
-    export function create(vertexCount: number, groupCount: number, vertexGroupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
-        const width = vertexGroupTexture.getWidth();
-        const height = vertexGroupTexture.getHeight();
+    export function create(vertexCount: number, groupCount: number, vertexTexture: Texture, groupTexture: Texture, normalTexture: Texture, boundingSphere: Sphere3D, textureMesh?: TextureMesh): TextureMesh {
+        const width = vertexTexture.getWidth();
+        const height = vertexTexture.getHeight();
         if (textureMesh) {
             textureMesh.vertexCount = vertexCount;
             textureMesh.groupCount = groupCount;
             ValueCell.update(textureMesh.geoTextureDim, Vec2.set(textureMesh.geoTextureDim.ref.value, width, height));
-            ValueCell.update(textureMesh.vertexGroupTexture, vertexGroupTexture);
+            ValueCell.update(textureMesh.vertexTexture, vertexTexture);
             ValueCell.update(textureMesh.normalTexture, normalTexture);
             Sphere3D.copy(textureMesh.boundingSphere, boundingSphere);
             return textureMesh;
@@ -59,7 +58,8 @@ export namespace TextureMesh {
                 vertexCount,
                 groupCount,
                 geoTextureDim: ValueCell.create(Vec2.create(width, height)),
-                vertexGroupTexture: ValueCell.create(vertexGroupTexture),
+                vertexTexture: ValueCell.create(vertexTexture),
+                groupTexture: ValueCell.create(groupTexture),
                 normalTexture: ValueCell.create(normalTexture),
                 boundingSphere: Sphere3D.clone(boundingSphere),
             };
@@ -109,11 +109,10 @@ export namespace TextureMesh {
 
         return {
             uGeoTexDim: textureMesh.geoTextureDim,
-            tPositionGroup: textureMesh.vertexGroupTexture,
+            tPosition: textureMesh.vertexTexture,
+            tGroup: textureMesh.groupTexture,
             tNormal: textureMesh.normalTexture,
 
-            // aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
-            aGroup: ValueCell.create(fillSerial(new Float32Array(textureMesh.vertexCount))),
             boundingSphere: ValueCell.create(boundingSphere),
             invariantBoundingSphere: ValueCell.create(invariantBoundingSphere),
             uInvariantBoundingSphere: ValueCell.create(Vec4.ofSphere(invariantBoundingSphere)),
@@ -148,11 +147,6 @@ export namespace TextureMesh {
         ValueCell.updateIfChanged(values.dFlipSided, props.flipSided);
         ValueCell.updateIfChanged(values.dIgnoreLight, props.ignoreLight);
         ValueCell.updateIfChanged(values.dXrayShaded, props.xrayShaded);
-
-        if (values.drawCount.ref.value > values.aGroup.ref.value.length) {
-            // console.log('updating vertex ids in aGroup to handle larger drawCount')
-            ValueCell.update(values.aGroup, fillSerial(new Float32Array(values.drawCount.ref.value)));
-        }
     }
 
     function updateBoundingSphere(values: TextureMeshValues, textureMesh: TextureMesh) {
diff --git a/src/mol-gl/compute/histogram-pyramid/reduction.ts b/src/mol-gl/compute/histogram-pyramid/reduction.ts
index de5caa40f50b3a6f648f157f1d8f0815125061d5..5d8054d2f6cc5f8bb316005adf6b4ea9d433a3a6 100644
--- a/src/mol-gl/compute/histogram-pyramid/reduction.ts
+++ b/src/mol-gl/compute/histogram-pyramid/reduction.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { createComputeRenderable } from '../../renderable';
+import { createComputeRenderable, ComputeRenderable } from '../../renderable';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
@@ -18,41 +18,46 @@ import { Framebuffer } from '../../../mol-gl/webgl/framebuffer';
 import { isPowerOfTwo } from '../../../mol-math/misc';
 import quad_vert from '../../../mol-gl/shader/quad.vert';
 import reduction_frag from '../../../mol-gl/shader/histogram-pyramid/reduction.frag';
+import { isWebGL2 } from '../../webgl/compat';
 
 const HistopyramidReductionSchema = {
     ...QuadSchema,
+    tInputLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
     tPreviousLevel: TextureSpec('texture', 'rgba', 'float', 'nearest'),
     uSize: UniformSpec('f'),
     uTexSize: UniformSpec('f'),
     uFirst: UniformSpec('b'),
 };
+type HistopyramidReductionValues = Values<typeof HistopyramidReductionSchema>
 
 const HistogramPyramidName = 'histogram-pyramid';
 
-function getHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
+function getHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Texture, previousLevel: Texture): ComputeRenderable<HistopyramidReductionValues> {
     if (ctx.namedComputeRenderables[HistogramPyramidName]) {
-        const v = ctx.namedComputeRenderables[HistogramPyramidName].values;
+        const v = ctx.namedComputeRenderables[HistogramPyramidName].values as HistopyramidReductionValues;
 
-        ValueCell.update(v.tPreviousLevel, initialTexture);
+        ValueCell.update(v.tInputLevel, inputLevel);
+        ValueCell.update(v.tPreviousLevel, previousLevel);
 
         ctx.namedComputeRenderables[HistogramPyramidName].update();
     } else {
-        ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, initialTexture);
+        ctx.namedComputeRenderables[HistogramPyramidName] = createHistopyramidReductionRenderable(ctx, inputLevel, previousLevel);
     }
     return ctx.namedComputeRenderables[HistogramPyramidName];
 }
 
-function createHistopyramidReductionRenderable(ctx: WebGLContext, initialTexture: Texture) {
-    const values: Values<typeof HistopyramidReductionSchema> = {
+function createHistopyramidReductionRenderable(ctx: WebGLContext, inputLevel: Texture, previousLevel: Texture) {
+    const values: HistopyramidReductionValues = {
         ...QuadValues,
-        tPreviousLevel: ValueCell.create(initialTexture),
+        tInputLevel: ValueCell.create(inputLevel),
+        tPreviousLevel: ValueCell.create(previousLevel),
         uSize: ValueCell.create(0),
         uTexSize: ValueCell.create(0),
         uFirst: ValueCell.create(true),
     };
 
     const schema = { ...HistopyramidReductionSchema };
-    const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag);
+    const shaderCode = ShaderCode('reduction', quad_vert, reduction_frag, {}, { 0: 'ivec4' });
     const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
@@ -64,11 +69,13 @@ function getLevelTextureFramebuffer(ctx: WebGLContext, level: number) {
     let textureFramebuffer = LevelTexturesFramebuffers[level];
     const size = Math.pow(2, level);
     if (textureFramebuffer === undefined) {
-        const texture = getTexture(`level${level}`, ctx, 'image-float32', 'rgba', 'float', 'nearest');
+        const texture = ctx.isWebGL2
+            ? getTexture(`level${level}`, ctx, 'image-int32', 'alpha', 'int', 'nearest')
+            : getTexture(`level${level}`, ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
+        texture.define(size, size);
         const framebuffer = getFramebuffer(`level${level}`, ctx);
         texture.attachFramebuffer(framebuffer, 0);
         textureFramebuffer = { texture, framebuffer };
-        textureFramebuffer.texture.define(size, size);
         LevelTexturesFramebuffers[level] = textureFramebuffer;
     }
     return textureFramebuffer;
@@ -122,20 +129,29 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
     // This part set the levels
     const levels = Math.ceil(Math.log(w) / Math.log(2));
     const maxSize = Math.pow(2, levels);
-    // console.log('levels', levels, 'maxSize', maxSize, 'input', w);
+    const maxSizeX = Math.pow(2, levels);
+    const maxSizeY = Math.pow(2, levels - 1);
+    // console.log('levels', levels, 'maxSize', maxSize, [maxSizeX, maxSizeY], 'input', w);
 
-    const pyramidTex = getTexture('pyramid', ctx, 'image-float32', 'rgba', 'float', 'nearest');
-    pyramidTex.define(maxSize, maxSize);
+    const pyramidTex = ctx.isWebGL2
+        ? getTexture('pyramid', ctx, 'image-int32', 'alpha', 'int', 'nearest')
+        : getTexture('pyramid', ctx, 'image-uint8', 'rgba', 'ubyte', 'nearest');
+    pyramidTex.define(maxSizeX, maxSizeY);
 
     const framebuffer = getFramebuffer('pyramid', ctx);
     pyramidTex.attachFramebuffer(framebuffer, 0);
-    gl.viewport(0, 0, maxSize, maxSize);
-    gl.clear(gl.COLOR_BUFFER_BIT);
+
+    gl.viewport(0, 0, maxSizeX, maxSizeY);
+    if (isWebGL2(gl)) {
+        gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
+    } else {
+        gl.clear(gl.COLOR_BUFFER_BIT);
+    }
 
     const levelTexturesFramebuffers: TextureFramebuffer[] = [];
     for (let i = 0; i < levels; ++i) levelTexturesFramebuffers.push(getLevelTextureFramebuffer(ctx, i));
 
-    const renderable = getHistopyramidReductionRenderable(ctx, inputTexture);
+    const renderable = getHistopyramidReductionRenderable(ctx, inputTexture, levelTexturesFramebuffers[0].texture);
     ctx.state.currentRenderItemId = -1;
     setRenderingDefaults(ctx);
 
@@ -146,7 +162,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
         tf.framebuffer.bind();
 
         const size = Math.pow(2, currLevel);
-        // console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i)
+        // console.log('size', size, 'draw-level', currLevel, 'read-level', levels - i);
 
         ValueCell.update(renderable.values.uSize, Math.pow(2, i + 1) / maxSize);
         ValueCell.update(renderable.values.uTexSize, size);
@@ -158,7 +174,11 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
         ctx.state.currentRenderItemId = -1;
         gl.viewport(0, 0, size, size);
         gl.scissor(0, 0, size, size);
-        gl.clear(gl.COLOR_BUFFER_BIT);
+        if (isWebGL2(gl)) {
+            gl.clearBufferiv(gl.COLOR, 0, [0, 0, 0, 0]);
+        } else {
+            gl.clear(gl.COLOR_BUFFER_BIT);
+        }
         gl.scissor(0, 0, gridTexDim[0], gridTexDim[1]);
         renderable.render();
 
@@ -179,7 +199,7 @@ export function createHistogramPyramid(ctx: WebGLContext, inputTexture: Texture,
     const count = Math.max(1, getHistopyramidSum(ctx, levelTexturesFramebuffers[0].texture));
     const height = Math.ceil(count / Math.pow(2, levels));
     // const scale = Vec2.create(maxSize / inputTexture.width, maxSize / inputTexture.height);
-    // console.log('height', height, 'finalCount', finalCount, 'scale', scale);
+    // console.log('height', height, 'finalCount', count, 'scale', scale);
 
     return { pyramidTex, count, height, levels, scale };
 }
\ No newline at end of file
diff --git a/src/mol-gl/compute/histogram-pyramid/sum.ts b/src/mol-gl/compute/histogram-pyramid/sum.ts
index 6461df6ca112872c4539f9b838e4738094492ff6..a20d779a78ee205637196263600f81c56a9cf422 100644
--- a/src/mol-gl/compute/histogram-pyramid/sum.ts
+++ b/src/mol-gl/compute/histogram-pyramid/sum.ts
@@ -1,10 +1,10 @@
 /**
- * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { createComputeRenderable } from '../../renderable';
+import { ComputeRenderable, createComputeRenderable } from '../../renderable';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec } from '../../renderable/schema';
@@ -15,17 +15,19 @@ import { decodeFloatRGB } from '../../../mol-util/float-packing';
 import { QuadSchema, QuadValues } from '../util';
 import quad_vert from '../../../mol-gl/shader/quad.vert';
 import sum_frag from '../../../mol-gl/shader/histogram-pyramid/sum.frag';
+import { isWebGL2 } from '../../webgl/compat';
 
 const HistopyramidSumSchema = {
     ...QuadSchema,
     tTexture: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 };
+type HistopyramidSumValues = Values<typeof HistopyramidSumSchema>
 
 const HistopyramidSumName = 'histopyramid-sum';
 
-function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
+function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture): ComputeRenderable<HistopyramidSumValues> {
     if (ctx.namedComputeRenderables[HistopyramidSumName]) {
-        const v = ctx.namedComputeRenderables[HistopyramidSumName].values;
+        const v = ctx.namedComputeRenderables[HistopyramidSumName].values as HistopyramidSumValues;
 
         ValueCell.update(v.tTexture, texture);
 
@@ -37,13 +39,13 @@ function getHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
 }
 
 function createHistopyramidSumRenderable(ctx: WebGLContext, texture: Texture) {
-    const values: Values<typeof HistopyramidSumSchema> = {
+    const values: HistopyramidSumValues = {
         ...QuadValues,
         tTexture: ValueCell.create(texture),
     };
 
     const schema = { ...HistopyramidSumSchema };
-    const shaderCode = ShaderCode('sum', quad_vert, sum_frag);
+    const shaderCode = ShaderCode('sum', quad_vert, sum_frag, {}, { 0: 'ivec4' });
     const renderItem = createComputeRenderItem(ctx, 'triangles', shaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
@@ -60,7 +62,9 @@ function setRenderingDefaults(ctx: WebGLContext) {
     state.clearColor(0, 0, 0, 0);
 }
 
-const sumArray = new Uint8Array(4);
+const sumBytes = new Uint8Array(4);
+const sumInts = new Int32Array(4);
+
 export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture) {
     const { gl, resources } = ctx;
 
@@ -73,7 +77,9 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
     const framebuffer = ctx.namedFramebuffers[HistopyramidSumName];
 
     if (!ctx.namedTextures[HistopyramidSumName]) {
-        ctx.namedTextures[HistopyramidSumName] = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+        ctx.namedTextures[HistopyramidSumName] = isWebGL2(gl)
+            ? resources.texture('image-int32', 'rgba', 'int', 'nearest')
+            : resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
         ctx.namedTextures[HistopyramidSumName].define(1, 1);
     }
     const sumTexture = ctx.namedTextures[HistopyramidSumName];
@@ -84,8 +90,11 @@ export function getHistopyramidSum(ctx: WebGLContext, pyramidTopTexture: Texture
     gl.viewport(0, 0, 1, 1);
     renderable.render();
     gl.finish();
-    ctx.readPixels(0, 0, 1, 1, sumArray);
+
+    ctx.readPixels(0, 0, 1, 1, isWebGL2(gl) ? sumInts : sumBytes);
     ctx.unbindFramebuffer();
 
-    return decodeFloatRGB(sumArray[0], sumArray[1], sumArray[2]);
+    return isWebGL2(gl)
+        ? sumInts[0]
+        : decodeFloatRGB(sumBytes[0], sumBytes[1], sumBytes[2]);
 }
\ No newline at end of file
diff --git a/src/mol-gl/compute/marching-cubes/active-voxels.ts b/src/mol-gl/compute/marching-cubes/active-voxels.ts
index c22119b447755a0a079b58f96530ecbcfda0edf0..04fac2e39d44dd249c20c0b41b4032ebd3f9eff3 100644
--- a/src/mol-gl/compute/marching-cubes/active-voxels.ts
+++ b/src/mol-gl/compute/marching-cubes/active-voxels.ts
@@ -4,7 +4,7 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { createComputeRenderable } from '../../renderable';
+import { ComputeRenderable, createComputeRenderable } from '../../renderable';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
 import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
@@ -29,12 +29,13 @@ const ActiveVoxelsSchema = {
 
     uScale: UniformSpec('v2'),
 };
+type ActiveVoxelsValues = Values<typeof ActiveVoxelsSchema>
 
 const ActiveVoxelsName = 'active-voxels';
 
-function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
+function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2): ComputeRenderable<ActiveVoxelsValues> {
     if (ctx.namedComputeRenderables[ActiveVoxelsName]) {
-        const v = ctx.namedComputeRenderables[ActiveVoxelsName].values;
+        const v = ctx.namedComputeRenderables[ActiveVoxelsName].values as ActiveVoxelsValues;
 
         ValueCell.update(v.uQuadScale, scale);
         ValueCell.update(v.tVolumeData, volumeData);
@@ -51,7 +52,7 @@ function getActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridD
 }
 
 function createActiveVoxelsRenderable(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, isoValue: number, scale: Vec2) {
-    const values: Values<typeof ActiveVoxelsSchema> = {
+    const values: ActiveVoxelsValues = {
         ...QuadValues,
         tTriCount: ValueCell.create(getTriCount()),
 
diff --git a/src/mol-gl/compute/marching-cubes/isosurface.ts b/src/mol-gl/compute/marching-cubes/isosurface.ts
index 4217ab20e94d52459c1c3dcdc5c374d6dff129e0..994c55bfd04992d5627435730e98f67061f785ea 100644
--- a/src/mol-gl/compute/marching-cubes/isosurface.ts
+++ b/src/mol-gl/compute/marching-cubes/isosurface.ts
@@ -4,19 +4,21 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
-import { createComputeRenderable } from '../../renderable';
+import { ComputeRenderable, createComputeRenderable } from '../../renderable';
 import { WebGLContext } from '../../webgl/context';
 import { createComputeRenderItem } from '../../webgl/render-item';
-import { Values, TextureSpec, UniformSpec } from '../../renderable/schema';
+import { Values, TextureSpec, UniformSpec, DefineSpec } from '../../renderable/schema';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { ShaderCode } from '../../../mol-gl/shader-code';
 import { ValueCell } from '../../../mol-util';
 import { Vec3, Vec2, Mat4 } from '../../../mol-math/linear-algebra';
 import { QuadSchema, QuadValues } from '../util';
-import { HistogramPyramid } from '../histogram-pyramid/reduction';
+import { createHistogramPyramid, HistogramPyramid } from '../histogram-pyramid/reduction';
 import { getTriIndices } from './tables';
 import quad_vert from '../../../mol-gl/shader/quad.vert';
 import isosurface_frag from '../../../mol-gl/shader/marching-cubes/isosurface.frag';
+import { calcActiveVoxels } from './active-voxels';
+import { isWebGL2 } from '../../webgl/compat';
 
 const IsosurfaceSchema = {
     ...QuadSchema,
@@ -34,15 +36,17 @@ const IsosurfaceSchema = {
     uGridDim: UniformSpec('v3'),
     uGridTexDim: UniformSpec('v3'),
     uGridTransform: UniformSpec('m4'),
-
     uScale: UniformSpec('v2'),
+
+    dPackedGroup: DefineSpec('boolean')
 };
+type IsosurfaceValues = Values<typeof IsosurfaceSchema>
 
 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) {
+function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean): ComputeRenderable<IsosurfaceValues> {
     if (ctx.namedComputeRenderables[IsosurfaceName]) {
-        const v = ctx.namedComputeRenderables[IsosurfaceName].values;
+        const v = ctx.namedComputeRenderables[IsosurfaceName].values as IsosurfaceValues;
 
         ValueCell.update(v.tActiveVoxelsPyramid, activeVoxelsPyramid);
         ValueCell.update(v.tActiveVoxelsBase, activeVoxelsBase);
@@ -58,16 +62,18 @@ function getIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture
         ValueCell.update(v.uGridTransform, transform);
         ValueCell.update(v.uScale, scale);
 
+        ValueCell.update(v.dPackedGroup, packedGroup);
+
         ctx.namedComputeRenderables[IsosurfaceName].update();
     } else {
-        ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
+        ctx.namedComputeRenderables[IsosurfaceName] = createIsosurfaceRenderable(ctx, activeVoxelsPyramid, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
     }
     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) {
+function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Texture, activeVoxelsBase: Texture, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, levels: number, scale: Vec2, count: number, packedGroup: boolean) {
     // console.log('uSize', Math.pow(2, levels))
-    const values: Values<typeof IsosurfaceSchema> = {
+    const values: IsosurfaceValues = {
         ...QuadValues,
         tTriIndices: ValueCell.create(getTriIndices()),
 
@@ -84,6 +90,8 @@ function createIsosurfaceRenderable(ctx: WebGLContext, activeVoxelsPyramid: Text
         uGridTexDim: ValueCell.create(gridTexDim),
         uGridTransform: ValueCell.create(transform),
         uScale: ValueCell.create(scale),
+
+        dPackedGroup: ValueCell.create(packedGroup)
     };
 
     const schema = { ...IsosurfaceSchema };
@@ -104,65 +112,72 @@ function setRenderingDefaults(ctx: WebGLContext) {
     state.clearColor(0, 0, 0, 0);
 }
 
-export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, vertexGroupTexture?: Texture, normalTexture?: Texture) {
-    const { gl, resources } = ctx;
+export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Texture, volumeData: Texture, histogramPyramid: HistogramPyramid, gridDim: Vec3, gridTexDim: Vec3, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
+    const { gl, resources, extensions } = ctx;
     const { pyramidTex, height, levels, scale, count } = histogramPyramid;
     const width = pyramidTex.getWidth();
 
-    // console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim)
-    // console.log('iso volumeData', volumeData)
+    // console.log('width', width, 'height', height);
+    // console.log('iso', 'gridDim', gridDim, 'scale', scale, 'gridTexDim', gridTexDim);
+    // console.log('iso volumeData', volumeData);
 
     if (!ctx.namedFramebuffers[IsosurfaceName]) {
         ctx.namedFramebuffers[IsosurfaceName] = resources.framebuffer();
     }
     const framebuffer = ctx.namedFramebuffers[IsosurfaceName];
 
-    if (!vertexGroupTexture) {
-        vertexGroupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
-    }
-    vertexGroupTexture.define(width, height);
-
-    if (!normalTexture) {
-        normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
-    }
-    normalTexture.define(width, height);
+    if (isWebGL2(gl)) {
+        if (!vertexTexture) {
+            vertexTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
+                ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
+                : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!groupTexture) {
+            groupTexture = resources.texture('image-uint8', 'rgba', 'ubyte', 'nearest');
+        }
+
+        if (!normalTexture) {
+            normalTexture = extensions.colorBufferHalfFloat && extensions.textureHalfFloat
+                ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
+                : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+    } else {
+        // in webgl1 drawbuffers must be in the same format for some reason
+        // this is quite wasteful but good enough for medium size meshes
 
-    // const infoTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-    // infoTex.define(pyramidTex.width, pyramidTex.height)
+        if (!vertexTexture) {
+            vertexTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
 
-    // const pointTexA = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-    // pointTexA.define(pyramidTex.width, pyramidTex.height)
+        if (!groupTexture) {
+            groupTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
 
-    // const pointTexB = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-    // pointTexB.define(pyramidTex.width, pyramidTex.height)
+        if (!normalTexture) {
+            normalTexture = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+    }
 
-    // const coordTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-    // coordTex.define(pyramidTex.width, pyramidTex.height)
+    vertexTexture.define(width, height);
+    groupTexture.define(width, height);
+    normalTexture.define(width, height);
 
-    // const indexTex = createTexture(ctx, 'image-float32', 'rgba', 'float', 'nearest')
-    // indexTex.define(pyramidTex.width, pyramidTex.height)
+    vertexTexture.attachFramebuffer(framebuffer, 0);
+    groupTexture.attachFramebuffer(framebuffer, 1);
+    normalTexture.attachFramebuffer(framebuffer, 2);
 
-    const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, height);
+    const renderable = getIsosurfaceRenderable(ctx, pyramidTex, activeVoxelsBase, volumeData, gridDim, gridTexDim, transform, isoValue, levels, scale, count, packedGroup);
     ctx.state.currentRenderItemId = -1;
 
-    vertexGroupTexture.attachFramebuffer(framebuffer, 0);
-    normalTexture.attachFramebuffer(framebuffer, 1);
-    // infoTex.attachFramebuffer(framebuffer, 1)
-    // pointTexA.attachFramebuffer(framebuffer, 2)
-    // pointTexB.attachFramebuffer(framebuffer, 3)
-    // coordTex.attachFramebuffer(framebuffer, 4)
-    // indexTex.attachFramebuffer(framebuffer, 5)
-
     const { drawBuffers } = ctx.extensions;
     if (!drawBuffers) throw new Error('need WebGL draw buffers');
 
+    framebuffer.bind();
     drawBuffers.drawBuffers([
         drawBuffers.COLOR_ATTACHMENT0,
         drawBuffers.COLOR_ATTACHMENT1,
-        // drawBuffers.COLOR_ATTACHMENT2,
-        // drawBuffers.COLOR_ATTACHMENT3,
-        // drawBuffers.COLOR_ATTACHMENT4,
-        // drawBuffers.COLOR_ATTACHMENT5
+        drawBuffers.COLOR_ATTACHMENT2,
     ]);
 
     setRenderingDefaults(ctx);
@@ -172,45 +187,26 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
 
     gl.flush();
 
-    // const vgt = readTexture(ctx, vertexGroupTexture, pyramidTex.width, height)
-    // console.log('vertexGroupTexture', vgt.array.subarray(0, 4 * count))
-
-    // const vt = readTexture(ctx, verticesTex, pyramidTex.width, height)
-    // console.log('vt', vt)
-    // const vertices = new Float32Array(3 * compacted.count)
-    // for (let i = 0; i < compacted.count; ++i) {
-    //     vertices[i * 3] = vt.array[i * 4]
-    //     vertices[i * 3 + 1] = vt.array[i * 4 + 1]
-    //     vertices[i * 3 + 2] = vt.array[i * 4 + 2]
-    // }
-    // console.log('vertices', vertices)
-
-    // const it = readTexture(ctx, infoTex, pyramidTex.width, height)
-    // console.log('info', it.array.subarray(0, 4 * compacted.count))
-
-    // const pat = readTexture(ctx, pointTexA, pyramidTex.width, height)
-    // console.log('point a', pat.array.subarray(0, 4 * compacted.count))
-
-    // const pbt = readTexture(ctx, pointTexB, pyramidTex.width, height)
-    // console.log('point b', pbt.array.subarray(0, 4 * compacted.count))
+    return { vertexTexture, groupTexture, normalTexture, vertexCount: count };
+}
 
-    // const ct = readTexture(ctx, coordTex, pyramidTex.width, height)
-    // console.log('coord', ct.array.subarray(0, 4 * compacted.count))
+//
 
-    // const idxt = readTexture(ctx, indexTex, pyramidTex.width, height)
-    // console.log('index', idxt.array.subarray(0, 4 * compacted.count))
+export function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
+    // console.time('calcActiveVoxels');
+    const activeVoxelsTex = calcActiveVoxels(ctx, volumeData, gridDim, gridTexDim, isoValue, gridTexScale);
+    // ctx.webgl.waitForGpuCommandsCompleteSync();
+    // console.timeEnd('calcActiveVoxels');
 
-    // const { field, idField } = await fieldFromTexture2d(ctx, volumeData, gridDimensions)
-    // console.log({ field, idField })
+    // console.time('createHistogramPyramid');
+    const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
+    // ctx.webgl.waitForGpuCommandsCompleteSync();
+    // console.timeEnd('createHistogramPyramid');
 
-    // const valuesA = new Float32Array(compacted.count)
-    // const valuesB = new Float32Array(compacted.count)
-    // for (let i = 0; i < compacted.count; ++i) {
-    //     valuesA[i] = field.space.get(field.data, pat.array[i * 4], pat.array[i * 4 + 1], pat.array[i * 4 + 2])
-    //     valuesB[i] = field.space.get(field.data, pbt.array[i * 4], pbt.array[i * 4 + 1], pbt.array[i * 4 + 2])
-    // }
-    // console.log('valuesA', valuesA)
-    // console.log('valuesB', valuesB)
+    // console.time('createIsosurfaceBuffers');
+    const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
+    // ctx.webgl.waitForGpuCommandsCompleteSync();
+    // console.timeEnd('createIsosurfaceBuffers');
 
-    return { vertexGroupTexture, normalTexture, vertexCount: count };
+    return gv;
 }
\ No newline at end of file
diff --git a/src/mol-gl/renderable/texture-mesh.ts b/src/mol-gl/renderable/texture-mesh.ts
index b6eb7fc62cd8c8c3bf9e497192e1485a9c31f4d8..66c5308fb59d94ffc337afccbefd47f8462b4356 100644
--- a/src/mol-gl/renderable/texture-mesh.ts
+++ b/src/mol-gl/renderable/texture-mesh.ts
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -7,17 +7,16 @@
 import { Renderable, RenderableState, createRenderable } from '../renderable';
 import { WebGLContext } from '../webgl/context';
 import { createGraphicsRenderItem } from '../webgl/render-item';
-import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema, AttributeSpec } from './schema';
+import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema } from './schema';
 import { MeshShaderCode } from '../shader-code';
 import { ValueCell } from '../../mol-util';
 
 export const TextureMeshSchema = {
     ...BaseSchema,
-    aGroup: AttributeSpec('float32', 1, 0),
     uGeoTexDim: UniformSpec('v2'),
-    /** texture has vertex positions in XYZ and group id in W */
-    tPositionGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
-    tNormal: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'),
+    tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'),
+    tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'),
 
     dFlatShaded: DefineSpec('boolean'),
     dDoubleSided: DefineSpec('boolean'),
diff --git a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
index a78c62cf64b924ec8b313f99881e9874c21eb89f..14717342505408ce3ca032b4b638c59de74b80ac 100644
--- a/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
+++ b/src/mol-gl/shader/chunks/assign-color-varying.glsl.ts
@@ -9,9 +9,9 @@ export default `
     #elif defined(dColorType_groupInstance)
         vColor.rgb = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
     #elif defined(dColorType_vertex)
-        vColor.rgb = readFromTexture(tColor, aVertex, uColorTexDim).rgb;
+        vColor.rgb = readFromTexture(tColor, VertexID, uColorTexDim).rgb;
     #elif defined(dColorType_vertexInstance)
-        vColor.rgb = readFromTexture(tColor, aInstance * float(uVertexCount) + aVertex, uColorTexDim).rgb;
+        vColor.rgb = readFromTexture(tColor, int(aInstance) * uVertexCount + VertexID, uColorTexDim).rgb;
     #endif
 
     #ifdef dOverpaint
diff --git a/src/mol-gl/shader/chunks/assign-group.glsl.ts b/src/mol-gl/shader/chunks/assign-group.glsl.ts
index 9af3e1dbdd950c4fb4462de893b3ea3437074d8f..dc1a163693a5c4e542e951214d494f9bb177ebd4 100644
--- a/src/mol-gl/shader/chunks/assign-group.glsl.ts
+++ b/src/mol-gl/shader/chunks/assign-group.glsl.ts
@@ -1,7 +1,6 @@
 export default `
 #ifdef dGeoTexture
-    // aGroup is used as a vertex index here and the group id is retirieved from tPositionGroup
-    float group = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).w;
+    float group = decodeFloatRGB(readFromTexture(tGroup, VertexID, uGeoTexDim).rgb);
 #else
     float group = aGroup;
 #endif
diff --git a/src/mol-gl/shader/chunks/assign-position.glsl.ts b/src/mol-gl/shader/chunks/assign-position.glsl.ts
index 7cb5bcac0f33605c142e1aa22030cc4a219e7ee5..14a164dece50711c57e1c898e1d0c3588778d5df 100644
--- a/src/mol-gl/shader/chunks/assign-position.glsl.ts
+++ b/src/mol-gl/shader/chunks/assign-position.glsl.ts
@@ -2,7 +2,7 @@ export default `
 mat4 model = uModel * aTransform;
 mat4 modelView = uView * model;
 #ifdef dGeoTexture
-    vec3 position = readFromTexture(tPositionGroup, aGroup, uGeoTexDim).xyz;
+    vec3 position = readFromTexture(tPosition, VertexID, uGeoTexDim).xyz;
 #else
     vec3 position = aPosition;
 #endif
diff --git a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts
index 3cc4112782ce1d0eb94e22af02756248678dd652..06c6cedb807b02b7006768703daf6acc188c9852 100644
--- a/src/mol-gl/shader/chunks/color-vert-params.glsl.ts
+++ b/src/mol-gl/shader/chunks/color-vert-params.glsl.ts
@@ -11,14 +11,6 @@ export default `
         uniform sampler2D tColor;
     #endif
 
-    #if defined(dColorType_vertex) || defined(dColorType_vertexInstance)
-        #if __VERSION__ == 100
-            attribute float aVertex;
-        #else
-            #define aVertex float(gl_VertexID)
-        #endif
-    #endif
-
     #ifdef dOverpaint
         varying vec4 vOverpaint;
         uniform vec2 uOverpaintTexDim;
diff --git a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts
index 965f46a0d6cf84eacbeb0dff2480d4f1e3be27a2..9ee8e3d95f02d19a6a9bd3a040b6a2539c031563 100644
--- a/src/mol-gl/shader/chunks/common-vert-params.glsl.ts
+++ b/src/mol-gl/shader/chunks/common-vert-params.glsl.ts
@@ -36,4 +36,11 @@ uniform sampler2D tMarker;
 
 varying vec3 vModelPosition;
 varying vec3 vViewPosition;
+
+#if __VERSION__ == 100
+    attribute float aVertex;
+    #define VertexID int(aVertex)
+#else
+    #define VertexID gl_VertexID
+#endif
 `;
\ No newline at end of file
diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts
index 1786927b9995637d10da794477ad012b4b4fd4e6..1d64760ef26b09366119cce8cbf7302d39ec188a 100644
--- a/src/mol-gl/shader/chunks/common.glsl.ts
+++ b/src/mol-gl/shader/chunks/common.glsl.ts
@@ -28,6 +28,7 @@ export default `
 float intDiv(const in float a, const in float b) { return float(int(a) / int(b)); }
 vec2 ivec2Div(const in vec2 a, const in vec2 b) { return vec2(ivec2(a) / ivec2(b)); }
 float intMod(const in float a, const in float b) { return a - b * float(int(a) / int(b)); }
+int imod(const in int a, const in int b) { return a - b * (a / b); }
 
 float pow2(const in float x) { return x * x; }
 
diff --git a/src/mol-gl/shader/chunks/read-from-texture.glsl.ts b/src/mol-gl/shader/chunks/read-from-texture.glsl.ts
index 4e97075a1c2bead711b00ac4c8db8d4733df65fd..8d7c6184c1c375cdf6e70815700e1a77850d885a 100644
--- a/src/mol-gl/shader/chunks/read-from-texture.glsl.ts
+++ b/src/mol-gl/shader/chunks/read-from-texture.glsl.ts
@@ -5,10 +5,17 @@
  */
 
 export default `
-vec4 readFromTexture (const in sampler2D tex, const in float i, const in vec2 dim) {
+vec4 readFromTexture(const in sampler2D tex, const in float i, const in vec2 dim) {
     float x = intMod(i, dim.x);
     float y = floor(intDiv(i, dim.x));
     vec2 uv = (vec2(x, y) + 0.5) / dim;
     return texture2D(tex, uv);
 }
+
+vec4 readFromTexture(const in sampler2D tex, const in int i, const in vec2 dim) {
+    int x = imod(i, int(dim.x));
+    int y = i / int(dim.x);
+    vec2 uv = (vec2(x, y) + 0.5) / dim;
+    return texture2D(tex, uv);
+}
 `;
\ No newline at end of file
diff --git a/src/mol-gl/shader/histogram-pyramid/reduction.frag.ts b/src/mol-gl/shader/histogram-pyramid/reduction.frag.ts
index d7f44c82e3702c6de69e5c69ee55e7e6a4409f86..867168c913b02a8ff7d67832a98cc753ceac299f 100644
--- a/src/mol-gl/shader/histogram-pyramid/reduction.frag.ts
+++ b/src/mol-gl/shader/histogram-pyramid/reduction.frag.ts
@@ -1,35 +1,59 @@
 export default `
 precision highp float;
+precision highp int;
 precision highp sampler2D;
 
-// input texture (previous level used to evaluate the new level)
-uniform sampler2D tPreviousLevel;
+uniform sampler2D tInputLevel;
+
+// previous level used to evaluate the new level
+#if __VERSION__ == 100
+    uniform sampler2D tPreviousLevel;
+#else
+    precision highp isampler2D;
+    uniform isampler2D tPreviousLevel;
+#endif
 
 // inverted size of the previous level texture.
 uniform float uSize;
 uniform float uTexSize;
 uniform bool uFirst;
 
+#include common
+
 void main(void) {
     float k = 0.5 * uSize;
     vec2 position = floor((gl_FragCoord.xy / uTexSize) / uSize) * uSize;
-    float a, b, c, d;
-
-    if (uFirst) {
-        a = texture2D(tPreviousLevel, position).r * 255.0;
-        b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r * 255.0;
-        c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r * 255.0;
-        d = texture2D(tPreviousLevel, position + vec2(k, k)).r * 255.0;
-    } else {
-        a = texture2D(tPreviousLevel, position).r;
-        b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r;
-        c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r;
-        d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
-    }
-
-    gl_FragColor.a = a;
-    gl_FragColor.b = a + b;
-    gl_FragColor.g = gl_FragColor.b + c;
-    gl_FragColor.r = gl_FragColor.g + d;
+
+    #if __VERSION__ == 100
+        float a, b, c, d;
+
+        if (uFirst) {
+            a = texture2D(tInputLevel, position).r * 255.0;
+            b = texture2D(tInputLevel, position + vec2(k, 0.0)).r * 255.0;
+            c = texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0;
+            d = texture2D(tInputLevel, position + vec2(k, k)).r * 255.0;
+        } else {
+            a = decodeFloatRGB(texture2D(tPreviousLevel, position).rgb);
+            b = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, 0.0)).rgb);
+            c = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(0.0, k)).rgb);
+            d = decodeFloatRGB(texture2D(tPreviousLevel, position + vec2(k, k)).rgb);
+        }
+        gl_FragColor = vec4(encodeFloatRGB(a + b + c + d), 1.0);
+    #else
+        int a, b, c, d;
+
+        if (uFirst) {
+            a = int(texture2D(tInputLevel, position).r * 255.0);
+            b = int(texture2D(tInputLevel, position + vec2(k, 0.0)).r * 255.0);
+            c = int(texture2D(tInputLevel, position + vec2(0.0, k)).r * 255.0);
+            d = int(texture2D(tInputLevel, position + vec2(k, k)).r * 255.0);
+        } else {
+            a = texture2D(tPreviousLevel, position).r;
+            b = texture2D(tPreviousLevel, position + vec2(k, 0.0)).r;
+            c = texture2D(tPreviousLevel, position + vec2(0.0, k)).r;
+            d = texture2D(tPreviousLevel, position + vec2(k, k)).r;
+        }
+        gl_FragColor = ivec4(a + b + c + d);
+    #endif
 }
 `;
\ No newline at end of file
diff --git a/src/mol-gl/shader/histogram-pyramid/sum.frag.ts b/src/mol-gl/shader/histogram-pyramid/sum.frag.ts
index fbe31eccc5d9d083c1d4ff0a7cbba15e6286195d..7760ee52306d7f1b12e77de143c35beaee5ec098 100644
--- a/src/mol-gl/shader/histogram-pyramid/sum.frag.ts
+++ b/src/mol-gl/shader/histogram-pyramid/sum.frag.ts
@@ -1,18 +1,26 @@
 /**
- * Copyright (c) 2019 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2019-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
 export default `
 precision highp float;
-precision highp sampler2D;
+precision highp int;
 
-uniform sampler2D tTexture;
-
-#include common
+#if __VERSION__ == 100
+    precision highp sampler2D;
+    uniform sampler2D tTexture;
+#else
+    precision highp isampler2D;
+    uniform isampler2D tTexture;
+#endif
 
 void main(void) {
-    gl_FragColor = vec4(encodeFloatRGB(texture2D(tTexture, vec2(0.5)).r), 1.0);
+    #if __VERSION__ == 100
+        gl_FragColor = texture2D(tTexture, vec2(0.5));
+    #else
+        gl_FragColor = ivec4(texture2D(tTexture, vec2(0.5)).r);
+    #endif
 }
 `;
\ No newline at end of file
diff --git a/src/mol-gl/shader/marching-cubes/isosurface.frag.ts b/src/mol-gl/shader/marching-cubes/isosurface.frag.ts
index e29802d761f3c799c2ef7ae2ed305c770e436edb..75d5f3a610a35a11f97e5c09687bf37c0068d5f7 100644
--- a/src/mol-gl/shader/marching-cubes/isosurface.frag.ts
+++ b/src/mol-gl/shader/marching-cubes/isosurface.frag.ts
@@ -3,7 +3,13 @@ precision highp float;
 precision highp int;
 precision highp sampler2D;
 
-uniform sampler2D tActiveVoxelsPyramid;
+#if __VERSION__ == 100
+    uniform sampler2D tActiveVoxelsPyramid;
+#else
+    precision highp isampler2D;
+    uniform isampler2D tActiveVoxelsPyramid;
+#endif
+
 uniform sampler2D tActiveVoxelsBase;
 uniform sampler2D tVolumeData;
 uniform sampler2D tTriIndices;
@@ -52,65 +58,114 @@ vec4 voxel(vec3 pos) {
     return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
 }
 
-vec4 voxel2(vec3 pos) {
-    pos = min(max(vec3(0.0), pos), uGridDim - vec3(vec2(2.0), 1.0));
+vec4 voxelPadded(vec3 pos) {
+    pos = min(max(vec3(0.0), pos), uGridDim - vec3(vec2(2.0), 1.0)); // remove xy padding
     return texture3dFrom2dNearest(tVolumeData, pos / uGridDim, uGridDim, uGridTexDim.xy);
 }
 
+int idot2(const in ivec2 a, const in ivec2 b) {
+    return a.x * b.x + a.y * b.y;
+}
+
+int idot4(const in ivec4 a, const in ivec4 b) {
+    return a.x * b.x + a.y * b.y + a.z * b.z + a.w * b.w;
+}
+
+#if __VERSION__ == 100
+    int pyramidVoxel(vec2 pos) {
+        return int(decodeFloatRGB(texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).rgb));
+    }
+#else
+    int pyramidVoxel(vec2 pos) {
+        return texture2D(tActiveVoxelsPyramid, pos / (vec2(1.0, 0.5) * uSize)).r;
+    }
+#endif
+
+vec4 baseVoxel(vec2 pos) {
+    return texture2D(tActiveVoxelsBase, pos / uSize);
+}
+
 void main(void) {
     // get 1D index
-    float vI = dot(floor(uSize * (gl_FragCoord.xy / uSize)), vec2(1.0, uSize));
+    int vI = int(gl_FragCoord.x) + int(gl_FragCoord.y) * int(uSize);
 
     // ignore 1D indices outside of the grid
-    if(vI >= uCount) discard;
-
-    float offset = uSize - 2.;
-    float k = 1. / uSize;
-
-    vec2 relativePosition = k * vec2(offset, 0.);
-    vec4 partialSums = texture2D(tActiveVoxelsPyramid, relativePosition);
-    float start = 0.;
-    vec4 starts = vec4(0.);
-    vec4 ends = vec4(0.);
-    float diff = 2.;
-    vec4 m = vec4(0.);
-    vec2 position = vec2(0.);
-    vec4 vI4 = vec4(vI);
+    if(vI >= int(uCount)) discard;
+
+    ivec2 offset = ivec2(int(uSize) - 2, 0);
+
+    int start = 0;
+    ivec4 starts = ivec4(0);
+    ivec4 ends = ivec4(0);
+    int diff = 2;
+    ivec4 m = ivec4(0);
+    ivec2 position = ivec2(0);
+    ivec4 vI4 = ivec4(vI);
+
+    ivec2 relativePosition = ivec2(0);
+    int end = 0;
+    ivec2 pos1 = ivec2(0);
+    ivec2 pos2 = ivec2(0);
+    ivec2 pos3 = ivec2(0);
+    ivec2 pos4 = ivec2(0);
+    ivec3 vI3 = ivec3(vI);
+    ivec3 mask = ivec3(0);
 
     // traverse the different levels of the pyramid
     for(int i = 1; i < 14; i++) {
         if(float(i) >= uLevels) break;
 
-        offset -= diff;
-        diff *= 2.;
-        relativePosition = position + k * vec2(offset, 0.);
-
-        ends = partialSums.wzyx + vec4(start);
-        starts = vec4(start, ends.xyz);
-        m = vec4(greaterThanEqual(vI4, starts)) * vec4(lessThan(vI4, ends));
-        relativePosition += m.y * vec2(k, 0.) + m.z * vec2(0., k) + m.w * vec2(k, k);
-
-        start = dot(m, starts);
-        position = 2. * (relativePosition - k * vec2(offset, 0.));
-        partialSums = texture2D(tActiveVoxelsPyramid, relativePosition);
+        offset.x -= diff;
+        diff *= 2;
+        relativePosition = position + offset;
+
+        end = start + pyramidVoxel(vec2(relativePosition));
+        pos1 = ivec2(relativePosition);
+        starts.x = start;
+        ends.x = end;
+        pos2 = ivec2(relativePosition + ivec2(1, 0));
+        starts.y = ends.x;
+        ends.y = ends.x + pyramidVoxel(vec2(pos2));
+        pos3 = relativePosition + ivec2(0, 1);
+        starts.z = ends.y;
+        ends.z = ends.y + pyramidVoxel(vec2(pos3));
+        pos4 = relativePosition + ivec2(1, 1);
+        starts.w = ends.z;
+        mask = ivec3(greaterThanEqual(vI3, starts.rgb)) * ivec3(lessThan(vI3, ends.rgb));
+        m = ivec4(mask, 1 - int(any(bvec3(mask))));
+
+        relativePosition = m.x * pos1 + m.y * pos2 + m.z * pos3 + m.w * pos4;
+        start = idot4(m, starts);
+        position = 2 * (relativePosition - offset);
     }
 
-    ends = partialSums.wzyx + vec4(start);
-    starts = vec4(start, ends.xyz);
-    m = vec4(greaterThanEqual(vI4, starts)) * vec4(lessThan(vI4, ends));
-    position += m.y * vec2(k, 0.) + m.z * vec2(0., k) + m.w * vec2(k, k);
-
-    vec2 coord2d = position / uScale;
+    end = start + int(baseVoxel(vec2(position)).r * 255.0);
+    pos1 = position;
+    starts.x = start;
+    ends.x = end;
+    pos2 = position + ivec2(1, 0);
+    starts.y = ends.x;
+    ends.y = ends.x + int(baseVoxel(vec2(pos2)).r * 255.0);
+    pos3 = position + ivec2(0, 1);
+    starts.z = ends.y;
+    ends.z = ends.y + int(baseVoxel(vec2(pos3)).r * 255.0);
+    pos4 = position + ivec2(1, 1);
+    starts.w = ends.z;
+    mask = ivec3(greaterThanEqual(vI3, starts.rgb)) * ivec3(lessThan(vI3, ends.rgb));
+    m = ivec4(mask, 1 - int(any(bvec3(mask))));
+    position = m.x * pos1 + m.y * pos2 + m.z * pos3 + m.w * pos4;
+
+    vec2 coord2d = (vec2(position) / uSize) / uScale;
     vec3 coord3d = floor(index3dFrom2d(coord2d) + 0.5);
 
-    float edgeIndex = floor(texture2D(tActiveVoxelsBase, position).a * 255.0 + 0.5);
+    float edgeIndex = floor(baseVoxel(vec2(position)).a * 255.0 + 0.5);
 
     // current vertex for the up to 15 MC cases
-    float currentVertex = vI - dot(m, starts);
+    int currentVertex = vI - idot4(m, starts);
 
     // get index into triIndices table
-    float mcIndex = 16. * edgeIndex + currentVertex;
-    vec4 mcData = texture2D(tTriIndices, vec2(intMod(mcIndex, 64.), floor(mcIndex / 64.)) / 64.);
+    int mcIndex = 16 * int(edgeIndex) + currentVertex;
+    vec4 mcData = texture2D(tTriIndices, vec2(imod(mcIndex, 64), mcIndex / 64) / 64.);
 
     // bit mask to avoid conditionals (see comment below) for getting MC case corner
     vec4 m0 = vec4(floor(mcData.a * 255.0 + 0.5));
@@ -188,30 +243,41 @@ void main(void) {
     // group id
     #if __VERSION__ == 100
         // webgl1 does not support 'flat' interpolation (i.e. no interpolation)
-        // so we ensure a constant group id per triangle
-        gl_FragData[0].w = decodeFloatRGB(voxel(coord3d).rgb);
+        // so we ensure a constant group id per triangle here
+        #ifdef dPackedGroup
+            gl_FragData[1] = vec4(voxel(coord3d).rgb, 1.0);
+        #else
+            vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
+            float group = coord3d.z + coord3d.y * gridDim.z + coord3d.x * gridDim.z * gridDim.y;
+            gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
+        #endif
     #else
-        gl_FragData[0].w = t < 0.5 ? decodeFloatRGB(d0.rgb) : decodeFloatRGB(d1.rgb);
+        #ifdef dPackedGroup
+            gl_FragData[1] = vec4(t < 0.5 ? d0.rgb : d1.rgb, 1.0);
+        #else
+            vec3 b = t < 0.5 ? b0 : b1;
+            vec3 gridDim = uGridDim - vec3(1.0, 1.0, 0.0); // remove xy padding
+            float group = b.z + b.y * gridDim.z + b.x * gridDim.z * gridDim.y;
+            gl_FragData[1] = vec4(group > 16777215.5 ? vec3(1.0) : encodeFloatRGB(group), 1.0);
+        #endif
     #endif
 
     // normals from gradients
     vec3 n0 = -normalize(vec3(
-        voxel2(b0 - c1).a - voxel2(b0 + c1).a,
-        voxel2(b0 - c3).a - voxel2(b0 + c3).a,
-        voxel2(b0 - c4).a - voxel2(b0 + c4).a
+        voxelPadded(b0 - c1).a - voxelPadded(b0 + c1).a,
+        voxelPadded(b0 - c3).a - voxelPadded(b0 + c3).a,
+        voxelPadded(b0 - c4).a - voxelPadded(b0 + c4).a
     ));
     vec3 n1 = -normalize(vec3(
-        voxel2(b1 - c1).a - voxel2(b1 + c1).a,
-        voxel2(b1 - c3).a - voxel2(b1 + c3).a,
-        voxel2(b1 - c4).a - voxel2(b1 + c4).a
+        voxelPadded(b1 - c1).a - voxelPadded(b1 + c1).a,
+        voxelPadded(b1 - c3).a - voxelPadded(b1 + c3).a,
+        voxelPadded(b1 - c4).a - voxelPadded(b1 + c4).a
     ));
-    gl_FragData[1].xyz = -vec3(
+    mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform)));
+    gl_FragData[2].xyz = normalMatrix * -vec3(
         n0.x + t * (n0.x - n1.x),
         n0.y + t * (n0.y - n1.y),
         n0.z + t * (n0.z - n1.z)
     );
-
-    mat3 normalMatrix = transpose3(inverse3(mat3(uGridTransform)));
-    gl_FragData[1].xyz = normalMatrix * gl_FragData[1].xyz;
 }
 `;
\ No newline at end of file
diff --git a/src/mol-gl/shader/mesh.vert.ts b/src/mol-gl/shader/mesh.vert.ts
index 1117ccb5952867ee6897dc9b78d28e650862c6ee..9014a43b7209fedaf8b96ddd98bd3b630a19b69c 100644
--- a/src/mol-gl/shader/mesh.vert.ts
+++ b/src/mol-gl/shader/mesh.vert.ts
@@ -7,6 +7,7 @@
 export default `
 precision highp float;
 precision highp int;
+precision highp sampler2D;
 
 #include common
 #include read_from_texture
@@ -16,19 +17,17 @@ precision highp int;
 
 #ifdef dGeoTexture
     uniform vec2 uGeoTexDim;
-    uniform sampler2D tPositionGroup;
+    uniform sampler2D tPosition;
+    uniform sampler2D tGroup;
+    uniform sampler2D tNormal;
 #else
     attribute vec3 aPosition;
+    attribute float aGroup;
+    attribute vec3 aNormal;
 #endif
 attribute mat4 aTransform;
 attribute float aInstance;
-attribute float aGroup;
 
-#ifdef dGeoTexture
-    uniform sampler2D tNormal;
-#else
-    attribute vec3 aNormal;
-#endif
 varying vec3 vNormal;
 
 void main(){
@@ -40,7 +39,7 @@ void main(){
     #include clip_instance
 
     #ifdef dGeoTexture
-        vec3 normal = readFromTexture(tNormal, aGroup, uGeoTexDim).xyz;
+        vec3 normal = readFromTexture(tNormal, VertexID, uGeoTexDim).xyz;
     #else
         vec3 normal = aNormal;
     #endif
diff --git a/src/mol-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts
index 5cad275f33d16ebdb0f86d3edd4fdee4c2cd3638..5719c006de43a70fefd3dedf7775b0f970ed140b 100644
--- a/src/mol-gl/webgl/texture.ts
+++ b/src/mol-gl/webgl/texture.ts
@@ -57,6 +57,7 @@ export function getFormat(gl: GLRenderingContext, format: TextureFormat, type: T
     switch (format) {
         case 'alpha':
             if (isWebGL2(gl) && type === 'float') return gl.RED;
+            else if (isWebGL2(gl) && type === 'int') return gl.RED_INTEGER;
             else return gl.ALPHA;
         case 'rgb':
             if (isWebGL2(gl) && type === 'int') return gl.RGB_INTEGER;
diff --git a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts
index 546d98e3a8c817228209d2eadd93ff3df5f173dd..f63ed65b1bd54be7b6a07502a3b2b5bd8a2f9551 100644
--- a/src/mol-repr/structure/visual/gaussian-surface-mesh.ts
+++ b/src/mol-repr/structure/visual/gaussian-surface-mesh.ts
@@ -15,9 +15,7 @@ import { computeMarchingCubesMesh } from '../../../mol-geo/util/marching-cubes/a
 import { ElementIterator, getElementLoci, eachElement, getSerialElementLoci, eachSerialElement } from './util/element';
 import { VisualUpdateState } from '../../util';
 import { TextureMesh } from '../../../mol-geo/geometry/texture-mesh/texture-mesh';
-import { calcActiveVoxels } from '../../../mol-gl/compute/marching-cubes/active-voxels';
-import { createHistogramPyramid } from '../../../mol-gl/compute/histogram-pyramid/reduction';
-import { createIsosurfaceBuffers } from '../../../mol-gl/compute/marching-cubes/isosurface';
+import { extractIsosurface } from '../../../mol-gl/compute/marching-cubes/isosurface';
 import { Sphere3D } from '../../../mol-math/geometry';
 import { ComplexVisual, ComplexMeshParams, ComplexMeshVisual, ComplexTextureMeshVisual, ComplexTextureMeshParams } from '../complex-visual';
 import { getUnitExtraRadius, getStructureExtraRadius } from './util/common';
@@ -169,29 +167,11 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
 
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
-    // console.time('calcActiveVoxels');
-    const activeVoxelsTex = calcActiveVoxels(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, isoLevel, densityTextureData.gridTexScale);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('calcActiveVoxels');
-
-    // console.time('createHistogramPyramid');
-    const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, densityTextureData.gridTexScale, densityTextureData.gridTexDim);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createHistogramPyramid');
-
-    // console.time('createIsosurfaceBuffers');
-    const gv = createIsosurfaceBuffers(ctx.webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoLevel, textureMesh ? textureMesh.vertexGroupTexture.ref.value : undefined, textureMesh ? textureMesh.normalTexture.ref.value : undefined);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createIsosurfaceBuffers');
+    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
-    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();
+    const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
+
     return surface;
 }
 
@@ -214,8 +194,9 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
             return !props.useGpu || !webgl;
         },
         dispose: (geometry: TextureMesh) => {
+            geometry.vertexTexture.ref.value.destroy();
+            geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
-            geometry.vertexGroupTexture.ref.value.destroy();
         }
     }, materialId);
 }
@@ -243,29 +224,11 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
 
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
-    // console.time('calcActiveVoxels');
-    const activeVoxelsTex = calcActiveVoxels(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, isoLevel, densityTextureData.gridTexScale);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('calcActiveVoxels');
-
-    // console.time('createHistogramPyramid');
-    const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, densityTextureData.gridTexScale, densityTextureData.gridTexDim);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createHistogramPyramid');
-
-    // console.time('createIsosurfaceBuffers');
-    const gv = createIsosurfaceBuffers(ctx.webgl, activeVoxelsTex, densityTextureData.texture, compacted, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.transform, isoLevel, textureMesh ? textureMesh.vertexGroupTexture.ref.value : undefined, textureMesh ? textureMesh.normalTexture.ref.value : undefined);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createIsosurfaceBuffers');
+    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
-    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();
+    const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
+
     return surface;
 }
 
@@ -287,8 +250,9 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
             return !props.useGpu || !webgl;
         },
         dispose: (geometry: TextureMesh) => {
+            geometry.vertexTexture.ref.value.destroy();
+            geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
-            geometry.vertexGroupTexture.ref.value.destroy();
         }
     }, materialId);
 }
\ No newline at end of file
diff --git a/src/mol-repr/volume/isosurface.ts b/src/mol-repr/volume/isosurface.ts
index 7ffac46b5aa5db42b22551c52702d836e90b7a79..b3d48765917f97a64ff107014b0ce2484affdf18 100644
--- a/src/mol-repr/volume/isosurface.ts
+++ b/src/mol-repr/volume/isosurface.ts
@@ -24,9 +24,7 @@ import { Tensor, Vec2, Vec3 } from '../../mol-math/linear-algebra';
 import { fillSerial } from '../../mol-util/array';
 import { createVolumeTexture2d, eachVolumeLoci, getVolumeTexture2dLayout } from './util';
 import { TextureMesh } from '../../mol-geo/geometry/texture-mesh/texture-mesh';
-import { calcActiveVoxels } from '../../mol-gl/compute/marching-cubes/active-voxels';
-import { createHistogramPyramid } from '../../mol-gl/compute/histogram-pyramid/reduction';
-import { createIsosurfaceBuffers } from '../../mol-gl/compute/marching-cubes/isosurface';
+import { extractIsosurface } from '../../mol-gl/compute/marching-cubes/isosurface';
 import { WebGLContext } from '../../mol-gl/webgl/context';
 import { CustomPropertyDescriptor } from '../../mol-model/custom-property';
 import { Texture } from '../../mol-gl/webgl/texture';
@@ -128,11 +126,11 @@ namespace VolumeIsosurfaceTexture {
         // console.log({ texDim, width, height, gridDimension });
 
         if (!volume._propertyData[name]) {
-            volume._propertyData[name] = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
+            volume._propertyData[name] = resources.texture('image-uint8', 'alpha', 'ubyte', 'linear');
             const texture = volume._propertyData[name] as Texture;
             texture.define(texDim, texDim);
             // load volume into sub-section of texture
-            texture.load(createVolumeTexture2d(volume, 'groups', padding), true);
+            texture.load(createVolumeTexture2d(volume, 'data', Padding), true);
             volume.customProperties.add(descriptor);
             volume.customProperties.assets(descriptor, [{ dispose: () => texture.destroy() }]);
         }
@@ -160,28 +158,10 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
 
     const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
 
-    // console.time('calcActiveVoxels');
-    const activeVoxelsTex = calcActiveVoxels(ctx.webgl, texture, gridDimension, gridTexDim, isoLevel, gridTexScale);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('calcActiveVoxels');
-
-    // console.time('createHistogramPyramid');
-    const compacted = createHistogramPyramid(ctx.webgl, activeVoxelsTex, gridTexScale, gridTexDim);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createHistogramPyramid');
-
-    // console.time('createIsosurfaceBuffers');
-    const gv = createIsosurfaceBuffers(ctx.webgl, activeVoxelsTex, texture, compacted, gridDimension, gridTexDim, transform, isoLevel, textureMesh ? textureMesh.vertexGroupTexture.ref.value : undefined, textureMesh ? textureMesh.normalTexture.ref.value : undefined);
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
-    // console.timeEnd('createIsosurfaceBuffers');
-
-    const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexGroupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
-    // console.log({
-    //     renderables: ctx.webgl.namedComputeRenderables,
-    //     framebuffers: ctx.webgl.namedFramebuffers,
-    //     textures: ctx.webgl.namedTextures,
-    // });
-    // ctx.webgl.waitForGpuCommandsCompleteSync();
+    const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
+
+    const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
+
     return surface;
 }
 
@@ -200,8 +180,9 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
             return !props.useGpu || !webgl;
         },
         dispose: (geometry: TextureMesh) => {
+            geometry.vertexTexture.ref.value.destroy();
+            geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
-            geometry.vertexGroupTexture.ref.value.destroy();
         }
     }, materialId);
 }
diff --git a/src/mol-repr/volume/util.ts b/src/mol-repr/volume/util.ts
index b35fd08e82ac015fbeca86c4a0971b7926d8a4fe..486639421fb0b8caea8dde64771b8b993ddfd3f2 100644
--- a/src/mol-repr/volume/util.ts
+++ b/src/mol-repr/volume/util.ts
@@ -75,13 +75,14 @@ export function getVolumeTexture2dLayout(dim: Vec3, padding = 0) {
     return { width, height, columns, rows, powerOfTwoSize: height < powerOfTwoSize ? powerOfTwoSize : powerOfTwoSize * 2 };
 }
 
-export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'groups', padding = 0) {
+export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'groups' | 'data', padding = 0) {
     const { cells: { space, data }, stats: { max, min } } = volume.grid;
     const dim = space.dimensions as Vec3;
     const { dataOffset: o } = space;
     const { width, height } = getVolumeTexture2dLayout(dim, padding);
 
-    const array = new Uint8Array(width * height * 4);
+    const itemSize = variant === 'data' ? 1 : 4;
+    const array = new Uint8Array(width * height * itemSize);
     const textureImage = { array, width, height };
 
     const diff = max - min;
@@ -102,28 +103,32 @@ export function createVolumeTexture2d(volume: Volume, variant: 'normals' | 'grou
                 const column = Math.floor(((z * xnp) % width) / xnp);
                 const row = Math.floor((z * xnp) / width);
                 const px = column * xnp + x;
-                const index = 4 * ((row * ynp * width) + (y * width) + px);
+                const index = itemSize * ((row * ynp * width) + (y * width) + px);
                 const offset = o(x, y, z);
 
-                if (variant === 'groups') {
-                    encodeFloatRGBtoArray(offset, array, index);
+                if (variant === 'data') {
+                    array[index] = Math.round(((data[offset] - min) / diff) * 255);
                 } else {
-                    v3set(n0,
-                        data[o(Math.max(0, x - 1), y, z)],
-                        data[o(x, Math.max(0, y - 1), z)],
-                        data[o(x, y, Math.max(0, z - 1))]
-                    );
-                    v3set(n1,
-                        data[o(Math.min(xn1, x + 1), y, z)],
-                        data[o(x, Math.min(yn1, y + 1), z)],
-                        data[o(x, y, Math.min(zn1, z + 1))]
-                    );
-                    v3normalize(n0, v3sub(n0, n0, n1));
-                    v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
-                    v3toArray(v3scale(n0, n0, 255), array, index);
+                    if (variant === 'groups') {
+                        encodeFloatRGBtoArray(offset, array, index);
+                    } else {
+                        v3set(n0,
+                            data[o(Math.max(0, x - 1), y, z)],
+                            data[o(x, Math.max(0, y - 1), z)],
+                            data[o(x, y, Math.max(0, z - 1))]
+                        );
+                        v3set(n1,
+                            data[o(Math.min(xn1, x + 1), y, z)],
+                            data[o(x, Math.min(yn1, y + 1), z)],
+                            data[o(x, y, Math.min(zn1, z + 1))]
+                        );
+                        v3normalize(n0, v3sub(n0, n0, n1));
+                        v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
+                        v3toArray(v3scale(n0, n0, 255), array, index);
+                    }
+
+                    array[index + 3] = Math.round(((data[offset] - min) / diff) * 255);
                 }
-
-                array[index + 3] = ((data[offset] - min) / diff) * 255;
             }
         }
     }
@@ -167,7 +172,7 @@ export function createVolumeTexture3d(volume: Volume) {
                 v3addScalar(n0, v3scale(n0, n0, 0.5), 0.5);
                 v3toArray(v3scale(n0, n0, 255), array, i);
 
-                array[i + 3] = ((data[offset] - min) / diff) * 255;
+                array[i + 3] = Math.round(((data[offset] - min) / diff) * 255);
                 i += 4;
             }
         }