Selaa lähdekoodia

refactor grid-based color smoothing

- support rgba and alpha values
- CPU and GPU versions
- for Mesh and TextureMesh
Alexander Rose 3 vuotta sitten
vanhempi
commit
bb795aca98

+ 2 - 2
src/extensions/geo-export/mesh-exporter.ts

@@ -91,7 +91,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         return decodeFloatRGB(r, g, b);
     }
 
-    protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: number, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
+    protected static getInterpolatedColors(vertices: Float32Array, vertexCount: number, values: BaseValues, stride: 3 | 4, colorType: 'volume' | 'volumeInstance', webgl: WebGLContext) {
         const colorGridTransform = values.uColorGridTransform.ref.value;
         const colorGridDim = values.uColorGridDim.ref.value;
         const colorTexDim = values.uColorTexDim.ref.value;
@@ -99,7 +99,7 @@ export abstract class MeshExporter<D extends RenderObjectExportData> implements
         const instanceCount = values.uInstanceCount.ref.value;
 
         const colorGrid = readTexture(webgl, values.tColorGrid.ref.value).array;
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4 });
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer: aTransform, positionBuffer: vertices, colorType, grid: colorGrid, gridDim: colorGridDim, gridTexDim: colorTexDim, gridTransform: colorGridTransform, vertexStride: stride, colorStride: 4, outputStride: 3 });
         return interpolated.array;
     }
 

+ 36 - 1
src/mol-geo/geometry/base.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018 mol* contributors, licensed under MIT, See LICENSE file for more info.
+ * Copyright (c) 2018-2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
  *
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
@@ -15,6 +15,7 @@ import { ColorNames } from '../../mol-util/color/names';
 import { NullLocation } from '../../mol-model/location';
 import { UniformColorTheme } from '../../mol-theme/color/uniform';
 import { UniformSizeTheme } from '../../mol-theme/size/uniform';
+import { smoothstep } from '../../mol-math/interpolate';
 
 export const VisualQualityInfo = {
     'custom': {},
@@ -33,6 +34,40 @@ export const VisualQualityOptions = PD.arrayToOptions(VisualQualityNames);
 
 //
 
+export const ColorSmoothingParams = {
+    smoothColors: PD.MappedStatic('auto', {
+        auto: PD.Group({}),
+        on: PD.Group({
+            resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
+            sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
+        }),
+        off: PD.Group({})
+    }),
+};
+export type ColorSmoothingParams = typeof ColorSmoothingParams
+
+export function hasColorSmoothingProp(props: PD.Values<any>): props is PD.Values<ColorSmoothingParams> {
+    return !!props.smoothColors;
+}
+
+export function getColorSmoothingProps(smoothColors: PD.Values<ColorSmoothingParams>['smoothColors'], preferSmoothing?: boolean, resolution?: number) {
+    if ((smoothColors.name === 'on' || (smoothColors.name === 'auto' && preferSmoothing)) && resolution && resolution < 3) {
+        let stride = 3;
+        if (smoothColors.name === 'on') {
+            resolution *= smoothColors.params.resolutionFactor;
+            stride = smoothColors.params.sampleStride;
+        } else {
+            // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
+            resolution *= 2 - smoothstep(0, 1.1, resolution);
+            resolution = Math.max(0.5, resolution);
+            if (resolution > 1.2) stride = 2;
+        }
+        return { resolution, stride };
+    };
+}
+
+//
+
 export namespace BaseGeometry {
     export const Params = {
         alpha: PD.Numeric(1, { min: 0, max: 1, step: 0.01 }, { label: 'Opacity', isEssential: true, description: 'How opaque/transparent the representation is rendered.' }),

+ 99 - 38
src/mol-geo/geometry/mesh/color-smoothing.ts

@@ -4,13 +4,15 @@
  * @author Alexander Rose <alexander.rose@weirdbyte.de>
  */
 
+import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { createTextureImage, TextureImage } from '../../../mol-gl/renderable/util';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { Box3D, Sphere3D } from '../../../mol-math/geometry';
+import { lerp } from '../../../mol-math/interpolate';
 import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { getVolumeTexture2dLayout } from '../../../mol-repr/volume/util';
-import { Color } from '../../../mol-util/color';
+import { ValueCell } from '../../../mol-util';
 
 interface ColorSmoothingInput {
     vertexCount: number
@@ -24,10 +26,11 @@ interface ColorSmoothingInput {
     colorType: 'group' | 'groupInstance'
     boundingSphere: Sphere3D
     invariantBoundingSphere: Sphere3D
+    itemSize: 4 | 3 | 1
 }
 
 export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl?: WebGLContext, texture?: Texture) {
-    const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer } = input;
+    const { colorType, vertexCount, groupCount, positionBuffer, transformBuffer, groupBuffer, itemSize } = input;
 
     const isInstanceType = colorType.endsWith('Instance');
     const box = Box3D.fromSphere3D(Box3D(), isInstanceType ? input.boundingSphere : input.invariantBoundingSphere);
@@ -43,7 +46,6 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     const { width, height } = getVolumeTexture2dLayout(gridDim);
     // console.log({ width, height, dim });
 
-    const itemSize = 3;
     const data = new Float32Array(width * height * itemSize);
     const count = new Float32Array(width * height);
 
@@ -78,10 +80,7 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
             const z = Math.floor(vz);
 
             // group colors
-            const ci = i * groupCount + groupBuffer[j];
-            const r = colors[ci * 3];
-            const g = colors[ci * 3 + 1];
-            const b = colors[ci * 3 + 2];
+            const ci = (i * groupCount + groupBuffer[j]) * itemSize;
 
             // Extents of grid to consider for this atom
             const begX = Math.max(0, x - p);
@@ -106,10 +105,10 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
 
                         const s = p - d;
                         const index = getIndex(xi, yi, zi);
-                        data[index] += r * s;
-                        data[index + 1] += g * s;
-                        data[index + 2] += b * s;
-                        count[index / 3] += s;
+                        for (let k = 0; k < itemSize; ++k) {
+                            data[index + k] += colors[ci + k] * s;
+                        }
+                        count[index / itemSize] += s;
                     }
                 }
             }
