Files
2026-07-13 13:13:17 +08:00

174 lines
6.2 KiB
C#

#nullable enable
using SharpDX;
using SharpDX.Direct3D11;
using Utilities = T3.Core.Utils.Utilities;
namespace Lib.point.combine;
[Guid("56f7cf15-678d-4527-a328-8666a80882d0")]
public sealed class _ExecuteCombineBuffers : Instance<_ExecuteCombineBuffers>
{
[Output(Guid = "d6770718-842e-441d-a5f6-db9b2a20839b")]
public readonly Slot<BufferWithViews?> Output = new();
public _ExecuteCombineBuffers()
{
Output.UpdateAction += Update;
}
private record struct InputBufferStatus(bool IsDirty, BufferWithViews Buffer, int StartIndex, int Length);
private readonly List<InputBufferStatus> _inputBuffers = [];
[SuppressMessage("ReSharper", "NotAccessedField.Local")]
[StructLayout(LayoutKind.Sequential, Pack = 4)]
private struct IndexDataForPass
{
public int StartIndex;
public int Length;
private Vector2 _padding; // pad to 16 bytes
}
private void Update(EvaluationContext context)
{
// Fetch inputs and prepare
var connections = InputBuffers.GetCollectedTypedInputs();
_computeShader = ComputeShader.GetValue(context);
if (connections.Count == 0 || _computeShader == null)
{
Output.Value = null;
return;
}
// Compute total size
var totalLength = 0;
var usedStride = -1;
_inputBuffers.Clear();
for (var connectionIndex = 0; connectionIndex < connections.Count; connectionIndex++)
{
var input = connections[connectionIndex];
var isDirty = input.DirtyFlag.IsDirty;
var bufferWithViews = input.GetValue(context);
if (bufferWithViews is not { Srv.IsDisposed: false })
continue;
var inputStride = bufferWithViews.Buffer.Description.StructureByteStride;
if (usedStride == -1)
{
usedStride = inputStride;
}
else if (inputStride != usedStride)
{
Log.Warning($"Ignoring buffer #{connectionIndex} not matching stride of {usedStride}. Has {inputStride} bytes", this);
continue;
}
//var sizeInBytes = bufferWithViews.Srv.Description.Buffer.ElementCount * inputStride;
//var length = sizeInBytes / 4; // number of uints
var length = bufferWithViews.Srv.Description.Buffer.ElementCount;
_inputBuffers.Add(new InputBufferStatus
{
IsDirty = isDirty,
Length = length, // as count of uints
StartIndex = totalLength,
Buffer = bufferWithViews
});
totalLength += length;
}
var device = ResourceManager.Device;
// Setup or update target buffer
ResourceManager.SetupStructuredBuffer(totalLength * usedStride, usedStride, ref _resultBuffer.Buffer);
ResourceManager.CreateStructuredBufferSrv(_resultBuffer.Buffer, ref _resultBuffer.Srv);
ResourceManager.CreateStructuredBufferUav(_resultBuffer.Buffer, UnorderedAccessViewBufferFlags.None, ref _resultBuffer.Uav);
// Setup pass parameter buffer (can be re-used for all passes)
if (_passParamBuffer == null)
SetupPassParamBuffer(0, 0);
var csStage = device.ImmediateContext.ComputeShader;
csStage.Set(_computeShader);
csStage.SetConstantBuffer(0, _passParamBuffer);
csStage.SetUnorderedAccessView(0, _resultBuffer.Uav);
foreach (var bufferState in _inputBuffers)
{
csStage.SetShaderResources(0, 1, bufferState.Buffer.Srv);
SetupPassParamBuffer(bufferState.StartIndex, bufferState.Length);
// Dispatch the shader
const int threadGroupSizeX = 256;
device.ImmediateContext.Dispatch(bufferState.Length / threadGroupSizeX + 1, 1, 1);
// Unbind pass
csStage.SetShaderResource(0, null);
}
Output.Value = _resultBuffer;
// Unbind resources
csStage.SetUnorderedAccessView(0, null);
csStage.SetConstantBuffer(0, null);
// Make sure the op can be cached
InputBuffers.DirtyFlag.Clear();
}
private void SetupPassParamBuffer(int startIndex, int length)
{
_passData[0].StartIndex = startIndex;
_passData[0].Length = length;
using var data = new DataStream(PassParamSizeInBytes, true, true);
data.WriteRange(_passData);
data.Position = 0;
if (_passParamBuffer == null)
{
var bufferDesc = new BufferDescription
{
Usage = ResourceUsage.Default,
SizeInBytes = PassParamSizeInBytes,
BindFlags = BindFlags.ConstantBuffer
};
_passParamBuffer = new Buffer(ResourceManager.Device, data, bufferDesc);
}
else
{
ResourceManager.Device
.ImmediateContext
.UpdateSubresource(new DataBox(data.DataPointer, 0, 0),
_passParamBuffer,
0);
}
}
protected override void Dispose(bool isDisposing)
{
if (!isDisposing)
return;
Utilities.Dispose(ref _passParamBuffer);
Utilities.Dispose(ref _resultBuffer);
}
private const int PassParamSizeInBytes = 16; // min required
private Buffer? _passParamBuffer;
private BufferWithViews _resultBuffer = new();
private readonly IndexDataForPass[] _passData = new IndexDataForPass[1];
private SharpDX.Direct3D11.ComputeShader? _computeShader;
[Input(Guid = "c8a5769e-2536-4caa-8380-22fbeed1ef12")]
public readonly MultiInputSlot<BufferWithViews> InputBuffers = new();
[Input(Guid = "d91e52f2-52c6-4533-ac14-f5b2ce8b4c0f")]
public readonly InputSlot<T3.Core.DataTypes.ComputeShader> ComputeShader = new();
}