summaryrefslogtreecommitdiff
path: root/engine/render/vulkan
diff options
context:
space:
mode:
Diffstat (limited to 'engine/render/vulkan')
-rw-r--r--engine/render/vulkan/context.c217
-rw-r--r--engine/render/vulkan/context.h32
-rw-r--r--engine/render/vulkan/device.c324
-rw-r--r--engine/render/vulkan/device.h34
-rw-r--r--engine/render/vulkan/fence.c84
-rw-r--r--engine/render/vulkan/fence.h33
-rw-r--r--engine/render/vulkan/framebuffer.c55
-rw-r--r--engine/render/vulkan/framebuffer.h30
-rw-r--r--engine/render/vulkan/image.c99
-rw-r--r--engine/render/vulkan/image.h30
-rw-r--r--engine/render/vulkan/renderer.c279
-rw-r--r--engine/render/vulkan/renderpass.c249
-rw-r--r--engine/render/vulkan/renderpass.h49
-rw-r--r--engine/render/vulkan/swapchain.c152
-rw-r--r--engine/render/vulkan/swapchain.h33
-rw-r--r--engine/render/vulkan/vk_types.h143
-rw-r--r--engine/render/vulkan/vkassert.h68
17 files changed, 1911 insertions, 0 deletions
diff --git a/engine/render/vulkan/context.c b/engine/render/vulkan/context.c
new file mode 100644
index 0000000..1282706
--- /dev/null
+++ b/engine/render/vulkan/context.c
@@ -0,0 +1,217 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "context.h"
+#include "vkassert.h"
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+#include <rune/core/config.h>
+#include <stdlib.h>
+#include <string.h>
+
+VKAPI_ATTR VkBool32 VKAPI_CALL _vulkan_db_callback(
+ VkDebugUtilsMessageSeverityFlagBitsEXT message_severity,
+ VkDebugUtilsMessageTypeFlagsEXT message_types,
+ const VkDebugUtilsMessengerCallbackDataEXT *callback_data,
+ void *user_data) {
+ switch (message_severity) {
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT:
+ log_output(LOG_ERROR, callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT:
+ log_output(LOG_WARN, callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT:
+ log_output(LOG_INFO, callback_data->pMessage);
+ break;
+ case VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT:
+ log_output(LOG_DEBUG, callback_data->pMessage);
+ break;
+ default:
+ break;
+ }
+ return VK_FALSE;
+}
+
+int _init_vkdebugger(vkcontext_t *context) {
+ log_output(LOG_INFO, "Initializing Vulkan debugger");
+
+ VkDebugUtilsMessengerCreateInfoEXT dbinfo;
+ dbinfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
+ dbinfo.pNext = NULL;
+ dbinfo.flags = 0;
+ dbinfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_INFO_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT;
+ dbinfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT;
+ dbinfo.pfnUserCallback = _vulkan_db_callback;
+ dbinfo.pUserData = NULL;
+
+ PFN_vkCreateDebugUtilsMessengerEXT func =
+ (PFN_vkCreateDebugUtilsMessengerEXT)
+ vkGetInstanceProcAddr(context->instance,
+ "vkCreateDebugUtilsMessengerEXT");
+
+ if (func == NULL) {
+ log_output(LOG_ERROR, "Failed to create Vulkan debugger");
+ return -1;
+ }
+
+ VkResult res;
+ res = func(context->instance, &dbinfo, NULL, &context->db_messenger);
+ if (res != VK_SUCCESS) {
+ char *err_str = get_vkerr_str(res);
+ log_output(LOG_ERROR, "Debug session error: %s", err_str);
+ free(err_str);
+ return -1;
+ }
+ return 0;
+}
+
+uint32_t _vertoi(const char *version) {
+ uint32_t major = 0;
+ uint32_t minor = 0;
+ uint32_t patch = 0;
+ char *version_copy = strdup(version);
+ char *token = strtok(version_copy, ".");
+ if (token != NULL)
+ major = (uint32_t)atoi(token);
+
+ token = strtok(NULL, ".");
+ if (token != NULL)
+ minor = (uint32_t)atoi(token);
+
+ token = strtok(NULL, ".");
+ if (token != NULL)
+ patch = (uint32_t)atoi(token);
+
+ free(version_copy);
+ return (major << 22) | (minor << 12) | patch;
+}
+
+vkcontext_t* create_vkcontext(vklayer_container_t *vklayers, ext_container_t *ext) {
+ vkcontext_t *ret = rune_calloc(0, sizeof(vkcontext_t));
+ ret->surface = rune_alloc(sizeof(vksurface_t));
+
+ VkApplicationInfo app_info;
+ app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
+ app_info.pNext = NULL;
+ app_info.apiVersion = VK_API_VERSION_1_2;
+ app_info.pApplicationName = rune_get_app_name();
+ const char *app_ver = rune_get_app_ver();
+ app_info.applicationVersion = _vertoi(app_ver);
+ app_info.pEngineName = "RuneEngine";
+ app_info.engineVersion = _vertoi(RUNE_VER);
+
+ VkInstanceCreateInfo cinfo;
+ cinfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
+ cinfo.pNext = NULL;
+ cinfo.flags = 0;
+ cinfo.pApplicationInfo = &app_info;
+ cinfo.enabledExtensionCount = ext->ext_count;
+ cinfo.ppEnabledExtensionNames = ext->extensions;
+ cinfo.pNext = NULL;
+
+ if (vklayers != NULL) {
+ log_output(LOG_INFO, "Validation layers enabled");
+ cinfo.enabledLayerCount = vklayers->vklayer_count;
+ cinfo.ppEnabledLayerNames = vklayers->vklayer_names;
+ } else {
+ log_output(LOG_INFO, "Validation layers disabled");
+ cinfo.enabledLayerCount = 0;
+ cinfo.ppEnabledLayerNames = NULL;
+ }
+
+ VkResult res;
+ res = vkCreateInstance(&cinfo, NULL, &ret->instance);
+ if (res != VK_SUCCESS) {
+ char *err_str = get_vkerr_str(res);
+ log_output(LOG_FATAL, "Cannot create a Vulkan instance: %s", err_str);
+ rune_abort();
+ }
+
+ if (vklayers != NULL)
+ _init_vkdebugger(ret);
+
+ return ret;
+}
+
+void destroy_vkcontext(vkcontext_t *context) {
+ log_output(LOG_DEBUG, "Closing Vulkan instance");
+ if (rune_get_vk_debug() == 1) {
+ PFN_vkDestroyDebugUtilsMessengerEXT func =
+ (PFN_vkDestroyDebugUtilsMessengerEXT)
+ vkGetInstanceProcAddr(context->instance,
+ "vkDestroyDebugUtilsMessengerEXT");
+ func(context->instance, context->db_messenger, NULL);
+ }
+ vkDestroySurfaceKHR(context->instance, context->surface->handle, NULL);
+ vkDestroyInstance(context->instance, NULL);
+ rune_free(context->surface);
+ rune_free(context);
+}
+
+vklayer_container_t* init_vklayers(ext_container_t *ext) {
+ ext->ext_count++;
+ const char** new_extensions = rune_alloc(sizeof(char*) * ext->ext_count);
+ if (new_extensions == NULL) {
+ log_output(LOG_FATAL, "Cannot allocate memory for debug extensions");
+ rune_abort();
+ }
+
+ for (uint32_t i = 0; i < ext->ext_count-1; i++)
+ new_extensions[i] = ext->extensions[i];
+ new_extensions[ext->ext_count-1] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
+ ext->extensions = new_extensions;
+
+ uint32_t layer_count;
+ vkEnumerateInstanceLayerProperties(&layer_count, NULL);
+ VkLayerProperties layer_props[layer_count];
+ vkEnumerateInstanceLayerProperties(&layer_count, layer_props);
+
+ vklayer_container_t *ret = rune_alloc(sizeof(vklayer_container_t));
+ ret->vklayer_count = 1;
+ ret->vklayer_names = rune_alloc(sizeof(char*) * ret->vklayer_count);
+ ret->vklayer_names[0] = "VK_LAYER_KHRONOS_validation";
+
+ for (uint32_t i = 0; i < ret->vklayer_count; i++) {
+ log_output(LOG_DEBUG, "Searching for layer: %s", ret->vklayer_names[i]);
+ int found = 0;
+ for (uint32_t j = 0; j < layer_count; j++) {
+ if (strcmp(ret->vklayer_names[i], layer_props[j].layerName) == 0) {
+ found = 1;
+ break;
+ }
+ }
+ if (found == 0) {
+ log_output(LOG_WARN, "Required validation layer is missing: %s", ret->vklayer_names[i]);
+ rune_free(ret);
+ rune_free(ext->extensions);
+ return NULL;
+ }
+ }
+ log_output(LOG_INFO, "All prerequisite validation layers found");
+ rune_free(ext->extensions);
+ return ret;
+}
diff --git a/engine/render/vulkan/context.h b/engine/render/vulkan/context.h
new file mode 100644
index 0000000..15817e8
--- /dev/null
+++ b/engine/render/vulkan/context.h
@@ -0,0 +1,32 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKCONTEXT_H
+#define VKCONTEXT_H
+
+#include "vk_types.h"
+
+vkcontext_t* create_vkcontext(vklayer_container_t *vklayers, ext_container_t *ext);
+void destroy_vkcontext(vkcontext_t *context);
+
+vklayer_container_t* init_vklayers(ext_container_t *ext);
+
+#endif
diff --git a/engine/render/vulkan/device.c b/engine/render/vulkan/device.c
new file mode 100644
index 0000000..11d9433
--- /dev/null
+++ b/engine/render/vulkan/device.c
@@ -0,0 +1,324 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "device.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+#include <rune/core/logging.h>
+
+static int gfx_qfam = -1;
+static int tsfr_qfam = -1;
+static int comp_qfam = -1;
+static int pres_qfam = -1;
+
+struct vkdev_data {
+ VkPhysicalDeviceProperties pdev_props;
+ VkPhysicalDeviceFeatures pdev_feats;
+ VkPhysicalDeviceMemoryProperties pdev_mprops;
+ uint32_t pdev_ext_count;
+ const char** pdev_extensions;
+};
+
+void _query_pdev_data(VkPhysicalDevice pdev, struct vkdev_data *pdata) {
+ vkGetPhysicalDeviceProperties(pdev, &pdata->pdev_props);
+ vkGetPhysicalDeviceFeatures(pdev, &pdata->pdev_feats);
+ vkGetPhysicalDeviceMemoryProperties(pdev, &pdata->pdev_mprops);
+}
+
+uint32_t _query_qfam_data(VkSurfaceKHR surface, VkPhysicalDevice pdev, VkQueueFamilyProperties** qfam_props) {
+ uint32_t count;
+ vkGetPhysicalDeviceQueueFamilyProperties(pdev, &count, NULL);
+ *qfam_props = rune_alloc(sizeof(VkQueueFamilyProperties) * count);
+ vkGetPhysicalDeviceQueueFamilyProperties(pdev, &count, *qfam_props);
+ return count;
+}
+
+int _query_gfx_index(int num_props, VkQueueFamilyProperties *qfam_props) {
+ for (int i = 0; i < num_props; i++) {
+ if (qfam_props[i].queueFlags & VK_QUEUE_GRAPHICS_BIT)
+ gfx_qfam = i;
+ }
+ return gfx_qfam;
+}
+
+int _query_tsfr_index(int num_props, VkQueueFamilyProperties *qfam_props) {
+ for (int i = 0; i < num_props; i++) {
+ if (qfam_props[i].queueFlags & VK_QUEUE_TRANSFER_BIT)
+ tsfr_qfam = i;
+ }
+ return tsfr_qfam;
+}
+
+int _query_comp_index(int num_props, VkQueueFamilyProperties *qfam_props) {
+ for (int i = 0; i < num_props; i++) {
+ if (qfam_props[i].queueFlags & VK_QUEUE_COMPUTE_BIT)
+ comp_qfam = i;
+ }
+ return comp_qfam;
+}
+
+int _query_pres_index(int num_props, VkQueueFamilyProperties *qfam_props, VkPhysicalDevice pdev, VkSurfaceKHR surface) {
+ VkBool32 present_bit;
+ for (int i = 0; i < num_props; i++) {
+ vkGetPhysicalDeviceSurfaceSupportKHR(pdev, i, surface, &present_bit);
+ if (present_bit != VK_FALSE)
+ pres_qfam = i;
+ }
+ return pres_qfam;
+}
+
+int _check_pdev(VkSurfaceKHR surface, VkPhysicalDevice pdev) {
+ int score = 0;
+
+ struct vkdev_data pdata;
+ _query_pdev_data(pdev, &pdata);
+ if (pdata.pdev_props.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU)
+ return score;
+
+ VkQueueFamilyProperties *qfam_props;
+ uint32_t num_qfams = _query_qfam_data(surface, pdev, &qfam_props);
+ if (_query_gfx_index(num_qfams, qfam_props) != -1)
+ score += 20;
+ if (_query_comp_index(num_qfams, qfam_props) != -1)
+ score += 20;
+ if (_query_tsfr_index(num_qfams, qfam_props) != -1)
+ score += 20;
+ if (_query_pres_index(num_qfams, qfam_props, pdev, surface) != -1)
+ score += 20;
+
+ rune_free(qfam_props);
+ return score;
+}
+
+VkPhysicalDevice _select_pdev(VkInstance instance, VkSurfaceKHR surface) {
+ uint32_t count;
+ vkEnumeratePhysicalDevices(instance, &count, NULL);
+ if (count == 0)
+ return NULL;
+
+ VkPhysicalDevice pdevs[count];
+ vkEnumeratePhysicalDevices(instance, &count, pdevs);
+
+ for (uint32_t i = 0; i < count; i++) {
+ if (_check_pdev(surface, pdevs[i]) >= 80)
+ return pdevs[i];
+ }
+ return NULL;
+}
+
+void _create_queue(vkdev_t *dev, int qfam_type, int qfam_index, int queue_index) {
+ VkQueue *queue_arr;
+ int num_queues;
+ switch (qfam_type) {
+ case QFAM_TYPE_GRAPHICS:
+ dev->num_gfx_queues++;
+ num_queues = dev->num_gfx_queues;
+ dev->gfx_queues = rune_realloc(dev->gfx_queues, sizeof(VkQueue)*num_queues);
+ queue_arr = dev->gfx_queues;
+ break;
+ case QFAM_TYPE_TRANSFER:
+ dev->num_tsfr_queues++;
+ num_queues = dev->num_tsfr_queues;
+ dev->gfx_queues = rune_realloc(dev->tsfr_queues, sizeof(VkQueue)*num_queues);
+ queue_arr = dev->tsfr_queues;
+ break;
+ case QFAM_TYPE_COMPUTE:
+ dev->num_comp_queues++;
+ num_queues = dev->num_comp_queues;
+ dev->gfx_queues = rune_realloc(dev->comp_queues, sizeof(VkQueue)*num_queues);
+ queue_arr = dev->comp_queues;
+ break;
+ case QFAM_TYPE_PRESENT:
+ if (dev->pres_queue != NULL)
+ rune_free(dev->pres_queue);
+ dev->pres_queue = rune_alloc(sizeof(VkQueue));
+ queue_arr = dev->pres_queue;
+ break;
+ default:
+ log_output(LOG_FATAL, "Requested unknown queue type");
+ rune_abort();
+ }
+
+ vkGetDeviceQueue(dev->ldev, qfam_index, queue_index, &queue_arr[num_queues-1]);
+ if (queue_arr[num_queues-1] == NULL) {
+ log_output(LOG_FATAL, "Error creating required Vulkan queue");
+ rune_abort();
+ }
+}
+
+vkdev_t* create_vkdev(VkInstance instance, VkSurfaceKHR surface, int num_gfx, int num_tsfr, int num_comp, int presentable) {
+ VkPhysicalDevice pdev = _select_pdev(instance, surface);
+ if (pdev == NULL) {
+ log_output(LOG_FATAL, "No device meets minimum requirements for rendering");
+ rune_abort();
+ }
+
+ vkdev_t *dev = rune_calloc(0, sizeof(vkdev_t));
+ dev->pdev = pdev;
+ dev->gfx_qfam = gfx_qfam;
+ dev->tsfr_qfam = tsfr_qfam;
+ dev->comp_qfam = comp_qfam;
+ dev->pres_qfam = pres_qfam;
+
+ if (num_gfx > 0 && gfx_qfam == -1) {
+ log_output(LOG_FATAL, "Requested graphics queues but none found on device");
+ rune_abort();
+ }
+ if (num_tsfr > 0 && tsfr_qfam == -1) {
+ log_output(LOG_FATAL, "Requested transfer queues but none found on device");
+ rune_abort();
+ }
+ if (num_comp > 0 && comp_qfam == -1) {
+ log_output(LOG_FATAL, "Requested compute queues but none found on device");
+ rune_abort();
+ }
+ if (presentable == 1 && pres_qfam == -1) {
+ log_output(LOG_FATAL, "Requested presentation queue but none found on device");
+ rune_abort();
+ }
+
+ int num_total = num_gfx + num_tsfr + num_comp + presentable;
+ int index = 0;
+ static const float queue_priority = 1.0f;
+ VkDeviceQueueCreateInfo *qcinfos = rune_alloc(sizeof(VkDeviceQueueCreateInfo)*num_total);
+ for (int i = 0; i < index+num_gfx; i++) {
+ qcinfos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ qcinfos[i].pNext = NULL;
+ qcinfos[i].flags = 0;
+ qcinfos[i].queueFamilyIndex = gfx_qfam;
+ qcinfos[i].queueCount = 1;
+ qcinfos[i].pQueuePriorities = &queue_priority;
+ }
+ //index += num_gfx;
+ //for (int i = index; i < index+num_tsfr; i++) {
+ // qcinfos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ // qcinfos[i].pNext = NULL;
+ // qcinfos[i].flags = 0;
+ // qcinfos[i].queueFamilyIndex = tsfr_qfam;
+ // qcinfos[i].queueCount = 1;
+ // qcinfos[i].pQueuePriorities = &queue_priority;
+ //}
+ //index += num_tsfr;
+ //for (int i = index; i < index+num_comp; i++) {
+ // qcinfos[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ // qcinfos[i].pNext = NULL;
+ // qcinfos[i].flags = 0;
+ // qcinfos[i].queueFamilyIndex = comp_qfam;
+ // qcinfos[i].queueCount = 1;
+ // qcinfos[i].pQueuePriorities = &queue_priority;
+ //}
+
+ struct vkdev_data pdata;
+ _query_pdev_data(pdev, &pdata);
+
+ VkDeviceCreateInfo dcinfo;
+ dcinfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ dcinfo.pNext = NULL;
+ dcinfo.flags = 0;
+ dcinfo.queueCreateInfoCount = num_total;
+ dcinfo.pQueueCreateInfos = qcinfos;
+ dcinfo.enabledLayerCount = 0;
+ dcinfo.ppEnabledLayerNames = NULL;
+ dcinfo.enabledExtensionCount = 1;
+ const char *ext_names = VK_KHR_SWAPCHAIN_EXTENSION_NAME;
+ dcinfo.ppEnabledExtensionNames = &ext_names;
+ dcinfo.pEnabledFeatures = &pdata.pdev_feats;
+ vkassert(vkCreateDevice(dev->pdev, &dcinfo, NULL, &dev->ldev));
+ rune_free(qcinfos);
+
+ for (int i = 0; i < num_gfx; i++)
+ _create_queue(dev, QFAM_TYPE_GRAPHICS, gfx_qfam, i);
+ for (int i = 0; i < num_tsfr; i++)
+ _create_queue(dev, QFAM_TYPE_TRANSFER, tsfr_qfam, i);
+ for (int i = 0; i < num_comp; i++)
+ _create_queue(dev, QFAM_TYPE_COMPUTE, comp_qfam, i);
+
+ VkCommandPoolCreateInfo pcinfo;
+ pcinfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+ pcinfo.pNext = NULL;
+ pcinfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+ pcinfo.queueFamilyIndex = gfx_qfam;
+ vkassert(vkCreateCommandPool(dev->ldev, &pcinfo, NULL, &dev->gfx_cmd_pool));
+ pcinfo.queueFamilyIndex = tsfr_qfam;
+ vkassert(vkCreateCommandPool(dev->ldev, &pcinfo, NULL, &dev->tsfr_cmd_pool));
+ pcinfo.queueFamilyIndex = comp_qfam;
+ vkassert(vkCreateCommandPool(dev->ldev, &pcinfo, NULL, &dev->comp_cmd_pool));
+
+ log_output(LOG_DEBUG, "Initialized new logical device");
+ return dev;
+}
+
+void destroy_vkdev(vkdev_t *dev) {
+ vkDestroyCommandPool(dev->ldev, dev->gfx_cmd_pool, NULL);
+ vkDestroyCommandPool(dev->ldev, dev->tsfr_cmd_pool, NULL);
+ vkDestroyCommandPool(dev->ldev, dev->comp_cmd_pool, NULL);
+ vkDestroyDevice(dev->ldev, NULL);
+ rune_free(dev);
+}
+
+void get_swapchain_data(vkdev_t *dev, VkSurfaceKHR *surface) {
+ vkGetPhysicalDeviceSurfaceCapabilitiesKHR(dev->pdev, *surface, &dev->scdata.capabilities);
+
+ vkGetPhysicalDeviceSurfaceFormatsKHR(dev->pdev, *surface, &dev->scdata.format_count, NULL);
+ dev->scdata.formats = rune_alloc(sizeof(VkSurfaceFormatKHR) * dev->scdata.format_count);
+ vkGetPhysicalDeviceSurfaceFormatsKHR(dev->pdev, *surface, &dev->scdata.format_count, dev->scdata.formats);
+
+ vkGetPhysicalDeviceSurfacePresentModesKHR(dev->pdev, *surface, &dev->scdata.present_count, NULL);
+ dev->scdata.present_modes = rune_alloc(sizeof(VkPresentModeKHR) * dev->scdata.present_count);
+ vkGetPhysicalDeviceSurfacePresentModesKHR(dev->pdev, *surface, &dev->scdata.present_count, dev->scdata.present_modes);
+}
+
+int get_depth_format(vkdev_t *dev) {
+ const uint64_t count = 3;
+ VkFormat formats[3] = {
+ VK_FORMAT_D32_SFLOAT_S8_UINT,
+ VK_FORMAT_D24_UNORM_S8_UINT,
+ VK_FORMAT_D32_SFLOAT};
+
+ uint32_t flags = VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT;
+ VkFormatProperties props;
+ for (uint64_t i = 0; i < count; i++) {
+ vkGetPhysicalDeviceFormatProperties(dev->pdev, formats[i], &props);
+ if ((props.linearTilingFeatures & flags) == flags) {
+ dev->depth_format = formats[i];
+ return 1;
+ } else if ((props.optimalTilingFeatures & flags) == flags) {
+ dev->depth_format = formats[i];
+ return 1;
+ }
+ }
+ return 0;
+}
+
+uint32_t get_memory_index(vkdev_t *dev, uint32_t type, uint32_t flags) {
+ VkPhysicalDeviceMemoryProperties mem_props;
+ vkGetPhysicalDeviceMemoryProperties(dev->pdev, &mem_props);
+
+ for (uint32_t i = 0; i < mem_props.memoryTypeCount; i++) {
+ uint32_t tmp = type & (1 << i);
+ uint32_t prop_flags = mem_props.memoryTypes[i].propertyFlags;
+ if (tmp & prop_flags & flags)
+ return i;
+ }
+
+ log_output(LOG_WARN, "Unable to find suitable memory type");
+ return -1;
+}
diff --git a/engine/render/vulkan/device.h b/engine/render/vulkan/device.h
new file mode 100644
index 0000000..3dab471
--- /dev/null
+++ b/engine/render/vulkan/device.h
@@ -0,0 +1,34 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKDEVICE_H
+#define VKDEVICE_H
+
+#include "vk_types.h"
+
+vkdev_t* create_vkdev(VkInstance instance, VkSurfaceKHR surface, int num_gfx, int num_tsfr, int num_comp, int presentable);
+void destroy_vkdev(vkdev_t *dev);
+
+void get_swapchain_data(vkdev_t *dev, VkSurfaceKHR *surface);
+int get_depth_format(vkdev_t *dev);
+uint32_t get_memory_index(vkdev_t *dev, uint32_t type, uint32_t flags);
+
+#endif
diff --git a/engine/render/vulkan/fence.c b/engine/render/vulkan/fence.c
new file mode 100644
index 0000000..cee022e
--- /dev/null
+++ b/engine/render/vulkan/fence.c
@@ -0,0 +1,84 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "fence.h"
+#include "vkassert.h"
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+
+vkfence_t* create_vkfence(vkdev_t *dev, uint8_t signal) {
+ vkfence_t *ret = rune_alloc(sizeof(vkfence_t));
+
+ VkFenceCreateInfo fcinfo;
+ fcinfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+ fcinfo.pNext = NULL;
+ if (signal == 1)
+ fcinfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
+ else
+ fcinfo.flags = 0;
+ vkassert(vkCreateFence(dev->ldev, &fcinfo, NULL, &ret->handle));
+ return ret;
+}
+
+void destroy_vkfence(vkfence_t *fence, vkdev_t *dev) {
+ if (fence->handle != NULL) {
+ vkDestroyFence(dev->ldev, fence->handle, NULL);
+ fence->handle = NULL;
+ }
+ rune_free(fence);
+}
+
+int fence_lock(vkfence_t *fence, vkdev_t *dev, uint64_t timeout) {
+ if (fence->signal == 1)
+ return 0;
+
+ VkResult res = vkWaitForFences(dev->ldev, 1, &fence->handle, VK_TRUE, timeout);
+ switch (res) {
+ case VK_SUCCESS:
+ fence->signal = 1;
+ return 0;
+ case VK_TIMEOUT:
+ log_output(LOG_WARN, "Vulkan fence timed out");
+ break;
+ case VK_ERROR_DEVICE_LOST:
+ log_output(LOG_ERROR, "Lost access to host device");
+ break;
+ case VK_ERROR_OUT_OF_HOST_MEMORY:
+ log_output(LOG_ERROR, "Out of host memory");
+ break;
+ case VK_ERROR_OUT_OF_DEVICE_MEMORY:
+ log_output(LOG_ERROR, "Out of device memory");
+ break;
+ default:
+ log_output(LOG_ERROR, "Unknown error occurred on Vulkan fence");
+ break;
+ }
+
+ return -1;
+}
+
+void fence_unlock(vkfence_t *fence, vkdev_t *dev) {
+ if (fence->signal == 0)
+ return;
+
+ vkassert(vkResetFences(dev->ldev, 1, &fence->handle));
+ fence->signal = 0;
+}
diff --git a/engine/render/vulkan/fence.h b/engine/render/vulkan/fence.h
new file mode 100644
index 0000000..49a04fe
--- /dev/null
+++ b/engine/render/vulkan/fence.h
@@ -0,0 +1,33 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKFENCE_H
+#define VKFENCE_H
+
+#include "vk_types.h"
+
+vkfence_t* create_vkfence(vkdev_t *dev, uint8_t signal);
+void destroy_vkfence(vkfence_t *fence, vkdev_t *dev);
+
+int fence_lock(vkfence_t *fence, vkdev_t *dev, uint64_t timeout);
+void fence_unlock(vkfence_t *fence, vkdev_t *dev);
+
+#endif
diff --git a/engine/render/vulkan/framebuffer.c b/engine/render/vulkan/framebuffer.c
new file mode 100644
index 0000000..697bbb9
--- /dev/null
+++ b/engine/render/vulkan/framebuffer.c
@@ -0,0 +1,55 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "framebuffer.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+#include <rune/core/logging.h>
+
+vkframebuffer_t* create_vkframebuffer(vkdev_t *dev, vkrendpass_t *rendpass, uint32_t width, uint32_t height, uint32_t at_count, VkImageView *at) {
+ vkframebuffer_t *ret = rune_alloc(sizeof(vkframebuffer_t));
+ ret->at_count = at_count;
+ ret->attachments = rune_alloc(sizeof(VkImageView) * at_count);
+ for (uint32_t i = 0; i < at_count; i++)
+ ret->attachments[i] = at[i];
+ ret->rendpass = rendpass;
+
+ VkFramebufferCreateInfo fbinfo;
+ fbinfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
+ fbinfo.pNext = NULL;
+ fbinfo.flags = 0;
+ fbinfo.renderPass = rendpass->handle;
+ fbinfo.attachmentCount = ret->at_count;
+ fbinfo.pAttachments = ret->attachments;
+ fbinfo.width = width;
+ fbinfo.height = height;
+ fbinfo.layers = 1;
+ vkassert(vkCreateFramebuffer(dev->ldev, &fbinfo, NULL, &ret->handle));
+
+ return ret;
+}
+
+void destroy_vkframebuffer(vkframebuffer_t *framebuffer, vkdev_t *dev) {
+ vkDestroyFramebuffer(dev->ldev, framebuffer->handle, NULL);
+ if (framebuffer->attachments)
+ rune_free(framebuffer->attachments);
+ rune_free(framebuffer);
+}
diff --git a/engine/render/vulkan/framebuffer.h b/engine/render/vulkan/framebuffer.h
new file mode 100644
index 0000000..dbf914d
--- /dev/null
+++ b/engine/render/vulkan/framebuffer.h
@@ -0,0 +1,30 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKFRAMEBUFFER_H
+#define VKFRAMEBUFFER_H
+
+#include "vk_types.h"
+
+vkframebuffer_t* create_vkframebuffer(vkdev_t *dev, vkrendpass_t *rendpass, uint32_t width, uint32_t height, uint32_t at_count, VkImageView *at);
+void destroy_vkframebuffer(vkframebuffer_t *framebuffer, vkdev_t *dev);
+
+#endif
diff --git a/engine/render/vulkan/image.c b/engine/render/vulkan/image.c
new file mode 100644
index 0000000..0334e94
--- /dev/null
+++ b/engine/render/vulkan/image.c
@@ -0,0 +1,99 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "image.h"
+#include "device.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+
+int _create_image_view(vkimage_t *image, vkdev_t *dev, VkFormat format, VkImageAspectFlags aflags) {
+ VkImageViewCreateInfo vcinfo;
+ vcinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ vcinfo.pNext = NULL;
+ vcinfo.flags = 0;
+ vcinfo.image = image->handle;
+ vcinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ vcinfo.format = format;
+ vcinfo.components.r = VK_COMPONENT_SWIZZLE_R;
+ vcinfo.components.g = VK_COMPONENT_SWIZZLE_G;
+ vcinfo.components.b = VK_COMPONENT_SWIZZLE_B;
+ vcinfo.components.a = VK_COMPONENT_SWIZZLE_A;
+ vcinfo.subresourceRange.aspectMask = aflags;
+ vcinfo.subresourceRange.baseMipLevel = 0;
+ vcinfo.subresourceRange.levelCount = 1;
+ vcinfo.subresourceRange.baseArrayLayer = 0;
+ vcinfo.subresourceRange.layerCount = 1;
+ vkassert(vkCreateImageView(dev->ldev, &vcinfo, NULL, &image->view));
+}
+
+vkimage_t* create_vkimage(vkdev_t *dev, VkFormat format, uint32_t width, uint32_t height, uint32_t usage, uint32_t mem_flags, uint32_t aflags, int create_view) {
+ vkimage_t *ret = rune_alloc(sizeof(vkimage_t));
+ ret->width = width;
+ ret->height = height;
+
+ VkImageCreateInfo icinfo;
+ icinfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
+ icinfo.pNext = NULL;
+ icinfo.flags = 0;
+ icinfo.imageType = VK_IMAGE_TYPE_2D;
+ icinfo.extent.width = width;
+ icinfo.extent.height = height;
+ icinfo.extent.depth = 1;
+ icinfo.mipLevels = 1;
+ icinfo.arrayLayers = 1;
+ icinfo.format = format;
+ icinfo.tiling = VK_IMAGE_TILING_OPTIMAL;
+ icinfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ icinfo.usage = usage;
+ icinfo.samples = VK_SAMPLE_COUNT_1_BIT;
+ icinfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ vkassert(vkCreateImage(dev->ldev, &icinfo, NULL, &ret->handle));
+
+ VkMemoryRequirements mem_req;
+ vkGetImageMemoryRequirements(dev->ldev, ret->handle, &mem_req);
+
+ int32_t mem_type = get_memory_index(dev, mem_req.memoryTypeBits, mem_flags);
+ if (mem_type == -1)
+ log_output(LOG_ERROR, "Image does not have a valid memory type");
+
+ VkMemoryAllocateInfo mainfo;
+ mainfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
+ mainfo.pNext = NULL;
+ mainfo.allocationSize = mem_req.size;
+ mainfo.memoryTypeIndex = mem_type;
+ vkassert(vkAllocateMemory(dev->ldev, &mainfo, NULL, &ret->memory));
+ vkassert(vkBindImageMemory(dev->ldev, ret->handle, ret->memory, 0));
+
+ if (create_view == 1)
+ _create_image_view(ret, dev, format, aflags);
+
+ return ret;
+}
+
+void destroy_vkimage(vkimage_t *image, vkdev_t *dev) {
+ if (image->view)
+ vkDestroyImageView(dev->ldev, image->view, NULL);
+ if (image->memory)
+ vkFreeMemory(dev->ldev, image->memory, NULL);
+ if (image->handle)
+ vkDestroyImage(dev->ldev, image->handle, NULL);
+ rune_free(image);
+}
diff --git a/engine/render/vulkan/image.h b/engine/render/vulkan/image.h
new file mode 100644
index 0000000..067025d
--- /dev/null
+++ b/engine/render/vulkan/image.h
@@ -0,0 +1,30 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKIMAGE_H
+#define VKIMAGE_H
+
+#include "vk_types.h"
+
+vkimage_t* create_vkimage(vkdev_t *dev, VkFormat format, uint32_t width, uint32_t height, uint32_t usage, uint32_t mem_flags, uint32_t aflags, int create_view);
+void destroy_vkimage(vkimage_t *image, vkdev_t *dev);
+
+#endif
diff --git a/engine/render/vulkan/renderer.c b/engine/render/vulkan/renderer.c
new file mode 100644
index 0000000..f02d84f
--- /dev/null
+++ b/engine/render/vulkan/renderer.c
@@ -0,0 +1,279 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "vk_types.h"
+#include "renderpass.h"
+#include "framebuffer.h"
+#include "swapchain.h"
+#include "device.h"
+#include "context.h"
+#include "image.h"
+#include "fence.h"
+#include "vkassert.h"
+#include <rune/render/renderer.h>
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+#include <rune/core/abort.h>
+#include <rune/core/config.h>
+#include <sys/time.h>
+
+static vkcontext_t *context = NULL;
+
+void _init_cmdbuffers(void) {
+ uint32_t num_buffers = context->swapchain->img_count;
+ if (context->cmdbuffers == NULL)
+ context->cmdbuffers = rune_calloc(0, sizeof(vkcmdbuffer_t*) * num_buffers);
+
+ for (uint32_t i = 0; i < num_buffers; i++) {
+ if (context->cmdbuffers[i] != NULL)
+ destroy_vkcmdbuffer(context->cmdbuffers[i], context->dev);
+ context->cmdbuffers[i] = create_vkcmdbuffer(context->dev, 1);
+ }
+ log_output(LOG_DEBUG, "Created %d command buffers", num_buffers);
+}
+
+void _destroy_cmdbuffers(void) {
+ uint32_t num_buffers = context->swapchain->img_count;
+ if (context->cmdbuffers == NULL)
+ return;
+ for (uint32_t i = 0; i < num_buffers; i++) {
+ if (context->cmdbuffers[i] != NULL)
+ destroy_vkcmdbuffer(context->cmdbuffers[i], context->dev);
+ }
+ log_output(LOG_DEBUG, "Destroyed %d command buffers", num_buffers);
+}
+
+void _init_framebuffers(void) {
+ uint32_t num_buffers = context->swapchain->img_count;
+ if (context->framebuffers == NULL)
+ context->framebuffers = rune_calloc(0, sizeof(vkframebuffer_t*) * num_buffers);
+
+ uint32_t at_count = 2;
+ for (uint32_t i = 0; i < num_buffers; i++) {
+ VkImageView attachments[at_count];
+ attachments[0] = context->swapchain->views[i];
+ attachments[1] = context->swapchain->depth_attachment->view;
+
+ if (context->framebuffers[i] != NULL)
+ destroy_vkframebuffer(context->framebuffers[i], context->dev);
+ context->framebuffers[i] = create_vkframebuffer(context->dev,
+ context->rendpass,
+ context->surface->width,
+ context->surface->height,
+ at_count,
+ attachments);
+ }
+ log_output(LOG_DEBUG, "Created %d frame buffers", num_buffers);
+}
+
+void _destroy_framebuffers(void) {
+ uint32_t num_buffers = context->swapchain->img_count;
+ if (context->framebuffers == NULL)
+ return;
+
+ for (uint32_t i = 0; i < num_buffers; i++)
+ destroy_vkframebuffer(context->framebuffers[i], context->dev);
+ log_output(LOG_DEBUG, "Destroyed %d frame buffers", num_buffers);
+}
+
+int _init_vulkan(window_t *window) {
+ log_output(LOG_DEBUG, "Initializing Vulkan");
+ struct timeval start;
+ struct timeval stop;
+ gettimeofday(&start, NULL);
+
+ ext_container_t ext;
+ ext.extensions = glfwGetRequiredInstanceExtensions(&ext.ext_count);
+
+ if (rune_get_vk_debug() == 1) {
+ vklayer_container_t *vklayers = init_vklayers(&ext);
+ context = create_vkcontext(vklayers, &ext);
+ } else {
+ context = create_vkcontext(NULL, &ext);
+ }
+
+ if (context == NULL)
+ return -1;
+
+ VkResult res = glfwCreateWindowSurface(context->instance,
+ window->window,
+ NULL,
+ &context->surface->handle);
+ if (res != VK_SUCCESS) {
+ log_output(LOG_FATAL, "Cannot create rendering surface");
+ return -1;
+ }
+
+ context->dev = create_vkdev(context->instance,
+ context->surface->handle,
+ 1,
+ 1,
+ 1,
+ 1);
+ if (context->dev == NULL)
+ return -1;
+
+ context->surface->width = window->winw;
+ context->surface->height = window->winh;
+ context->swapchain = create_swapchain(context->surface, context->dev);
+ if (context->swapchain == NULL)
+ return -1;
+
+ vec4 area = {0, 0, context->surface->width, context->surface->height};
+ vec4 color = {0, 0, 0, 1.0f};
+ context->rendpass = create_vkrendpass(context->dev, context->swapchain, area, color, 1.0, 0);
+ if (context->rendpass == NULL)
+ return -1;
+
+ _init_framebuffers();
+ _init_cmdbuffers();
+
+ context->image_semaphores = rune_alloc(sizeof(VkSemaphore) * context->swapchain->max_frames);
+ context->queue_semaphores = rune_alloc(sizeof(VkSemaphore) * context->swapchain->max_frames);
+ context->fences_in_flight = rune_calloc(0, sizeof(vkfence_t*) * context->swapchain->max_frames);
+
+ VkSemaphoreCreateInfo scinfo;
+ for (uint8_t i = 0; i < context->swapchain->max_frames; i++) {
+ scinfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+ scinfo.pNext = NULL;
+ scinfo.flags = 0;
+ vkCreateSemaphore(context->dev->ldev, &scinfo, NULL, &context->image_semaphores[i]);
+ vkCreateSemaphore(context->dev->ldev, &scinfo, NULL, &context->queue_semaphores[i]);
+ context->fences_in_flight[i] = create_vkfence(context->dev, 1);
+ }
+ context->images_in_flight = rune_calloc(0, sizeof(vkfence_t*) * context->swapchain->img_count);
+
+ gettimeofday(&stop, NULL);
+ log_output(LOG_INFO, "Finished initializing Vulkan in %lums", (stop.tv_sec - start.tv_sec) * 1000000 + stop.tv_usec - start.tv_usec);
+ return 0;
+}
+
+void _close_vulkan(void) {
+ vkDeviceWaitIdle(context->dev->ldev);
+ for (uint8_t i = 0; i < context->swapchain->max_frames; i++) {
+ if (context->image_semaphores[i] != NULL)
+ vkDestroySemaphore(context->dev->ldev, context->image_semaphores[i], NULL);
+ if (context->queue_semaphores[i] != NULL)
+ vkDestroySemaphore(context->dev->ldev, context->queue_semaphores[i], NULL);
+ destroy_vkfence(context->fences_in_flight[i], context->dev);
+ }
+
+ _destroy_cmdbuffers();
+ _destroy_framebuffers();
+ destroy_vkrendpass(context->rendpass, context->dev);
+ destroy_swapchain(context->swapchain, context->dev);
+ destroy_vkdev(context->dev);
+ destroy_vkcontext(context);
+}
+
+int _begin_frame(float time) {
+ vkfence_t *frame_fence = context->fences_in_flight[context->swapchain->frame];
+ if (fence_lock(frame_fence, context->dev, UINT64_MAX) == -1) {
+ log_output(LOG_WARN, "Error locking in-flight fence");
+ return -1;
+ }
+
+ uint32_t next_img = vkswapchain_get_next_img(context->swapchain,
+ context->dev,
+ UINT64_MAX,
+ NULL,
+ context->image_semaphores[context->swapchain->frame]);
+
+ if (next_img == -1)
+ return -1;
+
+ context->img_index = next_img;
+ vkcmdbuffer_t *cmdbuf = context->cmdbuffers[context->img_index];
+ cmdbuf_begin(cmdbuf, 0, 0, 0);
+
+ VkViewport vport;
+ vport.x = 0;
+ vport.y = (float)context->surface->height;
+ vport.width = (float)context->surface->width;
+ vport.height = (float)context->surface->height;
+ vport.minDepth = 0;
+ vport.maxDepth = 1;
+
+ VkRect2D scissor;
+ scissor.offset.x = 0;
+ scissor.offset.y = 0;
+ scissor.extent.width = context->surface->width;
+ scissor.extent.height = context->surface->height;
+
+ vkCmdSetViewport(cmdbuf->handle, 0, 1, &vport);
+ vkCmdSetScissor(cmdbuf->handle, 0, 1, &scissor);
+
+ context->rendpass->area[2] = context->surface->width;
+ context->rendpass->area[3] = context->surface->height;
+
+ VkFramebuffer framebuf = context->framebuffers[context->img_index]->handle;
+ renderpass_begin(cmdbuf, context->rendpass, framebuf);
+ return 0;
+}
+
+int _end_frame(float time) {
+ vkcmdbuffer_t *cmdbuf = context->cmdbuffers[context->img_index];
+ renderpass_end(cmdbuf, context->rendpass);
+ cmdbuf_end(cmdbuf);
+
+ vkfence_t** img_in_flight = &context->images_in_flight[context->img_index];
+ if (*img_in_flight != NULL)
+ fence_lock(*img_in_flight, context->dev, UINT64_MAX);
+
+ context->images_in_flight[context->img_index] = context->fences_in_flight[context->swapchain->frame];
+ *img_in_flight = context->fences_in_flight[context->swapchain->frame];
+ fence_unlock(*img_in_flight, context->dev);
+
+ for (int i = 0; i < context->dev->num_gfx_queues; i++) {
+ cmdbuf_submit(cmdbuf,
+ &context->queue_semaphores[context->swapchain->frame],
+ &context->image_semaphores[context->swapchain->frame],
+ context->dev->gfx_queues[i],
+ (*img_in_flight)->handle);
+ }
+
+ vkswapchain_present(context->swapchain,
+ context->dev,
+ &context->queue_semaphores[context->swapchain->frame],
+ &context->img_index);
+
+ cmdbuf_reset(cmdbuf);
+
+ return 0;
+}
+
+void _draw_vulkan(void) {
+ _begin_frame(0);
+ _end_frame(0);
+}
+
+void _clear_vulkan(void) {
+}
+
+renderer_t* select_render_vulkan(window_t *window) {
+ renderer_t *ret = rune_alloc(sizeof(renderer_t));
+ ret->close = _close_vulkan;
+ ret->draw = _draw_vulkan;
+ ret->clear = _clear_vulkan;
+ if (_init_vulkan(window) != 0)
+ rune_abort();
+ return ret;
+}
diff --git a/engine/render/vulkan/renderpass.c b/engine/render/vulkan/renderpass.c
new file mode 100644
index 0000000..e1e8743
--- /dev/null
+++ b/engine/render/vulkan/renderpass.c
@@ -0,0 +1,249 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "renderpass.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+
+vkcmdbuffer_t* create_vkcmdbuffer(vkdev_t *dev, int primary) {
+ vkcmdbuffer_t *ret = rune_calloc(0, sizeof(vkcmdbuffer_t));
+
+ VkCommandBufferAllocateInfo ainfo;
+ ainfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ ainfo.pNext = NULL;
+ ainfo.commandPool = dev->gfx_cmd_pool;
+ if (primary == 1)
+ ainfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+ else
+ ainfo.level = VK_COMMAND_BUFFER_LEVEL_SECONDARY;
+ ainfo.commandBufferCount = 1;
+ vkassert(vkAllocateCommandBuffers(dev->ldev, &ainfo, &ret->handle));
+
+ ret->state = CMDBUF_INITIAL;
+ return ret;
+}
+
+void destroy_vkcmdbuffer(vkcmdbuffer_t *cmdbuffer, vkdev_t *dev) {
+ vkFreeCommandBuffers(dev->ldev, dev->gfx_cmd_pool, 1, &cmdbuffer->handle);
+ rune_free(cmdbuffer);
+}
+
+void cmdbuf_begin(vkcmdbuffer_t *cmdbuffer, int single, int rpass_cont, int sim_use) {
+ if (cmdbuffer->state != CMDBUF_INITIAL) {
+ log_output(LOG_FATAL, "Attempted to record to a command buffer not in initial state");
+ rune_abort();
+ }
+
+ VkCommandBufferBeginInfo binfo;
+ binfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
+ binfo.flags = 0;
+ binfo.pNext = NULL;
+ if (single)
+ binfo.flags |= VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
+ if (rpass_cont)
+ binfo.flags |= VK_COMMAND_BUFFER_USAGE_RENDER_PASS_CONTINUE_BIT;
+ if (sim_use)
+ binfo.flags |= VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT;
+ vkassert(vkBeginCommandBuffer(cmdbuffer->handle, &binfo));
+ cmdbuffer->state = CMDBUF_RECORDING;
+}
+
+void cmdbuf_end(vkcmdbuffer_t *cmdbuffer) {
+ if (cmdbuffer->state != CMDBUF_RECORDING) {
+ log_output(LOG_FATAL, "Attempted to end command buffer not in recording state");
+ rune_abort();
+ }
+
+ vkassert(vkEndCommandBuffer(cmdbuffer->handle));
+ cmdbuffer->state = CMDBUF_READY;
+}
+
+void cmdbuf_submit(vkcmdbuffer_t *cmdbuffer, VkSemaphore *signal, VkSemaphore *wait, VkQueue queue_handle, VkFence fence_handle) {
+ if (cmdbuffer->state != CMDBUF_READY) {
+ log_output(LOG_FATAL, "Attempted to submit command buffer not in ready state");
+ return;
+ }
+
+ VkSubmitInfo sinfo;
+ sinfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ sinfo.pNext = NULL;
+ sinfo.commandBufferCount = 1;
+ sinfo.pCommandBuffers = &cmdbuffer->handle;
+ sinfo.signalSemaphoreCount = 1;
+ sinfo.pSignalSemaphores = signal;
+ sinfo.waitSemaphoreCount = 1;
+ sinfo.pWaitSemaphores = wait;
+ VkPipelineStageFlags flags[1] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
+ sinfo.pWaitDstStageMask = flags;
+ vkassert(vkQueueSubmit(queue_handle, 1, &sinfo, fence_handle));
+ cmdbuffer->state = CMDBUF_SUBMITTED;
+}
+
+void cmdbuf_reset(vkcmdbuffer_t *cmdbuffer) {
+ cmdbuffer->state = CMDBUF_INITIAL;
+}
+
+vkcmdbuffer_t* cmdbuf_begin_single_use(vkdev_t *dev) {
+ vkcmdbuffer_t *ret = create_vkcmdbuffer(dev, 1);
+ cmdbuf_begin(ret, 1, 0, 0);
+ return ret;
+}
+
+void cmdbuf_end_single_use(vkcmdbuffer_t *cmdbuffer, vkdev_t *dev, VkQueue queue) {
+ cmdbuf_end(cmdbuffer);
+ VkSubmitInfo sinfo;
+ sinfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+ sinfo.pNext = NULL;
+ sinfo.waitSemaphoreCount = 0;
+ sinfo.pWaitSemaphores = NULL;
+ sinfo.pWaitDstStageMask = NULL;
+ sinfo.commandBufferCount = 1;
+ sinfo.pCommandBuffers = &cmdbuffer->handle;
+ sinfo.signalSemaphoreCount = 0;
+ sinfo.pSignalSemaphores = NULL;
+ vkassert(vkQueueSubmit(queue, 1, &sinfo, 0));
+
+ vkassert(vkQueueWaitIdle(queue));
+ destroy_vkcmdbuffer(cmdbuffer, dev);
+}
+
+vkrendpass_t* create_vkrendpass(vkdev_t *dev, vkswapchain_t *swapchain, vec4 area, vec4 color, float depth, uint32_t stencil) {
+ VkAttachmentDescription atdesc[2];
+ atdesc[0].flags = 0;
+ atdesc[0].format = swapchain->format_khr.format;
+ atdesc[0].samples = VK_SAMPLE_COUNT_1_BIT;
+ atdesc[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ atdesc[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ atdesc[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ atdesc[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ atdesc[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ atdesc[0].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
+
+ atdesc[1].flags = 0;
+ atdesc[1].format = dev->depth_format;
+ atdesc[1].samples = VK_SAMPLE_COUNT_1_BIT;
+ atdesc[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
+ atdesc[1].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
+ atdesc[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
+ atdesc[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
+ atdesc[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
+ atdesc[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ VkAttachmentReference cref;
+ cref.attachment = 0;
+ cref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
+
+ VkAttachmentReference dref;
+ dref.attachment = 1;
+ dref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
+
+ VkSubpassDescription subpass;
+ subpass.flags = 0;
+ subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
+ subpass.colorAttachmentCount = 1;
+ subpass.pColorAttachments = &cref;
+ subpass.pDepthStencilAttachment = &dref;
+ subpass.preserveAttachmentCount = 0;
+ subpass.pPreserveAttachments = NULL;
+ subpass.inputAttachmentCount = 0;
+ subpass.pInputAttachments = NULL;
+
+ VkSubpassDependency dep;
+ dep.srcSubpass = VK_SUBPASS_EXTERNAL;
+ dep.dstSubpass = 0;
+ dep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
+ dep.srcAccessMask = 0;
+ dep.dstAccessMask = 0;
+ dep.dependencyFlags = 0;
+
+ vkrendpass_t *ret = rune_alloc(sizeof(vkrendpass_t));
+ ret->color[0] = color[0];
+ ret->color[1] = color[1];
+ ret->color[2] = color[2];
+ ret->color[3] = color[3];
+ ret->area[0] = area[0];
+ ret->area[1] = area[1];
+ ret->area[2] = area[2];
+ ret->area[3] = area[3];
+ ret->depth = depth;
+ ret->stencil = stencil;
+ VkRenderPassCreateInfo rcinfo;
+ rcinfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
+ rcinfo.pNext = NULL;
+ rcinfo.flags = 0;
+ rcinfo.attachmentCount = 2;
+ rcinfo.pAttachments = atdesc;
+ rcinfo.subpassCount = 1;
+ rcinfo.pSubpasses = &subpass;
+ rcinfo.dependencyCount = 1;
+ rcinfo.pDependencies = &dep;
+ vkassert(vkCreateRenderPass(dev->ldev, &rcinfo, NULL, &ret->handle));
+
+ log_output(LOG_DEBUG, "Initialized renderpass");
+ return ret;
+}
+
+void destroy_vkrendpass(vkrendpass_t *rendpass, vkdev_t *dev) {
+ if (rendpass->handle)
+ vkDestroyRenderPass(dev->ldev, rendpass->handle, NULL);
+ rune_free(rendpass);
+}
+
+void renderpass_begin(vkcmdbuffer_t *buf, vkrendpass_t *rendpass, VkFramebuffer framebuf) {
+ if (buf->state != CMDBUF_RECORDING) {
+ log_output(LOG_FATAL, "Attempted to place command buffer not in recording state in a render pass");
+ rune_abort();
+ }
+
+ VkClearValue cvals[2];
+ cvals[0].color.float32[0] = rendpass->color[0];
+ cvals[0].color.float32[1] = rendpass->color[1];
+ cvals[0].color.float32[2] = rendpass->color[2];
+ cvals[0].color.float32[3] = rendpass->color[3];
+ cvals[1].depthStencil.depth = rendpass->depth;
+ cvals[1].depthStencil.stencil = rendpass->stencil;
+
+ VkRenderPassBeginInfo binfo;
+ binfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+ binfo.pNext = NULL;
+ binfo.renderPass = rendpass->handle;
+ binfo.framebuffer = framebuf;
+ binfo.renderArea.offset.x = rendpass->area[0];
+ binfo.renderArea.offset.y = rendpass->area[1];
+ binfo.renderArea.extent.width = rendpass->area[2];
+ binfo.renderArea.extent.height = rendpass->area[3];
+ binfo.clearValueCount = 2;
+ binfo.pClearValues = cvals;
+
+ vkCmdBeginRenderPass(buf->handle, &binfo, VK_SUBPASS_CONTENTS_INLINE);
+ buf->state = CMDBUF_IN_RENDERPASS;
+}
+
+void renderpass_end(vkcmdbuffer_t *buf, vkrendpass_t *rendpass) {
+ if (buf->state != CMDBUF_IN_RENDERPASS) {
+ log_output(LOG_FATAL, "Attempted to purge command buffer not in render pass");
+ rune_abort();
+ }
+
+ vkCmdEndRenderPass(buf->handle);
+ buf->state = CMDBUF_RECORDING;
+}
diff --git a/engine/render/vulkan/renderpass.h b/engine/render/vulkan/renderpass.h
new file mode 100644
index 0000000..1524438
--- /dev/null
+++ b/engine/render/vulkan/renderpass.h
@@ -0,0 +1,49 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKRENDERPASS_H
+#define VKRENDERPASS_H
+
+#include "vk_types.h"
+
+enum cmdbuf_state {
+ CMDBUF_INITIAL,
+ CMDBUF_RECORDING,
+ CMDBUF_IN_RENDERPASS,
+ CMDBUF_READY,
+ CMDBUF_SUBMITTED,
+};
+
+vkcmdbuffer_t* create_vkcmdbuffer(vkdev_t *dev, int primary);
+void destroy_vkcmdbuffer(vkcmdbuffer_t *cmdbuffer, vkdev_t *dev);
+
+void cmdbuf_begin(vkcmdbuffer_t *cmdbuffer, int single, int rpass_cont, int sim_use);
+void cmdbuf_end(vkcmdbuffer_t *cmdbuffer);
+void cmdbuf_submit(vkcmdbuffer_t *cmdbuffer, VkSemaphore *signal, VkSemaphore *wait, VkQueue queue_handle, VkFence fence_handle);
+void cmdbuf_reset(vkcmdbuffer_t *cmdbuffer);
+
+vkrendpass_t* create_vkrendpass(vkdev_t *dev, vkswapchain_t *swapchain, vec4 area, vec4 color, float depth, uint32_t stencil);
+void destroy_vkrendpass(vkrendpass_t *rendpass, vkdev_t *dev);
+
+void renderpass_begin(vkcmdbuffer_t *buf, vkrendpass_t *rendpass, VkFramebuffer framebuf);
+void renderpass_end(vkcmdbuffer_t *buf, vkrendpass_t *rendpass);
+
+#endif
diff --git a/engine/render/vulkan/swapchain.c b/engine/render/vulkan/swapchain.c
new file mode 100644
index 0000000..36899de
--- /dev/null
+++ b/engine/render/vulkan/swapchain.c
@@ -0,0 +1,152 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#include "swapchain.h"
+#include "image.h"
+#include "device.h"
+#include "vkassert.h"
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+#include <rune/util/stubbed.h>
+
+vkswapchain_t* create_swapchain(vksurface_t *surface, vkdev_t *dev) {
+ vkswapchain_t *swapchain = rune_alloc(sizeof(vkswapchain_t));
+ VkExtent2D sc_extent = {surface->width, surface->height};
+ swapchain->max_frames = 2;
+ get_swapchain_data(dev, &surface->handle);
+ swapchain->format_khr = dev->scdata.formats[0];
+ if (dev->scdata.capabilities.currentExtent.width != UINT32_MAX)
+ sc_extent = dev->scdata.capabilities.currentExtent;
+
+ VkExtent2D min = dev->scdata.capabilities.minImageExtent;
+ VkExtent2D max = dev->scdata.capabilities.maxImageExtent;
+ sc_extent.width = clamp(sc_extent.width, min.width, max.width);
+ sc_extent.height = clamp(sc_extent.height, min.height, max.height);
+
+ uint32_t img_count = dev->scdata.capabilities.minImageCount + 1;
+ if (dev->scdata.capabilities.maxImageCount > 0 && img_count > dev->scdata.capabilities.maxImageCount)
+ img_count = dev->scdata.capabilities.maxImageCount;
+
+ VkSwapchainCreateInfoKHR cinfo;
+ cinfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
+ cinfo.pNext = NULL;
+ cinfo.flags = 0;
+ cinfo.surface = surface->handle;
+ cinfo.minImageCount = img_count;
+ cinfo.imageFormat = swapchain->format_khr.format;
+ cinfo.imageColorSpace = swapchain->format_khr.colorSpace;
+ cinfo.imageExtent = sc_extent;
+ cinfo.imageArrayLayers = 1;
+ cinfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
+ cinfo.preTransform = dev->scdata.capabilities.currentTransform;
+ cinfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
+ cinfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
+ cinfo.clipped = VK_TRUE;
+ cinfo.oldSwapchain = NULL;
+ if (dev->gfx_qfam != dev->pres_qfam) {
+ uint32_t qfams[] = {dev->gfx_qfam, dev->pres_qfam};
+ cinfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
+ cinfo.queueFamilyIndexCount = 2;
+ cinfo.pQueueFamilyIndices = qfams;
+ } else {
+ cinfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
+ cinfo.queueFamilyIndexCount = 0;
+ cinfo.pQueueFamilyIndices = 0;
+ }
+ vkassert(vkCreateSwapchainKHR(dev->ldev, &cinfo, NULL, &swapchain->handle));
+ vkassert(vkGetSwapchainImagesKHR(dev->ldev, swapchain->handle, &swapchain->img_count, NULL));
+
+ swapchain->images = rune_alloc(sizeof(VkImage) * swapchain->img_count);
+ swapchain->views = rune_alloc(sizeof(VkImageView) * swapchain->img_count);
+ vkassert(vkGetSwapchainImagesKHR(dev->ldev, swapchain->handle, &swapchain->img_count, swapchain->images));
+
+ VkImageViewCreateInfo vcinfo;
+ for (uint32_t i = 0; i < swapchain->img_count; i++) {
+ vcinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ vcinfo.pNext = NULL;
+ vcinfo.image = swapchain->images[i];
+ vcinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ vcinfo.format = swapchain->format_khr.format;
+ vcinfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+ vcinfo.subresourceRange.baseMipLevel = 0;
+ vcinfo.subresourceRange.levelCount = 1;
+ vcinfo.subresourceRange.baseArrayLayer = 0;
+ vcinfo.subresourceRange.layerCount = 1;
+ vkassert(vkCreateImageView(dev->ldev, &vcinfo, NULL, &swapchain->views[i]));
+ }
+
+ if (get_depth_format(dev) == 0) {
+ log_output(LOG_FATAL, "Failed to find a supported image format");
+ rune_abort();
+ }
+
+ swapchain->depth_attachment = create_vkimage(dev,
+ dev->depth_format,
+ surface->width,
+ surface->height,
+ VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
+ VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
+ VK_IMAGE_ASPECT_DEPTH_BIT,
+ 1);
+ swapchain->frame = 0;
+ log_output(LOG_DEBUG, "Initialized swapchain");
+ return swapchain;
+}
+
+void destroy_swapchain(vkswapchain_t *swapchain, vkdev_t *dev) {
+ for (uint32_t i = 0; i < swapchain->img_count; i++)
+ vkDestroyImageView(dev->ldev, swapchain->views[i], NULL);
+ destroy_vkimage(swapchain->depth_attachment, dev);
+ vkDestroySwapchainKHR(dev->ldev, swapchain->handle, NULL);
+ rune_free(swapchain->images);
+ rune_free(swapchain->views);
+}
+
+int32_t vkswapchain_get_next_img(vkswapchain_t *swapchain, vkdev_t *dev, uint64_t tmout, VkFence fence, VkSemaphore img_available) {
+ uint32_t ret = 0;
+ VkResult res = vkAcquireNextImageKHR(dev->ldev, swapchain->handle, tmout, img_available, fence, &ret);
+ if (res != VK_SUCCESS && res != VK_SUBOPTIMAL_KHR) {
+ log_output(LOG_ERROR, "Error on getting next image index");
+ return -1;
+ }
+
+ return (int32_t)ret;
+}
+
+void vkswapchain_present(vkswapchain_t *swapchain, vkdev_t *dev, VkSemaphore *render_complete, uint32_t *img_index) {
+ VkPresentInfoKHR pinfo;
+ pinfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+ pinfo.pNext = NULL;
+ pinfo.waitSemaphoreCount = 1;
+ pinfo.pWaitSemaphores = render_complete;
+ pinfo.swapchainCount = 1;
+ pinfo.pSwapchains = &swapchain->handle;
+ pinfo.pImageIndices = img_index;
+ pinfo.pResults = NULL;
+
+ VkResult res = vkQueuePresentKHR(dev->pres_queue, &pinfo);
+ if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR)
+ STUBBED("Recreate swapchain");
+ else if (res != VK_SUCCESS)
+ log_output(LOG_ERROR, "Vulkan error: %s", get_vkerr_str(res));
+
+ swapchain->frame = (swapchain->frame + 1) % swapchain->max_frames;
+}
diff --git a/engine/render/vulkan/swapchain.h b/engine/render/vulkan/swapchain.h
new file mode 100644
index 0000000..7c2e2a5
--- /dev/null
+++ b/engine/render/vulkan/swapchain.h
@@ -0,0 +1,33 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKSWAPCHAIN_H
+#define VKSWAPCHAIN_H
+
+#include "vk_types.h"
+
+vkswapchain_t* create_swapchain(vksurface_t *surface, vkdev_t *dev);
+void destroy_swapchain(vkswapchain_t *swapchain, vkdev_t *dev);
+
+int32_t vkswapchain_get_next_img(vkswapchain_t *swapchain, vkdev_t *dev, uint64_t tmout, VkFence fence, VkSemaphore img_available);
+void vkswapchain_present(vkswapchain_t *swapchain, vkdev_t *dev, VkSemaphore *render_complete, uint32_t *img_index);
+
+#endif
diff --git a/engine/render/vulkan/vk_types.h b/engine/render/vulkan/vk_types.h
new file mode 100644
index 0000000..2011771
--- /dev/null
+++ b/engine/render/vulkan/vk_types.h
@@ -0,0 +1,143 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VK_TYPES_H
+#define VK_TYPES_H
+
+#include <rune/util/types.h>
+#include <vulkan/vulkan.h>
+#include <GLFW/glfw3.h>
+#include <cglm/cglm.h>
+
+#define QFAM_TYPE_GRAPHICS 1
+#define QFAM_TYPE_TRANSFER 2
+#define QFAM_TYPE_COMPUTE 3
+#define QFAM_TYPE_PRESENT 4
+
+typedef struct vksurface {
+ VkSurfaceKHR handle;
+ uint32_t width;
+ uint32_t height;
+} vksurface_t;
+
+typedef struct vkcmdbuffer {
+ VkCommandBuffer handle;
+ int state;
+} vkcmdbuffer_t;
+
+typedef struct vkfence {
+ VkFence handle;
+ int signal;
+} vkfence_t;
+
+typedef struct vkimage {
+ VkImage handle;
+ VkDeviceMemory memory;
+ VkImageView view;
+ uint32_t width;
+ uint32_t height;
+} vkimage_t;
+
+typedef struct ext_container {
+ const char** extensions;
+ uint32_t ext_count;
+} ext_container_t;
+
+typedef struct vklayer_container {
+ const char** vklayer_names;
+ uint32_t vklayer_count;
+} vklayer_container_t;
+
+typedef struct vkswapchain_data {
+ VkSurfaceCapabilitiesKHR capabilities;
+ VkSurfaceFormatKHR *formats;
+ VkPresentModeKHR *present_modes;
+ uint32_t format_count;
+ uint32_t present_count;
+} vkswapchain_data_t;
+
+typedef struct vkrendpass {
+ VkRenderPass handle;
+ vec4 area;
+ vec4 color;
+ float depth;
+ uint32_t stencil;
+} vkrendpass_t;
+
+typedef struct vkframebuffer {
+ VkFramebuffer handle;
+ uint32_t at_count;
+ VkImageView *attachments;
+ vkrendpass_t *rendpass;
+} vkframebuffer_t;
+
+typedef struct vkdev {
+ VkPhysicalDevice pdev;
+ VkDevice ldev;
+ vkswapchain_data_t scdata;
+ VkQueue *gfx_queues;
+ int num_gfx_queues;
+ int gfx_qfam;
+ VkQueue *tsfr_queues;
+ int num_tsfr_queues;
+ int tsfr_qfam;
+ VkQueue *comp_queues;
+ int num_comp_queues;
+ int comp_qfam;
+ VkQueue *pres_queue;
+ int pres_qfam;
+ VkCommandPool gfx_cmd_pool;
+ VkCommandPool tsfr_cmd_pool;
+ VkCommandPool comp_cmd_pool;
+ VkCommandPool pres_cmd_pool;
+ VkFormat depth_format;
+} vkdev_t;
+
+typedef struct vkswapchain {
+ VkSwapchainKHR handle;
+ VkSurfaceFormatKHR format_khr;
+ VkFormat format;
+ VkImage *images;
+ VkImageView *views;
+ vkimage_t *depth_attachment;
+ uint8_t max_frames;
+ uint32_t frame;
+ uint32_t img_count;
+} vkswapchain_t;
+
+typedef struct vkcontext {
+ VkInstance instance;
+ VkDebugUtilsMessengerEXT db_messenger;
+ VkSemaphore *queue_semaphores;
+ VkSemaphore *image_semaphores;
+ vksurface_t *surface;
+ vkswapchain_t *swapchain;
+ vkrendpass_t *rendpass;
+ vkdev_t *dev;
+ vkcmdbuffer_t** cmdbuffers;
+ vkframebuffer_t** framebuffers;
+ vkfence_t** fences_in_flight;
+ vkfence_t** images_in_flight;
+ uint32_t num_fences_in_flight;
+ uint32_t img_index;
+} vkcontext_t;
+
+#endif
diff --git a/engine/render/vulkan/vkassert.h b/engine/render/vulkan/vkassert.h
new file mode 100644
index 0000000..ca0a89d
--- /dev/null
+++ b/engine/render/vulkan/vkassert.h
@@ -0,0 +1,68 @@
+/*
+ * Rune Game Engine
+ * Copyright 2024 Danny Holman <dholman@gymli.org>
+ *
+ * This software is provided 'as-is', without any express or implied
+ * warranty. In no event will the authors be held liable for any damages
+ * arising from the use of this software.
+ *
+ * Permission is granted to anyone to use this software for any purpose,
+ * including commercial applications, and to alter it and redistribute it
+ * freely, subject to the following restrictions:
+ *
+ * 1. The origin of this software must not be misrepresented; you must not
+ * claim that you wrote the original software. If you use this software
+ * in a product, an acknowledgment in the product documentation would be
+ * appreciated but is not required.
+ * 2. Altered source versions must be plainly marked as such, and must not be
+ * misrepresented as being the original software.
+ * 3. This notice may not be removed or altered from any source distribution.
+ */
+
+#ifndef VKASSERT_H
+#define VKASSERT_H
+
+#include "vk_types.h"
+#include <rune/core/logging.h>
+#include <rune/core/abort.h>
+#include <string.h>
+
+static char* get_vkerr_str(VkResult res) {
+ char *ret;
+ switch (res) {
+ case VK_SUCCESS:
+ ret = "SUCCESS";
+ break;
+ case VK_ERROR_OUT_OF_HOST_MEMORY:
+ ret = "OUT OF HOST MEMORY";
+ break;
+ case VK_ERROR_OUT_OF_DEVICE_MEMORY:
+ ret = "OUT OF DEVICE MEMORY";
+ break;
+ case VK_ERROR_INITIALIZATION_FAILED:
+ ret = "INITIALIZATION FAILED";
+ break;
+ case VK_ERROR_LAYER_NOT_PRESENT:
+ ret = "VALIDATION LAYER NOT PRESENT";
+ break;
+ case VK_ERROR_EXTENSION_NOT_PRESENT:
+ ret = "EXTENSION NOT PRESENT";
+ break;
+ case VK_ERROR_INCOMPATIBLE_DRIVER:
+ ret = "INCOMPATIBLE DRIVER";
+ break;
+ default:
+ ret = "UNKNOWN RESULT";
+ break;
+ }
+ return strdup(ret);
+}
+
+static inline void vkassert(VkResult value) {
+ if (value != VK_SUCCESS) {
+ log_output(LOG_FATAL, "Vulkan error: %s", get_vkerr_str(value));
+ rune_abort();
+ }
+}
+
+#endif