Source: og/webgl/Handler.js

/**
 * @module og/webgl/Handler
 */

"use strict";

import { cons } from "../cons.js";
import { Clock } from "../Clock.js";
import { ImageCanvas } from "../ImageCanvas.js";
import { isEmpty } from "../utils/shared.js";
import { ProgramController } from "./ProgramController.js";
import { Stack } from "../Stack.js";
import { Vec2 } from "../math/Vec2.js";

const vendorPrefixes = ["", "WEBKIT_", "MOZ_"];

// Maximal mipmap levels
const MAX_LEVELS = 2;

/**
 * A WebGL handler for accessing low-level WebGL capabilities.
 * @class
 * @param {string} id - Canvas element id that WebGL handler assing with. If it's null
 * or undefined creates hidden canvas and handler bacomes hidden.
 * @param {Object} [params] - Handler options:
 * @param {number} [params.anisotropy] - Anisotropy filter degree. 8 is default.
 * @param {number} [params.width] - Hidden handler width. 256 is default.
 * @param {number} [params.height] - Hidden handler height. 256 is default.
 * @param {Array.<string>} [params.extensions] - Additional WebGL extension list. Available by default: EXT_texture_filter_anisotropic.
 */
class Handler {
    constructor(id, params) {
        params = params || {};

        /**
         * Application default timer.
         * @public
         * @type {Clock}
         */
        this.defaultClock = new Clock();

        /**
         * Custom timers.
         * @protected
         * @type{Clock[]}
         */
        this._clocks = [];

        /**
         * Draw frame time in milliseconds.
         * @public
         * @readonly
         * @type {number}
         */
        this.deltaTime = 0;

        /**
         * WebGL rendering canvas element.
         * @public
         * @type {Object}
         */
        this.canvas = null;

        /**
         * WebGL context.
         * @public
         * @type {Object}
         */
        this.gl = null;

        /**
         * Shader program controller list.
         * @public
         * @type {Object.<og.webgl.ProgramController>}
         */
        this.programs = {};

        /**
         * Current active shader program controller.
         * @public
         * @type {ProgramController}
         */
        this.activeProgram = null;

        /**
         * Handler parameters.
         * @private
         * @type {Object}
         */
        this._params = params || {};
        this._params.anisotropy = this._params.anisotropy || 4;
        this._params.width = this._params.width || 256;
        this._params.height = this._params.height || 256;
        this._params.pixelRatio = this._params.pixelRatio || 1.0;
        this._params.context = this._params.context || {};
        this._params.extensions = this._params.extensions || [];
        this._oneByHeight = 1.0 / (this._params.height * this._params.pixelRatio);

        /**
         * Current WebGL extensions. Becomes here after context initialization.
         * @public
         * @type {Object}
         */
        this.extensions = {};

        /**
         * HTML Canvas object id.
         * @private
         * @type {Object}
         */
        this._id = id;

        this._lastAnimationFrameTime = 0;

        this._initialized = false;

        /**
         * Animation frame function assigned from outside(Ex. from Renderer).
         * @private
         * @type {frameCallback}
         */
        this._frameCallback = function () { };

        this.transparentTexture = null;

        this.framebufferStack = new Stack();

        this.createTexture = {
            "NEAREST": null,
            "LINEAR": null,
            "MIPMAP": null,
            "ANISOTROPIC": null
        };

        this.createTextureDefault = null;

        if (params.autoActivate || isEmpty(params.autoActivate)) {
            this.initialize();
        }
    }

    /**
     * The return value is null if the extension is not supported, or an extension object otherwise.
     * @param {Object} gl - WebGl context pointer.
     * @param {String} name - Extension name.
     * @returns {Object} -
     */
    static getExtension(gl, name) {
        let i, ext;
        for (i in vendorPrefixes) {
            ext = gl.getExtension(vendorPrefixes[i] + name);
            if (ext) {
                return ext;
            }
        }
        return null;
    }

