| 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;
 |