commit e6cef74c8b1b862ed687f5d6526e9b453f803745 Author: Vegard Matthey Date: Sun Jul 7 12:53:24 2024 +0200 inital diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..224a0d6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +VulkanApplication +shaders/*.spv diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..e48d25f --- /dev/null +++ b/Makefile @@ -0,0 +1,14 @@ +LDFLAGS = -lglfw -lvulkan -ldl -lpthread + +CFLAGS = -g -pedantic -Wall -Wextra -Wshadow -Wunused-macros + +VulkanApplication: main.c + gcc $(CFLAGS) -o VulkanApplication main.c $(LDFLAGS) + +.PHONY: run clean + +run: VulkanApplication + ./VulkanApplication + +clean: + rm -f VulkanApplication diff --git a/main.c b/main.c new file mode 100644 index 0000000..b16b4bb --- /dev/null +++ b/main.c @@ -0,0 +1,1058 @@ +#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(); +} diff --git a/shaders/compile.sh b/shaders/compile.sh new file mode 100755 index 0000000..157fb15 --- /dev/null +++ b/shaders/compile.sh @@ -0,0 +1,3 @@ +#!/bin/sh +glslc shader.vert -o vert.spv +glslc shader.frag -o frag.spv diff --git a/shaders/shader.frag b/shaders/shader.frag new file mode 100644 index 0000000..7c5b0e7 --- /dev/null +++ b/shaders/shader.frag @@ -0,0 +1,9 @@ +#version 450 + +layout(location = 0) in vec3 fragColor; + +layout(location = 0) out vec4 outColor; + +void main() { + outColor = vec4(fragColor, 1.0); +} diff --git a/shaders/shader.vert b/shaders/shader.vert new file mode 100644 index 0000000..f5b2f8d --- /dev/null +++ b/shaders/shader.vert @@ -0,0 +1,20 @@ +#version 450 + +layout(location = 0) out vec3 fragColor; + +vec2 positions[3] = vec2[]( + vec2(0.0, -0.5), + vec2(0.5, 0.5), + vec2(-0.5, 0.5) +); + +vec3 colors[3] = vec3[]( + vec3(1.0, 0.0, 0.0), + vec3(0.0, 1.0, 0.0), + vec3(0.0, 0.0, 1.0) +); + +void main() { + gl_Position = vec4(positions[gl_VertexIndex], 0.0, 1.0); + fragColor = colors[gl_VertexIndex]; +}