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