adding command list Vulkan support

This commit is contained in:
2024-11-17 10:52:29 +01:00
parent 0b417483f3
commit d8b14d1a7e
29 changed files with 716 additions and 186 deletions

View File

@@ -55,6 +55,39 @@ void VulkanUnloadBackend(PulseBackend backend)
VulkanLoaderShutdown();
}
const char* VulkanVerbaliseResult(VkResult res)
{
switch(res)
{
case VK_SUCCESS: return "Success";
case VK_NOT_READY: return "A fence or query has not yet completed";
case VK_TIMEOUT: return "A wait operation has not completed in the specified time";
case VK_EVENT_SET: return "An event is signaled";
case VK_EVENT_RESET: return "An event is unsignaled";
case VK_INCOMPLETE: return "A return array was too small for the result";
case VK_ERROR_OUT_OF_HOST_MEMORY: return "A host memory allocation has failed";
case VK_ERROR_OUT_OF_DEVICE_MEMORY: return "A device memory allocation has failed";
case VK_ERROR_INITIALIZATION_FAILED: return "Initialization of an object could not be completed for implementation-specific reasons";
case VK_ERROR_DEVICE_LOST: return "The logical or physical device has been lost";
case VK_ERROR_MEMORY_MAP_FAILED: return "Mapping of a memory object has failed";
case VK_ERROR_LAYER_NOT_PRESENT: return "A requested layer is not present or could not be loaded";
case VK_ERROR_EXTENSION_NOT_PRESENT: return "A requested extension is not supported";
case VK_ERROR_FEATURE_NOT_PRESENT: return "A requested feature is not supported";
case VK_ERROR_INCOMPATIBLE_DRIVER: return "The requested version of Vulkan is not supported by the driver or is otherwise incompatible";
case VK_ERROR_TOO_MANY_OBJECTS: return "Too many objects of the type have already been created";
case VK_ERROR_FORMAT_NOT_SUPPORTED: return "A requested format is not supported on this device";
case VK_ERROR_SURFACE_LOST_KHR: return "A surface is no longer available";
case VK_SUBOPTIMAL_KHR: return "A swapchain no longer matches the surface properties exactly, but can still be used";
case VK_ERROR_OUT_OF_DATE_KHR: return "A surface has changed in such a way that it is no longer compatible with the swapchain";
case VK_ERROR_INCOMPATIBLE_DISPLAY_KHR: return "The display used by a swapchain does not use the same presentable image layout";
case VK_ERROR_NATIVE_WINDOW_IN_USE_KHR: return "The requested window is already connected to a VkSurfaceKHR, or to some other non-Vulkan API";
case VK_ERROR_VALIDATION_FAILED_EXT: return "A validation layer found an error";
default: return "Unknown Vulkan error";
}
return "Unknown Vulkan error"; // Just to avoid warnings
}
PulseBackendHandler VulkanDriver = {
.PFN_LoadBackend = VulkanLoadBackend,
.PFN_UnloadBackend = VulkanUnloadBackend,

View File

@@ -16,14 +16,16 @@
#define VULKAN_RETRIEVE_DRIVER_DATA_AS(handle, cast) ((cast)handle->driver_data)
#define CHECK_VK_RETVAL(res, error, retval) \
#define CHECK_VK_RETVAL(backend, res, error, retval) \
if((res) != VK_SUCCESS) \
{ \
if(backend != PULSE_NULL_HANDLE && PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(backend)) \
PulseLogErrorFmt(backend, "(Vulkan) Call to Vulkan function failed due to %s", VulkanVerbaliseResult(res)); \
PulseSetInternalError(error); \
return retval; \
}
#define CHECK_VK(res, error) CHECK_VK_RETVAL(res, error, )
#define CHECK_VK(backend, res, error) CHECK_VK_RETVAL(backend, res, error, )
typedef struct VulkanGlobal
{
@@ -39,6 +41,8 @@ typedef struct VulkanDriverData
VulkanGlobal* VulkanGetGlobal();
const char* VulkanVerbaliseResult(VkResult res);
PulseBackendFlags VulkanCheckSupport(PulseBackendFlags candidates, PulseShaderFormatsFlags shader_formats_used); // Return PULSE_BACKEND_VULKAN in case of success and PULSE_BACKEND_INVALID otherwise
#endif // PULSE_VULKAN_H_

171
Sources/Backends/Vulkan/VulkanCommandList.c git.filemode.normal_file
View File

@@ -0,0 +1,171 @@
// Copyright (C) 2024 kanel
// This file is part of "Pulse"
// For conditions of distribution and use, see copyright notice in LICENSE
#include "Pulse.h"
#include "Vulkan.h"
#include "VulkanCommandList.h"
#include "VulkanCommandPool.h"
#include "VulkanDevice.h"
#include "VulkanQueue.h"
static void VulkanInitCommandList(VulkanCommandPool* pool, PulseCommandList cmd)
{
PULSE_CHECK_PTR(pool);
PULSE_CHECK_PTR(cmd);
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(pool->device, VulkanDevice*);
VulkanCommandList* vulkan_cmd = VULKAN_RETRIEVE_DRIVER_DATA_AS(cmd, VulkanCommandList*);
vulkan_cmd->pool = pool;
VkCommandBufferAllocateInfo info = {};
info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
info.commandPool = pool->pool;
info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
info.commandBufferCount = 1;
CHECK_VK(pool->device->backend, vulkan_device->vkAllocateCommandBuffers(vulkan_device->device, &info, &vulkan_cmd->cmd), PULSE_ERROR_INITIALIZATION_FAILED);
if(pool->available_command_lists_size == pool->available_command_lists_capacity)
{
pool->available_command_lists_capacity += 5;
pool->available_command_lists = (PulseCommandList*)realloc(pool->available_command_lists, pool->available_command_lists_capacity * sizeof(PulseCommandList));
PULSE_CHECK_ALLOCATION(pool->available_command_lists);
}
pool->available_command_lists[pool->available_command_lists_size] = cmd;
pool->available_command_lists_size++;
}
PulseCommandList VulkanRequestCommandList(PulseDevice device, PulseCommandListUsage usage)
{
PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULL_HANDLE);
VulkanCommandPool* pool;
switch(usage)
{
case PULSE_COMMAND_LIST_TRANSFER_ONLY: pool = VulkanRequestCmdPoolFromDevice(device, VULKAN_QUEUE_TRANSFER); break;
case PULSE_COMMAND_LIST_GENERAL: // fallthrough
default: pool = VulkanRequestCmdPoolFromDevice(device, VULKAN_QUEUE_COMPUTE); break;
}
PULSE_CHECK_PTR_RETVAL(pool, PULSE_NULL_HANDLE);
PulseCommandList cmd = PULSE_NULL_HANDLE;
for(uint32_t i = 0; i < pool->available_command_lists_size; i++)
{
if(pool->available_command_lists[i]->is_available)
{
cmd = pool->available_command_lists[i];
break;
}
}
if(cmd == PULSE_NULL_HANDLE)
{
cmd = (PulseCommandList)calloc(1, sizeof(PulseCommandListHandler));
PULSE_CHECK_ALLOCATION_RETVAL(cmd, PULSE_NULL_HANDLE);
VulkanCommandList* vulkan_cmd = (VulkanCommandList*)calloc(1, sizeof(VulkanCommandList));
PULSE_CHECK_ALLOCATION_RETVAL(vulkan_cmd, PULSE_NULL_HANDLE);
cmd->usage = usage;
cmd->device = device;
cmd->driver_data = vulkan_cmd;
cmd->thread_id = pool->thread_id;
VulkanInitCommandList(pool, cmd);
}
cmd->compute_pipelines_bound_size = 0;
cmd->state = PULSE_COMMAND_LIST_STATE_EMPTY;
cmd->is_available = false;
cmd->is_compute_pipeline_bound = false;
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
VulkanCommandList* vulkan_cmd = VULKAN_RETRIEVE_DRIVER_DATA_AS(cmd, VulkanCommandList*);
CHECK_VK_RETVAL(device->backend, vulkan_device->vkResetCommandBuffer(vulkan_cmd->cmd, 0), PULSE_ERROR_DEVICE_ALLOCATION_FAILED, PULSE_NULL_HANDLE);
VkCommandBufferBeginInfo begin_info = {};
begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
begin_info.flags = 0;
VkResult res = vulkan_device->vkBeginCommandBuffer(vulkan_cmd->cmd, &begin_info);
switch(res)
{
case VK_SUCCESS: break;
case VK_ERROR_OUT_OF_HOST_MEMORY: PulseSetInternalError(PULSE_ERROR_CPU_ALLOCATION_FAILED); return PULSE_NULL_HANDLE;
case VK_ERROR_OUT_OF_DEVICE_MEMORY: PulseSetInternalError(PULSE_ERROR_DEVICE_ALLOCATION_FAILED); return PULSE_NULL_HANDLE;
default: break;
}
return cmd;
}
bool VulkanSubmitCommandList(PulseDevice device, PulseCommandList cmd, PulseFence fence)
{
PULSE_UNUSED(device);
PULSE_CHECK_HANDLE_RETVAL(cmd, false);
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
VulkanCommandList* vulkan_cmd = VULKAN_RETRIEVE_DRIVER_DATA_AS(cmd, VulkanCommandList*);
VkResult res = vulkan_device->vkEndCommandBuffer(vulkan_cmd->cmd);
switch(res)
{
case VK_SUCCESS: break;
case VK_ERROR_OUT_OF_HOST_MEMORY: PulseSetInternalError(PULSE_ERROR_CPU_ALLOCATION_FAILED); return false;
case VK_ERROR_OUT_OF_DEVICE_MEMORY: PulseSetInternalError(PULSE_ERROR_DEVICE_ALLOCATION_FAILED); return false;
default: break;
}
VkFence vulkan_fence;
if(fence != PULSE_NULL_HANDLE)
{
vulkan_fence = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VkFence);
vulkan_device->vkResetFences(vulkan_device->device, 1, &vulkan_fence);
}
VulkanQueue* vulkan_queue;
switch(cmd->usage)
{
case PULSE_COMMAND_LIST_TRANSFER_ONLY: vulkan_queue = vulkan_device->queues[VULKAN_QUEUE_TRANSFER]; break;
case PULSE_COMMAND_LIST_GENERAL: // fallthrough
default: vulkan_queue = vulkan_device->queues[VULKAN_QUEUE_COMPUTE]; break;
}
PULSE_CHECK_PTR_RETVAL(vulkan_queue, false);
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &vulkan_cmd->cmd;
res = vulkan_device->vkQueueSubmit(vulkan_queue->queue, 1, &submit_info, vulkan_fence);
switch(res)
{
case VK_SUCCESS: return true;
case VK_ERROR_OUT_OF_HOST_MEMORY: PulseSetInternalError(PULSE_ERROR_CPU_ALLOCATION_FAILED); return false;
case VK_ERROR_OUT_OF_DEVICE_MEMORY: PulseSetInternalError(PULSE_ERROR_DEVICE_ALLOCATION_FAILED); return false;
case VK_ERROR_DEVICE_LOST: PulseSetInternalError(PULSE_ERROR_DEVICE_LOST); return false;
default: return false;
}
return false;
}
void VulkanReleaseCommandList(PulseDevice device, PulseCommandList cmd)
{
PULSE_CHECK_HANDLE(device);
VulkanCommandList* vulkan_cmd = VULKAN_RETRIEVE_DRIVER_DATA_AS(cmd, VulkanCommandList*);
for(uint32_t i = 0; i < vulkan_cmd->pool->available_command_lists_size; i++)
{
if(vulkan_cmd->pool->available_command_lists[i] == cmd)
{
cmd->is_available = true;
cmd->state = PULSE_COMMAND_LIST_STATE_INVALID;
break;
}
}
}

