diff --git a/Includes/Pulse.h b/Includes/Pulse.h index a43e497..d701b00 100644 --- a/Includes/Pulse.h +++ b/Includes/Pulse.h @@ -233,7 +233,7 @@ typedef struct PulseImageCreateInfo PulseImageUsageFlags usage; uint32_t width; uint32_t height; - uint32_t layer_count_or_depth; // This value is treated as a layer count on 2D array textures, and as a depth value on 3D textures + uint32_t layer_count_or_depth; // This value is treated as a layer count on 2D array images, and as a depth value on 3D images } PulseImageCreateInfo; typedef struct PulseImageLocation @@ -278,6 +278,7 @@ PULSE_API void PulseUnmapBuffer(PulseBuffer buffer); PULSE_API void PulseDestroyBuffer(PulseDevice device, PulseBuffer buffer); PULSE_API PulseImage PulseCreateImage(PulseDevice device, const PulseImageCreateInfo* create_infos); +PULSE_API bool PulseIsImageFormatValid(PulseDevice device, PulseImageFormat format, PulseImageType type, PulseImageUsageFlags usage); PULSE_API void PulseDestroyImage(PulseDevice device, PulseImage image); PULSE_API PulseCommandList PulseRequestCommandList(PulseDevice device, PulseCommandListUsage usage); diff --git a/Sources/Backends/Vulkan/Vulkan.c b/Sources/Backends/Vulkan/Vulkan.c index 5a68cfd..363857b 100644 --- a/Sources/Backends/Vulkan/Vulkan.c +++ b/Sources/Backends/Vulkan/Vulkan.c @@ -27,7 +27,7 @@ PulseBackendFlags VulkanCheckSupport(PulseBackendFlags candidates, PulseShaderFo if(!VulkanInitLoader()) return PULSE_BACKEND_INVALID; VulkanInstance instance; - if(!VulkanInitInstance(&instance, PULSE_NO_DEBUG)) // Instance creation will fail if Vulkan is not supported + if(!VulkanInitInstance(PULSE_NULL_HANDLE, &instance, PULSE_NO_DEBUG)) // Instance creation will fail if Vulkan is not supported { PulseGetLastErrorType(); // Clearing out the errors set by the failed instance creation return PULSE_BACKEND_INVALID; @@ -37,13 +37,13 @@ PulseBackendFlags VulkanCheckSupport(PulseBackendFlags candidates, PulseShaderFo return PULSE_BACKEND_VULKAN; } -bool VulkanLoadBackend(PulseDebugLevel debug_level) +bool VulkanLoadBackend(PulseBackend backend, PulseDebugLevel debug_level) { if(!VulkanInitLoader()) return false; VulkanDriverData* driver_data = (VulkanDriverData*)calloc(1, sizeof(VulkanDriverData)); PULSE_CHECK_ALLOCATION_RETVAL(driver_data, false); - if(!VulkanInitInstance(&driver_data->instance, debug_level)) + if(!VulkanInitInstance(backend, &driver_data->instance, debug_level)) return false; VulkanDriver.driver_data = driver_data; return true; @@ -59,29 +59,29 @@ 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"; + 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"; } diff --git a/Sources/Backends/Vulkan/Vulkan.h b/Sources/Backends/Vulkan/Vulkan.h index 7d6ea2f..466e109 100644 --- a/Sources/Backends/Vulkan/Vulkan.h +++ b/Sources/Backends/Vulkan/Vulkan.h @@ -21,7 +21,7 @@ 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)); \ + PulseLogErrorFmt(backend, "(Vulkan) call to a Vulkan function failed due to %s", VulkanVerbaliseResult(res)); \ PulseSetInternalError(error); \ return retval; \ } \ diff --git a/Sources/Backends/Vulkan/VulkanImage.c b/Sources/Backends/Vulkan/VulkanImage.c index 209e970..544eb2d 100644 --- a/Sources/Backends/Vulkan/VulkanImage.c +++ b/Sources/Backends/Vulkan/VulkanImage.c @@ -6,6 +6,7 @@ #include "Vulkan.h" #include "VulkanImage.h" #include "VulkanDevice.h" +#include static VkFormat PulseImageFormatToVkFormat[] = { VK_FORMAT_UNDEFINED, // INVALID @@ -126,26 +127,21 @@ PulseImage VulkanCreateImage(PulseDevice device, const PulseImageCreateInfo* cre else if(create_infos->type == PULSE_IMAGE_TYPE_3D) flags |= VK_IMAGE_CREATE_2D_ARRAY_COMPATIBLE_BIT; - VkImageCreateInfo image_info = { 0 }; - image_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; - image_info.imageType = VK_IMAGE_TYPE_2D; - image_info.extent.width = create_infos->width; - image_info.extent.height = create_infos->height; - image_info.extent.depth = depth; - image_info.mipLevels = 1; - image_info.arrayLayers = layer_count; - image_info.format = PulseImageFormatToVkFormat[create_infos->format]; - image_info.tiling = VK_IMAGE_TILING_OPTIMAL; - image_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; - image_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT; - image_info.samples = VK_SAMPLE_COUNT_1_BIT; - image_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; - image_info.flags = flags; - VkImageCreateInfo image_create_info = { 0 }; - image_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; - image_create_info.usage = vulkan_image->usage; + image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; + image_create_info.imageType = VK_IMAGE_TYPE_2D; + image_create_info.extent.width = create_infos->width; + image_create_info.extent.height = create_infos->height; + image_create_info.extent.depth = depth; + image_create_info.mipLevels = 1; + image_create_info.arrayLayers = layer_count; + image_create_info.format = PulseImageFormatToVkFormat[create_infos->format]; + image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL; + image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; + image_create_info.usage = VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_STORAGE_BIT; + image_create_info.samples = VK_SAMPLE_COUNT_1_BIT; image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; + image_create_info.flags = flags; CHECK_VK_RETVAL(device->backend, vmaCreateImage(vulkan_device->allocator, &image_create_info, &allocation_create_info, &vulkan_image->image, &vulkan_image->allocation, PULSE_NULLPTR), PULSE_ERROR_INITIALIZATION_FAILED, PULSE_NULL_HANDLE); vmaGetAllocationInfo(vulkan_device->allocator, vulkan_image->allocation, &vulkan_image->allocation_info); @@ -180,6 +176,18 @@ PulseImage VulkanCreateImage(PulseDevice device, const PulseImageCreateInfo* cre return image; } +bool VulkanIsImageFormatValid(PulseDevice device, PulseImageFormat format, PulseImageType type, PulseImageUsageFlags usage) +{ + (void)usage; + VulkanDriverData* vulkan_driver_data = VULKAN_RETRIEVE_DRIVER_DATA_AS(device->backend, VulkanDriverData*); + VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*); + VkImageCreateFlags vulkan_flags = 0; + VkImageFormatProperties properties; + if(type == PULSE_IMAGE_TYPE_CUBE || type == PULSE_IMAGE_TYPE_CUBE_ARRAY) + vulkan_flags = VK_IMAGE_CREATE_CUBE_COMPATIBLE_BIT; + return vulkan_driver_data->instance.vkGetPhysicalDeviceImageFormatProperties(vulkan_device->physical, PulseImageFormatToVkFormat[format], (type == PULSE_IMAGE_TYPE_3D) ? VK_IMAGE_TYPE_3D : VK_IMAGE_TYPE_2D, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_STORAGE_BIT, vulkan_flags, &properties) == VK_SUCCESS; +} + void VulkanDestroyImage(PulseDevice device, PulseImage image) { VulkanDevice* vulkan_device = VULKAN_RETRIEVE_DRIVER_DATA_AS(device, VulkanDevice*); diff --git a/Sources/Backends/Vulkan/VulkanImage.h b/Sources/Backends/Vulkan/VulkanImage.h index a057fed..bbb253b 100644 --- a/Sources/Backends/Vulkan/VulkanImage.h +++ b/Sources/Backends/Vulkan/VulkanImage.h @@ -24,6 +24,7 @@ typedef struct VulkanImage } VulkanImage; PulseImage VulkanCreateImage(PulseDevice device, const PulseImageCreateInfo* create_infos); +bool VulkanIsImageFormatValid(PulseDevice device, PulseImageFormat format, PulseImageType type, PulseImageUsageFlags usage); void VulkanDestroyImage(PulseDevice device, PulseImage image); #endif // PULSE_VULKAN_IMAGE_H_ diff --git a/Sources/Backends/Vulkan/VulkanInstance.c b/Sources/Backends/Vulkan/VulkanInstance.c index 72f735d..039e08c 100644 --- a/Sources/Backends/Vulkan/VulkanInstance.c +++ b/Sources/Backends/Vulkan/VulkanInstance.c @@ -4,13 +4,104 @@ #include "../../PulseInternal.h" #include "VulkanInstance.h" +#include "Pulse.h" +#include "PulseProfile.h" #include "VulkanLoader.h" #include "Vulkan.h" -static VkInstance VulkanCreateInstance(const char** extensions_enabled, uint32_t extensions_count, PulseDebugLevel debug_level) -{ - static const char* layer_names[] = { "VK_LAYER_KHRONOS_validation" }; +#include +#include +static const char* layer_names[] = { "VK_LAYER_KHRONOS_validation" }; + +static bool CheckValidationLayerSupport() +{ + uint32_t layer_count; + VulkanGetGlobal()->vkEnumerateInstanceLayerProperties(&layer_count, PULSE_NULLPTR); + VkLayerProperties* available_layers = (VkLayerProperties*)calloc(layer_count, sizeof(VkLayerProperties)); + PULSE_CHECK_ALLOCATION_RETVAL(available_layers, false); + VulkanGetGlobal()->vkEnumerateInstanceLayerProperties(&layer_count, available_layers); + + for(size_t i = 0; i < sizeof(layer_names) / sizeof(layer_names[0]); i++) + { + bool found = false; + for(size_t j = 0; j < layer_count; j++) + { + if(strcmp(available_layers[j].layerName, layer_names[i]) == 0) + { + found = true; + break; + } + } + if(!found) + { + free(available_layers); + return false; + } + } + free(available_layers); + return true; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL DebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) +{ + (void)type; + PulseBackend backend = user_data; + if(severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT || severity == VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) + PulseLogErrorFmt(backend, "(Vulkan) validation: %s", callback_data->pMessage); + return VK_FALSE; +} + +static VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* create_info, VkDebugUtilsMessengerEXT* messenger) +{ + PFN_vkCreateDebugUtilsMessengerEXT fn = (PFN_vkCreateDebugUtilsMessengerEXT)VulkanGetGlobal()->vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); + if(fn) + return fn(instance, create_info, NULL, messenger); + return VK_ERROR_EXTENSION_NOT_PRESENT; +} + +static bool InitValidationLayers(PulseBackend backend, VulkanInstance* instance) +{ + uint32_t extension_count; + VulkanGetGlobal()->vkEnumerateInstanceExtensionProperties(PULSE_NULLPTR, &extension_count, NULL); + VkExtensionProperties* extensions = (VkExtensionProperties*)calloc(extension_count, sizeof(VkExtensionProperties)); + PULSE_CHECK_ALLOCATION_RETVAL(extensions, false); + VulkanGetGlobal()->vkEnumerateInstanceExtensionProperties(PULSE_NULLPTR, &extension_count, extensions); + bool extension_found = false; + for(uint32_t i = 0; i < extension_count; i++) + { + if(strcmp(extensions[i].extensionName, VK_EXT_DEBUG_UTILS_EXTENSION_NAME) == 0) + { + extension_found = true; + break; + } + } + if(!extension_found) + { + fprintf(stderr, "Pulse: (Vulkan) " VK_EXT_DEBUG_UTILS_EXTENSION_NAME " is not present; cannot enable validation layers"); + free(extensions); + return false; + } + + VkDebugUtilsMessengerCreateInfoEXT create_info = { 0 }; + create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + create_info.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; + create_info.pfnUserCallback = DebugCallback; + create_info.pUserData = backend; + CHECK_VK_RETVAL(backend, CreateDebugUtilsMessengerEXT(instance->instance, &create_info, &instance->debug_messenger), PULSE_ERROR_INITIALIZATION_FAILED, false); + return true; +} + +static void DestroyDebugUtilsMessengerEXT(VulkanInstance* instance) +{ + PFN_vkDestroyDebugUtilsMessengerEXT fn = (PFN_vkDestroyDebugUtilsMessengerEXT)VulkanGetGlobal()->vkGetInstanceProcAddr(instance->instance, "vkDestroyDebugUtilsMessengerEXT"); + if(fn) + fn(instance->instance, instance->debug_messenger, PULSE_NULLPTR); +} + +static VkInstance VulkanCreateInstance(PulseBackend backend, const char** extensions_enabled, uint32_t extensions_count, bool enable_validation_layers) +{ VkInstance instance = VK_NULL_HANDLE; VkApplicationInfo app_info = {}; @@ -19,28 +110,46 @@ static VkInstance VulkanCreateInstance(const char** extensions_enabled, uint32_t app_info.engineVersion = PULSE_VERSION; app_info.apiVersion = VK_API_VERSION_1_0; - VkInstanceCreateInfo create_info = {}; + VkInstanceCreateInfo create_info = { 0 }; create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; create_info.pApplicationInfo = &app_info; create_info.enabledExtensionCount = extensions_count; create_info.ppEnabledExtensionNames = extensions_enabled; - if(debug_level == PULSE_NO_DEBUG) - create_info.enabledLayerCount = 0; - else - create_info.enabledLayerCount = sizeof(layer_names) / sizeof(layer_names[0]); - create_info.ppEnabledLayerNames = layer_names; - create_info.pNext = PULSE_NULLPTR; #ifdef PULSE_PLAT_MACOS create_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #else create_info.flags = 0; #endif + VkDebugUtilsMessengerCreateInfoEXT debug_create_info = { 0 }; + const char** new_extension_set = PULSE_NULLPTR; + if(enable_validation_layers) + { + new_extension_set = (const char**)calloc(extensions_count + 1, sizeof(char*)); + PULSE_CHECK_ALLOCATION_RETVAL(new_extension_set, PULSE_NULL_HANDLE); + memcpy(new_extension_set, extensions_enabled, sizeof(char*) * extensions_count); + new_extension_set[extensions_count] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; + + debug_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; + debug_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; + debug_create_info.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; + debug_create_info.pfnUserCallback = DebugCallback; + debug_create_info.pUserData = backend; + + create_info.enabledExtensionCount = extensions_count + 1; + create_info.ppEnabledExtensionNames = new_extension_set; + create_info.enabledLayerCount = sizeof(layer_names) / sizeof(layer_names[0]); + create_info.ppEnabledLayerNames = (const char* const*)layer_names; + create_info.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debug_create_info; + } + CHECK_VK_RETVAL(PULSE_NULL_HANDLE, VulkanGetGlobal()->vkCreateInstance(&create_info, PULSE_NULLPTR, &instance), PULSE_ERROR_INITIALIZATION_FAILED, VK_NULL_HANDLE); + if(enable_validation_layers) + free(new_extension_set); return instance; } -bool VulkanInitInstance(VulkanInstance* instance, PulseDebugLevel debug_level) +bool VulkanInitInstance(PulseBackend backend, VulkanInstance* instance, PulseDebugLevel debug_level) { #ifdef PULSE_PLAT_MACOS const char* extensions[] = { @@ -50,11 +159,14 @@ bool VulkanInitInstance(VulkanInstance* instance, PulseDebugLevel debug_level) const char* extensions[] = { }; #endif - instance->instance = VulkanCreateInstance(extensions, sizeof(extensions) / sizeof(char*), debug_level); + instance->validation_layers_enabled = (backend != PULSE_NULL_HANDLE && debug_level == PULSE_HIGH_DEBUG && CheckValidationLayerSupport()); + instance->instance = VulkanCreateInstance(backend, extensions, sizeof(extensions) / sizeof(char*), instance->validation_layers_enabled); if(instance->instance == VK_NULL_HANDLE) return false; if(!VulkanLoadInstance(instance)) return false; + if(instance->validation_layers_enabled) + InitValidationLayers(backend, instance); return true; } @@ -62,6 +174,8 @@ void VulkanDestroyInstance(VulkanInstance* instance) { if(instance == PULSE_NULLPTR || instance->instance == VK_NULL_HANDLE) return; + if(instance->validation_layers_enabled) + DestroyDebugUtilsMessengerEXT(instance); instance->vkDestroyInstance(instance->instance, PULSE_NULLPTR); instance->instance = VK_NULL_HANDLE; } diff --git a/Sources/Backends/Vulkan/VulkanInstance.h b/Sources/Backends/Vulkan/VulkanInstance.h index 67b7d2a..dcc9875 100644 --- a/Sources/Backends/Vulkan/VulkanInstance.h +++ b/Sources/Backends/Vulkan/VulkanInstance.h @@ -13,14 +13,16 @@ typedef struct VulkanInstance { - VkInstance instance; - #define PULSE_VULKAN_INSTANCE_FUNCTION(fn) PFN_##fn fn; #include "VulkanInstancePrototypes.h" #undef PULSE_VULKAN_INSTANCE_FUNCTION + + VkInstance instance; + VkDebugUtilsMessengerEXT debug_messenger; + bool validation_layers_enabled; } VulkanInstance; -bool VulkanInitInstance(VulkanInstance* instance, PulseDebugLevel debug_level); +bool VulkanInitInstance(PulseBackend backend, VulkanInstance* instance, PulseDebugLevel debug_level); void VulkanDestroyInstance(VulkanInstance* instance); #endif // PULSE_VULKAN_INSTANCE_H_ diff --git a/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp b/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp index 385a463..00dece7 100644 --- a/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp +++ b/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp @@ -6,4 +6,5 @@ #define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 #define VMA_VULKAN_VERSION 1000000 #define VMA_IMPLEMENTATION +#define VMA_ASSERT_LEAK(expr) (void(0)) #include diff --git a/Sources/PulseBackend.c b/Sources/PulseBackend.c index 58b4b77..56a16c2 100644 --- a/Sources/PulseBackend.c +++ b/Sources/PulseBackend.c @@ -105,7 +105,7 @@ PULSE_API PulseBackend PulseLoadBackend(PulseBackendFlags backend_candidates, Pu PulseBackend backend = PulseGetBackendFromFlag(backend_type); if(backend == PULSE_NULL_HANDLE) return PULSE_NULL_HANDLE; - if(!backend->PFN_LoadBackend(debug_level)) + if(!backend->PFN_LoadBackend(backend, debug_level)) return PULSE_NULL_HANDLE; backend->PFN_UserDebugCallback = PULSE_NULLPTR; backend->debug_level = debug_level; diff --git a/Sources/PulseBuffer.c b/Sources/PulseBuffer.c index 98ed6d7..9bee98b 100644 --- a/Sources/PulseBuffer.c +++ b/Sources/PulseBuffer.c @@ -48,5 +48,10 @@ PULSE_API void PulseDestroyBuffer(PulseDevice device, PulseBuffer buffer) PulseLogWarning(device->backend, "buffer is NULL, this may be a bug in your application"); return; } + if(buffer->is_mapped) + { + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogWarning(device->backend, "buffer is still mapped, consider unmapping it before destroy"); + } return device->PFN_DestroyBuffer(device, buffer); } diff --git a/Sources/PulseDefs.h b/Sources/PulseDefs.h index 845718b..1e924e3 100644 --- a/Sources/PulseDefs.h +++ b/Sources/PulseDefs.h @@ -94,6 +94,7 @@ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(UnmapBuffer, _namespace) \ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyBuffer, _namespace) \ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(CreateImage, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(IsImageFormatValid, _namespace) \ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyImage, _namespace) \ #endif // PULSE_DEFS_H_ diff --git a/Sources/PulseImage.c b/Sources/PulseImage.c index aac0a2c..cdd1ffd 100644 --- a/Sources/PulseImage.c +++ b/Sources/PulseImage.c @@ -1,6 +1,6 @@ // Copyright (C) 2024 kanel // This file is part of "Pulse" -// For conditions of distribution and use, see copyright notice in LICENSE +// conditions of distribution and use, see copyright notice in LICENSE #include "Pulse.h" #include "PulseDefs.h" @@ -17,10 +17,97 @@ PULSE_API PulseImage PulseCreateImage(PulseDevice device, const PulseImageCreate PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); return PULSE_NULL_HANDLE; } + + bool failed = false; + const uint32_t MAX_2D_DIMENSION = 16384; + const uint32_t MAX_3D_DIMENSION = 2048; + + if(create_infos->width <= 0 || create_infos->height <= 0 || create_infos->layer_count_or_depth <= 0) + { + PulseLogError(device->backend, "any image: width, height, and layer_count_or_depth must be >= 1"); + failed = true; + } + + if(create_infos->type == PULSE_IMAGE_TYPE_CUBE) + { + if(create_infos->width != create_infos->height) + { + PulseLogError(device->backend, "cube images: width and height must be identical"); + failed = true; + } + if(create_infos->width > MAX_2D_DIMENSION || create_infos->height > MAX_2D_DIMENSION) + { + PulseLogError(device->backend, "cube images: width and height must be <= 16384"); + failed = true; + } + if(create_infos->layer_count_or_depth != 6) + { + PulseLogError(device->backend, "cube images: layer_count_or_depth must be 6"); + failed = true; + } + if(!PulseIsImageFormatValid(device, create_infos->format, PULSE_IMAGE_TYPE_CUBE, create_infos->usage)) + { + PulseLogError(device->backend, "cube images: the format is unsupported for the given usage"); + failed = true; + } + } + else if(create_infos->type == PULSE_IMAGE_TYPE_CUBE_ARRAY) + { + if(create_infos->width != create_infos->height) + { + PulseLogError(device->backend, "cube array images: width and height must be identical"); + failed = true; + } + if(create_infos->width > MAX_2D_DIMENSION || create_infos->height > MAX_2D_DIMENSION) + { + PulseLogError(device->backend, "cube array images: width and height must be <= 16384"); + failed = true; + } + if(create_infos->layer_count_or_depth % 6 != 0) + { + PulseLogError(device->backend, "cube array images: layer_count_or_depth must be a multiple of 6"); + failed = true; + } + if(!PulseIsImageFormatValid(device, create_infos->format, PULSE_IMAGE_TYPE_CUBE_ARRAY, create_infos->usage)) + { + PulseLogError(device->backend, "cube array images: the format is unsupported for the given usage"); + failed = true; + } + } + else if(create_infos->type == PULSE_IMAGE_TYPE_3D) + { + if(create_infos->width > MAX_3D_DIMENSION || create_infos->height > MAX_3D_DIMENSION || create_infos->layer_count_or_depth > MAX_3D_DIMENSION) + { + PulseLogError(device->backend, "3D images: width, height, and layer_count_or_depth must be <= 2048"); + failed = true; + } + if(!PulseIsImageFormatValid(device, create_infos->format, PULSE_IMAGE_TYPE_3D, create_infos->usage)) + { + PulseLogError(device->backend, "3D images: the format is unsupported for the given usage"); + failed = true; + } + } + else + { + if(!PulseIsImageFormatValid(device, create_infos->format, PULSE_IMAGE_TYPE_2D, create_infos->usage)) + { + PulseLogError(device->backend, "2D images: the format is unsupported for the given usage"); + failed = true; + } + } + + if(failed) + return PULSE_NULL_HANDLE; } return device->PFN_CreateImage(device, create_infos); } +PULSE_API bool PulseIsImageFormatValid(PulseDevice device, PulseImageFormat format, PulseImageType type, PulseImageUsageFlags usage) +{ + PULSE_CHECK_HANDLE_RETVAL(device, false); + return device->PFN_IsImageFormatValid(device, format, type, usage); +} + PULSE_API void PulseDestroyImage(PulseDevice device, PulseImage image) { PULSE_CHECK_HANDLE(device); diff --git a/Sources/PulseInternal.h b/Sources/PulseInternal.h index 62d180c..db8629f 100644 --- a/Sources/PulseInternal.h +++ b/Sources/PulseInternal.h @@ -75,6 +75,7 @@ typedef struct PulseDeviceHandler PulseUnmapBufferPFN PFN_UnmapBuffer; PulseDestroyBufferPFN PFN_DestroyBuffer; PulseCreateImagePFN PFN_CreateImage; + PulseIsImageFormatValidPFN PFN_IsImageFormatValid; PulseDestroyImagePFN PFN_DestroyImage; // Attributes diff --git a/Sources/PulsePFNs.h b/Sources/PulsePFNs.h index b19674c..8d889b2 100644 --- a/Sources/PulsePFNs.h +++ b/Sources/PulsePFNs.h @@ -9,7 +9,7 @@ typedef PulseBackendFlags (*PulseCheckBackendSupportPFN)(PulseBackendFlags, PulseShaderFormatsFlags); -typedef bool (*PulseLoadBackendPFN)(PulseDebugLevel); +typedef bool (*PulseLoadBackendPFN)(PulseBackend, PulseDebugLevel); typedef void (*PulseUnloadBackendPFN)(PulseBackend); typedef PulseDevice (*PulseCreateDevicePFN)(PulseBackend, PulseDevice*, uint32_t); @@ -29,6 +29,7 @@ typedef bool (*PulseMapBufferPFN)(PulseBuffer, void**); typedef void (*PulseUnmapBufferPFN)(PulseBuffer); typedef void (*PulseDestroyBufferPFN)(PulseDevice, PulseBuffer); typedef PulseImage (*PulseCreateImagePFN)(PulseDevice, const PulseImageCreateInfo*); +typedef bool (*PulseIsImageFormatValidPFN)(PulseDevice, PulseImageFormat, PulseImageType, PulseImageUsageFlags); typedef void (*PulseDestroyImagePFN)(PulseDevice, PulseImage); #endif // PULSE_PFNS_H_ diff --git a/Tests/Vulkan/Buffer.c b/Tests/Vulkan/Buffer.c index 16c9932..593fb52 100644 --- a/Tests/Vulkan/Buffer.c +++ b/Tests/Vulkan/Buffer.c @@ -98,11 +98,15 @@ void TestBufferMapping() void* ptr; TEST_ASSERT_NOT_EQUAL_MESSAGE(PulseMapBuffer(buffer, &ptr), false, PulseVerbaliseErrorType(PulseGetLastErrorType())); TEST_ASSERT_NOT_NULL(ptr); - TEST_ASSERT_EQUAL(memcmp(ptr, data, 8), 0); + TEST_ASSERT_EQUAL(memcmp(ptr, data, 8), 0); PulseUnmapBuffer(buffer); } - PulseDestroyBuffer(device, buffer); + DISABLE_ERRORS; + void* ptr; + TEST_ASSERT_NOT_EQUAL_MESSAGE(PulseMapBuffer(buffer, &ptr), false, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyBuffer(device, buffer); + ENABLE_ERRORS; CleanupDevice(device); CleanupPulse(backend); diff --git a/Tests/Vulkan/Image.c b/Tests/Vulkan/Image.c index 01dc06f..0043733 100644 --- a/Tests/Vulkan/Image.c +++ b/Tests/Vulkan/Image.c @@ -10,15 +10,141 @@ void TestImageCreation() PulseDevice device; SetupDevice(backend, &device); - PulseImageCreateInfo image_create_info = { 0 }; - image_create_info.type = PULSE_IMAGE_TYPE_2D; - image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; - image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; - image_create_info.width = 256; - image_create_info.height = 256; - PulseImage image = PulseCreateImage(device, &image_create_info); - TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); - PulseDestroyImage(device, image); + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D_ARRAY; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_3D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_CUBE; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 6; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_CUBE_ARRAY; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 12; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_WRITE; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_SIMULTANEOUS_READWRITE; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8_SNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_NOT_EQUAL_MESSAGE(image, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyImage(device, image); + } + + DISABLE_ERRORS; + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 0; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_EQUAL(image, PULSE_NULL_HANDLE); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_2D; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = -1; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_EQUAL(image, PULSE_NULL_HANDLE); + PulseDestroyImage(device, image); + } + { + PulseImageCreateInfo image_create_info = { 0 }; + image_create_info.type = PULSE_IMAGE_TYPE_CUBE; + image_create_info.format = PULSE_IMAGE_FORMAT_R8G8B8A8_UNORM; + image_create_info.usage = PULSE_IMAGE_USAGE_STORAGE_READ; + image_create_info.width = 256; + image_create_info.height = 256; + image_create_info.layer_count_or_depth = 1; + PulseImage image = PulseCreateImage(device, &image_create_info); + TEST_ASSERT_EQUAL(image, PULSE_NULL_HANDLE); + PulseDestroyImage(device, image); + } + ENABLE_ERRORS; CleanupDevice(device); CleanupPulse(backend);