// vim:fileencoding=utf-8:foldmethod=marker #include "wapp/wapp.h" #include "vulkan_profiles/vulkan_profiles.h" #include #include #include #include #include #include #include #define VMA_IMPLEMENTATION #include #include #include #include #include enum ExitCode { EXIT_CODE_SUCCESS, EXIT_CODE_SDL_INIT_FAILED, EXIT_CODE_VULKAN_LIB_LOAD_FAILED, EXIT_CODE_WINDOW_CREATION_FAILED, EXIT_CODE_SURFACE_CREATION_FAILED, EXIT_CODE_GET_WINDOW_SIZE_FAILED, EXIT_CODE_NO_INSTANCE_SUPPORT, EXIT_CODE_NO_PHYSICAL_DEVICES, EXIT_CODE_ALLOCATION_FAILURE, EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE, EXIT_CODE_NO_PRESENTATION_SUPPORT, EXIT_CODE_NO_PHYSICAL_DEVICE_SUPPORT, EXIT_CODE_NO_QUEUE_FAMILIES, EXIT_CODE_NO_SUITABLE_QUEUE_FAMILY, }; typedef VkPhysicalDevice *VkPhysicalDeviceArray; typedef VkQueueFamilyProperties2 *VkQueueFamilyProperties2Array; typedef VkImage *VkImageArray; typedef VkImageView *VkImageViewArray; wapp_intern inline void check(VkResult result); wapp_intern inline void check_swapchain(VkResult result); wapp_intern inline void check(bool result, i32 code); wapp_intern bool running = true; wapp_intern bool update_swapchain = false; wapp_intern f32 display_scale = 1.0f; wapp_intern SDL_Window *window = nullptr; wapp_intern VkInstance instance = VK_NULL_HANDLE; wapp_intern VkPhysicalDevice physical_device = VK_NULL_HANDLE; wapp_intern VkSurfaceKHR surface = VK_NULL_HANDLE; wapp_intern u32 queue_family_index = 0; wapp_intern VkDevice device = VK_NULL_HANDLE; wapp_intern VkQueue queue = VK_NULL_HANDLE; wapp_intern VmaAllocator allocator = VK_NULL_HANDLE; wapp_intern glm::ivec2 window_size = {}; wapp_intern VkFormat image_format = VK_FORMAT_B8G8R8A8_SRGB; wapp_intern VkSwapchainKHR swapchain = VK_NULL_HANDLE; wapp_intern u32 swapchain_image_count = 0; wapp_intern VkImageArray swapchain_images = nullptr; wapp_intern VkImageViewArray swapchain_views = nullptr; int main() { // {{{ Initialisation Allocator arena = wapp_mem_arena_allocator_init(MiB(64)); check(SDL_Init(SDL_INIT_VIDEO), EXIT_CODE_SDL_INIT_FAILED); check(SDL_Vulkan_LoadLibrary(nullptr), EXIT_CODE_VULKAN_LIB_LOAD_FAILED); check(volkInitialize()); display_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay()); window = SDL_CreateWindow("How To Vulkan", (i32)(display_scale * 1920), (i32)(display_scale * 1080), SDL_WINDOW_VULKAN); check(window != nullptr, EXIT_CODE_WINDOW_CREATION_FAILED); // }}} // {{{ Get Vulkan Profile VkBool32 supported = VK_FALSE; VpProfileProperties profile = { .profileName = VP_LEARN_HOW_TO_VULKAN_2026_NAME, .specVersion = VP_LEARN_HOW_TO_VULKAN_2026_SPEC_VERSION, }; // }}} // {{{ Instance Creation // {{{ Get Platform Extensions u32 instance_extension_count = 0; const char *const *instance_extensions = SDL_Vulkan_GetInstanceExtensions(&instance_extension_count); // }}} // {{{ Check Instance Profile Support check(vpGetInstanceProfileSupport(NULL, &profile, &supported)); check(supported, EXIT_CODE_NO_INSTANCE_SUPPORT); // }}} VkApplicationInfo app_info = {}; app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; app_info.apiVersion = VP_LEARN_HOW_TO_VULKAN_2026_MIN_API_VERSION; // {{{ Create Instance VkInstanceCreateInfo instance_info = {}; instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; instance_info.pApplicationInfo = &app_info; instance_info.enabledExtensionCount = instance_extension_count; instance_info.ppEnabledExtensionNames = instance_extensions; VpInstanceCreateInfo vp_instance_info = {}; vp_instance_info.pCreateInfo = &instance_info; vp_instance_info.enabledFullProfileCount = 1; vp_instance_info.pEnabledFullProfiles = &profile; check(vpCreateInstance(&vp_instance_info, NULL, &instance)); // }}} volkLoadInstance(instance); // }}} wapp_mem_arena_allocator_temp_begin(&arena); // {{{ Physical Device Retrieval // {{{ Get Physical Device Count u32 physical_device_count = 0; check(vkEnumeratePhysicalDevices(instance, &physical_device_count, nullptr)); check(physical_device_count > 0, EXIT_CODE_NO_PHYSICAL_DEVICES); // }}} // {{{ Get All Physical Devices VkPhysicalDeviceArray physical_devices = wapp_array_alloc_capacity(VkPhysicalDevice, &arena, physical_device_count, ARRAY_INIT_FILLED); check(physical_devices != nullptr, EXIT_CODE_ALLOCATION_FAILURE); check(vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices)); // Set the count in case it changes after the second call to vkEnumeratePhysicalDevices wapp_array_set_count(physical_devices, physical_device_count); // }}} // {{{ Choose Appropriate Physical Device i32 index = -1; for (u32 i = 0; i < wapp_array_count(physical_devices); ++i) { VkPhysicalDeviceProperties2 base_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 }; vkGetPhysicalDeviceProperties2(physical_devices[i], &base_properties); switch (base_properties.properties.deviceType) { case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: { if (base_properties.properties.apiVersion >= VP_LEARN_HOW_TO_VULKAN_2026_MIN_API_VERSION) { index = i; } } break; case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: { if (base_properties.properties.apiVersion >= VP_LEARN_HOW_TO_VULKAN_2026_MIN_API_VERSION) { index = i; } } break; default: continue; } } check(index != -1, EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE); physical_device = physical_devices[index]; // {{{ Print Physical Device Information VkPhysicalDeviceDriverProperties p_device_driver_props = {}; p_device_driver_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES; VkPhysicalDeviceProperties2 p_device_props = {}; p_device_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2; p_device_props.pNext = &p_device_driver_props; vkGetPhysicalDeviceProperties2(physical_device, &p_device_props); u32 api_major = VK_API_VERSION_MAJOR(p_device_props.properties.apiVersion); u32 api_minor = VK_API_VERSION_MINOR(p_device_props.properties.apiVersion); u32 api_patch = VK_API_VERSION_PATCH(p_device_props.properties.apiVersion); u32 api_variant = VK_API_VERSION_VARIANT(p_device_props.properties.apiVersion); std::cout << "Selected GPU: " << p_device_props.properties.deviceName << '\n' << "Driver version: " << p_device_driver_props.driverInfo << '\n' << "API version: " << api_major << '.' << api_minor << '.' << api_patch << '.' << api_variant << '\n'; // }}} // }}} // {{{ Check Physical Device Profile Support check(vpGetPhysicalDeviceProfileSupport(instance, physical_device, &profile, &supported)); check(supported, EXIT_CODE_NO_PHYSICAL_DEVICE_SUPPORT); // }}} // }}} // {{{ Surface Creation check(SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface), EXIT_CODE_SURFACE_CREATION_FAILED); check(SDL_GetWindowSize(window, &window_size.x, &window_size.y), EXIT_CODE_GET_WINDOW_SIZE_FAILED); VkSurfaceCapabilitiesKHR surface_caps{}; check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_caps)); // }}} // {{{ Queue Family Retrieval // {{{ Get Queue Family Count u32 queue_family_count = 0; vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_count, nullptr); check(queue_family_count > 0, EXIT_CODE_NO_QUEUE_FAMILIES); // }}} // {{{ Get All Queue Families VkQueueFamilyProperties2Array queue_family_properties = wapp_array_alloc_capacity(VkQueueFamilyProperties2, &arena, queue_family_count, ARRAY_INIT_FILLED); check(queue_family_properties != nullptr, EXIT_CODE_ALLOCATION_FAILURE); for (u32 i = 0; i < wapp_array_count(queue_family_properties); ++i) { queue_family_properties[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2; } vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_count, queue_family_properties); // Set the count in case it changes after the second call to vkGetPhysicalDeviceQueueFamilyProperties2 wapp_array_set_count(queue_family_properties, queue_family_count); // }}} // {{{ Choose Appropriate Queue Family i32 family_index = -1; for (u32 i = 0; i < queue_family_count; ++i) { if ((queue_family_properties[i].queueFamilyProperties.queueFlags & (VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT)) != 0) { family_index = i; break; } } check(family_index != -1, EXIT_CODE_NO_SUITABLE_QUEUE_FAMILY); queue_family_index = (u32)family_index; // }}} // {{{ Check Presentation Support check(SDL_Vulkan_GetPresentationSupport(instance, physical_device, queue_family_index), EXIT_CODE_NO_PRESENTATION_SUPPORT); // }}} // }}} wapp_mem_arena_allocator_temp_end(&arena); // {{{ Device And Queue Creation // {{{ Create Device f32 queue_priority = 1.0f; VkDeviceQueueCreateInfo device_queue_create_info = {}; device_queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; device_queue_create_info.queueCount = 1; device_queue_create_info.queueFamilyIndex = queue_family_index; device_queue_create_info.pQueuePriorities = &queue_priority; VkDeviceCreateInfo device_create_info = {}; device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; device_create_info.queueCreateInfoCount = 1; device_create_info.pQueueCreateInfos = &device_queue_create_info; VpDeviceCreateInfo vp_device_create_info = {}; vp_device_create_info.pCreateInfo = &device_create_info; vp_device_create_info.enabledFullProfileCount = 1; vp_device_create_info.pEnabledFullProfiles = &profile; check(vpCreateDevice(physical_device, &vp_device_create_info, nullptr, &device)); // }}} // {{{ Get Queue vkGetDeviceQueue(device, queue_family_index, 0, &queue); // }}} volkLoadDevice(device); // }}} // {{{ Vulkan Memory Allocator (VMA) Setup VmaVulkanFunctions vk_functions = {}; vk_functions.vkGetInstanceProcAddr = vkGetInstanceProcAddr; vk_functions.vkGetDeviceProcAddr = vkGetDeviceProcAddr; vk_functions.vkCreateImage = vkCreateImage; VmaAllocatorCreateInfo allocator_create_info = {}; allocator_create_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT; allocator_create_info.instance = instance; allocator_create_info.physicalDevice = physical_device; allocator_create_info.device = device; allocator_create_info.pVulkanFunctions = &vk_functions; check(vmaCreateAllocator(&allocator_create_info, &allocator)); // }}} // {{{ Swapchain Creation // {{{ Calculate Extent VkExtent2D swapchain_extent = surface_caps.currentExtent; // Handle Wayland's special value (0xffffffff), which indicate that the surface size will // basically be determined by the size of the surface if (swapchain_extent.width == 0xffffffff) { swapchain_extent.width = (u32)window_size.x; swapchain_extent.height = (u32)window_size.y; } // }}} // {{{ Create Swapchain VkSwapchainCreateInfoKHR swapchain_create_info = {}; swapchain_create_info.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; swapchain_create_info.surface = surface; swapchain_create_info.minImageCount = surface_caps.minImageCount; swapchain_create_info.imageFormat = image_format; swapchain_create_info.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR; swapchain_create_info.imageExtent = swapchain_extent; swapchain_create_info.imageArrayLayers = 1; swapchain_create_info.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; swapchain_create_info.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; swapchain_create_info.queueFamilyIndexCount = 1; swapchain_create_info.pQueueFamilyIndices = &queue_family_index; swapchain_create_info.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; swapchain_create_info.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; swapchain_create_info.presentMode = VK_PRESENT_MODE_FIFO_KHR; check(vkCreateSwapchainKHR(device, &swapchain_create_info, nullptr, &swapchain)); // }}} // {{{ Get Swapchain Images check(vkGetSwapchainImagesKHR(device, swapchain, &swapchain_image_count, nullptr)); swapchain_images = wapp_array_alloc_capacity(VkImage, &arena, swapchain_image_count, ARRAY_INIT_FILLED); check(swapchain_images != nullptr, EXIT_CODE_ALLOCATION_FAILURE); check(vkGetSwapchainImagesKHR(device, swapchain, &swapchain_image_count, swapchain_images)); // Set the count in case it changes after the second call to vkGetSwapchainImagesKHR wapp_array_set_count(swapchain_images, swapchain_image_count); // }}} // {{{ Create Swapchain Image Views swapchain_views = wapp_array_alloc_capacity(VkImageView, &arena, swapchain_image_count, ARRAY_INIT_FILLED); check(swapchain_views != nullptr, EXIT_CODE_ALLOCATION_FAILURE); for (u32 i = 0; i < swapchain_image_count; ++i) { VkImageViewCreateInfo view_create_info = {}; view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; view_create_info.image = swapchain_images[i]; view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D; view_create_info.format = image_format; view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; view_create_info.subresourceRange.levelCount = 1; view_create_info.subresourceRange.layerCount = 1; check(vkCreateImageView(device, &view_create_info, nullptr, &swapchain_views[i])); } // }}} // }}} // {{{ Render Loop SDL_Event event = {}; while (running) { while (SDL_PollEvent(&event)) { switch (event.type) { case SDL_EVENT_QUIT: running = false; break; case SDL_EVENT_KEY_DOWN: if (event.key.key == SDLK_ESCAPE) { running = false; } break; } } } // }}} // {{{ Cleanup for (u32 i = 0; i < swapchain_image_count; ++i) { vkDestroyImageView(device, swapchain_views[i], nullptr); } vkDestroySwapchainKHR(device, swapchain, nullptr); vmaDestroyAllocator(allocator); vkDestroyDevice(device, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); SDL_DestroyWindow(window); SDL_Vulkan_UnloadLibrary(); SDL_Quit(); wapp_mem_arena_allocator_destroy(&arena); // }}} return 0; } // {{{ Helper Functions wapp_intern inline void check(VkResult result) { if (result != VK_SUCCESS) { std::cerr << "Vulkan call returned an error (" << string_VkResult(result) << ")\n"; exit(result); } } wapp_intern inline void check_swapchain(VkResult result) { if (result < VK_SUCCESS) { if (result == VK_ERROR_OUT_OF_DATE_KHR) { update_swapchain = true; return; } std::cerr << "Vulkan call returned an error (" << result << ")\n"; exit(result); } } wapp_intern inline void check(bool result, i32 code) { if (!result) { std::cerr << "Call returned an error\n"; exit(code); } } // }}}