// vim:fileencoding=utf-8:foldmethod=marker #include "wapp/wapp.h" #include "vulkan_profiles/vulkan_profiles.h" #include "ktx.h" #include "ktxvulkan.h" #include #include #include #include #include #include #include #include #include #include #define VMA_IMPLEMENTATION #include #include #include #include #include #include #include // {{{ 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; // }}} // {{{ 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 constexpr u32 max_frames_in_flight = 2; 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; 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; // }}} 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; 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 shapes; std::vector 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); wapp_array_append_alloc(Vertex, &arena, vertices, &v, ARRAY_INIT_NONE); 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(vertices); 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 data_buf_usage_flags = {}; data_buf_usage_flags.sType = VK_STRUCTURE_TYPE_BUFFER_USAGE_FLAGS_2_CREATE_INFO; data_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.usage = VK_BUFFER_USAGE_SHADER_DEVICE_ADDRESS_BIT; data_buf_create_info.pNext = &data_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_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); // {{{ 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 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 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); } } // }}}