summaryrefslogtreecommitdiff
path: root/main.cpp
diff options
context:
space:
mode:
authortalha aamir <talha@talhaamir.xyz>2025-09-24 21:27:03 +0500
committertalha aamir <talha@talhaamir.xyz>2025-09-24 21:27:03 +0500
commit6ad18ea8a3966e10b3d73b56663c502ef35e6f09 (patch)
tree161d0f01b5f5726b78653233f1f3520a0af686e0 /main.cpp
Added files to git.
- Starting index buffer chapter of vulkan-tutorial
Diffstat (limited to 'main.cpp')
-rwxr-xr-xmain.cpp1676
1 files changed, 1676 insertions, 0 deletions
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 <array>
+#include <chrono>
+#include <iostream>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+#include <thread>
+#include <vector>
+#include <vulkan/vulkan_core.h>
+
+#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<Vertex> 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<VkVertexInputAttributeDescription, 2> 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, &copy_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();
+}