123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641 |
- import Check from "../Core/Check.js";
- import defaultValue from "../Core/defaultValue.js";
- import defined from "../Core/defined.js";
- import destroyObject from "../Core/destroyObject.js";
- import DeveloperError from "../Core/DeveloperError.js";
- import RuntimeError from "../Core/RuntimeError.js";
- import AutomaticUniforms from "./AutomaticUniforms.js";
- import ContextLimits from "./ContextLimits.js";
- import createUniform from "./createUniform.js";
- import createUniformArray from "./createUniformArray.js";
- let nextShaderProgramId = 0;
- /**
- * @private
- */
- function ShaderProgram(options) {
- let vertexShaderText = options.vertexShaderText;
- let fragmentShaderText = options.fragmentShaderText;
- if (typeof spector !== "undefined") {
- // The #line statements common in Cesium shaders interfere with the ability of the
- // SpectorJS to show errors on the correct line. So remove them when SpectorJS
- // is active.
- vertexShaderText = vertexShaderText.replace(/^#line/gm, "//#line");
- fragmentShaderText = fragmentShaderText.replace(/^#line/gm, "//#line");
- }
- const modifiedFS = handleUniformPrecisionMismatches(
- vertexShaderText,
- fragmentShaderText
- );
- this._gl = options.gl;
- this._logShaderCompilation = options.logShaderCompilation;
- this._debugShaders = options.debugShaders;
- this._attributeLocations = options.attributeLocations;
- this._program = undefined;
- this._numberOfVertexAttributes = undefined;
- this._vertexAttributes = undefined;
- this._uniformsByName = undefined;
- this._uniforms = undefined;
- this._automaticUniforms = undefined;
- this._manualUniforms = undefined;
- this._duplicateUniformNames = modifiedFS.duplicateUniformNames;
- this._cachedShader = undefined; // Used by ShaderCache
- /**
- * @private
- */
- this.maximumTextureUnitIndex = undefined;
- this._vertexShaderSource = options.vertexShaderSource;
- this._vertexShaderText = options.vertexShaderText;
- this._fragmentShaderSource = options.fragmentShaderSource;
- this._fragmentShaderText = modifiedFS.fragmentShaderText;
- /**
- * @private
- */
- this.id = nextShaderProgramId++;
- }
- ShaderProgram.fromCache = function (options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- //>>includeStart('debug', pragmas.debug);
- Check.defined("options.context", options.context);
- //>>includeEnd('debug');
- return options.context.shaderCache.getShaderProgram(options);
- };
- ShaderProgram.replaceCache = function (options) {
- options = defaultValue(options, defaultValue.EMPTY_OBJECT);
- //>>includeStart('debug', pragmas.debug);
- Check.defined("options.context", options.context);
- //>>includeEnd('debug');
- return options.context.shaderCache.replaceShaderProgram(options);
- };
- Object.defineProperties(ShaderProgram.prototype, {
- /**
- * GLSL source for the shader program's vertex shader.
- * @memberof ShaderProgram.prototype
- *
- * @type {ShaderSource}
- * @readonly
- */
- vertexShaderSource: {
- get: function () {
- return this._vertexShaderSource;
- },
- },
- /**
- * GLSL source for the shader program's fragment shader.
- * @memberof ShaderProgram.prototype
- *
- * @type {ShaderSource}
- * @readonly
- */
- fragmentShaderSource: {
- get: function () {
- return this._fragmentShaderSource;
- },
- },
- vertexAttributes: {
- get: function () {
- initialize(this);
- return this._vertexAttributes;
- },
- },
- numberOfVertexAttributes: {
- get: function () {
- initialize(this);
- return this._numberOfVertexAttributes;
- },
- },
- allUniforms: {
- get: function () {
- initialize(this);
- return this._uniformsByName;
- },
- },
- });
- function extractUniforms(shaderText) {
- const uniformNames = [];
- const uniformLines = shaderText.match(/uniform.*?(?![^{]*})(?=[=\[;])/g);
- if (defined(uniformLines)) {
- const len = uniformLines.length;
- for (let i = 0; i < len; i++) {
- const line = uniformLines[i].trim();
- const name = line.slice(line.lastIndexOf(" ") + 1);
- uniformNames.push(name);
- }
- }
- return uniformNames;
- }
- function handleUniformPrecisionMismatches(
- vertexShaderText,
- fragmentShaderText
- ) {
- // If a uniform exists in both the vertex and fragment shader but with different precision qualifiers,
- // give the fragment shader uniform a different name. This fixes shader compilation errors on devices
- // that only support mediump in the fragment shader.
- const duplicateUniformNames = {};
- if (!ContextLimits.highpFloatSupported || !ContextLimits.highpIntSupported) {
- let i, j;
- let uniformName;
- let duplicateName;
- const vertexShaderUniforms = extractUniforms(vertexShaderText);
- const fragmentShaderUniforms = extractUniforms(fragmentShaderText);
- const vertexUniformsCount = vertexShaderUniforms.length;
- const fragmentUniformsCount = fragmentShaderUniforms.length;
- for (i = 0; i < vertexUniformsCount; i++) {
- for (j = 0; j < fragmentUniformsCount; j++) {
- if (vertexShaderUniforms[i] === fragmentShaderUniforms[j]) {
- uniformName = vertexShaderUniforms[i];
- duplicateName = `czm_mediump_${uniformName}`;
- // Update fragmentShaderText with renamed uniforms
- const re = new RegExp(`${uniformName}\\b`, "g");
- fragmentShaderText = fragmentShaderText.replace(re, duplicateName);
- duplicateUniformNames[duplicateName] = uniformName;
- }
- }
- }
- }
- return {
- fragmentShaderText: fragmentShaderText,
- duplicateUniformNames: duplicateUniformNames,
- };
- }
- const consolePrefix = "[Cesium WebGL] ";
- function createAndLinkProgram(gl, shader) {
- const vsSource = shader._vertexShaderText;
- const fsSource = shader._fragmentShaderText;
- const vertexShader = gl.createShader(gl.VERTEX_SHADER);
- gl.shaderSource(vertexShader, vsSource);
- gl.compileShader(vertexShader);
- const fragmentShader = gl.createShader(gl.FRAGMENT_SHADER);
- gl.shaderSource(fragmentShader, fsSource);
- gl.compileShader(fragmentShader);
- const program = gl.createProgram();
- gl.attachShader(program, vertexShader);
- gl.attachShader(program, fragmentShader);
- gl.deleteShader(vertexShader);
- gl.deleteShader(fragmentShader);
- const attributeLocations = shader._attributeLocations;
- if (defined(attributeLocations)) {
- for (const attribute in attributeLocations) {
- if (attributeLocations.hasOwnProperty(attribute)) {
- gl.bindAttribLocation(
- program,
- attributeLocations[attribute],
- attribute
- );
- }
- }
- }
- gl.linkProgram(program);
- let log;
- if (!gl.getProgramParameter(program, gl.LINK_STATUS)) {
- const debugShaders = shader._debugShaders;
- // For performance, only check compile errors if there is a linker error.
- if (!gl.getShaderParameter(fragmentShader, gl.COMPILE_STATUS)) {
- log = gl.getShaderInfoLog(fragmentShader);
- console.error(`${consolePrefix}Fragment shader compile log: ${log}`);
- if (defined(debugShaders)) {
- const fragmentSourceTranslation = debugShaders.getTranslatedShaderSource(
- fragmentShader
- );
- if (fragmentSourceTranslation !== "") {
- console.error(
- `${consolePrefix}Translated fragment shader source:\n${fragmentSourceTranslation}`
- );
- } else {
- console.error(`${consolePrefix}Fragment shader translation failed.`);
- }
- }
- gl.deleteProgram(program);
- throw new RuntimeError(
- `Fragment shader failed to compile. Compile log: ${log}`
- );
- }
- if (!gl.getShaderParameter(vertexShader, gl.COMPILE_STATUS)) {
- log = gl.getShaderInfoLog(vertexShader);
- console.error(`${consolePrefix}Vertex shader compile log: ${log}`);
- if (defined(debugShaders)) {
- const vertexSourceTranslation = debugShaders.getTranslatedShaderSource(
- vertexShader
- );
- if (vertexSourceTranslation !== "") {
- console.error(
- `${consolePrefix}Translated vertex shader source:\n${vertexSourceTranslation}`
- );
- } else {
- console.error(`${consolePrefix}Vertex shader translation failed.`);
- }
- }
- gl.deleteProgram(program);
- throw new RuntimeError(
- `Vertex shader failed to compile. Compile log: ${log}`
- );
- }
- log = gl.getProgramInfoLog(program);
- console.error(`${consolePrefix}Shader program link log: ${log}`);
- if (defined(debugShaders)) {
- console.error(
- `${consolePrefix}Translated vertex shader source:\n${debugShaders.getTranslatedShaderSource(
- vertexShader
- )}`
- );
- console.error(
- `${consolePrefix}Translated fragment shader source:\n${debugShaders.getTranslatedShaderSource(
- fragmentShader
- )}`
- );
- }
- gl.deleteProgram(program);
- throw new RuntimeError(`Program failed to link. Link log: ${log}`);
- }
- const logShaderCompilation = shader._logShaderCompilation;
- if (logShaderCompilation) {
- log = gl.getShaderInfoLog(vertexShader);
- if (defined(log) && log.length > 0) {
- console.log(`${consolePrefix}Vertex shader compile log: ${log}`);
- }
- }
- if (logShaderCompilation) {
- log = gl.getShaderInfoLog(fragmentShader);
- if (defined(log) && log.length > 0) {
- console.log(`${consolePrefix}Fragment shader compile log: ${log}`);
- }
- }
- if (logShaderCompilation) {
- log = gl.getProgramInfoLog(program);
- if (defined(log) && log.length > 0) {
- console.log(`${consolePrefix}Shader program link log: ${log}`);
- }
- }
- return program;
- }
- function findVertexAttributes(gl, program, numberOfAttributes) {
- const attributes = {};
- for (let i = 0; i < numberOfAttributes; ++i) {
- const attr = gl.getActiveAttrib(program, i);
- const location = gl.getAttribLocation(program, attr.name);
- attributes[attr.name] = {
- name: attr.name,
- type: attr.type,
- index: location,
- };
- }
- return attributes;
- }
- function findUniforms(gl, program) {
- const uniformsByName = {};
- const uniforms = [];
- const samplerUniforms = [];
- const numberOfUniforms = gl.getProgramParameter(program, gl.ACTIVE_UNIFORMS);
- for (let i = 0; i < numberOfUniforms; ++i) {
- const activeUniform = gl.getActiveUniform(program, i);
- const suffix = "[0]";
- const uniformName =
- activeUniform.name.indexOf(
- suffix,
- activeUniform.name.length - suffix.length
- ) !== -1
- ? activeUniform.name.slice(0, activeUniform.name.length - 3)
- : activeUniform.name;
- // Ignore GLSL built-in uniforms returned in Firefox.
- if (uniformName.indexOf("gl_") !== 0) {
- if (activeUniform.name.indexOf("[") < 0) {
- // Single uniform
- const location = gl.getUniformLocation(program, uniformName);
- // IE 11.0.9 needs this check since getUniformLocation can return null
- // if the uniform is not active (e.g., it is optimized out). Looks like
- // getActiveUniform() above returns uniforms that are not actually active.
- if (location !== null) {
- const uniform = createUniform(
- gl,
- activeUniform,
- uniformName,
- location
- );
- uniformsByName[uniformName] = uniform;
- uniforms.push(uniform);
- if (uniform._setSampler) {
- samplerUniforms.push(uniform);
- }
- }
- } else {
- // Uniform array
- let uniformArray;
- let locations;
- let value;
- let loc;
- // On some platforms - Nexus 4 in Firefox for one - an array of sampler2D ends up being represented
- // as separate uniforms, one for each array element. Check for and handle that case.
- const indexOfBracket = uniformName.indexOf("[");
- if (indexOfBracket >= 0) {
- // We're assuming the array elements show up in numerical order - it seems to be true.
- uniformArray = uniformsByName[uniformName.slice(0, indexOfBracket)];
- // Nexus 4 with Android 4.3 needs this check, because it reports a uniform
- // with the strange name webgl_3467e0265d05c3c1[1] in our globe surface shader.
- if (!defined(uniformArray)) {
- continue;
- }
- locations = uniformArray._locations;
- // On the Nexus 4 in Chrome, we get one uniform per sampler, just like in Firefox,
- // but the size is not 1 like it is in Firefox. So if we push locations here,
- // we'll end up adding too many locations.
- if (locations.length <= 1) {
- value = uniformArray.value;
- loc = gl.getUniformLocation(program, uniformName);
- // Workaround for IE 11.0.9. See above.
- if (loc !== null) {
- locations.push(loc);
- value.push(gl.getUniform(program, loc));
- }
- }
- } else {
- locations = [];
- for (let j = 0; j < activeUniform.size; ++j) {
- loc = gl.getUniformLocation(program, `${uniformName}[${j}]`);
- // Workaround for IE 11.0.9. See above.
- if (loc !== null) {
- locations.push(loc);
- }
- }
- uniformArray = createUniformArray(
- gl,
- activeUniform,
- uniformName,
- locations
- );
- uniformsByName[uniformName] = uniformArray;
- uniforms.push(uniformArray);
- if (uniformArray._setSampler) {
- samplerUniforms.push(uniformArray);
- }
- }
- }
- }
- }
- return {
- uniformsByName: uniformsByName,
- uniforms: uniforms,
- samplerUniforms: samplerUniforms,
- };
- }
- function partitionUniforms(shader, uniforms) {
- const automaticUniforms = [];
- const manualUniforms = [];
- for (const uniform in uniforms) {
- if (uniforms.hasOwnProperty(uniform)) {
- const uniformObject = uniforms[uniform];
- let uniformName = uniform;
- // if it's a duplicate uniform, use its original name so it is updated correctly
- const duplicateUniform = shader._duplicateUniformNames[uniformName];
- if (defined(duplicateUniform)) {
- uniformObject.name = duplicateUniform;
- uniformName = duplicateUniform;
- }
- const automaticUniform = AutomaticUniforms[uniformName];
- if (defined(automaticUniform)) {
- automaticUniforms.push({
- uniform: uniformObject,
- automaticUniform: automaticUniform,
- });
- } else {
- manualUniforms.push(uniformObject);
- }
- }
- }
- return {
- automaticUniforms: automaticUniforms,
- manualUniforms: manualUniforms,
- };
- }
- function setSamplerUniforms(gl, program, samplerUniforms) {
- gl.useProgram(program);
- let textureUnitIndex = 0;
- const length = samplerUniforms.length;
- for (let i = 0; i < length; ++i) {
- textureUnitIndex = samplerUniforms[i]._setSampler(textureUnitIndex);
- }
- gl.useProgram(null);
- return textureUnitIndex;
- }
- function initialize(shader) {
- if (defined(shader._program)) {
- return;
- }
- reinitialize(shader);
- }
- function reinitialize(shader) {
- const oldProgram = shader._program;
- const gl = shader._gl;
- const program = createAndLinkProgram(gl, shader, shader._debugShaders);
- const numberOfVertexAttributes = gl.getProgramParameter(
- program,
- gl.ACTIVE_ATTRIBUTES
- );
- const uniforms = findUniforms(gl, program);
- const partitionedUniforms = partitionUniforms(
- shader,
- uniforms.uniformsByName
- );
- shader._program = program;
- shader._numberOfVertexAttributes = numberOfVertexAttributes;
- shader._vertexAttributes = findVertexAttributes(
- gl,
- program,
- numberOfVertexAttributes
- );
- shader._uniformsByName = uniforms.uniformsByName;
- shader._uniforms = uniforms.uniforms;
- shader._automaticUniforms = partitionedUniforms.automaticUniforms;
- shader._manualUniforms = partitionedUniforms.manualUniforms;
- shader.maximumTextureUnitIndex = setSamplerUniforms(
- gl,
- program,
- uniforms.samplerUniforms
- );
- if (oldProgram) {
- shader._gl.deleteProgram(oldProgram);
- }
- // If SpectorJS is active, add the hook to make the shader editor work.
- // https://github.com/BabylonJS/Spector.js/blob/master/documentation/extension.md#shader-editor
- if (typeof spector !== "undefined") {
- shader._program.__SPECTOR_rebuildProgram = function (
- vertexSourceCode, // The new vertex shader source
- fragmentSourceCode, // The new fragment shader source
- onCompiled, // Callback triggered by your engine when the compilation is successful. It needs to send back the new linked program.
- onError // Callback triggered by your engine in case of error. It needs to send the WebGL error to allow the editor to display the error in the gutter.
- ) {
- const originalVS = shader._vertexShaderText;
- const originalFS = shader._fragmentShaderText;
- // SpectorJS likes to replace `!=` with `! =` for unknown reasons,
- // and that causes glsl compile failures. So fix that up.
- const regex = / ! = /g;
- shader._vertexShaderText = vertexSourceCode.replace(regex, " != ");
- shader._fragmentShaderText = fragmentSourceCode.replace(regex, " != ");
- try {
- reinitialize(shader);
- onCompiled(shader._program);
- } catch (e) {
- shader._vertexShaderText = originalVS;
- shader._fragmentShaderText = originalFS;
- // Only pass on the WebGL error:
- const errorMatcher = /(?:Compile|Link) error: ([^]*)/;
- const match = errorMatcher.exec(e.message);
- if (match) {
- onError(match[1]);
- } else {
- onError(e.message);
- }
- }
- };
- }
- }
- ShaderProgram.prototype._bind = function () {
- initialize(this);
- this._gl.useProgram(this._program);
- };
- ShaderProgram.prototype._setUniforms = function (
- uniformMap,
- uniformState,
- validate
- ) {
- let len;
- let i;
- if (defined(uniformMap)) {
- const manualUniforms = this._manualUniforms;
- len = manualUniforms.length;
- for (i = 0; i < len; ++i) {
- const mu = manualUniforms[i];
- mu.value = uniformMap[mu.name]();
- }
- }
- const automaticUniforms = this._automaticUniforms;
- len = automaticUniforms.length;
- for (i = 0; i < len; ++i) {
- const au = automaticUniforms[i];
- au.uniform.value = au.automaticUniform.getValue(uniformState);
- }
- ///////////////////////////////////////////////////////////////////
- // It appears that assigning the uniform values above and then setting them here
- // (which makes the GL calls) is faster than removing this loop and making
- // the GL calls above. I suspect this is because each GL call pollutes the
- // L2 cache making our JavaScript and the browser/driver ping-pong cache lines.
- const uniforms = this._uniforms;
- len = uniforms.length;
- for (i = 0; i < len; ++i) {
- uniforms[i].set();
- }
- if (validate) {
- const gl = this._gl;
- const program = this._program;
- gl.validateProgram(program);
- //>>includeStart('debug', pragmas.debug);
- if (!gl.getProgramParameter(program, gl.VALIDATE_STATUS)) {
- throw new DeveloperError(
- `Program validation failed. Program info log: ${gl.getProgramInfoLog(
- program
- )}`
- );
- }
- //>>includeEnd('debug');
- }
- };
- ShaderProgram.prototype.isDestroyed = function () {
- return false;
- };
- ShaderProgram.prototype.destroy = function () {
- this._cachedShader.cache.releaseShaderProgram(this);
- return undefined;
- };
- ShaderProgram.prototype.finalDestroy = function () {
- this._gl.deleteProgram(this._program);
- return destroyObject(this);
- };
- export default ShaderProgram;
|