8 changed files with 582 additions and 405 deletions
@ -0,0 +1,393 @@ |
|||
#include <Vulkan/Swapchain.h> |
|||
|
|||
#include <Logger.h> |
|||
|
|||
#include <SDL2/SDL_vulkan.h> |
|||
|
|||
#include <cstring> |
|||
#include <cassert> |
|||
#include <algorithm> |
|||
#include <tuple> |
|||
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
|
|||
SwapchainSettings::SwapchainSettings(Context* context) |
|||
: m_context(context) |
|||
{} |
|||
|
|||
SwapchainSettings::~SwapchainSettings() { |
|||
} |
|||
|
|||
Context* SwapchainSettings::context() const { |
|||
return m_context; |
|||
} |
|||
|
|||
std::vector<uint32_t> SwapchainSettings::queue_families() const { |
|||
return m_queue_families; |
|||
} |
|||
|
|||
SwapchainSettings& SwapchainSettings::with_queue(uint32_t queue_family) { |
|||
m_queue_families.push_back(queue_family); |
|||
return *this; |
|||
} |
|||
|
|||
|
|||
//////////////////////////////////////////////////////////////////////////////
|
|||
// Swapchain
|
|||
|
|||
|
|||
Swapchain::Swapchain() { |
|||
} |
|||
|
|||
Swapchain::~Swapchain() { |
|||
shutdown(); |
|||
} |
|||
|
|||
VkSwapchainKHR Swapchain::swapchain() { |
|||
return m_swapchain; |
|||
} |
|||
|
|||
VkExtent2D Swapchain::extent() const { |
|||
return m_extent; |
|||
} |
|||
|
|||
size_t Swapchain::image_count() const { |
|||
return m_image_resources.size(); |
|||
} |
|||
|
|||
uint32_t Swapchain::current_image_index() const { |
|||
return m_current_image_index; |
|||
} |
|||
|
|||
VkImage Swapchain::image() { |
|||
assert(m_current_image_index != CURRENT_IMAGE_INDEX); |
|||
return m_image_resources[m_current_image_index].image; |
|||
} |
|||
|
|||
VkImage Swapchain::image(size_t image_index) { |
|||
return m_image_resources[image_index].image; |
|||
} |
|||
|
|||
VkImageView Swapchain::image_view() { |
|||
assert(m_current_image_index != CURRENT_IMAGE_INDEX); |
|||
return m_image_resources[m_current_image_index].view; |
|||
} |
|||
|
|||
VkImageView Swapchain::image_view(size_t image_index) { |
|||
return m_image_resources[image_index].view; |
|||
} |
|||
|
|||
VkSemaphore Swapchain::ready_to_render() { |
|||
return m_frame_resources[m_frame_resources_index].ready_to_render; |
|||
} |
|||
|
|||
VkFence Swapchain::render_done() { |
|||
return m_frame_resources[m_frame_resources_index].render_done; |
|||
} |
|||
|
|||
void Swapchain::initialize(const SwapchainSettings& settings) { |
|||
assert(m_context == nullptr); |
|||
m_context = settings.context(); |
|||
|
|||
create(settings); |
|||
} |
|||
|
|||
void Swapchain::shutdown() { |
|||
if(!m_swapchain) |
|||
return; |
|||
|
|||
destroy(); |
|||
|
|||
m_context = nullptr; |
|||
} |
|||
|
|||
void Swapchain::begin_frame() { |
|||
assert(m_current_image_index == CURRENT_IMAGE_INDEX); |
|||
|
|||
auto& frame_resources = m_frame_resources[m_frame_resources_index]; |
|||
|
|||
// frame_resources are used cyclically. We wait for the current one to be
|
|||
// done rendering to not render more that MAX_FRAMES_IN_FLIGHT frames at
|
|||
// the same time.
|
|||
if(frame_resources.render_done != VK_NULL_HANDLE) { |
|||
vkWaitForFences( |
|||
m_context->device(), |
|||
1, &frame_resources.render_done, |
|||
VK_TRUE, |
|||
UINT64_MAX |
|||
); |
|||
} |
|||
|
|||
bool acquired_image = false; |
|||
while(!acquired_image) { |
|||
VkResult const result = vkAcquireNextImageKHR( |
|||
m_context->device(), |
|||
m_swapchain, |
|||
UINT64_MAX, |
|||
frame_resources.ready_to_render, |
|||
VK_NULL_HANDLE, |
|||
&m_current_image_index |
|||
); |
|||
if(result == VK_SUCCESS || result == VK_SUBOPTIMAL_KHR) { |
|||
acquired_image = true; |
|||
} |
|||
else if(result == VK_ERROR_OUT_OF_DATE_KHR) { |
|||
recreate(); |
|||
} |
|||
else { |
|||
throw std::runtime_error("failed to acquire swapchain image"); |
|||
} |
|||
} |
|||
|
|||
logger.info() << "begin frame " << m_frame_index |
|||
<< ": image " << m_current_image_index |
|||
<< ", frame: " << m_frame_resources_index; |
|||
|
|||
auto& image_resources = m_image_resources[m_current_image_index]; |
|||
|
|||
// In case vkAcquireNextImageKHR doesn't return images in-order, we wait
|
|||
// again to be sure.
|
|||
if(image_resources.render_done != VK_NULL_HANDLE) { |
|||
vkWaitForFences( |
|||
m_context->device(), |
|||
1, &image_resources.render_done, |
|||
VK_TRUE, |
|||
UINT64_MAX |
|||
); |
|||
} |
|||
|
|||
// Remember the right frame to wait for.
|
|||
frame_resources.render_done = image_resources.render_done; |
|||
|
|||
vkResetFences(m_context->device(), 1, &frame_resources.render_done); |
|||
} |
|||
|
|||
void Swapchain::swap_buffers(Array<VkSemaphore> wait_semaphores) { |
|||
assert(m_current_image_index != CURRENT_IMAGE_INDEX); |
|||
|
|||
VkPresentInfoKHR present_info { |
|||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, |
|||
.waitSemaphoreCount = uint32_t(wait_semaphores.size()), |
|||
.pWaitSemaphores = wait_semaphores.data(), |
|||
.swapchainCount = 1, |
|||
.pSwapchains = &m_swapchain, |
|||
.pImageIndices = &m_current_image_index, |
|||
.pResults = nullptr, |
|||
}; |
|||
|
|||
VkResult const result = vkQueuePresentKHR( |
|||
m_context->presentation_queue(), |
|||
&present_info |
|||
); |
|||
if(result == VK_SUCCESS) { |
|||
// Nothing to see here !
|
|||
} |
|||
else if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { |
|||
m_invalid = true; |
|||
} |
|||
else { |
|||
throw std::runtime_error("failed to present swapchain image"); |
|||
} |
|||
|
|||
if(m_invalid) |
|||
recreate(); |
|||
|
|||
m_frame_index += 1; |
|||
m_frame_resources_index = m_frame_index % MAX_FRAMES_IN_FLIGHT; |
|||
|
|||
m_current_image_index = CURRENT_IMAGE_INDEX; |
|||
} |
|||
|
|||
void Swapchain::invalidate() { |
|||
m_invalid = true; |
|||
} |
|||
|
|||
void Swapchain::register_creation_callback(CreationCallback callback) { |
|||
m_creation_callbacks.emplace_back(std::move(callback)); |
|||
} |
|||
|
|||
void Swapchain::register_destruction_callback(DestructionCallback callback) { |
|||
m_destruction_callbacks.emplace_back(std::move(callback)); |
|||
} |
|||
|
|||
void Swapchain::create(const SwapchainSettings& settings) { |
|||
m_queue_families = settings.queue_families(); |
|||
m_queue_families.push_back(m_context->presentation_queue_family()); |
|||
std::sort(m_queue_families.begin(), m_queue_families.end()); |
|||
m_queue_families.erase( |
|||
std::unique(m_queue_families.begin(), m_queue_families.end()), |
|||
m_queue_families.end() |
|||
); |
|||
|
|||
create(); |
|||
} |
|||
|
|||
void Swapchain::create() { |
|||
VkSurfaceCapabilitiesKHR capabilities; |
|||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR( |
|||
m_context->physical_device(), |
|||
m_context->surface(), |
|||
&capabilities |
|||
); |
|||
|
|||
m_extent = capabilities.currentExtent; |
|||
if(m_extent.width == UINT32_MAX) { |
|||
int width; |
|||
int height; |
|||
SDL_Vulkan_GetDrawableSize(m_context->window(), &width, &height); |
|||
|
|||
m_extent.width = std::clamp( |
|||
uint32_t(width), |
|||
capabilities.minImageExtent.width, |
|||
capabilities.maxImageExtent.width |
|||
); |
|||
m_extent.height = std::clamp( |
|||
uint32_t(height), |
|||
capabilities.minImageExtent.height, |
|||
capabilities.maxImageExtent.height |
|||
); |
|||
} |
|||
logger.debug() << "swapchain extent: " << m_extent.width |
|||
<< ", " << m_extent.height; |
|||
|
|||
uint32_t image_count = capabilities.minImageCount; |
|||
if(capabilities.maxImageCount != 0) |
|||
image_count = std::min(image_count, capabilities.maxImageCount); |
|||
logger.debug() << "swapchain image count: " << image_count; |
|||
|
|||
bool share_image = m_queue_families.size() > 1; |
|||
|
|||
VkSwapchainCreateInfoKHR swapchain_info { |
|||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, |
|||
.surface = m_context->surface(), |
|||
.minImageCount = image_count, |
|||
.imageFormat = m_context->surface_format().format, |
|||
.imageColorSpace = m_context->surface_format().colorSpace, |
|||
.imageExtent = m_extent, |
|||
.imageArrayLayers = 1, |
|||
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, |
|||
.imageSharingMode = share_image? |
|||
VK_SHARING_MODE_CONCURRENT: |
|||
VK_SHARING_MODE_EXCLUSIVE, |
|||
.queueFamilyIndexCount = share_image? |
|||
uint32_t(m_queue_families.size()): |
|||
0u, |
|||
.pQueueFamilyIndices = share_image? |
|||
m_queue_families.data(): |
|||
nullptr, |
|||
.preTransform = capabilities.currentTransform, |
|||
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, |
|||
.presentMode = m_context->present_mode(), |
|||
.clipped = VK_TRUE, |
|||
.oldSwapchain = VK_NULL_HANDLE, |
|||
}; |
|||
|
|||
if(vkCreateSwapchainKHR(m_context->device(), &swapchain_info, nullptr, &m_swapchain) != VK_SUCCESS) |
|||
throw std::runtime_error("failed to create swapchain"); |
|||
|
|||
vkGetSwapchainImagesKHR(m_context->device(), m_swapchain, &image_count, nullptr); |
|||
VkImage swapchain_images[16]; |
|||
vkGetSwapchainImagesKHR( |
|||
m_context->device(), |
|||
m_swapchain, |
|||
&image_count, |
|||
swapchain_images |
|||
); |
|||
|
|||
m_image_resources.resize(image_count, {}); |
|||
for(size_t index = 0; index < image_count; index += 1) { |
|||
auto& image_resources = m_image_resources[index]; |
|||
|
|||
image_resources.image = swapchain_images[index]; |
|||
|
|||
VkImageViewCreateInfo view_info { |
|||
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, |
|||
.image = image_resources.image, |
|||
.viewType = VK_IMAGE_VIEW_TYPE_2D, |
|||
.format = m_context->surface_format().format, |
|||
.components = { |
|||
.r = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
.g = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
.b = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
.a = VK_COMPONENT_SWIZZLE_IDENTITY, |
|||
}, |
|||
.subresourceRange = { |
|||
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, |
|||
.baseMipLevel = 0, |
|||
.levelCount = 1, |
|||
.baseArrayLayer = 0, |
|||
.layerCount = 1, |
|||
}, |
|||
}; |
|||
|
|||
if(vkCreateImageView( |
|||
m_context->device(), |
|||
&view_info, |
|||
nullptr, |
|||
&image_resources.view |
|||
) != VK_SUCCESS) { |
|||
throw std::runtime_error("failed to create swapchain image view"); |
|||
} |
|||
|
|||
VkFenceCreateInfo fence_info { |
|||
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, |
|||
.flags = VK_FENCE_CREATE_SIGNALED_BIT, |
|||
}; |
|||
if(vkCreateFence( |
|||
m_context->device(), |
|||
&fence_info, |
|||
nullptr, |
|||
&image_resources.render_done |
|||
) != VK_SUCCESS) |
|||
throw std::runtime_error("failed to create fence"); |
|||
} |
|||
|
|||
m_frame_resources.resize(MAX_FRAMES_IN_FLIGHT, {}); |
|||
|
|||
int index = 0; |
|||
for(auto& frame_resources: m_frame_resources) { |
|||
VkSemaphoreCreateInfo semaphore_info { |
|||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, |
|||
}; |
|||
if(vkCreateSemaphore( |
|||
m_context->device(), |
|||
&semaphore_info, |
|||
nullptr, |
|||
&frame_resources.ready_to_render |
|||
) != VK_SUCCESS) |
|||
throw std::runtime_error("failed to create semaphore"); |
|||
|
|||
index += 1; |
|||
} |
|||
|
|||
for(auto& callback: m_creation_callbacks) |
|||
callback(image_count); |
|||
} |
|||
|
|||
void Swapchain::destroy() { |
|||
for(auto& callback: m_destruction_callbacks) |
|||
callback(); |
|||
|
|||
for(auto& frame_resources: m_frame_resources) { |
|||
m_context->destroy_semaphore(frame_resources.ready_to_render); |
|||
// This is a reference to an ImageResources.render_done
|
|||
frame_resources.render_done = VK_NULL_HANDLE; |
|||
} |
|||
|
|||
for(auto& image_resources: m_image_resources) { |
|||
m_context->destroy_fence(image_resources.render_done); |
|||
m_context->destroy_image_view(image_resources.view); |
|||
} |
|||
|
|||
m_context->destroy_swapchain(m_swapchain); |
|||
} |
|||
|
|||
void Swapchain::recreate() { |
|||
destroy(); |
|||
create(); |
|||
} |
|||
|
|||
|
|||
} |
|||
@ -0,0 +1,109 @@ |
|||
#pragma once |
|||
|
|||
#include <Vulkan/Context.h> |
|||
#include <utils.h> |
|||
|
|||
#include <SDL2/SDL.h> |
|||
#include <vulkan/vulkan.h> |
|||
|
|||
#include <optional> |
|||
#include <initializer_list> |
|||
#include <vector> |
|||
#include <functional> |
|||
|
|||
|
|||
namespace Vulkan { |
|||
|
|||
|
|||
class Swapchain; |
|||
|
|||
class SwapchainSettings { |
|||
public: |
|||
SwapchainSettings(Context* context); |
|||
~SwapchainSettings(); |
|||
|
|||
Context* context() const; |
|||
|
|||
std::vector<uint32_t> queue_families() const; |
|||
SwapchainSettings& with_queue(uint32_t queue_family); |
|||
|
|||
private: |
|||
Context* m_context; |
|||
std::vector<uint32_t> m_queue_families; |
|||
}; |
|||
|
|||
class Swapchain { |
|||
public: |
|||
static constexpr uint32_t CURRENT_IMAGE_INDEX = UINT32_MAX; |
|||
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2; |
|||
|
|||
using CreationCallback = std::function<void(uint32_t)>; |
|||
using DestructionCallback = std::function<void()>; |
|||
|
|||
public: |
|||
Swapchain(); |
|||
Swapchain(const Swapchain&) = delete; |
|||
~Swapchain(); |
|||
|
|||
Swapchain& operator=(const Swapchain&) = delete; |
|||
|
|||
VkSwapchainKHR swapchain(); |
|||
VkExtent2D extent() const; |
|||
size_t image_count() const; |
|||
uint32_t current_image_index() const; |
|||
VkImage image(); |
|||
VkImage image(size_t image_index); |
|||
VkImageView image_view(); |
|||
VkImageView image_view(size_t image_index); |
|||
VkSemaphore ready_to_render(); |
|||
VkFence render_done(); |
|||
|
|||
void initialize(const SwapchainSettings& settings); |
|||
void shutdown(); |
|||
|
|||
void begin_frame(); |
|||
void swap_buffers(Array<VkSemaphore> wait_semaphores); |
|||
|
|||
void invalidate(); |
|||
|
|||
void register_creation_callback(CreationCallback callback); |
|||
void register_destruction_callback(DestructionCallback callback); |
|||
|
|||
private: |
|||
struct ImageResources { |
|||
VkImage image = VK_NULL_HANDLE; |
|||
VkImageView view = VK_NULL_HANDLE; |
|||
VkFence render_done = VK_NULL_HANDLE; |
|||
}; |
|||
|
|||
struct FrameResources { |
|||
VkSemaphore ready_to_render = VK_NULL_HANDLE; |
|||
VkFence render_done = VK_NULL_HANDLE; |
|||
}; |
|||
|
|||
private: |
|||
void create(const SwapchainSettings& settings); |
|||
void create(); |
|||
void destroy(); |
|||
void recreate(); |
|||
|
|||
private: |
|||
Context* m_context = nullptr; |
|||
|
|||
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE; |
|||
VkExtent2D m_extent; |
|||
std::vector<uint32_t> m_queue_families; |
|||
std::vector<ImageResources> m_image_resources; |
|||
std::vector<FrameResources> m_frame_resources; |
|||
bool m_invalid = false; |
|||
|
|||
std::vector<CreationCallback> m_creation_callbacks; |
|||
std::vector<DestructionCallback> m_destruction_callbacks; |
|||
|
|||
uint32_t m_current_image_index = CURRENT_IMAGE_INDEX; |
|||
size_t m_frame_index = 0; |
|||
size_t m_frame_resources_index = 0; |
|||
}; |
|||
|
|||
|
|||
} |
|||
Loading…
Reference in new issue