Skip to content

Commit c819ba2

Browse files
julienamsellemEvergreen
authored andcommitted
[VFX] Add support for more HLSL function prototype declaration
JIRA: UUM-59938 1️⃣ The current implementation for Custom HLSL operators do not support all kind of function declaration. The goal of this PR is to lift this limitation and support all kind of function that makes sense in that context. Here are the limitation that are lifted: ✔ In operators, a function can return void ✔ In operators, a function can have no parameter at all (but then must return a value) ✔ In operators, a function can have only out parameters (can return void or not) ✔ In operators, a function can have only in parameters (but then must return a value) ✔ In operators, the return slot name can be defined with a comment above the function: /// return: <name-of-the-slot> ❌ In operators, a function cannot return void if there's not at least one out parameter ✔ In blocks, a function still must have a `VFXAttributes` parameter but don't need to access it all ✔ In blocks and operators, `TextureCube` and `Texture2DArray` are now supported 2️⃣ Also, for function which returns a value, until now the output slot was automatically named `out`. Now the user can use the same syntax as for the tooltip of input parameters to specify a name for the return output. The syntax is like this: ```hlsl /// return: My Value float FloatFunction4(in float t) { return 3 * t; } ``` In that case, the output slot will be named `My Value` 3️⃣ Last but not least, the HLSL code editor now supports: - `CTRL`+`S`: saves only the current file (instead of the whole Unity project) - `CTRL`+`Z` and `CTRL`+`Y`: adds support for undo/redo inside the code editor. - `CTRL`+`MouseWheel`: Increase/decrease font size
1 parent b270818 commit c819ba2

File tree

21 files changed

+13533
-134
lines changed

21 files changed

+13533
-134
lines changed

Packages/com.unity.visualeffectgraph/Editor/Controls/VFXTextEditor.cs

