Add Support for Post Processing Effects (#3616)

* Add Post Processing Effects

* fix events and shader issues

* fix gtk upscale slider value

* fix bgra games

* don't swap swizzle if already swapped

* restore opengl texture state after effects run

* addressed review

* use single pipeline for smaa and fsr

* call finish on all pipelines

* addressed review

* attempt fix file case

* attempt fixing file case

* fix filter level tick frequency

* adjust filter slider margins

* replace fxaa shaders with original shader

* addressed review
This commit is contained in:
Emmanuel Hansen
2023-02-27 21:11:55 +00:00
committed by GitHub
parent 5d85468302
commit 80b4972139
60 changed files with 21954 additions and 26 deletions

View File

@ -0,0 +1,208 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal partial class FsrScalingFilter : IScalingFilter
{
private readonly VulkanRenderer _renderer;
private PipelineHelperShader _pipeline;
private ISampler _sampler;
private ShaderCollection _scalingProgram;
private ShaderCollection _sharpeningProgram;
private float _sharpeningLevel = 1;
private Device _device;
private TextureView _intermediaryTexture;
public float Level
{
get => _sharpeningLevel;
set
{
_sharpeningLevel = MathF.Max(0.01f, value);
}
}
public FsrScalingFilter(VulkanRenderer renderer, Device device)
{
_device = device;
_renderer = renderer;
Initialize();
}
public void Dispose()
{
_pipeline.Dispose();
_scalingProgram.Dispose();
_sharpeningProgram.Dispose();
_sampler.Dispose();
_intermediaryTexture?.Dispose();
}
public void Initialize()
{
_pipeline = new PipelineHelperShader(_renderer, _device);
_pipeline.Initialize();
var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
var computeBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
var sharpeningBindings = new ShaderBindings(
new[] { 2, 3, 4 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
_sampler = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(scalingShader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
});
_sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(sharpeningShader, sharpeningBindings, ShaderStage.Compute, TargetLanguage.Spirv)
});
}
public void Run(
TextureView view,
CommandBufferScoped cbs,
Auto<DisposableImageView> destinationTexture,
Silk.NET.Vulkan.Format format,
int width,
int height,
Extent2D source,
Extent2D destination)
{
if (_intermediaryTexture == null
|| _intermediaryTexture.Info.Width != width
|| _intermediaryTexture.Info.Height != height
|| !_intermediaryTexture.Info.Equals(view.Info))
{
var originalInfo = view.Info;
var swapRB = originalInfo.Format.IsBgr() && originalInfo.SwizzleR == SwizzleComponent.Red;
var info = new TextureCreateInfo(
width,
height,
originalInfo.Depth,
originalInfo.Levels,
originalInfo.Samples,
originalInfo.BlockWidth,
originalInfo.BlockHeight,
originalInfo.BytesPerPixel,
originalInfo.Format,
originalInfo.DepthStencilMode,
originalInfo.Target,
swapRB ? originalInfo.SwizzleB : originalInfo.SwizzleR,
originalInfo.SwizzleG,
swapRB ? originalInfo.SwizzleR : originalInfo.SwizzleB,
originalInfo.SwizzleA);
_intermediaryTexture?.Dispose();
_intermediaryTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
}
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, view.Width, view.Height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
scissors[0] = new Rectangle<int>(0, 0, view.Width, view.Height);
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_scalingProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
float srcWidth = Math.Abs(source.X2 - source.X1);
float srcHeight = Math.Abs(source.Y2 - source.Y1);
float scaleX = srcWidth / view.Width;
float scaleY = srcHeight / view.Height;
ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
{
source.X1,
source.X2,
source.Y1,
source.Y2,
destination.X1,
destination.X2,
destination.Y1,
destination.Y2,
scaleX,
scaleY
};
int rangeSize = dimensionsBuffer.Length * sizeof(float);
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
_renderer.BufferManager.SetData(bufferHandle, 0, dimensionsBuffer);
ReadOnlySpan<float> sharpeningBuffer = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f)};
var sharpeningBufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, sizeof(float), false);
_renderer.BufferManager.SetData(sharpeningBufferHandle, 0, sharpeningBuffer);
int threadGroupWorkRegionDim = 16;
int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _intermediaryTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, width, height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
scissors[0] = new Rectangle<int>(0, 0, width, height);
// Sharpening pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_sharpeningProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
var sharpeningRange = new BufferRange(sharpeningBufferHandle, 0, sizeof(float));
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningRange) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, destinationTexture);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
_pipeline.Finish();
_renderer.BufferManager.Delete(bufferHandle);
_renderer.BufferManager.Delete(sharpeningBufferHandle);
}
}
}

View File

