17
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-10-26 21:44:53 +01:00
Files
maszyna/rendering/vulkan14/vulkan14swapchain.cpp

456 lines
14 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
#include "stdafx.h"
#include "rendering/vulkan14/vulkan14swapchain.h"
#include "rendering/vulkan14/vulkan14instance.h"
#include "rendering/vulkan14/vulkan14device.h"
#include "utilities/Globals.h"
namespace vk14
{
namespace
{
char const *present_mode_string(VkPresentModeKHR const Mode)
{
switch (Mode)
{
case VK_PRESENT_MODE_IMMEDIATE_KHR:
return "immediate";
case VK_PRESENT_MODE_MAILBOX_KHR:
return "mailbox";
case VK_PRESENT_MODE_FIFO_KHR:
return "fifo";
case VK_PRESENT_MODE_FIFO_RELAXED_KHR:
return "fifo relaxed";
default:
break;
}
return "other";
}
bool is_srgb(VkFormat const Format)
{
switch (Format)
{
case VK_FORMAT_B8G8R8A8_SRGB:
case VK_FORMAT_R8G8B8A8_SRGB:
case VK_FORMAT_A8B8G8R8_SRGB_PACK32:
return true;
default:
break;
}
return false;
}
// the settings the swapchain follows: vsync, and whether the shaders write gamma themselves (as GLFW_SRGB_CAPABLE in opengl)
bool wanted_vsync()
{
return Global.VSync;
}
bool wanted_srgb()
{
return (false == Global.gfx_shadergamma);
}
} // namespace
swapchain::~swapchain()
{
destroy();
}
bool swapchain::create(instance const &Instance, device const &Device, GLFWwindow *Window, std::string const &Name, std::uint32_t const Frames, std::string &Reason)
{
m_instance = &Instance;
m_device = &Device;
m_window = Window;
m_name = Name;
if (!VK14_VERIFY(glfwCreateWindowSurface(Instance.handle(), Window, nullptr, &m_surface)))
{
m_surface = VK_NULL_HANDLE;
Reason = "the window of " + Name + " gives no vulkan surface";
return false;
}
Instance.set_object_name(Device.handle(), VK_OBJECT_TYPE_SURFACE_KHR, reinterpret_cast<std::uint64_t>(m_surface), Name + " surface");
VkBool32 present{VK_FALSE};
if (!VK14_VERIFY(vkGetPhysicalDeviceSurfaceSupportKHR(Device.physical(), Device.families().graphics, m_surface, &present)) || (present == VK_FALSE))
{
Reason = "the gpu can't show images in the window of " + Name;
return false;
}
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
m_acquiresemaphores.assign(Frames, VK_NULL_HANDLE);
for (std::uint32_t i = 0; i < Frames; ++i)
{
if (!VK14_VERIFY(vkCreateSemaphore(Device.handle(), &semaphoreinfo, nullptr, &m_acquiresemaphores[i])))
{
Reason = "no semaphores for " + Name;
return false;
}
Instance.set_object_name(Device.handle(), VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(m_acquiresemaphores[i]), Name + " acquire " + std::to_string(i));
}
return build(Reason);
}
bool swapchain::outdated() const
{
if (m_rebuild || (m_swapchain == VK_NULL_HANDLE) || (m_vsync != wanted_vsync()) || (m_srgb != wanted_srgb()))
{
return true;
}
int width{0}, height{0};
glfwGetFramebufferSize(m_window, &width, &height);
return (width != m_framebuffer.x) || (height != m_framebuffer.y);
}
bool swapchain::build(std::string &Reason)
{
auto const physical{m_device->physical()};
auto const device{m_device->handle()};
int width{0}, height{0};
glfwGetFramebufferSize(m_window, &width, &height);
if ((width <= 0) || (height <= 0))
{
// minimized: no swapchain of 0x0, the window is skipped until it comes back
m_rebuild = true;
return true;
}
VkSurfaceCapabilitiesKHR capabilities{};
auto const result{vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical, m_surface, &capabilities)};
if (result != VK_SUCCESS)
{
Reason = std::string("the surface of ") + m_name + " can't be queried (" + result_string(result) + ")";
return false;
}
VkExtent2D extent{static_cast<std::uint32_t>(width), static_cast<std::uint32_t>(height)};
if (capabilities.currentExtent.width != 0xFFFFFFFFu)
{
extent = capabilities.currentExtent;
}
extent.width = std::clamp(extent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width);
extent.height = std::clamp(extent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height);
if ((extent.width == 0) || (extent.height == 0))
{
m_rebuild = true;
return true;
}
// the format: srgb when the shaders write linear colours (gfx.shadergamma no), as GLFW_SRGB_CAPABLE in opengl
auto const srgb{wanted_srgb()};
std::uint32_t formatcount{0};
vkGetPhysicalDeviceSurfaceFormatsKHR(physical, m_surface, &formatcount, nullptr);
std::vector<VkSurfaceFormatKHR> formats(formatcount);
vkGetPhysicalDeviceSurfaceFormatsKHR(physical, m_surface, &formatcount, formats.data());
if (formats.empty())
{
Reason = "the surface of " + m_name + " has no formats";
return false;
}
std::vector<VkFormat> const wanted{srgb ? std::vector<VkFormat>{VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_R8G8B8A8_SRGB, VK_FORMAT_A8B8G8R8_SRGB_PACK32} : std::vector<VkFormat>{VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_R8G8B8A8_UNORM, VK_FORMAT_A8B8G8R8_UNORM_PACK32}};
VkSurfaceFormatKHR chosen{formats.front()};
if ((formats.size() == 1) && (formats.front().format == VK_FORMAT_UNDEFINED))
{
// any format will do
chosen = {wanted.front(), VK_COLOR_SPACE_SRGB_NONLINEAR_KHR};
}
else
{
auto found{false};
for (auto const format : wanted)
{
auto const lookup{std::find_if(formats.begin(), formats.end(), [format](VkSurfaceFormatKHR const &Entry) { return (Entry.format == format) && (Entry.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR); })};
if (lookup != formats.end())
{
chosen = *lookup;
found = true;
break;
}
}
if (false == found)
{
log_warning(m_name + ": no " + (srgb ? "srgb" : "unorm") + " 8 bit format of the surface, using format " + std::to_string(chosen.format));
}
}
// the presentation: vsync waits for the screen (fifo, always there), without vsync mailbox, or else immediate
auto const vsync{wanted_vsync()};
std::uint32_t modecount{0};
vkGetPhysicalDeviceSurfacePresentModesKHR(physical, m_surface, &modecount, nullptr);
std::vector<VkPresentModeKHR> modes(modecount);
vkGetPhysicalDeviceSurfacePresentModesKHR(physical, m_surface, &modecount, modes.data());
auto presentmode{VK_PRESENT_MODE_FIFO_KHR};
if (false == vsync)
{
for (auto const mode : {VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_IMMEDIATE_KHR})
{
if (std::find(modes.begin(), modes.end(), mode) != modes.end())
{
presentmode = mode;
break;
}
}
}
auto imagecount{capabilities.minImageCount + 1};
if (capabilities.maxImageCount > 0)
{
imagecount = std::min(imagecount, capabilities.maxImageCount);
}
auto compositealpha{VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR};
if ((capabilities.supportedCompositeAlpha & VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR) == 0)
{
for (auto const alpha : {VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR, VK_COMPOSITE_ALPHA_PRE_MULTIPLIED_BIT_KHR, VK_COMPOSITE_ALPHA_POST_MULTIPLIED_BIT_KHR})
{
if ((capabilities.supportedCompositeAlpha & alpha) != 0)
{
compositealpha = alpha;
break;
}
}
}
// the images still in use by the frames in flight and the presentation have to be done with before they go
auto const oldswapchain{m_swapchain};
if (oldswapchain != VK_NULL_HANDLE)
{
vkQueueWaitIdle(m_device->graphics_queue());
release_images();
}
VkSwapchainCreateInfoKHR create{};
create.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
create.surface = m_surface;
create.minImageCount = imagecount;
create.imageFormat = chosen.format;
create.imageColorSpace = chosen.colorSpace;
create.imageExtent = extent;
create.imageArrayLayers = 1;
// drawn into as a colour attachment; the frame is copied into it at the end (flipped, see K8 of the plan)
create.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT;
create.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
create.preTransform = capabilities.currentTransform;
create.compositeAlpha = compositealpha;
create.presentMode = presentmode;
create.clipped = VK_TRUE;
create.oldSwapchain = oldswapchain;
VkSwapchainKHR swapchain{VK_NULL_HANDLE};
auto const created{vkCreateSwapchainKHR(device, &create, nullptr, &swapchain)};
if (oldswapchain != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(device, oldswapchain, nullptr);
}
m_swapchain = VK_NULL_HANDLE;
if (created != VK_SUCCESS)
{
Reason = "the swapchain of " + m_name + " can't be made (" + result_string(created) + ")";
return false;
}
m_swapchain = swapchain;
m_format = chosen.format;
m_colorspace = chosen.colorSpace;
m_presentmode = presentmode;
m_extent = extent;
m_framebuffer = {width, height};
m_vsync = vsync;
m_srgb = srgb;
m_rebuild = false;
m_instance->set_object_name(device, VK_OBJECT_TYPE_SWAPCHAIN_KHR, reinterpret_cast<std::uint64_t>(m_swapchain), m_name + " swapchain");
std::uint32_t count{0};
vkGetSwapchainImagesKHR(device, m_swapchain, &count, nullptr);
m_images.resize(count);
vkGetSwapchainImagesKHR(device, m_swapchain, &count, m_images.data());
VkSemaphoreCreateInfo semaphoreinfo{};
semaphoreinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
m_views.assign(count, VK_NULL_HANDLE);
m_presentsemaphores.assign(count, VK_NULL_HANDLE);
for (std::uint32_t i = 0; i < count; ++i)
{
VkImageViewCreateInfo view{};
view.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
view.image = m_images[i];
view.viewType = VK_IMAGE_VIEW_TYPE_2D;
view.format = m_format;
view.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
if (!VK14_VERIFY(vkCreateImageView(device, &view, nullptr, &m_views[i])) || !VK14_VERIFY(vkCreateSemaphore(device, &semaphoreinfo, nullptr, &m_presentsemaphores[i])))
{
Reason = "the images of the swapchain of " + m_name + " can't be used";
return false;
}
m_instance->set_object_name(device, VK_OBJECT_TYPE_IMAGE, reinterpret_cast<std::uint64_t>(m_images[i]), m_name + " image " + std::to_string(i));
m_instance->set_object_name(device, VK_OBJECT_TYPE_SEMAPHORE, reinterpret_cast<std::uint64_t>(m_presentsemaphores[i]), m_name + " present " + std::to_string(i));
}
log_info("swapchain of " + m_name + ": " + std::to_string(m_extent.width) + "x" + std::to_string(m_extent.height) + ", " + std::to_string(count) + " images, " + (is_srgb(m_format) ? "srgb" : "unorm") + " format " + std::to_string(m_format) + ", present mode " +
present_mode_string(m_presentmode) + (m_vsync ? " (vsync)" : ""));
return true;
}
swapchain::acquire_result swapchain::acquire(std::uint32_t const Frame)
{
if (outdated())
{
std::string reason;
if (false == build(reason))
{
log_error(reason);
return acquire_result::failed;
}
}
if ((m_swapchain == VK_NULL_HANDLE) || m_rebuild)
{
return acquire_result::skipped;
}
auto const result{vkAcquireNextImageKHR(m_device->handle(), m_swapchain, UINT64_MAX, m_acquiresemaphores[Frame], VK_NULL_HANDLE, &m_image)};
switch (result)
{
case VK_SUCCESS:
return acquire_result::ready;
case VK_SUBOPTIMAL_KHR:
// the image is acquired and its semaphore will be signalled, so it's drawn and shown; the swapchain is made anew after
m_rebuild = true;
return acquire_result::ready;
case VK_ERROR_OUT_OF_DATE_KHR:
m_rebuild = true;
return acquire_result::skipped;
default:
break;
}
log_error(std::string("vkAcquireNextImageKHR of ") + m_name + " failed with " + result_string(result));
return acquire_result::failed;
}
void swapchain::presented(VkResult const Result)
{
if ((Result == VK_SUBOPTIMAL_KHR) || (Result == VK_ERROR_OUT_OF_DATE_KHR))
{
m_rebuild = true;
}
}
void swapchain::record_clear(VkCommandBuffer Commands, glm::vec4 const &Colour) const
{
VkImageMemoryBarrier2 barrier{};
barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
// the acquire semaphore is waited for at the colour output, so the change of layout waits for it too
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.srcAccessMask = VK_ACCESS_2_NONE;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
barrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED;
barrier.image = m_images[m_image];
barrier.subresourceRange = {VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1};
VkDependencyInfo dependency{};
dependency.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
dependency.imageMemoryBarrierCount = 1;
dependency.pImageMemoryBarriers = &barrier;
vkCmdPipelineBarrier2(Commands, &dependency);
VkRenderingAttachmentInfo colour{};
colour.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
colour.imageView = m_views[m_image];
colour.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colour.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colour.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
colour.clearValue.color = {{Colour.r, Colour.g, Colour.b, Colour.a}};
VkRenderingInfo rendering{};
rendering.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
rendering.renderArea = {{0, 0}, m_extent};
rendering.layerCount = 1;
rendering.colorAttachmentCount = 1;
rendering.pColorAttachments = &colour;
vkCmdBeginRendering(Commands, &rendering);
vkCmdEndRendering(Commands);
barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
barrier.dstStageMask = VK_PIPELINE_STAGE_2_NONE;
barrier.dstAccessMask = VK_ACCESS_2_NONE;
barrier.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
vkCmdPipelineBarrier2(Commands, &dependency);
}
void swapchain::release_images()
{
auto const device{m_device != nullptr ? m_device->handle() : VK_NULL_HANDLE};
if (device == VK_NULL_HANDLE)
{
return;
}
for (auto view : m_views)
{
if (view != VK_NULL_HANDLE)
{
vkDestroyImageView(device, view, nullptr);
}
}
for (auto semaphore : m_presentsemaphores)
{
if (semaphore != VK_NULL_HANDLE)
{
vkDestroySemaphore(device, semaphore, nullptr);
}
}
m_views.clear();
m_presentsemaphores.clear();
m_images.clear();
}
void swapchain::destroy()
{
auto const device{m_device != nullptr ? m_device->handle() : VK_NULL_HANDLE};
if (device != VK_NULL_HANDLE)
{
release_images();
if (m_swapchain != VK_NULL_HANDLE)
{
vkDestroySwapchainKHR(device, m_swapchain, nullptr);
}
for (auto semaphore : m_acquiresemaphores)
{
if (semaphore != VK_NULL_HANDLE)
{
vkDestroySemaphore(device, semaphore, nullptr);
}
}
}
m_swapchain = VK_NULL_HANDLE;
m_acquiresemaphores.clear();
if ((m_surface != VK_NULL_HANDLE) && (m_instance != nullptr) && (m_instance->handle() != VK_NULL_HANDLE))
{
vkDestroySurfaceKHR(m_instance->handle(), m_surface, nullptr);
}
m_surface = VK_NULL_HANDLE;
m_window = nullptr;
}
} // namespace vk14