You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
976 lines
29 KiB
976 lines
29 KiB
#include <Vulkan/Context.h>
|
|
|
|
#include <utils.h>
|
|
#include <Logger.h>
|
|
|
|
#include <SDL2/SDL_vulkan.h>
|
|
|
|
#include <cstring>
|
|
#include <cassert>
|
|
#include <algorithm>
|
|
#include <tuple>
|
|
|
|
|
|
namespace Vulkan {
|
|
|
|
|
|
ContextSettings::ContextSettings() {
|
|
}
|
|
|
|
ContextSettings::~ContextSettings() {
|
|
}
|
|
|
|
bool ContextSettings::debug() const {
|
|
return m_debug;
|
|
}
|
|
|
|
ContextSettings& ContextSettings::with_debug(bool enabled) {
|
|
m_debug = enabled;
|
|
return *this;
|
|
}
|
|
|
|
const std::optional<Uuid>& ContextSettings::physical_device() const {
|
|
return m_physical_device;
|
|
}
|
|
|
|
ContextSettings& ContextSettings::with_physical_device(Uuid uuid) {
|
|
m_physical_device = uuid;
|
|
return *this;
|
|
}
|
|
|
|
const std::vector<ContextSettings::QueueInfo>& ContextSettings::queues() const {
|
|
return m_queues;
|
|
}
|
|
|
|
ContextSettings& ContextSettings::with_queue(
|
|
uint32_t index, VkQueueFlagBits flags, bool use_swapchain_images
|
|
) {
|
|
m_queues.emplace_back(QueueInfo { index, flags, use_swapchain_images });
|
|
return *this;
|
|
}
|
|
|
|
SDL_Window* ContextSettings::window() const {
|
|
return m_window;
|
|
}
|
|
|
|
ContextSettings& ContextSettings::with_window(SDL_Window* window) {
|
|
m_window = window;
|
|
return *this;
|
|
}
|
|
|
|
|
|
//////////////////////////////////////////////////////////////////////////////
|
|
// Context
|
|
|
|
|
|
Context::Context() {
|
|
}
|
|
|
|
Context::~Context() {
|
|
shutdown();
|
|
}
|
|
|
|
VkInstance Context::instance() {
|
|
return m_instance;
|
|
}
|
|
|
|
VkPhysicalDevice Context::physical_device() {
|
|
return m_physical_device;
|
|
}
|
|
|
|
VkDevice Context::device() {
|
|
return m_device;
|
|
}
|
|
|
|
uint32_t Context::queue_family(size_t queue_index) const {
|
|
return m_queue_families[queue_index];
|
|
}
|
|
|
|
uint32_t Context::presentation_queue_family() const {
|
|
return m_presentation_queue_family;
|
|
}
|
|
|
|
VkQueue Context::queue(size_t queue_index) {
|
|
return m_queues[queue_index];
|
|
}
|
|
|
|
VkQueue Context::presentation_queue() {
|
|
return m_presentation_queue;
|
|
}
|
|
|
|
SDL_Window* Context::window() {
|
|
return m_window;
|
|
}
|
|
|
|
VkSurfaceKHR Context::surface() {
|
|
return m_surface;
|
|
}
|
|
|
|
VkSurfaceFormatKHR Context::surface_format() const {
|
|
return m_surface_format;
|
|
}
|
|
|
|
VkPresentModeKHR Context::present_mode() const {
|
|
return m_present_mode;
|
|
}
|
|
|
|
void Context::initialize(const ContextSettings& settings) {
|
|
m_window = settings.window();
|
|
|
|
create_instance(settings);
|
|
create_surface(settings);
|
|
choose_physical_device(settings);
|
|
create_device(settings);
|
|
create_internal_objects();
|
|
}
|
|
|
|
void Context::shutdown() {
|
|
if(!m_instance)
|
|
return;
|
|
|
|
vkDeviceWaitIdle(m_device);
|
|
|
|
for(auto& callback: m_context_destruction_callbacks)
|
|
callback();
|
|
|
|
destroy_fence(m_transfer_fence);
|
|
destroy_command_pool(m_transfer_command_pool);
|
|
|
|
destroy_device(m_device);
|
|
destroy_surface(m_surface);
|
|
destroy_debug_messenger(m_debug_messenger);
|
|
destroy_instance(m_instance);
|
|
}
|
|
|
|
void Context::register_context_destruction_callback(ContextDestructionCallback callback) {
|
|
m_context_destruction_callbacks.emplace_back(std::move(callback));
|
|
}
|
|
|
|
std::vector<VkExtensionProperties> Context::available_extensions() {
|
|
uint32_t count;
|
|
vkEnumerateInstanceExtensionProperties(nullptr, &count, nullptr);
|
|
|
|
std::vector<VkExtensionProperties> extensions(count);
|
|
vkEnumerateInstanceExtensionProperties(nullptr, &count, extensions.data());
|
|
|
|
return extensions;
|
|
}
|
|
|
|
std::vector<VkLayerProperties> Context::available_layers() {
|
|
uint32_t count;
|
|
vkEnumerateInstanceLayerProperties(&count, nullptr);
|
|
|
|
std::vector<VkLayerProperties> layers(count);
|
|
vkEnumerateInstanceLayerProperties(&count, layers.data());
|
|
|
|
return layers;
|
|
}
|
|
|
|
std::vector<const char*> Context::sdl_vulkan_extensions() const {
|
|
unsigned count;
|
|
if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, nullptr))
|
|
throw std::runtime_error("failed to get window's vulkan extensions");
|
|
|
|
std::vector<const char*> extensions(count);
|
|
if(!SDL_Vulkan_GetInstanceExtensions(m_window, &count, extensions.data()))
|
|
throw std::runtime_error("failed to get window's vulkan extensions");
|
|
|
|
return extensions;
|
|
}
|
|
|
|
std::vector<VkPhysicalDevice> Context::physical_devices() const {
|
|
uint32_t devices_count = 0;
|
|
vkEnumeratePhysicalDevices(m_instance, &devices_count, nullptr);
|
|
|
|
std::vector<VkPhysicalDevice> physical_devices(devices_count);
|
|
vkEnumeratePhysicalDevices(m_instance, &devices_count, physical_devices.data());
|
|
|
|
return physical_devices;
|
|
}
|
|
|
|
std::vector<VkQueueFamilyProperties> Context::queue_families(VkPhysicalDevice physical_device) const {
|
|
uint32_t queue_families_count = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, nullptr);
|
|
|
|
std::vector<VkQueueFamilyProperties> queue_families(queue_families_count);
|
|
vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_families_count, queue_families.data());
|
|
|
|
return queue_families;
|
|
}
|
|
|
|
std::vector<VkExtensionProperties> Context::device_extensions(VkPhysicalDevice physical_device) const {
|
|
uint32_t extensions_count = 0;
|
|
vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, nullptr);
|
|
|
|
std::vector<VkExtensionProperties> extensions(extensions_count);
|
|
vkEnumerateDeviceExtensionProperties(physical_device, nullptr, &extensions_count, extensions.data());
|
|
|
|
return extensions;
|
|
}
|
|
|
|
std::vector<VkSurfaceFormatKHR> Context::surface_formats(VkPhysicalDevice physical_device) const {
|
|
uint32_t surface_formats_count = 0;
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, nullptr);
|
|
|
|
std::vector<VkSurfaceFormatKHR> surface_formats(surface_formats_count);
|
|
vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, m_surface, &surface_formats_count, surface_formats.data());
|
|
|
|
return surface_formats;
|
|
}
|
|
|
|
std::vector<VkPresentModeKHR> Context::present_modes(VkPhysicalDevice physical_device) const {
|
|
uint32_t present_modes_count = 0;
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, nullptr);
|
|
|
|
std::vector<VkPresentModeKHR> present_modes(present_modes_count);
|
|
vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, m_surface, &present_modes_count, present_modes.data());
|
|
|
|
return present_modes;
|
|
}
|
|
|
|
|
|
void Context::set_object_name(VkObjectType type, uint64_t object, const char* name) {
|
|
VkDebugUtilsObjectNameInfoEXT name_info {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_OBJECT_NAME_INFO_EXT,
|
|
.objectType = type,
|
|
.objectHandle = object,
|
|
.pObjectName = name
|
|
};
|
|
if(setDebugUtilsObjectName(m_device, &name_info) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to set debug name");
|
|
}
|
|
|
|
void Context::set_object_name(VkObjectType type, uint64_t object, const std::string& name) {
|
|
set_object_name(type, object, name.c_str());
|
|
}
|
|
|
|
VkShaderModule Context::create_shader_module(const std::vector<char> bytecode) {
|
|
VkShaderModuleCreateInfo shader_info {
|
|
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
|
|
.codeSize = bytecode.size(),
|
|
.pCode = reinterpret_cast<const uint32_t*>(bytecode.data()),
|
|
};
|
|
|
|
VkShaderModule shader_module = VK_NULL_HANDLE;
|
|
if(vkCreateShaderModule(
|
|
m_device,
|
|
&shader_info,
|
|
nullptr,
|
|
&shader_module
|
|
) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to create shader module");
|
|
|
|
return shader_module;
|
|
}
|
|
|
|
VkShaderModule Context::create_shader_module_from_file(const char* path) {
|
|
auto const bytecode = read_binary_file(path);
|
|
try {
|
|
return create_shader_module(bytecode);
|
|
}
|
|
catch(std::exception err) {
|
|
throw std::runtime_error(cat("failed to create shader '", path, "':\n ", err.what()));
|
|
}
|
|
return VK_NULL_HANDLE;
|
|
}
|
|
|
|
int32_t Context::find_memory_type(uint32_t type_filter, VkMemoryPropertyFlags properties) {
|
|
for(uint32_t type_index = 0; type_index < m_memory_properties.memoryTypeCount; type_index += 1) {
|
|
if(((1 << type_index) & type_filter) &&
|
|
(m_memory_properties.memoryTypes[type_index].propertyFlags & properties) == properties)
|
|
return type_index;
|
|
}
|
|
|
|
return -1;
|
|
}
|
|
|
|
VkDeviceMemory Context::allocate_memory(VkDeviceSize size, uint32_t type_filter, VkMemoryPropertyFlags properties) {
|
|
uint32_t memory_type = find_memory_type(type_filter, properties);
|
|
|
|
VkMemoryAllocateInfo malloc_info {
|
|
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
|
|
.allocationSize = size,
|
|
.memoryTypeIndex = memory_type,
|
|
};
|
|
|
|
VkDeviceMemory memory = VK_NULL_HANDLE;
|
|
if(vkAllocateMemory(m_device, &malloc_info, nullptr, &memory) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to allocate device memory");
|
|
|
|
return memory;
|
|
}
|
|
|
|
std::tuple<VkBuffer, VkDeviceMemory> Context::create_buffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memory_properties) {
|
|
VkDeviceMemory memory = VK_NULL_HANDLE;
|
|
VkBuffer buffer = VK_NULL_HANDLE;
|
|
|
|
VkBufferCreateInfo buffer_info {
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
|
|
.size = size,
|
|
.usage = usage,
|
|
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
|
|
};
|
|
|
|
if(vkCreateBuffer(
|
|
m_device,
|
|
&buffer_info,
|
|
nullptr,
|
|
&buffer
|
|
) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to create buffer");
|
|
|
|
VkMemoryRequirements memory_requirements;
|
|
vkGetBufferMemoryRequirements(
|
|
m_device,
|
|
buffer,
|
|
&memory_requirements
|
|
);
|
|
|
|
memory = allocate_memory(
|
|
memory_requirements.size,
|
|
memory_requirements.memoryTypeBits,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
|
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
|
);
|
|
|
|
vkBindBufferMemory(m_device, buffer, memory, 0);
|
|
|
|
return { buffer, memory };
|
|
}
|
|
|
|
std::tuple<VkBuffer, VkDeviceMemory> Context::create_buffer(
|
|
VkDeviceSize size, char* data,
|
|
VkBufferUsageFlags usage,
|
|
VkMemoryPropertyFlags memory_properties,
|
|
uint32_t dst_queue_family
|
|
) {
|
|
VkBuffer tmp_buffer = VK_NULL_HANDLE;
|
|
VkDeviceMemory tmp_buffer_memory = VK_NULL_HANDLE;
|
|
std::tie(tmp_buffer, tmp_buffer_memory) = create_buffer(
|
|
size,
|
|
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
|
|
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT
|
|
| VK_MEMORY_PROPERTY_HOST_COHERENT_BIT
|
|
);
|
|
auto const tmp_buffer_guard = make_guard([&] {
|
|
free_memory(tmp_buffer_memory);
|
|
destroy_buffer(tmp_buffer);
|
|
});
|
|
|
|
VkBuffer buffer = VK_NULL_HANDLE;
|
|
VkDeviceMemory buffer_memory = VK_NULL_HANDLE;
|
|
std::tie(buffer, buffer_memory) = create_buffer(
|
|
size,
|
|
usage | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
|
|
memory_properties
|
|
);
|
|
|
|
void* device_buffer = nullptr;
|
|
vkMapMemory(
|
|
m_device,
|
|
tmp_buffer_memory,
|
|
0,
|
|
size,
|
|
0,
|
|
&device_buffer
|
|
);
|
|
|
|
memcpy(device_buffer, data, size_t(size));
|
|
|
|
vkUnmapMemory(
|
|
m_device,
|
|
tmp_buffer_memory
|
|
);
|
|
|
|
copy_buffer(
|
|
buffer,
|
|
tmp_buffer,
|
|
dst_queue_family,
|
|
size
|
|
);
|
|
|
|
return std::make_tuple(buffer, buffer_memory);
|
|
}
|
|
|
|
void Context::copy_buffer(VkBuffer dst, VkBuffer src, uint32_t dst_queue_family, VkDeviceSize size) {
|
|
VkCommandBufferAllocateInfo alloc_info {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
|
|
.commandPool = m_transfer_command_pool,
|
|
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
|
|
.commandBufferCount = 1,
|
|
};
|
|
|
|
VkCommandBuffer command_buffer;
|
|
vkAllocateCommandBuffers(m_device, &alloc_info, &command_buffer);
|
|
auto const command_buffer_guard = make_guard([this, command_buffer]() {
|
|
vkFreeCommandBuffers(m_device, m_transfer_command_pool, 1, &command_buffer);
|
|
});
|
|
|
|
VkCommandBufferBeginInfo begin_info {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
|
|
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
|
|
};
|
|
vkBeginCommandBuffer(command_buffer, &begin_info);
|
|
|
|
VkBufferCopy region {
|
|
.srcOffset = 0,
|
|
.dstOffset = 0,
|
|
.size = size,
|
|
};
|
|
vkCmdCopyBuffer(command_buffer, src, dst, 1, ®ion);
|
|
|
|
VkBufferMemoryBarrier buffer_barrier {
|
|
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER,
|
|
.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT,
|
|
.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT,
|
|
.srcQueueFamilyIndex = queue_family(VK_QUEUE_TRANSFER_BIT),
|
|
.dstQueueFamilyIndex = dst_queue_family,
|
|
.buffer = dst,
|
|
.offset = 0,
|
|
.size = size,
|
|
};
|
|
vkCmdPipelineBarrier(
|
|
command_buffer,
|
|
VK_PIPELINE_STAGE_TRANSFER_BIT,
|
|
VK_PIPELINE_STAGE_ALL_COMMANDS_BIT,
|
|
0,
|
|
0, nullptr,
|
|
1, &buffer_barrier,
|
|
0, nullptr
|
|
);
|
|
|
|
vkEndCommandBuffer(command_buffer);
|
|
|
|
vkResetFences(m_device, 1, &m_transfer_fence);
|
|
|
|
VkSubmitInfo submit_info {
|
|
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
|
|
.commandBufferCount = 1,
|
|
.pCommandBuffers = &command_buffer,
|
|
};
|
|
vkQueueSubmit(
|
|
queue(m_transfer_queue_index),
|
|
1, &submit_info,
|
|
m_transfer_fence
|
|
);
|
|
vkWaitForFences(m_device, 1, &m_transfer_fence, VK_TRUE, UINT64_MAX);
|
|
}
|
|
|
|
|
|
void Context::destroy_instance(VkInstance& instance) {
|
|
if(instance == nullptr)
|
|
return;
|
|
|
|
vkDestroyInstance(instance, nullptr);
|
|
instance = nullptr;
|
|
}
|
|
|
|
void Context::destroy_debug_messenger(VkDebugUtilsMessengerEXT& debug_messenger) {
|
|
if(debug_messenger == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
destroyDebugUtilsMessenger(m_instance, debug_messenger, nullptr);
|
|
debug_messenger = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_surface(VkSurfaceKHR& surface) {
|
|
if(surface == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroySurfaceKHR(m_instance, surface, nullptr);
|
|
surface = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_device(VkDevice& device) {
|
|
if(device == nullptr)
|
|
return;
|
|
|
|
vkDestroyDevice(device, nullptr);
|
|
device = nullptr;
|
|
}
|
|
|
|
void Context::destroy_swapchain(VkSwapchainKHR& swapchain) {
|
|
if(swapchain == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroySwapchainKHR(m_device, swapchain, nullptr);
|
|
swapchain = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_image(VkImage& image) {
|
|
if(image == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyImage(m_device, image, nullptr);
|
|
image = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_image_view(VkImageView& image_view) {
|
|
if(image_view == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyImageView(m_device, image_view, nullptr);
|
|
image_view = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_framebuffer(VkFramebuffer& framebuffer) {
|
|
if(framebuffer == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyFramebuffer(m_device, framebuffer, nullptr);
|
|
framebuffer = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_buffer(VkBuffer& buffer) {
|
|
if(buffer == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyBuffer(m_device, buffer, nullptr);
|
|
buffer = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_command_pool(VkCommandPool& command_pool) {
|
|
if(command_pool == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyCommandPool(m_device, command_pool, nullptr);
|
|
command_pool = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_descriptor_pool(VkDescriptorPool& descriptor_pool) {
|
|
if(descriptor_pool == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyDescriptorPool(m_device, descriptor_pool, nullptr);
|
|
descriptor_pool = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_render_pass(VkRenderPass& render_pass) {
|
|
if(render_pass == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyRenderPass(m_device, render_pass, nullptr);
|
|
render_pass = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_descriptor_set_layout(VkDescriptorSetLayout& descriptor_set_layout) {
|
|
if(descriptor_set_layout == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyDescriptorSetLayout(m_device, descriptor_set_layout, nullptr);
|
|
descriptor_set_layout = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_pipeline_layout(VkPipelineLayout& pipeline_layout) {
|
|
if(pipeline_layout == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyPipelineLayout(m_device, pipeline_layout, nullptr);
|
|
pipeline_layout = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_pipeline(VkPipeline& pipeline) {
|
|
if(pipeline == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyPipeline(m_device, pipeline, nullptr);
|
|
pipeline = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_semaphore(VkSemaphore& semaphore) {
|
|
if(semaphore == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroySemaphore(m_device, semaphore, nullptr);
|
|
semaphore = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::destroy_fence(VkFence& fence) {
|
|
if(fence == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkDestroyFence(m_device, fence, nullptr);
|
|
fence = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::free_memory(VkDeviceMemory& memory) {
|
|
if(memory == VK_NULL_HANDLE)
|
|
return;
|
|
|
|
vkFreeMemory(m_device, memory, nullptr);
|
|
memory = VK_NULL_HANDLE;
|
|
}
|
|
|
|
void Context::create_instance(const ContextSettings& settings) {
|
|
if(m_instance)
|
|
return;
|
|
|
|
char const* const name = "vk_expe";
|
|
uint32_t const version = VK_MAKE_VERSION(0, 1, 0);
|
|
|
|
VkApplicationInfo const app_info {
|
|
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
|
|
.pApplicationName = name,
|
|
.applicationVersion = version,
|
|
.pEngineName = name,
|
|
.engineVersion = version,
|
|
.apiVersion = VK_API_VERSION_1_1,
|
|
};
|
|
|
|
std::vector<char const*> layers;
|
|
std::vector<char const*> extensions = sdl_vulkan_extensions();
|
|
|
|
if(settings.debug()) {
|
|
extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
|
|
auto const available_layers = Context::available_layers();
|
|
|
|
auto add_layer_if_available = [&](const char* requested_layer) {
|
|
if(std::find_if(
|
|
available_layers.begin(),
|
|
available_layers.end(),
|
|
[requested_layer](const VkLayerProperties& layer) {
|
|
return std::strcmp(layer.layerName, requested_layer) == 0;
|
|
}
|
|
) != available_layers.end()) {
|
|
layers.push_back(requested_layer);
|
|
}
|
|
else {
|
|
logger.warning() << "requested layer '" << requested_layer
|
|
<< "' is not available";
|
|
}
|
|
};
|
|
|
|
add_layer_if_available("VK_LAYER_KHRONOS_validation");
|
|
}
|
|
|
|
VkDebugUtilsMessengerCreateInfoEXT const debug_info {
|
|
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
|
|
.messageSeverity =
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT,
|
|
.messageType =
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT |
|
|
VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT,
|
|
.pfnUserCallback = &log_debug_message,
|
|
.pUserData = nullptr,
|
|
};
|
|
|
|
VkInstanceCreateInfo const instance_info {
|
|
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
|
|
.pNext = &debug_info,
|
|
.pApplicationInfo = &app_info,
|
|
.enabledLayerCount = uint32_t(layers.size()),
|
|
.ppEnabledLayerNames = layers.data(),
|
|
.enabledExtensionCount = uint32_t(extensions.size()),
|
|
.ppEnabledExtensionNames = extensions.data(),
|
|
};
|
|
|
|
logger.debug() << "Requested extensions:";
|
|
for(char const* extension: extensions)
|
|
logger.debug() << " " << extension;
|
|
logger.debug() << "Requested layers:";
|
|
for(char const* layer: layers)
|
|
logger.debug() << " " << layer;
|
|
|
|
logger.debug() << "create vulkan instance...";
|
|
if(vkCreateInstance(&instance_info, nullptr, &m_instance) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to create vulkan instance");
|
|
|
|
initialize_extension_functions();
|
|
|
|
if(settings.debug()) {
|
|
createDebugUtilsMessenger(
|
|
m_instance, &debug_info, nullptr, &m_debug_messenger);
|
|
}
|
|
}
|
|
|
|
void Context::create_surface(const ContextSettings& settings) {
|
|
if(!SDL_Vulkan_CreateSurface(m_window, m_instance, &m_surface))
|
|
throw std::runtime_error("failed to create surface");
|
|
}
|
|
|
|
void Context::choose_physical_device(const ContextSettings& settings) {
|
|
auto physical_devices = this->physical_devices();
|
|
if(settings.physical_device()) {
|
|
auto const device_it = std::find_if(
|
|
physical_devices.begin(),
|
|
physical_devices.end(),
|
|
[&settings] (VkPhysicalDevice physical_device) {
|
|
VkPhysicalDeviceProperties device_properties;
|
|
vkGetPhysicalDeviceProperties(physical_device, &device_properties);
|
|
return make_uuid(device_properties.pipelineCacheUUID)
|
|
== *settings.physical_device();
|
|
}
|
|
);
|
|
if(device_it != physical_devices.end()) {
|
|
using std::swap;
|
|
swap(physical_devices[0], *device_it);
|
|
}
|
|
}
|
|
|
|
for(auto const physical_device: physical_devices) {
|
|
auto maybe_properties = select_physical_device(physical_device, settings);
|
|
if(maybe_properties) {
|
|
m_physical_device = physical_device;
|
|
m_physical_device_properties = *maybe_properties;
|
|
vkGetPhysicalDeviceMemoryProperties(m_physical_device, &m_memory_properties);
|
|
break;
|
|
}
|
|
}
|
|
|
|
if(!m_physical_device)
|
|
throw std::runtime_error("failed to find suitable physical device");
|
|
|
|
logger.info() << "use suitable device: " << m_physical_device_properties.deviceName;
|
|
logger.debug() << "swapchain format: " << m_surface_format.format;
|
|
logger.debug() << "present mode: " << m_present_mode;
|
|
}
|
|
|
|
std::optional<VkPhysicalDeviceProperties> Context::select_physical_device(
|
|
VkPhysicalDevice physical_device,
|
|
const ContextSettings& settings
|
|
) {
|
|
VkPhysicalDeviceProperties device_properties;
|
|
vkGetPhysicalDeviceProperties(physical_device, &device_properties);
|
|
|
|
VkPhysicalDeviceFeatures available_features;
|
|
vkGetPhysicalDeviceFeatures(physical_device, &available_features);
|
|
|
|
if (!available_features.geometryShader)
|
|
return std::nullopt;
|
|
|
|
|
|
m_transfer_queue_index = settings.queues().size();
|
|
m_queue_families.assign(settings.queues().size() + 1, INVALID_QUEUE_FAMILY);
|
|
m_presentation_queue_family = INVALID_QUEUE_FAMILY;
|
|
|
|
auto const queue_families = this->queue_families(physical_device);
|
|
uint32_t queue_family_index = 0;
|
|
for(auto const& queue_family: queue_families) {
|
|
if(m_window != nullptr) {
|
|
VkBool32 support_surface = false;
|
|
vkGetPhysicalDeviceSurfaceSupportKHR(
|
|
physical_device,
|
|
queue_family_index,
|
|
m_surface,
|
|
&support_surface
|
|
);
|
|
if(support_surface)
|
|
m_presentation_queue_family = queue_family_index;
|
|
}
|
|
|
|
for(auto const queue_info: settings.queues()) {
|
|
uint32_t const index = queue_info.index;
|
|
VkQueueFlagBits flags = queue_info.flags;
|
|
|
|
if(m_queue_families[index] != INVALID_QUEUE_FAMILY)
|
|
continue;
|
|
|
|
if((queue_family.queueFlags & flags) == flags)
|
|
m_queue_families[index] = queue_family_index;
|
|
}
|
|
|
|
if(m_queue_families[m_transfer_queue_index] == INVALID_QUEUE_FAMILY
|
|
&& queue_family.queueFlags == VK_QUEUE_TRANSFER_BIT)
|
|
m_queue_families[m_transfer_queue_index] = queue_family_index;
|
|
|
|
queue_family_index += 1;
|
|
}
|
|
|
|
for(auto const queue_family: m_queue_families) {
|
|
if(queue_family == INVALID_QUEUE_FAMILY)
|
|
return std::nullopt;
|
|
}
|
|
|
|
auto const available_extensions = this->device_extensions(physical_device);
|
|
auto has_extension = [&available_extensions](const char* required_extension) {
|
|
return std::find_if(
|
|
available_extensions.begin(),
|
|
available_extensions.end(),
|
|
[required_extension](const VkExtensionProperties& extension) {
|
|
return strcmp(extension.extensionName, required_extension) == 0;
|
|
}
|
|
) != available_extensions.end();
|
|
};
|
|
|
|
if(m_window) {
|
|
if(!has_extension(VK_KHR_SWAPCHAIN_EXTENSION_NAME))
|
|
return std::nullopt;
|
|
|
|
auto const surface_formats = this->surface_formats(physical_device);
|
|
if(surface_formats.empty())
|
|
return std::nullopt;
|
|
|
|
auto const present_modes = this->present_modes(physical_device);
|
|
if(present_modes.empty())
|
|
return std::nullopt;
|
|
|
|
auto const surface_format_it = std::find_if(
|
|
surface_formats.begin(),
|
|
surface_formats.end(),
|
|
[](const VkSurfaceFormatKHR& surface_format) {
|
|
return surface_format.format == VK_FORMAT_B8G8R8A8_SRGB
|
|
&& surface_format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
|
|
}
|
|
);
|
|
m_surface_format = (surface_format_it != surface_formats.end())?
|
|
*surface_format_it:
|
|
surface_formats[0];
|
|
|
|
m_present_mode = VK_PRESENT_MODE_FIFO_KHR;
|
|
// m_present_mode = VK_PRESENT_MODE_IMMEDIATE_KHR;
|
|
}
|
|
|
|
return device_properties;
|
|
}
|
|
|
|
void Context::create_device(const ContextSettings& settings) {
|
|
std::vector<uint32_t> queue_families = m_queue_families;
|
|
std::sort(queue_families.begin(), queue_families.end());
|
|
queue_families.erase(
|
|
std::unique(queue_families.begin(), queue_families.end()),
|
|
queue_families.end()
|
|
);
|
|
|
|
float const queue_priority = 1.0f;
|
|
std::vector<VkDeviceQueueCreateInfo> queue_infos;
|
|
for(uint32_t queue_family: queue_families) {
|
|
queue_infos.emplace_back(VkDeviceQueueCreateInfo {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
|
|
.queueFamilyIndex = queue_family,
|
|
.queueCount = 1,
|
|
.pQueuePriorities = &queue_priority,
|
|
});
|
|
}
|
|
|
|
VkPhysicalDeviceFeatures const device_features {
|
|
.geometryShader = true,
|
|
};
|
|
|
|
std::vector<const char*> extensions;
|
|
if(m_window) {
|
|
extensions.push_back(VK_KHR_SWAPCHAIN_EXTENSION_NAME);
|
|
};
|
|
|
|
VkDeviceCreateInfo const device_info {
|
|
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
|
|
.queueCreateInfoCount = uint32_t(queue_infos.size()),
|
|
.pQueueCreateInfos = queue_infos.data(),
|
|
.enabledExtensionCount = uint32_t(extensions.size()),
|
|
.ppEnabledExtensionNames = extensions.data(),
|
|
.pEnabledFeatures = &device_features,
|
|
};
|
|
|
|
logger.debug() << "create vulkan device...";
|
|
if(vkCreateDevice(
|
|
m_physical_device,
|
|
&device_info,
|
|
nullptr,
|
|
&m_device
|
|
) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to create logical device");
|
|
|
|
m_queues.resize(m_queue_families.size());
|
|
for(size_t index = 0; index < m_queue_families.size(); ++index) {
|
|
vkGetDeviceQueue(
|
|
m_device,
|
|
m_queue_families[index],
|
|
0,
|
|
&m_queues[index]
|
|
);
|
|
}
|
|
vkGetDeviceQueue(m_device, m_presentation_queue_family, 0, &m_presentation_queue);
|
|
}
|
|
|
|
void Context::create_internal_objects() {
|
|
VkCommandPoolCreateInfo pool_info {
|
|
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
|
|
.flags = 0,
|
|
.queueFamilyIndex = queue_family(m_transfer_queue_index),
|
|
};
|
|
if(vkCreateCommandPool(
|
|
m_device,
|
|
&pool_info,
|
|
nullptr,
|
|
&m_transfer_command_pool
|
|
) != VK_SUCCESS) {
|
|
throw std::runtime_error("failed to create command pool");
|
|
}
|
|
|
|
VkFenceCreateInfo fence_info {
|
|
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
|
|
};
|
|
if(vkCreateFence(m_device, &fence_info, nullptr, &m_transfer_fence) != VK_SUCCESS)
|
|
throw std::runtime_error("failed to create fence");
|
|
}
|
|
|
|
void Context::initialize_extension_functions() {
|
|
uint32_t errors_count = 0;
|
|
|
|
#define GET_PROC_ADDR(ptr_name, func_name) \
|
|
ptr_name = (PFN_vk ## func_name)vkGetInstanceProcAddr( \
|
|
m_instance, "vk" #func_name); \
|
|
if(ptr_name == nullptr) \
|
|
{ \
|
|
logger.error() << "failed to load extension function 'vk" #func_name "'"; \
|
|
errors_count += 1; \
|
|
}
|
|
|
|
GET_PROC_ADDR(createDebugUtilsMessenger, CreateDebugUtilsMessengerEXT)
|
|
GET_PROC_ADDR(destroyDebugUtilsMessenger, DestroyDebugUtilsMessengerEXT)
|
|
GET_PROC_ADDR(setDebugUtilsObjectName, SetDebugUtilsObjectNameEXT)
|
|
|
|
if(errors_count != 0)
|
|
throw std::runtime_error("failed to load extensions");
|
|
}
|
|
|
|
VKAPI_ATTR VkBool32 VKAPI_CALL Context::log_debug_message(
|
|
VkDebugUtilsMessageSeverityFlagBitsEXT severity,
|
|
VkDebugUtilsMessageTypeFlagsEXT type,
|
|
const VkDebugUtilsMessengerCallbackDataEXT* data,
|
|
void* user_data
|
|
) {
|
|
{
|
|
auto stream = [severity]() {
|
|
switch(severity) {
|
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
|
|
return logger.debug();
|
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
|
|
return logger.info();
|
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
|
|
return logger.warning();
|
|
case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
|
|
return logger.error();
|
|
}
|
|
return logger.debug();
|
|
}();
|
|
|
|
switch(type) {
|
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT:
|
|
stream << "[vk:general] ";
|
|
break;
|
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT:
|
|
stream << "[vk:validation] ";
|
|
break;
|
|
case VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT:
|
|
stream << "[vk:performance] ";
|
|
break;
|
|
}
|
|
|
|
stream << data->pMessage;
|
|
}
|
|
|
|
if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT
|
|
&& data->messageIdNumber != 2094043421 // wrong swapchain extent
|
|
)
|
|
abort();
|
|
|
|
return VK_FALSE;
|
|
}
|
|
|
|
|
|
}
|
|
|