@ -0,0 +1,127 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal partial class FxaaPostProcessingEffect : IPostProcessingEffect
{
private readonly VulkanRenderer _renderer;
private ISampler _samplerLinear;
private ShaderCollection _shaderProgram;
private PipelineHelperShader _pipeline;
private TextureView _texture;
public FxaaPostProcessingEffect(VulkanRenderer renderer, Device device)
{
_renderer = renderer;
_pipeline = new PipelineHelperShader(renderer, device);
Initialize();
}
public void Dispose()
{
_shaderProgram.Dispose();
_pipeline.Dispose();
_samplerLinear.Dispose();
_texture?.Dispose();
}
private void Initialize()
{
_pipeline.Initialize();
var shader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/Fxaa.spv");
var computeBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_shaderProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(shader, computeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
});
}
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
{
if (_texture == null || _texture.Width != view.Width || _texture.Height != view.Height)
{
_texture?.Dispose();
var info = view.Info;
if (view.Info.Format.IsBgr())
{
info = new TextureCreateInfo(info.Width,
info.Height,
info.Depth,
info.Levels,
info.Samples,
info.BlockWidth,
info.BlockHeight,
info.BytesPerPixel,
info.Format,
info.DepthStencilMode,
info.Target,
info.SwizzleB,
info.SwizzleG,
info.SwizzleR,
info.SwizzleA);
}
_texture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
}
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_shaderProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
int rangeSize = resolutionBuffer.Length * sizeof(float);
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, view.Width, view.Height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
_pipeline.SetScissors(stackalloc[] { new Rectangle<int>(0, 0, view.Width, view.Height) });
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _texture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_renderer.BufferManager.Delete(bufferHandle);
_pipeline.ComputeBarrier();
_pipeline.Finish();
return _texture;
}
}
}

View File

@ -0,0 +1,10 @@
using System;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal interface IPostProcessingEffect : IDisposable
{
const int LocalGroupSize = 64;
TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height);
}
}

View File

@ -0,0 +1,20 @@
using Silk.NET.Vulkan;
using System;
using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal interface IScalingFilter : IDisposable
{
float Level { get; set; }
void Run(
TextureView view,
CommandBufferScoped cbs,
Auto<DisposableImageView> destinationTexture,
Format format,
int width,
int height,
Extent2D source,
Extent2D destination);
}
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,15 @@
using System.Runtime.InteropServices;
namespace Ryujinx.Graphics.Vulkan.Effects
{
[StructLayout(LayoutKind.Sequential, Pack = 4)]
internal struct SmaaConstants
{
public int QualityLow;
public int QualityMedium;
public int QualityHigh;
public int QualityUltra;
public float Width;
public float Height;
}
}

View File

