ShaderBuilder.js 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566
  1. import Check from "../Core/Check.js";
  2. import clone from "../Core/clone.js";
  3. import defined from "../Core/defined.js";
  4. import defaultValue from "../Core/defaultValue.js";
  5. import DeveloperError from "../Core/DeveloperError.js";
  6. import ShaderDestination from "./ShaderDestination.js";
  7. import ShaderProgram from "./ShaderProgram.js";
  8. import ShaderSource from "./ShaderSource.js";
  9. import ShaderStruct from "./ShaderStruct.js";
  10. import ShaderFunction from "./ShaderFunction.js";
  11. /**
  12. * 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.
  13. * <p>
  14. * For vertex shaders, the shader builder tracks a list of <code>#defines</code>,
  15. * a list of attributes, a list of uniforms, and a list of shader lines. It also
  16. * tracks the location of each attribute so the caller can easily build the {@link VertexArray}
  17. * </p>
  18. * <p>
  19. * For fragment shaders, the shader builder tracks a list of <code>#defines</code>,
  20. * a list of attributes, a list of uniforms, and a list of shader lines.
  21. * </p>
  22. *
  23. * @alias ShaderBuilder
  24. * @constructor
  25. *
  26. * @example
  27. * const shaderBuilder = new ShaderBuilder();
  28. * shaderBuilder.addDefine("SOLID_COLOR", undefined, ShaderDestination.FRAGMENT);
  29. * shaderBuilder.addUniform("vec3", "u_color", ShaderDestination.FRAGMENT);
  30. * shaderBuilder.addVarying("vec3", v_color");
  31. * // These locations can be used when creating the VertexArray
  32. * const positionLocation = shaderBuilder.addPositionAttribute("vec3", "a_position");
  33. * const colorLocation = shaderBuilder.addAttribute("vec3", "a_color");
  34. * shaderBuilder.addVertexLines([
  35. * "void main()",
  36. * "{",
  37. * " v_color = a_color;",
  38. * " gl_Position = vec4(a_position, 1.0);",
  39. * "}"
  40. * ]);
  41. * shaderBuilder.addFragmentLines([
  42. * "void main()",
  43. * "{",
  44. * " #ifdef SOLID_COLOR",
  45. * " gl_FragColor = vec4(u_color, 1.0);",
  46. * " #else",
  47. * " gl_FragColor = vec4(v_color, 1.0);",
  48. * " #endif",
  49. * "}"
  50. * ]);
  51. * const shaderProgram = shaderBuilder.build(context);
  52. *
  53. * @private
  54. */
  55. export default function ShaderBuilder() {
  56. // Some WebGL implementations require attribute 0 to always
  57. // be active, so the position attribute is tracked separately
  58. this._positionAttributeLine = undefined;
  59. this._nextAttributeLocation = 1;
  60. this._attributeLocations = {};
  61. this._attributeLines = [];
  62. // Dynamically-generated structs and functions
  63. // these are dictionaries of id -> ShaderStruct or ShaderFunction respectively
  64. this._structs = {};
  65. this._functions = {};
  66. this._vertexShaderParts = {
  67. defineLines: [],
  68. uniformLines: [],
  69. shaderLines: [],
  70. varyingLines: [],
  71. // identifiers of structs/functions to include, listed in insertion order
  72. structIds: [],
  73. functionIds: [],
  74. };
  75. this._fragmentShaderParts = {
  76. defineLines: [],
  77. uniformLines: [],
  78. shaderLines: [],
  79. varyingLines: [],
  80. // identifiers of structs/functions to include, listed in insertion order
  81. structIds: [],
  82. functionIds: [],
  83. };
  84. }
  85. Object.defineProperties(ShaderBuilder.prototype, {
  86. /**
  87. * Get a dictionary of attribute names to the integer location in
  88. * the vertex shader.
  89. *
  90. * @memberof ShaderBuilder.prototype
  91. * @type {Object.<String, Number>}
  92. * @readonly
  93. * @private
  94. */
  95. attributeLocations: {
  96. get: function () {
  97. return this._attributeLocations;
  98. },
  99. },
  100. });
  101. /**
  102. * Add a <code>#define</code> macro to one or both of the shaders. These lines
  103. * will appear at the top of the final shader source.
  104. *
  105. * @param {String} identifier An identifier for the macro. Identifiers must use uppercase letters with underscores to be consistent with Cesium's style guide.
  106. * @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>
  107. * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the define appears in the vertex shader, the fragment shader, or both.
  108. *
  109. * @example
  110. * // creates the line "#define ENABLE_LIGHTING" in both shaders
  111. * shaderBuilder.addDefine("ENABLE_LIGHTING");
  112. * // creates the line "#define PI 3.141592" in the fragment shader
  113. * shaderBuilder.addDefine("PI", 3.141593, ShaderDestination.FRAGMENT);
  114. */
  115. ShaderBuilder.prototype.addDefine = function (identifier, value, destination) {
  116. //>>includeStart('debug', pragmas.debug);
  117. Check.typeOf.string("identifier", identifier);
  118. //>>includeEnd('debug');
  119. destination = defaultValue(destination, ShaderDestination.BOTH);
  120. // The ShaderSource created in build() will add the #define part
  121. let line = identifier;
  122. if (defined(value)) {
  123. line += ` ${value.toString()}`;
  124. }
  125. if (ShaderDestination.includesVertexShader(destination)) {
  126. this._vertexShaderParts.defineLines.push(line);
  127. }
  128. if (ShaderDestination.includesFragmentShader(destination)) {
  129. this._fragmentShaderParts.defineLines.push(line);
  130. }
  131. };
  132. /**
  133. * Add a new dynamically-generated struct to the shader
  134. * @param {String} structId A unique ID to identify this struct in {@link ShaderBuilder#addStructField}
  135. * @param {String} structName The name of the struct as it will appear in the shader.
  136. * @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both.
  137. *
  138. * @example
  139. * // generates the following struct in the fragment shader
  140. * // struct TestStruct
  141. * // {
  142. * // };
  143. * shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT);
  144. */
  145. ShaderBuilder.prototype.addStruct = function (
  146. structId,
  147. structName,
  148. destination
  149. ) {
  150. //>>includeStart('debug', pragmas.debug);
  151. Check.typeOf.string("structId", structId);
  152. Check.typeOf.string("structName", structName);
  153. Check.typeOf.number("destination", destination);
  154. //>>includeEnd('debug');
  155. this._structs[structId] = new ShaderStruct(structName);
  156. if (ShaderDestination.includesVertexShader(destination)) {
  157. this._vertexShaderParts.structIds.push(structId);
  158. }
  159. if (ShaderDestination.includesFragmentShader(destination)) {
  160. this._fragmentShaderParts.structIds.push(structId);
  161. }
  162. };
  163. /**
  164. * Add a field to a dynamically-generated struct.
  165. * @param {String} structId The ID of the struct. This must be created first with {@link ShaderBuilder#addStruct}
  166. * @param {String} type The GLSL type of the field
  167. * @param {String} identifier The identifier of the field.
  168. *
  169. * @example
  170. * // generates the following struct in the fragment shader
  171. * // struct TestStruct
  172. * // {
  173. * // float minimum;
  174. * // float maximum;
  175. * // };
  176. * shaderBuilder.addStruct("testStructId", "TestStruct", ShaderDestination.FRAGMENT);
  177. * shaderBuilder.addStructField("testStructId", "float", "maximum");
  178. * shaderBuilder.addStructField("testStructId", "float", "minimum");
  179. */
  180. ShaderBuilder.prototype.addStructField = function (structId, type, identifier) {
  181. //>>includeStart('debug', pragmas.debug);
  182. Check.typeOf.string("structId", structId);
  183. Check.typeOf.string("type", type);
  184. Check.typeOf.string("identifier", identifier);
  185. //>>includeEnd('debug');
  186. this._structs[structId].addField(type, identifier);
  187. };
  188. /**
  189. * Add a new dynamically-generated function to the shader.
  190. * @param {String} functionName The name of the function. This will be used to identify the function in {@link ShaderBuilder#addFunctionLines}.
  191. * @param {String} signature The full signature of the function as it will appear in the shader. Do not include the curly braces.
  192. * @param {ShaderDestination} destination Whether the struct will appear in the vertex shader, the fragment shader, or both.
  193. * @example
  194. * // generates the following function in the vertex shader
  195. * // vec3 testFunction(float parameter)
  196. * // {
  197. * // }
  198. * shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX);
  199. */
  200. ShaderBuilder.prototype.addFunction = function (
  201. functionName,
  202. signature,
  203. destination
  204. ) {
  205. //>>includeStart('debug', pragmas.debug);
  206. Check.typeOf.string("functionName", functionName);
  207. Check.typeOf.string("signature", signature);
  208. Check.typeOf.number("destination", destination);
  209. //>>includeEnd('debug');
  210. this._functions[functionName] = new ShaderFunction(signature);
  211. if (ShaderDestination.includesVertexShader(destination)) {
  212. this._vertexShaderParts.functionIds.push(functionName);
  213. }
  214. if (ShaderDestination.includesFragmentShader(destination)) {
  215. this._fragmentShaderParts.functionIds.push(functionName);
  216. }
  217. };
  218. /**
  219. * Add lines to a dynamically-generated function
  220. * @param {String} functionName The name of the function. This must be created beforehand using {@link ShaderBuilder#addFunction}
  221. * @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.
  222. *
  223. * @example
  224. * // generates the following function in the vertex shader
  225. * // vec3 testFunction(float parameter)
  226. * // {
  227. * // float signed = 2.0 * parameter - 1.0;
  228. * // return vec3(signed, 0.0, 0.0);
  229. * // }
  230. * shaderBuilder.addStruct("testFunction", "vec3 testFunction(float parameter)", ShaderDestination.VERTEX);
  231. * shaderBuilder.addFunctionLines("testFunction", [
  232. * "float signed = 2.0 * parameter - 1.0;",
  233. * "return vec3(parameter);"
  234. * ]);
  235. */
  236. ShaderBuilder.prototype.addFunctionLines = function (functionName, lines) {
  237. //>>includeStart('debug', pragmas.debug);
  238. Check.typeOf.string("functionName", functionName);
  239. Check.typeOf.object("lines", lines);
  240. //>>includeEnd('debug');
  241. this._functions[functionName].addLines(lines);
  242. };
  243. /**
  244. * Add a uniform declaration to one or both of the shaders. These lines
  245. * will appear grouped near the top of the final shader source.
  246. *
  247. * @param {String} type The GLSL type of the uniform.
  248. * @param {String} identifier An identifier for the uniform. Identifiers must begin with <code>u_</code> to be consistent with Cesium's style guide.
  249. * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the uniform appears in the vertex shader, the fragment shader, or both.
  250. *
  251. * @example
  252. * // creates the line "uniform vec3 u_resolution;"
  253. * shaderBuilder.addUniform("vec3", "u_resolution", ShaderDestination.FRAGMENT);
  254. * // creates the line "uniform float u_time;" in both shaders
  255. * shaderBuilder.addUniform("float", "u_time", ShaderDestination.BOTH);
  256. */
  257. ShaderBuilder.prototype.addUniform = function (type, identifier, destination) {
  258. //>>includeStart('debug', pragmas.debug);
  259. Check.typeOf.string("type", type);
  260. Check.typeOf.string("identifier", identifier);
  261. //>>includeEnd('debug');
  262. destination = defaultValue(destination, ShaderDestination.BOTH);
  263. const line = `uniform ${type} ${identifier};`;
  264. if (ShaderDestination.includesVertexShader(destination)) {
  265. this._vertexShaderParts.uniformLines.push(line);
  266. }
  267. if (ShaderDestination.includesFragmentShader(destination)) {
  268. this._fragmentShaderParts.uniformLines.push(line);
  269. }
  270. };
  271. /**
  272. * Add a position attribute declaration to the vertex shader. These lines
  273. * will appear grouped near the top of the final shader source.
  274. * <p>
  275. * Some WebGL implementations require attribute 0 to be enabled, so this is
  276. * reserved for the position attribute. For all other attributes, see
  277. * {@link ShaderBuilder#addAttribute}
  278. * </p>
  279. *
  280. * @param {String} type The GLSL type of the attribute
  281. * @param {String} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
  282. * @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.
  283. *
  284. * @example
  285. * // creates the line "attribute vec3 a_position;"
  286. * shaderBuilder.setPositionAttribute("vec3", "a_position");
  287. */
  288. ShaderBuilder.prototype.setPositionAttribute = function (type, identifier) {
  289. //>>includeStart('debug', pragmas.debug);
  290. Check.typeOf.string("type", type);
  291. Check.typeOf.string("identifier", identifier);
  292. if (defined(this._positionAttributeLine)) {
  293. throw new DeveloperError(
  294. "setPositionAttribute() must be called exactly once for the attribute used for gl_Position. For other attributes, use addAttribute()"
  295. );
  296. }
  297. //>>includeEnd('debug');
  298. this._positionAttributeLine = `attribute ${type} ${identifier};`;
  299. // Some WebGL implementations require attribute 0 to always be active, so
  300. // this builder assumes the position will always go in location 0
  301. this._attributeLocations[identifier] = 0;
  302. return 0;
  303. };
  304. /**
  305. * Add an attribute declaration to the vertex shader. These lines
  306. * will appear grouped near the top of the final shader source.
  307. * <p>
  308. * Some WebGL implementations require attribute 0 to be enabled, so this is
  309. * reserved for the position attribute. See {@link ShaderBuilder#setPositionAttribute}
  310. * </p>
  311. *
  312. * @param {String} type The GLSL type of the attribute
  313. * @param {String} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
  314. * @return {Number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}
  315. *
  316. * @example
  317. * // creates the line "attribute vec2 a_texCoord0;" in the vertex shader
  318. * shaderBuilder.addAttribute("vec2", "a_texCoord0");
  319. */
  320. ShaderBuilder.prototype.addAttribute = function (type, identifier) {
  321. //>>includeStart('debug', pragmas.debug);
  322. Check.typeOf.string("type", type);
  323. Check.typeOf.string("identifier", identifier);
  324. //>>includeEnd('debug');
  325. const line = `attribute ${type} ${identifier};`;
  326. this._attributeLines.push(line);
  327. const location = this._nextAttributeLocation;
  328. this._attributeLocations[identifier] = location;
  329. // Most attributes only require a single attribute location, but matrices
  330. // require more.
  331. this._nextAttributeLocation += getAttributeLocationCount(type);
  332. return location;
  333. };
  334. /**
  335. * Add a varying declaration to both the vertex and fragment shaders.
  336. *
  337. * @param {String} type The GLSL type of the varying
  338. * @param {String} identifier An identifier for the varying. Identifiers must begin with <code>v_</code> to be consistent with Cesium's style guide.
  339. *
  340. * @example
  341. * // creates the line "varying vec3 v_color;" in both shaders
  342. * shaderBuilder.addVarying("vec3", "v_color");
  343. */
  344. ShaderBuilder.prototype.addVarying = function (type, identifier) {
  345. //>>includeStart('debug', pragmas.debug);
  346. Check.typeOf.string("type", type);
  347. Check.typeOf.string("identifier", identifier);
  348. //>>includeEnd('debug');
  349. const line = `varying ${type} ${identifier};`;
  350. this._vertexShaderParts.varyingLines.push(line);
  351. this._fragmentShaderParts.varyingLines.push(line);
  352. };
  353. /**
  354. * Appends lines of GLSL code to the vertex shader
  355. *
  356. * @param {String[]} lines The lines to add to the end of the vertex shader source
  357. *
  358. * @example
  359. * shaderBuilder.addVertexLines([
  360. * "void main()",
  361. * "{",
  362. * " v_color = a_color;",
  363. * " gl_Position = vec4(a_position, 1.0);",
  364. * "}"
  365. * ]);
  366. */
  367. ShaderBuilder.prototype.addVertexLines = function (lines) {
  368. //>>includeStart('debug', pragmas.debug);
  369. Check.typeOf.object("lines", lines);
  370. //>>includeEnd('debug');
  371. Array.prototype.push.apply(this._vertexShaderParts.shaderLines, lines);
  372. };
  373. /**
  374. * Appends lines of GLSL code to the fragment shader
  375. *
  376. * @param {String[]} lines The lines to add to the end of the fragment shader source
  377. *
  378. * @example
  379. * shaderBuilder.addFragmentLines([
  380. * "void main()",
  381. * "{",
  382. * " #ifdef SOLID_COLOR",
  383. * " gl_FragColor = vec4(u_color, 1.0);",
  384. * " #else",
  385. * " gl_FragColor = vec4(v_color, 1.0);",
  386. * " #endif",
  387. * "}"
  388. * ]);
  389. */
  390. ShaderBuilder.prototype.addFragmentLines = function (lines) {
  391. //>>includeStart('debug', pragmas.debug);
  392. Check.typeOf.object("lines", lines);
  393. //>>includeEnd('debug');
  394. Array.prototype.push.apply(this._fragmentShaderParts.shaderLines, lines);
  395. };
  396. /**
  397. * Builds the {@link ShaderProgram} from the pieces added by the other methods.
  398. * Call this one time at the end of modifying the shader through the other
  399. * methods in this class.
  400. *
  401. * @param {Context} context The context to use for creating the shader.
  402. * @return {ShaderProgram} A shader program to use for rendering.
  403. *
  404. * @example
  405. * const shaderProgram = shaderBuilder.buildShaderProgram(context);
  406. */
  407. ShaderBuilder.prototype.buildShaderProgram = function (context) {
  408. //>>includeStart('debug', pragmas.debug);
  409. Check.typeOf.object("context", context);
  410. //>>includeEnd('debug');
  411. const positionAttribute = defined(this._positionAttributeLine)
  412. ? [this._positionAttributeLine]
  413. : [];
  414. const structLines = generateStructLines(this);
  415. const functionLines = generateFunctionLines(this);
  416. // Lines are joined here so the ShaderSource
  417. // generates a single #line 0 directive
  418. const vertexLines = positionAttribute
  419. .concat(
  420. this._attributeLines,
  421. this._vertexShaderParts.uniformLines,
  422. this._vertexShaderParts.varyingLines,
  423. structLines.vertexLines,
  424. functionLines.vertexLines,
  425. this._vertexShaderParts.shaderLines
  426. )
  427. .join("\n");
  428. const vertexShaderSource = new ShaderSource({
  429. defines: this._vertexShaderParts.defineLines,
  430. sources: [vertexLines],
  431. });
  432. const fragmentLines = this._fragmentShaderParts.uniformLines
  433. .concat(
  434. this._fragmentShaderParts.varyingLines,
  435. structLines.fragmentLines,
  436. functionLines.fragmentLines,
  437. this._fragmentShaderParts.shaderLines
  438. )
  439. .join("\n");
  440. const fragmentShaderSource = new ShaderSource({
  441. defines: this._fragmentShaderParts.defineLines,
  442. sources: [fragmentLines],
  443. });
  444. return ShaderProgram.fromCache({
  445. context: context,
  446. vertexShaderSource: vertexShaderSource,
  447. fragmentShaderSource: fragmentShaderSource,
  448. attributeLocations: this._attributeLocations,
  449. });
  450. };
  451. ShaderBuilder.prototype.clone = function () {
  452. return clone(this, true);
  453. };
  454. function generateStructLines(shaderBuilder) {
  455. const vertexLines = [];
  456. const fragmentLines = [];
  457. let i;
  458. let structIds = shaderBuilder._vertexShaderParts.structIds;
  459. let structId;
  460. let struct;
  461. let structLines;
  462. for (i = 0; i < structIds.length; i++) {
  463. structId = structIds[i];
  464. struct = shaderBuilder._structs[structId];
  465. structLines = struct.generateGlslLines();
  466. vertexLines.push.apply(vertexLines, structLines);
  467. }
  468. structIds = shaderBuilder._fragmentShaderParts.structIds;
  469. for (i = 0; i < structIds.length; i++) {
  470. structId = structIds[i];
  471. struct = shaderBuilder._structs[structId];
  472. structLines = struct.generateGlslLines();
  473. fragmentLines.push.apply(fragmentLines, structLines);
  474. }
  475. return {
  476. vertexLines: vertexLines,
  477. fragmentLines: fragmentLines,
  478. };
  479. }
  480. function getAttributeLocationCount(glslType) {
  481. switch (glslType) {
  482. case "mat2":
  483. return 2;
  484. case "mat3":
  485. return 3;
  486. case "mat4":
  487. return 4;
  488. default:
  489. return 1;
  490. }
  491. }
  492. function generateFunctionLines(shaderBuilder) {
  493. const vertexLines = [];
  494. const fragmentLines = [];
  495. let i;
  496. let functionIds = shaderBuilder._vertexShaderParts.functionIds;
  497. let functionId;
  498. let func;
  499. let functionLines;
  500. for (i = 0; i < functionIds.length; i++) {
  501. functionId = functionIds[i];
  502. func = shaderBuilder._functions[functionId];
  503. functionLines = func.generateGlslLines();
  504. vertexLines.push.apply(vertexLines, functionLines);
  505. }
  506. functionIds = shaderBuilder._fragmentShaderParts.functionIds;
  507. for (i = 0; i < functionIds.length; i++) {
  508. functionId = functionIds[i];
  509. func = shaderBuilder._functions[functionId];
  510. functionLines = func.generateGlslLines();
  511. fragmentLines.push.apply(fragmentLines, functionLines);
  512. }
  513. return {
  514. vertexLines: vertexLines,
  515. fragmentLines: fragmentLines,
  516. };
  517. }