理解Vulkan中的各种对象

本文来源:CSDN - wcj0626

学习Vulkan API的一个重要部分是了解其中定义了哪些类型的对象,它们代表了什么,以及它们如何相互关联。为了帮助解决这个问题,本文创建了一个图表,展示了所有vulkan对象及其一些关系,尤其是从另一个对象创建对象的顺序。

每个vulkan对象都是具有前缀Vk的特定类型的值。为了清楚起见,图表中省略了这些前缀,就像函数名vk前缀一样。例如图中的Sampler表示一个名为Vksampler的vulkan对象类型。这些类型不能当成指针或者普通的数值。不能以任何方式解释这些类型的值。只需将它们视为不透明的句柄,将它们从一个函数传递到另一个函数。当然不要忘记在不需要它们时销毁它们。绿色背景的对象没有自己的类型。相反,它们在其父对象中由类型为uint32_t的数字索引表示,例如QueryPool中的查询。

带箭头的实线代表创建顺序。例如,必须指定现有的DescriptorPool才能创建DescriptorSet。带有菱形的实线表示组合,这意味着不必创建改对象,但是它已经存在于其父对象中并且可以从中获取。例如,可以从instance对象枚举PhysicalDevice对象。虚线表示其他关系,例如向CommandBuffer提交各种命令。

图表划分为三部分。每个部门都有一个主要对象,用红色显示。一个部分中的所有其他对象都是直接或间接从该主对象创建的。例如,创建采样器的函数vkcreateSampler将Vkdevice作为其第一个参数。为了清楚起见,图表未绘制与主要对象的关系。

理解Vulkan中的各种对象

以下是所有对象的简要说明:

Instance是创建的第一个对象。它表示应用程序到Vulkan运行时的连接,因此在应用程序中只应存在一个。它还存储使用vulkan所需的所有应用程序特定状态。因此,必须在创建实例时指定所有层(如验证层)和要开启的所有扩展。

PhysicalDevice表示特定的Vulkan兼容设备,例如显卡。可以从instance中枚举这些,然后查询它们的vendorID,deviceid和支持的功能,以及其他属性和限制。

PhysicalDevice可以枚举所有可用类型的队列族。图形队列是主要队列,但可能还有其他支持计算或传输的队列。

PnysicalDevice还可以枚举其中的内存堆和内存类型。内存堆代表特定的RAM池。它可以抽象主板上的系统RAM或专用显卡上视频RAM中的某个内存空间,或实现想要公开的任何其他主机或设备特定的内存。分配内存时必须指定内存类型。它对内存blob(memory blob)具有特定要求,例如对主机可见,一致性(在cpu和GPU之间)和缓存。根据设备驱动程序的不同,可能有这些的任意组合。

Device可以被认为是一个逻辑设备,或开放设备。它表示已初始化vulkan设备的主对象,该设备已准备好创建所有其他对象。这与DirectX中的设备对象概念类似。在设备创建过程中,需要指定要开启的功能,其中一些功能是基本功能,如各向异性纹理过滤。还必须说明将要使用的所有队列、他们 鹅编号和队列族。

Queue是一个对象,表示要在设备上执行的命令队列。GPU完成的所有实际工作都是通过填充CommandBuffers,并使用函数vkQueueSubmit将它们提供给队列来请求的。如果有多个队列,例如主图形队列和计算队列,则可以向每个队列提交不同的CommandBuffers。通过这种方式,可以启用异步计算,如果操作正确,可以大大提高计算速度。

CommandPool是一个简单的对象,用于分配CommandBuffers。它连接到特定的队列族。

CommandBuffer是从特定的Commandpool分配的。它表示要由欧吉设备执行的各种命令的缓冲区。可以在命令缓冲区上调用各种函数,所有这些函数都以vkCmd开头。它们用于指定当Commandbuffer提交到Queue,并最终由设备使用时应执行的任务的顺序,类型和参数。

Sampler不绑定到任何特定的图像。它只是一组状态参数,如过滤模式(最近或线性)或过滤模式(重复,钳位到边缘,钳位到边界等)。

Buffer和Image是两种占用设备内存的资源。Buffer是更简单的一种。它是任何二进制数据的容易,以字节表示它的长度。另一方面,Image表示一组像素。这是在其他图形API中称为纹理的对象,需要更多参数来指定图像的创建。它可以是1D,2D或者3D,具有各种像素格式(如R8G8B8A8_UNORM或者R32_SFLOAT),还可以包含很多离散图像,因为它可以具有多个阵列层活MIP级别(或两者)。图像是一种单独的对象类型,因为它不一定只包含可以直接访问的线性像素集。图像可以具有由图形驱动程序管理的不同实现规定的内部格式(平铺和布局)。