Lines changed: 156 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
using System;
22
using System.Collections.Generic;
3-
3+
using System.Text;
4+
using System.IO;
5+
using System.IO.Compression;
6+
using Unity.Profiling;
47
using UnityEditor.Experimental;
5-
using UnityEditor.Experimental.GraphView;
68
using UnityEditor.UIElements;
79
using UnityEngine;
810
using UnityEngine.UIElements;
@@ -20,16 +22,24 @@ class VFXTextEditor : EditorWindow, ISerializationCallbackReceiver, IComparer<Op
2022
{
2123
readonly struct TextArea
2224
{
25+
public static float s_FontSize = 11f;
26+
27+
static ProfilerMarker s_TextChangedPerfMarker = new("TextArea.OnTextChanged");
28+
2329
readonly VFXTextEditor m_Editor;
2430
readonly ITextProvider m_TextProvider;
2531
readonly TextField m_TextField;
2632
readonly Label m_TitleLabel;
2733
readonly VisualElement m_Root;
34+
readonly Stack<byte[]> m_UndoStack;
35+
readonly Stack<byte[]> m_RedoStack;
2836

2937
public TextArea(VFXTextEditor editor, ITextProvider textProvider)
3038
{
3139
m_Editor = editor;
3240
m_TextProvider = textProvider;
41+
m_UndoStack = new Stack<byte[]>();
42+
m_RedoStack = new Stack<byte[]>();
3343

3444
var tpl = VFXView.LoadUXML("VFXTextEditorArea");
3545
m_Root = tpl.CloneTree();
@@ -57,22 +67,84 @@ public TextArea(VFXTextEditor editor, ITextProvider textProvider)
5767
closeButton.RegisterCallback<ClickEvent>(OnClose);
5868
m_TextProvider.titleChanged += OnProviderTitleChanged;
5969
m_TextProvider.textChanged += OnProviderTextChanged;
70+
m_TextField.style.fontSize = s_FontSize;
6071
}
6172

6273
public ITextProvider TextProvider => m_TextProvider;
6374

6475
public VisualElement GetRoot() => m_Root;
76+
public void Save() => OnSave(null);
6577

78+
public bool HasFocus() => m_TextField.HasFocus();
6679
public void Focus() => m_TextField.Focus();
6780

81+
public void UpdateTextSize()
82+
{
83+
m_TextField.style.fontSize = new StyleLength(s_FontSize);
84+
}
85+
86+
public void Undo()
87+
{
88+
if (m_UndoStack.Count > 0)
89+
{
90+
var previousText = Decompress(m_UndoStack.Pop());
91+
m_RedoStack.Push(Compress(m_TextField.value));
92+
m_TextField.SetValueWithoutNotify(previousText);
93+
}
94+
}
95+
96+
public void Redo()
97+
{
98+
if (m_RedoStack.Count > 0)
99+
{
100+
var previousText = Decompress(m_RedoStack.Pop());
101+
m_UndoStack.Push(Compress(m_TextField.value));
102+
m_TextField.SetValueWithoutNotify(previousText);
103+
}
104+
}
105+
106+
private byte[] Compress(string text)
107+
{
108+
using var memoryStream = new MemoryStream();
109+
using (var gzipStream = new GZipStream(memoryStream, System.IO.Compression.CompressionLevel.Optimal))
110+
{
111+
gzipStream.Write(Encoding.UTF8.GetBytes(text));
112+
}
113+
114+
return memoryStream.ToArray();
115+
}
116+
117+
private string Decompress(byte[] data)
118+
{
119+
using var memoryStream = new MemoryStream(data);
120+
using var outputStream = new MemoryStream();
121+
using (var gzipStream = new GZipStream(memoryStream, CompressionMode.Decompress))
122+
{
123+
gzipStream.CopyTo(outputStream);
124+
}
125+
126+
return Encoding.UTF8.GetString(outputStream.ToArray());
127+
}
128+
68129
private void OnProviderTitleChanged() => m_TitleLabel.text = m_TextProvider.title;
69130
private void OnProviderTextChanged() => m_TextField.value = m_TextProvider.text;
70131

71132
private void OnTextChanged(ChangeEvent<string> evt)
72133
{
73-
m_TitleLabel.text = evt.newValue != m_TextProvider.text
74-
? $"{m_TextProvider.title}*"
75-
: m_TextProvider.title;
134+
s_TextChangedPerfMarker.Begin();
135+
try
136+
{
137+
m_UndoStack.Push(Compress(evt.previousValue));
138+
m_RedoStack.Clear();
139+
m_TitleLabel.text = evt.newValue != m_TextProvider.text
140+
? $"{m_TextProvider.title}*"
141+
: m_TextProvider.title;
142+
}
143+
finally
144+
{
145+
s_TextChangedPerfMarker.End();
146+
}
147+
//Debug.Log($"Undo stack:\n\tmemory occupation: {m_UndoStack.Sum(x => x.Length) * sizeof(byte) / 1000}kb -- Length: {m_UndoStack.Count}");
76148
}
77149

78150
private void OnSave(ClickEvent ev)
@@ -84,9 +156,11 @@ private void OnSave(ClickEvent ev)
84156
}
85157
}
86158

87-
private void OnClose(ClickEvent evt)
159+
internal void OnClose(ClickEvent evt)
88160
{
89161
m_Editor.Close(this);
162+
m_UndoStack.Clear();
163+
m_RedoStack.Clear();
90164
m_TextProvider.titleChanged -= OnProviderTitleChanged;
91165
m_TextProvider.textChanged -= OnProviderTextChanged;
92166
(m_TextProvider as IDisposable)?.Dispose();
@@ -149,6 +223,7 @@ private int GetTextIndexFromMousePosition(TextField textField, Vector2 mousePosi
149223
const string VFXTextEditorTitle = "HLSL Editor";
150224

151225
[NonSerialized] readonly List<TextArea> m_OpenedEditors = new();
226+
[NonSerialized] readonly List<VFXGraph> m_RegisteredGraphs = new();
152227
[SerializeField] List<OpenedTextProvider> m_OpenedModels;
153228

154229
Label m_EmptyMessage;
@@ -163,11 +238,71 @@ public void Show(VFXModel model)
163238
container.Add(textArea.GetRoot());
164239
m_OpenedEditors.Add(textArea);
165240
m_EmptyMessage.style.display = DisplayStyle.None;
241+
242+
var graph = model.GetGraph();
243+
if (!m_RegisteredGraphs.Contains(graph))
244+
{
245+
m_RegisteredGraphs.Add(graph);
246+
graph.onInvalidateDelegate += OnGraphInvalidate;
247+
}
166248
}
167249

168250
textArea.Focus();
169251
}
170252