@@ -117,11 +116,11 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     }
 
     for (let i = 0, il = count.length; i < il; ++i) {
-        const i3 = i * 3;
+        const is = i * itemSize;
         const c = count[i];
-        grid[i3] = Math.round(data[i3] / c);
-        grid[i3 + 1] = Math.round(data[i3 + 1] / c);
-        grid[i3 + 2] = Math.round(data[i3 + 2] / c);
+        for (let k = 0; k < itemSize; ++k) {
+            grid[is + k] = Math.round(data[is + k] / c);
+        }
     }
 
     const gridTexDim = Vec2.create(width, height);
@@ -129,12 +128,16 @@ export function calcMeshColorSmoothing(input: ColorSmoothingInput, resolution: n
     const type = isInstanceType ? 'volumeInstance' as const : 'volume' as const;
 
     if (webgl) {
-        if (!texture) texture = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+        if (!texture) {
+            const format = itemSize === 4 ? 'rgba' :
+                itemSize === 3 ? 'rgb' : 'alpha';
+            texture = webgl.resources.texture('image-uint8', format, 'ubyte', 'linear');
+        }
         texture.load(textureImage);
 
         return { kind: 'volume' as const, texture, gridTexDim, gridDim, gridTransform, type };
     } else {
-        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: 3 });
+        const interpolated = getTrilinearlyInterpolated({ vertexCount, instanceCount, transformBuffer, positionBuffer, colorType: type, grid, gridDim, gridTexDim, gridTransform, vertexStride: 3, colorStride: itemSize, outputStride: itemSize });
 
         return {
             kind: 'vertex' as const,
@@ -157,16 +160,24 @@ interface ColorInterpolationInput {
     gridTexDim: Vec2
     gridDim: Vec3
     gridTransform: Vec4
-    vertexStride: number
-    colorStride: number
+    vertexStride: 3 | 4
+    colorStride: 1 | 3 | 4
+    outputStride: 1 | 3 | 4
+    itemOffset?: 0 | 1 | 2 | 3
 }
 
 export function getTrilinearlyInterpolated(input: ColorInterpolationInput): TextureImage<Uint8Array> {
     const { vertexCount, positionBuffer, transformBuffer, grid, gridDim, gridTexDim, gridTransform, vertexStride, colorStride } = input;
 
+    const itemOffset = input.itemOffset || 0;
+    const outputStride = input.outputStride;
+    if (outputStride + itemOffset > colorStride) {
+        throw new Error('outputStride + itemOffset must NOT be larger than colorStride');
+    }
+
     const isInstanceType = input.colorType.endsWith('Instance');
     const instanceCount = isInstanceType ? input.instanceCount : 1;
-    const image = createTextureImage(Math.max(1, instanceCount * vertexCount), 3, Uint8Array);
+    const image = createTextureImage(Math.max(1, instanceCount * vertexCount), outputStride, Uint8Array);
     const { array } = image;
 
     const [xn, yn] = gridDim;
@@ -204,26 +215,76 @@ export function getTrilinearlyInterpolated(input: ColorInterpolationInput): Text
             const [x1, y1, z1] = v1;
             const [xd, yd, zd] = vd;
 
-            const s000 = Color.fromArray(grid, getIndex(x0, y0, z0));
-            const s100 = Color.fromArray(grid, getIndex(x1, y0, z0));
-            const s001 = Color.fromArray(grid, getIndex(x0, y0, z1));
-            const s101 = Color.fromArray(grid, getIndex(x1, y0, z1));
-            const s010 = Color.fromArray(grid, getIndex(x0, y1, z0));
-            const s110 = Color.fromArray(grid, getIndex(x1, y1, z0));
-            const s011 = Color.fromArray(grid, getIndex(x0, y1, z1));
-            const s111 = Color.fromArray(grid, getIndex(x1, y1, z1));
-
-            const s00 = Color.interpolate(s000, s100, xd);
-            const s01 = Color.interpolate(s001, s101, xd);
-            const s10 = Color.interpolate(s010, s110, xd);
-            const s11 = Color.interpolate(s011, s111, xd);
-
-            const s0 = Color.interpolate(s00, s10, yd);
-            const s1 = Color.interpolate(s01, s11, yd);
-
-            Color.toArray(Color.interpolate(s0, s1, zd), array, (i * vertexCount + j) * 3);
+            const i000 = getIndex(x0, y0, z0) + itemOffset;
+            const i100 = getIndex(x1, y0, z0) + itemOffset;
+            const i001 = getIndex(x0, y0, z1) + itemOffset;
+            const i101 = getIndex(x1, y0, z1) + itemOffset;
+            const i010 = getIndex(x0, y1, z0) + itemOffset;
+            const i110 = getIndex(x1, y1, z0) + itemOffset;
+            const i011 = getIndex(x0, y1, z1) + itemOffset;
+            const i111 = getIndex(x1, y1, z1) + itemOffset;
+
+            const o = (i * vertexCount + j) * outputStride;
+
+            for (let k = 0; k < outputStride; ++k) {
+                const s000 = grid[i000 + k];
+                const s100 = grid[i100 + k];
+                const s001 = grid[i001 + k];
+                const s101 = grid[i101 + k];
+                const s010 = grid[i010 + k];
+                const s110 = grid[i110 + k];
+                const s011 = grid[i011 + k];
+                const s111 = grid[i111 + k];
+
+                const s00 = lerp(s000, s100, xd);
+                const s01 = lerp(s001, s101, xd);
+                const s10 = lerp(s010, s110, xd);
+                const s11 = lerp(s011, s111, xd);
+
+                const s0 = lerp(s00, s10, yd);
+                const s1 = lerp(s01, s11, yd);
+
+                array[o + k] = lerp(s0, s1, zd);
+            }
         }
     }
 
     return image;
 }
