mirror of
https://github.com/charles-lunarg/vk-bootstrap.git
synced 2024-11-10 02:41:47 +00:00
Add SwapchainManager library
This library is a utility which tracks all the required details so that applications can have a simplified interface to the swapchain. * Creation - Pass a SwapchainBuilder to specify how the swapchain should be created. * Recreation - Recreates the swapchain upon request. * Semaphores - Handles creation and usage of them, a particularly thorny part of swapchain management. Additional Libraries: - DelayedDeletionQueue; for easy deletion of objects currently in flight - ImagelessFramebufferBuilder; for easy creation of imageless framebuffers The new code lives in VkSwapchainManager.h, so its easier to distinguish the core parts of vk-bootstrap from the optional.
This commit is contained in:
parent
01de71738c
commit
6150e408ee
@ -7,6 +7,8 @@ target_link_libraries(vk-bootstrap-triangle
|
|||||||
vk-bootstrap-vulkan-headers)
|
vk-bootstrap-vulkan-headers)
|
||||||
target_include_directories(vk-bootstrap-triangle PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) #path to build directory for shaders
|
target_include_directories(vk-bootstrap-triangle PRIVATE ${CMAKE_CURRENT_BINARY_DIR}) #path to build directory for shaders
|
||||||
|
|
||||||
|
target_compile_features(vk-bootstrap-triangle PUBLIC cxx_std_17)
|
||||||
|
|
||||||
add_custom_command(
|
add_custom_command(
|
||||||
TARGET vk-bootstrap-triangle
|
TARGET vk-bootstrap-triangle
|
||||||
POST_BUILD
|
POST_BUILD
|
||||||
|
@ -4,114 +4,223 @@
|
|||||||
#include <iostream>
|
#include <iostream>
|
||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
#include <thread>
|
||||||
|
#include <chrono>
|
||||||
|
#include <mutex>
|
||||||
|
#include <atomic>
|
||||||
|
#include <condition_variable>
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
|
||||||
#include "../tests/common.h"
|
#include "../tests/common.h"
|
||||||
|
|
||||||
#include "example_config.h"
|
#include "example_config.h"
|
||||||
|
|
||||||
const int MAX_FRAMES_IN_FLIGHT = 2;
|
const size_t MAX_FRAMES_IN_FLIGHT = 2; // number of command buffers and fences
|
||||||
|
std::atomic_bool is_running;
|
||||||
|
std::atomic_bool should_resize;
|
||||||
|
|
||||||
struct Init {
|
std::mutex main_mutex;
|
||||||
|
|
||||||
|
std::mutex render_wait_mutex;
|
||||||
|
std::condition_variable render_wait_condition_variable;
|
||||||
|
|
||||||
|
const bool run_multithreaded = true;
|
||||||
|
const bool use_refresh_callback = true; // should be true for WindowsOS
|
||||||
|
const bool use_validation_layer = false; // enabling layers can cause some stuttering
|
||||||
|
const VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
||||||
|
// options are:
|
||||||
|
// VK_PRESENT_MODE_IMMEDIATE_KHR, VK_PRESENT_MODE_MAILBOX_KHR, VK_PRESENT_MODE_FIFO_KHR, VK_PRESENT_MODE_FIFO_RELAXED_KHR
|
||||||
|
|
||||||
|
struct Renderer {
|
||||||
GLFWwindow* window;
|
GLFWwindow* window;
|
||||||
VulkanLibrary vk_lib;
|
|
||||||
vkb::Instance instance;
|
vkb::Instance instance;
|
||||||
VkSurfaceKHR surface;
|
VkSurfaceKHR surface;
|
||||||
|
vkb::PhysicalDevice physical_device;
|
||||||
vkb::Device device;
|
vkb::Device device;
|
||||||
vkb::Swapchain swapchain;
|
vkb::DispatchTable dispatch;
|
||||||
//convenience
|
|
||||||
VulkanLibrary* operator->(){ return &vk_lib; }
|
|
||||||
};
|
|
||||||
|
|
||||||
struct RenderData {
|
|
||||||
VkQueue graphics_queue;
|
VkQueue graphics_queue;
|
||||||
VkQueue present_queue;
|
VkQueue present_queue;
|
||||||
|
|
||||||
std::vector<VkImage> swapchain_images;
|
vkb::SwapchainManager swapchain_manager;
|
||||||
std::vector<VkImageView> swapchain_image_views;
|
vkb::SwapchainInfo swap_info;
|
||||||
std::vector<VkFramebuffer> framebuffers;
|
|
||||||
|
vkb::DeletionQueue delete_queue;
|
||||||
|
|
||||||
VkRenderPass render_pass;
|
VkRenderPass render_pass;
|
||||||
|
VkFramebuffer framebuffer;
|
||||||
VkPipelineLayout pipeline_layout;
|
VkPipelineLayout pipeline_layout;
|
||||||
VkPipeline graphics_pipeline;
|
VkPipeline graphics_pipeline;
|
||||||
|
|
||||||
VkCommandPool command_pool;
|
VkCommandPool command_pool;
|
||||||
std::vector<VkCommandBuffer> command_buffers;
|
std::array<VkCommandBuffer, MAX_FRAMES_IN_FLIGHT> command_buffers;
|
||||||
|
std::array<VkFence, MAX_FRAMES_IN_FLIGHT> fences;
|
||||||
|
uint32_t current_index = 0;
|
||||||
|
|
||||||
std::vector<VkSemaphore> available_semaphores;
|
double current_time = 0;
|
||||||
std::vector<VkSemaphore> finished_semaphore;
|
|
||||||
std::vector<VkFence> in_flight_fences;
|
|
||||||
std::vector<VkFence> image_in_flight;
|
|
||||||
size_t current_frame = 0;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
int device_initialization (Init& init) {
|
enum class RecreateSwapchainRet { success, fail };
|
||||||
init.window = create_window_glfw ("Vulkan Triangle", true);
|
RecreateSwapchainRet recreate_swapchain(Renderer& renderer);
|
||||||
|
|
||||||
|
enum class DrawFrameRet { success, fail, out_of_date };
|
||||||
|
DrawFrameRet draw_frame(Renderer& renderer);
|
||||||
|
|
||||||
|
void glfw_resize_callback(GLFWwindow* window, int width, int height) {
|
||||||
|
if (!is_running || width == 0 || height == 0) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
should_resize = true;
|
||||||
|
bool should_notify = true;
|
||||||
|
std::unique_lock<std::mutex> lg(main_mutex);
|
||||||
|
Renderer* renderer = reinterpret_cast<Renderer*>(glfwGetWindowUserPointer(window));
|
||||||
|
auto res = recreate_swapchain(*renderer);
|
||||||
|
if (res == RecreateSwapchainRet::fail) {
|
||||||
|
is_running = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
should_resize = false; // makes draw_frame exit early instead of submitting.
|
||||||
|
if (!use_refresh_callback) {
|
||||||
|
switch (draw_frame(*renderer)) {
|
||||||
|
case (DrawFrameRet::success):
|
||||||
|
break;
|
||||||
|
case (DrawFrameRet::out_of_date): {
|
||||||
|
should_resize = true;
|
||||||
|
should_notify = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
case (DrawFrameRet::fail):
|
||||||
|
is_running = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lg.unlock();
|
||||||
|
if (!use_refresh_callback) {
|
||||||
|
if (should_notify) {
|
||||||
|
render_wait_condition_variable.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void glfw_refresh_callback(GLFWwindow* window) {
|
||||||
|
bool should_notify = false;
|
||||||
|
|
||||||
|
std::unique_lock<std::mutex> lg(main_mutex, std::try_to_lock);
|
||||||
|
if (lg.owns_lock()) {
|
||||||
|
if (!should_resize) {
|
||||||
|
should_notify = true;
|
||||||
|
Renderer* renderer = reinterpret_cast<Renderer*>(glfwGetWindowUserPointer(window));
|
||||||
|
switch (draw_frame(*renderer)) {
|
||||||
|
case (DrawFrameRet::success):
|
||||||
|
break;
|
||||||
|
case (DrawFrameRet::out_of_date): {
|
||||||
|
should_resize = true;
|
||||||
|
should_notify = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
case (DrawFrameRet::fail):
|
||||||
|
is_running = false;
|
||||||
|
should_notify = false;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
lg.unlock();
|
||||||
|
}
|
||||||
|
if (should_notify) {
|
||||||
|
render_wait_condition_variable.notify_one();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
inline VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
|
||||||
|
VkDebugUtilsMessageTypeFlagsEXT messageType,
|
||||||
|
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
|
||||||
|
void*) {
|
||||||
|
auto ms = vkb::to_string_message_severity(messageSeverity);
|
||||||
|
auto mt = vkb::to_string_message_type(messageType);
|
||||||
|
std::cerr << "[" << ms << ": " << mt << "]\n" << pCallbackData->pMessage << "\n";
|
||||||
|
|
||||||
|
return VK_FALSE; // Applications must return false here
|
||||||
|
}
|
||||||
|
|
||||||
|
int instance_initialization(Renderer& renderer) {
|
||||||
|
renderer.window = create_window_glfw("Vulkan Triangle", true);
|
||||||
|
|
||||||
|
glfwSetWindowUserPointer(renderer.window, &renderer);
|
||||||
|
|
||||||
vkb::InstanceBuilder instance_builder;
|
vkb::InstanceBuilder instance_builder;
|
||||||
auto instance_ret = instance_builder.use_default_debug_messenger ().request_validation_layers ().build ();
|
auto instance_ret = instance_builder.request_validation_layers(use_validation_layer)
|
||||||
|
.require_api_version(1, 2)
|
||||||
|
.set_debug_callback(debug_callback)
|
||||||
|
.build();
|
||||||
if (!instance_ret) {
|
if (!instance_ret) {
|
||||||
std::cout << instance_ret.error ().message () << "\n";
|
std::cout << instance_ret.error().message() << "\n";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
init.instance = instance_ret.value ();
|
renderer.instance = instance_ret.value();
|
||||||
|
|
||||||
init.vk_lib.init(init.instance);
|
glfwSetWindowSizeCallback(renderer.window, glfw_resize_callback);
|
||||||
|
if (use_refresh_callback) glfwSetWindowRefreshCallback(renderer.window, glfw_refresh_callback);
|
||||||
|
|
||||||
init.surface = create_surface_glfw (init.instance, init.window);
|
renderer.surface = create_surface_glfw(renderer.instance.instance, renderer.window);
|
||||||
|
VkPhysicalDeviceVulkan12Features features_1_2{};
|
||||||
|
features_1_2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES;
|
||||||
|
features_1_2.imagelessFramebuffer = true;
|
||||||
|
|
||||||
vkb::PhysicalDeviceSelector phys_device_selector (init.instance);
|
vkb::PhysicalDeviceSelector phys_device_selector(renderer.instance);
|
||||||
auto phys_device_ret = phys_device_selector.set_surface (init.surface).select ();
|
auto phys_device_ret = phys_device_selector.set_surface(renderer.surface).set_required_features_12(features_1_2).select();
|
||||||
if (!phys_device_ret) {
|
if (!phys_device_ret) {
|
||||||
std::cout << phys_device_ret.error ().message () << "\n";
|
std::cout << phys_device_ret.error().message() << "\n";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
vkb::PhysicalDevice physical_device = phys_device_ret.value ();
|
renderer.physical_device = phys_device_ret.value();
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
vkb::DeviceBuilder device_builder{ physical_device };
|
int device_initialization(Renderer& renderer) {
|
||||||
auto device_ret = device_builder.build ();
|
vkb::DeviceBuilder device_builder{ renderer.physical_device };
|
||||||
|
auto device_ret = device_builder.build();
|
||||||
if (!device_ret) {
|
if (!device_ret) {
|
||||||
std::cout << device_ret.error ().message () << "\n";
|
std::cout << device_ret.error().message() << "\n";
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
init.device = device_ret.value ();
|
renderer.device = device_ret.value();
|
||||||
init.vk_lib.init(init.device);
|
|
||||||
|
|
||||||
|
renderer.dispatch = renderer.device.make_table();
|
||||||
|
|
||||||
|
renderer.delete_queue = vkb::DeletionQueue(renderer.device.device, MAX_FRAMES_IN_FLIGHT);
|
||||||
|
|
||||||
|
auto swapchain_manager_ret = vkb::SwapchainManager::create(
|
||||||
|
vkb::SwapchainBuilder{ renderer.device }.set_desired_present_mode(present_mode).set_desired_extent(default_window_width, default_window_height));
|
||||||
|
if (!swapchain_manager_ret) {
|
||||||
|
std::cout << swapchain_manager_ret.error().message() << "\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
renderer.swapchain_manager = std::move(swapchain_manager_ret.value());
|
||||||
|
renderer.swap_info = renderer.swapchain_manager.get_info().value();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int create_swapchain (Init& init) {
|
int get_queues(Renderer& renderer) {
|
||||||
|
auto gq = renderer.device.get_queue(vkb::QueueType::graphics);
|
||||||
vkb::SwapchainBuilder swapchain_builder{ init.device };
|
if (!gq.has_value()) {
|
||||||
auto swap_ret = swapchain_builder.set_old_swapchain (init.swapchain).build ();
|
std::cout << "failed to get graphics queue: " << gq.error().message() << "\n";
|
||||||
if (!swap_ret) {
|
|
||||||
std::cout << swap_ret.error ().message () << " " << swap_ret.vk_result () << "\n";
|
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
vkb::destroy_swapchain(init.swapchain);
|
renderer.graphics_queue = gq.value();
|
||||||
init.swapchain = swap_ret.value ();
|
|
||||||
|
auto pq = renderer.device.get_queue(vkb::QueueType::present);
|
||||||
|
if (!pq.has_value()) {
|
||||||
|
std::cout << "failed to get present queue: " << pq.error().message() << "\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
renderer.present_queue = pq.value();
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int get_queues (Init& init, RenderData& data) {
|
int create_render_pass(Renderer& renderer) {
|
||||||
auto gq = init.device.get_queue (vkb::QueueType::graphics);
|
|
||||||
if (!gq.has_value ()) {
|
|
||||||
std::cout << "failed to get graphics queue: " << gq.error ().message () << "\n";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
data.graphics_queue = gq.value ();
|
|
||||||
|
|
||||||
auto pq = init.device.get_queue (vkb::QueueType::present);
|
|
||||||
if (!pq.has_value ()) {
|
|
||||||
std::cout << "failed to get present queue: " << pq.error ().message () << "\n";
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
data.present_queue = pq.value ();
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int create_render_pass (Init& init, RenderData& data) {
|
|
||||||
VkAttachmentDescription color_attachment = {};
|
VkAttachmentDescription color_attachment = {};
|
||||||
color_attachment.format = init.swapchain.image_format;
|
color_attachment.format = renderer.swap_info.image_format;
|
||||||
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
color_attachment.samples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||||
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
|
||||||
@ -146,51 +255,63 @@ int create_render_pass (Init& init, RenderData& data) {
|
|||||||
render_pass_info.dependencyCount = 1;
|
render_pass_info.dependencyCount = 1;
|
||||||
render_pass_info.pDependencies = &dependency;
|
render_pass_info.pDependencies = &dependency;
|
||||||
|
|
||||||
if (init->vkCreateRenderPass (init.device, &render_pass_info, nullptr, &data.render_pass) != VK_SUCCESS) {
|
if (renderer.dispatch.createRenderPass(&render_pass_info, nullptr, &renderer.render_pass) != VK_SUCCESS) {
|
||||||
std::cout << "failed to create render pass\n";
|
std::cout << "failed to create render pass\n";
|
||||||
return -1; // failed to create render pass!
|
return -1; // failed to create render pass!
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<char> readFile (const std::string& filename) {
|
int create_framebuffer(Renderer& renderer) {
|
||||||
std::ifstream file (filename, std::ios::ate | std::ios::binary);
|
|
||||||
|
|
||||||
if (!file.is_open ()) {
|
vkb::ImagelessFramebufferBuilder if_builder(renderer.device);
|
||||||
throw std::runtime_error ("failed to open file!");
|
renderer.framebuffer = if_builder.set_renderpass(renderer.render_pass)
|
||||||
|
.set_extent(renderer.swap_info.extent)
|
||||||
|
.set_layers(1)
|
||||||
|
.add_attachment(renderer.swap_info.image_usage_flags, renderer.swap_info.image_format)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::vector<char> readFile(const std::string& filename) {
|
||||||
|
std::ifstream file(filename, std::ios::ate | std::ios::binary);
|
||||||
|
|
||||||
|
if (!file.is_open()) {
|
||||||
|
throw std::runtime_error("failed to open file!");
|
||||||
}
|
}
|
||||||
|
|
||||||
size_t file_size = (size_t)file.tellg ();
|
size_t file_size = (size_t)file.tellg();
|
||||||
std::vector<char> buffer (file_size);
|
std::vector<char> buffer(file_size);
|
||||||
|
|
||||||
file.seekg (0);
|
file.seekg(0);
|
||||||
file.read (buffer.data (), static_cast<std::streamsize> (file_size));
|
file.read(buffer.data(), static_cast<std::streamsize>(file_size));
|
||||||
|
|
||||||
file.close ();
|
file.close();
|
||||||
|
|
||||||
return buffer;
|
return buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
VkShaderModule createShaderModule (Init& init, const std::vector<char>& code) {
|
VkShaderModule createShaderModule(Renderer& renderer, const std::vector<char>& code) {
|
||||||
VkShaderModuleCreateInfo create_info = {};
|
VkShaderModuleCreateInfo create_info = {};
|
||||||
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||||
create_info.codeSize = code.size ();
|
create_info.codeSize = code.size();
|
||||||
create_info.pCode = reinterpret_cast<const uint32_t*> (code.data ());
|
create_info.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
||||||
|
|
||||||
VkShaderModule shaderModule;
|
VkShaderModule shaderModule;
|
||||||
if (init->vkCreateShaderModule (init.device, &create_info, nullptr, &shaderModule) != VK_SUCCESS) {
|
if (renderer.dispatch.createShaderModule(&create_info, nullptr, &shaderModule) != VK_SUCCESS) {
|
||||||
return VK_NULL_HANDLE; // failed to create shader module
|
return VK_NULL_HANDLE; // failed to create shader module
|
||||||
}
|
}
|
||||||
|
|
||||||
return shaderModule;
|
return shaderModule;
|
||||||
}
|
}
|
||||||
|
|
||||||
int create_graphics_pipeline (Init& init, RenderData& data) {
|
int create_graphics_pipeline(Renderer& renderer) {
|
||||||
auto vert_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/vert.spv");
|
auto vert_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/vert.spv");
|
||||||
auto frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv");
|
auto frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv");
|
||||||
|
|
||||||
VkShaderModule vert_module = createShaderModule (init, vert_code);
|
VkShaderModule vert_module = createShaderModule(renderer, vert_code);
|
||||||
VkShaderModule frag_module = createShaderModule (init, frag_code);
|
VkShaderModule frag_module = createShaderModule(renderer, frag_code);
|
||||||
if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) {
|
if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) {
|
||||||
std::cout << "failed to create shader module\n";
|
std::cout << "failed to create shader module\n";
|
||||||
return -1; // failed to create shader modules
|
return -1; // failed to create shader modules
|
||||||
@ -223,14 +344,14 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
|||||||
VkViewport viewport = {};
|
VkViewport viewport = {};
|
||||||
viewport.x = 0.0f;
|
viewport.x = 0.0f;
|
||||||
viewport.y = 0.0f;
|
viewport.y = 0.0f;
|
||||||
viewport.width = (float)init.swapchain.extent.width;
|
viewport.width = (float)renderer.swap_info.extent.width;
|
||||||
viewport.height = (float)init.swapchain.extent.height;
|
viewport.height = (float)renderer.swap_info.extent.height;
|
||||||
viewport.minDepth = 0.0f;
|
viewport.minDepth = 0.0f;
|
||||||
viewport.maxDepth = 1.0f;
|
viewport.maxDepth = 1.0f;
|
||||||
|
|
||||||
VkRect2D scissor = {};
|
VkRect2D scissor = {};
|
||||||
scissor.offset = { 0, 0 };
|
scissor.offset = { 0, 0 };
|
||||||
scissor.extent = init.swapchain.extent;
|
scissor.extent = renderer.swap_info.extent;
|
||||||
|
|
||||||
VkPipelineViewportStateCreateInfo viewport_state = {};
|
VkPipelineViewportStateCreateInfo viewport_state = {};
|
||||||
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
|
||||||
@ -255,8 +376,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
|||||||
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
|
||||||
|
|
||||||
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
|
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
|
||||||
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
colorBlendAttachment.colorWriteMask =
|
||||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||||
colorBlendAttachment.blendEnable = VK_FALSE;
|
colorBlendAttachment.blendEnable = VK_FALSE;
|
||||||
|
|
||||||
VkPipelineColorBlendStateCreateInfo color_blending = {};
|
VkPipelineColorBlendStateCreateInfo color_blending = {};
|
||||||
@ -275,8 +396,7 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
|||||||
pipeline_layout_info.setLayoutCount = 0;
|
pipeline_layout_info.setLayoutCount = 0;
|
||||||
pipeline_layout_info.pushConstantRangeCount = 0;
|
pipeline_layout_info.pushConstantRangeCount = 0;
|
||||||
|
|
||||||
if (init->vkCreatePipelineLayout (
|
if (renderer.dispatch.createPipelineLayout(&pipeline_layout_info, nullptr, &renderer.pipeline_layout) != VK_SUCCESS) {
|
||||||
init.device, &pipeline_layout_info, nullptr, &data.pipeline_layout) != VK_SUCCESS) {
|
|
||||||
std::cout << "failed to create pipeline layout\n";
|
std::cout << "failed to create pipeline layout\n";
|
||||||
return -1; // failed to create pipeline layout
|
return -1; // failed to create pipeline layout
|
||||||
}
|
}
|
||||||
@ -285,8 +405,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
|||||||
|
|
||||||
VkPipelineDynamicStateCreateInfo dynamic_info = {};
|
VkPipelineDynamicStateCreateInfo dynamic_info = {};
|
||||||
dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||||
dynamic_info.dynamicStateCount = static_cast<uint32_t> (dynamic_states.size ());
|
dynamic_info.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||||
dynamic_info.pDynamicStates = dynamic_states.data ();
|
dynamic_info.pDynamicStates = dynamic_states.data();
|
||||||
|
|
||||||
VkGraphicsPipelineCreateInfo pipeline_info = {};
|
VkGraphicsPipelineCreateInfo pipeline_info = {};
|
||||||
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
|
||||||
@ -299,283 +419,268 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
|||||||
pipeline_info.pMultisampleState = &multisampling;
|
pipeline_info.pMultisampleState = &multisampling;
|
||||||
pipeline_info.pColorBlendState = &color_blending;
|
pipeline_info.pColorBlendState = &color_blending;
|
||||||
pipeline_info.pDynamicState = &dynamic_info;
|
pipeline_info.pDynamicState = &dynamic_info;
|
||||||
pipeline_info.layout = data.pipeline_layout;
|
pipeline_info.layout = renderer.pipeline_layout;
|
||||||
pipeline_info.renderPass = data.render_pass;
|
pipeline_info.renderPass = renderer.render_pass;
|
||||||
pipeline_info.subpass = 0;
|
pipeline_info.subpass = 0;
|
||||||
pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
|
pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
|
||||||
|
|
||||||
if (init->vkCreateGraphicsPipelines (
|
if (renderer.dispatch.createGraphicsPipelines(VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &renderer.graphics_pipeline) != VK_SUCCESS) {
|
||||||
init.device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &data.graphics_pipeline) != VK_SUCCESS) {
|
|
||||||
std::cout << "failed to create pipline\n";
|
std::cout << "failed to create pipline\n";
|
||||||
return -1; // failed to create graphics pipeline
|
return -1; // failed to create graphics pipeline
|
||||||
}
|
}
|
||||||
|
|
||||||
init->vkDestroyShaderModule (init.device, frag_module, nullptr);
|
renderer.dispatch.destroyShaderModule(frag_module, nullptr);
|
||||||
init->vkDestroyShaderModule (init.device, vert_module, nullptr);
|
renderer.dispatch.destroyShaderModule(vert_module, nullptr);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int create_framebuffers (Init& init, RenderData& data) {
|
int create_command_buffers(Renderer& renderer) {
|
||||||
data.swapchain_images = init.swapchain.get_images ().value ();
|
VkCommandPoolCreateInfo pool_info{};
|
||||||
data.swapchain_image_views = init.swapchain.get_image_views ().value ();
|
|
||||||
|
|
||||||
data.framebuffers.resize (data.swapchain_image_views.size ());
|
|
||||||
|
|
||||||
for (size_t i = 0; i < data.swapchain_image_views.size (); i++) {
|
|
||||||
VkImageView attachments[] = { data.swapchain_image_views[i] };
|
|
||||||
|
|
||||||
VkFramebufferCreateInfo framebuffer_info = {};
|
|
||||||
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
|
||||||
framebuffer_info.renderPass = data.render_pass;
|
|
||||||
framebuffer_info.attachmentCount = 1;
|
|
||||||
framebuffer_info.pAttachments = attachments;
|
|
||||||
framebuffer_info.width = init.swapchain.extent.width;
|
|
||||||
framebuffer_info.height = init.swapchain.extent.height;
|
|
||||||
framebuffer_info.layers = 1;
|
|
||||||
|
|
||||||
if (init->vkCreateFramebuffer (init.device, &framebuffer_info, nullptr, &data.framebuffers[i]) != VK_SUCCESS) {
|
|
||||||
return -1; // failed to create framebuffer
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int create_command_pool (Init& init, RenderData& data) {
|
|
||||||
VkCommandPoolCreateInfo pool_info = {};
|
|
||||||
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||||
pool_info.queueFamilyIndex = init.device.get_queue_index (vkb::QueueType::graphics).value ();
|
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||||
|
pool_info.queueFamilyIndex = renderer.device.get_queue_index(vkb::QueueType::graphics).value();
|
||||||
if (init->vkCreateCommandPool (init.device, &pool_info, nullptr, &data.command_pool) != VK_SUCCESS) {
|
if (renderer.dispatch.createCommandPool(&pool_info, nullptr, &renderer.command_pool) != VK_SUCCESS) {
|
||||||
std::cout << "failed to create command pool\n";
|
std::cout << "failed to create command pool\n";
|
||||||
return -1; // failed to create command pool
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkCommandBufferAllocateInfo info{};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
||||||
|
info.commandPool = renderer.command_pool;
|
||||||
|
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
||||||
|
info.commandBufferCount = static_cast<uint32_t>(MAX_FRAMES_IN_FLIGHT);
|
||||||
|
if (renderer.dispatch.allocateCommandBuffers(&info, renderer.command_buffers.data()) != VK_SUCCESS) {
|
||||||
|
std::cout << "failed to allocate command buffers\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFenceCreateInfo fence_info{};
|
||||||
|
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
||||||
|
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
||||||
|
for (auto& fence : renderer.fences) {
|
||||||
|
if (renderer.dispatch.createFence(&fence_info, nullptr, &fence) != VK_SUCCESS) {
|
||||||
|
std::cout << "failed to create fence\n";
|
||||||
|
return -1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int create_command_buffers (Init& init, RenderData& data) {
|
int record_command_buffer(Renderer& renderer, VkCommandBuffer command_buffer, VkImageView image_view) {
|
||||||
data.command_buffers.resize (data.framebuffers.size ());
|
|
||||||
|
|
||||||
VkCommandBufferAllocateInfo allocInfo = {};
|
|
||||||
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
|
|
||||||
allocInfo.commandPool = data.command_pool;
|
|
||||||
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
|
|
||||||
allocInfo.commandBufferCount = (uint32_t)data.command_buffers.size ();
|
|
||||||
|
|
||||||
if (init->vkAllocateCommandBuffers (init.device, &allocInfo, data.command_buffers.data ()) != VK_SUCCESS) {
|
|
||||||
return -1; // failed to allocate command buffers;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (size_t i = 0; i < data.command_buffers.size (); i++) {
|
|
||||||
VkCommandBufferBeginInfo begin_info = {};
|
VkCommandBufferBeginInfo begin_info = {};
|
||||||
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
|
||||||
|
|
||||||
if (init->vkBeginCommandBuffer (data.command_buffers[i], &begin_info) != VK_SUCCESS) {
|
if (renderer.dispatch.beginCommandBuffer(command_buffer, &begin_info) != VK_SUCCESS) {
|
||||||
|
std::cout << "failed to begin recording command buffer\n";
|
||||||
return -1; // failed to begin recording command buffer
|
return -1; // failed to begin recording command buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
VkRenderPassAttachmentBeginInfo attach_begin_info{};
|
||||||
|
attach_begin_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_ATTACHMENT_BEGIN_INFO;
|
||||||
|
attach_begin_info.attachmentCount = 1;
|
||||||
|
attach_begin_info.pAttachments = &image_view;
|
||||||
|
|
||||||
VkRenderPassBeginInfo render_pass_info = {};
|
VkRenderPassBeginInfo render_pass_info = {};
|
||||||
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||||
render_pass_info.renderPass = data.render_pass;
|
render_pass_info.pNext = &attach_begin_info;
|
||||||
render_pass_info.framebuffer = data.framebuffers[i];
|
render_pass_info.renderPass = renderer.render_pass;
|
||||||
|
render_pass_info.framebuffer = renderer.framebuffer;
|
||||||
render_pass_info.renderArea.offset = { 0, 0 };
|
render_pass_info.renderArea.offset = { 0, 0 };
|
||||||
render_pass_info.renderArea.extent = init.swapchain.extent;
|
render_pass_info.renderArea.extent = renderer.swap_info.extent;
|
||||||
VkClearValue clearColor{ { { 0.0f, 0.0f, 0.0f, 1.0f } } };
|
|
||||||
|
float x = static_cast<float>(std::sin(renderer.current_time * 1.5) * 0.5 + 0.5);
|
||||||
|
float z = static_cast<float>(std::cos(renderer.current_time * 1.5) * 0.5 + 0.5);
|
||||||
|
|
||||||
|
VkClearValue clearColor{ { { x, 0.0f, z, 1.0f } } };
|
||||||
render_pass_info.clearValueCount = 1;
|
render_pass_info.clearValueCount = 1;
|
||||||
render_pass_info.pClearValues = &clearColor;
|
render_pass_info.pClearValues = &clearColor;
|
||||||
|
|
||||||
VkViewport viewport = {};
|
VkViewport viewport = {};
|
||||||
viewport.x = 0.0f;
|
viewport.x = 0.0f;
|
||||||
viewport.y = 0.0f;
|
viewport.y = 0.0f;
|
||||||
viewport.width = (float)init.swapchain.extent.width;
|
viewport.width = (float)renderer.swap_info.extent.width;
|
||||||
viewport.height = (float)init.swapchain.extent.height;
|
viewport.height = (float)renderer.swap_info.extent.height;
|
||||||
viewport.minDepth = 0.0f;
|
viewport.minDepth = 0.0f;
|
||||||
viewport.maxDepth = 1.0f;
|
viewport.maxDepth = 1.0f;
|
||||||
|
|
||||||
VkRect2D scissor = {};
|
VkRect2D scissor = {};
|
||||||
scissor.offset = { 0, 0 };
|
scissor.offset = { 0, 0 };
|
||||||
scissor.extent = init.swapchain.extent;
|
scissor.extent = renderer.swap_info.extent;
|
||||||
|
|
||||||
init->vkCmdSetViewport (data.command_buffers[i], 0, 1, &viewport);
|
renderer.dispatch.cmdSetViewport(command_buffer, 0, 1, &viewport);
|
||||||
init->vkCmdSetScissor (data.command_buffers[i], 0, 1, &scissor);
|
renderer.dispatch.cmdSetScissor(command_buffer, 0, 1, &scissor);
|
||||||
|
|
||||||
init->vkCmdBeginRenderPass (data.command_buffers[i], &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
|
renderer.dispatch.cmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE);
|
||||||
|
|
||||||
init->vkCmdBindPipeline (data.command_buffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, data.graphics_pipeline);
|
renderer.dispatch.cmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, renderer.graphics_pipeline);
|
||||||
|
|
||||||
init->vkCmdDraw (data.command_buffers[i], 3, 1, 0, 0);
|
renderer.dispatch.cmdDraw(command_buffer, 3, 1, 0, 0);
|
||||||
|
|
||||||
init->vkCmdEndRenderPass (data.command_buffers[i]);
|
renderer.dispatch.cmdEndRenderPass(command_buffer);
|
||||||
|
|
||||||
if (init->vkEndCommandBuffer (data.command_buffers[i]) != VK_SUCCESS) {
|
if (renderer.dispatch.endCommandBuffer(command_buffer) != VK_SUCCESS) {
|
||||||
std::cout << "failed to record command buffer\n";
|
std::cout << "failed to record command buffer\n";
|
||||||
return -1; // failed to record command buffer!
|
return -1; // failed to record command buffer!
|
||||||
}
|
}
|
||||||
}
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
int create_sync_objects (Init& init, RenderData& data) {
|
RecreateSwapchainRet recreate_swapchain(Renderer& renderer) {
|
||||||
data.available_semaphores.resize (MAX_FRAMES_IN_FLIGHT);
|
renderer.delete_queue.add_framebuffer(renderer.framebuffer);
|
||||||
data.finished_semaphore.resize (MAX_FRAMES_IN_FLIGHT);
|
renderer.framebuffer = VK_NULL_HANDLE;
|
||||||
data.in_flight_fences.resize (MAX_FRAMES_IN_FLIGHT);
|
auto ret = renderer.swapchain_manager.recreate();
|
||||||
data.image_in_flight.resize (init.swapchain.image_count, VK_NULL_HANDLE);
|
if (!ret) {
|
||||||
|
std::cout << "failed to recreate swapchain\n";
|
||||||
VkSemaphoreCreateInfo semaphore_info = {};
|
return RecreateSwapchainRet::fail;
|
||||||
semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
|
||||||
|
|
||||||
VkFenceCreateInfo fence_info = {};
|
|
||||||
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
|
|
||||||
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
|
|
||||||
|
|
||||||
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
|
||||||
if (init->vkCreateSemaphore (init.device, &semaphore_info, nullptr, &data.available_semaphores[i]) != VK_SUCCESS ||
|
|
||||||
init->vkCreateSemaphore (init.device, &semaphore_info, nullptr, &data.finished_semaphore[i]) != VK_SUCCESS ||
|
|
||||||
init->vkCreateFence (init.device, &fence_info, nullptr, &data.in_flight_fences[i]) != VK_SUCCESS) {
|
|
||||||
std::cout << "failed to create sync objects\n";
|
|
||||||
return -1; // failed to create synchronization objects for a frame
|
|
||||||
}
|
}
|
||||||
}
|
renderer.swap_info = ret.value();
|
||||||
return 0;
|
if (0 != create_framebuffer(renderer)) return RecreateSwapchainRet::fail;
|
||||||
|
return RecreateSwapchainRet::success;
|
||||||
}
|
}
|
||||||
|
|
||||||
int recreate_swapchain (Init& init, RenderData& data) {
|
DrawFrameRet draw_frame(Renderer& renderer) {
|
||||||
init->vkDeviceWaitIdle (init.device);
|
|
||||||
|
|
||||||
init->vkDestroyCommandPool (init.device, data.command_pool, nullptr);
|
vkb::SwapchainAcquireInfo acquire_info;
|
||||||
|
auto acquire_ret = renderer.swapchain_manager.acquire_image();
|
||||||
for (auto framebuffer : data.framebuffers) {
|
if (acquire_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
|
||||||
init->vkDestroyFramebuffer (init.device, framebuffer, nullptr);
|
return DrawFrameRet::out_of_date;
|
||||||
|
} else if (!acquire_ret.has_value()) {
|
||||||
|
std::cout << "failed to acquire swapchain image\n";
|
||||||
|
return DrawFrameRet::fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
init.swapchain.destroy_image_views (data.swapchain_image_views);
|
acquire_info = acquire_ret.value();
|
||||||
|
if (should_resize) {
|
||||||
if (0 != create_swapchain (init)) return -1;
|
renderer.swapchain_manager.cancel_acquire_frame();
|
||||||
if (0 != create_framebuffers (init, data)) return -1;
|
return DrawFrameRet::out_of_date;
|
||||||
if (0 != create_command_pool (init, data)) return -1;
|
|
||||||
if (0 != create_command_buffers (init, data)) return -1;
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int draw_frame (Init& init, RenderData& data) {
|
|
||||||
init->vkWaitForFences (init.device, 1, &data.in_flight_fences[data.current_frame], VK_TRUE, UINT64_MAX);
|
|
||||||
|
|
||||||
uint32_t image_index = 0;
|
|
||||||
VkResult result = init->vkAcquireNextImageKHR (init.device,
|
|
||||||
init.swapchain,
|
|
||||||
UINT64_MAX,
|
|
||||||
data.available_semaphores[data.current_frame],
|
|
||||||
VK_NULL_HANDLE,
|
|
||||||
&image_index);
|
|
||||||
|
|
||||||
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
|
||||||
return recreate_swapchain (init, data);
|
|
||||||
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
|
|
||||||
std::cout << "failed to acquire swapchain image. Error " << result << "\n";
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (data.image_in_flight[image_index] != VK_NULL_HANDLE) {
|
renderer.dispatch.waitForFences(1, &renderer.fences[renderer.current_index], VK_TRUE, UINT64_MAX);
|
||||||
init->vkWaitForFences (init.device, 1, &data.image_in_flight[image_index], VK_TRUE, UINT64_MAX);
|
|
||||||
|
record_command_buffer(renderer, renderer.command_buffers[renderer.current_index], acquire_info.image_view);
|
||||||
|
|
||||||
|
VkSemaphore wait_semaphores[1] = { acquire_info.wait_semaphore };
|
||||||
|
VkSemaphore signal_semaphores[1] = { acquire_info.signal_semaphore };
|
||||||
|
VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
||||||
|
|
||||||
|
VkSubmitInfo submit_info = {};
|
||||||
|
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
|
submit_info.waitSemaphoreCount = 1;
|
||||||
|
submit_info.pWaitSemaphores = wait_semaphores;
|
||||||
|
submit_info.pWaitDstStageMask = wait_stages;
|
||||||
|
submit_info.commandBufferCount = 1;
|
||||||
|
submit_info.pCommandBuffers = &renderer.command_buffers[renderer.current_index];
|
||||||
|
submit_info.signalSemaphoreCount = 1;
|
||||||
|
submit_info.pSignalSemaphores = signal_semaphores;
|
||||||
|
|
||||||
|
if (should_resize) {
|
||||||
|
renderer.swapchain_manager.cancel_acquire_frame();
|
||||||
|
return DrawFrameRet::out_of_date;
|
||||||
}
|
}
|
||||||
data.image_in_flight[image_index] = data.in_flight_fences[data.current_frame];
|
// only reset if we are going to submit, this prevents the issue where we reset the fence but try to wait on it again
|
||||||
|
renderer.dispatch.resetFences(1, &renderer.fences[renderer.current_index]);
|
||||||
VkSubmitInfo submitInfo = {};
|
if (renderer.dispatch.queueSubmit(renderer.graphics_queue, 1, &submit_info, renderer.fences[renderer.current_index]) != VK_SUCCESS) {
|
||||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
std::cout << "failed to submit command buffer\n";
|
||||||
|
return DrawFrameRet::fail;
|
||||||
VkSemaphore wait_semaphores[] = { data.available_semaphores[data.current_frame] };
|
|
||||||
VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
|
||||||
submitInfo.waitSemaphoreCount = 1;
|
|
||||||
submitInfo.pWaitSemaphores = wait_semaphores;
|
|
||||||
submitInfo.pWaitDstStageMask = wait_stages;
|
|
||||||
|
|
||||||
submitInfo.commandBufferCount = 1;
|
|
||||||
submitInfo.pCommandBuffers = &data.command_buffers[image_index];
|
|
||||||
|
|
||||||
VkSemaphore signal_semaphores[] = { data.finished_semaphore[data.current_frame] };
|
|
||||||
submitInfo.signalSemaphoreCount = 1;
|
|
||||||
submitInfo.pSignalSemaphores = signal_semaphores;
|
|
||||||
|
|
||||||
init->vkResetFences (init.device, 1, &data.in_flight_fences[data.current_frame]);
|
|
||||||
|
|
||||||
if (init->vkQueueSubmit (data.graphics_queue, 1, &submitInfo, data.in_flight_fences[data.current_frame]) != VK_SUCCESS) {
|
|
||||||
std::cout << "failed to submit draw command buffer\n";
|
|
||||||
return -1; //"failed to submit draw command buffer
|
|
||||||
}
|
}
|
||||||
|
renderer.current_index = (renderer.current_index + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||||
|
|
||||||
VkPresentInfoKHR present_info = {};
|
// No need to cancel, if a resize has started, then present will bail
|
||||||
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
auto present_ret = renderer.swapchain_manager.present();
|
||||||
|
|
||||||
present_info.waitSemaphoreCount = 1;
|
if (present_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
|
||||||
present_info.pWaitSemaphores = signal_semaphores;
|
return DrawFrameRet::out_of_date;
|
||||||
|
} else if (!present_ret) {
|
||||||
VkSwapchainKHR swapChains[] = { init.swapchain };
|
|
||||||
present_info.swapchainCount = 1;
|
|
||||||
present_info.pSwapchains = swapChains;
|
|
||||||
|
|
||||||
present_info.pImageIndices = &image_index;
|
|
||||||
|
|
||||||
result = init->vkQueuePresentKHR (data.present_queue, &present_info);
|
|
||||||
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) {
|
|
||||||
return recreate_swapchain (init, data);
|
|
||||||
} else if (result != VK_SUCCESS) {
|
|
||||||
std::cout << "failed to present swapchain image\n";
|
std::cout << "failed to present swapchain image\n";
|
||||||
return -1;
|
return DrawFrameRet::fail;
|
||||||
}
|
}
|
||||||
|
renderer.delete_queue.tick();
|
||||||
|
renderer.current_time = glfwGetTime();
|
||||||
|
return DrawFrameRet::success;
|
||||||
|
}
|
||||||
|
|
||||||
data.current_frame = (data.current_frame + 1) % MAX_FRAMES_IN_FLIGHT;
|
void cleanup(Renderer& renderer) {
|
||||||
return 0;
|
renderer.dispatch.deviceWaitIdle();
|
||||||
}
|
|
||||||
|
for (auto& fence : renderer.fences) {
|
||||||
void cleanup (Init& init, RenderData& data) {
|
renderer.dispatch.destroyFence(fence, nullptr);
|
||||||
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
}
|
||||||
init->vkDestroySemaphore (init.device, data.finished_semaphore[i], nullptr);
|
renderer.dispatch.destroyCommandPool(renderer.command_pool, nullptr);
|
||||||
init->vkDestroySemaphore (init.device, data.available_semaphores[i], nullptr);
|
|
||||||
init->vkDestroyFence (init.device, data.in_flight_fences[i], nullptr);
|
renderer.dispatch.destroyPipeline(renderer.graphics_pipeline, nullptr);
|
||||||
}
|
renderer.dispatch.destroyPipelineLayout(renderer.pipeline_layout, nullptr);
|
||||||
|
renderer.dispatch.destroyFramebuffer(renderer.framebuffer, nullptr);
|
||||||
init->vkDestroyCommandPool (init.device, data.command_pool, nullptr);
|
renderer.dispatch.destroyRenderPass(renderer.render_pass, nullptr);
|
||||||
|
|
||||||
for (auto framebuffer : data.framebuffers) {
|
renderer.delete_queue.destroy();
|
||||||
init->vkDestroyFramebuffer (init.device, framebuffer, nullptr);
|
renderer.swapchain_manager.destroy();
|
||||||
}
|
|
||||||
|
vkb::destroy_device(renderer.device);
|
||||||
init->vkDestroyPipeline (init.device, data.graphics_pipeline, nullptr);
|
vkb::destroy_surface(renderer.instance, renderer.surface);
|
||||||
init->vkDestroyPipelineLayout (init.device, data.pipeline_layout, nullptr);
|
vkb::destroy_instance(renderer.instance);
|
||||||
init->vkDestroyRenderPass (init.device, data.render_pass, nullptr);
|
destroy_window_glfw(renderer.window);
|
||||||
|
}
|
||||||
init.swapchain.destroy_image_views (data.swapchain_image_views);
|
|
||||||
|
|
||||||
vkb::destroy_swapchain (init.swapchain);
|
void render_loop(Renderer* renderer) {
|
||||||
vkb::destroy_device (init.device);
|
while (is_running) {
|
||||||
vkb::destroy_surface(init.instance, init.surface);
|
std::unique_lock<std::mutex> lg(main_mutex, std::try_to_lock);
|
||||||
vkb::destroy_instance (init.instance);
|
if (lg.owns_lock()) {
|
||||||
destroy_window_glfw (init.window);
|
switch (draw_frame(*renderer)) {
|
||||||
}
|
case (DrawFrameRet::success):
|
||||||
|
break;
|
||||||
int main () {
|
case (DrawFrameRet::out_of_date): {
|
||||||
Init init;
|
lg.unlock();
|
||||||
RenderData render_data;
|
std::unique_lock<std::mutex> ulg(render_wait_mutex);
|
||||||
|
render_wait_condition_variable.wait(ulg);
|
||||||
if (0 != device_initialization (init)) return -1;
|
break;
|
||||||
if (0 != create_swapchain (init)) return -1;
|
}
|
||||||
if (0 != get_queues (init, render_data)) return -1;
|
default:
|
||||||
if (0 != create_render_pass (init, render_data)) return -1;
|
case (DrawFrameRet::fail):
|
||||||
if (0 != create_graphics_pipeline (init, render_data)) return -1;
|
is_running = false;
|
||||||
if (0 != create_framebuffers (init, render_data)) return -1;
|
break;
|
||||||
if (0 != create_command_pool (init, render_data)) return -1;
|
}
|
||||||
if (0 != create_command_buffers (init, render_data)) return -1;
|
} else {
|
||||||
if (0 != create_sync_objects (init, render_data)) return -1;
|
std::unique_lock<std::mutex> ulg(render_wait_mutex);
|
||||||
|
render_wait_condition_variable.wait(ulg);
|
||||||
while (!glfwWindowShouldClose (init.window)) {
|
}
|
||||||
glfwPollEvents ();
|
}
|
||||||
int res = draw_frame (init, render_data);
|
renderer->dispatch.deviceWaitIdle();
|
||||||
if (res != 0) {
|
}
|
||||||
std::cout << "failed to draw frame \n";
|
|
||||||
return -1;
|
int main() {
|
||||||
}
|
is_running = false;
|
||||||
}
|
should_resize = false;
|
||||||
init->vkDeviceWaitIdle (init.device);
|
Renderer renderer;
|
||||||
|
|
||||||
cleanup (init, render_data);
|
if (0 != instance_initialization(renderer)) return -1;
|
||||||
|
if (0 != device_initialization(renderer)) return -1;
|
||||||
|
if (0 != get_queues(renderer)) return -1;
|
||||||
|
if (0 != create_render_pass(renderer)) return -1;
|
||||||
|
if (0 != create_framebuffer(renderer)) return -1;
|
||||||
|
if (0 != create_graphics_pipeline(renderer)) return -1;
|
||||||
|
if (0 != create_command_buffers(renderer)) return -1;
|
||||||
|
is_running = true;
|
||||||
|
renderer.current_time = glfwGetTime();
|
||||||
|
if (run_multithreaded) {
|
||||||
|
std::thread render_thread{ render_loop, &renderer };
|
||||||
|
|
||||||
|
while (!glfwWindowShouldClose(renderer.window) && is_running) {
|
||||||
|
glfwPollEvents();
|
||||||
|
glfwWaitEvents();
|
||||||
|
}
|
||||||
|
is_running = false;
|
||||||
|
render_wait_condition_variable.notify_one();
|
||||||
|
render_thread.join();
|
||||||
|
} else {
|
||||||
|
while (!glfwWindowShouldClose(renderer.window) && is_running) {
|
||||||
|
glfwPollEvents();
|
||||||
|
DrawFrameRet res = draw_frame(renderer);
|
||||||
|
if (res == DrawFrameRet::fail) {
|
||||||
|
is_running = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cleanup(renderer);
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
#include "VkBootstrap.h"
|
#include "VkBootstrap.h"
|
||||||
|
#include "VkSwapchainManager.h"
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@ -30,9 +31,12 @@
|
|||||||
#include <dlfcn.h>
|
#include <dlfcn.h>
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <mutex>
|
#include <mutex>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
#include <iostream>
|
||||||
|
|
||||||
namespace vkb {
|
namespace vkb {
|
||||||
|
|
||||||
namespace detail {
|
namespace detail {
|
||||||
@ -370,6 +374,12 @@ struct SwapchainErrorCategory : std::error_category {
|
|||||||
};
|
};
|
||||||
const SwapchainErrorCategory swapchain_error_category;
|
const SwapchainErrorCategory swapchain_error_category;
|
||||||
|
|
||||||
|
struct SwapchainManagerErrorCategory : std::error_category {
|
||||||
|
const char* name() const noexcept override { return "vkb_swapchain_manager"; }
|
||||||
|
std::string message(int err) const override { return to_string(static_cast<SwapchainManagerError>(err)); }
|
||||||
|
};
|
||||||
|
const SwapchainManagerErrorCategory swapchain_manager_error_category;
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
std::error_code make_error_code(InstanceError instance_error) {
|
std::error_code make_error_code(InstanceError instance_error) {
|
||||||
@ -387,6 +397,10 @@ std::error_code make_error_code(DeviceError device_error) {
|
|||||||
std::error_code make_error_code(SwapchainError swapchain_error) {
|
std::error_code make_error_code(SwapchainError swapchain_error) {
|
||||||
return { static_cast<int>(swapchain_error), detail::swapchain_error_category };
|
return { static_cast<int>(swapchain_error), detail::swapchain_error_category };
|
||||||
}
|
}
|
||||||
|
std::error_code make_error_code(SwapchainManagerError swapchain_manager_error) {
|
||||||
|
return { static_cast<int>(swapchain_manager_error), detail::swapchain_manager_error_category };
|
||||||
|
}
|
||||||
|
|
||||||
#define CASE_TO_STRING(CATEGORY, TYPE) \
|
#define CASE_TO_STRING(CATEGORY, TYPE) \
|
||||||
case CATEGORY::TYPE: \
|
case CATEGORY::TYPE: \
|
||||||
return #TYPE;
|
return #TYPE;
|
||||||
@ -446,6 +460,25 @@ const char* to_string(SwapchainError err) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const char* to_string(SwapchainManagerError err) {
|
||||||
|
switch (err) {
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, swapchain_suboptimal)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, swapchain_out_of_date)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, surface_lost)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, queue_submit_failed)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, must_call_acquire_image_first)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, acquire_next_image_error)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, queue_present_error)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, surface_handle_not_provided)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, failed_query_surface_support_details)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, failed_create_swapchain)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, failed_get_swapchain_images)
|
||||||
|
CASE_TO_STRING(SwapchainManagerError, failed_create_swapchain_image_views)
|
||||||
|
default:
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#undef CASE_TO_STRING
|
||||||
|
|
||||||
Result<SystemInfo> SystemInfo::get_system_info() {
|
Result<SystemInfo> SystemInfo::get_system_info() {
|
||||||
if (!detail::vulkan_functions().init_vulkan_funcs(nullptr)) {
|
if (!detail::vulkan_functions().init_vulkan_funcs(nullptr)) {
|
||||||
@ -2017,4 +2050,560 @@ void SwapchainBuilder::add_desired_present_modes(std::vector<VkPresentModeKHR>&
|
|||||||
modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
|
modes.push_back(VK_PRESENT_MODE_MAILBOX_KHR);
|
||||||
modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
|
modes.push_back(VK_PRESENT_MODE_FIFO_KHR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Imageless framebuffer builder
|
||||||
|
|
||||||
|
ImagelessFramebufferBuilder::ImagelessFramebufferBuilder(Device const& device) noexcept : device(device.device) {
|
||||||
|
fp_vkCreateFramebuffer =
|
||||||
|
reinterpret_cast<PFN_vkCreateFramebuffer>(device.fp_vkGetDeviceProcAddr(device.device, "vkCreateFramebuffer"));
|
||||||
|
fp_vkDestroyFramebuffer =
|
||||||
|
reinterpret_cast<PFN_vkDestroyFramebuffer>(device.fp_vkGetDeviceProcAddr(device.device, "vkDestroyFramebuffer"));
|
||||||
|
}
|
||||||
|
VkFramebuffer ImagelessFramebufferBuilder::build() noexcept {
|
||||||
|
std::vector<VkFramebufferAttachmentImageInfo> attachment_infos;
|
||||||
|
for (auto& attachment : _attachments) {
|
||||||
|
VkFramebufferAttachmentImageInfo attach_image_info{};
|
||||||
|
attach_image_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENT_IMAGE_INFO;
|
||||||
|
attach_image_info.usage = attachment.usage_flags;
|
||||||
|
attach_image_info.width = _width;
|
||||||
|
attach_image_info.height = _height;
|
||||||
|
attach_image_info.layerCount = _layers;
|
||||||
|
attach_image_info.viewFormatCount = static_cast<uint32_t>(attachment.formats.size());
|
||||||
|
attach_image_info.pViewFormats = attachment.formats.data();
|
||||||
|
attachment_infos.push_back(attach_image_info);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkFramebufferAttachmentsCreateInfo attach_create_info{};
|
||||||
|
attach_create_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_ATTACHMENTS_CREATE_INFO;
|
||||||
|
attach_create_info.attachmentImageInfoCount = static_cast<uint32_t>(attachment_infos.size());
|
||||||
|
attach_create_info.pAttachmentImageInfos = attachment_infos.data();
|
||||||
|
|
||||||
|
VkFramebufferCreateInfo framebuffer_info = {};
|
||||||
|
framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
|
||||||
|
framebuffer_info.pNext = &attach_create_info;
|
||||||
|
framebuffer_info.flags = VK_FRAMEBUFFER_CREATE_IMAGELESS_BIT;
|
||||||
|
framebuffer_info.renderPass = _render_pass;
|
||||||
|
framebuffer_info.attachmentCount = static_cast<uint32_t>(attachment_infos.size());
|
||||||
|
framebuffer_info.width = _width;
|
||||||
|
framebuffer_info.height = _height;
|
||||||
|
framebuffer_info.layers = _layers;
|
||||||
|
|
||||||
|
VkFramebuffer framebuffer = VK_NULL_HANDLE;
|
||||||
|
if (fp_vkCreateFramebuffer(device, &framebuffer_info, nullptr, &framebuffer) != VK_SUCCESS) {
|
||||||
|
assert(false && "TODO");
|
||||||
|
}
|
||||||
|
return framebuffer;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_renderpass(VkRenderPass render_pass) {
|
||||||
|
_render_pass = render_pass;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_width(uint32_t width) {
|
||||||
|
_width = width;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_height(uint32_t height) {
|
||||||
|
_height = height;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_extent(VkExtent2D extent) {
|
||||||
|
_width = extent.width;
|
||||||
|
_height = extent.height;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::set_layers(uint32_t layer_count) {
|
||||||
|
_layers = layer_count;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::add_attachment(VkImageUsageFlags usage_flags, VkFormat format) {
|
||||||
|
_attachments.push_back(Attachment{ usage_flags, std::vector<VkFormat>(1, format) });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
ImagelessFramebufferBuilder& ImagelessFramebufferBuilder::add_attachment(
|
||||||
|
VkImageUsageFlags usage_flags, std::vector<VkFormat> const& formats) {
|
||||||
|
_attachments.push_back(Attachment{ usage_flags, formats });
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
DeletionQueue ::DeletionQueue(Device const& device, uint32_t deletion_delay) noexcept
|
||||||
|
: DeletionQueue(device.device, deletion_delay) {}
|
||||||
|
|
||||||
|
DeletionQueue::DeletionQueue(VkDevice device, uint32_t queue_depth) noexcept
|
||||||
|
: device(device), queue_depth(queue_depth) {
|
||||||
|
assert(queue_depth <= detail::MAX_SWAPCHAIN_IMAGE_COUNT &&
|
||||||
|
"queue_depth cannot exceed the max swapchain image count (8)");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyImage, "vkDestroyImage");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyImageView, "vkDestroyImageView");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroyFramebuffer, "vkDestroyFramebuffer");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, internal_table.fp_vkDestroySwapchainKHR, "vkDestroySwapchainKHR");
|
||||||
|
for (uint32_t i = 0; i < queue_depth; i++) {
|
||||||
|
sets[i].images.reserve(10);
|
||||||
|
sets[i].views.reserve(10);
|
||||||
|
sets[i].framebuffers.reserve(10);
|
||||||
|
sets[i].swapchains.reserve(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DeletionQueue::~DeletionQueue() noexcept { destroy(); }
|
||||||
|
|
||||||
|
DeletionQueue::DeletionQueue(DeletionQueue&& other) noexcept {
|
||||||
|
device = other.device;
|
||||||
|
queue_depth = other.queue_depth;
|
||||||
|
current_index = other.current_index;
|
||||||
|
sets = std::move(other.sets);
|
||||||
|
internal_table = other.internal_table;
|
||||||
|
other.device = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
DeletionQueue& DeletionQueue::operator=(DeletionQueue&& other) noexcept {
|
||||||
|
destroy();
|
||||||
|
device = other.device;
|
||||||
|
queue_depth = other.queue_depth;
|
||||||
|
current_index = other.current_index;
|
||||||
|
sets = std::move(other.sets);
|
||||||
|
internal_table = other.internal_table;
|
||||||
|
other.device = VK_NULL_HANDLE;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
void DeletionQueue::add_image(VkImage image) noexcept { sets[current_index].images.push_back(image); }
|
||||||
|
void DeletionQueue::add_images(uint32_t images_count, const VkImage* images) noexcept {
|
||||||
|
for (size_t i = 0; i < images_count; i++) {
|
||||||
|
sets[current_index].images.push_back(images[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DeletionQueue::add_image_view(VkImageView image_view) noexcept { sets[current_index].views.push_back(image_view); }
|
||||||
|
void DeletionQueue::add_image_views(uint32_t image_view_count, const VkImageView* image_view) noexcept {
|
||||||
|
for (size_t i = 0; i < image_view_count; i++) {
|
||||||
|
sets[current_index].views.push_back(image_view[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DeletionQueue::add_framebuffer(VkFramebuffer framebuffer) noexcept {
|
||||||
|
sets[current_index].framebuffers.push_back(framebuffer);
|
||||||
|
}
|
||||||
|
void DeletionQueue::add_framebuffers(uint32_t framebuffer_count, const VkFramebuffer* framebuffers) noexcept {
|
||||||
|
for (size_t i = 0; i < framebuffer_count; i++) {
|
||||||
|
sets[current_index].framebuffers.push_back(framebuffers[i]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
void DeletionQueue::add_swapchain(VkSwapchainKHR swapchain) noexcept {
|
||||||
|
sets[current_index].swapchains.push_back(swapchain);
|
||||||
|
}
|
||||||
|
void DeletionQueue::clear_set(vkb::DeletionQueue::DelaySets& set) noexcept {
|
||||||
|
for (auto const& image : set.images)
|
||||||
|
internal_table.fp_vkDestroyImage(device, image, nullptr);
|
||||||
|
for (auto const& image_view : set.views)
|
||||||
|
internal_table.fp_vkDestroyImageView(device, image_view, nullptr);
|
||||||
|
for (auto const& framebuffer : set.framebuffers)
|
||||||
|
internal_table.fp_vkDestroyFramebuffer(device, framebuffer, nullptr);
|
||||||
|
for (auto const& swapchain : set.swapchains)
|
||||||
|
internal_table.fp_vkDestroySwapchainKHR(device, swapchain, nullptr);
|
||||||
|
set.images.clear();
|
||||||
|
set.views.clear();
|
||||||
|
set.framebuffers.clear();
|
||||||
|
set.swapchains.clear();
|
||||||
|
}
|
||||||
|
void DeletionQueue::tick() noexcept {
|
||||||
|
assert(queue_depth != 0 && "Must construct DeletionQueue with valid VkDevice and non-zero queue_depth!");
|
||||||
|
current_index = (current_index + 1) % queue_depth;
|
||||||
|
clear_set(sets[current_index]);
|
||||||
|
}
|
||||||
|
|
||||||
|
void DeletionQueue::destroy() noexcept {
|
||||||
|
if (device != VK_NULL_HANDLE) {
|
||||||
|
for (auto& set : sets) {
|
||||||
|
clear_set(set);
|
||||||
|
}
|
||||||
|
device = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
|
||||||
|
// Swapchain Synchronization resources (semaphores, fences, & tracking)
|
||||||
|
SemaphoreManager::SemaphoreManager(VkDevice device, uint32_t image_count) noexcept : device(device) {
|
||||||
|
detail.swapchain_image_count = image_count;
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkCreateSemaphore, "vkCreateSemaphore");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkDestroySemaphore, "vkDestroySemaphore");
|
||||||
|
|
||||||
|
VkSemaphoreCreateInfo info{};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
|
|
||||||
|
for (uint32_t i = 0; i < detail.swapchain_image_count; i++) {
|
||||||
|
detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_acquire_semaphores[i]);
|
||||||
|
detail.fp_vkCreateSemaphore(device, &info, nullptr, &detail.active_submit_semaphores[i]);
|
||||||
|
}
|
||||||
|
// Make sure there are some semaphores to use when needed
|
||||||
|
detail.idle_semaphores.resize(10);
|
||||||
|
for (auto& semaphore : detail.idle_semaphores) {
|
||||||
|
detail.fp_vkCreateSemaphore(device, &info, nullptr, &semaphore);
|
||||||
|
}
|
||||||
|
for (uint32_t i = 0; i < FRAMES_IN_FLIGHT; i++)
|
||||||
|
detail.expired_semaphores[i].reserve(10);
|
||||||
|
detail.current_acquire_semaphore = get_fresh_semaphore();
|
||||||
|
}
|
||||||
|
SemaphoreManager::~SemaphoreManager() noexcept { destroy(); }
|
||||||
|
|
||||||
|
void SemaphoreManager::destroy() noexcept {
|
||||||
|
if (device != VK_NULL_HANDLE) {
|
||||||
|
for (uint32_t i = 0; i < detail.swapchain_image_count; i++) {
|
||||||
|
detail.fp_vkDestroySemaphore(device, detail.active_acquire_semaphores[i], nullptr);
|
||||||
|
detail.fp_vkDestroySemaphore(device, detail.active_submit_semaphores[i], nullptr);
|
||||||
|
}
|
||||||
|
for (auto& set : detail.expired_semaphores)
|
||||||
|
for (auto& semaphore : set)
|
||||||
|
detail.fp_vkDestroySemaphore(device, semaphore, nullptr);
|
||||||
|
for (auto& semaphore : detail.idle_semaphores)
|
||||||
|
detail.fp_vkDestroySemaphore(device, semaphore, nullptr);
|
||||||
|
detail.fp_vkDestroySemaphore(device, detail.current_acquire_semaphore, nullptr);
|
||||||
|
}
|
||||||
|
device = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
|
||||||
|
SemaphoreManager::SemaphoreManager(SemaphoreManager&& other) noexcept {
|
||||||
|
device = other.device;
|
||||||
|
detail = other.detail;
|
||||||
|
other.device = VK_NULL_HANDLE; // destroy only does stuff if device isn't null, therefore its a sentinel
|
||||||
|
}
|
||||||
|
SemaphoreManager& SemaphoreManager::operator=(SemaphoreManager&& other) noexcept {
|
||||||
|
destroy();
|
||||||
|
device = other.device;
|
||||||
|
detail = other.detail;
|
||||||
|
other.device = VK_NULL_HANDLE; // destroy only does stuff if device isn't null, therefore its a sentinel
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSemaphore SemaphoreManager::get_next_acquire_semaphore() noexcept { return detail.current_acquire_semaphore; }
|
||||||
|
void SemaphoreManager::update_current_semaphore_index(uint32_t index) noexcept {
|
||||||
|
detail.current_swapchain_index = index;
|
||||||
|
std::swap(detail.active_acquire_semaphores[index], detail.current_acquire_semaphore);
|
||||||
|
detail.in_use[index] = true;
|
||||||
|
detail.current_submit_index = (detail.current_submit_index + 1) % FRAMES_IN_FLIGHT;
|
||||||
|
detail.idle_semaphores.insert(detail.idle_semaphores.end(),
|
||||||
|
detail.expired_semaphores[detail.current_submit_index].begin(),
|
||||||
|
detail.expired_semaphores[detail.current_submit_index].end());
|
||||||
|
detail.expired_semaphores[detail.current_submit_index].clear();
|
||||||
|
}
|
||||||
|
VkSemaphore SemaphoreManager::get_acquire_semaphore() noexcept {
|
||||||
|
return detail.active_acquire_semaphores[detail.current_swapchain_index];
|
||||||
|
}
|
||||||
|
VkSemaphore SemaphoreManager::get_submit_semaphore() noexcept {
|
||||||
|
return detail.active_submit_semaphores[detail.current_swapchain_index];
|
||||||
|
}
|
||||||
|
|
||||||
|
void SemaphoreManager::recreate_swapchain_resources() noexcept {
|
||||||
|
for (uint32_t i = 0; i < detail.swapchain_image_count; i++) {
|
||||||
|
if (detail.in_use[i]) {
|
||||||
|
detail.expired_semaphores[detail.current_submit_index].push_back(detail.active_acquire_semaphores[i]);
|
||||||
|
detail.expired_semaphores[detail.current_submit_index].push_back(detail.active_submit_semaphores[i]);
|
||||||
|
detail.active_acquire_semaphores[i] = get_fresh_semaphore();
|
||||||
|
detail.active_submit_semaphores[i] = get_fresh_semaphore();
|
||||||
|
}
|
||||||
|
detail.in_use[i] = false;
|
||||||
|
}
|
||||||
|
detail.expired_semaphores[detail.current_submit_index].push_back(detail.current_acquire_semaphore);
|
||||||
|
detail.current_acquire_semaphore = get_fresh_semaphore();
|
||||||
|
detail.current_swapchain_index = detail::INDEX_MAX_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSemaphore SemaphoreManager::get_fresh_semaphore() noexcept {
|
||||||
|
VkSemaphore semaphore{};
|
||||||
|
if (detail.idle_semaphores.size() > 1) {
|
||||||
|
semaphore = detail.idle_semaphores.back();
|
||||||
|
detail.idle_semaphores.pop_back();
|
||||||
|
} else {
|
||||||
|
VkSemaphoreCreateInfo info{};
|
||||||
|
info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
|
||||||
|
detail.fp_vkCreateSemaphore(device, &info, nullptr, &semaphore);
|
||||||
|
}
|
||||||
|
return semaphore;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<vkb::SwapchainManager> convert(Result<vkb::Swapchain> err) {
|
||||||
|
switch (err.error().value()) {
|
||||||
|
case (static_cast<int>(SwapchainError::surface_handle_not_provided)):
|
||||||
|
return { make_error_code(SwapchainManagerError::surface_handle_not_provided), err.vk_result() };
|
||||||
|
case (static_cast<int>(SwapchainError::failed_query_surface_support_details)):
|
||||||
|
return { make_error_code(SwapchainManagerError::failed_query_surface_support_details), err.vk_result() };
|
||||||
|
case (static_cast<int>(SwapchainError::failed_create_swapchain)):
|
||||||
|
return { make_error_code(SwapchainManagerError::failed_create_swapchain), err.vk_result() };
|
||||||
|
case (static_cast<int>(SwapchainError::failed_get_swapchain_images)):
|
||||||
|
return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), err.vk_result() };
|
||||||
|
case (static_cast<int>(SwapchainError::failed_create_swapchain_image_views)):
|
||||||
|
return { make_error_code(SwapchainManagerError::failed_create_swapchain_image_views), err.vk_result() };
|
||||||
|
default:
|
||||||
|
assert(false && "Should never reach this");
|
||||||
|
return { make_error_code(SwapchainManagerError::surface_handle_not_provided) };
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
Result<SwapchainManager> SwapchainManager::create(SwapchainBuilder const& builder) noexcept {
|
||||||
|
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR;
|
||||||
|
PFN_vkCreateImageView fp_vkCreateImageView;
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(builder.info.device, fp_vkGetSwapchainImagesKHR, "vkGetSwapchainImagesKHR");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(builder.info.device, fp_vkCreateImageView, "vkCreateImageView");
|
||||||
|
|
||||||
|
auto swapchain_ret = builder.build();
|
||||||
|
if (!swapchain_ret.has_value()) {
|
||||||
|
return detail::convert(swapchain_ret);
|
||||||
|
}
|
||||||
|
|
||||||
|
auto swapchain_resources_ret = SwapchainManager::create_swapchain_resources(
|
||||||
|
swapchain_ret.value(), swapchain_ret.value().instance_version, fp_vkGetSwapchainImagesKHR, fp_vkCreateImageView);
|
||||||
|
if (!swapchain_resources_ret.has_value()) {
|
||||||
|
return swapchain_resources_ret.error();
|
||||||
|
}
|
||||||
|
return SwapchainManager(
|
||||||
|
builder, swapchain_ret.value(), swapchain_resources_ret.value(), fp_vkGetSwapchainImagesKHR, fp_vkCreateImageView);
|
||||||
|
}
|
||||||
|
|
||||||
|
SwapchainManager::SwapchainManager(SwapchainBuilder const& builder,
|
||||||
|
vkb::Swapchain swapchain,
|
||||||
|
SwapchainResources resources,
|
||||||
|
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
|
||||||
|
PFN_vkCreateImageView fp_vkCreateImageView) noexcept
|
||||||
|
: device(builder.info.device),
|
||||||
|
detail({ builder,
|
||||||
|
swapchain,
|
||||||
|
resources,
|
||||||
|
detail::SemaphoreManager(device, swapchain.image_count),
|
||||||
|
DeletionQueue(device, swapchain.image_count),
|
||||||
|
swapchain.instance_version }) {
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkGetDeviceQueue, "vkGetDeviceQueue");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkAcquireNextImageKHR, "vkAcquireNextImageKHR");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkQueuePresentKHR, "vkQueuePresentKHR");
|
||||||
|
detail::vulkan_functions().get_device_proc_addr(device, detail.fp_vkQueueSubmit, "vkQueueSubmit");
|
||||||
|
detail.fp_vkGetSwapchainImagesKHR = fp_vkGetSwapchainImagesKHR;
|
||||||
|
detail.fp_vkCreateImageView = fp_vkCreateImageView;
|
||||||
|
detail.fp_vkGetDeviceQueue(device, detail.builder.info.graphics_queue_index, 0, &detail.graphics_queue);
|
||||||
|
detail.fp_vkGetDeviceQueue(device, detail.builder.info.present_queue_index, 0, &detail.present_queue);
|
||||||
|
update_swapchain_info();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwapchainManager::destroy() noexcept {
|
||||||
|
if (device != VK_NULL_HANDLE) {
|
||||||
|
detail.semaphore_manager = detail::SemaphoreManager{};
|
||||||
|
detail.delete_queue.add_swapchain(detail.swapchain_resources.swapchain);
|
||||||
|
detail.delete_queue.add_image_views(
|
||||||
|
detail.current_swapchain.image_count, &detail.swapchain_resources.image_views.front());
|
||||||
|
detail.delete_queue.destroy();
|
||||||
|
device = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SwapchainManager::~SwapchainManager() noexcept { destroy(); }
|
||||||
|
|
||||||
|
SwapchainManager::SwapchainManager(SwapchainManager&& other) noexcept
|
||||||
|
: device(other.device), detail(std::move(other.detail)) {
|
||||||
|
other.device = VK_NULL_HANDLE;
|
||||||
|
}
|
||||||
|
SwapchainManager& SwapchainManager::operator=(SwapchainManager&& other) noexcept {
|
||||||
|
destroy();
|
||||||
|
device = other.device;
|
||||||
|
detail = std::move(other.detail);
|
||||||
|
other.device = VK_NULL_HANDLE;
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
SwapchainBuilder& SwapchainManager::get_builder() noexcept {
|
||||||
|
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
|
||||||
|
return detail.builder;
|
||||||
|
}
|
||||||
|
Result<SwapchainInfo> SwapchainManager::get_info() noexcept {
|
||||||
|
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
|
||||||
|
if (detail.current_status == Status::expired) {
|
||||||
|
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
|
||||||
|
}
|
||||||
|
return detail.current_info;
|
||||||
|
}
|
||||||
|
Result<SwapchainResources> SwapchainManager::get_swapchain_resources() noexcept {
|
||||||
|
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
|
||||||
|
if (detail.current_status == Status::expired) {
|
||||||
|
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
|
||||||
|
}
|
||||||
|
return detail.swapchain_resources;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SwapchainAcquireInfo> SwapchainManager::acquire_image() noexcept {
|
||||||
|
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
|
||||||
|
if (detail.current_status == Status::expired) {
|
||||||
|
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
|
||||||
|
} else if (detail.current_status == Status::ready_to_present) {
|
||||||
|
// dont do anything
|
||||||
|
SwapchainAcquireInfo out{};
|
||||||
|
out.image_view = detail.swapchain_resources.image_views[detail.current_image_index];
|
||||||
|
out.image_index = detail.current_image_index;
|
||||||
|
out.wait_semaphore = detail.semaphore_manager.get_acquire_semaphore();
|
||||||
|
out.signal_semaphore = detail.semaphore_manager.get_submit_semaphore();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset the current image index in case acquiring fails
|
||||||
|
detail.current_image_index = detail::INDEX_MAX_VALUE;
|
||||||
|
|
||||||
|
VkSemaphore acquire_semaphore = detail.semaphore_manager.get_next_acquire_semaphore();
|
||||||
|
|
||||||
|
VkResult result = detail.fp_vkAcquireNextImageKHR(
|
||||||
|
device, detail.swapchain_resources.swapchain, UINT64_MAX, acquire_semaphore, VK_NULL_HANDLE, &detail.current_image_index);
|
||||||
|
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||||
|
detail.current_status = Status::expired;
|
||||||
|
return { make_error_code(SwapchainManagerError::swapchain_out_of_date) };
|
||||||
|
} else if (result == VK_SUBOPTIMAL_KHR) {
|
||||||
|
} else if (result == VK_ERROR_SURFACE_LOST_KHR) {
|
||||||
|
return { make_error_code(SwapchainManagerError::surface_lost) };
|
||||||
|
} else if (result != VK_SUCCESS) {
|
||||||
|
return { make_error_code(SwapchainManagerError::acquire_next_image_error), result };
|
||||||
|
}
|
||||||
|
detail.semaphore_manager.update_current_semaphore_index(detail.current_image_index);
|
||||||
|
detail.current_status = Status::ready_to_present;
|
||||||
|
SwapchainAcquireInfo out{};
|
||||||
|
out.image_view = detail.swapchain_resources.image_views[detail.current_image_index];
|
||||||
|
out.image_index = detail.current_image_index;
|
||||||
|
out.wait_semaphore = detail.semaphore_manager.get_acquire_semaphore();
|
||||||
|
out.signal_semaphore = detail.semaphore_manager.get_submit_semaphore();
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<detail::void_t> SwapchainManager::present() noexcept {
|
||||||
|
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
|
||||||
|
if (detail.current_status == Status::expired) {
|
||||||
|
return make_error_code(SwapchainManagerError::swapchain_out_of_date);
|
||||||
|
}
|
||||||
|
if (detail.current_status == Status::ready_to_acquire) {
|
||||||
|
return make_error_code(SwapchainManagerError::must_call_acquire_image_first);
|
||||||
|
}
|
||||||
|
|
||||||
|
VkSemaphore wait_semaphores[1] = { detail.semaphore_manager.get_submit_semaphore() };
|
||||||
|
|
||||||
|
VkPresentInfoKHR present_info = {};
|
||||||
|
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||||
|
present_info.waitSemaphoreCount = 1;
|
||||||
|
present_info.pWaitSemaphores = wait_semaphores;
|
||||||
|
present_info.swapchainCount = 1;
|
||||||
|
present_info.pSwapchains = &detail.swapchain_resources.swapchain;
|
||||||
|
present_info.pImageIndices = &detail.current_image_index;
|
||||||
|
|
||||||
|
VkResult result = detail.fp_vkQueuePresentKHR(detail.present_queue, &present_info);
|
||||||
|
|
||||||
|
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
|
||||||
|
detail.current_status = Status::expired;
|
||||||
|
return { make_error_code(SwapchainManagerError::swapchain_out_of_date) };
|
||||||
|
} else if (result == VK_SUBOPTIMAL_KHR) {
|
||||||
|
|
||||||
|
} else if (result == VK_ERROR_SURFACE_LOST_KHR) {
|
||||||
|
return { make_error_code(SwapchainManagerError::surface_lost) };
|
||||||
|
} else if (result != VK_SUCCESS) {
|
||||||
|
return { make_error_code(SwapchainManagerError::queue_present_error), result };
|
||||||
|
} else {
|
||||||
|
detail.current_status = Status::ready_to_acquire;
|
||||||
|
}
|
||||||
|
|
||||||
|
// clean up old swapchain resources
|
||||||
|
detail.delete_queue.tick();
|
||||||
|
|
||||||
|
return detail::void_t{};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<detail::void_t> SwapchainManager::cancel_acquire_frame() noexcept {
|
||||||
|
VkResult result = WaitForSemaphore(detail.semaphore_manager.get_acquire_semaphore());
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
return { make_error_code(SwapchainManagerError::queue_submit_failed), result };
|
||||||
|
}
|
||||||
|
detail.current_status = Status::ready_to_acquire;
|
||||||
|
return detail::void_t{};
|
||||||
|
}
|
||||||
|
Result<detail::void_t> SwapchainManager::cancel_present_frame() noexcept {
|
||||||
|
VkResult result = WaitForSemaphore(detail.semaphore_manager.get_submit_semaphore());
|
||||||
|
if (result != VK_SUCCESS) {
|
||||||
|
return { make_error_code(SwapchainManagerError::queue_submit_failed), result };
|
||||||
|
}
|
||||||
|
detail.current_status = Status::ready_to_acquire;
|
||||||
|
return detail::void_t{};
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SwapchainInfo> SwapchainManager::recreate(uint32_t width, uint32_t height) noexcept {
|
||||||
|
assert(detail.current_status != Status::destroyed && "SwapchainManager was destroyed!");
|
||||||
|
|
||||||
|
auto old_resources = detail.swapchain_resources;
|
||||||
|
detail.swapchain_resources = {};
|
||||||
|
detail.delete_queue.add_swapchain(old_resources.swapchain);
|
||||||
|
detail.delete_queue.add_image_views(old_resources.image_count, &old_resources.image_views.front());
|
||||||
|
auto new_swapchain_ret = detail.builder.set_old_swapchain(old_resources.swapchain).set_desired_extent(width, height).build();
|
||||||
|
if (!new_swapchain_ret) {
|
||||||
|
return new_swapchain_ret.error();
|
||||||
|
}
|
||||||
|
detail.current_swapchain = new_swapchain_ret.value();
|
||||||
|
auto new_resources_ret = SwapchainManager::create_swapchain_resources(
|
||||||
|
detail.current_swapchain, detail.instance_version, detail.fp_vkGetSwapchainImagesKHR, detail.fp_vkCreateImageView);
|
||||||
|
if (!new_resources_ret) {
|
||||||
|
return new_resources_ret.error();
|
||||||
|
}
|
||||||
|
detail.swapchain_resources = new_resources_ret.value();
|
||||||
|
detail.semaphore_manager.recreate_swapchain_resources();
|
||||||
|
update_swapchain_info();
|
||||||
|
detail.current_status = Status::ready_to_acquire;
|
||||||
|
return detail.current_info;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SwapchainManager::update_swapchain_info() noexcept {
|
||||||
|
detail.current_info.image_count = detail.current_swapchain.image_count;
|
||||||
|
detail.current_info.image_format = detail.current_swapchain.image_format;
|
||||||
|
detail.current_info.extent = detail.current_swapchain.extent;
|
||||||
|
detail.current_info.image_usage_flags = detail.current_swapchain.image_usage_flags;
|
||||||
|
}
|
||||||
|
|
||||||
|
Result<SwapchainResources> SwapchainManager::create_swapchain_resources(vkb::Swapchain swapchain,
|
||||||
|
uint32_t instance_version,
|
||||||
|
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
|
||||||
|
PFN_vkCreateImageView fp_vkCreateImageView) noexcept {
|
||||||
|
|
||||||
|
SwapchainResources out{};
|
||||||
|
out.swapchain = swapchain.swapchain;
|
||||||
|
out.image_count = swapchain.image_count;
|
||||||
|
assert(out.image_count <= detail::MAX_SWAPCHAIN_IMAGE_COUNT);
|
||||||
|
|
||||||
|
VkResult result = fp_vkGetSwapchainImagesKHR(swapchain.device, swapchain.swapchain, &out.image_count, nullptr);
|
||||||
|
if (result != VK_SUCCESS) return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), result };
|
||||||
|
|
||||||
|
result = fp_vkGetSwapchainImagesKHR(swapchain.device, swapchain.swapchain, &out.image_count, out.images.data());
|
||||||
|
if (result != VK_SUCCESS) return { make_error_code(SwapchainManagerError::failed_get_swapchain_images), result };
|
||||||
|
|
||||||
|
#if defined(VK_VERSION_1_1)
|
||||||
|
VkImageViewUsageCreateInfo desired_flags{};
|
||||||
|
desired_flags.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_USAGE_CREATE_INFO;
|
||||||
|
desired_flags.usage = swapchain.image_usage_flags;
|
||||||
|
#endif
|
||||||
|
for (size_t i = 0; i < out.image_count; i++) {
|
||||||
|
VkImageViewCreateInfo createInfo = {};
|
||||||
|
createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
|
||||||
|
createInfo.image = out.images[i];
|
||||||
|
createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
|
||||||
|
createInfo.format = swapchain.image_format;
|
||||||
|
createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
|
||||||
|
createInfo.subresourceRange.baseMipLevel = 0;
|
||||||
|
createInfo.subresourceRange.levelCount = 1;
|
||||||
|
createInfo.subresourceRange.baseArrayLayer = 0;
|
||||||
|
createInfo.subresourceRange.layerCount = 1;
|
||||||
|
#if defined(VK_VERSION_1_1)
|
||||||
|
if (instance_version >= VKB_VK_API_VERSION_1_1) {
|
||||||
|
createInfo.pNext = &desired_flags;
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
result = fp_vkCreateImageView(swapchain.device, &createInfo, nullptr, &out.image_views[i]);
|
||||||
|
if (result != VK_SUCCESS)
|
||||||
|
return { make_error_code(SwapchainManagerError::failed_create_swapchain_image_views), result };
|
||||||
|
}
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
VkResult SwapchainManager::WaitForSemaphore(VkSemaphore semaphore) noexcept {
|
||||||
|
VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT };
|
||||||
|
|
||||||
|
VkSubmitInfo submit_info = {};
|
||||||
|
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
|
submit_info.waitSemaphoreCount = 1;
|
||||||
|
submit_info.pWaitSemaphores = &semaphore;
|
||||||
|
submit_info.pWaitDstStageMask = wait_stages;
|
||||||
|
|
||||||
|
return detail.fp_vkQueueSubmit(detail.graphics_queue, 1, &submit_info, VK_NULL_HANDLE);
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace vkb
|
} // namespace vkb
|
||||||
|
@ -20,6 +20,7 @@
|
|||||||
#include <cstdio>
|
#include <cstdio>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include <array>
|
||||||
#include <vector>
|
#include <vector>
|
||||||
#include <string>
|
#include <string>
|
||||||
#include <system_error>
|
#include <system_error>
|
||||||
@ -214,12 +215,28 @@ enum class SwapchainError {
|
|||||||
failed_create_swapchain_image_views,
|
failed_create_swapchain_image_views,
|
||||||
required_min_image_count_too_low,
|
required_min_image_count_too_low,
|
||||||
};
|
};
|
||||||
|
enum class SwapchainManagerError {
|
||||||
|
swapchain_suboptimal,
|
||||||
|
swapchain_out_of_date,
|
||||||
|
surface_lost,
|
||||||
|
queue_submit_failed,
|
||||||
|
must_call_acquire_image_first,
|
||||||
|
acquire_next_image_error,
|
||||||
|
queue_present_error,
|
||||||
|
surface_handle_not_provided,
|
||||||
|
failed_query_surface_support_details,
|
||||||
|
failed_create_swapchain,
|
||||||
|
failed_get_swapchain_images,
|
||||||
|
failed_create_swapchain_image_views,
|
||||||
|
};
|
||||||
|
|
||||||
std::error_code make_error_code(InstanceError instance_error);
|
std::error_code make_error_code(InstanceError instance_error);
|
||||||
std::error_code make_error_code(PhysicalDeviceError physical_device_error);
|
std::error_code make_error_code(PhysicalDeviceError physical_device_error);
|
||||||
std::error_code make_error_code(QueueError queue_error);
|
std::error_code make_error_code(QueueError queue_error);
|
||||||
std::error_code make_error_code(DeviceError device_error);
|
std::error_code make_error_code(DeviceError device_error);
|
||||||
std::error_code make_error_code(SwapchainError swapchain_error);
|
std::error_code make_error_code(SwapchainError swapchain_error);
|
||||||
|
std::error_code make_error_code(SwapchainManagerError swapchain_error);
|
||||||
|
|
||||||
|
|
||||||
const char* to_string_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT s);
|
const char* to_string_message_severity(VkDebugUtilsMessageSeverityFlagBitsEXT s);
|
||||||
const char* to_string_message_type(VkDebugUtilsMessageTypeFlagsEXT s);
|
const char* to_string_message_type(VkDebugUtilsMessageTypeFlagsEXT s);
|
||||||
@ -229,6 +246,8 @@ const char* to_string(PhysicalDeviceError err);
|
|||||||
const char* to_string(QueueError err);
|
const char* to_string(QueueError err);
|
||||||
const char* to_string(DeviceError err);
|
const char* to_string(DeviceError err);
|
||||||
const char* to_string(SwapchainError err);
|
const char* to_string(SwapchainError err);
|
||||||
|
const char* to_string(SwapchainManagerError err);
|
||||||
|
|
||||||
|
|
||||||
// Gathers useful information about the available vulkan capabilities, like layers and instance
|
// Gathers useful information about the available vulkan capabilities, like layers and instance
|
||||||
// extensions. Use this for enabling features conditionally, ie if you would like an extension but
|
// extensions. Use this for enabling features conditionally, ie if you would like an extension but
|
||||||
@ -359,11 +378,11 @@ class InstanceBuilder {
|
|||||||
|
|
||||||
// Prefer a vulkan instance API version. If the desired version isn't available, it will use the
|
// Prefer a vulkan instance API version. If the desired version isn't available, it will use the
|
||||||
// highest version available. Should be constructed with VK_MAKE_VERSION or VK_MAKE_API_VERSION.
|
// highest version available. Should be constructed with VK_MAKE_VERSION or VK_MAKE_API_VERSION.
|
||||||
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]]
|
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder&
|
||||||
InstanceBuilder& desire_api_version(uint32_t preferred_vulkan_version);
|
desire_api_version(uint32_t preferred_vulkan_version);
|
||||||
// Prefer a vulkan instance API version. If the desired version isn't available, it will use the highest version available.
|
// Prefer a vulkan instance API version. If the desired version isn't available, it will use the highest version available.
|
||||||
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]]
|
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder&
|
||||||
InstanceBuilder& desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0);
|
desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0);
|
||||||
|
|
||||||
// Adds a layer to be enabled. Will fail to create an instance if the layer isn't available.
|
// Adds a layer to be enabled. Will fail to create an instance if the layer isn't available.
|
||||||
InstanceBuilder& enable_layer(const char* layer_name);
|
InstanceBuilder& enable_layer(const char* layer_name);
|
||||||
@ -581,8 +600,8 @@ class PhysicalDeviceSelector {
|
|||||||
PhysicalDeviceSelector& add_desired_extensions(std::vector<const char*> extensions);
|
PhysicalDeviceSelector& add_desired_extensions(std::vector<const char*> extensions);
|
||||||
|
|
||||||
// Prefer a physical device that supports a (major, minor) version of vulkan.
|
// Prefer a physical device that supports a (major, minor) version of vulkan.
|
||||||
[[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]]
|
[[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]] PhysicalDeviceSelector&
|
||||||
PhysicalDeviceSelector& set_desired_version(uint32_t major, uint32_t minor);
|
set_desired_version(uint32_t major, uint32_t minor);
|
||||||
// Require a physical device that supports a (major, minor) version of vulkan.
|
// Require a physical device that supports a (major, minor) version of vulkan.
|
||||||
PhysicalDeviceSelector& set_minimum_version(uint32_t major, uint32_t minor);
|
PhysicalDeviceSelector& set_minimum_version(uint32_t major, uint32_t minor);
|
||||||
|
|
||||||
@ -675,6 +694,8 @@ enum class QueueType { present, graphics, compute, transfer };
|
|||||||
namespace detail {
|
namespace detail {
|
||||||
// Sentinel value, used in implementation only
|
// Sentinel value, used in implementation only
|
||||||
const uint32_t QUEUE_INDEX_MAX_VALUE = 65536;
|
const uint32_t QUEUE_INDEX_MAX_VALUE = 65536;
|
||||||
|
const uint32_t INDEX_MAX_VALUE = 65536;
|
||||||
|
|
||||||
} // namespace detail
|
} // namespace detail
|
||||||
|
|
||||||
// ---- Device ---- //
|
// ---- Device ---- //
|
||||||
@ -930,6 +951,10 @@ class SwapchainBuilder {
|
|||||||
VkSwapchainKHR old_swapchain = VK_NULL_HANDLE;
|
VkSwapchainKHR old_swapchain = VK_NULL_HANDLE;
|
||||||
VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE;
|
VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE;
|
||||||
} info;
|
} info;
|
||||||
|
|
||||||
|
friend class SwapchainManager;
|
||||||
|
// To allow SwapchainManager to construct it 'emptily'
|
||||||
|
explicit SwapchainBuilder() = default;
|
||||||
};
|
};
|
||||||
|
|
||||||
} // namespace vkb
|
} // namespace vkb
|
||||||
@ -941,4 +966,6 @@ template <> struct is_error_code_enum<vkb::PhysicalDeviceError> : true_type {};
|
|||||||
template <> struct is_error_code_enum<vkb::QueueError> : true_type {};
|
template <> struct is_error_code_enum<vkb::QueueError> : true_type {};
|
||||||
template <> struct is_error_code_enum<vkb::DeviceError> : true_type {};
|
template <> struct is_error_code_enum<vkb::DeviceError> : true_type {};
|
||||||
template <> struct is_error_code_enum<vkb::SwapchainError> : true_type {};
|
template <> struct is_error_code_enum<vkb::SwapchainError> : true_type {};
|
||||||
|
template <> struct is_error_code_enum<vkb::SwapchainManagerError> : true_type {};
|
||||||
|
|
||||||
} // namespace std
|
} // namespace std
|
||||||
|
378
src/VkSwapchainManager.h
Normal file
378
src/VkSwapchainManager.h
Normal file
@ -0,0 +1,378 @@
|
|||||||
|
/*
|
||||||
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated
|
||||||
|
* documentation files (the “Software”), to deal in the Software without restriction, including without
|
||||||
|
* limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
||||||
|
* of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
||||||
|
*
|
||||||
|
* The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
||||||
|
*
|
||||||
|
* THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT
|
||||||
|
* LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
* IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
|
||||||
|
* WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
*
|
||||||
|
* Copyright © 2022 Charles Giessen (charles@lunarg.com)
|
||||||
|
*/
|
||||||
|
|
||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include "VkBootstrap.h"
|
||||||
|
|
||||||
|
namespace vkb {
|
||||||
|
namespace detail {
|
||||||
|
// constants used for DeletionQueue and SwapchainManager
|
||||||
|
const int FRAMES_IN_FLIGHT = 3;
|
||||||
|
const int MAX_SWAPCHAIN_IMAGE_COUNT = 8;
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// ImagelessFramebufferBuilder
|
||||||
|
|
||||||
|
struct ImagelessFramebufferBuilder {
|
||||||
|
ImagelessFramebufferBuilder(Device const& device) noexcept;
|
||||||
|
VkFramebuffer build() noexcept;
|
||||||
|
ImagelessFramebufferBuilder& set_renderpass(VkRenderPass render_pass);
|
||||||
|
ImagelessFramebufferBuilder& set_width(uint32_t width);
|
||||||
|
ImagelessFramebufferBuilder& set_height(uint32_t height);
|
||||||
|
ImagelessFramebufferBuilder& set_extent(VkExtent2D extent);
|
||||||
|
ImagelessFramebufferBuilder& set_layers(uint32_t layer_count);
|
||||||
|
ImagelessFramebufferBuilder& add_attachment(VkImageUsageFlags usage_flags, VkFormat format);
|
||||||
|
ImagelessFramebufferBuilder& add_attachment(VkImageUsageFlags usage_flags, std::vector<VkFormat> const& formats);
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct Attachment {
|
||||||
|
VkImageUsageFlags usage_flags;
|
||||||
|
std::vector<VkFormat> formats;
|
||||||
|
};
|
||||||
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
|
PFN_vkCreateFramebuffer fp_vkCreateFramebuffer = nullptr;
|
||||||
|
PFN_vkDestroyFramebuffer fp_vkDestroyFramebuffer = nullptr;
|
||||||
|
VkRenderPass _render_pass = VK_NULL_HANDLE;
|
||||||
|
uint32_t _width = 0;
|
||||||
|
uint32_t _height = 0;
|
||||||
|
uint32_t _layers = 1;
|
||||||
|
std::vector<Attachment> _attachments;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* DeletionQueue is a helper utility that manages deleting of VkImages, VkImageViews,
|
||||||
|
*VkFramebuffers, and VkSwapchains at a later time. It works by keeping a list of all submitted
|
||||||
|
*handles in separate sets, and when a set is old enough, the DeletionQueue will iterate and delete
|
||||||
|
*all of the handles.
|
||||||
|
*
|
||||||
|
* The `tick()` function is when the deletion happens as well as incrementing the internal frame
|
||||||
|
*counter. Call `tick()` once per frame, preferably at the end.
|
||||||
|
*
|
||||||
|
* DeletionQueue is *not* a generic deletion queue that can execute arbitrary code to delete
|
||||||
|
*objects. Thus it may not be suitable for all applications.
|
||||||
|
*
|
||||||
|
* To destroy the DeletionQueue, call `destroy()`. Additionally, `destroy()` will be called
|
||||||
|
*automatically in DeletionQueue's destructor.
|
||||||
|
**/
|
||||||
|
class DeletionQueue {
|
||||||
|
public:
|
||||||
|
// default constructor, must replace with non-default constructed DeletionQueue to be useable
|
||||||
|
explicit DeletionQueue() noexcept = default;
|
||||||
|
|
||||||
|
// Main Contstructor Parameters:
|
||||||
|
// VkDevice device - VkDevice to use in the DeletionQueue
|
||||||
|
// uint32_t deletion_delay - how many ticks objects elapse before an added handle is destroyed
|
||||||
|
// Ex: deletion_delay of 4 means that a VkImage added will be destroyed after 4 calls to `tick()`
|
||||||
|
explicit DeletionQueue(VkDevice device, uint32_t deletion_delay) noexcept;
|
||||||
|
explicit DeletionQueue(Device const& device, uint32_t deletion_delay) noexcept;
|
||||||
|
~DeletionQueue() noexcept;
|
||||||
|
DeletionQueue(DeletionQueue const& other) = delete;
|
||||||
|
DeletionQueue& operator=(DeletionQueue const& other) = delete;
|
||||||
|
DeletionQueue(DeletionQueue&& other) noexcept;
|
||||||
|
DeletionQueue& operator=(DeletionQueue&& other) noexcept;
|
||||||
|
|
||||||
|
// Add the corresponding handle or handles to be deleted in 'queue_depth' frames
|
||||||
|
void add_image(VkImage) noexcept;
|
||||||
|
void add_images(uint32_t images_count, const VkImage* images) noexcept;
|
||||||
|
void add_image_view(VkImageView image_view) noexcept;
|
||||||
|
void add_image_views(uint32_t image_view_count, const VkImageView* image_view) noexcept;
|
||||||
|
void add_framebuffer(VkFramebuffer framebuffer) noexcept;
|
||||||
|
void add_framebuffers(uint32_t framebuffer_count, const VkFramebuffer* framebuffers) noexcept;
|
||||||
|
void add_swapchain(VkSwapchainKHR swapchain) noexcept;
|
||||||
|
|
||||||
|
// Destroy the objects which have waited for `deletion_delay` ticks, then increment the current frame counter
|
||||||
|
void tick() noexcept;
|
||||||
|
|
||||||
|
// Destroys the DeletionQueue, including destroying all contained Vulkan handles. Make sure all contained handles are not in use.
|
||||||
|
void destroy() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
struct DelaySets {
|
||||||
|
std::vector<VkImage> images;
|
||||||
|
std::vector<VkImageView> views;
|
||||||
|
std::vector<VkFramebuffer> framebuffers;
|
||||||
|
std::vector<VkSwapchainKHR> swapchains;
|
||||||
|
};
|
||||||
|
VkDevice device;
|
||||||
|
uint32_t queue_depth = 0;
|
||||||
|
uint32_t current_index = 0;
|
||||||
|
std::array<DelaySets, detail::MAX_SWAPCHAIN_IMAGE_COUNT> sets;
|
||||||
|
struct {
|
||||||
|
PFN_vkDestroyImage fp_vkDestroyImage;
|
||||||
|
PFN_vkDestroyImageView fp_vkDestroyImageView;
|
||||||
|
PFN_vkDestroyFramebuffer fp_vkDestroyFramebuffer;
|
||||||
|
PFN_vkDestroySwapchainKHR fp_vkDestroySwapchainKHR;
|
||||||
|
} internal_table;
|
||||||
|
void clear_set(vkb::DeletionQueue::DelaySets& set) noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The SwapchainManager is a utility class that handles the creation and recreation of the
|
||||||
|
* swapchain, the acquiring of swapchain image indices, the submission of command buffers which
|
||||||
|
* write to swapchain images, and the synchronization of the writing and presenting.
|
||||||
|
* */
|
||||||
|
|
||||||
|
namespace detail {
|
||||||
|
class SemaphoreManager {
|
||||||
|
public:
|
||||||
|
explicit SemaphoreManager() noexcept = default;
|
||||||
|
explicit SemaphoreManager(VkDevice device, uint32_t image_count) noexcept;
|
||||||
|
~SemaphoreManager() noexcept;
|
||||||
|
SemaphoreManager(SemaphoreManager const& other) = delete;
|
||||||
|
SemaphoreManager& operator=(SemaphoreManager const& other) = delete;
|
||||||
|
SemaphoreManager(SemaphoreManager&& other) noexcept;
|
||||||
|
SemaphoreManager& operator=(SemaphoreManager&& other) noexcept;
|
||||||
|
|
||||||
|
VkSemaphore get_next_acquire_semaphore() noexcept;
|
||||||
|
void update_current_semaphore_index(uint32_t index) noexcept;
|
||||||
|
VkSemaphore get_acquire_semaphore() noexcept;
|
||||||
|
VkSemaphore get_submit_semaphore() noexcept;
|
||||||
|
|
||||||
|
void recreate_swapchain_resources() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
|
struct Details {
|
||||||
|
uint32_t swapchain_image_count = 0;
|
||||||
|
uint32_t current_swapchain_index = detail::INDEX_MAX_VALUE;
|
||||||
|
std::array<bool, MAX_SWAPCHAIN_IMAGE_COUNT> in_use = {};
|
||||||
|
std::array<VkSemaphore, MAX_SWAPCHAIN_IMAGE_COUNT> active_acquire_semaphores{};
|
||||||
|
std::array<VkSemaphore, MAX_SWAPCHAIN_IMAGE_COUNT> active_submit_semaphores{};
|
||||||
|
uint32_t current_submit_index = 0;
|
||||||
|
std::array<std::vector<VkSemaphore>, FRAMES_IN_FLIGHT> expired_semaphores;
|
||||||
|
std::vector<VkSemaphore> idle_semaphores;
|
||||||
|
VkSemaphore current_acquire_semaphore = VK_NULL_HANDLE;
|
||||||
|
PFN_vkCreateSemaphore fp_vkCreateSemaphore;
|
||||||
|
PFN_vkDestroySemaphore fp_vkDestroySemaphore;
|
||||||
|
} detail;
|
||||||
|
void destroy() noexcept;
|
||||||
|
|
||||||
|
VkSemaphore get_fresh_semaphore() noexcept;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to signal that there may be an error that must be checked but there is no value returned
|
||||||
|
struct void_t {};
|
||||||
|
|
||||||
|
} // namespace detail
|
||||||
|
|
||||||
|
// Descriptive information about the current swapchain.
|
||||||
|
// Contains: image count, image format, extent (width & height), image usage flags
|
||||||
|
struct SwapchainInfo {
|
||||||
|
uint32_t image_count = 0;
|
||||||
|
VkFormat image_format = VK_FORMAT_UNDEFINED;
|
||||||
|
VkExtent2D extent = { 0, 0 };
|
||||||
|
VkImageUsageFlags image_usage_flags = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Vulkan Handles associated with the current swapchain
|
||||||
|
// Contains: VkSwapchainKHR handle, image count, array of VkImages, array of VkImageViews
|
||||||
|
// Caution: The image and image view arrays have `image_count` image/image views in them.
|
||||||
|
// This is for performance reasons, that way there isn't an extra indirection.
|
||||||
|
struct SwapchainResources {
|
||||||
|
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
|
||||||
|
uint32_t image_count = 0;
|
||||||
|
std::array<VkImage, detail::MAX_SWAPCHAIN_IMAGE_COUNT> images{};
|
||||||
|
std::array<VkImageView, detail::MAX_SWAPCHAIN_IMAGE_COUNT> image_views{};
|
||||||
|
};
|
||||||
|
|
||||||
|
// Struct returned from SwapchainManager::acquire_image()
|
||||||
|
struct SwapchainAcquireInfo {
|
||||||
|
// image view to use this frame
|
||||||
|
VkImageView image_view{};
|
||||||
|
// index of the swapchain image to use this frame
|
||||||
|
uint32_t image_index = detail::INDEX_MAX_VALUE;
|
||||||
|
VkSemaphore signal_semaphore;
|
||||||
|
VkSemaphore wait_semaphore;
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Swapchain Manager
|
||||||
|
*
|
||||||
|
* Creation:
|
||||||
|
* Call the static function `SwapchainManager::create(SwapchainBuilder const& builder)` and pass
|
||||||
|
* in a vkb::SwapchainBuilder. To configure the swapchain, use the member functions on the
|
||||||
|
* SwapchainBuilder before it is passed in. SwapchainManager will keep an internal
|
||||||
|
* copy of this builder. This function will also create a swapchain, so that the application can
|
||||||
|
* query the current extent, format, and usage flags of the created swapchain.
|
||||||
|
*
|
||||||
|
* Destruction:
|
||||||
|
* To destroy the SwapchainManager, call `destroy()`. Additionally, SwapchainManager will call
|
||||||
|
* `destroy()` automatically in its destructor.
|
||||||
|
*
|
||||||
|
* SYNCHRONIZATION GUARANTEES (or lack thereof):
|
||||||
|
* The SwapchainManager does NOT have internal synchronization. Thus applications must never call any
|
||||||
|
* of its functions from multiple threads at the same time.
|
||||||
|
*
|
||||||
|
* QUEUE SUBMISSION:
|
||||||
|
* The SwapchainManager uses the Presentation VkQueue. Thus, the user must guarantee that calling `present()`
|
||||||
|
* does not overlap with any other calls which use the present queue (which in most cases is the graphics queue).
|
||||||
|
*
|
||||||
|
* Suggested Vulkan feature to enable:
|
||||||
|
* Imageless Framebuffer - This feature makes it possible to create a framebuffer without a
|
||||||
|
* VkImageView by making it a parameter that is passed in at vkCmdBeginRenderPass time. It is
|
||||||
|
* recommended because it means one VkFrameBuffer is necessary for the entire swapchain, rather than
|
||||||
|
* the previous case of N framebuffers, one for each swapchain image. Vulkan 1.2 guarantees support
|
||||||
|
* for it.
|
||||||
|
*
|
||||||
|
* Example Initialization of SwapchainManager:
|
||||||
|
* {
|
||||||
|
* auto swapchain_manager_ret = vkb::SwapchainManager::create(vkb::SwapchainBuilder{ vk_device });
|
||||||
|
* if (!swapchain_manager_ret) {
|
||||||
|
* std::cout << swapchain_manager_ret.error().message() << "\n";
|
||||||
|
* return -1;
|
||||||
|
* }
|
||||||
|
* swapchain_manager = std::move(swapchain_manager_ret.value());
|
||||||
|
* swapchain_info = renderer.swapchain_manager.get_info();
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Example Application Usage during main rendering:
|
||||||
|
* {
|
||||||
|
* auto acquire_ret = renderer.swapchain_manager.acquire_image();
|
||||||
|
* if (!acquire_ret.has_value() && acquire_ret.error().value() ==
|
||||||
|
*(int)vkb::SwapchainManagerError::swapchain_out_of_date) { should_recreate = true; return; //abort rendering this frame
|
||||||
|
* } else if (!acquire_ret.has_value()) {
|
||||||
|
* return; //error out
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* auto acquire_info = acquire_ret.value(); //holds the image view, image index, and semaphores needed for
|
||||||
|
*submission VkImageView image_view = acquire_info.image_view;
|
||||||
|
*
|
||||||
|
* // record command buffers that use image_view
|
||||||
|
*
|
||||||
|
* // The semaphore that was passed into vkAcquireNextImageKHR (by the swapchain manager)
|
||||||
|
* VkSemaphore wait_semaphores[1] = { acquire_info.wait_semaphore }; //add in any user declared semaphores
|
||||||
|
*
|
||||||
|
* // The semaphore that gets passed into vkQueuePresentKHR (by the swapchain manager)
|
||||||
|
* VkSemaphore signal_semaphores[1] = { acquire_info.signal_semaphore }; //add in any user declared semaphores
|
||||||
|
* VkPipelineStageFlags wait_stages[1] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT };
|
||||||
|
*
|
||||||
|
* VkSubmitInfo submit_info = {};
|
||||||
|
* submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||||
|
* submit_info.waitSemaphoreCount = 1;
|
||||||
|
* submit_info.pWaitSemaphores = wait_semaphores;
|
||||||
|
* submit_info.pWaitDstStageMask = wait_stages;
|
||||||
|
* submit_info.commandBufferCount = 1;
|
||||||
|
* submit_info.pCommandBuffers = &renderer.command_buffers[renderer.current_index];
|
||||||
|
* submit_info.signalSemaphoreCount = 1;
|
||||||
|
* submit_info.pSignalSemaphores = signal_semaphores;
|
||||||
|
*
|
||||||
|
* }
|
||||||
|
*
|
||||||
|
* Example Application Recreation
|
||||||
|
*
|
||||||
|
*
|
||||||
|
* The SwapchainManager interally keeps track of the current 'state' of the swapchain. These states are:
|
||||||
|
* - ready_to_acquire: initial state, indicates is is safe to call `acquire_image`
|
||||||
|
* - ready_to_present: set after acquiring, indicates it is ready to accept command buffers to submit.
|
||||||
|
* - expired: the swapchain needs to be recreated, calling `acquire_image` while in this state will fail
|
||||||
|
* - destroyed: the swapchain was destroyed (likely manually), prevents further usage of the SwapchainManager
|
||||||
|
* After a successful call to `acquire_image()`, the state is set to `ready_to_present`
|
||||||
|
* After a successful call to `present()`, the state is set back to `ready_to_present`
|
||||||
|
* After a successful call to `recreate()`, the state is set back to `ready_to_acquire`
|
||||||
|
* Note that a successful call indicates that the return value didn't produce an error *and* the `out_of_date` parameter
|
||||||
|
*in `SwapchainAcquireInfo` or `SwapchainSubmitInfo` is false.
|
||||||
|
*
|
||||||
|
* Type Characteristics:
|
||||||
|
* SwapchainManager contains a default constructor, allowing it to be created in null state and initialized at a later
|
||||||
|
*point. SwapchainManager is a move-only type, due to it owning various Vulkan objects. Therefore it will make any
|
||||||
|
*struct or class that has a SwapchainManager member into a move-only type.
|
||||||
|
*
|
||||||
|
* Informative Details - Semaphores:
|
||||||
|
* SwapchainManager keeps all semaphore management internal, thus they are not an aspect a user
|
||||||
|
* needs to be involved with. However, it is possible to add additional wait and signal semaphores
|
||||||
|
* in the call to vkQueueSubmit if so desired by passing in the semaphores into `submit`.
|
||||||
|
**/
|
||||||
|
class SwapchainManager {
|
||||||
|
public:
|
||||||
|
static Result<SwapchainManager> create(SwapchainBuilder const& builder) noexcept;
|
||||||
|
|
||||||
|
explicit SwapchainManager() = default;
|
||||||
|
~SwapchainManager() noexcept;
|
||||||
|
void destroy() noexcept;
|
||||||
|
|
||||||
|
SwapchainManager(SwapchainManager const& other) = delete;
|
||||||
|
SwapchainManager& operator=(SwapchainManager const& other) = delete;
|
||||||
|
SwapchainManager(SwapchainManager&& other) noexcept;
|
||||||
|
SwapchainManager& operator=(SwapchainManager&& other) noexcept;
|
||||||
|
|
||||||
|
// Primary API
|
||||||
|
|
||||||
|
// Get a VkImageView handle to use in rendering
|
||||||
|
Result<SwapchainAcquireInfo> acquire_image() noexcept;
|
||||||
|
|
||||||
|
Result<detail::void_t> cancel_acquire_frame() noexcept;
|
||||||
|
Result<detail::void_t> cancel_present_frame() noexcept;
|
||||||
|
|
||||||
|
Result<detail::void_t> present() noexcept;
|
||||||
|
|
||||||
|
// Recreate the swapchain, putting currently in-use internal resources in a delete queue
|
||||||
|
Result<SwapchainInfo> recreate(uint32_t width = 0, uint32_t height = 0) noexcept;
|
||||||
|
|
||||||
|
// Get info about the swapchain
|
||||||
|
Result<SwapchainInfo> get_info() noexcept;
|
||||||
|
|
||||||
|
// Get access to the swapchain and resources associated with it
|
||||||
|
Result<SwapchainResources> get_swapchain_resources() noexcept;
|
||||||
|
|
||||||
|
// Access the internal builder. This is how an application can alter how the swapchain is recreated.
|
||||||
|
SwapchainBuilder& get_builder() noexcept;
|
||||||
|
|
||||||
|
private:
|
||||||
|
enum class Status {
|
||||||
|
ready_to_acquire,
|
||||||
|
ready_to_present,
|
||||||
|
expired, // needs to be recreated
|
||||||
|
destroyed, // no longer usable
|
||||||
|
};
|
||||||
|
VkDevice device = VK_NULL_HANDLE;
|
||||||
|
struct Details {
|
||||||
|
vkb::SwapchainBuilder builder;
|
||||||
|
vkb::Swapchain current_swapchain;
|
||||||
|
SwapchainResources swapchain_resources;
|
||||||
|
detail::SemaphoreManager semaphore_manager;
|
||||||
|
DeletionQueue delete_queue;
|
||||||
|
uint32_t instance_version = VKB_VK_API_VERSION_1_0;
|
||||||
|
Status current_status = Status::ready_to_acquire;
|
||||||
|
VkQueue graphics_queue{};
|
||||||
|
VkQueue present_queue{};
|
||||||
|
SwapchainInfo current_info{};
|
||||||
|
uint32_t current_image_index = detail::INDEX_MAX_VALUE;
|
||||||
|
PFN_vkGetDeviceQueue fp_vkGetDeviceQueue{};
|
||||||
|
PFN_vkAcquireNextImageKHR fp_vkAcquireNextImageKHR{};
|
||||||
|
PFN_vkQueuePresentKHR fp_vkQueuePresentKHR{};
|
||||||
|
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR{};
|
||||||
|
PFN_vkCreateImageView fp_vkCreateImageView{};
|
||||||
|
PFN_vkQueueSubmit fp_vkQueueSubmit{};
|
||||||
|
} detail;
|
||||||
|
|
||||||
|
explicit SwapchainManager(SwapchainBuilder const& builder,
|
||||||
|
Swapchain swapchain,
|
||||||
|
SwapchainResources resources,
|
||||||
|
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
|
||||||
|
PFN_vkCreateImageView fp_vkCreateImageView) noexcept;
|
||||||
|
|
||||||
|
void update_swapchain_info() noexcept;
|
||||||
|
|
||||||
|
static Result<SwapchainResources> create_swapchain_resources(vkb::Swapchain swapchain,
|
||||||
|
uint32_t instance_version,
|
||||||
|
PFN_vkGetSwapchainImagesKHR fp_vkGetSwapchainImagesKHR,
|
||||||
|
PFN_vkCreateImageView fp_vkCreateImageView) noexcept;
|
||||||
|
|
||||||
|
VkResult WaitForSemaphore(VkSemaphore semaphore) noexcept;
|
||||||
|
};
|
||||||
|
} // namespace vkb
|
@ -19,20 +19,23 @@
|
|||||||
#include "GLFW/glfw3.h"
|
#include "GLFW/glfw3.h"
|
||||||
|
|
||||||
#include "../src/VkBootstrap.h"
|
#include "../src/VkBootstrap.h"
|
||||||
|
#include "../src/VkSwapchainManager.h"
|
||||||
|
|
||||||
|
const int default_window_width = 512;
|
||||||
|
const int default_window_height = 512;
|
||||||
|
|
||||||
GLFWwindow* create_window_glfw(const char* window_name = "", bool resize = true) {
|
GLFWwindow* create_window_glfw(const char* window_name = "", bool resize = true) {
|
||||||
glfwInit();
|
glfwInit();
|
||||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||||
if (!resize) glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);
|
glfwWindowHint(GLFW_RESIZABLE, resize ? GLFW_TRUE : GLFW_FALSE);
|
||||||
|
|
||||||
return glfwCreateWindow(1024, 1024, window_name, NULL, NULL);
|
return glfwCreateWindow(default_window_width, default_window_height, window_name, NULL, NULL);
|
||||||
}
|
}
|
||||||
void destroy_window_glfw(GLFWwindow* window) {
|
void destroy_window_glfw(GLFWwindow* window) {
|
||||||
glfwDestroyWindow(window);
|
glfwDestroyWindow(window);
|
||||||
glfwTerminate();
|
glfwTerminate();
|
||||||
}
|
}
|
||||||
VkSurfaceKHR create_surface_glfw(
|
VkSurfaceKHR create_surface_glfw(VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) {
|
||||||
VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) {
|
|
||||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||||
VkResult err = glfwCreateWindowSurface(instance, window, allocator, &surface);
|
VkResult err = glfwCreateWindowSurface(instance, window, allocator, &surface);
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -70,11 +73,9 @@ struct VulkanLibrary {
|
|||||||
#endif
|
#endif
|
||||||
if (!library) return;
|
if (!library) return;
|
||||||
#if defined(__linux__) || defined(__APPLE__)
|
#if defined(__linux__) || defined(__APPLE__)
|
||||||
vkGetInstanceProcAddr =
|
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
|
||||||
reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
|
|
||||||
#elif defined(_WIN32)
|
#elif defined(_WIN32)
|
||||||
vkGetInstanceProcAddr =
|
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(library, "vkGetInstanceProcAddr"));
|
||||||
reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(library, "vkGetInstanceProcAddr"));
|
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,82 +88,5 @@ struct VulkanLibrary {
|
|||||||
library = 0;
|
library = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
void init(VkInstance instance) {
|
|
||||||
vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr)vkGetInstanceProcAddr(instance, "vkGetDeviceProcAddr");
|
|
||||||
vkDestroySurfaceKHR = (PFN_vkDestroySurfaceKHR)vkGetInstanceProcAddr(instance, "vkDestroySurfaceKHR");
|
|
||||||
}
|
|
||||||
|
|
||||||
void init(VkDevice device) {
|
|
||||||
vkCreateRenderPass = (PFN_vkCreateRenderPass)vkGetDeviceProcAddr(device, "vkCreateRenderPass");
|
|
||||||
vkCreateShaderModule = (PFN_vkCreateShaderModule)vkGetDeviceProcAddr(device, "vkCreateShaderModule");
|
|
||||||
vkCreatePipelineLayout =
|
|
||||||
(PFN_vkCreatePipelineLayout)vkGetDeviceProcAddr(device, "vkCreatePipelineLayout");
|
|
||||||
vkCreateGraphicsPipelines =
|
|
||||||
(PFN_vkCreateGraphicsPipelines)vkGetDeviceProcAddr(device, "vkCreateGraphicsPipelines");
|
|
||||||
vkDestroyShaderModule = (PFN_vkDestroyShaderModule)vkGetDeviceProcAddr(device, "vkDestroyShaderModule");
|
|
||||||
vkCreateFramebuffer = (PFN_vkCreateFramebuffer)vkGetDeviceProcAddr(device, "vkCreateFramebuffer");
|
|
||||||
vkCreateCommandPool = (PFN_vkCreateCommandPool)vkGetDeviceProcAddr(device, "vkCreateCommandPool");
|
|
||||||
vkAllocateCommandBuffers =
|
|
||||||
(PFN_vkAllocateCommandBuffers)vkGetDeviceProcAddr(device, "vkAllocateCommandBuffers");
|
|
||||||
vkBeginCommandBuffer = (PFN_vkBeginCommandBuffer)vkGetDeviceProcAddr(device, "vkBeginCommandBuffer");
|
|
||||||
vkEndCommandBuffer = (PFN_vkEndCommandBuffer)vkGetDeviceProcAddr(device, "vkEndCommandBuffer");
|
|
||||||
vkCmdSetViewport = (PFN_vkCmdSetViewport)vkGetDeviceProcAddr(device, "vkCmdSetViewport");
|
|
||||||
vkCmdSetScissor = (PFN_vkCmdSetScissor)vkGetDeviceProcAddr(device, "vkCmdSetScissor");
|
|
||||||
vkCmdBeginRenderPass = (PFN_vkCmdBeginRenderPass)vkGetDeviceProcAddr(device, "vkCmdBeginRenderPass");
|
|
||||||
vkCmdEndRenderPass = (PFN_vkCmdEndRenderPass)vkGetDeviceProcAddr(device, "vkCmdEndRenderPass");
|
|
||||||
vkCmdBindPipeline = (PFN_vkCmdBindPipeline)vkGetDeviceProcAddr(device, "vkCmdBindPipeline");
|
|
||||||
vkCmdDraw = (PFN_vkCmdDraw)vkGetDeviceProcAddr(device, "vkCmdDraw");
|
|
||||||
vkCreateSemaphore = (PFN_vkCreateSemaphore)vkGetDeviceProcAddr(device, "vkCreateSemaphore");
|
|
||||||
vkCreateFence = (PFN_vkCreateFence)vkGetDeviceProcAddr(device, "vkCreateFence");
|
|
||||||
vkDeviceWaitIdle = (PFN_vkDeviceWaitIdle)vkGetDeviceProcAddr(device, "vkDeviceWaitIdle");
|
|
||||||
vkDestroyCommandPool = (PFN_vkDestroyCommandPool)vkGetDeviceProcAddr(device, "vkDestroyCommandPool");
|
|
||||||
vkDestroyFramebuffer = (PFN_vkDestroyFramebuffer)vkGetDeviceProcAddr(device, "vkDestroyFramebuffer");
|
|
||||||
vkWaitForFences = (PFN_vkWaitForFences)vkGetDeviceProcAddr(device, "vkWaitForFences");
|
|
||||||
vkAcquireNextImageKHR = (PFN_vkAcquireNextImageKHR)vkGetDeviceProcAddr(device, "vkAcquireNextImageKHR");
|
|
||||||
vkResetFences = (PFN_vkResetFences)vkGetDeviceProcAddr(device, "vkResetFences");
|
|
||||||
vkQueueSubmit = (PFN_vkQueueSubmit)vkGetDeviceProcAddr(device, "vkQueueSubmit");
|
|
||||||
vkQueuePresentKHR = (PFN_vkQueuePresentKHR)vkGetDeviceProcAddr(device, "vkQueuePresentKHR");
|
|
||||||
vkDestroySemaphore = (PFN_vkDestroySemaphore)vkGetDeviceProcAddr(device, "vkDestroySemaphore");
|
|
||||||
vkDestroyFence = (PFN_vkDestroyFence)vkGetDeviceProcAddr(device, "vkDestroyFence");
|
|
||||||
vkDestroyPipeline = (PFN_vkDestroyPipeline)vkGetDeviceProcAddr(device, "vkDestroyPipeline");
|
|
||||||
vkDestroyPipelineLayout =
|
|
||||||
(PFN_vkDestroyPipelineLayout)vkGetDeviceProcAddr(device, "vkDestroyPipelineLayout");
|
|
||||||
vkDestroyRenderPass = (PFN_vkDestroyRenderPass)vkGetDeviceProcAddr(device, "vkDestroyRenderPass");
|
|
||||||
}
|
|
||||||
|
|
||||||
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = VK_NULL_HANDLE;
|
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = VK_NULL_HANDLE;
|
||||||
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr = VK_NULL_HANDLE;
|
|
||||||
|
|
||||||
PFN_vkCreateRenderPass vkCreateRenderPass = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreateShaderModule vkCreateShaderModule = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreatePipelineLayout vkCreatePipelineLayout = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreateGraphicsPipelines vkCreateGraphicsPipelines = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyShaderModule vkDestroyShaderModule = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreateFramebuffer vkCreateFramebuffer = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreateCommandPool vkCreateCommandPool = VK_NULL_HANDLE;
|
|
||||||
PFN_vkAllocateCommandBuffers vkAllocateCommandBuffers = VK_NULL_HANDLE;
|
|
||||||
PFN_vkBeginCommandBuffer vkBeginCommandBuffer = VK_NULL_HANDLE;
|
|
||||||
PFN_vkEndCommandBuffer vkEndCommandBuffer = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCmdSetViewport vkCmdSetViewport = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCmdSetScissor vkCmdSetScissor = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCmdBeginRenderPass vkCmdBeginRenderPass = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCmdEndRenderPass vkCmdEndRenderPass = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCmdBindPipeline vkCmdBindPipeline = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCmdDraw vkCmdDraw = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreateSemaphore vkCreateSemaphore = VK_NULL_HANDLE;
|
|
||||||
PFN_vkCreateFence vkCreateFence = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDeviceWaitIdle vkDeviceWaitIdle = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyCommandPool vkDestroyCommandPool = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyFramebuffer vkDestroyFramebuffer = VK_NULL_HANDLE;
|
|
||||||
PFN_vkWaitForFences vkWaitForFences = VK_NULL_HANDLE;
|
|
||||||
PFN_vkAcquireNextImageKHR vkAcquireNextImageKHR = VK_NULL_HANDLE;
|
|
||||||
PFN_vkResetFences vkResetFences = VK_NULL_HANDLE;
|
|
||||||
PFN_vkQueueSubmit vkQueueSubmit = VK_NULL_HANDLE;
|
|
||||||
PFN_vkQueuePresentKHR vkQueuePresentKHR = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroySemaphore vkDestroySemaphore = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyFence vkDestroyFence = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyPipeline vkDestroyPipeline = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyPipelineLayout vkDestroyPipelineLayout = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroySurfaceKHR vkDestroySurfaceKHR = VK_NULL_HANDLE;
|
|
||||||
PFN_vkDestroyRenderPass vkDestroyRenderPass = VK_NULL_HANDLE;
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user