Files
how-to-vulkan/main.cpp
T
2026-05-25 22:51:30 +01:00

405 lines
15 KiB
C++

// vim:fileencoding=utf-8:foldmethod=marker
#include "wapp/wapp.h"
#include "vulkan_profiles/vulkan_profiles.h"
#include <glm/ext/vector_int2.hpp>
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_keycode.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_vulkan.h>
#define VMA_IMPLEMENTATION
#include <vma/vk_mem_alloc.h>
#include <volk/volk.h>
#include <vulkan/vulkan.h>
#include <vulkan/vk_enum_string_helper.h>
#include <iostream>
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);
}
}
// }}}