GS/HW: Add options to maintain upscale in Native Scaling

This will adjust how the box filter samples to simulate bilinear when upscaling
This commit is contained in:
refractionpcsx2 2025-11-06 19:20:39 +00:00 committed by lightningterror
parent c5438ceca3
commit 44d66555cc
16 changed files with 44 additions and 19 deletions

View File

@ -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;

View File

@ -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;
}

View File

@ -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;
}

View File

@ -970,7 +970,6 @@ void GraphicsSettingsWidget::onTextureReplacementChanged()
m_texture.precacheTextureReplacements->setEnabled(enabled);
}
void GraphicsSettingsWidget::onCaptureContainerChanged()
{
const std::string container(

View File

@ -136,6 +136,16 @@
<string>Aggressive</string>
</property>
</item>
<item>
<property name="text">
<string>Normal (Maintain Upscale)</string>
</property>
</item>
<item>
<property name="text">
<string>Aggressive (Maintain Upscale)</string>
</property>
</item>
</widget>
</item>
<item row="4" column="0">

View File

@ -456,6 +456,8 @@ enum class GSNativeScaling : u8
Off,
Normal,
Aggressive,
NormalUpscaled,
AggressiveUpscaled,
MaxCount
};

View File

@ -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<float>(downsample_factor * downsample_factor), {}, clamp_min, static_cast<int>(downsample_factor), 0};
static_cast<float>(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast<int>(downsample_factor), 0};
m_ctx->UpdateSubresource(m_merge.cb.get(), 0, nullptr, &cb, 0, 0);
const ShaderConvert shader = ShaderConvert::DOWNSAMPLE_COPY;

View File

@ -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<float>(downsample_factor * downsample_factor), {}, clamp_min, static_cast<int>(downsample_factor), 0};
static_cast<float>(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f, {}, clamp_min, static_cast<int>(downsample_factor), 0};
SetUtilityRootSignature();
SetUtilityPushConstants(&cb, sizeof(cb));

View File

@ -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));

View File

@ -1754,7 +1754,7 @@ void GSDeviceMTL::FilteredDownsampleTexture(GSTexture* sTex, GSTexture* dTex, u3
[NSException raise:@"StretchRect Missing Pipeline" format:@"No pipeline for %d", static_cast<int>(shader)];
GSMTLDownsamplePSUniform uniform = { {static_cast<uint>(clamp_min.x), static_cast<uint>(clamp_min.x)}, downsample_factor,
static_cast<float>(downsample_factor * downsample_factor) };
static_cast<float>(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));
}}

View File

@ -72,6 +72,7 @@ struct GSMTLDownsamplePSUniform
vector_uint2 clamp_min;
uint downsample_factor;
float weight;
float step_multiplier;
};
struct GSMTLMainVertex

View File

@ -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;

View File

@ -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<float>(downsample_factor * downsample_factor));
prog.Uniform1f(3, (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f);
OMSetDepthStencilState(m_convert.dss);
OMSetBlendState(false);

View File

@ -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<int>(downsample_factor), 0, static_cast<float>(downsample_factor * downsample_factor)};
clamp_min, static_cast<int>(downsample_factor), 0, static_cast<float>(downsample_factor * downsample_factor), (GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive) ? 2.0f : 1.0f};
SetUtilityPushConstants(&uniforms, sizeof(uniforms));
const ShaderConvert shader = ShaderConvert::DOWNSAMPLE_COPY;

View File

@ -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)"),

View File

@ -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;