创建特定长度的Buffer或具有特定尺寸的Image不会自动为其分配内存。

这是一个3步过程,必须手动执行:
① 分配设备内存
② 创建buffer或Image
③ 使用函数vkBindBufferMemory或vkBindImageMemory把1和2绑定

这就是为什么必须创建一个Devicememory对象。它表示从特定内存类型(由PhysicalDevice支持)分配的具有特定长度(以字节为单位)的内存块。不应该为每个Buffer或者Image分配单独的DeviceMemory。相反,应该分配更大的内存块并将其中的一部分分配给Buffer和Image。分配是一项昂贵的操作,并且最大分配数量也有限制,所有这些都可以从PhysicalDevice中查询。

为每个图像分配和绑定Devicememory的操作的一个例外是创建交换链。这是一个概念,用于在屏幕上或者在操作系统上绘制的窗口内呈现最终图像。因此,创建它的方式取决于平套。如果使用系统API初始化了一个窗口,首先需要创建一个SurfaceKHR对象。它需要Instance对象,以及一些与系统相关的参数。例如在window上,它们是:实例句柄(HINSTANCE)和窗口句柄(HWND)。可以讲SurfaceKHR对象想象为窗口的vulkan表示。

可以从中创建SwapchainKHR。该对象需要一个设备。它表示可以在Surface上呈现的一组图像,例如使用双缓冲或三缓冲。从交换链中,可以查询它包含的图像。这些图像已经有系统分配的后备内存。

Buffer和Image不直接用于渲染。在它们之上还有另一层,称之为View。可以将它们想象成数据库中的视图--可用于以所需方式查看一组基础数据的参数集。BufferView是基于特定缓冲区创建的对象。在创建期间可以传递偏移量和范围,以讲视图限制为近缓冲区数据的子集。同样,Imageview是一组引用特定图像的参数。可以将像素解释为具有某种其他(兼容)格式, 混合任何组件,并将视图限制在MIP级别或阵列层的特定范围内。

着色器通过descriptor访问这些资源(Buffer,Image或Sampler)。描述符本身并不创建,但总是分组在描述符集中。但在创建描述符集之前,必须通过创建Descriptorsetlayout来指定其布局,其行为类似于描述符集的模板。例如,渲染通道用于绘制3D几何体的着色器可能需要:

理解Vulkan中的各种对象

需要创建DescriptorPool。它是一个用于分配描述符集的简单对象。创建描述符池时,必须指定要从中分配的描述符集和不同类型描述符的最大数量。

最后,分配一个DescriptorSet。需要DescriptorPool和DescriptorSetLayout才能做到这点。DescriptorSet表示保存实际描述符的内存,可以对其进行配置,使描述符指向特定的buffer,bufferView,Image或Sampler。使用函数vkUpdateDescriptorSets来实现。

可以使用函数vkCmdBindDescriptorSets将多个DescriptorSet绑定为CommandBuffer中的活动集,以供渲染命令使用。这个函数还需要另一个对象PipelineLayout,因为可能绑定了多个DescriptorSet,vulkan想提前知道它应该期望它们的数量和类型。PipelineLayout表示渲染管道的配置,即哪些类型的描述符集将绑定到CommandBuffer。从DescriptorSetLayouts数组创建它。

在其他图形API中,可以采用即时模式方法,只渲染列表中接下来出现的任何内容。但这在vulkan中是不可能的。相反,需要提前计划帧的渲染并将其组织成通道和子通道*(Subpass)。子通道不是单独的对象,所以不会在这里讨论它们,但是它们是vulkan渲染系统的重要组成部分。幸运的是,在准备工作负载时,必须要了解所有细节。例如,可以指定提交时要渲染的三角形数量。在Vulkan中定义RenderPass时,关键部分是该过程中使用的附件的数量和格式。

Attachment是vulkan的名称,它是一个渲染目标---一个用作渲染输出的图像。不会在此处指向特定的图像---只是描述它们的格式。例如,一个简单的渲染过程可以使用格式为R8G8B8A8_UNORM的颜色附件和格式为D16_UNORM的深度模板附件。还可以指定附件是否应在通道开始时保留、丢弃或清楚其内容。

