#define GLFW_INCLUDE_VULKAN #include //#include // Because this is included by glfw3 #define GLM_FORCE_RADIANS #define GLM_FORCE_DEPTH_ZERO_TO_ONE #define GLM_ENABLE_EXPERIMENTAL #include #include #include #include "imgui.h" #include "imgui_impl_glfw.h" #include "imgui_impl_vulkan.h" #include #include #include #include #include #include #include #include #include #include #include #include "scolor.hpp" #define STB_IMAGE_IMPLEMENTATION #include "stb_image.h" #include "tiny_obj_loader.h" const int WIDTH = 1200; // initial const int HEIGHT = 800; const int MAX_FRAMES_IN_FLIGHT = 2; bool enableValidationLayers = false; #define MODEL_PATH "models/chalet.obj" #define TEXTURE_PATH "textures/chalet.jpg" const char* validationLayers[] = {"VK_LAYER_LUNARG_standard_validation"}; const char* deviceExtensions[] = {VK_KHR_SWAPCHAIN_EXTENSION_NAME}; struct UniformBufferObject { glm::mat4 model; glm::mat4 view; glm::mat4 proj; }; struct Vertex { glm::vec3 pos; glm::vec3 color; glm::vec2 texCoord; static VkVertexInputBindingDescription getBindingDescription() { VkVertexInputBindingDescription bindingDescription = {}; bindingDescription.binding = 0; bindingDescription.stride = sizeof(Vertex); bindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; return bindingDescription; } static std::array getAttributeDescriptions() { std::array attributeDescriptions = {}; attributeDescriptions[0].binding = 0; attributeDescriptions[0].location = 0; attributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; attributeDescriptions[0].offset = 0; attributeDescriptions[1].binding = 0; attributeDescriptions[1].location = 1; attributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; attributeDescriptions[1].offset = offsetof(Vertex, color); attributeDescriptions[2].binding = 0; attributeDescriptions[2].location = 2; attributeDescriptions[2].format = VK_FORMAT_R32G32_SFLOAT; attributeDescriptions[2].offset = offsetof(Vertex, texCoord); return attributeDescriptions; } bool operator==(const Vertex& other) const { return pos == other.pos && color == other.color && texCoord == other.texCoord; } }; namespace std { template<> struct hash { size_t operator()(Vertex const& vertex) const { return ((hash()(vertex.pos) ^ (hash()(vertex.color) << 1)) >> 1) ^ (hash()(vertex.texCoord) << 1); } }; } /* // For non-model-loading const std::vector vertices = { {{-0.5f, -0.5f, 0.0f}, {1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, {{ 0.5f, -0.5f, 0.0f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, {{ 0.5f, 0.5f, 0.0f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, {{-0.5f, 0.5f, 0.0f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f}}, {{-0.5f, -0.5f, -0.5f}, {1.0f, 0.0f, 1.0f}, {1.0f, 0.0f}}, {{ 0.5f, -0.5f, -0.5f}, {0.0f, 1.0f, 0.0f}, {0.0f, 0.0f}}, {{ 0.5f, 0.5f, -0.5f}, {0.0f, 0.0f, 1.0f}, {0.0f, 1.0f}}, {{-0.5f, 0.5f, -0.5f}, {1.0f, 0.0f, 0.0f}, {1.0f, 1.0f}} }; const std::vector indices = {0, 1, 2, 2, 3, 0, 4, 5, 6, 6, 7, 4}; */ std::vector vertices; std::vector indices; bool checkValidationLayerSupport() { uint32_t layerCount = 0; vkEnumerateInstanceLayerProperties(&layerCount, 0); VkLayerProperties availableLayers[layerCount]; vkEnumerateInstanceLayerProperties(&layerCount, availableLayers); std::cout << CYAN("Available Validation Layers") << " (" << layerCount << " total):\n"; for(const auto& layerProperties : availableLayers) std::cout << '\t' << layerProperties.layerName << std::endl; for (const char* layerName : validationLayers){ bool layerFound = false; for(const auto& layerProperties : availableLayers) if(!strcmp(layerName, layerProperties.layerName)){ layerFound = true; break; } if(!layerFound){ return false; } } return true; } std::vector getRequiredExtensions(){ uint32_t glfwExtensionCount = 0; const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); std::vector extensions(glfwExtensions, glfwExtensions + glfwExtensionCount); if(enableValidationLayers) extensions.push_back("VK_EXT_debug_utils"); return extensions; } VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks *pAllocator, VkDebugUtilsMessengerEXT *pDebugMessenger){ auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT"); if(func) return func(instance, pCreateInfo, pAllocator, pDebugMessenger); return VK_ERROR_EXTENSION_NOT_PRESENT; } void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks *pAllocator){ auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT"); if(func) func(instance, debugMessenger, pAllocator); } struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector formats; std::vector presentModes; }; std::vector readFile(const std::string& filename){ std::ifstream file(filename, std::ios::ate | std::ios::binary); if(!file.is_open()) throw std::runtime_error("failed to open file"); size_t fileSize = (size_t) file.tellg(); std::vector buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; } #include void check_vk_result(VkResult err){ if(!err) return; printf("Error Received: %d\n", err); throw std::runtime_error("Received error from imlib"); } class HelloTriangleApplication { public: void run() { initWindow(); initVulkan(); initImGui(); mainLoop(); cleanup(); } private: GLFWwindow *window; VkSurfaceKHR surface; VkInstance instance; VkDebugUtilsMessengerEXT debugMessenger; VkDevice device; VkQueue graphicsQueue; VkQueue presentQueue; VkPhysicalDevice physicalDevice; VkSwapchainKHR swapChain; std::vector swapChainImages; std::vector swapChainImageViews; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; VkRenderPass renderPass; VkDescriptorSetLayout descriptorSetLayout; VkPipelineLayout pipelineLayout; VkPipeline graphicsPipeline; std::vector swapChainFramebuffers; VkCommandPool commandPool; std::vector commandBuffers; std::vector guiCommandBuffers; VkSemaphore imageAvailableSemaphores[MAX_FRAMES_IN_FLIGHT]; VkSemaphore renderFinishedSemaphores[MAX_FRAMES_IN_FLIGHT]; VkFence inFlightFences[MAX_FRAMES_IN_FLIGHT]; bool framebufferResized = false; VkBuffer vertexBuffer; VkDeviceMemory vertexBufferMemory; VkBuffer indexBuffer; VkDeviceMemory indexBufferMemory; std::vector uniformBuffers; std::vector uniformBuffersMemory; VkDescriptorPool descriptorPool; std::vector descriptorSets; VkImage textureImage; VkDeviceMemory textureImageMemory; VkImageView textureImageView; VkSampler textureSampler; VkImage depthImage; VkDeviceMemory depthImageMemory; VkImageView depthImageView; uint32_t mipLevels; VkSampleCountFlagBits msaaSamples = VK_SAMPLE_COUNT_8_BIT; VkImage colorImage; VkDeviceMemory colorImageMemory; VkImageView colorImageView; VkSurfaceFormatKHR surfaceFormat; VkPresentModeKHR presentMode; ImGui_ImplVulkanH_WindowData windowData; ImGui_ImplVulkanH_WindowData *wd; void initImGui() { ImGui::CreateContext(); ImGuiIO& io = ImGui::GetIO(); (void)io; // ImGui::StyleColorsDark(); ImGui_ImplGlfw_InitForVulkan(window, true); ImGui_ImplVulkan_InitInfo init_info = {}; init_info.Instance = instance; init_info.PhysicalDevice = physicalDevice; init_info.Device = device; init_info.QueueFamily = findQueueFamilies(physicalDevice); init_info.Queue = graphicsQueue; init_info.PipelineCache = 0; // Can be 0 apparently init_info.DescriptorPool = descriptorPool; init_info.Allocator = 0; init_info.CheckVkResultFn = check_vk_result; ImGui_ImplVulkan_Init(&init_info, renderPass, msaaSamples); // Font setup VkCommandBuffer command_buffer = beginSingleTimeCommands(); ImGui_ImplVulkan_CreateFontsTexture(command_buffer); endSingleTimeCommands(command_buffer); ImGui_ImplVulkan_InvalidateFontUploadObjects(); } void initWindow() { glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", 0, 0); glfwSetWindowUserPointer(window, this); glfwSetFramebufferSizeCallback(window, framebufferResizeCallback); } static void framebufferResizeCallback(GLFWwindow *window, int width, int height){ auto app = reinterpret_cast(glfwGetWindowUserPointer(window)); app->framebufferResized = true; } void createInstance() { VkApplicationInfo appInfo = {}; appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; appInfo.pApplicationName = "Hello Triangle"; appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.pEngineName = "No Engine"; appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); appInfo.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &appInfo; auto requiredExtensions = getRequiredExtensions(); createInfo.enabledExtensionCount = static_cast(requiredExtensions.size()); createInfo.ppEnabledExtensionNames = requiredExtensions.data(); if(enableValidationLayers){ std::cout << CYAN("Validation Requested, checking available layers\n"); if (!checkValidationLayerSupport()) throw std::runtime_error("validation layers requested, but not available!"); createInfo.enabledLayerCount = sizeof(validationLayers)/sizeof(char*); createInfo.ppEnabledLayerNames = validationLayers; } else { createInfo.enabledLayerCount = 0; } if(vkCreateInstance(&createInfo, 0, &instance)) throw std::runtime_error("failed to create instance"); uint32_t extensionCount = 0; vkEnumerateInstanceExtensionProperties(0, &extensionCount, 0); VkExtensionProperties extensions[extensionCount]; vkEnumerateInstanceExtensionProperties(0, &extensionCount, extensions); std::cout << CYAN("Available Extensions: \n"); for(const auto& extension : extensions) std::cout << "\t" << extension.extensionName << '\n'; } static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback( VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void *pUserData) { std::cerr << YELLOW("validation layer: ") << pCallbackData->pMessage << std::endl; return VK_FALSE; } void setupDebugMessenger(){ 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; createInfo.pUserData = 0; if(CreateDebugUtilsMessengerEXT(instance, &createInfo, 0, &debugMessenger)) throw std::runtime_error("failed to set up debug messenger!"); } bool checkDeviceExtensionSupport(VkPhysicalDevice device){ uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, 0, &extensionCount, 0); VkExtensionProperties availableExtensions[extensionCount]; vkEnumerateDeviceExtensionProperties(device, 0, &extensionCount, availableExtensions); for(auto re : deviceExtensions){ bool found = false; for(auto ae : availableExtensions) if(!strcmp(re, ae.extensionName)){ found = true; break; } if(!found) return false; } return true; } bool isDeviceSuitable(VkPhysicalDevice device){ VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(device, &deviceProperties); VkPhysicalDeviceFeatures deviceFeatures; vkGetPhysicalDeviceFeatures(device, &deviceFeatures); std::cout << GREEN("Found Device: ") << deviceProperties.deviceName << std::endl; bool swapChainAdequate = false; if(checkDeviceExtensionSupport(device)){ SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } return findQueueFamilies(device) != -1 && swapChainAdequate && deviceFeatures.samplerAnisotropy; // Implied checkDeviceExtensionSupport returned true } void pickPhysicalDevice(){ physicalDevice = VK_NULL_HANDLE; uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, 0); if(!deviceCount) throw std::runtime_error("failed to find GPUs with Vulkan support!"); VkPhysicalDevice devices[deviceCount]; vkEnumeratePhysicalDevices(instance, &deviceCount, devices); for(const auto& device : devices){ if(isDeviceSuitable(device)) { physicalDevice = device; msaaSamples = getMaxUsableSampleCount(); } } if(physicalDevice == VK_NULL_HANDLE) throw std::runtime_error("failed to find a suitable GPU!"); } int32_t findQueueFamilies(VkPhysicalDevice device){ int32_t index = -1; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, 0); VkQueueFamilyProperties queueFamilies[queueFamilyCount]; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies); int i = 0; bool foundPresent = false; for(const auto& queueFamily : queueFamilies){ if(queueFamily.queueCount > 0 && queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT){ index = i; } VkBool32 presentSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); if(queueFamily.queueCount > 0 && presentSupport) if(index != -1){ foundPresent = true; break; } i++; } if(!foundPresent) throw std::runtime_error("failed to find a queue for both graphics and present"); return index; } void createLogicalDevice(){ int32_t index = findQueueFamilies(physicalDevice); VkDeviceQueueCreateInfo queueCreateInfo = {}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = index; queueCreateInfo.queueCount = 1; float queuePriority = 1.0f; queueCreateInfo.pQueuePriorities = &queuePriority; VkPhysicalDeviceFeatures deviceFeatures = {}; deviceFeatures.samplerAnisotropy = VK_TRUE; VkDeviceCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.pQueueCreateInfos = &queueCreateInfo; createInfo.queueCreateInfoCount = 1; createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = 1; createInfo.ppEnabledExtensionNames = deviceExtensions; if(enableValidationLayers){ createInfo.enabledLayerCount = sizeof(validationLayers) / sizeof(char*); createInfo.ppEnabledLayerNames = validationLayers; } else { createInfo.enabledLayerCount = 0; } if(vkCreateDevice(physicalDevice, &createInfo, 0, &device)) throw std::runtime_error("failed to create logical device!"); vkGetDeviceQueue(device, index, 0, &graphicsQueue); vkGetDeviceQueue(device, index, 0, &presentQueue); } SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device){ SwapChainSupportDetails details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, 0); if(formatCount != 0){ details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); } uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, 0); if(presentModeCount != 0){ details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); } return details; } VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector& availableFormats){ if(availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) return {VK_FORMAT_B8G8R8A8_UNORM, VK_COLOR_SPACE_SRGB_NONLINEAR_KHR}; for(const auto& availableFormat : availableFormats) if(availableFormat.format == VK_FORMAT_B8G8R8A8_UNORM && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) return availableFormat; return availableFormats[0]; } VkPresentModeKHR chooseSwapPresentMode(const std::vector& availablePresentModes){ VkPresentModeKHR bestMode = VK_PRESENT_MODE_FIFO_KHR; for(const auto& availablePresentMode : availablePresentModes) if(availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) return availablePresentMode; else if(availablePresentMode == VK_PRESENT_MODE_IMMEDIATE_KHR) bestMode = availablePresentMode; return bestMode; } VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities){ if(capabilities.currentExtent.width != std::numeric_limits::max()) return capabilities.currentExtent; else { int width, height; glfwGetFramebufferSize(window, &width, &height); VkExtent2D actual = {(uint32_t)width, (uint32_t)height}; actual.width = std::max(capabilities.minImageExtent.width, std::min(capabilities.maxImageExtent.width, actual.width)); actual.height = std::max(capabilities.minImageExtent.height, std::min(capabilities.maxImageExtent.height, actual.height)); return actual; } } void createSurface(){ if(glfwCreateWindowSurface(instance, window, 0, &surface)) throw std::runtime_error("failed to create window surface"); } void createSwapChain(){ SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1; if(swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) imageCount = swapChainSupport.capabilities.maxImageCount; VkSwapchainCreateInfoKHR createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; createInfo.surface = 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; // NOTE: This line only supports using the same queue family for graphics and presentation createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; 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(device, &createInfo, 0, &swapChain)) throw std::runtime_error("failed to create swap chain"); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, 0); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; } VkImageView createImageView(VkImage image, VkFormat format, VkImageAspectFlags aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT){ return createImageViewMip(image, format, 1, aspectFlags); } VkImageView createImageViewMip(VkImage image, VkFormat format, uint32_t mipLevels, VkImageAspectFlags aspectFlags = VK_IMAGE_ASPECT_COLOR_BIT){ VkImageView retval; VkImageViewCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = image; createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; createInfo.format = format; 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 = aspectFlags; createInfo.subresourceRange.baseMipLevel = 0; createInfo.subresourceRange.levelCount = mipLevels; createInfo.subresourceRange.baseArrayLayer = 0; createInfo.subresourceRange.layerCount = 1; if(vkCreateImageView(device, &createInfo, 0, &retval)) throw std::runtime_error("failed to create image views!"); return retval; } void createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for(size_t i = 0; i < swapChainImages.size(); i++) { swapChainImageViews[i] = createImageView(swapChainImages[i], swapChainImageFormat); } } VkShaderModule createShaderModule(const std::vector& code) { VkShaderModuleCreateInfo createInfo = {}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); createInfo.pCode = reinterpret_cast(code.data()); VkShaderModule shaderModule; if(vkCreateShaderModule(device, &createInfo, 0, &shaderModule)) throw std::runtime_error("failed to create shader module!"); return shaderModule; } void createRenderPass(){ VkAttachmentDescription colorAttachment = {}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = msaaSamples; colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentReference colorAttachmentRef = {}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkAttachmentDescription depthAttachment = {}; depthAttachment.format = findDepthFormat(); depthAttachment.samples = msaaSamples; depthAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; depthAttachment.storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depthAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; depthAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; depthAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; depthAttachment.finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentReference depthAttachmentRef = {}; depthAttachmentRef.attachment = 1; depthAttachmentRef.layout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL; VkAttachmentDescription colorAttachmentResolve = {}; colorAttachmentResolve.format = swapChainImageFormat; colorAttachmentResolve.samples = VK_SAMPLE_COUNT_1_BIT; colorAttachmentResolve.loadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachmentResolve.storeOp = VK_ATTACHMENT_STORE_OP_STORE; colorAttachmentResolve.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; colorAttachmentResolve.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; colorAttachmentResolve.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachmentResolve.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; VkAttachmentReference colorAttachmentResolveRef = {}; colorAttachmentResolveRef.attachment = 2; colorAttachmentResolveRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; VkSubpassDescription subpass = {}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; subpass.pDepthStencilAttachment = &depthAttachmentRef; subpass.pResolveAttachments = &colorAttachmentResolveRef; VkSubpassDependency dependency = {}; dependency.srcSubpass = VK_SUBPASS_EXTERNAL; dependency.dstSubpass = 0; dependency.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.srcAccessMask = 0; dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; VkAttachmentDescription attachments[] = {colorAttachment, depthAttachment, colorAttachmentResolve}; VkRenderPassCreateInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 3; renderPassInfo.pAttachments = attachments; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; if(vkCreateRenderPass(device, &renderPassInfo, 0, &renderPass)) throw std::runtime_error("failed to create render pass!"); } void createGraphicsPipeline(){ auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); VkShaderModule vertShaderModule = createShaderModule(vertShaderCode); VkShaderModule fragShaderModule = createShaderModule(fragShaderCode); VkPipelineShaderStageCreateInfo vertShaderStageInfo = {}; vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; vertShaderStageInfo.module = vertShaderModule; vertShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo fragShaderStageInfo = {}; fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; fragShaderStageInfo.module = fragShaderModule; fragShaderStageInfo.pName = "main"; VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo}; VkPipelineVertexInputStateCreateInfo vertexInputInfo = {}; auto bindingDescription = Vertex::getBindingDescription(); auto attributeDescriptions = Vertex::getAttributeDescriptions(); vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 1; vertexInputInfo.pVertexBindingDescriptions = &bindingDescription; vertexInputInfo.vertexAttributeDescriptionCount = (uint32_t)attributeDescriptions.size(); vertexInputInfo.pVertexAttributeDescriptions = attributeDescriptions.data(); VkPipelineInputAssemblyStateCreateInfo inputAssembly = {}; inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; inputAssembly.primitiveRestartEnable = VK_FALSE; VkViewport viewport = {}; viewport.x = 0.0f; viewport.y = 0.0f; viewport.width = (float) swapChainExtent.width; viewport.height = (float) swapChainExtent.height; viewport.minDepth = 0.0f; viewport.maxDepth = 1.0f; VkRect2D scissor = {}; scissor.offset = {0, 0}; scissor.extent = swapChainExtent; VkPipelineViewportStateCreateInfo viewportState = {}; viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; viewportState.viewportCount = 1; viewportState.pViewports = &viewport; viewportState.scissorCount = 1; viewportState.pScissors = &scissor; VkPipelineRasterizationStateCreateInfo rasterizer = {}; 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_COUNTER_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; rasterizer.depthBiasConstantFactor = 0.0f; rasterizer.depthBiasClamp = 0.0f; rasterizer.depthBiasSlopeFactor = 1.0f; VkPipelineMultisampleStateCreateInfo multisampling = {}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = msaaSamples; multisampling.minSampleShading = 1.0f; multisampling.pSampleMask = 0; multisampling.alphaToCoverageEnable = VK_FALSE; multisampling.alphaToOneEnable = VK_FALSE; VkPipelineColorBlendAttachmentState colorBlendAttachment = {}; 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_TRUE; colorBlendAttachment.srcColorBlendFactor = VK_BLEND_FACTOR_SRC_ALPHA; colorBlendAttachment.dstColorBlendFactor = VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA; colorBlendAttachment.colorBlendOp = VK_BLEND_OP_ADD; colorBlendAttachment.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; colorBlendAttachment.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; colorBlendAttachment.alphaBlendOp = VK_BLEND_OP_ADD; VkPipelineColorBlendStateCreateInfo colorBlending = {}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.attachmentCount = 1; colorBlending.pAttachments = &colorBlendAttachment; // Maybe just for later VkDynamicState dynamicStates[] = {VK_DYNAMIC_STATE_VIEWPORT}; VkPipelineDynamicStateCreateInfo dynamicState = {}; dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO; dynamicState.dynamicStateCount = 1; dynamicState.pDynamicStates = dynamicStates; VkPipelineLayoutCreateInfo pipelineLayoutInfo = {}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; pipelineLayoutInfo.setLayoutCount = 1; pipelineLayoutInfo.pSetLayouts = &descriptorSetLayout; if(vkCreatePipelineLayout(device, &pipelineLayoutInfo, 0, &pipelineLayout)) throw std::runtime_error("failed to create pipeline layout!"); VkPipelineDepthStencilStateCreateInfo depthStencil = {}; depthStencil.sType = VK_STRUCTURE_TYPE_PIPELINE_DEPTH_STENCIL_STATE_CREATE_INFO; depthStencil.depthTestEnable = VK_TRUE; depthStencil.depthWriteEnable = VK_TRUE; depthStencil.depthCompareOp = VK_COMPARE_OP_LESS; depthStencil.depthBoundsTestEnable = VK_FALSE; depthStencil.stencilTestEnable = VK_FALSE; VkGraphicsPipelineCreateInfo pipelineInfo = {}; pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; pipelineInfo.stageCount = 2; pipelineInfo.pStages = shaderStages; pipelineInfo.pVertexInputState = &vertexInputInfo; pipelineInfo.pInputAssemblyState = &inputAssembly; pipelineInfo.pViewportState = &viewportState; pipelineInfo.pRasterizationState = &rasterizer; pipelineInfo.pMultisampleState = &multisampling; pipelineInfo.pDepthStencilState = &depthStencil; pipelineInfo.pColorBlendState = &colorBlending; pipelineInfo.layout = pipelineLayout; pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0; pipelineInfo.basePipelineHandle = VK_NULL_HANDLE; pipelineInfo.basePipelineIndex = -1; if(vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, 0, &graphicsPipeline)) throw std::runtime_error("failed to create graphics pipeline!"); vkDestroyShaderModule(device, fragShaderModule, 0); vkDestroyShaderModule(device, vertShaderModule, 0); } void createFramebuffers(){ swapChainFramebuffers.resize(swapChainImageViews.size()); for(size_t i = 0; i < swapChainImageViews.size(); i++){ VkImageView attachments[] = {colorImageView, depthImageView, swapChainImageViews[i]}; VkFramebufferCreateInfo framebufferInfo = {}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 3; framebufferInfo.pAttachments = attachments; framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; if(vkCreateFramebuffer(device, &framebufferInfo, 0, &swapChainFramebuffers[i])) throw std::runtime_error("failed to create framebuffer!"); } } void createCommandPool(){ VkCommandPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; poolInfo.queueFamilyIndex = findQueueFamilies(physicalDevice); poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; if(vkCreateCommandPool(device, &poolInfo, 0, &commandPool)) throw std::runtime_error("failed to create command pool!"); } void createCommandBuffers(){ commandBuffers.resize(swapChainFramebuffers.size()); guiCommandBuffers.resize(swapChainFramebuffers.size()); VkCommandBufferAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = (uint32_t) commandBuffers.size(); if(vkAllocateCommandBuffers(device, &allocInfo, commandBuffers.data())) throw std::runtime_error("failed to allocate command buffers!"); if(vkAllocateCommandBuffers(device, &allocInfo, guiCommandBuffers.data())) throw std::runtime_error("failed to allocate gui command buffers!"); for(size_t i = 0; i < swapChainFramebuffers.size(); i++){ } } void createSyncObjects(){ VkSemaphoreCreateInfo semaphoreInfo = {}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; VkFenceCreateInfo fenceInfo = {}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; for(size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++){ if(vkCreateSemaphore(device, &semaphoreInfo, 0, imageAvailableSemaphores+i) || vkCreateSemaphore(device, &semaphoreInfo, 0, renderFinishedSemaphores+i) || vkCreateFence(device, &fenceInfo, 0, inFlightFences+i)) throw std::runtime_error("failed to create semaphores!"); } } uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags properties){ VkPhysicalDeviceMemoryProperties memProperties; vkGetPhysicalDeviceMemoryProperties(physicalDevice, &memProperties); for(uint32_t i = 0; i < memProperties.memoryTypeCount; i++) if((typeFilter & (1 << i)) && (memProperties.memoryTypes[i].propertyFlags & properties) == properties) return i; throw std::runtime_error("failed to find suitable memory type!"); } void createBuffer(VkDeviceSize size, VkBufferUsageFlags usage, VkMemoryPropertyFlags properties, VkBuffer *buffer, VkDeviceMemory *bufferMemory) { VkBufferCreateInfo bufferInfo = {}; bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; bufferInfo.size = size; bufferInfo.usage = usage; bufferInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; if(vkCreateBuffer(device, &bufferInfo, 0, buffer)) throw std::runtime_error("failed to create vertex buffer!"); VkMemoryRequirements memRequirements; vkGetBufferMemoryRequirements(device, *buffer, &memRequirements); VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, properties); if(vkAllocateMemory(device, &allocInfo, 0, bufferMemory)) throw std::runtime_error("failed to allocate vertex buffer memory!"); vkBindBufferMemory(device, *buffer, *bufferMemory, 0); } void createUniformBuffers() { VkDeviceSize bufferSize = sizeof(UniformBufferObject); uniformBuffers.resize(swapChainImages.size()); uniformBuffersMemory.resize(swapChainImages.size()); for(size_t i = 0; i < swapChainImages.size(); i++){ createBuffer(bufferSize, VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &uniformBuffers[i], &uniformBuffersMemory[i]); } } void createVertexBuffer(){ VkDeviceSize vBufferSize = sizeof(vertices[0]) * vertices.size(); VkDeviceSize iBufferSize = sizeof(indices[0]) * indices.size(); VkDeviceSize stagingBufferSize = (vBufferSize > iBufferSize)? vBufferSize : iBufferSize; VkBuffer stagingBuffer; VkDeviceMemory stagingBufferMemory; createBuffer(stagingBufferSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory); cpuIntoBufferMemory(stagingBufferMemory, vertices.data(), (size_t)vBufferSize); createBuffer(vBufferSize, VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &vertexBuffer, &vertexBufferMemory); bufcpy(vertexBuffer, stagingBuffer, vBufferSize); cpuIntoBufferMemory(stagingBufferMemory, indices.data(), iBufferSize); createBuffer(iBufferSize, VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &indexBuffer, &indexBufferMemory); bufcpy(indexBuffer, stagingBuffer, iBufferSize); vkDestroyBuffer(device, stagingBuffer, 0); vkFreeMemory(device, stagingBufferMemory, 0); } /* * This function is not a really efficient way of doing things. * It would be better to keep a command queue around, and not make a new one each time * Then commands could be buffered until flushed, allow async operations. */ VkCommandBuffer beginSingleTimeCommands(){ VkCommandBufferAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandPool = commandPool; allocInfo.commandBufferCount = 1; VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer); VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; vkBeginCommandBuffer(commandBuffer, &beginInfo); return commandBuffer; } void endSingleTimeCommands(VkCommandBuffer commandBuffer){ vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); vkQueueWaitIdle(graphicsQueue); vkFreeCommandBuffers(device, commandPool, 1, &commandBuffer); } void bufcpy(VkBuffer dst, VkBuffer src, VkDeviceSize size) { VkCommandBuffer cmdBuffer = beginSingleTimeCommands(); VkBufferCopy copyRegion = {}; copyRegion.size = size; vkCmdCopyBuffer(cmdBuffer, src, dst, 1, ©Region); endSingleTimeCommands(cmdBuffer); } void createDescriptorSetLayout(){ VkDescriptorSetLayoutBinding uboLayoutBinding = {}; uboLayoutBinding.binding = 0; uboLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; uboLayoutBinding.descriptorCount = 1; uboLayoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; uboLayoutBinding.pImmutableSamplers = 0; VkDescriptorSetLayoutBinding samplerLayoutBinding = {}; samplerLayoutBinding.binding = 1; samplerLayoutBinding.descriptorCount = 1; samplerLayoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; samplerLayoutBinding.pImmutableSamplers = 0; samplerLayoutBinding.stageFlags = VK_SHADER_STAGE_FRAGMENT_BIT; VkDescriptorSetLayoutBinding bindings[] = {uboLayoutBinding, samplerLayoutBinding}; VkDescriptorSetLayoutCreateInfo layoutInfo = {}; layoutInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; layoutInfo.bindingCount = sizeof(bindings) / sizeof(bindings[0]); layoutInfo.pBindings = bindings; if(vkCreateDescriptorSetLayout(device, &layoutInfo, 0, &descriptorSetLayout)) throw std::runtime_error("failed to create descriptor set layout!"); } void createDescriptorPool(){ /* Big TODO here: * Figure out how many descriptor sets we actually need! */ VkDescriptorPoolSize pool_sizes[] = { { VK_DESCRIPTOR_TYPE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1000 }, { VK_DESCRIPTOR_TYPE_SAMPLED_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_IMAGE, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_TEXEL_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER, 1000 }, { VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_STORAGE_BUFFER_DYNAMIC, 1000 }, { VK_DESCRIPTOR_TYPE_INPUT_ATTACHMENT, 1000 } }; VkDescriptorPoolCreateInfo pool_info = {}; pool_info.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; pool_info.flags = VK_DESCRIPTOR_POOL_CREATE_FREE_DESCRIPTOR_SET_BIT; pool_info.maxSets = 1000 * IM_ARRAYSIZE(pool_sizes); pool_info.poolSizeCount = (uint32_t)IM_ARRAYSIZE(pool_sizes); pool_info.pPoolSizes = pool_sizes; /* VkDescriptorPoolSize poolSizes[3] = {}; poolSizes[0].type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; poolSizes[0].descriptorCount = (uint32_t)swapChainImages.size(); poolSizes[1].type = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; poolSizes[1].descriptorCount = poolSizes[0].descriptorCount; poolSizes[2] = {VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER, 1}; VkDescriptorPoolCreateInfo poolInfo = {}; poolInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; poolInfo.poolSizeCount = 3; poolInfo.pPoolSizes = poolSizes; poolInfo.maxSets = (uint32_t)swapChainImages.size(); */ if(vkCreateDescriptorPool(device, &pool_info, 0, &descriptorPool)) throw std::runtime_error("failed to create descriptor pool!"); } void createDescriptorSets(){ std::vector layouts(swapChainImages.size(), descriptorSetLayout); VkDescriptorSetAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; allocInfo.descriptorPool = descriptorPool; allocInfo.descriptorSetCount = (uint32_t)swapChainImages.size(); allocInfo.pSetLayouts = layouts.data(); descriptorSets.resize(swapChainImages.size()); if(vkAllocateDescriptorSets(device, &allocInfo, descriptorSets.data())) throw std::runtime_error("failed to allocate descriptor sets!"); for(size_t i = 0; i < swapChainImages.size(); i++){ VkDescriptorBufferInfo bufferInfo = {}; bufferInfo.buffer = uniformBuffers[i]; bufferInfo.offset = 0; bufferInfo.range = sizeof(UniformBufferObject); VkDescriptorImageInfo imageInfo = {}; imageInfo.imageLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; imageInfo.imageView = textureImageView; imageInfo.sampler = textureSampler; VkWriteDescriptorSet descriptorWrites[2] = {}; descriptorWrites[0].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[0].dstSet = descriptorSets[i]; descriptorWrites[0].dstBinding = 0; descriptorWrites[0].dstArrayElement = 0; descriptorWrites[0].descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; descriptorWrites[0].descriptorCount = 1; descriptorWrites[0].pBufferInfo = &bufferInfo; descriptorWrites[0].pImageInfo = 0; descriptorWrites[0].pTexelBufferView = 0; descriptorWrites[1].sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; descriptorWrites[1].dstSet = descriptorSets[i]; descriptorWrites[1].dstBinding = 1; descriptorWrites[1].dstArrayElement = 0; descriptorWrites[1].descriptorType = VK_DESCRIPTOR_TYPE_COMBINED_IMAGE_SAMPLER; descriptorWrites[1].descriptorCount = 1; descriptorWrites[1].pImageInfo = &imageInfo; vkUpdateDescriptorSets(device, 2, descriptorWrites, 0, 0); } } void createTextureImage() { int texWidth, texHeight, texChannels; stbi_uc *pixels = stbi_load(TEXTURE_PATH, &texWidth, &texHeight, &texChannels, STBI_rgb_alpha); VkDeviceSize imageSize = texWidth * texHeight * 4; if(!pixels) throw std::runtime_error("failed to load image texture"); mipLevels = (uint32_t)std::floor(std::log2(std::max(texWidth, texHeight))) + 1; VkBuffer stagingBuffer; VkDeviceMemory stagingBufferMemory; createBuffer(imageSize, VK_BUFFER_USAGE_TRANSFER_SRC_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, &stagingBuffer, &stagingBufferMemory); cpuIntoBufferMemory(stagingBufferMemory, pixels, (size_t)imageSize); stbi_image_free(pixels); createImage(texWidth, texHeight, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, textureImage, textureImageMemory, mipLevels); transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, mipLevels); copyBufferToImage(stagingBuffer, textureImage, (uint32_t)texWidth, (uint32_t)texHeight); // For mipmaps this is no longer used, transition happens in generateMipmaps // transitionImageLayout(textureImage, VK_FORMAT_R8G8B8A8_UNORM, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL, mipLevels); generateMipmaps(textureImage, VK_FORMAT_R8G8B8A8_UNORM, texWidth, texHeight, mipLevels); vkDestroyBuffer(device, stagingBuffer, 0); vkFreeMemory(device, stagingBufferMemory, 0); } void generateMipmaps(VkImage image, VkFormat imageFormat, int32_t texWidth, int32_t texHeight, uint32_t mipLevels){ VkFormatProperties formatProperties; vkGetPhysicalDeviceFormatProperties(physicalDevice, imageFormat, &formatProperties); if(!(formatProperties.optimalTilingFeatures & VK_FORMAT_FEATURE_SAMPLED_IMAGE_FILTER_LINEAR_BIT)) throw std::runtime_error("texture image format does not support linear blitting!"); VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkImageMemoryBarrier barrier = {}; barrier.image = image; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; barrier.subresourceRange.levelCount = 1; int32_t mipWidth = texWidth; int32_t mipHeight = texHeight; for(uint32_t i = 1; i < mipLevels; i++){ barrier.subresourceRange.baseMipLevel = i-1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_TRANSFER_READ_BIT; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_TRANSFER_BIT, 0, 0, 0, 0, 0, 1, &barrier); VkImageBlit blit = {}; blit.srcOffsets[0] = {0, 0, 0}; blit.srcOffsets[1] = {mipWidth, mipHeight, 1}; blit.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; blit.srcSubresource.mipLevel = i-1; blit.srcSubresource.baseArrayLayer = 0; blit.srcSubresource.layerCount = 1; blit.dstOffsets[0] = {0, 0, 0}; blit.dstOffsets[1] = {mipWidth > 1 ? mipWidth/2 : 1, mipHeight > 1 ? mipHeight/2 : 1, 1}; blit.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; blit.dstSubresource.mipLevel = i; blit.dstSubresource.baseArrayLayer = 0; blit.dstSubresource.layerCount = 1; vkCmdBlitImage(commandBuffer, image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, &blit, VK_FILTER_LINEAR); barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_READ_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, 0, 0, 0, 1, &barrier); if(mipWidth > 1) mipWidth /= 2; if(mipHeight > 1) mipHeight /= 2; } barrier.subresourceRange.baseMipLevel = mipLevels - 1; barrier.oldLayout = VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL; barrier.newLayout = VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL; barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; vkCmdPipelineBarrier(commandBuffer, VK_PIPELINE_STAGE_TRANSFER_BIT, VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT, 0, 0, 0, 0, 0, 1, &barrier); endSingleTimeCommands(commandBuffer); } void transitionImageLayout(VkImage image, VkFormat format, VkImageLayout oldLayout, VkImageLayout newLayout, uint32_t mipLevels = 1){ VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkImageMemoryBarrier barrier = {}; barrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; barrier.oldLayout = oldLayout; barrier.newLayout = newLayout; barrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; barrier.image = image; barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; barrier.subresourceRange.baseMipLevel = 0; barrier.subresourceRange.levelCount = mipLevels; barrier.subresourceRange.baseArrayLayer = 0; barrier.subresourceRange.layerCount = 1; if(newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL){ barrier.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT; if(hasStencilComponent(format)) barrier.subresourceRange.aspectMask |= VK_IMAGE_ASPECT_STENCIL_BIT; } VkPipelineStageFlags srcStage, dstStage; if(oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL){ barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dstStage = VK_PIPELINE_STAGE_TRANSFER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL && newLayout == VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL){ barrier.srcAccessMask = VK_ACCESS_TRANSFER_WRITE_BIT; barrier.dstAccessMask = VK_ACCESS_SHADER_READ_BIT; srcStage = VK_PIPELINE_STAGE_TRANSFER_BIT; dstStage = VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL){ barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT | VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT; srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dstStage = VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT; } else if (oldLayout == VK_IMAGE_LAYOUT_UNDEFINED && newLayout == VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL){ barrier.srcAccessMask = 0; barrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; srcStage = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; dstStage = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT; } else throw std::invalid_argument("unsupported layout transition"); vkCmdPipelineBarrier(commandBuffer, srcStage, dstStage, 0, 0, 0, 0, 0, 1, &barrier); endSingleTimeCommands(commandBuffer); } void copyBufferToImage(VkBuffer buffer, VkImage image, uint32_t width, uint32_t height){ VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkBufferImageCopy region = {}; region.bufferOffset = 0; region.bufferRowLength = 0; region.bufferImageHeight = 0; region.imageSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; region.imageSubresource.mipLevel = 0; region.imageSubresource.baseArrayLayer = 0; region.imageSubresource.layerCount = 1; region.imageOffset = {0, 0, 0}; region.imageExtent = {width, height, 1}; vkCmdCopyBufferToImage(commandBuffer, buffer, image, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL, 1, ®ion); endSingleTimeCommands(commandBuffer); } void createImage(uint32_t width, uint32_t height, VkFormat format, VkImageTiling tiling, VkImageUsageFlags usage, VkMemoryPropertyFlags properties, VkImage &image, VkDeviceMemory &imageMemory, uint32_t mipLevels = 1, VkSampleCountFlagBits numSamples = VK_SAMPLE_COUNT_1_BIT){ VkImageCreateInfo imageInfo = {}; imageInfo.sType = VK_STRUCTURE_TYPE_IMAGE_CREATE_INFO; imageInfo.imageType = VK_IMAGE_TYPE_2D; imageInfo.extent.width = (uint32_t)width; imageInfo.extent.height = (uint32_t)height; imageInfo.extent.depth = 1; imageInfo.mipLevels = mipLevels; imageInfo.arrayLayers = 1; imageInfo.format = format; imageInfo.tiling = tiling; imageInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; imageInfo.usage = usage; imageInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE; imageInfo.samples = numSamples; imageInfo.flags = 0; if(vkCreateImage(device, &imageInfo, 0, &image)) throw std::runtime_error("failed to create image!"); VkMemoryRequirements memRequirements; vkGetImageMemoryRequirements(device, image, &memRequirements); VkMemoryAllocateInfo allocInfo = {}; allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; allocInfo.allocationSize = memRequirements.size; allocInfo.memoryTypeIndex = findMemoryType(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT); if(vkAllocateMemory(device, &allocInfo, 0, &imageMemory)) throw std::runtime_error("failed to allocate image memory!"); vkBindImageMemory(device, image, imageMemory, 0); } void createTextureImageView(){ VkImageViewCreateInfo viewInfo = {}; viewInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; viewInfo.image = textureImage; viewInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; viewInfo.format = VK_FORMAT_R8G8B8A8_UNORM; viewInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; viewInfo.subresourceRange.baseMipLevel = 0; viewInfo.subresourceRange.levelCount = mipLevels; viewInfo.subresourceRange.baseArrayLayer = 0; viewInfo.subresourceRange.layerCount = 1; if(vkCreateImageView(device, &viewInfo, 0, &textureImageView)) throw std::runtime_error("failed to create texture image view!"); } void createTextureSampler() { VkSamplerCreateInfo samplerInfo = {}; samplerInfo.sType = VK_STRUCTURE_TYPE_SAMPLER_CREATE_INFO; samplerInfo.magFilter = VK_FILTER_LINEAR; samplerInfo.minFilter = VK_FILTER_LINEAR; samplerInfo.addressModeU = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeV = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.addressModeW = VK_SAMPLER_ADDRESS_MODE_REPEAT; samplerInfo.anisotropyEnable = VK_TRUE; samplerInfo.maxAnisotropy = 16; samplerInfo.borderColor = VK_BORDER_COLOR_INT_OPAQUE_BLACK; samplerInfo.unnormalizedCoordinates = VK_FALSE; samplerInfo.compareEnable = VK_FALSE; samplerInfo.compareOp = VK_COMPARE_OP_ALWAYS; samplerInfo.mipmapMode = VK_SAMPLER_MIPMAP_MODE_LINEAR; samplerInfo.mipLodBias = 0.0f; samplerInfo.minLod = 0; samplerInfo.maxLod = mipLevels; if(vkCreateSampler(device, &samplerInfo, 0, &textureSampler)) throw std::runtime_error("failed to create texture sampler!"); } VkFormat findSupportedFormat(const std::vector& candidates, VkImageTiling tiling, VkFormatFeatureFlags features){ for(VkFormat format : candidates){ VkFormatProperties props; vkGetPhysicalDeviceFormatProperties(physicalDevice, format, &props); if(tiling == VK_IMAGE_TILING_LINEAR && (props.linearTilingFeatures & features) == features) return format; else if(tiling == VK_IMAGE_TILING_OPTIMAL && (props.optimalTilingFeatures & features) == features) return format; } throw std::runtime_error("failed to find supported format!"); } VkFormat findDepthFormat() { return findSupportedFormat({VK_FORMAT_D32_SFLOAT, VK_FORMAT_D32_SFLOAT_S8_UINT, VK_FORMAT_D24_UNORM_S8_UINT}, VK_IMAGE_TILING_OPTIMAL, VK_FORMAT_FEATURE_DEPTH_STENCIL_ATTACHMENT_BIT); } bool hasStencilComponent(VkFormat format){ return format == VK_FORMAT_D32_SFLOAT_S8_UINT | format == VK_FORMAT_D24_UNORM_S8_UINT; } void createDepthResources(){ VkFormat depthFormat = findDepthFormat(); createImage(swapChainExtent.width, swapChainExtent.height, depthFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, depthImage, depthImageMemory, 1, msaaSamples); depthImageView = createImageView(depthImage, depthFormat, VK_IMAGE_ASPECT_DEPTH_BIT); transitionImageLayout(depthImage, depthFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL); } void loadModel(){ tinyobj::attrib_t attrib; std::vector shapes; std::vector materials; std::string warn, err; if(!tinyobj::LoadObj(&attrib, &shapes, &materials, &err, MODEL_PATH)) throw std::runtime_error(err); std::unordered_map uniqueVertices = {}; for(const auto& shape : shapes){ for(const auto& index : shape.mesh.indices){ Vertex vertex = {}; vertex.pos = { attrib.vertices[3 * index.vertex_index + 0], attrib.vertices[3 * index.vertex_index + 1], attrib.vertices[3 * index.vertex_index + 2] }; vertex.texCoord = { attrib.texcoords[2 * index.texcoord_index + 0], 1.0f - attrib.texcoords[2 * index.texcoord_index + 1] }; vertex.color = {1.0f, 1.0f, 1.0f}; if(uniqueVertices.count(vertex) == 0){ uniqueVertices[vertex] = (uint32_t)vertices.size(); vertices.push_back(vertex); } indices.push_back(uniqueVertices[vertex]); } } } VkSampleCountFlagBits getMaxUsableSampleCount() { VkPhysicalDeviceProperties pdp = {}; vkGetPhysicalDeviceProperties(physicalDevice, &pdp); VkSampleCountFlags counts = std::min(pdp.limits.framebufferColorSampleCounts, pdp.limits.framebufferDepthSampleCounts); if(counts & VK_SAMPLE_COUNT_64_BIT) return VK_SAMPLE_COUNT_64_BIT; if(counts & VK_SAMPLE_COUNT_32_BIT) return VK_SAMPLE_COUNT_32_BIT; if(counts & VK_SAMPLE_COUNT_16_BIT) return VK_SAMPLE_COUNT_16_BIT; if(counts & VK_SAMPLE_COUNT_8_BIT) return VK_SAMPLE_COUNT_8_BIT; if(counts & VK_SAMPLE_COUNT_4_BIT) return VK_SAMPLE_COUNT_4_BIT; if(counts & VK_SAMPLE_COUNT_2_BIT) return VK_SAMPLE_COUNT_2_BIT; return VK_SAMPLE_COUNT_1_BIT; } void createColorResources(){ VkFormat colorFormat = swapChainImageFormat; createImage(swapChainExtent.width, swapChainExtent.height, colorFormat, VK_IMAGE_TILING_OPTIMAL, VK_IMAGE_USAGE_TRANSIENT_ATTACHMENT_BIT | VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, colorImage, colorImageMemory, 1, msaaSamples); colorImageView = createImageView(colorImage, colorFormat, VK_IMAGE_ASPECT_COLOR_BIT); transitionImageLayout(colorImage, colorFormat, VK_IMAGE_LAYOUT_UNDEFINED, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL); } void initVulkan() { createInstance(); if(enableValidationLayers) setupDebugMessenger(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createSwapChain(); createImageViews(); createRenderPass(); createDescriptorSetLayout(); createCommandPool(); createGraphicsPipeline(); createColorResources(); createDepthResources(); createFramebuffers(); createTextureImage(); createTextureImageView(); createTextureSampler(); loadModel(); createVertexBuffer(); // And index buffer createUniformBuffers(); createDescriptorPool(); createDescriptorSets(); createCommandBuffers(); createSyncObjects(); } void recreateSwapChain() { int width = 0, height = 0; while(width == 0 || height == 0){ glfwGetFramebufferSize(window, &width, &height); glfwWaitEvents(); } vkDeviceWaitIdle(device); cleanupSwapChain(); createSwapChain(); createImageViews(); createRenderPass(); createGraphicsPipeline(); createColorResources(); createDepthResources(); createFramebuffers(); createCommandBuffers(); } size_t currentFrame = 0; bool reverse = false; bool duplicate = false; bool upsideDown = false; float speed = 1.0f; void drawFrame(){ static bool show_demo_window = true; ImGui_ImplVulkan_NewFrame(); ImGui_ImplGlfw_NewFrame(); ImGui::NewFrame(); ImGui::ShowDemoWindow(&show_demo_window); ImGui::Begin("Rotation"); ImGui::Checkbox("Reverse", &reverse); ImGui::Text("(%.1f FPS)", ImGui::GetIO().Framerate); ImGui::SliderFloat("speed", &speed, 0.0f, 10.0f); ImGui::Checkbox("Display extra little houses", &duplicate); ImGui::Checkbox("Upside Down", &upsideDown); ImGui::End(); ImGui::Render(); uint32_t imageIndex; vkWaitForFences(device, 1, inFlightFences + currentFrame, VK_TRUE, std::numeric_limits::max()); VkResult result = vkAcquireNextImageKHR(device, swapChain, 0xFFFFFFFFFFFFFFFF, imageAvailableSemaphores[currentFrame], VK_NULL_HANDLE, &imageIndex); if(result == VK_ERROR_OUT_OF_DATE_KHR){ recreateSwapChain(); return; } else if(result != VK_SUCCESS && result != VK_SUBOPTIMAL_KHR) throw std::runtime_error("failed to acquire swap chain images!"); vkResetFences(device, 1, inFlightFences + currentFrame); updateUniformBuffer(imageIndex); // The indented bit following came from the loop in createCommandBuffers // It has to be here though, because ImGui::GetDrawData() must run each frame // Since that affects the command recorded, they have to be re-recorded each frame // Maybe there's a way to pre-record some of them or something. uint32_t i = imageIndex; VkCommandBufferBeginInfo beginInfo = {}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; beginInfo.pInheritanceInfo = 0; if(vkBeginCommandBuffer(commandBuffers[i], &beginInfo)) throw std::runtime_error("failed to begin recording command buffer!"); VkRenderPassBeginInfo renderPassInfo = {}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[i]; renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = swapChainExtent; renderPassInfo.clearValueCount = 2; VkClearValue clearValues[2] = {}; clearValues[0].color = {0.0f, 0.0f, 0.0f, 1.0f}; clearValues[1].depthStencil = {1.0f, 0}; renderPassInfo.pClearValues = clearValues; vkCmdBeginRenderPass(commandBuffers[i], &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); VkDeviceSize offset = 0; vkCmdBindVertexBuffers(commandBuffers[i], 0, 1, &vertexBuffer, &offset); vkCmdBindIndexBuffer(commandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); vkCmdBindDescriptorSets(commandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSets[i], 0, 0); vkCmdDrawIndexed(commandBuffers[i], (uint32_t)indices.size(), duplicate? 5:1, 0, 0, 0); ImGui_ImplVulkan_RenderDrawData(ImGui::GetDrawData(), commandBuffers[i]); vkCmdEndRenderPass(commandBuffers[i]); if(vkEndCommandBuffer(commandBuffers[i])) throw std::runtime_error("failed to record command buffer"); VkSubmitInfo submitInfo = {}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; VkSemaphore waitSemaphores[] = {imageAvailableSemaphores[currentFrame]}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffers[imageIndex]; VkSemaphore signalSemaphores[] = {renderFinishedSemaphores[currentFrame]}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; if(vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFences[currentFrame])) throw std::runtime_error("failed to submit draw command buffer!"); // vkQueueWaitIdle(graphicsQueue); // TODO: Don't really do this VkPresentInfoKHR presentInfo = {}; presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; presentInfo.waitSemaphoreCount = 1; presentInfo.pWaitSemaphores = signalSemaphores; VkSwapchainKHR swapChains[] = {swapChain}; presentInfo.swapchainCount = 1; presentInfo.pSwapchains = swapChains; presentInfo.pImageIndices = &imageIndex; presentInfo.pResults = 0; result = vkQueuePresentKHR(presentQueue, &presentInfo); if(result == VK_ERROR_OUT_OF_DATE_KHR || result == VK_SUBOPTIMAL_KHR || framebufferResized){ framebufferResized = false; recreateSwapChain(); } else if(result) throw std::runtime_error("failed to present swap chain image!"); currentFrame = (currentFrame+1) % MAX_FRAMES_IN_FLIGHT; } #define PI 3.141592653f void updateUniformBuffer(uint32_t currentImage) { static auto startTime = std::chrono::high_resolution_clock::now(); static float time = 0.0f; auto currentTime = std::chrono::high_resolution_clock::now(); float adjustment = speed * std::chrono::duration(currentTime - startTime).count(); if(reverse) time += adjustment; else time -= adjustment; UniformBufferObject ubo = {}; ubo.model = glm::mat4(1.0f); if(upsideDown) ubo.model = glm::rotate(ubo.model, PI, glm::vec3(0.0, 1.0, 0.0)); if(duplicate) ubo.model = glm::translate(ubo.model, glm::vec3(0.0, 0.0, -1.0)); ubo.model = glm::rotate(ubo.model, time * PI/8, glm::vec3(0.0f, 0.0f, 1.0f)); ubo.view = glm::lookAt(glm::vec3(2.0f, 2.0f, 2.0f), glm::vec3(0.0f, 0.0f, 0.0f), glm::vec3(0.0f, 0.0f, 1.0f)); ubo.proj = glm::perspective(PI/4, swapChainExtent.width / (float)swapChainExtent.height, 0.1f, 10.0f); ubo.proj[1][1] *= -1; cpuIntoBufferMemory(uniformBuffersMemory[currentImage], &ubo, sizeof(ubo)); startTime = std::chrono::high_resolution_clock::now(); } void cpuIntoBufferMemory(VkDeviceMemory bufmem, const void *srcdata, size_t size){ void *data; vkMapMemory(device, bufmem, 0, size, 0, &data); memcpy(data, srcdata, size); vkUnmapMemory(device, bufmem); } void mainLoop() { while(!glfwWindowShouldClose(window)) { glfwPollEvents(); drawFrame(); // vkQueueWaitIdle(presentQueue);// This is not a very good idea, because we could start on the next frame } vkDeviceWaitIdle(device); } void cleanupSwapChain() { vkDestroyImageView(device, colorImageView, 0); vkDestroyImage(device, colorImage, 0); vkFreeMemory(device, colorImageMemory, 0); vkDestroyImageView(device, depthImageView, 0); vkDestroyImage(device, depthImage, 0); vkFreeMemory(device, depthImageMemory, 0); for(auto i : swapChainFramebuffers) vkDestroyFramebuffer(device, i, 0); vkFreeCommandBuffers(device, commandPool, commandBuffers.size(), commandBuffers.data()); vkDestroyPipeline(device, graphicsPipeline, 0); vkDestroyPipelineLayout(device, pipelineLayout, 0); vkDestroyRenderPass(device, renderPass, 0); for(auto imageView : swapChainImageViews) vkDestroyImageView(device, imageView, 0); vkDestroySwapchainKHR(device, swapChain, 0); } void cleanup() { ImGui_ImplVulkan_Shutdown(); cleanupSwapChain(); vkDestroySampler(device, textureSampler, 0); vkDestroyImageView(device, textureImageView, 0); vkDestroyImage(device, textureImage, 0); vkFreeMemory(device, textureImageMemory, 0); vkDestroyDescriptorPool(device, descriptorPool, 0); vkDestroyDescriptorSetLayout(device, descriptorSetLayout, 0); vkDestroyBuffer(device, indexBuffer, 0); vkFreeMemory(device, indexBufferMemory, 0); vkDestroyBuffer(device, vertexBuffer, 0); vkFreeMemory(device, vertexBufferMemory, 0); for(size_t i = 0; i < swapChainImages.size(); i++){ vkDestroyBuffer(device, uniformBuffers[i], 0); vkFreeMemory(device, uniformBuffersMemory[i], 0); } for(size_t i = 0; i < MAX_FRAMES_IN_FLIGHT; i++){ vkDestroySemaphore(device, renderFinishedSemaphores[i], 0); vkDestroySemaphore(device, imageAvailableSemaphores[i], 0); vkDestroyFence(device, inFlightFences[i], 0); } vkDestroyCommandPool(device, commandPool, 0); vkDestroyDevice(device, 0); if(enableValidationLayers) DestroyDebugUtilsMessengerEXT(instance, debugMessenger, 0); vkDestroySurfaceKHR(instance, surface, 0); vkDestroyInstance(instance, 0); glfwDestroyWindow(window); glfwTerminate(); } }; int main(int argc, char ** argv) { HelloTriangleApplication app; if(argc > 1 && argv[1][1] == 'd') enableValidationLayers = true; try { app.run(); } catch (const std::exception& e) { std::cerr << RED(e.what()) << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }