diff --git a/CHANGELOG.md b/CHANGELOG.md index a1f2da5d5ed941a4a87452a51edb463caf6de12a..4a743e0de754c689f787abc7236a98877fd41b43 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ Note that since we don't clearly distinguish between a public and private interf - Fix issues with marking camera/handle helper (#433) - Fix issues with array uniforms when running with headless-gl - Fix Polymer Chain Instance coloring +- Improve performance of scene marker/opacity average calculation ## [v3.8.0] - 2022-04-30 diff --git a/src/mol-canvas3d/passes/draw.ts b/src/mol-canvas3d/passes/draw.ts index 3c481dfd5d29e43e2669d6499467b8b180b4e9b0..407e5814b373832ff063466972b576444e7ee4ef 100644 --- a/src/mol-canvas3d/passes/draw.ts +++ b/src/mol-canvas3d/passes/draw.ts @@ -38,15 +38,13 @@ export class DrawPass { private readonly drawTarget: RenderTarget; readonly colorTarget: RenderTarget; - readonly depthTexture: Texture; - readonly depthTexturePrimitives: Texture; + readonly depthTextureTransparent: Texture; + readonly depthTextureOpaque: Texture; readonly packedDepth: boolean; - private depthTarget: RenderTarget; - private depthTargetPrimitives: RenderTarget | null; - private depthTargetVolumes: RenderTarget | null; - private depthTextureVolumes: Texture; + private depthTargetTransparent: RenderTarget; + private depthTargetOpaque: RenderTarget | null; private copyFboTarget: CopyRenderable; private copyFboPostprocessing: CopyRenderable; @@ -68,17 +66,14 @@ export class DrawPass { this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear'); this.packedDepth = !extensions.depthTexture; - this.depthTarget = webgl.createRenderTarget(width, height); - this.depthTexture = this.depthTarget.texture; + this.depthTargetTransparent = webgl.createRenderTarget(width, height); + this.depthTextureTransparent = this.depthTargetTransparent.texture; - this.depthTargetPrimitives = this.packedDepth ? webgl.createRenderTarget(width, height) : null; - this.depthTargetVolumes = this.packedDepth ? webgl.createRenderTarget(width, height) : null; + this.depthTargetOpaque = this.packedDepth ? webgl.createRenderTarget(width, height) : null; - this.depthTexturePrimitives = this.depthTargetPrimitives ? this.depthTargetPrimitives.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest'); - this.depthTextureVolumes = this.depthTargetVolumes ? this.depthTargetVolumes.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest'); + this.depthTextureOpaque = this.depthTargetOpaque ? this.depthTargetOpaque.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest'); if (!this.packedDepth) { - this.depthTexturePrimitives.define(width, height); - this.depthTextureVolumes.define(width, height); + this.depthTextureOpaque.define(width, height); } this.wboit = enableWboit ? new WboitPass(webgl, width, height) : undefined; @@ -100,18 +95,12 @@ export class DrawPass { if (width !== w || height !== h) { this.colorTarget.setSize(width, height); - this.depthTarget.setSize(width, height); + this.depthTargetTransparent.setSize(width, height); - if (this.depthTargetPrimitives) { - this.depthTargetPrimitives.setSize(width, height); + if (this.depthTargetOpaque) { + this.depthTargetOpaque.setSize(width, height); } else { - this.depthTexturePrimitives.define(width, height); - } - - if (this.depthTargetVolumes) { - this.depthTargetVolumes.setSize(width, height); - } else { - this.depthTextureVolumes.define(width, height); + this.depthTextureOpaque.define(width, height); } ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height)); @@ -134,17 +123,17 @@ export class DrawPass { renderer.clear(true); // render opaque primitives - this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); this.colorTarget.bind(); renderer.clearDepth(); renderer.renderWboitOpaque(scene.primitives, camera, null); if (PostprocessingPass.isEnabled(postprocessingProps)) { if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) { - this.depthTarget.bind(); + this.depthTargetTransparent.bind(); renderer.clearDepth(true); - if (scene.getOpacityAverage() < 1) { - renderer.renderDepthTransparent(scene.primitives, camera, this.depthTexturePrimitives); + if (scene.opacityAverage < 1) { + renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque); } } @@ -152,17 +141,19 @@ export class DrawPass { } // render transparent primitives and volumes - this.wboit.bind(); - renderer.renderWboitTransparent(scene.primitives, camera, this.depthTexturePrimitives); - renderer.renderWboitTransparent(scene.volumes, camera, this.depthTexturePrimitives); + if (scene.opacityAverage < 1 || scene.volumes.renderables.length > 0) { + this.wboit.bind(); + renderer.renderWboitTransparent(scene.primitives, camera, this.depthTextureOpaque); + renderer.renderWboitTransparent(scene.volumes, camera, this.depthTextureOpaque); - // evaluate wboit - if (PostprocessingPass.isEnabled(postprocessingProps)) { - this.postprocessing.target.bind(); - } else { - this.colorTarget.bind(); + // evaluate wboit + if (PostprocessingPass.isEnabled(postprocessingProps)) { + this.postprocessing.target.bind(); + } else { + this.colorTarget.bind(); + } + this.wboit.render(); } - this.wboit.render(); } private _renderBlended(renderer: Renderer, camera: ICamera, scene: Scene, toDrawingBuffer: boolean, transparentBackground: boolean, postprocessingProps: PostprocessingProps) { @@ -171,7 +162,7 @@ export class DrawPass { } else { this.colorTarget.bind(); if (!this.packedDepth) { - this.depthTexturePrimitives.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); + this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth'); } } @@ -181,8 +172,8 @@ export class DrawPass { if (!toDrawingBuffer) { // do a depth pass if not rendering to drawing buffer and // extensions.depthTexture is unsupported (i.e. depthTarget is set) - if (this.depthTargetPrimitives) { - this.depthTargetPrimitives.bind(); + if (this.depthTargetOpaque) { + this.depthTargetOpaque.bind(); renderer.clearDepth(true); renderer.renderDepthOpaque(scene.primitives, camera, null); this.colorTarget.bind(); @@ -190,29 +181,29 @@ export class DrawPass { if (PostprocessingPass.isEnabled(postprocessingProps)) { if (!this.packedDepth) { - this.depthTexturePrimitives.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth'); + this.depthTextureOpaque.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth'); } else { this.colorTarget.depthRenderbuffer?.detachFramebuffer(this.postprocessing.target.framebuffer); } if (PostprocessingPass.isOutlineEnabled(postprocessingProps)) { - this.depthTarget.bind(); + this.depthTargetTransparent.bind(); renderer.clearDepth(true); - if (scene.getOpacityAverage() < 1) { - renderer.renderDepthTransparent(scene.primitives, camera, this.depthTexturePrimitives); + if (scene.opacityAverage < 1) { + renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque); } } this.postprocessing.render(camera, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps); if (!this.packedDepth) { - this.depthTexturePrimitives.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth'); + this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth'); } else { this.colorTarget.depthRenderbuffer?.attachFramebuffer(this.postprocessing.target.framebuffer); } } - renderer.renderBlendedVolume(scene.volumes, camera, this.depthTexturePrimitives); + renderer.renderBlendedVolume(scene.volumes, camera, this.depthTextureOpaque); } renderer.renderBlendedTransparent(scene.primitives, camera, null); @@ -248,10 +239,9 @@ export class DrawPass { } if (markingEnabled) { - const markerAverage = scene.getMarkerAverage(); - if (markerAverage > 0) { + if (scene.markerAverage > 0) { const markingDepthTest = props.marking.ghostEdgeStrength < 1; - if (markingDepthTest && markerAverage !== 1) { + if (markingDepthTest && scene.markerAverage !== 1) { this.marking.depthTarget.bind(); renderer.clear(false, true); renderer.renderMarkingDepth(scene.primitives, camera, null); diff --git a/src/mol-canvas3d/passes/pick.ts b/src/mol-canvas3d/passes/pick.ts index d5777f84f15e0cfecb7eb1b89611830a2f577c3b..a8d0488efd8f371d09be32fc802743354c96ee6e 100644 --- a/src/mol-canvas3d/passes/pick.ts +++ b/src/mol-canvas3d/passes/pick.ts @@ -64,12 +64,10 @@ export class PickPass { } private renderVariant(renderer: Renderer, camera: ICamera, scene: Scene, helper: Helper, variant: 'pick' | 'depth', pickType: number) { - const depth = this.drawPass.depthTexturePrimitives; renderer.clear(false); renderer.update(camera); renderer.renderPick(scene.primitives, camera, variant, null, pickType); - renderer.renderPick(scene.volumes, camera, variant, depth, pickType); renderer.renderPick(helper.handle.scene, camera, variant, null, pickType); if (helper.camera.isEnabled) { diff --git a/src/mol-canvas3d/passes/postprocessing.ts b/src/mol-canvas3d/passes/postprocessing.ts index 437c802dbb46f959858d62fdc0da8f1c6d67052b..53e69da6718fb60364ef82866c28d2b3f9145f60 100644 --- a/src/mol-canvas3d/passes/postprocessing.ts +++ b/src/mol-canvas3d/passes/postprocessing.ts @@ -318,7 +318,7 @@ export class PostprocessingPass { } constructor(private webgl: WebGLContext, private drawPass: DrawPass) { - const { colorTarget, depthTexture: depthTextureTransparent, depthTexturePrimitives: depthTextureOpaque } = drawPass; + const { colorTarget, depthTextureTransparent, depthTextureOpaque } = drawPass; const width = colorTarget.getWidth(); const height = colorTarget.getHeight(); @@ -471,7 +471,7 @@ export class PostprocessingPass { this.ssaoDepthBlurProxyTexture.define(sw, sh); if (this.ssaoScale === 1) { - ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTexture); + ValueCell.update(this.ssaoRenderable.values.tDepth, this.drawPass.depthTextureTransparent); } else { ValueCell.update(this.ssaoRenderable.values.tDepth, this.downsampledDepthTarget.texture); } diff --git a/src/mol-gl/scene.ts b/src/mol-gl/scene.ts index ba1349f162097e36d1d6c70d6aca24795d9055c6..e0beca46d504df6e2cc722966c917068b9a2b249 100644 --- a/src/mol-gl/scene.ts +++ b/src/mol-gl/scene.ts @@ -80,8 +80,8 @@ interface Scene extends Object3D { has: (o: GraphicsRenderObject) => boolean clear: () => void forEach: (callbackFn: (value: GraphicsRenderable, key: GraphicsRenderObject) => void) => void - getMarkerAverage: () => number - getOpacityAverage: () => number + readonly markerAverage: number + readonly opacityAverage: number } namespace Scene { @@ -101,6 +101,9 @@ namespace Scene { let boundingSphereDirty = true; let boundingSphereVisibleDirty = true; + let markerAverage = 0; + let opacityAverage = 0; + const object3d = Object3D.create(); const { view, position, direction, up } = object3d; @@ -157,6 +160,7 @@ namespace Scene { } renderables.sort(renderableSort); + opacityAverage = calculateOpacityAverage(); return true; } @@ -178,12 +182,41 @@ namespace Scene { const newVisibleHash = computeVisibleHash(); if (newVisibleHash !== visibleHash) { boundingSphereVisibleDirty = true; + opacityAverage = calculateOpacityAverage(); return true; } else { return false; } } + function calculateMarkerAverage() { + if (primitives.length === 0) return 0; + let count = 0; + let markerAverage = 0; + for (let i = 0, il = primitives.length; i < il; ++i) { + if (!primitives[i].state.visible) continue; + markerAverage += primitives[i].values.markerAverage.ref.value; + count += 1; + } + return count > 0 ? markerAverage / count : 0; + } + + function calculateOpacityAverage() { + if (primitives.length === 0) return 0; + let count = 0; + let opacityAverage = 0; + for (let i = 0, il = primitives.length; i < il; ++i) { + const p = primitives[i]; + if (!p.state.visible) continue; + // TODO: simplify, handle in renderable.state??? + // uAlpha is updated in "render" so we need to recompute it here + const alpha = clamp(p.values.alpha.ref.value * p.state.alphaFactor, 0, 1); + opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha; + count += 1; + } + return count > 0 ? opacityAverage / count : 0; + } + return { view, position, direction, up, @@ -209,6 +242,8 @@ namespace Scene { } else { syncVisibility(); } + markerAverage = calculateMarkerAverage(); + opacityAverage = calculateOpacityAverage(); }, add: (o: GraphicsRenderObject) => commitQueue.add(o), remove: (o: GraphicsRenderObject) => commitQueue.remove(o), @@ -247,37 +282,11 @@ namespace Scene { } return boundingSphereVisible; }, - getMarkerAverage() { - if (primitives.length === 0 && volumes.length === 0) return 0; - let count = 0; - let markerAverage = 0; - for (let i = 0, il = primitives.length; i < il; ++i) { - if (!primitives[i].state.visible) continue; - markerAverage += primitives[i].values.markerAverage.ref.value; - count += 1; - } - for (let i = 0, il = volumes.length; i < il; ++i) { - if (!volumes[i].state.visible) continue; - markerAverage += volumes[i].values.markerAverage.ref.value; - count += 1; - } - return count > 0 ? markerAverage / count : 0; + get markerAverage() { + return markerAverage; }, - getOpacityAverage() { - if (primitives.length === 0) return 0; - let count = 0; - let opacityAverage = 0; - for (let i = 0, il = primitives.length; i < il; ++i) { - const p = primitives[i]; - if (!p.state.visible) continue; - - // TODO: simplify, handle in renderable.state??? - // uAlpha is updated in "render" so we need to recompute it here - const alpha = clamp(p.values.alpha.ref.value * p.state.alphaFactor, 0, 1); - opacityAverage += (1 - p.values.transparencyAverage.ref.value) * alpha; - count += 1; - } - return count > 0 ? opacityAverage / count : 0; + get opacityAverage() { + return opacityAverage; }, }; }