650 lines
27 KiB
C++
650 lines
27 KiB
C++
// vim:fileencoding=utf-8:foldmethod=marker
|
|
|
|
#include "wapp/wapp.h"
|
|
#include "vulkan_profiles/vulkan_profiles.h"
|
|
#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 <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>
|
|
|
|
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,
|
|
};
|
|
|
|
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;
|
|
};
|
|
|
|
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;
|
|
|
|
wapp_intern inline void check(VkResult result);
|
|
wapp_intern inline void check_swapchain(VkResult result);
|
|
wapp_intern inline void check(bool result, i32 code);
|
|
|
|
wapp_intern constexpr u32 max_frames_in_flight = 2;
|
|
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 = nullptr;
|
|
wapp_intern VkSemaphoreArray image_acquired_semaphores = nullptr;
|
|
wapp_intern VkSemaphoreArray render_completed_semaphores = nullptr;
|
|
|
|
int main() {
|
|
// {{{ Initialisation
|
|
Allocator arena = wapp_mem_arena_allocator_init(MiB(64));
|
|
|
|
check(SDL_Init(SDL_INIT_VIDEO), EXIT_CODE_SDL_INIT_FAILED);
|
|
check(SDL_Vulkan_LoadLibrary(nullptr), EXIT_CODE_VULKAN_LIB_LOAD_FAILED);
|
|
check(volkInitialize());
|
|
|
|
display_scale = SDL_GetDisplayContentScale(SDL_GetPrimaryDisplay());
|
|
window = SDL_CreateWindow("How To Vulkan", (i32)(display_scale * 1920),
|
|
(i32)(display_scale * 1080), SDL_WINDOW_VULKAN);
|
|
check(window != nullptr, EXIT_CODE_WINDOW_CREATION_FAILED);
|
|
// }}}
|
|
|
|
// {{{ Get Vulkan Profile
|
|
VkBool32 supported = VK_FALSE;
|
|
|
|
VpProfileProperties profile = {
|
|
.profileName = VP_LEARN_HOW_TO_VULKAN_2026_NAME,
|
|
.specVersion = VP_LEARN_HOW_TO_VULKAN_2026_SPEC_VERSION,
|
|
};
|
|
// }}}
|
|
|
|
// {{{ Instance Creation
|
|
// {{{ Get Platform Extensions
|
|
u32 instance_extension_count = 0;
|
|
const char *const *instance_extensions = SDL_Vulkan_GetInstanceExtensions(&instance_extension_count);
|
|
// }}}
|
|
|
|
// {{{ Check Instance Profile Support
|
|
check(vpGetInstanceProfileSupport(NULL, &profile, &supported));
|
|
check(supported, EXIT_CODE_NO_INSTANCE_SUPPORT);
|
|
// }}}
|
|
|
|
VkApplicationInfo app_info = {};
|
|
app_info.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
|
|
app_info.apiVersion = VP_LEARN_HOW_TO_VULKAN_2026_MIN_API_VERSION;
|
|
|
|
// {{{ Create Instance
|
|
VkInstanceCreateInfo instance_info = {};
|
|
instance_info.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
|
|
instance_info.pApplicationInfo = &app_info;
|
|
instance_info.enabledExtensionCount = instance_extension_count;
|
|
instance_info.ppEnabledExtensionNames = instance_extensions;
|
|
|
|
VpInstanceCreateInfo vp_instance_info = {};
|
|
vp_instance_info.pCreateInfo = &instance_info;
|
|
vp_instance_info.enabledFullProfileCount = 1;
|
|
vp_instance_info.pEnabledFullProfiles = &profile;
|
|
|
|
check(vpCreateInstance(&vp_instance_info, NULL, &instance));
|
|
// }}}
|
|
|
|
volkLoadInstance(instance);
|
|
// }}}
|
|
|
|
wapp_mem_arena_allocator_temp_begin(&arena);
|
|
|
|
// {{{ Physical Device Retrieval
|
|
// {{{ Get Physical Device Count
|
|
u32 physical_device_count = 0;
|
|
check(vkEnumeratePhysicalDevices(instance, &physical_device_count, nullptr));
|
|
check(physical_device_count > 0, EXIT_CODE_NO_PHYSICAL_DEVICES);
|
|
// }}}
|
|
|
|
// {{{ Get All Physical Devices
|
|
VkPhysicalDeviceArray physical_devices =
|
|
wapp_array_alloc_capacity(VkPhysicalDevice, &arena, physical_device_count, ARRAY_INIT_FILLED);
|
|
check(physical_devices != nullptr, EXIT_CODE_ALLOCATION_FAILURE);
|
|
|
|
check(vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices));
|
|
|
|
// Set the count in case it changes after the second call to vkEnumeratePhysicalDevices
|
|
wapp_array_set_count(physical_devices, physical_device_count);
|
|
// }}}
|
|
|
|
// {{{ Choose Appropriate Physical Device
|
|
i32 index = -1;
|
|
for (u32 i = 0; i < wapp_array_count(physical_devices); ++i) {
|
|
VkPhysicalDeviceProperties2 base_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 };
|
|
vkGetPhysicalDeviceProperties2(physical_devices[i], &base_properties);
|
|
switch (base_properties.properties.deviceType) {
|
|
case VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU: {
|
|
if (base_properties.properties.apiVersion >= VP_LEARN_HOW_TO_VULKAN_2026_MIN_API_VERSION) {
|
|
index = i;
|
|
}
|
|
} break;
|
|
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: {
|
|
if (base_properties.properties.apiVersion >= VP_LEARN_HOW_TO_VULKAN_2026_MIN_API_VERSION) {
|
|
index = i;
|
|
}
|
|
} break;
|
|
default: continue;
|
|
}
|
|
}
|
|
check(index != -1, EXIT_CODE_NO_SUITABLE_PHYSICAL_DEVICE);
|
|
|
|
physical_device = physical_devices[index];
|
|
|
|
// {{{ Print Physical Device Information
|
|
VkPhysicalDeviceDriverProperties p_device_driver_props = {};
|
|
p_device_driver_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DRIVER_PROPERTIES;
|
|
VkPhysicalDeviceProperties2 p_device_props = {};
|
|
p_device_props.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2;
|
|
p_device_props.pNext = &p_device_driver_props;
|
|
vkGetPhysicalDeviceProperties2(physical_device, &p_device_props);
|
|
|
|
u32 api_major = VK_API_VERSION_MAJOR(p_device_props.properties.apiVersion);
|
|
u32 api_minor = VK_API_VERSION_MINOR(p_device_props.properties.apiVersion);
|
|
u32 api_patch = VK_API_VERSION_PATCH(p_device_props.properties.apiVersion);
|
|
u32 api_variant = VK_API_VERSION_VARIANT(p_device_props.properties.apiVersion);
|
|
std::cout << "Selected GPU: " << p_device_props.properties.deviceName << '\n'
|
|
<< "Driver version: " << p_device_driver_props.driverInfo << '\n'
|
|
<< "API version: " << api_major << '.' << api_minor << '.' << api_patch << '.'
|
|
<< api_variant << '\n';
|
|
// }}}
|
|
// }}}
|
|
|
|
// {{{ Check Physical Device Profile Support
|
|
check(vpGetPhysicalDeviceProfileSupport(instance, physical_device, &profile, &supported));
|
|
check(supported, EXIT_CODE_NO_PHYSICAL_DEVICE_SUPPORT);
|
|
// }}}
|
|
// }}}
|
|
|
|
// {{{ Surface Creation
|
|
check(SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface), EXIT_CODE_SURFACE_CREATION_FAILED);
|
|
check(SDL_GetWindowSize(window, &window_size.x, &window_size.y), EXIT_CODE_GET_WINDOW_SIZE_FAILED);
|
|
VkSurfaceCapabilitiesKHR surface_caps{};
|
|
check(vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &surface_caps));
|
|
// }}}
|
|
|
|
// {{{ Queue Family Retrieval
|
|
// {{{ Get Queue Family Count
|
|
u32 queue_family_count = 0;
|
|
vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_count, nullptr);
|
|
check(queue_family_count > 0, EXIT_CODE_NO_QUEUE_FAMILIES);
|
|
// }}}
|
|
|
|
// {{{ Get All Queue Families
|
|
VkQueueFamilyProperties2Array queue_family_properties =
|
|
wapp_array_alloc_capacity(VkQueueFamilyProperties2, &arena, queue_family_count, ARRAY_INIT_FILLED);
|
|
check(queue_family_properties != nullptr, EXIT_CODE_ALLOCATION_FAILURE);
|
|
|
|
for (u32 i = 0; i < wapp_array_count(queue_family_properties); ++i) {
|
|
queue_family_properties[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
|
|
}
|
|
|
|
vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_count, queue_family_properties);
|
|
|
|
// Set the count in case it changes after the second call to vkGetPhysicalDeviceQueueFamilyProperties2
|
|
wapp_array_set_count(queue_family_properties, queue_family_count);
|
|
// }}}
|
|
|
|
// {{{ Choose Appropriate Queue Family
|
|
i32 family_index = -1;
|
|
for (u32 i = 0; i < queue_family_count; ++i) {
|
|
if ((queue_family_properties[i].queueFamilyProperties.queueFlags &
|
|
(VK_QUEUE_GRAPHICS_BIT | VK_QUEUE_COMPUTE_BIT | VK_QUEUE_TRANSFER_BIT)) != 0) {
|
|
family_index = i;
|
|
break;
|
|
}
|
|
}
|
|
|
|
check(family_index != -1, EXIT_CODE_NO_SUITABLE_QUEUE_FAMILY);
|
|
|
|
queue_family_index = (u32)family_index;
|
|
// }}}
|
|
|
|
// {{{ Check Presentation Support
|
|
check(SDL_Vulkan_GetPresentationSupport(instance, physical_device, queue_family_index),
|
|
EXIT_CODE_NO_PRESENTATION_SUPPORT);
|
|
// }}}
|
|
// }}}
|
|
|
|
wapp_mem_arena_allocator_temp_end(&arena);
|
|
|
|
// {{{ Device And Queue Creation
|
|
// {{{ Create Device
|
|
f32 queue_priority = 1.0f;
|
|
VkDeviceQueueCreateInfo device_queue_create_info = {};
|
|
device_queue_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
|
|
device_queue_create_info.queueCount = 1;
|
|
device_queue_create_info.queueFamilyIndex = queue_family_index;
|
|
device_queue_create_info.pQueuePriorities = &queue_priority;
|
|
|
|
VkDeviceCreateInfo device_create_info = {};
|
|
device_create_info.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
|
|
device_create_info.queueCreateInfoCount = 1;
|
|
device_create_info.pQueueCreateInfos = &device_queue_create_info;
|
|
|
|
VpDeviceCreateInfo vp_device_create_info = {};
|
|
vp_device_create_info.pCreateInfo = &device_create_info;
|
|
vp_device_create_info.enabledFullProfileCount = 1;
|
|
vp_device_create_info.pEnabledFullProfiles = &profile;
|
|
|
|
check(vpCreateDevice(physical_device, &vp_device_create_info, nullptr, &device));
|
|
// }}}
|
|
|
|
// {{{ Get Queue
|
|
vkGetDeviceQueue(device, queue_family_index, 0, &queue);
|
|
// }}}
|
|
|
|
volkLoadDevice(device);
|
|
// }}}
|
|
|
|
// {{{ Vulkan Memory Allocator (VMA) Setup
|
|
VmaVulkanFunctions vk_functions = {};
|
|
vk_functions.vkGetInstanceProcAddr = vkGetInstanceProcAddr;
|
|
vk_functions.vkGetDeviceProcAddr = vkGetDeviceProcAddr;
|
|
vk_functions.vkCreateImage = vkCreateImage;
|
|
|
|
VmaAllocatorCreateInfo allocator_create_info = {};
|
|
allocator_create_info.flags = VMA_ALLOCATOR_CREATE_BUFFER_DEVICE_ADDRESS_BIT |
|
|
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<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);
|
|
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, 3, 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_alloc_capacity(VkFence, &arena, max_frames_in_flight, ARRAY_INIT_FILLED);
|
|
check(fences != nullptr, EXIT_CODE_SYNC_OBJ_CREATE_FAILED);
|
|
|
|
image_acquired_semaphores = wapp_array_alloc_capacity(VkSemaphore, &arena, max_frames_in_flight, ARRAY_INIT_FILLED);
|
|
check(image_acquired_semaphores != nullptr, EXIT_CODE_SYNC_OBJ_CREATE_FAILED);
|
|
|
|
// 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]));
|
|
}
|
|
// }}}
|
|
|
|
// {{{ Render Loop
|
|
SDL_Event event = {};
|
|
while (running) {
|
|
while (SDL_PollEvent(&event)) {
|
|
switch (event.type) {
|
|
case SDL_EVENT_QUIT:
|
|
running = false;
|
|
break;
|
|
case SDL_EVENT_KEY_DOWN:
|
|
if (event.key.key == SDLK_ESCAPE) {
|
|
running = false;
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
// }}}
|
|
|
|
// {{{ Cleanup
|
|
|
|
for (u32 i = 0; i < swapchain_image_count; ++i) {
|
|
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);
|
|
}
|
|
}
|
|
// }}}
|