百科问答小站 logo
百科问答小站 font logo



如何正确的入门Vulkan? 第1页

  

user avatar   li-shao-yuan-42 网友的相关建议: 
      

Vulkan是所有图形API中细节最多,概念最繁琐,操纵层级和硬件最接近的API。

个人认为在学会使用Vulkan之前,不考虑编程能力(比如C++,数据结构这类知识的话),单就CG的知识需要

1、学习图形学的基本知识,比如线性代数、三维坐标MVP变换、光栅化流程等等。对如何使用计算机渲染到屏幕一个虚拟世界有基本的概念和认知。

2、了解GPU的流水线,这部分知识看书也可以,从LearnOpenGL上照着学OpenGL的渲染管线也可以。做到对GPU的Shader Stage,工作原理有一定的认知。

3、找一些具体的GPU Demo尝试去理解。因为工作原因我基本上OpenGL/Direct11/Direct12/Vulkan的官方/个人Demos看过不少。如果从学习Vulkan这个角度的话,我其实最推荐的是Apple的Metal Sampler。Metal是现代API里最简单的一个,它具备DX12和Vulkan里的一些和上一代API的区别本质,又比较适合上手,官方Sample也比较亲民。

4、如果前三部分都OK了,那第四步就可以去真正学Vulkan了。Github三个高星的Sample,从学习角度比较推荐Lunar的Sample,同时这个也是Lunar的Vulkan教程的对应代码。结合教程来看,相信你已经可以上手学习了。

对具体的学习方式来说

1、2其实是CG基础知识的学习,这里我们老板当年推荐的课程是MIT公开课计算机图形学-6.837。还国内GAMES新出的教程也是OK的,不过我没有仔细看过就不多做评论。

3其实是对一个具体的CG Application的熟悉,目的是学习CG的理论知识和CG Programming的结合点,直接过渡到4,或者你工作本身就有这样的经验可以接触也是完全可以的,具体要看个人的掌握。

4来说,就完全是一个不断学习不断进步的过程了,Lunar的Vulkan教程有些Code其实是有Bug的,不过通过它学习Vulkan的概念,还是比直接啃规范和遍地搜索各家公司的Presentation PPT要好一些。

从我个人的学习经验来说,不真正应用起来的东西终究是学不透彻的。我目前也仍然还处在踩坑Vulkan的过程中,正在和Vulkan的Samplers以及我们自己的图形引擎框架做着艰苦的斗争。


user avatar   chen-wicast 网友的相关建议: 
      

个人最近一直在捣腾Vulkan相关的内容,来说说我的路径和心得

看题主说有基础知识那就假设你学过OpenGL,我自己OpenGL的水平其实就只到扫过learnopengl,godot里玩过简单的shader,learnopengl 里的样例代码能看懂这样。只要理解里面的思路方法就ok,有这些就足够学vk了。至于怎么学图形学原理,玩shader其实算是另一个垂直领域的知识,你用不用vk其实没太大关联了,我们这里就只关注vulkan本身是如何操作 gpu 的。


一个月的话相信你应该走完这个教程了,对vk有个初步的认知了,但你可能像我当初一样看着这堆代码还没法建立一个体系的认知。反正初学者走完这篇完全不能算入门,vk有太多细节需要学习


vulkan 可以说接口繁杂,对象众多。

要理解 vulkan 我觉得最重要的是,得先理清vulkan的那些对象的作用以及各自的关联性。本身这些对象距离实际使用的上层概念有一定距离,而各自关系又是比较复杂,只有吃透这一部分才能顺畅地去使用。

在amd那边有张叫 understanding vulkan objects

但对于实际使用光看这个图可能还是晕,所以我们从功能这个维度进行一个大致的划分,以下是我自己整理的关系图:

解释一下各个对象,TLDR,你觉得自己都懂的话就可以跳过这段。

Hardware:即为硬件直接给出的几个组件,这5个都很好理解不多赘述,而其中 VkDevice 这个几乎和后面所有对象都关联所以不画连线了

在说 Queue 和 Command 前,这里就要先说到 Mantle 世代 api (都是从Mantle 演变而来我们就叫他们 Mantle 世代好了)对于上世代 api 的第一大区别了:这一代的api将图形绘制命令分成了 Record Submit,vulkan 中对应了 command buffer 和 queue,凡是vkCmd开头的调用都是录制命令,vkQueue 开头的都是提交(vkQueuePresentKHR 可以理解为特殊的提交)。

