Shader.js

  1. 'use strict';
  2. const parallel = require('async/parallel');
  3. const WebGLContext = require('./WebGLContext');
  4. const ShaderParser = require('./ShaderParser');
  5. const XHRLoader = require('../util/XHRLoader');
  6. const UNIFORM_FUNCTIONS = {
  7. 'bool': 'uniform1i',
  8. 'bool[]': 'uniform1iv',
  9. 'float': 'uniform1f',
  10. 'float[]': 'uniform1fv',
  11. 'int': 'uniform1i',
  12. 'int[]': 'uniform1iv',
  13. 'uint': 'uniform1i',
  14. 'uint[]': 'uniform1iv',
  15. 'vec2': 'uniform2fv',
  16. 'vec2[]': 'uniform2fv',
  17. 'ivec2': 'uniform2iv',
  18. 'ivec2[]': 'uniform2iv',
  19. 'vec3': 'uniform3fv',
  20. 'vec3[]': 'uniform3fv',
  21. 'ivec3': 'uniform3iv',
  22. 'ivec3[]': 'uniform3iv',
  23. 'vec4': 'uniform4fv',
  24. 'vec4[]': 'uniform4fv',
  25. 'ivec4': 'uniform4iv',
  26. 'ivec4[]': 'uniform4iv',
  27. 'mat2': 'uniformMatrix2fv',
  28. 'mat2[]': 'uniformMatrix2fv',
  29. 'mat3': 'uniformMatrix3fv',
  30. 'mat3[]': 'uniformMatrix3fv',
  31. 'mat4': 'uniformMatrix4fv',
  32. 'mat4[]': 'uniformMatrix4fv',
  33. 'sampler2D': 'uniform1i',
  34. 'samplerCube': 'uniform1i'
  35. };
  36. /**
  37. * Given a map of existing attributes, find the lowest index that is not already
  38. * used. If the attribute ordering was already provided, use that instead.
  39. *
  40. * @private
  41. *
  42. * @param {Map} attributes - The existing attributes map.
  43. * @param {Object} declaration - The attribute declaration object.
  44. *
  45. * @returns {number} The attribute index.
  46. */
  47. function getAttributeIndex(attributes, declaration) {
  48. // check if attribute is already declared, if so, use that index
  49. if (attributes.has(declaration.name)) {
  50. return attributes.get(declaration.name).index;
  51. }
  52. // return next available index
  53. return attributes.size;
  54. }
  55. /**
  56. * Given vertex and fragment shader source, parses the declarations and appends
  57. * information pertaining to the uniforms and attribtues declared.
  58. *
  59. * @private
  60. *
  61. * @param {Shader} shader - The shader object.
  62. * @param {string} vertSource - The vertex shader source.
  63. * @param {string} fragSource - The fragment shader source.
  64. *
  65. * @returns {Object} The attribute and uniform information.
  66. */
  67. function setAttributesAndUniforms(shader, vertSource, fragSource) {
  68. const declarations = ShaderParser.parseDeclarations(
  69. [vertSource, fragSource],
  70. ['uniform', 'attribute']);
  71. // for each declaration in the shader
  72. declarations.forEach(declaration => {
  73. // check if its an attribute or uniform
  74. if (declaration.qualifier === 'attribute') {
  75. // if attribute, store type and index
  76. const index = getAttributeIndex(shader.attributes, declaration);
  77. shader.attributes.set(declaration.name, {
  78. type: declaration.type,
  79. index: index
  80. });
  81. } else { // if (declaration.qualifier === 'uniform') {
  82. // if uniform, store type and buffer function name
  83. const type = declaration.type + (declaration.count > 1 ? '[]' : '');
  84. shader.uniforms.set(declaration.name, {
  85. type: declaration.type,
  86. func: UNIFORM_FUNCTIONS[type]
  87. });
  88. }
  89. });
  90. }
  91. /**
  92. * Given a lineNumber and max number of digits, pad the line accordingly.
  93. *
  94. * @private
  95. *
  96. * @param {number} lineNum - The line number.
  97. * @param {number} maxDigits - The max digits to pad.
  98. *
  99. * @returns {string} The padded string.
  100. */
  101. function padLineNumber(lineNum, maxDigits) {
  102. lineNum = lineNum.toString();
  103. const diff = maxDigits - lineNum.length;
  104. lineNum += ':';
  105. for (let i=0; i<diff; i++) {
  106. lineNum += ' ';
  107. }
  108. return lineNum;
  109. };
  110. /**
  111. * Given a shader source string and shader type, compiles the shader and returns
  112. * the resulting WebGLShader object.
  113. *
  114. * @private
  115. *
  116. * @param {WebGLRenderingContext} gl - The webgl rendering context.
  117. * @param {string} shaderSource - The shader source.
  118. * @param {string} type - The shader type.
  119. *
  120. * @returns {WebGLShader} The compiled shader object.
  121. */
  122. function compileShader(gl, shaderSource, type) {
  123. const shader = gl.createShader(gl[type]);
  124. gl.shaderSource(shader, shaderSource);
  125. gl.compileShader(shader);
  126. if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
  127. const split = shaderSource.split('\n');
  128. const maxDigits = (split.length).toString().length + 1;
  129. const srcByLines = split.map((line, index) => {
  130. return `${padLineNumber(index+1, maxDigits)} ${line}`;
  131. }).join('\n');
  132. const shaderLog = gl.getShaderInfoLog(shader);
  133. throw `An error occurred compiling the shader:\n\n${shaderLog.slice(0, shaderLog.length-1)}\n${srcByLines}`;
  134. }
  135. return shader;
  136. }
  137. /**
  138. * Binds the attribute locations for the Shader object.
  139. *
  140. * @private
  141. *
  142. * @param {Shader} shader - The Shader object.
  143. */
  144. function bindAttributeLocations(shader) {
  145. const gl = shader.gl;
  146. shader.attributes.forEach((attribute, name) => {
  147. // bind the attribute location
  148. gl.bindAttribLocation(
  149. shader.program,
  150. attribute.index,
  151. name);
  152. });
  153. }
  154. /**
  155. * Queries the webgl rendering context for the uniform locations.
  156. *
  157. * @private
  158. *
  159. * @param {Shader} shader - The Shader object.
  160. */
  161. function getUniformLocations(shader) {
  162. const gl = shader.gl;
  163. const uniforms = shader.uniforms;
  164. uniforms.forEach((uniform, name) => {
  165. // get the uniform location
  166. const location = gl.getUniformLocation(shader.program, name);
  167. // check if null, parse may detect uniform that is compiled out due to
  168. // not being used, or due to a preprocessor evaluation.
  169. if (location === null) {
  170. uniforms.delete(name);
  171. } else {
  172. uniform.location = location;
  173. }
  174. });
  175. }
  176. /**
  177. * Returns a function to load shader source from a url.
  178. *
  179. * @private
  180. *
  181. * @param {string} url - The url to load the resource from.
  182. *
  183. * @returns {Function} The function to load the shader source.
  184. */
  185. function loadShaderSource(url) {
  186. return function(done) {
  187. XHRLoader.load({
  188. url: url,
  189. responseType: 'text',
  190. success: function(res) {
  191. done(null, res);
  192. },
  193. error: function(err) {
  194. done(err, null);
  195. }
  196. });
  197. };
  198. }
  199. /**
  200. * Returns a function to pass through the shader source.
  201. *
  202. * @private
  203. *
  204. * @param {string} source - The source of the shader.
  205. *
  206. * @returns {Function} The function to pass through the shader source.
  207. */
  208. function passThroughSource(source) {
  209. return function(done) {
  210. done(null, source);
  211. };
  212. }
  213. /**
  214. * Returns a function that takes an array of GLSL source strings and URLs, and
  215. * resolves them into and array of GLSL source.
  216. *
  217. * @private
  218. *
  219. * @param {Array} sources - The shader sources.
  220. *
  221. * @returns {Function} A function to resolve the shader sources.
  222. */
  223. function resolveSources(sources) {
  224. return function(done) {
  225. const tasks = [];
  226. sources = sources || [];
  227. sources = !Array.isArray(sources) ? [sources] : sources;
  228. sources.forEach(source => {
  229. if (ShaderParser.isGLSL(source)) {
  230. tasks.push(passThroughSource(source));
  231. } else {
  232. tasks.push(loadShaderSource(source));
  233. }
  234. });
  235. parallel(tasks, done);
  236. };
  237. }
  238. /**
  239. * Injects the defines into the shader source.
  240. *
  241. * @private
  242. *
  243. * @param {Array} defines - The shader defines.
  244. *
  245. * @returns {Function} A function to resolve the shader sources.
  246. */
  247. const createDefines = function(defines = {}) {
  248. const res = [];
  249. Object.keys(defines).forEach(name => {
  250. res.push(`#define ${name} ${defines[name]}`);
  251. });
  252. return res.join('\n');
  253. };
  254. /**
  255. * Creates the shader program object from source strings. This includes:
  256. * 1) Compiling and linking the shader program.
  257. * 2) Parsing shader source for attribute and uniform information.
  258. * 3) Binding attribute locations, by order of delcaration.
  259. * 4) Querying and storing uniform location.
  260. *
  261. * @private
  262. *
  263. * @param {Shader} shader - The Shader object.
  264. * @param {Object} sources - A map containing sources under 'vert' and 'frag' attributes.
  265. *
  266. * @returns {Shader} The shader object, for chaining.
  267. */
  268. function createProgram(shader, sources) {
  269. const gl = shader.gl;
  270. const defines = createDefines(sources.define);
  271. const common = defines + (sources.common || '');
  272. const vert = sources.vert.join('');
  273. const frag = sources.frag.join('');
  274. // compile shaders
  275. const vertexShader = compileShader(gl, common + vert, 'VERTEX_SHADER');
  276. const fragmentShader = compileShader(gl, common + frag, 'FRAGMENT_SHADER');
  277. // parse source for attribute and uniforms
  278. setAttributesAndUniforms(shader, vert, frag);
  279. // create the shader program
  280. shader.program = gl.createProgram();
  281. // attach vertex and fragment shaders
  282. gl.attachShader(shader.program, vertexShader);
  283. gl.attachShader(shader.program, fragmentShader);
  284. // bind vertex attribute locations BEFORE linking
  285. bindAttributeLocations(shader);
  286. // link shader
  287. gl.linkProgram(shader.program);
  288. // If creating the shader program failed, alert
  289. if (!gl.getProgramParameter(shader.program, gl.LINK_STATUS)) {
  290. throw 'An error occured linking the shader:\n' + gl.getProgramInfoLog(shader.program);
  291. }
  292. // get shader uniform locations
  293. getUniformLocations(shader);
  294. }
  295. /**
  296. * A shader class to assist in compiling and linking webgl shaders, storing
  297. * attribute and uniform locations, and buffering uniforms.
  298. */
  299. class Shader {
  300. /**
  301. * Instantiates a Shader object.
  302. *
  303. * @param {Object} spec - The shader specification object.
  304. * @param {String|String[]|Object} spec.common - Sources / URLs to be shared by both vertex and fragment shaders.
  305. * @param {String|String[]|Object} spec.vert - The vertex shader sources / URLs.
  306. * @param {String|String[]|Object} spec.frag - The fragment shader sources / URLs.
  307. * @param {Object} spec.define - Any `#define` definitions to be injected into the glsl.
  308. * @param {String[]} spec.attributes - The attribute index orderings.
  309. * @param {Function} callback - The callback function to execute once the shader has been successfully compiled and linked.
  310. */
  311. constructor(spec = {}, callback = null) {
  312. // check source arguments
  313. if (!spec.vert) {
  314. throw 'Vertex shader argument `vert` has not been provided';
  315. }
  316. if (!spec.frag) {
  317. throw 'Fragment shader argument `frag` has not been provided';
  318. }
  319. this.program = 0;
  320. this.gl = WebGLContext.get();
  321. this.version = spec.version || '1.00';
  322. this.attributes = new Map();
  323. this.uniforms = new Map();
  324. // if attribute ordering is provided, use those indices
  325. if (spec.attributes) {
  326. spec.attributes.forEach((attr, index) => {
  327. this.attributes.set(attr, {
  328. index: index
  329. });
  330. });
  331. }
  332. // create the shader
  333. parallel({
  334. common: resolveSources(spec.common),
  335. vert: resolveSources(spec.vert),
  336. frag: resolveSources(spec.frag),
  337. }, (err, sources) => {
  338. if (err) {
  339. if (callback) {
  340. setTimeout(() => {
  341. callback(err, null);
  342. });
  343. }
  344. return;
  345. }
  346. // append defines
  347. sources.define = spec.define;
  348. // once all shader sources are loaded
  349. createProgram(this, sources);
  350. if (callback) {
  351. setTimeout(() => {
  352. callback(null, this);
  353. });
  354. }
  355. });
  356. }
  357. /**
  358. * Binds the shader program for use.
  359. *
  360. * @returns {Shader} The shader object, for chaining.
  361. */
  362. use() {
  363. // use the shader
  364. this.gl.useProgram(this.program);
  365. return this;
  366. }
  367. /**
  368. * Buffer a uniform value by name.
  369. *
  370. * @param {string} name - The uniform name in the shader source.
  371. * @param {*} value - The uniform value to buffer.
  372. *
  373. * @returns {Shader} - The shader object, for chaining.
  374. */
  375. setUniform(name, value) {
  376. const uniform = this.uniforms.get(name);
  377. // ensure that the uniform spec exists for the name
  378. if (!uniform) {
  379. throw `No uniform found under name \`${name}\``;
  380. }
  381. // check value
  382. if (value === undefined || value === null) {
  383. // ensure that the uniform argument is defined
  384. throw `Value passed for uniform \`${name}\` is undefined or null`;
  385. }
  386. // set the uniform
  387. // NOTE: checking type by string comparison is faster than wrapping
  388. // the functions.
  389. if (uniform.type === 'mat2' || uniform.type === 'mat3' || uniform.type === 'mat4') {
  390. this.gl[uniform.func](uniform.location, false, value);
  391. } else {
  392. this.gl[uniform.func](uniform.location, value);
  393. }
  394. return this;
  395. }
  396. /**
  397. * Buffer a map of uniform values.
  398. *
  399. * @param {Object} uniforms - The map of uniforms keyed by name.
  400. *
  401. * @returns {Shader} The shader object, for chaining.
  402. */
  403. setUniforms(uniforms) {
  404. Object.keys(uniforms).forEach(name => {
  405. this.setUniform(name, uniforms[name]);
  406. });
  407. return this;
  408. }
  409. }
  410. module.exports = Shader;