Procházet zdrojové kódy

fix texture-mesh rendering artifacts

- added double buffering for texture-mesh textures
- added buffered uniforms
Alexander Rose před 4 roky
rodič
revize
e6c8c69d0c

+ 29 - 1
src/mol-geo/geometry/texture-mesh/texture-mesh.ts

@@ -1,5 +1,5 @@
 /**
- * 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>
  */
@@ -36,11 +36,36 @@ export interface TextureMesh {
     readonly vertexTexture: ValueCell<Texture>,
     readonly groupTexture: ValueCell<Texture>,
     readonly normalTexture: ValueCell<Texture>,
+    readonly doubleBuffer: TextureMesh.DoubleBuffer
 
     readonly boundingSphere: Sphere3D
 }
 
 export namespace TextureMesh {
+    export class DoubleBuffer {
+        private index = 0;
+        private textures: ({ vertex: Texture, group: Texture, normal: Texture } | undefined)[] = []
+
+        get() {
+            return this.textures[this.index];
+        }
+
+        set(vertex: Texture, group: Texture, normal: Texture) {
+            this.textures[this.index] = Object.assign(this.textures[this.index] || {}, {
+                vertex, group, normal
+            });
+            this.index = (this.index + 1) % 2;
+        }
+
+        destroy() {
+            for (const buffer of this.textures) {
+                buffer!.vertex.destroy();
+                buffer!.group.destroy();
+                buffer!.normal.destroy();
+            }
+        }
+    }
+
     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();
@@ -49,7 +74,9 @@ export namespace TextureMesh {
             textureMesh.groupCount = groupCount;
             ValueCell.update(textureMesh.geoTextureDim, Vec2.set(textureMesh.geoTextureDim.ref.value, width, height));
             ValueCell.update(textureMesh.vertexTexture, vertexTexture);
+            ValueCell.update(textureMesh.groupTexture, groupTexture);
             ValueCell.update(textureMesh.normalTexture, normalTexture);
+            textureMesh.doubleBuffer.set(vertexTexture, groupTexture, normalTexture);
             Sphere3D.copy(textureMesh.boundingSphere, boundingSphere);
             return textureMesh;
         } else {
@@ -61,6 +88,7 @@ export namespace TextureMesh {
                 vertexTexture: ValueCell.create(vertexTexture),
                 groupTexture: ValueCell.create(groupTexture),
                 normalTexture: ValueCell.create(normalTexture),
+                doubleBuffer: new DoubleBuffer(),
                 boundingSphere: Sphere3D.clone(boundingSphere),
             };
         }

+ 3 - 14
src/mol-gl/compute/marching-cubes/isosurface.ts

@@ -19,7 +19,6 @@ 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';
-import { Scheduler } from '../../../mol-task';
 
 const IsosurfaceSchema = {
     ...QuadSchema,
@@ -193,29 +192,19 @@ export function createIsosurfaceBuffers(ctx: WebGLContext, activeVoxelsBase: Tex
 
 //
 
-function delay() {
-    return new Promise(r => Scheduler.setImmediate(r));
-}
-
-export async function extractIsosurface(ctx: WebGLContext, volumeData: Texture, gridDim: Vec3, gridTexDim: Vec3, gridTexScale: Vec2, transform: Mat4, isoValue: number, packedGroup: boolean, vertexTexture?: Texture, groupTexture?: Texture, normalTexture?: Texture) {
+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);
-    // apply advanced magic to solve incomplete buffer rendering issue
-    await delay();
+    // ctx.waitForGpuCommandsCompleteSync();
+    // console.timeEnd('calcActiveVoxels');
 
     // console.time('createHistogramPyramid');
     const compacted = createHistogramPyramid(ctx, activeVoxelsTex, gridTexScale, gridTexDim);
-    // apply advanced magic to solve incomplete buffer rendering issue
-    await delay();
-
     // ctx.waitForGpuCommandsCompleteSync();
     // console.timeEnd('createHistogramPyramid');
 
     // console.time('createIsosurfaceBuffers');
     const gv = createIsosurfaceBuffers(ctx, activeVoxelsTex, volumeData, compacted, gridDim, gridTexDim, transform, isoValue, packedGroup, vertexTexture, groupTexture, normalTexture);
-    // apply advanced magic to solve incomplete buffer rendering issue
-    await delay();
-
     // ctx.waitForGpuCommandsCompleteSync();
     // console.timeEnd('createIsosurfaceBuffers');
 

+ 10 - 8
src/mol-gl/renderable/schema.ts

@@ -37,6 +37,7 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
     const textureValues: TextureValues = {};
     const uniformValues: UniformValues = {};
     const materialUniformValues: UniformValues = {};
+    const bufferedUniformValues: UniformValues = {};
     Object.keys(schema).forEach(k => {
         const spec = schema[k];
         if (spec.type === 'attribute') attributeValues[k] = values[k];
@@ -45,11 +46,12 @@ export function splitValues(schema: RenderableSchema, values: RenderableValues)
         if (spec.type === 'texture' && values[k] !== undefined) textureValues[k] = values[k];
         // check if k exists in values to exclude global uniforms
         if (spec.type === 'uniform' && values[k] !== undefined) {
-            if (spec.isMaterial) materialUniformValues[k] = values[k];
+            if (spec.variant === 'material') materialUniformValues[k] = values[k];
+            else if (spec.variant === 'buffered') bufferedUniformValues[k] = values[k];
             else uniformValues[k] = values[k];
         }
     });
-    return { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues };
+    return { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues };
 }
 
 export type Versions<T extends RenderableValues> = { [k in keyof T]: number }
@@ -68,9 +70,9 @@ export function AttributeSpec<K extends AttributeKind>(kind: K, itemSize: Attrib
     return { type: 'attribute', kind, itemSize, divisor };
 }
 
-export type UniformSpec<K extends UniformKind> = { type: 'uniform', kind: K, isMaterial: boolean }
-export function UniformSpec<K extends UniformKind>(kind: K, isMaterial = false): UniformSpec<K> {
-    return { type: 'uniform', kind, isMaterial };
+export type UniformSpec<K extends UniformKind> = { type: 'uniform', kind: K, variant?: 'material' | 'buffered' }
+export function UniformSpec<K extends UniformKind>(kind: K, variant?: 'material' | 'buffered'): UniformSpec<K> {
+    return { type: 'uniform', kind, variant };
 }
 
 export type TextureSpec<K extends TextureKind> = { type: 'texture', kind: K, format: TextureFormat, dataType: TextureType, filter: TextureFilter }
@@ -180,7 +182,7 @@ export type InternalValues = Values<InternalSchema>
 
 export const ColorSchema = {
     // aColor: AttributeSpec('float32', 3, 0), // TODO
-    uColor: UniformSpec('v3', true),
+    uColor: UniformSpec('v3', 'material'),
     uColorTexDim: UniformSpec('v2'),
     tColor: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
     dColorType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance', 'vertex', 'vertexInstance']),
@@ -190,7 +192,7 @@ export type ColorValues = Values<ColorSchema>
 
 export const SizeSchema = {
     // aSize: AttributeSpec('float32', 1, 0), // TODO
-    uSize: UniformSpec('f', true),
+    uSize: UniformSpec('f', 'material'),
     uSizeTexDim: UniformSpec('v2'),
     tSize: TextureSpec('image-uint8', 'rgb', 'ubyte', 'nearest'),
     dSizeType: DefineSpec('string', ['uniform', 'attribute', 'instance', 'group', 'groupInstance']),
@@ -251,7 +253,7 @@ export const BaseSchema = {
     /**
      * final alpha, calculated as `values.alpha * state.alpha`
      */
-    uAlpha: UniformSpec('f', true),
+    uAlpha: UniformSpec('f', 'material'),
     uVertexCount: UniformSpec('i'),
     uInstanceCount: UniformSpec('i'),
     uGroupCount: UniformSpec('i'),

+ 1 - 1
src/mol-gl/renderable/texture-mesh.ts

@@ -13,7 +13,7 @@ import { ValueCell } from '../../mol-util';
 
 export const TextureMeshSchema = {
     ...BaseSchema,
-    uGeoTexDim: UniformSpec('v2'),
+    uGeoTexDim: UniformSpec('v2', 'buffered'),
     tPosition: TextureSpec('texture', 'rgb', 'float', 'nearest'),
     tGroup: TextureSpec('texture', 'alpha', 'float', 'nearest'),
     tNormal: TextureSpec('texture', 'rgb', 'float', 'nearest'),

+ 16 - 2
src/mol-gl/webgl/render-item.ts

@@ -17,6 +17,8 @@ import { checkFramebufferStatus } from './framebuffer';
 import { isDebugMode } from '../../mol-util/debug';
 import { VertexArray } from './vertex-array';
 import { fillSerial } from '../../mol-util/array';
+import { deepClone } from '../../mol-util/object';
+import { cloneUniformValues } from './uniform';
 
 const getNextRenderItemId = idFactory();
 
@@ -123,10 +125,12 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
         (schema as any).aVertex = AttributeSpec('float32', 1, 0);
     }
 
-    const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues } = splitValues(schema, values);
+    const { attributeValues, defineValues, textureValues, uniformValues, materialUniformValues, bufferedUniformValues } = splitValues(schema, values);
 
     const uniformValueEntries = Object.entries(uniformValues);
     const materialUniformValueEntries = Object.entries(materialUniformValues);
+    const backBufferUniformValueEntries = Object.entries(bufferedUniformValues);
+    const frontBufferUniformValueEntries = Object.entries(cloneUniformValues(bufferedUniformValues));
     const defineValueEntries = Object.entries(defineValues);
 
     const versions = getValueVersions(values);
@@ -192,6 +196,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
                     currentProgramId = program.id;
                 }
                 program.setUniforms(uniformValueEntries);
+                program.setUniforms(frontBufferUniformValueEntries);
                 if (sharedTexturesList && sharedTexturesList.length > 0) {
                     program.bindTextures(sharedTexturesList, 0);
                     program.bindTextures(textures, sharedTexturesList.length);
@@ -318,11 +323,20 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode:
                     if (schema[k].kind !== 'texture') {
                         // console.log('texture version changed, uploading image', k);
                         texture.load(value.ref.value as TextureImage<any> | TextureVolume<any>);
-                        versions[k] = value.ref.version;
                         valueChanges.textures = true;
                     } else {
                         textures[i][1] = value.ref.value as Texture;
                     }
+                    versions[k] = value.ref.version;
+                }
+            }
+
+            for (let i = 0, il = backBufferUniformValueEntries.length; i < il; ++i) {
+                const [k, uniform] = backBufferUniformValueEntries[i];
+                if (uniform.ref.version !== versions[k]) {
+                    // console.log('back-buffer uniform version changed, updating front-buffer', k);
+                    ValueCell.update(frontBufferUniformValueEntries[i][1], deepClone(uniform.ref.value));
+                    versions[k] = uniform.ref.version;
                 }
             }
 

+ 10 - 1
src/mol-gl/webgl/uniform.ts

@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2018-2020 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>
  */
@@ -9,6 +9,7 @@ import { ValueCell } from '../../mol-util';
 import { GLRenderingContext } from './compat';
 import { RenderableSchema } from '../../mol-gl/renderable/schema';
 import { ValueOf } from '../../mol-util/type-helpers';
+import { deepClone } from '../../mol-util/object';
 
 export type UniformKindValue = {
     'b': boolean; 'b[]': boolean[]
@@ -105,4 +106,12 @@ export function isUniformValueScalar(kind: UniformKind): boolean {
         default:
             return false;
     }
+}
+
+export function cloneUniformValues(uniformValues: UniformValues): UniformValues {
+    const clonedValues: UniformValues = {};
+    Object.keys(uniformValues).forEach(k => {
+        clonedValues[k] = ValueCell.create(deepClone(uniformValues[k].ref.value));
+    });
+    return clonedValues;
 }

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

@@ -33,14 +33,14 @@ export const GaussianDensitySchema = {
     uCurrentSlice: UniformSpec('f'),
     uCurrentX: UniformSpec('f'),
     uCurrentY: UniformSpec('f'),
-    uBboxMin: UniformSpec('v3', true),
-    uBboxSize: UniformSpec('v3', true),
-    uGridDim: UniformSpec('v3', true),
-    uGridTexDim: UniformSpec('v3', true),
-    uGridTexScale: UniformSpec('v2', true),
-    uAlpha: UniformSpec('f', true),
-    uResolution: UniformSpec('f', true),
-    uRadiusFactorInv: UniformSpec('f', true),
+    uBboxMin: UniformSpec('v3', 'material'),
+    uBboxSize: UniformSpec('v3', 'material'),
+    uGridDim: UniformSpec('v3', 'material'),
+    uGridTexDim: UniformSpec('v3', 'material'),
+    uGridTexScale: UniformSpec('v2', 'material'),
+    uAlpha: UniformSpec('f', 'material'),
+    uResolution: UniformSpec('f', 'material'),
+    uRadiusFactorInv: UniformSpec('f', 'material'),
     tMinDistanceTex: TextureSpec('texture', 'rgba', 'float', 'nearest'),
 
     dGridTexType: DefineSpec('string', ['2d', '3d']),

+ 6 - 2
src/mol-repr/structure/visual/gaussian-surface-mesh.ts

@@ -181,7 +181,8 @@ async function createGaussianSurfaceTextureMesh(ctx: VisualContext, unit: Unit,
 
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
-    const gv = await 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 buffer = textureMesh?.doubleBuffer.get();
+    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, buffer?.vertex, buffer?.group, buffer?.normal);
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), unit.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
@@ -211,6 +212,7 @@ export function GaussianSurfaceTextureMeshVisual(materialId: number): UnitsVisua
             geometry.vertexTexture.ref.value.destroy();
             geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
+            geometry.doubleBuffer.destroy();
         }
     }, materialId);
 }
