UNPKG

@itwin/core-frontend

Version:
899 lines • 122 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Bentley Systems, Incorporated. All rights reserved. * See LICENSE.md in the project root for license terms and full copyright notice. *--------------------------------------------------------------------------------------------*/ /** @packageDocumentation * @module WebGL */ import { assert, dispose } from "@itwin/core-bentley"; import { Transform, Vector2d, Vector3d } from "@itwin/core-geometry"; import { ModelFeature, RenderMode, SpatialClassifierInsideDisplay, SpatialClassifierOutsideDisplay, } from "@itwin/core-common"; import { RenderType } from "@itwin/webgl-compatibility"; import { Pixel } from "../../../render/Pixel"; import { BranchState } from "./BranchState"; import { AmbientOcclusionGeometry, BlurGeometry, BlurType, BoundaryType, CompositeGeometry, CopyPickBufferGeometry, SingleTexturedViewportQuadGeometry, ViewportQuadGeometry, VolumeClassifierGeometry, } from "./CachedGeometry"; import { Debug } from "./Diagnostics"; import { extractFlashedVolumeClassifierCommands, extractHilitedVolumeClassifierCommands } from "./DrawCommand"; import { FrameBuffer } from "./FrameBuffer"; import { GL } from "./GL"; import { IModelFrameLifecycle } from "./IModelFrameLifecycle"; import { Matrix4 } from "./Matrix"; import { TextureUnit } from "./RenderFlags"; import { RenderState } from "./RenderState"; import { getDrawParams } from "./ScratchDrawParams"; import { SolarShadowMap } from "./SolarShadowMap"; import { System } from "./System"; import { TextureHandle } from "./Texture"; import { RenderBufferMultiSample } from "./RenderBuffer"; import { EDLMode, EyeDomeLighting } from "./EDL"; export function collectTextureStatistics(texture, stats) { if (undefined !== texture) stats.addTextureAttachment(texture.bytesUsed); } function collectMsBufferStatistics(msBuff, stats) { if (undefined !== msBuff) stats.addTextureAttachment(msBuff.bytesUsed); } // Maintains the textures used by a SceneCompositor. The textures are reallocated when the dimensions of the viewport change. class Textures { accumulation; revealage; color; featureId; depthAndOrder; depthAndOrderHidden; // only used if AO and multisampling contours; contoursMsBuff; hilite; occlusion; occlusionBlur; volClassBlend; colorMsBuff; featureIdMsBuff; featureIdMsBuffHidden; depthAndOrderMsBuff; depthAndOrderMsBuffHidden; hiliteMsBuff; volClassBlendMsBuff; get isDisposed() { return undefined === this.accumulation && undefined === this.revealage && undefined === this.color && undefined === this.featureId && undefined === this.depthAndOrder && undefined === this.contours && undefined === this.contoursMsBuff && undefined === this.depthAndOrderHidden && undefined === this.hilite && undefined === this.occlusion && undefined === this.occlusionBlur && undefined === this.volClassBlend && undefined === this.colorMsBuff && undefined === this.featureIdMsBuff && undefined === this.featureIdMsBuffHidden && undefined === this.depthAndOrderMsBuff && undefined === this.depthAndOrderMsBuffHidden && undefined === this.hiliteMsBuff && undefined === this.volClassBlendMsBuff; } [Symbol.dispose]() { this.accumulation = dispose(this.accumulation); this.revealage = dispose(this.revealage); this.color = dispose(this.color); this.featureId = dispose(this.featureId); this.depthAndOrder = dispose(this.depthAndOrder); this.contours = dispose(this.contours); this.contoursMsBuff = dispose(this.contoursMsBuff); this.depthAndOrderHidden = dispose(this.depthAndOrderHidden); this.hilite = dispose(this.hilite); this.occlusion = dispose(this.occlusion); this.occlusionBlur = dispose(this.occlusionBlur); this.colorMsBuff = dispose(this.colorMsBuff); this.featureIdMsBuff = dispose(this.featureIdMsBuff); this.featureIdMsBuffHidden = dispose(this.featureIdMsBuffHidden); this.depthAndOrderMsBuff = dispose(this.depthAndOrderMsBuff); this.depthAndOrderMsBuffHidden = dispose(this.depthAndOrderMsBuffHidden); this.hiliteMsBuff = dispose(this.hiliteMsBuff); this.volClassBlend = dispose(this.volClassBlend); this.volClassBlendMsBuff = dispose(this.volClassBlendMsBuff); } collectStatistics(stats) { collectTextureStatistics(this.accumulation, stats); collectTextureStatistics(this.revealage, stats); collectTextureStatistics(this.color, stats); collectTextureStatistics(this.featureId, stats); collectTextureStatistics(this.depthAndOrder, stats); collectTextureStatistics(this.contours, stats); collectMsBufferStatistics(this.contoursMsBuff, stats); collectTextureStatistics(this.depthAndOrderHidden, stats); collectTextureStatistics(this.hilite, stats); collectTextureStatistics(this.occlusion, stats); collectTextureStatistics(this.occlusionBlur, stats); collectTextureStatistics(this.volClassBlend, stats); collectMsBufferStatistics(this.colorMsBuff, stats); collectMsBufferStatistics(this.featureIdMsBuff, stats); collectMsBufferStatistics(this.featureIdMsBuffHidden, stats); collectMsBufferStatistics(this.depthAndOrderMsBuff, stats); collectMsBufferStatistics(this.depthAndOrderMsBuffHidden, stats); collectMsBufferStatistics(this.hiliteMsBuff, stats); collectMsBufferStatistics(this.volClassBlendMsBuff, stats); } init(width, height, numSamples) { assert(undefined === this.accumulation); let pixelDataType = GL.Texture.DataType.UnsignedByte; switch (System.instance.maxRenderType) { case RenderType.TextureFloat: { pixelDataType = GL.Texture.DataType.Float; break; } case RenderType.TextureHalfFloat: { pixelDataType = System.instance.context.HALF_FLOAT; break; } /* falls through */ case RenderType.TextureUnsignedByte: { break; } } // NB: Both of these must be of the same type, because they are borrowed by pingpong and bound to the same frame buffer. this.accumulation = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, pixelDataType); this.revealage = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, pixelDataType); // Hilite texture is a simple on-off, but the smallest texture format WebGL allows us to use as output is RGBA with a byte per component. this.hilite = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.color = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.featureId = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.depthAndOrder = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.contours = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); let rVal = undefined !== this.accumulation && undefined !== this.revealage && undefined !== this.color && undefined !== this.featureId && undefined !== this.depthAndOrder && undefined !== this.contours && undefined !== this.hilite; if (rVal && numSamples > 1) { rVal = this.enableMultiSampling(width, height, numSamples); } return rVal; } enableOcclusion(width, height, numSamples) { assert(undefined === this.occlusion && undefined === this.occlusionBlur); this.occlusion = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); this.occlusionBlur = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); let rVal = undefined !== this.occlusion && undefined !== this.occlusionBlur; if (numSamples > 1) { // If multisampling then we need a texture for storing depth and order for hidden edges. this.depthAndOrderHidden = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); rVal = rVal && undefined !== this.depthAndOrderHidden; } return rVal; } disableOcclusion() { assert(undefined !== this.occlusion && undefined !== this.occlusionBlur); this.occlusion = dispose(this.occlusion); this.occlusionBlur = dispose(this.occlusionBlur); this.depthAndOrderHidden = dispose(this.depthAndOrderHidden); } enableVolumeClassifier(width, height, numSamples) { assert(undefined === this.volClassBlend); this.volClassBlend = TextureHandle.createForAttachment(width, height, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte); let rVal = undefined !== this.volClassBlend; if (rVal && undefined !== numSamples && numSamples > 1) { this.volClassBlendMsBuff = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); rVal = undefined !== this.volClassBlendMsBuff; } return rVal; } disableVolumeClassifier() { this.volClassBlend = dispose(this.volClassBlend); this.volClassBlendMsBuff = dispose(this.volClassBlendMsBuff); } enableMultiSampling(width, height, numSamples) { this.colorMsBuff = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); this.featureIdMsBuff = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); this.featureIdMsBuffHidden = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); this.depthAndOrderMsBuff = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); this.depthAndOrderMsBuffHidden = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); this.contoursMsBuff = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); this.hiliteMsBuff = RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.RGBA8, numSamples); return undefined !== this.colorMsBuff && undefined !== this.featureIdMsBuff && undefined !== this.featureIdMsBuffHidden && undefined !== this.depthAndOrderMsBuff && undefined !== this.depthAndOrderMsBuffHidden && undefined !== this.contoursMsBuff && undefined !== this.hiliteMsBuff; } disableMultiSampling() { this.colorMsBuff = dispose(this.colorMsBuff); this.featureIdMsBuff = dispose(this.featureIdMsBuff); this.featureIdMsBuffHidden = dispose(this.featureIdMsBuffHidden); this.depthAndOrderMsBuff = dispose(this.depthAndOrderMsBuff); this.depthAndOrderMsBuffHidden = dispose(this.depthAndOrderMsBuffHidden); this.contoursMsBuff = dispose(this.contoursMsBuff); this.hiliteMsBuff = dispose(this.hiliteMsBuff); return true; } } // Maintains the framebuffers used by a SceneCompositor. The color attachments are supplied by a Textures object. class FrameBuffers { opaqueColor; opaqueAndCompositeColor; depthAndOrder; contours; hilite; hiliteUsingStencil; stencilSet; occlusion; occlusionBlur; altZOnly; volClassCreateBlend; volClassCreateBlendAltZ; opaqueAll; opaqueAndCompositeAll; opaqueAndCompositeAllHidden; pingPong; pingPongMS; translucent; clearTranslucent; idsAndZ; idsAndAltZ; idsAndZComposite; idsAndAltZComposite; edlDrawCol; init(textures, depth, depthMS) { if (!this.initPotentialMSFbos(textures, depth, depthMS)) return false; this.depthAndOrder = FrameBuffer.create([textures.depthAndOrder], depth); this.contours = FrameBuffer.create([textures.contours], depth); this.hilite = FrameBuffer.create([textures.hilite], depth); this.hiliteUsingStencil = FrameBuffer.create([textures.hilite], depth); if (!this.depthAndOrder || !this.contours || !this.hilite || !this.hiliteUsingStencil) return false; assert(undefined === this.opaqueAll); if (!this.initPotentialMSMRTFbos(textures, depth, depthMS)) return false; assert(undefined !== textures.accumulation && undefined !== textures.revealage); const colors = [textures.accumulation, textures.revealage]; this.translucent = FrameBuffer.create(colors, depth); this.clearTranslucent = FrameBuffer.create(colors); // We borrow the SceneCompositor's accum and revealage textures for the surface pass. // First we render edges, writing to our textures. // Then we copy our textures to borrowed textures. // Finally we render surfaces, writing to our textures and reading from borrowed textures. assert(undefined !== textures.accumulation && undefined !== textures.revealage); const pingPong = [textures.accumulation, textures.revealage]; this.pingPong = FrameBuffer.create(pingPong); return undefined !== this.translucent && undefined !== this.clearTranslucent && undefined !== this.pingPong; } initPotentialMSFbos(textures, depth, depthMS) { const boundColor = System.instance.frameBufferStack.currentColorBuffer; assert(undefined !== boundColor && undefined !== textures.color); if (undefined === depthMS) { this.opaqueColor = FrameBuffer.create([boundColor], depth); this.opaqueAndCompositeColor = FrameBuffer.create([textures.color], depth); } else { assert(undefined !== textures.colorMsBuff); this.opaqueColor = FrameBuffer.create([boundColor], depth, [textures.colorMsBuff], [GL.MultiSampling.Filter.Linear], depthMS); this.opaqueAndCompositeColor = FrameBuffer.create([textures.color], depth, [textures.colorMsBuff], [GL.MultiSampling.Filter.Linear], depthMS); } return undefined !== this.opaqueColor && undefined !== this.opaqueAndCompositeColor; } initPotentialMSMRTFbos(textures, depth, depthMs) { const boundColor = System.instance.frameBufferStack.currentColorBuffer; assert(undefined !== boundColor && undefined !== textures.color && undefined !== textures.featureId && undefined !== textures.depthAndOrder && undefined !== textures.contours && undefined !== textures.accumulation && undefined !== textures.revealage); const colorAndPick = [boundColor, textures.featureId, textures.depthAndOrder, textures.contours]; if (undefined === depthMs) { this.opaqueAll = FrameBuffer.create(colorAndPick, depth); colorAndPick[0] = textures.color; this.opaqueAndCompositeAll = FrameBuffer.create(colorAndPick, depth); } else { assert(undefined !== textures.colorMsBuff && undefined !== textures.featureIdMsBuff && undefined !== textures.featureIdMsBuffHidden && undefined !== textures.contoursMsBuff && undefined !== textures.depthAndOrderMsBuff && undefined !== textures.depthAndOrderMsBuffHidden); const colorAndPickMsBuffs = [textures.colorMsBuff, textures.featureIdMsBuff, textures.depthAndOrderMsBuff, textures.contoursMsBuff]; const colorAndPickFilters = [GL.MultiSampling.Filter.Linear, GL.MultiSampling.Filter.Nearest, GL.MultiSampling.Filter.Nearest, GL.MultiSampling.Filter.Nearest]; this.opaqueAll = FrameBuffer.create(colorAndPick, depth, colorAndPickMsBuffs, colorAndPickFilters, depthMs); colorAndPick[0] = textures.color; this.opaqueAndCompositeAll = FrameBuffer.create(colorAndPick, depth, colorAndPickMsBuffs, colorAndPickFilters, depthMs); } return undefined !== this.opaqueAll && undefined !== this.opaqueAndCompositeAll; } enableOcclusion(textures, depth, depthMs) { assert(undefined !== textures.occlusion && undefined !== textures.occlusionBlur); this.occlusion = FrameBuffer.create([textures.occlusion]); this.occlusionBlur = FrameBuffer.create([textures.occlusionBlur]); let rVal = undefined !== this.occlusion && undefined !== this.occlusionBlur; if (undefined === depthMs) { // If not using multisampling then we can use the accumulation and revealage textures for the hidden pick buffers, assert(undefined !== textures.color && undefined !== textures.accumulation && undefined !== textures.revealage); const colorAndPick = [textures.color, textures.accumulation, textures.revealage]; this.opaqueAndCompositeAllHidden = FrameBuffer.create(colorAndPick, depth); rVal = rVal && undefined !== this.opaqueAndCompositeAllHidden; } else { // If multisampling then we cannot use the revealage texture for depthAndOrder for the hidden edges since it is of the wrong type for blitting, // so instead use a special depthAndOrderHidden texture just for this purpose. // The featureId texture is not needed for hidden edges, so the accumulation texture can be used for it if we don't blit from the multisample bufffer into it. assert(undefined !== textures.color && undefined !== textures.accumulation && undefined !== textures.depthAndOrderHidden); assert(undefined !== textures.colorMsBuff && undefined !== textures.featureIdMsBuffHidden && undefined !== textures.depthAndOrderMsBuffHidden); const colorAndPick = [textures.color, textures.accumulation, textures.depthAndOrderHidden]; const colorAndPickMsBuffs = [textures.colorMsBuff, textures.featureIdMsBuffHidden, textures.depthAndOrderMsBuffHidden]; const colorAndPickFilters = [GL.MultiSampling.Filter.Linear, GL.MultiSampling.Filter.Nearest, GL.MultiSampling.Filter.Nearest]; this.opaqueAndCompositeAllHidden = FrameBuffer.create(colorAndPick, depth, colorAndPickMsBuffs, colorAndPickFilters, depthMs); // We will also need a frame buffer for copying the real pick data buffers into these hidden edge pick data buffers. const pingPong = [textures.accumulation, textures.depthAndOrderHidden]; const pingPongMSBuffs = [textures.featureIdMsBuffHidden, textures.depthAndOrderMsBuffHidden]; const pingPongFilters = [GL.MultiSampling.Filter.Nearest, GL.MultiSampling.Filter.Nearest]; this.pingPongMS = FrameBuffer.create(pingPong, depth, pingPongMSBuffs, pingPongFilters, depthMs); rVal = rVal && undefined !== this.opaqueAndCompositeAllHidden && (undefined === depthMs || undefined !== this.pingPongMS); } return rVal; } disableOcclusion() { if (undefined !== this.occlusion) { this.occlusion = dispose(this.occlusion); this.occlusionBlur = dispose(this.occlusionBlur); } this.opaqueAndCompositeAllHidden = dispose(this.opaqueAndCompositeAllHidden); this.pingPongMS = dispose(this.pingPongMS); } enableVolumeClassifier(textures, depth, volClassDepth, depthMS, volClassDepthMS) { const boundColor = System.instance.frameBufferStack.currentColorBuffer; if (undefined === boundColor) return; if (undefined === this.stencilSet) { if (undefined !== depthMS) { // if multisampling use the multisampled depth everywhere this.stencilSet = FrameBuffer.create([], depth, [], [], depthMS); this.altZOnly = FrameBuffer.create([], volClassDepth, [], [], volClassDepthMS); this.volClassCreateBlend = FrameBuffer.create([textures.volClassBlend], depth, [textures.volClassBlendMsBuff], [GL.MultiSampling.Filter.Nearest], depthMS); this.volClassCreateBlendAltZ = FrameBuffer.create([textures.volClassBlend], volClassDepth, [textures.volClassBlendMsBuff], [GL.MultiSampling.Filter.Nearest], volClassDepthMS); } else if (undefined !== volClassDepth) { this.stencilSet = FrameBuffer.create([], depth); this.altZOnly = FrameBuffer.create([], volClassDepth); this.volClassCreateBlend = FrameBuffer.create([textures.volClassBlend], depth); this.volClassCreateBlendAltZ = FrameBuffer.create([textures.volClassBlend], volClassDepth); } } if (undefined !== this.opaqueAll && undefined !== this.opaqueAndCompositeAll) { if (undefined !== volClassDepth) { let ids = [this.opaqueAll.getColor(0), this.opaqueAll.getColor(1)]; this.idsAndZ = FrameBuffer.create(ids, depth); this.idsAndAltZ = FrameBuffer.create(ids, volClassDepth); ids = [this.opaqueAndCompositeAll.getColor(0), this.opaqueAndCompositeAll.getColor(1)]; this.idsAndZComposite = FrameBuffer.create(ids, depth); this.idsAndAltZComposite = FrameBuffer.create(ids, volClassDepth); } } } disableVolumeClassifier() { if (undefined !== this.stencilSet) { this.stencilSet = dispose(this.stencilSet); this.altZOnly = dispose(this.altZOnly); this.volClassCreateBlend = dispose(this.volClassCreateBlend); this.volClassCreateBlendAltZ = dispose(this.volClassCreateBlendAltZ); } if (undefined !== this.idsAndZ) { this.idsAndZ = dispose(this.idsAndZ); this.idsAndAltZ = dispose(this.idsAndAltZ); this.idsAndZComposite = dispose(this.idsAndZComposite); this.idsAndAltZComposite = dispose(this.idsAndAltZComposite); } } enableMultiSampling(textures, depth, depthMS) { this.opaqueColor = dispose(this.opaqueColor); this.opaqueAndCompositeColor = dispose(this.opaqueAndCompositeColor); let rVal = this.initPotentialMSFbos(textures, depth, depthMS); this.opaqueAll = dispose(this.opaqueAll); this.opaqueAndCompositeAll = dispose(this.opaqueAndCompositeAll); rVal = this.initPotentialMSMRTFbos(textures, depth, depthMS); return rVal; } disableMultiSampling(textures, depth) { this.opaqueAll = dispose(this.opaqueAll); this.opaqueAndCompositeAll = dispose(this.opaqueAndCompositeAll); if (!this.initPotentialMSMRTFbos(textures, depth, undefined)) return false; this.opaqueColor = dispose(this.opaqueColor); this.opaqueAndCompositeColor = dispose(this.opaqueAndCompositeColor); return this.initPotentialMSFbos(textures, depth, undefined); } get isDisposed() { return undefined === this.opaqueColor && undefined === this.opaqueAndCompositeColor && undefined === this.depthAndOrder && undefined === this.contours && undefined === this.hilite && undefined === this.hiliteUsingStencil && undefined === this.occlusion && undefined === this.occlusionBlur && undefined === this.stencilSet && undefined === this.altZOnly && undefined === this.volClassCreateBlend && undefined === this.volClassCreateBlendAltZ && undefined === this.opaqueAll && undefined === this.opaqueAndCompositeAll && undefined === this.opaqueAndCompositeAllHidden && undefined === this.pingPong && undefined === this.pingPongMS && undefined === this.translucent && undefined === this.clearTranslucent && undefined === this.idsAndZ && undefined === this.idsAndAltZ && undefined === this.idsAndZComposite && undefined === this.idsAndAltZComposite && undefined === this.edlDrawCol; } [Symbol.dispose]() { this.opaqueColor = dispose(this.opaqueColor); this.opaqueAndCompositeColor = dispose(this.opaqueAndCompositeColor); this.depthAndOrder = dispose(this.depthAndOrder); this.contours = dispose(this.contours); this.hilite = dispose(this.hilite); this.hiliteUsingStencil = dispose(this.hiliteUsingStencil); this.occlusion = dispose(this.occlusion); this.occlusionBlur = dispose(this.occlusionBlur); this.stencilSet = dispose(this.stencilSet); this.altZOnly = dispose(this.altZOnly); this.volClassCreateBlend = dispose(this.volClassCreateBlend); this.volClassCreateBlendAltZ = dispose(this.volClassCreateBlendAltZ); this.opaqueAll = dispose(this.opaqueAll); this.opaqueAndCompositeAll = dispose(this.opaqueAndCompositeAll); this.opaqueAndCompositeAll = dispose(this.opaqueAndCompositeAllHidden); this.pingPong = dispose(this.pingPong); this.pingPongMS = dispose(this.pingPongMS); this.translucent = dispose(this.translucent); this.clearTranslucent = dispose(this.clearTranslucent); this.idsAndZ = dispose(this.idsAndZ); this.idsAndAltZ = dispose(this.idsAndAltZ); this.idsAndZComposite = dispose(this.idsAndZComposite); this.idsAndAltZComposite = dispose(this.idsAndAltZComposite); this.edlDrawCol = dispose(this.edlDrawCol); } } export function collectGeometryStatistics(geom, stats) { if (undefined !== geom) geom.collectStatistics(stats); } // Maintains the geometry used to execute screenspace operations for a SceneCompositor. class Geometry { composite; volClassColorStencil; volClassCopyZ; volClassSetBlend; volClassBlend; occlusion; occlusionXBlur; occlusionYBlur; copyPickBuffers; clearTranslucent; clearPickAndColor; collectStatistics(stats) { collectGeometryStatistics(this.composite, stats); collectGeometryStatistics(this.volClassColorStencil, stats); collectGeometryStatistics(this.volClassCopyZ, stats); collectGeometryStatistics(this.volClassSetBlend, stats); collectGeometryStatistics(this.volClassBlend, stats); collectGeometryStatistics(this.occlusion, stats); collectGeometryStatistics(this.occlusionXBlur, stats); collectGeometryStatistics(this.occlusionYBlur, stats); collectGeometryStatistics(this.copyPickBuffers, stats); collectGeometryStatistics(this.clearTranslucent, stats); collectGeometryStatistics(this.clearPickAndColor, stats); } init(textures) { assert(undefined === this.composite); this.composite = CompositeGeometry.createGeometry(textures.color.getHandle(), textures.accumulation.getHandle(), textures.revealage.getHandle(), textures.hilite.getHandle()); if (undefined === this.composite) return false; assert(undefined === this.copyPickBuffers); this.copyPickBuffers = CopyPickBufferGeometry.createGeometry(textures.featureId.getHandle(), textures.depthAndOrder.getHandle()); this.clearTranslucent = ViewportQuadGeometry.create(16 /* TechniqueId.OITClearTranslucent */); this.clearPickAndColor = ViewportQuadGeometry.create(21 /* TechniqueId.ClearPickAndColor */); return undefined !== this.copyPickBuffers && undefined !== this.clearTranslucent && undefined !== this.clearPickAndColor; } enableOcclusion(textures, depth) { assert(undefined !== textures.occlusion && undefined !== textures.occlusionBlur && undefined !== textures.depthAndOrder && undefined !== textures.occlusionBlur); this.composite.occlusion = textures.occlusion.getHandle(); this.occlusion = AmbientOcclusionGeometry.createGeometry(textures.depthAndOrder.getHandle(), depth.getHandle()); this.occlusionXBlur = BlurGeometry.createGeometry(textures.occlusion.getHandle(), textures.depthAndOrder.getHandle(), undefined, new Vector2d(1.0, 0.0), BlurType.NoTest); const depthAndOrderHidden = (undefined === textures.depthAndOrderHidden ? textures.revealage?.getHandle() : textures.depthAndOrderHidden.getHandle()); this.occlusionYBlur = BlurGeometry.createGeometry(textures.occlusionBlur.getHandle(), textures.depthAndOrder.getHandle(), depthAndOrderHidden, new Vector2d(0.0, 1.0), BlurType.TestOrder); } disableOcclusion() { this.composite.occlusion = undefined; this.occlusion = dispose(this.occlusion); this.occlusionXBlur = dispose(this.occlusionXBlur); this.occlusionYBlur = dispose(this.occlusionYBlur); } enableVolumeClassifier(textures, depth) { assert(undefined === this.volClassColorStencil && undefined === this.volClassCopyZ && undefined === this.volClassSetBlend && undefined === this.volClassBlend); this.volClassColorStencil = ViewportQuadGeometry.create(20 /* TechniqueId.VolClassColorUsingStencil */); this.volClassCopyZ = SingleTexturedViewportQuadGeometry.createGeometry(depth.getHandle(), 31 /* TechniqueId.VolClassCopyZ */); this.volClassSetBlend = VolumeClassifierGeometry.createVCGeometry(depth.getHandle()); this.volClassBlend = SingleTexturedViewportQuadGeometry.createGeometry(textures.volClassBlend.getHandle(), 33 /* TechniqueId.VolClassBlend */); return undefined !== this.volClassColorStencil && undefined !== this.volClassCopyZ && undefined !== this.volClassSetBlend && undefined !== this.volClassBlend; } disableVolumeClassifier() { if (undefined !== this.volClassColorStencil) { this.volClassColorStencil = dispose(this.volClassColorStencil); this.volClassCopyZ = dispose(this.volClassCopyZ); this.volClassSetBlend = dispose(this.volClassSetBlend); this.volClassBlend = dispose(this.volClassBlend); } } get isDisposed() { return undefined === this.composite && undefined === this.occlusion && undefined === this.occlusionXBlur && undefined === this.occlusionYBlur && undefined === this.volClassColorStencil && undefined === this.volClassCopyZ && undefined === this.volClassSetBlend && undefined === this.volClassBlend && undefined === this.copyPickBuffers && undefined === this.clearTranslucent && undefined === this.clearPickAndColor; } [Symbol.dispose]() { this.composite = dispose(this.composite); this.occlusion = dispose(this.occlusion); this.occlusionXBlur = dispose(this.occlusionXBlur); this.occlusionYBlur = dispose(this.occlusionYBlur); this.disableVolumeClassifier(); this.copyPickBuffers = dispose(this.copyPickBuffers); this.clearTranslucent = dispose(this.clearTranslucent); this.clearPickAndColor = dispose(this.clearPickAndColor); } } // Represents a view of data read from a region of the frame buffer. class PixelBuffer { _rect; _selector; _featureId; _depthAndOrder; _contours; _batchState; _scratchModelFeature = ModelFeature.create(); get _numPixels() { return this._rect.width * this._rect.height; } getPixelIndex(x, y) { if (x < this._rect.left || y < this._rect.top) return this._numPixels; x -= this._rect.left; y -= this._rect.top; if (x >= this._rect.width || y >= this._rect.height) return this._numPixels; // NB: View coords have origin at top-left; GL at bottom-left. So our rows are upside-down. y = this._rect.height - 1 - y; return y * this._rect.width + x; } getPixel32(data, pixelIndex) { return pixelIndex < data.length ? data[pixelIndex] : undefined; } getFeature(pixelIndex, result) { const featureId = this.getFeatureId(pixelIndex); return undefined !== featureId ? this._batchState.getFeature(featureId, result) : undefined; } getFeatureId(pixelIndex) { return undefined !== this._featureId ? this.getPixel32(this._featureId, pixelIndex) : undefined; } getBatchInfo(pixelIndex) { const featureId = this.getFeatureId(pixelIndex); if (undefined !== featureId) { const batch = this._batchState.find(featureId); if (undefined !== batch) { return { featureTable: batch.featureTable, iModel: batch.batchIModel, transformFromIModel: batch.transformFromBatchIModel, tileId: batch.tileId, viewAttachmentId: batch.viewAttachmentId, inSectionDrawingAttachment: batch.inSectionDrawingAttachment, }; } } return undefined; } _scratchUint32Array = new Uint32Array(1); _scratchUint8Array = new Uint8Array(this._scratchUint32Array.buffer); _scratchVector3d = new Vector3d(); _mult = new Vector3d(1.0, 1.0 / 255.0, 1.0 / 65025.0); decodeDepthRgba(depthAndOrder) { this._scratchUint32Array[0] = depthAndOrder; const bytes = this._scratchUint8Array; const fpt = Vector3d.create(bytes[1] / 255.0, bytes[2] / 255.0, bytes[3] / 255.0, this._scratchVector3d); let depth = fpt.dotProduct(this._mult); assert(0.0 <= depth); assert(1.01 >= depth); // rounding error... depth = Math.min(1.0, depth); depth = Math.max(0.0, depth); return depth; } decodeRenderOrderRgba(depthAndOrder) { return this.decodeUint8(depthAndOrder, 16); } decodeUint8(rgba32, basis) { this._scratchUint32Array[0] = rgba32; const encByte = this._scratchUint8Array[0]; const enc = encByte / 255.0; const dec = Math.floor(basis * enc + 0.5); return dec; } _invalidPixelData = new Pixel.Data(); getPixel(x, y) { const px = this._invalidPixelData; const index = this.getPixelIndex(x, y); if (index >= this._numPixels) return px; // Initialize to the defaults... let distanceFraction = px.distanceFraction; let geometryType = px.type; let planarity = px.planarity; const haveFeatureIds = Pixel.Selector.None !== (this._selector & Pixel.Selector.Feature); const feature = haveFeatureIds ? this.getFeature(index, this._scratchModelFeature) : undefined; const batchInfo = haveFeatureIds ? this.getBatchInfo(index) : undefined; if (Pixel.Selector.None !== (this._selector & Pixel.Selector.GeometryAndDistance) && undefined !== this._depthAndOrder) { const depthAndOrder = this.getPixel32(this._depthAndOrder, index); if (undefined !== depthAndOrder) { distanceFraction = this.decodeDepthRgba(depthAndOrder); const orderWithPlanarBit = this.decodeRenderOrderRgba(depthAndOrder); const order = orderWithPlanarBit & ~8 /* RenderOrder.PlanarBit */; planarity = (orderWithPlanarBit === order) ? Pixel.Planarity.NonPlanar : Pixel.Planarity.Planar; switch (order) { case 0 /* RenderOrder.None */: geometryType = Pixel.GeometryType.None; planarity = Pixel.Planarity.None; break; case 1 /* RenderOrder.Background */: case 2 /* RenderOrder.BlankingRegion */: case 4 /* RenderOrder.LitSurface */: case 3 /* RenderOrder.UnlitSurface */: geometryType = Pixel.GeometryType.Surface; break; case 5 /* RenderOrder.Linear */: geometryType = Pixel.GeometryType.Linear; break; case 6 /* RenderOrder.Edge */: geometryType = Pixel.GeometryType.Edge; break; case 7 /* RenderOrder.Silhouette */: geometryType = Pixel.GeometryType.Silhouette; break; default: // ###TODO: may run into issues with point clouds - they are not written correctly in C++. assert(false, "Invalid render order"); geometryType = Pixel.GeometryType.None; planarity = Pixel.Planarity.None; break; } } } let contour; if (this._contours) { const contour32 = this.getPixel32(this._contours.data, index); if (contour32) { // undefined means out of bounds; zero means not a contour. const groupIndexAndType = this.decodeUint8(contour32, 32); const groupIndex = groupIndexAndType & ~(8 | 16); const group = this._contours.display.groups[groupIndex]; if (group) { const elevationFraction = this.decodeDepthRgba(contour32); let elevation = elevationFraction * (this._contours.zHigh - this._contours.zLow) + this._contours.zLow; // The shader rounds to the nearest contour elevation using single-precision arithmetic. // Re-round here using double-precision to get closer. const interval = group.contourDef.minorInterval; elevation = (elevation >= 0 ? Math.floor((elevation + interval / 2) / interval) : Math.ceil((elevation - interval / 2) / interval)) * interval; contour = { group, elevation, isMajor: groupIndexAndType > 15, }; } } } let featureTable, iModel, transformToIModel, tileId, viewAttachmentId, inSectionDrawingAttachment; if (undefined !== batchInfo) { featureTable = batchInfo.featureTable; iModel = batchInfo.iModel; transformToIModel = batchInfo.transformFromIModel; tileId = batchInfo.tileId; viewAttachmentId = batchInfo.viewAttachmentId; inSectionDrawingAttachment = batchInfo.inSectionDrawingAttachment; } return new Pixel.Data({ feature, distanceFraction, type: geometryType, planarity, batchType: featureTable?.type, iModel, transformFromIModel: transformToIModel, tileId, viewAttachmentId, inSectionDrawingAttachment, contour, }); } constructor(rect, selector, compositor) { this._rect = rect.clone(); this._selector = selector; this._batchState = compositor.target.uniforms.batch.state; if (Pixel.Selector.None !== (selector & Pixel.Selector.GeometryAndDistance)) { const depthAndOrderBytes = compositor.readDepthAndOrder(rect); if (undefined !== depthAndOrderBytes) this._depthAndOrder = new Uint32Array(depthAndOrderBytes.buffer); else this._selector &= ~Pixel.Selector.GeometryAndDistance; } if (Pixel.Selector.None !== (selector & Pixel.Selector.Feature)) { const features = compositor.readFeatureIds(rect); if (undefined !== features) this._featureId = new Uint32Array(features.buffer); else this._selector &= ~Pixel.Selector.Feature; } // Note: readContours is a no-op unless contours are actually being drawn. if (Pixel.Selector.None !== (selector & Pixel.Selector.Contours)) { this._contours = compositor.readContours(rect); } } get isEmpty() { return Pixel.Selector.None === this._selector; } static create(rect, selector, compositor) { const pdb = new PixelBuffer(rect, selector, compositor); return pdb.isEmpty ? undefined : pdb; } } /** Orchestrates rendering of the scene on behalf of a Target. * This base class exists only so we don't have to export all the types of the shared Compositor members like Textures, FrameBuffers, etc. * @internal */ export class SceneCompositor { target; solarShadowMap; eyeDomeLighting; _needHiddenEdges; get needHiddenEdges() { return this._needHiddenEdges; } constructor(target) { this.target = target; this.solarShadowMap = new SolarShadowMap(target); this.eyeDomeLighting = new EyeDomeLighting(target); this._needHiddenEdges = false; } static create(target) { return new Compositor(target); } } // This describes what types of primitives a compositor should draw. See the `drawPrimitive` method of Compositor. var PrimitiveDrawState; (function (PrimitiveDrawState) { PrimitiveDrawState[PrimitiveDrawState["Both"] = 0] = "Both"; PrimitiveDrawState[PrimitiveDrawState["Pickable"] = 1] = "Pickable"; PrimitiveDrawState[PrimitiveDrawState["NonPickable"] = 2] = "NonPickable"; })(PrimitiveDrawState || (PrimitiveDrawState = {})); // The actual base class. Specializations are provided based on whether or not multiple render targets are supported. class Compositor extends SceneCompositor { _width = -1; _height = -1; _includeOcclusion = false; _textures = new Textures(); _depth; _depthMS; // multisample depth buffer _fbos; _geom; _readPickDataFromPingPong = true; _opaqueRenderState = new RenderState(); _layerRenderState = new RenderState(); _translucentRenderState = new RenderState(); _hiliteRenderState = new RenderState(); _noDepthMaskRenderState = new RenderState(); _backgroundMapRenderState = new RenderState(); _pointCloudRenderState = new RenderState(); _debugStencil = 0; // 0 to draw stencil volumes normally, 1 to draw as opaque, 2 to draw blended _vcBranchState; _vcSetStencilRenderState; _vcCopyZRenderState; _vcColorRenderState; _vcBlendRenderState; _vcPickDataRenderState; _vcDebugRenderState; _vcAltDepthStencil; _vcAltDepthStencilMS; _haveVolumeClassifier = false; _antialiasSamples = 1; _viewProjectionMatrix = new Matrix4(); _primitiveDrawState = PrimitiveDrawState.Both; // used by drawPrimitive to decide whether a primitive needs to be drawn. forceBufferChange() { this._width = this._height = -1; } get featureIds() { return this.getSamplerTexture(this._readPickDataFromPingPong ? 0 : 1); } get depthAndOrder() { return this.getSamplerTexture(this._readPickDataFromPingPong ? 1 : 2); } get _samplerFbo() { return this._readPickDataFromPingPong ? this._fbos.pingPong : this._fbos.opaqueAll; } getSamplerTexture(index) { return this._samplerFbo.getColor(index); } drawPrimitive(primitive, exec, outputsToPick) { if ((outputsToPick && this._primitiveDrawState !== PrimitiveDrawState.NonPickable) || (!outputsToPick && this._primitiveDrawState !== PrimitiveDrawState.Pickable)) primitive.draw(exec); } clearOpaque(needComposite) { const fbo = needComposite ? this._fbos.opaqueAndCompositeAll : this._fbos.opaqueAll; const system = System.instance; system.frameBufferStack.execute(fbo, true, this.useMsBuffers, () => { // Clear pick data buffers to 0's and color buffer to background color // (0,0,0,0) in elementID0 and ElementID1 buffers indicates invalid element id // (0,0,0,0) in DepthAndOrder buffer indicates render order 0 and encoded depth of 0 (= far plane) system.applyRenderState(this._noDepthMaskRenderState); const params = getDrawParams(this.target, this._geom.clearPickAndColor); this.target.techniques.draw(params); // Clear depth buffer system.applyRenderState(RenderState.defaults); // depthMask == true. system.context.clearDepth(1.0); system.context.clear(GL.BufferBit.Depth); }); } renderLayers(commands, needComposite, pass) { const fbo = (needComposite ? this._fbos.opaqueAndCompositeAll : this._fbos.opaqueAll); const useMsBuffers = 1 /* RenderPass.OpaqueLayers */ === pass && fbo.isMultisampled && this.useMsBuffers; this._readPickDataFromPingPong = !useMsBuffers; System.instance.frameBufferStack.execute(fbo, true, useMsBuffers, () => { this.drawPass(commands, pass, true); }); this._readPickDataFromPingPong = false; } renderOpaque(commands, compositeFlags, renderForReadPixels) { if (0 /* CompositeFlags.None */ !== (compositeFlags & 4 /* CompositeFlags.AmbientOcclusion */) && !renderForReadPixels) { this.renderOpaqueAO(commands); return; } const needComposite = 0 /* CompositeFlags.None */ !== compositeFlags; const fbStack = System.instance.frameBufferStack; // Output the first 2 passes to color and pick data buffers. (All 3 in the case of rendering for readPixels() or ambient occlusion). let fbo = (needComposite ? this._fbos.opaqueAndCompositeAll : this._fbos.opaqueAll); const useMsBuffers = fbo.isMultisampled && this.useMsBuffers; this._readPickDataFromPingPong = !useMsBuffers; // if multisampling then can read pick textures directly. fbStack.execute(fbo, true, useMsBuffers, () => { this.drawPass(commands, 2 /* RenderPass.OpaqueLinear */); this.drawPass(commands, 3 /* RenderPass.OpaquePlanar */, true); if (renderForReadPixels) { this.drawPass(commands, 4 /* RenderPass.PointClouds */, true); // don't need EDL for this this.drawPass(commands, 5 /* RenderPass.OpaqueGeneral */, true); if (useMsBuffers) fbo.blitMsBuffersToTextures(true); } }); this._readPickDataFromPingPong = false; // The general pass (and following) will not bother to write to pick buffers and so can read from the actual pick buffers. if (!renderForReadPixels) { fbo = (needComposite ? this._fbos.opaqueAndCompositeColor : this._fbos.opaqueColor); fbStack.execute(fbo, true, useMsBuffers, () => { this.drawPass(commands, 5 /* RenderPass.OpaqueGeneral */, false); this.drawPass(commands, 9 /* RenderPass.HiddenEdge */, false); }); // assume we are done with MS at this point, so update the non-MS buffers if (useMsBuffers) fbo.blitMsBuffersToTextures(needComposite); } } renderOpaqueAO(commands) { const fbStack = System.instance.frameBufferStack; const haveHiddenEdges = 0 !== commands.getCommands(9 /* RenderPass.HiddenEdge */).length; // Output the linear, planar, and pickable surfaces to color and pick data buffers. let fbo = this._fbos.opaqueAndCompositeAll; const useMsBuffers = fbo.isMultisampled && this.useMsBuffers; this._readPickDataFromPingPong = !useMsBuffers; // if multisampling then can read pick textures directly. fbStack.execute(fbo, true, useMsBuffers, () => { this.drawPass(commands, 2 /* RenderPass.OpaqueLinear */); this.drawPass(commands, 3 /* RenderPass.OpaquePlanar */, true); this._primitiveDrawState = PrimitiveDrawState.Pickable; this.drawPass(commands, 5 /* RenderPass.OpaqueGeneral */, true); this._primitiveDrawState = PrimitiveDrawState.Both; if (useMsBuffers) fbo.blitMsBuffersToTextures(true); }); this._readPickDataFromPingPong = false; // Output the non-pickable surfaces and hidden edges to just the color buffer. fbo = this._fbos.opaqueAndCompositeColor; fbStack.execute(fbo, true, useMsBuffers, () => { this._primitiveDrawState = PrimitiveDrawState.NonPickable; this.drawPass(commands, 5 /* RenderPass.OpaqueGeneral */, false); if (haveHiddenEdges) this.drawPass(commands, 9 /* RenderPass.HiddenEdge */, false); this._primitiveDrawState = PrimitiveDrawState.Both; }); if (useMsBuffers) fbo.blitMsBuffersToTextures(true); // If there are no hidden edges, then we're done & can run the AO passes using the normal depthAndOrder texture. if (haveHiddenEdges) { // AO needs the pick data (orderAndDepth) for the hidden edges. We don't want it in with the other pick data though since they are not pickable, so we will use other textures. // If not multisampling we will re-use the ping-pong/transparency textures since we are done with ping-ponging at this point and transparency happens later. // If multisampling then we will use the accumulation texture for featureIDs and a special texture for depthAndOrder since the revealage texture is not the right type for multisampling. // First we will need to copy what's in the pick buffers so far into the hidden pick buffers. System.instance.applyRenderState(this._noDepthMaskRenderState); fbo