#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 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 _blurTargetsA = []; private readonly List _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 _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); } /// /// Helper class to save and restore specific D3D11 pipeline states /// commonly modified by fullscreen post-processing effects. /// private sealed class D3D11StateBackup : System.IDisposable { /// /// Saves the relevant D3D11 pipeline states modified by the effect. /// 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(); // 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; } /// /// Restores the previously saved D3D11 pipeline state. /// Also disposes the state objects retrieved during Save(). /// 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(); } /// /// Disposes potentially held state objects if Restore wasn't called. /// 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; } /// /// 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. /// private void CalculateDistribution(Vector2 gainAndBias, int bucketCount, ref List 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 = []; /// /// Simple Bisection method root finder for monotonic functions in [0, 1]. /// private static bool TryFindRootBisection(Func 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 SourceTexture = new(); [Input(Guid = "98E88D02-3B78-403C-B9C9-B5ECF8565ACD")] public readonly InputSlot SourceTextureSrv = new(); [Input(Guid = "AC3B575A-DC62-48BD-955C-F945A18A2246")] public readonly InputSlot ColorWeights = new(); [Input(Guid = "D9576354-76F1-403C-8B28-45386F657190")] public readonly InputSlot Threshold = new(); [Input(Guid = "0F6919CD-33C9-4E99-AD72-B2A8DCB2B7A2")] public readonly InputSlot Intensity = new(); [Input(Guid = "4B1AC17B-B54C-4741-BB38-5784F6C1891A")] public readonly InputSlot BlurOffset = new(); [Input(Guid = "388489EE-EA0C-49A3-AEE8-1AE2C4DCBDA2")] public readonly InputSlot Levels = new(); [Input(Guid = "2DB8E046-B81B-43F4-B454-4EEC8FCDD4B4")] public readonly InputSlot GainAndBias = new(); [Input(Guid = "E5548715-1CAE-4792-9E6E-8F9D8DAA9EDF")] public readonly InputSlot BlurGradient = new(); [Input(Guid = "D5914036-F628-4305-D7B5-1634B219C305")] public readonly InputSlot Clamp = new(); [Input(Guid = "E6A25147-0739-4416-E8C6-2745C32AD416")] public readonly InputSlot FullscreenVS = new(); [Input(Guid = "F7B36258-184A-4527-F9D7-3856D43BE527")] public readonly InputSlot BrightPassPS = new(); [Input(Guid = "08C47369-295B-4638-0AE8-4967E54CF638")] public readonly InputSlot DownsamplePS = new(); [Input(Guid = "19D5847A-3A6C-4749-1BF9-5A78F65D0749")] public readonly InputSlot SeparableBlurPS = new(); [Input(Guid = "2AE6958B-4B7D-485A-2C0A-6B89076E185A")] public readonly InputSlot UpsampleAddPS = new(); [Input(Guid = "3BF7A69C-5C8E-496B-3D1B-7C9A187F296B")] public readonly InputSlot CopyPS = new(); [Input(Guid = "5D19C8BE-7EA0-4B8D-5F3D-9EBB3A914B8D")] public readonly InputSlot LinearSampler = new(); [Input(Guid = "4C08B7AD-6D9F-4A7C-4E2C-8DAA29803A7C")] public readonly InputSlot PointSampler = new(); }