Chinaunix首页 | 论坛 | 博客
  • 博客访问: 166807
  • 博文数量: 48
  • 博客积分: 1410
  • 博客等级: 上尉
  • 技术积分: 640
  • 用 户 组: 普通用户
  • 注册时间: 2008-12-22 15:54
文章分类
文章存档

2011年(2)

2010年(13)

2009年(30)

2008年(3)

我的朋友

分类:

2009-01-07 15:15:00

锁定缓冲
  
锁存机制也许是Direct3D中被误解最多的技术,特别是在Managed DirectX中。人们经常会问如何锁定以及如何高效的完成锁定。

  那么,究竟什么是“锁定”缓存呢?它其实就是允许CPU直接访问资源中一定范围内数据的操作。你不能直接访问图形硬件,因此需要一种方法来控制程序中的顶点数据,而锁存就是用来完成这个任务的。来看看可用在顶点和索引缓冲上的众多lock方法吧。

public System.Array Lock ( System.Int32 offsetToLock ,Microsoft.DirectX.Direct3D.LockFlags flags )
public System.Array Lock ( System.Int32 offsetToLock , System.Type typeVertex , Microsoft.DirectX.Direct3D.LockFlags flags , params int[] ranks )
public Microsoft.DirectX.Direct3D.GraphicsStream Lock ( System.Int32 offsetToLock , System.Int32 sizeToLock , Microsoft.DirectX.Direct3D.LockFlags)

  如你所见,有3种方法可用来锁定缓冲。我们先从最简单的第一个开始。这个方法对只对通过使用System.Type以及一系列顶点或索引的构造函数创建的缓冲有用。实际上,这个方法只是使用构造函数所传入的数据来再调用第二个重载的方法而已。

  接下来的两个重载就比较有意思了。他们的第一个参数都表示开始锁定的偏移值(以比特为单位)。如果需要锁定整个缓冲,把这个值设置为0就可以了。你可能已经注意到前两个重载的方法都把数据以数组的方式作为返回值。在第二个重载中,第二个参数可以设置所返回的数组的类型。最后一个参数决定了返回数组的大小。

  由于某些原因“Rank”参数总是困扰着开发者,让我们来仔细讨论一下吧。假设有一个只包含了位置数据(Vector3)的顶点缓冲,并且它保存了1200个顶点。如果你想把它锁定为一个包含1200个元素的Vector3数组,那么应该这样调用方法:

Vector3[] data = (Vector3[])vb.Lock(0, typeof(Vector3), LockFlags.None, 1200);

注意到额外的参数没有?Rank参数实际上是一个参数数组。它还可以创建三维数组作为返回值。你可以使用这个参数来指定返回多大的数组。

特别提示:如何高效的锁定缓冲
  
LockFlag应该和最后一个重载放到一起来讨论,但首先,我想指出前两个以数组作为返回值的方法的缺点。第一位,也是最重要的,我们应该讨论一下性能。假设你使用“default”选项创建了一个顶点缓冲,并且没有lock flag,当调用这个方法的时,将发生以下情况:

顶点数据被锁定;保存下数据的内存地址。
根据rank参数指定的大小,在内存中定位一个类型正确的新数组。
数据从被锁定的内存中复制到新的缓冲。
新的缓冲被返回给用户进行修改。
调用Unlock方法的时,新缓冲中的数据再次被复制回锁定的内存中。
最后,解锁顶点数据。

  不难明白为什么这个方法要比你所预计的慢一些。 通过设置可以减少一次复制:创建缓冲时指定Usage.WriteOnly参数(避免第一次复制),或者在锁定缓冲时使用LockFlags.ReadOly标志(避免第二次复制); 但没有方法可以把两次复制都消除。对一块ReadOnly/WriteOnly的缓冲能干些什么呢?
最后一种重载是最强大的。同时,他也有一个其他重载没有的参数,指定了我们想要锁定的数据大小。在其他的重载中,这个值是通过调用(sizeof(type)*NumberRanks)方法计算出来的。如果使用这个方法,则只需要传入需要锁定的数据大小(以比特为单位)就可以了。如果如要锁定整个缓冲,那么把前两个参数设为0就可以了。

  这个方法将返回一个GraphicsStream类来让你直接控制锁定的数据,而不需要为数组分配额外的内存,也不需要额外的内存复制。你可以对这快内存作任意的修改。GraphicsStream对象有很多方法允许你把数据“写入”这块内存,这并不会带来很大的速度提升。但你可以通过直接控制内存来获得速度提升。

  当然,我并不是想告诉你返回数组的方法会慢很多。合适的使用这两个方法是很方便的,差不多和返回数据流的方法一样快。但是,如果你想把系统里每一盎司性能都炸干的话,就应该使用返回数据流的方法。


