Files
vulkan-spec-walkthrough/main.c
T
2026-05-25 11:04:30 +01:00

1311 lines
56 KiB
C

// vim:fileencoding=utf-8:foldmethod=marker
#include "volk/volk.h"
#include "vulkan/vulkan_core.h"
#include "wapp/wapp.h"
#include <stddef.h>
#define CGLM_FORCE_DEPTH_ZERO_TO_ONE
#include "cglm/cglm.h"
#include <SDL3/SDL.h>
#include <SDL3/SDL_events.h>
#include <SDL3/SDL_init.h>
#include <SDL3/SDL_keycode.h>
#include <SDL3/SDL_timer.h>
#include <SDL3/SDL_video.h>
#include <SDL3/SDL_vulkan.h>
#include <stdint.h>
#include <stdio.h>
#include <string.h>
#define WINDOW_WIDTH 1600
#define WINDOW_HEIGHT 1200
#define FRAMES_IN_FLIGHT 3
#define FRAME_TIME 1.0 / 60.0 * 1e+9
#define SHADER_FILE "09_shader_base.spv"
#define SHADER_COUNT 2
#define MIN_DEPTH 0.0f
#define MAX_DEPTH 1.0f
typedef struct {
vec3 position;
vec3 color;
} Vertex;
WAPP_DEF_ARRAY_TYPE(VkFence, VkFenceArray);
WAPP_DEF_ARRAY_TYPE(VkSemaphore, VkSemaphoreArray);
WAPP_DEF_ARRAY_TYPE(VkImage, VkImageArray);
WAPP_DEF_ARRAY_TYPE(VkImageView, VkImageViewArray);
WAPP_DEF_ARRAY_TYPE(Vertex, VertexArray);
enum ReturnCodes {
RETURN_SUCCESS,
RETURN_FAILED_TO_GET_INSTANCE_EXTENSIONS,
RETURN_FAILED_TO_ALLOCATE_EXTENSION_NAMES,
RETURN_FAILED_TO_GET_VK_GET_INSTANCE_PROC_ADDR,
RETURN_FAILED_TO_INITIALISE_VOLK,
RETURN_FAILED_TO_QUERY_API_VERSION,
RETURN_FAILED_TO_CREATE_INSTANCE,
RETURN_FAILED_TO_CREATE_SURFACE,
RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_COUNT,
RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_PHYSICAL_DEVICES,
RETURN_FAILED_TO_RETRIEVE_PHYSICAL_DEVICES,
RETURN_FAILED_TO_FIND_SUITABLE_PHYSICAL_DEVICE,
RETURN_NO_QUEUE_FAMILY_PROPERTIES_FOUND,
RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_QUEUE_FAMILY_PROPERTIES,
RETURN_SELECTED_DEVICE_DOES_NOT_SUPPORT_GRAPHICS,
RETURN_FAILED_TO_ENUMERATE_PHYSICAL_DEVICE_EXTENSIONS,
RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_EXTENSIONS,
RETURN_FAILED_TO_CREATE_LOGICAL_DEVICE,
RETURN_FAILED_TO_CREATE_COMMAND_POOL,
RETURN_FAILED_TO_ALLOCATE_COMMAND_BUFFER,
RETURN_FAILED_TO_QUERY_SURFACE_CAPABILITIES,
RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_FORMATS,
RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_FORMATS,
RETURN_FAILED_TO_QUERY_SURPPORTED_SURFACE_FORMATS,
RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_PRESENT_MODES,
RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_PRESENT_MODES,
RETURN_FAILED_TO_QUERY_SURPPORTED_SURFACE_PRESENT_MODES,
RETURN_FAILED_TO_CREATE_SWAPCHAIN,
RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGE_COUNT,
RETURN_FAILED_TO_ALLOCATE_SWAPCHAIN_IMAGES_MEMORY,
RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGES,
RETURN_FAILED_TO_CREATE_IMAGE_VIEWS,
RETURN_FAILED_TO_CREATE_SYNC_OBJECTS,
RETURN_FAILED_TO_CREATE_SHADER_MODULE,
RETURN_FAILED_TO_CREATE_DEPTH_IMAGE,
RETURN_FAILED_TO_FIND_SUITABLE_MEMORY_TYPE_FOR_DEPTH_IMAGE,
RETURN_FAILED_TO_ALLOCATE_DEPTH_IMAGE_MEMORY,
RETURN_FAILED_TO_BIND_DEPTH_IMAGE_MEMORY,
RETURN_FAILED_TO_CREATE_DEPTH_IMAGE_VIEW,
RETURN_FAILED_TO_CREATE_COPY_COMMAND_BUFFER,
RETURN_FAILED_TO_CREATE_VERTEX_BUFFER,
RETURN_FAILED_TO_CREATE_INDEX_BUFFER,
RETURN_FAILED_TO_COPY_VERTEX_AND_INDEX_DATA,
RETURN_FAILED_TO_CREATE_PIPELINE_LAYOUT,
RETURN_FAILED_TO_CREATE_GRAPHICS_PIPELINE,
};
VkInstance instance = VK_NULL_HANDLE;
VkPhysicalDevice physical_device = VK_NULL_HANDLE;
u32 queue_family_index = VK_QUEUE_FAMILY_IGNORED;
VkDevice device = VK_NULL_HANDLE;
VkQueue queue = VK_NULL_HANDLE;
VkCommandPool command_pool = VK_NULL_HANDLE;
VkCommandBuffer command_buffers[FRAMES_IN_FLIGHT] = {0};
VkSurfaceKHR surface = VK_NULL_HANDLE;
VkSwapchainKHR swapchain = VK_NULL_HANDLE;
VkSurfaceFormatKHR swapchain_format;
VkPresentModeKHR swapchain_present_mode;
VkImageArray *swapchain_images;
VkImageViewArray *swapchain_image_views;
VkExtent2D swapchain_extent;
u32 image_index;
VkImage depth_image = VK_NULL_HANDLE;
VkDeviceMemory depth_image_memory = VK_NULL_HANDLE;
VkImageView depth_image_view = VK_NULL_HANDLE;
VkFormat depth_image_format;
VkBuffer vertex_buffer = VK_NULL_HANDLE;
VkDeviceMemory vertex_buffer_memory = VK_NULL_HANDLE;
VkBuffer index_buffer = VK_NULL_HANDLE;
VkDeviceMemory index_buffer_memory = VK_NULL_HANDLE;
VkFenceArray fences = wapp_array_with_capacity(VkFence,
VkFenceArray,
FRAMES_IN_FLIGHT,
true);
VkSemaphoreArray present_complete_semaphores = wapp_array_with_capacity(VkSemaphore,
VkSemaphoreArray,
FRAMES_IN_FLIGHT,
true);
VkSemaphoreArray render_finished_semaphores = wapp_array_with_capacity(VkSemaphore,
VkSemaphoreArray,
FRAMES_IN_FLIGHT,
true);
VkShaderModule shader_module = VK_NULL_HANDLE;
VkPipelineLayout pipeline_layout = VK_NULL_HANDLE;
VkPipeline graphics_pipeline = VK_NULL_HANDLE;
u32 physical_device_count;
u64 frame_index = 0;
VertexArray vertices = wapp_array(Vertex, VertexArray,
{{-0.5f, 0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}},
{{-0.5f, -0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}},
{{-0.5f, 0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}},
{{-0.5f, -0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}},
{{ 0.5f, 0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}},
{{ 0.5f, -0.5f, 0.5f}, {0.42f, 0.05f, 0.14f}},
{{ 0.5f, 0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}},
{{ 0.5f, -0.5f, -0.5f}, { 0.0f, 0.1f, 0.21f}});
U32Array indices = wapp_array(u32, U32Array, 0, 2, 3,
3, 1, 0,
6, 4, 5,
5, 7, 6,
4, 0, 1,
1, 5, 4,
2, 6, 7,
7, 3, 2,
0, 4, 6,
6, 2, 0,
3, 7, 5,
5, 1, 3);
wapp_intern inline void draw_frame(void);
wapp_intern inline void record_command_buffer(void);
wapp_intern inline b8 create_buffer(VkPhysicalDeviceMemoryProperties *properties, VkDevice device,
VkBufferCreateFlags flags, VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags memory_properties, VkBuffer *buffer,
VkDeviceMemory *buffer_memory);
wapp_intern inline void transition_image_layout(
VkCommandBuffer command_buffer, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout,
VkPipelineStageFlags2 src_stage, VkPipelineStageFlags2 dst_stage,
VkAccessFlags2 src_access, VkAccessFlags2 dst_access, VkImageAspectFlags aspect_mask
);
wapp_intern inline i32 get_memory_type(const VkPhysicalDeviceMemoryProperties *properties,
u32 memory_type_bits, VkMemoryPropertyFlags flags);
wapp_intern inline u32 set_error(u32 code, const char *msg);
int main(void) {
// Initialisation {{{
Allocator arena = wapp_mem_arena_allocator_init(MB(256));
u32 status = RETURN_SUCCESS;
SDL_Init(SDL_INIT_VIDEO);
SDL_Window *window = SDL_CreateWindow("Vulkan", WINDOW_WIDTH, WINDOW_HEIGHT, SDL_WINDOW_VULKAN);
PFN_vkGetInstanceProcAddr vkGetInstanceProcAddr = (PFN_vkGetInstanceProcAddr)SDL_Vulkan_GetVkGetInstanceProcAddr();
if (!vkGetInstanceProcAddr) {
status = set_error(RETURN_FAILED_TO_GET_VK_GET_INSTANCE_PROC_ADDR,
"Failed to get address of vkGetInstanceProcAddr");
goto SDL_CLEANUP;
}
if (volkInitialize() != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_INITIALISE_VOLK, "Failed to initialise volk");
goto SDL_CLEANUP;
}
// }}}
// Instance creation {{{
u32 sdl_extensions_count;
const char *const *sdl_extensions = SDL_Vulkan_GetInstanceExtensions(&sdl_extensions_count);
if (!sdl_extensions) {
status = set_error(RETURN_FAILED_TO_GET_INSTANCE_EXTENSIONS,
"Failed to get instance extensions");
goto SDL_CLEANUP;
}
const char *extra_extensions[] = {
VK_KHR_SURFACE_EXTENSION_NAME,
VK_KHR_GET_SURFACE_CAPABILITIES_2_EXTENSION_NAME
};
u32 extra_extensions_count = sizeof(extra_extensions) / sizeof(const char *);
u32 all_extensions_count = sdl_extensions_count + extra_extensions_count;
const char **all_instance_extensions = wapp_mem_allocator_alloc(&arena, all_extensions_count * sizeof(const char *));
if (!all_instance_extensions) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_EXTENSION_NAMES,
"Failed to allocate memory for instance extensions names");
goto SDL_CLEANUP;
}
memcpy(all_instance_extensions, sdl_extensions, sdl_extensions_count * sizeof(const char *));
memcpy(&(all_instance_extensions[sdl_extensions_count]), extra_extensions, extra_extensions_count * sizeof(const char *));
u32 api_version;
if (vkEnumerateInstanceVersion(&api_version) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_QUERY_API_VERSION,
"Failed to query API version");
goto SDL_CLEANUP;
}
fprintf(stdout, "Vulkan API version: %u.%u.%u.%u\n", VK_API_VERSION_MAJOR(api_version),
VK_API_VERSION_MINOR(api_version),
VK_API_VERSION_PATCH(api_version),
VK_API_VERSION_VARIANT(api_version));
VkApplicationInfo app_info = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pNext = NULL,
.pApplicationName = "Vulkan spec test",
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "My vulkan engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = api_version,
};
VkInstanceCreateInfo instance_create_info = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &app_info,
.enabledExtensionCount = all_extensions_count,
.ppEnabledExtensionNames = all_instance_extensions,
};
if (vkCreateInstance(&instance_create_info, NULL, &instance) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_INSTANCE,
"Failed to create vulkan instance");
goto SDL_CLEANUP;
}
volkLoadInstanceOnly(instance);
// }}}
// Surface creation {{{
if (!SDL_Vulkan_CreateSurface(window, instance, NULL, &surface)) {
status = set_error(RETURN_FAILED_TO_CREATE_SURFACE,
"Failed to create surface");
goto DESTROY_VULKAN_INSTANCE;
}
// }}}
// Find suitable physical device {{{
if (vkEnumeratePhysicalDevices(instance, &physical_device_count, NULL) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_COUNT,
"Failed to query physical device count");
goto DESTROY_VULKAN_SURFACE;
}
VkPhysicalDevice *physical_devices = wapp_mem_allocator_alloc(&arena, physical_device_count * sizeof(VkPhysicalDevice));
if (!physical_devices) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_PHYSICAL_DEVICES,
"Failed to allocate memory for physical devices");
goto DESTROY_VULKAN_SURFACE;
}
if (vkEnumeratePhysicalDevices(instance, &physical_device_count, physical_devices) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_RETRIEVE_PHYSICAL_DEVICES,
"Failed to retrieve physical devices");
goto DESTROY_VULKAN_SURFACE;
}
int32_t index = -1;
u32 api = 0;
for (int32_t i = 0; i < physical_device_count; ++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 > api) {
api = base_properties.properties.apiVersion;
index = i;
}
} break;
case VK_PHYSICAL_DEVICE_TYPE_INTEGRATED_GPU: {
if (base_properties.properties.apiVersion > api) {
api = base_properties.properties.apiVersion;
index = i;
}
} break;
default: continue;
}
}
if (index == -1 || api == 0) {
status = set_error(RETURN_FAILED_TO_FIND_SUITABLE_PHYSICAL_DEVICE,
"Couldn't find suitable physical device");
goto DESTROY_VULKAN_SURFACE;
}
physical_device = physical_devices[index];
VkPhysicalDeviceProperties2 physical_device_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PROPERTIES_2 };
VkPhysicalDeviceVulkan11Properties vulkan_11_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_PROPERTIES };
VkPhysicalDeviceVulkan12Properties vulkan_12_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_PROPERTIES };
VkPhysicalDeviceVulkan13Properties vulkan_13_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_PROPERTIES };
VkPhysicalDeviceVulkan14Properties vulkan_14_properties = { .sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_PROPERTIES };
void *last = &physical_device_properties;
if (api >= VK_API_VERSION_1_1) {
last = ((VkPhysicalDeviceProperties2 *)last)->pNext = &vulkan_11_properties;
}
if (api >= VK_API_VERSION_1_2) {
last = ((VkPhysicalDeviceVulkan11Properties *)last)->pNext = &vulkan_12_properties;
}
if (api >= VK_API_VERSION_1_3) {
last = ((VkPhysicalDeviceVulkan12Properties *)last)->pNext = &vulkan_13_properties;
}
if (api >= VK_API_VERSION_1_4) {
last = ((VkPhysicalDeviceVulkan13Properties *)last)->pNext = &vulkan_14_properties;
}
vkGetPhysicalDeviceProperties2(physical_device, &physical_device_properties);
VkPhysicalDeviceMemoryProperties2 memory_properties = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_MEMORY_PROPERTIES_2,
};
vkGetPhysicalDeviceMemoryProperties2(physical_device, &memory_properties);
// }}}
// Find suitable queue family {{{
u32 queue_family_property_count = 0;
vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_property_count, NULL);
if (queue_family_property_count == 0) {
status = set_error(RETURN_NO_QUEUE_FAMILY_PROPERTIES_FOUND, "No queue family properties found");
goto DESTROY_VULKAN_SURFACE;
}
u32 alloc_size = queue_family_property_count * sizeof(VkQueueFamilyProperties2);
VkQueueFamilyProperties2 *queue_family_properties = wapp_mem_allocator_alloc(&arena, alloc_size);
if (!queue_family_properties) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_QUEUE_FAMILY_PROPERTIES,
"Failed to allocate memory for queue family properties");
goto DESTROY_VULKAN_SURFACE;
}
memset(queue_family_properties, 0, alloc_size);
for (u32 i = 0; i < queue_family_property_count; ++i) {
queue_family_properties[i].sType = VK_STRUCTURE_TYPE_QUEUE_FAMILY_PROPERTIES_2;
}
vkGetPhysicalDeviceQueueFamilyProperties2(physical_device, &queue_family_property_count, queue_family_properties);
b8 supports_graphics = false;
for (u32 i = 0; i < queue_family_property_count; ++i) {
supports_graphics = supports_graphics ||
(queue_family_properties[i].queueFamilyProperties.queueFlags & VK_QUEUE_GRAPHICS_BIT) == VK_QUEUE_GRAPHICS_BIT;
if (supports_graphics) {
queue_family_index = i;
break;
}
}
if (!supports_graphics) {
status = set_error(RETURN_SELECTED_DEVICE_DOES_NOT_SUPPORT_GRAPHICS,
"Selected physical device doesn't support graphics operations");
goto DESTROY_VULKAN_SURFACE;
}
// }}}
// Logical device creation {{{
VkPhysicalDeviceShaderObjectFeaturesEXT shader_object_feature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_SHADER_OBJECT_FEATURES_EXT,
.shaderObject = VK_TRUE,
};
VkPhysicalDevicePresentModeFifoLatestReadyFeaturesKHR fifo_latest_ready_feature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_PRESENT_MODE_FIFO_LATEST_READY_FEATURES_KHR,
.pNext = &shader_object_feature,
.presentModeFifoLatestReady = VK_TRUE,
};
VkPhysicalDeviceVulkan14Features vulkan_14_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_4_FEATURES,
.pNext = &fifo_latest_ready_feature,
.bresenhamLines = VK_TRUE,
.smoothLines = VK_TRUE,
.pushDescriptor = VK_TRUE,
};
VkPhysicalDeviceVulkan13Features vulkan_13_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_3_FEATURES,
.pNext = &vulkan_14_features,
.dynamicRendering = VK_TRUE,
.synchronization2 = VK_TRUE,
};
VkPhysicalDeviceVulkan12Features vulkan_12_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_2_FEATURES,
.pNext = &vulkan_13_features,
.timelineSemaphore = VK_TRUE,
};
VkPhysicalDeviceVulkan11Features vulkan_11_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_VULKAN_1_1_FEATURES,
.pNext = &vulkan_12_features,
.shaderDrawParameters = VK_TRUE,
};
VkPhysicalDeviceFeatures core_features = {
.logicOp = VK_TRUE,
.fillModeNonSolid = VK_TRUE,
.samplerAnisotropy = VK_TRUE,
};
VkPhysicalDeviceFeatures2 device_features = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_FEATURES_2,
.pNext = &vulkan_11_features,
.features = core_features,
};
f32 priorities[] = {0.5};
VkDeviceQueueCreateInfo queue_create_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queue_family_index,
.queueCount = 1,
.pQueuePriorities = priorities,
};
const char *device_extensions[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
VK_EXT_SHADER_OBJECT_EXTENSION_NAME,
VK_EXT_VERTEX_INPUT_DYNAMIC_STATE_EXTENSION_NAME,
VK_EXT_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME,
};
u32 device_extensions_count = sizeof(device_extensions) / sizeof(const char *);
VkDeviceCreateInfo device_create_info = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pNext = &device_features,
.queueCreateInfoCount = 1,
.pQueueCreateInfos = &queue_create_info,
.enabledExtensionCount = device_extensions_count,
.ppEnabledExtensionNames = device_extensions,
};
u32 dev_ext_count;
if (vkEnumerateDeviceExtensionProperties(physical_device, NULL,
&dev_ext_count, NULL) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_ENUMERATE_PHYSICAL_DEVICE_EXTENSIONS,
"Failed to enumerate physical device extensions");
goto DESTROY_VULKAN_SURFACE;
}
VkExtensionProperties *extension_properties = wapp_mem_allocator_alloc(&arena, dev_ext_count * sizeof(VkExtensionProperties));
if (vkEnumerateDeviceExtensionProperties(physical_device, NULL,
&dev_ext_count, extension_properties) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_QUERY_PHYSICAL_DEVICE_EXTENSIONS,
"Failed to query physical device extensions");
goto DESTROY_VULKAN_SURFACE;
}
for (u32 i = 0; i < dev_ext_count; ++i) {
if (memcmp(extension_properties[i].extensionName, VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME,
sizeof(VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME)) == 0) {
device_extensions[device_extensions_count - 1] = VK_KHR_PRESENT_MODE_FIFO_LATEST_READY_EXTENSION_NAME;
break;
}
}
if (vkCreateDevice(physical_device, &device_create_info, NULL, &device) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_LOGICAL_DEVICE,
"Failed to create device");
goto DESTROY_VULKAN_SURFACE;
}
volkLoadDevice(device);
/// }}}
// Get device queue {{{
vkGetDeviceQueue(device, queue_family_index, 0, &queue);
// }}}
// Create command pool {{{
VkCommandPoolCreateInfo pool_create_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = queue_family_index,
};
if (vkCreateCommandPool(device, &pool_create_info, NULL, &command_pool) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_COMMAND_POOL, "Failed to create command pool");
goto DESTROY_VULKAN_DEVICE;
}
// }}}
// Allocate command buffers {{{
VkCommandBufferAllocateInfo cmd_buf_alloc_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandBufferCount = 1,
.commandPool = command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
};
for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) {
if (vkAllocateCommandBuffers(device, &cmd_buf_alloc_info, &command_buffers[i]) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_COMMAND_BUFFER, "Failed to allocate command buffer");
goto FREE_COMMAND_BUFFER;
}
}
// }}}
// Create swapchain {{{
VkSurfaceCapabilitiesKHR capabilities = {0};
if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physical_device, surface, &capabilities) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_QUERY_SURFACE_CAPABILITIES, "Failed to query surface capabilities");
goto FREE_COMMAND_BUFFER;
}
swapchain_extent = capabilities.currentExtent;
u32 format_count;
if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, NULL) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_FORMATS,
"Failed to enumerate supported surface formats");
goto FREE_COMMAND_BUFFER;
}
VkSurfaceFormatKHR *formats = wapp_mem_allocator_alloc(&arena, format_count * sizeof(VkSurfaceFormatKHR));
if (!formats) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_FORMATS,
"Failed to allocate memory for surface formats");
goto FREE_COMMAND_BUFFER;
}
if (vkGetPhysicalDeviceSurfaceFormatsKHR(physical_device, surface, &format_count, formats) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_QUERY_SURPPORTED_SURFACE_FORMATS,
"Failed to query supported surface formats");
goto FREE_COMMAND_BUFFER;
}
VkSurfaceFormatKHR *format = NULL;
for (u32 i = 0; i < format_count; ++i) {
if (formats[i].format == VK_FORMAT_B8G8R8A8_SRGB && formats[i].colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) {
format = &(formats[i]);
break;
}
}
if (!format) { format = &formats[0]; }
swapchain_format = *format;
u32 present_mode_count;
if (vkGetPhysicalDeviceSurfacePresentModesKHR(physical_device, surface, &present_mode_count, NULL) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_ENUMERATE_SUPPORTED_SURFACE_PRESENT_MODES,
"Failed to enumerate supported surface present modes");
goto FREE_COMMAND_BUFFER;
}
VkPresentModeKHR *present_modes = wapp_mem_allocator_alloc(&arena, present_mode_count * sizeof(VkPresentModeKHR));
if (!present_modes) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_MEMORY_FOR_SURFACE_PRESENT_MODES,
"Failed to allocate memory for surface present modes");
goto FREE_COMMAND_BUFFER;
}
swapchain_present_mode = VK_PRESENT_MODE_FIFO_KHR;
for (u32 i = 0; i < present_mode_count; ++i) {
if (present_modes[i] == VK_PRESENT_MODE_FIFO_LATEST_READY_KHR) {
swapchain_present_mode = present_modes[i];
break;
}
}
VkSwapchainCreateInfoKHR swapchain_info = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = surface,
.minImageCount = capabilities.minImageCount,
.imageFormat = swapchain_format.format,
.imageColorSpace = swapchain_format.colorSpace,
.imageExtent = capabilities.currentExtent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE,
.queueFamilyIndexCount = 1,
.pQueueFamilyIndices = &queue_family_index,
.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = swapchain_present_mode,
};
if (vkCreateSwapchainKHR(device, &swapchain_info, NULL, &swapchain) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_SWAPCHAIN, "Failed to create swapchain");
goto FREE_COMMAND_BUFFER;
}
// }}}
// Get swapchain images {{{
u32 image_count;
if (vkGetSwapchainImagesKHR(device, swapchain, &image_count, NULL) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGE_COUNT, "Failed to get swapchain images count");
goto DESTROY_SWAPCHAIN;
}
swapchain_images = wapp_array_alloc_capacity(VkImage, VkImageArray, &arena, image_count, true);
swapchain_image_views = wapp_array_alloc_capacity(VkImageView, VkImageViewArray, &arena, image_count, false);
if (!swapchain_images || !swapchain_image_views) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_SWAPCHAIN_IMAGES_MEMORY,
"Failed to allocate swapchain images memory");
goto DESTROY_SWAPCHAIN;
}
if (vkGetSwapchainImagesKHR(device, swapchain, (u32 *)&(swapchain_images->count), swapchain_images->items) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_GET_SWAPCHAIN_IMAGES, "Failed to get swapchain images");
goto DESTROY_SWAPCHAIN;
}
// }}}
// Create swapchain image views {{{
for (u32 i = 0; i < swapchain_images->count; ++i) {
VkImageViewCreateInfo view_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = *wapp_array_get(VkImage, swapchain_images, i),
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = swapchain_format.format,
.subresourceRange = {
.levelCount = 1,
.baseMipLevel = 0,
.layerCount = 1,
.baseArrayLayer = 0,
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
},
};
VkImageView view;
if (vkCreateImageView(device, &view_info, NULL, &view) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_IMAGE_VIEWS, "Failed to create image views");
goto DESTROY_IMAGE_VIEWS;
}
wapp_array_append_capped(VkImageView, swapchain_image_views, &view);
}
// }}}
// Create synchronization objects {{{
VkFenceCreateInfo fence_info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
};
VkSemaphoreCreateInfo semaphore_info = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) {
if (vkCreateFence(device, &fence_info, NULL, wapp_array_get(VkFence, &fences, i)) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphore_info, NULL, wapp_array_get(VkSemaphore, &present_complete_semaphores, i)) != VK_SUCCESS ||
vkCreateSemaphore(device, &semaphore_info, NULL, wapp_array_get(VkSemaphore, &render_finished_semaphores, i)) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_SYNC_OBJECTS, "Failed to create sync objects");
goto DESTROY_SYNC_OBJECTS;
}
}
// }}}
// Create shader module {{{
FILE *shader_file = fopen(SHADER_FILE, "rb");
fseek(shader_file, 0, SEEK_END);
u64 length = ftell(shader_file);
fseek(shader_file, 0, SEEK_SET);
u8 *code = wapp_mem_allocator_alloc(&arena, length * sizeof(u8));
fread(code, sizeof(u8), length, shader_file);
VkShaderModuleCreateInfo module_info = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = length,
.pCode = (u32 *)code,
};
if (vkCreateShaderModule(device, &module_info, NULL, &shader_module) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_SHADER_MODULE, "Failed to create shader module");
goto DESTROY_SYNC_OBJECTS;
}
fclose(shader_file);
// }}}
// Create depth resources {{{
depth_image_format = VK_FORMAT_D32_SFLOAT_S8_UINT;
VkExtent3D depth_image_extent = {
.width = swapchain_extent.width,
.height = swapchain_extent.height,
.depth = 1
};
VkImageCreateInfo depth_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO,
.imageType = VK_IMAGE_TYPE_2D,
.format = depth_image_format,
.extent = depth_image_extent,
.mipLevels = 1,
.arrayLayers = 1,
.samples = VK_SAMPLE_COUNT_1_BIT,
.tiling = VK_IMAGE_TILING_OPTIMAL,
.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
if (vkCreateImage(device, &depth_info, NULL, &depth_image) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_DEPTH_IMAGE, "Failed to create depth image");
goto DESTROY_DEPTH_RESOURCES;
}
VkMemoryRequirements depth_memory_requirements = {0};
vkGetImageMemoryRequirements(device, depth_image, &depth_memory_requirements);
i32 memory_type_index = get_memory_type(&memory_properties.memoryProperties,
depth_memory_requirements.memoryTypeBits,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
if (memory_type_index == -1) {
status = set_error(RETURN_FAILED_TO_FIND_SUITABLE_MEMORY_TYPE_FOR_DEPTH_IMAGE,
"Failed to find suitable memory type for depth image");
goto DESTROY_DEPTH_RESOURCES;
}
VkMemoryPriorityAllocateInfoEXT allocation_priority = {
.sType = VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT,
.priority = 1.0f,
};
VkMemoryAllocateInfo depth_memory_alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &allocation_priority,
.allocationSize = depth_memory_requirements.size,
.memoryTypeIndex = (u32)memory_type_index,
};
if (vkAllocateMemory(device, &depth_memory_alloc_info, NULL, &depth_image_memory) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_ALLOCATE_DEPTH_IMAGE_MEMORY, "Failed to allocate depth image memory");
goto DESTROY_DEPTH_RESOURCES;
}
if (vkBindImageMemory(device, depth_image, depth_image_memory, 0) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_BIND_DEPTH_IMAGE_MEMORY, "Failed to bind depth image memory");
goto DESTROY_DEPTH_RESOURCES;
}
VkImageViewCreateInfo depth_view_info = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = depth_image,
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = depth_image_format,
.subresourceRange = (VkImageSubresourceRange){
.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
if (vkCreateImageView(device, &depth_view_info, NULL, &depth_image_view) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_DEPTH_IMAGE_VIEW, "Failed to create depth image view");
goto DESTROY_DEPTH_RESOURCES;
}
// }}}
// Create copying command buffer and fence {{{
VkFence copy_fence = VK_NULL_HANDLE;
VkFenceCreateInfo copy_fence_info = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
};
if (vkCreateFence(device, &copy_fence_info, NULL, &copy_fence) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_COPY_COMMAND_BUFFER, "Failed to create copy command buffer");
goto FREE_COPY_BUFFER;
}
VkCommandBuffer copy_buffer = VK_NULL_HANDLE;
VkCommandBufferAllocateInfo copy_buffer_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = command_pool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = 1,
};
if (vkAllocateCommandBuffers(device, &copy_buffer_info, &copy_buffer) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_COPY_COMMAND_BUFFER, "Failed to create copy command buffer");
goto FREE_COPY_BUFFER;
}
// }}}
VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(copy_buffer, &begin_info);
// Create vertex buffer {{{
u64 vertex_buffer_size = sizeof(Vertex) * vertices.count;
VkBuffer vertex_staging = VK_NULL_HANDLE;
VkDeviceMemory vertex_staging_memory = VK_NULL_HANDLE;
if (!create_buffer(&memory_properties.memoryProperties, device, 0, vertex_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&vertex_staging, &vertex_staging_memory)) {
status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create vertex buffer");
goto DESTROY_VERTEX_BUFFER;
}
void *vertex_staging_mapped = NULL;
if (vkMapMemory(device, vertex_staging_memory, 0, vertex_buffer_size, 0, &vertex_staging_mapped) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create vertex buffer");
goto DESTROY_VERTEX_BUFFER;
}
memcpy(vertex_staging_mapped, vertices.items, vertex_buffer_size);
vkUnmapMemory(device, vertex_staging_memory);
if (!create_buffer(&memory_properties.memoryProperties, device, 0, vertex_buffer_size,
VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertex_buffer, &vertex_buffer_memory)) {
status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create vertex buffer");
goto DESTROY_VERTEX_BUFFER;
}
VkBufferCopy vertex_buffer_copy = {
.srcOffset = 0,
.dstOffset = 0,
.size = vertex_buffer_size,
};
vkCmdCopyBuffer(copy_buffer, vertex_staging, vertex_buffer, 1, &vertex_buffer_copy);
// }}}
// Create index buffer {{{
u64 index_buffer_size = sizeof(u32) * indices.count;
VkBuffer index_staging = VK_NULL_HANDLE;
VkDeviceMemory index_staging_memory = VK_NULL_HANDLE;
if (!create_buffer(&memory_properties.memoryProperties, device, 0, index_buffer_size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&index_staging, &index_staging_memory)) {
status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create index buffer");
goto DESTROY_INDEX_BUFFER;
}
void *index_staging_mapped = NULL;
if (vkMapMemory(device, index_staging_memory, 0, index_buffer_size, 0, &index_staging_mapped) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create index buffer");
goto DESTROY_INDEX_BUFFER;
}
memcpy(index_staging_mapped, indices.items, index_buffer_size);
vkUnmapMemory(device, index_staging_memory);
if (!create_buffer(&memory_properties.memoryProperties, device, 0, index_buffer_size,
VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &index_buffer, &index_buffer_memory)) {
status = set_error(RETURN_FAILED_TO_CREATE_VERTEX_BUFFER, "Failed to create index buffer");
goto DESTROY_INDEX_BUFFER;
}
VkBufferCopy index_buffer_copy = {
.srcOffset = 0,
.dstOffset = 0,
.size = index_buffer_size,
};
vkCmdCopyBuffer(copy_buffer, index_staging, index_buffer, 1, &index_buffer_copy);
// }}}
vkEndCommandBuffer(copy_buffer);
// Submit copying vertex and index buffers {{{
VkSubmitInfo copy_submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &copy_buffer,
};
if (vkQueueSubmit(queue, 1, &copy_submit_info, copy_fence) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_COPY_VERTEX_AND_INDEX_DATA, "Failed to copy vertex and index data");
goto DESTROY_INDEX_BUFFER;
}
while (vkWaitForFences(device, 1, &copy_fence, VK_TRUE, UINT64_MAX) == VK_TIMEOUT) {}
// }}}
// Create graphics pipeline {{{
VkPipelineShaderStageCreateInfo shader_stages[SHADER_COUNT] = {
(VkPipelineShaderStageCreateInfo){
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = shader_module,
.pName = "vertMain",
},
(VkPipelineShaderStageCreateInfo){
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = shader_module,
.pName = "fragMain",
},
};
VkVertexInputBindingDescription vertex_binding = {
.binding = 0,
.stride = sizeof(Vertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
};
VkVertexInputAttributeDescription vertex_attributes[] = {
(VkVertexInputAttributeDescription){
.location = 0,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, position),
},
(VkVertexInputAttributeDescription){
.location = 1,
.binding = 0,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, color),
},
};
u64 attributes_count = sizeof(vertex_attributes) / sizeof(vertex_attributes[0]);
VkPipelineVertexInputStateCreateInfo vertex_input = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.pVertexBindingDescriptions = &vertex_binding,
.vertexAttributeDescriptionCount = attributes_count,
.pVertexAttributeDescriptions = vertex_attributes,
};
VkPipelineInputAssemblyStateCreateInfo input_assembly = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
};
VkPipelineRasterizationStateCreateInfo rasterisation = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.polygonMode = VK_POLYGON_MODE_FILL,
.cullMode = VK_CULL_MODE_BACK_BIT,
.frontFace = VK_FRONT_FACE_CLOCKWISE,
.lineWidth = 1.0f,
};
VkPipelineMultisampleStateCreateInfo multisampling = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
};
VkPipelineDepthStencilStateCreateInfo depth_stencil = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO,
.depthTestEnable = VK_TRUE,
.depthWriteEnable = VK_TRUE,
.depthCompareOp = VK_COMPARE_OP_LESS,
};
VkPipelineColorBlendAttachmentState blend_state = {
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT |
VK_COLOR_COMPONENT_G_BIT |
VK_COLOR_COMPONENT_B_BIT |
VK_COLOR_COMPONENT_A_BIT,
};
VkPipelineColorBlendStateCreateInfo blending = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.logicOpEnable = VK_TRUE,
.logicOp = VK_LOGIC_OP_COPY,
.attachmentCount = 1,
.pAttachments = &blend_state,
};
VkDynamicState dynamic_states[2] = {VK_DYNAMIC_STATE_VIEWPORT_WITH_COUNT, VK_DYNAMIC_STATE_SCISSOR_WITH_COUNT};
VkPipelineDynamicStateCreateInfo dynamic_state = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = 2,
.pDynamicStates = dynamic_states,
};
VkPipelineLayoutCreateInfo layout_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
};
if (vkCreatePipelineLayout(device, &layout_info, NULL, &pipeline_layout) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_PIPELINE_LAYOUT, "Failed to create pipeline layout");
goto DESTROY_DEPTH_RESOURCES;
}
VkFormat color_attachment_format = swapchain_format.format;
VkPipelineRenderingCreateInfo rendering_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &color_attachment_format,
.depthAttachmentFormat = depth_image_format,
.stencilAttachmentFormat = depth_image_format,
};
VkGraphicsPipelineCreateInfo pipeline_info = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.pNext = &rendering_info,
.stageCount = SHADER_COUNT,
.pStages = shader_stages,
.pVertexInputState = &vertex_input,
.pInputAssemblyState = &input_assembly,
.pRasterizationState = &rasterisation,
.pMultisampleState = &multisampling,
.pDepthStencilState = &depth_stencil,
.pColorBlendState = &blending,
.pDynamicState = &dynamic_state,
.layout = pipeline_layout,
};
if (vkCreateGraphicsPipelines(device, NULL, 1, &pipeline_info, NULL, &graphics_pipeline) != VK_SUCCESS) {
status = set_error(RETURN_FAILED_TO_CREATE_GRAPHICS_PIPELINE, "Failed to create graphics pipeline");
goto DESTROY_PIPELINE_LAYOUT;
}
// }}}
// Main loop {{{
u64 frame_start, frame_time;
b8 running = true;
SDL_Event event = {0};
while (running) {
frame_start = SDL_GetTicksNS();
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;
}
}
draw_frame();
frame_time = SDL_GetTicksNS() - frame_start;
if (frame_time < FRAME_TIME) { SDL_DelayNS(FRAME_TIME - frame_time); }
}
// }}}
// Cleanup {{{
vkDeviceWaitIdle(device);
DESTROY_GRAPHICS_PIPELINE:
vkDestroyPipeline(device, graphics_pipeline, NULL);
DESTROY_PIPELINE_LAYOUT:
vkDestroyPipelineLayout(device, pipeline_layout, NULL);
DESTROY_INDEX_BUFFER:
if (index_staging != VK_NULL_HANDLE) { vkDestroyBuffer(device, index_staging, NULL); }
if (index_staging_memory != VK_NULL_HANDLE) { vkFreeMemory(device, index_staging_memory, NULL); }
if (index_buffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, index_buffer, NULL); }
if (index_buffer_memory != VK_NULL_HANDLE) { vkFreeMemory(device, index_buffer_memory, NULL); }
DESTROY_VERTEX_BUFFER:
if (vertex_staging != VK_NULL_HANDLE) { vkDestroyBuffer(device, vertex_staging, NULL); }
if (vertex_staging_memory != VK_NULL_HANDLE) { vkFreeMemory(device, vertex_staging_memory, NULL); }
if (vertex_buffer != VK_NULL_HANDLE) { vkDestroyBuffer(device, vertex_buffer, NULL); }
if (vertex_buffer_memory != VK_NULL_HANDLE) { vkFreeMemory(device, vertex_buffer_memory, NULL); }
FREE_COPY_BUFFER:
if (copy_fence != VK_NULL_HANDLE) { vkDestroyFence(device, copy_fence, NULL); }
if (copy_buffer != VK_NULL_HANDLE) { vkFreeCommandBuffers(device, command_pool, 1, &copy_buffer); }
DESTROY_DEPTH_RESOURCES:
if (depth_image_view != VK_NULL_HANDLE) { vkDestroyImageView(device, depth_image_view, NULL); }
if (depth_image_memory != VK_NULL_HANDLE) { vkFreeMemory(device, depth_image_memory, NULL); }
if (depth_image != VK_NULL_HANDLE) { vkDestroyImage(device, depth_image, NULL); }
DESTROY_SHADER_MODULE:
if (shader_module != VK_NULL_HANDLE) { vkDestroyShaderModule(device, shader_module, NULL); }
DESTROY_SYNC_OBJECTS:
for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) {
if (wapp_array_get(VkFence, &fences, i) != VK_NULL_HANDLE) {
vkDestroyFence(device, *wapp_array_get(VkFence, &fences, i), NULL);
}
if (wapp_array_get(VkSemaphore, &present_complete_semaphores, i) != VK_NULL_HANDLE) {
vkDestroySemaphore(device, *wapp_array_get(VkSemaphore, &present_complete_semaphores, i), NULL);
}
if (wapp_array_get(VkSemaphore, &render_finished_semaphores, i) != VK_NULL_HANDLE) {
vkDestroySemaphore(device, *wapp_array_get(VkSemaphore, &render_finished_semaphores, i), NULL);
}
}
DESTROY_IMAGE_VIEWS:
for (u32 i = 0; i < swapchain_images->count; ++i) {
VkImageView view = *wapp_array_get(VkImageView, swapchain_image_views, i);
if (view != VK_NULL_HANDLE) { vkDestroyImageView(device, view, NULL); }
}
DESTROY_SWAPCHAIN:
vkDestroySwapchainKHR(device, swapchain, NULL);
FREE_COMMAND_BUFFER:
for (u32 i = 0; i < FRAMES_IN_FLIGHT; ++i) {
if (command_buffers[i] != VK_NULL_HANDLE) { vkFreeCommandBuffers(device, command_pool, 1, &command_buffers[i]); }
}
DESTROY_COMMAND_POOL:
vkDestroyCommandPool(device, command_pool, NULL);
DESTROY_VULKAN_DEVICE:
vkDestroyDevice(device, NULL);
DESTROY_VULKAN_SURFACE:
SDL_Vulkan_DestroySurface(instance, surface, NULL);
DESTROY_VULKAN_INSTANCE:
vkDestroyInstance(instance, NULL);
SDL_CLEANUP:
SDL_DestroyWindow(window);
SDL_Quit();
wapp_mem_arena_allocator_destroy(&arena);
// }}}
return status;
}
static inline void draw_frame(void) {
// Ensure queue isn't still rendering using objects for the current frame_index {{{
while (vkWaitForFences(device, 1, wapp_array_get(VkFence, &fences, frame_index), VK_TRUE, UINT64_MAX) == VK_TIMEOUT) {}
vkResetFences(device, 1, wapp_array_get(VkFence, &fences, frame_index));
// }}}
vkAcquireNextImageKHR(device, swapchain, UINT64_MAX, *wapp_array_get(VkSemaphore, &present_complete_semaphores, frame_index),
VK_NULL_HANDLE, &image_index);
record_command_buffer();
// Submit command buffer to queue {{{
VkPipelineStageFlags wait_stage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
VkSubmitInfo submit_info = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = wapp_array_get(VkSemaphore, &present_complete_semaphores, frame_index),
.commandBufferCount = 1,
.pCommandBuffers = &command_buffers[frame_index],
.pWaitDstStageMask = &wait_stage,
.signalSemaphoreCount = 1,
.pSignalSemaphores = wapp_array_get(VkSemaphore, &render_finished_semaphores, frame_index),
};
vkQueueSubmit(queue, 1, &submit_info, *wapp_array_get(VkFence, &fences, frame_index));
// }}}
// Present the swapchain image when rendering is done {{{
VkPresentInfoKHR present_info = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = wapp_array_get(VkSemaphore, &render_finished_semaphores, frame_index),
.swapchainCount = 1,
.pSwapchains = &(swapchain),
.pImageIndices = &image_index,
};
vkQueuePresentKHR(queue, &present_info);
// }}}
frame_index = (frame_index + 1) % FRAMES_IN_FLIGHT;
}
static inline void record_command_buffer() {
VkCommandBufferBeginInfo begin_info = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(command_buffers[frame_index], &begin_info);
// Transition image layout for rendering {{{
transition_image_layout(
command_buffers[frame_index],
*wapp_array_get(VkImage, swapchain_images, image_index),
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_ACCESS_NONE,
VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_COLOR_BIT
);
transition_image_layout(
command_buffers[frame_index],
depth_image,
VK_IMAGE_LAYOUT_UNDEFINED,
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT,
VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT
);
// }}}
// Dynamic rendering {{{
vkCmdBindPipeline(command_buffers[frame_index], VK_PIPELINE_BIND_POINT_GRAPHICS, graphics_pipeline);
VkDeviceSize vertex_buffer_offset = 0;
vkCmdBindVertexBuffers(command_buffers[frame_index], 0, 1, &vertex_buffer, &vertex_buffer_offset);
vkCmdBindIndexBuffer(command_buffers[frame_index], index_buffer, 0, VK_INDEX_TYPE_UINT32);
VkViewport viewport = {
.x = 0,
.y = 0,
.width = swapchain_extent.width,
.height = swapchain_extent.height,
.minDepth = MIN_DEPTH,
.maxDepth = MAX_DEPTH,
};
vkCmdSetViewportWithCount(command_buffers[frame_index], 1, &viewport);
VkRect2D scissor = {
.offset = (VkOffset2D){ .x = 0, .y = 0 },
.extent = swapchain_extent,
};
vkCmdSetScissorWithCount(command_buffers[frame_index], 1, &scissor);
VkClearValue color_clear = {
.color = (VkClearColorValue){ .float32 = {0.0f, 0.0f, 0.0f, 0.0f} }
};
VkRenderingAttachmentInfo color_attachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = *wapp_array_get(VkImageView, swapchain_image_views, image_index),
.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.clearValue = color_clear
};
VkClearValue depth_clear = {
.depthStencil = (VkClearDepthStencilValue){ .depth = MAX_DEPTH }
};
VkRenderingAttachmentInfo depth_attachment = {
.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO,
.imageView = depth_image_view,
.imageLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.clearValue = depth_clear
};
VkRect2D render_area = {
.offset = (VkOffset2D){.x = 0, .y = 0},
.extent = swapchain_extent,
};
VkRenderingInfo rendering_info = {
.sType = VK_STRUCTURE_TYPE_RENDERING_INFO,
.renderArea = render_area,
.layerCount = 1,
.colorAttachmentCount = 1,
.pColorAttachments = &color_attachment,
.pDepthAttachment = &depth_attachment
};
vkCmdBeginRendering(command_buffers[frame_index], &rendering_info);
vkCmdDrawIndexed(command_buffers[frame_index], indices.count, 1, 0, 0, 0);
vkCmdEndRendering(command_buffers[frame_index]);
// }}}
// Transition image layout for presentation {{{
transition_image_layout(
command_buffers[frame_index],
*wapp_array_get(VkImage, swapchain_images, image_index),
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT,
VK_ACCESS_2_COLOR_ATTACHMENT_WRITE_BIT,
VK_ACCESS_2_NONE,
VK_IMAGE_ASPECT_COLOR_BIT
);
// }}}
vkEndCommandBuffer(command_buffers[frame_index]);
}
wapp_intern inline b8 create_buffer(VkPhysicalDeviceMemoryProperties *properties, VkDevice device,
VkBufferCreateFlags flags, VkDeviceSize size, VkBufferUsageFlags usage,
VkMemoryPropertyFlags memory_properties, VkBuffer *buffer,
VkDeviceMemory *buffer_memory) {
VkBufferCreateInfo buffer_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.flags = flags,
.size = size,
.usage = usage,
.sharingMode = VK_SHARING_MODE_EXCLUSIVE,
};
if (vkCreateBuffer(device, &buffer_info, NULL, buffer) != VK_SUCCESS) { return false; }
VkBufferMemoryRequirementsInfo2 requirements_info = {
.sType = VK_STRUCTURE_TYPE_BUFFER_MEMORY_REQUIREMENTS_INFO_2,
.buffer = *buffer,
};
VkMemoryRequirements2 requirements = {
.sType = VK_STRUCTURE_TYPE_MEMORY_REQUIREMENTS_2,
};
vkGetBufferMemoryRequirements2(device, &requirements_info, &requirements);
i32 memory_type = get_memory_type(properties, requirements.memoryRequirements.memoryTypeBits, memory_properties);
if (memory_type < 0) { return false; }
VkMemoryPriorityAllocateInfoEXT priority = {
.sType = VK_STRUCTURE_TYPE_MEMORY_PRIORITY_ALLOCATE_INFO_EXT,
.priority = 0.5f,
};
VkMemoryAllocateInfo alloc_info = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.pNext = &priority,
.memoryTypeIndex = memory_type,
.allocationSize = requirements.memoryRequirements.size,
};
if (vkAllocateMemory(device, &alloc_info, NULL, buffer_memory) != VK_SUCCESS) { return false; }
if (vkBindBufferMemory(device, *buffer, *buffer_memory, 0) != VK_SUCCESS) { return false; }
return true;
}
wapp_intern inline void transition_image_layout(
VkCommandBuffer command_buffer, VkImage image, VkImageLayout old_layout, VkImageLayout new_layout,
VkPipelineStageFlags2 src_stage, VkPipelineStageFlags2 dst_stage,
VkAccessFlags2 src_access, VkAccessFlags2 dst_access, VkImageAspectFlags aspect_mask
) {
VkImageMemoryBarrier2 barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER_2,
.image = image,
.oldLayout = old_layout,
.newLayout = new_layout,
.srcStageMask = src_stage,
.dstStageMask = dst_stage,
.srcAccessMask = src_access,
.dstAccessMask = dst_access,
.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED,
.subresourceRange = {
.aspectMask = aspect_mask,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
},
};
VkDependencyInfo dependency = {
.sType = VK_STRUCTURE_TYPE_DEPENDENCY_INFO,
.imageMemoryBarrierCount = 1,
.pImageMemoryBarriers = &barrier,
};
vkCmdPipelineBarrier2(command_buffer, &dependency);
}
wapp_intern inline i32 get_memory_type(const VkPhysicalDeviceMemoryProperties *properties,
u32 memory_type_bits, VkMemoryPropertyFlags flags) {
for (u32 i = 0; i < properties->memoryTypeCount; ++i) {
u32 type = (u32)1 << i;
b8 is_type = (type & memory_type_bits) == type;
if (!is_type) { continue; }
b8 has_properties = (flags & properties->memoryTypes[i].propertyFlags) == flags;
if (has_properties) { return (i32)i; }
}
return -1;
}
wapp_intern inline u32 set_error(u32 code, const char *msg) {
fprintf(stderr, "%s\n", msg);
return code;
}