#include #include #define VK_USE_PLATFORM_WAYLAND_KHR #define GLFW_INCLUDE_VULKAN #define GLFW_EXPOSE_NATIVE_WAYLAND #include #include #include #include #include #include typedef enum { 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 { Vec3 pos; Vec3 color; Vec2 texCoord; } Vertex; struct PPM { uint32_t width; uint32_t height; uint32_t maxVal; void* pixels; }; 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; Vertex* vertices; uint32_t vertexCount; uint32_t* indices; uint32_t indexCount; 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]; uint32_t mipLevels; VkImage textureImage; VkDeviceMemory textureImageMemory; VkImageView textureImageView; VkSampler textureSampler; VkImage depthImage; VkDeviceMemory depthImageMemory; VkImageView depthImageView; VkSampleCountFlagBits msaaSamples; VkImage colorImage; VkDeviceMemory colorImageMemory; VkImageView colorImageView; }; struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; VkPresentModeKHR presentModes[5]; VkSurfaceFormatKHR formats[8]; }; struct Optional { uint32_t v; bool 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); } glfwSetWindowUserPointer(window, data); glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); return window; } static VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags, uint32_t mipLevels, struct VulkanData* data) { VkImageViewCreateInfo viewInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = format, .subresourceRange.aspectMask = aspectFlags, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = mipLevels, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, }; VkImageView imageView; if (vkCreateImageView(data->device, &viewInfo, NULL, &imageView) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create texture image view\n"); exit(1); } return imageView; } static VkCommandBuffer beginSingleTimeCommands(bool graphics, struct VulkanData* data) { VkCommandBufferAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; if (graphics) { allocInfo.commandPool = data->commandPool; } else { allocInfo.commandPool = data->transferCommandPool; } 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); return commandBuffer; } static void endSingleTimeCommands(VkCommandBuffer commandBuffer, bool graphics, struct VulkanData* data) { vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &commandBuffer, }; if (graphics) { vkQueueSubmit(data->graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); vkQueueWaitIdle(data->graphicsQueue); vkFreeCommandBuffers(data->device, data->commandPool, 1, &commandBuffer); } else { vkQueueSubmit(data->transferQueue, 1, &submitInfo, VK_NULL_HANDLE); vkQueueWaitIdle(data->transferQueue); vkFreeCommandBuffers(data->device, data->transferCommandPool, 1, &commandBuffer); } } 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]); } const VkBool32 setting_validate_core = VK_TRUE; const VkBool32 setting_validate_best_practices = VK_TRUE; const VkBool32 setting_validate_sync = VK_TRUE; const VkBool32 setting_thread_safety = VK_TRUE; const char* setting_debug_action[] = {"VK_DBG_LAYER_ACTION_LOG_MSG"}; const char* setting_report_flags[] = {"info", "warn", "perf", "error", "debug"}; const VkBool32 setting_enable_message_limit = VK_TRUE; const int32_t setting_duplicate_message_limit = 3; const VkLayerSettingEXT settings[] = { {VALIDATION_LAYERS[0], "validate_core", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_core}, {VALIDATION_LAYERS[0], "validate_best_practices", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_best_practices}, {VALIDATION_LAYERS[0], "validate_sync", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_validate_sync}, {VALIDATION_LAYERS[0], "thread_safety", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_thread_safety}, {VALIDATION_LAYERS[0], "debug_action", VK_LAYER_SETTING_TYPE_STRING_EXT, 1, setting_debug_action}, {VALIDATION_LAYERS[0], "report_flags", VK_LAYER_SETTING_TYPE_STRING_EXT, 5, setting_report_flags}, {VALIDATION_LAYERS[0], "enable_message_limit", VK_LAYER_SETTING_TYPE_BOOL32_EXT, 1, &setting_enable_message_limit}, {VALIDATION_LAYERS[0], "duplicate_message_limit", VK_LAYER_SETTING_TYPE_INT32_EXT, 1, &setting_duplicate_message_limit}, }; const VkLayerSettingsCreateInfoEXT layer_settings_create_info = { VK_STRUCTURE_TYPE_LAYER_SETTINGS_CREATE_INFO_EXT, NULL, 8, settings, }; VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pNext = &layer_settings_create_info, .flags = 0, .pApplicationInfo = &appInfo, .enabledExtensionCount = extensionCount, .ppEnabledExtensionNames = extensions, .enabledLayerCount = 1, }; 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 = &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) / sizeof(deviceFeatures.robustBufferAccess); 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 && deviceFeatures.samplerAnisotropy && swapChainAdequate && familyIndices.graphicsFamily.is_some && familyIndices.presentFamily.is_some && familyIndices.transferFamily.is_some; } VkSampleCountFlagBits getMaxUseableSampleCount(struct VulkanData* data) { VkPhysicalDeviceProperties physicalDeviceProperties; vkGetPhysicalDeviceProperties(data->physicalDevice, &physicalDeviceProperties); VkSampleCountFlags counts = physicalDeviceProperties.limits.framebufferColorSampleCounts & physicalDeviceProperties.limits.framebufferDepthSampleCounts; if (counts & VK_SAMPLE_COUNT_64_BIT) {return VK_SAMPLE_COUNT_64_BIT;} if (counts & VK_SAMPLE_COUNT_32_BIT) {return VK_SAMPLE_COUNT_32_BIT;} if (counts & VK_SAMPLE_COUNT_16_BIT) {return VK_SAMPLE_COUNT_16_BIT;} if (counts & VK_SAMPLE_COUNT_8_BIT) {return VK_SAMPLE_COUNT_8_BIT;} if (counts & VK_SAMPLE_COUNT_4_BIT) {return VK_SAMPLE_COUNT_4_BIT;} if (counts & VK_SAMPLE_COUNT_2_BIT) {return VK_SAMPLE_COUNT_2_BIT;} return VK_SAMPLE_COUNT_1_BIT; } 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; data->msaaSamples = getMaxUseableSampleCount(data); 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++; } deviceFeatures.samplerAnisotropy = VK_TRUE; deviceFeatures.sampleRateShading = VK_TRUE; 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++) { data->swapChainImageViews[i] = createImageView(data->swapChainImages[i], data->swapChainImageFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1, data); } } 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); return size; } VkFormat findSupportedFormat(const VkFormat* candidates, int candidateCount, VkImageTiling tiling, VkFormatFeatureFlags features, struct VulkanData* data) { for (int i = 0; i < candidateCount; i++) { VkFormat format = candidates[i]; VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(data->physicalDevice, format, &props); if (tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) { return format; } else if (tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) { return format; } } fprintf(stderr, "ERROR: Failed to find supported format\n"); exit(1); } VkFormat findDepthFormat(struct VulkanData* data) { const VkFormat candidates[] = {VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}; return findSupportedFormat( candidates, 3, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT, data ); } 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, }; VkPipelineDepthStencilStateCreateInfo depthStencil = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO, .depthTestEnable = VK_TRUE, .depthWriteEnable = VK_TRUE, .depthCompareOp = VK_COMPARE_OP_LESS, .depthBoundsTestEnable = VK_FALSE, .minDepthBounds = 0., // Optional .maxDepthBounds = 1., // Optional .stencilTestEnable = VK_FALSE, // .front = NULL, // Optional // .back = NULL, // Optional }; 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[] = { { .binding = 0, .location = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, pos), }, { .binding = 0, .location = 1, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, color), }, { .binding = 0, .location = 2, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, texCoord), } }; VkPipelineVertexInputStateCreateInfo vertexInputInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .vertexAttributeDescriptionCount = 3, .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, .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_TRUE, .rasterizationSamples = data->msaaSamples, .minSampleShading = .2, // 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 = &depthStencil, .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 = data->msaaSamples, .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_COLOR_ATTACHMENT_OPTIMAL, }; VkAttachmentReference colorAttachmentRef = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkAttachmentDescription depthAttachment = { .format = findDepthFormat(data), .samples = data->msaaSamples, .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_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; VkAttachmentReference depthAttachmentRef = { .attachment = 1, .layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, }; VkAttachmentDescription colorAttachmentResolve = { .format = data->swapChainImageFormat, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .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 colorAttachmentResolveRef = { .attachment = 2, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentRef, .pDepthStencilAttachment = &depthAttachmentRef, .pResolveAttachments = &colorAttachmentResolveRef, }; VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT, .srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT, }; VkAttachmentDescription attachments[3] = {colorAttachment, depthAttachment, colorAttachmentResolve}; VkRenderPassCreateInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 3, .pAttachments = attachments, .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->colorImageView, data->depthImageView, data->swapChainImageViews[i], }; VkFramebufferCreateInfo framebufferInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = data->renderPass, .attachmentCount = 3, .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 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); } bool hasStencilComponent(VkFormat format) { return format == VK_FORMAT_D32_SFLOAT_S8_UINT || format == VK_FORMAT_D24_UNORM_S8_UINT; } static void createImage(uint32_t width, uint32_t height, uint32_t mipLevels, VkSampleCountFlagBits numSamples, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage* image, VkDeviceMemory* imageMemory, struct VulkanData* data) { VkImageCreateInfo imageInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO, .imageType = VK_IMAGE_TYPE_2D, .extent.width = width, .extent.height = height, .extent.depth = 1, .mipLevels = mipLevels, .arrayLayers = 1, .format = format, .tiling = tiling, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, .samples = numSamples, .flags = 0, }; if (vkCreateImage(data->device, &imageInfo, NULL, image) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create image\n"); exit(1); } VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(data->device, *image, &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, imageMemory) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to allocate image memory\n"); exit(1); } vkBindImageMemory(data->device, *image, *imageMemory, 0); } 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 clearValues[] = { { .color = {{0.0f, 0.0f, 0.0f, 1.0f}}, }, { .depthStencil = {1., 0}, } }; 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 = 2, .pClearValues = clearValues, }; 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_UINT32); 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, data->indexCount, 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) { vkDestroyImageView(data->device, data->colorImageView, NULL); vkDestroyImage(data->device, data->colorImage, NULL); vkFreeMemory(data->device, data->colorImageMemory, NULL); vkDestroyImageView(data->device, data->depthImageView, NULL); vkDestroyImage(data->device, data->depthImage, NULL); vkFreeMemory(data->device, data->depthImageMemory, NULL); 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 transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels, struct VulkanData* data) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(true, data); VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .oldLayout = oldLayout, .newLayout = newLayout, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .image = image, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = mipLevels, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, .srcAccessMask = 0, .dstAccessMask = 0, }; VkPipelineStageFlags sourceStage; VkPipelineStageFlags destinationStage; if (newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; if (hasStencilComponent(format)) { barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; } } else { barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; } if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL) { barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; destinationStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL) { barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; sourceStage = VK_PIPELINE_STAGE_TRANSFER_BIT; destinationStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL) { barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; sourceStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; destinationStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; } else { fprintf(stderr, "ERROR: Unsupported layout transition\n"); exit(1); } vkCmdPipelineBarrier( commandBuffer, sourceStage, destinationStage, 0, 0, NULL, 0, NULL, 1, &barrier ); endSingleTimeCommands(commandBuffer, true, data); } static void createDepthResources(struct VulkanData* data) { VkFormat depthFormat = findDepthFormat(data); createImage( data->swapChainExtent.width, data->swapChainExtent.height, 1, data->msaaSamples, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->depthImage, &data->depthImageMemory, data ); data->depthImageView = createImageView(data->depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT, 1, data); transitionImageLayout(data->depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL, 1, data); } static void createColorResources(struct VulkanData* data) { VkFormat colorFormat = data->swapChainImageFormat; createImage(data->swapChainExtent.width, data->swapChainExtent.height, 1, data->msaaSamples, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->colorImage, &data->colorImageMemory, data); data->colorImageView = createImageView(data->colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT, 1, data); } 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); createColorResources(data); createDepthResources(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 void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height, struct VulkanData* data) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(false, data); VkBufferImageCopy region = { .bufferOffset = 0, .bufferRowLength = 0, .bufferImageHeight = 0, .imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .imageSubresource.mipLevel = 0, .imageSubresource.baseArrayLayer = 0, .imageSubresource.layerCount = 1, .imageOffset = {0, 0, 0}, .imageExtent = {width, height, 1}, }; vkCmdCopyBufferToImage( commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion ); endSingleTimeCommands(commandBuffer, false, data); } 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) { VkCommandBuffer commandBuffer = beginSingleTimeCommands(false, data); VkBufferCopy copyRegion = { .srcOffset = 0, .dstOffset = 0, .size = size, }; vkCmdCopyBuffer(commandBuffer, srcBuffer, dstBuffer, 1, ©Region); endSingleTimeCommands(commandBuffer, false, data); } static void createIndexBuffer(struct VulkanData* data) { VkDeviceSize bufferSize = sizeof(data->indices[0]) * data->indexCount; 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, data->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(data->vertices[0]) * data->vertexCount; 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, data->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, }; VkDescriptorSetLayoutBinding samplerLayoutBinding = { .binding = 1, .descriptorCount = 1, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .pImmutableSamplers = NULL, .stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT, }; VkDescriptorSetLayoutBinding bindings[2] = {uboLayoutBinding, samplerLayoutBinding}; VkDescriptorSetLayoutCreateInfo layoutInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO, .bindingCount = 2, .pBindings = bindings, }; 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 uboPoolSize = { .type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, .descriptorCount = MAX_FRAMES_IN_FLIGHT, }; VkDescriptorPoolSize samplerPoolSize = { .type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = MAX_FRAMES_IN_FLIGHT, }; VkDescriptorPoolSize poolSizes[2] = {uboPoolSize, samplerPoolSize}; VkDescriptorPoolCreateInfo poolInfo = { .sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO, .poolSizeCount = 2, .pPoolSizes = poolSizes, .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), }; VkDescriptorImageInfo imageInfo = { .imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, .imageView = data->textureImageView, .sampler = data->textureSampler, }; VkWriteDescriptorSet descriptorWrites[2] = { { .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, }, { .sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET, .dstSet = data->descriptorSets[i], .dstBinding = 1, .dstArrayElement = 0, .descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, .descriptorCount = 1, .pBufferInfo = NULL, .pImageInfo = &imageInfo, .pTexelBufferView = NULL, } }; vkUpdateDescriptorSets(data->device, 2, descriptorWrites, 0, NULL); } } // Because of the format support and alpha of 1 is added static struct PPM readPPM(char* fileName) { FILE* file = fopen(fileName, "r"); char header[2]; fread(header, sizeof(header[0]), 2, file); if (header[0] != 'P' || header[1] != '6') { fprintf(stderr, "ERROR: Unrecognized file format\n"); exit(1); } if (fseek(file, 1, SEEK_CUR)) { fprintf(stderr, "ERROR: Failed to read image: \"%s\"\n", fileName); exit(1); } int len = 0; char c = '0'; while (true) { if (fread(&c, 1, 1, file) != 1) { fprintf(stderr, "ERROR: Failed to read width of image: \"%s\" with char: %c\n", fileName, c); exit(1); } if (c < '0' || c > '9') { break; } len++; } fseek(file, -len - 1, SEEK_CUR); char widthStr[len + 1]; fread(widthStr, sizeof(widthStr[0]), len, file); widthStr[len] = 0; int width = atoi(widthStr); fseek(file, 1, SEEK_CUR); len = 0; c = '0'; while (true) { if (fread(&c, 1, 1, file) != 1) { fprintf(stderr, "ERROR: Failed to read height of image: \"%s\"\n", fileName); exit(1); } if (c < '0' || c > '9') { break; } len++; } fseek(file, -len - 1, SEEK_CUR); char heightStr[len + 1]; fread(heightStr, sizeof(heightStr[0]), len, file); heightStr[len] = 0; int height = atoi(heightStr); fseek(file, 1, SEEK_CUR); len = 0; c = '0'; while (true) { if (fread(&c, 1, 1, file) != 1) { fprintf(stderr, "ERROR: Failed to read maximum value for pixels in image: \"%s\"\n", fileName); exit(1); } if (c < '0' || c > '9') { break; } len++; } fseek(file, -len - 1, SEEK_CUR); char maxValStr[len + 1]; fread(maxValStr, sizeof(maxValStr[0]), len, file); maxValStr[len] = 0; int maxVal = atoi(maxValStr); printf("INFO: Image \"%s\" has a width and height of (%d, %d) and maximum value for pixels of %d\n", fileName, width, height, maxVal); fseek(file, 1, SEEK_CUR); int pixelCount = width * height; struct PPM ppm = { .width = width, .height = height, .maxVal = maxVal, .pixels = NULL, }; if (maxVal <= UCHAR_MAX) { uint8_t* pixels = malloc(pixelCount * 4); uint8_t* pixel_p = pixels; for (int i = 0; i < pixelCount; i++) { fread(pixel_p, sizeof(uint8_t), 3, file); pixel_p += 3; *pixel_p = UCHAR_MAX; pixel_p++; } ppm.pixels = pixels; } else if (maxVal <= USHRT_MAX) { uint16_t* pixels = malloc(pixelCount * sizeof(uint16_t) * 4); uint16_t* pixel_p = pixels; for (int i = 0; i < pixelCount; i++) { fread(pixel_p, sizeof(uint16_t), 3, file); pixel_p += 3; *pixel_p = USHRT_MAX; pixel_p++; } ppm.pixels = pixels; } else { fprintf(stderr, "ERROR: Specified maximum value for pixels is too large\n"); exit(1); } return ppm; } static void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels, struct VulkanData* data) { VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(data->physicalDevice, imageFormat, &formatProperties); if (!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) { fprintf(stderr, "ERROR: Texture image format does not support linear blitting\n"); exit(1); } VkCommandBuffer commandBuffer = beginSingleTimeCommands(true, data); VkImageMemoryBarrier barrier = { .sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER, .image = image, .srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, .subresourceRange.levelCount = 1, }; int32_t mipWidth = texWidth; int32_t mipHeight = texHeight; for (uint32_t i = 1; i < mipLevels; i++) { barrier.subresourceRange.baseMipLevel = i - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, NULL, 0, NULL, 1, &barrier ); VkImageBlit blit = { .srcOffsets[0] = {0, 0, 0}, .srcOffsets[1] = {mipWidth, mipHeight, 1}, .srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .srcSubresource.mipLevel = i - 1, .srcSubresource.baseArrayLayer = 0, .srcSubresource.layerCount = 1, .dstOffsets[0] = {0, 0, 0}, .dstOffsets[1] = { mipWidth > 1 ? mipWidth / 2 : 1, mipHeight > 1 ? mipHeight / 2 : 1, 1 }, .dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .dstSubresource.mipLevel = i, .dstSubresource.baseArrayLayer = 0, .dstSubresource.layerCount = 1, }; vkCmdBlitImage(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR ); barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &barrier ); if (mipWidth > 1) {mipWidth /= 2;} if (mipHeight > 1) {mipHeight /= 2;} } barrier.subresourceRange.baseMipLevel = mipLevels - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, NULL, 0, NULL, 1, &barrier ); endSingleTimeCommands(commandBuffer, true, data); } static void createTextureImage(struct VulkanData* data) { struct PPM ppm = readPPM("images/viking_room.ppm"); int nSize = 1; VkFormat format = VK_FORMAT_R8G8B8A8_SRGB; if (ppm.maxVal > 255) { nSize = 2; format = VK_FORMAT_R16G16B16_UINT; } VkDeviceSize imageSize = ppm.width * ppm.height * nSize * 4; data->mipLevels = floor(log2(fmax(ppm.width, ppm.height))) + 1; printf("mipLevels: %d\n", data->mipLevels); VkBuffer stagingBuffer; VkDeviceMemory stagingBufferMemory; createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory, data); void* imageData; vkMapMemory(data->device, stagingBufferMemory, 0, imageSize, 0, &imageData); memcpy(imageData, ppm.pixels, imageSize); vkUnmapMemory(data->device, stagingBufferMemory); free(ppm.pixels); createImage(ppm.width, ppm.height, data->mipLevels, VK_SAMPLE_COUNT_1_BIT, format, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &data->textureImage, &data->textureImageMemory, data), transitionImageLayout(data->textureImage, format, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, data->mipLevels, data); copyBufferToImage(stagingBuffer, data->textureImage, ppm.width, ppm.height, data); // transitioned to VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL while generating mipmaps generateMipmaps(data->textureImage, format, ppm.width, ppm.height, data->mipLevels, data); vkDestroyBuffer(data->device, stagingBuffer, NULL); vkFreeMemory(data->device, stagingBufferMemory, NULL); } static void createTextureImageView(struct VulkanData* data) { data->textureImageView = createImageView(data->textureImage, VK_FORMAT_R8G8B8A8_SRGB, VK_IMAGE_ASPECT_COLOR_BIT, data->mipLevels, data); } static void createTextureSampler(struct VulkanData* data) { VkPhysicalDeviceProperties properties; vkGetPhysicalDeviceProperties(data->physicalDevice, &properties); VkSamplerCreateInfo samplerInfo = { .sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO, .magFilter = VK_FILTER_LINEAR, .minFilter = VK_FILTER_LINEAR, .addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT, .addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT, .anisotropyEnable = VK_TRUE, .maxAnisotropy = properties.limits.maxSamplerAnisotropy, // set to 1 to disable .borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK, .unnormalizedCoordinates = VK_FALSE, .compareEnable = VK_FALSE, .compareOp = VK_COMPARE_OP_ALWAYS, .mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR, .minLod = 0., .maxLod = VK_LOD_CLAMP_NONE, .mipLodBias = 0., }; if (vkCreateSampler(data->device, &samplerInfo, NULL, &data->textureSampler) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create texture sampler\n"); exit(1); } } float parseFloat(char data[], char numberBuf[], int* i) { int j = 0; char c = data[*i + j]; while (c != ' ' && c != '\n') { if ((c >= '0' && c <= '9') || c == '.' || c == '-') { numberBuf[j] = c; } else { printf("ERROR: sheesh at char: %c\n", c); exit(1); } j++; c = data[*i + j]; } numberBuf[j] = 0; *i += j + 1; return atof(numberBuf); } struct ObjModel { void* p; int vertexCount; int normalCount; uint32_t faceCount; int faceSplitCount; Vec3* vertexPos; Vec2* texPos; Vec3* normalVectors; int* faces; int* faceSplits; }; static struct ObjModel readObjFile(const char* fileName) { FILE* file = fopen(fileName, "r"); int objFileSize = fileSize(fileName); char* data = malloc(objFileSize); fread(data, sizeof(char), objFileSize, file); int lines = 0; for (int i = 0; i < objFileSize; i++) { if (data[i] == '\n') { lines++; } } int lengthOfLines[lines]; int currentLine = 0; int lineLenCount = 0; for (int i = 0; i < objFileSize; i++) { lineLenCount++; if (data[i] == '\n') { lengthOfLines[currentLine] = lineLenCount - 1; lineLenCount = 0; currentLine++; } } int vertexCount = 0; int curveCount = 0; int normalCount = 0; int textureCount = 0; int faceCount = 0; int faceSplitCount = 0; currentLine = 0; for (int i = 0; currentLine < lines; i++) { if (data[i] == 'v') { switch(data[i + 1]) { case ' ': vertexCount++; break; case 'p': curveCount++; break; case 'n': normalCount++; break; case 't': textureCount++; break; default: printf("ERROR: Invalid char\n"); exit(1); } } else if (data[i] == 'f') { faceCount++; } else if (data[i] == 's') { faceSplitCount++; } i += lengthOfLines[currentLine]; currentLine++; } printf("vertices: %d, curvePoints: %d, normalVectors: %d, textureVertices: %d, faces: %d\n", vertexCount, curveCount, normalCount, textureCount, faceCount); int vertexSize = sizeof(Vec3) * vertexCount; int textureSize = sizeof(Vec2) * textureCount; int normalSize = sizeof(Vec3) * normalCount; int faceSize = sizeof(int) * 9 * faceCount; int faceSplitSize = sizeof(int) * faceSplitCount; char* p = malloc(vertexSize + textureSize + normalSize + faceSize + faceSplitSize); Vec3* vertexPos = (Vec3*) p; Vec2* texPos = (Vec2*) (p + vertexSize); Vec3* normalVectors = (Vec3*) (p + vertexSize + textureSize); int* faces = (int*) (p + vertexSize + textureSize + normalSize); int* faceSplits = (int*) (p + vertexSize + textureSize + normalSize + faceSize); int vertexIndex = 0; int textureIndex = 0; int normalIndex = 0; int faceIndex = 0; int faceSplitIndex = 0; char numberBuf[16]; currentLine = 0; for (int i = 0; i < objFileSize; i++) { if (data[i] == 'v') { if (data[i + 1] == ' ') { i += 2; Vec3 pos = { parseFloat(data, numberBuf, &i), parseFloat(data, numberBuf, &i), parseFloat(data, numberBuf, &i), }; vertexPos[vertexIndex] = pos; vertexIndex++; i--; } else if (data[i + 1] == 'p') { } else if(data[i + 1] == 'n') { i += 3; Vec3 pos = { parseFloat(data, numberBuf, &i), parseFloat(data, numberBuf, &i), parseFloat(data, numberBuf, &i), }; normalVectors[normalIndex] = pos; normalIndex++; i--; } else if (data[i + 1] == 't') { i += 3; Vec2 pos = { parseFloat(data, numberBuf, &i), parseFloat(data, numberBuf, &i), }; texPos[textureIndex] = pos; textureIndex++; i--; } else { printf("sheesh\n"); exit(1); } } else if (data[i] == 'f') { i += 2; int numLen = 0; for (int j = 0; j < 9; j++) { while (data[i + numLen] != '/' && data[i + numLen] != ' ' && data[i + numLen] != '\n') { numLen++; } for (int k = 0; k < numLen; k++) { numberBuf[k] = data[i + k]; } numberBuf[numLen] = 0; faces[faceIndex * 9 + j] = atoi(numberBuf); i += numLen + 1; numLen = 0; } i--; faceIndex++; } else if (data[i] == 's') { faceSplits[faceSplitIndex] = faceIndex; i += lengthOfLines[currentLine]; } else { i += lengthOfLines[currentLine]; } currentLine++; } printf("last vert pos: (%f, %f, %f)\n", vertexPos[vertexCount - 1].x, vertexPos[vertexCount - 1].y, vertexPos[vertexCount - 1].z); printf("last texture pos: (%f, %f)\n", texPos[textureCount - 1].x, texPos[textureCount - 1].y); printf("last normal pos: (%f, %f, %f)\n", normalVectors[normalCount - 1].x, normalVectors[normalCount - 1].y, normalVectors[normalCount - 1].z); printf("last face: %d, %d\n", faces[(faceCount - 1) * 9], faces[(faceCount - 1) * 9 + 8]); printf("face split: %d\n", faceSplits[0]); free(data); struct ObjModel model = { p, vertexCount, normalCount, faceCount, faceSplitCount, vertexPos, texPos, normalVectors, faces, faceSplits, }; return model; } bool isVertexEqual(Vertex* a, Vertex* b) { return a->pos.x == b->pos.x && a->pos.y == b->pos.y && a->pos.z == b->pos.z && a->texCoord.x == b->texCoord.x && a->texCoord.y == b->texCoord.y; } static void loadModel(struct VulkanData* data) { struct ObjModel model = readObjFile("models/viking_room.obj"); data->indexCount = model.faceCount * 3; void* p = malloc(data->indexCount * (sizeof(Vertex) + sizeof(uint32_t))); data->vertices = p; data->indices = (uint32_t*)((char*)p + sizeof(Vertex) * data->indexCount); int uniqueVertexCount = 0; printf("Vertices allocated %ld bytes\n", sizeof(Vertex) * data->indexCount); printf("Index count: %d\n", data->indexCount); for (uint32_t i = 0; i < data->indexCount; i++) { uint32_t* face = ((uint32_t*) model.faces + i * 3); Vertex vertex = { .pos = model.vertexPos[*(face) - 1], .texCoord = model.texPos[*(face + 1) - 1], .color = {1., 1., 1.}, }; vertex.texCoord.y = 1. - vertex.texCoord.y; bool seen = false; for (int j = 0; j < uniqueVertexCount; j++) { if (isVertexEqual(&vertex, &data->vertices[j])) { data->indices[i] = j; seen = true; break; } } if (seen) { continue; } data->vertices[uniqueVertexCount] = vertex; data->indices[i] = uniqueVertexCount; uniqueVertexCount++; } // Realign data such that vertices are right next to indices data->vertexCount = uniqueVertexCount; memmove((Vertex*)p + data->vertexCount, data->indices, sizeof(uint32_t) * data->indexCount); p = realloc(p, sizeof(Vertex) * data->vertexCount + sizeof(uint32_t) * data->indexCount); data->vertices = p; data->indices = (uint32_t*)((Vertex*)p + data->vertexCount); printf("Vertices are now using %ld bytes\n", sizeof(Vertex) * data->vertexCount); free(model.p); printf("Unique vertices: %d\n", data->vertexCount); } static void initVulkan(GLFWwindow* window, struct VulkanData* data) { data->currentFrame = 0; data->framebufferResized = false; data->msaaSamples = VK_SAMPLE_COUNT_1_BIT; createInstance(data); setupDebugMessenger(data); createSurface(data, window); pickPhysicalDevice(data); createLogicalDevice(data); createSwapChain(data, window); createImageViews(data); createRenderPass(data); createDescriptorSetLayout(data); createGraphicsPipeline(data); createCommandPool(data); createColorResources(data); createDepthResources(data); createFramebuffers(data); createTextureImage(data); createTextureImageView(data); createTextureSampler(data); loadModel(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) { struct timespec frameTimeStart, frameTimeEnd; int count = 100; double elapsed[count]; int i = 0; while (!glfwWindowShouldClose(window)) { if (clock_gettime(CLOCK_MONOTONIC, &frameTimeStart) != 0) { perror("clock_gettime"); exit(1); } glfwSwapBuffers(window); glfwPollEvents(); drawFrame(data, window, start); int state = glfwGetKey(window, GLFW_KEY_Q); if (state == GLFW_PRESS) { break; } if (clock_gettime(CLOCK_MONOTONIC, &frameTimeEnd) != 0) { perror("clock_gettime"); exit(1); } elapsed[i] = (frameTimeEnd.tv_sec - frameTimeStart.tv_sec) + (frameTimeEnd.tv_nsec - frameTimeStart.tv_nsec) / 1e6; i++; i %= count; if (i == 0) { double worst = elapsed[0]; for(int j = 1; j < count; j++) { worst = fmax(worst, elapsed[j]); } printf("INFO: worst of %d: %f ms\n", count, worst); } } vkDeviceWaitIdle(data->device); } static void cleanup(GLFWwindow* window, struct VulkanData* data) { cleanupSwapChain(data); // Only one allocation for both vertices and indices free(data->vertices); vkDestroySampler(data->device, data->textureSampler, NULL); vkDestroyImageView(data->device, data->textureImageView, NULL); vkDestroyImage(data->device, data->textureImage, NULL); vkFreeMemory(data->device, data->textureImageMemory, NULL); 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(); }