diff options
| author | Clement Sibille <clements@lisible.xyz> | 2025-05-05 08:32:33 +0200 |
|---|---|---|
| committer | Clement Sibille <clements@lisible.xyz> | 2025-05-05 12:24:27 +0200 |
| commit | b71eac2069a30349435c192d682e865718c86a15 (patch) | |
| tree | 33754245a23533e31e6a83390bf190c11dfe2bb9 /src/renderer | |
| parent | 6017db0069977ae85e698a1234f4a2b7632ee495 (diff) | |
Add a vulkan renderer that renders an OBJ
Diffstat (limited to 'src/renderer')
| -rw-r--r-- | src/renderer/renderer.c | 2559 | ||||
| -rw-r--r-- | src/renderer/renderer.h | 126 | ||||
| -rw-r--r-- | src/renderer/vma_usage.cpp | 4 | ||||
| -rw-r--r-- | src/renderer/vma_usage.h | 6 |
4 files changed, 2695 insertions, 0 deletions
diff --git a/src/renderer/renderer.c b/src/renderer/renderer.c new file mode 100644 index 0000000..d34ef73 --- /dev/null +++ b/src/renderer/renderer.c @@ -0,0 +1,2559 @@ +#include "renderer.h" +#include "../image.h" +#include "../log.h" +#include "../maths.h" +#include "../platform.h" +#include "vma_usage.h" +#include <math.h> + +#define TINYOBJ_LOADER_C_IMPLEMENTATION +#include "vendor/tiny_obj_loader_c.h" + +#include <assert.h> +#include <vulkan/vulkan_core.h> + +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; + } +} diff --git a/src/renderer/renderer.h b/src/renderer/renderer.h new file mode 100644 index 0000000..79e1f3d --- /dev/null +++ b/src/renderer/renderer.h @@ -0,0 +1,126 @@ +#ifndef VGLTF_RENDERER_H +#define VGLTF_RENDERER_H + +#include "../maths.h" +#include "../platform.h" +#include "vma_usage.h" +#include <vulkan/vulkan.h> + +struct vgltf_vertex { + vgltf_vec3 position; + vgltf_vec3 color; + vgltf_vec2 texture_coordinates; +}; +VkVertexInputBindingDescription vgltf_vertex_binding_description(void); + +struct vgltf_vertex_input_attribute_descriptions { + VkVertexInputAttributeDescription descriptions[3]; + uint32_t count; +}; +struct vgltf_vertex_input_attribute_descriptions +vgltf_vertex_attribute_descriptions(void); + +struct vgltf_renderer_uniform_buffer_object { + alignas(16) vgltf_mat4 model; + alignas(16) vgltf_mat4 view; + alignas(16) vgltf_mat4 projection; +}; + +struct vgltf_renderer_allocated_buffer { + VkBuffer buffer; + VmaAllocation allocation; + VmaAllocationInfo info; +}; + +struct vgltf_renderer_allocated_image { + VkImage image; + VmaAllocation allocation; + VmaAllocationInfo info; +}; + +struct vgltf_vk_instance { + VkInstance instance; +}; + +struct vgltf_vk_device { + VkPhysicalDevice physical_device; + VkDevice device; + VkQueue graphics_queue; + VkQueue present_queue; + VmaAllocator allocator; +}; + +struct vgltf_vk_surface { + VkSurfaceKHR surface; +}; + +constexpr int VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT = 32; +struct vgltf_vk_swapchain { + VkSwapchainKHR swapchain; + VkFormat swapchain_image_format; + VkImage swapchain_images[VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT]; + VkImageView swapchain_image_views[VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT]; + VkExtent2D swapchain_extent; + uint32_t swapchain_image_count; +}; + +struct vgltf_vk_pipeline { + VkPipelineLayout layout; + VkPipeline pipeline; +}; + +constexpr int VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT = 2; +struct vgltf_renderer { + struct vgltf_vk_instance instance; + struct vgltf_vk_device device; + VkDebugUtilsMessengerEXT debug_messenger; + struct vgltf_vk_surface surface; + struct vgltf_vk_swapchain swapchain; + struct vgltf_renderer_allocated_image depth_image; + VkImageView depth_image_view; + + VkRenderPass render_pass; + VkDescriptorSetLayout descriptor_set_layout; + + VkDescriptorPool descriptor_pool; + VkDescriptorSet descriptor_sets[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + VkPipelineLayout pipeline_layout; + VkPipeline graphics_pipeline; + + VkFramebuffer swapchain_framebuffers[VGLTF_RENDERER_MAX_SWAPCHAIN_IMAGE_COUNT]; + + VkCommandPool command_pool; + VkCommandBuffer command_buffer[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + VkSemaphore + image_available_semaphores[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + VkSemaphore + render_finished_semaphores[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + VkFence in_flight_fences[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + + struct vgltf_renderer_allocated_buffer + uniform_buffers[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + void *mapped_uniform_buffers[VGLTF_RENDERER_MAX_FRAME_IN_FLIGHT_COUNT]; + + uint32_t mip_level_count; + struct vgltf_renderer_allocated_image texture_image; + VkImageView texture_image_view; + VkSampler texture_sampler; + struct vgltf_vertex vertices[100000]; + int vertex_count; + uint16_t indices[100000]; + int index_count; + struct vgltf_renderer_allocated_buffer vertex_buffer; + struct vgltf_renderer_allocated_buffer index_buffer; + + struct vgltf_window_size window_size; + uint32_t current_frame; + bool framebuffer_resized; +}; +bool vgltf_renderer_init(struct vgltf_renderer *renderer, + struct vgltf_platform *platform); +void vgltf_renderer_deinit(struct vgltf_renderer *renderer); +bool vgltf_renderer_render_frame(struct vgltf_renderer *renderer); +void vgltf_renderer_on_window_resized(struct vgltf_renderer *renderer, + struct vgltf_window_size size); + +#endif // VGLTF_RENDERER_H diff --git a/src/renderer/vma_usage.cpp b/src/renderer/vma_usage.cpp new file mode 100644 index 0000000..83006a1 --- /dev/null +++ b/src/renderer/vma_usage.cpp @@ -0,0 +1,4 @@ +#include "vma_usage.h" + +#define VMA_IMPLEMENTATION +#include <vk_mem_alloc.h> diff --git a/src/renderer/vma_usage.h b/src/renderer/vma_usage.h new file mode 100644 index 0000000..e9b5aa4 --- /dev/null +++ b/src/renderer/vma_usage.h @@ -0,0 +1,6 @@ +#ifndef VGLTF_VMA_USAGE_H +#define VGLTF_VMA_USAGE_H + +#include <vk_mem_alloc.h> + +#endif // VGLTF_VMA_USAGE_H |
