856 lines
34 KiB
C#
856 lines
34 KiB
C#
#nullable enable
|
|
using System.Diagnostics;
|
|
using SharpDX;
|
|
using SharpDX.Direct3D11;
|
|
using SharpDX.Mathematics.Interop;
|
|
using T3.Core.Rendering;
|
|
using T3.Core.Utils;
|
|
using Utilities = T3.Core.Utils.Utilities;
|
|
|
|
// ReSharper disable RedundantNameQualifier
|
|
// ReSharper disable InconsistentNaming
|
|
|
|
namespace Lib.image.fx._;
|
|
|
|
[Guid("9d42dbe7-34a5-4165-877d-6f9c1c675b60")]
|
|
internal sealed class _ExecuteBloomPasses : Instance<_ExecuteBloomPasses>
|
|
{
|
|
[Output(Guid = "300c319d-86e8-47ce-9597-e81c5a008c8f")]
|
|
public readonly Slot<Texture2D?> OutputTexture = new();
|
|
|
|
public _ExecuteBloomPasses()
|
|
{
|
|
OutputTexture.UpdateAction += Update;
|
|
}
|
|
|
|
private void Update(EvaluationContext context)
|
|
{
|
|
try
|
|
{
|
|
UpdateSave(context);
|
|
}
|
|
catch (Exception ex)
|
|
{
|
|
Log.Warning(" Failed to execute bloom " + ex.Message, this);
|
|
}
|
|
}
|
|
|
|
private void UpdateSave(EvaluationContext context)
|
|
{
|
|
var device = ResourceManager.Device;
|
|
var deviceContext = device.ImmediateContext;
|
|
|
|
// Get Inputs
|
|
var sourceTexture = SourceTexture.GetValue(context);
|
|
var vs = FullscreenVS.GetValue(context);
|
|
var brightPassPS = BrightPassPS.GetValue(context);
|
|
var downSamplePS = DownsamplePS.GetValue(context);
|
|
var blurPS = SeparableBlurPS.GetValue(context);
|
|
var upsampleAddPS = UpsampleAddPS.GetValue(context);
|
|
var copyPS = CopyPS.GetValue(context);
|
|
var pointSampler = PointSampler.GetValue(context);
|
|
var linearSampler = LinearSampler.GetValue(context);
|
|
var sourceSrv = SourceTextureSrv.GetValue(context);
|
|
var colorWeights = ColorWeights.GetValue(context);
|
|
var colorGradient = BlurGradient.GetValue(context);
|
|
|
|
var threshold = Threshold.GetValue(context);
|
|
var intensity = Intensity.GetValue(context);
|
|
var blurOffset = BlurOffset.GetValue(context);
|
|
var levels = Levels.GetValue(context).Clamp(1, 10);
|
|
var gainAndBias = GainAndBias.GetValue(context);
|
|
var clamp = Clamp.GetValue(context);
|
|
|
|
// --- Validation ---
|
|
if (sourceTexture == null
|
|
|| sourceTexture.IsDisposed
|
|
|| sourceSrv == null
|
|
|| sourceSrv.IsDisposed
|
|
|| vs == null
|
|
|| brightPassPS == null
|
|
|| downSamplePS == null
|
|
|| blurPS == null
|
|
|| upsampleAddPS == null
|
|
|| copyPS == null
|
|
|| pointSampler == null
|
|
|| linearSampler == null
|
|
)
|
|
{
|
|
Log.Warning("BloomEffect requires valid inputs.", this);
|
|
OutputTexture.Value = sourceTexture;
|
|
return;
|
|
}
|
|
|
|
var initialResolution = new Size2(sourceTexture.Description.Width,
|
|
sourceTexture.Description.Height
|
|
);
|
|
var initialFormat = sourceTexture.Description.Format;
|
|
|
|
// This handles texture creation/recreation based on res/format/levels
|
|
var resourcesReady = InitializeOrUpdateResources(initialResolution, initialFormat, levels);
|
|
if (!resourcesReady)
|
|
{
|
|
OutputTexture.Value = sourceTexture;
|
|
return;
|
|
}
|
|
|
|
Debug.Assert(_brightPassTarget != null);
|
|
Debug.Assert(_compositeTarget != null);
|
|
|
|
// --- Update Level Intensities if Shape or Levels changed ---
|
|
if (gainAndBias != _lastGainAndBias || levels != _lastLevels || _levelIntensities.Count != levels) // Check count too for safety
|
|
{
|
|
CalculateDistribution(gainAndBias, levels, ref _levelIntensities);
|
|
_lastGainAndBias = gainAndBias;
|
|
}
|
|
|
|
// Check if calculation failed or lists mismatch
|
|
if (_levelIntensities.Count != levels)
|
|
{
|
|
Log.Warning($"Bloom level intensity calculation failed or list size mismatch.", this);
|
|
OutputTexture.Value = sourceTexture;
|
|
return;
|
|
}
|
|
|
|
_stateBackup.Save(deviceContext);
|
|
|
|
// --- Save State & Prepare Base State ---
|
|
var vsStage = deviceContext.VertexShader;
|
|
vsStage.Set(vs);
|
|
|
|
// ... (set common states) ...
|
|
device.ImmediateContext.OutputMerger.BlendState = DefaultRenderingStates.DisabledBlendState;
|
|
device.ImmediateContext.OutputMerger.DepthStencilState = DefaultRenderingStates.DisabledDepthStencilState;
|
|
device.ImmediateContext.Rasterizer.State = DefaultRenderingStates.DefaultRasterizerState;
|
|
|
|
device.ImmediateContext.InputAssembler.PrimitiveTopology = PrimitiveTopology.TriangleList;
|
|
|
|
// --- Pipeline Steps ---
|
|
|
|
// 1. Bright Pass
|
|
// ... set RTV, viewport, PS, Sampler, SRV, update/set CB ... draw ... unbind ...
|
|
{
|
|
/* ... Bright Pass Code ... */
|
|
deviceContext.OutputMerger.SetTargets(_brightPassTarget.RTV);
|
|
var viewport = new RawViewportF { X = 0, Y = 0, Width = initialResolution.Width, Height = initialResolution.Height, MinDepth = 0, MaxDepth = 1 };
|
|
deviceContext.Rasterizer.SetViewports([viewport]);
|
|
|
|
deviceContext.PixelShader.Set(brightPassPS);
|
|
deviceContext.PixelShader.SetSampler(0, linearSampler);
|
|
deviceContext.PixelShader.SetShaderResource(0, sourceSrv);
|
|
_thresholdParams.Threshold = threshold;
|
|
_thresholdParams.ColorWeights = new Vector3(colorWeights.X, colorWeights.Y, colorWeights.Z);
|
|
|
|
ResourceManager.SetupConstBuffer(_thresholdParams, ref _thresholdParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _thresholdParamsBuffer);
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
}
|
|
|
|
// 2. Downsample / Blur Pyramid (Same logic, result for level 'i' in _blurTargetsA[i].SRV)
|
|
var lastLevelSrv = _brightPassTarget.SRV;
|
|
for (var level = 0; level < levels; ++level)
|
|
{
|
|
// ... (Downsample pass: lastLevelSrv -> _blurTargetsA[level]) ...
|
|
// ... (Vertical Blur pass: _blurTargetsA[level].SRV -> _blurTargetsB[level].RTV) ...
|
|
// ... (Horizontal Blur pass: _blurTargetsB[level].SRV -> _blurTargetsA[level].RTV) ...
|
|
// ... (lastLevelSrv = _blurTargetsA[level].SRV) ...
|
|
var targetSetA = _blurTargetsA[level];
|
|
var targetSetB = _blurTargetsB[level];
|
|
|
|
if (targetSetA == null || targetSetB == null)
|
|
continue;
|
|
|
|
var levelResolution = targetSetA.Resolution;
|
|
// Downsample
|
|
{
|
|
deviceContext.OutputMerger.SetTargets(targetSetA.RTV);
|
|
deviceContext.Rasterizer.SetViewport(new RawViewportF { X = 0, Y = 0, Width = levelResolution.Width, Height = levelResolution.Height });
|
|
deviceContext.PixelShader.Set(downSamplePS);
|
|
deviceContext.PixelShader.SetSampler(0, linearSampler);
|
|
deviceContext.PixelShader.SetShaderResource(0, lastLevelSrv);
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
}
|
|
// Blur V (A->B)
|
|
_blurParamsData.Width = levelResolution.Width;
|
|
_blurParamsData.Height = levelResolution.Height;
|
|
_blurParamsData.ClampTexture = clamp ? 1 : 0;
|
|
_blurParamsData.UseMask = 0;
|
|
_blurParamsData.MaskInvert = 0;
|
|
|
|
{
|
|
deviceContext.OutputMerger.SetTargets(targetSetB.RTV);
|
|
deviceContext.PixelShader.Set(blurPS);
|
|
deviceContext.PixelShader.SetSampler(0, linearSampler);
|
|
deviceContext.PixelShader.SetShaderResource(0, targetSetA.SRV);
|
|
_blurParamsData.DirX = 0.0f;
|
|
_blurParamsData.DirY = blurOffset;
|
|
ResourceManager.SetupConstBuffer(_blurParamsData, ref _blurParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _blurParamsBuffer);
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
}
|
|
// Blur H (B->A)
|
|
{
|
|
deviceContext.OutputMerger.SetTargets(targetSetA.RTV);
|
|
deviceContext.PixelShader.Set(blurPS);
|
|
deviceContext.PixelShader.SetSampler(0, linearSampler);
|
|
deviceContext.PixelShader.SetShaderResource(0, targetSetB.SRV);
|
|
_blurParamsData.DirX = blurOffset;
|
|
_blurParamsData.DirY = 0.0f;
|
|
ResourceManager.SetupConstBuffer(_blurParamsData, ref _blurParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _blurParamsBuffer);
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
}
|
|
lastLevelSrv = targetSetA.SRV; // Result for this level is in A
|
|
}
|
|
|
|
// 3. Initial Composite
|
|
// ... copy sourceSRV -> _compositeTarget.RTV ...
|
|
{
|
|
/* ... Initial Composite Code ... */
|
|
deviceContext.OutputMerger.SetTargets(_compositeTarget.RTV);
|
|
deviceContext.OutputMerger.SetBlendState(DefaultRenderingStates.DisabledBlendState);
|
|
deviceContext.Rasterizer.SetViewport(new RawViewportF
|
|
{ X = 0, Y = 0, Width = _compositeTarget.Resolution.Width, Height = _compositeTarget.Resolution.Height });
|
|
deviceContext.PixelShader.Set(copyPS);
|
|
deviceContext.PixelShader.SetSampler(0, pointSampler);
|
|
deviceContext.PixelShader.SetShaderResource(0, sourceSrv);
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null);
|
|
}
|
|
|
|
// 4. Upsample and Additively Blend
|
|
{
|
|
deviceContext.OutputMerger.SetBlendState(DefaultRenderingStates.AdditiveBlendState);
|
|
deviceContext.PixelShader.Set(upsampleAddPS);
|
|
deviceContext.PixelShader.SetSampler(0, linearSampler);
|
|
deviceContext.OutputMerger.SetTargets(_compositeTarget.RTV); // Always write to composite
|
|
deviceContext.Rasterizer.SetViewport(new RawViewportF
|
|
{ X = 0, Y = 0, Width = _compositeTarget.Resolution.Width, Height = _compositeTarget.Resolution.Height });
|
|
|
|
for (var level = levels - 1; level >= 0; --level)
|
|
{
|
|
var sourceSet = _blurTargetsA[level]; // Blurred result for this level
|
|
if (sourceSet == null)
|
|
continue;
|
|
|
|
// Calculate final intensity for this specific pass
|
|
var normalizedLevelIntensity = _levelIntensities[level]; // Get precalculated value
|
|
|
|
_compositeParamsData.PassIntensity = normalizedLevelIntensity * intensity;
|
|
|
|
if (colorGradient == null)
|
|
{
|
|
_compositeParamsData.PassColor = Vector4.One;
|
|
}
|
|
else
|
|
{
|
|
var k = levels <= 1 ? 0.5f : (float)level / (levels - 1);
|
|
_compositeParamsData.PassColor = colorGradient.Sample(k);
|
|
}
|
|
|
|
_compositeParamsData.InvTargetSize = new Vector2(1.0f / _compositeTarget.Resolution.Width, 1.0f / _compositeTarget.Resolution.Height);
|
|
_compositeParamsData.InvSourceSize = new Vector2(1.0f / sourceSet.Resolution.Width, 1.0f / sourceSet.Resolution.Height);
|
|
ResourceManager.SetupConstBuffer(_compositeParamsData, ref _compositeParamsBuffer);
|
|
deviceContext.PixelShader.SetConstantBuffer(0, _compositeParamsBuffer); // Assuming slot 0
|
|
|
|
deviceContext.PixelShader.SetShaderResource(0, sourceSet.SRV); // Bind low-res texture
|
|
deviceContext.Draw(3, 0);
|
|
deviceContext.PixelShader.SetShaderResource(0, null); // Unbind
|
|
}
|
|
|
|
deviceContext.OutputMerger.SetBlendState(DefaultRenderingStates.DisabledBlendState); // Restore blend state
|
|
}
|
|
|
|
// --- Final Output ---
|
|
OutputTexture.Value = _compositeTarget.Texture;
|
|
_stateBackup.Restore(deviceContext);
|
|
}
|
|
|
|
// --- Internal Resources ---
|
|
// RenderTargetSet struct and Lists _blurTargetsA, _blurTargetsB
|
|
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?> _blurTargetsA = [];
|
|
private readonly List<RenderTargetSet?> _blurTargetsB = [];
|
|
private RenderTargetSet? _brightPassTarget;
|
|
private RenderTargetSet? _compositeTarget;
|
|
|
|
// Constant Buffers
|
|
[StructLayout(LayoutKind.Explicit, Size = 16)]
|
|
private struct ThresholdParams
|
|
{
|
|
[FieldOffset(0)]
|
|
public Vector3 ColorWeights;
|
|
|
|
[FieldOffset(3 * 4)]
|
|
public float Threshold;
|
|
|
|
public static readonly ThresholdParams Default = new();
|
|
}
|
|
|
|
private ThresholdParams _thresholdParams;
|
|
private Buffer? _thresholdParamsBuffer;
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = (4 * 4) + (4 * 4))]
|
|
private struct BlurParameters
|
|
{
|
|
[FieldOffset(0 * 4)]
|
|
public float DirX;
|
|
|
|
[FieldOffset(1 * 4)]
|
|
public float DirY;
|
|
|
|
[FieldOffset(2 * 4)]
|
|
public float Width;
|
|
|
|
[FieldOffset(3 * 4)]
|
|
public float Height;
|
|
|
|
[FieldOffset(4 * 4)]
|
|
public int UseMask;
|
|
|
|
[FieldOffset(5 * 4)]
|
|
public int MaskInvert;
|
|
|
|
[FieldOffset(6 * 4)]
|
|
public int ClampTexture;
|
|
|
|
[FieldOffset(7 * 4)]
|
|
public int _padding0;
|
|
|
|
public static readonly BlurParameters Default = new();
|
|
}
|
|
|
|
private BlurParameters _blurParamsData;
|
|
private Buffer? _blurParamsBuffer;
|
|
|
|
[StructLayout(LayoutKind.Explicit, Size = 16 * 4)] // Ensure size/padding matches HLSL
|
|
private struct CompositeParams
|
|
{
|
|
[FieldOffset(0)]
|
|
public Vector2 InvTargetSize;
|
|
|
|
[FieldOffset(2 * 4)]
|
|
public Vector2 InvSourceSize;
|
|
|
|
[FieldOffset(4 * 4)]
|
|
public Vector4 PassColor;
|
|
|
|
[FieldOffset(8 * 4)]
|
|
public float PassIntensity; // Combined overall Intensity * normalized level weight
|
|
|
|
[FieldOffset(12 * 4)]
|
|
public Vector3 __padding; // Combined overall Intensity * normalized level weight
|
|
|
|
public static readonly CompositeParams Default = new();
|
|
}
|
|
|
|
private CompositeParams _compositeParamsData;
|
|
private Buffer? _compositeParamsBuffer;
|
|
|
|
// State Tracking
|
|
private Size2 _lastResolution = Size2.Zero;
|
|
private Format _lastFormat = Format.Unknown;
|
|
private int _lastLevels = -1;
|
|
private Vector2 _lastGainAndBias = new(float.NaN, float.NaN);
|
|
private List<float> _levelIntensities = []; // Stores normalized intensity per level
|
|
|
|
// Creates/updates ALL internal resources
|
|
private bool InitializeOrUpdateResources(Size2 initialResolution, Format initialFormat, int numLevels)
|
|
{
|
|
// Check if general resource recreation is needed (resolution, format, levels changed, or resources missing)
|
|
var needsTextureRecreation = _compositeTarget?.Texture == null ||
|
|
_compositeTarget.Texture.IsDisposed ||
|
|
_brightPassTarget?.Texture == null ||
|
|
_brightPassTarget.Texture.IsDisposed ||
|
|
_blurTargetsA.Count != numLevels ||
|
|
_blurTargetsB.Count != numLevels ||
|
|
_lastResolution != initialResolution ||
|
|
_lastFormat != initialFormat ||
|
|
_lastLevels != numLevels ||
|
|
_blurTargetsA.Count > 0 && (_blurTargetsA[0]?.Texture == null || _blurTargetsA[0]!.Texture.IsDisposed);
|
|
|
|
// Check if constant buffers need creation (only happens once or after failure)
|
|
var needsBufferCreation = _thresholdParamsBuffer == null ||
|
|
_thresholdParamsBuffer.IsDisposed ||
|
|
_blurParamsBuffer == null ||
|
|
_blurParamsBuffer.IsDisposed ||
|
|
_compositeParamsBuffer == null ||
|
|
_compositeParamsBuffer.IsDisposed;
|
|
|
|
if (!needsTextureRecreation && !needsBufferCreation)
|
|
return true; // Nothing to do
|
|
|
|
// --- Cleanup and Recreate ---
|
|
if (needsTextureRecreation)
|
|
{
|
|
// Log.Debug($"Recreating Bloom textures/views for {initialResolution.Width}x{initialResolution.Height}, {numLevels} levels, Format: {initialFormat}",
|
|
// this);
|
|
CleanupResources(); // Cleans textures, views, AND buffers, resets state tracking
|
|
}
|
|
else
|
|
{
|
|
Log.Debug("Recreating Bloom constant buffers.", this);
|
|
// Only dispose buffers
|
|
Utilities.Dispose(ref _thresholdParamsBuffer);
|
|
Utilities.Dispose(ref _blurParamsBuffer);
|
|
Utilities.Dispose(ref _compositeParamsBuffer);
|
|
}
|
|
|
|
try
|
|
{
|
|
var device = ResourceManager.Device;
|
|
|
|
// --- Recreate Textures/Views if needed ---
|
|
if (needsTextureRecreation)
|
|
{
|
|
// Create Full Resolution Targets
|
|
var fullResDesc = new Texture2DDescription
|
|
{
|
|
/* ... as before ... */
|
|
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 SampleDescription(1, 0)
|
|
};
|
|
_brightPassTarget = new RenderTargetSet(device, fullResDesc);
|
|
_compositeTarget = new RenderTargetSet(device, fullResDesc);
|
|
|
|
// Create Downsample Pyramid Targets
|
|
var currentResolution = initialResolution;
|
|
for (var level = 0; level < numLevels; ++level)
|
|
{
|
|
currentResolution.Width = Math.Max(1, currentResolution.Width / 2);
|
|
currentResolution.Height = Math.Max(1, currentResolution.Height / 2);
|
|
var levelDesc = fullResDesc;
|
|
levelDesc.Width = currentResolution.Width;
|
|
levelDesc.Height = currentResolution.Height;
|
|
_blurTargetsA?.Add(new RenderTargetSet(device, levelDesc));
|
|
_blurTargetsB.Add(new RenderTargetSet(device, levelDesc));
|
|
}
|
|
|
|
// Update tracking info after successful texture creation
|
|
_lastResolution = initialResolution;
|
|
_lastFormat = initialFormat;
|
|
_lastLevels = numLevels;
|
|
}
|
|
|
|
// --- Create Constant Buffers if needed ---
|
|
if (_thresholdParamsBuffer == null) ResourceManager.SetupConstBuffer(ThresholdParams.Default, ref _thresholdParamsBuffer);
|
|
if (_blurParamsBuffer == null) ResourceManager.SetupConstBuffer(BlurParameters.Default, ref _blurParamsBuffer);
|
|
if (_compositeParamsBuffer == null) ResourceManager.SetupConstBuffer(CompositeParams.Default, ref _compositeParamsBuffer);
|
|
|
|
// Final check if all buffers were created
|
|
if (_thresholdParamsBuffer == null || _blurParamsBuffer == null || _compositeParamsBuffer == null)
|
|
throw new Exception("Failed to create one or more constant buffers.");
|
|
}
|
|
catch (Exception e)
|
|
{
|
|
Log.Error($"Failed to create Bloom resources: {e.Message}", this);
|
|
CleanupResources(); // Cleanup partially created resources
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
// Helper to create a RenderTargetSet
|
|
// private RenderTargetSet CreateRenderTargetSet(Device device, Texture2DDescription desc)
|
|
// {
|
|
// var set = new RenderTargetSet
|
|
// {
|
|
// Resolution = new Size2(desc.Width, desc.Height),
|
|
// Texture = Texture2D.CreateTexture2D(desc),
|
|
// };
|
|
// set.RTV = new RenderTargetView(device, set.Texture);
|
|
// set.SRV = new ShaderResourceView(device, set.Texture);
|
|
// return set;
|
|
// }
|
|
|
|
// CleanupResources disposes everything and resets state
|
|
private void CleanupResources()
|
|
{
|
|
// ... (Dispose textures/views in lists _blurTargetsA/B, _brightPassTarget, _compositeTarget) ...
|
|
foreach (var set in _blurTargetsA)
|
|
{
|
|
set?.Dispose();
|
|
}
|
|
|
|
_blurTargetsA.Clear();
|
|
foreach (var set in _blurTargetsB)
|
|
{
|
|
set?.Dispose();
|
|
}
|
|
|
|
_blurTargetsB.Clear();
|
|
_brightPassTarget?.Dispose();
|
|
_compositeTarget?.Dispose();
|
|
|
|
// ... (Dispose constant buffers) ...
|
|
Utilities.Dispose(ref _thresholdParamsBuffer);
|
|
Utilities.Dispose(ref _blurParamsBuffer);
|
|
Utilities.Dispose(ref _compositeParamsBuffer);
|
|
|
|
// ... (Reset state tracking) ...
|
|
_lastResolution = Size2.Zero;
|
|
_lastFormat = Format.Unknown;
|
|
_lastLevels = -1;
|
|
_levelIntensities.Clear();
|
|
}
|
|
|
|
private readonly D3D11StateBackup _stateBackup = new();
|
|
|
|
protected override void Dispose(bool disposing)
|
|
{
|
|
/* ... Call CleanupResources ... */
|
|
if (disposing)
|
|
{
|
|
CleanupResources();
|
|
_stateBackup.Dispose();
|
|
}
|
|
|
|
base.Dispose(disposing);
|
|
}
|
|
|
|
/// <summary>
|
|
/// Helper class to save and restore specific D3D11 pipeline states
|
|
/// commonly modified by fullscreen post-processing effects.
|
|
/// </summary>
|
|
private sealed class D3D11StateBackup : System.IDisposable
|
|
{
|
|
|
|
|
|
/// <summary>
|
|
/// Saves the relevant D3D11 pipeline states modified by the effect.
|
|
/// </summary>
|
|
public void Save(DeviceContext context)
|
|
{
|
|
if (_isSaved) return; // Prevent double saves without restore
|
|
|
|
// IA
|
|
_topology = context.InputAssembler.PrimitiveTopology;
|
|
|
|
// Shaders (Get the objects)
|
|
var vsStage = context.VertexShader;
|
|
_vertexShader = vsStage.Get();
|
|
_geometryShader = context.GeometryShader.Get(); // Save even if we set to null later
|
|
|
|
// Pixel Shader Resources
|
|
var psStage = context.PixelShader;
|
|
_pixelShader = psStage.Get();
|
|
_psConstantBuffers = psStage.GetConstantBuffers(0, 1);
|
|
_psShaderResourceViews = psStage.GetShaderResources(0, 2);
|
|
_psSamplerStates = psStage.GetSamplers(0, 1);
|
|
|
|
// Rasterizer
|
|
_rasterizerState = context.Rasterizer.State;
|
|
_viewports = context.Rasterizer.GetViewports<RawViewportF>(); // Get all active
|
|
|
|
// Output Merger
|
|
_blendState = context.OutputMerger.GetBlendState(out _blendFactor, out _sampleMask);
|
|
|
|
// Suggested API doesn't exist.
|
|
//context.OutputMerger.GetRenderTargets(OutputMergerStage.SimultaneousRenderTargetCount, _renderTargetViews, out _depthStencilView);
|
|
_prevRenderTargetViews = context.OutputMerger.GetRenderTargets(2);
|
|
context.OutputMerger.GetRenderTargets(out _depthStencilView);
|
|
_isSaved = true;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Restores the previously saved D3D11 pipeline state.
|
|
/// Also disposes the state objects retrieved during Save().
|
|
/// </summary>
|
|
public void Restore(DeviceContext context)
|
|
{
|
|
if (!_isSaved) return; // Nothing to restore
|
|
|
|
// IA
|
|
context.InputAssembler.PrimitiveTopology = _topology;
|
|
|
|
// Shaders
|
|
context.VertexShader.Set(_vertexShader);
|
|
context.GeometryShader.Set(_geometryShader);
|
|
|
|
// Pixel Shader Resources
|
|
var psStage = context.PixelShader;
|
|
psStage.Set(_pixelShader);
|
|
psStage.SetConstantBuffers(0, _psConstantBuffers.Length, _psConstantBuffers);
|
|
psStage.SetShaderResources(0, _psShaderResourceViews.Length, _psShaderResourceViews);
|
|
psStage.SetSamplers(0, _psSamplerStates.Length, _psSamplerStates);
|
|
|
|
// Rasterizer
|
|
context.Rasterizer.State = _rasterizerState;
|
|
context.Rasterizer.SetViewports(_viewports, _viewports?.Length ?? 0); // SetViewports handles null array / count 0 correctly
|
|
_viewports = null; // Clear local reference
|
|
|
|
// Output Merger
|
|
context.OutputMerger.SetBlendState(_blendState, _blendFactor, _sampleMask);
|
|
//context.OutputMerger.SetDepthStencilState(_depthStencilState, _stencilRef);
|
|
//context.OutputMerger.SetRenderTargets(_depthStencilView, _renderTargetViews); // SetRenderTargets handles null DSV
|
|
if (_prevRenderTargetViews.Length > 0)
|
|
context.OutputMerger.SetRenderTargets(_depthStencilView, _prevRenderTargetViews);
|
|
|
|
foreach (var rtv in _prevRenderTargetViews)
|
|
rtv?.Dispose();
|
|
|
|
_isSaved = false;
|
|
|
|
Dispose();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Disposes potentially held state objects if Restore wasn't called.
|
|
/// </summary>
|
|
public void Dispose()
|
|
{
|
|
// If Save was called but Restore wasn't, dispose the retrieved state objects
|
|
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;
|
|
}
|
|
// Don't dispose SRVs in _psShaderResourceViews array
|
|
|
|
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);
|
|
}
|
|
|
|
// Nullify references
|
|
_vertexShader = null;
|
|
_pixelShader = null;
|
|
_geometryShader = null; /* etc. */
|
|
_psShaderResourceViews[0] = null;
|
|
_psShaderResourceViews[1] = null;
|
|
_viewports = null;
|
|
_isSaved = false;
|
|
}
|
|
|
|
|
|
// --- Saved State Members ---
|
|
|
|
// Input Assembler
|
|
private PrimitiveTopology _topology;
|
|
|
|
// Vertex Shader (minimal - just shader, assuming VS inputs aren't changed)
|
|
private SharpDX.Direct3D11.VertexShader? _vertexShader;
|
|
|
|
// Geometry Shader (minimal - just shader)
|
|
private SharpDX.Direct3D11.GeometryShader? _geometryShader;
|
|
|
|
// Pixel Shader (Shader, 1 CB, 2 SRVs, 1 Sampler)
|
|
private SharpDX.Direct3D11.PixelShader? _pixelShader;
|
|
private Buffer?[] _psConstantBuffers = new Buffer[1]; // Slot 0
|
|
private ShaderResourceView?[] _psShaderResourceViews = new ShaderResourceView[2]; // Slots 0, 1
|
|
private SamplerState?[] _psSamplerStates = new SamplerState[1]; // Slot 0
|
|
|
|
// Rasterizer Stage
|
|
private RasterizerState? _rasterizerState;
|
|
private RawViewportF[]? _viewports; // Need to save all active viewports
|
|
|
|
// Output Merger Stage
|
|
private BlendState? _blendState;
|
|
private RawColor4 _blendFactor;
|
|
private int _sampleMask;
|
|
private DepthStencilState? _depthStencilState;
|
|
|
|
private RenderTargetView?[]
|
|
_prevRenderTargetViews = new RenderTargetView?[OutputMergerStage.SimultaneousRenderTargetCount]; // Save max possible RTVs
|
|
|
|
private DepthStencilView? _depthStencilView;
|
|
|
|
private bool _isSaved;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Calculates the frequency distribution for a monotonic gain/bias function,
|
|
/// normalizing the result so the frequencies sum to 1.0.
|
|
/// Assumes the function maps [0,1] to [0,1] and uses MathUtils.ApplyGainAndBias.
|
|
/// </summary>
|
|
private void CalculateDistribution(Vector2 gainAndBias, int bucketCount, ref List<float> distribution)
|
|
{
|
|
if (bucketCount <= 0)
|
|
throw new ArgumentOutOfRangeException(nameof(bucketCount), "Must be greater than 0.");
|
|
|
|
distribution.Clear();
|
|
|
|
var gain = gainAndBias.X.Clamp(0.002f, 0.95f);
|
|
var bias = gainAndBias.Y.Clamp(0.002f, 0.95f);
|
|
float last = 0;
|
|
for (var k = 1; k < bucketCount; k++)
|
|
{
|
|
var yTarget = (float)k / bucketCount;
|
|
|
|
if (!TryFindRootBisection(x => x.ApplyGainAndBias(gain, bias) - yTarget, out var r))
|
|
r = (float)k / bucketCount;
|
|
|
|
r = r.Clamp(0, 1);
|
|
distribution.Add(r - last);
|
|
last = r;
|
|
}
|
|
|
|
distribution.Add(1 - last);
|
|
}
|
|
|
|
//private float[] _frequencyBoundaries = [];
|
|
|
|
/// <summary>
|
|
/// Simple Bisection method root finder for monotonic functions in [0, 1].
|
|
/// </summary>
|
|
private static bool TryFindRootBisection(Func<float, float> function, out float result)
|
|
{
|
|
result = 0;
|
|
const float tolerance = 0.001f;
|
|
const float maxIterations = 20;
|
|
var lowerBound = 0f;
|
|
var upperBound = 1f;
|
|
|
|
var fLower = function(lowerBound);
|
|
if (Math.Abs(fLower) < tolerance)
|
|
{
|
|
result = lowerBound;
|
|
return true;
|
|
}
|
|
|
|
var fUpper = function(upperBound);
|
|
if (Math.Abs(fUpper) < tolerance)
|
|
{
|
|
result = upperBound;
|
|
return true;
|
|
}
|
|
|
|
// Check bracketing condition
|
|
if (Math.Sign(fLower) == Math.Sign(fUpper))
|
|
{
|
|
// This can happen if yTarget is outside the *actual* numerical range of f(x)
|
|
// due to internal clamping or precision issues in ApplyGainAndBias,
|
|
// even if theoretically f(0)=0 and f(1)=1.
|
|
return false;
|
|
}
|
|
|
|
for (var i = 0; i < maxIterations; i++)
|
|
{
|
|
var midpoint = lowerBound + 0.5f * (upperBound - lowerBound);
|
|
var fMidpoint = function(midpoint);
|
|
|
|
if (Math.Abs(fMidpoint) < tolerance)
|
|
{
|
|
result = midpoint;
|
|
return true;
|
|
}
|
|
|
|
// Narrow the interval
|
|
if (Math.Sign(fMidpoint) == Math.Sign(fLower))
|
|
{
|
|
lowerBound = midpoint;
|
|
fLower = fMidpoint;
|
|
}
|
|
else
|
|
{
|
|
upperBound = midpoint;
|
|
}
|
|
}
|
|
|
|
// Max iterations reached
|
|
return false;
|
|
}
|
|
|
|
[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<SharpDX.Direct3D11.ShaderResourceView> SourceTextureSrv = new();
|
|
|
|
[Input(Guid = "AC3B575A-DC62-48BD-955C-F945A18A2246")]
|
|
public readonly InputSlot<Vector4> ColorWeights = new();
|
|
|
|
[Input(Guid = "D9576354-76F1-403C-8B28-45386F657190")]
|
|
public readonly InputSlot<float> Threshold = new();
|
|
|
|
[Input(Guid = "0F6919CD-33C9-4E99-AD72-B2A8DCB2B7A2")]
|
|
public readonly InputSlot<float> Intensity = new();
|
|
|
|
[Input(Guid = "4B1AC17B-B54C-4741-BB38-5784F6C1891A")]
|
|
public readonly InputSlot<float> BlurOffset = new();
|
|
|
|
[Input(Guid = "388489EE-EA0C-49A3-AEE8-1AE2C4DCBDA2")]
|
|
public readonly InputSlot<int> Levels = new();
|
|
|
|
[Input(Guid = "2DB8E046-B81B-43F4-B454-4EEC8FCDD4B4")]
|
|
public readonly InputSlot<Vector2> GainAndBias = new();
|
|
|
|
[Input(Guid = "E5548715-1CAE-4792-9E6E-8F9D8DAA9EDF")]
|
|
public readonly InputSlot<Gradient> BlurGradient = new();
|
|
|
|
[Input(Guid = "D5914036-F628-4305-D7B5-1634B219C305")]
|
|
public readonly InputSlot<bool> Clamp = new();
|
|
|
|
[Input(Guid = "E6A25147-0739-4416-E8C6-2745C32AD416")]
|
|
public readonly InputSlot<T3.Core.DataTypes.VertexShader> FullscreenVS = new();
|
|
|
|
[Input(Guid = "F7B36258-184A-4527-F9D7-3856D43BE527")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> BrightPassPS = new();
|
|
|
|
[Input(Guid = "08C47369-295B-4638-0AE8-4967E54CF638")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> DownsamplePS = new();
|
|
|
|
[Input(Guid = "19D5847A-3A6C-4749-1BF9-5A78F65D0749")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> SeparableBlurPS = new();
|
|
|
|
[Input(Guid = "2AE6958B-4B7D-485A-2C0A-6B89076E185A")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> UpsampleAddPS = new();
|
|
|
|
[Input(Guid = "3BF7A69C-5C8E-496B-3D1B-7C9A187F296B")]
|
|
public readonly InputSlot<T3.Core.DataTypes.PixelShader> CopyPS = new();
|
|
|
|
[Input(Guid = "5D19C8BE-7EA0-4B8D-5F3D-9EBB3A914B8D")]
|
|
public readonly InputSlot<SharpDX.Direct3D11.SamplerState> LinearSampler = new();
|
|
|
|
[Input(Guid = "4C08B7AD-6D9F-4A7C-4E2C-8DAA29803A7C")]
|
|
public readonly InputSlot<SharpDX.Direct3D11.SamplerState> PointSampler = new();
|
|
} |