Vulkan学习总结

1 vulkan编程模型

在这里插入图片描述

1.1 硬件初始化

在这里插入图片描述
需要调用加载器激活Vulkan驱动。
当加载器定位到驱动位置并成功链接到API之后,应用程序就可以开始执行下面的操作
了:
· 创建一个Vulkan实例。
· 查询物理设备上所有的可用队列。
· 查询扩展功能并保存为新的函数指针,例如WSI或者有特定功能的API。
支持注入层来实现错误检查、调试或者验证的功能。

1.2 窗口展示表面

在这里插入图片描述
在这里,系统需要先后执行如下任务:
· 创建一个本地窗口(类似Windows OS中的CreateWindow方法)。
· 创建WSI表面并关联到窗口上。
· 创建交换链来显示表面。
· 从创建后的交换链中获取绘制后的图像。

1.3 资源设置

设置资源的过程意味着将数据存储到内存区域中。数据可以是任何类型的,例如,顶点属性,如位置、颜色或者图像类型/名称。
因此,在资源设置的阶段,用户需要先后执行如下任务:
1)创建一个资源对象;
2)查询应用程序内存实例,创建一个内存对象,例如缓存或者图像;
3)获取对象分配相应的内存需求;
4)分配空间并且保存数据到其中;
5)将内存绑定到我们创建的资源对象上。

1.4 流水线设置

流水线指的是根据应用程序逻辑定义的一系列事件,它们按照固定的顺序执行。事件主
要包含以下几种:设置着色器、绑定到资源,以及状态的管理
在这里插入图片描述

1.4.1 描述符集以及描述符缓冲池

描述符集合指的是资源和着色器之间的接口。它的结构非常简单,可以将着色器绑定到资源,例如图像或者缓存。它也可以将资源内存关联或者绑定到准备使用的着色器实例上。
以下给出了描述符集合相关的一些特性:
· 频繁变化:描述符集的自然特性就是可以频繁地进行修改。通常来说,它对应于材质、纹理等数据。
· 描述符缓冲池(descriptor pool):它与描述符集的特性密切相关,后者就是从描述符缓冲池中分配而来的,不需要引入全局的数据同步。
· 多线程的扩展性:支持多线程同步进行描述符集合的更新。
更新或者改变描述符集的过程是Vulkan渲染中最为关键的性能瓶颈之一。因此,描述符的设计对于性能最优化的需求而言也是最重要的一个方面。Vulkan支持场景中的多个描述符集的逻辑分割(低频率更新)、建模(中频率更新),以及渲染层(高频率更新)。这样就确保了高频率更新的描述符不会影响到低频率的描述符了。

1.4.2 基于SPIR-V的着色器

1.4.3 流水线管理

物理设备包括一系列硬件设置,用来定义准备发送的几何输入数据是如何解释和绘制的。这些设置可以被统称为流水线状态。其中包括了光栅化状态、融混状态,以及深度/模板状态,此外还包括了输入几何数据的图元拓扑类型(点/线/三角形)以及渲染所用的着色器。
流水线对象(pipeline object):流水线的创建是非常耗费资源的。它包含了着色器的重编译、资源的绑定、渲染通道(render pass)、帧缓存的管理,以及其他相关操作。流水线对象的数量可以成百上千,因此,每个不同的状态组合都可以保存到一个独立的流水线对象当中。
流水线缓冲对象(Pipeline Cache Object,PCO)流水线的创建是非常耗费资源的,因此当流水线被创建之后,它也可以进行缓冲使用。如果我们需要建立新的流水线,那么驱动将首先做一个近似匹配,然后在基础流水线之上构建新的流水线对象。流水线缓冲的实现是不透明的,它的实现细节是驱动完成的,没有公开定义。如果应用程序希望在运行过程中,能够利用这个潜在的对象复用特性,则需要从创建伊始就自行维护这个缓冲区。
流水线布局(pipeline layout):流水线布局提供了流水线中所用的描述符集,其中设置了各种不同的资源关联到着色器的不同方法。不同的流水线对象可以使用相同的流水线布局。

1.4.4 指令的记录

在这里插入图片描述

第一个vulkan伪代码程序

1 初始化

Vulkan的初始化过程包括验证层属性的初始化,以及实例对象(VkInstance)的构建。当实例创建完成之后,需要检查当前系统是否存在可用的物理设备(VkPhysicalDevice)。选择可用的物理设备,并通过实例对象创建一个对应的逻辑设备(VkDevice)。在Vulkan程序当中,逻辑设备对象会被大多数的API所使用,它可以被视为是当前物理设备的一个逻辑表示。
在这里插入图片描述
1)枚举实例层属性:Vulkan首先要和加载器进行通信并定位驱动位置。驱动中包含了多个功能扩展和层对象,对于不同的GPU供应商,这些对象可能也有所不同。vkEnumerateInstanceLayerProperties函数返回层的数量及其属性。每个层都包含了多个功能扩展,可以直接通过vkEnumerateInstanceExtensionProperties函数进行查询。

