UNPKG

@itwin/core-frontend

Version:
635 lines • 29.7 kB
"use strict"; /*--------------------------------------------------------------------------------------------- * 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 */ Object.defineProperty(exports, "__esModule", { value: true }); exports.RenderCommands = void 0; const core_bentley_1 = require("@itwin/core-bentley"); const core_geometry_1 = require("@itwin/core-geometry"); const core_common_1 = require("@itwin/core-common"); const SurfaceParams_1 = require("../../../common/internal/render/SurfaceParams"); const DrawCommand_1 = require("./DrawCommand"); const Graphic_1 = require("./Graphic"); const LayerCommands_1 = require("./LayerCommands"); const Mesh_1 = require("./Mesh"); const RenderFlags_1 = require("./RenderFlags"); /** A list of DrawCommands to be rendered, ordered by render pass. * @internal */ class RenderCommands { _frustumPlanes; _scratchFrustum = new core_common_1.Frustum(); _scratchRange = new core_geometry_1.Range3d(); _commands = new Array(21 /* RenderPass.COUNT */); _target; _stack; // refers to the Target's BranchStack _batchState; // refers to the Target's BatchState _forcedRenderPass = 255 /* RenderPass.None */; _addLayersAsNormalGraphics = false; _opaqueOverrides = false; _translucentOverrides = false; _addTranslucentAsOpaque = false; // true when rendering for _ReadPixels to force translucent items to be drawn in opaque pass. _layers; appearanceProvider; get target() { return this._target; } [Symbol.iterator]() { return this._commands[Symbol.iterator](); } get isEmpty() { for (const commands of this._commands) if (0 < commands.length) return false; return true; } get isDrawingLayers() { switch (this._forcedRenderPass) { case 1 /* RenderPass.OpaqueLayers */: case 7 /* RenderPass.TranslucentLayers */: case 11 /* RenderPass.OverlayLayers */: return true; default: return false; } } get currentViewFlags() { return this._stack.top.viewFlags; } get compositeFlags() { let flags = 0 /* CompositeFlags.None */; if (this.hasCommands(8 /* RenderPass.Translucent */)) flags |= 1 /* CompositeFlags.Translucent */; if (this.hasCommands(10 /* RenderPass.Hilite */) || this.hasCommands(16 /* RenderPass.HiliteClassification */) || this.hasCommands(18 /* RenderPass.HilitePlanarClassification */)) flags |= 2 /* CompositeFlags.Hilite */; if (this.target.wantAmbientOcclusion) flags |= 4 /* CompositeFlags.AmbientOcclusion */; return flags; } get _curBatch() { return this._batchState.currentBatch; } hasCommands(pass) { return 0 !== this.getCommands(pass).length; } isOpaquePass(pass) { return pass >= 2 /* RenderPass.OpaqueLinear */ && pass <= 5 /* RenderPass.OpaqueGeneral */; } constructor(target, stack, batchState) { this._target = target; this._stack = stack; this._batchState = batchState; this._layers = new LayerCommands_1.LayerCommandLists(this); for (let i = 0; i < 21 /* RenderPass.COUNT */; ++i) this._commands[i] = []; } reset(target, stack, batchState) { this._target = target; this._stack = stack; this._batchState = batchState; this.appearanceProvider = undefined; this.clear(); } collectGraphicsForPlanarProjection(scene) { (0, core_bentley_1.assert)(this._forcedRenderPass === 255 /* RenderPass.None */); (0, core_bentley_1.assert)(!this._addLayersAsNormalGraphics); this._addLayersAsNormalGraphics = true; this.addGraphics(scene); this._addLayersAsNormalGraphics = false; } addGraphics(scene, forcedPass = 255 /* RenderPass.None */) { this._forcedRenderPass = forcedPass; scene.forEach((entry) => entry.addCommands(this)); this._forcedRenderPass = 255 /* RenderPass.None */; } /** Add backgroundMap graphics to their own render pass. */ addBackgroundMapGraphics(backgroundMapGraphics) { this._forcedRenderPass = 15 /* RenderPass.BackgroundMap */; backgroundMapGraphics.forEach((entry) => entry.addCommands(this)); this._forcedRenderPass = 255 /* RenderPass.None */; } /** Add overlay graphics to the world overlay pass */ addOverlayGraphics(overlayGraphics) { this._forcedRenderPass = 12 /* RenderPass.WorldOverlay */; overlayGraphics.forEach((entry) => entry.addCommands(this)); this._forcedRenderPass = 255 /* RenderPass.None */; } addDecorations(dec, forcedPass = 255 /* RenderPass.None */) { this._forcedRenderPass = forcedPass; for (const entry of dec) { entry.addCommands(this); } this._forcedRenderPass = 255 /* RenderPass.None */; } addWorldDecorations(decs) { const world = this.target.getWorldDecorations(decs); this.pushAndPopBranch(world, () => { for (const entry of world.branch.entries) { entry.addCommands(this); } }); } addPickableDecorations(decs) { if (undefined !== decs.normal) { for (const normal of decs.normal) { const gf = normal; if (gf.isPickable) gf.addCommands(this); } } if (undefined !== decs.world) { const world = this.target.getWorldDecorations(decs.world); this.pushAndPopBranch(world, () => { for (const gf of world.branch.entries) { if (gf.isPickable) gf.addCommands(this); } }); } } addBackground(gf) { if (undefined === gf) return; (0, core_bentley_1.assert)(255 /* RenderPass.None */ === this._forcedRenderPass); this._forcedRenderPass = 0 /* RenderPass.Background */; this.pushAndPopState(this.target.decorationsState, () => gf.addCommands(this)); this._forcedRenderPass = 255 /* RenderPass.None */; } addSkyBox(gf) { if (undefined === gf) return; (0, core_bentley_1.assert)(255 /* RenderPass.None */ === this._forcedRenderPass); this._forcedRenderPass = 14 /* RenderPass.SkyBox */; this.pushAndPopState(this.target.decorationsState, () => gf.addCommands(this)); this._forcedRenderPass = 255 /* RenderPass.None */; } addPrimitiveCommand(command, pass) { if (undefined === pass) pass = command.getPass(this.target); if ("none" === pass) // Edges will return none if they don't want to draw at all (edges not turned on). return; if (255 /* RenderPass.None */ !== this._forcedRenderPass) { // Add the command to the forced render pass (background). this.getCommands(this._forcedRenderPass).push(command); return; } if (!command.hasFeatures) { // Draw in general opaque pass so they are not in pick data. switch (pass) { case "opaque-linear": case "opaque-planar": pass = "opaque"; break; case "opaque-planar-translucent": pass = "opaque-translucent"; break; } } const haveFeatureOverrides = (this._opaqueOverrides || this._translucentOverrides) && command.opcode && command.hasFeatures; if (RenderFlags_1.Pass.rendersTranslucent(pass) && this._addTranslucentAsOpaque) { switch (command.renderOrder) { case 12 /* RenderOrder.PlanarLitSurface */: case 11 /* RenderOrder.PlanarUnlitSurface */: case 2 /* RenderOrder.BlankingRegion */: pass = "opaque-planar"; break; case 4 /* RenderOrder.LitSurface */: case 3 /* RenderOrder.UnlitSurface */: pass = "opaque"; break; default: pass = "opaque-linear"; break; } } const isDoublePass = RenderFlags_1.Pass.rendersOpaqueAndTranslucent(pass); const renderTranslucentDuringOpaque = isDoublePass || (this._opaqueOverrides && haveFeatureOverrides); if (renderTranslucentDuringOpaque && RenderFlags_1.Pass.rendersTranslucent(pass) && !command.primitive.cachedGeometry.alwaysRenderTranslucent) { let opaquePass; if (RenderFlags_1.Pass.rendersOpaqueAndTranslucent(pass)) { opaquePass = RenderFlags_1.Pass.toOpaquePass(pass); } else { switch (command.renderOrder) { case 12 /* RenderOrder.PlanarLitSurface */: case 11 /* RenderOrder.PlanarUnlitSurface */: case 2 /* RenderOrder.BlankingRegion */: opaquePass = 3 /* RenderPass.OpaquePlanar */; break; case 4 /* RenderOrder.LitSurface */: case 3 /* RenderOrder.UnlitSurface */: opaquePass = 5 /* RenderPass.OpaqueGeneral */; break; default: opaquePass = 2 /* RenderPass.OpaqueLinear */; break; } } this.getCommands(opaquePass).push(command); } const renderOpaqueDuringTranslucent = isDoublePass || (this._translucentOverrides && haveFeatureOverrides); if (renderOpaqueDuringTranslucent && RenderFlags_1.Pass.rendersOpaque(pass) && !this._addTranslucentAsOpaque) this.getCommands(8 /* RenderPass.Translucent */).push(command); if (!RenderFlags_1.Pass.rendersOpaqueAndTranslucent(pass)) this.getCommands(RenderFlags_1.Pass.toRenderPass(pass)).push(command); } getCommands(pass) { let idx = pass; (0, core_bentley_1.assert)(idx < this._commands.length); if (idx >= this._commands.length) idx -= 1; return this._commands[idx]; } replaceCommands(pass, cmds) { const idx = pass; this._commands[idx].splice(0); this._commands[idx] = cmds; } addHiliteBranch(branch, pass) { this.pushAndPopBranchForPass(pass, branch, () => { branch.branch.entries.forEach((entry) => entry.addHiliteCommands(this, pass)); }); } processLayers(container) { (0, core_bentley_1.assert)(255 /* RenderPass.None */ === this._forcedRenderPass); if (255 /* RenderPass.None */ !== this._forcedRenderPass) return; this._forcedRenderPass = container.renderPass; this._layers.processLayers(container, () => container.graphic.addCommands(this)); this._forcedRenderPass = 255 /* RenderPass.None */; } addLayerCommands(layer) { if (this._addLayersAsNormalGraphics) { // GraphicsCollectorDrawArgs wants to collect graphics to project to a plane for masking. // It bypasses PlanProjectionTreeReference.createDrawArgs which would otherwise wrap the graphics in a LayerContainer. (0, core_bentley_1.assert)(this._forcedRenderPass === 255 /* RenderPass.None */); this._forcedRenderPass = 5 /* RenderPass.OpaqueGeneral */; layer.graphic.addCommands(this); this._forcedRenderPass = 255 /* RenderPass.None */; return; } (0, core_bentley_1.assert)(this.isDrawingLayers); if (!this.isDrawingLayers) return; // Let the graphic add its commands. Afterward, pull them out and add them to the LayerCommands. this._layers.currentLayer = layer; layer.graphic.addCommands(this); const cmds = this.getCommands(this._forcedRenderPass); this._layers.addCommands(cmds); cmds.length = 0; this._layers.currentLayer = undefined; } addHiliteLayerCommands(graphic, pass) { (0, core_bentley_1.assert)(this.isDrawingLayers || this._addLayersAsNormalGraphics); if (!this.isDrawingLayers && !this._addLayersAsNormalGraphics) return; const prevPass = this._forcedRenderPass; this._forcedRenderPass = 255 /* RenderPass.None */; graphic.addHiliteCommands(this, pass); this._forcedRenderPass = prevPass; } getAnimationBranchState(branch) { const animId = branch.branch.animationId; return undefined !== animId ? this.target.animationBranches?.branchStates.get(animId) : undefined; } pushAndPopBranchForPass(pass, branch, func) { (0, core_bentley_1.assert)(!this.isDrawingLayers); const animState = this.getAnimationBranchState(branch); if (animState?.omit) return; (0, core_bentley_1.assert)(255 /* RenderPass.None */ !== pass); this._stack.pushBranch(branch); if (branch.planarClassifier) branch.planarClassifier.pushBatchState(this._batchState); if (branch.secondaryClassifiers) branch.secondaryClassifiers.forEach((classifier) => classifier.pushBatchState(this._batchState)); const cmds = this.getCommands(pass); const clip = animState?.clip; const pushClip = undefined !== clip ? new DrawCommand_1.PushClipCommand(clip) : undefined; if (pushClip) cmds.push(pushClip); const push = new DrawCommand_1.PushBranchCommand(branch); cmds.push(push); func(); this._stack.pop(); if (cmds[cmds.length - 1] === push) { cmds.pop(); if (pushClip) cmds.pop(); } else { cmds.push(DrawCommand_1.PopBranchCommand.instance); if (pushClip) cmds.push(DrawCommand_1.PopClipCommand.instance); } } pushAndPop(push, pop, func) { if (this.isDrawingLayers) { this._commands[10 /* RenderPass.Hilite */].push(push); this._layers.pushAndPop(push, pop, func); const cmds = this._commands[10 /* RenderPass.Hilite */]; if (0 < cmds.length && cmds[cmds.length - 1] === push) cmds.pop(); else cmds.push(pop); return; } if (255 /* RenderPass.None */ === this._forcedRenderPass) { // Need to make sure the push command precedes any subsequent commands added to any render pass. for (const cmds of this._commands) cmds.push(push); } else { // May want to add hilite commands as well - add the push command to that pass. this._commands[this._forcedRenderPass].push(push); this._commands[10 /* RenderPass.Hilite */].push(push); } func(); // Remove push command from any passes that didn't receive any commands; add the pop command to any passes that did. if (255 /* RenderPass.None */ === this._forcedRenderPass) { for (const cmds of this._commands) { (0, core_bentley_1.assert)(0 < cmds.length); if (0 < cmds.length && cmds[cmds.length - 1] === push) cmds.pop(); else cmds.push(pop); } } else { (0, core_bentley_1.assert)(0 < this._commands[this._forcedRenderPass].length); (0, core_bentley_1.assert)(0 < this._commands[10 /* RenderPass.Hilite */].length); let cmds = this._commands[this._forcedRenderPass]; if (cmds[cmds.length - 1] === push) cmds.pop(); else cmds.push(pop); cmds = this._commands[10 /* RenderPass.Hilite */]; if (cmds[cmds.length - 1] === push) cmds.pop(); else cmds.push(pop); } } pushAndPopBranch(branch, func) { const animState = this.getAnimationBranchState(branch); if (animState?.omit) return; if (animState?.clip) this.pushAndPop(new DrawCommand_1.PushClipCommand(animState.clip), DrawCommand_1.PopClipCommand.instance, () => this._pushAndPopBranch(branch, func)); else this._pushAndPopBranch(branch, func); } _pushAndPopBranch(branch, func) { this._stack.pushBranch(branch); if (branch.planarClassifier) branch.planarClassifier.pushBatchState(this._batchState); if (branch.secondaryClassifiers) branch.secondaryClassifiers.forEach((classifier) => classifier.pushBatchState(this._batchState)); this.pushAndPop(new DrawCommand_1.PushBranchCommand(branch), DrawCommand_1.PopBranchCommand.instance, func); this._stack.pop(); } pushAndPopState(state, func) { this._stack.pushState(state); this.pushAndPop(new DrawCommand_1.PushStateCommand(state), DrawCommand_1.PopBranchCommand.instance, func); this._stack.pop(); } clear() { (0, core_bentley_1.assert)(this._batchState.isEmpty); this._clearCommands(); } _clearCommands() { this._commands.forEach((cmds) => cmds.splice(0)); this._layers.clear(); } initForPickOverlayDecorations(overlays) { for (const overlay of overlays) { const gf = overlay; if (gf.isPickable) gf.addCommands(this); } } initForPickOverlays(sceneOverlays, worldOverlayDecorations, viewOverlayDecorations) { this._clearCommands(); this._addTranslucentAsOpaque = true; for (const sceneGf of sceneOverlays) sceneGf.addCommands(this); if (worldOverlayDecorations?.length) { this.pushAndPopState(this.target.decorationsState, () => { this.initForPickOverlayDecorations(worldOverlayDecorations); }); } if (viewOverlayDecorations?.length) { this.pushAndPopState(this.target.decorationsState.withViewCoords(), () => { this.initForPickOverlayDecorations(viewOverlayDecorations); }); } this._addTranslucentAsOpaque = false; } initForReadPixels(gfx) { this.clear(); // Set flag to force translucent geometry to be put into the opaque pass. this._addTranslucentAsOpaque = true; // Add the scene graphics. this.addGraphics(gfx.foreground); // Also add any pickable decorations. if (undefined !== gfx.decorations) this.addPickableDecorations(gfx.decorations); // Also background map is pickable this.addBackgroundMapGraphics(gfx.background); this._addTranslucentAsOpaque = false; this.setupClassificationByVolume(); this._layers.outputCommands(); } initForRender(gfx) { this.clear(); this.addGraphics(gfx.foreground); this.addBackgroundMapGraphics(gfx.background); this.addOverlayGraphics(gfx.overlays); this.addGraphics(gfx.foregroundDynamics); this.addOverlayGraphics(gfx.overlayDynamics); const dec = gfx.decorations; if (undefined !== dec) { this.addBackground(dec.viewBackground); this.addSkyBox(dec.skyBox); if (undefined !== dec.normal && 0 < dec.normal.length) this.addGraphics(dec.normal); if (undefined !== dec.world && 0 < dec.world.length) this.addWorldDecorations(dec.world); this.pushAndPopState(this.target.decorationsState, () => { if (undefined !== dec.viewOverlay && 0 < dec.viewOverlay.length) this.addDecorations(dec.viewOverlay, 13 /* RenderPass.ViewOverlay */); if (undefined !== dec.worldOverlay && 0 < dec.worldOverlay.length) this.addDecorations(dec.worldOverlay, 12 /* RenderPass.WorldOverlay */); }); } this.setupClassificationByVolume(); this._layers.outputCommands(); } addPrimitive(prim) { // ###TODO Would be nice if we could detect outside active volume here, but active volume only applies to specific render passes // if (this.target.isGeometryOutsideActiveVolume(prim.cachedGeometry)) // return; if (undefined !== this._frustumPlanes) { // See if we can cull this primitive. if ("classification" === prim.getPass(this.target)) { const geom = prim.cachedGeometry; geom.computeRange(this._scratchRange); let frustum = core_common_1.Frustum.fromRange(this._scratchRange, this._scratchFrustum); frustum = frustum.transformBy(this.target.currentTransform, frustum); if (core_common_1.FrustumPlanes.Containment.Outside === this._frustumPlanes.computeFrustumContainment(frustum)) { return; } } } const command = new DrawCommand_1.PrimitiveCommand(prim); this.addPrimitiveCommand(command); if (255 /* RenderPass.None */ === this._forcedRenderPass && prim.isEdge) { const vf = this.target.currentViewFlags; if (vf.renderMode !== core_common_1.RenderMode.Wireframe && vf.hiddenEdges) this.getCommands(9 /* RenderPass.HiddenEdge */).push(command); } } addBranch(branch) { this.pushAndPopBranch(branch, () => { branch.branch.entries.forEach((entry) => entry.addCommands(this)); }); } computeBatchHiliteRenderPass(batch) { let pass = 10 /* RenderPass.Hilite */; if (batch.graphic instanceof Mesh_1.MeshGraphic) { const mg = batch.graphic; if (SurfaceParams_1.SurfaceType.VolumeClassifier === mg.surfaceType) pass = 16 /* RenderPass.HiliteClassification */; } else if (batch.graphic instanceof Graphic_1.GraphicsArray) { const ga = batch.graphic; if (ga.graphics[0] instanceof Mesh_1.MeshGraphic) { const mg = ga.graphics[0]; if (SurfaceParams_1.SurfaceType.VolumeClassifier === mg.surfaceType) pass = 16 /* RenderPass.HiliteClassification */; } else if (ga.graphics[0] instanceof Graphic_1.Branch) { const b = ga.graphics[0]; if (b.branch.entries.length > 0 && b.branch.entries[0] instanceof Mesh_1.MeshGraphic) { const mg = b.branch.entries[0]; if (SurfaceParams_1.SurfaceType.VolumeClassifier === mg.surfaceType) pass = 16 /* RenderPass.HiliteClassification */; } } } return pass; } addBatch(batch) { if (batch.locateOnly && !this.target.isReadPixelsInProgress) return; // Batches (aka element tiles) should only draw during ordinary (translucent or opaque) passes. // They may draw during both, or neither. // NB: This is no longer true - pickable overlay decorations are defined as Batches. Problem? // assert(RenderPass.None === this._forcedRenderPass); (0, core_bentley_1.assert)(!this._opaqueOverrides && !this._translucentOverrides); (0, core_bentley_1.assert)(undefined === this._curBatch); // If all features are overridden to be invisible, draw no graphics in this batch const overrides = batch.getOverrides(this.target, this.appearanceProvider ?? this._stack.top); if (overrides.allHidden) return; if (!batch.range.isNull) { // ###TODO Would be nice if we could detect outside active volume here, but active volume only applies to specific render passes // if (this.target.isRangeOutsideActiveVolume(batch.range)) // return; if (undefined !== this._frustumPlanes) { let frustum = core_common_1.Frustum.fromRange(batch.range, this._scratchFrustum); frustum = frustum.transformBy(this.target.currentTransform, frustum); if (core_common_1.FrustumPlanes.Containment.Outside === this._frustumPlanes.computeFrustumContainment(frustum)) { return; } } } const classifier = this._stack.top.planarClassifier; this._batchState.push(batch, true); this.pushAndPop(new DrawCommand_1.PushBatchCommand(batch), DrawCommand_1.PopBatchCommand.instance, () => { if (this.currentViewFlags.transparency || overrides.anyViewIndependentTranslucent) { this._opaqueOverrides = overrides.anyOpaque; this._translucentOverrides = overrides.anyTranslucent; if (undefined !== classifier) { this._opaqueOverrides = this._opaqueOverrides || classifier.anyOpaque; this._translucentOverrides = this._translucentOverrides || classifier.anyTranslucent; } } // If we have an active volume classifier then force all batches for the reality data being classified into a special render pass. let savedForcedRenderPass = 255 /* RenderPass.None */; if (undefined !== this.target.activeVolumeClassifierModelId && batch.featureTable.batchModelId === this.target.activeVolumeClassifierModelId) { savedForcedRenderPass = this._forcedRenderPass; this._forcedRenderPass = 20 /* RenderPass.VolumeClassifiedRealityData */; } batch.graphic.addCommands(this); if (20 /* RenderPass.VolumeClassifiedRealityData */ === this._forcedRenderPass) this._forcedRenderPass = savedForcedRenderPass; // If the batch contains hilited features, need to render them in the hilite pass const anyHilited = overrides.anyHilited; const planarClassifierHilited = undefined !== classifier && classifier.anyHilited; if (anyHilited || planarClassifierHilited) batch.graphic.addHiliteCommands(this, planarClassifierHilited ? 18 /* RenderPass.HilitePlanarClassification */ : this.computeBatchHiliteRenderPass(batch)); }); this._opaqueOverrides = this._translucentOverrides = false; this._batchState.pop(); } // Define a culling frustum. Commands associated with Graphics whose ranges do not intersect the frustum will be skipped. setCheckRange(frustum) { this._frustumPlanes = core_common_1.FrustumPlanes.fromFrustum(frustum); } // Clear the culling frustum. clearCheckRange() { this._frustumPlanes = undefined; } setupClassificationByVolume() { // To make it easier to process the classifiers individually, set up a secondary command list for them where they // are each separated by their own pushes & pops so that they can easily be drawn individually. This now supports // nested branches and batches. const groupedCmds = this._commands[6 /* RenderPass.Classification */]; const byIndexCmds = this._commands[17 /* RenderPass.ClassificationByIndex */]; const pushCommands = []; // will contain current set of pushes ahead of a primitive for (const cmd of groupedCmds) { switch (cmd.opcode) { case "pushBranch": case "pushBatch": case "pushState": pushCommands.push(cmd); break; case "drawPrimitive": for (const pushCmd of pushCommands) { byIndexCmds.push(pushCmd); } byIndexCmds.push(cmd); for (let i = pushCommands.length - 1; i >= 0; --i) { if ("pushBatch" === pushCommands[i].opcode) byIndexCmds.push(DrawCommand_1.PopBatchCommand.instance); else // should be eith pushBranch or pushState opcode byIndexCmds.push(DrawCommand_1.PopBranchCommand.instance); } break; case "popBatch": case "popBranch": pushCommands.pop(); break; } } } dump() { const dump = [ { name: "Primitives", count: 0 }, { name: "Batches", count: 0 }, { name: "Branches", count: 0 }, ]; for (const cmds of this._commands) { for (const cmd of cmds) { let index; switch (cmd.opcode) { case "drawPrimitive": index = 0; break; case "pushBatch": index = 1; break; case "pushBranch": index = 2; break; default: continue; } dump[index].count++; } } return dump; } } exports.RenderCommands = RenderCommands; //# sourceMappingURL=RenderCommands.js.map