Queue:队列这个东西是从 Mantle 世代的api出现的,我个人的理解是对硬件执行流水线的抽象,通过提交任务执行图形计算,简单理解的话就是提交的内容对应某一个 queue 上只能依顺序执行(gpu 的并行其实并不是像很多人想的像多线程那样fire多少个 task 他就自动并行,就vulkan来讲提交的任务在一个 queue 上是按一定规则排列的)。

  • QueueFamily与Queue:翻译过来应该叫做队列家族,每个Family里会有若干queue,常见的 Family 为三种:Graphic,Compute和 Transfer。Graphic一般是全能的啥都能做,内含的queue数量也是最多;Compute不支持Graphic相关功能;Transfer可以专门用于上传数据。这种分法不是绝对的,要看具体硬件提供的queueFamily数量和支持的功能。vulkan 里的对单个queue 是禁止同一时间有多个线程进行操作的,所以申请多个 queue 能做到多个线程做submit。

Command:录制使用的对象是command pool和command buffer,为啥这么设计现在可以先不管。录制本质上就是一个类似 push_back 的工作,每次调 vkCmd 就往 command buffer 塞内容进去,至于这个 command buffer 在cpu还是gpu这个完全取决于硬件和驱动实现,对vk程序员是透明的。录制结束之后就可以提交给queue执行了,所以理论上是这个时候gpu才开始真正执行。分成录制和提交的原因就是为了多线程。在vk里大家说的多线程渲染其实基本可以理解为多线程录制,多线程提交其实不太常见,也不好写,而且有更好的方案(往command buffer录制并不是直接就能多线程操作的,有很多限制,vk里有一个叫 secondary command buffer的东西可以直接用于多线程录制,不需要任何额外同步)。

这里有一篇arm关于多线程渲染流程最简单的描述

Buffer:Vulkan主要有两种 Buffer 和 Image,Buffer一般用于vertex、index以及uniform,Image用于位图数据,也就是贴图。而Buffer要真正使用起来还得配备一个 VkDeviceMemory,下面会说。

  1. Mesh:这个比较好理解,vertex、index各自对应一个buffer对象和一个memory对象组成一个Mesh,tutorial里也算写的很清楚。
  2. Texture:VkImage 除了 VkDeviceMemory 还需要 VkImageView 和 VkImageSampler,VkImageView 相当于一个 accessor,具体操作都需要操作这个accessor,所以基本上VkImage和VkImageView就是个强绑定关系。VkImageSampler是采样器,anisotropy、mips等在这里设置,可以多个图共享一个采样器。
  3. Uniform:类似Mesh,只不过这个Buffer对应的结构体一定一定要注意内存对齐的问题!!!不同编译器不同平台编出来的内存对齐模式都可能不一样,而vulkan对传过来的数据是有对齐要求的,这个问题我吃过药,tutorial那一章最后写过,当时给我跳过去了,结果shader里拿到的数据怎么都不对,更要命的是这个问题是validation layer无法检查出来的!

Memory:这里就是第二大不同了,以前的 OpenGL 创建一个Texture就能直接上传数据直接用了,uniform都是api直接传输的。到了vulkan里gpu的内存也放给程序员来管理了,VkDeviceMemory 这个对象就是代表你分配的显存。这和 c/c++自己手写内存分配器是差不多一个意思了,你可以定制很多更高级的分配策略,而且vulkan非常不鼓励小分配,vulkan的分配次数每个设备都是有上限的,就是为了让你做 suballocation。而这个活有现成的 vulkan memory allocator可以拿来直接用。

Synchronization

  1. vulkan不光让你自己管理内存,同步也需要手工做,cpu提交之后如何知道gpu完成任务了呢,所以有fence充当cpu和gpu之间的同步工具,cpu通过等待fence知道gpu完成任务。
  2. Semaphores 用于两个提交直接建立依赖,submit和present这两个提交如果没有semaphores,用fence监听submit再通知submit肯定是低效的,所以两次submit之间通过semaphores直接在gpu内部建立依赖。
  3. barrier是做啥的呢,vulkan上传上去的数据相当于都有一个状态(usage,access mask,VkImageLayout等),这个状态首先可以给gpu提供更多的优化信息,其次在操作数据时由于gpu直接操作的数据是先在一个缓存里,在另一个阶段读取这个数据的时候可能不是同一套核心的寄存器,这时候就会发生缓存不一致的问题,所以插入一个barrier就能告诉gpu这段写完下个阶段你得先同步缓存。
  4. event不太常用,我自己也没用过,但功能更精细化属于进阶内容,有兴趣可自行了解。
  5. subpass dependency这个其实也提供了barrier类似的功能,是 subpass 专用的功能,subpass 算是 vulkan 特有的功能,renderpass 必须包含至少一个 subpass,当它只有一个的时候,dependency 建议留空,vk会补上默认值,自己写其实容易写错,tutorial 里其实就写的有问题。

