Part 1 - 关于Vulkan

转载自最早发布在formu.ziyuesinicization.site的文章

晨魔(Morning Demon)

2018年3月7日,科纳斯组织发布了Vulkan 1.1,随后,大量的游戏和游戏引擎几乎都开始支持Vulkan,它以高性能著称,与Direct3D 12类似。

Vulkan对GPU进行了更直接的控制,是一个极底层的图形API。由于它具有极高的自由度,这也导致了它的代码及其繁琐,光是渲染一个在着色器硬编码的三角形就要写600多行代码。

下面我们来看看怎么回事:
Vulkan居有和OpenGL一样的扩展机制,Vulkan与窗口的联系,需要用到表面扩展,例如:用于Windows系统窗口的扩展为:VK_KHR_surface_win32。

要想使用Vulkan API首先要创建一个实例(Instance),创建实例前,需要获取扩展,同时为了方便后续的调试,我们还需要开启验证层(validation)。


unsigned int extensionCount;//扩展数量
const char* const* extensionNames = SDL_Vulkan_GetInstanceExtensions(&extensionCount);
//获取扩展

//开启验证层
const char* validation = "VK_LAYER_KHRONOS_validation";
const char* const* validationNames = &validation;
                                

在创建实例前,我需要填写两个结构体,一个是应用程序信息结构体(VkApplicationInfo),一个是实例创建信息结构体(VkInstanceCreateInfo)。


typedef struct VkApplicationInfo {
    VkStructureType    sType;          // 结构体的类型,由于我们使用的是VkApplicationInfo,所以这里必须为 VK_STRUCTURE_TYPE_APPLICATION_INFO
    const void*        pNext;          // 用于扩展链,不用管,设为 NULL或nullptr
    const char*        pApplicationName; // 应用程序名称,UTF-8字符
    uint32_t           applicationVersion; // 应用程序的版本号
    const char*        pEngineName;    // 使用的引擎名称,可以设为NULL或nullptr
    uint32_t           engineVersion;  // 引擎版本号
    uint32_t           apiVersion;     // 请求使用的 Vulkan API 版本
} VkApplicationInfo;
                                

typedef struct VkInstanceCreateInfo {
    VkStructureType             sType;                   // 同上,必须为 VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO
    const void*                 pNext;                   // 同上
    VkInstanceCreateFlags       flags;                   // 保留标志位不用管设为0
    const VkApplicationInfo*    pApplicationInfo;        // 指向 VkApplicationInfo 的指针
    uint32_t                    enabledLayerCount;       // 启用的验证层数量
    const char* const*          ppEnabledLayerNames;     // 启用的验证层
    uint32_t                    enabledExtensionCount;   // 启用的扩展数量
    const char* const*          ppEnabledExtensionNames; // 启用的扩展
} VkInstanceCreateInfo;
                                

示例:


VkApplicationInfo appInfo = {};
appInfo.apiVersion = VK_API_VERSION_1_3;
appInfo.applicationVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.engineVersion = VK_MAKE_VERSION(1, 0, 0);
appInfo.pApplicationName = "";
appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;

VkInstanceCreateInfo instanceCreateInfo = {};
instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
instanceCreateInfo.pApplicationInfo = &appInfo;
instanceCreateInfo.enabledExtensionCount = extensionCount;
instanceCreateInfo.enabledLayerCount = 1;
instanceCreateInfo.ppEnabledExtensionNames = extensionNames;
instanceCreateInfo.ppEnabledLayerNames = validationNames;		
								

采用了C++的结构体初始化时,设为NULL, nullptr, 0的地方会自动填上,不需要再访问填写。

之后就可以创建实例了:


VkInstance instance;
vkCreateInstance(&instanceCreateInfo, nullptr, &instance);
								

vkCreateInstance的第三项参数为VkAllocationCallbacks,他是用于内存分配的回调函数指针的结构,目前暂时用不到,可以设为nullptr。

创建好实例之后,我就可以从窗口创建表面了:


VkSurfaceKHR surface = VK_NULL_HANDLE;
SDL_Vulkan_CreateSurface(window, instance, nullptr, &surface);
								

接下来就是要选择一个GPU来创建逻辑设备(VkDevice)和队列族(VkQueue)。
逻辑设备是从GPU创建的,他是连接应用程序与GPU的重要工具,而GPU的功能有很多,除了常见的绘图,他还可以进行通用并行计算,数据传输等,为了能让GPU进行不同类型的工作,我们需要用队列族进行管理。

我们需要先获取GPU的一些信息,考虑到一些土豪老哥可能会给设备插多块显卡,所以我们需要找出你设备上所有的显卡:


unsigned int deviceCount;
vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr);

const unsigned int devicesNum = deviceCount;
VkPhysicalDevice devices[devicesNum];
vkEnumeratePhysicalDevices(instance, &deviceCount, devices);
								

这里有一个十分有趣的技巧,我们第一次调用vkEnumeratePhysicalDevices时,把最后一项参数VkPhysicalDevice*设为了nullptr,这一步实际上是为了获取显卡的数量,第二次再调用时才是真正获取了显卡,这个技巧后面还要用。

接下来,我们需要筛选出最适合的显卡,现在我们暂时只关心绘图的功能,我们需要再次获取每块显卡的队列族,并找到能绘图的那一块显卡,并获取他的索引,稍微接触过计算机图形学的都知道,一块能绘图的显卡,可以同时绘制图形和呈现图像,这两个功能通常应该是在一个队列族里的,但是不排除绘制图形和呈现图像这两个是分开的情况,所以我们需要获取绘制图形和呈现图像的队列索引。


VkPhysicalDevice physicalDevice = VK_NULL_HANDLE;//那块最佳的显卡

unsigned int graphicsIndex = -1; //绘制图形索引
unsigned int presentIndex = -1;//呈现图想索引

for (unsigned int i = 0; i < deviceCount; i++) {
       //获取GPU的各种信息
        VkPhysicalDeviceProperties deviceProperties;
        vkGetPhysicalDeviceProperties(devices[i], &deviceProperties);

        std::cout << "GPU " << i << ": " << deviceProperties.deviceName << std::endl;

         //获取GPU的队列族,利用刚刚的技巧
        unsigned int queueFamilyCount;
        vkGetPhysicalDeviceQueueFamilyProperties(devices[i], &queueFamilyCount, nullptr);

        const unsigned int queueFamiliesNum = queueFamilyCount;
        VkQueueFamilyProperties queueFamilies[queueFamiliesNum];
        vkGetPhysicalDeviceQueueFamilyProperties(devices[i], &queueFamilyCount, queueFamilies);

        for (unsigned int j = 0; j < queueFamilyCount; j++) {
            //检查GPU是否可以绘制图形
            if (queueFamilies[j].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
                graphicsIndex = j;
            }
 
            //检查GPU是否可以呈现图像
            VkBool32 supported = false;
            vkGetPhysicalDeviceSurfaceSupportKHR(devices[i], j, surface, &supported);
            if (supported) {
                presentIndex = j;
            }

            //都可以时,选择它作为最佳显卡
            if ((graphicsIndex != -1) && (presentIndex != -1)) {
                physicalDevice = devices[i];
                std::cout << "Selected GPU " << i << ": " << deviceProperties.deviceName << std::endl;
            }
        }

        //如果选择到了最佳显卡就跳出循环
        if (physicalDevice != VK_NULL_HANDLE) {
            break;
        }
    }

    //没有合适的显卡
    if (physicalDevice == VK_NULL_HANDLE) {
        std::cout << "Filed to find a suitable GPU" << std::endl;
    }
}
								

选择到了最佳显卡之后,就可以开始创建逻辑设备了,但是创建逻辑设备我们还需要填写两个结构体:


typedef struct VkDeviceQueueCreateInfo {
    VkStructureType             sType;            // 必须为 VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO
    const void*                 pNext;            // nullptr
    VkDeviceQueueCreateFlags    flags;            // 0
    uint32_t                    queueFamilyIndex; // 队列族索索引
    uint32_t                    queueCount;       // 要创建的队列数量
    const float*                pQueuePriorities; // 队列优先级数组(范围 0.0 ~ 1.0)
} VkDeviceQueueCreateInfo;
								

