/* Copyright (C) 2024 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 .
*/
#include "precompiled.h"
#include "SwapChain.h"
#include "lib/hash.h"
#include "maths/MathUtil.h"
#include "ps/ConfigDB.h"
#include "ps/Profile.h"
#include "renderer/backend/vulkan/Device.h"
#include "renderer/backend/vulkan/Framebuffer.h"
#include "renderer/backend/vulkan/RingCommandContext.h"
#include "renderer/backend/vulkan/Texture.h"
#include "renderer/backend/vulkan/Utilities.h"
#include
#include
namespace Renderer
{
namespace Backend
{
namespace Vulkan
{
// static
std::unique_ptr CSwapChain::Create(
CDevice* device, VkSurfaceKHR surface, int surfaceDrawableWidth, int surfaceDrawableHeight,
std::unique_ptr oldSwapChain)
{
VkPhysicalDevice physicalDevice = device->GetChoosenPhysicalDevice().device;
VkSurfaceCapabilitiesKHR surfaceCapabilities{};
ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(
physicalDevice, surface, &surfaceCapabilities));
const uint32_t swapChainWidth = Clamp(surfaceDrawableWidth,
surfaceCapabilities.minImageExtent.width,
surfaceCapabilities.maxImageExtent.width);
const uint32_t swapChainHeight = Clamp(surfaceDrawableHeight,
surfaceCapabilities.minImageExtent.height,
surfaceCapabilities.maxImageExtent.height);
// Some drivers (for example NVIDIA on Windows during minimize) might
// return zeroes for both minImageExtent and maxImageExtent. It means we're
// not able to create any swapchain. Because we can't choose zeros (they're
// not allowed) and we can't choose values bigger than maxImageExtent
// (which are also zeroes in that case).
if (swapChainWidth == 0 || swapChainHeight == 0)
return nullptr;
std::vector surfaceFormats;
uint32_t surfaceFormatCount = 0;
ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceFormatsKHR(
physicalDevice, surface, &surfaceFormatCount, nullptr));
if (surfaceFormatCount > 0)
{
surfaceFormats.resize(surfaceFormatCount);
ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfaceFormatsKHR(
physicalDevice, surface, &surfaceFormatCount, surfaceFormats.data()));
}
std::vector presentModes;
uint32_t presentModeCount = 0;
ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfacePresentModesKHR(
physicalDevice, surface, &presentModeCount, nullptr));
if (presentModeCount > 0)
{
presentModes.resize(presentModeCount);
ENSURE_VK_SUCCESS(vkGetPhysicalDeviceSurfacePresentModesKHR(
physicalDevice, surface, &presentModeCount, presentModes.data()));
}
// VK_PRESENT_MODE_FIFO_KHR is guaranteed to be supported.
VkPresentModeKHR presentMode = VK_PRESENT_MODE_FIFO_KHR;
auto isPresentModeAvailable = [&presentModes](const VkPresentModeKHR presentMode)
{
return std::find(presentModes.begin(), presentModes.end(), presentMode) != presentModes.end();
};
bool vsyncEnabled = true;
CFG_GET_VAL("vsync", vsyncEnabled);
if (vsyncEnabled)
{
// TODO: use the adaptive one when possible.
// https://gitlab.freedesktop.org/mesa/mesa/-/issues/5516
//if (isPresentModeAvailable(VK_PRESENT_MODE_MAILBOX_KHR))
// presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
}
else
{
if (isPresentModeAvailable(VK_PRESENT_MODE_IMMEDIATE_KHR))
presentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
}
// Spec says:
// The number of format pairs supported must be greater than or equal to 1.
// pSurfaceFormats must not contain an entry whose value for format is
// VK_FORMAT_UNDEFINED.
const auto surfaceFormatIt =
std::find_if(surfaceFormats.begin(), surfaceFormats.end(), IsSurfaceFormatSupported);
if (surfaceFormatIt == surfaceFormats.end())
{
LOGERROR("Can't find a suitable surface format to render to.");
return nullptr;
}
const VkSurfaceFormatKHR& surfaceFormat = *surfaceFormatIt;
VkSwapchainCreateInfoKHR swapChainCreateInfo{};
swapChainCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
swapChainCreateInfo.surface = surface;
// minImageCount + 1 is to have a less chance for a presenter to wait.
// maxImageCount might be zero, it means it's unlimited.
const uint32_t maxImageCount = surfaceCapabilities.maxImageCount > 0
? surfaceCapabilities.maxImageCount
: std::numeric_limits::max();
const uint32_t minImageCount = surfaceCapabilities.minImageCount < maxImageCount
? surfaceCapabilities.minImageCount + 1
: surfaceCapabilities.minImageCount;
swapChainCreateInfo.minImageCount =
Clamp(NUMBER_OF_FRAMES_IN_FLIGHT,
minImageCount, maxImageCount);
swapChainCreateInfo.imageFormat = surfaceFormat.format;
swapChainCreateInfo.imageColorSpace = surfaceFormat.colorSpace;
swapChainCreateInfo.imageExtent.width = swapChainWidth;
swapChainCreateInfo.imageExtent.height = swapChainHeight;
swapChainCreateInfo.imageArrayLayers = 1;
// VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT is guaranteed to present.
// VK_IMAGE_USAGE_TRANSFER_SRC_BIT allows a simpler backbuffer readback.
// VK_IMAGE_USAGE_TRANSFER_DST_BIT allows a blit to the backbuffer.
// VK_IMAGE_USAGE_STORAGE_BIT allows to write to the backbuffer directly
// from a compute shader.
swapChainCreateInfo.imageUsage =
(VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT) &
surfaceCapabilities.supportedUsageFlags;
swapChainCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
// We need to set these only if imageSharingMode is VK_SHARING_MODE_CONCURRENT.
swapChainCreateInfo.queueFamilyIndexCount = 0;
swapChainCreateInfo.pQueueFamilyIndices = nullptr;
// By default VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR is preferable.
if (surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR)
swapChainCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
else
swapChainCreateInfo.preTransform = surfaceCapabilities.currentTransform;
// By default VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR is preferable, other bits
// might require some format or rendering adjustemnts to avoid
// semi-transparent areas.
const VkCompositeAlphaFlagBitsKHR compositeAlphaOrder[] =
{
VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR,
VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR,
VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR
};
for (const VkCompositeAlphaFlagBitsKHR compositeAlpha : compositeAlphaOrder)
if (compositeAlpha & surfaceCapabilities.supportedCompositeAlpha)
{
swapChainCreateInfo.compositeAlpha = compositeAlpha;
break;
}
swapChainCreateInfo.presentMode = presentMode;
swapChainCreateInfo.clipped = VK_TRUE;
if (oldSwapChain)
swapChainCreateInfo.oldSwapchain = oldSwapChain->GetVkSwapchain();
std::unique_ptr swapChain(new CSwapChain());
swapChain->m_Device = device;
ENSURE_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);
uint32_t imageCount = 0;
VkResult getSwapchainImagesResult = VK_INCOMPLETE;
do
{
getSwapchainImagesResult = vkGetSwapchainImagesKHR(
device->GetVkDevice(), swapChain->m_SwapChain, &imageCount, nullptr);
if (getSwapchainImagesResult == VK_SUCCESS && imageCount > 0)
{
swapChain->m_Images.resize(imageCount);
getSwapchainImagesResult = vkGetSwapchainImagesKHR(
device->GetVkDevice(), swapChain->m_SwapChain, &imageCount, swapChain->m_Images.data());
}
} while (getSwapchainImagesResult == VK_INCOMPLETE);
LOGMESSAGE("SwapChain image count: %u (min: %u)", imageCount, swapChainCreateInfo.minImageCount);
ENSURE_VK_SUCCESS(getSwapchainImagesResult);
ENSURE(imageCount > 0);
swapChain->m_DepthTexture = CTexture::Create(
device, "SwapChainDepthTexture", ITexture::Type::TEXTURE_2D,
ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
device->GetPreferredDepthStencilFormat(
Renderer::Backend::ITexture::Usage::DEPTH_STENCIL_ATTACHMENT,
true, true),
swapChainWidth, swapChainHeight, Sampler::MakeDefaultSampler(
Sampler::Filter::NEAREST, Sampler::AddressMode::CLAMP_TO_EDGE),
1, 1);
swapChain->m_ImageFormat = swapChainCreateInfo.imageFormat;
swapChain->m_Textures.resize(imageCount);
swapChain->m_Backbuffers.resize(imageCount);
for (size_t index = 0; index < imageCount; ++index)
{
snprintf(nameBuffer, std::size(nameBuffer), "SwapChainImage #%zu", index);
device->SetObjectName(VK_OBJECT_TYPE_IMAGE, swapChain->m_Images[index], nameBuffer);
snprintf(nameBuffer, std::size(nameBuffer), "SwapChainImageView #%zu", index);
swapChain->m_Textures[index] = CTexture::WrapBackbufferImage(
device, nameBuffer, swapChain->m_Images[index], swapChainCreateInfo.imageFormat,
swapChainCreateInfo.imageUsage, swapChainWidth, swapChainHeight);
}
swapChain->m_IsValid = true;
return swapChain;
}
CSwapChain::CSwapChain() = default;
CSwapChain::~CSwapChain()
{
m_Backbuffers.clear();
m_Textures.clear();
m_DepthTexture.reset();
if (m_SwapChain != VK_NULL_HANDLE)
vkDestroySwapchainKHR(m_Device->GetVkDevice(), m_SwapChain, nullptr);
}
size_t CSwapChain::SwapChainBackbuffer::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;
}
CSwapChain::SwapChainBackbuffer::SwapChainBackbuffer() = default;
CSwapChain::SwapChainBackbuffer::SwapChainBackbuffer(SwapChainBackbuffer&& other) = default;
CSwapChain::SwapChainBackbuffer& CSwapChain::SwapChainBackbuffer::operator=(SwapChainBackbuffer&& other) = default;
bool CSwapChain::AcquireNextImage(VkSemaphore acquireImageSemaphore)
{
ENSURE(m_CurrentImageIndex == std::numeric_limits::max());
const VkResult acquireResult = vkAcquireNextImageKHR(
m_Device->GetVkDevice(), m_SwapChain, std::numeric_limits::max(),
acquireImageSemaphore,
VK_NULL_HANDLE, &m_CurrentImageIndex);
if (acquireResult != VK_SUCCESS)
{
if (acquireResult == VK_ERROR_OUT_OF_DATE_KHR)
m_IsValid = false;
else if (acquireResult != VK_SUBOPTIMAL_KHR)
{
LOGERROR("Acquire result: %d (%s)",
static_cast(acquireResult), Utilities::GetVkResultName(acquireResult));
debug_warn("Unknown acquire error.");
}
}
return m_IsValid;
}
void CSwapChain::SubmitCommandsAfterAcquireNextImage(
CRingCommandContext& commandContext)
{
const bool firstAcquirement = !m_Textures[m_CurrentImageIndex]->IsInitialized();
Utilities::SubmitImageMemoryBarrier(
commandContext.GetCommandBuffer(),
m_Images[m_CurrentImageIndex], 0, 0,
0, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
firstAcquirement ? VK_IMAGE_LAYOUT_UNDEFINED : VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT);
if (!m_DepthTexture->IsInitialized())
{
Utilities::SubmitImageMemoryBarrier(
commandContext.GetCommandBuffer(),
m_DepthTexture->GetImage(), 0, 0,
0, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT);
}
}
void CSwapChain::SubmitCommandsBeforePresent(
CRingCommandContext& commandContext)
{
ENSURE(m_CurrentImageIndex != std::numeric_limits::max());
Utilities::SubmitImageMemoryBarrier(
commandContext.GetCommandBuffer(), m_Images[m_CurrentImageIndex], 0, 0,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, 0,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT);
}
void CSwapChain::Present(VkSemaphore submitDone, VkQueue queue)
{
ENSURE(m_CurrentImageIndex != std::numeric_limits::max());
VkSwapchainKHR swapChains[] = {m_SwapChain};
VkPresentInfoKHR presentInfo{};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &m_CurrentImageIndex;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = &submitDone;
const VkResult presentResult = vkQueuePresentKHR(queue, &presentInfo);
if (presentResult != VK_SUCCESS)
{
if (presentResult == VK_ERROR_OUT_OF_DATE_KHR)
m_IsValid = false;
else if (presentResult != VK_SUBOPTIMAL_KHR)
{
LOGERROR("Present result: %d (%s)",
static_cast(presentResult), Utilities::GetVkResultName(presentResult));
debug_warn("Unknown present error.");
}
}
m_CurrentImageIndex = std::numeric_limits::max();
}
CFramebuffer* CSwapChain::GetCurrentBackbuffer(
const AttachmentLoadOp colorAttachmentLoadOp,
const AttachmentStoreOp colorAttachmentStoreOp,
const AttachmentLoadOp depthStencilAttachmentLoadOp,
const AttachmentStoreOp depthStencilAttachmentStoreOp)
{
ENSURE(m_CurrentImageIndex != std::numeric_limits::max());
SwapChainBackbuffer& swapChainBackbuffer =
m_Backbuffers[m_CurrentImageIndex];
const SwapChainBackbuffer::BackbufferKey key{
colorAttachmentLoadOp, colorAttachmentStoreOp,
depthStencilAttachmentLoadOp, depthStencilAttachmentStoreOp};
auto it = swapChainBackbuffer.backbuffers.find(key);
if (it == swapChainBackbuffer.backbuffers.end())
{
char nameBuffer[64];
snprintf(nameBuffer, std::size(nameBuffer), "Backbuffer #%u", m_CurrentImageIndex);
SColorAttachment colorAttachment{};
colorAttachment.texture = m_Textures[m_CurrentImageIndex].get();
colorAttachment.loadOp = colorAttachmentLoadOp;
colorAttachment.storeOp = colorAttachmentStoreOp;
SDepthStencilAttachment depthStencilAttachment{};
depthStencilAttachment.texture = m_DepthTexture.get();
depthStencilAttachment.loadOp = depthStencilAttachmentLoadOp;
depthStencilAttachment.storeOp = depthStencilAttachmentStoreOp;
it = swapChainBackbuffer.backbuffers.emplace(key, CFramebuffer::Create(
m_Device, nameBuffer, &colorAttachment, &depthStencilAttachment)).first;
}
return it->second.get();
}
CTexture* CSwapChain::GetCurrentBackbufferTexture()
{
ENSURE(m_CurrentImageIndex != std::numeric_limits::max());
return m_Textures[m_CurrentImageIndex].get();
}
} // namespace Vulkan
} // namespace Backend
} // namespace Renderer