PipelineVkPipeline 定义的是管线状态,隐藏面剔除啊,混合啊都是在这里设置的,而这里最重要的一个状态就是shader是在这里进行绑定的。一旦pipeline创建好了就不能改了,你要换shader就得换个新的pipeline。而 Compute Shader需要再独立的一个 Compute Pipeline,类型一样VkPipeline,但创建方法不同。提一嘴pipeline cache,这个东西就是用来加速shader加载的,因为shader即使是spirv这样底层的代码了,执行对应gpu的shader代码还是需要一趟编译变为gpu专用的最优内容为了节约这个编译时间就搞出了pipeline cache。

FrameBuffer:这个差不多就是 OpenGL 里的 attachment 了,只不过 FrameBuffer 通过引用各种 ImageView 打包成一个集合,FrameBuffer 就是一堆 View 的集合。从 SwapChain 获得的Image 再创建 ImageView 放到 FrameBuffer 里,就能给 gpu 用了。

RenderPass:最初学vulkan最让我迷惑的部分就是RenderPass了,扣了很久的文档和代码才理解,vulkan的RenderPass 本质上描述的是一次渲染我需要的绘制的目标是长什么样的。创建RenderPass时参数里填写的pAttachments其实不是真正的attachment,而是attachment的描述,比如第一个attachment是color,第二个是depth,而 RenderPass 通过 FrameBuffer 才能绑定真正的Image(vkRenderPassBeginInfo里绑定),位置一一对应。这里说一下创建时候为啥也得给一个 FrameBuffer,主要是可以限定这个 RenderPass 不要乱来,能在一创建就能保证和某个 FrameBuffer 相容。之后的渲染流程中只要符合 vk 定义的 Render Pass Compatibility 的 FrameBuffer ,这个FrameBuffer 就能拿来绑定。

Descriptor:shader 里读取数据在 vulkan 里除了 vertex 数据(layout(location = n) in 的形式)剩下最常见的就是 uniform 了,要传输uniform就得通过 DescriptorSet 或 PushConstraint。