253+
private void OnGraphInvalidate(VFXModel model, VFXModel.InvalidationCause cause)
254+
{
255+
foreach (var textArea in m_OpenedEditors.ToArray())
256+
{
257+
if (textArea.TextProvider.model.GetParent() == null)
258+
{
259+
textArea.OnClose(null);
260+
}
261+
}
262+
}
263+
264+
public void Undo()
265+
{
266+
foreach (var textArea in m_OpenedEditors)
267+
{
268+
if (textArea.HasFocus())
269+
{
270+
textArea.Undo();
271+
break;
272+
}
273+
}
274+
}
275+
276+
public void Redo()
277+
{
278+
foreach (var textArea in m_OpenedEditors)
279+
{
280+
if (textArea.HasFocus())
281+
{
282+
textArea.Redo();
283+
break;
284+
}
285+
}
286+
}
287+
288+
public void ChangeTextSize(int delta)
289+
{
290+
TextArea.s_FontSize = Mathf.Clamp(TextArea.s_FontSize + delta, 11f, 20f);
291+
m_OpenedEditors.ForEach(x => x.UpdateTextSize());
292+
}
293+
294+
public void Save()
295+
{
296+
foreach (var textArea in m_OpenedEditors)
297+
{
298+
if (textArea.HasFocus())
299+
{
300+
textArea.Save();
301+
break;
302+
}
303+
}
304+
}
305+
171306
private void CreateGUI()
172307
{
173308
titleContent.text = VFXTextEditorTitle;
@@ -204,6 +339,21 @@ private void Close(TextArea textArea)
204339
{
205340
m_EmptyMessage.style.display = DisplayStyle.Flex;
206341
}
342+
343+
var remainingGraphs = new HashSet<VFXGraph>();
344+
foreach (var editor in m_OpenedEditors)
345+
{
346+
remainingGraphs.Add(editor.TextProvider.model.GetGraph());
347+
}
348+
349+
foreach (var toRemoveGraphs in m_RegisteredGraphs.ToArray())
350+
{
351+
if (!remainingGraphs.Contains(toRemoveGraphs))
352+
{
353+
toRemoveGraphs.onInvalidateDelegate -= OnGraphInvalidate;
354+
m_RegisteredGraphs.Remove(toRemoveGraphs);
355+
}
356+
}
207357
}
208358

209359
public void OnBeforeSerialize()

Packages/com.unity.visualeffectgraph/Editor/Expressions/VFXExpressionHLSL.cs

