@itwin/core-frontend
Version:
iTwin.js frontend components
942 lines • 52.3 kB
JavaScript
/*---------------------------------------------------------------------------------------------
* 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
*/
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
if (value !== null && value !== void 0) {
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
var dispose, inner;
if (async) {
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
dispose = value[Symbol.asyncDispose];
}
if (dispose === void 0) {
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
dispose = value[Symbol.dispose];
if (async) inner = dispose;
}
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
env.stack.push({ value: value, dispose: dispose, async: async });
}
else if (async) {
env.stack.push({ async: true });
}
return value;
};
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
return function (env) {
function fail(e) {
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
env.hasError = true;
}
var r, s = 0;
function next() {
while (r = env.stack.pop()) {
try {
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
if (r.dispose) {
var result = r.dispose.call(r.value);
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
}
else s |= 1;
}
catch (e) {
fail(e);
}
}
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
if (env.hasError) throw env.error;
}
return next();
};
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
var e = new Error(message);
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
});
import { assert, dispose } from "@itwin/core-bentley";
import { BlurType } from "./CachedGeometry";
import { createClippingProgram } from "./ClippingProgram";
import { createAmbientOcclusionProgram } from "./glsl/AmbientOcclusion";
import { createBlurProgram } from "./glsl/Blur";
import { createEDLCalcBasicProgram, createEDLCalcFullProgram, createEDLFilterProgram, createEDLMixProgram } from "./glsl/EDL";
import { createClearPickAndColorProgram } from "./glsl/ClearPickAndColor";
import { createClearTranslucentProgram } from "./glsl/ClearTranslucent";
import { createCombine3TexturesProgram } from "./glsl/Combine3Textures";
import { createCombineTexturesProgram } from "./glsl/CombineTextures";
import { addEyeSpace, addFrustum, addShaderFlags } from "./glsl/Common";
import { createCompositeProgram } from "./glsl/Composite";
import { createCopyColorProgram } from "./glsl/CopyColor";
import { createCopyPickBuffersProgram } from "./glsl/CopyPickBuffers";
import { createVolClassBlendProgram, createVolClassColorUsingStencilProgram, createVolClassCopyZProgram, createVolClassSetBlendProgram, } from "./glsl/CopyStencil";
import { createEdgeBuilder } from "./glsl/Edge";
import { createEVSMProgram } from "./glsl/EVSMFromDepth";
import { addFeatureId, addFeatureSymbology, addRenderOrder, addUniformFeatureSymbology, mixFeatureColor } from "./glsl/FeatureSymbology";
import { addFragColorWithPreMultipliedAlpha, addPickBufferOutputs } from "./glsl/Fragment";
import { addLogDepth } from "./glsl/LogarithmicDepthBuffer";
import { addUnlitMonochrome } from "./glsl/Monochrome";
import createPlanarGridProgram from "./glsl/PlanarGrid";
import { createPointCloudBuilder, createPointCloudHiliter } from "./glsl/PointCloud";
import { createPointStringBuilder, createPointStringHiliter } from "./glsl/PointString";
import { createPolylineBuilder, createPolylineHiliter } from "./glsl/Polyline";
import { addColorOverrideMix, createClassifierRealityMeshHiliter, createRealityMeshBuilder, createRealityMeshHiliter, } from "./glsl/RealityMesh";
import { createSkyBoxProgram } from "./glsl/SkyBox";
import { createSkySphereBuilder } from "./glsl/SkySphere";
import { createSurfaceBuilder, createSurfaceHiliter } from "./glsl/Surface";
import { addTranslucency } from "./glsl/Translucency";
import { addModelViewMatrix } from "./glsl/Vertex";
import { ShaderProgramExecutor } from "./ShaderProgram";
import { System } from "./System";
import { TechniqueFlags, } from "./TechniqueFlags";
import { computeCompositeTechniqueId } from "./TechniqueId";
/** A rendering technique implemented using a single shader program, typically for some specialized purpose.
* @internal
*/
export class SingularTechnique {
program;
// Note: Technique assumes ownership of a program
constructor(program) { this.program = program; }
getShader(_flags) { return this.program; }
getShaderByIndex(_index) { return this.program; }
getShaderCount() { return 1; }
compileShaders() { return this.program.compile() === 0 /* CompileStatus.Success */; }
get isDisposed() { return this.program.isDisposed; }
[Symbol.dispose]() {
this.program[Symbol.dispose]();
}
/** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */
dispose() {
this[Symbol.dispose]();
}
}
function numFeatureVariants(numBaseShaders) {
return numBaseShaders * 3;
}
const numHiliteVariants = 2; // instanced and non-instanced.
const featureModes = [0 /* FeatureMode.None */, 1 /* FeatureMode.Pick */, 2 /* FeatureMode.Overrides */];
const scratchTechniqueFlags = new TechniqueFlags();
const scratchHiliteFlags = new TechniqueFlags();
/** A rendering technique implemented using multiple shader programs, selected based on TechniqueFlags.
* @internal
*/
export class VariedTechnique {
_basicPrograms = [];
_clippingPrograms = [];
/** TechniqueFlags identifying shader programs for which the fragment shader writes depth but does not contain any discards.
* Buggy Intel HD 620/630 drivers incorrectly apply early-Z optimization in this case; we must insert a never-executed
* conditional discard to prevent that.
*/
_earlyZFlags = [];
compileShaders() {
let allCompiled = true;
for (const program of this._basicPrograms) {
if (program.compile() !== 0 /* CompileStatus.Success */)
allCompiled = false;
}
for (const clipper of this._clippingPrograms)
if (!clipper.compile())
allCompiled = false;
return allCompiled;
}
finishConstruction() {
this._earlyZFlags.length = 0;
// Confirm no empty entries in our array.
let emptyShaderIndex = -1;
assert(-1 === (emptyShaderIndex = this._basicPrograms.findIndex((prog) => undefined === prog)), `Shader index ${emptyShaderIndex} is undefined in ${this.constructor.name}`);
}
_isDisposed = false;
get isDisposed() { return this._isDisposed; }
[Symbol.dispose]() {
if (this._isDisposed)
return;
for (const program of this._basicPrograms) {
assert(undefined !== program);
program[Symbol.dispose]();
}
this._basicPrograms.length = 0;
for (const clipShaderObj of this._clippingPrograms) {
assert(undefined !== clipShaderObj);
clipShaderObj[Symbol.dispose]();
}
this._clippingPrograms.length = 0;
this._isDisposed = true;
}
/** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */
dispose() {
this[Symbol.dispose]();
}
constructor(numPrograms) {
this._basicPrograms.length = numPrograms;
}
addShader(builder, flags, gl) {
const descr = `${this._debugDescription}: ${flags.buildDescription()}`;
builder.setDebugDescription(descr);
if (System.instance.supportsLogZBuffer) {
addLogDepth(builder);
assert(!builder.frag.requiresEarlyZWorkaround);
if (System.instance.fragDepthDoesNotDisableEarlyZ)
builder.frag.requiresEarlyZWorkaround = -1 !== this._earlyZFlags.findIndex((x) => x.equals(flags));
}
const index = this.getShaderIndex(flags);
this.addProgram(builder, index, gl);
assert(!builder.frag.requiresEarlyZWorkaround);
}
addProgram(builder, index, gl) {
assert(this._basicPrograms[index] === undefined);
this._basicPrograms[index] = builder.buildProgram(gl);
assert(this._basicPrograms[index] !== undefined);
// Clipping programs always include a discard, so never require workaround.
builder.frag.requiresEarlyZWorkaround = false;
assert(this._clippingPrograms[index] === undefined);
this._clippingPrograms[index] = createClippingProgram(builder);
assert(this._clippingPrograms[index] !== undefined);
}
addHiliteShader(gl, instanced, classified, posType, create) {
const builder = create(instanced, classified, posType);
scratchHiliteFlags.initForHilite(0, instanced, classified, posType);
this.addShader(builder, scratchHiliteFlags, gl);
}
addTranslucentShader(builder, flags, gl) {
flags.isTranslucent = true;
addTranslucency(builder);
this.addShader(builder, flags, gl);
}
addFeatureId(builder, feat) {
const frag = builder.frag;
if (0 /* FeatureMode.None */ === feat) {
addFragColorWithPreMultipliedAlpha(frag);
}
else {
const vert = builder.vert;
addFrustum(builder);
addEyeSpace(builder);
addModelViewMatrix(vert);
addRenderOrder(frag);
addFeatureId(builder, false);
addPickBufferOutputs(frag);
}
}
getShaderIndex(flags) {
assert(!flags.isHilite || (!flags.isTranslucent && (flags.isClassified === 1 /* IsClassified.Yes */ || flags.hasFeatures)), "invalid technique flags");
const index = this.computeShaderIndex(flags);
assert(index < this._basicPrograms.length, "shader index out of bounds");
return index;
}
getShader(flags) {
const index = this.getShaderIndex(flags);
let program;
if (flags.hasClip) {
const entry = this._clippingPrograms[index];
assert(undefined !== entry);
program = entry.getProgram(flags.numClipPlanes);
}
if (program === undefined)
program = this._basicPrograms[index];
return program;
}
// NB: Will ignore clipping shaders.
getShaderByIndex(index) {
return this._basicPrograms[index];
}
// NB: Will ignore clipping shaders.
getShaderCount() {
return this._basicPrograms.length;
}
/** For tests. */
forEachProgram(func) {
for (const basic of this._basicPrograms)
func(basic);
for (const clip of this._clippingPrograms) {
const prog = clip.getProgram(1);
assert(undefined !== prog);
func(prog);
}
}
}
const positionTypes = ["quantized", "unquantized"];
class SurfaceTechnique extends VariedTechnique {
static _kOpaque = 0;
static _kTranslucent = 1;
static _kInstanced = 2;
static _kAnimated = 4;
static _kWiremesh = 8;
static _kShadowable = 16;
static _kThematic = 32;
static _kFeature = 48;
static _kEdgeTestNeeded = SurfaceTechnique._kFeature * 3; // only when hasFeatures
static _kHilite = SurfaceTechnique._kEdgeTestNeeded + SurfaceTechnique._kFeature * 2;
// Classifiers are never animated or instanced. They do support shadows, thematic display, and translucency.
// There are 3 base variations - 1 per feature mode - each with translucent/shadowed/thematic variants; plus 1 for hilite.
static _kClassified = SurfaceTechnique._kHilite + numHiliteVariants;
// 3 base classified variations - 1 per feature mode.
// Plus thematic variant of each and shadowable variant of each = 9
// Plus translucent variant of each of those = 18
// Plus 1 hilite shader = 19
static _kUnquantized = SurfaceTechnique._kClassified + 19;
constructor(gl) {
super(SurfaceTechnique._kUnquantized * 2);
this._earlyZFlags = [
TechniqueFlags.fromDescription("Opaque-Hilite-Overrides"),
TechniqueFlags.fromDescription("Opaque-Instanced-Hilite-Overrides"),
TechniqueFlags.fromDescription("Opaque-Hilite-Classified"),
TechniqueFlags.fromDescription("Unquantized-Opaque-Hilite-Overrides"),
TechniqueFlags.fromDescription("Unquantized-Opaque-Instanced-Hilite-Overrides"),
TechniqueFlags.fromDescription("Unquantized-Opaque-Hilite-Classified"),
];
const flags = scratchTechniqueFlags;
for (const posType of positionTypes) {
for (let instanced = 0 /* IsInstanced.No */; instanced <= 1 /* IsInstanced.Yes */; instanced++) {
this.addHiliteShader(gl, instanced, 0 /* IsClassified.No */, posType, createSurfaceHiliter);
for (let iAnimate = 0 /* IsAnimated.No */; iAnimate <= 1 /* IsAnimated.Yes */; iAnimate++) {
for (let shadowable = 0 /* IsShadowable.No */; shadowable <= 1 /* IsShadowable.Yes */; shadowable++) {
for (let wiremesh = 0 /* IsWiremesh.No */; wiremesh <= 1 /* IsWiremesh.Yes */; wiremesh++) {
for (let thematic = 0 /* IsThematic.No */; thematic <= 1 /* IsThematic.Yes */; thematic++) {
for (let edgeTestNeeded = 0 /* IsEdgeTestNeeded.No */; edgeTestNeeded <= 1 /* IsEdgeTestNeeded.Yes */; edgeTestNeeded++) {
for (const featureMode of featureModes) {
for (let iTranslucent = 0; iTranslucent <= 1; iTranslucent++) {
if (0 /* FeatureMode.None */ !== featureMode || 0 /* IsEdgeTestNeeded.No */ === edgeTestNeeded) {
if (1 /* IsThematic.Yes */ === thematic && 1 /* IsShadowable.Yes */ === shadowable)
continue; // currently this combination is disallowed.
flags.reset(featureMode, instanced, shadowable, thematic, posType);
flags.isAnimated = iAnimate;
flags.isEdgeTestNeeded = edgeTestNeeded;
flags.isTranslucent = 1 === iTranslucent;
flags.isWiremesh = wiremesh;
const builder = createSurfaceBuilder(flags);
this.addShader(builder, flags, gl);
}
}
}
}
}
}
}
}
}
}
this.addHiliteShader(gl, 0 /* IsInstanced.No */, 1 /* IsClassified.Yes */, "quantized", createSurfaceHiliter);
this.addHiliteShader(gl, 0 /* IsInstanced.No */, 1 /* IsClassified.Yes */, "unquantized", createSurfaceHiliter);
for (const posType of positionTypes) {
for (let translucent = 0; translucent < 2; translucent++) {
for (let shadowable = 0 /* IsShadowable.No */; shadowable <= 1 /* IsShadowable.Yes */; shadowable++) {
for (let thematic = 0 /* IsThematic.No */; thematic <= 1 /* IsThematic.Yes */; thematic++) {
for (const featureMode of featureModes) {
if (1 /* IsThematic.Yes */ === thematic && 1 /* IsShadowable.Yes */ === shadowable)
continue; // currently this combination is disallowed.
flags.reset(featureMode, 0 /* IsInstanced.No */, shadowable, thematic, posType);
flags.isClassified = 1 /* IsClassified.Yes */;
flags.isTranslucent = (0 !== translucent);
const builder = createSurfaceBuilder(flags);
if (flags.isTranslucent)
addTranslucency(builder);
this.addShader(builder, flags, gl);
}
}
}
}
}
this.finishConstruction();
}
get _debugDescription() { return "Surface"; }
computeShaderIndex(flags) {
assert(!(flags.isThematic && flags.isShadowable));
const idxOffset = flags.positionType === "unquantized" ? SurfaceTechnique._kUnquantized : 0;
if (flags.isClassified) {
assert(!flags.isAnimated);
assert(!flags.isInstanced);
assert(!flags.isEdgeTestNeeded);
// First classified shader is for hilite
if (flags.isHilite)
return SurfaceTechnique._kClassified + idxOffset;
// The rest are organized in 3 groups of 6 - one group per feature mode.
// Each group contains opaque, translucent, opaque+thematic, translucent+thematic, opaque+shadowable, and translucent+shadowable variants.
let baseIndex = SurfaceTechnique._kClassified + 1;
if (flags.isTranslucent)
baseIndex += 1;
if (flags.isShadowable)
baseIndex += 2;
if (flags.isThematic)
baseIndex += 4;
const featureOffset = 6 * flags.featureMode;
return baseIndex + featureOffset + idxOffset;
}
else if (flags.isHilite) {
assert(flags.hasFeatures);
return SurfaceTechnique._kHilite + flags.isInstanced + idxOffset;
}
assert(flags.hasFeatures || flags.isEdgeTestNeeded === 0 /* IsEdgeTestNeeded.No */);
let index = flags.isTranslucent ? SurfaceTechnique._kTranslucent : SurfaceTechnique._kOpaque;
index += SurfaceTechnique._kInstanced * flags.isInstanced;
index += SurfaceTechnique._kAnimated * flags.isAnimated;
index += SurfaceTechnique._kShadowable * flags.isShadowable;
index += SurfaceTechnique._kThematic * flags.isThematic;
index += SurfaceTechnique._kWiremesh * flags.isWiremesh;
if (flags.isEdgeTestNeeded)
index += SurfaceTechnique._kEdgeTestNeeded + (flags.featureMode - 1) * SurfaceTechnique._kFeature;
else
index += SurfaceTechnique._kFeature * flags.featureMode;
return index + idxOffset;
}
}
class PolylineTechnique extends VariedTechnique {
static _kOpaque = 0;
static _kTranslucent = 1;
static _kInstanced = 2;
static _kFeature = 4;
static _kHilite = numFeatureVariants(PolylineTechnique._kFeature);
static _kUnquantized = PolylineTechnique._kHilite + numHiliteVariants;
constructor(gl) {
super(PolylineTechnique._kUnquantized * 2);
this._earlyZFlags = [
TechniqueFlags.fromDescription("Opaque-Hilite-Overrides"),
TechniqueFlags.fromDescription("Opaque-Instanced-Hilite-Overrides"),
TechniqueFlags.fromDescription("Unquantized-Opaque-Hilite-Overrides"),
TechniqueFlags.fromDescription("Unquantized-Opaque-Instanced-Hilite-Overrides"),
];
const flags = scratchTechniqueFlags;
for (const posType of positionTypes) {
for (let instanced = 0 /* IsInstanced.No */; instanced <= 1 /* IsInstanced.Yes */; instanced++) {
this.addHiliteShader(gl, instanced, 0 /* IsClassified.No */, posType, (inst, _class, pos) => createPolylineHiliter(inst, pos));
for (const featureMode of featureModes) {
flags.reset(featureMode, instanced, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, posType);
const builder = createPolylineBuilder(instanced, posType);
addUnlitMonochrome(builder.frag);
// The translucent shaders do not need the element IDs.
const builderTrans = createPolylineBuilder(instanced, posType);
addUnlitMonochrome(builderTrans.frag);
if (2 /* FeatureMode.Overrides */ === featureMode) {
addFeatureSymbology(builderTrans, featureMode, 31 /* FeatureSymbologyOptions.Linear */);
addFeatureSymbology(builder, featureMode, 31 /* FeatureSymbologyOptions.Linear */);
this.addTranslucentShader(builderTrans, flags, gl);
}
else {
this.addTranslucentShader(builderTrans, flags, gl);
addFeatureSymbology(builder, featureMode, 0 /* FeatureSymbologyOptions.None */);
}
this.addFeatureId(builder, featureMode);
flags.reset(featureMode, instanced, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, posType);
this.addShader(builder, flags, gl);
}
}
}
this.finishConstruction();
}
get _debugDescription() { return "Polyline"; }
computeShaderIndex(flags) {
const idxOffset = flags.positionType === "unquantized" ? PolylineTechnique._kUnquantized : 0;
if (flags.isHilite) {
assert(flags.hasFeatures);
return PolylineTechnique._kHilite + flags.isInstanced + idxOffset;
}
let index = flags.isTranslucent ? PolylineTechnique._kTranslucent : PolylineTechnique._kOpaque;
index += PolylineTechnique._kFeature * flags.featureMode;
index += PolylineTechnique._kInstanced * flags.isInstanced;
return index + idxOffset;
}
}
class EdgeTechnique extends VariedTechnique {
static _kOpaque = 0;
static _kTranslucent = 1;
static _kAnimated = 2;
static _kInstanced = 4;
static _kFeature = 8;
static _kUnquantized = numFeatureVariants(EdgeTechnique._kFeature);
_type;
constructor(gl, type) {
super(EdgeTechnique._kUnquantized * 2);
this._type = type;
const flags = scratchTechniqueFlags;
for (const posType of positionTypes) {
for (let instanced = 0 /* IsInstanced.No */; instanced <= 1 /* IsInstanced.Yes */; instanced++) {
for (let iAnimate = 0 /* IsAnimated.No */; iAnimate <= 1 /* IsAnimated.Yes */; iAnimate++) {
for (const featureMode of featureModes) {
flags.reset(featureMode, instanced, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, posType);
flags.isAnimated = iAnimate;
const builder = createEdgeBuilder(type, flags.isInstanced, flags.isAnimated, posType);
addUnlitMonochrome(builder.frag);
// The translucent shaders do not need the element IDs.
const builderTrans = createEdgeBuilder(type, flags.isInstanced, flags.isAnimated, posType);
addUnlitMonochrome(builderTrans.frag);
if (2 /* FeatureMode.Overrides */ === featureMode) {
addFeatureSymbology(builderTrans, featureMode, 31 /* FeatureSymbologyOptions.Linear */);
addFeatureSymbology(builder, featureMode, 31 /* FeatureSymbologyOptions.Linear */);
this.addTranslucentShader(builderTrans, flags, gl);
}
else {
this.addTranslucentShader(builderTrans, flags, gl);
addFeatureSymbology(builder, featureMode, 0 /* FeatureSymbologyOptions.None */);
}
this.addFeatureId(builder, featureMode);
flags.reset(featureMode, instanced, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, posType);
flags.isAnimated = iAnimate;
this.addShader(builder, flags, gl);
}
}
}
}
this.finishConstruction();
}
get _debugDescription() { return this._type; }
computeShaderIndex(flags) {
let index = flags.isTranslucent ? EdgeTechnique._kTranslucent : EdgeTechnique._kOpaque;
index += EdgeTechnique._kFeature * flags.featureMode;
if (flags.isAnimated)
index += EdgeTechnique._kAnimated;
if (flags.isInstanced)
index += EdgeTechnique._kInstanced;
if ("unquantized" === flags.positionType)
index += EdgeTechnique._kUnquantized;
return index;
}
}
class PointStringTechnique extends VariedTechnique {
static _kOpaque = 0;
static _kTranslucent = 1;
static _kInstanced = 2;
static _kFeature = 4;
static _kHilite = numFeatureVariants(PointStringTechnique._kFeature);
static _kUnquantized = PointStringTechnique._kHilite + numHiliteVariants;
constructor(gl) {
super(PointStringTechnique._kUnquantized * 2);
const flags = scratchTechniqueFlags;
for (const posType of positionTypes) {
for (let instanced = 0 /* IsInstanced.No */; instanced <= 1 /* IsInstanced.Yes */; instanced++) {
this.addHiliteShader(gl, instanced, 0 /* IsClassified.No */, posType, (inst, _class, pos) => createPointStringHiliter(inst, pos));
for (const featureMode of featureModes) {
flags.reset(featureMode, instanced, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, posType);
const builder = createPointStringBuilder(instanced, posType);
addUnlitMonochrome(builder.frag);
// The translucent shaders do not need the element IDs.
const builderTrans = createPointStringBuilder(instanced, posType);
addUnlitMonochrome(builderTrans.frag);
if (2 /* FeatureMode.Overrides */ === featureMode) {
addFeatureSymbology(builderTrans, featureMode, 29 /* FeatureSymbologyOptions.Point */);
addFeatureSymbology(builder, featureMode, 29 /* FeatureSymbologyOptions.Point */);
this.addTranslucentShader(builderTrans, flags, gl);
}
else {
this.addTranslucentShader(builderTrans, flags, gl);
addFeatureSymbology(builder, featureMode, 0 /* FeatureSymbologyOptions.None */);
}
this.addFeatureId(builder, featureMode);
flags.reset(featureMode, instanced, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, posType);
this.addShader(builder, flags, gl);
}
}
}
this.finishConstruction();
}
get _debugDescription() { return "PointString"; }
computeShaderIndex(flags) {
const idxOffset = "quantized" === flags.positionType ? 0 : PointStringTechnique._kUnquantized;
if (flags.isHilite) {
assert(flags.hasFeatures);
return PointStringTechnique._kHilite + flags.isInstanced + idxOffset;
}
let index = flags.isTranslucent ? PointStringTechnique._kTranslucent : PointStringTechnique._kOpaque;
index += PointStringTechnique._kFeature * flags.featureMode;
index += PointStringTechnique._kInstanced * flags.isInstanced;
return index + idxOffset;
}
}
class PointCloudTechnique extends VariedTechnique {
static _kHilite = 8;
constructor(gl) {
super(PointCloudTechnique._kHilite + 2);
for (let iClassified = 0 /* IsClassified.No */; iClassified <= 1 /* IsClassified.Yes */; iClassified++) {
this.addHiliteShader(gl, 0 /* IsInstanced.No */, iClassified, "quantized", (_inst, classified) => createPointCloudHiliter(classified));
const flags = scratchTechniqueFlags;
for (let thematic = 0 /* IsThematic.No */; thematic <= 1 /* IsThematic.Yes */; thematic++) {
const pointCloudFeatureModes = [0 /* FeatureMode.None */, 2 /* FeatureMode.Overrides */];
for (const featureMode of pointCloudFeatureModes) {
flags.reset(featureMode, 0 /* IsInstanced.No */, 0 /* IsShadowable.No */, thematic, "quantized");
flags.isClassified = iClassified;
const builder = createPointCloudBuilder(flags.isClassified, featureMode, thematic);
if (2 /* FeatureMode.Overrides */ === featureMode) {
addUniformFeatureSymbology(builder, true);
addColorOverrideMix(builder.vert);
builder.vert.set(7 /* VertexShaderComponent.ApplyFeatureColor */, mixFeatureColor);
}
this.addFeatureId(builder, featureMode);
this.addShader(builder, flags, gl);
}
}
}
this.finishConstruction();
}
get _debugDescription() { return "PointCloud"; }
computeShaderIndex(flags) {
assert(flags.positionType === "quantized", "Unquantized point cloud positions not currently supported");
if (flags.isHilite)
return PointCloudTechnique._kHilite + flags.isClassified;
else {
let ndx = 0;
if (flags.isClassified)
ndx++;
if (flags.featureMode !== 0 /* FeatureMode.None */)
ndx += 2;
if (flags.isThematic)
ndx += 4;
return ndx;
}
}
}
class RealityMeshTechnique extends VariedTechnique {
static _numVariants = 194;
constructor(gl) {
super(RealityMeshTechnique._numVariants);
this._earlyZFlags = [
TechniqueFlags.fromDescription("Opaque-Hilite-Overrides"),
TechniqueFlags.fromDescription("Opaque-Hilite-Classified"),
];
this.addHiliteShader(gl, 0 /* IsInstanced.No */, 0 /* IsClassified.No */, "quantized", createRealityMeshHiliter);
this.addHiliteShader(gl, 0 /* IsInstanced.No */, 1 /* IsClassified.Yes */, "quantized", createClassifierRealityMeshHiliter);
for (let iClassified = 0 /* IsClassified.No */; iClassified <= 1 /* IsClassified.Yes */; iClassified++) {
for (let iTranslucent = 0; iTranslucent <= 1; iTranslucent++) {
for (let shadowable = 0 /* IsShadowable.No */; shadowable <= 1 /* IsShadowable.Yes */; shadowable++) {
for (let thematic = 0 /* IsThematic.No */; thematic <= 1 /* IsThematic.Yes */; thematic++) {
for (let wiremesh = 0 /* IsWiremesh.No */; wiremesh <= 1 /* IsWiremesh.Yes */; wiremesh++) {
for (let enableAtmosphere = 0 /* EnableAtmosphere.No */; enableAtmosphere <= 1 /* EnableAtmosphere.Yes */; enableAtmosphere++) {
const flags = scratchTechniqueFlags;
for (const featureMode of featureModes) {
flags.reset(featureMode, 0 /* IsInstanced.No */, shadowable, thematic, "quantized");
flags.isClassified = iClassified;
flags.isWiremesh = wiremesh;
flags.isTranslucent = 1 === iTranslucent;
flags.enableAtmosphere = enableAtmosphere;
const builder = createRealityMeshBuilder(flags);
if (flags.isTranslucent) {
addShaderFlags(builder);
addTranslucency(builder);
}
else
this.addFeatureId(builder, featureMode);
this.addShader(builder, flags, gl);
}
}
}
}
}
}
}
this.finishConstruction();
}
get _debugDescription() { return "RealityMesh"; }
computeShaderIndex(flags) {
assert("quantized" === flags.positionType, "Unquantized reality mesh positions not currently supported.");
if (flags.isHilite)
return flags.isClassified ? 1 : 0;
let ndx = 2;
if (flags.isClassified)
ndx++;
if (flags.isShadowable)
ndx += 2;
if (flags.isTranslucent)
ndx += 4;
ndx += 8 * flags.featureMode;
if (flags.isThematic)
ndx += 24;
if (flags.isWiremesh)
ndx += 48;
if (flags.enableAtmosphere)
ndx += 96;
return ndx;
}
}
/**
* More generalized version of VariedTechnique, without assuming usage of clipping, logDepth, eyeSpace, etc.
* Similar to SingularTechnique in its simplicity, but with support for multiple shader programs per technique.
*/
class MultipleTechnique {
_programs = [];
_isDisposed = false;
get isDisposed() { return this._isDisposed; }
constructor(numPrograms) {
this._programs.length = numPrograms;
}
getShaderIndex(flags) {
assert(!flags.isHilite || (!flags.isTranslucent && (flags.isClassified === 1 /* IsClassified.Yes */ || flags.hasFeatures)), "invalid technique flags");
const index = this.computeShaderIndex(flags);
assert(index < this._programs.length, "shader index out of bounds");
return index;
}
getShader(flags) {
const index = this.getShaderIndex(flags);
let program;
if (program === undefined)
program = this._programs[index];
return program;
}
getShaderByIndex(index) {
return this._programs[index];
}
getShaderCount() {
return this._programs.length;
}
compileShaders() {
let allCompiled = true;
for (const program of this._programs) {
if (program.compile() !== 0 /* CompileStatus.Success */)
allCompiled = false;
}
return allCompiled;
}
[Symbol.dispose]() {
if (this._isDisposed)
return;
for (const program of this._programs) {
assert(undefined !== program);
dispose(program);
}
this._programs.length = 0;
this._isDisposed = true;
}
addShader(builder, flags, gl) {
const descr = `${this._debugDescription}: ${flags.buildDescription()}`;
builder.setDebugDescription(descr);
const index = this.getShaderIndex(flags);
this.addProgram(builder, index, gl);
assert(!builder.frag.requiresEarlyZWorkaround);
}
addProgram(builder, index, gl) {
assert(this._programs[index] === undefined);
this._programs[index] = builder.buildProgram(gl);
assert(this._programs[index] !== undefined);
}
finishConstruction() {
// Confirm no empty entries in our array.
let emptyShaderIndex = -1;
assert(-1 === (emptyShaderIndex = this._programs.findIndex((prog) => undefined === prog)), `Shader index ${emptyShaderIndex} is undefined in ${this.constructor.name}`);
}
}
class SkySphereTechnique extends MultipleTechnique {
static _numVariants = 2; // one binary flag (2 ** 1)
_isGradient;
constructor(gl, isGradient) {
super(SkySphereTechnique._numVariants);
this._isGradient = isGradient;
for (let enableAtmosphere = 0 /* EnableAtmosphere.No */; enableAtmosphere <= 1 /* EnableAtmosphere.Yes */; enableAtmosphere++) {
const tempFlags = scratchTechniqueFlags;
tempFlags.reset(0 /* FeatureMode.None */, 0 /* IsInstanced.No */, 0 /* IsShadowable.No */, 0 /* IsThematic.No */, "quantized");
tempFlags.enableAtmosphere = enableAtmosphere;
const builder = createSkySphereBuilder(isGradient, tempFlags);
this.addShader(builder, tempFlags, gl);
}
this.finishConstruction();
}
get _debugDescription() { return `SkySphere-${this._isGradient ? "Gradient" : "Texture"}`; }
computeShaderIndex(flags) {
let index = 0;
if (flags.enableAtmosphere)
index += 1 << 0;
return index;
}
}
const techniquesByPriority = [
// Compile these specific shader variations first because they seem most likely to be used immediately upon opening a file.
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 0 /* FeatureMode.None */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 0 /* IsEdgeTestNeeded.No */, isTranslucent: false } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 1 /* FeatureMode.Pick */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 0 /* IsEdgeTestNeeded.No */, isTranslucent: false } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 1 /* FeatureMode.Pick */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 1 /* IsEdgeTestNeeded.Yes */, isTranslucent: false } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 2 /* FeatureMode.Overrides */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 0 /* IsEdgeTestNeeded.No */, isTranslucent: false } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 2 /* FeatureMode.Overrides */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 1 /* IsEdgeTestNeeded.Yes */, isTranslucent: false } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 0 /* FeatureMode.None */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 0 /* IsEdgeTestNeeded.No */, isTranslucent: true } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 1 /* FeatureMode.Pick */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 0 /* IsEdgeTestNeeded.No */, isTranslucent: true } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 1 /* FeatureMode.Pick */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 1 /* IsEdgeTestNeeded.Yes */, isTranslucent: true } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 2 /* FeatureMode.Overrides */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 0 /* IsEdgeTestNeeded.No */, isTranslucent: true } },
{ techniqueId: 0 /* TechniqueId.Surface */, specificShader: { featureMode: 2 /* FeatureMode.Overrides */, isInstanced: 0 /* IsInstanced.No */, isShadowable: 0 /* IsShadowable.No */, isEdgeTestedNeeded: 1 /* IsEdgeTestNeeded.Yes */, isTranslucent: true } },
// Next, compile all shaders in specific techniques.
// Do surfaces first because (1) they are the most commonly used and (2) they take longer to compile.
{ techniqueId: 0 /* TechniqueId.Surface */ },
{ techniqueId: 4 /* TechniqueId.Edge */ },
{ techniqueId: 5 /* TechniqueId.SilhouetteEdge */ },
{ techniqueId: 1 /* TechniqueId.Polyline */ },
{ techniqueId: 3 /* TechniqueId.PointString */ },
{ techniqueId: 2 /* TechniqueId.PointCloud */ },
{ techniqueId: 7 /* TechniqueId.RealityMesh */ },
// The following techniques take a trivial amount of time to compile - do them last
{ techniqueId: 16 /* TechniqueId.OITClearTranslucent */ },
{ techniqueId: 17 /* TechniqueId.CopyPickBuffers */ },
{ techniqueId: 18 /* TechniqueId.CopyColor */ },
{ techniqueId: 19 /* TechniqueId.CopyColorNoAlpha */ },
{ techniqueId: 21 /* TechniqueId.ClearPickAndColor */ },
{ techniqueId: 10 /* TechniqueId.CompositeTranslucent */ },
{ techniqueId: 9 /* TechniqueId.CompositeHilite */ },
{ techniqueId: 11 /* TechniqueId.CompositeHiliteAndTranslucent */ },
{ techniqueId: 12 /* TechniqueId.CompositeOcclusion */ },
{ techniqueId: 13 /* TechniqueId.CompositeTranslucentAndOcclusion */ },
{ techniqueId: 14 /* TechniqueId.CompositeHiliteAndOcclusion */ },
{ techniqueId: 15 /* TechniqueId.CompositeAll */ },
{ techniqueId: 20 /* TechniqueId.VolClassColorUsingStencil */ },
{ techniqueId: 22 /* TechniqueId.EVSMFromDepth */ },
{ techniqueId: 23 /* TechniqueId.SkyBox */ },
{ techniqueId: 24 /* TechniqueId.SkySphereGradient */ },
{ techniqueId: 25 /* TechniqueId.SkySphereTexture */ },
{ techniqueId: 26 /* TechniqueId.AmbientOcclusion */ },
{ techniqueId: 27 /* TechniqueId.Blur */ },
{ techniqueId: 28 /* TechniqueId.BlurTestOrder */ },
{ techniqueId: 29 /* TechniqueId.CombineTextures */ },
{ techniqueId: 31 /* TechniqueId.VolClassCopyZ */ },
{ techniqueId: 32 /* TechniqueId.VolClassSetBlend */ },
{ techniqueId: 33 /* TechniqueId.VolClassBlend */ },
{ techniqueId: 30 /* TechniqueId.Combine3Textures */ },
{ techniqueId: 8 /* TechniqueId.PlanarGrid */ },
{ techniqueId: 34 /* TechniqueId.EDLCalcBasic */ },
{ techniqueId: 35 /* TechniqueId.EDLCalcFull */ },
{ techniqueId: 36 /* TechniqueId.EDLFilter */ },
{ techniqueId: 37 /* TechniqueId.EDLMix */ },
];
const numTechniquesByPriority = techniquesByPriority.length;
/** A collection of rendering techniques accessed by ID.
* @internal
*/
export class Techniques {
_list = new Array(); // indexed by TechniqueId, which may exceed TechniqueId.NumBuiltIn for dynamic techniques.
_dynamicTechniqueIds = new Array(); // technique ID = (index in this array) + TechniqueId.NumBuiltIn
_techniqueByPriorityIndex = 0;
_shaderIndex = 0;
static create(gl) {
const techs = new Techniques();
techs.initializeBuiltIns(gl);
return techs;
}
getTechnique(id) {
assert(id < this._list.length, "technique index out of bounds");
return this._list[id];
}
get numTechniques() {
return this._list.length;
}
addDynamicTechnique(technique, name) {
const id = this.getDynamicTechniqueId(name);
if (undefined !== id)
return id;
this._dynamicTechniqueIds.push(name);
this._list.push(technique);
return 38 /* TechniqueId.NumBuiltIn */ + this._dynamicTechniqueIds.length - 1;
}
getDynamicTechniqueId(name) {
const index = this._dynamicTechniqueIds.indexOf(name);
return -1 !== index ? index + 38 /* TechniqueId.NumBuiltIn */ + index : undefined;
}
/** Execute each command in the list */
execute(target, commands, renderPass) {
const env_1 = { stack: [], error: void 0, hasError: false };
try {
assert(255 /* RenderPass.None */ !== renderPass, "invalid render pass");
const executor = __addDisposableResource(env_1, new ShaderProgramExecutor(target, renderPass), false);
for (const command of commands)
command.execute(executor);
System.instance.frameBufferStack.markTargetsDirty();
}
catch (e_1) {
env_1.error = e_1;
env_1.hasError = true;
}
finally {
__disposeResources(env_1);
}
}
/** Execute the commands for a single given classification primitive (the first 3 commands should be a push, the primitive, then a pop) */
executeForIndexedClassifier(target, cmdsByIndex, renderPass) {
// ###TODO: Disable shadows. Probably in the ClassifierTileTree's ViewFlagOverrides.
this.execute(target, cmdsByIndex, renderPass);
}
/** Draw a single primitive. Usually used for special-purpose rendering techniques. */
draw(params) {
const env_2 = { stack: [], error: void 0, hasError: false };
try {
const tech = this.getTechnique(params.geometry.techniqueId);
const program = tech.getShader(TechniqueFlags.defaults);
const executor = __addDisposableResource(env_2, new ShaderProgramExecutor(params.target, params.renderPass, program), false);
assert(executor.isValid);
if (executor.isValid) {
executor.draw(params);
}
System.instance.frameBufferStack.markTargetsDirty();
}
catch (e_2) {
env_2.error = e_2;
env_2.hasError = true;
}
finally {
__disposeResources(env_2);
}
}
get isDisposed() { return 0 === this._list.length; }
[Symbol.dispose]() {
for (const tech of this._list)
dispose(tech);
this._list.length = 0;
}
/** @deprecated in 5.0 - will not be removed until after 2026-06-13. Use [Symbol.dispose] instead. */
dispose() {
this[Symbol.dispose]();
}
// Chiefly for tests - compiles all shader programs - more generally programs are compiled on demand.
compileShaders() {
let allCompiled = true;
for (const tech of this._list) {
if (!tech.compileShaders())
allCompiled = false;
}
return allCompiled;
}
/** Compile shader of next highest priority. Called when possible during an idle situation before any viewports exist. */
idleCompileNextShader() {
let compileStatus = 0 /* CompileStatus.Success */;
let wasPreviouslyCompiled = false;
do {
if (this._techniqueByPriorityIndex >= numTechniquesByPriority)
return false;
let shader;
let numShaders = 0;
const pTech = techniquesByPriority[this._techniqueByPriorityIndex];
const tech = this._list[pTech.techniqueId];
if (pTech.specificShader !== undefined) { // if this entry consists of a specific shader, just compile that
const flags = scratchTechniqueFlags;
flags.reset(pTech.specificShader.featureMode, pTech.specificShader.isInstanced, pTech.specificShader.isShadowable, 0 /* IsThematic.No */, "quantized");
flags.isEdgeTestNeeded = pTech.specificShader.isEdgeTestedNeeded;
flags.isTranslucent = pTech.specificShader.isTranslucent;
shader = tech.getShader(flags);
}
else { // if this entry only contains a techniqueId, then compile all uncompiled shaders for that technique
shader = tech.getShaderByIndex(this._shaderIndex);
this._shaderIndex++;
numShaders = tech.getShaderCount();
}
if (shader.isCompiled)
wasPreviouslyCompiled = true;
else {
compileStatus = shader.compile();
wasPreviouslyCompiled = false;
}
if (this._shaderIndex >= numShaders) {
this._techniqueByPriorityIndex++;
this._shaderIndex = 0;
}
} while (wasPreviouslyCompiled);
return compileStatus === 0 /* CompileStatus.Success */;
}
/** For tests. */
forEachVariedProgram(func) {
for (const technique of this._list)
if (technique instanceof VariedTechnique)
technique.forEachProgram(func);
}
constructor() { }
initializeBuiltIns(gl) {
this._list[16 /* TechniqueId.OITClearTranslucent */] = new SingularTechnique(createClearTranslucentProgram(gl));
this._list[21 /* TechniqueId.ClearPickAndColor */] = new SingularTechnique(createClearPickAndColorProgram(gl));
this._list[18 /* TechniqueId.CopyColor */] = new SingularTechnique(createCopyColorProgram(gl));
this._list[19 /* TechniqueId.CopyColorNoAlpha */] = new SingularTechnique(createCopyColorProgram(gl, false));
this._list[17 /* TechniqueId.CopyPickBuffers */] = new SingularTechnique(createCopyPickBuffersProgram(gl));
this._list[22 /* TechniqueId.EVSMFromDepth */] = new SingularTechnique(createEVSMProgram(gl));
this._list[23 /* TechniqueId.SkyBox */] = new SingularTechnique(createSkyBoxProgram(gl));
this._list[24 /* TechniqueId.SkySphereGradient */] = new SkySphereTechnique(gl, true);
this._list[25 /* TechniqueId.SkySphereTexture */] = new SkySphereTechnique(gl, false);
this._list[26 /* TechniqueId.AmbientOcclusion */] = new SingularTechnique(createAmbientOcclusionProgram(gl));
this._list[27 /* TechniqueId.Blur */] = new SingularTechnique(createBlurProgram(gl, BlurType.NoTest));
this._list[28 /* TechniqueId.BlurTestOrder */] = new SingularTechnique(createBlurProgram(gl, BlurType.TestO