diff --git a/.gitignore b/.gitignore index f4d798b..dc41e7e 100644 --- a/.gitignore +++ b/.gitignore @@ -6,6 +6,7 @@ !xmake.lua !/Includes/ !/Sources/ +!/Tests/ !/.github/ *.o *.swp diff --git a/Includes/Pulse.h b/Includes/Pulse.h index 8e1b147..17ef256 100644 --- a/Includes/Pulse.h +++ b/Includes/Pulse.h @@ -16,7 +16,7 @@ extern "C" { #include "PulseProfile.h" -#define PULSE_VERSION PULSE_MAKE_VERSION(1, 0, 0) +#define PULSE_VERSION PULSE_MAKE_VERSION(0, 0, 1) // Types typedef uint64_t PulseDeviceSize; @@ -35,7 +35,9 @@ PULSE_DEFINE_NULLABLE_HANDLE(PulseImage); typedef enum PulseBackendBits { PULSE_BACKEND_INVALID = PULSE_BIT(1), - PULSE_BACKEND_VULKAN = PULSE_BIT(2), + PULSE_BACKEND_ANY = PULSE_BIT(2), + PULSE_BACKEND_VULKAN = PULSE_BIT(3), + PULSE_BACKEND_D3D11 = PULSE_BIT(4), // More to come } PulseBackendBits; typedef PulseFlags PulseBackendFlags; @@ -65,13 +67,30 @@ typedef PulseFlags PulseImageUsageFlags; typedef enum PulseShaderFormatsBits { - PULSE_SHADER_FORMAT_INVALID_BIT = PULSE_BIT(1), - PULSE_SHADER_FORMAT_SPIRV_BIT = PULSE_BIT(2), + PULSE_SHADER_FORMAT_SPIRV_BIT = PULSE_BIT(1), // Can be used by Vulkan | D3D11 backends + PULSE_SHADER_FORMAT_DXBC_BIT = PULSE_BIT(2), // Can be used by D3D11 backend only // More to come } PulseShaderFormatsBits; typedef PulseFlags PulseShaderFormatsFlags; // Enums +typedef enum PulseDebugLevel +{ + PULSE_NO_DEBUG, + PULSE_LOW_DEBUG, + PULSE_HIGH_DEBUG, + PULSE_PARANOID_DEBUG // Causes every warning to be treated as error +} PulseDebugLevel; + +typedef enum PulseErrorType +{ + PULSE_ERROR_NONE, + + PULSE_ERROR_BACKENDS_CANDIDATES_SHADER_FORMAT_MISMATCH, + PULSE_ERROR_INITIALIZATION_FAILED, + PULSE_ERROR_INVALID_HANDLE, +} PulseErrorType; + typedef enum PulseImageType { PULSE_IMAGE_TYPE_2D, @@ -180,7 +199,6 @@ typedef struct PulseComputePipelineCreateInfo const uint8_t* code; const char* entrypoint; PulseShaderFormatsFlags format; - uint32_t num_samplers; uint32_t num_readonly_storage_images; uint32_t num_readonly_storage_buffers; uint32_t num_readwrite_storage_images; @@ -230,10 +248,14 @@ typedef struct PulseImageRegion } PulseImageRegion; // Functions -PULSE_API PulseDevice PulseCreateDevice(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used); +PULSE_API PulseDevice PulseCreateDevice(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used, PulseDebugLevel debug_level); 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 PulseDeviceSupportsSahderFormats(PulseDevice device, PulseShaderFormatsFlags shader_formats_used); + +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/Includes/PulseProfile.h b/Includes/PulseProfile.h index e89c889..8787d42 100644 --- a/Includes/PulseProfile.h +++ b/Includes/PulseProfile.h @@ -7,6 +7,8 @@ #ifndef PULSE_PROFILE_H_ #define PULSE_PROFILE_H_ +#include + #ifdef __cplusplus extern "C" { #endif @@ -76,6 +78,42 @@ extern "C" { #define PULSE_API #endif +#ifndef __cplusplus // if we compile in C + #ifdef __STDC__ + #ifdef __STDC_VERSION__ + #if __STDC_VERSION__ == 199409L + #define PULSE_C_VERSION 1994 + #elif __STDC_VERSION__ == 199901L + #define PULSE_C_VERSION 1999 + #elif __STDC_VERSION__ == 201112L + #define PULSE_C_VERSION 2011 + #elif __STDC_VERSION__ == 201710L + #define PULSE_C_VERSION 2017 + #elif __STDC_VERSION__ == 202311L + #define PULSE_C_VERSION 2023 + #else + #define PULSE_C_VERSION 0 + #endif + #else + #define PULSE_C_VERSION 0 + #endif + #else + #define PULSE_C_VERSION 0 + #endif +#else + #define PULSE_C_VERSION 0 +#endif + +#if PULSE_C_VERSION >= 2023 + #if defined(PULSE_COMPILER_GCC) || defined(PULSE_COMPILER_CLANG) // for now only GCC and Clang supports nullptr + #define PULSE_NULLPTR nullptr + #else + #define PULSE_NULLPTR NULL + #endif +#else + #define PULSE_NULLPTR NULL +#endif + #define PULSE_DEFINE_NULLABLE_HANDLE(object) typedef struct object##Handler* object #if (defined(__cplusplus) && (__cplusplus >= 201103L)) || (defined(_MSVC_LANG) && (_MSVC_LANG >= 201103L)) diff --git a/Sources/PulseDevice.h b/Sources/Backends/D3D11/D3D11.c similarity index 68% rename from Sources/PulseDevice.h rename to Sources/Backends/D3D11/D3D11.c index 9f106f5..f0cb043 100644 --- a/Sources/PulseDevice.h +++ b/Sources/Backends/D3D11/D3D11.c @@ -4,7 +4,8 @@ #include -typedef struct PulseDeviceHandler -{ +#ifdef PULSE_ENABLE_D3D11_BACKEND -} PulseDeviceHandler; + + +#endif // PULSE_ENABLE_D3D11_BACKEND diff --git a/Sources/Backends/Vulkan/Vulkan.c b/Sources/Backends/Vulkan/Vulkan.c new file mode 100644 index 0000000..6694b57 --- /dev/null +++ b/Sources/Backends/Vulkan/Vulkan.c @@ -0,0 +1,30 @@ +// 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" + +#include "Vulkan.h" +#include "VulkanLoader.h" + +VulkanGlobal* VulkanGetGlobal() +{ + static VulkanGlobal vulkan = { 0 }; + return &vulkan; +} + +bool VulkanLoadBackend() +{ + if(!VulkanInitLoader()) + return false; + + return true; +} + +PulseBackendLoader VulkanDriver = { + .PFN_LoadBackend = VulkanLoadBackend, + .PFN_CreateDevice = PULSE_NULLPTR, + .backend = PULSE_BACKEND_VULKAN, + .supported_shader_formats = PULSE_SHADER_FORMAT_SPIRV_BIT +}; diff --git a/Sources/Backends/Vulkan/Vulkan.h b/Sources/Backends/Vulkan/Vulkan.h new file mode 100644 index 0000000..001f471 --- /dev/null +++ b/Sources/Backends/Vulkan/Vulkan.h @@ -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 + +#ifdef PULSE_ENABLE_VULKAN_BACKEND + +#ifndef PULSE_VULKAN_H_ +#define PULSE_VULKAN_H_ + +#include + +#include "VulkanDevice.h" +#include "VulkanInstance.h" + +typedef struct VulkanGlobal +{ + #define PULSE_VULKAN_GLOBAL_FUNCTION(fn) PFN_##fn fn; + #include "VulkanGlobalPrototypes.h" + #undef PULSE_VULKAN_GLOBAL_FUNCTION +} VulkanGlobal; + +typedef struct VulkanContext +{ + VulkanInstance instance; + VulkanDevice device; +} VulkanContext; + +VulkanGlobal* VulkanGetGlobal(); + +#endif // PULSE_VULKAN_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/Backends/Vulkan/VulkanDevice.h b/Sources/Backends/Vulkan/VulkanDevice.h new file mode 100644 index 0000000..dd746c8 --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanDevice.h @@ -0,0 +1,35 @@ +// 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_DEVICE_H_ +#define PULSE_VULKAN_DEVICE_H_ + +#include + +#define VMA_STATIC_VULKAN_FUNCTIONS 0 +#define VMA_DYNAMIC_VULKAN_FUNCTIONS 0 +#define VMA_VULKAN_VERSION 1000000 +#include + +typedef struct VulkanDevice +{ + VkPhysicalDeviceFeatures features; + VkPhysicalDeviceMemoryProperties memory_properties; + VkPhysicalDeviceProperties properties; + VkPhysicalDevice physical; + + VkDevice device; + + VmaAllocator allocator; + + #define PULSE_VULKAN_DEVICE_FUNCTION(fn) PFN_##fn fn; + #include "VulkanDevicePrototypes.h" + #undef PULSE_VULKAN_DEVICE_FUNCTION +} VulkanDevice; + +#endif // PULSE_VULKAN_DEVICE_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/Backends/Vulkan/VulkanDevicePrototypes.h b/Sources/Backends/Vulkan/VulkanDevicePrototypes.h new file mode 100644 index 0000000..e9451cb --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanDevicePrototypes.h @@ -0,0 +1,75 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +// No header guard + +#ifndef PULSE_VULKAN_DEVICE_FUNCTION + #error "You must define PULSE_VULKAN_DEVICE_FUNCTION before including this file" +#endif + +#ifdef VK_VERSION_1_0 + PULSE_VULKAN_DEVICE_FUNCTION(vkAllocateCommandBuffers) + PULSE_VULKAN_DEVICE_FUNCTION(vkAllocateDescriptorSets) + PULSE_VULKAN_DEVICE_FUNCTION(vkAllocateMemory) + PULSE_VULKAN_DEVICE_FUNCTION(vkBeginCommandBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkBindBufferMemory) + PULSE_VULKAN_DEVICE_FUNCTION(vkBindImageMemory) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdBindDescriptorSets) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdBindPipeline) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdCopyBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdDispatch) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdDispatchIndirect) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdExecuteCommands) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdFillBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdPipelineBarrier) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdPushConstants) + PULSE_VULKAN_DEVICE_FUNCTION(vkCmdUpdateBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateBufferView) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateCommandPool) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateComputePipelines) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateDescriptorPool) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateDescriptorSetLayout) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateFence) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateImage) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreatePipelineCache) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreatePipelineLayout) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateSemaphore) + PULSE_VULKAN_DEVICE_FUNCTION(vkCreateShaderModule) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyBufferView) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyCommandPool) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyDescriptorPool) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyDescriptorSetLayout) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyDevice) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyFence) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyImage) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyPipeline) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyPipelineCache) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyPipelineLayout) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroySemaphore) + PULSE_VULKAN_DEVICE_FUNCTION(vkDestroyShaderModule) + PULSE_VULKAN_DEVICE_FUNCTION(vkDeviceWaitIdle) + PULSE_VULKAN_DEVICE_FUNCTION(vkEndCommandBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkFlushMappedMemoryRanges) + PULSE_VULKAN_DEVICE_FUNCTION(vkFreeCommandBuffers) + PULSE_VULKAN_DEVICE_FUNCTION(vkFreeMemory) + PULSE_VULKAN_DEVICE_FUNCTION(vkGetBufferMemoryRequirements) + PULSE_VULKAN_DEVICE_FUNCTION(vkGetDeviceMemoryCommitment) + PULSE_VULKAN_DEVICE_FUNCTION(vkGetDeviceQueue) + PULSE_VULKAN_DEVICE_FUNCTION(vkGetFenceStatus) + PULSE_VULKAN_DEVICE_FUNCTION(vkGetImageMemoryRequirements) + PULSE_VULKAN_DEVICE_FUNCTION(vkInvalidateMappedMemoryRanges) + PULSE_VULKAN_DEVICE_FUNCTION(vkMapMemory) + PULSE_VULKAN_DEVICE_FUNCTION(vkMergePipelineCaches) + PULSE_VULKAN_DEVICE_FUNCTION(vkQueueSubmit) + PULSE_VULKAN_DEVICE_FUNCTION(vkQueueWaitIdle) + PULSE_VULKAN_DEVICE_FUNCTION(vkResetCommandBuffer) + PULSE_VULKAN_DEVICE_FUNCTION(vkResetCommandPool) + PULSE_VULKAN_DEVICE_FUNCTION(vkResetDescriptorPool) + PULSE_VULKAN_DEVICE_FUNCTION(vkResetFences) + PULSE_VULKAN_DEVICE_FUNCTION(vkUnmapMemory) + PULSE_VULKAN_DEVICE_FUNCTION(vkUpdateDescriptorSets) + PULSE_VULKAN_DEVICE_FUNCTION(vkWaitForFences) +#endif diff --git a/Sources/Backends/Vulkan/VulkanGlobalPrototypes.h b/Sources/Backends/Vulkan/VulkanGlobalPrototypes.h new file mode 100644 index 0000000..961d3bd --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanGlobalPrototypes.h @@ -0,0 +1,16 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +// No header guard + +#ifndef PULSE_VULKAN_GLOBAL_FUNCTION + #error "You must define PULSE_VULKAN_GLOBAL_FUNCTION before including this file" +#endif + +#ifdef VK_VERSION_1_0 + PULSE_VULKAN_GLOBAL_FUNCTION(vkCreateInstance) + PULSE_VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceExtensionProperties) + PULSE_VULKAN_GLOBAL_FUNCTION(vkEnumerateInstanceLayerProperties) + PULSE_VULKAN_GLOBAL_FUNCTION(vkGetInstanceProcAddr) +#endif diff --git a/Sources/Backends/Vulkan/VulkanInstance.h b/Sources/Backends/Vulkan/VulkanInstance.h new file mode 100644 index 0000000..f937ab6 --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanInstance.h @@ -0,0 +1,23 @@ +// 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_INSTANCE_H_ +#define PULSE_VULKAN_INSTANCE_H_ + +#include + +typedef struct VulkanInstance +{ + VkInstance instance; + + #define PULSE_VULKAN_INSTANCE_FUNCTION(fn) PFN_##fn fn; + #include "VulkanInstancePrototypes.h" + #undef PULSE_VULKAN_INSTANCE_FUNCTION +} VulkanInstance; + +#endif // PULSE_VULKAN_INSTANCE_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/Backends/Vulkan/VulkanInstancePrototypes.h b/Sources/Backends/Vulkan/VulkanInstancePrototypes.h new file mode 100644 index 0000000..3cd2b2d --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanInstancePrototypes.h @@ -0,0 +1,23 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +// No header guard + +#ifndef PULSE_VULKAN_INSTANCE_FUNCTION + #error "You must define PULSE_VULKAN_INSTANCE_FUNCTION before including this file" +#endif + +#ifdef VK_VERSION_1_0 + PULSE_VULKAN_INSTANCE_FUNCTION(vkCreateDevice) + PULSE_VULKAN_INSTANCE_FUNCTION(vkDestroyInstance) + PULSE_VULKAN_INSTANCE_FUNCTION(vkEnumerateDeviceExtensionProperties) + PULSE_VULKAN_INSTANCE_FUNCTION(vkEnumeratePhysicalDevices) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetDeviceProcAddr) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFeatures) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceFormatProperties) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceImageFormatProperties) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceMemoryProperties) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceProperties) + PULSE_VULKAN_INSTANCE_FUNCTION(vkGetPhysicalDeviceQueueFamilyProperties) +#endif diff --git a/Sources/Backends/Vulkan/VulkanLoader.c b/Sources/Backends/Vulkan/VulkanLoader.c new file mode 100644 index 0000000..e2b9abf --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanLoader.c @@ -0,0 +1,169 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#include + +#include +#include "../../PulseInternal.h" + +#include "Vulkan.h" +#include "VulkanDevice.h" +#include "VulkanInstance.h" + +#ifdef PULSE_PLAT_MACOS + #include // getenv +#endif + +#ifdef PULSE_PLAT_WINDOWS + typedef const char* LPCSTR; + typedef struct HINSTANCE__* HINSTANCE; + typedef HINSTANCE HMODULE; + #if defined(_MINWINDEF_) + /* minwindef.h defines FARPROC, and attempting to redefine it may conflict with -Wstrict-prototypes */ + #elif defined(_WIN64) + typedef __int64 (__stdcall* FARPROC)(void); + #else + typedef int (__stdcall* FARPROC)(void); + #endif +#endif + +#ifdef PULSE_PLAT_WINDOWS + __declspec(dllimport) HMODULE __stdcall LoadLibraryA(LPCSTR); + __declspec(dllimport) FARPROC __stdcall GetProcAddress(HMODULE, LPCSTR); + __declspec(dllimport) int __stdcall FreeLibrary(HMODULE); + typedef HMODULE LibModule; +#else + #include + typedef void* LibModule; +#endif + +static LibModule vulkan_lib_module = PULSE_NULLPTR; + +static void VulkanLoadGlobalFunctions(void* context, PFN_vkVoidFunction (*load)(void*, const char*)); +static void VulkanLoadInstanceFunctions(VulkanInstance* instance, PFN_vkVoidFunction (*load)(void*, const char*)); +static void VulkanLoadDeviceFunctions(VulkanInstance* instance, VulkanDevice* device, PFN_vkVoidFunction (*load)(VulkanInstance*, void*, const char*)); + +static inline PFN_vkVoidFunction vkGetInstanceProcAddrStub(void* context, const char* name) +{ + PFN_vkVoidFunction fn = VulkanGetGlobal()->vkGetInstanceProcAddr((VkInstance)context, name); + if(!fn) + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return fn; +} + +static inline PFN_vkVoidFunction vkGetDeviceProcAddrStub(VulkanInstance* instance, void* context, const char* name) +{ + PFN_vkVoidFunction fn = instance->vkGetDeviceProcAddr((VkDevice)context, name); + if(!fn) + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return fn; +} + +static inline LibModule LoadLibrary(const char* libname) +{ + #ifdef PULSE_PLAT_WINDOWS + return LoadLibraryA(libname); + #else + return dlopen(libname, RTLD_NOW | RTLD_LOCAL); + #endif +} + +static inline void* GetSymbol(LibModule module, const char* name) +{ + #ifdef PULSE_PLAT_WINDOWS + return (void*)(void(*)(void))GetProcAddress(module, name); + #else + return dlsym(module, name); + #endif +} + +static inline void UnloadLibrary(LibModule lib) +{ + #ifdef PULSE_PLAT_WINDOWS + FreeLibrary(lib); + #else + dlclose(lib); + #endif +} + +bool VulkanInitLoader() +{ + #if defined(PULSE_PLAT_WINDOWS) + const char* libnames[] = { + "vulkan-1.dll" + }; + #elif defined(PULSE_PLAT_MACOS) + const char* libnames[] = { + "libvulkan.dylib", + "libvulkan.1.dylib", + "libMoltenVK.dylib", + "vulkan.framework/vulkan", + "MoltenVK.framework/MoltenVK", + "/usr/local/lib/libvulkan.dylib", + }; + #else + const char* libnames[] = { + "libvulkan.so.1", + "libvulkan.so" + }; + #endif + + for(size_t i = 0; i < sizeof(libnames) / sizeof(const char*); i++) + { + vulkan_lib_module = LoadLibrary(libnames[i]); + if(vulkan_lib_module) + { + VulkanGetGlobal()->vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)GetSymbol(vulkan_lib_module, "vkGetInstanceProcAddr"); + if(VulkanGetGlobal()->vkGetInstanceProcAddr == PULSE_NULLPTR) + { + UnloadLibrary(vulkan_lib_module); + vulkan_lib_module = PULSE_NULLPTR; + } + } + } + if(!vulkan_lib_module) + { + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return false; + } + VulkanLoadGlobalFunctions(PULSE_NULLPTR, vkGetInstanceProcAddrStub); + return true; +} + +void VulkanLoadInstance(VulkanInstance* instance) +{ + VulkanLoadInstanceFunctions(instance, vkGetInstanceProcAddrStub); +} + +void VulkanLoadDevice(VulkanInstance* instance, VulkanDevice* device) +{ + VulkanLoadDeviceFunctions(instance, device, vkGetDeviceProcAddrStub); +} + +void VulkanLoaderShutdown() +{ + UnloadLibrary(vulkan_lib_module); + vulkan_lib_module = PULSE_NULLPTR; +} + +static void VulkanLoadGlobalFunctions(void* context, PFN_vkVoidFunction (*load)(void*, const char*)) +{ + #define PULSE_VULKAN_GLOBAL_FUNCTION(func) VulkanGetGlobal()->func = (PFN_##func)load(context, #func); + #include "VulkanGlobalPrototypes.h" + #undef PULSE_VULKAN_GLOBAL_FUNCTION +} + +static void VulkanLoadInstanceFunctions(VulkanInstance* instance, PFN_vkVoidFunction (*load)(void*, const char*)) +{ + #define PULSE_VULKAN_INSTANCE_FUNCTION(func) instance->func = (PFN_##func)load(instance->instance, #func); + #include "VulkanInstancePrototypes.h" + #undef PULSE_VULKAN_INSTANCE_FUNCTION +} + +static void VulkanLoadDeviceFunctions(VulkanInstance* instance, VulkanDevice* device, PFN_vkVoidFunction (*load)(VulkanInstance*, void*, const char*)) +{ + #define PULSE_VULKAN_DEVICE_FUNCTION(func) device->func = (PFN_##func)load(instance, device->device, #func); + #include "VulkanDevicePrototypes.h" + #undef PULSE_VULKAN_DEVICE_FUNCTION +} diff --git a/Sources/Backends/Vulkan/VulkanLoader.h b/Sources/Backends/Vulkan/VulkanLoader.h new file mode 100644 index 0000000..59b172e --- /dev/null +++ b/Sources/Backends/Vulkan/VulkanLoader.h @@ -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 + +#ifdef PULSE_ENABLE_VULKAN_BACKEND + +#ifndef PULSE_VULKAN_LOADER_H_ +#define PULSE_VULKAN_LOADER_H_ + +#include + +typedef struct VulkanInstance VulkanInstance; +typedef struct VulkanDevice VulkanDevice; + +bool VulkanInitLoader(); +bool VulkanLoadInstance(VulkanInstance* instance); +bool VulkanLoadDevice(VulkanInstance* instance, VulkanDevice* device); +void VulkanLoaderShutdown(); + +#endif // PULSE_VULKAN_LOADER_H_ + +#endif // PULSE_ENABLE_VULKAN_BACKEND diff --git a/Sources/Backends/Vulkan/VulkanDevice.c b/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp similarity index 71% rename from Sources/Backends/Vulkan/VulkanDevice.c rename to Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp index cee4dbc..d02524c 100644 --- a/Sources/Backends/Vulkan/VulkanDevice.c +++ b/Sources/Backends/Vulkan/VulkanMemoryAllocatorImplementation.cpp @@ -2,5 +2,5 @@ // This file is part of "Pulse" // For conditions of distribution and use, see copyright notice in LICENSE -#include - +#define VMA_IMPLEMENTATION +#include diff --git a/Sources/PulseDevice.c b/Sources/PulseDevice.c index 3c94ec0..ae0982a 100644 --- a/Sources/PulseDevice.c +++ b/Sources/PulseDevice.c @@ -3,19 +3,122 @@ // For conditions of distribution and use, see copyright notice in LICENSE #include +#include "PulseInternal.h" -PULSE_API PulseDevice PulseCreateDevice(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used) +#define PULSE_CHECK_HANDLE_RETVAL(handle, retval) \ + do { \ + if(handle == PULSE_NULL_HANDLE) \ + { \ + PulseSetInternalError(PULSE_ERROR_INVALID_HANDLE); \ + return retval; \ + } \ + } while(0); \ + +#define PULSE_CHECK_HANDLE(handle) PULSE_CHECK_HANDLE_RETVAL(handle, ) + +// Ordered by default preference +static const PulseBackendLoader* backends[] = { + #ifdef PULSE_ENABLE_VULKAN_BACKEND + &VulkanDriver, + #endif + #ifdef PULSE_ENABLE_D3D11_BACKEND + &D3D11Driver, + #endif + PULSE_NULLPTR +}; + +static PulseErrorType last_error = PULSE_ERROR_NONE; + +void PulseSetInternalError(PulseErrorType error) { + last_error = error; +} + +static const PulseBackendLoader* PulseSelectBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used) +{ + if((backend_candidates & PULSE_BACKEND_INVALID) != 0) + { + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return PULSE_NULLPTR; + } + for(int i = 0; backends[i] != PULSE_NULLPTR; i++) + { + if(backend_candidates != PULSE_BACKEND_ANY && (backend_candidates & backends[i]->backend) == 0) + continue; + if((shader_formats_used & backends[i]->supported_shader_formats) == 0) + { + PulseSetInternalError(PULSE_ERROR_BACKENDS_CANDIDATES_SHADER_FORMAT_MISMATCH); + return PULSE_NULLPTR; + } + if(backends[i]->PFN_LoadBackend()) + return backends[i]; + } + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return PULSE_NULLPTR; +} + +PULSE_API PulseDevice PulseCreateDevice(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used, PulseDebugLevel debug_level) +{ + const PulseBackendLoader* backend = PulseSelectBackend(backend_candidates, shader_formats_used); + if(backend == PULSE_NULLPTR) // Error code already set by PulseSelectBackend + return PULSE_NULL_HANDLE; + + PulseDevice device = backend->PFN_CreateDevice(debug_level); + if(device == PULSE_NULL_HANDLE) + PulseSetInternalError(PULSE_ERROR_INITIALIZATION_FAILED); + return device; } PULSE_API void PulseDestroyDevice(PulseDevice device) { + PULSE_CHECK_HANDLE(device); + device->PFN_DestroyDevice(device); } PULSE_API PulseBackendBits PulseGetBackendInUseByDevice(PulseDevice device) { + PULSE_CHECK_HANDLE_RETVAL(device, PULSE_BACKEND_INVALID); + return device->backend; } PULSE_API bool PulseSupportsBackend(PulseBackendFlags backend_candidates, PulseShaderFormatsFlags shader_formats_used) { + if((backend_candidates & PULSE_BACKEND_INVALID) != 0) + return false; + if((backend_candidates & PULSE_BACKEND_ANY) != 0) + return true; + for(int i = 0; backends[i] != PULSE_NULLPTR; i++) + { + if((backend_candidates & backends[i]->backend) == 0) + continue; + if((shader_formats_used & backends[i]->supported_shader_formats) != 0) + return true; + } + return false; +} + +PULSE_API bool PulseDeviceSupportsSahderFormats(PulseDevice device, PulseShaderFormatsFlags shader_formats_used) +{ + PULSE_CHECK_HANDLE_RETVAL(device, false); + return (device->supported_shader_formats & shader_formats_used) != 0; +} + +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"; + + default: return "invalid error type"; + }; + return PULSE_NULLPTR; // To avoid warnings, should be unreachable } diff --git a/Sources/PulseInternal.h b/Sources/PulseInternal.h new file mode 100644 index 0000000..2cd1c53 --- /dev/null +++ b/Sources/PulseInternal.h @@ -0,0 +1,58 @@ +// Copyright (C) 2024 kanel +// This file is part of "Pulse" +// For conditions of distribution and use, see copyright notice in LICENSE + +#ifndef PULSE_INTERNAL_H_ +#define PULSE_INTERNAL_H_ + +#include + +#ifdef __cplusplus +extern "C" { +#endif + +typedef bool (*PulseLoadBackendPFN)(void); +typedef PulseDevice (*PulseCreateDevicePFN)(PulseDebugLevel); + +typedef void (*PulseDestroyDevicePFN)(PulseDevice); + +typedef struct PulseDeviceHandler +{ + // PFNs + PulseDestroyDevicePFN PFN_DestroyDevice; + + // Attributes + PulseBackendFlags backend; + PulseShaderFormatsFlags supported_shader_formats; + PulseDebugLevel debug_level; +} PulseDeviceHandler; + +typedef struct PulseBackendLoader +{ + // PFNs + PulseLoadBackendPFN PFN_LoadBackend; + PulseCreateDevicePFN PFN_CreateDevice; + + // Attributes + PulseBackendFlags backend; + PulseShaderFormatsFlags supported_shader_formats; +} PulseBackendLoader; + +void PulseSetInternalError(PulseErrorType error); + +#define PULSE_LOAD_DRIVER_DEVICE_FUNCTION(fn, _namespace) device->PFN_##fn = _namespace##fn; +#define PULSE_LOAD_DRIVER_DEVICE(_namespace) \ + PULSE_LOAD_DRIVER_DEVICE_FUNCTION(DestroyDevice, _namespace) \ + +#ifdef PULSE_ENABLE_VULKAN_BACKEND + extern PulseBackendLoader VulkanDriver; +#endif // PULSE_ENABLE_VULKAN_BACKEND +#ifdef PULSE_ENABLE_D3D11_BACKEND + extern PulseBackendLoader D3D11Driver; +#endif // PULSE_ENABLE_D3D11_BACKEND + +#ifdef __cplusplus +} +#endif + +#endif // PULSE_INTERNAL_H_ diff --git a/Tests/LoadingPulse/build/LoadingPulse.x86_64 b/Tests/LoadingPulse/build/LoadingPulse.x86_64 new file mode 100755 index 0000000..5e88219 Binary files /dev/null and b/Tests/LoadingPulse/build/LoadingPulse.x86_64 differ diff --git a/Tests/LoadingPulse/main.c b/Tests/LoadingPulse/main.c new file mode 100644 index 0000000..1478939 --- /dev/null +++ b/Tests/LoadingPulse/main.c @@ -0,0 +1,15 @@ +#include + +#include + +int main(void) +{ + PulseDevice device = PulseCreateDevice(PULSE_BACKEND_ANY, PULSE_SHADER_FORMAT_SPIRV_BIT, PULSE_NO_DEBUG); + if(device == PULSE_NULL_HANDLE) + { + fprintf(stderr, "Error while loading Pulse: %s", PulseVerbaliseErrorType(PulseGetLastErrorType())); + return 1; + } + PulseDestroyDevice(device); + return 0; +} diff --git a/Tests/LoadingPulse/xmake.lua b/Tests/LoadingPulse/xmake.lua new file mode 100644 index 0000000..5dc3c00 --- /dev/null +++ b/Tests/LoadingPulse/xmake.lua @@ -0,0 +1,8 @@ +target("LoadingPulse") + add_deps("pulse_gpu") + if is_plat("linux") then + set_extension(".x86_64") + end + add_files("main.c") + set_targetdir("build/") +target_end() diff --git a/Tests/xmake.lua b/Tests/xmake.lua new file mode 100644 index 0000000..46e818b --- /dev/null +++ b/Tests/xmake.lua @@ -0,0 +1,6 @@ +option("tests", { description = "Build tests", default = true }) + +if has_config("tests") then + set_group("Tests") + includes("*/xmake.lua") +end diff --git a/xmake.lua b/xmake.lua index ea84101..309b05b 100644 --- a/xmake.lua +++ b/xmake.lua @@ -2,6 +2,23 @@ -- This file is part of "Pulse" -- For conditions of distribution and use, see copyright notice in LICENSE +local backends = { + Vulkan = { + option = "vulkan", + default = true, + has_cpp_files = true, + packages = { "vulkan-headers", "vulkan-memory-allocator" }, + custom = function() + add_defines("VK_NO_PROTOTYPES") + end + }, + D3D11 = { + option = "d3d11", + default = is_plat("windows", "mingw"), + has_cpp_files = false, + } +} + local sanitizers = { asan = "address", lsan = "leak", @@ -15,10 +32,18 @@ for opt, policy in table.orderpairs(sanitizers) do end end +for name, module in table.orderpairs(backends) do + if module.option then + option(module.option, { description = "Enables the " .. name .. " backend", default = module.default }) + end +end + add_rules("mode.debug", "mode.release") add_includedirs("Includes") set_languages("c99", "cxx20") +set_encodings("utf-8") +set_warnings("allextra") set_objectdir("build/Objs/$(os)_$(arch)") set_targetdir("build/Bin/$(os)_$(arch)") @@ -27,17 +52,54 @@ set_dependir("build/.deps") set_optimize("fastest") +for name, module in pairs(backends) do + if has_config(module.option) then + if module.packages then + add_requires(table.unpack(module.packages)) + end + end +end + target("pulse_gpu") set_kind("$(kind)") add_defines("PULSE_BUILD") add_headerfiles("Includes/*.hpp)") - add_headerfiles("Sources/**.h", { prefixdir = "private", install = false }) - add_headerfiles("Sources/**.inl", { prefixdir = "private", install = false }) - add_files("Sources/**.c") - add_files("Sources/**.cpp") + add_headerfiles("Sources/*.h", { prefixdir = "private", install = false }) + add_headerfiles("Sources/*.inl", { prefixdir = "private", install = false }) + + add_files("Sources/*.c") + + for name, module in pairs(backends) do + if not has_config(module.option) then + goto continue + end + + if module.packages then + add_packages(table.unpack(module.packages)) + end + + add_defines("PULSE_ENABLE_" .. name:upper() .. "_BACKEND") + + add_headerfiles("Sources/Backends/" .. name .. "/**.h", { prefixdir = "private", install = false }) + add_headerfiles("Sources/Backends/" .. name .. "/*.inl", { prefixdir = "private", install = false }) + + add_files("Sources/Backends/" .. name .. "/**.c") + if module.has_cpp_files then + add_files("Sources/Backends/" .. name .. "/**.cpp") + end + + if module.custom then + module.custom() + end + + ::continue:: + end + on_load(function(target) if target:kind() == "static" then target:add("defines", "PULSE_STATIC", { public = true }) end end) target_end() + +includes("Tests/*.lua")