port from perforce

This commit is contained in:
2026-04-18 22:31:51 +02:00
commit 8d0ab5b7cc
8409 changed files with 3972376 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
using System.Xml.Serialization;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("BooleanLiteral", Namespace = _namespace)]
public sealed class BooleanLiteralModel : LiteralModelBase<bool>
{
public override CodeGenNodeViewModel CreateViewModel()
{
return new BooleanLiteralNode();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes.Code
{
public class BooleanLiteralNode : CodeGenNodeViewModel
{
static BooleanLiteralNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<BooleanLiteralNode>));
}
public BooleanLiteralNode() : base(NodeType.Literal)
{
Name = "Boolean";
Output = new CodeGenOutputViewModel<ITypedExpression<bool>>(EPortType.Boolean)
{
Name = "Value",
Editor = ValueEditor,
Value = ValueEditor.ValueChanged.Select(v => new BooleanLiteralValue { Value = v })
};
Outputs.Add(Output);
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
this.WhenAnyValue(vm => vm.Output.CurrentValue)
.Where(v => v != null)
.Subscribe(_ => stringPreview.Value = Output.CurrentValue.PreviewValue ?? "False");
}
public BooleanValueEditorViewModel ValueEditor { get; } = new();
public ValueNodeOutputViewModel<ITypedExpression<bool>> Output { get; }
public override NodeModelBase CreateModel()
{
return new BooleanLiteralModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var booleanLiteral = (BooleanLiteralModel)model;
ValueEditor.Value = booleanLiteral.Value;
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var booleanLiteral = (BooleanLiteralModel)model;
booleanLiteral.Value = ValueEditor.Value;
}
}
}

View File

@@ -0,0 +1,6 @@
namespace Intromat.Nodes.Code
{
public class BooleanLiteralValue : LiteralValueBase<bool>
{
}
}

View File

@@ -0,0 +1,14 @@
using System.Xml.Serialization;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("FloatLiteral", Namespace = _namespace)]
public sealed class FloatLiteralModel : LiteralModelBase<float>
{
public override CodeGenNodeViewModel CreateViewModel()
{
return new FloatLiteralNode();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes.Code
{
public class FloatLiteralNode : CodeGenNodeViewModel
{
static FloatLiteralNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<FloatLiteralNode>));
}
public FloatLiteralNode() : base(NodeType.Literal)
{
Name = "Float";
Output = new CodeGenOutputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Value",
Editor = ValueEditor,
Value = ValueEditor.ValueChanged.Select(v => new FloatLiteralValue { Value = v })
};
Outputs.Add(Output);
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
this.WhenAnyValue(vm => vm.Output.CurrentValue)
.Where(v => v != null)
.Subscribe(_ => stringPreview.Value = Output.CurrentValue.PreviewValue ?? "0.0");
}
public FloatValueEditorViewModel ValueEditor { get; } = new();
public ValueNodeOutputViewModel<ITypedExpression<float>> Output { get; }
public override NodeModelBase CreateModel()
{
return new FloatLiteralModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var floatLiteral = (FloatLiteralModel)model;
ValueEditor.Value = floatLiteral.Value;
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var floatLiteral = (FloatLiteralModel)model;
floatLiteral.Value = ValueEditor.Value;
}
}
}

View File

@@ -0,0 +1,9 @@
using System.Globalization;
namespace Intromat.Nodes.Code
{
public class FloatLiteralValue : LiteralValueBase<float>
{
public override string? PreviewValue => Value.ToString(CultureInfo.InvariantCulture);
}
}

View File

@@ -0,0 +1,19 @@
using System.Xml.Serialization;
using Intromat.PersistentModel;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("ForLoop", Namespace = _namespace)]
public sealed class ForLoopModel : NodeModelBase
{
public IntLiteralModel FirstIndex { get; set; } = null!;
public IntLiteralModel LastIndex { get; set; } = null!;
public override CodeGenNodeViewModel CreateViewModel()
{
return new ForLoopNode();
}
}
}

View File

@@ -0,0 +1,114 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes.Code
{
public class ForLoopNode : ExecutionNodeBase
{
static ForLoopNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<ForLoopNode>));
}
public ForLoopNode() : base(NodeType.Code)
{
var boundsGroup = new EndpointGroup("Bounds");
Name = "For Loop";
LoopBodyFlow = new CodeGenInputViewModel<IStatement>(EPortType.Execution)
{
Name = "Loop Body",
Group = _executionFlowGroup
};
Inputs.Add(LoopBodyFlow);
FirstIndex = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "First Index",
Group = boundsGroup,
Editor = FirstIndexEditor
};
Inputs.Add(FirstIndex);
LastIndex = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Last Index",
Group = boundsGroup,
Editor = LastIndexEditor
};
Inputs.Add(LastIndex);
var loopBodyChanged = LoopBodyFlow.ValueChanged.Select(_ => Unit.Default).StartWith(Unit.Default);
FlowIn.Value = loopBodyChanged
.CombineLatest(FlowOutChanged, FirstIndex.ValueChanged, LastIndex.ValueChanged, (bodyChange, endChange, firstI, lastI) => (BodyChange: bodyChange, EndChange: endChange, FirstI: firstI, LastI: lastI))
.Select(v => new ForLoopValue
{
LoopBody = LoopBodyFlow.Value,
FlowOut = FlowOut.Value,
LowerBound = v.FirstI,
UpperBound = v.LastI
});
CurrentIndex = new CodeGenOutputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Current Index",
Value = FlowIn.Value.Select(v => new VariableReference<int> { LocalVariable = ((ForLoopValue)v).CurrentIndex })
};
Outputs.Add(CurrentIndex);
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
FlowIn.Value.Subscribe(v =>
{
var forLoop = (ForLoopValue)v;
stringPreview.Value = $"[{forLoop.LowerBound?.PreviewValue ?? "0"}..{forLoop.UpperBound?.PreviewValue ?? "0"}]";
});
}
public ValueNodeInputViewModel<IStatement> LoopBodyFlow { get; }
public IntegerExpressionEditorViewModel FirstIndexEditor { get; } = new();
public ValueNodeInputViewModel<ITypedExpression<int>> FirstIndex { get; }
public IntegerExpressionEditorViewModel LastIndexEditor { get; } = new();
public ValueNodeInputViewModel<ITypedExpression<int>> LastIndex { get; }
public ValueNodeOutputViewModel<ITypedExpression<int>> CurrentIndex { get; }
public override NodeModelBase CreateModel()
{
return new ForLoopModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var forLoop = (ForLoopModel)model;
forLoop.FirstIndex = FirstIndexEditor.CreateModel();
forLoop.LastIndex = LastIndexEditor.CreateModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var forLoop = (ForLoopModel)model;
FirstIndexEditor.LoadModel(forLoop.FirstIndex);
LastIndexEditor.LoadModel(forLoop.LastIndex);
}
}
}

View File

@@ -0,0 +1,48 @@
using System.Diagnostics;
using System.Text;
using Intromat.Model;
using Intromat.Model.Compiler;
namespace Intromat.Nodes.Code
{
public class ForLoopValue : ExecutionValueBase
{
public IStatement? LoopBody { get; set; }
public ITypedExpression<int>? LowerBound { get; set; }
public ITypedExpression<int>? UpperBound { get; set; }
public InlineVariableDefinition<int> CurrentIndex { get; } = new();
public override void Compile(CompilerContext context, StringBuilder sb)
{
Debug.Assert(UpperBound != null, nameof(UpperBound) + " != null");
context.EnterNewScope("For loop");
CurrentIndex.Value = LowerBound;
sb.Append("for (");
CurrentIndex.Compile(context, sb);
sb.Append("; ");
sb.Append(CurrentIndex.VariableName);
sb.Append(" <= ");
UpperBound.Compile(context, sb);
sb.Append("; ++");
sb.Append(CurrentIndex.VariableName);
sb.Append(")\n{\n");
LoopBody?.Compile(context, sb);
sb.Append("\n}\n");
context.LeaveScope();
base.Compile(context, sb);
}
public override void CompileHeader(CompilerContext context, StringBuilder sb)
{
LoopBody?.CompileHeader(context, sb);
base.CompileHeader(context, sb);
}
}
}

View File

