Uses SwapChain instead of direct Device calls

This commit is contained in:
Vladislav Belov 2026-06-08 18:27:06 +02:00
parent cf4a4d8fd5
commit 4d83aa28e5
No known key found for this signature in database
GPG key ID: 353545E45DB9CCB3
7 changed files with 139 additions and 40 deletions

View file

@ -42,6 +42,7 @@
#include "ps/Pyrogenesis.h"
#include "renderer/Renderer.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/ISwapChain.h"
#include "renderer/backend/dummy/DeviceForward.h"
#include "renderer/backend/gl/DeviceForward.h"
#include "renderer/backend/vulkan/DeviceForward.h"
@ -474,9 +475,6 @@ bool CVideoMode::SetVideoMode(int w, int h, int bpp, bool fullscreen)
m_Window = nullptr;
return SetVideoMode(w, h, bpp, fullscreen);
}
if (isGLBackend)
SDL_GL_SetSwapInterval(m_ConfigVSync ? 1 : 0);
}
else
{
@ -623,6 +621,14 @@ void CVideoMode::Shutdown()
m_IsFullscreen = false;
m_IsInitialised = false;
if (m_BackendDevice)
{
// We need to wait before we can destroy the device.
m_BackendDevice->WaitUntilIdle();
}
m_SwapChain.reset();
m_BackendDevice.reset();
if (m_Window)
{
@ -672,6 +678,14 @@ bool CVideoMode::TryCreateBackendDevice(SDL_Window* window)
}
break;
}
// We don't need to wait because we don't have any swapchain in use.
RecreateSwapChain();
if (m_BackendDevice && (!m_SwapChain || !m_SwapChain->IsValid()))
{
// Currently we assume that we should have a valid swapchain on the device
// creation.
m_BackendDevice.reset();
}
return static_cast<bool>(m_BackendDevice);
}
@ -683,6 +697,45 @@ void CVideoMode::DowngradeBackendSettingAfterCreationFailure()
m_Backend = fallback;
}
void CVideoMode::RecreateSwapChain()
{
if (m_BackendDevice)
{
char nameBuffer[64];
snprintf(nameBuffer, std::size(nameBuffer), "SwapChain: %dx%d", g_xres, g_yres);
m_SwapChain = m_BackendDevice->CreateSwapChain(
nameBuffer, m_Window, g_xres, g_yres, m_ConfigVSync, std::move(m_SwapChain));
}
}
Renderer::Backend::ISwapChain* CVideoMode::GetOrCreateSwapChain()
{
#if !OS_MACOSX
const bool vsync{g_ConfigDB.Get("vsync", false)};
const bool vsyncChanged{m_ConfigVSync != vsync};
if (vsyncChanged)
m_ConfigVSync = vsync;
#else
// Currently there is a bug in MoltenVK which doesn't allow to recreate
// a swapchain with the same size (it makes it 1x1):
// https://github.com/KhronosGroup/MoltenVK/pull/2722
const bool vsyncChanged{false};
#endif
const bool shouldRecreate{
!m_SwapChain || !m_SwapChain->IsValid() || vsyncChanged};
if (shouldRecreate)
{
if (m_SwapChain)
{
// We need to wait until we can destroy the swapchain because it
// might be in use.
m_BackendDevice->WaitUntilIdle();
}
RecreateSwapChain();
}
return m_SwapChain.get();
}
bool CVideoMode::ResizeWindow(int w, int h)
{
ENSURE(m_IsInitialised);
@ -810,8 +863,17 @@ void CVideoMode::UpdateRenderer(int w, int h)
SViewPort vp = { 0, 0, w, h };
if (g_VideoMode.GetBackendDevice())
g_VideoMode.GetBackendDevice()->OnWindowResize(w, h);
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();
g_VideoMode.RecreateSwapChain();
}
if (CRenderer::IsInitialised())
g_Renderer.Resize(w, h);

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
@ -24,6 +24,7 @@
class CStrW;
namespace Renderer::Backend { class IDevice; }
namespace Renderer::Backend { class ISwapChain; }
typedef struct SDL_Window SDL_Window;
@ -114,6 +115,13 @@ public:
Renderer::Backend::IDevice* GetBackendDevice() { return m_BackendDevice.get(); }
/**
* @return A swapchain (may be invalid) if available. It's not allowed to
* call the function during a frame rendering when an old swapchain is
* already in use.
*/
Renderer::Backend::ISwapChain* GetOrCreateSwapChain();
private:
void ReadConfig();
int GetBestBPP();
@ -122,6 +130,12 @@ private:
bool TryCreateBackendDevice(SDL_Window* window);
void DowngradeBackendSettingAfterCreationFailure();
/**
* Immediately recreates a swapchain. It's a caller's responsibility to
* wait till the swapchain isn't in use anymore.
*/
void RecreateSwapChain();
/**
* Remember whether Init has been called. (This isn't used for anything
* important, just for verifying that the callers call our methods in
@ -172,6 +186,8 @@ private:
Renderer::Backend::Backend m_Backend = Renderer::Backend::Backend::GL;
std::unique_ptr<Renderer::Backend::IDevice> m_BackendDevice;
// SwapChain for the corresponding device.
std::unique_ptr<Renderer::Backend::ISwapChain> m_SwapChain;
};
extern CVideoMode g_VideoMode;

View file

@ -72,6 +72,7 @@
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/backend/IFramebuffer.h"
#include "renderer/backend/IShaderProgram.h"
#include "renderer/backend/ISwapChain.h"
#include "tools/atlas/GameInterface/GameLoop.h"
#include "tools/atlas/GameInterface/View.h"
@ -517,29 +518,32 @@ void CRenderer::RenderFrame(const bool needsPresent)
}
else
{
if (needsPresent)
{
// In case of no acquired backbuffer we have nothing render to.
if (!m->device->AcquireNextBackbuffer())
return;
}
Renderer::Backend::ISwapChain* swapChain{g_VideoMode.GetOrCreateSwapChain()};
if (!swapChain || !swapChain->IsValid())
return;
// In case of no acquired backbuffer we have nothing render to.
if (needsPresent && !swapChain->AcquireNextBackbuffer())
return;
if (m_ShouldPreloadResourcesBeforeNextFrame)
{
m_ShouldPreloadResourcesBeforeNextFrame = false;
// We don't need to render logger for the preload.
RenderFrameImpl(true, false);
RenderFrameImpl(*swapChain, true, false);
}
RenderFrameImpl(true, true);
RenderFrameImpl(*swapChain, true, true);
m->deviceCommandContext->Flush();
if (needsPresent)
m->device->Present();
swapChain->Present();
}
}
void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
void CRenderer::RenderFrameImpl(
Renderer::Backend::ISwapChain& swapChain,
const bool renderGUI, const bool renderLogger)
{
PROFILE3("render");
@ -585,7 +589,7 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
// We don't need to clear the color attachment of the framebuffer as the sky
// is going to be rendered anyway.
framebuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
swapChain.GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::CLEAR,
@ -610,7 +614,7 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
postprocManager.ApplyPostproc(m->deviceCommandContext.get());
Renderer::Backend::IFramebuffer* backbuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
swapChain.GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::LOAD,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::LOAD,
@ -642,7 +646,7 @@ void CRenderer::RenderFrameImpl(const bool renderGUI, const bool renderLogger)
? Renderer::Backend::AttachmentLoadOp::CLEAR
: Renderer::Backend::AttachmentLoadOp::DONT_CARE;
Renderer::Backend::IFramebuffer* backbuffer =
m->deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
swapChain.GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
depthStencilLoadOp,
@ -734,12 +738,6 @@ void CRenderer::RenderScreenShot(const bool needsPresent)
const size_t width = static_cast<size_t>(g_xres), height = static_cast<size_t>(g_yres);
const size_t bpp = 24;
if (needsPresent && !m->device->AcquireNextBackbuffer())
return;
// Hide log messages and re-render
RenderFrameImpl(true, false);
const size_t img_size = width * height * bpp / 8;
const size_t hdr_size = tex_hdr_size(filename);
std::shared_ptr<u8> buf;
@ -749,10 +747,20 @@ void CRenderer::RenderScreenShot(const bool needsPresent)
if (t.wrap(width, height, bpp, TEX_BOTTOM_UP, buf, hdr_size) < 0)
return;
m->deviceCommandContext->ReadbackFramebufferSync(0, 0, width, height, img);
Renderer::Backend::ISwapChain* swapChain{g_VideoMode.GetOrCreateSwapChain()};
if (!swapChain || !swapChain->IsValid())
return;
if (needsPresent && !swapChain->AcquireNextBackbuffer())
return;
// Hide log messages and re-render
RenderFrameImpl(*swapChain, false, false);
m->deviceCommandContext->ReadbackFramebufferSync(*swapChain, 0, 0, width, height, img);
m->deviceCommandContext->Flush();
if (needsPresent)
m->device->Present();
swapChain->Present();
if (tex_write(&t, filename) == INFO::OK)
{
@ -850,15 +858,19 @@ void CRenderer::RenderBigScreenShot(const bool needsPresent)
}
g_Game->GetView()->GetCamera()->SetProjection(projection);
if (!needsPresent || m->device->AcquireNextBackbuffer())
{
RenderFrameImpl(false, false);
Renderer::Backend::ISwapChain* swapChain{g_VideoMode.GetOrCreateSwapChain()};
if (!swapChain || !swapChain->IsValid())
continue;
m->deviceCommandContext->ReadbackFramebufferSync(0, 0, tileWidth, tileHeight, tileData);
if (!needsPresent || swapChain->AcquireNextBackbuffer())
{
RenderFrameImpl(*swapChain, false, false);
m->deviceCommandContext->ReadbackFramebufferSync(*swapChain, 0, 0, tileWidth, tileHeight, tileData);
m->deviceCommandContext->Flush();
if (needsPresent)
m->device->Present();
swapChain->Present();
}
// Copy the tile pixels into the main image

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
@ -36,6 +36,7 @@ class CVertexBufferManager;
namespace PS::Memory { class LinearAllocator; }
namespace Renderer::Backend { class IDevice; }
namespace Renderer::Backend { class IDeviceCommandContext; }
namespace Renderer::Backend { class ISwapChain; }
namespace Renderer::Backend { class IVertexInputLayout; }
namespace Renderer::Backend { struct SVertexAttributeFormat; }
@ -161,7 +162,9 @@ protected:
bool ShouldRender() const;
void RenderFrameImpl(const bool renderGUI, const bool renderLogger);
void RenderFrameImpl(
Renderer::Backend::ISwapChain& swapChain,
const bool renderGUI, const bool renderLogger);
void RenderFrame2D(const bool renderGUI, const bool renderLogger);
void RenderScreenShot(const bool needsPresent);
void RenderBigScreenShot(const bool needsPresent);

View file

@ -60,6 +60,7 @@
#include "renderer/backend/IDevice.h"
#include "renderer/backend/IDeviceCommandContext.h"
#include "renderer/backend/IFramebuffer.h"
#include "renderer/backend/ISwapChain.h"
#include "scriptinterface/ScriptInterface.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpAttack.h"
@ -528,6 +529,11 @@ void ActorViewer::Render()
{
// TODO: ActorViewer should reuse CRenderer code and not duplicate it.
Renderer::Backend::ISwapChain* swapChain{
g_VideoMode.GetOrCreateSwapChain()};
if (!swapChain || !swapChain->IsValid())
return;
CSceneRenderer& sceneRenderer = g_Renderer.GetSceneRenderer();
// Set simulation context for rendering purposes
@ -557,7 +563,7 @@ void ActorViewer::Render()
sceneRenderer.PrepareScene(deviceCommandContext, m);
Renderer::Backend::IFramebuffer* backbuffer =
deviceCommandContext->GetDevice()->GetCurrentBackbuffer(
swapChain->GetCurrentBackbuffer(
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
Renderer::Backend::AttachmentStoreOp::STORE,
Renderer::Backend::AttachmentLoadOp::CLEAR,

View file

@ -130,8 +130,6 @@ MESSAGEHANDLER(InitGraphics)
{
g_VideoMode.CreateBackendDevice(false);
g_VideoMode.GetBackendDevice()->OnWindowResize(g_xres, g_yres);
g_ScriptInterface.emplace("Engine", "GUIManager", *g_ScriptContext);
InitGraphics(g_AtlasGameLoop->args, g_InitFlags, {}, *g_ScriptContext, *g_ScriptInterface);
}

View file

@ -37,6 +37,7 @@
#include "renderer/Renderer.h"
#include "renderer/SceneRenderer.h"
#include "renderer/backend/IDevice.h"
#include "renderer/backend/ISwapChain.h"
#include "simulation2/Simulation2.h"
#include "simulation2/components/ICmpParticleManager.h"
#include "simulation2/components/ICmpPathfinder.h"
@ -228,7 +229,8 @@ void AtlasViewGame::Update(float realFrameLength)
void AtlasViewGame::Render()
{
if (!g_VideoMode.GetBackendDevice()->AcquireNextBackbuffer())
Renderer::Backend::ISwapChain* swapChain{g_VideoMode.GetOrCreateSwapChain()};
if (!swapChain || !swapChain->IsValid() || !swapChain->AcquireNextBackbuffer())
return;
SViewPort vp = { 0, 0, g_xres, g_yres };
@ -239,9 +241,9 @@ void AtlasViewGame::Render()
g_Renderer.RenderFrame(false);
Atlas_GLSwapBuffers((void*)g_AtlasGameLoop->glCanvas);
// In case of atlas the device's present will do only internal stuff
// In case of atlas the swapchain's present will do only internal stuff
// without calling a real backbuffer swap.
g_VideoMode.GetBackendDevice()->Present();
swapChain->Present();
}
void AtlasViewGame::DrawCinemaPathTool(Renderer::Backend::IDeviceCommandContext& deviceCommandContext)