Vulkan入门系列3 - 绘制三角形代码解析(全)

一:概述

       本节对Vulkan绘制三角形代码做一个详细的解析,详细情况看代码注释。

二:代码 

#define GLFW_INCLUDE_VULKAN
#include <GLFW/glfw3.h>

#include <iostream>
#include <fstream>
#include <stdexcept> //std::exception, std::runtime_error
#include <algorithm>
#include <vector>
#include <cstring> 
#include <cstdlib> //EXIT_SUCCESS, EXIT_FAILURE
#include <cstdint>
#include <limits>
#include <optional> //std::optional
#include <set>
#include <filesystem>

//窗口宽度
const uint32_t WIDTH = 800;
//窗口高度
const uint32_t HEIGHT = 600;


//验证层扩展,启用此层可以帮助开发者诊断错误,验证Vulkan API使用的正确性
const std::vector<const char*> validationLayers = {
    "VK_LAYER_KHRONOS_validation"
};

//交换链扩展,使用此扩展可以将渲染结果显示在窗口表面上
const std::vector<const char*> deviceExtensions = {
    VK_KHR_SWAPCHAIN_EXTENSION_NAME
};

//如果是Debug模式
#ifdef NDEBUG
//开启校验层
const bool enableValidationLayers = false;
//如果是Release模式
#else
//关闭校验层
const bool enableValidationLayers = true;
#endif

//获取DebugMessage扩展函数指针,并创建VkDebugUtilsMessengerEXT对象
VkResult CreateDebugUtilsMessengerEXT(VkInstance instance, const VkDebugUtilsMessengerCreateInfoEXT* pCreateInfo, const VkAllocationCallbacks* pAllocator, VkDebugUtilsMessengerEXT* pDebugMessenger) {
    //查找 PFN_vkCreateDebugUtilsMessengerEXT 函数实例
    auto func = (PFN_vkCreateDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkCreateDebugUtilsMessengerEXT");
    //如果找到该函数
    if (func != nullptr) {
        //创建VkDebugUtilsMessengerEXT对象
        return func(instance, pCreateInfo, pAllocator, pDebugMessenger);
    } else {
        //返回扩展不存在
        return VK_ERROR_EXTENSION_NOT_PRESENT;
    }
}

//释放VkDebugUtilsMessengerEXT对象
void DestroyDebugUtilsMessengerEXT(VkInstance instance, VkDebugUtilsMessengerEXT debugMessenger, const VkAllocationCallbacks* pAllocator) {
    //查找 vkDestroyDebugUtilsMessengerEXT 函数实例
    auto func = (PFN_vkDestroyDebugUtilsMessengerEXT) vkGetInstanceProcAddr(instance, "vkDestroyDebugUtilsMessengerEXT");
    //如果找到该函数
    if (func != nullptr) {
        //释放VkDebugUtilsMessengerEXT对象
        func(instance, debugMessenger, pAllocator);
    }
}

//存放队列族信息的结构体
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;
    //可用的显示模式,有立即显示模式,FIFO显示模式等
    std::vector<VkPresentModeKHR> presentModes;
};

//渲染三角形的app类
class HelloTriangleApplication {
public:
    //运行app
    void run() {
        //初始化窗口
        initWindow();
        //初始化Vulkan相关资源
        initVulkan();
        //渲染循环
        mainLoop();
        //清理资源
        cleanup();
    }

private:
    //GLFW 窗口
    GLFWwindow* window;

    //Vulkan 实例
    VkInstance instance;

    //用来存储回调函数信息
    VkDebugUtilsMessengerEXT debugMessenger;

    //Vulkan 表面
    VkSurfaceKHR surface;

    //Vulkan 物理设备
    VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;

    //Vulkan 逻辑设备
    VkDevice device;

    //渲染队列
    VkQueue graphicsQueue;
    //送显队列
    VkQueue presentQueue;

    //交换链
    VkSwapchainKHR swapChain;

    //交换链中的图像
    std::vector<VkImage> swapChainImages;
    //交换链中图像格式
    VkFormat swapChainImageFormat;
    //交换链中图像尺寸
    VkExtent2D swapChainExtent;
    //交换链中图像视图
    std::vector<VkImageView> swapChainImageViews;
    //交换链中的帧缓冲区
    std::vector<VkFramebuffer> swapChainFramebuffers;

    //渲染通道
    VkRenderPass renderPass;
    //渲染管线布局(通过它可以访问管线描述符集合,进而可以将管线中着色器与数据绑定起来)
    VkPipelineLayout pipelineLayout;
    //渲染管线对象
    VkPipeline graphicsPipeline;

    //命令池,用来分配命令缓冲区的
    VkCommandPool commandPool;
    //命令缓冲区,用来存放命令的
    VkCommandBuffer commandBuffer;

    //图像呈现信号量
    VkSemaphore imageAvailableSemaphore;
    //渲染完成信号量
    VkSemaphore renderFinishedSemaphore;
    //栅栏对象,同于同步两个Command
    VkFence inFlightFence;

    //初始化窗口
    void initWindow() {
        //glftInit初始化
        glfwInit();

        //为Vulkan的设置,告诉GLFW不要创建OpenGL上下文了
        glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
        //禁止调整窗口大小
        glfwWindowHint(GLFW_RESIZABLE, GLFW_FALSE);

        //创建窗口
        window = glfwCreateWindow(WIDTH, HEIGHT, "Vulkan", nullptr, nullptr);
    }
    
    //初始化,渲染前准备工作
    void initVulkan() {
        //创建VkInstance对象
        createInstance();
        //设置Debug Layer
        setupDebugMessenger();
        //创建渲染表面
        createSurface();
        //选择物理设备
        pickPhysicalDevice();
        //创建逻辑设备
        createLogicalDevice();
        //创建交换链
        createSwapChain();
        //创建图像视图
        createImageViews();
        //创建渲染通道
        createRenderPass();
        //创建渲染管线
        createGraphicsPipeline();
        //创建帧缓冲区
        createFramebuffers();
        //创建命令池
        createCommandPool();
        //创建命令缓缓冲区
        createCommandBuffer();
        //创建同步对象
        createSyncObjects();
    }

    //主循环
    void mainLoop() {
        //如果窗口不关闭,循环
        while (!glfwWindowShouldClose(window)) {
            //处理窗口事件
            glfwPollEvents();
            //渲染一帧
            drawFrame();
        }
        //等待设备空闲
        vkDeviceWaitIdle(device);
    }

