398 lines
15 KiB
C++
398 lines
15 KiB
C++
// Jolt Physics Library (https://github.com/jrouwe/JoltPhysics)
|
|
// SPDX-FileCopyrightText: 2025 Jorrit Rouwe
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include <Jolt/Jolt.h>
|
|
|
|
#ifdef JPH_USE_VK
|
|
|
|
#include <Jolt/Compute/VK/ComputeSystemVKImpl.h>
|
|
#include <Jolt/Core/QuickSort.h>
|
|
#include <Jolt/Core/IncludeWindows.h>
|
|
#if defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID) ||defined(JPH_PLATFORM_MACOS)
|
|
#include <dlfcn.h>
|
|
#endif
|
|
|
|
JPH_NAMESPACE_BEGIN
|
|
|
|
JPH_IMPLEMENT_RTTI_VIRTUAL(ComputeSystemVKImpl)
|
|
{
|
|
JPH_ADD_BASE_CLASS(ComputeSystemVKImpl, ComputeSystemVKWithAllocator)
|
|
}
|
|
|
|
#ifdef JPH_DEBUG
|
|
|
|
static VKAPI_ATTR VkBool32 VKAPI_CALL sVulkanDebugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT inSeverity, [[maybe_unused]] VkDebugUtilsMessageTypeFlagsEXT inType, const VkDebugUtilsMessengerCallbackDataEXT *inCallbackData, [[maybe_unused]] void *inUserData)
|
|
{
|
|
if (inSeverity & (VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT))
|
|
Trace("VK: %s", inCallbackData->pMessage);
|
|
JPH_ASSERT((inSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT) == 0);
|
|
return VK_FALSE;
|
|
}
|
|
|
|
#endif // JPH_DEBUG
|
|
|
|
ComputeSystemVKImpl::~ComputeSystemVKImpl()
|
|
{
|
|
ComputeSystemVK::Shutdown();
|
|
|
|
if (mDevice != VK_NULL_HANDLE && mVkDestroyDevice != nullptr)
|
|
mVkDestroyDevice(mDevice, nullptr);
|
|
|
|
#ifdef JPH_DEBUG
|
|
if (mInstance != VK_NULL_HANDLE && mDebugMessenger != VK_NULL_HANDLE && mVkDestroyDebugUtilsMessengerEXT != nullptr)
|
|
mVkDestroyDebugUtilsMessengerEXT(mInstance, mDebugMessenger, nullptr);
|
|
#endif
|
|
|
|
if (mInstance != VK_NULL_HANDLE && mVkDestroyInstance != nullptr)
|
|
mVkDestroyInstance(mInstance, nullptr);
|
|
}
|
|
|
|
bool ComputeSystemVKImpl::Initialize(ComputeSystemResult &outResult)
|
|
{
|
|
#ifdef JPH_PLATFORM_WINDOWS
|
|
HMODULE module = LoadLibraryA("vulkan-1.dll");
|
|
if (!module)
|
|
{
|
|
outResult.SetError("Failed to load vulkan-1.dll");
|
|
return false;
|
|
}
|
|
mVkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(reinterpret_cast<void *>(GetProcAddress(module, "vkGetInstanceProcAddr")));
|
|
#elif defined(JPH_PLATFORM_LINUX) || defined(JPH_PLATFORM_ANDROID)
|
|
void *library = dlopen("libvulkan.so.1", RTLD_NOW | RTLD_LOCAL);
|
|
if (!library)
|
|
library = dlopen("libvulkan.so", RTLD_NOW | RTLD_LOCAL);
|
|
if (!library)
|
|
{
|
|
outResult.SetError("Failed to load libvulkan.so.1 or libvulkan.so");
|
|
return false;
|
|
}
|
|
mVkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
|
|
#elif defined(JPH_PLATFORM_MACOS)
|
|
void *library = dlopen("libvulkan.1.dylib", RTLD_NOW | RTLD_LOCAL);
|
|
if (!library)
|
|
library = dlopen("libvulkan.dylib", RTLD_NOW | RTLD_LOCAL);
|
|
if (!library)
|
|
{
|
|
outResult.SetError("Failed to load libvulkan.1.dylib or libvulkan.dylib");
|
|
return false;
|
|
}
|
|
mVkGetInstanceProcAddr = reinterpret_cast<PFN_vkGetInstanceProcAddr>(dlsym(library, "vkGetInstanceProcAddr"));
|
|
#else
|
|
#error "Unsupported platform"
|
|
#endif
|
|
|
|
// Check vkGetInstanceProcAddr
|
|
if (mVkGetInstanceProcAddr == nullptr)
|
|
{
|
|
outResult.SetError("Failed to get vkGetInstanceProcAddr");
|
|
return false;
|
|
}
|
|
|
|
// Required instance extensions
|
|
Array<const char *> required_instance_extensions;
|
|
required_instance_extensions.push_back(VK_KHR_SURFACE_EXTENSION_NAME);
|
|
required_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
#ifdef JPH_PLATFORM_MACOS
|
|
required_instance_extensions.push_back("VK_KHR_portability_enumeration");
|
|
required_instance_extensions.push_back("VK_KHR_get_physical_device_properties2");
|
|
#endif
|
|
GetInstanceExtensions(required_instance_extensions);
|
|
|
|
// Required device extensions
|
|
Array<const char *> required_device_extensions;
|
|
required_device_extensions.push_back(VK_EXT_SCALAR_BLOCK_LAYOUT_EXTENSION_NAME);
|
|
#ifdef JPH_PLATFORM_MACOS
|
|
required_device_extensions.push_back("VK_KHR_portability_subset"); // VK_KHR_PORTABILITY_SUBSET_EXTENSION_NAME
|
|
#endif
|
|
GetDeviceExtensions(required_device_extensions);
|
|
|
|
// Load pre-instance Vulkan functions
|
|
#define JPH_LOAD_VK_PRE_INST(name) mVk##name = reinterpret_cast<PFN_vk##name>(reinterpret_cast<void *>(mVkGetInstanceProcAddr(nullptr, "vk" #name))); JPH_ASSERT(mVk##name != nullptr)
|
|
JPH_LOAD_VK_PRE_INST(CreateInstance);
|
|
JPH_LOAD_VK_PRE_INST(EnumerateInstanceExtensionProperties);
|
|
JPH_LOAD_VK_PRE_INST(EnumerateInstanceLayerProperties);
|
|
#undef JPH_LOAD_VK_PRE_INST
|
|
|
|
// Query supported instance extensions
|
|
uint32 instance_extension_count = 0;
|
|
if (VKFailed(mVkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, nullptr), outResult))
|
|
return false;
|
|
Array<VkExtensionProperties> instance_extensions;
|
|
instance_extensions.resize(instance_extension_count);
|
|
if (VKFailed(mVkEnumerateInstanceExtensionProperties(nullptr, &instance_extension_count, instance_extensions.data()), outResult))
|
|
return false;
|
|
|
|
// Query supported validation layers
|
|
uint32 validation_layer_count;
|
|
mVkEnumerateInstanceLayerProperties(&validation_layer_count, nullptr);
|
|
Array<VkLayerProperties> validation_layers(validation_layer_count);
|
|
mVkEnumerateInstanceLayerProperties(&validation_layer_count, validation_layers.data());
|
|
|
|
VkApplicationInfo app_info = {};
|
|
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
app_info.apiVersion = VK_API_VERSION_1_1;
|
|
|
|
// Create Vulkan instance
|
|
VkInstanceCreateInfo instance_create_info = {};
|
|
instance_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
#ifdef JPH_PLATFORM_MACOS
|
|
instance_create_info.flags = VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR;
|
|
#endif
|
|
instance_create_info.pApplicationInfo = &app_info;
|
|
|
|
#ifdef JPH_DEBUG
|
|
// Enable validation layer if supported
|
|
const char *desired_validation_layers[] = { "VK_LAYER_KHRONOS_validation" };
|
|
for (const VkLayerProperties &p : validation_layers)
|
|
if (strcmp(desired_validation_layers[0], p.layerName) == 0)
|
|
{
|
|
instance_create_info.enabledLayerCount = 1;
|
|
instance_create_info.ppEnabledLayerNames = desired_validation_layers;
|
|
break;
|
|
}
|
|
|
|
// Setup debug messenger callback if the extension is supported
|
|
VkDebugUtilsMessengerCreateInfoEXT messenger_create_info = {};
|
|
for (const VkExtensionProperties &ext : instance_extensions)
|
|
if (strcmp(VK_EXT_DEBUG_UTILS_EXTENSION_NAME, ext.extensionName) == 0)
|
|
{
|
|
messenger_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
|
|
messenger_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
|
|
messenger_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
|
|
messenger_create_info.pfnUserCallback = sVulkanDebugCallback;
|
|
instance_create_info.pNext = &messenger_create_info;
|
|
required_instance_extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
|
|
break;
|
|
}
|
|
#endif
|
|
|
|
instance_create_info.enabledExtensionCount = (uint32)required_instance_extensions.size();
|
|
instance_create_info.ppEnabledExtensionNames = required_instance_extensions.data();
|
|
if (VKFailed(mVkCreateInstance(&instance_create_info, nullptr, &mInstance), outResult))
|
|
return false;
|
|
|
|
// Load instance-level Vulkan functions
|
|
#define JPH_LOAD_VK_INST(name) mVk##name = reinterpret_cast<PFN_vk##name>(reinterpret_cast<void *>(mVkGetInstanceProcAddr(mInstance, "vk" #name))); JPH_ASSERT(mVk##name != nullptr)
|
|
JPH_LOAD_VK_INST(CreateDevice);
|
|
JPH_LOAD_VK_INST(DestroyInstance);
|
|
JPH_LOAD_VK_INST(EnumerateDeviceExtensionProperties);
|
|
JPH_LOAD_VK_INST(EnumeratePhysicalDevices);
|
|
JPH_LOAD_VK_INST(GetPhysicalDeviceProperties);
|
|
JPH_LOAD_VK_INST(GetPhysicalDeviceQueueFamilyProperties);
|
|
#undef JPH_LOAD_VK_INST
|
|
|
|
// Get vkGetDeviceProcAddr
|
|
mVkGetDeviceProcAddr = reinterpret_cast<PFN_vkGetDeviceProcAddr>(reinterpret_cast<void *>(mVkGetInstanceProcAddr(mInstance, "vkGetDeviceProcAddr")));
|
|
if (mVkGetDeviceProcAddr == nullptr)
|
|
{
|
|
outResult.SetError("Failed to get vkGetDeviceProcAddr");
|
|
return false;
|
|
}
|
|
|
|
#ifdef JPH_DEBUG
|
|
// Finalize debug messenger callback
|
|
PFN_vkCreateDebugUtilsMessengerEXT vkCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)(std::uintptr_t)mVkGetInstanceProcAddr(mInstance, "vkCreateDebugUtilsMessengerEXT");
|
|
mVkDestroyDebugUtilsMessengerEXT = reinterpret_cast<PFN_vkDestroyDebugUtilsMessengerEXT>(reinterpret_cast<void *>(mVkGetInstanceProcAddr(mInstance, "vkDestroyDebugUtilsMessengerEXT")));
|
|
if (vkCreateDebugUtilsMessengerEXT != nullptr && VKFailed(vkCreateDebugUtilsMessengerEXT(mInstance, &messenger_create_info, nullptr, &mDebugMessenger), outResult))
|
|
return false;
|
|
#endif
|
|
|
|
// Notify that instance has been created
|
|
OnInstanceCreated();
|
|
|
|
// Select device
|
|
uint32 device_count = 0;
|
|
if (VKFailed(mVkEnumeratePhysicalDevices(mInstance, &device_count, nullptr), outResult))
|
|
return false;
|
|
Array<VkPhysicalDevice> devices;
|
|
devices.resize(device_count);
|
|
if (VKFailed(mVkEnumeratePhysicalDevices(mInstance, &device_count, devices.data()), outResult))
|
|
return false;
|
|
struct Device
|
|
{
|
|
VkPhysicalDevice mPhysicalDevice;
|
|
String mName;
|
|
VkSurfaceFormatKHR mFormat;
|
|
uint32 mGraphicsQueueIndex;
|
|
uint32 mPresentQueueIndex;
|
|
uint32 mComputeQueueIndex;
|
|
int mScore;
|
|
};
|
|
Array<Device> available_devices;
|
|
for (VkPhysicalDevice device : devices)
|
|
{
|
|
// Get device properties
|
|
VkPhysicalDeviceProperties properties;
|
|
mVkGetPhysicalDeviceProperties(device, &properties);
|
|
|
|
// Test if it is an appropriate type
|
|
int score = 0;
|
|
switch (properties.deviceType)
|
|
{
|
|
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU:
|
|
score = 30;
|
|
break;
|
|
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU:
|
|
score = 20;
|
|
break;
|
|
case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU:
|
|
score = 10;
|
|
break;
|
|
case VK_PHYSICAL_DEVICE_TYPE_CPU:
|
|
score = 5;
|
|
break;
|
|
case VK_PHYSICAL_DEVICE_TYPE_OTHER:
|
|
case VK_PHYSICAL_DEVICE_TYPE_MAX_ENUM:
|
|
continue;
|
|
}
|
|
|
|
// Check if the device supports all our required extensions
|
|
uint32 device_extension_count;
|
|
mVkEnumerateDeviceExtensionProperties(device, nullptr, &device_extension_count, nullptr);
|
|
Array<VkExtensionProperties> available_extensions;
|
|
available_extensions.resize(device_extension_count);
|
|
mVkEnumerateDeviceExtensionProperties(device, nullptr, &device_extension_count, available_extensions.data());
|
|
int found_extensions = 0;
|
|
for (const char *required_device_extension : required_device_extensions)
|
|
for (const VkExtensionProperties &ext : available_extensions)
|
|
if (strcmp(required_device_extension, ext.extensionName) == 0)
|
|
{
|
|
found_extensions++;
|
|
break;
|
|
}
|
|
if (found_extensions != int(required_device_extensions.size()))
|
|
continue;
|
|
|
|
// Find the right queues
|
|
uint32 queue_family_count = 0;
|
|
mVkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr);
|
|
Array<VkQueueFamilyProperties> queue_families;
|
|
queue_families.resize(queue_family_count);
|
|
mVkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families.data());
|
|
uint32 graphics_queue = ~uint32(0);
|
|
uint32 present_queue = ~uint32(0);
|
|
uint32 compute_queue = ~uint32(0);
|
|
for (uint32 i = 0; i < uint32(queue_families.size()); ++i)
|
|
{
|
|
if (queue_families[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
|
|
{
|
|
graphics_queue = i;
|
|
|
|
if (queue_families[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
|
|
compute_queue = i;
|
|
}
|
|
|
|
if (HasPresentSupport(device, i))
|
|
present_queue = i;
|
|
|
|
if (graphics_queue != ~uint32(0) && present_queue != ~uint32(0) && compute_queue != ~uint32(0))
|
|
break;
|
|
}
|
|
if (graphics_queue == ~uint32(0) || present_queue == ~uint32(0) || compute_queue == ~uint32(0))
|
|
continue;
|
|
|
|
// Select surface format
|
|
VkSurfaceFormatKHR selected_format = SelectFormat(device);
|
|
if (selected_format.format == VK_FORMAT_UNDEFINED)
|
|
continue;
|
|
|
|
// Add the device
|
|
available_devices.push_back({ device, properties.deviceName, selected_format, graphics_queue, present_queue, compute_queue, score });
|
|
}
|
|
if (available_devices.empty())
|
|
{
|
|
outResult.SetError("No suitable Vulkan device found");
|
|
return false;
|
|
}
|
|
|
|
// Sort the devices by score
|
|
QuickSort(available_devices.begin(), available_devices.end(), [](const Device &inLHS, const Device &inRHS) {
|
|
return inLHS.mScore > inRHS.mScore;
|
|
});
|
|
const Device &selected_device = available_devices[0];
|
|
|
|
// Create device
|
|
float queue_priority = 1.0f;
|
|
VkDeviceQueueCreateInfo queue_create_info[3] = {};
|
|
for (VkDeviceQueueCreateInfo &q : queue_create_info)
|
|
{
|
|
q.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
q.queueCount = 1;
|
|
q.pQueuePriorities = &queue_priority;
|
|
}
|
|
uint32 num_queues = 0;
|
|
queue_create_info[num_queues++].queueFamilyIndex = selected_device.mGraphicsQueueIndex;
|
|
bool found = false;
|
|
for (uint32 i = 0; i < num_queues; ++i)
|
|
if (queue_create_info[i].queueFamilyIndex == selected_device.mPresentQueueIndex)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
if (!found)
|
|
queue_create_info[num_queues++].queueFamilyIndex = selected_device.mPresentQueueIndex;
|
|
found = false;
|
|
for (uint32 i = 0; i < num_queues; ++i)
|
|
if (queue_create_info[i].queueFamilyIndex == selected_device.mComputeQueueIndex)
|
|
{
|
|
found = true;
|
|
break;
|
|
}
|
|
if (!found)
|
|
queue_create_info[num_queues++].queueFamilyIndex = selected_device.mComputeQueueIndex;
|
|
|
|
VkPhysicalDeviceScalarBlockLayoutFeatures enable_scalar_block = {};
|
|
enable_scalar_block.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SCALAR_BLOCK_LAYOUT_FEATURES;
|
|
enable_scalar_block.scalarBlockLayout = VK_TRUE;
|
|
|
|
VkPhysicalDeviceFeatures2 enabled_features2 = {};
|
|
enabled_features2.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2;
|
|
GetEnabledFeatures(enabled_features2);
|
|
enable_scalar_block.pNext = enabled_features2.pNext;
|
|
enabled_features2.pNext = &enable_scalar_block;
|
|
|
|
VkDeviceCreateInfo device_create_info = {};
|
|
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
device_create_info.queueCreateInfoCount = num_queues;
|
|
device_create_info.pQueueCreateInfos = queue_create_info;
|
|
device_create_info.enabledExtensionCount = uint32(required_device_extensions.size());
|
|
device_create_info.ppEnabledExtensionNames = required_device_extensions.data();
|
|
device_create_info.pNext = &enabled_features2;
|
|
device_create_info.pEnabledFeatures = nullptr;
|
|
|
|
VkDevice device = VK_NULL_HANDLE;
|
|
if (VKFailed(mVkCreateDevice(selected_device.mPhysicalDevice, &device_create_info, nullptr, &device), outResult))
|
|
return false;
|
|
|
|
// Store selected format
|
|
mSelectedFormat = selected_device.mFormat;
|
|
|
|
// Initialize the compute system (loads device-level functions)
|
|
if (!ComputeSystemVKWithAllocator::Initialize(mInstance, selected_device.mPhysicalDevice, mVkGetInstanceProcAddr, mVkGetDeviceProcAddr, device, selected_device.mComputeQueueIndex, outResult))
|
|
return false;
|
|
|
|
// Get the queues
|
|
mGraphicsQueueIndex = selected_device.mGraphicsQueueIndex;
|
|
mPresentQueueIndex = selected_device.mPresentQueueIndex;
|
|
mVkGetDeviceQueue(mDevice, mGraphicsQueueIndex, 0, &mGraphicsQueue);
|
|
mVkGetDeviceQueue(mDevice, mPresentQueueIndex, 0, &mPresentQueue);
|
|
|
|
return true;
|
|
}
|
|
|
|
ComputeSystemResult CreateComputeSystemVK()
|
|
{
|
|
ComputeSystemResult result;
|
|
|
|
Ref<ComputeSystemVKImpl> compute = new ComputeSystemVKImpl;
|
|
if (!compute->Initialize(result))
|
|
return result;
|
|
|
|
result.Set(compute.GetPtr());
|
|
return result;
|
|
}
|
|
|
|
JPH_NAMESPACE_END
|
|
|
|
#endif // JPH_USE_VK
|