diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp index 76f9619197..4d1a6e9a45 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.cpp @@ -186,7 +186,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.crcFixLevel, "EmuCore/GS", "crc_hack_level", static_cast(CRCHackLevel::Automatic), -1); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.blending, "EmuCore/GS", "accurate_blending_unit", static_cast(AccBlendLevel::Basic)); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.accurateDATE, "EmuCore/GS", "accurate_date", true); - SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.conservativeBufferAllocation, "EmuCore/GS", "conservative_framebuffer", true); SettingWidgetBinder::BindWidgetToBoolSetting(sif, m_ui.gpuPaletteConversion, "EmuCore/GS", "paltex", false); SettingWidgetBinder::BindWidgetToIntSetting(sif, m_ui.texturePreloading, "EmuCore/GS", "texture_preloading", static_cast(TexturePreloadingLevel::Off)); @@ -355,10 +354,6 @@ GraphicsSettingsWidget::GraphicsSettingsWidget(SettingsDialog* dialog, QWidget* tr("Implement a more accurate algorithm to compute GS destination alpha testing. " "It improves shadow and transparency rendering.")); - dialog->registerWidgetHelp(m_ui.conservativeBufferAllocation, tr("Conservative Buffer Allocation"), tr("Checked"), - tr("Disabled: Reserves a larger framebuffer to prevent FMV flickers. Increases GPU/memory requirements. " - "Disabling this can amplify stuttering due to low RAM/VRAM.")); - dialog->registerWidgetHelp(m_ui.gpuPaletteConversion, tr("GPU Palette Conversion"), tr("Unchecked"), tr("When enabled GPU converts colormap-textures, otherwise the CPU will. " "It is a trade-off between GPU and CPU.")); diff --git a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui index a14b2a695a..07fea2419b 100644 --- a/pcsx2-qt/Settings/GraphicsSettingsWidget.ui +++ b/pcsx2-qt/Settings/GraphicsSettingsWidget.ui @@ -58,7 +58,7 @@ - 0 + 1 true @@ -642,38 +642,6 @@ - - - - - - GPU Palette Conversion - - - - - - - Conservative Buffer Allocation - - - - - - - Accurate Destination Alpha Test - - - - - - - Manual Hardware Renderer Fixes - - - - - @@ -700,6 +668,31 @@ + + + + + + Accurate Destination Alpha Test + + + + + + + GPU Palette Conversion + + + + + + + Manual Hardware Renderer Fixes + + + + + diff --git a/pcsx2/Config.h b/pcsx2/Config.h index 6c83aec207..0343f65372 100644 --- a/pcsx2/Config.h +++ b/pcsx2/Config.h @@ -461,7 +461,6 @@ struct Pcsx2Config HWDisableReadbacks : 1, AccurateDATE : 1, GPUPaletteConversion : 1, - ConservativeFramebuffer : 1, AutoFlushSW : 1, PreloadFrameWithGSData : 1, WrapGSMem : 1, diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index 2951d36e97..c1a6a09b64 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -829,7 +829,6 @@ void GSUpdateConfig(const Pcsx2Config::GSOptions& new_config) // reload texture cache when trilinear filtering or TC options change if ( (GSConfig.UseHardwareRenderer() && GSConfig.HWMipmap != old_config.HWMipmap) || - GSConfig.ConservativeFramebuffer != old_config.ConservativeFramebuffer || GSConfig.TexturePreloading != old_config.TexturePreloading || GSConfig.UserHacks_TriFilter != old_config.UserHacks_TriFilter || GSConfig.GPUPaletteConversion != old_config.GPUPaletteConversion || @@ -1456,7 +1455,6 @@ void GSApp::Init() m_default_configuration["pcrtc_overscan"] = "0"; m_default_configuration["IntegerScaling"] = "0"; m_default_configuration["deinterlace"] = "7"; - m_default_configuration["conservative_framebuffer"] = "1"; m_default_configuration["linear_present"] = "1"; m_default_configuration["LoadTextureReplacements"] = "0"; m_default_configuration["LoadTextureReplacementsAsync"] = "1"; diff --git a/pcsx2/GS/GSExtra.h b/pcsx2/GS/GSExtra.h index 71750975c2..97374d7b3d 100644 --- a/pcsx2/GS/GSExtra.h +++ b/pcsx2/GS/GSExtra.h @@ -76,12 +76,6 @@ __forceinline bool BitEqual(const T& a, const T& b) return eqb; } -#ifdef ENABLE_ACCURATE_BUFFER_EMULATION -static const GSVector2i default_rt_size(2048, 2048); -#else -static const GSVector2i default_rt_size(0, 0); -#endif - extern Pcsx2Config::GSOptions GSConfig; // Maximum texture size to skip preload/hash path. diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index c76b27fced..e0d785c543 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -20,12 +20,11 @@ #include "GS/Renderers/SW/GSTextureCacheSW.h" #include "GS/Renderers/SW/GSDrawScanline.h" #include "Host.h" +#include "common/Align.h" #include "common/StringUtil.h" GSRendererHW::GSRendererHW() : GSRenderer() - , m_width(default_rt_size.x) - , m_height(default_rt_size.y) , m_tc(new GSTextureCache()) , m_src(nullptr) , m_reset(false) @@ -50,7 +49,7 @@ GSRendererHW::GSRendererHW() ResetStates(); } -void GSRendererHW::SetScaling() +GSVector2i GSRendererHW::GetOutputSize(int real_h) { GSVector2i crtc_size(GetResolution()); @@ -90,63 +89,10 @@ void GSRendererHW::SetScaling() } } - // Details of (potential) perf impact of a big framebuffer - // 1/ extra memory - // 2/ texture cache framebuffer rescaling/copy - // 3/ upload of framebuffer (preload hack) - // 4/ framebuffer clear (color/depth/stencil) - // 5/ read back of the frambuffer - // - // With the solution - // 1/ Nothing to do.Except the texture cache bug (channel shuffle effect) - // most of the market is 1GB of VRAM (and soon 2GB) - // 2/ limit rescaling/copy to the valid data of the framebuffer - // 3/ ??? no solution so far - // 4a/ stencil can be limited to valid data. - // 4b/ is it useful to clear color? depth? (in any case, it ought to be few operation) - // 5/ limit the read to the valid data + // Include negative display offsets in the height here. + crtc_size.y = std::max(crtc_size.y, real_h); - // Framebuffer width is always a multiple of 64 so at certain cases it can't cover some weird width values. - // 480P , 576P use width as 720 which is not referencable by FBW * 64. so it produces 704 ( the closest value multiple by 64). - // In such cases, let's just use the CRTC width. - const int fb_width = std::max({(int)m_context->FRAME.FBW * 64, crtc_size.x, 512}); - // GS doesn't have a specific register for the FrameBuffer height. so we get the height - // from physical units of the display rectangle in case the game uses a heigher value of height. - // - // Gregory: the framebuffer must have enough room to draw - // * at least 2 frames such as FMV (see OI_BlitFMV) - // * high resolution game such as snowblind engine game - // - // Autodetection isn't a good idea because it will create flickering - // If memory consumption is an issue, there are 2 possibilities - // * 1/ Avoid to create hundreds of RT - // * 2/ Use sparse texture (requires recent HW) - // - // Avoid to alternate between 640x1280 and 1280x1024 on snow blind engine game - // int fb_height = (fb_width < 1024) ? 1280 : 1024; - // - // Until performance issue is properly fixed, let's keep an option to reduce the framebuffer size. - // - // m_large_framebuffer has been inverted to m_conservative_framebuffer, it isn't an option that benefits being enabled all the time for everyone. - int fb_height = MAX_FRAMEBUFFER_HEIGHT; - if (GSConfig.ConservativeFramebuffer) - { - fb_height = fb_width < 1024 ? std::max(512, crtc_size.y) : 1024; - } - - const int upscaled_fb_w = fb_width * GSConfig.UpscaleMultiplier; - const int upscaled_fb_h = fb_height * GSConfig.UpscaleMultiplier; - const bool good_rt_size = m_width >= upscaled_fb_w && m_height >= upscaled_fb_h; - - // No need to resize for native/custom resolutions as default size will be enough for native and we manually get RT Buffer size for custom. - // don't resize until the display rectangle and register states are stabilized. - if (good_rt_size) - return; - - m_tc->RemovePartial(); - m_width = upscaled_fb_w; - m_height = upscaled_fb_h; - printf("Frame buffer size set to %dx%d (%dx%d)\n", fb_width, fb_height, m_width, m_height); + return crtc_size * GSVector2i(static_cast(GSConfig.UpscaleMultiplier), static_cast(GSConfig.UpscaleMultiplier)); } void GSRendererHW::SetTCOffset() @@ -192,11 +138,6 @@ void GSRendererHW::SetGameCRC(u32 crc, int options) bool GSRendererHW::CanUpscale() { - if (m_hacks.m_cu && !(this->*m_hacks.m_cu)()) - { - return false; - } - return GSConfig.UpscaleMultiplier != 1; } @@ -227,20 +168,12 @@ void GSRendererHW::VSync(u32 field, bool registers_written) if (m_reset) { m_tc->RemoveAll(); - - // Reset RT size. - m_width = default_rt_size.x; - m_height = default_rt_size.y; - m_reset = false; } if (GSConfig.LoadTextureReplacements) GSTextureReplacements::ProcessAsyncLoadedTextures(); - //Check if the frame buffer width or display width has changed - SetScaling(); - GSRenderer::VSync(field, registers_written); m_tc->IncAge(); @@ -286,7 +219,7 @@ GSTexture* GSRendererHW::GetOutput(int i, int& y_offset) GSTexture* t = NULL; - if (GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, GetTargetSize(), fb_height)) + if (GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height), fb_height)) { t = rt->m_texture; @@ -321,7 +254,8 @@ GSTexture* GSRendererHW::GetFeedbackOutput() TEX0.TBW = m_regs->EXTBUF.EXBW; TEX0.PSM = m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPFB.PSM; - GSTextureCache::Target* rt = m_tc->LookupTarget(TEX0, GetTargetSize(), /*GetFrameRect(i).bottom*/ m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPLAY.DH); + const int fb_height = /*GetFrameRect(i).bottom*/ m_regs->DISP[m_regs->EXTBUF.FBIN & 1].DISPLAY.DH; + GSTextureCache::Target* rt = m_tc->LookupDisplayTarget(TEX0, GetOutputSize(fb_height), fb_height); GSTexture* t = rt->m_texture; @@ -763,35 +697,41 @@ void GSRendererHW::MergeSprite(GSTextureCache::Source* tex) } } -GSVector2 GSRendererHW::GetTextureScaleFactor(const bool force_upscaling) -{ - GSVector2 scale_factor{ 1.0f, 1.0f }; - if (force_upscaling || CanUpscale()) - { - const int multiplier = GetUpscaleMultiplier(); - scale_factor.x = multiplier; - scale_factor.y = multiplier; - } - - return scale_factor; -} - GSVector2 GSRendererHW::GetTextureScaleFactor() { - return GetTextureScaleFactor(false); + const float f_upscale = static_cast(GetUpscaleMultiplier()); + return GSVector2(f_upscale, f_upscale); } GSVector2i GSRendererHW::GetTargetSize() { - const GSVector2i t_size = { m_width, m_height }; - if (GetUpscaleMultiplier() == 1 || CanUpscale()) - return t_size; - // Undo the upscaling for native resolution draws. - const GSVector2 up_s = GetTextureScaleFactor(true); - return { - static_cast(std::ceil(static_cast(t_size.x) / up_s.x)), - static_cast(std::ceil(static_cast(t_size.y) / up_s.y)), - }; + // Don't blindly expand out to the scissor size if we're not drawing to it. + // e.g. Burnout 3, God of War II, etc. + u32 min_height = std::min(m_context->scissor.in.w, m_r.w); + + // Another thing these games like to do, is draw a 512x896 shuffle, which would result in us + // expanding the target out to 896 height, but the extra area would all be black, with the + // draw effectively changing nothing for the new area. So, instead, lets try to detect these + // draws by double-checking we're not stretching the texture (gradient of <1). + if (PRIM->TME && m_vt.m_primclass == GS_SPRITE_CLASS && m_src && (m_src->m_target || m_src->m_from_target)) + { + const float diff = std::abs((m_vt.m_max.p.y - m_vt.m_min.p.y) - (m_vt.m_max.t.y - m_vt.m_min.t.y)); + if (diff <= 1.0f) + { + // Clamp to the texture size. We're working in unscaled coordinates here, so undo the upscaling. + min_height = std::min(min_height, static_cast(static_cast(m_src->m_texture->GetHeight()) / m_src->m_texture->GetScale().y)); + } + } + + // Align to even lines, reduces the chance of tiny resizes. + min_height = Common::AlignUpPow2(min_height, 2); + + const u32 width = m_context->FRAME.FBW * 64u; + const u32 height = m_tc->GetTargetHeight(m_context->FRAME.FBP, m_context->FRAME.FBW, m_context->FRAME.PSM, min_height); + + GL_INS("Target size for %x %u %u: %ux%u", m_context->FRAME.FBP, m_context->FRAME.FBW, m_context->FRAME.PSM, width, height); + + return GSVector2i(static_cast(width * GSConfig.UpscaleMultiplier), static_cast(height * GSConfig.UpscaleMultiplier)); } void GSRendererHW::InvalidateVideoMem(const GIFRegBITBLTBUF& BITBLTBUF, const GSVector4i& r) @@ -1605,6 +1545,8 @@ void GSRendererHW::Draw() } } + // The rectangle of the draw + m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in)); const GSVector2i t_size = GetTargetSize(); TEX0.TBP0 = context->FRAME.Block(); @@ -1612,24 +1554,16 @@ void GSRendererHW::Draw() TEX0.PSM = context->FRAME.PSM; GSTextureCache::Target* rt = nullptr; - GSTexture* rt_tex = nullptr; if (!no_rt) - { rt = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::RenderTarget, true, fm); - rt_tex = rt->m_texture; - } TEX0.TBP0 = context->ZBUF.Block(); TEX0.TBW = context->FRAME.FBW; TEX0.PSM = context->ZBUF.PSM; GSTextureCache::Target* ds = nullptr; - GSTexture* ds_tex = nullptr; if (!no_ds) - { ds = m_tc->LookupTarget(TEX0, t_size, GSTextureCache::DepthStencil, context->DepthWrite()); - ds_tex = ds->m_texture; - } if (rt) { @@ -1639,38 +1573,24 @@ void GSRendererHW::Draw() rt->m_32_bits_fmt = m_texture_shuffle || (GSLocalMemory::m_psm[context->FRAME.PSM].bpp != 16); } - // The rectangle of the draw - m_r = GSVector4i(m_vt.m_min.p.xyxy(m_vt.m_max.p)).rintersect(GSVector4i(context->scissor.in)); - { - const GSVector2 up_s = GetTextureScaleFactor(); - const int up_w = static_cast(std::ceil(static_cast(m_r.z) * up_s.x)); - const int up_h = static_cast(std::ceil(static_cast(m_r.w) * up_s.y)); - const int new_w = std::max(up_w, std::max(rt_tex ? rt_tex->GetWidth() : 0, ds_tex ? ds_tex->GetWidth() : 0)); - const int new_h = std::max(up_h, std::max(rt_tex ? rt_tex->GetHeight() : 0, ds_tex ? ds_tex->GetHeight() : 0)); - std::array ts{ rt, ds }; - for (GSTextureCache::Target* t : ts) + // We still need to make sure the dimensions of the targets match. + const GSVector2 up_s(GetTextureScaleFactor()); + const int new_w = std::max(t_size.x, std::max(rt ? rt->m_texture->GetWidth() : 0, ds ? ds->m_texture->GetWidth() : 0)); + const int new_h = std::max(t_size.y, std::max(rt ? rt->m_texture->GetHeight() : 0, ds ? ds->m_texture->GetHeight() : 0)); + + // Ensure draw rect is clamped to framebuffer size. Necessary for updating valid area. + m_r = m_r.rintersect(GSVector4i(0, 0, new_w, new_h)); + + if (rt) { - if (t) - { - // Adjust texture size to fit current draw if necessary. - GSTexture* tex = t->m_texture; - assert(up_s == tex->GetScale()); - const int w = tex->GetWidth(); - const int h = tex->GetHeight(); - if (w != new_w || h != new_h) - { - const bool is_rt = t == rt; - t->m_texture = is_rt ? - g_gs_device->CreateSparseRenderTarget(new_w, new_h, tex->GetFormat()) : - g_gs_device->CreateSparseDepthStencil(new_w, new_h, tex->GetFormat()); - const GSVector4i r{ 0, 0, w, h }; - g_gs_device->CopyRect(tex, t->m_texture, r, 0, 0); - g_gs_device->Recycle(tex); - t->m_texture->SetScale(up_s); - (is_rt ? rt_tex : ds_tex) = t->m_texture; - } - } + pxAssert(rt->m_texture->GetScale() == up_s); + rt->ResizeTexture(new_w, new_h, up_s); + } + if (ds) + { + pxAssert(ds->m_texture->GetScale() == up_s); + ds->ResizeTexture(new_w, new_h, up_s); } } @@ -1708,20 +1628,20 @@ void GSRendererHW::Draw() { s = StringUtil::StdStringFromFormat("%05d_f%lld_rt0_%05x_%s.bmp", s_n, frame, context->FRAME.Block(), psm_str(context->FRAME.PSM)); - if (rt_tex) - rt_tex->Save(m_dump_root + s); + if (rt->m_texture) + rt->m_texture->Save(m_dump_root + s); } if (s_savez && s_n >= s_saven) { s = StringUtil::StdStringFromFormat("%05d_f%lld_rz0_%05x_%s.bmp", s_n, frame, context->ZBUF.Block(), psm_str(context->ZBUF.PSM)); - if (ds_tex) - ds_tex->Save(m_dump_root + s); + if (ds->m_texture) + ds->m_texture->Save(m_dump_root + s); } } - if (m_hacks.m_oi && !(this->*m_hacks.m_oi)(rt_tex, ds_tex, m_src)) + if (m_hacks.m_oi && !(this->*m_hacks.m_oi)(rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, m_src)) { GL_INS("Warning skipping a draw call (%d)", s_n); return; @@ -1746,7 +1666,7 @@ void GSRendererHW::Draw() OI_GsMemClear(); - OI_DoubleHalfClear(rt_tex, ds_tex); + OI_DoubleHalfClear(rt, ds); } } @@ -1801,7 +1721,7 @@ void GSRendererHW::Draw() // - DrawPrims(rt_tex, ds_tex, m_src); + DrawPrims(rt ? rt->m_texture : nullptr, ds ? ds->m_texture : nullptr, m_src); // @@ -1853,16 +1773,16 @@ void GSRendererHW::Draw() { s = StringUtil::StdStringFromFormat("%05d_f%lld_rt1_%05x_%s.bmp", s_n, frame, context->FRAME.Block(), psm_str(context->FRAME.PSM)); - if (rt_tex) - rt_tex->Save(m_dump_root + s); + if (rt) + rt->m_texture->Save(m_dump_root + s); } if (s_savez && s_n >= s_saven) { s = StringUtil::StdStringFromFormat("%05d_f%lld_rz1_%05x_%s.bmp", s_n, frame, context->ZBUF.Block(), psm_str(context->ZBUF.PSM)); - if (ds_tex) - ds_tex->Save(m_dump_root + s); + if (ds) + rt->m_texture->Save(m_dump_root + s); } if (s_savel > 0 && (s_n - s_saven) > s_savel) @@ -3217,10 +3137,6 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc area_out.x, area_out.y, area_out.z, area_out.w); #endif - const GSVector2i& rtsize = ds ? ds->GetSize() : rt->GetSize(); - const GSVector2& rtscale = ds ? ds->GetScale() : rt->GetScale(); - const GSDevice::FeatureSupport features(g_gs_device->Features()); - const bool DATE = m_context->TEST.DATE && m_context->FRAME.PSM != PSM_PSMCT24; bool DATE_PRIMID = false; bool DATE_BARRIER = false; @@ -3250,6 +3166,7 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc // Upscaling hack to avoid various line/grid issues MergeSprite(tex); + const GSDevice::FeatureSupport features(g_gs_device->Features()); if (!features.framebuffer_fetch) m_prim_overlap = PrimitiveOverlap(); else @@ -3418,6 +3335,8 @@ void GSRendererHW::DrawPrims(GSTexture* rt, GSTexture* ds, GSTextureCache::Sourc m_conf.vs.fst = PRIM->FST; // FIXME D3D11 and GL support half pixel center. Code could be easier!!! + const GSVector2i rtsize(m_conf.ds ? m_conf.ds->GetSize() : m_conf.rt->GetSize()); + const GSVector2 rtscale(m_conf.ds ? m_conf.ds->GetScale() : m_conf.rt->GetScale()); const float sx = 2.0f * rtscale.x / (rtsize.x << 4); const float sy = 2.0f * rtscale.y / (rtsize.y << 4); const float ox = (float)(int)m_context->XYOFFSET.OFX; @@ -4177,10 +4096,8 @@ bool GSRendererHW::SwPrimRender() GSRendererHW::Hacks::Hacks() : m_oi_map(m_oi_list) , m_oo_map(m_oo_list) - , m_cu_map(m_cu_list) , m_oi(NULL) , m_oo(NULL) - , m_cu(NULL) { m_oi_list.push_back(HackEntry(CRC::BigMuthaTruckers, CRC::RegionCount, &GSRendererHW::OI_BigMuthaTruckers)); m_oi_list.push_back(HackEntry(CRC::DBZBT2, CRC::RegionCount, &GSRendererHW::OI_DBZBTGames)); @@ -4206,7 +4123,6 @@ void GSRendererHW::Hacks::SetGameCRC(const CRC::Game& game) m_oi = m_oi_map[hash]; m_oo = m_oo_map[hash]; - m_cu = m_cu_map[hash]; if (GSConfig.PointListPalette) { @@ -4220,7 +4136,7 @@ void GSRendererHW::Hacks::SetGameCRC(const CRC::Game& game) // Trick to do a fast clear on the GS // Set frame buffer pointer on the start of the buffer. Set depth buffer pointer on the half buffer // FB + depth write will fill the full buffer. -void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds) +void GSRendererHW::OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCache::Target*& ds) { // Note gs mem clear must be tested before calling this function @@ -4266,20 +4182,35 @@ void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds) GL_INS("OI_DoubleHalfClear:%s: base %x half %x. w_pages %d h_pages %d fbw %d. Color %x", clear_depth ? "depth" : "target", base << 5, half << 5, w_pages, h_pages, m_context->FRAME.FBW, color); - // Commit texture with a factor 2 on the height - GSTexture* t = clear_depth ? ds : rt; - const GSVector4i commitRect = ComputeBoundingBox(t->GetScale(), t->GetSize()); - t->CommitRegion(GSVector2i(commitRect.z, 2 * commitRect.w)); + // Handle the case where the game stacks FBP and ZBP immediately after one another. + // We incorrectly compute the height here, because both the scissor and draw rectangle will only be half + // the height of what's effectively being cleared. Spider-Man 2's shadows are a good test case here: it + // draws the shadow map to a 128x128 texture, but relies on a 1 pixel border around the edge to "cut off" + // the shadows. We cap it to a 256 height, because having a >=512 height framebuffer is very rare, and it + // stops us doubling actual framebuffers unintentionally (very common). + GSTextureCache::Target* t = clear_depth ? ds : rt; + const u32 unscaled_height = static_cast(static_cast(t->m_texture->GetHeight()) / t->m_texture->GetScale().y); + if (unscaled_height == m_context->scissor.in.w && unscaled_height <= 256) + { + t->ResizeTexture(t->m_texture->GetWidth(), t->m_texture->GetHeight() * 2, t->m_texture->GetScale()); + if (clear_depth) + rt = nullptr; + else + ds = nullptr; + + // Feed it back into the height cache. + m_tc->GetTargetHeight(t->m_TEX0.TBP0, t->m_TEX0.TBW, t->m_TEX0.PSM, unscaled_height * 2); + } if (clear_depth) { // Only pure clear are supported for depth ASSERT(color == 0); - g_gs_device->ClearDepth(t); + g_gs_device->ClearDepth(ds->m_texture); } else { - g_gs_device->ClearRenderTarget(t, color); + g_gs_device->ClearRenderTarget(rt->m_texture, color); } } } @@ -4295,10 +4226,10 @@ void GSRendererHW::OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds) // If both buffers are side by side we can expect a fast clear in on-going const u32 color = v[1].RGBAQ.U32[0]; - const GSVector4i commitRect = ComputeBoundingBox(rt->GetScale(), rt->GetSize()); - rt->CommitRegion(GSVector2i(commitRect.z, commitRect.w)); + const GSVector4i commitRect = ComputeBoundingBox(rt->m_texture->GetScale(), rt->m_texture->GetSize()); + rt->m_texture->CommitRegion(GSVector2i(commitRect.z, commitRect.w)); - g_gs_device->ClearRenderTarget(rt, color); + g_gs_device->ClearRenderTarget(rt->m_texture, color); } } @@ -4689,12 +4620,14 @@ bool GSRendererHW::OI_SonicUnleashed(GSTexture* rt, GSTexture* ds, GSTextureCach GL_INS("OI_SonicUnleashed replace draw by a copy"); - GSTextureCache::Target* src = m_tc->LookupTarget(Texture, GetTargetSize(), GSTextureCache::RenderTarget, true); + GSTextureCache::Target* src = m_tc->LookupTarget(Texture, GSVector2i(1, 1), GSTextureCache::RenderTarget, true); - const GSVector2i size = rt->GetSize(); + const GSVector2i rt_size(rt->GetSize()); + const GSVector2i src_size(src->m_texture->GetSize()); + const GSVector2i copy_size(std::min(rt_size.x, src_size.x), std::min(rt_size.y, src_size.y)); - const GSVector4 sRect(0, 0, 1, 1); - const GSVector4 dRect(0, 0, size.x, size.y); + const GSVector4 sRect(0.0f, 0.0f, static_cast(copy_size.x) / static_cast(src_size.x), static_cast(copy_size.y) / static_cast(src_size.y)); + const GSVector4 dRect(0, 0, copy_size.x, copy_size.y); g_gs_device->StretchRect(src->m_texture, sRect, rt, dRect, true, true, true, false); diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index 77644777cf..bddc29f311 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -29,9 +29,6 @@ public: static constexpr int MAX_FRAMEBUFFER_HEIGHT = 1280; private: - int m_width; - int m_height; - static constexpr float SSR_UV_TOLERANCE = 1.0f; #pragma region hacks @@ -43,7 +40,7 @@ private: // Require special argument bool OI_BlitFMV(GSTextureCache::Target* _rt, GSTextureCache::Source* t, const GSVector4i& r_draw); void OI_GsMemClear(); // always on - void OI_DoubleHalfClear(GSTexture* rt, GSTexture* ds); // always on + void OI_DoubleHalfClear(GSTextureCache::Target*& rt, GSTextureCache::Target*& ds); // always on bool OI_BigMuthaTruckers(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); bool OI_DBZBTGames(GSTexture* rt, GSTexture* ds, GSTextureCache::Source* t); @@ -108,16 +105,13 @@ private: std::list> m_oi_list; std::list> m_oo_list; - std::list> m_cu_list; FunctionMap m_oi_map; FunctionMap m_oo_map; - FunctionMap m_cu_map; public: OI_Ptr m_oi; OO_Ptr m_oo; - CU_Ptr m_cu; Hacks(); @@ -183,15 +177,14 @@ public: void SetGameCRC(u32 crc, int options) override; bool CanUpscale() override; int GetUpscaleMultiplier() override; - void SetScaling(); void Lines2Sprites(); void EmulateAtst(GSVector4& FogColor_AREF, u8& atst, const bool pass_2); void ConvertSpriteTextureShuffle(bool& write_ba, bool& read_ba); GSVector4 RealignTargetTextureCoordinate(const GSTextureCache::Source* tex); GSVector4i ComputeBoundingBox(const GSVector2& rtscale, const GSVector2i& rtsize); void MergeSprite(GSTextureCache::Source* tex); - GSVector2 GetTextureScaleFactor(const bool force_upscaling); GSVector2 GetTextureScaleFactor() override; + GSVector2i GetOutputSize(int real_h); GSVector2i GetTargetSize(); void Reset(bool hardware_reset) override; diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp index 8cbe0835dc..e5432a9b93 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.cpp +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.cpp @@ -83,6 +83,7 @@ void GSTextureCache::RemoveAll() m_hash_cache_memory_usage = 0; m_palette_map.Clear(); + m_target_heights.clear(); } GSTextureCache::Source* GSTextureCache::LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette) @@ -416,7 +417,7 @@ GSTextureCache::Source* GSTextureCache::LookupSource(const GIFRegTEX0& TEX0, con GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask, const bool is_frame, const int real_h) { const GSLocalMemory::psm_t& psm_s = GSLocalMemory::m_psm[TEX0.PSM]; - const GSVector2& new_s = g_gs_renderer->GetTextureScaleFactor(); + const GSVector2& new_s = static_cast(g_gs_renderer.get())->GetTextureScaleFactor(); const u32 bp = TEX0.TBP0; GSVector2 res_size{ 0, 0 }; GSVector2i new_size{ 0, 0 }; @@ -619,7 +620,7 @@ GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, con return dst; } -GSTextureCache::Target* GSTextureCache::LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h) +GSTextureCache::Target* GSTextureCache::LookupDisplayTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h) { return LookupTarget(TEX0, size, RenderTarget, true, 0, true, real_h); } @@ -1187,6 +1188,20 @@ bool GSTextureCache::Move(u32 SBP, u32 SBW, u32 SPSM, int sx, int sy, u32 DBP, u const int scaled_w = static_cast(w * scale.x); const int scaled_h = static_cast(h * scale.y); + // Expand the target when we used a more conservative size. + const int required_dh = scaled_dy + scaled_h; + if ((scaled_dx + scaled_w) <= dst->m_texture->GetWidth() && required_dh > dst->m_texture->GetHeight()) + { + const int new_height = dy + h; + if (new_height > GSRendererHW::MAX_FRAMEBUFFER_HEIGHT) + return false; + + const int scaled_new_height = static_cast(static_cast(new_height) * scale.y); + GL_INS("Resize %dx%d target to %dx%d for move", dst->m_texture->GetWidth(), dst->m_texture->GetHeight(), dst->m_texture->GetHeight(), scaled_new_height); + dst->ResizeTexture(dst->m_texture->GetWidth(), scaled_new_height); + GetTargetHeight(DBP, DBW, DPSM, new_height); + } + // Make sure the copy doesn't go out of bounds (it shouldn't). if ((scaled_sx + scaled_w) > src->m_texture->GetWidth() || (scaled_sy + scaled_h) > src->m_texture->GetHeight() || (scaled_dx + scaled_w) > dst->m_texture->GetWidth() || (scaled_dy + scaled_h) > dst->m_texture->GetHeight()) @@ -1230,6 +1245,36 @@ GSTextureCache::Target* GSTextureCache::GetTargetWithSharedBits(u32 BP, u32 PSM) return nullptr; } +u32 GSTextureCache::GetTargetHeight(u32 fbp, u32 fbw, u32 psm, u32 min_height) +{ + TargetHeightElem search = {}; + search.fbp = fbp; + search.fbw = fbw; + search.psm = psm; + search.height = min_height; + + for (auto it = m_target_heights.begin(); it != m_target_heights.end(); ++it) + { + TargetHeightElem& elem = const_cast(*it); + if (elem.bits == search.bits) + { + if (elem.height < min_height) + { + DbgCon.WriteLn("Expand height at %x %u %u from %u to %u", fbp, fbw, psm, elem.height, min_height); + elem.height = min_height; + } + + m_target_heights.MoveFront(it.Index()); + elem.age = 0; + return elem.height; + } + } + + DbgCon.WriteLn("New height at %x %u %u: %u", fbp, fbw, psm, min_height); + m_target_heights.push_front(search); + return min_height; +} + // Hack: remove Target that are strictly included in current rt. Typically uses for FMV // For example, game is rendered at 0x800->0x1000, fmv will be uploaded to 0x0->0x2800 // FIXME In theory, we ought to report the data from the sub rt to the main rt. But let's @@ -1345,6 +1390,20 @@ void GSTextureCache::IncAge() } } } + + for (auto it = m_target_heights.begin(); it != m_target_heights.end();) + { + TargetHeightElem& elem = const_cast(*it); + if (elem.age >= max_rt_age) + { + it = m_target_heights.erase(it); + } + else + { + elem.age++; + ++it; + } + } } //Fixme: Several issues in here. Not handling depth stencil, pitch conversion doesnt work. @@ -1822,7 +1881,7 @@ GSTextureCache::Target* GSTextureCache::CreateTarget(const GIFRegTEX0& TEX0, int t->m_texture = g_gs_device->CreateSparseDepthStencil(w, h, GSTexture::Format::DepthStencil, clear); } - t->m_texture->SetScale(g_gs_renderer->GetTextureScaleFactor()); + t->m_texture->SetScale(static_cast(g_gs_renderer.get())->GetTextureScaleFactor()); m_dst[type].push_front(t); @@ -1999,6 +2058,47 @@ bool GSTextureCache::Surface::Overlaps(u32 bp, u32 bw, u32 psm, const GSVector4i return overlap; } +void GSTextureCache::Surface::ResizeTexture(int new_width, int new_height) +{ + ResizeTexture(new_width, new_height, m_texture->GetScale()); +} + +void GSTextureCache::Surface::ResizeTexture(int new_width, int new_height, GSVector2 new_scale) +{ + const int width = m_texture->GetWidth(); + const int height = m_texture->GetHeight(); + if (width == new_width && height == new_height) + return; + + const bool clear = (new_width > width || new_height > height); + GSTexture* tex = m_texture->IsDepthStencil() ? + g_gs_device->CreateSparseDepthStencil(new_width, new_height, m_texture->GetFormat(), clear) : + g_gs_device->CreateSparseRenderTarget(new_width, new_height, m_texture->GetFormat(), clear); + if (!tex) + { + Console.Error("(GSTextureCache::Surface::ResizeTexture) Failed to allocate %dx%d texture", new_width, new_height); + return; + } + + tex->SetScale(new_scale); + + const GSVector4i rc(0, 0, std::min(width, new_width), std::min(height, new_height)); + if (tex->IsDepthStencil()) + { + // Can't do partial copies in DirectX for depth textures, and it's probably not ideal in other + // APIs either. So use a fullscreen quad setting depth instead. + g_gs_device->StretchRect(m_texture, tex, GSVector4(rc), ShaderConvert::DEPTH_COPY, false); + } + else + { + // Fast memcpy()-like path for color targets. + g_gs_device->CopyRect(m_texture, tex, rc, 0, 0); + } + + g_gs_device->Recycle(m_texture); + m_texture = tex; +} + // GSTextureCache::Source GSTextureCache::Source::Source(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, bool dummy_container) @@ -2410,7 +2510,10 @@ void GSTextureCache::Target::UpdateIfDirtyIntersects(const GSVector4i& rc) void GSTextureCache::Target::UpdateValidity(const GSVector4i& rect) { - m_valid = m_valid.runion(rect); + if (m_valid.eq(GSVector4i::zero())) + m_valid = rect; + else + m_valid = m_valid.runion(rect); // Block of the bottom right texel of the validity rectangle, last valid block of the texture m_end_block = GSLocalMemory::m_psm[m_TEX0.PSM].info.bn(m_valid.z - 1, m_valid.w - 1, m_TEX0.TBP0, m_TEX0.TBW); // Valid only for color formats diff --git a/pcsx2/GS/Renderers/HW/GSTextureCache.h b/pcsx2/GS/Renderers/HW/GSTextureCache.h index 63fe66cee9..6f5756fbfd 100644 --- a/pcsx2/GS/Renderers/HW/GSTextureCache.h +++ b/pcsx2/GS/Renderers/HW/GSTextureCache.h @@ -92,6 +92,9 @@ public: void UpdateAge(); bool Inside(u32 bp, u32 bw, u32 psm, const GSVector4i& rect); bool Overlaps(u32 bp, u32 bw, u32 psm, const GSVector4i& rect); + + void ResizeTexture(int new_width, int new_height); + void ResizeTexture(int new_width, int new_height, GSVector2 new_scale); }; struct PaletteKey @@ -243,6 +246,25 @@ public: void RemoveAt(Source* s); }; + struct TargetHeightElem + { + union + { + u32 bits; + + struct + { + u32 fbp : 9; + u32 fbw : 6; + u32 psm : 6; + u32 pad : 11; + }; + }; + + u32 height; + u32 age; + }; + struct SurfaceOffsetKeyElem { u32 psm; @@ -278,6 +300,7 @@ protected: std::unordered_map m_hash_cache; u64 m_hash_cache_memory_usage = 0; FastList m_dst[2]; + FastList m_target_heights; static u8* m_temp; constexpr static size_t S_SURFACE_OFFSET_CACHE_MAX_SIZE = std::numeric_limits::max(); std::unordered_map m_surface_offset_cache; @@ -309,12 +332,14 @@ public: Source* LookupDepthSource(const GIFRegTEX0& TEX0, const GIFRegTEXA& TEXA, const GSVector4i& r, bool palette = false); Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, int type, bool used, u32 fbmask = 0, const bool is_frame = false, const int real_h = 0); - Target* LookupTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h); + Target* LookupDisplayTarget(const GIFRegTEX0& TEX0, const GSVector2i& size, const int real_h); /// Looks up a target in the cache, and only returns it if the BP/BW/PSM match exactly. Target* GetExactTarget(u32 BP, u32 BW, u32 PSM) const; Target* GetTargetWithSharedBits(u32 BP, u32 PSM) const; + u32 GetTargetHeight(u32 fbp, u32 fbw, u32 psm, u32 min_height); + void InvalidateVideoMemType(int type, u32 bp); void InvalidateVideoMemSubTarget(GSTextureCache::Target* rt); void InvalidateVideoMem(const GSOffset& off, const GSVector4i& r, bool target = true); diff --git a/pcsx2/GS/Window/GSSetting.cpp b/pcsx2/GS/Window/GSSetting.cpp index e454055ec4..ba80f6896d 100644 --- a/pcsx2/GS/Window/GSSetting.cpp +++ b/pcsx2/GS/Window/GSSetting.cpp @@ -191,12 +191,6 @@ const char* dialog_message(int ID, bool* updateText) case IDC_DISABLE_PARTIAL_TC_INV: return cvtString("By default, the texture cache handles partial invalidations. Unfortunately it is very costly to compute CPU wise." "\n\nThis hack replaces the partial invalidation with a complete deletion of the texture to reduce the CPU load.\n\nIt helps snowblind engine games."); - case IDC_CONSERVATIVE_FB: - return cvtString("Disabled: Reserves a larger framebuffer to prevent FMV flickers.\n" - "Increases GPU/memory requirements.\n" - "Disabling this can amplify stuttering due to low RAM/VRAM.\n\n" - "Note: It should be enabled for Armored Core, Destroy All Humans, Gran Turismo and possibly others.\n" - "This option does not improve the graphics or the FPS."); case IDC_DITHERING: return cvtString("In the PS2's case, it reduces banding between colors and improves the perceived color depth.\n" "In the PS1's case, it was used more aggressively due to 16-bit colour.\n" diff --git a/pcsx2/GS/Window/GSSetting.h b/pcsx2/GS/Window/GSSetting.h index 048a60c4ee..83e7b83ef5 100644 --- a/pcsx2/GS/Window/GSSetting.h +++ b/pcsx2/GS/Window/GSSetting.h @@ -50,7 +50,6 @@ enum IDC_PRELOAD_TEXTURES, IDC_ACCURATE_DATE, IDC_PALTEX, - IDC_CONSERVATIVE_FB, IDC_AFCOMBO, IDC_DITHERING, IDC_MIPMAP_HW, diff --git a/pcsx2/GS/Window/GSwxDialog.cpp b/pcsx2/GS/Window/GSwxDialog.cpp index 90bfdab8ae..9f26df950b 100644 --- a/pcsx2/GS/Window/GSwxDialog.cpp +++ b/pcsx2/GS/Window/GSwxDialog.cpp @@ -285,7 +285,6 @@ RendererTab::RendererTab(wxWindow* parent) auto* hw_checks_box = new wxWrapSizer(wxHORIZONTAL); m_ui.addCheckBox(hw_checks_box, "Accurate Destination Alpha Test", "accurate_date", IDC_ACCURATE_DATE, hw_prereq); - m_ui.addCheckBox(hw_checks_box, "Conservative Buffer Allocation", "conservative_framebuffer", IDC_CONSERVATIVE_FB, upscale_prereq); auto* paltex_prereq = m_ui.addCheckBox(hw_checks_box, "GPU Palette Conversion", "paltex", IDC_PALTEX, hw_prereq); auto aniso_prereq = [this, paltex_prereq]{ return m_is_hardware && paltex_prereq->GetValue() == false; }; diff --git a/pcsx2/GameDatabase.cpp b/pcsx2/GameDatabase.cpp index d812363b18..3e4492e3db 100644 --- a/pcsx2/GameDatabase.cpp +++ b/pcsx2/GameDatabase.cpp @@ -270,7 +270,6 @@ void GameDatabase::parseAndInsert(const std::string_view& serial, const c4::yml: static const char* s_gs_hw_fix_names[] = { "autoFlush", - "conservativeFramebuffer", "cpuFramebufferConversion", "disableDepthSupport", "wrapGSMem", @@ -317,7 +316,6 @@ bool GameDatabaseSchema::isUserHackHWFix(GSHWFixId id) case GSHWFixId::Deinterlace: case GSHWFixId::Mipmap: case GSHWFixId::TexturePreloading: - case GSHWFixId::ConservativeFramebuffer: case GSHWFixId::PointListPalette: return false; @@ -446,9 +444,6 @@ bool GameDatabaseSchema::GameEntry::configMatchesHWFix(const Pcsx2Config::GSOpti case GSHWFixId::AutoFlush: return (static_cast(config.UserHacks_AutoFlush) == value); - case GSHWFixId::ConservativeFramebuffer: - return (static_cast(config.ConservativeFramebuffer) == value); - case GSHWFixId::CPUFramebufferConversion: return (static_cast(config.UserHacks_CPUFBConversion) == value); @@ -542,10 +537,6 @@ u32 GameDatabaseSchema::GameEntry::applyGSHardwareFixes(Pcsx2Config::GSOptions& config.UserHacks_AutoFlush = (value > 0); break; - case GSHWFixId::ConservativeFramebuffer: - config.ConservativeFramebuffer = (value > 0); - break; - case GSHWFixId::CPUFramebufferConversion: config.UserHacks_CPUFBConversion = (value > 0); break; diff --git a/pcsx2/GameDatabase.h b/pcsx2/GameDatabase.h index 04f4fed868..0432d2a6ac 100644 --- a/pcsx2/GameDatabase.h +++ b/pcsx2/GameDatabase.h @@ -60,7 +60,6 @@ namespace GameDatabaseSchema { // boolean settings AutoFlush, - ConservativeFramebuffer, CPUFramebufferConversion, DisableDepthSupport, WrapGSMem, diff --git a/pcsx2/Pcsx2Config.cpp b/pcsx2/Pcsx2Config.cpp index 0a08f11239..18f922fc60 100644 --- a/pcsx2/Pcsx2Config.cpp +++ b/pcsx2/Pcsx2Config.cpp @@ -325,7 +325,6 @@ Pcsx2Config::GSOptions::GSOptions() HWDisableReadbacks = false; AccurateDATE = true; GPUPaletteConversion = false; - ConservativeFramebuffer = true; AutoFlushSW = true; PreloadFrameWithGSData = false; WrapGSMem = false; @@ -546,7 +545,6 @@ void Pcsx2Config::GSOptions::ReloadIniSettings() GSSettingBool(HWDisableReadbacks); GSSettingBoolEx(AccurateDATE, "accurate_date"); GSSettingBoolEx(GPUPaletteConversion, "paltex"); - GSSettingBoolEx(ConservativeFramebuffer, "conservative_framebuffer"); GSSettingBoolEx(AutoFlushSW, "autoflush_sw"); GSSettingBoolEx(PreloadFrameWithGSData, "preload_frame_with_gs_data"); GSSettingBoolEx(WrapGSMem, "wrap_gs_mem");