@@ -0,0 +1,31 @@
using System.Collections.Generic;
using System.Diagnostics;
using System.Text;
using Intromat.Model.Compiler;
namespace Intromat.Nodes.Code
{
public class FunctionCall : ExecutionValueBase
{
public string? FunctionName { get; set; }
public List<IExpression> Parameters { get; } = new();
public override void Compile(CompilerContext context, StringBuilder sb)
{
Debug.Assert(FunctionName != null, nameof(FunctionName) + " != null");
sb.Append($"{FunctionName}(");
for (int i = 0, n = Parameters.Count; i < n; ++i)
{
var p = Parameters[i];
p.Compile(context, sb);
if (i != n - 1)
sb.Append(", ");
}
sb.Append(")\n");
base.Compile(context, sb);
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Xml.Serialization;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("IntLiteral", Namespace = _namespace)]
public sealed class IntLiteralModel : LiteralModelBase<int>
{
public override CodeGenNodeViewModel CreateViewModel()
{
return new IntLiteralNode();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes.Code
{
public class IntLiteralNode : CodeGenNodeViewModel
{
static IntLiteralNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<IntLiteralNode>));
}
public IntLiteralNode() : base(NodeType.Literal)
{
Name = "Integer";
Output = new CodeGenOutputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Value",
Editor = ValueEditor,
Value = ValueEditor.ValueChanged.Select(v => new IntLiteralValue { Value = v })
};
Outputs.Add(Output);
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
this.WhenAnyValue(vm => vm.Output.CurrentValue)
.Where(v => v != null)
.Subscribe(_ => stringPreview.Value = Output.CurrentValue.PreviewValue ?? "0");
}
public IntegerValueEditorViewModel ValueEditor { get; } = new();
public ValueNodeOutputViewModel<ITypedExpression<int>> Output { get; }
public override NodeModelBase CreateModel()
{
return new IntLiteralModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var intLiteral = (IntLiteralModel)model;
ValueEditor.Value = intLiteral.Value;
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var intLiteral = (IntLiteralModel)model;
intLiteral.Value = ValueEditor.Value;
}
}
}

View File

@@ -0,0 +1,25 @@
using System;
namespace Intromat.Nodes.Code
{
public class IntLiteralValue : LiteralValueBase<int>
{
public int EvaluateDimension()
{
var relativeDimension = RelativeSource switch
{
ERelativeSource.Parent => 1 << ParentValue,
ERelativeSource.Input => InputValue,
_ => 1
};
var result = Value switch
{
< 0 => relativeDimension >> -Value,
> 0 => relativeDimension << Value,
_ => relativeDimension
};
return Math.Max(1, Math.Min(8192, result));
}
}
}

View File

@@ -0,0 +1,34 @@
using System.Xml.Serialization;
using Intromat.Nodes.Textures;
using Intromat.PersistentModel;
namespace Intromat.Nodes.Code
{
public enum ERelativeSource
{
Custom,
Parent,
Input
}
[XmlInclude(typeof(IntLiteralModel))]
[XmlInclude(typeof(StringLiteralModel))]
[XmlInclude(typeof(FloatLiteralModel))]
[XmlInclude(typeof(BooleanLiteralModel))]
[XmlInclude(typeof(SamplerLiteralModel))]
public abstract class LiteralModelBase : NodeModelBase
{
public ERelativeSource RelativeSource { get; set; }
}
public class LiteralModelEntry
{
public string Key { get; set; } = null!;
public LiteralModelBase Literal { get; set; } = null!;
}
public abstract class LiteralModelBase<T> : LiteralModelBase
{
public T Value { get; set; } = default!;
}
}

View File

@@ -0,0 +1,28 @@
using System.Text;
using Intromat.Model.Compiler;
namespace Intromat.Nodes.Code
{
public abstract class LiteralValueBase<T> : ITypedExpression<T>
{
public virtual string? PreviewValue => Value?.ToString();
public T ParentValue { get; set; } = default!;
public T InputValue { get; set; } = default!;
public T Value { get; set; } = default!;
public ERelativeSource RelativeSource { get; set; }
public virtual void Compile(CompilerContext context, StringBuilder sb)
{
sb.Append(Value);
}
public virtual T Evaluate()
{
return Value;
}
}
}

View File

@@ -0,0 +1,17 @@
using System.Xml.Serialization;
using Intromat.PersistentModel;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("Print", Namespace = _namespace)]
public sealed class PrintModel : NodeModelBase
{
public string? Text { get; set; }
public override CodeGenNodeViewModel CreateViewModel()
{
return new PrintNode();
}
}
}

View File

@@ -0,0 +1,75 @@
using System;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes.Code
{
public class PrintNode : ExecutionNodeBase
{
static PrintNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<PrintNode>));
}
public PrintNode() : base(NodeType.Code)
{
Name = "Print";
Text = new CodeGenInputViewModel<ITypedExpression<string>>(EPortType.String)
{
Name = "Text",
Editor = TextEditor
};
Inputs.Add(Text);
FlowIn.Value = FlowOutChanged
.CombineLatest(Text.ValueChanged, (flowOutChanged, textValueChanged) => (FlowOutChanged: flowOutChanged, StringExpression: textValueChanged))
.Select(v => new FunctionCall
{
FunctionName = "print",
FlowOut = FlowOut.Value,
Parameters = { v.StringExpression ?? new StringLiteralValue { Value = "" } }
});
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
this.WhenAnyValue(vm => vm.Text.Value)
.Where(v => v != null)
.Subscribe(_ => stringPreview.Value = Text.Value.PreviewValue ?? string.Empty);
}
public StringExpressionEditorViewModel TextEditor { get; } = new();
public ValueNodeInputViewModel<ITypedExpression<string>> Text { get; }
public override NodeModelBase CreateModel()
{
return new PrintModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var print = (PrintModel)model;
print.Text = TextEditor.Value is StringLiteralValue text ? text.Value : null;
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var print = (PrintModel)model;
if (print.Text != null)
TextEditor.Value = new StringLiteralValue { Value = print.Text };
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Xml.Serialization;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("StringFile", Namespace = _namespace)]
public sealed class StringFileModel : LiteralModelBase<string?>
{
public override CodeGenNodeViewModel CreateViewModel()
{
return new StringFileNode();
}
}
}

View File

@@ -0,0 +1,121 @@
using System;
using System.IO;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.Utilities;
using ReactiveUI;
using RxFileSystemWatcher;
using Splat;
namespace Intromat.Nodes.Code
{
public class StringFileNode : CodeGenNodeViewModel
{
private readonly ObservableAsPropertyHelper<ObservableFileSystemWatcher> _fileWatcher;
private readonly ObservableAsPropertyHelper<string> _fullPath;
static StringFileNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<StringFileNode>));
}
public StringFileNode()
: base(NodeType.Literal)
{
Name = "String (file)";
Path = new CodeGenInputViewModel<string?>(EPortType.String)
{
Name = "Path",
Editor = PathEditor,
};
Inputs.Add(Path);
Contents = new CodeGenOutputViewModel<ITypedExpression<string?>>(EPortType.String)
{
Name = "Contents",
};
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
this.WhenAnyValue(vm => vm.Parent, vm => vm.Path.Value)
.Where(pair => pair.Item1 != null && pair.Item2 != null)
.Select(pair => System.IO.Path.Combine(((CodeGenNetworkViewModel)pair.Item1).Document.Parent.FullPath, pair.Item2!))
.StartWith(string.Empty)
.ToProperty(this, vm => vm.FullPath, out _fullPath);
this.WhenAnyValue(vm => vm.FullPath)
.Where(File.Exists)
.Select(path => new ObservableFileSystemWatcher(c =>
{
c.Path = System.IO.Path.GetDirectoryName(path)!;
}))
.ToProperty(this, vm => vm.FileWatcher, out _fileWatcher);
Contents.Value = this.WhenAnyValue(vm => vm.FileWatcher)
.PairWithPreviousValue()
.Select(pair =>
{
var oldWatcher = pair.OldValue;
var newWatcher = pair.NewValue;
oldWatcher?.Dispose();
if (newWatcher != null)
{
stringPreview.Value = Path.Value!;
var result =
newWatcher.Renamed.Where(e => e.FullPath == FullPath)
.Merge(newWatcher.Changed.Where(e => e.FullPath == FullPath))
.Throttle(TimeSpan.FromMilliseconds(100))
.Select(_ => new StringLiteralValue() { Value = File.ReadAllText(FullPath) })
.StartWith(new StringLiteralValue() { Value = File.ReadAllText(FullPath) });
newWatcher.Start();
return result;
}
stringPreview.Value = string.Empty;
return Observable.Empty<StringLiteralValue>();
})
.Switch();
Outputs.Add(Contents);
}
public ObservableFileSystemWatcher FileWatcher => _fileWatcher.Value;
public string FullPath => _fullPath.Value;
public StringValueEditorViewModel PathEditor { get; } = new();
public ValueNodeInputViewModel<string?> Path { get; }
public ValueNodeOutputViewModel<ITypedExpression<string?>> Contents { get; }
public override NodeModelBase CreateModel()
{
return new StringFileModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var fileModel = (StringFileModel)model;
PathEditor.Value = fileModel.Value;
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var fileModel = (StringFileModel)model;
fileModel.Value = PathEditor.Value;
}
}
}

View File

@@ -0,0 +1,14 @@
using System.Xml.Serialization;
using Intromat.ViewModels;
namespace Intromat.Nodes.Code
{
[XmlRoot("StringLiteral", Namespace = _namespace)]
public sealed class StringLiteralModel : LiteralModelBase<string?>
{
public override CodeGenNodeViewModel CreateViewModel()
{
return new StringLiteralNode();
}
}
}

View File

