From 6ad18ea8a3966e10b3d73b56663c502ef35e6f09 Mon Sep 17 00:00:00 2001 From: talha aamir Date: Wed, 24 Sep 2025 21:27:03 +0500 Subject: Added files to git. - Starting index buffer chapter of vulkan-tutorial --- main.cpp | 1676 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1676 insertions(+) create mode 100755 main.cpp (limited to 'main.cpp') diff --git a/main.cpp b/main.cpp new file mode 100755 index 0000000..0925c89 --- /dev/null +++ b/main.cpp @@ -0,0 +1,1676 @@ +/* +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(); +} -- cgit v1.2.3