/ src / Ryujinx.Graphics.Vulkan / Effects / FsrScalingFilter.cs
FsrScalingFilter.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 Extent2D = Ryujinx.Graphics.GAL.Extents2D;
  8  using Format = Silk.NET.Vulkan.Format;
  9  using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
 10  
 11  namespace Ryujinx.Graphics.Vulkan.Effects
 12  {
 13      internal class FsrScalingFilter : IScalingFilter
 14      {
 15          private readonly VulkanRenderer _renderer;
 16          private PipelineHelperShader _pipeline;
 17          private ISampler _sampler;
 18          private ShaderCollection _scalingProgram;
 19          private ShaderCollection _sharpeningProgram;
 20          private float _sharpeningLevel = 1;
 21          private Device _device;
 22          private TextureView _intermediaryTexture;
 23  
 24          public float Level
 25          {
 26              get => _sharpeningLevel;
 27              set
 28              {
 29                  _sharpeningLevel = MathF.Max(0.01f, value);
 30              }
 31          }
 32  
 33          public FsrScalingFilter(VulkanRenderer renderer, Device device)
 34          {
 35              _device = device;
 36              _renderer = renderer;
 37  
 38              Initialize();
 39          }
 40  
 41          public void Dispose()
 42          {
 43              _pipeline.Dispose();
 44              _scalingProgram.Dispose();
 45              _sharpeningProgram.Dispose();
 46              _sampler.Dispose();
 47              _intermediaryTexture?.Dispose();
 48          }
 49  
 50          public void Initialize()
 51          {
 52              _pipeline = new PipelineHelperShader(_renderer, _device);
 53  
 54              _pipeline.Initialize();
 55  
 56              var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrScaling.spv");
 57              var sharpeningShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/FsrSharpening.spv");
 58  
 59              var scalingResourceLayout = new ResourceLayoutBuilder()
 60                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
 61                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
 62                  .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 63  
 64              var sharpeningResourceLayout = new ResourceLayoutBuilder()
 65                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
 66                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 3)
 67                  .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 4)
 68                  .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
 69                  .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
 70  
 71              _sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
 72  
 73              _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
 74              {
 75                  new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
 76              }, scalingResourceLayout);
 77  
 78              _sharpeningProgram = _renderer.CreateProgramWithMinimalLayout(new[]
 79              {
 80                  new ShaderSource(sharpeningShader, ShaderStage.Compute, TargetLanguage.Spirv),
 81              }, sharpeningResourceLayout);
 82          }
 83  
 84          public void Run(
 85              TextureView view,
 86              CommandBufferScoped cbs,
 87              Auto<DisposableImageView> destinationTexture,
 88              Format format,
 89              int width,
 90              int height,
 91              Extent2D source,
 92              Extent2D destination)
 93          {
 94              if (_intermediaryTexture == null
 95                  || _intermediaryTexture.Info.Width != width
 96                  || _intermediaryTexture.Info.Height != height
 97                  || !_intermediaryTexture.Info.Equals(view.Info))
 98              {
 99                  var originalInfo = view.Info;
100  
101                  var info = new TextureCreateInfo(
102                      width,
103                      height,
104                      originalInfo.Depth,
105                      originalInfo.Levels,
106                      originalInfo.Samples,
107                      originalInfo.BlockWidth,
108                      originalInfo.BlockHeight,
109                      originalInfo.BytesPerPixel,
110                      originalInfo.Format,
111                      originalInfo.DepthStencilMode,
112                      originalInfo.Target,
113                      originalInfo.SwizzleR,
114                      originalInfo.SwizzleG,
115                      originalInfo.SwizzleB,
116                      originalInfo.SwizzleA);
117                  _intermediaryTexture?.Dispose();
118                  _intermediaryTexture = _renderer.CreateTexture(info) as TextureView;
119              }
120  
121              _pipeline.SetCommandBuffer(cbs);
122              _pipeline.SetProgram(_scalingProgram);
123              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
124  
125              float srcWidth = Math.Abs(source.X2 - source.X1);
126              float srcHeight = Math.Abs(source.Y2 - source.Y1);
127              float scaleX = srcWidth / view.Width;
128              float scaleY = srcHeight / view.Height;
129  
130              ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
131              {
132                  source.X1,
133                  source.X2,
134                  source.Y1,
135                  source.Y2,
136                  destination.X1,
137                  destination.X2,
138                  destination.Y1,
139                  destination.Y2,
140                  scaleX,
141                  scaleY,
142              };
143  
144              int rangeSize = dimensionsBuffer.Length * sizeof(float);
145              using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
146              buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
147  
148              ReadOnlySpan<float> sharpeningBufferData = stackalloc float[] { 1.5f - (Level * 0.01f * 1.5f) };
149              using var sharpeningBuffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, sizeof(float));
150              sharpeningBuffer.Holder.SetDataUnchecked(sharpeningBuffer.Offset, sharpeningBufferData);
151  
152              int threadGroupWorkRegionDim = 16;
153              int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
154              int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
155  
156              _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
157              _pipeline.SetImage(ShaderStage.Compute, 0, _intermediaryTexture.GetView(FormatTable.ConvertRgba8SrgbToUnorm(view.Info.Format)));
158              _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
159              _pipeline.ComputeBarrier();
160  
161              // Sharpening pass
162              _pipeline.SetProgram(_sharpeningProgram);
163              _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, _intermediaryTexture, _sampler);
164              _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(4, sharpeningBuffer.Range) });
165              _pipeline.SetImage(0, destinationTexture);
166              _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
167              _pipeline.ComputeBarrier();
168  
169              _pipeline.Finish();
170          }
171      }
172  }