/* Second Attempt at starting vulkan-tutorial. Goal: Keep things simple, Use as much C as possible. Don't do anything stupid or fancy with the code. I created my own basic string helpers. I reused my arena helpers for memory allocation. This allowed me to avoid polluting lifetimes. I have a few notes on that. I used a temporary arena and I use the calls `temp_arena_begin` and `temp_arena_end` to use that. I have found a simpler approach though. In block scopes {} / functions, I just create an Arena variable via copy. This gets cleaned up when the scope ends and I don't have to write temp_arena_begin or temp_arena_end as there is some additional things I have to cater to with that. */ /* Progress Notes: Hello Triangle Lesson Completed - setup chapter completed. - Presentation chapter completed - Graphics Pipeline chapter completed - Drawing chapter completed. - Swapchain recreation (resizing completed) */ /* * @research: * 1. why does mapping all colors to red channel give me a monochrome image? * 2. what is a render target? * 3. read about framebuffers a bit: https://docs.vulkan.org/spec/latest/chapters/framebuffer.html * 4. I am interested in learning about the difference between renderpasses and dynamic rendering: https://www.team-nutshell.dev/nutshellengine/articles/vulkan-renderpass.html * 5. I am trying to see how I can get VK_SUBOPTIMAL_KHR surface issues to show up. I have not * handled them currently, and unless I get to see what exactly changes in their case, I cannot. * Most likely I would have to rework a small amount of functionality. * 6. Regarding resize swapchain, create swapchain, swapchain image view, framebuffer * When do items like surface format, surface capabilities and surface present change dynamically, * due to window events * 7. resize is broken on linux, no idea why. It could just be linux being linux but like, * the resize window is very choppy/laggy on wayland and the resize operation is laggy on x11. * Meanwhile, everything works correctly on W11. */ #include #include #include #include #include #include #include #include #include #include "SDL2/SDL.h" #include "SDL2/SDL_timer.h" #include "SDL2/SDL_video.h" #include "SDL2/SDL_vulkan.h" #include "vulkan/vulkan.h" #include "glm/glm.hpp" // Start utils typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; typedef uint64_t u64; typedef int8_t s8; typedef int16_t s16; typedef int32_t s32; typedef int64_t s64; typedef float r32; typedef double r64; typedef u8 b8; typedef unsigned char uchar; #define KB(x) ((x)*1024U) #define MB(x) ((x)*KB(1024U)) #define GB(x) ((x)*MB(1024U)) #define internal static #ifdef NDEBUG #define assert(x) #define SDL_assert(x) #else #endif #include "arena.h" struct Vertex { glm::vec2 pos; glm::vec3 col; }; // string struct Str256 { char buffer[256]; u32 len; }; typedef struct Str256 Str256; internal Str256 str256(const char* str) { Str256 res = {}; u32 i = 0; while (str[i] != '\0' && i < 256) { res.buffer[i] = str[i]; res.len++; i++; } return res; } internal bool str256_match(Str256 a, Str256 b) { if (a.len != b.len) { return false; } return memcmp(a.buffer, b.buffer, a.len) == 0; } #define ARR_LEN(x) ((x) ? (sizeof((x))/sizeof((x)[0])) : 0) u32 clamp_u32(u32 val, u32 bot, u32 top) { if (val < bot) { return bot; } else if (val > top) { return top; } return val; } // Alternate generic way to define VK arrays. // This is separate because VK Image and ImageViews use u32 size // Example: // DefineArrVk(VkImage); // DefineArrVk(VkImageView); #define DefineArrVk(Type) \ struct Arr##Type { u32 size; Type *buffer; }; DefineArrVk(VkImage) DefineArrVk(VkImageView) DefineArrVk(VkFramebuffer) struct ArrUChar { size_t size; uchar *buffer; }; // END utils #ifdef NDEBUG bool enable_validation_layers = false; const char **required_validation_layers = nullptr; const char **required_extensions_internal = nullptr; #else bool enable_validation_layers = true; const char* required_extensions_internal[] = { VK_EXT_DEBUG_UTILS_EXTENSION_NAME }; const char *required_validation_layers[] = { "VK_LAYER_KHRONOS_validation" }; #endif struct MegaState { Arena arena; SDL_Window* window; VkInstance vk_instance; VkPhysicalDevice vk_physical_device; s32 queue_family_gfx_index; s32 queue_family_present_index; VkDevice vk_device; VkQueue vk_queue_gfx; VkQueue vk_queue_present; VkSurfaceKHR vk_khr_surface; // surface attributes VkSurfaceCapabilitiesKHR surface_capabilities; VkSurfaceFormatKHR surface_format; VkPresentModeKHR present_mode; VkExtent2D surface_resolution; u32 surface_image_count; VkSwapchainKHR vk_khr_swapchain; ArrVkImage vk_swapchain_images; ArrVkImageView vk_swapchain_image_views; ArrVkFramebuffer vk_framebuffers; VkRenderPass vk_render_pass; VkBuffer vk_staging_vertex_buffer; VkDeviceMemory vk_mem_staging_vertex_buffer; VkBuffer vk_vertex_buffer; VkDeviceMemory vk_mem_vertex_buffer; }; internal b8 resizable = 0; const u32 MAX_FRAMES_IN_FLIGHT = 2; internal const char* required_device_extensions[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData ) { if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { printf("VK_VALIDATION_MESSAGE :: %s\n\n", pCallbackData->pMessage); } return VK_FALSE; } u32 resize_swapchain(MegaState* state) { /* @todo: implement this functionality as I guess extra practise. * biggest challenge I see here is clearing up the lifetimes * REF: vulkan-tutorial * the disadvantage of this * approach is that we need to stop all rendering before creating the new swap * chain. It is possible to create a new swap chain while drawing commands on an * image from the old swap chain are still in - flight.You need to pass the previous * swap chain to the oldSwapChain field in the VkSwapchainCreateInfoKHR struct * and destroy the old swap chain as soon as you've finished using it. */ vkDeviceWaitIdle(state->vk_device); // get surface attributes { vkGetPhysicalDeviceSurfaceCapabilitiesKHR( state->vk_physical_device, state->vk_khr_surface, &state->surface_capabilities ); // choose swap extent // get new swapchain pixel resolution { s32 fb_width, fb_height; SDL_Vulkan_GetDrawableSize(state->window, &fb_width, &fb_height); VkExtent2D vk_extent = { (u32)fb_width, (u32)fb_height }; vk_extent.width = clamp_u32( vk_extent.width, state->surface_capabilities.minImageExtent.width, state->surface_capabilities.maxImageExtent.width ); vk_extent.height = clamp_u32( vk_extent.height, state->surface_capabilities.minImageExtent.height, state->surface_capabilities.maxImageExtent.height ); state->surface_resolution = vk_extent; } } // recreate swapchain VkSwapchainKHR old_swapchain = state->vk_khr_swapchain; { VkSwapchainCreateInfoKHR swapchain_create_info = {}; swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchain_create_info.surface = state->vk_khr_surface; swapchain_create_info.minImageCount = state->surface_image_count; swapchain_create_info.imageFormat = state->surface_format.format; swapchain_create_info.imageColorSpace = state->surface_format.colorSpace; swapchain_create_info.imageExtent = state->surface_resolution; swapchain_create_info.imageArrayLayers = 1; swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; u32 queue_family_indices[2] = { (u32)state->queue_family_gfx_index, (u32)state->queue_family_present_index }; if (state->queue_family_gfx_index == state->queue_family_present_index) { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchain_create_info.queueFamilyIndexCount = 0; swapchain_create_info.pQueueFamilyIndices = NULL; } else { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchain_create_info.queueFamilyIndexCount = 2; swapchain_create_info.pQueueFamilyIndices = queue_family_indices; } swapchain_create_info.preTransform = state->surface_capabilities.currentTransform; swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchain_create_info.presentMode = state->present_mode; swapchain_create_info.clipped = VK_TRUE; swapchain_create_info.oldSwapchain = old_swapchain; VkResult vk_result = vkCreateSwapchainKHR(state->vk_device, &swapchain_create_info, nullptr, &state->vk_khr_swapchain); if (vk_result != VK_SUCCESS) { printf("failed to create swapchain. Error: %d", vk_result); return -1; } u32 new_img_count = 0; vkGetSwapchainImagesKHR(state->vk_device, state->vk_khr_swapchain, &new_img_count, NULL); SDL_assert(("new swapchain image count != existing image count. These should be the same and resizing is not supported", new_img_count == state->vk_swapchain_images.size)); vkGetSwapchainImagesKHR( state->vk_device, state->vk_khr_swapchain, &state->vk_swapchain_images.size, state->vk_swapchain_images.buffer); } { for (u32 i = 0; i < state->surface_image_count; i++) { VkFramebuffer framebuffer = state->vk_framebuffers.buffer[i]; vkDestroyFramebuffer(state->vk_device, framebuffer, nullptr); VkImageView imageview = state->vk_swapchain_image_views.buffer[i]; vkDestroyImageView(state->vk_device, imageview, nullptr); } // @note: will destroy old swapchain later, once it's done rendering vkDestroySwapchainKHR(state->vk_device, old_swapchain, nullptr); } // recreate swapchain image views { for (u32 i = 0; i < state->vk_swapchain_images.size; i++) { VkImageViewCreateInfo image_view_create_info = {}; image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image_view_create_info.image = state->vk_swapchain_images.buffer[i]; image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image_view_create_info.format = state->surface_format.format; image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_view_create_info.subresourceRange.baseMipLevel = 0; image_view_create_info.subresourceRange.levelCount = 1; image_view_create_info.subresourceRange.baseArrayLayer = 0; image_view_create_info.subresourceRange.layerCount = 1; VkResult vk_result = vkCreateImageView(state->vk_device, &image_view_create_info, NULL, &state->vk_swapchain_image_views.buffer[i]); if (vk_result != VK_SUCCESS) { printf("failed to create image view for image index %d, Error: %d", i, vk_result); return -1; } } } // recreate framebuffer { for (u32 i = 0; i < state->vk_swapchain_image_views.size; i++) { VkImageView attachments[] = { state->vk_swapchain_image_views.buffer[i] }; VkFramebufferCreateInfo ci_framebuffer = {}; ci_framebuffer.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; ci_framebuffer.renderPass = state->vk_render_pass; ci_framebuffer.attachmentCount = ARR_LEN(attachments); ci_framebuffer.pAttachments = attachments; ci_framebuffer.width = state->surface_resolution.width; ci_framebuffer.height = state->surface_resolution.height; ci_framebuffer.layers = 1; if (vkCreateFramebuffer(state->vk_device, &ci_framebuffer, nullptr, &state->vk_framebuffers.buffer[i]) != VK_SUCCESS) { return -1; } } } printf("swapchain resized successfully\n"); resizable = 0; return 0; } b8 vk_create_buffer(VkDevice device, VkPhysicalDevice physical_device, VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags property_flags, VkBuffer *buffer, VkDeviceMemory *buffer_memory) { VkBufferCreateInfo ci{}; ci.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; ci.size = size; ci.usage = usage; ci.sharingMode = VK_SHARING_MODE_EXCLUSIVE; printf("Creating vulkan buffer\n"); if (vkCreateBuffer(device, &ci, nullptr, buffer) != VK_SUCCESS) { printf("Failed to create buffer\n"); return 0; } // find_matched_memory: VkMemoryRequirements mem_req; vkGetBufferMemoryRequirements(device, *buffer, &mem_req); s32 matched_mem_index = -1; { VkPhysicalDeviceMemoryProperties memory_properties; vkGetPhysicalDeviceMemoryProperties(physical_device, &memory_properties); // find memory meeting type requirement for (u32 i = 0; i < memory_properties.memoryTypeCount; i++) { b8 type_match = mem_req.memoryTypeBits & (1 << i); b8 properties_match = (memory_properties.memoryTypes[i].propertyFlags & property_flags) == property_flags; if (type_match && properties_match) { matched_mem_index = i; break; } } if (matched_mem_index == -1) { printf("Error! failed to find a memory that matches buffer requirements\n"); return 0; } } // allocate_memory: (debug_only) { VkMemoryAllocateInfo ai_memory{}; ai_memory.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; ai_memory.allocationSize = mem_req.size; ai_memory.memoryTypeIndex = matched_mem_index; if (vkAllocateMemory(device, &ai_memory, nullptr, buffer_memory) != VK_SUCCESS) { printf("Error! failed to allocate device memory\n"); return 0; } } vkBindBufferMemory(device, *buffer, *buffer_memory, 0); return 1; } int main() { MegaState state = {}; if (SDL_Init(SDL_INIT_VIDEO) != 0) { printf("Failed to initialize SDL2: %s\n", SDL_GetError()); return 1; } if (SDL_Vulkan_LoadLibrary(nullptr) != 0) { printf("Failed to load vulkan library: %s\n", SDL_GetError()); return 1; } state.window = SDL_CreateWindow( "vulkan-tutorial", SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, 800, 600, SDL_WINDOW_VULKAN | SDL_WINDOW_RESIZABLE ); uchar* raw_memory = (uchar *)malloc(sizeof(uchar)*MB(16)); arena_init(&state.arena, raw_memory, MB(100U) * sizeof(uchar)); VkDebugUtilsMessengerEXT vk_debug_messenger = {}; // Create Vulkan Instance { Arena vk_instance_arena = state.arena; VkApplicationInfo vk_app_info = {}; vk_app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; vk_app_info.pApplicationName = "Vulkan Hello Triangle"; vk_app_info.applicationVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); vk_app_info.pEngineName = "Sndgine"; vk_app_info.engineVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); vk_app_info.apiVersion = VK_MAKE_API_VERSION(0, 1, 0, 0); VkInstanceCreateInfo vk_create_info = {}; vk_create_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; vk_create_info.pApplicationInfo = &vk_app_info; char* required_extensions_256[8] = {}; { // Set extensions // 1. get sdl extensions required to create a vulkan instance u32 sdl_extension_count = 0; SDL_Vulkan_GetInstanceExtensions(state.window, &sdl_extension_count, nullptr); const char *sdl_extensions[sdl_extension_count] = {}; SDL_Vulkan_GetInstanceExtensions(state.window, &sdl_extension_count, sdl_extensions); u32 internal_extension_count = ARR_LEN(required_extensions_internal); u32 required_extension_count = sdl_extension_count + internal_extension_count; SDL_assert(("required extensions exceed the size of storage array", (sdl_extension_count + internal_extension_count) <= 8)); // 1.1. add required extensions to a common list for (int i = 0; i < sdl_extension_count; i++) { required_extensions_256[i] = (char*)arena_alloc(&vk_instance_arena, sizeof(char) * 256); Str256 extension_iter = str256(sdl_extensions[i]); memcpy(required_extensions_256[i], extension_iter.buffer, extension_iter.len*sizeof(extension_iter.buffer)); } for (int i = sdl_extension_count; i < sdl_extension_count + internal_extension_count; i++) { required_extensions_256[i] = (char*)arena_alloc(&vk_instance_arena, sizeof(char) * 256); Str256 extension_iter = str256(required_extensions_internal[i - sdl_extension_count]); memcpy( required_extensions_256[i], extension_iter.buffer, extension_iter.len*sizeof(extension_iter.buffer) ); } // 2. get all supported extensions u32 supported_extensions_count = 0; VkExtensionProperties supported_extensions[32] = {}; // 3. get extension count vkEnumerateInstanceExtensionProperties(nullptr, &supported_extensions_count, nullptr); SDL_assert(("supported extensions exceed the size of storage array", supported_extensions_count <= 32)); // 4. get extension list vkEnumerateInstanceExtensionProperties(nullptr, &supported_extensions_count, supported_extensions); // 5. Enumerate and check if glfw Extensions are supported. u32 available_extensions = 0; for (u32 g = 0; g < required_extension_count; g++) { for (u32 i = 0; i < supported_extensions_count; i++) { if (str256_match(str256(required_extensions_256[g]), str256(supported_extensions[i].extensionName))) { available_extensions++; } } } SDL_assert(( "Not all required GLFW extensions are supported by this device", required_extension_count == available_extensions )); vk_create_info.enabledExtensionCount = required_extension_count; vk_create_info.ppEnabledExtensionNames = required_extensions_256; } { // Set validation layers u32 supported_validation_layers_count = 0; VkLayerProperties supported_validation_layers[32] = {}; // 1. get validation layer count vkEnumerateInstanceLayerProperties(&supported_validation_layers_count, nullptr); SDL_assert(("supported validation layers exceed storage size", supported_validation_layers_count <= 32)); // 2. get validation layers vkEnumerateInstanceLayerProperties(&supported_validation_layers_count, supported_validation_layers); // 3. check that required validation layers are in supported list u32 required_validation_layers_count = ARR_LEN(required_validation_layers); u32 available_validation_layers = 0; for (int v = 0; v < required_validation_layers_count; v++) { for (int i = 0; i < supported_validation_layers_count; i++) { if (str256_match(str256(required_validation_layers[v]), str256(supported_validation_layers[i].layerName))) { available_validation_layers++; } } } SDL_assert(("Not all required validation layers are supported by this device", required_validation_layers_count == available_validation_layers )); vk_create_info.enabledLayerCount = required_validation_layers_count; vk_create_info.ppEnabledLayerNames = required_validation_layers; } VkDebugUtilsMessengerCreateInfoEXT debug_create_info = {}; if (enable_validation_layers) { // setup debug info struct debug_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debug_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; debug_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; debug_create_info.pfnUserCallback = debugCallback; debug_create_info.pUserData = nullptr; vk_create_info.pNext = (VkDebugUtilsMessengerCreateInfoEXT*)&debug_create_info; } VkResult vk_result = vkCreateInstance(&vk_create_info, nullptr, &state.vk_instance); if (vk_result != VK_SUCCESS) { printf("failure in creating vulkan instance. Error: %d", vk_result); return -1; } printf("successfully created vulkan instance\n"); } // Setup Debug Info if (enable_validation_layers) { // setup debug create info struct VkDebugUtilsMessengerCreateInfoEXT debug_create_info = {}; debug_create_info.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; debug_create_info.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; debug_create_info.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; debug_create_info.pfnUserCallback = debugCallback; debug_create_info.pUserData = NULL; // load vkCreateDebugUtilsMessengerEXT { auto fn = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(state.vk_instance, "vkCreateDebugUtilsMessengerEXT"); SDL_assert(("failed to load function symbols", fn != NULL)); VkResult res = fn(state.vk_instance, &debug_create_info, NULL, &vk_debug_messenger); SDL_assert(("failed to create debug utils messenger", res == VK_SUCCESS)); } } // setup vulkan surface { SDL_bool vk_result = SDL_Vulkan_CreateSurface(state.window, state.vk_instance, &state.vk_khr_surface); if (vk_result != SDL_TRUE) { printf("failed to create vulkan window surface. Error: %s", SDL_GetError()); return -1; } } // select physical device state.queue_family_gfx_index = -1; state.queue_family_present_index = -1; // swap chain (surface) details // find suitable device // get surface capabilities // get queue family indexes { Arena vk_physical_device_arena = state.arena; u32 physical_device_count = 0; vkEnumeratePhysicalDevices(state.vk_instance, &physical_device_count, NULL); if (physical_device_count == 0) { printf("no physical devices available, this is most likely an issue with the gpu or the drivers"); } SDL_assert(("number of physical devices larger than storage size", physical_device_count <= 8)); VkPhysicalDevice physical_devices[8] = {}; vkEnumeratePhysicalDevices(state.vk_instance, &physical_device_count, physical_devices); u32 best_device_score = 0; VkPhysicalDevice best_physical_device = VK_NULL_HANDLE; for (int i = 0; i < physical_device_count; i++) { u32 current_device_score = 0; s32 current_graphics_index = -1; s32 current_present_index = -1; // @func: check if device is suitable VkPhysicalDevice current_physical_device = physical_devices[i]; // get device properties VkPhysicalDeviceProperties device_properties; vkGetPhysicalDeviceProperties(current_physical_device, &device_properties); // get device features VkPhysicalDeviceFeatures device_features; vkGetPhysicalDeviceFeatures(current_physical_device, &device_features); if (device_properties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) current_device_score += 1000; current_device_score += device_properties.limits.maxImageDimension2D; if (device_features.geometryShader == 0) { continue; } { // @func: check device extensions support u32 extension_count = 0; vkEnumerateDeviceExtensionProperties(current_physical_device, NULL, &extension_count, NULL); VkExtensionProperties* supported_extensions = (VkExtensionProperties*)arena_alloc(&vk_physical_device_arena, sizeof(VkExtensionProperties) * extension_count); vkEnumerateDeviceExtensionProperties(current_physical_device, NULL, &extension_count, supported_extensions); u32 available_extension_count = 0; for (u32 si = 0; si < extension_count; si++) { VkExtensionProperties supported_extension = supported_extensions[si]; for (u32 ri = 0; ri < ARR_LEN(required_device_extensions); ri++) { const char* required_extension = required_device_extensions[ri]; if (str256_match(str256(supported_extension.extensionName), str256(required_extension))) { available_extension_count++; } } } if (available_extension_count != ARR_LEN(required_device_extensions)) { // if required extensions are not available, we can't run anything, skip device. continue; } } // check swapchain support { // check swapchain has attributes u32 surface_format_count = 0; vkGetPhysicalDeviceSurfaceFormatsKHR( current_physical_device, state.vk_khr_surface, &surface_format_count, NULL ); u32 surface_present_mode_count = 0; vkGetPhysicalDeviceSurfacePresentModesKHR( current_physical_device, state.vk_khr_surface, &surface_present_mode_count, NULL ); if (surface_format_count == 0 || surface_present_mode_count == 0) { // It is impossible to do anything without these. Skip the device continue; } } // check physical device queue families { // get vulkan queue families u32 queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties(current_physical_device, &queue_family_count, NULL); VkQueueFamilyProperties queue_families[16] = {}; SDL_assert(("queue family count larger than storage size", queue_family_count < 16)); vkGetPhysicalDeviceQueueFamilyProperties(current_physical_device, &queue_family_count, queue_families); for (int i = 0; i < queue_family_count; i++) { VkQueueFamilyProperties entry_queue_family = queue_families[i]; u32 has_present_family = 0; if ( vkGetPhysicalDeviceSurfaceSupportKHR( current_physical_device, i, state.vk_khr_surface, &has_present_family ) == VK_SUCCESS) { current_device_score += 100; current_present_index = i; } if (entry_queue_family.queueFlags & VK_QUEUE_GRAPHICS_BIT) { current_device_score += 100; current_graphics_index = i; } if (current_graphics_index >= 0 && current_graphics_index == current_present_index) { break; } } } if (best_device_score < current_device_score && current_graphics_index != -1 && current_present_index != -1) { best_device_score = current_device_score; best_physical_device = current_physical_device; state.queue_family_gfx_index = current_graphics_index; state.queue_family_present_index = current_present_index; } } state.vk_physical_device = best_physical_device; } // create a logical device { // 1.1 specify device queue SDL_assert(("graphics queue family index must exist", state.queue_family_gfx_index != -1)); SDL_assert(("present queue family index must exist", state.queue_family_present_index != -1)); r32 queue_priority = 1.0; VkDeviceQueueCreateInfo queue_create_info[2] = {}; u32 queue_size = 0; { // a. graphics queue VkDeviceQueueCreateInfo create_info_gfx = {}; create_info_gfx.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; create_info_gfx.queueFamilyIndex = state.queue_family_gfx_index; create_info_gfx.queueCount = 1; create_info_gfx.pQueuePriorities = &queue_priority; queue_create_info[0] = create_info_gfx; queue_size++; // check if the present queue index is different. // In case it is, only then should we create another queue entry. if (state.queue_family_gfx_index != state.queue_family_present_index) { // b. presentation VkDeviceQueueCreateInfo create_info_present = {}; create_info_present.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; create_info_present.queueFamilyIndex = state.queue_family_present_index; create_info_present.queueCount = 1; create_info_present.pQueuePriorities = &queue_priority; queue_create_info[1] = create_info_present; queue_size++; } } // 2. specify device features to use VkPhysicalDeviceFeatures device_features = {}; // 3. create logical device 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 = queue_size; device_create_info.pEnabledFeatures = &device_features; device_create_info.enabledExtensionCount = ARR_LEN(required_device_extensions); device_create_info.ppEnabledExtensionNames = required_device_extensions; // deprecated, done for legacy reasons device_create_info.enabledLayerCount = ARR_LEN(required_validation_layers); device_create_info.ppEnabledLayerNames = required_validation_layers; // create vulkan device VkResult vk_result = vkCreateDevice(state.vk_physical_device, &device_create_info, NULL, &state.vk_device); if (vk_result != VK_SUCCESS) { printf("failed to create a vulkan device. Error %d\n", vk_result); return -1; } // get queue family handle vkGetDeviceQueue(state.vk_device, state.queue_family_gfx_index, 0, &state.vk_queue_gfx); vkGetDeviceQueue(state.vk_device, state.queue_family_present_index, 0, &state.vk_queue_present); } // get surface attributes { Arena attribs_arena_temp = state.arena; vkGetPhysicalDeviceSurfaceCapabilitiesKHR( state.vk_physical_device, state.vk_khr_surface, &state.surface_capabilities ); u32 surface_format_count = 0; vkGetPhysicalDeviceSurfaceFormatsKHR( state.vk_physical_device, state.vk_khr_surface, &surface_format_count, NULL ); VkSurfaceFormatKHR* surface_formats = (VkSurfaceFormatKHR*)arena_alloc(&attribs_arena_temp, sizeof(VkSurfaceFormatKHR) * surface_format_count); vkGetPhysicalDeviceSurfaceFormatsKHR( state.vk_physical_device, state.vk_khr_surface, &surface_format_count, surface_formats ); u32 surface_present_mode_count = 0; vkGetPhysicalDeviceSurfacePresentModesKHR( state.vk_physical_device, state.vk_khr_surface, &surface_present_mode_count, NULL ); VkPresentModeKHR* surface_present_modes = (VkPresentModeKHR*)arena_alloc( &attribs_arena_temp, sizeof(VkPresentModeKHR) * surface_present_mode_count ); vkGetPhysicalDeviceSurfacePresentModesKHR( state.vk_physical_device, state.vk_khr_surface, &surface_present_mode_count, surface_present_modes ); // check swapchain attribute properties for application requirements // swap chain surface formats state.surface_format = surface_formats[0]; for (int sws_i = 0; sws_i < surface_format_count; sws_i++) { VkSurfaceFormatKHR sf_iter = surface_formats[sws_i]; if (sf_iter.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR && sf_iter.format == VK_FORMAT_R8G8B8A8_SRGB) { state.surface_format = sf_iter; break; } } // check swapchain surface present modes // this mode is always guaranteed to exist state.present_mode = VK_PRESENT_MODE_FIFO_KHR; for (int swp_i = 0; swp_i < surface_present_mode_count; swp_i++) { VkPresentModeKHR pm_iter = surface_present_modes[swp_i]; if (pm_iter == VK_PRESENT_MODE_MAILBOX_KHR) { state.present_mode = pm_iter; break; } } // choose swap extent // get swapchain pixel resolution { s32 fb_width, fb_height; SDL_Vulkan_GetDrawableSize(state.window, &fb_width, &fb_height); VkExtent2D vk_extent = { (u32)fb_width, (u32)fb_height }; vk_extent.width = clamp_u32( vk_extent.width, state.surface_capabilities.minImageExtent.width, state.surface_capabilities.maxImageExtent.width ); vk_extent.height = clamp_u32( vk_extent.height, state.surface_capabilities.minImageExtent.height, state.surface_capabilities.maxImageExtent.height ); state.surface_resolution = vk_extent; } } // create swap chain (surface) { state.surface_image_count = state.surface_capabilities.minImageCount + 1; if (state.surface_capabilities.maxImageCount > 0 && state.surface_image_count > state.surface_capabilities.maxImageCount) { state.surface_image_count = state.surface_capabilities.maxImageCount; } VkSwapchainCreateInfoKHR swapchain_create_info = {}; swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchain_create_info.surface = state.vk_khr_surface; swapchain_create_info.minImageCount = state.surface_image_count; swapchain_create_info.imageFormat = state.surface_format.format; swapchain_create_info.imageColorSpace = state.surface_format.colorSpace; swapchain_create_info.imageExtent = state.surface_resolution; swapchain_create_info.imageArrayLayers = 1; swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; u32 queue_family_indices[2] = { (u32)state.queue_family_gfx_index, (u32)state.queue_family_present_index }; if (state.queue_family_gfx_index == state.queue_family_present_index) { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchain_create_info.queueFamilyIndexCount = 0; swapchain_create_info.pQueueFamilyIndices = NULL; } else { swapchain_create_info.imageSharingMode = VK_SHARING_MODE_CONCURRENT; swapchain_create_info.queueFamilyIndexCount = 2; swapchain_create_info.pQueueFamilyIndices = queue_family_indices; } swapchain_create_info.preTransform = state.surface_capabilities.currentTransform; swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchain_create_info.presentMode = state.present_mode; swapchain_create_info.clipped = VK_TRUE; swapchain_create_info.oldSwapchain = VK_NULL_HANDLE; VkResult vk_result = vkCreateSwapchainKHR(state.vk_device, &swapchain_create_info, nullptr, &state.vk_khr_swapchain); if (vk_result != VK_SUCCESS) { printf("failed to create swapchain. Error: %d", vk_result); return -1; } vkGetSwapchainImagesKHR(state.vk_device, state.vk_khr_swapchain, &state.vk_swapchain_images.size, NULL); state.vk_swapchain_images.buffer = (VkImage*)arena_alloc(&state.arena, sizeof(VkImage) * state.vk_swapchain_images.size); vkGetSwapchainImagesKHR( state.vk_device, state.vk_khr_swapchain, &state.vk_swapchain_images.size, state.vk_swapchain_images.buffer); } // create image views { state.vk_swapchain_image_views.size = state.vk_swapchain_images.size; state.vk_swapchain_image_views.buffer = (VkImageView*)arena_alloc(&state.arena, sizeof(VkImageView) * state.vk_swapchain_image_views.size); for (u32 i = 0; i < state.vk_swapchain_images.size; i++) { VkImageViewCreateInfo image_view_create_info = {}; image_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; image_view_create_info.image = state.vk_swapchain_images.buffer[i]; image_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; image_view_create_info.format = state.surface_format.format; image_view_create_info.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; image_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; image_view_create_info.subresourceRange.baseMipLevel = 0; image_view_create_info.subresourceRange.levelCount = 1; image_view_create_info.subresourceRange.baseArrayLayer = 0; image_view_create_info.subresourceRange.layerCount = 1; VkResult vk_result = vkCreateImageView(state.vk_device, &image_view_create_info, NULL, &state.vk_swapchain_image_views.buffer[i]); if (vk_result != VK_SUCCESS) { printf("failed to create image view for image index %d, Error: %d", i, vk_result); return -1; } } } // create render pass { VkAttachmentDescription color_attachment = {}; color_attachment.format = state.surface_format.format; color_attachment.samples = VK_SAMPLE_COUNT_1_BIT; color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; color_attachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; color_attachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; color_attachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; color_attachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // renderpass VkAttachmentReference color_attachment_ref = {}; color_attachment_ref.attachment = 0; color_attachment_ref.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // subpass VkSubpassDescription subpass_description = {}; subpass_description.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass_description.colorAttachmentCount = 1; subpass_description.pColorAttachments = &color_attachment_ref; // create subpass dependency VkSubpassDependency subpass_dependency = {}; subpass_dependency.srcSubpass = VK_SUBPASS_EXTERNAL; subpass_dependency.dstSubpass = 0; subpass_dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; subpass_dependency.srcAccessMask = 0; subpass_dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; subpass_dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkRenderPassCreateInfo ci_render_pass = {}; ci_render_pass.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; ci_render_pass.attachmentCount = 1; ci_render_pass.pAttachments = &color_attachment; ci_render_pass.subpassCount = 1; ci_render_pass.pSubpasses = &subpass_description; ci_render_pass.dependencyCount = 1; ci_render_pass.pDependencies = &subpass_dependency; VkResult vk_result = vkCreateRenderPass(state.vk_device, &ci_render_pass, nullptr, &state.vk_render_pass); if (vk_result != VK_SUCCESS) { printf("failed to create the render pass. Error: %d", vk_result); return -1; } } // @func: create_graphics_pipeline VkPipelineLayout vk_pipeline_layout; VkPipeline vk_pipeline; // Vertex Data const std::vector vertices = { {{0.0f, -0.5f}, {1.0f, 0.0f, 0.0f}}, {{0.5f, 0.5f}, {0.0f, 1.0f, 0.0f}}, {{-0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}} }; { Arena temp_pipeline_arena = state.arena; ArrUChar vertex_shader = {}; ArrUChar fragment_shader = {}; // read vertex shader { u64 fsize = 0; const char filename[256] = "shaders/triangle.vert.spv"; // get a FILE pointer FILE* fp = fopen(filename, "rb"); if (fp != nullptr) { // set fp to end of file fseek(fp, 0, SEEK_END); // get filesize fsize = ftell(fp); vertex_shader.size = fsize; vertex_shader.buffer = (uchar*)arena_alloc(&temp_pipeline_arena, vertex_shader.size); fseek(fp, 0, SEEK_SET); fread(vertex_shader.buffer, sizeof(uchar), vertex_shader.size, fp); fclose(fp); } } // read fragment shader { u64 fsize = 0; const char filename[256] = "shaders/triangle.frag.spv"; // get a FILE pointer FILE* fp = fopen(filename, "rb"); if (fp != nullptr) { // set fp to end of file fseek(fp, 0, SEEK_END); // get filesize fsize = ftell(fp); fragment_shader.size = fsize; fragment_shader.buffer = (uchar*)arena_alloc(&temp_pipeline_arena, fragment_shader.size); fseek(fp, 0, SEEK_SET); fread(fragment_shader.buffer, sizeof(uchar), fragment_shader.size, fp); fclose(fp); } } VkShaderModule vertex_shader_module = {}; // create vertex shader module { VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; create_info.codeSize = vertex_shader.size; create_info.pCode = (u32*)vertex_shader.buffer; VkResult vk_result = vkCreateShaderModule(state.vk_device, &create_info, nullptr, &vertex_shader_module); if (vk_result != VK_SUCCESS) { printf("failed to create vertex shader module. Error: %d", vk_result); return -1; } } // create fragment shader module VkShaderModule fragment_shader_module = {}; { VkShaderModuleCreateInfo create_info = {}; create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; create_info.codeSize = fragment_shader.size; create_info.pCode = (u32*)fragment_shader.buffer; VkResult vk_result = vkCreateShaderModule(state.vk_device, &create_info, NULL, &fragment_shader_module); if (vk_result != VK_SUCCESS) { printf("failed to create fragment shader module. Error: %d", vk_result); return -1; } } // shader stage creation VkPipelineShaderStageCreateInfo ci_pipeline_shader_stages[2] = {}; { ci_pipeline_shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; ci_pipeline_shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT; ci_pipeline_shader_stages[0].module = vertex_shader_module; ci_pipeline_shader_stages[0].pName = "main"; } { ci_pipeline_shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; ci_pipeline_shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT; ci_pipeline_shader_stages[1].module = fragment_shader_module; ci_pipeline_shader_stages[1].pName = "main"; } // pipeline dynamic state VkDynamicState pipeline_dynamic_states[] = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR }; VkPipelineDynamicStateCreateInfo ci_pipeline_dynamic_state = {}; ci_pipeline_dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; ci_pipeline_dynamic_state.dynamicStateCount = ARR_LEN(pipeline_dynamic_states); ci_pipeline_dynamic_state.pDynamicStates = pipeline_dynamic_states; // vertex input VkPipelineVertexInputStateCreateInfo ci_pipeline_vertex_input = {}; VkVertexInputBindingDescription bind_desc{}; std::array attr_desc{}; { bind_desc.binding = 0; bind_desc.stride = sizeof(Vertex); bind_desc.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; attr_desc[0].binding = 0; attr_desc[0].location = 0; attr_desc[0].format = VK_FORMAT_R32G32_SFLOAT; attr_desc[0].offset = 0; attr_desc[1].binding = 0; attr_desc[1].location = 1; attr_desc[1].format = VK_FORMAT_R32G32B32_SFLOAT; attr_desc[1].offset = sizeof(glm::vec2); ci_pipeline_vertex_input.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; ci_pipeline_vertex_input.vertexBindingDescriptionCount = 1; ci_pipeline_vertex_input.pVertexBindingDescriptions = &bind_desc; ci_pipeline_vertex_input.vertexAttributeDescriptionCount = (u32)attr_desc.size(); ci_pipeline_vertex_input.pVertexAttributeDescriptions = attr_desc.data(); } // input assembly VkPipelineInputAssemblyStateCreateInfo ci_pipeline_input_assembly = {}; ci_pipeline_input_assembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; ci_pipeline_input_assembly.primitiveRestartEnable = VK_FALSE; ci_pipeline_input_assembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; // viewports & scissors VkPipelineViewportStateCreateInfo ci_pipeline_viewport_state = {}; ci_pipeline_viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; ci_pipeline_viewport_state.viewportCount = 1; ci_pipeline_viewport_state.scissorCount = 1; // rasterizer VkPipelineRasterizationStateCreateInfo ci_pipeline_raster = {}; ci_pipeline_raster.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; ci_pipeline_raster.depthClampEnable = VK_FALSE; ci_pipeline_raster.rasterizerDiscardEnable = VK_FALSE; ci_pipeline_raster.polygonMode = VK_POLYGON_MODE_FILL; ci_pipeline_raster.lineWidth = 1.0f; ci_pipeline_raster.cullMode = VK_CULL_MODE_BACK_BIT; ci_pipeline_raster.frontFace = VK_FRONT_FACE_CLOCKWISE; ci_pipeline_raster.depthBiasEnable = VK_FALSE; ci_pipeline_raster.depthBiasClamp = 0.0f; ci_pipeline_raster.depthBiasConstantFactor = 0.0f; ci_pipeline_raster.depthBiasSlopeFactor = 0.0f; // vk pipeline multisample create info // disabled for now VkPipelineMultisampleStateCreateInfo ci_pipeline_multisample = {}; ci_pipeline_multisample.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; ci_pipeline_multisample.sampleShadingEnable = VK_FALSE; ci_pipeline_multisample.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; ci_pipeline_multisample.minSampleShading = 1.0f; ci_pipeline_multisample.pSampleMask = NULL; ci_pipeline_multisample.alphaToCoverageEnable = VK_FALSE; ci_pipeline_multisample.alphaToOneEnable = VK_FALSE; // depth and stencil testing // not needed so: NULL VkPipelineDepthStencilStateCreateInfo* ci_pipeline_depth_stencil = nullptr; // color blending VkPipelineColorBlendAttachmentState pipeline_color_blend_attachment = {}; pipeline_color_blend_attachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; pipeline_color_blend_attachment.blendEnable = VK_FALSE; pipeline_color_blend_attachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; pipeline_color_blend_attachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; pipeline_color_blend_attachment.colorBlendOp = VK_BLEND_OP_ADD; pipeline_color_blend_attachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; pipeline_color_blend_attachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; pipeline_color_blend_attachment.alphaBlendOp = VK_BLEND_OP_ADD; VkPipelineColorBlendStateCreateInfo ci_pipeline_color_blend = {}; ci_pipeline_color_blend.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; ci_pipeline_color_blend.logicOpEnable = VK_FALSE; ci_pipeline_color_blend.logicOp = VK_LOGIC_OP_COPY; ci_pipeline_color_blend.attachmentCount = 1; ci_pipeline_color_blend.pAttachments = &pipeline_color_blend_attachment; ci_pipeline_color_blend.blendConstants[0] = 0.0f; ci_pipeline_color_blend.blendConstants[1] = 0.0f; ci_pipeline_color_blend.blendConstants[2] = 0.0f; ci_pipeline_color_blend.blendConstants[3] = 0.0f; // pipeline layout VkPipelineLayoutCreateInfo ci_pipeline_layout = {}; ci_pipeline_layout.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; ci_pipeline_layout.setLayoutCount = 0; ci_pipeline_layout.pSetLayouts = nullptr; ci_pipeline_layout.pushConstantRangeCount = 0; ci_pipeline_layout.pPushConstantRanges = nullptr; VkResult vk_result = vkCreatePipelineLayout(state.vk_device, &ci_pipeline_layout, nullptr, &vk_pipeline_layout); if (vk_result != VK_SUCCESS) { printf("failed to create a pipeline layout. Error: %d", vk_result); return -1; } // create the graphics pipeline VkGraphicsPipelineCreateInfo ci_pipeline_graphics = {}; ci_pipeline_graphics.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; ci_pipeline_graphics.stageCount = 2; ci_pipeline_graphics.pStages = ci_pipeline_shader_stages; ci_pipeline_graphics.pDynamicState = &ci_pipeline_dynamic_state; ci_pipeline_graphics.pVertexInputState = &ci_pipeline_vertex_input; ci_pipeline_graphics.pInputAssemblyState = &ci_pipeline_input_assembly; ci_pipeline_graphics.pViewportState = &ci_pipeline_viewport_state; ci_pipeline_graphics.pRasterizationState = &ci_pipeline_raster; ci_pipeline_graphics.pMultisampleState = &ci_pipeline_multisample; ci_pipeline_graphics.pDepthStencilState = ci_pipeline_depth_stencil; ci_pipeline_graphics.pColorBlendState = &ci_pipeline_color_blend; ci_pipeline_graphics.layout = vk_pipeline_layout; ci_pipeline_graphics.renderPass = state.vk_render_pass; ci_pipeline_graphics.subpass = 0; ci_pipeline_graphics.basePipelineHandle = VK_NULL_HANDLE; ci_pipeline_graphics.basePipelineIndex = -1; if (vkCreateGraphicsPipelines(state.vk_device, VK_NULL_HANDLE, 1, &ci_pipeline_graphics, nullptr, &vk_pipeline) != VK_SUCCESS) { // no printf from this point as validation layers should cover it. // todo: remove older printf. return -1; } // destroy shader modules vkDestroyShaderModule(state.vk_device, vertex_shader_module, NULL); vkDestroyShaderModule(state.vk_device, fragment_shader_module, NULL); } // create framebuffers { state.vk_framebuffers.size = state.vk_swapchain_image_views.size; state.vk_framebuffers.buffer = (VkFramebuffer*)arena_alloc( &state.arena, sizeof(VkFramebuffer) * state.vk_framebuffers.size ); for (u32 i = 0; i < state.vk_swapchain_image_views.size; i++) { VkImageView attachments[] = { state.vk_swapchain_image_views.buffer[i] }; VkFramebufferCreateInfo ci_framebuffer = {}; ci_framebuffer.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; ci_framebuffer.renderPass = state.vk_render_pass; ci_framebuffer.attachmentCount = ARR_LEN(attachments); ci_framebuffer.pAttachments = attachments; ci_framebuffer.width = state.surface_resolution.width; ci_framebuffer.height = state.surface_resolution.height; ci_framebuffer.layers = 1; if (vkCreateFramebuffer(state.vk_device, &ci_framebuffer, nullptr, &state.vk_framebuffers.buffer[i]) != VK_SUCCESS) { return -1; } } } // create_command_pools: VkCommandPool vk_command_pool; { VkCommandPoolCreateInfo ci_command_pool = {}; ci_command_pool.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; ci_command_pool.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; ci_command_pool.queueFamilyIndex = state.queue_family_gfx_index; if (vkCreateCommandPool(state.vk_device, &ci_command_pool, nullptr, &vk_command_pool) != VK_SUCCESS) { return -1; } } // create_vertex_buffer: { // staging buffer u32 buffer_size = sizeof(vertices[0]) * vertices.size(); VkBuffer staging_buffer; VkDeviceMemory staging_buffer_memory; vk_create_buffer(state.vk_device, state.vk_physical_device, buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &staging_buffer, &staging_buffer_memory); // fill vertex buffer void *vertex_data; vkMapMemory(state.vk_device, staging_buffer_memory, 0, buffer_size, 0, &vertex_data); memcpy(vertex_data, vertices.data(), buffer_size); vkUnmapMemory(state.vk_device, staging_buffer_memory); vk_create_buffer(state.vk_device, state.vk_physical_device, buffer_size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &state.vk_vertex_buffer, &state.vk_mem_vertex_buffer); // copy buffer from SRC -> DEST // vk_copy_buffer: { VkCommandBufferAllocateInfo alloc_info{}; alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; alloc_info.commandPool = vk_command_pool; alloc_info.commandBufferCount = 1; VkCommandBuffer command_buffer; vkAllocateCommandBuffers(state.vk_device, &alloc_info, &command_buffer); { VkCommandBufferBeginInfo begin_info{}; begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(command_buffer, &begin_info); VkBufferCopy copy_region{}; copy_region.srcOffset = 0; copy_region.dstOffset = 0; copy_region.size = buffer_size; vkCmdCopyBuffer(command_buffer, staging_buffer, state.vk_vertex_buffer, 1, ©_region); vkEndCommandBuffer(command_buffer); VkSubmitInfo submit_info{}; submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submit_info.commandBufferCount = 1; submit_info.pCommandBuffers = &command_buffer; vkQueueSubmit(state.vk_queue_gfx, 1, &submit_info, VK_NULL_HANDLE); vkQueueWaitIdle(state.vk_queue_gfx); } vkFreeCommandBuffers(state.vk_device, vk_command_pool, 1, &command_buffer); } vkDestroyBuffer(state.vk_device, staging_buffer, nullptr); vkFreeMemory(state.vk_device, staging_buffer_memory, nullptr); } // create command buffer VkCommandBuffer vk_command_buffer[MAX_FRAMES_IN_FLIGHT] = {}; { VkCommandBufferAllocateInfo ai_command_buffer = {}; ai_command_buffer.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; ai_command_buffer.commandPool = vk_command_pool; ai_command_buffer.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; ai_command_buffer.commandBufferCount = MAX_FRAMES_IN_FLIGHT; if (vkAllocateCommandBuffers(state.vk_device, &ai_command_buffer, vk_command_buffer) != VK_SUCCESS) { return -1; } } // create synchronization objects VkSemaphore vk_smp_image_available[MAX_FRAMES_IN_FLIGHT] = {}; VkSemaphore vk_smp_render_done[state.surface_image_count] = {}; VkFence vk_fence_frame_flight[MAX_FRAMES_IN_FLIGHT] = {}; { VkSemaphoreCreateInfo ci_semaphore = {}; ci_semaphore.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; VkFenceCreateInfo ci_fence = {}; ci_fence.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; ci_fence.flags = VK_FENCE_CREATE_SIGNALED_BIT; for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { VkResult res_image_available = vkCreateSemaphore(state.vk_device, &ci_semaphore, nullptr, &vk_smp_image_available[i]); VkResult res_frame_flight = vkCreateFence(state.vk_device, &ci_fence, nullptr, &vk_fence_frame_flight[i]); if (res_image_available != VK_SUCCESS || res_frame_flight != VK_SUCCESS) { return -1; } } // submit (render done semaphore) for (u32 i = 0; i < state.surface_image_count; i++) { VkResult res_render_done = vkCreateSemaphore(state.vk_device, &ci_semaphore, nullptr, &vk_smp_render_done[i]); if (res_render_done != VK_SUCCESS) { return -1; } } } u32 current_frame = 0; b8 running = 1; while (running) { // draw frame SDL_Event event; while(SDL_PollEvent(&event)) { switch(event.type) { case SDL_QUIT: { running = 0; } break; case SDL_WINDOWEVENT: { switch (event.window.event) { case SDL_WINDOWEVENT_RESIZED: { printf("sdl window size changed\n"); resizable = 1; } break; default: break; } } break; case SDL_KEYDOWN: { switch (event.key.keysym.scancode) { case SDL_SCANCODE_ESCAPE:{ printf("Closing game now\n"); running = 0; } break; default: break; } } break; default: break; } } // wait until previous frame finishes vkWaitForFences(state.vk_device, 1, vk_fence_frame_flight, VK_TRUE, UINT64_MAX); // get an image from the swapchain (sw) u32 sw_image_index = 0; VkResult vk_result = vkAcquireNextImageKHR(state.vk_device, state.vk_khr_swapchain, UINT64_MAX, vk_smp_image_available[current_frame], VK_NULL_HANDLE, &sw_image_index); if (vk_result == VK_ERROR_OUT_OF_DATE_KHR) { printf("swapchain is out of date\n"); // recreate swapchain -> currently only resizing is supported if (resize_swapchain(&state) != 0) { return -1; } // go to next frame // @note: we go to the next frame because the images are no longer valid. // technically nothing is valid, but the image needs to be acquired after creation // so that should perhaps help explain this directly //continue; } else if (vk_result != VK_SUCCESS && vk_result != VK_SUBOPTIMAL_KHR) { // suboptimal swapchain is allowed to proceed without recreation return -1; } // reset fence once we are sure we will be submitting work to the device vkResetFences(state.vk_device, 1, &vk_fence_frame_flight[current_frame]); vkResetCommandBuffer(vk_command_buffer[current_frame], 0); // @func: record_command_buffer { VkCommandBufferBeginInfo bi_command_buffer = {}; bi_command_buffer.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; bi_command_buffer.flags = 0; bi_command_buffer.pInheritanceInfo = nullptr; if (vkBeginCommandBuffer(vk_command_buffer[current_frame], &bi_command_buffer) != VK_SUCCESS) { return -1; } // start render pass { VkRenderPassBeginInfo bi_render_pass = {}; bi_render_pass.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; bi_render_pass.renderPass = state.vk_render_pass; bi_render_pass.framebuffer = state.vk_framebuffers.buffer[sw_image_index]; bi_render_pass.renderArea.offset = { 0, 0 }; bi_render_pass.renderArea.extent = state.surface_resolution; VkClearValue clear_color = { {{0.0f, 0.0f, 0.0f, 1.0f}} }; bi_render_pass.clearValueCount = 1; bi_render_pass.pClearValues = &clear_color; vkCmdBeginRenderPass(vk_command_buffer[current_frame], &bi_render_pass, VK_SUBPASS_CONTENTS_INLINE); { vkCmdBindPipeline(vk_command_buffer[current_frame], VK_PIPELINE_BIND_POINT_GRAPHICS, vk_pipeline); // viewport VkViewport vk_viewport = {}; vk_viewport.x = 0.0f; vk_viewport.y = 0.0f; vk_viewport.width = (r32)state.surface_resolution.width; vk_viewport.height = (r32)state.surface_resolution.height; vk_viewport.minDepth = 0.0f; vk_viewport.maxDepth = 1.0f; vkCmdSetViewport(vk_command_buffer[current_frame], 0, 1, &vk_viewport); // scissor VkRect2D scissor = {}; scissor.offset = { 0, 0 }; scissor.extent = state.surface_resolution; vkCmdSetScissor(vk_command_buffer[current_frame], 0, 1, &scissor); // vertex buffer VkBuffer vertex_buffers[] = {state.vk_vertex_buffer}; VkDeviceSize offsets[] = {0}; vkCmdBindVertexBuffers(vk_command_buffer[current_frame], 0, 1, vertex_buffers, offsets); vkCmdDraw(vk_command_buffer[current_frame], (u32)vertices.size(), 1, 0, 0); } vkCmdEndRenderPass(vk_command_buffer[current_frame]); } if (vkEndCommandBuffer(vk_command_buffer[current_frame]) != VK_SUCCESS) { return -1; } } // submit the command buffer VkSemaphore wait_semaphores[] = { vk_smp_image_available[current_frame] }; VkSemaphore signal_semaphores[] = { vk_smp_render_done[sw_image_index] }; { VkSubmitInfo si_command_buffer = {}; si_command_buffer.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkPipelineStageFlags wait_stages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; si_command_buffer.waitSemaphoreCount = 1; si_command_buffer.pWaitSemaphores = wait_semaphores; si_command_buffer.pWaitDstStageMask = wait_stages; si_command_buffer.commandBufferCount = 1; si_command_buffer.pCommandBuffers = &vk_command_buffer[current_frame]; si_command_buffer.signalSemaphoreCount = 1; si_command_buffer.pSignalSemaphores = signal_semaphores; if (vkQueueSubmit(state.vk_queue_gfx, 1, &si_command_buffer, vk_fence_frame_flight[current_frame]) != VK_SUCCESS) { return -1; } } // submit result back to swapchain { VkPresentInfoKHR present_info = {}; present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; present_info.waitSemaphoreCount = 1; present_info.pWaitSemaphores = signal_semaphores; VkSwapchainKHR swapchain_list[] = { state.vk_khr_swapchain }; present_info.swapchainCount = 1; present_info.pSwapchains = swapchain_list; present_info.pImageIndices = &sw_image_index; present_info.pResults = nullptr; VkResult vk_result = vkQueuePresentKHR(state.vk_queue_present, &present_info); if ((vk_result == VK_ERROR_OUT_OF_DATE_KHR || vk_result == VK_SUBOPTIMAL_KHR) || resizable) { // @note: the suboptimal check which usually works when the surface is changed // will not change anything because the recreate function only resizes swapchain // @todo: I guess I can add that for completeness, // even though I have no way to test it out realistically printf("swapchain is out of date\n"); if (resize_swapchain(&state) != 0) return -1; } else if (vk_result != VK_SUCCESS) { return -1; } } current_frame = (current_frame + 1) % MAX_FRAMES_IN_FLIGHT; SDL_Delay(16); } // wait for logical device to finish operations vkDeviceWaitIdle(state.vk_device); // cleanup for (u32 i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) { vkDestroyFence(state.vk_device, vk_fence_frame_flight[i], nullptr); vkDestroySemaphore(state.vk_device, vk_smp_image_available[i], nullptr); } for (u32 i = 0; i < state.surface_image_count; i++) { vkDestroySemaphore(state.vk_device, vk_smp_render_done[i], nullptr); } vkDestroyCommandPool(state.vk_device, vk_command_pool, nullptr); vkDestroyBuffer(state.vk_device, state.vk_vertex_buffer, nullptr); vkFreeMemory(state.vk_device, state.vk_mem_vertex_buffer, nullptr); // destroy framebuffers { for (u32 i= 0; i < state.vk_framebuffers.size; i++) { VkFramebuffer framebuffer_item = state.vk_framebuffers.buffer[i]; vkDestroyFramebuffer(state.vk_device, framebuffer_item, nullptr); } } vkDestroyPipeline(state.vk_device, vk_pipeline, NULL); vkDestroyPipelineLayout(state.vk_device, vk_pipeline_layout, NULL); vkDestroyRenderPass(state.vk_device, state.vk_render_pass, NULL); // destroy image views { for (u32 i = 0; i < state.vk_swapchain_image_views.size; i++) { vkDestroyImageView(state.vk_device, state.vk_swapchain_image_views.buffer[i], NULL); } } vkDestroySwapchainKHR(state.vk_device, state.vk_khr_swapchain, NULL); vkDestroyDevice(state.vk_device, NULL); if (enable_validation_layers) { auto fn = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(state.vk_instance, "vkDestroyDebugUtilsMessengerEXT"); SDL_assert(("failed to load function symbols", fn != NULL)); fn(state.vk_instance, vk_debug_messenger, NULL); } // @revise: why are we destroying surface here? after destroying the logical device vkDestroySurfaceKHR(state.vk_instance, state.vk_khr_surface, NULL); vkDestroyInstance(state.vk_instance, NULL); SDL_DestroyWindow(state.window); SDL_Quit(); }