View File

@@ -10,22 +10,17 @@
#include <vulkan/vulkan_core.h>
#include <Pulse.h>
#include "../../PulseInternal.h"
#include "VulkanCommandPool.h"
typedef struct VulkanCommandList
{
PulseDevice device;
VulkanCommandPool* pool;
PulseThreadID thread_id;
VkCommandBuffer cmd;
PulseComputePipeline* compute_pipelines_bound;
uint32_t compute_pipelines_bound_capacity;
uint32_t compute_pipelines_bound_size;
} VulkanCommandList;
void VulkanInitCommandList(VulkanCommandPool* pool);
PulseCommandList VulkanRequestCommandList(PulseDevice device, PulseCommandListUsage usage);
bool VulkanSubmitCommandList(PulseDevice device, PulseCommandList cmd, PulseFence fence);
void VulkanReleaseCommandList(PulseDevice device, PulseCommandList cmd);
#endif // PULSE_VULKAN_COMMAND_LIST_H_

View File

@@ -19,7 +19,7 @@ bool VulkanInitCommandPool(PulseDevice device, VulkanCommandPool* pool, VulkanQu
create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
create_info.queueFamilyIndex = vulkan_device->queues[queue_type]->queue_family_index;
create_info.pNext = PULSE_NULLPTR;
CHECK_VK_RETVAL(vulkan_device->vkCreateCommandPool(vulkan_device->device, &create_info, PULSE_NULLPTR, &pool->pool), PULSE_ERROR_INITIALIZATION_FAILED, false);
CHECK_VK_RETVAL(device->backend, vulkan_device->vkCreateCommandPool(vulkan_device->device, &create_info, PULSE_NULLPTR, &pool->pool), PULSE_ERROR_INITIALIZATION_FAILED, false);
pool->thread_id = PulseGetThreadID();

View File

@@ -0,0 +1,17 @@
// Copyright (C) 2024 kanel
// This file is part of "Pulse"
// For conditions of distribution and use, see copyright notice in LICENSE
#include "VulkanComputePipeline.h"
PulseComputePipeline VulkanCreateComputePipeline(PulseDevice device, const PulseComputePipelineCreateInfo* info)
{
}
void VulkanBindComputePipeline(PulseComputePass pass, PulseComputePipeline pipeline)
{
}
void VulkanDestroyComputePipeline(PulseDevice device, PulseComputePipeline pipeline)
{
}

View File

@@ -16,7 +16,7 @@ typedef struct VulkanComputePipeline
} VulkanComputePipeline;
PulseComputePipeline VulkanCreateComputePipeline(PulseDevice device, const PulseComputePipelineCreateInfo* info);
void VulkanBindComputePipeline(PulseComputePass pass, PulseComputePipeline pipeline);
void VulkanBindComputePipeline(PulseComputePipeline pipeline);
void VulkanDestroyComputePipeline(PulseDevice device, PulseComputePipeline pipeline);
#endif // PULSE_VULKAN_COMPUTE_PIPELINE_H_

