port from perforce
This commit is contained in:
477
intromat/Intromat/Nodes/Textures/ShaderTextureNode.cs
Normal file
477
intromat/Intromat/Nodes/Textures/ShaderTextureNode.cs
Normal 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);
|
||||
}"
|
||||
};
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user