Reference Source

src/viewer/scene/mesh/draw/DrawRenderer.js

/**
 * @author xeolabs / https://github.com/xeolabs
 */

import {Map} from "../../utils/Map.js";
import {DrawShaderSource} from "./DrawShaderSource.js";
import {Program} from "../../webgl/Program.js";
import {stats} from '../../stats.js';
import {WEBGL_INFO} from '../../webglInfo.js';
import {math} from "../../math/math.js";
import {getPlaneRTCPos} from "../../math/rtcCoords.js";

const tempVec3a = math.vec3();

const ids = new Map({});

/**
 * @private
 */
const DrawRenderer = function (hash, mesh) {
    this.id = ids.addItem({});
    this._hash = hash;
    this._scene = mesh.scene;
    this._useCount = 0;
    this._shaderSource = new DrawShaderSource(mesh);
    this._allocate(mesh);
};

const drawRenderers = {};

DrawRenderer.get = function (mesh) {
    const scene = mesh.scene;
    const hash = [
        scene.canvas.canvas.id,
        (scene.gammaInput ? "gi;" : ";") + (scene.gammaOutput ? "go" : ""),
        scene._lightsState.getHash(),
        scene._sectionPlanesState.getHash(),
        mesh._geometry._state.hash,
        mesh._material._state.hash,
        mesh._state.drawHash
    ].join(";");
    let renderer = drawRenderers[hash];
    if (!renderer) {
        renderer = new DrawRenderer(hash, mesh);
        if (renderer.errors) {
            console.log(renderer.errors.join("\n"));
            return null;
        }
        drawRenderers[hash] = renderer;
        stats.memory.programs++;
    }
    renderer._useCount++;
    return renderer;
};

DrawRenderer.prototype.put = function () {
    if (--this._useCount === 0) {
        ids.removeItem(this.id);
        if (this._program) {
            this._program.destroy();
        }
        delete drawRenderers[this._hash];
        stats.memory.programs--;
    }
};

DrawRenderer.prototype.webglContextRestored = function () {
    this._program = null;
};

