為DrawPrimitiveUP(DrawUserPrimitive)洗冤

來源:互聯網
上載者:User
為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剁幾下就行了。另外注意測試的時候一定要用硬體模式測,軟體模式的結果是完全不同的。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.