    //清理释放资源
    void cleanup() {
        //释放信号量
        vkDestroySemaphore(device, renderFinishedSemaphore, nullptr);
        //释放信号量
        vkDestroySemaphore(device, imageAvailableSemaphore, nullptr);
        //释放栅栏对象
        vkDestroyFence(device, inFlightFence, nullptr);

        //释放命令池对象
        vkDestroyCommandPool(device, commandPool, nullptr);

        //循环释放帧缓冲区对象
        for (auto framebuffer : swapChainFramebuffers) {
            //释放帧缓冲区对象
            vkDestroyFramebuffer(device, framebuffer, nullptr);
        }

        //释放渲染管线对象
        vkDestroyPipeline(device, graphicsPipeline, nullptr);
        //释放渲染管线布局对象
        vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
        //释放渲染通道对象
        vkDestroyRenderPass(device, renderPass, nullptr);

        //循环释放图像视图对象
        for (auto imageView : swapChainImageViews) {
            //释放图像视图对象
            vkDestroyImageView(device, imageView, nullptr);
        }

        //释放交换链对象
        vkDestroySwapchainKHR(device, swapChain, nullptr);
        //释放逻辑设备对象
        vkDestroyDevice(device, nullptr);

        //释放Debug Layer
        if (enableValidationLayers) {
            DestroyDebugUtilsMessengerEXT(instance, debugMessenger, nullptr);
        }

        //释放渲染表面对象
        vkDestroySurfaceKHR(instance, surface, nullptr);
        //释放Vulkan Instance
        vkDestroyInstance(instance, nullptr);

        //退出窗口
        glfwDestroyWindow(window);

        //退出glfw
        glfwTerminate();
    }

    //创建Vulkan 实例
    void createInstance() {
        //如果没开启校验层或者不支持校验层,则抛出一个异常
        if (enableValidationLayers && !checkValidationLayerSupport()) {
            throw std::runtime_error("validation layers requested, but not available!");
        }

        //为了创建VkInstance实例,首先要收集下应用程序信息
        VkApplicationInfo appInfo{};
        //必须是VK_STRUCTURE_TYPE_APPLICATION_INFO,指定结构体类型
        appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
        //应用程序名称
        appInfo.pApplicationName = "Hello Triangle";
        //应用程序版本
        appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
        //引擎名称,应用程序用的哪个引擎,此处无
        appInfo.pEngineName = "No Engine";
        //引擎版本,应用程序使用的引擎版本
        appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
        //Vulkan版本
        appInfo.apiVersion = VK_API_VERSION_1_0;

        //VkInstance创建信息
        VkInstanceCreateInfo createInfo{};
        //必须是VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO, 指定结构体类型
        createInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
        //前面的appInfo
        createInfo.pApplicationInfo = &appInfo;

        //获取全局(VulkanInstance级别)的扩展信息
        auto extensions = getRequiredExtensions();
        //扩展的数量
        createInfo.enabledExtensionCount = static_cast<uint32_t>(extensions.size());
        //都有哪些扩展
        createInfo.ppEnabledExtensionNames = extensions.data();

        //创建DebugManager对象,用来调试Vulkan
        VkDebugUtilsMessengerCreateInfoEXT debugCreateInfo{};
        //如果开启校验层
        if (enableValidationLayers) {
            //配置校验层数量
            createInfo.enabledLayerCount = static_cast<uint32_t>(validationLayers.size());
            //配置校验层列表
            createInfo.ppEnabledLayerNames = validationLayers.data();
            //配置DebugMessage信息
            populateDebugMessengerCreateInfo(debugCreateInfo);
            //在Vulkan中,所有结构的.pNext用来指定扩展信息,此处指向DebugLayer扩展
            createInfo.pNext = (VkDebugUtilsMessengerCreateInfoEXT*) &debugCreateInfo;
        } else {
            //否则开启层数为0
            createInfo.enabledLayerCount = 0;
            //且因为没有扩展信息,置空
            createInfo.pNext = nullptr;
        }

        //创建Vulkan实例
        if (vkCreateInstance(&createInfo, nullptr, &instance) != VK_SUCCESS) {
            //如果创建不成功,抛出异常
            throw std::runtime_error("failed to create instance!");
        }
    }

    //配置DebugMessage信息
    void populateDebugMessengerCreateInfo(VkDebugUtilsMessengerCreateInfoEXT& createInfo) {
        //初始化配置
        createInfo = {};
        //必须是VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT,设置结构体类型
        createInfo.sType = VK_STRUCTURE_TYPE_DEBUG_UTILS_MESSENGER_CREATE_INFO_EXT;
        //设置回调函数接收的消息级别 
        createInfo.messageSeverity = VK_DEBUG_UTILS_MESSAGE_SEVERITY_VERBOSE_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT;
        //设置回调函数接收的消息类型
        createInfo.messageType = VK_DEBUG_UTILS_MESSAGE_TYPE_GENERAL_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_VALIDATION_BIT_EXT | VK_DEBUG_UTILS_MESSAGE_TYPE_PERFORMANCE_BIT_EXT;
        //设置回调函数,当满足上面条件时会触发回调函数
        createInfo.pfnUserCallback = debugCallback;
    }

    //设置DebugMessager信息
    void setupDebugMessenger() {
        //如果不开启校验层时,就不进行Debug相关配置了
        if (!enableValidationLayers) return;

        //指定创建DebugMessenger对象的参数
        VkDebugUtilsMessengerCreateInfoEXT createInfo;
        //设置DebugMessager相关参数
        populateDebugMessengerCreateInfo(createInfo);

        //创建DebugMessenger对象
        if (CreateDebugUtilsMessengerEXT(instance, &createInfo, nullptr, &debugMessenger) != VK_SUCCESS) {
            //如果创建失败,抛出异常
            throw std::runtime_error("failed to set up debug messenger!");
        }
    }

    //创建渲染表面
    void createSurface() {
        //glfw调用原生窗口平台扩展来创建基于原生窗口的渲染表面
        if (glfwCreateWindowSurface(instance, window, nullptr, &surface) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to create window surface!");
        }
    }

