原文:《A trip through the Graphics Pipeline 2011》
翻译:往昔之剑
 
转载请注明出处
 
你可以找到很多PC图形栈的功能描述,但是通常却不明所以然。我会尽量避开硬件部分的种种细节,来填补这些空白知识点。我打算讲述
一下在Windows上运行d3d9/10/11的dx11接口的硬件,因为发生在PC上的堆栈细节我再熟悉不过了,而不是API之类的细节。这第一部分,会讲很多我们实际在GPU上执行的本地指令。
 
应用程序
这是你的代码部分,还包括各种bug,没错,运行时API和驱动程序都有bug,但不是指的这两者,现在准备好修复掉他们。
 
运行时API
即通过API的资源创建/状态设置/draw call等,运行时API跟踪应用程序设置的状态,验证参数,处理错误,一致性检测,管理用户的可见资源,或者检验shader代码和link shader(在d3d中是这样,OpenGL是在驱动层做的),还可能分批处理更多的任务,然后在图形驱动中处理所有事情——更准确的说,是在用户模式驱动中(user-mode driver)
 
用户模式驱动(UMD)
这是CPU端隐藏的最为神秘的部分,如果你的应用程序因调用了某些API崩溃了,那通常都是这里造成的:)。它可能是“nbd3dum.dll”(NVidia)或者“atiumd*.dll”(AMD)。 如命名所示,这是用户模式代码。它和你的应用程序一样,都运行在相同的上下文和地址空间里,并没有什么特殊的。它实现的底层API(DDI)即D3D。这个API和你表面上看到的很类似,但是在内存管理上是有一点区别的。
 
这个模块诸如发生在Shader编译期间。D3D传递一个预校验好的Shader记号给UMD,即代码已经检查过了,是语法正确,符合D3D约束的(指使用正确的类型,没超出可用的纹理/采样器,没超出可用的常量缓冲,等)。这是从HLSL代码编译的,通常有相当多的高级优化(各种循环优化,消除没用的代码,常量传递,预测分支等)——这是很好的,在编译阶段进行了大量的优化是很有帮助的。然而,还有大量的底层优化(比如寄存器分配和循环展开)都是由驱动来完成的。长话短说,通常都先转化为中间表示(Intermediate Representation,IR),然后再编译。Shader硬件指令很接近于D3D字节码(HLSL编译器已经帮助做了高度的优化工作),但是仍有一些底层细节(比如硬件资源限制和调度约束),D3D不知道也并不关心,这不是重要过程。
 
当然,如果你的应用程序是知名游戏,NV/AMD的程序员可能会查看你的shader,并给出针对于硬件优化过的shader。这些Shader能被UMD检测到并替换,是很友好的。
 
更有趣的是:某些API状态可能实际上最终被编译进Shader里——举个例子,相对一些不常使用的特性,比如纹理边框(texture borders)可能没实现进纹理采样器里,但是通过额外的shader代码来模拟实现(或者完全不支持)。这意味着,有时相同的Shader有多个版本,对应不同的API状态。
 
顺便说一下,这就是为什么你经常看到第一次使用一个新的shader时候会发生延迟。大量的创建/编译工作被驱动延迟执行,并且只在它实际用到的时候才执行(你根本不会相信一些应用程序创建了多少没用的资源)。图形程序员都知道的一个事情——如果你想要确保某些东西被实际创建了(而不是仅仅的保存在内存里),你需要执行一个虚拟的draw call来让它被“唤醒”(原文:warm it up)。这很烦人,但是这自从我1999年第一次开始使用3D硬件就一直这样,这是个不争的事实,这点上要去适应它:)
 
继续话题。UMD还能处理一些有趣的东西,比如D3D9遗留的Shader版本和固定管线——没错,所有的D3D功能都支持。3.0 shader profile并不糟糕(实际上还相当合理),但是2.0有点混乱,各种1.x的shader版本那就更乱套了——还记得1.3 pixel shader吗?还有 带顶点光照的固定顶点管线?是的,现代显卡支持所有的D3D功能,尽管他们目前只是翻译到新的Shader版本(这样做已经很长时间了)。
 
还有内存管理。UMD要获取到纹理创建指令的内容,并且需要为他们分配空间。实际上UMD只是进一步从KMD(内核模式驱动,Kernel-Mode Driver)分配一些大的内存块,实际的映射和非映射页(管理UMD可见的显存,反之,GPU可以访问的系统内存)是KMD的特权,UMD不能做。
 
但是,UMD可以做,像重组纹理(swizzling textures)(除了GPU可以在硬件里执行,通常使用2D块传输单元而不是真正的3D管线)和系统内存与映射显存之间的传输调度。最重要的是,一旦KMD已经分配和处理好,UMD还可以写command buffer(或叫”DMA Buffer“——我将交替使用这个名称)。Command buffer,包含了各种指令。所有的状态改变和绘制操作将通过UMD转化成硬件可识别指令。很多事情不需要手动触发——比如上传纹理和Shader到显存。
 