控制如何锁定缓冲
最后,我们来讨论一下锁定内存时可以使用的标志(flags)。锁定顶点缓冲时,只有以下标志是有效的:

LockFlags.None
LockFlags.Discard
LockFlags.NoOverwrite
LockFlags.NoDirtyUpdate
LockFlags.NoSystemLock
LockFlags.ReadOnly

显然,当不使用锁定标致时,就使用默认的锁定机制。但是,如果你需要对锁定进行更多控制,其他标志就可以完成多种选项。

Discard标志只能用于动态缓冲。对顶点缓冲和索引缓冲来说,整个缓冲都会被丢弃,另外返回一块新的内存,以防还有其他程序在访问旧的数据。这个标志在填充动态的顶点缓冲时特别有用。一旦完成了对顶点缓冲的填充,锁定就结束了。它通常和其他标志一起使用。

NoOverWrite标志(同样也只对动态缓冲有用)告诉Direct3D你不会复写顶点和索引缓冲中的任何数据。时用这个标志,会使调用立即返回,并且继续使用缓冲。如果不使用这个标志,那么锁定调用直到完成了当前所有渲染之后才会返回。既然你不会修改但前缓冲中的任何数据,它只在向缓冲中添加顶点时才有用。

这两个标志使用最多的情况,是在填充大块的动态缓冲时。但持续向缓冲填充数据的时候,保持使用NoOverwrite标志,直到填充完毕。接下来,使用Discard标志刷新缓冲,然后再次开始填充。现在来看看随DirectX SDK一起发布的实例Point Sprites:

if (++numParticlesToRender == flush)
{
    // Done filling this chunk of the vertex buffer. Lets unlock and
    // draw this portion so we can begin filling the next chunk.
    vertexBuffer.Unlock();
    dev.DrawPrimitives(PrimitiveType.PointList, baseParticle,
    numParticlesToRender);
    // Lock the next chunk of the vertex buffer. If we are at the
    // end of the vertex buffer, LockFlags.Discard the vertex buffer and start
    // at the beginning. Otherwise, specify LockFlags.NoOverWrite, so we can
    // continue filling the VB while the previous chunk is drawing.
    baseParticle += flush;
    if (baseParticle >= discard)
        baseParticle = 0;
    vertices = (PointVertex[])vertexBuffer.Lock(baseParticle *DXHelp.GetTypeSize(typeof(PointVertex)), typeof(PointVertex),(baseParticle != 0) ? LockFlags.NoOverwrite : LockFlags.Discard,flush);
    count = 0;
    numParticlesToRender = 0;
}

  在这段代码中,我们检测是否是应该“刷新”数据了。如果需要,我们就解锁并且渲染。接下来,检查是否到达了缓存的最后(baseParticle>=discard),最后再次锁定缓冲。我们使用NoOverwrite标志,从缓冲的尾部开始锁定,假如baseParticle是0,就使用Discard标志,从缓冲头开始锁定。这样就可以不停把新的point sprite添加到场景中,直到缓冲满了,这时候,就丢弃旧的缓冲,使用一块新的。

  讨论完了2个“复杂”的标志之后,来看看剩下的标志。其中最简单的就是ReadOnly标志了,正如它名字所示,它会告诉Direct3D你不会写入缓冲。当使用返还数组的重载方法时,它也表示在最后解锁缓冲时,不需要复制更新过的数据。

  NoDirtyUpdate标志防止对任何处于“dirty”状态的资源进行修改。没有这个标志,锁定资源时会自动对资源添加一个脏区域(dirty region)。

  最后一个标志不太常用。一般来说,当你锁定显存中的资源时,会保留一个系统级别的临界区,在使用这个标志的锁定期间,不允许改变任何显示模式。使用systemLock标志,可以取消这种效果。它只有在你预计锁定时间很长,并且还需要保证很快的系统速度时使用。通常不推荐使用这个选项。

  自然,在调用了lock方法之后,你还必须在某个时刻告诉Direct3D完成了锁定。Unlock就是用来完成这个任务的。这个函数没有其他的参数,而且必须在每锁定之后都调用unlock方法。有任何为解锁的数据都将导致渲染失败。
阅读(983) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~