webgpuShaderProcessors.ts 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. import { Nullable } from '../../types';
  2. import { IShaderProcessor } from '../Processors/iShaderProcessor';
  3. import { ShaderProcessingContext } from "../processors/shaderProcessingOptions";
  4. import { WebGPUShaderProcessingContext } from './webgpuShaderProcessingContext';
  5. const _knownUBOs: { [key: string]: { setIndex: number, bindingIndex: number} } = {
  6. "Scene": { setIndex: 0, bindingIndex: 0 },
  7. "Light0": { setIndex: 0, bindingIndex: 5 },
  8. "Light1": { setIndex: 0, bindingIndex: 6 },
  9. "Light2": { setIndex: 0, bindingIndex: 7 },
  10. "Light3": { setIndex: 0, bindingIndex: 8 },
  11. "Material": { setIndex: 1, bindingIndex: 0 },
  12. "Mesh": { setIndex: 1, bindingIndex: 1 },
  13. };
  14. const _knownSamplers: { [key: string]: { setIndex: number, bindingIndex: number} } = {
  15. "environmentBrdfSampler": { setIndex: 0, bindingIndex: 1 },
  16. // "reflectionSampler": { setIndex: 0, bindingIndex: 3 },
  17. };
  18. // TODO WEBGPU. sampler3D
  19. const _samplerFunctionByWebGLSamplerType: { [key: string]: string } = {
  20. "textureCube": "samplerCube",
  21. "texture2D": "sampler2D",
  22. "sampler2D": "sampler2D",
  23. "samplerCube": "samplerCube"
  24. };
  25. const _textureTypeByWebGLSamplerType: { [key: string]: string } = {
  26. "textureCube": "textureCube",
  27. "texture2D": "texture2D",
  28. "sampler2D": "texture2D",
  29. "samplerCube": "textureCube"
  30. };
  31. /** @hidden */
  32. export class WebGPUShaderProcessor implements IShaderProcessor {
  33. public varyingProcessor(varying: string, isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) {
  34. const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
  35. const varyingRegex = new RegExp(/\s*varying\s+(\S+)\s+(\S+)\s*;/gm);
  36. const match = varyingRegex.exec(varying);
  37. if (match != null) {
  38. const name = match[2];
  39. let location: number;
  40. if (isFragment) {
  41. location = webgpuProcessingContext.availableVaryings[name];
  42. }
  43. else {
  44. location = webgpuProcessingContext.varyingNextLocation++;
  45. webgpuProcessingContext.availableVaryings[name] = location;
  46. }
  47. varying = varying.replace(match[0], `layout(location = ${location}) ${isFragment ? "in" : "out"} ${match[1]} ${name};`);
  48. }
  49. return varying;
  50. }
  51. public attributeProcessor(attribute: string, processingContext: Nullable<ShaderProcessingContext>) {
  52. const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
  53. const attribRegex = new RegExp(/\s*attribute\s+(\S+)\s+(\S+)\s*;/gm);
  54. const match = attribRegex.exec(attribute);
  55. if (match != null) {
  56. const name = match[2];
  57. const location = webgpuProcessingContext.attributeNextLocation++;
  58. webgpuProcessingContext.availableAttributes[name] = location;
  59. webgpuProcessingContext.orderedAttributes[location] = name;
  60. attribute = attribute.replace(match[0], `layout(location = ${location}) in ${match[1]} ${name};`);
  61. }
  62. return attribute;
  63. }
  64. public uniformProcessor(uniform: string, isFragment: boolean, preProcessors: { [key: string]: string }, processingContext: Nullable<ShaderProcessingContext>): string {
  65. const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
  66. const uniformRegex = new RegExp(/\s*uniform\s+(\S+)\s+(\S+)\s*;/gm);
  67. const match = uniformRegex.exec(uniform);
  68. if (match != null) {
  69. const uniformType = match[1];
  70. let name = match[2];
  71. // TODO WEBGPU. Ensures it does not conflict with some of today's used construct for shadows.
  72. if (uniformType.indexOf("texture") === 0 || uniformType.indexOf("sampler") === 0) {
  73. let samplerInfo = _knownSamplers[name];
  74. if (!samplerInfo) {
  75. samplerInfo = webgpuProcessingContext.getNextFreeTextureBinding();
  76. }
  77. const setIndex = samplerInfo.setIndex;
  78. const textureBindingIndex = samplerInfo.bindingIndex;
  79. const samplerBindingIndex = samplerInfo.bindingIndex + 1;
  80. const samplerFunction = _samplerFunctionByWebGLSamplerType[uniformType];
  81. const textureType = _textureTypeByWebGLSamplerType[uniformType];
  82. // Manage textures and samplers.
  83. uniform = `layout(set = ${setIndex}, binding = ${textureBindingIndex}) uniform ${textureType} ${name}Texture;
  84. layout(set = ${setIndex}, binding = ${samplerBindingIndex}) uniform sampler ${name}Sampler;
  85. #define ${name} ${samplerFunction}(${name}Texture, ${name}Sampler)`;
  86. webgpuProcessingContext.availableSamplers[name] = samplerInfo;
  87. if (!webgpuProcessingContext.orderedUBOsAndSamplers[setIndex]) {
  88. webgpuProcessingContext.orderedUBOsAndSamplers[setIndex] = [];
  89. }
  90. webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][textureBindingIndex] = { isSampler: true, name };
  91. }
  92. else {
  93. // Check the size of the uniform array in case of array.
  94. let length = 0;
  95. const startArray = name.indexOf("[");
  96. const endArray = name.indexOf("]");
  97. if (startArray > 0 && endArray > 0) {
  98. const lengthInString = name.substring(startArray + 1, endArray);
  99. length = +(lengthInString);
  100. if (isNaN(length)) {
  101. length = +(preProcessors[lengthInString]);
  102. }
  103. name = name.substr(0, startArray);
  104. }
  105. webgpuProcessingContext.leftOverUniforms.push({
  106. name,
  107. type: uniformType,
  108. length
  109. });
  110. uniform = "";
  111. }
  112. }
  113. return uniform;
  114. }
  115. public uniformBufferProcessor(uniformBuffer: string, isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>): string {
  116. const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
  117. const uboRegex = new RegExp(/uniform\s+(\w+)/gm);
  118. const match = uboRegex.exec(uniformBuffer);
  119. if (match != null) {
  120. const name = match[1];
  121. let setIndex: number;
  122. let bindingIndex: number;
  123. const knownUBO = _knownUBOs[name];
  124. if (knownUBO) {
  125. setIndex = knownUBO.setIndex;
  126. bindingIndex = knownUBO.bindingIndex;
  127. }
  128. else {
  129. if (isFragment) {
  130. const availableUBO = webgpuProcessingContext.availableUBOs[name];
  131. if (availableUBO) {
  132. setIndex = availableUBO.setIndex;
  133. bindingIndex = availableUBO.bindingIndex;
  134. }
  135. else {
  136. const nextBinding = webgpuProcessingContext.getNextFreeUBOBinding();
  137. setIndex = nextBinding.setIndex;
  138. bindingIndex = nextBinding.bindingIndex;
  139. }
  140. }
  141. else {
  142. const nextBinding = webgpuProcessingContext.getNextFreeUBOBinding();
  143. setIndex = nextBinding.setIndex;
  144. bindingIndex = nextBinding.bindingIndex;
  145. }
  146. }
  147. webgpuProcessingContext.availableUBOs[name] = { setIndex, bindingIndex };
  148. if (!webgpuProcessingContext.orderedUBOsAndSamplers[setIndex]) {
  149. webgpuProcessingContext.orderedUBOsAndSamplers[setIndex] = [];
  150. }
  151. webgpuProcessingContext.orderedUBOsAndSamplers[setIndex][bindingIndex] = { isSampler: false, name };
  152. uniformBuffer = uniformBuffer.replace("uniform", `layout(set = ${setIndex}, binding = ${bindingIndex}) uniform`);
  153. }
  154. return uniformBuffer;
  155. }
  156. // public endOfUniformBufferProcessor(closingBracketLine: string, isFragment: boolean): string {
  157. // console.log("uniformBuffer closingBracketLine ", closingBracketLine);
  158. // return closingBracketLine;
  159. // }
  160. public postProcessor(code: string, defines: string[], isFragment: boolean, processingContext: Nullable<ShaderProcessingContext>) {
  161. const hasDrawBuffersExtension = code.search(/#extension.+GL_EXT_draw_buffers.+require/) !== -1;
  162. // Remove extensions
  163. var regex = /#extension.+(GL_OVR_multiview2|GL_OES_standard_derivatives|GL_EXT_shader_texture_lod|GL_EXT_frag_depth|GL_EXT_draw_buffers).+(enable|require)/g;
  164. code = code.replace(regex, "");
  165. // Replace instructions
  166. code = code.replace(/texture2D\s*\(/g, "texture(");
  167. if (isFragment) {
  168. code = code.replace(/texture2DLodEXT\s*\(/g, "textureLod(");
  169. code = code.replace(/textureCubeLodEXT\s*\(/g, "textureLod(");
  170. code = code.replace(/textureCube\s*\(/g, "texture(");
  171. code = code.replace(/gl_FragDepthEXT/g, "gl_FragDepth");
  172. code = code.replace(/gl_FragColor/g, "glFragColor");
  173. code = code.replace(/gl_FragData/g, "glFragData");
  174. code = code.replace(/void\s+?main\s*\(/g, (hasDrawBuffersExtension ? "" : "layout(location = 0) out vec4 glFragColor;\n") + "void main(");
  175. } else {
  176. var hasMultiviewExtension = defines.indexOf("#define MULTIVIEW") !== -1;
  177. if (hasMultiviewExtension) {
  178. return "#extension GL_OVR_multiview2 : require\nlayout (num_views = 2) in;\n" + code;
  179. }
  180. }
  181. // Flip Y.
  182. // TODO WEBGPU. Triple check this part and wait on Google News for this issue.
  183. // https://github.com/gpuweb/gpuweb/issues/379
  184. if (!isFragment) {
  185. const lastClosingCurly = code.lastIndexOf("}");
  186. code = code.substring(0, lastClosingCurly);
  187. code += "gl_Position.y *= -1.; }";
  188. }
  189. return code;
  190. }
  191. public finalizeShaders(vertexCode: string, fragmentCode: string, processingContext: Nullable<ShaderProcessingContext>): { vertexCode: string, fragmentCode: string } {
  192. // Builds the leftover UBOs.
  193. const webgpuProcessingContext = processingContext! as WebGPUShaderProcessingContext;
  194. if (webgpuProcessingContext.leftOverUniforms.length) {
  195. const name = "LeftOver";
  196. let availableUBO = webgpuProcessingContext.availableUBOs[name];
  197. if (!availableUBO) {
  198. availableUBO = webgpuProcessingContext.getNextFreeUBOBinding();
  199. webgpuProcessingContext.availableUBOs[name] = availableUBO;
  200. if (!webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex]) {
  201. webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex] = [];
  202. }
  203. webgpuProcessingContext.orderedUBOsAndSamplers[availableUBO.setIndex][availableUBO.bindingIndex] = { isSampler: false, name };
  204. }
  205. let ubo = `layout(set = ${availableUBO.setIndex}, binding = ${availableUBO.bindingIndex}) uniform ${name} {\n `;
  206. for (let leftOverUniform of webgpuProcessingContext.leftOverUniforms) {
  207. if (leftOverUniform.length > 0) {
  208. ubo += ` ${leftOverUniform.type} ${leftOverUniform.name}[${leftOverUniform.length}];\n`;
  209. }
  210. else {
  211. ubo += ` ${leftOverUniform.type} ${leftOverUniform.name};\n`;
  212. }
  213. }
  214. ubo += "};\n\n";
  215. // Currently set in both vert and frag but could be optim away if necessary.
  216. vertexCode = ubo + vertexCode;
  217. fragmentCode = ubo + fragmentCode;
  218. }
  219. return { vertexCode, fragmentCode };
  220. }
  221. }