先说Descriptor:VkDescriptorSet 的作用是引用用于作为 uniform 的 buffer 数据,主要是 VkBuffer 和 VkImage 两种,VkDescriptorSet 类似 command buffer 需要从一个 DescriptorPool 分配出来,然后通过 vkUpdateDescriptorSets() 方法绑定对应的对象。有了一个 VkDescriptorSet 之后也不能直接用,还需要一个 VkDescriptorSetLayout 来规范约束你这里多少个set,每个set里有多少buffer和image。vulkan支持一个shader用多个descriptorSet读取数据(layout(set=m,binding = n ) 形式),VkDescriptorSetLayout 本身只是个 layout,不存储具体的 set,set 需要在渲染时绑定。有了 VkDescriptorSetLayout 还不够,还需要一个VkPipelineLayout ,主要时因为有 PushConstraint,把前面的 VkDescriptorSetLayout 和 PushConstraint 合一块就是 VkPipelineLayout 了。同时注意 VkPipelineLayout 在创建VkPipeline 时也需要给出,也就是说 Pipeline里的 shader 也需要遵守这个 Layout。而PushConstraint 专门用于小数据传输,性能好但容量也小,直接存储在 command buffer 里的,而不是 VkBuffer,所以也不需要每次更新去 map memory然后 memcpy。当然可以不用 PushConstraint (


好了,理清这些对象之后如何调用 vulkan 其实就非常清晰了,建议自己看着amd的图,对着代码和api按自己的理解也整理一遍,不需要和我一样(而且我这图也主要是为自己看的),等你做完就能有一个体系的认知了。而渲染流程撇开多线程,record&submit,同步。本身和 OpenGL 没什么大的区别。

大致描述一下渲染步骤的主体就是以下这几步:

  1. vkAcquireNextImageKHR —— 从 SwapChains 获取下一个可以绘制到屏幕的Image
  2. vkResetCommandPool/vkResetCommandBuffer —— 清除上一次录制的 CommandBuffer,可以不清但一般每帧的内容都可能发生变化一般都是要清理的。
  3. vkBeginCommandBuffer —— 开始录制
  4. vkCmdBeginRenderPass —— 启用一个RenderPass,这里就连同绑定了一个 FrameBuffer
  5. vkCmdBindPipeline —— 绑定Pipeline,想要换shader就得在这里做
  6. vkCmdBindDescriptorSets —— 绑定 DescriptorSets,可以一次绑多个Set,也可以多次绑定多个Set,同时需要给出 PipeLineLayout
  7. vkCmdBindVertexBuffers&vkCmdBindIndexBuffer —— 没啥好多说的了,绑模型
  8. vkCmdDrawIndexed —— 最关键的绘制命令,这里可以根据显卡的特性支持情况换更高级的绘制命令比如indirect,相应的数据绑定也需要改。
  9. vkCmdEndRenderPass —— 结束 RenderPass
  10. vkEndCommandBuffer —— 结束 CommandBuffer
  11. vkQueueSubmit —— 提交执行渲染任务
  12. vkQueuePresentKHR —— 呈现渲染数据,这时候调用可能 vkQueueSubmit 还没执行完,但 Semaphores 会帮我们打点好。

看到这里都明白了的话基本就是入门了,而具体怎么写代码加高级的功能,这里我要给出几个建议和自己踩过的坑。

  1. 首先上来第一个点,也是我觉得最重要的一个

Validation Layer 一定要学会用!!!

Validation Layer 一定要学会用!!!

Validation Layer 一定要学会用!!!

vulkan 太复杂了,如何写正确是个实实在在的难题,各种状态维护很容易搞错,所以搞了这个 Validation Layer,能帮你快速定位问题。而 vulkan tutorial 里只教了怎么在应用里设置,但Validation Layer是个外部工具,,它还有自己其他的设置细项,文章末尾只说了vk_layer_settings.txt 可以改这些配置,没说怎么配,我手头的n卡具有一定鲁棒性,即使用错了依然是可以运行的,导致我一开始很多错误都没反馈给我。

其实 vulkan sdk 里自带一个 configurator,用这个调就可以启用各种检查了,一般只有打开这个 configurator 的时候才有效,关闭的时候会自动恢复成默认设置。

吐槽下 vulkan tutorial ,其实它里面的内容有些地方拿 Validation Layer 扫是有问题的,最近版本的可能修了,我学的早期版本。

Validation Layer 是实实在在救狗命的工具,很多问题自己看代码解决是几乎不可能的,下断点报错的位置一般都在submit之类的地方。

2. Unifrom buffer 一定记得调整对齐,上面已经提到过,这里写错了 Validation Layer 也救不了你

3. 多写代码不必我多说了,初学进阶的话现在官方的Sample就非常好有讲解有代码,第二推荐SaschaWillems的Sample,他本人其实就给官方 Sample 写,不过这个repo有些东西还是比较新,比如截至到目前光追更新到了正式版,官方那里还是旧的。

推荐几个我自己觉得不错的repo和读物:



这个是nVidia的sample仓库,很多vk开头的都是vulkan的例子

这篇是教的最佳实践,各种对象如何用给出了很多可行方案,值得参考。

vulkan同步是个难点,这里可以看看这个演讲帮助理解。

同上

4. RenderDoc 之类的工具玩熟对 debug 是有很大帮助的,这种工具你要行当里混肯定要多玩。我曾经犯过一个很蠢的问题,渲染出来一片黑, Validation Layer 无任何报错。本来想试试RenderDoc debug pixel,结果怎么也不能调试。后来发现改代码的时候物体位置也改了,默认开始相机里就是啥都没有……自然 debug pixel 没用。RenderDoc 有个功能叫 overlay,随便用一个就能直接看出我这画面是真的啥都没画的还是画失败了。

5. 官方的spec算是一个终极参考,远比市面上的书来的靠谱,书上说的再细致也比不过spec来到可靠,啃书一样是吃力不如直接看spec来的明确。

这里能下到最新的pdf可以离线看,建议就下 all published Extensions 的这个,有关扩展的内容也包含在里面。

6. 遇到问题实在不能解决的时候,k组的官方论坛是给非常好的提问地点,还是有活人在那里解答问题的,此外reddit的vulkan版也是不错的选择,不过国内的社区我其实就不怎么了解了。英语不好的话只能自己想办法了。

以上,希望帮到你(其实我也是顺道留个档总结下)。


2020/12/20:加了arm对多线程渲染的解释,添加了queueFamily 的解释




  

相关话题

  游戏制作过程中,2D 比 3D 需要更多的资金和人力吗? 
  龚神给微软 Linux 子系统写的支持 DirectX 9、11的代码到底属不属于“驱动”? 
  【图形渲染-引擎】软光栅真的这么强?为何ue5之前的引擎做不到? 
  为什么从PS2到PS3画面进步这么明显,而PS3到PS4不怎么明显?PS4NEO和PS5会怎样 
  游戏画面的油腻感是从何而来的? 
  能否通过渲染矢量画面,从根本上解决游戏锯齿问题? 
  Windows 支持 DirectX 和 OpenGL,为什么大多数 PC 游戏还是 DirectX 开发? 
  为什么时至今日仍有人质疑手机的图形性能? 
  主流的游戏引擎都是如何解决Alpha Blending问题的呢? 
  计算机图形学,下一步如何提高? 

前一个讨论
为什么机械硬盘的缓存不是越大越好?
下一个讨论
为什么船舶的造型直观上不符合减少阻力的设计?





© 2024-11-22 - tinynew.org. All Rights Reserved.
© 2024-11-22 - tinynew.org. 保留所有权利