mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Uses SwapChain instead of direct Device calls
This commit is contained in:
parent
cf4a4d8fd5
commit
4d83aa28e5
7 changed files with 139 additions and 40 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
Loading…
Reference in a new issue