From 405c8b186a0e0041186afe7d39dce8e80b6ecc86 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Thu, 10 Oct 2024 15:22:56 +0200 Subject: [PATCH] adding Vulkan device creation, adding unit tests, adding few API function declarations --- .gitignore | 1 + Examples/Vulkan/main.c | 23 +++ .../Vulkan}/xmake.lua | 4 +- Examples/xmake.lua | 7 + Includes/Pulse.h | 38 +++- Sources/Backends/Vulkan/Vulkan.h | 14 +- .../Backends/Vulkan/VulkanComputePipeline.h | 24 +++ Sources/Backends/Vulkan/VulkanDevice.c | 183 +++++++++++++++--- Sources/Backends/Vulkan/VulkanDevice.h | 10 +- Sources/Backends/Vulkan/VulkanEnums.h | 20 ++ Sources/Backends/Vulkan/VulkanInstance.c | 5 +- .../VulkanMemoryAllocatorImplementation.cpp | 3 + Sources/Backends/Vulkan/VulkanQueue.c | 83 ++++++++ Sources/Backends/Vulkan/VulkanQueue.h | 27 +++ Sources/PulseComputePipeline.c | 22 +++ Sources/PulseDevice.c | 8 +- Sources/PulseInternal.h | 15 +- Tests/LoadingPulse/main.c | 16 -- Tests/Vulkan/DeviceSetup.c | 11 ++ Tests/Vulkan/main.c | 41 ++++ Tests/Vulkan/xmake.lua | 16 ++ Tests/xmake.lua | 6 - xmake.lua | 4 +- 23 files changed, 517 insertions(+), 64 deletions(-) create mode 100644 Examples/Vulkan/main.c rename {Tests/LoadingPulse => Examples/Vulkan}/xmake.lua (68%) create mode 100644 Examples/xmake.lua create mode 100644 Sources/Backends/Vulkan/VulkanComputePipeline.h create mode 100644 Sources/Backends/Vulkan/VulkanEnums.h create mode 100644 Sources/Backends/Vulkan/VulkanQueue.c create mode 100644 Sources/Backends/Vulkan/VulkanQueue.h create mode 100644 Sources/PulseComputePipeline.c delete mode 100644 Tests/LoadingPulse/main.c create mode 100644 Tests/Vulkan/DeviceSetup.c create mode 100644 Tests/Vulkan/main.c create mode 100644 Tests/Vulkan/xmake.lua delete mode 100644 Tests/xmake.lua diff --git a/.gitignore b/.gitignore index 5e2ad6f..ce48fec 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ !/Includes/ !/Sources/ !/Tests/ +!/Examples/ !/.github/ build/ Build/ diff --git a/Examples/Vulkan/main.c b/Examples/Vulkan/main.c new file mode 100644 index 0000000..7953e4d --- /dev/null +++ b/Examples/Vulkan/main.c @@ -0,0 +1,23 @@ +#include + +#include + +#define CHECK_PULSE_HANDLE_RETVAL(handle, retval) \ + if(handle == PULSE_NULL_HANDLE) \ + { \ + fprintf(stderr, "Error: %s", PulseVerbaliseErrorType(PulseGetLastErrorType())); \ + return retval; \ + } \ + +int main(void) +{ + PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_NO_DEBUG); + CHECK_PULSE_HANDLE_RETVAL(backend, 1); + PulseDevice device = PulseCreateDevice(backend, NULL, 0); + CHECK_PULSE_HANDLE_RETVAL(device, 1); + + PulseDestroyDevice(device); + PulseUnloadBackend(backend); + puts("Successfully loaded Pulse using Vulkan !"); + return 0; +} diff --git a/Tests/LoadingPulse/xmake.lua b/Examples/Vulkan/xmake.lua similarity index 68% rename from Tests/LoadingPulse/xmake.lua rename to Examples/Vulkan/xmake.lua index 1839a52..77dab0a 100644 --- a/Tests/LoadingPulse/xmake.lua +++ b/Examples/Vulkan/xmake.lua @@ -1,7 +1,7 @@ -target("LoadingPulse") +target("VulkanExample") add_deps("pulse_gpu") if is_plat("linux") then set_extension(".x86_64") end - add_files("main.c") + add_files("*.c") target_end() diff --git a/Examples/xmake.lua b/Examples/xmake.lua new file mode 100644 index 0000000..65bdc48 --- /dev/null +++ b/Examples/xmake.lua @@ -0,0 +1,7 @@ +option("examples", { description = "Build the examples", default = false }) + +if has_config("examples") then + set_group("Examples") + includes("*/xmake.lua") +end + diff --git a/Includes/Pulse.h b/Includes/Pulse.h index 221f9e5..d921087 100644 --- a/Includes/Pulse.h +++ b/Includes/Pulse.h @@ -27,9 +27,9 @@ PULSE_DEFINE_NULLABLE_HANDLE(PulseBuffer); PULSE_DEFINE_NULLABLE_HANDLE(PulseCommandList); PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePass); PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePipeline); -PULSE_DEFINE_NULLABLE_HANDLE(PulseCopyPass); PULSE_DEFINE_NULLABLE_HANDLE(PulseDevice); PULSE_DEFINE_NULLABLE_HANDLE(PulseFence); +PULSE_DEFINE_NULLABLE_HANDLE(PulseGeneralPass); PULSE_DEFINE_NULLABLE_HANDLE(PulseImage); // Flags @@ -83,6 +83,19 @@ typedef enum PulseDebugLevel PULSE_PARANOID_DEBUG // Causes every warning to be treated as error } PulseDebugLevel; +typedef enum PulseDebugMessageSeverity +{ + PULSE_DEBUG_MESSAGE_SEVERITY_INFO, + PULSE_DEBUG_MESSAGE_SEVERITY_WARNING, + PULSE_DEBUG_MESSAGE_SEVERITY_ERROR +} PulseDebugMessageSeverity; + +typedef enum PulseDebugMessageType +{ + PULSE_DEBUG_MESSAGE_TYPE_GENERAL, + PULSE_DEBUG_MESSAGE_TYPE_PERFORMANCE +} PulseDebugMessageType; + typedef enum PulseErrorType { PULSE_ERROR_NONE, @@ -250,14 +263,37 @@ typedef struct PulseImageRegion } PulseImageRegion; // Functions +typedef void (*PulseDebugCallbackPFN)(PulseDebugMessageSeverity, PulseDebugMessageType, const char*); + PULSE_API PulseBackend PulseLoadBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used, PulseDebugLevel debug_level); PULSE_API void PulseUnloadBackend(PulseBackend backend); +PULSE_API void PulseSetDebugCallback(PulseBackend backend, PulseDebugCallbackPFN callback); + PULSE_API PulseDevice PulseCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count); PULSE_API void PulseDestroyDevice(PulseDevice device); PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device); PULSE_API bool PulseSupportsBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used); PULSE_API bool PulseDeviceSupportsShaderFormats(PulseDevice device, PulseShaderFormatsFlags shader_formats_used); +PULSE_API PulseCommandList PulseRequestCommandList(PulseDevice device); +PULSE_API bool PulseSubmitCommandList(PulseDevice device, PulseCommandList cmd, PulseFence fence); +PULSE_API void PulseReleaseCommandList(PulseDevice device, PulseCommandList cmd); + +PULSE_API PulseComputePass PulseBeginComputePass(PulseCommandList cmd); +PULSE_API void PulseEndComputePass(PulseComputePass pass); + +PULSE_API PulseGeneralPass PulseBeginGeneralPass(PulseCommandList cmd); +PULSE_API void PulseEndGeneralPass(PulseGeneralPass pass); + +PULSE_API PulseFence PulseCreateFence(PulseDevice device); +PULSE_API void PulseDestroyFence(PulseDevice device, PulseFence fence); +PULSE_API bool PulseIsFenceReady(PulseDevice device, PulseFence fence); +PULSE_API bool PulseWaitForFences(PulseDevice device, PulseFence* const* fences, uint32_t fences_count, bool wait_for_all); + +PULSE_API PulseComputePipeline PulseCreateComputePipeline(PulseDevice device, const PulseComputePipelineCreateInfo* info); +PULSE_API void PulseDestroyComputePipeline(PulseDevice device, PulseComputePipeline pipeline); +PULSE_API void PulseBindComputePipeline(PulseComputePass pass, PulseComputePipeline pipeline); + PULSE_API PulseErrorType PulseGetLastErrorType(); // /!\ Warning /!\ Call to this function resets the internal last error variable PULSE_API const char* PulseVerbaliseErrorType(PulseErrorType error); diff --git a/Sources/Backends/Vulkan/Vulkan.h b/Sources/Backends/Vulkan/Vulkan.h index bb4d526..69281da 100644 --- a/Sources/Backends/Vulkan/Vulkan.h +++ b/Sources/Backends/Vulkan/Vulkan.h @@ -12,7 +12,18 @@ #include "VulkanDevice.h" #include "VulkanInstance.h" -#define VULKAN_RETRIEVE_DRIVER_DATA(device) ((VulkanDriverData*)device->driver_data) +#include "../../PulseInternal.h" + +#define VULKAN_RETRIEVE_DRIVER_DATA(handle) ((VulkanDriverData*)handle->driver_data) + +#define CHECK_VK_RETVAL(res, error, retval) \ + if((res) != VK_SUCCESS) \ + { \ + PulseSetInternalError(error); \ + return retval; \ + } + +#define CHECK_VK(res, error) CHECK_VK_RETVAL(res, error, ) typedef struct VulkanGlobal { @@ -24,7 +35,6 @@ typedef struct VulkanGlobal typedef struct VulkanDriverData { VulkanInstance instance; - VulkanDevice device; } VulkanDriverData; VulkanGlobal* VulkanGetGlobal(); diff --git a/Sources/Backends/Vulkan/VulkanComputePipeline.h b/Sources/Backends/Vulkan/VulkanComputePipeline.h new file mode 100644 index 0000000..2d6b571 --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanComputePipeline.h @@ -0,0 +1,24 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#ifdef PULSE_ENABLE_VULKAN_BACKEND + +#ifndef PULSE_VULKAN_COMPUTE_PIPELINE_H_ +#define PULSE_VULKAN_COMPUTE_PIPELINE_H_ + +#include + +#include + +typedef struct VulkanComputePipeline +{ +} VulkanComputePipeline; + +PulseComputePipeline VulkanCreateComputePipeline(PulseDevice device, const PulseComputePipelineCreateInfo* info); +void VulkanBindComputePipeline(PulseComputePass pass, PulseComputePipeline pipeline); +void VulkanDestroyComputePipeline(PulseDevice device, PulseComputePipeline pipeline); + +#endif // PULSE_VULKAN_COMPUTE_PIPELINE_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/Backends/Vulkan/VulkanDevice.c b/Sources/Backends/Vulkan/VulkanDevice.c index 92827c6..6d0d919 100644 --- a/Sources/Backends/Vulkan/VulkanDevice.c +++ b/Sources/Backends/Vulkan/VulkanDevice.c @@ -2,26 +2,32 @@ // This file is part of "Pulse" // For conditions of distribution and use, see copyright notice in LICENSE -#include "VulkanInstance.h" +#include "Pulse.h" +#include "Vulkan.h" +#include "VulkanComputePipeline.h" #include "VulkanDevice.h" +#include "VulkanInstance.h" +#include "VulkanLoader.h" +#include "VulkanQueue.h" #include "../../PulseInternal.h" -/* -static int32_t VulkanScorePhysicalDevice(VkPhysicalDevice device, const char** device_extensions, uint32_t device_extensions_count) + +#include + +static int32_t VulkanScorePhysicalDevice(VulkanInstance* instance, VkPhysicalDevice device, const char** device_extensions, uint32_t device_extensions_count) { - PULSE_DECLARE_STACK_FIXED_ALLOCATOR(allocator, sizeof(VkExtensionProperties) * 4096, sizeof(VkExtensionProperties)); // Check extensions support uint32_t extension_count; - kbhGetVulkanPFNs()->vkEnumerateDeviceExtensionProperties(device, PULSE_NULLPTR, &extension_count, PULSE_NULLPTR); - VkExtensionProperties* props = (VkExtensionProperties*)kbhCallocInFixed(&allocator, extension_count, sizeof(VkExtensionProperties)); + instance->vkEnumerateDeviceExtensionProperties(device, PULSE_NULLPTR, &extension_count, PULSE_NULLPTR); + VkExtensionProperties* props = (VkExtensionProperties*)calloc(extension_count, sizeof(VkExtensionProperties)); if(!props) return -1; - kbhGetVulkanPFNs()->vkEnumerateDeviceExtensionProperties(device, PULSE_NULLPTR, &extension_count, props); + instance->vkEnumerateDeviceExtensionProperties(device, PULSE_NULLPTR, &extension_count, props); bool are_there_required_device_extensions = true; - for(int j = 0; j < device_extensions_count; j++) + for(uint32_t j = 0; j < device_extensions_count; j++) { bool is_there_extension = false; - for(int k = 0; k < extension_count; k++) + for(uint32_t k = 0; k < extension_count; k++) { if(strcmp(device_extensions[j], props[k].extensionName) == 0) { @@ -40,18 +46,18 @@ static int32_t VulkanScorePhysicalDevice(VkPhysicalDevice device, const char** d // Check Queue Families Support int32_t queue; - if(kbhFindPhysicalDeviceQueueFamily(device, KBH_VULKAN_QUEUE_COMPUTE, &queue) != KBH_RHI_SUCCESS) + if(!VulkanFindPhysicalDeviceQueueFamily(instance, device, VULKAN_QUEUE_COMPUTE, &queue)) return -1; VkPhysicalDeviceProperties device_props; - kbhGetVulkanPFNs()->vkGetPhysicalDeviceProperties(device, &device_props); + instance->vkGetPhysicalDeviceProperties(device, &device_props); VkPhysicalDeviceFeatures device_features; - kbhGetVulkanPFNs()->vkGetPhysicalDeviceFeatures(device, &device_features); + instance->vkGetPhysicalDeviceFeatures(device, &device_features); int32_t score = -1; if(device_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) - score += 1001; + score += 10000; score += device_props.limits.maxComputeWorkGroupCount[0]; score += device_props.limits.maxComputeWorkGroupCount[1]; @@ -61,7 +67,20 @@ static int32_t VulkanScorePhysicalDevice(VkPhysicalDevice device, const char** d return score; } -static VkPhysicalDevice VulkanPickPhysicalDevice(VulkanInstance* instance) +static bool VulkanIsDeviceForbidden(VkPhysicalDevice device, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count) +{ + if(device == VK_NULL_HANDLE) + return true; + #pragma omp parallel for + for(uint32_t i = 0; i < forbiden_devices_count; i++) + { + if(device == ((VulkanDevice*)forbiden_devices[i]->driver_data)->physical) + return true; + } + return false; +} + +static VkPhysicalDevice VulkanPickPhysicalDevice(VulkanInstance* instance, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count) { VkPhysicalDevice* devices = PULSE_NULLPTR; VkPhysicalDevice chosen_one = VK_NULL_HANDLE; @@ -73,9 +92,12 @@ static VkPhysicalDevice VulkanPickPhysicalDevice(VulkanInstance* instance) PULSE_CHECK_ALLOCATION_RETVAL(devices, VK_NULL_HANDLE); instance->vkEnumeratePhysicalDevices(instance->instance, &device_count, devices); - for(int i = 0; i < device_count; i++) + #pragma omp parallel for + for(uint32_t i = 0; i < device_count; i++) { - int32_t current_device_score = VulkanScorePhysicalDevice(devices[i], PULSE_NULLPTR, 0); + if(VulkanIsDeviceForbidden(devices[i], forbiden_devices, forbiden_devices_count)) + continue; + int32_t current_device_score = VulkanScorePhysicalDevice(instance, devices[i], PULSE_NULLPTR, 0); if(current_device_score > best_device_score) { best_device_score = current_device_score; @@ -84,17 +106,132 @@ static VkPhysicalDevice VulkanPickPhysicalDevice(VulkanInstance* instance) } return chosen_one; } -*/ -void* VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count) +PulseDevice VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count) { + PULSE_CHECK_HANDLE_RETVAL(backend, PULSE_NULLPTR); + + PulseDevice pulse_device = (PulseDeviceHandler*)calloc(1, sizeof(PulseDeviceHandler)); + PULSE_CHECK_ALLOCATION_RETVAL(pulse_device, PULSE_NULL_HANDLE); + + VulkanDevice* device = (VulkanDevice*)calloc(1, sizeof(VulkanDevice)); + PULSE_CHECK_ALLOCATION_RETVAL(device, PULSE_NULLPTR); + + VulkanInstance* instance = &VULKAN_RETRIEVE_DRIVER_DATA(backend)->instance; + + device->physical = VulkanPickPhysicalDevice(instance, forbiden_devices, forbiden_devices_count); + PULSE_CHECK_HANDLE_RETVAL(device->physical, PULSE_NULLPTR); + + const float queue_priority = 1.0f; + + VkDeviceQueueCreateInfo* queue_create_infos = (VkDeviceQueueCreateInfo*)PulseStaticAllocStack(VULKAN_QUEUE_END_ENUM * sizeof(VkDeviceQueueCreateInfo)); + // No need to check allocation, it is allocated on the stack + + uint32_t unique_queues_count = 1; + + for(int32_t i = 0; i < VULKAN_QUEUE_END_ENUM; i++) // Needs to be done before next loop + { + if(!VulkanPrepareDeviceQueue(instance, device, i)) + { + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return PULSE_NULLPTR; + } + } + + for(int32_t i = 0; i < VULKAN_QUEUE_END_ENUM; i++) + { + if(device->queues[i]->queue_family_index == -1) + continue; + int j; + for(j = i; j < VULKAN_QUEUE_END_ENUM; j++) // Ugly shit but array will never be big so it's okay + { + if(device->queues[i]->queue_family_index == device->queues[j]->queue_family_index) + break; + } + if(j == VULKAN_QUEUE_END_ENUM) + unique_queues_count++; + queue_create_infos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; + queue_create_infos[i].queueFamilyIndex = device->queues[i]->queue_family_index; + queue_create_infos[i].queueCount = 1; + queue_create_infos[i].pQueuePriorities = &queue_priority; + queue_create_infos[i].flags = 0; + queue_create_infos[i].pNext = PULSE_NULLPTR; + } + + instance->vkGetPhysicalDeviceProperties(device->physical, &device->properties); + instance->vkGetPhysicalDeviceMemoryProperties(device->physical, &device->memory_properties); + instance->vkGetPhysicalDeviceFeatures(device->physical, &device->features); + + VkDeviceCreateInfo create_info = {}; + create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; + create_info.queueCreateInfoCount = unique_queues_count; + create_info.pQueueCreateInfos = queue_create_infos; + create_info.pEnabledFeatures = &device->features; + create_info.enabledExtensionCount = 0; + create_info.ppEnabledExtensionNames = PULSE_NULLPTR; + create_info.enabledLayerCount = 0; + create_info.ppEnabledLayerNames = PULSE_NULLPTR; + 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); + if(!VulkanLoadDevice(instance, device)) + { + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return PULSE_NULLPTR; + } + + for(int32_t i = 0; i < VULKAN_QUEUE_END_ENUM; i++) + { + if(!VulkanRetrieveDeviceQueue(device, (VulkanQueueType)i)) + { + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return PULSE_NULLPTR; + } + } + + VmaVulkanFunctions vma_vulkan_func = {}; + vma_vulkan_func.vkAllocateMemory = device->vkAllocateMemory; + vma_vulkan_func.vkBindBufferMemory = device->vkBindBufferMemory; + vma_vulkan_func.vkBindImageMemory = device->vkBindImageMemory; + vma_vulkan_func.vkCreateBuffer = device->vkCreateBuffer; + vma_vulkan_func.vkCreateImage = device->vkCreateImage; + vma_vulkan_func.vkDestroyBuffer = device->vkDestroyBuffer; + vma_vulkan_func.vkDestroyImage = device->vkDestroyImage; + vma_vulkan_func.vkFlushMappedMemoryRanges = device->vkFlushMappedMemoryRanges; + vma_vulkan_func.vkFreeMemory = device->vkFreeMemory; + vma_vulkan_func.vkGetBufferMemoryRequirements = device->vkGetBufferMemoryRequirements; + vma_vulkan_func.vkGetImageMemoryRequirements = device->vkGetImageMemoryRequirements; + vma_vulkan_func.vkInvalidateMappedMemoryRanges = device->vkInvalidateMappedMemoryRanges; + vma_vulkan_func.vkMapMemory = device->vkMapMemory; + vma_vulkan_func.vkUnmapMemory = device->vkUnmapMemory; + vma_vulkan_func.vkCmdCopyBuffer = device->vkCmdCopyBuffer; + vma_vulkan_func.vkGetPhysicalDeviceMemoryProperties = instance->vkGetPhysicalDeviceMemoryProperties; + vma_vulkan_func.vkGetPhysicalDeviceProperties = instance->vkGetPhysicalDeviceProperties; + + VmaAllocatorCreateInfo allocator_create_info = {}; + allocator_create_info.vulkanApiVersion = VK_API_VERSION_1_0; + allocator_create_info.physicalDevice = device->physical; + allocator_create_info.device = device->device; + 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); + + pulse_device->driver_data = device; + pulse_device->backend = backend; + PULSE_LOAD_DRIVER_DEVICE(Vulkan); + return pulse_device; } -void VulkanDestroyDevice(VulkanDevice* device) +void VulkanDestroyDevice(PulseDevice device) { - if(device == PULSE_NULLPTR || device->device == VK_NULL_HANDLE) + VulkanDevice* vulkan_device = (VulkanDevice*)device->driver_data; + if(vulkan_device == PULSE_NULLPTR || vulkan_device->device == VK_NULL_HANDLE) return; - vmaDestroyAllocator(device->allocator); - device->vkDestroyDevice(device->device, PULSE_NULLPTR); - device->device = VK_NULL_HANDLE; + vmaDestroyAllocator(vulkan_device->allocator); + vulkan_device->vkDestroyDevice(vulkan_device->device, PULSE_NULLPTR); + vulkan_device->device = VK_NULL_HANDLE; + free(vulkan_device); + free(device); } diff --git a/Sources/Backends/Vulkan/VulkanDevice.h b/Sources/Backends/Vulkan/VulkanDevice.h index 8bd3e81..ed8e492 100644 --- a/Sources/Backends/Vulkan/VulkanDevice.h +++ b/Sources/Backends/Vulkan/VulkanDevice.h @@ -16,8 +16,14 @@ #include +#include "VulkanEnums.h" + +struct VulkanQueue; + typedef struct VulkanDevice { + struct VulkanQueue* queues[VULKAN_QUEUE_END_ENUM]; + VkPhysicalDeviceFeatures features; VkPhysicalDeviceMemoryProperties memory_properties; VkPhysicalDeviceProperties properties; @@ -32,8 +38,8 @@ typedef struct VulkanDevice #undef PULSE_VULKAN_DEVICE_FUNCTION } VulkanDevice; -void* VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count); -void VulkanDestroyDevice(VulkanDevice* device); +PulseDevice VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count); +void VulkanDestroyDevice(PulseDevice device); #endif // PULSE_VULKAN_DEVICE_H_ diff --git a/Sources/Backends/Vulkan/VulkanEnums.h b/Sources/Backends/Vulkan/VulkanEnums.h new file mode 100644 index 0000000..5afa011 --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanEnums.h @@ -0,0 +1,20 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#ifdef PULSE_ENABLE_VULKAN_BACKEND + +#ifndef PULSE_VULKAN_ENUMS_H_ +#define PULSE_VULKAN_ENUMS_H_ + +typedef enum VulkanQueueType +{ + VULKAN_QUEUE_COMPUTE = 0, + VULKAN_QUEUE_TRANSFER = 1, + + VULKAN_QUEUE_END_ENUM // For internal use only +} VulkanQueueType; + +#endif // PULSE_VULKAN_ENUMS_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/Backends/Vulkan/VulkanInstance.c b/Sources/Backends/Vulkan/VulkanInstance.c index 5740d5a..31f003d 100644 --- a/Sources/Backends/Vulkan/VulkanInstance.c +++ b/Sources/Backends/Vulkan/VulkanInstance.c @@ -36,7 +36,7 @@ static VkInstance VulkanCreateInstance(const char** extensions_enabled, uint32_t create_info.flags = 0; #endif - VulkanGetGlobal()->vkCreateInstance(&create_info, PULSE_NULLPTR, &instance); + CHECK_VK_RETVAL(VulkanGetGlobal()->vkCreateInstance(&create_info, PULSE_NULLPTR, &instance), PULSE_ERROR_INITIALIZATION_FAILED, VK_NULL_HANDLE); return instance; } @@ -52,10 +52,7 @@ bool VulkanInitInstance(VulkanInstance* instance, PulseDebugLevel debug_level) #endif instance->instance = VulkanCreateInstance(extensions, sizeof(extensions) / sizeof(char*), debug_level); if(instance->instance == VK_NULL_HANDLE) - { - PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); return false; - } if(!VulkanLoadInstance(instance)) return false; return true; diff --git a/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp b/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp index d02524c..385a463 100644 --- a/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp +++ b/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp @@ -2,5 +2,8 @@ // This file is part of "Pulse" // For conditions of distribution and use, see copyright notice in LICENSE +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#define VMA_VULKAN_VERSION 1000000 #define VMA_IMPLEMENTATION #include diff --git a/Sources/Backends/Vulkan/VulkanQueue.c b/Sources/Backends/Vulkan/VulkanQueue.c new file mode 100644 index 0000000..b9f767d --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanQueue.c @@ -0,0 +1,83 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#include "VulkanQueue.h" +#include "Vulkan.h" + +#include + +bool VulkanFindPhysicalDeviceQueueFamily(VulkanInstance* instance, VkPhysicalDevice physical, VulkanQueueType type, int32_t* queue_family_index) +{ + if(physical == VK_NULL_HANDLE) + return false; + + uint32_t queue_family_count; + instance->vkGetPhysicalDeviceQueueFamilyProperties(physical, &queue_family_count, PULSE_NULLPTR); + VkQueueFamilyProperties* queue_families = (VkQueueFamilyProperties*)calloc(queue_family_count, sizeof(VkQueueFamilyProperties)); + if(!queue_families) + return false; + instance->vkGetPhysicalDeviceQueueFamilyProperties(physical, &queue_family_count, queue_families); + + bool found = false; + for(uint32_t i = 0; i < queue_family_count; i++) + { + if(type == VULKAN_QUEUE_COMPUTE) + { + // try to find a queue that's only for compute + if(queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT && (queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT) == 0) + { + *queue_family_index = i; + found = true; + break; + } + if(queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT) // else just find a compute queue + { + *queue_family_index = i; + found = true; + break; + } + } + else if(type == VULKAN_QUEUE_TRANSFER) + { + if(queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT && (queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT) == 0) + { + *queue_family_index = i; + found = true; + break; + } + if(queue_families[i].queueFlags & VK_QUEUE_TRANSFER_BIT) + { + *queue_family_index = i; + found = true; + break; + } + } + } + free(queue_families); + return found; +} + +bool VulkanPrepareDeviceQueue(VulkanInstance* instance, VulkanDevice* device, VulkanQueueType type) +{ + if(device == PULSE_NULLPTR) + return false; + device->queues[(int)type] = (VulkanQueue*)malloc(sizeof(VulkanQueue)); + if(!device->queues[(int)type]) + return false; + VulkanQueue* queue = device->queues[(int)type]; + if(!queue) + return false; + return VulkanFindPhysicalDeviceQueueFamily(instance, device->physical, type, &queue->queue_family_index); +} + +bool VulkanRetrieveDeviceQueue(VulkanDevice* device, VulkanQueueType type) +{ + if(device == PULSE_NULLPTR) + return false; + VulkanQueue* queue = device->queues[(int)type]; + if(!queue) + return false; + device->vkGetDeviceQueue(device->device, queue->queue_family_index, 0, &queue->queue); + return true; +} diff --git a/Sources/Backends/Vulkan/VulkanQueue.h b/Sources/Backends/Vulkan/VulkanQueue.h new file mode 100644 index 0000000..4cb8c8f --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanQueue.h @@ -0,0 +1,27 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#ifdef PULSE_ENABLE_VULKAN_BACKEND + +#ifndef PULSE_VULKAN_QUEUES_H_ +#define PULSE_VULKAN_QUEUES_H_ + +#include "VulkanEnums.h" +#include "VulkanDevice.h" +#include "VulkanInstance.h" + +typedef struct VulkanQueue +{ + VulkanDevice* device; + VkQueue queue; + int32_t queue_family_index; +} VulkanQueue; + +bool VulkanFindPhysicalDeviceQueueFamily(VulkanInstance* instance, VkPhysicalDevice physical, VulkanQueueType type, int32_t* queue_family_index); +bool VulkanPrepareDeviceQueue(VulkanInstance* instance, VulkanDevice* device, VulkanQueueType type); +bool VulkanRetrieveDeviceQueue(VulkanDevice* device, VulkanQueueType type); + +#endif // PULSE_VULKAN_QUEUES_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/PulseComputePipeline.c b/Sources/PulseComputePipeline.c new file mode 100644 index 0000000..89dfc01 --- /dev/null +++ b/Sources/PulseComputePipeline.c @@ -0,0 +1,22 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#include +#include "PulseInternal.h" + +PULSE_API PulseComputePipeline PulseCreateComputePipeline(PulseDevice device, const PulseComputePipelineCreateInfo* info) +{ + PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULL_HANDLE); +} + +PULSE_API void PulseBindComputePipeline(PulseComputePass pass, PulseComputePipeline pipeline) +{ + PULSE_CHECK_HANDLE(pass); + PULSE_CHECK_HANDLE(pipeline); +} + +PULSE_API void PulseDestroyComputePipeline(PulseDevice device, PulseComputePipeline pipeline) +{ + PULSE_CHECK_HANDLE(device); +} diff --git a/Sources/PulseDevice.c b/Sources/PulseDevice.c index fdbeae3..4488c42 100644 --- a/Sources/PulseDevice.c +++ b/Sources/PulseDevice.c @@ -7,17 +7,15 @@ PULSE_API PulseDevice PulseCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count) { - PulseDevice device = backend->PFN_CreateDevice(backend, forbiden_devices, forbiden_devices_count); - if(device == PULSE_NULL_HANDLE) - PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); - device->backend = backend; - return device; + PULSE_CHECK_HANDLE_RETVAL(backend, PULSE_NULL_HANDLE); + return backend->PFN_CreateDevice(backend, forbiden_devices, forbiden_devices_count); } PULSE_API void PulseDestroyDevice(PulseDevice device) { PULSE_CHECK_HANDLE(device); device->PFN_DestroyDevice(device); + device->driver_data = PULSE_NULLPTR; } PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device) diff --git a/Sources/PulseInternal.h b/Sources/PulseInternal.h index 222f4fd..f8dc8e7 100644 --- a/Sources/PulseInternal.h +++ b/Sources/PulseInternal.h @@ -11,6 +11,8 @@ extern "C" { #endif +#define PulseStaticAllocStack(size) ((char[size]){ 0 }) + #define PULSE_CHECK_ALLOCATION_RETVAL(ptr, retval) \ do { \ if(ptr == PULSE_NULLPTR) \ @@ -37,9 +39,12 @@ typedef PulseBackendFlags (*PulseCheckBackendSupportPFN)(PulseBackendFlags, Puls typedef bool (*PulseLoadBackendPFN)(PulseDebugLevel); typedef void (*PulseUnloadBackendPFN)(PulseBackend); -typedef void* (*PulseCreateDevicePFN)(PulseBackend, PulseDevice*, uint32_t); +typedef PulseDevice (*PulseCreateDevicePFN)(PulseBackend, PulseDevice*, uint32_t); typedef void (*PulseDestroyDevicePFN)(PulseDevice); +typedef PulseComputePipeline (*PulseCreateComputePipelinePFN)(PulseDevice, const PulseComputePipelineCreateInfo*); +typedef void (*PulseBindComputePipelinePFN)(PulseComputePass, PulseComputePipeline); +typedef void (*PulseDestroyComputePipelinePFN)(PulseDevice, PulseComputePipeline); typedef struct PulseBackendHandler { @@ -58,6 +63,9 @@ typedef struct PulseDeviceHandler { // PFNs PulseDestroyDevicePFN PFN_DestroyDevice; + PulseCreateComputePipelinePFN PFN_CreateComputePipeline; + PulseBindComputePipelinePFN PFN_BindComputePipeline; + PulseDestroyComputePipelinePFN PFN_DestroyComputePipeline; // Attributes void* driver_data; @@ -66,9 +74,12 @@ typedef struct PulseDeviceHandler void PulseSetInternalError(PulseErrorType error); -#define PULSE_LOAD_DRIVER_DEVICE_FUNCTION(fn, _namespace) device->PFN_##fn = _namespace##fn; +#define PULSE_LOAD_DRIVER_DEVICE_FUNCTION(fn, _namespace) pulse_device->PFN_##fn = _namespace##fn; #define PULSE_LOAD_DRIVER_DEVICE(_namespace) \ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyDevice, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(CreateComputePipeline, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(BindComputePipeline, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyComputePipeline, _namespace) \ #ifdef PULSE_ENABLE_VULKAN_BACKEND extern PulseBackendHandler VulkanDriver; diff --git a/Tests/LoadingPulse/main.c b/Tests/LoadingPulse/main.c deleted file mode 100644 index 62d9959..0000000 --- a/Tests/LoadingPulse/main.c +++ /dev/null @@ -1,16 +0,0 @@ -#include - -#include - -int main(void) -{ - PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_ANY, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_NO_DEBUG); - if(backend == PULSE_NULL_HANDLE) - { - fprintf(stderr, "Error while loading Pulse: %s", PulseVerbaliseErrorType(PulseGetLastErrorType())); - return 1; - } - PulseUnloadBackend(backend); - puts("Successfully loaded Pulse !"); - return 0; -} diff --git a/Tests/Vulkan/DeviceSetup.c b/Tests/Vulkan/DeviceSetup.c new file mode 100644 index 0000000..4861f74 --- /dev/null +++ b/Tests/Vulkan/DeviceSetup.c @@ -0,0 +1,11 @@ +#include +#include + +extern PulseBackend backend; + +void TestDeviceSetup() +{ + PulseDevice device = PulseCreateDevice(backend, NULL, 0); + TEST_ASSERT_NOT_EQUAL_MESSAGE(device, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyDevice(device); +} diff --git a/Tests/Vulkan/main.c b/Tests/Vulkan/main.c new file mode 100644 index 0000000..539c7b1 --- /dev/null +++ b/Tests/Vulkan/main.c @@ -0,0 +1,41 @@ +#include +#include + +#include + +PulseBackend backend; + +extern void TestDeviceSetup(); + +bool SetupPulse() +{ + backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_NO_DEBUG); + if(backend == PULSE_NULL_HANDLE) + { + fprintf(stderr, "Fatal Error: could not load Pulse using Vulkan due to %s", PulseVerbaliseErrorType(PulseGetLastErrorType())); + return false; + } + puts("Pulse loaded using Vulkan"); + return true; +} + +int RunUnitTests() +{ + UNITY_BEGIN(); + RUN_TEST(TestDeviceSetup); + return UNITY_END(); +} + +void UnloadPulse() +{ + PulseUnloadBackend(backend); + puts("Pulse unloaded"); +} + +int main(void) +{ + SetupPulse(); + int result = RunUnitTests(); + UnloadPulse(); + return result; +} diff --git a/Tests/Vulkan/xmake.lua b/Tests/Vulkan/xmake.lua new file mode 100644 index 0000000..48fb84b --- /dev/null +++ b/Tests/Vulkan/xmake.lua @@ -0,0 +1,16 @@ +option("vulkan-tests", { description = "Build Vulkan tests", default = false }) + +if has_config("vulkan-tests") then + set_group("VulkanTests") + add_requires("unity_test") + + target("VulkanUnitTests") + set_kind("binary") + add_deps("pulse_gpu") + add_files("**.c") + add_packages("unity_test") + if is_plat("linux") then + set_extension(".x86_64") + end + target_end() +end diff --git a/Tests/xmake.lua b/Tests/xmake.lua deleted file mode 100644 index c4cde8f..0000000 --- a/Tests/xmake.lua +++ /dev/null @@ -1,6 +0,0 @@ -option("tests", { description = "Build tests", default = false }) - -if has_config("tests") then - set_group("Tests") - includes("*/xmake.lua") -end diff --git a/xmake.lua b/xmake.lua index 77c26ad..1203901 100644 --- a/xmake.lua +++ b/xmake.lua @@ -97,4 +97,6 @@ target("pulse_gpu") end) target_end() -includes("Tests/*.lua") +includes("Examples/*.lua") + +includes("Tests/Vulkan/*.lua")