DrawRenderer.prototype.drawMesh = function (frameCtx, mesh) {

    if (!this._program) {
        this._allocate(mesh);
    }

    const maxTextureUnits = WEBGL_INFO.MAX_TEXTURE_UNITS;
    const scene = mesh.scene;
    const material = mesh._material;
    const gl = scene.canvas.gl;
    const program = this._program;
    const meshState = mesh._state;
    const materialState = mesh._material._state;
    const geometryState = mesh._geometry._state;
    const camera = scene.camera;
    const origin = mesh.origin;
    const background = meshState.background;

    if (frameCtx.lastProgramId !== this._program.id) {
        frameCtx.lastProgramId = this._program.id;
        if (background) {
            gl.depthFunc(gl.LEQUAL);
        }
        this._bindProgram(frameCtx);
    }

    gl.uniformMatrix4fv(this._uViewMatrix, false, origin ? frameCtx.getRTCViewMatrix(meshState.originHash, origin) : camera.viewMatrix);
    gl.uniformMatrix4fv(this._uViewNormalMatrix, false, camera.viewNormalMatrix);

    if (meshState.clippable) {
        const numAllocatedSectionPlanes = scene._sectionPlanesState.getNumAllocatedSectionPlanes();
        const numSectionPlanes = scene._sectionPlanesState.sectionPlanes.length;
        if (numAllocatedSectionPlanes > 0) {
            const sectionPlanes = scene._sectionPlanesState.sectionPlanes;
            const renderFlags = mesh.renderFlags;
            for (let sectionPlaneIndex = 0; sectionPlaneIndex < numAllocatedSectionPlanes; sectionPlaneIndex++) {
                const sectionPlaneUniforms = this._uSectionPlanes[sectionPlaneIndex];
                if (sectionPlaneUniforms) {
                    if (sectionPlaneIndex < numSectionPlanes) {
                        const active = renderFlags.sectionPlanesActivePerLayer[sectionPlaneIndex];
                        gl.uniform1i(sectionPlaneUniforms.active, active ? 1 : 0);
                        if (active) {
                            const sectionPlane = sectionPlanes[sectionPlaneIndex];
                            if (origin) {
                                const rtcSectionPlanePos = getPlaneRTCPos(sectionPlane.dist, sectionPlane.dir, origin, tempVec3a);
                                gl.uniform3fv(sectionPlaneUniforms.pos, rtcSectionPlanePos);
                            } else {
                                gl.uniform3fv(sectionPlaneUniforms.pos, sectionPlane.pos);
                            }
                            gl.uniform3fv(sectionPlaneUniforms.dir, sectionPlane.dir);
                        }
                    } else {
                        gl.uniform1i(sectionPlaneUniforms.active, 0);
                    }
                }
            }
        }
    }

    if (materialState.id !== this._lastMaterialId) {

        frameCtx.textureUnit = this._baseTextureUnit;

        const backfaces = materialState.backfaces;
        if (frameCtx.backfaces !== backfaces) {
            if (backfaces) {
                gl.disable(gl.CULL_FACE);
            } else {
                gl.enable(gl.CULL_FACE);
            }
            frameCtx.backfaces = backfaces;
        }

        const frontface = materialState.frontface;
        if (frameCtx.frontface !== frontface) {
            if (frontface) {
                gl.frontFace(gl.CCW);
            } else {
                gl.frontFace(gl.CW);
            }
            frameCtx.frontface = frontface;
        }

        if (frameCtx.lineWidth !== materialState.lineWidth) {
            gl.lineWidth(materialState.lineWidth);
            frameCtx.lineWidth = materialState.lineWidth;
        }

        if (this._uPointSize) {
            gl.uniform1f(this._uPointSize, materialState.pointSize);
        }

        switch (materialState.type) {
            case "LambertMaterial":
                if (this._uMaterialAmbient) {
                    gl.uniform3fv(this._uMaterialAmbient, materialState.ambient);
                }
                if (this._uMaterialColor) {
                    gl.uniform4f(this._uMaterialColor, materialState.color[0], materialState.color[1], materialState.color[2], materialState.alpha);
                }
                if (this._uMaterialEmissive) {
                    gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);
                }
                break;

            case "PhongMaterial":
                if (this._uMaterialShininess) {
                    gl.uniform1f(this._uMaterialShininess, materialState.shininess);
                }
                if (this._uMaterialAmbient) {
                    gl.uniform3fv(this._uMaterialAmbient, materialState.ambient);
                }
                if (this._uMaterialDiffuse) {
                    gl.uniform3fv(this._uMaterialDiffuse, materialState.diffuse);
                }
                if (this._uMaterialSpecular) {
                    gl.uniform3fv(this._uMaterialSpecular, materialState.specular);
                }
                if (this._uMaterialEmissive) {
                    gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);
                }
                if (this._uAlphaModeCutoff) {
                    gl.uniform4f(
                        this._uAlphaModeCutoff,
                        1.0 * materialState.alpha,
                        materialState.alphaMode === 1 ? 1.0 : 0.0,
                        materialState.alphaCutoff,
                        0);
                }
                if (material._ambientMap && material._ambientMap._state.texture && this._uMaterialAmbientMap) {
                    program.bindTexture(this._uMaterialAmbientMap, material._ambientMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uMaterialAmbientMapMatrix) {
                        gl.uniformMatrix4fv(this._uMaterialAmbientMapMatrix, false, material._ambientMap._state.matrix);
                    }
                }
                if (material._diffuseMap && material._diffuseMap._state.texture && this._uDiffuseMap) {
                    program.bindTexture(this._uDiffuseMap, material._diffuseMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uDiffuseMapMatrix) {
                        gl.uniformMatrix4fv(this._uDiffuseMapMatrix, false, material._diffuseMap._state.matrix);
                    }
                }
                if (material._specularMap && material._specularMap._state.texture && this._uSpecularMap) {
                    program.bindTexture(this._uSpecularMap, material._specularMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uSpecularMapMatrix) {
                        gl.uniformMatrix4fv(this._uSpecularMapMatrix, false, material._specularMap._state.matrix);
                    }
                }
                if (material._emissiveMap && material._emissiveMap._state.texture && this._uEmissiveMap) {
                    program.bindTexture(this._uEmissiveMap, material._emissiveMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uEmissiveMapMatrix) {
                        gl.uniformMatrix4fv(this._uEmissiveMapMatrix, false, material._emissiveMap._state.matrix);
                    }
                }
                if (material._alphaMap && material._alphaMap._state.texture && this._uAlphaMap) {
                    program.bindTexture(this._uAlphaMap, material._alphaMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uAlphaMapMatrix) {
                        gl.uniformMatrix4fv(this._uAlphaMapMatrix, false, material._alphaMap._state.matrix);
                    }
                }
                if (material._reflectivityMap && material._reflectivityMap._state.texture && this._uReflectivityMap) {
                    program.bindTexture(this._uReflectivityMap, material._reflectivityMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    if (this._uReflectivityMapMatrix) {
                        gl.uniformMatrix4fv(this._uReflectivityMapMatrix, false, material._reflectivityMap._state.matrix);
                    }
                }
                if (material._normalMap && material._normalMap._state.texture && this._uNormalMap) {
                    program.bindTexture(this._uNormalMap, material._normalMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uNormalMapMatrix) {
                        gl.uniformMatrix4fv(this._uNormalMapMatrix, false, material._normalMap._state.matrix);
                    }
                }
                if (material._occlusionMap && material._occlusionMap._state.texture && this._uOcclusionMap) {
                    program.bindTexture(this._uOcclusionMap, material._occlusionMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uOcclusionMapMatrix) {
                        gl.uniformMatrix4fv(this._uOcclusionMapMatrix, false, material._occlusionMap._state.matrix);
                    }
                }
                if (material._diffuseFresnel) {
                    if (this._uDiffuseFresnelEdgeBias) {
                        gl.uniform1f(this._uDiffuseFresnelEdgeBias, material._diffuseFresnel.edgeBias);
                    }
                    if (this._uDiffuseFresnelCenterBias) {
                        gl.uniform1f(this._uDiffuseFresnelCenterBias, material._diffuseFresnel.centerBias);
                    }
                    if (this._uDiffuseFresnelEdgeColor) {
                        gl.uniform3fv(this._uDiffuseFresnelEdgeColor, material._diffuseFresnel.edgeColor);
                    }
                    if (this._uDiffuseFresnelCenterColor) {
                        gl.uniform3fv(this._uDiffuseFresnelCenterColor, material._diffuseFresnel.centerColor);
                    }
                    if (this._uDiffuseFresnelPower) {
                        gl.uniform1f(this._uDiffuseFresnelPower, material._diffuseFresnel.power);
                    }
                }
                if (material._specularFresnel) {
                    if (this._uSpecularFresnelEdgeBias) {
                        gl.uniform1f(this._uSpecularFresnelEdgeBias, material._specularFresnel.edgeBias);
                    }
                    if (this._uSpecularFresnelCenterBias) {
                        gl.uniform1f(this._uSpecularFresnelCenterBias, material._specularFresnel.centerBias);
                    }
                    if (this._uSpecularFresnelEdgeColor) {
                        gl.uniform3fv(this._uSpecularFresnelEdgeColor, material._specularFresnel.edgeColor);
                    }
                    if (this._uSpecularFresnelCenterColor) {
                        gl.uniform3fv(this._uSpecularFresnelCenterColor, material._specularFresnel.centerColor);
                    }
                    if (this._uSpecularFresnelPower) {
                        gl.uniform1f(this._uSpecularFresnelPower, material._specularFresnel.power);
                    }
                }
                if (material._alphaFresnel) {
                    if (this._uAlphaFresnelEdgeBias) {
                        gl.uniform1f(this._uAlphaFresnelEdgeBias, material._alphaFresnel.edgeBias);
                    }
                    if (this._uAlphaFresnelCenterBias) {
                        gl.uniform1f(this._uAlphaFresnelCenterBias, material._alphaFresnel.centerBias);
                    }
                    if (this._uAlphaFresnelEdgeColor) {
                        gl.uniform3fv(this._uAlphaFresnelEdgeColor, material._alphaFresnel.edgeColor);
                    }
                    if (this._uAlphaFresnelCenterColor) {
                        gl.uniform3fv(this._uAlphaFresnelCenterColor, material._alphaFresnel.centerColor);
                    }
                    if (this._uAlphaFresnelPower) {
                        gl.uniform1f(this._uAlphaFresnelPower, material._alphaFresnel.power);
                    }
                }
                if (material._reflectivityFresnel) {
                    if (this._uReflectivityFresnelEdgeBias) {
                        gl.uniform1f(this._uReflectivityFresnelEdgeBias, material._reflectivityFresnel.edgeBias);
                    }
                    if (this._uReflectivityFresnelCenterBias) {
                        gl.uniform1f(this._uReflectivityFresnelCenterBias, material._reflectivityFresnel.centerBias);
                    }
                    if (this._uReflectivityFresnelEdgeColor) {
                        gl.uniform3fv(this._uReflectivityFresnelEdgeColor, material._reflectivityFresnel.edgeColor);
                    }
                    if (this._uReflectivityFresnelCenterColor) {
                        gl.uniform3fv(this._uReflectivityFresnelCenterColor, material._reflectivityFresnel.centerColor);
                    }
                    if (this._uReflectivityFresnelPower) {
                        gl.uniform1f(this._uReflectivityFresnelPower, material._reflectivityFresnel.power);
                    }
                }
                if (material._emissiveFresnel) {
                    if (this._uEmissiveFresnelEdgeBias) {
                        gl.uniform1f(this._uEmissiveFresnelEdgeBias, material._emissiveFresnel.edgeBias);
                    }
                    if (this._uEmissiveFresnelCenterBias) {
                        gl.uniform1f(this._uEmissiveFresnelCenterBias, material._emissiveFresnel.centerBias);
                    }
                    if (this._uEmissiveFresnelEdgeColor) {
                        gl.uniform3fv(this._uEmissiveFresnelEdgeColor, material._emissiveFresnel.edgeColor);
                    }
                    if (this._uEmissiveFresnelCenterColor) {
                        gl.uniform3fv(this._uEmissiveFresnelCenterColor, material._emissiveFresnel.centerColor);
                    }
                    if (this._uEmissiveFresnelPower) {
                        gl.uniform1f(this._uEmissiveFresnelPower, material._emissiveFresnel.power);
                    }
                }
                break;

            case "MetallicMaterial":
                if (this._uBaseColor) {
                    gl.uniform3fv(this._uBaseColor, materialState.baseColor);
                }
                if (this._uMaterialMetallic) {
                    gl.uniform1f(this._uMaterialMetallic, materialState.metallic);
                }
                if (this._uMaterialRoughness) {
                    gl.uniform1f(this._uMaterialRoughness, materialState.roughness);
                }
                if (this._uMaterialSpecularF0) {
                    gl.uniform1f(this._uMaterialSpecularF0, materialState.specularF0);
                }
                if (this._uMaterialEmissive) {
                    gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);
                }
                if (this._uAlphaModeCutoff) {
                    gl.uniform4f(
                        this._uAlphaModeCutoff,
                        1.0 * materialState.alpha,
                        materialState.alphaMode === 1 ? 1.0 : 0.0,
                        materialState.alphaCutoff,
                        0.0);
                }
                const baseColorMap = material._baseColorMap;
                if (baseColorMap && baseColorMap._state.texture && this._uBaseColorMap) {
                    program.bindTexture(this._uBaseColorMap, baseColorMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uBaseColorMapMatrix) {
                        gl.uniformMatrix4fv(this._uBaseColorMapMatrix, false, baseColorMap._state.matrix);
                    }
                }
                const metallicMap = material._metallicMap;
                if (metallicMap && metallicMap._state.texture && this._uMetallicMap) {
                    program.bindTexture(this._uMetallicMap, metallicMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uMetallicMapMatrix) {
                        gl.uniformMatrix4fv(this._uMetallicMapMatrix, false, metallicMap._state.matrix);
                    }
                }
                const roughnessMap = material._roughnessMap;
                if (roughnessMap && roughnessMap._state.texture && this._uRoughnessMap) {
                    program.bindTexture(this._uRoughnessMap, roughnessMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uRoughnessMapMatrix) {
                        gl.uniformMatrix4fv(this._uRoughnessMapMatrix, false, roughnessMap._state.matrix);
                    }
                }
                const metallicRoughnessMap = material._metallicRoughnessMap;
                if (metallicRoughnessMap && metallicRoughnessMap._state.texture && this._uMetallicRoughnessMap) {
                    program.bindTexture(this._uMetallicRoughnessMap, metallicRoughnessMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uMetallicRoughnessMapMatrix) {
                        gl.uniformMatrix4fv(this._uMetallicRoughnessMapMatrix, false, metallicRoughnessMap._state.matrix);
                    }
                }
                var emissiveMap = material._emissiveMap;
                if (emissiveMap && emissiveMap._state.texture && this._uEmissiveMap) {
                    program.bindTexture(this._uEmissiveMap, emissiveMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uEmissiveMapMatrix) {
                        gl.uniformMatrix4fv(this._uEmissiveMapMatrix, false, emissiveMap._state.matrix);
                    }
                }
                var occlusionMap = material._occlusionMap;
                if (occlusionMap && material._occlusionMap._state.texture && this._uOcclusionMap) {
                    program.bindTexture(this._uOcclusionMap, occlusionMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uOcclusionMapMatrix) {
                        gl.uniformMatrix4fv(this._uOcclusionMapMatrix, false, occlusionMap._state.matrix);
                    }
                }
                var alphaMap = material._alphaMap;
                if (alphaMap && alphaMap._state.texture && this._uAlphaMap) {
                    program.bindTexture(this._uAlphaMap, alphaMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uAlphaMapMatrix) {
                        gl.uniformMatrix4fv(this._uAlphaMapMatrix, false, alphaMap._state.matrix);
                    }
                }
                var normalMap = material._normalMap;
                if (normalMap && normalMap._state.texture && this._uNormalMap) {
                    program.bindTexture(this._uNormalMap, normalMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uNormalMapMatrix) {
                        gl.uniformMatrix4fv(this._uNormalMapMatrix, false, normalMap._state.matrix);
                    }
                }
                break;

            case "SpecularMaterial":
                if (this._uMaterialDiffuse) {
                    gl.uniform3fv(this._uMaterialDiffuse, materialState.diffuse);
                }
                if (this._uMaterialSpecular) {
                    gl.uniform3fv(this._uMaterialSpecular, materialState.specular);
                }
                if (this._uMaterialGlossiness) {
                    gl.uniform1f(this._uMaterialGlossiness, materialState.glossiness);
                }
                if (this._uMaterialReflectivity) {
                    gl.uniform1f(this._uMaterialReflectivity, materialState.reflectivity);
                }
                if (this._uMaterialEmissive) {
                    gl.uniform3fv(this._uMaterialEmissive, materialState.emissive);
                }
                if (this._uAlphaModeCutoff) {
                    gl.uniform4f(
                        this._uAlphaModeCutoff,
                        1.0 * materialState.alpha,
                        materialState.alphaMode === 1 ? 1.0 : 0.0,
                        materialState.alphaCutoff,
                        0.0);
                }
                const diffuseMap = material._diffuseMap;
                if (diffuseMap && diffuseMap._state.texture && this._uDiffuseMap) {
                    program.bindTexture(this._uDiffuseMap, diffuseMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uDiffuseMapMatrix) {
                        gl.uniformMatrix4fv(this._uDiffuseMapMatrix, false, diffuseMap._state.matrix);
                    }
                }
                const specularMap = material._specularMap;
                if (specularMap && specularMap._state.texture && this._uSpecularMap) {
                    program.bindTexture(this._uSpecularMap, specularMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uSpecularMapMatrix) {
                        gl.uniformMatrix4fv(this._uSpecularMapMatrix, false, specularMap._state.matrix);
                    }
                }
                const glossinessMap = material._glossinessMap;
                if (glossinessMap && glossinessMap._state.texture && this._uGlossinessMap) {
                    program.bindTexture(this._uGlossinessMap, glossinessMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uGlossinessMapMatrix) {
                        gl.uniformMatrix4fv(this._uGlossinessMapMatrix, false, glossinessMap._state.matrix);
                    }
                }
                const specularGlossinessMap = material._specularGlossinessMap;
                if (specularGlossinessMap && specularGlossinessMap._state.texture && this._uSpecularGlossinessMap) {
                    program.bindTexture(this._uSpecularGlossinessMap, specularGlossinessMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uSpecularGlossinessMapMatrix) {
                        gl.uniformMatrix4fv(this._uSpecularGlossinessMapMatrix, false, specularGlossinessMap._state.matrix);
                    }
                }
                var emissiveMap = material._emissiveMap;
                if (emissiveMap && emissiveMap._state.texture && this._uEmissiveMap) {
                    program.bindTexture(this._uEmissiveMap, emissiveMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uEmissiveMapMatrix) {
                        gl.uniformMatrix4fv(this._uEmissiveMapMatrix, false, emissiveMap._state.matrix);
                    }
                }
                var occlusionMap = material._occlusionMap;
                if (occlusionMap && occlusionMap._state.texture && this._uOcclusionMap) {
                    program.bindTexture(this._uOcclusionMap, occlusionMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uOcclusionMapMatrix) {
                        gl.uniformMatrix4fv(this._uOcclusionMapMatrix, false, occlusionMap._state.matrix);
                    }
                }
                var alphaMap = material._alphaMap;
                if (alphaMap && alphaMap._state.texture && this._uAlphaMap) {
                    program.bindTexture(this._uAlphaMap, alphaMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uAlphaMapMatrix) {
                        gl.uniformMatrix4fv(this._uAlphaMapMatrix, false, alphaMap._state.matrix);
                    }
                }
                var normalMap = material._normalMap;
                if (normalMap && normalMap._state.texture && this._uNormalMap) {
                    program.bindTexture(this._uNormalMap, normalMap._state.texture, frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                    if (this._uNormalMapMatrix) {
                        gl.uniformMatrix4fv(this._uNormalMapMatrix, false, normalMap._state.matrix);
                    }
                }
                break;
        }
        this._lastMaterialId = materialState.id;
    }

    gl.uniformMatrix4fv(this._uModelMatrix, gl.FALSE, mesh.worldMatrix);
    if (this._uModelNormalMatrix) {
        gl.uniformMatrix4fv(this._uModelNormalMatrix, gl.FALSE, mesh.worldNormalMatrix);
    }

    if (this._uClippable) {
        gl.uniform1i(this._uClippable, meshState.clippable);
    }

    if (this._uColorize) {
        const colorize = meshState.colorize;
        const lastColorize = this._lastColorize;
        if (lastColorize[0] !== colorize[0] ||
            lastColorize[1] !== colorize[1] ||
            lastColorize[2] !== colorize[2] ||
            lastColorize[3] !== colorize[3]) {
            gl.uniform4fv(this._uColorize, colorize);
            lastColorize[0] = colorize[0];
            lastColorize[1] = colorize[1];
            lastColorize[2] = colorize[2];
            lastColorize[3] = colorize[3];
        }
    }

    gl.uniform3fv(this._uOffset, meshState.offset);

    // Bind VBOs

    if (geometryState.id !== this._lastGeometryId) {
        if (this._uPositionsDecodeMatrix) {
            gl.uniformMatrix4fv(this._uPositionsDecodeMatrix, false, geometryState.positionsDecodeMatrix);
        }
        if (this._uUVDecodeMatrix) {
            gl.uniformMatrix3fv(this._uUVDecodeMatrix, false, geometryState.uvDecodeMatrix);
        }
        if (this._aPosition) {
            this._aPosition.bindArrayBuffer(geometryState.positionsBuf);
            frameCtx.bindArray++;
        }
        if (this._aNormal) {
            this._aNormal.bindArrayBuffer(geometryState.normalsBuf);
            frameCtx.bindArray++;
        }
        if (this._aUV) {
            this._aUV.bindArrayBuffer(geometryState.uvBuf);
            frameCtx.bindArray++;
        }
        if (this._aColor) {
            this._aColor.bindArrayBuffer(geometryState.colorsBuf);
            frameCtx.bindArray++;
        }
        if (this._aFlags) {
            this._aFlags.bindArrayBuffer(geometryState.flagsBuf);
            frameCtx.bindArray++;
        }
        if (geometryState.indicesBuf) {
            geometryState.indicesBuf.bind();
            frameCtx.bindArray++;
        }
        this._lastGeometryId = geometryState.id;
    }

    // Draw (indices bound in prev step)

    if (geometryState.indicesBuf) {
        gl.drawElements(geometryState.primitive, geometryState.indicesBuf.numItems, geometryState.indicesBuf.itemType, 0);
        frameCtx.drawElements++;
    } else if (geometryState.positions) {
        gl.drawArrays(gl.TRIANGLES, 0, geometryState.positions.numItems);
        frameCtx.drawArrays++;
    }

    if (background) {
        gl.depthFunc(gl.LESS);
    }
};