+
+//
+
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
+    return x === 'group' || x === 'groupInstance';
+}
+
+export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    const smoothingData = calcMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionBuffer: values.aPosition.ref.value,
+        groupBuffer: values.aGroup.ref.value,
+        colorData: values.tColor.ref.value,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+        itemSize: 3
+    }, resolution, stride, webgl, colorTexture);
+
+    if (smoothingData.kind === 'volume') {
+        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+        ValueCell.update(values.tColorGrid, smoothingData.texture);
+        ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+        ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+        ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
+    } else if (smoothingData.kind === 'vertex') {
+        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+        ValueCell.update(values.tColor, smoothingData.texture);
+        ValueCell.update(values.uColorTexDim, smoothingData.texDim);
+    }
+}

+ 111 - 27
src/mol-geo/geometry/texture-mesh/color-smoothing.ts

@@ -18,7 +18,8 @@ import { Vec2, Vec3, Vec4 } from '../../../mol-math/linear-algebra';
 import { Box3D, Sphere3D } from '../../../mol-math/geometry';
 import { accumulate_frag } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.frag';
 import { accumulate_vert } from '../../../mol-gl/shader/compute/color-smoothing/accumulate.vert';
-import { TextureImage } from '../../../mol-gl/renderable/util';
+import { isWebGL2 } from '../../../mol-gl/webgl/compat';
+import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 
 export const ColorAccumulateSchema = {
     drawCount: ValueSpec('number'),
@@ -38,7 +39,7 @@ export const ColorAccumulateSchema = {
     tGroup: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 
     uColorTexDim: UniformSpec('v2'),
-    tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
+    tColor: TextureSpec('texture', 'rgba', 'ubyte', 'nearest'),
     dColorType: DefineSpec('string', ['group', 'groupInstance', 'vertex', 'vertexInstance']),
 
     uCurrentSlice: UniformSpec('f'),
@@ -50,6 +51,7 @@ export const ColorAccumulateSchema = {
 };
 type ColorAccumulateValues = Values<typeof ColorAccumulateSchema>
 const ColorAccumulateName = 'color-accumulate';
+const ColorCountName = 'color-count';
 
 interface AccumulateInput {
     vertexCount: number
@@ -59,7 +61,7 @@ interface AccumulateInput {
     instanceBuffer: Float32Array
     positionTexture: Texture
     groupTexture: Texture
-    colorData: TextureImage<Uint8Array>
+    colorData: Texture
     colorType: 'group' | 'groupInstance'
 }
 
@@ -96,7 +98,7 @@ function getAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, box:
         ValueCell.update(v.tPosition, input.positionTexture);
         ValueCell.update(v.tGroup, input.groupTexture);
 
-        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.width, input.colorData.height));
+        ValueCell.update(v.uColorTexDim, Vec2.set(v.uColorTexDim.ref.value, input.colorData.getWidth(), input.colorData.getHeight()));
         ValueCell.update(v.tColor, input.colorData);
         ValueCell.updateIfChanged(v.dColorType, input.colorType);
 
@@ -135,7 +137,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
         tPosition: ValueCell.create(input.positionTexture),
         tGroup: ValueCell.create(input.groupTexture),
 
-        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.width, input.colorData.height)),
+        uColorTexDim: ValueCell.create(Vec2.create(input.colorData.getWidth(), input.colorData.getHeight())),
         tColor: ValueCell.create(input.colorData),
         dColorType: ValueCell.create(input.colorType),
 