@@ -238,7 +240,8 @@ async function createStructureGaussianSurfaceTextureMesh(ctx: VisualContext, str
 
     const isoLevel = Math.exp(-props.smoothness) / densityTextureData.radiusFactor;
 
-    const gv = await 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 buffer = textureMesh?.doubleBuffer.get();
+    const gv = extractIsosurface(ctx.webgl, densityTextureData.texture, densityTextureData.gridDim, densityTextureData.gridTexDim, densityTextureData.gridTexScale, densityTextureData.transform, isoLevel, true, buffer?.vertex, buffer?.group, buffer?.normal);
 
     const boundingSphere = Sphere3D.expand(Sphere3D(), structure.boundary.sphere, props.radiusOffset + getStructureExtraRadius(structure));
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, boundingSphere, textureMesh);
@@ -267,6 +270,7 @@ export function StructureGaussianSurfaceTextureMeshVisual(materialId: number): C
             geometry.vertexTexture.ref.value.destroy();
             geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
+            geometry.doubleBuffer.destroy();
         }
     }, materialId);
 }

+ 3 - 1
src/mol-repr/volume/isosurface.ts

@@ -175,7 +175,8 @@ async function createVolumeIsosurfaceTextureMesh(ctx: VisualContext, volume: Vol
 
     const { texture, gridDimension, gridTexDim, gridTexScale, transform } = VolumeIsosurfaceTexture.get(volume, ctx.webgl);
 
-    const gv = await extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, textureMesh?.vertexTexture.ref.value, textureMesh?.groupTexture.ref.value, textureMesh?.normalTexture.ref.value);
+    const buffer = textureMesh?.doubleBuffer.get();
+    const gv = extractIsosurface(ctx.webgl, texture, gridDimension, gridTexDim, gridTexScale, transform, isoLevel, false, buffer?.vertex, buffer?.group, buffer?.normal);
 
     const surface = TextureMesh.create(gv.vertexCount, 1, gv.vertexTexture, gv.groupTexture, gv.normalTexture, Volume.getBoundingSphere(volume), textureMesh);
 
@@ -200,6 +201,7 @@ export function IsosurfaceTextureMeshVisual(materialId: number): VolumeVisual<Is
             geometry.vertexTexture.ref.value.destroy();
             geometry.groupTexture.ref.value.destroy();
             geometry.normalTexture.ref.value.destroy();
+            geometry.doubleBuffer.destroy();
         }
     }, materialId);
 }