From 69c5172b45e4df8ffd05049226bc07e14d13ddf9 Mon Sep 17 00:00:00 2001 From: refractionpcsx2 Date: Thu, 20 Nov 2025 14:55:51 +0000 Subject: [PATCH] GS/HW: Tweak Native Scale Upscaled to work in more scenarios --- pcsx2/GS/Renderers/HW/GSRendererHW.cpp | 21 ++++++++++++++------- pcsx2/GS/Renderers/HW/GSTextureCache.cpp | 17 ++++++++++++++++- 2 files changed, 30 insertions(+), 8 deletions(-) diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index a96009e991..875ba2c354 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -3194,6 +3194,7 @@ void GSRendererHW::Draw() float target_scale = GetTextureScaleFactor(); bool scaled_copy = false; int scale_draw = IsScalingDraw(src, m_primitive_covers_without_gaps != NoGapsType::GapsFound); + if (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off) { if (target_scale > 1.0f && scale_draw > 0) @@ -3202,9 +3203,15 @@ void GSRendererHW::Draw() // 2 == Upscale, so likely putting it over the top of the render target. if (scale_draw == 1) { - 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; + const bool highlights_only = m_cached_ctx.TEST.ATE || (PRIM->ABE && m_context->ALPHA.C == 2 && m_context->ALPHA.FIX == 255); + // If it's alpha tested/stenciling for the bloom, we don't want to using the Upscaled versions. Also if the source is already native scale, may as well keep it so. + // Overlap check is for games such as Tomb Raider, where it recursively downsamples. + // Also make sure this isn't a blend, just draw (possibly with modulation). + if (GSConfig.UserHacks_NativeScaling < GSNativeScaling::NormalUpscaled || highlights_only || !PRIM->ABE || (src->m_from_target && src->m_from_target->Overlaps(m_cached_ctx.FRAME.Block(), m_cached_ctx.FRAME.FBW, m_cached_ctx.FRAME.PSM, m_r))) + { + target_scale = 1.0f; + } } else 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. @@ -3232,7 +3239,7 @@ void GSRendererHW::Draw() // This upscaling hack is for games which construct P8 textures by drawing a bunch of small sprites in C32, // then reinterpreting it as P8. We need to keep the off-screen intermediate textures at native resolution, // but not propagate that through to the normal render targets. Test Case: Crash Wrath of Cortex. - if (no_ds && src && !m_channel_shuffle && src->m_from_target && (GSConfig.UserHacks_NativePaletteDraw || (src->m_target_direct && src->m_from_target->m_downscaled && scale_draw <= 1)) && + if (no_ds && src && !m_channel_shuffle && src->m_from_target && (GSConfig.UserHacks_NativePaletteDraw || (src->m_target_direct && src->m_from_target->m_downscaled && scale_draw <= 1)) && src->m_scale == 1.0f && (src->m_TEX0.PSM == PSMT8 || src->m_TEX0.TBP0 == m_cached_ctx.FRAME.Block())) { GL_CACHE("HW: Using native resolution for target based on texture source"); @@ -3525,7 +3532,7 @@ void GSRendererHW::Draw() // Preserve downscaled target when copying directly from a downscaled target, or it's a normal draw using a downscaled target. Clears that are drawing to the target can also preserve size. // 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; + const bool preserve_downscale_draw = (GSConfig.UserHacks_NativeScaling != GSNativeScaling::Off && ((std::abs(scale_draw) == 1 && !scaled_copy) || (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 || 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(), @@ -7025,7 +7032,7 @@ __ri void GSRendererHW::HandleTextureHazards(const GSTextureCache::Target* rt, c // When using native HPO, the top-left column/row of pixels are often not drawn. Clamp these away to avoid sampling black, // causing bleeding into the edges of the downsampled texture. const u32 downsample_factor = static_cast(src_target->GetScale()); - const GSVector2i clamp_min = (GSConfig.UserHacks_HalfPixelOffset < GSHalfPixelOffset::Native) ? + const GSVector2i clamp_min = (GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Native) ? GSVector2i(0, 0) : GSVector2i(downsample_factor, downsample_factor); GSVector4i copy_rect = tmm.coverage; @@ -9212,7 +9219,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps) const bool no_resize = (std::abs(draw_size.x - tex_size.x) <= 1 && std::abs(draw_size.y - tex_size.y) <= 1); const bool can_maintain = no_resize || (!is_target_src && m_index.tail == 2); - if (!src || ((!is_target_src || src->m_from_target->m_downscaled) && can_maintain)) + if (!src || ((!is_target_src || (src->m_from_target->m_downscaled || GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive)) && can_maintain)) return -1; const GSDrawingContext& next_ctx = m_env.CTXT[m_env.PRIM.CTXT]; @@ -9227,7 +9234,7 @@ int GSRendererHW::IsScalingDraw(GSTextureCache::Source* src, bool no_gaps) // Only allow non-bilineared downscales if it's most of the target (misdetections of shadows in Naruto, Transformers etc), otherwise it's fine. const GSVector4i src_valid = src->m_from_target ? src->m_from_target->m_valid : src->m_valid_rect; const GSVector2i tex_size_half = GSVector2i((src->GetRegion().HasX() ? src->GetRegionSize().x : src_valid.width()) / 2, (src->GetRegion().HasY() ? src->GetRegionSize().y : src_valid.height()) / 2); - const bool possible_downscale = m_context->TEX1.MMIN == 1 || !src->m_from_target || src->m_from_target->m_downscaled || tex_size.x >= tex_size_half.x || tex_size.y >= tex_size_half.y; + const bool possible_downscale = m_context->TEX1.MMIN == 1 || !src->m_from_target || src->m_from_target->m_downscaled || GSConfig.UserHacks_NativeScaling > GSNativeScaling::Aggressive || tex_size.x >= tex_size_half.x || tex_size.y >= tex_size_half.y; if (is_downscale && (draw_size.x >= PCRTCDisplays.GetResolution().x || !possible_downscale)) return 0; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 289aa36695..8fdbfb169c 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -2744,7 +2744,22 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(GIFRegTEX0 TEX0, const GSVe if (!tex) return nullptr; - g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, dst->m_scale < scale); + if (scale == 1.0f) + { + // When using native HPO, the top-left column/row of pixels are often not drawn. Clamp these away to avoid sampling black, + // causing bleeding into the edges of the downsampled texture. + const u32 downsample_factor = static_cast(dst->GetScale()); + const GSVector2i clamp_min = (GSConfig.UserHacks_HalfPixelOffset != GSHalfPixelOffset::Native) ? + GSVector2i(0, 0) : + GSVector2i(downsample_factor, downsample_factor); + + const GSVector4 dRect = GSVector4(dst->GetUnscaledRect()); + + g_gs_device->FilteredDownsampleTexture(dst->m_texture, tex, downsample_factor, clamp_min, dRect); + } + else + g_gs_device->StretchRect(dst->m_texture, sRect, tex, dRect, (type == RenderTarget) ? ShaderConvert::COPY : ShaderConvert::DEPTH_COPY, dst->m_scale < scale); + g_perfmon.Put(GSPerfMon::TextureCopies, 1); m_target_memory_usage = (m_target_memory_usage - dst->m_texture->GetMemUsage()) + tex->GetMemUsage();