Unity 3D : ComputeShader 全面詳解
https://blog.csdn.net/weixin_38884324/article/details/80570160
前言:
會寫一些簡單的 ComputeShader, 但還是很多人可能還搞不懂
- Dispatch (x, y, z)
- [numthreads (x, y, z)]
- SV_GroupID
- SV_GroupThreadID
- SV_GroupIndex
- SV_DispatchThreadID
這些是啥,今天我來為大家做一個比較全面的說明,畢竟 ComputeShader 的資源還算是比較少的。
另外我當大家已經運行過一些簡單的 ComputeShader 代碼了,只是不了解其中的奧秘。
如果你沒運行過,那可以先到這邊爽一下:
https://blog.csdn.net/weixin_38884324/article/details/79285371
※ 需要注意的是,本篇文章是用圖片的方式來說明,並非使用矩陣的方式來講解,因為我覺得圖片的方式會比較好理解。而且輸入(Texture2D)與輸出(RWTexture2D) 的圖片是同一個解析度。
第一節:
Dispatch (x, y, z) 與 numthreads (x, y, z) 的關係
很多人把 ComputeShader 代碼 Run 起來了,但或多或少會產生一個疑問,就是在
C# 中的 Dispatch
與
ComputeShader 中的 numthreads
是啥關係,感覺其中數字設定有一些奧妙所在 ? 試著亂調其中的數字圖片還會有部分會無法顯示….WTF?
假設你輸入(Texture2D)與輸出(RWTexture2D) 的圖片是同一個解析度,你只是要做一些單像素的計算 (例如轉成灰階),那麼 Dispatch 與 numthreads 要如何設置才能顯示完整的圖片? 而不會缺一角?
其實很簡單,假設圖片解析度是 256 x 128,那麼以下幾種設置都能完整顯示圖片:
Dispatch (k, 256, 128, 1) [numthreads (1, 1, 1)]
Dispatch (k, 32, 16, 1) [numthreads (8, 8, 1)]
Dispatch (k, 256/8, 128/8, 1) [numthreads (8, 8, 1)]
Dispatch (k, 8, 4, 1) [numthreads (32, 32, 1)]
- 1
- 2
- 3
- 4
哇靠,這麼多種 ? 那有甚麼規律呢 ?
有的,你會發現 Dispatch.x * numthreads.x = 256 ( 圖片寬度 ),Dispatch.y * numthreads.y = 128 ( 圖片高度 )。只要 Dispatch * numthreads 是圖片大小,那麼圖片就能完全顯示,不會少漏一個像素。
But ! But ! But !
但是非常重要的事情是,numthreads 使用上是有限制的,numthreads.x * numthreads.y * numthreads.z 必須小於等於 1024
以下是錯誤例子,乖孩子別學:
[numthreads (64, 32, 1)]
[numthreads (16, 16, 16)]
[numthreads (256, 128, 1)]
- 1
- 2
- 3
好了,現在你已經能夠完美顯示圖片了,但是並不瞭解其中的原理,所以下一章開始會更詳細的說明。
第二節:
更深入的了解 Group 與 Thread 的關係
我們第一節使用 Dispatch(x, y, z),其實這就是在設定 Group 數量;而 numthreads(x, y, z) 就是在設定 Thread 數量。
假設我們有一張 4X4的圖像,使用參數 Dispatch(k, 2, 2, 1) 與 [numthreads(2, 2, 1)] 。那麼在 GPU 中劃分就如同下圖 ( 四個 Group, 每個 Group 中又包四個 Thread ):
Group 的
長、寬、高
是由 numthreads 所設定的,同理一個Group的大小是不能超過 1024 ( 見第一節 )。
同理 [numthreads(32, 32, 1)] 的 Group 大小就是 32 * 32 * 1 ( 也許你可以理解 32 * 32 像素 )
然而如果你的 GPU 有 64 核心,而你的 Group 也有 64 個,那麼每個核心將能處理一個 Group。
假設 ThreadSize = ( numthreads.x * numthreads.y * numthreads.z )
那麼不同的 ThreadSize 在不同硬件廠下分配的資源是不同的
建議:
AMD:ThreadSize 使用 64 的倍數 ( wavefront 架構 )
NVIDIA:ThreadSize 使用 32 的倍數 ( SIMD32 (Warp) 架構 )
另外使用 Group 有個好處,就是可以等待所有 Thread 同步。不過不同的 Group 是無法同步的。
如果你要在一個 Group 中同步所有 Thread,你可以用下面這語法
GroupMemoryBarrierWithGroupSync();
- 1
第三節:
SV_GroupID、SV_GroupThreadID、SV_GroupIndex 關係
由上圖可知,我們在 ComputeShader 函式中可以放入四個值,分別是
SV_GroupID : 線程組 ID
SV_GroupThreadID : 線程組內的線程 ID (三維,你可以理解為 Group 內的座標)
SV_GroupIndex : 線程組內的線程 ID (一維)
SV_DispatchThreadID : 唯一ID (你可以理解成整張圖片座標)
- 1
- 2
- 3
- 4
用下圖可以知道他們的座標關係:
看不懂 ? 好吧,我實際 Run 結果應該就明白啦 ! 如下圖:
Group ID 一看就懂 :
來看一下索米 :
Group Thread ID 一看就懂 :
Group Index 一看就懂 :
第四節:
DispatchThreadID
公式:DispatchThreadID = GroupID * numthreads + GroupThreadID
// 敬請期待…
第五節:
性能優化
// 敬請期待…
第六節
原子鎖
// 敬請期待…
–
=第七節
共享內存
// 敬請期待…
groupshared float4 gCache[256]
最大空间为32kb
用 Group Thread ID 索引
結語:
好累哈,花了一整天總算寫完 一 ~ 三節 ( 後面節數慢慢補充 ),也算是自己思緒地整理,雖然一些地方表達得不是很好,但我盡量用最簡單的方式說明,如果你覺得有更好的建議或不完美的地方,歡迎在下面留言。如果你喜歡本文章就按讚吧,如果你要轉載請註明出處吧。
–
參考
參考:
https://blog.csdn.net/YgritteSnow/article/details/54988931