aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--src/Ryujinx.Graphics.GAL/UpscaleType.cs1
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs106
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs6
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs23
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl119
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl2
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj1
-rw-r--r--src/Ryujinx.Graphics.OpenGL/Window.cs10
-rw-r--r--src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs101
-rw-r--r--src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl122
-rw-r--r--src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spvbin0 -> 12428 bytes
-rw-r--r--src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj1
-rw-r--r--src/Ryujinx.Graphics.Vulkan/Window.cs7
-rw-r--r--src/Ryujinx/Assets/Locales/en_US.json3
-rw-r--r--src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml5
15 files changed, 489 insertions, 18 deletions
diff --git a/src/Ryujinx.Graphics.GAL/UpscaleType.cs b/src/Ryujinx.Graphics.GAL/UpscaleType.cs
index ca24199c4..e2482faef 100644
--- a/src/Ryujinx.Graphics.GAL/UpscaleType.cs
+++ b/src/Ryujinx.Graphics.GAL/UpscaleType.cs
@@ -5,5 +5,6 @@ namespace Ryujinx.Graphics.GAL
5 Bilinear, 5 Bilinear,
6 Nearest, 6 Nearest,
7 Fsr, 7 Fsr,
8 Area,
8 } 9 }
9} 10}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
new file mode 100644
index 000000000..9b19f2f26
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
@@ -0,0 +1,106 @@
1using OpenTK.Graphics.OpenGL;
2using Ryujinx.Common;
3using Ryujinx.Graphics.GAL;
4using Ryujinx.Graphics.OpenGL.Image;
5using System;
6using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
7
8namespace Ryujinx.Graphics.OpenGL.Effects
9{
10 internal class AreaScalingFilter : IScalingFilter
11 {
12 private readonly OpenGLRenderer _renderer;
13 private int _inputUniform;
14 private int _outputUniform;
15 private int _srcX0Uniform;
16 private int _srcX1Uniform;
17 private int _srcY0Uniform;
18 private int _scalingShaderProgram;
19 private int _srcY1Uniform;
20 private int _dstX0Uniform;
21 private int _dstX1Uniform;
22 private int _dstY0Uniform;
23 private int _dstY1Uniform;
24
25 public float Level { get; set; }
26
27 public AreaScalingFilter(OpenGLRenderer renderer)
28 {
29 Initialize();
30
31 _renderer = renderer;
32 }
33
34 public void Dispose()
35 {
36 if (_scalingShaderProgram != 0)
37 {
38 GL.DeleteProgram(_scalingShaderProgram);
39 }
40 }
41
42 private void Initialize()
43 {
44 var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl");
45
46 _scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
47
48 _inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
49 _outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
50
51 _srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
52 _srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
53 _srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
54 _srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
55 _dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
56 _dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
57 _dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
58 _dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
59 }
60
61 public void Run(
62 TextureView view,
63 TextureView destinationTexture,
64 int width,
65 int height,
66 Extents2D source,
67 Extents2D destination)
68 {
69 int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
70 int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
71 GL.ActiveTexture(TextureUnit.Texture0);
72 int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
73
74 GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
75
76 int threadGroupWorkRegionDim = 16;
77 int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
78 int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
79
80 // Scaling pass
81 GL.UseProgram(_scalingShaderProgram);
82 view.Bind(0);
83 GL.Uniform1(_inputUniform, 0);
84 GL.Uniform1(_outputUniform, 0);
85 GL.Uniform1(_srcX0Uniform, (float)source.X1);
86 GL.Uniform1(_srcX1Uniform, (float)source.X2);
87 GL.Uniform1(_srcY0Uniform, (float)source.Y1);
88 GL.Uniform1(_srcY1Uniform, (float)source.Y2);
89 GL.Uniform1(_dstX0Uniform, (float)destination.X1);
90 GL.Uniform1(_dstX1Uniform, (float)destination.X2);
91 GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
92 GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
93 GL.DispatchCompute(dispatchX, dispatchY, 1);
94
95 GL.UseProgram(previousProgram);
96 GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
97
98 (_renderer.Pipeline as Pipeline).RestoreImages1And2();
99
100 GL.ActiveTexture(TextureUnit.Texture0);
101 GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
102
103 GL.ActiveTexture((TextureUnit)previousUnit);
104 }
105 }
106}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
index 1a130bebb..0522e28e0 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
@@ -18,7 +18,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
18 private int _srcY0Uniform; 18 private int _srcY0Uniform;
19 private int _scalingShaderProgram; 19 private int _scalingShaderProgram;
20 private int _sharpeningShaderProgram; 20 private int _sharpeningShaderProgram;
21 private float _scale = 1; 21 private float _sharpeningLevel = 1;
22 private int _srcY1Uniform; 22 private int _srcY1Uniform;
23 private int _dstX0Uniform; 23 private int _dstX0Uniform;
24 private int _dstX1Uniform; 24 private int _dstX1Uniform;
@@ -30,10 +30,10 @@ namespace Ryujinx.Graphics.OpenGL.Effects
30 30
31 public float Level 31 public float Level
32 { 32 {
33 get => _scale; 33 get => _sharpeningLevel;
34 set 34 set
35 { 35 {
36 _scale = MathF.Max(0.01f, value); 36 _sharpeningLevel = MathF.Max(0.01f, value);
37 } 37 }
38 } 38 }
39 39
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
index c25fe5b25..637b2fba8 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
@@ -1,4 +1,5 @@
1using OpenTK.Graphics.OpenGL; 1using OpenTK.Graphics.OpenGL;
2using Ryujinx.Common.Logging;
2 3
3namespace Ryujinx.Graphics.OpenGL.Effects 4namespace Ryujinx.Graphics.OpenGL.Effects
4{ 5{
@@ -6,18 +7,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
6 { 7 {
7 public static int CompileProgram(string shaderCode, ShaderType shaderType) 8 public static int CompileProgram(string shaderCode, ShaderType shaderType)
8 { 9 {
9 var shader = GL.CreateShader(shaderType); 10 return CompileProgram(new string[] { shaderCode }, shaderType);
10 GL.ShaderSource(shader, shaderCode);
11 GL.CompileShader(shader);
12
13 var program = GL.CreateProgram();
14 GL.AttachShader(program, shader);
15 GL.LinkProgram(program);
16
17 GL.DetachShader(program, shader);
18 GL.DeleteShader(shader);
19
20 return program;
21 } 11 }
22 12
23 public static int CompileProgram(string[] shaders, ShaderType shaderType) 13 public static int CompileProgram(string[] shaders, ShaderType shaderType)
@@ -26,6 +16,15 @@ namespace Ryujinx.Graphics.OpenGL.Effects
26 GL.ShaderSource(shader, shaders.Length, shaders, (int[])null); 16 GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
27 GL.CompileShader(shader); 17 GL.CompileShader(shader);
28 18
19 GL.GetShader(shader, ShaderParameter.CompileStatus, out int isCompiled);
20 if (isCompiled == 0)
21 {
22 string log = GL.GetShaderInfoLog(shader);
23 Logger.Error?.Print(LogClass.Gpu, $"Failed to compile effect shader:\n\n{log}\n");
24 GL.DeleteShader(shader);
25 return 0;
26 }
27
29 var program = GL.CreateProgram(); 28 var program = GL.CreateProgram();
30 GL.AttachShader(program, shader); 29 GL.AttachShader(program, shader);
31 GL.LinkProgram(program); 30 GL.LinkProgram(program);
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl
new file mode 100644
index 000000000..0fe20d3f9
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl
@@ -0,0 +1,119 @@
1#version 430 core
2precision mediump float;
3layout (local_size_x = 16, local_size_y = 16) in;
4layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
5layout( location=1 ) uniform sampler2D Source;
6layout( location=2 ) uniform float srcX0;
7layout( location=3 ) uniform float srcX1;
8layout( location=4 ) uniform float srcY0;
9layout( location=5 ) uniform float srcY1;
10layout( location=6 ) uniform float dstX0;
11layout( location=7 ) uniform float dstX1;
12layout( location=8 ) uniform float dstY0;
13layout( location=9 ) uniform float dstY1;
14
15/***** Area Sampling *****/
16
17// By Sam Belliveau and Filippo Tarpini. Public Domain license.
18// Effectively a more accurate sharp bilinear filter when upscaling,
19// that also works as a mathematically perfect downscale filter.
20// https://entropymine.com/imageworsener/pixelmixing/
21// https://github.com/obsproject/obs-studio/pull/1715
22// https://legacy.imagemagick.org/Usage/filter/
23vec4 AreaSampling(vec2 xy)
24{
25 // Determine the sizes of the source and target images.
26 vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
27 vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
28 vec2 inverted_target_size = vec2(1.0) / target_size;
29
30 // Compute the top-left and bottom-right corners of the target pixel box.
31 vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
32 vec2 t_end = t_beg + vec2(1.0, 1.0);
33
34 // Convert the target pixel box to source pixel box.
35 vec2 beg = t_beg * inverted_target_size * source_size;
36 vec2 end = t_end * inverted_target_size * source_size;
37
38 // Compute the top-left and bottom-right corners of the pixel box.
39 ivec2 f_beg = ivec2(beg);
40 ivec2 f_end = ivec2(end);
41
42 // Compute how much of the start and end pixels are covered horizontally & vertically.
43 float area_w = 1.0 - fract(beg.x);
44 float area_n = 1.0 - fract(beg.y);
45 float area_e = fract(end.x);
46 float area_s = fract(end.y);
47
48 // Compute the areas of the corner pixels in the pixel box.
49 float area_nw = area_n * area_w;
50 float area_ne = area_n * area_e;
51 float area_sw = area_s * area_w;
52 float area_se = area_s * area_e;
53
54 // Initialize the color accumulator.
55 vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
56
57 // Accumulate corner pixels.
58 avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
59 avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
60 avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
61 avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
62
63 // Determine the size of the pixel box.
64 int x_range = int(f_end.x - f_beg.x - 0.5);
65 int y_range = int(f_end.y - f_beg.y - 0.5);
66
67 // Accumulate top and bottom edge pixels.
68 for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
69 {
70 avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
71 avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
72 }
73
74 // Accumulate left and right edge pixels and all the pixels in between.
75 for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
76 {
77 avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
78 avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
79
80 for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
81 {
82 avg_color += texelFetch(Source, ivec2(x, y), 0);
83 }
84 }
85
86 // Compute the area of the pixel box that was sampled.
87 float area_corners = area_nw + area_ne + area_sw + area_se;
88 float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
89 float area_center = float(x_range) * float(y_range);
90
91 // Return the normalized average color.
92 return avg_color / (area_corners + area_edges + area_center);
93}
94
95float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
96 vec2 s = step(bLeft, v) - step(tRight, v);
97 return s.x * s.y;
98}
99
100vec2 translateDest(vec2 pos) {
101 vec2 translatedPos = vec2(pos.x, pos.y);
102 translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
103 translatedPos.y = dstY0 > dstY1 ? dstY0 + dstY1 - translatedPos.y - 1 : translatedPos.y;
104 return translatedPos;
105}
106
107void main()
108{
109 vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
110 vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
111 ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
112 if (insideBox(loc, bLeft, tRight) == 0) {
113 imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
114 return;
115 }
116
117 vec4 outColor = AreaSampling(loc);
118 imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
119}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
index 8e8755db2..3c7d485b1 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
@@ -85,4 +85,4 @@ void main() {
85 CurrFilter(gxy); 85 CurrFilter(gxy);
86 gxy.x -= 8u; 86 gxy.x -= 8u;
87 CurrFilter(gxy); 87 CurrFilter(gxy);
88} \ No newline at end of file 88}
diff --git a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
index 3d64da99b..f3071f486 100644
--- a/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
+++ b/src/Ryujinx.Graphics.OpenGL/Ryujinx.Graphics.OpenGL.csproj
@@ -21,6 +21,7 @@
21 <EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" /> 21 <EmbeddedResource Include="Effects\Shaders\ffx_fsr1.h" />
22 <EmbeddedResource Include="Effects\Shaders\ffx_a.h" /> 22 <EmbeddedResource Include="Effects\Shaders\ffx_a.h" />
23 <EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" /> 23 <EmbeddedResource Include="Effects\Shaders\fsr_scaling.glsl" />
24 <EmbeddedResource Include="Effects\Shaders\area_scaling.glsl" />
24 </ItemGroup> 25 </ItemGroup>
25 26
26 <ItemGroup> 27 <ItemGroup>
diff --git a/src/Ryujinx.Graphics.OpenGL/Window.cs b/src/Ryujinx.Graphics.OpenGL/Window.cs
index 6bcfefa4e..285ab725e 100644
--- a/src/Ryujinx.Graphics.OpenGL/Window.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Window.cs
@@ -375,6 +375,16 @@ namespace Ryujinx.Graphics.OpenGL
375 375
376 RecreateUpscalingTexture(); 376 RecreateUpscalingTexture();
377 break; 377 break;
378 case ScalingFilter.Area:
379 if (_scalingFilter is not AreaScalingFilter)
380 {
381 _scalingFilter?.Dispose();
382 _scalingFilter = new AreaScalingFilter(_renderer);
383 }
384 _isLinear = false;
385
386 RecreateUpscalingTexture();
387 break;
378 } 388 }
379 } 389 }
380 } 390 }
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
new file mode 100644
index 000000000..87b46df80
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
@@ -0,0 +1,101 @@
1using Ryujinx.Common;
2using Ryujinx.Graphics.GAL;
3using Ryujinx.Graphics.Shader;
4using Ryujinx.Graphics.Shader.Translation;
5using Silk.NET.Vulkan;
6using System;
7using Extent2D = Ryujinx.Graphics.GAL.Extents2D;
8using Format = Silk.NET.Vulkan.Format;
9using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
10
11namespace Ryujinx.Graphics.Vulkan.Effects
12{
13 internal class AreaScalingFilter : IScalingFilter
14 {
15 private readonly VulkanRenderer _renderer;
16 private PipelineHelperShader _pipeline;
17 private ISampler _sampler;
18 private ShaderCollection _scalingProgram;
19 private Device _device;
20
21 public float Level { get; set; }
22
23 public AreaScalingFilter(VulkanRenderer renderer, Device device)
24 {
25 _device = device;
26 _renderer = renderer;
27
28 Initialize();
29 }
30
31 public void Dispose()
32 {
33 _pipeline.Dispose();
34 _scalingProgram.Dispose();
35 _sampler.Dispose();
36 }
37
38 public void Initialize()
39 {
40 _pipeline = new PipelineHelperShader(_renderer, _device);
41
42 _pipeline.Initialize();
43
44 var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv");
45
46 var scalingResourceLayout = new ResourceLayoutBuilder()
47 .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
48 .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
49 .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
50
51 _sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
52
53 _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
54 {
55 new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
56 }, scalingResourceLayout);
57 }
58
59 public void Run(
60 TextureView view,
61 CommandBufferScoped cbs,
62 Auto<DisposableImageView> destinationTexture,
63 Format format,
64 int width,
65 int height,
66 Extent2D source,
67 Extent2D destination)
68 {
69 _pipeline.SetCommandBuffer(cbs);
70 _pipeline.SetProgram(_scalingProgram);
71 _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
72
73 ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
74 {
75 source.X1,
76 source.X2,
77 source.Y1,
78 source.Y2,
79 destination.X1,
80 destination.X2,
81 destination.Y1,
82 destination.Y2,
83 };
84
85 int rangeSize = dimensionsBuffer.Length * sizeof(float);
86 using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
87 buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
88
89 int threadGroupWorkRegionDim = 16;
90 int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
91 int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
92
93 _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
94 _pipeline.SetImage(0, destinationTexture);
95 _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
96 _pipeline.ComputeBarrier();
97
98 _pipeline.Finish();
99 }
100 }
101}
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
new file mode 100644
index 000000000..e34dd77dd
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
@@ -0,0 +1,122 @@
1// Scaling
2
3#version 430 core
4layout (local_size_x = 16, local_size_y = 16) in;
5layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
6layout( binding = 1, set = 2) uniform sampler2D Source;
7layout( binding = 2 ) uniform dimensions{
8 float srcX0;
9 float srcX1;
10 float srcY0;
11 float srcY1;
12 float dstX0;
13 float dstX1;
14 float dstY0;
15 float dstY1;
16};
17
18/***** Area Sampling *****/
19
20// By Sam Belliveau and Filippo Tarpini. Public Domain license.
21// Effectively a more accurate sharp bilinear filter when upscaling,
22// that also works as a mathematically perfect downscale filter.
23// https://entropymine.com/imageworsener/pixelmixing/
24// https://github.com/obsproject/obs-studio/pull/1715
25// https://legacy.imagemagick.org/Usage/filter/
26vec4 AreaSampling(vec2 xy)
27{
28 // Determine the sizes of the source and target images.
29 vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
30 vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
31 vec2 inverted_target_size = vec2(1.0) / target_size;
32
33 // Compute the top-left and bottom-right corners of the target pixel box.
34 vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
35 vec2 t_end = t_beg + vec2(1.0, 1.0);
36
37 // Convert the target pixel box to source pixel box.
38 vec2 beg = t_beg * inverted_target_size * source_size;
39 vec2 end = t_end * inverted_target_size * source_size;
40
41 // Compute the top-left and bottom-right corners of the pixel box.
42 ivec2 f_beg = ivec2(beg);
43 ivec2 f_end = ivec2(end);
44
45 // Compute how much of the start and end pixels are covered horizontally & vertically.
46 float area_w = 1.0 - fract(beg.x);
47 float area_n = 1.0 - fract(beg.y);
48 float area_e = fract(end.x);
49 float area_s = fract(end.y);
50
51 // Compute the areas of the corner pixels in the pixel box.
52 float area_nw = area_n * area_w;
53 float area_ne = area_n * area_e;
54 float area_sw = area_s * area_w;
55 float area_se = area_s * area_e;
56
57 // Initialize the color accumulator.
58 vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
59
60 // Accumulate corner pixels.
61 avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
62 avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
63 avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
64 avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
65
66 // Determine the size of the pixel box.
67 int x_range = int(f_end.x - f_beg.x - 0.5);
68 int y_range = int(f_end.y - f_beg.y - 0.5);
69
70 // Accumulate top and bottom edge pixels.
71 for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
72 {
73 avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
74 avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
75 }
76
77 // Accumulate left and right edge pixels and all the pixels in between.
78 for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
79 {
80 avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
81 avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
82
83 for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
84 {
85 avg_color += texelFetch(Source, ivec2(x, y), 0);
86 }
87 }
88
89 // Compute the area of the pixel box that was sampled.
90 float area_corners = area_nw + area_ne + area_sw + area_se;
91 float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
92 float area_center = float(x_range) * float(y_range);
93
94 // Return the normalized average color.
95 return avg_color / (area_corners + area_edges + area_center);
96}
97
98float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
99 vec2 s = step(bLeft, v) - step(tRight, v);
100 return s.x * s.y;
101}
102
103vec2 translateDest(vec2 pos) {
104 vec2 translatedPos = vec2(pos.x, pos.y);
105 translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
106 translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y;
107 return translatedPos;
108}
109
110void main()
111{
112 vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
113 vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
114 ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
115 if (insideBox(loc, bLeft, tRight) == 0) {
116 imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
117 return;
118 }
119
120 vec4 outColor = AreaSampling(loc);
121 imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
122}
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv
new file mode 100644
index 000000000..7d097280f
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv
Binary files differ
diff --git a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
index f6a7be91e..aae28733f 100644
--- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
+++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -15,6 +15,7 @@
15 <ItemGroup> 15 <ItemGroup>
16 <EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" /> 16 <EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
17 <EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" /> 17 <EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
18 <EmbeddedResource Include="Effects\Shaders\AreaScaling.spv" />
18 <EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" /> 19 <EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
19 <EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" /> 20 <EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
20 <EmbeddedResource Include="Effects\Shaders\Fxaa.spv" /> 21 <EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />
diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs
index d67362be3..3dc6d4e19 100644
--- a/src/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Window.cs
@@ -568,6 +568,13 @@ namespace Ryujinx.Graphics.Vulkan
568 568
569 _scalingFilter.Level = _scalingFilterLevel; 569 _scalingFilter.Level = _scalingFilterLevel;
570 break; 570 break;
571 case ScalingFilter.Area:
572 if (_scalingFilter is not AreaScalingFilter)
573 {
574 _scalingFilter?.Dispose();
575 _scalingFilter = new AreaScalingFilter(_gd, _device);
576 }
577 break;
571 } 578 }
572 } 579 }
573 } 580 }
diff --git a/src/Ryujinx/Assets/Locales/en_US.json b/src/Ryujinx/Assets/Locales/en_US.json
index 3031dea0d..b3cab7f5f 100644
--- a/src/Ryujinx/Assets/Locales/en_US.json
+++ b/src/Ryujinx/Assets/Locales/en_US.json
@@ -758,10 +758,11 @@
758 "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.", 758 "GraphicsAATooltip": "Applies anti-aliasing to the game render.\n\nFXAA will blur most of the image, while SMAA will attempt to find jagged edges and smooth them out.\n\nNot recommended to use in conjunction with the FSR scaling filter.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on NONE if unsure.",
759 "GraphicsAALabel": "Anti-Aliasing:", 759 "GraphicsAALabel": "Anti-Aliasing:",
760 "GraphicsScalingFilterLabel": "Scaling Filter:", 760 "GraphicsScalingFilterLabel": "Scaling Filter:",
761 "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.", 761 "GraphicsScalingFilterTooltip": "Choose the scaling filter that will be applied when using resolution scale.\n\nBilinear works well for 3D games and is a safe default option.\n\nNearest is recommended for pixel art games.\n\nFSR 1.0 is merely a sharpening filter, not recommended for use with FXAA or SMAA.\n\nArea scaling is recommended when downscaling resolutions that are larger than the output window. It can be used to achieve a supersampled anti-aliasing effect when downscaling by more than 2x.\n\nThis option can be changed while a game is running by clicking \"Apply\" below; you can simply move the settings window aside and experiment until you find your preferred look for a game.\n\nLeave on BILINEAR if unsure.",
762 "GraphicsScalingFilterBilinear": "Bilinear", 762 "GraphicsScalingFilterBilinear": "Bilinear",
763 "GraphicsScalingFilterNearest": "Nearest", 763 "GraphicsScalingFilterNearest": "Nearest",
764 "GraphicsScalingFilterFsr": "FSR", 764 "GraphicsScalingFilterFsr": "FSR",
765 "GraphicsScalingFilterArea": "Area",
765 "GraphicsScalingFilterLevelLabel": "Level", 766 "GraphicsScalingFilterLevelLabel": "Level",
766 "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.", 767 "GraphicsScalingFilterLevelTooltip": "Set FSR 1.0 sharpening level. Higher is sharper.",
767 "SmaaLow": "SMAA Low", 768 "SmaaLow": "SMAA Low",
diff --git a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml
index 5cffc6848..0a12575ad 100644
--- a/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml
+++ b/src/Ryujinx/UI/Views/Settings/SettingsGraphicsView.axaml
@@ -1,4 +1,4 @@
1<UserControl 1<UserControl
2 x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsGraphicsView" 2 x:Class="Ryujinx.Ava.UI.Views.Settings.SettingsGraphicsView"
3 xmlns="https://github.com/avaloniaui" 3 xmlns="https://github.com/avaloniaui"
4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" 4 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
@@ -173,6 +173,9 @@
173 <ComboBoxItem> 173 <ComboBoxItem>
174 <TextBlock Text="{locale:Locale GraphicsScalingFilterFsr}" /> 174 <TextBlock Text="{locale:Locale GraphicsScalingFilterFsr}" />
175 </ComboBoxItem> 175 </ComboBoxItem>
176 <ComboBoxItem>
177 <TextBlock Text="{locale:Locale GraphicsScalingFilterArea}" />
178 </ComboBoxItem>
176 </ComboBox> 179 </ComboBox>
177 <controls:SliderScroll Value="{Binding ScalingFilterLevel}" 180 <controls:SliderScroll Value="{Binding ScalingFilterLevel}"
178 ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}" 181 ToolTip.Tip="{locale:Locale GraphicsScalingFilterLevelTooltip}"