all files / src/gpu/ ProgramManager.js

97.14% Statements 68/70
80.77% Branches 21/26
100% Functions 7/7
97.14% Lines 68/70
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163                            154×   154× 44×     38×     154× 154×     146×         154×   49×             154×     48× 48× 26×   48×       48× 48× 42× 26× 26× 26×       48× 48× 20× 20×       48× 73× 73×     67×     48×           24× 24× 48×   24×       24×             26× 26×     89×   89× 89× 69×   89×     89×   89× 65×     24× 24× 24× 24× 24× 24×                 24×   24×   24× 24×               24× 24×   24× 24× 24× 24× 24×   24×   24×      
import GLProgram from './GLProgram';
 
var loopRegex = /for\s*?\(int\s*?_idx_\s*\=\s*([\w-]+)\;\s*_idx_\s*<\s*([\w-]+);\s*_idx_\s*\+\+\s*\)\s*\{\{([\s\S]+?)(?=\}\})\}\}/g;
 
function unrollLoop(shaderStr, defines, lightsNumbers) {
    // Loop unroll from three.js, https://github.com/mrdoob/three.js/blob/master/src/renderers/webgl/WebGLProgram.js#L175
    // In some case like shadowMap in loop use 'i' to index value much slower.
 
    // Loop use _idx_ and increased with _idx_++ will be unrolled
    // Use {{ }} to match the pair so the if statement will not be affected
    // Write like following
    // for (int _idx_ = 0; _idx_ < 4; _idx_++) {{
    //     vec3 color = texture2D(textures[_idx_], uv).rgb;
    // }}
    function replace(match, start, end, snippet) {
        var unroll = '';
        // Try to treat as define
        if (isNaN(start)) {
            if (start in defines) {
                start = defines[start];
            }
            else {
                start = lightNumberDefines[start];
            }
        }
        Eif (isNaN(end)) {
            if (end in defines) {
                end = defines[end];
            }
            else {
                end = lightNumberDefines[end];
            }
        }
        // TODO Error checking
 
        for (var idx = parseInt(start); idx < parseInt(end); idx++) {
            // PENDING Add scope?
            unroll += '{'
                + snippet
                    .replace(/float\s*\(\s*_idx_\s*\)/g, idx.toFixed(1))
                    .replace(/_idx_/g, idx)
            + '}';
        }
 
        return unroll;
    }
 
    var lightNumberDefines = {};
    for (var lightType in lightsNumbers) {
        lightNumberDefines[lightType + '_COUNT'] = lightsNumbers[lightType];
    }
    return shaderStr.replace(loopRegex, replace);
}
 
function getDefineCode(defines, lightsNumbers, enabledTextures) {
    var defineStr = [];
    if (lightsNumbers) {
        for (var lightType in lightsNumbers) {
            var count = lightsNumbers[lightType];
            Eif (count > 0) {
                defineStr.push('#define ' + lightType.toUpperCase() + '_COUNT ' + count);
            }
        }
    }
    Eif (enabledTextures) {
        for (var i = 0; i < enabledTextures.length; i++) {
            var symbol = enabledTextures[i];
            defineStr.push('#define ' + symbol.toUpperCase() + '_ENABLED');
        }
    }
    // Custom Defines
    for (var symbol in defines) {
        var value = defines[symbol];
        if (value === null) {
            defineStr.push('#define ' + symbol);
        }
        else{
            defineStr.push('#define ' + symbol + ' ' + value.toString());
        }
    }
    return defineStr.join('\n');
}
 
function getExtensionCode(exts) {
    // Extension declaration must before all non-preprocessor codes
    // TODO vertex ? extension enum ?
    var extensionStr = [];
    for (var i = 0; i < exts.length; i++) {
        extensionStr.push('#extension GL_' + exts[i] + ' : enable');
    }
    return extensionStr.join('\n');
}
 
function getPrecisionCode(precision) {
    return ['precision', precision, 'float'].join(' ') + ';\n'
        + ['precision', precision, 'int'].join(' ') + ';\n'
        // depth texture may have precision problem on iOS device.
        + ['precision', precision, 'sampler2D'].join(' ') + ';\n';
}
 
function ProgramManager(renderer) {
    this._renderer = renderer;
    this._cache = {};
}
 
ProgramManager.prototype.getProgram = function (renderable, material, scene) {
    var cache = this._cache;
 
    var key = 's' + material.shader.shaderID + 'm' + material.getProgramKey();
    if (scene) {
        key += 'se' + scene.getProgramKey(renderable.lightGroup);
    }
    Iif (renderable.isSkinnedMesh()) {
        key += ',' + renderable.joints.length;
    }
    var program = cache[key];
 
    if (program) {
        return program;
    }
 
    var lightsNumbers = scene ? scene.getLightsNumbers(renderable.lightGroup) : {};
    var renderer = this._renderer;
    var _gl = renderer.gl;
    var enabledTextures = material.getEnabledTextures();
    var skinDefineCode = '';
    Iif (renderable.isSkinnedMesh()) {
        // TODO Add skinning code?
        skinDefineCode = '\n' + getDefineCode({
            SKINNING: null,
            JOINT_COUNT: renderable.joints.length
        }) + '\n';
    }
    // TODO Optimize key generation
    // VERTEX
    var vertexDefineStr = skinDefineCode + getDefineCode(material.vertexDefines, lightsNumbers, enabledTextures);
    // FRAGMENT
    var fragmentDefineStr = skinDefineCode + getDefineCode(material.fragmentDefines, lightsNumbers, enabledTextures);
 
    var vertexCode = vertexDefineStr + '\n' + material.shader.vertex;
    var fragmentCode = getExtensionCode([
            // TODO Not hard coded
            'OES_standard_derivatives',
            'EXT_shader_texture_lod'
        ]) + '\n'
            + getPrecisionCode(material.precision) + '\n'
            + fragmentDefineStr + '\n' + material.shader.fragment;
 
    var finalVertexCode = unrollLoop(vertexCode, material.vertexDefines, lightsNumbers);
    var finalFragmentCode = unrollLoop(fragmentCode, material.fragmentDefines, lightsNumbers);
 
    var program = new GLProgram();
    program.uniformSemantics = material.shader.uniformSemantics;
    program.attributes = material.shader.attributes;
    var errorMsg = program.buildProgram(_gl, material.shader, finalVertexCode, finalFragmentCode);
    program.__error = errorMsg;
 
    cache[key] = program;
 
    return program;
};
 
export default ProgramManager;