public void GetKeywordsDeclaration(ShaderStringBuilder builder, GenerationMode mode) { if (keywords.Count == 0) { return; } // Declare keywords foreach (var keyword in keywords) { // Hardcode active keywords in preview to reduce compiled variants if (mode == GenerationMode.Preview) { string declaration = keyword.GetKeywordPreviewDeclarationString(); if (!string.IsNullOrEmpty(declaration)) { builder.AppendLine(declaration); } } else { string declaration = keyword.GetKeywordDeclarationString(); if (!string.IsNullOrEmpty(declaration)) { builder.AppendLine(declaration); } } } // Declare another keyword per permutation for simpler if/defs in the graph code builder.AppendNewLine(); KeywordUtil.GetKeywordPermutationDeclarations(builder, permutations); builder.AppendNewLine(); }
static void GenerateDescriptionForNode( AbstractMaterialNode activeNode, List <int> keywordPermutations, FunctionRegistry functionRegistry, ShaderStringBuilder descriptionFunction, PropertyCollector shaderProperties, KeywordCollector shaderKeywords, GraphData graph, GenerationMode mode) { if (activeNode is IGeneratesFunction functionNode) { functionRegistry.builder.currentNode = activeNode; functionNode.GenerateNodeFunction(functionRegistry, mode); functionRegistry.builder.ReplaceInCurrentMapping(PrecisionUtil.Token, activeNode.concretePrecision.ToShaderString()); } if (activeNode is IGeneratesBodyCode bodyNode) { if (keywordPermutations != null) { descriptionFunction.AppendLine(KeywordUtil.GetKeywordPermutationSetConditional(keywordPermutations)); } descriptionFunction.currentNode = activeNode; bodyNode.GenerateNodeCode(descriptionFunction, mode); descriptionFunction.ReplaceInCurrentMapping(PrecisionUtil.Token, activeNode.concretePrecision.ToShaderString()); if (keywordPermutations != null) { descriptionFunction.AppendLine("#endif"); } } activeNode.CollectShaderProperties(shaderProperties, mode); if (activeNode is SubGraphNode subGraphNode) { subGraphNode.CollectShaderKeywords(shaderKeywords, mode); } }
public static void BuildType(System.Type t, ActiveFields activeFields, ShaderGenerator result, bool isDebug) { result.AddShaderChunk("struct " + t.Name); result.AddShaderChunk("{"); result.Indent(); foreach (FieldInfo field in t.GetFields(BindingFlags.Instance | BindingFlags.NonPublic | BindingFlags.Public)) { if (field.MemberType == MemberTypes.Field) { bool isOptional = false; var fieldIsActive = false; var keywordIfdefs = string.Empty; if (activeFields.permutationCount > 0) { // Evaluate all activeFields instance var instances = activeFields .allPermutations.instances .Where(i => ShouldSpliceField(t, field, i, out isOptional)) .ToList(); fieldIsActive = instances.Count > 0; if (fieldIsActive) { keywordIfdefs = KeywordUtil.GetKeywordPermutationSetConditional(instances .Select(i => i.permutationIndex).ToList()); } } else { fieldIsActive = ShouldSpliceField(t, field, activeFields.baseInstance, out isOptional); } if (fieldIsActive) { // The field is used, so generate it var semanticString = GetFieldSemantic(field); int componentCount; var fieldType = GetFieldType(field, out componentCount); var conditional = GetFieldConditional(field); if (conditional != null) { result.AddShaderChunk("#if " + conditional); } if (!string.IsNullOrEmpty(keywordIfdefs)) { result.AddShaderChunk(keywordIfdefs); } var fieldDecl = fieldType + " " + field.Name + semanticString + ";" + (isOptional & isDebug ? " // optional" : string.Empty); result.AddShaderChunk(fieldDecl); if (!string.IsNullOrEmpty(keywordIfdefs)) { result.AddShaderChunk("#endif" + (isDebug ? " // Shader Graph Keywords" : string.Empty)); } if (conditional != null) { result.AddShaderChunk("#endif" + (isDebug ? $" // {conditional}" : string.Empty)); } } } } result.Deindent(); result.AddShaderChunk("};"); object[] packAttributes = t.GetCustomAttributes(typeof(InterpolatorPack), false); if (packAttributes.Length > 0) { result.AddNewLine(); if (activeFields.permutationCount > 0) { var generatedPackedTypes = new Dictionary <string, (ShaderGenerator, List <int>)>(); foreach (var instance in activeFields.allPermutations.instances) { var instanceGenerator = new ShaderGenerator(); BuildPackedType(t, instance, instanceGenerator, isDebug); var key = instanceGenerator.GetShaderString(0); if (generatedPackedTypes.TryGetValue(key, out var value)) { value.Item2.Add(instance.permutationIndex); } else { generatedPackedTypes.Add(key, (instanceGenerator, new List <int> { instance.permutationIndex }));
public static bool GenerateShaderPass(AbstractMaterialNode masterNode, ShaderPass pass, GenerationMode mode, ActiveFields activeFields, ShaderGenerator result, List <string> sourceAssetDependencyPaths, List <Dependency[]> dependencies, string resourceClassName, string assemblyName) { // -------------------------------------------------- // Debug // Get scripting symbols BuildTargetGroup buildTargetGroup = EditorUserBuildSettings.selectedBuildTargetGroup; string defines = PlayerSettings.GetScriptingDefineSymbolsForGroup(buildTargetGroup); bool isDebug = defines.Contains(kDebugSymbol); // -------------------------------------------------- // Setup // Initiailize Collectors var propertyCollector = new PropertyCollector(); var keywordCollector = new KeywordCollector(); masterNode.owner.CollectShaderKeywords(keywordCollector, mode); // Get upstream nodes from ShaderPass port mask List <AbstractMaterialNode> vertexNodes; List <AbstractMaterialNode> pixelNodes; GetUpstreamNodesForShaderPass(masterNode, pass, out vertexNodes, out pixelNodes); // Track permutation indices for all nodes List <int>[] vertexNodePermutations = new List <int> [vertexNodes.Count]; List <int>[] pixelNodePermutations = new List <int> [pixelNodes.Count]; // Get active fields from upstream Node requirements ShaderGraphRequirementsPerKeyword graphRequirements; GetActiveFieldsAndPermutationsForNodes(masterNode, pass, keywordCollector, vertexNodes, pixelNodes, vertexNodePermutations, pixelNodePermutations, activeFields, out graphRequirements); // GET CUSTOM ACTIVE FIELDS HERE! // Get active fields from ShaderPass AddRequiredFields(pass.requiredAttributes, activeFields.baseInstance); AddRequiredFields(pass.requiredVaryings, activeFields.baseInstance); // Get Port references from ShaderPass var pixelSlots = FindMaterialSlotsOnNode(pass.pixelPorts, masterNode); var vertexSlots = FindMaterialSlotsOnNode(pass.vertexPorts, masterNode); // Function Registry var functionBuilder = new ShaderStringBuilder(); var functionRegistry = new FunctionRegistry(functionBuilder); // Hash table of named $splice(name) commands // Key: splice token // Value: string to splice Dictionary <string, string> spliceCommands = new Dictionary <string, string>(); // -------------------------------------------------- // Dependencies // Propagate active field requirements using dependencies // Must be executed before types are built foreach (var instance in activeFields.all.instances) { ShaderSpliceUtil.ApplyDependencies(instance, dependencies); } // -------------------------------------------------- // Pass Setup // Name if (!string.IsNullOrEmpty(pass.displayName)) { spliceCommands.Add("PassName", $"Name \"{pass.displayName}\""); } else { spliceCommands.Add("PassName", "// Name: <None>"); } // Tags if (!string.IsNullOrEmpty(pass.lightMode)) { spliceCommands.Add("LightMode", $"\"LightMode\" = \"{pass.lightMode}\""); } else { spliceCommands.Add("LightMode", "// LightMode: <None>"); } // Render state BuildRenderStatesFromPass(pass, ref spliceCommands); // -------------------------------------------------- // Pass Code // Pragmas using (var passPragmaBuilder = new ShaderStringBuilder()) { if (pass.pragmas != null) { foreach (string pragma in pass.pragmas) { passPragmaBuilder.AppendLine($"#pragma {pragma}"); } } if (passPragmaBuilder.length == 0) { passPragmaBuilder.AppendLine("// PassPragmas: <None>"); } spliceCommands.Add("PassPragmas", passPragmaBuilder.ToCodeBlack()); } // Includes using (var passIncludeBuilder = new ShaderStringBuilder()) { if (pass.includes != null) { foreach (string include in pass.includes) { passIncludeBuilder.AppendLine($"#include \"{include}\""); } } if (passIncludeBuilder.length == 0) { passIncludeBuilder.AppendLine("// PassIncludes: <None>"); } spliceCommands.Add("PassIncludes", passIncludeBuilder.ToCodeBlack()); } // Keywords using (var passKeywordBuilder = new ShaderStringBuilder()) { if (pass.keywords != null) { foreach (KeywordDescriptor keyword in pass.keywords) { passKeywordBuilder.AppendLine(keyword.ToDeclarationString()); } } if (passKeywordBuilder.length == 0) { passKeywordBuilder.AppendLine("// PassKeywords: <None>"); } spliceCommands.Add("PassKeywords", passKeywordBuilder.ToCodeBlack()); } // -------------------------------------------------- // Graph Vertex var vertexBuilder = new ShaderStringBuilder(); // If vertex modification enabled if (activeFields.baseInstance.Contains("features.graphVertex")) { // Setup string vertexGraphInputName = "VertexDescriptionInputs"; string vertexGraphOutputName = "VertexDescription"; string vertexGraphFunctionName = "VertexDescriptionFunction"; var vertexGraphInputGenerator = new ShaderGenerator(); var vertexGraphFunctionBuilder = new ShaderStringBuilder(); var vertexGraphOutputBuilder = new ShaderStringBuilder(); // Build vertex graph inputs ShaderSpliceUtil.BuildType(GetTypeForStruct("VertexDescriptionInputs", resourceClassName, assemblyName), activeFields, vertexGraphInputGenerator, isDebug); // Build vertex graph outputs // Add struct fields to active fields SubShaderGenerator.GenerateVertexDescriptionStruct(vertexGraphOutputBuilder, vertexSlots, vertexGraphOutputName, activeFields.baseInstance); // Build vertex graph functions from ShaderPass vertex port mask SubShaderGenerator.GenerateVertexDescriptionFunction( masterNode.owner as GraphData, vertexGraphFunctionBuilder, functionRegistry, propertyCollector, keywordCollector, mode, masterNode, vertexNodes, vertexNodePermutations, vertexSlots, vertexGraphInputName, vertexGraphFunctionName, vertexGraphOutputName); // Generate final shader strings vertexBuilder.AppendLines(vertexGraphInputGenerator.GetShaderString(0, false)); vertexBuilder.AppendNewLine(); vertexBuilder.AppendLines(vertexGraphOutputBuilder.ToString()); vertexBuilder.AppendNewLine(); vertexBuilder.AppendLines(vertexGraphFunctionBuilder.ToString()); } // Add to splice commands if (vertexBuilder.length == 0) { vertexBuilder.AppendLine("// GraphVertex: <None>"); } spliceCommands.Add("GraphVertex", vertexBuilder.ToCodeBlack()); // -------------------------------------------------- // Graph Pixel // Setup string pixelGraphInputName = "SurfaceDescriptionInputs"; string pixelGraphOutputName = "SurfaceDescription"; string pixelGraphFunctionName = "SurfaceDescriptionFunction"; var pixelGraphInputGenerator = new ShaderGenerator(); var pixelGraphOutputBuilder = new ShaderStringBuilder(); var pixelGraphFunctionBuilder = new ShaderStringBuilder(); // Build pixel graph inputs ShaderSpliceUtil.BuildType(GetTypeForStruct("SurfaceDescriptionInputs", resourceClassName, assemblyName), activeFields, pixelGraphInputGenerator, isDebug); // Build pixel graph outputs // Add struct fields to active fields SubShaderGenerator.GenerateSurfaceDescriptionStruct(pixelGraphOutputBuilder, pixelSlots, pixelGraphOutputName, activeFields.baseInstance); // Build pixel graph functions from ShaderPass pixel port mask SubShaderGenerator.GenerateSurfaceDescriptionFunction( pixelNodes, pixelNodePermutations, masterNode, masterNode.owner as GraphData, pixelGraphFunctionBuilder, functionRegistry, propertyCollector, keywordCollector, mode, pixelGraphFunctionName, pixelGraphOutputName, null, pixelSlots, pixelGraphInputName); using (var pixelBuilder = new ShaderStringBuilder()) { // Generate final shader strings pixelBuilder.AppendLines(pixelGraphInputGenerator.GetShaderString(0, false)); pixelBuilder.AppendNewLine(); pixelBuilder.AppendLines(pixelGraphOutputBuilder.ToString()); pixelBuilder.AppendNewLine(); pixelBuilder.AppendLines(pixelGraphFunctionBuilder.ToString()); // Add to splice commands if (pixelBuilder.length == 0) { pixelBuilder.AppendLine("// GraphPixel: <None>"); } spliceCommands.Add("GraphPixel", pixelBuilder.ToCodeBlack()); } // -------------------------------------------------- // Graph Functions if (functionBuilder.length == 0) { functionBuilder.AppendLine("// GraphFunctions: <None>"); } spliceCommands.Add("GraphFunctions", functionBuilder.ToCodeBlack()); // -------------------------------------------------- // Graph Keywords using (var keywordBuilder = new ShaderStringBuilder()) { keywordCollector.GetKeywordsDeclaration(keywordBuilder, mode); if (keywordBuilder.length == 0) { keywordBuilder.AppendLine("// GraphKeywords: <None>"); } spliceCommands.Add("GraphKeywords", keywordBuilder.ToCodeBlack()); } // -------------------------------------------------- // Graph Properties using (var propertyBuilder = new ShaderStringBuilder()) { propertyCollector.GetPropertiesDeclaration(propertyBuilder, mode, masterNode.owner.concretePrecision); if (propertyBuilder.length == 0) { propertyBuilder.AppendLine("// GraphProperties: <None>"); } spliceCommands.Add("GraphProperties", propertyBuilder.ToCodeBlack()); } // -------------------------------------------------- // Graph Defines using (var graphDefines = new ShaderStringBuilder()) { graphDefines.AppendLine("#define {0}", pass.referenceName); if (graphRequirements.permutationCount > 0) { List <int> activePermutationIndices; // Depth Texture activePermutationIndices = graphRequirements.allPermutations.instances .Where(p => p.requirements.requiresDepthTexture) .Select(p => p.permutationIndex) .ToList(); if (activePermutationIndices.Count > 0) { graphDefines.AppendLine(KeywordUtil.GetKeywordPermutationSetConditional(activePermutationIndices)); graphDefines.AppendLine("#define REQUIRE_DEPTH_TEXTURE"); graphDefines.AppendLine("#endif"); } // Opaque Texture activePermutationIndices = graphRequirements.allPermutations.instances .Where(p => p.requirements.requiresCameraOpaqueTexture) .Select(p => p.permutationIndex) .ToList(); if (activePermutationIndices.Count > 0) { graphDefines.AppendLine(KeywordUtil.GetKeywordPermutationSetConditional(activePermutationIndices)); graphDefines.AppendLine("#define REQUIRE_OPAQUE_TEXTURE"); graphDefines.AppendLine("#endif"); } } else { // Depth Texture if (graphRequirements.baseInstance.requirements.requiresDepthTexture) { graphDefines.AppendLine("#define REQUIRE_DEPTH_TEXTURE"); } // Opaque Texture if (graphRequirements.baseInstance.requirements.requiresCameraOpaqueTexture) { graphDefines.AppendLine("#define REQUIRE_OPAQUE_TEXTURE"); } } // Add to splice commands spliceCommands.Add("GraphDefines", graphDefines.ToCodeBlack()); } // -------------------------------------------------- // Main // Main include is expected to contain vert/frag definitions for the pass // This must be defined after all graph code using (var mainBuilder = new ShaderStringBuilder()) { mainBuilder.AppendLine($"#include \"{pass.varyingsInclude}\""); mainBuilder.AppendLine($"#include \"{pass.passInclude}\""); // Add to splice commands spliceCommands.Add("MainInclude", mainBuilder.ToCodeBlack()); } // -------------------------------------------------- // Debug // Debug output all active fields using (var debugBuilder = new ShaderStringBuilder()) { if (isDebug) { // Active fields debugBuilder.AppendLine("// ACTIVE FIELDS:"); foreach (string field in activeFields.baseInstance.fields) { debugBuilder.AppendLine("// " + field); } } if (debugBuilder.length == 0) { debugBuilder.AppendLine("// <None>"); } // Add to splice commands spliceCommands.Add("Debug", debugBuilder.ToCodeBlack()); } // -------------------------------------------------- // Finalize // Get Template string templateLocation = GetTemplatePath("PassMesh.template"); if (!File.Exists(templateLocation)) { return(false); } // Get Template preprocessor string templatePath = "Assets/com.unity.render-pipelines.universal/Editor/ShaderGraph/Templates"; var templatePreprocessor = new ShaderSpliceUtil.TemplatePreprocessor(activeFields, spliceCommands, isDebug, templatePath, sourceAssetDependencyPaths, assemblyName, resourceClassName); // Process Template templatePreprocessor.ProcessTemplateFile(templateLocation); result.AddShaderChunk(templatePreprocessor.GetShaderCode().ToString(), false); return(true); }
// TODO: could get rid of this if we could run a codegen prepass (with proper keyword #ifdef) public static void GenerateVirtualTextureFeedback( List <AbstractMaterialNode> downstreamNodesIncludingRoot, List <int>[] keywordPermutationsPerNode, ShaderStringBuilder surfaceDescriptionFunction, KeywordCollector shaderKeywords) { // A note on how we handle vt feedback in combination with keywords: // We essentially generate a fully separate feedback path for each permutation of keywords // so per permutation we gather variables contribution to feedback and we generate // feedback gathering for each permutation individually. var feedbackVariablesPerPermutation = PooledList <PooledList <string> > .Get(); try { if (shaderKeywords.permutations.Count >= 1) { for (int i = 0; i < shaderKeywords.permutations.Count; i++) { feedbackVariablesPerPermutation.Add(PooledList <string> .Get()); } } else { // Create a dummy single permutation feedbackVariablesPerPermutation.Add(PooledList <string> .Get()); } int index = 0; //for keywordPermutationsPerNode foreach (var node in downstreamNodesIncludingRoot) { if (node is SampleVirtualTextureNode vtNode) { if (vtNode.noFeedback) { continue; } if (keywordPermutationsPerNode[index] == null) { Debug.Assert(shaderKeywords.permutations.Count == 0, $"Shader has {shaderKeywords.permutations.Count} permutations but keywordPermutationsPerNode of some nodes are null."); feedbackVariablesPerPermutation[0].Add(vtNode.GetFeedbackVariableName()); } else { foreach (int perm in keywordPermutationsPerNode[index]) { feedbackVariablesPerPermutation[perm].Add(vtNode.GetFeedbackVariableName()); } } } if (node is SubGraphNode sgNode) { if (sgNode.asset == null) { continue; } if (keywordPermutationsPerNode[index] == null) { Debug.Assert(shaderKeywords.permutations.Count == 0, $"Shader has {shaderKeywords.permutations.Count} permutations but keywordPermutationsPerNode of some nodes are null."); foreach (var feedbackSlot in sgNode.asset.vtFeedbackVariables) { feedbackVariablesPerPermutation[0].Add(node.GetVariableNameForNode() + "_" + feedbackSlot); } } else { foreach (var feedbackSlot in sgNode.asset.vtFeedbackVariables) { foreach (int perm in keywordPermutationsPerNode[index]) { feedbackVariablesPerPermutation[perm].Add(node.GetVariableNameForNode() + "_" + feedbackSlot); } } } } index++; } index = 0; foreach (var feedbackVariables in feedbackVariablesPerPermutation) { // If it's a dummy single always-on permutation don't put an ifdef around the code if (shaderKeywords.permutations.Count >= 1) { surfaceDescriptionFunction.AppendLine(KeywordUtil.GetKeywordPermutationConditional(index)); } using (surfaceDescriptionFunction.BlockScope()) { if (feedbackVariables.Count == 0) { string feedBackCode = "surface.VTPackedFeedback = float4(1.0f,1.0f,1.0f,1.0f);"; surfaceDescriptionFunction.AppendLine(feedBackCode); } else if (feedbackVariables.Count == 1) { string feedBackCode = "surface.VTPackedFeedback = GetPackedVTFeedback(" + feedbackVariables[0] + ");"; surfaceDescriptionFunction.AppendLine(feedBackCode); } else if (feedbackVariables.Count > 1) { surfaceDescriptionFunction.AppendLine("float4 VTFeedback_array[" + feedbackVariables.Count + "];"); int arrayIndex = 0; foreach (var variable in feedbackVariables) { surfaceDescriptionFunction.AppendLine("VTFeedback_array[" + arrayIndex + "] = " + variable + ";"); arrayIndex++; } // TODO: should read from NDCPosition instead... surfaceDescriptionFunction.AppendLine("uint pixelColumn = (IN.ScreenPosition.x / IN.ScreenPosition.w) * _ScreenParams.x;"); surfaceDescriptionFunction.AppendLine( "surface.VTPackedFeedback = GetPackedVTFeedback(VTFeedback_array[(pixelColumn + _FrameCount) % (uint)" + feedbackVariables.Count + "]);"); } } if (shaderKeywords.permutations.Count >= 1) { surfaceDescriptionFunction.AppendLine("#endif"); } index++; } } finally { foreach (var list in feedbackVariablesPerPermutation) { list.Dispose(); } feedbackVariablesPerPermutation.Dispose(); } }
private bool ProcessPredicate(Token predicate, int endLine, ref int cur, ref bool appendEndln) { // eval if(param) var fieldName = predicate.GetString(); var nonwhitespace = SkipWhitespace(predicate.s, predicate.end + 1, endLine); if (!fieldName.StartsWith("features") && activeFields.permutationCount > 0) { var passedPermutations = activeFields.allPermutations.instances.Where(i => i.Contains(fieldName)).ToList(); if (passedPermutations.Count > 0) { var ifdefs = KeywordUtil.GetKeywordPermutationSetConditional( passedPermutations.Select(i => i.permutationIndex).ToList() ); result.AppendLine(ifdefs); //Append the rest of the line AppendSubstring(predicate.s, nonwhitespace, true, endLine, false); result.AppendNewLine(); result.AppendLine("#endif"); return(false); } else { appendEndln = false; //if line isn't active, remove whitespace } return(false); } else { // eval if(param) if (activeFields.baseInstance.Contains(fieldName)) { // predicate is active // append everything before the beginning of the escape sequence AppendSubstring(predicate.s, cur, true, predicate.start - 1, false); // continue parsing the rest of the line, starting with the first nonwhitespace character cur = nonwhitespace; return(true); } else { // predicate is not active if (isDebug) { // append everything before the beginning of the escape sequence AppendSubstring(predicate.s, cur, true, predicate.start - 1, false); // append the rest of the line, commented out result.Append("// "); AppendSubstring(predicate.s, nonwhitespace, true, endLine, false); } else { // don't append anything appendEndln = false; } return(false); } } }