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