mirror of
https://gitea.wildfiregames.com/0ad/0ad
synced 2026-06-16 05:13:58 -07:00
Adds SwapChain as an explicit DeviceObject
It allows us more flexibility on how we control swapchain. That includes toggling V-Sync in real time.
This commit is contained in:
parent
dab6e1a37a
commit
cf4a4d8fd5
22 changed files with 701 additions and 360 deletions
|
|
@ -39,6 +39,7 @@ namespace Renderer::Backend { class IDeviceCommandContext; }
|
|||
namespace Renderer::Backend { class IFramebuffer; }
|
||||
namespace Renderer::Backend { class IGraphicsPipelineState; }
|
||||
namespace Renderer::Backend { class IShaderProgram; }
|
||||
namespace Renderer::Backend { class ISwapChain; }
|
||||
namespace Renderer::Backend { class IVertexInputLayout; }
|
||||
namespace Renderer::Backend { enum class AttachmentLoadOp; }
|
||||
namespace Renderer::Backend { enum class AttachmentStoreOp; }
|
||||
|
|
@ -51,6 +52,8 @@ namespace Renderer::Backend { struct SGraphicsPipelineStateDesc; }
|
|||
namespace Renderer::Backend { struct SVertexAttributeFormat; }
|
||||
namespace Renderer::Backend::Sampler { struct Desc; }
|
||||
|
||||
typedef struct SDL_Window SDL_Window;
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
|
||||
|
|
@ -77,6 +80,10 @@ public:
|
|||
double timestampMultiplier;
|
||||
};
|
||||
|
||||
/**
|
||||
* It's a responsibility of a device owner to make sure (via WaitUntilIdle)
|
||||
* that the device is available to be destroyed.
|
||||
*/
|
||||
virtual ~IDevice() {}
|
||||
|
||||
virtual Backend GetBackend() const = 0;
|
||||
|
|
@ -90,6 +97,27 @@ public:
|
|||
|
||||
virtual std::unique_ptr<IDeviceCommandContext> CreateCommandContext() = 0;
|
||||
|
||||
/**
|
||||
* To be able to present something on a window it needs to create swapchain
|
||||
* which provides framebuffer to output rendering result. Generally it's
|
||||
* not allowed to have multiple swapchains for the same window.
|
||||
*
|
||||
* We use the provided surface size when the window is nullptr. It's used
|
||||
* in Atlas when we don't have an SDL_Window.
|
||||
*
|
||||
* @return A valid swapchain if it was created successfully else nullptr.
|
||||
*/
|
||||
virtual std::unique_ptr<ISwapChain> CreateSwapChain(
|
||||
const char* name, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight, const bool vsync,
|
||||
std::unique_ptr<ISwapChain> oldSwapChain) = 0;
|
||||
|
||||
/**
|
||||
* Waits until all submitted work (via DeviceCommandContext::Flush) to
|
||||
* backend is completed and it's safe to release a resource like SwapChain.
|
||||
*/
|
||||
virtual void WaitUntilIdle() = 0;
|
||||
|
||||
/**
|
||||
* Creates a graphics pipeline state. It's a caller responsibility to
|
||||
* guarantee a lifespan of IShaderProgram stored in the description.
|
||||
|
|
@ -141,45 +169,6 @@ public:
|
|||
virtual std::unique_ptr<IShaderProgram> CreateShaderProgram(
|
||||
const CStr& name, const CShaderDefines& defines) = 0;
|
||||
|
||||
/**
|
||||
* Acquires a backbuffer for rendering a frame.
|
||||
*
|
||||
* @return True if it was successfully acquired and we can render to it.
|
||||
*/
|
||||
virtual bool AcquireNextBackbuffer() = 0;
|
||||
|
||||
/**
|
||||
* Returns a framebuffer for the current backbuffer with the required
|
||||
* attachment operations. It should not be called if the last
|
||||
* AcquireNextBackbuffer call returned false.
|
||||
*
|
||||
* It's guaranteed that for the same acquired backbuffer this function returns
|
||||
* a framebuffer with the same attachments and properties except load and
|
||||
* store operations.
|
||||
*
|
||||
* @return The last successfully acquired framebuffer that wasn't
|
||||
* presented.
|
||||
*/
|
||||
virtual IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) = 0;
|
||||
|
||||
/**
|
||||
* Presents the backbuffer to the swapchain queue to be flipped on a
|
||||
* screen. Should be called only if the last AcquireNextBackbuffer call
|
||||
* returned true.
|
||||
*/
|
||||
virtual void Present() = 0;
|
||||
|
||||
/**
|
||||
* Should be called on window surface resize. It's the device owner
|
||||
* responsibility to call that function. Shouldn't be called during
|
||||
* rendering to an acquired backbuffer.
|
||||
*/
|
||||
virtual void OnWindowResize(const uint32_t width, const uint32_t height) = 0;
|
||||
|
||||
virtual bool IsTextureFormatSupported(const Format format) const = 0;
|
||||
|
||||
virtual bool IsFramebufferFormatSupported(const Format format) const = 0;
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -29,6 +29,7 @@
|
|||
|
||||
namespace Renderer::Backend { class IComputePipelineState; }
|
||||
namespace Renderer::Backend { class IGraphicsPipelineState; }
|
||||
namespace Renderer::Backend { class ISwapChain; }
|
||||
namespace Renderer::Backend { class IVertexInputLayout; }
|
||||
namespace Renderer::Backend { enum class Format; }
|
||||
namespace Renderer::Backend::Sampler { enum class Filter; }
|
||||
|
|
@ -112,7 +113,8 @@ public:
|
|||
* but a client doesn't support that yet.
|
||||
*/
|
||||
virtual void ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
ISwapChain& swapChain, const uint32_t x, const uint32_t y,
|
||||
const uint32_t width, const uint32_t height,
|
||||
void* data) = 0;
|
||||
|
||||
virtual void UploadTexture(ITexture* texture, const Format dataFormat,
|
||||
|
|
|
|||
78
source/renderer/backend/ISwapChain.h
Normal file
78
source/renderer/backend/ISwapChain.h
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
/* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_RENDERER_BACKEND_ISWAPCHAIN
|
||||
#define INCLUDED_RENDERER_BACKEND_ISWAPCHAIN
|
||||
|
||||
#include "renderer/backend/IDeviceObject.h"
|
||||
#include "renderer/backend/IFramebuffer.h"
|
||||
|
||||
#include <cstdint>
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
|
||||
namespace Backend
|
||||
{
|
||||
|
||||
class ISwapChain : public IDeviceObject<ISwapChain>
|
||||
{
|
||||
public:
|
||||
/**
|
||||
* @return True if we still can use the swapchain to present. A swapchain
|
||||
* can become invalid on window resize or some other system event.
|
||||
*/
|
||||
virtual bool IsValid() const = 0;
|
||||
|
||||
/**
|
||||
* Acquires a backbuffer for rendering a frame.
|
||||
*
|
||||
* @return True if it was successfully acquired and we can render to it.
|
||||
*/
|
||||
virtual bool AcquireNextBackbuffer() = 0;
|
||||
|
||||
/**
|
||||
* Returns a framebuffer for the current backbuffer with the required
|
||||
* attachment operations. It should not be called if the last
|
||||
* AcquireNextBackbuffer call returned false.
|
||||
*
|
||||
* It's guaranteed that for the same acquired backbuffer this function returns
|
||||
* a framebuffer with the same attachments and properties except load and
|
||||
* store operations.
|
||||
*
|
||||
* @return The last successfully acquired framebuffer that wasn't
|
||||
* presented.
|
||||
*/
|
||||
virtual IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) = 0;
|
||||
|
||||
/**
|
||||
* Presents the backbuffer to the swapchain queue to be flipped on a
|
||||
* screen. Should be called only if the last AcquireNextBackbuffer call
|
||||
* returned true.
|
||||
*/
|
||||
virtual void Present() = 0;
|
||||
};
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
} // namespace Renderer
|
||||
|
||||
#endif // INCLUDED_RENDERER_BACKEND_ISWAPCHAIN
|
||||
|
|
@ -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
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
#include "renderer/backend/dummy/Framebuffer.h"
|
||||
#include "renderer/backend/dummy/PipelineState.h"
|
||||
#include "renderer/backend/dummy/ShaderProgram.h"
|
||||
#include "renderer/backend/dummy/SwapChain.h"
|
||||
#include "renderer/backend/dummy/Texture.h"
|
||||
#include "scriptinterface/Object.h"
|
||||
|
||||
|
|
@ -49,8 +50,6 @@ CDevice::CDevice()
|
|||
m_DriverInformation = "Unknown";
|
||||
m_Extensions = {};
|
||||
|
||||
m_Backbuffer = CFramebuffer::Create(this);
|
||||
|
||||
m_Capabilities.S3TC = true;
|
||||
m_Capabilities.computeShaders = true;
|
||||
m_Capabilities.debugLabels = true;
|
||||
|
|
@ -75,6 +74,18 @@ std::unique_ptr<IDeviceCommandContext> CDevice::CreateCommandContext()
|
|||
return CDeviceCommandContext::Create(this);
|
||||
}
|
||||
|
||||
std::unique_ptr<ISwapChain> CDevice::CreateSwapChain(
|
||||
const char*, SDL_Window*, int, int,
|
||||
const bool, std::unique_ptr<ISwapChain> oldSwapChain)
|
||||
{
|
||||
oldSwapChain.reset();
|
||||
return CSwapChain::Create(this);
|
||||
}
|
||||
|
||||
void CDevice::WaitUntilIdle()
|
||||
{
|
||||
}
|
||||
|
||||
std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
|
||||
const SGraphicsPipelineStateDesc& pipelineStateDesc)
|
||||
{
|
||||
|
|
@ -129,28 +140,6 @@ std::unique_ptr<IShaderProgram> CDevice::CreateShaderProgram(
|
|||
return CShaderProgram::Create(this);
|
||||
}
|
||||
|
||||
bool CDevice::AcquireNextBackbuffer()
|
||||
{
|
||||
// We have nothing to acquire.
|
||||
return true;
|
||||
}
|
||||
|
||||
IFramebuffer* CDevice::GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp, const AttachmentStoreOp,
|
||||
const AttachmentLoadOp, const AttachmentStoreOp)
|
||||
{
|
||||
return m_Backbuffer.get();
|
||||
}
|
||||
|
||||
void CDevice::Present()
|
||||
{
|
||||
// We have nothing to present.
|
||||
}
|
||||
|
||||
void CDevice::OnWindowResize(const uint32_t /*width*/, const uint32_t /*height*/)
|
||||
{
|
||||
}
|
||||
|
||||
bool CDevice::IsTextureFormatSupported(const Format) const
|
||||
{
|
||||
return true;
|
||||
|
|
|
|||
|
|
@ -59,6 +59,13 @@ public:
|
|||
|
||||
std::unique_ptr<IDeviceCommandContext> CreateCommandContext() override;
|
||||
|
||||
std::unique_ptr<ISwapChain> CreateSwapChain(
|
||||
const char* name, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain) override;
|
||||
|
||||
void WaitUntilIdle() override;
|
||||
|
||||
std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
|
||||
const SGraphicsPipelineStateDesc& pipelineStateDesc) override;
|
||||
|
||||
|
|
@ -88,16 +95,6 @@ public:
|
|||
std::unique_ptr<IShaderProgram> CreateShaderProgram(
|
||||
const CStr& name, const CShaderDefines& defines) override;
|
||||
|
||||
bool AcquireNextBackbuffer() override;
|
||||
|
||||
IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp, const AttachmentStoreOp,
|
||||
const AttachmentLoadOp, const AttachmentStoreOp) override;
|
||||
|
||||
void Present() override;
|
||||
|
||||
void OnWindowResize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
bool IsTextureFormatSupported(const Format format) const override;
|
||||
|
||||
bool IsFramebufferFormatSupported(const Format format) const override;
|
||||
|
|
@ -124,8 +121,6 @@ protected:
|
|||
std::string m_DriverInformation;
|
||||
std::vector<std::string> m_Extensions;
|
||||
|
||||
std::unique_ptr<IFramebuffer> m_Backbuffer;
|
||||
|
||||
Capabilities m_Capabilities{};
|
||||
};
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -126,7 +126,7 @@ void CDeviceCommandContext::EndFramebufferPass()
|
|||
}
|
||||
|
||||
void CDeviceCommandContext::ReadbackFramebufferSync(
|
||||
const uint32_t, const uint32_t, const uint32_t, const uint32_t, void*)
|
||||
ISwapChain&, const uint32_t, const uint32_t, const uint32_t, const uint32_t, void*)
|
||||
{
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -59,7 +59,8 @@ public:
|
|||
void BeginFramebufferPass(IFramebuffer* framebuffer) override;
|
||||
void EndFramebufferPass() override;
|
||||
void ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
ISwapChain& swapChain, const uint32_t x, const uint32_t y,
|
||||
const uint32_t width, const uint32_t height,
|
||||
void* data) override;
|
||||
|
||||
void UploadTexture(ITexture* texture, const Format dataFormat,
|
||||
|
|
|
|||
75
source/renderer/backend/dummy/SwapChain.cpp
Normal file
75
source/renderer/backend/dummy/SwapChain.cpp
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
/* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "SwapChain.h"
|
||||
|
||||
#include "renderer/backend/dummy/Device.h"
|
||||
#include "renderer/backend/dummy/Framebuffer.h"
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
|
||||
namespace Backend
|
||||
{
|
||||
|
||||
namespace Dummy
|
||||
{
|
||||
|
||||
// static
|
||||
std::unique_ptr<ISwapChain> CSwapChain::Create(CDevice* device)
|
||||
{
|
||||
std::unique_ptr<CSwapChain> swapChain(new CSwapChain());
|
||||
swapChain->m_Device = device;
|
||||
swapChain->m_Backbuffer =
|
||||
device->CreateFramebuffer("Backbuffer", nullptr, nullptr);
|
||||
return swapChain;
|
||||
}
|
||||
|
||||
CSwapChain::CSwapChain() = default;
|
||||
|
||||
CSwapChain::~CSwapChain() = default;
|
||||
|
||||
IDevice* CSwapChain::GetDevice()
|
||||
{
|
||||
return m_Device;
|
||||
}
|
||||
|
||||
bool CSwapChain::AcquireNextBackbuffer()
|
||||
{
|
||||
// We have nothing to acquire.
|
||||
return true;
|
||||
}
|
||||
|
||||
IFramebuffer* CSwapChain::GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp, const AttachmentStoreOp,
|
||||
const AttachmentLoadOp, const AttachmentStoreOp)
|
||||
{
|
||||
return m_Backbuffer.get();
|
||||
}
|
||||
|
||||
void CSwapChain::Present()
|
||||
{
|
||||
// We have nothing to present.
|
||||
}
|
||||
|
||||
} // namespace Dummy
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
} // namespace Renderer
|
||||
74
source/renderer/backend/dummy/SwapChain.h
Normal file
74
source/renderer/backend/dummy/SwapChain.h
Normal file
|
|
@ -0,0 +1,74 @@
|
|||
/* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_RENDERER_BACKEND_DUMMY_SWAPCHAIN
|
||||
#define INCLUDED_RENDERER_BACKEND_DUMMY_SWAPCHAIN
|
||||
|
||||
#include "renderer/backend/ISwapChain.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
|
||||
namespace Backend
|
||||
{
|
||||
|
||||
namespace Dummy
|
||||
{
|
||||
|
||||
class CDevice;
|
||||
|
||||
class CSwapChain : public ISwapChain
|
||||
{
|
||||
public:
|
||||
~CSwapChain() override;
|
||||
|
||||
IDevice* GetDevice() override;
|
||||
|
||||
bool IsValid() const override { return true; }
|
||||
|
||||
bool AcquireNextBackbuffer() override;
|
||||
|
||||
IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
|
||||
|
||||
void Present() override;
|
||||
|
||||
private:
|
||||
friend class CDevice;
|
||||
|
||||
static std::unique_ptr<ISwapChain> Create(CDevice* device);
|
||||
|
||||
CSwapChain();
|
||||
|
||||
CDevice* m_Device{nullptr};
|
||||
|
||||
std::unique_ptr<IFramebuffer> m_Backbuffer;
|
||||
};
|
||||
|
||||
} // namespace Dummy
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
} // namespace Renderer
|
||||
|
||||
#endif // INCLUDED_RENDERER_BACKEND_DUMMY_SWAPCHAIN
|
||||
|
|
@ -24,7 +24,6 @@
|
|||
#include "lib/config2.h"
|
||||
#include "lib/debug.h"
|
||||
#include "lib/external_libraries/libsdl.h"
|
||||
#include "lib/hash.h"
|
||||
#include "lib/ogl.h"
|
||||
#include "lib/secure_crt.h"
|
||||
#include "lib/sysdep/os.h"
|
||||
|
|
@ -38,6 +37,7 @@
|
|||
#include "renderer/backend/gl/Framebuffer.h"
|
||||
#include "renderer/backend/gl/PipelineState.h"
|
||||
#include "renderer/backend/gl/ShaderProgram.h"
|
||||
#include "renderer/backend/gl/SwapChain.h"
|
||||
#include "renderer/backend/gl/Texture.h"
|
||||
#include "scriptinterface/Object.h"
|
||||
|
||||
|
|
@ -241,7 +241,6 @@ std::unique_ptr<IDevice> CDevice::Create(SDL_Window* window)
|
|||
LOGERROR("SDL_GL_CreateContext failed: '%s'", SDL_GetError());
|
||||
return nullptr;
|
||||
}
|
||||
SDL_GL_GetDrawableSize(window, &device->m_SurfaceDrawableWidth, &device->m_SurfaceDrawableHeight);
|
||||
|
||||
#if OS_WIN
|
||||
ogl_Init(SDL_GL_GetProcAddress, wutil_GetAppHDC());
|
||||
|
|
@ -801,6 +800,27 @@ std::unique_ptr<IDeviceCommandContext> CDevice::CreateCommandContext()
|
|||
return commandContet;
|
||||
}
|
||||
|
||||
std::unique_ptr<ISwapChain> CDevice::CreateSwapChain(
|
||||
[[maybe_unused]] const char* name, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain)
|
||||
{
|
||||
oldSwapChain.reset();
|
||||
ENSURE(window == m_Window);
|
||||
if (window)
|
||||
SDL_GL_GetDrawableSize(window, &surfaceDrawableWidth, &surfaceDrawableHeight);
|
||||
return CSwapChain::Create(
|
||||
this, window, surfaceDrawableWidth, surfaceDrawableHeight, vsync);
|
||||
}
|
||||
|
||||
void CDevice::WaitUntilIdle()
|
||||
{
|
||||
// Technically we don't need to call glFinish since a driver ensures safety
|
||||
// itself. Though it might give a hint to a driver and it might reduce a
|
||||
// memory spike during recreating resources.
|
||||
glFinish();
|
||||
}
|
||||
|
||||
std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
|
||||
const SGraphicsPipelineStateDesc& pipelineStateDesc)
|
||||
{
|
||||
|
|
@ -857,74 +877,6 @@ std::unique_ptr<IShaderProgram> CDevice::CreateShaderProgram(
|
|||
return CShaderProgram::Create(this, name, defines);
|
||||
}
|
||||
|
||||
bool CDevice::AcquireNextBackbuffer()
|
||||
{
|
||||
ENSURE(!m_BackbufferAcquired);
|
||||
m_BackbufferAcquired = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t CDevice::BackbufferKeyHash::operator()(const BackbufferKey& key) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
hash_combine(seed, std::get<0>(key));
|
||||
hash_combine(seed, std::get<1>(key));
|
||||
hash_combine(seed, std::get<2>(key));
|
||||
hash_combine(seed, std::get<3>(key));
|
||||
return seed;
|
||||
}
|
||||
|
||||
IFramebuffer* CDevice::GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp)
|
||||
{
|
||||
const BackbufferKey key{
|
||||
colorAttachmentLoadOp, colorAttachmentStoreOp,
|
||||
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp};
|
||||
auto it = m_Backbuffers.find(key);
|
||||
if (it == m_Backbuffers.end())
|
||||
{
|
||||
it = m_Backbuffers.emplace(key, CFramebuffer::CreateBackbuffer(
|
||||
this, m_SurfaceDrawableWidth, m_SurfaceDrawableHeight,
|
||||
colorAttachmentLoadOp, colorAttachmentStoreOp,
|
||||
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp)).first;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void CDevice::Present()
|
||||
{
|
||||
ENSURE(m_BackbufferAcquired);
|
||||
m_BackbufferAcquired = false;
|
||||
|
||||
if (m_Window)
|
||||
{
|
||||
PROFILE3("swap buffers");
|
||||
SDL_GL_SwapWindow(m_Window);
|
||||
ogl_WarnIfError();
|
||||
}
|
||||
|
||||
#if defined(NDEBUG)
|
||||
if (!g_ConfigDB.Get("gl.checkerrorafterswap", false))
|
||||
return;
|
||||
#endif
|
||||
PROFILE3("error check");
|
||||
// We have to check GL errors after SwapBuffer to avoid possible
|
||||
// synchronizations during rendering.
|
||||
if (GLenum err = glGetError())
|
||||
ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err));
|
||||
}
|
||||
|
||||
void CDevice::OnWindowResize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
ENSURE(!m_BackbufferAcquired);
|
||||
m_Backbuffers.clear();
|
||||
m_SurfaceDrawableWidth = width;
|
||||
m_SurfaceDrawableHeight = height;
|
||||
}
|
||||
|
||||
bool CDevice::IsTextureFormatSupported(const Format format) const
|
||||
{
|
||||
bool supported = false;
|
||||
|
|
@ -1081,7 +1033,7 @@ void CDevice::InsertTimestampQuery(const uint32_t handle)
|
|||
|
||||
void CDevice::CollectStatistics(StatisticsVector& statistics) const
|
||||
{
|
||||
statistics.emplace_back("Backbuffer count", "", static_cast<uint32_t>(m_Backbuffers.size()));
|
||||
statistics.emplace_back("Query count", "", static_cast<uint32_t>(m_Queries.size()));
|
||||
}
|
||||
|
||||
std::unique_ptr<IDevice> CreateDevice(SDL_Window* window)
|
||||
|
|
|
|||
|
|
@ -73,6 +73,13 @@ public:
|
|||
|
||||
std::unique_ptr<IDeviceCommandContext> CreateCommandContext() override;
|
||||
|
||||
std::unique_ptr<ISwapChain> CreateSwapChain(
|
||||
const char* name, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain) override;
|
||||
|
||||
void WaitUntilIdle() override;
|
||||
|
||||
std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
|
||||
const SGraphicsPipelineStateDesc& pipelineStateDesc) override;
|
||||
|
||||
|
|
@ -104,18 +111,6 @@ public:
|
|||
std::unique_ptr<IShaderProgram> CreateShaderProgram(
|
||||
const CStr& name, const CShaderDefines& defines) override;
|
||||
|
||||
bool AcquireNextBackbuffer() override;
|
||||
|
||||
IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
|
||||
|
||||
void Present() override;
|
||||
|
||||
void OnWindowResize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
bool UseFramebufferInvalidating() const { return m_UseFramebufferInvalidating; }
|
||||
|
||||
bool IsTextureFormatSupported(const Format format) const override;
|
||||
|
|
@ -144,7 +139,6 @@ private:
|
|||
|
||||
SDL_Window* m_Window = nullptr;
|
||||
SDL_GLContext m_Context = nullptr;
|
||||
int m_SurfaceDrawableWidth = 0, m_SurfaceDrawableHeight = 0;
|
||||
|
||||
std::string m_Name;
|
||||
std::string m_Version;
|
||||
|
|
@ -156,19 +150,6 @@ private:
|
|||
// it's used only as a helper for transition.
|
||||
CDeviceCommandContext* m_ActiveCommandContext = nullptr;
|
||||
|
||||
using BackbufferKey = std::tuple<
|
||||
AttachmentLoadOp, AttachmentStoreOp,
|
||||
AttachmentLoadOp, AttachmentStoreOp>;
|
||||
struct BackbufferKeyHash
|
||||
{
|
||||
size_t operator()(const BackbufferKey& key) const;
|
||||
};
|
||||
// We use std::unordered_map to avoid storing sizes of Attachment*Op
|
||||
// enumerations. If it becomes a performance issue we'll replace it
|
||||
// by an array.
|
||||
std::unordered_map<
|
||||
BackbufferKey, std::unique_ptr<CFramebuffer>, BackbufferKeyHash> m_Backbuffers;
|
||||
bool m_BackbufferAcquired = false;
|
||||
bool m_UseFramebufferInvalidating = false;
|
||||
|
||||
struct Query
|
||||
|
|
|
|||
|
|
@ -227,11 +227,6 @@ void InvalidateFramebuffer(
|
|||
std::unique_ptr<CDeviceCommandContext> CDeviceCommandContext::Create(CDevice* device)
|
||||
{
|
||||
std::unique_ptr<CDeviceCommandContext> deviceCommandContext(new CDeviceCommandContext(device));
|
||||
deviceCommandContext->m_Framebuffer = device->GetCurrentBackbuffer(
|
||||
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentStoreOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentStoreOp::DONT_CARE)->As<CFramebuffer>();
|
||||
deviceCommandContext->ResetStates();
|
||||
return deviceCommandContext;
|
||||
}
|
||||
|
|
@ -610,12 +605,8 @@ void CDeviceCommandContext::ResetStates()
|
|||
{
|
||||
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), true);
|
||||
SetScissors(0, nullptr);
|
||||
m_Framebuffer = m_Device->GetCurrentBackbuffer(
|
||||
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentStoreOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentStoreOp::DONT_CARE)->As<CFramebuffer>();
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, m_Framebuffer->GetHandle());
|
||||
m_Framebuffer = nullptr;
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
||||
ogl_WarnIfError();
|
||||
}
|
||||
|
||||
|
|
@ -994,26 +985,21 @@ void CDeviceCommandContext::EndFramebufferPass()
|
|||
}
|
||||
ENSURE(m_InsideFramebufferPass);
|
||||
m_InsideFramebufferPass = false;
|
||||
CFramebuffer* framebuffer = m_Device->GetCurrentBackbuffer(
|
||||
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentStoreOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentLoadOp::DONT_CARE,
|
||||
Renderer::Backend::AttachmentStoreOp::DONT_CARE)->As<CFramebuffer>();
|
||||
if (framebuffer->GetHandle() != m_Framebuffer->GetHandle())
|
||||
if (m_Framebuffer->GetHandle() != 0)
|
||||
{
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, framebuffer->GetHandle());
|
||||
glBindFramebufferEXT(GL_FRAMEBUFFER_EXT, 0);
|
||||
ogl_WarnIfError();
|
||||
}
|
||||
m_Framebuffer = framebuffer;
|
||||
m_Framebuffer = nullptr;
|
||||
|
||||
SetGraphicsPipelineStateImpl(MakeDefaultGraphicsPipelineStateDesc(), false);
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
ISwapChain&, const uint32_t x, const uint32_t y,
|
||||
const uint32_t width, const uint32_t height,
|
||||
void* data)
|
||||
{
|
||||
ENSURE(m_Framebuffer);
|
||||
glReadPixels(x, y, width, height, GL_RGB, GL_UNSIGNED_BYTE, data);
|
||||
ogl_WarnIfError();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -74,7 +74,8 @@ public:
|
|||
void EndFramebufferPass() override;
|
||||
void ClearFramebuffer(const bool color, const bool depth, const bool stencil) override;
|
||||
void ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
ISwapChain& swapChain, const uint32_t x, const uint32_t y,
|
||||
const uint32_t width, const uint32_t height,
|
||||
void* data) override;
|
||||
|
||||
void UploadTexture(ITexture* texture, const Format dataFormat,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
/* Copyright (C) 2022 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
|
||||
|
|
@ -58,6 +58,7 @@ public:
|
|||
|
||||
private:
|
||||
friend class CDevice;
|
||||
friend class CSwapChain;
|
||||
|
||||
static std::unique_ptr<CFramebuffer> Create(
|
||||
CDevice* device, const char* name, SColorAttachment* colorAttachment,
|
||||
|
|
|
|||
136
source/renderer/backend/gl/SwapChain.cpp
Normal file
136
source/renderer/backend/gl/SwapChain.cpp
Normal file
|
|
@ -0,0 +1,136 @@
|
|||
/* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "precompiled.h"
|
||||
|
||||
#include "SwapChain.h"
|
||||
|
||||
#include "lib/config2.h"
|
||||
#include "lib/debug.h"
|
||||
#include "lib/hash.h"
|
||||
#include "ps/ConfigDB.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "renderer/backend/gl/Device.h"
|
||||
|
||||
#include <SDL_video.h>
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
|
||||
namespace Backend
|
||||
{
|
||||
|
||||
namespace GL
|
||||
{
|
||||
|
||||
namespace
|
||||
{
|
||||
|
||||
} // anonymous namespace
|
||||
|
||||
// static
|
||||
std::unique_ptr<CSwapChain> CSwapChain::Create(
|
||||
CDevice* device, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync)
|
||||
{
|
||||
std::unique_ptr<CSwapChain> swapChain(new CSwapChain());
|
||||
swapChain->m_Device = device;
|
||||
swapChain->m_Window = window;
|
||||
swapChain->m_SurfaceDrawableWidth = surfaceDrawableWidth;
|
||||
swapChain->m_SurfaceDrawableHeight = surfaceDrawableHeight;
|
||||
SDL_GL_SetSwapInterval(vsync ? 1 : 0);
|
||||
return swapChain;
|
||||
}
|
||||
|
||||
CSwapChain::CSwapChain() = default;
|
||||
|
||||
CSwapChain::~CSwapChain()
|
||||
{
|
||||
}
|
||||
|
||||
IDevice* CSwapChain::GetDevice()
|
||||
{
|
||||
return m_Device;
|
||||
}
|
||||
|
||||
bool CSwapChain::AcquireNextBackbuffer()
|
||||
{
|
||||
ENSURE(!m_BackbufferAcquired);
|
||||
m_BackbufferAcquired = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
size_t CSwapChain::BackbufferKeyHash::operator()(const BackbufferKey& key) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
hash_combine(seed, std::get<0>(key));
|
||||
hash_combine(seed, std::get<1>(key));
|
||||
hash_combine(seed, std::get<2>(key));
|
||||
hash_combine(seed, std::get<3>(key));
|
||||
return seed;
|
||||
}
|
||||
|
||||
IFramebuffer* CSwapChain::GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp)
|
||||
{
|
||||
const BackbufferKey key{
|
||||
colorAttachmentLoadOp, colorAttachmentStoreOp,
|
||||
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp};
|
||||
auto it = m_Backbuffers.find(key);
|
||||
if (it == m_Backbuffers.end())
|
||||
{
|
||||
it = m_Backbuffers.emplace(key, CFramebuffer::CreateBackbuffer(
|
||||
m_Device, m_SurfaceDrawableWidth, m_SurfaceDrawableHeight,
|
||||
colorAttachmentLoadOp, colorAttachmentStoreOp,
|
||||
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp)).first;
|
||||
}
|
||||
return it->second.get();
|
||||
}
|
||||
|
||||
void CSwapChain::Present()
|
||||
{
|
||||
ENSURE(m_BackbufferAcquired);
|
||||
m_BackbufferAcquired = false;
|
||||
|
||||
if (m_Window)
|
||||
{
|
||||
PROFILE3("swap buffers");
|
||||
SDL_GL_SwapWindow(m_Window);
|
||||
ogl_WarnIfError();
|
||||
}
|
||||
|
||||
#if defined(NDEBUG)
|
||||
if (!g_ConfigDB.Get("gl.checkerrorafterswap", false))
|
||||
return;
|
||||
#endif
|
||||
PROFILE3("error check");
|
||||
// We have to check GL errors after SwapBuffer to avoid possible
|
||||
// synchronizations during rendering.
|
||||
if (GLenum err = glGetError())
|
||||
ONCE(LOGERROR("GL error %s (0x%04x) occurred", ogl_GetErrorName(err), err));
|
||||
}
|
||||
|
||||
} // namespace GL
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
} // namespace Renderer
|
||||
97
source/renderer/backend/gl/SwapChain.h
Normal file
97
source/renderer/backend/gl/SwapChain.h
Normal file
|
|
@ -0,0 +1,97 @@
|
|||
/* 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
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* 0 A.D. is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with 0 A.D. If not, see <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef INCLUDED_RENDERER_BACKEND_GL_SWAPCHAIN
|
||||
#define INCLUDED_RENDERER_BACKEND_GL_SWAPCHAIN
|
||||
|
||||
#include "lib/ogl.h"
|
||||
#include "renderer/backend/gl/Framebuffer.h"
|
||||
#include "renderer/backend/ISwapChain.h"
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <unordered_map>
|
||||
|
||||
typedef struct SDL_Window SDL_Window;
|
||||
|
||||
namespace Renderer
|
||||
{
|
||||
|
||||
namespace Backend
|
||||
{
|
||||
|
||||
namespace GL
|
||||
{
|
||||
|
||||
class CDevice;
|
||||
|
||||
class CSwapChain final : public ISwapChain
|
||||
{
|
||||
public:
|
||||
~CSwapChain() override;
|
||||
|
||||
IDevice* GetDevice() override;
|
||||
|
||||
bool IsValid() const override { return true; }
|
||||
|
||||
bool AcquireNextBackbuffer() override;
|
||||
|
||||
IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
|
||||
|
||||
void Present() override;
|
||||
|
||||
private:
|
||||
friend class CDevice;
|
||||
|
||||
static std::unique_ptr<CSwapChain> Create(
|
||||
CDevice* device, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync);
|
||||
|
||||
CSwapChain();
|
||||
|
||||
CDevice* m_Device{nullptr};
|
||||
SDL_Window* m_Window{nullptr};
|
||||
int m_SurfaceDrawableWidth{0};
|
||||
int m_SurfaceDrawableHeight{0};
|
||||
|
||||
using BackbufferKey = std::tuple<
|
||||
AttachmentLoadOp, AttachmentStoreOp,
|
||||
AttachmentLoadOp, AttachmentStoreOp>;
|
||||
struct BackbufferKeyHash
|
||||
{
|
||||
size_t operator()(const BackbufferKey& key) const;
|
||||
};
|
||||
// We use std::unordered_map to avoid storing sizes of Attachment*Op
|
||||
// enumerations. If it becomes a performance issue we'll replace it
|
||||
// by an array.
|
||||
std::unordered_map<
|
||||
BackbufferKey, std::unique_ptr<CFramebuffer>, BackbufferKeyHash> m_Backbuffers;
|
||||
bool m_BackbufferAcquired{false};
|
||||
};
|
||||
|
||||
} // namespace GL
|
||||
|
||||
} // namespace Backend
|
||||
|
||||
} // namespace Renderer
|
||||
|
||||
#endif // INCLUDED_RENDERER_BACKEND_GL_SWAPCHAIN
|
||||
|
|
@ -635,12 +635,6 @@ std::unique_ptr<CDevice> CDevice::Create(SDL_Window* window)
|
|||
device->m_DescriptorManager =
|
||||
std::make_unique<CDescriptorManager>(device.get(), useDescriptorIndexing);
|
||||
|
||||
device->RecreateSwapChain();
|
||||
// Currently we assume that we should have a valid swapchain on the device
|
||||
// creation.
|
||||
if (!device->m_SwapChain)
|
||||
return nullptr;
|
||||
|
||||
device->m_Name = choosenDevice.properties.deviceName;
|
||||
device->m_Version =
|
||||
std::to_string(VK_API_VERSION_VARIANT(choosenDevice.properties.apiVersion)) +
|
||||
|
|
@ -677,14 +671,9 @@ CDevice::CDevice() = default;
|
|||
|
||||
CDevice::~CDevice()
|
||||
{
|
||||
if (m_Device)
|
||||
vkDeviceWaitIdle(m_Device);
|
||||
|
||||
// The order of destroying does matter to avoid use-after-free and validation
|
||||
// layers complaints.
|
||||
|
||||
m_BackbufferReadbackTexture.reset();
|
||||
|
||||
m_SubmitScheduler.reset();
|
||||
|
||||
if (m_QueryPool)
|
||||
|
|
@ -695,7 +684,6 @@ CDevice::~CDevice()
|
|||
m_RenderPassManager.reset();
|
||||
m_SamplerManager.reset();
|
||||
m_DescriptorManager.reset();
|
||||
m_SwapChain.reset();
|
||||
|
||||
ProcessObjectToDestroyQueue(true);
|
||||
|
||||
|
|
@ -744,6 +732,30 @@ void CDevice::Report(const ScriptRequest& rq, JS::HandleValue settings)
|
|||
Script::SetProperty(rq, settings, "validation_layers", m_ValidationLayers);
|
||||
}
|
||||
|
||||
std::unique_ptr<ISwapChain> CDevice::CreateSwapChain(
|
||||
const char* name, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain)
|
||||
{
|
||||
ENSURE(window == m_Window);
|
||||
ENSURE(m_Window);
|
||||
if (window)
|
||||
SDL_Vulkan_GetDrawableSize(window, &surfaceDrawableWidth, &surfaceDrawableHeight);
|
||||
return CSwapChain::Create(
|
||||
this, m_SubmitScheduler.get(), name, m_Surface, surfaceDrawableWidth, surfaceDrawableHeight,
|
||||
vsync, std::move(oldSwapChain));
|
||||
}
|
||||
|
||||
void CDevice::WaitUntilIdle()
|
||||
{
|
||||
vkDeviceWaitIdle(m_Device);
|
||||
|
||||
// Since we know there is no GPU work in progress we can free resources
|
||||
// queued for deletion.
|
||||
ProcessDeviceObjectToDestroyQueue(true);
|
||||
ProcessObjectToDestroyQueue(true);
|
||||
}
|
||||
|
||||
std::unique_ptr<IGraphicsPipelineState> CDevice::CreateGraphicsPipelineState(
|
||||
const SGraphicsPipelineStateDesc& pipelineStateDesc)
|
||||
{
|
||||
|
|
@ -813,55 +825,14 @@ std::unique_ptr<IDeviceCommandContext> CDevice::CreateCommandContext()
|
|||
return CDeviceCommandContext::Create(this);
|
||||
}
|
||||
|
||||
bool CDevice::AcquireNextBackbuffer()
|
||||
void CDevice::OnPresent()
|
||||
{
|
||||
if (!IsSwapChainValid())
|
||||
{
|
||||
RecreateSwapChain();
|
||||
if (!IsSwapChainValid())
|
||||
return false;
|
||||
}
|
||||
|
||||
PROFILE3("AcquireNextBackbuffer");
|
||||
return m_SubmitScheduler->AcquireNextImage(*m_SwapChain);
|
||||
}
|
||||
|
||||
IFramebuffer* CDevice::GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp)
|
||||
{
|
||||
return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbuffer(
|
||||
colorAttachmentLoadOp, colorAttachmentStoreOp,
|
||||
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp) : nullptr;
|
||||
}
|
||||
|
||||
void CDevice::Present()
|
||||
{
|
||||
if (!IsSwapChainValid())
|
||||
return;
|
||||
|
||||
PROFILE3("Present");
|
||||
|
||||
m_SubmitScheduler->Present(*m_SwapChain);
|
||||
|
||||
ProcessObjectToDestroyQueue();
|
||||
ProcessDeviceObjectToDestroyQueue();
|
||||
|
||||
++m_FrameID;
|
||||
}
|
||||
|
||||
void CDevice::OnWindowResize(const uint32_t width, const uint32_t height)
|
||||
{
|
||||
if (!IsSwapChainValid() ||
|
||||
width != m_SwapChain->GetDepthTexture()->GetWidth() ||
|
||||
height != m_SwapChain->GetDepthTexture()->GetHeight())
|
||||
{
|
||||
RecreateSwapChain();
|
||||
}
|
||||
}
|
||||
|
||||
bool CDevice::IsTextureFormatSupported(const Format format) const
|
||||
{
|
||||
switch (format)
|
||||
|
|
@ -1069,38 +1040,6 @@ std::unique_ptr<CRingCommandContext> CDevice::CreateRingCommandContext(const siz
|
|||
this, size, m_GraphicsQueueFamilyIndex, *m_SubmitScheduler);
|
||||
}
|
||||
|
||||
void CDevice::RecreateSwapChain()
|
||||
{
|
||||
if (m_SwapChain)
|
||||
{
|
||||
vkDeviceWaitIdle(m_Device);
|
||||
|
||||
// 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{
|
||||
g_ConfigDB.Get("renderer.backend.vulkan.destroyoldswapchainbefore", false)};
|
||||
if (destroyOldSwapchainBefore)
|
||||
m_SwapChain.reset();
|
||||
|
||||
m_BackbufferReadbackTexture.reset();
|
||||
|
||||
// Since we know there is no GPU work in progress we can free resources
|
||||
// queued for deletion.
|
||||
ProcessDeviceObjectToDestroyQueue(true);
|
||||
ProcessObjectToDestroyQueue(true);
|
||||
}
|
||||
|
||||
int surfaceDrawableWidth = 0, surfaceDrawableHeight = 0;
|
||||
SDL_Vulkan_GetDrawableSize(m_Window, &surfaceDrawableWidth, &surfaceDrawableHeight);
|
||||
m_SwapChain = CSwapChain::Create(
|
||||
this, m_Surface, surfaceDrawableWidth, surfaceDrawableHeight, std::move(m_SwapChain));
|
||||
}
|
||||
|
||||
bool CDevice::IsSwapChainValid()
|
||||
{
|
||||
return m_SwapChain && m_SwapChain->IsValid();
|
||||
}
|
||||
|
||||
void CDevice::ProcessObjectToDestroyQueue(const bool ignoreFrameID)
|
||||
{
|
||||
while (!m_ObjectToDestroyQueue.empty() &&
|
||||
|
|
@ -1171,27 +1110,6 @@ void CDevice::ProcessDeviceObjectToDestroyQueue(const bool ignoreFrameID)
|
|||
}
|
||||
}
|
||||
|
||||
CTexture* CDevice::GetCurrentBackbufferTexture()
|
||||
{
|
||||
return IsSwapChainValid() ? m_SwapChain->GetCurrentBackbufferTexture() : nullptr;
|
||||
}
|
||||
|
||||
CTexture* CDevice::GetOrCreateBackbufferReadbackTexture()
|
||||
{
|
||||
if (!IsSwapChainValid())
|
||||
return nullptr;
|
||||
if (!m_BackbufferReadbackTexture)
|
||||
{
|
||||
CTexture* currentBackbufferTexture = m_SwapChain->GetCurrentBackbufferTexture();
|
||||
m_BackbufferReadbackTexture = CTexture::CreateReadback(
|
||||
this, "BackbufferReadback",
|
||||
currentBackbufferTexture->GetFormat(),
|
||||
currentBackbufferTexture->GetWidth(),
|
||||
currentBackbufferTexture->GetHeight());
|
||||
}
|
||||
return m_BackbufferReadbackTexture.get();
|
||||
}
|
||||
|
||||
DeviceObjectUID CDevice::GenerateNextDeviceObjectUID()
|
||||
{
|
||||
ENSURE(m_LastAvailableUID < std::numeric_limits<DeviceObjectUID>::max());
|
||||
|
|
|
|||
|
|
@ -84,6 +84,13 @@ public:
|
|||
|
||||
std::unique_ptr<IDeviceCommandContext> CreateCommandContext() override;
|
||||
|
||||
std::unique_ptr<ISwapChain> CreateSwapChain(
|
||||
const char* name, SDL_Window* window,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain) override;
|
||||
|
||||
void WaitUntilIdle() override;
|
||||
|
||||
std::unique_ptr<IGraphicsPipelineState> CreateGraphicsPipelineState(
|
||||
const SGraphicsPipelineStateDesc& pipelineStateDesc) override;
|
||||
|
||||
|
|
@ -116,18 +123,6 @@ public:
|
|||
std::unique_ptr<IShaderProgram> CreateShaderProgram(
|
||||
const CStr& name, const CShaderDefines& defines) override;
|
||||
|
||||
bool AcquireNextBackbuffer() override;
|
||||
|
||||
IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
|
||||
|
||||
void Present() override;
|
||||
|
||||
void OnWindowResize(const uint32_t width, const uint32_t height) override;
|
||||
|
||||
bool IsTextureFormatSupported(const Format format) const override;
|
||||
|
||||
bool IsFramebufferFormatSupported(const Format format) const override;
|
||||
|
|
@ -167,6 +162,8 @@ public:
|
|||
|
||||
void ScheduleBufferToDestroy(const DeviceObjectUID uid);
|
||||
|
||||
void OnPresent();
|
||||
|
||||
void SetObjectName(VkObjectType type, const void* handle, const char* name)
|
||||
{
|
||||
SetObjectName(type, reinterpret_cast<uint64_t>(handle), name);
|
||||
|
|
@ -184,10 +181,6 @@ public:
|
|||
|
||||
CDescriptorManager& GetDescriptorManager() { return *m_DescriptorManager; }
|
||||
|
||||
CTexture* GetCurrentBackbufferTexture();
|
||||
|
||||
CTexture* GetOrCreateBackbufferReadbackTexture();
|
||||
|
||||
DeviceObjectUID GenerateNextDeviceObjectUID();
|
||||
|
||||
uint32_t GetFrameID() const { return m_FrameID; }
|
||||
|
|
@ -195,8 +188,6 @@ public:
|
|||
private:
|
||||
CDevice();
|
||||
|
||||
void RecreateSwapChain();
|
||||
bool IsSwapChainValid();
|
||||
void ProcessObjectToDestroyQueue(const bool ignoreFrameID = false);
|
||||
void ProcessDeviceObjectToDestroyQueue(const bool ignoreFrameID = false);
|
||||
|
||||
|
|
@ -235,9 +226,6 @@ private:
|
|||
};
|
||||
std::vector<Query> m_Queries;
|
||||
|
||||
std::unique_ptr<CSwapChain> m_SwapChain;
|
||||
std::unique_ptr<CTexture> m_BackbufferReadbackTexture;
|
||||
|
||||
uint32_t m_FrameID = 0;
|
||||
|
||||
struct ObjectToDestroy
|
||||
|
|
|
|||
|
|
@ -41,6 +41,7 @@
|
|||
#include "renderer/backend/vulkan/PipelineState.h"
|
||||
#include "renderer/backend/vulkan/RingCommandContext.h"
|
||||
#include "renderer/backend/vulkan/ShaderProgram.h"
|
||||
#include "renderer/backend/vulkan/SwapChain.h"
|
||||
#include "renderer/backend/vulkan/Texture.h"
|
||||
#include "renderer/backend/vulkan/Utilities.h"
|
||||
|
||||
|
|
@ -677,10 +678,13 @@ void CDeviceCommandContext::EndFramebufferPass()
|
|||
}
|
||||
|
||||
void CDeviceCommandContext::ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
ISwapChain& swapChain, const uint32_t x, const uint32_t y,
|
||||
const uint32_t width, const uint32_t height,
|
||||
void* data)
|
||||
{
|
||||
CTexture* texture = m_Device->GetCurrentBackbufferTexture();
|
||||
CSwapChain* queuedSwapChain{(&swapChain)->As<CSwapChain>()};
|
||||
ENSURE(queuedSwapChain->IsValid());
|
||||
CTexture* texture{queuedSwapChain->GetCurrentBackbufferTexture()};
|
||||
if (!texture)
|
||||
{
|
||||
LOGERROR("Vulkan: backbuffer is unavailable.");
|
||||
|
|
@ -692,6 +696,8 @@ void CDeviceCommandContext::ReadbackFramebufferSync(
|
|||
return;
|
||||
}
|
||||
m_QueuedReadbacks.emplace_back(x, y, width, height, data);
|
||||
ENSURE(!m_QueuedReadbackSwapChain || m_QueuedReadbackSwapChain == queuedSwapChain);
|
||||
m_QueuedReadbackSwapChain = queuedSwapChain;
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::UploadTexture(ITexture* texture, const Format dataFormat,
|
||||
|
|
@ -1056,12 +1062,14 @@ void CDeviceCommandContext::Flush()
|
|||
|
||||
m_IsPipelineStateDirty = true;
|
||||
|
||||
CTexture* backbufferReadbackTexture = m_QueuedReadbacks.empty()
|
||||
? nullptr : m_Device->GetOrCreateBackbufferReadbackTexture();
|
||||
CTexture* backbufferReadbackTexture{
|
||||
!m_QueuedReadbacks.empty() && m_QueuedReadbackSwapChain && m_QueuedReadbackSwapChain->IsValid()
|
||||
? m_QueuedReadbackSwapChain->GetOrCreateBackbufferReadbackTexture()
|
||||
: nullptr};
|
||||
const bool needsReadback = backbufferReadbackTexture;
|
||||
if (needsReadback)
|
||||
{
|
||||
CTexture* backbufferTexture = m_Device->GetCurrentBackbufferTexture();
|
||||
CTexture* backbufferTexture{m_QueuedReadbackSwapChain->GetCurrentBackbufferTexture()};
|
||||
|
||||
{
|
||||
// We assume that the readback texture is in linear tiling.
|
||||
|
|
@ -1139,6 +1147,7 @@ void CDeviceCommandContext::Flush()
|
|||
}
|
||||
|
||||
m_QueuedReadbacks.clear();
|
||||
m_QueuedReadbackSwapChain = nullptr;
|
||||
}
|
||||
|
||||
void CDeviceCommandContext::PreDraw()
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -44,6 +44,7 @@ class CFramebuffer;
|
|||
class CGraphicsPipelineState;
|
||||
class CRingCommandContext;
|
||||
class CShaderProgram;
|
||||
class CSwapChain;
|
||||
class CVertexInputLayout;
|
||||
|
||||
class CDeviceCommandContext final : public IDeviceCommandContext
|
||||
|
|
@ -67,7 +68,8 @@ public:
|
|||
void BeginFramebufferPass(IFramebuffer* framebuffer) override;
|
||||
void EndFramebufferPass() override;
|
||||
void ReadbackFramebufferSync(
|
||||
const uint32_t x, const uint32_t y, const uint32_t width, const uint32_t height,
|
||||
ISwapChain& swapChain, const uint32_t x, const uint32_t y,
|
||||
const uint32_t width, const uint32_t height,
|
||||
void* data) override;
|
||||
|
||||
void UploadTexture(ITexture* texture, const Format dataFormat,
|
||||
|
|
@ -218,6 +220,7 @@ private:
|
|||
void* data = nullptr;
|
||||
};
|
||||
PS::StaticVector<QueuedReadback, 2> m_QueuedReadbacks;
|
||||
CSwapChain* m_QueuedReadbackSwapChain{nullptr};
|
||||
|
||||
bool m_DebugBarrierAfterFramebufferPass = false;
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -25,6 +25,7 @@
|
|||
#include "maths/MathUtil.h"
|
||||
#include "ps/CLogger.h"
|
||||
#include "ps/ConfigDB.h"
|
||||
#include "ps/Profile.h"
|
||||
#include "renderer/backend/ITexture.h"
|
||||
#include "renderer/backend/Sampler.h"
|
||||
#include "renderer/backend/vulkan/Device.h"
|
||||
|
|
@ -38,6 +39,7 @@
|
|||
#include <cstdio>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <SDL_video.h>
|
||||
#include <utility>
|
||||
|
||||
namespace Renderer
|
||||
|
|
@ -51,9 +53,18 @@ namespace Vulkan
|
|||
|
||||
// static
|
||||
std::unique_ptr<CSwapChain> CSwapChain::Create(
|
||||
CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
std::unique_ptr<CSwapChain> oldSwapChain)
|
||||
CDevice* device, CSubmitScheduler* submitScheduler,
|
||||
const char* name, VkSurfaceKHR surface,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain)
|
||||
{
|
||||
// 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{
|
||||
g_ConfigDB.Get("renderer.backend.vulkan.destroyoldswapchainbefore", false)};
|
||||
if (destroyOldSwapchainBefore)
|
||||
oldSwapChain.reset();
|
||||
|
||||
VkPhysicalDevice physicalDevice = device->GetChoosenPhysicalDevice().device;
|
||||
|
||||
VkSurfaceCapabilitiesKHR surfaceCapabilities{};
|
||||
|
|
@ -103,7 +114,7 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
|
|||
{
|
||||
return std::find(presentModes.begin(), presentModes.end(), presentMode) != presentModes.end();
|
||||
};
|
||||
if (g_ConfigDB.Get("vsync", true))
|
||||
if (vsync)
|
||||
{
|
||||
// TODO: use the adaptive one when possible.
|
||||
// https://gitlab.freedesktop.org/mesa/mesa/-/issues/5516
|
||||
|
|
@ -184,17 +195,16 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
|
|||
swapChainCreateInfo.presentMode = presentMode;
|
||||
swapChainCreateInfo.clipped = VK_TRUE;
|
||||
if (oldSwapChain)
|
||||
swapChainCreateInfo.oldSwapchain = oldSwapChain->GetVkSwapchain();
|
||||
swapChainCreateInfo.oldSwapchain = oldSwapChain->As<CSwapChain>()->GetVkSwapchain();
|
||||
|
||||
std::unique_ptr<CSwapChain> swapChain(new CSwapChain());
|
||||
swapChain->m_Device = device;
|
||||
swapChain->m_SubmitScheduler = submitScheduler;
|
||||
|
||||
RETURN_NULLPTR_IF_NOT_VK_SUCCESS(vkCreateSwapchainKHR(
|
||||
device->GetVkDevice(), &swapChainCreateInfo, nullptr, &swapChain->m_SwapChain));
|
||||
|
||||
char nameBuffer[64];
|
||||
snprintf(nameBuffer, std::size(nameBuffer), "SwapChain: %dx%d", surfaceDrawableWidth, surfaceDrawableHeight);
|
||||
device->SetObjectName(VK_OBJECT_TYPE_SWAPCHAIN_KHR, swapChain->m_SwapChain, nameBuffer);
|
||||
device->SetObjectName(VK_OBJECT_TYPE_SWAPCHAIN_KHR, swapChain->m_SwapChain, name);
|
||||
|
||||
uint32_t imageCount = 0;
|
||||
VkResult getSwapchainImagesResult = VK_INCOMPLETE;
|
||||
|
|
@ -227,6 +237,7 @@ std::unique_ptr<CSwapChain> CSwapChain::Create(
|
|||
|
||||
swapChain->m_Textures.resize(imageCount);
|
||||
swapChain->m_Backbuffers.resize(imageCount);
|
||||
char nameBuffer[64];
|
||||
for (size_t index = 0; index < imageCount; ++index)
|
||||
{
|
||||
snprintf(nameBuffer, std::size(nameBuffer), "SwapChainImage #%zu", index);
|
||||
|
|
@ -255,6 +266,28 @@ CSwapChain::~CSwapChain()
|
|||
vkDestroySwapchainKHR(m_Device->GetVkDevice(), m_SwapChain, nullptr);
|
||||
}
|
||||
|
||||
IDevice* CSwapChain::GetDevice()
|
||||
{
|
||||
return m_Device;
|
||||
}
|
||||
|
||||
bool CSwapChain::AcquireNextBackbuffer()
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
|
||||
PROFILE3("AcquireNextBackbuffer");
|
||||
return m_SubmitScheduler->AcquireNextImage(*this);
|
||||
}
|
||||
|
||||
void CSwapChain::Present()
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
|
||||
PROFILE3("Present");
|
||||
m_SubmitScheduler->Present(*this);
|
||||
m_Device->OnPresent();
|
||||
}
|
||||
|
||||
size_t CSwapChain::SwapChainBackbuffer::BackbufferKeyHash::operator()(const BackbufferKey& key) const
|
||||
{
|
||||
size_t seed = 0;
|
||||
|
|
@ -273,6 +306,7 @@ CSwapChain::SwapChainBackbuffer& CSwapChain::SwapChainBackbuffer::operator=(Swap
|
|||
|
||||
bool CSwapChain::AcquireNextImage(VkSemaphore acquireImageSemaphore)
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
ENSURE(m_CurrentImageIndex == std::numeric_limits<uint32_t>::max());
|
||||
|
||||
const VkResult acquireResult = vkAcquireNextImageKHR(
|
||||
|
|
@ -331,6 +365,7 @@ void CSwapChain::SubmitCommandsBeforePresent(
|
|||
|
||||
void CSwapChain::Present(VkSemaphore submitDone, VkQueue queue)
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
ENSURE(m_CurrentImageIndex != std::numeric_limits<uint32_t>::max());
|
||||
|
||||
VkSwapchainKHR swapChains[] = {m_SwapChain};
|
||||
|
|
@ -358,12 +393,13 @@ void CSwapChain::Present(VkSemaphore submitDone, VkQueue queue)
|
|||
m_CurrentImageIndex = std::numeric_limits<uint32_t>::max();
|
||||
}
|
||||
|
||||
CFramebuffer* CSwapChain::GetCurrentBackbuffer(
|
||||
IFramebuffer* CSwapChain::GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp)
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
ENSURE(m_CurrentImageIndex != std::numeric_limits<uint32_t>::max());
|
||||
SwapChainBackbuffer& swapChainBackbuffer =
|
||||
m_Backbuffers[m_CurrentImageIndex];
|
||||
|
|
@ -398,6 +434,21 @@ CTexture* CSwapChain::GetCurrentBackbufferTexture()
|
|||
return m_Textures[m_CurrentImageIndex].get();
|
||||
}
|
||||
|
||||
CTexture* CSwapChain::GetOrCreateBackbufferReadbackTexture()
|
||||
{
|
||||
ENSURE(m_IsValid);
|
||||
if (!m_BackbufferReadbackTexture)
|
||||
{
|
||||
CTexture* currentBackbufferTexture{GetCurrentBackbufferTexture()};
|
||||
m_BackbufferReadbackTexture = CTexture::CreateReadback(
|
||||
m_Device, "BackbufferReadback",
|
||||
currentBackbufferTexture->GetFormat(),
|
||||
currentBackbufferTexture->GetWidth(),
|
||||
currentBackbufferTexture->GetHeight());
|
||||
}
|
||||
return m_BackbufferReadbackTexture.get();
|
||||
}
|
||||
|
||||
} // namespace Vulkan
|
||||
|
||||
} // namespace Backend
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -19,6 +19,7 @@
|
|||
#define INCLUDED_RENDERER_BACKEND_VULKAN_SWAPCHAIN
|
||||
|
||||
#include "renderer/backend/IFramebuffer.h"
|
||||
#include "renderer/backend/ISwapChain.h"
|
||||
|
||||
#include <cstddef>
|
||||
#include <cstdint>
|
||||
|
|
@ -32,6 +33,7 @@
|
|||
namespace Renderer::Backend::Vulkan { class CDevice; }
|
||||
namespace Renderer::Backend::Vulkan { class CFramebuffer; }
|
||||
namespace Renderer::Backend::Vulkan { class CRingCommandContext; }
|
||||
namespace Renderer::Backend::Vulkan { class CSubmitScheduler; }
|
||||
namespace Renderer::Backend::Vulkan { class CTexture; }
|
||||
|
||||
namespace Renderer
|
||||
|
|
@ -43,15 +45,27 @@ namespace Backend
|
|||
namespace Vulkan
|
||||
{
|
||||
|
||||
class CSwapChain final
|
||||
class CSwapChain final : public ISwapChain
|
||||
{
|
||||
public:
|
||||
~CSwapChain();
|
||||
~CSwapChain() override;
|
||||
|
||||
IDevice* GetDevice() override;
|
||||
|
||||
bool IsValid() const override { return m_IsValid; }
|
||||
|
||||
bool AcquireNextBackbuffer() override;
|
||||
|
||||
IFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp) override;
|
||||
|
||||
void Present() override;
|
||||
|
||||
VkSwapchainKHR GetVkSwapchain() { return m_SwapChain; }
|
||||
|
||||
bool IsValid() const { return m_IsValid; }
|
||||
|
||||
bool AcquireNextImage(VkSemaphore acquireImageSemaphore);
|
||||
void SubmitCommandsAfterAcquireNextImage(
|
||||
CRingCommandContext& commandContext);
|
||||
|
|
@ -59,26 +73,25 @@ public:
|
|||
CRingCommandContext& commandContext);
|
||||
void Present(VkSemaphore submitDone, VkQueue queue);
|
||||
|
||||
CFramebuffer* GetCurrentBackbuffer(
|
||||
const AttachmentLoadOp colorAttachmentLoadOp,
|
||||
const AttachmentStoreOp colorAttachmentStoreOp,
|
||||
const AttachmentLoadOp depthStencilAttachmentLoadOp,
|
||||
const AttachmentStoreOp depthStencilAttachmentStoreOp);
|
||||
|
||||
CTexture* GetCurrentBackbufferTexture();
|
||||
|
||||
CTexture* GetOrCreateBackbufferReadbackTexture();
|
||||
|
||||
CTexture* GetDepthTexture() { return m_DepthTexture.get(); }
|
||||
|
||||
private:
|
||||
friend class CDevice;
|
||||
|
||||
static std::unique_ptr<CSwapChain> Create(
|
||||
CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
std::unique_ptr<CSwapChain> oldSwapChain);
|
||||
CDevice* device, CSubmitScheduler* submitScheduler,
|
||||
const char* name, VkSurfaceKHR surface,
|
||||
int surfaceDrawableWidth, int surfaceDrawableHeight,
|
||||
const bool vsync, std::unique_ptr<ISwapChain> oldSwapChain);
|
||||
|
||||
CSwapChain();
|
||||
|
||||
CDevice* m_Device = nullptr;
|
||||
CSubmitScheduler* m_SubmitScheduler{nullptr};
|
||||
|
||||
bool m_IsValid = false;
|
||||
VkSwapchainKHR m_SwapChain = VK_NULL_HANDLE;
|
||||
|
|
@ -111,6 +124,8 @@ private:
|
|||
SwapChainBackbuffer& operator=(SwapChainBackbuffer&& other);
|
||||
};
|
||||
std::vector<SwapChainBackbuffer> m_Backbuffers;
|
||||
|
||||
std::unique_ptr<CTexture> m_BackbufferReadbackTexture;
|
||||
};
|
||||
|
||||
} // namespace Vulkan
|
||||
|
|
|
|||
Loading…
Reference in a new issue