From 8de4b8677be47c9b59f9e8d089180ad5ff4bd7e4 Mon Sep 17 00:00:00 2001 From: Kbz-8 Date: Tue, 8 Oct 2024 09:40:39 +0200 Subject: [PATCH] adding D3D11 base, working on Vulkan backend --- .gitignore | 1 + Includes/Pulse.h | 34 +++- Includes/PulseProfile.h | 38 ++++ .../{PulseDevice.h => Backends/D3D11/D3D11.c} | 7 +- Sources/Backends/Vulkan/Vulkan.c | 30 ++++ Sources/Backends/Vulkan/Vulkan.h | 32 ++++ Sources/Backends/Vulkan/VulkanDevice.h | 35 ++++ .../Backends/Vulkan/VulkanDevicePrototypes.h | 75 ++++++++ .../Backends/Vulkan/VulkanGlobalPrototypes.h | 16 ++ Sources/Backends/Vulkan/VulkanInstance.h | 23 +++ .../Vulkan/VulkanInstancePrototypes.h | 23 +++ Sources/Backends/Vulkan/VulkanLoader.c | 169 ++++++++++++++++++ Sources/Backends/Vulkan/VulkanLoader.h | 22 +++ ...> VulkanMemoryAllocatorImplementation.cpp} | 4 +- Sources/PulseDevice.c | 105 ++++++++++- Sources/PulseInternal.h | 58 ++++++ Tests/LoadingPulse/build/LoadingPulse.x86_64 | Bin 0 -> 183560 bytes Tests/LoadingPulse/main.c | 15 ++ Tests/LoadingPulse/xmake.lua | 8 + Tests/xmake.lua | 6 + xmake.lua | 70 +++++++- 21 files changed, 755 insertions(+), 16 deletions(-) rename Sources/{PulseDevice.h => Backends/D3D11/D3D11.c} (68%) create mode 100644 Sources/Backends/Vulkan/Vulkan.c create mode 100644 Sources/Backends/Vulkan/Vulkan.h create mode 100644 Sources/Backends/Vulkan/VulkanDevice.h create mode 100644 Sources/Backends/Vulkan/VulkanDevicePrototypes.h create mode 100644 Sources/Backends/Vulkan/VulkanGlobalPrototypes.h create mode 100644 Sources/Backends/Vulkan/VulkanInstance.h create mode 100644 Sources/Backends/Vulkan/VulkanInstancePrototypes.h create mode 100644 Sources/Backends/Vulkan/VulkanLoader.c create mode 100644 Sources/Backends/Vulkan/VulkanLoader.h rename Sources/Backends/Vulkan/{VulkanDevice.c => VulkanMemoryAllocatorImplementation.cpp} (71%) create mode 100644 Sources/PulseInternal.h create mode 100755 Tests/LoadingPulse/build/LoadingPulse.x86_64 create mode 100644 Tests/LoadingPulse/main.c create mode 100644 Tests/LoadingPulse/xmake.lua create mode 100644 Tests/xmake.lua 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 0000000000000000000000000000000000000000..5e88219f7d45e704ac1794ef6f4a72dde720957b GIT binary patch literal 183560 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 + +#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")