diff --git a/src/apps/viewer/index.html b/src/apps/viewer/index.html index 9fe81219e16721be9b77f5bbba68b30dbf0bb317..c05ed16bc5b439ae7ad931c55acc3366afb10541 100644 --- a/src/apps/viewer/index.html +++ b/src/apps/viewer/index.html @@ -50,12 +50,14 @@ var disableAntialiasing = getParam('disable-antialiasing', '[^&]+').trim() === '1'; var pixelScale = parseFloat(getParam('pixel-scale', '[^&]+').trim() || '1'); + var enableWboit = getParam('enable-wboit', '[^&]+').trim() === '1'; var hideControls = getParam('hide-controls', '[^&]+').trim() === '1'; var pdbProvider = getParam('pdb-provider', '[^&]+').trim().toLowerCase(); var emdbProvider = getParam('emdb-provider', '[^&]+').trim().toLowerCase(); var viewer = new molstar.Viewer('app', { disableAntialiasing: disableAntialiasing, pixelScale: pixelScale, + enableWboit: enableWboit, layoutShowControls: !hideControls, viewportShowExpand: false, pdbProvider: pdbProvider || 'pdbe', diff --git a/src/apps/viewer/index.ts b/src/apps/viewer/index.ts index d62b7440e0a40226ca667962bc8cd2e0c0278382..99448e793c6fbb91dbed01a8c1a874bed3d97013 100644 --- a/src/apps/viewer/index.ts +++ b/src/apps/viewer/index.ts @@ -68,6 +68,7 @@ const DefaultViewerOptions = { layoutShowLeftPanel: true, disableAntialiasing: false, pixelScale: 1, + enableWboit: false, viewportShowExpand: PluginConfig.Viewport.ShowExpand.defaultValue, viewportShowControls: PluginConfig.Viewport.ShowControls.defaultValue, @@ -117,6 +118,7 @@ export class Viewer { config: [ [PluginConfig.General.DisableAntialiasing, o.disableAntialiasing], [PluginConfig.General.PixelScale, o.pixelScale], + [PluginConfig.General.EnableWboit, o.enableWboit], [PluginConfig.Viewport.ShowExpand, o.viewportShowExpand], [PluginConfig.Viewport.ShowControls, o.viewportShowControls], [PluginConfig.Viewport.ShowSettings, o.viewportShowSettings], diff --git a/src/mol-canvas3d/canvas3d.ts b/src/mol-canvas3d/canvas3d.ts index 720e51cb76df5effb0ae4866ed494063d4b77145..5322f1bf133722c7802b474166e7fcca28dff341 100644 --- a/src/mol-canvas3d/canvas3d.ts +++ b/src/mol-canvas3d/canvas3d.ts @@ -149,7 +149,7 @@ namespace Canvas3D { export interface DragEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, pageStart: Vec2, pageEnd: Vec2 } export interface ClickEvent { current: Representation.Loci, buttons: ButtonsType, button: ButtonsType.Flag, modifiers: ModifiersKeys, position?: Vec3 } - export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number }> = {}) { + export function fromCanvas(canvas: HTMLCanvasElement, props: Partial<Canvas3DProps> = {}, attribs: Partial<{ antialias: boolean, pixelScale: number, pickScale: number, enableWboit: boolean }> = {}) { const gl = getGLContext(canvas, { alpha: true, antialias: attribs.antialias ?? true, @@ -301,7 +301,7 @@ namespace Canvas3D { if (MultiSamplePass.isEnabled(p.multiSample)) { multiSampleHelper.render(renderer, cam, scene, helper, true, p.transparentBackground, p); } else { - const toDrawingBuffer = false; // !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0; + const toDrawingBuffer = !PostprocessingPass.isEnabled(p.postprocessing) && scene.volumes.renderables.length === 0 && !passes.draw.wboitEnabled; passes.draw.render(renderer, cam, scene, helper, toDrawingBuffer, p.transparentBackground); if (!toDrawingBuffer) passes.postprocessing.render(cam, true, p.postprocessing); } @@ -667,7 +667,7 @@ namespace Canvas3D { } }, getImagePass: (props: Partial<ImageProps> = {}) => { - return new ImagePass(webgl, renderer, scene, camera, helper, props); + return new ImagePass(webgl, renderer, scene, camera, helper, passes.draw.wboitEnabled, props); }, get props() { diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 6383b94b6d6677a2cacf121eb3857ff84c270469..861c8500850fc6458c20772adeef9d2137ecb34d 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -2,10 +2,11 @@ * Copyright (c) 2019-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> */ import { WebGLContext } from '../../mol-gl/webgl/context'; -import { RenderTarget } from '../../mol-gl/webgl/render-target'; +import { createNullRenderTarget, RenderTarget } from '../../mol-gl/webgl/render-target'; import Renderer from '../../mol-gl/renderer'; import Scene from '../../mol-gl/scene'; import { Texture } from '../../mol-gl/webgl/texture'; @@ -50,24 +51,30 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text } export class DrawPass { + private readonly drawTarget: RenderTarget + readonly colorTarget: RenderTarget readonly depthTexture: Texture readonly depthTexturePrimitives: Texture - readonly depthTexturePrimitivesTransparent: Texture private readonly packedDepth: boolean private depthTarget: RenderTarget private depthTargetPrimitives: RenderTarget | null - private depthTargetPrimitivesTransparent: RenderTarget | null private depthTargetVolumes: RenderTarget | null private depthTextureVolumes: Texture private depthMerge: DepthMergeRenderable - private wboit: WboitPass + private wboit: WboitPass | undefined + + get wboitEnabled() { + return !!this.wboit?.enabled; + } - constructor(private webgl: WebGLContext, width: number, height: number) { + constructor(private webgl: WebGLContext, width: number, height: number, enableWboit: boolean) { const { extensions, resources } = webgl; + this.drawTarget = createNullRenderTarget(webgl.gl); + this.colorTarget = webgl.createRenderTarget(width, height); this.packedDepth = !extensions.depthTexture; @@ -75,20 +82,17 @@ export class DrawPass { this.depthTexture = this.depthTarget.texture; this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null; - this.depthTargetPrimitivesTransparent = this.packedDepth ? webgl.createRenderTarget(width, height) : null; this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null; this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest'); - this.depthTexturePrimitivesTransparent = this.depthTargetPrimitivesTransparent ? this.depthTargetPrimitivesTransparent.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest'); this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', 'ushort', 'nearest'); if (!this.packedDepth) { this.depthTexturePrimitives.define(width, height); - this.depthTexturePrimitivesTransparent.define(width, height); this.depthTextureVolumes.define(width, height); } this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth); - this.wboit = new WboitPass(webgl, width, height); + this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; } setSize(width: number, height: number) { @@ -104,11 +108,6 @@ export class DrawPass { } else { this.depthTexturePrimitives.define(width, height); } - if (this.depthTargetPrimitivesTransparent) { - this.depthTargetPrimitivesTransparent.setSize(width, height); - } else { - this.depthTexturePrimitivesTransparent.define(width, height); - } if (this.depthTargetVolumes) { this.depthTargetVolumes.setSize(width, height); @@ -118,107 +117,132 @@ export class DrawPass { ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height)); - this.wboit.setSize(width, height); + if (this.wboit?.enabled) { + this.wboit.setSize(width, height); + } } } - _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) { - const { x, y, width, height } = camera.viewport; - renderer.setViewport(x, y, width, height); + private _depthMerge() { + const { state, gl } = this.webgl; + + this.depthMerge.update(); + this.depthTarget.bind(); + state.disable(gl.BLEND); + state.disable(gl.DEPTH_TEST); + state.disable(gl.CULL_FACE); + state.depthMask(false); + state.clearColor(1, 1, 1, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + this.depthMerge.render(); + } + + private _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) { + if (!this.wboit?.enabled) throw new Error('expected wboit to be enabled'); + + const renderTarget = toDrawingBuffer ? this.drawTarget : this.colorTarget; + renderTarget.bind(); + renderer.clear(true); + + // render opaque primitives + this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth'); + renderTarget.bind(); + renderer.renderWboitOpaque(scene.primitives, camera, null); - // console.log('toDrawingBuffer', toDrawingBuffer); + // render opaque volumes + this.depthTextureVolumes.attachFramebuffer(renderTarget.framebuffer, 'depth'); + renderTarget.bind(); + renderer.clearDepth(); + renderer.renderWboitOpaque(scene.volumes, camera, this.depthTexturePrimitives); - let renderTarget; + // merge depth of opaque primitives and volumes + this._depthMerge(); + + // render transparent primitives and volumes + this.wboit.bind(); + renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexture); + renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexture); + + // evaluate wboit + this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth'); + renderTarget.bind(); + this.wboit.render(); + } + + private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean) { if (toDrawingBuffer) { - renderTarget = null; + this.webgl.unbindFramebuffer(); } else { + this.colorTarget.bind(); if (!this.packedDepth) { this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); } - renderTarget = this.colorTarget; } + renderer.clear(true); + // TODO: split into opaque and transparent pass to handle opaque volume isosurfaces + renderer.renderBlended(scene.primitives, camera, null); + // do a depth pass if not rendering to drawing buffer and // extensions.depthTexture is unsupported (i.e. depthTarget is set) if (!toDrawingBuffer && this.depthTargetPrimitives) { - console.log('packed depth opaque primitives'); - renderer.render(this.depthTargetPrimitives, scene.primitives, camera, 'depth', true, transparentBackground, 1, null, false, this.wboit); + this.depthTargetPrimitives.bind(); + renderer.clear(false); + renderer.renderDepth(scene.primitives, camera, null); + this.colorTarget.bind(); } - // render opaque color - renderer.render(renderTarget, scene.primitives, camera, 'color', true, transparentBackground, 1, null, false, this.wboit); + // do direct-volume rendering + if (!toDrawingBuffer) { + if (!this.packedDepth) { + this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + renderer.clearDepth(); + } + renderer.renderBlended(scene.volumes, camera, this.depthTexturePrimitives); - if (helper.debug.isEnabled) { - helper.debug.syncVisibility(); - renderer.render(renderTarget, helper.debug.scene, camera, 'color', false, transparentBackground, 1, null, false, this.wboit); - } - if (helper.handle.isEnabled) { - renderer.render(renderTarget, helper.handle.scene, camera, 'color', false, transparentBackground, 1, null, false, this.wboit); - } - if (helper.camera.isEnabled) { - helper.camera.update(camera); - renderer.render(renderTarget, helper.camera.scene, helper.camera.camera, 'color', false, transparentBackground, 1, null, false, this.wboit); + // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set) + if (this.depthTargetVolumes) { + this.depthTargetVolumes.bind(); + renderer.clear(false); + renderer.renderDepth(scene.volumes, camera, this.depthTexturePrimitives); + this.colorTarget.bind(); + } } - if (!toDrawingBuffer && this.depthTargetPrimitivesTransparent) { - console.log('packed depth transparent primitives'); - renderer.render(this.depthTargetPrimitivesTransparent, scene.primitives, camera, 'depth', true, transparentBackground, 1, null, false, this.wboit); + // merge depths from primitive and volume rendering + if (!toDrawingBuffer) { + this._depthMerge(); + this.colorTarget.bind(); } + } + + private _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) { + const { x, y, width, height } = camera.viewport; + renderer.setViewport(x, y, width, height); + renderer.setTransparentBackground(transparentBackground); + renderer.setDrawingBufferScale(1); + renderer.update(camera); - // render transparent color - if (renderTarget !== null) { - this.depthTexturePrimitivesTransparent.attachFramebuffer(renderTarget.framebuffer, 'depth'); - renderer.render(renderTarget, scene.primitives, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit); + if (this.wboitEnabled) { + this._renderWboit(renderer, camera, scene, toDrawingBuffer); + } else { + this._renderBlended(renderer, camera, scene, toDrawingBuffer); } if (helper.debug.isEnabled) { helper.debug.syncVisibility(); - renderer.render(renderTarget, helper.debug.scene, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit); + renderer.renderBlended(helper.debug.scene, camera, null); } if (helper.handle.isEnabled) { - renderer.render(renderTarget, helper.handle.scene, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit); + renderer.renderBlended(helper.handle.scene, camera, null); } if (helper.camera.isEnabled) { helper.camera.update(camera); - renderer.render(renderTarget, helper.camera.scene, helper.camera.camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit); + renderer.update(helper.camera.camera); + renderer.renderBlended(helper.camera.scene, helper.camera.camera, null); } - // do direct-volume rendering - if (!toDrawingBuffer) { - if (!this.packedDepth) { - this.depthTextureVolumes.attachFramebuffer(renderTarget!.framebuffer, 'depth'); - renderTarget!.bind(); - this.webgl.state.enable(this.webgl.gl.DEPTH_TEST); - this.webgl.state.depthMask(true); - this.webgl.gl.viewport(x, y, width, height); - this.webgl.gl.scissor(x, y, width, height); - this.webgl.gl.clear(this.webgl.gl.DEPTH_BUFFER_BIT); - } - renderer.render(renderTarget, scene.volumes, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit); - - // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set) - if (this.depthTargetVolumes) { - console.log('packed depth volumes'); - renderer.render(this.depthTargetVolumes, scene.volumes, camera, 'depth', true, transparentBackground, 1, this.depthTexturePrimitives, false, this.wboit); - } - } - - // merge depths from primitive and volume rendering - if (!toDrawingBuffer) { - this.depthMerge.update(); - this.depthTarget.bind(); - // this.webgl.state.disable(this.webgl.gl.SCISSOR_TEST); - this.webgl.state.disable(this.webgl.gl.BLEND); - this.webgl.state.disable(this.webgl.gl.DEPTH_TEST); - this.webgl.state.disable(this.webgl.gl.CULL_FACE); - this.webgl.state.depthMask(false); - this.webgl.state.clearColor(1, 1, 1, 1); - this.webgl.gl.viewport(x, y, width, height); - this.webgl.gl.scissor(x, y, width, height); - this.webgl.gl.clear(this.webgl.gl.COLOR_BUFFER_BIT); - this.depthMerge.render(); - renderTarget!.bind(); - } + this.webgl.gl.flush(); } render(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) { diff --git a/src/mol-canvas3d/passes/image.ts b/src/mol-canvas3d/passes/image.ts index 36a12f6fce280a913feed2fda7a32eb0d8ede377..740c66949d30cb6d47e2cbe88cbedf8c0f1576a0 100644 --- a/src/mol-canvas3d/passes/image.ts +++ b/src/mol-canvas3d/passes/image.ts @@ -46,10 +46,10 @@ export class ImagePass { get width() { return this._width; } get height() { return this._height; } - constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, props: Partial<ImageProps>) { + constructor(private webgl: WebGLContext, private renderer: Renderer, private scene: Scene, private camera: Camera, helper: Helper, enableWboit: boolean, props: Partial<ImageProps>) { this.props = { ...PD.getDefaultValues(ImageParams), ...props }; - this.drawPass = new DrawPass(webgl, 128, 128); + this.drawPass = new DrawPass(webgl, 128, 128, enableWboit); this.postprocessingPass = new PostprocessingPass(webgl, this.drawPass); this.multiSamplePass = new MultiSamplePass(webgl, this.drawPass, this.postprocessingPass); this.multiSampleHelper = new MultiSampleHelper(this.multiSamplePass); diff --git a/src/mol-canvas3d/passes/multi-sample.ts b/src/mol-canvas3d/passes/multi-sample.ts index 12dbadc6a3b450f49aeecab91d5d35c73a374677..6db628cd96cc1cec14066676dabc18eea85913d6 100644 --- a/src/mol-canvas3d/passes/multi-sample.ts +++ b/src/mol-canvas3d/passes/multi-sample.ts @@ -69,11 +69,11 @@ export class MultiSamplePass { private compose: ComposeRenderable constructor(private webgl: WebGLContext, private drawPass: DrawPass, private postprocessing: PostprocessingPass) { - const { extensions } = webgl; + const { colorBufferFloat, textureFloat } = webgl.extensions; const width = drawPass.colorTarget.getWidth(); const height = drawPass.colorTarget.getHeight(); this.colorTarget = webgl.createRenderTarget(width, height, false); - this.composeTarget = webgl.createRenderTarget(width, height, false, extensions.colorBufferFloat ? 'float32' : 'uint8'); + this.composeTarget = webgl.createRenderTarget(width, height, false, colorBufferFloat && textureFloat ? 'float32' : 'uint8'); this.holdTarget = webgl.createRenderTarget(width, height, false); this.compose = getComposeRenderable(webgl, drawPass.colorTarget.texture); } @@ -100,6 +100,14 @@ export class MultiSamplePass { } } + private bindOutputTarget(toDrawingBuffer: boolean) { + if (toDrawingBuffer) { + this.webgl.unbindFramebuffer(); + } else { + this.colorTarget.bind(); + } + } + private renderMultiSample(renderer: Renderer, camera: Camera | StereoCamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean, props: Props) { const { compose, composeTarget, drawPass, postprocessing, webgl } = this; const { gl, state } = webgl; @@ -159,12 +167,7 @@ export class MultiSamplePass { ValueCell.update(compose.values.tColor, composeTarget.texture); compose.update(); - if (toDrawingBuffer) { - webgl.unbindFramebuffer(); - } else { - this.colorTarget.bind(); - } - + this.bindOutputTarget(toDrawingBuffer); gl.viewport(x, y, width, height); gl.scissor(x, y, width, height); @@ -245,12 +248,7 @@ export class MultiSamplePass { } } - if (toDrawingBuffer) { - webgl.unbindFramebuffer(); - } else { - this.colorTarget.bind(); - } - + this.bindOutputTarget(toDrawingBuffer); gl.viewport(x, y, width, height); gl.scissor(x, y, width, height); diff --git a/src/mol-canvas3d/passes/passes.ts b/src/mol-canvas3d/passes/passes.ts index 24660ab3f49d6f7af26f914a040ac30321b857dd..8e06be24334680d4d5f97a4d5115355e2e746d1b 100644 --- a/src/mol-canvas3d/passes/passes.ts +++ b/src/mol-canvas3d/passes/passes.ts @@ -16,9 +16,9 @@ export class Passes { readonly postprocessing: PostprocessingPass readonly multiSample: MultiSamplePass - constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number }> = {}) { + constructor(private webgl: WebGLContext, attribs: Partial<{ pickScale: number, enableWboit: boolean }> = {}) { const { gl } = webgl; - this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight); + this.draw = new DrawPass(webgl, gl.drawingBufferWidth, gl.drawingBufferHeight, attribs.enableWboit || false); this.pick = new PickPass(webgl, this.draw, attribs.pickScale || 0.25); this.postprocessing = new PostprocessingPass(webgl, this.draw); this.multiSample = new MultiSamplePass(webgl, this.draw, this.postprocessing); diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts index fef4677fd15a83be0b8d504545b18f3aa0fab2ec..841fc7a1819684c5e351c238b35ecbee57cafdea 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -63,26 +63,28 @@ export class PickPass { } } - private renderVariant(renderTarget: RenderTarget, renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) { - const pickScale = this.pickBaseScale / this.webgl.pixelRatio; + private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) { const depth = this.drawPass.depthTexturePrimitives; - renderer.render(renderTarget, scene.primitives, camera, variant, true, false, pickScale, null, false, null); - renderer.render(renderTarget, scene.volumes, camera, variant, false, false, pickScale, depth, false, null); - renderer.render(renderTarget, helper.handle.scene, camera, variant, false, false, pickScale, null, false, null); + renderer.clear(false); + renderer.renderPick(scene.primitives, camera, variant, null); + renderer.renderPick(scene.volumes, camera, variant, depth); + renderer.renderPick(helper.handle.scene, camera, variant, null); } render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper) { + renderer.update(camera); + this.objectPickTarget.bind(); - this.renderVariant(this.objectPickTarget, renderer, camera, scene, helper, 'pickObject'); + this.renderVariant(renderer, camera, scene, helper, 'pickObject'); this.instancePickTarget.bind(); - this.renderVariant(this.instancePickTarget, renderer, camera, scene, helper, 'pickInstance'); + this.renderVariant(renderer, camera, scene, helper, 'pickInstance'); this.groupPickTarget.bind(); - this.renderVariant(this.groupPickTarget, renderer, camera, scene, helper, 'pickGroup'); + this.renderVariant(renderer, camera, scene, helper, 'pickGroup'); this.depthPickTarget.bind(); - this.renderVariant(this.depthPickTarget, renderer, camera, scene, helper, 'depth'); + this.renderVariant(renderer, camera, scene, helper, 'depth'); } } @@ -165,17 +167,19 @@ export class PickHelper { private render(camera: Camera | StereoCamera) { const { pickX, pickY, pickWidth, pickHeight, halfPickWidth } = this; - const { renderer, scene, helper } = this; + renderer.setTransparentBackground(false); + renderer.setDrawingBufferScale(this.pickScale); + if (StereoCamera.is(camera)) { - this.renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight); + renderer.setViewport(pickX, pickY, halfPickWidth, pickHeight); this.pickPass.render(renderer, camera.left, scene, helper); - this.renderer.setViewport(pickX + halfPickWidth, pickY, pickWidth - halfPickWidth, pickHeight); + renderer.setViewport(pickX + halfPickWidth, pickY, pickWidth - halfPickWidth, pickHeight); this.pickPass.render(renderer, camera.right, scene, helper); } else { - this.renderer.setViewport(pickX, pickY, pickWidth, pickHeight); + renderer.setViewport(pickX, pickY, pickWidth, pickHeight); this.pickPass.render(renderer, camera, scene, helper); } diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index 49aa45fb12c9b9b84fff558a65aec9839d18b53c..7880a0cb5337a8b5e60ba12a04aaedc0d52c0254 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -110,6 +110,14 @@ export class PostprocessingPass { this.renderable = getPostprocessingRenderable(webgl, colorTarget.texture, depthTexture); } + private bindTarget(toDrawingBuffer: boolean) { + if (toDrawingBuffer) { + this.webgl.unbindFramebuffer(); + } else { + this.target.bind(); + } + } + syncSize() { const width = this.drawPass.colorTarget.getWidth(); const height = this.drawPass.colorTarget.getHeight(); @@ -159,11 +167,8 @@ export class PostprocessingPass { } const { gl, state } = this.webgl; - if (toDrawingBuffer) { - this.webgl.unbindFramebuffer(); - } else { - this.target.bind(); - } + this.bindTarget(toDrawingBuffer); + state.disable(gl.SCISSOR_TEST); state.disable(gl.BLEND); state.disable(gl.DEPTH_TEST); diff --git a/src/mol-canvas3d/passes/wboit.ts b/src/mol-canvas3d/passes/wboit.ts index 6ac32116524f5e2d7aa86a333c6a88fb9841b9fb..d9ea9a1b54c4b54d97889c744a48369e7bf9d7e3 100644 --- a/src/mol-canvas3d/passes/wboit.ts +++ b/src/mol-canvas3d/passes/wboit.ts @@ -7,7 +7,7 @@ import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; -import { TextureSpec, Values } from '../../mol-gl/renderable/schema'; +import { TextureSpec, UniformSpec, Values } from '../../mol-gl/renderable/schema'; import { ShaderCode } from '../../mol-gl/shader-code'; import { WebGLContext } from '../../mol-gl/webgl/context'; import { createComputeRenderItem } from '../../mol-gl/webgl/render-item'; @@ -16,11 +16,14 @@ import { ValueCell } from '../../mol-util'; import quad_vert from '../../mol-gl/shader/quad.vert'; import evaluate_wboit_frag from '../../mol-gl/shader/evaluate-wboit.frag'; import { Framebuffer } from '../../mol-gl/webgl/framebuffer'; +import { Vec2 } from '../../mol-math/linear-algebra'; +import { isDebugMode } from '../../mol-util/debug'; const EvaluateWboitSchema = { ...QuadSchema, tWboitA: TextureSpec('texture', 'rgba', 'float', 'nearest'), tWboitB: TextureSpec('texture', 'rgba', 'float', 'nearest'), + uTexSize: UniformSpec('v2'), }; const EvaluateWboitShaderCode = ShaderCode('evaluate-wboit', quad_vert, evaluate_wboit_frag); type EvaluateWboitRenderable = ComputeRenderable<Values<typeof EvaluateWboitSchema>> @@ -30,6 +33,7 @@ function getEvaluateWboitRenderable(ctx: WebGLContext, wboitATexture: Texture, w ...QuadValues, tWboitA: ValueCell.create(wboitATexture), tWboitB: ValueCell.create(wboitBTexture), + uTexSize: ValueCell.create(Vec2.create(wboitATexture.getWidth(), wboitATexture.getHeight())), }; const schema = { ...EvaluateWboitSchema }; @@ -77,14 +81,21 @@ export class WboitPass { } setSize(width: number, height: number) { - this.textureA.define(width, height); - this.textureB.define(width, height); + const [w, h] = this.renderable.values.uTexSize.ref.value; + if (width !== w || height !== h) { + this.textureA.define(width, height); + this.textureB.define(width, height); + ValueCell.update(this.renderable.values.uTexSize, Vec2.set(this.renderable.values.uTexSize.ref.value, width, height)); + } } constructor(private webgl: WebGLContext, width: number, height: number) { const { resources, extensions } = webgl; const { drawBuffers, textureFloat, colorBufferFloat, depthTexture } = extensions; - if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers) return; + if (!textureFloat || !colorBufferFloat || !depthTexture || !drawBuffers) { + if (isDebugMode) console.log('Missing extensions required for "wboit"'); + return; + } this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest'); this.textureA.define(width, height); diff --git a/src/mol-geo/geometry/direct-volume/direct-volume.ts b/src/mol-geo/geometry/direct-volume/direct-volume.ts index a16aad59ecde49253f4024f846c986b64d33b9fe..90f6bfb007738891ed4a1d4506d58e8313d91209 100644 --- a/src/mol-geo/geometry/direct-volume/direct-volume.ts +++ b/src/mol-geo/geometry/direct-volume/direct-volume.ts @@ -299,8 +299,6 @@ export namespace DirectVolume { dFlatShaded: ValueCell.create(props.flatShaded), dFlipSided: ValueCell.create(props.flipSided), dIgnoreLight: ValueCell.create(props.ignoreLight), - - uRenderWboit: ValueCell.create(1), }; } diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index d9ffb4561a4e70d62507f44c3a01e41ebcdb30d4..7e70cfec82a43fc3b2f0d07d2d5dc86d55bd8514 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -1,11 +1,11 @@ /** - * Copyright (c) 2018-2019 mol* contributors, licensed under MIT, See LICENSE file for more info. + * Copyright (c) 2018-2020 mol* contributors, licensed under MIT, See LICENSE file for more info. * * @author Alexander Rose <alexander.rose@weirdbyte.de> */ import { Program } from './webgl/program'; -import { RenderableValues, Values, RenderableSchema } from './renderable/schema'; +import { RenderableValues, Values, RenderableSchema, BaseValues } from './renderable/schema'; import { GraphicsRenderItem, ComputeRenderItem, GraphicsRenderVariant } from './webgl/render-item'; import { ValueCell } from '../mol-util'; import { idFactory } from '../mol-util/id-factory'; @@ -54,6 +54,8 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem: }; } +export type GraphicsRenderable = Renderable<RenderableValues & BaseValues> + // export interface ComputeRenderable<T extends RenderableValues> { diff --git a/src/mol-gl/renderable/direct-volume.ts b/src/mol-gl/renderable/direct-volume.ts index a0a5fde4266d73de6632e778ea78672d1e1669c9..3ab83368fb85a8c8065255371c7198bd6081b038 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -62,8 +62,6 @@ export const DirectVolumeSchema = { uAlpha: UniformSpec('f'), - uRenderWboit: UniformSpec('i'), - uIsoValue: UniformSpec('v2'), uBboxMin: UniformSpec('v3'), uBboxMax: UniformSpec('v3'), diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index ab74b8055e04099c4ce6865063612ac37146748b..c96b6cdb85a52f7cb5dc912c95e6a635013b927b 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -9,10 +9,10 @@ import { ICamera } from '../mol-canvas3d/camera'; import Scene from './scene'; import { WebGLContext } from './webgl/context'; import { Mat4, Vec3, Vec4, Vec2, Quat } from '../mol-math/linear-algebra'; -import { Renderable } from './renderable'; +import { GraphicsRenderable } from './renderable'; import { Color } from '../mol-util/color'; import { ValueCell, deepEqual } from '../mol-util'; -import { RenderableValues, GlobalUniformValues, BaseValues } from './renderable/schema'; +import { GlobalUniformValues } from './renderable/schema'; import { GraphicsRenderVariant } from './webgl/render-item'; import { ParamDefinition as PD } from '../mol-util/param-definition'; import { Clipping } from '../mol-theme/clipping'; @@ -20,9 +20,7 @@ import { stringToWords } from '../mol-util/string'; import { Transparency } from '../mol-theme/transparency'; import { degToRad } from '../mol-math/misc'; import { createNullTexture, Texture, Textures } from './webgl/texture'; -import { RenderTarget } from './webgl/render-target'; import { arrayMapUpsert } from '../mol-util/array'; -import { WboitPass } from '../mol-canvas3d/passes/wboit'; export interface RendererStats { programCount: number @@ -44,10 +42,21 @@ interface Renderer { readonly stats: RendererStats readonly props: Readonly<RendererProps> - clear: (transparentBackground: boolean) => void - render: (renderTarget: RenderTarget | null, group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null, renderTransparent: boolean, wboit: WboitPass | null) => void + clear: (toBackgroundColor: boolean) => void + clearDepth: () => void + update: (camera: ICamera) => void + + renderPick: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => void + renderDepth: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderBlended: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderWboitOpaque: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + renderWboitTransparent: (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => void + setProps: (props: Partial<RendererProps>) => void setViewport: (x: number, y: number, width: number, height: number) => void + setTransparentBackground: (value: boolean) => void + setDrawingBufferScale: (value: number) => void + dispose: () => void } @@ -170,6 +179,9 @@ namespace Renderer { const drawingBufferSize = Vec2.create(gl.drawingBufferWidth, gl.drawingBufferHeight); const bgColor = Color.toVec3Normalized(Vec3(), p.backgroundColor); + let transparentBackground = false; + let drawingBufferScale = 1; + const nullDepthTexture = createNullTexture(gl, 'image-depth'); const sharedTexturesList: Textures = [ ['tDepth', nullDepthTexture] @@ -243,7 +255,7 @@ namespace Renderer { let globalUniformsNeedUpdate = true; - const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant, wboit: WboitPass | null) => { + const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant) => { if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) { return; } @@ -281,7 +293,7 @@ namespace Renderer { state.disable(gl.CULL_FACE); state.frontFace(gl.CCW); - if (!wboit?.enabled) { + if (variant === 'colorBlended') { // depth test done manually in shader against `depthTexture` // still need to enable when fragDepth can be used to write depth if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) { @@ -318,24 +330,17 @@ namespace Renderer { state.cullFace(gl.BACK); } - if (!wboit?.enabled) state.depthMask(r.state.writeDepth); + if (variant === 'colorBlended') state.depthMask(r.state.writeDepth); } r.render(variant, sharedTexturesList); }; - const render = (renderTarget: RenderTarget | null, group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null, renderTransparent: boolean, wboit: WboitPass | null) => { - arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture); - - ValueCell.update(globalUniforms.uModel, group.view); + const update = (camera: ICamera) => { ValueCell.update(globalUniforms.uView, camera.view); ValueCell.update(globalUniforms.uInvView, Mat4.invert(invView, camera.view)); - ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view)); - ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView)); ValueCell.update(globalUniforms.uProjection, camera.projection); ValueCell.update(globalUniforms.uInvProjection, Mat4.invert(invProjection, camera.projection)); - ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection)); - ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection)); ValueCell.updateIfChanged(globalUniforms.uIsOrtho, camera.state.mode === 'orthographic' ? 1 : 0); ValueCell.update(globalUniforms.uViewOffset, camera.viewOffset.enabled ? Vec2.set(viewOffset, camera.viewOffset.offsetX * 16, camera.viewOffset.offsetY * 16) : Vec2.set(viewOffset, 0, 0)); @@ -349,8 +354,6 @@ namespace Renderer { ValueCell.updateIfChanged(globalUniforms.uFogNear, camera.fogNear); ValueCell.updateIfChanged(globalUniforms.uTransparentBackground, transparentBackground); - ValueCell.updateIfChanged(globalUniforms.uRenderWboit, 0); - if (gl.drawingBufferWidth * drawingBufferScale !== drawingBufferSize[0] || gl.drawingBufferHeight * drawingBufferScale !== drawingBufferSize[1] ) { @@ -359,116 +362,153 @@ namespace Renderer { gl.drawingBufferHeight * drawingBufferScale )); } + }; + + const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean) => { + arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture); + + ValueCell.update(globalUniforms.uModel, group.view); + ValueCell.update(globalUniforms.uModelView, Mat4.mul(modelView, group.view, camera.view)); + ValueCell.update(globalUniforms.uInvModelView, Mat4.invert(invModelView, modelView)); + ValueCell.update(globalUniforms.uModelViewProjection, Mat4.mul(modelViewProjection, modelView, camera.projection)); + ValueCell.update(globalUniforms.uInvModelViewProjection, Mat4.invert(invModelViewProjection, modelViewProjection)); + + ValueCell.updateIfChanged(globalUniforms.uRenderWboit, renderWboit ? 1 : 0); + + state.enable(gl.SCISSOR_TEST); + state.colorMask(true, true, true, true); + + const { x, y, width, height } = viewport; + gl.viewport(x, y, width, height); + gl.scissor(x, y, width, height); globalUniformsNeedUpdate = true; state.currentRenderItemId = -1; + }; + + const renderPick = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, false); const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + if (!renderables[i].state.colorOnly) { + renderObject(renderables[i], variant); + } + } + }; - if (renderTarget) { - renderTarget.bind(); - } else { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); + const renderDepth = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + + updateInternal(group, camera, depthTexture, false); + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + renderObject(renderables[i], 'depth'); } + }; - state.enable(gl.SCISSOR_TEST); + const renderBlended = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { state.disable(gl.BLEND); - state.colorMask(true, true, true, true); state.enable(gl.DEPTH_TEST); state.depthMask(true); - const { x, y, width, height } = viewport; - gl.viewport(x, y, width, height); - gl.scissor(x, y, width, height); + updateInternal(group, camera, depthTexture, false); - if (clear) { - if (variant === 'color') { - state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1); - } else { - state.clearColor(1, 1, 1, 1); + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + if (r.state.opaque) { + renderObject(r, 'colorBlended'); } - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } - if (variant === 'color') { - if (wboit?.enabled) { - if (!renderTransparent) { - state.enable(gl.DEPTH_TEST); - state.depthMask(true); - - for (let i = 0, il = renderables.length; i < il; ++i) { - const r = renderables[i]; - if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values?.dRenderMode?.ref.value !== 'volume') { - renderObject(r, variant, wboit); - } - } - } else { - wboit.bind(); + state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); + state.enable(gl.BLEND); - ValueCell.updateIfChanged(globalUniforms.uRenderWboit, 1); - globalUniformsNeedUpdate = true; + state.depthMask(true); + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + if (!r.state.opaque && r.state.writeDepth) { + renderObject(r, 'colorBlended'); + } + } - for (let i = 0, il = renderables.length; i < il; ++i) { - const r = renderables[i]; - if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value !== 0) { - renderObject(r, variant, wboit); - } - } + state.depthMask(false); + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + if (!r.state.opaque && !r.state.writeDepth) { + renderObject(r, 'colorBlended'); + } + } + }; - if (renderTarget) { - renderTarget.bind(); - } else { - gl.bindFramebuffer(gl.FRAMEBUFFER, null); - } + const renderWboitOpaque = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + state.disable(gl.BLEND); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); - wboit.render(); - } - } else { - for (let i = 0, il = renderables.length; i < il; ++i) { - const r = renderables[i]; - if (r.state.opaque) { - renderObject(r, variant, wboit); - } - } + updateInternal(group, camera, depthTexture, false); - state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); - state.enable(gl.BLEND); - for (let i = 0, il = renderables.length; i < il; ++i) { - const r = renderables[i]; - if (!r.state.opaque && r.state.writeDepth) { - renderObject(r, variant, wboit); - } - } - for (let i = 0, il = renderables.length; i < il; ++i) { - const r = renderables[i]; - if (!r.state.opaque && !r.state.writeDepth) { - renderObject(r, variant, wboit); - } - } - } - } else { // picking & depth - if (!renderTransparent) { - for (let i = 0, il = renderables.length; i < il; ++i) { - if (!renderables[i].state.colorOnly) { - renderObject(renderables[i], variant, wboit); - } - } + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + if (r.values.uAlpha.ref.value === 1 && r.values.transparencyAverage.ref.value !== 1 && r.values?.dRenderMode?.ref.value !== 'volume') { + renderObject(r, 'colorWboit'); } } + }; + + const renderWboitTransparent = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null) => { + updateInternal(group, camera, depthTexture, true); - gl.flush(); + // arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture); + // wboit.bind(); + // ValueCell.updateIfChanged(globalUniforms.uRenderWboit, 1); + // globalUniformsNeedUpdate = true; + + const { renderables } = group; + for (let i = 0, il = renderables.length; i < il; ++i) { + const r = renderables[i]; + if (r.values.uAlpha.ref.value < 1 || r.values.transparencyAverage.ref.value > 0 || r.values?.dRenderMode?.ref.value === 'volume') { + renderObject(r, 'colorWboit'); + } + } }; return { - clear: (transparentBackground: boolean) => { - ctx.unbindFramebuffer(); + clear: (toBackgroundColor: boolean) => { state.enable(gl.SCISSOR_TEST); - state.depthMask(true); + state.enable(gl.DEPTH_TEST); state.colorMask(true, true, true, true); - state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1); + state.depthMask(true); + + if (toBackgroundColor) { + state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1); + } else { + state.clearColor(1, 1, 1, 1); + } gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); }, - render, + clearDepth: () => { + state.enable(gl.SCISSOR_TEST); + state.enable(gl.DEPTH_TEST); + state.depthMask(true); + gl.clear(gl.DEPTH_BUFFER_BIT); + }, + update, + + renderPick, + renderDepth, + renderBlended, + renderWboitOpaque, + renderWboitTransparent, setProps: (props: Partial<RendererProps>) => { if (props.backgroundColor !== undefined && props.backgroundColor !== p.backgroundColor) { @@ -535,6 +575,12 @@ namespace Renderer { ValueCell.update(globalUniforms.uViewport, Vec4.set(globalUniforms.uViewport.ref.value, x, y, width, height)); } }, + setTransparentBackground: (value: boolean) => { + transparentBackground = value; + }, + setDrawingBufferScale: (value: number) => { + drawingBufferScale = value; + }, get props() { return p; diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index 3834adc1e73773006579c1cdbce40d8afe8f33b0..9974f3b80bcc4d0f837020c2c277f59787e17bd7 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -5,9 +5,7 @@ * @author David Sehnal <david.sehnal@gmail.com> */ -import { Renderable } from './renderable'; import { WebGLContext } from './webgl/context'; -import { RenderableValues, BaseValues } from './renderable/schema'; import { GraphicsRenderObject, createRenderable } from './render-object'; import { Object3D } from './object3d'; import { Sphere3D } from '../mol-math/geometry'; @@ -16,10 +14,11 @@ import { now } from '../mol-util/now'; import { arraySetRemove } from '../mol-util/array'; import { BoundaryHelper } from '../mol-math/geometry/boundary-helper'; import { hash1 } from '../mol-data/util'; +import { GraphicsRenderable } from './renderable'; const boundaryHelper = new BoundaryHelper('98'); -function calculateBoundingSphere(renderables: Renderable<RenderableValues & BaseValues>[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D { +function calculateBoundingSphere(renderables: GraphicsRenderable[], boundingSphere: Sphere3D, onlyVisible: boolean): Sphere3D { boundaryHelper.reset(); for (let i = 0, il = renderables.length; i < il; ++i) { @@ -43,9 +42,9 @@ function calculateBoundingSphere(renderables: Renderable<RenderableValues & Base return boundaryHelper.getSphere(boundingSphere); } -function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Renderable<RenderableValues & BaseValues>) { - const drawProgramIdA = a.getProgram('color').id; - const drawProgramIdB = b.getProgram('color').id; +function renderableSort(a: GraphicsRenderable, b: GraphicsRenderable) { + const drawProgramIdA = a.getProgram('colorBlended').id; + const drawProgramIdB = b.getProgram('colorBlended').id; const materialIdA = a.materialId; const materialIdB = b.materialId; @@ -62,7 +61,7 @@ function renderableSort(a: Renderable<RenderableValues & BaseValues>, b: Rendera interface Scene extends Object3D { readonly count: number - readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>> + readonly renderables: ReadonlyArray<GraphicsRenderable> readonly boundingSphere: Sphere3D readonly boundingSphereVisible: Sphere3D @@ -72,28 +71,28 @@ interface Scene extends Object3D { /** Returns `true` if some visibility has changed, `false` otherwise. */ syncVisibility: () => boolean update: (objects: ArrayLike<GraphicsRenderObject> | undefined, keepBoundingSphere: boolean) => void - add: (o: GraphicsRenderObject) => void // Renderable<any> + add: (o: GraphicsRenderObject) => void // GraphicsRenderable remove: (o: GraphicsRenderObject) => void commit: (maxTimeMs?: number) => boolean readonly needsCommit: boolean has: (o: GraphicsRenderObject) => boolean clear: () => void - forEach: (callbackFn: (value: Renderable<RenderableValues & BaseValues>, key: GraphicsRenderObject) => void) => void + forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void } namespace Scene { export interface Group extends Object3D { - readonly renderables: ReadonlyArray<Renderable<RenderableValues & BaseValues>> + readonly renderables: ReadonlyArray<GraphicsRenderable> } export function create(ctx: WebGLContext): Scene { - const renderableMap = new Map<GraphicsRenderObject, Renderable<RenderableValues & BaseValues>>(); - const renderables: Renderable<RenderableValues & BaseValues>[] = []; + const renderableMap = new Map<GraphicsRenderObject, GraphicsRenderable>(); + const renderables: GraphicsRenderable[] = []; const boundingSphere = Sphere3D(); const boundingSphereVisible = Sphere3D(); - const primitives: Renderable<RenderableValues & BaseValues>[] = []; - const volumes: Renderable<RenderableValues & BaseValues>[] = []; + const primitives: GraphicsRenderable[] = []; + const volumes: GraphicsRenderable[] = []; let boundingSphereDirty = true; let boundingSphereVisibleDirty = true; @@ -223,7 +222,7 @@ namespace Scene { boundingSphereDirty = true; boundingSphereVisibleDirty = true; }, - forEach: (callbackFn: (value: Renderable<any>, key: GraphicsRenderObject) => void) => { + forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => { renderableMap.forEach(callbackFn); }, get count() { diff --git a/src/mol-gl/shader-code.ts b/src/mol-gl/shader-code.ts index c43abeac4fb6cf09cab6b35d4e18299ab8faddb9..d37bcc0d3a02638676b136da75f273e304136050 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -190,6 +190,7 @@ function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: Sha if (extensions.drawBuffers) { prefix.push('#extension GL_EXT_draw_buffers : require'); prefix.push('#define requiredDrawBuffers'); + prefix.push('#define gl_FragColor gl_FragData[0]'); } else if (shaderExtensions.drawBuffers === 'required') { throw new Error(`required 'GL_EXT_draw_buffers' extension not available`); } @@ -202,6 +203,9 @@ function getGlsl100FragPrefix(extensions: WebGLExtensions, shaderExtensions: Sha throw new Error(`required 'GL_EXT_shader_texture_lod' extension not available`); } } + if (extensions.depthTexture) { + prefix.push('#define depthTextureSupport'); + } return prefix.join('\n') + '\n'; } @@ -212,6 +216,8 @@ const glsl300VertPrefix = `#version 300 es `; const glsl300FragPrefixCommon = ` +layout(location = 0) out highp vec4 out_FragData0; + #define varying in #define texture2D texture #define texture2DLodEXT textureLod @@ -219,7 +225,7 @@ const glsl300FragPrefixCommon = ` #define gl_FragColor out_FragData0 #define gl_FragDepthEXT gl_FragDepth -#define requiredDrawBuffers +#define depthTextureSupport `; function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExtensions, shaderExtensions: ShaderExtensions) { @@ -230,12 +236,16 @@ function getGlsl300FragPrefix(gl: WebGL2RenderingContext, extensions: WebGLExten if (shaderExtensions.fragDepth) { prefix.push('#define enabledFragDepth'); } - if (extensions.drawBuffers) { + if (shaderExtensions.drawBuffers) { + prefix.push('#define requiredDrawBuffers'); const maxDrawBuffers = gl.getParameter(gl.MAX_DRAW_BUFFERS) as number; - for (let i = 0, il = maxDrawBuffers; i < il; ++i) { + for (let i = 1, il = maxDrawBuffers; i < il; ++i) { prefix.push(`layout(location = ${i}) out highp vec4 out_FragData${i};`); } } + if (shaderExtensions.shaderTextureLod) { + prefix.push('#define enabledShaderTextureLod'); + } prefix.push(glsl300FragPrefixCommon); return prefix.join('\n') + '\n'; } diff --git a/src/mol-gl/shader/chunks/common.glsl.ts b/src/mol-gl/shader/chunks/common.glsl.ts index 486632ba92d1ae5d708fc11acb78e05ad571e79f..710e8779fb0938206849f7be11191c8e067b31e6 100644 --- a/src/mol-gl/shader/chunks/common.glsl.ts +++ b/src/mol-gl/shader/chunks/common.glsl.ts @@ -1,6 +1,10 @@ export default ` // TODO find a better place for these convenience defines +#if defined(dRenderVariant_colorBlended) || defined(dRenderVariant_colorWboit) + #define dRenderVariant_color +#endif + #if defined(dRenderVariant_pickObject) || defined(dRenderVariant_pickInstance) || defined(dRenderVariant_pickGroup) #define dRenderVariant_pick #endif diff --git a/src/mol-gl/shader/chunks/wboit-params.glsl.ts b/src/mol-gl/shader/chunks/wboit-params.glsl.ts index d4cf78a0a82a70e928353b5baf24a7604ae2cedd..3655cf1d3528d6d1d770cb0e50360d19599160be 100644 --- a/src/mol-gl/shader/chunks/wboit-params.glsl.ts +++ b/src/mol-gl/shader/chunks/wboit-params.glsl.ts @@ -1,20 +1,18 @@ export default ` -#if defined(dRenderVariant_color) +#if defined(dRenderVariant_colorWboit) #if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface) uniform sampler2D tDepth; uniform vec2 uDrawingBufferSize; float getDepth(const in vec2 coords) { - #ifdef dPackedDepth - return unpackRGBAToDepth(texture2D(tDepth, coords)); - #else - return texture2D(tDepth, coords).r; - #endif + // always packed due to merged depth from primitives and volumes + return unpackRGBAToDepth(texture2D(tDepth, coords)); } #endif - uniform int uRenderWboit; #endif +uniform int uRenderWboit; + float calcDepth(const in vec3 pos) { vec2 clipZW = pos.z * uProjection[2].zw + uProjection[3].zw; return 0.5 + 0.5 * clipZW.x / clipZW.y; diff --git a/src/mol-gl/shader/chunks/wboit-write.glsl.ts b/src/mol-gl/shader/chunks/wboit-write.glsl.ts index af2b744c47fbdb8e8736087f75174dd6f508d351..234a3ef9064de194ecf3f2d254d07bd2f4c053c6 100644 --- a/src/mol-gl/shader/chunks/wboit-write.glsl.ts +++ b/src/mol-gl/shader/chunks/wboit-write.glsl.ts @@ -1,16 +1,18 @@ export default ` -if (uRenderWboit == 0) { - if (gl_FragColor.a != 1.0) { - discard; +#if defined(dRenderVariant_colorWboit) + if (uRenderWboit == 0) { + if (gl_FragColor.a != 1.0) { + discard; + } + } else if (uRenderWboit == 1) { + if (gl_FragColor.a != 1.0 && absFragDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) { + float alpha = gl_FragColor.a; + float wboitWeight = alpha * clamp(pow(1.0 - absFragDepth, 2.0), 0.01, 1.0); + gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha); + gl_FragData[1] = vec4(alpha * wboitWeight); + } else { + discard; + } } -} else if (uRenderWboit == 1) { - if (gl_FragColor.a != 1.0 && absFragDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) { - float alpha = gl_FragColor.a; - float wboitWeight = alpha * clamp(pow(1.0 - absFragDepth, 2.0), 0.01, 1.0); - gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha); - out_FragData1 = vec4(alpha * wboitWeight); - } else { - discard; - } -} +#endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/direct-volume.frag.ts b/src/mol-gl/shader/direct-volume.frag.ts index a8ce217c383e872d72e0539935919aeb01ed027b..b087d5457e4217a2053a9d6f66894c2a84cb3891 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -128,10 +128,15 @@ vec4 transferFunction(float value) { } float getDepth(const in vec2 coords) { - #ifdef dPackedDepth - return unpackRGBAToDepth(texture2D(tDepth, coords)); + #ifdef depthTextureSupport + if (uRenderWboit == 0) { + // in case of opaque volumes (and depth texture support) + return texture2D(tDepth, coords).r; + } else { + return unpackRGBAToDepth(texture2D(tDepth, coords)); + } #else - return texture2D(tDepth, coords).r; + return unpackRGBAToDepth(texture2D(tDepth, coords)); #endif } diff --git a/src/mol-gl/shader/evaluate-wboit.frag.ts b/src/mol-gl/shader/evaluate-wboit.frag.ts index 3f0ad43f0697c5f30834fadc5460b7e5d037fd09..d797cf7aa81276f14345ef1022337dd733c4a1d0 100644 --- a/src/mol-gl/shader/evaluate-wboit.frag.ts +++ b/src/mol-gl/shader/evaluate-wboit.frag.ts @@ -3,14 +3,15 @@ precision highp float; uniform sampler2D tWboitA; uniform sampler2D tWboitB; +uniform vec2 uTexSize; void main() { - ivec2 coords = ivec2(gl_FragCoord.xy); - - vec4 accum = texelFetch(tWboitA, coords, 0); + vec2 coords = gl_FragCoord.xy / uTexSize; + + vec4 accum = texture2D(tWboitA, coords); float r = 1.0 - accum.a; - accum.a = texelFetch(tWboitB, coords, 0).r; + accum.a = texture2D(tWboitB, coords).r; gl_FragColor = vec4(accum.rgb / clamp(accum.a, 0.0001, 50000.0), r); } `; \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index 091a96ec76f60d79dffd6834dfd64e2bfb8f1205..8823b75f35a3171fa9620eedf80cdac9e20503ee 100644 --- a/src/mol-gl/webgl/context.ts +++ b/src/mol-gl/webgl/context.ts @@ -18,15 +18,17 @@ import { now } from '../../mol-util/now'; import { Texture, TextureFilter } from './texture'; import { ComputeRenderable } from '../renderable'; -export function getGLContext(canvas: HTMLCanvasElement, contextAttributes?: WebGLContextAttributes): GLRenderingContext | null { - function getContext(contextId: 'webgl' | 'experimental-webgl' | 'webgl2') { +export function getGLContext(canvas: HTMLCanvasElement, attribs?: WebGLContextAttributes): GLRenderingContext | null { + function get(id: 'webgl' | 'experimental-webgl' | 'webgl2') { try { - return canvas.getContext(contextId, contextAttributes) as GLRenderingContext | null; + return canvas.getContext(id, attribs) as GLRenderingContext | null; } catch (e) { return null; } } - return getContext('webgl2') || getContext('webgl') || getContext('experimental-webgl'); + const gl = get('webgl2') || get('webgl') || get('experimental-webgl'); + if (isDebugMode) console.log(`isWebgl2: ${isWebGL2(gl)}`); + return gl; } export function getErrorDescription(gl: GLRenderingContext, error: number) { diff --git a/src/mol-gl/webgl/extensions.ts b/src/mol-gl/webgl/extensions.ts index df056ddb8c64d5fc52a97b765c0d07e7f8cc3c19..712dddf18f56e222eb4e8c94724c8973fc2ea222 100644 --- a/src/mol-gl/webgl/extensions.ts +++ b/src/mol-gl/webgl/extensions.ts @@ -42,7 +42,6 @@ export function createExtensions(gl: GLRenderingContext): WebGLExtensions { } const textureFloat = getTextureFloat(gl); if (isDebugMode && textureFloat === null) { - // TODO make sure non-support is handled downstream console.log('Could not find support for "texture_float"'); } const textureFloatLinear = getTextureFloatLinear(gl); diff --git a/src/mol-gl/webgl/framebuffer.ts b/src/mol-gl/webgl/framebuffer.ts index 3f982cfa37d9917af86de6fe61da26c060a7398b..6122f79dec6d4566394ada40a32da15737898640 100644 --- a/src/mol-gl/webgl/framebuffer.ts +++ b/src/mol-gl/webgl/framebuffer.ts @@ -50,15 +50,15 @@ function getFramebuffer(gl: GLRenderingContext) { return framebuffer; } -export function createFramebuffer (gl: GLRenderingContext): Framebuffer { +export function createFramebuffer(gl: GLRenderingContext): Framebuffer { let _framebuffer = getFramebuffer(gl); let destroyed = false; return { id: getNextFramebufferId(), - bind: () => gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer), + bind: () => gl.bindFramebuffer(gl.FRAMEBUFFER, _framebuffer), reset: () => { _framebuffer = getFramebuffer(gl); }, @@ -68,4 +68,16 @@ export function createFramebuffer (gl: GLRenderingContext): Framebuffer { destroyed = true; } }; +} + +// + +export function createNullFramebuffer(): Framebuffer { + return { + id: getNextFramebufferId(), + + bind: () => {}, + reset: () => {}, + destroy: () => {} + }; } \ No newline at end of file diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 421178aa238791f2cd0be573fd6c73e1b69c605b..02eb1c15e21387bfb592b8281416b6431374906a 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -47,7 +47,7 @@ export interface RenderItem<T extends string> { // -const GraphicsRenderVariant = { 'color': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '' }; +const GraphicsRenderVariant = { 'colorBlended': '', 'colorWboit': '', 'pickObject': '', 'pickInstance': '', 'pickGroup': '', 'depth': '' }; export type GraphicsRenderVariant = keyof typeof GraphicsRenderVariant const GraphicsRenderVariants = Object.keys(GraphicsRenderVariant) as GraphicsRenderVariant[]; diff --git a/src/mol-gl/webgl/render-target.ts b/src/mol-gl/webgl/render-target.ts index 232c0d9d5388da135c61b063ad3bc0c1b396e40c..a2c9e751240726132b13d74f09ae2a978ef85756 100644 --- a/src/mol-gl/webgl/render-target.ts +++ b/src/mol-gl/webgl/render-target.ts @@ -5,8 +5,8 @@ */ import { idFactory } from '../../mol-util/id-factory'; -import { Texture, TextureFilter } from './texture'; -import { Framebuffer } from './framebuffer'; +import { createNullTexture, Texture, TextureFilter } from './texture'; +import { createNullFramebuffer, Framebuffer } from './framebuffer'; import { WebGLResources } from './resources'; import { GLRenderingContext } from './compat'; @@ -78,3 +78,22 @@ export function createRenderTarget(gl: GLRenderingContext, resources: WebGLResou } }; } + +// + +export function createNullRenderTarget(gl: GLRenderingContext): RenderTarget { + return { + id: getNextRenderTargetId(), + texture: createNullTexture(gl, 'image-uint8'), + framebuffer: createNullFramebuffer(), + + getWidth: () => 0, + getHeight: () => 0, + bind: () => { + gl.bindFramebuffer(gl.FRAMEBUFFER, null); + }, + setSize: () => {}, + reset: () => {}, + 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 181fb35a92dc64dd2a5f74992af1bb04c25c1d76..7e1fe21903acabf281d7db848a160f3738e20917 100644 --- a/src/mol-math/geometry/gaussian-density/gpu.ts +++ b/src/mol-math/geometry/gaussian-density/gpu.ts @@ -119,7 +119,7 @@ type GaussianDensityTextureData = { function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData { // console.log('2d'); - const { gl, resources, state, extensions } = webgl; + const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl; const { smoothness, resolution } = props; const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props); @@ -143,7 +143,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat framebuffer.bind(); setRenderingDefaults(webgl); - if (!texture) texture = extensions.colorBufferFloat + if (!texture) texture = colorBufferFloat && textureFloat ? resources.texture('image-float32', 'rgba', 'float', 'linear') : resources.texture('image-uint8', 'rgba', 'ubyte', 'linear'); texture.define(texDimX, texDimY); @@ -191,7 +191,7 @@ function calcGaussianDensityTexture2d(webgl: WebGLContext, position: PositionDat function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionData, box: Box3D, radius: (index: number) => number, props: GaussianDensityGPUProps, texture?: Texture): GaussianDensityTextureData { // console.log('3d'); - const { gl, resources, state, extensions } = webgl; + const { gl, resources, state, extensions: { colorBufferFloat, textureFloat } } = webgl; const { smoothness, resolution } = props; const { drawCount, positions, radii, groups, scale, expandedBox, dim, maxRadius } = prepareGaussianDensityData(position, box, radius, props); @@ -214,7 +214,7 @@ function calcGaussianDensityTexture3d(webgl: WebGLContext, position: PositionDat setRenderingDefaults(webgl); gl.viewport(0, 0, dx, dy); - if (!texture) texture = extensions.colorBufferFloat + if (!texture) texture = colorBufferFloat && textureFloat ? resources.texture('volume-float32', 'rgba', 'float', 'linear') : resources.texture('volume-uint8', 'rgba', 'ubyte', 'linear'); texture.define(dx, dy, dz); diff --git a/src/mol-plugin/config.ts b/src/mol-plugin/config.ts index b70604b4c8eb8220543461524ab3b6312befe048..914bea78158d6c9a21ec23e2504a577ab511f26d 100644 --- a/src/mol-plugin/config.ts +++ b/src/mol-plugin/config.ts @@ -24,7 +24,8 @@ export const PluginConfig = { General: { IsBusyTimeoutMs: item('plugin-config.is-busy-timeout', 750), DisableAntialiasing: item('plugin-config.disable-antialiasing', false), - PixelScale: item('plugin-config.pixel-scale', 1) + PixelScale: item('plugin-config.pixel-scale', 1), + EnableWboit: item('plugin-config.enable-wboit', false) }, State: { DefaultServer: item('plugin-state.server', 'https://webchem.ncbr.muni.cz/molstar-state'), diff --git a/src/mol-plugin/context.ts b/src/mol-plugin/context.ts index 13c17aa4ad906ba368aefc24a9938f1184150013..e534bc50a8f043b612d027e6291c186ebf446833 100644 --- a/src/mol-plugin/context.ts +++ b/src/mol-plugin/context.ts @@ -190,7 +190,8 @@ export class PluginContext { const antialias = !(this.config.get(PluginConfig.General.DisableAntialiasing) ?? false); const pixelScale = this.config.get(PluginConfig.General.PixelScale) || 1; - (this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas, {}, { antialias, pixelScale }); + const enableWboit = this.config.get(PluginConfig.General.EnableWboit) || false; + (this.canvas3d as Canvas3D) = Canvas3D.fromCanvas(canvas, {}, { antialias, pixelScale, enableWboit }); this.canvas3dInit.next(true); let props = this.spec.components?.viewport?.canvas3d; diff --git a/src/mol-plugin/util/viewport-screenshot.ts b/src/mol-plugin/util/viewport-screenshot.ts index c18f813e116c5ed40068d9ec0418856dcd0e46fe..cf3186f1094ec8760a93dd4bc90aaa038e870ca9 100644 --- a/src/mol-plugin/util/viewport-screenshot.ts +++ b/src/mol-plugin/util/viewport-screenshot.ts @@ -107,12 +107,13 @@ class ViewportScreenshotHelper extends PluginComponent { private createPass(mutlisample: boolean) { const c = this.plugin.canvas3d!; + const { colorBufferFloat, textureFloat } = c.webgl.extensions; return this.plugin.canvas3d!.getImagePass({ transparentBackground: this.values.transparent, cameraHelper: { axes: this.values.axes }, multiSample: { mode: mutlisample ? 'on' : 'off', - sampleLevel: c.webgl.extensions.colorBufferFloat ? 4 : 2 + sampleLevel: colorBufferFloat && textureFloat ? 4 : 2 }, postprocessing: c.props.postprocessing });