Lines changed: 0 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -4,43 +4,6 @@
44
namespace UnityEditor.VFX
55
{
66
#pragma warning disable 0659
7-
class VFXExpressionPassThrough : VFXExpression
8-
{
9-
private int m_ParentIndex;
10-
private VFXValueType m_ValueType;
11-
12-
public VFXExpressionPassThrough() : this(0, VFXValueType.None, new [] { VFXValue<int>.Default })
13-
{
14-
}
15-
16-
public VFXExpressionPassThrough(int index, VFXValueType type, params VFXExpression[] parents) : base(Flags.InvalidOnCPU, parents)
17-
{
18-
m_ParentIndex = index;
19-
m_ValueType = type;
20-
}
21-
22-
public override VFXExpressionOperation operation => VFXExpressionOperation.None;
23-
24-
public override VFXValueType valueType => m_ValueType;
25-
26-
protected sealed override VFXExpression Evaluate(VFXExpression[] constParents)
27-
{
28-
return this;
29-
}
30-
31-
protected override VFXExpression Reduce(VFXExpression[] reducedParents)
32-
{
33-
var newExpression = (VFXExpressionPassThrough)base.Reduce(reducedParents);
34-
newExpression.m_ParentIndex = m_ParentIndex;
35-
newExpression.m_ValueType = m_ValueType;
36-
return newExpression;
37-
}
38-
39-
public override string GetCodeString(string[] parents)
40-
{
41-
return parents[m_ParentIndex];
42-
}
43-
}
447

458
class VFXExpressionHLSL : VFXExpression, IHLSLCodeHolder
469
{

Packages/com.unity.visualeffectgraph/Editor/Models/Blocks/Implementations/HLSL/CustomHLSL.cs

Lines changed: 17 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -46,20 +46,28 @@ public IEnumerable<IHLSMessage> Validate(IEnumerable<string> functions, HLSLFunc
4646
HLSLFunctionParameter attributesInput = null;
4747
foreach (var input in selectedFunction.inputs)
4848
{
49-
if (input.rawType == "Texture2D")
49+
if (input.access is HLSLAccess.IN or HLSLAccess.INOUT &&
50+
input.rawType is "Texture2D" or "Texture3D" or "TextureCube" or "Texture2DArray")
5051
{
51-
yield return new HLSLTexture2DShouldNotBeUsed(input.name);
52+
yield return new HLSLWrongHLSLTextureType(input.rawType, input.name);
53+
}
54+
if (input.rawType is "TextureCubeArray" or "VFXSamplerCubeArray")
55+
{
56+
yield return new HLSLTextureCubeArrayNotSupported(input.name);
5257
}
5358
if (input.type == typeof(VFXAttribute))
5459
{
5560
attributesInput = input;
5661
}
62+
else if (input.access is HLSLAccess.OUT or HLSLAccess.INOUT)
63+
{
64+
yield return new HLSLOutParameterNotAllowed(input.name);
65+
}
5766
}
5867
if (attributesInput == null)
5968
{
6069
yield return new HLSLMissingVFXAttribute();
6170
}
62-
6371
if (attributesInput != null && selectedFunction.attributes.Count > 0)
6472
{
6573
if (!attributesInput.access.HasFlag(HLSLAccess.OUT))
@@ -309,7 +317,7 @@ private void ParseCodeIfNeeded()
309317
return;
310318
}
311319

312-
var hasError = m_Function?.errorList != null;
320+
var hasError = m_Function?.errorList.Count > 0;
313321
var strippedHLSL = HLSLParser.StripCommentedCode(GetHLSLCode());
314322
if (hasError || strippedHLSL != cachedHLSLCode || m_SelectedFunction != m_AvailableFunction.GetSelection() || m_AvailableFunction.values == null)
315323
{
@@ -355,7 +363,7 @@ private void ParseCodeIfNeeded()
355363
m_Properties = new List<VFXPropertyWithValue>();
356364
foreach (var input in m_Function.inputs)
357365
{
358-
if (input.type != null && input.type != typeof(VFXAttribute))
366+
if (input.type != null && input.type != typeof(VFXAttribute) && input.access is HLSLAccess.IN)
359367
{
360368
m_Properties.Add(CreateProperty(input));
361369
}
@@ -403,16 +411,12 @@ private string BuildSource()
403411
var functionParameters = new List<string>();
404412

405413
// Create and initialize a VFXAttributes structure
406-
if (m_Attributes.Count > 0)
414+
builder.AppendLine("VFXAttributes att = (VFXAttributes)0;");
415+
foreach (var attribute in m_Attributes)
407416
{
408-
builder.AppendLine("VFXAttributes att = (VFXAttributes)0;");
409-
foreach (var attribute in m_Attributes)
410-
{
411-
builder.AppendLine($"att.{attribute.attrib.name} = {attribute.attrib.name};");
412-
}
413-
//builder.AppendLine();
414-
functionParameters.Add("att");
417+
builder.AppendLine($"att.{attribute.attrib.name} = {attribute.attrib.name};");
415418
}
419+
functionParameters.Add("att");
416420

417421
// Make the call to custom hlsl function
418422
var functionName = HasShaderFile()

0 commit comments

Comments
 (0)