DrawRenderer.prototype._allocate = function (mesh) {
    const scene = mesh.scene;
    const gl = scene.canvas.gl;
    const material = mesh._material;
    const lightsState = scene._lightsState;
    const sectionPlanesState = scene._sectionPlanesState;
    const materialState = mesh._material._state;

    this._program = new Program(gl, this._shaderSource);
    if (this._program.errors) {
        this.errors = this._program.errors;
        return;
    }
    const program = this._program;
    this._uPositionsDecodeMatrix = program.getLocation("positionsDecodeMatrix");
    this._uUVDecodeMatrix = program.getLocation("uvDecodeMatrix");
    this._uModelMatrix = program.getLocation("modelMatrix");
    this._uModelNormalMatrix = program.getLocation("modelNormalMatrix");
    this._uViewMatrix = program.getLocation("viewMatrix");
    this._uViewNormalMatrix = program.getLocation("viewNormalMatrix");
    this._uProjMatrix = program.getLocation("projMatrix");
    this._uGammaFactor = program.getLocation("gammaFactor");
    this._uLightAmbient = [];
    this._uLightColor = [];
    this._uLightDir = [];
    this._uLightPos = [];
    this._uLightAttenuation = [];
    this._uShadowViewMatrix = [];
    this._uShadowProjMatrix = [];

    if (scene.logarithmicDepthBufferEnabled) {
        this._uLogDepthBufFC = program.getLocation("logDepthBufFC");
    }

    const lights = lightsState.lights;
    let light;

    for (var i = 0, len = lights.length; i < len; i++) {
        light = lights[i];
        switch (light.type) {

            case "ambient":
                this._uLightAmbient[i] = program.getLocation("lightAmbient");
                break;

            case "dir":
                this._uLightColor[i] = program.getLocation("lightColor" + i);
                this._uLightPos[i] = null;
                this._uLightDir[i] = program.getLocation("lightDir" + i);
                break;

            case "point":
                this._uLightColor[i] = program.getLocation("lightColor" + i);
                this._uLightPos[i] = program.getLocation("lightPos" + i);
                this._uLightDir[i] = null;
                this._uLightAttenuation[i] = program.getLocation("lightAttenuation" + i);
                break;

            case "spot":
                this._uLightColor[i] = program.getLocation("lightColor" + i);
                this._uLightPos[i] = program.getLocation("lightPos" + i);
                this._uLightDir[i] = program.getLocation("lightDir" + i);
                this._uLightAttenuation[i] = program.getLocation("lightAttenuation" + i);
                break;
        }

        if (light.castsShadow) {
            this._uShadowViewMatrix[i] = program.getLocation("shadowViewMatrix" + i);
            this._uShadowProjMatrix[i] = program.getLocation("shadowProjMatrix" + i);
        }
    }

    if (lightsState.lightMaps.length > 0) {
        this._uLightMap = "lightMap";
    }

    if (lightsState.reflectionMaps.length > 0) {
        this._uReflectionMap = "reflectionMap";
    }

    this._uSectionPlanes = [];
    const sectionPlanes = sectionPlanesState.sectionPlanes;
    for (var i = 0, len = sectionPlanes.length; i < len; i++) {
        this._uSectionPlanes.push({
            active: program.getLocation("sectionPlaneActive" + i),
            pos: program.getLocation("sectionPlanePos" + i),
            dir: program.getLocation("sectionPlaneDir" + i)
        });
    }

    this._uPointSize = program.getLocation("pointSize");

    switch (materialState.type) {
        case "LambertMaterial":
            this._uMaterialColor = program.getLocation("materialColor");
            this._uMaterialEmissive = program.getLocation("materialEmissive");
            this._uAlphaModeCutoff = program.getLocation("materialAlphaModeCutoff");
            break;

        case "PhongMaterial":
            this._uMaterialAmbient = program.getLocation("materialAmbient");
            this._uMaterialDiffuse = program.getLocation("materialDiffuse");
            this._uMaterialSpecular = program.getLocation("materialSpecular");
            this._uMaterialEmissive = program.getLocation("materialEmissive");
            this._uAlphaModeCutoff = program.getLocation("materialAlphaModeCutoff");
            this._uMaterialShininess = program.getLocation("materialShininess");
            if (material._ambientMap) {
                this._uMaterialAmbientMap = "ambientMap";
                this._uMaterialAmbientMapMatrix = program.getLocation("ambientMapMatrix");
            }
            if (material._diffuseMap) {
                this._uDiffuseMap = "diffuseMap";
                this._uDiffuseMapMatrix = program.getLocation("diffuseMapMatrix");
            }
            if (material._specularMap) {
                this._uSpecularMap = "specularMap";
                this._uSpecularMapMatrix = program.getLocation("specularMapMatrix");
            }
            if (material._emissiveMap) {
                this._uEmissiveMap = "emissiveMap";
                this._uEmissiveMapMatrix = program.getLocation("emissiveMapMatrix");
            }
            if (material._alphaMap) {
                this._uAlphaMap = "alphaMap";
                this._uAlphaMapMatrix = program.getLocation("alphaMapMatrix");
            }
            if (material._reflectivityMap) {
                this._uReflectivityMap = "reflectivityMap";
                this._uReflectivityMapMatrix = program.getLocation("reflectivityMapMatrix");
            }
            if (material._normalMap) {
                this._uNormalMap = "normalMap";
                this._uNormalMapMatrix = program.getLocation("normalMapMatrix");
            }
            if (material._occlusionMap) {
                this._uOcclusionMap = "occlusionMap";
                this._uOcclusionMapMatrix = program.getLocation("occlusionMapMatrix");
            }
            if (material._diffuseFresnel) {
                this._uDiffuseFresnelEdgeBias = program.getLocation("diffuseFresnelEdgeBias");
                this._uDiffuseFresnelCenterBias = program.getLocation("diffuseFresnelCenterBias");
                this._uDiffuseFresnelEdgeColor = program.getLocation("diffuseFresnelEdgeColor");
                this._uDiffuseFresnelCenterColor = program.getLocation("diffuseFresnelCenterColor");
                this._uDiffuseFresnelPower = program.getLocation("diffuseFresnelPower");
            }
            if (material._specularFresnel) {
                this._uSpecularFresnelEdgeBias = program.getLocation("specularFresnelEdgeBias");
                this._uSpecularFresnelCenterBias = program.getLocation("specularFresnelCenterBias");
                this._uSpecularFresnelEdgeColor = program.getLocation("specularFresnelEdgeColor");
                this._uSpecularFresnelCenterColor = program.getLocation("specularFresnelCenterColor");
                this._uSpecularFresnelPower = program.getLocation("specularFresnelPower");
            }
            if (material._alphaFresnel) {
                this._uAlphaFresnelEdgeBias = program.getLocation("alphaFresnelEdgeBias");
                this._uAlphaFresnelCenterBias = program.getLocation("alphaFresnelCenterBias");
                this._uAlphaFresnelEdgeColor = program.getLocation("alphaFresnelEdgeColor");
                this._uAlphaFresnelCenterColor = program.getLocation("alphaFresnelCenterColor");
                this._uAlphaFresnelPower = program.getLocation("alphaFresnelPower");
            }
            if (material._reflectivityFresnel) {
                this._uReflectivityFresnelEdgeBias = program.getLocation("reflectivityFresnelEdgeBias");
                this._uReflectivityFresnelCenterBias = program.getLocation("reflectivityFresnelCenterBias");
                this._uReflectivityFresnelEdgeColor = program.getLocation("reflectivityFresnelEdgeColor");
                this._uReflectivityFresnelCenterColor = program.getLocation("reflectivityFresnelCenterColor");
                this._uReflectivityFresnelPower = program.getLocation("reflectivityFresnelPower");
            }
            if (material._emissiveFresnel) {
                this._uEmissiveFresnelEdgeBias = program.getLocation("emissiveFresnelEdgeBias");
                this._uEmissiveFresnelCenterBias = program.getLocation("emissiveFresnelCenterBias");
                this._uEmissiveFresnelEdgeColor = program.getLocation("emissiveFresnelEdgeColor");
                this._uEmissiveFresnelCenterColor = program.getLocation("emissiveFresnelCenterColor");
                this._uEmissiveFresnelPower = program.getLocation("emissiveFresnelPower");
            }
            break;

        case "MetallicMaterial":
            this._uBaseColor = program.getLocation("materialBaseColor");
            this._uMaterialMetallic = program.getLocation("materialMetallic");
            this._uMaterialRoughness = program.getLocation("materialRoughness");
            this._uMaterialSpecularF0 = program.getLocation("materialSpecularF0");
            this._uMaterialEmissive = program.getLocation("materialEmissive");
            this._uAlphaModeCutoff = program.getLocation("materialAlphaModeCutoff");
            if (material._baseColorMap) {
                this._uBaseColorMap = "baseColorMap";
                this._uBaseColorMapMatrix = program.getLocation("baseColorMapMatrix");
            }
            if (material._metallicMap) {
                this._uMetallicMap = "metallicMap";
                this._uMetallicMapMatrix = program.getLocation("metallicMapMatrix");
            }
            if (material._roughnessMap) {
                this._uRoughnessMap = "roughnessMap";
                this._uRoughnessMapMatrix = program.getLocation("roughnessMapMatrix");
            }
            if (material._metallicRoughnessMap) {
                this._uMetallicRoughnessMap = "metallicRoughnessMap";
                this._uMetallicRoughnessMapMatrix = program.getLocation("metallicRoughnessMapMatrix");
            }
            if (material._emissiveMap) {
                this._uEmissiveMap = "emissiveMap";
                this._uEmissiveMapMatrix = program.getLocation("emissiveMapMatrix");
            }
            if (material._occlusionMap) {
                this._uOcclusionMap = "occlusionMap";
                this._uOcclusionMapMatrix = program.getLocation("occlusionMapMatrix");
            }
            if (material._alphaMap) {
                this._uAlphaMap = "alphaMap";
                this._uAlphaMapMatrix = program.getLocation("alphaMapMatrix");
            }
            if (material._normalMap) {
                this._uNormalMap = "normalMap";
                this._uNormalMapMatrix = program.getLocation("normalMapMatrix");
            }
            break;

        case "SpecularMaterial":
            this._uMaterialDiffuse = program.getLocation("materialDiffuse");
            this._uMaterialSpecular = program.getLocation("materialSpecular");
            this._uMaterialGlossiness = program.getLocation("materialGlossiness");
            this._uMaterialReflectivity = program.getLocation("reflectivityFresnel");
            this._uMaterialEmissive = program.getLocation("materialEmissive");
            this._uAlphaModeCutoff = program.getLocation("materialAlphaModeCutoff");
            if (material._diffuseMap) {
                this._uDiffuseMap = "diffuseMap";
                this._uDiffuseMapMatrix = program.getLocation("diffuseMapMatrix");
            }
            if (material._specularMap) {
                this._uSpecularMap = "specularMap";
                this._uSpecularMapMatrix = program.getLocation("specularMapMatrix");
            }
            if (material._glossinessMap) {
                this._uGlossinessMap = "glossinessMap";
                this._uGlossinessMapMatrix = program.getLocation("glossinessMapMatrix");
            }
            if (material._specularGlossinessMap) {
                this._uSpecularGlossinessMap = "materialSpecularGlossinessMap";
                this._uSpecularGlossinessMapMatrix = program.getLocation("materialSpecularGlossinessMapMatrix");
            }
            if (material._emissiveMap) {
                this._uEmissiveMap = "emissiveMap";
                this._uEmissiveMapMatrix = program.getLocation("emissiveMapMatrix");
            }
            if (material._occlusionMap) {
                this._uOcclusionMap = "occlusionMap";
                this._uOcclusionMapMatrix = program.getLocation("occlusionMapMatrix");
            }
            if (material._alphaMap) {
                this._uAlphaMap = "alphaMap";
                this._uAlphaMapMatrix = program.getLocation("alphaMapMatrix");
            }
            if (material._normalMap) {
                this._uNormalMap = "normalMap";
                this._uNormalMapMatrix = program.getLocation("normalMapMatrix");
            }
            break;
    }

    this._aPosition = program.getAttribute("position");
    this._aNormal = program.getAttribute("normal");
    this._aUV = program.getAttribute("uv");
    this._aColor = program.getAttribute("color");
    this._aFlags = program.getAttribute("flags");

    this._uClippable = program.getLocation("clippable");
    this._uColorize = program.getLocation("colorize");
    this._uOffset = program.getLocation("offset");

    this._lastMaterialId = null;
    this._lastVertexBufsId = null;
    this._lastGeometryId = null;

    this._lastColorize = new Float32Array(4);

    this._baseTextureUnit = 0;

};

