Source: graphics/texture.js

Object.assign(pc, function () {
    'use strict';

    /**
     * @constructor
     * @name pc.Texture
     * @classdesc A texture is a container for texel data that can be utilized in a fragment shader.
     * Typically, the texel data represents an image that is mapped over geometry.
     * @description Creates a new texture.
     * @param {pc.GraphicsDevice} graphicsDevice The graphics device used to manage this texture.
     * @param {Object} options Object for passing optional arguments.
     * @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 (WebGL2 only). Defaults to 1 (single 2D image).
     * @param {Number} options.format The pixel format of the texture. Can be:
     * <ul>
     *     <li>{@link pc.PIXELFORMAT_A8}</li>
     *     <li>{@link pc.PIXELFORMAT_L8}</li>
     *     <li>{@link pc.PIXELFORMAT_L8_A8}</li>
     *     <li>{@link pc.PIXELFORMAT_R5_G6_B5}</li>
     *     <li>{@link pc.PIXELFORMAT_R5_G5_B5_A1}</li>
     *     <li>{@link pc.PIXELFORMAT_R4_G4_B4_A4}</li>
     *     <li>{@link pc.PIXELFORMAT_R8_G8_B8}</li>
     *     <li>{@link pc.PIXELFORMAT_R8_G8_B8_A8}</li>
     *     <li>{@link pc.PIXELFORMAT_DXT1}</li>
     *     <li>{@link pc.PIXELFORMAT_DXT3}</li>
     *     <li>{@link pc.PIXELFORMAT_DXT5}</li>
     *     <li>{@link pc.PIXELFORMAT_RGB16F}</li>
     *     <li>{@link pc.PIXELFORMAT_RGBA16F}</li>
     *     <li>{@link pc.PIXELFORMAT_RGB32F}</li>
     *     <li>{@link pc.PIXELFORMAT_RGBA32F}</li>
     *     <li>{@link pc.PIXELFORMAT_ETC1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_2BPP_RGB_1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_4BPP_RGB_1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1}</li>
     *     <li>{@link pc.PIXELFORMAT_111110F}</li>
     * </ul>
     * Defaults to pc.PIXELFORMAT_R8_G8_B8_A8.
     * @param {Number} options.minFilter The minification filter type to use. Defaults to {@link pc.FILTER_LINEAR_MIPMAP_LINEAR}
     * @param {Number} options.magFilter The magnification filter type to use. Defaults to {@link pc.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 pc.ADDRESS_REPEAT}
     * @param {Number} options.addressV The repeat mode to use in the V direction. Defaults to {@link pc.ADDRESS_REPEAT}
     * @param {Boolean} options.mipmaps When enabled try to generate or use mipmaps for this texture. Default is true
     * @param {Boolean} options.cubemap Specifies whether the texture is to be a cubemap. Defaults to false.
     * @param {Boolean} options.volume Specifies whether the texture is to be a 3D volume (WebGL2 only). Defaults to false.
     * @param {Boolean} options.rgbm Specifies whether the texture contains RGBM-encoded HDR data. Defaults to false.
     * @param {Boolean} options.fixCubemapSeams Specifies whether this cubemap texture requires special
     * seam fixing shader code to look right. Defaults to false.
     * @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 true.
     * @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 pc.PIXELFORMAT_DEPTH or pc.PIXELFORMAT_DEPTHSTENCIL,
     * hardware PCF is enabled for this texture, and you can get filtered results of comparison using texture() in your shader (WebGL2 only).
     * Defaults to false.
     * @param {Number} options.compareFunc Comparison function when compareOnRead is enabled (WebGL2 only). Defaults to pc.FUNC_LESS.
     * Possible values:
     * <ul>
     *     <li>pc.FUNC_LESS</li>
     *     <li>pc.FUNC_LESSEQUAL</li>
     *     <li>pc.FUNC_GREATER</li>
     *     <li>pc.FUNC_GREATEREQUAL</li>
     *     <li>pc.FUNC_EQUAL</li>
     *     <li>pc.FUNC_NOTEQUAL</li>
     * </ul>
     * @example
     * // Create a 8x8x24-bit texture
     * var texture = new pc.Texture(graphicsDevice, {
     *     width: 8,
     *     height: 8,
     *     format: pc.PIXELFORMAT_R8_G8_B8
     * });
     *
     * // Fill the texture with a gradient
     * var pixels = texture.lock();
     * var count = 0;
     * for (var i = 0; i < 8; i++) {
     *     for (var j = 0; j < 8; j++) {
     *         pixels[count++] = i * 32;
     *         pixels[count++] = j * 32;
     *         pixels[count++] = 255;
     *     }
     * }
     * texture.unlock();
     * @property {String} name The name of the texture. Defaults to null.
     */
    var Texture = function (graphicsDevice, options) {
        this.device = graphicsDevice;

        this.name = null;
        this._width = 4;
        this._height = 4;
        this._depth = 1;
        this._pot = true;

        this._format = pc.PIXELFORMAT_R8_G8_B8_A8;
        this.rgbm = false;

        this._cubemap = false;
        this._volume = false;
        this.fixCubemapSeams = false;
        this._flipY = true;
        this._premultiplyAlpha = false;

        this._mipmaps = true;

        this._minFilter = pc.FILTER_LINEAR_MIPMAP_LINEAR;
        this._magFilter = pc.FILTER_LINEAR;
        this._anisotropy = 1;
        this._addressU = pc.ADDRESS_REPEAT;
        this._addressV = pc.ADDRESS_REPEAT;
        this._addressW = pc.ADDRESS_REPEAT;

        this._compareOnRead = false;
        this._compareFunc = pc.FUNC_LESS;

        // #ifdef PROFILER
        this.profilerHint = 0;
        // #endif

        if (options !== undefined) {
            this._width = (options.width !== undefined) ? options.width : this._width;
            this._height = (options.height !== undefined) ? options.height : this._height;
            this._pot = pc.math.powerOfTwo(this._width) && pc.math.powerOfTwo(this._height);

            this._format = (options.format !== undefined) ? options.format : this._format;
            this.rgbm = (options.rgbm !== undefined) ? options.rgbm : this.rgbm;

            if (options.mipmaps !== undefined) {
                this._mipmaps = options.mipmaps;
            } else {
                this._mipmaps = (options.autoMipmap !== undefined) ? options.autoMipmap : this._mipmaps;
            }

            this._levels = options.levels;

            this._cubemap = (options.cubemap !== undefined) ? options.cubemap : this._cubemap;
            this.fixCubemapSeams = (options.fixCubemapSeams !== undefined) ? options.fixCubemapSeams : this.fixCubemapSeams;

            this._minFilter = (options.minFilter !== undefined) ? options.minFilter : this._minFilter;
            this._magFilter = (options.magFilter !== undefined) ? options.magFilter : this._magFilter;
            this._anisotropy = (options.anisotropy !== undefined) ? options.anisotropy : this._anisotropy;
            this._addressU = (options.addressU !== undefined) ? options.addressU : this._addressU;
            this._addressV = (options.addressV !== undefined) ? options.addressV : this._addressV;

            this._compareOnRead = (options.compareOnRead !== undefined) ? options.compareOnRead : this._compareOnRead;
            this._compareFunc = (options._compareFunc !== undefined) ? options._compareFunc : this._compareFunc;

            this._flipY = (options.flipY !== undefined) ? options.flipY : this._flipY;
            this._premultiplyAlpha = (options.premultiplyAlpha !== undefined) ? options.premultiplyAlpha : this._premultiplyAlpha;

            if (graphicsDevice.webgl2) {
                this._depth = (options.depth !== undefined) ? options.depth : this._depth;
                this._volume = (options.volume !== undefined) ? options.volume : this._volume;
                this._addressW = (options.addressW !== undefined) ? options.addressW : this._addressW;
            }

            // #ifdef PROFILER
            this.profilerHint = (options.profilerHint !== undefined) ? options.profilerHint : this.profilerHint;
            // #endif
        }

        this._compressed = (this._format === pc.PIXELFORMAT_DXT1 ||
                            this._format === pc.PIXELFORMAT_DXT3 ||
                            this._format === pc.PIXELFORMAT_DXT5 ||
                            this._format >= pc.PIXELFORMAT_ETC1);

        // Mip levels
        this._invalid = false;
        this._lockedLevel = -1;
        if (!this._levels) {
            this._levels = this._cubemap ? [[null, null, null, null, null, null]] : [null];
        }

        this.dirtyAll();

        this._gpuSize = 0;
    };

    // Public properties
    /**
     * @name pc.Texture#minFilter
     * @type Number
     * @description The minification filter to be applied to the texture. Can be:
     * <ul>
     *     <li>{@link pc.FILTER_NEAREST}</li>
     *     <li>{@link pc.FILTER_LINEAR}</li>
     *     <li>{@link pc.FILTER_NEAREST_MIPMAP_NEAREST}</li>
     *     <li>{@link pc.FILTER_NEAREST_MIPMAP_LINEAR}</li>
     *     <li>{@link pc.FILTER_LINEAR_MIPMAP_NEAREST}</li>
     *     <li>{@link pc.FILTER_LINEAR_MIPMAP_LINEAR}</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'minFilter', {
        get: function () {
            return this._minFilter;
        },
        set: function (v) {
            if (this._minFilter !== v) {
                this._minFilter = v;
                this._parameterFlags |= 1;
            }
        }
    });

    /**
     * @name pc.Texture#magFilter
     * @type Number
     * @description The magnification filter to be applied to the texture. Can be:
     * <ul>
     *     <li>{@link pc.FILTER_NEAREST}</li>
     *     <li>{@link pc.FILTER_LINEAR}</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'magFilter', {
        get: function () {
            return this._magFilter;
        },
        set: function (v) {
            if (this._magFilter !== v) {
                this._magFilter = v;
                this._parameterFlags |= 2;
            }
        }
    });

    /**
     * @name pc.Texture#addressU
     * @type Number
     * @description The addressing mode to be applied to the texture horizontally. Can be:
     * <ul>
     *     <li>{@link pc.ADDRESS_REPEAT}</li>
     *     <li>{@link pc.ADDRESS_CLAMP_TO_EDGE}</li>
     *     <li>{@link pc.ADDRESS_MIRRORED_REPEAT}</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'addressU', {
        get: function () {
            return this._addressU;
        },
        set: function (v) {
            if (this._addressU !== v) {
                this._addressU = v;
                this._parameterFlags |= 4;
            }
        }
    });

    /**
     * @name pc.Texture#addressV
     * @type Number
     * @description The addressing mode to be applied to the texture vertically. Can be:
     * <ul>
     *     <li>{@link pc.ADDRESS_REPEAT}</li>
     *     <li>{@link pc.ADDRESS_CLAMP_TO_EDGE}</li>
     *     <li>{@link pc.ADDRESS_MIRRORED_REPEAT}</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'addressV', {
        get: function () {
            return this._addressV;
        },
        set: function (v) {
            if (this._addressV !== v) {
                this._addressV = v;
                this._parameterFlags |= 8;
            }
        }
    });

    /**
     * @name pc.Texture#addressW
     * @type Number
     * @description The addressing mode to be applied to the 3D texture depth (WebGL2 only). Can be:
     * <ul>
     *     <li>{@link pc.ADDRESS_REPEAT}</li>
     *     <li>{@link pc.ADDRESS_CLAMP_TO_EDGE}</li>
     *     <li>{@link pc.ADDRESS_MIRRORED_REPEAT}</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'addressW', {
        get: function () {
            return this._addressW;
        },
        set: function (addressW) {
            if (!this.device.webgl2) return;
            if (!this._volume) {
                // #ifdef DEBUG
                console.warn("pc.Texture#addressW: Can't set W addressing mode for a non-3D texture.");
                // #endif
                return;
            }
            if (addressW !== this._addressW) {
                this._addressW = addressW;
                this._parameterFlags |= 16;
            }
        }
    });

    /**
     * @name pc.Texture#compareOnRead
     * @type Boolean
     * @description When enabled, and if texture format is pc.PIXELFORMAT_DEPTH or pc.PIXELFORMAT_DEPTHSTENCIL,
     * hardware PCF is enabled for this texture, and you can get filtered results of comparison using texture() in your shader (WebGL2 only).
     */
    Object.defineProperty(Texture.prototype, 'compareOnRead', {
        get: function () {
            return this._compareOnRead;
        },
        set: function (v) {
            if (this._compareOnRead !== v) {
                this._compareOnRead = v;
                this._parameterFlags |= 32;
            }
        }
    });

    /**
     * @name pc.Texture#compareFunc
     * @type Number
     * @description Comparison function when compareOnRead is enabled (WebGL2 only).
     * Possible values:
     * <ul>
     *     <li>pc.FUNC_LESS</li>
     *     <li>pc.FUNC_LESSEQUAL</li>
     *     <li>pc.FUNC_GREATER</li>
     *     <li>pc.FUNC_GREATEREQUAL</li>
     *     <li>pc.FUNC_EQUAL</li>
     *     <li>pc.FUNC_NOTEQUAL</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'compareFunc', {
        get: function () {
            return this._compareFunc;
        },
        set: function (v) {
            if (this._compareFunc !== v) {
                this._compareFunc = v;
                this._parameterFlags |= 64;
            }
        }
    });

    /**
     * @name pc.Texture#anisotropy
     * @type Number
     * @description Integer value specifying the level of anisotropic to apply to the texture
     * ranging from 1 (no anisotropic filtering) to the {@link pc.GraphicsDevice} property maxAnisotropy.
     */
    Object.defineProperty(Texture.prototype, 'anisotropy', {
        get: function () {
            return this._anisotropy;
        },
        set: function (v) {
            if (this._anisotropy !== v) {
                this._anisotropy = v;
                this._parameterFlags |= 128;
            }
        }
    });

    /**
     * @private
     * @deprecated
     * @name pc.Texture#autoMipmap
     * @type Boolean
     * @description Toggles automatic mipmap generation. Can't be used on non power of two textures.
     */
    Object.defineProperty(Texture.prototype, 'autoMipmap', {
        get: function () {
            return this._mipmaps;
        },
        set: function (v) {
            this._mipmaps = v;
        }
    });

    /**
     * @name pc.Texture#mipmaps
     * @type Boolean
     * @description Defines if texture should generate/upload mipmaps if possible.
     */
    Object.defineProperty(Texture.prototype, 'mipmaps', {
        get: function () {
            return this._mipmaps;
        },
        set: function (v) {
            if (this._mipmaps !== v) {
                this._mipmaps = v;
                this._minFilterDirty = true;

                if (v) this._needsMipmapsUpload = true;
            }
        }
    });

    /**
     * @readonly
     * @name pc.Texture#width
     * @type Number
     * @description The width of the texture in pixels.
     */
    Object.defineProperty(Texture.prototype, 'width', {
        get: function () {
            return this._width;
        }
    });

    /**
     * @readonly
     * @name pc.Texture#height
     * @type Number
     * @description The height of the texture in pixels.
     */
    Object.defineProperty(Texture.prototype, 'height', {
        get: function () {
            return this._height;
        }
    });

    /**
     * @readonly
     * @name pc.Texture#depth
     * @type Number
     * @description The number of depth slices in a 3D texture (WebGL2 only).
     */
    Object.defineProperty(Texture.prototype, 'depth', {
        get: function () {
            return this._depth;
        }
    });

    /**
     * @readonly
     * @name pc.Texture#format
     * @type Number
     * @description The pixel format of the texture. Can be:
     * <ul>
     *     <li>{@link pc.PIXELFORMAT_A8}</li>
     *     <li>{@link pc.PIXELFORMAT_L8}</li>
     *     <li>{@link pc.PIXELFORMAT_L8_A8}</li>
     *     <li>{@link pc.PIXELFORMAT_R5_G6_B5}</li>
     *     <li>{@link pc.PIXELFORMAT_R5_G5_B5_A1}</li>
     *     <li>{@link pc.PIXELFORMAT_R4_G4_B4_A4}</li>
     *     <li>{@link pc.PIXELFORMAT_R8_G8_B8}</li>
     *     <li>{@link pc.PIXELFORMAT_R8_G8_B8_A8}</li>
     *     <li>{@link pc.PIXELFORMAT_DXT1}</li>
     *     <li>{@link pc.PIXELFORMAT_DXT3}</li>
     *     <li>{@link pc.PIXELFORMAT_DXT5}</li>
     *     <li>{@link pc.PIXELFORMAT_RGB16F}</li>
     *     <li>{@link pc.PIXELFORMAT_RGBA16F}</li>
     *     <li>{@link pc.PIXELFORMAT_RGB32F}</li>
     *     <li>{@link pc.PIXELFORMAT_RGBA32F}</li>
     *     <li>{@link pc.PIXELFORMAT_ETC1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_2BPP_RGB_1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_4BPP_RGB_1}</li>
     *     <li>{@link pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1}</li>
     *     <li>{@link pc.PIXELFORMAT_111110F}</li>
     * </ul>
     */
    Object.defineProperty(Texture.prototype, 'format', {
        get: function () {
            return this._format;
        }
    });

    /**
     * @readonly
     * @name pc.Texture#cubemap
     * @type Boolean
     * @description Returns true if this texture is a cube map and false otherwise.
     */
    Object.defineProperty(Texture.prototype, 'cubemap', {
        get: function () {
            return this._cubemap;
        }
    });

    var _pixelFormat2Size = null;

    Object.defineProperty(Texture.prototype, 'gpuSize', {
        get: function () {
            if (!_pixelFormat2Size) {
                _pixelFormat2Size = [];
                _pixelFormat2Size[pc.PIXELFORMAT_A8] = 1;
                _pixelFormat2Size[pc.PIXELFORMAT_L8] = 1;
                _pixelFormat2Size[pc.PIXELFORMAT_L8_A8] = 1;
                _pixelFormat2Size[pc.PIXELFORMAT_R5_G6_B5] = 2;
                _pixelFormat2Size[pc.PIXELFORMAT_R5_G5_B5_A1] = 2;
                _pixelFormat2Size[pc.PIXELFORMAT_R4_G4_B4_A4] = 2;
                _pixelFormat2Size[pc.PIXELFORMAT_R8_G8_B8] = 4;
                _pixelFormat2Size[pc.PIXELFORMAT_R8_G8_B8_A8] = 4;
                _pixelFormat2Size[pc.PIXELFORMAT_RGB16F] = 8;
                _pixelFormat2Size[pc.PIXELFORMAT_RGBA16F] = 8;
                _pixelFormat2Size[pc.PIXELFORMAT_RGB32F] = 16;
                _pixelFormat2Size[pc.PIXELFORMAT_RGBA32F] = 16;
                _pixelFormat2Size[pc.PIXELFORMAT_R32F] = 4;
                _pixelFormat2Size[pc.PIXELFORMAT_DEPTH] = 4; // can be smaller using WebGL1 extension?
                _pixelFormat2Size[pc.PIXELFORMAT_DEPTHSTENCIL] = 4;
                _pixelFormat2Size[pc.PIXELFORMAT_111110F] = 4;
                _pixelFormat2Size[pc.PIXELFORMAT_SRGB] = 4;
                _pixelFormat2Size[pc.PIXELFORMAT_SRGBA] = 4;
            }

            var mips = 1;
            if (this._pot && (this._mipmaps || this._minFilter === pc.FILTER_NEAREST_MIPMAP_NEAREST ||
                this._minFilter === pc.FILTER_NEAREST_MIPMAP_LINEAR || this._minFilter === pc.FILTER_LINEAR_MIPMAP_NEAREST ||
                this._minFilter === pc.FILTER_LINEAR_MIPMAP_LINEAR) && !(this._compressed && this._levels.length === 1)) {

                mips = Math.round(Math.log2(Math.max(this._width, this._height)) + 1);
            }
            var mipWidth = this._width;
            var mipHeight = this._height;
            var mipDepth = this._depth;
            var size = 0;

            for (var i = 0; i < mips; i++) {
                if (!this._compressed) {
                    size += mipWidth * mipHeight * mipDepth * _pixelFormat2Size[this._format];
                } else if (this._format === pc.PIXELFORMAT_ETC1) {
                    size += Math.floor((mipWidth + 3) / 4) * Math.floor((mipHeight + 3) / 4) * 8 * mipDepth;
                } else if (this._format === pc.PIXELFORMAT_PVRTC_2BPP_RGB_1 || this._format === pc.PIXELFORMAT_PVRTC_2BPP_RGBA_1) {
                    size += Math.max(mipWidth, 16) * Math.max(mipHeight, 8) / 4 * mipDepth;
                } else if (this._format === pc.PIXELFORMAT_PVRTC_4BPP_RGB_1 || this._format === pc.PIXELFORMAT_PVRTC_4BPP_RGBA_1) {
                    size += Math.max(mipWidth, 8) * Math.max(mipHeight, 8) / 2 * mipDepth;
                } else {
                    var DXT_BLOCK_WIDTH = 4;
                    var DXT_BLOCK_HEIGHT = 4;
                    var blockSize = this._format === pc.PIXELFORMAT_DXT1 ? 8 : 16;
                    var numBlocksAcross = Math.floor((mipWidth + DXT_BLOCK_WIDTH - 1) / DXT_BLOCK_WIDTH);
                    var numBlocksDown = Math.floor((mipHeight + DXT_BLOCK_HEIGHT - 1) / DXT_BLOCK_HEIGHT);
                    var numBlocks = numBlocksAcross * numBlocksDown;
                    size += numBlocks * blockSize * mipDepth;
                }
                mipWidth = Math.max(mipWidth * 0.5, 1);
                mipHeight = Math.max(mipHeight * 0.5, 1);
                mipDepth = Math.max(mipDepth * 0.5, 1);
            }

            if (this._cubemap) size *= 6;
            return size;
        }
    });

    /**
     * @readonly
     * @name pc.Texture#volume
     * @type Boolean
     * @description Returns true if this texture is a 3D volume and false otherwise.
     */
    Object.defineProperty(Texture.prototype, 'volume', {
        get: function () {
            return this._volume;
        }
    });

    /**
     * @name pc.Texture#flipY
     * @type Boolean
     * @description 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 true.
     */
    Object.defineProperty(Texture.prototype, 'flipY', {
        get: function () {
            return this._flipY;
        },
        set: function (flipY) {
            if (this._flipY !== flipY) {
                this._flipY = flipY;
                this._needsUpload = true;
            }
        }
    });

    Object.defineProperty(Texture.prototype, 'premultiplyAlpha', {
        get: function () {
            return this._premultiplyAlpha;
        },
        set: function (premultiplyAlpha) {
            if (this._premultiplyAlpha !== premultiplyAlpha) {
                this._premultiplyAlpha = premultiplyAlpha;
                this._needsUpload = true;
            }
        }
    });

    // Public methods
    Object.assign(Texture.prototype, {
        /**
         * @function
         * @name pc.Texture#destroy
         * @description Forcibly free up the underlying WebGL resource owned by the texture.
         */
        destroy: function () {
            if (this.device) {
                this.device.destroyTexture(this);
            }
            this.device = null;
            this._levels = null;
        },

        // Force a full resubmission of the texture to WebGL (used on a context restore event)
        dirtyAll: function () {
            this._levelsUpdated = this._cubemap ? [[true, true, true, true, true, true]] : [true];

            this._needsUpload = true;
            this._needsMipmapsUpload = this._mipmaps;
            this._mipmapsUploaded = false;

            this._parameterFlags = 255; // 1 | 2 | 4 | 8 | 16 | 32 | 64 | 128
        },

        /**
         * @function
         * @name pc.Texture#lock
         * @description 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.
         * @returns {ArrayBuffer} A typed array containing the pixel data of the locked mip level.
         */
        lock: function (options) {
            // Initialize options to some sensible defaults
            options = options || { level: 0, face: 0, mode: pc.TEXTURELOCK_WRITE };
            if (options.level === undefined) {
                options.level = 0;
            }
            if (options.face === undefined) {
                options.face = 0;
            }
            if (options.mode === undefined) {
                options.mode = pc.TEXTURELOCK_WRITE;
            }

            this._lockedLevel = options.level;

            if (this._levels[options.level] === null) {
                switch (this._format) {
                    case pc.PIXELFORMAT_A8:
                    case pc.PIXELFORMAT_L8:
                        this._levels[options.level] = new Uint8Array(this._width * this._height * this._depth);
                        break;
                    case pc.PIXELFORMAT_L8_A8:
                        this._levels[options.level] = new Uint8Array(this._width * this._height *  this._depth * 2);
                        break;
                    case pc.PIXELFORMAT_R5_G6_B5:
                    case pc.PIXELFORMAT_R5_G5_B5_A1:
                    case pc.PIXELFORMAT_R4_G4_B4_A4:
                        this._levels[options.level] = new Uint16Array(this._width * this._height * this._depth);
                        break;
                    case pc.PIXELFORMAT_R8_G8_B8:
                        this._levels[options.level] = new Uint8Array(this._width * this._height * this._depth * 3);
                        break;
                    case pc.PIXELFORMAT_R8_G8_B8_A8:
                        this._levels[options.level] = new Uint8Array(this._width * this._height * this._depth * 4);
                        break;
                    case pc.PIXELFORMAT_DXT1:
                        this._levels[options.level] = new Uint8Array(Math.floor((this._width + 3) / 4) * Math.floor((this._height + 3) / 4) * 8 * this._depth);
                        break;
                    case pc.PIXELFORMAT_DXT3:
                    case pc.PIXELFORMAT_DXT5:
                        this._levels[options.level] = new Uint8Array(Math.floor((this._width + 3) / 4) * Math.floor((this._height + 3) / 4) * 16 * this._depth);
                        break;
                    case pc.PIXELFORMAT_RGB16F:
                        this._levels[options.level] = new Uint16Array(this._width * this._height * this._depth * 3);
                        break;
                    case pc.PIXELFORMAT_RGB32F:
                        this._levels[options.level] = new Float32Array(this._width * this._height * this._depth * 3);
                        break;
                    case pc.PIXELFORMAT_RGBA16F:
                        this._levels[options.level] = new Uint16Array(this._width * this._height * this._depth * 4);
                        break;
                    case pc.PIXELFORMAT_RGBA32F:
                        this._levels[options.level] = new Float32Array(this._width * this._height * this._depth * 4);
                        break;
                }
            }

            return this._levels[options.level];
        },

        /**
         * @function
         * @name pc.Texture#setSource
         * @description 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|Array} 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: function (source, mipLevel) {
            var i;
            var invalid = false;
            var width, height;

            mipLevel = mipLevel || 0;

            if (this._cubemap) {
                if (source[0]) {
                    // rely on first face sizes
                    width = source[0].width || 0;
                    height = source[0].height || 0;

                    for (i = 0; i < 6; i++) {
                        // cubemap becomes invalid if any condition is not satisfied
                        if (!source[i] || // face is missing
                            source[i].width !== width || // face is different width
                            source[i].height !== height || // face is different height
                            (!(source[i] instanceof HTMLImageElement) && // not image and
                            !(source[i] instanceof HTMLCanvasElement) && // not canvas and
                            !(source[i] instanceof HTMLVideoElement))) { // not video

                            invalid = true;
                            break;
                        }
                    }
                } else {
                    // first face is missing
                    invalid = true;
                }

                if (!invalid) {
                    // mark levels as updated
                    for (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 (!(source instanceof HTMLImageElement) && !(source instanceof HTMLCanvasElement) && !(source instanceof HTMLVideoElement))
                    invalid = true;

                if (!invalid) {
                    // mark level as updated
                    if (source !== this._levels[mipLevel])
                        this._levelsUpdated[mipLevel] = true;

                    width = source.width;
                    height = source.height;
                }
            }

            if (invalid) {
                // invalid texture

                // default sizes
                this._width = 4;
                this._height = 4;
                this._pot = true;

                // remove levels
                if (this._cubemap) {
                    for (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._pot = pc.math.powerOfTwo(this._width) && pc.math.powerOfTwo(this._height);

                this._levels[mipLevel] = source;
            }

            // valid or changed state of validity
            if (this._invalid !== invalid || !invalid) {
                this._invalid = invalid;

                // reupload
                this.upload();
            }
        },

        /**
         * @function
         * @name pc.Texture#getSource
         * @description 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: function (mipLevel) {
            mipLevel = mipLevel || 0;
            return this._levels[mipLevel];
        },

        /**
         * @function
         * @name pc.Texture#unlock
         * @description Unlocks the currently locked mip level and uploads it to VRAM.
         */
        unlock: function () {
            // #ifdef DEBUG
            if (this._lockedLevel === -1) {
                console.log("pc.Texture#unlock: Attempting to unlock a texture that is not locked.");
            }
            // #endif

            // Upload the new pixel data
            this.upload();
            this._lockedLevel = -1;
        },

        /**
         * @function
         * @name pc.Texture#upload
         * @description Forces a reupload of the textures pixel data to graphics memory. Ordinarily, this function
         * is called by internally by {@link pc.Texture#setSource} and {@link pc.Texture#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: function () {
            this._needsUpload = true;
            this._needsMipmapsUpload = this._mipmaps;
        },

        getDds: function () {
            if (this.format !== pc.PIXELFORMAT_R8_G8_B8_A8)
                console.error("This format is not implemented yet");

            var fsize = 128;
            var i = 0;
            var j;
            var face;
            while (this._levels[i]) {
                var mipSize;
                if (!this.cubemap) {
                    mipSize = this._levels[i].length;
                    if (!mipSize) {
                        console.error("No byte array for mip " + i);
                        return;
                    }
                    fsize += mipSize;
                } else {
                    for (face = 0; face < 6; face++) {
                        if (!this._levels[i][face]) {
                            console.error('No level data for mip ' + i + ', face ' + face);
                            return;
                        }
                        mipSize = this._levels[i][face].length;
                        if (!mipSize) {
                            console.error("No byte array for mip " + i + ", face " + face);
                            return;
                        }
                        fsize += mipSize;
                    }
                }
                fsize += this._levels[i].length;
                i++;
            }

            var buff = new ArrayBuffer(fsize);
            var header = new Uint32Array(buff, 0, 128 / 4);

            var DDS_MAGIC = 542327876; // "DDS"
            var DDS_HEADER_SIZE = 124;
            var DDS_FLAGS_REQUIRED = 0x01 | 0x02 | 0x04 | 0x1000 | 0x80000; // caps | height | width | pixelformat | linearsize
            var DDS_FLAGS_MIPMAP = 0x20000;
            var DDS_PIXELFORMAT_SIZE = 32;
            var DDS_PIXELFLAGS_RGBA8 = 0x01 | 0x40; // alpha | rgb
            var DDS_CAPS_REQUIRED = 0x1000;
            var DDS_CAPS_MIPMAP = 0x400000;
            var DDS_CAPS_COMPLEX = 0x8;
            var DDS_CAPS2_CUBEMAP = 0x200 | 0x400 | 0x800 | 0x1000 | 0x2000 | 0x4000 | 0x8000; // cubemap | all faces

            var flags = DDS_FLAGS_REQUIRED;
            if (this._levels.length > 1) flags |= DDS_FLAGS_MIPMAP;

            var caps = DDS_CAPS_REQUIRED;
            if (this._levels.length > 1) caps |= DDS_CAPS_MIPMAP;
            if (this._levels.length > 1 || this.cubemap) caps |= DDS_CAPS_COMPLEX;

            var caps2 = this.cubemap ? DDS_CAPS2_CUBEMAP : 0;

            header[0] = DDS_MAGIC;
            header[1] = DDS_HEADER_SIZE;
            header[2] = flags;
            header[3] = this.height;
            header[4] = this.width;
            header[5] = this.width * this.height * 4;
            header[6] = 0; // depth
            header[7] = this._levels.length;
            for (i = 0; i < 11; i++) header[8 + i] = 0;
            header[19] = DDS_PIXELFORMAT_SIZE;
            header[20] = DDS_PIXELFLAGS_RGBA8;
            header[21] = 0; // fourcc
            header[22] = 32; // bpp
            header[23] = 0x00FF0000; // R mask
            header[24] = 0x0000FF00; // G mask
            header[25] = 0x000000FF; // B mask
            header[26] = 0xFF000000; // A mask
            header[27] = caps;
            header[28] = caps2;
            header[29] = 0;
            header[30] = 0;
            header[31] = 0;

            var offset = 128;
            var level, mip;
            if (!this.cubemap) {
                for (i = 0; i < this._levels.length; i++) {
                    level = this._levels[i];
                    mip = new Uint8Array(buff, offset, level.length);
                    for (j = 0; j < level.length; j++) mip[j] = level[j];
                    offset += level.length;
                }
            } else {
                for (face = 0; face < 6; face++) {
                    for (i = 0; i < this._levels.length; i++) {
                        level = this._levels[i][face];
                        mip = new Uint8Array(buff, offset, level.length);
                        for (j = 0; j < level.length; j++) mip[j] = level[j];
                        offset += level.length;
                    }
                }
            }

            return buff;
        }
    });

    return {
        Texture: Texture
    };
}());