typedef struct VkDeviceCreateInfo {
    VkStructureType                    sType;                   // 必须为 VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO
    const void*                        pNext;                   // nullptr
    VkDeviceCreateFlags                flags;                   // 0
    uint32_t                           queueCreateInfoCount;    // 队列创建信息数量
    const VkDeviceQueueCreateInfo*     pQueueCreateInfos;       // 队列创建信息数组
    uint32_t                           enabledLayerCount;       // 启用的验证层数量, 已弃用,建议设为 0
    const char* const*                 ppEnabledLayerNames;     // 验证层名称数组,已弃用, 不用管
    uint32_t                           enabledExtensionCount;   // 启用的设备扩展数量
    const char* const*                 ppEnabledExtensionNames; // 设备扩展名称数组
    const VkPhysicalDeviceFeatures*    pEnabledFeatures;        // 启用的设备特性
} VkDeviceCreateInfo;
								

刚刚我们考虑到绘制图形和呈现图像可能是两个队列族,所以我们需要创建两个设备队列创建信息(VkDeviceQueueCreateInfo),另外,我们需要启用交换链扩展(VK_KHR_SWAPCHAIN_EXTENSION_NAME), 至于设备特性我们暂时不使用设为NULL。实例如下:


    VkDeviceQueueCreateInfo dqCreateInfo[2];//0: Graphics 1: Present
    float queuePriority = 1.0f;//优先级默认为1

    for (unsigned int i = 0; i < 2; i++) {
        dqCreateInfo[i].sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
        dqCreateInfo[i].pQueuePriorities = &queuePriority;
        dqCreateInfo[i].queueCount = 1;
    }

    dqCreateInfo[0].queueFamilyIndex = graphicsIndex;
    dqCreateInfo[1].queueFamilyIndex = presentIndex;

    const char* deviceExtensions[] = {
        VK_KHR_SWAPCHAIN_EXTENSION_NAME
    };

    VkDeviceCreateInfo dCreateInfo = {};
    dCreateInfo.enabledExtensionCount = sizeof(deviceExtensions) / sizeof(deviceExtensions[0]);
    dCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    dCreateInfo.enabledLayerCount = 0;
    dCreateInfo.ppEnabledExtensionNames = deviceExtensions;
    dCreateInfo.ppEnabledLayerNames = nullptr;
    dCreateInfo.pEnabledFeatures = NULL;

   //判断是否为同一队列族
    if (graphicsIndex == presentIndex) {
        dCreateInfo.pQueueCreateInfos = &dqCreateInfo[0];
        dCreateInfo.queueCreateInfoCount = 1;
    }
    else {
        dCreateInfo.pQueueCreateInfos = dqCreateInfo;
        dCreateInfo.queueCreateInfoCount = 2;
    }
								

现在开始创建逻辑设备和获取绘制图形队列和呈现图像队列:


    vkCreateDevice(physicalDevice, &dCreateInfo, nullptr, &device);

    VkQueue graphicsQueue;
    VkQueue presentQueue;

    vkGetDeviceQueue(device, graphicsIndex, 0, &graphicsQueue);
    vkGetDeviceQueue(device, presentIndex, 0, &presentQueue);
								

接下来就是创建交换链(Swap Chain)以及从交换链获取图像视图。
交换链,先前用OpenGL或Direct3D的应该不陌生,不知道的可以去google一下,图像视图代表了最原始的内存数据,后面的绘图操作都是在图像视图上进行的。

我们先看一下交换链创建信息结构体:


typedef struct VkSwapchainCreateInfoKHR {
    VkStructureType                  sType;                 // 必须为 VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR
    const void*                      pNext;                 // nullptr
    VkSwapchainCreateFlagsKHR        flags;                 // 0
    VkSurfaceKHR                     surface;               // 窗口表面
    uint32_t                         minImageCount;         // 最小缓冲区数量, 2为二重缓冲,3为三重缓冲
    VkFormat                         imageFormat;           // 像素格式,通常为VK_FORMAT_B8G8R8A8_SRGB就够用了
    VkColorSpaceKHR                  imageColorSpace;       // 色彩空间,通常为VK_COLOR_SPACE_SRGB_NONLINEAR_KHR
    VkExtent2D                       imageExtent;           // 图像尺寸,通常为窗口大小
    uint32_t                         imageArrayLayers;      // 数组层数,通常为 1,如果是VR或3维图像则为2
    VkImageUsageFlags                imageUsage;            // 图像用途,设为VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT即可
   //这三个后面再提
    VkSharingMode                    imageSharingMode;      
    uint32_t                         queueFamilyIndexCount; 
    const uint32_t*                  pQueueFamilyIndices;   

    VkSurfaceTransformFlagBitsKHR    preTransform;          // 图像变换(旋转/翻转),通常为VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR:不变换

    VkCompositeAlphaFlagBitsKHR      compositeAlpha;        // 透明混合方式,通常为VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR:完全不透明

    VkPresentModeKHR                 presentMode;           // 呈现模式,共有四种模式:
//VK_PRESENT_MODE_FIFO_KHR	等待垂直同步,队列先进先出	
//VK_PRESENT_MODE_MAILBOX_KHR	保留最新图像,替换队列中的旧图像	
//VK_PRESENT_MODE_IMMEDIATE_KHR	立即显示,不等待 V-Sync	
//VK_PRESENT_MODE_FIFO_RELAXED_KHR	FIFO 变体,错过 V-Sync 则立即显示	
//一般为VK_PRESENT_MODE_MAILBOX_KHR即可

    VkBool32                         clipped;               // 是否裁剪被遮挡像素
    VkSwapchainKHR                   oldSwapchain;          // 旧的交换链句柄(重建时使用)
} VkSwapchainCreateInfoKHR;
								

一个简单示例:


    VkSwapchainCreateInfoKHR spCreateInfo;
    spCreateInfo.sType = VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHR;
    spCreateInfo.minImageCount = 2;
    spCreateInfo.clipped = VK_TRUE;
    spCreateInfo.compositeAlpha = VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHR;
    spCreateInfo.imageArrayLayers = 1;
    spCreateInfo.imageColorSpace = VK_COLOR_SPACE_SRGB_NONLINEAR_KHR;
    spCreateInfo.imageExtent = {window_width, window_height};
    spCreateInfo.imageFormat = VK_FORMAT_B8G8R8A8_SRGB;

   //如果有多个队列则为,则开启共享
    uint32_t queueFamilyIndices[2] = {graphicsIndex, presentIndex};
    if (graphicsIndex != presentIndex) {
        spCreateInfo.imageSharingMode = VK_SHARING_MODE_CONCURRENT;
        spCreateInfo.queueFamilyIndexCount = 2; //队列数量
        spCreateInfo.pQueueFamilyIndices = queueFamilyIndices;//队列索引
    }
    else {
        spCreateInfo.imageSharingMode = VK_SHARING_MODE_EXCLUSIVE;
    }

    spCreateInfo.imageUsage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT;
    spCreateInfo.oldSwapchain = VK_NULL_HANDLE;
    spCreateInfo.presentMode = VK_PRESENT_MODE_MAILBOX_KHR;
    spCreateInfo.preTransform = VK_SURFACE_TRANSFORM_IDENTITY_BIT_KHR;
    spCreateInfo.surface = surface;
								

接下来,我们就可以创建交换链了:


VkSwapchainKHR swapChain;
vkCreateSwapchainKHR(device, &spCreateInfo, nullptr, &swapChain);
								

要注意的是,我们手动填写的参数,不一定所有显卡都能用,我可以通过vkGetPhysicalDeviceSurfaceCapabilitiesKHR,来检查那些参数是否有用:


    VkSurfaceCapabilitiesKHR capabilities;
    vkGetPhysicalDeviceSurfaceCapabilitiesKHR(physicalDevice, surface, &capabilities);

    //Format
    unsigned int formatCount;
    vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, nullptr);

    const unsigned int formatsNum = formatCount;
    VkSurfaceFormatKHR formats[formatsNum];
    vkGetPhysicalDeviceSurfaceFormatsKHR(physicalDevice, surface, &formatCount, formats);

    VkSurfaceFormatKHR surfaceFormat = formats[0];
    for (unsigned int i = 0; i < formatCount; i++) {
        if (formats[i].format == VK_FORMAT_R8G8B8A8_SRGB &&
            formats[i].colorSpace == VK_COLOR_SPACE_SRGB_NONLINEAR_KHR) {
            surfaceFormat = formats[i];
        }
    }

    //Present Mode
    unsigned int presentModeCount;
    vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, nullptr);

    const unsigned int presentModesNum = presentModeCount;
    VkPresentModeKHR presentModes[presentModesNum];
    vkGetPhysicalDeviceSurfacePresentModesKHR(physicalDevice, surface, &presentModeCount, presentModes);

    VkPresentModeKHR surfacePresentMode = VK_PRESENT_MODE_IMMEDIATE_KHR;
    for (unsigned int i = 0;i < presentModeCount; i++) {
        if (presentModes[i] == VK_PRESENT_MODE_MAILBOX_KHR) {
            surfacePresentMode = presentModes[i];
        }
    }

    //Extent
    VkExtent2D spExtent = capabilities.currentExtent;
    spExtent.width = window_width;
    spExtent.height = window_height;

    unsigned int imageCount = capabilities.minImageCount + 1;
    if (capabilities.maxImageCount > 0 && imageCount > capabilities.maxImageCount) {
        imageCount = capabilities.maxImageCount;
    }
								

接下来,我们需要从交换链获取图像,以此来创建图像视图:


    unsigned int imageCount;
    vkGetSwapchainImagesKHR(device, swapChain, &imageCount, nullptr);

    const unsigned int imagesNum = imageCount;
    VkImage spImages[imagesNum];
    vkGetSwapchainImagesKHR(device, swapChain, &imageCount, spImages);
								

在创建图像视图前,我们依旧需要填写信息结构体:


typedef struct VkImageViewCreateInfo {
    VkStructureType            sType;                 // 必须为 VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO
    const void*                pNext;                 // nullptr
    VkImageViewCreateFlags     flags;                 // 0
    VkImage                    image;                 // 目标图像句柄
    VkImageViewType            viewType;              // 视图类型,默认为2D视图,填VK_IMAGE_VIEW_TYPE_2D即可
    VkFormat                   format;                // 图像格式,与上面创建交换链的格式一样即可
    VkComponentMapping         components;            // 分量映射,RGBA 通道重排
    VkImageSubresourceRange    subresourceRange;      // 访问的图像子集,层/级别范围
} VkImageViewCreateInfo;
                               

其中有几个需要单独拿出来:


typedef struct VkComponentMapping {
    VkComponentSwizzle r;  // 红色分量来源
    VkComponentSwizzle g;  // 绿色分量来源
    VkComponentSwizzle b;  // 蓝色分量来源
    VkComponentSwizzle a;  // Alpha 分量来源
   //全填VK_COMPONENT_SWIZZLE_IDENTITY,即原始通道即可
} VkComponentMapping;
                                

typedef struct VkImageSubresourceRange {
    VkImageAspectFlags    aspectMask;      // 访问的方面(颜色/深度/模板)
    uint32_t              baseMipLevel;    // 起始 Mipmap 级别
    uint32_t              levelCount;      // Mipmap 级别数量
    uint32_t              baseArrayLayer;  // 起始数组层
    uint32_t              layerCount;      // 数组层数量
} VkImageSubresourceRange;
								

我们为交换链的每个图像都创建一个图像视图即可:


    VkImageView spImagesView[imagesNum];
    for (unsigned int i = 0;i < imageCount; i++) {
        VkImageViewCreateInfo ivCreateInfo;
        ivCreateInfo.sType = VK_STRUCTURE_TYPE_IMAGE_VIEW_CREATE_INFO;
        ivCreateInfo.format = surfaceFormat.format;
        ivCreateInfo.image = spImages[i];
        ivCreateInfo.viewType = VK_IMAGE_VIEW_TYPE_2D;

        ivCreateInfo.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
        ivCreateInfo.subresourceRange.baseMipLevel = 0;
        ivCreateInfo.subresourceRange.levelCount = 1;
        ivCreateInfo.subresourceRange.baseArrayLayer = 0;
        ivCreateInfo.subresourceRange.layerCount = 1;

        ivCreateInfo.components.r = VK_COMPONENT_SWIZZLE_IDENTITY;
        ivCreateInfo.components.g = VK_COMPONENT_SWIZZLE_IDENTITY;
        ivCreateInfo.components.b = VK_COMPONENT_SWIZZLE_IDENTITY;
        ivCreateInfo.components.a = VK_COMPONENT_SWIZZLE_IDENTITY;

        vkCreateImageView(device, &ivCreateInfo, nullptr, &spImagesView[i]);
    }
                                

接着,我们还需要命令缓冲区(Command Buffer)和命令池(Command Pool)
我们进行清屏,绘制等操作时,需要先把对应的指令放在命令池,由命令池管理这些命令,随后通过命令缓冲区录制,提交,执行这些命令给GPU。

同样,创建命令池和命令缓冲区,需要先填写信息结构体:


typedef struct VkCommandPoolCreateInfo {
    VkStructureType             sType;            // 必须为 VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO
    const void*                 pNext;            // nullptr
    VkCommandPoolCreateFlags    flags;            // 这里有三个标志可以设置:
//VK_COMMAND_POOL_CREATE_TRANSIENT_BIT	池中的命令缓冲区生命周期短、频繁重置	每帧重新录制的缓冲区
//VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT允许单独重置池中的单个缓冲区	需要灵活管理单个缓冲区,而非重置整个池
//VK_COMMAND_POOL_CREATE_PROTECTED_BIT	用于受保护内存的命令缓冲区

   一般填VK_COMMAND_POOL_CREATE_TRANSIENT_BIT即可
    uint32_t                    queueFamilyIndex; // 绑定的队列族索引
} VkCommandPoolCreateInfo;
								

typedef struct VkCommandBufferAllocateInfo {
    VkStructureType         sType;              // 必须为 VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO
    const void*             pNext;              // nullptr
    VkCommandPool           commandPool;        // 所属的命令池
    VkCommandBufferLevel    level;              // 缓冲区级别(Primary/Secondary)
    uint32_t                commandBufferCount; // 分配数量
} VkCommandBufferAllocateInfo;
								

以下简单的示例:


    VkCommandPool commandPool;

    VkCommandPoolCreateInfo cpCreateInfo;
    cpCreateInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    cpCreateInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    cpCreateInfo.queueFamilyIndex = graphicsIndex;

    vkCreateCommandPool(device, &cpCreateInfo, nullptr, &commandPool);

    VkCommandBuffer commandBuffer;

    VkCommandBufferAllocateInfo cbaInfo;
    cbaInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
    cbaInfo.commandBufferCount = 1;
    cbaInfo.commandPool = commandPool;
    cbaInfo.level = VK_COMMAND_BUFFER_LEVEL_PRIMARY;

    vkAllocateCommandBuffers(device, &cbaInfo, &commandBuffer);
								

接下来我们需要信号量(VkSemaphore)和栅栏(VkFence)

栅栏是 CPU 和 GPU 之间的同步机制。它像一个"门闩":
GPU 执行完提交的命令缓冲区后,会触发(Signal) Fence。
CPU 可以通过 vkWaitForFences 等待这个触发信号,实现 CPU 阻塞。
核心用途:让 CPU 知道 GPU 什么时候完成了特定任务,以便安全地回收资源或读取结果。

信号量是 GPU 内部队列之间和队列内部的同步机制。它像一个"接力棒":
一个队列完成工作后,触发(Signal) Semaphore。
另一个队列在开始工作前,等待(Wait) 这个 Semaphore。
核心用途:协调 GPU 内部不同工作之间的执行顺序,避免数据竞争。

我们需要两个信号量,一个是图像可用信号量,一个是渲染结束信号量,以及一个飞行栅栏。

在创建信号量和栅栏之前,又双叒叕需要填写信息结构:


typedef struct VkSemaphoreCreateInfo {
    VkStructureType           sType;        // 必须为 VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
    const void*               pNext;        //nullptr
    VkSemaphoreCreateFlags    flags;        // 0
} VkSemaphoreCreateInfo;
								

typedef struct VkFenceCreateInfo {
    VkStructureType       sType;        // 必须为 VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
    const void*           pNext;        // nullptr
    VkFenceCreateFlags    flags;        // 创建标志,有两种初始状态
//0:Fence 刚创建时是未触发的,需要提交工作并关联 Fence 才会触发。适合"先提交,后等待"的模式。
//VK_FENCE_CREATE_SIGNALED_BIT:Fence 创建时就已经触发,使得首次等待立即返回。适合"多帧并行"场景,避免第一帧时阻塞。
//我们选择VK_FENCE_CREATE_SIGNALED_BIT即可
} VkFenceCreateInfo;
								

创建即可:


    VkSemaphore imageSem, renderSem;
    VkFence flightFen;

    VkSemaphoreCreateInfo semCreateInfo;
    semCreateInfo.pNext = nullptr;
    semCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    semCreateInfo.flags = 0;

    VkFenceCreateInfo fenCreateInfo;
    fenCreateInfo.pNext = nullptr;
    fenCreateInfo.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
    fenCreateInfo.flags = VK_FENCE_CREATE_SIGNALED_BIT;

    vkCreateSemaphore(device, &semCreateInfo, nullptr, &imageSem);
    vkCreateSemaphore(device, &semCreateInfo, nullptr, &renderSem);
    vkCreateFence(device, &fenCreateInfo, nullptr, &flightFen);
								

