为DrawPrimitiveUP(DrawUserPrimitive)洗冤

 


最初只因DXSDK文档里说了句推荐用Vertex Buffer而不要用DrawPrimitiveUP(C#里叫DrawUserPrimitive),DrawPrimitiveUP很快被描绘成传说中的瘟疫,人人都在警告不要接近它。估计有人会想过,既然DrawPrimitiveUP这么不好,为什么还要提供它,难道只是为了显示DX也可以像OpenGL一样简单地画三角形?

记得当年我就是抱着这种想法在网上狂搜,功夫不负有心人,还真找到了。不过看了很不好意思,人家上来就批判不实际测试、以讹传讹的问题,我也比较懒,没动手测一下。那么为了和我一样的懒人,我把问题用中文解释一遍。

首先,DrawPrimitiveUP内部其实就是一个dynamic vertex buffer(动态顶点缓冲),和我们自己实现一个动态顶点缓冲没区别。一般情况下,DrawPrimitiveUP和用动态顶点缓冲的效率也没多大区别。也就是说DrawPrimitiveUP其实很高效的,而且简单易用。Irrlicht引擎几乎所有绘制都用的DrawPrimitiveUP,也很快的。

那为什么不推荐用?

原因一:DX8发布时显存容量已经有了很大提高,静态顶点缓冲可以缓存在显存或AGP内存里,从而节省带宽占用。所以推荐能用静态顶点缓冲的一定要用静态的。静态的可以比DrawPrimitiveUP和动态顶点缓冲都快很多。

原因二:DrawPrimitiveUP相对动态顶点缓冲而言,需要将用户内存里的顶点数据复制到内部动态顶点缓冲,即多了一次复制,如果顶点数量较大,复制开销也会加大。但很多程序里的动态缓冲设计并不太好,为了抽象或方便使用,也会复制一次数据。所以便丧失了这条优势。

原因三:这个比较复杂,我们知道一帧内Batch(批)的数量直接影响CPU的占用率,1G处理器30FPS下每帧700Batch左右就会占用100%CPU。每个设置设备状态到发出绘制命令的转换都将产生一个Batch。动态顶点缓冲的推荐使用模式是一个可以合并Batch的模式,即不断地填充顶点数据,但不立刻绘制,在缓冲填满时才提交绘制一次,当然能合并的前提是各个batch都使用相同的设备状态,即纹理、材质、RenderStates、变换矩阵等。

原因四:DrawPrimitiveUP只支持一个顶点流。这其实是个不算是原因的原因。当然是只用一个顶点流时才用它。

综上所述,这其实是个优化问题,用动态顶点缓冲有可能做更多的优化,但如果做得不好,会比DrawPrimitiveUP差。如果正确使用了,但没有进一步的优化或者引擎的用法不具备可优化的特性,那么也就和DrawPrimitiveUP效率相当。

但事实上用动态顶点缓冲做错了的也很多,最常见的就是没有正确使用Lock标志位,用锁定静态缓冲的方法锁定,根本得不到动态缓冲的效果。另外用C#和MDX的,如果用返回数组的Lock方法重载,也完全没有意义,因为在内部整个缓冲被复制到数组,Unlock时再复制回去。即使用GraphicsStream写顶点数据也很慢,因为会导致大量的Boxing,只有直接用指针写数据才能发挥动态缓冲的优势。

怎么才算做得好,DXSDK里有明确的样例,为了懒人,我帖出来:
    // 用法 1
    // 每次绘制抛弃整个顶点缓冲内容并重新填充几千个顶点
    // 可能包含多个物体,有可能需要按设备状态分几次DrawPrimitive
 
    // 计算需要填充的字节数
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // 抛弃并重新填充
    CONST DWORD dwLockFlags = D3DLOCK_DISCARD;
   
    // 锁定顶点缓冲内存
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( 0, 0, &pBytes, dwLockFlags ) ) )
        return false;
   
    // 将顶点数据复制到顶点缓冲
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // 绘制
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST, 0, nNumberOfVertices/3)

    // 用法 2
    // 对多个物体复用一个顶点缓冲
 
    // 计算需要填充的字节数
    UINT nSizeOfData = nNumberOfVertices * m_nVertexStride;
 
    // 如果顶点缓冲内的剩余空间可以容纳要填充的顶点数量,则指定不覆盖原有数据
    DWORD dwLockFlags = D3DLOCK_NOOVERWRITE;
   
    // 检查顶点缓冲空间是否用光
    if( m_nNextVertexData > m_nSizeOfVB - nSizeOfData )
    {
        // 没有足够的空间,抛弃原有数据重新开始
        dwLockFlags = D3DLOCK_DISCARD;
        m_nNextVertexData = 0;
    }
   
    // 锁定顶点缓冲内存
    BYTE* pBytes;
    if( FAILED( m_pVertexBuffer->Lock( (UINT)m_nNextVertexData, nSizeOfData,
               &pBytes, dwLockFlags ) ) )
        return false;
   
    // 将顶点数据复制到顶点缓冲
    memcpy( pBytes, pVertices, nSizeOfData );
    m_pVertexBuffer->Unlock();
 
    // 绘制
    m_pDevice->DrawPrimitive( D3DPT_TRIANGLELIST,
               m_nNextVertexData/m_nVertexStride, nNumberOfVertices/3)
 
    // 计算下一次的写入位置
    m_nNextVertexData += nSizeOfData;

当然,这只是个正确用法样例,优化起来可还是会面目全非的。如果你的D3D功夫不够剑豪剑圣级别,大可安心地用DrawPrimitiveUP,对付一些杂碎三角面,也大可不必杀鸡用牛刀,用DrawPrimitiveUP剁几下就行了。另外注意测试的时候一定要用硬件模式测,软件模式的结果是完全不同的。

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/Nightmare/archive/2007/07/10/1684490.aspx

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值