Files
how-to-vulkan/main.cpp
T
2026-06-21 20:09:54 +01:00

1449 lines
65 KiB
C++

// vim:fileencoding=utf-8:foldmethod=marker
#include "wapp/wapp.h"
#include "vulkan/vulkan_core.h"
#include "vulkan_profiles/vulkan_profiles.h"
#include "ktx.h"
#include "ktxvulkan.h"
#include <SDL3/SDL_timer.h>
#include <glm/ext/matrix_clip_space.hpp>
#include <glm/glm.hpp>
#include <glm/gtc/matrix_transform.hpp>
#include <glm/gtc/quaternion.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>
#include <slang/slang.h>
#include <slang/slang-com-ptr.h>
#include <tiny_obj_loader.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>
#include <vector>
#include <cstdint>
// {{{ Exit Codes
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,
EXIT_CODE_NO_SUITABLE_DEPTH_FORMAT,
EXIT_CODE_MESH_LOAD_FAILED,
EXIT_CODE_SYNC_OBJ_CREATE_FAILED,
};
// }}}
// {{{ Types
struct Vertex {
glm::vec3 pos;
glm::vec3 normal;
glm::vec2 uv;
};
struct ShaderData {
glm::mat4 projection;
glm::mat4 view;
glm::mat4 model[3];
glm::vec4 light_pos{ 0.0f, -10.0f, 10.0f, 0.0f };
u32 selected{ 1 };
};
struct ShaderDataBuffer {
VmaAllocation allocation;
VmaAllocationInfo allocation_info;
VkBuffer buffer;
VkDeviceAddress device_address;
};
struct Texture {
VmaAllocation allocation;
VkImage image;
VkImageView view;
VkSampler sampler;
};
typedef VkPhysicalDevice *VkPhysicalDeviceArray;
typedef VkQueueFamilyProperties2 *VkQueueFamilyProperties2Array;
typedef VkImage *VkImageArray;
typedef VkImageView *VkImageViewArray;
typedef VkFormat *VkFormatArray;
typedef Vertex *VertexArray;
typedef ShaderDataBuffer *ShaderDataBufferArray;
typedef VkFence *VkFenceArray;
typedef VkSemaphore *VkSemaphoreArray;
typedef VkCommandBuffer *VkCommandBufferArray;
typedef Texture *TextureArray;
typedef VkDescriptorImageInfo *VkDescriptorImageInfoArray;
typedef VkBufferImageCopy *VkBufferImageCopyArray;
typedef slang::TargetDesc *SlangTargetDescArray;
typedef slang::CompilerOptionEntry *SlangCompOptEntryArray;
typedef VkVertexInputAttributeDescription *VkVertexInputAttributeDescriptionArray;
typedef VkPipelineShaderStageCreateInfo *VkPipelineShaderStageCreateInfoArray;
typedef VkDynamicState *VkDynamicStateArray;
typedef glm::vec3 *GlmVec3Array;
typedef VkImageMemoryBarrier2 *VkImageMemoryBarrier2Array;
// }}}
// {{{ Helper Function Declarations
wapp_intern inline void check(VkResult result);
wapp_intern inline void check_swapchain(VkResult result);
wapp_intern inline void check(bool result, i32 code);
// }}}
// {{{ Global Variables
wapp_intern u32 frame_index = 0;
wapp_intern u32 image_index = 0;
wapp_intern constexpr u32 max_frames_in_flight = 2;
wapp_intern constexpr u32 instance_count = 3;
wapp_intern constexpr u32 texture_count = 3;
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 = {};
// NOTE (Abdelrahman): The following three variables are better queried from the physical device
// to find the different supported values. For the purposes of this tutorial, we're using defaults
// that are guaranteed by the spec to be available on any valid implementation.
wapp_intern VkFormat image_format = VK_FORMAT_B8G8R8A8_SRGB;
wapp_intern VkColorSpaceKHR colorspace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
wapp_intern VkPresentModeKHR present_mode = VK_PRESENT_MODE_FIFO_KHR;
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;
wapp_intern VkFormat depth_format = VK_FORMAT_UNDEFINED;
wapp_intern VkImage depth_image = VK_NULL_HANDLE;
wapp_intern VkImageView depth_view = VK_NULL_HANDLE;
wapp_intern VmaAllocation depth_allocation = VK_NULL_HANDLE;
wapp_intern VkBuffer vert_index_buf = VK_NULL_HANDLE;
wapp_intern VmaAllocation vert_index_buf_alloc = VK_NULL_HANDLE;
wapp_intern ShaderData shader_data = {};
wapp_intern ShaderDataBufferArray shader_data_bufs;
wapp_intern VkFenceArray fences;
wapp_intern VkSemaphoreArray image_acquired_semaphores = nullptr;
wapp_intern VkSemaphoreArray render_completed_semaphores = nullptr;
wapp_intern VkCommandPool command_pool = VK_NULL_HANDLE;
wapp_intern VkCommandBufferArray command_buffers;
wapp_intern TextureArray textures;
wapp_intern VkDescriptorImageInfoArray tex_descriptors;
wapp_intern VkDescriptorSetLayout desc_set_layout_tex = VK_NULL_HANDLE;
wapp_intern VkDescriptorPool desc_pool = VK_NULL_HANDLE;
wapp_intern VkDescriptorSet desc_set_tex = VK_NULL_HANDLE;
wapp_intern Slang::ComPtr<slang::IGlobalSession> slang_global_session;
wapp_intern VkShaderModule shader_module = VK_NULL_HANDLE;
wapp_intern VkPipelineLayout graphics_pipeline_layout = VK_NULL_HANDLE;
wapp_intern VkPipeline graphics_pipeline = VK_NULL_HANDLE;
wapp_intern glm::vec3 camera_position = {0.0f, 0.0f, -6.0f};
wapp_intern GlmVec3Array object_rotations;
// }}}
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 |
VMA_ALLOCATOR_CREATE_EXT_MEMORY_PRIORITY_BIT |
VMA_ALLOCATOR_CREATE_KHR_MAINTENANCE5_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 = colorspace;
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 = present_mode;
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]));
}
// }}}
// }}}
// {{{ Depth Attachment Setup
// {{{ Check Supported Depth Formats
// NOTE (Abdelrahman): Favour 24-bit format for performance
VkFormatArray depth_formats = wapp_array(VkFormat, VK_FORMAT_D24_UNORM_S8_UINT, VK_FORMAT_D32_SFLOAT_S8_UINT);
for (u32 i = 0; i < wapp_array_count(depth_formats); ++i) {
VkFormatProperties2 format_properties = { VK_STRUCTURE_TYPE_FORMAT_PROPERTIES_2 };
vkGetPhysicalDeviceFormatProperties2(physical_device, depth_formats[i], &format_properties);
if (format_properties.formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT) {
depth_format = depth_formats[i];
break;
}
}
check(depth_format != VK_FORMAT_UNDEFINED, EXIT_CODE_NO_SUITABLE_DEPTH_FORMAT);
// }}}
// {{{ Create Depth Image
VkImageCreateInfo depth_image_create_info = {};
depth_image_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
depth_image_create_info.imageType = VK_IMAGE_TYPE_2D;
depth_image_create_info.format = depth_format;
depth_image_create_info.extent = {swapchain_extent.width, swapchain_extent.height, 1};
depth_image_create_info.mipLevels = 1;
depth_image_create_info.arrayLayers = 1;
depth_image_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
depth_image_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
depth_image_create_info.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
depth_image_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
depth_image_create_info.queueFamilyIndexCount = 1;
depth_image_create_info.pQueueFamilyIndices = &queue_family_index;
depth_image_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo depth_alloc_create_info = {};
depth_alloc_create_info.flags = VMA_ALLOCATION_CREATE_DEDICATED_MEMORY_BIT;
depth_alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO;
check(vmaCreateImage(allocator, &depth_image_create_info, &depth_alloc_create_info, &depth_image,
&depth_allocation, nullptr));
// }}}
// {{{ Create Depth Image View
VkImageViewCreateInfo depth_view_create_info = {};
depth_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
depth_view_create_info.image = depth_image;
depth_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
depth_view_create_info.format = depth_format;
depth_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT;
depth_view_create_info.subresourceRange.levelCount = 1;
depth_view_create_info.subresourceRange.layerCount = 1;
check(vkCreateImageView(device, &depth_view_create_info, nullptr, &depth_view));
// }}}
// }}}
wapp_mem_arena_allocator_temp_begin(&arena);
// {{{ Mesh Loading
// {{{ Load Mesh Data
tinyobj::attrib_t attrib;
std::vector<tinyobj::shape_t> shapes;
std::vector<tinyobj::material_t> materials;
check(tinyobj::LoadObj(&attrib, &shapes, &materials, nullptr, nullptr, "assets/suzanne.obj"),
EXIT_CODE_MESH_LOAD_FAILED);
const VkDeviceSize index_count = shapes[0].mesh.indices.size();
VertexArray vertices = wapp_array_alloc_capacity(Vertex, &arena, 128, ARRAY_INIT_NONE);
U16Array indices = wapp_array_alloc_capacity(u16, &arena, 128, ARRAY_INIT_NONE);
for (tinyobj::index_t &index : shapes[0].mesh.indices) {
Vertex v = {};
v.pos = {
attrib.vertices[index.vertex_index * 3],
-attrib.vertices[index.vertex_index * 3 + 1],
attrib.vertices[index.vertex_index * 3 + 2]
};
v.normal = {
attrib.normals[index.normal_index * 3],
-attrib.normals[index.normal_index * 3 + 1],
attrib.normals[index.normal_index * 3 + 2]
};
v.uv = {
attrib.texcoords[index.texcoord_index * 2],
1.0 - attrib.texcoords[index.texcoord_index * 2 + 1]
};
u16 idx = (u16)wapp_array_count(indices);
vertices = wapp_array_append_alloc(Vertex, &arena, vertices, &v, ARRAY_INIT_NONE);
indices = wapp_array_append_alloc(u16, &arena, indices, &idx, ARRAY_INIT_NONE);
}
// }}}
// {{{ Upload to GPU
VkDeviceSize vertex_buf_size = sizeof(Vertex) * wapp_array_count(vertices);
VkDeviceSize index_buf_size = sizeof(u16) * wapp_array_count(indices);
VkBufferCreateInfo buf_create_info = {};
buf_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
buf_create_info.size = vertex_buf_size + index_buf_size;
buf_create_info.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT;
VmaAllocationCreateInfo buf_alloc_create_info = {};
buf_alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT;
buf_alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO;
VmaAllocationInfo buf_alloc_info = {};
check(vmaCreateBuffer(allocator, &buf_create_info, &buf_alloc_create_info, &vert_index_buf,
&vert_index_buf_alloc, &buf_alloc_info));
memcpy(buf_alloc_info.pMappedData, vertices, vertex_buf_size);
memcpy((void *)((u8 *)buf_alloc_info.pMappedData + vertex_buf_size), indices, index_buf_size);
// }}}
// }}}
wapp_mem_arena_allocator_temp_end(&arena);
// {{{ Shader Data Buffers
shader_data_bufs = wapp_array_with_capacity(ShaderDataBuffer, max_frames_in_flight, ARRAY_INIT_FILLED);
for (u32 i = 0; i < max_frames_in_flight; ++i) {
// {{{ Create Buffer
VkBufferUsageFlags2CreateInfo buf_usage_flags = {};
buf_usage_flags.sType = VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO;
buf_usage_flags.usage = VK_BUFFER_USAGE_2_SHADER_DEVICE_ADDRESS_BIT;
VkBufferCreateInfo data_buf_create_info = {};
data_buf_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
data_buf_create_info.size = sizeof(ShaderData);
data_buf_create_info.pNext = &buf_usage_flags;
VmaAllocationCreateInfo data_buf_alloc_create_info = {};
data_buf_alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_HOST_ACCESS_ALLOW_TRANSFER_INSTEAD_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT;
data_buf_alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO;
check(vmaCreateBuffer(allocator, &data_buf_create_info, &data_buf_alloc_create_info,
&shader_data_bufs[i].buffer, &shader_data_bufs[i].allocation,
&shader_data_bufs[i].allocation_info));
// }}}
// {{{ Get Buffer GPU Address
VkBufferDeviceAddressInfo buf_dev_addr_info = {};
buf_dev_addr_info.sType = VK_STRUCTURE_TYPE_BUFFER_DEVICE_ADDRESS_INFO;
buf_dev_addr_info.buffer = shader_data_bufs[i].buffer;
shader_data_bufs[i].device_address = vkGetBufferDeviceAddress(device, &buf_dev_addr_info);
// }}}
}
// }}}
// Synchronization Objects {{{
fences = wapp_array_with_capacity(VkFence, max_frames_in_flight, ARRAY_INIT_FILLED);
image_acquired_semaphores = wapp_array_with_capacity(VkSemaphore, max_frames_in_flight, ARRAY_INIT_FILLED);
// The number of semaphores used to signal rendering needs to match that of the swapchain's images
render_completed_semaphores = wapp_array_alloc_capacity(VkSemaphore, &arena, swapchain_image_count, ARRAY_INIT_FILLED);
check(render_completed_semaphores != nullptr, EXIT_CODE_SYNC_OBJ_CREATE_FAILED);
VkFenceCreateInfo fence_create_info = {};
fence_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fence_create_info.flags = VK_FENCE_CREATE_SIGNALED_BIT;
VkSemaphoreCreateInfo semaphore_create_info = {};
semaphore_create_info.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
for (u32 i = 0; i < max_frames_in_flight; ++i) {
check(vkCreateFence(device, &fence_create_info, NULL, &fences[i]));
check(vkCreateSemaphore(device, &semaphore_create_info, NULL, &image_acquired_semaphores[i]));
}
for (u32 i = 0; i < swapchain_image_count; ++i) {
check(vkCreateSemaphore(device, &semaphore_create_info, NULL, &render_completed_semaphores[i]));
}
// }}}
// Command Pool & Buffers {{{
VkCommandPoolCreateInfo pool_create_info = {};
pool_create_info.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
pool_create_info.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
pool_create_info.queueFamilyIndex = queue_family_index;
check(vkCreateCommandPool(device, &pool_create_info, NULL, &command_pool));
command_buffers = wapp_array_with_capacity(VkCommandBuffer, max_frames_in_flight, ARRAY_INIT_FILLED);
VkCommandBufferAllocateInfo buffer_alloc_info = {};
buffer_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
buffer_alloc_info.commandPool = command_pool;
buffer_alloc_info.commandBufferCount = max_frames_in_flight;
check(vkAllocateCommandBuffers(device, &buffer_alloc_info, command_buffers));
// }}}
wapp_mem_arena_allocator_temp_begin(&arena);
// Texture Images {{{
textures = wapp_array_with_capacity(Texture, texture_count, ARRAY_INIT_FILLED);
tex_descriptors = wapp_array_with_capacity(VkDescriptorImageInfo, texture_count, ARRAY_INIT_FILLED);
constexpr u32 buf_size = 2048;
for (u32 i = 0; i < wapp_array_count(textures); ++i) {
// {{{ Load Textures From File
ktxTexture *texture = nullptr;
char buf[buf_size] = {};
Str8 filename = wapp_str8_buf(buf_size);
wapp_str8_format(&filename, "assets/suzanne%i.ktx", i);
wapp_str8_copy_to_cstr(buf, &filename, buf_size);
ktxTexture_CreateFromNamedFile(buf, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &texture);
// }}}
// {{{ Create Image And View
VkImageCreateInfo tex_create_info = {};
tex_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO;
tex_create_info.imageType = VK_IMAGE_TYPE_2D;
tex_create_info.format = ktxTexture_GetVkFormat(texture);
tex_create_info.extent.width = texture->baseWidth;
tex_create_info.extent.height = texture->baseHeight;
tex_create_info.extent.depth = 1;
tex_create_info.mipLevels = texture->numLevels;
tex_create_info.arrayLayers = 1;
tex_create_info.samples = VK_SAMPLE_COUNT_1_BIT;
tex_create_info.tiling = VK_IMAGE_TILING_OPTIMAL;
tex_create_info.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
tex_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
tex_create_info.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
VmaAllocationCreateInfo tex_alloc_info = {};
tex_alloc_info.usage = VMA_MEMORY_USAGE_AUTO;
check(vmaCreateImage(allocator, &tex_create_info, &tex_alloc_info, &textures[i].image,
&textures[i].allocation, nullptr));
VkImageViewCreateInfo tex_view_create_info = {};
tex_view_create_info.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
tex_view_create_info.image = textures[i].image;
tex_view_create_info.viewType = VK_IMAGE_VIEW_TYPE_2D;
tex_view_create_info.format = tex_create_info.format;
tex_view_create_info.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
tex_view_create_info.subresourceRange.levelCount = tex_create_info.mipLevels;
tex_view_create_info.subresourceRange.layerCount = 1;
check(vkCreateImageView(device, &tex_view_create_info, nullptr, &textures[i].view));
// }}}
// {{{ Create Intermediate Buffer And Upload Texture Data
VkBuffer img_src_buf = {};
VmaAllocation img_src_allocation = {};
VmaAllocationInfo img_src_alloc_info = {};
VkBufferCreateInfo img_src_buf_create_info = {};
img_src_buf_create_info.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
img_src_buf_create_info.size = (u32)texture->dataSize;
img_src_buf_create_info.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT;
img_src_buf_create_info.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VmaAllocationCreateInfo img_src_buf_alloc_create_info = {};
img_src_buf_alloc_create_info.flags = VMA_ALLOCATION_CREATE_HOST_ACCESS_SEQUENTIAL_WRITE_BIT |
VMA_ALLOCATION_CREATE_MAPPED_BIT;
img_src_buf_alloc_create_info.usage = VMA_MEMORY_USAGE_AUTO;
check(vmaCreateBuffer(allocator, &img_src_buf_create_info, &img_src_buf_alloc_create_info,
&img_src_buf, &img_src_allocation, &img_src_alloc_info));
memcpy(img_src_alloc_info.pMappedData, texture->pData, texture->dataSize);
// }}}
// {{{ Create Fence For Synchronization
VkFence fence_one_time = {};
VkFenceCreateInfo fence_one_time_create_info = {};
fence_one_time_create_info.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
check(vkCreateFence(device, &fence_one_time_create_info, NULL, &fence_one_time));
// }}}
// {{{ Allocate Temp Command Buffer
VkCommandBuffer cb_one_time = {};
VkCommandBufferAllocateInfo cb_one_time_alloc_info = {};
cb_one_time_alloc_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
cb_one_time_alloc_info.commandPool = command_pool;
cb_one_time_alloc_info.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
cb_one_time_alloc_info.commandBufferCount = 1;
check(vkAllocateCommandBuffers(device, &cb_one_time_alloc_info, &cb_one_time));
// }}}
// {{{ Record Commands To Copy Texture Data To Image
VkCommandBufferBeginInfo cb_one_time_begin_info = {};
cb_one_time_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
cb_one_time_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
check(vkBeginCommandBuffer(cb_one_time, &cb_one_time_begin_info));
// {{{ Transition Image Layout For Data Transfer
VkImageMemoryBarrier2 barrier_tex_image = {};
barrier_tex_image.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier_tex_image.srcStageMask = VK_PIPELINE_STAGE_2_NONE;
barrier_tex_image.srcAccessMask = VK_ACCESS_2_NONE;
barrier_tex_image.dstStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
barrier_tex_image.dstAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier_tex_image.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
barrier_tex_image.newLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier_tex_image.image = textures[i].image;
barrier_tex_image.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier_tex_image.subresourceRange.levelCount = texture->numLevels;
barrier_tex_image.subresourceRange.layerCount = 1;
VkDependencyInfo barrier_tex_info = {};
barrier_tex_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
barrier_tex_info.imageMemoryBarrierCount = 1;
barrier_tex_info.pImageMemoryBarriers = &barrier_tex_image;
vkCmdPipelineBarrier2(cb_one_time, &barrier_tex_info);
// }}}
// {{{ Copy All Mip Levels
VkBufferImageCopyArray copy_regions = wapp_array_alloc_capacity(VkBufferImageCopy, &arena,
texture->numLevels, ARRAY_INIT_NONE);
for (u32 j = 0; j < texture->numLevels; ++j) {
ktx_size_t mip_offset = 0;
KTX_error_code ret = ktxTexture_GetImageOffset(texture, j, 0, 0, &mip_offset);
VkBufferImageCopy buf_img_copy = {};
buf_img_copy.bufferOffset = mip_offset;
buf_img_copy.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
buf_img_copy.imageSubresource.mipLevel = j;
buf_img_copy.imageSubresource.layerCount = 1;
buf_img_copy.imageExtent.width = texture->baseWidth >> j;
buf_img_copy.imageExtent.height = texture->baseHeight >> j;
buf_img_copy.imageExtent.depth = 1;
wapp_array_append_capped(VkBufferImageCopy, copy_regions, &buf_img_copy);
}
vkCmdCopyBufferToImage(cb_one_time, img_src_buf, textures[i].image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
wapp_array_count(copy_regions), copy_regions);
// }}}
// {{{ Transition Image Layout For Reading
VkImageMemoryBarrier2 barrier_tex_read = {};
barrier_tex_read.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
barrier_tex_read.srcStageMask = VK_PIPELINE_STAGE_2_TRANSFER_BIT;
barrier_tex_read.srcAccessMask = VK_ACCESS_2_TRANSFER_WRITE_BIT;
barrier_tex_read.dstStageMask = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT;
barrier_tex_read.dstAccessMask = VK_ACCESS_2_SHADER_READ_BIT;
barrier_tex_read.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL;
barrier_tex_read.newLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL;
barrier_tex_read.image = textures[i].image;
barrier_tex_read.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
barrier_tex_read.subresourceRange.levelCount = texture->numLevels;
barrier_tex_read.subresourceRange.layerCount = 1;
barrier_tex_info.pImageMemoryBarriers = &barrier_tex_read;
vkCmdPipelineBarrier2(cb_one_time, &barrier_tex_info);
// }}}
check(vkEndCommandBuffer(cb_one_time));
// }}}
// {{{ Submit Command Buffer
VkSubmitInfo cb_submit_info = {};
cb_submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
cb_submit_info.commandBufferCount = 1;
cb_submit_info.pCommandBuffers = &cb_one_time;
check(vkQueueSubmit(queue, 1, &cb_submit_info, fence_one_time));
check(vkWaitForFences(device, 1, &fence_one_time, VK_TRUE, UINT64_MAX));
// }}}
// {{{ Create Sampler And Setup Descriptor
VkSamplerCreateInfo sampler_create_info = {};
sampler_create_info.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO;
sampler_create_info.magFilter = VK_FILTER_LINEAR;
sampler_create_info.minFilter = VK_FILTER_LINEAR;
sampler_create_info.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR;
sampler_create_info.anisotropyEnable = VK_TRUE;
sampler_create_info.maxAnisotropy = 8.0f;
sampler_create_info.maxLod = VK_LOD_CLAMP_NONE;
check(vkCreateSampler(device, &sampler_create_info, NULL, &textures[i].sampler));
tex_descriptors[i].imageView = textures[i].view;
tex_descriptors[i].sampler = textures[i].sampler;
tex_descriptors[i].imageLayout = VK_IMAGE_LAYOUT_READ_ONLY_OPTIMAL;
// }}}
// {{{ Clean Temp Objects
vkDestroyFence(device, fence_one_time, NULL);
vmaDestroyBuffer(allocator, img_src_buf, img_src_allocation);
ktxTexture_Destroy(texture);
// }}}
}
// {{{ Descriptor Indexing
// A descriptor is a handle that describes a shader resource.
//
// Descriptors are added into Descriptor Sets which are allocated from a Descriptor Pool.
// A descriptor set object is an opaque object containing storage for a set of descriptors,
// where the types and number of descriptors is defined by a descriptor set layout.
//
// Each descriptor set has a layout. A descriptor set layout object is defined by an array of
// zero or more descriptor bindings. Each individual descriptor binding is specified by
// a descriptor type, a count (array size) of the number of descriptors in the binding, a set
// of shader stages that can access the binding, and (if using immutable samplers) an array of
// sampler descriptors.
// {{{ Create Descriptor Set Layout
// Create a single binding with variable number of descriptors
u32 texture_count = (u32)wapp_array_count(textures);
VkDescriptorBindingFlags desc_variable_flag = VK_DESCRIPTOR_BINDING_VARIABLE_DESCRIPTOR_COUNT_BIT;
VkDescriptorSetLayoutBindingFlagsCreateInfo desc_binding_flags = {};
desc_binding_flags.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_BINDING_FLAGS_CREATE_INFO;
desc_binding_flags.bindingCount = 1;
desc_binding_flags.pBindingFlags = &desc_variable_flag;
VkDescriptorSetLayoutBinding desc_layout_binding_tex = {};
// We combine texture images and samplers, so the binding's type needs to be
// VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER
desc_layout_binding_tex.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
desc_layout_binding_tex.descriptorCount = texture_count;
// We only need to access this from the fragment shader, so we set stageFlags to
// VK_SHADER_STAGE_FRAGMENT_BIT
desc_layout_binding_tex.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT;
VkDescriptorSetLayoutCreateInfo desc_set_layout_create_info = {};
desc_set_layout_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
desc_set_layout_create_info.pNext = &desc_binding_flags;
desc_set_layout_create_info.bindingCount = 1;
desc_set_layout_create_info.pBindings = &desc_layout_binding_tex;
check(vkCreateDescriptorSetLayout(device, &desc_set_layout_create_info, NULL, &desc_set_layout_tex));
// }}}
// {{{ Create Descriptor Pool To Allocate Descriptors
VkDescriptorPoolSize desc_pool_size = {};
desc_pool_size.type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
desc_pool_size.descriptorCount = texture_count;
VkDescriptorPoolCreateInfo desc_pool_create_info = {};
desc_pool_create_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
desc_pool_create_info.maxSets = 1;
desc_pool_create_info.poolSizeCount = 1;
desc_pool_create_info.pPoolSizes = &desc_pool_size;
check(vkCreateDescriptorPool(device, &desc_pool_create_info, NULL, &desc_pool));
// }}}
// {{{ Allocate Descriptor Sets
// Descriptor indexing allocate info
VkDescriptorSetVariableDescriptorCountAllocateInfo var_desc_count_alloc_info = {};
var_desc_count_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_VARIABLE_DESCRIPTOR_COUNT_ALLOCATE_INFO;
var_desc_count_alloc_info.descriptorSetCount = 1;
var_desc_count_alloc_info.pDescriptorCounts = &texture_count;
VkDescriptorSetAllocateInfo tex_desc_set_alloc_info = {};
tex_desc_set_alloc_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
tex_desc_set_alloc_info.pNext = &var_desc_count_alloc_info;
tex_desc_set_alloc_info.descriptorPool = desc_pool;
tex_desc_set_alloc_info.descriptorSetCount = 1;
tex_desc_set_alloc_info.pSetLayouts = &desc_set_layout_tex;
check(vkAllocateDescriptorSets(device, &tex_desc_set_alloc_info, &desc_set_tex));
// }}}
// {{{ Write Descriptor Set Data
VkWriteDescriptorSet write_desc_set_tex = {};
write_desc_set_tex.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
write_desc_set_tex.dstSet = desc_set_tex;
write_desc_set_tex.dstBinding = 0;
write_desc_set_tex.descriptorCount = (u32)wapp_array_count(tex_descriptors);
write_desc_set_tex.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER;
write_desc_set_tex.pImageInfo = tex_descriptors;
vkUpdateDescriptorSets(device, 1, &write_desc_set_tex, 0, NULL);
// }}}
// }}}
// }}}
wapp_mem_arena_allocator_temp_end(&arena);
// {{{ Shaders
// {{{ Runtime Compile Shaders
// {{{ Initialise Slang Session
// Global session: the connection between the application and the Slang library
slang::createGlobalSession(slang_global_session.writeRef());
// {{{ Define Compilation Scope
slang::TargetDesc target = {};
target.format = SLANG_SPIRV;
target.profile = {slang_global_session->findProfile("spirv_1_4")};
SlangTargetDescArray slang_targets = wapp_array_with_capacity(slang::TargetDesc, 8, ARRAY_INIT_NONE);
wapp_array_append_capped(slang::TargetDesc, slang_targets, &target);
slang::CompilerOptionEntry entry = {};
entry.name = slang::CompilerOptionName::EmitSpirvDirectly;
entry.value = {slang::CompilerOptionValueKind::Int, 1};
SlangCompOptEntryArray slang_options = wapp_array_with_capacity(slang::CompilerOptionEntry, 8, ARRAY_INIT_NONE);
wapp_array_append_capped(slang::CompilerOptionEntry, slang_options, &entry);
// }}}
// {{{ Create Session
slang::SessionDesc slang_session_desc = {};
slang_session_desc.targets = slang_targets;
slang_session_desc.targetCount = wapp_array_count(slang_targets);
slang_session_desc.defaultMatrixLayoutMode = SLANG_MATRIX_LAYOUT_COLUMN_MAJOR;
slang_session_desc.compilerOptionEntries = slang_options;
slang_session_desc.compilerOptionEntryCount = wapp_array_count(slang_options);
Slang::ComPtr<slang::ISession> slang_session;
slang_global_session->createSession(slang_session_desc, slang_session.writeRef());
// }}}
// }}}
// {{{ Load Shader Code
Slang::ComPtr<slang::IModule> slang_module {
slang_session->loadModuleFromSource("triangle", "assets/shader.slang", nullptr, nullptr),
};
Slang::ComPtr<slang::IBlob> spirv;
slang_module->getTargetCode(0, spirv.writeRef());
// }}}
// }}}
// {{{ Create Shader Module
VkShaderModuleCreateInfo module_create_info = {};
module_create_info.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
module_create_info.codeSize = spirv->getBufferSize();
module_create_info.pCode = (u32 *)spirv->getBufferPointer();
check(vkCreateShaderModule(device, &module_create_info, NULL, &shader_module));
// }}}
// }}}
// {{{ Graphics Pipeline
// {{{ Create Pipeline Layout
// The pipeline layout defines the interface between the pipeline and our shaders. We add a push
// constant range and the texture descriptor set layout. A push constant range defines a range of
// values that we can directly push to the shader without having to go through a buffer. We use
// these to pass a pointer to the shader data buffer.
VkPushConstantRange push_constants = {};
push_constants.stageFlags = VK_SHADER_STAGE_VERTEX_BIT;
push_constants.size = sizeof(VkDeviceAddress);
VkPipelineLayoutCreateInfo pl_create_info = {};
pl_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pl_create_info.setLayoutCount = 1;
pl_create_info.pSetLayouts = &desc_set_layout_tex;
pl_create_info.pushConstantRangeCount = 1;
pl_create_info.pPushConstantRanges = &push_constants;
check(vkCreatePipelineLayout(device, &pl_create_info, NULL, &graphics_pipeline_layout));
// }}}
// {{{ Vertex Binding And Attributes
VkVertexInputBindingDescription vertex_binding = {};
vertex_binding.binding = 0;
vertex_binding.stride = sizeof(Vertex);
vertex_binding.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescriptionArray vertex_attributes = wapp_array(
VkVertexInputAttributeDescription,
// Location, Binding, Format, Offset
VkVertexInputAttributeDescription{0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0},
VkVertexInputAttributeDescription{1, 0, VK_FORMAT_R32G32B32_SFLOAT, offsetof(Vertex, normal)},
VkVertexInputAttributeDescription{2, 0, VK_FORMAT_R32G32_SFLOAT, offsetof(Vertex, uv)}
);
VkPipelineVertexInputStateCreateInfo vertex_input_state = {};
vertex_input_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertex_input_state.vertexBindingDescriptionCount = 1;
vertex_input_state.pVertexBindingDescriptions = &vertex_binding;
vertex_input_state.vertexAttributeDescriptionCount = wapp_array_count(vertex_attributes);
vertex_input_state.pVertexAttributeDescriptions = vertex_attributes;
// }}}
// {{{ Input Assembly
VkPipelineInputAssemblyStateCreateInfo input_assembly_state = {};
input_assembly_state.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
input_assembly_state.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
// }}}
// {{{ Shader Stages
VkPipelineShaderStageCreateInfoArray shader_stages = wapp_array_with_capacity(VkPipelineShaderStageCreateInfo, 2, ARRAY_INIT_FILLED);
// Vertex Shader
shader_stages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
shader_stages[0].module = shader_module;
shader_stages[0].pName = "main";
// Fragment Shader
shader_stages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
shader_stages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
shader_stages[1].module = shader_module;
shader_stages[1].pName = "main";
// }}}
// {{{ Viewport And Scissor Dynamic States
VkPipelineViewportStateCreateInfo viewport_state = {};
viewport_state.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
viewport_state.viewportCount = 1;
viewport_state.scissorCount = 1;
VkDynamicStateArray dynamic_states = wapp_array(VkDynamicState, VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR);
VkPipelineDynamicStateCreateInfo dynamic_state = {};
dynamic_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamic_state.dynamicStateCount = wapp_array_count(dynamic_states);
dynamic_state.pDynamicStates = dynamic_states;
// }}}
// {{{ Depth/Stencil State
VkPipelineDepthStencilStateCreateInfo depth_stencil_state = {};
depth_stencil_state.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO;
depth_stencil_state.depthTestEnable = VK_TRUE;
depth_stencil_state.depthWriteEnable = VK_TRUE;
depth_stencil_state.depthCompareOp = VK_COMPARE_OP_LESS_OR_EQUAL;
// }}}
// {{{ Dynamic Rendering
VkPipelineRenderingCreateInfo rendering_create_info = {};
rendering_create_info.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO;
rendering_create_info.colorAttachmentCount = 1;
rendering_create_info.pColorAttachmentFormats = &image_format;
rendering_create_info.depthAttachmentFormat = depth_format;
// }}}
// {{{ Blending State
// Blending disabled
VkPipelineColorBlendAttachmentState blend_attachment = {};
blend_attachment.colorWriteMask = 0xf;
VkPipelineColorBlendStateCreateInfo blend_state = {};
blend_state.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
blend_state.attachmentCount = 1;
blend_state.pAttachments = &blend_attachment;
// }}}
// {{{ Rasterization State
VkPipelineRasterizationStateCreateInfo rasterization_state = {};
rasterization_state.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterization_state.lineWidth = 1.0f;
// }}}
// {{{ Multisampling State
VkPipelineMultisampleStateCreateInfo multisampling_state = {};
multisampling_state.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling_state.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
// }}}
// {{{ Create Graphics Pipeline
VkGraphicsPipelineCreateInfo graphics_pipeline_create_info = {};
graphics_pipeline_create_info.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
graphics_pipeline_create_info.pNext = &rendering_create_info;
graphics_pipeline_create_info.stageCount = wapp_array_count(shader_stages);
graphics_pipeline_create_info.pStages = shader_stages;
graphics_pipeline_create_info.pVertexInputState = &vertex_input_state;
graphics_pipeline_create_info.pInputAssemblyState = &input_assembly_state;
graphics_pipeline_create_info.pViewportState = &viewport_state;
graphics_pipeline_create_info.pRasterizationState = &rasterization_state;
graphics_pipeline_create_info.pMultisampleState = &multisampling_state;
graphics_pipeline_create_info.pDepthStencilState = &depth_stencil_state;
graphics_pipeline_create_info.pColorBlendState = &blend_state;
graphics_pipeline_create_info.pDynamicState = &dynamic_state;
graphics_pipeline_create_info.layout = graphics_pipeline_layout;
check(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &graphics_pipeline_create_info, NULL, &graphics_pipeline));
// }}}
// }}}
// {{{ Render Loop
/**
* Render loop overview:
* - Wait on fence
* - Acquire next image
* - Update shader data
* - Record command buffer
* - Submit command buffer
* - Present image
* - Poll events
*/
object_rotations = wapp_array_with_capacity(glm::vec3, instance_count, ARRAY_INIT_FILLED);
u64 last_time = SDL_GetTicks();
SDL_Event event = {};
while (running) {
// {{{ Wait On Fence
check(vkWaitForFences(device, 1, &fences[frame_index], VK_TRUE, UINT64_MAX));
check(vkResetFences(device, 1, &fences[frame_index]));
// }}}
// {{{ Acquire Next Swapchain Image
check_swapchain(vkAcquireNextImageKHR(device, swapchain, UINT64_MAX,
image_acquired_semaphores[frame_index], VK_NULL_HANDLE,
&image_index));
// }}}
// {{{ Update Shader Data
shader_data.projection = glm::perspective(glm::radians(45.0f), (f32)window_size.x / (f32)window_size.y,
0.1f, 32.0f);
shader_data.view = glm::translate(glm::mat4(1.0f), camera_position);
for (i32 i = 0; i < instance_count; ++i) {
glm::vec3 instance_pos = glm::vec3((f32)(i - 1) * 3.0f, 0.0f, 0.0f);
shader_data.model[i] = glm::translate(glm::mat4(1.0f), instance_pos) *
glm::mat4_cast(glm::quat(object_rotations[i]));
}
memcpy(shader_data_bufs[frame_index].allocation_info.pMappedData, &shader_data, sizeof(ShaderData));
// }}}
// {{{ Record Command Buffer
VkCommandBuffer cb = command_buffers[frame_index];
check(vkResetCommandBuffer(cb, 0));
VkCommandBufferBeginInfo cmd_begin_info = {};
cmd_begin_info.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
cmd_begin_info.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
check(vkBeginCommandBuffer(cb, &cmd_begin_info));
VkImageMemoryBarrier2Array memory_barriers = wapp_array_with_capacity(VkImageMemoryBarrier2, 2, ARRAY_INIT_FILLED);
// Color Attachment
memory_barriers[0].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
memory_barriers[0].srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barriers[0].srcAccessMask = 0;
memory_barriers[0].dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
memory_barriers[0].dstAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
memory_barriers[0].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
memory_barriers[0].newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
memory_barriers[0].image = swapchain_images[image_index];
memory_barriers[0].subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
memory_barriers[0].subresourceRange.levelCount = 1;
memory_barriers[0].subresourceRange.layerCount = 1;
// Depth Attachment
memory_barriers[1].sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
memory_barriers[1].srcStageMask = VK_PIPELINE_STAGE_2_LATE_FRAGMENT_TESTS_BIT;
memory_barriers[1].srcAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
memory_barriers[1].dstStageMask = VK_PIPELINE_STAGE_2_EARLY_FRAGMENT_TESTS_BIT;
memory_barriers[1].dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT;
memory_barriers[1].oldLayout = VK_IMAGE_LAYOUT_UNDEFINED;
memory_barriers[1].newLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
memory_barriers[1].image = depth_image;
memory_barriers[1].subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
memory_barriers[1].subresourceRange.levelCount = 1;
memory_barriers[1].subresourceRange.layerCount = 1;
// {{{ Transition Layout Color And Depth Images
VkDependencyInfo barrier_dependency_info = {};
barrier_dependency_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
barrier_dependency_info.imageMemoryBarrierCount = wapp_array_count(memory_barriers);
barrier_dependency_info.pImageMemoryBarriers = memory_barriers;
vkCmdPipelineBarrier2(cb, &barrier_dependency_info);
// }}}
// {{{ Dynamic Rendering Commands
VkClearValue color_clear_value = {};
color_clear_value.color = { 0.0f, 0.0f, 0.0f, 0.0f };
VkClearValue depth_clear_value = {};
depth_clear_value.depthStencil = { 1.0f, 0 };
VkRenderingAttachmentInfo color_attachment = {};
color_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
color_attachment.imageView = swapchain_views[image_index];
color_attachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
color_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
color_attachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
color_attachment.clearValue = color_clear_value;
VkRenderingAttachmentInfo depth_attachment = {};
depth_attachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
depth_attachment.imageView = depth_view;
depth_attachment.imageLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
depth_attachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
depth_attachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
depth_attachment.clearValue = depth_clear_value;
VkRect2D render_area = {};
render_area.extent = { (u32)window_size.x, (u32)window_size.y };
VkRenderingInfo render_info = {};
render_info.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
render_info.renderArea = render_area;
render_info.layerCount = 1;
render_info.colorAttachmentCount = 1;
render_info.pColorAttachments = &color_attachment;
render_info.pDepthAttachment = &depth_attachment;
vkCmdBeginRendering(cb, &render_info);
// {{{ Set Viewport And Scissor Dynamic States
VkViewport viewport = {};
viewport.width = window_size.x;
viewport.height = window_size.y;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(cb, 0, 1, &viewport);
VkRect2D scissor = {};
scissor.extent = render_area.extent;
vkCmdSetScissor(cb, 0, 1, &scissor);
// }}}
// {{{ Bind Resources
vkCmdBindPipeline(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
vkCmdBindDescriptorSets(cb, VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline_layout, 0, 1, &desc_set_tex, 0, NULL);
VkDeviceSize vertex_offset = {};
vkCmdBindVertexBuffers(cb, 0, 1, &vert_index_buf, &vertex_offset);
vkCmdBindIndexBuffer(cb, vert_index_buf, vertex_buf_size, VK_INDEX_TYPE_UINT16);
// }}}
// {{{ Send ShaderData Buffer Device Address To Shader
vkCmdPushConstants(cb, graphics_pipeline_layout, VK_SHADER_STAGE_VERTEX_BIT, 0,
sizeof(VkDeviceAddress), &shader_data_bufs[frame_index].device_address);
// }}}
// {{{ Draw Commands
vkCmdDrawIndexed(cb, index_count, instance_count, 0, 0, 0);
// }}}
vkCmdEndRendering(cb);
// }}}
// {{{ Transition Color Attachment For Presentation
VkImageMemoryBarrier2 present_barrier = {};
present_barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2;
present_barrier.srcStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
present_barrier.srcAccessMask = VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT;
present_barrier.dstStageMask = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
present_barrier.dstAccessMask = 0;
present_barrier.oldLayout = VK_IMAGE_LAYOUT_ATTACHMENT_OPTIMAL;
present_barrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;
present_barrier.image = swapchain_images[image_index];
present_barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
present_barrier.subresourceRange.levelCount = 1;
present_barrier.subresourceRange.layerCount = 1;
VkDependencyInfo present_dependency_info = {};
present_dependency_info.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO;
present_dependency_info.imageMemoryBarrierCount = 1;
present_dependency_info.pImageMemoryBarriers = &present_barrier;
vkCmdPipelineBarrier2(cb, &present_dependency_info);
// }}}
check(vkEndCommandBuffer(cb));
// }}}
// {{{ Submit Command Buffer
VkPipelineStageFlags wait_stages = VK_PIPELINE_STAGE_2_COLOR_ATTACHMENT_OUTPUT_BIT;
// The wait semaphore in pWaitSemaphores ensures that execution of the command buffer does not
// begin until the swapchain image we want to render to has been acquired. This means
// presentation has finished and the image has been released by the presentation engine. This is
// required because swapchain images are owned by the presentation engine rather than by our
// application. The pipeline stage specified in pWaitDstStageMask makes that wait happen at the
// color attachment output stage, so in theory the GPU may already begin work on earlier
// pipeline stages, such as vertex fetching. The signal semaphore in pSignalSemaphores, on the
// other hand, is signaled by the GPU once command buffer execution has completed and ensures
// that presentation does not begin until the command buffer has finished execution. Together,
// these guarantees prevent read/write hazards that could cause the GPU to read from or write
// to resources that are still in use.
VkSubmitInfo submit_info = {};
submit_info.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submit_info.waitSemaphoreCount = 1;
submit_info.pWaitSemaphores = &image_acquired_semaphores[frame_index];
submit_info.pWaitDstStageMask = &wait_stages;
submit_info.commandBufferCount = 1;
submit_info.pCommandBuffers = &cb;
submit_info.signalSemaphoreCount = 1;
submit_info.pSignalSemaphores = &render_completed_semaphores[image_index];
check(vkQueueSubmit(queue, 1, &submit_info, fences[frame_index]));
// }}}
frame_index = (frame_index + 1) % max_frames_in_flight;
// {{{ Present Image
VkPresentInfoKHR present_info = {};
present_info.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
present_info.waitSemaphoreCount = 1;
present_info.pWaitSemaphores = &render_completed_semaphores[image_index];
present_info.swapchainCount = 1;
present_info.pSwapchains = &swapchain;
present_info.pImageIndices = &image_index;
check_swapchain(vkQueuePresentKHR(queue, &present_info));
// }}}
// {{{ Poll Events
f32 elapsed_time = (SDL_GetTicks() - last_time) / 1000.0f;
last_time = SDL_GetTicks();
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;
}
// Select active model instance
if (event.key.key == SDLK_PLUS || event.key.key == SDLK_KP_PLUS || event.key.key == SDLK_EQUALS) {
shader_data.selected = (shader_data.selected < 2) ? shader_data.selected + 1 : 0;
}
if (event.key.key == SDLK_MINUS || event.key.key == SDLK_KP_MINUS) {
shader_data.selected = (shader_data.selected > 0) ? shader_data.selected - 1 : 2;
}
break;
// Rotate the selected object with mouse drag
case SDL_EVENT_MOUSE_MOTION:
if (event.button.button == SDL_BUTTON_LEFT) {
object_rotations[shader_data.selected].x -= (float)event.motion.yrel * elapsed_time;
object_rotations[shader_data.selected].y += (float)event.motion.xrel * elapsed_time;
}
break;
// Zooming with the mouse wheel
case SDL_EVENT_MOUSE_WHEEL:
camera_position.z += (float)event.wheel.y * elapsed_time * 10.0f;
break;
// Window resize
case SDL_EVENT_WINDOW_RESIZED:
update_swapchain = true;
break;
}
}
// }}}
}
// }}}
// {{{ Cleanup
check(vkQueueWaitIdle(queue));
vkDestroyPipeline(device, graphics_pipeline, NULL);
vkDestroyPipelineLayout(device, graphics_pipeline_layout, NULL);
vkDestroyShaderModule(device, shader_module, NULL);
vkDestroyDescriptorPool(device, desc_pool, NULL);
vkDestroyDescriptorSetLayout(device, desc_set_layout_tex, NULL);
for (u32 i = 0; i < wapp_array_count(textures); ++i) {
vkDestroySampler(device, textures[i].sampler, NULL);
vkDestroyImageView(device, textures[i].view, NULL);
vmaDestroyImage(allocator, textures[i].image, textures[i].allocation);
}
vkFreeCommandBuffers(device, command_pool, max_frames_in_flight, command_buffers);
vkDestroyCommandPool(device, command_pool, NULL);
for (u32 i = 0; i < swapchain_image_count; ++i) {
vkDestroySemaphore(device, render_completed_semaphores[i], NULL);
}
for (u32 i = 0; i < max_frames_in_flight; ++i) {
vkDestroySemaphore(device, image_acquired_semaphores[i], NULL);
vkDestroyFence(device, fences[i], NULL);
}
for (u32 i = 0; i < max_frames_in_flight; ++i) {
vmaDestroyBuffer(allocator, shader_data_bufs[i].buffer, shader_data_bufs[i].allocation);
}
vmaDestroyBuffer(allocator, vert_index_buf, vert_index_buf_alloc);
vkDestroyImageView(device, depth_view, nullptr);
vmaDestroyImage(allocator, depth_image, depth_allocation);
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 EXIT_CODE_SUCCESS;
}
// {{{ 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);
}
}
// }}}