#define GLFW_INCLUDE_VULKAN #include #include #include #include #include #include #include #include #include #include using TimePoint = std::chrono::time_point; using TimeMs = long; TimeMs timeDeltaMs(const TimePoint& point) { auto timeDelta = std::chrono::system_clock::now() - point; auto timeDelayMs = std::chrono::duration_cast(timeDelta).count(); return timeDelayMs; } struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities{}; std::vector formats; std::vector presentModes; }; struct Vertex { glm::vec2 pos; glm::vec3 color; }; const std::vector gDeviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME, }; const std::vector gValidationLayers = { #ifdef NDEBUG #else "VK_LAYER_KHRONOS_validation" #endif }; const std::vector vertices = { {{-0.5f, -0.5f}, {1.f, 0.f, 0.f}}, {{0.5f, -0.5f}, {0.f, 1.f, 0.f}}, {{0.5f, 0.5f}, {0.f, 0.f, 1.f}}, {{-0.5f, 0.5f}, {1.f, 1.f, 1.f}}, }; const std::vector indices = { 0, 1, 2, 2, 3, 0 }; struct CustomShader { void create(VkDevice device) { std::vector vertShaderByteCode = readFile("bin/vert.spv"); mVertexModule = createShaderModule(device, vertShaderByteCode); std::vector fragShaderByteCode = readFile("bin/frag.spv"); mFragmentModule = createShaderModule(device, fragShaderByteCode); VkPipelineShaderStageCreateInfo vertShaderStageCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_VERTEX_BIT, .module = mVertexModule, .pName = "main", }; VkPipelineShaderStageCreateInfo fragShaderStageCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO, .stage = VK_SHADER_STAGE_FRAGMENT_BIT, .module = mFragmentModule, .pName = "main", }; mStageCreateInfos = { vertShaderStageCreateInfo, fragShaderStageCreateInfo }; } void destroy(VkDevice device) const { vkDestroyShaderModule(device, mVertexModule, nullptr); vkDestroyShaderModule(device, mFragmentModule, nullptr); } static 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("cannot open file"); } size_t fileSize = (size_t) file.tellg(); std::vector buffer(fileSize); file.seekg(0); file.read(buffer.data(), (std::streamsize) fileSize); file.close(); return buffer; } static VkShaderModule createShaderModule(VkDevice device, const std::vector &bytecode) { VkShaderModuleCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO, .codeSize = (uint32_t) bytecode.size(), .pCode = (uint32_t *) bytecode.data(), }; VkShaderModule shaderModule; if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("cannot create shader module"); } return shaderModule; } public: struct UniformBuffer { glm::mat4 transforms {}; glm::vec4 origin {}; } mVertexUBO; VkVertexInputBindingDescription mVertexInputDescription { .binding = 0, .stride = sizeof(Vertex), .inputRate = VK_VERTEX_INPUT_RATE_VERTEX, }; std::vector mVertexAttributes = { { .location = 0, .binding = 0, .format = VK_FORMAT_R32G32_SFLOAT, .offset = offsetof(Vertex, pos), }, { .location = 1, .binding = 0, .format = VK_FORMAT_R32G32B32_SFLOAT, .offset = offsetof(Vertex, color), }, }; std::vector mStageCreateInfos; VkShaderModule mVertexModule = VK_NULL_HANDLE; VkShaderModule mFragmentModule = VK_NULL_HANDLE; }; class Application { public: void run() { initWindow(); initVulkan(); mainLoop(); cleanup(); } void scheduleWindowResize(int sizeX, int sizeY) { mWindowSizeDirtyFlagTime = std::chrono::system_clock::now(); mWindowSizeDirtyFlag = true; mWindowFramebufferSize = std::make_pair(sizeX, sizeY); assert(!(sizeX <= 0 || sizeY <= 0)); } private: void initWindow() { // glfwInitHint(GLFW_PLATFORM, GLFW_PLATFORM_X11); glfwInit(); glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); // glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); mWindow = glfwCreateWindow(800, 600, "App", nullptr, nullptr); scheduleWindowResize(800, 600); glfwSetWindowUserPointer(mWindow, this); glfwSetFramebufferSizeCallback(mWindow, [](GLFWwindow* window, int sizeX, int sizeY){ ((Application*) glfwGetWindowUserPointer(window))->scheduleWindowResize(sizeX, sizeY); }); } void initVulkan() { createInstance(); createWindowSurface(); pickPhysicalDevice(); findPhysicalDeviceQueueFamilies(); createLogicalDevice(); getQueues(); int width, height; glfwGetFramebufferSize(mWindow, &width, &height); createSwapChain(width, height); createSwapChainImageViews(); mShader.create(mDevice); createRenderPass(); createGraphicsPipeline(); createSwapChainFramebuffers(); createCommandPool(); createCommandBuffer(); createSynchronizationObjects(); createVertexBuffer(); createIndexBuffer(); } void mainLoop() { while (!glfwWindowShouldClose(mWindow)) { glfwPollEvents(); if (mWindowSizeDirtyFlag) { auto timeDelayMs = timeDeltaMs(mWindowSizeDirtyFlagTime); if (timeDelayMs > mWindowSizeApplyMinDelay) { recreateSwapChain(mWindowFramebufferSize.first, mWindowFramebufferSize.second); mWindowSizeDirtyFlag = false; } } drawFrame(); } vkDeviceWaitIdle(mDevice); } void createInstance() { if (!checkValidationLayerSupport()) { throw std::runtime_error("no required validation layers present"); } uint32_t glfwExtensionCount = 0; const char **glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount); VkApplicationInfo appInfo{ .sType = VK_STRUCTURE_TYPE_APPLICATION_INFO, .pApplicationName = "App", .applicationVersion = VK_MAKE_VERSION(0, 0, 0), .pEngineName = "no", .engineVersion = VK_MAKE_VERSION(0, 0, 0), .apiVersion = VK_API_VERSION_1_0, }; VkInstanceCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, .pApplicationInfo = &appInfo, .enabledLayerCount = (uint32_t) gValidationLayers.size(), .ppEnabledLayerNames = gValidationLayers.data(), .enabledExtensionCount = glfwExtensionCount, .ppEnabledExtensionNames = glfwExtensions, }; if (vkCreateInstance(&createInfo, nullptr, &mInstance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } static bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector availableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data()); for (const auto &requestedLayer: gValidationLayers) { bool presents = false; for (auto &layer: availableLayers) { if (strcmp(requestedLayer, layer.layerName) == 0) { presents = true; break; } } if (!presents) return false; } return true; } void pickPhysicalDevice() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(mInstance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("no gpu with vulkan support"); } std::vector devices(deviceCount); vkEnumeratePhysicalDevices(mInstance, &deviceCount, devices.data()); for (const auto &device: devices) { if (isDeviceSuitable(device)) { mPhysicalDevice = device; break; } } if (!mPhysicalDevice) throw std::runtime_error("no suitable gpu"); } bool isDeviceSuitable(VkPhysicalDevice device) { VkPhysicalDeviceProperties properties; VkPhysicalDeviceFeatures features; vkGetPhysicalDeviceProperties(device, &properties); vkGetPhysicalDeviceFeatures(device, &features); // if (properties.deviceType != VK_PHYSICAL_DEVICE_TYPE_DISCRETE_GPU) return false; if (!features.geometryShader) return false; if (!checkDeviceExtensions(device)) return false; if (!checkDeviceSwapChain(device)) return false; return true; } bool checkDeviceSwapChain(VkPhysicalDevice device) { SwapChainSupportDetails details = querySwapChainSupportDetails(device); return !(details.presentModes.empty() || details.formats.empty()); } static bool checkDeviceExtensions(VkPhysicalDevice device) { uint32_t extensionsCount = 0; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionsCount, nullptr); std::vector extensions(extensionsCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionsCount, extensions.data()); for (const auto &requiredExtension: gDeviceExtensions) { bool found = false; for (const auto &extension: extensions) { if (strcmp(requiredExtension, extension.extensionName) == 0) { found = true; break; } } if (!found) return false; } return true; } void findPhysicalDeviceQueueFamilies() { uint32_t queuesCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queuesCount, nullptr); std::vector queueFamilyProperties(queuesCount); vkGetPhysicalDeviceQueueFamilyProperties(mPhysicalDevice, &queuesCount, queueFamilyProperties.data()); int index = 0; for (const auto &familyProperty: queueFamilyProperties) { if (familyProperty.queueFlags & VK_QUEUE_GRAPHICS_BIT) { mGraphicsQueueFamilyIndex = index; } VkBool32 presentationQueueSupport = false; vkGetPhysicalDeviceSurfaceSupportKHR(mPhysicalDevice, index, mSurface, &presentationQueueSupport); if (presentationQueueSupport) { mPresentationQueueFamilyIndex = index; } index++; } if (mPresentationQueueFamilyIndex == -1 || mGraphicsQueueFamilyIndex == -1) { throw std::runtime_error("nu require queue families found"); } } SwapChainSupportDetails querySwapChainSupportDetails(VkPhysicalDevice device) { SwapChainSupportDetails details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, mSurface, &details.capabilities); uint32_t formatCount = 0; vkGetPhysicalDeviceSurfaceFormatsKHR(device, mSurface, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, mSurface, &formatCount, details.formats.data()); } uint32_t modeCount = 0; vkGetPhysicalDeviceSurfacePresentModesKHR(device, mSurface, &modeCount, nullptr); if (modeCount != 0) { details.presentModes.resize(modeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, mSurface, &modeCount, details.presentModes.data()); } return details; } void createLogicalDevice() { float queuePriority = 1.f; VkDeviceQueueCreateInfo graphicsQueueCreateInfos{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = mGraphicsQueueFamilyIndex, .queueCount = 1, .pQueuePriorities = &queuePriority, }; VkDeviceQueueCreateInfo presentationQueueCreateInfos{ .sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO, .queueFamilyIndex = mPresentationQueueFamilyIndex, .queueCount = 1, .pQueuePriorities = &queuePriority, }; std::vector queues = {graphicsQueueCreateInfos}; if (mPresentationQueueFamilyIndex != mGraphicsQueueFamilyIndex) { queues.push_back(presentationQueueCreateInfos); } VkPhysicalDeviceFeatures features{}; VkDeviceCreateInfo deviceCreateInfo{ .sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO, .queueCreateInfoCount = (uint32_t) queues.size(), .pQueueCreateInfos = queues.data(), .enabledExtensionCount = (uint32_t) gDeviceExtensions.size(), .ppEnabledExtensionNames = gDeviceExtensions.data(), .pEnabledFeatures = &features, }; vkCreateDevice(mPhysicalDevice, &deviceCreateInfo, nullptr, &mDevice); if (mDevice == VK_NULL_HANDLE) throw std::runtime_error("failed to create vulkan logical device"); } void getQueues() { vkGetDeviceQueue(mDevice, mGraphicsQueueFamilyIndex, 0, &mGraphicsQueue); vkGetDeviceQueue(mDevice, mPresentationQueueFamilyIndex, 0, &mPresentQueue); } void createWindowSurface() { if (glfwCreateWindowSurface(mInstance, mWindow, nullptr, &mSurface) != VK_SUCCESS) { throw std::runtime_error("failed to create vulkan window surface"); } } void recreateSwapChain(int sizeX, int sizeY) { vkDeviceWaitIdle(mDevice); destroySwapChainFramebuffers(); destroySwapChainImageViews(); destroySwapChain(); createSwapChain(sizeX, sizeY); createSwapChainImageViews(); createSwapChainFramebuffers(); } void createSwapChain(int sizeX, int sizeY) { SwapChainSupportDetails details = querySwapChainSupportDetails(mPhysicalDevice); VkSurfaceFormatKHR surfaceFormat = pickSwapChainSurfaceFormat(details); VkPresentModeKHR presentMode = pickSwapChainPresentMode(details); VkExtent2D extent2D = pickSwapChainExtent(details.capabilities, sizeX, sizeY); // +1 so will not have to wait for device to finish frame to query an image to render to uint32_t imageCount = details.capabilities.minImageCount + 1; if (details.capabilities.maxImageCount > 0 && imageCount > details.capabilities.maxImageCount) { imageCount = details.capabilities.maxImageCount; } VkSwapchainCreateInfoKHR createInfo{ .sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR, .surface = mSurface, .minImageCount = imageCount, .imageFormat = surfaceFormat.format, .imageExtent = extent2D, .imageArrayLayers = 1, .imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT, .preTransform = details.capabilities.currentTransform, .compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR, .presentMode = presentMode, .clipped = VK_TRUE, .oldSwapchain = VK_NULL_HANDLE, }; uint32_t queueIndices[] = {mGraphicsQueueFamilyIndex, mPresentationQueueFamilyIndex}; if (mGraphicsQueueFamilyIndex != mPresentationQueueFamilyIndex) { createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueIndices; } else { createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; } if (vkCreateSwapchainKHR(mDevice, &createInfo, nullptr, &mSwapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain"); } mSwapChainExtent = extent2D; mSwapChainFormat = surfaceFormat.format; uint32_t createdImageCount = 0; vkGetSwapchainImagesKHR(mDevice, mSwapChain, &createdImageCount, nullptr); mSwapChainImages.resize(createdImageCount); vkGetSwapchainImagesKHR(mDevice, mSwapChain, &createdImageCount, mSwapChainImages.data()); } static VkSurfaceFormatKHR pickSwapChainSurfaceFormat(const SwapChainSupportDetails &details) { for (const auto &format: details.formats) { if (format.format == VK_FORMAT_B8G8R8A8_SRGB && format.colorSpace == VK_COLORSPACE_SRGB_NONLINEAR_KHR) { return format; } } return details.formats.front(); } static VkPresentModeKHR pickSwapChainPresentMode(const SwapChainSupportDetails &details) { for (const auto &mode: details.presentModes) { if (mode == VK_PRESENT_MODE_MAILBOX_KHR) { return mode; } } return VK_PRESENT_MODE_FIFO_KHR; // guaranteed exists } static VkExtent2D pickSwapChainExtent(const VkSurfaceCapabilitiesKHR &capabilities, int sizeX, int sizeY) { // if set by vulkan just keep it if (capabilities.currentExtent.width != std::numeric_limits::max()) { return capabilities.currentExtent; } VkExtent2D out = {(uint32_t) sizeX, (uint32_t) sizeY}; out.width = std::clamp(out.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); out.height = std::clamp(out.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return out; } void createSwapChainImageViews() { mSwapChainImageViews.resize(mSwapChainImages.size()); int i = 0; for (const auto &image: mSwapChainImages) { VkImageViewCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO, .image = image, .viewType = VK_IMAGE_VIEW_TYPE_2D, .format = mSwapChainFormat, .components = { .r = VK_COMPONENT_SWIZZLE_R, .g = VK_COMPONENT_SWIZZLE_G, .b = VK_COMPONENT_SWIZZLE_B, .a = VK_COMPONENT_SWIZZLE_A, }, .subresourceRange = { .aspectMask = VK_IMAGE_ASPECT_COLOR_BIT, .baseMipLevel = 0, .levelCount = 1, .baseArrayLayer = 0, .layerCount = 1, } }; if (vkCreateImageView(mDevice, &createInfo, nullptr, &mSwapChainImageViews[i]) != VK_SUCCESS) { throw std::runtime_error("cannot create image view for the swap chain"); } i++; } } void createSwapChainFramebuffers() { mSwapChainFrameBuffers.resize(mSwapChainImageViews.size()); for (size_t i = 0; i < mSwapChainImageViews.size(); i++) { VkImageView attachments[] = { mSwapChainImageViews[i], }; VkFramebufferCreateInfo framebufferCreateInfo{ .sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO, .renderPass = mGraphicsRenderPass, .attachmentCount = 1, .pAttachments = attachments, .width = mSwapChainExtent.width, .height = mSwapChainExtent.height, .layers = 1, }; if (vkCreateFramebuffer(mDevice, &framebufferCreateInfo, nullptr, &mSwapChainFrameBuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create swapchain framebuffers"); } } } void destroySwapChainFramebuffers() { for (const auto &buffer: mSwapChainFrameBuffers) { vkDestroyFramebuffer(mDevice, buffer, nullptr); } } void destroySwapChain() { vkDestroySwapchainKHR(mDevice, mSwapChain, nullptr); } void destroySwapChainImageViews() { for (const auto &imageView: mSwapChainImageViews) { vkDestroyImageView(mDevice, imageView, nullptr); } } void createGraphicsPipeline() { std::vector dynamicStates = { VK_DYNAMIC_STATE_VIEWPORT, VK_DYNAMIC_STATE_SCISSOR, }; VkPipelineDynamicStateCreateInfo dynamicStateCreateInfo = { .sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO, .dynamicStateCount = (uint32_t) dynamicStates.size(), .pDynamicStates = dynamicStates.data(), }; VkPipelineVertexInputStateCreateInfo vertexInputStateCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO, .vertexBindingDescriptionCount = 1, .pVertexBindingDescriptions = &mShader.mVertexInputDescription, .vertexAttributeDescriptionCount = (uint32_t) mShader.mVertexAttributes.size(), .pVertexAttributeDescriptions = mShader.mVertexAttributes.data(), }; VkPipelineInputAssemblyStateCreateInfo inputAssemblyStateCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO, .topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, .primitiveRestartEnable = VK_FALSE, }; VkViewport viewport{ .x = 0, .y = 0, .width = (float) mSwapChainExtent.width, .height = (float) mSwapChainExtent.height, .minDepth = 0.f, .maxDepth = 1.f, }; VkRect2D scissor{ .offset = {0, 0}, .extent = mSwapChainExtent, }; VkPipelineViewportStateCreateInfo viewportState{ .sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO, .viewportCount = 1, .pViewports = &viewport, .scissorCount = 1, .pScissors = &scissor, }; VkPipelineRasterizationStateCreateInfo rasterizationStateCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO, .depthClampEnable = VK_FALSE, .rasterizerDiscardEnable = VK_FALSE, .polygonMode = VK_POLYGON_MODE_FILL, .cullMode = VK_CULL_MODE_BACK_BIT, .frontFace = VK_FRONT_FACE_CLOCKWISE, .depthBiasEnable = VK_FALSE, .depthBiasConstantFactor = 0.f, .depthBiasClamp = 0.f, .depthBiasSlopeFactor = 0.f, .lineWidth = 1, }; VkPipelineMultisampleStateCreateInfo multisampleStateCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO, .rasterizationSamples = VK_SAMPLE_COUNT_1_BIT, .sampleShadingEnable = VK_FALSE, }; VkPipelineColorBlendAttachmentState colorBlendAttachmentState{ .blendEnable = VK_FALSE, .colorWriteMask = (VK_COLOR_COMPONENT_R_BIT | VK_COLOR_COMPONENT_G_BIT | VK_COLOR_COMPONENT_B_BIT | VK_COLOR_COMPONENT_A_BIT), }; VkPipelineColorBlendStateCreateInfo colorBlendStateCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO, .logicOpEnable = VK_FALSE, .attachmentCount = 1, .pAttachments = &colorBlendAttachmentState, }; VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo{ .sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO, }; if (vkCreatePipelineLayout(mDevice, &pipelineLayoutCreateInfo, nullptr, &mGraphicsPipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout"); } VkGraphicsPipelineCreateInfo graphicsPipelineCreateInfo{ .sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO, .stageCount = 2, .pStages = mShader.mStageCreateInfos.data(), .pVertexInputState = &vertexInputStateCreateInfo, .pInputAssemblyState = &inputAssemblyStateCreateInfo, .pViewportState = &viewportState, .pRasterizationState = &rasterizationStateCreateInfo, .pMultisampleState = &multisampleStateCreateInfo, .pColorBlendState = &colorBlendStateCreateInfo, .pDynamicState = &dynamicStateCreateInfo, .layout = mGraphicsPipelineLayout, .renderPass = mGraphicsRenderPass, .subpass = 0, }; if (vkCreateGraphicsPipelines(mDevice, nullptr, 1, &graphicsPipelineCreateInfo, nullptr, &mGraphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline"); } } void createRenderPass() { VkAttachmentDescription colorAttachment{ .format = mSwapChainFormat, .samples = VK_SAMPLE_COUNT_1_BIT, .loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR, .storeOp = VK_ATTACHMENT_STORE_OP_STORE, .stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE, .stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE, .initialLayout = VK_IMAGE_LAYOUT_UNDEFINED, .finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR, }; VkAttachmentReference colorAttachmentReference{ .attachment = 0, .layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL, }; VkSubpassDescription subpassDescription{ .pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS, .colorAttachmentCount = 1, .pColorAttachments = &colorAttachmentReference, }; VkSubpassDependency dependency { .srcSubpass = VK_SUBPASS_EXTERNAL, .dstSubpass = 0, .srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT, .srcAccessMask = 0, .dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT, }; VkRenderPassCreateInfo renderPassCreateInfo{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO, .attachmentCount = 1, .pAttachments = &colorAttachment, .subpassCount = 1, .pSubpasses = &subpassDescription, .dependencyCount = 1, .pDependencies = &dependency }; if (vkCreateRenderPass(mDevice, &renderPassCreateInfo, nullptr, &mGraphicsRenderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass"); } } void destroyGraphicsPipeline() { mShader.destroy(mDevice); vkDestroyPipelineLayout(mDevice, mGraphicsPipelineLayout, nullptr); vkDestroyPipeline(mDevice, mGraphicsPipeline, nullptr); } void createCommandPool() { VkCommandPoolCreateInfo createInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO, .flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT, .queueFamilyIndex = mGraphicsQueueFamilyIndex, }; if (vkCreateCommandPool(mDevice, &createInfo, nullptr, &mCommandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool"); } } void createCommandBuffer() { VkCommandBufferAllocateInfo allocateInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = mCommandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1, }; if (vkAllocateCommandBuffers(mDevice, &allocateInfo, &mCommandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to create command buffer"); } } uint32_t findMemoryType(uint32_t typeFilter, VkMemoryPropertyFlags flags) { VkPhysicalDeviceMemoryProperties memoryProperties; vkGetPhysicalDeviceMemoryProperties(mPhysicalDevice, &memoryProperties); for (uint32_t i = 0; i < memoryProperties.memoryTypeCount; i++) { if (typeFilter & (1 << i) && (memoryProperties.memoryTypes[i].propertyFlags & flags) == flags) { return i; } } throw std::runtime_error("no suitable memory for vertex buffer found"); } void createIndexBuffer() { VkDeviceSize size = sizeof(indices[0]) * indices.size(); VkBuffer stagingBuffer; VkDeviceMemory stagingBufferMemory; createBuffer(&stagingBuffer, &stagingBufferMemory, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); { void* memory; vkMapMemory(mDevice, stagingBufferMemory, 0, size, 0, &memory); memcpy(memory, indices.data(), size); vkUnmapMemory(mDevice, stagingBufferMemory); } createBuffer(&mIndexBuffer, &mIndexBufferMemory, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_INDEX_BUFFER_BIT); copyBuffer(stagingBuffer, mIndexBuffer, size); destroyBuffer(stagingBuffer, stagingBufferMemory); } void createVertexBuffer() { size_t size = vertices.size() * sizeof(vertices[0]); VkBuffer stagingBuffer; VkDeviceMemory stagingBufferMemory; createBuffer(&stagingBuffer, &stagingBufferMemory, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, size, VK_BUFFER_USAGE_TRANSFER_SRC_BIT); { void* memory; vkMapMemory(mDevice, stagingBufferMemory, 0, size, 0, &memory); memcpy(memory, vertices.data(), size); vkUnmapMemory(mDevice, stagingBufferMemory); } createBuffer(&mVertexBuffer, &mVertexBufferMemory, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT, size, VK_BUFFER_USAGE_TRANSFER_DST_BIT | VK_BUFFER_USAGE_VERTEX_BUFFER_BIT); copyBuffer(stagingBuffer, mVertexBuffer, size); destroyBuffer(stagingBuffer, stagingBufferMemory); } void copyBuffer(VkBuffer src, VkBuffer dst, VkDeviceSize size) { VkCommandBufferAllocateInfo allocateInfo { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO, .commandPool = mCommandPool, .level = VK_COMMAND_BUFFER_LEVEL_PRIMARY, .commandBufferCount = 1 }; VkCommandBuffer commandBuffer; vkAllocateCommandBuffers(mDevice, &allocateInfo, &commandBuffer); VkCommandBufferBeginInfo beginInfo { .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, .flags = VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT, }; vkBeginCommandBuffer(commandBuffer, &beginInfo); VkBufferCopy copyRegion { .srcOffset = 0, .dstOffset = 0, .size = size, }; vkCmdCopyBuffer(commandBuffer, src, dst, 1, ©Region); vkEndCommandBuffer(commandBuffer); VkSubmitInfo submitInfo { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .commandBufferCount = 1, .pCommandBuffers = &commandBuffer, }; vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, VK_NULL_HANDLE); vkQueueWaitIdle(mGraphicsQueue); vkFreeCommandBuffers(mDevice, mCommandPool, 1, &commandBuffer); } void createBuffer(VkBuffer *buffer, VkDeviceMemory *memory, VkMemoryPropertyFlags properties, VkDeviceSize size, VkBufferUsageFlags usage) { VkBufferCreateInfo createInfo { .sType = VK_STRUCTURE_TYPE_BUFFER_CREATE_INFO, .size = size, .usage = usage, .sharingMode = VK_SHARING_MODE_EXCLUSIVE, }; if (vkCreateBuffer(mDevice, &createInfo, nullptr, buffer) != VK_SUCCESS) { throw std::runtime_error("failed to create vertex buffer"); } VkMemoryRequirements memoryRequirements; vkGetBufferMemoryRequirements(mDevice, *buffer, &memoryRequirements); VkMemoryAllocateInfo allocateInfo = { .sType = VK_STRUCTURE_TYPE_MEMORY_ALLOCATE_INFO, .allocationSize = memoryRequirements.size, .memoryTypeIndex = findMemoryType(memoryRequirements.memoryTypeBits, properties), }; if (vkAllocateMemory(mDevice, &allocateInfo, nullptr, memory) != VK_SUCCESS) { throw std::runtime_error("failed to allocate vertex buffer memory"); } vkBindBufferMemory(mDevice, *buffer, *memory, 0); } void destroyBuffer(VkBuffer buffer, VkDeviceMemory memory) { vkDestroyBuffer(mDevice, buffer, nullptr); vkFreeMemory(mDevice, memory, nullptr); } void populateGraphicsCommandBuffer(VkCommandBuffer commandBuffer, uint32_t swapChainImageIndex) { VkCommandBufferBeginInfo commandBufferBeginInfo{ .sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO, }; if (vkBeginCommandBuffer(commandBuffer, &commandBufferBeginInfo) != VK_SUCCESS) { throw std::runtime_error("failed to begin command buffer ="); } VkClearValue clearColor{.color = {.float32 = {0.f, 0.f, 0.f, 1.f}}}; VkRenderPassBeginInfo renderPassBeginInfo{ .sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO, .renderPass = mGraphicsRenderPass, .framebuffer = mSwapChainFrameBuffers[swapChainImageIndex], .renderArea = { .offset = {0, 0}, .extent = mSwapChainExtent, }, .clearValueCount = 1, .pClearValues = &clearColor, }; vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, mGraphicsPipeline); VkViewport viewport{ .x = 0, .y = 0, .width = (float) mSwapChainExtent.width, .height = (float) mSwapChainExtent.height, .minDepth = 0.f, .maxDepth = 1.f, }; VkRect2D scissor{ .offset = {0, 0}, .extent = mSwapChainExtent, }; vkCmdSetViewport(commandBuffer, 0, 1, &viewport); vkCmdSetScissor(commandBuffer, 0, 1, &scissor); VkBuffer vertexBuffers[] = { mVertexBuffer }; VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); vkCmdBindIndexBuffer(commandBuffer, mIndexBuffer, 0, VK_INDEX_TYPE_UINT16); vkCmdDrawIndexed(commandBuffer, indices.size(), 1, 0, 0, 0); vkCmdEndRenderPass(commandBuffer); if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to end command buffer"); } } void destroyCommandPool() { vkDestroyCommandPool(mDevice, mCommandPool, nullptr); } void createSynchronizationObjects() { VkSemaphoreCreateInfo semaphoreCreateInfo { .sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO, }; VkFenceCreateInfo fenceCreateInfo { .sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO, .flags = VK_FENCE_CREATE_SIGNALED_BIT, }; bool failure = false; failure |= vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphoreImageAcquired) != VK_SUCCESS; failure |= vkCreateSemaphore(mDevice, &semaphoreCreateInfo, nullptr, &mSemaphoreFramebufferDrawn) != VK_SUCCESS; failure |= vkCreateFence(mDevice, &fenceCreateInfo, nullptr, &mFenceCanStartNewFrame) != VK_SUCCESS; if (failure) throw std::runtime_error("failed to create synchronization objects"); } void drawFrame() { vkWaitForFences(mDevice, 1, &mFenceCanStartNewFrame, VK_TRUE, UINT64_MAX); vkResetFences(mDevice, 1, &mFenceCanStartNewFrame); uint32_t imageIndex = 0; vkAcquireNextImageKHR(mDevice, mSwapChain, UINT64_MAX, mSemaphoreImageAcquired, VK_NULL_HANDLE, &imageIndex); vkResetCommandBuffer(mCommandBuffer, 0); populateGraphicsCommandBuffer(mCommandBuffer, imageIndex); VkSemaphore waitSemaphores[] = { mSemaphoreImageAcquired }; VkSemaphore signalSemaphores[] = { mSemaphoreFramebufferDrawn }; VkPipelineStageFlags waitStages[] = { VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT }; VkSubmitInfo submitInfo { .sType = VK_STRUCTURE_TYPE_SUBMIT_INFO, .waitSemaphoreCount = 1, .pWaitSemaphores = waitSemaphores, .pWaitDstStageMask = waitStages, .commandBufferCount = 1, .pCommandBuffers = &mCommandBuffer, .signalSemaphoreCount = 1, .pSignalSemaphores = signalSemaphores, }; if (vkQueueSubmit(mGraphicsQueue, 1, &submitInfo, mFenceCanStartNewFrame) != VK_SUCCESS) { throw std::runtime_error("failed to submit to graphics queue"); } VkSwapchainKHR swapchains[] = { mSwapChain }; VkPresentInfoKHR presentInfo { .sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR, .waitSemaphoreCount = 1, .pWaitSemaphores = signalSemaphores, .swapchainCount = 1, .pSwapchains = swapchains, .pImageIndices = &imageIndex, }; vkQueuePresentKHR(mPresentQueue, &presentInfo); /* Somehow res is always VK_SUCCESS if (res == VK_ERROR_OUT_OF_DATE_KHR || res == VK_SUBOPTIMAL_KHR) { recreateSwapChain(); } else if (res != VK_SUCCESS) { throw std::runtime_error("cannot acquire new khr image"); } */ } void destroySynchronizationObjects() { vkDestroySemaphore(mDevice, mSemaphoreImageAcquired, nullptr); vkDestroySemaphore(mDevice, mSemaphoreFramebufferDrawn, nullptr); vkDestroyFence(mDevice, mFenceCanStartNewFrame, nullptr); } void cleanup() { destroyBuffer(mVertexBuffer, mVertexBufferMemory); destroyBuffer(mIndexBuffer, mIndexBufferMemory); destroySynchronizationObjects(); destroyCommandPool(); destroySwapChainFramebuffers(); vkDestroyRenderPass(mDevice, mGraphicsRenderPass, nullptr); destroyGraphicsPipeline(); destroySwapChainImageViews(); destroySwapChain(); vkDestroyDevice(mDevice, nullptr); vkDestroySurfaceKHR(mInstance, mSurface, nullptr); vkDestroyInstance(mInstance, nullptr); glfwDestroyWindow(mWindow); glfwTerminate(); } private: GLFWwindow *mWindow = nullptr; bool mWindowSizeDirtyFlag = true; TimePoint mWindowSizeDirtyFlagTime = std::chrono::system_clock::now(); TimeMs mWindowSizeApplyMinDelay = 200; std::pair mWindowFramebufferSize = { 0, 0 }; uint32_t mGraphicsQueueFamilyIndex = -1; uint32_t mPresentationQueueFamilyIndex = -1; VkInstance mInstance = VK_NULL_HANDLE; VkPhysicalDevice mPhysicalDevice = VK_NULL_HANDLE; VkDevice mDevice = VK_NULL_HANDLE; VkQueue mGraphicsQueue = VK_NULL_HANDLE; VkQueue mPresentQueue = VK_NULL_HANDLE; VkSurfaceKHR mSurface = VK_NULL_HANDLE; VkSwapchainKHR mSwapChain = VK_NULL_HANDLE; VkFormat mSwapChainFormat{}; VkExtent2D mSwapChainExtent{}; std::vector mSwapChainImages; std::vector mSwapChainImageViews; std::vector mSwapChainFrameBuffers; CustomShader mShader; VkPipeline mGraphicsPipeline = VK_NULL_HANDLE; VkRenderPass mGraphicsRenderPass = VK_NULL_HANDLE; VkPipelineLayout mGraphicsPipelineLayout{}; // no uniforms used in the shader VkCommandPool mCommandPool = VK_NULL_HANDLE; VkCommandBuffer mCommandBuffer = VK_NULL_HANDLE; VkSemaphore mSemaphoreImageAcquired = VK_NULL_HANDLE; VkSemaphore mSemaphoreFramebufferDrawn = VK_NULL_HANDLE; VkFence mFenceCanStartNewFrame = VK_NULL_HANDLE; VkBuffer mVertexBuffer = VK_NULL_HANDLE; VkDeviceMemory mVertexBufferMemory = VK_NULL_HANDLE; VkBuffer mIndexBuffer = VK_NULL_HANDLE; VkDeviceMemory mIndexBufferMemory = VK_NULL_HANDLE; }; int main() { Application app; try { app.run(); } catch (const std::exception &e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }