shaderProcessor.ts 17 KB

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