shaderProcessor.ts 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. import { Tools } from '../../Misc/tools';
  2. import { ShaderCodeNode } from './shaderCodeNode';
  3. import { ShaderCodeCursor } from './shaderCodeCursor';
  4. import { ShaderCodeConditionNode } from './shaderCodeConditionNode';
  5. import { ShaderCodeTestNode } from './shaderCodeTestNode';
  6. import { ShaderDefineIsDefinedOperator } from './Expressions/Operators/shaderDefineIsDefinedOperator';
  7. import { ShaderDefineOrOperator } from './Expressions/Operators/shaderDefineOrOperator';
  8. import { ShaderDefineAndOperator } from './Expressions/Operators/shaderDefineAndOperator';
  9. import { ShaderDefineExpression } from './Expressions/shaderDefineExpression';
  10. import { ShaderDefineArithmeticOperator } from './Expressions/Operators/shaderDefineArithmeticOperator';
  11. import { ProcessingOptions } from './shaderProcessingOptions';
  12. /** @hidden */
  13. export class ShaderProcessor {
  14. public static Process(sourceCode: string, options: ProcessingOptions, callback: (migratedCode: string) => void) {
  15. this._ProcessIncludes(sourceCode, options, (codeWithIncludes) => {
  16. let migratedCode = this._ProcessShaderConversion(codeWithIncludes, options);
  17. callback(migratedCode);
  18. });
  19. }
  20. private static _ProcessPrecision(source: string, options: ProcessingOptions): string {
  21. const shouldUseHighPrecisionShader = options.shouldUseHighPrecisionShader;
  22. if (source.indexOf("precision highp float") === -1) {
  23. if (!shouldUseHighPrecisionShader) {
  24. source = "precision mediump float;\n" + source;
  25. } else {
  26. source = "precision highp float;\n" + source;
  27. }
  28. } else {
  29. if (!shouldUseHighPrecisionShader) { // Moving highp to mediump
  30. source = source.replace("precision highp float", "precision mediump float");
  31. }
  32. }
  33. return source;
  34. }
  35. private static _ExtractOperation(expression: string) {
  36. let regex = /defined\((.+)\)/;
  37. let match = regex.exec(expression);
  38. if (match && match.length) {
  39. return new ShaderDefineIsDefinedOperator(match[1].trim(), expression[0] === "!");
  40. }
  41. let operators = ["==", ">=", "<=", "<", ">"];
  42. let operator = "";
  43. let indexOperator = 0;
  44. for (operator of operators) {
  45. indexOperator = expression.indexOf(operator);
  46. if (indexOperator > -1) {
  47. break;
  48. }
  49. }
  50. if (indexOperator === -1) {
  51. return new ShaderDefineIsDefinedOperator(expression);
  52. }
  53. let define = expression.substring(0, indexOperator).trim();
  54. let value = expression.substring(indexOperator + operator.length).trim();
  55. return new ShaderDefineArithmeticOperator(define, operator, value);
  56. }
  57. private static _BuildSubExpression(expression: string): ShaderDefineExpression {
  58. let indexOr = expression.indexOf("||");
  59. if (indexOr === -1) {
  60. let indexAnd = expression.indexOf("&&");
  61. if (indexAnd > -1) {
  62. let andOperator = new ShaderDefineAndOperator();
  63. let leftPart = expression.substring(0, indexAnd).trim();
  64. let rightPart = expression.substring(indexAnd + 2).trim();
  65. andOperator.leftOperand = this._BuildSubExpression(leftPart);
  66. andOperator.rightOperand = this._BuildSubExpression(rightPart);
  67. return andOperator;
  68. } else {
  69. return this._ExtractOperation(expression);
  70. }
  71. } else {
  72. let orOperator = new ShaderDefineOrOperator();
  73. let leftPart = expression.substring(0, indexOr).trim();;
  74. let rightPart = expression.substring(indexOr + 2).trim();;
  75. orOperator.leftOperand = this._BuildSubExpression(leftPart);
  76. orOperator.rightOperand = this._BuildSubExpression(rightPart);
  77. return orOperator;
  78. }
  79. }
  80. private static _BuildExpression(line: string, start: number): ShaderCodeTestNode {
  81. let node = new ShaderCodeTestNode();
  82. let command = line.substring(0, start);
  83. let expression = line.substring(start).trim();
  84. if (command === "#ifdef") {
  85. node.testExpression = new ShaderDefineIsDefinedOperator(expression);
  86. } else if (command === "#ifndef") {
  87. node.testExpression = new ShaderDefineIsDefinedOperator(expression, true);
  88. } else {
  89. node.testExpression = this._BuildSubExpression(expression);
  90. }
  91. return node;
  92. }
  93. private static _MoveCursorWithinIf(cursor: ShaderCodeCursor, rootNode: ShaderCodeConditionNode, ifNode: ShaderCodeNode) {
  94. let line = cursor.currentLine;
  95. while (this._MoveCursor(cursor, ifNode)) {
  96. line = cursor.currentLine;
  97. let first5 = line.substring(0, 5).toLowerCase();
  98. if (first5 === "#else") {
  99. let elseNode = new ShaderCodeNode();
  100. rootNode.children.push(elseNode);
  101. this._MoveCursor(cursor, elseNode);
  102. return;
  103. } else if (first5 === "#elif") {
  104. let elifNode = this._BuildExpression(line, 5);
  105. rootNode.children.push(elifNode);
  106. ifNode = elifNode;
  107. }
  108. }
  109. }
  110. private static _MoveCursor(cursor: ShaderCodeCursor, rootNode: ShaderCodeNode): boolean {
  111. while (cursor.canRead) {
  112. cursor.lineIndex++;
  113. let line = cursor.currentLine;
  114. const keywords = /(#ifdef)|(#else)|(#elif)|(#endif)|(#ifndef)|(#if)/
  115. const matches = keywords.exec(line);
  116. if (matches && matches.length) {
  117. let keyword = matches[0];
  118. switch (keyword) {
  119. case "#ifdef": {
  120. let newRootNode = new ShaderCodeConditionNode();
  121. rootNode.children.push(newRootNode);
  122. let ifNode = this._BuildExpression(line, 6);
  123. newRootNode.children.push(ifNode);
  124. this._MoveCursorWithinIf(cursor, newRootNode, ifNode);
  125. break;
  126. }
  127. case "#else":
  128. case "#elif":
  129. return true;
  130. case "#endif":
  131. return false;
  132. case "#ifndef": {
  133. let newRootNode = new ShaderCodeConditionNode();
  134. rootNode.children.push(newRootNode);
  135. let ifNode = this._BuildExpression(line, 7);
  136. newRootNode.children.push(ifNode);
  137. this._MoveCursorWithinIf(cursor, newRootNode, ifNode);
  138. break;
  139. }
  140. case "#if": {
  141. let newRootNode = new ShaderCodeConditionNode();
  142. let ifNode = this._BuildExpression(line, 3);
  143. rootNode.children.push(newRootNode);
  144. newRootNode.children.push(ifNode);
  145. this._MoveCursorWithinIf(cursor, newRootNode, ifNode);
  146. break;
  147. }
  148. }
  149. }
  150. else {
  151. let newNode = new ShaderCodeNode();
  152. newNode.line = line;
  153. rootNode.children.push(newNode);
  154. // Detect additional defines
  155. if (line[0] === "#" && line[1] === "d") {
  156. let split = line.replace(";", "").split(" ");
  157. newNode.additionalDefineKey = split[1];
  158. if (split.length === 3) {
  159. newNode.additionalDefineValue = split[2];
  160. }
  161. }
  162. }
  163. }
  164. return false;
  165. }
  166. private static _EvaluatePreProcessors(sourceCode: string, preprocessors: { [key: string]: string }, options: ProcessingOptions): string {
  167. const rootNode = new ShaderCodeNode();
  168. let cursor = new ShaderCodeCursor();
  169. cursor.lineIndex = -1;
  170. cursor.lines = sourceCode.split("\n");
  171. // Decompose
  172. this._MoveCursor(cursor, rootNode);
  173. // Recompose
  174. return rootNode.process(preprocessors, options);
  175. }
  176. private static _ProcessShaderConversion(sourceCode: string, options: ProcessingOptions): string {
  177. var preparedSourceCode = this._ProcessPrecision(sourceCode, options);
  178. if (!options.processor) {
  179. return preparedSourceCode;
  180. }
  181. // Already converted
  182. if (preparedSourceCode.indexOf("#version 3") !== -1) {
  183. return preparedSourceCode.replace("#version 300 es", "");
  184. }
  185. let defines = options.defines.split("\n");
  186. let preprocessors: { [key: string]: string } = {};
  187. for (var define of defines) {
  188. let keyValue = define.replace("#define", "").replace(";", "").trim();
  189. let split = keyValue.split(" ");
  190. preprocessors[split[0]] = split.length > 1 ? split[1] : "";
  191. }
  192. preprocessors["GL_ES"] = "true";
  193. preprocessors["__VERSION__"] = options.version;
  194. if (options.processor.preProcessor) {
  195. preparedSourceCode = options.processor.preProcessor(preparedSourceCode, defines, options.isFragment);
  196. }
  197. preparedSourceCode = this._EvaluatePreProcessors(preparedSourceCode, preprocessors, options);
  198. // Add multiview setup to top of file when defined
  199. if (options.processor.postProcessor) {
  200. preparedSourceCode = options.processor.postProcessor(preparedSourceCode, defines, options.isFragment);
  201. }
  202. return preparedSourceCode;
  203. }
  204. private static _ProcessIncludes(sourceCode: string, options: ProcessingOptions, callback: (data: any) => void): void {
  205. var regex = /#include<(.+)>(\((.*)\))*(\[(.*)\])*/g;
  206. var match = regex.exec(sourceCode);
  207. var returnValue = new String(sourceCode);
  208. while (match != null) {
  209. var includeFile = match[1];
  210. // Uniform declaration
  211. if (includeFile.indexOf("__decl__") !== -1) {
  212. includeFile = includeFile.replace(/__decl__/, "");
  213. if (options.supportsUniformBuffers) {
  214. includeFile = includeFile.replace(/Vertex/, "Ubo");
  215. includeFile = includeFile.replace(/Fragment/, "Ubo");
  216. }
  217. includeFile = includeFile + "Declaration";
  218. }
  219. if (options.includesShadersStore[includeFile]) {
  220. // Substitution
  221. var includeContent = options.includesShadersStore[includeFile];
  222. if (match[2]) {
  223. var splits = match[3].split(",");
  224. for (var index = 0; index < splits.length; index += 2) {
  225. var source = new RegExp(splits[index], "g");
  226. var dest = splits[index + 1];
  227. includeContent = includeContent.replace(source, dest);
  228. }
  229. }
  230. if (match[4]) {
  231. var indexString = match[5];
  232. if (indexString.indexOf("..") !== -1) {
  233. var indexSplits = indexString.split("..");
  234. var minIndex = parseInt(indexSplits[0]);
  235. var maxIndex = parseInt(indexSplits[1]);
  236. var sourceIncludeContent = includeContent.slice(0);
  237. includeContent = "";
  238. if (isNaN(maxIndex)) {
  239. maxIndex = options.indexParameters[indexSplits[1]];
  240. }
  241. for (var i = minIndex; i < maxIndex; i++) {
  242. if (!options.supportsUniformBuffers) {
  243. // Ubo replacement
  244. sourceIncludeContent = sourceIncludeContent.replace(/light\{X\}.(\w*)/g, (str: string, p1: string) => {
  245. return p1 + "{X}";
  246. });
  247. }
  248. includeContent += sourceIncludeContent.replace(/\{X\}/g, i.toString()) + "\n";
  249. }
  250. } else {
  251. if (!options.supportsUniformBuffers) {
  252. // Ubo replacement
  253. includeContent = includeContent.replace(/light\{X\}.(\w*)/g, (str: string, p1: string) => {
  254. return p1 + "{X}";
  255. });
  256. }
  257. includeContent = includeContent.replace(/\{X\}/g, indexString);
  258. }
  259. }
  260. // Replace
  261. returnValue = returnValue.replace(match[0], includeContent);
  262. } else {
  263. var includeShaderUrl = options.shadersRepository + "ShadersInclude/" + includeFile + ".fx";
  264. Tools.LoadFile(includeShaderUrl, (fileContent) => {
  265. options.includesShadersStore[includeFile] = fileContent as string;
  266. this._ProcessIncludes(<string>returnValue, options, callback);
  267. });
  268. return;
  269. }
  270. match = regex.exec(sourceCode);
  271. }
  272. callback(returnValue);
  273. }
  274. }