-
-
Save Overv/7ac07356037592a121225172d7d78f2d to your computer and use it in GitHub Desktop.
| #include <iostream> | |
| #include <fstream> | |
| #include <vector> | |
| #include <string> | |
| #include <algorithm> | |
| #include <chrono> | |
| #include <functional> | |
| #define GLFW_INCLUDE_VULKAN | |
| #include <GLFW/glfw3.h> | |
| #include <glm/gtc/matrix_transform.hpp> | |
| using namespace std::placeholders; | |
| // Configuration | |
| const uint32_t WIDTH = 640; | |
| const uint32_t HEIGHT = 480; | |
| const bool ENABLE_DEBUGGING = false; | |
| const char* DEBUG_LAYER = "VK_LAYER_LUNARG_standard_validation"; | |
| // Debug callback | |
| VkBool32 debugCallback(VkDebugReportFlagsEXT flags, VkDebugReportObjectTypeEXT objType, uint64_t srcObject, size_t location, int32_t msgCode, const char* pLayerPrefix, const char* pMsg, void* pUserData) { | |
| if (flags & VK_DEBUG_REPORT_ERROR_BIT_EXT) { | |
| std::cerr << "ERROR: [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << std::endl; | |
| } else if (flags & VK_DEBUG_REPORT_WARNING_BIT_EXT) { | |
| std::cerr << "WARNING: [" << pLayerPrefix << "] Code " << msgCode << " : " << pMsg << std::endl; | |
| } | |
| //exit(1); | |
| return VK_FALSE; | |
| } | |
| // Vertex layout | |
| struct Vertex { | |
| float pos[3]; | |
| float color[3]; | |
| }; | |
| bool windowResized = false; | |
| // Note: support swap chain recreation (not only required for resized windows!) | |
| // Note: window resize may not result in Vulkan telling that the swap chain should be recreated, should be handled explicitly! | |
| class TriangleApplication { | |
| public: | |
| TriangleApplication() { | |
| timeStart = std::chrono::high_resolution_clock::now(); | |
| } | |
| void run() { | |
| // Note: dynamically loading loader may be a better idea to fail gracefully when Vulkan is not supported | |
| // Create window for Vulkan | |
| glfwInit(); | |
| glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); | |
| window = glfwCreateWindow(WIDTH, HEIGHT, "The spinning triangle that took 1397 lines of code", nullptr, nullptr); | |
| glfwSetWindowSizeCallback(window, TriangleApplication::onWindowResized); | |
| // Use Vulkan | |
| setupVulkan(); | |
| mainLoop(); | |
| cleanup(true); | |
| } | |
| private: | |
| GLFWwindow* window; | |
| VkInstance instance; | |
| VkSurfaceKHR windowSurface; | |
| VkPhysicalDevice physicalDevice; | |
| VkDevice device; | |
| VkDebugReportCallbackEXT callback; | |
| VkQueue graphicsQueue; | |
| VkQueue presentQueue; | |
| VkPhysicalDeviceMemoryProperties deviceMemoryProperties; | |
| VkSemaphore imageAvailableSemaphore; | |
| VkSemaphore renderingFinishedSemaphore; | |
| VkBuffer vertexBuffer; | |
| VkDeviceMemory vertexBufferMemory; | |
| VkBuffer indexBuffer; | |
| VkDeviceMemory indexBufferMemory; | |
| VkVertexInputBindingDescription vertexBindingDescription; | |
| std::vector<VkVertexInputAttributeDescription> vertexAttributeDescriptions; | |
| struct { | |
| glm::mat4 transformationMatrix; | |
| } uniformBufferData; | |
| VkBuffer uniformBuffer; | |
| VkDeviceMemory uniformBufferMemory; | |
| VkDescriptorSetLayout descriptorSetLayout; | |
| VkDescriptorPool descriptorPool; | |
| VkDescriptorSet descriptorSet; | |
| VkExtent2D swapChainExtent; | |
| VkFormat swapChainFormat; | |
| VkSwapchainKHR oldSwapChain; | |
| VkSwapchainKHR swapChain; | |
| std::vector<VkImage> swapChainImages; | |
| std::vector<VkImageView> swapChainImageViews; | |
| std::vector<VkFramebuffer> swapChainFramebuffers; | |
| VkRenderPass renderPass; | |
| VkPipeline graphicsPipeline; | |
| VkPipelineLayout pipelineLayout; | |
| VkCommandPool commandPool; | |
| std::vector<VkCommandBuffer> graphicsCommandBuffers; | |
| uint32_t graphicsQueueFamily; | |
| uint32_t presentQueueFamily; | |
| std::chrono::high_resolution_clock::time_point timeStart; | |
| void setupVulkan() { | |
| oldSwapChain = VK_NULL_HANDLE; | |
| createInstance(); | |
| createDebugCallback(); | |
| createWindowSurface(); | |
| findPhysicalDevice(); | |
| checkSwapChainSupport(); | |
| findQueueFamilies(); | |
| createLogicalDevice(); | |
| createSemaphores(); | |
| createCommandPool(); | |
| createVertexBuffer(); | |
| createUniformBuffer(); | |
| createSwapChain(); | |
| createRenderPass(); | |
| createImageViews(); | |
| createFramebuffers(); | |
| createGraphicsPipeline(); | |
| createDescriptorPool(); | |
| createDescriptorSet(); | |
| createCommandBuffers(); | |
| } | |
| void mainLoop() { | |
| while (!glfwWindowShouldClose(window)) { | |
| updateUniformData(); | |
| draw(); | |
| glfwPollEvents(); | |
| } | |
| } | |
| static void onWindowResized(GLFWwindow* window, int width, int height) { | |
| windowResized = true; | |
| } | |
| void onWindowSizeChanged() { | |
| windowResized = false; | |
| // Only recreate objects that are affected by framebuffer size changes | |
| cleanup(false); | |
| createSwapChain(); | |
| createRenderPass(); | |
| createImageViews(); | |
| createFramebuffers(); | |
| createGraphicsPipeline(); | |
| createCommandBuffers(); | |
| } | |
| void cleanup(bool fullClean) { | |
| vkDeviceWaitIdle(device); | |
| vkFreeCommandBuffers(device, commandPool, (uint32_t) graphicsCommandBuffers.size(), graphicsCommandBuffers.data()); | |
| vkDestroyPipeline(device, graphicsPipeline, nullptr); | |
| vkDestroyRenderPass(device, renderPass, nullptr); | |
| for (size_t i = 0; i < swapChainImages.size(); i++) { | |
| vkDestroyFramebuffer(device, swapChainFramebuffers[i], nullptr); | |
| vkDestroyImageView(device, swapChainImageViews[i], nullptr); | |
| } | |
| vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr); | |
| if (fullClean) { | |
| vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); | |
| vkDestroySemaphore(device, renderingFinishedSemaphore, nullptr); | |
| vkDestroyCommandPool(device, commandPool, nullptr); | |
| // Clean up uniform buffer related objects | |
| vkDestroyDescriptorPool(device, descriptorPool, nullptr); | |
| vkDestroyBuffer(device, uniformBuffer, nullptr); | |
| vkFreeMemory(device, uniformBufferMemory, nullptr); | |
| // Buffers must be destroyed after no command buffers are referring to them anymore | |
| vkDestroyBuffer(device, vertexBuffer, nullptr); | |
| vkFreeMemory(device, vertexBufferMemory, nullptr); | |
| vkDestroyBuffer(device, indexBuffer, nullptr); | |
| vkFreeMemory(device, indexBufferMemory, nullptr); | |
| // Note: implicitly destroys images (in fact, we're not allowed to do that explicitly) | |
| vkDestroySwapchainKHR(device, swapChain, nullptr); | |
| vkDestroyDevice(device, nullptr); | |
| vkDestroySurfaceKHR(instance, windowSurface, nullptr); | |
| if (ENABLE_DEBUGGING) { | |
| PFN_vkDestroyDebugReportCallbackEXT DestroyDebugReportCallback = (PFN_vkDestroyDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"); | |
| DestroyDebugReportCallback(instance, callback, nullptr); | |
| } | |
| vkDestroyInstance(instance, nullptr); | |
| } | |
| } | |
| void createInstance() { | |
| VkApplicationInfo appInfo = {}; | |
| appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; | |
| appInfo.pApplicationName = "VulkanClear"; | |
| appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); | |
| appInfo.pEngineName = "ClearScreenEngine"; | |
| appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); | |
| appInfo.apiVersion = VK_API_VERSION_1_0; | |
| // Get instance extensions required by GLFW to draw to window | |
| unsigned int glfwExtensionCount; | |
| const char** glfwExtensions; | |
| glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); | |
| std::vector<const char*> extensions; | |
| for (size_t i = 0; i < glfwExtensionCount; i++) { | |
| extensions.push_back(glfwExtensions[i]); | |
| } | |
| if (ENABLE_DEBUGGING) { | |
| extensions.push_back(VK_EXT_DEBUG_REPORT_EXTENSION_NAME); | |
| } | |
| // Check for extensions | |
| uint32_t extensionCount = 0; | |
| vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, nullptr); | |
| if (extensionCount == 0) { | |
| std::cerr << "no extensions supported!" << std::endl; | |
| exit(1); | |
| } | |
| std::vector<VkExtensionProperties> availableExtensions(extensionCount); | |
| vkEnumerateInstanceExtensionProperties(nullptr, &extensionCount, availableExtensions.data()); | |
| std::cout << "supported extensions:" << std::endl; | |
| for (const auto& extension : availableExtensions) { | |
| std::cout << "\t" << extension.extensionName << std::endl; | |
| } | |
| VkInstanceCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; | |
| createInfo.pApplicationInfo = &appInfo; | |
| createInfo.enabledExtensionCount = (uint32_t) extensions.size(); | |
| createInfo.ppEnabledExtensionNames = extensions.data(); | |
| if (ENABLE_DEBUGGING) { | |
| createInfo.enabledLayerCount = 1; | |
| createInfo.ppEnabledLayerNames = &DEBUG_LAYER; | |
| } | |
| // Initialize Vulkan instance | |
| if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { | |
| std::cerr << "failed to create instance!" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created vulkan instance" << std::endl; | |
| } | |
| } | |
| void createWindowSurface() { | |
| if (glfwCreateWindowSurface(instance, window, NULL, &windowSurface) != VK_SUCCESS) { | |
| std::cerr << "failed to create window surface!" << std::endl; | |
| exit(1); | |
| } | |
| std::cout << "created window surface" << std::endl; | |
| } | |
| void findPhysicalDevice() { | |
| // Try to find 1 Vulkan supported device | |
| // Note: perhaps refactor to loop through devices and find first one that supports all required features and extensions | |
| uint32_t deviceCount = 0; | |
| if (vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr) != VK_SUCCESS || deviceCount == 0) { | |
| std::cerr << "failed to get number of physical devices" << std::endl; | |
| exit(1); | |
| } | |
| deviceCount = 1; | |
| VkResult res = vkEnumeratePhysicalDevices(instance, &deviceCount, &physicalDevice); | |
| if (res != VK_SUCCESS && res != VK_INCOMPLETE) { | |
| std::cerr << "enumerating physical devices failed!" << std::endl; | |
| exit(1); | |
| } | |
| if (deviceCount == 0) { | |
| std::cerr << "no physical devices that support vulkan!" << std::endl; | |
| exit(1); | |
| } | |
| std::cout << "physical device with vulkan support found" << std::endl; | |
| // Check device features | |
| // Note: will apiVersion >= appInfo.apiVersion? Probably yes, but spec is unclear. | |
| VkPhysicalDeviceProperties deviceProperties; | |
| VkPhysicalDeviceFeatures deviceFeatures; | |
| vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); | |
| vkGetPhysicalDeviceFeatures(physicalDevice, &deviceFeatures); | |
| uint32_t supportedVersion[] = { | |
| VK_VERSION_MAJOR(deviceProperties.apiVersion), | |
| VK_VERSION_MINOR(deviceProperties.apiVersion), | |
| VK_VERSION_PATCH(deviceProperties.apiVersion) | |
| }; | |
| std::cout << "physical device supports version " << supportedVersion[0] << "." << supportedVersion[1] << "." << supportedVersion[2] << std::endl; | |
| } | |
| void checkSwapChainSupport() { | |
| uint32_t extensionCount = 0; | |
| vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, nullptr); | |
| if (extensionCount == 0) { | |
| std::cerr << "physical device doesn't support any extensions" << std::endl; | |
| exit(1); | |
| } | |
| std::vector<VkExtensionProperties> deviceExtensions(extensionCount); | |
| vkEnumerateDeviceExtensionProperties(physicalDevice, nullptr, &extensionCount, deviceExtensions.data()); | |
| for (const auto& extension : deviceExtensions) { | |
| if (strcmp(extension.extensionName, VK_KHR_SWAPCHAIN_EXTENSION_NAME) == 0) { | |
| std::cout << "physical device supports swap chains" << std::endl; | |
| return; | |
| } | |
| } | |
| std::cerr << "physical device doesn't support swap chains" << std::endl; | |
| exit(1); | |
| } | |
| void findQueueFamilies() { | |
| // Check queue families | |
| uint32_t queueFamilyCount = 0; | |
| vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr); | |
| if (queueFamilyCount == 0) { | |
| std::cout << "physical device has no queue families!" << std::endl; | |
| exit(1); | |
| } | |
| // Find queue family with graphics support | |
| // Note: is a transfer queue necessary to copy vertices to the gpu or can a graphics queue handle that? | |
| std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); | |
| vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilies.data()); | |
| std::cout << "physical device has " << queueFamilyCount << " queue families" << std::endl; | |
| bool foundGraphicsQueueFamily = false; | |
| bool foundPresentQueueFamily = false; | |
| for (uint32_t i = 0; i < queueFamilyCount; i++) { | |
| VkBool32 presentSupport = false; | |
| vkGetPhysicalDeviceSurfaceSupportKHR(physicalDevice, i, windowSurface, &presentSupport); | |
| if (queueFamilies[i].queueCount > 0 && queueFamilies[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) { | |
| graphicsQueueFamily = i; | |
| foundGraphicsQueueFamily = true; | |
| if (presentSupport) { | |
| presentQueueFamily = i; | |
| foundPresentQueueFamily = true; | |
| break; | |
| } | |
| } | |
| if (!foundPresentQueueFamily && presentSupport) { | |
| presentQueueFamily = i; | |
| foundPresentQueueFamily = true; | |
| } | |
| } | |
| if (foundGraphicsQueueFamily) { | |
| std::cout << "queue family #" << graphicsQueueFamily << " supports graphics" << std::endl; | |
| if (foundPresentQueueFamily) { | |
| std::cout << "queue family #" << presentQueueFamily << " supports presentation" << std::endl; | |
| } else { | |
| std::cerr << "could not find a valid queue family with present support" << std::endl; | |
| exit(1); | |
| } | |
| } else { | |
| std::cerr << "could not find a valid queue family with graphics support" << std::endl; | |
| exit(1); | |
| } | |
| } | |
| void createLogicalDevice() { | |
| // Greate one graphics queue and optionally a separate presentation queue | |
| float queuePriority = 1.0f; | |
| VkDeviceQueueCreateInfo queueCreateInfo[2] = {}; | |
| queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
| queueCreateInfo[0].queueFamilyIndex = graphicsQueueFamily; | |
| queueCreateInfo[0].queueCount = 1; | |
| queueCreateInfo[0].pQueuePriorities = &queuePriority; | |
| queueCreateInfo[0].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; | |
| queueCreateInfo[0].queueFamilyIndex = presentQueueFamily; | |
| queueCreateInfo[0].queueCount = 1; | |
| queueCreateInfo[0].pQueuePriorities = &queuePriority; | |
| // Create logical device from physical device | |
| // Note: there are separate instance and device extensions! | |
| VkDeviceCreateInfo deviceCreateInfo = {}; | |
| deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; | |
| deviceCreateInfo.pQueueCreateInfos = queueCreateInfo; | |
| if (graphicsQueueFamily == presentQueueFamily) { | |
| deviceCreateInfo.queueCreateInfoCount = 1; | |
| } else { | |
| deviceCreateInfo.queueCreateInfoCount = 2; | |
| } | |
| // Necessary for shader (for some reason) | |
| VkPhysicalDeviceFeatures enabledFeatures = {}; | |
| enabledFeatures.shaderClipDistance = VK_TRUE; | |
| enabledFeatures.shaderCullDistance = VK_TRUE; | |
| const char* deviceExtensions = VK_KHR_SWAPCHAIN_EXTENSION_NAME; | |
| deviceCreateInfo.enabledExtensionCount = 1; | |
| deviceCreateInfo.ppEnabledExtensionNames = &deviceExtensions; | |
| deviceCreateInfo.pEnabledFeatures = &enabledFeatures; | |
| if (ENABLE_DEBUGGING) { | |
| deviceCreateInfo.enabledLayerCount = 1; | |
| deviceCreateInfo.ppEnabledLayerNames = &DEBUG_LAYER; | |
| } | |
| if (vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device) != VK_SUCCESS) { | |
| std::cerr << "failed to create logical device" << std::endl; | |
| exit(1); | |
| } | |
| std::cout << "created logical device" << std::endl; | |
| // Get graphics and presentation queues (which may be the same) | |
| vkGetDeviceQueue(device, graphicsQueueFamily, 0, &graphicsQueue); | |
| vkGetDeviceQueue(device, presentQueueFamily, 0, &presentQueue); | |
| std::cout << "acquired graphics and presentation queues" << std::endl; | |
| vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties); | |
| } | |
| void createDebugCallback() { | |
| if (ENABLE_DEBUGGING) { | |
| VkDebugReportCallbackCreateInfoEXT createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT; | |
| createInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT) debugCallback; | |
| createInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT; | |
| PFN_vkCreateDebugReportCallbackEXT CreateDebugReportCallback = (PFN_vkCreateDebugReportCallbackEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"); | |
| if (CreateDebugReportCallback(instance, &createInfo, nullptr, &callback) != VK_SUCCESS) { | |
| std::cerr << "failed to create debug callback" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created debug callback" << std::endl; | |
| } | |
| } else { | |
| std::cout << "skipped creating debug callback" << std::endl; | |
| } | |
| } | |
| void createSemaphores() { | |
| VkSemaphoreCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; | |
| if (vkCreateSemaphore(device, &createInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || | |
| vkCreateSemaphore(device, &createInfo, nullptr, &renderingFinishedSemaphore) != VK_SUCCESS) { | |
| std::cerr << "failed to create semaphores" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created semaphores" << std::endl; | |
| } | |
| } | |
| void createCommandPool() { | |
| // Create graphics command pool | |
| VkCommandPoolCreateInfo poolCreateInfo = {}; | |
| poolCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; | |
| poolCreateInfo.queueFamilyIndex = graphicsQueueFamily; | |
| if (vkCreateCommandPool(device, &poolCreateInfo, nullptr, &commandPool) != VK_SUCCESS) { | |
| std::cerr << "failed to create command queue for graphics queue family" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created command pool for graphics queue family" << std::endl; | |
| } | |
| } | |
| void createVertexBuffer() { | |
| // Setup vertices | |
| std::vector<Vertex> vertices = { | |
| { { -0.5f, -0.5f, 0.0f }, { 1.0f, 0.0f, 0.0f } }, | |
| { { -0.5f, 0.5f, 0.0f }, { 0.0f, 1.0f, 0.0f } }, | |
| { { 0.5f, 0.5f, 0.0f }, { 0.0f, 0.0f, 1.0f } } | |
| }; | |
| uint32_t verticesSize = (uint32_t) (vertices.size() * sizeof(vertices[0])); | |
| // Setup indices | |
| std::vector<uint32_t> indices = { 0, 1, 2 }; | |
| uint32_t indicesSize = (uint32_t) (indices.size() * sizeof(indices[0])); | |
| VkMemoryAllocateInfo memAlloc = {}; | |
| memAlloc.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | |
| VkMemoryRequirements memReqs; | |
| void* data; | |
| struct StagingBuffer { | |
| VkDeviceMemory memory; | |
| VkBuffer buffer; | |
| }; | |
| struct { | |
| StagingBuffer vertices; | |
| StagingBuffer indices; | |
| } stagingBuffers; | |
| // Allocate command buffer for copy operation | |
| VkCommandBufferAllocateInfo cmdBufInfo = {}; | |
| cmdBufInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; | |
| cmdBufInfo.commandPool = commandPool; | |
| cmdBufInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; | |
| cmdBufInfo.commandBufferCount = 1; | |
| VkCommandBuffer copyCommandBuffer; | |
| vkAllocateCommandBuffers(device, &cmdBufInfo, ©CommandBuffer); | |
| // First copy vertices to host accessible vertex buffer memory | |
| VkBufferCreateInfo vertexBufferInfo = {}; | |
| vertexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
| vertexBufferInfo.size = verticesSize; | |
| vertexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; | |
| vkCreateBuffer(device, &vertexBufferInfo, nullptr, &stagingBuffers.vertices.buffer); | |
| vkGetBufferMemoryRequirements(device, stagingBuffers.vertices.buffer, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); | |
| vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.vertices.memory); | |
| vkMapMemory(device, stagingBuffers.vertices.memory, 0, verticesSize, 0, &data); | |
| memcpy(data, vertices.data(), verticesSize); | |
| vkUnmapMemory(device, stagingBuffers.vertices.memory); | |
| vkBindBufferMemory(device, stagingBuffers.vertices.buffer, stagingBuffers.vertices.memory, 0); | |
| // Then allocate a gpu only buffer for vertices | |
| vertexBufferInfo.usage = VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; | |
| vkCreateBuffer(device, &vertexBufferInfo, nullptr, &vertexBuffer); | |
| vkGetBufferMemoryRequirements(device, vertexBuffer, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex); | |
| vkAllocateMemory(device, &memAlloc, nullptr, &vertexBufferMemory); | |
| vkBindBufferMemory(device, vertexBuffer, vertexBufferMemory, 0); | |
| // Next copy indices to host accessible index buffer memory | |
| VkBufferCreateInfo indexBufferInfo = {}; | |
| indexBufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
| indexBufferInfo.size = indicesSize; | |
| indexBufferInfo.usage = VK_BUFFER_USAGE_TRANSFER_SRC_BIT; | |
| vkCreateBuffer(device, &indexBufferInfo, nullptr, &stagingBuffers.indices.buffer); | |
| vkGetBufferMemoryRequirements(device, stagingBuffers.indices.buffer, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &memAlloc.memoryTypeIndex); | |
| vkAllocateMemory(device, &memAlloc, nullptr, &stagingBuffers.indices.memory); | |
| vkMapMemory(device, stagingBuffers.indices.memory, 0, indicesSize, 0, &data); | |
| memcpy(data, indices.data(), indicesSize); | |
| vkUnmapMemory(device, stagingBuffers.indices.memory); | |
| vkBindBufferMemory(device, stagingBuffers.indices.buffer, stagingBuffers.indices.memory, 0); | |
| // And allocate another gpu only buffer for indices | |
| indexBufferInfo.usage = VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT; | |
| vkCreateBuffer(device, &indexBufferInfo, nullptr, &indexBuffer); | |
| vkGetBufferMemoryRequirements(device, indexBuffer, &memReqs); | |
| memAlloc.allocationSize = memReqs.size; | |
| getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, &memAlloc.memoryTypeIndex); | |
| vkAllocateMemory(device, &memAlloc, nullptr, &indexBufferMemory); | |
| vkBindBufferMemory(device, indexBuffer, indexBufferMemory, 0); | |
| // Now copy data from host visible buffer to gpu only buffer | |
| VkCommandBufferBeginInfo bufferBeginInfo = {}; | |
| bufferBeginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |
| bufferBeginInfo.flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT; | |
| vkBeginCommandBuffer(copyCommandBuffer, &bufferBeginInfo); | |
| VkBufferCopy copyRegion = {}; | |
| copyRegion.size = verticesSize; | |
| vkCmdCopyBuffer(copyCommandBuffer, stagingBuffers.vertices.buffer, vertexBuffer, 1, ©Region); | |
| copyRegion.size = indicesSize; | |
| vkCmdCopyBuffer(copyCommandBuffer, stagingBuffers.indices.buffer, indexBuffer, 1, ©Region); | |
| vkEndCommandBuffer(copyCommandBuffer); | |
| // Submit to queue | |
| 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); | |
| vkDestroyBuffer(device, stagingBuffers.vertices.buffer, nullptr); | |
| vkFreeMemory(device, stagingBuffers.vertices.memory, nullptr); | |
| vkDestroyBuffer(device, stagingBuffers.indices.buffer, nullptr); | |
| vkFreeMemory(device, stagingBuffers.indices.memory, nullptr); | |
| std::cout << "set up vertex and index buffers" << std::endl; | |
| // Binding and attribute descriptions | |
| vertexBindingDescription.binding = 0; | |
| vertexBindingDescription.stride = sizeof(vertices[0]); | |
| vertexBindingDescription.inputRate = VK_VERTEX_INPUT_RATE_VERTEX; | |
| // vec2 position | |
| vertexAttributeDescriptions.resize(2); | |
| vertexAttributeDescriptions[0].binding = 0; | |
| vertexAttributeDescriptions[0].location = 0; | |
| vertexAttributeDescriptions[0].format = VK_FORMAT_R32G32B32_SFLOAT; | |
| // vec3 color | |
| vertexAttributeDescriptions[1].binding = 0; | |
| vertexAttributeDescriptions[1].location = 1; | |
| vertexAttributeDescriptions[1].format = VK_FORMAT_R32G32B32_SFLOAT; | |
| vertexAttributeDescriptions[1].offset = sizeof(float) * 3; | |
| } | |
| void createUniformBuffer() { | |
| VkBufferCreateInfo bufferInfo = {}; | |
| bufferInfo.sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO; | |
| bufferInfo.size = sizeof(uniformBufferData); | |
| bufferInfo.usage = VK_BUFFER_USAGE_UNIFORM_BUFFER_BIT; | |
| vkCreateBuffer(device, &bufferInfo, nullptr, &uniformBuffer); | |
| VkMemoryRequirements memReqs; | |
| vkGetBufferMemoryRequirements(device, uniformBuffer, &memReqs); | |
| VkMemoryAllocateInfo allocInfo = {}; | |
| allocInfo.sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO; | |
| allocInfo.allocationSize = memReqs.size; | |
| getMemoryType(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT, &allocInfo.memoryTypeIndex); | |
| vkAllocateMemory(device, &allocInfo, nullptr, &uniformBufferMemory); | |
| vkBindBufferMemory(device, uniformBuffer, uniformBufferMemory, 0); | |
| updateUniformData(); | |
| } | |
| void updateUniformData() { | |
| // Rotate based on time | |
| auto timeNow = std::chrono::high_resolution_clock::now(); | |
| long long millis = std::chrono::duration_cast<std::chrono::milliseconds>(timeStart - timeNow).count(); | |
| float angle = (millis % 4000) / 4000.0f * glm::radians(360.f); | |
| glm::mat4 modelMatrix; | |
| modelMatrix = glm::rotate(modelMatrix, angle, glm::vec3(0, 0, 1)); | |
| modelMatrix = glm::translate(modelMatrix, glm::vec3(0.5f / 3.0f, -0.5f / 3.0f, 0.0f)); | |
| // Set up view | |
| auto viewMatrix = glm::lookAt(glm::vec3(1, 1, 1), glm::vec3(0, 0, 0), glm::vec3(0, 0, -1)); | |
| // Set up projection | |
| auto projMatrix = glm::perspective(glm::radians(70.f), swapChainExtent.width / (float) swapChainExtent.height, 0.1f, 10.0f); | |
| uniformBufferData.transformationMatrix = projMatrix * viewMatrix * modelMatrix; | |
| void* data; | |
| vkMapMemory(device, uniformBufferMemory, 0, sizeof(uniformBufferData), 0, &data); | |
| memcpy(data, &uniformBufferData, sizeof(uniformBufferData)); | |
| vkUnmapMemory(device, uniformBufferMemory); | |
| } | |
| // Find device memory that is supported by the requirements (typeBits) and meets the desired properties | |
| VkBool32 getMemoryType(uint32_t typeBits, VkFlags properties, uint32_t* typeIndex) { | |
| for (uint32_t i = 0; i < 32; i++) { | |
| if ((typeBits & 1) == 1) { | |
| if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) { | |
| *typeIndex = i; | |
| return true; | |
| } | |
| } | |
| typeBits >>= 1; | |
| } | |
| return false; | |
| } | |
| void createSwapChain() { | |
| // Find surface capabilities | |
| VkSurfaceCapabilitiesKHR surfaceCapabilities; | |
| if (vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, windowSurface, &surfaceCapabilities) != VK_SUCCESS) { | |
| std::cerr << "failed to acquire presentation surface capabilities" << std::endl; | |
| exit(1); | |
| } | |
| // Find supported surface formats | |
| uint32_t formatCount; | |
| if (vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, windowSurface, &formatCount, nullptr) != VK_SUCCESS || formatCount == 0) { | |
| std::cerr << "failed to get number of supported surface formats" << std::endl; | |
| exit(1); | |
| } | |
| std::vector<VkSurfaceFormatKHR> surfaceFormats(formatCount); | |
| if (vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, windowSurface, &formatCount, surfaceFormats.data()) != VK_SUCCESS) { | |
| std::cerr << "failed to get supported surface formats" << std::endl; | |
| exit(1); | |
| } | |
| // Find supported present modes | |
| uint32_t presentModeCount; | |
| if (vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, windowSurface, &presentModeCount, nullptr) != VK_SUCCESS || presentModeCount == 0) { | |
| std::cerr << "failed to get number of supported presentation modes" << std::endl; | |
| exit(1); | |
| } | |
| std::vector<VkPresentModeKHR> presentModes(presentModeCount); | |
| if (vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, windowSurface, &presentModeCount, presentModes.data()) != VK_SUCCESS) { | |
| std::cerr << "failed to get supported presentation modes" << std::endl; | |
| exit(1); | |
| } | |
| // Determine number of images for swap chain | |
| uint32_t imageCount = surfaceCapabilities.minImageCount + 1; | |
| if (surfaceCapabilities.maxImageCount != 0 && imageCount > surfaceCapabilities.maxImageCount) { | |
| imageCount = surfaceCapabilities.maxImageCount; | |
| } | |
| std::cout << "using " << imageCount << " images for swap chain" << std::endl; | |
| // Select a surface format | |
| VkSurfaceFormatKHR surfaceFormat = chooseSurfaceFormat(surfaceFormats); | |
| // Select swap chain size | |
| swapChainExtent = chooseSwapExtent(surfaceCapabilities); | |
| // Determine transformation to use (preferring no transform) | |
| VkSurfaceTransformFlagBitsKHR surfaceTransform; | |
| if (surfaceCapabilities.supportedTransforms & VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR) { | |
| surfaceTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR; | |
| } else { | |
| surfaceTransform = surfaceCapabilities.currentTransform; | |
| } | |
| // Choose presentation mode (preferring MAILBOX ~= triple buffering) | |
| VkPresentModeKHR presentMode = choosePresentMode(presentModes); | |
| // Finally, create the swap chain | |
| VkSwapchainCreateInfoKHR createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR; | |
| createInfo.surface = windowSurface; | |
| createInfo.minImageCount = imageCount; | |
| createInfo.imageFormat = surfaceFormat.format; | |
| createInfo.imageColorSpace = surfaceFormat.colorSpace; | |
| createInfo.imageExtent = swapChainExtent; | |
| createInfo.imageArrayLayers = 1; | |
| createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; | |
| createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; | |
| createInfo.queueFamilyIndexCount = 0; | |
| createInfo.pQueueFamilyIndices = nullptr; | |
| createInfo.preTransform = surfaceTransform; | |
| createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; | |
| createInfo.presentMode = presentMode; | |
| createInfo.clipped = VK_TRUE; | |
| createInfo.oldSwapchain = oldSwapChain; | |
| if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { | |
| std::cerr << "failed to create swap chain" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created swap chain" << std::endl; | |
| } | |
| if (oldSwapChain != VK_NULL_HANDLE) { | |
| vkDestroySwapchainKHR(device, oldSwapChain, nullptr); | |
| } | |
| oldSwapChain = swapChain; | |
| swapChainFormat = surfaceFormat.format; | |
| // Store the images used by the swap chain | |
| // Note: these are the images that swap chain image indices refer to | |
| // Note: actual number of images may differ from requested number, since it's a lower bound | |
| uint32_t actualImageCount = 0; | |
| if (vkGetSwapchainImagesKHR(device, swapChain, &actualImageCount, nullptr) != VK_SUCCESS || actualImageCount == 0) { | |
| std::cerr << "failed to acquire number of swap chain images" << std::endl; | |
| exit(1); | |
| } | |
| swapChainImages.resize(actualImageCount); | |
| if (vkGetSwapchainImagesKHR(device, swapChain, &actualImageCount, swapChainImages.data()) != VK_SUCCESS) { | |
| std::cerr << "failed to acquire swap chain images" << std::endl; | |
| exit(1); | |
| } | |
| std::cout << "acquired swap chain images" << std::endl; | |
| } | |
| VkSurfaceFormatKHR chooseSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) { | |
| // We can either choose any format | |
| if (availableFormats.size() == 1 && availableFormats[0].format == VK_FORMAT_UNDEFINED) { | |
| return{ VK_FORMAT_R8G8B8A8_UNORM, VK_COLORSPACE_SRGB_NONLINEAR_KHR }; | |
| } | |
| // Or go with the standard format - if available | |
| for (const auto& availableSurfaceFormat : availableFormats) { | |
| if (availableSurfaceFormat.format == VK_FORMAT_R8G8B8A8_UNORM) { | |
| return availableSurfaceFormat; | |
| } | |
| } | |
| // Or fall back to the first available one | |
| return availableFormats[0]; | |
| } | |
| VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& surfaceCapabilities) { | |
| if (surfaceCapabilities.currentExtent.width == -1) { | |
| VkExtent2D swapChainExtent = {}; | |
| swapChainExtent.width = std::min(std::max(WIDTH, surfaceCapabilities.minImageExtent.width), surfaceCapabilities.maxImageExtent.width); | |
| swapChainExtent.height = std::min(std::max(HEIGHT, surfaceCapabilities.minImageExtent.height), surfaceCapabilities.maxImageExtent.height); | |
| return swapChainExtent; | |
| } else { | |
| return surfaceCapabilities.currentExtent; | |
| } | |
| } | |
| VkPresentModeKHR choosePresentMode(const std::vector<VkPresentModeKHR> presentModes) { | |
| for (const auto& presentMode : presentModes) { | |
| if (presentMode == VK_PRESENT_MODE_MAILBOX_KHR) { | |
| return presentMode; | |
| } | |
| } | |
| // If mailbox is unavailable, fall back to FIFO (guaranteed to be available) | |
| return VK_PRESENT_MODE_FIFO_KHR; | |
| } | |
| void createRenderPass() { | |
| VkAttachmentDescription attachmentDescription = {}; | |
| attachmentDescription.format = swapChainFormat; | |
| attachmentDescription.samples = VK_SAMPLE_COUNT_1_BIT; | |
| attachmentDescription.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; | |
| attachmentDescription.storeOp = VK_ATTACHMENT_STORE_OP_STORE; | |
| attachmentDescription.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE; | |
| attachmentDescription.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE; | |
| attachmentDescription.initialLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
| attachmentDescription.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
| // Note: hardware will automatically transition attachment to the specified layout | |
| // Note: index refers to attachment descriptions array | |
| VkAttachmentReference colorAttachmentReference = {}; | |
| colorAttachmentReference.attachment = 0; | |
| colorAttachmentReference.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; | |
| // Note: this is a description of how the attachments of the render pass will be used in this sub pass | |
| // e.g. if they will be read in shaders and/or drawn to | |
| VkSubpassDescription subPassDescription = {}; | |
| subPassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; | |
| subPassDescription.colorAttachmentCount = 1; | |
| subPassDescription.pColorAttachments = &colorAttachmentReference; | |
| // Create the render pass | |
| VkRenderPassCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; | |
| createInfo.attachmentCount = 1; | |
| createInfo.pAttachments = &attachmentDescription; | |
| createInfo.subpassCount = 1; | |
| createInfo.pSubpasses = &subPassDescription; | |
| if (vkCreateRenderPass(device, &createInfo, nullptr, &renderPass) != VK_SUCCESS) { | |
| std::cerr << "failed to create render pass" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created render pass" << std::endl; | |
| } | |
| } | |
| void createImageViews() { | |
| swapChainImageViews.resize(swapChainImages.size()); | |
| // Create an image view for every image in the swap chain | |
| for (size_t i = 0; i < swapChainImages.size(); i++) { | |
| VkImageViewCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; | |
| createInfo.image = swapChainImages[i]; | |
| createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; | |
| createInfo.format = swapChainFormat; | |
| createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY; | |
| createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY; | |
| createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY; | |
| createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY; | |
| createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
| createInfo.subresourceRange.baseMipLevel = 0; | |
| createInfo.subresourceRange.levelCount = 1; | |
| createInfo.subresourceRange.baseArrayLayer = 0; | |
| createInfo.subresourceRange.layerCount = 1; | |
| if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) { | |
| std::cerr << "failed to create image view for swap chain image #" << i << std::endl; | |
| exit(1); | |
| } | |
| } | |
| std::cout << "created image views for swap chain images" << std::endl; | |
| } | |
| void createFramebuffers() { | |
| swapChainFramebuffers.resize(swapChainImages.size()); | |
| // Note: Framebuffer is basically a specific choice of attachments for a render pass | |
| // That means all attachments must have the same dimensions, interesting restriction | |
| for (size_t i = 0; i < swapChainImages.size(); i++) { | |
| VkFramebufferCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; | |
| createInfo.renderPass = renderPass; | |
| createInfo.attachmentCount = 1; | |
| createInfo.pAttachments = &swapChainImageViews[i]; | |
| createInfo.width = swapChainExtent.width; | |
| createInfo.height = swapChainExtent.height; | |
| createInfo.layers = 1; | |
| if (vkCreateFramebuffer(device, &createInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { | |
| std::cerr << "failed to create framebuffer for swap chain image view #" << i << std::endl; | |
| exit(1); | |
| } | |
| } | |
| std::cout << "created framebuffers for swap chain image views" << std::endl; | |
| } | |
| VkShaderModule createShaderModule(const std::string& filename) { | |
| std::ifstream file(filename, std::ios::ate | std::ios::binary); | |
| std::vector<char> fileBytes(file.tellg()); | |
| file.seekg(0, std::ios::beg); | |
| file.read(fileBytes.data(), fileBytes.size()); | |
| file.close(); | |
| VkShaderModuleCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; | |
| createInfo.codeSize = fileBytes.size(); | |
| createInfo.pCode = (uint32_t*) fileBytes.data(); | |
| VkShaderModule shaderModule; | |
| if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { | |
| std::cerr << "failed to create shader module for " << filename << std::endl; | |
| exit(1); | |
| } | |
| std::cout << "created shader module for " << filename << std::endl; | |
| return shaderModule; | |
| } | |
| void createGraphicsPipeline() { | |
| VkShaderModule vertexShaderModule = createShaderModule("shaders/vert.spv"); | |
| VkShaderModule fragmentShaderModule = createShaderModule("shaders/frag.spv"); | |
| // Set up shader stage info | |
| VkPipelineShaderStageCreateInfo vertexShaderCreateInfo = {}; | |
| vertexShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
| vertexShaderCreateInfo.stage = VK_SHADER_STAGE_VERTEX_BIT; | |
| vertexShaderCreateInfo.module = vertexShaderModule; | |
| vertexShaderCreateInfo.pName = "main"; | |
| VkPipelineShaderStageCreateInfo fragmentShaderCreateInfo = {}; | |
| fragmentShaderCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO; | |
| fragmentShaderCreateInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT; | |
| fragmentShaderCreateInfo.module = fragmentShaderModule; | |
| fragmentShaderCreateInfo.pName = "main"; | |
| VkPipelineShaderStageCreateInfo shaderStages[] = { vertexShaderCreateInfo, fragmentShaderCreateInfo }; | |
| // Describe vertex input | |
| VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {}; | |
| vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; | |
| vertexInputCreateInfo.vertexBindingDescriptionCount = 1; | |
| vertexInputCreateInfo.pVertexBindingDescriptions = &vertexBindingDescription; | |
| vertexInputCreateInfo.vertexAttributeDescriptionCount = 2; | |
| vertexInputCreateInfo.pVertexAttributeDescriptions = vertexAttributeDescriptions.data(); | |
| // Describe input assembly | |
| VkPipelineInputAssemblyStateCreateInfo inputAssemblyCreateInfo = {}; | |
| inputAssemblyCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO; | |
| inputAssemblyCreateInfo.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST; | |
| inputAssemblyCreateInfo.primitiveRestartEnable = VK_FALSE; | |
| // Describe viewport and scissor | |
| 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.x = 0; | |
| scissor.offset.y = 0; | |
| scissor.extent.width = swapChainExtent.width; | |
| scissor.extent.height = swapChainExtent.height; | |
| // Note: scissor test is always enabled (although dynamic scissor is possible) | |
| // Number of viewports must match number of scissors | |
| VkPipelineViewportStateCreateInfo viewportCreateInfo = {}; | |
| viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO; | |
| viewportCreateInfo.viewportCount = 1; | |
| viewportCreateInfo.pViewports = &viewport; | |
| viewportCreateInfo.scissorCount = 1; | |
| viewportCreateInfo.pScissors = &scissor; | |
| // Describe rasterization | |
| // Note: depth bias and using polygon modes other than fill require changes to logical device creation (device features) | |
| VkPipelineRasterizationStateCreateInfo rasterizationCreateInfo = {}; | |
| rasterizationCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO; | |
| rasterizationCreateInfo.depthClampEnable = VK_FALSE; | |
| rasterizationCreateInfo.rasterizerDiscardEnable = VK_FALSE; | |
| rasterizationCreateInfo.polygonMode = VK_POLYGON_MODE_FILL; | |
| rasterizationCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT; | |
| rasterizationCreateInfo.frontFace = VK_FRONT_FACE_COUNTER_CLOCKWISE; | |
| rasterizationCreateInfo.depthBiasEnable = VK_FALSE; | |
| rasterizationCreateInfo.depthBiasConstantFactor = 0.0f; | |
| rasterizationCreateInfo.depthBiasClamp = 0.0f; | |
| rasterizationCreateInfo.depthBiasSlopeFactor = 0.0f; | |
| rasterizationCreateInfo.lineWidth = 1.0f; | |
| // Describe multisampling | |
| // Note: using multisampling also requires turning on device features | |
| VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {}; | |
| multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; | |
| multisampleCreateInfo.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; | |
| multisampleCreateInfo.sampleShadingEnable = VK_FALSE; | |
| multisampleCreateInfo.minSampleShading = 1.0f; | |
| multisampleCreateInfo.alphaToCoverageEnable = VK_FALSE; | |
| multisampleCreateInfo.alphaToOneEnable = VK_FALSE; | |
| // Describing color blending | |
| // Note: all paramaters except blendEnable and colorWriteMask are irrelevant here | |
| VkPipelineColorBlendAttachmentState colorBlendAttachmentState = {}; | |
| colorBlendAttachmentState.blendEnable = VK_FALSE; | |
| colorBlendAttachmentState.srcColorBlendFactor = VK_BLEND_FACTOR_ONE; | |
| colorBlendAttachmentState.dstColorBlendFactor = VK_BLEND_FACTOR_ZERO; | |
| colorBlendAttachmentState.colorBlendOp = VK_BLEND_OP_ADD; | |
| colorBlendAttachmentState.srcAlphaBlendFactor = VK_BLEND_FACTOR_ONE; | |
| colorBlendAttachmentState.dstAlphaBlendFactor = VK_BLEND_FACTOR_ZERO; | |
| colorBlendAttachmentState.alphaBlendOp = VK_BLEND_OP_ADD; | |
| colorBlendAttachmentState.colorWriteMask = VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT; | |
| // Note: all attachments must have the same values unless a device feature is enabled | |
| VkPipelineColorBlendStateCreateInfo colorBlendCreateInfo = {}; | |
| colorBlendCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; | |
| colorBlendCreateInfo.logicOpEnable = VK_FALSE; | |
| colorBlendCreateInfo.logicOp = VK_LOGIC_OP_COPY; | |
| colorBlendCreateInfo.attachmentCount = 1; | |
| colorBlendCreateInfo.pAttachments = &colorBlendAttachmentState; | |
| colorBlendCreateInfo.blendConstants[0] = 0.0f; | |
| colorBlendCreateInfo.blendConstants[1] = 0.0f; | |
| colorBlendCreateInfo.blendConstants[2] = 0.0f; | |
| colorBlendCreateInfo.blendConstants[3] = 0.0f; | |
| // Describe pipeline layout | |
| // Note: this describes the mapping between memory and shader resources (descriptor sets) | |
| // This is for uniform buffers and samplers | |
| VkDescriptorSetLayoutBinding layoutBinding = {}; | |
| layoutBinding.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
| layoutBinding.descriptorCount = 1; | |
| layoutBinding.stageFlags = VK_SHADER_STAGE_VERTEX_BIT; | |
| VkDescriptorSetLayoutCreateInfo descriptorLayoutCreateInfo = {}; | |
| descriptorLayoutCreateInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_LAYOUT_CREATE_INFO; | |
| descriptorLayoutCreateInfo.bindingCount = 1; | |
| descriptorLayoutCreateInfo.pBindings = &layoutBinding; | |
| if (vkCreateDescriptorSetLayout(device, &descriptorLayoutCreateInfo, nullptr, &descriptorSetLayout) != VK_SUCCESS) { | |
| std::cerr << "failed to create descriptor layout" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created descriptor layout" << std::endl; | |
| } | |
| VkPipelineLayoutCreateInfo layoutCreateInfo = {}; | |
| layoutCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; | |
| layoutCreateInfo.setLayoutCount = 1; | |
| layoutCreateInfo.pSetLayouts = &descriptorSetLayout; | |
| if (vkCreatePipelineLayout(device, &layoutCreateInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { | |
| std::cerr << "failed to create pipeline layout" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created pipeline layout" << std::endl; | |
| } | |
| // Create the graphics pipeline | |
| VkGraphicsPipelineCreateInfo pipelineCreateInfo = {}; | |
| pipelineCreateInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO; | |
| pipelineCreateInfo.stageCount = 2; | |
| pipelineCreateInfo.pStages = shaderStages; | |
| pipelineCreateInfo.pVertexInputState = &vertexInputCreateInfo; | |
| pipelineCreateInfo.pInputAssemblyState = &inputAssemblyCreateInfo; | |
| pipelineCreateInfo.pViewportState = &viewportCreateInfo; | |
| pipelineCreateInfo.pRasterizationState = &rasterizationCreateInfo; | |
| pipelineCreateInfo.pMultisampleState = &multisampleCreateInfo; | |
| pipelineCreateInfo.pColorBlendState = &colorBlendCreateInfo; | |
| pipelineCreateInfo.layout = pipelineLayout; | |
| pipelineCreateInfo.renderPass = renderPass; | |
| pipelineCreateInfo.subpass = 0; | |
| pipelineCreateInfo.basePipelineHandle = VK_NULL_HANDLE; | |
| pipelineCreateInfo.basePipelineIndex = -1; | |
| if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineCreateInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { | |
| std::cerr << "failed to create graphics pipeline" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created graphics pipeline" << std::endl; | |
| } | |
| // No longer necessary | |
| vkDestroyShaderModule(device, vertexShaderModule, nullptr); | |
| vkDestroyShaderModule(device, fragmentShaderModule, nullptr); | |
| } | |
| void createDescriptorPool() { | |
| // This describes how many descriptor sets we'll create from this pool for each type | |
| VkDescriptorPoolSize typeCount; | |
| typeCount.type = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
| typeCount.descriptorCount = 1; | |
| VkDescriptorPoolCreateInfo createInfo = {}; | |
| createInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_POOL_CREATE_INFO; | |
| createInfo.poolSizeCount = 1; | |
| createInfo.pPoolSizes = &typeCount; | |
| createInfo.maxSets = 1; | |
| if (vkCreateDescriptorPool(device, &createInfo, nullptr, &descriptorPool) != VK_SUCCESS) { | |
| std::cerr << "failed to create descriptor pool" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created descriptor pool" << std::endl; | |
| } | |
| } | |
| void createDescriptorSet() { | |
| // There needs to be one descriptor set per binding point in the shader | |
| VkDescriptorSetAllocateInfo allocInfo = {}; | |
| allocInfo.sType = VK_STRUCTURE_TYPE_DESCRIPTOR_SET_ALLOCATE_INFO; | |
| allocInfo.descriptorPool = descriptorPool; | |
| allocInfo.descriptorSetCount = 1; | |
| allocInfo.pSetLayouts = &descriptorSetLayout; | |
| if (vkAllocateDescriptorSets(device, &allocInfo, &descriptorSet) != VK_SUCCESS) { | |
| std::cerr << "failed to create descriptor set" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "created descriptor set" << std::endl; | |
| } | |
| // Update descriptor set with uniform binding | |
| VkDescriptorBufferInfo descriptorBufferInfo = {}; | |
| descriptorBufferInfo.buffer = uniformBuffer; | |
| descriptorBufferInfo.offset = 0; | |
| descriptorBufferInfo.range = sizeof(uniformBufferData); | |
| VkWriteDescriptorSet writeDescriptorSet = {}; | |
| writeDescriptorSet.sType = VK_STRUCTURE_TYPE_WRITE_DESCRIPTOR_SET; | |
| writeDescriptorSet.dstSet = descriptorSet; | |
| writeDescriptorSet.descriptorCount = 1; | |
| writeDescriptorSet.descriptorType = VK_DESCRIPTOR_TYPE_UNIFORM_BUFFER; | |
| writeDescriptorSet.pBufferInfo = &descriptorBufferInfo; | |
| writeDescriptorSet.dstBinding = 0; | |
| vkUpdateDescriptorSets(device, 1, &writeDescriptorSet, 0, nullptr); | |
| } | |
| void createCommandBuffers() { | |
| // Allocate graphics command buffers | |
| graphicsCommandBuffers.resize(swapChainImages.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) swapChainImages.size(); | |
| if (vkAllocateCommandBuffers(device, &allocInfo, graphicsCommandBuffers.data()) != VK_SUCCESS) { | |
| std::cerr << "failed to allocate graphics command buffers" << std::endl; | |
| exit(1); | |
| } else { | |
| std::cout << "allocated graphics command buffers" << std::endl; | |
| } | |
| // Prepare data for recording command buffers | |
| VkCommandBufferBeginInfo beginInfo = {}; | |
| beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; | |
| beginInfo.flags = VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT; | |
| VkImageSubresourceRange subResourceRange = {}; | |
| subResourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT; | |
| subResourceRange.baseMipLevel = 0; | |
| subResourceRange.levelCount = 1; | |
| subResourceRange.baseArrayLayer = 0; | |
| subResourceRange.layerCount = 1; | |
| VkClearValue clearColor = { | |
| { 0.1f, 0.1f, 0.1f, 1.0f } // R, G, B, A | |
| }; | |
| // Record command buffer for each swap image | |
| for (size_t i = 0; i < swapChainImages.size(); i++) { | |
| vkBeginCommandBuffer(graphicsCommandBuffers[i], &beginInfo); | |
| // If present queue family and graphics queue family are different, then a barrier is necessary | |
| // The barrier is also needed initially to transition the image to the present layout | |
| VkImageMemoryBarrier presentToDrawBarrier = {}; | |
| presentToDrawBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | |
| presentToDrawBarrier.srcAccessMask = 0; | |
| presentToDrawBarrier.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
| presentToDrawBarrier.oldLayout = VK_IMAGE_LAYOUT_UNDEFINED; | |
| presentToDrawBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
| if (presentQueueFamily != graphicsQueueFamily) { | |
| presentToDrawBarrier.srcQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |
| presentToDrawBarrier.dstQueueFamilyIndex = VK_QUEUE_FAMILY_IGNORED; | |
| } else { | |
| presentToDrawBarrier.srcQueueFamilyIndex = presentQueueFamily; | |
| presentToDrawBarrier.dstQueueFamilyIndex = graphicsQueueFamily; | |
| } | |
| presentToDrawBarrier.image = swapChainImages[i]; | |
| presentToDrawBarrier.subresourceRange = subResourceRange; | |
| vkCmdPipelineBarrier(graphicsCommandBuffers[i], VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, 0, 0, nullptr, 0, nullptr, 1, &presentToDrawBarrier); | |
| VkRenderPassBeginInfo renderPassBeginInfo = {}; | |
| renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; | |
| renderPassBeginInfo.renderPass = renderPass; | |
| renderPassBeginInfo.framebuffer = swapChainFramebuffers[i]; | |
| renderPassBeginInfo.renderArea.offset.x = 0; | |
| renderPassBeginInfo.renderArea.offset.y = 0; | |
| renderPassBeginInfo.renderArea.extent = swapChainExtent; | |
| renderPassBeginInfo.clearValueCount = 1; | |
| renderPassBeginInfo.pClearValues = &clearColor; | |
| vkCmdBeginRenderPass(graphicsCommandBuffers[i], &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); | |
| vkCmdBindDescriptorSets(graphicsCommandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, pipelineLayout, 0, 1, &descriptorSet, 0, nullptr); | |
| vkCmdBindPipeline(graphicsCommandBuffers[i], VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); | |
| VkDeviceSize offset = 0; | |
| vkCmdBindVertexBuffers(graphicsCommandBuffers[i], 0, 1, &vertexBuffer, &offset); | |
| vkCmdBindIndexBuffer(graphicsCommandBuffers[i], indexBuffer, 0, VK_INDEX_TYPE_UINT32); | |
| vkCmdDrawIndexed(graphicsCommandBuffers[i], 3, 1, 0, 0, 0); | |
| vkCmdEndRenderPass(graphicsCommandBuffers[i]); | |
| // If present and graphics queue families differ, then another barrier is required | |
| if (presentQueueFamily != graphicsQueueFamily) { | |
| VkImageMemoryBarrier drawToPresentBarrier = {}; | |
| drawToPresentBarrier.sType = VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER; | |
| drawToPresentBarrier.srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT; | |
| drawToPresentBarrier.dstAccessMask = VK_ACCESS_MEMORY_READ_BIT; | |
| drawToPresentBarrier.oldLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
| drawToPresentBarrier.newLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; | |
| drawToPresentBarrier.srcQueueFamilyIndex = graphicsQueueFamily; | |
| drawToPresentBarrier.dstQueueFamilyIndex = presentQueueFamily; | |
| drawToPresentBarrier.image = swapChainImages[i]; | |
| drawToPresentBarrier.subresourceRange = subResourceRange; | |
| vkCmdPipelineBarrier(graphicsCommandBuffers[i], VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT, 0, 0, nullptr, 0, nullptr, 1, &drawToPresentBarrier); | |
| } | |
| if (vkEndCommandBuffer(graphicsCommandBuffers[i]) != VK_SUCCESS) { | |
| std::cerr << "failed to record command buffer" << std::endl; | |
| exit(1); | |
| } | |
| } | |
| std::cout << "recorded command buffers" << std::endl; | |
| // No longer needed | |
| vkDestroyPipelineLayout(device, pipelineLayout, nullptr); | |
| } | |
| void draw() { | |
| // Acquire image | |
| uint32_t imageIndex; | |
| VkResult res = vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); | |
| // Unless surface is out of date right now, defer swap chain recreation until end of this frame | |
| if (res == VK_ERROR_OUT_OF_DATE_KHR) { | |
| onWindowSizeChanged(); | |
| return; | |
| } else if (res != VK_SUCCESS) { | |
| std::cerr << "failed to acquire image" << std::endl; | |
| exit(1); | |
| } | |
| // Wait for image to be available and draw | |
| VkSubmitInfo submitInfo = {}; | |
| submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; | |
| submitInfo.waitSemaphoreCount = 1; | |
| submitInfo.pWaitSemaphores = &imageAvailableSemaphore; | |
| submitInfo.signalSemaphoreCount = 1; | |
| submitInfo.pSignalSemaphores = &renderingFinishedSemaphore; | |
| // This is the stage where the queue should wait on the semaphore | |
| VkPipelineStageFlags waitDstStageMask = VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT; | |
| submitInfo.pWaitDstStageMask = &waitDstStageMask; | |
| submitInfo.commandBufferCount = 1; | |
| submitInfo.pCommandBuffers = &graphicsCommandBuffers[imageIndex]; | |
| if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, VK_NULL_HANDLE) != VK_SUCCESS) { | |
| std::cerr << "failed to submit draw command buffer" << std::endl; | |
| exit(1); | |
| } | |
| // Present drawn image | |
| // Note: semaphore here is not strictly necessary, because commands are processed in submission order within a single queue | |
| VkPresentInfoKHR presentInfo = {}; | |
| presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR; | |
| presentInfo.waitSemaphoreCount = 1; | |
| presentInfo.pWaitSemaphores = &renderingFinishedSemaphore; | |
| presentInfo.swapchainCount = 1; | |
| presentInfo.pSwapchains = &swapChain; | |
| presentInfo.pImageIndices = &imageIndex; | |
| res = vkQueuePresentKHR(presentQueue, &presentInfo); | |
| if (res == VK_SUBOPTIMAL_KHR || res == VK_ERROR_OUT_OF_DATE_KHR || windowResized) { | |
| onWindowSizeChanged(); | |
| } else if (res != VK_SUCCESS) { | |
| std::cerr << "failed to submit present command buffer" << std::endl; | |
| exit(1); | |
| } | |
| } | |
| }; | |
| int main() { | |
| TriangleApplication app; | |
| app.run(); | |
| return 0; | |
| } |
@MohammadFakhreddin I'm not sure where you found this gist, but it's pretty similar to the Hello Triangle code in my Vulkan tutorial, which includes links to the shader files: https://vulkan-tutorial.com/Drawing_a_triangle/Swap_chain_recreation#page_Handling-minimization
@Overv Thanks
It doesn't seem to work for me? also the shaders there are spirv and the shaders in that link seem to be in glsl?
Thanks god I decided to go for GLFW + OpenGL (but in all fairness this seems to be creating the context by itself)
damn bro wrote 1.3k lines for a triangle opengl you need 5
@jacksonlevine I have completed it as part of my Vulkan tutorial: https://github.com/Overv/VulkanTutorial
I have changed my mind about learning vulkan
And I felt like the 200 lines in openGL were too much. I was actually proud of myself 🤣🤣.
change my mind, not doing vulkan
wtf. Is this really hello world?
If this is real, then doing Vulkan for smaller projects is a big no no. WTF. I think I have less lines of code for a more complex OpenGL setup that renders transformed quads with shaders, textures, render-buffers, etc.
Thank you for sharing this complete example.
Are the shaders for this example available in github as well ?