Files
vulkan/main.c
2024-07-10 04:54:48 +02:00

1548 lines
52 KiB
C

#include <stdio.h>
#include <stdlib.h>
#define VK_USE_PLATFORM_WAYLAND_KHR
#define GLFW_INCLUDE_VULKAN
#define GLFW_EXPOSE_NATIVE_WAYLAND
#include <GLFW/glfw3.h>
#include <GLFW/glfw3native.h>
#include <string.h>
#include <limits.h>
#include <time.h>
#include <math.h>
typedef enum bool {
false = 0,
true = 1,
} bool;
static const uint32_t WIDTH = 800;
static const uint32_t HEIGHT = 600;
#define MAX_FRAMES_IN_FLIGHT 2
static const char* VALIDATION_LAYERS[] = { "VK_LAYER_KHRONOS_validation" };
#define VALIDATION_LAYER_COUNT 1
static const char* DEVICE_EXTENSIONS[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
#define DEVICE_EXTENSION_COUNT 1
#ifdef NDEBUG
static const bool enableValidationLayers = false;
#else
static const bool enableValidationLayers = true;
#endif
typedef struct Vec2 {
float x;
float y;
} Vec2;
typedef struct Vec3 {
float x;
float y;
float z;
} Vec3;
typedef struct Vec4 {
float x;
float y;
float z;
float w;
} Vec4;
typedef struct Mat4 {
Vec4 x;
Vec4 y;
Vec4 z;
Vec4 w;
} Mat4;
struct UniformBufferObject {
Mat4 model;
Mat4 view;
Mat4 proj;
} UniformBufferObject;
typedef struct Vertex {
Vec2 pos;
Vec3 color;
} Vertex;
#define VERTEX_COUNT 4
static const Vertex vertices[VERTEX_COUNT] = {
{{-0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}},
{{0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}},
{{0.5f, 0.5f}, {0.0f, 0.0f, 1.0f}},
{{-0.5f, 0.5f}, {1.0f, 1.0f, 1.0f}}
};
#define INDEX_COUNT 6
static const uint16_t indices[INDEX_COUNT] = {
0, 1, 2, 2, 3, 0
};
struct VulkanData {
VkInstance instance;
VkDebugUtilsMessengerEXT debugMessenger;
VkPhysicalDevice physicalDevice;
VkDevice device;
VkQueue graphicsQueue;
VkQueue presentQueue;
VkQueue transferQueue;
VkSurfaceKHR surface;
VkSwapchainKHR swapChain;
VkImage swapChainImages[4];
VkFormat swapChainImageFormat;
VkExtent2D swapChainExtent;
VkImageView swapChainImageViews[4];
int imageCount;
VkDescriptorSetLayout descriptorSetLayout;
VkPipelineLayout pipelineLayout;
VkRenderPass renderPass;
VkPipeline graphicsPipeline;
VkFramebuffer swapChainFramebuffers[4];
VkCommandPool commandPool;
VkCommandPool transferCommandPool;
VkCommandBuffer commandBuffers[MAX_FRAMES_IN_FLIGHT];
VkSemaphore imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT];
VkSemaphore renderFinishedSemaphores[MAX_FRAMES_IN_FLIGHT];
VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT];
uint32_t currentFrame;
bool framebufferResized;
VkBuffer vertexBuffer;
VkDeviceMemory vertexBufferMemory;
VkBuffer indexBuffer;
VkDeviceMemory indexBufferMemory;
VkBuffer uniformBuffers[MAX_FRAMES_IN_FLIGHT];
VkDeviceMemory uniformBuffersMemory[MAX_FRAMES_IN_FLIGHT];
void* uniformBuffersMapped[MAX_FRAMES_IN_FLIGHT];
VkDescriptorPool descriptorPool;
VkDescriptorSet descriptorSets[MAX_FRAMES_IN_FLIGHT];
};
struct SwapChainSupportDetails {
VkSurfaceCapabilitiesKHR capabilities;
VkPresentModeKHR presentModes[5];
VkSurfaceFormatKHR formats[8];
};
struct Optional {
uint32_t v;
uint32_t is_some;
};
struct QueueFamilyIndices {
struct Optional graphicsFamily;
struct Optional presentFamily;
struct Optional transferFamily;
};
static Vec3 normalize(Vec3 v) {
float size = sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
Vec3 result = {
v.x / size,
v.y / size,
v.z / size,
};
return result;
}
static Vec3 crossProduct(Vec3 a, Vec3 b) {
Vec3 result = {
a.y * b.z - a.z * b.y,
a.z * b.x - a.x * b.z,
a.x * b.y - a.y * b.x,
};
return result;
}
static float dotProduct(Vec3 a, Vec3 b) {
return a.x * b.x + a.y * b.y + a.z * b.z;
}
static Mat4 rotate(float angle, Vec3 v) {
float c = cos(angle);
float s = sin(angle);
float x = v.x, y = v.y, z = v.z;
Mat4 result = {
{c + x * x * (1. - c), x * y * (1. - c) - z * s, x * z * (1. - c) + y * s, 0.},
{x * y * (1. - c) + z * s, c + y * y * (1. - c), y * z * (1. - c) - x * s, 0.},
{x * z * (1. - c) - y * s, y * z * (1. - c) + x * s, c + z * z * (1. - c), 0.},
{0., 0., 0., 1.}
};
return result;
}
static Mat4 lookAt(Vec3 eye, Vec3 center, Vec3 up) {
Vec3 f = {
center.x - eye.x,
center.y - eye.y,
center.z - eye.z,
};
f = normalize(f);
up = normalize(up);
Vec3 s = normalize(crossProduct(f, up));
Vec3 u = crossProduct(s, f);
Mat4 mat = {
{s.x, u.x, -f.x, 0.},
{s.y, u.y, -f.y, 0.},
{s.z, u.z, -f.z, 0.},
{-dotProduct(s, eye), -dotProduct(u, eye), dotProduct(f, eye), 1.},
};
return mat;
}
static Mat4 perspective(float angle, float aspectRatio, float near, float far) {
float f = cos(angle / 2.) / sin(angle / 2.);
Mat4 mat = {
{f / aspectRatio, 0., 0., 0.},
{0., f, 0., 0.},
{0., 0., (far + near)/(near - far), -1.},
{0., 0., (2. * far * near)/(near - far), 0.},
};
return mat;
}
static VkVertexInputBindingDescription getBindingDescription() {
VkVertexInputBindingDescription bindingDescription = {
.binding = 0,
.stride = sizeof(Vertex),
.inputRate = VK_VERTEX_INPUT_RATE_VERTEX,
};
return bindingDescription;
}
static void framebufferResizeCallback(GLFWwindow* window, int width, int height) {
printf("INFO: resizing from width: %d, height: %d\n", width, height);
struct VulkanData* app = (struct VulkanData*) glfwGetWindowUserPointer(window);
app->framebufferResized = true;
}
GLFWwindow* initWindow(struct VulkanData* data) {
if (!glfwInit()) {
fprintf(stderr, "ERROR: Failed to initialize window\n");
exit(1);
}
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", NULL, NULL);
if (!window) {
glfwTerminate();
fprintf(stderr, "ERROR: Failed to create window\n");
exit(1);
}
glfwMakeContextCurrent(window);
glfwSetWindowUserPointer(window, data);
glfwSetFramebufferSizeCallback(window, framebufferResizeCallback);
GLFWcursor* cursor = glfwCreateStandardCursor(GLFW_ARROW_CURSOR);
glfwSetCursor(window, cursor);
return window;
}
static bool checkValidationLayerSupport() {
uint32_t layerCount;
vkEnumerateInstanceLayerProperties(&layerCount, NULL);
VkLayerProperties availableLayers[layerCount];
vkEnumerateInstanceLayerProperties(&layerCount, availableLayers);
for (int i = 0; i < VALIDATION_LAYER_COUNT; i++) {
const char* layerName = VALIDATION_LAYERS[i];
uint32_t layerFound = 0;
for (uint32_t j = 0; j < layerCount; j++) {
VkLayerProperties layerProperties = availableLayers[j];
if (strcmp(layerName, layerProperties.layerName) == 0) {
layerFound = 1;
break;
}
}
if (!layerFound) {
return false;
}
}
return true;
}
static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(
VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity,
VkDebugUtilsMessageTypeFlagsEXT messageType,
const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData,
void* pUserData) {
if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT && messageType >= VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT) {
printf("validation layer: %s, with data: %p\n", pCallbackData->pMessage, pUserData);
}
return VK_FALSE;
}
static void createInstance(struct VulkanData* data) {
if (enableValidationLayers && !checkValidationLayerSupport()) {
printf("ERROR: Validation layers requested, but not available\n");
exit(1);
}
VkApplicationInfo appInfo = {
.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO,
.pApplicationName = "Hello Triangle",
.applicationVersion = VK_MAKE_VERSION(1, 0, 0),
.pEngineName = "No Engine",
.engineVersion = VK_MAKE_VERSION(1, 0, 0),
.apiVersion = VK_API_VERSION_1_3,
};
uint32_t count = 0;
vkEnumerateInstanceExtensionProperties(NULL, &count, NULL);
VkExtensionProperties availableExtensions[count];
vkEnumerateInstanceExtensionProperties(NULL, &count, availableExtensions);
printf("available extensions:\n");
for (uint32_t i = 0; i < count; i++) {
VkExtensionProperties extension = availableExtensions[i];
printf("\t%s\n", extension.extensionName);
}
uint32_t glfwExtensionCount = 0;
const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);
const char* extensions[glfwExtensionCount + 1];
for (uint32_t i = 0; i < glfwExtensionCount; i++) {
extensions[i] = glfwExtensions[i];
}
if (enableValidationLayers) {
extensions[glfwExtensionCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME;
}
uint32_t extensionCount = sizeof(extensions) / sizeof(extensions[0]);
printf("required extensions:\n");
for (uint32_t i = 0; i < extensionCount; i++) {
printf("\t%s\n", extensions[i]);
}
VkInstanceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO,
.pApplicationInfo = &appInfo,
.enabledExtensionCount = extensionCount,
.ppEnabledExtensionNames = extensions,
.enabledLayerCount = 0,
};
if (enableValidationLayers) {
createInfo.enabledLayerCount = VALIDATION_LAYER_COUNT;
createInfo.ppEnabledLayerNames = VALIDATION_LAYERS;
VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.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,
.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,
.pfnUserCallback = debugCallback,
};
createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
} else {
createInfo.enabledLayerCount = 0;
createInfo.pNext = NULL;
}
VkInstance instance;
if (vkCreateInstance(&createInfo, NULL, &instance) != VK_SUCCESS) {
printf("ERROR: Failed to create instance\n");
exit(1);
}
data->instance = instance;
}
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
if (func == NULL) {
return VK_ERROR_EXTENSION_NOT_PRESENT;
}
return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
}
static void setupDebugMessenger(struct VulkanData* data) {
if (!enableValidationLayers) return;
VkDebugUtilsMessengerCreateInfoEXT createInfo = {
.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,
.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,
.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,
.pfnUserCallback = debugCallback,
};
if (CreateDebugUtilsMessengerEXT(data->instance, &createInfo, NULL, &data->debugMessenger) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to set up debug messenger\n");
exit(1);
}
}
static void createSurface(struct VulkanData* data, GLFWwindow* window) {
if (glfwCreateWindowSurface(data->instance, window, NULL, &data->surface) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create window surface\n");
exit(1);
}
}
static void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
if (func != NULL) {
func(instance, debugMessenger, pAllocator);
}
}
static uint32_t clamp(uint32_t value, uint32_t min, uint32_t max) {
if (value > max) return max;
if (value < min) return min;
return value;
}
static int checkDeviceExtensionSupport(VkPhysicalDevice device) {
uint32_t extensionCount;
vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, NULL);
VkExtensionProperties availableExtensions[extensionCount];
vkEnumerateDeviceExtensionProperties(device, NULL, &extensionCount, availableExtensions);
int requiredExtensionsFound = 0;
for (uint32_t i = 0; i < extensionCount; i++) {
VkExtensionProperties extension = availableExtensions[i];
for (int j = 0; j < DEVICE_EXTENSION_COUNT; j++) {
if (strcmp(extension.extensionName, DEVICE_EXTENSIONS[j]) == 0) {
requiredExtensionsFound++;
break;
}
}
}
return requiredExtensionsFound == DEVICE_EXTENSION_COUNT;
}
static struct SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device, VkSurfaceKHR surface) {
struct SwapChainSupportDetails details;
vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, NULL);
if (formatCount != 0) {
vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats);
}
uint32_t presentModeCount;
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, NULL);
if (presentModeCount != 0) {
vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes);
}
return details;
}
static struct QueueFamilyIndices findQueueFamilies(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) {
struct QueueFamilyIndices familyIndices = {
.graphicsFamily.v = 0,
.graphicsFamily.is_some = 0,
.presentFamily.v = 0,
.presentFamily.is_some = 0,
.transferFamily.v = 0,
.transferFamily.is_some = 0,
};
uint32_t queueFamilyCount = 0;
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, NULL);
VkQueueFamilyProperties queueFamilies[queueFamilyCount];
vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies);
for (uint32_t i = 0; i < queueFamilyCount; i++) {
VkQueueFamilyProperties queueFamily = queueFamilies[i];
if (queueFamily.queueFlags & VK_QUEUE_TRANSFER_BIT
&& !(queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT)) {
familyIndices.transferFamily.v = i;
familyIndices.transferFamily.is_some = true;
}
if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) {
familyIndices.graphicsFamily.v = i;
familyIndices.graphicsFamily.is_some = true;
}
VkBool32 presentSupport = 0;
vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &presentSupport);
if (presentSupport) {
familyIndices.presentFamily.v = i;
familyIndices.presentFamily.is_some = true;
}
if (familyIndices.presentFamily.is_some && familyIndices.graphicsFamily.is_some && familyIndices.transferFamily.is_some) {
break;
}
}
return familyIndices;
}
static bool isDeviceSuitable(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) { // One could pick the device based on a score.
struct QueueFamilyIndices familyIndices = findQueueFamilies(physicalDevice, surface);
VkPhysicalDeviceProperties deviceProperties;
vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
printf("GPU: %s\n", deviceProperties.deviceName);
VkPhysicalDeviceFeatures deviceFeatures;
int featureCount = sizeof(deviceFeatures) / 4;
VkBool32* p = &deviceFeatures.robustBufferAccess;
for (int i = 0; i < featureCount; i++) {
*p = VK_FALSE;
p++;
}
vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures);
int extensionsSupported = checkDeviceExtensionSupport(physicalDevice);
int swapChainAdequate = 0;
if (extensionsSupported) {
struct SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice, surface);
swapChainAdequate = swapChainSupport.formats[0].format != 0;
}
return
deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU &&
deviceFeatures.geometryShader &&
swapChainAdequate &&
familyIndices.graphicsFamily.is_some && familyIndices.presentFamily.is_some && familyIndices.transferFamily.is_some;
}
static void pickPhysicalDevice(struct VulkanData* data) {
data->physicalDevice = VK_NULL_HANDLE;
uint32_t deviceCount = 0;
vkEnumeratePhysicalDevices(data->instance, &deviceCount, NULL);
if (deviceCount == 0) {
printf("ERROR: Failed to find GPUs with Vulkan support\n");
exit(1);
}
VkPhysicalDevice devices[deviceCount];
vkEnumeratePhysicalDevices(data->instance, &deviceCount, devices);
for (uint32_t i = 0; i < deviceCount; i++) {
VkPhysicalDevice device = devices[i];
if (isDeviceSuitable(device, data->surface)) {
data->physicalDevice = device;
break;
}
}
if (data->physicalDevice == VK_NULL_HANDLE) {
printf("ERROR: Failed to find a suitable GPU\n");
exit(1);
}
}
static void createLogicalDevice(struct VulkanData* data) {
struct QueueFamilyIndices familyIndices = findQueueFamilies(data->physicalDevice, data->surface);
float queuePriority = 1.0f;
int queueCount = 3;
VkDeviceQueueCreateInfo queueCreateInfos[queueCount];
uint32_t uniqueQueueFamilies[] = {familyIndices.graphicsFamily.v, familyIndices.transferFamily.v, familyIndices.presentFamily.v};
for (int i = 0; i < queueCount; i++) {
uint32_t queueFamily = uniqueQueueFamilies[i];
VkDeviceQueueCreateInfo queueCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queueFamily,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
queueCreateInfos[i] = queueCreateInfo;
}
if (familyIndices.graphicsFamily.v == familyIndices.presentFamily.v) {
queueCount--;
}
for (int i = 0; i < queueCount; i++) {
uint32_t queueFamily = uniqueQueueFamilies[i];
VkDeviceQueueCreateInfo queueCreateInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO,
.queueFamilyIndex = queueFamily,
.queueCount = 1,
.pQueuePriorities = &queuePriority,
};
queueCreateInfos[i] = queueCreateInfo;
}
VkPhysicalDeviceFeatures deviceFeatures;
int featureCount = sizeof(deviceFeatures) / 4;
VkBool32* p = &deviceFeatures.robustBufferAccess;
for (int i = 0; i < featureCount; i++) {
*p = VK_FALSE;
p++;
}
VkDeviceCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO,
.pEnabledFeatures = &deviceFeatures,
.enabledExtensionCount = DEVICE_EXTENSION_COUNT,
.queueCreateInfoCount = queueCount,
.pQueueCreateInfos = queueCreateInfos,
.ppEnabledExtensionNames = DEVICE_EXTENSIONS,
};
if (enableValidationLayers) {
createInfo.enabledLayerCount = VALIDATION_LAYER_COUNT;
createInfo.ppEnabledLayerNames = VALIDATION_LAYERS;
} else {
createInfo.enabledLayerCount = 0;
}
if (vkCreateDevice(data->physicalDevice, &createInfo, NULL, &data->device) != VK_SUCCESS) {
printf("ERROR: Failed to create logical device\n");
exit(1);
}
if (!familyIndices.transferFamily.is_some) {
printf("no graphics\n");
}
if (!familyIndices.presentFamily.is_some) {
printf("no present\n");
}
vkGetDeviceQueue(data->device, familyIndices.graphicsFamily.v, 0, &data->graphicsQueue);
vkGetDeviceQueue(data->device, familyIndices.presentFamily.v, 0, &data->presentQueue);
vkGetDeviceQueue(data->device, familyIndices.transferFamily.v, 0, &data->transferQueue);
}
static VkExtent2D chooseSwapExtent(VkSurfaceCapabilitiesKHR* capabilities, GLFWwindow* window) {
if (capabilities->currentExtent.width != UINT_MAX) {
return capabilities->currentExtent;
} else {
int width, height;
glfwGetFramebufferSize(window, &width, &height);
VkExtent2D actualExtent = {
(uint32_t)(width),
(uint32_t)(height)
};
actualExtent.width = clamp(actualExtent.width, capabilities->minImageExtent.width, capabilities->maxImageExtent.width);
actualExtent.height = clamp(actualExtent.height, capabilities->minImageExtent.height, capabilities->maxImageExtent.height);
return actualExtent;
}
}
static VkSurfaceFormatKHR chooseSwapSurfaceFormat(VkSurfaceFormatKHR availableFormats[]) {
for(int i = 0; 1; i++) {
VkSurfaceFormatKHR availableFormat = availableFormats[i];
if (availableFormat.format == VK_FORMAT_UNDEFINED) {
break;
}
if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
return availableFormat;
}
}
return availableFormats[0];
}
static VkPresentModeKHR chooseSwapPresentMode(VkPresentModeKHR availablePresentModes[]) {
for(int i = 0; 1; i++) {
if (availablePresentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
return availablePresentModes[i];
}
}
return VK_PRESENT_MODE_FIFO_KHR;
}
static void createSwapChain(struct VulkanData* data, GLFWwindow* window) {
struct SwapChainSupportDetails swapChainSupport = querySwapChainSupport(data->physicalDevice, data->surface);
VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
VkExtent2D extent = chooseSwapExtent(&swapChainSupport.capabilities, window);
uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;
data->imageCount = imageCount;
if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
imageCount = swapChainSupport.capabilities.maxImageCount;
}
struct QueueFamilyIndices familyIndices = findQueueFamilies(data->physicalDevice, data->surface);
uint32_t queueFamilyIndices[] = {familyIndices.graphicsFamily.v, familyIndices.presentFamily.v};
uint32_t otherFamilyIndices[] = {familyIndices.transferFamily.v, familyIndices.graphicsFamily.v};
VkSwapchainCreateInfoKHR createInfo = {
.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR,
.surface = data->surface,
.minImageCount = imageCount,
.imageFormat = surfaceFormat.format,
.imageColorSpace = surfaceFormat.colorSpace,
.imageExtent = extent,
.imageArrayLayers = 1,
.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT,
.queueFamilyIndexCount = 2,
.imageSharingMode = VK_SHARING_MODE_CONCURRENT,
.pQueueFamilyIndices = otherFamilyIndices,
.preTransform = swapChainSupport.capabilities.currentTransform,
.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR,
.presentMode = presentMode,
.clipped = VK_TRUE,
.oldSwapchain = VK_NULL_HANDLE,
};
if (familyIndices.graphicsFamily.v != familyIndices.presentFamily.v) {
createInfo.pQueueFamilyIndices = queueFamilyIndices;
}
if (vkCreateSwapchainKHR(data->device, &createInfo, NULL, &data->swapChain) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create swap chain");
}
data->swapChainImageFormat = surfaceFormat.format;
data->swapChainExtent = extent;
vkGetSwapchainImagesKHR(data->device, data->swapChain, &imageCount, NULL);
vkGetSwapchainImagesKHR(data->device, data->swapChain, &imageCount, data->swapChainImages);
}
static void createImageViews(struct VulkanData* data) {
for (int i = 0; i < data->imageCount; i++) {
VkImageViewCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO,
.image = data->swapChainImages[i],
.viewType = VK_IMAGE_VIEW_TYPE_2D,
.format = data->swapChainImageFormat,
.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(data->device, &createInfo, NULL, &data->swapChainImageViews[i]) != VK_SUCCESS) {
fprintf(stderr, "failed to create image views!");
}
}
}
static void readFile(const char* fileName, char buffer[], int len) {
FILE* file = fopen(fileName, "r");
if (!file) {
printf("ERROR: File \"%s\" can't be opened \n", fileName);
exit(1);
}
fread(buffer, sizeof(buffer[0]), len, file);
fclose(file);
}
static uint32_t fileSize(const char* fileName) {
FILE* file = fopen(fileName, "r");
if (!file) {
printf("ERROR: File \"%s\" can't be opened \n", fileName);
exit(1);
}
fseek(file, 0L, SEEK_END);
uint32_t size = ftell(file);
printf("INFO: File \"%s\" has size %d\n", fileName, size);
rewind(file);
return size;
}
static VkShaderModule createShaderModule(const char* code, uint32_t size, VkDevice device) {
VkShaderModuleCreateInfo createInfo = {
.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO,
.codeSize = size,
.pCode = (uint32_t*)code,
};
VkShaderModule shaderModule;
if (vkCreateShaderModule(device, &createInfo, NULL, &shaderModule) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create shader module\n");
}
return shaderModule;
}
static void createGraphicsPipeline(struct VulkanData* data) {
const char* vertFileName = "shaders/vert.spv";
uint32_t vertLen = fileSize(vertFileName);
char vertShaderCode[vertLen];
readFile(vertFileName, vertShaderCode, vertLen);
const char* fragFileName = "shaders/frag.spv";
uint32_t fragLen = fileSize(fragFileName);
char fragShaderCode[fragLen];
readFile(fragFileName, fragShaderCode, fragLen);
VkShaderModule vertShaderModule = createShaderModule(vertShaderCode, vertLen, data->device);
VkShaderModule fragShaderModule = createShaderModule(fragShaderCode, fragLen, data->device);
VkPipelineShaderStageCreateInfo vertShaderStageInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_VERTEX_BIT,
.module = vertShaderModule,
.pName = "main",
};
VkPipelineShaderStageCreateInfo fragShaderStageInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO,
.stage = VK_SHADER_STAGE_FRAGMENT_BIT,
.module = fragShaderModule,
.pName = "main",
.pSpecializationInfo = NULL,
};
VkPipelineShaderStageCreateInfo shaderStages[2] = {vertShaderStageInfo, fragShaderStageInfo};
uint32_t dynamicStateCount = 2;
VkDynamicState dynamicStates[] = {
VK_DYNAMIC_STATE_VIEWPORT,
VK_DYNAMIC_STATE_SCISSOR
};
VkPipelineDynamicStateCreateInfo dynamicState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO,
.dynamicStateCount = dynamicStateCount,
.pDynamicStates = dynamicStates,
};
VkVertexInputBindingDescription bindingDescription = getBindingDescription();
VkVertexInputAttributeDescription attributeDescriptions[2] = {
{
.binding = 0,
.location = 0,
.format = VK_FORMAT_R32G32_SFLOAT,
.offset = offsetof(Vertex, pos),
},
{
.binding = 0,
.location = 1,
.format = VK_FORMAT_R32G32B32_SFLOAT,
.offset = offsetof(Vertex, color),
}
};
VkPipelineVertexInputStateCreateInfo vertexInputInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO,
.vertexBindingDescriptionCount = 1,
.vertexAttributeDescriptionCount = 2,
.pVertexBindingDescriptions = &bindingDescription,
.pVertexAttributeDescriptions = attributeDescriptions,
};
VkPipelineInputAssemblyStateCreateInfo inputAssembly = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO,
.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST,
.primitiveRestartEnable = VK_FALSE,
};
VkViewport viewport = {
.x = 0.0f,
.y = 0.0f,
.width = (float) data->swapChainExtent.width,
.height = (float) data->swapChainExtent.height,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
VkRect2D scissor = {
.offset = {0, 0},
.extent = data->swapChainExtent,
};
VkPipelineViewportStateCreateInfo viewportState = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO,
.viewportCount = 1,
.pViewports = &viewport,
.scissorCount = 1,
.pScissors = &scissor,
};
VkPipelineRasterizationStateCreateInfo rasterizer = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO,
.depthClampEnable = VK_FALSE,
.rasterizerDiscardEnable = VK_FALSE,
.polygonMode = VK_POLYGON_MODE_FILL,
.lineWidth = 1.0f,
// .cullMode = VK_CULL_MODE_BACK_BIT,
.cullMode = 0,
.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE,
.depthBiasEnable = VK_FALSE,
.depthBiasConstantFactor = 0.0f, // Optional
.depthBiasClamp = 0.0f, // Optional
.depthBiasSlopeFactor = 0.0f, // Optional
};
VkPipelineMultisampleStateCreateInfo multisampling = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO,
.sampleShadingEnable = VK_FALSE,
.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT,
.minSampleShading = 1.0f, // Optional
.pSampleMask = NULL, // Optional
.alphaToCoverageEnable = VK_FALSE, // Optional
.alphaToOneEnable = VK_FALSE, // Optional
};
VkPipelineColorBlendAttachmentState colorBlendAttachment = {
.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT,
.blendEnable = VK_FALSE,
.srcColorBlendFactor = VK_BLEND_FACTOR_ONE, // Optional
.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, // Optional
.colorBlendOp = VK_BLEND_OP_ADD, // Optional
.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, // Optional
.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, // Optional
.alphaBlendOp = VK_BLEND_OP_ADD, // Optional
};
VkPipelineColorBlendStateCreateInfo colorBlending = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO,
.logicOpEnable = VK_FALSE,
.logicOp = VK_LOGIC_OP_COPY, // Optional
.attachmentCount = 1,
.pAttachments = &colorBlendAttachment,
.blendConstants[0] = 0.0f, // Optional
.blendConstants[1] = 0.0f, // Optional
.blendConstants[2] = 0.0f, // Optional
.blendConstants[3] = 0.0f, // Optional
};
VkPipelineLayoutCreateInfo pipelineLayoutInfo = {
.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO,
.setLayoutCount = 1,
.pSetLayouts = &data->descriptorSetLayout,
.pushConstantRangeCount = 0, // Optional
.pPushConstantRanges = NULL, // Optional
};
if (vkCreatePipelineLayout(data->device, &pipelineLayoutInfo, NULL, &data->pipelineLayout) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create pipeline layout\n");
exit(1);
}
VkGraphicsPipelineCreateInfo pipelineInfo = {
.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO,
.stageCount = 2,
.pStages = shaderStages,
.pVertexInputState = &vertexInputInfo,
.pInputAssemblyState = &inputAssembly,
.pViewportState = &viewportState,
.pRasterizationState = &rasterizer,
.pMultisampleState = &multisampling,
.pDepthStencilState = NULL, // Optional
.pColorBlendState = &colorBlending,
.pDynamicState = &dynamicState,
.layout = data->pipelineLayout,
.renderPass = data->renderPass,
.subpass = 0,
.basePipelineHandle = VK_NULL_HANDLE, // Optional
.basePipelineIndex = -1, // Optional
};
if (vkCreateGraphicsPipelines(data->device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &data->graphicsPipeline) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create graphics pipeline\n");
exit(1);
}
vkDestroyShaderModule(data->device, fragShaderModule, NULL);
vkDestroyShaderModule(data->device, vertShaderModule, NULL);
}
static void createRenderPass(struct VulkanData* data) {
VkAttachmentDescription colorAttachment = {
.format = data->swapChainImageFormat,
.samples = VK_SAMPLE_COUNT_1_BIT,
.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR,
.storeOp = VK_ATTACHMENT_STORE_OP_STORE,
.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE,
.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE,
.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED,
.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR,
};
VkAttachmentReference colorAttachmentRef = {
.attachment = 0,
.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL,
};
VkSubpassDescription subpass = {
.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS,
.colorAttachmentCount = 1,
.pColorAttachments = &colorAttachmentRef,
};
VkSubpassDependency dependency = {
.srcSubpass = VK_SUBPASS_EXTERNAL,
.dstSubpass = 0,
.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.srcAccessMask = 0,
.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT,
.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT,
};
VkRenderPassCreateInfo renderPassInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO,
.attachmentCount = 1,
.pAttachments = &colorAttachment,
.subpassCount = 1,
.pSubpasses = &subpass,
.dependencyCount = 1,
.pDependencies = &dependency,
};
if (vkCreateRenderPass(data->device, &renderPassInfo, NULL, &data->renderPass) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create render pass!\n");
exit(1);
}
}
static void createFramebuffers(struct VulkanData* data) {
for (int i = 0; i < data->imageCount; i++) {
VkImageView attachments[] = {
data->swapChainImageViews[i]
};
VkFramebufferCreateInfo framebufferInfo = {
.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO,
.renderPass = data->renderPass,
.attachmentCount = 1,
.pAttachments = attachments,
.width = data->swapChainExtent.width,
.height = data->swapChainExtent.height,
.layers = 1,
};
if (vkCreateFramebuffer(data->device, &framebufferInfo, NULL, &data->swapChainFramebuffers[i]) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create framebuffer\n");
exit(1);
}
}
}
static void createCommandPool(struct VulkanData* data) {
struct QueueFamilyIndices queueFamilyIndices = findQueueFamilies(data->physicalDevice, data->surface);
VkCommandPoolCreateInfo poolInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT,
.queueFamilyIndex = queueFamilyIndices.graphicsFamily.v,
};
VkCommandPoolCreateInfo transferPoolInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO,
.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT | VK_COMMAND_POOL_CREATE_TRANSIENT_BIT,
.queueFamilyIndex = queueFamilyIndices.transferFamily.v,
};
if (vkCreateCommandPool(data->device, &poolInfo, NULL, &data->commandPool) != VK_SUCCESS) {
fprintf(stderr, "ERROR: failed to create command pool\n");
exit(1);
}
if (vkCreateCommandPool(data->device, &transferPoolInfo, NULL, &data->transferCommandPool) != VK_SUCCESS) {
fprintf(stderr, "ERROR: failed to create transfer command pool\n");
exit(1);
}
}
static void createCommandBuffers(struct VulkanData* data) {
VkCommandBufferAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.commandPool = data->commandPool,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandBufferCount = MAX_FRAMES_IN_FLIGHT,
};
if (vkAllocateCommandBuffers(data->device, &allocInfo, &data->commandBuffers[data->currentFrame]) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to allocate command buffers\n");
exit(1);
}
}
static void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, struct VulkanData* data) {
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = 0,
.pInheritanceInfo = NULL,
};
if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to begin recording command buffer\n");
exit(1);
}
VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
VkRenderPassBeginInfo renderPassInfo = {
.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO,
.renderPass = data->renderPass,
.framebuffer = data->swapChainFramebuffers[imageIndex],
.renderArea.offset = {0, 0},
.renderArea.extent = data->swapChainExtent,
.clearValueCount = 1,
.pClearValues = &clearColor,
};
vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);
vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, data->graphicsPipeline);
VkBuffer vertexBuffers[] = {data->vertexBuffer};
VkDeviceSize offsets[] = {0};
vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets);
vkCmdBindIndexBuffer(commandBuffer, data->indexBuffer, 0, VK_INDEX_TYPE_UINT16);
VkViewport viewport = {
.x = 0.0f,
.y = 0.0f,
.width = (float) data->swapChainExtent.width,
.height = (float) data->swapChainExtent.height,
.minDepth = 0.0f,
.maxDepth = 1.0f,
};
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
VkRect2D scissor = {
.offset = {0, 0},
.extent = data->swapChainExtent,
};
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
vkCmdBindDescriptorSets(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, data->pipelineLayout, 0, 1, &data->descriptorSets[data->currentFrame], 0, NULL);
vkCmdDrawIndexed(commandBuffer, INDEX_COUNT, 1, 0, 0, 0);
vkCmdEndRenderPass(commandBuffer);
if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to record command buffer\n");
exit(1);
}
}
static void cleanupSwapChain(struct VulkanData* data) {
for (int i = 0; i < data->imageCount; i++) {
vkDestroyFramebuffer(data->device, data->swapChainFramebuffers[i], NULL);
}
for (int i = 0; i < data->imageCount; i++) {
vkDestroyImageView(data->device, data->swapChainImageViews[i], NULL);
}
vkDestroySwapchainKHR(data->device, data->swapChain, NULL);
}
static void recreateSwapChain(struct VulkanData* data, GLFWwindow* window) {
int width = 0, height = 0;
glfwGetFramebufferSize(window, &width, &height);
while (width == 0 || height == 0) {
glfwGetFramebufferSize(window, &width, &height);
glfwWaitEvents();
}
vkDeviceWaitIdle(data->device);
cleanupSwapChain(data);
createSwapChain(data, window);
createImageViews(data);
createFramebuffers(data);
}
static void updateUniformBuffer(uint32_t currentImage, struct VulkanData* data, struct timespec* start) {
struct timespec end;
if (clock_gettime(CLOCK_MONOTONIC, &end) != 0) {
perror("clock_gettime");
exit(1);
}
double elapsed = (end.tv_sec - start->tv_sec) + (end.tv_nsec - start->tv_nsec) / 1e9;
Vec3 v1 = { 2., 2., 2. };
Vec3 v2 = { 0., 0., 0. };
Vec3 v3 = { 0., 0., 1. };
struct UniformBufferObject ubo = {
.model = rotate(elapsed * M_PI / 2., v3),
.view = lookAt(v1, v2, v3),
.proj = perspective(M_PI / 4., data->swapChainExtent.width / (float) data->swapChainExtent.height, 0.1, 10.),
};
ubo.proj.y.y *= -1.;
memcpy(data->uniformBuffersMapped[currentImage], &ubo, sizeof(ubo));
}
static void drawFrame(struct VulkanData* data, GLFWwindow* window, struct timespec* start) {
vkWaitForFences(data->device, 1, &data->inFlightFences[data->currentFrame], VK_TRUE, UINT64_MAX);
uint32_t imageIndex;
VkResult result = vkAcquireNextImageKHR(data->device, data->swapChain, UINT64_MAX, data->imageAvailableSemaphores[data->currentFrame], VK_NULL_HANDLE, &imageIndex);
if (result == VK_ERROR_OUT_OF_DATE_KHR) {
recreateSwapChain(data, window);
return;
} else if (result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) {
fprintf(stderr, "ERROR: Failed to acquire swap chain image");
exit(1);
}
vkResetFences(data->device, 1, &data->inFlightFences[data->currentFrame]);
vkResetCommandBuffer(data->commandBuffers[data->currentFrame], 0);
recordCommandBuffer(data->commandBuffers[data->currentFrame], imageIndex, data);
VkSemaphore waitSemaphores[] = {data->imageAvailableSemaphores[data->currentFrame]};
VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
VkSemaphore signalSemaphores[] = {data->renderFinishedSemaphores[data->currentFrame]};
updateUniformBuffer(data->currentFrame, data, start);
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.waitSemaphoreCount = 1,
.pWaitSemaphores = waitSemaphores,
.pWaitDstStageMask = waitStages,
.commandBufferCount = 1,
.pCommandBuffers = &data->commandBuffers[data->currentFrame],
.signalSemaphoreCount = 1,
.pSignalSemaphores = signalSemaphores,
};
if (vkQueueSubmit(data->graphicsQueue, 1, &submitInfo, data->inFlightFences[data->currentFrame]) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to submit draw command buffer\n");
exit(1);
}
VkSwapchainKHR swapChains[] = {data->swapChain};
VkPresentInfoKHR presentInfo = {
.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,
.waitSemaphoreCount = 1,
.pWaitSemaphores = signalSemaphores,
.swapchainCount = 1,
.pSwapchains = swapChains,
.pImageIndices = &imageIndex,
.pResults = NULL,
};
result = vkQueuePresentKHR(data->presentQueue, &presentInfo);
if (result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || data->framebufferResized) {
data->framebufferResized = false;
recreateSwapChain(data, window);
} else if (result != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to present swap chain image");
exit(1);
}
data->currentFrame = (data->currentFrame + 1) % MAX_FRAMES_IN_FLIGHT;
}
static void createSyncObjects(struct VulkanData* data) {
VkSemaphoreCreateInfo semaphoreInfo = {
.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO,
};
VkFenceCreateInfo fenceInfo = {
.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO,
.flags = VK_FENCE_CREATE_SIGNALED_BIT,
};
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
if (vkCreateSemaphore(data->device, &semaphoreInfo, NULL, &data->imageAvailableSemaphores[i]) != VK_SUCCESS ||
vkCreateSemaphore(data->device, &semaphoreInfo, NULL, &data->renderFinishedSemaphores[i]) != VK_SUCCESS ||
vkCreateFence(data->device, &fenceInfo, NULL, &data->inFlightFences[i]) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create semaphores\n");
exit(1);
}
}
}
static uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties, struct VulkanData* data) {
VkPhysicalDeviceMemoryProperties memProperties;
vkGetPhysicalDeviceMemoryProperties(data->physicalDevice, &memProperties);
for (uint32_t i = 0; i < memProperties.memoryTypeCount; i++) {
if (typeFilter & (1 << i) && (memProperties.memoryTypes[i].propertyFlags & properties)) {
return i;
}
}
fprintf(stderr, "ERROR: Failed to find suitable memory type\n");
exit(1);
}
static void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer* buffer, VkDeviceMemory* bufferMemory, struct VulkanData* data) {
struct QueueFamilyIndices familyIndices = findQueueFamilies(data->physicalDevice, data->surface);
uint32_t otherFamilyIndices[] = {familyIndices.transferFamily.v, familyIndices.graphicsFamily.v};
VkBufferCreateInfo bufferInfo = {
.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO,
.size = size,
.usage = usage,
.sharingMode = VK_SHARING_MODE_CONCURRENT,
.queueFamilyIndexCount = 2,
.pQueueFamilyIndices = otherFamilyIndices,
};
if (vkCreateBuffer(data->device, &bufferInfo, NULL, buffer) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create buffer\n");
exit(1);
}
VkMemoryRequirements memRequirements;
vkGetBufferMemoryRequirements(data->device, *buffer, &memRequirements);
VkMemoryAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO,
.allocationSize = memRequirements.size,
.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties, data),
};
if (vkAllocateMemory(data->device, &allocInfo, NULL, bufferMemory) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to allocate buffer memory\n");
exit(1);
}
vkBindBufferMemory(data->device, *buffer, *bufferMemory, 0);
}
static void copyBuffer(VkBuffer srcBuffer, VkBuffer dstBuffer, VkDeviceSize size, struct VulkanData* data) {
VkCommandBufferAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO,
.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY,
.commandPool = data->transferCommandPool,
.commandBufferCount = 1,
};
VkCommandBuffer commandBuffer;
vkAllocateCommandBuffers(data->device, &allocInfo, &commandBuffer);
VkCommandBufferBeginInfo beginInfo = {
.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO,
.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT,
};
vkBeginCommandBuffer(commandBuffer, &beginInfo);
VkBufferCopy copyRegion = {
.srcOffset = 0,
.dstOffset = 0,
.size = size,
};
vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, &copyRegion);
vkEndCommandBuffer(commandBuffer);
VkSubmitInfo submitInfo = {
.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO,
.commandBufferCount = 1,
.pCommandBuffers = &commandBuffer,
};
vkQueueSubmit(data->transferQueue, 1, &submitInfo, VK_NULL_HANDLE);
vkQueueWaitIdle(data->transferQueue);
vkFreeCommandBuffers(data->device, data->transferCommandPool, 1, &commandBuffer);
}
static void createIndexBuffer(struct VulkanData* data) {
VkDeviceSize bufferSize = sizeof(indices[0]) * INDEX_COUNT;
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, data);
void* indexData;
vkMapMemory(data->device, stagingBufferMemory, 0, bufferSize, 0, &indexData);
memcpy(indexData, indices, bufferSize);
vkUnmapMemory(data->device, stagingBufferMemory);
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->indexBuffer, &data->indexBufferMemory, data);
copyBuffer(stagingBuffer, data->indexBuffer, bufferSize, data);
vkDestroyBuffer(data->device, stagingBuffer, NULL);
vkFreeMemory(data->device, stagingBufferMemory, NULL);
}
static void createVertexBuffer(struct VulkanData* data) {
VkDeviceSize bufferSize = sizeof(vertices[0]) * VERTEX_COUNT;
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, data);
void* vertexData;
vkMapMemory(data->device, stagingBufferMemory, 0, bufferSize, 0, &vertexData);
memcpy(vertexData, vertices, bufferSize);
vkUnmapMemory(data->device, stagingBufferMemory);
createBuffer(bufferSize, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->vertexBuffer, &data->vertexBufferMemory, data);
copyBuffer(stagingBuffer, data->vertexBuffer, bufferSize, data);
vkDestroyBuffer(data->device, stagingBuffer, NULL);
vkFreeMemory(data->device, stagingBufferMemory, NULL);
}
static void createDescriptorSetLayout(struct VulkanData* data) {
VkDescriptorSetLayoutBinding uboLayoutBinding = {
.binding = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.stageFlags = VK_SHADER_STAGE_VERTEX_BIT,
.pImmutableSamplers = NULL,
};
VkDescriptorSetLayoutCreateInfo layoutInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO,
.bindingCount = 1,
.pBindings = &uboLayoutBinding,
};
if (vkCreateDescriptorSetLayout(data->device, &layoutInfo, NULL, &data->descriptorSetLayout) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create descriptor set layout\n");
exit(1);
}
}
static void createUniformBuffers(struct VulkanData* data) {
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, &data->uniformBuffers[i], &data->uniformBuffersMemory[i], data);
vkMapMemory(data->device, data->uniformBuffersMemory[i], 0, bufferSize, 0, &data->uniformBuffersMapped[i]);
}
}
static void createDescriptorPool(struct VulkanData* data) {
VkDescriptorPoolSize poolSize = {
.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = MAX_FRAMES_IN_FLIGHT,
};
VkDescriptorPoolCreateInfo poolInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO,
.poolSizeCount = 1,
.pPoolSizes = &poolSize,
.maxSets = MAX_FRAMES_IN_FLIGHT,
.flags = 0,
};
if (vkCreateDescriptorPool(data->device, &poolInfo, NULL, &data->descriptorPool) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to create descriptor pool\n");
exit(1);
}
}
static void createDescriptorSets(struct VulkanData* data) {
VkDescriptorSetLayout layouts[MAX_FRAMES_IN_FLIGHT];
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
layouts[i] = data->descriptorSetLayout;
}
VkDescriptorSetAllocateInfo allocInfo = {
.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO,
.descriptorPool = data->descriptorPool,
.descriptorSetCount = MAX_FRAMES_IN_FLIGHT,
.pSetLayouts = layouts,
};
if (vkAllocateDescriptorSets(data->device, &allocInfo, data->descriptorSets) != VK_SUCCESS) {
fprintf(stderr, "ERROR: Failed to allocate descriptor sets\n");
exit(1);
}
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
VkDescriptorBufferInfo bufferInfo = {
.buffer = data->uniformBuffers[i],
.offset = 0,
.range = sizeof(UniformBufferObject),
};
VkWriteDescriptorSet descriptorWrite = {
.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET,
.dstSet = data->descriptorSets[i],
.dstBinding = 0,
.dstArrayElement = 0,
.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER,
.descriptorCount = 1,
.pBufferInfo = &bufferInfo,
.pImageInfo = NULL,
.pTexelBufferView = NULL,
};
vkUpdateDescriptorSets(data->device, 1, &descriptorWrite, 0, NULL);
}
}
static void initVulkan(GLFWwindow* window, struct VulkanData* data) {
data->currentFrame = 0;
data->framebufferResized = 0;
createInstance(data);
setupDebugMessenger(data);
createSurface(data, window);
pickPhysicalDevice(data);
createLogicalDevice(data);
createSwapChain(data, window);
createImageViews(data);
createRenderPass(data);
createDescriptorSetLayout(data);
createGraphicsPipeline(data);
createFramebuffers(data);
createCommandPool(data);
createVertexBuffer(data);
createIndexBuffer(data);
createUniformBuffers(data);
createDescriptorPool(data);
createDescriptorSets(data);
createCommandBuffers(data);
createSyncObjects(data);
}
static void mainLoop(GLFWwindow* window, struct VulkanData* data, struct timespec* start) {
while (!glfwWindowShouldClose(window)) {
glfwSwapBuffers(window);
glfwPollEvents();
drawFrame(data, window, start);
int state = glfwGetKey(window, GLFW_KEY_Q);
if (state == GLFW_PRESS) {
break;
}
}
vkDeviceWaitIdle(data->device);
}
static void cleanup(GLFWwindow* window, struct VulkanData* data) {
cleanupSwapChain(data);
for (size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroyBuffer(data->device, data->uniformBuffers[i], NULL);
vkFreeMemory(data->device, data->uniformBuffersMemory[i], NULL);
}
vkDestroyDescriptorPool(data->device, data->descriptorPool, NULL);
vkDestroyDescriptorSetLayout(data->device, data->descriptorSetLayout, NULL);
vkDestroyBuffer(data->device, data->vertexBuffer, NULL);
vkFreeMemory(data->device, data->vertexBufferMemory, NULL);
vkDestroyBuffer(data->device, data->indexBuffer, NULL);
vkFreeMemory(data->device, data->indexBufferMemory, NULL);
vkDestroyPipeline(data->device, data->graphicsPipeline, NULL);
vkDestroyPipelineLayout(data->device, data->pipelineLayout, NULL);
vkDestroyRenderPass(data->device, data->renderPass, NULL);
for (int i = 0; i < MAX_FRAMES_IN_FLIGHT; i++) {
vkDestroySemaphore(data->device, data->imageAvailableSemaphores[i], NULL);
vkDestroySemaphore(data->device, data->renderFinishedSemaphores[i], NULL);
vkDestroyFence(data->device, data->inFlightFences[i], NULL);
}
vkDestroyCommandPool(data->device, data->commandPool, NULL);
vkDestroyCommandPool(data->device, data->transferCommandPool, NULL);
vkDestroyDevice(data->device, NULL);
if (enableValidationLayers) {
DestroyDebugUtilsMessengerEXT(data->instance, data->debugMessenger, NULL);
}
vkDestroySurfaceKHR(data->instance, data->surface, NULL);
vkDestroyInstance(data->instance, NULL);
glfwDestroyWindow(window);
glfwTerminate();
}
static void run() {
struct VulkanData data;
printf("INFO: Size of data struct: %ld bytes\n", sizeof(data));
GLFWwindow* window = initWindow(&data);
initVulkan(window, &data);
struct timespec start;
if (clock_gettime(CLOCK_MONOTONIC, &start) != 0) {
perror("clock_gettime");
exit(1);
}
mainLoop(window, &data, &start);
cleanup(window, &data);
}
int main() {
run();
}