一般来说,驱动会尝试尽可能的在UMD中实际处理,UMD是用户模式代码,所以运行在这里的部分不需要昂贵内核模式转换,它可以自由分配内存,分派出多个线程工作,等等——这只是一个常规的DLL(尽管API不是应用程序直接提供的)。这也有利于驱动开发——假如UMD崩溃了,应用程序也会崩溃,但是不是整个系统都崩溃,当系统运行时,它可以被替换(它仅仅是个DLL),可以被常规的调试器调试,等等。所以,这不仅很有效,还很方便。
 
还有一个重点我没有提到。
 
我说的是“User-Mode Driver”吗?我说的是“User-Mode Drivers”。
如之前所说,UMD只是个DLL。好吧,借助D3D的帮助直接通向KMD,但它仍旧是个普通的DLL,运行在调用进程的地址空间中。
但是,我们如今在使用的多任务系统已经有很长时间了。
我一直在谈论关于GPU的什么?是共享资源。你的主显示只有一个(即使你使用SLI/交火)。然而,我们有多个应用程序尝试访问它(假设只有他们这样做)。这不会自动工作。在过去,解决办法是 当应用程序激活时,一次只给出一个3D应用程序,所有其他的不能访问。但是如果你尝试让你的窗口系统使用GPU渲染,这就不行了。这个原因就是为什么你需要一些组件 来仲裁访问GPU,并分配时间片等。
 
进入调度器
这是一个系统组件。我这里说的图形调度器,不是CPU或IO调度器。确切的说,由它来决定在不同应用程序之间何时访问想要使用的3D管线。一些GPU状态切换(生成额外的commands到command buffer)会发生上下文切换,还可能交换一些资源在显存内外。当然,在指定的时间只有一个进程可以实际提交commands到3D管线。
 
你会经常发现终端程序员抱怨PC上的3D API太高层,不可控,并且耗性能。但是,比起终端游戏,PC上的3D API/驱动真的有太多复杂问题要解决——他们需要跟踪全部的当前状态,因为随时可能从底层发现问题!他们还要管不好使的应用程序,尝试修复背后的性能问题。这是很烦人的事,没人喜欢,当然也包括驱动作者他们自己,但实际上站在商业角度,人们想要应用能继续运行(并且顺利运行)。只对着应用程序喊“出错啦!”,然后生着闷气,慢慢检查,你是赚不到朋友的。
 
管线之旅的下一站:内核模式!
 
内核模式(KMD)
 
这部分由硬件实际处理。可能有多个UMD实例运行在同一时间,但永远只有一个KMD,如果KMD崩溃了,你的程序也就玩完了——过去是蓝屏,但现在的Windows知道如何杀死崩溃驱动的进程并且重新载入它。尽管它只是崩溃,而非内核内存被污染,但一旦发生,所有的东西就都没了。 
 
KMD一次性处理所有事物,尽管多个应用程序都争夺使用它,但只有一份GPU内存。某些程序实际调用的是物理内存。同样,某些程序在启动时必须初始化GPU,设置显示模式(从显示设备获取信息),管理硬件鼠标指针,制定硬件监视器,如果一定时间内无响应,就重置GPU等等。这就是KMD做的事情。  
 
还有视频播放器DRM格式的内容保护,以及GPU解码像素对非法用户不可见,可能会导致一些糟糕的事情,比如转存储到磁盘。KMD也会参与这些事情。
 
对我们来说最重要的是,KMD管理实际的command buffer。要知道,这才是硬件的实际消耗。UMD处理的command buffer并不是真实的——它们只是能访问到的GPU随机内存片段。实际上,由UMD完成处理,将它们提交到调度器,然后等到该处理了,再传递UMD的command buffer到KMD。然后,KMD向主command buffer写入调用指令,再跟据GPU指令处理器能否读取主内存,可能会先执行DMA访问显存。主command buffer通常是一个(相当小的)环形缓冲结构——只能获取写入的系统/初始化指令和调用实际的3D command buffer。
 
但这还只是一个内存缓冲,显卡还得知道它的位置——通常在主command buffer里有一个GPU端的读指针和一个标记KMD已写入缓冲位置的写指针(或更准确的说,到目前为止告诉GPU写了多少)。KMD会周期性更新这些被内存映射的寄存器(通常是每次提交新工作块的时候)……
 
总线
 
不直接访问显卡(除非是集成在CPU上的),因为它需要先通过PCI Express总线,DMA传输也是走的这个路线。这部分不会讲述很长时间,但也是我们的图形管线之旅的一站地。
 
指令处理器
 
这是GPU的前端——实际读取KMD写入指令的地方。我将继续在下一篇讲述,这篇文章写得已经有点长了:)
 
小旁白:OpenGL
 