View File

@@ -5,6 +5,7 @@
#include "Pulse.h"
#include "Vulkan.h"
#include "VulkanComputePipeline.h"
#include "VulkanCommandList.h"
#include "VulkanDevice.h"
#include "VulkanFence.h"
#include "VulkanInstance.h"
@@ -175,9 +176,11 @@ PulseDevice VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devic
create_info.flags = 0;
create_info.pNext = PULSE_NULLPTR;
CHECK_VK_RETVAL(instance->vkCreateDevice(device->physical, &create_info, PULSE_NULLPTR, &device->device), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULLPTR);
CHECK_VK_RETVAL(backend, instance->vkCreateDevice(device->physical, &create_info, PULSE_NULLPTR, &device->device), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULLPTR);
if(!VulkanLoadDevice(instance, device))
{
if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(backend))
PulseLogInfoFmt(backend, "(Vulkan) Could not load device functions from %s", device->properties.deviceName);
PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED);
return PULSE_NULLPTR;
}
@@ -217,11 +220,14 @@ PulseDevice VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devic
allocator_create_info.instance = instance->instance;
allocator_create_info.pVulkanFunctions = &vma_vulkan_func;
CHECK_VK_RETVAL(vmaCreateAllocator(&allocator_create_info, &device->allocator), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULLPTR);
CHECK_VK_RETVAL(backend, vmaCreateAllocator(&allocator_create_info, &device->allocator), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULLPTR);
pulse_device->driver_data = device;
pulse_device->backend = backend;
PULSE_LOAD_DRIVER_DEVICE(Vulkan);
if(PULSE_IS_BACKEND_HIGH_LEVEL_DEBUG(backend))
PulseLogInfoFmt(backend, "(Vulkan) Created device from %s", device->properties.deviceName);
return pulse_device;
}
@@ -230,8 +236,35 @@ void VulkanDestroyDevice(PulseDevice device)
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
if(vulkan_device == PULSE_NULLPTR || vulkan_device->device == VK_NULL_HANDLE)
return;
for(uint32_t i = 0; i < vulkan_device->cmd_pools_size; i++)
vulkan_device->vkDestroyCommandPool(vulkan_device->device, vulkan_device->cmd_pools[i].pool, PULSE_NULLPTR);
vmaDestroyAllocator(vulkan_device->allocator);
vulkan_device->vkDestroyDevice(vulkan_device->device, PULSE_NULLPTR);
if(PULSE_IS_BACKEND_HIGH_LEVEL_DEBUG(device->backend))
PulseLogInfoFmt(device->backend, "(Vulkan) Destroyed device created from %s", vulkan_device->properties.deviceName);
free(vulkan_device->cmd_pools);
free(vulkan_device);
free(device);
}
VulkanCommandPool* VulkanRequestCmdPoolFromDevice(PulseDevice device, VulkanQueueType queue_type)
{
PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULLPTR);
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
if(vulkan_device == PULSE_NULLPTR || vulkan_device->device == VK_NULL_HANDLE)
return PULSE_NULLPTR;
PulseThreadID thread_id = PulseGetThreadID();
for(uint32_t i = 0; i < vulkan_device->cmd_pools_size; i++)
{
if(thread_id == vulkan_device->cmd_pools[i].thread_id && queue_type == vulkan_device->cmd_pools[i].queue_type)
return &vulkan_device->cmd_pools[i];
}
vulkan_device->cmd_pools_size++;
vulkan_device->cmd_pools = (VulkanCommandPool*)realloc(vulkan_device->cmd_pools, vulkan_device->cmd_pools_size * sizeof(VulkanCommandPool));
if(!VulkanInitCommandPool(device, &vulkan_device->cmd_pools[vulkan_device->cmd_pools_size - 1], queue_type))
return PULSE_NULLPTR;
return &vulkan_device->cmd_pools[vulkan_device->cmd_pools_size - 1];
}

