diff --git a/bin/resources/shaders/dx11/convert.fx b/bin/resources/shaders/dx11/convert.fx
index e504ef2fde..2d169effb0 100644
--- a/bin/resources/shaders/dx11/convert.fx
+++ b/bin/resources/shaders/dx11/convert.fx
@@ -80,6 +80,7 @@ PS_OUTPUT ps_downsample_copy(PS_INPUT input)
int DownsampleFactor = DOFFSET;
int2 ClampMin = int2(EMODA, EMODC);
float Weight = BGColor.x;
+ float step_multiplier = BGColor.y;
int2 coord = max(int2(input.p.xy) * DownsampleFactor, ClampMin);
@@ -88,7 +89,7 @@ PS_OUTPUT ps_downsample_copy(PS_INPUT input)
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
{
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
- output.c += Texture.Load(int3(coord + int2(xoff, yoff), 0));
+ output.c += Texture.Load(int3(coord + int2(xoff * step_multiplier, yoff * step_multiplier), 0));
}
output.c /= Weight;
return output;
diff --git a/bin/resources/shaders/opengl/convert.glsl b/bin/resources/shaders/opengl/convert.glsl
index 92336d86b2..4989c71c98 100644
--- a/bin/resources/shaders/opengl/convert.glsl
+++ b/bin/resources/shaders/opengl/convert.glsl
@@ -70,6 +70,7 @@ void ps_depth_copy()
uniform ivec2 ClampMin;
uniform int DownsampleFactor;
uniform float Weight;
+uniform float StepMultiplier;
void ps_downsample_copy()
{
@@ -78,7 +79,7 @@ void ps_downsample_copy()
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
{
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
- result += texelFetch(TextureSampler, coord + ivec2(xoff, yoff), 0);
+ result += texelFetch(TextureSampler, coord + ivec2(xoff * StepMultiplier, yoff * StepMultiplier), 0);
}
SV_Target0 = result / Weight;
}
diff --git a/bin/resources/shaders/vulkan/convert.glsl b/bin/resources/shaders/vulkan/convert.glsl
index 0844108f59..0aed8dba32 100644
--- a/bin/resources/shaders/vulkan/convert.glsl
+++ b/bin/resources/shaders/vulkan/convert.glsl
@@ -66,7 +66,8 @@ layout(push_constant) uniform cb10
int DownsampleFactor;
int pad0;
float Weight;
- vec3 pad1;
+ float step_multiplier;
+ vec2 pad1;
};
void ps_downsample_copy()
{
@@ -75,7 +76,9 @@ void ps_downsample_copy()
for (int yoff = 0; yoff < DownsampleFactor; yoff++)
{
for (int xoff = 0; xoff < DownsampleFactor; xoff++)
- result += texelFetch(samp0, coord + ivec2(xoff, yoff), 0);
+ {
+ result += texelFetch(samp0, coord + ivec2(xoff * step_multiplier, yoff * step_multiplier), 0);
+ }
}
o_col0 = result / Weight;
}
diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
index 57409e48d1..337180adca 100644
--- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
+++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp
@@ -970,7 +970,6 @@ void GraphicsSettingsWidget::onTextureReplacementChanged()
m_texture.precacheTextureReplacements->setEnabled(enabled);
}
-
void GraphicsSettingsWidget::onCaptureContainerChanged()
{
const std::string container(
diff --git a/pcsx2-qt/Settings/GraphicsUpscalingFixesSettingsTab.ui b/pcsx2-qt/Settings/GraphicsUpscalingFixesSettingsTab.ui
index 30276e8c88..d6f6fcc5d4 100644
--- a/pcsx2-qt/Settings/GraphicsUpscalingFixesSettingsTab.ui
+++ b/pcsx2-qt/Settings/GraphicsUpscalingFixesSettingsTab.ui
@@ -136,6 +136,16 @@
Aggressive
+ -
+
+ Normal (Maintain Upscale)
+
+
+ -
+
+ Aggressive (Maintain Upscale)
+
+
-
diff --git a/pcsx2/Config.h b/pcsx2/Config.h
index 86b1c4d225..14776ae8d5 100644
--- a/pcsx2/Config.h
+++ b/pcsx2/Config.h
@@ -456,6 +456,8 @@ enum class GSNativeScaling : u8
Off,
Normal,
Aggressive,
+ NormalUpscaled,
+ AggressiveUpscaled,
MaxCount
};
diff --git a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp
index 7bbab09748..8bb8761d27 100644
--- a/pcsx2/GS/Renderers/DX11/GSDevice11.cpp
+++ b/pcsx2/GS/Renderers/DX11/GSDevice11.cpp
@@ -1465,14 +1465,15 @@ void GSDevice11::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32
struct Uniforms
{
float weight;
- float pad0[3];
+ float step_multiplier;
+ float pad0[2];
GSVector2i clamp_min;
int downsample_factor;
int pad1;
};
const Uniforms cb = {
- static_cast(downsample_factor * downsample_factor), {}, clamp_min, static_cast(downsample_factor), 0};
+ static_cast(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast(downsample_factor), 0};
m_ctx->UpdateSubresource(m_merge.cb.get(), 0, nullptr, &cb, 0, 0);
const ShaderConvert shader = ShaderConvert::DOWNSAMPLE_COPY;
diff --git a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp
index fe5fe2e1d9..62f989a5a5 100644
--- a/pcsx2/GS/Renderers/DX12/GSDevice12.cpp
+++ b/pcsx2/GS/Renderers/DX12/GSDevice12.cpp
@@ -1512,14 +1512,15 @@ void GSDevice12::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32
struct Uniforms
{
float weight;
- float pad0[3];
+ float step_multiplier;
+ float pad0[2];
GSVector2i clamp_min;
int downsample_factor;
int pad1;
};
const Uniforms cb = {
- static_cast(downsample_factor * downsample_factor), {}, clamp_min, static_cast(downsample_factor), 0};
+ static_cast(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast(downsample_factor), 0};
SetUtilityRootSignature();
SetUtilityPushConstants(&cb, sizeof(cb));
diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
index 9c0ef6eeff..0a2264acb7 100644
--- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
+++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp
@@ -3192,11 +3192,12 @@ void GSRendererHW::Draw()
// 2 == Upscale, so likely putting it over the top of the render target.
if (scale_draw == 1)
{
- target_scale = 1.0f;
+ if (!PRIM->ABE || GSConfig.UserHacks_NativeScaling < GSNativeScaling::NormalUpscaled)
+ target_scale = 1.0f;
m_downscale_source = src->m_from_target ? src->m_from_target->GetScale() > 1.0f : false;
}
else
- m_downscale_source = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive || !src->m_from_target) ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa.
+ m_downscale_source = ((GSConfig.UserHacks_NativeScaling != GSNativeScaling::Aggressive && GSConfig.UserHacks_NativeScaling != GSNativeScaling::AggressiveUpscaled) || !src->m_from_target) ? false : src->m_from_target->GetScale() > 1.0f; // Bad for GTA + Full Spectrum Warrior, good for Sacred Blaze + Parappa.
}
else
{
@@ -3516,7 +3517,7 @@ void GSRendererHW::Draw()
// Of course if this size is different (in width) or this is a shuffle happening, this will be bypassed.
const bool preserve_downscale_draw = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && (std::abs(scale_draw) == 1 || (scale_draw == 0 && src && src->m_from_target && src->m_from_target->m_downscaled))) || is_possible_mem_clear == ClearType::ClearWithDraw;
- rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true,
+ rt = g_texture_cache->LookupTarget(FRAME_TEX0, t_size, ((src && src->m_scale != 1) && (GSConfig.UserHacks_NativeScaling == GSNativeScaling::Normal || GSConfig.UserHacks_NativeScaling == GSNativeScaling::NormalUpscaled) && !possible_shuffle) ? GetTextureScaleFactor() : target_scale, GSTextureCache::RenderTarget, true,
fm, false, force_preload, preserve_rt_rgb, preserve_rt_alpha, lookup_rect, possible_shuffle, is_possible_mem_clear && FRAME_TEX0.TBP0 != m_cached_ctx.ZBUF.Block(),
GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && preserve_downscale_draw && is_possible_mem_clear != ClearType::NormalClear, src, ds, (no_ds || !ds) ? -1 : (m_cached_ctx.ZBUF.Block() - ds->m_TEX0.TBP0));
diff --git a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm
index 376a3b4380..4dcf53c2bb 100644
--- a/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm
+++ b/pcsx2/GS/Renderers/Metal/GSDeviceMTL.mm
@@ -1754,7 +1754,7 @@ void GSDeviceMTL::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u3
[NSException raise:@"StretchRect Missing Pipeline" format:@"No pipeline for %d", static_cast(shader)];
GSMTLDownsamplePSUniform uniform = { {static_cast(clamp_min.x), static_cast(clamp_min.x)}, downsample_factor,
- static_cast(downsample_factor * downsample_factor) };
+ static_cast(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f };
DoStretchRect(sTex, GSVector4::zero(), dTex, dRect, pipeline, false, LoadAction::DontCareIfFull, &uniform, sizeof(uniform));
}}
diff --git a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h
index 1f87d89ab6..f7fb7f72e1 100644
--- a/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h
+++ b/pcsx2/GS/Renderers/Metal/GSMTLSharedHeader.h
@@ -72,6 +72,7 @@ struct GSMTLDownsamplePSUniform
vector_uint2 clamp_min;
uint downsample_factor;
float weight;
+ float step_multiplier;
};
struct GSMTLMainVertex
diff --git a/pcsx2/GS/Renderers/Metal/convert.metal b/pcsx2/GS/Renderers/Metal/convert.metal
index eaed4d1970..5c5c249bcb 100644
--- a/pcsx2/GS/Renderers/Metal/convert.metal
+++ b/pcsx2/GS/Renderers/Metal/convert.metal
@@ -192,7 +192,7 @@ fragment float4 ps_downsample_copy(ConvertShaderData data [[stage_in]],
for (uint yoff = 0; yoff < uniform.downsample_factor; yoff++)
{
for (uint xoff = 0; xoff < uniform.downsample_factor; xoff++)
- result += texture.read(coord + uint2(xoff, yoff), 0);
+ result += texture.read(coord + uint2(xoff * uniform.step_multiplier, yoff * uniform.step_multiplier), 0);
}
result /= uniform.weight;
return result;
diff --git a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp
index f3023a4d4d..67e7452bba 100644
--- a/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp
+++ b/pcsx2/GS/Renderers/OpenGL/GSDeviceOGL.cpp
@@ -365,6 +365,7 @@ bool GSDeviceOGL::Create(GSVSyncMode vsync_mode, bool allow_present_throttle)
m_convert.ps[i].RegisterUniform("ClampMin");
m_convert.ps[i].RegisterUniform("DownsampleFactor");
m_convert.ps[i].RegisterUniform("Weight");
+ m_convert.ps[i].RegisterUniform("StepMultiplier");
}
}
@@ -1632,6 +1633,7 @@ void GSDeviceOGL::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u3
prog.Uniform2iv(0, clamp_min.v);
prog.Uniform1i(1, downsample_factor);
prog.Uniform1f(2, static_cast(downsample_factor * downsample_factor));
+ prog.Uniform1f(3, (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f);
OMSetDepthStencilState(m_convert.dss);
OMSetBlendState(false);
diff --git a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp
index 3332f9fb26..a39cb8b84d 100644
--- a/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp
+++ b/pcsx2/GS/Renderers/Vulkan/GSDeviceVK.cpp
@@ -3154,11 +3154,12 @@ void GSDeviceVK::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u32
int downsample_factor;
int pad0;
float weight;
- float pad1[3];
+ float step_multiplier;
+ float pad1[2];
};
const Uniforms uniforms = {
- clamp_min, static_cast(downsample_factor), 0, static_cast(downsample_factor * downsample_factor)};
+ clamp_min, static_cast(downsample_factor), 0, static_cast(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f};
SetUtilityPushConstants(&uniforms, sizeof(uniforms));
const ShaderConvert shader = ShaderConvert::DOWNSAMPLE_COPY;
diff --git a/pcsx2/ImGui/FullscreenUI.cpp b/pcsx2/ImGui/FullscreenUI.cpp
index 62742acdde..0bca7e40b0 100644
--- a/pcsx2/ImGui/FullscreenUI.cpp
+++ b/pcsx2/ImGui/FullscreenUI.cpp
@@ -4573,9 +4573,11 @@ void FullscreenUI::DrawGraphicsSettingsPage(SettingsInterface* bsi, bool show_ad
FSUI_NSTR("Align to Native - with Texture Offset"),
};
static constexpr const char* s_native_scaling_options[] = {
- FSUI_NSTR("Normal (Default)"),
+ FSUI_NSTR("Off (Default)"),
+ FSUI_NSTR("Normal"),
FSUI_NSTR("Aggressive"),
- FSUI_NSTR("Off"),
+ FSUI_NSTR("Normal (Maintain Upscale)"),
+ FSUI_NSTR("Aggressive (Maintain Upscale)"),
};
static constexpr const char* s_round_sprite_options[] = {
FSUI_NSTR("Off (Default)"),
diff --git a/pcsx2/ShaderCacheVersion.h b/pcsx2/ShaderCacheVersion.h
index 7c6de74bb9..a52eb2645c 100644
--- a/pcsx2/ShaderCacheVersion.h
+++ b/pcsx2/ShaderCacheVersion.h
@@ -3,4 +3,4 @@
/// Version number for GS and other shaders. Increment whenever any of the contents of the
/// shaders change, to invalidate the cache.
-static constexpr u32 SHADER_CACHE_VERSION = 76;
+static constexpr u32 SHADER_CACHE_VERSION = 77;