summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/common/settings.cpp10
-rw-r--r--src/common/settings.h17
-rw-r--r--src/video_core/host_shaders/CMakeLists.txt4
-rw-r--r--src/video_core/host_shaders/opengl_present_bicubic.frag56
-rw-r--r--src/video_core/host_shaders/opengl_present_scaleforce.frag135
-rw-r--r--src/video_core/host_shaders/vulkan_present_bicubic.frag56
-rw-r--r--src/video_core/host_shaders/vulkan_present_scaleforce.frag137
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.cpp27
-rw-r--r--src/video_core/renderer_opengl/renderer_opengl.h4
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.cpp123
-rw-r--r--src/video_core/renderer_vulkan/vk_blit_screen.h8
-rw-r--r--src/yuzu/configuration/config.cpp5
-rw-r--r--src/yuzu/configuration/config.h1
-rw-r--r--src/yuzu/configuration/configure_graphics.cpp25
-rw-r--r--src/yuzu/configuration/configure_graphics.ui46
15 files changed, 620 insertions, 34 deletions
diff --git a/src/common/settings.cpp b/src/common/settings.cpp
index 4b7fa4b82..f0686a7c5 100644
--- a/src/common/settings.cpp
+++ b/src/common/settings.cpp
@@ -48,6 +48,7 @@ void LogSettings() {
48 log_setting("Core_UseMultiCore", values.use_multi_core.GetValue()); 48 log_setting("Core_UseMultiCore", values.use_multi_core.GetValue());
49 log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue()); 49 log_setting("CPU_Accuracy", values.cpu_accuracy.GetValue());
50 log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue()); 50 log_setting("Renderer_UseResolutionScaling", values.resolution_setup.GetValue());
51 log_setting("Renderer_ScalingFilter", values.scaling_filter.GetValue());
51 log_setting("Renderer_UseSpeedLimit", values.use_speed_limit.GetValue()); 52 log_setting("Renderer_UseSpeedLimit", values.use_speed_limit.GetValue());
52 log_setting("Renderer_SpeedLimit", values.speed_limit.GetValue()); 53 log_setting("Renderer_SpeedLimit", values.speed_limit.GetValue());
53 log_setting("Renderer_UseDiskShaderCache", values.use_disk_shader_cache.GetValue()); 54 log_setting("Renderer_UseDiskShaderCache", values.use_disk_shader_cache.GetValue());
@@ -113,19 +114,10 @@ void UpdateRescalingInfo() {
113 info.up_scale = 1; 114 info.up_scale = 1;
114 info.down_shift = 1; 115 info.down_shift = 1;
115 break; 116 break;
116 case ResolutionSetup::Res3_4X:
117 info.up_scale = 3;
118 info.down_shift = 2;
119 break;
120 case ResolutionSetup::Res1X: 117 case ResolutionSetup::Res1X:
121 info.up_scale = 1; 118 info.up_scale = 1;
122 info.down_shift = 0; 119 info.down_shift = 0;
123 break; 120 break;
124 case ResolutionSetup::Res3_2X: {
125 info.up_scale = 3;
126 info.down_shift = 1;
127 break;
128 }
129 case ResolutionSetup::Res2X: 121 case ResolutionSetup::Res2X:
130 info.up_scale = 2; 122 info.up_scale = 2;
131 info.down_shift = 0; 123 info.down_shift = 0;
diff --git a/src/common/settings.h b/src/common/settings.h
index ca88c086b..f629c7c56 100644
--- a/src/common/settings.h
+++ b/src/common/settings.h
@@ -54,12 +54,16 @@ enum class NvdecEmulation : u32 {
54 54
55enum class ResolutionSetup : u32 { 55enum class ResolutionSetup : u32 {
56 Res1_2X = 0, 56 Res1_2X = 0,
57 Res3_4X = 1, 57 Res1X = 1,
58 Res1X = 2, 58 Res2X = 2,
59 Res3_2X = 3, 59 Res3X = 3,
60 Res2X = 4, 60 Res4X = 4,
61 Res3X = 5, 61};
62 Res4X = 6, 62
63enum class ScalingFilter : u32 {
64 Bilinear = 0,
65 Bicubic = 1,
66 ScaleForce = 2,
63}; 67};
64 68
65struct ResolutionScalingInfo { 69struct ResolutionScalingInfo {
@@ -471,6 +475,7 @@ struct Values {
471 475
472 ResolutionScalingInfo resolution_info{}; 476 ResolutionScalingInfo resolution_info{};
473 Setting<ResolutionSetup> resolution_setup{ResolutionSetup::Res1X, "resolution_setup"}; 477 Setting<ResolutionSetup> resolution_setup{ResolutionSetup::Res1X, "resolution_setup"};
478 Setting<ScalingFilter> scaling_filter{ScalingFilter::Bilinear, "scaling_filter"};
474 // *nix platforms may have issues with the borderless windowed fullscreen mode. 479 // *nix platforms may have issues with the borderless windowed fullscreen mode.
475 // Default to exclusive fullscreen on these platforms for now. 480 // Default to exclusive fullscreen on these platforms for now.
476 RangedSetting<FullscreenMode> fullscreen_mode{ 481 RangedSetting<FullscreenMode> fullscreen_mode{
diff --git a/src/video_core/host_shaders/CMakeLists.txt b/src/video_core/host_shaders/CMakeLists.txt
index 20d748c12..835b37944 100644
--- a/src/video_core/host_shaders/CMakeLists.txt
+++ b/src/video_core/host_shaders/CMakeLists.txt
@@ -6,11 +6,15 @@ set(SHADER_FILES
6 convert_float_to_depth.frag 6 convert_float_to_depth.frag
7 full_screen_triangle.vert 7 full_screen_triangle.vert
8 opengl_copy_bc4.comp 8 opengl_copy_bc4.comp
9 opengl_present_scaleforce.frag
10 opengl_present_bicubic.frag
9 opengl_present.frag 11 opengl_present.frag
10 opengl_present.vert 12 opengl_present.vert
11 pitch_unswizzle.comp 13 pitch_unswizzle.comp
12 vulkan_blit_color_float.frag 14 vulkan_blit_color_float.frag
13 vulkan_blit_depth_stencil.frag 15 vulkan_blit_depth_stencil.frag
16 vulkan_present_bicubic.frag
17 vulkan_present_scaleforce.frag
14 vulkan_present.frag 18 vulkan_present.frag
15 vulkan_present.vert 19 vulkan_present.vert
16 vulkan_quad_indexed.comp 20 vulkan_quad_indexed.comp
diff --git a/src/video_core/host_shaders/opengl_present_bicubic.frag b/src/video_core/host_shaders/opengl_present_bicubic.frag
new file mode 100644
index 000000000..17772095a
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_present_bicubic.frag
@@ -0,0 +1,56 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#version 460 core
6
7layout (location = 0) in vec2 frag_tex_coord;
8
9layout (location = 0) out vec4 color;
10
11layout (binding = 1) uniform sampler2D color_texture;
12
13vec4 cubic(float v) {
14 vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
15 vec4 s = n * n * n;
16 float x = s.x;
17 float y = s.y - 4.0 * s.x;
18 float z = s.z - 4.0 * s.y + 6.0 * s.x;
19 float w = 6.0 - x - y - z;
20 return vec4(x, y, z, w) * (1.0 / 6.0);
21}
22
23vec4 textureBicubic( sampler2D textureSampler, vec2 texCoords ) {
24
25 vec2 texSize = textureSize(textureSampler, 0);
26 vec2 invTexSize = 1.0 / texSize;
27
28 texCoords = texCoords * texSize - 0.5;
29
30 vec2 fxy = fract(texCoords);
31 texCoords -= fxy;
32
33 vec4 xcubic = cubic(fxy.x);
34 vec4 ycubic = cubic(fxy.y);
35
36 vec4 c = texCoords.xxyy + vec2(-0.5, +1.5).xyxy;
37
38 vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
39 vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
40
41 offset *= invTexSize.xxyy;
42
43 vec4 sample0 = texture(textureSampler, offset.xz);
44 vec4 sample1 = texture(textureSampler, offset.yz);
45 vec4 sample2 = texture(textureSampler, offset.xw);
46 vec4 sample3 = texture(textureSampler, offset.yw);
47
48 float sx = s.x / (s.x + s.y);
49 float sy = s.z / (s.z + s.w);
50
51 return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
52}
53
54void main() {
55 color = vec4(textureBicubic(color_texture, frag_tex_coord).rgb, 1.0f);
56}
diff --git a/src/video_core/host_shaders/opengl_present_scaleforce.frag b/src/video_core/host_shaders/opengl_present_scaleforce.frag
new file mode 100644
index 000000000..0153f62c0
--- /dev/null
+++ b/src/video_core/host_shaders/opengl_present_scaleforce.frag
@@ -0,0 +1,135 @@
1// from https://github.com/BreadFish64/ScaleFish/tree/master/scale_force
2
3// MIT License
4//
5// Copyright (c) 2020 BreadFish64
6//
7// Permission is hereby granted, free of charge, to any person obtaining a copy
8// of this software and associated documentation files (the "Software"), to deal
9// in the Software without restriction, including without limitation the rights
10// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11// copies of the Software, and to permit persons to whom the Software is
12// furnished to do so, subject to the following conditions:
13//
14// The above copyright notice and this permission notice shall be included in all
15// copies or substantial portions of the Software.
16//
17// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23// SOFTWARE.
24
25precision mediump float;
26
27layout (location = 0) in vec2 tex_coord;
28
29layout (location = 0) out vec4 frag_color;
30
31layout (binding = 1) uniform sampler2D input_texture;
32
33vec2 tex_size;
34vec2 inv_tex_size;
35
36vec4 cubic(float v) {
37 vec3 n = vec3(1.0, 2.0, 3.0) - v;
38 vec3 s = n * n * n;
39 float x = s.x;
40 float y = s.y - 4.0 * s.x;
41 float z = s.z - 4.0 * s.y + 6.0 * s.x;
42 float w = 6.0 - x - y - z;
43 return vec4(x, y, z, w) / 6.0;
44}
45
46// Bicubic interpolation
47vec4 textureBicubic(vec2 tex_coords) {
48 tex_coords = tex_coords * tex_size - 0.5;
49
50 vec2 fxy = modf(tex_coords, tex_coords);
51
52 vec4 xcubic = cubic(fxy.x);
53 vec4 ycubic = cubic(fxy.y);
54
55 vec4 c = tex_coords.xxyy + vec2(-0.5, +1.5).xyxy;
56
57 vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
58 vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
59
60 offset *= inv_tex_size.xxyy;
61
62 vec4 sample0 = textureLod(input_texture, offset.xz, 0.0);
63 vec4 sample1 = textureLod(input_texture, offset.yz, 0.0);
64 vec4 sample2 = textureLod(input_texture, offset.xw, 0.0);
65 vec4 sample3 = textureLod(input_texture, offset.yw, 0.0);
66
67 float sx = s.x / (s.x + s.y);
68 float sy = s.z / (s.z + s.w);
69
70 return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
71}
72
73mat4x3 center_matrix;
74vec4 center_alpha;
75
76// Finds the distance between four colors and cc in YCbCr space
77vec4 ColorDist(vec4 A, vec4 B, vec4 C, vec4 D) {
78 // https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.2020_conversion
79 const vec3 K = vec3(0.2627, 0.6780, 0.0593);
80 const float LUMINANCE_WEIGHT = .6;
81 const mat3 YCBCR_MATRIX =
82 mat3(K * LUMINANCE_WEIGHT, -.5 * K.r / (1.0 - K.b), -.5 * K.g / (1.0 - K.b), .5, .5,
83 -.5 * K.g / (1.0 - K.r), -.5 * K.b / (1.0 - K.r));
84
85 mat4x3 colors = mat4x3(A.rgb, B.rgb, C.rgb, D.rgb) - center_matrix;
86 mat4x3 YCbCr = YCBCR_MATRIX * colors;
87 vec4 color_dist = vec3(1.0) * YCbCr;
88 color_dist *= color_dist;
89 vec4 alpha = vec4(A.a, B.a, C.a, D.a);
90
91 return sqrt((color_dist + abs(center_alpha - alpha)) * alpha * center_alpha);
92}
93
94void main() {
95 vec4 bl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, -1));
96 vec4 bc = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(0, -1));
97 vec4 br = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, -1));
98 vec4 cl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, 0));
99 vec4 cc = textureLod(input_texture, tex_coord, 0.0);
100 vec4 cr = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, 0));
101 vec4 tl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, 1));
102 vec4 tc = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(0, 1));
103 vec4 tr = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, 1));
104
105
106 tex_size = vec2(textureSize(input_texture, 0));
107 inv_tex_size = 1.0 / tex_size;
108 center_matrix = mat4x3(cc.rgb, cc.rgb, cc.rgb, cc.rgb);
109 center_alpha = cc.aaaa;
110
111 vec4 offset_tl = ColorDist(tl, tc, tr, cr);
112 vec4 offset_br = ColorDist(br, bc, bl, cl);
113
114 // Calculate how different cc is from the texels around it
115 float total_dist = dot(offset_tl + offset_br, vec4(1.0));
116
117 // Add together all the distances with direction taken into account
118 vec4 tmp = offset_tl - offset_br;
119 vec2 total_offset = tmp.wy + tmp.zz + vec2(-tmp.x, tmp.x);
120
121 if (total_dist == 0.0) {
122 // Doing bicubic filtering just past the edges where the offset is 0 causes black floaters
123 // and it doesn't really matter which filter is used when the colors aren't changing.
124 frag_color = vec4(cc.rgb, 1.0f);
125 } else {
126 // When the image has thin points, they tend to split apart.
127 // This is because the texels all around are different
128 // and total_offset reaches into clear areas.
129 // This works pretty well to keep the offset in bounds for these cases.
130 float clamp_val = length(total_offset) / total_dist;
131 vec2 final_offset = clamp(total_offset, -clamp_val, clamp_val) * inv_tex_size;
132
133 frag_color = vec4(textureBicubic(tex_coord - final_offset).rgb, 1.0f);
134 }
135}
diff --git a/src/video_core/host_shaders/vulkan_present_bicubic.frag b/src/video_core/host_shaders/vulkan_present_bicubic.frag
new file mode 100644
index 000000000..17772095a
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_present_bicubic.frag
@@ -0,0 +1,56 @@
1// Copyright 2019 yuzu Emulator Project
2// Licensed under GPLv2 or any later version
3// Refer to the license.txt file included.
4
5#version 460 core
6
7layout (location = 0) in vec2 frag_tex_coord;
8
9layout (location = 0) out vec4 color;
10
11layout (binding = 1) uniform sampler2D color_texture;
12
13vec4 cubic(float v) {
14 vec4 n = vec4(1.0, 2.0, 3.0, 4.0) - v;
15 vec4 s = n * n * n;
16 float x = s.x;
17 float y = s.y - 4.0 * s.x;
18 float z = s.z - 4.0 * s.y + 6.0 * s.x;
19 float w = 6.0 - x - y - z;
20 return vec4(x, y, z, w) * (1.0 / 6.0);
21}
22
23vec4 textureBicubic( sampler2D textureSampler, vec2 texCoords ) {
24
25 vec2 texSize = textureSize(textureSampler, 0);
26 vec2 invTexSize = 1.0 / texSize;
27
28 texCoords = texCoords * texSize - 0.5;
29
30 vec2 fxy = fract(texCoords);
31 texCoords -= fxy;
32
33 vec4 xcubic = cubic(fxy.x);
34 vec4 ycubic = cubic(fxy.y);
35
36 vec4 c = texCoords.xxyy + vec2(-0.5, +1.5).xyxy;
37
38 vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
39 vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
40
41 offset *= invTexSize.xxyy;
42
43 vec4 sample0 = texture(textureSampler, offset.xz);
44 vec4 sample1 = texture(textureSampler, offset.yz);
45 vec4 sample2 = texture(textureSampler, offset.xw);
46 vec4 sample3 = texture(textureSampler, offset.yw);
47
48 float sx = s.x / (s.x + s.y);
49 float sy = s.z / (s.z + s.w);
50
51 return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
52}
53
54void main() {
55 color = vec4(textureBicubic(color_texture, frag_tex_coord).rgb, 1.0f);
56}
diff --git a/src/video_core/host_shaders/vulkan_present_scaleforce.frag b/src/video_core/host_shaders/vulkan_present_scaleforce.frag
new file mode 100644
index 000000000..801c8eae9
--- /dev/null
+++ b/src/video_core/host_shaders/vulkan_present_scaleforce.frag
@@ -0,0 +1,137 @@
1#version 320 es
2
3// from https://github.com/BreadFish64/ScaleFish/tree/master/scale_force
4
5// MIT License
6//
7// Copyright (c) 2020 BreadFish64
8//
9// Permission is hereby granted, free of charge, to any person obtaining a copy
10// of this software and associated documentation files (the "Software"), to deal
11// in the Software without restriction, including without limitation the rights
12// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
13// copies of the Software, and to permit persons to whom the Software is
14// furnished to do so, subject to the following conditions:
15//
16// The above copyright notice and this permission notice shall be included in all
17// copies or substantial portions of the Software.
18//
19// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
20// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
21// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
22// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
23// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
24// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
25// SOFTWARE.
26
27precision mediump float;
28
29layout (location = 0) in vec2 tex_coord;
30
31layout (location = 0) out vec4 frag_color;
32
33layout (binding = 1) uniform sampler2D input_texture;
34
35vec2 tex_size;
36vec2 inv_tex_size;
37
38vec4 cubic(float v) {
39 vec3 n = vec3(1.0, 2.0, 3.0) - v;
40 vec3 s = n * n * n;
41 float x = s.x;
42 float y = s.y - 4.0 * s.x;
43 float z = s.z - 4.0 * s.y + 6.0 * s.x;
44 float w = 6.0 - x - y - z;
45 return vec4(x, y, z, w) / 6.0;
46}
47
48// Bicubic interpolation
49vec4 textureBicubic(vec2 tex_coords) {
50 tex_coords = tex_coords * tex_size - 0.5;
51
52 vec2 fxy = modf(tex_coords, tex_coords);
53
54 vec4 xcubic = cubic(fxy.x);
55 vec4 ycubic = cubic(fxy.y);
56
57 vec4 c = tex_coords.xxyy + vec2(-0.5, +1.5).xyxy;
58
59 vec4 s = vec4(xcubic.xz + xcubic.yw, ycubic.xz + ycubic.yw);
60 vec4 offset = c + vec4(xcubic.yw, ycubic.yw) / s;
61
62 offset *= inv_tex_size.xxyy;
63
64 vec4 sample0 = textureLod(input_texture, offset.xz, 0.0);
65 vec4 sample1 = textureLod(input_texture, offset.yz, 0.0);
66 vec4 sample2 = textureLod(input_texture, offset.xw, 0.0);
67 vec4 sample3 = textureLod(input_texture, offset.yw, 0.0);
68
69 float sx = s.x / (s.x + s.y);
70 float sy = s.z / (s.z + s.w);
71
72 return mix(mix(sample3, sample2, sx), mix(sample1, sample0, sx), sy);
73}
74
75mat4x3 center_matrix;
76vec4 center_alpha;
77
78// Finds the distance between four colors and cc in YCbCr space
79vec4 ColorDist(vec4 A, vec4 B, vec4 C, vec4 D) {
80 // https://en.wikipedia.org/wiki/YCbCr#ITU-R_BT.2020_conversion
81 const vec3 K = vec3(0.2627, 0.6780, 0.0593);
82 const float LUMINANCE_WEIGHT = .6;
83 const mat3 YCBCR_MATRIX =
84 mat3(K * LUMINANCE_WEIGHT, -.5 * K.r / (1.0 - K.b), -.5 * K.g / (1.0 - K.b), .5, .5,
85 -.5 * K.g / (1.0 - K.r), -.5 * K.b / (1.0 - K.r));
86
87 mat4x3 colors = mat4x3(A.rgb, B.rgb, C.rgb, D.rgb) - center_matrix;
88 mat4x3 YCbCr = YCBCR_MATRIX * colors;
89 vec4 color_dist = vec3(1.0) * YCbCr;
90 color_dist *= color_dist;
91 vec4 alpha = vec4(A.a, B.a, C.a, D.a);
92
93 return sqrt((color_dist + abs(center_alpha - alpha)) * alpha * center_alpha);
94}
95
96void main() {
97 vec4 bl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, -1));
98 vec4 bc = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(0, -1));
99 vec4 br = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, -1));
100 vec4 cl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, 0));
101 vec4 cc = textureLod(input_texture, tex_coord, 0.0);
102 vec4 cr = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, 0));
103 vec4 tl = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(-1, 1));
104 vec4 tc = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(0, 1));
105 vec4 tr = textureLodOffset(input_texture, tex_coord, 0.0, ivec2(1, 1));
106
107
108 tex_size = vec2(textureSize(input_texture, 0));
109 inv_tex_size = 1.0 / tex_size;
110 center_matrix = mat4x3(cc.rgb, cc.rgb, cc.rgb, cc.rgb);
111 center_alpha = cc.aaaa;
112
113 vec4 offset_tl = ColorDist(tl, tc, tr, cr);
114 vec4 offset_br = ColorDist(br, bc, bl, cl);
115
116 // Calculate how different cc is from the texels around it
117 float total_dist = dot(offset_tl + offset_br, vec4(1.0));
118
119 // Add together all the distances with direction taken into account
120 vec4 tmp = offset_tl - offset_br;
121 vec2 total_offset = tmp.wy + tmp.zz + vec2(-tmp.x, tmp.x);
122
123 if (total_dist == 0.0) {
124 // Doing bicubic filtering just past the edges where the offset is 0 causes black floaters
125 // and it doesn't really matter which filter is used when the colors aren't changing.
126 frag_color = vec4(cc.rgb, 1.0f);
127 } else {
128 // When the image has thin points, they tend to split apart.
129 // This is because the texels all around are different
130 // and total_offset reaches into clear areas.
131 // This works pretty well to keep the offset in bounds for these cases.
132 float clamp_val = length(total_offset) / total_dist;
133 vec2 final_offset = clamp(total_offset, -clamp_val, clamp_val) * inv_tex_size;
134
135 frag_color = vec4(textureBicubic(tex_coord - final_offset).rgb, 1.0f);
136 }
137}
diff --git a/src/video_core/renderer_opengl/renderer_opengl.cpp b/src/video_core/renderer_opengl/renderer_opengl.cpp
index 0f7b69c6d..71a5e3adf 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.cpp
+++ b/src/video_core/renderer_opengl/renderer_opengl.cpp
@@ -21,7 +21,9 @@
21#include "core/memory.h" 21#include "core/memory.h"
22#include "core/perf_stats.h" 22#include "core/perf_stats.h"
23#include "core/telemetry_session.h" 23#include "core/telemetry_session.h"
24#include "video_core/host_shaders/opengl_present_bicubic_frag.h"
24#include "video_core/host_shaders/opengl_present_frag.h" 25#include "video_core/host_shaders/opengl_present_frag.h"
26#include "video_core/host_shaders/opengl_present_scaleforce_frag.h"
25#include "video_core/host_shaders/opengl_present_vert.h" 27#include "video_core/host_shaders/opengl_present_vert.h"
26#include "video_core/renderer_opengl/gl_rasterizer.h" 28#include "video_core/renderer_opengl/gl_rasterizer.h"
27#include "video_core/renderer_opengl/gl_shader_manager.h" 29#include "video_core/renderer_opengl/gl_shader_manager.h"
@@ -252,7 +254,11 @@ void RendererOpenGL::LoadColorToActiveGLTexture(u8 color_r, u8 color_g, u8 color
252void RendererOpenGL::InitOpenGLObjects() { 254void RendererOpenGL::InitOpenGLObjects() {
253 // Create shader programs 255 // Create shader programs
254 present_vertex = CreateProgram(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER); 256 present_vertex = CreateProgram(HostShaders::OPENGL_PRESENT_VERT, GL_VERTEX_SHADER);
255 present_fragment = CreateProgram(HostShaders::OPENGL_PRESENT_FRAG, GL_FRAGMENT_SHADER); 257 present_bilinear_fragment = CreateProgram(HostShaders::OPENGL_PRESENT_FRAG, GL_FRAGMENT_SHADER);
258 present_bicubic_fragment =
259 CreateProgram(HostShaders::OPENGL_PRESENT_BICUBIC_FRAG, GL_FRAGMENT_SHADER);
260 present_scaleforce_fragment =
261 CreateProgram(HostShaders::OPENGL_PRESENT_SCALEFORCE_FRAG, GL_FRAGMENT_SHADER);
256 262
257 // Generate presentation sampler 263 // Generate presentation sampler
258 present_sampler.Create(); 264 present_sampler.Create();
@@ -337,7 +343,24 @@ void RendererOpenGL::DrawScreen(const Layout::FramebufferLayout& layout) {
337 // Set projection matrix 343 // Set projection matrix
338 const std::array ortho_matrix = 344 const std::array ortho_matrix =
339 MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height)); 345 MakeOrthographicMatrix(static_cast<float>(layout.width), static_cast<float>(layout.height));
340 program_manager.BindPresentPrograms(present_vertex.handle, present_fragment.handle); 346
347 GLuint fragment_handle;
348 const auto filter = Settings::values.scaling_filter.GetValue();
349 switch (filter) {
350 case Settings::ScalingFilter::Bilinear:
351 fragment_handle = present_bilinear_fragment.handle;
352 break;
353 case Settings::ScalingFilter::Bicubic:
354 fragment_handle = present_bicubic_fragment.handle;
355 break;
356 case Settings::ScalingFilter::ScaleForce:
357 fragment_handle = present_scaleforce_fragment.handle;
358 break;
359 default:
360 fragment_handle = present_bilinear_fragment.handle;
361 break;
362 }
363 program_manager.BindPresentPrograms(present_vertex.handle, fragment_handle);
341 glProgramUniformMatrix3x2fv(present_vertex.handle, ModelViewMatrixLocation, 1, GL_FALSE, 364 glProgramUniformMatrix3x2fv(present_vertex.handle, ModelViewMatrixLocation, 1, GL_FALSE,
342 ortho_matrix.data()); 365 ortho_matrix.data());
343 366
diff --git a/src/video_core/renderer_opengl/renderer_opengl.h b/src/video_core/renderer_opengl/renderer_opengl.h
index d455f572f..bf3d3502c 100644
--- a/src/video_core/renderer_opengl/renderer_opengl.h
+++ b/src/video_core/renderer_opengl/renderer_opengl.h
@@ -111,7 +111,9 @@ private:
111 OGLSampler present_sampler; 111 OGLSampler present_sampler;
112 OGLBuffer vertex_buffer; 112 OGLBuffer vertex_buffer;
113 OGLProgram present_vertex; 113 OGLProgram present_vertex;
114 OGLProgram present_fragment; 114 OGLProgram present_bilinear_fragment;
115 OGLProgram present_bicubic_fragment;
116 OGLProgram present_scaleforce_fragment;
115 OGLFramebuffer screenshot_framebuffer; 117 OGLFramebuffer screenshot_framebuffer;
116 118
117 // GPU address of the vertex buffer 119 // GPU address of the vertex buffer
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.cpp b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
index 7051e6559..19d91ecfc 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.cpp
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.cpp
@@ -12,11 +12,14 @@
12#include "common/assert.h" 12#include "common/assert.h"
13#include "common/common_types.h" 13#include "common/common_types.h"
14#include "common/math_util.h" 14#include "common/math_util.h"
15#include "common/settings.h"
15#include "core/core.h" 16#include "core/core.h"
16#include "core/frontend/emu_window.h" 17#include "core/frontend/emu_window.h"
17#include "core/memory.h" 18#include "core/memory.h"
18#include "video_core/gpu.h" 19#include "video_core/gpu.h"
20#include "video_core/host_shaders/vulkan_present_bicubic_frag_spv.h"
19#include "video_core/host_shaders/vulkan_present_frag_spv.h" 21#include "video_core/host_shaders/vulkan_present_frag_spv.h"
22#include "video_core/host_shaders/vulkan_present_scaleforce_frag_spv.h"
20#include "video_core/host_shaders/vulkan_present_vert_spv.h" 23#include "video_core/host_shaders/vulkan_present_vert_spv.h"
21#include "video_core/renderer_vulkan/renderer_vulkan.h" 24#include "video_core/renderer_vulkan/renderer_vulkan.h"
22#include "video_core/renderer_vulkan/vk_blit_screen.h" 25#include "video_core/renderer_vulkan/vk_blit_screen.h"
@@ -258,8 +261,22 @@ VkSemaphore VKBlitScreen::Draw(const Tegra::FramebufferConfig& framebuffer,
258 .offset = {0, 0}, 261 .offset = {0, 0},
259 .extent = size, 262 .extent = size,
260 }; 263 };
264 const auto filter = Settings::values.scaling_filter.GetValue();
261 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE); 265 cmdbuf.BeginRenderPass(renderpass_bi, VK_SUBPASS_CONTENTS_INLINE);
262 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *pipeline); 266 switch (filter) {
267 case Settings::ScalingFilter::Bilinear:
268 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bilinear_pipeline);
269 break;
270 case Settings::ScalingFilter::Bicubic:
271 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bicubic_pipeline);
272 break;
273 case Settings::ScalingFilter::ScaleForce:
274 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *scaleforce_pipeline);
275 break;
276 default:
277 cmdbuf.BindPipeline(VK_PIPELINE_BIND_POINT_GRAPHICS, *bilinear_pipeline);
278 break;
279 }
263 cmdbuf.SetViewport(0, viewport); 280 cmdbuf.SetViewport(0, viewport);
264 cmdbuf.SetScissor(0, scissor); 281 cmdbuf.SetScissor(0, scissor);
265 282
@@ -324,7 +341,9 @@ void VKBlitScreen::RefreshResources(const Tegra::FramebufferConfig& framebuffer)
324 341
325void VKBlitScreen::CreateShaders() { 342void VKBlitScreen::CreateShaders() {
326 vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV); 343 vertex_shader = BuildShader(device, VULKAN_PRESENT_VERT_SPV);
327 fragment_shader = BuildShader(device, VULKAN_PRESENT_FRAG_SPV); 344 bilinear_fragment_shader = BuildShader(device, VULKAN_PRESENT_FRAG_SPV);
345 bicubic_fragment_shader = BuildShader(device, VULKAN_PRESENT_BICUBIC_FRAG_SPV);
346 scaleforce_fragment_shader = BuildShader(device, VULKAN_PRESENT_SCALEFORCE_FRAG_SPV);
328} 347}
329 348
330void VKBlitScreen::CreateSemaphores() { 349void VKBlitScreen::CreateSemaphores() {
@@ -468,7 +487,7 @@ void VKBlitScreen::CreatePipelineLayout() {
468} 487}
469 488
470void VKBlitScreen::CreateGraphicsPipeline() { 489void VKBlitScreen::CreateGraphicsPipeline() {
471 const std::array<VkPipelineShaderStageCreateInfo, 2> shader_stages{{ 490 const std::array<VkPipelineShaderStageCreateInfo, 2> bilinear_shader_stages{{
472 { 491 {
473 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, 492 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
474 .pNext = nullptr, 493 .pNext = nullptr,
@@ -483,7 +502,49 @@ void VKBlitScreen::CreateGraphicsPipeline() {
483 .pNext = nullptr, 502 .pNext = nullptr,
484 .flags = 0, 503 .flags = 0,
485 .stage = VK_SHADER_STAGE_FRAGMENT_BIT, 504 .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
486 .module = *fragment_shader, 505 .module = *bilinear_fragment_shader,
506 .pName = "main",
507 .pSpecializationInfo = nullptr,
508 },
509 }};
510
511 const std::array<VkPipelineShaderStageCreateInfo, 2> bicubic_shader_stages{{
512 {
513 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
514 .pNext = nullptr,
515 .flags = 0,
516 .stage = VK_SHADER_STAGE_VERTEX_BIT,
517 .module = *vertex_shader,
518 .pName = "main",
519 .pSpecializationInfo = nullptr,
520 },
521 {
522 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
523 .pNext = nullptr,
524 .flags = 0,
525 .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
526 .module = *bicubic_fragment_shader,
527 .pName = "main",
528 .pSpecializationInfo = nullptr,
529 },
530 }};
531
532 const std::array<VkPipelineShaderStageCreateInfo, 2> scaleforce_shader_stages{{
533 {
534 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
535 .pNext = nullptr,
536 .flags = 0,
537 .stage = VK_SHADER_STAGE_VERTEX_BIT,
538 .module = *vertex_shader,
539 .pName = "main",
540 .pSpecializationInfo = nullptr,
541 },
542 {
543 .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
544 .pNext = nullptr,
545 .flags = 0,
546 .stage = VK_SHADER_STAGE_FRAGMENT_BIT,
547 .module = *scaleforce_fragment_shader,
487 .pName = "main", 548 .pName = "main",
488 .pSpecializationInfo = nullptr, 549 .pSpecializationInfo = nullptr,
489 }, 550 },
@@ -583,12 +644,56 @@ void VKBlitScreen::CreateGraphicsPipeline() {
583 .pDynamicStates = dynamic_states.data(), 644 .pDynamicStates = dynamic_states.data(),
584 }; 645 };
585 646
586 const VkGraphicsPipelineCreateInfo pipeline_ci{ 647 const VkGraphicsPipelineCreateInfo bilinear_pipeline_ci{
648 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
649 .pNext = nullptr,
650 .flags = 0,
651 .stageCount = static_cast<u32>(bilinear_shader_stages.size()),
652 .pStages = bilinear_shader_stages.data(),
653 .pVertexInputState = &vertex_input_ci,
654 .pInputAssemblyState = &input_assembly_ci,
655 .pTessellationState = nullptr,
656 .pViewportState = &viewport_state_ci,
657 .pRasterizationState = &rasterization_ci,
658 .pMultisampleState = &multisampling_ci,
659 .pDepthStencilState = nullptr,
660 .pColorBlendState = &color_blend_ci,
661 .pDynamicState = &dynamic_state_ci,
662 .layout = *pipeline_layout,
663 .renderPass = *renderpass,
664 .subpass = 0,
665 .basePipelineHandle = 0,
666 .basePipelineIndex = 0,
667 };
668
669 const VkGraphicsPipelineCreateInfo bicubic_pipeline_ci{
670 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
671 .pNext = nullptr,
672 .flags = 0,
673 .stageCount = static_cast<u32>(bicubic_shader_stages.size()),
674 .pStages = bicubic_shader_stages.data(),
675 .pVertexInputState = &vertex_input_ci,
676 .pInputAssemblyState = &input_assembly_ci,
677 .pTessellationState = nullptr,
678 .pViewportState = &viewport_state_ci,
679 .pRasterizationState = &rasterization_ci,
680 .pMultisampleState = &multisampling_ci,
681 .pDepthStencilState = nullptr,
682 .pColorBlendState = &color_blend_ci,
683 .pDynamicState = &dynamic_state_ci,
684 .layout = *pipeline_layout,
685 .renderPass = *renderpass,
686 .subpass = 0,
687 .basePipelineHandle = 0,
688 .basePipelineIndex = 0,
689 };
690
691 const VkGraphicsPipelineCreateInfo scaleforce_pipeline_ci{
587 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, 692 .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
588 .pNext = nullptr, 693 .pNext = nullptr,
589 .flags = 0, 694 .flags = 0,
590 .stageCount = static_cast<u32>(shader_stages.size()), 695 .stageCount = static_cast<u32>(scaleforce_shader_stages.size()),
591 .pStages = shader_stages.data(), 696 .pStages = scaleforce_shader_stages.data(),
592 .pVertexInputState = &vertex_input_ci, 697 .pVertexInputState = &vertex_input_ci,
593 .pInputAssemblyState = &input_assembly_ci, 698 .pInputAssemblyState = &input_assembly_ci,
594 .pTessellationState = nullptr, 699 .pTessellationState = nullptr,
@@ -605,7 +710,9 @@ void VKBlitScreen::CreateGraphicsPipeline() {
605 .basePipelineIndex = 0, 710 .basePipelineIndex = 0,
606 }; 711 };
607 712
608 pipeline = device.GetLogical().CreateGraphicsPipeline(pipeline_ci); 713 bilinear_pipeline = device.GetLogical().CreateGraphicsPipeline(bilinear_pipeline_ci);
714 bicubic_pipeline = device.GetLogical().CreateGraphicsPipeline(bicubic_pipeline_ci);
715 scaleforce_pipeline = device.GetLogical().CreateGraphicsPipeline(scaleforce_pipeline_ci);
609} 716}
610 717
611void VKBlitScreen::CreateSampler() { 718void VKBlitScreen::CreateSampler() {
diff --git a/src/video_core/renderer_vulkan/vk_blit_screen.h b/src/video_core/renderer_vulkan/vk_blit_screen.h
index 430bcfbca..d3a16f0ba 100644
--- a/src/video_core/renderer_vulkan/vk_blit_screen.h
+++ b/src/video_core/renderer_vulkan/vk_blit_screen.h
@@ -107,11 +107,15 @@ private:
107 const VKScreenInfo& screen_info; 107 const VKScreenInfo& screen_info;
108 108
109 vk::ShaderModule vertex_shader; 109 vk::ShaderModule vertex_shader;
110 vk::ShaderModule fragment_shader; 110 vk::ShaderModule bilinear_fragment_shader;
111 vk::ShaderModule bicubic_fragment_shader;
112 vk::ShaderModule scaleforce_fragment_shader;
111 vk::DescriptorPool descriptor_pool; 113 vk::DescriptorPool descriptor_pool;
112 vk::DescriptorSetLayout descriptor_set_layout; 114 vk::DescriptorSetLayout descriptor_set_layout;
113 vk::PipelineLayout pipeline_layout; 115 vk::PipelineLayout pipeline_layout;
114 vk::Pipeline pipeline; 116 vk::Pipeline bilinear_pipeline;
117 vk::Pipeline bicubic_pipeline;
118 vk::Pipeline scaleforce_pipeline;
115 vk::RenderPass renderpass; 119 vk::RenderPass renderpass;
116 std::vector<vk::Framebuffer> framebuffers; 120 std::vector<vk::Framebuffer> framebuffers;
117 vk::DescriptorSets descriptor_sets; 121 vk::DescriptorSets descriptor_sets;
diff --git a/src/yuzu/configuration/config.cpp b/src/yuzu/configuration/config.cpp
index 7ed833203..3803bf501 100644
--- a/src/yuzu/configuration/config.cpp
+++ b/src/yuzu/configuration/config.cpp
@@ -825,6 +825,7 @@ void Config::ReadRendererValues() {
825 ReadGlobalSetting(Settings::values.fullscreen_mode); 825 ReadGlobalSetting(Settings::values.fullscreen_mode);
826 ReadGlobalSetting(Settings::values.aspect_ratio); 826 ReadGlobalSetting(Settings::values.aspect_ratio);
827 ReadGlobalSetting(Settings::values.resolution_setup); 827 ReadGlobalSetting(Settings::values.resolution_setup);
828 ReadGlobalSetting(Settings::values.scaling_filter);
828 ReadGlobalSetting(Settings::values.max_anisotropy); 829 ReadGlobalSetting(Settings::values.max_anisotropy);
829 ReadGlobalSetting(Settings::values.use_speed_limit); 830 ReadGlobalSetting(Settings::values.use_speed_limit);
830 ReadGlobalSetting(Settings::values.speed_limit); 831 ReadGlobalSetting(Settings::values.speed_limit);
@@ -1371,6 +1372,10 @@ void Config::SaveRendererValues() {
1371 static_cast<u32>(Settings::values.resolution_setup.GetValue(global)), 1372 static_cast<u32>(Settings::values.resolution_setup.GetValue(global)),
1372 static_cast<u32>(Settings::values.resolution_setup.GetDefault()), 1373 static_cast<u32>(Settings::values.resolution_setup.GetDefault()),
1373 Settings::values.resolution_setup.UsingGlobal()); 1374 Settings::values.resolution_setup.UsingGlobal());
1375 WriteSetting(QString::fromStdString(Settings::values.scaling_filter.GetLabel()),
1376 static_cast<u32>(Settings::values.scaling_filter.GetValue(global)),
1377 static_cast<u32>(Settings::values.scaling_filter.GetDefault()),
1378 Settings::values.scaling_filter.UsingGlobal());
1374 WriteGlobalSetting(Settings::values.max_anisotropy); 1379 WriteGlobalSetting(Settings::values.max_anisotropy);
1375 WriteGlobalSetting(Settings::values.use_speed_limit); 1380 WriteGlobalSetting(Settings::values.use_speed_limit);
1376 WriteGlobalSetting(Settings::values.speed_limit); 1381 WriteGlobalSetting(Settings::values.speed_limit);
diff --git a/src/yuzu/configuration/config.h b/src/yuzu/configuration/config.h
index fbb91d312..97dc1bb47 100644
--- a/src/yuzu/configuration/config.h
+++ b/src/yuzu/configuration/config.h
@@ -190,5 +190,6 @@ Q_DECLARE_METATYPE(Settings::GPUAccuracy);
190Q_DECLARE_METATYPE(Settings::FullscreenMode); 190Q_DECLARE_METATYPE(Settings::FullscreenMode);
191Q_DECLARE_METATYPE(Settings::NvdecEmulation); 191Q_DECLARE_METATYPE(Settings::NvdecEmulation);
192Q_DECLARE_METATYPE(Settings::ResolutionSetup); 192Q_DECLARE_METATYPE(Settings::ResolutionSetup);
193Q_DECLARE_METATYPE(Settings::ScalingFilter);
193Q_DECLARE_METATYPE(Settings::RendererBackend); 194Q_DECLARE_METATYPE(Settings::RendererBackend);
194Q_DECLARE_METATYPE(Settings::ShaderBackend); 195Q_DECLARE_METATYPE(Settings::ShaderBackend);
diff --git a/src/yuzu/configuration/configure_graphics.cpp b/src/yuzu/configuration/configure_graphics.cpp
index 4f08ae3e0..e01efaeda 100644
--- a/src/yuzu/configuration/configure_graphics.cpp
+++ b/src/yuzu/configuration/configure_graphics.cpp
@@ -89,6 +89,7 @@ void ConfigureGraphics::SetConfiguration() {
89 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock); 89 ui->use_asynchronous_gpu_emulation->setEnabled(runtime_lock);
90 ui->use_disk_shader_cache->setEnabled(runtime_lock); 90 ui->use_disk_shader_cache->setEnabled(runtime_lock);
91 ui->nvdec_emulation_widget->setEnabled(runtime_lock); 91 ui->nvdec_emulation_widget->setEnabled(runtime_lock);
92 ui->resolution_combobox->setEnabled(runtime_lock);
92 ui->accelerate_astc->setEnabled(runtime_lock); 93 ui->accelerate_astc->setEnabled(runtime_lock);
93 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue()); 94 ui->use_disk_shader_cache->setChecked(Settings::values.use_disk_shader_cache.GetValue());
94 ui->use_asynchronous_gpu_emulation->setChecked( 95 ui->use_asynchronous_gpu_emulation->setChecked(
@@ -104,6 +105,8 @@ void ConfigureGraphics::SetConfiguration() {
104 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue()); 105 ui->aspect_ratio_combobox->setCurrentIndex(Settings::values.aspect_ratio.GetValue());
105 ui->resolution_combobox->setCurrentIndex( 106 ui->resolution_combobox->setCurrentIndex(
106 static_cast<int>(Settings::values.resolution_setup.GetValue())); 107 static_cast<int>(Settings::values.resolution_setup.GetValue()));
108 ui->scaling_filter_combobox->setCurrentIndex(
109 static_cast<int>(Settings::values.scaling_filter.GetValue()));
107 } else { 110 } else {
108 ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend); 111 ConfigurationShared::SetPerGameSetting(ui->api, &Settings::values.renderer_backend);
109 ConfigurationShared::SetHighlight(ui->api_widget, 112 ConfigurationShared::SetHighlight(ui->api_widget,
@@ -129,6 +132,11 @@ void ConfigureGraphics::SetConfiguration() {
129 ConfigurationShared::SetHighlight(ui->resolution_label, 132 ConfigurationShared::SetHighlight(ui->resolution_label,
130 !Settings::values.resolution_setup.UsingGlobal()); 133 !Settings::values.resolution_setup.UsingGlobal());
131 134
135 ConfigurationShared::SetPerGameSetting(ui->scaling_filter_combobox,
136 &Settings::values.scaling_filter);
137 ConfigurationShared::SetHighlight(ui->scaling_filter_label,
138 !Settings::values.scaling_filter.UsingGlobal());
139
132 ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1); 140 ui->bg_combobox->setCurrentIndex(Settings::values.bg_red.UsingGlobal() ? 0 : 1);
133 ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal()); 141 ui->bg_button->setEnabled(!Settings::values.bg_red.UsingGlobal());
134 ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal()); 142 ConfigurationShared::SetHighlight(ui->bg_layout, !Settings::values.bg_red.UsingGlobal());
@@ -144,6 +152,10 @@ void ConfigureGraphics::ApplyConfiguration() {
144 ui->resolution_combobox->currentIndex() - 152 ui->resolution_combobox->currentIndex() -
145 ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET)); 153 ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
146 154
155 const auto scaling_filter = static_cast<Settings::ScalingFilter>(
156 ui->scaling_filter_combobox->currentIndex() -
157 ((Settings::IsConfiguringGlobal()) ? 0 : ConfigurationShared::USE_GLOBAL_OFFSET));
158
147 ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode, 159 ConfigurationShared::ApplyPerGameSetting(&Settings::values.fullscreen_mode,
148 ui->fullscreen_mode_combobox); 160 ui->fullscreen_mode_combobox);
149 ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio, 161 ConfigurationShared::ApplyPerGameSetting(&Settings::values.aspect_ratio,
@@ -178,6 +190,9 @@ void ConfigureGraphics::ApplyConfiguration() {
178 if (Settings::values.resolution_setup.UsingGlobal()) { 190 if (Settings::values.resolution_setup.UsingGlobal()) {
179 Settings::values.resolution_setup.SetValue(resolution_setup); 191 Settings::values.resolution_setup.SetValue(resolution_setup);
180 } 192 }
193 if (Settings::values.scaling_filter.UsingGlobal()) {
194 Settings::values.scaling_filter.SetValue(scaling_filter);
195 }
181 } else { 196 } else {
182 if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { 197 if (ui->resolution_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
183 Settings::values.resolution_setup.SetGlobal(true); 198 Settings::values.resolution_setup.SetGlobal(true);
@@ -185,6 +200,12 @@ void ConfigureGraphics::ApplyConfiguration() {
185 Settings::values.resolution_setup.SetGlobal(false); 200 Settings::values.resolution_setup.SetGlobal(false);
186 Settings::values.resolution_setup.SetValue(resolution_setup); 201 Settings::values.resolution_setup.SetValue(resolution_setup);
187 } 202 }
203 if (ui->scaling_filter_combobox->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
204 Settings::values.scaling_filter.SetGlobal(true);
205 } else {
206 Settings::values.scaling_filter.SetGlobal(false);
207 Settings::values.scaling_filter.SetValue(scaling_filter);
208 }
188 if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) { 209 if (ui->api->currentIndex() == ConfigurationShared::USE_GLOBAL_INDEX) {
189 Settings::values.renderer_backend.SetGlobal(true); 210 Settings::values.renderer_backend.SetGlobal(true);
190 Settings::values.shader_backend.SetGlobal(true); 211 Settings::values.shader_backend.SetGlobal(true);
@@ -333,6 +354,7 @@ void ConfigureGraphics::SetupPerGameUI() {
333 ui->fullscreen_mode_combobox->setEnabled(Settings::values.fullscreen_mode.UsingGlobal()); 354 ui->fullscreen_mode_combobox->setEnabled(Settings::values.fullscreen_mode.UsingGlobal());
334 ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal()); 355 ui->aspect_ratio_combobox->setEnabled(Settings::values.aspect_ratio.UsingGlobal());
335 ui->resolution_combobox->setEnabled(Settings::values.resolution_setup.UsingGlobal()); 356 ui->resolution_combobox->setEnabled(Settings::values.resolution_setup.UsingGlobal());
357 ui->scaling_filter_combobox->setEnabled(Settings::values.scaling_filter.UsingGlobal());
336 ui->use_asynchronous_gpu_emulation->setEnabled( 358 ui->use_asynchronous_gpu_emulation->setEnabled(
337 Settings::values.use_asynchronous_gpu_emulation.UsingGlobal()); 359 Settings::values.use_asynchronous_gpu_emulation.UsingGlobal());
338 ui->nvdec_emulation->setEnabled(Settings::values.nvdec_emulation.UsingGlobal()); 360 ui->nvdec_emulation->setEnabled(Settings::values.nvdec_emulation.UsingGlobal());
@@ -364,6 +386,9 @@ void ConfigureGraphics::SetupPerGameUI() {
364 ConfigurationShared::SetColoredComboBox( 386 ConfigurationShared::SetColoredComboBox(
365 ui->resolution_combobox, ui->resolution_label, 387 ui->resolution_combobox, ui->resolution_label,
366 static_cast<int>(Settings::values.resolution_setup.GetValue(true))); 388 static_cast<int>(Settings::values.resolution_setup.GetValue(true)));
389 ConfigurationShared::SetColoredComboBox(
390 ui->scaling_filter_combobox, ui->scaling_filter_label,
391 static_cast<int>(Settings::values.scaling_filter.GetValue(true)));
367 ConfigurationShared::InsertGlobalItem( 392 ConfigurationShared::InsertGlobalItem(
368 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true))); 393 ui->api, static_cast<int>(Settings::values.renderer_backend.GetValue(true)));
369 ConfigurationShared::InsertGlobalItem( 394 ConfigurationShared::InsertGlobalItem(
diff --git a/src/yuzu/configuration/configure_graphics.ui b/src/yuzu/configuration/configure_graphics.ui
index 1b6ac3cbb..d5e0d4e89 100644
--- a/src/yuzu/configuration/configure_graphics.ui
+++ b/src/yuzu/configuration/configure_graphics.ui
@@ -340,32 +340,66 @@
340 </item> 340 </item>
341 <item> 341 <item>
342 <property name="text"> 342 <property name="text">
343 <string>0.75X (540p/810p)</string> 343 <string>1X (720p/1080p)</string>
344 </property> 344 </property>
345 </item> 345 </item>
346 <item> 346 <item>
347 <property name="text"> 347 <property name="text">
348 <string>1X (720p/1080p)</string> 348 <string>2X (1440p/2160[4K]p)</string>
349 </property> 349 </property>
350 </item> 350 </item>
351 <item> 351 <item>
352 <property name="text"> 352 <property name="text">
353 <string>1.5X (1080p/1620p)</string> 353 <string>3X (2160p[4K]/3240p[6K])</string>
354 </property> 354 </property>
355 </item> 355 </item>
356 <item> 356 <item>
357 <property name="text"> 357 <property name="text">
358 <string>2X (1440p/2160[4K]p)</string> 358 <string>4X (2880p/4320p[8K])</string>
359 </property> 359 </property>
360 </item> 360 </item>
361 </widget>
362 </item>
363 </layout>
364 </widget>
365 </item>
366 <item>
367 <widget class="QWidget" name="scaling_filter_layout" native="true">
368 <layout class="QHBoxLayout" name="horizontalLayout_5">
369 <property name="leftMargin">
370 <number>0</number>
371 </property>
372 <property name="topMargin">
373 <number>0</number>
374 </property>
375 <property name="rightMargin">
376 <number>0</number>
377 </property>
378 <property name="bottomMargin">
379 <number>0</number>
380 </property>
381 <item>
382 <widget class="QLabel" name="scaling_filter_label">
383 <property name="text">
384 <string>Window Adapting Filter:</string>
385 </property>
386 </widget>
387 </item>
388 <item>
389 <widget class="QComboBox" name="scaling_filter_combobox">
361 <item> 390 <item>
362 <property name="text"> 391 <property name="text">
363 <string>3X (2160p[4K]/3240p[6K])</string> 392 <string>Bilinear</string>
364 </property> 393 </property>
365 </item> 394 </item>
366 <item> 395 <item>
367 <property name="text"> 396 <property name="text">
368 <string>4X (2880p/4320p[8K])</string> 397 <string>Bicubic</string>
398 </property>
399 </item>
400 <item>
401 <property name="text">
402 <string>ScaleForce</string>
369 </property> 403 </property>
370 </item> 404 </item>
371 </widget> 405 </widget>