ShaderPatcher.cs 9.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207
  1. using System;
  2. using System.Linq;
  3. using System.Text;
  4. using System.Text.RegularExpressions;
  5. namespace SoftMasking.TextMeshPro.Editor {
  6. public class PatchException : Exception {
  7. public PatchException(string message) : base(message) { }
  8. }
  9. public static class ShaderPatcher {
  10. public static string Patch(string shader) {
  11. var names = Analyze(shader);
  12. var result = shader;
  13. result = UpdateShaderName(result);
  14. result = InjectProperty(result);
  15. result = InjectPragma(result);
  16. result = InjectInclude(result);
  17. result = InjectV2FFields(result, names.v2fStruct);
  18. result = InjectVertexInstructions(result, names.v2fStruct, names.v2fPositionField, names.vertShader);
  19. result = InjectFragmentInstructions(result, names.fragShader);
  20. result = FixVertexInitialization(result, names.v2fStruct);
  21. return result;
  22. }
  23. struct Names {
  24. public string vertShader;
  25. public string fragShader;
  26. public string v2fStruct;
  27. public string v2fPositionField;
  28. }
  29. static readonly Regex VERTEX_PRAMGA_PATTERN = new Regex(@"#pragma\s+vertex\s+(\w+)");
  30. static readonly Regex FRAGMENT_PRAMGA_PATTERN = new Regex(@"#pragma\s+fragment\s+(\w+)");
  31. static Names Analyze(string shader) {
  32. var vertShader =
  33. EnsureMatch(VERTEX_PRAMGA_PATTERN, shader, "Unable to find vertex shader #pragma")
  34. .Groups[1].Value;
  35. var fragShader =
  36. EnsureMatch(FRAGMENT_PRAMGA_PATTERN, shader, "Unable to find fragment shader #pragma")
  37. .Groups[1].Value;
  38. var vertReturnTypePattern = new Regex(@"(\w*)\s*" + vertShader + @"\s*\([^)]*\)");
  39. var match = EnsureMatch(vertReturnTypePattern, shader, "Unable to find V2F struct declaration");
  40. var v2fStruct = match.Groups[1].Value;
  41. var v2fPositionFieldPattern = new Regex(@"struct\s+" + v2fStruct + @"\s*\{[^}]*float\d\s*(\w+)\s*:\s*(?:SV_)?POSITION;[^}]*\}");
  42. var v2fPositionField = EnsureMatch(v2fPositionFieldPattern, shader, "Unable to determine V2F position field").Groups[1].Value;
  43. return new Names {
  44. vertShader = vertShader,
  45. fragShader = fragShader,
  46. v2fStruct = v2fStruct,
  47. v2fPositionField = v2fPositionField
  48. };
  49. }
  50. static readonly Regex SHADER_NAME_PATTERN = new Regex(@"Shader\s+""([^""]+)""");
  51. static string UpdateShaderName(string shader) {
  52. var match = EnsureMatch(SHADER_NAME_PATTERN, shader, "Unable to find shader declaration");
  53. return shader.Insert(match.Groups[1].Index, "Soft Mask/");
  54. }
  55. static readonly Regex PROPERTIES_PATTERN = new Regex(
  56. @"Properties\s*\{" +
  57. @"(?:[^{}]|(?<open>\{)|(?<-open>\}))*" + // Swallow all the content with balancing
  58. @"(?(open)(?!))" + // Match only if braces are balanced
  59. @"()$" + // Capture a point on the line end of last property
  60. @"(\s*)\}", // Capture whitespace before ending } to keep indent
  61. RegexOptions.Multiline);
  62. static string InjectProperty(string shader) {
  63. var match = EnsureMatch(PROPERTIES_PATTERN, shader, "Unable to inject Soft Mask property");
  64. var endOfLastProperty = match.Groups[1].Index;
  65. var padding = match.Groups[2].Value;
  66. return shader.Insert(endOfLastProperty,
  67. "\n" + padding + "\t_SoftMask(\"Mask\", 2D) = \"white\" {} // Soft Mask");
  68. }
  69. static readonly Regex LAST_PRAGMA_PATTERN = LastDirectivePattern("pragma");
  70. static readonly Regex LAST_INCLUDE_PATTERN = LastDirectivePattern("include");
  71. static Regex LastDirectivePattern(string directive) {
  72. return new Regex(
  73. @"^([ \t]*)#" + directive + @"[^\n]*()$",
  74. RegexOptions.Multiline);
  75. }
  76. static string InjectPragma(string shader) {
  77. var match = EnsureLastMatch(LAST_PRAGMA_PATTERN, shader, "Unable to inject Soft Mask #pragma");
  78. var padding = match.Groups[1].Value;
  79. return shader.Insert(match.Groups[2].Index,
  80. "\n" +
  81. "\n" + padding + "// Soft Mask" +
  82. "\n" + padding + "#pragma multi_compile __ SOFTMASK_SIMPLE SOFTMASK_SLICED SOFTMASK_TILED\n");
  83. }
  84. static string InjectInclude(string shader) {
  85. var match = EnsureLastMatch(LAST_INCLUDE_PATTERN, shader, "Unable to inject Soft Mask #include");
  86. var padding = match.Groups[1].Value;
  87. return shader.Insert(match.Groups[2].Index,
  88. "\n" + padding + "#include \"SoftMask.cginc\" // Soft Mask");
  89. }
  90. static readonly Regex TEXCOORD_PATTERN = new Regex(@"TEXCOORD(\d+)");
  91. static string InjectV2FFields(string shader, string v2fStructName) {
  92. var pattern = new Regex(
  93. @"struct\s+" + v2fStructName + @"\s*\{[^}]*()$(\s*)\}", // Do not balance braces -
  94. RegexOptions.Multiline); // expecting no braces inside struct
  95. var match = EnsureMatch(pattern, shader, "Unable to inject Soft Mask V2F signature");
  96. var texcoords = TEXCOORD_PATTERN.Matches(match.Value);
  97. var maxUsedTexcoord =
  98. texcoords.OfType<Match>()
  99. .Where(m => m.Success)
  100. .Max(m => int.Parse(m.Groups[1].Value));
  101. var padding = match.Groups[2].Value;
  102. return shader.Insert(match.Groups[1].Index,
  103. "\n" + padding + "\tSOFTMASK_COORDS(" + (maxUsedTexcoord + 1) + ") // Soft Mask");
  104. }
  105. static Regex VertexFunctionPattern(string v2fStruct, string function) {
  106. return FunctionPattern(v2fStruct, function);
  107. }
  108. static Regex FunctionPattern(string returnType, string function) {
  109. // See PROPERTIES_PATTERN for some explanation of this regex
  110. return new Regex(
  111. returnType + @"\s+" + function + @"\s*\(\w+\s+(\w+)\)\s*(?::\s*[a-zA-Z0-9_]+\s*)?\{" +
  112. @"(?:[^{}]|(?<open>\{)|(?<-open>\}))*" +
  113. @"(?(open)(?!))" +
  114. @"()$" +
  115. @"(\s*)\}", RegexOptions.Multiline);
  116. }
  117. static readonly Regex RETURN_PATTERN = new Regex(@"^([^\n]*)return\s+([^;]+);\s*$", RegexOptions.Multiline);
  118. static string InjectVertexInstructions(string shader, string v2fStruct, string v2fPositionField, string function) {
  119. return ModifyFunctionReturn(shader, v2fStruct, function, (varName, inputName) =>
  120. "SOFTMASK_CALCULATE_COORDS(" + varName + ", " + inputName + "." + v2fPositionField + ")"
  121. );
  122. }
  123. static string InjectFragmentInstructions(string shader, string function) {
  124. return ModifyFunctionReturn(shader, @"fixed4", function, (varName, inputName) =>
  125. varName + " *= SOFTMASK_GET_MASK(" + inputName + ");"
  126. );
  127. }
  128. static string ModifyFunctionReturn(
  129. string shader,
  130. string returnType,
  131. string function,
  132. Func<string, string, string> modify) {
  133. var functionMatch =
  134. EnsureMatch(
  135. FunctionPattern(returnType, function),
  136. shader,
  137. "Unable to locate vertex shader function");
  138. var functionBody = functionMatch.Value;
  139. var inputName = functionMatch.Groups[1].Value;
  140. var returnMatch = EnsureMatch(RETURN_PATTERN, functionBody, "Unable to find vertex shader's return statement");
  141. var variableName = returnMatch.Groups[2].Value;
  142. var padding = returnMatch.Groups[1].Value;
  143. var sb = new StringBuilder(shader);
  144. var returnStart = functionMatch.Index + returnMatch.Index;
  145. sb.Remove(returnStart, returnMatch.Length);
  146. sb.Insert(returnStart,
  147. padding + "// Soft Mask\n" +
  148. padding + returnType + " result_SoftMask = " + variableName + ";\n" +
  149. padding + modify("result_SoftMask", inputName) + "\n" +
  150. padding + "return result_SoftMask;");
  151. return sb.ToString();
  152. }
  153. static string FixVertexInitialization(string shader, string v2fStruct) {
  154. var pattern = new Regex(v2fStruct + @"\s+\w+\s*=\s*\{[^}]*()$(\s*)\}", RegexOptions.Multiline);
  155. var match = pattern.Match(shader);
  156. if (match.Success) {
  157. var padding = match.Groups[2].Value;
  158. return shader.Insert(match.Groups[1].Index,
  159. padding + "#ifdef __SOFTMASK_ENABLE" +
  160. padding + "\tfloat4(0, 0, 0, 0), // Soft Mask" +
  161. padding + "#endif");
  162. }
  163. return shader;
  164. }
  165. static Match EnsureMatch(Regex regex, string pattern, string errorMessage) {
  166. var match = regex.Match(pattern);
  167. if (!match.Success)
  168. throw new PatchException(errorMessage);
  169. return match;
  170. }
  171. static Match EnsureLastMatch(Regex regex, string pattern, string errorMessage) {
  172. Match match = null;
  173. Match lastMatch;
  174. do {
  175. lastMatch = match;
  176. match = regex.Match(pattern, lastMatch != null ? lastMatch.Index + lastMatch.Length : 0);
  177. } while (match.Success);
  178. if (lastMatch == null || !lastMatch.Success)
  179. throw new PatchException(errorMessage);
  180. return lastMatch;
  181. }
  182. }
  183. }