/*---------------------------------------------------------------------------------------------
* 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, expectDefined, 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, Texture2DCreateParams, 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;
    _lineCodeTextureListener;
    _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);
        if (this._lineCodeTextureListener) {
            this._lineCodeTextureListener();
            this._lineCodeTextureListener = undefined;
        }
        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;
        }
    }
    reloadLineCodeTexture() {
        if (!this._lineCodeTexture)
            return;
        const params = Texture2DCreateParams.createForData(LineCode.size, LineCode.capacity(), LineCode.getTextureData(), true, GL.Texture.WrapMode.Repeat, GL.Texture.Format.Luminance);
        this._lineCodeTexture.reload(params);
    }
    onInitialized() {
        // Initialize LineCode capacity BEFORE creating techniques (which compile shaders that use the capacity)
        LineCode.initializeCapacity(this.maxTextureSize);
        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.capacity(), LineCode.getTextureData(), true, GL.Texture.WrapMode.Repeat, GL.Texture.Format.Luminance);
        assert(undefined !== this._lineCodeTexture, "System.lineCodeTexture not created.");
        this._lineCodeTextureListener = LineCode.onTextureUpdated(() => this.reloadLineCodeTexture());
        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) {
        expectDefined(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