From b7bfbfe3216bb9aacd6df19f117fff8192ba18d5 Mon Sep 17 00:00:00 2001 From: Jordan Halase Date: Mon, 28 Oct 2019 17:02:57 -0500 Subject: Create instance and select physical device --- main.c | 451 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 428 insertions(+), 23 deletions(-) (limited to 'main.c') diff --git a/main.c b/main.c index cf59f16..5bdedf3 100644 --- a/main.c +++ b/main.c @@ -14,10 +14,26 @@ OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ -// TODO: Create debug report callback +/* Note: Due to the length of many function and variable names in the + * Vulkan API, the line width of any Vulkan-related code here may be + * extended from 80 columns to 120 colums to improve readability. + */ + +/* The goal of this project is to create an entirely self-contained, embeddable + * Vulkan renderer. While dynamic linking directly against the system Vulkan + * loader is safe, it will crash if attempting to run on a system without it + * installed. By loading the Vulkan library dynamically, the application may + * gracefully close or even fall back to a different rendering API. + * Furthermore, Vulkan specifies that using functions retrieved via + * `vkGetDeviceProcAddr` may actually run faster than if the application were + * using the Vulkan loader directly due to bypassing the loader terminator. + * + * https://vulkan.lunarg.com/doc/sdk/1.0.61.1/windows/LoaderAndLayerInterface.html + */ #include #include +#include #include @@ -29,17 +45,105 @@ PERFORMANCE OF THIS SOFTWARE. #include #endif +#define STB_SPRINTF_IMPLEMENTATION +#include "stb_sprintf.h" + +#if defined(__GNUC__) +#define APP_THREAD_LOCAL __thread +#else +#define APP_THREAD_LOCAL __declspec(thread) +#endif + +/** Vulkan allocation callbacks if we ever decide to use them when debugging. + * + * This is put in a macro so we don't have to look for every Vulkan function + * that uses it. + */ +#define ALLOC_VK NULL + +struct VulkanAPI { + void *handle; + PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr; + PFN_vkCreateInstance vkCreateInstance; + PFN_vkDestroyInstance vkDestroyInstance; + PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT; + PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallbackEXT; + PFN_vkEnumeratePhysicalDevices vkEnumeratePhysicalDevices; + PFN_vkGetPhysicalDeviceProperties vkGetPhysicalDeviceProperties; + PFN_vkGetPhysicalDeviceQueueFamilyProperties vkGetPhysicalDeviceQueueFamilyProperties; + PFN_vkGetPhysicalDeviceSurfaceSupportKHR vkGetPhysicalDeviceSurfaceSupportKHR; + PFN_vkGetPhysicalDeviceMemoryProperties vkGetPhysicalDeviceMemoryProperties; + PFN_vkEnumerateDeviceExtensionProperties vkEnumerateDeviceExtensionProperties; +}; + struct RenderVulkan { - /** Put `device` first in struct to reduce pointer arithmetic on most Vulkan functions */ - VkDevice device; + struct VulkanAPI *api; + char *errMsg; VkInstance instance; + VkDebugReportCallbackEXT debugCallback; + VkSurfaceKHR surface; + uint32_t graphicsIndex; + VkDevice device; }; struct RenderVulkan *vk; +#define ERRMSG_LEN 4096 + +void setErrMsg(struct RenderVulkan *vk, const char *fmt, ...) +{ + if (!vk->errMsg) { + vk->errMsg = calloc(1, ERRMSG_LEN); + } + va_list args; + va_start(args, fmt); + stbsp_vsnprintf(vk->errMsg, ERRMSG_LEN, fmt, args); + va_end(args); +} + +void clearErrMsg(struct RenderVulkan *vk) +{ + if (vk->errMsg) { + free(vk->errMsg); + } +} + +const char *getErrMsg(struct RenderVulkan *vk) +{ + return vk->errMsg; +} + #if defined(_WIN32) -void getRequiredInstanceExtensions(unsigned *nRequired, const char **extensions) +#define VULKAN_SONAME_LATEST "vulkan-1.dll" +void *appDlopen(const char *soname) +{ + return LoadLibraryA(soname); +} + +char *appDlerror() +{ + DWORD errCode = GetLastError(); + static APP_THREAD_LOCAL char errStr[64]; + stbsp_sprintf(errStr, "Dynamic Library Error: %d", errCode); + return errStr; +} + +void *appDlsym(void *handle, const char *symbol) +{ + const uintptr_t ulAddr = (uintptr_t)GetProcAddress(handle, symbol); + return (void*)ulAddr; +} + +int appDlclose(void *handle) +{ + return FreeLibrary(handle); +} + +void getRequiredInstanceExtensions(void *windowCtx, + unsigned *nRequired, + const char **const extensions) { + (void)windowCtx; static const char *const required[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_WIN32_SURFACE_EXTENSION_NAME @@ -54,8 +158,38 @@ void getRequiredInstanceExtensions(unsigned *nRequired, const char **extensions) } } #else -void getRequiredInstanceExtensions(unsigned *nRequired, const char **const extensions) +#define VULKAN_SONAME_LATEST "libvulkan.so.1" +#include +void *appDlopen(const char *soname) { + return dlopen(soname, RTLD_NOW); +} + +char *appDlerror() +{ + return dlerror(); +} + +void *appDlsym(void *handle, const char *symbol) +{ + return dlsym(handle, symbol); +} + +int appDlclose(void *handle) +{ + return dlclose(handle); +} + +/* TODO: Linux actually has three possible surfaces: Xlib, XCB, and Wayland. + * This should be figured out at runtime (without using #ifdefs). + * In which case, `windowCtx` will be used to determine which to use. + * As of now, Pugl (and LV2!) only supports Xlib. + */ +void getRequiredInstanceExtensions(void *windowCtx, + unsigned *nRequired, + const char **const extensions) +{ + (void)windowCtx; static const char *const required[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_XLIB_SURFACE_EXTENSION_NAME @@ -71,9 +205,46 @@ void getRequiredInstanceExtensions(unsigned *nRequired, const char **const exten } #endif -void createInstance(struct RenderVulkan *vk, - const uint32_t nLayers, const char *const *layers, - const uint32_t nAdditional, const char *const *additionalExtensions) +void *loadVulkanLibrary(const char *prefix) +{ + (void)prefix; // TODO + void *handle = appDlopen(VULKAN_SONAME_LATEST); + return handle; +} + +void loadVulkanGetInstanceProcAddrFunc(void *handle, + PFN_vkGetInstanceProcAddr *getInstanceProcAddrFunc) +{ + uintptr_t *ulGetInstanceProcAddrFunc = (uintptr_t*)getInstanceProcAddrFunc; + *ulGetInstanceProcAddrFunc = (uintptr_t)appDlsym(handle, "vkGetInstanceProcAddr"); +} + +int unloadVulkanLibrary(void *handle) +{ + if (handle) { + return appDlclose(handle); + } + return 0; +} + +static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( + VkDebugReportFlagsEXT flags, + VkDebugReportObjectTypeEXT objType, + uint64_t obj, + size_t location, + int32_t code, + const char *layerPrefix, + const char *msg, + void *userData + ) +{ + fprintf(stderr, "\nValidation layer:\n%s\n\n", msg); + return VK_FALSE; +} + +VkResult createInstance(struct RenderVulkan *vk, + const uint32_t nLayers, const char *const *const layers, + const uint32_t nAdditional, const char *const *const additionalExtensions) { VkApplicationInfo appInfo = { 0 }; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; @@ -81,15 +252,16 @@ void createInstance(struct RenderVulkan *vk, appInfo.applicationVersion = VK_MAKE_VERSION(0, 1, 0); appInfo.pEngineName = "Pugl Vulkan Test Engine"; appInfo.engineVersion = VK_MAKE_VERSION(0, 1, 0); - appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); // MoltenVK for macOS only supports Vulkan 1.0 + /* MoltenVK for macOS currently only supports Vulkan 1.0 */ + appInfo.apiVersion = VK_MAKE_VERSION(1, 0, 0); unsigned i, j, nRequired; - getRequiredInstanceExtensions(&nRequired, NULL); + getRequiredInstanceExtensions(NULL, &nRequired, NULL); const uint32_t nExtensions = nRequired + nAdditional; const char **const extensions = malloc(sizeof(const char*) * nExtensions); - getRequiredInstanceExtensions(NULL, extensions); + getRequiredInstanceExtensions(NULL, NULL, extensions); for (i = nRequired, j = 0; i < nExtensions; ++i, ++j) { extensions[i] = additionalExtensions[j]; } @@ -99,7 +271,7 @@ void createInstance(struct RenderVulkan *vk, } for (i = 0; i < nLayers; ++i) { - printf("Using instance layer:\t%s\n", layers[i]); + printf("Using instance layer:\t\t%s\n", layers[i]); } VkInstanceCreateInfo createInfo = { 0 }; @@ -110,21 +282,46 @@ void createInstance(struct RenderVulkan *vk, createInfo.enabledExtensionCount = nExtensions; createInfo.ppEnabledExtensionNames = extensions; - VkResult result; - if ((result = vkCreateInstance(&createInfo, NULL, &vk->instance))) { - exit(result); + VkResult result = VK_SUCCESS; + if ((result = vk->api->vkCreateInstance(&createInfo, ALLOC_VK, &vk->instance))) { + setErrMsg(vk, "Could not create Vulkan Instance: %d\n", result); } free(extensions); + return result; } /** Must not be called until all derivative objects are destroyed first */ void destroyInstance(struct RenderVulkan *vk) { - vkDestroyInstance(vk->instance, NULL); - vk->instance = NULL; + vk->api->vkDestroyInstance(vk->instance, ALLOC_VK); + vk->instance = VK_NULL_HANDLE; } -int main() +/** This must work no matter the current state of `vk` */ +void renderVulkanDestroy(struct RenderVulkan *vk) +{ + if (vk) { + if (vk->debugCallback) { + /* `vk->debugCallback` implies `vk->api` and instance functions loaded */ + vk->api->vkDestroyDebugReportCallbackEXT(vk->instance, vk->debugCallback, ALLOC_VK); + } + if (vk->instance) destroyInstance(vk); + if (vk->api) { + if (vk->api->handle) unloadVulkanLibrary(vk->api->handle); + free(vk->api); + } + if (vk->errMsg) free(vk->errMsg); + if (vk) free(vk); + } +} + +/** Create a self-contained Vulkan instance and set up a debug reporter + * + * If errors occurred, the struct will be returned in an unusable state, + * and MUST be checked via `getErrMsg`. It MUST then be destroyed via + * `renderVulkanDestroy`. + * */ +struct RenderVulkan *renderVulkanCreate() { static const char *const instanceLayers[] = { "VK_LAYER_LUNARG_standard_validation" @@ -135,12 +332,220 @@ int main() }; const uint32_t nInstanceExtensions = sizeof(instanceExtensions) / sizeof(instanceExtensions[0]); - vk = malloc(sizeof(*vk)); - createInstance(vk, nInstanceLayers, instanceLayers, nInstanceExtensions, instanceExtensions); + struct RenderVulkan *vk = calloc(1, sizeof(*vk)); + vk->api = calloc(1, sizeof(*vk->api)); + + vk->api->handle = loadVulkanLibrary(NULL); + if (!vk->api->handle) { + setErrMsg(vk, "Error loading Vulkan shared library:\n%s\n", appDlerror()); + return vk; + } + + loadVulkanGetInstanceProcAddrFunc(vk->api->handle, &vk->api->vkGetInstanceProcAddr); + if (!vk->api->vkGetInstanceProcAddr) { + setErrMsg(vk, "Error loading `vkGetInstanceProcAddr`:\n%s", appDlerror()); + return vk; + } + + uintptr_t *const ulCreateInstance = (uintptr_t*)&vk->api->vkCreateInstance; + *ulCreateInstance = (uintptr_t)vk->api->vkGetInstanceProcAddr(NULL, "vkCreateInstance"); + if (!vk->api->vkCreateInstance) { + setErrMsg(vk, "Error loading `vkCreateInstance`"); + return vk; + } + + VkResult result; + //if ((result = createInstance(vk, 0, NULL, 0, NULL))) { + if ((result = createInstance(vk, nInstanceLayers, instanceLayers, nInstanceExtensions, instanceExtensions))) { + return vk; + } + + /* TODO: This could perhaps be generated */ + static const char *const strErrLd = "Error loading function %s"; + + static const char *const strDestroyInstance = "vkDestroyInstance"; + uintptr_t *const ulDestroyInstance = (uintptr_t*)&vk->api->vkDestroyInstance; + *ulDestroyInstance = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strDestroyInstance); + if (!vk->api->vkDestroyInstance) { + setErrMsg(vk, strErrLd, strDestroyInstance); + return vk; + } + + /* It is okay if debug reporter functions are not resolved */ + uintptr_t *ulCreateDebugReportCallbackEXT = (uintptr_t*)&vk->api->vkCreateDebugReportCallbackEXT; + *ulCreateDebugReportCallbackEXT = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, "vkCreateDebugReportCallbackEXT"); + + uintptr_t *ulDestroyDebugReportCallbackEXT = (uintptr_t*)&vk->api->vkDestroyDebugReportCallbackEXT; + *ulDestroyDebugReportCallbackEXT = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, "vkDestroyDebugReportCallbackEXT"); + + /* But not if we are unable to destroy a created debug reporter */ + if (vk->api->vkCreateDebugReportCallbackEXT && !vk->api->vkDestroyDebugReportCallbackEXT) { + setErrMsg(vk, "No debug reporter destroy function loaded for corresponding create function\n"); + return vk; + } + + static const char *const strEnumeratePhysicalDevices = "vkEnumeratePhysicalDevices"; + uintptr_t *ulEnumeratePhysicalDevices = (uintptr_t*)&vk->api->vkEnumeratePhysicalDevices; + *ulEnumeratePhysicalDevices = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strEnumeratePhysicalDevices); + if (!vk->api->vkEnumeratePhysicalDevices) { + setErrMsg(vk, strErrLd, strEnumeratePhysicalDevices); + return vk; + } + + static const char *const strGetPhysicalDeviceProperties = "vkGetPhysicalDeviceProperties"; + uintptr_t *ulGetPhysicalDeviceProperties = (uintptr_t*)&vk->api->vkGetPhysicalDeviceProperties; + *ulGetPhysicalDeviceProperties = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strGetPhysicalDeviceProperties); + if (!vk->api->vkGetPhysicalDeviceProperties) { + setErrMsg(vk, strErrLd, strGetPhysicalDeviceProperties); + return vk; + } + + static const char *const strGetPhysicalDeviceQueueFamilyProperties = "vkGetPhysicalDeviceQueueFamilyProperties"; + uintptr_t *ulGetPhysicalDeviceQueueFamilyProperties = (uintptr_t*)&vk->api->vkGetPhysicalDeviceQueueFamilyProperties; + *ulGetPhysicalDeviceQueueFamilyProperties = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strGetPhysicalDeviceQueueFamilyProperties); + if (!vk->api->vkGetPhysicalDeviceQueueFamilyProperties) { + setErrMsg(vk, strErrLd, strGetPhysicalDeviceQueueFamilyProperties); + return vk; + } + + static const char *const strGetPhysicalDeviceSurfaceSupportKHR = "vkGetPhysicalDeviceSurfaceSupportKHR"; + uintptr_t *ulGetPhysicalDeviceSurfaceSupportKHR = (uintptr_t*)&vk->api->vkGetPhysicalDeviceSurfaceSupportKHR; + *ulGetPhysicalDeviceSurfaceSupportKHR = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strGetPhysicalDeviceSurfaceSupportKHR); + if (!vk->api->vkGetPhysicalDeviceSurfaceSupportKHR) { + setErrMsg(vk, strErrLd, strGetPhysicalDeviceSurfaceSupportKHR); + return vk; + } + + static const char *const strGetPhysicalDeviceMemoryProperties = "vkGetPhysicalDeviceMemoryProperties"; + uintptr_t *ulGetPhysicalDeviceMemoryProperties = (uintptr_t*)&vk->api->vkGetPhysicalDeviceMemoryProperties; + *ulGetPhysicalDeviceMemoryProperties = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strGetPhysicalDeviceMemoryProperties); + if (!vk->api->vkGetPhysicalDeviceMemoryProperties) { + setErrMsg(vk, strErrLd, strGetPhysicalDeviceMemoryProperties); + return vk; + } + + static const char *const strEnumerateDeviceExtensionProperties = "vkEnumerateDeviceExtensionProperties"; + uintptr_t *ulEnumerateDeviceExtensionProperties = (uintptr_t*)&vk->api->vkEnumerateDeviceExtensionProperties; + *ulEnumerateDeviceExtensionProperties = (uintptr_t)vk->api->vkGetInstanceProcAddr(vk->instance, strEnumerateDeviceExtensionProperties); + if (!vk->api->vkEnumerateDeviceExtensionProperties) { + setErrMsg(vk, strErrLd, strEnumerateDeviceExtensionProperties); + return vk; + } + + if (vk->api->vkCreateDebugReportCallbackEXT) { + VkDebugReportCallbackCreateInfoEXT debugInfo = { 0 }; + debugInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; + debugInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; + debugInfo.pfnCallback = debugCallback; + if ((result = vk->api->vkCreateDebugReportCallbackEXT(vk->instance, + &debugInfo, ALLOC_VK, &vk->debugCallback))) { + setErrMsg(vk, "Could not create debug reporter: %d", result); + return vk; + } + } + + return vk; +} + +int isDeviceSuitable(struct RenderVulkan *vk, VkPhysicalDevice pd) +{ + uint32_t nQueueFamilies; + vk->api->vkGetPhysicalDeviceQueueFamilyProperties(pd, &nQueueFamilies, NULL); + VkQueueFamilyProperties *queueProperties = malloc(nQueueFamilies * sizeof(*queueProperties)); + vk->api->vkGetPhysicalDeviceQueueFamilyProperties(pd, &nQueueFamilies, queueProperties); + for (uint32_t i = 0; i < nQueueFamilies; ++i) { + printf("Queue Family %d queueCount:\t%d\n", i, queueProperties[i].queueCount); + } + uint32_t graphicsIndex; + for (graphicsIndex = 0; graphicsIndex < nQueueFamilies; ++graphicsIndex) { + if (queueProperties[graphicsIndex].queueFlags & VK_QUEUE_GRAPHICS_BIT) { + VkBool32 canSurface; + vk->api->vkGetPhysicalDeviceSurfaceSupportKHR(pd, graphicsIndex, vk->surface, &canSurface); + if (canSurface) break; + } + } + if (graphicsIndex >= nQueueFamilies) { + /* Some esoteric devices may have separate graphics and present queue families, or none at all. + * An example would be a device without any video output plugs. + * We only support graphics and present on the same queue family. + */ + return 0; + } + vk->graphicsIndex = graphicsIndex; + return 1; +} + +void selectPhysicalDevice(struct RenderVulkan *vk) +{ + if (!vk->surface) { + setErrMsg(vk, "Cannot select a physical device without a surface"); + return; + } + static const char *const strErrEnum = "Could not enumerate physical devices: %s"; + static const char *const strWarnEnum = "Warn: Incomplete enumeration of physical devices"; + uint32_t nDevices; + VkResult result; + if ((result = vk->api->vkEnumeratePhysicalDevices(vk->instance, &nDevices, NULL))) { + if (result != VK_INCOMPLETE) { + setErrMsg(vk, strErrEnum, result); + return; + } else { + fprintf(stderr, "%s\n", strWarnEnum); + } + } + if (!nDevices) { + setErrMsg(vk, "No physical devices found"); + return; + } + VkPhysicalDevice *devices = malloc(nDevices * sizeof(*devices)); + if ((result = vk->api->vkEnumeratePhysicalDevices(vk->instance, &nDevices, devices))) { + if (result != VK_INCOMPLETE) { + setErrMsg(vk, strErrEnum, result); + goto done; + } else { + fprintf(stderr, "%s\n", strWarnEnum); + } + } + + for (uint32_t i = 0; i < nDevices; ++i) { + VkPhysicalDeviceProperties deviceProperties; + vk->api->vkGetPhysicalDeviceProperties(devices[i], &deviceProperties); + printf("Found physical device:\t`%s`\n", deviceProperties.deviceName); + } + for (uint32_t i = 0; i < nDevices; ++i) { + VkPhysicalDeviceProperties deviceProperties; + vk->api->vkGetPhysicalDeviceProperties(devices[i], &deviceProperties); + printf("Checking suitability for `%s`...\n", deviceProperties.deviceName); + if (isDeviceSuitable(vk, devices[i])) { + printf("Using physical device:\t`%s`\n", deviceProperties.deviceName); + break; + } + printf("Device `%s` not suitable\n", deviceProperties.deviceName); + } +done: + free(devices); +} + +int main() +{ + const char *errMsg = NULL; + struct RenderVulkan *vk = renderVulkanCreate(); + if ((errMsg = getErrMsg(vk))) { + fprintf(stderr, "%s\n", errMsg); + renderVulkanDestroy(vk); + return 1; + } + printf("Created Vulkan Instance Successfully\n"); - destroyInstance(vk); - free(vk); - vk = NULL; + + selectPhysicalDevice(vk); + if ((errMsg = getErrMsg(vk))) { + fprintf(stderr, "%s\n", errMsg); + renderVulkanDestroy(vk); + return 1; + } + + renderVulkanDestroy(vk); return 0; } -- cgit v1.2.1