#include <stdbool.h>
#include <stdlib.h>
#include <time.h>
#include <shaderc/shaderc.h>
#define SDL_MAIN_HANDLED
#define VK_USE_PLATFORM_XCB_KHR
#include <SDL2/SDL.h>
#include <SDL2/SDL_vulkan.h>
#include <vulkan/vulkan.h>
#define VMA_STATIC_VULKAN_FUNCTIONS 0
#define VMA_DYNAMIC_VULKAN_FUNCTIONS 1
#include "vk_mem_alloc.h"
//#include "cplusplus.h"
#include "vkutil.h"
#include "state.h"
// embedded clgm library
uint32_t currentFrame = 0;
state_t s;
const uint32_t validation_layer_count = 1;
const char *const validation_layers[] = {
"VK_LAYER_KHRONOS_validation"
};
const uint32_t deviceExtensionCount = 1;
const char *const deviceExtensions[] = {
VK_KHR_SWAPCHAIN_EXTENSION_NAME,
};
#ifdef VKDEBUG
const bool enableValidationLayers = true;
#else
const bool enableValidationLayers = false;
#endif
typedef struct {
float x;
float y;
} V2;
typedef struct {
float x;
float y;
float z;
} V3;
typedef struct {
V2 pos;
V3 color;
} Vertex;
/* Vertex vertices[] = { */
/* (Vertex) { (V2) {-0.2f, -0.5f}, (V3) {0.0f, 1.0f, 0.0f}}, */
/* (Vertex) { (V2) {0.5f, 0.3f}, (V3) {0.0f, 0.0f, 1.0f}}, */
/* (Vertex) { (V2) {-0.5f, 0.7f}, (V3) {1.0f, 0.0f, 0.0f}}, */
/* (Vertex) { (V2) {0.2f, -0.5f}, (V3) {0.0f, 0.0f, 1.0f}}, */
/* (Vertex) { (V2) {0.5f, 0.7f}, (V3) {1.0f, 0.0f, 0.0f}}, */
/* (Vertex) { (V2) {-0.5f, 0.3f}, (V3) {0.0f, 1.0f, 0.0f}}, */
/* (Vertex) { (V2) {0.0f, -0.5f}, (V3) {1.0f, 0.0f, 0.0f}}, */
/* (Vertex) { (V2) {0.5f, 0.5f}, (V3) {0.0f, 1.0f, 0.0f}}, */
/* (Vertex) { (V2) {-0.5f, 0.5f}, (V3) {0.0f, 0.0f, 1.0f}}, */
/* }; */
/* const int VERTICES_SIZE = VK_ARRAY_LEN(vertices); */
/* const uint16_t indices[] = { */
/* 0, 1, 2, 3, 4, 5, 6, 7, 8, */
/* }; */
/* const int INDICES_SIZE = VK_ARRAY_LEN(indices); */
Vertex vertices[] = {
(Vertex) { (V2) {-0.5f, -0.5f}, (V3) {1.0f, 0.0f, 0.0f}},
(Vertex) { (V2) {0.5f, -0.5f}, (V3) {0.0f, 1.0f, 0.0f}},
(Vertex) { (V2) {0.5f, 0.5f}, (V3) {0.0f, 0.0f, 1.0f}},
(Vertex) { (V2) {-0.5f, 0.5f}, (V3) {1.0f, 1.0f, 1.0f}},
};
const int VERTICES_SIZE = VK_ARRAY_LEN(vertices);
const uint16_t indices[] = {
0, 1, 2, 2, 3, 0
};
const int INDICES_SIZE = VK_ARRAY_LEN(indices);
static int resizing_event_watcher(void* data, SDL_Event* event) {
if (event->type == SDL_WINDOWEVENT &&
event->window.event == SDL_WINDOWEVENT_RESIZED) {
s.sdl_window_resized = 1;
}
return 0;
}
bool
init()
{
if (SDL_Init(SDL_INIT_VIDEO) < 0) {
vk_log(VK_INFO, "SDL could not initialize! SDL_Error: %s\n", SDL_GetError());
return false;
}
s.sdl_window = SDL_CreateWindow("Vulkanizater",
SDL_WINDOWPOS_UNDEFINED,
SDL_WINDOWPOS_UNDEFINED,
s.window_w,
s.window_h,
SDL_WINDOW_SHOWN | SDL_WINDOW_RESIZABLE | SDL_WINDOW_VULKAN);
if (s.sdl_window == NULL) {
vk_log(VK_INFO, "Window could not be created! SDL_Error: %s\n", SDL_GetError());
return false;
}
SDL_AddEventWatch(resizing_event_watcher,NULL);
return true;
}
void
closeSDL()
{
SDL_DestroyWindow(s.sdl_window);
s.sdl_window = NULL;
SDL_Quit();
}
bool
checkValidationLayerSupport()
{
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, NULL);
VkLayerProperties availableLayers[layerCount];
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);
for (uint32_t i = 0; i < validation_layer_count; i++) {
bool layerFound = false;
for (uint32_t j = 0; j < layerCount; j++) {
if (strcmp(validation_layers[i], availableLayers[j].layerName) == 0) {
layerFound = true;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
static VKAPI_ATTR VkBool32 VKAPI_CALL
debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData)
{
vk_log(VK_ERROR, "validation layer: %s\n", pCallbackData->pMessage);
return VK_FALSE;
}
void
vulkan_setup_debug_messenger()
{
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo = {0};
createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
createInfo.pfnUserCallback = debugCallback;
createInfo.pUserData = NULL; // Optional
// TODO: func pointers returned are NULL
s.pfnCreateDebugUtilsMessengerEXT = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr(s.vk_instance, "vkCreateDebugUtilsMessengerEXT");
s.pfnDestroyDebugUtilsMessengerEXT = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr(s.vk_instance, "vkDestroyDebugUtilsMessengerEXT");
VkResult result = s.pfnCreateDebugUtilsMessengerEXT(s.vk_instance, &createInfo, NULL, &s.vk_debug_messenger);
if (result != VK_SUCCESS) {
vk_log(VK_WARN, "failed to set up debug messenger!\n");
} else {
vk_log(VK_INFO, "Debug messanger created!\n");
}
}
void
vulkan_create_instance()
{
if (enableValidationLayers && !checkValidationLayerSupport()) {
vk_log(VK_ERROR, "validation layers requested, but not available!\n");
}
uint32_t instanceVersion;
VkResult result = vkEnumerateInstanceVersion(&instanceVersion);
if (result == VK_SUCCESS) {
if (instanceVersion < VK_MAKE_API_VERSION(0, 1, 3, 0)) {
vk_log(VK_ERROR, "Vulkan version 1.3 or greater required!\n");
exit(1);
}
vk_log(VK_INFO, "Vulkan version found (%d) %d.%d.%d\n",
VK_API_VERSION_VARIANT(instanceVersion),
VK_API_VERSION_MAJOR(instanceVersion),
VK_API_VERSION_MINOR(instanceVersion),
VK_API_VERSION_PATCH(instanceVersion));
} else {
vk_log(VK_ERROR, "Failed to retrieve vulkan version, is vulkan supported in this system?\n");
exit(1);
}
// Load Vulkan and create instance
VkApplicationInfo appInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "Vulkan Application",
.applicationVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
.pEngineName = NULL,
.engineVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
.apiVersion = VK_MAKE_API_VERSION(0, 1, 3, 0),
};
uint32_t sdlExtensionCount = 0;
if (SDL_Vulkan_GetInstanceExtensions(s.sdl_window, &sdlExtensionCount, NULL) == SDL_FALSE) {
vk_log(VK_ERROR, "SDL_Vulkan_GetInstanceExtensions failed: %s\n", SDL_GetError());
}
// make space for debug extenetion
if (enableValidationLayers) {
sdlExtensionCount++;
}
const char* sdlExtensions[sdlExtensionCount];
if (SDL_Vulkan_GetInstanceExtensions(s.sdl_window, &sdlExtensionCount, sdlExtensions) == SDL_FALSE) {
vk_log(VK_ERROR, "SDL_Vulkan_GetInstanceExtensions failed: %s\n", SDL_GetError());
}
// add debug extenetion
if (enableValidationLayers) {
sdlExtensions[sdlExtensionCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
}
vk_log(VK_INFO, "The sdl extensions:\n");
for (uint32_t i = 0; i < sdlExtensionCount; i++) {
vk_log(VK_INFO, "\t%s\n", sdlExtensions[i]);
}
VkInstanceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &appInfo,
.enabledExtensionCount = sdlExtensionCount,
.ppEnabledExtensionNames = sdlExtensions,
.enabledLayerCount = 0,
};
if (enableValidationLayers) {
createInfo.enabledLayerCount = validation_layer_count;
createInfo.ppEnabledLayerNames = validation_layers;
}
if (vkCreateInstance(&createInfo, NULL, &s.vk_instance) != VK_SUCCESS) {
vk_log(VK_ERROR, "Can't start vulkan instance\n");
}
vk_log(VK_INFO, "Vulkan instance created\n");
}
VkExtent2D
chooseSwapExtent(const VkSurfaceCapabilitiesKHR * capabilities)
{
if (capabilities->currentExtent.width != UINT32_MAX) {
return capabilities->currentExtent;
} else {
int width, height;
SDL_GetWindowSize(s.sdl_window, &width, &height);
VkExtent2D actualExtent;
actualExtent.width = (uint32_t) width;
actualExtent.height = (uint32_t) height;
// Manual implementation of std::clamp since it is not available in C
actualExtent.width = (actualExtent.width < capabilities->minImageExtent.width) ? capabilities->minImageExtent.width :
(actualExtent.width > capabilities->maxImageExtent.width) ? capabilities->maxImageExtent.width :
actualExtent.width;
actualExtent.height = (actualExtent.height < capabilities->minImageExtent.height) ? capabilities->minImageExtent.height :
(actualExtent.height > capabilities->maxImageExtent.height) ? capabilities->maxImageExtent.height :
actualExtent.height;
return actualExtent;
}
}
VkSurfaceFormatKHR
chooseSwapSurfaceFormat(const VkSurfaceFormatKHR * availableFormats, uint32_t formatCount)
{
for (uint32_t i = 0 ; i < formatCount; i ++) {
if (availableFormats[i].format == VK_FORMAT_B8G8R8A8_SRGB &&
availableFormats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormats[i];
}
}
// if it fails pick the first one
return availableFormats[0];
}
VkPresentModeKHR
chooseSwapPresentMode(const VkPresentModeKHR * presentModes, uint32_t presentModeCount)
{
for (uint32_t i = 0 ; i < presentModeCount; i ++) {
if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
return presentModes[i];
}
}
// if it fails pick the the FIFO one
return VK_PRESENT_MODE_FIFO_KHR;
}
typedef struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
VkSurfaceFormatKHR formats[100];
uint32_t formatCount;
VkPresentModeKHR presentModes[100];
uint32_t presentModeCount;
} SwapChainSupportDetails;
SwapChainSupportDetails
querySwapChainSupport(VkPhysicalDevice device)
{
// TODO Make SwapChainSupportDetails malloc it;s arrays and free it after it is used.
SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, s.vk_surface, &details.capabilities);
vkGetPhysicalDeviceSurfaceFormatsKHR(device, s.vk_surface, &details.formatCount, NULL);
if (details.formatCount != 0) {
// todo alloc format arrray
vkGetPhysicalDeviceSurfaceFormatsKHR(device, s.vk_surface, &details.formatCount, details.formats);
}
vkGetPhysicalDeviceSurfacePresentModesKHR(device, s.vk_surface, &details.presentModeCount, NULL);
if (details.presentModeCount != 0) {
// todo alloc presentModes array
vkGetPhysicalDeviceSurfacePresentModesKHR(device, s.vk_surface, &details.presentModeCount, details.presentModes);
}
return details;
}
typedef struct QueueFamilyIndices {
uint32_t graphicsFamily;
bool graphicsFlag;
uint32_t presentFamily;
bool presentFlag;
} QueueFamilyIndices;
bool
vulkan_queue_family_check_flags(QueueFamilyIndices x)
{
return x.graphicsFlag && x.presentFlag;
}
QueueFamilyIndices vulkan_find_queue_families(VkPhysicalDevice device) {
QueueFamilyIndices indices;
indices.graphicsFlag = false;
indices.presentFlag = false;
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL);
VkQueueFamilyProperties queueFamilies[queueFamilyCount];
vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies);
for (uint32_t i = 0; i < queueFamilyCount; i++) {
if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
indices.graphicsFamily = i;
indices.graphicsFlag = true;
}
VkBool32 presentSupport = false;
vkGetPhysicalDeviceSurfaceSupportKHR(device, i, s.vk_surface, &presentSupport);
if (presentSupport) {
indices.presentFamily = i;
indices.presentFlag = true;
}
if (vulkan_queue_family_check_flags(indices)) break;
}
return indices;
}
bool
vulkan_check_device_extension_support(VkPhysicalDevice device)
{
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, NULL);
VkExtensionProperties availableExtensions[extensionCount];
vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, availableExtensions);
uint32_t flag = 0;
for (uint32_t i = 0; i < deviceExtensionCount; i++) {
for (uint32_t j = 0; j < extensionCount; j++) {
if (strcmp(deviceExtensions[i], availableExtensions[j].extensionName) == 0) {
flag++;
break;
}
}
}
return flag == deviceExtensionCount;
}
bool
vulkan_is_device_suitable(VkPhysicalDevice device)
{
QueueFamilyIndices indices = vulkan_find_queue_families(device);
bool extensionsSupported = vulkan_check_device_extension_support(device);
bool swapChainAdequate = false;
if (extensionsSupported) {
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
swapChainAdequate = !(swapChainSupport.formatCount == 0) && !(swapChainSupport.presentModeCount == 0);
}
return vulkan_queue_family_check_flags(indices) && extensionsSupported && swapChainAdequate;
}
void
vulkan_pick_physical_device()
{
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(s.vk_instance, &deviceCount, NULL);
if (deviceCount == 0) {
vk_log(VK_INFO, "failed to find GPUs with Vulkan support!\n");
}
VkPhysicalDevice devices[deviceCount];
vkEnumeratePhysicalDevices(s.vk_instance, &deviceCount, devices);
for (uint32_t i = 0; i < deviceCount; i++) {
if (vulkan_is_device_suitable(devices[i])) {
s.vk_physical_device = devices[i];
break;
}
}
if (s.vk_physical_device == VK_NULL_HANDLE) {
vk_log(VK_ERROR, "failed to find a suitable GPU!\n");
}
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(s.vk_physical_device, &deviceProperties);
vk_log(VK_INFO, "Picked [%s] physical device.\n", deviceProperties.deviceName);
uint32_t extensionCount = 0;
vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL);
VkExtensionProperties extensions[extensionCount];
VkResult result = vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions);
vk_log(VK_INFO, "Vulkan enabled extensions: %s\n", string_VkResult(result));
for (uint32_t i = 0; i < extensionCount; i++) {
vk_log(VK_INFO, "\t%s\n", extensions[i].extensionName);
}
}
void
vulkan_create_logical_device()
{
QueueFamilyIndices indices = vulkan_find_queue_families(s.vk_physical_device);
// TODO CREATE MULPILE QUEUES
// https://vulkan-tutorial.com/en/Drawing_a_triangle/Presentation/Window_surface#page_Creating-the-presentation-queue
VkDeviceQueueCreateInfo queueCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = indices.graphicsFamily,
.queueCount = 1,
};
float queuePriority = 1.0f;
queueCreateInfo.pQueuePriorities = &queuePriority;
VkPhysicalDeviceFeatures deviceFeatures = {0};
vkGetPhysicalDeviceFeatures(s.vk_physical_device, &deviceFeatures);
#ifndef NDEBUG
/* Disable robust buffer access when building without debug */
deviceFeatures.robustBufferAccess = VK_FALSE;
#endif
VkPhysicalDeviceDynamicRenderingFeaturesKHR dynamic_rendering_feature = {
.sType = VK_STRUCTURE_TYPE_PHYSICAL_DEVICE_DYNAMIC_RENDERING_FEATURES_KHR,
.dynamicRendering = VK_TRUE,
};
VkDeviceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pQueueCreateInfos = &queueCreateInfo,
.queueCreateInfoCount = 1,
.pNext = &dynamic_rendering_feature,
.pEnabledFeatures = &deviceFeatures,
.enabledExtensionCount = deviceExtensionCount,
.ppEnabledExtensionNames = deviceExtensions,
.enabledLayerCount = 0,
};
if (vkCreateDevice(s.vk_physical_device, &createInfo, NULL, &s.vk_device) != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to create logical device!\n");
}
vk_log(VK_INFO, "Vulkan logical device created\n");
vkGetDeviceQueue(s.vk_device, indices.graphicsFamily, 0, &s.vk_graphics_queue);
vkGetDeviceQueue(s.vk_device, indices.presentFamily, 0, &s.vk_present_queue);
}
void
update_camera(float xoffset, float yoffset)
{
s.camera.yaw += xoffset;
s.camera.pitch += yoffset;
// Make sure that when pitch is out of bounds, the screen doesn't get flipped
if (s.camera.pitch > 89.0f)
s.camera.pitch = 89.0f;
if (s.camera.pitch < -89.0f)
s.camera.pitch = -89.0f;
/* vec3 front; */
/* front[0] = -sin(glm_rad(s.camera.yaw)) * cos(glm_rad(s.camera.pitch)); */
/* front[1] = sin(glm_rad(s.camera.pitch)); */
/* front[2] = -cos(glm_rad(s.camera.yaw)) * cos(glm_rad(s.camera.pitch)); */
/* glm_normalize_to(front, s.camera.front); */
}
void
mouseCallback(SDL_Event *event)
{
if (event->type != SDL_MOUSEMOTION) return;
if (!s.mouse_pressed) return;
float xoffset = event->motion.xrel;
float yoffset = -event->motion.yrel; // Reversed since y-coordinates range from bottom to top
float sensitivity = 0.1f; // Change this value to your liking
xoffset *= sensitivity;
yoffset *= sensitivity;
update_camera(xoffset, yoffset);
}
void
vulkan_create_surface()
{
if (SDL_Vulkan_CreateSurface(s.sdl_window, s.vk_instance, &s.vk_surface) == SDL_FALSE) {
vk_log(VK_ERROR, "Failed to create surface\n");
} else {
vk_log(VK_INFO, "Vulkan surface created\n");
}
}
void
vulkan_create_swap_chain()
{
SwapChainSupportDetails swapChainSupport = querySwapChainSupport(s.vk_physical_device);
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats, swapChainSupport.formatCount);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes, swapChainSupport.presentModeCount);
VkExtent2D extent = chooseSwapExtent(&swapChainSupport.capabilities);
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
VkSwapchainCreateInfoKHR createInfo = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = s.vk_surface,
.minImageCount = imageCount,
.imageFormat = surfaceFormat.format,
.imageColorSpace = surfaceFormat.colorSpace,
.imageExtent = extent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
};
QueueFamilyIndices indices = vulkan_find_queue_families(s.vk_physical_device);
uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily};
if (indices.graphicsFamily != indices.presentFamily) {
createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
createInfo.queueFamilyIndexCount = 2;
createInfo.pQueueFamilyIndices = queueFamilyIndices;
} else {
createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
createInfo.queueFamilyIndexCount = 0; // Optional
createInfo.pQueueFamilyIndices = NULL; // Optional
}
createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
createInfo.presentMode = presentMode;
createInfo.clipped = VK_TRUE;
createInfo.oldSwapchain = VK_NULL_HANDLE;
VK_CHECK(vkCreateSwapchainKHR(s.vk_device, &createInfo, NULL, &s.vk_swap_chain));
/* if (result != VK_SUCCESS) { */
/* vk_log(VK_ERROR, "ERROR: failed to create swap chain! %s\n", string_VkResult(result)); */
/* } */
VK_CHECK(vkGetSwapchainImagesKHR(s.vk_device, s.vk_swap_chain, &s.vk_swap_chain_image_count, NULL));
//vk_log(VK_INFO, "vk_swap_chain_images count: %d\n", s.vk_swap_chain_image_count);
// todo alloc space for images
VK_CHECK(vkGetSwapchainImagesKHR(s.vk_device, s.vk_swap_chain, &s.vk_swap_chain_image_count, s.vk_swap_chain_images));
s.vk_swap_chain_image_format = surfaceFormat.format;
s.vk_swap_chain_extent = extent;
vk_log(VK_INFO, "Vulkan swapchain created!\n");
}
void
vulkan_create_image_views()
{
for (size_t i = 0; i < s.vk_swap_chain_image_count; i++) {
VkImageViewCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = s.vk_swap_chain_images[i],
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = s.vk_swap_chain_image_format,
.components.r = VK_COMPONENT_SWIZZLE_IDENTITY,
.components.g = VK_COMPONENT_SWIZZLE_IDENTITY,
.components.b = VK_COMPONENT_SWIZZLE_IDENTITY,
.components.a = VK_COMPONENT_SWIZZLE_IDENTITY,
.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.subresourceRange.baseMipLevel = 0,
.subresourceRange.levelCount = 1,
.subresourceRange.baseArrayLayer = 0,
.subresourceRange.layerCount = 1,
};
if (vkCreateImageView(s.vk_device, &createInfo, NULL, &s.vk_swap_chain_image_views[i]) != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to create image views!\n");
}
}
vk_log(VK_INFO, "Vulkan image views created!\n");
}
VkShaderModule
createShaderModule(const char * code, long size)
{
VkShaderModuleCreateInfo createInfo = {0};
createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
createInfo.codeSize = size;
createInfo.pCode = (const uint32_t *)code;
VkShaderModule shaderModule;
if (vkCreateShaderModule(s.vk_device, &createInfo, NULL, &shaderModule) != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to create shader module!\n");
}
return shaderModule;
}
shaderc_compilation_result_t
load_compile_shader_data(const char * path, shaderc_shader_kind shader_kind)
{
FILE* file = fopen(path, "r");
if (!file) {
vk_log(VK_ERROR, "SHADER COMPILATION: Failed to open file: %s\n", path);
return NULL;
}
fseek(file, 0, SEEK_END);
long glsl_length = ftell(file);
fseek(file, 0, SEEK_SET);
char* glsl_src = (char*)malloc(glsl_length + 1);
if (!glsl_src) {
vk_log(VK_ERROR, "SHADER COMPILATION: Failed to allocate memory\n");
fclose(file);
return NULL;
}
fread(glsl_src, 1, glsl_length, file);
glsl_src[glsl_length] = '\0';
fclose(file);
shaderc_compiler_t compiler = shaderc_compiler_initialize();
if (!compiler) {
vk_log(VK_ERROR, "SHADER COMPILATION: Failed to initialize shader compiler\n");
free(glsl_src);
return NULL;
}
shaderc_compile_options_t options = shaderc_compile_options_initialize();
shaderc_compilation_result_t result = shaderc_compile_into_spv(compiler,
glsl_src,
glsl_length,
shader_kind,
path, "main", options);
if (shaderc_result_get_compilation_status(result) != shaderc_compilation_status_success) {
vk_log(VK_ERROR, "SHADER COMPILATION: error: %s\n", shaderc_result_get_error_message(result));
shaderc_result_release(result);
shaderc_compiler_release(compiler);
free(glsl_src);
return NULL;
}
shaderc_compiler_release(compiler);
free(glsl_src);
return result;
}
void
vulkan_create_graphics_pipeline()
{
shaderc_compilation_result_t vert_result = load_compile_shader_data("src/shader.vert", shaderc_vertex_shader);
if (!vert_result) {
vk_log(VK_ERROR, "Can't load vertex shader\n");
if (s.prev_vert_result) {
vert_result = s.prev_vert_result;
}
}
if (s.prev_vert_result && vert_result != s.prev_vert_result) shaderc_result_release(s.prev_vert_result);
s.prev_vert_result = vert_result;
shaderc_compilation_result_t frag_result = load_compile_shader_data("src/shader.frag", shaderc_fragment_shader);
if (!frag_result) {
vk_log(VK_ERROR, "Can't load fragment shader\n");
if (s.prev_frag_result) {
frag_result = s.prev_frag_result;
}
}
if (s.prev_frag_result && frag_result != s.prev_frag_result) shaderc_result_release(s.prev_frag_result);
s.prev_frag_result = frag_result;
const char * vert_data = shaderc_result_get_bytes(vert_result);
long vert_size = shaderc_result_get_length(vert_result);
const char * frag_data = shaderc_result_get_bytes(frag_result);
long frag_size = shaderc_result_get_length(frag_result);
vk_log(VK_INFO, "Shaders loaded\n");
VkShaderModule vertShaderModule = createShaderModule(vert_data, vert_size);
VkShaderModule fragShaderModule = createShaderModule(frag_data, frag_size);
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {0};
vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
vertShaderStageInfo.module = vertShaderModule;
vertShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {0};
fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
fragShaderStageInfo.module = fragShaderModule;
fragShaderStageInfo.pName = "main";
VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState = {0};
dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
dynamicState.dynamicStateCount = 2;
dynamicState.pDynamicStates = dynamicStates;
VkVertexInputBindingDescription bindingDescription = {0};
bindingDescription.binding = 0;
bindingDescription.stride = sizeof(Vertex);
bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX;
VkVertexInputAttributeDescription attributeDescriptions[2] = {0};
attributeDescriptions[0].binding = 0;
attributeDescriptions[0].location = 0;
attributeDescriptions[0].format = VK_FORMAT_R32G32_SFLOAT;
attributeDescriptions[0].offset = offsetof(Vertex, pos);
attributeDescriptions[1].binding = 0;
attributeDescriptions[1].location = 1;
attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT;
attributeDescriptions[1].offset = offsetof(Vertex, color);
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {0};
vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
vertexInputInfo.vertexBindingDescriptionCount = 1;
vertexInputInfo.pVertexBindingDescriptions = &bindingDescription;
vertexInputInfo.vertexAttributeDescriptionCount = 2;
vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions;
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {};
inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
inputAssembly.primitiveRestartEnable = VK_FALSE;
VkViewport viewport = {0};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float) s.vk_swap_chain_extent.width;
viewport.height = (float) s.vk_swap_chain_extent.height;
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
VkRect2D scissor = {0};
scissor.offset = (VkOffset2D){0, 0};
scissor.extent = s.vk_swap_chain_extent;
VkPipelineViewportStateCreateInfo viewportState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
VkPipelineRasterizationStateCreateInfo rasterizer = {0};
rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
rasterizer.depthClampEnable = VK_FALSE;
rasterizer.rasterizerDiscardEnable = VK_FALSE;
rasterizer.polygonMode = VK_POLYGON_MODE_FILL;
rasterizer.lineWidth = 1.0f;
rasterizer.cullMode = VK_CULL_MODE_NONE;
//rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
//rasterizer.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE;
rasterizer.depthBiasEnable = VK_FALSE;
rasterizer.depthBiasConstantFactor = 0.0f; // Optional
rasterizer.depthBiasClamp = 0.0f; // Optional
rasterizer.depthBiasSlopeFactor = 0.0f; // Optional
VkPipelineMultisampleStateCreateInfo multisampling = {0};
multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
multisampling.sampleShadingEnable = VK_FALSE;
multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT;
multisampling.minSampleShading = 1.0f; // Optional
multisampling.pSampleMask = NULL; // Optional
multisampling.alphaToCoverageEnable = VK_FALSE; // Optional
multisampling.alphaToOneEnable = VK_FALSE; // Optional
VkPipelineColorBlendAttachmentState colorBlendAttachment = {0};
colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT;
colorBlendAttachment.blendEnable = VK_TRUE;
colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA;
colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA;
colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD;
colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE;
colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO;
colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD;
/* colorBlendAttachment.blendEnable = VK_FALSE; */
/* colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional */
/* colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional */
/* colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional */
/* colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional */
/* colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional */
/* colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional */
VkPipelineColorBlendStateCreateInfo colorBlending = {0};
colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
colorBlending.logicOpEnable = VK_TRUE;
colorBlending.logicOp = VK_LOGIC_OP_COPY; // Optional
colorBlending.attachmentCount = 1;
colorBlending.pAttachments = &colorBlendAttachment;
colorBlending.blendConstants[0] = 0.0f; // Optional
colorBlending.blendConstants[1] = 0.0f; // Optional
colorBlending.blendConstants[2] = 0.0f; // Optional
colorBlending.blendConstants[3] = 0.0f; // Optional
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0};
pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
pipelineLayoutInfo.setLayoutCount = 1;
pipelineLayoutInfo.pSetLayouts = &s.vk_descriptor_set_layout;
pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional
pipelineLayoutInfo.pPushConstantRanges = NULL; // Optional
if (vkCreatePipelineLayout(s.vk_device, &pipelineLayoutInfo, NULL, &s.vk_pipeline_layout) != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to create pipeline layout!\n");
}
VkPipelineRenderingCreateInfo pipeline_rendering_create_info = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RENDERING_CREATE_INFO_KHR,
.colorAttachmentCount = 1,
.pColorAttachmentFormats = &s.vk_swap_chain_image_format,
};
VkGraphicsPipelineCreateInfo pipelineInfo = {0};
pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
pipelineInfo.stageCount = 2;
pipelineInfo.pStages = shaderStages;
pipelineInfo.pNext = &pipeline_rendering_create_info,
pipelineInfo.pVertexInputState = &vertexInputInfo;
pipelineInfo.pInputAssemblyState = &inputAssembly;
pipelineInfo.pViewportState = &viewportState;
pipelineInfo.pRasterizationState = &rasterizer;
pipelineInfo.pMultisampleState = &multisampling;
pipelineInfo.pDepthStencilState = NULL; // Optional
pipelineInfo.pColorBlendState = &colorBlending;
pipelineInfo.pDynamicState = &dynamicState;
pipelineInfo.layout = s.vk_pipeline_layout;
pipelineInfo.renderPass = VK_NULL_HANDLE;
pipelineInfo.subpass = 0;
pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; // Optional
pipelineInfo.basePipelineIndex = -1; // Optional
if (vkCreateGraphicsPipelines(s.vk_device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &s.vk_graphics_pipeline) != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to create graphics pipeline!\n");
}
if (s.prev_vert_result != vert_result) {
shaderc_result_release(vert_result);
}
if (s.prev_frag_result != frag_result) {
shaderc_result_release(frag_result);
}
vkDestroyShaderModule(s.vk_device, fragShaderModule, NULL);
vkDestroyShaderModule(s.vk_device, vertShaderModule, NULL);
}
void
vulkan_create_command_pool()
{
QueueFamilyIndices queueFamilyIndices = vulkan_find_queue_families(s.vk_physical_device);
VkCommandPoolCreateInfo poolInfo = {0};
poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily;
VK_CHECK(vkCreateCommandPool(s.vk_device, &poolInfo, NULL, &s.vk_command_pool));
}
void
vulkan_create_command_buffer()
{
// TODO Find a way to group allocation
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkCommandBufferAllocateInfo allocInfo = {0};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.commandPool = s.vk_command_pool;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandBufferCount = 1;
VK_CHECK(vkAllocateCommandBuffers(s.vk_device, &allocInfo, &s.frames[i].vk_command_buffer));
}
}
void
recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex)
{
VkCommandBufferBeginInfo beginInfo = {0};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = 0; // Optional
beginInfo.pInheritanceInfo = NULL; // Optional
VK_CHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo));
VkImageMemoryBarrier image_memory_barrier = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.newLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.image = s.vk_swap_chain_images[imageIndex],
.subresourceRange = (VkImageSubresourceRange){
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
}
};
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT, // srcStageMask
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // dstStageMask
0,
0,
NULL,
0,
NULL,
1, // imageMemoryBarrierCount
&image_memory_barrier // pImageMemoryBarriers
);
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
// TODO DYNAMIC RENDERING
VkRenderingAttachmentInfo colorAttachment = {};
colorAttachment.sType = VK_STRUCTURE_TYPE_RENDERING_ATTACHMENT_INFO;
colorAttachment.imageView = s.vk_swap_chain_image_views[imageIndex];
colorAttachment.imageLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;
colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
//colorAttachment.clearValue.color = clearColor;
VkRenderingInfo renderingInfo = {};
renderingInfo.sType = VK_STRUCTURE_TYPE_RENDERING_INFO;
renderingInfo.renderArea = (VkRect2D){ {0, 0}, {(float)(s.vk_swap_chain_extent.width), (float)(s.vk_swap_chain_extent.height)} };
renderingInfo.layerCount = 1;
renderingInfo.colorAttachmentCount = 1;
renderingInfo.pColorAttachments = &colorAttachment;
vkCmdBeginRendering(commandBuffer, &renderingInfo);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, s.vk_graphics_pipeline);
VkViewport viewport = {0};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = (float)(s.vk_swap_chain_extent.width);
viewport.height = (float)(s.vk_swap_chain_extent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor = {0};
scissor.offset = (VkOffset2D){0, 0};
scissor.extent = s.vk_swap_chain_extent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
VkBuffer vertexBuffers[] = {s.vk_vertex_buffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, s.vk_index_buffer, 0, VK_INDEX_TYPE_UINT16);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, s.vk_pipeline_layout, 0, 1, &s.frames[currentFrame].vk_descriptor_set, 0, NULL);
vkCmdDrawIndexed(commandBuffer, INDICES_SIZE, 1, 0, 0, 0);
vkCmdEndRendering(commandBuffer);
/* vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); */
/* vkCmdEndRenderPass(commandBuffer); */
VkImageMemoryBarrier image_memory_barrier2 = {
.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER,
.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
.oldLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
.image = s.vk_swap_chain_images[imageIndex],
.subresourceRange = (VkImageSubresourceRange){
.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT,
.baseMipLevel = 0,
.levelCount = 1,
.baseArrayLayer = 0,
.layerCount = 1,
}
};
vkCmdPipelineBarrier(
commandBuffer,
VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, // srcStageMask
VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, // dstStageMask
0,
0,
NULL,
0,
NULL,
1, // imageMemoryBarrierCount
&image_memory_barrier2 // pImageMemoryBarriers
);
VK_CHECK(vkEndCommandBuffer(commandBuffer));
}
void
vulkan_create_sync_objects()
{
VkSemaphoreCreateInfo semaphoreInfo = {0};
semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
VkFenceCreateInfo fenceInfo = {0};
fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VK_CHECK(vkCreateSemaphore(s.vk_device, &semaphoreInfo, NULL, &s.frames[i].image_available_semaphore));
VK_CHECK(vkCreateSemaphore(s.vk_device, &semaphoreInfo, NULL, &s.frames[i].render_finished_semaphore));
VK_CHECK(vkCreateFence(s.vk_device, &fenceInfo, NULL, &s.frames[i].in_flight_fence));
}
}
uint32_t
findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties)
{
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(s.vk_physical_device, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if ((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) {
return i;
}
}
vk_log(VK_ERROR, "failed to find suitable memory type!\n");
return 9999;
}
void
createBuffer(VkDeviceSize size,
VkBufferUsageFlags usage,
VkMemoryPropertyFlags properties,
VkBuffer *buffer, VkDeviceMemory *bufferMemory)
{
VkBufferCreateInfo bufferInfo = {0};
bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO;
bufferInfo.size = size;
bufferInfo.usage = usage;
bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
VK_CHECK(vkCreateBuffer(s.vk_device, &bufferInfo, NULL, buffer));
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(s.vk_device, *buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {0};
allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO;
allocInfo.allocationSize = memRequirements.size;
allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties);
// TODO: group allocations etc... (allocations limited by hardware)
VK_CHECK(vkAllocateMemory(s.vk_device, &allocInfo, NULL, bufferMemory));
VK_CHECK(vkBindBufferMemory(s.vk_device, *buffer, *bufferMemory, 0));
}
void
copy_buffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size) {
VkCommandBufferAllocateInfo allocInfo = {0};
allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
allocInfo.commandPool = s.vk_command_pool;
allocInfo.commandBufferCount = 1;
VkCommandBuffer commandBuffer;
VK_CHECK(vkAllocateCommandBuffers(s.vk_device, &allocInfo, &commandBuffer));
VkCommandBufferBeginInfo beginInfo = {0};
beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT;
VK_CHECK(vkBeginCommandBuffer(commandBuffer, &beginInfo));
VkBufferCopy copyRegion = {0};
copyRegion.srcOffset = 0; // Optional
copyRegion.dstOffset = 0; // Optional
copyRegion.size = size;
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region);
VK_CHECK(vkEndCommandBuffer(commandBuffer));
VkSubmitInfo submitInfo = {0};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &commandBuffer;
VK_CHECK(vkQueueSubmit(s.vk_graphics_queue, 1, &submitInfo, VK_NULL_HANDLE));
VK_CHECK(vkQueueWaitIdle(s.vk_graphics_queue));
vkFreeCommandBuffers(s.vk_device, s.vk_command_pool, 1, &commandBuffer);
}
void
vulkan_create_vertex_buffer()
{
VkDeviceSize bufferSize = sizeof(vertices[0]) * VERTICES_SIZE;
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
createBuffer(bufferSize,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&stagingBuffer, &stagingBufferMemory);
void* data;
VK_CHECK(vkMapMemory(s.vk_device, stagingBufferMemory, 0, bufferSize, 0, &data));
memcpy(data, vertices, (size_t) bufferSize);
vkUnmapMemory(s.vk_device, stagingBufferMemory);
createBuffer(bufferSize,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
&s.vk_vertex_buffer, &s.vk_vertex_buffer_memory);
copy_buffer(stagingBuffer, s.vk_vertex_buffer, bufferSize);
vkDestroyBuffer(s.vk_device, stagingBuffer, NULL);
vkFreeMemory(s.vk_device, stagingBufferMemory, NULL);
}
void
vulkan_create_index_buffer()
{
VkDeviceSize bufferSize = sizeof(indices[0]) * INDICES_SIZE;
VkBuffer stagingBuffer;
VkDeviceMemory stagingBufferMemory;
createBuffer(bufferSize,
VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&stagingBuffer, &stagingBufferMemory);
void* data;
VK_CHECK(vkMapMemory(s.vk_device, stagingBufferMemory, 0, bufferSize, 0, &data));
memcpy(data, indices, (size_t) bufferSize);
vkUnmapMemory(s.vk_device, stagingBufferMemory);
createBuffer(bufferSize,
VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT,
VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
&s.vk_index_buffer, &s.vk_index_buffer_memory);
copy_buffer(stagingBuffer, s.vk_index_buffer, bufferSize);
vkDestroyBuffer(s.vk_device, stagingBuffer, NULL);
vkFreeMemory(s.vk_device, stagingBufferMemory, NULL);
}
void
vulkan_create_descriptor_set_layout()
{
VkDescriptorSetLayoutBinding uboLayoutBinding = {0};
uboLayoutBinding.binding = 0;
uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
uboLayoutBinding.descriptorCount = 1;
uboLayoutBinding.stageFlags = VK_SHADER_STAGE_ALL;
//uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT | VK_SHADER_STAGE_FRAGMENT_BIT;
uboLayoutBinding.pImmutableSamplers = NULL; // optional
VkDescriptorSetLayoutCreateInfo layoutInfo = {0};
layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO;
layoutInfo.bindingCount = 1;
layoutInfo.pBindings = &uboLayoutBinding;
VK_CHECK(vkCreateDescriptorSetLayout(s.vk_device, &layoutInfo, NULL, &s.vk_descriptor_set_layout));
}
void
vulkan_create_uniform_buffers()
{
VkDeviceSize bufferSize = sizeof(UniformBufferObject);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
createBuffer(bufferSize,
VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT,
VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
&s.frames[i].vk_uniform_buffer, &s.frames[i].vk_uniform_buffer_memory);
VK_CHECK(vkMapMemory(s.vk_device, s.frames[i].vk_uniform_buffer_memory, 0, bufferSize, 0, &s.frames[i].vk_uniform_buffer_mapped));
}
}
void
vulkan_create_descriptor_pool()
{
VkDescriptorPoolSize poolSize = {0};
poolSize.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
poolSize.descriptorCount = MAX_FRAMES_IN_FLIGHT;
VkDescriptorPoolCreateInfo poolInfo = {0};
poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO;
poolInfo.poolSizeCount = 1;
poolInfo.pPoolSizes = &poolSize;
poolInfo.maxSets = MAX_FRAMES_IN_FLIGHT;
VK_CHECK(vkCreateDescriptorPool(s.vk_device, &poolInfo, NULL, &s.vk_descriptor_pool));
}
VkDescriptorSet * temp_set;
void
vulkan_create_descriptor_sets()
{
VkDescriptorSetLayout layouts[MAX_FRAMES_IN_FLIGHT] = {0};
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
layouts[i] = s.vk_descriptor_set_layout;
}
// TODO Find a way to group allocation
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorSetAllocateInfo allocInfo = {0};
allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO;
allocInfo.descriptorPool = s.vk_descriptor_pool;
allocInfo.descriptorSetCount = 1;
allocInfo.pSetLayouts = layouts;
VK_CHECK(vkAllocateDescriptorSets(s.vk_device, &allocInfo, &s.frames[i].vk_descriptor_set));
}
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo bufferInfo = {0};
bufferInfo.buffer = s.frames[i].vk_uniform_buffer;
bufferInfo.offset = 0;
bufferInfo.range = sizeof(UniformBufferObject);
VkWriteDescriptorSet descriptorWrite = {0};
descriptorWrite.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET;
descriptorWrite.dstSet = s.frames[i].vk_descriptor_set;
descriptorWrite.dstBinding = 0;
descriptorWrite.dstArrayElement = 0;
descriptorWrite.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER;
descriptorWrite.descriptorCount = 1;
descriptorWrite.pBufferInfo = &bufferInfo;
descriptorWrite.pImageInfo = NULL; // Optional
descriptorWrite.pTexelBufferView = NULL; // Optional
vkUpdateDescriptorSets(s.vk_device, 1, &descriptorWrite, 0, NULL);
}
}
void
cleanupSwapChain()
{
for (uint32_t i = 0; i < s.vk_swap_chain_image_count; i++) {
vkDestroyImageView(s.vk_device, s.vk_swap_chain_image_views[i], NULL);
}
vkDestroySwapchainKHR(s.vk_device, s.vk_swap_chain, NULL);
}
void
recreateSwapChain()
{
vkDeviceWaitIdle(s.vk_device);
cleanupSwapChain();
vulkan_create_swap_chain();
vulkan_create_image_views();
}
void
handle_input(bool * quit)
{
SDL_Event e;
while (SDL_PollEvent(&e) != 0) {
// User requests quit
if (e.type == SDL_QUIT) {
*quit = true;
}
// User presses a key
else if (e.type == SDL_KEYDOWN) {
switch (e.key.keysym.sym) {
case SDLK_w:
s.camera.pos[0] += 0.1f;
break;
case SDLK_s:
s.camera.pos[0] -= 0.1f;
break;
case SDLK_LEFT:
break;
case SDLK_g: // reload shaders
recreateSwapChain();
vkDestroyPipeline(s.vk_device, s.vk_graphics_pipeline, NULL);
vkDestroyPipelineLayout(s.vk_device, s.vk_pipeline_layout, NULL);
vulkan_create_graphics_pipeline();
break;
case SDLK_l:
s.rotate = s.rotate ? 0 : 1;
break;
case SDLK_r:
s.camera.pos[0] = 1.0f;
s.camera.pos[1] = 1.0f;
s.camera.pos[2] = 1.0f;
s.camera.front[0] = 0.0f;
s.camera.front[1] = 0.0f;
s.camera.front[2] = 0.0f;
s.camera.up[0] = 0.0f;
s.camera.up[1] = 0.0f;
s.camera.up[2] = 1.0f;
s.camera.yaw = 0.0f;
s.camera.pitch = 90.0f;
s.camera.lastX = 400.0f;
s.camera.lastY = 300.0f;
s.camera.fov = 45.0f;
update_camera(0,0);
break;
}
}
else if (e.type == SDL_MOUSEWHEEL) {
if(e.wheel.y > 0) // scroll up
{
s.zoom += 1;
}
else if(e.wheel.y < 0) // scroll down
{
s.zoom -= 1;
if (s.zoom == -100) s.zoom = 1;
}
}
else if (e.type == SDL_MOUSEBUTTONDOWN) {
s.mouse_pressed = 1;
}
else if (e.type == SDL_MOUSEBUTTONUP) {
s.mouse_pressed = 0;
}
mouseCallback(&e);
}
}
void
init_vulkan()
{
vk_log(VK_WARN, "====================================\n");
vk_log(VK_WARN, " DEBUG ON \n");
vk_log(VK_WARN, "====================================\n");
vulkan_create_instance();
// vulkan_setup_debug_messenger();
vulkan_create_surface();
vulkan_pick_physical_device();
vulkan_create_logical_device();
vulkan_create_swap_chain();
vulkan_create_image_views();
vulkan_create_descriptor_set_layout();
vulkan_create_graphics_pipeline();
vulkan_create_command_pool();
vulkan_create_vertex_buffer();
vulkan_create_index_buffer();
vulkan_create_uniform_buffers();
vulkan_create_descriptor_pool();
vulkan_create_descriptor_sets();
vulkan_create_command_buffer();
vulkan_create_sync_objects();
}
void
close_vulkan()
{
vkDeviceWaitIdle(s.vk_device);
// Cleanup
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(s.vk_device, s.frames[i].image_available_semaphore, NULL);
vkDestroySemaphore(s.vk_device, s.frames[i].render_finished_semaphore, NULL);
vkDestroyFence(s.vk_device, s.frames[i].in_flight_fence, NULL);
}
vkDestroyCommandPool(s.vk_device, s.vk_command_pool, NULL);
cleanupSwapChain();
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroyBuffer(s.vk_device, s.frames[i].vk_uniform_buffer, NULL);
vkFreeMemory(s.vk_device, s.frames[i].vk_uniform_buffer_memory, NULL);
}
vkDestroyDescriptorPool(s.vk_device, s.vk_descriptor_pool, NULL);
vkDestroyDescriptorSetLayout(s.vk_device, s.vk_descriptor_set_layout, NULL);
vkDestroyBuffer(s.vk_device, s.vk_vertex_buffer, NULL);
vkFreeMemory(s.vk_device, s.vk_vertex_buffer_memory, NULL);
vkDestroyBuffer(s.vk_device, s.vk_index_buffer, NULL);
vkFreeMemory(s.vk_device, s.vk_index_buffer_memory, NULL);
vkDestroyPipeline(s.vk_device, s.vk_graphics_pipeline, NULL);
vkDestroyPipelineLayout(s.vk_device, s.vk_pipeline_layout, NULL);
vkDestroyDevice(s.vk_device, NULL);
vkDestroySurfaceKHR(s.vk_instance, s.vk_surface, NULL);
/* if (enableValidationLayers) { */
/* DestroyDebugUtilsMessengerEXT(s.vk_instance, s.vk_debug_messenger, NULL); */
/* } */
vkDestroyInstance(s.vk_instance, NULL);
if (s.prev_vert_result) {
shaderc_result_release(s.prev_vert_result);
}
if (s.prev_frag_result) {
shaderc_result_release(s.prev_frag_result);
}
}
float
current_time()
{
static struct timespec startTime;
static int isStartTimeInitialized = 0;
if (!isStartTimeInitialized) {
clock_gettime(CLOCK_MONOTONIC, &startTime);
isStartTimeInitialized = 1;
}
struct timespec currentTime;
clock_gettime(CLOCK_MONOTONIC, ¤tTime);
return (currentTime.tv_sec - startTime.tv_sec) +
(currentTime.tv_nsec - startTime.tv_nsec) / 1e9f;
}
void
updateUniformBuffer(uint32_t currentImage)
{
float time = current_time();
UniformBufferObject ubo = {0};
ubo.resolution[0] = s.vk_swap_chain_extent.width;
ubo.resolution[1] = s.vk_swap_chain_extent.height;
glm_mat4_identity(ubo.model);
if (s.rotate)
glm_rotate(ubo.model, glm_rad(50 * time * glm_rad(90.0f)), GLM_ZUP);
vec3 eye = GLM_VEC3_ONE_INIT;
vec3 center = GLM_VEC3_ZERO_INIT;
// vk_log(VK_INFO, "{%.2f, %.2f, %.2f}\n", s.camera.front[0], s.camera.front[1], s.camera.front[2]);
/* glm_vec3_add(s.camera.pos, s.camera.front, center); */
glm_lookat(s.camera.pos, s.camera.front, s.camera.up, ubo.view);
/* glm_lookat(eye, center, GLM_ZUP, ubo.view); */
float aspect = s.vk_swap_chain_extent.width / (float)s.vk_swap_chain_extent.height;
glm_perspective(glm_rad(55.0f - s.zoom ), aspect, 0.1f, 100.0f, ubo.proj);
// Inverting the Y axis for Vulkan
ubo.proj[1][1] *= -1;
memcpy(s.frames[currentImage].vk_uniform_buffer_mapped, &ubo, sizeof(ubo));
}
void
draw_frame() {
vkWaitForFences(s.vk_device, 1, &s.frames[currentFrame].in_flight_fence, VK_TRUE, UINT64_MAX);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(s.vk_device, s.vk_swap_chain, UINT64_MAX, s.frames[currentFrame].image_available_semaphore, VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain();
return;
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
vk_log(VK_ERROR, "failed to acquire swap chain image!\n");
}
updateUniformBuffer(currentFrame);
vkResetFences(s.vk_device, 1, &s.frames[currentFrame].in_flight_fence);
vkResetCommandBuffer(s.frames[currentFrame].vk_command_buffer, 0);
recordCommandBuffer(s.frames[currentFrame].vk_command_buffer, imageIndex);
VkSubmitInfo submitInfo = {0};
submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
VkSemaphore waitSemaphores[] = {s.frames[currentFrame].image_available_semaphore};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
submitInfo.waitSemaphoreCount = 1;
submitInfo.pWaitSemaphores = waitSemaphores;
submitInfo.pWaitDstStageMask = waitStages;
submitInfo.commandBufferCount = 1;
submitInfo.pCommandBuffers = &s.frames[currentFrame].vk_command_buffer;
VkSemaphore signalSemaphores[] = {s.frames[currentFrame].render_finished_semaphore};
submitInfo.signalSemaphoreCount = 1;
submitInfo.pSignalSemaphores = signalSemaphores;
if (vkQueueSubmit(s.vk_graphics_queue, 1, &submitInfo, s.frames[currentFrame].in_flight_fence) != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to submit draw command buffer!\n");
}
VkSwapchainKHR swapChains[] = {s.vk_swap_chain};
VkPresentInfoKHR presentInfo = {0};
presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
presentInfo.waitSemaphoreCount = 1;
presentInfo.pWaitSemaphores = signalSemaphores;
presentInfo.swapchainCount = 1;
presentInfo.pSwapchains = swapChains;
presentInfo.pImageIndices = &imageIndex;
presentInfo.pResults = NULL;
result = vkQueuePresentKHR(s.vk_present_queue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || s.sdl_window_resized) {
s.sdl_window_resized = 0;
recreateSwapChain();
} else if (result != VK_SUCCESS) {
vk_log(VK_ERROR, "failed to present swap chain image!\n");
}
currentFrame = (currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
int
main(int argc, char* args[])
{
init_state(&s);
if (!init()) {
vk_log(VK_INFO, "Failed to initialize!\n");
}
else {
init_vulkan();
bool quit = false;
/* VMA POC */
VmaVulkanFunctions vulkanFunctions = {0};
vulkanFunctions.vkGetInstanceProcAddr = &vkGetInstanceProcAddr;
vulkanFunctions.vkGetDeviceProcAddr = &vkGetDeviceProcAddr;
VmaAllocatorCreateInfo allocatorCreateInfo = {0};
allocatorCreateInfo.flags = VMA_ALLOCATOR_CREATE_EXT_MEMORY_BUDGET_BIT;
allocatorCreateInfo.vulkanApiVersion = VK_API_VERSION_1_2;
allocatorCreateInfo.physicalDevice = s.vk_physical_device;
allocatorCreateInfo.device = s.vk_device;
allocatorCreateInfo.instance = s.vk_instance;
allocatorCreateInfo.pVulkanFunctions = &vulkanFunctions;
VmaAllocator allocator;
vmaCreateAllocator(&allocatorCreateInfo, &allocator);
// Entire program...
// At the end, don't forget to:
vmaDestroyAllocator(allocator);
// Game loop
update_camera(0, 0);
while (!quit) {
handle_input(&quit);
draw_frame();
//SDL_Delay(16);
}
close_vulkan();
}
// Free resources and close SDL
closeSDL();
return 0;
}