GS: Rework texture pooling behavior

- Split into texture/target pools.
 - Keep textures around when they're used recently regardless of size
   (saves work in the backend/driver).
 - Don't boot textures out of the pool when it's an idle frame.
This commit is contained in:
Stenzek 2023-04-19 22:49:03 +10:00 committed by refractionpcsx2
parent a4e99366fb
commit 0ab6eb6587
10 changed files with 81 additions and 45 deletions

View File

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

View File

@ -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<GSTexture*>& 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<GSTexture*>& 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<GSTexture*>& 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<GSTexture*>& pool : m_pool)
{
for (GSTexture* t : pool)
delete t;
pool.clear();
}
m_pool_memory_usage = 0;
}

View File

@ -738,18 +738,21 @@ public:
// clang-format on
private:
FastList<GSTexture*> m_pool;
std::array<FastList<GSTexture*>, 2> m_pool; // [texture, target]
u64 m_pool_memory_usage = 0;
static const std::array<HWBlend, 3*3*3*3> m_blendMap;
static const std::array<u8, 16> 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; }

View File

@ -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<u32>* pixels)
{

View File

@ -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<u32>* pixels);

View File

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

View File

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

View File

@ -149,10 +149,6 @@ private:
std::unique_ptr<GSTextureCacheSW::Texture> m_sw_texture[7 + 1];
std::unique_ptr<GSVirtualAlignedClass<32>> 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;

View File

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

View File

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