    /**
     * Returns a drawing context on the canvas, or null if the context identifier is not supported.
     * @param {Object} canvas - HTML canvas object.
     * @param {Object} [contextAttributes] - See canvas.getContext contextAttributes.
     * @returns {Object} -
     */
    static getContext(canvas, contextAttributes) {
        const CONTEXT_TYPE = ["webgl2", "webgl"];
        let ctx = null;

        try {
            for (let i = 0; i < CONTEXT_TYPE.length; i++) {
                ctx = canvas.getContext(CONTEXT_TYPE[i], contextAttributes);
                if (ctx) {
                    console.log(CONTEXT_TYPE[i]);
                    ctx.type = CONTEXT_TYPE[i];
                    break;
                }
            }
        } catch (ex) {
            cons.logErr("exception during the GL context initialization");
        }

        if (!ctx) {
            cons.logErr("could not initialise WebGL");
        }

        return ctx;
    }

    /**
     * Sets animation frame function.
     * @public
     * @param {callback} callback - Frame callback.
     */
    setFrameCallback(callback) {
        callback && (this._frameCallback = callback);
    }

    /**
     * Creates empty texture.
     * @public
     * @param {Number} [width=1] - Specifies the width of the texture image..
     * @param {Number} [height=1] - Specifies the width of the texture image..
     * @param {String} [filter="NEAREST"] - Specifies GL_TEXTURE_MIN(MAX)_FILTER texture value.
     * @param {String} [internalFormat="RGBA"] - Specifies the color components in the texture.
     * @param {String} [format="RGBA"] - Specifies the format of the texel data.
     * @param {String} [type="UNSIGNED_BYTE"] - Specifies the data type of the texel data.
     * @param {Number} [levels=0] - Specifies the level-of-detail number. Level 0 is the base image level. Level n is the nth mipmap reduction image.
     * @returns {Object} - WebGL texture object.
     */
    createEmptyTexture2DExt(
        width = 1,
        height = 1,
        filter = "NEAREST",
        internalFormat = "RGBA",
        format = "RGBA",
        type = "UNSIGNED_BYTE",
        level = 0
    ) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texImage2D(
            gl.TEXTURE_2D,
            level,
            gl[internalFormat.toUpperCase()],
            width,
            height,
            0,
            gl[format.toUpperCase()],
            gl[type.toUpperCase()],
            null
        );

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl[filter.toUpperCase()]);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl[filter.toUpperCase()]);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates Empty NEAREST filtered texture.
     * @public
     * @param {number} width - Empty texture width.
     * @param {number} height - Empty texture height.
     * @returns {Object} - WebGL texture object.
     */
    createEmptyTexture_n(width, height, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat || gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates empty LINEAR filtered texture.
     * @public
     * @param {number} width - Empty texture width.
     * @param {number} height - Empty texture height.
     * @returns {Object} - WebGL texture object.
     */
    createEmptyTexture_l(width, height, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat || gl.RGBA, width, height, 0, gl.RGBA, gl.UNSIGNED_BYTE, null);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates NEAREST filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_n_webgl1(image, internalFormat) {
        var gl = this.gl;
        var texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat || gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates LINEAR filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_l_webgl1(image, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);

        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat || gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates MIPMAP filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_mm_webgl1(image, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat || gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

        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_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates ANISOTROPY filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_a_webgl1(image, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texImage2D(gl.TEXTURE_2D, 0, internalFormat || gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);

        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        gl.texParameterf(gl.TEXTURE_2D, this.extensions.EXT_texture_filter_anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, this._params.anisotropy);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates NEAREST filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_n_webgl2(image, internalFormat) {
        var gl = this.gl;
        var texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texStorage2D(gl.TEXTURE_2D, 1, internalFormat || gl.RGBA8, image.width, image.height);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, gl.RGBA, gl.UNSIGNED_BYTE, image);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates LINEAR filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_l_webgl2(image, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);

        gl.texStorage2D(gl.TEXTURE_2D, 1, internalFormat || gl.RGBA8, image.width, image.height);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, gl.RGBA, gl.UNSIGNED_BYTE, image);

        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates MIPMAP filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_mm_webgl2(image, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texStorage2D(gl.TEXTURE_2D, MAX_LEVELS, internalFormat || gl.RGBA8, image.width, image.height);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, gl.RGBA, gl.UNSIGNED_BYTE, image);

        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_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates ANISOTROPY filter texture.
     * @public
     * @param {Object} image - Image or Canvas object.
     * @returns {Object} - WebGL texture object.
     */
    createTexture_a_webgl2(image, internalFormat) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_2D, texture);
        gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);

        gl.texStorage2D(gl.TEXTURE_2D, MAX_LEVELS, internalFormat || gl.RGBA8, image.width, image.height);
        gl.texSubImage2D(gl.TEXTURE_2D, 0, 0, 0, image.width, image.height, gl.RGBA, gl.UNSIGNED_BYTE, image);

        gl.generateMipmap(gl.TEXTURE_2D);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_LINEAR);
        gl.texParameterf(gl.TEXTURE_2D, this.extensions.EXT_texture_filter_anisotropic.TEXTURE_MAX_ANISOTROPY_EXT, this._params.anisotropy);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.bindTexture(gl.TEXTURE_2D, null);
        return texture;
    }

    /**
     * Creates cube texture.
     * @public
     * @param {Object.<string>} params - Face image urls:
     * @param {string} params.px - Positive X or right image url.
     * @param {string} params.nx - Negative X or left image url.
     * @param {string} params.py - Positive Y or up image url.
     * @param {string} params.ny - Negative Y or bottom image url.
     * @param {string} params.pz - Positive Z or face image url.
     * @param {string} params.nz - Negative Z or back image url.
     * @returns {Object} - WebGL texture object.
     */
    loadCubeMapTexture(params) {
        let gl = this.gl;
        let texture = gl.createTexture();
        gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        gl.texParameteri(gl.TEXTURE_CUBE_MAP, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

        let faces = [
            [params.px, gl.TEXTURE_CUBE_MAP_POSITIVE_X],
            [params.nx, gl.TEXTURE_CUBE_MAP_NEGATIVE_X],
            [params.py, gl.TEXTURE_CUBE_MAP_POSITIVE_Y],
            [params.ny, gl.TEXTURE_CUBE_MAP_NEGATIVE_Y],
            [params.pz, gl.TEXTURE_CUBE_MAP_POSITIVE_Z],
            [params.nz, gl.TEXTURE_CUBE_MAP_NEGATIVE_Z]
        ];

        let imageCanvas = new ImageCanvas();
        imageCanvas.fillEmpty();
        let emptyImage = imageCanvas.getImage();

        for (let i = 0; i < faces.length; i++) {
            let face = faces[i][1];
            gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
            gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
            gl.texImage2D(face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, emptyImage);
        }

        for (let i = 0; i < faces.length; i++) {
            let face = faces[i][1];
            let image = new Image();
            image.crossOrigin = "";
            image.onload = (function (texture, face, image) {
                return function () {
                    gl.bindTexture(gl.TEXTURE_CUBE_MAP, texture);
                    gl.pixelStorei(gl.UNPACK_FLIP_Y_WEBGL, false);
                    gl.texImage2D(face, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
                };
            })(texture, face, image);
            image.src = faces[i][0];
        }
        return texture;
    }

    /**
     * Adds shader program to the handler.
     * @public
     * @param {Program} program - Shader program.
     * @param {boolean} [notActivate] - If it's true program will not compile.
     * @return {Program} -
     */
    addProgram(program, notActivate) {
        if (!this.programs[program.name]) {
            let sc = new ProgramController(this, program);
            this.programs[program.name] = sc;
            this._initProgramController(sc);
            if (notActivate) {
                sc._activated = false;
            }
        } else {
            console.log(
                "og.webgl.Handler:284 - shader program: '" + program.name + "' is allready exists."
            );
        }
        return program;
    }

    /**
     * Removes shader program from handler.
     * @public
     * @param {String} name - Shader program name.
     */
    removeProgram(name) {
        this.programs[name] && this.programs[name].remove();
    }

    /**
     * Adds shader programs to the handler.
     * @public
     * @param {Array.<Program>} programsArr - Shader program array.
     */
    addPrograms(programsArr) {
        for (let i = 0; i < programsArr.length; i++) {
            this.addProgram(programsArr[i]);
        }
    }

    /**
     * Used in addProgram
     * @private
     * @param {ProgramController} sc - Program controller
     */
    _initProgramController(sc) {
        if (this._initialized) {
            sc.initialize();
            if (!this.activeProgram) {
                this.activeProgram = sc;
                sc.activate();
            } else {
                sc.deactivate();
                this.activeProgram._program.enableAttribArrays();
                this.activeProgram._program.use();
            }
        }
    }

    /**
     * Used in init function.
     * @private
     */
    _initPrograms() {
        for (let p in this.programs) {
            this._initProgramController(this.programs[p]);
        }
    }

    /**
     * Initialize additional WebGL extensions.
     * @public
     * @param {string} extensionStr - Extension name.
     * @param {boolean} showLog - Show logging.
     * @return {Object} -
     */
    initializeExtension(extensionStr, showLog) {
        if (!(this.extensions && this.extensions[extensionStr])) {
            let ext = Handler.getExtension(this.gl, extensionStr);
            if (ext) {
                this.extensions[extensionStr] = ext;
            } else if (showLog) {
                console.log(
                    "og.webgl.Handler: extension '" + extensionStr + "' doesn't initialize."
                );
            }
        }
        return this.extensions && this.extensions[extensionStr];
    }

    /**
     * Main function that initialize handler.
     * @public
     */
    initialize() {
        if (this._id) {
            this.canvas = document.getElementById(this._id);
        } else {
            this.canvas = document.createElement("canvas");
            this.canvas.width = this._params.width;
            this.canvas.height = this._params.height;
        }

        this.gl = Handler.getContext(this.canvas, this._params.context);
        this._initialized = true;

        /** Sets deafult extensions */
        this._params.extensions.push("EXT_texture_filter_anisotropic");

        if (this.gl.type === "webgl") {
            this._params.extensions.push("OES_standard_derivatives");
            this._params.extensions.push("OES_element_index_uint");
            this._params.extensions.push("WEBGL_depth_texture");
            //this._params.extensions.push("EXT_frag_depth");
        } else {
            this._params.extensions.push("EXT_color_buffer_float");
            this._params.extensions.push("OES_texture_float_linear");
        }

        let i = this._params.extensions.length;
        while (i--) {
            this.initializeExtension(this._params.extensions[i], true);
        }

        if (this.gl.type === "webgl") {
            this.createTexture_n = this.createTexture_n_webgl1.bind(this);
            this.createTexture_l = this.createTexture_l_webgl1.bind(this);
            this.createTexture_mm = this.createTexture_mm_webgl1.bind(this);
            this.createTexture_a = this.createTexture_a_webgl1.bind(this);
        } else {
            this.createTexture_n = this.createTexture_n_webgl2.bind(this);
            this.createTexture_l = this.createTexture_l_webgl2.bind(this);
            this.createTexture_mm = this.createTexture_mm_webgl2.bind(this);
            this.createTexture_a = this.createTexture_a_webgl2.bind(this);
        }

        this.createTexture["NEAREST"] = this.createTexture_n;
        this.createTexture["LINEAR"] = this.createTexture_l;
        this.createTexture["MIPMAP"] = this.createTexture_mm;
        this.createTexture["ANISOTROPIC"] = this.createTexture_a;

        if (!this.extensions.EXT_texture_filter_anisotropic) {
            this.createTextureDefault = this.createTexture_mm;
        } else {
            this.createTextureDefault = this.createTexture_a;
        }

        /** Initilalize shaders and rendering parameters*/
        this._initPrograms();
        this._setDefaults();
    }

    /**
     * Sets default gl render parameters. Used in init function.
     * @private
     */
    _setDefaults() {
        this.activateDepthTest();
        this.setSize(
            this.canvas.clientWidth || this._params.width,
            this.canvas.clientHeight || this._params.height
        );
        this.gl.frontFace(this.gl.CCW);
        this.gl.cullFace(this.gl.BACK);
        this.activateFaceCulling();
        this.deactivateBlending();
        let that = this;
        this.createDefaultTexture({ color: "rgba(0,0,0,0.0)" }, function (t) {
            that.transparentTexture = t;
        });
    }

    /**
     * Activate depth test.
     * @public
     */
    activateDepthTest() {
        this.gl.enable(this.gl.DEPTH_TEST);
    }

    /**
     * Deactivate depth test.
     * @public
     */
    deactivateDepthTest() {
        this.gl.disable(this.gl.DEPTH_TEST);
    }

    /**
     * Activate face culling.
     * @public
     */
    activateFaceCulling() {
        this.gl.enable(this.gl.CULL_FACE);
    }

    /**
     * Deactivate face cullting.
     * @public
     */
    deactivateFaceCulling() {
        this.gl.disable(this.gl.CULL_FACE);
    }

    /**
     * Activate blending.
     * @public
     */
    activateBlending() {
        this.gl.enable(this.gl.BLEND);
    }

    /**
     * Deactivate blending.
     * @public
     */
    deactivateBlending() {
        this.gl.disable(this.gl.BLEND);
    }

    /**
     * Creates STREAM_DRAW ARRAY buffer.
     * @public
     * @param {Array.<number>} array - Input array.
     * @param {number} itemSize - Array item size.
     * @param {number} numItems - Items quantity.
     * @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
     * @return {Object} -
     */
    createStreamArrayBuffer(itemSize, numItems, usage, bites = 4) {
        let buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
        this.gl.bufferData(
            this.gl.ARRAY_BUFFER,
            numItems * itemSize * bites,
            usage || this.gl.STREAM_DRAW
        );
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
        buffer.itemSize = itemSize;
        buffer.numItems = numItems;
        return buffer;
    }

    /**
     * Load stream ARRAY buffer.
     * @public
     * @param {Array.<number>} array - Input array.
     * @param {number} itemSize - Array item size.
     * @param {number} numItems - Items quantity.
     * @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
     * @return {Object} -
     */
    setStreamArrayBuffer(buffer, array, offset = 0) {
        let gl = this.gl;
        gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
        gl.bufferSubData(this.gl.ARRAY_BUFFER, offset, array);
        gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
        return buffer;
    }

    /**
     * Creates ARRAY buffer.
     * @public
     * @param {Array.<number>} array - Input array.
     * @param {number} itemSize - Array item size.
     * @param {number} numItems - Items quantity.
     * @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
     * @return {Object} -
     */
    createArrayBuffer(array, itemSize, numItems, usage) {
        let buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, array, usage || this.gl.STATIC_DRAW);
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
        buffer.itemSize = itemSize;
        buffer.numItems = numItems;
        return buffer;
    }

    /**
     * Creates ARRAY buffer specific length.
     * @public
     * @param {Array.<number>} array - Input array.
     * @param {number} itemSize - Array item size.
     * @param {number} numItems - Items quantity.
     * @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
     * @return {Object} -
     */
    createArrayBufferLength(size, usage) {
        let buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, buffer);
        this.gl.bufferData(this.gl.ARRAY_BUFFER, size, usage || this.gl.STATIC_DRAW);
        this.gl.bindBuffer(this.gl.ARRAY_BUFFER, null);
        buffer.itemSize = 1;
        buffer.numItems = size;
        return buffer;
    }

    /**
     * Creates ELEMENT ARRAY buffer.
     * @public
     * @param {Array.<number>} array - Input array.
     * @param {number} itemSize - Array item size.
     * @param {number} numItems - Items quantity.
     * @param {number} [usage=STATIC_DRAW] - Parameter of the bufferData call can be one of STATIC_DRAW, DYNAMIC_DRAW, or STREAM_DRAW.
     * @return {Object} -
     */
    createElementArrayBuffer(array, itemSize, numItems, usage) {
        let buffer = this.gl.createBuffer();
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, buffer);
        this.gl.bufferData(this.gl.ELEMENT_ARRAY_BUFFER, array, usage || this.gl.STATIC_DRAW);
        this.gl.bindBuffer(this.gl.ELEMENT_ARRAY_BUFFER, null);
        buffer.itemSize = itemSize;
        buffer.numItems = numItems || array.length;
        return buffer;
    }

    /**
     * Sets handler canvas size.
     * @public
     * @param {number} w - Canvas width.
     * @param {number} h - Canvas height.
     */
    setSize(w, h) {
        this._params.width = w;
        this._params.height = h;
        this.canvas.width = w * this._params.pixelRatio;
        this.canvas.height = h * this._params.pixelRatio;

        this._oneByHeight = 1.0 / this.canvas.height;

        this.gl && this.gl.viewport(0, 0, w, h);
        this.onCanvasResize && this.onCanvasResize(this.canvas);
    }

    get pixelRatio() {
        return this._params.pixelRatio;
    }

    set pixelRatio(pr) {
        this._params.pixelRatio = pr;
        this.setSize(this._params.width, this._params.height);
    }

    /**
     * Returns context screen width.
     * @public
     * @returns {number} -
     */
    getWidth() {
        return this.canvas.width;
    }

    /**
     * Returns context screen height.
     * @public
     * @returns {number} -
     */
    getHeight() {
        return this.canvas.height;
    }

    /**
     * Returns canvas aspect ratio.
     * @public
     * @returns {number} -
     */
    getClientAspect() {
        return this.canvas.clientWidth / this.canvas.clientHeight;
    }

    /**
     * Returns screen center coordinates.
     * @public
     * @returns {number} -
     */
    getCenter() {
        let c = this.canvas;
        return new Vec2(Math.round(c.width * 0.5), Math.round(c.height * 0.5));
    }

    /**
     * Draw single frame.
     * @public
     * @param {number} now - Frame current time milliseconds.
     */
    drawFrame() {
        /** Calculating frame time */
        let now = window.performance.now();
        this.deltaTime = now - this._lastAnimationFrameTime;
        this._lastAnimationFrameTime = now;

        this.defaultClock._tick(this.deltaTime);

        for (let i = 0; i < this._clocks.length; i++) {
            this._clocks[i]._tick(this.deltaTime);
        }

        /** Canvas resize checking */
        let canvas = this.canvas;

        if (Math.floor(canvas.clientWidth * this._params.pixelRatio) !== canvas.width || Math.floor(canvas.clientHeight * this._params.pixelRatio) !== canvas.height) {
            this.setSize(canvas.clientWidth, canvas.clientHeight);
        }

        /** Draw frame */
        this._frameCallback();
    }

    /**
     * Clearing gl frame.
     * @public
     */
    clearFrame() {
        let gl = this.gl;
        this.gl.clearColor(0.0, 0.0, 0.0, 1.0);
        gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT);
    }

    /**
     * Starts animation loop.
     * @public
     */
    start() {
        if (!this._requestAnimationFrameId && this._initialized) {
            this._lastAnimationFrameTime = window.performance.now();
            this.defaultClock.setDate(new Date());
            this._animationFrameCallback();
        }
    }

    stop() {
        if (this._requestAnimationFrameId) {
            window.cancelAnimationFrame(this._requestAnimationFrameId);
            this._requestAnimationFrameId = null;
        }
    }

    /**
     * Make animation.
     * @private
     */
    _animationFrameCallback() {
        this._requestAnimationFrameId = window.requestAnimationFrame(() => {
            this.drawFrame();
            this._animationFrameCallback();
        });
    }

    /**
     * Creates default texture object
     * @public
     * @param {Object} [params] - Texture parameters:
     * @param {callback} [success] - Creation callback
     */
    createDefaultTexture(params, success) {
        let imgCnv;
        let texture;
        if (params && params.color) {
            imgCnv = new ImageCanvas(2, 2);
            imgCnv.fillColor(params.color);
            texture = this.createTexture_n_webgl2(imgCnv._canvas);
            texture.default = true;
            success(texture);
        } else if (params && params.url) {
            let img = new Image();
            let that = this;
            img.onload = function () {
                texture = that.createTexture(this);
                texture.default = true;
                success(texture);
            };
            img.src = params.url;
        } else {
            imgCnv = new ImageCanvas(2, 2);
            imgCnv.fillColor("#C5C5C5");
            texture = this.createTexture_n_webgl2(imgCnv._canvas);
            texture.default = true;
            success(texture);
        }
    }

    /**
     * @public
     */
    destroy() {
        let gl = this.gl;

        this.stop();

        for (let p in this.programs) {
            this.removeProgram(p);
        }

        gl.deleteTexture(this.transparentTexture);
        this.transparentTexture = null;

        this.framebufferStack = null;
        this.framebufferStack = new Stack();

        if (this.canvas.parentNode) {
            this.canvas.parentNode.removeChild(this.canvas);
        }
        this.canvas.width = 1;
        this.canvas.height = 1;
        this.canvas = null;

        let numAttribs = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
        let tmp = gl.createBuffer();
        gl.bindBuffer(gl.ARRAY_BUFFER, tmp);
        for (let ii = 0; ii < numAttribs; ++ii) {
            gl.disableVertexAttribArray(ii);
            gl.vertexAttribPointer(ii, 4, gl.FLOAT, false, 0, 0);
            gl.vertexAttrib1f(ii, 0);
        }
        gl.deleteBuffer(tmp);

        let numTextureUnits = gl.getParameter(gl.MAX_TEXTlURE_IMAGE_UNITS);
        for (let ii = 0; ii < numTextureUnits; ++ii) {
            gl.activeTexture(gl.TEXTURE0 + ii);
            gl.bindTexture(gl.TEXTURE_CUBE_MAP, null);
            gl.bindTexture(gl.TEXTURE_2D, null);
        }

        gl.activeTexture(gl.TEXTURE0);
        gl.useProgram(null);
        gl.bindBuffer(gl.ARRAY_BUFFER, null);
        gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
        gl.bindFramebuffer(gl.FRAMEBUFFER, null);
        gl.bindRenderbuffer(gl.RENDERBUFFER, null);
        gl.disable(gl.BLEND);
        gl.disable(gl.CULL_FACE);
        gl.disable(gl.DEPTH_TEST);
        gl.disable(gl.DITHER);
        gl.disable(gl.SCISSOR_TEST);
        gl.blendColor(0, 0, 0, 0);
        gl.blendEquation(gl.FUNC_ADD);
        gl.blendFunc(gl.ONE, gl.ZERO);
        gl.clearColor(0, 0, 0, 0);
        gl.clearDepth(1);
        gl.clearStencil(-1);

        this.gl = null;

        this._initialized = false;
    }

    addClock(clock) {
        if (!clock.__handler) {
            clock.__handler = this;
            this._clocks.push(clock);
        }
    }

    addClocks(clockArr) {
        for (let i = 0; i < clockArr.length; i++) {
            this.addClock(clockArr[i]);
        }
    }

    removeClock(clock) {
        if (clock.__handler) {
            let c = this._clocks;
            let i = c.length;
            while (i--) {
                if (c[i].equal(clock)) {
                    clock.__handler = null;
                    c.splice(i, 1);
                    break;
                }
            }
        }
    }
}

export { Handler };