/*---------------------------------------------------------------------------------------------
* 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, BeEvent, dispose } from "@itwin/core-bentley";
import { ImageBuffer, ImageBufferFormat, ImageSource, isPowerOfTwo, nextHighestPowerOfTwo, RenderTexture, TextureTransparency, } from "@itwin/core-common";
import { getImageSourceMimeType, imageBufferToPngDataUrl, imageElementFromImageSource, openImageDataUrlInNewWindow } from "../../common/ImageUtil";
import { IModelApp } from "../../IModelApp";
import { GL } from "./GL";
import { TextureUnit } from "./RenderFlags";
import { System } from "./System";
function computeBytesUsed(width, height, format, dataType) {
    const bytesPerComponent = GL.Texture.DataType.UnsignedByte === dataType ? 1 : 4;
    let componentsPerPixel = 1;
    switch (format) {
        case GL.Texture.Format.Rgb:
            componentsPerPixel = 3;
            break;
        case GL.Texture.Format.Rgba:
            componentsPerPixel = 4;
            break;
    }
    return width * height * componentsPerPixel * bytesPerComponent;
}
/** Associate texture data with a WebGLTexture from a canvas, image, OR a bitmap. */
function loadTexture2DImageData(handle, params, bytes, source) {
    handle.bytesUsed = undefined !== bytes ? bytes.byteLength : computeBytesUsed(params.width, params.height, params.format, params.dataType);
    const tex = handle.getHandle();
    const gl = System.instance.context;
    // Use tightly packed data
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    // Bind the texture object; make sure we do not interfere with other active textures
    System.instance.activateTexture2d(TextureUnit.Zero, tex);
    // Figure out the internal format.  For all but WebGL2 float/half-float datatypes it is just same as format.
    // TODO: probably need to just support internal format types in Texture2DCreateParams.
    let internalFormat = params.format;
    const context2 = System.instance.context;
    if (GL.Texture.Format.Rgba === params.format) {
        if (GL.Texture.DataType.Float === params.dataType)
            internalFormat = context2.RGBA32F;
        else if (context2.HALF_FLOAT === params.dataType)
            internalFormat = context2.RGBA16F;
    }
    else if (GL.Texture.Format.DepthStencil === params.format)
        internalFormat = context2.DEPTH24_STENCIL8;
    // send the texture data
    if (undefined !== source) {
        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, params.format, params.dataType, source);
    }
    else {
        const pixelData = undefined !== bytes ? bytes : null;
        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat, params.width, params.height, 0, params.format, params.dataType, pixelData);
    }
    if (params.useMipMaps) {
        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    }
    else {
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, params.interpolate ? gl.LINEAR : gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, params.interpolate ? gl.LINEAR : gl.NEAREST);
    }
    if (params.anisotropicFilter) {
        System.instance.setMaxAnisotropy(params.anisotropicFilter);
    }
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, params.wrapMode);
    gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, params.wrapMode);
    System.instance.bindTexture2d(TextureUnit.Zero, undefined);
}
function loadTextureFromBytes(handle, params, bytes) {
    loadTexture2DImageData(handle, params, bytes);
}
/** Associate cube texture data with a WebGLTexture from an image. */
function loadTextureCubeImageData(handle, params, images) {
    handle.bytesUsed = computeBytesUsed(params.dim * 6, params.dim, params.format, params.dataType);
    const tex = handle.getHandle();
    const gl = System.instance.context;
    // Use tightly packed data
    gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
    // Bind the texture object; make sure we do not interfere with other active textures
    System.instance.activateTextureCubeMap(TextureUnit.Zero, tex);
    const cubeTargets = [GL.Texture.Target.CubeMapPositiveX, GL.Texture.Target.CubeMapNegativeX, GL.Texture.Target.CubeMapPositiveY, GL.Texture.Target.CubeMapNegativeY, GL.Texture.Target.CubeMapPositiveZ, GL.Texture.Target.CubeMapNegativeZ];
    for (let i = 0; i < 6; i++) {
        gl.texImage2D(cubeTargets[i], 0, params.format, params.format, params.dataType, images[i]);
    }
    gl.texParameteri(GL.Texture.Target.CubeMap, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
    gl.texParameteri(GL.Texture.Target.CubeMap, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
    gl.texParameteri(GL.Texture.Target.CubeMap, gl.TEXTURE_WRAP_S, params.wrapMode);
    gl.texParameteri(GL.Texture.Target.CubeMap, gl.TEXTURE_WRAP_T, params.wrapMode);
    // gl.texParameteri(GL.Texture.Target.CubeMap, gl.TEXTURE_WRAP_R, params.wrapMode); // Unavailable in GLES2
    System.instance.bindTextureCubeMap(TextureUnit.Zero, undefined);
}
/** Wrapper class for a WebGL texture handle and parameters specific to an individual texture.
 * @internal
 */
export class Texture extends RenderTexture {
    get bytesUsed() { return this.texture.bytesUsed; }
    get hasOwner() { return undefined !== this.ownership; }
    get key() {
        return typeof this.ownership !== "string" && typeof this.ownership?.key === "string" ? this.ownership.key : undefined;
    }
    constructor(params) {
        super(params.type);
        this.ownership = params.ownership;
        this.texture = params.handle;
        this.transparency = params.handle.format === GL.Texture.Format.Rgba ? params.transparency : TextureTransparency.Opaque;
    }
    get isDisposed() { return this.texture.isDisposed; }
    /** Free this object in the WebGL wrapper. */
    dispose() {
        dispose(this.texture);
    }
}
function getDataType(data) {
    return data instanceof Float32Array ? GL.Texture.DataType.Float : GL.Texture.DataType.UnsignedByte;
}
/** Parameters used internally to define how to create a texture for use with WebGL. */
class Texture2DCreateParams {
    constructor(width, height, format, dataType, wrapMode, loadImageData, useMipMaps, interpolate, anisotropicFilter, dataBytes) {
        this.width = width;
        this.height = height;
        this.format = format;
        this.dataType = dataType;
        this.wrapMode = wrapMode;
        this.loadImageData = loadImageData;
        this.useMipMaps = useMipMaps;
        this.interpolate = interpolate;
        this.anisotropicFilter = anisotropicFilter;
        this.dataBytes = dataBytes;
    }
    static createForData(width, height, data, preserveData = false, wrapMode = GL.Texture.WrapMode.ClampToEdge, format = GL.Texture.Format.Rgba) {
        const bytes = (preserveData && data instanceof Uint8Array) ? data : undefined;
        return new Texture2DCreateParams(width, height, format, getDataType(data), wrapMode, (tex, params) => loadTextureFromBytes(tex, params, data), undefined, undefined, undefined, bytes);
    }
    static createForImageBuffer(image, type) {
        const props = this.getImageProperties(type);
        if (ImageBufferFormat.Rgb === image.format)
            props.format = GL.Texture.Format.Rgb;
        return new Texture2DCreateParams(image.width, image.height, props.format, GL.Texture.DataType.UnsignedByte, props.wrapMode, (tex, params) => loadTextureFromBytes(tex, params, image.data), props.useMipMaps, props.interpolate);
    }
    static createForAttachment(width, height, format, dataType) {
        return new Texture2DCreateParams(width, height, format, dataType, GL.Texture.WrapMode.ClampToEdge, (tex, params) => loadTextureFromBytes(tex, params), undefined, undefined);
    }
    static createForImage(image, type) {
        const props = this.getImageProperties(type);
        let targetWidth = image.naturalWidth;
        let targetHeight = image.naturalHeight;
        if (RenderTexture.Type.Glyph === type) {
            targetWidth = nextHighestPowerOfTwo(targetWidth);
            targetHeight = nextHighestPowerOfTwo(targetHeight);
        }
        else if (!System.instance.supportsNonPowerOf2Textures && (!isPowerOfTwo(targetWidth) || !isPowerOfTwo(targetHeight))) {
            if (GL.Texture.WrapMode.ClampToEdge === props.wrapMode) {
                // NPOT are supported but not mipmaps
                // Probably on poor hardware so I choose to disable mipmaps for lower memory usage over quality. If quality is required we need to resize the image to a pow of 2.
                // Above comment is not necessarily true - WebGL doesn't support NPOT mipmapping, only supporting base NPOT caps
                props.useMipMaps = undefined;
            }
            else if (GL.Texture.WrapMode.Repeat === props.wrapMode) {
                targetWidth = nextHighestPowerOfTwo(targetWidth);
                targetHeight = nextHighestPowerOfTwo(targetHeight);
            }
        }
        // Cap texture dimensions to system WebGL capabilities
        const maxTexSize = System.instance.maxTextureSize;
        targetWidth = Math.min(targetWidth, maxTexSize);
        targetHeight = Math.min(targetHeight, maxTexSize);
        let element = image;
        if (targetWidth !== image.naturalWidth || targetHeight !== image.naturalHeight) {
            // Resize so dimensions are powers-of-two
            const canvas = document.createElement("canvas");
            canvas.width = targetWidth;
            canvas.height = targetHeight;
            const context = canvas.getContext("2d");
            context.drawImage(image, 0, 0, canvas.width, canvas.height);
            element = canvas;
        }
        return new Texture2DCreateParams(targetWidth, targetHeight, props.format, GL.Texture.DataType.UnsignedByte, props.wrapMode, (tex, params) => loadTexture2DImageData(tex, params, undefined, element), props.useMipMaps, props.interpolate, props.anisotropicFilter);
    }
    static createForImageBitmap(image, type) {
        const props = this.getImageProperties(type);
        let targetWidth = image.width;
        let targetHeight = image.height;
        if (RenderTexture.Type.Glyph === type) {
            targetWidth = nextHighestPowerOfTwo(targetWidth);
            targetHeight = nextHighestPowerOfTwo(targetHeight);
        }
        else if (!System.instance.supportsNonPowerOf2Textures && (!isPowerOfTwo(targetWidth) || !isPowerOfTwo(targetHeight))) {
            if (GL.Texture.WrapMode.ClampToEdge === props.wrapMode) {
                // NPOT are supported but not mipmaps
                // Probably on poor hardware so I choose to disable mipmaps for lower memory usage over quality. If quality is required we need to resize the image to a pow of 2.
                // Above comment is not necessarily true - WebGL doesn't support NPOT mipmapping, only supporting base NPOT caps
                props.useMipMaps = undefined;
            }
            else if (GL.Texture.WrapMode.Repeat === props.wrapMode) {
                targetWidth = nextHighestPowerOfTwo(targetWidth);
                targetHeight = nextHighestPowerOfTwo(targetHeight);
            }
        }
        // If we have to resize, use a canvas
        let source = image;
        if (image.width !== targetWidth || image.height !== targetHeight) {
            const canvas = document.createElement("canvas");
            canvas.width = targetWidth;
            canvas.height = targetHeight;
            const context = canvas.getContext("2d");
            context.drawImage(image, 0, 0, canvas.width, canvas.height);
            source = canvas;
        }
        return new Texture2DCreateParams(targetWidth, targetHeight, props.format, GL.Texture.DataType.UnsignedByte, props.wrapMode, (tex, params) => loadTexture2DImageData(tex, params, undefined, source), props.useMipMaps, props.interpolate, props.anisotropicFilter);
    }
    static getImageProperties(type) {
        const isSky = RenderTexture.Type.SkyBox === type;
        const isTile = RenderTexture.Type.TileSection === type;
        const isThematic = RenderTexture.Type.ThematicGradient === type;
        const isFilteredTile = RenderTexture.Type.FilteredTileSection === type;
        const maxAnisotropicFilterLevel = 16;
        const wrapMode = RenderTexture.Type.Normal === type ? GL.Texture.WrapMode.Repeat : GL.Texture.WrapMode.ClampToEdge;
        const useMipMaps = (!isSky && !isTile && !isFilteredTile && !isThematic) ? true : undefined;
        const interpolate = isThematic ? undefined : true;
        const anisotropicFilter = isFilteredTile ? maxAnisotropicFilterLevel : undefined;
        // Always use RGBA. RGB is much slower and almost certainly does not actually save any GPU memory.
        const format = GL.Texture.Format.Rgba;
        return { format, wrapMode, useMipMaps, interpolate, anisotropicFilter };
    }
}
Texture2DCreateParams.placeholderParams = new Texture2DCreateParams(1, 1, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte, GL.Texture.WrapMode.ClampToEdge, (_tex, _params) => undefined);
class TextureCubeCreateParams {
    constructor(dim, format, dataType, wrapMode, loadImageData) {
        this.dim = dim;
        this.format = format;
        this.dataType = dataType;
        this.wrapMode = wrapMode;
        this.loadImageData = loadImageData;
    }
    static createForCubeImages(posX, negX, posY, negY, posZ, negZ) {
        const targetDim = posX.naturalWidth;
        if (posX.naturalHeight !== targetDim) // Cube texture dimensions must match (width must equal height)
            return undefined;
        const images = [posX, negX, posY, negY, posZ, negZ];
        for (let i = 1; i < images.length; i++) { // Dimensions of all six sides must match each other
            if (images[i].naturalWidth !== targetDim || images[i].naturalHeight !== targetDim)
                return undefined;
        }
        return new TextureCubeCreateParams(targetDim, GL.Texture.Format.Rgba, GL.Texture.DataType.UnsignedByte, GL.Texture.WrapMode.ClampToEdge, (tex, params) => loadTextureCubeImageData(tex, params, images));
    }
}
/** Wraps a WebGLTextureHandle
 * @internal
 */
export class TextureHandle {
    get bytesUsed() { return this._bytesUsed; }
    set bytesUsed(bytesUsed) {
        // assert(0 === this.bytesUsed);
        this._bytesUsed = bytesUsed;
    }
    /** Get the WebGLTexture for this TextureHandle. */
    getHandle() { return this._glTexture; }
    get isDisposed() { return this._glTexture === undefined; }
    dispose() {
        if (!this.isDisposed) {
            System.instance.disposeTexture(this._glTexture);
            this._glTexture = undefined;
            this.bytesUsed = 0;
        }
    }
    /** Create a 2D texture for use as a color attachment for rendering */
    static createForAttachment(width, height, format, dataType) {
        return Texture2DHandle.createForAttachment(width, height, format, dataType);
    }
    /** Create a 2D texture to hold non-image data */
    static createForData(width, height, data, wantPreserveData = false, wrapMode = GL.Texture.WrapMode.ClampToEdge, format = GL.Texture.Format.Rgba) {
        return Texture2DHandle.createForData(width, height, data, wantPreserveData, wrapMode, format);
    }
    /** Create a 2D texture from a bitmap */
    static createForImageBuffer(image, type) {
        return Texture2DHandle.createForImageBuffer(image, type);
    }
    /** Create a 2D texture from an HTMLImageElement. */
    static createForImage(image, type) {
        return Texture2DHandle.createForImage(image, type);
    }
    /** Create a 2D texture from an ImageBitmap. */
    static createForImageBitmap(image, type) {
        return Texture2DHandle.createForImageBitmap(image, type);
    }
    /** Create a cube map texture from six HTMLImageElement objects. */
    static createForCubeImages(posX, negX, posY, negY, posZ, negZ) {
        return TextureCubeHandle.createForCubeImages(posX, negX, posY, negY, posZ, negZ);
    }
    static createForElement(id, imodel, type, format, onLoaded) {
        return Texture2DHandle.createForElement(id, imodel, type, format, onLoaded);
    }
    constructor(glTexture) {
        this._bytesUsed = 0;
        this._glTexture = glTexture;
    }
    /** For debugging purposes, open a new window containing this texture as an image. */
    showDebugImage() {
        const gl = System.instance.context;
        const fbo = gl.createFramebuffer();
        gl.bindFramebuffer(gl.FRAMEBUFFER, fbo);
        gl.framebufferTexture2D(gl.FRAMEBUFFER, gl.COLOR_ATTACHMENT0, gl.TEXTURE_2D, this.getHandle(), 0);
        if (gl.FRAMEBUFFER_COMPLETE === gl.checkFramebufferStatus(gl.FRAMEBUFFER)) {
            const w = this.width;
            const h = this.height;
            const pixels = new Uint8Array(w * h * 4);
            gl.readPixels(0, 0, w, h, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
            const buffer = ImageBuffer.create(pixels, ImageBufferFormat.Rgba, w);
            const url = imageBufferToPngDataUrl(buffer, false);
            openImageDataUrlInNewWindow(url, "Classifiers");
        }
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.deleteFramebuffer(fbo);
    }
}
/** @internal */
export class Texture2DHandle extends TextureHandle {
    get width() { return this._width; }
    get height() { return this._height; }
    get format() { return this._format; }
    get dataType() { return this._dataType; }
    get dataBytes() { return this._dataBytes; }
    /** Bind specified texture handle to specified texture unit. */
    static bindTexture(texUnit, glTex) {
        assert(!(glTex instanceof TextureHandle));
        System.instance.bindTexture2d(texUnit, glTex);
    }
    /** Bind the specified texture to a uniform sampler2D */
    static bindSampler(uniform, tex, unit) {
        assert(!(tex instanceof TextureHandle));
        this.bindTexture(unit, tex);
        uniform.setUniform1i(unit - TextureUnit.Zero);
    }
    /** Bind texture handle (if available) associated with an instantiation of this class to specified texture unit. */
    bind(texUnit) {
        if (undefined === this._glTexture)
            return false;
        Texture2DHandle.bindTexture(texUnit, this._glTexture);
        return true;
    }
    /** Bind this texture to a uniform sampler2D */
    bindSampler(uniform, unit) {
        if (undefined !== this._glTexture)
            Texture2DHandle.bindSampler(uniform, this._glTexture, unit);
    }
    /** Update the 2D texture contents. */
    update(updater) {
        if (0 === this.width || 0 === this.height || undefined === this._dataBytes || 0 === this._dataBytes.length) {
            assert(false);
            return false;
        }
        if (!updater.modified)
            return false;
        return this.replaceTextureData(this._dataBytes);
    }
    /** Replace the 2D texture contents. */
    replaceTextureData(data) {
        assert((GL.Texture.DataType.Float === this._dataType) === (data instanceof Float32Array));
        const tex = this.getHandle();
        if (undefined === tex)
            return false;
        const gl = System.instance.context;
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);
        // Go through System to ensure we don't interfere with currently-bound textures!
        System.instance.activateTexture2d(TextureUnit.Zero, tex);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, this.width, this.height, this._format, this._dataType, data);
        System.instance.bindTexture2d(TextureUnit.Zero, undefined);
        return true;
    }
    static create(params) {
        const glTex = System.instance.context.createTexture();
        return null !== glTex ? new Texture2DHandle(glTex, params) : undefined;
    }
    /** Create a texture for use as a color attachment for rendering */
    static createForAttachment(width, height, format, dataType) {
        return this.create(Texture2DCreateParams.createForAttachment(width, height, format, dataType));
    }
    /** Create a texture to hold non-image data */
    static createForData(width, height, data, wantPreserveData = false, wrapMode = GL.Texture.WrapMode.ClampToEdge, format = GL.Texture.Format.Rgba) {
        return this.create(Texture2DCreateParams.createForData(width, height, data, wantPreserveData, wrapMode, format));
    }
    /** Create a texture from a bitmap */
    static createForImageBuffer(image, type) {
        if (RenderTexture.Type.TileSection !== type && RenderTexture.Type.ThematicGradient !== type)
            assert(isPowerOfTwo(image.width) && isPowerOfTwo(image.height), "###TODO: Resize image dimensions to powers-of-two if necessary");
        return this.create(Texture2DCreateParams.createForImageBuffer(image, type));
    }
    /** Create a 2D texture from an HTMLImageElement. */
    static createForImage(image, type) {
        return this.create(Texture2DCreateParams.createForImage(image, type));
    }
    /** Create a 2D texture from an ImageBitmap. */
    static createForImageBitmap(image, type) {
        return this.create(Texture2DCreateParams.createForImageBitmap(image, type));
    }
    static createForElement(id, imodel, type, format, onLoaded) {
        // set a placeholder texture while we wait for the external texture to load
        const handle = this.createForData(1, 1, this._placeHolderTextureData, undefined, undefined, GL.Texture.Format.Rgba);
        if (undefined === handle)
            return undefined;
        // kick off loading the texture from the backend
        ExternalTextureLoader.instance.loadTexture(handle, id, imodel, type, format, onLoaded);
        return handle;
    }
    reload(params) {
        this._width = params.width;
        this._height = params.height;
        this._format = params.format;
        this._dataType = params.dataType;
        this._dataBytes = params.dataBytes;
        params.loadImageData(this, params);
    }
    constructor(glTexture, params) {
        super(glTexture);
        this._width = params.width;
        this._height = params.height;
        this._format = params.format;
        this._dataType = params.dataType;
        this._dataBytes = params.dataBytes;
        params.loadImageData(this, params);
    }
}
Texture2DHandle._placeHolderTextureData = new Uint8Array([128, 128, 128, 255]);
/** @internal */
export class ExternalTextureLoader {
    get numActiveRequests() { return this._activeRequests.length; }
    get numPendingRequests() { return this._pendingRequests.length; }
    get maxActiveRequests() { return this._maxActiveRequests; }
    constructor(maxActiveRequests) {
        this.onTexturesLoaded = new BeEvent();
        this._activeRequests = [];
        this._pendingRequests = [];
        this._convertRequests = [];
        this._convertPending = false;
        this._maxActiveRequests = maxActiveRequests;
    }
    async _nextRequest(prevReq) {
        this._activeRequests.splice(this._activeRequests.indexOf(prevReq), 1);
        if (this._activeRequests.length < this._maxActiveRequests && this._pendingRequests.length > 0) {
            const req = this._pendingRequests.shift();
            await this._activateRequest(req);
        }
        if (this._activeRequests.length < 1 && this._pendingRequests.length < 1)
            this.onTexturesLoaded.raiseEvent();
    }
    async _activateRequest(req) {
        if (req.imodel.isClosed)
            return;
        this._activeRequests.push(req);
        try {
            if (!req.imodel.isClosed) {
                const maxTextureSize = System.instance.maxTexSizeAllow;
                const texData = await req.imodel.queryTextureData({ name: req.name, maxTextureSize });
                if (undefined !== texData) {
                    const cnvReq = { req, texData };
                    this._convertRequests.push(cnvReq);
                    // _convertPending is used to prevent overlapping calls to _convertTexture (from overlapping calls to _activateRequest)
                    // it has been put on the list, so if it doesn't get converted here it will get converted by the loop that is converting the current one
                    do {
                        if (!this._convertPending)
                            await this._convertTexture();
                    } while (!this._convertPending && this._convertRequests.length > 0);
                    if (!req.imodel.isClosed) {
                        IModelApp.tileAdmin.invalidateAllScenes();
                        if (undefined !== req.onLoaded)
                            req.onLoaded(req, texData);
                    }
                }
            }
        }
        catch { }
        return this._nextRequest(req);
    }
    async _convertTexture() {
        this._convertPending = true;
        try {
            const cnvReq = this._convertRequests.shift();
            if (undefined !== cnvReq) {
                const imageSource = new ImageSource(cnvReq.texData.bytes, cnvReq.texData.format);
                if (System.instance.supportsCreateImageBitmap) {
                    const blob = new Blob([imageSource.data], { type: getImageSourceMimeType(imageSource.format) });
                    const image = await createImageBitmap(blob, 0, 0, cnvReq.texData.width, cnvReq.texData.height);
                    if (!cnvReq.req.imodel.isClosed) {
                        cnvReq.req.handle.reload(Texture2DCreateParams.createForImageBitmap(image, cnvReq.req.type));
                    }
                }
                else {
                    const image = await imageElementFromImageSource(imageSource);
                    if (!cnvReq.req.imodel.isClosed) {
                        cnvReq.req.handle.reload(Texture2DCreateParams.createForImage(image, cnvReq.req.type));
                    }
                }
            }
        }
        catch { }
        this._convertPending = false;
    }
    _requestExists(reqToCheck) {
        for (const r of this._activeRequests)
            if (reqToCheck.name === r.name && reqToCheck.imodel === r.imodel)
                return true;
        for (const r of this._pendingRequests)
            if (reqToCheck.name === r.name && reqToCheck.imodel === r.imodel)
                return true;
        return false;
    }
    loadTexture(handle, name, imodel, type, format, onLoaded) {
        const req = { handle, name, imodel, type, format, onLoaded };
        if (this._requestExists(req))
            return;
        if (this._activeRequests.length + 1 > this._maxActiveRequests) {
            this._pendingRequests.push(req);
        }
        else
            this._activateRequest(req); // eslint-disable-line @typescript-eslint/no-floating-promises
    }
}
ExternalTextureLoader.instance = new ExternalTextureLoader(2);
/** @internal */
export class TextureCubeHandle extends TextureHandle {
    get width() { return this._dim; }
    get height() { return this._dim; }
    get format() { return this._format; }
    get dataType() { return this._dataType; }
    get dataBytes() { return undefined; }
    /** Bind specified cubemap texture handle to specified texture unit. */
    static bindTexture(texUnit, glTex) {
        assert(!(glTex instanceof TextureHandle));
        System.instance.bindTextureCubeMap(texUnit, glTex);
    }
    /** Bind the specified texture to a uniform sampler2D */
    static bindSampler(uniform, tex, unit) {
        assert(!(tex instanceof TextureHandle));
        this.bindTexture(unit, tex);
        uniform.setUniform1i(unit - TextureUnit.Zero);
    }
    /** Bind texture handle (if available) associated with an instantiation of this class to specified texture unit. */
    bind(texUnit) {
        if (undefined === this._glTexture)
            return false;
        TextureCubeHandle.bindTexture(texUnit, this._glTexture);
        return true;
    }
    /** Bind this texture to a uniform sampler2D */
    bindSampler(uniform, unit) {
        if (undefined !== this._glTexture)
            TextureCubeHandle.bindSampler(uniform, this._glTexture, unit);
    }
    static create(params) {
        const glTex = System.instance.context.createTexture();
        return null !== glTex ? new TextureCubeHandle(glTex, params) : undefined;
    }
    /** Create a cube map texture from six HTMLImageElement objects. */
    static createForCubeImages(posX, negX, posY, negY, posZ, negZ) {
        const params = TextureCubeCreateParams.createForCubeImages(posX, negX, posY, negY, posZ, negZ);
        return params !== undefined ? this.create(params) : undefined;
    }
    constructor(glTexture, params) {
        super(glTexture);
        this._dim = params.dim;
        this._format = params.format;
        this._dataType = params.dataType;
        params.loadImageData(this, params);
    }
}
/** @internal */
export class Texture2DDataUpdater {
    constructor(data) {
        this.modified = false;
        this.data = data;
    }
    setByteAtIndex(index, byte) {
        assert(index < this.data.length);
        if (byte !== this.data[index]) {
            this.data[index] = byte;
            this.modified = true;
        }
    }
    setOvrFlagsAtIndex(index, value) {
        assert(index < this.data.length - 1);
        assert(value < 0xffff);
        this.setByteAtIndex(index, value & 0xff);
        this.setByteAtIndex(index + 1, (value & 0xff00) >> 8);
    }
    getByteAtIndex(index) {
        assert(index < this.data.length);
        return this.data[index];
    }
    getOvrFlagsAtIndex(index) {
        const lo = this.getByteAtIndex(index);
        const hi = this.getByteAtIndex(index + 1);
        return lo | (hi << 8);
    }
}
//# sourceMappingURL=Texture.js.map