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,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);
}"
};
}
}