2017-03-08 89 views
0

我正在嘗試改進一些我已實現的簡單DirectX渲染代碼。我的想法是隻在必要時更新渲染流水線,因爲我的理解是儘可能減少流水線修改的次數是有益的。我的意思是體現在以下僞代碼:瞭解DirectX管道優化

ID3D11VertexShader *t_shader = getVertexShader(); 
ID3D11DeviceContext->VSSetShader(t_shader, nullptr, 0); 
// Do some other processing/pipeline setup without modifying t_shader 
ID3D11DeviceContext->VSSetShader(t_shader, nullptr, 0); 
ID3D11DeviceContext->Draw(10, 0); 

這是低效的,因爲我們調用VSSetShader兩次當着色器並沒有改變。這是一種過度簡化,但希望你能得到我來自哪裏,我的基本理解是這些不必要的綁定/調用是低效的?

如果是這種情況,那麼是否可以在兩個單獨的ID3D11DeviceContext :: Draw調用之間進行以下優化? (再次僞所以請原諒缺少的步驟,並承擔所有我們需要做的是設置一個頂點&像素着色器連同拓撲我們得出前):

void Object1::Draw() { 
    ID3D11VertexShader *t_vs = ShaderMgr::vertexShader1(); 
    ID3D11DeviceContext->VSSetShader(t_vs, nullptr, 0); 

    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader1(); 
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0); 

    ID3D11DeviceContext->IASetPrimitiveTopology(ID3D11_PRIMITIVE_TOPOLOGY_LINELIST); 
    ID3D11DeviceContext->Draw(m_vertexCount, 0); 
} 

void Object2::Draw() { 
    ID3D11VertexShader *t_vs = ShaderMgr::vertexShader1(); 
    ID3D11DeviceContext->VSSetShader(t_vs, nullptr, 0); 

    // Use a different pixel shader to Object1 
    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader2(); 
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0); 

    ID3D11DeviceContext->IASetPrimitiveTopology(ID3D11_PRIMITIVE_TOPOLOGY_LINELIST); 
    ID3D11DeviceContext->Draw(m_vertexCount, 0); 
} 

兩個平局之間的唯一區別呼叫是使用不同的像素着色器。那麼下面是可能的優化還是每個繪製調用都會有效地重置管道?

void Object1::Draw() { 
    // Removed common set code 
    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader1(); 
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0); 
    ID3D11DeviceContext->Draw(m_vertexCount, 0); 
} 

void Object2::Draw() { 
    // Removed common set code 
    ID3D11PixelShader *t_ps = ShaderMgr::pixelShader2(); 
    ID3D11DeviceContext->PSSetShader(t_ps, nullptr, 0); 
    ID3D11DeviceContext->Draw(m_vertexCount, 0); 
} 

void drawObjects() { 
    // Common states amongst object1 and object2 
    ID3D11VertexShader *t_vs = ShaderMgr::vertexShader1(); 
    ID3D11DeviceContext->VSSetShader(t_vs, nullptr, 0); 
    ID3D11DeviceContext->IASetPrimitiveTopology(ID3D11_PRIMITIVE_TOPOLOGY_LINELIST); 

    m_object1->draw(); 

    // Don't bother setting the vs or topology here 

    m_object2->draw(); 
} 

任何反饋/信息將不勝感激。

+1

您可以將設備封裝在一個可以進行狀態緩存以捕獲冗餘的類中。通常情況下,性能增益,除非真正壞的事情做得很少,當你擺脫這些調用時,無論如何,因爲驅動程序通常緩存並且已經測試了不需要的狀態改變。 – galop1n

+0

這就是我現在要實現的目標,設計一個只在必要時才改變狀態的管道類,儘管在初始繪製調用之後我看到了問題,但管道的「狀態」似乎有已被重置,我必須重新設置所有內容,例如InputTopology,頂點緩衝區等。 – TheRarebit

+1

您可以看看DirectX12如何封裝管道狀態(管道狀態對象)。我並不是說你實際上使用了DirectX 12,除非你已經是DirectX 11的專家用戶,但是這個設計反映了現代GPU硬件的偏好。 –

回答

0

只是發佈了一個我自己的問題的答案,因爲我發現了一個錯誤,在我的測試代碼中混淆了這個問題,希望這可以幫助其他任何看到的人。

我的困惑是,在我的測試代碼中,我只有一個單一的對象,我正在渲染一個簡單的平面。它使用的唯一資源是頂點緩衝區,頂點着色器和像素着色器。我嘗試添加上面提到的優化,儘量減少ID3D11DeviceContext調用的次數。對於這個簡單的對象,ID3D11DeviceContext調用例如對我來說似乎是明智的。 VSSetShader,PSSetShader等應該只需要調用一次,因爲這是唯一正在渲染的對象。然而,事實並非如此,因爲一旦網格消失並且再也沒有渲染過,網格就會被渲染。

在RenderDoc的幫助下,我能夠捕獲一個渲染框架,並注意到有兩個繪製調用正在進行,當我只是期待一個。我忘記了我通過DIrectXTK創建了一個SpriteFont和SpriteBatch類,用於寫出我的攝像頭位置進行調試。這個調用修改了管道狀態,而繞過我的管道類(這是控制這些優化),我沒有意識到。這意味着當網格第二次渲染時,管道處於不正確的狀態。

所以事實證明,這些優化是可能的,並且作爲繪製調用的結果,流水線不會被清除。所以如果你有類似上面的例子那麼它足以在調用之間調用一次上下文調用。我還了解到,渲染調試器中內置的調試工具(如RenderDoc或Visual Studios)對於追蹤這些類型的問題至關重要。