4553 字
23 分钟
Vulkan 学习记录(一) —— 画三角形
虽然之前已经学了 Godot 的渲染服务,但不清楚其实现原理。
于是前几天两天突然心血来潮,诞生了学 Vulkan 的念头。
之前学图形学时已经学过一些 OpenGL ,但是和 Vulkan 相比,OpenGL 的复杂度属实是小巫见大巫了。
早就对 Vulkan 的复杂度有所耳闻,据说几百行代码才能画出一个三角形。之前完全想象不到怎么会这么复杂,学习后才实际感受到 Vulkan 的自由度是如此之高。
跟着官方教程学了两个晚上,成功画出了第一个三角形。又花了一晚上研究,在注释中写了笔记,个人感觉已经把我觉得难理解的地方都解释清楚了。
以下是包含笔记的完整 cpp 代码,shader 代码参见官方教程。
main.cpp
#define VK_USE_PLATFORM_XLIB_KHR #define GLFW_INCLUDE_VULKAN #include <GLFW/glfw3.h> #define GLFW_EXPOSE_NATIVE_XLIB #include <GLFW/glfw3native.h> #include <vulkan/vulkan_core.h> #include <algorithm> #include <cstdint> #include <cstdlib> #include <cstring> #include <iostream> #include <limits> #include <optional> #include <set> #include <stdexcept> #include <vector> #include "constants.h" #include "utils.h" struct QueueFamilyIndices { std::optional<uint32_t> graphicsFamily; std::optional<uint32_t> presentFamily; bool isComplete() { return graphicsFamily.has_value() && presentFamily.has_value(); } }; struct SwapChainSupportDetails { VkSurfaceCapabilitiesKHR capabilities; std::vector<VkSurfaceFormatKHR> formats; std::vector<VkPresentModeKHR> presentModes; }; class HelloTriangleApplication { GLFWwindow* window; VkInstance instance; VkPhysicalDevice physicalDevice{VK_NULL_HANDLE}; VkDevice device; VkQueue graphicsQueue; VkSurfaceKHR surface; VkQueue presentQueue; VkSwapchainKHR swapChain; std::vector<VkImage> swapChainImages; VkFormat swapChainImageFormat; VkExtent2D swapChainExtent; std::vector<VkImageView> swapChainImageViews; std::vector<VkFramebuffer> swapChainFramebuffers; VkShaderModule vertShaderModule; VkShaderModule fragShaderModule; VkRenderPass renderPass; VkPipelineLayout pipelineLayout; VkPipeline graphicsPipeline; VkCommandPool commandPool; VkCommandBuffer commandBuffer; VkSemaphore imageAvailableSemaphore; VkSemaphore renderFinishedSemaphore; VkFence inFlightFence; public: void run() { initWindow(); initVulkan(); mainLoop(); cleanup(); } private: // 检查系统中是否有所需的验证层 bool checkValidationLayerSupport() { uint32_t layerCount; vkEnumerateInstanceLayerProperties(&layerCount, nullptr); std::vector<VkLayerProperties> avaliableLayers(layerCount); vkEnumerateInstanceLayerProperties(&layerCount, avaliableLayers.data()); for (const char* layerName : validationLayers) { bool layerFound{false}; for (const auto& layerProperties : avaliableLayers) { if (strcmp(layerName, layerProperties.layerName) == 0) { layerFound = true; break; } } if (!layerFound) { return false; } } return true; } // 创建 Vulkan 实例 void createInstance() { if (enableValidationLayers && !checkValidationLayerSupport()) { throw std::runtime_error("validation layers requested, but not available!"); } VkApplicationInfo applicationInfo{}; applicationInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO; applicationInfo.pApplicationName = "Hello Triangle"; applicationInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0); applicationInfo.pEngineName = "No Engine"; applicationInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0); applicationInfo.apiVersion = VK_API_VERSION_1_0; VkInstanceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO; createInfo.pApplicationInfo = &applicationInfo; uint32_t glfwExtentionCount = 0; const char** glfwExtentions; // 关于 Instance 的 Extensions 和 Device 的 Extensions 的区别参考这篇文章 // https://stackoverflow.com/questions/53050182/vulkan-difference-between-instance-and-device-extensions glfwExtentions = glfwGetRequiredInstanceExtensions(&glfwExtentionCount); createInfo.enabledExtensionCount = glfwExtentionCount; createInfo.ppEnabledExtensionNames = glfwExtentions; if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) { throw std::runtime_error("failed to create instance!"); } } // 查找所需的指令队列的序号 QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) { QueueFamilyIndices indices; uint32_t queueFamilyCount = 0; vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr); std::vector<VkQueueFamilyProperties> queueFamilies(queueFamilyCount); vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, queueFamilies.data()); int i = 0; for (const auto& queueFamily : queueFamilies) { if (queueFamily.queueFlags & VK_QUEUE_GRAPHICS_BIT) { indices.graphicsFamily = i; } VkBool32 presentSupport{false}; vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport); if (presentSupport) { indices.presentFamily = i; } if (indices.isComplete()) { break; } i++; } return indices; } // 检查显卡是否支持 deviceExtensions 常量数组中指定的 Extentions bool checkDeviceExtentionSupport(VkPhysicalDevice device) { uint32_t extensionCount; vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr); std::vector<VkExtensionProperties> availableExtensions(extensionCount); vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data()); std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end()); for (const auto& extension : availableExtensions) { requiredExtensions.erase(extension.extensionName); } return requiredExtensions.empty(); } // 检查 GPU 是否支持程序所需的功能 // 例如纹理压缩、64位浮点数和多视口渲染、几何着色器等等 // 除了判断是否支持必备特性,还可以用打分的形式选出对可选特性支持最好的GPU bool isDeviceSuitable(VkPhysicalDevice device) { auto indecies = findQueueFamilies(device); bool extentionsSupported = checkDeviceExtentionSupport(device); bool swapChainAdequate{false}; if (extentionsSupported) { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device); swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty(); } return indecies.isComplete() && extentionsSupported && swapChainAdequate; } // 查找满足程序要求的GPU void pickPhysicalDevice() { uint32_t deviceCount = 0; vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr); if (deviceCount == 0) { throw std::runtime_error("failed to find GPUs with Vulkan support!"); } std::vector<VkPhysicalDevice> avaliableDevices(deviceCount); vkEnumeratePhysicalDevices(instance, &deviceCount, avaliableDevices.data()); for (const auto& device : avaliableDevices) { if (isDeviceSuitable(device)) { physicalDevice = device; break; } } if (physicalDevice == VK_NULL_HANDLE) { throw std::runtime_error("failed to find a suitable GPU!"); } VkPhysicalDeviceProperties deviceProperties; vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties); std::cout << "Picked GPU: " << deviceProperties.deviceName << std::endl; } // 创建逻辑设备 // 一个物理设备可以创建多个逻辑设备,每个逻辑设备根据启用选用物理设备中的部分功能 void createLogicalDevice() { QueueFamilyIndices indices = findQueueFamilies(physicalDevice); std::vector<VkDeviceQueueCreateInfo> queueCreateInfos; std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()}; float queuePriority = 1.0f; for (uint32_t queueFamily : uniqueQueueFamilies) { VkDeviceQueueCreateInfo queueCreateInfo{}; queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO; queueCreateInfo.queueFamilyIndex = queueFamily; queueCreateInfo.queueCount = 1; queueCreateInfo.pQueuePriorities = &queuePriority; queueCreateInfos.push_back(queueCreateInfo); } VkDeviceCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO; createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size()); createInfo.pQueueCreateInfos = queueCreateInfos.data(); VkPhysicalDeviceFeatures deviceFeatures{}; createInfo.pEnabledFeatures = &deviceFeatures; createInfo.enabledExtensionCount = static_cast<uint32_t>(deviceExtensions.size()); createInfo.ppEnabledExtensionNames = deviceExtensions.data(); if (enableValidationLayers) { createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size()); createInfo.ppEnabledLayerNames = validationLayers.data(); } else { createInfo.enabledLayerCount = 0; } if (vkCreateDevice(physicalDevice, &createInfo, nullptr, &device) != VK_SUCCESS) { throw std::runtime_error("failed to create logical device!"); } vkGetDeviceQueue(device, indices.graphicsFamily.value(), 0, &graphicsQueue); vkGetDeviceQueue(device, indices.presentFamily.value(), 0, &presentQueue); } // 创建 Surface,其实就是绑定到 GLFW 窗口 void createSurface() { if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) { throw std::runtime_error("failed to create window surface!"); } } // 查询设备对 SwapChain 的相关支持 SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) { SwapChainSupportDetails details; vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities); uint32_t formatCount; vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr); if (formatCount != 0) { details.formats.resize(formatCount); vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data()); } uint32_t presentModeCount; vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr); if (presentModeCount != 0) { details.presentModes.resize(presentModeCount); vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data()); } return details; } // 以下三个函数用于选择 SwapChain 的属性 // 选择格式和颜色空间,格式影响如何解释内存布局,颜色空间影响怎么解释其中的值 VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) { for (const auto& availableFormat : availableFormats) { if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) { return availableFormat; } } // 找不到理想的Format,则返回第一个 return availableFormats[0]; } // 选择显示模式,主要影响 SwapChain 从图像队列中存取图像的策略 VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) { for (const auto& availablePresentMode : availablePresentModes) { if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) { return availablePresentMode; } } // 找不到理想的 PresentMode,返回总是可用的FIFO MODE return VK_PRESENT_MODE_FIFO_KHR; } // 选择 SwapChain 中图像的尺寸 VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) { // Extent 的 width 为 uint 32 上限时表示允许程序自己选择 SwapChain 的尺寸 if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) { // 如果 capabilities 中已经指定了 SwapChain 的尺寸 ,直接使用 return capabilities.currentExtent; } else { // 否则自己选择合适的尺寸 int width, height; glfwGetFramebufferSize(window, &width, &height); VkExtent2D actualExtent = {static_cast<uint32_t>(width), static_cast<uint32_t>(height)}; // 尽量贴合屏幕分辨率,但不能超出设备支持的 SwapChainExtent 范围。 actualExtent.width = std::clamp(actualExtent.width, capabilities.minImageExtent.width, capabilities.maxImageExtent.width); actualExtent.height = std::clamp(actualExtent.height, capabilities.minImageExtent.height, capabilities.maxImageExtent.height); return actualExtent; } } // 创建 SwapChain // Vulkan 不假定渲染的目标是用于显示到屏幕上,这样可以提高离屏渲染的效率 // 因此为了展示渲染结果,就需要使用 SwapChain 扩展,其实就是一个 Image 队列 void createSwapChain() { SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice); VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats); VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes); VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities); swapChainImageFormat = surfaceFormat.format; swapChainExtent = extent; // 选择 SwapChain 中有几张图像 // 加一以确保至少有两张,一张用于显示,一张用于绘制,这样才能避免画面撕裂 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; // 采用和 surface 相同的 format 和 colorSpace createInfo.imageFormat = surfaceFormat.format; createInfo.imageColorSpace = surfaceFormat.colorSpace; createInfo.imageExtent = extent; // 非 3D 纹理,Layers 都是 1 createInfo.imageArrayLayers = 1; // 指定该 SwapChain 的渲染目标是用于直接展示的 colorAttachment createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT; QueueFamilyIndices indices = findQueueFamilies(physicalDevice); uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()}; // 多个队列并发访问 SwapChain 时,如何处理 if (indices.graphicsFamily != indices.presentFamily) { // 如果两个 QueueFamily 不同,则采用 CONCURRENT 模式,且指定允许两个队列同时访问 createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT; createInfo.queueFamilyIndexCount = 2; createInfo.pQueueFamilyIndices = queueFamilyIndices; } else { // 如果相同,则采用 EXLUSIVE 模式即可,因为 CONCURRENT 模式至少要求有两个 QueueFamily createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE; createInfo.queueFamilyIndexCount = 0; // Optional createInfo.pQueueFamilyIndices = nullptr; // Optional } // 是否对图形做变换,这里的 currentTransform 表示不做变换 createInfo.preTransform = swapChainSupport.capabilities.currentTransform; // 当和其他窗口重叠时,要不要进行颜色混合 // 这里选择直接覆盖,通常也都是这么做 createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR; createInfo.presentMode = presentMode; // clipped 表示是否不渲染窗口被遮挡的部分 createInfo.clipped = VK_TRUE; // 当窗口大小变换时,SwapChain 会失效,此属性决定怎么处理旧的 SwapChain,此处先直接抛弃 createInfo.oldSwapchain = VK_NULL_HANDLE; if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) { throw std::runtime_error("failed to create swap chain!"); } // 获取 SwapChain 中的 Images,按我的理解这些 Images 构成一个循环队列 vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr); swapChainImages.resize(imageCount); vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data()); } // 创建 ImageViews // 关于 ImageViews 的作用看这篇文章 https://stackoverflow.com/questions/39557141/what-is-the-difference-between-framebuffer-and-image-in-vulkan void createImageViews() { swapChainImageViews.resize(swapChainImages.size()); for (size_t i = 0; i < swapChainImages.size(); i++) { VkImageViewCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO; createInfo.image = swapChainImages[i]; // viewType 表示该 View 被视为什么,此处表示被视为 2D 纹理 createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D; // format 表示如何解释 Image 的数据,可以与 Image 的 Format 不同 // 例如,加入 Image 的格式是 R8G8B8A8, View 的格式可以是 R16G16 // 不过此处使用的是相同的格式 createInfo.format = swapChainImageFormat; // components 改变各个通道的映射关系,例如可以把红色映射到绿色通道上,也可以映射为 0 或 1 的常量 // 此处表示的是不改变映射关系 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; // subresourceRange 表示该 View 会覆盖 Image 的哪些部分,也就是通过该 View 能访问的部分 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) { throw std::runtime_error("failed to create image views!"); } } } VkShaderModule createShaderModule(const std::vector<char>& code) { VkShaderModuleCreateInfo createInfo{}; createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO; createInfo.codeSize = code.size(); createInfo.pCode = reinterpret_cast<const uint32_t*>(code.data()); VkShaderModule shaderModule; if (vkCreateShaderModule(device, &createInfo, nullptr, &shaderModule) != VK_SUCCESS) { throw std::runtime_error("failed to create shader module!"); } return shaderModule; } // 创建 Render Pass void createRenderPass() { VkAttachmentDescription colorAttachment{}; colorAttachment.format = swapChainImageFormat; colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT; // 以下两个属性在渲染开始和渲染结束时如何处理 Attachment 中的数据 // 此处设为在开始时清除,在结束时保留 colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR; colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE; // 以下两个属性指定图像的布局如何过渡,以和前后的操作衔接 // 此处设为在渲染开始时不关心布局,在结束时要求为可供 SwapChain 展示的布局 colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED; colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR; // 每个 Subpass 可以引用到一个或多个所属 Render Pass 持有的 Attachment // 每个 AttachmentReference 就表示一个引用关系 // 此处引用 0 号,也就是该 Render Pass 中的第一个 Attachment,对应片段着色器中的 layout(location = 0) out vec4 outColor; VkAttachmentReference colorAttachmentRef{}; colorAttachmentRef.attachment = 0; colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL; // 指定该 SubPass 用于处理图形 VkSubpassDescription subpass{}; subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS; // 指定该 SubPass 中包含的 Attachments 引用 subpass.colorAttachmentCount = 1; subpass.pColorAttachments = &colorAttachmentRef; // 指定 SubPass 中步骤间的依赖关系 // 此处表示 0 号步骤,也就是第一步的 VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT 步骤的 VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT 操作依赖于隐式的 -1 步的 // VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT 阶段完成 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_WRITE_BIT; // 创建 Render Pass VkRenderPassCreateInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO; renderPassInfo.attachmentCount = 1; renderPassInfo.pAttachments = &colorAttachment; renderPassInfo.subpassCount = 1; renderPassInfo.pSubpasses = &subpass; renderPassInfo.dependencyCount = 1; renderPassInfo.pDependencies = &dependency; if (vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass) != VK_SUCCESS) { throw std::runtime_error("failed to create render pass!"); } } void createGraphicsPipeline() { // 加载 shader 的字节码 auto vertShaderCode = readFile("shaders/vert.spv"); auto fragShaderCode = readFile("shaders/frag.spv"); // 将字节码用 ShaderModule 包装,这是一种专门用于持有 shader 字节码的结构 vertShaderModule = createShaderModule(vertShaderCode); fragShaderModule = createShaderModule(fragShaderCode); // 创建 VertextShaderStage 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"; // 创建 FragmentShaderStage 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}; // 由于目前顶点数据硬编码在 shader 中,不需要接受输入 VkPipelineVertexInputStateCreateInfo vertexInputInfo{}; vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO; vertexInputInfo.vertexBindingDescriptionCount = 0; vertexInputInfo.vertexAttributeDescriptionCount = 0; // 解析顶点的方式,因为要画三角形,所以设定为 TRIANGLE_LIST,也就是三个顶点一组视为一个三角形 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; // 这里采用静态的方式指定上面创建好的视口和裁剪区域 // 如果采用动态的方式,需要创建 VkPipelineDynamicStateCreateInfo,并在绘制时传入实际数据 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_CLOCKWISE; rasterizer.depthBiasEnable = VK_FALSE; // 设置多重采样,也就是抗锯齿 // 此处先不开启该功能 VkPipelineMultisampleStateCreateInfo multisampling{}; multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO; multisampling.sampleShadingEnable = VK_FALSE; multisampling.rasterizationSamples = VK_SAMPLE_COUNT_1_BIT; // 颜色混合方案,此处先不开启该功能 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_FALSE; // 全局颜色混合方案,包含所有的 ColorBlendAttachment,且还可以再进行混合,此处先不开启 VkPipelineColorBlendStateCreateInfo colorBlending{}; colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO; colorBlending.logicOpEnable = VK_FALSE; colorBlending.attachmentCount = 1; colorBlending.pAttachments = &colorBlendAttachment; // Pipeline 布局,用于指定 uniform 变量的传递方式,此处先留空 VkPipelineLayoutCreateInfo pipelineLayoutInfo{}; pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO; if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) { throw std::runtime_error("failed to create pipeline layout!"); } // 创建 Pipeline 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.pColorBlendState = &colorBlending; pipelineInfo.layout = pipelineLayout; pipelineInfo.renderPass = renderPass; pipelineInfo.subpass = 0; if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) { throw std::runtime_error("failed to create graphics pipeline!"); } } // 创建 Render Pass 时只是定义了流程中需要哪些 Attachments ,但并没有指定这些 Attachments 的实际储存位置 // 因此需要由 FrameBuffer 来提供 Attachments 的实例 // 对于每一个 Image,都需要一个 FrameBuffer void createFramebuffers() { swapChainFramebuffers.resize(swapChainImageViews.size()); for (size_t i = 0; i < swapChainImageViews.size(); i++) { // 由于此处每个 Image 只有一个 colorAttachment,所以 FrameBuffer 也只包含一个 attachment VkImageView attachments[] = {swapChainImageViews[i]}; VkFramebufferCreateInfo framebufferInfo{}; framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO; framebufferInfo.renderPass = renderPass; framebufferInfo.attachmentCount = 1; // 这里就是实际引用了储存的 ImageViews 作为 Attachments 的实例 framebufferInfo.pAttachments = attachments; framebufferInfo.width = swapChainExtent.width; framebufferInfo.height = swapChainExtent.height; framebufferInfo.layers = 1; if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) { throw std::runtime_error("failed to create framebuffer!"); } } } // 创建 CommandPool,用于管理 CommandBuffers 的生命周期和提交目标 void createCommandPool() { QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice); VkCommandPoolCreateInfo poolInfo{}; poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO; // 以下标记表示 Pool 中的 Buffers 可以被单独重置,而不是必须一起重置 // 之所以采用这个方案,是因为有些 Buffers 可能变化不频繁,可以一次录制多次使用 poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT; // queueFamilyIndex 指定了该 Pool 中的 Buffers 能被提交到哪些队列 poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value(); if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) { throw std::runtime_error("failed to create command pool!"); } } // 创建 CommandBuffers,用于录制 Commands // Vulkan 中的命令都是一组一组提交的,这样可以提高效率,CommandBuffer 就是用于提交一组命令 void createCommandBuffer() { VkCommandBufferAllocateInfo allocInfo{}; allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO; allocInfo.commandPool = commandPool; // Primary 级别表示该 Buffer 用于直接提交到队列中 // 与之对应的级别是 Secondary,表示不能别直接提交,而是被 Primary Buffer 调用 allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY; allocInfo.commandBufferCount = 1; if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to allocate command buffers!"); } } // 创建同步用的对象 void createSyncObjects() { VkSemaphoreCreateInfo semaphoreInfo{}; semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO; VkFenceCreateInfo fenceInfo{}; fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO; // 让 fence 一开始处于打开状态,否则无法开始第一次绘制 fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT; // inFlightFence 用于表示上一次渲染是否完成 // imageAvailableSemaphore 用于表示 Image 是否准备完毕,可以用于写入 // renderFinishedSemaphore 用于表示渲染是否完成,可以用于展示到屏幕 if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS || vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS || vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) { throw std::runtime_error("failed to create semaphores!"); } } // 初始化窗口 void initWindow() { glfwInit(); // GLFW 最初是为了 OpenGL 设计的,现在用的是 Vulkan,要提示关闭 OpenGL 的 API glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API); glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE); window = glfwCreateWindow(800, 600, "Vulkan", nullptr, nullptr); } // 初始化 Vulkan void initVulkan() { createInstance(); createSurface(); pickPhysicalDevice(); createLogicalDevice(); createSwapChain(); createImageViews(); createRenderPass(); createGraphicsPipeline(); createFramebuffers(); createCommandPool(); createCommandBuffer(); createSyncObjects(); } void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) { VkCommandBufferBeginInfo beginInfo{}; beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO; // 开始录制命令 if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) { throw std::runtime_error("failed to begin recording command buffer!"); } // 启用 Render Pass 所需的信息 VkRenderPassBeginInfo renderPassInfo{}; renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex]; // 渲染区域的位置和大小,通常和 SwapChain 的一致 renderPassInfo.renderArea.offset = {0, 0}; renderPassInfo.renderArea.extent = swapChainExtent; // 指定背景色 VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}}; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; // 启用 Render Pass,该操作会导致产生一个 Render Pass 的临时实例 vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); // 绑定 Pipeline vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline); // 关于 Render Pass 和 Pipeline 的关系,我的理解是: // Render Pass 就像一份清单,记录了从原材料到成品要经过哪些工序,每个工序中输入是什么,输出是什么,量有多少 // 而 Pipeline 则是产房的布局,规定了流水线上具体使用哪些机器来完成这些工序,可能有多个工序用到同一种机器 // Pipeline 只需要创建好就不变了,后续能一直使用(Bind) // 而 Render Pass 中包含可变的内容,可以看到上面动态设置了很多参数,因此每次绘制都需要创建新的实例(Begin),作为本次渲染的上下文 // 发出 Draw 命令,开始绘制 vkCmdDraw(commandBuffer, 3, 1, 0, 0); // 结束 Render Pass,释放相关上下文 vkCmdEndRenderPass(commandBuffer); // 结束录制命令 if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) { throw std::runtime_error("failed to record command buffer!"); } } void drawFrame() { // 等待上一次渲染完成 vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX); vkResetFences(device, 1, &inFlightFence); // 储存要渲染的目标图片的序号 uint32_t imageIndex; // 此处让目标图片准备好后,发出一个信号量 vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex); // 清空 CommandBuffer vkResetCommandBuffer(commandBuffer, 0); // 记录所需的命令 recordCommandBuffer(commandBuffer, imageIndex); VkSubmitInfo submitInfo{}; submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO; // 渲染流程中,在片段着色器之前的步骤,如顶点着色器,可以不依赖于目标图片的内存 // 而片段着色器后的步骤,都需要往目标图片里写入东西 // 因此让 COLOR_ATTACHMENT_OUTPUT 阶段等收到图片准备好的信号量后,再继续进行 // 这样可以让能并行的步骤充分并行 VkSemaphore waitSemaphores[] = {imageAvailableSemaphore}; VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT}; submitInfo.waitSemaphoreCount = 1; submitInfo.pWaitSemaphores = waitSemaphores; submitInfo.pWaitDstStageMask = waitStages; submitInfo.commandBufferCount = 1; submitInfo.pCommandBuffers = &commandBuffer; // 此处让渲染完成后,发出一个信号量 VkSemaphore signalSemaphores[] = {renderFinishedSemaphore}; submitInfo.signalSemaphoreCount = 1; submitInfo.pSignalSemaphores = signalSemaphores; if (vkQueueSubmit(graphicsQueue, 1, &submitInfo, inFlightFence) != VK_SUCCESS) { throw std::runtime_error("failed to submit draw command buffer!"); } 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; vkQueuePresentKHR(presentQueue, &presentInfo); } // 事件循环 void mainLoop() { while (!glfwWindowShouldClose(window)) { // 处理 GLFW 的事件 glfwPollEvents(); // 进行绘制 drawFrame(); } vkDeviceWaitIdle(device); } void cleanSwapChainImageViews() { for (auto imageView : swapChainImageViews) { vkDestroyImageView(device, imageView, nullptr); } } void cleanFrameBuffers() { for (auto framebuffer : swapChainFramebuffers) { vkDestroyFramebuffer(device, framebuffer, nullptr); } } void cleanSemaphores() { vkDestroySemaphore(device, imageAvailableSemaphore, nullptr); vkDestroySemaphore(device, renderFinishedSemaphore, nullptr); vkDestroyFence(device, inFlightFence, nullptr); } // 资源释放 void cleanup() { cleanSemaphores(); vkDestroyCommandPool(device, commandPool, nullptr); cleanFrameBuffers(); vkDestroyPipeline(device, graphicsPipeline, nullptr); vkDestroyPipelineLayout(device, pipelineLayout, nullptr); vkDestroyRenderPass(device, renderPass, nullptr); cleanSwapChainImageViews(); vkDestroySwapchainKHR(device, swapChain, nullptr); vkDestroyShaderModule(device, fragShaderModule, nullptr); vkDestroyShaderModule(device, vertShaderModule, nullptr); vkDestroyDevice(device, nullptr); vkDestroySurfaceKHR(instance, surface, nullptr); vkDestroyInstance(instance, nullptr); glfwDestroyWindow(window); glfwTerminate(); } }; int main() { HelloTriangleApplication app; try { app.run(); } catch (const std::exception& e) { std::cerr << e.what() << std::endl; return EXIT_FAILURE; } return EXIT_SUCCESS; }
constants.h
#pragma once #include <cstdint> #include <vector> #include <vulkan/vulkan.h> #ifdef NDEBUG const bool enableValidationLayers = false; #else const bool enableValidationLayers = true; #endif const uint32_t WIDTH = 800; const uint32_t HEIGHT = 600; const std::vector<const char*> validationLayers = { "VK_LAYER_KHRONOS_validation" }; const std::vector<const char*> deviceExtensions = { VK_KHR_SWAPCHAIN_EXTENSION_NAME };
utils.h
#pragma once #include <string> #include <vector> std::vector<char> readFile(const std::string& filename);
utils.cpp
#include "utils.h" #include <fstream> #include <vector> std::vector<char> 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<char> buffer(fileSize); file.seekg(0); file.read(buffer.data(), fileSize); file.close(); return buffer; }
Vulkan 学习记录(一) —— 画三角形
https://lry722.github.io/posts/vulkan-学习记录一--画三角形/