// vim:fileencoding=utf-8:foldmethod=marker #include "volk/volk.h" #include "vulkan/vulkan_core.h" #include "wapp/wapp.h" #include #define CGLM_FORCE_DEPTH_ZERO_TO_ONE #include "cglm/cglm.h" #include #include #include #include #include #include #include #include #include #include #define WINDOW_WIDTH 1600 #define WINDOW_HEIGHT 1200 #define FRAMES_IN_FLIGHT 3 #define FRAME_TIME 1.0 / 60.0 * 1e+9 #define SHADER_FILE "09_shader_base.spv" #define SHADER_COUNT 2 #define MIN_DEPTH 0.0f #define MAX_DEPTH 1.0f typedef struct { vec3 position; vec3 color; } Vertex; WAPP_DEF_ARRAY_TYPE(VkFence, VkFenceArray); WAPP_DEF_ARRAY_TYPE(VkSemaphore, VkSemaphoreArray); WAPP_DEF_ARRAY_TYPE(VkImage, VkImageArray); WAPP_DEF_ARRAY_TYPE(VkImageView, VkImageViewArray); WAPP_DEF_ARRAY_TYPE(Vertex, VertexArray); enum ReturnCodes { RETURN_SUCCESS, RETURN_FAILED_TO_GET_INSTANCE_EXTENSIONS, RETURN_FAILED_TO_ALLOCATE_EXTENSION_NAMES, RETURN_FAILED_TO_GET_VK_GET_INSTANCE_PROC_ADDR, RETURN_FAILED_TO_INITIALISE_VOLK, RETURN_FAILED_TO_QUERY_API_VERSION, RETURN_FAILED_TO_CREATE_INSTANCE, RETURN_FAILED_TO_CREATE_SURFACE, RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_COUNT, RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_PHYSICAL_DEVICES, RETURN_FAILED_TO_RETRIEVE_PHYSICAL_DEVICES, RETURN_FAILED_TO_FIND_SUITABLE_PHYSICAL_DEVICE, RETURN_NO_QUEUE_FAMILY_PROPERTIES_FOUND, RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_QUEUE_FAMILY_PROPERTIES, RETURN_SELECTED_DEVICE_DOES_NOT_SUPPORT_GRAPHICS, RETURN_FAILED_TO_ENUMERATE_PHYSICAL_DEVICE_EXTENSIONS, RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_EXTENSIONS, RETURN_FAILED_TO_CREATE_LOGICAL_DEVICE, RETURN_FAILED_TO_CREATE_COMMAND_POOL, RETURN_FAILED_TO_ALLOCATE_COMMAND_BUFFER, RETURN_FAILED_TO_QUERY_SURFACE_CAPABILITIES, RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_FORMATS, RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_FORMATS, RETURN_FAILED_TO_QUERY_SURPPORTED_SURFACE_FORMATS, RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_PRESENT_MODES, RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_PRESENT_MODES, RETURN_FAILED_TO_QUERY_SURPPORTED_SURFACE_PRESENT_MODES, RETURN_FAILED_TO_CREATE_SWAPCHAIN, RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGE_COUNT, RETURN_FAILED_TO_ALLOCATE_SWAPCHAIN_IMAGES_MEMORY, RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGES, RETURN_FAILED_TO_CREATE_IMAGE_VIEWS, RETURN_FAILED_TO_CREATE_SYNC_OBJECTS, RETURN_FAILED_TO_CREATE_SHADER_MODULE, RETURN_FAILED_TO_CREATE_DEPTH_IMAGE, RETURN_FAILED_TO_FIND_SUITABLE_MEMORY_TYPE_FOR_DEPTH_IMAGE, RETURN_FAILED_TO_ALLOCATE_DEPTH_IMAGE_MEMORY, RETURN_FAILED_TO_BIND_DEPTH_IMAGE_MEMORY, RETURN_FAILED_TO_CREATE_DEPTH_IMAGE_VIEW, RETURN_FAILED_TO_CREATE_COPY_COMMAND_BUFFER, RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, RETURN_FAILED_TO_CREATE_INDEX_BUFFER, RETURN_FAILED_TO_COPY_VERTEX_AND_INDEX_DATA, RETURN_FAILED_TO_CREATE_PIPELINE_LAYOUT, RETURN_FAILED_TO_CREATE_GRAPHICS_PIPELINE, }; VkInstance instance = VK_NULL_HANDLE; VkPhysicalDevice physical_device = VK_NULL_HANDLE; u32 queue_family_index = VK_QUEUE_FAMILY_IGNORED; VkDevice device = VK_NULL_HANDLE; VkQueue queue = VK_NULL_HANDLE; VkCommandPool command_pool = VK_NULL_HANDLE; VkCommandBuffer command_buffers[FRAMES_IN_FLIGHT] = {0}; VkSurfaceKHR surface = VK_NULL_HANDLE; VkSwapchainKHR swapchain = VK_NULL_HANDLE; VkSurfaceFormatKHR swapchain_format; VkPresentModeKHR swapchain_present_mode; VkImageArray *swapchain_images; VkImageViewArray *swapchain_image_views; VkExtent2D swapchain_extent; u32 image_index; VkImage depth_image = VK_NULL_HANDLE; VkDeviceMemory depth_image_memory = VK_NULL_HANDLE; VkImageView depth_image_view = VK_NULL_HANDLE; VkFormat depth_image_format; VkBuffer vertex_buffer = VK_NULL_HANDLE; VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE; VkBuffer index_buffer = VK_NULL_HANDLE; VkDeviceMemory index_buffer_memory = VK_NULL_HANDLE; VkFenceArray fences = wapp_array_with_capacity(VkFence, VkFenceArray, FRAMES_IN_FLIGHT, true); VkSemaphoreArray present_complete_semaphores = wapp_array_with_capacity(VkSemaphore, VkSemaphoreArray, FRAMES_IN_FLIGHT, true); VkSemaphoreArray render_finished_semaphores = wapp_array_with_capacity(VkSemaphore, VkSemaphoreArray, FRAMES_IN_FLIGHT, true); VkShaderModule shader_module = VK_NULL_HANDLE; VkPipelineLayout pipeline_layout = VK_NULL_HANDLE; VkPipeline graphics_pipeline = VK_NULL_HANDLE; u32 physical_device_count; u64 frame_index = 0; VertexArray vertices = wapp_array(Vertex, VertexArray, {{-0.5f, 0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}}, {{-0.5f, -0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}}, {{-0.5f, 0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}}, {{-0.5f, -0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}}, {{ 0.5f, 0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}}, {{ 0.5f, -0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}}, {{ 0.5f, 0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}}, {{ 0.5f, -0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}}); U32Array indices = wapp_array(u32, U32Array, 0, 2, 3, 3, 1, 0, 6, 4, 5, 5, 7, 6, 4, 0, 1, 1, 5, 4, 2, 6, 7, 7, 3, 2, 0, 4, 6, 6, 2, 0, 3, 7, 5, 5, 1, 3); wapp_intern inline void draw_frame(void); wapp_intern inline void record_command_buffer(void); wapp_intern inline b8 create_buffer(VkPhysicalDeviceMemoryProperties *properties, VkDevice device, VkBufferCreateFlags flags, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memory_properties, VkBuffer *buffer, VkDeviceMemory *buffer_memory); wapp_intern inline void transition_image_layout( VkCommandBuffer command_buffer, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, VkPipelineStageFlags2 src_stage, VkPipelineStageFlags2 dst_stage, VkAccessFlags2 src_access, VkAccessFlags2 dst_access, VkImageAspectFlags aspect_mask ); wapp_intern inline i32 get_memory_type(const VkPhysicalDeviceMemoryProperties *properties, u32 memory_type_bits, VkMemoryPropertyFlags flags); wapp_intern inline u32 set_error(u32 code, const char *msg); int main(void) { // Initialisation {{{ Allocator arena = wapp_mem_arena_allocator_init(MB(256)); u32 status = RETURN_SUCCESS; SDL_Init(SDL_INIT_VIDEO); SDL_Window *window = SDL_CreateWindow("Vulkan", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_VULKAN); PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr(); if (!vkGetInstanceProcAddr) { status = set_error(RETURN_FAILED_TO_GET_VK_GET_INSTANCE_PROC_ADDR, "Failed to get address of vkGetInstanceProcAddr"); goto SDL_CLEANUP; } if (volkInitialize() != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_INITIALISE_VOLK, "Failed to initialise volk"); goto SDL_CLEANUP; } // }}} // Instance creation {{{ u32 sdl_extensions_count; const char *const *sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extensions_count); if (!sdl_extensions) { status = set_error(RETURN_FAILED_TO_GET_INSTANCE_EXTENSIONS, "Failed to get instance extensions"); goto SDL_CLEANUP; } const char *extra_extensions[] = { VK_KHR_SURFACE_EXTENSION_NAME, VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME }; u32 extra_extensions_count = sizeof(extra_extensions) / sizeof(const char *); u32 all_extensions_count = sdl_extensions_count + extra_extensions_count; const char **all_instance_extensions = wapp_mem_allocator_alloc(&arena, all_extensions_count * sizeof(const char *)); if (!all_instance_extensions) { status = set_error(RETURN_FAILED_TO_ALLOCATE_EXTENSION_NAMES, "Failed to allocate memory for instance extensions names"); goto SDL_CLEANUP; } memcpy(all_instance_extensions, sdl_extensions, sdl_extensions_count * sizeof(const char *)); memcpy(&(all_instance_extensions[sdl_extensions_count]), extra_extensions, extra_extensions_count * sizeof(const char *)); u32 api_version; if (vkEnumerateInstanceVersion(&api_version) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_QUERY_API_VERSION, "Failed to query API version"); goto SDL_CLEANUP; } fprintf(stdout, "Vulkan API version: %u.%u.%u.%u\n", VK_API_VERSION_MAJOR(api_version), VK_API_VERSION_MINOR(api_version), VK_API_VERSION_PATCH(api_version), VK_API_VERSION_VARIANT(api_version)); VkApplicationInfo app_info = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pNext = NULL, .pApplicationName = "Vulkan spec test", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "My vulkan engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = api_version, }; VkInstanceCreateInfo instance_create_info = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &app_info, .enabledExtensionCount = all_extensions_count, .ppEnabledExtensionNames = all_instance_extensions, }; if (vkCreateInstance(&instance_create_info, NULL, &instance) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_INSTANCE, "Failed to create vulkan instance"); goto SDL_CLEANUP; } volkLoadInstanceOnly(instance); // }}} // Surface creation {{{ if (!SDL_Vulkan_CreateSurface(window, instance, NULL, &surface)) { status = set_error(RETURN_FAILED_TO_CREATE_SURFACE, "Failed to create surface"); goto DESTROY_VULKAN_INSTANCE; } // }}} // Find suitable physical device {{{ if (vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_COUNT, "Failed to query physical device count"); goto DESTROY_VULKAN_SURFACE; } VkPhysicalDevice *physical_devices = wapp_mem_allocator_alloc(&arena, physical_device_count * sizeof(VkPhysicalDevice)); if (!physical_devices) { status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_PHYSICAL_DEVICES, "Failed to allocate memory for physical devices"); goto DESTROY_VULKAN_SURFACE; } if (vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_RETRIEVE_PHYSICAL_DEVICES, "Failed to retrieve physical devices"); goto DESTROY_VULKAN_SURFACE; } int32_t index = -1; u32 api = 0; for (int32_t i = 0; i < physical_device_count; ++i) { VkPhysicalDeviceProperties2 base_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; vkGetPhysicalDeviceProperties2(physical_devices[i], &base_properties); switch (base_properties.properties.deviceType) { case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { if (base_properties.properties.apiVersion > api) { api = base_properties.properties.apiVersion; index = i; } } break; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: { if (base_properties.properties.apiVersion > api) { api = base_properties.properties.apiVersion; index = i; } } break; default: continue; } } if (index == -1 || api == 0) { status = set_error(RETURN_FAILED_TO_FIND_SUITABLE_PHYSICAL_DEVICE, "Couldn't find suitable physical device"); goto DESTROY_VULKAN_SURFACE; } physical_device = physical_devices[index]; VkPhysicalDeviceProperties2 physical_device_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; VkPhysicalDeviceVulkan11Properties vulkan_11_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES }; VkPhysicalDeviceVulkan12Properties vulkan_12_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES }; VkPhysicalDeviceVulkan13Properties vulkan_13_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES }; VkPhysicalDeviceVulkan14Properties vulkan_14_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES }; void *last = &physical_device_properties; if (api >= VK_API_VERSION_1_1) { last = ((VkPhysicalDeviceProperties2 *)last)->pNext = &vulkan_11_properties; } if (api >= VK_API_VERSION_1_2) { last = ((VkPhysicalDeviceVulkan11Properties *)last)->pNext = &vulkan_12_properties; } if (api >= VK_API_VERSION_1_3) { last = ((VkPhysicalDeviceVulkan12Properties *)last)->pNext = &vulkan_13_properties; } if (api >= VK_API_VERSION_1_4) { last = ((VkPhysicalDeviceVulkan13Properties *)last)->pNext = &vulkan_14_properties; } vkGetPhysicalDeviceProperties2(physical_device, &physical_device_properties); VkPhysicalDeviceMemoryProperties2 memory_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2, }; vkGetPhysicalDeviceMemoryProperties2(physical_device, &memory_properties); // }}} // Find suitable queue family {{{ u32 queue_family_property_count = 0; vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_property_count, NULL); if (queue_family_property_count == 0) { status = set_error(RETURN_NO_QUEUE_FAMILY_PROPERTIES_FOUND, "No queue family properties found"); goto DESTROY_VULKAN_SURFACE; } u32 alloc_size = queue_family_property_count * sizeof(VkQueueFamilyProperties2); VkQueueFamilyProperties2 *queue_family_properties = wapp_mem_allocator_alloc(&arena, alloc_size); if (!queue_family_properties) { status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_QUEUE_FAMILY_PROPERTIES, "Failed to allocate memory for queue family properties"); goto DESTROY_VULKAN_SURFACE; } memset(queue_family_properties, 0, alloc_size); for (u32 i = 0; i < queue_family_property_count; ++i) { queue_family_properties[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2; } vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_property_count, queue_family_properties); b8 supports_graphics = false; for (u32 i = 0; i < queue_family_property_count; ++i) { supports_graphics = supports_graphics || (queue_family_properties[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) == VK_QUEUE_GRAPHICS_BIT; if (supports_graphics) { queue_family_index = i; break; } } if (!supports_graphics) { status = set_error(RETURN_SELECTED_DEVICE_DOES_NOT_SUPPORT_GRAPHICS, "Selected physical device doesn't support graphics operations"); goto DESTROY_VULKAN_SURFACE; } // }}} // Logical device creation {{{ VkPhysicalDeviceShaderObjectFeaturesEXT shader_object_feature = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT, .shaderObject = VK_TRUE, }; VkPhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifo_latest_ready_feature = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_KHR, .pNext = &shader_object_feature, .presentModeFifoLatestReady = VK_TRUE, }; VkPhysicalDeviceVulkan14Features vulkan_14_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES, .pNext = &fifo_latest_ready_feature, .bresenhamLines = VK_TRUE, .smoothLines = VK_TRUE, .pushDescriptor = VK_TRUE, }; VkPhysicalDeviceVulkan13Features vulkan_13_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES, .pNext = &vulkan_14_features, .dynamicRendering = VK_TRUE, .synchronization2 = VK_TRUE, }; VkPhysicalDeviceVulkan12Features vulkan_12_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES, .pNext = &vulkan_13_features, .timelineSemaphore = VK_TRUE, }; VkPhysicalDeviceVulkan11Features vulkan_11_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES, .pNext = &vulkan_12_features, .shaderDrawParameters = VK_TRUE, }; VkPhysicalDeviceFeatures core_features = { .logicOp = VK_TRUE, .fillModeNonSolid = VK_TRUE, .samplerAnisotropy = VK_TRUE, }; VkPhysicalDeviceFeatures2 device_features = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2, .pNext = &vulkan_11_features, .features = core_features, }; f32 priorities[] = {0.5}; VkDeviceQueueCreateInfo queue_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = queue_family_index, .queueCount = 1, .pQueuePriorities = priorities, }; const char *device_extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, VK_EXT_SHADER_OBJECT_EXTENSION_NAME, VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME, VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME, }; u32 device_extensions_count = sizeof(device_extensions) / sizeof(const char *); VkDeviceCreateInfo device_create_info = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pNext = &device_features, .queueCreateInfoCount = 1, .pQueueCreateInfos = &queue_create_info, .enabledExtensionCount = device_extensions_count, .ppEnabledExtensionNames = device_extensions, }; u32 dev_ext_count; if (vkEnumerateDeviceExtensionProperties(physical_device, NULL, &dev_ext_count, NULL) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_ENUMERATE_PHYSICAL_DEVICE_EXTENSIONS, "Failed to enumerate physical device extensions"); goto DESTROY_VULKAN_SURFACE; } VkExtensionProperties *extension_properties = wapp_mem_allocator_alloc(&arena, dev_ext_count * sizeof(VkExtensionProperties)); if (vkEnumerateDeviceExtensionProperties(physical_device, NULL, &dev_ext_count, extension_properties) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_EXTENSIONS, "Failed to query physical device extensions"); goto DESTROY_VULKAN_SURFACE; } for (u32 i = 0; i < dev_ext_count; ++i) { if (memcmp(extension_properties[i].extensionName, VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME, sizeof(VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME)) == 0) { device_extensions[device_extensions_count - 1] = VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME; break; } } if (vkCreateDevice(physical_device, &device_create_info, NULL, &device) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_LOGICAL_DEVICE, "Failed to create device"); goto DESTROY_VULKAN_SURFACE; } volkLoadDevice(device); /// }}} // Get device queue {{{ vkGetDeviceQueue(device, queue_family_index, 0, &queue); // }}} // Create command pool {{{ VkCommandPoolCreateInfo pool_create_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = queue_family_index, }; if (vkCreateCommandPool(device, &pool_create_info, NULL, &command_pool) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_COMMAND_POOL, "Failed to create command pool"); goto DESTROY_VULKAN_DEVICE; } // }}} // Allocate command buffers {{{ VkCommandBufferAllocateInfo cmd_buf_alloc_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandBufferCount = 1, .commandPool = command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, }; for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) { if (vkAllocateCommandBuffers(device, &cmd_buf_alloc_info, &command_buffers[i]) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_ALLOCATE_COMMAND_BUFFER, "Failed to allocate command buffer"); goto FREE_COMMAND_BUFFER; } } // }}} // Create swapchain {{{ VkSurfaceCapabilitiesKHR capabilities = {0}; if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_QUERY_SURFACE_CAPABILITIES, "Failed to query surface capabilities"); goto FREE_COMMAND_BUFFER; } swapchain_extent = capabilities.currentExtent; u32 format_count; if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, NULL) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_FORMATS, "Failed to enumerate supported surface formats"); goto FREE_COMMAND_BUFFER; } VkSurfaceFormatKHR *formats = wapp_mem_allocator_alloc(&arena, format_count * sizeof(VkSurfaceFormatKHR)); if (!formats) { status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_FORMATS, "Failed to allocate memory for surface formats"); goto FREE_COMMAND_BUFFER; } if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_QUERY_SURPPORTED_SURFACE_FORMATS, "Failed to query supported surface formats"); goto FREE_COMMAND_BUFFER; } VkSurfaceFormatKHR *format = NULL; for (u32 i = 0; i < format_count; ++i) { if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && formats[i].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) { format = &(formats[i]); break; } } if (!format) { format = &formats[0]; } swapchain_format = *format; u32 present_mode_count; if (vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count, NULL) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_PRESENT_MODES, "Failed to enumerate supported surface present modes"); goto FREE_COMMAND_BUFFER; } VkPresentModeKHR *present_modes = wapp_mem_allocator_alloc(&arena, present_mode_count * sizeof(VkPresentModeKHR)); if (!present_modes) { status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_PRESENT_MODES, "Failed to allocate memory for surface present modes"); goto FREE_COMMAND_BUFFER; } swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR; for (u32 i = 0; i < present_mode_count; ++i) { if (present_modes[i] == VK_PRESENT_MODE_FIFO_LATEST_READY_KHR) { swapchain_present_mode = present_modes[i]; break; } } VkSwapchainCreateInfoKHR swapchain_info = { .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = surface, .minImageCount = capabilities.minImageCount, .imageFormat = swapchain_format.format, .imageColorSpace = swapchain_format.colorSpace, .imageExtent = capabilities.currentExtent, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .imageSharingMode = VK_SHARING_MODE_EXCLUSIVE, .queueFamilyIndexCount = 1, .pQueueFamilyIndices = &queue_family_index, .preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = swapchain_present_mode, }; if (vkCreateSwapchainKHR(device, &swapchain_info, NULL, &swapchain) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_SWAPCHAIN, "Failed to create swapchain"); goto FREE_COMMAND_BUFFER; } // }}} // Get swapchain images {{{ u32 image_count; if (vkGetSwapchainImagesKHR(device, swapchain, &image_count, NULL) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGE_COUNT, "Failed to get swapchain images count"); goto DESTROY_SWAPCHAIN; } swapchain_images = wapp_array_alloc_capacity(VkImage, VkImageArray, &arena, image_count, true); swapchain_image_views = wapp_array_alloc_capacity(VkImageView, VkImageViewArray, &arena, image_count, false); if (!swapchain_images || !swapchain_image_views) { status = set_error(RETURN_FAILED_TO_ALLOCATE_SWAPCHAIN_IMAGES_MEMORY, "Failed to allocate swapchain images memory"); goto DESTROY_SWAPCHAIN; } if (vkGetSwapchainImagesKHR(device, swapchain, (u32 *)&(swapchain_images->count), swapchain_images->items) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGES, "Failed to get swapchain images"); goto DESTROY_SWAPCHAIN; } // }}} // Create swapchain image views {{{ for (u32 i = 0; i < swapchain_images->count; ++i) { VkImageViewCreateInfo view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = *wapp_array_get(VkImage, swapchain_images, i), .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = swapchain_format.format, .subresourceRange = { .levelCount = 1, .baseMipLevel = 0, .layerCount = 1, .baseArrayLayer = 0, .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, }, }; VkImageView view; if (vkCreateImageView(device, &view_info, NULL, &view) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_IMAGE_VIEWS, "Failed to create image views"); goto DESTROY_IMAGE_VIEWS; } wapp_array_append_capped(VkImageView, swapchain_image_views, &view); } // }}} // Create synchronization objects {{{ VkFenceCreateInfo fence_info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; VkSemaphoreCreateInfo semaphore_info = { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) { if (vkCreateFence(device, &fence_info, NULL, wapp_array_get(VkFence, &fences, i)) != VK_SUCCESS || vkCreateSemaphore(device, &semaphore_info, NULL, wapp_array_get(VkSemaphore, &present_complete_semaphores, i)) != VK_SUCCESS || vkCreateSemaphore(device, &semaphore_info, NULL, wapp_array_get(VkSemaphore, &render_finished_semaphores, i)) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_SYNC_OBJECTS, "Failed to create sync objects"); goto DESTROY_SYNC_OBJECTS; } } // }}} // Create shader module {{{ FILE *shader_file = fopen(SHADER_FILE, "rb"); fseek(shader_file, 0, SEEK_END); u64 length = ftell(shader_file); fseek(shader_file, 0, SEEK_SET); u8 *code = wapp_mem_allocator_alloc(&arena, length * sizeof(u8)); fread(code, sizeof(u8), length, shader_file); VkShaderModuleCreateInfo module_info = { .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = length, .pCode = (u32 *)code, }; if (vkCreateShaderModule(device, &module_info, NULL, &shader_module) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_SHADER_MODULE, "Failed to create shader module"); goto DESTROY_SYNC_OBJECTS; } fclose(shader_file); // }}} // Create depth resources {{{ depth_image_format = VK_FORMAT_D32_SFLOAT_S8_UINT; VkExtent3D depth_image_extent = { .width = swapchain_extent.width, .height = swapchain_extent.height, .depth = 1 }; VkImageCreateInfo depth_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .format = depth_image_format, .extent = depth_image_extent, .mipLevels = 1, .arrayLayers = 1, .samples = VK_SAMPLE_COUNT_1_BIT, .tiling = VK_IMAGE_TILING_OPTIMAL, .usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; if (vkCreateImage(device, &depth_info, NULL, &depth_image) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_DEPTH_IMAGE, "Failed to create depth image"); goto DESTROY_DEPTH_RESOURCES; } VkMemoryRequirements depth_memory_requirements = {0}; vkGetImageMemoryRequirements(device, depth_image, &depth_memory_requirements); i32 memory_type_index = get_memory_type(&memory_properties.memoryProperties, depth_memory_requirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if (memory_type_index == -1) { status = set_error(RETURN_FAILED_TO_FIND_SUITABLE_MEMORY_TYPE_FOR_DEPTH_IMAGE, "Failed to find suitable memory type for depth image"); goto DESTROY_DEPTH_RESOURCES; } VkMemoryPriorityAllocateInfoEXT allocation_priority = { .sType = VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT, .priority = 1.0f, }; VkMemoryAllocateInfo depth_memory_alloc_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &allocation_priority, .allocationSize = depth_memory_requirements.size, .memoryTypeIndex = (u32)memory_type_index, }; if (vkAllocateMemory(device, &depth_memory_alloc_info, NULL, &depth_image_memory) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_ALLOCATE_DEPTH_IMAGE_MEMORY, "Failed to allocate depth image memory"); goto DESTROY_DEPTH_RESOURCES; } if (vkBindImageMemory(device, depth_image, depth_image_memory, 0) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_BIND_DEPTH_IMAGE_MEMORY, "Failed to bind depth image memory"); goto DESTROY_DEPTH_RESOURCES; } VkImageViewCreateInfo depth_view_info = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = depth_image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = depth_image_format, .subresourceRange = (VkImageSubresourceRange){ .aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; if (vkCreateImageView(device, &depth_view_info, NULL, &depth_image_view) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_DEPTH_IMAGE_VIEW, "Failed to create depth image view"); goto DESTROY_DEPTH_RESOURCES; } // }}} // Create copying command buffer and fence {{{ VkFence copy_fence = VK_NULL_HANDLE; VkFenceCreateInfo copy_fence_info = { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, }; if (vkCreateFence(device, ©_fence_info, NULL, ©_fence) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_COPY_COMMAND_BUFFER, "Failed to create copy command buffer"); goto FREE_COPY_BUFFER; } VkCommandBuffer copy_buffer = VK_NULL_HANDLE; VkCommandBufferAllocateInfo copy_buffer_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = command_pool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; if (vkAllocateCommandBuffers(device, ©_buffer_info, ©_buffer) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_COPY_COMMAND_BUFFER, "Failed to create copy command buffer"); goto FREE_COPY_BUFFER; } // }}} VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(copy_buffer, &begin_info); // Create vertex buffer {{{ u64 vertex_buffer_size = sizeof(Vertex) * vertices.count; VkBuffer vertex_staging = VK_NULL_HANDLE; VkDeviceMemory vertex_staging_memory = VK_NULL_HANDLE; if (!create_buffer(&memory_properties.memoryProperties, device, 0, vertex_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &vertex_staging, &vertex_staging_memory)) { status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create vertex buffer"); goto DESTROY_VERTEX_BUFFER; } void *vertex_staging_mapped = NULL; if (vkMapMemory(device, vertex_staging_memory, 0, vertex_buffer_size, 0, &vertex_staging_mapped) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create vertex buffer"); goto DESTROY_VERTEX_BUFFER; } memcpy(vertex_staging_mapped, vertices.items, vertex_buffer_size); vkUnmapMemory(device, vertex_staging_memory); if (!create_buffer(&memory_properties.memoryProperties, device, 0, vertex_buffer_size, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertex_buffer, &vertex_buffer_memory)) { status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create vertex buffer"); goto DESTROY_VERTEX_BUFFER; } VkBufferCopy vertex_buffer_copy = { .srcOffset = 0, .dstOffset = 0, .size = vertex_buffer_size, }; vkCmdCopyBuffer(copy_buffer, vertex_staging, vertex_buffer, 1, &vertex_buffer_copy); // }}} // Create index buffer {{{ u64 index_buffer_size = sizeof(u32) * indices.count; VkBuffer index_staging = VK_NULL_HANDLE; VkDeviceMemory index_staging_memory = VK_NULL_HANDLE; if (!create_buffer(&memory_properties.memoryProperties, device, 0, index_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &index_staging, &index_staging_memory)) { status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create index buffer"); goto DESTROY_INDEX_BUFFER; } void *index_staging_mapped = NULL; if (vkMapMemory(device, index_staging_memory, 0, index_buffer_size, 0, &index_staging_mapped) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create index buffer"); goto DESTROY_INDEX_BUFFER; } memcpy(index_staging_mapped, indices.items, index_buffer_size); vkUnmapMemory(device, index_staging_memory); if (!create_buffer(&memory_properties.memoryProperties, device, 0, index_buffer_size, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &index_buffer, &index_buffer_memory)) { status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create index buffer"); goto DESTROY_INDEX_BUFFER; } VkBufferCopy index_buffer_copy = { .srcOffset = 0, .dstOffset = 0, .size = index_buffer_size, }; vkCmdCopyBuffer(copy_buffer, index_staging, index_buffer, 1, &index_buffer_copy); // }}} vkEndCommandBuffer(copy_buffer); // Submit copying vertex and index buffers {{{ VkSubmitInfo copy_submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = ©_buffer, }; if (vkQueueSubmit(queue, 1, ©_submit_info, copy_fence) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_COPY_VERTEX_AND_INDEX_DATA, "Failed to copy vertex and index data"); goto DESTROY_INDEX_BUFFER; } while (vkWaitForFences(device, 1, ©_fence, VK_TRUE, UINT64_MAX) == VK_TIMEOUT) {} // }}} // Create graphics pipeline {{{ VkPipelineShaderStageCreateInfo shader_stages[SHADER_COUNT] = { (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = shader_module, .pName = "vertMain", }, (VkPipelineShaderStageCreateInfo){ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = shader_module, .pName = "fragMain", }, }; VkVertexInputBindingDescription vertex_binding = { .binding = 0, .stride = sizeof(Vertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }; VkVertexInputAttributeDescription vertex_attributes[] = { (VkVertexInputAttributeDescription){ .location = 0, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, position), }, (VkVertexInputAttributeDescription){ .location = 1, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, color), }, }; u64 attributes_count = sizeof(vertex_attributes) / sizeof(vertex_attributes[0]); VkPipelineVertexInputStateCreateInfo vertex_input = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &vertex_binding, .vertexAttributeDescriptionCount = attributes_count, .pVertexAttributeDescriptions = vertex_attributes, }; VkPipelineInputAssemblyStateCreateInfo input_assembly = { .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, }; VkPipelineRasterizationStateCreateInfo rasterisation = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_CLOCKWISE, .lineWidth = 1.0f, }; VkPipelineMultisampleStateCreateInfo multisampling = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, }; VkPipelineDepthStencilStateCreateInfo depth_stencil = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS, }; VkPipelineColorBlendAttachmentState blend_state = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, }; VkPipelineColorBlendStateCreateInfo blending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_TRUE, .logicOp = VK_LOGIC_OP_COPY, .attachmentCount = 1, .pAttachments = &blend_state, }; VkDynamicState dynamic_states[2] = {VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT, VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT}; VkPipelineDynamicStateCreateInfo dynamic_state = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount = 2, .pDynamicStates = dynamic_states, }; VkPipelineLayoutCreateInfo layout_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, }; if (vkCreatePipelineLayout(device, &layout_info, NULL, &pipeline_layout) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_PIPELINE_LAYOUT, "Failed to create pipeline layout"); goto DESTROY_DEPTH_RESOURCES; } VkFormat color_attachment_format = swapchain_format.format; VkPipelineRenderingCreateInfo rendering_info = { .sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO, .colorAttachmentCount = 1, .pColorAttachmentFormats = &color_attachment_format, .depthAttachmentFormat = depth_image_format, .stencilAttachmentFormat = depth_image_format, }; VkGraphicsPipelineCreateInfo pipeline_info = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .pNext = &rendering_info, .stageCount = SHADER_COUNT, .pStages = shader_stages, .pVertexInputState = &vertex_input, .pInputAssemblyState = &input_assembly, .pRasterizationState = &rasterisation, .pMultisampleState = &multisampling, .pDepthStencilState = &depth_stencil, .pColorBlendState = &blending, .pDynamicState = &dynamic_state, .layout = pipeline_layout, }; if (vkCreateGraphicsPipelines(device, NULL, 1, &pipeline_info, NULL, &graphics_pipeline) != VK_SUCCESS) { status = set_error(RETURN_FAILED_TO_CREATE_GRAPHICS_PIPELINE, "Failed to create graphics pipeline"); goto DESTROY_PIPELINE_LAYOUT; } // }}} // Main loop {{{ u64 frame_start, frame_time; b8 running = true; SDL_Event event = {0}; while (running) { frame_start = SDL_GetTicksNS(); while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: { running = false; } break; case SDL_EVENT_KEY_DOWN: { if (event.key.key == SDLK_ESCAPE) { running = false; } } break; } } draw_frame(); frame_time = SDL_GetTicksNS() - frame_start; if (frame_time < FRAME_TIME) { SDL_DelayNS(FRAME_TIME - frame_time); } } // }}} // Cleanup {{{ vkDeviceWaitIdle(device); DESTROY_GRAPHICS_PIPELINE: vkDestroyPipeline(device, graphics_pipeline, NULL); DESTROY_PIPELINE_LAYOUT: vkDestroyPipelineLayout(device, pipeline_layout, NULL); DESTROY_INDEX_BUFFER: if (index_staging != VK_NULL_HANDLE) { vkDestroyBuffer(device, index_staging, NULL); } if (index_staging_memory != VK_NULL_HANDLE) { vkFreeMemory(device, index_staging_memory, NULL); } if (index_buffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, index_buffer, NULL); } if (index_buffer_memory != VK_NULL_HANDLE) { vkFreeMemory(device, index_buffer_memory, NULL); } DESTROY_VERTEX_BUFFER: if (vertex_staging != VK_NULL_HANDLE) { vkDestroyBuffer(device, vertex_staging, NULL); } if (vertex_staging_memory != VK_NULL_HANDLE) { vkFreeMemory(device, vertex_staging_memory, NULL); } if (vertex_buffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, vertex_buffer, NULL); } if (vertex_buffer_memory != VK_NULL_HANDLE) { vkFreeMemory(device, vertex_buffer_memory, NULL); } FREE_COPY_BUFFER: if (copy_fence != VK_NULL_HANDLE) { vkDestroyFence(device, copy_fence, NULL); } if (copy_buffer != VK_NULL_HANDLE) { vkFreeCommandBuffers(device, command_pool, 1, ©_buffer); } DESTROY_DEPTH_RESOURCES: if (depth_image_view != VK_NULL_HANDLE) { vkDestroyImageView(device, depth_image_view, NULL); } if (depth_image_memory != VK_NULL_HANDLE) { vkFreeMemory(device, depth_image_memory, NULL); } if (depth_image != VK_NULL_HANDLE) { vkDestroyImage(device, depth_image, NULL); } DESTROY_SHADER_MODULE: if (shader_module != VK_NULL_HANDLE) { vkDestroyShaderModule(device, shader_module, NULL); } DESTROY_SYNC_OBJECTS: for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) { if (wapp_array_get(VkFence, &fences, i) != VK_NULL_HANDLE) { vkDestroyFence(device, *wapp_array_get(VkFence, &fences, i), NULL); } if (wapp_array_get(VkSemaphore, &present_complete_semaphores, i) != VK_NULL_HANDLE) { vkDestroySemaphore(device, *wapp_array_get(VkSemaphore, &present_complete_semaphores, i), NULL); } if (wapp_array_get(VkSemaphore, &render_finished_semaphores, i) != VK_NULL_HANDLE) { vkDestroySemaphore(device, *wapp_array_get(VkSemaphore, &render_finished_semaphores, i), NULL); } } DESTROY_IMAGE_VIEWS: for (u32 i = 0; i < swapchain_images->count; ++i) { VkImageView view = *wapp_array_get(VkImageView, swapchain_image_views, i); if (view != VK_NULL_HANDLE) { vkDestroyImageView(device, view, NULL); } } DESTROY_SWAPCHAIN: vkDestroySwapchainKHR(device, swapchain, NULL); FREE_COMMAND_BUFFER: for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) { if (command_buffers[i] != VK_NULL_HANDLE) { vkFreeCommandBuffers(device, command_pool, 1, &command_buffers[i]); } } DESTROY_COMMAND_POOL: vkDestroyCommandPool(device, command_pool, NULL); DESTROY_VULKAN_DEVICE: vkDestroyDevice(device, NULL); DESTROY_VULKAN_SURFACE: SDL_Vulkan_DestroySurface(instance, surface, NULL); DESTROY_VULKAN_INSTANCE: vkDestroyInstance(instance, NULL); SDL_CLEANUP: SDL_DestroyWindow(window); SDL_Quit(); wapp_mem_arena_allocator_destroy(&arena); // }}} return status; } static inline void draw_frame(void) { // Ensure queue isn't still rendering using objects for the current frame_index {{{ while (vkWaitForFences(device, 1, wapp_array_get(VkFence, &fences, frame_index), VK_TRUE, UINT64_MAX) == VK_TIMEOUT) {} vkResetFences(device, 1, wapp_array_get(VkFence, &fences, frame_index)); // }}} vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, *wapp_array_get(VkSemaphore, &present_complete_semaphores, frame_index), VK_NULL_HANDLE, &image_index); record_command_buffer(); // Submit command buffer to queue {{{ VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; VkSubmitInfo submit_info = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = wapp_array_get(VkSemaphore, &present_complete_semaphores, frame_index), .commandBufferCount = 1, .pCommandBuffers = &command_buffers[frame_index], .pWaitDstStageMask = &wait_stage, .signalSemaphoreCount = 1, .pSignalSemaphores = wapp_array_get(VkSemaphore, &render_finished_semaphores, frame_index), }; vkQueueSubmit(queue, 1, &submit_info, *wapp_array_get(VkFence, &fences, frame_index)); // }}} // Present the swapchain image when rendering is done {{{ VkPresentInfoKHR present_info = { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = wapp_array_get(VkSemaphore, &render_finished_semaphores, frame_index), .swapchainCount = 1, .pSwapchains = &(swapchain), .pImageIndices = &image_index, }; vkQueuePresentKHR(queue, &present_info); // }}} frame_index = (frame_index + 1) % FRAMES_IN_FLIGHT; } static inline void record_command_buffer() { VkCommandBufferBeginInfo begin_info = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(command_buffers[frame_index], &begin_info); // Transition image layout for rendering {{{ transition_image_layout( command_buffers[frame_index], *wapp_array_get(VkImage, swapchain_images, image_index), VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_ACCESS_NONE, VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_COLOR_BIT ); transition_image_layout( command_buffers[frame_index], depth_image, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT ); // }}} // Dynamic rendering {{{ vkCmdBindPipeline(command_buffers[frame_index], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline); VkDeviceSize vertex_buffer_offset = 0; vkCmdBindVertexBuffers(command_buffers[frame_index], 0, 1, &vertex_buffer, &vertex_buffer_offset); vkCmdBindIndexBuffer(command_buffers[frame_index], index_buffer, 0, VK_INDEX_TYPE_UINT32); VkViewport viewport = { .x = 0, .y = 0, .width = swapchain_extent.width, .height = swapchain_extent.height, .minDepth = MIN_DEPTH, .maxDepth = MAX_DEPTH, }; vkCmdSetViewportWithCount(command_buffers[frame_index], 1, &viewport); VkRect2D scissor = { .offset = (VkOffset2D){ .x = 0, .y = 0 }, .extent = swapchain_extent, }; vkCmdSetScissorWithCount(command_buffers[frame_index], 1, &scissor); VkClearValue color_clear = { .color = (VkClearColorValue){ .float32 = {0.0f, 0.0f, 0.0f, 0.0f} } }; VkRenderingAttachmentInfo color_attachment = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = *wapp_array_get(VkImageView, swapchain_image_views, image_index), .imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .clearValue = color_clear }; VkClearValue depth_clear = { .depthStencil = (VkClearDepthStencilValue){ .depth = MAX_DEPTH } }; VkRenderingAttachmentInfo depth_attachment = { .sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO, .imageView = depth_image_view, .imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .clearValue = depth_clear }; VkRect2D render_area = { .offset = (VkOffset2D){.x = 0, .y = 0}, .extent = swapchain_extent, }; VkRenderingInfo rendering_info = { .sType = VK_STRUCTURE_TYPE_RENDERING_INFO, .renderArea = render_area, .layerCount = 1, .colorAttachmentCount = 1, .pColorAttachments = &color_attachment, .pDepthAttachment = &depth_attachment }; vkCmdBeginRendering(command_buffers[frame_index], &rendering_info); vkCmdDrawIndexed(command_buffers[frame_index], indices.count, 1, 0, 0, 0); vkCmdEndRendering(command_buffers[frame_index]); // }}} // Transition image layout for presentation {{{ transition_image_layout( command_buffers[frame_index], *wapp_array_get(VkImage, swapchain_images, image_index), VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT, VK_ACCESS_2_NONE, VK_IMAGE_ASPECT_COLOR_BIT ); // }}} vkEndCommandBuffer(command_buffers[frame_index]); } wapp_intern inline b8 create_buffer(VkPhysicalDeviceMemoryProperties *properties, VkDevice device, VkBufferCreateFlags flags, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags memory_properties, VkBuffer *buffer, VkDeviceMemory *buffer_memory) { VkBufferCreateInfo buffer_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .flags = flags, .size = size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; if (vkCreateBuffer(device, &buffer_info, NULL, buffer) != VK_SUCCESS) { return false; } VkBufferMemoryRequirementsInfo2 requirements_info = { .sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2, .buffer = *buffer, }; VkMemoryRequirements2 requirements = { .sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2, }; vkGetBufferMemoryRequirements2(device, &requirements_info, &requirements); i32 memory_type = get_memory_type(properties, requirements.memoryRequirements.memoryTypeBits, memory_properties); if (memory_type < 0) { return false; } VkMemoryPriorityAllocateInfoEXT priority = { .sType = VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT, .priority = 0.5f, }; VkMemoryAllocateInfo alloc_info = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .pNext = &priority, .memoryTypeIndex = memory_type, .allocationSize = requirements.memoryRequirements.size, }; if (vkAllocateMemory(device, &alloc_info, NULL, buffer_memory) != VK_SUCCESS) { return false; } if (vkBindBufferMemory(device, *buffer, *buffer_memory, 0) != VK_SUCCESS) { return false; } return true; } wapp_intern inline void transition_image_layout( VkCommandBuffer command_buffer, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout, VkPipelineStageFlags2 src_stage, VkPipelineStageFlags2 dst_stage, VkAccessFlags2 src_access, VkAccessFlags2 dst_access, VkImageAspectFlags aspect_mask ) { VkImageMemoryBarrier2 barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2, .image = image, .oldLayout = old_layout, .newLayout = new_layout, .srcStageMask = src_stage, .dstStageMask = dst_stage, .srcAccessMask = src_access, .dstAccessMask = dst_access, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .subresourceRange = { .aspectMask = aspect_mask, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, }, }; VkDependencyInfo dependency = { .sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO, .imageMemoryBarrierCount = 1, .pImageMemoryBarriers = &barrier, }; vkCmdPipelineBarrier2(command_buffer, &dependency); } wapp_intern inline i32 get_memory_type(const VkPhysicalDeviceMemoryProperties *properties, u32 memory_type_bits, VkMemoryPropertyFlags flags) { for (u32 i = 0; i < properties->memoryTypeCount; ++i) { u32 type = (u32)1 << i; b8 is_type = (type & memory_type_bits) == type; if (!is_type) { continue; } b8 has_properties = (flags & properties->memoryTypes[i].propertyFlags) == flags; if (has_properties) { return (i32)i; } } return -1; } wapp_intern inline u32 set_error(u32 code, const char *msg) { fprintf(stderr, "%s\n", msg); return code; }