123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510 |
- using System;
- using System.IO;
- using System.Collections.Generic;
- using System.Linq;
- using UnityEngine;
- using UnityEditor.Graphing;
- using UnityEditor.Rendering;
- using UnityEngine.UIElements;
- using UnityEditor.ShaderGraph.Drawing;
- using System.Text;
-
- namespace UnityEditor.ShaderGraph
- {
- [HasDependencies(typeof(MinimalCustomFunctionNode))]
- [Title("Utility", "Custom Function")]
- class CustomFunctionNode : AbstractMaterialNode, IGeneratesBodyCode, IGeneratesFunction, IMayRequireTransform
- {
- // 0 original version
- // 1 differentiate between struct-based UnityTexture2D and bare Texture2D resources (for all texture and samplerstate resources)
- public override int latestVersion => 1;
-
- public override IEnumerable<int> allowedNodeVersions => new int[] { 1 };
-
- [Serializable]
- public class MinimalCustomFunctionNode : IHasDependencies
- {
- [SerializeField]
- HlslSourceType m_SourceType = HlslSourceType.File;
-
- [SerializeField]
- string m_FunctionName = k_DefaultFunctionName;
-
- [SerializeField]
- string m_FunctionSource = null;
-
- public void GetSourceAssetDependencies(AssetCollection assetCollection)
- {
- if (m_SourceType == HlslSourceType.File)
- {
- m_FunctionSource = UpgradeFunctionSource(m_FunctionSource);
- if (IsValidFunction(m_SourceType, m_FunctionName, m_FunctionSource, null))
- {
- if (GUID.TryParse(m_FunctionSource, out GUID guid))
- {
- // as this is just #included into the generated .shader file
- // it doesn't actually need to be a dependency, other than for export package
- assetCollection.AddAssetDependency(guid, AssetCollection.Flags.IncludeInExportPackage);
- }
- }
- }
- }
- }
-
- enum SourceFileStatus
- {
- Empty, // No File specified
- DoesNotExist, // Either file doesn't exist (empty name) or guid points to a non-existant file
- Invalid, // File exists but isn't of a valid type (such as wrong extension)
- Valid
- };
-
- // With ShaderInclude asset type, it should no longer be necessary to soft-check the extension.
- public static string[] s_ValidExtensions = { ".hlsl", ".cginc", ".cg" };
- const string k_InvalidFileType = "Source file is not a valid file type. Valid file extensions are .hlsl, .cginc, and .cg";
- const string k_MissingFile = "Source file does not exist. A valid .hlsl, .cginc, or .cg file must be referenced";
- const string k_MissingOutputSlot = "A Custom Function Node must have at least one output slot";
-
- public CustomFunctionNode()
- {
- UpdateNodeName();
- synonyms = new string[] { "code", "HLSL" };
- }
-
- void UpdateNodeName()
- {
- if ((functionName == defaultFunctionName) || (functionName == null))
- name = "Custom Function";
- else
- name = functionName + " (Custom Function)";
- }
-
- public override bool hasPreview => true;
-
- [SerializeField]
- HlslSourceType m_SourceType = HlslSourceType.File;
-
- public HlslSourceType sourceType
- {
- get => m_SourceType;
- set => m_SourceType = value;
- }
-
- [SerializeField]
- string m_FunctionName = k_DefaultFunctionName;
-
- const string k_DefaultFunctionName = "Enter function name here...";
-
- public string functionName
- {
- get => m_FunctionName;
- set
- {
- m_FunctionName = value;
- UpdateNodeName();
- }
- }
-
- public string hlslFunctionName
- {
- get => m_FunctionName + "_$precision";
- }
-
-
- public static string defaultFunctionName => k_DefaultFunctionName;
-
- [SerializeField]
- string m_FunctionSource;
-
- const string k_DefaultFunctionSource = "Enter function source file path here...";
-
- public string functionSource
- {
- get => m_FunctionSource;
- set => m_FunctionSource = value;
- }
-
- [SerializeField]
- string m_FunctionBody = k_DefaultFunctionBody;
-
- const string k_DefaultFunctionBody = "Enter function body here...";
-
- public string functionBody
- {
- get => m_FunctionBody;
- set => m_FunctionBody = value;
- }
-
- public static string defaultFunctionBody => k_DefaultFunctionBody;
-
- public void GenerateNodeCode(ShaderStringBuilder sb, GenerationMode generationMode)
- {
- using (var inputSlots = PooledList<MaterialSlot>.Get())
- using (var outputSlots = PooledList<MaterialSlot>.Get())
- {
- GetInputSlots<MaterialSlot>(inputSlots);
- GetOutputSlots<MaterialSlot>(outputSlots);
-
- if (!IsValidFunction())
- {
- // invalid functions generate special preview code.. (why?)
- if (generationMode == GenerationMode.Preview && outputSlots.Count != 0)
- {
- outputSlots.OrderBy(s => s.id);
- var hlslVariableType = outputSlots[0].concreteValueType.ToShaderString();
- sb.AppendLine("{0} {1};",
- hlslVariableType,
- GetVariableNameForSlot(outputSlots[0].id));
- }
- return;
- }
-
- // declare output variables
- foreach (var output in outputSlots)
- {
- sb.AppendLine("{0} {1};",
- output.concreteValueType.ToShaderString(),
- GetVariableNameForSlot(output.id));
-
- if (output.bareResource)
- AssignDefaultBareResource(output, sb);
- }
-
- // call function
- sb.TryAppendIndentation();
- sb.Append(hlslFunctionName);
- sb.Append("(");
- bool first = true;
-
- foreach (var input in inputSlots)
- {
- if (!first)
- sb.Append(", ");
- first = false;
-
- sb.Append(SlotInputValue(input, generationMode));
-
- // fixup input for Bare types
- if (input.bareResource)
- {
- if (input is SamplerStateMaterialSlot)
- sb.Append(".samplerstate");
- else
- sb.Append(".tex");
- }
- }
-
- foreach (var output in outputSlots)
- {
- if (!first)
- sb.Append(", ");
- first = false;
- sb.Append(GetVariableNameForSlot(output.id));
-
- // fixup output for Bare types
- if (output.bareResource)
- {
- if (output is SamplerStateMaterialSlot)
- sb.Append(".samplerstate");
- else
- sb.Append(".tex");
- }
- }
- sb.Append(");");
- sb.AppendNewLine();
- }
- }
-
- void AssignDefaultBareResource(MaterialSlot slot, ShaderStringBuilder sb)
- {
- switch (slot.concreteValueType)
- {
- case ConcreteSlotValueType.Texture2D:
- {
- var slotVariable = GetVariableNameForSlot(slot.id);
- sb.TryAppendIndentation();
- sb.Append(slotVariable);
- sb.Append(".samplerstate = default_sampler_Linear_Repeat;");
- sb.AppendNewLine();
- sb.TryAppendIndentation();
- sb.Append(slotVariable);
- sb.Append(".texelSize = float4(1.0f/128.0f, 1.0f/128.0f, 128.0f, 128.0f);");
- sb.AppendNewLine();
- sb.TryAppendIndentation();
- sb.Append(slotVariable);
- sb.Append(".scaleTranslate = float4(1.0f, 1.0f, 0.0f, 0.0f);");
- sb.AppendNewLine();
- }
- break;
- case ConcreteSlotValueType.Texture3D:
- case ConcreteSlotValueType.Texture2DArray:
- case ConcreteSlotValueType.Cubemap:
- {
- var slotVariable = GetVariableNameForSlot(slot.id);
- sb.TryAppendIndentation();
- sb.Append(slotVariable);
- sb.Append(".samplerstate = default_sampler_Linear_Repeat;");
- sb.AppendNewLine();
- }
- break;
- }
- }
-
- public void GenerateNodeFunction(FunctionRegistry registry, GenerationMode generationMode)
- {
- if (!IsValidFunction())
- return;
-
- switch (sourceType)
- {
- case HlslSourceType.File:
- string path = AssetDatabase.GUIDToAssetPath(functionSource);
-
- // This is required for upgrading without console errors
- if (string.IsNullOrEmpty(path))
- path = functionSource;
-
- registry.RequiresIncludePath(path);
- break;
- case HlslSourceType.String:
- registry.ProvideFunction(hlslFunctionName, builder =>
- {
- // add a hint for the analytic derivative code to ignore user functions
- builder.AddLine("// unity-custom-func-begin");
- GetFunctionHeader(builder);
- using (builder.BlockScope())
- {
- builder.AppendLines(functionBody);
- }
- builder.AddLine("// unity-custom-func-end");
- });
- break;
- default:
- throw new ArgumentOutOfRangeException();
- }
- }
-
- void GetFunctionHeader(ShaderStringBuilder sb)
- {
- using (var inputSlots = PooledList<MaterialSlot>.Get())
- using (var outputSlots = PooledList<MaterialSlot>.Get())
- {
- GetInputSlots(inputSlots);
- GetOutputSlots(outputSlots);
-
- sb.Append("void ");
- sb.Append(hlslFunctionName);
- sb.Append("(");
-
- var first = true;
-
- foreach (var argument in inputSlots)
- {
- if (!first)
- sb.Append(", ");
- first = false;
- argument.AppendHLSLParameterDeclaration(sb, argument.shaderOutputName);
- }
-
- foreach (var argument in outputSlots)
- {
- if (!first)
- sb.Append(", ");
- first = false;
- sb.Append("out ");
- argument.AppendHLSLParameterDeclaration(sb, argument.shaderOutputName);
- }
-
- sb.Append(")");
- }
- }
-
- string SlotInputValue(MaterialSlot port, GenerationMode generationMode)
- {
- IEdge[] edges = port.owner.owner.GetEdges(port.slotReference).ToArray();
- if (edges.Any())
- {
- var fromSocketRef = edges[0].outputSlot;
- var fromNode = fromSocketRef.node;
- if (fromNode == null)
- return string.Empty;
-
- return fromNode.GetOutputForSlot(fromSocketRef, port.concreteValueType, generationMode);
- }
-
- return port.GetDefaultValue(generationMode);
- }
-
- bool IsValidFunction()
- {
- return IsValidFunction(sourceType, functionName, functionSource, functionBody);
- }
-
- static bool IsValidFunction(HlslSourceType sourceType, string functionName, string functionSource, string functionBody)
- {
- bool validFunctionName = !string.IsNullOrEmpty(functionName) && functionName != k_DefaultFunctionName;
-
- if (sourceType == HlslSourceType.String)
- {
- bool validFunctionBody = !string.IsNullOrEmpty(functionBody) && functionBody != k_DefaultFunctionBody;
- return validFunctionName & validFunctionBody;
- }
- else
- {
- if (!validFunctionName || string.IsNullOrEmpty(functionSource) || functionSource == k_DefaultFunctionSource)
- return false;
-
- string path = AssetDatabase.GUIDToAssetPath(functionSource);
- if (string.IsNullOrEmpty(path))
- path = functionSource;
-
- string extension = Path.GetExtension(path);
- return s_ValidExtensions.Contains(extension);
- }
- }
-
- void ValidateSlotName()
- {
- using (var slots = PooledList<MaterialSlot>.Get())
- {
- GetSlots(slots);
- foreach (var slot in slots)
- {
- // check for bad slot names
- var error = NodeUtils.ValidateSlotName(slot.RawDisplayName(), out string errorMessage);
- if (error)
- {
- owner.AddValidationError(objectId, errorMessage);
- break;
- }
- }
- }
- }
-
- void ValidateBareTextureSlots()
- {
- using (var outputSlots = PooledList<MaterialSlot>.Get())
- {
- GetOutputSlots(outputSlots);
- foreach (var slot in outputSlots)
- {
- if (slot.bareResource)
- {
- owner.AddValidationError(objectId, "This node uses Bare Texture or SamplerState outputs, which may produce unexpected results when fed to other nodes. Please convert the node to use the non-Bare struct-based outputs (see the structs defined in com.unity.render-pipelines.core/ShaderLibrary/Texture.hlsl)", ShaderCompilerMessageSeverity.Warning);
- break;
- }
- }
- }
- }
-
- public override void ValidateNode()
- {
- bool hasAnyOutputs = this.GetOutputSlots<MaterialSlot>().Any();
- if (sourceType == HlslSourceType.File)
- {
- SourceFileStatus fileStatus = SourceFileStatus.Empty;
- if (!string.IsNullOrEmpty(functionSource))
- {
- string path = AssetDatabase.GUIDToAssetPath(functionSource);
- if (!string.IsNullOrEmpty(path) && AssetDatabase.LoadAssetAtPath<UnityEngine.Object>(path) != null)
- {
- string extension = path.Substring(path.LastIndexOf('.'));
- if (!s_ValidExtensions.Contains(extension))
- {
- fileStatus = SourceFileStatus.Invalid;
- }
- else
- {
- fileStatus = SourceFileStatus.Valid;
- }
- }
- else
- fileStatus = SourceFileStatus.DoesNotExist;
- }
-
- if (fileStatus == SourceFileStatus.DoesNotExist || (fileStatus == SourceFileStatus.Empty && hasAnyOutputs))
- owner.AddValidationError(objectId, k_MissingFile, ShaderCompilerMessageSeverity.Error);
- else if (fileStatus == SourceFileStatus.Invalid)
- owner.AddValidationError(objectId, k_InvalidFileType, ShaderCompilerMessageSeverity.Error);
- else if (fileStatus == SourceFileStatus.Valid)
- owner.ClearErrorsForNode(this);
- }
- if (!hasAnyOutputs)
- {
- owner.AddValidationError(objectId, k_MissingOutputSlot, ShaderCompilerMessageSeverity.Warning);
- }
- ValidateSlotName();
- ValidateBareTextureSlots();
-
- base.ValidateNode();
- }
-
- public bool Reload(HashSet<string> changedFileDependencyGUIDs)
- {
- if (changedFileDependencyGUIDs.Contains(m_FunctionSource))
- {
- owner.ClearErrorsForNode(this);
- ValidateNode();
- Dirty(ModificationScope.Graph);
- return true;
- }
- return false;
- }
-
- public static string UpgradeFunctionSource(string functionSource)
- {
- // Handle upgrade from legacy asset path version
- // If functionSource is not empty or a guid then assume it is legacy version
- // If asset can be loaded from path then get its guid
- // Otherwise it was the default string so set to empty
- Guid guid;
- if (!string.IsNullOrEmpty(functionSource) && !Guid.TryParse(functionSource, out guid))
- {
- // not sure why we don't use AssetDatabase.AssetPathToGUID...
- // I guess we are testing that it actually exists and can be loaded here before converting?
- string guidString = string.Empty;
- ShaderInclude shaderInclude = AssetDatabase.LoadAssetAtPath<ShaderInclude>(functionSource);
- if (shaderInclude != null)
- {
- long localId;
- AssetDatabase.TryGetGUIDAndLocalFileIdentifier(shaderInclude, out guidString, out localId);
- }
- functionSource = guidString;
- }
-
- return functionSource;
- }
-
- public override void OnAfterDeserialize()
- {
- base.OnAfterDeserialize();
- functionSource = UpgradeFunctionSource(functionSource);
- UpdateNodeName();
- }
-
- public override void OnAfterMultiDeserialize(string json)
- {
- if (sgVersion < 1)
- {
- // any Texture2D slots used prior to version 1 should be flagged as "bare" so we can
- // generate backwards compatible code
- var slots = new List<MaterialSlot>();
- GetSlots(slots);
- foreach (var slot in slots)
- {
- slot.bareResource = true;
- }
- ChangeVersion(1);
- }
- }
-
- public NeededTransform[] RequiresTransform(ShaderStageCapability stageCapability = ShaderStageCapability.All)
- {
- return new[]
- {
- NeededTransform.ObjectToWorld,
- NeededTransform.WorldToObject
- };
- }
- }
- }
|