第8章 绘制
8.1 准备绘制
- 准备绘制
void vkCmdBeginRenderPass (
VkCommandBuffer commandBuffer,
const VkRenderPassBeginInfo* pRenderPassBegin,
VkSubpassContents contents
);
-
VkRenderPassBeginInfo
typedef struct VkRenderPassBeginInfo
{
//VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO
VkStructureType sType;
//nullptr
const void* pNext;
VkRenderPass renderPass;
//被渲染的帧缓冲区
VkFramebuffer framebuffer;
//可以选择只渲染到图像附件的一部分区域
VkRect2D renderArea;
uint32_t clearValueCount;
//转变颜色
const VkClearValue* pClearValues;
} VkRenderPassBeginInfo;
-
VkClearValue
typedef union VkClearValue
{
VkClearColorValue color;
VkClearDepthStencilValue depthStencil;
} VkClearValue;
-
VkClearColorValue
typedef union VkClearColorValue
{
float float32[4];
int32_t int32[4];
uint32_t uint32[4];
} VkClearColorValue;
-
VkClearDepthStencilValue
typedef struct VkClearDepthStencilValue
{
float depth;
uint32_t stencil;
} VkClearDepthStencilValue;
- 终止渲染通道中的渲染
void vkCmdEndRenderPass (VkCommandBuffer commandBuffer);
8.2 顶点数据
- 把缓冲区当作顶点数据来用
//要更新顶点缓冲区的非连续绑定范围,需要多次调用
void vkCmdBindVertexBuffers (
VkCommandBuffer commandBuffer,
//更新的第一个绑定的索引
uint32_t firstBinding,
//一个给定的管线可能会引用很多个顶点缓冲
uint32_t bindingCount,
//缓冲区对象
const VkBuffer* pBuffers,
//偏移量的数组
const VkDeviceSize* pOffsets
);
8.3 索引绘制
- 索引绘制
void vkCmdDrawIndexed (
VkCommandBuffer commandBuffer,
uint32_t indexCount,
uint32_t instanceCount,
uint32_t firstIndex,
int32_t vertexOffset,
uint32_t firstInstance
);
- 索引缓冲区绑定到命令缓冲区
void vkCmdBindIndexBuffer (
VkCommandBuffer commandBuffer,
VkBuffer buffer,
VkDeviceSize offset,
//索引的类型
//VK_INDEX_TYPE_UINT16
//VK_INDEX_TYPE_UINT32
VkIndexType indexType
);
- 当调用
vkCmdDrawIndexed()
时,Vulkan 将从当前绑定的索引缓冲区中的如下索引位置开始读取数据
offset + firstIndex * sizeof(index)
- 支持索引数值的最大范围
-
VkPhysicalDeviceLimits
- 至少为
2
24
−
1
2^{24}-1
224−1
8.3.1 只用索引的绘制
- 很多几何对象的局部顶点位置可以用 16 位、10 位,甚至 8 位数据来表示,并有足够的精度。3 个 10 位数据可以打包进一个 32 位的字中
- VK_FORMAT_A2R10G10B10_SNORM_PACK32
- 思想
- 把顶点写在索引缓冲区
- VS 时解包
- Page 222
8.3.2 重置索引
- 索引化绘制的另外一个特征允许你使用图元重启索引
- 这个特殊的索引值可以存储进索引缓冲区里,用于通知一个新图元的开始
- 图元重启特性开启
- 索引类型的最大可能值的特殊值用作特殊重启标志
- VK_INDEX_TYPE_UINT16 是 0xFFFF
- VK_INDEX_TYPE_UINT32 是 0xFFFFFFFF
- 如果没有启用图元重启,这个特殊的重置标志会被当作一个普通的顶点索引
- 在把大型条带或者扇形绘制分解成许多小块时,重置索引很有用
- 在有些架构上使用重置索引会影响性能,使用列表拓扑并展开索引缓冲区(不要试图用条带)可能会更好
8.4 实例化
-
vkCmdDraw()
和 vkCmdDrawIndexed()
的 firstInstance
和 instanceCount
- 在一个着色器输入上使用内置的 InstanceIndex 修饰符来以当前实例的索引作为着色器的输入
- 这个输入变量随后可以用来从 uniform 类型缓冲区中取出参
- 通过编程计算每个实例的变化量
- 使用实例化的顶点属性, 让 Vulkan 为顶点着色器提供每个实例特有的数据
- 示例
- Page 224
8.5 间接绘制
- 不知道每一次绘制的准确参数情况
- 几何物体的所有结构是已知的,但是顶点的个数和在顶点缓冲区的位置是未知的
- 如某个对象永远以相同的方式渲染,但是细节层次可能随时间变化
- 绘制命令由设备生成,而非主机
- 绘制命令从设备可访问的内存获取参数
- 它执行非索引化绘制,使用的参数包含在一个缓冲区
- 执行非索引化的间接绘制
void vkCmdDrawIndirect (
VkCommandBuffer commandBuffer,
VkBuffer buffer,
VkDeviceSize offset,
uint32_t drawCount,
uint32_t stride
);
-
VkDrawIndirectCommand
typedef struct VkDrawIndirectCommand
{
uint32_t vertexCount;
uint32_t instanceCount;
uint32_t firstVertex;
uint32_t firstInstance;
} VkDrawIndirectCommand;
- 执行索引化的间接绘制
void vkCmdDrawIndexedIndirect (
VkCommandBuffer commandBuffer,
VkBuffer buffer,
VkDeviceSize offset,
uint32_t drawCount,
uint32_t stride
);
-
VkDrawIndexedIndirectCommand
typedef struct VkDrawIndexedIndirectCommand
{
uint32_t indexCount;
uint32_t instanceCount;
uint32_t firstIndex;
int32_t vertexOffset;
uint32_t firstInstance;
} VkDrawIndexedIndirectCommand;
- 关于应用详看 Page 225
第9章 几何体处理
9.1 表面细分
9.1.1 表面细分配置
表面细分模式
- 细分的两种主要模式
- 每一个图元片都会有一个内部的和一个外部的细分等级集合
- 外部的细分等级集合控制沿着图元片外部边缘的细分等级
- 内部细分模式控制图元片内的细分等级
- 四边形和三角形细分模式会生成三角形,等值线模式则会生成直线
- 表面细分引擎生成单个的点
- 图元片会按照正常方式细分,但是细分点并不会组合,而是被直接送入管线的余下阶段
-
polygonMode
设置为 VK_POLYGON_MODE_POINT
控制细分
- 是控制相邻图元片的各条边如何排列
- SpacingEqual
- 每条边的细分等级会限制在区间
[1,maxLevel]
- 向上取整为下一个较大的整数 n
- 然后在质心空间下把边细分为等长的 n 段
- SpacingFractionalEven
- 每条边的细分等级会限制在区间
[2,maxLevel]
- 向上取整为最近的偶数 n
- 然后把边细分为等长的 n−2 段
- SpacingFractionalOdd
- 每条边的细分等级会限制在区间
[1,maxLevel − 1]
- 向上取整为最近的奇数 n
- 然后把边细分为等长的 n−2 段
- 表面细分环绕顺序
layout (cw) in;
layout (ccw) in;
9.1.2 表面细分相关变量
- 表面细分控制着色器传递到表面细分评估着色器的控制点的数量可以通过在表面细分控制着色器中使用输出布局限定符来设置
layout (vertices = 9) out;
- 内部的和外部的细分等级分别用内置的
gl_TessLevelInner
与 gl_TessLevelOuter
两个变量表示
- 最大细分等级查询
-
VkPhysicalDeviceLimits
maxTessellationGenerationLevel
- Vulkan 能够保证支持的最小值是 64
-
gl_InvocationID
- 每一个输出顶点能够生成的组件总数
-
VkPhysicalDeviceLimits
maxTessellationControlTotalOutputComponents
- 至少能够支持 2048 个
- 表面细分评估着色器接收的组件总数
-
VkPhysicalDeviceLimits
maxTessellationEvaluationInputComponents
- 至少是 64
图元片的相关变量
- 图元片输出主要用于以下两个目的
- 存储每个图元片数据,把数据从表面细分控制着色器传递到表面细分评估着色器
- 在同一个图元片内的不同的表面细分控制着色器调用中共享数据
- 声明一个图元片的输出变量
- 同步一个图元片内的调用
- 表面细分控制着色器中调用内置函数
barrier()
即可生成这些指令
表面细分评估着色器
- 表面细分控制着色器执行结束并把细分系数传递给固定功能细分单元之后后,图元片内部会产
生新的顶点并且在图元片空间中给顶点分配质心坐标
- 每一个新生成的顶点都会调用一次表面细分评估着色器
- 能够生成的数据总量
-
VkPhysicalDeviceLimits
maxTessellationEvaluationOutputComponents
9.1.3 表面细分示例:置换贴图
9.2 几何着色器
- 是否支持几何着色器
- 几何着色器必须包含以下信息
- 单次几何着色器调用能够生成的最大顶点数量
-
VkPhysicalDeviceLimits
maxGeometryOutputVertices
- 至少为 256
- 几何着色器输出的每一个顶点能够生成的最大组件数量
maxGeometryOutputComponents
- 至少为 64
- 几何着色器的输出来自如下两个位置
- gl_PerVertex 输入块声明中包含的内置输入声明
- 用户自定义的与前面一个着色器阶段中的输出声明相对应的输入
- 输入几何着色器的所有组件总数
-
VkPhysicalDeviceLimits
maxGeometryInputComponents
- 至少 64 个
9.2.1 图元裁剪
9.2.2 几何着色器实例化
- 一种非常高效的实例化机制
- GLSL 着色器中使用 invocations 输入布局限定符改变几何着色器执行次数
layout (invocations = 8) in;
- 调用编号可以通过 GLSL 内置变量
gl_InvocationID
获取
- 几何着色器的最大调用次数依赖于具体实现
- 至少为 32
-
VkPhysicalDeviceLimits
maxGeometryShaderInvocations
9.3 可编程顶点尺寸
- 点能够按照以下 3 种方式之一进行光栅化
- 仅通过顶点着色器和片段着色器渲染
- 设置图元的拓扑为 VK_PRIMITIVE_TOPOLOGY_POINT_LIST
- 启用表面细分
- 通过使用 SPIR-V 的执行模式 PointMode 修饰表面细分着色器入口点置表面细分为点模式
- 使用一个生成点的几何着色器
- 带 PointSize 修饰符的输出变量中的值必须在设备支持的尺寸范围内
- 点最终的像素尺寸会量化为一个与设备相关的尺度
- 把一个编译时常量写入 PointSize 会让很多 Vulkan 实现变得更高效
9.4 线的宽度以及光栅化
9.5 用户裁剪和剔除
- 使用 ClipDistance 修饰符修饰输出变量,能够产生裁剪距离变量
- 裁剪距离也可用作片段着色器的输入
- 通过使用裁剪距离能够实现另一种裁剪方式
- 要使用剔除距离,声明一个带 CullDistance 修饰符的输出变量即可
- 很多设备会有一个单次可用距离值的数量的综合限制
9.6 视口变换
- 至少视口的最大尺寸保证是 4096×4096 像素
- 视口可偏移的最大限度
- 视口状态也是可以动态设置的
- VK_DYNAMIC_STATE_VIEWPORT
- 动态设置视口边界
void vkCmdSetViewport (
VkCommandBuffer commandBuffer,
uint32_t firstViewport,
uint32_t viewportCount,
const VkViewport* pViewports
);
- 当前管线支持的视口数量
-
VkPipelineViewportStateCreateInfo
- 设备支持的视口总数
-
VkPhysicalDeviceLimits
- 那么这个值至少是 16
- 几何着色器可以通过对输出变量使用 ViewportIndex 修饰符来选择视口索引
- 把几何体投影到多个视口的简单方法就是,让几何着色器的执行次数与管线中的视口数量相同
第10章 片段处理
10.1 裁剪测试
- 该测试只是简单地判断片段是否位于帧缓冲区的指定矩形区域内
- 帧缓冲区的全尺寸视口和裁剪区域的区别
- 视口变换改变图元在帧缓冲区里的位置
- 视口区域会影响裁剪
- 裁剪测试总是运行在片段着色器之前
- 修改裁剪区域
void vkCmdSetScissor(
VkCommandBuffer commandBuffer,
uint32_t firstScissor,
uint32_t scissorCount,
const VkRect2D* pScissors
);
10.2 深度和模板测试
10.2.1 深度测试
- 深度测试的最大最小范围设置
void vkCmdSetDepthBounds (
VkCommandBuffer commandBuffer,
float minDepthBounds,
float maxDepthBounds
);
- 是否支持深度范围测试
- 深度偏差
typedef struct VkPipelineRasterizationStateCreateInfo
{
VkStructureType sType;
const void* pNext;
VkPipelineRasterizationStateCreateFlags flags;
VkBool32 depthClampEnable;
VkBool32 rasterizerDiscardEnable;
VkPolygonMode polygonMode;
VkCullModeFlags cullMode;
VkFrontFace frontFace;
//深度偏差
VkBool32 depthBiasEnable;
//偏差方程系数 方程:Page274
float depthBiasConstantFactor;
float depthBiasClamp;
float depthBiasSlopeFactor;
float lineWidth;
} VkPipelineRasterizationStateCreateInfo;
- 设置深度偏差方程参数
void vkCmdSetDepthBias (
VkCommandBuffer commandBuffer,
float depthBiasConstantFactor,
float depthBiasClamp,
float depthBiasSlopeFactor
);
10.2.2 模板测试
- 模板测试可以对图元正面和背面进行不同测试
typedef struct VkStencilOpState
{
//VkStencilOp 可执行操作:Page 275
//深度测试成功,模板测试失败执行
VkStencilOp failOp;
//深度测试成功,模板测试成功执行
VkStencilOp passOp;
//深度测试失败,执行,跳过模板测试
VkStencilOp depthFailOp;
//比较模板参考值和缓冲区里的值
VkCompareOp compareOp;
//比较用的资源
uint32_t compareMask;
uint32_t writeMask;
uint32_t reference;
} VkStencilOpState;
- 模板测试参数设置
void vkCmdSetStencilReference (
VkCommandBuffer commandBuffer,
//是否将新的状态应用到正面、背面或者双面
VkStencilFaceFlags faceMask,
uint32_t reference
);
void vkCmdSetStencilCompareMask (
VkCommandBuffer commandBuffer,
//是否将新的状态应用到正面、背面或者双面
VkStencilFaceFlags faceMask,
uint32_t compareMask
);
void vkCmdSetStencilWriteMask (
VkCommandBuffer commandBuffer,
//是否将新的状态应用到正面、背面或者双面
VkStencilFaceFlags faceMask,
uint32_t writeMask
);
10.2.3 早期片段测试
- 在片段着色器之后进行深度和模板测试
- 片段着色器通过着色器内置变量 FragDepth 来修改当前片段的深度值
- 片段着色器有其他副作用,比如存储到图像中
- 如果片段着色器使用 SPIR-V 的 OpKill 指令丢弃当前片,深度缓存不会更新
- 如果上述情况不存在,可以在片段着色器之前进行深度和模板测试
- 启动
- 在片段着色器的 SPIR-V 代码的函数入口点使用 EarlyFragmentTest 修饰符
- 用一个预渲染的深度图像进行深度测试
10.3 多重采样渲染
- 两种通用方式可以生成多重采样图像
- 大多数 Vulkan 实现所支持的采样次数为 1~8 或者 1~16
- 不同格式支持的采样数不同,查询
VkResult vkGetPhysicalDeviceImageFormatProperties (
VkPhysicalDevice physicalDevice,
VkFormat format,
VkImageType type,
VkImageTiling tiling,
VkImageUsageFlags usage,
VkImageCreateFlags flags,
VkImageFormatProperties* pImageFormatProperties
);
- 采样分布
10.3.1 采样率着色
- 启用
-
VkPipelineMultiSampleStateCreateInfo
-
sampleShadingEnable
-
minSampleShading
-
alphaToCoverage
10.3.2 多重采样解析
- 两种解析图像的方法
- 将非多重采样图像包含到结构体
VkSubpassDescription
- 显式地将多重采样图像解析成单重采样图像
- 调用函数
void vkCmdResolveImage (
VkCommandBuffer commandBuffer,
VkImage srcImage,
VkImageLayout srcImageLayout,
VkImage dstImage,
VkImageLayout dstImageLayout,
uint32_t regionCount,
//图像解析区域(可以部分解析)
const VkImageResolve* pRegions
);
-
VkImageResolve
typedef struct VkImageResolve {
VkImageSubresourceLayers srcSubresource;
VkOffset3D srcOffset;
VkImageSubresourceLayers dstSubresource;
VkOffset3D dstOffset;
VkExtent3D extent;
} VkImageResolve;
10.4 逻辑操作
10.5 片段着色器的输出
10.6 颜色混合
第11章 同步
- Vulkan 提供的 3 种主要的同步原语类型
- 栅栏(fence)
- 当主机需要等待设备完成某次提交中的大量工作时使用,通常需要操作系统的协助
- 事件(event)
- 表示一个细粒度的同步原语,可由主机或者设备发出
- 当设备发出信号时,可以在命令缓冲区中通知它,并且在管线中的特定点上可以由设备等待它
- 信号量(semaphore)
11.1 栅栏
- 栅栏常常对应于一个操作系统提供的本地同步原语
- 所以通常当线程等待栅栏时可能会休眠,这样就能节能
- 如
- 等待多个命令缓冲区执行完毕
- 将渲染完的帧展示给用户
- 创建栅栏
VkResult vkCreateFence (
VkDevice device,
const VkFenceCreateInfo* pCreateInfo,
//用于分配栅栏所需的主机内存
const VkAllocationCallbacks* pAllocator,
VkFence* pFence
);
-
VkFenceCreateInfo
typedef struct VkFenceCreateInfo
{
//VK_STRUCTURE_TYPE_FENCE_CREATE_INFO
VkStructureType sType;
//nullptr
const void* pNext;
//栅栏的行为
//VK_FENCE_CREATE_SIGNALED_BIT
//如果设置,初始状态是有信号的
//未设置,则无信号
VkFenceCreateFlags flags;
} VkFenceCreateInfo;
- 销毁栅栏
void vkDestroyFence (
VkDevice device,
VkFence fence,
const VkAllocationCallbacks* pAllocator
);
- 栅栏应用于任何接受栅栏参数的命令中 (队列)
VkResult vkQueueSubmit (
VkQueue queue,
uint32_t submitCount,
const VkSubmitInfo* pSubmits,
//当队列引发的所有工作都完成后,把字段 fence 指定的栅栏对象设置为有信号的状态
VkFence fence
);
- 设备可以直接向栅栏对象发送信号
- 设备通过中断或者其他硬件机制向操作系统发送信号,操作系统再改变栅栏的状态
- 判断栅栏的状态
VkResult vkGetFenceStatus (
VkDevice device,
VkFence fence
);
- VK_SUCCESS
- VK_NOT_READY
- 可能会引发错误,进而自旋
- 避免自旋应调用
vkWaitForFences
-
vkWaitForFences
VkResult vkWaitForFences (
VkDevice device,
uint32_t fenceCount,
const VkFence* pFences,
VkBool32 waitAll,
//超时设置
uint64_t timeout
);
- 可以等待任何数量的栅栏对象
- 可以等待所有栅栏有信号
- 也可以等待任意一个变为有信号返回
- 检查多个栅栏比
vkGetFenceStatus
高效
- 允许超时
- 返回值
- 要重置一个或者多个栅栏为无信号的状态
VkResult vkResetFences (
VkDevice device,
uint32_t fenceCount,
const VkFence* pFences
);
- 栅栏用途
- 防止主机修改正在被设备使用的数据,或设备可能即将使用的数据
- 3 种机制来实现主机和设备之间的同步,并确保主机在设备使用缓冲区里的数据之前不重写数据
- 调用 vkQueueWaitIdle()来保证所有提交到队列的工作已完成
- 使用一个栅栏,该栅栏和使用这些数据的提交任务相关联,并且在重写缓冲区的内容之前等待这个栅栏
- 将缓冲区细分为 4 等份每一份关联一个栅栏,在重写每一份之前,等待关联的栅栏
11.2 事件
- 事件对象代表了一个细粒度的同步原语,用于精确地界定管线里发生的操作
- 事件有两种状态
- 可以显式地向事件发送信号或者进行重置
- 设备不但可以直接地操作事件的状态,还可以在管线里的特定时间点上进行操作
- 创建事件对象
VkResult vkCreateEvent (
VkDevice device,
const VkEventCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkEvent* pEvent
);
-
VkEventCreateInfo
typedef struct VkEventCreateInfo
{
//VK_STRUCTURE_TYPE_EVENT_CREATE_INFO
VkStructureType sType;
//nullptr
const void* pNext;
//0
VkEventCreateFlags flags;
} VkEventCreateInfo;
- 释放事件对象
void vkDestroyEvent (
VkDevice device,
VkEvent event,
const VkAllocationCallbacks* pAllocator
);
- 事件的初始状态是无信号的或者重置的
- 主机上改变事件的状态
VkResult vkSetEvent (
VkDevice device,
VkEvent event
);
- 当主机设置事件对象后,事件对象的状态就立即变成了有信号的状态。如果另一个线程正在通过调用
vkCmdWaitEvents()
等待这个事件对象,该线程将立即变为非阻塞状态
- 重置事件状态
VkResult vkResetEvent (
VkDevice device,
VkEvent event
);
- 获取事件状态
VkResult vkGetEventStatus (
VkDevice device,
VkEvent event
);
- 返回值
- VK_EVENT_SET
- VK_EVENT_RESET
- 除了在循环里自旋以等待
vkGetEventStatus()
返回 VK_EVENT_SET 之外,主机没有办法等待事件对象
- 该自旋非常低效,如果确实需要这么做,需要和系统配合
- 例如,使当前线程休眠,或者在查询事件对象状态之间做些其他有用的事情
- 事件对象也可以由设备操作
void vkCmdSetEvent (
VkCommandBuffer commandBuffer,
VkEvent event,
VkPipelineStageFlags stageMask
);
- 设备操作重置事件
void vkCmdResetEvent (
VkCommandBuffer commandBuffer,
VkEvent event,
VkPipelineStageFlags stageMask
);
- 设备不能直接获取事件对象的状态,但是能等待一个或者多个事件
void vkCmdWaitEvents (
//指定哪个命令缓冲区会中止运行,以等待事件
VkCommandBuffer commandBuffer,
//需要等待的事件的数量
uint32_t eventCount,
const VkEvent* pEvents,
//在哪些管线阶段会通知事件
VkPipelineStageFlags srcStageMask,
//在哪些阶段等待事件会变为有信号状态
//在 dstStageMask 指定的阶段,等待肯定会发生
VkPipelineStageFlags dstStageMask,
//变为有信号的状态执行内存操作
uint32_t memoryBarrierCount,
const VkMemoryBarrier* pMemoryBarriers,
uint32_t bufferMemoryBarrierCount,
const VkBufferMemoryBarrier* pBufferMemoryBarriers,
uint32_t imageMemoryBarrierCount,
const VkImageMemoryBarrier* pImageMemoryBarriers
);
11.3 信号量
- 信号量代表了可以被硬件以原子方式设置和重置的标记
- 信号量不能被设备显式地通知或者等待
- 创建信号量对象
VkResult vkCreateSemaphore (
VkDevice device,
const VkSemaphoreCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkSemaphore* pSemaphore
);
-
VkSemaphoreCreateInfo
typedef struct VkSemaphoreCreateInfo
{
//VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO
VkStructureType sType;
//nullptr
const void* pNext;
//0
VkSemaphoreCreateFlags flags;
} VkSemaphoreCreateInfo;
- 销毁信号量
void vkDestroySemaphore (
VkDevice device,
VkSemaphore semaphore,
const VkAllocationCallbacks* pAllocator
);
- 信号量对象不允许显式地设置、重置和等待
- 用来同步不同队列对资源的访问,从而形成向设备提交工作的完整部分
-
vkQueueSubmit
VkResult vkQueueSubmit
(
VkQueue queue,
uint32_t submitCount,
const VkSubmitInfo* pSubmits,
VkFence fence
);
-
VkSubmitInfo
typedef struct VkSubmitInfo
{
VkStructureType sType;
const void* pNext;
uint32_t waitSemaphoreCount;
//队列等待的信号量
const VkSemaphore* pWaitSemaphores;
const VkPipelineStageFlags* pWaitDstStageMask;
uint32_t commandBufferCount;
const VkCommandBuffer* pCommandBuffers;
uint32_t signalSemaphoreCount;
//完成后发送型号
const VkSemaphore* pSignalSemaphores;
} VkSubmitInfo;
- 信号量是外部同步的,必须保证信号量不能同时用于两个不同线程中的不同队列中
- 可以将一定量的命令缓冲区提交给只负责计算的队列,这个队列随后会在完成时通知信号量
- 该信号量出现在等待列表中,等待第二次提交到图形队列里
- 信号量的相同同步机制在其他方面也有运用
第12章 回读数据
12.1 查询
- Vulkan 读取统计数据的主要机制是依靠查询对象(query object)
- 查询对象通过池进行创建和管理
- 每个对象实际上是池里的一个槽(slot)
- 而不是一个单独管理的离散对象
- 查询池创建
VkResult vkCreateQueryPool (
VkDevice device,
const VkQueryPoolCreateInfo* pCreateInfo,
const VkAllocationCallbacks* pAllocator,
VkQueryPool* pQueryPool
);
-
VkQueryPoolCreateInfo
typedef struct VkQueryPoolCreateInfo
{
//VK_STRUCTURE_TYPE_QUERY_POOL_CREATE_INFO
VkStructureType sType;
//nullptr
const void* pNext;
//0
VkQueryPoolCreateFlags flags;
//查询的类型
VkQueryType queryType;
uint32_t queryCount;
//如何获取统计信息
VkQueryPipelineStatisticFlags pipelineStatistics;
} VkQueryPoolCreateInfo;
-
VkQueryType
- VK_QUERY_TYPE_OCCLUSION
- VK_QUERY_TYPE_PIPELINE_STATISTICS
- 管线统计查询
- 统计在设备的运行过程中产生的各种统计信息
- VK_QUERY_TYPE_TIMESTAMP
- 时间戳查询
- 测量执行一个命令缓冲区里的命令所花费的时间
- 销毁查询池
void vkDestroyQueryPool (
VkDevice device,
VkQueryPool queryPool,
const VkAllocationCallbacks* pAllocator
);
- 重置池
void vkCmdResetQueryPool (
VkCommandBuffer commandBuffer,
VkQueryPool queryPool,
uint32_t firstQuery,
uint32_t queryCount
);
12.1.1 执行查询
- 查询通过在命令缓冲区中包含特定的开始-停止指令
-
vkCmdBeginQuery()
void vkCmdBeginQuery (
VkCommandBuffer commandBuffer,
VkQueryPool queryPool,
uint32_t query,
//控制查询执行的额外标志位
//VK_QUERY_CONTROL_PRECISE_BIT 获取精确结果,会有额外开销
//未设置返回近似结果
VkQueryControlFlags flags
);
-
vkCmdEndQuery()
void vkCmdEndQuery (
VkCommandBuffer commandBuffer,
VkQueryPool queryPool,
uint32_t query
);
- 必须成对出现
- 从池中的一个或者多个查询中获取结果
VkResult vkGetQueryPoolResults (
VkDevice device,
VkQueryPool queryPool,
uint32_t firstQuery,
uint32_t queryCount,
size_t dataSize,
//指向的主机内存
void* pData,
//间隔写入
VkDeviceSize stride,
//查询的类型将决定什么会写入内存中
VkQueryResultFlags flags
);
-
VkQueryResultFlags
- VK_QUERY_RESULT_64_BIT
- 结果将会以 64 位的数量返回
- 否则,以 32 位的数量返回
- VK_QUERY_RESULT_WAIT_BIT
- 将会在查询可用前一直等待
- 否则,将会返回状态编码,来指出要查询的命令是否执行完毕
- VK_QUERY_RESULT_WITH_AVAILABILITY_BIT
- 当查询没有就绪时,在调用时 Vulkan 会写入一个 0
- 同时任何就绪的查询都会返回一个非零的结果。
- VK_QUERY_RESULT_PARTIAL_BIT
- 在查询包括的指令执行完成前就将当前的值写入结果缓冲区中
- 将查询结果写入缓冲区对象
void vkCmdCopyQueryPoolResults (
VkCommandBuffer commandBuffer,
VkQueryPool queryPool,
uint32_t firstQuery,
uint32_t queryCount,
VkBuffer dstBuffer,
VkDeviceSize dstOffset,
VkDeviceSize stride,
VkQueryResultFlags flags
);
- 必须以同步方式访问写入缓冲区的对象结果,该访问使用屏障来完成
遮挡查询
- 计数是通过深度和模板测试的片段数
- 应用场景
- 将场景的一部分渲染到深度缓冲区中
- Page 308
管线统计信息查询
-
pipelineStatistics
- 前缀
- VK_QUERY_PIPELINE_STATISTIC_
- …INPUT_ASSEMBLY_VERTICES_BIT
- …INPUT_ASSEMBLY_PRIMITIVES_BIT
- …VERTEX_SHADER_INVOCATIONS_BIT
- 统计图形管线中顶点着色器的总调用次数
- 可能与组装的顶点数不同
- …GEOMETRY_SHADER_INVOCATIONS_BIT
- …GEOMETRY_SHADER_PRIMITIVES_BIT
- …CLIPPING_INVOCATIONS_BIT
- 统计进入图形管线裁剪阶段的图元数
- 如果一个图元可以完全丢弃而不进行裁剪,这个计数器不会递增
- …CLIPPING_PRIMITIVES_BIT
- …FRAGMENT_SHADER_INVOCATIONS_BIT
- …TESSELLATION_CONTROL_SHADER_PATCHES_BIT
- …TESSELLATION_EVALUATION_SHADER_INVOCATIONS_BIT:
- …COMPUTE_SHADER_INVOCATIONS_BIT
12.1.2 计时查询
- 测量在命令缓冲区中执行命令所花费的时间
- 将当前设备时间写入查询池
void vkCmdWriteTimestamp (
VkCommandBuffer commandBuffer,
//指定的管线阶段将当前设备时间写入指定的查询
VkPipelineStageFlagBits pipelineStage,
VkQueryPool queryPool,
uint32_t query
);
12.2 通过主机读取数据
第十三章 多通道渲染
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)