diff --git a/main.cpp b/main.cpp new file mode 100644 index 0000000..848a304 --- /dev/null +++ b/main.cpp @@ -0,0 +1,404 @@ +// 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); + } +} +// }}}