#include "renderer.h" #include "../image.h" #include "../log.h" #include "../maths.h" #include "../platform.h" #include "vma_usage.h" #include #define TINYOBJ_LOADER_C_IMPLEMENTATION #include "vendor/tiny_obj_loader_c.h" #include #include static const char MODEL_PATH[] = "assets/model.obj"; static const char TEXTURE_PATH[] = "assets/texture.png"; VkVertexInputBindingDescription vgltf_vertex_binding_description() { return (VkVertexInputBindingDescription){ .binding = 0, .stride = sizeof(struct vgltf_vertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX}; } struct vgltf_vertex_input_attribute_descriptions vgltf_vertex_attribute_descriptions(void) { return (struct vgltf_vertex_input_attribute_descriptions){ .descriptions = {(VkVertexInputAttributeDescription){ .binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct vgltf_vertex, position)}, (VkVertexInputAttributeDescription){ .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct vgltf_vertex, color)}, (VkVertexInputAttributeDescription){ .binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(struct vgltf_vertex, texture_coordinates)}}, .count = 3}; } static const char *VALIDATION_LAYERS[] = {"VK_LAYER_KHRONOS_validation"}; static constexpr int VALIDATION_LAYER_COUNT = sizeof(VALIDATION_LAYERS) / sizeof(VALIDATION_LAYERS[0]); #ifdef VGLTF_DEBUG static constexpr bool enable_validation_layers = true; #else static constexpr bool enable_validation_layers = false; #endif static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback(VkDebugUtilsMessageSeverityFlagBitsEXT message_severity, VkDebugUtilsMessageTypeFlagBitsEXT message_type, const VkDebugUtilsMessengerCallbackDataEXT *callback_data, void *user_data) { (void)message_severity; (void)message_type; (void)user_data; VGLTF_LOG_DBG("validation layer: %s", callback_data->pMessage); return VK_FALSE; } static constexpr int REQUIRED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY = 10; struct required_instance_extensions { const char *extensions[REQUIRED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY]; uint32_t count; }; void required_instance_extensions_push( struct required_instance_extensions *required_instance_extensions, const char *required_instance_extension) { if (required_instance_extensions->count == REQUIRED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY) { VGLTF_PANIC("required instance extensions array is full"); } required_instance_extensions ->extensions[required_instance_extensions->count++] = required_instance_extension; } static constexpr int SUPPORTED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY = 128; struct supported_instance_extensions { VkExtensionProperties properties[SUPPORTED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY]; uint32_t count; }; bool supported_instance_extensions_init( struct supported_instance_extensions *supported_instance_extensions) { if (vkEnumerateInstanceExtensionProperties( nullptr, &supported_instance_extensions->count, nullptr) != VK_SUCCESS) { goto err; } if (supported_instance_extensions->count > SUPPORTED_INSTANCE_EXTENSIONS_ARRAY_CAPACITY) { VGLTF_LOG_ERR("supported instance extensions array cannot fit all the " "VkExtensionProperties"); goto err; } if (vkEnumerateInstanceExtensionProperties( nullptr, &supported_instance_extensions->count, supported_instance_extensions->properties) != VK_SUCCESS) { goto err; } return true; err: return false; } void supported_instance_extensions_debug_print( const struct supported_instance_extensions *supported_instance_extensions) { VGLTF_LOG_DBG("Supported instance extensions:"); for (uint32_t i = 0; i < supported_instance_extensions->count; i++) { VGLTF_LOG_DBG("\t- %s", supported_instance_extensions->properties[i].extensionName); } } bool supported_instance_extensions_includes( const struct supported_instance_extensions *supported_instance_extensions, const char *extension_name) { for (uint32_t supported_instance_extension_index = 0; supported_instance_extension_index < supported_instance_extensions->count; supported_instance_extension_index++) { const VkExtensionProperties *extension_properties = &supported_instance_extensions ->properties[supported_instance_extension_index]; if (strcmp(extension_properties->extensionName, extension_name) == 0) { return true; } } return false; } static constexpr uint32_t SUPPORTED_VALIDATION_LAYERS_ARRAY_CAPACITY = 64; struct supported_validation_layers { VkLayerProperties properties[SUPPORTED_VALIDATION_LAYERS_ARRAY_CAPACITY]; uint32_t count; }; bool supported_validation_layers_init( struct supported_validation_layers *supported_validation_layers) { if (vkEnumerateInstanceLayerProperties(&supported_validation_layers->count, nullptr) != VK_SUCCESS) { goto err; } if (supported_validation_layers->count > SUPPORTED_VALIDATION_LAYERS_ARRAY_CAPACITY) { VGLTF_LOG_ERR("supported validation layers array cannot fit all the " "VkLayerProperties"); goto err; } if (vkEnumerateInstanceLayerProperties( &supported_validation_layers->count, supported_validation_layers->properties) != VK_SUCCESS) { goto err; } return true; err: return false; } static bool are_validation_layer_supported() { struct supported_validation_layers supported_layers = {}; if (!supported_validation_layers_init(&supported_layers)) { goto err; } for (int requested_layer_index = 0; requested_layer_index < VALIDATION_LAYER_COUNT; requested_layer_index++) { const char *requested_layer_name = VALIDATION_LAYERS[requested_layer_index]; bool requested_layer_found = false; for (uint32_t supported_layer_index = 0; supported_layer_index < supported_layers.count; supported_layer_index++) { VkLayerProperties *supported_layer = &supported_layers.properties[supported_layer_index]; if (strcmp(requested_layer_name, supported_layer->layerName) == 0) { requested_layer_found = true; break; } } if (!requested_layer_found) { goto err; } } return true; err: return false; } static bool fetch_required_instance_extensions( struct required_instance_extensions *required_extensions, struct vgltf_platform *platform) { struct supported_instance_extensions supported_extensions = {}; if (!supported_instance_extensions_init(&supported_extensions)) { VGLTF_LOG_ERR( "Couldn't fetch supported instance extensions details (OOM?)"); goto err; } supported_instance_extensions_debug_print(&supported_extensions); uint32_t platform_required_extension_count = 0; const char *const *platform_required_extensions = vgltf_platform_get_vulkan_instance_extensions( platform, &platform_required_extension_count); for (uint32_t platform_required_extension_index = 0; platform_required_extension_index < platform_required_extension_count; platform_required_extension_index++) { required_instance_extensions_push( required_extensions, platform_required_extensions[platform_required_extension_index]); } #ifdef VGLTF_PLATFORM_MACOS required_instance_extensions_push( required_extensions, VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME); #endif // VGLTF_PLATFORM_MACOS if (enable_validation_layers) { required_instance_extensions_push(required_extensions, VK_EXT_DEBUG_UTILS_EXTENSION_NAME); } bool all_extensions_supported = true; for (uint32_t required_extension_index = 0; required_extension_index < required_extensions->count; required_extension_index++) { const char *required_extension_name = required_extensions->extensions[required_extension_index]; if (!supported_instance_extensions_includes(&supported_extensions, required_extension_name)) { VGLTF_LOG_ERR("Unsupported instance extension: %s", required_extension_name); all_extensions_supported = false; } } if (!all_extensions_supported) { VGLTF_LOG_ERR("Some required extensions are unsupported."); goto err; } return true; err: return false; } static void populate_debug_messenger_create_info( VkDebugUtilsMessengerCreateInfoEXT *create_info) { *create_info = (VkDebugUtilsMessengerCreateInfoEXT){}; create_info->sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; create_info->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; create_info->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; create_info->pfnUserCallback = debug_callback; } static bool vgltf_vk_instance_init(struct vgltf_vk_instance *instance, struct vgltf_platform *platform) { VGLTF_LOG_INFO("Creating vulkan instance..."); if (enable_validation_layers && !are_validation_layer_supported()) { VGLTF_LOG_ERR("Requested validation layers aren't supported"); goto err; } VkApplicationInfo application_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "Visible GLTF", .applicationVersion = VK_MAKE_VERSION(0, 1, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_2}; struct required_instance_extensions required_extensions = {}; fetch_required_instance_extensions(&required_extensions, platform); VkInstanceCreateFlags flags = 0; #ifdef VGLTF_PLATFORM_MACOS flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; #endif // VGLTF_PLATFORM_MACOS VkInstanceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &application_info, .enabledExtensionCount = required_extensions.count, .ppEnabledExtensionNames = required_extensions.extensions, .flags = flags}; VkDebugUtilsMessengerCreateInfoEXT debug_create_info; if (enable_validation_layers) { create_info.enabledLayerCount = VALIDATION_LAYER_COUNT; create_info.ppEnabledLayerNames = VALIDATION_LAYERS; populate_debug_messenger_create_info(&debug_create_info); create_info.pNext = &debug_create_info; } if (vkCreateInstance(&create_info, nullptr, &instance->instance) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to create VkInstance"); goto err; } return true; err: return false; } static void vgltf_vk_instance_deinit(struct vgltf_vk_instance *instance) { vkDestroyInstance(instance->instance, nullptr); } static VkResult create_debug_utils_messenger_ext( VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *create_info, const VkAllocationCallbacks *allocator, VkDebugUtilsMessengerEXT *debug_messenger) { auto func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkCreateDebugUtilsMessengerEXT"); if (func != nullptr) { return func(instance, create_info, allocator, debug_messenger); } return VK_ERROR_EXTENSION_NOT_PRESENT; } static void destroy_debug_utils_messenger_ext(VkInstance instance, VkDebugUtilsMessengerEXT debug_messenger, const VkAllocationCallbacks *allocator) { auto func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != nullptr) { func(instance, debug_messenger, allocator); } } static void vgltf_renderer_setup_debug_messenger(struct vgltf_renderer *renderer) { if (!enable_validation_layers) return; VkDebugUtilsMessengerCreateInfoEXT create_info; populate_debug_messenger_create_info(&create_info); create_debug_utils_messenger_ext(renderer->instance.instance, &create_info, nullptr, &renderer->debug_messenger); } static constexpr int AVAILABLE_PHYSICAL_DEVICE_ARRAY_CAPACITY = 128; struct available_physical_devices { VkPhysicalDevice devices[AVAILABLE_PHYSICAL_DEVICE_ARRAY_CAPACITY]; uint32_t count; }; static bool available_physical_devices_init(VkInstance instance, struct available_physical_devices *devices) { if (vkEnumeratePhysicalDevices(instance, &devices->count, nullptr) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't enumerate physical devices"); goto err; } if (devices->count == 0) { VGLTF_LOG_ERR("Failed to find any GPU with Vulkan support"); goto err; } if (devices->count > AVAILABLE_PHYSICAL_DEVICE_ARRAY_CAPACITY) { VGLTF_LOG_ERR("available physical devices array cannot fit all available " "physical devices"); goto err; } if (vkEnumeratePhysicalDevices(instance, &devices->count, devices->devices) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't enumerate physical devices"); goto err; } return true; err: return false; } struct queue_family_indices { uint32_t graphics_family; uint32_t present_family; bool has_graphics_family; bool has_present_family; }; bool queue_family_indices_is_complete( const struct queue_family_indices *indices) { return indices->has_graphics_family && indices->has_present_family; } bool queue_family_indices_for_device(struct queue_family_indices *indices, VkPhysicalDevice device, VkSurfaceKHR surface) { static constexpr uint32_t QUEUE_FAMILY_PROPERTIES_ARRAY_CAPACITY = 64; uint32_t queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, nullptr); if (queue_family_count > QUEUE_FAMILY_PROPERTIES_ARRAY_CAPACITY) { VGLTF_LOG_ERR( "Queue family properties array cannot fit all queue family properties"); goto err; } VkQueueFamilyProperties queue_family_properties[QUEUE_FAMILY_PROPERTIES_ARRAY_CAPACITY] = {}; vkGetPhysicalDeviceQueueFamilyProperties(device, &queue_family_count, queue_family_properties); for (uint32_t queue_family_index = 0; queue_family_index < queue_family_count; queue_family_index++) { VkQueueFamilyProperties *queue_family = &queue_family_properties[queue_family_index]; VkBool32 present_support; vkGetPhysicalDeviceSurfaceSupportKHR(device, queue_family_index, surface, &present_support); if (queue_family->queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices->graphics_family = queue_family_index; indices->has_graphics_family = true; } if (present_support) { indices->present_family = queue_family_index; indices->has_present_family = true; } if (queue_family_indices_is_complete(indices)) { break; } } return true; err: return false; } static bool is_in_array(uint32_t *array, int length, uint32_t value) { for (int i = 0; i < length; i++) { if (array[i] == value) { return true; } } return false; } static constexpr uint32_t SUPPORTED_EXTENSIONS_ARRAY_CAPACITY = 1024; struct supported_extensions { VkExtensionProperties properties[SUPPORTED_EXTENSIONS_ARRAY_CAPACITY]; uint32_t count; }; bool supported_extensions_init( struct supported_extensions *supported_extensions, VkPhysicalDevice device) { if (vkEnumerateDeviceExtensionProperties(device, nullptr, &supported_extensions->count, nullptr) != VK_SUCCESS) { goto err; } if (supported_extensions->count > SUPPORTED_EXTENSIONS_ARRAY_CAPACITY) { VGLTF_LOG_ERR("supported extensions array cannot fit all the supported " "VkExtensionProperties (%u)", supported_extensions->count); goto err; } if (vkEnumerateDeviceExtensionProperties( device, nullptr, &supported_extensions->count, supported_extensions->properties) != VK_SUCCESS) { goto err; } return true; err: return false; } static bool supported_extensions_includes_extension( struct supported_extensions *supported_extensions, const char *extension_name) { for (uint32_t supported_extension_index = 0; supported_extension_index < supported_extensions->count; supported_extension_index++) { if (strcmp(supported_extensions->properties[supported_extension_index] .extensionName, extension_name) == 0) { return true; } } return false; } static const char *DEVICE_EXTENSIONS[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, #ifdef VGLTF_PLATFORM_MACOS "VK_KHR_portability_subset", #endif }; static constexpr int DEVICE_EXTENSION_COUNT = sizeof(DEVICE_EXTENSIONS) / sizeof(DEVICE_EXTENSIONS[0]); static bool are_device_extensions_supported(VkPhysicalDevice device) { struct supported_extensions supported_extensions = {}; if (!supported_extensions_init(&supported_extensions, device)) { goto err; } for (uint32_t required_extension_index = 0; required_extension_index < DEVICE_EXTENSION_COUNT; required_extension_index++) { if (!supported_extensions_includes_extension( &supported_extensions, DEVICE_EXTENSIONS[required_extension_index])) { VGLTF_LOG_DBG("Unsupported: %s", DEVICE_EXTENSIONS[required_extension_index]); goto err; } } return true; err: return false; } static constexpr int SWAPCHAIN_SUPPORT_DETAILS_MAX_SURFACE_FORMAT_COUNT = 256; static constexpr int SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT = 256; struct swapchain_support_details { VkSurfaceCapabilitiesKHR capabilities; VkSurfaceFormatKHR formats[SWAPCHAIN_SUPPORT_DETAILS_MAX_SURFACE_FORMAT_COUNT]; VkPresentModeKHR present_modes[SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT]; uint32_t format_count; uint32_t present_mode_count; }; bool swapchain_support_details_query_from_device( struct swapchain_support_details *swapchain_support_details, VkPhysicalDevice device, VkSurfaceKHR surface) { if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR( device, surface, &swapchain_support_details->capabilities) != VK_SUCCESS) { goto err; } if (vkGetPhysicalDeviceSurfaceFormatsKHR( device, surface, &swapchain_support_details->format_count, nullptr) != VK_SUCCESS) { goto err; } if (swapchain_support_details->format_count != 0 && swapchain_support_details->format_count < SWAPCHAIN_SUPPORT_DETAILS_MAX_SURFACE_FORMAT_COUNT) { if (vkGetPhysicalDeviceSurfaceFormatsKHR( device, surface, &swapchain_support_details->format_count, swapchain_support_details->formats) != VK_SUCCESS) { goto err; } } if (vkGetPhysicalDeviceSurfacePresentModesKHR( device, surface, &swapchain_support_details->present_mode_count, nullptr) != VK_SUCCESS) { goto err; } if (swapchain_support_details->present_mode_count != 0 && swapchain_support_details->present_mode_count < SWAPCHAIN_SUPPORT_DETAILS_MAX_PRESENT_MODE_COUNT) { if (vkGetPhysicalDeviceSurfacePresentModesKHR( device, surface, &swapchain_support_details->present_mode_count, swapchain_support_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_for_device(&indices, device, surface); VGLTF_LOG_DBG("Checking for physical device extension support"); bool extensions_supported = are_device_extensions_supported(device); VGLTF_LOG_DBG("Supported: %d", extensions_supported); bool swapchain_adequate = false; if (extensions_supported) { VGLTF_LOG_DBG("Checking for swapchain support details"); struct swapchain_support_details swapchain_support_details = {}; if (!swapchain_support_details_query_from_device(&swapchain_support_details, device, surface)) { VGLTF_LOG_ERR("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; } VkPhysicalDeviceFeatures supported_features; vkGetPhysicalDeviceFeatures(device, &supported_features); return queue_family_indices_is_complete(&indices) && extensions_supported && swapchain_adequate && supported_features.samplerAnisotropy; err: return false; } static bool pick_physical_device(VkPhysicalDevice *physical_device, struct vgltf_vk_instance *instance, VkSurfaceKHR surface) { VkPhysicalDevice vk_physical_device = VK_NULL_HANDLE; struct available_physical_devices available_physical_devices = {}; if (!available_physical_devices_init(instance->instance, &available_physical_devices)) { VGLTF_LOG_ERR("Couldn't fetch available physical devices"); goto err; } for (uint32_t available_physical_device_index = 0; available_physical_device_index < available_physical_devices.count; available_physical_device_index++) { VkPhysicalDevice available_physical_device = available_physical_devices.devices[available_physical_device_index]; if (is_physical_device_suitable(available_physical_device, surface)) { vk_physical_device = available_physical_device; break; } } if (vk_physical_device == VK_NULL_HANDLE) { VGLTF_LOG_ERR("Failed to find a suitable GPU"); goto err; } *physical_device = vk_physical_device; return true; err: return false; } static bool create_logical_device(VkDevice *device, VkQueue *graphics_queue, VkQueue *present_queue, VkPhysicalDevice physical_device, VkSurfaceKHR surface) { struct queue_family_indices queue_family_indices = {}; queue_family_indices_for_device(&queue_family_indices, physical_device, surface); static constexpr int MAX_QUEUE_FAMILY_COUNT = 2; uint32_t unique_queue_families[MAX_QUEUE_FAMILY_COUNT] = {}; int unique_queue_family_count = 0; if (!is_in_array(unique_queue_families, unique_queue_family_count, queue_family_indices.graphics_family)) { assert(unique_queue_family_count < MAX_QUEUE_FAMILY_COUNT); unique_queue_families[unique_queue_family_count++] = queue_family_indices.graphics_family; } if (!is_in_array(unique_queue_families, unique_queue_family_count, queue_family_indices.present_family)) { assert(unique_queue_family_count < MAX_QUEUE_FAMILY_COUNT); unique_queue_families[unique_queue_family_count++] = queue_family_indices.present_family; } float queue_priority = 1.f; VkDeviceQueueCreateInfo queue_create_infos[MAX_QUEUE_FAMILY_COUNT] = {}; int queue_create_info_count = 0; for (int unique_queue_family_index = 0; unique_queue_family_index < unique_queue_family_count; unique_queue_family_index++) { queue_create_infos[queue_create_info_count++] = (VkDeviceQueueCreateInfo){ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = unique_queue_families[unique_queue_family_index], .queueCount = 1, .pQueuePriorities = &queue_priority}; } VkPhysicalDeviceFeatures device_features = { .samplerAnisotropy = VK_TRUE, }; VkDeviceCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pQueueCreateInfos = queue_create_infos, .queueCreateInfoCount = queue_create_info_count, .pEnabledFeatures = &device_features, .ppEnabledExtensionNames = DEVICE_EXTENSIONS, .enabledExtensionCount = DEVICE_EXTENSION_COUNT}; if (vkCreateDevice(physical_device, &create_info, nullptr, device) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to create logical device"); goto err; } vkGetDeviceQueue(*device, queue_family_indices.graphics_family, 0, graphics_queue); vkGetDeviceQueue(*device, queue_family_indices.present_family, 0, present_queue); return true; err: return false; } static bool create_allocator(VmaAllocator *allocator, struct vgltf_vk_device *device, struct vgltf_vk_instance *instance) { VmaAllocatorCreateInfo create_info = {.device = device->device, .instance = instance->instance, .physicalDevice = device->physical_device}; if (vmaCreateAllocator(&create_info, allocator) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create VMA allocator"); goto err; } return true; err: return false; } static bool vgltf_vk_surface_init(struct vgltf_vk_surface *surface, struct vgltf_vk_instance *instance, struct vgltf_platform *platform) { if (!vgltf_platform_create_vulkan_surface(platform, instance->instance, &surface->surface)) { VGLTF_LOG_ERR("Couldn't create surface"); goto err; } return true; err: return false; } static void vgltf_vk_surface_deinit(struct vgltf_vk_surface *surface, struct vgltf_vk_instance *instance) { vkDestroySurfaceKHR(instance->instance, surface->surface, nullptr); } static VkSurfaceFormatKHR choose_swapchain_surface_format(VkSurfaceFormatKHR *available_formats, uint32_t available_format_count) { for (uint32_t available_format_index = 0; available_format_index < available_format_count; available_format_index++) { VkSurfaceFormatKHR *available_format = &available_formats[available_format_index]; if (available_format->format == VK_FORMAT_B8G8R8A8_SRGB && available_format->colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return *available_format; } } return available_formats[0]; } static VkPresentModeKHR choose_swapchain_present_mode(VkPresentModeKHR *available_modes, uint32_t available_mode_count) { for (uint32_t available_mode_index = 0; available_mode_index < available_mode_count; available_mode_index++) { VkPresentModeKHR available_mode = available_modes[available_mode_index]; if (available_mode == VK_PRESENT_MODE_MAILBOX_KHR) { return available_mode; } } return VK_PRESENT_MODE_FIFO_KHR; } static uint32_t clamp_uint32(uint32_t min, uint32_t max, uint32_t value) { return value < min ? min : value > max ? max : value; } static VkExtent2D choose_swapchain_extent(const VkSurfaceCapabilitiesKHR *capabilities, int width, int height) { if (capabilities->currentExtent.width != UINT32_MAX) { return capabilities->currentExtent; } else { VkExtent2D actual_extent = {width, height}; actual_extent.width = clamp_uint32(capabilities->minImageExtent.width, capabilities->maxImageExtent.width, actual_extent.width); actual_extent.height = clamp_uint32(capabilities->minImageExtent.height, capabilities->maxImageExtent.height, actual_extent.height); return actual_extent; } } static bool create_swapchain(struct vgltf_vk_swapchain *swapchain, struct vgltf_vk_device *device, struct vgltf_vk_surface *surface, struct vgltf_window_size *window_size) { struct swapchain_support_details swapchain_support_details = {}; swapchain_support_details_query_from_device( &swapchain_support_details, device->physical_device, surface->surface); VkSurfaceFormatKHR surface_format = choose_swapchain_surface_format(swapchain_support_details.formats, swapchain_support_details.format_count); VkPresentModeKHR present_mode = choose_swapchain_present_mode( swapchain_support_details.present_modes, swapchain_support_details.present_mode_count); VkExtent2D extent = choose_swapchain_extent(&swapchain_support_details.capabilities, window_size->width, window_size->height); uint32_t image_count = swapchain_support_details.capabilities.minImageCount + 1; if (swapchain_support_details.capabilities.maxImageCount > 0 && image_count > swapchain_support_details.capabilities.maxImageCount) { image_count = swapchain_support_details.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR create_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface->surface, .minImageCount = image_count, .imageFormat = surface_format.format, .imageColorSpace = surface_format.colorSpace, .imageExtent = extent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT}; struct queue_family_indices indices = {}; queue_family_indices_for_device(&indices, device->physical_device, surface->surface); uint32_t queue_family_indices[] = {indices.graphics_family, indices.present_family}; if (indices.graphics_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 = swapchain_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, nullptr, &swapchain->swapchain) != VK_SUCCESS) { VGLTF_LOG_ERR("Swapchain creation failed!"); goto err; } if (vkGetSwapchainImagesKHR(device->device, swapchain->swapchain, &swapchain->swapchain_image_count, nullptr) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't get swapchain image count"); goto destroy_swapchain; } if (swapchain->swapchain_image_count > VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT) { VGLTF_LOG_ERR("Swapchain image array cannot fit all %d swapchain images", swapchain->swapchain_image_count); goto destroy_swapchain; } if (vkGetSwapchainImagesKHR(device->device, swapchain->swapchain, &swapchain->swapchain_image_count, swapchain->swapchain_images) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't get swapchain images"); goto destroy_swapchain; } swapchain->swapchain_image_format = surface_format.format; swapchain->swapchain_extent = extent; return true; destroy_swapchain: vkDestroySwapchainKHR(device->device, swapchain->swapchain, nullptr); err: return false; } static bool create_image_view(struct vgltf_vk_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, nullptr, image_view) != VK_SUCCESS) { return false; } return true; } static bool create_swapchain_image_views(struct vgltf_vk_swapchain *swapchain, struct vgltf_vk_device *device) { uint32_t swapchain_image_index; for (swapchain_image_index = 0; swapchain_image_index < swapchain->swapchain_image_count; swapchain_image_index++) { VkImage swapchain_image = swapchain->swapchain_images[swapchain_image_index]; if (!create_image_view( device, swapchain_image, swapchain->swapchain_image_format, &swapchain->swapchain_image_views[swapchain_image_index], VK_IMAGE_ASPECT_COLOR_BIT, 1)) { 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->swapchain_image_views[to_remove_index], nullptr); } return false; } static bool create_shader_module(VkDevice device, const unsigned char *code, int size, VkShaderModule *out) { VkShaderModuleCreateInfo create_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = size, .pCode = (const uint32_t *)code, }; if (vkCreateShaderModule(device, &create_info, nullptr, out) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create shader module"); goto err; } return true; err: return false; } static VkFormat find_supported_format(struct vgltf_renderer *renderer, const VkFormat *candidates, int candidate_count, VkImageTiling tiling, VkFormatFeatureFlags features) { for (int candidate_index = 0; candidate_index < candidate_count; candidate_index++) { VkFormat candidate = candidates[candidate_index]; VkFormatProperties properties; vkGetPhysicalDeviceFormatProperties(renderer->device.physical_device, candidate, &properties); if (tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) { return candidate; } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) { return candidate; } } return VK_FORMAT_UNDEFINED; } static VkFormat find_depth_format(struct vgltf_renderer *renderer) { return find_supported_format(renderer, (const VkFormat[]){VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, 3, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); } static bool vgltf_renderer_create_render_pass(struct vgltf_renderer *renderer) { VkAttachmentDescription color_attachment = { .format = renderer->swapchain.swapchain_image_format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR}; VkAttachmentReference color_attachment_ref = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkAttachmentDescription depth_attachment = { .format = find_depth_format(renderer), .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL}; VkAttachmentReference depth_attachment_ref = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .pColorAttachments = &color_attachment_ref, .colorAttachmentCount = 1, .pDepthStencilAttachment = &depth_attachment_ref}; VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, .srcAccessMask = 0, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT}; VkAttachmentDescription attachments[] = {color_attachment, depth_attachment}; int attachment_count = sizeof(attachments) / sizeof(attachments[0]); VkRenderPassCreateInfo render_pass_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = attachment_count, .pAttachments = attachments, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency}; if (vkCreateRenderPass(renderer->device.device, &render_pass_info, nullptr, &renderer->render_pass) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to create render pass"); goto err; } return true; err: return false; } static bool vgltf_renderer_create_descriptor_set_layout(struct vgltf_renderer *renderer) { VkDescriptorSetLayoutBinding ubo_layout_binding = { .binding = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, }; VkDescriptorSetLayoutBinding sampler_layout_binding = { .binding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }; VkDescriptorSetLayoutBinding bindings[] = {ubo_layout_binding, sampler_layout_binding}; int binding_count = sizeof(bindings) / sizeof(bindings[0]); VkDescriptorSetLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = binding_count, .pBindings = bindings}; if (vkCreateDescriptorSetLayout(renderer->device.device, &layout_info, nullptr, &renderer->descriptor_set_layout) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to create descriptor set layout"); goto err; } return true; err: return false; } static bool vgltf_renderer_create_graphics_pipeline(struct vgltf_renderer *renderer) { static constexpr unsigned char triangle_shader_vert_code[] = { #embed "../compiled_shaders/triangle.vert.spv" }; static constexpr unsigned char triangle_shader_frag_code[] = { #embed "../compiled_shaders/triangle.frag.spv" }; VkShaderModule triangle_shader_vert_module; if (!create_shader_module(renderer->device.device, triangle_shader_vert_code, sizeof(triangle_shader_vert_code), &triangle_shader_vert_module)) { VGLTF_LOG_ERR("Couldn't create triangle vert shader module"); goto err; } VkShaderModule triangle_shader_frag_module; if (!create_shader_module(renderer->device.device, triangle_shader_frag_code, sizeof(triangle_shader_frag_code), &triangle_shader_frag_module)) { VGLTF_LOG_ERR("Couldn't create triangle frag shader module"); goto destroy_vert_shader_module; } VkPipelineShaderStageCreateInfo triangle_shader_vert_stage_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = triangle_shader_vert_module, .pName = "main"}; VkPipelineShaderStageCreateInfo triangle_shader_frag_stage_create_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = triangle_shader_frag_module, .pName = "main"}; VkPipelineShaderStageCreateInfo shader_stages[] = { triangle_shader_vert_stage_create_info, triangle_shader_frag_stage_create_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 = sizeof(dynamic_states) / sizeof(dynamic_states[0]), .pDynamicStates = dynamic_states}; VkVertexInputBindingDescription vertex_binding_description = vgltf_vertex_binding_description(); struct vgltf_vertex_input_attribute_descriptions vertex_attribute_descriptions = vgltf_vertex_attribute_descriptions(); VkPipelineVertexInputStateCreateInfo vertex_input_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .vertexAttributeDescriptionCount = vertex_attribute_descriptions.count, .pVertexBindingDescriptions = &vertex_binding_description, .pVertexAttributeDescriptions = vertex_attribute_descriptions.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, }; VkPipelineDepthStencilStateCreateInfo depth_stencil = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS, .depthBoundsTestEnable = VK_FALSE, .stencilTestEnable = VK_FALSE, }; VkPipelineColorBlendStateCreateInfo color_blending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .attachmentCount = 1, .pAttachments = &color_blend_attachment}; VkPipelineLayoutCreateInfo pipeline_layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 1, .pSetLayouts = &renderer->descriptor_set_layout}; if (vkCreatePipelineLayout(renderer->device.device, &pipeline_layout_info, nullptr, &renderer->pipeline_layout) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create pipeline layout"); goto destroy_frag_shader_module; } 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, .pColorBlendState = &color_blending, .pDepthStencilState = &depth_stencil, .pDynamicState = &dynamic_state, .layout = renderer->pipeline_layout, .renderPass = renderer->render_pass, .subpass = 0, }; if (vkCreateGraphicsPipelines(renderer->device.device, VK_NULL_HANDLE, 1, &pipeline_info, nullptr, &renderer->graphics_pipeline) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create pipeline"); goto destroy_pipeline_layout; } vkDestroyShaderModule(renderer->device.device, triangle_shader_frag_module, nullptr); vkDestroyShaderModule(renderer->device.device, triangle_shader_vert_module, nullptr); return true; destroy_pipeline_layout: vkDestroyPipelineLayout(renderer->device.device, renderer->pipeline_layout, nullptr); destroy_frag_shader_module: vkDestroyShaderModule(renderer->device.device, triangle_shader_frag_module, nullptr); destroy_vert_shader_module: vkDestroyShaderModule(renderer->device.device, triangle_shader_vert_module, nullptr); err: return false; } static bool vgltf_renderer_create_framebuffers(struct vgltf_renderer *renderer) { for (uint32_t i = 0; i < renderer->swapchain.swapchain_image_count; i++) { VkImageView attachments[] = {renderer->swapchain.swapchain_image_views[i], renderer->depth_image_view}; int attachment_count = sizeof(attachments) / sizeof(attachments[0]); VkFramebufferCreateInfo framebuffer_info = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = renderer->render_pass, .attachmentCount = attachment_count, .pAttachments = attachments, .width = renderer->swapchain.swapchain_extent.width, .height = renderer->swapchain.swapchain_extent.height, .layers = 1}; if (vkCreateFramebuffer(renderer->device.device, &framebuffer_info, nullptr, &renderer->swapchain_framebuffers[i]) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to create framebuffer"); goto err; } } return true; err: return false; } static bool vgltf_renderer_create_command_pool(struct vgltf_renderer *renderer) { struct queue_family_indices queue_family_indices = {}; if (!queue_family_indices_for_device(&queue_family_indices, renderer->device.physical_device, renderer->surface.surface)) { VGLTF_LOG_ERR("Couldn't fetch queue family indices"); goto err; } 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(renderer->device.device, &pool_info, nullptr, &renderer->command_pool) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create command pool"); goto err; } return true; err: return false; } static VkCommandBuffer begin_single_time_commands(struct vgltf_renderer *renderer) { VkCommandBufferAllocateInfo allocate_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandPool = renderer->command_pool, .commandBufferCount = 1}; VkCommandBuffer command_buffer; vkAllocateCommandBuffers(renderer->device.device, &allocate_info, &command_buffer); VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT}; vkBeginCommandBuffer(command_buffer, &begin_info); return command_buffer; } static void end_single_time_commands(struct vgltf_renderer *renderer, VkCommandBuffer command_buffer) { vkEndCommandBuffer(command_buffer); VkSubmitInfo submit_info = {.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &command_buffer}; vkQueueSubmit(renderer->device.graphics_queue, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(renderer->device.graphics_queue); vkFreeCommandBuffers(renderer->device.device, renderer->command_pool, 1, &command_buffer); } static bool vgltf_renderer_copy_buffer(struct vgltf_renderer *renderer, VkBuffer src_buffer, VkBuffer dst_buffer, VkDeviceSize size) { VkCommandBuffer command_buffer = begin_single_time_commands(renderer); VkBufferCopy copy_region = {.size = size}; vkCmdCopyBuffer(command_buffer, src_buffer, dst_buffer, 1, ©_region); end_single_time_commands(renderer, command_buffer); return true; } static void vgltf_renderer_create_image( struct vgltf_renderer *renderer, uint32_t width, uint32_t height, uint32_t mip_level_count, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, struct vgltf_renderer_allocated_image *image) { vmaCreateImage( renderer->device.allocator, &(const VkImageCreateInfo){ .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .extent = {width, height, 1}, .mipLevels = mip_level_count, .arrayLayers = 1, .format = format, .tiling = tiling, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .samples = VK_SAMPLE_COUNT_1_BIT, }, &(const VmaAllocationCreateInfo){.usage = VMA_MEMORY_USAGE_GPU_ONLY, .requiredFlags = properties}, &image->image, &image->allocation, &image->info); } static bool has_stencil_component(VkFormat format) { return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; } static bool transition_image_layout(struct vgltf_renderer *renderer, VkImage image, VkFormat format, VkImageLayout old_layout, VkImageLayout new_layout, uint32_t mip_level_count) { (void)format; VkCommandBuffer command_buffer = begin_single_time_commands(renderer); 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 = mip_level_count, .baseArrayLayer = 0, .layerCount = 1}, .srcAccessMask = 0, .dstAccessMask = 0}; if (new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; if (has_stencil_component(format)) { barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_DEPTH_BIT; } } else { barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } 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 if (old_layout == VK_IMAGE_LAYOUT_UNDEFINED && new_layout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; source_stage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; destination_stage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; } else { goto err; } vkCmdPipelineBarrier(command_buffer, source_stage, destination_stage, 0, 0, nullptr, 0, nullptr, 1, &barrier); end_single_time_commands(renderer, command_buffer); return true; err: return false; } void copy_buffer_to_image(struct vgltf_renderer *renderer, VkBuffer buffer, VkImage image, uint32_t width, uint32_t height) { VkCommandBuffer command_buffer = begin_single_time_commands(renderer); VkBufferImageCopy region = { .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = 0, .baseArrayLayer = 0, .layerCount = 1}, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}}; vkCmdCopyBufferToImage(command_buffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); end_single_time_commands(renderer, command_buffer); } static bool vgltf_renderer_create_depth_resources(struct vgltf_renderer *renderer) { VkFormat depth_format = find_depth_format(renderer); vgltf_renderer_create_image( renderer, renderer->swapchain.swapchain_extent.width, renderer->swapchain.swapchain_extent.height, 1, depth_format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &renderer->depth_image); create_image_view(&renderer->device, renderer->depth_image.image, depth_format, &renderer->depth_image_view, VK_IMAGE_ASPECT_DEPTH_BIT, 1); transition_image_layout(renderer, renderer->depth_image.image, depth_format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1); return true; } static bool vgltf_renderer_create_buffer(struct vgltf_renderer *renderer, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, struct vgltf_renderer_allocated_buffer *buffer) { VkBufferCreateInfo buffer_info = {.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE}; VmaAllocationCreateInfo alloc_info = { .usage = VMA_MEMORY_USAGE_AUTO, .flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT | VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT, .preferredFlags = properties}; if (vmaCreateBuffer(renderer->device.allocator, &buffer_info, &alloc_info, &buffer->buffer, &buffer->allocation, &buffer->info) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to create buffer"); goto err; } return true; err: return false; } static void generate_mipmaps(struct vgltf_renderer *renderer, VkImage image, VkFormat image_format, int32_t texture_width, int32_t texture_height, uint32_t mip_levels) { VkFormatProperties format_properties; vkGetPhysicalDeviceFormatProperties(renderer->device.physical_device, image_format, &format_properties); if (!(format_properties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { VGLTF_PANIC("Texture image format does not support linear blitting!"); } VkCommandBuffer command_buffer = begin_single_time_commands(renderer); VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .image = image, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .subresourceRange = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseArrayLayer = 0, .layerCount = 1, .levelCount = 1}}; int32_t mip_width = texture_width; int32_t mip_height = texture_height; for (uint32_t i = 1; i < mip_levels; i++) { barrier.subresourceRange.baseMipLevel = i - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); VkImageBlit blit = { .srcOffsets = {{0, 0, 0}, {mip_width, mip_height, 1}}, .srcSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = i - 1, .baseArrayLayer = 0, .layerCount = 1}, .dstOffsets = {{0, 0, 0}, {mip_width > 1 ? mip_width / 2 : 1, mip_height > 1 ? mip_height / 2 : 1, 1}}, .dstSubresource = {.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .mipLevel = i, .baseArrayLayer = 0, .layerCount = 1}, }; vkCmdBlitImage(command_buffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); if (mip_width > 1) mip_width /= 2; if (mip_height > 1) mip_height /= 2; } barrier.subresourceRange.baseMipLevel = mip_levels - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(command_buffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, nullptr, 0, nullptr, 1, &barrier); end_single_time_commands(renderer, command_buffer); } static bool vgltf_renderer_create_texture_image(struct vgltf_renderer *renderer) { struct vgltf_image image; if (!vgltf_image_load_from_file(&image, SV(TEXTURE_PATH))) { VGLTF_LOG_ERR("Couldn't load image from file"); goto err; } renderer->mip_level_count = floor(log2(VGLTF_MAX(image.width, image.height))) + 1; VkDeviceSize image_size = image.width * image.height * 4; struct vgltf_renderer_allocated_buffer staging_buffer = {}; if (!vgltf_renderer_create_buffer(renderer, image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buffer)) { VGLTF_LOG_ERR("Couldn't create staging buffer"); goto deinit_image; } void *data; vmaMapMemory(renderer->device.allocator, staging_buffer.allocation, &data); memcpy(data, image.data, image_size); vmaUnmapMemory(renderer->device.allocator, staging_buffer.allocation); vgltf_renderer_create_image( renderer, image.width, image.height, renderer->mip_level_count, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &renderer->texture_image); transition_image_layout(renderer, renderer->texture_image.image, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, renderer->mip_level_count); copy_buffer_to_image(renderer, staging_buffer.buffer, renderer->texture_image.image, image.width, image.height); generate_mipmaps(renderer, renderer->texture_image.image, VK_FORMAT_R8G8B8A8_SRGB, image.width, image.height, renderer->mip_level_count); vmaDestroyBuffer(renderer->device.allocator, staging_buffer.buffer, staging_buffer.allocation); vgltf_image_deinit(&image); return true; deinit_image: vgltf_image_deinit(&image); err: return false; } static bool vgltf_renderer_create_texture_image_view(struct vgltf_renderer *renderer) { return create_image_view( &renderer->device, renderer->texture_image.image, VK_FORMAT_R8G8B8A8_SRGB, &renderer->texture_image_view, VK_IMAGE_ASPECT_COLOR_BIT, renderer->mip_level_count); } static bool vgltf_renderer_create_texture_sampler(struct vgltf_renderer *renderer) { VkPhysicalDeviceProperties properties = {}; vkGetPhysicalDeviceProperties(renderer->device.physical_device, &properties); VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .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 = renderer->mip_level_count}; if (vkCreateSampler(renderer->device.device, &sampler_info, nullptr, &renderer->texture_sampler) != VK_SUCCESS) { goto err; } return true; err: return false; } static void get_file_data(void *ctx, const char *filename, const int is_mtl, const char *obj_filename, char **data, size_t *len) { (void)ctx; (void)is_mtl; if (!filename) { VGLTF_LOG_ERR("Null filename"); *data = NULL; *len = 0; return; } *data = vgltf_platform_read_file_to_string(obj_filename, len); } static bool load_model(struct vgltf_renderer *renderer) { tinyobj_attrib_t attrib; tinyobj_shape_t *shapes = nullptr; size_t shape_count; tinyobj_material_t *materials = nullptr; size_t material_count; if ((tinyobj_parse_obj(&attrib, &shapes, &shape_count, &materials, &material_count, MODEL_PATH, get_file_data, nullptr, TINYOBJ_FLAG_TRIANGULATE)) != TINYOBJ_SUCCESS) { VGLTF_LOG_ERR("Couldn't load obj"); return false; } for (size_t shape_index = 0; shape_index < shape_count; shape_index++) { tinyobj_shape_t *shape = &shapes[shape_index]; unsigned int face_offset = shape->face_offset; for (size_t face_index = face_offset; face_index < face_offset + shape->length; face_index++) { float v[3][3]; float t[3][2]; tinyobj_vertex_index_t idx0 = attrib.faces[face_index * 3 + 0]; tinyobj_vertex_index_t idx1 = attrib.faces[face_index * 3 + 1]; tinyobj_vertex_index_t idx2 = attrib.faces[face_index * 3 + 2]; for (int k = 0; k < 3; k++) { int f0 = idx0.v_idx; int f1 = idx1.v_idx; int f2 = idx2.v_idx; v[0][k] = attrib.vertices[3 * (size_t)f0 + k]; v[1][k] = attrib.vertices[3 * (size_t)f1 + k]; v[2][k] = attrib.vertices[3 * (size_t)f2 + k]; } for (int k = 0; k < 2; k++) { int t0 = idx0.vt_idx; int t1 = idx1.vt_idx; int t2 = idx2.vt_idx; t[0][k] = attrib.texcoords[2 * (size_t)t0 + k]; t[1][k] = attrib.texcoords[2 * (size_t)t1 + k]; t[2][k] = attrib.texcoords[2 * (size_t)t2 + k]; } for (int k = 0; k < 3; k++) { renderer->vertices[renderer->vertex_count++] = (struct vgltf_vertex){ .position = {v[k][0], v[k][1], v[k][2]}, .texture_coordinates = {t[k][0], 1.f - t[k][1]}, .color = {1.f, 1.f, 1.f}}; renderer->indices[renderer->index_count++] = renderer->index_count; } } tinyobj_attrib_free(&attrib); tinyobj_shapes_free(shapes, shape_count); tinyobj_materials_free(materials, material_count); } return true; } static bool vgltf_renderer_create_vertex_buffer(struct vgltf_renderer *renderer) { VkDeviceSize buffer_size = renderer->vertex_count * sizeof(struct vgltf_vertex); struct vgltf_renderer_allocated_buffer staging_buffer = {}; if (!vgltf_renderer_create_buffer(renderer, buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buffer)) { VGLTF_LOG_ERR("Failed to create transfer buffer"); goto err; } void *data; vmaMapMemory(renderer->device.allocator, staging_buffer.allocation, &data); memcpy(data, renderer->vertices, renderer->vertex_count * sizeof(struct vgltf_vertex)); vmaUnmapMemory(renderer->device.allocator, staging_buffer.allocation); if (!vgltf_renderer_create_buffer( renderer, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &renderer->vertex_buffer)) { VGLTF_LOG_ERR("Failed to create vertex buffer"); goto destroy_staging_buffer; } vgltf_renderer_copy_buffer(renderer, staging_buffer.buffer, renderer->vertex_buffer.buffer, buffer_size); vmaDestroyBuffer(renderer->device.allocator, staging_buffer.buffer, staging_buffer.allocation); return true; destroy_staging_buffer: vmaDestroyBuffer(renderer->device.allocator, staging_buffer.buffer, staging_buffer.allocation); err: return false; } static bool vgltf_renderer_create_index_buffer(struct vgltf_renderer *renderer) { VkDeviceSize buffer_size = renderer->index_count * sizeof(uint16_t); struct vgltf_renderer_allocated_buffer staging_buffer = {}; if (!vgltf_renderer_create_buffer(renderer, buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buffer)) { VGLTF_LOG_ERR("Failed to create transfer buffer"); goto err; } void *data; vmaMapMemory(renderer->device.allocator, staging_buffer.allocation, &data); memcpy(data, renderer->indices, renderer->index_count * sizeof(uint16_t)); vmaUnmapMemory(renderer->device.allocator, staging_buffer.allocation); if (!vgltf_renderer_create_buffer( renderer, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &renderer->index_buffer)) { VGLTF_LOG_ERR("Failed to create index buffer"); goto destroy_staging_buffer; } vgltf_renderer_copy_buffer(renderer, staging_buffer.buffer, renderer->index_buffer.buffer, buffer_size); vmaDestroyBuffer(renderer->device.allocator, staging_buffer.buffer, staging_buffer.allocation); return true; destroy_staging_buffer: vmaDestroyBuffer(renderer->device.allocator, staging_buffer.buffer, staging_buffer.allocation); err: return false; } static bool vgltf_renderer_create_command_buffer(struct vgltf_renderer *renderer) { VkCommandBufferAllocateInfo allocate_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = renderer->command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT}; if (vkAllocateCommandBuffers(renderer->device.device, &allocate_info, renderer->command_buffer) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't allocate command buffers"); goto err; } return true; err: return false; } static bool vgltf_renderer_create_sync_objects(struct vgltf_renderer *renderer) { VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; VkFenceCreateInfo fence_info = {.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT}; int frame_in_flight_index = 0; for (; frame_in_flight_index < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; frame_in_flight_index++) { if (vkCreateSemaphore( renderer->device.device, &semaphore_info, nullptr, &renderer->image_available_semaphores[frame_in_flight_index]) != VK_SUCCESS || vkCreateSemaphore( renderer->device.device, &semaphore_info, nullptr, &renderer->render_finished_semaphores[frame_in_flight_index]) != VK_SUCCESS || vkCreateFence(renderer->device.device, &fence_info, nullptr, &renderer->in_flight_fences[frame_in_flight_index]) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create sync objects"); goto err; } } return true; err: for (int frame_in_flight_to_delete_index = 0; frame_in_flight_to_delete_index < frame_in_flight_index; frame_in_flight_to_delete_index++) { vkDestroyFence(renderer->device.device, renderer->in_flight_fences[frame_in_flight_index], nullptr); vkDestroySemaphore( renderer->device.device, renderer->render_finished_semaphores[frame_in_flight_index], nullptr); vkDestroySemaphore( renderer->device.device, renderer->image_available_semaphores[frame_in_flight_index], nullptr); } return false; } static bool vgltf_vk_swapchain_init(struct vgltf_vk_swapchain *swapchain, struct vgltf_vk_device *device, struct vgltf_vk_surface *surface, struct vgltf_window_size *window_size) { if (!create_swapchain(swapchain, device, surface, window_size)) { VGLTF_LOG_ERR("Couldn't create swapchain"); goto err; } if (!create_swapchain_image_views(swapchain, device)) { VGLTF_LOG_ERR("Couldn't create image views"); goto destroy_swapchain; } return true; destroy_swapchain: vkDestroySwapchainKHR(device->device, swapchain->swapchain, nullptr); err: return false; } static void vgltf_vk_swapchain_deinit(struct vgltf_vk_swapchain *swapchain, struct vgltf_vk_device *device) { for (uint32_t swapchain_image_view_index = 0; swapchain_image_view_index < swapchain->swapchain_image_count; swapchain_image_view_index++) { vkDestroyImageView( device->device, swapchain->swapchain_image_views[swapchain_image_view_index], nullptr); } vkDestroySwapchainKHR(device->device, swapchain->swapchain, nullptr); } static void vgltf_renderer_cleanup_swapchain(struct vgltf_renderer *renderer) { vkDestroyImageView(renderer->device.device, renderer->depth_image_view, nullptr); vmaDestroyImage(renderer->device.allocator, renderer->depth_image.image, renderer->depth_image.allocation); for (uint32_t framebuffer_index = 0; framebuffer_index < renderer->swapchain.swapchain_image_count; framebuffer_index++) { vkDestroyFramebuffer(renderer->device.device, renderer->swapchain_framebuffers[framebuffer_index], nullptr); } vgltf_vk_swapchain_deinit(&renderer->swapchain, &renderer->device); } static bool vgltf_renderer_recreate_swapchain(struct vgltf_renderer *renderer) { vkDeviceWaitIdle(renderer->device.device); vgltf_renderer_cleanup_swapchain(renderer); // TODO add error handling create_swapchain(&renderer->swapchain, &renderer->device, &renderer->surface, &renderer->window_size); create_swapchain_image_views(&renderer->swapchain, &renderer->device); vgltf_renderer_create_depth_resources(renderer); vgltf_renderer_create_framebuffers(renderer); return true; } static void vgltf_renderer_triangle_pass(struct vgltf_renderer *renderer, uint32_t swapchain_image_index) { VkRenderPassBeginInfo render_pass_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = renderer->render_pass, .framebuffer = renderer->swapchain_framebuffers[swapchain_image_index], .renderArea = {.offset = {}, .extent = renderer->swapchain.swapchain_extent}, .clearValueCount = 2, .pClearValues = (const VkClearValue[]){{.color = {.float32 = {0.f, 0.f, 0.f, 1.f}}}, {.depthStencil = {1.0f, 0}}}, }; vkCmdBeginRenderPass(renderer->command_buffer[renderer->current_frame], &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(renderer->command_buffer[renderer->current_frame], VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->graphics_pipeline); VkViewport viewport = { .x = 0.f, .y = 0.f, .width = (float)renderer->swapchain.swapchain_extent.width, .height = (float)renderer->swapchain.swapchain_extent.height, .minDepth = 0.f, .maxDepth = 1.f}; vkCmdSetViewport(renderer->command_buffer[renderer->current_frame], 0, 1, &viewport); VkRect2D scissor = {.offset = {}, .extent = renderer->swapchain.swapchain_extent}; vkCmdSetScissor(renderer->command_buffer[renderer->current_frame], 0, 1, &scissor); VkBuffer vertex_buffers[] = {renderer->vertex_buffer.buffer}; VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(renderer->command_buffer[renderer->current_frame], 0, 1, vertex_buffers, offsets); vkCmdBindIndexBuffer(renderer->command_buffer[renderer->current_frame], renderer->index_buffer.buffer, 0, VK_INDEX_TYPE_UINT16); vkCmdBindDescriptorSets( renderer->command_buffer[renderer->current_frame], VK_PIPELINE_BIND_POINT_GRAPHICS, renderer->pipeline_layout, 0, 1, &renderer->descriptor_sets[renderer->current_frame], 0, nullptr); vkCmdDrawIndexed(renderer->command_buffer[renderer->current_frame], renderer->index_count, 1, 0, 0, 0); vkCmdEndRenderPass(renderer->command_buffer[renderer->current_frame]); } static void update_uniform_buffer(struct vgltf_renderer *renderer, uint32_t current_frame) { static long long start_time_nanoseconds = 0; if (start_time_nanoseconds == 0) { if (!vgltf_platform_get_current_time_nanoseconds(&start_time_nanoseconds)) { VGLTF_LOG_ERR("Couldn't get current time"); } } long long current_time_nanoseconds = 0; if (!vgltf_platform_get_current_time_nanoseconds(¤t_time_nanoseconds)) { VGLTF_LOG_ERR("Couldn't get current time"); } long elapsed_time_nanoseconds = current_time_nanoseconds - start_time_nanoseconds; float elapsed_time_seconds = elapsed_time_nanoseconds / 1e9f; VGLTF_LOG_INFO("Elapsed time: %f", elapsed_time_seconds); vgltf_mat4 model_matrix; vgltf_mat4_rotate(model_matrix, (vgltf_mat4)VGLTF_MAT4_IDENTITY, elapsed_time_seconds * VGLTF_MATHS_DEG_TO_RAD(90.0f), (vgltf_vec3){0.f, 0.f, 1.f}); vgltf_mat4 view_matrix; vgltf_mat4_look_at(view_matrix, (vgltf_vec3){2.f, 2.f, 2.f}, (vgltf_vec3){0.f, 0.f, 0.f}, (vgltf_vec3){0.f, 0.f, 1.f}); vgltf_mat4 projection_matrix; vgltf_mat4_perspective(projection_matrix, VGLTF_MATHS_DEG_TO_RAD(45.f), (float)renderer->swapchain.swapchain_extent.width / (float)renderer->swapchain.swapchain_extent.height, 0.1f, 10.f); projection_matrix[1 * 4 + 1] *= -1; struct vgltf_renderer_uniform_buffer_object ubo = {}; memcpy(ubo.model, model_matrix, sizeof(vgltf_mat4)); memcpy(ubo.view, view_matrix, sizeof(vgltf_mat4)); memcpy(ubo.projection, projection_matrix, sizeof(vgltf_mat4)); memcpy(renderer->mapped_uniform_buffers[current_frame], &ubo, sizeof(ubo)); } bool vgltf_renderer_render_frame(struct vgltf_renderer *renderer) { vkWaitForFences(renderer->device.device, 1, &renderer->in_flight_fences[renderer->current_frame], VK_TRUE, UINT64_MAX); uint32_t image_index; VkResult acquire_swapchain_image_result = vkAcquireNextImageKHR( renderer->device.device, renderer->swapchain.swapchain, UINT64_MAX, renderer->image_available_semaphores[renderer->current_frame], VK_NULL_HANDLE, &image_index); if (acquire_swapchain_image_result == VK_ERROR_OUT_OF_DATE_KHR || acquire_swapchain_image_result == VK_SUBOPTIMAL_KHR || renderer->framebuffer_resized) { renderer->framebuffer_resized = false; vgltf_renderer_recreate_swapchain(renderer); return true; } else if (acquire_swapchain_image_result != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to acquire a swapchain image"); goto err; } vkResetFences(renderer->device.device, 1, &renderer->in_flight_fences[renderer->current_frame]); vkResetCommandBuffer(renderer->command_buffer[renderer->current_frame], 0); VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; if (vkBeginCommandBuffer(renderer->command_buffer[renderer->current_frame], &begin_info) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to begin recording command buffer"); goto err; } vgltf_renderer_triangle_pass(renderer, image_index); if (vkEndCommandBuffer(renderer->command_buffer[renderer->current_frame]) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to record command buffer"); goto err; } update_uniform_buffer(renderer, renderer->current_frame); VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, }; VkSemaphore wait_semaphores[] = { renderer->image_available_semaphores[renderer->current_frame]}; VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = wait_semaphores; submit_info.pWaitDstStageMask = wait_stages; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &renderer->command_buffer[renderer->current_frame]; VkSemaphore signal_semaphores[] = { renderer->render_finished_semaphores[renderer->current_frame]}; submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = signal_semaphores; if (vkQueueSubmit(renderer->device.graphics_queue, 1, &submit_info, renderer->in_flight_fences[renderer->current_frame]) != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to submit draw command buffer"); goto err; } VkPresentInfoKHR present_info = {.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = signal_semaphores}; VkSwapchainKHR swapchains[] = {renderer->swapchain.swapchain}; present_info.swapchainCount = 1; present_info.pSwapchains = swapchains; present_info.pImageIndices = &image_index; VkResult result = vkQueuePresentKHR(renderer->device.present_queue, &present_info); if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { vgltf_renderer_recreate_swapchain(renderer); } else if (acquire_swapchain_image_result != VK_SUCCESS) { VGLTF_LOG_ERR("Failed to acquire a swapchain image"); goto err; } renderer->current_frame = (renderer->current_frame + 1) % VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; return true; err: return false; } static bool vgltf_renderer_create_uniform_buffers(struct vgltf_renderer *renderer) { VkDeviceSize buffer_size = sizeof(struct vgltf_renderer_uniform_buffer_object); for (int i = 0; i < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; i++) { vgltf_renderer_create_buffer(renderer, buffer_size, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &renderer->uniform_buffers[i]); vmaMapMemory(renderer->device.allocator, renderer->uniform_buffers[i].allocation, &renderer->mapped_uniform_buffers[i]); } return true; } static bool vgltf_renderer_create_descriptor_pool(struct vgltf_renderer *renderer) { VkDescriptorPoolSize pool_sizes[] = { (VkDescriptorPoolSize){.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT}, (VkDescriptorPoolSize){.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT}}; int pool_size_count = sizeof(pool_sizes) / sizeof(pool_sizes[0]); VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = pool_size_count, .pPoolSizes = pool_sizes, .maxSets = VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT}; if (vkCreateDescriptorPool(renderer->device.device, &pool_info, nullptr, &renderer->descriptor_pool) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create uniform descriptor pool"); goto err; } return true; err: return false; } static bool vgltf_renderer_create_descriptor_sets(struct vgltf_renderer *renderer) { VkDescriptorSetLayout layouts[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT] = {}; for (int layout_index = 0; layout_index < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; layout_index++) { layouts[layout_index] = renderer->descriptor_set_layout; } VkDescriptorSetAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = renderer->descriptor_pool, .descriptorSetCount = VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT, .pSetLayouts = layouts}; if (vkAllocateDescriptorSets(renderer->device.device, &alloc_info, renderer->descriptor_sets) != VK_SUCCESS) { VGLTF_LOG_ERR("Couldn't create descriptor sets"); goto err; } for (int set_index = 0; set_index < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; set_index++) { VkDescriptorBufferInfo buffer_info = { .buffer = renderer->uniform_buffers[set_index].buffer, .offset = 0, .range = sizeof(struct vgltf_renderer_uniform_buffer_object)}; VkDescriptorImageInfo image_info = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = renderer->texture_image_view, .sampler = renderer->texture_sampler, }; VkWriteDescriptorSet descriptor_writes[] = { (VkWriteDescriptorSet){.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = renderer->descriptor_sets[set_index], .dstBinding = 0, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pBufferInfo = &buffer_info}, (VkWriteDescriptorSet){.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = renderer->descriptor_sets[set_index], .dstBinding = 1, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pImageInfo = &image_info}}; int descriptor_write_count = sizeof(descriptor_writes) / sizeof(descriptor_writes[0]); vkUpdateDescriptorSets(renderer->device.device, descriptor_write_count, descriptor_writes, 0, nullptr); } return true; err: return false; } static bool vgltf_vk_device_init(struct vgltf_vk_device *device, struct vgltf_vk_instance *instance, struct vgltf_vk_surface *surface) { if (!pick_physical_device(&device->physical_device, instance, surface->surface)) { VGLTF_LOG_ERR("Couldn't pick physical device"); goto err; } if (!create_logical_device(&device->device, &device->graphics_queue, &device->present_queue, device->physical_device, surface->surface)) { VGLTF_LOG_ERR("Couldn't pick logical device"); goto err; } if (!create_allocator(&device->allocator, device, instance)) { VGLTF_LOG_ERR("Couldn't create allocator"); goto destroy_logical_device; } return true; destroy_logical_device: vkDestroyDevice(device->device, nullptr); err: return false; } static void vgltf_vk_device_deinit(struct vgltf_vk_device *device) { vmaDestroyAllocator(device->allocator); vkDestroyDevice(device->device, nullptr); } bool vgltf_renderer_init(struct vgltf_renderer *renderer, struct vgltf_platform *platform) { if (!vgltf_vk_instance_init(&renderer->instance, platform)) { VGLTF_LOG_ERR("instance creation failed"); goto err; } vgltf_renderer_setup_debug_messenger(renderer); if (!vgltf_vk_surface_init(&renderer->surface, &renderer->instance, platform)) { goto destroy_instance; } if (!vgltf_vk_device_init(&renderer->device, &renderer->instance, &renderer->surface)) { VGLTF_LOG_ERR("Device creation failed"); goto destroy_surface; } struct vgltf_window_size window_size = {800, 600}; if (!vgltf_platform_get_window_size(platform, &window_size)) { VGLTF_LOG_ERR("Couldn't get window size"); goto destroy_device; } renderer->window_size = window_size; if (!vgltf_vk_swapchain_init(&renderer->swapchain, &renderer->device, &renderer->surface, &renderer->window_size)) { VGLTF_LOG_ERR("Couldn't create swapchain"); goto destroy_device; } if (!vgltf_renderer_create_render_pass(renderer)) { VGLTF_LOG_ERR("Couldn't create render pass"); goto destroy_swapchain; } if (!vgltf_renderer_create_descriptor_set_layout(renderer)) { VGLTF_LOG_ERR("Couldn't create descriptor set layout"); goto destroy_render_pass; } if (!vgltf_renderer_create_graphics_pipeline(renderer)) { VGLTF_LOG_ERR("Couldn't create graphics pipeline"); goto destroy_descriptor_set_layout; } if (!vgltf_renderer_create_command_pool(renderer)) { VGLTF_LOG_ERR("Couldn't create command pool"); goto destroy_graphics_pipeline; } if (!vgltf_renderer_create_depth_resources(renderer)) { VGLTF_LOG_ERR("Couldn't create depth resources"); goto destroy_command_pool; } if (!vgltf_renderer_create_framebuffers(renderer)) { VGLTF_LOG_ERR("Couldn't create framebuffers"); goto destroy_depth_resources; } if (!vgltf_renderer_create_texture_image(renderer)) { VGLTF_LOG_ERR("Couldn't create texture image"); goto destroy_frame_buffers; } if (!vgltf_renderer_create_texture_image_view(renderer)) { VGLTF_LOG_ERR("Couldn't create texture image view"); goto destroy_texture_image; } if (!vgltf_renderer_create_texture_sampler(renderer)) { VGLTF_LOG_ERR("Couldn't create texture sampler"); goto destroy_texture_image_view; } if (!load_model(renderer)) { VGLTF_LOG_ERR("Couldn't load model"); goto destroy_texture_sampler; } if (!vgltf_renderer_create_vertex_buffer(renderer)) { VGLTF_LOG_ERR("Couldn't create vertex buffer"); goto destroy_model; } if (!vgltf_renderer_create_index_buffer(renderer)) { VGLTF_LOG_ERR("Couldn't create index buffer"); goto destroy_vertex_buffer; } if (!vgltf_renderer_create_uniform_buffers(renderer)) { VGLTF_LOG_ERR("Couldn't create uniform buffers"); goto destroy_index_buffer; } if (!vgltf_renderer_create_descriptor_pool(renderer)) { VGLTF_LOG_ERR("Couldn't create descriptor pool"); goto destroy_uniform_buffers; } if (!vgltf_renderer_create_descriptor_sets(renderer)) { VGLTF_LOG_ERR("Couldn't create descriptor sets"); goto destroy_descriptor_pool; } if (!vgltf_renderer_create_command_buffer(renderer)) { VGLTF_LOG_ERR("Couldn't create command buffer"); goto destroy_descriptor_pool; } if (!vgltf_renderer_create_sync_objects(renderer)) { VGLTF_LOG_ERR("Couldn't create sync objects"); goto destroy_descriptor_pool; } return true; destroy_descriptor_pool: vkDestroyDescriptorPool(renderer->device.device, renderer->descriptor_pool, nullptr); destroy_uniform_buffers: for (int i = 0; i < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; i++) { vmaDestroyBuffer(renderer->device.allocator, renderer->uniform_buffers[i].buffer, renderer->uniform_buffers[i].allocation); } destroy_index_buffer: vmaDestroyBuffer(renderer->device.allocator, renderer->index_buffer.buffer, renderer->index_buffer.allocation); destroy_vertex_buffer: vmaDestroyBuffer(renderer->device.allocator, renderer->vertex_buffer.buffer, renderer->vertex_buffer.allocation); destroy_model: // TODO destroy_texture_sampler: vkDestroySampler(renderer->device.device, renderer->texture_sampler, nullptr); destroy_texture_image_view: vkDestroyImageView(renderer->device.device, renderer->texture_image_view, nullptr); destroy_texture_image: vmaDestroyImage(renderer->device.allocator, renderer->texture_image.image, renderer->texture_image.allocation); destroy_depth_resources: vkDestroyImageView(renderer->device.device, renderer->depth_image_view, nullptr); vmaDestroyImage(renderer->device.allocator, renderer->depth_image.image, renderer->depth_image.allocation); destroy_command_pool: vkDestroyCommandPool(renderer->device.device, renderer->command_pool, nullptr); destroy_frame_buffers: for (uint32_t swapchain_framebuffer_index = 0; swapchain_framebuffer_index < renderer->swapchain.swapchain_image_count; swapchain_framebuffer_index++) { vkDestroyFramebuffer( renderer->device.device, renderer->swapchain_framebuffers[swapchain_framebuffer_index], nullptr); } destroy_graphics_pipeline: vkDestroyPipeline(renderer->device.device, renderer->graphics_pipeline, nullptr); vkDestroyPipelineLayout(renderer->device.device, renderer->pipeline_layout, nullptr); destroy_descriptor_set_layout: vkDestroyDescriptorSetLayout(renderer->device.device, renderer->descriptor_set_layout, nullptr); destroy_render_pass: vkDestroyRenderPass(renderer->device.device, renderer->render_pass, nullptr); destroy_swapchain: vgltf_vk_swapchain_deinit(&renderer->swapchain, &renderer->device); destroy_device: vgltf_vk_device_deinit(&renderer->device); destroy_surface: vgltf_vk_surface_deinit(&renderer->surface, &renderer->instance); destroy_instance: if (enable_validation_layers) { destroy_debug_utils_messenger_ext(renderer->instance.instance, renderer->debug_messenger, nullptr); } vgltf_vk_instance_deinit(&renderer->instance); err: return false; } void vgltf_renderer_deinit(struct vgltf_renderer *renderer) { vkDeviceWaitIdle(renderer->device.device); vgltf_renderer_cleanup_swapchain(renderer); for (int i = 0; i < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; i++) { vmaUnmapMemory(renderer->device.allocator, renderer->uniform_buffers[i].allocation); vmaDestroyBuffer(renderer->device.allocator, renderer->uniform_buffers[i].buffer, renderer->uniform_buffers[i].allocation); } vmaDestroyBuffer(renderer->device.allocator, renderer->index_buffer.buffer, renderer->index_buffer.allocation); vmaDestroyBuffer(renderer->device.allocator, renderer->vertex_buffer.buffer, renderer->vertex_buffer.allocation); vkDestroySampler(renderer->device.device, renderer->texture_sampler, nullptr); vkDestroyImageView(renderer->device.device, renderer->texture_image_view, nullptr); vmaDestroyImage(renderer->device.allocator, renderer->texture_image.image, renderer->texture_image.allocation); vkDestroyPipeline(renderer->device.device, renderer->graphics_pipeline, nullptr); vkDestroyPipelineLayout(renderer->device.device, renderer->pipeline_layout, nullptr); vkDestroyDescriptorPool(renderer->device.device, renderer->descriptor_pool, nullptr); vkDestroyDescriptorSetLayout(renderer->device.device, renderer->descriptor_set_layout, nullptr); vkDestroyRenderPass(renderer->device.device, renderer->render_pass, nullptr); for (int i = 0; i < VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT; i++) { vkDestroySemaphore(renderer->device.device, renderer->image_available_semaphores[i], nullptr); vkDestroySemaphore(renderer->device.device, renderer->render_finished_semaphores[i], nullptr); vkDestroyFence(renderer->device.device, renderer->in_flight_fences[i], nullptr); } vkDestroyCommandPool(renderer->device.device, renderer->command_pool, nullptr); vgltf_vk_device_deinit(&renderer->device); vgltf_vk_surface_deinit(&renderer->surface, &renderer->instance); if (enable_validation_layers) { destroy_debug_utils_messenger_ext(renderer->instance.instance, renderer->debug_messenger, nullptr); } vgltf_vk_instance_deinit(&renderer->instance); } void vgltf_renderer_on_window_resized(struct vgltf_renderer *renderer, struct vgltf_window_size size) { if (size.width > 0 && size.height > 0 && size.width != renderer->window_size.width && size.height != renderer->window_size.height) { renderer->window_size = size; renderer->framebuffer_resized = true; } }