[英]Speeding up transform calculations
我正在編寫一個OpenGL3 2D引擎。 目前,我正在努力解決瓶頸問題。 因此,AMD Profiler的以下輸出: http : //h7.abload.de/img/profilerausa.png
數據是使用數千個精靈制作的。
然而,在50,000個精靈上,testapp已經無法以5 fps的速度使用。
這表明,我的瓶頸是我使用的轉換功能。 這是相應的功能: http : //code.google.com/p/nightlight2d/source/browse/NightLightDLL/NLBoundingBox.cpp#130
void NLBoundingBox::applyTransform(NLVertexData* vertices)
{
if ( needsTransform() )
{
// Apply Matrix
for ( int i=0; i<6; i++ )
{
glm::vec4 transformed = m_rotation * m_translation * glm::vec4(vertices[i].x, vertices[i].y, 0, 1.0f);
vertices[i].x = transformed.x;
vertices[i].y = transformed.y;
}
m_translation = glm::mat4(1);
m_rotation = glm::mat4(1);
m_needsTransform = false;
}
}
我無法在着色器中執行此操作,因為我正在同時批處理所有精靈。 這意味着,我必須使用CPU來計算變換。
我的問題是:解決這個瓶頸的最佳方法是什么?
我不使用任何線程atm,所以當我使用vsync時,我也會獲得額外的性能,因為它等待屏幕完成。 這告訴我應該使用線程。
另一種方法是使用OpenCL嗎? 我想避免使用CUDA,因為據我所知它只能在NVIDIA顯卡上運行。 那正確嗎?
后腳本:
如果您願意,可以在此處下載演示:
http://www63.zippyshare.com/v/45025690/file.html
請注意,這需要安裝VC ++ 2008,因為它是運行探查器的調試版本。
我要做的第一件事就是在你進入for循環之前將旋轉和變換矩陣連接成一個矩陣......這樣你就不會在每個for循環上計算兩個矩陣乘法和一個向量; 相反,你只會乘以一個向量和矩陣。 其次,您可能希望展開展開循環然后使用更高的優化級別進行編譯(在g++
我至少會使用-O2
,但我不熟悉MSVC,因此您必須自己翻譯該優化級別)。 這樣可以避免代碼中的分支可能產生的任何開銷,尤其是在緩存刷新時。 最后,如果你還沒有研究過它,那么在你處理向量時檢查一些SSE優化。
更新 :我將添加一個涉及線程的最后一個想法...當你進行線程時基本上管道你的頂點。 例如,假設您有一台具有八個可用CPU線程的計算機(即具有超線程的四核)。 為頂點管道處理設置六個線程,並使用非鎖定單用戶/生成器隊列在管道的各個階段之間傳遞消息。 每個階段將轉換六個成員頂點數組的單個成員。 我猜這些六個成員的頂點數組中有一堆,所以在流中通過管道設置,你可以非常有效地處理流,並避免使用互斥鎖和其他鎖定信號等。有關快速非鎖定單生產者/消費者隊列的更多信息, 請參閱我的答案 。
更新2 :你只有一個雙核處理器...所以轉儲管道的想法,因為它會遇到瓶頸,因為每個線程爭奪CPU資源。
我無法在着色器中執行此操作,因為我正在同時批處理所有精靈。 這意味着,我必須使用CPU來計算變換。
這聽起來很可疑,就像你做的過早優化一樣,假設批處理是你可以做的最重要的事情,因此你構建你的渲染器來進行最少數量的繪制調用。 而現在它又回來咬你了。
你需要做的是沒有更少的批次。 您需要擁有正確數量的批次。 當你放棄GPU頂點轉換以支持CPU轉換時,你知道你已經過分了。
正如Datenwolf建議的那樣,你需要進行一些實例化才能在GPU上重新獲得轉換。 但即使這樣,你也需要撤消一些你在這里過度批量處理的事情。 你還沒有談到你正在渲染什么樣的場景(頂部有精靈的瓷磚地圖,大型粒子系統等),所以很難知道建議什么。
此外,GLM是一個很好的數學庫,但它不是為最大性能而設計的。 如果我需要每幀在CPU上轉換300,000個頂點,通常不會使用它。
循環中的賦值可能是個問題,但我對這個庫並不熟悉。 將它移到for循環之外,手動執行字段分配可能會有所幫助。 在環路外移動轉換也會有所幫助。
編輯:
這更符合我的想法。
// Apply Matrix
glm::vec4 transformed;
glm::mat4 translation = m_rotation * m_translation;
for ( int i=0; i<6; i++ )
{
transformed.x = vertices[i].x;
transformed.y = vertices[i].y;
transformed.z = vertices[i].z;
transformed.w = 1.f; // ?
/* I can't find docs, but assume they have an in-place multiply
transformed.mult(translation);
// */
vertices[i].x = transformed.x;
vertices[i].y = transformed.y;
}
也許,只是可能,賦值是讓編譯器不要內聯或展開。 我有點猜測乘法很大,足以將其從指令緩存中刪除。 實際上,如果你開始談論緩存的大小,你就不會在許多平台上具有彈性。
你可以嘗試復制一些堆棧並制作更多更小的循環。
glm::vec4 transformed[6];
for (size_t i = 0; i < 6; i++) {
transformed[i].x = vertices[i].x;
transformed[i].y = vertices[i].y;
transformed[i].z = vertices[i].z;
transformed.w = 1.f; // ?
}
glm::mat4 translation = m_rotation * m_translation;
for (size_t i = 0; i < 6; i++) {
/* I can't find docs, but assume they have an in-place multiply
transformed.mult(translation);
// */
}
for (size_t i = 0; i < 6; i++) {
vertices[i].x = transformed[i].x;
vertices[i].y = transformed[i].y;
}
正如Jason所說,手動展開這些循環可能很有趣。
不過,我真的不認為你會看到任何這些變化都會有一個數量級的改進。
我懷疑調用這個函數比使這個函數更快更重要。 事實上你有這個函數需要轉換檢查這個函數讓我覺得這可能是相關的。
當你在低級代碼中遇到這樣的高級別問題時,你最終只是盲目地調用這種方法一遍又一遍地認為它是免費的。 無論你對變換需求變換的假設是否真實,都可能非常不正確。
實際情況是,您應該只是調用此方法一次。 當您想要applyTransform時,您應該applyTransform。 你不應該叫applyTransform時,你可能要applyTransform。 接口應該是合同,對待它們。
如果你堅持在CPU上進行計算,你應該自己做數學計算。
現在,你在2D環境中使用4x4矩陣,其中一個2x2矩陣用於旋轉,一個簡單的矢量用於翻譯就足夠了。 這是4次乘法和4次旋轉加法,以及兩次翻譯補充。
如果你絕對需要兩個矩陣(因為你需要結合平移和旋轉),它仍然會比你現在的要少得多。 但是你也可以通過移動矢量的位置,旋轉然后再將它移回來“手動”組合這兩個,這可能比乘法快一點,盡管我不確定。
與那些4x4矩陣現在所做的操作相比,這要少得多。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.