import { Debug } from '../../core/debug.js';
import { TRACEID_TEXTURE_ALLOC, TRACEID_VRAM_TEXTURE } from '../../core/constants.js';
import { math } from '../../core/math/math.js';
import { TEXTURELOCK_NONE, PIXELFORMAT_RGBA8, isCompressedPixelFormat, isIntegerPixelFormat, FILTER_NEAREST, FILTER_LINEAR_MIPMAP_LINEAR, FILTER_LINEAR, ADDRESS_REPEAT, FUNC_LESS, TEXTURETYPE_DEFAULT, TEXTUREPROJECTION_NONE, TEXTUREPROJECTION_CUBE, pixelFormatInfo, TEXHINT_SHADOWMAP, TEXHINT_ASSET, TEXHINT_LIGHTMAP, TEXPROPERTY_MIN_FILTER, TEXPROPERTY_MAG_FILTER, TEXPROPERTY_ADDRESS_U, TEXPROPERTY_ADDRESS_V, TEXPROPERTY_ADDRESS_W, TEXPROPERTY_COMPARE_ON_READ, TEXPROPERTY_COMPARE_FUNC, TEXPROPERTY_ANISOTROPY, isSrgbPixelFormat, pixelFormatLinearToGamma, pixelFormatGammaToLinear, TEXTURETYPE_RGBP, TEXTURETYPE_RGBE, TEXTURETYPE_RGBM, requiresManualGamma, TEXPROPERTY_ALL, TEXTURELOCK_WRITE, TEXTURELOCK_READ, getPixelFormatArrayType } from './constants.js';
import { TextureUtils } from './texture-utils.js';

/**
 * @import { GraphicsDevice } from './graphics-device.js'
 * @import { RenderTarget } from './render-target.js'
 */ let id = 0;
