diff options
Diffstat (limited to 'engine/render/vulkan')
-rw-r--r-- | engine/render/vulkan/context.c | 217 | ||||
-rw-r--r-- | engine/render/vulkan/context.h | 32 | ||||
-rw-r--r-- | engine/render/vulkan/device.c | 324 | ||||
-rw-r--r-- | engine/render/vulkan/device.h | 34 | ||||
-rw-r--r-- | engine/render/vulkan/fence.c | 84 | ||||
-rw-r--r-- | engine/render/vulkan/fence.h | 33 | ||||
-rw-r--r-- | engine/render/vulkan/framebuffer.c | 55 | ||||
-rw-r--r-- | engine/render/vulkan/framebuffer.h | 30 | ||||
-rw-r--r-- | engine/render/vulkan/image.c | 99 | ||||
-rw-r--r-- | engine/render/vulkan/image.h | 30 | ||||
-rw-r--r-- | engine/render/vulkan/renderer.c | 279 | ||||
-rw-r--r-- | engine/render/vulkan/renderpass.c | 249 | ||||
-rw-r--r-- | engine/render/vulkan/renderpass.h | 49 | ||||
-rw-r--r-- | engine/render/vulkan/swapchain.c | 152 | ||||
-rw-r--r-- | engine/render/vulkan/swapchain.h | 33 | ||||
-rw-r--r-- | engine/render/vulkan/vk_types.h | 143 | ||||
-rw-r--r-- | engine/render/vulkan/vkassert.h | 68 |
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 |