    //选择物理设备
    void pickPhysicalDevice() {
        //设备数量
        uint32_t deviceCount = 0;
        //查询设备数量
        vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

        //如果没有设备
        if (deviceCount == 0) {
            //抛出异常
            throw std::runtime_error("failed to find GPUs with Vulkan support!");
        }

        //创建存放VkPhysicalDevice对象的列表
        std::vector<VkPhysicalDevice> devices(deviceCount);
        //枚举设备,填充VkPhysicalDevice对象信息
        vkEnumeratePhysicalDevices(instance, &deviceCount, devices.data());

        //循环查找是否有满足应用要求的设备
        for (const auto& device : devices) {
            //检查设备是否满足要求
            if (isDeviceSuitable(device)) {
                //找到设备
                physicalDevice = device;
                break;
            }
        }

        //如果没找到合适的设备
        if (physicalDevice == VK_NULL_HANDLE) {
            //抛出异常
            throw std::runtime_error("failed to find a suitable GPU!");
        }
    }

    //创建逻辑设备
    void createLogicalDevice() {
        //查找设备支持的队列族
        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);

        //创建一个vector,存放设备队列创建信息
        std::vector<VkDeviceQueueCreateInfo> queueCreateInfos;
        //设备队列族(图形 + 显示)
        std::set<uint32_t> uniqueQueueFamilies = {indices.graphicsFamily.value(), indices.presentFamily.value()};