FrameBuffer(不要与SwapchainKHR混淆)表示可用作附件(渲染目标)的实际图像的链接。通过指定RenderPass和一组Imageview创建一个Framebuffer对象。当然,它们的数量和格式必须与RenderPass的规范相匹配。Framebuffer是Image之上的另一层,基本上将这些Imageview组合在一起,以便在渲染特定RenderPass期间绑定为附件。每当开始渲染RernderPass时,调用函数vkCmdBeginRenderPass并将FrameBuffer传递给它。

Pipeline是最大的一个,因为它包含了前面列出的大部分对象。它代表了整个流水线的配置,有很多参数。其中之一是PipelineLayout---它定义了描述符和推送常量的布局。管道有两种类型--ComputePipeline和GraphicsPipeline。ComputePipeline比较简单,因为它只支持计算程序(有时称为计算着色器)。GraphicsPipeline要复杂很多,因为它包含所有参数,如顶点、片段、集合、计算和曲面细分(如果适用),以及顶点属性、原始拓扑、背面剔除和混合模式等内容。所有这些参数,在更早的图形API(DirectX9、OpenGL)中是单独设置的参数,后来随着API的发展(DirectX10和11)被分组到数量较少的状态对象中,现在必须用今天的现代API(如vulkan)烘培成一个大的、不可变的对象。对于渲染期间所需的每一组不同参数、必须创建一个新的管道。然后通过调用函数vkcmdBindPipeline将其设置为CommandBuffer中的当前活动管道。

着色器编译在Vulkan中一个多阶段的过程。首先,Vulkan不支持任何高级着色语言,如GLSL或HLSL。相反,Vulkan接受一种称为SPIR-V的中间格式,任何更高级的语言都可以发出这种格式。SPIR-V中填充数据的缓冲区用于创建ShaderModule。这个对象代表一段着色器代码,可能是部分编译的形式,但GPU还不能执行它。只有在为每个着色器阶段(顶点、细分控制、细分计算、几何体、片段和计算)创建管道时,才能指定ShaderModule以及入口点函数的名称(如“main”)。

还有一个名为PipelineCache的辅助对象,可用于加速管道创建。它是一个简单的对象,可以在创建管道期间选择性地传入它,但这确实有助于通过减少内存使用和管道的编译时间来提高性能。驱动程序可以在内部使用他来存储一些中间数据,这样创建类似的管道可能会更快。还可以将pipelineCache对象的状态保存并加载到二进制数据缓冲区,讲其保存在磁盘上,并在下次执行应用时使用。

Query是Vulkan中的另一种对象,可用于读回由GPU写入的某些数值。有不同类型的查询,例如Occlusion(告诉是否渲染了某些像素,即它们通过了所有的着色前和着色后的测试并进入帧)或Timestamp(来自某些GPU硬件计数器的时间戳值)。Query没有自己的类型,因为它始终驻留在QueryPool中,并且仅由uint32_t索引表示。可以通过指定要包含的查询的类型和数量来创建QueryPool,然后可以使用它们向CommandBuffer发出命令,例如vkCmdBeginQuery(),vkCmdEndQuery()或vkCmdWriteTimerstamp。

最后,还有用于同步的对象:Fence,Semaphore和Event。

Fence向主机发出任务执行完成的信号。它可以在主机上等待、轮询和手动取消信号。它没有自己的命令函数,但在调用vkQueueSubmit时传递。一旦提交的队列完成,相应的栅栏就会发出信号。

Semaphore是在没有配置参数的情况下创建的。它可用于控制跨多个队列的资源访问。可以作为命令缓冲区提交的一部分发出信号或等待,也可以调用vkQueueSubmit,它可以在一个队列(例如计算)上发出信号并在另一个队列(例如图形)上等待。

创建Event也不带参数。可以使用vkCmdSetEven、vkCmdResetEvent和vkCmdWaitEvents在GPU上等待活发出信号,作为提交给CommandBuffer的单独命令。也可以被设置、重置和等待(通过从一个或多个CPU线程轮询调用vkGetEventStatus)。如果同步发生在GPU上的单个点上,或者可以在渲染过程中使用子过程依赖项,vkCmdPipelineBarrier也可以用于类似目的。


本文来源CSDN博主:wcj0626
原文链接:https://blog.csdn.net/wcj0626/article/details/123345570
转载此文目的在于传递更多信息,版权归原作者所有。如不支持转载,请联系小编demi@eetrend.com删除。

最新文章