571 lines
19 KiB
C#
571 lines
19 KiB
C#
// _ExecuteFastBlurPasses.cs
|
|
#nullable enable
|
|
using SharpDX;
|
|
using SharpDX.Direct3D11;
|
|
using SharpDX.Mathematics.Interop;
|
|
using T3.Core.Rendering;
|
|
using T3.Core.Utils;
|
|
using Utilities = T3.Core.Utils.Utilities;
|
|
|
|
namespace Lib.image.fx._;
|
|
|
|
[Guid("46ce6fad-87fe-4d1f-b236-ae644dd1f76c")]
|
|
internal sealed class _ExecuteFastBlurPasses : Instance<_ExecuteFastBlurPasses>
|
|
{
|
|
[Output(Guid = "6D01B25B-0B2F-4E92-9B5E-0F6D69F2CF5B")]
|
|
public readonly Slot<Texture2D?> OutputTexture = new();
|
|
|
|
public _ExecuteFastBlurPasses()
|
|
{
|
|
OutputTexture.UpdateAction += Update;
|
|
}
|
|
|
|
private void Update(EvaluationContext context)
|
|
{
|
|
try
|
|
{
|
|
UpdateSafe(context);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(" Failed to execute Dual Kawase++ blur " + ex.Message, this);
|
|
OutputTexture.Value = SourceTexture.GetValue(context);
|
|
}
|
|
}
|
|
|
|
private void UpdateSafe(EvaluationContext context)
|
|
{
|
|
var device = ResourceManager.Device;
|
|
var deviceContext = device.ImmediateContext;
|
|
|
|
var sourceTexture = SourceTexture.GetValue(context);
|
|
var sourceSrv = SourceTextureSrv.GetValue(context);
|
|
|
|
var vs = FullscreenVS.GetValue(context);
|
|
var downPS = DownsampleBlurPS.GetValue(context);
|
|
var upPS = UpsampleBlurPS.GetValue(context);
|
|
|
|
var linearSampler = LinearSampler.GetValue(context);
|
|
|
|
var stepsIn = Steps.GetValue(context);
|
|
|
|
if (sourceTexture == null
|
|
|| sourceTexture.IsDisposed
|
|
|| sourceSrv == null
|
|
|| sourceSrv.IsDisposed
|
|
|| vs == null
|
|
|| downPS == null
|
|
|| upPS == null
|
|
|| linearSampler == null)
|
|
{
|
|
Log.Warning("DualKawase++ requires valid inputs.", this);
|
|
OutputTexture.Value = sourceTexture;
|
|
return;
|
|
}
|
|
|
|
var initialResolution = new Size2(sourceTexture.Description.Width, sourceTexture.Description.Height);
|
|
var initialFormat = sourceTexture.Description.Format;
|
|
|
|
var steps = ResolveSteps(stepsIn, initialResolution);
|
|
if (!InitializeOrUpdateResources(initialResolution, initialFormat, steps))
|
|
{
|
|
OutputTexture.Value = sourceTexture;
|
|
return;
|
|
}
|
|
|
|
_stateBackup.Save(deviceContext);
|
|
|
|
deviceContext.VertexShader.Set(vs);
|
|
|
|
deviceContext.OutputMerger.BlendState = DefaultRenderingStates.DisabledBlendState;
|
|
deviceContext.OutputMerger.DepthStencilState = DefaultRenderingStates.DisabledDepthStencilState;
|
|
deviceContext.Rasterizer.State = DefaultRenderingStates.DefaultRasterizerState;
|
|
deviceContext.InputAssembler.PrimitiveTopology = SharpDX.Direct3D.PrimitiveTopology.TriangleList;
|
|
|
|
// Downsample + blur
|
|
var lastSrv = sourceSrv;
|
|
var lastRes = initialResolution;
|
|
|
|
deviceContext.PixelShader.SetSampler(0, linearSampler);
|
|
|
|
for (var level = 0; level < steps; level++)
|
|
{
|
|
var dst = _levels[level];
|
|
if (dst == null)
|
|
continue;
|
|
|
|
var dstRes = dst.Resolution;
|
|
|
|
deviceContext.OutputMerger.SetTargets(dst.RTV);
|
|
deviceContext.Rasterizer.SetViewport(new RawViewportF
|
|
{
|
|
X = 0, Y = 0,
|
|
Width = dstRes.Width, Height = dstRes.Height,
|
|
MinDepth = 0, MaxDepth = 1
|
|
});
|
|
|
|
deviceContext.PixelShader.Set(downPS);
|
|
deviceContext.PixelShader.SetShaderResource(0, lastSrv);
|
|
|
|
_downParams.InvSrcSize = new Vector2(1f / Math.Max(1, lastRes.Width), 1f / Math.Max(1, lastRes.Height));
|
|
_downParams.OffsetPx = 1.0f; // conservative default; adjust later if you want “stronger per step”
|
|
ResourceManager.SetupConstBuffer(_downParams, ref _downParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _downParamsBuffer);
|
|
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
|
|
lastSrv = dst.SRV;
|
|
lastRes = dstRes;
|
|
}
|
|
|
|
// Upsample + blur (Dual Kawase++): write back into higher levels, final into full-res output
|
|
deviceContext.PixelShader.Set(upPS);
|
|
|
|
// Up from smallest -> ... -> half res
|
|
for (var level = steps - 2; level >= 0; level--)
|
|
{
|
|
var low = _levels[level + 1];
|
|
var dst = _levels[level];
|
|
if (low == null || dst == null)
|
|
continue;
|
|
|
|
deviceContext.OutputMerger.SetTargets(dst.RTV);
|
|
deviceContext.Rasterizer.SetViewport(new RawViewportF
|
|
{
|
|
X = 0, Y = 0,
|
|
Width = dst.Resolution.Width, Height = dst.Resolution.Height,
|
|
MinDepth = 0, MaxDepth = 1
|
|
});
|
|
|
|
deviceContext.PixelShader.SetShaderResource(0, low.SRV);
|
|
|
|
FillUpsampleKernel(stageIndex: (steps - 2) - level, stageCount: steps, low.Resolution);
|
|
ResourceManager.SetupConstBuffer(_upParams, ref _upParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _upParamsBuffer);
|
|
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
}
|
|
|
|
// Final up to full resolution
|
|
if (_fullResOutput != null && steps > 0)
|
|
{
|
|
var low = _levels[0];
|
|
if (low != null)
|
|
{
|
|
deviceContext.OutputMerger.SetTargets(_fullResOutput.RTV);
|
|
deviceContext.Rasterizer.SetViewport(new RawViewportF
|
|
{
|
|
X = 0, Y = 0,
|
|
Width = _fullResOutput.Resolution.Width, Height = _fullResOutput.Resolution.Height,
|
|
MinDepth = 0, MaxDepth = 1
|
|
});
|
|
|
|
deviceContext.PixelShader.SetShaderResource(0, low.SRV);
|
|
|
|
FillUpsampleKernel(stageIndex: steps - 1, stageCount: steps, low.Resolution);
|
|
ResourceManager.SetupConstBuffer(_upParams, ref _upParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _upParamsBuffer);
|
|
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
|
|
OutputTexture.Value = _fullResOutput.Texture;
|
|
}
|
|
else
|
|
{
|
|
OutputTexture.Value = sourceTexture;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
OutputTexture.Value = sourceTexture;
|
|
}
|
|
|
|
_stateBackup.Restore(deviceContext);
|
|
}
|
|
|
|
private static int ResolveSteps(int stepsIn, Size2 res)
|
|
{
|
|
if (stepsIn > 0)
|
|
return stepsIn.Clamp(1, 12);
|
|
|
|
var minDim = Math.Min(res.Width, res.Height);
|
|
if (minDim <= 1)
|
|
return 1;
|
|
|
|
// leave a bit of headroom; avoids spending time on 1x1 tail by default
|
|
var auto = (int)MathF.Floor(MathF.Log2(minDim)) - 2;
|
|
return Math.Max(1, auto).Clamp(1, 12);
|
|
}
|
|
|
|
|
|
private void FillUpsampleKernel(int stageIndex, int stageCount, Size2 lowRes)
|
|
{
|
|
var t = stageCount <= 1 ? 1f : (float)stageIndex / (stageCount - 1); // 0..1 from deep mip -> final
|
|
var wideC = 2f;
|
|
var wideCard = 2f;
|
|
var wideDiag = 2f;
|
|
|
|
// Tight: strong center, lighter diagonals
|
|
var tightC = 8f;
|
|
var tightCard = 2f;
|
|
var tightDiag = 1f;
|
|
|
|
var c = Lerp(wideC, tightC, t);
|
|
var card = Lerp(wideCard, tightCard, t);
|
|
var diag = Lerp(wideDiag, tightDiag, t);
|
|
|
|
var sum = c + 4f * card + 4f * diag;
|
|
var inv = sum > 1e-8f ? 1f / sum : 1f;
|
|
|
|
_upParams.InvLowSize = new Vector2(1f / Math.Max(1, lowRes.Width), 1f / Math.Max(1, lowRes.Height));
|
|
_upParams.OffsetPx = 1.0f; // you can later vary this per stage if desired
|
|
_upParams.WCenter = c * inv;
|
|
_upParams.WCard = card * inv;
|
|
_upParams.WDiag = diag * inv;
|
|
}
|
|
|
|
// Gain/Bias function mapping [0,1] -> [0,1]
|
|
// private static float ApplyGainAndBias(float x, float gain, float bias)
|
|
// {
|
|
// x = x.Clamp(0f, 1f);
|
|
//
|
|
// // Bias
|
|
// var b = bias;
|
|
// var y = x / (((1f / b - 2f) * (1f - x)) + 1f);
|
|
//
|
|
// // Gain (symmetric around 0.5)
|
|
// var g = gain;
|
|
// if (y < 0.5f)
|
|
// return 0.5f * (y / (((1f / g - 2f) * (1f - 2f * y)) + 1f));
|
|
//
|
|
// var y2 = 1f - y;
|
|
// var r = 0.5f * (y2 / (((1f / g - 2f) * (1f - 2f * y2)) + 1f));
|
|
// return 1f - r;
|
|
// }
|
|
|
|
private static float Lerp(float a, float b, float t) => a + (b - a) * t;
|
|
|
|
private sealed class RenderTargetSet
|
|
{
|
|
public RenderTargetSet(Device device, Texture2DDescription desc)
|
|
{
|
|
Resolution = new Size2(desc.Width, desc.Height);
|
|
Texture = Texture2D.CreateTexture2D(desc);
|
|
RTV = new RenderTargetView(device, Texture);
|
|
SRV = new ShaderResourceView(device, Texture);
|
|
}
|
|
|
|
public Texture2D Texture;
|
|
public RenderTargetView RTV;
|
|
public ShaderResourceView SRV;
|
|
public readonly Size2 Resolution;
|
|
|
|
public void Dispose()
|
|
{
|
|
Utilities.Dispose(ref Texture);
|
|
Utilities.Dispose(ref RTV);
|
|
Utilities.Dispose(ref SRV);
|
|
}
|
|
}
|
|
|
|
private readonly List<RenderTargetSet?> _levels = new();
|
|
private RenderTargetSet? _fullResOutput;
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
private struct DownParams
|
|
{
|
|
[FieldOffset(0)] public Vector2 InvSrcSize;
|
|
[FieldOffset(8)] public float OffsetPx;
|
|
[FieldOffset(12)] public float _pad0;
|
|
}
|
|
|
|
private DownParams _downParams;
|
|
private Buffer? _downParamsBuffer;
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = 32)]
|
|
private struct UpParams
|
|
{
|
|
[FieldOffset(0)] public Vector2 InvLowSize;
|
|
[FieldOffset(8)] public float OffsetPx;
|
|
[FieldOffset(12)] public float WCenter;
|
|
[FieldOffset(16)] public float WCard;
|
|
[FieldOffset(20)] public float WDiag;
|
|
[FieldOffset(24)] public Vector2 _pad0;
|
|
}
|
|
|
|
private UpParams _upParams;
|
|
private Buffer? _upParamsBuffer;
|
|
|
|
private Size2 _lastResolution = Size2.Zero;
|
|
private SharpDX.DXGI.Format _lastFormat = SharpDX.DXGI.Format.Unknown;
|
|
private int _lastSteps = -1;
|
|
|
|
private bool InitializeOrUpdateResources(Size2 initialResolution, SharpDX.DXGI.Format initialFormat, int steps)
|
|
{
|
|
var needsRecreate =
|
|
_fullResOutput?.Texture == null ||
|
|
_fullResOutput.Texture.IsDisposed ||
|
|
_levels.Count != steps ||
|
|
_lastResolution != initialResolution ||
|
|
_lastFormat != initialFormat ||
|
|
_lastSteps != steps ||
|
|
(_levels.Count > 0 && (_levels[0]?.Texture == null || _levels[0]!.Texture.IsDisposed));
|
|
|
|
var needsBuffers =
|
|
_downParamsBuffer == null || _downParamsBuffer.IsDisposed ||
|
|
_upParamsBuffer == null || _upParamsBuffer.IsDisposed;
|
|
|
|
if (!needsRecreate && !needsBuffers)
|
|
return true;
|
|
|
|
if (needsRecreate)
|
|
{
|
|
CleanupResources();
|
|
}
|
|
else
|
|
{
|
|
Utilities.Dispose(ref _downParamsBuffer);
|
|
Utilities.Dispose(ref _upParamsBuffer);
|
|
}
|
|
|
|
try
|
|
{
|
|
var device = ResourceManager.Device;
|
|
|
|
if (needsRecreate)
|
|
{
|
|
var fullDesc = new Texture2DDescription
|
|
{
|
|
Width = initialResolution.Width,
|
|
Height = initialResolution.Height,
|
|
Format = initialFormat,
|
|
BindFlags = BindFlags.RenderTarget | BindFlags.ShaderResource,
|
|
Usage = ResourceUsage.Default,
|
|
CpuAccessFlags = CpuAccessFlags.None,
|
|
OptionFlags = ResourceOptionFlags.None,
|
|
MipLevels = 1,
|
|
ArraySize = 1,
|
|
SampleDescription = new SharpDX.DXGI.SampleDescription(1, 0)
|
|
};
|
|
|
|
_fullResOutput = new RenderTargetSet(device, fullDesc);
|
|
|
|
var cur = initialResolution;
|
|
for (var i = 0; i < steps; i++)
|
|
{
|
|
cur.Width = Math.Max(1, cur.Width / 2);
|
|
cur.Height = Math.Max(1, cur.Height / 2);
|
|
|
|
var levelDesc = fullDesc;
|
|
levelDesc.Width = cur.Width;
|
|
levelDesc.Height = cur.Height;
|
|
|
|
_levels.Add(new RenderTargetSet(device, levelDesc));
|
|
}
|
|
|
|
_lastResolution = initialResolution;
|
|
_lastFormat = initialFormat;
|
|
_lastSteps = steps;
|
|
}
|
|
|
|
if (_downParamsBuffer == null)
|
|
ResourceManager.SetupConstBuffer(default(DownParams), ref _downParamsBuffer);
|
|
|
|
if (_upParamsBuffer == null)
|
|
ResourceManager.SetupConstBuffer(default(UpParams), ref _upParamsBuffer);
|
|
|
|
if (_downParamsBuffer == null || _upParamsBuffer == null)
|
|
throw new Exception("Failed to create constant buffers.");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error("Failed to create DualKawase++ resources: " + e.Message, this);
|
|
CleanupResources();
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
private void CleanupResources()
|
|
{
|
|
foreach (var set in _levels)
|
|
set?.Dispose();
|
|
_levels.Clear();
|
|
|
|
_fullResOutput?.Dispose();
|
|
_fullResOutput = null;
|
|
|
|
Utilities.Dispose(ref _downParamsBuffer);
|
|
Utilities.Dispose(ref _upParamsBuffer);
|
|
|
|
_lastResolution = Size2.Zero;
|
|
_lastFormat = SharpDX.DXGI.Format.Unknown;
|
|
_lastSteps = -1;
|
|
}
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
if (disposing)
|
|
{
|
|
CleanupResources();
|
|
_stateBackup.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
private readonly D3D11StateBackup _stateBackup = new();
|
|
|
|
private sealed class D3D11StateBackup : IDisposable
|
|
{
|
|
public void Save(DeviceContext context)
|
|
{
|
|
if (_isSaved) return;
|
|
|
|
_topology = context.InputAssembler.PrimitiveTopology;
|
|
|
|
_vertexShader = context.VertexShader.Get();
|
|
_geometryShader = context.GeometryShader.Get();
|
|
|
|
var ps = context.PixelShader;
|
|
_pixelShader = ps.Get();
|
|
_psConstantBuffers = ps.GetConstantBuffers(0, 1);
|
|
_psShaderResourceViews = ps.GetShaderResources(0, 1);
|
|
_psSamplerStates = ps.GetSamplers(0, 1);
|
|
|
|
_rasterizerState = context.Rasterizer.State;
|
|
_viewports = context.Rasterizer.GetViewports<RawViewportF>();
|
|
|
|
_blendState = context.OutputMerger.GetBlendState(out _blendFactor, out _sampleMask);
|
|
_prevRenderTargetViews = context.OutputMerger.GetRenderTargets(1);
|
|
context.OutputMerger.GetRenderTargets(out _depthStencilView);
|
|
|
|
_isSaved = true;
|
|
}
|
|
|
|
public void Restore(DeviceContext context)
|
|
{
|
|
if (!_isSaved) return;
|
|
|
|
context.InputAssembler.PrimitiveTopology = _topology;
|
|
|
|
context.VertexShader.Set(_vertexShader);
|
|
context.GeometryShader.Set(_geometryShader);
|
|
|
|
var ps = context.PixelShader;
|
|
ps.Set(_pixelShader);
|
|
ps.SetConstantBuffers(0, _psConstantBuffers.Length, _psConstantBuffers);
|
|
ps.SetShaderResources(0, _psShaderResourceViews.Length, _psShaderResourceViews);
|
|
ps.SetSamplers(0, _psSamplerStates.Length, _psSamplerStates);
|
|
|
|
context.Rasterizer.State = _rasterizerState;
|
|
context.Rasterizer.SetViewports(_viewports, _viewports?.Length ?? 0);
|
|
_viewports = null;
|
|
|
|
context.OutputMerger.SetBlendState(_blendState, _blendFactor, _sampleMask);
|
|
|
|
if (_prevRenderTargetViews.Length > 0)
|
|
context.OutputMerger.SetRenderTargets(_depthStencilView, _prevRenderTargetViews);
|
|
|
|
foreach (var rtv in _prevRenderTargetViews)
|
|
rtv?.Dispose();
|
|
|
|
_isSaved = false;
|
|
Dispose();
|
|
}
|
|
|
|
public void Dispose()
|
|
{
|
|
if (_isSaved)
|
|
{
|
|
Utilities.Dispose(ref _vertexShader);
|
|
Utilities.Dispose(ref _geometryShader);
|
|
Utilities.Dispose(ref _pixelShader);
|
|
|
|
for (var i = 0; i < _psConstantBuffers.Length; i++)
|
|
{
|
|
Utilities.Dispose(ref _psConstantBuffers[i]);
|
|
_psConstantBuffers[i] = null;
|
|
}
|
|
|
|
for (var i = 0; i < _psSamplerStates.Length; i++)
|
|
{
|
|
Utilities.Dispose(ref _psSamplerStates[i]);
|
|
_psSamplerStates[i] = null;
|
|
}
|
|
|
|
Utilities.Dispose(ref _rasterizerState);
|
|
Utilities.Dispose(ref _blendState);
|
|
Utilities.Dispose(ref _depthStencilState);
|
|
|
|
for (var i = 0; i < _prevRenderTargetViews.Length; i++)
|
|
{
|
|
Utilities.Dispose(ref _prevRenderTargetViews[i]);
|
|
_prevRenderTargetViews[i] = null;
|
|
}
|
|
|
|
Utilities.Dispose(ref _depthStencilView);
|
|
}
|
|
|
|
_vertexShader = null;
|
|
_geometryShader = null;
|
|
_pixelShader = null;
|
|
|
|
if (_psShaderResourceViews.Length > 0)
|
|
_psShaderResourceViews[0] = null;
|
|
|
|
_viewports = null;
|
|
_isSaved = false;
|
|
}
|
|
|
|
private SharpDX.Direct3D.PrimitiveTopology _topology;
|
|
|
|
private SharpDX.Direct3D11.VertexShader? _vertexShader;
|
|
private SharpDX.Direct3D11.GeometryShader? _geometryShader;
|
|
|
|
private SharpDX.Direct3D11.PixelShader? _pixelShader;
|
|
private Buffer?[] _psConstantBuffers = new Buffer?[1];
|
|
private ShaderResourceView?[] _psShaderResourceViews = new ShaderResourceView?[1];
|
|
private SamplerState?[] _psSamplerStates = new SamplerState?[1];
|
|
|
|
private RasterizerState? _rasterizerState;
|
|
private RawViewportF[]? _viewports;
|
|
|
|
private BlendState? _blendState;
|
|
private RawColor4 _blendFactor;
|
|
private int _sampleMask;
|
|
private DepthStencilState? _depthStencilState;
|
|
|
|
private RenderTargetView?[] _prevRenderTargetViews = new RenderTargetView?[1];
|
|
private DepthStencilView? _depthStencilView;
|
|
|
|
private bool _isSaved;
|
|
}
|
|
|
|
[Input(Guid = "692BC2F0-68F2-45CA-A0FB-CD1C5D08E982")]
|
|
public readonly InputSlot<T3.Core.DataTypes.Texture2D> SourceTexture = new();
|
|
|
|
[Input(Guid = "98E88D02-3B78-403C-B9C9-B5ECF8565ACD")]
|
|
public readonly InputSlot<ShaderResourceView> SourceTextureSrv = new();
|
|
|
|
[Input(Guid = "7F79B69A-5DD8-4C49-9B60-2A2D4C2B5A0F")]
|
|
public readonly InputSlot<int> Steps = new(); // 0 => auto
|
|
|
|
|
|
[Input(Guid = "E6A25147-0739-4416-E8C6-2745C32AD416")]
|
|
public readonly InputSlot<T3.Core.DataTypes.VertexShader> FullscreenVS = new();
|
|
|
|
[Input(Guid = "D0B64C21-46C5-45A3-9F68-2E7B2B2CF3A1")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> DownsampleBlurPS = new();
|
|
|
|
[Input(Guid = "B82C9D56-19D6-4D2B-9E6F-1A1B1CBE9A25")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> UpsampleBlurPS = new();
|
|
|
|
[Input(Guid = "5D19C8BE-7EA0-4B8D-5F3D-9EBB3A914B8D")]
|
|
public readonly InputSlot<SamplerState> LinearSampler = new();
|
|
}
|