| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566 | import Check from "../Core/Check.js";import clone from "../Core/clone.js";import defined from "../Core/defined.js";import defaultValue from "../Core/defaultValue.js";import DeveloperError from "../Core/DeveloperError.js";import ShaderDestination from "./ShaderDestination.js";import ShaderProgram from "./ShaderProgram.js";import ShaderSource from "./ShaderSource.js";import ShaderStruct from "./ShaderStruct.js";import ShaderFunction from "./ShaderFunction.js";/** * An object that makes it easier to build the text of a {@link ShaderProgram}. This tracks GLSL code for both the vertex shader and the fragment shader. * <p> * For vertex shaders, the shader builder tracks a list of <code>#defines</code>, * a list of attributes, a list of uniforms, and a list of shader lines. It also * tracks the location of each attribute so the caller can easily build the {@link VertexArray} * </p> * <p> * For fragment shaders, the shader builder tracks a list of <code>#defines</code>, * a list of attributes, a list of uniforms, and a list of shader lines. * </p> * * @alias ShaderBuilder * @constructor * * @example * const shaderBuilder = new ShaderBuilder(); * shaderBuilder.addDefine("SOLID_COLOR", undefined, ShaderDestination.FRAGMENT); * shaderBuilder.addUniform("vec3", "u_color", ShaderDestination.FRAGMENT); * shaderBuilder.addVarying("vec3", v_color"); * // These locations can be used when creating the VertexArray * const positionLocation = shaderBuilder.addPositionAttribute("vec3", "a_position"); * const colorLocation = shaderBuilder.addAttribute("vec3", "a_color"); * shaderBuilder.addVertexLines([ *  "void main()", *  "{", *  "    v_color = a_color;", *  "    gl_Position = vec4(a_position, 1.0);", *  "}" * ]); * shaderBuilder.addFragmentLines([ *  "void main()", *  "{", *  "    #ifdef SOLID_COLOR", *  "    gl_FragColor = vec4(u_color, 1.0);", *  "    #else", *  "    gl_FragColor = vec4(v_color, 1.0);", *  "    #endif", *  "}" * ]); * const shaderProgram = shaderBuilder.build(context); * * @private */export default function ShaderBuilder() {  // Some WebGL implementations require attribute 0 to always  // be active, so the position attribute is tracked separately  this._positionAttributeLine = undefined;  this._nextAttributeLocation = 1;  this._attributeLocations = {};  this._attributeLines = [];  // Dynamically-generated structs and functions  // these are dictionaries of id -> ShaderStruct or ShaderFunction respectively  this._structs = {};  this._functions = {};  this._vertexShaderParts = {    defineLines: [],    uniformLines: [],    shaderLines: [],    varyingLines: [],    // identifiers of structs/functions to include, listed in insertion order    structIds: [],    functionIds: [],  };  this._fragmentShaderParts = {    defineLines: [],    uniformLines: [],    shaderLines: [],    varyingLines: [],    // identifiers of structs/functions to include, listed in insertion order    structIds: [],    functionIds: [],  };}Object.defineProperties(ShaderBuilder.prototype, {  /**   * Get a dictionary of attribute names to the integer location in   * the vertex shader.   *   * @memberof ShaderBuilder.prototype   * @type {Object.<String, Number>}   * @readonly   * @private   */  attributeLocations: {    get: function () {      return this._attributeLocations;    },  },});/** * Add a <code>#define</code> macro to one or both of the shaders. These lines * will appear at the top of the final shader source. * * @param {String} identifier An identifier for the macro. Identifiers must use uppercase letters with underscores to be consistent with Cesium's style guide. * @param {String} [value] The value of the macro. If undefined, the define will not include a value. The value will be converted to GLSL code via <code>toString()</code> * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the define appears in the vertex shader, the fragment shader, or both. * * @example * // creates the line "#define ENABLE_LIGHTING" in both shaders * shaderBuilder.addDefine("ENABLE_LIGHTING"); * // creates the line "#define PI 3.141592" in the fragment shader * shaderBuilder.addDefine("PI", 3.141593, ShaderDestination.FRAGMENT); */ShaderBuilder.prototype.addDefine = function (identifier, value, destination) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("identifier", identifier);  //>>includeEnd('debug');  destination = defaultValue(destination, ShaderDestination.BOTH);  // The ShaderSource created in build() will add the #define part  let line = identifier;  if (defined(value)) {    line += ` ${value.toString()}`;  }  if (ShaderDestination.includesVertexShader(destination)) {    this._vertexShaderParts.defineLines.push(line);  }  if (ShaderDestination.includesFragmentShader(destination)) {    this._fragmentShaderParts.defineLines.push(line);  }};/** * Add a new dynamically-generated struct to the shader * @param {String} structId A unique ID to identify this struct in {@link ShaderBuilder#addStructField} * @param {String} structName The name of the struct as it will appear in the shader. * @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both. * * @example * // generates the following struct in the fragment shader * // struct TestStruct * // { * // }; * shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT); */ShaderBuilder.prototype.addStruct = function (  structId,  structName,  destination) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("structId", structId);  Check.typeOf.string("structName", structName);  Check.typeOf.number("destination", destination);  //>>includeEnd('debug');  this._structs[structId] = new ShaderStruct(structName);  if (ShaderDestination.includesVertexShader(destination)) {    this._vertexShaderParts.structIds.push(structId);  }  if (ShaderDestination.includesFragmentShader(destination)) {    this._fragmentShaderParts.structIds.push(structId);  }};/** * Add a field to a dynamically-generated struct. * @param {String} structId The ID of the struct. This must be created first with {@link ShaderBuilder#addStruct} * @param {String} type The GLSL type of the field * @param {String} identifier The identifier of the field. * * @example * // generates the following struct in the fragment shader * // struct TestStruct * // { * //    float minimum; * //    float maximum; * // }; * shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT); * shaderBuilder.addStructField("testStructId", "float", "maximum"); * shaderBuilder.addStructField("testStructId", "float", "minimum"); */ShaderBuilder.prototype.addStructField = function (structId, type, identifier) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("structId", structId);  Check.typeOf.string("type", type);  Check.typeOf.string("identifier", identifier);  //>>includeEnd('debug');  this._structs[structId].addField(type, identifier);};/** * Add a new dynamically-generated function to the shader. * @param {String} functionName The name of the function. This will be used to identify the function in {@link ShaderBuilder#addFunctionLines}. * @param {String} signature The full signature of the function as it will appear in the shader. Do not include the curly braces. * @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both. * @example * // generates the following function in the vertex shader * // vec3 testFunction(float parameter) * // { * // } * shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX); */ShaderBuilder.prototype.addFunction = function (  functionName,  signature,  destination) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("functionName", functionName);  Check.typeOf.string("signature", signature);  Check.typeOf.number("destination", destination);  //>>includeEnd('debug');  this._functions[functionName] = new ShaderFunction(signature);  if (ShaderDestination.includesVertexShader(destination)) {    this._vertexShaderParts.functionIds.push(functionName);  }  if (ShaderDestination.includesFragmentShader(destination)) {    this._fragmentShaderParts.functionIds.push(functionName);  }};/** * Add lines to a dynamically-generated function * @param {String} functionName The name of the function. This must be created beforehand using {@link ShaderBuilder#addFunction} * @param {String[]} lines An array of lines of GLSL code to add to the function body. Do not include any preceding or ending whitespace, but do include the semicolon for each line. * * @example * // generates the following function in the vertex shader * // vec3 testFunction(float parameter) * // { * //   float signed = 2.0 * parameter - 1.0; * //   return vec3(signed, 0.0, 0.0); * // } * shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX); * shaderBuilder.addFunctionLines("testFunction", [ *   "float signed = 2.0 * parameter - 1.0;", *   "return vec3(parameter);" * ]); */ShaderBuilder.prototype.addFunctionLines = function (functionName, lines) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("functionName", functionName);  Check.typeOf.object("lines", lines);  //>>includeEnd('debug');  this._functions[functionName].addLines(lines);};/** * Add a uniform declaration to one or both of the shaders. These lines * will appear grouped near the top of the final shader source. * * @param {String} type The GLSL type of the uniform. * @param {String} identifier An identifier for the uniform. Identifiers must begin with <code>u_</code> to be consistent with Cesium's style guide. * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the uniform appears in the vertex shader, the fragment shader, or both. * * @example * // creates the line "uniform vec3 u_resolution;" * shaderBuilder.addUniform("vec3", "u_resolution", ShaderDestination.FRAGMENT); * // creates the line "uniform float u_time;" in both shaders * shaderBuilder.addUniform("float", "u_time", ShaderDestination.BOTH); */ShaderBuilder.prototype.addUniform = function (type, identifier, destination) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("type", type);  Check.typeOf.string("identifier", identifier);  //>>includeEnd('debug');  destination = defaultValue(destination, ShaderDestination.BOTH);  const line = `uniform ${type} ${identifier};`;  if (ShaderDestination.includesVertexShader(destination)) {    this._vertexShaderParts.uniformLines.push(line);  }  if (ShaderDestination.includesFragmentShader(destination)) {    this._fragmentShaderParts.uniformLines.push(line);  }};/** * Add a position attribute declaration to the vertex shader. These lines * will appear grouped near the top of the final shader source. * <p> * Some WebGL implementations require attribute 0 to be enabled, so this is * reserved for the position attribute. For all other attributes, see * {@link ShaderBuilder#addAttribute} * </p> * * @param {String} type The GLSL type of the attribute * @param {String} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide. * @return {Number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}. This will always be 0. * * @example * // creates the line "attribute vec3 a_position;" * shaderBuilder.setPositionAttribute("vec3", "a_position"); */ShaderBuilder.prototype.setPositionAttribute = function (type, identifier) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("type", type);  Check.typeOf.string("identifier", identifier);  if (defined(this._positionAttributeLine)) {    throw new DeveloperError(      "setPositionAttribute() must be called exactly once for the attribute used for gl_Position. For other attributes, use addAttribute()"    );  }  //>>includeEnd('debug');  this._positionAttributeLine = `attribute ${type} ${identifier};`;  // Some WebGL implementations require attribute 0 to always be active, so  // this builder assumes the position will always go in location 0  this._attributeLocations[identifier] = 0;  return 0;};/** * Add an attribute declaration to the vertex shader. These lines * will appear grouped near the top of the final shader source. * <p> * Some WebGL implementations require attribute 0 to be enabled, so this is * reserved for the position attribute. See {@link ShaderBuilder#setPositionAttribute} * </p> * * @param {String} type The GLSL type of the attribute * @param {String} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide. * @return {Number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray} * * @example * // creates the line "attribute vec2 a_texCoord0;" in the vertex shader * shaderBuilder.addAttribute("vec2", "a_texCoord0"); */ShaderBuilder.prototype.addAttribute = function (type, identifier) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("type", type);  Check.typeOf.string("identifier", identifier);  //>>includeEnd('debug');  const line = `attribute ${type} ${identifier};`;  this._attributeLines.push(line);  const location = this._nextAttributeLocation;  this._attributeLocations[identifier] = location;  // Most attributes only require a single attribute location, but matrices  // require more.  this._nextAttributeLocation += getAttributeLocationCount(type);  return location;};/** * Add a varying declaration to both the vertex and fragment shaders. * * @param {String} type The GLSL type of the varying * @param {String} identifier An identifier for the varying. Identifiers must begin with <code>v_</code> to be consistent with Cesium's style guide. * * @example * // creates the line "varying vec3 v_color;" in both shaders * shaderBuilder.addVarying("vec3", "v_color"); */ShaderBuilder.prototype.addVarying = function (type, identifier) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.string("type", type);  Check.typeOf.string("identifier", identifier);  //>>includeEnd('debug');  const line = `varying ${type} ${identifier};`;  this._vertexShaderParts.varyingLines.push(line);  this._fragmentShaderParts.varyingLines.push(line);};/** * Appends lines of GLSL code to the vertex shader * * @param {String[]} lines The lines to add to the end of the vertex shader source * * @example * shaderBuilder.addVertexLines([ *  "void main()", *  "{", *  "    v_color = a_color;", *  "    gl_Position = vec4(a_position, 1.0);", *  "}" * ]); */ShaderBuilder.prototype.addVertexLines = function (lines) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("lines", lines);  //>>includeEnd('debug');  Array.prototype.push.apply(this._vertexShaderParts.shaderLines, lines);};/** * Appends lines of GLSL code to the fragment shader * * @param {String[]} lines The lines to add to the end of the fragment shader source * * @example * shaderBuilder.addFragmentLines([ *  "void main()", *  "{", *  "    #ifdef SOLID_COLOR", *  "    gl_FragColor = vec4(u_color, 1.0);", *  "    #else", *  "    gl_FragColor = vec4(v_color, 1.0);", *  "    #endif", *  "}" * ]); */ShaderBuilder.prototype.addFragmentLines = function (lines) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("lines", lines);  //>>includeEnd('debug');  Array.prototype.push.apply(this._fragmentShaderParts.shaderLines, lines);};/** * Builds the {@link ShaderProgram} from the pieces added by the other methods. * Call this one time at the end of modifying the shader through the other * methods in this class. * * @param {Context} context The context to use for creating the shader. * @return {ShaderProgram} A shader program to use for rendering. * * @example * const shaderProgram = shaderBuilder.buildShaderProgram(context); */ShaderBuilder.prototype.buildShaderProgram = function (context) {  //>>includeStart('debug', pragmas.debug);  Check.typeOf.object("context", context);  //>>includeEnd('debug');  const positionAttribute = defined(this._positionAttributeLine)    ? [this._positionAttributeLine]    : [];  const structLines = generateStructLines(this);  const functionLines = generateFunctionLines(this);  // Lines are joined here so the ShaderSource  // generates a single #line 0 directive  const vertexLines = positionAttribute    .concat(      this._attributeLines,      this._vertexShaderParts.uniformLines,      this._vertexShaderParts.varyingLines,      structLines.vertexLines,      functionLines.vertexLines,      this._vertexShaderParts.shaderLines    )    .join("\n");  const vertexShaderSource = new ShaderSource({    defines: this._vertexShaderParts.defineLines,    sources: [vertexLines],  });  const fragmentLines = this._fragmentShaderParts.uniformLines    .concat(      this._fragmentShaderParts.varyingLines,      structLines.fragmentLines,      functionLines.fragmentLines,      this._fragmentShaderParts.shaderLines    )    .join("\n");  const fragmentShaderSource = new ShaderSource({    defines: this._fragmentShaderParts.defineLines,    sources: [fragmentLines],  });  return ShaderProgram.fromCache({    context: context,    vertexShaderSource: vertexShaderSource,    fragmentShaderSource: fragmentShaderSource,    attributeLocations: this._attributeLocations,  });};ShaderBuilder.prototype.clone = function () {  return clone(this, true);};function generateStructLines(shaderBuilder) {  const vertexLines = [];  const fragmentLines = [];  let i;  let structIds = shaderBuilder._vertexShaderParts.structIds;  let structId;  let struct;  let structLines;  for (i = 0; i < structIds.length; i++) {    structId = structIds[i];    struct = shaderBuilder._structs[structId];    structLines = struct.generateGlslLines();    vertexLines.push.apply(vertexLines, structLines);  }  structIds = shaderBuilder._fragmentShaderParts.structIds;  for (i = 0; i < structIds.length; i++) {    structId = structIds[i];    struct = shaderBuilder._structs[structId];    structLines = struct.generateGlslLines();    fragmentLines.push.apply(fragmentLines, structLines);  }  return {    vertexLines: vertexLines,    fragmentLines: fragmentLines,  };}function getAttributeLocationCount(glslType) {  switch (glslType) {    case "mat2":      return 2;    case "mat3":      return 3;    case "mat4":      return 4;    default:      return 1;  }}function generateFunctionLines(shaderBuilder) {  const vertexLines = [];  const fragmentLines = [];  let i;  let functionIds = shaderBuilder._vertexShaderParts.functionIds;  let functionId;  let func;  let functionLines;  for (i = 0; i < functionIds.length; i++) {    functionId = functionIds[i];    func = shaderBuilder._functions[functionId];    functionLines = func.generateGlslLines();    vertexLines.push.apply(vertexLines, functionLines);  }  functionIds = shaderBuilder._fragmentShaderParts.functionIds;  for (i = 0; i < functionIds.length; i++) {    functionId = functionIds[i];    func = shaderBuilder._functions[functionId];    functionLines = func.generateGlslLines();    fragmentLines.push.apply(fragmentLines, functionLines);  }  return {    vertexLines: vertexLines,    fragmentLines: fragmentLines,  };}
 |