@@ -0,0 +1,66 @@
using System;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes.Code
{
public class StringLiteralNode : CodeGenNodeViewModel
{
static StringLiteralNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<StringLiteralNode>));
}
public StringLiteralNode() : base(NodeType.Literal)
{
Name = "String";
Output = new CodeGenOutputViewModel<ITypedExpression<string?>>(EPortType.String)
{
Name = "Value",
Editor = ValueEditor,
Value = ValueEditor.ValueChanged.Select(v => new StringLiteralValue { Value = v })
};
Outputs.Add(Output);
var stringPreview = new StringPreviewViewModel();
Preview = stringPreview;
this.WhenAnyValue(vm => vm.Output.CurrentValue)
.Where(v => v != null)
.Subscribe(_ => stringPreview.Value = ((StringLiteralValue)Output.CurrentValue).Value ?? string.Empty);
}
public StringValueEditorViewModel ValueEditor { get; } = new();
public ValueNodeOutputViewModel<ITypedExpression<string?>> Output { get; }
public override NodeModelBase CreateModel()
{
return new StringLiteralModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var textLiteral = (StringLiteralModel)model;
ValueEditor.Value = textLiteral.Value;
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var textLiteral = (StringLiteralModel)model;
textLiteral.Value = ValueEditor.Value;
}
}
}

View File

@@ -0,0 +1,21 @@
using System.Diagnostics;
using System.Text;
using Intromat.Model.Compiler;
namespace Intromat.Nodes.Code
{
public class StringLiteralValue : LiteralValueBase<string?>
{
public override string? PreviewValue => Value;
public override void Compile(CompilerContext context, StringBuilder sb)
{
sb.Append($"\"{Value}\"");
}
public override string Evaluate()
{
return Value ?? string.Empty;
}
}
}

View File

@@ -0,0 +1,32 @@
using System;
using System.Diagnostics;
using System.Text;
using Intromat.Model;
using Intromat.Model.Compiler;
using Intromat.Model.Compiler.Error;
namespace Intromat.Nodes.Code
{
public class VariableReference<T> : ITypedExpression<T>
{
public string? PreviewValue => LocalVariable?.VariableName;
public ITypedVariableDefinition<T>? LocalVariable { get; set; }
public void Compile(CompilerContext context, StringBuilder sb)
{
var localVariable = LocalVariable;
Debug.Assert(localVariable != null, nameof(localVariable) + " != null");
Debug.Assert(localVariable.VariableName != null, $"{nameof(localVariable)}.{nameof(localVariable.VariableName)} != null");
if (!context.IsInScope(localVariable))
throw new VariableOutOfScopeException(localVariable.VariableName);
sb.Append(localVariable.VariableName);
}
public T Evaluate()
{
throw new NotImplementedException();
}
}
}

View File

@@ -0,0 +1,28 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
using DynamicData;
using Intromat.ViewModels;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
namespace Intromat.Nodes
{
public abstract class DxNodeBase : CodeGenNodeViewModel
{
protected DxNodeBase()
: base(NodeType.Literal)
{
AnyInputChanged = Inputs
.Connect()
.Filter(input => input is ValueNodeInputViewModelBase)
.WhenValueChanged(input => ((ValueNodeInputViewModelBase)input).UnitValueChanged)
.SelectMany(i => i!);
AnyInputChanged.Subscribe(_ => { });
Resizable = ResizeOrientation.HorizontalAndVertical;
}
public IObservable<Unit> AnyInputChanged { get; }
}
}

View File

@@ -0,0 +1,44 @@
using System;
using System.Reactive;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.ViewModels;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
namespace Intromat.Nodes
{
public abstract class ExecutionNodeBase : CodeGenNodeViewModel
{
protected readonly EndpointGroup _executionFlowGroup;
protected ExecutionNodeBase(NodeType type)
: base(type)
{
_executionFlowGroup = new EndpointGroup("Execution Flow");
FlowOut = new CodeGenInputViewModel<IStatement>(EPortType.Execution)
{
Name = "Flow",
Group = _executionFlowGroup
};
Inputs.Add(FlowOut);
FlowOutChanged = FlowOut.ValueChanged.Select(_ => Unit.Default).StartWith(Unit.Default);
FlowIn = new CodeGenOutputViewModel<IStatement>(EPortType.Execution)
{
Name = "Flow",
Group = _executionFlowGroup
};
Outputs.Add(FlowIn);
}
public ValueNodeOutputViewModel<IStatement> FlowIn { get; }
public ValueNodeInputViewModel<IStatement> FlowOut { get; }
public IObservable<Unit> FlowOutChanged { get; }
}
}

View File

@@ -0,0 +1,24 @@
using System.Text;
using Intromat.Model.Compiler;
namespace Intromat.Nodes
{
public abstract class ExecutionValueBase : IStatement
{
public IStatement? FlowOut { get; set; }
public virtual void Compile(CompilerContext context, StringBuilder sb)
{
if (FlowOut != null)
{
FlowOut.Compile(context, sb);
sb.Append("\n");
}
}
public virtual void CompileHeader(CompilerContext context, StringBuilder sb)
{
FlowOut?.CompileHeader(context, sb);
}
}
}

View File

@@ -0,0 +1,15 @@
using System.Xml.Serialization;
using Intromat.PersistentModel;
using Intromat.ViewModels;
namespace Intromat.Nodes
{
[XmlRoot("MainEntryPoint", Namespace = _namespace)]
public sealed class MainEntryPointModel : NodeModelBase
{
public override CodeGenNodeViewModel CreateViewModel()
{
return new MainEntryPointNode();
}
}
}

View File

@@ -0,0 +1,36 @@
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using Splat;
namespace Intromat.Nodes
{
public class MainEntryPointNode : CodeGenNodeViewModel
{
static MainEntryPointNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<MainEntryPointNode>));
}
public MainEntryPointNode()
: base(NodeType.Special)
{
Name = "Main";
OnClickFlow = new CodeGenInputViewModel<IStatement>(EPortType.Execution) { Name = "Entry point" };
Inputs.Add(OnClickFlow);
}
public ValueNodeInputViewModel<IStatement> OnClickFlow { get; }
public override NodeModelBase CreateModel()
{
return new MainEntryPointModel();
}
}
}

View File

@@ -0,0 +1,8 @@
using Intromat.PersistentModel;
namespace Intromat.Nodes.Meshes
{
public abstract class DxMeshModelBase : NodeModelBase
{
}
}

View File

@@ -0,0 +1,97 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Graphics;
using Intromat.Model.Compiler;
using Intromat.Nodes.Code;
using Intromat.Nodes.Meshes;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
using ReactiveUI;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using Splat;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Device = SharpDX.Direct3D11.Device;
namespace Intromat.Nodes.Meshs
{
[CategoryOrder("Mesh", 0)]
public abstract class DxMeshNodeBase : DxNodeBase
{
protected MeshValue? _output;
protected bool _resourcesCreated;
protected DxMeshNodeBase()
{
var meshGroup = new EndpointGroup("Mesh");
Outputs.Add(Output = new CodeGenOutputViewModel<MeshValue?>(EPortType.Mesh)
{
Name = "Output",
Group = meshGroup
});
var dxHost = Locator.Current.GetService<DxHost>()!;
Output.Value = this
.WhenAnyObservable(vm => vm.ParentChanged, vm => vm.AnyInputChanged, (parent, _) => parent)
.Where(parent => parent != null)
.Throttle(TimeSpan.FromMilliseconds(1))
.ObserveOn(dxHost.RenderDispatcher)
.Do(_ => { UpdateFrame(dxHost.Device, dxHost.Device.ImmediateContext); })
.Select(_ => new MeshValue(_output!));
var dxMeshPreviewViewModel = new DxMeshPreviewViewModel(this, Output.Value);
Preview = dxMeshPreviewViewModel;
}
[MemberNotNull(nameof(_output))]
protected bool EnsureBuffers(Device device)
{
return EnsureMesh(device, ref _output);
}
protected bool EnsureMesh(Device device, [NotNull] ref MeshValue? meshValue)
{
meshValue = null!;
//if (false) // TODO: changed
//{
// return true;
//}
return false;
}
protected virtual void UpdateFrame(Device device, DeviceContext deviceContext)
{
if (!_resourcesCreated)
{
_resourcesCreated = true;
CreateDeviceResources(device);
}
EnsureBuffers(device);
}
protected virtual void CreateDeviceResources(Device device)
{
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var meshModel = (DxMeshModelBase)model;
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var meshModel = (DxMeshModelBase)model;
}
public ValueNodeOutputViewModel<MeshValue?> Output { get; }
}
}

View File

@@ -0,0 +1,22 @@
using System;
using System.Xml.Serialization;
using Intromat.ViewModels;
namespace Intromat.Nodes.Textures
{
[XmlRoot("Blend2DColor", Namespace = _namespace)]
public class Blend2DColorModel : DxTextureModelBase
{
public enum EBlendMode
{
Multiply
}
public EBlendMode BlendMode { get; set; }
public override CodeGenNodeViewModel CreateViewModel()
{
return new Blend2DColorNode();
}
}
}

View File

