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 }