aboutsummaryrefslogtreecommitdiff
path: root/src/Ryujinx.Graphics.OpenGL/Effects
diff options
context:
space:
mode:
Diffstat (limited to 'src/Ryujinx.Graphics.OpenGL/Effects')
-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
5 files changed, 240 insertions, 16 deletions
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
new file mode 100644
index 00000000..9b19f2f2
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/AreaScalingFilter.cs
@@ -0,0 +1,106 @@
+using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common;
+using Ryujinx.Graphics.GAL;
+using Ryujinx.Graphics.OpenGL.Image;
+using System;
+using static Ryujinx.Graphics.OpenGL.Effects.ShaderHelper;
+
+namespace Ryujinx.Graphics.OpenGL.Effects
+{
+ internal class AreaScalingFilter : IScalingFilter
+ {
+ private readonly OpenGLRenderer _renderer;
+ private int _inputUniform;
+ private int _outputUniform;
+ private int _srcX0Uniform;
+ private int _srcX1Uniform;
+ private int _srcY0Uniform;
+ private int _scalingShaderProgram;
+ private int _srcY1Uniform;
+ private int _dstX0Uniform;
+ private int _dstX1Uniform;
+ private int _dstY0Uniform;
+ private int _dstY1Uniform;
+
+ public float Level { get; set; }
+
+ public AreaScalingFilter(OpenGLRenderer renderer)
+ {
+ Initialize();
+
+ _renderer = renderer;
+ }
+
+ public void Dispose()
+ {
+ if (_scalingShaderProgram != 0)
+ {
+ GL.DeleteProgram(_scalingShaderProgram);
+ }
+ }
+
+ private void Initialize()
+ {
+ var scalingShader = EmbeddedResources.ReadAllText("Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl");
+
+ _scalingShaderProgram = CompileProgram(scalingShader, ShaderType.ComputeShader);
+
+ _inputUniform = GL.GetUniformLocation(_scalingShaderProgram, "Source");
+ _outputUniform = GL.GetUniformLocation(_scalingShaderProgram, "imgOutput");
+
+ _srcX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX0");
+ _srcX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcX1");
+ _srcY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY0");
+ _srcY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "srcY1");
+ _dstX0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX0");
+ _dstX1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstX1");
+ _dstY0Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY0");
+ _dstY1Uniform = GL.GetUniformLocation(_scalingShaderProgram, "dstY1");
+ }
+
+ public void Run(
+ TextureView view,
+ TextureView destinationTexture,
+ int width,
+ int height,
+ Extents2D source,
+ Extents2D destination)
+ {
+ int previousProgram = GL.GetInteger(GetPName.CurrentProgram);
+ int previousUnit = GL.GetInteger(GetPName.ActiveTexture);
+ GL.ActiveTexture(TextureUnit.Texture0);
+ int previousTextureBinding = GL.GetInteger(GetPName.TextureBinding2D);
+
+ GL.BindImageTexture(0, destinationTexture.Handle, 0, false, 0, TextureAccess.ReadWrite, SizedInternalFormat.Rgba8);
+
+ int threadGroupWorkRegionDim = 16;
+ int dispatchX = (width + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+ int dispatchY = (height + (threadGroupWorkRegionDim - 1)) / threadGroupWorkRegionDim;
+
+ // Scaling pass
+ GL.UseProgram(_scalingShaderProgram);
+ view.Bind(0);
+ GL.Uniform1(_inputUniform, 0);
+ GL.Uniform1(_outputUniform, 0);
+ GL.Uniform1(_srcX0Uniform, (float)source.X1);
+ GL.Uniform1(_srcX1Uniform, (float)source.X2);
+ GL.Uniform1(_srcY0Uniform, (float)source.Y1);
+ GL.Uniform1(_srcY1Uniform, (float)source.Y2);
+ GL.Uniform1(_dstX0Uniform, (float)destination.X1);
+ GL.Uniform1(_dstX1Uniform, (float)destination.X2);
+ GL.Uniform1(_dstY0Uniform, (float)destination.Y1);
+ GL.Uniform1(_dstY1Uniform, (float)destination.Y2);
+ GL.DispatchCompute(dispatchX, dispatchY, 1);
+
+ GL.UseProgram(previousProgram);
+ GL.MemoryBarrier(MemoryBarrierFlags.ShaderImageAccessBarrierBit);
+
+ (_renderer.Pipeline as Pipeline).RestoreImages1And2();
+
+ GL.ActiveTexture(TextureUnit.Texture0);
+ GL.BindTexture(TextureTarget.Texture2D, previousTextureBinding);
+
+ GL.ActiveTexture((TextureUnit)previousUnit);
+ }
+ }
+}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs b/src/Ryujinx.Graphics.OpenGL/Effects/FsrScalingFilter.cs
index 1a130beb..0522e28e 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
private int _srcY0Uniform;
private int _scalingShaderProgram;
private int _sharpeningShaderProgram;
- private float _scale = 1;
+ private float _sharpeningLevel = 1;
private int _srcY1Uniform;
private int _dstX0Uniform;
private int _dstX1Uniform;
@@ -30,10 +30,10 @@ namespace Ryujinx.Graphics.OpenGL.Effects
public float Level
{
- get => _scale;
+ get => _sharpeningLevel;
set
{
- _scale = MathF.Max(0.01f, value);
+ _sharpeningLevel = MathF.Max(0.01f, value);
}
}
diff --git a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
index c25fe5b2..637b2fba 100644
--- a/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/ShaderHelper.cs
@@ -1,4 +1,5 @@
using OpenTK.Graphics.OpenGL;
+using Ryujinx.Common.Logging;
namespace Ryujinx.Graphics.OpenGL.Effects
{
@@ -6,18 +7,7 @@ namespace Ryujinx.Graphics.OpenGL.Effects
{
public static int CompileProgram(string shaderCode, ShaderType shaderType)
{
- var shader = GL.CreateShader(shaderType);
- GL.ShaderSource(shader, shaderCode);
- GL.CompileShader(shader);
-
- var program = GL.CreateProgram();
- GL.AttachShader(program, shader);
- GL.LinkProgram(program);
-
- GL.DetachShader(program, shader);
- GL.DeleteShader(shader);
-
- return program;
+ return CompileProgram(new string[] { shaderCode }, shaderType);
}
public static int CompileProgram(string[] shaders, ShaderType shaderType)
@@ -26,6 +16,15 @@ namespace Ryujinx.Graphics.OpenGL.Effects
GL.ShaderSource(shader, shaders.Length, shaders, (int[])null);
GL.CompileShader(shader);
+ GL.GetShader(shader, ShaderParameter.CompileStatus, out int isCompiled);
+ if (isCompiled == 0)
+ {
+ string log = GL.GetShaderInfoLog(shader);
+ Logger.Error?.Print(LogClass.Gpu, $"Failed to compile effect shader:\n\n{log}\n");
+ GL.DeleteShader(shader);
+ return 0;
+ }
+
var program = GL.CreateProgram();
GL.AttachShader(program, shader);
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 00000000..0fe20d3f
--- /dev/null
+++ b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/area_scaling.glsl
@@ -0,0 +1,119 @@
+#version 430 core
+precision mediump float;
+layout (local_size_x = 16, local_size_y = 16) in;
+layout(rgba8, binding = 0, location=0) uniform image2D imgOutput;
+layout( location=1 ) uniform sampler2D Source;
+layout( location=2 ) uniform float srcX0;
+layout( location=3 ) uniform float srcX1;
+layout( location=4 ) uniform float srcY0;
+layout( location=5 ) uniform float srcY1;
+layout( location=6 ) uniform float dstX0;
+layout( location=7 ) uniform float dstX1;
+layout( location=8 ) uniform float dstY0;
+layout( location=9 ) uniform 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 ? dstY0 + dstY1 - 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.OpenGL/Effects/Shaders/fsr_scaling.glsl b/src/Ryujinx.Graphics.OpenGL/Effects/Shaders/fsr_scaling.glsl
index 8e8755db..3c7d485b 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() {
CurrFilter(gxy);
gxy.x -= 8u;
CurrFilter(gxy);
-} \ No newline at end of file
+}