@@ -148,7 +150,7 @@ function createAccumulateRenderable(ctx: WebGLContext, input: AccumulateInput, b
     };
 
     const schema = { ...ColorAccumulateSchema };
-    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag);
+    const shaderCode = ShaderCode('accumulate', accumulate_vert, accumulate_frag, { drawBuffers: 'required' });
     const renderItem = createComputeRenderItem(ctx, 'points', shaderCode, schema, values);
 
     return createComputeRenderable(renderItem, values);
@@ -172,30 +174,33 @@ export const ColorNormalizeSchema = {
     ...QuadSchema,
 
     tColor: TextureSpec('texture', 'rgba', 'float', 'nearest'),
+    tCount: TextureSpec('texture', 'alpha', 'float', 'nearest'),
     uTexSize: UniformSpec('v2'),
 
 };
 type ColorNormalizeValues = Values<typeof ColorNormalizeSchema>
 const ColorNormalizeName = 'color-normalize';
 
-function getNormalizeRenderable(ctx: WebGLContext, color: Texture): ComputeRenderable<ColorNormalizeValues> {
+function getNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture): ComputeRenderable<ColorNormalizeValues> {
     if (ctx.namedComputeRenderables[ColorNormalizeName]) {
         const v = ctx.namedComputeRenderables[ColorNormalizeName].values as ColorNormalizeValues;
 
         ValueCell.update(v.tColor, color);
+        ValueCell.update(v.tCount, count);
         ValueCell.update(v.uTexSize, Vec2.set(v.uTexSize.ref.value, color.getWidth(), color.getHeight()));
 
         ctx.namedComputeRenderables[ColorNormalizeName].update();
     } else {
-        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color);
+        ctx.namedComputeRenderables[ColorNormalizeName] = createColorNormalizeRenderable(ctx, color, count);
     }
     return ctx.namedComputeRenderables[ColorNormalizeName];
 }
 