//:1,枚举实例的层属性
//获取实例层的数量
uint32_t instanceLayerCount;
//设置第二个参数为NULL即可返回层的数量
vkEnumerateInstanceLayerProperties(&instanceLayerCount,NULL);
VkLayerProperties *layerProperty = NULL;
vkEnumerateInstanceLayerProperties(&instanceLayerCount,layerProperty);
//遍历每一个可用的实例层,获取功能扩展信息
foreach layerProperty(
	VkExtensionProperties *instanceExtensions;
	res = vkEnumerateInstanceExtensionProperties(layer_name, &instanceExtensionCount,instanceExtensions);

2)创建实例:实例对象(VkInstance)的创建需要使用vkCreateInstance()这一API函数,参数设置为层的名称,以及期望开启(以便进行验证或者调试)的功能扩展。这些名称是通过VkInstanceCreateInfo结构体来设置的。

//2.侧建实例
//vulkan实例对象
VkInstance instance;
VkInstanceCreateInfo instanceInfo = {};
//设置实例中需要开启的层的名称
instanceInfo.ppEnabledLayerNames = {
	"VK LAYER_LUNARG_standard_validation",
	"VK LAYER_LUNARG_object_tracker"};
//设置实例中需要开启的功能扩展
instanceInfo.ppEnabledExtensionNames = {
	VK_KHR_SURFACE_EXTENSION_NAME,
	VK_KHR_WIN32_SURFACE_EXTENSION_NAME};
//创建实例对象
vkCreateInstance(&instanceInfo,NULL,&instance);

3)创建设备:枚举当前系统中所有的物理设备或者GPU的数量,并且调用vkEnumeratePhysicalDevices()API函数。

//3,枚举物理设备*/
VkPhysicalDevice gpu;//物理设备

uint32_t gpuCount;//物理设备的数量

vector<VkPhysicalDevice>gpuList;//物理设备列表
//获取GPU的数量
vkEnumeratePhysicalDevices (instance,&gpuCount,NULL);
//获取GPU的信息
vkEnumeratePhysicalDevices(instance,&gpuCount,gpuList);

当我们已经获取了物理设备的列表之后,可以查询以下设备信息。
·队列和队列的类型:使用vkGetPhysicalDeviceQueueFamilyPropertiesAPI函数查询物理设
备中的队列以及队列属性。我们还可以从查询得到的队列中搜索图形相关的队列,将它的队
列索引号保存到应用程序中,稍后使用。之所以要在这里搜索图形队列,是因为我们在本书
中只想进行绘制操作。
·内存信息:API函数vkGetPhysicalDeviceMemoryProperties()可以用来获取给定物理设
备中可用的内存类型。
·物理设备属性:用户可以自由选择将物理设备的属性保存起来,以便在后续编程的过
程中获取某些特定的属性。这一操作可以通过API函数vkGetPhysicalDeviceProperties()来
完成。
设备对象的创建是通过函数vkCreateDevice()完成的。它是物理设备在应用程序层面
的逻辑表示形式。应用程序可以在不同的地方直接使用设备对象

/*4,创建设备*/
//获取队列和队列类型
vkGetPhysicalDeviceQueueFamilyProperties(gpu, &queueCount, queueProperties);
//获取物理设备或者GPU中的内存属性
vkGetPhysicalDeviceMemoryProperties (gpu,&memoryProperties);
//获取物理设备或者GPU的属性
vkGetPhysicalDeviceProperties(gpu,&gpuProps);
//从物理设备创建逻辑设备对象
VkDeviceCreateInfo deviceInfo = {};
vkCreateDevice(gpuList[0], &deviceInfo, NULL, &device);

在这里插入图片描述

交换链初始化

展示层负责将已经渲染完成的内容显示到输出窗口中。因此我们需要创建一个空窗口,以便将绘制的结果图像粘贴到窗口中。创建空窗口的方法是使用API函数CreateWindowEx(Windows平台)或者xcb_create_window(Linux平台)。
我们首先需要使用实例和设备的WSI扩展函数来初始化展示层。这些API函数允许用户使用多种不同的表面属性来创建展示表面。
这些API必须动态地链接到程序中,并且保存为函数指针的形式。我们可以使用API函数vkGetInstanceProcAddr()来进行查询
使用已有实例来执行的扩展函数API如下所示。
在这里插入图片描述
相应地,使用设备作为参数来执行的扩展函数API见表
在这里插入图片描述
这些API是我们执行图像展示相关的功能需求所必不可少的。我们看一看还有什么操作
是必须完成的:
· 创建一个抽象的表面对象:创建绘制表面首先要创建VkSurfaceKHR对象。这个对象可以将本地系统平台(Windows、Linux、Wayland、Android等)的窗口/表面功能机制抽象出来。创建这个对象需要调用API函数vkCreate<Win32/Wayland/Android>SurfaceKHR()。
· 在展示层中使用图形队列:我们可以使用创建后的抽象表面对象,搜索可用于当前展示层的图形队列,对应的API函数为vkGetPhysicalDeviceSurfaceSupportKHR()。
我们可以将搜索到的队列的句柄或者索引号保存下来。然后用它来查询表面的属
性,创建对应该队列的逻辑对象(下一步的工作)。
· 获取兼容的队列:在开始记录任何一种指令缓存之前,我们都需要获取对应的队列来完成指令的收取操作。我们可以使用函数vkGetDeviceQueue()并指定可用队列的句柄或者索引号,而这个句柄或者索引号是我们在上一步中获得并保存的。
· 查询表面格式:获取给定物理设备所支持的所有的绘制表面格式,可以通过函数
vkGetPhysicalDeviceSurfaceFormatsKHR实现。