接着,我们需要创建渲染管线,Vulkan的渲染管线大致为:

Vertex/Index buffer 顶点/索引输入
                           |
Input assembler 对输入的顶点按照索引装配成多边形
                           |
Vertex Shader 顶点着色器
                           |
Tessellation 曲面细分
                           |
Geometry shader 几何着色器
                           |
Rasterization 光栅化,转为像素
                           |
Fragment shader 片段着色器
                           |
Color blending 颜色混合
                           |
Framebuffer 输出到帧缓冲

创建管线时,我们只关注Input assembler, Vertex Shader, Rasterization, Fragment Shader, Color blending以及Framebuffer。

不过在此之前,我们需要顶点着色器与片段着色器。与传统的OpenGL和Direct3D的着色器不同,Vulkan只支持SPIR-V(Standard Portable Intermediate Representation)的中间二进制格式的编译,所以,你需要先用GLSL或HLSL语言编写着色器,然后用glslc编译为SPIR-V,最后才能给Vulkan使用。这里我用HLSL语言编写着色器:

vertex.hlsl:


struct VSOutput {
    float4 Pos : SV_Position; 
    float3 Color : COLOR0;    
};

VSOutput main(uint vertexID : SV_VertexID)
{
    VSOutput output;

    float2 positions[3] = {
        float2( 0.0, -0.5),  
        float2( 0.5,  0.5),  
        float2(-0.5,  0.5)   
    };

    float3 colors[3] = {
        float3(1.0, 0.0, 0.0), 
        float3(0.0, 1.0, 0.0), 
        float3(0.0, 0.0, 1.0) 
    };

    output.Pos = float4(positions[vertexID], 0.0, 1.0);
    output.Color = colors[vertexID];

    return output;
}
								

pixel.hlsl:


struct VSOutput {
    float4 Pos : SV_Position; 
    float3 Color : COLOR0;    
};

float4 main(VSOutput input) : SV_Target
{
    return float4(input.Color, 1.0);
}
								

接着进行编译:


glslc -x hlsl -fshader-stage=vertex -fentry-point=main vertex.hlsl -o vert.spv
glslc -x hlsl -fshader-stage=fragment -fentry-point=main pixel.hlsl -o frag.spv
								

注意,在用HLSL编写着色器时,尽管我们还是习惯称作为Pixel Shader(像素着色器),但是在Vulkan中是不存在的,你依然要用fragment的格式去编译它.

接下来,我们需要创建着色器模块(VkShaderModule),还是需要填写信息结构:


typedef struct VkShaderModuleCreateInfo {
    VkStructureType              sType;           // 必须为 VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO
    const void*                  pNext;           // nullptr
    VkShaderModuleCreateFlags    flags;           // 0
    size_t                       codeSize;        // SPIR-V 字节码大小(字节)
    const uint32_t*              pCode;           // SPIR-V 字节码数据指针
} VkShaderModuleCreateInfo;
								

我写一个CreateShaderModule的函数:


VkShaderModule CreateShaderModule(VkDevice device, const char* code, long size) {
    VkShaderModuleCreateInfo smCreateInfo = {};
    smCreateInfo.sType = VK_STRUCTURE_TYPE_SHADER_MODULE_CREATE_INFO;
    smCreateInfo.codeSize = size;
    smCreateInfo.pCode = reinterpret_cast(code);

    VkShaderModule shaderMod;

    vkCreateShaderModule(device, &smCreateInfo, nullptr, &shaderMod);

    return shaderMod;
}
								

然后创建顶点和片段着色器模块:


    long vertexSize, fragmentSize;

    const char* vertexCode = ReadFile("shaders/vert.spv", &vertexSize);
    const char* fragmentCode = ReadFile("shaders/frag.spv", &fragmentSize);

    VkShaderModule vertexMod = CreateShaderModule(device, vertexCode, vertexSize);
    VkShaderModule fragmentMod = CreateShaderModule(device, fragmentCode, fragmentSize);
								

我们需要用VkPipelineShaderStageCreateInfo来创建着色器


typedef struct VkPipelineShaderStageCreateInfo {
    VkStructureType                     sType;               // 必须为 VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO
    const void*                         pNext;               // nullptr
    VkPipelineShaderStageCreateFlags    flags;               // 0
    VkShaderStageFlagBits               stage;               // 着色器阶段,主要有:
//VK_SHADER_STAGE_VERTEX_BIT	顶点着色器	
//VK_SHADER_STAGE_FRAGMENT_BIT	片元着色器	
//VK_SHADER_STAGE_GEOMETRY_BIT	几何着色器	
//VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT	曲面细分控制着色器	
//VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT	曲面细分评估着色器	
//VK_SHADER_STAGE_COMPUTE_BIT	计算着色器
//VK_SHADER_STAGE_RAYGEN_BIT_KHR	光线生成着色器	(扩展)
//VK_SHADER_STAGE_ANY_HIT_BIT_KHR	Any-Hit 着色器	(扩展)
//VK_SHADER_STAGE_CLOSEST_HIT_BIT_KHR	Closest-Hit 着色器	(扩展)
//VK_SHADER_STAGE_MISS_BIT_KHR	Miss 着色器	(扩展)
//VK_SHADER_STAGE_INTERSECTION_BIT_KHR	相交着色器	(扩展)
//VK_SHADER_STAGE_CALLABLE_BIT_KHR	可调用着色器	(扩展)

    VkShaderModule                      module;              // 着色器模块
    const char*                         pName;               // 入口点函数名称
    const VkSpecializationInfo*         pSpecializationInfo; // 特化常量信息(用于优化)
} VkPipelineShaderStageCreateInfo;
								

我们这样进行创建:


    long vertexSize, fragmentSize;

    const char* vertexCode = ReadFile("shaders/vert.spv", &vertexSize);
    const char* fragmentCode = ReadFile("shaders/frag.spv", &fragmentSize);

    VkShaderModule vertexMod = CreateShader(device, vertexCode, vertexSize);
    VkShaderModule fragmentMod = CreateShader(device, fragmentCode, fragmentSize);

    VkPipelineShaderStageCreateInfo vssInfo = {};
    vssInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    vssInfo.stage = VK_SHADER_STAGE_VERTEX_BIT;
    vssInfo.module = vertexMod;
    vssInfo.pName = "main";

    VkPipelineShaderStageCreateInfo fssInfo = {};
    fssInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    fssInfo.stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    fssInfo.module = fragmentMod;
    fssInfo.pName = "main";

    VkPipelineShaderStageCreateInfo shaderStages[2] = {vssInfo, fssInfo};
								

现在,我们开始创建管线,创建管线需要填写大量结构体:


typedef struct VkPipelineVertexInputStateCreateInfo {
    VkStructureType                             sType;           // 必须为 VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO
    const void*                                 pNext;           // nullptr
    VkPipelineVertexInputStateCreateFlags       flags;           // 0
    uint32_t                                    vertexBindingDescriptionCount;    // 顶点绑定描述数量
    const VkVertexInputBindingDescription*      pVertexBindingDescriptions;      // 绑定描述数组
    uint32_t                                    vertexAttributeDescriptionCount;  // 顶点属性描述数量
    const VkVertexInputAttributeDescription*    pVertexAttributeDescriptions;    // 属性描述数组
} VkPipelineVertexInputStateCreateInfo;
								

typedef struct VkPipelineInputAssemblyStateCreateInfo {
    VkStructureType                            sType;                  // 必须为 VK_STRUCTURE_TYPE_PIPELINE_INPUT_ASSEMBLY_STATE_CREATE_INFO
    const void*                                pNext;                  // nullptr
    VkPipelineInputAssemblyStateCreateFlags    flags;                  //  0
    VkPrimitiveTopology                        topology;               // 图元拓扑,拓扑类型主要有:
//VK_PRIMITIVE_TOPOLOGY_POINT_LIST	点列表(每个顶点独立)	
//VK_PRIMITIVE_TOPOLOGY_LINE_LIST	线条列表(每 2 个顶点一条线)	
//VK_PRIMITIVE_TOPOLOGY_LINE_STRIP	线条带(连续线条)	≥2	连续路径、折线
//VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST	三角形列表(每 3 个顶点一个三角形)	
//VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP	三角形带(连续三角形)
//VK_PRIMITIVE_TOPOLOGY_TRIANGLE_FAN	三角形扇	
//VK_PRIMITIVE_TOPOLOGY_LINE_LIST_WITH_ADJACENCY	线条列表 + 邻接信息	4 的倍数	
//VK_PRIMITIVE_TOPOLOGY_LINE_STRIP_WITH_ADJACENCY	线条带 + 邻接信息
//VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST_WITH_ADJACENCY	三角形列表 + 邻接信息	
//VK_PRIMITIVE_TOPOLOGY_TRIANGLE_STRIP_WITH_ADJACENCY	三角形带 + 邻接信息	
//VK_PRIMITIVE_TOPOLOGY_PATCH_LIST	面片列表(用于细分着色器)

    VkBool32                                   primitiveRestartEnable; // 是否启用图元重启
} VkPipelineInputAssemblyStateCreateInfo;
								