@@ -0,0 +1,129 @@
using System;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using ReactiveUI;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using Splat;
using Buffer = SharpDX.Direct3D11.Buffer;
namespace Intromat.Nodes.Textures
{
public class Blend2DColorNode : DxTextureFilterNodeBase
{
private Buffer? _constantBuffer;
private ComputeShader? _cs;
private const string _computeShader = @"cbuffer cb
{
int m;
}
Texture2D tSource;
Texture2D tTarget;
SamplerState ss;
RWTexture2D<float4> o;
[numthreads(16,16,1)]
void main(in uint3 i : SV_DispatchThreadID)
{
float2 dim;
o.GetDimensions(dim.x, dim.y);
float2 uv = i.xy / dim;
float4 src = tSource.SampleLevel(ss, uv, 0);
float4 dst = tTarget.SampleLevel(ss, uv, 0);
switch (m)
{
case 0: o[i.xy] = src * dst; break;
}
}";
static Blend2DColorNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<Blend2DColorNode>));
}
public Blend2DColorNode()
{
Name = "Blend";
Inputs.Add(Target = new CodeGenInputViewModel<TextureValue?>(EPortType.Texture)
{
Name = "Target",
Group = Width.Group
});
Inputs.Add(BlendMode = new CodeGenInputViewModel<int>(EPortType.None)
{
Name = "Blend mode",
Group = Width.Group,
Editor = BlendModeEditor
});
}
public override NodeModelBase CreateModel()
{
return new Blend2DColorModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var blend2D = (Blend2DColorModel)model;
blend2D.BlendMode = (Blend2DColorModel.EBlendMode)BlendModeEditor.Value;
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var blend2D = (Blend2DColorModel)model;
BlendModeEditor.Value = (int)blend2D.BlendMode;
}
protected override void CreateDeviceResources(Device device)
{
base.CreateDeviceResources(device);
var bufferDesc = new BufferDescription(16, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
_constantBuffer = new Buffer(device, bufferDesc);
var flags = ShaderFlags.None;
#if DEBUG
flags |= ShaderFlags.Debug;
#endif
_cs = new ComputeShader(device, ShaderBytecode.Compile(_computeShader, "main", "cs_5_0", flags).Bytecode.Data);
}
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
{
base.UpdateFrame(device, context);
var srvSource = Input.Value?.ShaderResourceView;
var srvTarget = Target.Value?.ShaderResourceView;
if (srvSource == null || srvTarget == null)
return;
context.MapSubresource(_constantBuffer, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
var ints = new Span<int>((void*)stream.DataPointer, 4);
ints[0] = BlendMode.Value;
context.UnmapSubresource(_constantBuffer, 0);
context.ComputeShader.SetConstantBuffer(0, _constantBuffer);
context.ComputeShader.SetShaderResource(0, srvSource);
context.ComputeShader.SetShaderResource(1, srvTarget);
context.ComputeShader.SetSampler(0, _samplerState);
context.ComputeShader.SetShader(_cs, null, 0);
context.ComputeShader.SetUnorderedAccessView(0, _output!.UnorderedAccessView);
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
context.ComputeShader.SetShaderResource(0, null);
context.ComputeShader.SetShaderResource(1, null);
context.ComputeShader.SetUnorderedAccessView(0, null);
}
public ValueNodeInputViewModel<TextureValue?> Target { get; }
public EnumEditorViewModel BlendModeEditor { get; } = new(typeof(Blend2DColorModel.EBlendMode));
public ValueNodeInputViewModel<int> BlendMode { get; }
}
}

View File

@@ -0,0 +1,39 @@
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.ViewModels;
using NodeNetwork.Toolkit.ValueNode;
using SharpDX.Direct3D11;
namespace Intromat.Nodes.Textures
{
public abstract class DxTextureFilterNodeBase : DxTextureNodeBase
{
protected SamplerState? _samplerState;
protected DxTextureFilterNodeBase()
{
Inputs.Add(Input = new CodeGenInputViewModel<TextureValue?>(EPortType.Texture)
{
Name = "Input",
Group = Width.Group
});
var inputObservable = Input.ValueChanged;
WidthEditor.InputValueProvider = inputObservable.Select(input => input?.Width ?? 0);
HeightEditor.InputValueProvider = inputObservable.Select(input => input?.Height ?? 0);
}
[MemberNotNull(nameof(_samplerState))]
protected override void CreateDeviceResources(Device device)
{
base.CreateDeviceResources(device);
var samplerDesc = SamplerStateDescription.Default();
samplerDesc.Filter = Filter.MinMagMipLinear;
_samplerState = new SamplerState(device, samplerDesc);
}
public ValueNodeInputViewModel<TextureValue?> Input { get; }
}
}

View File

@@ -0,0 +1,12 @@
using Intromat.Nodes.Code;
using Intromat.PersistentModel;
namespace Intromat.Nodes.Textures
{
public abstract class DxTextureModelBase : NodeModelBase
{
public IntLiteralModel Width { get; set; } = null!;
public IntLiteralModel Height { get; set; } = null!;
}
}

View File

@@ -0,0 +1,157 @@
using System;
using System.Diagnostics.CodeAnalysis;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Graphics;
using Intromat.Model.Compiler;
using Intromat.Nodes.Code;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.ViewModels.Previews;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
using ReactiveUI;
using SharpDX.Direct3D11;
using SharpDX.DXGI;
using Splat;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Device = SharpDX.Direct3D11.Device;
namespace Intromat.Nodes.Textures
{
[CategoryOrder("Texture", 0)]
public abstract class DxTextureNodeBase : DxNodeBase
{
protected TextureValue? _output;
protected Texture2DDescription _texDesc;
protected bool _resourcesCreated;
protected DxTextureNodeBase()
{
var textureGroup = new EndpointGroup("Texture");
var parentObservable = this.WhenAnyObservable(vm => vm.ParentChanged).Select(parent => (CodeGenNetworkViewModel?)parent);
Inputs.Add(Width = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.None)
{
Name = "Width",
Group = textureGroup,
Editor = WidthEditor = new()
{
ParentValueProvider = parentObservable.Select(parent => parent?.DefaultWidth ?? -1),
MinValue = -16,
MaxValue = 16,
CustomValue = 0,
RelativeSource = ERelativeSource.Parent
}
});
Inputs.Add(Height = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.None)
{
Name = "Height",
Group = textureGroup,
Editor = HeightEditor = new()
{
ParentValueProvider = parentObservable.Select(parent => parent?.DefaultHeight ?? -1),
MinValue = -16,
MaxValue = 16,
CustomValue = 0,
RelativeSource = ERelativeSource.Parent
}
});
Outputs.Add(Output = new CodeGenOutputViewModel<TextureValue?>(EPortType.Texture)
{
Name = "Output",
Group = textureGroup
});
var dxHost = Locator.Current.GetService<DxHost>()!;
Output.Value = this
.WhenAnyObservable(vm => vm.ParentChanged, vm => vm.AnyInputChanged, (parent, _) => parent)
.Where(parent => parent != null)
.Throttle(TimeSpan.FromMilliseconds(1))
.ObserveOn(dxHost.RenderDispatcher)
.Do(_ => { UpdateFrame(dxHost.Device, dxHost.Device.ImmediateContext); })
.Select(_ => new TextureValue(_output!));
var dxTexturePreviewViewModel = new DxTexturePreviewViewModel(this, Output.Value);
Preview = dxTexturePreviewViewModel;
_texDesc = new Texture2DDescription
{
ArraySize = 1,
Format = Format.R16G16B16A16_Float,
BindFlags = BindFlags.ShaderResource | BindFlags.UnorderedAccess,
Usage = ResourceUsage.Default,
SampleDescription = new(1, 0)
};
}
[MemberNotNull(nameof(_output))]
protected bool EnsureBuffers(Device device)
{
return EnsureTexture(device, ref _output);
}
protected bool EnsureTexture(Device device, [NotNull] ref TextureValue? textureValue)
{
var width = ((IntLiteralValue)Width.Value).EvaluateDimension();
var height = ((IntLiteralValue)Height.Value).EvaluateDimension();
if (_texDesc.Width != width || _texDesc.Height != height || textureValue == null)
{
textureValue?.ShaderResourceView?.Resource?.Dispose();
textureValue?.ShaderResourceView?.Dispose();
textureValue?.UnorderedAccessView?.Dispose();
_texDesc.Width = width;
_texDesc.Height = height;
var texture = new Texture2D(device, _texDesc);
textureValue ??= new TextureValue();
textureValue.Width = width;
textureValue.Height = height;
textureValue.ShaderResourceView = new ShaderResourceView(device, texture);
textureValue.UnorderedAccessView = new UnorderedAccessView(device, texture);
return true;
}
return false;
}
protected virtual void UpdateFrame(Device device, DeviceContext deviceContext)
{
if (!_resourcesCreated)
{
_resourcesCreated = true;
CreateDeviceResources(device);
}
EnsureBuffers(device);
}
protected virtual void CreateDeviceResources(Device device)
{
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var textureModel = (DxTextureModelBase)model;
textureModel.Width = WidthEditor.CreateModel();
textureModel.Height = HeightEditor.CreateModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var textureModel = (DxTextureModelBase)model;
WidthEditor.LoadModel(textureModel.Width);
HeightEditor.LoadModel(textureModel.Height);
}
public DimensionEditorViewModel WidthEditor { get; }
public ValueNodeInputViewModel<ITypedExpression<int>> Width { get; }
public DimensionEditorViewModel HeightEditor { get; }
public ValueNodeInputViewModel<ITypedExpression<int>> Height { get; }
public ValueNodeOutputViewModel<TextureValue?> Output { get; }
}
}

