Source: render/Texture.js

/*
 * Copyright 2003-2006, 2009, 2017, United States Government, as represented by the Administrator of the
 * National Aeronautics and Space Administration. All rights reserved.
 *
 * The NASAWorldWind/WebWorldWind platform is licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/**
 * @exports Texture
 */
define([
        '../error/ArgumentError',
        '../util/Logger',
        '../util/WWMath'
    ],
    function (ArgumentError,
              Logger,
              WWMath) {
        "use strict";

        /**
         * Constructs a texture for a specified image.
         * @alias Texture
         * @constructor
         * @classdesc Represents a WebGL texture. Applications typically do not interact with this class.
         * @param {WebGLRenderingContext} gl The current WebGL rendering context.
         * @param {Image} image The texture's image.
         * @param {GLenum} wrapMode Optional. Specifies the wrap mode of the texture. Defaults to gl.CLAMP_TO_EDGE
         * @throws {ArgumentError} If the specified WebGL context or image is null or undefined.
         */
        var Texture = function (gl, image, wrapMode) {

            if (!gl) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
                    "missingGlContext"));
            }

            if (!image) {
                throw new ArgumentError(Logger.logMessage(Logger.LEVEL_SEVERE, "Texture", "constructor",
                    "missingImage"));
            }

            if (!wrapMode) {
                wrapMode = gl.CLAMP_TO_EDGE;
            }

            var textureId = gl.createTexture(),
                isPowerOfTwo = (WWMath.isPowerOfTwo(image.width) && WWMath.isPowerOfTwo(image.height));

            this.originalImageWidth = image.width;
            this.originalImageHeight = image.height;

            if (wrapMode === gl.REPEAT && !isPowerOfTwo) {
                image = this.resizeImage(image);
                isPowerOfTwo = true;
            }

            this.imageWidth = image.width;
            this.imageHeight = image.height;
            this.size = image.width * image.height * 4;

            gl.bindTexture(gl.TEXTURE_2D, textureId);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER,
                isPowerOfTwo ? gl.LINEAR_MIPMAP_LINEAR : gl.LINEAR);

            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, wrapMode);
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, wrapMode);

            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 1);
            gl.texImage2D(gl.TEXTURE_2D, 0,
                gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
            gl.pixelStorei(gl.UNPACK_PREMULTIPLY_ALPHA_WEBGL, 0);

            if (isPowerOfTwo) {
                gl.generateMipmap(gl.TEXTURE_2D);
            }

            this.textureId = textureId;

            /**
             * The time at which this texture was created.
             * @type {Date}
             */
            this.creationTime = new Date();

            // Internal use only. Intentionally not documented.
            this.texParameters = {};

            // Internal use only. Intentionally not documented.
            // https://www.khronos.org/registry/webgl/extensions/EXT_texture_filter_anisotrop
            this.anisotropicFilterExt = (gl.getExtension("EXT_texture_filter_anisotropic") ||
                                         gl.getExtension("WEBKIT_EXT_texture_filter_anisotropic"));
        };

        /**
         * Sets a texture parameter to apply when binding this texture.
         *
         * Currently only gl.TEXTURE_MAG_FILTER has an effect.
         *
         * @param {Glenum} name The name of the parameter
         * @param {GLint} value The value for this parameter
         */
        Texture.prototype.setTexParameter = function (name, value) {
            this.texParameters[name] = value;
        };

        /**
         * Returns the value of a texture parameter to be assigned to this texture.
         * @param {Glenum} name The name of the parameter
         * @returns {GLint} The value for this parameter
         */
        Texture.prototype.getTexParameter = function (name) {
            return this.texParameters[name];
        };

        /**
         * Clears the list of texture parameters to apply when binding this texture.
         */
        Texture.prototype.clearTexParameters = function () {
            this.texParameters = {};
        };

        /**
         * Disposes of the WebGL texture object associated with this texture.
         * @param gl
         */
        Texture.prototype.dispose = function (gl) {
            gl.deleteTexture(this.textureId);
            delete this.textureId;
        };

        /**
         * Binds this texture in the current WebGL graphics context.
         * @param {DrawContext} dc The current draw context.
         */
        Texture.prototype.bind = function (dc) {
            var gl = dc.currentGlContext;

            gl.bindTexture(gl.TEXTURE_2D, this.textureId);

            this.applyTexParameters(dc);

            dc.frameStatistics.incrementTextureLoadCount(1);
            return true;
        };

        /**
         * Applies the configured texture parameters to the OpenGL context.
         * @param {DrawContext} dc The current draw context.
         */
        Texture.prototype.applyTexParameters = function (dc) {
            var gl = dc.currentGlContext;

            // Configure the OpenGL texture magnification function. Use linear by default.
            var textureMagFilter = this.texParameters[gl.TEXTURE_MAG_FILTER] || gl.LINEAR;
            gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, textureMagFilter);

            // Try to enable the anisotropic texture filtering only if we have a linear magnification filter.
            // This can't be enabled all the time because Windows seems to ignore the TEXTURE_MAG_FILTER parameter when
            // this extension is enabled.
            if (textureMagFilter === gl.LINEAR) {
                // Setup 4x anisotropic texture filtering when this feature is available.
                if (this.anisotropicFilterExt) {
                    gl.texParameteri(gl.TEXTURE_2D, this.anisotropicFilterExt.TEXTURE_MAX_ANISOTROPY_EXT, 4);
                }
            }
        };

        /**
         * Resizes an image to a power of two.
         * @param {Image} image The image to resize.
         */
        Texture.prototype.resizeImage = function (image) {
            var canvas = document.createElement("canvas");
            canvas.width = WWMath.powerOfTwoFloor(image.width);
            canvas.height = WWMath.powerOfTwoFloor(image.height);
            var ctx = canvas.getContext("2d");
            ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
            return canvas;
        };


        return Texture;
    });