目录

  • 第3章 队列和命令
    • 3.1 设备队列
    • 3.2 创建命令缓冲区
    • 3.3 记录命令
    • 3.4 回收利用命令缓冲区
    • 3.5 命令的提交
  • 第4章 移动数据
    • 4.1 管理资源状态
      • 4.1.1 管线屏障
      • 4.1.2 全局内存屏障
      • 4.1.3 缓冲区内存屏障
      • 4.1.4 图像内存屏障
    • 4.2 清除和填充缓冲区
    • 4.3 清空和填充图像
    • 4.4 复制图像数据
    • 4.5 复制压缩图像数据
    • 4.6 拉伸图像
  • 第5章 展示
    • 5.1 展示扩展
    • 5.2 展示表面
      • 5.2.1 在微软的 Windows 上展示
      • 5.2.2 在基于 Xlib 的平台上展示
      • 5.2.3 在 Xcb 上展示
    • 5.3 交换链
    • 5.4 全屏表面
    • 5.5 执行展示
    • 5.6 清除

第3章 队列和命令

3.1 设备队列

  • 查询物理设备队列族的属性

    • vkGetPhysicalDeviceQueueFamilyProperties

      • VkQueueFamilyProperties
  • VkDeviceQueueCreateInfo
    typedef struct VkDeviceQueueCreateInfo
    {//VK_STRUCTURE_TYPE_QUEUE_CREATE_INFO。VkStructureType          sType;//nullptrconst void*              pNext;//控制队列构建的标记信息, 暂时无标志,设置为 0VkDeviceQueueCreateFlags flags;//从哪个族中分配队列uint32_t                 queueFamilyIndex;//需分配的队列的个数uint32_t                 queueCount;const float*             pQueuePriorities;
    } VkDeviceQueueCreateInfo;
    
  • 设备中获取队列
    void vkGetDeviceQueue (VkDevice device,uint32_t queueFamilyIndex,uint32_t queueIndex,VkQueue* pQueue
    );
    

3.2 创建命令缓冲区

  • 队列的主要目的就是代表应用程序处理任务。任务就是记录到命令缓冲区(command buffer) 中的一系列命令
  • 创建命令池
    VkResult vkCreateCommandPool
    (VkDevice                       device,const VkCommandPoolCreateInfo* pCreateInfo,//应用程序管理的主机内存分配const VkAllocationCallbacks*   pAllocator,VkCommandPool*                 pCommandPool
    );
    
  • VkCommandPoolCreateInfo
    typedef struct VkCommandPoolCreateInfo
    {//VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFOVkStructureType          sType;//nullptrconst void*              pNext;//池以及从池分配的命令缓冲区的行为VkCommandPoolCreateFlags flags;//指定队列族uint32_t                 queueFamilyIndex;
    } VkCommandPoolCreateInfo;
    
  • VkCommandPoolCreateFlagBits
    • VK_COMMAND_POOL_CREATE_TRANSIENT_BIT

      • 命令缓冲区使用周期短,使用完很快退回给缓存池
      • 不设置这个标志位就意味着告诉 Vulkan,你将长时间持有这个命令缓冲区
      • 使用更先进的分配策略,以避免在频繁分配和归还命令缓冲区时产生内存碎片
    • VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT
      • 允许单个命令缓冲区可通过重置(或重启)而重用
      • 没有这个标志位, 只有池本身能够重置, 它隐式地回收所有由它分配的命令缓冲区
      • 跟踪每个命令缓冲区的重置状态,而不是简单地只是在池的级别上跟踪
  • 每一个标志位都会增加一些开销
  • 获取命令缓冲区
    VkResult vkAllocateCommandBuffers (VkDevice                           device,const VkCommandBufferAllocateInfo* pAllocateInfo,VkCommandBuffer*                   pCommandBuffers
    );
    
  • VkCommandBufferAllocateInfo
    typedef struct VkCommandBufferAllocateInfo
    {//VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_ INFO//or//VK_COMMAND_BUFFER_LEVEL_SECONDARYVkStructureType      sType;const void*          pNext;VkCommandPool        commandPool;VkCommandBufferLevel level;//要从缓存池中分配的命令缓冲区的数量uint32_t             commandBufferCount;
    } VkCommandBufferAllocateInfo;
    
  • 释放命令缓冲区
    void vkFreeCommandBuffers (VkDevice               device,VkCommandPool          commandPool,uint32_t               commandBufferCount,const VkCommandBuffer* pCommandBuffers
    );
    
  • 释放一个命令池所用的所有资源和它创建的所有命令缓冲区
    void vkDestroyCommandPool (VkDevice                     device,VkCommandPool                commandPool,const VkAllocationCallbacks* pAllocator
    );
    

