/ src / Ryujinx.Graphics.Vulkan / Effects / SmaaPostProcessingEffect.cs
SmaaPostProcessingEffect.cs
  1  using Ryujinx.Common;
  2  using Ryujinx.Graphics.GAL;
  3  using Ryujinx.Graphics.Shader;
  4  using Ryujinx.Graphics.Shader.Translation;
  5  using Silk.NET.Vulkan;
  6  using System;
  7  using Format = Ryujinx.Graphics.GAL.Format;
  8  using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
  9  
 10  namespace Ryujinx.Graphics.Vulkan.Effects
 11  {
 12      internal class SmaaPostProcessingEffect : IPostProcessingEffect
 13      {
 14          public const int AreaWidth = 160;
 15          public const int AreaHeight = 560;
 16          public const int SearchWidth = 64;
 17          public const int SearchHeight = 16;
 18  
 19          private readonly VulkanRenderer _renderer;
 20          private ISampler _samplerLinear;
 21          private SmaaConstants _specConstants;
 22          private ShaderCollection _edgeProgram;
 23          private ShaderCollection _blendProgram;
 24          private ShaderCollection _neighbourProgram;
 25  
 26          private PipelineHelperShader _pipeline;
 27  
 28          private TextureView _outputTexture;
 29          private TextureView _edgeOutputTexture;
 30          private TextureView _blendOutputTexture;
 31          private TextureView _areaTexture;
 32          private TextureView _searchTexture;
 33          private Device _device;
 34          private bool _recreatePipelines;
 35          private int _quality;
 36  
 37          public SmaaPostProcessingEffect(VulkanRenderer renderer, Device device, int quality)
 38          {
 39              _device = device;
 40              _renderer = renderer;
 41              _quality = quality;
 42  
 43              Initialize();
 44          }
 45  
 46          public int Quality
 47          {
 48              get => _quality;
 49              set
 50              {
 51                  _quality = value;
 52  
 53                  _recreatePipelines = true;
 54              }
 55          }
 56  
 57          public void Dispose()
 58          {
 59              DeletePipelines();
 60              _samplerLinear?.Dispose();
 61              _outputTexture?.Dispose();
 62              _edgeOutputTexture?.Dispose();
 63              _blendOutputTexture?.Dispose();
 64              _areaTexture?.Dispose();
 65              _searchTexture?.Dispose();
 66          }
 67  
 68          private void RecreateShaders(int width, int height)
 69          {
 70              _recreatePipelines = false;
 71  
 72              DeletePipelines();
 73              _pipeline = new PipelineHelperShader(_renderer, _device);
 74  
 75              _pipeline.Initialize();
 76  
 77              var edgeShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaEdge.spv");
 78              var blendShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaBlend.spv");
 79              var neighbourShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/SmaaNeighbour.spv");
 80  
 81              var edgeResourceLayout = new ResourceLayoutBuilder()
 82                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
 83                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
 84                  .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 85  
 86              var blendResourceLayout = new ResourceLayoutBuilder()
 87                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
 88                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
 89                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
 90                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 4)
 91                  .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 92  
 93              var neighbourResourceLayout = new ResourceLayoutBuilder()
 94                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
 95                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
 96                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 3)
 97                  .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 98  
 99              _samplerLinear = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
100  
101              _specConstants = new SmaaConstants
102              {
103                  Width = width,
104                  Height = height,
105                  QualityLow = Quality == 0 ? 1 : 0,
106                  QualityMedium = Quality == 1 ? 1 : 0,
107                  QualityHigh = Quality == 2 ? 1 : 0,
108                  QualityUltra = Quality == 3 ? 1 : 0,
109              };
110  
111              var specInfo = new SpecDescription(
112                  (0, SpecConstType.Int32),
113                  (1, SpecConstType.Int32),
114                  (2, SpecConstType.Int32),
115                  (3, SpecConstType.Int32),
116                  (4, SpecConstType.Float32),
117                  (5, SpecConstType.Float32));
118  
119              _edgeProgram = _renderer.CreateProgramWithMinimalLayout(new[]
120              {
121                  new ShaderSource(edgeShader, ShaderStage.Compute, TargetLanguage.Spirv),
122              }, edgeResourceLayout, new[] { specInfo });
123  
124              _blendProgram = _renderer.CreateProgramWithMinimalLayout(new[]
125              {
126                  new ShaderSource(blendShader, ShaderStage.Compute, TargetLanguage.Spirv),
127              }, blendResourceLayout, new[] { specInfo });
128  
129              _neighbourProgram = _renderer.CreateProgramWithMinimalLayout(new[]
130              {
131                  new ShaderSource(neighbourShader, ShaderStage.Compute, TargetLanguage.Spirv),
132              }, neighbourResourceLayout, new[] { specInfo });
133          }
134  
135          public void DeletePipelines()
136          {
137              _pipeline?.Dispose();
138              _edgeProgram?.Dispose();
139              _blendProgram?.Dispose();
140              _neighbourProgram?.Dispose();
141          }
142  
143          private void Initialize()
144          {
145              var areaInfo = new TextureCreateInfo(AreaWidth,
146                  AreaHeight,
147                  1,
148                  1,
149                  1,
150                  1,
151                  1,
152                  1,
153                  Format.R8G8Unorm,
154                  DepthStencilMode.Depth,
155                  Target.Texture2D,
156                  SwizzleComponent.Red,
157                  SwizzleComponent.Green,
158                  SwizzleComponent.Blue,
159                  SwizzleComponent.Alpha);
160  
161              var searchInfo = new TextureCreateInfo(SearchWidth,
162                  SearchHeight,
163                  1,
164                  1,
165                  1,
166                  1,
167                  1,
168                  1,
169                  Format.R8Unorm,
170                  DepthStencilMode.Depth,
171                  Target.Texture2D,
172                  SwizzleComponent.Red,
173                  SwizzleComponent.Green,
174                  SwizzleComponent.Blue,
175                  SwizzleComponent.Alpha);
176  
177              var areaTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaAreaTexture.bin");
178              var searchTexture = EmbeddedResources.ReadFileToRentedMemory("Ryujinx.Graphics.Vulkan/Effects/Textures/SmaaSearchTexture.bin");
179  
180              _areaTexture = _renderer.CreateTexture(areaInfo) as TextureView;
181              _searchTexture = _renderer.CreateTexture(searchInfo) as TextureView;
182  
183              _areaTexture.SetData(areaTexture);
184              _searchTexture.SetData(searchTexture);
185          }
186  
187          public TextureView Run(TextureView view, CommandBufferScoped cbs, int width, int height)
188          {
189              if (_recreatePipelines || _outputTexture == null || _outputTexture.Info.Width != view.Width || _outputTexture.Info.Height != view.Height)
190              {
191                  RecreateShaders(view.Width, view.Height);
192                  _outputTexture?.Dispose();
193                  _edgeOutputTexture?.Dispose();
194                  _blendOutputTexture?.Dispose();
195  
196                  _outputTexture = _renderer.CreateTexture(view.Info) as TextureView;
197                  _edgeOutputTexture = _renderer.CreateTexture(view.Info) as TextureView;
198                  _blendOutputTexture = _renderer.CreateTexture(view.Info) as TextureView;
199              }
200  
201              _pipeline.SetCommandBuffer(cbs);
202  
203              Clear(_edgeOutputTexture);
204              Clear(_blendOutputTexture);
205  
206              _renderer.Pipeline.TextureBarrier();
207  
208              var dispatchX = BitUtils.DivRoundUp(view.Width, IPostProcessingEffect.LocalGroupSize);
209              var dispatchY = BitUtils.DivRoundUp(view.Height, IPostProcessingEffect.LocalGroupSize);
210  
211              // Edge pass
212              _pipeline.SetProgram(_edgeProgram);
213              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
214              _pipeline.Specialize(_specConstants);
215  
216              ReadOnlySpan<float> resolutionBuffer = stackalloc float[] { view.Width, view.Height };
217              int rangeSize = resolutionBuffer.Length * sizeof(float);
218              using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
219  
220              buffer.Holder.SetDataUnchecked(buffer.Offset, resolutionBuffer);
221              _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
222              _pipeline.SetImage(ShaderStage.Compute, 0, _edgeOutputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
223              _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
224              _pipeline.ComputeBarrier();
225  
226              // Blend pass
227              _pipeline.SetProgram(_blendProgram);
228              _pipeline.Specialize(_specConstants);
229              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _edgeOutputTexture, _samplerLinear);
230              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _areaTexture, _samplerLinear);
231              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 4, _searchTexture, _samplerLinear);
232              _pipeline.SetImage(ShaderStage.Compute, 0, _blendOutputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
233              _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
234              _pipeline.ComputeBarrier();
235  
236              // Neighbour pass
237              _pipeline.SetProgram(_neighbourProgram);
238              _pipeline.Specialize(_specConstants);
239              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 3, _blendOutputTexture, _samplerLinear);
240              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _samplerLinear);
241              _pipeline.SetImage(ShaderStage.Compute, 0, _outputTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
242              _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
243              _pipeline.ComputeBarrier();
244  
245              _pipeline.Finish();
246  
247              return _outputTexture;
248          }
249  
250          private void Clear(TextureView texture)
251          {
252              Span<uint> colorMasks = stackalloc uint[1];
253  
254              colorMasks[0] = 0xf;
255  
256              Span<Rectangle<int>> scissors = stackalloc Rectangle<int>[1];
257  
258              scissors[0] = new Rectangle<int>(0, 0, texture.Width, texture.Height);
259  
260              _pipeline.SetRenderTarget(texture, (uint)texture.Width, (uint)texture.Height);
261              _pipeline.SetRenderTargetColorMasks(colorMasks);
262              _pipeline.SetScissors(scissors);
263              _pipeline.ClearRenderTargetColor(0, 0, 1, new ColorF(0f, 0f, 0f, 1f));
264          }
265      }
266  }