View File

@@ -24,6 +24,7 @@ struct VulkanQueue;
typedef struct VulkanDevice
{
VulkanCommandPool* cmd_pools;
uint32_t cmd_pools_size;
struct VulkanQueue* queues[VULKAN_QUEUE_END_ENUM];
@@ -43,6 +44,7 @@ typedef struct VulkanDevice
PulseDevice VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count);
void VulkanDestroyDevice(PulseDevice device);
VulkanCommandPool* VulkanRequestCmdPoolFromDevice(PulseDevice device, VulkanQueueType queue_type);
#endif // PULSE_VULKAN_DEVICE_H_

View File

@@ -10,12 +10,14 @@
PulseFence VulkanCreateFence(PulseDevice device)
{
PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULL_HANDLE);
VkFenceCreateInfo fence_info = {};
fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
VkFence vulkan_fence;
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
CHECK_VK_RETVAL(vulkan_device->vkCreateFence(vulkan_device->device, &fence_info, PULSE_NULLPTR, &vulkan_fence), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULL_HANDLE);
CHECK_VK_RETVAL(device->backend, vulkan_device->vkCreateFence(vulkan_device->device, &fence_info, PULSE_NULLPTR, &vulkan_fence), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULL_HANDLE);
PulseFenceHandler* fence = (PulseFenceHandler*)malloc(sizeof(PulseFenceHandler));
PULSE_CHECK_ALLOCATION_RETVAL(fence, PULSE_NULL_HANDLE);
@@ -25,6 +27,15 @@ PulseFence VulkanCreateFence(PulseDevice device)
void VulkanDestroyFence(PulseDevice device, PulseFence fence)
{
PULSE_CHECK_HANDLE(device);
if(fence == PULSE_NULL_HANDLE)
{
if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend))
PulseLogWarning(device->backend, "fence is NULL, this may be a bug in your application");
return;
}
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
if(vulkan_device == PULSE_NULLPTR || vulkan_device->device == VK_NULL_HANDLE)
return;
@@ -37,6 +48,9 @@ void VulkanDestroyFence(PulseDevice device, PulseFence fence)
bool VulkanIsFenceReady(PulseDevice device, PulseFence fence)
{
PULSE_CHECK_HANDLE_RETVAL(device, false);
PULSE_CHECK_HANDLE_RETVAL(fence, false);
VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*);
if(vulkan_device == PULSE_NULLPTR || vulkan_device->device == VK_NULL_HANDLE)
return false;
@@ -66,7 +80,7 @@ bool VulkanWaitForFences(PulseDevice device, const PulseFence* fences, uint32_t
VkFence* vulkan_fences = (VkFence*)calloc(fences_count, sizeof(VkFence));
PULSE_CHECK_ALLOCATION_RETVAL(vulkan_fences, false);
for(uint32_t i = 0; i < fences_count; i++)
vulkan_fences[i] = VULKAN_RETRIEVE_DRIVER_DATA_AS(((PulseFence)fences + i), VkFence);
vulkan_fences[i] = VULKAN_RETRIEVE_DRIVER_DATA_AS(((PulseFence)fences[i]), VkFence);
VkResult result = vulkan_device->vkWaitForFences(vulkan_device->device, fences_count, vulkan_fences, wait_for_all, UINT64_MAX);
free(vulkan_fences);
switch(result)

View File

@@ -36,7 +36,7 @@ static VkInstance VulkanCreateInstance(const char** extensions_enabled, uint32_t
create_info.flags = 0;
#endif
CHECK_VK_RETVAL(VulkanGetGlobal()->vkCreateInstance(&create_info, PULSE_NULLPTR, &instance), PULSE_ERROR_INITIALIZATION_FAILED, VK_NULL_HANDLE);
CHECK_VK_RETVAL(PULSE_NULL_HANDLE, VulkanGetGlobal()->vkCreateInstance(&create_info, PULSE_NULLPTR, &instance), PULSE_ERROR_INITIALIZATION_FAILED, VK_NULL_HANDLE);
return instance;
}