3.3 记录命令

  • 多线程记录命令规则

    • 一个线程可以通过调用命令缓冲区函数依次将命令记录到多个命令缓冲区中
    • 两个或多个线程可以同时构建一个命令缓冲区,只要应用程序可以保证它们不同时执行命令缓冲区的构建函数
  • 最好对于一个线程有一个或者多个命令缓冲区,而不是多个线程共享一个
  • 为每一个线程创建一个命令池,就不会有任何冲突
  • 启动命令缓冲区
    VkResult vkBeginCommandBuffer (VkCommandBuffer                 commandBuffer,const VkCommandBufferBeginInfo* pBeginInfo
    );
    
  • VkCommandBufferBeginInfo
    typedef struct VkCommandBufferBeginInfo
    {//VK_STRUCTURE_TYPE_COMMAND_BUFFER_BEGIN_INFOVkStructureType                       sType;//nullptrconst void*                           pNext;//命令缓冲区将会如何使用VkCommandBufferUsageFlags             flags;//开启副命令缓冲区所需要的 第 13 章介绍const VkCommandBufferInheritanceInfo* pInheritanceInfo;
    } VkCommandBufferBeginInfo;
    
  • VkCommandBufferUsageFlagBits
    • VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT

      • 命令缓冲区只会记录和执行一次,然后销毁或回收
    • VK_COMMAND_BUFFER_USAGE_ONE_TIME_SUBMIT_BIT
      • 命令缓冲区会在渲染通道(render pass)里使用,只在副命令缓冲区里有效
      • 在创建主命令缓冲区时这个标志位会被忽略
    • VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT
      • 命令缓冲区有可能多次执行或者暂停
  • 在两个缓冲区对象之间复制数据
    void vkCmdCopyBuffer (VkCommandBuffer     commandBuffer,//指定源VkBuffer            srcBuffer,//指目标VkBuffer            dstBuffer,//指定区域的数量uint32_t            regionCount,//区域数组的地址const VkBufferCopy* pRegions);
    
  • VkBufferCopy
    typedef struct VkBufferCopy
    {//源偏移值VkDeviceSize srcOffset;//目标偏移值VkDeviceSize dstOffset;//要复制的区域的大小VkDeviceSize size;
    } VkBufferCopy;
    
  • 关于 Vulkan 操作的基本事实是,命令在调用时并不是立即执行的,仅仅把它们添加到命令缓 冲区的尾部。如果你正在从一个 CPU 可访问的内存区域复制数据(或者向其中复制),那么你需要确保以下几件事
    • 保证在设备执行命令前,数据在源区域
    • 保证源区域的数据是有效的,直到命令在设备上执行以后
    • 保证不读取目标数据,直到命令在设备上执行之后
  • 记录命令已完成
    VkResult vkEndCommandBuffer (VkCommandBuffer commandBuffer);
    

3.4 回收利用命令缓冲区

  • 命令缓冲区重量级的操作

    • 调用 vkAllocateCommand Buffers() 获取一个或者多个命令缓冲区的句柄,将命令记录到命令缓冲区中
    • 然后调用 vkFreeCommandBuffers() 把缓冲区归还到各自的池内
  • 重用命令缓冲区,重置
    VkResult vkResetCommandBuffer (VkCommandBuffer           commandBuffer,//指定重置命令缓冲区时的附加操作//现只有 VK_COMMAND_BUFFER_RESET_RELEASE_RESOURCES_BITVkCommandBufferResetFlags flags
    );
    
  • 一次性把从一个池分配的所有命令缓冲区进行重置
    VkResult vkResetCommandPool (VkDevice                device,VkCommandPool           commandPool,//VK_COMMAND_POOL_RESET_RELEASE_RESOURCES_BITVkCommandPoolResetFlags flags
    );
    

3.5 命令的提交

  • 命令缓冲区提交给设备的一个队列

    VkResult vkQueueSubmit (//目标设备队列VkQueue             queue,//提交的次数uint32_t            submitCount,//描述了每一次提交的信息const VkSubmitInfo* pSubmits,//栅栏(fence)对象的句柄//可用来等待本次提交执行的命令完成VkFence             fence
    );
    
  • VkSubmitInfo
    typedef struct VkSubmitInfo
    {//VK_STRUCTURE_TYPE_SUBMIT_INFOVkStructureType             sType;//nullptrconst void*                 pNext;//第十一章介绍等待信号量数量uint32_t                    waitSemaphoreCount;//第十一章介绍等待信号量const VkSemaphore*          pWaitSemaphores;//第十一章介绍const VkPipelineStageFlags* pWaitDstStageMask;//要执行的命令缓冲区的个数uint32_t                    commandBufferCount;//要执行的命令缓冲区const VkCommandBuffer*      pCommandBuffers;//第十一章介绍信号量个数uint32_t                    signalSemaphoreCount;//第十一章介绍信号量const VkSemaphore*          pSignalSemaphores;
    } VkSubmitInfo;
    
  • 对队列的访问必须要在外部保持同步
  • 等待提交给队列的所有任务完成
    VkResult vkQueueWaitIdle (VkQueue queue);
    
  • 要等待一个设备上所有队列的所有命令完成
    VkResult vkDeviceWaitIdle (VkDevice device);
    
  • 调用 vkQueueWaitIdle() 或者 vkDeviceWaitIdle()是不推荐的
    • 因为它们会强制完成队列或设备上的任何工作,这是非常重量级的工作

第4章 移动数据

  • 移动数据命令

    • 填充、复制、清除缓冲区与图像的相关命令

4.1 管理资源状态

  • 将资源从一个状态转变为另一个状态的基本行为就是屏障

4.1.1 管线屏障

  • 屏障是一种同步机制,用来管理内存访问,以及在 Vulkan 管线各个阶段里的资源状态变化

  • 对资源访问进行同步和改变状态的主要命令

    void vkCmdPipelineBarrier (VkCommandBuffer              commandBuffer,//哪个阶段的管线最后写入资源VkPipelineStageFlags         srcStageMask,//哪个阶段接下来要从资源读数据VkPipelineStageFlags         dstStageMask,//由屏障表示的依赖关系如何影响屏障引用的资源//只有 VK_DEPENDENCY_BY_REGION_BIT//表示屏障只影响被源阶段(如果能确定)改变的区域,此区域被目标阶段所使用VkDependencyFlags            dependencyFlags,uint32_t                     memoryBarrierCount,const VkMemoryBarrier*       pMemoryBarriers,uint32_t                     bufferMemoryBarrierCount,const VkBufferMemoryBarrier* pBufferMemoryBarriers,uint32_t                     imageMemoryBarrierCount,const VkImageMemoryBarrier*  pImageMemoryBarriers
    );
    
  • VkPipelineStageFlags

    • VK_PIPELINE_STAGE_TOP_OF_PIPE_BIT

      • 当设备开始处理命令时,马上认为访问到了管线的顶端
    • VK_PIPELINE_STAGE_DRAW_INDIRECT_BIT
      • 当管线执行一个间接命令时,它为命令从内存中取出一些参数。这是取出这些参数的阶段
    • VK_PIPELINE_STAGE_VERTEX_INPUT_BIT
      • 这是顶点属性从它们所在的缓冲区被取回的阶段
      • 此后,就可以覆盖顶点缓冲区的内容了,即使相关的顶点着色器没有执行完
    • VK_PIPELINE_STAGE_VERTEX_SHADER_BIT
      • 当一个绘制命令产生的所有顶点着色器工作完成时,这个阶段通过
    • VK_PIPELINE_STAGE_TESSELLATION_CONTROL_SHADER_BIT
      • 当一个绘制命令产生的所有细分控制着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_TESSELLATION_EVALUATION_SHADER_BIT
      • 当一个绘制命令产生的所有细分评估着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_GEOMETRY_SHADER_BIT
      • 当一个绘制命令产生的所有几何着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_FRAGMENT_SHADER_BIT
      • 当一个绘制命令产生的所有片段着色器调用都完成时,这个阶段通过
    • VK_PIPELINE_STAGE_EARLY_FRAGMENT_TESTS_BIT
      • 在片段着色器开始运行之前可能发生的所有逐片段测试都已经完成了
    • VK_PIPELINE_STAGE_LATE_FRAGMENT_TESTS_BIT
      • 在片段着色器开始运行之后可能发生的所有逐片段测试都已经完成了
    • VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT
      • 管线产生的片段都已经写入颜色附件中
    • VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT
      • 调度产生的计算着色器调用已经完成
    • VK_PIPELINE_STAGE_TRANSFER_BIT
      • 诸如 vkCmdCopyImage()vkCmdCopyBuffer() 等调用产生的任何延迟转移已经完成
    • VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT
      • 图形管线任何一部分的操作都已经完成
    • VK_PIPELINE_STAGE_HOST_BIT
      • 该管线阶段对应来自主机的访问
    • VK_PIPELINE_STAGE_ALL_GRAPHICS_BIT
      • 当用作目标时,这个特殊的标志表示任何管线阶段都可能访问内存
      • 当用作源时,实际上和 VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT 相同
    • VK_PIPELINE_STAGE_ALL_COMMANDS_BIT
      • 当你不知道接下来要发生什么时,使用它,它将同步所有的东西
  • 单次调用 vkCmdPipelineBarrier() 可用来触发多个屏障操作

  • 有 3 种类型的屏障操作

    • 全局内存屏障

      • 例如在主机和设备之间对映射内存的同步访问
    • 缓冲区屏障
      • 影响设备对缓冲区资源的访问
    • 图像屏障
      • 影响设备对图像资源的访问

4.1.2 全局内存屏障

  • vkCmdPipelineBarrier() 的形参 VkMemoryBarrier

    • 代表一个内存屏障
    typedef struct VkMemoryBarrier
    {//VK_STRUCTURE_TYPE_MEMORY_BARRIERVkStructureType sType;//nullptrconst void*     pNext;//源的访问掩码VkAccessFlags   srcAccessMask;//目标的访问掩码VkAccessFlags   dstAccessMask;
    } VkMemoryBarrier;
    
  • VkAccessFlagBits
    • 用于表示如何写入,指明该内存接下来如何读取
    • VK_ACCESS_INDIRECT_COMMAND_READ_BIT
      • 引用的内存将会是位于间接绘制命令或者调度命令里的命令源

        • vkCmdDrawIndirect()
        • vkCmdDispatchIndirect()
    • VK_ACCESS_INDEX_READ_BIT
      • 引用的内存将会是索引绘制命令

        • vkCmdDrawIndexed()
        • vkCmdDrawIndexedIndirect()
    • VK_ACCESS_VERTEX_ATTRIBUTE_READ_BIT
      • 引用的内存将是顶点数据源
    • VK_ACCESS_UNIFORM_READ_BIT
      • 引用的内存是被着色器访问的 uniform 块的数据源
    • VK_ACCESS_INPUT_ATTACHMENT_READ_BIT
      • 引用的内存用来存储用作一个输入附件的图像
    • VK_ACCESS_SHADER_READ_BIT
      • 引用的内存用于存储一个图像对象,在着色器内使用图像加载或者纹理读取的操作来读取该对象
    • VK_ACCESS_SHADER_WRITE_BIT
      • 引用的内存用于存储一个图像对象,在着色器内使用图像存储操作写入该对象
    • VK_ACCESS_COLOR_ATTACHMENT_READ_BIT
      • 引用的内存用于存储一个用作颜色附件的图像对象
    • VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT:
      • 引用的内存用来存储图像,该图像用作可写入的颜色附件
    • VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_READ_BIT
      • 用来存储图像,该图像用作深度或模板附件
    • VK_ACCESS_DEPTH_STENCIL_ATTACHMENT_WRITE_BIT
      • 用来存储图像,该图像用作深度或模板附件
    • VK_ACCESS_TRANSFER_READ_BIT
      • 用作转移操作

        • vkCmdCopyImage()
        • vkCmdCopyBuffer()
        • vkCmdCopyBufferToImage()
    • VK_ACCESS_TRANSFER_WRITE_BIT
      • 引用的内存用作转移操作的目标
    • VK_ACCESS_HOST_READ_BIT
      • 引用的内存被映射,并将被主机读取
    • VK_ACCESS_HOST_WRITE_BIT
      • 引用的内存被映射,并将被主机写入
    • VK_ACCESS_MEMORY_READ_BIT
      • 所有没有在之前明确提到的其他内存读取都应该指定这个标志位
    • VK_ACCESS_MEMORY_WRITE_BIT
      • 所有没有在之前明确提到的其他内存写入都应该指定这个标志位
  • 内存屏障提供两个重要的功能
    • 帮助避免危险的事

      • 写后读
      • 读后写
      • 写后写
    • 可确保数据一致性
      • 保证在管线不同部分中数据的视图的一致性

4.1.3 缓冲区内存屏障

  • 缓冲区内存屏障为备份缓冲区对象提供细粒度的内存控制
  • 执行的缓冲区内存屏障
    • vkCmdPipeline Barrier()

      • pBufferMemoryBarriers
  • VkBufferMemoryBarrier
    typedef struct VkBufferMemoryBarrier
    {//VK_STRUCTURE_TYPE_BUFFER_MEMORY_BARRIER VkStructureType sType;//nullptrconst void*     pNext;VkAccessFlags   srcAccessMask;VkAccessFlags   dstAccessMask;//缓冲区的所有权从一个队列转移到另一个队列且这些队列属于不同的族//没有转移下面两个参数为 VK_QUEUE_FAMILY_IGNOREDuint32_t        srcQueueFamilyIndex;uint32_t        dstQueueFamilyIndex;//缓冲区VkBuffer        buffer;//控制对整个缓冲区的访问为 0VkDeviceSize    offset;//控制对整个缓冲区的访问为 VK_WHOLE_SIZEVkDeviceSize    size;
    } VkBufferMemoryBarrier;
    

4.1.4 图像内存屏障

  • VkImageMemoryBarrier

    typedef struct VkImageMemoryBarrier
    {//VK_STRUCTURE_TYPE_IMAGE_MEMORY_BARRIER VkStructureType         sType;//nullptrconst void*             pNext;VkAccessFlags           srcAccessMask;VkAccessFlags           dstAccessMask;//屏障前面的图像所使用的布局VkImageLayout           oldLayout;//屏障后面的图像所使用的布局VkImageLayout           newLayout;uint32_t                srcQueueFamilyIndex;uint32_t                dstQueueFamilyIndex;VkImage                 image;//影响到图像的哪些部分VkImageSubresourceRange subresourceRange;
    } VkImageMemoryBarrier;
    
  • VkImageSubresourceRange
    typedef struct VkImageSubresourceRange
    {VkImageAspectFlags aspectMask;//指定最小数字的(最高分辨率)mipmap 层级//无则为0uint32_t           baseMipLevel;//指定层级数量//无则为1uint32_t           levelCount;//设置第一个层的索引//无则为0uint32_t           baseArrayLayer;//包含的层数//无则为1uint32_t           layerCount;
    } VkImageSubresourceRange;
    

4.2 清除和填充缓冲区

  • 向缓冲区填入特定值

    void vkCmdFillBuffer (VkCommandBuffer commandBuffer,//需要填充数据的缓冲区VkBuffer        dstBuffer,//指定填充操作开始的位置//必须是 4 的倍数//全部填充 0VkDeviceSize    dstOffset,//指定填充区域的大小//必须是 4 的倍数//全部填充 VK_WHOLE_SIZEVkDeviceSize    size,uint32_t        data
    );
    
    • 它适合小的、立即模式的缓冲区更新
  • 用浮点数清除缓冲区,可以把浮点数的值转换为 uint32_t 类型的值
    • 再传入 vkCmdFillBuffer()
    • 示例 Page 106
  • 更新数组或者小的数据结构里的数据
    • 直接从主机内存复制数据到缓冲区对象中
    void vkCmdUpdateBuffer (VkCommandBuffer commandBuffer,VkBuffer        dstBuffer,VkDeviceSize    dstOffset,VkDeviceSize    dataSize,//指向包含数据(最终会复制进缓冲区对象)的主机内存const uint32_t* pData
    );
    
  • vkCmdUpdateBuffer() 调用完成后,就可以释放主机内存数据结构体,或覆盖该结构体的内容
  • 向 uniform 缓冲区写入单个值,使用 vkCmdFillBuffer() 比使用缓冲区映射并调用 vkCmdCopyBuffer() 高效得多

4.3 清空和填充图像

  • 清除图像并把它变成固定值

    void vkCmdClearColorImage (VkCommandBuffer                commandBuffer,//需要清除数据的图像VkImage                        image,//在执行清除操作时,图像期望的布局//VK_IMAGE_LAYOUT_GENERAL//VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL VkImageLayout                  imageLayout,//用于清除图像的值 unionconst VkClearColorValue*       pColor,//指定区域的数量uint32_t                       rangeCount,const VkImageSubresourceRange* pRanges
    );
    
  • VkClearColorValue
    typedef union VkClearColorValue
    {float    float32[4];int32_t  int32[4];uint32_t uint32[4];
    } VkClearColorValue;
    
  • VkImageSubresourceRange
    typedef struct VkImageSubresourceRange
    {//必须设置为 VK_IMAGE_ASPECT_COLOR_BITVkImageAspectFlags aspectMask;uint32_t           baseMipLevel;uint32_t           levelCount;uint32_t           baseArrayLayer;uint32_t           layerCount;
    } VkImageSubresourceRange;
    
  • 如果需要清除同一个图像里的多个区域,并填充不同的颜色值,就需要多次调用该函数
  • 清除深度-模板图像
    void vkCmdClearDepthStencilImage (VkCommandBuffer                 commandBuffer,VkImage                         image,VkImageLayout                   imageLayout,const VkClearDepthStencilValue* pDepthStencil,uint32_t                        rangeCount,const VkImageSubresourceRange * pRanges
    );
    
  • VkClearDepthStencilValue
    typedef struct VkClearDepthStencilValue
    {float depth;uint32_t stencil;
    } VkClearDepthStencilValue;
    
  • VkImageSubresourceRange
    typedef struct VkImageSubresourceRange
    {//含VK_IMAGE_ASPECT_DEPTH_BIT和/或VK_IMAGE_ASPECT_STENCIL_BITVkImageAspectFlags aspectMask;uint32_t           baseMipLevel;uint32_t           levelCount;uint32_t           baseArrayLayer;uint32_t           layerCount;
    } VkImageSubresourceRange;
    
  • 给单个区域指定 VK_IMAGE_ASPECT_DEPTH_BIT 和 VK_IMAGE_ASPECT_STENCIL_BIT 属性,通常会比为两个区域中的每一个指定一个标志位要高效得多

4.4 复制图像数据

  • Vulkan 支持 3 种图像数据复制方式

    • 从缓冲区向图像复制
    • 在图像之间复制
    • 从图像向缓冲区复制
  • 从缓冲区向图像的一个或者多个区域复制数据
    void vkCmdCopyBufferToImage (VkCommandBuffer          commandBuffer,VkBuffer                 srcBuffer,VkImage                  dstImage,//VK_IMAGE_LAYOUT_GENERAL//VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMALVkImageLayout            dstImageLayout,uint32_t                 regionCount,//需要更新的区域const VkBufferImageCopy* pRegions
    );
    
  • VkBufferImageCopy
    typedef struct VkBufferImageCopy
    {//缓冲区中数据的偏移量VkDeviceSize             bufferOffset;//源图像中纹素的个数//为 0, 图像是在缓冲区中紧密排布与imageExtent.width 相等uint32_t                 bufferRowLength;//指定图像数据的行数//为 0,为源图像的行数等于imageExtent.heightuint32_t                 bufferImageHeight;VkImageSubresourceLayers imageSubresource;VkOffset3D               imageOffset;VkExtent3D               imageExtent;
    } VkBufferImageCopy;
    
  • VkImageSubresourceLayers
    typedef struct VkImageSubresourceLayers
    {//包含一个或者多个图像层面VkImageAspectFlags aspectMask;uint32_t           mipLevel;uint32_t           baseArrayLayer;uint32_t           layerCount;
    } VkImageSubresourceLayers;
    
  • 从一个图像对象复制数据到缓冲区中
    void vkCmdCopyImageToBuffer (VkCommandBuffer          commandBuffer,VkImage                  srcImage,VkImageLayout            srcImageLayout,VkBuffer                 dstBuffer,uint32_t                 regionCount,const VkBufferImageCopy* pRegions
    );
    
  • 在两幅图像之间复制数据
    • 可以一次复制多个区域
    void vkCmdCopyImage (VkCommandBuffer    commandBuffer,VkImage            srcImage,VkImageLayout      srcImageLayout,VkImage            dstImage,VkImageLayout      dstImageLayout,uint32_t           regionCount,const VkImageCopy* pRegions
    );
    
  • VkImageCopy
    typedef struct VkImageCopy
    {VkImageSubresourceLayers srcSubresource;VkOffset3D               srcOffset;VkImageSubresourceLayers dstSubresource;VkOffset3D               dstOffset;VkExtent3D               extent;
    } VkImageCopy;
    

4.5 复制压缩图像数据

  • 调用 vkCmdCopyImage() 命令,也可以在两幅压缩图像间或者压缩图像与非压缩图像间复制数据

    • 要求源图像和目标图像格式必须拥有相同的压缩块大小
    • 当从非压缩图像向压缩图像复制数据时,每一个源纹素被当作单个原生数据
      • 包含一个位数,与压缩图像中一个块的大小相同
    • 当从压缩格式向非压缩格式复制数据时,正好相反
      • Vulkan 不会解压缩图像数据

4.6 拉伸图像

  • 支持格式转换和改变复制区域的尺寸

    void vkCmdBlitImage (VkCommandBuffer    commandBuffer,//必须支持 VK_FORMAT_FEATURE_BLIT_SRC_BITVkImage            srcImage,VkImageLayout      srcImageLayout,//必须支持 VK_FORMAT_FEATURE_ BLIT_DST_BITVkImage            dstImage,VkImageLayout      dstImageLayout,uint32_t           regionCount,const VkImageBlit* pRegions,//VK_FILTER_NEAREST 或 VK_FILTER_LINEARVkFilter           filter
    );
    
  • VkImageBli
    typedef struct VkImageBlit {//定义源图像的子资源VkImageSubresourceLayers srcSubresource;//定义待复制区域的一角VkOffset3D               srcOffsets[2];//定义目标图像子资源VkImageSubresourceLayers dstSubresource;//定义目标区域的另外一角VkOffset3D               dstOffsets[2];
    } VkImageBlit;
    

第5章 展示

5.1 展示扩展

  • 实际上,一个 Vulkan 实现或许根本不支持展示

    • 展示是通过一套扩展处理的,这套扩展统称为 WS(I Window System Integration) 扩展或 WSI 系统
  • Vulkan 中的扩展功能在使用前必须显式地开启

5.2 展示表面

  • 在包含 <vulkan.h> 之前定义展示扩展的宏

    • VK_USE_PLATFORM_WIN32_KHR
    • VK_USE_PLATFORM_XLIB_KHR
    • VK_USE_PLATFORM_LIB_XCB_KHR

5.2.1 在微软的 Windows 上展示

  • 判断一个队列是否支持展示操作

    VkBool32 vkGetPhysicalDeviceWin32PresentationSupportKHR(VkPhysicalDevice physicalDevice,uint32_t         queueFamilyIndex
    );
    
  • 创建表面对象
    VkResult vkCreateWin32SurfaceKHR(VkInstance                         instance,const VkWin32SurfaceCreateInfoKHR* pCreateInfo,const VkAllocationCallbacks*       pAllocator,VkSurfaceKHR*                      pSurface
    );
    
  • VkWin32SurfaceCreateInfoKHR
    typedef struct VkWin32SurfaceCreateInfoKHR
    {//VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR VkStructure Type             sType;//nullptrconst void*                  pNext;//为 0VkWin32SurfaceCreateFlagsKHR flags;//用来创建本地窗口的应用程序或者模块的 HINSTANCE//可以调用 Win32 函数 GetModuleHandle 获取HINSTANCE                    hinstance;//本地窗口的句柄HWND                         hwnd;
    } VkWin32SurfaceCreateInfoKHR;
    

5.2.2 在基于 Xlib 的平台上展示

5.2.3 在 Xcb 上展示

5.3 交换链

  • 交换链对象是用来请求窗口系统创建一个或者多个可用来代表 Vulkan 表面的图像
  • 每一个交换链对象管理一个图像集
  • 创建交换链对象
    VkResult vkCreateSwapchainKHR(VkDevice                        device,const VkSwapchainCreateInfoKHR* pCreateInfo,const VkAllocationCallbacks*    pAllocator,VkSwapchainKHR*                 pSwapchain
    );
    
  • VkSwapchainCreateInfoKHR
    typedef struct VkSwapchainCreateInfoKHR
    {//VK_STRUCTURE_TYPE_SWAPCHAIN_CREATE_INFO_KHRVkStructureType               sType;//nullptrconst void*                   pNext;//设置为 0VkSwapchainCreateFlagsKHR     flags;VkSurfaceKHR                  surface;//交换链中的图像个数//双缓冲或者三缓冲//为 1 表示要求直接渲染到前端缓冲区并显示//可调用 vkGetPhysicalDeviceSurfaceCapabilitiesKHR()来获取交换链支持的最小和最大图像数目uint32_t                      minImageCount;//的图像的格式VkFormat                      imageFormat;//颜色空间VkColorSpaceKHR               imageColorSpace;//中图像以像素为单位的维度VkExtent2D                    imageExtent;//每张图像的层数uint32_t                      imageArrayLayers;//图像将如何使用VkImageUsageFlags             imageUsage;//图像在队列之间是如何共享的//如果图像在每个时刻仅用在一个队列上//为 VK_SHARING_MODE_EXCLUSIVE//用在多个队列上VK_SHARING_MODE_CONCURRENTVkSharingMode                 imageSharingMode;uint32_t                      queueFamilyIndexCount;const uint32_t*               pQueueFamilyIndices;//图像在展示给用户之前如何做变换//允许旋转或者翻转VkSurfaceTransformFlagBitsKHR preTransform;//如何处理 alpha 分量//忽略 alpha,为VK_COMPOSITE_ALPHA_OPAQUE_BIT_KHRVkCompositeAlphaFlagBitsKHR   compositeAlpha;//控制了与窗口系统的同步,以及图像展示到表面上的速率VkPresentModeKHR              presentMode;VkBool32                      clipped;//窗口改变尺寸并且交换链需要重新分配更大的图像时需要VkSwapchainKHR                oldSwapchain;
    } VkSwapchainCreateInfoKHR;
    
  • VkCompositeAlphaFlagBitsKHR
    • VK_PRESENT_MODE_IMMEDIATE_KHR

      • 当展示已经安排就绪时,图像要尽快展示给用户,不用等待诸如垂直清空等外部事件
      • 能提供可能的最高帧率
      • 但是会引起屏幕分裂或者其他瑕疵
    • VK_PRESENT_MODE_MAILBOX_KHR
      • 当展示新的图像时,就把它标记为待处理图像,在下一次,系统将把它展示给用户
    • VK_PRESENT_MODE_FIFO_KHR
      • 将要展示的图像存储在一个内部队列给,顺序地展示用户
    • VK_PRESENT_MODE_FIFO_RELAXED_KHR
      • 如果队列是空的并且垂直刷新发生了,队列中下一幅图像就立即展示
  • VkSwapchainCreateInfoKHR 中的参数必须和表面的能力相匹配
    • 查询

      VkResult vkGetPhysicalDeviceSurfaceCapabilitiesKHR(VkPhysicalDevice          physicalDevice,VkSurfaceKHR              surface,VkSurfaceCapabilitiesKHR* pSurfaceCapabilities
      );
      
  • VkSurfaceCapabilitiesKHR
    typedef struct VkSurfaceCapabilitiesKHR {uint32_t                      minImageCount;uint32_t                      maxImageCount;VkExtent2D                    currentExtent;VkExtent2D                    minImageExtent;VkExtent2D                    maxImageExtent;uint32_t                      maxImageArrayLayers;VkSurfaceTransformFlagsKHR    supportedTransforms; VkSurfaceTransformFlagBitsKHR currentTransform;VkCompositeAlphaFlagsKHR      supportedCompositeAlpha;VkImageUsageFlags             supportedUsageFlags;
    } VkSurfaceCapabilitiesKHR;
    
  • 获取交换链图像
    VkResult vkGetSwapchainImagesKHR(VkDevice       device,VkSwapchainKHR swapchain,//获取的图像数量//pSwapchainImages 为 nullptr时会被重写为交换链中的图像数量uint32_t*      pSwapchainImageCount,//指向 VkImage 类型数组的指针VkImage*       pSwapchainImages
    );
    
  • 获知交换链中实际上有多少幅图像
    • vkGetSwapchainImagesKHR()
  • 获知哪些格式可用于和表面相关联的交换链
    VKAPI_ATTR VkResult VKAPI_CALL vkGetPhysicalDeviceSurfaceFormatsKHR(VkPhysicalDevice    physicalDevice,VkSurfaceKHR        surface,uint32_t*           pSurfaceFormatCount,VkSurfaceFormatKHR* pSurfaceFormats
    );
    
  • VkSurfaceFormatKHR
    typedef struct VkSurfaceFormatKHR {VkFormat        format;//VK_COLORSPACE_SRGB_NONLINEAR_KHR。VkColorSpaceKHR colorSpace;
    } VkSurfaceFormatKHR;
    
  • 调用 vkGetSwapchainImagesKHR() 获取的图像并不是马上就能用的
    • 在写入之前,需要调用 vkAcquireNextImageKHR()获取下一幅可用的图像

      VkResult vkAcquireNextImageKHR(VkDevice       device,VkSwapchainKHR swapchain,//等待时间//如果超时, 将会返回 VK_NOT_READY//为 0,可以实现非阻塞行为uint64_t       timeout,//传入信号量句柄VkSemaphore    semaphore,//传入栅栏VkFence        fence,//下一个应用程序渲染的图像的索引uint32_t*      pImageIndex
      );
      

5.4 全屏表面

  • 直接渲染到显示器

    • VK_KHR_displayVK_KHR_display_swapchain 扩展提供
  • 获知物理设备连接了几个显示器
    VkResult vkGetPhysicalDeviceDisplayPropertiesKHR(VkPhysicalDevice        physicalDevice,uint32_t*               pPropertyCount,VkDisplayPropertiesKHR* pProperties
    );
    
  • VkDisplayPropertiesKHR
    typedef struct VkDisplayPropertiesKHR
    {VkDisplayKHR               display;//可读的字符串,描述了显示器const char*                displayName;//显示器的尺寸,以毫米为单位VkExtent2D                 physicalDimensions;//显示器的分辨率,以像素为单位VkExtent2D                 physicalResolution;//显示时支持翻转或者旋转VkSurfaceTransformFlagsKHR supportedTransforms;//如果显示器支持多个平面,那么当这些平面相互之间的顺序可重排时//设置为 VK_TRUE//如果这些平面只能以固定的顺序显示,设置为 VK_FALSEVkBool32                   planeReorderPossible;//接受部分更新或者低频率的更新VkBool32                   persistentContent;
    } VkDisplayPropertiesKHR;
    
  • 查询设备支持的平面个数和类型
    VkResult vkGetPhysicalDeviceDisplayPlanePropertiesKHR(VkPhysicalDevice             physicalDevice,uint32_t*                    pPropertyCount,VkDisplayPlanePropertiesKHR* pProperties
    );
    
  • VkDisplayPlanePropertiesKHR
    typedef struct VkDisplayPlanePropertiesKHR
    {//显示器VkDisplayKHR currentDisplay;//平面之间的叠加顺序uint32_t     currentStackIndex;
    } VkDisplayPlanePropertiesKHR;
    
  • 来获知哪些显示器设备对一个显示平面是可见的
    • 一些设备的显示器平面也许会跨越多个物理显示器
    VkResult vkGetDisplayPlaneSupportedDisplaysKHR(VkPhysicalDevice physicalDevice,//显示器平面uint32_t         planeIndex,//显示器数量uint32_t*        pDisplayCount,VkDisplayKHR*    pDisplays
    );
    
  • 获知显示平面功能
    VkResult vkGetDisplayPlaneCapabilitiesKHR(VkPhysicalDevice               physicalDevice,//显示模式VkDisplayModeKHR               mode,uint32_t                       planeIndex,VkDisplayPlaneCapabilitiesKHR* pCapabilities
    );
    
  • VkDisplayPlaneCapabilitiesKHR
    typedef struct VkDisplayPlaneCapabilitiesKHR
    {//显示平面支持的组合模式VkDisplayPlaneAlphaFlagsKHR supportedAlpha;//可展示区域的最小偏移量VkOffset2D                  minSrcPosition;//可展示区域的最大偏移量VkOffset2D                  maxSrcPosition;//最小尺寸VkExtent2D                  minSrcExtent;//最大尺寸VkExtent2D                  maxSrcExtent;//在其上放置对应的物理显示器上的平面最小偏移量VkOffset2D                  minDstPosition;//在其上放置对应的物理显示器上的平面最大偏移量VkOffset2D                  maxDstPosition;//该显示器上以像素为单位的物理尺寸VkExtent2D                  minDstExtent;//该显示器上以像素为单位的物理尺寸VkExtent2D                  maxDstExtent;
    } VkDisplayPlaneCapabilitiesKHR;
    
  • VkDisplayPlaneAlphaFlagsKHR
    • VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHR

      • 这个平面不支持混合,该平面上展示的所有表面都是完全不透明的
    • VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR
      • 这个平面支持一个全局的 alpha 值
      • 通过用于创建表面的结构体 VkDisplaySurfaceCreateInfoKHR 的成员 globalAlpha 来传递
    • VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR
      • 平面支持逐像素半透明, 从展示到表面的图像的 alpha 通道里取值
  • 获取每一个显示器预定义的显示模式
    VkResult vkGetDisplayModePropertiesKHR(VkPhysicalDevice            physicalDevice,VkDisplayKHR                display,uint32_t*                   pPropertyCount,VkDisplayModePropertiesKHR* pProperties
    );
    
  • VkDisplayModePropertiesKHR
    typedef struct VkDisplayModePropertiesKHR {//该显示模式VkDisplayModeKHR           displayMode;VkDisplayModeParametersKHR parameters;
    } VkDisplayModePropertiesKHR;
    
  • VkDisplayModeParametersKHR
    typedef struct VkDisplayModeParametersKHR
    {//显示范围(以像素为单位)VkExtent2D visibleRegion;//刷新频率uint32_t   refreshRate;
    } VkDisplayModeParametersKHR;
    
  • 如果没有合适的预定义的显示模式,创建新的模式
    VkResult vkCreateDisplayModeKHR(VkPhysicalDevice                  physicalDevice,VkDisplayKHR                      display,const VkDisplayModeCreateInfoKHR* pCreateInfo,const VkAllocationCallbacks*      pAllocator,VkDisplayModeKHR*                 pMode
    );
    
  • VkDisplayModeCreateInfoKHR
    typedef struct VkDisplayModeCreateInfoKHR
    {//VK_STRUCTURE_TYPE_DISPLAY_MODE_CREATE_INFO_KHRVkStructureType             sType;//nullptrconst void*                 pNext;//为 0VkDisplayModeCreateFlagsKHR flags;VkDisplayModeParametersKHR  parameters;
    } VkDisplayModeCreateInfoKHR;
    
  • 创建一个 VkSurfaceKHR 对象
    VkResult vkCreateDisplayPlaneSurfaceKHR(VkInstance                           instance,const VkDisplaySurfaceCreateInfoKHR* pCreateInfo,const VkAllocationCallbacks*         pAllocator,VkSurfaceKHR*                        pSurface
    );
    
  • VkDisplaySurfaceCreateInfoKHR
    typedef struct VkDisplaySurfaceCreateInfoKHR
    {//VK_STRUCTURE_TYPE_DISPLAY_SURFACE_CREATE_INFO_KHR VkStructureType                sType;//nullptrconst void*                    pNext;//为 0VkDisplaySurfaceCreateFlagsKHR flags;//显示模式//可以是预定义的模式//或通过调用 vkCreateDisplayModeKHR() 产生的用户自定义的显示模式VkDisplayModeKHR               displayMode;//该平面uint32_t                       planeIndex;//相对次序uint32_t                       planeStackIndex;//图像翻转或者旋转VkSurfaceTransformFlagBitsKHR  transform;float                          globalAlpha;//VK_DISPLAY_PLANE_ALPHA_GLOBAL_BIT_KHR//VK_DISPLAY_PLANE_ALPHA_PER_PIXEL_BIT_KHR//VK_DISPLAY_PLANE_ALPHA_OPAQUE_BIT_KHRVkDisplayPlaneAlphaFlagBitsKHR alphaMode;//可展示表面的尺寸//对于全屏渲染,这个应该和 displayMode 选择的显示模式的范围一样VkExtent2D                     imageExtent;
    } VkDisplaySurfaceCreateInfoKHR;
    
  • 在展示时可应用于一个表面的变换依赖于设备和表面能力
    • 调用 vkGetPhysicalDeviceSurfaceCapabilitiesKHR() 可以获取该能力

5.5 执行展示

  • 一个队列是否支持展示

    VkResult vkGetPhysicalDeviceSurfaceSupportKHR(VkPhysicalDevice physicalDevice,uint32_t         queueFamilyIndex,VkSurfaceKHR     surface,VkBool32*        pSupported
    );
    
  • 在图像能够展示之前须有正确的布局
    • 使用图像内存屏障,图像可从一个布局转变到另一个布局
    • 用图像内存屏障把图像从 VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL 转变到 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 布局
      • Page 133
  • 图像处于 VK_IMAGE_LAYOUT_PRESENT_SRC_KHR 布局即可展示
    VkResult vkQueuePresentKHR(VkQueue                 queue,const VkPresentInfoKHR* pPresentInfo
    );
    
  • VkPresentInfoKHR
    typedef struct VkPresentInfoKHR
    {//VK_STRUCTURE_TYPE_PRESENT_INFO_KHR,VkStructureType       sType;//nullptrconst void*           pNext;//等待一个或者多个信号量//来使渲染图像和展示操作保持同步uint32_t              waitSemaphoreCount;const VkSemaphore*    pWaitSemaphores;uint32_t              swapchainCount;const VkSwapchainKHR* pSwapchains;const uint32_t*       pImageIndices;VkResult*             pResults;
    } VkPresentInfoKHR;
    
  • 单次调用 vkQueuePresentKHR() 可以同时向多个交换链中展示多幅图像

5.6 清除

  • 不管在应用程序中用什么方法来展示,都需要正确地清理

    void vkDestroySwapchainKHR(VkDevice                     device,VkSwapchainKHR               swapchain,const VkAllocationCallbacks* pAllocator
    );
    

闭关之 Vulkan 应用开发指南笔记(二):队列、命令、移动数据和展示相关推荐

  1. 开发指南专题二:JEECG微云快速开发平台JEECG框架初探

    开发指南专题二:JEECG微云快速开发平台JEECG框架初探 2.JEECG框架初探 2.1演示系统 打开浏览器输入JEECG演示环境地址:http://demo.jeecg.org:8090/可以看 ...

  2. 《MFC游戏开发》笔记二 建立工程、调整窗口

    本系列文章由七十一雾央编写,转载请注明出处. http://blog.csdn.net/u011371356/article/details/9300383 作者:七十一雾央 新浪微博:http:// ...

  3. 7、T5L DGUS ll 应用开发指南摘录(二)

    @[TOC](T5L DGUS ll 应用开发指南摘录(二)) 1.UART2 串口通讯协议   DGUS 屏采用异步.全双工串口(UART),串口模式为 8n1,即每个数据传送采用十个位,包括 1 ...

  4. Polyworks脚本开发学习笔记(二十)-补充几个常见操作指令的使用

    Polyworks脚本开发学习笔记(二十)-补充几个常见操作指令的使用 大概要写到结尾了,最后几篇就将手册的各常用命令再看一遍,组合一下,并列举出常见的一些有用的操作. DATA_COLOR_MAP数 ...

  5. Polyworks脚本开发学习笔记(二)-TREEVIEW基本操作

    Polyworks脚本开发学习笔记(二)-TREEVIEW对象基本操作 TREEVIEW对象选择 选择/不选对象 TREEVIEW OBJECT SELECT ALL 全部对象选择,包括参考/数据/对 ...

  6. Polyworks脚本开发学习笔记(二二)-调取视角用脚本自动生成报告

    Polyworks脚本开发学习笔记(二二)-调取视角用脚本自动生成报告 Polyworks中,3D场景的视图可用标准视角及等轴侧视角.项目视角等方式调用,也可以用txt格式保存下来调用,如果以脚本的形 ...

  7. 开源项目SMSS开发指南(二)——基于libevent的线程池

    libevent是一套轻量级的网络库,基于事件驱动开发.能够实现多线程的多路复用和注册事件响应.本文将介绍libevent的基本功能以及如何利用libevent开发一个线程池. 一. 使用指南 监听服 ...

  8. [微信开发] 开发指南笔记

    (1)测试号申请 https://mp.weixin.qq.com/debug/cgi-bin/sandbox?t=sandbox/login (2)接入指南 1.每个用户对每个公众号有一个唯一的Op ...

  9. Qt与FFmpeg联合开发指南(二)——解码(2):封装和界面设计

    与解码相关的主要代码在上一篇博客中已经做了介绍,本篇我们会先讨论一下如何控制解码速度再提供一个我个人的封装思路.最后回归到界面设计环节重点看一下如何保证播放器界面在缩放和拖动的过程中保证视频画面的宽高 ...

  10. 龙芯1C300B主板V3.4嵌入式开发学习笔记二

    1 PMON更新 接着笔记一:https://blog.csdn.net/weixin_38709708/article/details/81271493 笔者手中的开发板一开始就烧写了PMON,当是 ...

最新文章

  1. centos 安装搜狗输入法_Ubuntu 20.04 安装搜狗输入法
  2. 031_yml配置文件
  3. 强制修改mysql 中root的密码
  4. idea启动java Maven项目,出现“ java: 程序包xxxx不存在“
  5. java后端工程师平时开发或多或少会用到eclipse,那么它有哪些快捷键呢
  6. python开发图片_python实现简单的图片隐写术
  7. oracle迁移性能对比,SQL Server 2015与Oracle性能对比.doc
  8. 简单的HTML5灰度图查看器 simple PACS DICOM HTML5 viewer
  9. 详解 Linux 中 apt 命令的使用
  10. 5.22青海云南同震
  11. css html实现粒子特效,CSS实现粒子动态按钮效果
  12. 没有基础怎么自学渗透测试工程师?
  13. qpython 3h下载_【分享】QPython 3H3.0.0 一个伟大的脚本编辑器!
  14. 虚拟机上WindowsXP系统下载QQ和打开https网站证书问题打不开解决
  15. 服务器硬件配置应如何选择?
  16. 转torchscript报错:Expected a value of type ‘Tensor (inferred)‘ for argument ‘scale‘ but instead found t
  17. 游戏打的菜?当然是延迟的锅啦~
  18. 永信至诚成功当选CNCERT网络安全应急服务支撑单位
  19. 金融科技大数据产品推荐:金融魔方 ---专业的金融SaaS服务平台
  20. *****MBA数学备考良言一(chenjian)*****

热门文章

  1. New Year Garland
  2. CityEngine 免费试用申请、下载与配置(2018)
  3. vs2015软件系统开源_2015年最佳开源游戏
  4. Could not set property ‘XXX‘ of ‘class com.entity.XXX‘
  5. 用PPO玩2048游戏--可以达到合成2048的目的
  6. 六.期货期权及其他金融衍生品(投资分析)
  7. 教师资格考试科目二 word Exele ppt 应用
  8. 目标检测论文解读复现之十九:基于YOLOv5网络模型的人员口罩佩戴实时检测
  9. python爬虫-urllib-handler和代理
  10. struct files_struct和struct fdtable