diff --git a/pcsx2/GS/GS.cpp b/pcsx2/GS/GS.cpp index e457236887..33afa776bc 100644 --- a/pcsx2/GS/GS.cpp +++ b/pcsx2/GS/GS.cpp @@ -516,7 +516,7 @@ void GSvsync(u32 field, bool registers_written) { try { - g_gs_renderer->VSync(field, registers_written); + g_gs_renderer->VSync(field, registers_written, g_gs_renderer->IsIdleFrame()); } catch (GSRecoverableError) { diff --git a/pcsx2/GS/Renderers/Common/GSDevice.cpp b/pcsx2/GS/Renderers/Common/GSDevice.cpp index 20bf175fdb..c14e875ca8 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.cpp +++ b/pcsx2/GS/Renderers/Common/GSDevice.cpp @@ -91,7 +91,7 @@ GSDevice::GSDevice() = default; GSDevice::~GSDevice() { // should've been cleaned up in Destroy() - pxAssert(m_pool.empty() && !m_merge && !m_weavebob && !m_blend && !m_mad && !m_target_tmp && !m_cas); + pxAssert(m_pool[0].empty() && m_pool[1].empty() && !m_merge && !m_weavebob && !m_blend && !m_mad && !m_target_tmp && !m_cas); } const char* GSDevice::RenderAPIToString(RenderAPI api) @@ -248,11 +248,12 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i { const GSVector2i size(width, height); const bool prefer_new_texture = (m_features.prefer_new_textures && type == GSTexture::Type::Texture && !prefer_reuse); + FastList& pool = m_pool[type != GSTexture::Type::Texture]; GSTexture* t = nullptr; - auto fallback = m_pool.end(); + auto fallback = pool.end(); - for (auto i = m_pool.begin(); i != m_pool.end(); ++i) + for (auto i = pool.begin(); i != pool.end(); ++i) { t = *i; @@ -263,10 +264,10 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i if (!prefer_new_texture || t->GetLastFrameUsed() != m_frame) { m_pool_memory_usage -= t->GetMemUsage(); - m_pool.erase(i); + pool.erase(i); break; } - else if (fallback == m_pool.end()) + else if (fallback == pool.end()) { fallback = i; } @@ -277,11 +278,12 @@ GSTexture* GSDevice::FetchSurface(GSTexture::Type type, int width, int height, i if (!t) { - if (m_pool.size() >= MAX_POOLED_TEXTURES && fallback != m_pool.end()) + if (pool.size() >= ((type == GSTexture::Type::Texture) ? MAX_POOLED_TEXTURES : MAX_POOLED_TARGETS) && + fallback != pool.end()) { t = *fallback; m_pool_memory_usage -= t->GetMemUsage(); - m_pool.erase(fallback); + pool.erase(fallback); } else { @@ -323,17 +325,24 @@ void GSDevice::Recycle(GSTexture* t) t->SetLastFrameUsed(m_frame); - m_pool.push_front(t); + FastList& pool = m_pool[!t->IsTexture()]; + pool.push_front(t); m_pool_memory_usage += t->GetMemUsage(); - //printf("%d\n",m_pool.size()); - - while (m_pool.size() > MAX_POOLED_TEXTURES) + const u32 max_size = t->IsTexture() ? MAX_POOLED_TEXTURES : MAX_POOLED_TARGETS; + const u32 max_age = t->IsTexture() ? MAX_TEXTURE_AGE : MAX_TARGET_AGE; + while (pool.size() > max_size) { - m_pool_memory_usage -= m_pool.back()->GetMemUsage(); - delete m_pool.back(); + // Don't toss when the texture was last used in this frame. + // Because we're going to need to keep it alive anyway. + GSTexture* back = pool.back(); + if ((m_frame - back->GetLastFrameUsed()) < max_age) + break; - m_pool.pop_back(); + m_pool_memory_usage -= back->GetMemUsage(); + delete back; + + pool.pop_back(); } } @@ -347,20 +356,33 @@ void GSDevice::AgePool() { m_frame++; - while (m_pool.size() > 40 && m_frame - m_pool.back()->GetLastFrameUsed() > 10) + // Toss out textures when they're not too-recently used. + for (u32 pool_idx = 0; pool_idx < m_pool.size(); pool_idx++) { - m_pool_memory_usage -= m_pool.back()->GetMemUsage(); - delete m_pool.back(); + const u32 max_age = (pool_idx == 0) ? MAX_TEXTURE_AGE : MAX_TARGET_AGE; + FastList& pool = m_pool[pool_idx]; + while (!pool.empty()) + { + GSTexture* back = pool.back(); + if ((m_frame - back->GetLastFrameUsed()) < max_age) + break; - m_pool.pop_back(); + m_pool_memory_usage -= back->GetMemUsage(); + delete back; + + pool.pop_back(); + } } } void GSDevice::PurgePool() { - for (auto t : m_pool) - delete t; - m_pool.clear(); + for (FastList& pool : m_pool) + { + for (GSTexture* t : pool) + delete t; + pool.clear(); + } m_pool_memory_usage = 0; } diff --git a/pcsx2/GS/Renderers/Common/GSDevice.h b/pcsx2/GS/Renderers/Common/GSDevice.h index c365fe3ccd..6f6ee0ab4c 100644 --- a/pcsx2/GS/Renderers/Common/GSDevice.h +++ b/pcsx2/GS/Renderers/Common/GSDevice.h @@ -738,18 +738,21 @@ public: // clang-format on private: - FastList m_pool; + std::array, 2> m_pool; // [texture, target] u64 m_pool_memory_usage = 0; static const std::array m_blendMap; static const std::array m_replaceDualSrcBlendMap; protected: - static constexpr int NUM_INTERLACE_SHADERS = 5; + static constexpr int NUM_INTERLACE_SHADERS = 5; static constexpr float MAD_SENSITIVITY = 0.08f; - static constexpr u32 MAX_POOLED_TEXTURES = 300; - static constexpr u32 NUM_CAS_CONSTANTS = 12; // 8 plus src offset x/y, 16 byte alignment - static constexpr u32 EXPAND_BUFFER_SIZE = sizeof(u16) * 65532 * 6; + static constexpr u32 MAX_POOLED_TARGETS = 300; + static constexpr u32 MAX_TARGET_AGE = 20; + static constexpr u32 MAX_POOLED_TEXTURES = 300; + static constexpr u32 MAX_TEXTURE_AGE = 10; + static constexpr u32 NUM_CAS_CONSTANTS = 12; // 8 plus src offset x/y, 16 byte alignment + static constexpr u32 EXPAND_BUFFER_SIZE = sizeof(u16) * 65532 * 6; WindowInfo m_window_info; VsyncMode m_vsync_mode = VsyncMode::Off; @@ -806,7 +809,6 @@ public: /// Generates a fixed index buffer for expanding points and sprites. Buffer is assumed to be at least EXPAND_BUFFER_SIZE in size. static void GenerateExpansionIndexBuffer(void* buffer); - __fi unsigned int GetFrameNumber() const { return m_frame; } __fi u64 GetPoolMemoryUsage() const { return m_pool_memory_usage; } __fi FeatureSupport Features() const { return m_features; } diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.cpp b/pcsx2/GS/Renderers/Common/GSRenderer.cpp index f214bd2953..df037d2dea 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.cpp +++ b/pcsx2/GS/Renderers/Common/GSRenderer.cpp @@ -527,7 +527,7 @@ void GSRenderer::EndPresentFrame() ImGuiManager::NewFrame(); } -void GSRenderer::VSync(u32 field, bool registers_written) +void GSRenderer::VSync(u32 field, bool registers_written, bool idle_frame) { Flush(GSFlushReason::VSYNC); @@ -569,6 +569,9 @@ void GSRenderer::VSync(u32 field, bool registers_written) const bool blank_frame = !Merge(field); + m_last_draw_n = s_n; + m_last_transfer_n = s_transfer_n; + if (skip_frame) { if (BeginPresentFrame(true)) @@ -578,7 +581,8 @@ void GSRenderer::VSync(u32 field, bool registers_written) return; } - g_gs_device->AgePool(); + if (!idle_frame) + g_gs_device->AgePool(); g_perfmon.EndFrame(); if ((g_perfmon.GetFrame() & 0x1f) == 0) @@ -898,6 +902,11 @@ GSTexture* GSRenderer::LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2 return nullptr; } +bool GSRenderer::IsIdleFrame() const +{ + return (m_last_draw_n == s_n && m_last_transfer_n == s_transfer_n); +} + bool GSRenderer::SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders, u32* width, u32* height, std::vector* pixels) { diff --git a/pcsx2/GS/Renderers/Common/GSRenderer.h b/pcsx2/GS/Renderers/Common/GSRenderer.h index 8496738781..0a9d166893 100644 --- a/pcsx2/GS/Renderers/Common/GSRenderer.h +++ b/pcsx2/GS/Renderers/Common/GSRenderer.h @@ -32,6 +32,10 @@ private: u32 m_dump_frames = 0; u32 m_skipped_duplicate_frames = 0; + // Tracking draw counters for idle frame detection. + int m_last_draw_n = 0; + int m_last_transfer_n = 0; + protected: GSVector2i m_real_size{0, 0}; bool m_texture_shuffle = false; @@ -48,7 +52,7 @@ public: virtual void Destroy(); - virtual void VSync(u32 field, bool registers_written); + virtual void VSync(u32 field, bool registers_written, bool idle_frame); virtual bool CanUpscale() { return false; } virtual float GetUpscaleMultiplier() { return 1.0f; } virtual float GetTextureScaleFactor() { return 1.0f; } @@ -57,6 +61,8 @@ public: virtual GSTexture* LookupPaletteSource(u32 CBP, u32 CPSM, u32 CBW, GSVector2i& offset, float* scale, const GSVector2i& size); + bool IsIdleFrame() const; + bool SaveSnapshotToMemory(u32 window_width, u32 window_height, bool apply_aspect, bool crop_borders, u32* width, u32* height, std::vector* pixels); diff --git a/pcsx2/GS/Renderers/Common/GSTexture.h b/pcsx2/GS/Renderers/Common/GSTexture.h index 4074bee6d2..9131039a9a 100644 --- a/pcsx2/GS/Renderers/Common/GSTexture.h +++ b/pcsx2/GS/Renderers/Common/GSTexture.h @@ -126,6 +126,10 @@ public: { return (m_type == Type::DepthStencil); } + __fi bool IsTexture() const + { + return (m_type == Type::Texture); + } __fi State GetState() const { return m_state; } __fi void SetState(State state) { m_state = state; } diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp index ba87b64012..e81ab85ca7 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.cpp +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.cpp @@ -112,7 +112,7 @@ void GSRendererHW::UpdateSettings(const Pcsx2Config::GSOptions& old_config) SetTCOffset(); } -void GSRendererHW::VSync(u32 field, bool registers_written) +void GSRendererHW::VSync(u32 field, bool registers_written, bool idle_frame) { if (m_force_preload > 0) { @@ -138,7 +138,7 @@ void GSRendererHW::VSync(u32 field, bool registers_written) // Don't age the texture cache when no draws or EE writes have occurred. // Xenosaga needs its targets kept around while it's loading, because it uses them for a fade transition. - if (m_last_draw_n == s_n && m_last_transfer_n == s_transfer_n) + if (idle_frame) { GL_INS("No draws or transfers, not aging TC"); } @@ -147,10 +147,7 @@ void GSRendererHW::VSync(u32 field, bool registers_written) g_texture_cache->IncAge(); } - m_last_draw_n = s_n + 1; // +1 for vsync - m_last_transfer_n = s_transfer_n; - - GSRenderer::VSync(field, registers_written); + GSRenderer::VSync(field, registers_written, idle_frame); if (g_texture_cache->GetHashCacheMemoryUsage() > 1024 * 1024 * 1024) { diff --git a/pcsx2/GS/Renderers/HW/GSRendererHW.h b/pcsx2/GS/Renderers/HW/GSRendererHW.h index d3bfdaed84..12c6c280a2 100644 --- a/pcsx2/GS/Renderers/HW/GSRendererHW.h +++ b/pcsx2/GS/Renderers/HW/GSRendererHW.h @@ -149,10 +149,6 @@ private: std::unique_ptr m_sw_texture[7 + 1]; std::unique_ptr> m_sw_rasterizer; - // Tracking draw counters for idle frame detection. - int m_last_draw_n = 0; - int m_last_transfer_n = 0; - public: GSRendererHW(); virtual ~GSRendererHW() override; @@ -178,7 +174,7 @@ public: void Reset(bool hardware_reset) override; void UpdateSettings(const Pcsx2Config::GSOptions& old_config) override; - void VSync(u32 field, bool registers_written) override; + void VSync(u32 field, bool registers_written, bool idle_frame) override; GSTexture* GetOutput(int i, float& scale, int& y_offset) override; GSTexture* GetFeedbackOutput(float& scale) override; diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp index 2de43a19b0..983032cdf1 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.cpp +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.cpp @@ -78,7 +78,7 @@ void GSRendererSW::Destroy() m_output = nullptr; } -void GSRendererSW::VSync(u32 field, bool registers_written) +void GSRendererSW::VSync(u32 field, bool registers_written, bool idle_frame) { Sync(0); // IncAge might delete a cached texture in use @@ -99,7 +99,7 @@ void GSRendererSW::VSync(u32 field, bool registers_written) // */ - GSRenderer::VSync(field, registers_written); + GSRenderer::VSync(field, registers_written, idle_frame); m_tc->IncAge(); diff --git a/pcsx2/GS/Renderers/SW/GSRendererSW.h b/pcsx2/GS/Renderers/SW/GSRendererSW.h index b9d12341d8..c8934b6692 100644 --- a/pcsx2/GS/Renderers/SW/GSRendererSW.h +++ b/pcsx2/GS/Renderers/SW/GSRendererSW.h @@ -73,7 +73,7 @@ protected: GSVector4i m_dimx[8] = {}; void Reset(bool hardware_reset) override; - void VSync(u32 field, bool registers_written) override; + void VSync(u32 field, bool registers_written, bool idle_frame) override; GSTexture* GetOutput(int i, float& scale, int& y_offset) override; GSTexture* GetFeedbackOutput(float& scale) override;