No Description
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

FunctionRegistry.cs 5.7KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133
  1. using System;
  2. using System.Collections.Generic;
  3. using UnityEditor.ShaderGraph.Internal;
  4. using UnityEngine;
  5. namespace UnityEditor.ShaderGraph
  6. {
  7. class FunctionSource
  8. {
  9. public string code;
  10. public HashSet<AbstractMaterialNode> nodes;
  11. public bool isGeneric;
  12. public int graphPrecisionFlags; // Flags<GraphPrecision>
  13. public int concretePrecisionFlags; // Flags<ConcretePrecision>
  14. }
  15. class FunctionRegistry
  16. {
  17. Dictionary<string, FunctionSource> m_Sources = new Dictionary<string, FunctionSource>();
  18. bool m_Validate = false;
  19. ShaderStringBuilder m_Builder;
  20. IncludeCollection m_Includes;
  21. public FunctionRegistry(ShaderStringBuilder builder, IncludeCollection includes, bool validate = false)
  22. {
  23. m_Builder = builder;
  24. m_Includes = includes;
  25. m_Validate = validate;
  26. }
  27. internal ShaderStringBuilder builder => m_Builder;
  28. public Dictionary<string, FunctionSource> sources => m_Sources;
  29. public void RequiresIncludes(IncludeCollection includes)
  30. {
  31. m_Includes.Add(includes);
  32. }
  33. public void RequiresIncludePath(string includePath, bool shouldIncludeWithPragmas = false)
  34. {
  35. m_Includes.Add(includePath, IncludeLocation.Graph, shouldIncludeWithPragmas);
  36. }
  37. // this list is somewhat redundant, but it preserves function declaration ordering
  38. // (i.e. when nodes add multiple functions, they require being defined in a certain order)
  39. public List<string> names { get; } = new List<string>();
  40. public void ProvideFunction(string name, GraphPrecision graphPrecision, ConcretePrecision concretePrecision, Action<ShaderStringBuilder> generator)
  41. {
  42. // appends code, construct the standalone code string
  43. var originalIndex = builder.length;
  44. builder.AppendNewLine();
  45. var startIndex = builder.length;
  46. generator(builder);
  47. var length = builder.length - startIndex;
  48. var code = builder.ToString(startIndex, length);
  49. // validate some assumptions around generics
  50. bool isGenericName = name.Contains("$");
  51. bool isGenericFunc = code.Contains("$");
  52. bool isGeneric = isGenericName || isGenericFunc;
  53. bool containsFunctionName = code.Contains(name);
  54. var curNode = builder.currentNode;
  55. if (isGenericName != isGenericFunc)
  56. curNode.owner.AddValidationError(curNode.objectId, $"Function {name} provided by node {curNode.name} contains $precision tokens in the name or the code, but not both. This is very likely an error.");
  57. if (!containsFunctionName)
  58. curNode.owner.AddValidationError(curNode.objectId, $"Function {name} provided by node {curNode.name} does not contain the name of the function. This is very likely an error.");
  59. int graphPrecisionFlag = (1 << (int)graphPrecision);
  60. int concretePrecisionFlag = (1 << (int)concretePrecision);
  61. FunctionSource existingSource;
  62. if (m_Sources.TryGetValue(name, out existingSource))
  63. {
  64. // function already provided
  65. existingSource.nodes.Add(builder.currentNode);
  66. // let's check if the requested precision variant has already been provided (or if it's not generic there are no variants)
  67. bool concretePrecisionExists = ((existingSource.concretePrecisionFlags & concretePrecisionFlag) != 0) || !isGeneric;
  68. // if this precision was already added -- remove the duplicate code from the builder
  69. if (concretePrecisionExists)
  70. builder.length -= (builder.length - originalIndex);
  71. // save the flags
  72. existingSource.graphPrecisionFlags = existingSource.graphPrecisionFlags | graphPrecisionFlag;
  73. existingSource.concretePrecisionFlags = existingSource.concretePrecisionFlags | concretePrecisionFlag;
  74. // if validate, we double check that the two function declarations are the same
  75. if (m_Validate)
  76. {
  77. if (code != existingSource.code)
  78. {
  79. var errorMessage = string.Format("Function `{0}` has conflicting implementations:{1}{1}{2}{1}{1}{3}", name, Environment.NewLine, code, existingSource.code);
  80. foreach (var n in existingSource.nodes)
  81. n.owner.AddValidationError(n.objectId, errorMessage);
  82. }
  83. }
  84. }
  85. else
  86. {
  87. var newSource = new FunctionSource
  88. {
  89. code = code,
  90. isGeneric = isGeneric,
  91. graphPrecisionFlags = graphPrecisionFlag,
  92. concretePrecisionFlags = concretePrecisionFlag,
  93. nodes = new HashSet<AbstractMaterialNode> { builder.currentNode }
  94. };
  95. m_Sources.Add(name, newSource);
  96. names.Add(name);
  97. }
  98. // fully concretize any generic code by replacing any precision tokens by the node's concrete precision
  99. if (isGeneric && (builder.length > originalIndex))
  100. {
  101. int start = originalIndex;
  102. int count = builder.length - start;
  103. builder.Replace(PrecisionUtil.Token, concretePrecision.ToShaderString(), start, count);
  104. }
  105. }
  106. public void ProvideFunction(string name, Action<ShaderStringBuilder> generator)
  107. {
  108. ProvideFunction(name, builder.currentNode.graphPrecision, builder.currentNode.concretePrecision, generator);
  109. }
  110. }
  111. }