View File

@@ -0,0 +1,23 @@
using ReactiveUI;
using SharpDX.Direct3D11;
namespace Intromat.Nodes.Textures
{
public class SamplerDesc : ReactiveObject
{
private Filter _filter = SamplerStateDescription.Default().Filter;
private TextureAddressMode _address = SamplerStateDescription.Default().AddressU;
public Filter Filter
{
get => _filter;
set => this.RaiseAndSetIfChanged(ref _filter, value);
}
public TextureAddressMode Address
{
get => _address;
set => this.RaiseAndSetIfChanged(ref _address, value);
}
}
}

View File

@@ -0,0 +1,16 @@
using System.Xml.Serialization;
using Intromat.Nodes.Code;
using Intromat.ViewModels;
using SharpDX.Direct3D11;
namespace Intromat.Nodes.Textures
{
[XmlRoot("SamplerLiteral", Namespace = _namespace)]
public sealed class SamplerLiteralModel : LiteralModelBase<SamplerDesc>
{
public override CodeGenNodeViewModel? CreateViewModel()
{
return null;
}
}
}

View File

@@ -0,0 +1,10 @@
using Intromat.Nodes.Code;
using SharpDX.Direct3D11;
namespace Intromat.Nodes.Textures
{
public class SamplerLiteralValue : LiteralValueBase<SamplerDesc>
{
public SamplerState? SamplerState { get; set; }
}
}

View File

@@ -0,0 +1,18 @@
using System.Xml.Serialization;
using Intromat.Nodes.Code;
using Intromat.ViewModels;
namespace Intromat.Nodes.Textures
{
[XmlRoot("ShaderTexture", Namespace = _namespace)]
public sealed class ShaderTextureModel : DxTextureModelBase
{
public StringLiteralModel Source { get; set; } = null!;
public LiteralModelEntry[] InputValues { get; set; } = null!;
public override CodeGenNodeViewModel CreateViewModel()
{
return new ShaderTextureNode();
}
}
}

View File

