||
为了某个原因成文于2013年,略有几处文字上的修改,也可参考文末提到的两个博客,更加详细一些。忽然想起金庸小说中的胡家刀法,宝树凭借前面几页就从一个跌打医生练就成一个武林高手,可见有时候基础之紧要。
摘 要 3D graphics是一个复杂系统,开发者非常容易陷入“只见树木、不见森林”的困境,本文介绍了3D graphics的来由,知其然并知其所以然,然后稍进一步的探讨了3Dgraphics pipeline的关键过程和相关术语,最后介绍了3D graphics系统和外部系统的多进程和多线程交互,使得读者可以建立起框架性的图景,起到学习过程中的指南导航作用,利于后续的深入研究。
1.引言
3D graphics(三维图形)是一个复杂的系统,目前能够看到的资料往往都是只在3D graphics范畴之内讨论,而缺乏对其边界和内部过程和术语的综述性解释,即使对具有一定经验的开发者而言,也非常容易陷入“只见树木、不见森林”的困境。
先把书读厚,再把书读薄,本文从读厚再读薄后的相关开发经验出发,力图进行最基础性质的解读,以起到学习过程中的指南导航作用。
2.Framebuffer和3D graphics
考察计算机显卡最后输出到显示器的环节,有一块和屏幕分辨率大小一致的物理连续的memory,有一个名为Display Controller的硬件设备,该硬件将memory中的数据传递给显示器,进而在显示器屏幕上显示,如图1所示。可以存在多块memory,其内容都可以被Display Controller传给显示设备,这些memory有个术语叫framebuffer,而当前正被Display Controller传输数据的那块framebuffer被称为on screen framebuffer。当然,framebuffer还有更多的细节和解释,在此简要略过。
图1) Framebuffer的最终显示
在3D graphics出现之前,framebuffer中的数据由CPU计算给出,适用于控制台字符界面、二维图形绘制等场合,但在碰到具有大量三维建模的场景时CPU就会力不从心。于是,三维图形加速显卡应运而生,如图2所示,借助3D graphics的硬件(GPU)及其驱动程序,即可无需CPU介入生成framebuffer中的数据。这种行为一般在业界被称为GPU的硬件加速功能。
图2) 3D graphics的产生
3D graphics定义了数百个API以供应用程序调用,借助这些API,应用程序可以设置现实世界中的几何对象、灯光、眼睛位置等信息,这些三维(3D)空间中的信息将被转换为一幅二维(2D)平面图像(即人眼在屏幕中所看到的),而这幅平面图像被绘制在framebuffer中。三维空间模型如何被转换为二维图像?以第一人称视角游戏为例,进入一个新的游戏场景时,整个三维空间场景首先被建模,然后绘制出当前视角下能看到的内容,这个内容就是人眼在显示器屏幕中所看到的,本质上就是一个二维平面图像。假如移动一下鼠标(即调整视角),就会在屏幕上看到新视角下的场景内容。所以,根据视角的调整,同一个三维空间场景会被绘制成不同的二维图像,在这个过程中,整个三维空间场景不需要被重新建模,只需要应用程序检测鼠标事件并通过API来修改视角即可。
3.3D graphics内部的pipeline
3D graphics的内部过程,即其pipeline的构成如图3所示。三维空间中的物体,从几何角度来说,是由多个顶点构成的,所以Pipeline的第一个步骤就是顶点处理(Vertex Operation),计算每个顶点的属性,比如这个顶点在当前光照条件下应该是什么颜色,在当前的视野下应该做的坐标变换等等。在这个步骤,每个顶点都是被独立处理的,不存在相互依赖关系,本步骤的输出还是顶点。第二个步骤是图元处理(Geometry Operation),这个阶段去除比如因为遮挡而看不见的顶点,引入新的边界处顶点,可能还会生成新的图元等等,本阶段的输出是由顶点组成的图元,包括点、线段和三角形。第三个步骤是光栅化(Rasterization),输出点、线段和三角形覆盖的像素(fragment)并传递到下个步骤,这里产生的像素和最终在屏幕上显示的像素在位置上一一对应。第四个步骤是像素处理(Fragment Operation),像素属性由相应的顶点属性在光栅化阶段插值而来,这个阶段最主要功能是改变像素的颜色值,在这个阶段,每个像素都是被独立处理的,不存在相互依赖关系,本阶段的输出还是像素。最后步骤是Framebuffer Operation,即根据来自上个步骤的像素属性与Framebuffer中相应位置的像素属性进行运算,或者舍弃,或者混合,或者不变等等。
在物理上形式上,GPU硬件及其驱动程序共同构成了3D pipeline。如果GPU的能力强一点,驱动所做的事情就少一点;如果GPU的能力弱一些,那驱动要做的事情就会相应的多一些,极端情况就蜕化为纯软件实现的方法。
图3) 3D graphics内部关键过程
4.3D graphics的context和多进程支持
GPU只有一个,在同时运行多个3D应用程序时,Pipeline是如何处理的?这归功于驱动程序中引入的context概念,context是pipeline中所有参数设置的一个参数值集合。3D应用程序在绘图之前,需要先创建一个窗口(此窗口隐含着一个on screenframebuffer)或者创建一个offscreen bufferbuffer,同时创建一个context,然后将创建的framebuffer和context使用wglMakeCurrent[[1]]/eglMakeCurrent[[2]]/glXMakeCurrent[[3]]等函数进行绑定,再接下去的绘图动作就会作用于此context,从而使得pipeline按照所设置的参数将绘制结果写入相应的framebuffer中。如下图4所示,每个3D应用程序进程因为独占自己的context而呈现出独占GPU硬件的表象。实际上,多个context分时占用GPU,不同context对GPU的访问是互斥的。当某个context占用GPU时,就构成了一个Pipeline。当占用GPU的context发生切换时,驱动程序会确保GPU中的诸如硬件寄存器等状态值都被重新设置。
图4) 3D graphics的多进程支持
5.3D graphics的多线程支持
不同3D应用程序进程使用不同的context进行区分,实际上,在同一个3D进程中,还可以创建多个framebuffer,创建多个context,并且用多线程的方法进行同时绘制,如图5所示,线程甲作用于context 3,绘制结果写入framebuffer B;线程乙作用于context 1,绘制结果写入framebuffer A;线程丙不作3D绘制;线程丁作用于context 4,绘制结果写入framebuffer C;context 2目前未被使用。因为线程切换引起的context切换,在驱动呈现的内部处理上,和多进程间的context切换时非常类似的,使得每个线程呈现出独占GPU硬件的表象。
从图5可以形象的看出,每个线程同时只能最多和一个context连接,因为,假如超过一个的话,该线程接下去的调用就将无法确定应该作用于哪个context连接了。当某个线程调用MakeCurrent函数重新设置和context、framebuffer的连接关系时,原有的连接关系失效。每个context最多可以和一个线程连接,因为,假如超过一个的话,多个线程作用于同一个context,其绘制结果显然不是所希望的。每个contxt只和一个framebuffer连接,显然是无法和多个framebuffer连接的。每个framebuffer只和一个context连接,虽然spec并没有显式禁止,但是,强烈不建议这样做,很有可能驱动程序不支持这种情况的。在默认情况下,不同context之间是相互独立的。如果采用share context技术,可以使不同context之间共享部分信息,对一个context的改动,就会在在另一个context中体现出来。
图5) 3D graphics的多线程支持
6.结语
本文从通俗概念出发,一步步的引入3Dgraphics,解释了其内部关键过程及其术语,并介绍了其和外部系统的多进程和多线程交互,使得读者可以建立起框架性的图景,非常有利于之后的深入研究。
说明:本文部分内容曾发布于本人下述博客中:
http://guoyejun.blog.51cto.com/all/2183091/1
http://blog.csdn.net/yjguo2004/article/category/569821
参考文献
[1] WGL Functions. http://msdn.microsoft.com/en-us/library/dd374394%28v=vs.85%29.aspx
[2] EGL (Native Platform Graphics Interface), http://www.khronos.org/egl
[3] OpenGL Graphics with the X Window System, www.opengl.org/registry/doc/glx1.4.pdf
Archiver|手机版|科学网 ( 京ICP备07017567号-12 )
GMT+8, 2024-11-23 05:57
Powered by ScienceNet.cn
Copyright © 2007- 中国科学报社