@ -0,0 +1,314 @@
using Ryujinx.Common;
using Ryujinx.Graphics.GAL;
using Ryujinx.Graphics.Shader;
using Ryujinx.Graphics.Shader.Translation;
using Silk.NET.Vulkan;
using System;
using Format = Ryujinx.Graphics.GAL.Format;
namespace Ryujinx.Graphics.Vulkan.Effects
{
internal partial class SmaaPostProcessingEffect : IPostProcessingEffect
{
public const int AreaWidth = 160;
public const int AreaHeight = 560;
public const int SearchWidth = 64;
public const int SearchHeight = 16;
private readonly VulkanRenderer _renderer;
private ISampler _samplerLinear;
private SmaaConstants _specConstants;
private ShaderCollection _edgeProgram;
private ShaderCollection _blendProgram;
private ShaderCollection _neighbourProgram;
private PipelineHelperShader _pipeline;
private TextureView _outputTexture;
private TextureView _edgeOutputTexture;
private TextureView _blendOutputTexture;
private TextureView _areaTexture;
private TextureView _searchTexture;
private Device _device;
private bool _recreatePipelines;
private int _quality;
public SmaaPostProcessingEffect(VulkanRenderer renderer, Device device, int quality)
{
_device = device;
_renderer = renderer;
_quality = quality;
Initialize();
}
public int Quality
{
get => _quality;
set
{
_quality = value;
_recreatePipelines = true;
}
}
public void Dispose()
{
DeletePipelines();
_samplerLinear?.Dispose();
_outputTexture?.Dispose();
_edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose();
_areaTexture?.Dispose();
_searchTexture?.Dispose();
}
private unsafe void RecreateShaders(int width, int height)
{
_recreatePipelines = false;
DeletePipelines();
_pipeline = new PipelineHelperShader(_renderer, _device);
_pipeline.Initialize();
var edgeShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv");
var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
var edgeBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1 },
new[] { 0 });
var blendBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1, 3, 4 },
new[] { 0 });
var neighbourBindings = new ShaderBindings(
new[] { 2 },
Array.Empty<int>(),
new[] { 1, 3 },
new[] { 0 });
_samplerLinear = _renderer.CreateSampler(GAL.SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
_specConstants = new SmaaConstants()
{
Width = width,
Height = height,
QualityLow = Quality == 0 ? 1 : 0,
QualityMedium = Quality == 1 ? 1 : 0,
QualityHigh = Quality == 2 ? 1 : 0,
QualityUltra = Quality == 3 ? 1 : 0,
};
var specInfo = new SpecDescription(
(0, SpecConstType.Int32),
(1, SpecConstType.Int32),
(2, SpecConstType.Int32),
(3, SpecConstType.Int32),
(4, SpecConstType.Float32),
(5, SpecConstType.Float32));
_edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(edgeShader, edgeBindings, ShaderStage.Compute, TargetLanguage.Spirv)
}, new[] { specInfo });
_blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(blendShader, blendBindings, ShaderStage.Compute, TargetLanguage.Spirv)
}, new[] { specInfo });
_neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
{
new ShaderSource(neighbourShader, neighbourBindings, ShaderStage.Compute, TargetLanguage.Spirv)
}, new[] { specInfo });
}
public void DeletePipelines()
{
_pipeline?.Dispose();
_edgeProgram?.Dispose();
_blendProgram?.Dispose();
_neighbourProgram?.Dispose();
}
private void Initialize()
{
var areaInfo = new TextureCreateInfo(AreaWidth,
AreaHeight,
1,
1,
1,
1,
1,
1,
Format.R8G8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
var searchInfo = new TextureCreateInfo(SearchWidth,
SearchHeight,
1,
1,
1,
1,
1,
1,
Format.R8Unorm,
DepthStencilMode.Depth,
Target.Texture2D,
SwizzleComponent.Red,
SwizzleComponent.Green,
SwizzleComponent.Blue,
SwizzleComponent.Alpha);
var areaTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin");
var searchTexture = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin");
_areaTexture = _renderer.CreateTexture(areaInfo, 1) as TextureView;
_searchTexture = _renderer.CreateTexture(searchInfo, 1) as TextureView;
_areaTexture.SetData(areaTexture);
_searchTexture.SetData(searchTexture);
}
public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
{
if (_recreatePipelines || _outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
{
RecreateShaders(view.Width, view.Height);
_outputTexture?.Dispose();
_edgeOutputTexture?.Dispose();
_blendOutputTexture?.Dispose();
var info = view.Info;
if (view.Info.Format.IsBgr())
{
info = new TextureCreateInfo(info.Width,
info.Height,
info.Depth,
info.Levels,
info.Samples,
info.BlockWidth,
info.BlockHeight,
info.BytesPerPixel,
info.Format,
info.DepthStencilMode,
info.Target,
info.SwizzleB,
info.SwizzleG,
info.SwizzleR,
info.SwizzleA);
}
_outputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
_edgeOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
_blendOutputTexture = _renderer.CreateTexture(info, view.ScaleFactor) as TextureView;
}
Span<GAL.Viewport> viewports = stackalloc GAL.Viewport[1];
viewports[0] = new GAL.Viewport(
new Rectangle<float>(0, 0, view.Width, view.Height),
ViewportSwizzle.PositiveX,
ViewportSwizzle.PositiveY,
ViewportSwizzle.PositiveZ,
ViewportSwizzle.PositiveW,
0f,
1f);
Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
scissors[0] = new Rectangle<int>(0, 0, view.Width, view.Height);
_renderer.HelperShader.Clear(_renderer,
_edgeOutputTexture.GetImageView(),
new float[] { 0, 0, 0, 1 },
(uint)(ColorComponentFlags.RBit | ColorComponentFlags.GBit | ColorComponentFlags.BBit | ColorComponentFlags.ABit),
view.Width,
view.Height,
_edgeOutputTexture.VkFormat,
ComponentType.UnsignedInteger,
scissors[0]);
_renderer.HelperShader.Clear(_renderer,
_blendOutputTexture.GetImageView(),
new float[] { 0, 0, 0, 1 },
(uint)(ColorComponentFlags.RBit | ColorComponentFlags.GBit | ColorComponentFlags.BBit | ColorComponentFlags.ABit),
view.Width,
view.Height,
_blendOutputTexture.VkFormat,
ComponentType.UnsignedInteger,
scissors[0]);
_renderer.Pipeline.TextureBarrier();
var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
// Edge pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_edgeProgram);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.Specialize(_specConstants);
ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
int rangeSize = resolutionBuffer.Length * sizeof(float);
var bufferHandle = _renderer.BufferManager.CreateWithHandle(_renderer, rangeSize, false);
_renderer.BufferManager.SetData(bufferHandle, 0, resolutionBuffer);
var bufferRanges = new BufferRange(bufferHandle, 0, rangeSize);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _edgeOutputTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
// Blend pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_blendProgram);
_pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _blendOutputTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
// Neighbour pass
_pipeline.SetCommandBuffer(cbs);
_pipeline.SetProgram(_neighbourProgram);
_pipeline.Specialize(_specConstants);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
_pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
_pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, bufferRanges) });
_pipeline.SetScissors(scissors);
_pipeline.SetViewports(viewports, false);
_pipeline.SetImage(0, _outputTexture, GAL.Format.R8G8B8A8Unorm);
_pipeline.DispatchCompute(dispatchX, dispatchY, 1);
_pipeline.ComputeBarrier();
_pipeline.Finish();
_renderer.BufferManager.Delete(bufferHandle);
return _outputTexture;
}
}
}