Merge remote-tracking branch 'origin/master' into 1.13

# Conflicts:
#	Source/Engine/GraphicsDevice/Vulkan/GPUSwapChainVulkan.cpp
This commit is contained in:
2026-08-05 08:12:00 +02:00
38 changed files with 1420 additions and 120 deletions
@@ -27,22 +27,30 @@ void BackBufferVulkan::Setup(GPUSwapChainVulkan* window, VkImage backbuffer, Pix
ImageAcquiredSemaphore = New<SemaphoreVulkan>(Device);
}
void BackBufferVulkan::WaitForSubmit()
{
if (SubmitCmdBuffer)
{
if (SubmitCmdBufferFenceCounter == SubmitCmdBuffer->GetFenceSignaledCounter())
SubmitCmdBuffer->Wait();
SubmitCmdBuffer = nullptr;
SubmitCmdBufferFenceCounter = 0;
}
}
void BackBufferVulkan::Release()
{
WaitForSubmit();
Handle.Release();
Delete(RenderingDoneSemaphore);
Delete(ImageAcquiredSemaphore);
if (SubmitCmdBuffer)
{
SubmitCmdBuffer->Wait();
SubmitCmdBuffer = nullptr;
}
Device = nullptr;
}
GPUSwapChainVulkan::GPUSwapChainVulkan(GPUDeviceVulkan* device, Window* window)
: GPUResourceVulkan(device, StringView::Empty)
, _surface(VK_NULL_HANDLE)
, _surfaceWindowHandle(nullptr)
, _swapChain(VK_NULL_HANDLE)
, _currentImageIndex(-1)
, _semaphoreIndex(0)
@@ -61,16 +69,17 @@ void GPUSwapChainVulkan::ReleaseBackBuffer()
_backBuffers.Clear();
}
void GPUSwapChainVulkan::OnReleaseGPU()
void GPUSwapChainVulkan::ReleaseSwapChain(bool releaseSurface)
{
GPUDeviceLock lock(_device);
_device->WaitForGPU();
// The caller must ensure GPU work using the current swapchain is complete before destroying it.
if (_memoryUsage != 0)
{
PROFILE_MEM_DEC(Graphics, _memoryUsage);
_memoryUsage = 0;
}
ReleaseBackBuffer();
// Release data
PROFILE_MEM_DEC(Graphics, _memoryUsage);
_currentImageIndex = -1;
_semaphoreIndex = 0;
_acquiredImageIndex = -1;
@@ -80,13 +89,23 @@ void GPUSwapChainVulkan::OnReleaseGPU()
vkDestroySwapchainKHR(_device->Device, _swapChain, nullptr);
_swapChain = VK_NULL_HANDLE;
}
if (_surface != VK_NULL_HANDLE)
// Resize only invalidates the swapchain. Destroy the native surface only if it is no longer valid
// or when the whole GPU resource is being released.
if (releaseSurface && _surface != VK_NULL_HANDLE)
{
vkDestroySurfaceKHR(GPUDeviceVulkan::Instance, _surface, nullptr);
_surface = VK_NULL_HANDLE;
_surfaceWindowHandle = nullptr;
}
_width = _height = 0;
_memoryUsage = 0;
}
void GPUSwapChainVulkan::OnReleaseGPU()
{
GPUDeviceLock lock(_device);
_device->WaitForGPU();
ReleaseSwapChain(true);
}
bool GPUSwapChainVulkan::IsFullscreen()
@@ -116,24 +135,34 @@ GPUTextureView* GPUSwapChainVulkan::GetBackBufferView()
if (_acquiredImageIndex == -1)
{
PROFILE_CPU();
auto context = _device->MainContext;
auto cmdBufferManager = context->GetCmdBufferManager();
// Keep commands recorded before acquire independent from the image-acquired semaphore wait below.
if (cmdBufferManager->HasPendingActiveCmdBuffer())
context->Flush();
if (TryPresent(DoAcquireImageIndex) < 0)
{
LOG(Fatal, "Swapchain acquire image index failed!");
}
ASSERT(_acquiredImageIndex != -1);
auto context = _device->MainContext;
// Wait for prior GPU work that used this acquired image before recording
// commands against it again. Waiting before acquire can target a different image
// and unnecessarily serialize frames when the swapchain has multiple images.
auto& acquiredBackBuffer = _backBuffers[_acquiredImageIndex];
acquiredBackBuffer.WaitForSubmit();
const auto backBuffer = &_backBuffers[_acquiredImageIndex].Handle;
auto cmdBufferManager = context->GetCmdBufferManager();
auto cmdBuffer = cmdBufferManager->GetCmdBuffer();
// Transition to render target (typical usage in most cases when calling backbuffer getter)
context->AddImageBarrier(backBuffer, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL);
context->FlushBarriers();
// Submit here so we can add a dependency with the acquired semaphore
cmdBuffer->AddWaitSemaphore(VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, _acquiredSemaphore);
// Wait until the presentation engine releases the image before the first color-output work can use it.
cmdBuffer->AddWaitSemaphore(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, _acquiredSemaphore);
cmdBufferManager->SubmitActiveCmdBuffer();
cmdBufferManager->GetNewActiveCommandBuffer();
ASSERT(cmdBufferManager->HasPendingActiveCmdBuffer() && cmdBufferManager->GetActiveCmdBuffer()->GetState() == CmdBufferVulkan::State::IsInsideBegin);
@@ -145,17 +174,6 @@ void GPUSwapChainVulkan::Begin(RenderTask* task)
{
GPUSwapChain::Begin(task);
// Wait for the backbuffer to be available
if (_currentImageIndex != -1)
{
auto& backBuffer = _backBuffers[_currentImageIndex];
if (backBuffer.SubmitCmdBuffer)
{
backBuffer.SubmitCmdBuffer->Wait();
backBuffer.SubmitCmdBuffer = nullptr;
}
}
// Rebuild swapchain if need to
if (_vsyncPending != _vsyncCurrent)
{
@@ -171,8 +189,9 @@ bool GPUSwapChainVulkan::Resize(int32 width, int32 height)
if (width == _width && height == _height)
return false;
// Wait for GPU to flush commands
_device->WaitForGPU();
// Flush any pending commands referencing the previous backbuffer before waiting on the device.
if (_swapChain != VK_NULL_HANDLE)
_device->GetMainContext()->Flush();
return CreateSwapChain(width, height);
}
@@ -204,7 +223,7 @@ void GPUSwapChainVulkan::CopyBackbuffer(GPUContext* context, GPUTexture* dst)
vkCmdCopyImage(contextVulkan->GetCmdBufferManager()->GetCmdBuffer()->GetHandle(), backBuffer->Image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, dstVulkan->GetHandle(), VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &region);
}
bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height, bool recreateSurface)
{
// Skip if window handle is missing (eg. Android window is not yet visible)
auto windowHandle = _window->GetNativePtr();
@@ -213,14 +232,20 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
PROFILE_CPU();
GPUDeviceLock lock(_device);
const auto device = _device->Device;
// Check if surface has been created before
if (_surface != VK_NULL_HANDLE)
const bool hasSwapChain = _swapChain != VK_NULL_HANDLE;
if (_surface != VK_NULL_HANDLE && _surfaceWindowHandle != windowHandle)
{
// Release previous data
ReleaseGPU();
// Android can replace ANativeWindow during lifecycle changes; iOS can replace the UIView/CAMetalLayer.
// A VkSurfaceKHR must not outlive the native object it was created from.
recreateSurface = true;
}
// Flush removed resources
if (recreateSurface || hasSwapChain)
{
// Retire old swapchain resources before creating replacement images. On plain resize the VkSurfaceKHR
// is intentionally reused; recreate it only when Vulkan or the platform window lifetime requires it.
_device->WaitForGPU();
ReleaseSwapChain(recreateSurface);
_device->DeferredDeletionQueue.ReleaseResources(true);
}
if (_vsyncInit)
@@ -231,15 +256,19 @@ bool GPUSwapChainVulkan::CreateSwapChain(int32 width, int32 height)
_vsyncPending &= _window == Engine::MainWindow; // Don't use VSync on new context menus or tooltips in Editor
_vsyncInit = false;
}
ASSERT(_surface == VK_NULL_HANDLE);
ASSERT(!recreateSurface || _surface == VK_NULL_HANDLE);
ASSERT_LOW_LAYER(_backBuffers.Count() == 0);
// Create platform-dependent surface
VulkanPlatform::CreateSurface(_window, _device, GPUDeviceVulkan::Instance, &_surface);
// Create platform-dependent surface if this is the first swapchain or if the previous surface was lost.
if (_surface == VK_NULL_HANDLE)
{
LOG(Warning, "Failed to create Vulkan surface.");
return true;
VulkanPlatform::CreateSurface(_window, _device, GPUDeviceVulkan::Instance, &_surface);
if (_surface == VK_NULL_HANDLE)
{
LOG(Warning, "Failed to create Vulkan surface.");
return true;
}
_surfaceWindowHandle = windowHandle;
}
_memoryUsage = 1;
@@ -542,8 +571,8 @@ int32 GPUSwapChainVulkan::TryPresent(Function<int32(GPUSwapChainVulkan*, void*)>
// Recreate swapchain
ASSERT(_swapChain != VK_NULL_HANDLE);
int32 width = _width, height = _height;
ReleaseGPU();
CreateSwapChain(width, height);
// Preserve the surface for regular out-of-date swaps; recreate it only when Vulkan reports loss.
CreateSwapChain(width, height, status == (int32)Status::LostSurface);
// Flush commands
_device->GetMainContext()->Flush();
@@ -617,6 +646,7 @@ void GPUSwapChainVulkan::Present(bool vsync)
acquiredBackBuffer.SubmitCmdBuffer = context->GetCmdBufferManager()->GetActiveCmdBuffer();
context->GetCmdBufferManager()->SubmitActiveCmdBuffer(_backBuffers[_acquiredImageIndex].RenderingDoneSemaphore);
acquiredBackBuffer.SubmitCmdBufferFenceCounter = acquiredBackBuffer.SubmitCmdBuffer->GetSubmittedFenceCounter();
// Present the back buffer to the viewport window
const auto result = TryPresent(DoPresent, _device->PresentQueue, true);
@@ -630,7 +660,6 @@ void GPUSwapChainVulkan::Present(bool vsync)
// Rebuild swapchain for the next present
int32 width = _width, height = _height;
ReleaseGPU();
CreateSwapChain(width, height);
_device->GetMainContext()->Flush();
_device->WaitForGPU();