//5,展示层初始化
//创建空窗口
CreatewindowEx(...); /*windows*/
xcb_create_window(...) /*Linux*/
//查询WSI扩展函数并保存为函数指针的形式
//例知:vkCreateSwapchainKHR,vkCreateSwapchainKHR.,.,
//创建一个抽象表面对象
Vkwin32SurfaceCreateInfoKHR createInfo = {};
vkCreatewin32SurfaceKHR(instance,&createInfo,NULL,&surface);
//从所有的队列中,选择一个支持当前展示层的队列
foreach Queue in All Queues {
	vkGetPhysicalDeviceSurfaceSupportKHR(gpu,queueIndex,surface,6isPresentationSupported);
//保存队列的索引号
	if (isPresentationSupported) {
		graphicsQueueFamilyIndex = Queue.index;
		break;
	}
}
//获取兼容展示层的队列,它同时也是一个图形队列
vkGetDeviceQueue(device,graphicsQueueFamilyIndex,0,&queue);
//分配内存空间来记录检制表面的格式的总数
uint32_t formatCount;
vkGetPhysicalDeviceSurfaceFormatsKHR(gpu,surface,&formatCount,NULL);
VkSurfaceFormatKHR *surfaceFormats = allocate memory(formatCount * VkSurfaceFormatKHR);
//将表而格式保存到VkSurfaceFormatKHR对象中
vkGetPhysicalDeviceSurfaceFormatsKHR(gpu,surface,&formatCount,surfaceFormats);

在这里插入图片描述

1 指令缓存初始化——分配指令缓存

在创建一个展示表面之前,我们需要用到指令缓存。指令缓存负责记录指令并发送到一
个对应的队列中进行处理。
指令缓存的初始化过程主要包括以下步骤:
·指令池的创建:还记得吗?我们已经保存了当前展示层可用的图形队列的句柄信息。
现在可以使用索引号或者句柄来创建一个指令池了,对应的API函数是
vkCreateCommandPool(),它与其他的查询函数无异。
·分配指令缓存:当我们有了指令池之后,指令缓存的分配可以直接通过函数
vkAllocateCommandBuffers()来完成。
我们不需要每帧都从指令池当中创建新的指令缓存,即使它是需要被反复使用的。
而如果一个已有的指令缓存已经不再被使用了,我们也可以将它高效地直接复用。
如图2-5所示,指令缓存池可以用来指定一块内存区域,在不进行全局同步的前提下直
接创建新的指令缓存
在这里插入图片描述

2.资源对象——管理图像和缓存

Vulkan将资源分为两种类型:缓存和图像
在这里插入图片描述
缓存:缓存对象使用线性的数组类型来表示不同的资源。缓存对象的类型是VkBuffer,
可以通过API函数vkCreateBuffer()创建。这个函数需要输入一个VkBufferCreateInfo结构体
作为参数,它设置了对象创建过程中可能会用到的各种不同的属性参数。例如,用户可以设
置图像平铺的数量、图像的用途、大小、队列兼容性,等等。我们现在再了解一下缓存视图
是如何被建立的。
·缓存视图:缓存视图(VkBufferView)表示数据缓存自身。它可以将数据用连续的方式
保存起来,并设置一个特定的数据解析格式。创建数据视图时需要用到函数
vkCreateBufferView()。它需要一个VkBufferViewCreateInfo结构体作为输入,以设置各种缓
存相关的属性,例如所用的缓存对象(VkBuffer)格式、缓存视图的范围,等等。
·图像:我们在程序中使用VkImage来表示它。这个对象可以保存一维到三维的缓存数
组。对象的创建是通过函数vkCreateImage()完成的。与缓存对象类似,这里也需要用到一
个VkImageCreateInfo结构体来设置创建对象时可能用到的各种属性。现在我们再了解一下图
像视图是什么。
·图像视图:与缓存视图类似,图像视图对象的类型是VkImageView。我们需要使用结构
体VkImageViewCreateInfo和vkCreateImageView()API来创建一个图像视图对象。
应用程序中并不会直接访问缓存(VkBuffer)和图像(VkImage)对象,而是使用对
应的视图对象(VkBufferView以及VkImageView)来完成。

3.创建展示表面——创建交换链

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值