@@ -0,0 +1,477 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Reactive.Linq;
using DynamicData;
using Intromat.Graphics;
using Intromat.Model.Compiler;
using Intromat.Nodes.Code;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.Views;
using NodeNetwork.ViewModels;
using ReactiveUI;
using SharpDX;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D;
using SharpDX.Direct3D11;
using Splat;
using Buffer = SharpDX.Direct3D11.Buffer;
namespace Intromat.Nodes.Textures
{
public class ShaderTextureNode : DxTextureNodeBase
{
private readonly List<Buffer> _constantBuffers = new();
private ComputeShader? _cs;
private byte[]? _byteCode;
private TextureValue[]? _customOutputs;
private readonly List<(NodeInputViewModel, InputDesc)> _customInputs = new();
private readonly EndpointGroup _group;
private readonly Dictionary<string, object> _inputValues = new();
public record InputDesc(int ConstantBufferIndex, int Offset, Type VariableType, string Name);
static ShaderTextureNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<ShaderTextureNode>));
}
public ShaderTextureNode()
{
Name = "Shader Texture";
_group = new EndpointGroup("Shader Texture");
Inputs.Add(Source = new CodeGenInputViewModel<ITypedExpression<string>>(EPortType.String)
{
Name = "Source",
Group = _group,
Editor = SourceEditor
});
var dxHost = Locator.Current.GetService<DxHost>()!;
Source.ValueChanged
.ObserveOn(dxHost.RenderDispatcher)
.Subscribe(_ =>
{
_resourcesCreated = false;
UpdateFrame(dxHost.Device, dxHost.Device.ImmediateContext);
});
}
public override NodeModelBase CreateModel()
{
return new ShaderTextureModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var shaderTexture = (ShaderTextureModel)model;
shaderTexture.Source = SourceEditor.CreateModel();
shaderTexture.InputValues = _customInputs
.Where(i => i.Item1.Editor != null)
.Select(i => new LiteralModelEntry()
{
Key = i.Item1.Name,
Literal = CreateLiteralModel((dynamic)i.Item1.Editor)
})
.ToArray();
}
private TModel CreateLiteralModel<T, TValue, TModel>(ExpressionEditorBaseViewModel<T, TValue, TModel> editor) where TValue : LiteralValueBase<T>, new() where TModel : LiteralModelBase<T>, new()
{
return editor.CreateModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var shaderTexture = (ShaderTextureModel)model;
SourceEditor.LoadModel(shaderTexture.Source);
foreach (var inputValue in shaderTexture.InputValues)
{
_inputValues[inputValue.Key] = inputValue.Literal;
}
var dxHost = Locator.Current.GetService<DxHost>()!;
_resourcesCreated = true;
CreateDeviceResources(dxHost.Device);
}
protected override void CreateDeviceResources(Device device)
{
foreach (var cb in _constantBuffers)
cb.Dispose();
_constantBuffers.Clear();
var flags = ShaderFlags.None;
#if DEBUG
flags |= ShaderFlags.Debug;
#endif
var errors = new List<string>();
var shader = Source.Value.Evaluate();
if (string.IsNullOrEmpty(shader))
{
errors.Add("Shader is empty");
}
else
{
try
{
var compilationResult = ShaderBytecode.Compile(shader, "main", "cs_5_0", flags);
if (compilationResult.HasErrors)
{
errors.Add(compilationResult.Message);
}
else
{
_byteCode = compilationResult.Bytecode.Data;
var reflection = new ShaderReflection(_byteCode);
var newInputs = new List<InputDesc>();
var newOutputs = new List<InputDesc>();
for (int i = 0; i < reflection.Description.BoundResources; ++i)
{
var resBindingDesc = reflection.GetResourceBindingDescription(i);
switch (resBindingDesc.Type)
{
case ShaderInputType.ConstantBuffer:
{
var cb = reflection.GetConstantBuffer(resBindingDesc.Name);
var cbIndex = resBindingDesc.BindPoint;
var cbSize = cb.Description.Size;
var bufferDesc = new BufferDescription(cbSize, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
_constantBuffers.Add(new Buffer(device, bufferDesc));
for (int j = 0; j < cb.Description.VariableCount; ++j)
{
var variable = cb.GetVariable(j);
var varTypeDesc = variable.GetVariableType()
.Description;
var type = varTypeDesc.Type switch
{
ShaderVariableType.Float => typeof(float),
ShaderVariableType.Int => typeof(int),
ShaderVariableType.Bool => typeof(bool),
_ => null
};
if (type == null)
{
errors.Add($"Constant buffer {resBindingDesc.Name} at slot {cbIndex} has a variable {variable.Description.Name} with unsupported type {varTypeDesc.Type}");
continue;
}
for (int k = 0; k < varTypeDesc.ColumnCount; ++k)
{
var suffix = "" + 'X' + k;
var inputName = varTypeDesc.ColumnCount == 1 ? variable.Description.Name : $"{variable.Description.Name}.{suffix}";
newInputs.Add(new(cbIndex, variable.Description.StartOffset + k * variable.Description.Size, type, inputName));
}
}
break;
}
case ShaderInputType.Texture:
{
var texName = resBindingDesc.Name;
newInputs.Add(new(-1, resBindingDesc.BindPoint, typeof(TextureValue), texName));
break;
}
case ShaderInputType.Sampler:
{
var texName = resBindingDesc.Name;
newInputs.Add(new(-1, resBindingDesc.BindPoint, typeof(SamplerLiteralValue), texName));
break;
}
case ShaderInputType.UnorderedAccessViewRWTyped:
{
var texName = resBindingDesc.Name;
if (resBindingDesc.Dimension != ShaderResourceViewDimension.Texture2D)
{
errors.Add($"UAV {texName} at slot {resBindingDesc.BindPoint} has unsupported dimension {resBindingDesc.Dimension}");
break;
}
newOutputs.Add(new(-1, resBindingDesc.BindPoint, typeof(TextureValue), texName));
break;
}
default:
{
errors.Add($"Resource {resBindingDesc.Name} at slot {resBindingDesc.BindPoint} has unsupported type {resBindingDesc.Type}");
break;
}
}
}
if (newOutputs.Count == 0)
{
errors.Add("The shader doesn't contain any UAV suitable for the texture output");
}
Inputs.Edit(inputs =>
{
var maxSamplerStateIndex = 0;
for (var i = _customInputs.Count - 1; i >= 0; --i)
{
var existingInput = _customInputs[i];
if (newInputs.All(inputDesc => inputDesc.Name != existingInput.Item1.Name))
{
inputs.Remove(existingInput.Item1);
_customInputs.RemoveAt(i);
}
}
foreach (var newInputDesc in newInputs)
{
if (_customInputs.Any(i => i.Item2 == newInputDesc))
continue;
NodeInputViewModel newInput;
if (newInputDesc.VariableType == typeof(float))
{
newInput = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = newInputDesc.Name,
Group = _group,
Editor = new FloatExpressionEditorViewModel { MaxValue = 1 }
};
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is FloatLiteralModel model)
((FloatExpressionEditorViewModel)newInput.Editor).LoadModel(model);
}
else if (newInputDesc.VariableType == typeof(int))
{
newInput = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = newInputDesc.Name,
Group = _group,
Editor = new IntegerExpressionEditorViewModel { MaxValue = 100 }
};
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is IntLiteralModel model)
((IntegerExpressionEditorViewModel)newInput.Editor).LoadModel(model);
}
else if (newInputDesc.VariableType == typeof(bool))
{
newInput = new CodeGenInputViewModel<ITypedExpression<bool>>(EPortType.Boolean)
{
Name = newInputDesc.Name,
Group = _group,
Editor = new BooleanExpressionEditorViewModel()
};
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is BooleanLiteralModel model)
((BooleanExpressionEditorViewModel)newInput.Editor).LoadModel(model);
}
else if (newInputDesc.VariableType == typeof(TextureValue))
{
newInput = new CodeGenInputViewModel<TextureValue>(EPortType.Texture) { Name = newInputDesc.Name };
if (newInputDesc.Offset == 0)
{
var inputObservable = ((CodeGenInputViewModel<TextureValue>)newInput).ValueChanged;
WidthEditor.InputValueProvider = inputObservable.Select(input => input?.Width ?? 0);
HeightEditor.InputValueProvider = inputObservable.Select(input => input?.Height ?? 0);
}
}
else if (newInputDesc.VariableType == typeof(SamplerLiteralValue))
{
maxSamplerStateIndex = Math.Max(maxSamplerStateIndex, newInputDesc.Offset);
newInput = new CodeGenInputViewModel<ITypedExpression<SamplerDesc>>(EPortType.None)
{
Name = newInputDesc.Name,
Group = _group,
Editor = new SamplerEditorViewModel { CustomValue = new SamplerDesc() }
};
if (_inputValues.TryGetValue(newInputDesc.Name, out var inputValue) && inputValue is SamplerLiteralModel model)
((SamplerEditorViewModel)newInput.Editor).LoadModel(model);
}
else
{
continue;
}
_customInputs.Add((newInput, newInputDesc));
inputs.Add(newInput);
}
});
Outputs.Edit(outputs =>
{
if (outputs.Count > newOutputs.Count)
{
if (newOutputs.Count == 0)
{
if (outputs.Count > 1)
outputs.RemoveRange(1, outputs.Count - 1);
outputs[0]
.Name = "Output";
}
else
{
outputs.RemoveRange(newOutputs.Count, outputs.Count - newOutputs.Count);
}
}
var outputIndex = 0;
if (outputIndex < newOutputs.Count)
for (; outputIndex < outputs.Count; ++outputIndex)
outputs[outputIndex]
.Name = newOutputs[outputIndex]
.Name;
for (; outputIndex < newOutputs.Count; ++outputIndex)
{
var newOutputDesc = newOutputs[outputIndex];
NodeOutputViewModel newOutput;
if (newOutputDesc.VariableType == typeof(TextureValue))
newOutput = new CodeGenOutputViewModel<TextureValue>(EPortType.Texture) { Name = newOutputDesc.Name };
else
continue;
outputs.Add(newOutput);
}
_customOutputs = new TextureValue[outputs.Count];
});
}
}
catch (CompilationException e)
{
errors.Add(e.Message);
}
}
_cs?.Dispose();
_cs = errors.Count == 0 ? new ComputeShader(device, _byteCode) : null;
Errors.Edit(e =>
{
e.Clear();
e.AddRange(errors);
});
}
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
{
if (!_resourcesCreated)
{
_resourcesCreated = true;
var dxHost = Locator.Current.GetService<DxHost>()!;
dxHost.MainDispatcher!.Invoke(() => CreateDeviceResources(device));
}
EnsureBuffers(device);
if (_customOutputs == null || _cs == null)
return;
_customOutputs[0] = _output;
for (int i = 1; i < Outputs.Count; ++i)
{
EnsureTexture(device, ref _customOutputs![i]);
}
var cbData = _constantBuffers.Select(cb =>
{
context.MapSubresource(cb, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
return stream.DataPointer;
}).ToArray();
int maxSrvIndex = 0;
foreach (var input in _customInputs)
{
var desc = input.Item2;
if (desc.ConstantBufferIndex >= 0)
{
var dataPtr = cbData[desc.ConstantBufferIndex] + desc.Offset;
if (desc.VariableType == typeof(float))
{
_inputValues[input.Item1.Name] = ((FloatExpressionEditorViewModel)input.Item1.Editor).CreateModel();
*(float*)dataPtr = ((CodeGenInputViewModel<ITypedExpression<float>>)input.Item1).Value.Evaluate();
}
else if (desc.VariableType == typeof(int))
{
_inputValues[input.Item1.Name] = ((IntegerExpressionEditorViewModel)input.Item1.Editor).CreateModel();
*(int*)dataPtr = ((CodeGenInputViewModel<ITypedExpression<int>>)input.Item1).Value.Evaluate();
}
else if (desc.VariableType == typeof(bool))
{
_inputValues[input.Item1.Name] = ((BooleanExpressionEditorViewModel)input.Item1.Editor).CreateModel();
*(bool*)dataPtr = ((CodeGenInputViewModel<ITypedExpression<bool>>)input.Item1).Value.Evaluate();
}
}
else
{
if (desc.VariableType == typeof(TextureValue))
{
maxSrvIndex = Math.Max(maxSrvIndex, desc.Offset);
context.ComputeShader.SetShaderResource(desc.Offset, ((CodeGenInputViewModel<TextureValue>)input.Item1).Value?.ShaderResourceView);
}
else if (desc.VariableType == typeof(SamplerLiteralValue))
{
_inputValues[input.Item1.Name] = ((SamplerEditorViewModel)input.Item1.Editor).CreateModel();
var samplerValue = (SamplerLiteralValue)((CodeGenInputViewModel<ITypedExpression<SamplerDesc>>)input.Item1).Value;
EnsureSamplerState(device, ref samplerValue);
context.ComputeShader.SetSampler(desc.Offset, samplerValue.SamplerState);
}
}
}
for (var i = 0; i < _customOutputs!.Length; i++)
{
var output = _customOutputs![i];
context.ComputeShader.SetUnorderedAccessView(i, output.UnorderedAccessView);
}
for (int i = 0; i < _constantBuffers.Count; ++i)
{
context.UnmapSubresource(_constantBuffers[i], 0);
context.ComputeShader.SetConstantBuffer(i, _constantBuffers[i]);
}
context.ComputeShader.SetShader(_cs, null, 0);
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
for (var i = 0; i < _customOutputs!.Length; i++)
{
context.ComputeShader.SetUnorderedAccessView(i, null);
}
for (var i = 0; i <= maxSrvIndex; i++)
{
context.ComputeShader.SetShaderResource(i, null);
}
}
private void EnsureSamplerState(Device device, ref SamplerLiteralValue samplerLiteralValue)
{
if (samplerLiteralValue.SamplerState == null || samplerLiteralValue.SamplerState.Description.AddressU != samplerLiteralValue.Value.Address || samplerLiteralValue.SamplerState.Description.Filter != samplerLiteralValue.Value.Filter)
{
var desc = SamplerStateDescription.Default();
desc.AddressU = desc.AddressV = desc.AddressW = samplerLiteralValue.Value.Address;
desc.Filter = samplerLiteralValue.Value.Filter;
samplerLiteralValue.SamplerState = new SamplerState(device, desc);
}
}
public ISourceList<string> Errors { get; } = new SourceList<string>();
public CodeGenInputViewModel<ITypedExpression<string>> Source { get; }
public StringExpressionEditorViewModel SourceEditor { get; } = new()
{
CustomValue = @"
cbuffer cb
{
float f;
}
Texture2D t0;
SamplerState ss;
RWTexture2D<float4> o;
[numthreads(16,16,1)]
void main(uint3 i : SV_DispatchThreadID)
{
float2 s;
o.GetDimensions(s.x, s.y);
float2 uv = i.xy/s;
float4 c = t0.SampleLevel(ss, uv, 0);
o[i.xy] = float4(c.xyz, 1);
}"
};
}
}

View File

