diff --git a/source/ps/VideoMode.cpp b/source/ps/VideoMode.cpp index bc2a73e883..c3b45d84ce 100644 --- a/source/ps/VideoMode.cpp +++ b/source/ps/VideoMode.cpp @@ -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(); diff --git a/source/renderer/PostprocManager.cpp b/source/renderer/PostprocManager.cpp index 01d099e6a1..7ede6592aa 100644 --- a/source/renderer/PostprocManager.cpp +++ b/source/renderer/PostprocManager.cpp @@ -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(); diff --git a/source/renderer/PostprocManager.h b/source/renderer/PostprocManager.h index 1873579d31..eaa23230bd 100644 --- a/source/renderer/PostprocManager.h +++ b/source/renderer/PostprocManager.h @@ -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 GetPostEffects(); diff --git a/source/renderer/Renderer.cpp b/source/renderer/Renderer.cpp index bed9962d4b..dc97931338 100644 --- a/source/renderer/Renderer.cpp +++ b/source/renderer/Renderer.cpp @@ -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); } diff --git a/source/renderer/Renderer.h b/source/renderer/Renderer.h index 045a9f754d..74e80bb06d 100644 --- a/source/renderer/Renderer.h +++ b/source/renderer/Renderer.h @@ -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; } diff --git a/source/renderer/RenderingOptions.cpp b/source/renderer/RenderingOptions.cpp index c13df14907..033e69735b 100644 --- a/source/renderer/RenderingOptions.cpp +++ b/source/renderer/RenderingOptions.cpp @@ -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", []() diff --git a/source/renderer/SceneRenderer.cpp b/source/renderer/SceneRenderer.cpp index bd26226a13..2335e24527 100644 --- a/source/renderer/SceneRenderer.cpp +++ b/source/renderer/SceneRenderer.cpp @@ -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() diff --git a/source/renderer/SceneRenderer.h b/source/renderer/SceneRenderer.h index 13361f6f09..beb4e162bd 100644 --- a/source/renderer/SceneRenderer.h +++ b/source/renderer/SceneRenderer.h @@ -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(); diff --git a/source/renderer/ShadowMap.cpp b/source/renderer/ShadowMap.cpp index 657d7d9cc7..45b1200138 100644 --- a/source/renderer/ShadowMap.cpp +++ b/source/renderer/ShadowMap.cpp @@ -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: diff --git a/source/renderer/ShadowMap.h b/source/renderer/ShadowMap.h index f86d18c13d..68e9f91b92 100644 --- a/source/renderer/ShadowMap.h +++ b/source/renderer/ShadowMap.h @@ -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. diff --git a/source/renderer/WaterManager.cpp b/source/renderer/WaterManager.cpp index 1b83343f95..e0a956b8f9 100644 --- a/source/renderer/WaterManager.cpp +++ b/source/renderer/WaterManager.cpp @@ -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. diff --git a/source/renderer/WaterManager.h b/source/renderer/WaterManager.h index f5d8eded5c..76b7f0ffbb 100644 --- a/source/renderer/WaterManager.h +++ b/source/renderer/WaterManager.h @@ -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. diff --git a/source/renderer/backend/vulkan/Device.cpp b/source/renderer/backend/vulkan/Device.cpp index fa0c8808ac..26046908ea 100644 --- a/source/renderer/backend/vulkan/Device.cpp +++ b/source/renderer/backend/vulkan/Device.cpp @@ -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) diff --git a/source/renderer/backend/vulkan/Device.h b/source/renderer/backend/vulkan/Device.h index 97a5b6d7f0..cb4a365896 100644 --- a/source/renderer/backend/vulkan/Device.h +++ b/source/renderer/backend/vulkan/Device.h @@ -164,6 +164,8 @@ public: void OnPresent(); + void OnSwapChainRecreation(); + void SetObjectName(VkObjectType type, const void* handle, const char* name) { SetObjectName(type, reinterpret_cast(handle), name); diff --git a/source/renderer/backend/vulkan/SwapChain.cpp b/source/renderer/backend/vulkan/SwapChain.cpp index 579519ea98..3ee1cba88b 100644 --- a/source/renderer/backend/vulkan/SwapChain.cpp +++ b/source/renderer/backend/vulkan/SwapChain.cpp @@ -59,6 +59,16 @@ std::unique_ptr CSwapChain::Create( int surfaceDrawableWidth, int surfaceDrawableHeight, const bool vsync, std::unique_ptr oldSwapChain) { + // We don't need to wait to destroy the depth texture as it's independent + // from VkSwapchainKHR. + if (oldSwapChain) + { + oldSwapChain->As()->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::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,