diff --git a/bin/resources/shaders/dx11/present.fx b/bin/resources/shaders/dx11/present.fx
index ca682a9f20..986e380a7e 100644
--- a/bin/resources/shaders/dx11/present.fx
+++ b/bin/resources/shaders/dx11/present.fx
@@ -395,4 +395,46 @@ PS_OUTPUT ps_filter_lottes(PS_INPUT input)
return output;
}
+PS_OUTPUT ps_4x_rgss(PS_INPUT input)
+{
+ PS_OUTPUT output;
+
+ float2 dxy = float2(ddx(input.t.x), ddy(input.t.y));
+ float3 color = 0;
+
+ float s = 1.0/8.0;
+ float l = 3.0/8.0;
+
+ color += sample_c(input.t + float2( s, l) * dxy).rgb;
+ color += sample_c(input.t + float2( l,-s) * dxy).rgb;
+ color += sample_c(input.t + float2(-s,-l) * dxy).rgb;
+ color += sample_c(input.t + float2(-l, s) * dxy).rgb;
+
+ output.c = float4(color * 0.25,1);
+ return output;
+}
+
+PS_OUTPUT ps_automagical_supersampling(PS_INPUT input)
+{
+ PS_OUTPUT output;
+
+ float2 ratio = (u_source_size / u_target_size) * 0.5;
+ float2 steps = floor(ratio);
+ float3 col = sample_c(input.t).rgb;
+ float div = 1;
+
+ for (float y = 0; y < steps.y; y++)
+ {
+ for (float x = 0; x < steps.x; x++)
+ {
+ float2 offset = float2(x,y) - ratio * 0.5;
+ col += sample_c(input.t + offset * u_rcp_source_resolution * 2.0).rgb;
+ div++;
+ }
+ }
+
+ output.c = float4(col / div, 1);
+ return output;
+}
+
#endif
diff --git a/bin/resources/shaders/opengl/present.glsl b/bin/resources/shaders/opengl/present.glsl
index ce713965e0..9ab11b3ece 100644
--- a/bin/resources/shaders/opengl/present.glsl
+++ b/bin/resources/shaders/opengl/present.glsl
@@ -54,6 +54,11 @@ vec4 sample_c()
return texture(TextureSampler, PSin_t);
}
+vec4 sample_c(vec2 uv)
+{
+ return texture(TextureSampler, uv);
+}
+
vec4 ps_crt(uint i)
{
vec4 mask[4] = vec4[4](
@@ -384,4 +389,44 @@ void ps_filter_lottes()
#endif
+#ifdef ps_4x_rgss
+void ps_4x_rgss()
+{
+ vec2 dxy = vec2(dFdx(PSin_t.x), dFdy(PSin_t.y));
+ vec3 color = vec3(0);
+
+ float s = 1.0/8.0;
+ float l = 3.0/8.0;
+
+ color += sample_c(PSin_t + vec2( s, l) * dxy).rgb;
+ color += sample_c(PSin_t + vec2( l,-s) * dxy).rgb;
+ color += sample_c(PSin_t + vec2(-s,-l) * dxy).rgb;
+ color += sample_c(PSin_t + vec2(-l, s) * dxy).rgb;
+
+ SV_Target0 = vec4(color * 0.25,1);
+}
+#endif
+
+#ifdef ps_automagical_supersampling
+void ps_automagical_supersampling()
+{
+ vec2 ratio = (u_source_size / u_target_size) * 0.5;
+ vec2 steps = floor(ratio);
+ vec3 col = sample_c(PSin_t).rgb;
+ float div = 1;
+
+ for (float y = 0; y < steps.y; y++)
+ {
+ for (float x = 0; x < steps.x; x++)
+ {
+ vec2 offset = vec2(x,y) - ratio * 0.5;
+ col += sample_c(PSin_t + offset * u_rcp_source_resolution * 2.0).rgb;
+ div++;
+ }
+ }
+
+ SV_Target0 = vec4(col / div, 1);
+}
+#endif
+
#endif
diff --git a/bin/resources/shaders/vulkan/present.glsl b/bin/resources/shaders/vulkan/present.glsl
index f03082892e..f7eedb16aa 100644
--- a/bin/resources/shaders/vulkan/present.glsl
+++ b/bin/resources/shaders/vulkan/present.glsl
@@ -359,4 +359,44 @@ void ps_filter_lottes()
#endif
+#ifdef ps_4x_rgss
+void ps_4x_rgss()
+{
+ vec2 dxy = vec2(dFdx(v_tex.x), dFdy(v_tex.y));
+ vec3 color = vec3(0);
+
+ float s = 1.0/8.0;
+ float l = 3.0/8.0;
+
+ color += sample_c(v_tex + vec2( s, l) * dxy).rgb;
+ color += sample_c(v_tex + vec2( l,-s) * dxy).rgb;
+ color += sample_c(v_tex + vec2(-s,-l) * dxy).rgb;
+ color += sample_c(v_tex + vec2(-l, s) * dxy).rgb;
+
+ o_col0 = vec4(color * 0.25,1);
+}
+#endif
+
+#ifdef ps_automagical_supersampling
+void ps_automagical_supersampling()
+{
+ vec2 ratio = (u_source_size / u_target_size) * 0.5;
+ vec2 steps = floor(ratio);
+ vec3 col = sample_c(v_tex).rgb;
+ float div = 1;
+
+ for (float y = 0; y < steps.y; y++)
+ {
+ for (float x = 0; x < steps.x; x++)
+ {
+ vec2 offset = vec2(x,y) - ratio * 0.5;
+ col += sample_c(v_tex + offset * u_rcp_source_resolution * 2.0).rgb;
+ div++;
+ }
+ }
+
+ o_col0 = vec4(col / div, 1);
+}
+#endif
+
#endif
diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui
index 990cfa78b2..9e807996bb 100644
--- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui
+++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui
@@ -1479,6 +1479,16 @@
Lottes CRT
+ -
+
+ 4xRGSS downsampling (4x Rotated Grid SuperSampling)
+
+
+ -
+
+ NxAGSS downsampling (Nx Automatic Grid SuperSampling)
+
+
-
diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp
index a3d81862ec..f0c13acb80 100644
--- a/pcsx2/GS/Renderers/Common/GSDevice.cpp
+++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp
@@ -68,12 +68,14 @@ const char* shaderName(PresentShader value)
switch (value)
{
// clang-format off
- case PresentShader::COPY: return "ps_copy";
- case PresentShader::SCANLINE: return "ps_filter_scanlines";
- case PresentShader::DIAGONAL_FILTER: return "ps_filter_diagonal";
- case PresentShader::TRIANGULAR_FILTER: return "ps_filter_triangular";
- case PresentShader::COMPLEX_FILTER: return "ps_filter_complex";
- case PresentShader::LOTTES_FILTER: return "ps_filter_lottes";
+ case PresentShader::COPY: return "ps_copy";
+ case PresentShader::SCANLINE: return "ps_filter_scanlines";
+ case PresentShader::DIAGONAL_FILTER: return "ps_filter_diagonal";
+ case PresentShader::TRIANGULAR_FILTER: return "ps_filter_triangular";
+ case PresentShader::COMPLEX_FILTER: return "ps_filter_complex";
+ case PresentShader::LOTTES_FILTER: return "ps_filter_lottes";
+ case PresentShader::SUPERSAMPLE_4xRGSS: return "ps_4x_rgss";
+ case PresentShader::SUPERSAMPLE_AUTO: return "ps_automagical_supersampling";
// clang-format on
default:
ASSERT(0);
diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h
index 9427528327..2ceac2459b 100644
--- a/pcsx2/GS/Renderers/Common/GSDevice.h
+++ b/pcsx2/GS/Renderers/Common/GSDevice.h
@@ -131,6 +131,8 @@ enum class PresentShader
TRIANGULAR_FILTER,
COMPLEX_FILTER,
LOTTES_FILTER,
+ SUPERSAMPLE_4xRGSS,
+ SUPERSAMPLE_AUTO,
Count
};
diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp
index 825c601957..3dfbce0e9f 100644
--- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp
+++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp
@@ -44,10 +44,11 @@
#include
#include
-static constexpr std::array s_tv_shader_indices = {
+static constexpr std::array s_tv_shader_indices = {
PresentShader::COPY, PresentShader::SCANLINE,
PresentShader::DIAGONAL_FILTER, PresentShader::TRIANGULAR_FILTER,
- PresentShader::COMPLEX_FILTER, PresentShader::LOTTES_FILTER};
+ PresentShader::COMPLEX_FILTER, PresentShader::LOTTES_FILTER,
+ PresentShader::SUPERSAMPLE_4xRGSS, PresentShader::SUPERSAMPLE_AUTO};
static std::deque s_screenshot_threads;
static std::mutex s_screenshot_threads_mutex;
diff --git a/pcsx2/GS/Renderers/Metal/present.metal b/pcsx2/GS/Renderers/Metal/present.metal
index 96b5084770..a7e173b979 100644
--- a/pcsx2/GS/Renderers/Metal/present.metal
+++ b/pcsx2/GS/Renderers/Metal/present.metal
@@ -328,3 +328,40 @@ fragment float4 ps_filter_lottes(ConvertShaderData data [[stage_in]], ConvertPSR
{
return LottesCRTPass(res, uniform).Run(data.p);
}
+
+fragment float4 ps_4x_rgss(ConvertShaderData data [[stage_in]], ConvertPSRes res)
+{
+ float2 dxy = float2(dfdx(data.t.x), dfdy(data.t.y));
+ float3 color = 0;
+
+ float s = 1.0/8.0;
+ float l = 3.0/8.0;
+
+ color += res.sample(data.t + float2( s, l) * dxy).rgb;
+ color += res.sample(data.t + float2( l,-s) * dxy).rgb;
+ color += res.sample(data.t + float2(-s,-l) * dxy).rgb;
+ color += res.sample(data.t + float2(-l, s) * dxy).rgb;
+
+ return float4(color * 0.25,1);
+}
+
+fragment float4 ps_automagical_supersampling(ConvertShaderData data [[stage_in]], ConvertPSRes res,
+ constant GSMTLPresentPSUniform& cb [[buffer(GSMTLBufferIndexUniforms)]])
+{
+ float2 ratio = (cb.source_size / cb.target_size) * 0.5;
+ float2 steps = floor(ratio);
+ float3 col = res.sample(data.t).rgb;
+ float div = 1;
+
+ for (float y = 0; y < steps.y; y++)
+ {
+ for (float x = 0; x < steps.x; x++)
+ {
+ float2 offset = float2(x,y) - ratio * 0.5;
+ col += res.sample(data.t + offset * cb.rcp_source_resolution * 2.0).rgb;
+ div++;
+ }
+ }
+
+ return float4(col / div, 1);
+}
diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index ff09f9a8ca..c3d13ba8e8 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -3349,7 +3349,7 @@ void FullscreenUI::DrawGraphicsSettingsPage()
1, 100, "%d", shadeboost_active);
static constexpr const char* s_tv_shaders[] = {
- "None (Default)", "Scanline Filter", "Diagonal Filter", "Triangular Filter", "Wave Filter", "Lottes CRT"};
+ "None (Default)", "Scanline Filter", "Diagonal Filter", "Triangular Filter", "Wave Filter", "Lottes CRT", "4xRGSS", "NxAGSS"};
DrawIntListSetting(
bsi, "TV Shaders", "Selects post-processing TV shader.", "EmuCore/GS", "TVShader", 0, s_tv_shaders, std::size(s_tv_shaders));
}