Move swapchain-related code to Vulkan::Swapchain.
This commit is contained in:
@@ -86,16 +86,24 @@ uint32_t Context::queue_family(size_t queue_index) const {
|
||||
return m_queue_families[queue_index];
|
||||
}
|
||||
|
||||
uint32_t Context::presentation_queue_family() const {
|
||||
return m_presentation_queue_family;
|
||||
}
|
||||
|
||||
VkQueue Context::queue(size_t queue_index) {
|
||||
return m_queues[queue_index];
|
||||
}
|
||||
|
||||
VkSurfaceKHR Context::surface() {
|
||||
return m_surface;
|
||||
VkQueue Context::presentation_queue() {
|
||||
return m_presentation_queue;
|
||||
}
|
||||
|
||||
VkSwapchainKHR Context::swapchain() {
|
||||
return m_swapchain;
|
||||
SDL_Window* Context::window() {
|
||||
return m_window;
|
||||
}
|
||||
|
||||
VkSurfaceKHR Context::surface() {
|
||||
return m_surface;
|
||||
}
|
||||
|
||||
VkSurfaceFormatKHR Context::surface_format() const {
|
||||
@@ -106,44 +114,6 @@ VkPresentModeKHR Context::present_mode() const {
|
||||
return m_present_mode;
|
||||
}
|
||||
|
||||
VkExtent2D Context::swapchain_extent() const {
|
||||
return m_swapchain_extent;
|
||||
}
|
||||
|
||||
size_t Context::swapchain_image_count() const {
|
||||
return m_image_resources.size();
|
||||
}
|
||||
|
||||
uint32_t Context::current_image_index() const {
|
||||
return m_current_image_index;
|
||||
}
|
||||
|
||||
VkImage Context::swapchain_image() {
|
||||
assert(m_current_image_index != CURRENT_IMAGE_INDEX);
|
||||
return m_image_resources[m_current_image_index].image;
|
||||
}
|
||||
|
||||
VkImage Context::swapchain_image(size_t image_index) {
|
||||
return m_image_resources[image_index].image;
|
||||
}
|
||||
|
||||
VkImageView Context::swapchain_image_view() {
|
||||
assert(m_current_image_index != CURRENT_IMAGE_INDEX);
|
||||
return m_image_resources[m_current_image_index].view;
|
||||
}
|
||||
|
||||
VkImageView Context::swapchain_image_view(size_t image_index) {
|
||||
return m_image_resources[image_index].view;
|
||||
}
|
||||
|
||||
VkSemaphore Context::ready_to_render() {
|
||||
return m_frame_resources[m_frame_resources_index].ready_to_render;
|
||||
}
|
||||
|
||||
VkFence Context::render_done() {
|
||||
return m_frame_resources[m_frame_resources_index].render_done;
|
||||
}
|
||||
|
||||
void Context::initialize(const ContextSettings& settings) {
|
||||
m_window = settings.window();
|
||||
|
||||
@@ -151,7 +121,6 @@ void Context::initialize(const ContextSettings& settings) {
|
||||
create_surface(settings);
|
||||
choose_physical_device(settings);
|
||||
create_device(settings);
|
||||
create_swapchain(settings);
|
||||
}
|
||||
|
||||
void Context::shutdown() {
|
||||
@@ -163,120 +132,12 @@ void Context::shutdown() {
|
||||
for(auto& callback: m_context_destruction_callbacks)
|
||||
callback();
|
||||
|
||||
destroy_swapchain();
|
||||
|
||||
destroy_device(m_device);
|
||||
destroy_surface(m_surface);
|
||||
destroy_debug_messenger(m_debug_messenger);
|
||||
destroy_instance(m_instance);
|
||||
}
|
||||
|
||||
void Context::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_device,
|
||||
1, &frame_resources.render_done,
|
||||
VK_TRUE,
|
||||
UINT64_MAX
|
||||
);
|
||||
}
|
||||
|
||||
bool acquired_image = false;
|
||||
while(!acquired_image) {
|
||||
VkResult const result = vkAcquireNextImageKHR(
|
||||
m_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_swapchain();
|
||||
}
|
||||
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_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_device, 1, &frame_resources.render_done);
|
||||
}
|
||||
|
||||
void Context::swap_buffers(uint32_t semaphore_count, VkSemaphore* wait_semaphores) {
|
||||
assert(m_current_image_index != CURRENT_IMAGE_INDEX);
|
||||
|
||||
VkPresentInfoKHR present_info {
|
||||
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
|
||||
.waitSemaphoreCount = semaphore_count,
|
||||
.pWaitSemaphores = wait_semaphores,
|
||||
.swapchainCount = 1,
|
||||
.pSwapchains = &m_swapchain,
|
||||
.pImageIndices = &m_current_image_index,
|
||||
.pResults = nullptr,
|
||||
};
|
||||
|
||||
VkResult const result = vkQueuePresentKHR(m_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_swapchain = true;
|
||||
}
|
||||
else {
|
||||
throw std::runtime_error("failed to present swapchain image");
|
||||
}
|
||||
|
||||
if(m_invalid_swapchain)
|
||||
recreate_swapchain();
|
||||
|
||||
m_frame_index += 1;
|
||||
m_frame_resources_index = m_frame_index % MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
m_current_image_index = CURRENT_IMAGE_INDEX;
|
||||
}
|
||||
|
||||
void Context::invalidate_swapchain() {
|
||||
m_invalid_swapchain = true;
|
||||
}
|
||||
|
||||
void Context::register_swapchain_creation_callback(SwapchainCreationCallback callback) {
|
||||
m_swapchain_creation_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
|
||||
void Context::register_swapchain_destruction_callback(SwapchainDestructionCallback callback) {
|
||||
m_swapchain_destruction_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
|
||||
void Context::register_context_destruction_callback(ContextDestructionCallback callback) {
|
||||
m_context_destruction_callbacks.emplace_back(std::move(callback));
|
||||
}
|
||||
@@ -778,184 +639,6 @@ void Context::create_device(const ContextSettings& settings) {
|
||||
vkGetDeviceQueue(m_device, m_presentation_queue_family, 0, &m_presentation_queue);
|
||||
}
|
||||
|
||||
void Context::create_swapchain(const ContextSettings& settings) {
|
||||
m_swapchain_queue_families = {
|
||||
m_presentation_queue_family,
|
||||
};
|
||||
for(size_t index = 0; index < m_queue_families.size(); index += 1) {
|
||||
if(settings.queues()[index].use_swapchain_images)
|
||||
m_swapchain_queue_families.push_back(m_queue_families[index]);
|
||||
}
|
||||
std::sort(m_swapchain_queue_families.begin(), m_swapchain_queue_families.end());
|
||||
m_swapchain_queue_families.erase(
|
||||
std::unique(m_swapchain_queue_families.begin(), m_swapchain_queue_families.end()),
|
||||
m_swapchain_queue_families.end()
|
||||
);
|
||||
|
||||
create_swapchain();
|
||||
}
|
||||
|
||||
void Context::create_swapchain() {
|
||||
VkSurfaceCapabilitiesKHR capabilities;
|
||||
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(m_physical_device, m_surface, &capabilities);
|
||||
|
||||
m_swapchain_extent = capabilities.currentExtent;
|
||||
if(m_swapchain_extent.width == UINT32_MAX) {
|
||||
int width;
|
||||
int height;
|
||||
SDL_Vulkan_GetDrawableSize(m_window, &width, &height);
|
||||
|
||||
m_swapchain_extent.width = std::clamp(
|
||||
uint32_t(width),
|
||||
capabilities.minImageExtent.width,
|
||||
capabilities.maxImageExtent.width
|
||||
);
|
||||
m_swapchain_extent.height = std::clamp(
|
||||
uint32_t(height),
|
||||
capabilities.minImageExtent.height,
|
||||
capabilities.maxImageExtent.height
|
||||
);
|
||||
}
|
||||
logger.debug() << "swapchain extent: " << m_swapchain_extent.width
|
||||
<< ", " << m_swapchain_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_swapchain_queue_families.size() > 1;
|
||||
|
||||
VkSwapchainCreateInfoKHR swapchain_info {
|
||||
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
|
||||
.surface = m_surface,
|
||||
.minImageCount = image_count,
|
||||
.imageFormat = m_surface_format.format,
|
||||
.imageColorSpace = m_surface_format.colorSpace,
|
||||
.imageExtent = m_swapchain_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_swapchain_queue_families.size()):
|
||||
0u,
|
||||
.pQueueFamilyIndices = share_image?
|
||||
m_swapchain_queue_families.data():
|
||||
nullptr,
|
||||
.preTransform = capabilities.currentTransform,
|
||||
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
|
||||
.presentMode = m_present_mode,
|
||||
.clipped = VK_TRUE,
|
||||
.oldSwapchain = VK_NULL_HANDLE,
|
||||
};
|
||||
|
||||
if(vkCreateSwapchainKHR(m_device, &swapchain_info, nullptr, &m_swapchain) != VK_SUCCESS)
|
||||
throw std::runtime_error("failed to create swapchain");
|
||||
|
||||
vkGetSwapchainImagesKHR(m_device, m_swapchain, &image_count, nullptr);
|
||||
VkImage swapchain_images[16];
|
||||
vkGetSwapchainImagesKHR(
|
||||
m_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_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_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_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_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_swapchain_creation_callbacks)
|
||||
callback(image_count);
|
||||
}
|
||||
|
||||
void Context::destroy_swapchain() {
|
||||
for(auto& callback: m_swapchain_destruction_callbacks)
|
||||
callback();
|
||||
|
||||
for(auto& frame_resources: m_frame_resources) {
|
||||
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) {
|
||||
destroy_fence(image_resources.render_done);
|
||||
destroy_image_view(image_resources.view);
|
||||
}
|
||||
|
||||
destroy_swapchain(m_swapchain);
|
||||
}
|
||||
|
||||
void Context::recreate_swapchain() {
|
||||
destroy_swapchain();
|
||||
create_swapchain();
|
||||
}
|
||||
|
||||
void Context::initialize_extension_functions() {
|
||||
uint32_t errors_count = 0;
|
||||
|
||||
|
||||
@@ -52,11 +52,6 @@ constexpr uint32_t INVALID_QUEUE_FAMILY = UINT32_MAX;
|
||||
|
||||
class Context {
|
||||
public:
|
||||
static constexpr uint32_t CURRENT_IMAGE_INDEX = UINT32_MAX;
|
||||
static constexpr uint32_t MAX_FRAMES_IN_FLIGHT = 2;
|
||||
|
||||
using SwapchainCreationCallback = std::function<void(uint32_t)>;
|
||||
using SwapchainDestructionCallback = std::function<void()>;
|
||||
using ContextDestructionCallback = std::function<void()>;
|
||||
|
||||
public:
|
||||
@@ -69,36 +64,21 @@ public:
|
||||
VkInstance instance();
|
||||
VkPhysicalDevice physical_device();
|
||||
VkDevice device();
|
||||
size_t queue_family_count() const;
|
||||
uint32_t queue_family(size_t queue_index) const;
|
||||
uint32_t presentation_queue_family() const;
|
||||
VkQueue queue(size_t queue_index);
|
||||
VkQueue presentation_queue();
|
||||
|
||||
SDL_Window* window();
|
||||
VkSurfaceKHR surface();
|
||||
VkSwapchainKHR swapchain();
|
||||
|
||||
VkSurfaceFormatKHR surface_format() const;
|
||||
VkPresentModeKHR present_mode() const;
|
||||
VkExtent2D swapchain_extent() const;
|
||||
size_t swapchain_image_count() const;
|
||||
uint32_t current_image_index() const;
|
||||
VkImage swapchain_image();
|
||||
VkImage swapchain_image(size_t image_index);
|
||||
VkImageView swapchain_image_view();
|
||||
VkImageView swapchain_image_view(size_t image_index);
|
||||
// VkFramebuffer framebuffer();
|
||||
VkSemaphore ready_to_render();
|
||||
VkFence render_done();
|
||||
|
||||
|
||||
void initialize(const ContextSettings& settings);
|
||||
void shutdown();
|
||||
|
||||
void begin_frame();
|
||||
void swap_buffers(uint32_t semaphore_count, VkSemaphore* wait_semaphores);
|
||||
|
||||
void invalidate_swapchain();
|
||||
|
||||
void register_swapchain_creation_callback(SwapchainCreationCallback callback);
|
||||
void register_swapchain_destruction_callback(SwapchainDestructionCallback callback);
|
||||
void register_context_destruction_callback(ContextDestructionCallback callback);
|
||||
|
||||
|
||||
@@ -145,18 +125,6 @@ public:
|
||||
PFN_vkDestroyDebugUtilsMessengerEXT destroyDebugUtilsMessenger = nullptr;
|
||||
PFN_vkSetDebugUtilsObjectNameEXT setDebugUtilsObjectName = nullptr;
|
||||
|
||||
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_instance(const ContextSettings& settings);
|
||||
void create_surface(const ContextSettings& settings);
|
||||
@@ -166,11 +134,6 @@ private:
|
||||
const ContextSettings& settings
|
||||
);
|
||||
void create_device(const ContextSettings& settings);
|
||||
void create_swapchain(const ContextSettings& settings);
|
||||
void create_swapchain();
|
||||
|
||||
void destroy_swapchain();
|
||||
void recreate_swapchain();
|
||||
|
||||
void initialize_extension_functions();
|
||||
|
||||
@@ -197,25 +160,10 @@ private:
|
||||
VkQueue m_presentation_queue = nullptr;
|
||||
|
||||
VkSurfaceKHR m_surface = VK_NULL_HANDLE;
|
||||
VkSwapchainKHR m_swapchain = VK_NULL_HANDLE;
|
||||
|
||||
VkSurfaceFormatKHR m_surface_format;
|
||||
VkPresentModeKHR m_present_mode;
|
||||
VkExtent2D m_swapchain_extent;
|
||||
std::vector<uint32_t> m_swapchain_queue_families;
|
||||
std::vector<ImageResources> m_image_resources;
|
||||
std::vector<FrameResources> m_frame_resources;
|
||||
bool m_invalid_swapchain = false;
|
||||
|
||||
std::vector<SwapchainCreationCallback> m_swapchain_creation_callbacks;
|
||||
std::vector<SwapchainDestructionCallback> m_swapchain_destruction_callbacks;
|
||||
std::vector<ContextDestructionCallback> m_context_destruction_callbacks;
|
||||
|
||||
uint32_t m_current_image_index = CURRENT_IMAGE_INDEX;
|
||||
size_t m_frame_index = 0;
|
||||
size_t m_frame_resources_index = 0;
|
||||
|
||||
friend class ContextSettings;
|
||||
};
|
||||
|
||||
|
||||
|
||||
393
src/Vulkan/Swapchain.cpp
Normal file
393
src/Vulkan/Swapchain.cpp
Normal file
@@ -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();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
109
src/Vulkan/Swapchain.h
Normal file
109
src/Vulkan/Swapchain.h
Normal file
@@ -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;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
@@ -47,9 +47,9 @@ const std::vector<Vertex> vertices = {
|
||||
|
||||
|
||||
VulkanTutorial::VulkanTutorial() {
|
||||
m_context.register_swapchain_creation_callback(
|
||||
m_swapchain.register_creation_callback(
|
||||
std::bind(&VulkanTutorial::create_swapchain_objects, this, std::placeholders::_1));
|
||||
m_context.register_swapchain_destruction_callback(
|
||||
m_swapchain.register_destruction_callback(
|
||||
std::bind(&VulkanTutorial::destroy_swapchain_objects, this));
|
||||
}
|
||||
|
||||
@@ -58,13 +58,17 @@ VulkanTutorial::~VulkanTutorial() {
|
||||
}
|
||||
|
||||
void VulkanTutorial::initialize(SDL_Window* window) {
|
||||
auto const settings = Vulkan::ContextSettings()
|
||||
auto const context_settings = Vulkan::ContextSettings()
|
||||
#if defined(VKEXPE_ENABLE_VALIDATION) || !defined(NDEBUG)
|
||||
.with_debug(true)
|
||||
#endif
|
||||
.with_queue(GRAPHIC_QUEUE, VK_QUEUE_GRAPHICS_BIT)
|
||||
.with_window(window);
|
||||
m_context.initialize(settings);
|
||||
m_context.initialize(context_settings);
|
||||
|
||||
auto const swapchain_settings = Vulkan::SwapchainSettings(&m_context)
|
||||
.with_queue(m_context.queue_family(GRAPHIC_QUEUE));
|
||||
m_swapchain.initialize(swapchain_settings);
|
||||
|
||||
create_command_pool();
|
||||
create_vertex_buffer();
|
||||
@@ -87,15 +91,16 @@ void VulkanTutorial::shutdown() {
|
||||
m_context.destroy_semaphore(semaphore);
|
||||
m_render_done.clear();
|
||||
|
||||
m_swapchain.shutdown();
|
||||
m_context.shutdown();
|
||||
}
|
||||
|
||||
void VulkanTutorial::draw_frame() {
|
||||
m_context.begin_frame();
|
||||
auto const image_index = m_context.current_image_index();
|
||||
m_swapchain.begin_frame();
|
||||
auto const image_index = m_swapchain.current_image_index();
|
||||
|
||||
VkSemaphore wait_semaphores[] = {
|
||||
m_context.ready_to_render(),
|
||||
m_swapchain.ready_to_render(),
|
||||
};
|
||||
VkPipelineStageFlags stages[] = {
|
||||
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
|
||||
@@ -114,15 +119,15 @@ void VulkanTutorial::draw_frame() {
|
||||
if(vkQueueSubmit(
|
||||
m_context.queue(GRAPHIC_QUEUE),
|
||||
1, &submit_info,
|
||||
m_context.render_done()
|
||||
m_swapchain.render_done()
|
||||
))
|
||||
throw std::runtime_error("failed to submit draw command buffer");
|
||||
|
||||
m_context.swap_buffers(1, &m_render_done[image_index]);
|
||||
m_swapchain.swap_buffers({1, &m_render_done[image_index]});
|
||||
}
|
||||
|
||||
void VulkanTutorial::invalidate_swapchain() {
|
||||
m_context.invalidate_swapchain();
|
||||
m_swapchain.invalidate();
|
||||
}
|
||||
|
||||
void VulkanTutorial::create_swapchain_objects(uint32_t image_count) {
|
||||
@@ -131,7 +136,7 @@ void VulkanTutorial::create_swapchain_objects(uint32_t image_count) {
|
||||
create_graphic_pipeline();
|
||||
create_command_buffers();
|
||||
|
||||
m_render_done.resize(m_context.swapchain_image_count());
|
||||
m_render_done.resize(m_swapchain.image_count());
|
||||
|
||||
VkSemaphoreCreateInfo semaphore_info {
|
||||
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
|
||||
@@ -268,15 +273,15 @@ void VulkanTutorial::create_graphic_pipeline() {
|
||||
VkViewport viewport {
|
||||
.x = 0.0f,
|
||||
.y = 0.0f,
|
||||
.width = float(m_context.swapchain_extent().width),
|
||||
.height = float(m_context.swapchain_extent().height),
|
||||
.width = float(m_swapchain.extent().width),
|
||||
.height = float(m_swapchain.extent().height),
|
||||
.minDepth = 0.0f,
|
||||
.maxDepth = 1.0f,
|
||||
};
|
||||
|
||||
VkRect2D scissor = {
|
||||
.offset = { 0, 0 },
|
||||
.extent = m_context.swapchain_extent(),
|
||||
.extent = m_swapchain.extent(),
|
||||
};
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_info {
|
||||
@@ -381,18 +386,18 @@ void VulkanTutorial::create_graphic_pipeline() {
|
||||
}
|
||||
|
||||
void VulkanTutorial::create_framebuffers() {
|
||||
m_framebuffers.resize(m_context.swapchain_image_count());
|
||||
m_framebuffers.resize(m_swapchain.image_count());
|
||||
|
||||
for(size_t index = 0; index < m_framebuffers.size(); index += 1) {
|
||||
VkImageView view = m_context.swapchain_image_view(index);
|
||||
VkImageView view = m_swapchain.image_view(index);
|
||||
|
||||
VkFramebufferCreateInfo framebuffer_info {
|
||||
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
|
||||
.renderPass = m_render_pass,
|
||||
.attachmentCount = 1,
|
||||
.pAttachments = &view,
|
||||
.width = m_context.swapchain_extent().width,
|
||||
.height = m_context.swapchain_extent().height,
|
||||
.width = m_swapchain.extent().width,
|
||||
.height = m_swapchain.extent().height,
|
||||
.layers = 1,
|
||||
};
|
||||
|
||||
@@ -547,7 +552,7 @@ void VulkanTutorial::create_command_buffers() {
|
||||
.framebuffer = m_framebuffers[index],
|
||||
.renderArea = {
|
||||
.offset = { 0, 0 },
|
||||
.extent = m_context.swapchain_extent(),
|
||||
.extent = m_swapchain.extent(),
|
||||
},
|
||||
.clearValueCount = 1,
|
||||
.pClearValues = &clear_color,
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#pragma once
|
||||
|
||||
#include <Vulkan/Context.h>
|
||||
#include <Vulkan/Swapchain.h>
|
||||
|
||||
#include <SDL2/SDL.h>
|
||||
#include <vulkan/vulkan.h>
|
||||
@@ -54,6 +55,7 @@ private:
|
||||
|
||||
private:
|
||||
Vulkan::Context m_context;
|
||||
Vulkan::Swapchain m_swapchain;
|
||||
VkQueue m_graphic_queue = nullptr;
|
||||
VkQueue m_presentation_queue = nullptr;
|
||||
|
||||
|
||||
36
src/utils.h
36
src/utils.h
@@ -1,5 +1,6 @@
|
||||
#pragma once
|
||||
|
||||
#include <cassert>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <sstream>
|
||||
@@ -38,3 +39,38 @@ std::string cat(Args&&... args) {
|
||||
}
|
||||
|
||||
std::vector<char> read_binary_file(const char* path);
|
||||
|
||||
template<typename T>
|
||||
class Array {
|
||||
public:
|
||||
Array() {
|
||||
}
|
||||
|
||||
Array(size_t size, T* data)
|
||||
: m_size(size)
|
||||
, m_data(data)
|
||||
{
|
||||
assert((size == 0 && m_data == nullptr) || (size != 0 && m_data != nullptr));
|
||||
}
|
||||
|
||||
Array(const std::vector<T>& vector)
|
||||
: m_size(vector.size())
|
||||
, m_data(vector.data())
|
||||
{}
|
||||
|
||||
explicit operator bool() const {
|
||||
return m_size != 0 && m_data != nullptr;
|
||||
}
|
||||
|
||||
size_t size() const {
|
||||
return m_size;
|
||||
}
|
||||
|
||||
T* data() const {
|
||||
return m_data;
|
||||
}
|
||||
|
||||
private:
|
||||
size_t m_size = 0;
|
||||
T* m_data = nullptr;
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user