-function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture) {
+function createColorNormalizeRenderable(ctx: WebGLContext, color: Texture, count: Texture) {
     const values: ColorNormalizeValues = {
         ...QuadValues,
         tColor: ValueCell.create(color),
+        tCount: ValueCell.create(count),
         uTexSize: ValueCell.create(Vec2.create(color.getWidth(), color.getHeight())),
     };
 
@@ -247,6 +252,9 @@ interface ColorSmoothingInput extends AccumulateInput {
 }
 
 export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolution: number, stride: number, webgl: WebGLContext, texture?: Texture) {
+    const { drawBuffers } = webgl.extensions;
+    if (!drawBuffers) throw new Error('need WebGL draw buffers');
+
     const { gl, resources, state, extensions: { colorBufferHalfFloat, textureHalfFloat } } = webgl;
 
     const isInstanceType = input.colorType.endsWith('Instance');
@@ -263,29 +271,55 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     const { texDimX: width, texDimY: height, texCols } = getTexture2dSize(gridDim);
     // console.log({ width, height, texCols, dim, resolution });
 
-    if (!webgl.namedTextures[ColorAccumulateName]) {
-        webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
-            ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
-            : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
+        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
     }
+    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
+
+    if (isWebGL2(gl)) {
+        if (!webgl.namedTextures[ColorAccumulateName]) {
+            webgl.namedTextures[ColorAccumulateName] = colorBufferHalfFloat && textureHalfFloat
+                ? resources.texture('image-float16', 'rgba', 'fp16', 'nearest')
+                : resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!webgl.namedTextures[ColorCountName]) {
+            webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'alpha', '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
+
+        if (!webgl.namedTextures[ColorAccumulateName]) {
+            webgl.namedTextures[ColorAccumulateName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+
+        if (!webgl.namedTextures[ColorCountName]) {
+            webgl.namedTextures[ColorCountName] = resources.texture('image-float32', 'rgba', 'float', 'nearest');
+        }
+    }
+
     const accumulateTexture = webgl.namedTextures[ColorAccumulateName];
+    const countTexture = webgl.namedTextures[ColorCountName];
+
     accumulateTexture.define(width, height);
+    countTexture.define(width, height);
+
+    accumulateTexture.attachFramebuffer(framebuffer, 0);
+    countTexture.attachFramebuffer(framebuffer, 1);
 
     const accumulateRenderable = getAccumulateRenderable(webgl, input, box, resolution, stride);
+    state.currentRenderItemId = -1;
 
-    //
+    framebuffer.bind();
+    drawBuffers.drawBuffers([
+        drawBuffers.COLOR_ATTACHMENT0,
+        drawBuffers.COLOR_ATTACHMENT1,
+    ]);
 
     const { uCurrentSlice, uCurrentX, uCurrentY } = accumulateRenderable.values;
 
-    if (!webgl.namedFramebuffers[ColorAccumulateName]) {
-        webgl.namedFramebuffers[ColorAccumulateName] = webgl.resources.framebuffer();
-    }
-    const framebuffer = webgl.namedFramebuffers[ColorAccumulateName];
-    framebuffer.bind();
-
     setAccumulateDefaults(webgl);
-    state.currentRenderItemId = -1;
-    accumulateTexture.attachFramebuffer(framebuffer, 0);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
     gl.clear(gl.COLOR_BUFFER_BIT);
@@ -310,21 +344,31 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
         currX += dx;
     }
 
+    accumulateTexture.detachFramebuffer(framebuffer, 0);
+    countTexture.detachFramebuffer(framebuffer, 1);
+    drawBuffers.drawBuffers([gl.COLOR_ATTACHMENT0, gl.NONE]);
+
     // const accImage = new Float32Array(width * height * 4);
     // accumulateTexture.attachFramebuffer(framebuffer, 0);
     // webgl.readPixels(0, 0, width, height, accImage);
     // console.log(accImage);
-    // printTextureImage({ array: accImage, width, height }, 1 / 4);
+    // printTextureImage({ array: accImage, width, height }, { scale: 1 });
+
+    // const cntImage = new Float32Array(width * height * 4);
+    // countTexture.attachFramebuffer(framebuffer, 0);
+    // webgl.readPixels(0, 0, width, height, cntImage);
+    // console.log(cntImage);
+    // printTextureImage({ array: cntImage, width, height }, { scale: 1 });
 
     // normalize
 
-    if (!texture) texture = resources.texture('image-uint8', 'rgb', 'ubyte', 'linear');
+    if (!texture) texture = resources.texture('image-uint8', 'rgba', 'ubyte', 'linear');
     texture.define(width, height);
 
-    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture);
+    const normalizeRenderable = getNormalizeRenderable(webgl, accumulateTexture, countTexture);
+    state.currentRenderItemId = -1;
 
     setNormalizeDefaults(webgl);
-    state.currentRenderItemId = -1;
     texture.attachFramebuffer(framebuffer, 0);
     gl.viewport(0, 0, width, height);
     gl.scissor(0, 0, width, height);
@@ -335,10 +379,50 @@ export function calcTextureMeshColorSmoothing(input: ColorSmoothingInput, resolu
     // texture.attachFramebuffer(framebuffer, 0);
     // webgl.readPixels(0, 0, width, height, normImage);
     // console.log(normImage);
-    // printTextureImage({ array: normImage, width, height }, 1 / 4);
+    // printTextureImage({ array: normImage, width, height }, { scale: 1 });
 
     const gridTransform = Vec4.create(min[0], min[1], min[2], scaleFactor);
     const type = isInstanceType ? 'volumeInstance' : 'volume';
 
     return { texture, gridDim, gridTexDim: Vec2.create(width, height), gridTransform, type };
 }
+
+//
+
+const ColorSmoothingRgbName = 'color-smoothing-rgb';
+
+function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
+    return x === 'group' || x === 'groupInstance';
+}
+
+export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
+    if (!isSupportedColorType(values.dColorType.ref.value)) return;
+
+    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
+
+    if (!webgl.namedTextures[ColorSmoothingRgbName]) {
+        webgl.namedTextures[ColorSmoothingRgbName] = webgl.resources.texture('image-uint8', 'rgb', 'ubyte', 'nearest');
+    }
+    const colorData = webgl.namedTextures[ColorSmoothingRgbName];
+    colorData.load(values.tColor.ref.value);
+
+    const smoothingData = calcTextureMeshColorSmoothing({
+        vertexCount: values.uVertexCount.ref.value,
+        instanceCount: values.uInstanceCount.ref.value,
+        groupCount: values.uGroupCount.ref.value,
+        transformBuffer: values.aTransform.ref.value,
+        instanceBuffer: values.aInstance.ref.value,
+        positionTexture: values.tPosition.ref.value,
+        groupTexture: values.tGroup.ref.value,
+        colorData,
+        colorType: values.dColorType.ref.value,
+        boundingSphere: values.boundingSphere.ref.value,
+        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
+    }, resolution, stride, webgl, colorTexture);
+
+    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
+    ValueCell.update(values.tColorGrid, smoothingData.texture);
+    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
+    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
+    ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
+}

+ 4 - 2
src/mol-gl/shader/compute/color-smoothing/accumulate.frag.ts

@@ -8,7 +8,7 @@ export const accumulate_frag = `
 precision highp float;
 
 varying vec3 vPosition;
-varying vec3 vColor;
+varying vec4 vColor;
 
 uniform float uCurrentSlice;
 uniform float uCurrentX;
@@ -23,6 +23,8 @@ void main() {
     float dist = distance(fragPos, vPosition);
     if (dist > p) discard;
 
-    gl_FragColor = vec4(vColor, 1.0) * (p - dist);
+    float f = p - dist;
+    gl_FragColor = vColor * f;
+    gl_FragData[1] = vec4(f);
 }
 `;

+ 3 - 3
src/mol-gl/shader/compute/color-smoothing/accumulate.vert.ts

@@ -27,7 +27,7 @@ uniform vec2 uColorTexDim;
 uniform sampler2D tColor;
 
 varying vec3 vPosition;
-varying vec3 vColor;
+varying vec4 vColor;
 
 uniform vec3 uBboxSize;
 uniform vec3 uBboxMin;
@@ -43,9 +43,9 @@ void main() {
     gl_Position = vec4(((position - uBboxMin) / uBboxSize) * 2.0 - 1.0, 1.0);
 
     #if defined(dColorType_group)
-        vColor = readFromTexture(tColor, group, uColorTexDim).rgb;
+        vColor = readFromTexture(tColor, group, uColorTexDim);
     #elif defined(dColorType_groupInstance)
-        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim).rgb;
+        vColor = readFromTexture(tColor, aInstance * float(uGroupCount) + group, uColorTexDim);
     #endif
 }
 `;

+ 3 - 1
src/mol-gl/shader/compute/color-smoothing/normalize.frag.ts

@@ -9,12 +9,14 @@ precision highp float;
 precision highp sampler2D;
 
 uniform sampler2D tColor;
+uniform sampler2D tCount;
 uniform vec2 uTexSize;
 
 void main(void) {
     vec2 coords = gl_FragCoord.xy / uTexSize;
     vec4 color = texture2D(tColor, coords);
+    float count = texture2D(tCount, coords).r;
 
-    gl_FragColor.rgb = color.rgb / color.a;
+    gl_FragColor = color / count;
 }
 `;

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

