Reduces memory peak during window resize

This commit is contained in:
Vladislav Belov
2026-09-10 15:38:55 +02:00
parent 63e05d3c65
commit 4ae50fd4a4
15 changed files with 145 additions and 55 deletions
+6 -4
View File
@@ -875,11 +875,13 @@ void CVideoMode::UpdateRenderer(int w, int h)
SViewPort vp = { 0, 0, w, h };
// We need to separate destroy and create of objects to be able to flush
// all queued to destroy resources during WaitUntilIdle.
if (CRenderer::IsInitialised())
g_Renderer.DestroyViewSizeDependentObjects();
if (g_VideoMode.m_BackendDevice)
{
// TODO: implement freeing window size dependent resources before
// waiting to reduce a memory spike.
// We need to wait until we can destroy the swapchain because it
// might be in use.
g_VideoMode.m_BackendDevice->WaitUntilIdle();
@@ -888,7 +890,7 @@ void CVideoMode::UpdateRenderer(int w, int h)
}
if (CRenderer::IsInitialised())
g_Renderer.Resize(w, h);
g_Renderer.CreateViewSizeDependentObjects(w, h);
if (g_GUI)
g_GUI->UpdateResolution();
+18 -12
View File
@@ -103,11 +103,6 @@ CPostprocManager::CPostprocManager(Renderer::Backend::IDevice* device)
{
}
CPostprocManager::~CPostprocManager()
{
Cleanup();
}
bool CPostprocManager::IsEnabled() const
{
const bool isDepthStencilFormatPresent =
@@ -143,7 +138,7 @@ void CPostprocManager::Cleanup()
}
}
void CPostprocManager::Initialize()
void CPostprocManager::Initialize(const uint32_t width, const uint32_t height)
{
if (m_IsInitialized)
return;
@@ -168,8 +163,9 @@ void CPostprocManager::Initialize()
std::back_inserter(m_AllowedSampleCounts),
[maxSamples](const uint32_t sampleCount) { return sampleCount <= maxSamples; } );
// The screen size starts out correct and then must be updated with Resize()
RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
// The screen size starts out correct and then must be updated with
// Create/DestroyViewSizeDependentObjects()
RecalculateSize(width, height);
RecreateBuffers();
m_IsInitialized = true;
@@ -229,15 +225,27 @@ void CPostprocManager::InitializePBR()
}
}
void CPostprocManager::Resize()
void CPostprocManager::Recreate()
{
RecalculateSize(g_Renderer.GetWidth(), g_Renderer.GetHeight());
DestroyViewSizeDependentObjects();
CreateViewSizeDependentObjects(m_UnscaledWidth, m_UnscaledHeight);
}
void CPostprocManager::CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height)
{
RecalculateSize(width, height);
// If the buffers were intialized, recreate them to the new size.
if (m_IsInitialized)
RecreateBuffers();
}
void CPostprocManager::DestroyViewSizeDependentObjects()
{
Cleanup();
DestroyMultisampleBuffer();
}
void CPostprocManager::RecreateBuffers()
{
Cleanup();
@@ -986,8 +994,6 @@ void CPostprocManager::CreateMultisampleBuffer()
void CPostprocManager::DestroyMultisampleBuffer()
{
if (m_UsingMultisampleBuffer)
return;
m_MultisampleFramebuffer.reset();
m_MultisampleColorTex.reset();
m_MultisampleDepthTex.reset();
+6 -4
View File
@@ -37,17 +37,19 @@ class CPostprocManager
{
public:
CPostprocManager(Renderer::Backend::IDevice* device);
~CPostprocManager();
// Returns true if the the manager can be used.
bool IsEnabled() const;
// Create all buffers/textures in GPU memory and set default effect.
// @note Must be called before using in the renderer. May be called multiple times.
void Initialize();
void Initialize(const uint32_t width, const uint32_t height);
// Update the size of the screen
void Resize();
// Recreate needed resources. Useful to call on settings changes.
void Recreate();
void CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height);
void DestroyViewSizeDependentObjects();
// Returns a list of xml files found in shaders/effects/postproc.
static std::vector<CStrW> GetPostEffects();
+22 -9
View File
@@ -476,7 +476,7 @@ void CRenderer::ReloadShaders()
m->ShadersDirty = false;
}
bool CRenderer::Open(int width, int height)
bool CRenderer::Open(const uint32_t width, const uint32_t height)
{
m->IsOpen = true;
@@ -487,21 +487,27 @@ bool CRenderer::Open(int width, int height)
m->debugRenderer.Initialize();
if (m->postprocManager.IsEnabled())
m->postprocManager.Initialize();
m->postprocManager.Initialize(width, height);
m->sceneRenderer.Initialize();
m->sceneRenderer.Initialize(width, height);
return true;
}
void CRenderer::Resize(int width, int height)
void CRenderer::CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height)
{
m_Width = width;
m_Height = height;
m->postprocManager.Resize();
m->postprocManager.CreateViewSizeDependentObjects(width, height);
m->sceneRenderer.Resize(width, height);
m->sceneRenderer.CreateViewSizeDependentObjects(width, height);
}
void CRenderer::DestroyViewSizeDependentObjects()
{
m->sceneRenderer.DestroyViewSizeDependentObjects();
m->postprocManager.DestroyViewSizeDependentObjects();
}
bool CRenderer::ShouldRender() const
@@ -588,7 +594,7 @@ void CRenderer::RenderFrameImpl(
CPostprocManager& postprocManager{GetPostprocManager()};
if (postprocManager.IsEnabled())
{
postprocManager.Initialize();
postprocManager.Initialize(m_Width, m_Height);
RenderGameWithPostProcessingAndGUI(
swapChain, gameView, postprocManager, renderGUI, renderLogger);
}
@@ -897,7 +903,10 @@ void CRenderer::RenderBigScreenShot(const bool needsPresent)
// Resize various things so that the sizes and aspect ratios are correct
{
g_Renderer.Resize(tileWidth, tileHeight);
DestroyViewSizeDependentObjects();
m->device->WaitUntilIdle();
CreateViewSizeDependentObjects(tileWidth, tileHeight);
SViewPort vp = { 0, 0, tileWidth, tileHeight };
g_Game->GetView()->SetViewport(vp);
}
@@ -948,8 +957,12 @@ void CRenderer::RenderBigScreenShot(const bool needsPresent)
// Restore the viewport settings
{
g_Renderer.Resize(g_VideoMode.GetWindowWidth(), g_VideoMode.GetWindowHeight());
DestroyViewSizeDependentObjects();
m->device->WaitUntilIdle();
SViewPort vp = { 0, 0, g_VideoMode.GetWindowWidth(), g_VideoMode.GetWindowHeight() };
CreateViewSizeDependentObjects(vp.m_Width, vp.m_Height);
g_Game->GetView()->SetViewport(vp);
g_Game->GetView()->SetCamera(oldCamera);
}
+3 -3
View File
@@ -82,10 +82,10 @@ public:
~CRenderer();
// open up the renderer: performs any necessary initialisation
bool Open(int width, int height);
bool Open(const uint32_t width, const uint32_t height);
// resize renderer view
void Resize(int width, int height);
void CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height);
void DestroyViewSizeDependentObjects();
// return view width
int GetWidth() const { return m_Width; }
+1 -1
View File
@@ -170,7 +170,7 @@ void CRenderingOptions::ReadConfigAndSetupHooks()
m_ConfigHooks->Setup("renderer.scale", []()
{
if (CRenderer::IsInitialised())
g_Renderer.GetPostprocManager().Resize();
g_Renderer.GetPostprocManager().Recreate();
});
m_ConfigHooks->Setup("renderer.upscale.technique", []()
+13 -5
View File
@@ -365,22 +365,30 @@ void CSceneRenderer::ReloadShaders([[maybe_unused]] Renderer::Backend::IDevice*
}
}
void CSceneRenderer::Initialize()
void CSceneRenderer::Initialize(const uint32_t width, const uint32_t height)
{
// Let component renderers perform one-time initialization after graphics capabilities and
// the shader path have been determined.
m->waterManager.Initialize();
m->terrainRenderer.Initialize();
m->overlayRenderer.Initialize();
CreateViewSizeDependentObjects(width, height);
}
// resize renderer view
void CSceneRenderer::Resize(int /*width*/, int /*height*/)
void CSceneRenderer::CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height)
{
// need to recreate the shadow map object to resize the shadow texture
m->shadow.RecreateTexture();
m->shadow.CreateViewSizeDependentObjects(width, height);
m->waterManager.RecreateOrLoadTexturesIfNeeded();
m->waterManager.CreateViewSizeDependentObjects(width, height);
}
void CSceneRenderer::DestroyViewSizeDependentObjects()
{
m->waterManager.DestroyViewSizeDependentObjects();
m->shadow.DestroyViewSizeDependentObjects();
}
void CSceneRenderer::BeginFrame()
+4 -2
View File
@@ -64,8 +64,10 @@ public:
CSceneRenderer(Renderer::Backend::IDevice* device);
~CSceneRenderer();
void Initialize();
void Resize(int width, int height);
void Initialize(const uint32_t width, const uint32_t height);
void CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height);
void DestroyViewSizeDependentObjects();
void BeginFrame();
void EndFrame();
+16 -12
View File
@@ -131,7 +131,7 @@ struct ShadowMapInternals
CCamera SavedViewCamera;
void CalculateShadowMatrices(const int cascade);
void CreateTexture();
void CreateTexture(const uint32_t width, const uint32_t height);
void UpdateCascadesParameters();
};
@@ -227,25 +227,29 @@ ShadowMap::~ShadowMap()
delete m;
}
// Force the texture/buffer/etc to be recreated, particularly when the renderer's
// size has changed
void ShadowMap::RecreateTexture()
void ShadowMap::CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height)
{
m->UpdateCascadesParameters();
m->CreateTexture(width, height);
}
void ShadowMap::DestroyViewSizeDependentObjects()
{
m->Framebuffer.reset();
m->Texture.reset();
m->DummyTexture.reset();
}
m->UpdateCascadesParameters();
// (Texture will be constructed in next SetupFrame)
void ShadowMap::RecreateTexture()
{
DestroyViewSizeDependentObjects();
CreateViewSizeDependentObjects(g_Renderer.GetWidth(), g_Renderer.GetHeight());
}
// SetupFrame: camera and light direction for this frame
void ShadowMap::SetupFrame(const CCamera& camera, const CVector3D& lightdir)
{
if (!m->Texture)
m->CreateTexture();
CVector3D x(0, 1, 0), eyepos;
CVector3D z = lightdir;
@@ -486,7 +490,7 @@ void ShadowMapInternals::CalculateShadowMatrices(const int cascade)
}
// Create the shadow map
void ShadowMapInternals::CreateTexture()
void ShadowMapInternals::CreateTexture(const uint32_t width, const uint32_t height)
{
// Cleanup
Framebuffer.reset();
@@ -509,7 +513,7 @@ void ShadowMapInternals::CreateTexture()
break;
// Ultra
case 2:
shadowMapSize = std::max(round_up_to_pow2(std::max(g_Renderer.GetWidth(), g_Renderer.GetHeight())), 4096);
shadowMapSize = std::max(round_up_to_pow2(std::max(width, height)), 4096u);
break;
// Medium as is
default:
+5 -2
View File
@@ -42,11 +42,14 @@ public:
/**
* RecreateTexture: Destroy the current shadow texture and force creation of
* a new one. Useful when the renderer's size has changed and the texture
* should be resized too.
* a new one. Useful when shadows settings have changed and the texture
* should be resized/updated too.
*/
void RecreateTexture();
void CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height);
void DestroyViewSizeDependentObjects();
/**
* SetupFrame: Configure light space for the given camera and light direction,
* create the shadow texture if necessary, etc.
+25
View File
@@ -236,6 +236,31 @@ int WaterManager::LoadWaterTextures()
return 0;
}
void WaterManager::CreateViewSizeDependentObjects(const uint32_t, const uint32_t)
{
RecreateOrLoadTexturesIfNeeded();
}
void WaterManager::DestroyViewSizeDependentObjects()
{
m_ReflectionFramebuffer.reset();
m_ReflectionTexture.reset();
m_ReflFboDepthTexture.reset();
m_ReflectionFramebufferInitialized = false;
m_RefractionFramebuffer.reset();
m_RefractionTexture.reset();
m_RefrFboDepthTexture.reset();
m_RefractionFramebufferInitialized = false;
m_FancyEffectsFramebuffer.reset();
m_FancyEffectsOccludersFramebuffer.reset();
m_FancyTexture.reset();
m_FancyTextureDepth.reset();
}
void WaterManager::RecreateOrLoadTexturesIfNeeded()
{
// Use screen-sized textures for minimum artifacts.
+4 -1
View File
@@ -1,4 +1,4 @@
/* Copyright (C) 2025 Wildfire Games.
/* Copyright (C) 2026 Wildfire Games.
* This file is part of 0 A.D.
*
* 0 A.D. is free software: you can redistribute it and/or modify
@@ -154,6 +154,9 @@ public:
*/
void RecreateOrLoadTexturesIfNeeded();
void CreateViewSizeDependentObjects(const uint32_t width, const uint32_t height);
void DestroyViewSizeDependentObjects();
/**
* ReloadWaterNormalTextures: Reload the normal textures so that changing
* water type in Atlas will actually do the right thing.
@@ -836,6 +836,14 @@ void CDevice::OnPresent()
++m_FrameID;
}
void CDevice::OnSwapChainRecreation()
{
// We're sure that a client must call WaitUntilIdle to be able to
// recreate a swapchain.
ProcessObjectToDestroyQueue(true);
ProcessDeviceObjectToDestroyQueue(true);
}
bool CDevice::IsTextureFormatSupported(const Format format) const
{
switch (format)
+2
View File
@@ -164,6 +164,8 @@ public:
void OnPresent();
void OnSwapChainRecreation();
void SetObjectName(VkObjectType type, const void* handle, const char* name)
{
SetObjectName(type, reinterpret_cast<uint64_t>(handle), name);
@@ -59,6 +59,16 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
int surfaceDrawableWidth, int surfaceDrawableHeight,
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain)
{
// We don't need to wait to destroy the depth texture as it's independent
// from VkSwapchainKHR.
if (oldSwapChain)
{
oldSwapChain->As<CSwapChain>()->m_DepthTexture.reset();
// Workaround to free the depth texture immediately.
device->OnSwapChainRecreation();
}
// It seems some drivers might not reuse the same swapchain memory. So
// to avoid higher memory peaks destroy the old swapchain before.
const bool destroyOldSwapchainBefore{
@@ -225,6 +235,8 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
ENSURE_VK_SUCCESS(getSwapchainImagesResult);
ENSURE(imageCount > 0);
// We create a depth texture to mimic GL behavior where SDL creates it for
// a backbuffer.
swapChain->m_DepthTexture = CTexture::Create(
device, "SwapChainDepthTexture", ITexture::Type::TEXTURE_2D,
ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,