molstar
Version:
A comprehensive macromolecular library.
434 lines (433 loc) • 21.9 kB
JavaScript
/**
* Copyright (c) 2019-2025 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>
* @author Gianluca Tomasello <giagitom@gmail.com>
*/
import { createNullRenderTarget } from '../../mol-gl/webgl/render-target';
import { ValueCell } from '../../mol-util';
import { Vec2 } from '../../mol-math/linear-algebra';
import { StereoCamera } from '../camera/stereo';
import { WboitPass } from './wboit';
import { DpoitPass } from './dpoit';
import { AntialiasingPass, PostprocessingPass } from './postprocessing';
import { MarkingPass } from './marking';
import { createCopyRenderable } from '../../mol-gl/compute/util';
import { isDebugMode, isTimingMode } from '../../mol-util/debug';
import { DofPass } from './dof';
import { BloomPass } from './bloom';
export class DrawPass {
setTransparency(transparency) {
if (transparency === 'wboit') {
this.transparencyMode = this.wboit.supported ? 'wboit' : 'blended';
if (isDebugMode && !this.wboit.supported) {
console.log('Missing "wboit" support, falling back to "blended".');
}
}
else if (transparency === 'dpoit') {
this.transparencyMode = this.dpoit.supported ? 'dpoit' : 'blended';
if (isDebugMode && !this.dpoit.supported) {
console.log('Missing "dpoit" support, falling back to "blended".');
}
}
else {
this.transparencyMode = 'blended';
}
this.depthTextureOpaque.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
}
get transparency() {
return this.transparencyMode;
}
constructor(webgl, assetManager, width, height, transparency) {
this.webgl = webgl;
this.transparencyMode = 'blended';
const { extensions, resources, isWebGL2 } = webgl;
this.drawTarget = createNullRenderTarget(webgl.gl);
this.colorTarget = webgl.createRenderTarget(width, height, true, 'uint8', 'linear');
this.transparentColorTarget = webgl.createRenderTarget(width, height, false, 'uint8', 'linear');
this.packedDepth = !extensions.depthTexture;
this.depthTargetTransparent = webgl.createRenderTarget(width, height);
this.depthTextureTransparent = this.depthTargetTransparent.texture;
this.depthTargetOpaque = this.packedDepth ? webgl.createRenderTarget(width, height) : null;
this.depthTextureOpaque = this.depthTargetOpaque ? this.depthTargetOpaque.texture : resources.texture('image-depth', 'depth', isWebGL2 ? 'float' : 'ushort', 'nearest');
if (!this.packedDepth) {
this.depthTextureOpaque.define(width, height);
}
this.wboit = new WboitPass(webgl, width, height);
this.dpoit = new DpoitPass(webgl, width, height);
this.marking = new MarkingPass(webgl, width, height);
this.postprocessing = new PostprocessingPass(webgl, assetManager, this);
this.antialiasing = new AntialiasingPass(webgl, width, height);
this.bloom = new BloomPass(webgl, width, height);
this.dof = new DofPass(webgl, width, height);
this.copyFboTarget = createCopyRenderable(webgl, this.colorTarget.texture);
this.copyFboPostprocessing = createCopyRenderable(webgl, this.postprocessing.target.texture);
this.setTransparency(transparency);
}
reset() {
this.wboit.reset();
this.dpoit.reset();
this.postprocessing.reset();
}
setSize(width, height) {
const w = this.colorTarget.getWidth();
const h = this.colorTarget.getHeight();
if (width !== w || height !== h) {
this.colorTarget.setSize(width, height);
this.depthTargetTransparent.setSize(width, height);
this.transparentColorTarget.setSize(width, height);
if (this.depthTargetOpaque) {
this.depthTargetOpaque.setSize(width, height);
}
else {
this.depthTextureOpaque.define(width, height);
}
ValueCell.update(this.copyFboTarget.values.uTexSize, Vec2.set(this.copyFboTarget.values.uTexSize.ref.value, width, height));
ValueCell.update(this.copyFboPostprocessing.values.uTexSize, Vec2.set(this.copyFboPostprocessing.values.uTexSize.ref.value, width, height));
}
if (this.wboit.supported) {
this.wboit.setSize(width, height);
}
if (this.dpoit.supported) {
this.dpoit.setSize(width, height);
}
this.marking.setSize(width, height);
this.postprocessing.setSize(width, height);
this.antialiasing.setSize(width, height);
this.dof.setSize(width, height);
this.bloom.setSize(width, height);
}
_renderDpoit(renderer, camera, scene, iterations, transparentBackground, postprocessingProps) {
if (!this.dpoit.supported)
throw new Error('expected dpoit to be supported');
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
renderer.clear(true);
// render opaque primitives
if (scene.hasOpaque) {
renderer.renderOpaque(scene.primitives, camera);
}
this.depthTextureOpaque.detachFramebuffer(this.colorTarget.framebuffer, 'depth');
if (PostprocessingPass.isTransparentDepthRequired(scene, postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
}
}
// render transparent primitives
const isPostprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
if (scene.opacityAverage < 1) {
const target = isPostprocessingEnabled ? this.transparentColorTarget : this.colorTarget;
if (isPostprocessingEnabled) {
target.bind();
renderer.clear(false, false, true);
}
const dpoitTextures = this.dpoit.bind();
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
for (let i = 0; i < iterations; i++) {
if (isTimingMode)
this.webgl.timer.mark('DpoitPass.layer');
const dpoitTextures = this.dpoit.bindDualDepthPeeling();
renderer.renderDpoitTransparent(scene.primitives, camera, this.depthTextureOpaque, dpoitTextures);
target.bind();
this.dpoit.renderBlendBack();
if (isTimingMode)
this.webgl.timer.markEnd('DpoitPass.layer');
}
// evaluate dpoit
target.bind();
this.dpoit.render();
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.render(camera, scene, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light, renderer.ambientColor);
}
// render transparent volumes
if (scene.volumes.renderables.length > 0) {
renderer.renderVolume(scene.volumes, camera, this.depthTextureOpaque);
}
}
_renderWboit(renderer, camera, scene, transparentBackground, postprocessingProps) {
if (!this.wboit.supported)
throw new Error('expected wboit to be supported');
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
renderer.clear(true);
// render opaque primitives
if (scene.hasOpaque) {
renderer.renderOpaque(scene.primitives, camera);
}
if (PostprocessingPass.isTransparentDepthRequired(scene, postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
}
}
// render transparent primitives
const isPostprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
if (scene.opacityAverage < 1) {
const target = isPostprocessingEnabled ? this.transparentColorTarget : this.colorTarget;
if (isPostprocessingEnabled) {
target.bind();
renderer.clear(false, false, true);
}
this.wboit.bind();
renderer.renderWboitTransparent(scene.primitives, camera, this.depthTextureOpaque);
// evaluate wboit
target.bind();
this.wboit.render();
}
if (PostprocessingPass.isEnabled(postprocessingProps)) {
this.postprocessing.render(camera, scene, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light, renderer.ambientColor);
}
// render volumes
if (scene.volumes.renderables.length > 0) {
this.wboit.bind();
renderer.renderWboitTransparent(scene.volumes, camera, this.depthTextureOpaque);
// evaluate wboit
const target = isPostprocessingEnabled ? this.postprocessing.target : this.colorTarget;
target.bind();
this.wboit.render();
}
}
_renderBlended(renderer, camera, scene, toDrawingBuffer, transparentBackground, postprocessingProps) {
var _a, _b, _c, _d, _e, _f;
if (toDrawingBuffer) {
this.drawTarget.bind();
}
else {
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.colorTarget.framebuffer, 'depth');
}
else {
this.colorTarget.bind();
}
}
renderer.clear(true);
if (scene.hasOpaque) {
renderer.renderOpaque(scene.primitives, camera);
}
if (!toDrawingBuffer) {
// do a depth pass if not rendering to drawing buffer and
// extensions.depthTexture is unsupported (i.e. depthTarget is set)
if (this.depthTargetOpaque) {
this.depthTargetOpaque.bind();
renderer.clearDepth(true);
renderer.renderDepthOpaque(scene.primitives, camera);
this.colorTarget.bind();
}
if (PostprocessingPass.isTransparentDepthRequired(scene, postprocessingProps)) {
this.depthTargetTransparent.bind();
renderer.clearDepth(true);
if (scene.opacityAverage < 1) {
renderer.renderDepthTransparent(scene.primitives, camera, this.depthTextureOpaque);
}
}
// render transparent primitives
const isPostprocessingEnabled = PostprocessingPass.isEnabled(postprocessingProps);
if (scene.opacityAverage < 1) {
if (isPostprocessingEnabled) {
this.transparentColorTarget.bind();
renderer.clear(false, false, true);
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.transparentColorTarget.framebuffer, 'depth');
}
else {
(_a = this.colorTarget.depthRenderbuffer) === null || _a === void 0 ? void 0 : _a.attachFramebuffer(this.transparentColorTarget.framebuffer);
}
}
renderer.renderBlendedTransparent(scene.primitives, camera);
if (isPostprocessingEnabled) {
if (!this.packedDepth) {
this.depthTextureOpaque.detachFramebuffer(this.transparentColorTarget.framebuffer, 'depth');
}
else {
(_b = this.colorTarget.depthRenderbuffer) === null || _b === void 0 ? void 0 : _b.detachFramebuffer(this.transparentColorTarget.framebuffer);
}
}
}
if (isPostprocessingEnabled) {
if (!this.packedDepth) {
this.depthTextureOpaque.detachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
}
else {
(_c = this.colorTarget.depthRenderbuffer) === null || _c === void 0 ? void 0 : _c.detachFramebuffer(this.postprocessing.target.framebuffer);
}
this.postprocessing.render(camera, scene, false, transparentBackground, renderer.props.backgroundColor, postprocessingProps, renderer.light, renderer.ambientColor);
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(this.postprocessing.target.framebuffer, 'depth');
}
else {
(_d = this.colorTarget.depthRenderbuffer) === null || _d === void 0 ? void 0 : _d.attachFramebuffer(this.postprocessing.target.framebuffer);
}
}
if (scene.volumes.renderables.length > 0) {
const target = PostprocessingPass.isEnabled(postprocessingProps)
? this.postprocessing.target : this.colorTarget;
if (!this.packedDepth) {
this.depthTextureOpaque.detachFramebuffer(target.framebuffer, 'depth');
}
else {
(_e = this.colorTarget.depthRenderbuffer) === null || _e === void 0 ? void 0 : _e.detachFramebuffer(target.framebuffer);
}
target.bind();
renderer.renderVolume(scene.volumes, camera, this.depthTextureOpaque);
if (!this.packedDepth) {
this.depthTextureOpaque.attachFramebuffer(target.framebuffer, 'depth');
}
else {
(_f = this.colorTarget.depthRenderbuffer) === null || _f === void 0 ? void 0 : _f.attachFramebuffer(target.framebuffer);
}
target.bind();
}
}
else if (scene.opacityAverage < 1) {
renderer.renderBlendedTransparent(scene.primitives, camera);
}
}
_render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props) {
var _a, _b;
const volumeRendering = scene.volumes.renderables.length > 0;
const postprocessingEnabled = PostprocessingPass.isEnabled(props.postprocessing);
const antialiasingEnabled = AntialiasingPass.isEnabled(props.postprocessing);
const markingEnabled = MarkingPass.isEnabled(props.marking);
const dofEnabled = DofPass.isEnabled(props.postprocessing);
const { x, y, width, height } = camera.viewport;
renderer.setViewport(x, y, width, height);
renderer.update(camera, scene);
if (transparentBackground && !antialiasingEnabled && toDrawingBuffer) {
this.drawTarget.bind();
renderer.clear(false);
}
let oitEnabled = false;
if (this.transparencyMode === 'wboit' && this.wboit.supported) {
this._renderWboit(renderer, camera, scene, transparentBackground, props.postprocessing);
oitEnabled = true;
}
else if (this.transparencyMode === 'dpoit' && this.dpoit.supported) {
this._renderDpoit(renderer, camera, scene, props.dpoitIterations, transparentBackground, props.postprocessing);
oitEnabled = true;
}
else {
this._renderBlended(renderer, camera, scene, !volumeRendering && !postprocessingEnabled && !antialiasingEnabled && toDrawingBuffer, transparentBackground, props.postprocessing);
}
const target = postprocessingEnabled
? this.postprocessing.target
: !toDrawingBuffer || volumeRendering || oitEnabled
? this.colorTarget
: this.drawTarget;
if (markingEnabled && scene.markerAverage > 0) {
const markingDepthTest = props.marking.ghostEdgeStrength < 1;
if (markingDepthTest && scene.markerAverage !== 1) {
this.marking.depthTarget.bind();
renderer.clear(false, true);
renderer.renderMarkingDepth(scene.primitives, camera);
}
this.marking.maskTarget.bind();
renderer.clear(false, true);
renderer.renderMarkingMask(scene.primitives, camera, markingDepthTest ? this.marking.depthTarget.texture : null);
this.marking.update(props.marking);
this.marking.render(camera.viewport, target);
}
else {
target.bind();
}
if (helper.debug.isEnabled) {
helper.debug.syncVisibility();
renderer.renderBlended(helper.debug.scene, camera);
}
if (helper.handle.isEnabled) {
renderer.renderBlended(helper.handle.scene, camera);
}
if (helper.camera.isEnabled) {
helper.camera.update(camera);
renderer.update(helper.camera.camera, helper.camera.scene);
renderer.renderBlended(helper.camera.scene, helper.camera.camera);
}
let needsTargetCopy = false;
if (antialiasingEnabled) {
const input = PostprocessingPass.isEnabled(props.postprocessing)
? this.postprocessing.target.texture
: this.colorTarget.texture;
this.antialiasing.render(camera, input, toDrawingBuffer && !dofEnabled, props.postprocessing);
}
else if (toDrawingBuffer && !DofPass.isEnabled(props.postprocessing)) {
needsTargetCopy = true;
}
if (props.postprocessing.dof.name === 'on') {
const input = AntialiasingPass.isEnabled(props.postprocessing)
? this.antialiasing.target.texture
: PostprocessingPass.isEnabled(props.postprocessing)
? this.postprocessing.target.texture
: this.colorTarget.texture;
this.dof.update(camera, input, ((_a = this.depthTargetOpaque) === null || _a === void 0 ? void 0 : _a.texture) || this.depthTextureOpaque, this.depthTextureTransparent, props.postprocessing.dof.params, scene.boundingSphereVisible);
this.dof.render(camera.viewport, toDrawingBuffer ? undefined : this.getColorTarget(props.postprocessing));
}
else if (toDrawingBuffer && !AntialiasingPass.isEnabled(props.postprocessing)) {
needsTargetCopy = true;
}
if (needsTargetCopy) {
this.drawTarget.bind();
this.webgl.state.disable(this.webgl.gl.DEPTH_TEST);
if (postprocessingEnabled) {
this.copyFboPostprocessing.render();
}
else if (volumeRendering || oitEnabled) {
this.copyFboTarget.render();
}
}
if (props.postprocessing.bloom.name === 'on') {
const emissiveBloom = props.postprocessing.bloom.params.mode === 'emissive';
if (emissiveBloom && scene.emissiveAverage > 0) {
this.bloom.emissiveTarget.bind();
renderer.clear(false, true);
renderer.update(camera, scene);
renderer.renderEmissive(scene.primitives, camera);
}
if (!emissiveBloom || scene.emissiveAverage > 0) {
this.bloom.update(this.colorTarget.texture, this.bloom.emissiveTarget.texture, ((_b = this.depthTargetOpaque) === null || _b === void 0 ? void 0 : _b.texture) || this.depthTextureOpaque, props.postprocessing.bloom.params);
this.bloom.render(camera.viewport, toDrawingBuffer ? undefined : this.getColorTarget(props.postprocessing));
}
}
this.webgl.gl.flush();
}
render(ctx, props, toDrawingBuffer) {
if (isTimingMode)
this.webgl.timer.mark('DrawPass.render');
const { renderer, camera, scene, helper } = ctx;
this.postprocessing.setTransparentBackground(props.transparentBackground);
const transparentBackground = props.transparentBackground || this.postprocessing.background.isEnabled(props.postprocessing.background);
renderer.setTransparentBackground(transparentBackground);
renderer.setDrawingBufferSize(this.colorTarget.getWidth(), this.colorTarget.getHeight());
renderer.setPixelRatio(this.webgl.pixelRatio);
if (StereoCamera.is(camera)) {
if (isTimingMode)
this.webgl.timer.mark('StereoCamera.left');
this._render(renderer, camera.left, scene, helper, toDrawingBuffer, transparentBackground, props);
if (isTimingMode)
this.webgl.timer.markEnd('StereoCamera.left');
if (isTimingMode)
this.webgl.timer.mark('StereoCamera.right');
this._render(renderer, camera.right, scene, helper, toDrawingBuffer, transparentBackground, props);
if (isTimingMode)
this.webgl.timer.markEnd('StereoCamera.right');
}
else {
this._render(renderer, camera, scene, helper, toDrawingBuffer, transparentBackground, props);
}
if (isTimingMode)
this.webgl.timer.markEnd('DrawPass.render');
}
getColorTarget(postprocessingProps) {
if (DofPass.isEnabled(postprocessingProps)) {
return this.dof.target;
}
else if (AntialiasingPass.isEnabled(postprocessingProps)) {
return this.antialiasing.target;
}
else if (PostprocessingPass.isEnabled(postprocessingProps)) {
return this.postprocessing.target;
}
return this.colorTarget;
}
}