From 717805899da670763ae35bd4e02d88ec700258e8 Mon Sep 17 00:00:00 2001
From: Alexander Rose <alexander.rose@weirdbyte.de>
Date: Tue, 30 Oct 2018 14:35:31 -0700
Subject: [PATCH] added async readPixels via PBOs for webgl2

---
 package-lock.json                             | Bin 418435 -> 418463 bytes
 src/apps/canvas/index.ts                      |   2 +
 src/apps/canvas/structure-view.ts             |   6 +-
 src/mol-gl/webgl/context.ts                   |  61 ++++++++++++------
 src/mol-math/geometry/gaussian-density/gpu.ts |  43 ++++++++++--
 5 files changed, 84 insertions(+), 28 deletions(-)

diff --git a/package-lock.json b/package-lock.json
index 3e56bf553dd9a8ab29d8a547f0d8eb76e636aa8d..66479bb0b1072af8562b449de08b30246a2ca285 100644
GIT binary patch
delta 399
zcmZoZDmnkC<c4Oy$rEBFHqY{laGhK*Q+6`{boR-*Q#dxuPPxVe64=Z?eYFXYAu;*F
zS+32uPdBr{IrW#iA)JI;tee$tPUWBc;i=^2%P+LtfeeZ1>+~4ewo4f>E@MP+?;0|$
zH<_-G!6>nPaT?>TnCTNvFiLHoc9`*(-}DQHtoqXrY-d?NJ)WOUbbFI9Q&;W81LD&g
zCNZ&YSD(nV^7`}#!Aug{pJy;1VuCOaWHXzCnUdT2^O#dir*Bx#A~9V*iB)j>?8VIC
z{?i-YGfPf)_{Pk#o$oXA&gSU`LX48r4OAJ`wzHPA95n}-B02rM18e{G!u2d!tkW+%
zVV2tdWedwZCa`n11D)fl05)~I%ng>00n-<Fv)FF;)MU-Ag6IWW+c({Q5i9HV#3igR
v=ECKZn5W0uv9WJ|=gOwej3l~!vk%)Devr2Ul37`&XX~<YZC?_@cApgh!NH+B

delta 470
zcmbP#RI>S~<c4Oy=?7jji*26e7wHOSO}1abwpn1xQzkfXvcMGf&63lXm_S6<U*_I?
z|8z4Og4c4X8^YVHc5^mASbX!17dq}>*7W;^j2zpQ4H%a)g8AEl{B01Uwy#NJyd47(
zop+e=FT^R+7ihDxOpa_kJl%kcQFMEsFjH48#H0t4nOL?PPh?tk9irz;2J>NvGq<11
zX10JbW%HQROd%TnoMaK&zGg9VxIc(5zFqn=^S)+?-VbFgZ_Od9fEp%jZ&}Zh%?jbq
zf5t4fU1lpwH^e}gd(Lg2u!E&r9-`go2Fs@ah;ji-CZ+BCnM{GqU<XgvS7BwJUaQK=
zF+G%vQEYNTn8|h-UDi!yNS2i@VSPInyY6jyOd`{dI{=CEuFPiJ<K5UUK%IeR&-QaZ
UY-b>WJ-uF+jeq-r7`FSY0Ku8TeE<Le

diff --git a/src/apps/canvas/index.ts b/src/apps/canvas/index.ts
index db1a873e3..03b49b69b 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 bd35a273e..768dfb01b 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 93924a73f..f81176669 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 2c8a8ddda..34a1f9661 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 }
-- 
GitLab