7.2  使用执行缓冲

  正如先前所说的那样,我们有两种方法来使用立即模式:使用DrawPrimitive方法,或者使用执行缓冲(显示列表)。

  执行缓冲理解起来比较复杂,并且填充和调试时也有一定困难。但是另一方面,它却能获得最佳的性能。由于与驱动器间的通信速度比较慢,因此程序就允许以批处理的方式来进行通信,这也就是使用执行缓冲。

这一部分来介绍有关执行缓冲的内容以及它的用法:

7.2.1 执行缓冲的结构

  执行缓冲首先由变换模块进行处理。模块运行顶点列表,使用为它而设置的状态信息对顶点进行变换。可以使用裁剪,通过使用视口参数产生附加的裁剪信息来进行再次裁剪。如果没有顶点可见的话,整个缓冲区可以被舍弃掉。然后,顶点由光线模块进行处理,根据缓冲器中的光线指令对顶点添加颜色。最后,光栅模块分析指令流,使用产生的顶点信息渲染图元。

  程序调用IDirect3DDevice::Execute方法时,系统决定顶点列表是否需要进行变换及添加光线等操作。这些操作完成之后,开始分析指令列表和渲染。

  事实上由两个执行缓冲:一个用于程序,一个用于驱动器。程序数据缓冲器由程序进行填充。它保留几何(例如顶点和三角形)和状态信息(变换、光线和光栅状态)。这些信息一直持续到程序要明确的改变它们为止。另一方面,驱动器数据缓冲器保留变换和光线模块的输出(也就是说,它保留变换和光照后的几何体),同时还保留离开光栅模块的数据。每一个驱动器都只有一个这样的“TL buffers”。下图描述了这些数据缓冲器之间的关系:

pic39.gif (11876 bytes)

  使用执行缓冲时,可以使光线模块无效,也可以使光线和变换两个模块都无效。这样可以改变顶点列表进行内插运算的途径,允许使用者为渲染管道的光栅阶段直接提供经过预先变换和预先列表的顶点。要注意,每一个执行缓冲中只能使用一种顶点类型。

  除了执行缓冲和状态改变之外,Direct3D还接受第三种调用机制。在这种机制中,变换模块或光线模块都可以直接调用。在不需要使用光栅时,这一功能非常有用,例如使用变换模块进行边界盒检测。

 

7.2.2 执行缓冲的内容

  执行缓冲中,包含着一系列顶点,顶点之后是如何使用这些顶点的指令流(这些数据都是双字DWORD-aligned)。下面,我们来讨论有关这些顶点和指令流的内容,分以下几个部分:

下图描绘了缓冲器的格式。注意:缓冲器中的数据都是双字。

pic40.gif (3230 bytes)

  指令流包括了运算代码(operation codes)或者操作码(opcodes),以及由操作码来执行的数据。操作码定义了如何对顶点进行光线和渲染处理。Direct3D操作码在D3DOPCODE枚举类型中列出。D3DINSTRUCTION结构用来描述执行缓冲中的指令;它包括一个操作码,每一个指令数据单元的大小,以及后面相应的数据单元的数量。

最普通的指令是一个三角形列表(D3DOP_TRIANGLE),它是一个简单的三角形图元的列表,这个图元引用了顶点列表中的顶点。由于所有指令流中的图元都只引用顶点列表中的顶点,这样对于变换模块来说,要放弃一个处于视锥之外的图元就很容易了。

 

缓冲器中的顶点数据之后,是一个指令流。每一个指令又分为:

  执行缓冲指令用来支配驱动器。每一个指令由一个操作代码(操作码)来进行标识。所有的执行数据都以一个指令头来开头。数据伴随着每一次操作码的重复。每一个操作码都可以有多个变元,包括多个三角形或多个状态改变等。指令类型只有很少的几种,我们接下来进行讨论:

绘图指令(Drawing Instructions

  最重要的绘图指令用来定义一个三角形。在一个三角形中,顶点在执行缓冲的顶点索引中是从0开始的。有关三角形的详细内容见“三角形”部分。

  其他的重要的绘图指令包括线段绘图指令D3DLINE和点绘图指令D3DPOINT

状态改变指令(State-change Instructions

  系统保存几何管道中的每一个模块的状态,直到这些状态被执行缓冲中的一个指令改变为止。

变换状态

世界、观察和透视矩阵

灯光状态

表面材质,雾化,环境光

渲染状态

纹理,反走样,z-buffering

流程控制指令(Flow-control Instructions

  流程控制指令允许对指令进行分支,或者跳转到缓冲器上的一个新位置,跳过或重复某些指令。也就是说,你可以象某些程序语言一样来使用流程控制指令。

  一个执行缓冲中的最后一条流程控制指令必须是D3DOP_EXIT

其他指令(Other Instructions

其他指令包括:

纹理

把一个纹理加载到设备

矩阵

矩阵加载或矩阵相乘

范围(span),设置状态(SetState

对图元和渲染状态的高级控制

 

7.2.3 创建一个执行缓冲

创建执行缓冲的技巧在于将多大的内存分配给它。有两种办法来决定正确的大小:

  硬件决定着缓冲器的大小。调用IDirect3DDevice2::GetCaps方法,查看D3DDEVICEDESC结构的dwMaxBufferSize成员,可以得到这个大小。通常,使用软件驱动器时,64 KB的大小比较合适,因为这一大小能充分利用二级缓存。当程序使用硬件加速时,应该尽量减小缓冲器的大小,以便能够充分利用主缓存。

  填充完D3DEXECUTEBUFFERDESC结构之后,就可以调用IDirect3DDevice::CreateExecuteBuffer来创建执行缓冲了。

  计算执行缓冲的大小以及创建一个执行缓冲的过程,可以查看“Direct3D执行缓冲教程”中的“创建场景”。

7.2.4 锁定执行缓冲

  在修改执行缓冲之前,必须将它锁定。这样可以防止在运行过程中对一个执行缓冲进行修改。

  要锁定一个缓冲器,可以调用IDirect3DExecuteBuffer::Lock方法。这个方法只有一个参数;是一个指向D3DEXECUTEBUFFERDESC结构的指针,它用来声明缓冲器在内存中的明确位置。

  运行执行缓冲时,需要管理三个指针:缓冲器的开始地址(由IDirect3DExecuteBuffer::Lock得到),指令开始地址,程序在缓冲器中的当前位置。使用这三个指针来计算顶点偏移量,指令偏移量和缓冲器的大小。填充完执行缓冲之后,可以使用这些指针来向驱动器描述缓冲器。

7.2.5 填充执行缓冲

执行缓冲经过填充之后,它就包括了用来描述模型的顶点和一系列的指令。下面描述填充缓冲器的过程:

  你可以使用DirectX SDK例子中提供的辅助宏(helper macros)来使填充缓冲器的过程流程化。D3dmacs.h文件包含了许多有用的宏。PUTD3DINSTRUCTIONVERTEX_DATA宏对于填充执行缓冲非常有用。

  填充执行缓冲的例子见“Direct3D执行缓冲教程”中的“填充执行缓冲”。

  1. 选择顶点类型
  2.   程序可以使用全部的Direct3D渲染管道,也可以只使用一部分。程序中使用的顶点类型将决定使用多少渲染管道。详细内容见“顶点格式”。

  3. 三角形
  4.   使用D3DOP_TRIANGLE操作码在一个执行缓冲中插入一个三角形。在一个三角形中,顶点在执行缓冲的顶点索引中是从0开始的。三角形在D3DTRIANGLE结构中进行描述。

      三角形是唯一能被光栅模块处理的几何类型。屏幕坐标从设备(屏幕或窗口)左上角的(0, 0)开始,直到设备右下角的(width 1, height 1)为止。深度值从位于视锥前端点的0直到视锥的后表面。光栅的作用是在对相邻的两个三角形(这两个三角形有一条公共边)进行渲染时,避免对这条公共边渲染两次。根据三角形顶点的缠绕方向,光栅对朝后的三角形进行Cull。只有顶点按照顺时针方向排列的三角形才进行渲染。

      必须要确定的是,三角形数据必须是QWORD(8-byte)D3dmacs.h文件中的OP_NOP辅助宏可以帮助你完成这一任务。注意:如果要使用这个宏,必须将它放在开始和结束方括号之间。

  5. 处理顶点
  6.   填充完顶点之后,需要使用D3DOP_PROCESSVERTICES操作码来设置顶点的光线和变换。

      D3DPROCESSVERTICES结构用来描述如何对顶点进行处理。结构中的dwFlags成员用来声明所使用的顶点类型。如果使用D3DTLVERTEX顶点,应该将dwFlags 声明为D3DPROCESSVERTICES_COPY。对于D3DLVERTEX类型,要声明为D3DPROCESSVERTICES_TRANSFORM。对于D3DVERTEX类型,要声明为D3DPROCESSVERTICES_TRANSFORMLIGHT

  7. 结束指令

  指令流的最后一个操作码应该是D3DOP_EXIT。这个操作码用来发出系统可以停止处理数据的信息。

 

7.2.6 解锁执行缓冲

  程序填充完缓冲器之后,必须解锁。它会通知驱动器,现在可以运行缓冲器了。使用IDirect3DExecuteBuffer::Unlock方法来打开缓冲器。

  执行缓冲打开后,调用IDirect3DExecuteBuffer::SetExecuteData方法向驱动器提供一些有关缓冲器的重要信息。这个方法使用一个指针指向D3DEXECUTEDATA结构。在这个结构中包含了要提供的信息,包括顶点的偏移量和锁定缓冲器后程序正在进行跟踪的指令。

 

7.2.7 运行执行缓冲

  运行一个执行缓冲,就是要调用IDirect3DDevice::Execute方法,其中,要使用指向执行缓冲的指针和指向视口的指针。程序可以通过查看这个方法的返回值,来确定运行是否成功。

  IDirect3DDevice::ExecutedwFlags参数用来声明是否对顶点进行裁剪。如果所有的顶点都包含在视口中,那么应该将它声明为D3DEXECUTE_UNCLIPPED,否则应声明为D3DEXECUTE_CLIPPED

  运行完缓冲器之后,可以调用IDirect3DExecuteBuffer::Release方法将它释放掉。如果你愿意的话,也可以使用D3dmacs.h中的RELEASE宏。

7.2.8 状态与状态覆盖

  Direct3D根据当前的状态设置来解释缓冲器中的数据。在通知系统渲染数据之前,程序要设置这些状态。D3DSTATE结构包含了三中枚举类型:D3DTRANSFORMSTATETYPE,它设置变换模块的状态;D3DLIGHTSTATETYPE,设置光线模块;D3DRENDERSTATETYPE,设置光栅模块。

  每个状态都有一个布尔类型的值,它是一个只读标志。如果这个标志被设置为TRUE,那么,将不允许对状态进行改变。

  通过使用D3DSTATE_OVERRIDE宏,程序可以覆盖一个模块的只读状态。这一机制允许程序重新使用一个执行缓冲,通过改变系统的状态来改变缓冲器要完成的操作。Direct3D保留模式中,使用状态覆盖(state override)来完成一些需要重建缓冲器才能完成的任务。例如,保留模式API可以通过状态覆盖用一个框架的纹理来替换一个多面体的纹理。

  程序也可以使用D3DSTATE_OVERRIDE宏来打开和关闭Gouraud明暗处理模式,如下例所示。(渲染的明暗处理模式通过D3DRENDERSTATETYPED3DRENDERSTATE_SHADEMODE成员来定义。)

OP_STATE_RENDER(2, lpBuffer);
STATE_DATA(D3DRENDERSTATE_SHADEMODE, D3DSHADE_GOURAUD, lpBuffer);
STATE_DATA(D3DSTATE_OVERRIDE(D3DRENDERSTATE_SHADEMODE), TRUE,lpBuffer);

  OP_STATE_RENDER宏隐含的使用D3DOP_STATERENDER操作码,这个操作码D3DOPCODE枚举类型的一个成员。D3DSHADE_GOURAUDD3DSHADEMODE枚举类型的一个成员。

  运行完执行缓冲,程序仍然可以使用D3DSTATE_OVERRIDE宏,允许改变明暗处理模式:

STATE_DATA(D3DSTATE_OVERRIDE(D3DRENDERSTATE_SHADEMODE), FALSE, lpBuffer);

  OP_STATE_RENDERSTATE_DATA宏在DirectX SDK例子的Misc目录的D3dmacs.h文件中定义。

7.3  执行缓冲教程

  见“执行缓冲教程”。

 

上一页 | 目录 | 下一页