@@ -0,0 +1,34 @@
using System.Xml.Serialization;
using Intromat.Nodes.Code;
using Intromat.ViewModels;
namespace Intromat.Nodes.Textures
{
[XmlRoot("Shape2DGrayscale", Namespace = _namespace)]
public class Shape2DGrayscaleModel : DxTextureModelBase
{
public enum EPattern
{
Square
}
public IntLiteralModel Tiling { get; set; } = default!;
public EPattern Pattern { get; set; }
public FloatLiteralModel Scale { get; set; } = default!;
public FloatLiteralModel SizeX { get; set; } = default!;
public FloatLiteralModel SizeY { get; set; } = default!;
public FloatLiteralModel Angle { get; set; } = default!;
public BooleanLiteralModel Rotate45 { get; set; } = default!;
public override CodeGenNodeViewModel CreateViewModel()
{
return new Shape2DGrayscaleNode();
}
}
}

View File

@@ -0,0 +1,213 @@
using System;
using System.Diagnostics.CodeAnalysis;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
using ReactiveUI;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using Splat;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Buffer = SharpDX.Direct3D11.Buffer;
namespace Intromat.Nodes.Textures
{
[CategoryOrder("Shape 2D", 1)]
public class Shape2DGrayscaleNode : DxTextureNodeBase
{
private Buffer? _constantBuffer;
private ComputeShader? _cs;
private const string _computeShader = @"cbuffer cb
{
int t;
int p;
float s;
float sx;
float sy;
float a;
int r;
}
float Square(float2 uv)
{
return step(uv.x, sx) * step(-sx, uv.x) * step(uv.y, sy) * step(-sy, uv.y);
}
RWTexture2D<float> o;
[numthreads(16,16,1)]
void main(in uint3 i : SV_DispatchThreadID)
{
float sa, ca;
sincos(a, sa, ca);
float2 dim;
o.GetDimensions(dim.x, dim.y);
float2 uv = i.xy / dim;
if (r > 0)
{
float f = sqrt(2)/2;
uv = uv * 2 - 1;
uv = mul(uv, float2x2(f, -f, f, f));
uv = uv * .5 + .5;
}
uv *= t;
uv = frac(uv);
uv = uv * 2 - 1;
uv /= s;
uv = mul(uv, float2x2(ca, -sa, sa, ca));
float z = 0;
switch (p)
{
case 0: z = Square(uv); break;
}
o[i.xy] = z;
}";
static Shape2DGrayscaleNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<Shape2DGrayscaleNode>));
}
public Shape2DGrayscaleNode()
{
Name = "Shape 2D";
var group = new EndpointGroup("Shape 2D");
Inputs.Add(Tiling = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Tiling",
Group = group,
Editor = TilingEditor
});
Inputs.Add(Pattern = new CodeGenInputViewModel<int>(EPortType.None)
{
Name = "Pattern",
Group = group,
Editor = PatternEditor
});
Inputs.Add(Scale = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Scale",
Group = group,
Editor = ScaleEditor
});
Inputs.Add(SizeX = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Size X",
Group = group,
Editor = SizeXEditor
});
Inputs.Add(SizeY = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Size Y",
Group = group,
Editor = SizeYEditor
});
Inputs.Add(Angle = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Angle",
Group = group,
Editor = AngleEditor
});
Inputs.Add(Rotate45 = new CodeGenInputViewModel<ITypedExpression<bool>>(EPortType.Float)
{
Name = "Rotate 45°",
Group = group,
Editor = Rotate45Editor
});
}
public override NodeModelBase CreateModel()
{
return new Shape2DGrayscaleModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var shape2D = (Shape2DGrayscaleModel)model;
shape2D.Tiling = TilingEditor.CreateModel();
shape2D.Pattern = (Shape2DGrayscaleModel.EPattern)PatternEditor.Value;
shape2D.Scale = ScaleEditor.CreateModel();
shape2D.SizeX = SizeXEditor.CreateModel();
shape2D.SizeY = SizeYEditor.CreateModel();
shape2D.Angle = AngleEditor.CreateModel();
shape2D.Rotate45 = Rotate45Editor.CreateModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var shape2D = (Shape2DGrayscaleModel)model;
TilingEditor.LoadModel(shape2D.Tiling);
PatternEditor.Value = (int)shape2D.Pattern;
ScaleEditor.LoadModel(shape2D.Scale);
SizeXEditor.LoadModel(shape2D.SizeX);
SizeYEditor.LoadModel(shape2D.SizeY);
AngleEditor.LoadModel(shape2D.Angle);
Rotate45Editor.LoadModel(shape2D.Rotate45);
}
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
{
base.UpdateFrame(device, context);
context.MapSubresource(_constantBuffer, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
var floats = new Span<float>((void*)stream.DataPointer, 8);
var ints = new Span<int>((void*)stream.DataPointer, 8);
ints[0] = Tiling.Value.Evaluate();
ints[1] = Pattern.Value;
floats[2] = Scale.Value.Evaluate();
floats[3] = SizeX.Value.Evaluate();
floats[4] = SizeY.Value.Evaluate();
floats[5] = Angle.Value.Evaluate();
ints[6] = Rotate45.Value.Evaluate() ? 1 : 0;
context.UnmapSubresource(_constantBuffer, 0);
context.ComputeShader.SetConstantBuffer(0, _constantBuffer);
context.ComputeShader.SetShader(_cs, null, 0);
context.ComputeShader.SetUnorderedAccessView(0, _output!.UnorderedAccessView);
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
context.ComputeShader.SetUnorderedAccessView(0, null);
}
[MemberNotNull(nameof(_constantBuffer))]
[MemberNotNull(nameof(_cs))]
protected override void CreateDeviceResources(Device device)
{
base.CreateDeviceResources(device);
var bufferDesc = new BufferDescription(32, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
_constantBuffer = new Buffer(device, bufferDesc);
var flags = ShaderFlags.None;
#if DEBUG
flags |= ShaderFlags.Debug;
#endif
_cs = new ComputeShader(device, ShaderBytecode.Compile(_computeShader, "main", "cs_5_0", flags).Bytecode.Data);
}
public IntegerExpressionEditorViewModel TilingEditor { get; } = new() { CustomValue = 1, MinValue = 1, MaxValue = 16 };
public ValueNodeInputViewModel<ITypedExpression<int>> Tiling { get; }
public EnumEditorViewModel PatternEditor { get; } = new(typeof(Shape2DGrayscaleModel.EPattern));
public ValueNodeInputViewModel<int> Pattern { get; }
public FloatExpressionEditorViewModel ScaleEditor { get; } = new() { CustomValue = 1, MaxValue = 1 };
public ValueNodeInputViewModel<ITypedExpression<float>> Scale { get; }
public FloatExpressionEditorViewModel SizeXEditor { get; } = new() { CustomValue = .5f, MaxValue = 1 };
public ValueNodeInputViewModel<ITypedExpression<float>> SizeX { get; }
public FloatExpressionEditorViewModel SizeYEditor { get; } = new() { CustomValue = .5f, MaxValue = 1 };
public ValueNodeInputViewModel<ITypedExpression<float>> SizeY { get; }
public FloatExpressionEditorViewModel AngleEditor { get; } = new() { MaxValue = (float)(2 * Math.PI) };
public ValueNodeInputViewModel<ITypedExpression<float>> Angle { get; }
public BooleanExpressionEditorViewModel Rotate45Editor { get; } = new();
public ValueNodeInputViewModel<ITypedExpression<bool>> Rotate45 { get; }
}
}

View File

@@ -0,0 +1,19 @@
using System.Xml.Serialization;
using Intromat.Nodes.Code;
using Intromat.ViewModels;
namespace Intromat.Nodes.Textures
{
[XmlRoot("SolidColor", Namespace = _namespace)]
public sealed class SolidColorModel : DxTextureModelBase
{
public IntLiteralModel Red { get; set; } = null!;
public IntLiteralModel Green { get; set; } = null!;
public IntLiteralModel Blue { get; set; } = null!;
public override CodeGenNodeViewModel CreateViewModel()
{
return new SolidColorNode();
}
}
}

View File

@@ -0,0 +1,90 @@
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
using ReactiveUI;
using SharpDX.Direct3D11;
using SharpDX.Mathematics.Interop;
using Splat;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
namespace Intromat.Nodes.Textures
{
[CategoryOrder("Solid Color", 1)]
public class SolidColorNode : DxTextureNodeBase
{
static SolidColorNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<SolidColorNode>));
}
public SolidColorNode()
{
Name = "Solid Color";
var group = new EndpointGroup("SolidColor");
Inputs.Add(Red = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Input",
Group = group,
Editor = RedEditor
});
Inputs.Add(Green = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Green",
Group = group,
Editor = GreenEditor
});
Inputs.Add(Blue = new CodeGenInputViewModel<ITypedExpression<int>>(EPortType.Integer)
{
Name = "Blue",
Group = group,
Editor = BlueEditor
});
}
public override NodeModelBase CreateModel()
{
return new SolidColorModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var solidColor = (SolidColorModel)model;
solidColor.Red = RedEditor.CreateModel();
solidColor.Green = GreenEditor.CreateModel();
solidColor.Blue = BlueEditor.CreateModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var solidColor = (SolidColorModel)model;
RedEditor.LoadModel(solidColor.Red);
GreenEditor.LoadModel(solidColor.Green);
BlueEditor.LoadModel(solidColor.Blue);
}
protected override void UpdateFrame(Device device, DeviceContext context)
{
base.UpdateFrame(device, context);
float r = Red.Value.Evaluate() / 255.0f;
float g = Green.Value.Evaluate() / 255.0f;
float b = Blue.Value.Evaluate() / 255.0f;
context.ClearUnorderedAccessView(_output!.UnorderedAccessView, new RawVector4(r, g, b, 1.0f));
}
public IntegerExpressionEditorViewModel RedEditor { get; } = new() { MinValue = 0, MaxValue = 255};
public ValueNodeInputViewModel<ITypedExpression<int>> Red { get; }
public IntegerExpressionEditorViewModel GreenEditor { get; } = new() { MinValue = 0, MaxValue = 255};
public ValueNodeInputViewModel<ITypedExpression<int>> Green { get; }
public IntegerExpressionEditorViewModel BlueEditor { get; } = new() { MinValue = 0, MaxValue = 255};
public ValueNodeInputViewModel<ITypedExpression<int>> Blue { get; }
}
}