/**
 * Represents a texture, which is typically an image composed of pixels (texels). Textures are
 * fundamental resources for rendering graphical objects. They are commonly used by
 * {@link Material}s and sampled in {@link Shader}s (usually fragment shaders) to define the visual
 * appearance of a 3D model's surface. Beyond storing color images, textures can hold various data
 * types like normal maps, environment maps (cubemaps), or custom data for shader computations. Key
 * properties control how the texture data is sampled, including filtering modes and coordinate
 * wrapping.
 *
 * Note on **HDR texture format** support:
 * 1. **As textures**:
 *     - float (i.e. {@link PIXELFORMAT_RGBA32F}), half-float (i.e. {@link PIXELFORMAT_RGBA16F}) and
 * small-float ({@link PIXELFORMAT_111110F}) formats are always supported on both WebGL2 and WebGPU
 * with point sampling.
 *     - half-float and small-float formats are always supported on WebGL2 and WebGPU with linear
 * sampling.
 *     - float formats are supported on WebGL2 and WebGPU with linear sampling only if
 * {@link GraphicsDevice#textureFloatFilterable} is true.
 *
 * 2. **As renderable textures** that can be used as color buffers in a {@link RenderTarget}:
 *     - on WebGPU, rendering to float and half-float formats is always supported.
 *     - on WebGPU, rendering to small-float format is supported only if
 * {@link GraphicsDevice#textureRG11B10Renderable} is true.
 *     - on WebGL2, rendering to these 3 formats formats is supported only if
 * {@link GraphicsDevice#textureFloatRenderable} is true.
 *     - on WebGL2, if {@link GraphicsDevice#textureFloatRenderable} is false, but
 * {@link GraphicsDevice#textureHalfFloatRenderable} is true, rendering to half-float formats only
 * is supported. This is the case of many mobile iOS devices.
 *     - you can determine available renderable HDR format using
 * {@link GraphicsDevice#getRenderableHdrFormat}.
 * @category Graphics
 */ class Texture {
    /**
     * Create a new Texture instance.
     *
     * @param {GraphicsDevice} graphicsDevice - The graphics device used to manage this texture.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {string} [options.name] - The name of the texture. Defaults to null.
     * @param {number} [options.width] - The width of the texture in pixels. Defaults to 4.
     * @param {number} [options.height] - The height of the texture in pixels. Defaults to 4.
     * @param {number} [options.depth] - The number of depth slices in a 3D texture.
     * @param {number} [options.format] - The pixel format of the texture. Can be:
     *
     * - {@link PIXELFORMAT_R8}
     * - {@link PIXELFORMAT_RG8}
     * - {@link PIXELFORMAT_RGB565}
     * - {@link PIXELFORMAT_RGBA5551}
     * - {@link PIXELFORMAT_RGBA4}
     * - {@link PIXELFORMAT_RGB8}
     * - {@link PIXELFORMAT_RGBA8}
     * - {@link PIXELFORMAT_DXT1}
     * - {@link PIXELFORMAT_DXT3}
     * - {@link PIXELFORMAT_DXT5}
     * - {@link PIXELFORMAT_RGB16F}
     * - {@link PIXELFORMAT_RGBA16F}
     * - {@link PIXELFORMAT_RGB32F}
     * - {@link PIXELFORMAT_RGBA32F}
     * - {@link PIXELFORMAT_ETC1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGBA_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGBA_1}
     * - {@link PIXELFORMAT_111110F}
     * - {@link PIXELFORMAT_ASTC_4x4}
     * - {@link PIXELFORMAT_ATC_RGB}
     * - {@link PIXELFORMAT_ATC_RGBA}
     *
     * Defaults to {@link PIXELFORMAT_RGBA8}.
     * @param {string} [options.projection] - The projection type of the texture, used when the
     * texture represents an environment. Can be:
     *
     * - {@link TEXTUREPROJECTION_NONE}
     * - {@link TEXTUREPROJECTION_CUBE}
     * - {@link TEXTUREPROJECTION_EQUIRECT}
     * - {@link TEXTUREPROJECTION_OCTAHEDRAL}
     *
     * Defaults to {@link TEXTUREPROJECTION_CUBE} if options.cubemap is true, otherwise
     * {@link TEXTUREPROJECTION_NONE}.
     * @param {number} [options.minFilter] - The minification filter type to use. Defaults to
     * {@link FILTER_LINEAR_MIPMAP_LINEAR}.
     * @param {number} [options.magFilter] - The magnification filter type to use. Defaults to
     * {@link FILTER_LINEAR}.
     * @param {number} [options.anisotropy] - The level of anisotropic filtering to use. Defaults
     * to 1.
     * @param {number} [options.addressU] - The repeat mode to use in the U direction. Defaults to
     * {@link ADDRESS_REPEAT}.
     * @param {number} [options.addressV] - The repeat mode to use in the V direction. Defaults to
     * {@link ADDRESS_REPEAT}.
     * @param {number} [options.addressW] - The repeat mode to use in the W direction. Defaults to
     * {@link ADDRESS_REPEAT}.
     * @param {boolean} [options.mipmaps] - When enabled try to generate or use mipmaps for this
     * texture. Default is true.
     * @param {number} [options.numLevels] - Specifies the number of mip levels to generate. If not
     * specified, the number is calculated based on the texture size. When this property is set,
     * the mipmaps property is ignored.
     * @param {boolean} [options.cubemap] - Specifies whether the texture is to be a cubemap.
     * Defaults to false.
     * @param {number} [options.arrayLength] - Specifies whether the texture is to be a 2D texture array.
     * When passed in as undefined or < 1, this is not an array texture. If >= 1, this is an array texture.
     * Defaults to undefined.
     * @param {boolean} [options.volume] - Specifies whether the texture is to be a 3D volume.
     * Defaults to false.
     * @param {string} [options.type] - Specifies the texture type.  Can be:
     *
     * - {@link TEXTURETYPE_DEFAULT}
     * - {@link TEXTURETYPE_RGBM}
     * - {@link TEXTURETYPE_RGBE}
     * - {@link TEXTURETYPE_RGBP}
     * - {@link TEXTURETYPE_SWIZZLEGGGR}
     *
     * Defaults to {@link TEXTURETYPE_DEFAULT}.
     * @param {boolean} [options.flipY] - Specifies whether the texture should be flipped in the
     * Y-direction. Only affects textures with a source that is an image, canvas or video element.
     * Does not affect cubemaps, compressed textures or textures set from raw pixel data. Defaults
     * to false.
     * @param {boolean} [options.premultiplyAlpha] - If true, the alpha channel of the texture (if
     * present) is multiplied into the color channels. Defaults to false.
     * @param {boolean} [options.compareOnRead] - When enabled, and if texture format is
     * {@link PIXELFORMAT_DEPTH} or {@link PIXELFORMAT_DEPTHSTENCIL}, hardware PCF is enabled for
     * this texture, and you can get filtered results of comparison using texture() in your shader.
     * Defaults to false.
     * @param {number} [options.compareFunc] - Comparison function when compareOnRead is enabled.
     * Can be:
     *
     * - {@link FUNC_LESS}
     * - {@link FUNC_LESSEQUAL}
     * - {@link FUNC_GREATER}
     * - {@link FUNC_GREATEREQUAL}
     * - {@link FUNC_EQUAL}
     * - {@link FUNC_NOTEQUAL}
     *
     * Defaults to {@link FUNC_LESS}.
     * @param {Uint8Array[]|Uint16Array[]|Uint32Array[]|Float32Array[]|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]|Uint8Array[][]} [options.levels]
     * - Array of Uint8Array or other supported browser interface; or a two-dimensional array
     * of Uint8Array if options.arrayLength is defined and greater than zero.
     * @param {boolean} [options.storage] - Defines if texture can be used as a storage texture by
     * a compute shader. Defaults to false.
     * @example
     * // Create a 8x8x24-bit texture
     * const texture = new pc.Texture(graphicsDevice, {
     *     width: 8,
     *     height: 8,
     *     format: pc.PIXELFORMAT_RGB8
     * });
     *
     * // Fill the texture with a gradient
     * const pixels = texture.lock();
     * const count = 0;
     * for (let i = 0; i < 8; i++) {
     *     for (let j = 0; j < 8; j++) {
     *         pixels[count++] = i * 32;
     *         pixels[count++] = j * 32;
     *         pixels[count++] = 255;
     *     }
     * }
     * texture.unlock();
     */ constructor(graphicsDevice, options = {}){
        /** @ignore */ this._gpuSize = 0;
        /** @protected */ this.id = id++;
        /** @protected */ this._invalid = false;
        /** @protected */ this._lockedLevel = -1;
        /** @protected */ this._lockedMode = TEXTURELOCK_NONE;
        /**
     * A render version used to track the last time the texture properties requiring bind group
     * to be updated were changed.
     *
     * @type {number}
     * @ignore
     */ this.renderVersionDirty = 0;
        /** @protected */ this._storage = false;
        /** @protected */ this._numLevels = 0;
        this.device = graphicsDevice;
        Debug.assert(this.device, 'Texture constructor requires a graphicsDevice to be valid');
        Debug.assert(!options.width || Number.isInteger(options.width), 'Texture width must be an integer number, got', options);
        Debug.assert(!options.height || Number.isInteger(options.height), 'Texture height must be an integer number, got', options);
        Debug.assert(!options.depth || Number.isInteger(options.depth), 'Texture depth must be an integer number, got', options);
        this.name = options.name ?? '';
        this._width = Math.floor(options.width ?? 4);
        this._height = Math.floor(options.height ?? 4);
        this._format = options.format ?? PIXELFORMAT_RGBA8;
        this._compressed = isCompressedPixelFormat(this._format);
        this._integerFormat = isIntegerPixelFormat(this._format);
        if (this._integerFormat) {
            options.minFilter = FILTER_NEAREST;
            options.magFilter = FILTER_NEAREST;
        }
        this._volume = options.volume ?? false;
        this._depth = Math.floor(options.depth ?? 1);
        this._arrayLength = Math.floor(options.arrayLength ?? 0);
        this._storage = options.storage ?? false;
        this._cubemap = options.cubemap ?? false;
        this._flipY = options.flipY ?? false;
        this._premultiplyAlpha = options.premultiplyAlpha ?? false;
        this._mipmaps = options.mipmaps ?? true;
        this._numLevelsRequested = options.numLevels;
        if (options.numLevels !== undefined) {
            this._numLevels = options.numLevels;
        }
        this._updateNumLevel();
        this._minFilter = options.minFilter ?? FILTER_LINEAR_MIPMAP_LINEAR;
        this._magFilter = options.magFilter ?? FILTER_LINEAR;
        this._anisotropy = options.anisotropy ?? 1;
        this._addressU = options.addressU ?? ADDRESS_REPEAT;
        this._addressV = options.addressV ?? ADDRESS_REPEAT;
        this._addressW = options.addressW ?? ADDRESS_REPEAT;
        this._compareOnRead = options.compareOnRead ?? false;
        this._compareFunc = options.compareFunc ?? FUNC_LESS;
        this._type = options.type ?? TEXTURETYPE_DEFAULT;
        Debug.assert(!options.hasOwnProperty('rgbm'), 'Use options.type.');
        Debug.assert(!options.hasOwnProperty('swizzleGGGR'), 'Use options.type.');
        this.projection = TEXTUREPROJECTION_NONE;
        if (this._cubemap) {
            this.projection = TEXTUREPROJECTION_CUBE;
        } else if (options.projection && options.projection !== TEXTUREPROJECTION_CUBE) {
            this.projection = options.projection;
        }
        this.profilerHint = options.profilerHint ?? 0;
        this._levels = options.levels;
        const upload = !!options.levels;
        if (!this._levels) {
            this._clearLevels();
        }
        this.recreateImpl(upload);
        // track the texture
        graphicsDevice.textures.push(this);
        Debug.trace(TRACEID_TEXTURE_ALLOC, `Alloc: Id ${this.id} ${this.name}: ${this.width}x${this.height} [${pixelFormatInfo.get(this.format)?.name}]` + `${this.cubemap ? '[Cubemap]' : ''}` + `${this.volume ? '[Volume]' : ''}` + `${this.array ? '[Array]' : ''}` + `[MipLevels:${this.numLevels}]`, this);
    }
    /**
     * Frees resources associated with this texture.
     */ destroy() {
        Debug.trace(TRACEID_TEXTURE_ALLOC, `DeAlloc: Id ${this.id} ${this.name}`);
        const device = this.device;
        if (device) {
            // stop tracking the texture
            const idx = device.textures.indexOf(this);
            if (idx !== -1) {
                device.textures.splice(idx, 1);
            }
            // Remove texture from any uniforms
            device.scope.removeValue(this);
            // destroy implementation
            this.impl.destroy(device);
            // Update texture stats
            this.adjustVramSizeTracking(device._vram, -this._gpuSize);
            this._levels = null;
            this.device = null;
        }
    }
    recreateImpl(upload = true) {
        const { device } = this;
        // destroy existing
        this.impl?.destroy(device);
        this.impl = null;
        // create new
        this.impl = device.createTextureImpl(this);
        this.dirtyAll();
        if (upload) {
            this.upload();
        }
    }
    _clearLevels() {
        this._levels = this._cubemap ? [
            [
                null,
                null,
                null,
                null,
                null,
                null
            ]
        ] : [
            null
        ];
    }
    /**
     * Resizes the texture. This operation is supported for render target textures, and it resizes
     * the allocated buffer used for rendering, not the existing content of the texture.
     *
     * It is also supported for textures with data provided via the {@link lock} method. After
     * resizing, the appropriately sized data must be assigned by calling {@link lock} again.
     *
     * @param {number} width - The new width of the texture.
     * @param {number} height - The new height of the texture.
     * @param {number} [depth] - The new depth of the texture. Defaults to 1.
     * @ignore
     */ resize(width, height, depth = 1) {
        // destroy texture impl
        const device = this.device;
        this.adjustVramSizeTracking(device._vram, -this._gpuSize);
        this.impl.destroy(device);
        this._clearLevels();
        this._width = Math.floor(width);
        this._height = Math.floor(height);
        this._depth = Math.floor(depth);
        this._updateNumLevel();
        // re-create the implementation
        this.impl = device.createTextureImpl(this);
        this.dirtyAll();
    }
    /**
     * Called when the rendering context was lost. It releases all context related resources.
     *
     * @ignore
     */ loseContext() {
        this.impl.loseContext();
        this.dirtyAll();
    }
    /**
     * Updates vram size tracking for the texture, size can be positive to add or negative to subtract
     *
     * @ignore
     */ adjustVramSizeTracking(vram, size) {
        Debug.trace(TRACEID_VRAM_TEXTURE, `${this.id} ${this.name} size: ${size} vram.texture: ${vram.tex} => ${vram.tex + size}`);
        vram.tex += size;
        if (this.profilerHint === TEXHINT_SHADOWMAP) {
            vram.texShadow += size;
        } else if (this.profilerHint === TEXHINT_ASSET) {
            vram.texAsset += size;
        } else if (this.profilerHint === TEXHINT_LIGHTMAP) {
            vram.texLightmap += size;
        }
    }
    propertyChanged(flag) {
        this.impl.propertyChanged(flag);
        this.renderVersionDirty = this.device.renderVersion;
    }
    _updateNumLevel() {
        const maxLevels = this.mipmaps ? TextureUtils.calcMipLevelsCount(this.width, this.height) : 1;
        const requestedLevels = this._numLevelsRequested;
        if (requestedLevels !== undefined && requestedLevels > maxLevels) {
            Debug.warn('Texture#numLevels: requested mip level count is greater than the maximum possible, will be clamped to', maxLevels, this);
        }
        this._numLevels = Math.min(requestedLevels ?? maxLevels, maxLevels);
        this._mipmaps = this._numLevels > 1;
    }
    /**
     * Returns the current lock mode. One of:
     *
     * - {@link TEXTURELOCK_NONE}
     * - {@link TEXTURELOCK_READ}
     * - {@link TEXTURELOCK_WRITE}
     *
     * @ignore
     * @type {number}
     */ get lockedMode() {
        return this._lockedMode;
    }
    /**
     * Sets the minification filter to be applied to the texture. Can be:
     *
     * - {@link FILTER_NEAREST}
     * - {@link FILTER_LINEAR}
     * - {@link FILTER_NEAREST_MIPMAP_NEAREST}
     * - {@link FILTER_NEAREST_MIPMAP_LINEAR}
     * - {@link FILTER_LINEAR_MIPMAP_NEAREST}
     * - {@link FILTER_LINEAR_MIPMAP_LINEAR}
     *
     * @type {number}
     */ set minFilter(v) {
        if (this._minFilter !== v) {
            if (isIntegerPixelFormat(this._format)) {
                Debug.warn('Texture#minFilter: minFilter property cannot be changed on an integer texture, will remain FILTER_NEAREST', this);
            } else {
                this._minFilter = v;
                this.propertyChanged(TEXPROPERTY_MIN_FILTER);
            }
        }
    }
    /**
     * Gets the minification filter to be applied to the texture.
     *
     * @type {number}
     */ get minFilter() {
        return this._minFilter;
    }
    /**
     * Sets the magnification filter to be applied to the texture. Can be:
     *
     * - {@link FILTER_NEAREST}
     * - {@link FILTER_LINEAR}
     *
     * @type {number}
     */ set magFilter(v) {
        if (this._magFilter !== v) {
            if (isIntegerPixelFormat(this._format)) {
                Debug.warn('Texture#magFilter: magFilter property cannot be changed on an integer texture, will remain FILTER_NEAREST', this);
            } else {
                this._magFilter = v;
                this.propertyChanged(TEXPROPERTY_MAG_FILTER);
            }
        }
    }
    /**
     * Gets the magnification filter to be applied to the texture.
     *
     * @type {number}
     */ get magFilter() {
        return this._magFilter;
    }
    /**
     * Sets the addressing mode to be applied to the texture horizontally. Can be:
     *
     * - {@link ADDRESS_REPEAT}
     * - {@link ADDRESS_CLAMP_TO_EDGE}
     * - {@link ADDRESS_MIRRORED_REPEAT}
     *
     * @type {number}
     */ set addressU(v) {
        if (this._addressU !== v) {
            this._addressU = v;
            this.propertyChanged(TEXPROPERTY_ADDRESS_U);
        }
    }
    /**
     * Gets the addressing mode to be applied to the texture horizontally.
     *
     * @type {number}
     */ get addressU() {
        return this._addressU;
    }
    /**
     * Sets the addressing mode to be applied to the texture vertically. Can be:
     *
     * - {@link ADDRESS_REPEAT}
     * - {@link ADDRESS_CLAMP_TO_EDGE}
     * - {@link ADDRESS_MIRRORED_REPEAT}
     *
     * @type {number}
     */ set addressV(v) {
        if (this._addressV !== v) {
            this._addressV = v;
            this.propertyChanged(TEXPROPERTY_ADDRESS_V);
        }
    }
    /**
     * Gets the addressing mode to be applied to the texture vertically.
     *
     * @type {number}
     */ get addressV() {
        return this._addressV;
    }
    /**
     * Sets the addressing mode to be applied to the 3D texture depth. Can be:
     *
     * - {@link ADDRESS_REPEAT}
     * - {@link ADDRESS_CLAMP_TO_EDGE}
     * - {@link ADDRESS_MIRRORED_REPEAT}
     *
     * @type {number}
     */ set addressW(addressW) {
        if (!this._volume) {
            Debug.warn('pc.Texture#addressW: Can\'t set W addressing mode for a non-3D texture.');
            return;
        }
        if (addressW !== this._addressW) {
            this._addressW = addressW;
            this.propertyChanged(TEXPROPERTY_ADDRESS_W);
        }
    }
    /**
     * Gets the addressing mode to be applied to the 3D texture depth.
     *
     * @type {number}
     */ get addressW() {
        return this._addressW;
    }
    /**
     * When enabled, and if texture format is {@link PIXELFORMAT_DEPTH} or
     * {@link PIXELFORMAT_DEPTHSTENCIL}, hardware PCF is enabled for this texture, and you can get
     * filtered results of comparison using texture() in your shader.
     *
     * @type {boolean}
     */ set compareOnRead(v) {
        if (this._compareOnRead !== v) {
            this._compareOnRead = v;
            this.propertyChanged(TEXPROPERTY_COMPARE_ON_READ);
        }
    }
    /**
     * Gets whether you can get filtered results of comparison using texture() in your shader.
     *
     * @type {boolean}
     */ get compareOnRead() {
        return this._compareOnRead;
    }
    /**
     * Sets the comparison function when {@link compareOnRead} is enabled. Possible values:
     *
     * - {@link FUNC_LESS}
     * - {@link FUNC_LESSEQUAL}
     * - {@link FUNC_GREATER}
     * - {@link FUNC_GREATEREQUAL}
     * - {@link FUNC_EQUAL}
     * - {@link FUNC_NOTEQUAL}
     *
     * @type {number}
     */ set compareFunc(v) {
        if (this._compareFunc !== v) {
            this._compareFunc = v;
            this.propertyChanged(TEXPROPERTY_COMPARE_FUNC);
        }
    }
    /**
     * Gets the comparison function when {@link compareOnRead} is enabled.
     *
     * @type {number}
     */ get compareFunc() {
        return this._compareFunc;
    }
    /**
     * Sets the integer value specifying the level of anisotropy to apply to the texture. The value
     * ranges from 1 (no anisotropic filtering) to the maximum anisotropy supported by the graphics
     * device (see {@link GraphicsDevice#maxAnisotropy}).
     *
     * @type {number}
     */ set anisotropy(v) {
        if (this._anisotropy !== v) {
            this._anisotropy = v;
            this.propertyChanged(TEXPROPERTY_ANISOTROPY);
        }
    }
    /**
     * Gets the integer value specifying the level of anisotropy to apply to the texture.
     *
     * @type {number}
     */ get anisotropy() {
        return this._anisotropy;
    }
    /**
     * Sets whether the texture should generate/upload mipmaps.
     *
     * @type {boolean}
     */ set mipmaps(v) {
        if (this._mipmaps !== v) {
            if (this.device.isWebGPU) {
                Debug.warn('Texture#mipmaps: mipmap property is currently not allowed to be changed on WebGPU, create the texture appropriately.', this);
            } else if (isIntegerPixelFormat(this._format)) {
                Debug.warn('Texture#mipmaps: mipmap property cannot be changed on an integer texture, will remain false', this);
            } else {
                this._mipmaps = v;
            }
            if (v) this._needsMipmapsUpload = true;
        }
    }
    /**
     * Gets whether the texture should generate/upload mipmaps.
     *
     * @type {boolean}
     */ get mipmaps() {
        return this._mipmaps;
    }
    /**
     * Gets the number of mip levels.
     *
     * @type {number}
     */ get numLevels() {
        return this._numLevels;
    }
    /**
     * Defines if texture can be used as a storage texture by a compute shader.
     *
     * @type {boolean}
     */ get storage() {
        return this._storage;
    }
    /**
     * The width of the texture in pixels.
     *
     * @type {number}
     */ get width() {
        return this._width;
    }
    /**
     * The height of the texture in pixels.
     *
     * @type {number}
     */ get height() {
        return this._height;
    }
    /**
     * The number of depth slices in a 3D texture.
     *
     * @type {number}
     */ get depth() {
        return this._depth;
    }
    /**
     * The pixel format of the texture. Can be:
     *
     * - {@link PIXELFORMAT_R8}
     * - {@link PIXELFORMAT_RG8}
     * - {@link PIXELFORMAT_RGB565}
     * - {@link PIXELFORMAT_RGBA5551}
     * - {@link PIXELFORMAT_RGBA4}
     * - {@link PIXELFORMAT_RGB8}
     * - {@link PIXELFORMAT_RGBA8}
     * - {@link PIXELFORMAT_DXT1}
     * - {@link PIXELFORMAT_DXT3}
     * - {@link PIXELFORMAT_DXT5}
     * - {@link PIXELFORMAT_RGB16F}
     * - {@link PIXELFORMAT_RGBA16F}
     * - {@link PIXELFORMAT_RGB32F}
     * - {@link PIXELFORMAT_RGBA32F}
     * - {@link PIXELFORMAT_ETC1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_2BPP_RGBA_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGB_1}
     * - {@link PIXELFORMAT_PVRTC_4BPP_RGBA_1}
     * - {@link PIXELFORMAT_111110F}
     * - {@link PIXELFORMAT_ASTC_4x4}
     * - {@link PIXELFORMAT_ATC_RGB}
     * - {@link PIXELFORMAT_ATC_RGBA}
     *
     * @type {number}
     */ get format() {
        return this._format;
    }
    /**
     * Returns true if this texture is a cube map and false otherwise.
     *
     * @type {boolean}
     */ get cubemap() {
        return this._cubemap;
    }
    get gpuSize() {
        const mips = this.pot && this._mipmaps && !(this._compressed && this._levels.length === 1);
        return TextureUtils.calcGpuSize(this._width, this._height, this._depth, this._format, mips, this._cubemap);
    }
    /**
     * Returns true if this texture is a 2D texture array and false otherwise.
     *
     * @type {boolean}
     */ get array() {
        return this._arrayLength > 0;
    }
    /**
     * Returns the number of textures inside this texture if this is a 2D array texture or 0 otherwise.
     *
     * @type {number}
     */ get arrayLength() {
        return this._arrayLength;
    }
    /**
     * Returns true if this texture is a 3D volume and false otherwise.
     *
     * @type {boolean}
     */ get volume() {
        return this._volume;
    }
    /**
     * Sets the texture type.
     *
     * @type {string}
     * @ignore
     */ set type(value) {
        if (this._type !== value) {
            this._type = value;
            // update all shaders to respect the encoding of the texture (needed by the standard material)
            this.device._shadersDirty = true;
        }
    }
    /**
     * Gets the texture type.
     *
     * @type {string}
     * @ignore
     */ get type() {
        return this._type;
    }
    /**
     * Sets the texture's internal format to an sRGB or linear equivalent of its current format.
     * When set to true, the texture is stored in sRGB format and automatically converted to linear
     * space when sampled. When set to false, the texture remains in a linear format. Changing this
     * property recreates the texture on the GPU, which is an expensive operation, so it is
     * preferable to create the texture with the correct format from the start. If the texture
     * format has no sRGB variant, this operation is ignored.
     * This is not a public API and is used by Editor only to update rendering when the sRGB
     * property is changed in the inspector. The higher cost is acceptable in this case.
     *
     * @type {boolean}
     * @ignore
     */ set srgb(value) {
        const currentSrgb = isSrgbPixelFormat(this.format);
        if (value !== currentSrgb) {
            if (value) {
                // switch to sRGB
                const srgbFormat = pixelFormatLinearToGamma(this.format);
                if (this._format !== srgbFormat) {
                    Debug.warn(`Switching format of texture '${this.name}' to sRGB equivalent: ${pixelFormatInfo.get(this.format)?.name} -> ${pixelFormatInfo.get(srgbFormat)?.name}. This is an expensive operation, and the texture should be created using the right format to avoid this.`, this);
                    this._format = srgbFormat;
                    this.recreateImpl();
                    // update all shaders to respect the encoding of the texture (needed by the standard material)
                    this.device._shadersDirty = true;
                }
            } else {
                // switch to linear
                const linearFormat = pixelFormatGammaToLinear(this.format);
                if (this._format !== linearFormat) {
                    Debug.warn(`Switching format of texture '${this.name}' to linear equivalent: ${pixelFormatInfo.get(this.format)?.name} -> ${pixelFormatInfo.get(linearFormat)?.name}. This is an expensive operation, and the texture should be created using the right format to avoid this.`, this);
                    this._format = linearFormat;
                    this.recreateImpl();
                    // update all shaders to respect the encoding of the texture (needed by the standard material)
                    this.device._shadersDirty = true;
                }
            }
        }
    }
    /**
     * Returns true if the texture is stored in an sRGB format, meaning it will be converted to
     * linear space when sampled. Returns false if the texture is stored in a linear format.
     *
     * @type {boolean}
     */ get srgb() {
        return isSrgbPixelFormat(this.format);
    }
    /**
     * Sets whether the texture should be flipped in the Y-direction. Only affects textures
     * with a source that is an image, canvas or video element. Does not affect cubemaps,
     * compressed textures or textures set from raw pixel data. Defaults to true.
     *
     * @type {boolean}
     */ set flipY(flipY) {
        if (this._flipY !== flipY) {
            this._flipY = flipY;
            this._needsUpload = true;
        }
    }
    /**
     * Gets whether the texture should be flipped in the Y-direction.
     *
     * @type {boolean}
     */ get flipY() {
        return this._flipY;
    }
    set premultiplyAlpha(premultiplyAlpha) {
        if (this._premultiplyAlpha !== premultiplyAlpha) {
            this._premultiplyAlpha = premultiplyAlpha;
            this._needsUpload = true;
        }
    }
    get premultiplyAlpha() {
        return this._premultiplyAlpha;
    }
    /**
     * Returns true if all dimensions of the texture are power of two, and false otherwise.
     *
     * @type {boolean}
     */ get pot() {
        return math.powerOfTwo(this._width) && math.powerOfTwo(this._height);
    }
    // get the texture's encoding type
    get encoding() {
        switch(this.type){
            case TEXTURETYPE_RGBM:
                return 'rgbm';
            case TEXTURETYPE_RGBE:
                return 'rgbe';
            case TEXTURETYPE_RGBP:
                return 'rgbp';
        }
        // note that the srgb part only makes sense for texture storing color data
        return requiresManualGamma(this.format) ? 'srgb' : 'linear';
    }
    // Force a full resubmission of the texture to the GPU (used on a context restore event)
    dirtyAll() {
        this._levelsUpdated = this._cubemap ? [
            [
                true,
                true,
                true,
                true,
                true,
                true
            ]
        ] : [
            true
        ];
        this._needsUpload = true;
        this._needsMipmapsUpload = this._mipmaps;
        this._mipmapsUploaded = false;
        this.propertyChanged(TEXPROPERTY_ALL);
    }
    /**
     * Locks a miplevel of the texture, returning a typed array to be filled with pixel data.
     *
     * @param {object} [options] - Optional options object. Valid properties are as follows:
     * @param {number} [options.level] - The mip level to lock with 0 being the top level. Defaults
     * to 0.
     * @param {number} [options.face] - If the texture is a cubemap, this is the index of the face
     * to lock.
     * @param {number} [options.mode] - The lock mode. Can be:
     * - {@link TEXTURELOCK_READ}
     * - {@link TEXTURELOCK_WRITE}
     * Defaults to {@link TEXTURELOCK_WRITE}.
     * @returns {Uint8Array|Uint16Array|Uint32Array|Float32Array} A typed array containing the pixel data of
     * the locked mip level.
     */ lock(options = {}) {
        // Initialize options to some sensible defaults
        options.level ??= 0;
        options.face ??= 0;
        options.mode ??= TEXTURELOCK_WRITE;
        Debug.assert(this._lockedMode === TEXTURELOCK_NONE, 'The texture is already locked. Call `texture.unlock()` before attempting to lock again.', this);
        Debug.assert(options.mode === TEXTURELOCK_READ || options.mode === TEXTURELOCK_WRITE, 'Cannot lock a texture with TEXTURELOCK_NONE. To unlock a texture, call `texture.unlock()`.', this);
        this._lockedMode = options.mode;
        this._lockedLevel = options.level;
        const levels = this.cubemap ? this._levels[options.face] : this._levels;
        if (levels[options.level] === null) {
            // allocate storage for this mip level
            const width = Math.max(1, this._width >> options.level);
            const height = Math.max(1, this._height >> options.level);
            const depth = Math.max(1, this._depth >> options.level);
            const data = new ArrayBuffer(TextureUtils.calcLevelGpuSize(width, height, depth, this._format));
            levels[options.level] = new (getPixelFormatArrayType(this._format))(data);
        }
        return levels[options.level];
    }
    /**
     * Set the pixel data of the texture from a canvas, image, video DOM element. If the texture is
     * a cubemap, the supplied source must be an array of 6 canvases, images or videos.
     *
     * @param {HTMLCanvasElement|HTMLImageElement|HTMLVideoElement|HTMLCanvasElement[]|HTMLImageElement[]|HTMLVideoElement[]} source - A
     * canvas, image or video element, or an array of 6 canvas, image or video elements.
     * @param {number} [mipLevel] - A non-negative integer specifying the image level of detail.
     * Defaults to 0, which represents the base image source. A level value of N, that is greater
     * than 0, represents the image source for the Nth mipmap reduction level.
     */ setSource(source, mipLevel = 0) {
        let invalid = false;
        let width, height;
        if (this._cubemap) {
            if (source[0]) {
                // rely on first face sizes
                width = source[0].width || 0;
                height = source[0].height || 0;
                for(let i = 0; i < 6; i++){
                    const face = source[i];
                    // cubemap becomes invalid if any condition is not satisfied
                    if (!face || // face is missing
                    face.width !== width || // face is different width
                    face.height !== height || // face is different height
                    !this.device._isBrowserInterface(face)) {
                        invalid = true;
                        break;
                    }
                }
            } else {
                // first face is missing
                invalid = true;
            }
            if (!invalid) {
                // mark levels as updated
                for(let i = 0; i < 6; i++){
                    if (this._levels[mipLevel][i] !== source[i]) {
                        this._levelsUpdated[mipLevel][i] = true;
                    }
                }
            }
        } else {
            // check if source is valid type of element
            if (!this.device._isBrowserInterface(source)) {
                invalid = true;
            }
            if (!invalid) {
                // mark level as updated
                if (source !== this._levels[mipLevel]) {
                    this._levelsUpdated[mipLevel] = true;
                }
                if (source instanceof HTMLVideoElement) {
                    width = source.videoWidth;
                    height = source.videoHeight;
                } else {
                    width = source.width;
                    height = source.height;
                }
            }
        }
        if (invalid) {
            // invalid texture
            // default sizes
            this._width = 4;
            this._height = 4;
            // remove levels
            if (this._cubemap) {
                for(let i = 0; i < 6; i++){
                    this._levels[mipLevel][i] = null;
                    this._levelsUpdated[mipLevel][i] = true;
                }
            } else {
                this._levels[mipLevel] = null;
                this._levelsUpdated[mipLevel] = true;
            }
        } else {
            // valid texture
            if (mipLevel === 0) {
                this._width = width;
                this._height = height;
            }
            this._levels[mipLevel] = source;
        }
        // valid or changed state of validity
        if (this._invalid !== invalid || !invalid) {
            this._invalid = invalid;
            // reupload
            this.upload();
        }
    }
    /**
     * Get the pixel data of the texture. If this is a cubemap then an array of 6 images will be
     * returned otherwise a single image.
     *
     * @param {number} [mipLevel] - A non-negative integer specifying the image level of detail.
     * Defaults to 0, which represents the base image source. A level value of N, that is greater
     * than 0, represents the image source for the Nth mipmap reduction level.
     * @returns {HTMLImageElement} The source image of this texture. Can be null if source not
     * assigned for specific image level.
     */ getSource(mipLevel = 0) {
        return this._levels[mipLevel];
    }
    /**
     * Unlocks the currently locked mip level and uploads it to VRAM.
     */ unlock() {
        if (this._lockedMode === TEXTURELOCK_NONE) {
            Debug.warn('pc.Texture#unlock: Attempting to unlock a texture that is not locked.', this);
        }
        // Upload the new pixel data if locked in write mode (default)
        if (this._lockedMode === TEXTURELOCK_WRITE) {
            this.upload();
        }
        this._lockedLevel = -1;
        this._lockedMode = TEXTURELOCK_NONE;
    }
    /**
     * Forces a reupload of the textures pixel data to graphics memory. Ordinarily, this function
     * is called by internally by {@link setSource} and {@link unlock}. However, it still needs to
     * be called explicitly in the case where an HTMLVideoElement is set as the source of the
     * texture.  Normally, this is done once every frame before video textured geometry is
     * rendered.
     */ upload() {
        this._needsUpload = true;
        this._needsMipmapsUpload = this._mipmaps;
        this.impl.uploadImmediate?.(this.device, this);
    }
    /**
     * Download the textures data from the graphics memory to the local memory.
     *
     * Note a public API yet, as not all options are implemented on all platforms.
     *
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} width - The width of the rectangle.
     * @param {number} height - The height of the rectangle.
     * @param {object} [options] - Object for passing optional arguments.
     * @param {RenderTarget} [options.renderTarget] - The render target using the texture as a color
     * buffer. Provide as an optimization to avoid creating a new render target. Important especially
     * when this function is called with high frequency (per frame). Note that this is only utilized
     * on the WebGL platform, and ignored on WebGPU.
     * @param {number} [options.mipLevel] - The mip level to download. Defaults to 0.
     * @param {number} [options.face] - The face to download. Defaults to 0.
     * @param {Uint8Array|Uint16Array|Uint32Array|Float32Array} [options.data] - The data buffer to
     * write the pixel data to. If not provided, a new buffer will be created. The type of the buffer
     * must match the texture's format.
     * @param {boolean} [options.immediate] - If true, the read operation will be executed as soon as
     * possible. This has a performance impact, so it should be used only when necessary. Defaults
     * to false.
     * @returns {Promise<Uint8Array|Uint16Array|Uint32Array|Float32Array>} A promise that resolves
     * with the pixel data of the texture.
     * @ignore
     */ read(x, y, width, height, options = {}) {
        return this.impl.read?.(x, y, width, height, options);
    }
    /**
     * Upload texture data asynchronously to the GPU.
     *
     * @param {number} x - The left edge of the rectangle.
     * @param {number} y - The top edge of the rectangle.
     * @param {number} width - The width of the rectangle.
     * @param {number} height - The height of the rectangle.
     * @param {Uint8Array|Uint16Array|Uint32Array|Float32Array} data - The pixel data to upload. This should be a typed array.
     *
     * @returns {Promise<void>} A promise that resolves when the upload is complete.
     * @ignore
     */ write(x, y, width, height, data) {
        return this.impl.write?.(x, y, width, height, data);
    }
}

export { Texture };