@@ -23,7 +23,9 @@ import { WebGLContext } from '../../../mol-gl/webgl/context';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { TextureMeshValues } from '../../../mol-gl/renderable/texture-mesh';
 import { Texture } from '../../../mol-gl/webgl/texture';
-import { applyMeshColorSmoothing, applyTextureMeshColorSmoothing, ColorSmoothingParams, getColorSmoothingProps } from './util/color';
+import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
+import { applyTextureMeshColorSmoothing } from '../../../mol-geo/geometry/texture-mesh/color-smoothing';
+import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 
 const SharedParams = {
     ...GaussianDensityParams,
@@ -131,7 +133,7 @@ export function GaussianSurfaceMeshVisual(materialId: number): UnitsVisual<Gauss
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
@@ -191,7 +193,7 @@ export function StructureGaussianSurfaceMeshVisual(materialId: number): ComplexV
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta.colorTexture as GaussianSurfaceMeta['colorTexture']) = values.tColorGrid.ref.value;
@@ -264,7 +266,7 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
         },
         processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp && webgl) {
                 applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;
@@ -340,7 +342,7 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
         },
         processValues: (values: TextureMeshValues, geometry: TextureMesh, props: PD.Values<GaussianSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as GaussianSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp && webgl) {
                 applyTextureMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as GaussianSurfaceMeta).colorTexture = values.tColorGrid.ref.value;