        //队列优先级,Vulkan允许从0.0 到 1.0 为队列分配优先级,1.0是最高优先级
        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);
        }

        //初始化设备特性信息, 比如是否支持几何着色器,这里不需要,设置为空
        VkPhysicalDeviceFeatures deviceFeatures{};

        //初始化设备创建信息
        VkDeviceCreateInfo createInfo{};
        //设置结构体类别
        createInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
        //设备队列信息个数
        createInfo.queueCreateInfoCount = static_cast<uint32_t>(queueCreateInfos.size());
        //设备队列信息
        createInfo.pQueueCreateInfos = queueCreateInfos.data();
        //设备特性信息
        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 {
            //否则该字段设置为0
            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);
    }

    //创建交换链
    void createSwapChain() {
        //查询交换链的所支持的信息(基本表面能力,表面格式,可用的显示模式)
        SwapChainSupportDetails swapChainSupport = querySwapChainSupport(physicalDevice);

        //选择一种像素格式和颜色空间
        VkSurfaceFormatKHR surfaceFormat = chooseSwapSurfaceFormat(swapChainSupport.formats);
        //选择一种显示模式
        VkPresentModeKHR presentMode = chooseSwapPresentMode(swapChainSupport.presentModes);
        //选择一个屏幕分辨率
        VkExtent2D extent = chooseSwapExtent(swapChainSupport.capabilities);

        //确定交换链需要多少个图像,通常时会指定交换链运行所需最小数量 + 1,这样渲染线程就可以不必等待交换链显示完成,就可渲染另一个图像了。
        uint32_t imageCount = swapChainSupport.capabilities.minImageCount + 1;

        //如果图像个数超过了交换链所支持的最大图像个数
        if (swapChainSupport.capabilities.maxImageCount > 0 && imageCount > swapChainSupport.capabilities.maxImageCount) {
            //则截取到交换链所支持的最大图像个数
            imageCount = swapChainSupport.capabilities.maxImageCount;
        }

        //初始化交换链创建信息
        VkSwapchainCreateInfoKHR createInfo{};
        //设置结构体类型
        createInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
        //设置渲染表面,在这里交换链和原生窗口表面关联上了
        createInfo.surface = surface;

        //设置交换链运行所需的图像个数
        createInfo.minImageCount = imageCount;
        //设置像素格式
        createInfo.imageFormat = surfaceFormat.format;
        //设置颜色空间
        createInfo.imageColorSpace = surfaceFormat.colorSpace;
        //设置分辨率
        createInfo.imageExtent = extent;
        //设置每个图像的层数,除非开发立体3D应用,否则该值始终为1
        createInfo.imageArrayLayers = 1;
        //设置交换链中的图像用于何种操作,在这里用于渲染,所以要设为颜色附件
        createInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;

        //检查设备支持哪些队列族
        QueueFamilyIndices indices = findQueueFamilies(physicalDevice);
        //图形显示队列 和 送显队列
        uint32_t queueFamilyIndices[] = {indices.graphicsFamily.value(), indices.presentFamily.value()};

        //检查交换链中的图像是否跨队列族使用,如果图形队列和显示队列不同,就会跨队列使用
        if (indices.graphicsFamily != indices.presentFamily) {
            //设置图像可以跨队列族,不需要显示转移所有权
            createInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
            //设置跨队列族数量是2
            createInfo.queueFamilyIndexCount = 2;
            //设置跨队列族列表
            createInfo.pQueueFamilyIndices = queueFamilyIndices;
        } else {
            //设置图像被一个队列族拥有,在将其用于另一个队列前,必须显示转移所有权
            createInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
        }
        
        //设置图像是否支持旋转,如90度旋转
        createInfo.preTransform = swapChainSupport.capabilities.currentTransform;
        //设置是否支持与其它窗口透明度混合,本例不支持
        createInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
        //设置显示模式
        createInfo.presentMode = presentMode;
        //设置裁剪,如果被其他窗口遮挡自动裁剪掉被遮挡的部分,提高显示效率
        createInfo.clipped = VK_TRUE;

        //当窗口大小被调整时,交换链会变无效,必须重建交换链,所以这种情况必须将老的交换链保存起来,本例忽略
        createInfo.oldSwapchain = VK_NULL_HANDLE;

        //创建交换链
        if (vkCreateSwapchainKHR(device, &createInfo, nullptr, &swapChain) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to create swap chain!");
        }

        //获取交换链使用的图像数量,因为刚才我们只是指定了最小图像数量,这里返回实际的
        vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);
        //调整swapChainImages大小
        swapChainImages.resize(imageCount);
        //获取交换链中的图像,并存到swapChainImages中
        vkGetSwapchainImagesKHR(device, swapChain, &imageCount, swapChainImages.data());

        //将交换链格式保存到swapChainImageFormat中
        swapChainImageFormat = surfaceFormat.format;
        //将图像分辨率保存到swapChainExtent中
        swapChainExtent = extent;
    }

    //创建图像视图
    void createImageViews() {
        //调整swapChainImageViews大小,和swapChainImages一致
        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];
            //设置图像类别 2D
            createInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;
            //设置图像格式
            createInfo.format = swapChainImageFormat;
            //设置颜色通道,此处采用默认设置
            createInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
            createInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;
            //设置为彩色图像
            createInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
            //设置没有mipmapping 
            createInfo.subresourceRange.baseMipLevel = 0;
            //设置图像 mipmapping 级别为 1
            createInfo.subresourceRange.levelCount = 1;
            //设置单层,无多层图像
            createInfo.subresourceRange.baseArrayLayer = 0;
            //设置图像层数为 1
            createInfo.subresourceRange.layerCount = 1;

            //创建图像视图
            if (vkCreateImageView(device, &createInfo, nullptr, &swapChainImageViews[i]) != VK_SUCCESS) {
                //如果失败,抛出异常
                throw std::runtime_error("failed to create image views!");
            }
        }
    }

    //创建渲染通道
    void createRenderPass() {
        //设置颜色附件,在本例中,只有一个颜色附件,就是交换链中的一个图像
        VkAttachmentDescription colorAttachment{};
        //设置图像格式
        colorAttachment.format = swapChainImageFormat;
        //设置像素采样个数 1
        colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
        //loadOp定义了在显示前如何处理附件中的图像数据,本例是清除
        colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
        //storeOp定义了在显示后如何处理附件中的图像数据,本例是保留现有附件中的内容
        colorAttachment.storeOp = VK_ATTACHMENT_STORE_OP_STORE;
        //模板附件的数据在显示前如何处理,本例不关心
        colorAttachment.stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
        //模板附件的数据在显示后如何处理,本例不关心
        colorAttachment.stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
        //指定渲染通道开始前,图像数据的布局,本例不关心
        colorAttachment.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
        //指定渲染结束后,图像数据的布局,如果希望在渲染后能在交换链中显示,就设置为 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR
        colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

        //每个渲染通道引用一个或多个附件,这里设置附件引用信息
        VkAttachmentReference colorAttachmentRef{};
        //0 代表第一个附件,及上面设置的附件 
        colorAttachmentRef.attachment = 0;
        //在这里,我们打算将该附件作为颜色缓冲区使用
        colorAttachmentRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

        //Vulkan中一个渲染通道由一个或多个子通道组成,每一个子通道使用前一个通道中帧缓冲区的内容,本例中只有一个渲染子通道
        //设置渲染子通道信息
        VkSubpassDescription subpass{};
        //Vulkan将来要支持计算子通道,本例这里需显示指定为图像子通道
        subpass.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
        //颜色附件个数为 1
        subpass.colorAttachmentCount = 1;
        //设置颜色附件引用
        subpass.pColorAttachments = &colorAttachmentRef;

        //设置子通道间的依赖关系
        VkSubpassDependency dependency{};
        //本例只有一个子通道,因为是第一个子通道,它对前面没有任何依赖,所以设置为VK_SUBPASS_EXTERNAL
        dependency.srcSubpass = VK_SUBPASS_EXTERNAL;
        //下一个子通道要依赖当前子通道,所以索引设置为 0 
        dependency.dstSubpass = 0;
        //指定等待的操作(无),因为是第一个子通道,那就不同等待任何操作
        dependency.srcStageMask = VK_PIPELINE_STAGE_NONE_KHR;
        //指定等待发生的阶段(无),因为是第一个子通道,那就是不用等
        dependency.srcAccessMask = 0;
        //等待颜色附件输出阶段完成
        dependency.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
        //在颜色附件写出的阶段等待
        dependency.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

        //渲染通道创建信息
        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() {
        //获取当前工作目录
        auto path = std::filesystem::current_path(); 
        std::cout << "path" << path << std::endl;

        //读取顶点着色器代码
        auto vertShaderCode = readFile(path.string() + "/shaders/vert.spv");
        //读取片段着色器代码
        auto fragShaderCode = readFile(path.string() + "/shaders/frag.spv");

        //创建顶点着色器模块
        VkShaderModule vertShaderModule = createShaderModule(vertShaderCode);
        //创建片段着色器模块
        VkShaderModule fragShaderModule = createShaderModule(fragShaderCode);

        //将着色器分配特定的管线阶段
        VkPipelineShaderStageCreateInfo vertShaderStageInfo{};
        //设置结构体类别
        vertShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        //设置到顶点着色器阶段
        vertShaderStageInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
        //绑定顶点着色器模块
        vertShaderStageInfo.module = vertShaderModule;
        //着色器主函数名称
        vertShaderStageInfo.pName = "main";

        //将着色器分配特定的管线阶段
        VkPipelineShaderStageCreateInfo fragShaderStageInfo{};
        //设置结构体类型
        fragShaderStageInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
        //设置到片段着色器阶段
        fragShaderStageInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
        //绑定片段着色器模块
        fragShaderStageInfo.module = fragShaderModule;
        //着色器主函数名称
        fragShaderStageInfo.pName = "main";

        //着色器阶段信息
        VkPipelineShaderStageCreateInfo shaderStages[] = {vertShaderStageInfo, fragShaderStageInfo};

        //描述传递给顶点着色器的数据格式
        VkPipelineVertexInputStateCreateInfo vertexInputInfo{};
        //设置结构体类别
        vertexInputInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
        //设置顶点数据之间的间隔
        vertexInputInfo.vertexBindingDescriptionCount = 0;
        //传递给顶点着色器顶点属性数据个数,本例为0
        vertexInputInfo.vertexAttributeDescriptionCount = 0;

        //描述图元装配状态信息
        VkPipelineInputAssemblyStateCreateInfo inputAssembly{};
        //设置结构体类别
        inputAssembly.sType = VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO;
        //设置图像拓扑结构,每三个顶点组成一个三角形
        inputAssembly.topology = VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST;
        //设置图元重启,本例不设置,复杂形状采用到这个(如果顶点数据涉及图元重启的话)
        inputAssembly.primitiveRestartEnable = VK_FALSE;

        //设置viewport信息
        VkPipelineViewportStateCreateInfo viewportState{};
        viewportState.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
        //设置viewport 个数
        viewportState.viewportCount = 1;
        //设置裁剪器的个数
        viewportState.scissorCount = 1;

        //设置光栅化相关操作
        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;
        //线宽1.0
        rasterizer.lineWidth = 1.0f;
        //背面裁剪
        rasterizer.cullMode = VK_CULL_MODE_BACK_BIT;
        //逆时针为正面
        rasterizer.frontFace = VK_FRONT_FACE_CLOCKWISE;
        //设置depthBias, 此处不设置,这个一般用来解决两个三角形离得非常近而产生抖动的问题,
        rasterizer.depthBiasEnable = VK_FALSE;

        //设置多重采样
        VkPipelineMultisampleStateCreateInfo multisampling{};
        multisampling.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
        //不开启多重采样
        multisampling.sampleShadingEnable = VK_FALSE;
        //采样像素点默认为 1
        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;

        //设置管线的混合信息
        VkPipelineColorBlendStateCreateInfo colorBlending{};
        colorBlending.sType = VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO;
        //是否开启按位混合,此处为False
        colorBlending.logicOpEnable = VK_FALSE;
        //按位的操作
        colorBlending.logicOp = VK_LOGIC_OP_COPY;
        //附件个数
        colorBlending.attachmentCount = 1;
        //附件混合信息
        colorBlending.pAttachments = &colorBlendAttachment;
        //设置rgba的混合常数
        colorBlending.blendConstants[0] = 0.0f;
        colorBlending.blendConstants[1] = 0.0f;
        colorBlending.blendConstants[2] = 0.0f;
        colorBlending.blendConstants[3] = 0.0f;

        //管线中也允许少量动态更改状态信息,比如动态调整视口,裁剪窗口信息
        std::vector<VkDynamicState> dynamicStates = {
            VK_DYNAMIC_STATE_VIEWPORT,
            VK_DYNAMIC_STATE_SCISSOR
        };

        //设置动态状态信息
        VkPipelineDynamicStateCreateInfo dynamicState{};
        dynamicState.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
        //动态状态的数量
        dynamicState.dynamicStateCount = static_cast<uint32_t>(dynamicStates.size());
        //动态状态信息
        dynamicState.pDynamicStates = dynamicStates.data();

        //配置管线布局信息,用来设置给着色器传递数据,比如uniform等,本例不设置这些,只初始化一个空的结构体变量
        VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
        pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
        //布局数量为 0
        pipelineLayoutInfo.setLayoutCount = 0;
        //布局常量个数为 0 
        pipelineLayoutInfo.pushConstantRangeCount = 0;

        //创建管道布局对象
        if (vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to create pipeline layout!");
        }

        //图形管线创建信息
        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.pDynamicState = &dynamicState;
        //管线布局状态
        pipelineInfo.layout = pipelineLayout;
        //渲染通道状态
        pipelineInfo.renderPass = renderPass;
        //从第一个子通道开始绘制
        pipelineInfo.subpass = 0;
        //目前只有一个管线,设置为VK_NULL_HANDLE
        pipelineInfo.basePipelineHandle = VK_NULL_HANDLE;

        //创建渲染管线
        if (vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to create graphics pipeline!");
        }

        //释放两个着色器模块对象
        vkDestroyShaderModule(device, fragShaderModule, nullptr);
        vkDestroyShaderModule(device, vertShaderModule, nullptr);
    }

    //创建帧缓冲区
    void createFramebuffers() {
        //设置帧缓冲区个数同swapChainImageViews个数
        swapChainFramebuffers.resize(swapChainImageViews.size());

        //遍历 swapChainImageViews 
        for (size_t i = 0; i < swapChainImageViews.size(); i++) {
            //设置帧缓冲区挂载的图像视图
            VkImageView attachments[] = {
                swapChainImageViews[i]
            };

            //配置帧缓冲区创建信息
            VkFramebufferCreateInfo framebufferInfo{};
            framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
            //设置渲染通道
            framebufferInfo.renderPass = renderPass;
            //设置附件个数,1个图像视图
            framebufferInfo.attachmentCount = 1;
            //设置附件视图
            framebufferInfo.pAttachments = attachments;
            //设置帧缓冲的宽
            framebufferInfo.width = swapChainExtent.width;
            //设置帧缓冲的高
            framebufferInfo.height = swapChainExtent.height;
            //设置帧缓冲的层数 1, 是2D帧缓冲区   
            framebufferInfo.layers = 1;

            //创建帧缓冲区
            if (vkCreateFramebuffer(device, &framebufferInfo, nullptr, &swapChainFramebuffers[i]) != VK_SUCCESS) {
                //如果失败,抛出异常
                throw std::runtime_error("failed to create framebuffer!");
            }
        }
    }

    //创建命令池
    void createCommandPool() {
        //检查设备支持哪些队列族
        QueueFamilyIndices queueFamilyIndices = findQueueFamilies(physicalDevice);

        //配置命令池创建信息
        VkCommandPoolCreateInfo poolInfo{};
        poolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
        //允许单独地重置命令缓冲区,并重新记录
        poolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
        //设置命令队列族为图像队列族
        poolInfo.queueFamilyIndex = queueFamilyIndices.graphicsFamily.value();

        //创建命令缓池
        if (vkCreateCommandPool(device, &poolInfo, nullptr, &commandPool) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to create command pool!");
        }
    }

    //创建命令缓冲区
    void createCommandBuffer() {
        //配置命令缓冲区
        VkCommandBufferAllocateInfo allocInfo{};
        allocInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
        //配置命令池,缓冲区从该池中分配
        allocInfo.commandPool = commandPool;
        //主缓冲区,可以提到的命令队列中执行
        allocInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;
        //命令缓冲区个数为 1,本例我们只分配了一个命令缓冲区
        allocInfo.commandBufferCount = 1;

        //创建命令缓冲区
        if (vkAllocateCommandBuffers(device, &allocInfo, &commandBuffer) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to allocate command buffers!");
        }
    }

    //将想要执行的命令写入命令缓冲区中
    void recordCommandBuffer(VkCommandBuffer commandBuffer, uint32_t imageIndex) {
        //命令缓冲区开始信息 
        VkCommandBufferBeginInfo beginInfo{};
        beginInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;

        //总是通过 vkBeginCommandBuffer 来开始记录命令缓冲区
        if (vkBeginCommandBuffer(commandBuffer, &beginInfo) != VK_SUCCESS) {
            throw std::runtime_error("failed to begin recording command buffer!");
        }

        //为vkCmdBeginRenderPass配置一些参数信息,绘图马上开始
        VkRenderPassBeginInfo renderPassInfo{};
        renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
        //配置渲染通道
        renderPassInfo.renderPass = renderPass;
        //配置framebuffer
        renderPassInfo.framebuffer = swapChainFramebuffers[imageIndex];
        //配置绘制区域
        renderPassInfo.renderArea.offset = {0, 0};
        renderPassInfo.renderArea.extent = swapChainExtent;

        //配置清屏信息
        VkClearValue clearColor = {{{0.0f, 0.0f, 0.0f, 1.0f}}};
        //clear个数
        renderPassInfo.clearValueCount = 1;
        //clear颜色
        renderPassInfo.pClearValues = &clearColor;

        //开始绘图,渲染命令将会记录到commandBuffer中
        vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

        //绑定渲染管线,这管线里面记录了渲染状态
        vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);

        //配置动态渲染状态,视口信息
        VkViewport viewport{};
        //x 位置
        viewport.x = 0.0f;
        //y 位置
        viewport.y = 0.0f;
        //width 值
        viewport.width = static_cast<float>(swapChainExtent.width);
        //height 值
        viewport.height = static_cast<float>(swapChainExtent.height);
        //minDepth 值
        viewport.minDepth = 0.0f;
        //maxDepth 值
        viewport.maxDepth = 1.0f;
        //配置视口的命令
        vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

        //配置动态渲染状态,裁剪窗口信息
        VkRect2D scissor{};
        //offset 值
        scissor.offset = {0, 0};
        //分辨率
        scissor.extent = swapChainExtent;
        //配置裁剪窗口的命令
        vkCmdSetScissor(commandBuffer, 0, 1, &scissor);

        //绘制三角形命令
        vkCmdDraw(commandBuffer, 3, 1, 0, 0);

        //结束渲染通道
        vkCmdEndRenderPass(commandBuffer);

        //完成了命令缓冲区的记录
        if (vkEndCommandBuffer(commandBuffer) != VK_SUCCESS) {
            //如果失败,抛出异常
            throw std::runtime_error("failed to record command buffer!");
        }
    }

    //创建同步对象
    void createSyncObjects() {
        //信号量创建信息
        VkSemaphoreCreateInfo semaphoreInfo{};
        semaphoreInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;

        //栅栏创建信息
        VkFenceCreateInfo fenceInfo{};
        fenceInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
        //设置为有信号
        fenceInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

        //创建一个信号量 imageAvailableSemaphore,用来表示图像已经从交换链中获取并准备好进行渲染
        if (vkCreateSemaphore(device, &semaphoreInfo, nullptr, &imageAvailableSemaphore) != VK_SUCCESS ||
            //创建一个信号量 renderFinishedSemaphore,用来表示渲染已经完成并可以进行显示了
            vkCreateSemaphore(device, &semaphoreInfo, nullptr, &renderFinishedSemaphore) != VK_SUCCESS ||
            //创建一个栅栏 inFlightFence,用来确保一次只渲染一帧
            vkCreateFence(device, &fenceInfo, nullptr, &inFlightFence) != VK_SUCCESS) {
            throw std::runtime_error("failed to create synchronization objects for a frame!");
        }

    }

    //绘制一帧
    void drawFrame() {
        //在一帧的开始,我们要等待上一帧完成,以便命令缓冲区可用,为此调用vkWaitForFences等待,注意在第一帧时不会等,由于inFlightFence一开始是有信号的
        vkWaitForFences(device, 1, &inFlightFence, VK_TRUE, UINT64_MAX);
        //等到之后,手动将 inFlightFence 置为无信号状态 
        vkResetFences(device, 1, &inFlightFence);

        //交换链中的当前可用的图像索引
        uint32_t imageIndex;
        //查询交换链中当前可用的图像索引,交换链显示完图像后会将imageAvailableSemaphore置为有信号
        vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageAvailableSemaphore, VK_NULL_HANDLE, &imageIndex);

        //重置命令缓冲区
        vkResetCommandBuffer(commandBuffer, /*VkCommandBufferResetFlagBits*/ 0);
        //重新在缓冲区中记录命令, 使用imageIndex的图像
        recordCommandBuffer(commandBuffer, imageIndex);

        //记录完命令之后,现在要提交命令到硬件上执行了,先配置下提交命令的信息
        VkSubmitInfo submitInfo{};
        submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;

        //waitSemaphores 指定了在执行命令之前要等待哪个信号量, 这里是要等待图像已经从交换链中获取
        VkSemaphore waitSemaphores[] = {imageAvailableSemaphore};
        //waitStages 指定了在管道的哪个阶段等待,这里希望是完成颜色缓冲区中的数据写入,因此是颜色附件的输出阶段
        VkPipelineStageFlags waitStages[] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};
        //指定等待信号量个数是 1 
        submitInfo.waitSemaphoreCount = 1;
        //指定等待的信号量
        submitInfo.pWaitSemaphores = waitSemaphores;
        //指定等待的阶段
        submitInfo.pWaitDstStageMask = waitStages;

        //设置命令缓冲区个数
        submitInfo.commandBufferCount = 1;
        //绑定命令缓冲区
        submitInfo.pCommandBuffers = &commandBuffer;

        //设置在命令缓冲区中的命令执行完成之后激活哪个信号量,本例是renderFinishedSemaphore
        VkSemaphore signalSemaphores[] = {renderFinishedSemaphore};
        //设置激活的信号量个数
        submitInfo.signalSemaphoreCount = 1;
        //设置激活的信号量
        submitInfo.pSignalSemaphores = signalSemaphores;

        //将命令缓冲区的命令提交到图形队列,当命令缓冲区命令执行完成时,inFlightFence将置为有信号,这样就和开头的同步接上了 
        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;
        //等待信号量的个数是 1  
        presentInfo.waitSemaphoreCount = 1;
        //因为我们要等待命令缓冲区命令执行完成,所以这里等待的是 signalSemaphores, 即 renderFinishedSemaphore 
        presentInfo.pWaitSemaphores = signalSemaphores;

        //指定显示结果的交换链,这里就是 swapChain
        VkSwapchainKHR swapChains[] = {swapChain};
        //指定交换链的个数
        presentInfo.swapchainCount = 1;
        //指定交换链
        presentInfo.pSwapchains = swapChains;
        //指定显示图像的索引
        presentInfo.pImageIndices = &imageIndex;
        //提交显示请求,将图像显示给交换链
        vkQueuePresentKHR(presentQueue, &presentInfo);
    }

    //创建着色器模块
    VkShaderModule createShaderModule(const std::vector<char>& code) {
        //配置着色器信息
        VkShaderModuleCreateInfo createInfo{};
        createInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
        //着色器代码size
        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;
    }

    //选择一种像素格式和颜色空间
    VkSurfaceFormatKHR chooseSwapSurfaceFormat(const std::vector<VkSurfaceFormatKHR>& availableFormats) {
        //遍历交换链所支持的像素格式和颜色空间
        for (const auto& availableFormat : availableFormats) {
            //选取RGBA像素格式和SRGB颜色空间
            if (availableFormat.format == VK_FORMAT_B8G8R8A8_SRGB && availableFormat.colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
                //如果匹配到,则返回该格式
                return availableFormat;
            }
        }
        //没有合适的,默认返回第一个
        return availableFormats[0];
    }

    //选择一种显示模式
    VkPresentModeKHR chooseSwapPresentMode(const std::vector<VkPresentModeKHR>& availablePresentModes) {
        //从交换链所支持的显示模式中遍历
        for (const auto& availablePresentMode : availablePresentModes) {
            //选取MAILBOX模式,这种模式会等待vsync时更新图像,不会导致画面撕裂
            if (availablePresentMode == VK_PRESENT_MODE_MAILBOX_KHR) {
                return availablePresentMode;
            }
        }
        //如果没有匹配到,则返回FIFO模式
        return VK_PRESENT_MODE_FIFO_KHR;
    }

    //选择一个屏幕分辨率
    VkExtent2D chooseSwapExtent(const VkSurfaceCapabilitiesKHR& capabilities) {
        //如果capabilities.currentExtent.width有值
        if (capabilities.currentExtent.width != std::numeric_limits<uint32_t>::max()) {
            //则返回capabilities.currentExtent的分辨率
            return capabilities.currentExtent;
        } else {
            //否则获取窗口大小
            int width, height;
            glfwGetFramebufferSize(window, &width, &height);
            //赋值给 actualExtent
            VkExtent2D actualExtent = {
                static_cast<uint32_t>(width),
                static_cast<uint32_t>(height)
            };

            //确保actualExtent当前值不超过capabilities所支持的范围
            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;
        }
    }

    //查询给定设备上交换链所支持信息
    SwapChainSupportDetails querySwapChainSupport(VkPhysicalDevice device) {
        //存放查询结果
        SwapChainSupportDetails details;

        //获取基本表面能力,比如交换链中图像的最大/最小数量,图像做支持的分辨率等
        vkGetPhysicalDeviceSurfaceCapabilitiesKHR(device, surface, &details.capabilities);

        //交换链支持的曲面格式个数
        uint32_t formatCount;
        //查询交换链支持的曲面格式个数
        vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, nullptr);

        //如果支持格式个数大于0
        if (formatCount != 0) {
            //存到details结构体变量中
            details.formats.resize(formatCount);
            //将格式信息存到details结构体变量中
            vkGetPhysicalDeviceSurfaceFormatsKHR(device, surface, &formatCount, details.formats.data());
        }

        //可用的显示模式个数
        uint32_t presentModeCount;
        //查询可用的显示模式个数
        vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, nullptr);

        //如果有可用的显示模式
        if (presentModeCount != 0) {
            //存到details中
            details.presentModes.resize(presentModeCount);
            //查询显示模式并存到details中
            vkGetPhysicalDeviceSurfacePresentModesKHR(device, surface, &presentModeCount, details.presentModes.data());
        }
        //返回结果
        return details;
    }

    //评估设备是否满足我们的要求
    bool isDeviceSuitable(VkPhysicalDevice device) {
        //枚举设备支持的队列族
        QueueFamilyIndices indices = findQueueFamilies(device);
        //检查扩展是否被设备所支持
        bool extensionsSupported = checkDeviceExtensionSupport(device);

        //交换链是否可用
        bool swapChainAdequate = false;
        //如果扩展被设备支持
        if (extensionsSupported) {
            //查询交换链信息
            SwapChainSupportDetails swapChainSupport = querySwapChainSupport(device);
            //交换链是否可用
            swapChainAdequate = !swapChainSupport.formats.empty() && !swapChainSupport.presentModes.empty();
        }
        //返回结果
        return indices.isComplete() && extensionsSupported && swapChainAdequate;
    }

    //检查扩展是否被设备支持
    bool checkDeviceExtensionSupport(VkPhysicalDevice device) {
        //设备支持扩展的个数
        uint32_t extensionCount;
        //查询个数
        vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, nullptr);

        //枚举扩展信息,存到列表中
        std::vector<VkExtensionProperties> availableExtensions(extensionCount);
        vkEnumerateDeviceExtensionProperties(device, nullptr, &extensionCount, availableExtensions.data());

        //将请求支持的扩展转存到set集合中
        std::set<std::string> requiredExtensions(deviceExtensions.begin(), deviceExtensions.end());

        //遍历当前设备所支持的扩展
        for (const auto& extension : availableExtensions) {
            //是否在所请求的扩展列表中
            requiredExtensions.erase(extension.extensionName);
        }
        //如果所请求的扩展都被设备所支持,则返回true,否则返回false
        return requiredExtensions.empty();
    }

    //检查设备支持哪些队列族
    QueueFamilyIndices findQueueFamilies(VkPhysicalDevice device) {
        //存放队列族信息
        QueueFamilyIndices indices;

        //队列族数量
        uint32_t queueFamilyCount = 0;
        //获取设备支持的队列族数量
        vkGetPhysicalDeviceQueueFamilyProperties(device, &queueFamilyCount, nullptr);

        //创建vector来存放队列族对象
        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) {
                //记住id
                indices.graphicsFamily = i;
            }

            VkBool32 presentSupport = false;
            //是否具有显示功能
            vkGetPhysicalDeviceSurfaceSupportKHR(device, i, surface, &presentSupport);

            //如果支持
            if (presentSupport) {
                //记住id
                indices.presentFamily = i;
            }
            
            //如果都已找到,退出循环
            if (indices.isComplete()) {
                break;
            }

            i++;
        }

        //返回查询的信息
        return indices;
    }

    //返回扩展列表
    std::vector<const char*> getRequiredExtensions() {
        //扩展的数量
        uint32_t glfwExtensionCount = 0;
        //扩展列表字符串数组指针
        const char** glfwExtensions;
        //查询扩展
        glfwExtensions = glfwGetRequiredInstanceExtensions(&glfwExtensionCount);

        //将扩展列表存到vector中
        std::vector<const char*> extensions(glfwExtensions, glfwExtensions + glfwExtensionCount);

        //如果开启了校验层,则加入VK_EXT_DEBUG_UTILS_EXTENSION_NAME扩展
        if (enableValidationLayers) {
            extensions.push_back(VK_EXT_DEBUG_UTILS_EXTENSION_NAME);
        }
        //返回扩展列表
        return extensions;
    }

    bool checkValidationLayerSupport() {
        uint32_t layerCount;
        vkEnumerateInstanceLayerProperties(&layerCount, nullptr);

        std::vector<VkLayerProperties> availableLayers(layerCount);
        vkEnumerateInstanceLayerProperties(&layerCount, availableLayers.data());

        for (const char* layerName : validationLayers) {
            bool layerFound = false;

            for (const auto& layerProperties : availableLayers) {
                if (strcmp(layerName, layerProperties.layerName) == 0) {
                    layerFound = true;
                    break;
                }
            }

            if (!layerFound) {
                return false;
            }
        }

        return true;
    }

    static 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;
    }

    //设置Debug回调函数
    static VKAPI_ATTR VkBool32 VKAPI_CALL debugCallback(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageType, const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData) {
        //向标准错误流中输出回调消息
        std::cerr << "validation layer: " << pCallbackData->pMessage << std::endl;
        //按Vulkan的规定,应用层总是返回VK_FALSE
        return VK_FALSE;
    }
};

