#include #include #define VK_USE_PLATFORM_WAYLAND_KHR #define GLFW_INCLUDE_VULKAN #include #define GLFW_EXPOSE_NATIVE_WAYLAND #include #include #include const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; const int MAX_FRAMES_IN_FLIGHT = 2; const char* VALIDATION_LAYERS[] = { "VK_LAYER_KHRONOS_validation" }; #define VALIDATION_LAYER_COUNT 1 const char* DEVICE_EXTENSIONS[] = { VK_KHR_SWAPCHAIN_EXTENSION_NAME }; #define DEVICE_EXTENSION_COUNT 1 #ifdef NDEBUG const uint32_t enableValidationLayers = 0; #else const uint32_t enableValidationLayers = 1; #endif struct VulkanData { VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger; VkPhysicalDevice physicalDevice; VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; VkSurfaceKHR surface; VkSwapchainKHR swapChain; VkImage swapChainImages[4]; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; VkImageView swapChainImageViews[4]; int imageCount; VkPipelineLayout pipelineLayout; VkRenderPass renderPass; VkPipeline graphicsPipeline; VkFramebuffer swapChainFramebuffers[4]; VkCommandPool commandPool; VkCommandBuffer commandBuffer; VkSemaphore imageAvailableSemaphore; VkSemaphore renderFinishedSemaphore; VkFence inFlightFence; }; struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; VkPresentModeKHR presentModes[5]; VkSurfaceFormatKHR formats[8]; }; struct Optional { uint32_t v; uint32_t is_some; }; struct QueueFamilyIndices { struct Optional graphicsFamily; struct Optional presentFamily; }; GLFWwindow* initWindow() { if (!glfwInit()) { fprintf(stderr, "ERROR: Failed to initialize window\n"); exit(1); } GLFWwindow* window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", NULL, NULL); if (!window) { glfwTerminate(); fprintf(stderr, "ERROR: Failed to create window\n"); exit(1); } glfwMakeContextCurrent(window); return window; } // INDIRECTION: main->run->initVulkan->createInstance uint32_t 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 0; } } return 1; } // INDIRECTION: main->run->initVulkan->createInstance 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; } // INDIRECTION: main->run->initVulkan void createInstance(struct VulkanData* data) { if (enableValidationLayers && !checkValidationLayerSupport()) { printf("ERROR: Validation layers requested, but not available\n"); exit(1); } VkApplicationInfo appInfo = { .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "Hello Triangle", .applicationVersion = VK_MAKE_VERSION(1, 0, 0), .pEngineName = "No Engine", .engineVersion = VK_MAKE_VERSION(1, 0, 0), .apiVersion = VK_API_VERSION_1_3, }; uint32_t count = 0; vkEnumerateInstanceExtensionProperties(NULL, &count, NULL); VkExtensionProperties availableExtensions[count]; vkEnumerateInstanceExtensionProperties(NULL, &count, availableExtensions); printf("available extensions:\n"); for (uint32_t i = 0; i < count; i++) { VkExtensionProperties extension = availableExtensions[i]; printf("\t%s\n", extension.extensionName); } uint32_t glfwExtensionCount = 0; const char** glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); const char* extensions[glfwExtensionCount + 1]; for (uint32_t i = 0; i < glfwExtensionCount; i++) { extensions[i] = glfwExtensions[i]; } if (enableValidationLayers) { extensions[glfwExtensionCount] = VK_EXT_DEBUG_UTILS_EXTENSION_NAME; } uint32_t extensionCount = sizeof(extensions) / sizeof(extensions[0]); printf("required extensions:\n"); for (uint32_t i = 0; i < extensionCount; i++) { printf("\t%s\n", extensions[i]); } VkInstanceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, .enabledExtensionCount = extensionCount, .ppEnabledExtensionNames = extensions, .enabledLayerCount = 0, }; if (enableValidationLayers) { createInfo.enabledLayerCount = VALIDATION_LAYER_COUNT; createInfo.ppEnabledLayerNames = VALIDATION_LAYERS; VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo = { .sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT, .messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT, .messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT, .pfnUserCallback = debugCallback, }; createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo; } else { createInfo.enabledLayerCount = 0; createInfo.pNext = NULL; } VkInstance instance; if (vkCreateInstance(&createInfo, NULL, &instance) != VK_SUCCESS) { printf("ERROR: Failed to create instance\n"); exit(1); } data->instance = instance; } VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) { PFN_vkCreateDebugUtilsMessengerEXT func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if (func == NULL) { return VK_ERROR_EXTENSION_NOT_PRESENT; } return func(instance, pCreateInfo, pAllocator, pDebugMessenger); } 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); } } void createSurface(struct VulkanData* data, GLFWwindow* window) { VkWaylandSurfaceCreateInfoKHR createInfo = { .sType = VK_STRUCTURE_TYPE_WAYLAND_SURFACE_CREATE_INFO_KHR, .display = glfwGetWaylandDisplay(), .surface = glfwGetWaylandWindow(window), }; // if (glfwCreateWindowSurface(data->instance, window, NULL, &surface) != VK_SUCCESS) { // fprintf(stderr, "ERROR: Failed to create window surface\n"); // exit(1); // } if (vkCreateWaylandSurfaceKHR(data->instance, &createInfo, NULL, &data->surface) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create window surface\n"); exit(1); } } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) { PFN_vkDestroyDebugUtilsMessengerEXT func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if (func != NULL) { func(instance, debugMessenger, pAllocator); } } uint32_t clamp(uint32_t value, uint32_t min, uint32_t max) { if (value > max) return max; if (value < min) return min; return value; } 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; } 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; } struct QueueFamilyIndices findQueueFamilies(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) { struct QueueFamilyIndices indices = { .graphicsFamily.v = 0, .graphicsFamily.is_some = 0, .presentFamily.v = 0, .presentFamily.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_GRAPHICS_BIT) { indices.graphicsFamily.v = i; indices.graphicsFamily.is_some = 1; } VkBool32 presentSupport = 0; vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, surface, &presentSupport); if (presentSupport) { indices.presentFamily.v = i; indices.presentFamily.is_some = 1; } if (indices.presentFamily.is_some && indices.graphicsFamily.is_some) { break; } } return indices; } int isDeviceSuitable(VkPhysicalDevice physicalDevice, VkSurfaceKHR surface) { // One could pick the device based on a score. struct QueueFamilyIndices indices = findQueueFamilies(physicalDevice, surface); VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); printf("GPU: %s\n", deviceProperties.deviceName); VkPhysicalDeviceFeatures deviceFeatures; int featureCount = sizeof(deviceFeatures) / 4; VkBool32* p = &deviceFeatures.robustBufferAccess; for (int i = 0; i < featureCount; i++) { *p = VK_FALSE; p++; } vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures); int extensionsSupported = checkDeviceExtensionSupport(physicalDevice); int swapChainAdequate = 0; if (extensionsSupported) { struct SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice, surface); swapChainAdequate = swapChainSupport.formats[0].format != 0; } return deviceProperties.deviceType == VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU && deviceFeatures.geometryShader && swapChainAdequate && indices.graphicsFamily.is_some && indices.presentFamily.is_some; } void pickPhysicalDevice(struct VulkanData* data) { data->physicalDevice = VK_NULL_HANDLE; uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(data->instance, &deviceCount, NULL); if (deviceCount == 0) { printf("ERROR: Failed to find GPUs with Vulkan support\n"); exit(1); } VkPhysicalDevice devices[deviceCount]; vkEnumeratePhysicalDevices(data->instance, &deviceCount, devices); for (uint32_t i = 0; i < deviceCount; i++) { VkPhysicalDevice device = devices[i]; if (isDeviceSuitable(device, data->surface)) { data->physicalDevice = device; break; } } if (data->physicalDevice == VK_NULL_HANDLE) { printf("ERROR: Failed to find a suitable GPU\n"); exit(1); } } void createLogicalDevice(struct VulkanData* data) { struct QueueFamilyIndices indices = findQueueFamilies(data->physicalDevice, data->surface); float queuePriority = 1.0f; VkDeviceQueueCreateInfo queueCreateInfos[2]; uint32_t uniqueQueueFamilies[] = {indices.graphicsFamily.v, indices.presentFamily.v}; for (int i = 0; i < 2; 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; } int queueCreateInfoCount = 2; if (indices.graphicsFamily.v == indices.presentFamily.v) { queueCreateInfoCount = 1; } for (int i = 0; i < queueCreateInfoCount; i++) { uint32_t queueFamily = uniqueQueueFamilies[i]; VkDeviceQueueCreateInfo queueCreateInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = queueFamily, .queueCount = 1, .pQueuePriorities = &queuePriority, }; queueCreateInfos[i] = queueCreateInfo; } VkPhysicalDeviceFeatures deviceFeatures; int featureCount = sizeof(deviceFeatures) / 4; VkBool32* p = &deviceFeatures.robustBufferAccess; for (int i = 0; i < featureCount; i++) { *p = VK_FALSE; p++; } VkDeviceCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .pEnabledFeatures = &deviceFeatures, .enabledExtensionCount = DEVICE_EXTENSION_COUNT, .queueCreateInfoCount = queueCreateInfoCount, .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 (!indices.graphicsFamily.is_some) { printf("no graphics\n"); } if (!indices.presentFamily.is_some) { printf("no present\n"); } vkGetDeviceQueue(data->device, indices.graphicsFamily.v, 0, &data->graphicsQueue); vkGetDeviceQueue(data->device, indices.presentFamily.v, 0, &data->presentQueue); } 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; } } 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]; } 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; } 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; } 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, }; struct QueueFamilyIndices indices = findQueueFamilies(data->physicalDevice, data->surface); uint32_t queueFamilyIndices[] = {indices.graphicsFamily.v, indices.presentFamily.v}; if (indices.graphicsFamily.v != indices.presentFamily.v) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.queueFamilyIndexCount = 0; // Optional createInfo.pQueueFamilyIndices = NULL; // Optional } createInfo.preTransform = swapChainSupport.capabilities.currentTransform; createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = presentMode; createInfo.clipped = VK_TRUE; createInfo.oldSwapchain = VK_NULL_HANDLE; 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); } void createImageViews(struct VulkanData* data) { for (int i = 0; i < data->imageCount; i++) { VkImageViewCreateInfo createInfo = { .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = data->swapChainImages[i], .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = data->swapChainImageFormat, .components.r = VK_COMPONENT_SWIZZLE_IDENTITY, .components.g = VK_COMPONENT_SWIZZLE_IDENTITY, .components.b = VK_COMPONENT_SWIZZLE_IDENTITY, .components.a = VK_COMPONENT_SWIZZLE_IDENTITY, .subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .subresourceRange.baseMipLevel = 0, .subresourceRange.levelCount = 1, .subresourceRange.baseArrayLayer = 0, .subresourceRange.layerCount = 1, }; if (vkCreateImageView(data->device, &createInfo, NULL, &data->swapChainImageViews[i]) != VK_SUCCESS) { fprintf(stderr, "failed to create image views!"); } } } 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); } uint32_t fileSize(const char* fileName) { FILE* file = fopen(fileName, "r"); if (!file) { printf("ERROR: File \"%s\" can't be opened \n", fileName); exit(1); } fseek(file, 0L, SEEK_END); uint32_t size = ftell(file); printf("INFO: File \"%s\" has size %d\n", fileName, size); rewind(file); return size; } 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; } void createGraphicsPipeline(struct VulkanData* data) { const char* vertFileName = "shaders/vert.spv"; uint32_t vertLen = fileSize(vertFileName); char vertShaderCode[vertLen]; readFile(vertFileName, vertShaderCode, vertLen); const char* fragFileName = "shaders/frag.spv"; uint32_t fragLen = fileSize(fragFileName); char fragShaderCode[fragLen]; readFile(fragFileName, fragShaderCode, fragLen); VkShaderModule vertShaderModule = createShaderModule(vertShaderCode, vertLen, data->device); VkShaderModule fragShaderModule = createShaderModule(fragShaderCode, fragLen, data->device); VkPipelineShaderStageCreateInfo vertShaderStageInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = vertShaderModule, .pName = "main", }; VkPipelineShaderStageCreateInfo fragShaderStageInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = fragShaderModule, .pName = "main", .pSpecializationInfo = NULL, }; VkPipelineShaderStageCreateInfo shaderStages[] = {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, }; VkPipelineVertexInputStateCreateInfo vertexInputInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 0, .pVertexBindingDescriptions = NULL, // Optional .vertexAttributeDescriptionCount = 0, .pVertexAttributeDescriptions = NULL, // Optional }; 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_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.0f, // Optional .depthBiasClamp = 0.0f, // Optional .depthBiasSlopeFactor = 0.0f, // Optional }; VkPipelineMultisampleStateCreateInfo multisampling = { .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .sampleShadingEnable = VK_FALSE, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, .minSampleShading = 1.0f, // Optional .pSampleMask = NULL, // Optional .alphaToCoverageEnable = VK_FALSE, // Optional .alphaToOneEnable = VK_FALSE, // Optional }; VkPipelineColorBlendAttachmentState colorBlendAttachment = { .colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT, .blendEnable = VK_FALSE, .srcColorBlendFactor = VK_BLEND_FACTOR_ONE, // Optional .dstColorBlendFactor = VK_BLEND_FACTOR_ZERO, // Optional .colorBlendOp = VK_BLEND_OP_ADD, // Optional .srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE, // Optional .dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO, // Optional .alphaBlendOp = VK_BLEND_OP_ADD, // Optional }; VkPipelineColorBlendStateCreateInfo colorBlending = { .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .logicOp = VK_LOGIC_OP_COPY, // Optional .attachmentCount = 1, .pAttachments = &colorBlendAttachment, .blendConstants[0] = 0.0f, // Optional .blendConstants[1] = 0.0f, // Optional .blendConstants[2] = 0.0f, // Optional .blendConstants[3] = 0.0f, // Optional }; VkPipelineLayoutCreateInfo pipelineLayoutInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, .setLayoutCount = 0, // Optional .pSetLayouts = NULL, // Optional .pushConstantRangeCount = 0, // Optional .pPushConstantRanges = NULL, // Optional }; if (vkCreatePipelineLayout(data->device, &pipelineLayoutInfo, NULL, &data->pipelineLayout) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create pipeline layout\n"); exit(1); } VkGraphicsPipelineCreateInfo pipelineInfo = { .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = shaderStages, .pVertexInputState = &vertexInputInfo, .pInputAssemblyState = &inputAssembly, .pViewportState = &viewportState, .pRasterizationState = &rasterizer, .pMultisampleState = &multisampling, .pDepthStencilState = NULL, // Optional .pColorBlendState = &colorBlending, .pDynamicState = &dynamicState, .layout = data->pipelineLayout, .renderPass = data->renderPass, .subpass = 0, .basePipelineHandle = VK_NULL_HANDLE, // Optional .basePipelineIndex = -1, // Optional }; if (vkCreateGraphicsPipelines(data->device, VK_NULL_HANDLE, 1, &pipelineInfo, NULL, &data->graphicsPipeline) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create graphics pipeline\n"); exit(1); } vkDestroyShaderModule(data->device, fragShaderModule, NULL); vkDestroyShaderModule(data->device, vertShaderModule, NULL); } void createRenderPass(struct VulkanData* data) { VkAttachmentDescription colorAttachment = { .format = data->swapChainImageFormat, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, }; VkAttachmentReference colorAttachmentRef = { .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpass = { .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentRef, }; VkSubpassDependency dependency = { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, }; VkRenderPassCreateInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &colorAttachment, .subpassCount = 1, .pSubpasses = &subpass, .dependencyCount = 1, .pDependencies = &dependency, }; if (vkCreateRenderPass(data->device, &renderPassInfo, NULL, &data->renderPass) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create render pass!\n"); exit(1); } } void createFramebuffers(struct VulkanData* data) { for (int i = 0; i < data->imageCount; i++) { VkImageView attachments[] = { data->swapChainImageViews[i] }; VkFramebufferCreateInfo framebufferInfo = { .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = data->renderPass, .attachmentCount = 1, .pAttachments = attachments, .width = data->swapChainExtent.width, .height = data->swapChainExtent.height, .layers = 1, }; if (vkCreateFramebuffer(data->device, &framebufferInfo, NULL, &data->swapChainFramebuffers[i]) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create framebuffer\n"); exit(1); } } } 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, }; if (vkCreateCommandPool(data->device, &poolInfo, NULL, &data->commandPool) != VK_SUCCESS) { fprintf(stderr, "ERROR: failed to create command pool\n"); exit(1); } } void createCommandBuffer(struct VulkanData* data) { VkCommandBufferAllocateInfo allocInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = data->commandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; if (vkAllocateCommandBuffers(data->device, &allocInfo, &data->commandBuffer) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to allocate command buffers\n"); exit(1); } } void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex, struct VulkanData* data) { VkCommandBufferBeginInfo beginInfo = { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = 0, .pInheritanceInfo = NULL, }; if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to begin recording command buffer\n"); exit(1); } VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; VkRenderPassBeginInfo renderPassInfo = { .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = data->renderPass, .framebuffer = data->swapChainFramebuffers[imageIndex], .renderArea.offset = {0, 0}, .renderArea.extent = data->swapChainExtent, .clearValueCount = 1, .pClearValues = &clearColor, }; vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, data->graphicsPipeline); 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); vkCmdDraw(commandBuffer, 3, 1, 0, 0); vkCmdEndRenderPass(commandBuffer); if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to record command buffer\n"); exit(1); } } void drawFrame(struct VulkanData* data) { vkWaitForFences(data->device, 1, &data->inFlightFence, VK_TRUE, UINT64_MAX); vkResetFences(data->device, 1, &data->inFlightFence); uint32_t imageIndex; vkAcquireNextImageKHR(data->device, data->swapChain, UINT64_MAX, data->imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); vkResetCommandBuffer(data->commandBuffer, 0); recordCommandBuffer(data->commandBuffer, imageIndex, data); VkSemaphore waitSemaphores[] = {data->imageAvailableSemaphore}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; VkSemaphore signalSemaphores[] = {data->renderFinishedSemaphore}; VkSubmitInfo submitInfo = { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = waitSemaphores, .pWaitDstStageMask = waitStages, .commandBufferCount = 1, .pCommandBuffers = &data->commandBuffer, .signalSemaphoreCount = 1, .pSignalSemaphores = signalSemaphores, }; if (vkQueueSubmit(data->graphicsQueue, 1, &submitInfo, data->inFlightFence) != 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, }; vkQueuePresentKHR(data->presentQueue, &presentInfo); } 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, }; if (vkCreateSemaphore(data->device, &semaphoreInfo, NULL, &data->imageAvailableSemaphore) != VK_SUCCESS || vkCreateSemaphore(data->device, &semaphoreInfo, NULL, &data->renderFinishedSemaphore) != VK_SUCCESS || vkCreateFence(data->device, &fenceInfo, NULL, &data->inFlightFence) != VK_SUCCESS) { fprintf(stderr, "ERROR: Failed to create semaphores\n"); exit(1); } } struct VulkanData initVulkan(GLFWwindow* window) { struct VulkanData data; createInstance(&data); setupDebugMessenger(&data); createSurface(&data, window); pickPhysicalDevice(&data); createLogicalDevice(&data); createSwapChain(&data, window); createImageViews(&data); createRenderPass(&data); createGraphicsPipeline(&data); createFramebuffers(&data); createCommandPool(&data); createCommandBuffer(&data); createSyncObjects(&data); return data; } void mainLoop(GLFWwindow* window, struct VulkanData* data) { while (!glfwWindowShouldClose(window)) { glfwSwapBuffers(window); glfwPollEvents(); drawFrame(data); int state = glfwGetKey(window, GLFW_KEY_Q); if (state == GLFW_PRESS) { break; } } vkDeviceWaitIdle(data->device); } void cleanup(GLFWwindow* window, struct VulkanData* data) { vkDestroySemaphore(data->device, data->imageAvailableSemaphore, NULL); vkDestroySemaphore(data->device, data->renderFinishedSemaphore, NULL); vkDestroyFence(data->device, data->inFlightFence, NULL); vkDestroyCommandPool(data->device, data->commandPool, NULL); for (int i = 0; i < data->imageCount; i++) { vkDestroyFramebuffer(data->device, data->swapChainFramebuffers[i], NULL); } vkDestroyPipeline(data->device, data->graphicsPipeline, NULL); vkDestroyPipelineLayout(data->device, data->pipelineLayout, NULL); vkDestroyRenderPass(data->device, data->renderPass, NULL); for (int i = 0; i < data->imageCount; i++) { vkDestroyImageView(data->device, data->swapChainImageViews[i], NULL); } vkDestroySwapchainKHR(data->device, data->swapChain, 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(); } void run() { GLFWwindow* window = initWindow(); struct VulkanData data = initVulkan(window); mainLoop(window, &data); cleanup(window, &data); } int main() { run(); }