From d8b14d1a7efdcd5ce6a17c64b60514346813d18b Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Sun, 17 Nov 2024 10:52:29 +0100 Subject: [PATCH] adding command list Vulkan support --- Examples/Vulkan/main.c | 36 +++- Includes/Pulse.h | 45 +++-- Sources/Backends/Vulkan/Vulkan.c | 33 ++++ Sources/Backends/Vulkan/Vulkan.h | 8 +- Sources/Backends/Vulkan/VulkanCommandList.c | 171 ++++++++++++++++++ Sources/Backends/Vulkan/VulkanCommandList.h | 11 +- Sources/Backends/Vulkan/VulkanCommandPool.c | 2 +- .../Backends/Vulkan/VulkanComputePipeline.c | 17 ++ .../Backends/Vulkan/VulkanComputePipeline.h | 2 +- Sources/Backends/Vulkan/VulkanDevice.c | 37 +++- Sources/Backends/Vulkan/VulkanDevice.h | 2 + Sources/Backends/Vulkan/VulkanFence.c | 18 +- Sources/Backends/Vulkan/VulkanInstance.c | 2 +- Sources/PulseBackend.c | 83 ++++++--- Sources/PulseCommandList.c | 71 ++++++++ Sources/PulseComputePipeline.c | 3 +- Sources/PulseDebug.c | 27 --- Sources/PulseDebug.h | 13 -- Sources/PulseDefs.h | 10 + Sources/PulseEnums.h | 18 -- Sources/PulseFence.c | 32 ++++ Sources/PulseInternal.h | 36 ++-- Sources/PulsePFNs.h | 5 +- Tests/Vulkan/Backend.c | 42 +++++ Tests/Vulkan/Common.c | 28 +++ Tests/Vulkan/Common.h | 26 +++ Tests/Vulkan/Device.c | 75 ++++++++ Tests/Vulkan/DeviceSetup.c | 11 -- Tests/Vulkan/main.c | 38 +--- 29 files changed, 716 insertions(+), 186 deletions(-) create mode 100644 Sources/Backends/Vulkan/VulkanCommandList.c create mode 100644 Sources/Backends/Vulkan/VulkanComputePipeline.c create mode 100644 Sources/PulseCommandList.c delete mode 100644 Sources/PulseDebug.h create mode 100644 Sources/PulseFence.c create mode 100644 Tests/Vulkan/Backend.c create mode 100644 Tests/Vulkan/Common.c create mode 100644 Tests/Vulkan/Common.h create mode 100644 Tests/Vulkan/Device.c delete mode 100644 Tests/Vulkan/DeviceSetup.c diff --git a/Examples/Vulkan/main.c b/Examples/Vulkan/main.c index 1cdba30..691c97b 100644 --- a/Examples/Vulkan/main.c +++ b/Examples/Vulkan/main.c @@ -3,29 +3,47 @@ #include #include +#define CHECK_PULSE_HANDLE_RETVAL(handle, retval) \ + if(handle == PULSE_NULL_HANDLE) \ + { \ + fprintf(stderr, "Error: %s", PulseVerbaliseErrorType(PulseGetLastErrorType())); \ + return retval; \ + } \ + void DebugCallBack(PulseDebugMessageSeverity severity, const char* message) { if(severity == PULSE_DEBUG_MESSAGE_SEVERITY_ERROR) { - fprintf(stderr, "Pulse Error: %s", message); + fprintf(stderr, "Pulse Error: %s\n", message); exit(1); } else if(severity == PULSE_DEBUG_MESSAGE_SEVERITY_WARNING) - fprintf(stderr, "Pulse Warning: %s", message); + fprintf(stderr, "Pulse Warning: %s\n", message); else - printf("Pulse: %s", message); + printf("Pulse: %s\n", message); } int main(void) { - PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_NO_DEBUG); - if(backend == PULSE_NULL_HANDLE) - { - fprintf(stderr, "Pulse: could not create backend"); - return 1; - } + PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_HIGH_DEBUG); + CHECK_PULSE_HANDLE_RETVAL(backend, 1); PulseSetDebugCallback(backend, DebugCallBack); PulseDevice device = PulseCreateDevice(backend, NULL, 0); + CHECK_PULSE_HANDLE_RETVAL(device, 1); + + PulseFence fence = PulseCreateFence(device); + CHECK_PULSE_HANDLE_RETVAL(fence, 1); + PulseCommandList cmd = PulseRequestCommandList(device, PULSE_COMMAND_LIST_GENERAL); + CHECK_PULSE_HANDLE_RETVAL(cmd, 1); + + + if(!PulseSubmitCommandList(device, cmd, fence)) + fprintf(stderr, "Could not submit command list, %s\n", PulseVerbaliseErrorType(PulseGetLastErrorType())); + if(!PulseWaitForFences(device, &fence, 1, true)) + fprintf(stderr, "Could not wait for fences, %s\n", PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseReleaseCommandList(device, cmd); + PulseDestroyFence(device, fence); PulseDestroyDevice(device); PulseUnloadBackend(backend); diff --git a/Includes/Pulse.h b/Includes/Pulse.h index 830d653..909f159 100644 --- a/Includes/Pulse.h +++ b/Includes/Pulse.h @@ -25,11 +25,9 @@ typedef uint32_t PulseFlags; PULSE_DEFINE_NULLABLE_HANDLE(PulseBackend); PULSE_DEFINE_NULLABLE_HANDLE(PulseBuffer); PULSE_DEFINE_NULLABLE_HANDLE(PulseCommandList); -PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePass); PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePipeline); PULSE_DEFINE_NULLABLE_HANDLE(PulseDevice); PULSE_DEFINE_NULLABLE_HANDLE(PulseFence); -PULSE_DEFINE_NULLABLE_HANDLE(PulseGeneralPass); PULSE_DEFINE_NULLABLE_HANDLE(PulseImage); // Flags @@ -75,12 +73,18 @@ typedef enum PulseShaderFormatsBits typedef PulseFlags PulseShaderFormatsFlags; // Enums +typedef enum PulseCommandListUsage +{ + PULSE_COMMAND_LIST_GENERAL, + PULSE_COMMAND_LIST_TRANSFER_ONLY +} PulseCommandListUsage; + typedef enum PulseDebugLevel { - PULSE_NO_DEBUG, - PULSE_LOW_DEBUG, - PULSE_HIGH_DEBUG, - PULSE_PARANOID_DEBUG // Causes every warning to be treated as error + PULSE_NO_DEBUG = 0, + PULSE_LOW_DEBUG = 1, + PULSE_HIGH_DEBUG = 2, + PULSE_PARANOID_DEBUG = 3 // Causes every warning to be treated as error } PulseDebugLevel; typedef enum PulseDebugMessageSeverity @@ -90,6 +94,18 @@ typedef enum PulseDebugMessageSeverity PULSE_DEBUG_MESSAGE_SEVERITY_ERROR } PulseDebugMessageSeverity; +typedef enum PulseErrorType +{ + PULSE_ERROR_NONE, + PULSE_ERROR_BACKENDS_CANDIDATES_SHADER_FORMAT_MISMATCH, + PULSE_ERROR_INITIALIZATION_FAILED, + PULSE_ERROR_INVALID_HANDLE, + PULSE_ERROR_CPU_ALLOCATION_FAILED, + PULSE_ERROR_DEVICE_ALLOCATION_FAILED, + PULSE_ERROR_DEVICE_LOST, + PULSE_ERROR_INVALID_INTERNAL_POINTER, +} PulseErrorType; + typedef enum PulseImageType { PULSE_IMAGE_TYPE_2D, @@ -249,26 +265,22 @@ typedef struct PulseImageRegion // Functions typedef void (*PulseDebugCallbackPFN)(PulseDebugMessageSeverity, const char*); +PULSE_API bool PulseSupportsBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used); + PULSE_API PulseBackend PulseLoadBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used, PulseDebugLevel debug_level); PULSE_API void PulseUnloadBackend(PulseBackend backend); +PULSE_API PulseBackendFlags PulseGetBackendType(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 PulseCommandList PulseRequestCommandList(PulseDevice device, PulseCommandListUsage usage); 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); @@ -276,7 +288,10 @@ PULSE_API bool PulseWaitForFences(PulseDevice device, const PulseFence* fences, 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 void PulseBindComputePipeline(PulseComputePipeline pipeline); + +PULSE_API PulseErrorType PulseGetLastErrorType(); // /!\ Warning /!\ Call to this function resets the internal last error variable +PULSE_API const char* PulseVerbaliseErrorType(PulseErrorType error); #ifdef __cplusplus } diff --git a/Sources/Backends/Vulkan/Vulkan.c b/Sources/Backends/Vulkan/Vulkan.c index 0168acf..5a68cfd 100644 --- a/Sources/Backends/Vulkan/Vulkan.c +++ b/Sources/Backends/Vulkan/Vulkan.c @@ -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, diff --git a/Sources/Backends/Vulkan/Vulkan.h b/Sources/Backends/Vulkan/Vulkan.h index d951644..21623d5 100644 --- a/Sources/Backends/Vulkan/Vulkan.h +++ b/Sources/Backends/Vulkan/Vulkan.h @@ -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_ diff --git a/Sources/Backends/Vulkan/VulkanCommandList.c b/Sources/Backends/Vulkan/VulkanCommandList.c new file mode 100644 index 0000000..b8e0102 --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanCommandList.c @@ -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; + } + } +} diff --git a/Sources/Backends/Vulkan/VulkanCommandList.h b/Sources/Backends/Vulkan/VulkanCommandList.h index 6433f5d..70651ea 100644 --- a/Sources/Backends/Vulkan/VulkanCommandList.h +++ b/Sources/Backends/Vulkan/VulkanCommandList.h @@ -10,22 +10,17 @@ #include #include -#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_ diff --git a/Sources/Backends/Vulkan/VulkanCommandPool.c b/Sources/Backends/Vulkan/VulkanCommandPool.c index a70b8ba..814f2a9 100644 --- a/Sources/Backends/Vulkan/VulkanCommandPool.c +++ b/Sources/Backends/Vulkan/VulkanCommandPool.c @@ -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(); diff --git a/Sources/Backends/Vulkan/VulkanComputePipeline.c b/Sources/Backends/Vulkan/VulkanComputePipeline.c new file mode 100644 index 0000000..6748cb9 --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanComputePipeline.c @@ -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) +{ +} diff --git a/Sources/Backends/Vulkan/VulkanComputePipeline.h b/Sources/Backends/Vulkan/VulkanComputePipeline.h index 2d6b571..4eaafc8 100644 --- a/Sources/Backends/Vulkan/VulkanComputePipeline.h +++ b/Sources/Backends/Vulkan/VulkanComputePipeline.h @@ -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_ diff --git a/Sources/Backends/Vulkan/VulkanDevice.c b/Sources/Backends/Vulkan/VulkanDevice.c index 4112cca..7cf8dea 100644 --- a/Sources/Backends/Vulkan/VulkanDevice.c +++ b/Sources/Backends/Vulkan/VulkanDevice.c @@ -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]; +} diff --git a/Sources/Backends/Vulkan/VulkanDevice.h b/Sources/Backends/Vulkan/VulkanDevice.h index 56b1c57..18c2898 100644 --- a/Sources/Backends/Vulkan/VulkanDevice.h +++ b/Sources/Backends/Vulkan/VulkanDevice.h @@ -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_ diff --git a/Sources/Backends/Vulkan/VulkanFence.c b/Sources/Backends/Vulkan/VulkanFence.c index 42dcb08..3dd6d67 100644 --- a/Sources/Backends/Vulkan/VulkanFence.c +++ b/Sources/Backends/Vulkan/VulkanFence.c @@ -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) diff --git a/Sources/Backends/Vulkan/VulkanInstance.c b/Sources/Backends/Vulkan/VulkanInstance.c index 31f003d..72f735d 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 - 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; } diff --git a/Sources/PulseBackend.c b/Sources/PulseBackend.c index 4c25ec1..3a5cb76 100644 --- a/Sources/PulseBackend.c +++ b/Sources/PulseBackend.c @@ -2,7 +2,8 @@ // This file is part of "Pulse" // For conditions of distribution and use, see copyright notice in LICENSE -#include +#include +#include #include #include "PulseInternal.h" @@ -25,36 +26,41 @@ static const PulseCheckBackendSupportPFN backends_supports[] = { PULSE_NULLPTR }; -struct -{ - char file[1024]; - char function[1024]; - PulseErrorType type; - int line; -} last_error = { .file = { 0 }, .function = { 0 }, .type = PULSE_ERROR_NONE, .line = -1 }; +static PulseErrorType last_error = PULSE_ERROR_NONE; -void PulseSetInternalErrorBackend(PulseErrorType error, const char* file, const char* function, int line) +void PulseSetInternalError(PulseErrorType error) { - strcpy(last_error.file, file); - strcpy(last_error.function, function); - last_error.type = error; - last_error.line = line; + last_error = error; } -void PulseLogErrorBackend(PulseBackend backend, PulseErrorType error, const char* file, const char* function, int line) +#define LOG_MESSAGE_MAX_LENGTH 4096 + +void PulseLogBackend(PulseBackend backend, PulseDebugMessageSeverity type, const char* message, const char* file, const char* function, int line, ...) { + (void)file; // May be used later if(backend == PULSE_NULL_HANDLE) return; if(!backend->PFN_UserDebugCallback) return; -} -void PulseLogWarningBackend(PulseBackend backend, PulseWarningType warning, const char* file, const char* function, int line) -{ -} + va_list argptr; + va_start(argptr, line); -void PulseLogInfoBackend(PulseBackend backend, const char* message, const char* file, const char* function, int line) -{ + char complete_message[LOG_MESSAGE_MAX_LENGTH] = { 0 }; + + int shift = 0; + + if(type != PULSE_DEBUG_MESSAGE_SEVERITY_INFO) + { + shift = snprintf(complete_message, LOG_MESSAGE_MAX_LENGTH, "[%s:%d] ", function, line); + if(backend->debug_level == PULSE_PARANOID_DEBUG && type == PULSE_DEBUG_MESSAGE_SEVERITY_WARNING) + type = PULSE_DEBUG_MESSAGE_SEVERITY_ERROR; + if(shift == -1) + shift = 0; + } + vsnprintf(complete_message + shift, LOG_MESSAGE_MAX_LENGTH - shift, message, argptr); + backend->PFN_UserDebugCallback(type, complete_message); + va_end(argptr); } static PulseBackendFlags PulseSelectBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used) @@ -74,10 +80,6 @@ static PulseBackendFlags PulseSelectBackend(PulseBackendFlags backend_candidates return PULSE_BACKEND_INVALID; } -static const char* PulseVerbaliseErrorType(PulseErrorType error) -{ -} - static PulseBackend PulseGetBackendFromFlag(PulseBackendBits flag) { switch(flag) @@ -86,7 +88,7 @@ static PulseBackend PulseGetBackendFromFlag(PulseBackendBits flag) case PULSE_BACKEND_VULKAN: return &VulkanDriver; #endif #ifdef PULSE_ENABLE_D3D11_BACKEND - case PULSE_BACKEND_VULKAN: return &D3D11Driver; + case PULSE_BACKEND_D3D11: return &D3D11Driver; #endif default: break; @@ -106,6 +108,7 @@ PULSE_API PulseBackend PulseLoadBackend(PulseBackendFlags backend_candidates, Pu if(!backend->PFN_LoadBackend(debug_level)) return PULSE_NULL_HANDLE; backend->PFN_UserDebugCallback = PULSE_NULLPTR; + backend->debug_level = debug_level; return (PulseBackend)backend; } @@ -115,6 +118,12 @@ PULSE_API void PulseUnloadBackend(PulseBackend backend) backend->PFN_UnloadBackend(backend); } +PULSE_API PulseBackendFlags PulseGetBackendType(PulseBackend backend) +{ + PULSE_CHECK_HANDLE_RETVAL(backend, PULSE_BACKEND_INVALID); + return backend->backend; +} + PULSE_API bool PulseSupportsBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used) { if((backend_candidates & PULSE_BACKEND_INVALID) != 0) @@ -133,3 +142,27 @@ PULSE_API void PulseSetDebugCallback(PulseBackend backend, PulseDebugCallbackPFN PULSE_CHECK_HANDLE(backend); backend->PFN_UserDebugCallback = callback; } + +PULSE_API PulseErrorType PulseGetLastErrorType() +{ + PulseErrorType error = last_error; + last_error = PULSE_ERROR_NONE; + return error; +} + +PULSE_API const char* PulseVerbaliseErrorType(PulseErrorType error) +{ + switch(error) + { + case PULSE_ERROR_NONE: return "no error"; + case PULSE_ERROR_BACKENDS_CANDIDATES_SHADER_FORMAT_MISMATCH: return "no backend candidates support the required shader formats"; + case PULSE_ERROR_INITIALIZATION_FAILED: return "initialization of an object could not be completed for implementation-specific reasons"; + case PULSE_ERROR_CPU_ALLOCATION_FAILED: return "an internal CPU allocation failed"; + case PULSE_ERROR_DEVICE_ALLOCATION_FAILED: return "a device allocation failed"; + case PULSE_ERROR_DEVICE_LOST: return "device has been lost"; + case PULSE_ERROR_INVALID_INTERNAL_POINTER: return "invalid internal pointer"; + + default: return "invalid error type"; + }; + return PULSE_NULLPTR; // To avoid warnings, should be unreachable +} diff --git a/Sources/PulseCommandList.c b/Sources/PulseCommandList.c new file mode 100644 index 0000000..3005864 --- /dev/null +++ b/Sources/PulseCommandList.c @@ -0,0 +1,71 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#include "PulseDefs.h" +#include "PulseInternal.h" + +PULSE_API PulseCommandList PulseRequestCommandList(PulseDevice device, PulseCommandListUsage usage) +{ + PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULL_HANDLE); + return device->PFN_RequestCommandList(device, usage); +} + +PULSE_API bool PulseSubmitCommandList(PulseDevice device, PulseCommandList cmd, PulseFence fence) +{ + PULSE_CHECK_HANDLE_RETVAL(device, false); + PULSE_CHECK_HANDLE_RETVAL(cmd, false); + + if(cmd->state != PULSE_COMMAND_LIST_STATE_READY) + { + switch(cmd->state) + { + case PULSE_COMMAND_LIST_STATE_INVALID: + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogError(device->backend, "command list is in invalid state"); + return false; + + case PULSE_COMMAND_LIST_STATE_EMPTY: + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogWarning(device->backend, "command list is empty"); + return false; + + case PULSE_COMMAND_LIST_STATE_RECORDING: + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogError(device->backend, "command list is in recording state"); + return false; + + case PULSE_COMMAND_LIST_STATE_SENT: + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogWarning(device->backend, "command list has already been submitted"); + return false; + + default: break; + } + } + + return device->PFN_SubmitCommandList(device, cmd, fence); +} + +PULSE_API void PulseReleaseCommandList(PulseDevice device, PulseCommandList cmd) +{ + PULSE_CHECK_HANDLE(device); + + if(cmd == PULSE_NULL_HANDLE) + { + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogWarning(device->backend, "command list is NULL, this may be a bug in your application"); + return; + } + + switch(cmd->state) + { + case PULSE_COMMAND_LIST_STATE_RECORDING: + if(PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(device->backend)) + PulseLogWarning(device->backend, "command list is in recording state"); + break; + + default: break; + } + return device->PFN_ReleaseCommandList(device, cmd); +} diff --git a/Sources/PulseComputePipeline.c b/Sources/PulseComputePipeline.c index 89dfc01..b188bd5 100644 --- a/Sources/PulseComputePipeline.c +++ b/Sources/PulseComputePipeline.c @@ -10,9 +10,8 @@ PULSE_API PulseComputePipeline PulseCreateComputePipeline(PulseDevice device, co PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULL_HANDLE); } -PULSE_API void PulseBindComputePipeline(PulseComputePass pass, PulseComputePipeline pipeline) +PULSE_API void PulseBindComputePipeline(PulseComputePipeline pipeline) { - PULSE_CHECK_HANDLE(pass); PULSE_CHECK_HANDLE(pipeline); } diff --git a/Sources/PulseDebug.c b/Sources/PulseDebug.c index 34a3b5d..0da9104 100644 --- a/Sources/PulseDebug.c +++ b/Sources/PulseDebug.c @@ -3,30 +3,3 @@ // For conditions of distribution and use, see copyright notice in LICENSE #include -#include "PulseDebug.h" - -const char* PulseVerbaliseErrorType(PulseErrorType error) -{ - switch(error) - { - case PULSE_ERROR_BACKENDS_CANDIDATES_SHADER_FORMAT_MISMATCH: return "no backend candidates support the required shader formats"; - case PULSE_ERROR_INITIALIZATION_FAILED: return "initialization of an object could not be completed for implementation-specific reasons"; - case PULSE_ERROR_CPU_ALLOCATION_FAILED: return "a CPU allocation failed"; - case PULSE_ERROR_DEVICE_ALLOCATION_FAILED: return "a device allocation failed"; - case PULSE_ERROR_DEVICE_LOST: return "device has been lost"; - case PULSE_ERROR_INVALID_INTERNAL_POINTER: return "invalid internal pointer"; - case PULSE_ERROR_INVALID_HANDLE: return "invalid handle"; - - default: return "invalid error type"; - }; - return PULSE_NULLPTR; // To avoid warnings, should be unreachable -} - -const char* PulseVerbaliseWarningType(PulseWarningType warning) -{ - switch(warning) - { - default: return "invalid warning type"; - }; - return PULSE_NULLPTR; // To avoid warnings, should be unreachable -} diff --git a/Sources/PulseDebug.h b/Sources/PulseDebug.h deleted file mode 100644 index bfe9884..0000000 --- a/Sources/PulseDebug.h +++ /dev/null @@ -1,13 +0,0 @@ -// Copyright (C) 2024 kanel -// This file is part of "Pulse" -// For conditions of distribution and use, see copyright notice in LICENSE - -#ifndef PULSE_DEBUG_H_ -#define PULSE_DEBUG_H_ - -#include "PulseEnums.h" - -const char* PulseVerbaliseErrorType(PulseErrorType error); -const char* PulseVerbaliseWarningType(PulseWarningType warning); - -#endif diff --git a/Sources/PulseDefs.h b/Sources/PulseDefs.h index 50ac73f..69311d4 100644 --- a/Sources/PulseDefs.h +++ b/Sources/PulseDefs.h @@ -40,6 +40,13 @@ } \ } while(0); \ +#define PULSE_UNUSED(x) ((void)x) + +#define PULSE_CHECK_BACKEND_DEBUG_LEVEL(backend, level) (backend != PULSE_NULL_HANDLE && ((PulseBackend)backend)->debug_level >= level) + +#define PULSE_IS_BACKEND_LOW_LEVEL_DEBUG(backend) PULSE_CHECK_BACKEND_DEBUG_LEVEL(backend, PULSE_LOW_DEBUG) +#define PULSE_IS_BACKEND_HIGH_LEVEL_DEBUG(backend) PULSE_CHECK_BACKEND_DEBUG_LEVEL(backend, PULSE_HIGH_DEBUG) + #define PULSE_CHECK_PTR(handle) PULSE_CHECK_PTR_RETVAL(handle, ) #define PULSE_LOAD_DRIVER_DEVICE_FUNCTION(fn, _namespace) pulse_device->PFN_##fn = _namespace##fn; @@ -52,5 +59,8 @@ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyFence, _namespace) \ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(IsFenceReady, _namespace) \ PULSE_LOAD_DRIVER_DEVICE_FUNCTION(WaitForFences, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(RequestCommandList, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(SubmitCommandList, _namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(ReleaseCommandList, _namespace) \ #endif // PULSE_DEFS_H_ diff --git a/Sources/PulseEnums.h b/Sources/PulseEnums.h index dcf7b46..dad196e 100644 --- a/Sources/PulseEnums.h +++ b/Sources/PulseEnums.h @@ -14,22 +14,4 @@ typedef enum PulseCommandListState PULSE_COMMAND_LIST_STATE_SENT } PulseCommandListState; -typedef enum PulseErrorType -{ - PULSE_ERROR_NONE, - - PULSE_ERROR_DEVICE_LOST, - PULSE_ERROR_INVALID_HANDLE, - PULSE_ERROR_INVALID_INTERNAL_POINTER, - PULSE_ERROR_INITIALIZATION_FAILED, - PULSE_ERROR_CPU_ALLOCATION_FAILED, - PULSE_ERROR_DEVICE_ALLOCATION_FAILED, - PULSE_ERROR_BACKENDS_CANDIDATES_SHADER_FORMAT_MISMATCH, -} PulseErrorType; - -typedef enum PulseWarningType -{ - PULSE_WARNING_NONE, -} PulseWarningType; - #endif diff --git a/Sources/PulseFence.c b/Sources/PulseFence.c new file mode 100644 index 0000000..9cc55d1 --- /dev/null +++ b/Sources/PulseFence.c @@ -0,0 +1,32 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#include "PulseDefs.h" +#include "PulseInternal.h" + +PULSE_API PulseFence PulseCreateFence(PulseDevice device) +{ + PULSE_CHECK_HANDLE_RETVAL(device, PULSE_NULL_HANDLE); + return device->PFN_CreateFence(device); +} + +PULSE_API void PulseDestroyFence(PulseDevice device, PulseFence fence) +{ + PULSE_CHECK_HANDLE(device); + return device->PFN_DestroyFence(device, fence); +} + +PULSE_API bool PulseIsFenceReady(PulseDevice device, PulseFence fence) +{ + PULSE_CHECK_HANDLE_RETVAL(device, false); + PULSE_CHECK_HANDLE_RETVAL(fence, false); + return device->PFN_IsFenceReady(device, fence); +} + +PULSE_API bool PulseWaitForFences(PulseDevice device, const PulseFence* fences, uint32_t fences_count, bool wait_for_all) +{ + PULSE_CHECK_HANDLE_RETVAL(device, false); + PULSE_CHECK_PTR_RETVAL(fences, false); + return device->PFN_WaitForFences(device, fences, fences_count, wait_for_all); +} diff --git a/Sources/PulseInternal.h b/Sources/PulseInternal.h index ac20d50..bc91513 100644 --- a/Sources/PulseInternal.h +++ b/Sources/PulseInternal.h @@ -25,6 +25,7 @@ typedef struct PulseBackendHandler PulseShaderFormatsFlags supported_shader_formats; void* driver_data; PulseDebugCallbackPFN PFN_UserDebugCallback; + PulseDebugLevel debug_level; } PulseBackendHandler; typedef struct PulseBufferHandler @@ -36,15 +37,16 @@ typedef struct PulseCommandListHandler { PulseDevice device; void* driver_data; + PulseThreadID thread_id; + PulseComputePipeline* compute_pipelines_bound; + uint32_t compute_pipelines_bound_capacity; + uint32_t compute_pipelines_bound_size; PulseCommandListState state; + PulseCommandListUsage usage; bool is_compute_pipeline_bound; + bool is_available; } PulseCommandListHandler; -typedef struct PulseComputePassHandler -{ - void* driver_data; -} PulseComputePassHandler; - typedef struct PulseComputePipelineHandler { void* driver_data; @@ -61,6 +63,9 @@ typedef struct PulseDeviceHandler PulseDestroyFencePFN PFN_DestroyFence; PulseIsFenceReadyPFN PFN_IsFenceReady; PulseWaitForFencesPFN PFN_WaitForFences; + PulseRequestCommandListPFN PFN_RequestCommandList; + PulseSubmitCommandListPFN PFN_SubmitCommandList; + PulseReleaseCommandListPFN PFN_ReleaseCommandList; // Attributes void* driver_data; @@ -72,11 +77,6 @@ typedef struct PulseFenceHandler void* driver_data; } PulseFenceHandler; -typedef struct PulseGeneralPassHandler -{ - void* driver_data; -} PulseGeneralPassHandler; - typedef struct PulseImageHandler { void* driver_data; @@ -84,13 +84,17 @@ typedef struct PulseImageHandler PulseThreadID PulseGetThreadID(); -void PulseLogErrorBackend(PulseBackend backend, PulseErrorType error, const char* file, const char* function, int line); -void PulseLogWarningBackend(PulseBackend backend, PulseWarningType warning, const char* file, const char* function, int line); -void PulseLogInfoBackend(PulseBackend backend, const char* message, const char* file, const char* function, int line); +void PulseSetInternalError(PulseErrorType error); -#define PulseLogError(backend, type) PulseSetInternalErrorBackend(backend, type, __FILE__, __FUNCTION__, __LINE__) -#define PulseLogWarning(backend, type) PulseSetInternalErrorBackend(backend, type, __FILE__, __FUNCTION__, __LINE__) -#define PulseLogInfo(backend, msg) PulseSetInternalErrorBackend(backend, msg, __FILE__, __FUNCTION__, __LINE__) +void PulseLogBackend(PulseBackend backend, PulseDebugMessageSeverity type, const char* message, const char* file, const char* function, int line, ...); + +#define PulseLogError(backend, msg) PulseLogBackend(backend, PULSE_DEBUG_MESSAGE_SEVERITY_ERROR, msg, __FILE__, __FUNCTION__, __LINE__) +#define PulseLogWarning(backend, msg) PulseLogBackend(backend, PULSE_DEBUG_MESSAGE_SEVERITY_WARNING, msg, __FILE__, __FUNCTION__, __LINE__) +#define PulseLogInfo(backend, msg) PulseLogBackend(backend, PULSE_DEBUG_MESSAGE_SEVERITY_INFO, msg, __FILE__, __FUNCTION__, __LINE__) + +#define PulseLogErrorFmt(backend, msg, ...) PulseLogBackend(backend, PULSE_DEBUG_MESSAGE_SEVERITY_ERROR, msg, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) +#define PulseLogWarningFmt(backend, msg, ...) PulseLogBackend(backend, PULSE_DEBUG_MESSAGE_SEVERITY_WARNING, msg, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) +#define PulseLogInfoFmt(backend, msg, ...) PulseLogBackend(backend, PULSE_DEBUG_MESSAGE_SEVERITY_INFO, msg, __FILE__, __FUNCTION__, __LINE__, __VA_ARGS__) #ifdef PULSE_ENABLE_VULKAN_BACKEND extern PulseBackendHandler VulkanDriver; diff --git a/Sources/PulsePFNs.h b/Sources/PulsePFNs.h index b6ed4a0..f67d8f7 100644 --- a/Sources/PulsePFNs.h +++ b/Sources/PulsePFNs.h @@ -15,11 +15,14 @@ typedef PulseDevice (*PulseCreateDevicePFN)(PulseBackend, PulseDevice*, uint32_t typedef void (*PulseDestroyDevicePFN)(PulseDevice); typedef PulseComputePipeline (*PulseCreateComputePipelinePFN)(PulseDevice, const PulseComputePipelineCreateInfo*); -typedef void (*PulseBindComputePipelinePFN)(PulseComputePass, PulseComputePipeline); +typedef void (*PulseBindComputePipelinePFN)(PulseComputePipeline); typedef void (*PulseDestroyComputePipelinePFN)(PulseDevice, PulseComputePipeline); typedef PulseFence (*PulseCreateFencePFN)(PulseDevice); typedef void (*PulseDestroyFencePFN)(PulseDevice, PulseFence); typedef bool (*PulseIsFenceReadyPFN)(PulseDevice, PulseFence); typedef bool (*PulseWaitForFencesPFN)(PulseDevice, const PulseFence*, uint32_t, bool); +typedef PulseCommandList (*PulseRequestCommandListPFN)(PulseDevice, PulseCommandListUsage); +typedef bool (*PulseSubmitCommandListPFN)(PulseDevice, PulseCommandList, PulseFence); +typedef void (*PulseReleaseCommandListPFN)(PulseDevice, PulseCommandList); #endif // PULSE_PFNS_H_ diff --git a/Tests/Vulkan/Backend.c b/Tests/Vulkan/Backend.c new file mode 100644 index 0000000..41ec5f0 --- /dev/null +++ b/Tests/Vulkan/Backend.c @@ -0,0 +1,42 @@ +#include "Common.h" + +#include +#include + +void DumbDebugCallBack(PulseDebugMessageSeverity severity, const char* message) +{ + (void)severity; + (void)message; +} + +void TestBackendSetup() +{ + PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_HIGH_DEBUG); + TEST_ASSERT_NOT_EQUAL_MESSAGE(backend, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseSetDebugCallback(backend, DumbDebugCallBack); + PulseUnloadBackend(backend); +} + +void TestBackendAnySetup() +{ + PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_ANY, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_HIGH_DEBUG); + TEST_ASSERT_NOT_EQUAL_MESSAGE(backend, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_EQUAL(PulseGetBackendType(backend), PULSE_BACKEND_VULKAN); + PulseSetDebugCallback(backend, DumbDebugCallBack); + PulseUnloadBackend(backend); +} + +void TestWrongBackendSetup() +{ + PulseBackend backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_DXBC_BIT, PULSE_HIGH_DEBUG); + TEST_ASSERT_EQUAL(backend, PULSE_NULL_HANDLE); + PulseSetDebugCallback(backend, DumbDebugCallBack); + PulseUnloadBackend(backend); +} + +void TestBackend() +{ + RUN_TEST(TestBackendSetup); + RUN_TEST(TestBackendAnySetup); + RUN_TEST(TestWrongBackendSetup); +} diff --git a/Tests/Vulkan/Common.c b/Tests/Vulkan/Common.c new file mode 100644 index 0000000..fe08d5b --- /dev/null +++ b/Tests/Vulkan/Common.c @@ -0,0 +1,28 @@ +#include "Common.h" +#include + +void DebugCallBack(PulseDebugMessageSeverity severity, const char* message) +{ + if(severity == PULSE_DEBUG_MESSAGE_SEVERITY_ERROR) + TEST_FAIL_MESSAGE(message); +} + +#define LOG_MESSAGE_MAX_LENGTH 4096 + +void SetupPulse(PulseBackend* backend) +{ + *backend = PulseLoadBackend(PULSE_BACKEND_VULKAN, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_PARANOID_DEBUG); + if(*backend == PULSE_NULL_HANDLE) + { + char complete_message[LOG_MESSAGE_MAX_LENGTH] = { 0 }; + snprintf(complete_message, LOG_MESSAGE_MAX_LENGTH, "Fatal Error: could not load Pulse using Vulkan due to %s", PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_FAIL_MESSAGE(complete_message); + TEST_ABORT(); + } + PulseSetDebugCallback(*backend, DebugCallBack); +} + +void CleanupPulse(PulseBackend backend) +{ + PulseUnloadBackend(backend); +} diff --git a/Tests/Vulkan/Common.h b/Tests/Vulkan/Common.h new file mode 100644 index 0000000..5f0c70f --- /dev/null +++ b/Tests/Vulkan/Common.h @@ -0,0 +1,26 @@ +#ifndef TEST_COMMON_H_ +#define TEST_COMMON_H_ + +#define RUN_TEST_AT_LINE(fn, line) \ +{ \ + Unity.TestFile = __FILE__; \ + Unity.CurrentTestName = #fn; \ + Unity.CurrentTestLineNumber = line; \ + Unity.NumberOfTests++; \ + UNITY_CLR_DETAILS(); \ + UNITY_EXEC_TIME_START(); \ + if(TEST_PROTECT()) \ + fn(); \ + UNITY_EXEC_TIME_STOP(); \ + UnityConcludeTest(); \ +} + +#define RUN_TEST(fn) RUN_TEST_AT_LINE(fn, __LINE__); + +#include + +void DebugCallBack(PulseDebugMessageSeverity severity, const char* message); +void SetupPulse(PulseBackend* backend); +void CleanupPulse(PulseBackend backend); + +#endif diff --git a/Tests/Vulkan/Device.c b/Tests/Vulkan/Device.c new file mode 100644 index 0000000..d2d3fa0 --- /dev/null +++ b/Tests/Vulkan/Device.c @@ -0,0 +1,75 @@ +#include "Common.h" + +#include +#include + +void TestDeviceSetup() +{ + PulseBackend backend; + SetupPulse(&backend); + + PulseDevice device = PulseCreateDevice(backend, NULL, 0); + TEST_ASSERT_NOT_EQUAL_MESSAGE(device, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + PulseDestroyDevice(device); + + CleanupPulse(backend); +} + +void TestForbiddenDeviceSetup() +{ + PulseBackend backend; + SetupPulse(&backend); + + PulseDevice device1 = PulseCreateDevice(backend, NULL, 0); + TEST_ASSERT_NOT_EQUAL_MESSAGE(device1, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + + PulseDevice device2 = PulseCreateDevice(backend, &device1, 1); + TEST_ASSERT_NOT_EQUAL(device2, device1); + + PulseDestroyDevice(device2); + PulseDestroyDevice(device1); + + CleanupPulse(backend); +} + +void TestInvalidBackendDeviceSetup() +{ + PulseDevice device= PulseCreateDevice(PULSE_NULL_HANDLE, NULL, 0); + TEST_ASSERT_EQUAL(device, PULSE_NULL_HANDLE); + PulseDestroyDevice(device); +} + +void TestBackendInUse() +{ + PulseBackend backend; + SetupPulse(&backend); + + PulseDevice device = PulseCreateDevice(backend, NULL, 0); + TEST_ASSERT_NOT_EQUAL_MESSAGE(device, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_EQUAL(PulseGetBackendInUseByDevice(device), PULSE_BACKEND_VULKAN); + PulseDestroyDevice(device); + + CleanupPulse(backend); +} + +void TestShaderFormatSupport() +{ + PulseBackend backend; + SetupPulse(&backend); + + PulseDevice device = PulseCreateDevice(backend, NULL, 0); + TEST_ASSERT_NOT_EQUAL_MESSAGE(device, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType())); + TEST_ASSERT_TRUE(PulseDeviceSupportsShaderFormats(device, PULSE_SHADER_FORMAT_SPIRV_BIT)); + PulseDestroyDevice(device); + + CleanupPulse(backend); +} + +void TestDevice() +{ + RUN_TEST(TestDeviceSetup); + RUN_TEST(TestForbiddenDeviceSetup); + RUN_TEST(TestInvalidBackendDeviceSetup); + RUN_TEST(TestBackendInUse); + RUN_TEST(TestShaderFormatSupport); +} diff --git a/Tests/Vulkan/DeviceSetup.c b/Tests/Vulkan/DeviceSetup.c deleted file mode 100644 index 4861f74..0000000 --- a/Tests/Vulkan/DeviceSetup.c +++ /dev/null @@ -1,11 +0,0 @@ -#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 index 539c7b1..e43f00d 100644 --- a/Tests/Vulkan/main.c +++ b/Tests/Vulkan/main.c @@ -3,39 +3,13 @@ #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"); -} +extern void TestBackend(); +extern void TestDevice(); int main(void) { - SetupPulse(); - int result = RunUnitTests(); - UnloadPulse(); - return result; + UNITY_BEGIN(); + TestBackend(); + TestDevice(); + return UNITY_END(); }