"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.System = exports.IdMap = void 0;
const core_bentley_1 = require("@itwin/core-bentley");
const core_common_1 = require("@itwin/core-common");
const webgl_compatibility_1 = require("@itwin/webgl-compatibility");
const IModelApp_1 = require("../../IModelApp");
const IModelConnection_1 = require("../../IModelConnection");
const ImageUtil_1 = require("../../common/ImageUtil");
const PrimitiveBuilder_1 = require("../../internal/render/PrimitiveBuilder");
const RenderSystem_1 = require("../RenderSystem");
const BackgroundMapDrape_1 = require("./BackgroundMapDrape");
const CachedGeometry_1 = require("./CachedGeometry");
const ClipVolume_1 = require("./ClipVolume");
const Diagnostics_1 = require("./Diagnostics");
const FrameBuffer_1 = require("./FrameBuffer");
const GL_1 = require("./GL");
const GLTimer_1 = require("./GLTimer");
const Graphic_1 = require("./Graphic");
const InstancedGeometry_1 = require("./InstancedGeometry");
const Layer_1 = require("./Layer");
const LineCode_1 = require("./LineCode");
const Material_1 = require("./Material");
const Mesh_1 = require("./Mesh");
const PlanarGrid_1 = require("./PlanarGrid");
const PointCloud_1 = require("./PointCloud");
const PointString_1 = require("./PointString");
const Polyline_1 = require("./Polyline");
const Primitive_1 = require("./Primitive");
const RealityMesh_1 = require("./RealityMesh");
const RenderBuffer_1 = require("./RenderBuffer");
const RenderFlags_1 = require("./RenderFlags");
const RenderState_1 = require("./RenderState");
const ScreenSpaceEffect_1 = require("./ScreenSpaceEffect");
const Target_1 = require("./Target");
const Technique_1 = require("./Technique");
const Texture_1 = require("./Texture");
/** Id map holds key value pairs for both materials and textures, useful for caching such objects.
 * @internal
 */
