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-学习记录一--画三角形/