aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.Vulkan
diff options
context:
space:
mode:
authorZenoArrows <129334871+ZenoArrows@users.noreply.github.com>2024-09-17 20:30:50 +0200
committerGitHub <noreply@github.com>2024-09-17 15:30:50 -0300
commitf39e89ece79436f5058bb58d50a1a4dcd6823f4e (patch)
tree079cd6cd332b1f2691bb13f587789e191ddf2c6d /src/Ryujinx.Graphics.Vulkan
parentcf77c011e4f90efd93509c3852350c48f2597344 (diff)
Add area sampling scaler to allow for super-sampled anti-aliasing. (#7304)
* Add area sampling scaler to allow for super-sampled anti-aliasing. * Area scaling filter doesn't have a scaling level. * Add further clarification to the tooltip on how to achieve supersampling. * ShaderHelper: Merge the two CompileProgram functions. * Convert tabs to spaces in area scaling shaders * Fixup Vulkan and OpenGL project files. * AreaScaling: Replace texture() by texelFetch() and use integer vectors. No functional difference, but it cleans up the code a bit. * AreaScaling: Delete unused sharpening level member. Also rename _scale to _sharpeningLevel for clarity and consistency. * AreaScaling: Delete unused scaleX/scaleY uniforms. * AreaScaling: Force the alpha to 1 when storing the pixel. * AreaScaling: Remove left-over sharpening buffer.
Diffstat (limited to 'src/Ryujinx.Graphics.Vulkan')
-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
5 files changed, 231 insertions, 0 deletions
diff --git a/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs b/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
new file mode 100644
index 00000000..87b46df8
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/AreaScalingFilter.cs
@@ -0,0 +1,101 @@
+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;
+using Format = Silk.NET.Vulkan.Format;
+using SamplerCreateInfo = Ryujinx.Graphics.GAL.SamplerCreateInfo;
+
+namespace Ryujinx.Graphics.Vulkan.Effects
+{
+ internal class AreaScalingFilter : IScalingFilter
+ {
+ private readonly VulkanRenderer _renderer;
+ private PipelineHelperShader _pipeline;
+ private ISampler _sampler;
+ private ShaderCollection _scalingProgram;
+ private Device _device;
+
+ public float Level { get; set; }
+
+ public AreaScalingFilter(VulkanRenderer renderer, Device device)
+ {
+ _device = device;
+ _renderer = renderer;
+
+ Initialize();
+ }
+
+ public void Dispose()
+ {
+ _pipeline.Dispose();
+ _scalingProgram.Dispose();
+ _sampler.Dispose();
+ }
+
+ public void Initialize()
+ {
+ _pipeline = new PipelineHelperShader(_renderer, _device);
+
+ _pipeline.Initialize();
+
+ var scalingShader = EmbeddedResources.Read("Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.spv");
+
+ var scalingResourceLayout = new ResourceLayoutBuilder()
+ .Add(ResourceStages.Compute, ResourceType.UniformBuffer, 2)
+ .Add(ResourceStages.Compute, ResourceType.TextureAndSampler, 1)
+ .Add(ResourceStages.Compute, ResourceType.Image, 0, true).Build();
+
+ _sampler = _renderer.CreateSampler(SamplerCreateInfo.Create(MinFilter.Linear, MagFilter.Linear));
+
+ _scalingProgram = _renderer.CreateProgramWithMinimalLayout(new[]
+ {
+ new ShaderSource(scalingShader, ShaderStage.Compute, TargetLanguage.Spirv),
+ }, scalingResourceLayout);
+ }
+
+ public void Run(
+ TextureView view,
+ CommandBufferScoped cbs,
+ Auto<DisposableImageView> destinationTexture,
+ Format format,
+ int width,
+ int height,
+ Extent2D source,
+ Extent2D destination)
+ {
+ _pipeline.SetCommandBuffer(cbs);
+ _pipeline.SetProgram(_scalingProgram);
+ _pipeline.SetTextureAndSampler(ShaderStage.Compute, 1, view, _sampler);
+
+ ReadOnlySpan<float> dimensionsBuffer = stackalloc float[]
+ {
+ source.X1,
+ source.X2,
+ source.Y1,
+ source.Y2,
+ destination.X1,
+ destination.X2,
+ destination.Y1,
+ destination.Y2,
+ };
+
+ int rangeSize = dimensionsBuffer.Length * sizeof(float);
+ using var buffer = _renderer.BufferManager.ReserveOrCreate(_renderer, cbs, rangeSize);
+ buffer.Holder.SetDataUnchecked(buffer.Offset, dimensionsBuffer);
+
+ int threadGroupWorkRegionDim = 16;
+ int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+ int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+
+ _pipeline.SetUniformBuffers(stackalloc[] { new BufferAssignment(2, buffer.Range) });
+ _pipeline.SetImage(0, destinationTexture);
+ _pipeline.DispatchCompute(dispatchX, dispatchY, 1);
+ _pipeline.ComputeBarrier();
+
+ _pipeline.Finish();
+ }
+ }
+}
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 00000000..e34dd77d
--- /dev/null
+++ b/src/Ryujinx.Graphics.Vulkan/Effects/Shaders/AreaScaling.glsl
@@ -0,0 +1,122 @@
+// Scaling
+
+#version 430 core
+layout (local_size_x = 16, local_size_y = 16) in;
+layout( rgba8, binding = 0, set = 3) uniform image2D imgOutput;
+layout( binding = 1, set = 2) uniform sampler2D Source;
+layout( binding = 2 ) uniform dimensions{
+ float srcX0;
+ float srcX1;
+ float srcY0;
+ float srcY1;
+ float dstX0;
+ float dstX1;
+ float dstY0;
+ float dstY1;
+};
+
+/***** Area Sampling *****/
+
+// By Sam Belliveau and Filippo Tarpini. Public Domain license.
+// Effectively a more accurate sharp bilinear filter when upscaling,
+// that also works as a mathematically perfect downscale filter.
+// https://entropymine.com/imageworsener/pixelmixing/
+// https://github.com/obsproject/obs-studio/pull/1715
+// https://legacy.imagemagick.org/Usage/filter/
+vec4 AreaSampling(vec2 xy)
+{
+ // Determine the sizes of the source and target images.
+ vec2 source_size = vec2(abs(srcX1 - srcX0), abs(srcY1 - srcY0));
+ vec2 target_size = vec2(abs(dstX1 - dstX0), abs(dstY1 - dstY0));
+ vec2 inverted_target_size = vec2(1.0) / target_size;
+
+ // Compute the top-left and bottom-right corners of the target pixel box.
+ vec2 t_beg = floor(xy - vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1));
+ vec2 t_end = t_beg + vec2(1.0, 1.0);
+
+ // Convert the target pixel box to source pixel box.
+ vec2 beg = t_beg * inverted_target_size * source_size;
+ vec2 end = t_end * inverted_target_size * source_size;
+
+ // Compute the top-left and bottom-right corners of the pixel box.
+ ivec2 f_beg = ivec2(beg);
+ ivec2 f_end = ivec2(end);
+
+ // Compute how much of the start and end pixels are covered horizontally & vertically.
+ float area_w = 1.0 - fract(beg.x);
+ float area_n = 1.0 - fract(beg.y);
+ float area_e = fract(end.x);
+ float area_s = fract(end.y);
+
+ // Compute the areas of the corner pixels in the pixel box.
+ float area_nw = area_n * area_w;
+ float area_ne = area_n * area_e;
+ float area_sw = area_s * area_w;
+ float area_se = area_s * area_e;
+
+ // Initialize the color accumulator.
+ vec4 avg_color = vec4(0.0, 0.0, 0.0, 0.0);
+
+ // Accumulate corner pixels.
+ avg_color += area_nw * texelFetch(Source, ivec2(f_beg.x, f_beg.y), 0);
+ avg_color += area_ne * texelFetch(Source, ivec2(f_end.x, f_beg.y), 0);
+ avg_color += area_sw * texelFetch(Source, ivec2(f_beg.x, f_end.y), 0);
+ avg_color += area_se * texelFetch(Source, ivec2(f_end.x, f_end.y), 0);
+
+ // Determine the size of the pixel box.
+ int x_range = int(f_end.x - f_beg.x - 0.5);
+ int y_range = int(f_end.y - f_beg.y - 0.5);
+
+ // Accumulate top and bottom edge pixels.
+ for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
+ {
+ avg_color += area_n * texelFetch(Source, ivec2(x, f_beg.y), 0);
+ avg_color += area_s * texelFetch(Source, ivec2(x, f_end.y), 0);
+ }
+
+ // Accumulate left and right edge pixels and all the pixels in between.
+ for (int y = f_beg.y + 1; y <= f_beg.y + y_range; ++y)
+ {
+ avg_color += area_w * texelFetch(Source, ivec2(f_beg.x, y), 0);
+ avg_color += area_e * texelFetch(Source, ivec2(f_end.x, y), 0);
+
+ for (int x = f_beg.x + 1; x <= f_beg.x + x_range; ++x)
+ {
+ avg_color += texelFetch(Source, ivec2(x, y), 0);
+ }
+ }
+
+ // Compute the area of the pixel box that was sampled.
+ float area_corners = area_nw + area_ne + area_sw + area_se;
+ float area_edges = float(x_range) * (area_n + area_s) + float(y_range) * (area_w + area_e);
+ float area_center = float(x_range) * float(y_range);
+
+ // Return the normalized average color.
+ return avg_color / (area_corners + area_edges + area_center);
+}
+
+float insideBox(vec2 v, vec2 bLeft, vec2 tRight) {
+ vec2 s = step(bLeft, v) - step(tRight, v);
+ return s.x * s.y;
+}
+
+vec2 translateDest(vec2 pos) {
+ vec2 translatedPos = vec2(pos.x, pos.y);
+ translatedPos.x = dstX1 < dstX0 ? dstX1 - translatedPos.x : translatedPos.x;
+ translatedPos.y = dstY0 < dstY1 ? dstY1 + dstY0 - translatedPos.y - 1 : translatedPos.y;
+ return translatedPos;
+}
+
+void main()
+{
+ vec2 bLeft = vec2(dstX0 < dstX1 ? dstX0 : dstX1, dstY0 < dstY1 ? dstY0 : dstY1);
+ vec2 tRight = vec2(dstX1 > dstX0 ? dstX1 : dstX0, dstY1 > dstY0 ? dstY1 : dstY0);
+ ivec2 loc = ivec2(gl_GlobalInvocationID.x, gl_GlobalInvocationID.y);
+ if (insideBox(loc, bLeft, tRight) == 0) {
+ imageStore(imgOutput, loc, vec4(0, 0, 0, 1));
+ return;
+ }
+
+ vec4 outColor = AreaSampling(loc);
+ imageStore(imgOutput, ivec2(translateDest(loc)), vec4(outColor.rgb, 1));
+}
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 00000000..7d097280
--- /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 f6a7be91..aae28733 100644
--- a/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
+++ b/src/Ryujinx.Graphics.Vulkan/Ryujinx.Graphics.Vulkan.csproj
@@ -15,6 +15,7 @@
<ItemGroup>
<EmbeddedResource Include="Effects\Textures\SmaaAreaTexture.bin" />
<EmbeddedResource Include="Effects\Textures\SmaaSearchTexture.bin" />
+ <EmbeddedResource Include="Effects\Shaders\AreaScaling.spv" />
<EmbeddedResource Include="Effects\Shaders\FsrScaling.spv" />
<EmbeddedResource Include="Effects\Shaders\FsrSharpening.spv" />
<EmbeddedResource Include="Effects\Shaders\Fxaa.spv" />
diff --git a/src/Ryujinx.Graphics.Vulkan/Window.cs b/src/Ryujinx.Graphics.Vulkan/Window.cs
index d67362be..3dc6d4e1 100644
--- a/src/Ryujinx.Graphics.Vulkan/Window.cs
+++ b/src/Ryujinx.Graphics.Vulkan/Window.cs
@@ -568,6 +568,13 @@ namespace Ryujinx.Graphics.Vulkan
_scalingFilter.Level = _scalingFilterLevel;
break;
+ case ScalingFilter.Area:
+ if (_scalingFilter is not AreaScalingFilter)
+ {
+ _scalingFilter?.Dispose();
+ _scalingFilter = new AreaScalingFilter(_gd, _device);
+ }
+ break;
}
}
}