mirror of
https://github.com/charles-lunarg/vk-bootstrap.git
synced 2024-11-22 07:24:34 +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)
|
||||
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(
|
||||
TARGET vk-bootstrap-triangle
|
||||
POST_BUILD
|
||||
|
@ -4,114 +4,223 @@
|
||||
#include <iostream>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
#include <thread>
|
||||
#include <chrono>
|
||||
#include <mutex>
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
|
||||
#include <cmath>
|
||||
|
||||
#include "../tests/common.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;
|
||||
VulkanLibrary vk_lib;
|
||||
vkb::Instance instance;
|
||||
VkSurfaceKHR surface;
|
||||
vkb::PhysicalDevice physical_device;
|
||||
vkb::Device device;
|
||||
vkb::Swapchain swapchain;
|
||||
//convenience
|
||||
VulkanLibrary* operator->(){ return &vk_lib; }
|
||||
};
|
||||
vkb::DispatchTable dispatch;
|
||||
|
||||
struct RenderData {
|
||||
VkQueue graphics_queue;
|
||||
VkQueue present_queue;
|
||||
|
||||
std::vector<VkImage> swapchain_images;
|
||||
std::vector<VkImageView> swapchain_image_views;
|
||||
std::vector<VkFramebuffer> framebuffers;
|
||||
vkb::SwapchainManager swapchain_manager;
|
||||
vkb::SwapchainInfo swap_info;
|
||||
|
||||
vkb::DeletionQueue delete_queue;
|
||||
|
||||
VkRenderPass render_pass;
|
||||
VkFramebuffer framebuffer;
|
||||
VkPipelineLayout pipeline_layout;
|
||||
VkPipeline graphics_pipeline;
|
||||
|
||||
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;
|
||||
std::vector<VkSemaphore> finished_semaphore;
|
||||
std::vector<VkFence> in_flight_fences;
|
||||
std::vector<VkFence> image_in_flight;
|
||||
size_t current_frame = 0;
|
||||
double current_time = 0;
|
||||
};
|
||||
|
||||
int device_initialization (Init& init) {
|
||||
init.window = create_window_glfw ("Vulkan Triangle", true);
|
||||
enum class RecreateSwapchainRet { success, fail };
|
||||
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;
|
||||
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) {
|
||||
std::cout << instance_ret.error ().message () << "\n";
|
||||
std::cout << instance_ret.error().message() << "\n";
|
||||
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);
|
||||
auto phys_device_ret = phys_device_selector.set_surface (init.surface).select ();
|
||||
vkb::PhysicalDeviceSelector phys_device_selector(renderer.instance);
|
||||
auto phys_device_ret = phys_device_selector.set_surface(renderer.surface).set_required_features_12(features_1_2).select();
|
||||
if (!phys_device_ret) {
|
||||
std::cout << phys_device_ret.error ().message () << "\n";
|
||||
std::cout << phys_device_ret.error().message() << "\n";
|
||||
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 };
|
||||
auto device_ret = device_builder.build ();
|
||||
int device_initialization(Renderer& renderer) {
|
||||
vkb::DeviceBuilder device_builder{ renderer.physical_device };
|
||||
auto device_ret = device_builder.build();
|
||||
if (!device_ret) {
|
||||
std::cout << device_ret.error ().message () << "\n";
|
||||
std::cout << device_ret.error().message() << "\n";
|
||||
return -1;
|
||||
}
|
||||
init.device = device_ret.value ();
|
||||
init.vk_lib.init(init.device);
|
||||
renderer.device = device_ret.value();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
int create_swapchain (Init& init) {
|
||||
|
||||
vkb::SwapchainBuilder swapchain_builder{ init.device };
|
||||
auto swap_ret = swapchain_builder.set_old_swapchain (init.swapchain).build ();
|
||||
if (!swap_ret) {
|
||||
std::cout << swap_ret.error ().message () << " " << swap_ret.vk_result () << "\n";
|
||||
int get_queues(Renderer& renderer) {
|
||||
auto gq = renderer.device.get_queue(vkb::QueueType::graphics);
|
||||
if (!gq.has_value()) {
|
||||
std::cout << "failed to get graphics queue: " << gq.error().message() << "\n";
|
||||
return -1;
|
||||
}
|
||||
vkb::destroy_swapchain(init.swapchain);
|
||||
init.swapchain = swap_ret.value ();
|
||||
renderer.graphics_queue = gq.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;
|
||||
}
|
||||
|
||||
int get_queues (Init& init, RenderData& data) {
|
||||
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) {
|
||||
int create_render_pass(Renderer& renderer) {
|
||||
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.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
|
||||
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.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";
|
||||
return -1; // failed to create render pass!
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
std::vector<char> readFile (const std::string& filename) {
|
||||
std::ifstream file (filename, std::ios::ate | std::ios::binary);
|
||||
int create_framebuffer(Renderer& renderer) {
|
||||
|
||||
if (!file.is_open ()) {
|
||||
throw std::runtime_error ("failed to open file!");
|
||||
vkb::ImagelessFramebufferBuilder if_builder(renderer.device);
|
||||
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 ();
|
||||
std::vector<char> buffer (file_size);
|
||||
size_t file_size = (size_t)file.tellg();
|
||||
std::vector<char> buffer(file_size);
|
||||
|
||||
file.seekg (0);
|
||||
file.read (buffer.data (), static_cast<std::streamsize> (file_size));
|
||||
file.seekg(0);
|
||||
file.read(buffer.data(), static_cast<std::streamsize>(file_size));
|
||||
|
||||
file.close ();
|
||||
file.close();
|
||||
|
||||
return buffer;
|
||||
}
|
||||
|
||||
VkShaderModule createShaderModule (Init& init, const std::vector<char>& code) {
|
||||
VkShaderModule createShaderModule(Renderer& renderer, const std::vector<char>& code) {
|
||||
VkShaderModuleCreateInfo create_info = {};
|
||||
create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
|
||||
create_info.codeSize = code.size ();
|
||||
create_info.pCode = reinterpret_cast<const uint32_t*> (code.data ());
|
||||
create_info.codeSize = code.size();
|
||||
create_info.pCode = reinterpret_cast<const uint32_t*>(code.data());
|
||||
|
||||
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 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 frag_code = readFile(std::string(EXAMPLE_BUILD_DIRECTORY) + "/frag.spv");
|
||||
|
||||
VkShaderModule vert_module = createShaderModule (init, vert_code);
|
||||
VkShaderModule frag_module = createShaderModule (init, frag_code);
|
||||
VkShaderModule vert_module = createShaderModule(renderer, vert_code);
|
||||
VkShaderModule frag_module = createShaderModule(renderer, frag_code);
|
||||
if (vert_module == VK_NULL_HANDLE || frag_module == VK_NULL_HANDLE) {
|
||||
std::cout << "failed to create shader module\n";
|
||||
return -1; // failed to create shader modules
|
||||
@ -223,14 +344,14 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
||||
VkViewport viewport = {};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.width = (float)init.swapchain.extent.width;
|
||||
viewport.height = (float)init.swapchain.extent.height;
|
||||
viewport.width = (float)renderer.swap_info.extent.width;
|
||||
viewport.height = (float)renderer.swap_info.extent.height;
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
|
||||
VkRect2D scissor = {};
|
||||
scissor.offset = { 0, 0 };
|
||||
scissor.extent = init.swapchain.extent;
|
||||
scissor.extent = renderer.swap_info.extent;
|
||||
|
||||
VkPipelineViewportStateCreateInfo viewport_state = {};
|
||||
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;
|
||||
|
||||
VkPipelineColorBlendAttachmentState colorBlendAttachment = {};
|
||||
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT |
|
||||
VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
colorBlendAttachment.colorWriteMask =
|
||||
VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
|
||||
colorBlendAttachment.blendEnable = VK_FALSE;
|
||||
|
||||
VkPipelineColorBlendStateCreateInfo color_blending = {};
|
||||
@ -275,8 +396,7 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
||||
pipeline_layout_info.setLayoutCount = 0;
|
||||
pipeline_layout_info.pushConstantRangeCount = 0;
|
||||
|
||||
if (init->vkCreatePipelineLayout (
|
||||
init.device, &pipeline_layout_info, nullptr, &data.pipeline_layout) != VK_SUCCESS) {
|
||||
if (renderer.dispatch.createPipelineLayout(&pipeline_layout_info, nullptr, &renderer.pipeline_layout) != VK_SUCCESS) {
|
||||
std::cout << "failed to create pipeline layout\n";
|
||||
return -1; // failed to create pipeline layout
|
||||
}
|
||||
@ -285,8 +405,8 @@ int create_graphics_pipeline (Init& init, RenderData& data) {
|
||||
|
||||
VkPipelineDynamicStateCreateInfo dynamic_info = {};
|
||||
dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
|
||||
dynamic_info.dynamicStateCount = static_cast<uint32_t> (dynamic_states.size ());
|
||||
dynamic_info.pDynamicStates = dynamic_states.data ();
|
||||
dynamic_info.dynamicStateCount = static_cast<uint32_t>(dynamic_states.size());
|
||||
dynamic_info.pDynamicStates = dynamic_states.data();
|
||||
|
||||
VkGraphicsPipelineCreateInfo pipeline_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.pColorBlendState = &color_blending;
|
||||
pipeline_info.pDynamicState = &dynamic_info;
|
||||
pipeline_info.layout = data.pipeline_layout;
|
||||
pipeline_info.renderPass = data.render_pass;
|
||||
pipeline_info.layout = renderer.pipeline_layout;
|
||||
pipeline_info.renderPass = renderer.render_pass;
|
||||
pipeline_info.subpass = 0;
|
||||
pipeline_info.basePipelineHandle = VK_NULL_HANDLE;
|
||||
|
||||
if (init->vkCreateGraphicsPipelines (
|
||||
init.device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &data.graphics_pipeline) != VK_SUCCESS) {
|
||||
if (renderer.dispatch.createGraphicsPipelines(VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &renderer.graphics_pipeline) != VK_SUCCESS) {
|
||||
std::cout << "failed to create pipline\n";
|
||||
return -1; // failed to create graphics pipeline
|
||||
}
|
||||
|
||||
init->vkDestroyShaderModule (init.device, frag_module, nullptr);
|
||||
init->vkDestroyShaderModule (init.device, vert_module, nullptr);
|
||||
renderer.dispatch.destroyShaderModule(frag_module, nullptr);
|
||||
renderer.dispatch.destroyShaderModule(vert_module, nullptr);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_framebuffers (Init& init, RenderData& data) {
|
||||
data.swapchain_images = init.swapchain.get_images ().value ();
|
||||
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 = {};
|
||||
int create_command_buffers(Renderer& renderer) {
|
||||
VkCommandPoolCreateInfo pool_info{};
|
||||
pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
|
||||
pool_info.queueFamilyIndex = init.device.get_queue_index (vkb::QueueType::graphics).value ();
|
||||
|
||||
if (init->vkCreateCommandPool (init.device, &pool_info, nullptr, &data.command_pool) != VK_SUCCESS) {
|
||||
pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
|
||||
pool_info.queueFamilyIndex = renderer.device.get_queue_index(vkb::QueueType::graphics).value();
|
||||
if (renderer.dispatch.createCommandPool(&pool_info, nullptr, &renderer.command_pool) != VK_SUCCESS) {
|
||||
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;
|
||||
}
|
||||
|
||||
int create_command_buffers (Init& init, RenderData& data) {
|
||||
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++) {
|
||||
int record_command_buffer(Renderer& renderer, VkCommandBuffer command_buffer, VkImageView image_view) {
|
||||
VkCommandBufferBeginInfo 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
|
||||
}
|
||||
|
||||
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 = {};
|
||||
render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
|
||||
render_pass_info.renderPass = data.render_pass;
|
||||
render_pass_info.framebuffer = data.framebuffers[i];
|
||||
render_pass_info.pNext = &attach_begin_info;
|
||||
render_pass_info.renderPass = renderer.render_pass;
|
||||
render_pass_info.framebuffer = renderer.framebuffer;
|
||||
render_pass_info.renderArea.offset = { 0, 0 };
|
||||
render_pass_info.renderArea.extent = init.swapchain.extent;
|
||||
VkClearValue clearColor{ { { 0.0f, 0.0f, 0.0f, 1.0f } } };
|
||||
render_pass_info.renderArea.extent = renderer.swap_info.extent;
|
||||
|
||||
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.pClearValues = &clearColor;
|
||||
|
||||
VkViewport viewport = {};
|
||||
viewport.x = 0.0f;
|
||||
viewport.y = 0.0f;
|
||||
viewport.width = (float)init.swapchain.extent.width;
|
||||
viewport.height = (float)init.swapchain.extent.height;
|
||||
viewport.width = (float)renderer.swap_info.extent.width;
|
||||
viewport.height = (float)renderer.swap_info.extent.height;
|
||||
viewport.minDepth = 0.0f;
|
||||
viewport.maxDepth = 1.0f;
|
||||
|
||||
VkRect2D scissor = {};
|
||||
scissor.offset = { 0, 0 };
|
||||
scissor.extent = init.swapchain.extent;
|
||||
scissor.extent = renderer.swap_info.extent;
|
||||
|
||||
init->vkCmdSetViewport (data.command_buffers[i], 0, 1, &viewport);
|
||||
init->vkCmdSetScissor (data.command_buffers[i], 0, 1, &scissor);
|
||||
renderer.dispatch.cmdSetViewport(command_buffer, 0, 1, &viewport);
|
||||
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";
|
||||
return -1; // failed to record command buffer!
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
int create_sync_objects (Init& init, RenderData& data) {
|
||||
data.available_semaphores.resize (MAX_FRAMES_IN_FLIGHT);
|
||||
data.finished_semaphore.resize (MAX_FRAMES_IN_FLIGHT);
|
||||
data.in_flight_fences.resize (MAX_FRAMES_IN_FLIGHT);
|
||||
data.image_in_flight.resize (init.swapchain.image_count, VK_NULL_HANDLE);
|
||||
|
||||
VkSemaphoreCreateInfo semaphore_info = {};
|
||||
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
|
||||
RecreateSwapchainRet recreate_swapchain(Renderer& renderer) {
|
||||
renderer.delete_queue.add_framebuffer(renderer.framebuffer);
|
||||
renderer.framebuffer = VK_NULL_HANDLE;
|
||||
auto ret = renderer.swapchain_manager.recreate();
|
||||
if (!ret) {
|
||||
std::cout << "failed to recreate swapchain\n";
|
||||
return RecreateSwapchainRet::fail;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
renderer.swap_info = ret.value();
|
||||
if (0 != create_framebuffer(renderer)) return RecreateSwapchainRet::fail;
|
||||
return RecreateSwapchainRet::success;
|
||||
}
|
||||
|
||||
int recreate_swapchain (Init& init, RenderData& data) {
|
||||
init->vkDeviceWaitIdle (init.device);
|
||||
DrawFrameRet draw_frame(Renderer& renderer) {
|
||||
|
||||
init->vkDestroyCommandPool (init.device, data.command_pool, nullptr);
|
||||
|
||||
for (auto framebuffer : data.framebuffers) {
|
||||
init->vkDestroyFramebuffer (init.device, framebuffer, nullptr);
|
||||
vkb::SwapchainAcquireInfo acquire_info;
|
||||
auto acquire_ret = renderer.swapchain_manager.acquire_image();
|
||||
if (acquire_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
|
||||
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);
|
||||
|
||||
if (0 != create_swapchain (init)) return -1;
|
||||
if (0 != create_framebuffers (init, data)) return -1;
|
||||
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;
|
||||
acquire_info = acquire_ret.value();
|
||||
if (should_resize) {
|
||||
renderer.swapchain_manager.cancel_acquire_frame();
|
||||
return DrawFrameRet::out_of_date;
|
||||
}
|
||||
|
||||
if (data.image_in_flight[image_index] != VK_NULL_HANDLE) {
|
||||
init->vkWaitForFences (init.device, 1, &data.image_in_flight[image_index], VK_TRUE, UINT64_MAX);
|
||||
renderer.dispatch.waitForFences(1, &renderer.fences[renderer.current_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];
|
||||
|
||||
VkSubmitInfo submitInfo = {};
|
||||
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
|
||||
|
||||
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
|
||||
// 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]);
|
||||
if (renderer.dispatch.queueSubmit(renderer.graphics_queue, 1, &submit_info, renderer.fences[renderer.current_index]) != VK_SUCCESS) {
|
||||
std::cout << "failed to submit command buffer\n";
|
||||
return DrawFrameRet::fail;
|
||||
}
|
||||
renderer.current_index = (renderer.current_index + 1) % MAX_FRAMES_IN_FLIGHT;
|
||||
|
||||
VkPresentInfoKHR present_info = {};
|
||||
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
|
||||
// No need to cancel, if a resize has started, then present will bail
|
||||
auto present_ret = renderer.swapchain_manager.present();
|
||||
|
||||
present_info.waitSemaphoreCount = 1;
|
||||
present_info.pWaitSemaphores = signal_semaphores;
|
||||
|
||||
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) {
|
||||
if (present_ret.matches_error(vkb::SwapchainManagerError::swapchain_out_of_date)) {
|
||||
return DrawFrameRet::out_of_date;
|
||||
} else if (!present_ret) {
|
||||
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;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cleanup (Init& init, RenderData& data) {
|
||||
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
|
||||
init->vkDestroySemaphore (init.device, data.finished_semaphore[i], nullptr);
|
||||
init->vkDestroySemaphore (init.device, data.available_semaphores[i], nullptr);
|
||||
init->vkDestroyFence (init.device, data.in_flight_fences[i], nullptr);
|
||||
}
|
||||
|
||||
init->vkDestroyCommandPool (init.device, data.command_pool, nullptr);
|
||||
|
||||
for (auto framebuffer : data.framebuffers) {
|
||||
init->vkDestroyFramebuffer (init.device, framebuffer, nullptr);
|
||||
}
|
||||
|
||||
init->vkDestroyPipeline (init.device, data.graphics_pipeline, nullptr);
|
||||
init->vkDestroyPipelineLayout (init.device, data.pipeline_layout, nullptr);
|
||||
init->vkDestroyRenderPass (init.device, data.render_pass, nullptr);
|
||||
|
||||
init.swapchain.destroy_image_views (data.swapchain_image_views);
|
||||
|
||||
vkb::destroy_swapchain (init.swapchain);
|
||||
vkb::destroy_device (init.device);
|
||||
vkb::destroy_surface(init.instance, init.surface);
|
||||
vkb::destroy_instance (init.instance);
|
||||
destroy_window_glfw (init.window);
|
||||
}
|
||||
|
||||
int main () {
|
||||
Init init;
|
||||
RenderData render_data;
|
||||
|
||||
if (0 != device_initialization (init)) return -1;
|
||||
if (0 != create_swapchain (init)) return -1;
|
||||
if (0 != get_queues (init, render_data)) return -1;
|
||||
if (0 != create_render_pass (init, render_data)) return -1;
|
||||
if (0 != create_graphics_pipeline (init, render_data)) return -1;
|
||||
if (0 != create_framebuffers (init, render_data)) return -1;
|
||||
if (0 != create_command_pool (init, render_data)) return -1;
|
||||
if (0 != create_command_buffers (init, render_data)) return -1;
|
||||
if (0 != create_sync_objects (init, render_data)) return -1;
|
||||
|
||||
while (!glfwWindowShouldClose (init.window)) {
|
||||
glfwPollEvents ();
|
||||
int res = draw_frame (init, render_data);
|
||||
if (res != 0) {
|
||||
std::cout << "failed to draw frame \n";
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
init->vkDeviceWaitIdle (init.device);
|
||||
|
||||
cleanup (init, render_data);
|
||||
void cleanup(Renderer& renderer) {
|
||||
renderer.dispatch.deviceWaitIdle();
|
||||
|
||||
for (auto& fence : renderer.fences) {
|
||||
renderer.dispatch.destroyFence(fence, nullptr);
|
||||
}
|
||||
renderer.dispatch.destroyCommandPool(renderer.command_pool, nullptr);
|
||||
|
||||
renderer.dispatch.destroyPipeline(renderer.graphics_pipeline, nullptr);
|
||||
renderer.dispatch.destroyPipelineLayout(renderer.pipeline_layout, nullptr);
|
||||
renderer.dispatch.destroyFramebuffer(renderer.framebuffer, nullptr);
|
||||
renderer.dispatch.destroyRenderPass(renderer.render_pass, nullptr);
|
||||
|
||||
renderer.delete_queue.destroy();
|
||||
renderer.swapchain_manager.destroy();
|
||||
|
||||
vkb::destroy_device(renderer.device);
|
||||
vkb::destroy_surface(renderer.instance, renderer.surface);
|
||||
vkb::destroy_instance(renderer.instance);
|
||||
destroy_window_glfw(renderer.window);
|
||||
}
|
||||
|
||||
|
||||
void render_loop(Renderer* renderer) {
|
||||
while (is_running) {
|
||||
std::unique_lock<std::mutex> lg(main_mutex, std::try_to_lock);
|
||||
if (lg.owns_lock()) {
|
||||
switch (draw_frame(*renderer)) {
|
||||
case (DrawFrameRet::success):
|
||||
break;
|
||||
case (DrawFrameRet::out_of_date): {
|
||||
lg.unlock();
|
||||
std::unique_lock<std::mutex> ulg(render_wait_mutex);
|
||||
render_wait_condition_variable.wait(ulg);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
case (DrawFrameRet::fail):
|
||||
is_running = false;
|
||||
break;
|
||||
}
|
||||
} else {
|
||||
std::unique_lock<std::mutex> ulg(render_wait_mutex);
|
||||
render_wait_condition_variable.wait(ulg);
|
||||
}
|
||||
}
|
||||
renderer->dispatch.deviceWaitIdle();
|
||||
}
|
||||
|
||||
int main() {
|
||||
is_running = false;
|
||||
should_resize = false;
|
||||
Renderer renderer;
|
||||
|
||||
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;
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
#include "VkBootstrap.h"
|
||||
#include "VkSwapchainManager.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
@ -30,9 +31,12 @@
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
|
||||
#include <algorithm>
|
||||
#include <mutex>
|
||||
#include <algorithm>
|
||||
|
||||
#include <iostream>
|
||||
|
||||
namespace vkb {
|
||||
|
||||
namespace detail {
|
||||
@ -370,6 +374,12 @@ struct SwapchainErrorCategory : std::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
|
||||
|
||||
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) {
|
||||
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) \
|
||||
case CATEGORY::TYPE: \
|
||||
return #TYPE;
|
||||
@ -446,6 +460,25 @@ const char* to_string(SwapchainError err) {
|
||||
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() {
|
||||
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_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
|
||||
|
@ -20,6 +20,7 @@
|
||||
#include <cstdio>
|
||||
#include <cstring>
|
||||
|
||||
#include <array>
|
||||
#include <vector>
|
||||
#include <string>
|
||||
#include <system_error>
|
||||
@ -214,12 +215,28 @@ enum class SwapchainError {
|
||||
failed_create_swapchain_image_views,
|
||||
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(PhysicalDeviceError physical_device_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(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_type(VkDebugUtilsMessageTypeFlagsEXT s);
|
||||
@ -229,6 +246,8 @@ const char* to_string(PhysicalDeviceError err);
|
||||
const char* to_string(QueueError err);
|
||||
const char* to_string(DeviceError 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
|
||||
// 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
|
||||
// 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.")]]
|
||||
InstanceBuilder& desire_api_version(uint32_t preferred_vulkan_version);
|
||||
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder&
|
||||
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.
|
||||
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]]
|
||||
InstanceBuilder& desire_api_version(uint32_t major, uint32_t minor, uint32_t patch = 0);
|
||||
[[deprecated("Use require_api_version + set_minimum_instance_version instead.")]] InstanceBuilder&
|
||||
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.
|
||||
InstanceBuilder& enable_layer(const char* layer_name);
|
||||
@ -581,8 +600,8 @@ class PhysicalDeviceSelector {
|
||||
PhysicalDeviceSelector& add_desired_extensions(std::vector<const char*> extensions);
|
||||
|
||||
// Prefer a physical device that supports a (major, minor) version of vulkan.
|
||||
[[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]]
|
||||
PhysicalDeviceSelector& set_desired_version(uint32_t major, uint32_t minor);
|
||||
[[deprecated("Use set_minimum_version + InstanceBuilder::require_api_version.")]] PhysicalDeviceSelector&
|
||||
set_desired_version(uint32_t major, uint32_t minor);
|
||||
// Require a physical device that supports a (major, minor) version of vulkan.
|
||||
PhysicalDeviceSelector& set_minimum_version(uint32_t major, uint32_t minor);
|
||||
|
||||
@ -675,6 +694,8 @@ enum class QueueType { present, graphics, compute, transfer };
|
||||
namespace detail {
|
||||
// Sentinel value, used in implementation only
|
||||
const uint32_t QUEUE_INDEX_MAX_VALUE = 65536;
|
||||
const uint32_t INDEX_MAX_VALUE = 65536;
|
||||
|
||||
} // namespace detail
|
||||
|
||||
// ---- Device ---- //
|
||||
@ -930,6 +951,10 @@ class SwapchainBuilder {
|
||||
VkSwapchainKHR old_swapchain = VK_NULL_HANDLE;
|
||||
VkAllocationCallbacks* allocation_callbacks = VK_NULL_HANDLE;
|
||||
} info;
|
||||
|
||||
friend class SwapchainManager;
|
||||
// To allow SwapchainManager to construct it 'emptily'
|
||||
explicit SwapchainBuilder() = default;
|
||||
};
|
||||
|
||||
} // 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::DeviceError> : true_type {};
|
||||
template <> struct is_error_code_enum<vkb::SwapchainError> : true_type {};
|
||||
template <> struct is_error_code_enum<vkb::SwapchainManagerError> : true_type {};
|
||||
|
||||
} // 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 "../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) {
|
||||
glfwInit();
|
||||
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) {
|
||||
glfwDestroyWindow(window);
|
||||
glfwTerminate();
|
||||
}
|
||||
VkSurfaceKHR create_surface_glfw(
|
||||
VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) {
|
||||
VkSurfaceKHR create_surface_glfw(VkInstance instance, GLFWwindow* window, VkAllocationCallbacks* allocator = nullptr) {
|
||||
VkSurfaceKHR surface = VK_NULL_HANDLE;
|
||||
VkResult err = glfwCreateWindowSurface(instance, window, allocator, &surface);
|
||||
if (err) {
|
||||
@ -70,11 +73,9 @@ struct VulkanLibrary {
|
||||
#endif
|
||||
if (!library) return;
|
||||
#if defined(__linux__) || defined(__APPLE__)
|
||||
vkGetInstanceProcAddr =
|
||||
reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
|
||||
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
|
||||
#elif defined(_WIN32)
|
||||
vkGetInstanceProcAddr =
|
||||
reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(library, "vkGetInstanceProcAddr"));
|
||||
vkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(GetProcAddress(library, "vkGetInstanceProcAddr"));
|
||||
#endif
|
||||
}
|
||||
|
||||
@ -87,82 +88,5 @@ struct VulkanLibrary {
|
||||
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_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