Last active
July 19, 2024 13:53
-
-
Save romualdo97/77f413645187550beceb719f09deb5ca to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Windows | |
#include <windows.h> | |
#include <libloaderapi.h> | |
// Vulkan | |
#include <vulkan.h> | |
// Std | |
#include <iostream> | |
#include <vector> | |
// ************************************************************ // | |
// Exported functions // | |
// // | |
// These functions are always exposed by vulkan libraries. // | |
// ************************************************************ // | |
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; | |
// ************************************************************ // | |
// Global level functions // | |
// // | |
// They allow checking what instance extensions are available // | |
// and allow creation of a Vulkan Instance. // | |
// ************************************************************ // | |
PFN_vkCreateInstance vkCreateInstance; | |
PFN_vkEnumerateInstanceExtensionProperties vkEnumerateInstanceExtensionProperties; | |
PFN_vkEnumerateInstanceLayerProperties vkEnumerateInstanceLayerProperties; | |
// ************************************************************ // | |
// Instance level functions // | |
// // | |
// These functions allow for device queries and creation. // | |
// They help choose which device is well suited for our needs. // | |
// ************************************************************ // | |
PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; | |
PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; | |
PFN_vkGetPhysicalDeviceFeatures vkGetPhysicalDeviceFeatures; | |
PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; | |
PFN_vkCreateDevice vkCreateDevice; | |
PFN_vkGetDeviceProcAddr vkGetDeviceProcAddr; | |
PFN_vkDestroyInstance vkDestroyInstance; | |
// ************************************************************ // | |
// Device level functions // | |
// // | |
// These functions are used mainly for drawing // | |
// these functions could be loaded using vkGetInstanceProcAddr, // | |
// but this is in reality a dispatch function, that jumps to // | |
// a real device function, so is it preferable to load these // | |
// for each device using vkGetDeviceProcAddr // | |
// to avoid extra overhead // | |
// ************************************************************ // | |
PFN_vkGetDeviceQueue vkGetDeviceQueue; | |
PFN_vkDeviceWaitIdle vkDeviceWaitIdle; | |
PFN_vkDestroyDevice vkDestroyDevice; | |
// ************************************************************ // | |
// Main program // | |
// // | |
// Utilities and other functions using Vulkan // | |
// ************************************************************ // | |
bool LoadExportedAndGlobalLevelVulkanFunctions(); | |
bool CreateVulkanInstance(); | |
bool LoadInstanceLevelVulkanFunctions(); | |
bool SelectVulkanPhysicalDevice(); | |
bool CreateVulkanLogicalDevice(); | |
bool LoadDeviceLevelVulkanFunctions(); | |
bool SelectVulkanLogicalDeviceQueue(); | |
bool FreeResources(); | |
static HMODULE VulkanLibrary; | |
static VkInstance VulkanInstance; | |
static VkPhysicalDevice SelectedVulkanPhysicalDevice; | |
static uint32_t SelectedVulkanQueueFamilyIndex; | |
static VkDevice SelectedVulkanLogicalDevice; | |
static VkQueue SelectedVulkanDeviceQueue; | |
int main(int argc, char* argv[]) | |
{ | |
std::cout << "Hello World" << std::endl; | |
LoadExportedAndGlobalLevelVulkanFunctions(); | |
CreateVulkanInstance(); | |
LoadInstanceLevelVulkanFunctions(); | |
SelectVulkanPhysicalDevice(); | |
CreateVulkanLogicalDevice(); | |
LoadDeviceLevelVulkanFunctions(); | |
FreeResources(); | |
return 0; | |
} | |
bool LoadExportedAndGlobalLevelVulkanFunctions() | |
{ | |
// https://learn.microsoft.com/en-us/windows/win32/api/libloaderapi/nf-libloaderapi-loadlibrarya?source=docs | |
// Loads the specified module into the address space of the calling process. The specified module may cause other modules to be loaded. | |
VulkanLibrary = LoadLibrary(L"vulkan-1.dll"); | |
if (VulkanLibrary == nullptr) | |
{ | |
std::cerr << "Could not load Vulkan library!" << std::endl; | |
return false; | |
} | |
// 1. Only function exported in the vulkan dynamic library, it should be loaded using the respective SO function | |
vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)GetProcAddress(VulkanLibrary, "vkGetInstanceProcAddr"); | |
if (!vkGetInstanceProcAddr) | |
{ | |
std::cerr << "Failed to load vkGetInstanceProcAddr" << std::endl; | |
} | |
// 2. Load the global functions using the above function, these can be called null-instance or no-instance level functions soometimes | |
vkCreateInstance = (PFN_vkCreateInstance)vkGetInstanceProcAddr(nullptr, "vkCreateInstance"); | |
if (!vkCreateInstance) | |
{ | |
std::cerr << "Failed to load vkCreateInstance" << std::endl; | |
return false; | |
} | |
vkEnumerateInstanceExtensionProperties = (PFN_vkEnumerateInstanceExtensionProperties)vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceExtensionProperties"); | |
if (!vkEnumerateInstanceExtensionProperties) | |
{ | |
std::cerr << "Failed to load vkEnumerateInstanceExtensionProperties" << std::endl; | |
return false; | |
} | |
vkEnumerateInstanceLayerProperties = (PFN_vkEnumerateInstanceLayerProperties)vkGetInstanceProcAddr(nullptr, "vkEnumerateInstanceLayerProperties"); | |
if (!vkEnumerateInstanceLayerProperties) | |
{ | |
std::cerr << "Failed to load vkEnumerateInstanceLayerProperties" << std::endl; | |
return false; | |
} | |
return true; | |
} | |
bool CreateVulkanInstance() | |
{ | |
VkApplicationInfo ApplicationInfo | |
{ | |
VK_STRUCTURE_TYPE_APPLICATION_INFO, | |
nullptr, | |
"Api without secrets: Hello Vulkan", | |
VK_MAKE_VERSION(1, 0, 0), | |
"Romualdo Engine", | |
VK_MAKE_VERSION(1, 0, 0), | |
VK_API_VERSION_1_3 | |
}; | |
const VkInstanceCreateInfo InstanceCreateInfo | |
{ | |
VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, | |
nullptr, | |
0, | |
&ApplicationInfo, | |
0, | |
nullptr, | |
0, | |
nullptr | |
}; | |
if (vkCreateInstance(&InstanceCreateInfo, nullptr, &VulkanInstance) != VK_SUCCESS) | |
{ | |
std::cerr << "Failed to create vulkan instance" << std::endl; | |
return false; | |
} | |
return true; | |
} | |
bool LoadInstanceLevelVulkanFunctions() | |
{ | |
// Loading every Vulkan procedure using the vkGetInstanceProcAddr() function and Vulkan instance handle comes | |
// with some trade-offs. When we use Vulkan for data processing, we must create a logical device and acquire | |
// device-level functions. But on the computer that runs our application, there may be many devices that | |
// support Vulkan. Determining which device to use depends on the mentioned logical device. But vkGetInstanceProcAddr() | |
// doesn't recognize a logical device, as there is no parameter for it. When we acquire device-level procedures | |
// using this function we in fact acquire addresses of a simple "jump" functions. These functions take the handle of a logical device | |
// and jump to a proper implementation (function implemented for a specific device). The overhead of this jump can be avoided. | |
// The recommended behavior is to load procedures for each device separately using another function. But we still have to use the | |
// vkGetInstanceProcAddr() function to load functions that allow us to create such a logical device. | |
vkEnumeratePhysicalDevices = (PFN_vkEnumeratePhysicalDevices) vkGetInstanceProcAddr(VulkanInstance, "vkEnumeratePhysicalDevices"); | |
if (!vkEnumeratePhysicalDevices) | |
{ | |
std::cerr << "Failed to load vkEnumeratePhysicalDevices" << std::endl; | |
return false; | |
} | |
vkGetPhysicalDeviceProperties = (PFN_vkGetPhysicalDeviceProperties) vkGetInstanceProcAddr(VulkanInstance, "vkGetPhysicalDeviceProperties"); | |
if (!vkGetPhysicalDeviceProperties) | |
{ | |
std::cerr << "Failed to load vkGetPhysicalDeviceProperties" << std::endl; | |
return false; | |
} | |
vkGetPhysicalDeviceFeatures = (PFN_vkGetPhysicalDeviceFeatures) vkGetInstanceProcAddr(VulkanInstance, "vkGetPhysicalDeviceFeatures"); | |
if (!vkGetPhysicalDeviceFeatures) | |
{ | |
std::cerr << "Failed to load vkGetPhysicalDeviceFeatures" << std::endl; | |
return false; | |
} | |
vkGetPhysicalDeviceQueueFamilyProperties = (PFN_vkGetPhysicalDeviceQueueFamilyProperties) vkGetInstanceProcAddr(VulkanInstance, "vkGetPhysicalDeviceQueueFamilyProperties"); | |
if (!vkGetPhysicalDeviceQueueFamilyProperties) | |
{ | |
std::cerr << "Failed to load vkGetPhysicalDeviceQueueFamilyProperties" << std::endl; | |
return false; | |
} | |
vkCreateDevice = (PFN_vkCreateDevice) vkGetInstanceProcAddr(VulkanInstance, "vkCreateDevice"); | |
if (!vkCreateDevice) | |
{ | |
std::cerr << "Failed to load vkCreateDevice" << std::endl; | |
return false; | |
} | |
vkGetDeviceProcAddr = (PFN_vkGetDeviceProcAddr) vkGetInstanceProcAddr(VulkanInstance, "vkGetDeviceProcAddr"); | |
if (!vkGetDeviceProcAddr) | |
{ | |
std::cerr << "Failed to load vkGetDeviceProcAddr" << std::endl; | |
return false; | |
} | |
vkDestroyInstance = (PFN_vkDestroyInstance) vkGetInstanceProcAddr(VulkanInstance, "vkDestroyInstance"); | |
if (!vkDestroyInstance) | |
{ | |
std::cerr << "Failed to load vkDestroyInstance" << std::endl; | |
return false; | |
} | |
return true; | |
} | |
std::string QueueFlagsToString(const VkQueueFlags& QueueFlags) | |
{ | |
std::string Out; | |
if (QueueFlags & VK_QUEUE_GRAPHICS_BIT) | |
{ | |
Out += "VK_QUEUE_GRAPHICS_BIT, "; | |
} | |
if (QueueFlags & VK_QUEUE_COMPUTE_BIT) | |
{ | |
Out += "VK_QUEUE_COMPUTE_BIT, "; | |
} | |
if (QueueFlags & VK_QUEUE_TRANSFER_BIT) | |
{ | |
Out += "VK_QUEUE_TRANSFER_BIT, "; | |
} | |
if (QueueFlags & VK_QUEUE_SPARSE_BINDING_BIT) | |
{ | |
Out += "VK_QUEUE_SPARSE_BINDING_BIT, "; | |
} | |
if (QueueFlags & VK_QUEUE_VIDEO_DECODE_BIT_KHR) | |
{ | |
Out += "VK_QUEUE_VIDEO_DECODE_BIT_KHR, "; | |
} | |
if (QueueFlags & VK_QUEUE_VIDEO_ENCODE_BIT_KHR) | |
{ | |
Out += "VK_QUEUE_VIDEO_ENCODE_BIT_KHR, "; | |
} | |
if (QueueFlags & VK_QUEUE_OPTICAL_FLOW_BIT_NV) | |
{ | |
Out += "VK_QUEUE_OPTICAL_FLOW_BIT_NV, "; | |
} | |
if (QueueFlags & VK_QUEUE_PROTECTED_BIT) | |
{ | |
Out += "VK_QUEUE_PROTECTED_BIT, "; | |
} | |
return Out; | |
} | |
bool SelectVulkanPhysicalDevice() | |
{ | |
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/vkEnumeratePhysicalDevices.html | |
// If pPhysicalDevices is NULL, then the number of physical devices available is returned in pPhysicalDeviceCount. | |
// Otherwise, pPhysicalDeviceCount must point to a variable set by the application to the number of elements in | |
// the pPhysicalDevices array, and on return the variable is overwritten with the number of handles actually | |
// written to pPhysicalDevices. If pPhysicalDeviceCount is less than the number of physical devices available, | |
// at most pPhysicalDeviceCount structures will be written, and VK_INCOMPLETE will be returned instead of VK_SUCCESS, | |
// to indicate that not all the available physical devices were returned. | |
uint32_t NumDevices = 0; | |
if ((vkEnumeratePhysicalDevices(VulkanInstance, &NumDevices, nullptr) != VK_SUCCESS) || | |
(NumDevices == 0)) | |
{ | |
std::cerr << "Failed to query physical devices count" << std::endl; | |
return false; | |
} | |
std::cout << "Found " << NumDevices << " Physical devices" << std::endl; | |
std::vector<VkPhysicalDevice> PhysicalDevices{ NumDevices }; | |
if (vkEnumeratePhysicalDevices(VulkanInstance, &NumDevices, PhysicalDevices.data()) != VK_SUCCESS) | |
{ | |
std::cerr << "Failed to query physical devices" << std::endl; | |
return false; | |
} | |
for (const VkPhysicalDevice& PhysicalDevice : PhysicalDevices) | |
{ | |
VkPhysicalDeviceProperties PhysicalDeviceProperties; | |
vkGetPhysicalDeviceProperties(PhysicalDevice, &PhysicalDeviceProperties); | |
VkPhysicalDeviceFeatures PhysicalDeviceFeatures; | |
vkGetPhysicalDeviceFeatures(PhysicalDevice, &PhysicalDeviceFeatures); | |
std::cout << "PhysicalDeviceName: " << PhysicalDeviceProperties.deviceName << std::endl; | |
std::cout << "\tApiVersion: " << VK_VERSION_MAJOR(PhysicalDeviceProperties.apiVersion) << "." << VK_VERSION_MINOR(PhysicalDeviceProperties.apiVersion) << "." << VK_API_VERSION_PATCH(PhysicalDeviceProperties.apiVersion) << std::endl; | |
std::cout << "\tDriverVersion: " << VK_VERSION_MAJOR(PhysicalDeviceProperties.driverVersion) << "." << VK_VERSION_MINOR(PhysicalDeviceProperties.driverVersion) << "." << VK_API_VERSION_PATCH(PhysicalDeviceProperties.driverVersion) << std::endl; | |
std::cout << "\tVendorId: " << PhysicalDeviceProperties.vendorID << std::endl; | |
std::cout << "\tDeviceID: " << PhysicalDeviceProperties.deviceID << std::endl; | |
std::cout << "\tdeviceType: " << PhysicalDeviceProperties.deviceType << std::endl; | |
// Check desired features and properties | |
if ((VK_VERSION_MAJOR(PhysicalDeviceProperties.apiVersion) < 1) || | |
(PhysicalDeviceProperties.limits.maxImageDimension2D < 4096)) | |
{ | |
std::cerr << "Physical devices misses expected properties"; | |
continue; | |
} | |
// Check queue family features | |
uint32_t QueueFamilyPropertyCount = 0; | |
vkGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &QueueFamilyPropertyCount, nullptr); | |
if (QueueFamilyPropertyCount == 0) | |
{ | |
std::cerr << "Failed to enumerate Queue Family Properties" << std::endl; | |
return false; | |
} | |
bool bHasQueueFamilyForGraphics = false; | |
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkQueueFamilyProperties.html | |
uint32_t CurrentQueueFamilyIndex = SelectedVulkanQueueFamilyIndex = -1; | |
std::vector<VkQueueFamilyProperties> QueueFamiliesProperties{ QueueFamilyPropertyCount }; | |
vkGetPhysicalDeviceQueueFamilyProperties(PhysicalDevice, &QueueFamilyPropertyCount, QueueFamiliesProperties.data()); | |
for (const VkQueueFamilyProperties& QueueFamilyProperty : QueueFamiliesProperties) | |
{ | |
++CurrentQueueFamilyIndex; | |
std::cout << "\tQueue Family Properties" << std::endl; | |
std::cout << "\t\tQueueCount: " << QueueFamilyProperty.queueCount << std::endl; | |
std::cout << "\t\tQueueFlags: " << QueueFlagsToString(QueueFamilyProperty.queueFlags) << std::endl; | |
std::cout << "\t\tMinImageTransferGranularity: " << "(" << QueueFamilyProperty.minImageTransferGranularity.width << ", " << QueueFamilyProperty.minImageTransferGranularity.height << ", " << QueueFamilyProperty.minImageTransferGranularity.depth << ")" << std::endl; | |
if (QueueFamilyProperty.queueCount > 0 && QueueFamilyProperty.queueFlags & VK_QUEUE_GRAPHICS_BIT) | |
{ | |
SelectedVulkanQueueFamilyIndex = CurrentQueueFamilyIndex; | |
bHasQueueFamilyForGraphics = true; | |
std::cout << "\t\tSelected Queue Family identified with index " << SelectedVulkanQueueFamilyIndex << std::endl; | |
} | |
} | |
if (bHasQueueFamilyForGraphics) | |
{ | |
SelectedVulkanPhysicalDevice = PhysicalDevice; | |
return true; | |
} | |
else | |
{ | |
std::cerr << "Did not find a Queue Family that supports graphics operations" << std::endl; | |
return false; | |
} | |
} | |
return false; | |
} | |
bool CreateVulkanLogicalDevice() | |
{ | |
if (SelectedVulkanPhysicalDevice == VK_NULL_HANDLE || SelectedVulkanQueueFamilyIndex < 0) | |
{ | |
std::cerr << "Cannot crete a logical device because a physical device or queue family were not selected" << std::endl; | |
return false; | |
} | |
// Only one Queue, even though we could use up to VkQueueFamilyProperties::queueCount | |
std::vector<float> QueuePriorities = { 1.0f }; | |
// How many queues in this QueueFamily? | |
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDeviceQueueCreateInfo.html | |
VkDeviceQueueCreateInfo DeviceQueueCreateInfo | |
{ | |
VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, | |
nullptr, | |
0, | |
SelectedVulkanQueueFamilyIndex, | |
static_cast<uint32_t>(QueuePriorities.size()), | |
QueuePriorities.data() | |
}; | |
// What QueueFamilies will be available in this logical device? | |
// https://registry.khronos.org/vulkan/specs/1.3-extensions/man/html/VkDeviceCreateInfo.html | |
const VkDeviceCreateInfo DeviceCrateInfo | |
{ | |
VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, | |
nullptr, | |
0, | |
1, | |
&DeviceQueueCreateInfo, | |
false, | |
nullptr, | |
0, | |
nullptr, | |
nullptr | |
}; | |
if (vkCreateDevice(SelectedVulkanPhysicalDevice, &DeviceCrateInfo, nullptr, &SelectedVulkanLogicalDevice) != VK_SUCCESS) | |
{ | |
std::cerr << "Failed to create logical device" << std::endl; | |
return false; | |
} | |
return true; | |
} | |
bool LoadDeviceLevelVulkanFunctions() | |
{ | |
vkGetDeviceQueue = (PFN_vkGetDeviceQueue) vkGetDeviceProcAddr(SelectedVulkanLogicalDevice, "vkGetDeviceQueue"); | |
if (!vkGetDeviceQueue) | |
{ | |
std::cerr << "Failed to load vkGetDeviceQueue" << std::endl; | |
return false; | |
} | |
vkDeviceWaitIdle = (PFN_vkDeviceWaitIdle) vkGetDeviceProcAddr(SelectedVulkanLogicalDevice, "vkDeviceWaitIdle"); | |
if (!vkDeviceWaitIdle) | |
{ | |
std::cerr << "Failed to load vkDeviceWaitIdle" << std::endl; | |
return false; | |
} | |
vkDestroyDevice = (PFN_vkDestroyDevice) vkGetDeviceProcAddr(SelectedVulkanLogicalDevice, "vkDestroyDevice"); | |
if (!vkDestroyDevice) | |
{ | |
std::cerr << "Failed to load vkDestroyDevice" << std::endl; | |
return false; | |
} | |
return true; | |
} | |
bool SelectVulkanLogicalDeviceQueue() | |
{ | |
vkGetDeviceQueue(SelectedVulkanLogicalDevice, SelectedVulkanQueueFamilyIndex, 0, &SelectedVulkanDeviceQueue); | |
if (SelectedVulkanDeviceQueue == VK_NULL_HANDLE) | |
{ | |
std::cerr << "Failed to get Queue from Logical Device" << std::endl; | |
return false; | |
} | |
return true; | |
} | |
bool FreeResources() | |
{ | |
if (SelectedVulkanLogicalDevice != VK_NULL_HANDLE) | |
{ | |
vkDeviceWaitIdle(SelectedVulkanLogicalDevice); | |
vkDestroyDevice(SelectedVulkanLogicalDevice, nullptr); | |
} | |
if (VulkanInstance != VK_NULL_HANDLE) | |
{ | |
vkDestroyInstance(VulkanInstance, nullptr); | |
} | |
if (VulkanLibrary) | |
{ | |
FreeLibrary(VulkanLibrary); | |
} | |
return true; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Simple hello vulkan program that loads vulkan functions from dynamic library, creates an instance and queries properties, functions are loaded without any macro for illustrative purposes
Inspired from API Without secrets tutorial
https://www.intel.com/content/www/us/en/developer/articles/training/api-without-secrets-introduction-to-vulkan-part-1.html