diff --git a/src/mol-gl/shader/gaussian-density.frag b/src/mol-gl/shader/gaussian-density.frag
index 547840a30a93c92bcf78951ce0d37925f11b3d56..546267b47df69a4640019cd537ffad4ba96b4272 100644
--- a/src/mol-gl/shader/gaussian-density.frag
+++ b/src/mol-gl/shader/gaussian-density.frag
@@ -43,7 +43,7 @@ const vec3 color = vec3(1.0, 1.0, 1.0);
 void main() {
     float radiusSq = radius * radius;
     vec2 v = gl_FragCoord.xy - vec2(uCurrentX, uCurrentY) - 0.5;
-    out_FragColor = vec4(color, calcDensity(v.x, v.y, uCurrentSlice, radiusSq));
+    gl_FragColor = vec4(color, calcDensity(v.x, v.y, uCurrentSlice, radiusSq));
     #if dDrawBuffers >= 4
         out1 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 1.0, radiusSq));
         out2 = vec4(color, calcDensity(v.x, v.y, uCurrentSlice + 2.0, radiusSq));
diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts
index f9b70a3c0bc72f480cca045f8649740ff8ac8e7a..869526c5fe3c76e8364ccb09c8a9c312502b1f3b 100644
--- a/src/mol-gl/webgl/context.ts
+++ b/src/mol-gl/webgl/context.ts
@@ -51,6 +51,35 @@ function unbindFramebuffer(gl: GLRenderingContext) {
     gl.bindFramebuffer(gl.FRAMEBUFFER, null)
 }
 
+const tmpPixel = new Uint8Array(1 * 4);
+async function waitForGpuCommandsComplete(gl: GLRenderingContext) {
+    if (isWebGL2(gl)) {
+        const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0);
+        if (sync) {
+            // TODO too slow in Firefox
+            // await new Promise(resolve => {
+            //     const check = async () => {
+            //         if (gl.getSyncParameter(sync, gl.SYNC_STATUS) === gl.SIGNALED) {
+            //             gl.deleteSync(sync)
+            //             resolve();
+            //         } else {
+            //             setTimeout(check, 50)
+            //         }
+            //     };
+            //     setTimeout(check, 10)
+            // })
+            gl.deleteSync(sync)
+            gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+        } else {
+            console.warn('unable to get webgl sync object')
+            gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+        }
+    } else {
+        console.info('webgl sync object not supported in webgl 1')
+        gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+    }
+}
+
 export function createImageData(buffer: ArrayLike<number>, width: number, height: number) {
     const w = width * 4
     const h = height
@@ -66,6 +95,8 @@ export function createImageData(buffer: ArrayLike<number>, width: number, height
     return new ImageData(data, width, height);
 }
 
+//
+
 type Extensions = {
     instancedArrays: COMPAT_instanced_arrays
     standardDerivatives: COMPAT_standard_derivatives
@@ -100,11 +131,10 @@ export interface Context {
 
     unbindFramebuffer: () => void
     readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
+    waitForGpuCommandsComplete: () => Promise<void>
     destroy: () => void
 }
 
-
-
 export function createContext(gl: GLRenderingContext): Context {
     const instancedArrays = getInstancedArrays(gl)
     if (instancedArrays === null) {
@@ -178,6 +208,7 @@ export function createContext(gl: GLRenderingContext): Context {
             //     console.error('Reading pixels failed. Framebuffer not complete.')
             // }
         },
+        waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl),
 
         destroy: () => {
             unbindResources(gl)
diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts
index 170170fc0204e5d8d72f4d775d305f8846125fcd..259d3ff576a1acd61ff554f271dfa287bca332b7 100644
--- a/src/mol-math/geometry/gaussian-density/gpu.ts
+++ b/src/mol-math/geometry/gaussian-density/gpu.ts
@@ -113,6 +113,9 @@ async function GaussianDensitySingleDrawBuffer(ctx: RuntimeContext, webgl: Conte
 
     framebuffer.destroy() // clean up
 
+    await ctx.update({ message: 'gpu gaussian density calculation' });
+    await webgl.waitForGpuCommandsComplete()
+
     return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
 }
 
@@ -133,29 +136,15 @@ async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Contex
     const framebuffer = createFramebuffer(webgl)
     framebuffer.bind()
 
+    setDrawBuffers(gl, drawBuffers)
+    gl.viewport(0, 0, dx, dy)
+    setRenderingDefaults(gl)
+
     if (!texture) {
         texture = createTexture(webgl, 'volume-uint8', 'rgba', 'ubyte', 'linear')
     }
     texture.define(dx, dy, dz)
 
-    if (drawBuffers === 1) {
-        gl.drawBuffers([
-            gl.COLOR_ATTACHMENT0,
-        ]);
-    } else if (drawBuffers === 4) {
-        gl.drawBuffers([
-            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
-        ]);
-    } else if (drawBuffers === 8) {
-        gl.drawBuffers([
-            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
-            gl.COLOR_ATTACHMENT4, gl.COLOR_ATTACHMENT5, gl.COLOR_ATTACHMENT6, gl.COLOR_ATTACHMENT7,
-        ]);
-    }
-
-    gl.viewport(0, 0, dx, dy)
-    setRenderingDefaults(gl)
-
     // z-slices to be render with multi render targets
     const dzMulti = Math.floor(dz / drawBuffers) * drawBuffers
 
@@ -167,7 +156,7 @@ async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Contex
         for (let k = 0; k < drawBuffers; ++k) {
             texture.attachFramebuffer(framebuffer, k as TextureAttachment, i + k)
         }
-        renderable.render('draw')
+        renderable.render('draw');
     }
 
     // render single target
@@ -188,6 +177,9 @@ async function GaussianDensityMultiDrawBuffer(ctx: RuntimeContext, webgl: Contex
 
     framebuffer.destroy() // clean up
 
+    await ctx.update({ message: 'gpu gaussian density calculation' });
+    await webgl.waitForGpuCommandsComplete()
+
     return { texture, scale: Vec3.inverse(Vec3.zero(), delta), bbox: expandedBox, dim }
 }
 
@@ -288,6 +280,23 @@ function setRenderingDefaults(gl: GLRenderingContext) {
     gl.enable(gl.BLEND)
 }
 
+function setDrawBuffers(gl: WebGL2RenderingContext, drawBuffers: number) {
+    if (drawBuffers === 1) {
+        gl.drawBuffers([
+            gl.COLOR_ATTACHMENT0,
+        ]);
+    } else if (drawBuffers === 4) {
+        gl.drawBuffers([
+            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
+        ]);
+    } else if (drawBuffers === 8) {
+        gl.drawBuffers([
+            gl.COLOR_ATTACHMENT0, gl.COLOR_ATTACHMENT1, gl.COLOR_ATTACHMENT2, gl.COLOR_ATTACHMENT3,
+            gl.COLOR_ATTACHMENT4, gl.COLOR_ATTACHMENT5, gl.COLOR_ATTACHMENT6, gl.COLOR_ATTACHMENT7,
+        ]);
+    }
+}
+
 function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
     console.time('fieldFromTexture2d')
     const { gl } = ctx