diff --git a/package-lock.json b/package-lock.json
index 3e56bf553dd9a8ab29d8a547f0d8eb76e636aa8d..66479bb0b1072af8562b449de08b30246a2ca285 100644
Binary files a/package-lock.json and b/package-lock.json differ
diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts
index db1a873e3f91795a36b3663e63f1417f75817cdf..03b49b69b33663d35650e5eebee7741439b4fd42 100644
--- a/src/apps/canvas/index.ts
+++ b/src/apps/canvas/index.ts
@@ -23,6 +23,8 @@ const assemblyId = urlQueryParameter('assembly')
 const pdbId = urlQueryParameter('pdb')
 if (pdbId) app.loadPdbIdOrMmcifUrl(pdbId, { assemblyId })
 
+app.loadPdbIdOrMmcifUrl('http://localhost:8091/ngl/data/1crn.cif')
+
 // app.loadPdbIdOrMmcifUrl('3pqr')
 // app.loadCcp4Url('http://localhost:8091/ngl/data/3pqr-mode0.ccp4')
 
diff --git a/src/apps/canvas/structure-view.ts b/src/apps/canvas/structure-view.ts
index bd35a273e5f6cd52f55fbb6a2c7af42ad78b1ab4..768dfb01ba9c2d5e16c0f4f4f8f7efb5a7b10623 100644
--- a/src/apps/canvas/structure-view.ts
+++ b/src/apps/canvas/structure-view.ts
@@ -66,11 +66,11 @@ interface StructureViewProps {
 
 export async function StructureView(app: App, viewer: Viewer, models: ReadonlyArray<Model>, props: StructureViewProps = {}): Promise<StructureView> {
     const active: { [k: string]: boolean } = {
-        cartoon: true,
+        cartoon: false,
         point: false,
-        surface: false,
+        surface: true,
         ballAndStick: false,
-        carbohydrate: true,
+        carbohydrate: false,
         spacefill: false,
         distanceRestraint: false,
         symmetryAxes: false,
diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts
index 93924a73f7ee88ff24895b4c27d603cd45728dee..f811766691b590d518df73ac4c2a494bdd0c2e14 100644
--- a/src/mol-gl/webgl/context.ts
+++ b/src/mol-gl/webgl/context.ts
@@ -52,28 +52,33 @@ function unbindFramebuffer(gl: GLRenderingContext) {
 }
 
 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)
+
+function fence(gl: WebGL2RenderingContext) {
+    return new Promise(resolve => {
+        const sync = gl.fenceSync(gl.SYNC_GPU_COMMANDS_COMPLETE, 0)
+        if (!sync) {
+            console.warn('could not create a WebGL2 sync object')
             gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+            resolve()
         } else {
-            console.warn('unable to get webgl sync object')
-            gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
+            gl.flush(); // Ensure the fence is submitted.
+            const check = () => {
+                const status = gl.getSyncParameter(sync, gl.SYNC_STATUS)
+                if (status == gl.SIGNALED) {
+                    gl.deleteSync(sync);
+                    resolve();
+                } else {
+                    setTimeout(check, 0)
+                }
+            }
+            setTimeout(check, 0)
         }
+    })
+}
+
+async function waitForGpuCommandsComplete(gl: GLRenderingContext) {
+    if (isWebGL2(gl)) {
+        await fence(gl)
     } else {
         console.info('webgl sync object not supported in webgl 1')
         gl.readPixels(0, 0, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, tmpPixel)
@@ -133,6 +138,7 @@ export interface Context {
 
     unbindFramebuffer: () => void
     readPixels: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => void
+    readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
     waitForGpuCommandsComplete: () => Promise<void>
     destroy: () => void
 }
@@ -184,6 +190,22 @@ export function createContext(gl: GLRenderingContext): Context {
         throw new Error('Need "MAX_VERTEX_TEXTURE_IMAGE_UNITS" >= 4')
     }
 
+    let readPixelsAsync: (x: number, y: number, width: number, height: number, buffer: Uint8Array) => Promise<void>
+    if (isWebGL2(gl)) {
+        const pbo = gl.createBuffer()
+        readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
+            gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo)
+            gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STATIC_COPY)
+            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0)
+            await fence(gl)
+            gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, buffer);
+        }
+    } else {
+        readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => {
+            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, buffer)
+        }
+    }
+
     return {
         gl,
         isWebGL2: isWebGL2(gl),
@@ -225,6 +247,7 @@ export function createContext(gl: GLRenderingContext): Context {
             //     console.error('Reading pixels failed. Framebuffer not complete.')
             // }
         },
+        readPixelsAsync,
         waitForGpuCommandsComplete: () => waitForGpuCommandsComplete(gl),
 
         destroy: () => {
diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts
index 2c8a8ddda83090c2ac8a4f1e6675d3aaeb99b2a1..34a1f966166a542f91ab944c0d2b49f4340112b9 100644
--- a/src/mol-math/geometry/gaussian-density/gpu.ts
+++ b/src/mol-math/geometry/gaussian-density/gpu.ts
@@ -18,7 +18,7 @@ import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/rend
 import { Context, createContext, getGLContext } from 'mol-gl/webgl/context';
 import { createFramebuffer } from 'mol-gl/webgl/framebuffer';
 import { createTexture, Texture } from 'mol-gl/webgl/texture';
-import { GLRenderingContext } from 'mol-gl/webgl/compat';
+import { GLRenderingContext, isWebGL2 } from 'mol-gl/webgl/compat';
 import { decodeIdRGB } from 'mol-geo/geometry/picking';
 
 export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> {
@@ -28,7 +28,7 @@ export async function GaussianDensityGPU(ctx: RuntimeContext, position: Position
     console.time('GaussianDensityTexture2d')
     const { scale, bbox, texture, dim } = await GaussianDensityTexture2d(ctx, webgl, position, box, radius, props)
     console.timeEnd('GaussianDensityTexture2d')
-    const { field, idField } = fieldFromTexture2d(webgl, texture, dim)
+    const { field, idField } = await fieldFromTexture2d(webgl, texture, dim)
 
     const transform = Mat4.identity()
     Mat4.fromScaling(transform, scale)
@@ -310,7 +310,25 @@ function getTexture2dSize(maxTexSize: number, gridDim: Vec3) {
     return { texDimX, texDimY, texRows, texCols }
 }
 
-function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
+  
+//   function pick_nonblocking_getBufferSubData() {
+//     gl.readPixels(mouse.x, pickingTexture.height - mouse.y, 1, 1, gl.RGBA, gl.UNSIGNED_BYTE, 0);
+  
+//     fence().then(function() {
+//       stats1.begin();
+//       gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, readbackBuffer);
+//       stats1.end();
+//       gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null);
+  
+//       var id = (readbackBuffer[0] << 16) | (readbackBuffer[1] << 8) | (readbackBuffer[2]);
+//       render(id);
+//       gl.finish();
+//       stats2.end();
+//     });
+//   }
+
+async function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
+    console.log('isWebGL2', isWebGL2(ctx.gl))
     console.time('fieldFromTexture2d')
     const { gl } = ctx
     const [ dx, dy, dz ] = dim
@@ -327,9 +345,23 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
 
     const framebuffer = createFramebuffer(ctx)
     framebuffer.bind()
-
     texture.attachFramebuffer(framebuffer, 0)
-    gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image)
+    
+    if (isWebGL2(gl)) {
+        const pbo = gl.createBuffer()
+        gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo)
+        gl.bufferData(gl.PIXEL_PACK_BUFFER, width * height * 4, gl.STATIC_COPY)
+        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, 0)
+        await ctx.waitForGpuCommandsComplete()
+        gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, image);
+        gl.deleteBuffer(pbo)
+    } else {
+        gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image)
+    }
+    // gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, image)
+
+    framebuffer.destroy()
+    gl.finish()
 
     let j = 0
     let tmpCol = 0
@@ -350,7 +382,6 @@ function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) {
         tmpCol++
     }
 
-    framebuffer.destroy()
     console.timeEnd('fieldFromTexture2d')
 
     return { field, idField }