Browse Source

Merge pull request #2 from arose/wboit

wip, wboit integration
AronKovacs 4 years ago
parent
commit
80fbc474f6

+ 2 - 0
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',

+ 2 - 0
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],

+ 3 - 3
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() {

+ 90 - 120
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,161 +117,132 @@ export class DrawPass {
 
             ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, width, height));
 
-            if (this.wboit.enabled) {
+            if (this.wboit?.enabled) {
                 this.wboit.setSize(width, height);
             }
         }
     }
 
-    _render(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, toDrawingBuffer: boolean, transparentBackground: boolean) {
-        if (this.wboit.enabled) {
-            this._renderWboit(renderer, camera, scene, helper, transparentBackground);
-        } else {
-            this._renderStandard(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground);
-        }
+    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();
     }
 
-    _renderStandard(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 _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);
+
+        // render opaque volumes
+        this.depthTextureVolumes.attachFramebuffer(renderTarget.framebuffer, 'depth');
+        renderTarget.bind();
+        renderer.clearDepth();
+        renderer.renderWboitOpaque(scene.volumes, camera, this.depthTexturePrimitives);
+
+        // 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);
 
-        let renderTarget;
+        // 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);
-        }
-
-        // render color
-        renderer.render(renderTarget, scene.primitives, camera, 'color', true, transparentBackground, 1, null, false, this.wboit);
-
-        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);
-        }
-
-        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);
+            this.depthTargetPrimitives.bind();
+            renderer.clear(false);
+            renderer.renderDepth(scene.primitives, camera, null);
+            this.colorTarget.bind();
         }
 
         // 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);
+                this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
+                renderer.clearDepth();
             }
-            renderer.render(renderTarget, scene.volumes, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit);
+            renderer.renderBlended(scene.volumes, camera, this.depthTexturePrimitives);
 
             // 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);
+                this.depthTargetVolumes.bind();
+                renderer.clear(false);
+                renderer.renderDepth(scene.volumes, camera, this.depthTexturePrimitives);
+                this.colorTarget.bind();
             }
         }
 
         // 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._depthMerge();
+            this.colorTarget.bind();
         }
     }
 
-    _renderWboit(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, transparentBackground: boolean) {
+    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);
 
-        let renderTarget = this.colorTarget;
-        this.depthTexturePrimitives.attachFramebuffer(renderTarget.framebuffer, 'depth');
-
-        // render opaque color
-        renderer.render(renderTarget, scene.primitives, camera, 'color', true, transparentBackground, 1, null, false, this.wboit);
-
-        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);
+        if (this.wboitEnabled) {
+            this._renderWboit(renderer, camera, scene, toDrawingBuffer);
+        } else {
+            this._renderBlended(renderer, camera, scene, toDrawingBuffer);
         }
 
-        // render transparent color
-        this.depthTexturePrimitivesTransparent.attachFramebuffer(renderTarget.framebuffer, 'depth');
-        renderer.render(renderTarget, scene.primitives, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives, true, this.wboit);
-
         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
-        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);
-
-        // merge depths from primitive and volume rendering
-        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) {

+ 2 - 2
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);

+ 12 - 14
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);
 

+ 2 - 2
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);

+ 17 - 13
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);
         }
 

+ 10 - 5
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);

+ 15 - 4
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);

+ 0 - 2
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),
         };
     }
 

+ 4 - 2
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> {

+ 0 - 2
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'),

+ 144 - 98
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;

+ 14 - 15
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() {

+ 13 - 3
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';
 }

+ 4 - 0
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

+ 5 - 7
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;

+ 15 - 13
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
 `;

+ 8 - 3
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
 }
 

+ 5 - 4
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);
 }
 `;

+ 6 - 4
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) {

+ 0 - 1
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);

+ 14 - 2
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: () => {}
+    };
 }

+ 1 - 1
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[];
 

+ 21 - 2
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: () => {}
+    };
+}

+ 4 - 4
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);

+ 2 - 1
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'),

+ 2 - 1
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;
 

+ 2 - 1
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
         });