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 412b2b8aac9c2064c6d643306193ad17508b0d4b..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 = !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 bd04e66ce9835c6450734f62e360415d5fbb4e8b..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'; @@ -22,6 +23,7 @@ import { Helper } from '../helper/helper'; import quad_vert from '../../mol-gl/shader/quad.vert'; import depthMerge_frag from '../../mol-gl/shader/depth-merge.frag'; import { StereoCamera } from '../camera/stereo'; +import { WboitPass } from './wboit'; const DepthMergeSchema = { ...QuadSchema, @@ -49,6 +51,8 @@ function getDepthMergeRenderable(ctx: WebGLContext, depthTexturePrimitives: Text } export class DrawPass { + private readonly drawTarget: RenderTarget + readonly colorTarget: RenderTarget readonly depthTexture: Texture readonly depthTexturePrimitives: Texture @@ -60,9 +64,17 @@ export class DrawPass { private depthTextureVolumes: Texture private depthMerge: DepthMergeRenderable - constructor(private webgl: WebGLContext, width: number, height: number) { + private wboit: WboitPass | undefined + + get wboitEnabled() { + return !!this.wboit?.enabled; + } + + 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; @@ -79,6 +91,8 @@ export class DrawPass { this.depthTextureVolumes.define(width, height); } this.depthMerge = getDepthMergeRenderable(webgl, this.depthTexturePrimitives, this.depthTextureVolumes, this.packedDepth); + + this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; } setSize(width: number, height: number) { @@ -102,13 +116,60 @@ export class DrawPass { } ValueCell.update(this.depthMerge.values.uTexSize, Vec2.set(this.depthMerge.values.uTexSize.ref.value, 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); + + // 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); + + // 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) { this.webgl.unbindFramebuffer(); } else { @@ -118,13 +179,16 @@ export class DrawPass { } } - renderer.render(scene.primitives, camera, 'color', true, transparentBackground, 1, null); + 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) { this.depthTargetPrimitives.bind(); - renderer.render(scene.primitives, camera, 'depth', true, transparentBackground, 1, null); + renderer.clear(false); + renderer.renderDepth(scene.primitives, camera, null); this.colorTarget.bind(); } @@ -132,49 +196,53 @@ export class DrawPass { if (!toDrawingBuffer) { if (!this.packedDepth) { this.depthTextureVolumes.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); - 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.clearDepth(); } - renderer.render(scene.volumes, camera, 'color', false, transparentBackground, 1, this.depthTexturePrimitives); + renderer.renderBlended(scene.volumes, camera, this.depthTexturePrimitives); // do volume depth pass if extensions.depthTexture is unsupported (i.e. depthTarget is set) if (this.depthTargetVolumes) { this.depthTargetVolumes.bind(); - renderer.render(scene.volumes, camera, 'depth', true, transparentBackground, 1, this.depthTexturePrimitives); + 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(); + 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); + + 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(helper.debug.scene, camera, 'color', false, transparentBackground, 1, null); + renderer.renderBlended(helper.debug.scene, camera, null); } if (helper.handle.isEnabled) { - renderer.render(helper.handle.scene, camera, 'color', false, transparentBackground, 1, null); + renderer.renderBlended(helper.handle.scene, camera, null); } if (helper.camera.isEnabled) { helper.camera.update(camera); - renderer.render(helper.camera.scene, helper.camera.camera, 'color', false, transparentBackground, 1, null); + renderer.update(helper.camera.camera); + renderer.renderBlended(helper.camera.scene, helper.camera.camera, null); } + + 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 348180eae64322f32fd96755a4e9bee70fa347d4..841fc7a1819684c5e351c238b35ecbee57cafdea 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -64,14 +64,16 @@ export class PickPass { } private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: GraphicsRenderVariant) { - const pickScale = this.pickBaseScale / this.webgl.pixelRatio; const depth = this.drawPass.depthTexturePrimitives; - renderer.render(scene.primitives, camera, variant, true, false, pickScale, null); - renderer.render(scene.volumes, camera, variant, false, false, pickScale, depth); - renderer.render(helper.handle.scene, camera, variant, false, false, pickScale, 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(renderer, camera, scene, helper, 'pickObject'); @@ -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 new file mode 100644 index 0000000000000000000000000000000000000000..d9ea9a1b54c4b54d97889c744a48369e7bf9d7e3 --- /dev/null +++ b/src/mol-canvas3d/passes/wboit.ts @@ -0,0 +1,120 @@ +/** + * Copyright (c) 2020 mol* contributors, licensed under MIT, See LICENSE file for more info. + * + * @author Áron Samuel Kovács <aron.kovacs@mail.muni.cz> + * @author Alexander Rose <alexander.rose@weirdbyte.de> + */ + +import { QuadSchema, QuadValues } from '../../mol-gl/compute/util'; +import { ComputeRenderable, createComputeRenderable } from '../../mol-gl/renderable'; +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'; +import { Texture } from '../../mol-gl/webgl/texture'; +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>> + +function getEvaluateWboitRenderable(ctx: WebGLContext, wboitATexture: Texture, wboitBTexture: Texture): EvaluateWboitRenderable { + const values: Values<typeof EvaluateWboitSchema> = { + ...QuadValues, + tWboitA: ValueCell.create(wboitATexture), + tWboitB: ValueCell.create(wboitBTexture), + uTexSize: ValueCell.create(Vec2.create(wboitATexture.getWidth(), wboitATexture.getHeight())), + }; + + const schema = { ...EvaluateWboitSchema }; + const renderItem = createComputeRenderItem(ctx, 'triangles', EvaluateWboitShaderCode, schema, values); + + return createComputeRenderable(renderItem, values); +} + +// + +export class WboitPass { + private readonly renderable: EvaluateWboitRenderable + + private readonly framebuffer: Framebuffer + private readonly textureA: Texture + private readonly textureB: Texture + + private _enabled = false; + get enabled() { + return this._enabled; + } + + bind() { + const { state, gl } = this.webgl; + + this.framebuffer.bind(); + + state.clearColor(0, 0, 0, 1); + gl.clear(gl.COLOR_BUFFER_BIT); + + state.disable(gl.DEPTH_TEST); + + state.blendFuncSeparate(gl.ONE, gl.ONE, gl.ZERO, gl.ONE_MINUS_SRC_ALPHA); + state.enable(gl.BLEND); + } + + render() { + const { state, gl } = this.webgl; + + state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); + state.enable(gl.BLEND); + + this.renderable.update(); + this.renderable.render(); + } + + setSize(width: number, height: number) { + 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) { + if (isDebugMode) console.log('Missing extensions required for "wboit"'); + return; + } + + this.textureA = resources.texture('image-float32', 'rgba', 'float', 'nearest'); + this.textureA.define(width, height); + + this.textureB = resources.texture('image-float32', 'rgba', 'float', 'nearest'); + this.textureB.define(width, height); + + this.renderable = getEvaluateWboitRenderable(webgl, this.textureA, this.textureB); + + this.framebuffer = resources.framebuffer(); + this.framebuffer.bind(); + drawBuffers.drawBuffers([ + drawBuffers.COLOR_ATTACHMENT0, + drawBuffers.COLOR_ATTACHMENT1, + ]); + + this.textureA.attachFramebuffer(this.framebuffer, 'color0'); + this.textureB.attachFramebuffer(this.framebuffer, 'color1'); + + this._enabled = true; + } +} \ No newline at end of file diff --git a/src/mol-geo/geometry/transparency-data.ts b/src/mol-geo/geometry/transparency-data.ts index 22879551be9ae5d7999ff0439e4a2de61174a029..85e79813510cffddfb0e6cfa6c45169d9bea901f 100644 --- a/src/mol-geo/geometry/transparency-data.ts +++ b/src/mol-geo/geometry/transparency-data.ts @@ -13,6 +13,7 @@ export type TransparencyData = { uTransparencyTexDim: ValueCell<Vec2> dTransparency: ValueCell<boolean>, dTransparencyVariant: ValueCell<string>, + transparencyAverage: ValueCell<number>, } export function applyTransparencyValue(array: Uint8Array, start: number, end: number, value: number) { @@ -22,6 +23,14 @@ export function applyTransparencyValue(array: Uint8Array, start: number, end: nu return true; } +export function getTransparencyAverage(array: Uint8Array, count: number): number { + let sum = 0; + for (let i = 0; i < count; ++i) { + sum += array[i]; + } + return sum / (255 * count); +} + export function clearTransparency(array: Uint8Array, start: number, end: number) { array.fill(0, start, end); } @@ -32,6 +41,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc ValueCell.update(transparencyData.tTransparency, transparency); ValueCell.update(transparencyData.uTransparencyTexDim, Vec2.create(transparency.width, transparency.height)); ValueCell.updateIfChanged(transparencyData.dTransparency, count > 0); + ValueCell.updateIfChanged(transparencyData.transparencyAverage, getTransparencyAverage(transparency.array, count)); return transparencyData; } else { return { @@ -39,6 +49,7 @@ export function createTransparency(count: number, transparencyData?: Transparenc uTransparencyTexDim: ValueCell.create(Vec2.create(transparency.width, transparency.height)), dTransparency: ValueCell.create(count > 0), dTransparencyVariant: ValueCell.create('single'), + transparencyAverage: ValueCell.create(0), }; } } @@ -55,6 +66,7 @@ export function createEmptyTransparency(transparencyData?: TransparencyData): Tr uTransparencyTexDim: ValueCell.create(Vec2.create(1, 1)), dTransparency: ValueCell.create(false), dTransparencyVariant: ValueCell.create('single'), + transparencyAverage: ValueCell.create(0), }; } } \ No newline at end of file diff --git a/src/mol-gl/_spec/renderer.spec.ts b/src/mol-gl/_spec/renderer.spec.ts index 7c4872bd3aa1e3cbe6ea35ea917b95bd56147a34..342eac9dd1bfe16527700b481e13915bd6039cde 100644 --- a/src/mol-gl/_spec/renderer.spec.ts +++ b/src/mol-gl/_spec/renderer.spec.ts @@ -132,17 +132,17 @@ describe('renderer', () => { scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(ctx.isWebGL2 ? 4 : 5); expect(ctx.stats.resourceCounts.texture).toBe(6); - expect(ctx.stats.resourceCounts.vertexArray).toBe(5); - expect(ctx.stats.resourceCounts.program).toBe(5); - expect(ctx.stats.resourceCounts.shader).toBe(10); + expect(ctx.stats.resourceCounts.vertexArray).toBe(6); + expect(ctx.stats.resourceCounts.program).toBe(6); + expect(ctx.stats.resourceCounts.shader).toBe(12); scene.remove(points); scene.commit(); expect(ctx.stats.resourceCounts.attribute).toBe(0); expect(ctx.stats.resourceCounts.texture).toBe(0); expect(ctx.stats.resourceCounts.vertexArray).toBe(0); - expect(ctx.stats.resourceCounts.program).toBe(5); - expect(ctx.stats.resourceCounts.shader).toBe(10); + expect(ctx.stats.resourceCounts.program).toBe(6); + expect(ctx.stats.resourceCounts.shader).toBe(12); ctx.resources.destroy(); expect(ctx.stats.resourceCounts.program).toBe(0); diff --git a/src/mol-gl/renderable.ts b/src/mol-gl/renderable.ts index 51f087b79b309950499064488082646990a4a909..7e70cfec82a43fc3b2f0d07d2d5dc86d55bd8514 100644 --- a/src/mol-gl/renderable.ts +++ b/src/mol-gl/renderable.ts @@ -1,15 +1,16 @@ /** - * 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'; import { clamp } from '../mol-math/interpolate'; +import { Textures } from './webgl/texture'; const getNextRenderableId = idFactory(); @@ -19,7 +20,7 @@ export type RenderableState = { pickable: boolean colorOnly: boolean opaque: boolean - writeDepth: boolean, + writeDepth: boolean } export interface Renderable<T extends RenderableValues> { @@ -28,7 +29,7 @@ export interface Renderable<T extends RenderableValues> { readonly values: T readonly state: RenderableState - render: (variant: GraphicsRenderVariant) => void + render: (variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => void getProgram: (variant: GraphicsRenderVariant) => Program update: () => void dispose: () => void @@ -41,11 +42,11 @@ export function createRenderable<T extends Values<RenderableSchema>>(renderItem: values, state, - render: (variant: GraphicsRenderVariant) => { + render: (variant: GraphicsRenderVariant, sharedTexturesList?: Textures) => { if (values.uAlpha && values.alpha) { ValueCell.updateIfChanged(values.uAlpha, clamp(values.alpha.ref.value * state.alphaFactor, 0, 1)); } - renderItem.render(variant); + renderItem.render(variant, sharedTexturesList); }, getProgram: (variant: GraphicsRenderVariant) => renderItem.getProgram(variant), update: () => renderItem.update(), @@ -53,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 a0fd532f6b031ba3547c9a04021200fce39dcce6..3ab83368fb85a8c8065255371c7198bd6081b038 100644 --- a/src/mol-gl/renderable/direct-volume.ts +++ b/src/mol-gl/renderable/direct-volume.ts @@ -28,6 +28,7 @@ export const DirectVolumeSchema = { tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), dTransparencyVariant: DefineSpec('string', ['single', 'multi']), + transparencyAverage: ValueSpec('number'), dClipObjectCount: DefineSpec('number'), dClipVariant: DefineSpec('string', ['instance', 'pixel']), diff --git a/src/mol-gl/renderable/image.ts b/src/mol-gl/renderable/image.ts index 0ba595cca0e1d49c1cc73c7cba7964664b8cc6a2..82c5172132755ee064546fa403ebf7224f74b18a 100644 --- a/src/mol-gl/renderable/image.ts +++ b/src/mol-gl/renderable/image.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { AttributeSpec, Values, GlobalUniformSchema, InternalSchema, TextureSpec, ElementsSpec, DefineSpec, InternalValues, BaseSchema, UniformSpec } from './schema'; +import { AttributeSpec, Values, GlobalUniformSchema, InternalSchema, TextureSpec, ElementsSpec, DefineSpec, InternalValues, BaseSchema, UniformSpec, GlobalTextureSchema } from './schema'; import { ImageShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; import { InterpolationTypeNames } from '../../mol-geo/geometry/image/image'; @@ -30,7 +30,7 @@ export type ImageSchema = typeof ImageSchema export type ImageValues = Values<ImageSchema> export function ImageRenderable(ctx: WebGLContext, id: number, values: ImageValues, state: RenderableState, materialId: number): Renderable<ImageValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...ImageSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...ImageSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderable/lines.ts b/src/mol-gl/renderable/lines.ts index aa6c31bbb6f95e195880871fde0845aba2714344..a8272b29cca6434e272be5e0c043d2196894f2f0 100644 --- a/src/mol-gl/renderable/lines.ts +++ b/src/mol-gl/renderable/lines.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, ElementsSpec, InternalValues } from './schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, DefineSpec, Values, InternalSchema, SizeSchema, ElementsSpec, InternalValues, GlobalTextureSchema } from './schema'; import { ValueCell } from '../../mol-util'; import { LinesShaderCode } from '../shader-code'; @@ -26,7 +26,7 @@ export type LinesSchema = typeof LinesSchema export type LinesValues = Values<LinesSchema> export function LinesRenderable(ctx: WebGLContext, id: number, values: LinesValues, state: RenderableState, materialId: number): Renderable<LinesValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...LinesSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...LinesSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderable/mesh.ts b/src/mol-gl/renderable/mesh.ts index 5c56c062cd7373b15388715d36da78e8f2491b3c..50de083a80a73d3e2086e5ad441a944a0a2f0c76 100644 --- a/src/mol-gl/renderable/mesh.ts +++ b/src/mol-gl/renderable/mesh.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues } from './schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, ElementsSpec, DefineSpec, Values, InternalSchema, InternalValues, GlobalTextureSchema } from './schema'; import { MeshShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; @@ -25,7 +25,7 @@ export type MeshSchema = typeof MeshSchema export type MeshValues = Values<MeshSchema> export function MeshRenderable(ctx: WebGLContext, id: number, values: MeshValues, state: RenderableState, materialId: number): Renderable<MeshValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...MeshSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...MeshSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderable/points.ts b/src/mol-gl/renderable/points.ts index 4f50b314e84b77f8e55303936e283a08d2f14830..a97b8bb597525ae33dea1f87055b445a9f9f8abd 100644 --- a/src/mol-gl/renderable/points.ts +++ b/src/mol-gl/renderable/points.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema, SizeSchema, InternalValues } from './schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, DefineSpec, Values, InternalSchema, SizeSchema, InternalValues, GlobalTextureSchema } from './schema'; import { PointsShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; @@ -23,7 +23,7 @@ export type PointsSchema = typeof PointsSchema export type PointsValues = Values<PointsSchema> export function PointsRenderable(ctx: WebGLContext, id: number, values: PointsValues, state: RenderableState, materialId: number): Renderable<PointsValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...PointsSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...PointsSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderable/schema.ts b/src/mol-gl/renderable/schema.ts index 06f65477c0393f337969b1c1cc124fe58ccc1554..40f0c0854e94f47955665f0c87948ddc71b7b3d5 100644 --- a/src/mol-gl/renderable/schema.ts +++ b/src/mol-gl/renderable/schema.ts @@ -155,6 +155,8 @@ export const GlobalUniformSchema = { uHighlightColor: UniformSpec('v3'), uSelectColor: UniformSpec('v3'), + + uRenderWboit: UniformSpec('i'), } as const; export type GlobalUniformSchema = typeof GlobalUniformSchema export type GlobalUniformValues = Values<GlobalUniformSchema> @@ -212,6 +214,7 @@ export const TransparencySchema = { tTransparency: TextureSpec('image-uint8', 'alpha', 'ubyte', 'nearest'), dTransparency: DefineSpec('boolean'), dTransparencyVariant: DefineSpec('string', ['single', 'multi']), + transparencyAverage: ValueSpec('number'), } as const; export type TransparencySchema = typeof TransparencySchema export type TransparencyValues = Values<TransparencySchema> diff --git a/src/mol-gl/renderable/spheres.ts b/src/mol-gl/renderable/spheres.ts index fbc5d0ec6b7db04ae2ef7554d8324fdcb27d440f..ebb602fbf5d27f16ecf9aa0e6cfdea8d1ca0a461 100644 --- a/src/mol-gl/renderable/spheres.ts +++ b/src/mol-gl/renderable/spheres.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec } from './schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, Values, InternalSchema, SizeSchema, InternalValues, ElementsSpec, ValueSpec, DefineSpec, GlobalTextureSchema } from './schema'; import { SpheresShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; @@ -26,7 +26,7 @@ export type SpheresSchema = typeof SpheresSchema export type SpheresValues = Values<SpheresSchema> export function SpheresRenderable(ctx: WebGLContext, id: number, values: SpheresValues, state: RenderableState, materialId: number): Renderable<SpheresValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...SpheresSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...SpheresSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderable/text.ts b/src/mol-gl/renderable/text.ts index a655bf144271ed92f81c78e4654ab9e8b51df28c..a31ca9939613df91ec0d334b89d88e575da65729 100644 --- a/src/mol-gl/renderable/text.ts +++ b/src/mol-gl/renderable/text.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, Values, InternalSchema, SizeSchema, InternalValues, TextureSpec, ElementsSpec, ValueSpec } from './schema'; +import { GlobalUniformSchema, BaseSchema, AttributeSpec, UniformSpec, Values, InternalSchema, SizeSchema, InternalValues, TextureSpec, ElementsSpec, ValueSpec, GlobalTextureSchema } from './schema'; import { TextShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; @@ -35,7 +35,7 @@ export type TextSchema = typeof TextSchema export type TextValues = Values<TextSchema> export function TextRenderable(ctx: WebGLContext, id: number, values: TextValues, state: RenderableState, materialId: number): Renderable<TextValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...TextSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...TextSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderable/texture-mesh.ts b/src/mol-gl/renderable/texture-mesh.ts index a0d9be2ba1ff06d5649f36b085a11f97dbbf13ef..062936f5f5578591d0cebf92f3b4b466a458b9f9 100644 --- a/src/mol-gl/renderable/texture-mesh.ts +++ b/src/mol-gl/renderable/texture-mesh.ts @@ -7,7 +7,7 @@ import { Renderable, RenderableState, createRenderable } from '../renderable'; import { WebGLContext } from '../webgl/context'; import { createGraphicsRenderItem } from '../webgl/render-item'; -import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec } from './schema'; +import { GlobalUniformSchema, BaseSchema, DefineSpec, Values, InternalSchema, InternalValues, UniformSpec, TextureSpec, GlobalTextureSchema } from './schema'; import { MeshShaderCode } from '../shader-code'; import { ValueCell } from '../../mol-util'; @@ -28,7 +28,7 @@ export type TextureMeshSchema = typeof TextureMeshSchema export type TextureMeshValues = Values<TextureMeshSchema> export function TextureMeshRenderable(ctx: WebGLContext, id: number, values: TextureMeshValues, state: RenderableState, materialId: number): Renderable<TextureMeshValues> { - const schema = { ...GlobalUniformSchema, ...InternalSchema, ...TextureMeshSchema }; + const schema = { ...GlobalUniformSchema, ...GlobalTextureSchema, ...InternalSchema, ...TextureMeshSchema }; const internalValues: InternalValues = { uObjectId: ValueCell.create(id), }; diff --git a/src/mol-gl/renderer.ts b/src/mol-gl/renderer.ts index 09c94035d5f6e09dca17aad38bb6d67c27e5eb1b..ea27728397c023b0ad202af6d7b31112a47fb005 100644 --- a/src/mol-gl/renderer.ts +++ b/src/mol-gl/renderer.ts @@ -9,17 +9,18 @@ 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'; import { stringToWords } from '../mol-util/string'; import { Transparency } from '../mol-theme/transparency'; import { degToRad } from '../mol-math/misc'; -import { Texture } from './webgl/texture'; +import { createNullTexture, Texture, Textures } from './webgl/texture'; +import { arrayMapUpsert } from '../mol-util/array'; export interface RendererStats { programCount: number @@ -41,10 +42,21 @@ interface Renderer { readonly stats: RendererStats readonly props: Readonly<RendererProps> - clear: (transparentBackground: boolean) => void - render: (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | 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 } @@ -167,6 +179,14 @@ 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] + ]; + const view = Mat4(); const invView = Mat4(); const modelView = Mat4(); @@ -204,6 +224,9 @@ namespace Renderer { uFogNear: ValueCell.create(1), uFogFar: ValueCell.create(10000), uFogColor: ValueCell.create(bgColor), + + uRenderWboit: ValueCell.create(0), + uTransparentBackground: ValueCell.create(false), uClipObjectType: ValueCell.create(clip.objects.type), @@ -232,7 +255,7 @@ namespace Renderer { let globalUniformsNeedUpdate = true; - const renderObject = (r: Renderable<RenderableValues & BaseValues>, variant: GraphicsRenderVariant, depthTexture: Texture | null) => { + const renderObject = (r: GraphicsRenderable, variant: GraphicsRenderVariant) => { if (!r.state.visible || (!r.state.pickable && variant[0] === 'p')) { return; } @@ -265,26 +288,23 @@ namespace Renderer { globalUniformsNeedUpdate = false; } - if (depthTexture) program.bindTextures([['tDepth', depthTexture]]); - if (r.values.dRenderMode) { // indicates direct-volume - // always cull front - state.enable(gl.CULL_FACE); - state.frontFace(gl.CW); - state.cullFace(gl.BACK); - - // depth test done manually in shader against `depthTexture` - // still need to enable when fragDepth can be used to write depth - // (unclear why depthMask is insufficient) - if (r.values.dRenderMode.ref.value === 'volume' || !fragDepth) { - state.disable(gl.DEPTH_TEST); - state.depthMask(false); - } else { - state.enable(gl.DEPTH_TEST); - state.depthMask(r.state.writeDepth); + // culling done in fragment shader + state.disable(gl.CULL_FACE); + state.frontFace(gl.CCW); + + 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) { + state.disable(gl.DEPTH_TEST); + state.depthMask(false); + } else { + state.enable(gl.DEPTH_TEST); + state.depthMask(r.state.writeDepth); + } } } else { - state.enable(gl.DEPTH_TEST); if (r.values.dDoubleSided) { if (r.values.dDoubleSided.ref.value || r.values.hasReflection.ref.value) { state.disable(gl.CULL_FACE); @@ -310,22 +330,17 @@ namespace Renderer { state.cullFace(gl.BACK); } - state.depthMask(r.state.writeDepth); + if (variant === 'colorBlended') state.depthMask(r.state.writeDepth); } - r.render(variant); + r.render(variant, sharedTexturesList); }; - const render = (group: Scene.Group, camera: ICamera, variant: GraphicsRenderVariant, clear: boolean, transparentBackground: boolean, drawingBufferScale: number, depthTexture: Texture | null) => { - 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)); @@ -347,74 +362,150 @@ namespace Renderer { gl.drawingBufferHeight * drawingBufferScale )); } + }; - globalUniformsNeedUpdate = true; - state.currentRenderItemId = -1; + const updateInternal = (group: Scene.Group, camera: ICamera, depthTexture: Texture | null, renderWboit: boolean) => { + arrayMapUpsert(sharedTexturesList, 'tDepth', depthTexture || nullDepthTexture); - const { renderables } = group; + 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.disable(gl.BLEND); state.colorMask(true, true, true, true); - state.enable(gl.DEPTH_TEST); const { x, y, width, height } = viewport; gl.viewport(x, y, width, height); gl.scissor(x, y, width, height); - if (clear) { - state.depthMask(true); - if (variant === 'color') { - state.clearColor(bgColor[0], bgColor[1], bgColor[2], transparentBackground ? 0 : 1); - } else { - state.clearColor(1, 1, 1, 1); + 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); } - gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT); } + }; - if (variant === 'color') { - for (let i = 0, il = renderables.length; i < il; ++i) { - const r = renderables[i]; - if (r.state.opaque) { - renderObject(r, variant, depthTexture); - } + 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'); + } + }; + + const renderBlended = (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) { + const r = renderables[i]; + if (r.state.opaque) { + renderObject(r, 'colorBlended'); } + } - 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, depthTexture); - } + state.blendFuncSeparate(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA, gl.ONE, gl.ONE); + state.enable(gl.BLEND); + + 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.state.opaque && !r.state.writeDepth) { - renderObject(r, variant, depthTexture); - } + } + + 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'); } - } else { // picking & depth - for (let i = 0, il = renderables.length; i < il; ++i) { - if (!renderables[i].state.colorOnly) { - renderObject(renderables[i], variant, depthTexture); - } + } + }; + + const renderWboitOpaque = (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) { + 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(); + 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 (transparentBackground) { + state.clearColor(1, 1, 1, 0); + } else if (toBackgroundColor) { + state.clearColor(bgColor[0], bgColor[1], bgColor[2], 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) { @@ -481,6 +572,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 3b85e1800ca82654970e38f8574769d84f19a0bb..d37bcc0d3a02638676b136da75f273e304136050 100644 --- a/src/mol-gl/shader-code.ts +++ b/src/mol-gl/shader-code.ts @@ -59,6 +59,8 @@ import size_vert_params from './shader/chunks/size-vert-params.glsl'; import texture3d_from_1d_trilinear from './shader/chunks/texture3d-from-1d-trilinear.glsl'; import texture3d_from_2d_linear from './shader/chunks/texture3d-from-2d-linear.glsl'; import texture3d_from_2d_nearest from './shader/chunks/texture3d-from-2d-nearest.glsl'; +import wboit_params from './shader/chunks/wboit-params.glsl'; +import wboit_write from './shader/chunks/wboit-write.glsl'; const ShaderChunks: { [k: string]: string } = { apply_fog, @@ -88,7 +90,9 @@ const ShaderChunks: { [k: string]: string } = { size_vert_params, texture3d_from_1d_trilinear, texture3d_from_2d_linear, - texture3d_from_2d_nearest + texture3d_from_2d_nearest, + wboit_params, + wboit_write }; const reInclude = /^(?!\/\/)\s*#include\s+(\S+)/gmi; @@ -115,31 +119,31 @@ export function ShaderCode(name: string, vert: string, frag: string, extensions: import points_vert from './shader/points.vert'; import points_frag from './shader/points.frag'; -export const PointsShaderCode = ShaderCode('points', points_vert, points_frag); +export const PointsShaderCode = ShaderCode('points', points_vert, points_frag, { drawBuffers: 'optional' }); import spheres_vert from './shader/spheres.vert'; import spheres_frag from './shader/spheres.frag'; -export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required' }); +export const SpheresShaderCode = ShaderCode('spheres', spheres_vert, spheres_frag, { fragDepth: 'required', drawBuffers: 'optional' }); import text_vert from './shader/text.vert'; import text_frag from './shader/text.frag'; -export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required' }); +export const TextShaderCode = ShaderCode('text', text_vert, text_frag, { standardDerivatives: 'required', drawBuffers: 'optional' }); import lines_vert from './shader/lines.vert'; import lines_frag from './shader/lines.frag'; -export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag); +export const LinesShaderCode = ShaderCode('lines', lines_vert, lines_frag, { drawBuffers: 'optional' }); import mesh_vert from './shader/mesh.vert'; import mesh_frag from './shader/mesh.frag'; -export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional' }); +export const MeshShaderCode = ShaderCode('mesh', mesh_vert, mesh_frag, { standardDerivatives: 'optional', drawBuffers: 'optional' }); import direct_volume_vert from './shader/direct-volume.vert'; import direct_volume_frag from './shader/direct-volume.frag'; -export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: 'optional' }); +export const DirectVolumeShaderCode = ShaderCode('direct-volume', direct_volume_vert, direct_volume_frag, { fragDepth: 'optional', drawBuffers: 'optional' }); import image_vert from './shader/image.vert'; import image_frag from './shader/image.frag'; -export const ImageShaderCode = ShaderCode('image', image_vert, image_frag); +export const ImageShaderCode = ShaderCode('image', image_vert, image_frag, { drawBuffers: 'optional' }); // @@ -186,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`); } @@ -198,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'; } @@ -208,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 @@ -215,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) { @@ -226,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/apply-fog.glsl.ts b/src/mol-gl/shader/chunks/apply-fog.glsl.ts index e58c39ca1bf7857d5005ae73540cf81e4adadda3..6bd3ef7ebc5fdd75a8cd76027c278bc5e471307f 100644 --- a/src/mol-gl/shader/chunks/apply-fog.glsl.ts +++ b/src/mol-gl/shader/chunks/apply-fog.glsl.ts @@ -2,12 +2,12 @@ export default ` float fogDepth = length(vViewPosition); float fogFactor = smoothstep(uFogNear, uFogFar, fogDepth); float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a; +float preFogAlpha = gl_FragColor.a < 1.0 ? fogAlpha : 1.0; if (!uTransparentBackground) { gl_FragColor.rgb = mix(gl_FragColor.rgb, uFogColor, fogFactor); if (gl_FragColor.a < 1.0) gl_FragColor.a = fogAlpha; } else { - float fogAlpha = (1.0 - fogFactor) * gl_FragColor.a; gl_FragColor.a = fogAlpha; } `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts index 4ad0f6a17c3f5960e9a4269f390aa0e6eaf0aad1..a79bb18924f2672a0af13d2973c21f96a244a334 100644 --- a/src/mol-gl/shader/chunks/assign-material-color.glsl.ts +++ b/src/mol-gl/shader/chunks/assign-material-color.glsl.ts @@ -28,25 +28,30 @@ export default ` if (ta < uPickingAlphaThreshold) discard; // ignore so the element below can be picked #else - float at = 0.0; + #if defined(dRenderVariant_colorBlended) + float at = 0.0; - // shift by view-offset during multi-sample rendering to allow for blending - vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25; + // shift by view-offset during multi-sample rendering to allow for blending + vec2 coord = gl_FragCoord.xy + uViewOffset * 0.25; - #if defined(dTransparencyVariant_single) - const mat4 thresholdMatrix = mat4( - 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0, - 13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0, - 4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0, - 16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0 - ); - at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))]; - #elif defined(dTransparencyVariant_multi) - at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f)); - #endif + #if defined(dTransparencyVariant_single) + const mat4 thresholdMatrix = mat4( + 1.0 / 17.0, 9.0 / 17.0, 3.0 / 17.0, 11.0 / 17.0, + 13.0 / 17.0, 5.0 / 17.0, 15.0 / 17.0, 7.0 / 17.0, + 4.0 / 17.0, 12.0 / 17.0, 2.0 / 17.0, 10.0 / 17.0, + 16.0 / 17.0, 8.0 / 17.0, 14.0 / 17.0, 6.0 / 17.0 + ); + at = thresholdMatrix[int(intMod(coord.x, 4.0))][int(intMod(coord.y, 4.0))]; + #elif defined(dTransparencyVariant_multi) + at = fract(dot(vec3(coord, vGroup + 0.5), vec3(2.0, 7.0, 23.0) / 17.0f)); + #endif - if (ta < 0.99 && (ta < 0.01 || ta < at)) - discard; + if (ta < 0.99 && (ta < 0.01 || ta < at)) { + discard; + } + #elif defined(dRenderVariant_colorWboit) + material.a *= ta; + #endif #endif #endif `; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts index e5e44da56106b0c335a4cf7d007e72a2847ab90a..6c784e73b062809b0f6d047331e8b5f3eca14c9a 100644 --- a/src/mol-gl/shader/chunks/common-frag-params.glsl.ts +++ b/src/mol-gl/shader/chunks/common-frag-params.glsl.ts @@ -43,4 +43,6 @@ uniform float uInteriorDarkening; uniform bool uInteriorColorFlag; uniform vec3 uInteriorColor; bool interior; + +uniform mat4 uProjection; `; \ No newline at end of file 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 new file mode 100644 index 0000000000000000000000000000000000000000..3655cf1d3528d6d1d770cb0e50360d19599160be --- /dev/null +++ b/src/mol-gl/shader/chunks/wboit-params.glsl.ts @@ -0,0 +1,20 @@ +export default ` +#if defined(dRenderVariant_colorWboit) + #if !defined(dRenderMode_volume) && !defined(dRenderMode_isosurface) + uniform sampler2D tDepth; + uniform vec2 uDrawingBufferSize; + + float getDepth(const in vec2 coords) { + // always packed due to merged depth from primitives and volumes + return unpackRGBAToDepth(texture2D(tDepth, coords)); + } + #endif +#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; +} +`; \ No newline at end of file diff --git a/src/mol-gl/shader/chunks/wboit-write.glsl.ts b/src/mol-gl/shader/chunks/wboit-write.glsl.ts new file mode 100644 index 0000000000000000000000000000000000000000..18b7f229d372050229d1bbfbdd83556893d22421 --- /dev/null +++ b/src/mol-gl/shader/chunks/wboit-write.glsl.ts @@ -0,0 +1,18 @@ +export default ` +#if defined(dRenderVariant_colorWboit) + if (uRenderWboit == 0) { + if (preFogAlpha < 1.0) { + discard; + } + } else if (uRenderWboit == 1) { + if (preFogAlpha != 1.0 && !interior && fragmentDepth < getDepth(gl_FragCoord.xy / uDrawingBufferSize)) { + float alpha = preFogAlpha; + float wboitWeight = alpha * clamp(pow(1.0 - fragmentDepth, 2.0), 0.01, 1.0); + gl_FragColor = vec4(gl_FragColor.rgb * alpha * wboitWeight, alpha); + gl_FragData[1] = 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 bde2839a47f971504a6ed4c2c1480552fd6a8dfb..b17260d5d5d847aff57e07ccb38e398dc72268cf 100644 --- a/src/mol-gl/shader/direct-volume.frag.ts +++ b/src/mol-gl/shader/direct-volume.frag.ts @@ -121,21 +121,22 @@ uniform mat4 uCartnToUnit; } #endif +#include wboit_params + vec4 transferFunction(float value) { return texture2D(tTransferTex, vec2(value, 0.0)); } -// Calculate depth based on the given camera position. -float calcDepth(const in vec3 cameraPos){ - vec2 clipZW = cameraPos.z * uProjection[2].zw + uProjection[3].zw; - return 0.5 + 0.5 * clipZW.x / clipZW.y; -} - 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 } @@ -145,6 +146,8 @@ vec3 v3m4(vec3 p, mat4 m) { return (m * vec4(p, 1.0)).xyz; } +float preFogAlphaBlended = 0.0; + vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { #if defined(dRenderVariant_color) && !defined(dIgnoreLight) mat3 normalMatrix = transpose3(inverse3(mat3(uModelView * vTransform))); @@ -321,6 +324,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; #include apply_interior_color #include apply_marker_color + + preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended; #include apply_fog src = gl_FragColor; @@ -382,6 +387,8 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; #include apply_marker_color + + preFogAlphaBlended = (1.0 - preFogAlphaBlended) * gl_FragColor.a + preFogAlphaBlended; #include apply_fog src = gl_FragColor; @@ -411,6 +418,9 @@ vec4 raymarch(vec3 startLoc, vec3 step, vec3 rayDir) { // TODO: support clipping exclusion texture support void main () { + if (gl_FrontFacing) + discard; + #if defined(dRenderVariant_pick) || defined(dRenderVariant_depth) #if defined(dRenderMode_volume) // always ignore pick & depth for volume @@ -433,5 +443,16 @@ void main () { if (gl_FragColor == vec4(0.0)) discard; #endif + + #if defined(dRenderVariant_color) + #if defined(dRenderMode_isosurface) && defined(enabledFragDepth) + float fragmentDepth = gl_FragDepthEXT; + #else + float fragmentDepth = calcDepth((uView * vec4(uCameraPosition + (d * rayDir), 1.0)).xyz); + #endif + float preFogAlpha = clamp(preFogAlphaBlended, 0.0, 1.0); + interior = false; + #include wboit_write + #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/evaluate-wboit.frag.ts b/src/mol-gl/shader/evaluate-wboit.frag.ts new file mode 100644 index 0000000000000000000000000000000000000000..d797cf7aa81276f14345ef1022337dd733c4a1d0 --- /dev/null +++ b/src/mol-gl/shader/evaluate-wboit.frag.ts @@ -0,0 +1,17 @@ +export default ` +precision highp float; + +uniform sampler2D tWboitA; +uniform sampler2D tWboitB; +uniform vec2 uTexSize; + +void main() { + vec2 coords = gl_FragCoord.xy / uTexSize; + + vec4 accum = texture2D(tWboitA, coords); + float r = 1.0 - accum.a; + + 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/shader/image.frag.ts b/src/mol-gl/shader/image.frag.ts index 3b2e076d4d530c30b7ad7e16a8b4100e4f85ae4c..995f4797dc6ac124d7e82696b7e070a7d375d227 100644 --- a/src/mol-gl/shader/image.frag.ts +++ b/src/mol-gl/shader/image.frag.ts @@ -10,6 +10,7 @@ precision highp int; #include common #include read_from_texture #include common_frag_params +#include wboit_params uniform vec2 uImageTexDim; uniform sampler2D tImageTex; @@ -122,6 +123,10 @@ void main() { float vMarker = readFromTexture(tMarker, vInstance * float(uGroupCount) + group, uMarkerTexDim).a; #include apply_marker_color #include apply_fog + + float fragmentDepth = gl_FragCoord.z; + bool interior = false; + #include wboit_write #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/lines.frag.ts b/src/mol-gl/shader/lines.frag.ts index 81c91489b990406d39ded0af995917f11098f6d0..e85fb79e6cc3e8c3c71740e28059ebc115311446 100644 --- a/src/mol-gl/shader/lines.frag.ts +++ b/src/mol-gl/shader/lines.frag.ts @@ -12,6 +12,7 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip +#include wboit_params void main(){ #include clip_pixel @@ -27,6 +28,10 @@ void main(){ #include apply_marker_color #include apply_fog + + float fragmentDepth = gl_FragCoord.z; + bool interior = false; + #include wboit_write #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/mesh.frag.ts b/src/mol-gl/shader/mesh.frag.ts index 0ed9f7ebe318da42c6fa91bea2d1d1199fa575c4..1884c5be13766e905138eba3779aaf1650c81dd8 100644 --- a/src/mol-gl/shader/mesh.frag.ts +++ b/src/mol-gl/shader/mesh.frag.ts @@ -14,6 +14,7 @@ precision highp int; #include light_frag_params #include normal_frag_params #include common_clip +#include wboit_params void main() { #include clip_pixel @@ -59,6 +60,9 @@ void main() { #include apply_interior_color #include apply_marker_color #include apply_fog + + float fragmentDepth = gl_FragCoord.z; + #include wboit_write #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/points.frag.ts b/src/mol-gl/shader/points.frag.ts index e3e8fafb350afdfe991fafd83d8a651f05067d03..9aefed044b946219f472c51510a7c113b9e97d89 100644 --- a/src/mol-gl/shader/points.frag.ts +++ b/src/mol-gl/shader/points.frag.ts @@ -12,6 +12,7 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip +#include wboit_params #ifdef dPointFilledCircle uniform float uPointEdgeBleach; @@ -41,6 +42,10 @@ void main(){ #include apply_marker_color #include apply_fog + + float fragmentDepth = gl_FragCoord.z; + bool interior = false; + #include wboit_write #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/spheres.frag.ts b/src/mol-gl/shader/spheres.frag.ts index de5731f3deaa07513cd6c1bf2666a7ede3bbe0de..39829d6c2e4b24ca0ff56cde9a493ef939407d79 100644 --- a/src/mol-gl/shader/spheres.frag.ts +++ b/src/mol-gl/shader/spheres.frag.ts @@ -13,8 +13,7 @@ precision highp int; #include color_frag_params #include light_frag_params #include common_clip - -uniform mat4 uProjection; +#include wboit_params uniform float uClipNear; uniform float uIsOrtho; @@ -27,12 +26,6 @@ varying vec3 vPointViewPosition; vec3 cameraPos; vec3 cameraNormal; -// Calculate depth based on the given camera position. -float calcDepth(const in vec3 cameraPos){ - vec2 clipZW = cameraPos.z * uProjection[2].zw + uProjection[3].zw; - return 0.5 + 0.5 * clipZW.x / clipZW.y; -} - float calcClip(const in vec3 cameraPos) { return dot(vec4(cameraPos, 1.0), vec4(0.0, 0.0, 1.0, uClipNear - 0.5)); } @@ -114,6 +107,9 @@ void main(void){ #include apply_interior_color #include apply_marker_color #include apply_fog + + float fragmentDepth = gl_FragDepthEXT; + #include wboit_write #endif } `; \ No newline at end of file diff --git a/src/mol-gl/shader/text.frag.ts b/src/mol-gl/shader/text.frag.ts index c19dc7ab961ecb817f06175dc073cd16b91ff501..5fb133bba9133f2c4b34587751b0ebe76658d2d3 100644 --- a/src/mol-gl/shader/text.frag.ts +++ b/src/mol-gl/shader/text.frag.ts @@ -12,6 +12,7 @@ precision highp int; #include common_frag_params #include color_frag_params #include common_clip +#include wboit_params uniform sampler2D tFont; @@ -65,6 +66,10 @@ void main(){ #elif defined(dRenderVariant_color) #include apply_marker_color #include apply_fog + + float fragmentDepth = gl_FragCoord.z; + bool interior = false; + #include wboit_write #endif } `; \ No newline at end of file diff --git a/src/mol-gl/webgl/context.ts b/src/mol-gl/webgl/context.ts index e9303b859574b1cb6826032105360fe21aab5036..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) { @@ -186,6 +188,7 @@ export interface WebGLContext { readonly maxTextureSize: number readonly maxRenderbufferSize: number readonly maxDrawBuffers: number + readonly maxTextureImageUnits: number readonly isContextLost: boolean readonly contextRestored: BehaviorSubject<now.Timestamp> @@ -220,6 +223,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal maxTextureSize: gl.getParameter(gl.MAX_TEXTURE_SIZE) as number, maxRenderbufferSize: gl.getParameter(gl.MAX_RENDERBUFFER_SIZE) as number, maxDrawBuffers: isWebGL2(gl) ? gl.getParameter(gl.MAX_DRAW_BUFFERS) as number : 0, + maxTextureImageUnits: gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS) as number, maxVertexTextureImageUnits: gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS) as number, }; @@ -285,6 +289,7 @@ export function createContext(gl: GLRenderingContext, props: Partial<{ pixelScal get maxTextureSize () { return parameters.maxTextureSize; }, get maxRenderbufferSize () { return parameters.maxRenderbufferSize; }, get maxDrawBuffers () { return parameters.maxDrawBuffers; }, + get maxTextureImageUnits () { return parameters.maxTextureImageUnits; }, namedComputeRenderables: Object.create(null), namedFramebuffers: Object.create(null), 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/program.ts b/src/mol-gl/webgl/program.ts index 065fed789c6e70607e8eef1cb5ae61bca8ccc638..fcf40dca8c20951fa830531758181285c44c0f57 100644 --- a/src/mol-gl/webgl/program.ts +++ b/src/mol-gl/webgl/program.ts @@ -24,7 +24,7 @@ export interface Program { use: () => void setUniforms: (uniformValues: UniformsList) => void bindAttributes: (attribueBuffers: AttributeBuffers) => void - bindTextures: (textures: Textures) => void + bindTextures: (textures: Textures, startingTargetUnit?: number) => void reset: () => void destroy: () => void @@ -198,21 +198,15 @@ export function createProgram(gl: GLRenderingContext, state: WebGLState, extensi if (l !== -1) buffer.bind(l); } }, - bindTextures: (textures: Textures) => { + bindTextures: (textures: Textures, startingTargetUnit?: number) => { + startingTargetUnit = startingTargetUnit ?? 0; + for (let i = 0, il = textures.length; i < il; ++i) { const [k, texture] = textures[i]; const l = locations[k]; if (l !== null && l !== undefined) { - if (k === 'tDepth') { - // TODO find more explicit way? - texture.bind(15 as TextureId); - uniformSetters[k](gl, l, 15 as TextureId); - } else { - // TODO if the order and count of textures in a material can be made invariant - // bind needs to be called only when the material changes - texture.bind(i as TextureId); - uniformSetters[k](gl, l, i as TextureId); - } + texture.bind((i + startingTargetUnit) as TextureId); + uniformSetters[k](gl, l, (i + startingTargetUnit) as TextureId); } } }, diff --git a/src/mol-gl/webgl/render-item.ts b/src/mol-gl/webgl/render-item.ts index 88b9058212e518b9245354db4be0135eb128d7fb..02eb1c15e21387bfb592b8281416b6431374906a 100644 --- a/src/mol-gl/webgl/render-item.ts +++ b/src/mol-gl/webgl/render-item.ts @@ -5,7 +5,7 @@ */ import { createAttributeBuffers, ElementsBuffer, AttributeKind } from './buffer'; -import { createTextures, Texture } from './texture'; +import { createTextures, Texture, Textures } from './texture'; import { WebGLContext, checkError } from './context'; import { ShaderCode, DefineValues } from '../shader-code'; import { Program } from './program'; @@ -40,14 +40,14 @@ export interface RenderItem<T extends string> { readonly materialId: number getProgram: (variant: T) => Program - render: (variant: T) => void + render: (variant: T, sharedTexturesList?: Textures) => void update: () => Readonly<ValueChanges> destroy: () => void } // -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[]; @@ -164,12 +164,17 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: materialId, getProgram: (variant: T) => programs[variant], - render: (variant: T) => { + render: (variant: T, sharedTexturesList?: Textures) => { if (drawCount === 0 || instanceCount === 0 || ctx.isContextLost) return; const program = programs[variant]; if (program.id === currentProgramId && state.currentRenderItemId === id) { program.setUniforms(uniformValueEntries); - program.bindTextures(textures); + if (sharedTexturesList && sharedTexturesList.length > 0) { + program.bindTextures(sharedTexturesList, 0); + program.bindTextures(textures, sharedTexturesList.length); + } else { + program.bindTextures(textures); + } } else { const vertexArray = vertexArrays[variant]; if (program.id !== state.currentProgramId || program.id !== currentProgramId || @@ -182,7 +187,12 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: currentProgramId = program.id; } program.setUniforms(uniformValueEntries); - program.bindTextures(textures); + if (sharedTexturesList && sharedTexturesList.length > 0) { + program.bindTextures(sharedTexturesList, 0); + program.bindTextures(textures, sharedTexturesList.length); + } else { + program.bindTextures(textures); + } if (vertexArray) { vertexArray.bind(); // need to bind elements buffer explicitly since it is not always recorded in the VAO @@ -194,7 +204,11 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: state.currentRenderItemId = id; } if (isDebugMode) { - checkFramebufferStatus(ctx.gl); + try { + checkFramebufferStatus(ctx.gl); + } catch (e) { + throw new Error(`Framebuffer error rendering item id ${id}: '${e}'`); + } } if (elementsBuffer) { instancedArrays.drawElementsInstanced(glDrawMode, drawCount, elementsBuffer._dataType, 0, instanceCount); @@ -205,7 +219,7 @@ export function createRenderItem<T extends string>(ctx: WebGLContext, drawMode: try { checkError(ctx.gl); } catch (e) { - throw new Error(`Error rendering item id ${id}: '${e}'`); + throw new Error(`Draw error rendering item id ${id}: '${e}'`); } } }, 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-gl/webgl/texture.ts b/src/mol-gl/webgl/texture.ts index 3ccd3093cec7727c13e42966478edd75f2ba64a7..11eb2404657c148591954952e9145eaafc8824e0 100644 --- a/src/mol-gl/webgl/texture.ts +++ b/src/mol-gl/webgl/texture.ts @@ -312,4 +312,37 @@ export function createTextures(ctx: WebGLContext, schema: RenderableSchema, valu } }); return textures; +} + +// + +export function createNullTexture(gl: GLRenderingContext, kind: TextureKind): Texture { + const target = getTarget(gl, kind); + return { + id: getNextTextureId(), + target, + format: 0, + internalFormat: 0, + type: 0, + + getWidth: () => 0, + getHeight: () => 0, + getDepth: () => 0, + + define: () => {}, + load: () => {}, + bind: (id: TextureId) => { + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(target, null); + }, + unbind: (id: TextureId) => { + gl.activeTexture(gl.TEXTURE0 + id); + gl.bindTexture(target, null); + }, + attachFramebuffer: () => {}, + detachFramebuffer: () => {}, + + 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 7d1818660c7bb49a85e342b07132617989b633ce..625dc61f696936fa4c21dac5ddad82817d585bbe 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 }); diff --git a/src/mol-repr/visual.ts b/src/mol-repr/visual.ts index b163b9f3ba811ec4e237c8dacc43fd4d6632f4dd..7b1651ac030b62e40f525085faeae432bc58d275 100644 --- a/src/mol-repr/visual.ts +++ b/src/mol-repr/visual.ts @@ -20,7 +20,7 @@ import { Overpaint } from '../mol-theme/overpaint'; import { createOverpaint, clearOverpaint, applyOverpaintColor } from '../mol-geo/geometry/overpaint-data'; import { Interval } from '../mol-data/int'; import { Transparency } from '../mol-theme/transparency'; -import { createTransparency, clearTransparency, applyTransparencyValue } from '../mol-geo/geometry/transparency-data'; +import { createTransparency, clearTransparency, applyTransparencyValue, getTransparencyAverage } from '../mol-geo/geometry/transparency-data'; import { Clipping } from '../mol-theme/clipping'; import { createClipping, applyClippingGroups, clearClipping } from '../mol-geo/geometry/clipping-data'; @@ -114,7 +114,7 @@ namespace Visual { export function setTransparency(renderObject: GraphicsRenderObject | undefined, transparency: Transparency, lociApply: LociApply, clear: boolean) { if (!renderObject) return; - const { tTransparency, uGroupCount, instanceCount } = renderObject.values; + const { tTransparency, transparencyAverage, uGroupCount, instanceCount } = renderObject.values; const count = uGroupCount.ref.value * instanceCount.ref.value; // ensure texture has right size and variant @@ -134,6 +134,7 @@ namespace Visual { lociApply(loci, apply, false); } ValueCell.update(tTransparency, tTransparency.ref.value); + ValueCell.updateIfChanged(transparencyAverage, getTransparencyAverage(array, count)); } export function setClipping(renderObject: GraphicsRenderObject | undefined, clipping: Clipping, lociApply: LociApply, clear: boolean) { diff --git a/src/mol-util/array.ts b/src/mol-util/array.ts index 16b1ded0ab0905bab17f89e2b2c6b297c557cdc3..7a3931e568551c19c646423a467b7fe1dc7ca5b6 100644 --- a/src/mol-util/array.ts +++ b/src/mol-util/array.ts @@ -122,4 +122,14 @@ export function arrayIsIdentity(xs: ArrayLike<number>) { if (xs[i] !== i) return false; } return true; -} \ No newline at end of file +} + +export function arrayMapUpsert<T>(xs: [string, T][], key: string, value: T) { + for (let i = 0, il = xs.length; i < il; ++i) { + if (xs[i][0] === key) { + xs[i][1] = value; + return; + } + } + xs.push([key, value]); +}