#include "lrhi.h" #include #include #include #include #include #include #include #include enum { MAX_FRAME_IN_FLIGHT = 2 }; static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData) { (void)messageSeverity; (void)messageType; (void)pUserData; LRHI_LOG("validation layer: %s", pCallbackData->pMessage); return VK_FALSE; } static VkResult create_debug_utils_messenger_ext( VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDebugUtilsMessengerEXT *pDebugMessenger) { PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkCreateDebugUtilsMessengerEXT"); if (func != NULL) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void destroy_debug_utils_messenger_ext( VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks *pAllocator) { PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != NULL) { func(instance, debugMessenger, pAllocator); } } static bool are_validation_layers_supported(const char **validation_layers, const uint32_t validation_layer_count) { enum { AVAILABLE_LAYERS_BUF_SIZE = 128 }; VkLayerProperties available_layers_buf[AVAILABLE_LAYERS_BUF_SIZE] = {0}; uint32_t available_layer_count; vkEnumerateInstanceLayerProperties(&available_layer_count, NULL); if (available_layer_count > AVAILABLE_LAYERS_BUF_SIZE) { LRHI_LOG("available layer count > buf size, will be truncated"); available_layer_count = AVAILABLE_LAYERS_BUF_SIZE; } vkEnumerateInstanceLayerProperties(&available_layer_count, available_layers_buf); for (uint32_t layer_idx = 0; layer_idx < validation_layer_count; layer_idx++) { const char *required_layer = validation_layers[layer_idx]; bool layer_found = false; for (uint32_t available_layer_idx = 0; available_layer_idx < available_layer_count; available_layer_idx++) { VkLayerProperties *available_layer = &available_layers_buf[available_layer_idx]; if (strcmp(required_layer, available_layer->layerName) == 0) { layer_found = true; break; } } if (!layer_found) { return false; } } return true; } static void get_required_instance_extensions( enum lrhi_window_backend window_backend, const char **required_extensions, uint32_t required_extensions_capacity, uint32_t *required_extension_count, bool enable_validation_layers) { // uint32_t platform_required_extension_count; // const char *const *platform_required_extensions = // lrhi_platform_get_vulkan_required_instance_extensions( // platform, &platform_required_extension_count); uint32_t ext_index = 0; // for (uint32_t i = 0; i < platform_required_extension_count; i++) { // if (ext_index >= required_extensions_capacity) { // LRHI_LOG("Platform required extension count too large for the required // " // "extensions buffer, will be truncated."); // break; // } // required_extensions[ext_index++] = platform_required_extensions[i]; // } if (window_backend == LRHI_WINDOW_BACKEND_X11) { required_extensions[ext_index++] = VK_KHR_SURFACE_EXTENSION_NAME; required_extensions[ext_index++] = VK_KHR_XLIB_SURFACE_EXTENSION_NAME; } else { LRHI_PANIC("Unsupported window backend."); } if (ext_index < required_extensions_capacity && enable_validation_layers) { required_extensions[ext_index++] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; } *required_extension_count = ext_index; } static const char *REQUIRED_VALIDATION_LAYERS[] = { "VK_LAYER_KHRONOS_validation"}; static const uint32_t REQUIRED_VALIDATION_LAYER_COUNT = ARRAY_LENGTH(REQUIRED_VALIDATION_LAYERS); struct lrhi_instance { VkInstance instance; VkDebugUtilsMessengerEXT debug_messenger; struct lrhi_allocator allocator; bool enable_validation_layers; }; lrhi_instance *lrhi_instance_create(struct lrhi_allocator *allocator, enum lrhi_window_backend window_backend, const struct lrhi_instance_desc *desc) { struct lrhi_instance *instance = lrhi_allocator_allocate(allocator, sizeof(struct lrhi_instance)); instance->allocator = *allocator; // Creating instance and debug messenger if (desc->enable_validation_layers) { if (!are_validation_layers_supported(REQUIRED_VALIDATION_LAYERS, REQUIRED_VALIDATION_LAYER_COUNT)) { LRHI_LOG_ERR("required validation layers are not supported."); goto fail; } } VkApplicationInfo application_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "lrhi", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "none", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_3}; VkInstanceCreateInfo instance_create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &application_info}; enum { REQUIRED_EXTENSIONS_BUF_SIZE = 256 }; const char *required_extensions[REQUIRED_EXTENSIONS_BUF_SIZE]; uint32_t required_extension_count = 0; get_required_instance_extensions( window_backend, required_extensions, REQUIRED_EXTENSIONS_BUF_SIZE, &required_extension_count, desc->enable_validation_layers); instance_create_info.enabledExtensionCount = required_extension_count; instance_create_info.ppEnabledExtensionNames = required_extensions; VkDebugUtilsMessengerCreateInfoEXT debug_utils_messenger_create_info = { .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, .pfnUserCallback = debug_callback, }; if (desc->enable_validation_layers) { instance_create_info.enabledLayerCount = REQUIRED_VALIDATION_LAYER_COUNT; instance_create_info.ppEnabledLayerNames = REQUIRED_VALIDATION_LAYERS; instance_create_info.pNext = (VkDebugUtilsMessengerCreateInfoEXT *)&debug_utils_messenger_create_info; } else { instance_create_info.enabledLayerCount = 0; instance_create_info.pNext = NULL; } if (vkCreateInstance(&instance_create_info, NULL, &instance->instance) != VK_SUCCESS) { LRHI_LOG_ERR("Vulkan instance creation failed."); goto fail; } LRHI_LOG("Vulkan instance created."); if (desc->enable_validation_layers) { if (create_debug_utils_messenger_ext( instance->instance, &debug_utils_messenger_create_info, NULL, &instance->debug_messenger) != VK_SUCCESS) { LRHI_LOG_ERR("Couldn't create VkDebugUtilsMessengerCreateInfoEXT"); goto destroy_instance; } instance->enable_validation_layers = true; } return instance; destroy_instance: vkDestroyInstance(instance->instance, NULL); fail: lrhi_allocator_free(allocator, sizeof(struct lrhi_instance), instance); return NULL; } void lrhi_instance_destroy(struct lrhi_allocator *allocator, lrhi_instance *instance) { if (instance->enable_validation_layers) { destroy_debug_utils_messenger_ext(instance->instance, instance->debug_messenger, NULL); } vkDestroyInstance(instance->instance, NULL); lrhi_allocator_free(allocator, sizeof(struct lrhi_instance), instance); } struct lrhi_texture { VkImage image; // Note: These fields are NULL for the swapchain textures VmaAllocation allocation; VmaAllocationInfo allocation_info; }; struct lrhi_texture_view { VkImageView view; }; #define VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY 32 struct lrhi_surface { VkSurfaceKHR surface; VkSwapchainKHR swapchain; lrhi_texture images[VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY]; lrhi_texture_view image_views[VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY]; VkSemaphore image_available_semaphore[MAX_FRAME_IN_FLIGHT]; VkSemaphore render_finished_semaphore[VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY]; VkExtent2D new_extent; VkExtent2D extent; VkFormat fmt; lrhi_surface_reconfigured_callback surface_reconfigured_callback; void *surface_reconfigured_user_data; bool resized; uint32_t img_count; uint32_t image_index; }; lrhi_surface *lrhi_instance_create_surface( lrhi_instance *instance, struct lrhi_native_surface *native_surface, lrhi_surface_reconfigured_callback cb, void *user_data) { lrhi_surface *surface = lrhi_allocator_allocate(&instance->allocator, sizeof(struct lrhi_surface)); surface->swapchain = NULL; if (native_surface->backend == LRHI_WINDOW_BACKEND_X11) { VkXlibSurfaceCreateInfoKHR surface_create_info = { .sType = VK_STRUCTURE_TYPE_XLIB_SURFACE_CREATE_INFO_KHR, .dpy = (Display *)native_surface->display_hnd, .window = (Window)(uintptr_t)native_surface->window_hnd}; if (vkCreateXlibSurfaceKHR(instance->instance, &surface_create_info, NULL, &surface->surface) != VK_SUCCESS) { LRHI_LOG_ERR("Vulkan surface creation failed."); goto fail; } } else { LRHI_PANIC("Unsupported window backend."); } surface->surface_reconfigured_callback = cb; surface->surface_reconfigured_user_data = user_data; return surface; fail: lrhi_allocator_free(&instance->allocator, sizeof(struct lrhi_surface), surface); return NULL; } struct queue_family_indices { uint32_t graphics_family; uint32_t present_family; bool has_graphics; bool has_present; }; static bool queue_family_indices_complete(const struct queue_family_indices *indices) { return indices->has_graphics && indices->has_present; } static struct queue_family_indices queue_family_indices_find_for_device(VkPhysicalDevice device, VkSurfaceKHR surface) { struct queue_family_indices indices = {0}; enum { QUEUE_FAMILIES_BUF_SIZE = 64 }; VkQueueFamilyProperties queue_families[QUEUE_FAMILIES_BUF_SIZE] = {0}; uint32_t queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, NULL); if (queue_family_count > QUEUE_FAMILIES_BUF_SIZE) { LRHI_LOG("Physical device queue family count too large for the queue " "families buffer, will be truncated."); queue_family_count = QUEUE_FAMILIES_BUF_SIZE; } vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_families); for (uint32_t queue_family_idx = 0; queue_family_idx < queue_family_count; queue_family_idx++) { VkQueueFamilyProperties *queue_family = &queue_families[queue_family_idx]; if (queue_family->queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphics_family = queue_family_idx; indices.has_graphics = true; } VkBool32 present_support; vkGetPhysicalDeviceSurfaceSupportKHR(device, queue_family_idx, surface, &present_support); if (present_support) { indices.present_family = queue_family_idx; indices.has_present = true; } if (queue_family_indices_complete(&indices)) { break; } } return indices; } static const char *REQUIRED_DEVICE_EXTS[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, #ifdef LRHI_PLATFORM_MACOS "VK_KHR_portability_subset", #endif // LRHI_PLATFORM_MACOS }; #define SUPPORTED_EXT_BUFFER_CAPACITY 1024 static const uint32_t REQUIRED_DEVICE_EXT_COUNT = sizeof(REQUIRED_DEVICE_EXTS) / sizeof(REQUIRED_DEVICE_EXTS[0]); static bool are_required_device_extensions_supported(VkPhysicalDevice pd) { // Fetch supported extensions uint32_t supported_ext_count = 0; if (vkEnumerateDeviceExtensionProperties(pd, NULL, &supported_ext_count, NULL) != VK_SUCCESS) { LRHI_LOG("Couldn't enumerate supported device extensions"); goto err; } if (supported_ext_count > SUPPORTED_EXT_BUFFER_CAPACITY) { LRHI_LOG( "There are %u supported extensions for this device, but the render " "backend " "buffer for them " "has a capacity of %u, some of the device extensions will be ignored", supported_ext_count, SUPPORTED_EXT_BUFFER_CAPACITY); supported_ext_count = SUPPORTED_EXT_BUFFER_CAPACITY; } const uint32_t supported_ext_requested_count = supported_ext_count; VkExtensionProperties supported_exts[SUPPORTED_EXT_BUFFER_CAPACITY]; if (vkEnumerateDeviceExtensionProperties(pd, NULL, &supported_ext_count, supported_exts) != VK_SUCCESS) { LRHI_LOG("Couldn't enumerate supported device extensions"); goto err; } if (supported_ext_count < supported_ext_requested_count) { LRHI_LOG("Actual supported extension count is smaller than expected"); } for (uint32_t required_ext_index = 0; required_ext_index < REQUIRED_DEVICE_EXT_COUNT; required_ext_index++) { const char *required_ext_name = REQUIRED_DEVICE_EXTS[required_ext_index]; bool found = false; for (uint32_t supported_ext_index = 0; supported_ext_index < supported_ext_count; supported_ext_index++) { const char *supported_ext_name = supported_exts[supported_ext_index].extensionName; if (strcmp(required_ext_name, supported_ext_name) == 0) { found = true; break; } } if (!found) { return false; } } return true; err: return false; } #define SWAPCHAIN_SUPPORT_DETAILS_MAX_SURF_FMT_COUNT 256 #define SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT 256 struct swapchain_support_details { VkSurfaceCapabilitiesKHR capabilities; VkSurfaceFormatKHR formats[SWAPCHAIN_SUPPORT_DETAILS_MAX_SURF_FMT_COUNT]; VkPresentModeKHR present_modes[SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT]; uint32_t format_count; uint32_t present_mode_count; }; static bool get_swapchain_support_details(VkPhysicalDevice pd, VkSurfaceKHR surf, struct swapchain_support_details *details) { if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR( pd, surf, &details->capabilities) != VK_SUCCESS) { goto err; } if (vkGetPhysicalDeviceSurfaceFormatsKHR(pd, surf, &details->format_count, NULL) != VK_SUCCESS) { goto err; } if (details->format_count != 0 && details->format_count < SWAPCHAIN_SUPPORT_DETAILS_MAX_SURF_FMT_COUNT) { if (vkGetPhysicalDeviceSurfaceFormatsKHR(pd, surf, &details->format_count, details->formats) != VK_SUCCESS) { goto err; } } if (vkGetPhysicalDeviceSurfacePresentModesKHR( pd, surf, &details->present_mode_count, NULL) != VK_SUCCESS) { goto err; } if (details->present_mode_count != 0 && details->present_mode_count < SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT) { if (vkGetPhysicalDeviceSurfacePresentModesKHR( pd, surf, &details->present_mode_count, details->present_modes) != VK_SUCCESS) { goto err; } } return true; err: return false; } static bool is_physical_device_suitable(VkPhysicalDevice device, VkSurfaceKHR surface) { struct queue_family_indices indices = queue_family_indices_find_for_device(device, surface); VkPhysicalDeviceFeatures supported_features; vkGetPhysicalDeviceFeatures(device, &supported_features); bool extensions_supported = are_required_device_extensions_supported(device); bool swapchain_adequate = false; if (extensions_supported) { struct swapchain_support_details swapchain_support_details = {0}; if (!get_swapchain_support_details(device, surface, &swapchain_support_details)) { LRHI_LOG("Couldn't query swapchain support details from device"); goto err; } swapchain_adequate = swapchain_support_details.format_count > 0 && swapchain_support_details.present_mode_count > 0; } return queue_family_indices_complete(&indices) && extensions_supported && swapchain_adequate && supported_features.samplerAnisotropy; err: return false; } struct lrhi_command_buffer { lrhi_device *device; VkCommandBuffer buffer; }; struct lrhi_descriptor_set { VkDescriptorSet set; }; enum { MAX_RENDER_PASS_COLOR_ATTACHMENT = 64 }; struct framebuffer_cache_entry { VkFramebuffer framebuffer; VkRenderPass render_pass; VkImageView attachments[MAX_RENDER_PASS_COLOR_ATTACHMENT]; uint32_t attachment_count; uint32_t width; uint32_t height; bool used; }; enum { FRAMEBUFFER_CACHE_CAPACITY = 64 }; enum { DESCRIPTOR_SET_POOL_CAPACITY = 1024 }; struct lrhi_device { struct lrhi_allocator *allocator; VkPhysicalDevice physical_device; VkDevice device; VkQueue graphics_queue; VkQueue present_queue; VkCommandPool command_pool; VmaAllocator vma_allocator; VkFence in_flight_fence[MAX_FRAME_IN_FLIGHT]; lrhi_command_buffer command_buffers[MAX_FRAME_IN_FLIGHT]; VkDescriptorPool descriptor_pool; lrhi_descriptor_set descriptor_sets[DESCRIPTOR_SET_POOL_CAPACITY]; int used_descriptor_set_count; uint32_t current_frame; // TODO need to clear the framebuffer cache on destroy struct framebuffer_cache_entry framebuffer_cache[FRAMEBUFFER_CACHE_CAPACITY]; }; static void framebuffer_cache_clear(lrhi_device *device) { for (uint32_t i = 0; i < FRAMEBUFFER_CACHE_CAPACITY; i++) { if (device->framebuffer_cache[i].used) { vkDestroyFramebuffer(device->device, device->framebuffer_cache[i].framebuffer, NULL); device->framebuffer_cache[i].used = false; } } } static VkFramebuffer framebuffer_cache_get_or_create( lrhi_device *device, VkRenderPass pass, const VkImageView *attachments, uint32_t attachment_count, uint32_t width, uint32_t height) { // Looking for a matching framebuffer for (uint32_t i = 0; i < FRAMEBUFFER_CACHE_CAPACITY; i++) { struct framebuffer_cache_entry *entry = &device->framebuffer_cache[i]; if (!entry->used) { continue; } if (entry->render_pass == pass && entry->attachment_count == attachment_count && entry->width == width && entry->height == height) { bool same_attachments = true; for (uint32_t i = 0; i < attachment_count; i++) { if (attachments[i] != entry->attachments[i]) { same_attachments = false; break; } } if (same_attachments) { return entry->framebuffer; } } } // We haven't found one, so we create a new one uint32_t free_slot = UINT32_MAX; for (uint32_t i = 0; i < FRAMEBUFFER_CACHE_CAPACITY; i++) { if (!device->framebuffer_cache[i].used) { free_slot = i; break; } } if (free_slot == UINT32_MAX) { LRHI_PANIC("Framebuffer cache is full, this shouldn't happen."); } struct framebuffer_cache_entry *entry = &device->framebuffer_cache[free_slot]; VkFramebufferCreateInfo fb_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = pass, .attachmentCount = attachment_count, .pAttachments = attachments, .width = width, .height = height, .layers = 1}; if (vkCreateFramebuffer(device->device, &fb_info, NULL, &entry->framebuffer) != VK_SUCCESS) { LRHI_PANIC("Framebuffer creation failed!"); } entry->render_pass = pass; entry->attachment_count = attachment_count; entry->width = width; entry->height = height; entry->used = true; for (uint32_t i = 0; i < attachment_count; i++) { entry->attachments[i] = attachments[i]; } return entry->framebuffer; } lrhi_device *lrhi_instance_create_device(lrhi_instance *instance, lrhi_surface *surface) { struct lrhi_device *device = lrhi_allocator_allocate(&instance->allocator, sizeof(struct lrhi_device)); device->allocator = &instance->allocator; // Picking physical device VkPhysicalDevice physical_device = VK_NULL_HANDLE; uint32_t device_count; vkEnumeratePhysicalDevices(instance->instance, &device_count, NULL); if (device_count == 0) { LRHI_LOG_ERR("Failed to find any GPU with Vulkan support."); goto fail; } enum { PHYSICAL_DEVICE_BUF_SIZE = 64 }; VkPhysicalDevice physical_devices[PHYSICAL_DEVICE_BUF_SIZE] = {0}; if (device_count > PHYSICAL_DEVICE_BUF_SIZE) { LRHI_LOG("Physical device count too large for the physical " "devices buffer, will be truncated."); device_count = PHYSICAL_DEVICE_BUF_SIZE; } vkEnumeratePhysicalDevices(instance->instance, &device_count, physical_devices); for (uint32_t physical_device_idx = 0; physical_device_idx < device_count; physical_device_idx++) { if (is_physical_device_suitable(physical_devices[physical_device_idx], surface->surface)) { physical_device = physical_devices[physical_device_idx]; } } if (physical_device == VK_NULL_HANDLE) { LRHI_LOG_ERR("Failed to find a suitable GPU."); goto fail; } device->physical_device = physical_device; LRHI_LOG("Picked physical device."); // Create logical device struct queue_family_indices queue_family_indices = queue_family_indices_find_for_device(physical_device, surface->surface); enum { UNIQUE_QUEUE_FAMILIES_BUF_SIZE = 2 }; uint32_t unique_queue_families[UNIQUE_QUEUE_FAMILIES_BUF_SIZE]; int unique_queue_family_count = 1; unique_queue_families[0] = queue_family_indices.graphics_family; if (queue_family_indices.present_family != queue_family_indices.graphics_family) { unique_queue_families[1] = queue_family_indices.present_family; unique_queue_family_count++; } VkDeviceQueueCreateInfo queue_create_infos[UNIQUE_QUEUE_FAMILIES_BUF_SIZE] = { 0}; float queue_priority = 1.f; for (int i = 0; i < unique_queue_family_count; i++) { VkDeviceQueueCreateInfo *queue_create_info = &queue_create_infos[i]; queue_create_info->sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_info->queueFamilyIndex = unique_queue_families[i]; queue_create_info->queueCount = 1; queue_create_info->pQueuePriorities = &queue_priority; } VkPhysicalDeviceFeatures device_features = {.samplerAnisotropy = VK_TRUE}; VkDeviceCreateInfo device_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = queue_create_infos, .queueCreateInfoCount = unique_queue_family_count, .pEnabledFeatures = &device_features, .ppEnabledExtensionNames = REQUIRED_DEVICE_EXTS, .enabledExtensionCount = REQUIRED_DEVICE_EXT_COUNT}; if (instance->enable_validation_layers) { device_create_info.enabledLayerCount = REQUIRED_VALIDATION_LAYER_COUNT; device_create_info.ppEnabledLayerNames = REQUIRED_VALIDATION_LAYERS; } else { device_create_info.enabledLayerCount = 0; } if (vkCreateDevice(physical_device, &device_create_info, NULL, &device->device) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to create logical device."); goto fail; } vkGetDeviceQueue(device->device, queue_family_indices.graphics_family, 0, &device->graphics_queue); vkGetDeviceQueue(device->device, queue_family_indices.present_family, 0, &device->present_queue); if (vmaCreateAllocator( &(const VmaAllocatorCreateInfo){.device = device->device, .instance = instance->instance, .physicalDevice = device->physical_device}, &device->vma_allocator) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to create vulkan memory allocator"); goto destroy_device; } VkCommandPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = queue_family_indices.graphics_family}; if (vkCreateCommandPool(device->device, &pool_info, NULL, &device->command_pool) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to create command pool."); goto destroy_vma; } VkCommandBufferAllocateInfo command_buffer_allocate_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = device->command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = MAX_FRAME_IN_FLIGHT}; VkCommandBuffer cmd_bufs[MAX_FRAME_IN_FLIGHT]; if (vkAllocateCommandBuffers(device->device, &command_buffer_allocate_info, cmd_bufs) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to allocate command buffers."); goto destroy_command_pool; } for (int i = 0; i < MAX_FRAME_IN_FLIGHT; i++) { device->command_buffers[i].buffer = cmd_bufs[i]; device->command_buffers[i].device = device; } VkFenceCreateInfo fence_infos = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; uint32_t fence_idx; for (fence_idx = 0; fence_idx < MAX_FRAME_IN_FLIGHT; fence_idx++) { if (vkCreateFence(device->device, &fence_infos, NULL, &device->in_flight_fence[fence_idx]) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to create fences."); goto destroy_in_flight_fences; } } // Creating descriptor pools VkDescriptorPoolSize uniform_buffer_descriptor_pool_size = { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = MAX_FRAME_IN_FLIGHT}; VkDescriptorPoolSize combined_image_sampler_pool_size = { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = MAX_FRAME_IN_FLIGHT}; VkDescriptorPoolCreateInfo descriptor_pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = 2, .pPoolSizes = (const VkDescriptorPoolSize[]){uniform_buffer_descriptor_pool_size, combined_image_sampler_pool_size}, .maxSets = MAX_FRAME_IN_FLIGHT}; if (vkCreateDescriptorPool(device->device, &descriptor_pool_info, NULL, &device->descriptor_pool) != VK_SUCCESS) { LRHI_LOG_ERR("Uniform descriptor pool creation failed."); goto destroy_in_flight_fences; } device->used_descriptor_set_count = 0; device->current_frame = 0; // zero-initializing the framebuffer cache, so all .used flag are false memset(device->framebuffer_cache, 0, sizeof(device->framebuffer_cache)); return device; destroy_in_flight_fences: for (uint32_t i = 0; i < fence_idx; i++) { vkDestroyFence(device->device, device->in_flight_fence[i], NULL); } VkCommandBuffer command_buffers_to_free[MAX_FRAME_IN_FLIGHT] = {0}; for (int i = 0; i < MAX_FRAME_IN_FLIGHT; i++) { command_buffers_to_free[i] = device->command_buffers[i].buffer; } vkFreeCommandBuffers(device->device, device->command_pool, MAX_FRAME_IN_FLIGHT, command_buffers_to_free); destroy_command_pool: vkDestroyCommandPool(device->device, device->command_pool, NULL); destroy_vma: vmaDestroyAllocator(device->vma_allocator); destroy_device: vkDestroyDevice(device->device, NULL); fail: lrhi_allocator_free(&instance->allocator, sizeof(struct lrhi_device), device); return NULL; } void lrhi_instance_destroy_device(lrhi_instance *instance, lrhi_device *device) { framebuffer_cache_clear(device); vkDestroyCommandPool(device->device, device->command_pool, NULL); vkDestroyDescriptorPool(device->device, device->descriptor_pool, NULL); for (int i = 0; i < MAX_FRAME_IN_FLIGHT; i++) { vkDestroyFence(device->device, device->in_flight_fence[i], NULL); } vmaDestroyAllocator(device->vma_allocator); vkDestroyDevice(device->device, NULL); lrhi_allocator_free(&instance->allocator, sizeof(struct lrhi_device), device); LRHI_LOG("Logical device destroyed."); } struct lrhi_shader_module { VkShaderModule module; }; lrhi_shader_module * lrhi_device_create_shader_module(lrhi_device *device, const struct lrhi_shader_module_desc *desc) { lrhi_shader_module *module = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_shader_module)); if (vkCreateShaderModule( device->device, &(const VkShaderModuleCreateInfo){ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .pCode = (const uint32_t *)desc->spirv_source, .codeSize = desc->spirv_source_size}, NULL, &module->module) != VK_SUCCESS) { if (desc->label) { LRHI_LOG_ERR("Shader module \"%s\" creation failed.", desc->label); } else { LRHI_LOG_ERR("Shader module creation failed."); } goto fail; } return module; fail: return NULL; } void lrhi_device_wait_idle(lrhi_device *device) { vkDeviceWaitIdle(device->device); } void lrhi_device_destroy_shader_module(lrhi_device *device, lrhi_shader_module *module) { vkDestroyShaderModule(device->device, module->module, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_shader_module), module); } static VkDescriptorType lrhi_descriptor_type_to_vk_descriptor_type(enum lrhi_descriptor_type type) { switch (type) { case lrhi_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER: return VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; case lrhi_DESCRIPTOR_TYPE_UNIFORM_BUFFER: default: return VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; } } static VkShaderStageFlags lrhi_shader_stages_to_vk_shader_stage_flags(lrhi_shader_stages stages) { VkShaderStageFlags flags = 0; if (stages & lrhi_SHADER_STAGE_VERTEX) { flags |= VK_SHADER_STAGE_VERTEX_BIT; } if (stages & lrhi_SHADER_STAGE_FRAGMENT) { flags |= VK_SHADER_STAGE_FRAGMENT_BIT; } return flags; } struct lrhi_descriptor_set_layout { VkDescriptorSetLayout layout; }; lrhi_descriptor_set_layout *lrhi_device_create_descriptor_set_layout( lrhi_device *device, const struct lrhi_descriptor_set_layout_desc *desc) { struct lrhi_descriptor_set_layout *layout = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_descriptor_set_layout)); enum { BINDINGS_BUF_CAP = 64 }; VkDescriptorSetLayoutBinding bindings[BINDINGS_BUF_CAP]; for (uint32_t i = 0; i < desc->binding_count; i++) { bindings[i].descriptorCount = desc->bindings[i].descriptor_count; bindings[i].descriptorType = lrhi_descriptor_type_to_vk_descriptor_type( desc->bindings[i].descriptor_type); bindings[i].binding = desc->bindings[i].binding; bindings[i].pImmutableSamplers = NULL; bindings[i].stageFlags = lrhi_shader_stages_to_vk_shader_stage_flags( desc->bindings[i].visibility); } VkDescriptorSetLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = desc->binding_count, .pBindings = bindings}; if (vkCreateDescriptorSetLayout(device->device, &layout_info, NULL, &layout->layout) != VK_SUCCESS) { LRHI_LOG_ERR("Descriptor set layout creation failed."); goto fail; } return layout; fail: return NULL; } void lrhi_device_destroy_descriptor_set_layout( lrhi_device *device, lrhi_descriptor_set_layout *layout) { vkDestroyDescriptorSetLayout(device->device, layout->layout, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_descriptor_set_layout), layout); } struct lrhi_buffer { VkBuffer buffer; VmaAllocation allocation; VmaAllocationInfo allocation_info; }; struct lrhi_sampler { VkSampler sampler; }; lrhi_descriptor_set * lrhi_device_create_descriptor_set(lrhi_device *device, const struct lrhi_descriptor_set_desc *desc) { if (device->used_descriptor_set_count >= DESCRIPTOR_SET_POOL_CAPACITY) { LRHI_LOG_ERR("Descriptor set pool capacity exceeded."); goto fail; } lrhi_descriptor_set *set = &device->descriptor_sets[device->used_descriptor_set_count++]; VkDescriptorSetLayout layouts[] = {desc->layout->layout}; VkDescriptorSetAllocateInfo descriptor_set_allocate_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = device->descriptor_pool, .descriptorSetCount = 1, .pSetLayouts = layouts}; if (vkAllocateDescriptorSets(device->device, &descriptor_set_allocate_info, &set->set) != VK_SUCCESS) { LRHI_LOG_ERR("Couldn't allocate descriptor set."); goto fail; } VkWriteDescriptorSet descriptor_writes[64] = {0}; for (uint32_t i = 0; i < desc->entry_count; i++) { const VkDescriptorBufferInfo *buffer_info = NULL; const VkDescriptorImageInfo *image_info = NULL; if (desc->entries[i].buffer_info) { buffer_info = &(const VkDescriptorBufferInfo){ .buffer = desc->entries[i].buffer_info->buffer->buffer, .offset = desc->entries[i].buffer_info->offset, .range = desc->entries[i].buffer_info->range}; } else if (desc->entries[i].texture_info) { image_info = &(const VkDescriptorImageInfo){ .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = desc->entries[i].texture_info->view->view, .sampler = desc->entries[i].texture_info->sampler->sampler, }; } descriptor_writes[i].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptor_writes[i].dstSet = set->set; descriptor_writes[i].dstBinding = desc->entries[i].binding; descriptor_writes[i].dstArrayElement = 0; descriptor_writes[i].descriptorType = lrhi_descriptor_type_to_vk_descriptor_type( desc->entries[i].descriptor_type); descriptor_writes[i].descriptorCount = 1; descriptor_writes[i].pBufferInfo = buffer_info; descriptor_writes[i].pImageInfo = image_info; descriptor_writes[i].pTexelBufferView = NULL; } vkUpdateDescriptorSets(device->device, desc->entry_count, descriptor_writes, 0, NULL); return set; fail: return NULL; } struct lrhi_pipeline_layout { VkPipelineLayout layout; }; lrhi_pipeline_layout *lrhi_device_create_pipeline_layout( lrhi_device *device, const struct lrhi_pipeline_layout_desc *desc) { lrhi_pipeline_layout *layout = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_pipeline_layout)); // TODO add bound checking VkDescriptorSetLayout layouts[256]; for (uint32_t i = 0; i < desc->set_layout_count; i++) { layouts[i] = desc->set_layouts[i].layout; } VkPipelineLayoutCreateInfo pipeline_layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = desc->set_layout_count, .pSetLayouts = layouts, .pushConstantRangeCount = 0}; if (vkCreatePipelineLayout(device->device, &pipeline_layout_info, NULL, &layout->layout) != VK_SUCCESS) { LRHI_LOG_ERR("Pipeline layout creation failed."); goto fail; } return layout; fail: return NULL; } void lrhi_device_destroy_pipeline_layout(lrhi_device *device, lrhi_pipeline_layout *layout) { vkDestroyPipelineLayout(device->device, layout->layout, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_pipeline_layout), layout); } static VkFormat lrhi_format_to_vk_format(enum lrhi_format format) { switch (format) { case lrhi_FORMAT_B8G8R8A8_SRGB: return VK_FORMAT_B8G8R8A8_SRGB; case lrhi_FORMAT_R8G8B8A8_SRGB: return VK_FORMAT_R8G8B8A8_SRGB; case lrhi_FORMAT_R32G32_SFLOAT: return VK_FORMAT_R32G32_SFLOAT; case lrhi_FORMAT_D32_SFLOAT: return VK_FORMAT_D32_SFLOAT; case lrhi_FORMAT_R32G32B32_SFLOAT: default: return VK_FORMAT_R32G32B32_SFLOAT; } } struct lrhi_render_pass { VkRenderPass pass; }; static VkAttachmentLoadOp lrhi_attachment_load_op_to_vk_attachment_load_op( enum lrhi_attachment_load_op op) { switch (op) { case lrhi_ATTACHMENT_LOAD_OP_CLEAR: return VK_ATTACHMENT_LOAD_OP_CLEAR; case lrhi_ATTACHMENT_LOAD_OP_LOAD: return VK_ATTACHMENT_LOAD_OP_LOAD; case lrhi_ATTACHMENT_LOAD_OP_DONT_CARE: default: return VK_ATTACHMENT_LOAD_OP_DONT_CARE; } } static VkAttachmentStoreOp lrhi_attachment_store_op_to_vk_attachment_store_op( enum lrhi_attachment_store_op op) { switch (op) { case lrhi_ATTACHMENT_STORE_OP_STORE: return VK_ATTACHMENT_STORE_OP_STORE; case lrhi_ATTACHMENT_STORE_OP_DONT_CARE: default: return VK_ATTACHMENT_STORE_OP_DONT_CARE; } } lrhi_render_pass * lrhi_device_create_render_pass(lrhi_device *device, const struct lrhi_render_pass_desc *desc) { lrhi_render_pass *pass = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_render_pass)); VkAttachmentDescription color_attachments[MAX_RENDER_PASS_COLOR_ATTACHMENT] = {0}; VkAttachmentReference color_attachment_refs[MAX_RENDER_PASS_COLOR_ATTACHMENT] = {0}; // TODO check bound for (uint32_t i = 0; i < desc->color_attachment_count; i++) { color_attachments[i].format = lrhi_format_to_vk_format(desc->color_attachments[i].format); color_attachments[i].samples = desc->color_attachments[i].sample_count; color_attachments[i].loadOp = lrhi_attachment_load_op_to_vk_attachment_load_op( desc->color_attachments[i].load_op); color_attachments[i].storeOp = lrhi_attachment_store_op_to_vk_attachment_store_op( desc->color_attachments[i].store_op); color_attachments[i].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; color_attachments[i].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; // NOTE This might not be sufficient if (color_attachments[i].loadOp == VK_ATTACHMENT_LOAD_OP_LOAD) { color_attachments[i].initialLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; color_attachments[i].finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; } else { color_attachments[i].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; color_attachments[i].finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } color_attachment_refs[i].attachment = i; color_attachment_refs[i].layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; } VkAttachmentDescription depth_attachment = {0}; VkAttachmentReference depth_attachment_ref = {0}; if (desc->depth_stencil_attachment) { depth_attachment.format = lrhi_format_to_vk_format(desc->depth_stencil_attachment->format); depth_attachment.samples = desc->depth_stencil_attachment->sample_count; depth_attachment.loadOp = lrhi_attachment_load_op_to_vk_attachment_load_op( desc->depth_stencil_attachment->load_op); depth_attachment.storeOp = lrhi_attachment_store_op_to_vk_attachment_store_op( desc->depth_stencil_attachment->store_op); depth_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; depth_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depth_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; depth_attachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; depth_attachment_ref.attachment = 1; depth_attachment_ref.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; } VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = desc->color_attachment_count, .pColorAttachments = color_attachment_refs}; VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstSubpass = 0, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT}; if (desc->depth_stencil_attachment) { subpass.pDepthStencilAttachment = &depth_attachment_ref; dependency.srcStageMask |= VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT; dependency.srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; dependency.dstStageMask |= VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; dependency.dstAccessMask |= VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; } VkAttachmentDescription attachments[64] = {0}; uint32_t attachment_index; for (attachment_index = 0; attachment_index < desc->color_attachment_count; attachment_index++) { attachments[attachment_index] = color_attachments[attachment_index]; } if (desc->depth_stencil_attachment) { attachments[attachment_index++] = depth_attachment; } VkRenderPassCreateInfo render_pass_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = attachment_index, .pAttachments = attachments, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency}; if (vkCreateRenderPass(device->device, &render_pass_info, NULL, &pass->pass) != VK_SUCCESS) { LRHI_LOG_ERR("Couldn't create render pass."); goto fail; } return pass; fail: return NULL; } void lrhi_device_destroy_render_pass(lrhi_device *device, lrhi_render_pass *pass) { vkDestroyRenderPass(device->device, pass->pass, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_render_pass), pass); } static VkVertexInputRate lrhi_vertex_input_rate_to_vk_vertex_input_rate( enum lrhi_vertex_input_rate rate) { switch (rate) { case lrhi_VERTEX_INPUT_RATE_INSTANCE: return VK_VERTEX_INPUT_RATE_INSTANCE; case lrhi_VERTEX_INPUT_RATE_VERTEX: default: return VK_VERTEX_INPUT_RATE_VERTEX; } } VkCompareOp lrhi_compare_function_to_vk_compare_op(enum lrhi_compare_function op) { switch (op) { case lrhi_COMPARE_FUNCTION_LESS: return VK_COMPARE_OP_LESS; case lrhi_COMPARE_FUNCTION_LESS_EQUAL: return VK_COMPARE_OP_LESS_OR_EQUAL; case lrhi_COMPARE_FUNCTION_EQUAL: return VK_COMPARE_OP_EQUAL; case lrhi_COMPARE_FUNCTION_NOT_EQUAL: return VK_COMPARE_OP_NOT_EQUAL; case lrhi_COMPARE_FUNCTION_GREATER_EQUAL: return VK_COMPARE_OP_GREATER_OR_EQUAL; case lrhi_COMPARE_FUNCTION_GREATER: return VK_COMPARE_OP_GREATER; case lrhi_COMPARE_FUNCTION_ALWAYS: return VK_COMPARE_OP_ALWAYS; case lrhi_COMPARE_FUNCTION_NEVER: default: return VK_COMPARE_OP_NEVER; } } struct lrhi_pipeline { VkPipeline pipeline; }; lrhi_pipeline * lrhi_device_create_pipeline(lrhi_device *device, const struct lrhi_pipeline_desc *desc) { lrhi_pipeline *pipeline = lrhi_allocator_allocate(device->allocator, sizeof(struct lrhi_pipeline)); VkPipelineShaderStageCreateInfo vertex_shader_stage_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = desc->vertex_module->module, .pName = "main"}; VkPipelineShaderStageCreateInfo fragment_shader_stage_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = desc->fragment_module->module, .pName = "main"}; VkPipelineShaderStageCreateInfo shader_stages[] = { vertex_shader_stage_info, fragment_shader_stage_info}; VkDynamicState dynamic_states[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; VkPipelineDynamicStateCreateInfo dynamic_state = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount = ARRAY_LENGTH(dynamic_states), .pDynamicStates = dynamic_states}; enum { VERTEX_INPUT_BINDING_DESCRIPTION_BUFFER_CAP = 16 }; VkVertexInputBindingDescription vertex_input_binding_descriptions [VERTEX_INPUT_BINDING_DESCRIPTION_BUFFER_CAP]; uint32_t vertex_input_binding_description_count = desc->vertex_input->binding_count; assert(vertex_input_binding_description_count <= VERTEX_INPUT_BINDING_DESCRIPTION_BUFFER_CAP); for (int i = 0; i < desc->vertex_input->binding_count; i++) { vertex_input_binding_descriptions[i].binding = desc->vertex_input->bindings[i].binding; vertex_input_binding_descriptions[i].stride = desc->vertex_input->bindings[i].stride; vertex_input_binding_descriptions[i].inputRate = lrhi_vertex_input_rate_to_vk_vertex_input_rate( desc->vertex_input->bindings[i].input_rate); } enum { VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_BUFFER_CAP = 16 }; VkVertexInputAttributeDescription vertex_input_attribute_descriptions [VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_BUFFER_CAP]; uint32_t vertex_input_attribute_description_count = desc->vertex_input->attribute_count; assert(vertex_input_attribute_description_count <= VERTEX_INPUT_ATTRIBUTE_DESCRIPTION_BUFFER_CAP); for (uint32_t i = 0; i < vertex_input_attribute_description_count; i++) { vertex_input_attribute_descriptions[i].binding = desc->vertex_input->attributes[i].binding; vertex_input_attribute_descriptions[i].location = desc->vertex_input->attributes[i].location; vertex_input_attribute_descriptions[i].offset = desc->vertex_input->attributes[i].offset; vertex_input_attribute_descriptions[i].format = lrhi_format_to_vk_format(desc->vertex_input->attributes[i].format); } VkPipelineVertexInputStateCreateInfo vertex_input_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = vertex_input_binding_description_count, .pVertexBindingDescriptions = vertex_input_binding_descriptions, .vertexAttributeDescriptionCount = vertex_input_attribute_description_count, .pVertexAttributeDescriptions = vertex_input_attribute_descriptions}; VkPipelineInputAssemblyStateCreateInfo input_assembly = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE, }; VkPipelineViewportStateCreateInfo viewport_state = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .scissorCount = 1}; VkPipelineRasterizationStateCreateInfo rasterizer = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .lineWidth = 1.f, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE, .depthBiasEnable = VK_FALSE}; VkPipelineMultisampleStateCreateInfo multisampling = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT}; VkPipelineColorBlendAttachmentState color_blend_attachment = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, .blendEnable = VK_FALSE}; VkPipelineColorBlendStateCreateInfo color_blending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .attachmentCount = 1, .pAttachments = &color_blend_attachment}; VkPipelineDepthStencilStateCreateInfo *depth_stencil = NULL; if (desc->depth_stencil) { depth_stencil = &(VkPipelineDepthStencilStateCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = desc->depth_stencil->depth_write_enabled, .depthCompareOp = lrhi_compare_function_to_vk_compare_op( desc->depth_stencil->compare_function), .depthBoundsTestEnable = VK_FALSE, .stencilTestEnable = VK_FALSE, }; } VkGraphicsPipelineCreateInfo pipeline_info = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = shader_stages, .pVertexInputState = &vertex_input_info, .pInputAssemblyState = &input_assembly, .pViewportState = &viewport_state, .pRasterizationState = &rasterizer, .pMultisampleState = &multisampling, .pDepthStencilState = depth_stencil, .pColorBlendState = &color_blending, .pDynamicState = &dynamic_state, .layout = desc->layout->layout, .renderPass = desc->pass->pass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, .basePipelineIndex = -1}; if (vkCreateGraphicsPipelines(device->device, VK_NULL_HANDLE, 1, &pipeline_info, NULL, &pipeline->pipeline) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to create graphics pipeline."); goto fail; } return pipeline; fail: return NULL; } void lrhi_device_destroy_pipeline(lrhi_device *device, lrhi_pipeline *pipeline) { vkDestroyPipeline(device->device, pipeline->pipeline, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_pipeline), pipeline); } static VkBufferUsageFlags lrhi_buffer_usage_to_vk_buffer_usage_flags(lrhi_buffer_usage usage) { VkBufferUsageFlags flags = 0; if (usage & lrhi_BUFFER_USAGE_VERTEX) { flags |= VK_BUFFER_USAGE_VERTEX_BUFFER_BIT; flags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; } else if (usage & lrhi_BUFFER_USAGE_INDEX) { flags |= VK_BUFFER_USAGE_INDEX_BUFFER_BIT; flags |= VK_BUFFER_USAGE_TRANSFER_DST_BIT; } else if (usage & lrhi_BUFFER_USAGE_TRANSFER) { flags |= VK_BUFFER_USAGE_TRANSFER_SRC_BIT; } else if (usage & lrhi_BUFFER_USAGE_UNIFORM) { flags |= VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; } return flags; } lrhi_buffer *lrhi_device_create_buffer(lrhi_device *device, const struct lrhi_buffer_desc *desc) { struct lrhi_buffer *buffer = lrhi_allocator_allocate(device->allocator, sizeof(struct lrhi_buffer)); VkBufferCreateInfo buffer_create_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = desc->size, .usage = lrhi_buffer_usage_to_vk_buffer_usage_flags(desc->usage), .sharingMode = VK_SHARING_MODE_EXCLUSIVE}; VmaAllocationCreateInfo allocation_create_info = {0}; if (desc->usage & lrhi_BUFFER_USAGE_TRANSFER) { allocation_create_info.usage = VMA_MEMORY_USAGE_CPU_ONLY; allocation_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT; allocation_create_info.preferredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } else if (desc->usage & lrhi_BUFFER_USAGE_UNIFORM) { allocation_create_info.usage = VMA_MEMORY_USAGE_AUTO; allocation_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_RANDOM_BIT, allocation_create_info.preferredFlags = VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT; } else { allocation_create_info.usage = VMA_MEMORY_USAGE_GPU_ONLY; allocation_create_info.preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT; } if (vmaCreateBuffer(device->vma_allocator, &buffer_create_info, &allocation_create_info, &buffer->buffer, &buffer->allocation, &buffer->allocation_info) != VK_SUCCESS) { LRHI_LOG_ERR("Buffer allocation failed."); goto fail; } return buffer; fail: return NULL; } lrhi_command_buffer *lrhi_device_create_command_buffer(lrhi_device *device) { lrhi_command_buffer *buffer = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_command_buffer)); VkCommandBufferAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = device->command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1}; if (vkAllocateCommandBuffers(device->device, &alloc_info, &buffer->buffer) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to allocate command buffer."); goto fail; } buffer->device = device; return buffer; fail: return NULL; } void lrhi_device_destroy_command_buffer(lrhi_device *device, lrhi_command_buffer *buffer) { lrhi_allocator_free(device->allocator, sizeof(struct lrhi_command_buffer), buffer); } static lrhi_command_buffer * record_one_time_command_buffer(lrhi_device *device) { lrhi_command_buffer *command_buffer = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_command_buffer)); VkCommandBufferAllocateInfo command_buffer_allocate_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandPool = device->command_pool, .commandBufferCount = 1}; vkAllocateCommandBuffers(device->device, &command_buffer_allocate_info, &command_buffer->buffer); command_buffer->device = device; VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; vkBeginCommandBuffer(command_buffer->buffer, &begin_info); return command_buffer; } static void submit_one_time_command_buffer(lrhi_device *device, lrhi_command_buffer *cmd_buf) { vkEndCommandBuffer(cmd_buf->buffer); VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &cmd_buf->buffer}; vkQueueSubmit(device->graphics_queue, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(device->graphics_queue); lrhi_device_destroy_command_buffer(device, cmd_buf); } lrhi_buffer * lrhi_device_create_buffer_init(lrhi_device *device, const struct lrhi_buffer_init_desc *desc) { lrhi_buffer *staging_buffer = lrhi_device_create_buffer( device, &(const struct lrhi_buffer_desc){ .usage = lrhi_BUFFER_USAGE_TRANSFER, .size = desc->size}); if (!staging_buffer) { LRHI_LOG_ERR("Staging buffer creation failed."); goto fail; } void *mapped_staging_buffer = lrhi_device_map_buffer(device, staging_buffer); memcpy(mapped_staging_buffer, desc->content, desc->size); lrhi_device_unmap_buffer(device, staging_buffer); lrhi_buffer *buffer = lrhi_device_create_buffer( device, &(const struct lrhi_buffer_desc){.size = desc->size, .usage = desc->usage}); if (!buffer) { LRHI_LOG_ERR("Buffer creation failed."); goto destroy_staging_buffer; } lrhi_command_buffer *command_buffer = record_one_time_command_buffer(device); struct lrhi_buffer_copy copy = { .src = staging_buffer, .dst = buffer, .region_count = 1, .regions = &(const struct lrhi_buffer_copy_region){.size = desc->size}}; lrhi_command_copy_buffer_to_buffer(command_buffer, ©); submit_one_time_command_buffer(device, command_buffer); lrhi_device_destroy_buffer(device, staging_buffer); return buffer; destroy_staging_buffer: vmaDestroyBuffer(device->vma_allocator, staging_buffer->buffer, staging_buffer->allocation); fail: return NULL; } void lrhi_device_destroy_buffer(lrhi_device *device, lrhi_buffer *buffer) { vmaDestroyBuffer(device->vma_allocator, buffer->buffer, buffer->allocation); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_buffer), buffer); } void *lrhi_device_map_buffer(lrhi_device *device, lrhi_buffer *buffer) { void *mapped = NULL; vmaMapMemory(device->vma_allocator, buffer->allocation, &mapped); return mapped; } void lrhi_device_unmap_buffer(lrhi_device *device, lrhi_buffer *buffer) { vmaUnmapMemory(device->vma_allocator, buffer->allocation); } uint32_t lrhi_buffer_size(lrhi_buffer *buffer) { return buffer->allocation_info.size; } static VkImageUsageFlags lrhi_texture_usage_to_vk_image_usage_flags(lrhi_texture_usage usage) { VkImageUsageFlags flags = 0; if (usage & lrhi_TEXTURE_USAGE_COPY_SRC) { flags |= VK_IMAGE_USAGE_TRANSFER_SRC_BIT; } if (usage & lrhi_TEXTURE_USAGE_COPY_DST) { flags |= VK_IMAGE_USAGE_TRANSFER_DST_BIT; } if (usage & lrhi_TEXTURE_USAGE_TEXTURE_BINDING) { flags |= VK_IMAGE_USAGE_SAMPLED_BIT; } if (usage & lrhi_TEXTURE_USAGE_DEPTH_STENCIL_ATTACHMENT) { flags |= VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT; } return flags; } lrhi_texture *lrhi_device_create_texture(lrhi_device *device, const struct lrhi_texture_desc *desc) { lrhi_texture *texture = lrhi_allocator_allocate(device->allocator, sizeof(struct lrhi_texture)); VkImageCreateInfo image_create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .extent = {.width = desc->width, .height = desc->height, .depth = 1}, .mipLevels = desc->mip_level_count, .arrayLayers = 1, .format = lrhi_format_to_vk_format(desc->format), .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = lrhi_texture_usage_to_vk_image_usage_flags(desc->usage), .samples = desc->sample_count, .sharingMode = VK_SHARING_MODE_EXCLUSIVE}; VmaAllocationCreateInfo allocation_create_info = { .usage = VMA_MEMORY_USAGE_AUTO, .preferredFlags = VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT}; if (vmaCreateImage(device->vma_allocator, &image_create_info, &allocation_create_info, &texture->image, &texture->allocation, &texture->allocation_info) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to create texture."); goto fail; } return texture; fail: lrhi_allocator_free(device->allocator, sizeof(struct lrhi_texture), texture); return NULL; } void lrhi_device_destroy_texture(lrhi_device *device, lrhi_texture *texture) { vmaDestroyImage(device->vma_allocator, texture->image, texture->allocation); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_texture), texture); } static void transition_image_layout(lrhi_device *device, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout) { lrhi_command_buffer *cmdbuf = record_one_time_command_buffer(device); VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = old_layout, .newLayout = new_layout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }}; VkPipelineStageFlags source_stage; VkPipelineStageFlags destination_stage; if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; destination_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (old_layout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && new_layout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; source_stage = VK_PIPELINE_STAGE_TRANSFER_BIT; destination_stage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else { LRHI_PANIC("Unsupported layout transition"); } vkCmdPipelineBarrier(cmdbuf->buffer, source_stage, destination_stage, 0, 0, NULL, 0, NULL, 1, &barrier); submit_one_time_command_buffer(device, cmdbuf); } void lrhi_device_write_texture( lrhi_device *device, const struct lrhi_texel_copy_texture_desc *texture, unsigned char *data, size_t data_size, const struct lrhi_texel_copy_buffer_layout *layout, uint32_t width, uint32_t height) { lrhi_buffer *staging_buffer = lrhi_device_create_buffer( device, &(const struct lrhi_buffer_desc){ .usage = lrhi_BUFFER_USAGE_TRANSFER, .size = data_size}); void *mapped_staging_buffer = lrhi_device_map_buffer(device, staging_buffer); memcpy(mapped_staging_buffer, data, data_size); lrhi_device_unmap_buffer(device, staging_buffer); transition_image_layout(device, texture->texture->image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL); lrhi_command_buffer *cmdbuf = record_one_time_command_buffer(device); lrhi_command_copy_buffer_to_texture( cmdbuf, &(const struct lrhi_texel_copy_buffer_desc){.buffer = staging_buffer, .layout = layout}, texture, width, height); submit_one_time_command_buffer(device, cmdbuf); transition_image_layout(device, texture->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL); lrhi_device_destroy_buffer(device, staging_buffer); } static bool create_image_view(lrhi_device *device, VkImage image, VkFormat format, VkImageView *image_view, VkImageAspectFlags aspect_flags, uint32_t mip_level_count) { VkImageViewCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format, .components = {VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY, VK_COMPONENT_SWIZZLE_IDENTITY}, .subresourceRange = {.aspectMask = aspect_flags, .levelCount = mip_level_count, .layerCount = 1}}; if (vkCreateImageView(device->device, &create_info, NULL, image_view) != VK_SUCCESS) { return false; } return true; } static VkImageAspectFlags lrhi_image_aspect_to_vk_image_aspect_flags(lrhi_image_aspect aspect) { VkImageAspectFlags flags = 0; if (aspect & lrhi_IMAGE_ASPECT_COLOR) { flags |= VK_IMAGE_ASPECT_COLOR_BIT; } if (aspect & lrhi_IMAGE_ASPECT_DEPTH) { flags |= VK_IMAGE_ASPECT_DEPTH_BIT; } return flags; } lrhi_texture_view * lrhi_device_create_texture_view(lrhi_device *device, const struct lrhi_texture_view_desc *desc) { lrhi_texture_view *view = lrhi_allocator_allocate( device->allocator, sizeof(struct lrhi_texture_view)); if (!create_image_view( device, desc->texture->image, lrhi_format_to_vk_format(desc->format), &view->view, lrhi_image_aspect_to_vk_image_aspect_flags(desc->aspect), 1)) { LRHI_LOG_ERR("Image view creation failed."); goto fail; } return view; fail: lrhi_allocator_free(device->allocator, sizeof(struct lrhi_texture_view), view); return NULL; } void lrhi_device_destroy_texture_view(lrhi_device *device, lrhi_texture_view *texture_view) { vkDestroyImageView(device->device, texture_view->view, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_texture_view), texture_view); } static VkFilter lrhi_filter_to_vk_filter(enum lrhi_filter filter) { switch (filter) { case lrhi_FILTER_LINEAR: default: return VK_FILTER_LINEAR; case lrhi_FILTER_NEAREST: return VK_FILTER_NEAREST; } } static VkSamplerAddressMode lrhi_sampler_address_mode_to_vk_sampler_address_mode( enum lrhi_sampler_address_mode sampler_address_mode) { switch (sampler_address_mode) { case lrhi_SAMPLER_ADDRESS_MODE_REPEAT: default: return VK_SAMPLER_ADDRESS_MODE_REPEAT; case lrhi_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT: return VK_SAMPLER_ADDRESS_MODE_MIRRORED_REPEAT; case lrhi_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_EDGE; case lrhi_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER: return VK_SAMPLER_ADDRESS_MODE_CLAMP_TO_BORDER; } } lrhi_sampler *lrhi_device_create_sampler(lrhi_device *device, const struct lrhi_sampler_desc *desc) { lrhi_sampler *sampler = lrhi_allocator_allocate(device->allocator, sizeof(struct lrhi_sampler)); VkPhysicalDeviceProperties properties = {0}; vkGetPhysicalDeviceProperties(device->physical_device, &properties); VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = lrhi_filter_to_vk_filter(desc->mag_filter), .minFilter = lrhi_filter_to_vk_filter(desc->min_filter), .addressModeU = lrhi_sampler_address_mode_to_vk_sampler_address_mode( desc->address_mode_u), .addressModeV = lrhi_sampler_address_mode_to_vk_sampler_address_mode( desc->address_mode_v), .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .anisotropyEnable = VK_TRUE, .maxAnisotropy = properties.limits.maxSamplerAnisotropy, .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, .unnormalizedCoordinates = VK_FALSE, .compareEnable = VK_FALSE, .compareOp = VK_COMPARE_OP_ALWAYS, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, .mipLodBias = 0.f, .minLod = 0.f, .maxLod = 0.f}; if (vkCreateSampler(device->device, &sampler_info, NULL, &sampler->sampler) != VK_SUCCESS) { LRHI_LOG_ERR("Texture sampler creation failed."); goto fail; } return sampler; fail: lrhi_allocator_free(device->allocator, sizeof(struct lrhi_sampler), sampler); return NULL; } void lrhi_device_destroy_sampler(lrhi_device *device, lrhi_sampler *sampler) { vkDestroySampler(device->device, sampler->sampler, NULL); lrhi_allocator_free(device->allocator, sizeof(struct lrhi_sampler), sampler); } lrhi_command_buffer *lrhi_command_buffer_begin(lrhi_device *device) { lrhi_command_buffer *command_buffer = &device->command_buffers[device->current_frame]; vkResetFences(device->device, 1, &device->in_flight_fence[device->current_frame]); vkResetCommandBuffer(command_buffer->buffer, 0); VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO}; if (vkBeginCommandBuffer(command_buffer->buffer, &begin_info) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to begin recording command buffer."); goto fail; } return command_buffer; fail: return NULL; } bool lrhi_command_buffer_end(lrhi_command_buffer *buffer) { return vkEndCommandBuffer(buffer->buffer) == VK_SUCCESS; } bool lrhi_device_submit_command_buffer(lrhi_device *device, lrhi_command_buffer *buffer, lrhi_surface *surface) { VkSemaphore wait_semaphores[] = { surface->image_available_semaphore[device->current_frame]}; VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; VkSemaphore signal_semaphores[] = { surface->render_finished_semaphore[surface->image_index]}; VkSubmitInfo submit = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = wait_semaphores, .pWaitDstStageMask = wait_stages, .commandBufferCount = 1, .pCommandBuffers = &buffer->buffer, .signalSemaphoreCount = 1, .pSignalSemaphores = signal_semaphores}; if (vkQueueSubmit(device->graphics_queue, 1, &submit, device->in_flight_fence[device->current_frame]) != VK_SUCCESS) { LRHI_LOG_ERR("Failed to submit draw command buffer."); goto fail; } return true; fail: return false; } void lrhi_command_begin_render_pass( lrhi_command_buffer *buffer, const struct lrhi_render_pass_begin_desc *desc) { VkClearValue clear_values[2] = {0}; clear_values[0] = (VkClearValue){{{desc->clear_color.r, desc->clear_color.g, desc->clear_color.b, desc->clear_color.a}}}; clear_values[1] = (VkClearValue){.depthStencil = {1.f, 0}}; enum { MAX_ATTACHMENT_COUNT = 64 }; if (desc->color_attachment_count >= MAX_ATTACHMENT_COUNT - 1) { LRHI_PANIC("Too many color attachments for render pass"); } VkImageView attachments[MAX_ATTACHMENT_COUNT] = {0}; int attachment_count = 0; for (uint32_t i = 0; i < desc->color_attachment_count; i++) { attachments[attachment_count++] = desc->color_attachments[i].view->view; } if (desc->depth_stencil_attachment) { attachments[attachment_count++] = desc->depth_stencil_attachment->view->view; } VkFramebuffer framebuffer = framebuffer_cache_get_or_create( buffer->device, desc->pass->pass, attachments, attachment_count, desc->surface->extent.width, desc->surface->extent.height); VkRenderPassBeginInfo render_pass_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = desc->pass->pass, .framebuffer = framebuffer, .renderArea.offset = {0.f, 0.f}, .renderArea.extent = desc->surface->extent, .clearValueCount = 2, .pClearValues = clear_values}; vkCmdBeginRenderPass(buffer->buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); } void lrhi_command_end_render_pass(lrhi_command_buffer *buffer) { vkCmdEndRenderPass(buffer->buffer); } void lrhi_command_copy_buffer_to_buffer(lrhi_command_buffer *cmd_buf, const struct lrhi_buffer_copy *copy) { VkBufferCopy *copy_regions = lrhi_allocator_allocate( cmd_buf->device->allocator, copy->region_count * sizeof(VkBufferCopy)); for (uint32_t i = 0; i < copy->region_count; i++) { copy_regions[i].size = copy->regions[i].size; copy_regions[i].srcOffset = copy->regions[i].src_offset; copy_regions[i].dstOffset = copy->regions[i].dst_offset; } vkCmdCopyBuffer(cmd_buf->buffer, copy->src->buffer, copy->dst->buffer, copy->region_count, copy_regions); lrhi_allocator_free(cmd_buf->device->allocator, copy->region_count * sizeof(VkBufferCopy), copy_regions); } void lrhi_command_copy_buffer_to_texture( lrhi_command_buffer *cmdbuf, const struct lrhi_texel_copy_buffer_desc *buffer, const struct lrhi_texel_copy_texture_desc *texture, uint32_t width, uint32_t height) { VkBufferImageCopy region = { .bufferOffset = buffer->layout->offset, .bufferRowLength = buffer->layout->bytes_per_row, .bufferImageHeight = buffer->layout->rows_per_image, .imageSubresource = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = texture->mip_level, .baseArrayLayer = 0, .layerCount = 1, }, .imageOffset = { 0, 0, 0, }, .imageExtent = {width, height, 1}, }; vkCmdCopyBufferToImage(cmdbuf->buffer, buffer->buffer->buffer, texture->texture->image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); } void lrhi_command_bind_pipeline(lrhi_command_buffer *buffer, lrhi_pipeline *pipeline) { vkCmdBindPipeline(buffer->buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline->pipeline); } void lrhi_command_bind_vertex_buffers(lrhi_command_buffer *cmdbuf, uint32_t first_binding, uint32_t binding_count, lrhi_buffer **buffers, uint64_t *offsets) { enum { VK_BUFFERS_BUFFER_CAP = 16 }; VkBuffer vk_buffers[VK_BUFFERS_BUFFER_CAP]; assert(binding_count <= VK_BUFFERS_BUFFER_CAP); for (uint32_t i = 0; i < binding_count; i++) { vk_buffers[i] = buffers[i]->buffer; } vkCmdBindVertexBuffers(cmdbuf->buffer, first_binding, binding_count, vk_buffers, offsets); } void lrhi_command_bind_index_buffer(lrhi_command_buffer *cmdbuf, lrhi_buffer *index_buffer, uint64_t offset) { vkCmdBindIndexBuffer(cmdbuf->buffer, index_buffer->buffer, offset, VK_INDEX_TYPE_UINT16); } void lrhi_command_bind_descriptor_set( lrhi_command_buffer *cmdbuf, lrhi_pipeline_layout *pipeline_layout, uint32_t first_set, uint32_t descriptor_set_count, const lrhi_descriptor_set *descriptor_sets, uint32_t dynamic_offset_count, const uint32_t *dynamic_offsets) { VkDescriptorSet sets[256]; for (uint32_t i = 0; i < descriptor_set_count; i++) { sets[i] = descriptor_sets[i].set; } vkCmdBindDescriptorSets(cmdbuf->buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline_layout->layout, first_set, descriptor_set_count, sets, dynamic_offset_count, dynamic_offsets); } void lrhi_command_set_viewport(lrhi_command_buffer *buffer, const struct lrhi_viewport *viewport) { vkCmdSetViewport(buffer->buffer, 0, 1, &(const VkViewport){.x = viewport->x, .y = viewport->y, .width = viewport->width, .height = viewport->height, .minDepth = viewport->min_depth, .maxDepth = viewport->max_depth}); } void lrhi_command_set_scissor(lrhi_command_buffer *buffer, const struct lrhi_scissor *scissor) { vkCmdSetScissor( buffer->buffer, 0, 1, &(const VkRect2D){ .offset = {scissor->x_offset, scissor->y_offset}, .extent = {.width = scissor->width, .height = scissor->height}}); } void lrhi_command_draw(lrhi_command_buffer *buffer, uint32_t vertex_count, uint32_t instance_count, uint32_t first_vertex, uint32_t first_instance) { vkCmdDraw(buffer->buffer, vertex_count, instance_count, first_vertex, first_instance); } void lrhi_command_draw_indexed(lrhi_command_buffer *cmdbuf, uint32_t index_count, uint32_t instance_count, uint32_t first_index, uint32_t vertex_offset, uint32_t first_instance) { vkCmdDrawIndexed(cmdbuf->buffer, index_count, instance_count, first_index, vertex_offset, first_instance); } static uint32_t clamp_uint32(uint32_t min, uint32_t max, uint32_t value) { return value < min ? min : value > max ? max : value; } static void lrhi_surface_destroy_swapchain(lrhi_surface *surface, lrhi_device *device) { for (uint32_t img_view_index = 0; img_view_index < surface->img_count; img_view_index++) { vkDestroyImageView(device->device, surface->image_views[img_view_index].view, NULL); } vkDestroySwapchainKHR(device->device, surface->swapchain, NULL); } static bool create_swapchain_image_views(VkImageView *swapchain_image_views, const VkImage *swapchain_images, uint32_t swapchain_image_count, VkFormat swapchain_fmt, lrhi_device *device) { uint32_t swapchain_image_index; for (swapchain_image_index = 0; swapchain_image_index < swapchain_image_count; swapchain_image_index++) { VkImage img = swapchain_images[swapchain_image_index]; if (!create_image_view(device, img, swapchain_fmt, &swapchain_image_views[swapchain_image_index], VK_IMAGE_ASPECT_COLOR_BIT, 1)) { LRHI_LOG("Image view creation failed"); goto err; } } return true; err: for (uint32_t to_remove_index = 0; to_remove_index < swapchain_image_index; to_remove_index++) { vkDestroyImageView(device->device, swapchain_image_views[to_remove_index], NULL); } return false; } static bool lrhi_surface_create_swapchain( lrhi_surface *surface, struct lrhi_device *device, const struct lrhi_surface_configuration *configuration) { struct swapchain_support_details support_details = {0}; if (!get_swapchain_support_details(device->physical_device, surface->surface, &support_details)) { LRHI_LOG("Couldn't query swapchain support details from device"); goto err; } // Pick swapchain format VkSurfaceFormatKHR surface_fmt = {0}; for (uint32_t available_format_index = 0; available_format_index < support_details.format_count; available_format_index++) { const VkSurfaceFormatKHR *available_format = &support_details.formats[available_format_index]; if (available_format->format == VK_FORMAT_B8G8R8A8_SRGB && available_format->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { surface_fmt = *available_format; break; } } // Pick present mode VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR; for (uint32_t available_mode_index = 0; available_mode_index < support_details.present_mode_count; available_mode_index++) { VkPresentModeKHR available_mode = support_details.present_modes[available_mode_index]; if (available_mode == VK_PRESENT_MODE_MAILBOX_KHR) { present_mode = available_mode; break; } } // Pick swapchain extent VkExtent2D extent; if (support_details.capabilities.currentExtent.width != UINT32_MAX) { extent = support_details.capabilities.currentExtent; } else { VkExtent2D actual_extent = {configuration->width, configuration->height}; actual_extent.width = clamp_uint32( support_details.capabilities.minImageExtent.width, support_details.capabilities.maxImageExtent.width, actual_extent.width); actual_extent.height = clamp_uint32(support_details.capabilities.minImageExtent.height, support_details.capabilities.maxImageExtent.height, actual_extent.height); extent = actual_extent; } uint32_t image_count = support_details.capabilities.minImageCount + 1; if (support_details.capabilities.maxImageCount > 0 && image_count > support_details.capabilities.maxImageCount) { image_count = support_details.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface->surface, .minImageCount = image_count, .imageFormat = surface_fmt.format, .imageColorSpace = surface_fmt.colorSpace, .imageExtent = extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT}; struct queue_family_indices found_queue_family_indices = queue_family_indices_find_for_device(device->physical_device, surface->surface); uint32_t queue_family_indices[] = {found_queue_family_indices.graphics_family, found_queue_family_indices.present_family}; if (found_queue_family_indices.graphics_family != found_queue_family_indices.present_family) { create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; create_info.queueFamilyIndexCount = 2; create_info.pQueueFamilyIndices = queue_family_indices; } else { create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } create_info.preTransform = support_details.capabilities.currentTransform; create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; create_info.presentMode = present_mode; create_info.clipped = VK_TRUE; create_info.oldSwapchain = VK_NULL_HANDLE; if (vkCreateSwapchainKHR(device->device, &create_info, NULL, &surface->swapchain) != VK_SUCCESS) { LRHI_LOG("Swapchain creation failed!"); goto err; } if (vkGetSwapchainImagesKHR(device->device, surface->swapchain, &surface->img_count, NULL) != VK_SUCCESS) { LRHI_LOG("Couldn't get swapchain image count"); goto destroy_swapchain; } if (surface->img_count > VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY) { LRHI_LOG("Swapchain image array cannot fit all %u swapchain images", surface->img_count); goto destroy_swapchain; } VkImage images[VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY] = {0}; if (vkGetSwapchainImagesKHR(device->device, surface->swapchain, &surface->img_count, images) != VK_SUCCESS) { LRHI_LOG("Couldn't get swapchain images"); goto destroy_swapchain; } VkImageView image_views[VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY] = {0}; if (!create_swapchain_image_views(image_views, images, surface->img_count, surface_fmt.format, device)) { LRHI_LOG("Couldn't create swapchain image views."); goto destroy_swapchain; } for (uint32_t i = 0; i < surface->img_count; i++) { surface->images[i].image = images[i]; surface->image_views[i].view = image_views[i]; } surface->extent = extent; surface->fmt = surface_fmt.format; surface->resized = false; return true; destroy_swapchain: vkDestroySwapchainKHR(device->device, surface->swapchain, NULL); err: return false; } void lrhi_surface_reconfigure( lrhi_surface *surface, lrhi_device *device, const struct lrhi_surface_configuration *surface_configuration) { lrhi_surface_destroy_swapchain(surface, device); lrhi_surface_create_swapchain(surface, device, surface_configuration); surface->surface_reconfigured_callback( surface->extent.width, surface->extent.height, surface->surface_reconfigured_user_data); } bool lrhi_surface_configure( lrhi_surface *surface, struct lrhi_device *device, const struct lrhi_surface_configuration *configuration) { if (!lrhi_surface_create_swapchain(surface, device, configuration)) { LRHI_LOG_ERR("Swapchain creation failed"); goto fail; } VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO}; uint32_t semaphore_frame_idx = 0; for (semaphore_frame_idx = 0; semaphore_frame_idx < VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY; semaphore_frame_idx++) { if ((semaphore_frame_idx < MAX_FRAME_IN_FLIGHT && vkCreateSemaphore( device->device, &semaphore_info, NULL, &surface->image_available_semaphore[semaphore_frame_idx]) != VK_SUCCESS) || vkCreateSemaphore( device->device, &semaphore_info, NULL, &surface->render_finished_semaphore[semaphore_frame_idx]) != VK_SUCCESS) { LRHI_LOG_ERR("Surface semaphores creation failed"); goto destroy_semaphores; } } return true; destroy_semaphores: for (uint32_t i = 0; i < semaphore_frame_idx; i++) { if (i < MAX_FRAME_IN_FLIGHT) { vkDestroySemaphore( device->device, surface->image_available_semaphore[semaphore_frame_idx], NULL); } vkDestroySemaphore(device->device, surface->render_finished_semaphore[semaphore_frame_idx], NULL); } lrhi_surface_destroy_swapchain(surface, device); fail: return false; } void lrhi_surface_resize(lrhi_surface *surface, int32_t width, int32_t height) { surface->new_extent.width = width; surface->new_extent.height = height; surface->resized = true; } lrhi_texture_view *lrhi_surface_acquire_next_image(lrhi_surface *surface, lrhi_device *device) { vkWaitForFences(device->device, 1, &device->in_flight_fence[device->current_frame], VK_TRUE, UINT64_MAX); VkResult acquire_result = vkAcquireNextImageKHR( device->device, surface->swapchain, UINT64_MAX, surface->image_available_semaphore[device->current_frame], VK_NULL_HANDLE, &surface->image_index); if (acquire_result == VK_ERROR_OUT_OF_DATE_KHR) { lrhi_surface_reconfigure( surface, device, &(const struct lrhi_surface_configuration){ .width = surface->extent.width, .height = surface->extent.height}); } else if (acquire_result != VK_SUCCESS && acquire_result != VK_SUBOPTIMAL_KHR) { LRHI_LOG_ERR("Failed to acquire swapchain image"); goto fail; } vkResetFences(device->device, 1, &device->in_flight_fence[device->current_frame]); return &surface->image_views[surface->image_index]; fail: return NULL; } bool lrhi_surface_present(lrhi_device *device, lrhi_surface *surface) { VkSwapchainKHR swapchains[] = {surface->swapchain}; VkSemaphore signal_semaphores[] = { surface->render_finished_semaphore[surface->image_index]}; VkPresentInfoKHR present_info = { VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = signal_semaphores, .swapchainCount = 1, .pSwapchains = swapchains, .pImageIndices = &surface->image_index, }; device->current_frame = (device->current_frame + 1) % MAX_FRAME_IN_FLIGHT; VkResult present_result = vkQueuePresentKHR(device->present_queue, &present_info); if (present_result == VK_ERROR_OUT_OF_DATE_KHR || present_result == VK_SUBOPTIMAL_KHR || surface->resized) { surface->resized = false; vkDeviceWaitIdle(device->device); framebuffer_cache_clear(device); lrhi_surface_reconfigure( surface, device, &(const struct lrhi_surface_configuration){ .width = surface->extent.width, .height = surface->extent.height}); } else if (present_result != VK_SUCCESS) { LRHI_LOG_ERR("Failed to present swapchain image"); goto fail; } return true; fail: return false; } void lrhi_instance_destroy_surface(lrhi_instance *instance, lrhi_device *device, lrhi_surface *surface) { for (int i = 0; i < VK_SWAPCHAIN_IMAGE_BUFFER_CAPACITY; i++) { if (i < MAX_FRAME_IN_FLIGHT) { vkDestroySemaphore(device->device, surface->image_available_semaphore[i], NULL); } vkDestroySemaphore(device->device, surface->render_finished_semaphore[i], NULL); } if (surface->swapchain != NULL) { lrhi_surface_destroy_swapchain(surface, device); } vkDestroySurfaceKHR(instance->instance, surface->surface, NULL); lrhi_allocator_free(&instance->allocator, sizeof(struct lrhi_surface), surface); }