class IdMap {
    constructor(iModel) {
        /** Mapping of materials by their key values. */
        this.materials = new Map();
        /** Mapping of textures by their key values. */
        this.textures = new Map();
        /** Mapping of textures using gradient symbology. */
        this.gradients = new core_bentley_1.Dictionary((lhs, rhs) => core_common_1.Gradient.Symb.compareSymb(lhs, rhs));
        /** Pending promises to create a texture from an ImageSource. This prevents us from decoding the same ImageSource multiple times */
        this.texturesFromImageSources = new Map();
        this._iModel = iModel;
    }
    get isDisposed() {
        return 0 === this.textures.size && 0 === this.gradients.size;
    }
    dispose() {
        const textureArr = Array.from(this.textures.values());
        const gradientArr = this.gradients.extractArrays().values;
        for (const texture of textureArr)
            (0, core_bentley_1.dispose)(texture);
        for (const gradient of gradientArr)
            (0, core_bentley_1.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) {
        (0, core_bentley_1.assert)(texture instanceof Texture_1.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. */
    // eslint-disable-next-line deprecation/deprecation
    getMaterial(params) {
        if (!params.key || !core_bentley_1.Id64.isValidId64(params.key)) // Only cache persistent materials.
            return new Material_1.Material(params);
        let material = this.materials.get(params.key);
        if (!material) {
            material = new Material_1.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);
    }
    // eslint-disable-next-line deprecation/deprecation
    getTextureFromElement(key, iModel, params, format) {
        let tex = this.findTexture(params.key);
        if (tex)
            return tex;
        const handle = Texture_1.TextureHandle.createForElement(key, iModel, params.type, format, (_, data) => {
            if (tex) {
                (0, core_bentley_1.assert)(tex instanceof Texture_1.Texture);
                tex.transparency = data.transparency ?? core_common_1.TextureTransparency.Mixed;
            }
        });
        if (!handle)
            return undefined;
        tex = new Texture_1.Texture({ handle, type: params.type, ownership: { key, iModel }, transparency: core_common_1.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 = core_common_1.ImageSourceFormat.Jpeg === args.source.format ? core_common_1.TextureTransparency.Opaque : (args.transparency ?? core_common_1.TextureTransparency.Mixed);
        try {
            const image = await (0, ImageUtil_1.imageElementFromImageSource)(args.source);
            if (!IModelApp_1.IModelApp.hasRenderSystem)
                return undefined;
            return IModelApp_1.IModelApp.renderSystem.createTexture({
                type: args.type,
                ownership: args.ownership,
                image: {
                    source: image,
                    transparency,
                },
            });
        }
        catch {
            return undefined;
        }
        finally {
            this.texturesFromImageSources.delete(key);
        }
    }
    // eslint-disable-next-line deprecation/deprecation
    getTextureFromCubeImages(posX, negX, posY, negY, posZ, negZ, params) {
        let tex = this.findTexture(params.key);
        if (tex)
            return tex;
        const handle = Texture_1.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_1.Texture({ handle, ownership, type: params.type, transparency: core_common_1.TextureTransparency.Opaque });
        this.addTexture(tex);
        return tex;
    }
    collectStatistics(stats) {
        for (const texture of this.textures.values())
            if (texture instanceof Texture_1.Texture)
                stats.addTexture(texture.bytesUsed);
        for (const gradient of this.gradients)
            if (gradient instanceof Texture_1.Texture)
                stats.addTexture(gradient.bytesUsed);
    }
}
exports.IdMap = IdMap;
function getMaterialColor(color) {
    if (color instanceof core_common_1.ColorDef)
        return color;
    return color ? core_common_1.ColorDef.from(color.r, color.g, color.b) : undefined;
}
/** @internal */
class System extends RenderSystem_1.RenderSystem {
    static get instance() { return IModelApp_1.IModelApp.renderSystem; }
    get isValid() { return this.canvas !== undefined; }
    get lineCodeTexture() { return this._lineCodeTexture; }
    get noiseTexture() { return this._noiseTexture; }
    get techniques() {
        (0, core_bentley_1.assert)(undefined !== this._techniques);
        return this._techniques;
    }
    get screenSpaceEffects() {
        (0, core_bentley_1.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 = Texture_1.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 = Texture_1.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 core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Failed to obtain HTMLCanvasElement");
        const context = this.createContext(canvas, true, optionsIn?.contextAttributes);
        if (undefined === context)
            throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Failed to obtain WebGL context");
        if (!(context instanceof WebGL2RenderingContext))
            throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "WebGL 2 support is required");
        const capabilities = webgl_compatibility_1.Capabilities.create(context, options.disabledExtensions);
        if (undefined === capabilities)
            throw new core_common_1.IModelError(core_bentley_1.BentleyStatus.ERROR, "Failed to initialize rendering capabilities");
        // set actual gl state to match desired state defaults
        context.depthFunc(GL_1.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 = (0, core_bentley_1.dispose)(this._techniques);
        this._screenSpaceEffects = (0, core_bentley_1.dispose)(this._screenSpaceEffects);
        this._lineCodeTexture = (0, core_bentley_1.dispose)(this._lineCodeTexture);
        this._noiseTexture = (0, core_bentley_1.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) => {
            (0, core_bentley_1.dispose)(idMap);
        });
        this.resourceCache.clear();
        if (undefined !== this._removeEventListener) {
            this._removeEventListener();
            this._removeEventListener = undefined;
        }
    }
    onInitialized() {
        this._techniques = Technique_1.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 = Texture_1.TextureHandle.createForData(noiseDim, noiseDim, noiseArr, false, GL_1.GL.Texture.WrapMode.Repeat, GL_1.GL.Texture.Format.Luminance);
        (0, core_bentley_1.assert)(undefined !== this._noiseTexture, "System.noiseTexture not created.");
        this._lineCodeTexture = Texture_1.TextureHandle.createForData(LineCode_1.LineCode.size, LineCode_1.LineCode.count, new Uint8Array(LineCode_1.LineCode.lineCodeData), false, GL_1.GL.Texture.WrapMode.Repeat, GL_1.GL.Texture.Format.Luminance);
        (0, core_bentley_1.assert)(undefined !== this._lineCodeTexture, "System.lineCodeTexture not created.");
        this._screenSpaceEffects = new ScreenSpaceEffect_1.ScreenSpaceEffects();
    }
    createTarget(canvas) {
        return new Target_1.OnScreenTarget(canvas);
    }
    createOffscreenTarget(rect) {
        return new Target_1.OffScreenTarget(rect);
    }
    createGraphic(options) {
        return new PrimitiveBuilder_1.PrimitiveBuilder(this, options);
    }
    createPlanarGrid(frustum, grid) {
        return PlanarGrid_1.PlanarGridGeometry.create(frustum, grid, this);
    }
    createTerrainMesh(params, transform, disableTextureDisposal = false) {
        return RealityMesh_1.RealityMeshGeometry.createForTerrain(params, transform, disableTextureDisposal);
    }
    createRealityMeshGraphic(params, disableTextureDisposal = false) {
        return RealityMesh_1.RealityMeshGeometry.createGraphic(this, params, disableTextureDisposal);
    }
    createRealityMesh(realityMesh, disableTextureDisposal = false) {
        const geom = RealityMesh_1.RealityMeshGeometry.createFromRealityMesh(realityMesh, disableTextureDisposal);
        return geom ? Primitive_1.Primitive.create(geom) : undefined;
    }
    createMeshGeometry(params, viOrigin) {
        return Mesh_1.MeshRenderGeometry.create(params, viOrigin);
    }
    createPolylineGeometry(params, viOrigin) {
        return Polyline_1.PolylineGeometry.create(params, viOrigin);
    }
    createPointStringGeometry(params, viOrigin) {
        return PointString_1.PointStringGeometry.create(params, viOrigin);
    }
    createAreaPattern(params) {
        return InstancedGeometry_1.PatternBuffers.create(params);
    }
    createRenderGraphic(geometry, instances) {
        if (!(geometry instanceof Mesh_1.MeshRenderGeometry)) {
            if (geometry instanceof Polyline_1.PolylineGeometry || geometry instanceof PointString_1.PointStringGeometry)
                return Primitive_1.Primitive.create(geometry, instances);
            (0, core_bentley_1.assert)(false, "Invalid RenderGeometry for System.createRenderGraphic");
            return undefined;
        }
        (0, core_bentley_1.assert)(!instances || instances instanceof InstancedGeometry_1.PatternBuffers || (0, InstancedGeometry_1.isInstancedGraphicParams)(instances));
        return Mesh_1.MeshGraphic.create(geometry, instances);
    }
    createPointCloud(args) {
        return Primitive_1.Primitive.create(new PointCloud_1.PointCloudGeometry(args));
    }
    createGraphicList(primitives) {
        return new Graphic_1.GraphicsArray(primitives);
    }
    createGraphicBranch(branch, transform, options) {
        return new Graphic_1.Branch(branch, transform, undefined, options);
    }
    createAnimationTransformNode(graphic, nodeId) {
        return new Graphic_1.AnimationTransformBranch(graphic, nodeId);
    }
    createBatch(graphic, features, range, options) {
        return new Graphic_1.Batch(graphic, features, range, options);
    }
    createGraphicOwner(owned) {
        return new Graphic_1.GraphicOwner(owned);
    }
    createGraphicLayer(graphic, layerId) {
        return new Layer_1.Layer(graphic, layerId);
    }
    createGraphicLayerContainer(graphic, drawAsOverlay, transparency, elevation) {
        return new Layer_1.LayerContainer(graphic, drawAsOverlay, transparency, elevation);
    }
    createSkyBox(params) {
        if ("cube" === params.type)
            return Primitive_1.SkyCubePrimitive.create(CachedGeometry_1.SkyBoxQuadsGeometry.create(params.texture));
        return Primitive_1.SkySpherePrimitive.create(CachedGeometry_1.SkySphereViewportQuadGeometry.createGeometry(params));
    }
    createScreenSpaceEffectBuilder(params) {
        return (0, ScreenSpaceEffect_1.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 RenderBuffer_1.RenderBufferMultiSample.create(width, height, WebGL2RenderingContext.DEPTH24_STENCIL8, numSamples);
        else
            return Texture_1.TextureHandle.createForAttachment(width, height, GL_1.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;
        (0, core_bentley_1.dispose)(idMap);
        this.resourceCache.delete(imodel);
    }
    /** Attempt to create a material for the given iModel using a set of material parameters. */
    // eslint-disable-next-line deprecation/deprecation
    createMaterial(params, imodel) {
        const idMap = this.getIdMap(imodel);
        const material = idMap.getMaterial(params);
        return material;
    }
    createRenderMaterial(args) {
        if (args.source) {
            const cached = this.findMaterial(args.source.id, args.source.iModel);
            if (cached)
                return cached;
        }
        // eslint-disable-next-line deprecation/deprecation
        const params = new core_common_1.RenderMaterial.Params();
        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 core_common_1.TextureMapping(args.textureMapping.texture, new core_common_1.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_1.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 ?? core_common_1.RenderTexture.Type.Normal;
        const source = args.image.source;
        let handle;
        if (source instanceof core_common_1.ImageBuffer)
            handle = Texture_1.TextureHandle.createForImageBuffer(source, type);
        else if (source instanceof ImageBitmap)
            handle = Texture_1.TextureHandle.createForImageBitmap(source, type);
        else if (source instanceof HTMLImageElement)
            handle = Texture_1.TextureHandle.createForImage(source, type);
        else
            (0, core_bentley_1.assert)(false);
        if (!handle)
            return undefined;
        const texture = new Texture_1.Texture({ handle, type, ownership: args.ownership, transparency: args.image.transparency ?? core_common_1.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);
    }
    // eslint-disable-next-line deprecation/deprecation
    createTextureFromElement(id, imodel, params, format) {
        return this.getIdMap(imodel).getTextureFromElement(id, imodel, params, format);
    }
    // eslint-disable-next-line deprecation/deprecation
    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 === core_common_1.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: core_common_1.ImageBufferFormat.Rgba === source.format ? core_common_1.TextureTransparency.Mixed : core_common_1.TextureTransparency.Opaque,
            },
            ownership: iModel ? { iModel, key: symb } : undefined,
            type: core_common_1.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_1.ClipVolume.create(clipVector);
    }
    createBackgroundMapDrape(drapedTree, mapTree) {
        return BackgroundMapDrape_1.BackgroundMapDrape.create(drapedTree, mapTree);
    }
    constructor(canvas, context, capabilities, options) {
        super(options);
        this.currentRenderState = new RenderState_1.RenderState();
        this.frameBufferStack = new FrameBuffer_1.FrameBufferStack(); // frame buffers are not owned by the system
        this._textureBindings = [];
        // NB: Increase the size of these arrays when the maximum number of attributes used by any one shader increases.
        this._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 */,
        ];
        this._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 */,
        ];
        this.debugShaderFiles = [];
        this.canvas = canvas;
        this.context = context;
        this._capabilities = capabilities;
        this.resourceCache = new Map();
        this.glTimer = GLTimer_1.GLTimer.create(this);
        // Make this System a subscriber to the the IModelConnection onClose event
        this._removeEventListener = IModelConnection_1.IModelConnection.onClose.addListener((imodel) => this.removeIModelMap(imodel));
        canvas.addEventListener("webglcontextlost", async () => RenderSystem_1.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 - RenderFlags_1.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_1.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_1.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_1.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_1.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) {
        (0, core_bentley_1.assert)(id < this._nextVertexAttribStates.length, "if you add new vertex attributes you must update array length");
        (0, core_bentley_1.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) {
        Diagnostics_1.Debug.printEnabled = RenderSystem_1.RenderDiagnostics.None !== (enable & RenderSystem_1.RenderDiagnostics.DebugOutput);
        Diagnostics_1.Debug.evaluateEnabled = RenderSystem_1.RenderDiagnostics.None !== (enable & RenderSystem_1.RenderDiagnostics.WebGL);
    }
    // RenderSystemDebugControl
    get debugControl() { return this; }
    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);
    }
}
exports.System = System;
//# sourceMappingURL=System.js.map