typedef struct VkPipelineDynamicStateCreateInfo {
    VkStructureType                      sType;               // 必须为 VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO
    const void*                          pNext;               // nullptr
    VkPipelineDynamicStateCreateFlags    flags;               // 0
    uint32_t                             dynamicStateCount;   // 动态状态数量
    const VkDynamicState*                pDynamicStates;      // 动态状态枚举数组
} VkPipelineDynamicStateCreateInfo;
								

Vulkan支持的动态状态枚举主要有:
动态状态枚举 对应的设置函数 描述
VK_DYNAMIC_STATE_VIEWPORT vkCmdSetViewport 视口(Viewport)
VK_DYNAMIC_STATE_SCISSOR vkCmdSetScissor 裁剪区域(Scissor)
VK_DYNAMIC_STATE_LINE_WIDTH vkCmdSetLineWidth 线宽
VK_DYNAMIC_STATE_DEPTH_BIAS vkCmdSetDepthBias 深度偏移
VK_DYNAMIC_STATE_BLEND_CONSTANTS vkCmdSetBlendConstants 混合常量
VK_DYNAMIC_STATE_DEPTH_BOUNDS vkCmdSetDepthBounds 深度边界
VK_DYNAMIC_STATE_STENCIL_COMPARE_MASK vkCmdSetStencilCompareMask 模板比较掩码
VK_DYNAMIC_STATE_STENCIL_WRITE_MASK vkCmdSetStencilWriteMask 模板写入掩码
VK_DYNAMIC_STATE_STENCIL_REFERENCE vkCmdSetStencilReference 模板参考值
VK_DYNAMIC_STATE_CULL_MODE vkCmdSetCullMode 剔除模式(Vulkan 1.3)
VK_DYNAMIC_STATE_FRONT_FACE vkCmdSetFrontFace 正面朝向(Vulkan 1.3)
VK_DYNAMIC_STATE_PRIMITIVE_TOPOLOGY vkCmdSetPrimitiveTopology 图元拓扑(Vulkan 1.3)
VK_DYNAMIC_STATE_VERTEX_INPUT_BINDING_STRIDE vkCmdSetVertexInputBindingStride 顶点绑定步长(Vulkan 1.3)
VK_DYNAMIC_STATE_DEPTH_TEST_ENABLE vkCmdSetDepthTestEnable 深度测试启用(Vulkan 1.3)
VK_DYNAMIC_STATE_DEPTH_WRITE_ENABLE vkCmdSetDepthWriteEnable 深度写入启用(Vulkan 1.3)
VK_DYNAMIC_STATE_DEPTH_COMPARE_OP vkCmdSetDepthCompareOp 深度比较操作(Vulkan 1.3)
VK_DYNAMIC_STATE_DEPTH_BOUNDS_TEST_ENABLE vkCmdSetDepthBoundsTestEnable 深度边界测试启用(Vulkan 1.3)
VK_DYNAMIC_STATE_STENCIL_TEST_ENABLE vkCmdSetStencilTestEnable 模板测试启用(Vulkan 1.3)
VK_DYNAMIC_STATE_STENCIL_OP vkCmdSetStencilOp 模板操作(Vulkan 1.3)
VK_DYNAMIC_STATE_RASTERIZER_DISCARD_ENABLE vkCmdSetRasterizerDiscardEnable 光栅化丢弃启用(Vulkan 1.3)
VK_DYNAMIC_STATE_DEPTH_BIAS_ENABLE vkCmdSetDepthBiasEnable 深度偏移启用(Vulkan 1.3)
VK_DYNAMIC_STATE_PRIMITIVE_RESTART_ENABLE vkCmdSetPrimitiveRestartEnable 图元重启启用(Vulkan 1.3)


typedef struct VkPipelineViewportStateCreateInfo {
    VkStructureType                       sType;                 // 必须为 VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO
    const void*                           pNext;                 // nullptr
    VkPipelineViewportStateCreateFlags    flags;                 // 0
    uint32_t                              viewportCount;         // 视口数量
    const VkViewport*                     pViewports;            // 视口数组
    uint32_t                              scissorCount;          // 裁剪区域数量
    const VkRect2D*                       pScissors;             // 裁剪区域数组
} VkPipelineViewportStateCreateInfo;
								

typedef struct VkPipelineRasterizationStateCreateInfo {
    VkStructureType                            sType;                     // 必须为 VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO
    const void*                                pNext;                     // nullptr
    VkPipelineRasterizationStateCreateFlags    flags;                     // 0
    VkBool32                                   depthClampEnable;          // 是否启用深度钳制
    VkBool32                                   rasterizerDiscardEnable;   // 是否丢弃所有片元(不渲染任何内容)
    VkPolygonMode                              polygonMode;               // 多边形模式,主要有:
//VK_POLYGON_MODE_FILL	        填充模式(默认)	
//VK_POLYGON_MODE_LINE	线框模式	
//VK_POLYGON_MODE_POINT	点模式	

    VkCullModeFlags                            cullMode;                  // 面剔除模式,主要有:
//VK_CULL_MODE_NONE	不剔除任何面(所有面都渲染)
//VK_CULL_MODE_FRONT_BIT	剔除正面(朝向相机的面)
//VK_CULL_MODE_BACK_BIT	剔除背面(背向相机的面)【最常用】
//VK_CULL_MODE_FRONT_AND_BACK	剔除所有面(什么也不渲染)

    VkFrontFace                                frontFace;                 // 正面朝向
//VK_FRONT_FACE_COUNTER_CLOCKWISE	逆时针环绕(CCW)为正 【Vulkan 默认】
//VK_FRONT_FACE_CLOCKWISE	顺时针环绕(CW)为正

    VkBool32                                   depthBiasEnable;           // 是否启用深度偏移
    float                                      depthBiasConstantFactor;   // 深度偏移常量因子
    float                                      depthBiasClamp;            // 深度偏移最大值
    float                                      depthBiasSlopeFactor;      // 深度偏移斜率因子
//计算公式为:depthBias = constantFactor + max(slopeFactor * maxSlope, 0.0)

    float                                      lineWidth;                 // 线宽(仅在 Vulkan 1.0 中静态设置)
} VkPipelineRasterizationStateCreateInfo;
								

typedef struct VkPipelineMultisampleStateCreateInfo {
    VkStructureType                          sType;                 // 必须为 VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO
    const void*                              pNext;                 // nullptr
    VkPipelineMultisampleStateCreateFlags    flags;                 // 0
    VkSampleCountFlagBits                    rasterizationSamples;  // 采样点数
//VK_SAMPLE_COUNT_1_BIT	1(无 MSAA)	标准渲染(性能最优)
//VK_SAMPLE_COUNT_2_BIT	2	轻量级抗锯齿
//VK_SAMPLE_COUNT_4_BIT	4	最常用(质量和性能的平衡)
//VK_SAMPLE_COUNT_8_BIT	8	高质量抗锯齿(性能开销较大)
//VK_SAMPLE_COUNT_16_BIT	16	极高品质(仅高端硬件支持)
//VK_SAMPLE_COUNT_32_BIT	32	超采样(非常罕见)
//VK_SAMPLE_COUNT_64_BIT	64	几乎不可用

    VkBool32                                 sampleShadingEnable;   // 是否启用样本着色(Sample Shading)
    float                                    minSampleShading;      // 最小样本着色率(0.0 ~ 1.0)
    const VkSampleMask*                      pSampleMask;           // 样本遮罩(控制哪些样本被使用)
    VkBool32                                 alphaToCoverageEnable; // 是否启用 Alpha-to-Coverage(透明度抗锯齿)
    VkBool32                                 alphaToOneEnable;      // 是否启用 Alpha-to-One(将 Alpha 替换为 1.0)
} VkPipelineMultisampleStateCreateInfo;
								

