summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--CMakeLists.txt44
-rw-r--r--render/vulkan/context.c176
-rw-r--r--render/vulkan/context.h43
-rw-r--r--render/vulkan/device.c213
-rw-r--r--render/vulkan/device.h60
-rw-r--r--render/vulkan/fence.c63
-rw-r--r--render/vulkan/fence.h19
-rw-r--r--render/vulkan/framebuffer.c34
-rw-r--r--render/vulkan/framebuffer.h17
-rw-r--r--render/vulkan/image.c71
-rw-r--r--render/vulkan/image.h19
-rw-r--r--render/vulkan/renderer.c167
-rw-r--r--render/vulkan/renderpass.c172
-rw-r--r--render/vulkan/renderpass.h42
-rw-r--r--render/vulkan/surface.h13
-rw-r--r--render/vulkan/swapchain.c112
-rw-r--r--render/vulkan/swapchain.h25
-rw-r--r--render/vulkan/vkassert.h15
18 files changed, 1304 insertions, 1 deletions
diff --git a/CMakeLists.txt b/CMakeLists.txt
index ed4e53c..35a543e 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -20,19 +20,61 @@ list(APPEND SOURCE_FILES
)
list(APPEND SOURCE_FILES
+ render/directx/renderer.c
+ render/vulkan/context.c
+ render/vulkan/device.c
+ render/vulkan/fence.c
+ render/vulkan/framebuffer.c
+ render/vulkan/image.c
+ render/vulkan/renderer.c
+ render/vulkan/renderpass.c
+ render/vulkan/swapchain.c
ui/window.c
ui/input.c
ui/renderer.c
)
+find_package(Vulkan REQUIRED)
+find_package(glfw3 REQUIRED)
+find_package(OpenAL REQUIRED)
+find_package(cglm REQUIRED)
+find_package(Threads REQUIRED)
+
set(HEADER_DIR include)
+list(APPEND INCLUDE_DIRS
+ ${HEADER_DIR}
+ ${Vulkan_INCLUDE_DIRS}
+ ${GLFW_INCLUDE_DIR}
+ ${OPENAL_INCLUDE_DIR}
+)
+
+list(APPEND LINK_LIBS
+ ${Vulkan_LIBRARIES}
+ ${GLFW_LIBRARIES}
+ ${OPENAL_LIBRARY}
+ ${CMAKE_THREAD_LIBS_INIT}
+)
+
+if (WIN32)
+ find_package(dlfcn-win32 REQUIRED)
+ set(GLFW_LIBRARIES glfw3dll)
+ set(DX12_LIBRARIES d3d12.lib dxgi.lib dxguid.lib)
+ set(DL_LIBRARIES dlfcn-win32::dl)
+ list(APPEND LINK_LIBS ${DX12_LIBRARIES} ${DL_LIBRARIES})
+else()
+ set(GLFW_LIBRARIES glfw)
+endif()
+
+list(APPEND LINK_LIBS ${GLFW_LIBRARIES})
+
add_compile_definitions(VERSION="${PROJECT_VERSION}")
add_compile_definitions(RAPI_EXPORT)
add_library(${PROJECT_NAME} SHARED ${SOURCE_FILES})
set_property(TARGET ${PROJECT_NAME} PROPERTY ENABLE_EXPORTS ON)
-target_include_directories(${PROJECT_NAME} PUBLIC ${HEADER_DIR})
+target_include_directories(${PROJECT_NAME} PUBLIC ${INCLUDE_DIRS})
+target_link_libraries(${PROJECT_NAME} ${LINK_LIBS})
set_target_properties(${PROJECT_NAME} PROPERTIES VERSION ${PROJECT_VERSION})
set_target_properties(${PROJECT_NAME} PROPERTIES SOVERSION ${PROJECT_VERSION_MAJOR})
diff --git a/render/vulkan/context.c b/render/vulkan/context.c
new file mode 100644
index 0000000..c0fa650
--- /dev/null
+++ b/render/vulkan/context.c
@@ -0,0 +1,176 @@
+#include "context.h"
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+#include <stdlib.h>
+#include <string.h>
+
+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);
+}
+
+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(struct vkcontext *context) {
+ log_output(LOG_INFO, "Initializing Vulkan debugger");
+ uint32_t log_severity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT |
+ VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT;
+ VkDebugUtilsMessengerCreateInfoEXT dbinfo = {VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT};
+ dbinfo.messageSeverity = log_severity;
+ 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, "Cannot create a Vulkan debug session: %s", err_str);
+ free(err_str);
+ return -1;
+ }
+ return 0;
+}
+
+struct vkcontext* create_vkcontext(struct vklayer_container *vklayers, struct ext_container *ext) {
+ log_output(LOG_DEBUG, "Initializing Vulkan");
+ VkApplicationInfo app_info = {VK_STRUCTURE_TYPE_APPLICATION_INFO};
+ app_info.apiVersion = VK_API_VERSION_1_2;
+ app_info.pApplicationName = "RuneClient";
+ app_info.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
+ app_info.pEngineName = "RuneEngine";
+ app_info.engineVersion = VK_MAKE_VERSION(1, 0, 0);
+
+ VkInstanceCreateInfo cinfo = {VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO};
+ cinfo.pApplicationInfo = &app_info;
+ cinfo.enabledExtensionCount = ext->ext_count;
+ cinfo.ppEnabledExtensionNames = ext->extensions;
+ cinfo.pNext = NULL;
+
+ struct vkcontext *ret = rune_calloc(0, sizeof(struct vkcontext));
+ ret->surface = rune_alloc(sizeof(struct vksurface));
+ 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);
+ free(err_str);
+ rune_free(ret);
+ ret = NULL;
+ }
+
+ if (vklayers != NULL) {
+ log_output(LOG_INFO, "Validation layers enabled");
+ _init_vkdebugger(ret);
+ cinfo.enabledLayerCount = vklayers->vklayer_count;
+ cinfo.ppEnabledLayerNames = vklayers->vklayer_names;
+ } else {
+ log_output(LOG_INFO, "Validation layers disabled");
+ }
+
+ ret->frame = 0;
+ return ret;
+}
+
+void destroy_vkcontext(struct vkcontext *context) {
+ log_output(LOG_DEBUG, "Closing Vulkan instance");
+ 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);
+}
+
+struct vklayer_container* init_vklayers(struct ext_container *ext) {
+ const char** new_extensions = rune_alloc(sizeof(char*) * ext->ext_count++);
+ 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);
+
+ struct vklayer_container *ret = rune_alloc(sizeof(struct vklayer_container));
+ 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/render/vulkan/context.h b/render/vulkan/context.h
new file mode 100644
index 0000000..14a86f1
--- /dev/null
+++ b/render/vulkan/context.h
@@ -0,0 +1,43 @@
+#ifndef VKCONTEXT_H
+#define VKCONTEXT_H
+
+#include "surface.h"
+#include "device.h"
+#include "swapchain.h"
+#include "renderpass.h"
+#include "framebuffer.h"
+#include "fence.h"
+
+struct ext_container {
+ const char** extensions;
+ uint32_t ext_count;
+};
+
+struct vklayer_container {
+ const char** vklayer_names;
+ uint32_t vklayer_count;
+};
+
+struct vkcontext {
+ VkInstance instance;
+ VkDebugUtilsMessengerEXT db_messenger;
+ VkSemaphore *queue_semaphores;
+ VkSemaphore *image_semaphores;
+ struct vksurface *surface;
+ struct vkswapchain *swapchain;
+ struct vkrendpass *rendpass;
+ struct vkdev *dev;
+ struct vkcmdbuffer** cmdbuffers;
+ struct vkframebuffer** framebuffers;
+ struct vkfence** fences_in_flight;
+ struct vkfence** images_in_flight;
+ uint32_t num_fences_in_flight;
+ uint32_t img_index;
+};
+
+struct vkcontext* create_vkcontext(struct vklayer_container *vklayers, struct ext_container *ext);
+void destroy_vkcontext(struct vkcontext *context);
+
+struct vklayer_container* init_vklayers(struct ext_container *ext);
+
+#endif
diff --git a/render/vulkan/device.c b/render/vulkan/device.c
new file mode 100644
index 0000000..02cf246
--- /dev/null
+++ b/render/vulkan/device.c
@@ -0,0 +1,213 @@
+#include "device.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+#include <rune/core/logging.h>
+
+struct vkqfam_data {
+ uint32_t gfx;
+ uint32_t present;
+ uint32_t compute;
+ uint32_t transfer;
+};
+
+struct vkdev_data {
+ VkPhysicalDeviceProperties pdev_props;
+ VkPhysicalDeviceFeatures pdev_feats;
+ VkPhysicalDeviceMemoryProperties pdev_mprops;
+ uint32_t pdev_ext_count;
+ const char** pdev_extensions;
+};
+
+int _query_qfam_data(VkSurfaceKHR surface, VkPhysicalDevice pdev, VkQueueFamilyProperties **qfam_props) {
+ uint32_t qfam_count = 0;
+ vkGetPhysicalDeviceQueueFamilyProperties(pdev, &qfam_count, NULL);
+ *qfam_props = rune_alloc(sizeof(VkQueueFamilyProperties) * qfam_count);
+ vkGetPhysicalDeviceQueueFamilyProperties(pdev, &qfam_count, *qfam_props);
+ return qfam_count;
+}
+
+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);
+}
+
+int _get_qfam_index(int num_props, uint32_t queue_type, VkQueueFamilyProperties *qfam_props) {
+ for (int i = 0; i < num_props; i++) {
+ if (qfam_props[i].queueFlags & queue_type)
+ return i;
+ }
+ return -1;
+}
+
+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;
+ int num_qfams = _query_qfam_data(surface, pdev, &qfam_props);
+ if (_get_qfam_index(num_qfams, VK_QUEUE_GRAPHICS_BIT, qfam_props) != -1)
+ score += 20;
+ if (_get_qfam_index(num_qfams, VK_QUEUE_COMPUTE_BIT, qfam_props) != -1)
+ score += 5;
+ if (_get_qfam_index(num_qfams, VK_QUEUE_TRANSFER_BIT, qfam_props) != -1)
+ score += 20;
+
+ VkBool32 present_bit;
+ vkGetPhysicalDeviceSurfaceSupportKHR(pdev, 0, surface, &present_bit);
+ if (present_bit != VK_FALSE)
+ score += 20;
+
+ rune_free(qfam_props);
+ return score;
+}
+
+struct vkdev* create_vkdev(VkInstance instance, VkSurfaceKHR surface) {
+ uint32_t pdev_count;
+ vkEnumeratePhysicalDevices(instance, &pdev_count, NULL);
+ if (pdev_count == 0)
+ return NULL;
+ VkPhysicalDevice pdevs[pdev_count];
+ vkEnumeratePhysicalDevices(instance, &pdev_count, pdevs);
+
+ uint32_t selected_pdev = -1;
+ for (uint32_t i = 0; i < pdev_count; i++) {
+ if (_check_pdev(surface, pdevs[i]) >= 60) {
+ selected_pdev = i;
+ break;
+ }
+ }
+
+ if (selected_pdev == -1) {
+ log_output(LOG_FATAL, "No device meets minimum requirements for rendering");
+ return NULL;
+ }
+
+ struct vkdev *dev = rune_alloc(sizeof(struct vkdev));
+ dev->pdev = pdevs[selected_pdev];
+
+ VkQueueFamilyProperties *qfam_props;
+ int num_qfams = _query_qfam_data(surface, pdevs[selected_pdev], &qfam_props);
+ VkBool32 present_support;
+ for (int i = 0; i < num_qfams; i++) {
+ vkassert(vkGetPhysicalDeviceSurfaceSupportKHR(dev->pdev, i, surface, &present_support), "Error retrieving present queue support");
+ if (present_support == VK_TRUE)
+ dev->pres_index = i;
+ }
+ dev->gfx_index = _get_qfam_index(num_qfams, VK_QUEUE_GRAPHICS_BIT, qfam_props);
+ dev->tsfr_index = _get_qfam_index(num_qfams, VK_QUEUE_TRANSFER_BIT, qfam_props);
+ dev->comp_index = _get_qfam_index(num_qfams, VK_QUEUE_COMPUTE_BIT, qfam_props);
+
+ float queue_priority = 1.0f;
+ VkDeviceQueueCreateInfo qcinfos[3];
+ qcinfos[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ qcinfos[0].queueFamilyIndex = dev->gfx_index;
+ qcinfos[0].queueCount = 1;
+ qcinfos[0].flags = 0;
+ qcinfos[0].pNext = NULL;
+ qcinfos[0].pQueuePriorities = &queue_priority;
+
+ qcinfos[1].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ qcinfos[1].queueFamilyIndex = dev->tsfr_index;
+ qcinfos[1].queueCount = 1;
+ qcinfos[1].flags = 0;
+ qcinfos[1].pNext = NULL;
+ qcinfos[1].pQueuePriorities = &queue_priority;
+
+ qcinfos[2].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
+ qcinfos[2].queueFamilyIndex = dev->comp_index;
+ qcinfos[2].queueCount = 1;
+ qcinfos[2].flags = 0;
+ qcinfos[2].pNext = NULL;
+ qcinfos[2].pQueuePriorities = &queue_priority;
+
+ struct vkdev_data pdata;
+ _query_pdev_data(pdevs[selected_pdev], &pdata);
+
+ VkDeviceCreateInfo dcinfo;
+ dcinfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
+ dcinfo.pNext = NULL;
+ dcinfo.flags = 0;
+ dcinfo.queueCreateInfoCount = 2;
+ 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), "Error creating Vulkan logical device");
+
+ vkGetDeviceQueue(dev->ldev, dev->gfx_index, 0, &dev->gfx_queue);
+ vkGetDeviceQueue(dev->ldev, dev->tsfr_index, 0, &dev->tsfr_queue);
+ vkGetDeviceQueue(dev->ldev, dev->pres_index, 0, &dev->pres_queue);
+ vkGetDeviceQueue(dev->ldev, dev->comp_index, 0, &dev->comp_queue);
+
+ 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 = dev->gfx_index;
+ vkassert(vkCreateCommandPool(dev->ldev, &pcinfo, NULL, &dev->cmd_pool), "Error creating Vulkan command pool");
+
+ log_output(LOG_DEBUG, "Initialized new logical device");
+ return dev;
+}
+
+void destroy_vkdev(struct vkdev *dev) {
+ vkDestroyDevice(dev->ldev, NULL);
+ rune_free(dev);
+}
+
+void get_swapchain_data(struct vkdev *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(struct vkdev *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(struct vkdev *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/render/vulkan/device.h b/render/vulkan/device.h
new file mode 100644
index 0000000..6bdd4f5
--- /dev/null
+++ b/render/vulkan/device.h
@@ -0,0 +1,60 @@
+/*
+ * 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 <rune/util/types.h>
+#include <vulkan/vulkan.h>
+#include <GLFW/glfw3.h>
+
+struct vkswapchain_data {
+ VkSurfaceCapabilitiesKHR capabilities;
+ VkSurfaceFormatKHR *formats;
+ VkPresentModeKHR *present_modes;
+ uint32_t format_count;
+ uint32_t present_count;
+};
+
+struct vkdev {
+ VkPhysicalDevice pdev;
+ VkDevice ldev;
+ struct vkswapchain_data scdata;
+ uint32_t gfx_index;
+ uint32_t pres_index;
+ uint32_t tsfr_index;
+ uint32_t comp_index;
+ VkQueue gfx_queue;
+ VkQueue pres_queue;
+ VkQueue tsfr_queue;
+ VkQueue comp_queue;
+ VkCommandPool cmd_pool;
+ VkFormat depth_format;
+};
+
+struct vkdev* create_vkdev(VkInstance instance, VkSurfaceKHR surface);
+void destroy_vkdev(struct vkdev *dev);
+
+void get_swapchain_data(struct vkdev *dev, VkSurfaceKHR *surface);
+int get_depth_format(struct vkdev *dev);
+uint32_t get_memory_index(struct vkdev *dev, uint32_t type, uint32_t flags);
+
+#endif
diff --git a/render/vulkan/fence.c b/render/vulkan/fence.c
new file mode 100644
index 0000000..6d869c9
--- /dev/null
+++ b/render/vulkan/fence.c
@@ -0,0 +1,63 @@
+#include "fence.h"
+#include "vkassert.h"
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+
+struct vkfence* create_vkfence(struct vkdev *dev, uint8_t signal) {
+ struct vkfence *ret = rune_alloc(sizeof(struct vkfence));
+
+ 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), "Failed to create fence");
+ return ret;
+}
+
+void destroy_vkfence(struct vkfence *fence, struct vkdev *dev) {
+ if (fence->handle != NULL) {
+ vkDestroyFence(dev->ldev, fence->handle, NULL);
+ fence->handle = NULL;
+ }
+ rune_free(fence);
+}
+
+uint8_t fence_lock(struct vkfence *fence, struct vkdev *dev, uint64_t timeout) {
+ if (fence->signal)
+ return 1;
+
+ VkResult res = vkWaitForFences(dev->ldev, 1, &fence->handle, VK_TRUE, timeout);
+ switch (res) {
+ case VK_SUCCESS:
+ fence->signal = 1;
+ return 1;
+ 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 0;
+}
+
+void fence_unlock(struct vkfence *fence, struct vkdev *dev) {
+ if (fence->signal == 0)
+ return;
+
+ vkassert(vkResetFences(dev->ldev, 1, &fence->handle), "Cannot reset Vulkan fence");
+ fence->signal = 1;
+}
diff --git a/render/vulkan/fence.h b/render/vulkan/fence.h
new file mode 100644
index 0000000..34af87d
--- /dev/null
+++ b/render/vulkan/fence.h
@@ -0,0 +1,19 @@
+#ifndef VKFENCE_H
+#define VKFENCE_H
+
+#include "device.h"
+#include <rune/util/types.h>
+#include <vulkan/vulkan.h>
+
+struct vkfence {
+ VkFence handle;
+ int signal;
+};
+
+struct vkfence* create_vkfence(struct vkdev *dev, uint8_t signal);
+void destroy_vkfence(struct vkfence *fence, struct vkdev *dev);
+
+uint8_t fence_lock(struct vkfence *fence, struct vkdev *dev, uint64_t timeout);
+void fence_unlock(struct vkfence *fence, struct vkdev *dev);
+
+#endif
diff --git a/render/vulkan/framebuffer.c b/render/vulkan/framebuffer.c
new file mode 100644
index 0000000..36d90c0
--- /dev/null
+++ b/render/vulkan/framebuffer.c
@@ -0,0 +1,34 @@
+#include "framebuffer.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+#include <rune/core/logging.h>
+
+struct vkframebuffer* create_vkframebuffer(struct vkdev *dev, struct vkrendpass *rendpass, uint32_t width, uint32_t height, uint32_t at_count, VkImageView *at) {
+ struct vkframebuffer *ret = rune_alloc(sizeof(struct vkframebuffer));
+ 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), "Failed to create Vulkan framebuffer");
+
+ return ret;
+}
+
+void destroy_vkframebuffer(struct vkframebuffer *framebuffer, struct vkdev *dev) {
+ vkDestroyFramebuffer(dev->ldev, framebuffer->handle, NULL);
+ if (framebuffer->attachments)
+ rune_free(framebuffer->attachments);
+ rune_free(framebuffer);
+}
diff --git a/render/vulkan/framebuffer.h b/render/vulkan/framebuffer.h
new file mode 100644
index 0000000..4bd7e86
--- /dev/null
+++ b/render/vulkan/framebuffer.h
@@ -0,0 +1,17 @@
+#ifndef VKFRAMEBUFFER_H
+#define VKFRAMEBUFFER_H
+
+#include "device.h"
+#include "renderpass.h"
+
+struct vkframebuffer {
+ VkFramebuffer handle;
+ uint32_t at_count;
+ VkImageView *attachments;
+ struct vkrendpass *rendpass;
+};
+
+struct vkframebuffer* create_vkframebuffer(struct vkdev *dev, struct vkrendpass *rendpass, uint32_t width, uint32_t height, uint32_t at_count, VkImageView *at);
+void destroy_vkframebuffer(struct vkframebuffer *framebuffer, struct vkdev *dev);
+
+#endif
diff --git a/render/vulkan/image.c b/render/vulkan/image.c
new file mode 100644
index 0000000..9142f9e
--- /dev/null
+++ b/render/vulkan/image.c
@@ -0,0 +1,71 @@
+#include "image.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+
+int _create_image_view(struct vkimage *image, struct vkdev *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.format = format;
+ 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), "Failed to create image view");
+}
+
+struct vkimage* create_vkimage(struct vkdev *dev, VkFormat format, uint32_t width, uint32_t height, uint32_t usage, uint32_t mem_flags, uint32_t aflags, int create_view) {
+ struct vkimage *ret = rune_alloc(sizeof(struct vkimage));
+ 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), "Failed to create image");
+
+ 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.allocationSize = mem_req.size;
+ mainfo.memoryTypeIndex = mem_type;
+ vkassert(vkAllocateMemory(dev->ldev, &mainfo, NULL, &ret->memory), "Failed to allocate image memory");
+ vkassert(vkBindImageMemory(dev->ldev, ret->handle, ret->memory, 0), "Failed to bind image memory");
+
+ if (create_view == 1)
+ _create_image_view(ret, dev, format, aflags);
+
+ return ret;
+}
+
+void destroy_vkimage(struct vkimage *image, struct vkdev *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/render/vulkan/image.h b/render/vulkan/image.h
new file mode 100644
index 0000000..9e5b101
--- /dev/null
+++ b/render/vulkan/image.h
@@ -0,0 +1,19 @@
+#ifndef VKIMAGE_H
+#define VKIMAGE_H
+
+#include <rune/util/types.h>
+#include "surface.h"
+#include "device.h"
+
+struct vkimage {
+ VkImage handle;
+ VkDeviceMemory memory;
+ VkImageView view;
+ uint32_t width;
+ uint32_t height;
+};
+
+struct vkimage* create_vkimage(struct vkdev *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(struct vkimage *image, struct vkdev *dev);
+
+#endif
diff --git a/render/vulkan/renderer.c b/render/vulkan/renderer.c
new file mode 100644
index 0000000..91dc3fb
--- /dev/null
+++ b/render/vulkan/renderer.c
@@ -0,0 +1,167 @@
+#include <vulkan/vulkan.h>
+#include <GLFW/glfw3.h>
+#include <rune/ui/renderer.h>
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+#include <rune/core/abort.h>
+#include "context.h"
+#include "image.h"
+#include <sys/time.h>
+
+static struct vkcontext *context = NULL;
+
+void _init_cmdbuffers(void) {
+ uint32_t num_buffers = context->swapchain->img_count;
+ if (context->cmdbuffers == NULL)
+ context->cmdbuffers = rune_calloc(0, sizeof(struct vkcmdbuffer*) * 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(struct vkframebuffer*) * 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(struct rune_window *window) {
+ struct timeval start;
+ struct timeval stop;
+ gettimeofday(&start, NULL);
+
+ struct ext_container ext;
+ ext.extensions = glfwGetRequiredInstanceExtensions(&ext.ext_count);
+ struct vklayer_container *vklayers = init_vklayers(&ext);
+
+ context = create_vkcontext(vklayers, &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);
+ 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(struct vkfence*) * 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(struct vkfence*) * 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);
+}
+
+void _draw_vulkan(void) {
+ if (fence_lock(context->fences_in_flight[context->swapchain->frame], context->dev, UINT64_MAX) != 0)
+ log_output(LOG_WARN, "In-flight fence locking failure");
+}
+
+void _clear_vulkan(void) {
+}
+
+struct rune_renderer* select_render_vulkan(struct rune_window *window) {
+ struct rune_renderer *ret = rune_alloc(sizeof(struct rune_renderer));
+ ret->init = _init_vulkan;
+ ret->close = _close_vulkan;
+ ret->draw = _draw_vulkan;
+ ret->clear = _clear_vulkan;
+ if ((*ret->init)(window) != 0)
+ rune_abort();
+ return ret;
+}
diff --git a/render/vulkan/renderpass.c b/render/vulkan/renderpass.c
new file mode 100644
index 0000000..59d1c26
--- /dev/null
+++ b/render/vulkan/renderpass.c
@@ -0,0 +1,172 @@
+#include "renderpass.h"
+#include "vkassert.h"
+#include <rune/core/alloc.h>
+
+struct vkcmdbuffer* create_vkcmdbuffer(struct vkdev *dev, int primary) {
+ struct vkcmdbuffer *ret = rune_calloc(0, sizeof(struct vkcmdbuffer));
+
+ VkCommandBufferAllocateInfo ainfo;
+ ainfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+ ainfo.pNext = NULL;
+ ainfo.commandPool = dev->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), "Failed to initialize command buffer");
+
+ ret->state = CMDBUF_READY;
+ return ret;
+}
+
+void destroy_vkcmdbuffer(struct vkcmdbuffer *cmdbuffer, struct vkdev *dev) {
+ vkFreeCommandBuffers(dev->ldev, dev->cmd_pool, 1, &cmdbuffer->handle);
+ rune_free(cmdbuffer);
+}
+
+void cmdbuf_begin(struct vkcmdbuffer *cmdbuffer, int single, int rpass_cont, int sim_use) {
+ 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), "Cannot record commands into command buffer");
+ cmdbuffer->state = CMDBUF_RECORD;
+}
+
+void cmdbuf_end(struct vkcmdbuffer *cmdbuffer) {
+ vkassert(vkEndCommandBuffer(cmdbuffer->handle), "Cannot end command buffer recording");
+ cmdbuffer->state = CMDBUF_ENDREC;
+}
+
+struct vkcmdbuffer* cmdbuf_begin_single_use(struct vkdev *dev) {
+ struct vkcmdbuffer *ret = create_vkcmdbuffer(dev, 1);
+ cmdbuf_begin(ret, 1, 0, 0);
+ return ret;
+}
+
+void cmdbuf_end_single_use(struct vkcmdbuffer *cmdbuffer, struct vkdev *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), "Cannot submit single use command buffer");
+
+ vkassert(vkQueueWaitIdle(queue), "Error while waiting for Vulkan queue");
+ destroy_vkcmdbuffer(cmdbuffer, dev);
+}
+
+struct vkrendpass* create_vkrendpass(struct vkdev *dev, struct vkswapchain *swapchain, vec4 area, vec4 color, float depth, uint32_t stencil) {
+ VkAttachmentDescription atdesc[2];
+ atdesc[0].flags = 0;
+ atdesc[0].format = swapchain->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;
+
+ struct vkrendpass *ret = rune_alloc(sizeof(struct vkrendpass));
+ 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), "Failed to create renderpass");
+
+ log_output(LOG_DEBUG, "Initialized renderpass");
+ return ret;
+}
+
+void destroy_vkrendpass(struct vkrendpass *rendpass, struct vkdev *dev) {
+ if (rendpass->handle)
+ vkDestroyRenderPass(dev->ldev, rendpass->handle, NULL);
+ rune_free(rendpass);
+}
+
+void renderpass_begin(struct vkcmdbuffer *buf, struct vkrendpass *rendpass, VkFramebuffer framebuf) {
+ 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.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_RPASS;
+}
+
+void renderpass_end(struct vkcmdbuffer *buf, struct vkrendpass *rendpass) {
+ vkCmdEndRenderPass(buf->handle);
+ buf->state = CMDBUF_RECORD;
+}
diff --git a/render/vulkan/renderpass.h b/render/vulkan/renderpass.h
new file mode 100644
index 0000000..9fbb417
--- /dev/null
+++ b/render/vulkan/renderpass.h
@@ -0,0 +1,42 @@
+#ifndef VKRENDERPASS_H
+#define VKRENDERPASS_H
+
+#include "swapchain.h"
+#include "device.h"
+#include <rune/util/types.h>
+#include <cglm/cglm.h>
+
+enum cmdbuf_state {
+ CMDBUF_READY,
+ CMDBUF_RECORD,
+ CMDBUF_RPASS,
+ CMDBUF_ENDREC,
+ CMDBUF_SUBMIT
+};
+
+struct vkcmdbuffer {
+ VkCommandBuffer handle;
+ int state;
+};
+
+struct vkrendpass {
+ VkRenderPass handle;
+ vec4 area;
+ vec4 color;
+ float depth;
+ uint32_t stencil;
+};
+
+struct vkcmdbuffer* create_vkcmdbuffer(struct vkdev *dev, int primary);
+void destroy_vkcmdbuffer(struct vkcmdbuffer *cmdbuffer, struct vkdev *dev);
+
+void cmdbuf_begin(struct vkcmdbuffer *cmdbuffer, int single, int rpass_cont, int sim_use);
+void cmdbuf_end(struct vkcmdbuffer *cmdbuffer);
+
+struct vkrendpass* create_vkrendpass(struct vkdev *dev, struct vkswapchain *swapchain, vec4 area, vec4 color, float depth, uint32_t stencil);
+void destroy_vkrendpass(struct vkrendpass *rendpass, struct vkdev *dev);
+
+void renderpass_begin(struct vkcmdbuffer *buf, struct vkrendpass *rendpass, VkFramebuffer framebuf);
+void renderpass_end(struct vkcmdbuffer *buf, struct vkrendpass *rendpass);
+
+#endif
diff --git a/render/vulkan/surface.h b/render/vulkan/surface.h
new file mode 100644
index 0000000..8d59ab8
--- /dev/null
+++ b/render/vulkan/surface.h
@@ -0,0 +1,13 @@
+#ifndef VKSURFACE_H
+#define VKSURFACE_H
+
+#include <vulkan/vulkan.h>
+#include <GLFW/glfw3.h>
+
+struct vksurface {
+ VkSurfaceKHR handle;
+ uint32_t width;
+ uint32_t height;
+};
+
+#endif
diff --git a/render/vulkan/swapchain.c b/render/vulkan/swapchain.c
new file mode 100644
index 0000000..18b87a5
--- /dev/null
+++ b/render/vulkan/swapchain.c
@@ -0,0 +1,112 @@
+#include "swapchain.h"
+#include "vkassert.h"
+#include "image.h"
+#include <rune/core/logging.h>
+#include <rune/core/alloc.h>
+#include <rune/util/stubbed.h>
+
+struct vkswapchain* create_swapchain(struct vksurface *surface, struct vkdev *dev) {
+ struct vkswapchain *swapchain = rune_alloc(sizeof(struct vkswapchain));
+ 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.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_index != dev->pres_index) {
+ uint32_t qfams[] = {dev->gfx_index, dev->pres_index};
+ 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), "Failed to create swapchain");
+ vkassert(vkGetSwapchainImagesKHR(dev->ldev, swapchain->handle, &swapchain->img_count, NULL), "Failed to aquire swapchain image count");
+
+ 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), "Failed to aquire swapchain images");
+
+ VkImageViewCreateInfo vcinfo;
+ for (uint32_t i = 0; i < swapchain->img_count; i++) {
+ vcinfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
+ vcinfo.image = swapchain->images[i];
+ vcinfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
+ vcinfo.format = swapchain->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]), "Failed to create image view");
+ }
+
+ 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);
+ log_output(LOG_DEBUG, "Initialized swapchain");
+ return swapchain;
+}
+
+void destroy_swapchain(struct vkswapchain *swapchain, struct vkdev *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);
+}
+
+void vkswapchain_present(struct vkswapchain *swapchain, struct vkdev *dev) {
+ VkPresentInfoKHR pinfo = {VK_STRUCTURE_TYPE_PRESENT_INFO_KHR};
+ pinfo.waitSemaphoreCount = 1;
+ pinfo.pWaitSemaphores = &swapchain->render_complete;
+ pinfo.swapchainCount = 1;
+ pinfo.pSwapchains = &swapchain->handle;
+ pinfo.pImageIndices = &dev->pres_index;
+
+ 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_FATAL, "Cannot present swapchain image");
+
+ swapchain->frame = (swapchain->frame + 1) % swapchain->max_frames;
+}
diff --git a/render/vulkan/swapchain.h b/render/vulkan/swapchain.h
new file mode 100644
index 0000000..d2f3d89
--- /dev/null
+++ b/render/vulkan/swapchain.h
@@ -0,0 +1,25 @@
+#ifndef VKSWAPCHAIN_H
+#define VKSWAPCHAIN_H
+
+#include "surface.h"
+#include "device.h"
+
+struct vkswapchain {
+ VkSwapchainKHR handle;
+ VkSurfaceFormatKHR format_khr;
+ VkFormat format;
+ VkSemaphore render_complete;
+ VkImage *images;
+ VkImageView *views;
+ struct vkimage *depth_attachment;
+ uint8_t max_frames;
+ uint32_t frame;
+ uint32_t img_count;
+};
+
+struct vkswapchain* create_swapchain(struct vksurface *surface, struct vkdev *dev);
+void destroy_swapchain(struct vkswapchain *swapchain, struct vkdev *dev);
+
+void vkswapchain_present(struct vkswapchain *swapchain, struct vkdev *dev);
+
+#endif
diff --git a/render/vulkan/vkassert.h b/render/vulkan/vkassert.h
new file mode 100644
index 0000000..393e2de
--- /dev/null
+++ b/render/vulkan/vkassert.h
@@ -0,0 +1,15 @@
+#ifndef VKASSERT_H
+#define VKASSERT_H
+
+#include <rune/core/logging.h>
+#include <rune/core/abort.h>
+#include <vulkan/vulkan.h>
+
+static inline void vkassert(VkResult value, const char *str) {
+ if (value != VK_SUCCESS) {
+ log_output(LOG_ERROR, "Vulkan assertion failed: %s", str);
+ rune_abort();
+ }
+}
+
+#endif