shaderProcessor.ts 15 KB

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