typedef struct VkPipelineColorBlendAttachmentState {
    VkBool32                 blendEnable;            // 是否启用混合
    VkBlendFactor            srcColorBlendFactor;    // 源颜色的混合因子(RGB)
    VkBlendFactor            dstColorBlendFactor;    // 目标颜色的混合因子(RGB)
    VkBlendOp                colorBlendOp;           // 颜色混合操作(RGB)
    VkBlendFactor            srcAlphaBlendFactor;    // 源颜色的混合因子(Alpha)
    VkBlendFactor            dstAlphaBlendFactor;    // 目标颜色的混合因子(Alpha)
    VkBlendOp                alphaBlendOp;           // Alpha 混合操作
    VkColorComponentFlags    colorWriteMask;         // 颜色通道写入掩码(RGBA)
} VkPipelineColorBlendAttachmentState;
								

混合因子主要有:
混合因子 公式(RGB) 公式(Alpha)
VK_BLEND_FACTOR_ZERO (0, 0, 0) 0
VK_BLEND_FACTOR_ONE (1, 1, 1) 1
VK_BLEND_FACTOR_SRC_COLOR (Rs, Gs, Bs) As
VK_BLEND_FACTOR_ONE_MINUS_SRC_COLOR (1-Rs, 1-Gs, 1-Bs) 1-As
VK_BLEND_FACTOR_DST_COLOR (Rd, Gd, Bd) Ad
VK_BLEND_FACTOR_ONE_MINUS_DST_COLOR (1-Rd, 1-Gd, 1-Bd) 1-Ad
VK_BLEND_FACTOR_SRC_ALPHA (As, As, As) As
VK_BLEND_FACTOR_ONE_MINUS_SRC_ALPHA (1-As, 1-As, 1-As) 1-As
VK_BLEND_FACTOR_DST_ALPHA (Ad, Ad, Ad) Ad
VK_BLEND_FACTOR_ONE_MINUS_DST_ALPHA (1-Ad, 1-Ad, 1-Ad) 1-Ad
VK_BLEND_FACTOR_CONSTANT_COLOR (Cc, Cc, Cc) Ac
VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_COLOR (1-Cc, 1-Cc, 1-Cc) 1-Ac
VK_BLEND_FACTOR_CONSTANT_ALPHA (Ac, Ac, Ac) Ac
VK_BLEND_FACTOR_ONE_MINUS_CONSTANT_ALPHA (1-Ac, 1-Ac, 1-Ac) 1-Ac
VK_BLEND_FACTOR_SRC_ALPHA_SATURATE (f, f, f),其中 f = min(As, 1-Ad) 1

混合操作主要有:
混合操作 公式
VK_BLEND_OP_ADD Src * SrcFactor + Dst * DstFactor
VK_BLEND_OP_SUBTRACT Src * SrcFactor - Dst * DstFactor
VK_BLEND_OP_REVERSE_SUBTRACT Dst * DstFactor - Src * SrcFactor
VK_BLEND_OP_MIN min(Src, Dst)
VK_BLEND_OP_MAX max(Src, Dst)
VK_BLEND_OP_ZERO_EXT 扩展操作(较少使用)

颜色通道写入掩码主要有:
VK_COLOR_COMPONENT_R_BIT 写入红色通道
VK_COLOR_COMPONENT_G_BIT 写入绿色通道
VK_COLOR_COMPONENT_B_BIT 写入蓝色通道
VK_COLOR_COMPONENT_A_BIT 写入 Alpha 通道

 
typedef struct VkPipelineColorBlendStateCreateInfo {
    VkStructureType                               sType;               // 必须为 VK_STRUCTURE_TYPE_PIPELINE_COLOR_BLEND_STATE_CREATE_INFO
    const void*                                   pNext;               // nullptr
    VkPipelineColorBlendStateCreateFlags          flags;               //0
    VkBool32                                      logicOpEnable;       // 是否启用逻辑操作(取代混合)
    VkLogicOp                                     logicOp;             // 逻辑操作类型,主要有
//逻辑操作	公式(按位)
//VK_LOGIC_OP_CLEAR	0	
//VK_LOGIC_OP_AND	Src & Dst	
//VK_LOGIC_OP_OR	Src | Dst	
//VK_LOGIC_OP_XOR	Src ^ Dst	
//VK_LOGIC_OP_SET	1	
//VK_LOGIC_OP_COPY	
//VK_LOGIC_OP_COPY_INVERTED	~Src	
//VK_LOGIC_OP_NAND	~(Src & Dst)	
//VK_LOGIC_OP_NOR	~(Src | Dst)	
//VK_LOGIC_OP_EQUIVALENT	~(Src ^ Dst)	

    uint32_t                                      attachmentCount;     // 颜色附件数量
    const VkPipelineColorBlendAttachmentState*    pAttachments;        // 每个附件的混合配置数组
    float                                         blendConstants[4];   // 全局混合常量(RGBA)
} VkPipelineColorBlendStateCreateInfo;
								
 
typedef struct VkPipelineLayoutCreateInfo {
    VkStructureType                 sType;                  // 必须为 VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO
    const void*                     pNext;                  // nullptr
    VkPipelineLayoutCreateFlags     flags;                  //  0
    uint32_t                        setLayoutCount;         // 描述符集布局数量
    const VkDescriptorSetLayout*    pSetLayouts;            // 描述符集布局数组
    uint32_t                        pushConstantRangeCount; // 推式常量范围数量
    const VkPushConstantRange*      pPushConstantRanges;    // 推式常量范围数组
} VkPipelineLayoutCreateInfo;									
								
 
typedef struct VkGraphicsPipelineCreateInfo {
    VkStructureType                                 sType;               // 必须为 VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO
    const void*                                     pNext;               // nullptr
    VkPipelineCreateFlags                           flags;               // 创建标志,主要有
//VK_PIPELINE_CREATE_DISABLE_OPTIMIZATION_BIT	禁用优化(快速编译)	
//VK_PIPELINE_CREATE_ALLOW_DERIVATIVES_BIT	允许派生子管线	
//VK_PIPELINE_CREATE_DERIVATIVE_BIT	标记为派生管线	
//VK_PIPELINE_CREATE_VIEW_INDEX_FROM_DEVICE_INDEX_BIT	多设备渲染
//VK_PIPELINE_CREATE_DISPATCH_BASE_BIT	支持基础调度	

    uint32_t                                        stageCount;          // 着色器阶段数量
    const VkPipelineShaderStageCreateInfo*          pStages;             // 着色器阶段数组
    const VkPipelineVertexInputStateCreateInfo*     pVertexInputState;   // 顶点输入状态
    const VkPipelineInputAssemblyStateCreateInfo*   pInputAssemblyState; // 输入组装状态
    const VkPipelineTessellationStateCreateInfo*    pTessellationState;  // 细分状态
    const VkPipelineViewportStateCreateInfo*        pViewportState;      // 视口状态
    const VkPipelineRasterizationStateCreateInfo*   pRasterizationState; // 光栅化状态
    const VkPipelineMultisampleStateCreateInfo*     pMultisampleState;   // 多重采样状态
    const VkPipelineDepthStencilStateCreateInfo*    pDepthStencilState;  // 深度模板状态
    const VkPipelineColorBlendStateCreateInfo*      pColorBlendState;    // 颜色混合状态
    const VkPipelineDynamicStateCreateInfo*         pDynamicState;       // 动态状态
    VkPipelineLayout                                layout;              // 管线布局
    VkRenderPass                                    renderPass;          // 渲染通道
    uint32_t                                        subpass;             // 子通道索引
    VkPipeline                                      basePipelineHandle;  // 基管线句柄
    int32_t                                         basePipelineIndex;   // 基管线索引
} VkGraphicsPipelineCreateInfo;								
								
 
typedef struct VkAttachmentDescription {
    VkAttachmentDescriptionFlags    flags;          //  0
    VkFormat                        format;         // 像素格式与上面的图像视图一样即可
    VkSampleCountFlagBits           samples;        // 采样数,和上面那个一样
    VkAttachmentLoadOp              loadOp;         // 颜色/深度数据加载操作
    VkAttachmentStoreOp             storeOp;        // 颜色/深度数据存储操作
    VkAttachmentLoadOp              stencilLoadOp;  // 模板数据加载操作
    VkAttachmentStoreOp             stencilStoreOp; // 模板数据存储操作
    VkImageLayout                   initialLayout;  // 渲染前的图像布局
    VkImageLayout                   finalLayout;    // 渲染后的图像布局
} VkAttachmentDescription;									
								

关于上面出现的后面带Op的,他们的操作有如下几种:
VK_ATTACHMENT_LOAD_OP_LOAD 保留附件已有内容
VK_ATTACHMENT_LOAD_OP_CLEAR 清除附件内容(指定颜色/深度值)
VK_ATTACHMENT_LOAD_OP_DONT_CARE 不关心旧内容(可丢弃)
VK_ATTACHMENT_STORE_OP_STORE 保存渲染结果
VK_ATTACHMENT_STORE_OP_DONT_CARE 丢弃渲染结果