View File

@@ -0,0 +1,22 @@
using System.Xml.Serialization;
using Intromat.Nodes.Code;
using Intromat.ViewModels;
namespace Intromat.Nodes.Textures
{
[XmlRoot("Transform2DColor", Namespace = _namespace)]
public sealed class Transform2DColorModel : DxTextureModelBase
{
public BooleanLiteralModel Clamp { get; set; } = default!;
public FloatLiteralModel ScaleX { get; set; } = default!;
public FloatLiteralModel ScaleY { get; set; } = default!;
public FloatLiteralModel OffsetX { get; set; } = default!;
public FloatLiteralModel OffsetY { get; set; } = default!;
public FloatLiteralModel Rotate { get; set; } = default!;
public override CodeGenNodeViewModel CreateViewModel()
{
return new Transform2DColorNode();
}
}
}

View File

@@ -0,0 +1,190 @@
using System;
using System.Diagnostics.CodeAnalysis;
using DynamicData;
using Intromat.Model.Compiler;
using Intromat.PersistentModel;
using Intromat.ViewModels;
using Intromat.ViewModels.Editors;
using Intromat.Views;
using NodeNetwork.Toolkit.ValueNode;
using NodeNetwork.ViewModels;
using ReactiveUI;
using SharpDX.D3DCompiler;
using SharpDX.Direct3D11;
using Splat;
using Xceed.Wpf.Toolkit.PropertyGrid.Attributes;
using Buffer = SharpDX.Direct3D11.Buffer;
namespace Intromat.Nodes.Textures
{
[CategoryOrder("Transform 2D", 1)]
public class Transform2DColorNode : DxTextureFilterNodeBase
{
private Buffer? _constantBuffer;
private ComputeShader? _cs;
private const string _computeShader = @"cbuffer cb
{
float2 s;
float2 p;
float r;
int c;
}
Texture2D t;
SamplerState ss;
RWTexture2D<float4> o;
[numthreads(16,16,1)]
void main(in uint3 i : SV_DispatchThreadID)
{
float sa, ca;
sincos(r, sa, ca);
float2 dim;
o.GetDimensions(dim.x, dim.y);
float2 uv = i.xy / dim;
uv -= p;
uv = uv * 2 - 1;
uv = mul(uv, float2x2(ca, -sa, sa, ca));
uv /= s;
uv = uv * .5 + .5;
if (c == 0) uv = frac(uv);
o[i.xy] = t.SampleLevel(ss, uv, 0);
}";
static Transform2DColorNode()
{
Locator.CurrentMutable.Register(() => new CodeGenNodeView(), typeof(IViewFor<Transform2DColorNode>));
}
public Transform2DColorNode()
{
Name = "Transform 2D";
var group = new EndpointGroup("Transform 2D");
Inputs.Add(Clamp = new CodeGenInputViewModel<ITypedExpression<bool>>(EPortType.Boolean)
{
Name = "Clamp",
Group = group,
Editor = ClampEditor
});
Inputs.Add(ScaleX = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Scale X",
Group = group,
Editor = ScaleXEditor
});
Inputs.Add(ScaleY = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Scale Y",
Group = group,
Editor = ScaleYEditor
});
Inputs.Add(OffsetX = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Offset X",
Group = group,
Editor = OffsetXEditor
});
Inputs.Add(OffsetY = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Offset Y",
Group = group,
Editor = OffsetYEditor
});
Inputs.Add(Rotate = new CodeGenInputViewModel<ITypedExpression<float>>(EPortType.Float)
{
Name = "Rotate",
Group = group,
Editor = RotateEditor
});
}
public override NodeModelBase CreateModel()
{
return new Transform2DColorModel();
}
public override void SaveModel(NodeModelBase model)
{
base.SaveModel(model);
var transform2DColor = (Transform2DColorModel)model;
transform2DColor.ScaleX = ScaleXEditor.CreateModel();
transform2DColor.ScaleY = ScaleYEditor.CreateModel();
transform2DColor.OffsetX = OffsetXEditor.CreateModel();
transform2DColor.OffsetY = OffsetYEditor.CreateModel();
transform2DColor.Rotate = RotateEditor.CreateModel();
transform2DColor.Clamp = ClampEditor.CreateModel();
}
public override void LoadModel(NodeModelBase model)
{
base.LoadModel(model);
var transform2DColor = (Transform2DColorModel)model;
ScaleXEditor.LoadModel(transform2DColor.ScaleX);
ScaleYEditor.LoadModel(transform2DColor.ScaleY);
OffsetXEditor.LoadModel(transform2DColor.OffsetX);
OffsetYEditor.LoadModel(transform2DColor.OffsetY);
RotateEditor.LoadModel(transform2DColor.Rotate);
ClampEditor.LoadModel(transform2DColor.Clamp);
}
[MemberNotNull(nameof(_constantBuffer))]
[MemberNotNull(nameof(_cs))]
protected override void CreateDeviceResources(Device device)
{
base.CreateDeviceResources(device);
var bufferDesc = new BufferDescription(32, ResourceUsage.Dynamic, BindFlags.ConstantBuffer, CpuAccessFlags.Write, ResourceOptionFlags.None, 0);
_constantBuffer = new Buffer(device, bufferDesc);
var flags = ShaderFlags.None;
#if DEBUG
flags |= ShaderFlags.Debug;
#endif
_cs = new ComputeShader(device, ShaderBytecode.Compile(_computeShader, "main", "cs_5_0", flags).Bytecode.Data);
}
protected override unsafe void UpdateFrame(Device device, DeviceContext context)
{
base.UpdateFrame(device, context);
var srv = Input.Value?.ShaderResourceView;
if (srv == null)
return;
context.MapSubresource(_constantBuffer, 0, MapMode.WriteDiscard, MapFlags.None, out var stream);
var floats = new Span<float>((void*)stream.DataPointer, 8);
var ints = new Span<int>((void*)stream.DataPointer, 8);
floats[0] = ScaleX.Value.Evaluate();
floats[1] = ScaleY.Value.Evaluate();
floats[2] = OffsetX.Value.Evaluate();
floats[3] = OffsetY.Value.Evaluate();
floats[4] = Rotate.Value.Evaluate();
ints[5] = Clamp.Value.Evaluate() ? 1 : 0;
context.UnmapSubresource(_constantBuffer, 0);
context.ComputeShader.SetConstantBuffer(0, _constantBuffer);
context.ComputeShader.SetShaderResource(0, srv);
context.ComputeShader.SetSampler(0, _samplerState);
context.ComputeShader.SetShader(_cs, null, 0);
context.ComputeShader.SetUnorderedAccessView(0, _output!.UnorderedAccessView);
context.Dispatch(_texDesc.Width / 16, _texDesc.Height / 16, 1);
context.ComputeShader.SetShaderResource(0, null);
context.ComputeShader.SetUnorderedAccessView(0, null);
}
public ValueNodeInputViewModel<ITypedExpression<bool>> Clamp { get; }
public BooleanExpressionEditorViewModel ClampEditor { get; } = new();
public ValueNodeInputViewModel<ITypedExpression<float>> ScaleX { get; }
public FloatExpressionEditorViewModel ScaleXEditor { get; } = new() { CustomValue = 1, MaxValue = 4 };
public ValueNodeInputViewModel<ITypedExpression<float>> ScaleY { get; }
public FloatExpressionEditorViewModel ScaleYEditor { get; } = new() { CustomValue = 1, MaxValue = 4 };
public ValueNodeInputViewModel<ITypedExpression<float>> OffsetX { get; }
public FloatExpressionEditorViewModel OffsetXEditor { get; } = new() { MinValue = -1, MaxValue = 1 };
public ValueNodeInputViewModel<ITypedExpression<float>> OffsetY { get; }
public FloatExpressionEditorViewModel OffsetYEditor { get; } = new() { MinValue = -1, MaxValue = 1 };
public ValueNodeInputViewModel<ITypedExpression<float>> Rotate { get; }
public FloatExpressionEditorViewModel RotateEditor { get; } = new() { MaxValue = (float)(2 * Math.PI) };
}
}