diff --git a/src/apps/canvas/component/viewport.tsx b/src/apps/canvas/component/viewport.tsx index 7814b26479102e741894b4662e75bc8134029770..3a5d2072cc396ede866ba8720111aaca9b0a5960 100644 --- a/src/apps/canvas/component/viewport.tsx +++ b/src/apps/canvas/component/viewport.tsx @@ -45,9 +45,9 @@ export class Viewport extends React.Component<ViewportProps, ViewportState> { viewer.input.resize.subscribe(() => this.handleResize()) let prevLoci: Loci = EmptyLoci - viewer.input.move.subscribe(({x, y, inside, buttons}) => { + viewer.input.move.subscribe(async ({x, y, inside, buttons}) => { if (!inside || buttons) return - const p = viewer.identify(x, y) + const p = await viewer.identify(x, y) if (p) { const loci = viewer.getLoci(p) diff --git a/src/mol-canvas3d/viewer.ts b/src/mol-canvas3d/viewer.ts index aab76b56f79e2f5444ee06aea0bbf388e141ecb8..788a2a0bd5dfc403fc80060621f36c848666984b 100644 --- a/src/mol-canvas3d/viewer.ts +++ b/src/mol-canvas3d/viewer.ts @@ -43,7 +43,7 @@ interface Viewer { requestDraw: (force?: boolean) => void animate: () => void pick: () => void - identify: (x: number, y: number) => PickingId | undefined + identify: (x: number, y: number) => Promise<PickingId | undefined> mark: (loci: Loci, action: MarkerAction) => void getLoci: (pickingId: PickingId) => Loci @@ -216,7 +216,7 @@ namespace Viewer { pickDirty = false } - function identify(x: number, y: number): PickingId | undefined { + async function identify(x: number, y: number): Promise<PickingId | undefined> { if (pickDirty) return undefined isPicking = true @@ -230,15 +230,15 @@ namespace Viewer { const yp = Math.round(y * pickScale) objectPickTarget.bind() - ctx.readPixels(xp, yp, 1, 1, buffer) + await ctx.readPixelsAsync(xp, yp, 1, 1, buffer) const objectId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) instancePickTarget.bind() - ctx.readPixels(xp, yp, 1, 1, buffer) + await ctx.readPixels(xp, yp, 1, 1, buffer) const instanceId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) groupPickTarget.bind() - ctx.readPixels(xp, yp, 1, 1, buffer) + await ctx.readPixels(xp, yp, 1, 1, buffer) const groupId = decodeIdRGB(buffer[0], buffer[1], buffer[2]) isPicking = false diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index f811766691b590d518df73ac4c2a494bdd0c2e14..a94f6e5831bf9811c94a8e7c3731329bcab053ec 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -7,6 +7,7 @@ import { createProgramCache, ProgramCache } from './program' import { createShaderCache, ShaderCache } from './shader' import { GLRenderingContext, COMPAT_instanced_arrays, COMPAT_standard_derivatives, COMPAT_vertex_array_object, getInstancedArrays, getStandardDerivatives, getVertexArrayObject, isWebGL2, COMPAT_element_index_uint, getElementIndexUint, COMPAT_texture_float, getTextureFloat, COMPAT_texture_float_linear, getTextureFloatLinear, COMPAT_blend_minmax, getBlendMinMax, getFragDepth, COMPAT_frag_depth } from './compat'; +import { createFramebufferCache, FramebufferCache } from './framebuffer'; export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { @@ -122,6 +123,7 @@ export interface Context { readonly shaderCache: ShaderCache readonly programCache: ProgramCache + readonly framebufferCache: FramebufferCache bufferCount: number framebufferCount: number @@ -179,6 +181,7 @@ export function createContext(gl: GLRenderingContext): Context { const shaderCache = createShaderCache() const programCache = createProgramCache() + const framebufferCache = createFramebufferCache() const parameters = { maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE), @@ -197,8 +200,12 @@ export function createContext(gl: GLRenderingContext): Context { 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) + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null) + // need to unbind/bind PBO before/after async awaiting the fence await fence(gl) - gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, buffer); + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, pbo) + gl.getBufferSubData(gl.PIXEL_PACK_BUFFER, 0, buffer) + gl.bindBuffer(gl.PIXEL_PACK_BUFFER, null) } } else { readPixelsAsync = async (x: number, y: number, width: number, height: number, buffer: Uint8Array) => { @@ -223,6 +230,7 @@ export function createContext(gl: GLRenderingContext): Context { shaderCache, programCache, + framebufferCache, bufferCount: 0, framebufferCount: 0, @@ -254,6 +262,7 @@ export function createContext(gl: GLRenderingContext): Context { unbindResources(gl) programCache.dispose() shaderCache.dispose() + framebufferCache.dispose() // TODO destroy buffers and textures } } diff --git a/src/mol-gl/webgl/framebuffer.ts b/src/mol-gl/webgl/framebuffer.ts index f359c1ed64d15b238e3da589ad2ca6131c8cbe0e..77eeee74a07a52e16f8723109c259b6efbd84b46 100644 --- a/src/mol-gl/webgl/framebuffer.ts +++ b/src/mol-gl/webgl/framebuffer.ts @@ -6,6 +6,7 @@ import { Context } from './context' import { idFactory } from 'mol-util/id-factory'; +import { ReferenceCache, createReferenceCache } from 'mol-util/reference-cache'; const getNextFramebufferId = idFactory() @@ -37,4 +38,14 @@ export function createFramebuffer (ctx: Context): Framebuffer { ctx.framebufferCount -= 1 } } +} + +export type FramebufferCache = ReferenceCache<Framebuffer, string, Context> + +export function createFramebufferCache(): FramebufferCache { + return createReferenceCache( + (name: string) => name, + (ctx: Context) => createFramebuffer(ctx), + (framebuffer: Framebuffer) => { framebuffer.destroy() } + ) } \ No newline at end of file diff --git a/src/mol-math/geometry/gaussian-density/gpu.ts b/src/mol-math/geometry/gaussian-density/gpu.ts index 34a1f966166a542f91ab944c0d2b49f4340112b9..dffd04cad16e296ee742095aab2e298c08eed766 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -16,11 +16,13 @@ import { ValueCell, defaults } from 'mol-util' import { RenderableState, Renderable } from 'mol-gl/renderable' import { createRenderable, createGaussianDensityRenderObject } from 'mol-gl/render-object' 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, isWebGL2 } from 'mol-gl/webgl/compat'; import { decodeIdRGB } from 'mol-geo/geometry/picking'; +/** name for shared framebuffer used for gpu gaussian surface operations */ +const FramebufferName = 'gaussian-density-gpu' + export async function GaussianDensityGPU(ctx: RuntimeContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityProps): Promise<DensityData> { const webgl = defaults(props.webgl, getWebGLContext()) // always use texture2d when the gaussian density needs to be downloaded from the GPU, @@ -68,10 +70,10 @@ async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: Context, pos // - const { gl } = webgl + const { gl, framebufferCache } = webgl const { uCurrentSlice, uCurrentX, uCurrentY } = renderObject.values - const framebuffer = createFramebuffer(webgl) + const framebuffer = framebufferCache.get(webgl, FramebufferName).value framebuffer.bind() setRenderingDefaults(gl) @@ -108,8 +110,6 @@ async function GaussianDensityTexture2d(ctx: RuntimeContext, webgl: Context, pos setupGroupIdRendering(webgl, renderable) render(texture) - framebuffer.destroy() // clean up - await ctx.update({ message: 'gpu gaussian density calculation' }); await webgl.waitForGpuCommandsComplete() @@ -129,10 +129,10 @@ async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: Context, pos // - const { gl } = webgl + const { gl, framebufferCache } = webgl const { uCurrentSlice } = renderObject.values - const framebuffer = createFramebuffer(webgl) + const framebuffer = framebufferCache.get(webgl, FramebufferName).value framebuffer.bind() setRenderingDefaults(gl) gl.viewport(0, 0, dx, dy) @@ -157,8 +157,6 @@ async function GaussianDensityTexture3d(ctx: RuntimeContext, webgl: Context, pos setupGroupIdRendering(webgl, renderable) render(texture) - framebuffer.destroy() // clean up - await ctx.update({ message: 'gpu gaussian density calculation' }); await webgl.waitForGpuCommandsComplete() @@ -310,27 +308,10 @@ function getTexture2dSize(maxTexSize: number, gridDim: Vec3) { return { texDimX, texDimY, texRows, texCols } } - -// 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 { framebufferCache } = ctx const [ dx, dy, dz ] = dim const { width, height } = texture const fboTexCols = Math.floor(width / dx) @@ -343,25 +324,10 @@ async function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { const image = new Uint8Array(width * height * 4) - const framebuffer = createFramebuffer(ctx) + const framebuffer = framebufferCache.get(ctx, FramebufferName).value framebuffer.bind() - texture.attachFramebuffer(framebuffer, 0) - - 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() + texture.attachFramebuffer(framebuffer, 0) + await ctx.readPixelsAsync(0, 0, width, height, image) let j = 0 let tmpCol = 0 @@ -385,40 +351,4 @@ async function fieldFromTexture2d(ctx: Context, texture: Texture, dim: Vec3) { console.timeEnd('fieldFromTexture2d') return { field, idField } -} - -// function fieldFromTexture3d(ctx: Context, texture: Texture, dim: Vec3) { -// console.time('fieldFromTexture3d') -// const { gl } = ctx -// const { width, height, depth } = texture - -// const space = Tensor.Space(dim, [2, 1, 0], Float32Array) -// const data = space.create() -// const field = Tensor.create(space, data) -// const idData = space.create() -// const idField = Tensor.create(space, idData) - -// const slice = new Uint8Array(width * height * 4) - -// const framebuffer = createFramebuffer(ctx) -// framebuffer.bind() - -// let j = 0 -// for (let i = 0; i < depth; ++i) { -// texture.attachFramebuffer(framebuffer, 0, i) -// gl.readPixels(0, 0, width, height, gl.RGBA, gl.UNSIGNED_BYTE, slice) -// for (let iy = 0; iy < height; ++iy) { -// for (let ix = 0; ix < width; ++ix) { -// const idx = 4 * (iy * width + ix) -// data[j] = slice[idx + 3] / 255 -// idData[j] = decodeIdRGB(slice[idx], slice[idx + 1], slice[idx + 2]) -// ++j -// } -// } -// } - -// framebuffer.destroy() -// console.timeEnd('fieldFromTexture3d') - -// return { field, idField } -// } \ No newline at end of file +} \ No newline at end of file