还有Layout结尾的:
VK_IMAGE_LAYOUT_UNDEFINED 未定义(内容无效) 配合 LOAD_OP_CLEAR 或 DONT_CARE
VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 颜色附件的最佳布局 颜色渲染目标
VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL 深度/模板附件的最佳布局 深度/模板渲染目标
VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 用于呈现到屏幕 交换链图像的最终布局
VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL 着色器只读 纹理采样
VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL / DEST 数据传输 拷贝/清除图像

 
typedef struct VkAttachmentReference {
    uint32_t         attachment;  // 附件索引(指向 VkAttachmentDescription 数组)
    VkImageLayout    layout;      // 该子通道中使用的图像布局,主要有:
//颜色附件:VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL
//深度/模板附件:VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL
//只读输入:VK_IMAGE_LAYOUT_SHADER_READ_ONLY_OPTIMAL
//不关心:VK_IMAGE_LAYOUT_UNDEFINED
} VkAttachmentReference;
								
 
typedef struct VkSubpassDescription {
    VkSubpassDescriptionFlags       flags;                 // 0
    VkPipelineBindPoint             pipelineBindPoint;     // 管线绑定点
//VK_PIPELINE_BIND_POINT_GRAPHICS:图形管线
//VK_PIPELINE_BIND_POINT_COMPUTE:计算管线

    uint32_t                        inputAttachmentCount;  // 输入附件数量
    const VkAttachmentReference*    pInputAttachments;     // 输入附件引用数组
    uint32_t                        colorAttachmentCount;  // 颜色附件数量
    const VkAttachmentReference*    pColorAttachments;     // 颜色附件引用数组
    const VkAttachmentReference*    pResolveAttachments;   // 解析附件引用数组(用于 MSAA)
    const VkAttachmentReference*    pDepthStencilAttachment; // 深度/模板附件引用(单个)
    uint32_t                        preserveAttachmentCount; // 保留附件数量
    const uint32_t*                 pPreserveAttachments;  // 保留附件索引数组
} VkSubpassDescription;									
								
 
typedef struct VkSubpassDependency {
    uint32_t                srcSubpass;      // 源子通道索引(依赖的起点)特殊值:VK_SUBPASS_EXTERNAL表示外部操作
    uint32_t                dstSubpass;      // 目标子通道索引(依赖的终点)特殊值:VK_SUBPASS_EXTERNAL表示外部操作
    VkPipelineStageFlags    srcStageMask;    // 源管线阶段掩码(在哪个阶段完成)常见值:VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT、VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
    VkPipelineStageFlags    dstStageMask;    // 目标管线阶段掩码(在哪个阶段等待)
    VkAccessFlags           srcAccessMask;   // 源访问掩码(需要哪些访问完成)VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT(颜色写入完成)
    VkAccessFlags           dstAccessMask;   // 目标访问掩码(需要哪些访问可用)VK_ACCESS_INPUT_ATTACHMENT_READ_BIT(输入附件读取可用)
    VkDependencyFlags       dependencyFlags; // 依赖标志
//0:默认依赖(所有前序工作完成才能开始后续工作)。
//VK_DEPENDENCY_BY_REGION_BIT:按区域依赖(只依赖重叠的像素区域,性能优化!)。
//VK_DEPENDENCY_VIEW_LOCAL_BIT:视图本地依赖(用于多视图渲染)。
} VkSubpassDependency;									
								
 
typedef struct VkRenderPassCreateInfo {
    VkStructureType                   sType;           // 必须为 VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO
    const void*                       pNext;           // nullptr
    VkRenderPassCreateFlags           flags;           // 0
    uint32_t                          attachmentCount; // 附件数量
    const VkAttachmentDescription*    pAttachments;    // 附件描述数组
    uint32_t                          subpassCount;    // 子通道数量
    const VkSubpassDescription*       pSubpasses;      // 子通道描述数组
    uint32_t                          dependencyCount; // 子通道依赖数量
    const VkSubpassDependency*        pDependencies;   // 子通道依赖数组
} VkRenderPassCreateInfo;									
								

填写好这些信息之后,我就可以开始创建渲染管线了,先创建渲染通道(VkRenderPass),然后创建管线布局(VkPipelineLayout),最后就是渲染管线(VkPipeline)了,创建完后,先前的两个着色器就可以销毁了,示例代码:


    VkAttachmentDescription colorAttachment = {};
    colorAttachment.format = surfaceFormat.format;
    colorAttachment.samples = VK_SAMPLE_COUNT_1_BIT;
    colorAttachment.loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    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;
    colorAttachment.finalLayout = VK_IMAGE_LAYOUT_PRESENT_SRC_KHR;

    VkAttachmentReference colorAttachRef = {};
    colorAttachRef.attachment = 0;
    colorAttachRef.layout = VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL;

    VkSubpassDescription spDes = {};
    spDes.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    spDes.colorAttachmentCount = 1;
    spDes.pColorAttachments = &colorAttachRef;

    VkSubpassDependency spDep = {};
    spDep.srcSubpass = VK_SUBPASS_EXTERNAL;
    spDep.dstSubpass = 0;
    spDep.srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    spDep.srcAccessMask = 0;
    spDep.dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    spDep.dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;

    VkRenderPassCreateInfo rpCreateInfo = {};
    rpCreateInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    rpCreateInfo.attachmentCount = 1;
    rpCreateInfo.pAttachments = &colorAttachment;
    rpCreateInfo.subpassCount = 1;
    rpCreateInfo.pSubpasses = &spDes;
    rpCreateInfo.dependencyCount = 1;
    rpCreateInfo.pDependencies = &spDep;

    VkRenderPass renderPass;
    vkCreateRenderPass(device, &rpCreateInfo, nullptr, &renderPass);


    VkPipelineVertexInputStateCreateInfo vertexInputCreateInfo = {};
    vertexInputCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VERTEX_INPUT_STATE_CREATE_INFO;
    vertexInputCreateInfo.vertexAttributeDescriptionCount = 0;
    vertexInputCreateInfo.vertexBindingDescriptionCount = 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;

    VkDynamicState dynamicStates[] = {
        VK_DYNAMIC_STATE_VIEWPORT,
        VK_DYNAMIC_STATE_SCISSOR
    };

    VkPipelineDynamicStateCreateInfo dynaStateCreateInfo = {};
    dynaStateCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_DYNAMIC_STATE_CREATE_INFO;
    dynaStateCreateInfo.dynamicStateCount = sizeof(dynamicStates) / sizeof(dynamicStates[0]);
    dynaStateCreateInfo.pDynamicStates = dynamicStates;

    VkPipelineViewportStateCreateInfo viewportCreateInfo = {};
    viewportCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_VIEWPORT_STATE_CREATE_INFO;
    viewportCreateInfo.viewportCount = 1;
    viewportCreateInfo.scissorCount = 1;

    VkPipelineRasterizationStateCreateInfo rasterCreateInfo = {};
    rasterCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_RASTERIZATION_STATE_CREATE_INFO;
    rasterCreateInfo.depthClampEnable = VK_FALSE;
    rasterCreateInfo.rasterizerDiscardEnable = VK_FALSE;
    rasterCreateInfo.polygonMode = VK_POLYGON_MODE_FILL;
    rasterCreateInfo.lineWidth = 1.0f;
    rasterCreateInfo.cullMode = VK_CULL_MODE_BACK_BIT;
    rasterCreateInfo.frontFace = VK_FRONT_FACE_CLOCKWISE;
    rasterCreateInfo.depthBiasEnable = VK_FALSE;

    VkPipelineMultisampleStateCreateInfo multisampleCreateInfo = {};
    multisampleCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_MULTISAMPLE_STATE_CREATE_INFO;
    multisampleCreateInfo.sampleShadingEnable = VK_FALSE;
    multisampleCreateInfo.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;
    colorBlending.logicOpEnable = VK_FALSE;
    colorBlending.attachmentCount = 1;
    colorBlending.pAttachments = &colorBlendAttachment;

    VkPipelineLayoutCreateInfo pipelineLayoutInfo{};
    pipelineLayoutInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_LAYOUT_CREATE_INFO;
    pipelineLayoutInfo.setLayoutCount = 0;
    pipelineLayoutInfo.pushConstantRangeCount = 0;

    VkPipelineLayout pipelineLayout;
    vkCreatePipelineLayout(device, &pipelineLayoutInfo, nullptr, &pipelineLayout);

    VkGraphicsPipelineCreateInfo pipelineInfo{};
    pipelineInfo.sType = VK_STRUCTURE_TYPE_GRAPHICS_PIPELINE_CREATE_INFO;
    pipelineInfo.stageCount = 2;
    pipelineInfo.pStages = shaderStages;
    pipelineInfo.pVertexInputState = &vertexInputCreateInfo;
    pipelineInfo.pInputAssemblyState = &inputAssembly;
    pipelineInfo.pViewportState = &viewportCreateInfo;
    pipelineInfo.pRasterizationState = &rasterCreateInfo;
    pipelineInfo.pMultisampleState = &multisampleCreateInfo;
    pipelineInfo.pDepthStencilState = nullptr;
    pipelineInfo.pColorBlendState = &colorBlending;
    pipelineInfo.pDynamicState = &dynaStateCreateInfo;
    pipelineInfo.layout = pipelineLayout;
    pipelineInfo.renderPass = renderPass;
    pipelineInfo.subpass = 0;

    VkPipeline graphicsPipeline;
    vkCreateGraphicsPipelines(device, VK_NULL_HANDLE, 1, &pipelineInfo, nullptr, &graphicsPipeline);

    vkDestroyShaderModule(device, vertexMod, nullptr);
    vkDestroyShaderModule(device, fragmentMod, nullptr);
								

