@itwin/core-frontend
Version:
iTwin.js frontend components
762 lines • 35.5 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
*/
import { assert, BentleyStatus, Dictionary, dispose, Id64 } from "@itwin/core-bentley";
import { ColorDef, Gradient, ImageBuffer, ImageBufferFormat, ImageSourceFormat, IModelError, RenderMaterialParams, RenderTexture, TextureMapping, TextureTransparency } from "@itwin/core-common";
import { Range3d, Transform } from "@itwin/core-geometry";
import { Capabilities } from "@itwin/webgl-compatibility";
import { IModelApp } from "../../../IModelApp";
import { IModelConnection } from "../../../IModelConnection";
import { imageElementFromImageSource } from "../../../common/ImageUtil";
import { GraphicBranch } from "../../../render/GraphicBranch";
import { PrimitiveBuilder } from "../../../internal/render/PrimitiveBuilder";
import { RenderSystem, } from "../../../render/RenderSystem";
import { RenderDiagnostics } from "../RenderSystemDebugControl";
import { BackgroundMapDrape } from "./BackgroundMapDrape";
import { SkyBoxQuadsGeometry, SkySphereViewportQuadGeometry } from "./CachedGeometry";
import { ClipVolume } from "./ClipVolume";
import { Debug } from "./Diagnostics";
import { FrameBufferStack } from "./FrameBuffer";
import { GL } from "./GL";
import { GLTimer } from "./GLTimer";
import { AnimationTransformBranch, Batch, Branch, GraphicOwner, GraphicsArray } from "./Graphic";
import { InstanceBuffers, isInstancedGraphicParams, PatternBuffers, RenderInstancesImpl } from "./InstancedGeometry";
import { Layer, LayerContainer } from "./Layer";
import { LineCode } from "./LineCode";
import { Material } from "./Material";
import { MeshGraphic, MeshRenderGeometry } from "./Mesh";
import { PlanarGridGeometry } from "./PlanarGrid";
import { PointCloudGeometry } from "./PointCloud";
import { PointStringGeometry } from "./PointString";
import { PolylineGeometry } from "./Polyline";
import { Primitive, SkyCubePrimitive, SkySpherePrimitive } from "./Primitive";
import { RealityMeshGeometry } from "./RealityMesh";
import { RenderBufferMultiSample } from "./RenderBuffer";
import { TextureUnit } from "./RenderFlags";
import { RenderState } from "./RenderState";
import { createScreenSpaceEffectBuilder, ScreenSpaceEffects } from "./ScreenSpaceEffect";
import { OffScreenTarget, OnScreenTarget } from "./Target";
import { Techniques } from "./Technique";
import { ExternalTextureLoader, Texture, TextureHandle } from "./Texture";
import { _batch, _branch, _featureTable, _nodes } from "../../../common/internal/Symbols";
/** Id map holds key value pairs for both materials and textures, useful for caching such objects.
* @internal
*/
export class IdMap {
_iModel;
/** Mapping of materials by their key values. */
materials = new Map();
/** Mapping of textures by their key values. */
textures = new Map();
/** Mapping of textures using gradient symbology. */
gradients = new Dictionary((lhs, rhs) => Gradient.Symb.compareSymb(lhs, rhs));
/** Pending promises to create a texture from an ImageSource. This prevents us from decoding the same ImageSource multiple times */
texturesFromImageSources = new Map();
constructor(iModel) {
this._iModel = iModel;
}
get isDisposed() {
return 0 === this.textures.size && 0 === this.gradients.size;
}
[Symbol.dispose]() {
const textureArr = Array.from(this.textures.values());
const gradientArr = this.gradients.extractArrays().values;
for (const texture of textureArr)
dispose(texture);
for (const gradient of gradientArr)
dispose(gradient);
this.textures.clear();
this.gradients.clear();
this.materials.clear();
}
/** Add a material to this IdMap, given that it has a valid key. */
addMaterial(material) {
if (material.key)
this.materials.set(material.key, material);
}
/** Add a texture to this IdMap, given that it has a valid string key. If specified, it will instead use the key parameter, which could also be a gradient symb. */
addTexture(texture, key) {
assert(texture instanceof Texture);
if (undefined !== key) {
if ("string" === typeof key)
this.textures.set(key, texture);
else
this.addGradient(key, texture);
}
else if (texture.key)
this.textures.set(texture.key, texture);
}
/** Add a texture to this IdMap using gradient symbology. */
addGradient(gradientSymb, texture) {
this.gradients.set(gradientSymb, texture);
}
/** Find a cached material using its key. If not found, returns undefined. */
findMaterial(key) {
return this.materials.get(key);
}
/** Find a cached gradient using the gradient symbology. If not found, returns undefined. */
findGradient(symb) {
return this.gradients.get(symb);
}
/** Find or create a new material given material parameters. This will cache the material if its key is valid. */
getMaterial(params) {
if (!params.key || !Id64.isValidId64(params.key)) // Only cache persistent materials.
return new Material(params);
let material = this.materials.get(params.key);
if (!material) {
material = new Material(params);
this.materials.set(params.key, material);
}
return material;
}
findTexture(key) {
if (undefined === key)
return undefined;
else if (typeof key === "string")
return this.textures.get(key);
else
return this.findGradient(key);
}
getTextureFromElement(key, iModel, params, format) {
let tex = this.findTexture(params.key);
if (tex)
return tex;
const handle = TextureHandle.createForElement(key, iModel, params.type, format, (_, data) => {
if (tex) {
assert(tex instanceof Texture);
tex.transparency = data.transparency ?? TextureTransparency.Mixed;
}
});
if (!handle)
return undefined;
tex = new Texture({ handle, type: params.type, ownership: { key, iModel }, transparency: TextureTransparency.Opaque });
this.addTexture(tex);
return tex;
}
async getTextureFromImageSource(args, key) {
const texture = this.findTexture(key);
if (texture)
return texture;
// Are we already in the process of decoding this image?
let promise = this.texturesFromImageSources.get(key);
if (promise)
return promise;
promise = this.createTextureFromImageSource(args, key);
this.texturesFromImageSources.set(key, promise);
return promise;
}
async createTextureFromImageSource(args, key) {
// JPEGs don't support transparency.
const transparency = ImageSourceFormat.Jpeg === args.source.format ? TextureTransparency.Opaque : (args.transparency ?? TextureTransparency.Mixed);
try {
const image = await imageElementFromImageSource(args.source);
if (!IModelApp.hasRenderSystem)
return undefined;
return IModelApp.renderSystem.createTexture({
type: args.type,
ownership: args.ownership,
image: {
source: image,
transparency,
},
});
}
catch {
return undefined;
}
finally {
this.texturesFromImageSources.delete(key);
}
}
getTextureFromCubeImages(posX, negX, posY, negY, posZ, negZ, params) {
let tex = this.findTexture(params.key);
if (tex)
return tex;
const handle = TextureHandle.createForCubeImages(posX, negX, posY, negY, posZ, negZ);
if (!handle)
return undefined;
const ownership = params.key ? { key: params.key, iModel: this._iModel } : (params.isOwned ? "external" : undefined);
tex = new Texture({ handle, ownership, type: params.type, transparency: TextureTransparency.Opaque });
this.addTexture(tex);
return tex;
}
collectStatistics(stats) {
for (const texture of this.textures.values())
if (texture instanceof Texture)
stats.addTexture(texture.bytesUsed);
for (const gradient of this.gradients)
if (gradient instanceof Texture)
stats.addTexture(gradient.bytesUsed);
}
}
function getMaterialColor(color) {
if (color instanceof ColorDef)
return color;
return color ? ColorDef.from(color.r, color.g, color.b) : undefined;
}
/** @internal */
export class System extends RenderSystem {
canvas;
currentRenderState = new RenderState();
context;
frameBufferStack = new FrameBufferStack(); // frame buffers are not owned by the system
_capabilities;
resourceCache;
glTimer;
_textureBindings = [];
_removeEventListener;
// NB: Increase the size of these arrays when the maximum number of attributes used by any one shader increases.
_curVertexAttribStates = [
0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */,
0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */,
0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */,
];
_nextVertexAttribStates = [
0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */,
0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */,
0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */, 0 /* VertexAttribState.Disabled */,
];
// The following are initialized immediately after the System is constructed.
_lineCodeTexture;
_noiseTexture;
_techniques;
_screenSpaceEffects;
debugShaderFiles = [];
static get instance() { return IModelApp.renderSystem; }
get isValid() { return this.canvas !== undefined; }
get lineCodeTexture() { return this._lineCodeTexture; }
get noiseTexture() { return this._noiseTexture; }
get techniques() {
assert(undefined !== this._techniques);
return this._techniques;
}
get screenSpaceEffects() {
assert(undefined !== this._screenSpaceEffects);
return this._screenSpaceEffects;
}
get maxTextureSize() { return this._capabilities.maxTextureSize; }
get supportsCreateImageBitmap() { return this._capabilities.supportsCreateImageBitmap; }
get maxRenderType() { return this._capabilities.maxRenderType; }
get fragDepthDoesNotDisableEarlyZ() { return this._capabilities.driverBugs.fragDepthDoesNotDisableEarlyZ; }
get maxAntialiasSamples() { return this._capabilities.maxAntialiasSamples; }
get supportsNonPowerOf2Textures() { return this._capabilities.supportsNonPowerOf2Textures; }
get maxTexSizeAllow() { return this._capabilities.maxTexSizeAllow; }
get disjointTimerQuery() {
const ext = this._capabilities.queryExtensionObject("EXT_disjoint_timer_query_webgl2");
return ext ?? this._capabilities.queryExtensionObject("EXT_disjoint_timer_query");
}
get isMobile() { return this._capabilities.isMobile; }
setDrawBuffers(attachments) {
this.context.drawBuffers(attachments);
}
doIdleWork() {
return this.techniques.idleCompileNextShader();
}
/** Return a Promise which when resolved indicates that all pending external textures have finished loading from the backend. */
async waitForAllExternalTextures() {
const extTexLoader = ExternalTextureLoader.instance;
if (extTexLoader.numActiveRequests < 1 && extTexLoader.numPendingRequests < 1)
return Promise.resolve();
const promise = new Promise((resolve) => {
extTexLoader.onTexturesLoaded.addOnce(() => {
resolve();
});
});
return promise;
}
get hasExternalTextureRequests() {
const loader = ExternalTextureLoader.instance;
return loader.numActiveRequests > 0 || loader.numPendingRequests > 0;
}
/** Attempt to create a WebGLRenderingContext, returning undefined if unsuccessful. */
static createContext(canvas, useWebGL2, inputContextAttributes) {
if (!useWebGL2)
return undefined; // WebGL 2 is required.
let contextAttributes = { powerPreference: "high-performance" };
if (undefined !== inputContextAttributes) {
// NOTE: Order matters with spread operator - if caller wants to override powerPreference, he should be able to.
contextAttributes = { ...contextAttributes, ...inputContextAttributes };
}
const context = canvas.getContext("webgl2", contextAttributes);
return context ?? undefined;
}
static create(optionsIn) {
const options = undefined !== optionsIn ? optionsIn : {};
const canvas = document.createElement("canvas");
if (null === canvas)
throw new IModelError(BentleyStatus.ERROR, "Failed to obtain HTMLCanvasElement");
const context = this.createContext(canvas, true, optionsIn?.contextAttributes);
if (undefined === context)
throw new IModelError(BentleyStatus.ERROR, "Failed to obtain WebGL context");
if (!(context instanceof WebGL2RenderingContext))
throw new IModelError(BentleyStatus.ERROR, "WebGL 2 support is required");
const capabilities = Capabilities.create(context, options.disabledExtensions);
if (undefined === capabilities)
throw new IModelError(BentleyStatus.ERROR, "Failed to initialize rendering capabilities");
// set actual gl state to match desired state defaults
context.depthFunc(GL.DepthFunc.Default); // LessOrEqual
return new this(canvas, context, capabilities, options);
}
get isDisposed() {
return undefined === this._techniques
&& undefined === this._lineCodeTexture
&& undefined === this._noiseTexture
&& undefined === this._screenSpaceEffects;
}
// Note: FrameBuffers inside of the FrameBufferStack are not owned by the System, and are only used as a central storage device
dispose() {
this._techniques = dispose(this._techniques);
this._screenSpaceEffects = dispose(this._screenSpaceEffects);
this._lineCodeTexture = dispose(this._lineCodeTexture);
this._noiseTexture = dispose(this._noiseTexture);
// We must attempt to dispose of each idmap in the resourceCache (if idmap is already disposed, has no effect)
this.resourceCache.forEach((idMap) => {
dispose(idMap);
});
this.resourceCache.clear();
if (undefined !== this._removeEventListener) {
this._removeEventListener();
this._removeEventListener = undefined;
}
}
onInitialized() {
this._techniques = Techniques.create(this.context);
const noiseDim = 4;
const noiseArr = new Uint8Array([152, 235, 94, 173, 219, 215, 115, 176, 73, 205, 43, 201, 10, 81, 205, 198]);
this._noiseTexture = TextureHandle.createForData(noiseDim, noiseDim, noiseArr, false, GL.Texture.WrapMode.Repeat, GL.Texture.Format.Luminance);
assert(undefined !== this._noiseTexture, "System.noiseTexture not created.");
this._lineCodeTexture = TextureHandle.createForData(LineCode.size, LineCode.count, new Uint8Array(LineCode.lineCodeData), false, GL.Texture.WrapMode.Repeat, GL.Texture.Format.Luminance);
assert(undefined !== this._lineCodeTexture, "System.lineCodeTexture not created.");
this._screenSpaceEffects = new ScreenSpaceEffects();
}
createTarget(canvas) {
return new OnScreenTarget(canvas);
}
createOffscreenTarget(rect) {
return new OffScreenTarget(rect);
}
createGraphic(options) {
return new PrimitiveBuilder(this, options);
}
createPlanarGrid(frustum, grid) {
return PlanarGridGeometry.create(frustum, grid, this);
}
createTerrainMesh(params, transform, disableTextureDisposal = false) {
return RealityMeshGeometry.createForTerrain(params, transform, disableTextureDisposal);
}
createRealityMeshGraphic(params, disableTextureDisposal = false) {
return RealityMeshGeometry.createGraphic(this, params, disableTextureDisposal);
}
createRealityMeshGeometry(realityMesh, disableTextureDisposal = false) {
return RealityMeshGeometry.createFromRealityMesh(realityMesh, disableTextureDisposal);
}
createMeshGeometry(params, viOrigin) {
return MeshRenderGeometry.create(params, viOrigin);
}
createPolylineGeometry(params, viOrigin) {
return PolylineGeometry.create(params, viOrigin);
}
createPointStringGeometry(params, viOrigin) {
return PointStringGeometry.create(params, viOrigin);
}
createAreaPattern(params) {
return PatternBuffers.create(params);
}
createRenderInstances(params) {
return RenderInstancesImpl.create(params);
}
createInstancedGraphic(geometry, instances) {
const geom = geometry;
return this.createRenderGraphic(geom, InstanceBuffers.fromRenderInstances(instances, geom.computeRange()));
}
createGraphicFromTemplate(args) {
const template = args.template;
const instances = args.instances;
if (instances && !template.isInstanceable) {
throw new Error("GraphicTemplate is not instanceable");
}
const graphics = [];
for (const node of template[_nodes]) {
const nodeGraphics = [];
for (const geometry of node.geometry) {
const gf = instances ? this.createInstancedGraphic(geometry, instances) : this.createRenderGraphic(geometry);
if (gf) {
nodeGraphics.push(gf);
}
}
if (nodeGraphics.length === 0) {
continue;
}
if (node.transform) {
const branch = new GraphicBranch();
for (const gf of nodeGraphics) {
branch.add(gf);
}
graphics.push(this.createGraphicBranch(branch, node.transform));
}
else {
graphics.push(this.createGraphicList(nodeGraphics));
}
}
let graphic = this.createGraphicList(graphics);
if (instances && instances[_featureTable]) {
const range = new Range3d();
graphic.unionRange(range);
graphic = this.createBatch(graphic, instances[_featureTable], range);
}
else if (template[_batch]) {
graphic = this.createBatch(graphic, template[_batch].featureTable, template[_batch].range, template[_batch].options);
}
const templateBranch = template[_branch];
if (templateBranch) {
const branch = new GraphicBranch(true);
if (templateBranch.viewFlagOverrides) {
branch.setViewFlagOverrides(templateBranch.viewFlagOverrides);
}
branch.add(graphic);
graphic = this.createBranch(branch, templateBranch.transform ?? Transform.createIdentity());
}
return graphic;
}
createRenderGraphic(geometry, instances) {
const geom = geometry;
let buffers;
if (instances) {
if (!geometry.isInstanceable) {
throw new Error("RenderGeometry is not instanceable");
}
if (instances instanceof PatternBuffers || instances instanceof InstanceBuffers) {
buffers = instances;
}
else {
assert(isInstancedGraphicParams(instances));
buffers = InstanceBuffers.fromParams(instances, () => geom.computeRange());
if (!buffers) {
return undefined;
}
}
}
return geom.renderGeometryType === "mesh" ? MeshGraphic.create(geom, buffers) : Primitive.create(geom, buffers);
}
createPointCloudGeometry(args) {
return new PointCloudGeometry(args);
}
createGraphicList(primitives) {
return primitives.length === 1 ? primitives[0] : new GraphicsArray(primitives);
}
createGraphicBranch(branch, transform, options) {
return new Branch(branch, transform, undefined, options);
}
createAnimationTransformNode(graphic, nodeId) {
return new AnimationTransformBranch(graphic, nodeId);
}
createBatch(graphic, features, range, options) {
return new Batch(graphic, features, range, options);
}
createGraphicOwner(owned) {
return new GraphicOwner(owned);
}
createGraphicLayer(graphic, layerId) {
return new Layer(graphic, layerId);
}
createGraphicLayerContainer(graphic, drawAsOverlay, transparency, elevation) {
return new LayerContainer(graphic, drawAsOverlay, transparency, elevation);
}
createSkyBox(params) {
if ("cube" === params.type)
return SkyCubePrimitive.create(SkyBoxQuadsGeometry.create(params.texture));
return SkySpherePrimitive.create(SkySphereViewportQuadGeometry.createGeometry(params));
}
createScreenSpaceEffectBuilder(params) {
return createScreenSpaceEffectBuilder(params);
}
applyRenderState(newState) {
newState.apply(this.currentRenderState);
this.currentRenderState.copyFrom(newState);
}
createDepthBuffer(width, height, numSamples = 1) {
// Note: The buffer/texture created here have ownership passed to the caller (system will not dispose of these)
if (numSamples > 1)
return RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.DEPTH24_STENCIL8, numSamples);
else
return TextureHandle.createForAttachment(width, height, GL.Texture.Format.DepthStencil, this.context.UNSIGNED_INT_24_8);
}
/** Returns the corresponding IdMap for an IModelConnection. Creates a new one if it doesn't exist. */
createIModelMap(imodel) {
let idMap = this.resourceCache.get(imodel);
if (!idMap) {
idMap = new IdMap(imodel);
this.resourceCache.set(imodel, idMap);
}
return idMap;
}
/** Removes an IModelConnection-IdMap pairing from the system's resource cache. */
removeIModelMap(imodel) {
const idMap = this.resourceCache.get(imodel);
if (idMap === undefined)
return;
dispose(idMap);
this.resourceCache.delete(imodel);
}
createRenderMaterial(args) {
if (args.source) {
const cached = this.findMaterial(args.source.id, args.source.iModel);
if (cached)
return cached;
}
const params = new RenderMaterialParams();
params.alpha = args.alpha;
if (undefined !== args.diffuse?.weight)
params.diffuse = args.diffuse.weight;
params.diffuseColor = getMaterialColor(args.diffuse?.color);
if (args.specular) {
params.specularColor = getMaterialColor(args.specular?.color);
if (undefined !== args.specular.weight)
params.specular = args.specular.weight;
if (undefined !== args.specular.exponent)
params.specularExponent = args.specular.exponent;
}
if (args.textureMapping) {
params.textureMapping = new TextureMapping(args.textureMapping.texture, new TextureMapping.Params({
textureMat2x3: args.textureMapping.transform,
mapMode: args.textureMapping.mode,
textureWeight: args.textureMapping.weight,
worldMapping: args.textureMapping.worldMapping,
useConstantLod: args.textureMapping.useConstantLod,
constantLodProps: args.textureMapping.constantLodProps,
}));
params.textureMapping.normalMapParams = args.textureMapping.normalMapParams;
}
if (args.source) {
params.key = args.source.id;
return this.getIdMap(args.source.iModel).getMaterial(params);
}
else {
return new Material(params);
}
}
/** Using its key, search for an existing material of an open iModel. */
findMaterial(key, imodel) {
const idMap = this.resourceCache.get(imodel);
if (!idMap)
return undefined;
return idMap.findMaterial(key);
}
getTextureCacheInfo(args) {
const owner = undefined !== args.ownership && args.ownership !== "external" ? args.ownership : undefined;
return owner ? { idMap: this.getIdMap(owner.iModel), key: owner.key } : undefined;
}
createTexture(args) {
const info = this.getTextureCacheInfo(args);
const existing = info?.idMap.findTexture(info?.key);
if (existing)
return existing;
const type = args.type ?? RenderTexture.Type.Normal;
const source = args.image.source;
let handle;
if (source instanceof ImageBuffer)
handle = TextureHandle.createForImageBuffer(source, type);
else if (source instanceof ImageBitmap)
handle = TextureHandle.createForImageBitmap(source, type);
else if (source instanceof HTMLImageElement)
handle = TextureHandle.createForImage(source, type);
else
assert(false);
if (!handle)
return undefined;
const texture = new Texture({ handle, type, ownership: args.ownership, transparency: args.image.transparency ?? TextureTransparency.Mixed });
if (texture && info)
info.idMap.addTexture(texture, info.key);
return texture;
}
async createTextureFromSource(args) {
if (typeof args.ownership !== "object")
return super.createTextureFromSource(args);
return this.getIdMap(args.ownership.iModel).getTextureFromImageSource(args, args.ownership.key);
}
createTextureFromElement(id, imodel, params, format) {
return this.getIdMap(imodel).getTextureFromElement(id, imodel, params, format);
}
createTextureFromCubeImages(posX, negX, posY, negY, posZ, negZ, imodel, params) {
return this.getIdMap(imodel).getTextureFromCubeImages(posX, negX, posY, negY, posZ, negZ, params);
}
/** Attempt to create a texture using gradient symbology. */
getGradientTexture(symb, iModel) {
let width = 0x100;
let height = 0x100;
if (symb.mode === Gradient.Mode.Thematic) {
// Pixels in each row are identical, no point in having width > 1.
width = 1;
// We want maximum height to minimize bleeding of margin color.
height = this.maxTextureSize;
}
const source = symb.produceImage({ width, height, includeThematicMargin: true });
return this.createTexture({
image: {
source,
transparency: ImageBufferFormat.Rgba === source.format ? TextureTransparency.Mixed : TextureTransparency.Opaque,
},
ownership: iModel ? { iModel, key: symb } : undefined,
type: RenderTexture.Type.Normal,
});
}
/** Using its key, search for an existing texture of an open iModel. */
findTexture(key, imodel) {
const idMap = this.resourceCache.get(imodel);
if (!idMap)
return undefined;
return idMap.findTexture(key);
}
createClipVolume(clipVector) {
return ClipVolume.create(clipVector);
}
createBackgroundMapDrape(drapedTree, mapTree) {
return BackgroundMapDrape.create(drapedTree, mapTree);
}
constructor(canvas, context, capabilities, options) {
super(options);
this.canvas = canvas;
this.context = context;
this._capabilities = capabilities;
this.resourceCache = new Map();
this.glTimer = GLTimer.create(this);
// Make this System a subscriber to the the IModelConnection onClose event
this._removeEventListener = IModelConnection.onClose.addListener((imodel) => this.removeIModelMap(imodel));
canvas.addEventListener("webglcontextlost", async () => RenderSystem.contextLossHandler(), false);
}
/** Exposed strictly for tests. */
getIdMap(imodel) {
const map = this.resourceCache.get(imodel);
return undefined !== map ? map : this.createIModelMap(imodel);
}
bindTexture(unit, target, texture, makeActive) {
const index = unit - TextureUnit.Zero;
if (this._textureBindings[index] === texture) {
if (makeActive)
this.context.activeTexture(unit);
return;
}
this._textureBindings[index] = texture;
this.context.activeTexture(unit);
this.context.bindTexture(target, undefined !== texture ? texture : null);
}
/** Bind the specified texture to the specified unit. This may *or may not* make the texture *active* */
bindTexture2d(unit, texture) { this.bindTexture(unit, GL.Texture.Target.TwoDee, texture, false); }
/** Bind the specified texture to the specified unit. This may *or may not* make the texture *active* */
bindTextureCubeMap(unit, texture) { this.bindTexture(unit, GL.Texture.Target.CubeMap, texture, false); }
/** Bind the specified texture to the specified unit. This *always* makes the texture *active* */
activateTexture2d(unit, texture) { this.bindTexture(unit, GL.Texture.Target.TwoDee, texture, true); }
/** Bind the specified texture to the specified unit. This *always* makes the texture *active* */
activateTextureCubeMap(unit, texture) { this.bindTexture(unit, GL.Texture.Target.CubeMap, texture, true); }
// Ensure *something* is bound to suppress 'no texture assigned to unit x' warnings.
ensureSamplerBound(uniform, unit) {
this.lineCodeTexture.bindSampler(uniform, unit);
}
get maxRealityImageryLayers() {
return 6;
}
disposeTexture(texture) {
System.instance.context.deleteTexture(texture);
for (let i = 0; i < this._textureBindings.length; i++) {
if (this._textureBindings[i] === texture) {
this._textureBindings[i] = undefined;
break;
}
}
}
// System keeps track of current enabled state of vertex attribute arrays.
// This prevents errors caused by leaving a vertex attrib array enabled after disposing of the buffer bound to it;
// also prevents unnecessarily 'updating' the enabled state of a vertex attrib array when it hasn't actually changed.
enableVertexAttribArray(id, instanced) {
assert(id < this._nextVertexAttribStates.length, "if you add new vertex attributes you must update array length");
assert(id < this._curVertexAttribStates.length, "if you add new vertex attributes you must update array length");
this._nextVertexAttribStates[id] = instanced ? 5 /* VertexAttribState.InstancedEnabled */ : 1 /* VertexAttribState.Enabled */;
}
updateVertexAttribArrays() {
const cur = this._curVertexAttribStates;
const next = this._nextVertexAttribStates;
const context = this.context;
for (let i = 0; i < next.length; i++) {
const oldState = cur[i];
const newState = next[i];
if (oldState !== newState) {
// Update the enabled state if it changed.
const wasEnabled = 0 !== (1 /* VertexAttribState.Enabled */ & oldState);
const nowEnabled = 0 !== (1 /* VertexAttribState.Enabled */ & newState);
if (wasEnabled !== nowEnabled) {
if (nowEnabled) {
context.enableVertexAttribArray(i);
}
else {
context.disableVertexAttribArray(i);
}
}
// Only update the divisor if the attribute is enabled.
if (nowEnabled) {
const wasInstanced = 0 !== (4 /* VertexAttribState.Instanced */ & oldState);
const nowInstanced = 0 !== (4 /* VertexAttribState.Instanced */ & newState);
if (wasInstanced !== nowInstanced) {
this.vertexAttribDivisor(i, nowInstanced ? 1 : 0);
}
}
cur[i] = newState;
}
// Set the attribute back to disabled, but preserve the divisor.
next[i] &= ~1 /* VertexAttribState.Enabled */;
}
}
vertexAttribDivisor(index, divisor) {
this.context.vertexAttribDivisor(index, divisor);
}
drawArrays(type, first, count, numInstances) {
if (0 !== numInstances) {
this.context.drawArraysInstanced(type, first, count, numInstances);
}
else {
this.context.drawArrays(type, first, count);
}
}
invalidateFrameBuffer(attachments) {
this.context.invalidateFramebuffer(this.context.FRAMEBUFFER, attachments);
}
enableDiagnostics(enable) {
enable = enable ?? RenderDiagnostics.All;
Debug.printEnabled = RenderDiagnostics.None !== (enable & RenderDiagnostics.DebugOutput);
Debug.evaluateEnabled = RenderDiagnostics.None !== (enable & RenderDiagnostics.WebGL);
}
// RenderSystemDebugControl
get debugControl() { return this; }
_dpiAwareLOD;
get dpiAwareLOD() { return this._dpiAwareLOD ?? super.dpiAwareLOD; }
set dpiAwareLOD(dpiAware) { this._dpiAwareLOD = dpiAware; }
loseContext() {
const ext = this._capabilities.queryExtensionObject("WEBGL_lose_context");
if (undefined === ext)
return false;
ext.loseContext();
return true;
}
compileAllShaders() {
return this.techniques.compileShaders();
}
get isGLTimerSupported() { return this.glTimer.isSupported; }
set resultsCallback(callback) {
this.glTimer.resultsCallback = callback;
}
collectStatistics(stats) {
if (undefined !== this._lineCodeTexture)
stats.addTexture(this._lineCodeTexture.bytesUsed);
if (undefined !== this._noiseTexture)
stats.addTexture(this._noiseTexture.bytesUsed);
for (const idMap of this.resourceCache.values())
idMap.collectStatistics(stats);
}
setMaxAnisotropy(max) {
this._capabilities.setMaxAnisotropy(max, this.context);
}
}
//# sourceMappingURL=System.js.map