#include "dyn_arr.h" #include #include #include #include #include #include #include #include #include #define GLFW_INCLUDE_VULKAN #include #ifdef NDEBUG const bool enableValidationLayers = false; #else const bool enableValidationLayers = true; #endif static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT *pCallbackData, void *pUserData) { // TODO: Put all messages into a log file if (messageSeverity >= VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT) { fprintf(stderr, "validation layer: %s\n", pCallbackData->pMessage); } return VK_FALSE; } const char *deviceExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; size_t deviceExtentionCount = sizeof(deviceExtensions) / sizeof(deviceExtensions[0]); const char *validationLayers[] = {"VK_LAYER_KHRONOS_validation"}; size_t validationLayerCount = sizeof(validationLayers) / sizeof(validationLayers[0]); typedef struct Application { GLFWwindow *window; VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger; VkSurfaceKHR surface; VkPhysicalDevice physicalDevice; VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; VkSwapchainKHR swapChain; VkImage *swapChainImages; uint32_t swapChainImageCount; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; VkImageView *swapChainImageViews; uint32_t swapChainImageViewCount; VkPipelineLayout pipelineLayout; } Application; typedef struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; uint32_t numFormats; VkSurfaceFormatKHR *formats; uint32_t numPresentModes; VkPresentModeKHR *presentModes; } SwapChainSupportDetails; bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, NULL); VkLayerProperties availableLayers[layerCount]; vkEnumerateInstanceLayerProperties(&layerCount, availableLayers); for (int i = 0; i < validationLayerCount; i++) { bool layerFound = false; for (int j = 0; j < layerCount; j++) { if (strcmp(availableLayers[j].layerName, validationLayers[i]) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; } bool verifyExtensionSupport(uint32_t extensionCount, VkExtensionProperties *extentions, uint32_t glfwExtensionCount, const char **glfwExtensions) { for (uint32_t i = 0; i < glfwExtensionCount; i++) { bool layerFound = false; for (uint32_t j = 0; j < extensionCount; j++) { if (strcmp(glfwExtensions[i], extentions[j].extensionName) == 0) { layerFound = true; break; } } if (!layerFound) { fprintf(stderr, "Missing %s vulkan extention\n", glfwExtensions[i]); return false; } } return true; } void populateDebugMessengerCreateInfo( VkDebugUtilsMessengerCreateInfoEXT *createInfo) { createInfo->sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT; createInfo->messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT; createInfo->messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT; createInfo->pfnUserCallback = debugCallback; } void createInstance(Application *app) { if (enableValidationLayers && !checkValidationLayerSupport()) { fprintf(stderr, "Validation layers requested, but not available!\n"); exit(EXIT_FAILURE); } 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_0, }; uint32_t glfwExtensionCount = 0; const char **glfwExtensions; glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); const char *glfwExtensionsDebug[glfwExtensionCount + 1]; VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, .enabledExtensionCount = glfwExtensionCount, .ppEnabledExtensionNames = glfwExtensions, .enabledLayerCount = 0, }; uint32_t extensionCount = 0; vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, NULL); VkExtensionProperties extensions[extensionCount]; vkEnumerateInstanceExtensionProperties(NULL, &extensionCount, extensions); VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = {0}; if (enableValidationLayers) { populateDebugMessengerCreateInfo(&debugCreateInfo); createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT *)&debugCreateInfo; createInfo.enabledLayerCount = validationLayerCount; createInfo.ppEnabledLayerNames = validationLayers; for (size_t i = 0; i < glfwExtensionCount; i++) { glfwExtensionsDebug[i] = glfwExtensions[i]; } glfwExtensionsDebug[glfwExtensionCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; createInfo.enabledExtensionCount = glfwExtensionCount + 1; createInfo.ppEnabledExtensionNames = glfwExtensionsDebug; if (!verifyExtensionSupport(extensionCount, extensions, glfwExtensionCount + 1, glfwExtensionsDebug)) { fprintf( stderr, "Failed to find all required vulkan extentions for glfw and debug\n"); exit(EXIT_FAILURE); } } else { if (!verifyExtensionSupport(extensionCount, extensions, glfwExtensionCount, glfwExtensions)) { fprintf(stderr, "Failed to find all required vulkan extentions for glfw\n"); exit(EXIT_FAILURE); } } // This currently leaks memory on laptop :( VkResult result = vkCreateInstance(&createInfo, NULL, &app->instance); if (result != VK_SUCCESS) { fprintf(stderr, "Failed to create vulkan instance\n"); exit(EXIT_FAILURE); } } void initWindow(Application *app) { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); app->window = glfwCreateWindow(800, 600, "Vulkan", NULL, NULL); } VkResult CreateDebugUtilsMessengerEXT( VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT *pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDebugUtilsMessengerEXT *pDebugMessenger) { PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkCreateDebugUtilsMessengerEXT"); if (func != NULL) { return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } else { return VK_ERROR_EXTENSION_NOT_PRESENT; } } void setupDebugMessenger(Application *app) { if (!enableValidationLayers) { return; } if (!checkValidationLayerSupport()) { fprintf(stderr, "Validation layers requested, but not available!\n"); exit(EXIT_FAILURE); } VkDebugUtilsMessengerCreateInfoEXT createInfo = {}; populateDebugMessengerCreateInfo(&createInfo); if (CreateDebugUtilsMessengerEXT(app->instance, &createInfo, NULL, &app->debugMessenger) != VK_SUCCESS) { fprintf(stderr, "failed to set up debug messenger!\n"); } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks *pAllocator) { PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT)vkGetInstanceProcAddr( instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != NULL) { func(instance, debugMessenger, pAllocator); } } struct QueueFamilyIndices_s { bool graphicsFamilyExists; uint32_t graphicsFamily; bool presentFamilyExists; uint32_t presentFamily; } QueueFamilyIndices_default = { .graphicsFamilyExists = false, .graphicsFamily = 0, .presentFamilyExists = false, .presentFamily = 0, }; typedef struct QueueFamilyIndices_s QueueFamilyIndices; QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device, VkSurfaceKHR *surface) { QueueFamilyIndices indices = QueueFamilyIndices_default; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, NULL); VkQueueFamilyProperties queueFamilies[queueFamilyCount]; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies); for (uint32_t i = 0; i < queueFamilyCount; i++) { // Graphics support if (queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; indices.graphicsFamilyExists = true; } // Surface support VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, *surface, &presentSupport); if (presentSupport) { indices.presentFamily = i; indices.presentFamilyExists = true; } // Early break if we've found everything if (indices.presentFamilyExists && indices.graphicsFamilyExists) { break; } } return indices; } bool checkDeviceExtensionSupport(VkPhysicalDevice device) { uint32_t availableExtensionCount; vkEnumerateDeviceExtensionProperties(device, NULL, &availableExtensionCount, NULL); VkExtensionProperties availableExtentions[availableExtensionCount]; vkEnumerateDeviceExtensionProperties(device, NULL, &availableExtensionCount, availableExtentions); for (uint i = 0; i < deviceExtentionCount; i++) { bool extentionFound = false; for (uint j = 0; j < availableExtensionCount; j++) { if (strcmp(availableExtentions[j].extensionName, deviceExtensions[i]) == 0) { extentionFound = true; break; } } if (!extentionFound) { return false; } } return true; } // TODO: find a better way of managing memory SwapChainSupportDetails querySwapchainSupport(VkPhysicalDevice device, VkSurfaceKHR *surface) { SwapChainSupportDetails details = {0}; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, *surface, &details.capabilities); vkGetPhysicalDeviceSurfaceFormatsKHR(device, *surface, &details.numFormats, NULL); if (details.numFormats > 0) { details.formats = malloc(details.numFormats * sizeof(VkSurfaceFormatKHR)); vkGetPhysicalDeviceSurfaceFormatsKHR(device, *surface, &details.numFormats, details.formats); } vkGetPhysicalDeviceSurfacePresentModesKHR(device, *surface, &details.numPresentModes, NULL); if (details.numPresentModes > 0) { details.presentModes = malloc(details.numPresentModes * sizeof(VkPresentModeKHR)); vkGetPhysicalDeviceSurfacePresentModesKHR( device, *surface, &details.numPresentModes, details.presentModes); } return details; } // Prefer one format but otherwise go for VkSurfaceFormatKHR chooseSwapSurfaceFormat(VkSurfaceFormatKHR *availableFormats, size_t availableFormatCount) { for (uint i = 0; i < availableFormatCount; i++) { if (availableFormats[i].format == VK_FORMAT_B8G8R8_SRGB && availableFormats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormats[i]; } } return availableFormats[0]; } // Prefer mailbox otherwise go for the garenteed available present mode VkPresentModeKHR chooseSwapPresentMode(VkPresentModeKHR *availablePresentModes, size_t availablePresentModesCount) { for (uint i = 0; i < availablePresentModesCount; i++) { if (availablePresentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentModes[i]; } } return VK_PRESENT_MODE_FIFO_KHR; } VkExtent2D chooseSwapExtent(GLFWwindow *window, const VkSurfaceCapabilitiesKHR *capabilities) { if (capabilities->currentExtent.width != UINT_MAX) { return capabilities->currentExtent; } int width, height; glfwGetFramebufferSize(window, &width, &height); VkExtent2D actualExtent = {(uint32_t)width, (uint32_t)height}; // check width and height are within bounds actualExtent.width = fmin(capabilities->maxImageExtent.width, actualExtent.width); actualExtent.width = fmax(capabilities->minImageExtent.width, actualExtent.width); actualExtent.height = fmin(capabilities->maxImageExtent.height, actualExtent.height); actualExtent.height = fmax(capabilities->minImageExtent.height, actualExtent.height); return actualExtent; } bool isDeviceSuitable(VkPhysicalDevice device, VkSurfaceKHR *surface) { QueueFamilyIndices indices = findQueueFamilies(device, surface); bool completeIndeces = indices.graphicsFamilyExists && indices.presentFamilyExists; bool extensionsSupported = checkDeviceExtensionSupport(device); bool swapChainAdequate = false; if (extensionsSupported) { SwapChainSupportDetails swapChainDetails = querySwapchainSupport(device, surface); if (swapChainDetails.numFormats > 0 && swapChainDetails.numPresentModes > 0) { swapChainAdequate = true; } free(swapChainDetails.presentModes); free(swapChainDetails.formats); } return completeIndeces && extensionsSupported && swapChainAdequate; } void pickPhysicalDevice(Application *app) { VkPhysicalDevice physicalDevice = VK_NULL_HANDLE; uint32_t physicalDeviceCount; vkEnumeratePhysicalDevices(app->instance, &physicalDeviceCount, NULL); if (physicalDeviceCount == 0) { fprintf(stderr, "Failed to find GPU with vulkan support!\n"); fprintf(stderr, "Failed to find GPU with vulkan support!\n"); exit(EXIT_FAILURE); } VkPhysicalDevice physicalDevices[physicalDeviceCount]; vkEnumeratePhysicalDevices(app->instance, &physicalDeviceCount, physicalDevices); // TODO: pick device off of more than if it's just suitable for (int i = 0; i < physicalDeviceCount; i++) { if (isDeviceSuitable(physicalDevices[i], &app->surface)) { physicalDevice = physicalDevices[i]; break; } } if (physicalDevice == VK_NULL_HANDLE) { fprintf(stderr, "Failed to find suitable GPU\n"); exit(EXIT_FAILURE); } app->physicalDevice = physicalDevice; } void createLogicalDevice(Application *app) { QueueFamilyIndices indices = findQueueFamilies(app->physicalDevice, &app->surface); uint32_t queueFamilies[] = {indices.graphicsFamily, indices.presentFamily}; size_t numQueues = sizeof(queueFamilies) / sizeof(uint32_t); // Make sure queue families don't have duplicates // TODO: eventually move over to a set rather than a dynamic array uint32_t *uniqueQueueFamilies = dyna_init(uint32_t); for (uint i = 0; i < numQueues; i++) { bool isUnique = true; for (uint j = 0; j < dyna_length(uniqueQueueFamilies); j++) { if (uniqueQueueFamilies[j] == queueFamilies[i]) { isUnique = false; break; } } if (isUnique) { dyna_append(uniqueQueueFamilies, queueFamilies[i]); } } VkDeviceQueueCreateInfo queueCreateInfos[dyna_length(uniqueQueueFamilies)]; for (uint i = 0; i < dyna_length(uniqueQueueFamilies); i++) { VkDeviceQueueCreateInfo queueCreateInfo = {}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = uniqueQueueFamilies[i]; queueCreateInfo.queueCount = 1; float queuePriority = 1.0f; queueCreateInfo.pQueuePriorities = &queuePriority; queueCreateInfos[i] = queueCreateInfo; } // Specify device features VkPhysicalDeviceFeatures deviceFeatures = {VK_FALSE}; // Specify logical device VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.pQueueCreateInfos = queueCreateInfos; createInfo.queueCreateInfoCount = dyna_length(uniqueQueueFamilies); createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = deviceExtentionCount; createInfo.ppEnabledExtensionNames = deviceExtensions; dyna_deinit(uniqueQueueFamilies); // Old Vulkan specify layers createInfo.enabledLayerCount = 0; createInfo.ppEnabledLayerNames = 0; if (enableValidationLayers) { createInfo.enabledLayerCount = validationLayerCount; createInfo.ppEnabledLayerNames = validationLayers; } if (vkCreateDevice(app->physicalDevice, &createInfo, NULL, &app->device) != VK_SUCCESS) { fprintf(stderr, "Failed to create logical device.\n"); exit(EXIT_FAILURE); } // Set graphics queue from logical device vkGetDeviceQueue(app->device, indices.graphicsFamily, 0, &app->graphicsQueue); vkGetDeviceQueue(app->device, indices.presentFamily, 0, &app->presentQueue); } void createSurface(Application *app) { if (glfwCreateWindowSurface(app->instance, app->window, NULL, &app->surface) != VK_SUCCESS) { fprintf(stderr, "Error creating vulkan surface in glfw window."); exit(EXIT_FAILURE); } } void createSwapChain(Application *app) { SwapChainSupportDetails swapChainSupport = querySwapchainSupport(app->physicalDevice, &app->surface); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat( swapChainSupport.formats, swapChainSupport.numFormats); VkPresentModeKHR presentMode = chooseSwapPresentMode( swapChainSupport.presentModes, swapChainSupport.numPresentModes); VkExtent2D extent = chooseSwapExtent(app->window, &swapChainSupport.capabilities); uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) { imageCount = swapChainSupport.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR createInfo = {0}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = app->surface; createInfo.minImageCount = imageCount; createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = extent; createInfo.imageArrayLayers = 1; createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(app->physicalDevice, &app->surface); uint32_t queueFamilyIndices[] = {indices.graphicsFamily, indices.presentFamily}; if (indices.graphicsFamily != indices.presentFamily) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.queueFamilyIndexCount = 0; createInfo.pQueueFamilyIndices = NULL; } createInfo.preTransform = swapChainSupport.capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; createInfo.oldSwapchain = VK_NULL_HANDLE; if (vkCreateSwapchainKHR(app->device, &createInfo, NULL, &app->swapChain) != VK_SUCCESS) { fprintf(stderr, "Failed to create swapchain!"); exit(EXIT_FAILURE); } vkGetSwapchainImagesKHR(app->device, app->swapChain, &app->swapChainImageCount, NULL); app->swapChainImages = malloc(app->swapChainImageCount * sizeof(VkImage)); vkGetSwapchainImagesKHR(app->device, app->swapChain, &app->swapChainImageCount, app->swapChainImages); app->swapChainImageFormat = surfaceFormat.format; app->swapChainExtent = extent; free(swapChainSupport.presentModes); free(swapChainSupport.formats); } void createImageViews(Application *app) { app->swapChainImageViewCount = app->swapChainImageCount; app->swapChainImageViews = malloc(app->swapChainImageViewCount * sizeof(VkImageView)); for (size_t i = 0; i < app->swapChainImageViewCount; i++) { VkImageViewCreateInfo createInfo = {0}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = app->swapChainImages[i]; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = app->swapChainImageFormat; createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; createInfo.subresourceRange.baseMipLevel = 0; createInfo.subresourceRange.levelCount = 1; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; if (vkCreateImageView(app->device, &createInfo, NULL, &app->swapChainImageViews[i]) != VK_SUCCESS) { fprintf(stderr, "Failed to create image views!\n"); exit(EXIT_FAILURE); } } } // Buffer should be freed typedef struct { char *buffer; size_t bufferSize; } ShaderBuffer; ShaderBuffer loadShaderIntoBuffer(const char *fileLocation) { ShaderBuffer buf = {.buffer = NULL, .bufferSize = 0}; FILE *file = fopen(fileLocation, "r"); if (file == NULL) { fprintf(stderr, "Could not open file \"%s\"!\n", fileLocation); exit(EXIT_FAILURE); } if (fseek(file, 0L, SEEK_END) != 0) { fprintf(stderr, "Could not seek to the end of file \"%s\"!\n", fileLocation); exit(EXIT_FAILURE); } buf.bufferSize = ftell(file); if (fseek(file, 0L, SEEK_SET) != 0) { fprintf(stderr, "Could not seek to the start of file \"%s\"!\n", fileLocation); exit(EXIT_FAILURE); } buf.buffer = malloc(sizeof(char) * (buf.bufferSize)); // Not '\0' terminting ourselves size_t tempSize = fread(buf.buffer, sizeof(char), buf.bufferSize, file); if (ferror(file) != 0) { fprintf(stderr, "Error reading file into buffer \"%s\"!\n", fileLocation); exit(EXIT_FAILURE); } fclose(file); return buf; } VkShaderModule createShaderModule(VkDevice device, ShaderBuffer code) { VkShaderModuleCreateInfo createInfo = {0}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.bufferSize; createInfo.pCode = (uint32_t *)code.buffer; VkShaderModule shaderModule; if (vkCreateShaderModule(device, &createInfo, NULL, &shaderModule) != VK_SUCCESS) { fprintf(stderr, "Failed to create shaderModule from Shader Code!"); exit(EXIT_FAILURE); } return shaderModule; } void createGraphicsPipeline(Application *app) { ShaderBuffer vertShaderCode = loadShaderIntoBuffer("shaders/shader.vert.spv"); ShaderBuffer fragShaderCode = loadShaderIntoBuffer("shaders/shader.frag.spv"); VkShaderModule vertShaderModule = createShaderModule(app->device, vertShaderCode); VkShaderModule fragShaderModule = createShaderModule(app->device, fragShaderCode); free(vertShaderCode.buffer); free(fragShaderCode.buffer); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {0}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertShaderStageInfo.module = vertShaderModule; vertShaderStageInfo.pName = "main"; vertShaderStageInfo.pSpecializationInfo = NULL; VkPipelineShaderStageCreateInfo fragShaderStageInfo = {0}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; fragShaderStageInfo.pName = "main"; vertShaderStageInfo.pSpecializationInfo = NULL; VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; size_t dynamicStatesCount = 2; VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR}; VkPipelineDynamicStateCreateInfo dynamicState = {0}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = dynamicStatesCount; dynamicState.pDynamicStates = dynamicStates; VkPipelineVertexInputStateCreateInfo vertexInputInfo = {0}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.pVertexBindingDescriptions = NULL; // Optional vertexInputInfo.vertexAttributeDescriptionCount = 0; vertexInputInfo.pVertexAttributeDescriptions = NULL; // Optional VkPipelineInputAssemblyStateCreateInfo inputAssembly = {0}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = VK_FALSE; VkViewport viewport = {0}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float)app->swapChainExtent.width; viewport.height = (float)app->swapChainExtent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkPipelineViewportStateCreateInfo viewportState = {0}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.scissorCount = 1; VkRect2D scissor = {0}; scissor.offset.x = 0; scissor.offset.y = 0; scissor.extent = app->swapChainExtent; VkPipelineRasterizationStateCreateInfo rasterizer = {0}; rasterizer.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; rasterizer.depthClampEnable = VK_FALSE; rasterizer.rasterizerDiscardEnable = VK_FALSE; rasterizer.polygonMode = VK_POLYGON_MODE_FILL; rasterizer.lineWidth = 1.0f; rasterizer.cullMode = VK_CULL_MODE_BACK_BIT; rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; rasterizer.depthBiasConstantFactor = 0.0f; // Optional rasterizer.depthBiasClamp = 0.0f; // Optional rasterizer.depthBiasSlopeFactor = 0.0f; // Optional // disable multisampling for now VkPipelineMultisampleStateCreateInfo multisampling = {0}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; multisampling.minSampleShading = 1.0f; // Optional multisampling.pSampleMask = NULL; // Optional multisampling.alphaToCoverageEnable = VK_FALSE; // Optional multisampling.alphaToOneEnable = VK_FALSE; // Optional VkPipelineColorBlendAttachmentState colorBlendAttachment = {0}; colorBlendAttachment.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; colorBlendAttachment.blendEnable = VK_FALSE; colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; // Optional colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; // Optional colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; // Optional colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; // Optional colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; // Optional VkPipelineLayoutCreateInfo pipelineLayoutInfo = {0}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 0; // Optional pipelineLayoutInfo.pSetLayouts = NULL; // Optional pipelineLayoutInfo.pushConstantRangeCount = 0; // Optional pipelineLayoutInfo.pPushConstantRanges = NULL; // Optional if (vkCreatePipelineLayout(app->device, &pipelineLayoutInfo, NULL, &app->pipelineLayout) != VK_SUCCESS) { fprintf(stderr, "Failed to create pipeline layout!"); exit(EXIT_FAILURE); } vkDestroyShaderModule(app->device, fragShaderModule, NULL); vkDestroyShaderModule(app->device, vertShaderModule, NULL); } void initVulkan(Application *app) { createInstance(app); setupDebugMessenger(app); createSurface(app); pickPhysicalDevice(app); createLogicalDevice(app); createSwapChain(app); createImageViews(app); createGraphicsPipeline(app); } void mainLoop(Application *app) { while (!glfwWindowShouldClose(app->window)) { glfwPollEvents(); } } void cleanup(Application *app) { vkDestroyPipelineLayout(app->device, app->pipelineLayout, NULL); for (size_t i = 0; i < app->swapChainImageViewCount; i++) { vkDestroyImageView(app->device, app->swapChainImageViews[i], NULL); } free(app->swapChainImageViews); free(app->swapChainImages); vkDestroySwapchainKHR(app->device, app->swapChain, NULL); vkDestroyDevice(app->device, NULL); if (enableValidationLayers) { DestroyDebugUtilsMessengerEXT(app->instance, app->debugMessenger, NULL); } vkDestroySurfaceKHR(app->instance, app->surface, NULL); vkDestroyInstance(app->instance, NULL); glfwDestroyWindow(app->window); glfwTerminate(); } int main(void) { Application app = {0}; initWindow(&app); initVulkan(&app); mainLoop(&app); cleanup(&app); return EXIT_SUCCESS; }