#define VK_USE_PLATFORM_MACOS_MVK #include "vulkan/vulkan_core.h" #define GLFW_INCLUDE_VULKAN #include #define GLFW_EXPOSE_NATIVE_COCOA #include #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #include #include #include #include #include #include #include #include typedef struct QueueIndicesStruct { uint32_t graphics_family; uint32_t graphics_index; uint32_t present_family; uint32_t present_index; uint32_t transfer_family; uint32_t transfer_index; } QueueIndices; typedef struct QueuesStruct { VkQueue graphics; VkQueue present; VkQueue transfer; } Queues; typedef struct SwapchainDetailsStruct { VkSurfaceCapabilitiesKHR capabilities; VkSurfaceFormatKHR* formats; uint32_t formats_count; VkPresentModeKHR* present_modes; uint32_t present_modes_count; } SwapchainDetails; typedef struct SwapchainImagesStruct { VkImage* images; uint32_t count; } SwapchainImages; typedef struct AllocatedBufferStruct { VkBuffer buffer; VkDeviceMemory memory; } AllocatedBuffer; typedef struct AllocatedImageStruct { VkImage image; VkDeviceMemory memory; } AllocatedImage; typedef struct TextureStruct { AllocatedImage image; VkImageView view; VkSampler sampler; } Texture; // Defines how a mesh is read from a buffer into a graphics pipeline typedef struct MeshTypeStruct { uint32_t bindings_count; VkVertexInputBindingDescription* bindings; uint32_t attributes_count; VkVertexInputAttributeDescription* attributes; } MeshType; // Defines what descriptors are bound at two different upate rates for the pipeline typedef struct PipelineLayoutStruct { uint32_t mesh_bindings_count; VkDescriptorSetLayoutBinding* mesh_bindings; uint32_t material_bindings_count; VkDescriptorSetLayoutBinding* material_bindings; } PipelineLayout; typedef struct MeshStruct { uint32_t vertex_count; AllocatedBuffer vertex_buffer; uint32_t index_count; AllocatedBuffer index_buffer; } Mesh; typedef struct DescriptorPoolStruct { VkDescriptorPool handle; uint32_t allocated; } DescriptorPool; typedef struct GrowingDescriptorPoolStruct { uint32_t num_pool_sizes; VkDescriptorPoolSize* pool_sizes; uint32_t sets_per_pool; VkDescriptorPoolCreateInfo pool_info; VkDescriptorSetLayout set_layout; uint32_t num_pools; DescriptorPool* pools; } GrowingDescriptorPool; typedef struct MaterialStruct { VkDescriptorSetLayout material_set_layout; VkDescriptorSetLayout mesh_set_layout; VkPipelineLayout layout; VkPipeline pipeline; } Material; typedef struct VulkanContextStruct { VkInstance instance; VkDebugUtilsMessengerEXT debug_messenger; VkPhysicalDevice physical_device; QueueIndices queue_indices; VkDevice device; Queues queues; VkSurfaceKHR surface; SwapchainDetails swapchain_details; VkSwapchainKHR swapchain; VkSurfaceFormatKHR swapchain_format; VkPresentModeKHR swapchain_present_mode; VkExtent2D swapchain_extent; uint32_t swapchain_image_count; // Per image objects VkImage* swapchain_images; VkImageView* swapchain_image_views; VkFramebuffer* swapchain_framebuffers; AllocatedImage depth_image; VkFormat depth_format; VkImageView depth_image_view; uint32_t max_frames_in_flight; // Per frame objects VkCommandBuffer* swapchain_command_buffers; VkSemaphore* image_available_semaphores; VkSemaphore* render_finished_semaphores; VkFence* in_flight_fences; VkRenderPass render_pass; VkCommandPool graphics_command_pool; VkCommandPool transfer_command_pool; VkDescriptorPool scene_ubo_pool; VkDescriptorSetLayout scene_ubo_layout; VkDescriptorSet* scene_ubo_descriptors; AllocatedBuffer* scene_ubos; void** scene_ubo_ptrs; Mesh triangle_mesh; Mesh triangle_mesh_textured; Material simple_mesh_material; Material texture_mesh_material; uint32_t current_frame; } VulkanContext; struct TextureVertex { vec3 pos; vec3 color; vec2 tex; }; struct Vertex { vec3 pos; vec3 color; }; struct SceneUBO { mat4 view; mat4 proj; }; const struct Vertex vertices[] = { {.pos = {-0.5f, -0.5f, 0.5f}, .color = {1.0f, 0.0f, 0.0f}}, {.pos = { 0.5f, -0.5f, 0.5f}, .color = {0.0f, 1.0f, 0.0f}}, {.pos = { 0.5f, 0.5f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}}, {.pos = {-0.5f, 0.5f, 0.5f}, .color = {1.0f, 1.0f, 1.0f}}, }; const struct TextureVertex texture_vertices[] = { {.pos = {-0.5f, -0.5f, 0.5f}, .color = {1.0f, 0.0f, 0.0f}, .tex = {0.0f, 1.0f}}, {.pos = { 0.5f, -0.5f, 0.5f}, .color = {0.0f, 1.0f, 0.0f}, .tex = {1.0f, 0.0f}}, {.pos = { 0.5f, 0.5f, 0.5f}, .color = {0.0f, 0.0f, 1.0f}, .tex = {1.0f, 1.0f}}, {.pos = {-0.5f, 0.5f, 0.5f}, .color = {1.0f, 1.0f, 1.0f}, .tex = {0.5f, 0.5f}}, }; const uint16_t indices[] = { 2, 1, 0, 0, 3, 2, }; const char * validation_layers[] = { "VK_LAYER_KHRONOS_validation", //"VK_LAYER_LUNARG_api_dump", //"VK_LAYER_KHRONOS_profiles", //"VK_LAYER_KHRONOS_synchronization2", "VK_LAYER_KHRONOS_shader_object", }; uint32_t validation_layer_count = sizeof(validation_layers) / sizeof(const char *); const char * instance_extensions[] = { VK_KHR_PORTABILITY_ENUMERATION_EXTENSION_NAME, VK_EXT_DEBUG_UTILS_EXTENSION_NAME, VK_MVK_MACOS_SURFACE_EXTENSION_NAME, VK_KHR_SURFACE_EXTENSION_NAME, }; uint32_t instance_extension_count = sizeof(instance_extensions) / sizeof(const char *); const char * device_extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; uint32_t device_extension_count = sizeof(device_extensions) / sizeof(const char *); void glfw_error(int error, const char* description) { fprintf(stderr, "GLFW_ERR: 0x%02x - %s\n", error, description); } GLFWwindow* init_window(int width, int height) { glfwInit(); glfwSetErrorCallback(glfw_error); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_TRUE); GLFWwindow* window = glfwCreateWindow(width, height, "Vulkan window", 0, 0); return window; } VkFormat find_depth_format(VkPhysicalDevice physical_device, uint32_t num_requested, VkFormat* requested, VkImageTiling tiling, VkFormatFeatureFlags features) { for(uint32_t i = 0; i < num_requested; i++) { VkFormatProperties properties; vkGetPhysicalDeviceFormatProperties(physical_device, requested[i], &properties); if(tiling == VK_IMAGE_TILING_LINEAR && (properties.linearTilingFeatures & features) == features) { return requested[i]; } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (properties.optimalTilingFeatures & features) == features) { return requested[i]; } } return VK_FORMAT_MAX_ENUM; } bool check_validation_layers(const char ** layers, uint32_t num_layers) { uint32_t layer_count; VkResult result; result = vkEnumerateInstanceLayerProperties(&layer_count, 0); if(result != VK_SUCCESS) { return false; } VkLayerProperties* available_layers = malloc(sizeof(VkLayerProperties)*layer_count); result = vkEnumerateInstanceLayerProperties(&layer_count, available_layers); for(uint32_t i = 0; i < num_layers; i++) { bool found = false; for(uint32_t j = 0; j < layer_count; j++) { if(strcmp(layers[i], available_layers[j].layerName) == 0) { found = true; } } if(found == false) { free(available_layers); return false; } } free(available_layers); return true; } static VKAPI_ATTR VkBool32 VKAPI_CALL debug_callback( VkDebugUtilsMessageSeverityFlagBitsEXT severity, VkDebugUtilsMessageTypeFlagsEXT type, const VkDebugUtilsMessengerCallbackDataEXT* callback_data, void* user_data) { (void)severity; (void)type; (void)user_data; fprintf(stderr, "Validation layer: %s\n", callback_data->pMessage); return VK_FALSE; } GrowingDescriptorPool create_growing_descriptor_pool(VkDevice device, uint32_t num_bindings, VkDescriptorSetLayoutBinding* bindings, uint32_t sets_per_pool) { GrowingDescriptorPool ret = { .sets_per_pool = sets_per_pool, }; VkDescriptorSetLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = num_bindings, .pBindings = bindings, }; VkDescriptorSetLayout layout; VkResult result = vkCreateDescriptorSetLayout(device, &layout_info, 0, &layout); if(result != VK_SUCCESS) { return ret; } VkDescriptorPoolSize* pool_sizes = malloc(sizeof(VkDescriptorPoolSize)*num_bindings); if(pool_sizes == 0) { return ret; } for(uint32_t i = 0; i < num_bindings; i++) { VkDescriptorPoolSize size = { .type = bindings[i].descriptorType, .descriptorCount = bindings[i].descriptorCount * sets_per_pool, }; pool_sizes[i] = size; } VkDescriptorPoolCreateInfo pool_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = num_bindings, .pPoolSizes = pool_sizes, .maxSets = sets_per_pool, }; DescriptorPool* pools = malloc(sizeof(DescriptorPool)); if(pools == 0) { free(pool_sizes); return ret; } result = vkCreateDescriptorPool(device, &pool_info, 0, &pools[0].handle); if(result != VK_SUCCESS) { free(pool_sizes); free(pools); } ret.set_layout = layout; ret.pool_info = pool_info; ret.pools = pools; ret.num_pools = 1; ret.pool_info = pool_info; ret.pool_sizes = pool_sizes; return ret; } DescriptorPool* grow_descriptor_pool(VkDevice device, GrowingDescriptorPool* pool) { VkDescriptorPool handle; VkResult result = vkCreateDescriptorPool(device, &pool->pool_info, 0, &handle); if(result != VK_SUCCESS) { return 0; } uint32_t new_size = pool->num_pools + 1; DescriptorPool* new_pools = realloc(pool->pools, sizeof(DescriptorPool)*new_size); if(new_pools == 0) { return 0; } new_pools[new_size-1].allocated = 0; new_pools[new_size-1].handle = handle; pool->pools = new_pools; pool->num_pools = new_size; return &new_pools[new_size-1]; } VkDescriptorSet allocate_descriptor_set(VkDevice device, GrowingDescriptorPool* pool) { DescriptorPool* selected_pool = 0; uint32_t index = 0; for(uint32_t i = 0; i < pool->num_pools; i++) { if(pool->pools[i].allocated < pool->sets_per_pool) { selected_pool = &pool->pools[i]; index = i; break; } } if(selected_pool == 0) { selected_pool = grow_descriptor_pool(device, pool); index = pool->num_pools - 1; if(selected_pool == 0) { return VK_NULL_HANDLE; } } VkDescriptorSetAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO, .descriptorPool = selected_pool->handle, .pSetLayouts = &pool->set_layout, .descriptorSetCount = 1, }; VkDescriptorSet new_set; VkResult result = vkAllocateDescriptorSets(device, &alloc_info, &new_set); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } pool->pools[index].allocated += 1; return new_set; } VkDescriptorSet* create_descriptor_sets(VkDevice device, VkDescriptorSetLayout layout, VkDescriptorPool pool, uint32_t count) { VkDescriptorSetLayout* layouts = malloc(sizeof(VkDescriptorSetLayout)*count); if(layouts == 0) { return 0; } VkDescriptorSet* sets = malloc(sizeof(VkDescriptorSet)*count); if(sets == 0) { free(layouts); return 0; } for(uint32_t i = 0; i < count; i++) { layouts[i] = layout; } VkDescriptorSetAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; alloc_info.descriptorPool = pool; alloc_info.descriptorSetCount = count; alloc_info.pSetLayouts = layouts; VkResult result = vkAllocateDescriptorSets(device, &alloc_info, sets); free(layouts); if(result != VK_SUCCESS) { free(sets); return 0; } return sets; } VkDescriptorSetLayout create_descriptor_set_layout(VkDevice device, VkDescriptorSetLayoutBinding* bindings, uint32_t bindings_count) { VkDescriptorSetLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layout_info.bindingCount = bindings_count; layout_info.pBindings = bindings; VkDescriptorSetLayout layout; VkResult result = vkCreateDescriptorSetLayout(device, &layout_info, 0, &layout); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return layout; } VkSurfaceKHR create_surface_khr(VkInstance instance, GLFWwindow* window) { VkSurfaceKHR surface; VkResult result = glfwCreateWindowSurface(instance, window, 0, &surface); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return surface; } VkPhysicalDevice get_best_physical_device(VkInstance instance) { VkPhysicalDevice device = VK_NULL_HANDLE; uint32_t device_count = 0; VkResult result; result = vkEnumeratePhysicalDevices(instance, &device_count, 0); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } VkPhysicalDevice* devices = malloc(sizeof(VkPhysicalDevice)*device_count); result = vkEnumeratePhysicalDevices(instance, &device_count, devices); if(result != VK_SUCCESS) { free(devices); return VK_NULL_HANDLE; } int top_score = -1; for(uint32_t i = 0; i < device_count; i++) { int score = 0; VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(devices[i], &properties); VkPhysicalDeviceFeatures features; vkGetPhysicalDeviceFeatures(devices[i], &features); switch(properties.deviceType) { case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: score += 100; break; case VK_PHYSICAL_DEVICE_TYPE_VIRTUAL_GPU: score += 50; break; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: score += 25; break; case VK_PHYSICAL_DEVICE_TYPE_CPU: score += 0; break; default: continue; } if(score > top_score) { top_score = score; device = devices[i]; } } free(devices); return device; } bool check_queue_indices(QueueIndices indices) { return ((indices.graphics_family != 0xFFFFFFFF) && (indices.present_family != 0xFFFFFFFF) && (indices.transfer_family != 0xFFFFFFFF)); } QueueIndices get_queue_indices(VkPhysicalDevice physical_device, VkSurfaceKHR surface) { QueueIndices indices = {}; indices.graphics_family = 0xFFFFFFFF; indices.graphics_index = 0xFFFFFFFF; indices.present_family = 0xFFFFFFFF; indices.present_index = 0xFFFFFFFF; indices.transfer_family = 0xFFFFFFFF; indices.transfer_index = 0xFFFFFFFF; uint32_t queue_family_count; vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, 0); VkQueueFamilyProperties* queue_families = malloc(sizeof(VkQueueFamilyProperties)*queue_family_count); vkGetPhysicalDeviceQueueFamilyProperties(physical_device, &queue_family_count, queue_families); for(uint32_t family_idx = 0; family_idx < queue_family_count; family_idx++) { VkBool32 present_support; vkGetPhysicalDeviceSurfaceSupportKHR(physical_device, family_idx, surface, &present_support); for(uint32_t queue_idx = 0; queue_idx < queue_families[family_idx].queueCount; queue_idx++) { if(((indices.graphics_family == 0xFFFFFFFF) || (indices.present_family == 0xFFFFFFFF) || (indices.present_family != indices.graphics_family)) && (queue_families[family_idx].queueFlags & VK_QUEUE_GRAPHICS_BIT) && (present_support == VK_TRUE)) { fprintf(stderr, "Selected %d:%d for graphics and present queues\n", family_idx, queue_idx); indices.graphics_family = family_idx; indices.graphics_index = queue_idx; indices.present_family = family_idx; indices.present_index = queue_idx; } else if((indices.graphics_family == 0xFFFFFFFF) && (queue_families[family_idx].queueFlags & VK_QUEUE_GRAPHICS_BIT)) { fprintf(stderr, "Selected %d:%d for graphics queue\n", family_idx, queue_idx); indices.graphics_family = family_idx; indices.graphics_index = queue_idx; } else if((indices.present_family == 0xFFFFFFFF) && (present_support == VK_TRUE)) { fprintf(stderr, "Selected %d:%d for present queue\n", family_idx, queue_idx); indices.present_family = family_idx; indices.present_index = queue_idx; } else if((indices.transfer_family == 0xFFFFFFFF) && (queue_families[family_idx].queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT))) { fprintf(stderr, "Selected %d:%d for transfer queue\n", family_idx, queue_idx); indices.transfer_family = family_idx; indices.transfer_index = queue_idx; } } } free(queue_families); return indices; } VkDebugUtilsMessengerEXT create_debug_messenger(VkInstance instance) { VkDebugUtilsMessengerEXT debug_messenger; VkDebugUtilsMessengerCreateInfoEXT messenger_info = {}; messenger_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; messenger_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT; messenger_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 | VK_DEBUG_UTILS_MESSAGE_TYPE_DEVICE_ADDRESS_BINDING_BIT_EXT; messenger_info.pfnUserCallback = debug_callback; messenger_info.pUserData = 0; PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); VkResult result; result = func(instance, &messenger_info, 0, &debug_messenger); if(result != VK_SUCCESS) { fprintf(stderr, "failed to create debug messenger\n"); return VK_NULL_HANDLE; } return debug_messenger; } VkDescriptorPool create_descriptor_pool(VkDevice device, VkDescriptorPoolSize* sizes, uint32_t num_sizes, uint32_t max_sets) { VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.poolSizeCount = num_sizes; pool_info.pPoolSizes = sizes; pool_info.maxSets = max_sets; VkDescriptorPool pool; VkResult result = vkCreateDescriptorPool(device, &pool_info, 0, &pool); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return pool; } VkInstance create_instance() { VkInstance instance; if(check_validation_layers(validation_layers, validation_layer_count) == false) { fprintf(stderr, "requested validation layers not supported"); return VK_NULL_HANDLE; } VkApplicationInfo app_info = {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.pApplicationName = "spacegame"; app_info.applicationVersion = VK_MAKE_VERSION(0, 0, 1); app_info.pEngineName = "spacegame"; app_info.engineVersion = VK_MAKE_VERSION(0, 0, 1); app_info.apiVersion = VK_API_VERSION_1_3; VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.pApplicationInfo = &app_info; instance_info.enabledLayerCount = validation_layer_count; instance_info.ppEnabledLayerNames = validation_layers; uint32_t glfwExtensionCount = 0; const char** glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); const char** requested_extensions = malloc(sizeof(char*)*(glfwExtensionCount + instance_extension_count)); for (uint32_t i = 0; i < glfwExtensionCount; i++) { requested_extensions[i] = glfwExtensions[i]; } for (uint32_t i = 0; i < instance_extension_count; i++) { requested_extensions[glfwExtensionCount + i] = instance_extensions[i]; } instance_info.enabledExtensionCount = glfwExtensionCount + instance_extension_count; instance_info.ppEnabledExtensionNames = requested_extensions; instance_info.flags |= VK_INSTANCE_CREATE_ENUMERATE_PORTABILITY_BIT_KHR; VkResult result = vkCreateInstance(&instance_info, 0, &instance); if(result != VK_SUCCESS) { fprintf(stderr, "vkCreateInstance: 0x%02x\n", result); return VK_NULL_HANDLE; } free(requested_extensions); return instance; } VkDevice create_logical_device(VkPhysicalDevice physical_device, QueueIndices queue_indices) { VkDevice device; uint32_t unique_families[3] = {0xFFFFFFFF, 0xFFFFFFFF, 0xFFFFFFFF}; uint32_t unique_family_queues[3] = {0, 0, 0}; uint32_t unique_family_count = 0; uint32_t queue_family[] = {queue_indices.transfer_family, queue_indices.graphics_family, queue_indices.present_family}; uint32_t unique_queue_count = 3; if((queue_indices.graphics_family == queue_indices.present_family) && (queue_indices.graphics_index == queue_indices.present_index)) { unique_queue_count = 2; } for(uint32_t queue_idx = 0; queue_idx < unique_queue_count; queue_idx++) { uint32_t idx = 0xFFFFFFFF; for(uint32_t check_idx = 0; check_idx < unique_family_count; check_idx++) { if(queue_family[queue_idx] == unique_families[check_idx]) { idx = check_idx; break; } } if(idx == 0xFFFFFFFF) { unique_families[unique_family_count] = queue_family[queue_idx]; unique_family_queues[unique_family_count] += 1; unique_family_count += 1; } else { unique_family_queues[idx] += 1; } } VkDeviceQueueCreateInfo queue_create_info[3] = {}; float default_queue_priority = 1.0f; for(uint32_t i = 0; i < unique_family_count; i++) { queue_create_info[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queue_create_info[i].queueFamilyIndex = unique_families[i]; queue_create_info[i].queueCount = unique_family_queues[i]; queue_create_info[i].pQueuePriorities = &default_queue_priority; } VkPhysicalDeviceFeatures device_features = { .samplerAnisotropy = VK_TRUE, }; VkDeviceCreateInfo device_create_info = {}; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; device_create_info.pQueueCreateInfos = queue_create_info; device_create_info.queueCreateInfoCount = unique_family_count; device_create_info.pEnabledFeatures = &device_features; device_create_info.enabledExtensionCount = device_extension_count; device_create_info.ppEnabledExtensionNames = device_extensions; device_create_info.enabledLayerCount = validation_layer_count; device_create_info.ppEnabledLayerNames = validation_layers; VkResult result = vkCreateDevice(physical_device, &device_create_info, 0, &device); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return device; } SwapchainDetails get_swapchain_details(VkPhysicalDevice physical_device, VkSurfaceKHR surface) { SwapchainDetails details = {}; details.formats = 0; details.present_modes = 0; VkResult result; result = vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &details.capabilities); if(result != VK_SUCCESS) { return details; } result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &details.formats_count, 0); if(result != VK_SUCCESS) { return details; } details.formats = malloc(sizeof(VkSurfaceFormatKHR)*details.formats_count); result = vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &details.formats_count, details.formats); if(result != VK_SUCCESS) { free(details.formats); details.formats = 0; return details; } result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &details.present_modes_count, 0); if(result != VK_SUCCESS) { free(details.formats); details.formats = 0; return details; } details.present_modes = malloc(sizeof(VkPresentModeKHR)*details.present_modes_count); result = vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &details.present_modes_count, details.present_modes); if(result != VK_SUCCESS) { free(details.formats); free(details.present_modes); details.formats = 0; details.present_modes = 0; return details; } return details; } VkSurfaceFormatKHR choose_swapchain_format(SwapchainDetails swapchain_details) { for(uint32_t i = 0; i < swapchain_details.formats_count; i++) { VkSurfaceFormatKHR format = swapchain_details.formats[i]; if(format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return format; } } return swapchain_details.formats[0]; } VkPresentModeKHR choose_present_mode(SwapchainDetails swapchain_details) { for(uint32_t i = 0; i < swapchain_details.present_modes_count; i++) { if(swapchain_details.present_modes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { return VK_PRESENT_MODE_MAILBOX_KHR; } } return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D choose_swapchain_extent(SwapchainDetails swapchain_details) { return swapchain_details.capabilities.currentExtent; } VkSwapchainKHR create_swapchain(VkDevice device, VkSurfaceFormatKHR format, VkPresentModeKHR present_mode, VkExtent2D extent, VkSurfaceKHR surface, VkSurfaceCapabilitiesKHR capabilities, QueueIndices indices, VkSwapchainKHR old_swapchain) { uint32_t image_count = capabilities.minImageCount + 1; uint32_t max_images = capabilities.maxImageCount; if((max_images > 0) && (image_count > max_images)) { image_count = max_images; } VkSwapchainCreateInfoKHR swapchain_info = {}; swapchain_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchain_info.surface = surface; swapchain_info.minImageCount = image_count; swapchain_info.imageFormat = format.format; swapchain_info.imageColorSpace = format.colorSpace; swapchain_info.imageExtent = extent; swapchain_info.imageArrayLayers = 1; swapchain_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; uint32_t queue_families[2] = {indices.graphics_family, indices.present_index}; if(indices.graphics_family != indices.present_family) { swapchain_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchain_info.queueFamilyIndexCount = 2; swapchain_info.pQueueFamilyIndices = queue_families; } else { swapchain_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchain_info.queueFamilyIndexCount = 0; swapchain_info.pQueueFamilyIndices = 0; } swapchain_info.preTransform = capabilities.currentTransform; swapchain_info.compositeAlpha = VK_COMPOSITE_ALPHA_INHERIT_BIT_KHR; swapchain_info.presentMode = present_mode; swapchain_info.clipped = VK_TRUE; swapchain_info.oldSwapchain = old_swapchain; VkSwapchainKHR swapchain; VkResult result; result = vkCreateSwapchainKHR(device, &swapchain_info, 0, &swapchain); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return swapchain; } SwapchainImages get_swapchain_images(VkDevice device, VkSwapchainKHR swapchain) { SwapchainImages images; images.images = 0; images.count = 0; VkResult result; result = vkGetSwapchainImagesKHR(device, swapchain, &images.count, 0); if(result != VK_SUCCESS) { images.count = 0; return images; } images.images = malloc(sizeof(VkImage)*images.count); if(images.images == 0) { images.count = 0; return images; } result = vkGetSwapchainImagesKHR(device, swapchain, &images.count, images.images); if(result != VK_SUCCESS) { images.count = 0; return images; } return images; } VkImageView* create_image_views(VkDevice device, uint32_t image_count, VkImage* images, VkSurfaceFormatKHR format) { VkImageView* image_views = malloc(sizeof(VkImageView)*image_count); if(image_views == 0) { return 0; } for(uint32_t i = 0; i < image_count; i++) { VkImageViewCreateInfo view_info = {}; view_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view_info.image = images[i]; view_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_info.format = format.format; view_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; view_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; view_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; view_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; view_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view_info.subresourceRange.baseMipLevel = 0; view_info.subresourceRange.levelCount = 1; view_info.subresourceRange.baseArrayLayer = 0; view_info.subresourceRange.layerCount = 1; VkResult result = vkCreateImageView(device, &view_info, 0, &image_views[i]); if(result != VK_SUCCESS) { free(image_views); return 0; } } return image_views; } VkFramebuffer* create_swapchain_framebuffers(VkDevice device, uint32_t image_count, VkImageView* image_views, VkImageView depth_image_view, VkRenderPass render_pass, VkExtent2D extent) { VkFramebuffer* framebuffers = malloc(sizeof(VkFramebuffer)*image_count); if(framebuffers == 0) { return 0; } for(uint32_t i = 0; i < image_count; i++) { VkImageView attachments[] = { image_views[i], depth_image_view, }; VkFramebufferCreateInfo framebuffer_info = {}; framebuffer_info.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebuffer_info.renderPass = render_pass; framebuffer_info.attachmentCount = 2; framebuffer_info.pAttachments = attachments; framebuffer_info.width = extent.width; framebuffer_info.height = extent.height; framebuffer_info.layers = 1; VkResult result = vkCreateFramebuffer(device, &framebuffer_info, 0, &framebuffers[i]); if(result != VK_SUCCESS) { free(framebuffers); return 0; } } return framebuffers; } VkShaderModule create_shader_module(VkDevice device, const char * code, uint32_t code_size) { VkShaderModuleCreateInfo shader_info = {}; shader_info.sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; shader_info.codeSize = code_size; shader_info.pCode = (uint32_t*)code; VkShaderModule shader; VkResult result; result = vkCreateShaderModule(device, &shader_info, 0, &shader); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return shader; } VkShaderModule load_shader_file(uint32_t buffer_size, const char* path, VkDevice device) { FILE* file; file = fopen(path, "r"); if(file == 0) { return VK_NULL_HANDLE; } char * buffer = malloc(buffer_size); if(buffer == 0) { return VK_NULL_HANDLE; } size_t read = fread(buffer, 1, buffer_size, file); VkShaderModule shader = create_shader_module(device, buffer, read); free(buffer); return shader; } VkRenderPass create_render_pass(VkDevice device, VkSurfaceFormatKHR format, VkFormat depth_format) { VkAttachmentDescription attachments[] = { { .format = format.format, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_STORE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, }, { .format = depth_format, .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 color_attachment_refs[] = { { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }, }; VkAttachmentReference depth_attachment_ref = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = sizeof(color_attachment_refs)/sizeof(VkAttachmentReference), .pColorAttachments = color_attachment_refs, .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, }; VkRenderPassCreateInfo render_info = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = sizeof(attachments)/sizeof(VkAttachmentDescription), .pAttachments = attachments, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency, }; VkRenderPass render_pass; VkResult result = vkCreateRenderPass(device, &render_info, 0, &render_pass); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return render_pass; } VkPipelineLayout create_pipeline_layout(VkDevice device, uint32_t set_count, VkDescriptorSetLayout* sets, uint32_t pcr_count, VkPushConstantRange* pcrs) { VkPipelineLayout layout; VkPipelineLayoutCreateInfo layout_info = {}; layout_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; layout_info.setLayoutCount = set_count; layout_info.pSetLayouts = sets; layout_info.pushConstantRangeCount = pcr_count; layout_info.pPushConstantRanges = pcrs; VkResult result; result = vkCreatePipelineLayout(device, &layout_info, 0, &layout); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return layout; } uint32_t find_memory_type(VkPhysicalDevice physical_device, uint32_t type_filter, VkMemoryPropertyFlags properties) { VkPhysicalDeviceMemoryProperties memory_properties; vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); for(uint32_t i = 0; i < memory_properties.memoryTypeCount; i++) { if ((type_filter & (1 << i)) && (memory_properties.memoryTypes[i].propertyFlags & properties) == properties) { return i; } } return 0xFFFFFFFF; } AllocatedImage allocate_image(VkPhysicalDevice physical_device, VkDevice device, VkImageType type, VkFormat format, VkExtent3D size, VkImageUsageFlags usage, VkMemoryPropertyFlags properties) { VkImageCreateInfo image_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = type, .extent = size, .mipLevels = 1, .arrayLayers = 1, .format = format, .tiling = VK_IMAGE_TILING_OPTIMAL, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .samples = VK_SAMPLE_COUNT_1_BIT, .flags = 0, }; AllocatedImage allocated = { .memory = VK_NULL_HANDLE, .image = VK_NULL_HANDLE, }; VkResult result = vkCreateImage(device, &image_info, 0, &allocated.image); if(result != VK_SUCCESS) { return allocated; } VkMemoryRequirements memory_requirements; vkGetImageMemoryRequirements(device, allocated.image, &memory_requirements); VkMemoryAllocateInfo memory_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memory_requirements.size, .memoryTypeIndex = find_memory_type(physical_device, memory_requirements.memoryTypeBits, properties), }; result = vkAllocateMemory(device, &memory_info, 0, &allocated.memory); if(result != VK_SUCCESS) { vkDestroyImage(device, allocated.image, 0); allocated.image = VK_NULL_HANDLE; return allocated; } result = vkBindImageMemory(device, allocated.image, allocated.memory, 0); if(result != VK_SUCCESS) { vkFreeMemory(device, allocated.memory, 0); vkDestroyImage(device, allocated.image, 0); allocated.memory = VK_NULL_HANDLE; allocated.image = VK_NULL_HANDLE; return allocated; } return allocated; } AllocatedBuffer allocate_buffer(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties) { AllocatedBuffer ret = {}; ret.memory = VK_NULL_HANDLE; ret.buffer = VK_NULL_HANDLE; VkBufferCreateInfo buffer_info = {}; buffer_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; buffer_info.size = size; buffer_info.usage = usage; buffer_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE; VkResult result = vkCreateBuffer(device, &buffer_info, 0, &ret.buffer); if(result != VK_SUCCESS) { ret.buffer = VK_NULL_HANDLE; ret.memory = VK_NULL_HANDLE; return ret; } VkMemoryRequirements memory_requirements; vkGetBufferMemoryRequirements(device, ret.buffer, &memory_requirements); VkMemoryAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; alloc_info.allocationSize = memory_requirements.size; alloc_info.memoryTypeIndex = find_memory_type(physical_device, memory_requirements.memoryTypeBits, properties); result = vkAllocateMemory(device, &alloc_info, 0, &ret.memory); if(result != VK_SUCCESS) { vkDestroyBuffer(device, ret.buffer, 0); ret.buffer = VK_NULL_HANDLE; ret.memory = VK_NULL_HANDLE; return ret; } result = vkBindBufferMemory(device, ret.buffer, ret.memory, 0); if(result != VK_SUCCESS) { vkDestroyBuffer(device, ret.buffer, 0); ret.buffer = VK_NULL_HANDLE; ret.memory = VK_NULL_HANDLE; return ret; } return ret; } void deallocate_buffer(VkDevice device, AllocatedBuffer buffer) { vkDestroyBuffer(device, buffer.buffer, 0); vkFreeMemory(device, buffer.memory, 0); }; void deallocate_image(VkDevice device, AllocatedImage image) { vkDestroyImage(device, image.image, 0); vkFreeMemory(device, image.memory, 0); }; AllocatedBuffer* allocate_buffers(VkPhysicalDevice physical_device, VkDevice device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, uint32_t count) { AllocatedBuffer* buffers = malloc(sizeof(AllocatedBuffer)*count); if(buffers == 0) { return 0; } for(uint32_t i = 0; i < count; i++) { buffers[i] = allocate_buffer(physical_device, device, size, usage, properties); if(buffers[i].memory == VK_NULL_HANDLE) { for(uint32_t j = 0; j < i; j++) { deallocate_buffer(device, buffers[i]); } free(buffers); return 0; } } return buffers; } VkCommandBuffer command_begin_single(VkDevice device, VkCommandPool transfer_pool) { VkCommandBufferAllocateInfo command_info = {}; command_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; command_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; command_info.commandPool = transfer_pool; command_info.commandBufferCount = 1; VkCommandBuffer command_buffer; VkResult result = vkAllocateCommandBuffers(device, &command_info, &command_buffer); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } VkCommandBufferBeginInfo begin_info = {}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; result = vkBeginCommandBuffer(command_buffer, &begin_info); if(result != VK_SUCCESS) { vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); return VK_NULL_HANDLE; } return command_buffer; } VkResult command_end_single(VkDevice device, VkCommandBuffer command_buffer, VkCommandPool transfer_pool, VkQueue transfer_queue) { VkSubmitInfo submit_info = {}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &command_buffer; VkResult result = vkQueueSubmit(transfer_queue, 1, &submit_info, 0); if(result != VK_SUCCESS) { vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); return result; } result = vkQueueWaitIdle(transfer_queue); vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); return result; } VkResult command_copy_buffers(VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkBuffer source, VkBuffer dest, VkDeviceSize size) { VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool); VkBufferCopy copy_region = {}; copy_region.srcOffset = 0; copy_region.dstOffset = 0; copy_region.size = size; vkCmdCopyBuffer(command_buffer, source, dest, 1, ©_region); VkResult result = vkEndCommandBuffer(command_buffer); if(result != VK_SUCCESS) { vkFreeCommandBuffers(device, transfer_pool, 1, &command_buffer); return result; } return command_end_single(device, command_buffer, transfer_pool, transfer_queue); } VkResult command_transition_image_layout(VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkImageLayout old_layout, VkImageLayout new_layout, VkImage image) { VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool); 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, .levelCount = 1, .layerCount = 1, .baseMipLevel = 0, .baseArrayLayer = 0, }, .srcAccessMask = 0, .dstAccessMask = 0, }; vkCmdPipelineBarrier(command_buffer, 0, 0, 0, 0, 0, 0, 0, 1, &barrier); return command_end_single(device, command_buffer, transfer_pool, transfer_queue); } VkResult command_copy_buffer_to_image(VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkExtent3D image_size, VkBuffer source, VkImage dest) { VkCommandBuffer command_buffer = command_begin_single(device, transfer_pool); VkBufferImageCopy region = { .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource = { .baseArrayLayer = 0, .layerCount = 1, .mipLevel = 0, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, }, .imageOffset = { .x = 0, .y = 0, .z = 0, }, .imageExtent = image_size, }; vkCmdCopyBufferToImage(command_buffer, source, dest, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); return command_end_single(device, command_buffer, transfer_pool, transfer_queue); } AllocatedBuffer create_populated_buffer(VkPhysicalDevice physical_device, VkDevice device, void* data, VkDeviceSize size, VkCommandPool transfer_pool, VkQueue transfer_queue, VkBufferUsageFlags usage) { AllocatedBuffer staging_buffer = {}; AllocatedBuffer vertex_buffer = {}; staging_buffer = allocate_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if(staging_buffer.memory == VK_NULL_HANDLE) { return vertex_buffer; } void* buffer_data; VkResult result = vkMapMemory(device, staging_buffer.memory, 0, size, 0, &buffer_data); if(result != VK_SUCCESS) { deallocate_buffer(device, staging_buffer); return vertex_buffer; } memcpy(buffer_data, data, size); vkUnmapMemory(device, staging_buffer.memory); vertex_buffer = allocate_buffer(physical_device, device, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | usage, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if(vertex_buffer.memory == VK_NULL_HANDLE) { deallocate_buffer(device, staging_buffer); return vertex_buffer; } result = command_copy_buffers(device, transfer_pool, transfer_queue, staging_buffer.buffer, vertex_buffer.buffer, size); if(result != VK_SUCCESS) { deallocate_buffer(device, staging_buffer); deallocate_buffer(device, vertex_buffer); vertex_buffer.buffer = VK_NULL_HANDLE; vertex_buffer.memory = VK_NULL_HANDLE; return vertex_buffer; } return vertex_buffer; } Texture load_texture(VkPhysicalDevice physical_device, VkDevice device, VkCommandPool transfer_pool, VkQueue transfer_queue, VkExtent2D size, uint32_t stride, VkFormat format, void* image_data){ Texture ret = { .image.image = VK_NULL_HANDLE, .image.memory = VK_NULL_HANDLE, .view = VK_NULL_HANDLE, }; uint32_t image_size = size.width * size.height * stride; AllocatedBuffer staging = allocate_buffer(physical_device, device, image_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT); if(staging.memory == VK_NULL_HANDLE) { return ret; } void* staging_ptr; VkResult result = vkMapMemory(device, staging.memory, 0, image_size, 0, &staging_ptr); if(result != VK_SUCCESS) { deallocate_buffer(device, staging); return ret; } memcpy(staging_ptr, image_data, image_size); vkUnmapMemory(device, staging.memory); VkExtent3D full_extent = { .width = size.width, .height = size.height, .depth = 1, }; AllocatedImage image = allocate_image(physical_device, device, VK_IMAGE_TYPE_2D, format, full_extent, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if(image.memory == VK_NULL_HANDLE) { deallocate_buffer(device, staging); deallocate_image(device, image); return ret; } result = command_transition_image_layout(device, transfer_pool, transfer_queue, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, image.image); if(result != VK_SUCCESS) { deallocate_buffer(device, staging); deallocate_image(device, image); return ret; } result = command_copy_buffer_to_image(device, transfer_pool, transfer_queue, full_extent, staging.buffer, image.image); if(result != VK_SUCCESS) { deallocate_buffer(device, staging); deallocate_image(device, image); return ret; } VkImageView view; VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image.image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .components = { .a = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .r = VK_COMPONENT_SWIZZLE_IDENTITY, }, .format = format, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .layerCount = 1, .levelCount = 1, .baseArrayLayer = 0, .baseMipLevel = 0, }, }; result = vkCreateImageView(device, &view_info, 0, &view); if(result != VK_SUCCESS) { deallocate_buffer(device, staging); deallocate_image(device, image); return ret; } VkSampler sampler; VkSamplerCreateInfo sampler_info = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_NEAREST, .minFilter = VK_FILTER_NEAREST, .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .anisotropyEnable = VK_TRUE, .maxAnisotropy = 2.0f, .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.0f, .minLod = 0.0f, .maxLod = 0.0f, }; result = vkCreateSampler(device, &sampler_info, 0, &sampler); if(result != VK_SUCCESS) { deallocate_buffer(device, staging); deallocate_image(device, image); return ret; } ret.image = image; ret.view = view; ret.sampler = sampler; return ret; } VkPipeline create_graphics_pipeline( VkDevice device, VkExtent2D extent, VkPipelineLayout layout, VkRenderPass render_pass, uint32_t shader_stage_count, VkPipelineShaderStageCreateInfo* shader_stages, MeshType mesh_type ) { VkDynamicState dynamic_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; uint32_t dynamic_state_count = sizeof(dynamic_states)/sizeof(VkDynamicState); VkPipelineDynamicStateCreateInfo dynamic_info = {}; dynamic_info.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamic_info.dynamicStateCount = dynamic_state_count; dynamic_info.pDynamicStates = dynamic_states; VkPipelineVertexInputStateCreateInfo vertex_input_info = {}; vertex_input_info.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertex_input_info.vertexBindingDescriptionCount = mesh_type.bindings_count; vertex_input_info.pVertexBindingDescriptions = mesh_type.bindings; vertex_input_info.vertexAttributeDescriptionCount = mesh_type.attributes_count; vertex_input_info.pVertexAttributeDescriptions = mesh_type.attributes; VkPipelineInputAssemblyStateCreateInfo input_assembly_info = {}; input_assembly_info.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; input_assembly_info.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; input_assembly_info.primitiveRestartEnable = VK_FALSE; VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float)(extent.width); viewport.height = (float)(extent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; VkOffset2D scissor_offset = {.x = 0, .y = 0}; scissor.offset = scissor_offset; scissor.extent = extent; VkPipelineViewportStateCreateInfo viewport_state = {}; viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewport_state.viewportCount = 1; viewport_state.pViewports = &viewport; viewport_state.scissorCount = 1; viewport_state.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo raster_info = {}; raster_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; raster_info.depthClampEnable = VK_FALSE; raster_info.rasterizerDiscardEnable = VK_FALSE; raster_info.polygonMode = VK_POLYGON_MODE_FILL; raster_info.lineWidth = 1.0f; raster_info.cullMode = VK_CULL_MODE_BACK_BIT; raster_info.frontFace = VK_FRONT_FACE_CLOCKWISE; raster_info.depthBiasEnable = VK_FALSE; raster_info.depthBiasConstantFactor = 0.0f; raster_info.depthBiasClamp = 0.0f; raster_info.depthBiasSlopeFactor = 0.0f; VkPipelineMultisampleStateCreateInfo multisample_info = {}; multisample_info.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisample_info.sampleShadingEnable = VK_FALSE; multisample_info.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisample_info.minSampleShading = 1.0f; multisample_info.pSampleMask = 0; multisample_info.alphaToCoverageEnable = VK_FALSE; multisample_info.alphaToOneEnable = VK_FALSE; VkPipelineDepthStencilStateCreateInfo depth_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS, .depthBoundsTestEnable = VK_FALSE, .maxDepthBounds = 1.0f, .minDepthBounds = 0.0f, .stencilTestEnable = VK_FALSE, .front = {}, .back = {}, }; VkPipelineColorBlendAttachmentState color_blend_attachment = {}; color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; color_blend_attachment.blendEnable = VK_TRUE; color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; VkPipelineColorBlendStateCreateInfo color_blend_info = {}; color_blend_info.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; color_blend_info.logicOpEnable = VK_FALSE; color_blend_info.logicOp = VK_LOGIC_OP_COPY; color_blend_info.attachmentCount = 1; color_blend_info.pAttachments = &color_blend_attachment; color_blend_info.blendConstants[0] = 0.0f; color_blend_info.blendConstants[1] = 0.0f; color_blend_info.blendConstants[2] = 0.0f; color_blend_info.blendConstants[3] = 0.0f; VkGraphicsPipelineCreateInfo pipeline_info = {}; pipeline_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipeline_info.stageCount = shader_stage_count; pipeline_info.pStages = shader_stages; pipeline_info.pVertexInputState = &vertex_input_info; pipeline_info.pInputAssemblyState = &input_assembly_info; pipeline_info.pViewportState = &viewport_state; pipeline_info.pRasterizationState = &raster_info; pipeline_info.pDepthStencilState = 0; pipeline_info.pColorBlendState = &color_blend_info; pipeline_info.pDynamicState = &dynamic_info; pipeline_info.pDepthStencilState = &depth_info; pipeline_info.layout = layout; pipeline_info.renderPass = render_pass; pipeline_info.subpass = 0; pipeline_info.basePipelineHandle = VK_NULL_HANDLE; pipeline_info.basePipelineIndex = -1; VkPipeline pipeline; VkResult result = vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipeline_info, 0, &pipeline); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return pipeline; } VkCommandPool create_command_pool(VkDevice device, uint32_t queue_family) { VkCommandPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; pool_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; pool_info.queueFamilyIndex = queue_family; VkCommandPool command_pool; VkResult result = vkCreateCommandPool(device, &pool_info, 0, &command_pool); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return command_pool; } VkResult recreate_swap_chain(VulkanContext* context, VkExtent2D new_extent) { for(uint32_t i = 0; i < context->swapchain_image_count; i++) { vkDestroyFramebuffer(context->device, context->swapchain_framebuffers[i], 0); vkDestroyImageView(context->device, context->swapchain_image_views[i], 0); } vkDestroySwapchainKHR(context->device, context->swapchain, 0); free(context->swapchain_images); free(context->swapchain_image_views); free(context->swapchain_framebuffers); free(context->swapchain_details.formats); free(context->swapchain_details.present_modes); context->swapchain_extent = new_extent; SwapchainDetails swapchain_details = get_swapchain_details(context->physical_device, context->surface); if(swapchain_details.formats == 0) { return 0; } else { context->swapchain_details = swapchain_details; } context->swapchain_format = choose_swapchain_format(context->swapchain_details); context->swapchain_present_mode = choose_present_mode(context->swapchain_details); context->swapchain_extent = choose_swapchain_extent(context->swapchain_details); VkSwapchainKHR swapchain = create_swapchain(context->device, context->swapchain_format, context->swapchain_present_mode, context->swapchain_extent, context->surface, context->swapchain_details.capabilities, context->queue_indices, context->swapchain); if(swapchain == VK_NULL_HANDLE) { context->swapchain = VK_NULL_HANDLE; return VK_ERROR_INITIALIZATION_FAILED; } else { context->swapchain = swapchain; } SwapchainImages swapchain_images = get_swapchain_images(context->device, context->swapchain); if(swapchain_images.count == 0) { return VK_ERROR_INITIALIZATION_FAILED; } else { context->swapchain_images = swapchain_images.images; context->swapchain_image_count = swapchain_images.count; } VkImageView* image_views = create_image_views(context->device, context->swapchain_image_count, context->swapchain_images, context->swapchain_format); if(image_views == 0) { return VK_ERROR_INITIALIZATION_FAILED; } else { context->swapchain_image_views = image_views; } VkFramebuffer* framebuffers = create_swapchain_framebuffers(context->device, context->swapchain_image_count, context->swapchain_image_views, context->depth_image_view, context->render_pass, context->swapchain_extent); if(framebuffers == 0) { return VK_ERROR_INITIALIZATION_FAILED; } else { context->swapchain_framebuffers = framebuffers; } return VK_SUCCESS; } void record_command_buffer_mesh(Mesh mesh, VkCommandBuffer command_buffer) { VkBuffer vertex_buffers[] = {mesh.vertex_buffer.buffer}; VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(command_buffer, 0, 1, vertex_buffers, offsets); vkCmdBindIndexBuffer(command_buffer, mesh.index_buffer.buffer, 0, VK_INDEX_TYPE_UINT16); vkCmdDrawIndexed(command_buffer, mesh.index_count, 1, 0, 0, 0); } void record_command_buffer_material(Material material, uint32_t mesh_count, Mesh* meshes, VkDescriptorSet scene_ubo_descriptor, VkCommandBuffer command_buffer) { vkCmdBindPipeline(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.pipeline); vkCmdBindDescriptorSets(command_buffer, VK_PIPELINE_BIND_POINT_GRAPHICS, material.layout, 0, 1, &scene_ubo_descriptor, 0, 0); for(uint32_t i = 0; i < mesh_count; i++) { record_command_buffer_mesh(meshes[i], command_buffer); } } VkResult record_command_buffer_scene(uint32_t materials_count, Material* materials, uint32_t* mesh_counts, Mesh** meshes, VkDescriptorSet scene_ubo_descriptor, VkCommandBuffer command_buffer, VkRenderPass render_pass, VkFramebuffer framebuffer, VkExtent2D extent) { VkCommandBufferBeginInfo begin_info = {}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = 0; begin_info.pInheritanceInfo = 0; VkResult result = vkBeginCommandBuffer(command_buffer, &begin_info); if(result != VK_SUCCESS) { return result; } VkRenderPassBeginInfo render_pass_info = {}; render_pass_info.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; render_pass_info.renderPass = render_pass; render_pass_info.framebuffer = framebuffer; VkOffset2D render_offset = {.x = 0, .y = 0}; render_pass_info.renderArea.offset = render_offset; render_pass_info.renderArea.extent = extent; VkClearValue clear_colors[] = { { .color = { {0.0f, 0.0f, 0.0f, 1.0f} } }, { .depthStencil = {1.0f, 0.0f}, }, }; render_pass_info.clearValueCount = sizeof(clear_colors)/sizeof(VkClearValue); render_pass_info.pClearValues = clear_colors; vkCmdBeginRenderPass(command_buffer, &render_pass_info, VK_SUBPASS_CONTENTS_INLINE); VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float)(extent.width); viewport.height = (float)(extent.height); viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; vkCmdSetViewport(command_buffer, 0, 1, &viewport); VkRect2D scissor = {}; VkOffset2D scissor_offset = {.x = 0.0f, .y = 0.0f}; scissor.offset = scissor_offset; scissor.extent = extent; vkCmdSetScissor(command_buffer, 0, 1, &scissor); for(uint i = 0; i < materials_count; i++) { record_command_buffer_material(materials[i], mesh_counts[i], meshes[i], scene_ubo_descriptor, command_buffer); } vkCmdEndRenderPass(command_buffer); return vkEndCommandBuffer(command_buffer); } VkCommandBuffer* create_command_buffers(VkDevice device, VkCommandPool command_pool, uint32_t image_count) { VkCommandBufferAllocateInfo alloc_info = {}; alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; alloc_info.commandPool = command_pool; alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; alloc_info.commandBufferCount = image_count; VkCommandBuffer* command_buffers = malloc(sizeof(VkCommandBuffer)*image_count); if(command_buffers == 0) { return 0; } VkResult result = vkAllocateCommandBuffers(device, &alloc_info, command_buffers); if(result != VK_SUCCESS) { return VK_NULL_HANDLE; } return command_buffers; } VkSemaphore* create_semaphores(VkDevice device, VkSemaphoreCreateFlags flags, uint32_t count) { VkSemaphore* semaphores = malloc(sizeof(VkSemaphore)*count); if(semaphores == 0) { return 0; } VkSemaphoreCreateInfo semaphore_info = {}; semaphore_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; semaphore_info.flags = flags; for(uint32_t i = 0; i < count; i++) { VkResult result = vkCreateSemaphore(device, &semaphore_info, 0, &semaphores[i]); if(result != VK_SUCCESS) { free(semaphores); return 0; } } return semaphores; } VkFence* create_fences(VkDevice device, VkFenceCreateFlags flags, uint32_t count) { VkFence* fences = malloc(sizeof(VkFence)*count); if(fences == 0) { return 0; } VkFenceCreateInfo fence_info = {}; fence_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fence_info.flags = flags; for(uint32_t i = 0; i < count; i++) { VkResult result = vkCreateFence(device, &fence_info, 0, &fences[i]); if(result != VK_SUCCESS) { free(fences); return 0; } } return fences; } Mesh load_texture_mesh(VkPhysicalDevice physical_device, VkDevice device, struct TextureVertex* vertices, uint32_t vertex_count, uint16_t* indices, uint32_t index_count, VkCommandPool transfer_pool, VkQueue transfer_queue) { Mesh mesh = {}; mesh.vertex_buffer.buffer = VK_NULL_HANDLE; mesh.vertex_buffer.memory = VK_NULL_HANDLE; mesh.index_buffer.buffer = VK_NULL_HANDLE; mesh.index_buffer.memory = VK_NULL_HANDLE; AllocatedBuffer vertex_buffer = create_populated_buffer(physical_device, device, (void*)vertices, sizeof(struct TextureVertex) * vertex_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); if(vertex_buffer.memory == VK_NULL_HANDLE) { return mesh; } mesh.vertex_buffer = vertex_buffer; mesh.vertex_count = vertex_count; AllocatedBuffer index_buffer = create_populated_buffer(physical_device, device, (void*)indices, sizeof(uint16_t) * index_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); if(index_buffer.memory == VK_NULL_HANDLE) { deallocate_buffer(device, vertex_buffer); AllocatedBuffer tmp = { .memory = VK_NULL_HANDLE, .buffer = VK_NULL_HANDLE}; mesh.vertex_buffer = tmp; return mesh; } mesh.index_buffer = index_buffer; mesh.index_count = index_count; return mesh; } Mesh load_simple_mesh(VkPhysicalDevice physical_device, VkDevice device, struct Vertex* vertices, uint32_t vertex_count, uint16_t* indices, uint32_t index_count, VkCommandPool transfer_pool, VkQueue transfer_queue) { Mesh mesh = {}; mesh.vertex_buffer.buffer = VK_NULL_HANDLE; mesh.vertex_buffer.memory = VK_NULL_HANDLE; mesh.index_buffer.buffer = VK_NULL_HANDLE; mesh.index_buffer.memory = VK_NULL_HANDLE; AllocatedBuffer vertex_buffer = create_populated_buffer(physical_device, device, (void*)vertices, sizeof(struct Vertex) * vertex_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); if(vertex_buffer.memory == VK_NULL_HANDLE) { return mesh; } mesh.vertex_buffer = vertex_buffer; mesh.vertex_count = vertex_count; AllocatedBuffer index_buffer = create_populated_buffer(physical_device, device, (void*)indices, sizeof(uint16_t) * index_count, transfer_pool, transfer_queue, VK_BUFFER_USAGE_INDEX_BUFFER_BIT); if(index_buffer.memory == VK_NULL_HANDLE) { deallocate_buffer(device, vertex_buffer); AllocatedBuffer tmp = { .memory = VK_NULL_HANDLE, .buffer = VK_NULL_HANDLE}; mesh.vertex_buffer = tmp; return mesh; } mesh.index_buffer = index_buffer; mesh.index_count = index_count; return mesh; } uint32_t HARDCODED_SETS_PER_POOL = 10; Material create_material( VkDevice device, VkExtent2D extent, VkRenderPass render_pass, uint32_t shader_stage_count, VkPipelineShaderStageCreateInfo* shader_stages, VkDescriptorSetLayout scene_ubo_layout, PipelineLayout pipeline_layout, MeshType mesh_type ) { Material zero_material = { .pipeline = VK_NULL_HANDLE, }; VkDescriptorSetLayout material_set_layout; VkDescriptorSetLayout mesh_set_layout; VkDescriptorSetLayout all_layouts[3] = {scene_ubo_layout, VK_NULL_HANDLE, VK_NULL_HANDLE}; uint32_t num_layouts = 1; if(pipeline_layout.material_bindings_count > 0) { VkDescriptorSetLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = pipeline_layout.material_bindings_count, .pBindings = pipeline_layout.material_bindings, }; VkResult result = vkCreateDescriptorSetLayout(device, &layout_info, 0, &material_set_layout); if(result != VK_SUCCESS) { return zero_material; } all_layouts[num_layouts] = material_set_layout; num_layouts += 1; } if(pipeline_layout.mesh_bindings_count > 0) { VkDescriptorSetLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = pipeline_layout.mesh_bindings_count, .pBindings = pipeline_layout.mesh_bindings, }; VkResult result = vkCreateDescriptorSetLayout(device, &layout_info, 0, &mesh_set_layout); if(result != VK_SUCCESS) { return zero_material; } all_layouts[num_layouts] = mesh_set_layout; num_layouts += 1; } VkPipelineLayout layout = create_pipeline_layout(device, num_layouts, all_layouts, 0, 0); if(layout == VK_NULL_HANDLE) { return zero_material; } VkPipeline pipeline = create_graphics_pipeline(device, extent, layout, render_pass, shader_stage_count, shader_stages, mesh_type); if(pipeline == VK_NULL_HANDLE) { return zero_material; } Material material = { .layout = layout, .pipeline = pipeline, .material_set_layout = material_set_layout, .mesh_set_layout = mesh_set_layout, }; return material; } Material create_simple_mesh_material(VkDevice device, VkExtent2D extent, VkRenderPass render_pass, VkDescriptorSetLayout scene_ubo_layout) { VkShaderModule vert_shader = load_shader_file(2048, "shader_src/basic.vert.spv", device); VkShaderModule frag_shader = load_shader_file(2048, "shader_src/basic.frag.spv", device); VkPipelineShaderStageCreateInfo shader_stages[2] = {}; shader_stages[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shader_stages[0].module = vert_shader; shader_stages[0].pName = "main"; shader_stages[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].module = frag_shader; shader_stages[1].pName = "main"; VkVertexInputBindingDescription bindings[1] = { { .binding = 0, // Which buffer 'binding' to use .stride = sizeof(struct Vertex), // How many bytes to increase the index between instance .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, // Whether an instance is a vertex or an index }, }; VkVertexInputAttributeDescription attributes[2] = { { .binding = 0, // Which buffer 'binding' to use .location = 0, // Which 'location' to export as to shader .format = VK_FORMAT_R32G32B32_SFLOAT, // What format to interpret as for shader .offset = offsetof(struct Vertex, pos), // What offset from instance start }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct Vertex, color), }, }; MeshType simple_mesh_type = { .bindings = bindings, .bindings_count = sizeof(bindings)/sizeof(VkVertexInputBindingDescription), .attributes = attributes, .attributes_count = sizeof(attributes)/sizeof(VkVertexInputAttributeDescription), }; PipelineLayout simple_layout = { }; return create_material(device, extent, render_pass, 2, shader_stages, scene_ubo_layout, simple_layout, simple_mesh_type); } Material create_texture_mesh_material(VkDevice device, VkExtent2D extent, VkRenderPass render_pass, VkDescriptorSetLayout scene_ubo_layout) { VkShaderModule vert_shader = load_shader_file(2048, "shader_src/texture.vert.spv", device); if(vert_shader == VK_NULL_HANDLE) { Material tmp = {}; return tmp; } VkShaderModule frag_shader = load_shader_file(2048, "shader_src/texture.frag.spv", device); if(frag_shader == VK_NULL_HANDLE) { Material tmp = {}; return tmp; } VkPipelineShaderStageCreateInfo shader_stages[2] = {}; shader_stages[0].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; shader_stages[0].module = vert_shader; shader_stages[0].pName = "main"; shader_stages[1].sType = VK_STRUCTURE_TYPE_SHADER_CREATE_INFO_EXT; shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; shader_stages[1].module = frag_shader; shader_stages[1].pName = "main"; VkVertexInputBindingDescription bindings[] = { { .binding = 0, .stride = sizeof(struct TextureVertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }, }; VkVertexInputAttributeDescription attributes[] = { { .binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct TextureVertex, pos), }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(struct TextureVertex, color), }, { .binding = 0, .location = 2, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(struct TextureVertex, tex), }, }; VkDescriptorSetLayoutBinding mesh_set_bindings[] = { { .binding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImmutableSamplers = 0, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }, }; MeshType textured_mesh_type = { .bindings = bindings, .bindings_count = sizeof(bindings)/sizeof(VkVertexInputBindingDescription), .attributes = attributes, .attributes_count = sizeof(attributes)/sizeof(VkVertexInputAttributeDescription), }; PipelineLayout texture_layout = { .mesh_bindings_count = sizeof(mesh_set_bindings)/sizeof(VkDescriptorSetLayoutBinding), .mesh_bindings = mesh_set_bindings, }; return create_material(device, extent, render_pass, 2, shader_stages, scene_ubo_layout, texture_layout, textured_mesh_type); } VulkanContext* init_vulkan(GLFWwindow* window, uint32_t max_frames_in_flight) { VulkanContext* context = (VulkanContext*)malloc(sizeof(VulkanContext)); VkInstance instance = create_instance(); if(instance == VK_NULL_HANDLE) { fprintf(stderr, "failed to initialize vulkan instance\n"); return 0; } else { context->instance = instance; } VkDebugUtilsMessengerEXT debug_messenger = create_debug_messenger(context->instance); if(debug_messenger == VK_NULL_HANDLE) { fprintf(stderr, "failed to initialize vulkan debug messenger\n"); return 0; } else { context->debug_messenger = debug_messenger; } VkPhysicalDevice physical_device = get_best_physical_device(context->instance); if(physical_device == VK_NULL_HANDLE) { fprintf(stderr, "failed to pick vulkan physical device\n"); return 0; } else { context->physical_device = physical_device; } VkSurfaceKHR surface = create_surface_khr(context->instance, window); if(surface == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan surface\n"); return 0; } else { context->surface = surface; } QueueIndices queue_indices = get_queue_indices(context->physical_device, context->surface); if(check_queue_indices(queue_indices) == false) { fprintf(stderr, "failed to get vulkan queue indices\n"); return 0; } else { context->queue_indices = queue_indices; } VkDevice device = create_logical_device(context->physical_device, context->queue_indices); if(device == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan logical device\n"); return 0; } else { context->device = device; } vkGetDeviceQueue(device, context->queue_indices.graphics_family, context->queue_indices.graphics_index, &context->queues.graphics); vkGetDeviceQueue(device, context->queue_indices.present_family, context->queue_indices.present_index, &context->queues.present); vkGetDeviceQueue(device, context->queue_indices.transfer_family, context->queue_indices.transfer_index, &context->queues.transfer); SwapchainDetails swapchain_details = get_swapchain_details(context->physical_device, context->surface); if(swapchain_details.formats == 0) { fprintf(stderr, "failed to create vulkan logical device\n"); return 0; } else { context->swapchain_details = swapchain_details; } context->swapchain_format = choose_swapchain_format(context->swapchain_details); context->swapchain_present_mode = choose_present_mode(context->swapchain_details); context->swapchain_extent = choose_swapchain_extent(context->swapchain_details); VkSwapchainKHR swapchain = create_swapchain(context->device, context->swapchain_format, context->swapchain_present_mode, context->swapchain_extent, context->surface, context->swapchain_details.capabilities, context->queue_indices, VK_NULL_HANDLE); if(swapchain == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan swapchain\n"); return 0; } else { context->swapchain = swapchain; } SwapchainImages swapchain_images = get_swapchain_images(context->device, context->swapchain); if(swapchain_images.count == 0) { fprintf(stderr, "failed to get vulkan swapchain images\n"); return 0; } else { context->swapchain_image_count = swapchain_images.count; context->swapchain_images = swapchain_images.images; } VkImageView* image_views = create_image_views(context->device, context->swapchain_image_count, context->swapchain_images, context->swapchain_format); if(image_views == 0) { fprintf(stderr, "failed to create vulkan image views\n"); return 0; } else { context->swapchain_image_views = image_views; } VkFormat requested[] = { VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT }; VkFormat depth_format = find_depth_format(context->physical_device, 3, requested, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); if(depth_format == VK_FORMAT_MAX_ENUM) { fprintf(stderr, "failed to find a suitable depth image format\n"); return 0; } else { context->depth_format = depth_format; } VkRenderPass render_pass = create_render_pass(context->device, context->swapchain_format, context->depth_format); if(render_pass == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan render pass\n"); return 0; } else { context->render_pass = render_pass; } VkExtent3D depth_extent = { .width = context->swapchain_extent.width, .height = context->swapchain_extent.height, .depth = 1, }; AllocatedImage depth_image = allocate_image(context->physical_device, context->device, VK_IMAGE_TYPE_2D, depth_format, depth_extent, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if(depth_image.memory == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan depth image\n"); return 0; } else { context->depth_image = depth_image; } VkImageViewCreateInfo depth_view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = depth_image.image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = depth_format, .components = { .r = VK_COMPONENT_SWIZZLE_IDENTITY, .g = VK_COMPONENT_SWIZZLE_IDENTITY, .b = VK_COMPONENT_SWIZZLE_IDENTITY, .a = VK_COMPONENT_SWIZZLE_IDENTITY, }, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; VkImageView depth_image_view; VkResult result = vkCreateImageView(context->device, &depth_view_info, 0, &depth_image_view); if(result != VK_SUCCESS) { fprintf(stderr, "failed to create vulkan depth image view\n"); return 0; } else { context->depth_image_view = depth_image_view; } VkFramebuffer* framebuffers = create_swapchain_framebuffers(context->device, context->swapchain_image_count, context->swapchain_image_views, context->depth_image_view, context->render_pass, context->swapchain_extent); if(framebuffers == 0) { fprintf(stderr, "failed to create vulkan framebuffers\n"); return 0; } else { context->swapchain_framebuffers = framebuffers; } VkCommandPool graphics_command_pool = create_command_pool(context->device, context->queue_indices.graphics_family); if(graphics_command_pool == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan graphics command pool"); return 0; } else { context->graphics_command_pool = graphics_command_pool; } VkCommandPool transfer_command_pool = create_command_pool(context->device, context->queue_indices.transfer_family); if(transfer_command_pool == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan transfer command pool"); return 0; } else { context->transfer_command_pool = transfer_command_pool; } context->max_frames_in_flight = max_frames_in_flight; VkCommandBuffer* swapchain_command_buffers = create_command_buffers(context->device, context->graphics_command_pool, max_frames_in_flight); if(swapchain_command_buffers == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan swapchain command buffer\n"); return 0; } else { context->swapchain_command_buffers = swapchain_command_buffers; } VkSemaphore* ia_semaphores = create_semaphores(context->device, 0, max_frames_in_flight); if(ia_semaphores == 0) { fprintf(stderr, "failed to create vulkan image available semaphores\n"); return 0; } else { context->image_available_semaphores = ia_semaphores; } VkSemaphore* rf_semaphores = create_semaphores(context->device, 0, max_frames_in_flight); if(rf_semaphores == 0) { fprintf(stderr, "failed to create vulkan render finished semaphores\n"); return 0; } else { context->render_finished_semaphores = rf_semaphores; } VkFence* if_fences = create_fences(context->device, VK_FENCE_CREATE_SIGNALED_BIT, max_frames_in_flight); if(if_fences == 0) { fprintf(stderr, "failed to create vulkan in flight fence\n"); return 0; } else { context->in_flight_fences = if_fences; } VkDescriptorPoolSize ubo_pool_sizes[] = { { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = max_frames_in_flight, } }; VkDescriptorPool scene_ubo_pool = create_descriptor_pool(device, ubo_pool_sizes, 1, max_frames_in_flight); if(scene_ubo_pool == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan scene descriptor pool\n"); return 0; } else { context->scene_ubo_pool = scene_ubo_pool; } VkDescriptorSetLayoutBinding scene_ubo_layout_binding = { .binding = 0, .stageFlags = VK_SHADER_STAGE_VERTEX_BIT, .descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = 1, .pImmutableSamplers = 0, }; VkDescriptorSetLayout scene_ubo_layout = create_descriptor_set_layout(device, &scene_ubo_layout_binding, 1); if(scene_ubo_layout == VK_NULL_HANDLE) { fprintf(stderr, "failed to create vulkan scene descriptor layout\n"); return 0; } else { context->scene_ubo_layout = scene_ubo_layout; } VkDescriptorSet* scene_ubo_descriptors = create_descriptor_sets(context->device, context->scene_ubo_layout, context->scene_ubo_pool, max_frames_in_flight); if(scene_ubo_descriptors == 0) { fprintf(stderr, "failed to create vulkan scene descriptore\n"); return 0; } else { context->scene_ubo_descriptors = scene_ubo_descriptors; } AllocatedBuffer* scene_ubos = allocate_buffers(context->physical_device, context->device, sizeof(struct SceneUBO), VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_COHERENT_BIT | VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, max_frames_in_flight); if(scene_ubos == 0) { fprintf(stderr, "failed to create vulkan scnene ubo buffers\n"); return 0; } else { context->scene_ubos = scene_ubos; } void** scene_ubo_ptrs = malloc(sizeof(void*)*max_frames_in_flight); if(scene_ubo_ptrs == 0) { return 0; } else { context->scene_ubo_ptrs = scene_ubo_ptrs; } for(uint32_t i = 0; i < max_frames_in_flight; i++) { VkResult result = vkMapMemory(device, context->scene_ubos[i].memory, 0, sizeof(struct SceneUBO), 0, &context->scene_ubo_ptrs[i]); if(result != VK_SUCCESS) { for(uint32_t j = 0; j < i; j++) { vkUnmapMemory(device, context->scene_ubos[j].memory); } fprintf(stderr, "failed to map vulkan buffers to pointers for scene ubos\n"); return 0; } } for(uint32_t i = 0; i < max_frames_in_flight; i++) { VkDescriptorBufferInfo buffer_info = {}; buffer_info.buffer = context->scene_ubos[i].buffer; buffer_info.offset = 0; buffer_info.range = sizeof(struct SceneUBO); VkWriteDescriptorSet descriptor_write = {}; descriptor_write.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptor_write.dstSet = context->scene_ubo_descriptors[i]; descriptor_write.dstBinding = 0; descriptor_write.dstArrayElement = 0; descriptor_write.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptor_write.descriptorCount = 1; descriptor_write.pBufferInfo = &buffer_info; descriptor_write.pImageInfo = 0; descriptor_write.pTexelBufferView = 0; vkUpdateDescriptorSets(device, 1, &descriptor_write, 0, 0); } Material simple_mesh_material = create_simple_mesh_material(context->device, context->swapchain_extent, context->render_pass, context->scene_ubo_layout); if(simple_mesh_material.pipeline == VK_NULL_HANDLE) { fprintf(stderr, "failed to create simple mesh material\n"); return 0; } else { context->simple_mesh_material = simple_mesh_material; } Material texture_mesh_material = create_texture_mesh_material(context->device, context->swapchain_extent, context->render_pass, context->scene_ubo_layout); if(texture_mesh_material.pipeline == VK_NULL_HANDLE) { fprintf(stderr, "failed to create texture mesh material\n"); return 0; } else { context->texture_mesh_material = texture_mesh_material; } Mesh triangle_mesh = load_simple_mesh(context->physical_device, context->device, (struct Vertex*)vertices, 4, (uint16_t*)indices, 6, context->transfer_command_pool, context->queues.transfer); if(triangle_mesh.vertex_buffer.buffer == VK_NULL_HANDLE) { fprintf(stderr, "failed to load triangle mesh\n"); return 0; } else { context->triangle_mesh = triangle_mesh; } Mesh triangle_mesh_textured = load_texture_mesh(context->physical_device, context->device, (struct TextureVertex*)texture_vertices, 4, (uint16_t*)indices, 6, context->transfer_command_pool, context->queues.transfer); if(triangle_mesh_textured.vertex_buffer.buffer == VK_NULL_HANDLE) { fprintf(stderr, "failed to load textured triangle mesh\n"); return 0; } else { context->triangle_mesh_textured = triangle_mesh_textured; } return context; } struct { bool forward; bool backward; bool left; bool right; bool up; bool down; bool turn_left; bool turn_right; bool turn_up; bool turn_down; bool roll_left; bool roll_right; } key_flags = { .forward = false, .backward = false, .left = false, .right = false, .turn_left = false, .turn_right = false, .turn_up = false, .turn_down = false, .roll_left = false, .roll_right = false, }; void key_callback(GLFWwindow* window, int key, int scancode, int action, int mods) { (void)scancode; (void)window; (void)mods; switch(key) { case GLFW_KEY_W: if(action == GLFW_PRESS) { key_flags.forward = true; } else if(action == GLFW_RELEASE) { key_flags.forward = false; } break; case GLFW_KEY_A: if(action == GLFW_PRESS) { key_flags.left = true; } else if(action == GLFW_RELEASE) { key_flags.left = false; } break; case GLFW_KEY_S: if(action == GLFW_PRESS) { key_flags.backward = true; } else if(action == GLFW_RELEASE) { key_flags.backward = false; } break; case GLFW_KEY_D: if(action == GLFW_PRESS) { key_flags.right = true; } else if(action == GLFW_RELEASE) { key_flags.right = false; } break; case GLFW_KEY_SPACE: if(action == GLFW_PRESS) { key_flags.up = true; } else if(action == GLFW_RELEASE) { key_flags.up = false; } break; case GLFW_KEY_LEFT_SHIFT: if(action == GLFW_PRESS) { key_flags.down = true; } else if(action == GLFW_RELEASE) { key_flags.down = false; } break; case GLFW_KEY_RIGHT: if(action == GLFW_PRESS) { key_flags.turn_right = true; } else if(action == GLFW_RELEASE) { key_flags.turn_right = false; } break; case GLFW_KEY_LEFT: if(action == GLFW_PRESS) { key_flags.turn_left = true; } else if(action == GLFW_RELEASE) { key_flags.turn_left = false; } break; case GLFW_KEY_UP: if(action == GLFW_PRESS) { key_flags.turn_up = true; } else if(action == GLFW_RELEASE) { key_flags.turn_up = false; } break; case GLFW_KEY_DOWN: if(action == GLFW_PRESS) { key_flags.turn_down = true; } else if(action == GLFW_RELEASE) { key_flags.turn_down = false; } break; case GLFW_KEY_Q: if(action == GLFW_PRESS) { key_flags.roll_left = true; } else if(action == GLFW_RELEASE) { key_flags.roll_left = false; } break; case GLFW_KEY_E: if(action == GLFW_PRESS) { key_flags.roll_right = true; } else if(action == GLFW_RELEASE) { key_flags.roll_right = false; } break; } } vec3 world_position = {0.0f, 0.0f, 0.0f}; versor world_rotation = {-1.0f, 0.0f, 0.0f, 0.0f}; VkResult update_scene_ubo(void** buffers, uint32_t frame_index, vec3 world_position, versor world_rotation, float aspect_ratio, float time_delta) { vec3 movement_sum = {0.0f, 0.0f, 0.0f}; if(key_flags.forward) { movement_sum[2] -= 1 * time_delta; } if(key_flags.backward) { movement_sum[2] += 1 * time_delta; } if(key_flags.left) { movement_sum[0] -= 1 * time_delta; } if(key_flags.right) { movement_sum[0] += 1 * time_delta; } if(key_flags.up) { movement_sum[1] -= 1 * time_delta; } if(key_flags.down) { movement_sum[1] += 1 * time_delta; } vec3 eular_rotation = {0.0f, 0.0f, 0.0f}; if(key_flags.turn_right) { eular_rotation[0] += 1 * time_delta; } if(key_flags.turn_left) { eular_rotation[0] -= 1 * time_delta; } if(key_flags.turn_up) { eular_rotation[1] -= 1 * time_delta; } if(key_flags.turn_down) { eular_rotation[1] += 1 * time_delta; } if(key_flags.roll_right) { eular_rotation[2] += 1 * time_delta; } if(key_flags.roll_left) { eular_rotation[2] -= 1 * time_delta; } vec3 left = {1.0f, 0.0f, 0.0f}; vec3 up = {0.0f, -1.0f, 0.0f}; vec3 forward = {0.0f, 0.0f, 1.0f}; glm_quat_rotatev(world_rotation, left, left); glm_quat_rotatev(world_rotation, up, up); glm_quat_rotatev(world_rotation, forward, forward); versor relative_rotation_y; glm_quatv(relative_rotation_y, eular_rotation[1], left); versor relative_rotation_x; glm_quatv(relative_rotation_x, eular_rotation[0], up); versor relative_rotation_z; glm_quatv(relative_rotation_z, eular_rotation[2], forward); glm_quat_mul(relative_rotation_x, world_rotation, world_rotation); glm_quat_mul(relative_rotation_y, world_rotation, world_rotation); glm_quat_mul(relative_rotation_z, world_rotation, world_rotation); vec3 movement_rot; glm_quat_rotatev(world_rotation, movement_sum, movement_rot); glm_vec3_add(movement_rot, world_position, world_position); struct SceneUBO ubo = {}; glm_perspective(90.0f, aspect_ratio, 0.1, 100, ubo.proj); glm_quat_look(world_position, world_rotation, ubo.view); memcpy(buffers[frame_index], (void*)&ubo, sizeof(ubo)); return VK_SUCCESS; } VkResult draw_frame(VulkanContext* context) { update_scene_ubo(context->scene_ubo_ptrs, context->current_frame, world_position, world_rotation, (float)context->swapchain_extent.width/(float)context->swapchain_extent.height, 0.01); VkResult result; result = vkWaitForFences(context->device, 1, &context->in_flight_fences[context->current_frame], VK_TRUE, UINT64_MAX); if(result != VK_SUCCESS) { return result; } uint32_t image_index; result = vkAcquireNextImageKHR(context->device, context->swapchain, UINT64_MAX, context->image_available_semaphores[context->current_frame], VK_NULL_HANDLE, &image_index); if(result != VK_SUCCESS) { return result; } result = vkResetFences(context->device, 1, &context->in_flight_fences[context->current_frame]); if(result != VK_SUCCESS) { return result; } result = vkResetCommandBuffer(context->swapchain_command_buffers[context->current_frame], 0); if(result != VK_SUCCESS) { return result; } uint32_t mesh_counts[] = {1}; Mesh* meshes[] = {&context->triangle_mesh_textured}; result = record_command_buffer_scene(1, &context->texture_mesh_material, (uint32_t*)&mesh_counts, (Mesh**)meshes, context->scene_ubo_descriptors[context->current_frame], context->swapchain_command_buffers[context->current_frame], context->render_pass, context->swapchain_framebuffers[image_index], context->swapchain_extent); if(result != VK_SUCCESS) { return result; } VkSubmitInfo submit_info = {}; VkPipelineStageFlags wait_stages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.waitSemaphoreCount = 1; submit_info.pWaitSemaphores = &context->image_available_semaphores[context->current_frame]; submit_info.pWaitDstStageMask = wait_stages; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &context->swapchain_command_buffers[context->current_frame]; submit_info.signalSemaphoreCount = 1; submit_info.pSignalSemaphores = &context->render_finished_semaphores[context->current_frame]; result = vkQueueSubmit(context->queues.graphics, 1, &submit_info, context->in_flight_fences[context->current_frame]); if(result != VK_SUCCESS) { return result; } VkPresentInfoKHR present_info = {}; present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present_info.waitSemaphoreCount = 1; present_info.pWaitSemaphores = &context->render_finished_semaphores[context->current_frame]; present_info.swapchainCount = 1; present_info.pSwapchains = &context->swapchain; present_info.pImageIndices = &image_index; present_info.pResults = 0; return vkQueuePresentKHR(context->queues.present, &present_info); } void main_loop(GLFWwindow* window, VulkanContext* context) { context->current_frame = 0; while(!glfwWindowShouldClose(window)) { glfwPollEvents(); VkResult result = draw_frame(context); if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR) { int width, height; glfwGetWindowSize(window, &width, &height); VkExtent2D window_extent = {width, height}; recreate_swap_chain(context, window_extent); } else if(result != VK_SUCCESS) { fprintf(stderr, "draw_frame error %d\n", result); return; } context->current_frame += 1; if(context->current_frame >= context->max_frames_in_flight) { context->current_frame = 0; } } vkDeviceWaitIdle(context->device); } void cleanup(GLFWwindow* window, VulkanContext* context) { if(context != 0) { if(context->instance != VK_NULL_HANDLE) { if(context->swapchain != VK_NULL_HANDLE) { vkDestroySwapchainKHR(context->device, context->swapchain, 0); } if(context->surface != VK_NULL_HANDLE) { vkDestroySurfaceKHR(context->instance, context->surface, 0); } if(context->device != VK_NULL_HANDLE) { vkDestroyDevice(context->device, 0); } if(context->debug_messenger != VK_NULL_HANDLE) { PFN_vkDestroyDebugUtilsMessengerEXT destroy_messenger = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(context->instance, "vkDestroyDebugUtilsMessengerEXT"); destroy_messenger(context->instance, context->debug_messenger, 0); } vkDestroyInstance(context->instance, 0); } free(context); } if(window != 0) { glfwDestroyWindow(window); glfwTerminate(); } } int main() { GLFWwindow* window = init_window(800, 600); if(window == 0) { fprintf(stderr, "failed to initialize glfw window\n"); return 1; } VulkanContext* context = init_vulkan(window, 2); if (context == 0) { fprintf(stderr, "failed to initialize vulkan context\n"); return 2; } glfwSetKeyCallback(window, key_callback); main_loop(window, context); cleanup(window, context); return 0; }