adding Vulkan device creation, adding unit tests, adding few API function declarations

This commit is contained in:
2024-10-10 15:22:56 +02:00
parent f189928c82
commit 405c8b186a
23 changed files with 517 additions and 64 deletions

1
.gitignore vendored
View File

@@ -7,6 +7,7 @@
!/Includes/ !/Includes/
!/Sources/ !/Sources/
!/Tests/ !/Tests/
!/Examples/
!/.github/ !/.github/
build/ build/
Build/ Build/

23
Examples/Vulkan/main.c git.filemode.normal_file
View File

@@ -0,0 +1,23 @@
#include <Pulse.h>
#include <stdio.h>
#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;
}

View File

@@ -1,7 +1,7 @@
target("LoadingPulse") target("VulkanExample")
add_deps("pulse_gpu") add_deps("pulse_gpu")
if is_plat("linux") then if is_plat("linux") then
set_extension(".x86_64") set_extension(".x86_64")
end end
add_files("main.c") add_files("*.c")
target_end() target_end()

7
Examples/xmake.lua git.filemode.normal_file
View File

@@ -0,0 +1,7 @@
option("examples", { description = "Build the examples", default = false })
if has_config("examples") then
set_group("Examples")
includes("*/xmake.lua")
end

View File

@@ -27,9 +27,9 @@ PULSE_DEFINE_NULLABLE_HANDLE(PulseBuffer);
PULSE_DEFINE_NULLABLE_HANDLE(PulseCommandList); PULSE_DEFINE_NULLABLE_HANDLE(PulseCommandList);
PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePass); PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePass);
PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePipeline); PULSE_DEFINE_NULLABLE_HANDLE(PulseComputePipeline);
PULSE_DEFINE_NULLABLE_HANDLE(PulseCopyPass);
PULSE_DEFINE_NULLABLE_HANDLE(PulseDevice); PULSE_DEFINE_NULLABLE_HANDLE(PulseDevice);
PULSE_DEFINE_NULLABLE_HANDLE(PulseFence); PULSE_DEFINE_NULLABLE_HANDLE(PulseFence);
PULSE_DEFINE_NULLABLE_HANDLE(PulseGeneralPass);
PULSE_DEFINE_NULLABLE_HANDLE(PulseImage); PULSE_DEFINE_NULLABLE_HANDLE(PulseImage);
// Flags // Flags
@@ -83,6 +83,19 @@ typedef enum PulseDebugLevel
PULSE_PARANOID_DEBUG // Causes every warning to be treated as error PULSE_PARANOID_DEBUG // Causes every warning to be treated as error
} PulseDebugLevel; } 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 typedef enum PulseErrorType
{ {
PULSE_ERROR_NONE, PULSE_ERROR_NONE,
@@ -250,14 +263,37 @@ typedef struct PulseImageRegion
} PulseImageRegion; } PulseImageRegion;
// Functions // Functions
typedef void (*PulseDebugCallbackPFN)(PulseDebugMessageSeverity, PulseDebugMessageType, const char*);
PULSE_API PulseBackend PulseLoadBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used, PulseDebugLevel debug_level); PULSE_API PulseBackend PulseLoadBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used, PulseDebugLevel debug_level);
PULSE_API void PulseUnloadBackend(PulseBackend backend); 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 PulseDevice PulseCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count);
PULSE_API void PulseDestroyDevice(PulseDevice device); PULSE_API void PulseDestroyDevice(PulseDevice device);
PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device); PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device);
PULSE_API bool PulseSupportsBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used); PULSE_API bool PulseSupportsBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used);
PULSE_API bool PulseDeviceSupportsShaderFormats(PulseDevice device, 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 PulseErrorType PulseGetLastErrorType(); // /!\ Warning /!\ Call to this function resets the internal last error variable
PULSE_API const char* PulseVerbaliseErrorType(PulseErrorType error); PULSE_API const char* PulseVerbaliseErrorType(PulseErrorType error);

View File

@@ -12,7 +12,18 @@
#include "VulkanDevice.h" #include "VulkanDevice.h"
#include "VulkanInstance.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 typedef struct VulkanGlobal
{ {
@@ -24,7 +35,6 @@ typedef struct VulkanGlobal
typedef struct VulkanDriverData typedef struct VulkanDriverData
{ {
VulkanInstance instance; VulkanInstance instance;
VulkanDevice device;
} VulkanDriverData; } VulkanDriverData;
VulkanGlobal* VulkanGetGlobal(); VulkanGlobal* VulkanGetGlobal();

View File

@@ -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 <vulkan/vulkan_core.h>
#include <Pulse.h>
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

View File

@@ -2,26 +2,32 @@
// This file is part of "Pulse" // This file is part of "Pulse"
// For conditions of distribution and use, see copyright notice in LICENSE // 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 "VulkanDevice.h"
#include "VulkanInstance.h"
#include "VulkanLoader.h"
#include "VulkanQueue.h"
#include "../../PulseInternal.h" #include "../../PulseInternal.h"
/*
static int32_t VulkanScorePhysicalDevice(VkPhysicalDevice device, const char** device_extensions, uint32_t device_extensions_count) #include <string.h>
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 // Check extensions support
uint32_t extension_count; uint32_t extension_count;
kbhGetVulkanPFNs()->vkEnumerateDeviceExtensionProperties(device, PULSE_NULLPTR, &extension_count, PULSE_NULLPTR); instance->vkEnumerateDeviceExtensionProperties(device, PULSE_NULLPTR, &extension_count, PULSE_NULLPTR);
VkExtensionProperties* props = (VkExtensionProperties*)kbhCallocInFixed(&allocator, extension_count, sizeof(VkExtensionProperties)); VkExtensionProperties* props = (VkExtensionProperties*)calloc(extension_count, sizeof(VkExtensionProperties));
if(!props) if(!props)
return -1; 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; 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; 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) 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 // Check Queue Families Support
int32_t queue; int32_t queue;
if(kbhFindPhysicalDeviceQueueFamily(device, KBH_VULKAN_QUEUE_COMPUTE, &queue) != KBH_RHI_SUCCESS) if(!VulkanFindPhysicalDeviceQueueFamily(instance, device, VULKAN_QUEUE_COMPUTE, &queue))
return -1; return -1;
VkPhysicalDeviceProperties device_props; VkPhysicalDeviceProperties device_props;
kbhGetVulkanPFNs()->vkGetPhysicalDeviceProperties(device, &device_props); instance->vkGetPhysicalDeviceProperties(device, &device_props);
VkPhysicalDeviceFeatures device_features; VkPhysicalDeviceFeatures device_features;
kbhGetVulkanPFNs()->vkGetPhysicalDeviceFeatures(device, &device_features); instance->vkGetPhysicalDeviceFeatures(device, &device_features);
int32_t score = -1; int32_t score = -1;
if(device_props.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) 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[0];
score += device_props.limits.maxComputeWorkGroupCount[1]; score += device_props.limits.maxComputeWorkGroupCount[1];
@@ -61,7 +67,20 @@ static int32_t VulkanScorePhysicalDevice(VkPhysicalDevice device, const char** d
return score; 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* devices = PULSE_NULLPTR;
VkPhysicalDevice chosen_one = VK_NULL_HANDLE; VkPhysicalDevice chosen_one = VK_NULL_HANDLE;
@@ -73,9 +92,12 @@ static VkPhysicalDevice VulkanPickPhysicalDevice(VulkanInstance* instance)
PULSE_CHECK_ALLOCATION_RETVAL(devices, VK_NULL_HANDLE); PULSE_CHECK_ALLOCATION_RETVAL(devices, VK_NULL_HANDLE);
instance->vkEnumeratePhysicalDevices(instance->instance, &device_count, devices); 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) if(current_device_score > best_device_score)
{ {
best_device_score = current_device_score; best_device_score = current_device_score;
@@ -84,17 +106,132 @@ static VkPhysicalDevice VulkanPickPhysicalDevice(VulkanInstance* instance)
} }
return chosen_one; 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; return;
vmaDestroyAllocator(device->allocator); vmaDestroyAllocator(vulkan_device->allocator);
device->vkDestroyDevice(device->device, PULSE_NULLPTR); vulkan_device->vkDestroyDevice(vulkan_device->device, PULSE_NULLPTR);
device->device = VK_NULL_HANDLE; vulkan_device->device = VK_NULL_HANDLE;
free(vulkan_device);
free(device);
} }

View File

@@ -16,8 +16,14 @@
#include <Pulse.h> #include <Pulse.h>
#include "VulkanEnums.h"
struct VulkanQueue;
typedef struct VulkanDevice typedef struct VulkanDevice
{ {
struct VulkanQueue* queues[VULKAN_QUEUE_END_ENUM];
VkPhysicalDeviceFeatures features; VkPhysicalDeviceFeatures features;
VkPhysicalDeviceMemoryProperties memory_properties; VkPhysicalDeviceMemoryProperties memory_properties;
VkPhysicalDeviceProperties properties; VkPhysicalDeviceProperties properties;
@@ -32,8 +38,8 @@ typedef struct VulkanDevice
#undef PULSE_VULKAN_DEVICE_FUNCTION #undef PULSE_VULKAN_DEVICE_FUNCTION
} VulkanDevice; } VulkanDevice;
void* VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count); PulseDevice VulkanCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count);
void VulkanDestroyDevice(VulkanDevice* device); void VulkanDestroyDevice(PulseDevice device);
#endif // PULSE_VULKAN_DEVICE_H_ #endif // PULSE_VULKAN_DEVICE_H_

20
Sources/Backends/Vulkan/VulkanEnums.h git.filemode.normal_file
View File

@@ -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

View File

@@ -36,7 +36,7 @@ static VkInstance VulkanCreateInstance(const char** extensions_enabled, uint32_t
create_info.flags = 0; create_info.flags = 0;
#endif #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; return instance;
} }
@@ -52,10 +52,7 @@ bool VulkanInitInstance(VulkanInstance* instance, PulseDebugLevel debug_level)
#endif #endif
instance->instance = VulkanCreateInstance(extensions, sizeof(extensions) / sizeof(char*), debug_level); instance->instance = VulkanCreateInstance(extensions, sizeof(extensions) / sizeof(char*), debug_level);
if(instance->instance == VK_NULL_HANDLE) if(instance->instance == VK_NULL_HANDLE)
{
PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED);
return false; return false;
}
if(!VulkanLoadInstance(instance)) if(!VulkanLoadInstance(instance))
return false; return false;
return true; return true;

View File

@@ -2,5 +2,8 @@
// This file is part of "Pulse" // This file is part of "Pulse"
// For conditions of distribution and use, see copyright notice in LICENSE // 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 #define VMA_IMPLEMENTATION
#include <vk_mem_alloc.h> #include <vk_mem_alloc.h>

83
Sources/Backends/Vulkan/VulkanQueue.c git.filemode.normal_file
View File

@@ -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 <stdlib.h>
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;
}

27
Sources/Backends/Vulkan/VulkanQueue.h git.filemode.normal_file
View File

@@ -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

22
Sources/PulseComputePipeline.c git.filemode.normal_file
View File

@@ -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 <Pulse.h>
#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);
}

View File

@@ -7,17 +7,15 @@
PULSE_API PulseDevice PulseCreateDevice(PulseBackend backend, PulseDevice* forbiden_devices, uint32_t forbiden_devices_count) 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); PULSE_CHECK_HANDLE_RETVAL(backend, PULSE_NULL_HANDLE);
if(device == PULSE_NULL_HANDLE) return backend->PFN_CreateDevice(backend, forbiden_devices, forbiden_devices_count);
PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED);
device->backend = backend;
return device;
} }
PULSE_API void PulseDestroyDevice(PulseDevice device) PULSE_API void PulseDestroyDevice(PulseDevice device)
{ {
PULSE_CHECK_HANDLE(device); PULSE_CHECK_HANDLE(device);
device->PFN_DestroyDevice(device); device->PFN_DestroyDevice(device);
device->driver_data = PULSE_NULLPTR;
} }
PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device) PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device)

View File

@@ -11,6 +11,8 @@
extern "C" { extern "C" {
#endif #endif
#define PulseStaticAllocStack(size) ((char[size]){ 0 })
#define PULSE_CHECK_ALLOCATION_RETVAL(ptr, retval) \ #define PULSE_CHECK_ALLOCATION_RETVAL(ptr, retval) \
do { \ do { \
if(ptr == PULSE_NULLPTR) \ if(ptr == PULSE_NULLPTR) \
@@ -37,9 +39,12 @@ typedef PulseBackendFlags (*PulseCheckBackendSupportPFN)(PulseBackendFlags, Puls
typedef bool (*PulseLoadBackendPFN)(PulseDebugLevel); typedef bool (*PulseLoadBackendPFN)(PulseDebugLevel);
typedef void (*PulseUnloadBackendPFN)(PulseBackend); typedef void (*PulseUnloadBackendPFN)(PulseBackend);
typedef void* (*PulseCreateDevicePFN)(PulseBackend, PulseDevice*, uint32_t); typedef PulseDevice (*PulseCreateDevicePFN)(PulseBackend, PulseDevice*, uint32_t);
typedef void (*PulseDestroyDevicePFN)(PulseDevice); typedef void (*PulseDestroyDevicePFN)(PulseDevice);
typedef PulseComputePipeline (*PulseCreateComputePipelinePFN)(PulseDevice, const PulseComputePipelineCreateInfo*);
typedef void (*PulseBindComputePipelinePFN)(PulseComputePass, PulseComputePipeline);
typedef void (*PulseDestroyComputePipelinePFN)(PulseDevice, PulseComputePipeline);
typedef struct PulseBackendHandler typedef struct PulseBackendHandler
{ {
@@ -58,6 +63,9 @@ typedef struct PulseDeviceHandler
{ {
// PFNs // PFNs
PulseDestroyDevicePFN PFN_DestroyDevice; PulseDestroyDevicePFN PFN_DestroyDevice;
PulseCreateComputePipelinePFN PFN_CreateComputePipeline;
PulseBindComputePipelinePFN PFN_BindComputePipeline;
PulseDestroyComputePipelinePFN PFN_DestroyComputePipeline;
// Attributes // Attributes
void* driver_data; void* driver_data;
@@ -66,9 +74,12 @@ typedef struct PulseDeviceHandler
void PulseSetInternalError(PulseErrorType error); 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) \ #define PULSE_LOAD_DRIVER_DEVICE(_namespace) \
PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyDevice, _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 #ifdef PULSE_ENABLE_VULKAN_BACKEND
extern PulseBackendHandler VulkanDriver; extern PulseBackendHandler VulkanDriver;

View File

@@ -1,16 +0,0 @@
#include <Pulse.h>
#include <stdio.h>
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;
}

11
Tests/Vulkan/DeviceSetup.c git.filemode.normal_file
View File

@@ -0,0 +1,11 @@
#include <unity/unity.h>
#include <Pulse.h>
extern PulseBackend backend;
void TestDeviceSetup()
{
PulseDevice device = PulseCreateDevice(backend, NULL, 0);
TEST_ASSERT_NOT_EQUAL_MESSAGE(device, PULSE_NULL_HANDLE, PulseVerbaliseErrorType(PulseGetLastErrorType()));
PulseDestroyDevice(device);
}

41
Tests/Vulkan/main.c git.filemode.normal_file
View File

@@ -0,0 +1,41 @@
#include <unity/unity.h>
#include <Pulse.h>
#include <stdbool.h>
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;
}

16
Tests/Vulkan/xmake.lua git.filemode.normal_file
View File

@@ -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

View File

@@ -1,6 +0,0 @@
option("tests", { description = "Build tests", default = false })
if has_config("tests") then
set_group("Tests")
includes("*/xmake.lua")
end

View File

@@ -97,4 +97,6 @@ target("pulse_gpu")
end) end)
target_end() target_end()
includes("Tests/*.lua") includes("Examples/*.lua")
includes("Tests/Vulkan/*.lua")