int main() {
    //创建app
    HelloTriangleApplication app;

    try {
        //允许
        app.run();
    } catch (const std::exception& e) {
        //捕捉异常
        std::cerr << e.what() << std::endl;
        //返回错误
        return EXIT_FAILURE;
    }

    //正常退出 
    return EXIT_SUCCESS;
}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
在信号处理领域,DOA(Direction of Arrival)估计是一项关键技术,主要用于确定多个信号源到达接收阵列的方向。本文将详细探讨三种ESPRIT(Estimation of Signal Parameters via Rotational Invariance Techniques)算法在DOA估计中的实现,以及它们在MATLAB环境中的具体应用。 ESPRIT算法是由Paul Kailath等人于1986年提出的,其核心思想是利用阵列数据的旋转不变性来估计信号源的角度。这种算法相比传统的 MUSIC(Multiple Signal Classification)算法具有较低的计算复杂度,且无需进行特征值分解,因此在实际应用中颇具优势。 1. 普通ESPRIT算法 普通ESPRIT算法分为两个主要步骤:构造等效旋转不变系统和估计角度。通过空间平移(如延时)构建两个子阵列,使得它们之间的关系具有旋转不变性。然后,通过对子阵列数据进行最小二乘拟合,可以得到信号源的角频率估计,进一步转换为DOA估计。 2. 常规ESPRIT算法实现 在描述中提到的`common_esprit_method1.m`和`common_esprit_method2.m`是两种不同的普通ESPRIT算法实现。它们可能在实现细节上略有差异,比如选择子阵列的方式、参数估计的策略等。MATLAB代码通常会包含预处理步骤(如数据归一化)、子阵列构造、旋转不变性矩阵的建立、最小二乘估计等部分。通过运行这两个文件,可以比较它们在估计精度和计算效率上的异同。 3. TLS_ESPRIT算法 TLS(Total Least Squares)ESPRIT是对普通ESPRIT的优化,它考虑了数据噪声的影响,提高了估计的稳健性。在TLS_ESPRIT算法中,不假设数据噪声是高斯白噪声,而是采用总最小二乘准则来拟合数据。这使得算法在噪声环境下表现更优。`TLS_esprit.m`文件应该包含了TLS_ESPRIT算法的完整实现,包括TLS估计的步骤和旋转不变性矩阵的改进处理。 在实际应用中,选择合适的ESPRIT变体取决于系统条件,例如噪声水平、信号质量以及计算资源。通过MATLAB实现,研究者和工程师可以方便地比较不同算法的效果,并根据需要进行调整和优化。同时,这些代码也为教学和学习DOA估计提供了一个直观的平台,有助于深入理解ESPRIT算法的工作原理。
要使用Vulkan绘制一个三角形,需要经过以下步骤: 1. 创建一个Vulkan实例 2. 创建一个逻辑设备 3. 创建一个窗口表面 4. 创建一个交换链 5. 创建渲染通道和帧缓冲区 6. 创建着色器模块 7. 创建管线布局和管线 8. 分配顶点缓冲区 9. 开始绘制 下面是一个简单的示例代码,用于绘制一个三角形: ``` // 顶点数据 std::vector<Vertex> vertices = { { { 0.0f, -0.5f },{ 1.0f, 0.0f, 0.0f } }, { { 0.5f, 0.5f },{ 0.0f, 1.0f, 0.0f } }, { { -0.5f, 0.5f },{ 0.0f, 0.0f, 1.0f } } }; // 创建顶点缓冲区 VkBuffer vertexBuffer; VkDeviceMemory vertexBufferMemory; createBuffer(sizeof(vertices[0]) * vertices.size(), VK_BUFFER_USAGE_VERTEX_BUFFER_BIT, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT, vertexBuffer, vertexBufferMemory); // 在CPU上填充顶点缓冲区 void* data; vkMapMemory(device, vertexBufferMemory, 0, sizeof(vertices[0]) * vertices.size(), 0, &data); memcpy(data, vertices.data(), sizeof(vertices[0]) * vertices.size()); vkUnmapMemory(device, vertexBufferMemory); // 创建渲染通道和帧缓冲区 VkRenderPass renderPass; createRenderPass(renderPass); VkFramebuffer framebuffer; createFramebuffer(framebuffer); // 创建着色器模块 VkShaderModule vertShaderModule; VkShaderModule fragShaderModule; createShaderModule("vert.spv", vertShaderModule); createShaderModule("frag.spv", fragShaderModule); // 创建管线布局和管线 VkPipelineLayout pipelineLayout; VkPipeline pipeline; createPipelineLayout(pipelineLayout); createPipeline(pipeline, pipelineLayout, renderPass, vertShaderModule, fragShaderModule); // 开始绘制 VkCommandBuffer commandBuffer = beginSingleTimeCommands(); VkClearValue clearColor = { 0.0f, 0.0f, 0.0f, 1.0f }; VkRenderPassBeginInfo renderPassInfo = { VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO }; renderPassInfo.renderPass = renderPass; renderPassInfo.framebuffer = framebuffer; renderPassInfo.renderArea.offset = { 0, 0 }; renderPassInfo.renderArea.extent = swapChainExtent; renderPassInfo.clearValueCount = 1; renderPassInfo.pClearValues = &clearColor; vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE); vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline); VkBuffer vertexBuffers[] = { vertexBuffer }; VkDeviceSize offsets[] = { 0 }; vkCmdBindVertexBuffers(commandBuffer, 0, 1, vertexBuffers, offsets); vkCmdDraw(commandBuffer, 3, 1, 0, 0); vkCmdEndRenderPass(commandBuffer); endSingleTimeCommands(commandBuffer); ``` 这段代码中,我们首先创建一个包含三个顶点的顶点缓冲区,然后使用着色器模块、管线布局和管线来定义如何渲染这些顶点。最后,我们通过调用`vkCmdDraw`函数来绘制三角形
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

黑不溜秋的

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值