根据刚刚的Vulkan渲染管线图像最后会输出到帧缓冲(Framebuffer),所以我还要为交换链中的每个图像视图创建帧缓冲,信息格式如下:


typedef struct VkFramebufferCreateInfo {
    VkStructureType           sType;              // 必须为 VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO
    const void*               pNext;              // nullptr
    VkFramebufferCreateFlags  flags;              // 0
    VkRenderPass              renderPass;         // 关联的渲染通道
    uint32_t                  attachmentCount;    // 附件数量
    const VkImageView*        pAttachments;       // 图像视图数组
    uint32_t                  width;              // 帧缓冲宽度(像素)
    uint32_t                  height;             // 帧缓冲高度(像素)
    uint32_t                  layers;             // 层数(通常为 1)
} VkFramebufferCreateInfo;
								

创建示例如下:

 
	VkFramebuffer frameBuffers[imagesNum];

    for (unsigned int i = 0; i < imagesNum; i++) {
        VkImageView attachments[] = {
            spImagesView[i]
        };

        VkFramebufferCreateInfo framebufferInfo{};
        framebufferInfo.sType = VK_STRUCTURE_TYPE_FRAMEBUFFER_CREATE_INFO;
        framebufferInfo.renderPass = renderPass;
        framebufferInfo.attachmentCount = 1;
        framebufferInfo.pAttachments = attachments;
        framebufferInfo.width = spExtent.width;
        framebufferInfo.height = spExtent.height;
        framebufferInfo.layers = 1;

        vkCreateFramebuffer(device, &framebufferInfo, nullptr, &frameBuffers[i]);
    }
								

做完这些,我们就可以进入循环,开始渲染了。
首先我们可以让CPU先准备下一帧的绘制,但是为了保证CPU 不会在 GPU 还在使用资源时覆盖或修改它们,我们需要等栅栏被触发完再进行下一步,设置栅栏为未触发状态后,进行下一步:

 
vkWaitForFences(device, 1, &flightFen, VK_TRUE, UINT64_MAX);
vkResetFences(device, 1, &flightFen);
								

接着我们需要从交换链中申请一张可以用来绘图的图像:


uint32_t imageIndex;
vkAcquireNextImageKHR(device, swapChain, UINT64_MAX, imageSem, VK_NULL_HANDLE, &imageIndex);
								

我们重置命令缓冲区,重新绘制命令:


vkResetCommandBuffer(commandBuffer, 0);
							    

我们重置命令缓冲区,重新绘制命令:

 
vkResetCommandBuffer(commandBuffer, 0);
								

用一个空白的缓冲区开始录制命令:

 
VkCommandBufferBeginInfo cbbInfo = {};
cbbInfo.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFO;
vkBeginCommandBuffer(commandBuffer, &cbbInfo);
								

同时我们绑定渲染通道和渲染管线

 
VkRenderPassBeginInfo renderPassInfo{};
renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
renderPassInfo.renderPass = renderPass;
renderPassInfo.framebuffer = frameBuffers[imageIndex];
renderPassInfo.renderArea.offset = { 0, 0 };
renderPassInfo.renderArea.extent = spExtent;

VkClearValue clearColor = { {{0.0f, 0.0f, 0.0f, 1.0f}} };
renderPassInfo.clearValueCount = 1;
renderPassInfo.pClearValues = &clearColor;

vkCmdBeginRenderPass(commandBuffer, &renderPassInfo, VK_SUBPASS_CONTENTS_INLINE);

vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, graphicsPipeline);
								

VkRenderPassBeginInfo的详细参数可以去Vulkan的官方文档查看

然后我们设置视口和裁剪器:


VkViewport viewport{};
viewport.x = 0.0f;
viewport.y = 0.0f;
viewport.width = static_cast(spExtent.width);
viewport.height = static_cast(spExtent.height);
viewport.minDepth = 0.0f;
viewport.maxDepth = 1.0f;
vkCmdSetViewport(commandBuffer, 0, 1, &viewport);

VkRect2D scissor{};
scissor.offset = { 0, 0 };
scissor.extent = spExtent;
vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
								

我们设置渲染命令,参数依次为:vertexCount(顶点数)、instanceCount(实例数)、firstVertex(起始顶点偏移)、firstInstance(起始实例偏移)。


vkCmdDraw(commandBuffer, 3, 1, 0, 0);
								

然后我们结束渲染通道和命令缓冲区的录制:


vkCmdEndRenderPass(commandBuffer);
vkEndCommandBuffer(commandBuffer);
								

然后我们将命令缓冲区的队列提交到GPU队列:


		VkSemaphore waitSem[1] = {imageSem};
        VkSemaphore signalSem[1] = { renderSem };
        VkPipelineStageFlags waitStages[1] = {VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT};

        VkSubmitInfo submitInfo = {};
        submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
        submitInfo.waitSemaphoreCount = 1;
        submitInfo.pWaitSemaphores = waitSem;
        submitInfo.pWaitDstStageMask = waitStages;
        submitInfo.commandBufferCount = 1;
        submitInfo.pCommandBuffers = &commandBuffer;
        submitInfo.signalSemaphoreCount = 1;
        submitInfo.pSignalSemaphores = signalSem;

        vkQueueSubmit(graphicsQueue, 1, &submitInfo, flightFen) ;
								

最后我们将图像呈现即可:


        VkSwapchainKHR swapchains[1] = {swapChain};
        VkPresentInfoKHR presentInfo = {};
        presentInfo.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
        presentInfo.waitSemaphoreCount = 1;
        presentInfo.pWaitSemaphores = signalSem;

        presentInfo.swapchainCount = 1;
        presentInfo.pSwapchains = swapchains;

        presentInfo.pImageIndices = &imageIndex;

        vkQueuePresentKHR(presentQueue, &presentInfo);
								

循环部分就结束了,不过一定要记得在循环结束后调用vkDeviceWaitIdle(device);,让GPU先把最后一帧的活干完再停下。

最后要退出程序时,别忘了把你之前创建的那一大堆东西都销毁掉!并且记住销毁顺序:先创建的后销毁,后创建的先销毁!


    for (auto framebuffer : frameBuffers) {
        vkDestroyFramebuffer(device, framebuffer, nullptr);
    }

    vkDestroyPipeline(device, graphicsPipeline, nullptr);
    vkDestroyPipelineLayout(device, pipelineLayout, nullptr);

    vkDestroyRenderPass(device, renderPass, nullptr);

    free((void*)vertexCode);
    free((void*)fragmentCode);

    vkDestroySemaphore(device, imageSem, nullptr);
    vkDestroySemaphore(device, renderSem, nullptr);
    vkDestroyFence(device, flightFen, nullptr);
    vkDestroyCommandPool(device, commandPool, nullptr);

    for (auto imageView : spImagesView) {
        vkDestroyImageView(device, imageView, nullptr);
    }

    vkDestroySwapchainKHR(device, swapChain, nullptr);
    vkDestroyDevice(device, nullptr);
    SDL_Vulkan_DestroySurface(instance, surface, nullptr);
    vkDestroyInstance(instance, nullptr);
								

可以看到这么一套下来已经快写了五六百行代码了,但这也只是搭建起了最基本的一个框架,里面的三角形甚至都是硬编码出来的。

参考资料:
https://tutorial.vulkan.net.cn/
https://docs.vulkan.org/refpages/latest/refpages/
https://zh.wikipedia.org/wiki/Vulkan