DrawRenderer.prototype._bindProgram = function (frameCtx) {

    const maxTextureUnits = WEBGL_INFO.MAX_TEXTURE_UNITS;
    const scene = this._scene;
    const gl = scene.canvas.gl;
    const lightsState = scene._lightsState;
    const project = scene.camera.project;
    let light;

    const program = this._program;

    program.bind();

    frameCtx.useProgram++;
    frameCtx.textureUnit = 0;

    this._lastMaterialId = null;
    this._lastVertexBufsId = null;
    this._lastGeometryId = null;

    this._lastColorize[0] = -1;
    this._lastColorize[1] = -1;
    this._lastColorize[2] = -1;
    this._lastColorize[3] = -1;

    gl.uniformMatrix4fv(this._uProjMatrix, false, project.matrix);

    if (scene.logarithmicDepthBufferEnabled) {
        const logDepthBufFC = 2.0 / (Math.log(project.far + 1.0) / Math.LN2);
        gl.uniform1f(this._uLogDepthBufFC, logDepthBufFC);
    }

    for (var i = 0, len = lightsState.lights.length; i < len; i++) {

        light = lightsState.lights[i];

        if (this._uLightAmbient[i]) {
            gl.uniform4f(this._uLightAmbient[i], light.color[0], light.color[1], light.color[2], light.intensity);

        } else {

            if (this._uLightColor[i]) {
                gl.uniform4f(this._uLightColor[i], light.color[0], light.color[1], light.color[2], light.intensity);
            }

            if (this._uLightPos[i]) {
                gl.uniform3fv(this._uLightPos[i], light.pos);
                if (this._uLightAttenuation[i]) {
                    gl.uniform1f(this._uLightAttenuation[i], light.attenuation);
                }
            }

            if (this._uLightDir[i]) {
                gl.uniform3fv(this._uLightDir[i], light.dir);
            }

            if (light.castsShadow) {
                if (this._uShadowViewMatrix[i]) {
                    gl.uniformMatrix4fv(this._uShadowViewMatrix[i], false, light.getShadowViewMatrix());
                }
                if (this._uShadowProjMatrix[i]) {
                    gl.uniformMatrix4fv(this._uShadowProjMatrix[i], false, light.getShadowProjMatrix());
                }
                const shadowRenderBuf = light.getShadowRenderBuf();
                if (shadowRenderBuf) {
                    program.bindTexture("shadowMap" + i, shadowRenderBuf.getTexture(), frameCtx.textureUnit);
                    frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
                    frameCtx.bindTexture++;
                }
            }
        }
    }

    if (lightsState.lightMaps.length > 0 && lightsState.lightMaps[0].texture && this._uLightMap) {
        program.bindTexture(this._uLightMap, lightsState.lightMaps[0].texture, frameCtx.textureUnit);
        frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
        frameCtx.bindTexture++;
    }

    if (lightsState.reflectionMaps.length > 0 && lightsState.reflectionMaps[0].texture && this._uReflectionMap) {
        program.bindTexture(this._uReflectionMap, lightsState.reflectionMaps[0].texture, frameCtx.textureUnit);
        frameCtx.textureUnit = (frameCtx.textureUnit + 1) % maxTextureUnits;
        frameCtx.bindTexture++;
    }

    if (this._uGammaFactor) {
        gl.uniform1f(this._uGammaFactor, scene.gammaFactor);
    }

    this._baseTextureUnit = frameCtx.textureUnit;
};

export {DrawRenderer};