ShaderBuilder.js 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  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. * " out_FragColor = vec4(u_color, 1.0);",
  46. * " #else",
  47. * " out_FragColor = vec4(v_color, 1.0);",
  48. * " #endif",
  49. * "}"
  50. * ]);
  51. * const shaderProgram = shaderBuilder.build(context);
  52. *
  53. * @private
  54. */
  55. 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|string[]} lines One or more 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. if (typeof lines !== "string" && !Array.isArray(lines)) {
  240. throw new DeveloperError(
  241. `Expected lines to be a string or an array of strings, actual value was ${lines}`
  242. );
  243. }
  244. //>>includeEnd('debug');
  245. this._functions[functionName].addLines(lines);
  246. };
  247. /**
  248. * Add a uniform declaration to one or both of the shaders. These lines
  249. * will appear grouped near the top of the final shader source.
  250. *
  251. * @param {string} type The GLSL type of the uniform.
  252. * @param {string} identifier An identifier for the uniform. Identifiers must begin with <code>u_</code> to be consistent with Cesium's style guide.
  253. * @param {ShaderDestination} [destination=ShaderDestination.BOTH] Whether the uniform appears in the vertex shader, the fragment shader, or both.
  254. *
  255. * @example
  256. * // creates the line "uniform vec3 u_resolution;"
  257. * shaderBuilder.addUniform("vec3", "u_resolution", ShaderDestination.FRAGMENT);
  258. * // creates the line "uniform float u_time;" in both shaders
  259. * shaderBuilder.addUniform("float", "u_time", ShaderDestination.BOTH);
  260. */
  261. ShaderBuilder.prototype.addUniform = function (type, identifier, destination) {
  262. //>>includeStart('debug', pragmas.debug);
  263. Check.typeOf.string("type", type);
  264. Check.typeOf.string("identifier", identifier);
  265. //>>includeEnd('debug');
  266. destination = defaultValue(destination, ShaderDestination.BOTH);
  267. const line = `uniform ${type} ${identifier};`;
  268. if (ShaderDestination.includesVertexShader(destination)) {
  269. this._vertexShaderParts.uniformLines.push(line);
  270. }
  271. if (ShaderDestination.includesFragmentShader(destination)) {
  272. this._fragmentShaderParts.uniformLines.push(line);
  273. }
  274. };
  275. /**
  276. * Add a position attribute declaration to the vertex shader. These lines
  277. * will appear grouped near the top of the final shader source.
  278. * <p>
  279. * Some WebGL implementations require attribute 0 to be enabled, so this is
  280. * reserved for the position attribute. For all other attributes, see
  281. * {@link ShaderBuilder#addAttribute}
  282. * </p>
  283. *
  284. * @param {string} type The GLSL type of the attribute
  285. * @param {string} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
  286. * @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.
  287. *
  288. * @example
  289. * // creates the line "in vec3 a_position;"
  290. * shaderBuilder.setPositionAttribute("vec3", "a_position");
  291. */
  292. ShaderBuilder.prototype.setPositionAttribute = function (type, identifier) {
  293. //>>includeStart('debug', pragmas.debug);
  294. Check.typeOf.string("type", type);
  295. Check.typeOf.string("identifier", identifier);
  296. if (defined(this._positionAttributeLine)) {
  297. throw new DeveloperError(
  298. "setPositionAttribute() must be called exactly once for the attribute used for gl_Position. For other attributes, use addAttribute()"
  299. );
  300. }
  301. //>>includeEnd('debug');
  302. this._positionAttributeLine = `in ${type} ${identifier};`;
  303. // Some WebGL implementations require attribute 0 to always be active, so
  304. // this builder assumes the position will always go in location 0
  305. this._attributeLocations[identifier] = 0;
  306. return 0;
  307. };
  308. /**
  309. * Add an attribute declaration to the vertex shader. These lines
  310. * will appear grouped near the top of the final shader source.
  311. * <p>
  312. * Some WebGL implementations require attribute 0 to be enabled, so this is
  313. * reserved for the position attribute. See {@link ShaderBuilder#setPositionAttribute}
  314. * </p>
  315. *
  316. * @param {string} type The GLSL type of the attribute
  317. * @param {string} identifier An identifier for the attribute. Identifiers must begin with <code>a_</code> to be consistent with Cesium's style guide.
  318. * @return {number} The integer location of the attribute. This location can be used when creating attributes for a {@link VertexArray}
  319. *
  320. * @example
  321. * // creates the line "in vec2 a_texCoord0;" in the vertex shader
  322. * shaderBuilder.addAttribute("vec2", "a_texCoord0");
  323. */
  324. ShaderBuilder.prototype.addAttribute = function (type, identifier) {
  325. //>>includeStart('debug', pragmas.debug);
  326. Check.typeOf.string("type", type);
  327. Check.typeOf.string("identifier", identifier);
  328. //>>includeEnd('debug');
  329. const line = `in ${type} ${identifier};`;
  330. this._attributeLines.push(line);
  331. const location = this._nextAttributeLocation;
  332. this._attributeLocations[identifier] = location;
  333. // Most attributes only require a single attribute location, but matrices
  334. // require more.
  335. this._nextAttributeLocation += getAttributeLocationCount(type);
  336. return location;
  337. };
  338. /**
  339. * Add a varying declaration to both the vertex and fragment shaders.
  340. *
  341. * @param {string} type The GLSL type of the varying
  342. * @param {string} identifier An identifier for the varying. Identifiers must begin with <code>v_</code> to be consistent with Cesium's style guide.
  343. *
  344. * @example
  345. * // creates the line "in vec3 v_color;" in the vertex shader
  346. * // creates the line "out vec3 v_color;" in the fragment shader
  347. * shaderBuilder.addVarying("vec3", "v_color");
  348. */
  349. ShaderBuilder.prototype.addVarying = function (type, identifier) {
  350. //>>includeStart('debug', pragmas.debug);
  351. Check.typeOf.string("type", type);
  352. Check.typeOf.string("identifier", identifier);
  353. //>>includeEnd('debug');
  354. const line = `${type} ${identifier};`;
  355. this._vertexShaderParts.varyingLines.push(`out ${line}`);
  356. this._fragmentShaderParts.varyingLines.push(`in ${line}`);
  357. };
  358. /**
  359. * Appends lines of GLSL code to the vertex shader
  360. *
  361. * @param {string|string[]} lines One or more lines to add to the end of the vertex shader source
  362. *
  363. * @example
  364. * shaderBuilder.addVertexLines([
  365. * "void main()",
  366. * "{",
  367. * " v_color = a_color;",
  368. * " gl_Position = vec4(a_position, 1.0);",
  369. * "}"
  370. * ]);
  371. */
  372. ShaderBuilder.prototype.addVertexLines = function (lines) {
  373. //>>includeStart('debug', pragmas.debug);
  374. if (typeof lines !== "string" && !Array.isArray(lines)) {
  375. throw new DeveloperError(
  376. `Expected lines to be a string or an array of strings, actual value was ${lines}`
  377. );
  378. }
  379. //>>includeEnd('debug');
  380. const vertexLines = this._vertexShaderParts.shaderLines;
  381. if (Array.isArray(lines)) {
  382. vertexLines.push.apply(vertexLines, lines);
  383. } else {
  384. // Single string case
  385. vertexLines.push(lines);
  386. }
  387. };
  388. /**
  389. * Appends lines of GLSL code to the fragment shader
  390. *
  391. * @param {string[]} lines The lines to add to the end of the fragment shader source
  392. *
  393. * @example
  394. * shaderBuilder.addFragmentLines([
  395. * "void main()",
  396. * "{",
  397. * " #ifdef SOLID_COLOR",
  398. * " out_FragColor = vec4(u_color, 1.0);",
  399. * " #else",
  400. * " out_FragColor = vec4(v_color, 1.0);",
  401. * " #endif",
  402. * "}"
  403. * ]);
  404. */
  405. ShaderBuilder.prototype.addFragmentLines = function (lines) {
  406. //>>includeStart('debug', pragmas.debug);
  407. if (typeof lines !== "string" && !Array.isArray(lines)) {
  408. throw new DeveloperError(
  409. `Expected lines to be a string or an array of strings, actual value was ${lines}`
  410. );
  411. }
  412. //>>includeEnd('debug');
  413. const fragmentLines = this._fragmentShaderParts.shaderLines;
  414. if (Array.isArray(lines)) {
  415. fragmentLines.push.apply(fragmentLines, lines);
  416. } else {
  417. // Single string case
  418. fragmentLines.push(lines);
  419. }
  420. };
  421. /**
  422. * Builds the {@link ShaderProgram} from the pieces added by the other methods.
  423. * Call this one time at the end of modifying the shader through the other
  424. * methods in this class.
  425. *
  426. * @param {Context} context The context to use for creating the shader.
  427. * @return {ShaderProgram} A shader program to use for rendering.
  428. *
  429. * @example
  430. * const shaderProgram = shaderBuilder.buildShaderProgram(context);
  431. */
  432. ShaderBuilder.prototype.buildShaderProgram = function (context) {
  433. //>>includeStart('debug', pragmas.debug);
  434. Check.typeOf.object("context", context);
  435. //>>includeEnd('debug');
  436. const positionAttribute = defined(this._positionAttributeLine)
  437. ? [this._positionAttributeLine]
  438. : [];
  439. const structLines = generateStructLines(this);
  440. const functionLines = generateFunctionLines(this);
  441. // Lines are joined here so the ShaderSource
  442. // generates a single #line 0 directive
  443. const vertexLines = positionAttribute
  444. .concat(
  445. this._attributeLines,
  446. this._vertexShaderParts.uniformLines,
  447. this._vertexShaderParts.varyingLines,
  448. structLines.vertexLines,
  449. functionLines.vertexLines,
  450. this._vertexShaderParts.shaderLines
  451. )
  452. .join("\n");
  453. const vertexShaderSource = new ShaderSource({
  454. defines: this._vertexShaderParts.defineLines,
  455. sources: [vertexLines],
  456. });
  457. const fragmentLines = this._fragmentShaderParts.uniformLines
  458. .concat(
  459. this._fragmentShaderParts.varyingLines,
  460. structLines.fragmentLines,
  461. functionLines.fragmentLines,
  462. this._fragmentShaderParts.shaderLines
  463. )
  464. .join("\n");
  465. const fragmentShaderSource = new ShaderSource({
  466. defines: this._fragmentShaderParts.defineLines,
  467. sources: [fragmentLines],
  468. });
  469. return ShaderProgram.fromCache({
  470. context: context,
  471. vertexShaderSource: vertexShaderSource,
  472. fragmentShaderSource: fragmentShaderSource,
  473. attributeLocations: this._attributeLocations,
  474. });
  475. };
  476. ShaderBuilder.prototype.clone = function () {
  477. return clone(this, true);
  478. };
  479. function generateStructLines(shaderBuilder) {
  480. const vertexLines = [];
  481. const fragmentLines = [];
  482. let i;
  483. let structIds = shaderBuilder._vertexShaderParts.structIds;
  484. let structId;
  485. let struct;
  486. let structLines;
  487. for (i = 0; i < structIds.length; i++) {
  488. structId = structIds[i];
  489. struct = shaderBuilder._structs[structId];
  490. structLines = struct.generateGlslLines();
  491. vertexLines.push.apply(vertexLines, structLines);
  492. }
  493. structIds = shaderBuilder._fragmentShaderParts.structIds;
  494. for (i = 0; i < structIds.length; i++) {
  495. structId = structIds[i];
  496. struct = shaderBuilder._structs[structId];
  497. structLines = struct.generateGlslLines();
  498. fragmentLines.push.apply(fragmentLines, structLines);
  499. }
  500. return {
  501. vertexLines: vertexLines,
  502. fragmentLines: fragmentLines,
  503. };
  504. }
  505. function getAttributeLocationCount(glslType) {
  506. switch (glslType) {
  507. case "mat2":
  508. return 2;
  509. case "mat3":
  510. return 3;
  511. case "mat4":
  512. return 4;
  513. default:
  514. return 1;
  515. }
  516. }
  517. function generateFunctionLines(shaderBuilder) {
  518. const vertexLines = [];
  519. const fragmentLines = [];
  520. let i;
  521. let functionIds = shaderBuilder._vertexShaderParts.functionIds;
  522. let functionId;
  523. let func;
  524. let functionLines;
  525. for (i = 0; i < functionIds.length; i++) {
  526. functionId = functionIds[i];
  527. func = shaderBuilder._functions[functionId];
  528. functionLines = func.generateGlslLines();
  529. vertexLines.push.apply(vertexLines, functionLines);
  530. }
  531. functionIds = shaderBuilder._fragmentShaderParts.functionIds;
  532. for (i = 0; i < functionIds.length; i++) {
  533. functionId = functionIds[i];
  534. func = shaderBuilder._functions[functionId];
  535. functionLines = func.generateGlslLines();
  536. fragmentLines.push.apply(fragmentLines, functionLines);
  537. }
  538. return {
  539. vertexLines: vertexLines,
  540. fragmentLines: fragmentLines,
  541. };
  542. }
  543. export default ShaderBuilder;