在 初探 C# GPU 通用計算技術 中,我使用 Accelerator 編寫了一個簡單的 GPU 計算程序。也簡單看了一些 Brahma 的代碼,從它的 SVN 最新代碼看,Brahma 要轉移到使用 OpenCL.Net 作為底層了,于是也去網上搜索了一下,發現了 OpenCL.Net 和另一個相關的項目 OpenCLTemplate。
?
看了一些它的代碼,頗像 DirectCompute 的風格,其 GPU 程序是標準 C 代碼,所以編寫和閱讀也容易一些,而 Host 程序是 C# 的,把 GPU 代碼字符串傳給編譯器進行編譯,然后就可以在 C# 對它進行調用,并且取回結果了。
?
安裝了 ati-stream-sdk-v2.1-vista-win7-64,折騰了一下它的例子程序 FirstOpenCLProgram,運行時會拋出一個 InvalidContext 的異常,到它的論壇去問,版主建議我安裝 ati 最新 driver 先,雖然覺得本本剛買沒幾天,應該驅動比較新,還是去安裝了最新的驅動,果然不再報異常了。只是如果直接引用 OpenCLTemplate 下的 OpenCL.NET.dll 和 OpenCLTemplate.dll 的話,在初始化的時候會報空指針;而引用 FirstOpenCLProgram 下的這兩個 dll 的話,則初始化時會閃現好幾個控制臺窗口,但是后續都是正常的。
?
既然正常了,就還是以上次那個程序,來看看 OpenCL 的方式,會不會有更大的速度提升。
?
使用 OpenCL 的程序代碼如下:
?
?

private const int GridSize = 1024; private readonly float[] _map;private const string Code = @" __kernel void Test(__global float* v1) {int i = get_global_id(0);float p = v1[i];v1[i] = p * p * p / 4 + 194; }"; private readonly CLCalc.Program.Kernel _test; private readonly CLCalc.Program.Variable _vmap; private readonly CLCalc.Program.Variable[] _args; private readonly int[] _workers;public Form1() {InitializeComponent();_map = new float[GridSize * GridSize];for (int y = 0; y < GridSize; y++){for (int x = 0; x < GridSize; x++){_map[x * GridSize + y] = x * y;}}CLCalc.InitCL();CLCalc.Program.Compile(new[] { Code });_test = new CLCalc.Program.Kernel("Test");_vmap = new CLCalc.Program.Variable(_map);_args = new[] { _vmap };_workers = new[] { GridSize * GridSize };Render(); }private void Start_Click(object sender, EventArgs e) {var stopwatch = new Stopwatch();stopwatch.Start();_test.Execute(_args, _workers);_vmap.ReadFromDeviceTo(_map);var time = stopwatch.ElapsedMilliseconds;this.Text = time.ToString();Render(); }private void Render() {var workingBitmap = new Bitmap(pictureBox1.Width, pictureBox1.Height);for (int y = 0; y < pictureBox1.Height; y++){for (int x = 0; x < pictureBox1.Width; x++){workingBitmap.SetPixel(x, y, Color.FromArgb(-0x1000000 | (int)_map[x * 2 * GridSize + y * 2]));}}pictureBox1.Image = workingBitmap; }
?
?
運行程序,點擊 4 次按鈕,顯示圖形和前兩個程序相同,說明程序運算正常,4 次時間為:8、8、7、8。比使用 Accelerator 的程序速度也快了 5 倍以上。
?
因為是標準 C 程序,所以我們的自由度很大,我也在 OpenCL 下實現了一下 Life 游戲,C# 部分的代碼就不貼了,GPU 代碼如下:
?
?

#define width 512 #define length 262144int GetValue(__global int* v, int index) {if(index < 0 || index >= length){return 0;}return v[index] == 0 ? 0 : 1; };__kernel void Test(__global int* v) {int i = get_global_id(0);int topLeft = GetValue(v, i - width - 1);int top = GetValue(v, i - width);int topRight = GetValue(v, i - width + 1);int left = GetValue(v, i - 1);int current = GetValue(v, i);int right = GetValue(v, i + 1);int bottomLeft = GetValue(v, i + width - 1);int bottom = GetValue(v, i + width);int bottomRight = GetValue(v, i + width + 1);int liveNeighbors = topLeft + top + topRight + left + right + bottomLeft + bottom + bottomRight;if(current > 0){v[i] = ((liveNeighbors < 2) || (liveNeighbors > 3)) ? 0 : 255;}else{v[i] = (liveNeighbors == 3) ? 255 : 0;} };
?
?
很長時間不用 C,有很多像函數聲明順序等規則都忘了,好的一點是 OpenCL.Net 中還提供了 OpenCLCodeChecker,用來進行代碼檢測,同時也在側邊欄里提供了語言幫助,只是它的檢測稍嫌弱智,代碼稍微復雜,提示的錯誤位置很奇怪,有時候告知編譯出錯,Log 里面卻沒有任何錯誤信息。不過總體來說,幫助還是很大就是了。
?
這個 Life 程序,有一個小 Bug,在于沒有判斷超出右邊界的代碼,所以如果左邊有生物,右邊雖然原來沒有生物,也會無中生有 :)
?
總的來說,用 OpenCL.Net 編程,感覺還是很愉快的。