OpenGL和我上面讲述很类似,除了API和UMD层的区别。不像D3D,GLSL shader编译不能通过API操作,只能在驱动中完成。这点有个不好的地方,有很多GLSL平台,像3D硬件厂商,他们只实现相同的规范,但有各自的bug和问题。这意味着驱动不得不自己做好所有的优化。D3D字节码格式在这点上解决方案很简洁——只有一个编译器(因此不同厂商很少有不兼容的情况),并且可以进行数据流分析。
 
 

图形管线之旅 Part 1的更多相关文章

  1. 图形管线之旅 Part4

    原文:<A trip through the Graphics Pipeline 2011> 翻译:往昔之剑   转载请注明出处   欢迎回来.上个部分是关于vertex shader的, ...

  2. 图形管线之旅 Part3

    原文:<A trip through the Graphics Pipeline 2011> 翻译:往昔之剑   转载请注明出处   此时,我们一路上通过多个驱动层和命令处理器将draw ...

  3. 图形管线之旅 Part2

    原文:<A trip through the Graphics Pipeline 2011> 翻译:往昔之剑   转载请注明出处   还没那么快   在上一篇,讲述了渲染命令在被GPU处理 ...

  4. 图形管线之旅 Part6

    原文:<A trip through the Graphics Pipeline 2011> 翻译:往昔之剑   转载请注明出处   欢迎回来.这次我们去看看三角形的光栅化.但在光栅化三角 ...

  5. 图形管线之旅 Part5

    原文:<A trip through the Graphics Pipeline 2011> 翻译:往昔之剑   转载请注明出处   在上一篇关于纹理采样器之后,我们现在回到了3D前端.那 ...

  6. OpenGL图形管线和坐标变换[转]

    1. OpenGL 渲染管线 OpenGL渲染管线分为两大部分,模型观测变换(ModelView Transformation)和投影变换(Projection Transformation).做个比 ...

  7. OpenGL图形管线和坐标变换

    转:http://blog.csdn.net/zhulinpptor/article/details/5897102 1. OpenGL 渲染管线 OpenGL渲染管线分为两大部分,模型观测变换(Mo ...

  8. Vulkan Tutorial 10 图形管线

    操作系统:Windows8.1 显卡:Nivida GTX965M 开发工具:Visual Studio 2017 Introduction 通过接下来的章节,我们将会开启有关图形管线的话题,通过对图 ...

  9. 浅谈Unity的渲染优化(1): 性能分析和瓶颈判断(上篇)

    http://www.taidous.com/article-667-1.html 前言 首先,这个系列文章做个大致的介绍,题目"浅谈Unity",因为公司和国内大部分3D手游开发 ...

随机推荐

  1. 【CODEVS】3546 矩阵链乘法

    [算法]区间DP [题解] 注意先输出右括号后输出左括号. f[i][i+x-1]=min(f[i][i+x-1],f[i][j]+f[j+1][i+x-1]+p[i]*p[j+1]*p[i+x]) ...

  2. Spring boot 集成Dubbox(山东数漫江湖)

    前言 因为工作原因,需要在项目中集成dubbo,所以去查询dubbo相关文档,发现dubbo目前已经不更新了,所以把目光投向了dubbox,dubbox是当当网基于dubbo二次开发的一个项目,dub ...

  3. Morley's Theorem (计算几何基础+向量点积、叉积、旋转、夹角等+两直线的交点)

    题目链接:https://uva.onlinejudge.org/index.php?option=com_onlinejudge&Itemid=8&page=show_problem ...

  4. java 连接数据库报错:Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server time zone value '

    1.解决方法: 报错信息为: Caused by: com.mysql.cj.exceptions.InvalidConnectionAttributeException: The server ti ...

  5. JQGrid 导出Excel 获取筛选条件

    需求描述:页面加载后,进行相关数据搜索,要求点击导出按钮后  下载Excel文件. 思路:希望在点击[导出Excel]按钮时,获取到表格搜索时的filters内容. 在百度.api.jqgrid.js ...

  6. Less & Sass

    CSS不是一种编程语言.它开发网页样式,但是没法用它编程.也就是说,CSS基本上是设计师的工具,它没有变量,也没有条件语句,只是一行行单纯的描述.有人就开始为CSS加入编程元素,这被叫做"C ...

  7. c语言中网络字节序和主机字节序的转换

    函数说明   相关函数:htonl, htons, ntohl 头文件:#include <netinet/in.h> 定义函数:unsigned short int ntohs(unsi ...

  8. promise 如何知道所有的回调都执行完了?

    var fs = require('fs'); /** * @return {object} Promise */ function doThing(fileName) { // ... // con ...

  9. 在linux下有没有什么软件可以连接windows上的MSSQL SERVER

    在linux下有没有什么软件可以连接windows上的MSSQL SERVER GUI的http://dbeaver.jkiss.org/ http://bbs.csdn.net/topics/391 ...

  10. redis cluster 实现

    Redis cluster是一个redis官方提供的集群功能,集群节点最小3个节点,配置比较多,记录下来,以供下次使用.我在这使用的redis 4.0.6. 因为最新的ruby redis扩展需要ru ...