+ 3 - 2
src/mol-repr/structure/visual/molecular-surface-mesh.ts

@@ -20,7 +20,8 @@ import { Sphere3D } from '../../../mol-math/geometry';
 import { MeshValues } from '../../../mol-gl/renderable/mesh';
 import { Texture } from '../../../mol-gl/webgl/texture';
 import { WebGLContext } from '../../../mol-gl/webgl/context';
-import { applyMeshColorSmoothing, ColorSmoothingParams, getColorSmoothingProps } from './util/color';
+import { applyMeshColorSmoothing } from '../../../mol-geo/geometry/mesh/color-smoothing';
+import { ColorSmoothingParams, getColorSmoothingProps } from '../../../mol-geo/geometry/base';
 
 export const MolecularSurfaceMeshParams = {
     ...UnitsMeshParams,
@@ -87,7 +88,7 @@ export function MolecularSurfaceMeshVisual(materialId: number): UnitsVisual<Mole
         },
         processValues: (values: MeshValues, geometry: Mesh, props: PD.Values<MolecularSurfaceMeshParams>, theme: Theme, webgl?: WebGLContext) => {
             const { resolution, colorTexture } = geometry.meta as MolecularSurfaceMeta;
-            const csp = getColorSmoothingProps(props, theme, resolution);
+            const csp = getColorSmoothingProps(props.smoothColors, theme.color.preferSmoothing, resolution);
             if (csp) {
                 applyMeshColorSmoothing(values, csp.resolution, csp.stride, webgl, colorTexture);
                 (geometry.meta as MolecularSurfaceMeta).colorTexture = values.tColorGrid.ref.value;

+ 0 - 104
src/mol-repr/structure/visual/util/color.ts

@@ -1,104 +0,0 @@
-/**
- * Copyright (c) 2021 mol* contributors, licensed under MIT, See LICENSE file for more info.
- *
- * @author Alexander Rose <alexander.rose@weirdbyte.de>
- */
-
-import { calcMeshColorSmoothing } from '../../../../mol-geo/geometry/mesh/color-smoothing';
-import { calcTextureMeshColorSmoothing } from '../../../../mol-geo/geometry/texture-mesh/color-smoothing';
-import { MeshValues } from '../../../../mol-gl/renderable/mesh';
-import { TextureMeshValues } from '../../../../mol-gl/renderable/texture-mesh';
-import { WebGLContext } from '../../../../mol-gl/webgl/context';
-import { Texture } from '../../../../mol-gl/webgl/texture';
-import { smoothstep } from '../../../../mol-math/interpolate';
-import { Theme } from '../../../../mol-theme/theme';
-import { ValueCell } from '../../../../mol-util';
-import { ParamDefinition as PD } from '../../../../mol-util/param-definition';
-
-export const ColorSmoothingParams = {
-    smoothColors: PD.MappedStatic('auto', {
-        auto: PD.Group({}),
-        on: PD.Group({
-            resolutionFactor: PD.Numeric(2, { min: 0.5, max: 6, step: 0.1 }),
-            sampleStride: PD.Numeric(3, { min: 1, max: 12, step: 1 }),
-        }),
-        off: PD.Group({})
-    }),
-};
-export type ColorSmoothingParams = typeof ColorSmoothingParams
-
-export function getColorSmoothingProps(props: PD.Values<ColorSmoothingParams>, theme: Theme, resolution?: number) {
-    if ((props.smoothColors.name === 'on' || (props.smoothColors.name === 'auto' && theme.color.preferSmoothing)) && resolution && resolution < 3) {
-        let stride = 3;
-        if (props.smoothColors.name === 'on') {
-            resolution *= props.smoothColors.params.resolutionFactor;
-            stride = props.smoothColors.params.sampleStride;
-        } else {
-            // https://graphtoy.com/?f1(x,t)=(2-smoothstep(0,1.1,x))*x&coords=0.7,0.6,1.8
-            resolution *= 2 - smoothstep(0, 1.1, resolution);
-            resolution = Math.max(0.5, resolution);
-            if (resolution > 1.2) stride = 2;
-        }
-        return { resolution, stride };
-    };
-}
-
-function isSupportedColorType(x: string): x is 'group' | 'groupInstance' {
-    return x === 'group' || x === 'groupInstance';
-}
-
-export function applyMeshColorSmoothing(values: MeshValues, resolution: number, stride: number, webgl?: WebGLContext, colorTexture?: Texture) {
-    if (!isSupportedColorType(values.dColorType.ref.value)) return;
-
-    const smoothingData = calcMeshColorSmoothing({
-        vertexCount: values.uVertexCount.ref.value,
-        instanceCount: values.uInstanceCount.ref.value,
-        groupCount: values.uGroupCount.ref.value,
-        transformBuffer: values.aTransform.ref.value,
-        instanceBuffer: values.aInstance.ref.value,
-        positionBuffer: values.aPosition.ref.value,
-        groupBuffer: values.aGroup.ref.value,
-        colorData: values.tColor.ref.value,
-        colorType: values.dColorType.ref.value,
-        boundingSphere: values.boundingSphere.ref.value,
-        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-    }, resolution, stride, webgl, colorTexture);
-
-    if (smoothingData.kind === 'volume') {
-        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-        ValueCell.update(values.tColorGrid, smoothingData.texture);
-        ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
-        ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
-        ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
-    } else if (smoothingData.kind === 'vertex') {
-        ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-        ValueCell.update(values.tColor, smoothingData.texture);
-        ValueCell.update(values.uColorTexDim, smoothingData.texDim);
-    }
-}
-
-export function applyTextureMeshColorSmoothing(values: TextureMeshValues, resolution: number, stride: number, webgl: WebGLContext, colorTexture?: Texture) {
-    if (!isSupportedColorType(values.dColorType.ref.value)) return;
-
-    stride *= 3; // triple because TextureMesh is never indexed (no elements buffer)
-
-    const smoothingData = calcTextureMeshColorSmoothing({
-        vertexCount: values.uVertexCount.ref.value,
-        instanceCount: values.uInstanceCount.ref.value,
-        groupCount: values.uGroupCount.ref.value,
-        transformBuffer: values.aTransform.ref.value,
-        instanceBuffer: values.aInstance.ref.value,
-        positionTexture: values.tPosition.ref.value,
-        groupTexture: values.tGroup.ref.value,
-        colorData: values.tColor.ref.value,
-        colorType: values.dColorType.ref.value,
-        boundingSphere: values.boundingSphere.ref.value,
-        invariantBoundingSphere: values.invariantBoundingSphere.ref.value,
-    }, resolution, stride, webgl, colorTexture);
-
-    ValueCell.updateIfChanged(values.dColorType, smoothingData.type);
-    ValueCell.update(values.tColorGrid, smoothingData.texture);
-    ValueCell.update(values.uColorTexDim, smoothingData.gridTexDim);
-    ValueCell.update(values.uColorGridDim, smoothingData.gridDim);
-    ValueCell.update(values.uColorGridTransform, smoothingData.gridTransform);
-}