summaryrefslogtreecommitdiff
path: root/main.c
diff options
context:
space:
mode:
Diffstat (limited to 'main.c')
-rw-r--r--main.c451
1 files changed, 428 insertions, 23 deletions
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 <stdio.h>
#include <stdlib.h>
+#include <stdint.h>
#include <vulkan/vulkan.h>
@@ -29,17 +45,105 @@ PERFORMANCE OF THIS SOFTWARE.
#include <vulkan/vulkan_xlib.h>
#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 <dlfcn.h>
+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;
}