作者erspicu (.)
看板C_Sharp
標題[心得] GDI速度再進化
時間Thu Oct 22 17:02:34 2015
又研究出一些心得分享
https://dl.dropboxusercontent.com/u/61164954/project/RenderingTest/index.html
C#一般開發模式風格並不像是C++開發視窗程式需要動到很多底層的WIN32 API,
或是太過於複雜跟系統有關的觀念,但發展到一個需求,如果一些怪怪的需求多了,
C#一定會有無法勝任的地方,不然就是靠wrapper.不然就是靠pinvoke,
去存取C/CC++ dll或是win32 api.
特別提到一塊是高速rendering需求,C#用的GDI+是無法勝任的...
尤其在畫面開始慢慢變得大張後.
這時候很多人也許就開始會想借重opengl或是directx去處理,
但還有一個東西叫GDI,這東西一定得靠WIN32 API去做,用pinvoke,
如果你的需求是2d影像,快速播放,而不像是開發遊戲還需要用到一些功能特性,
後來我覺得gdi比opengl或是directx好用,只是看你的用法.
不過像這種native api的使用其實跟c#沒有真正的直接關係就是,
反來會是mfc之類的開發比較需要.
這邊說到gdi(不是c#原來用的gdi+),一般c#用gdi,首先就是一個BitMap物件,
建立Graphics , 建立GDI物件, 到最後顯示
public static void DrawImage(ref Graphics grDest, ref Bitmap grSrcBitmap)
{
grSrc = Graphics.FromImage(grSrcBitmap);
hdcDest = grDest.GetHdc();
hdcSrc = grSrc.GetHdc();
hBitmap = grSrcBitmap.GetHbitmap();
hOldObject = SelectObject(hdcSrc, hBitmap);
BitBlt(hdcDest, 0, 0, grSrcBitmap.Width, grSrcBitmap.Height,
hdcSrc, 0, 0, 0x00CC0020U);
if (hOldObject != IntPtr.Zero) SelectObject(hdcSrc, hOldObject);
if (hBitmap != IntPtr.Zero) DeleteObject(hBitmap);
if (hdcDest != IntPtr.Zero) grDest.ReleaseHdc(hdcDest);
if (hdcSrc != IntPtr.Zero) grSrc.ReleaseHdc(hdcSrc);
}
這中間有好幾步驟虛要新的記憶體,產生物件,到釋放.....
步驟多又慢 (但即使如此還是比gdi+強...)
一直覺得這種複製建立的過程不太合理....BitMap也慢...
所以就直接把 bitmap 資料寫入到 BitMap的記憶體中,
少了好幾個步驟
public unsafe static void DrawImageHighSpeed()
{
SetDIBits(hdcDest, hBitmap, 0, (uint)h, data_ptr, ref info, DIB_RGB_COLORS);
BitBlt(hdcDest, 0, 0, w , h , hdcSrc, 0, 0, 0x00CC0020U);
}
但要用這方式虛要先做一個初始化,當確定不再坐rendering工作後,
也得自己釋放一下記憶體(c#管不到它自己以外的部分...)
public unsafe static void initHighSpeed(ref Graphics _grDest, int width,
int height, uint[] data)
{
w = width;
h = height;
_Bitmap = new Bitmap(width, height);
grSrc = Graphics.FromImage(_Bitmap);
grDest = _grDest;
hdcDest = grDest.GetHdc();
hdcSrc = grSrc.GetHdc();
hBitmap = _Bitmap.GetHbitmap();
hOldObject = SelectObject(hdcSrc, hBitmap);
info = new BITMAPINFO();
info.bmiHeader = new BITMAPINFOHEADER();
info.bmiHeader.biSize = (uint)Marshal.SizeOf(info.bmiHeader);
info.bmiHeader.biWidth = w;
info.bmiHeader.biHeight = h;
info.bmiHeader.biPlanes = 1;
info.bmiHeader.biBitCount = 32;
info.bmiHeader.biCompression = BitmapCompressionMode.BI_RGB;
info.bmiHeader.biSizeImage = (uint)(w * h * 4);
fixed (uint* dptr = data)
{ data_ptr = (IntPtr)dptr;}
}
public unsafe static void freeHighSpeed()
{
if (hOldObject != IntPtr.Zero) SelectObject(hdcSrc, hOldObject);
if (hBitmap != IntPtr.Zero) DeleteObject(hBitmap);
if (hdcDest != IntPtr.Zero) grDest.ReleaseHdc(hdcDest);
if (hdcSrc != IntPtr.Zero) grSrc.ReleaseHdc(hdcSrc);
try { _Bitmap.Dispose(); }
catch { }
}
最後再進化一次....
有沒有辦法直接把自己的data array寫入到圖型裝置記憶體?
有的...而且是最快的方式
public unsafe static void DrawImageHighSpeedtoDevice()
{
SetDIBitsToDevice(hdcDest, 0, 0, (uint)w, (uint)h, 0, 0, 0,
(uint)h, data_ptr, ref info, DIB_RGB_COLORS);
}
一個步驟不拖泥帶水...(不過這種方式開始得做一點初始化工作,但只有第一次需要
要更新畫面讀寫一下自己的data arry call SetDIBitsToDevice 重刷一下就好....
這rendering的模式破1000fps以上....(data array已經準備號,單純刷畫面的速度)
800*600畫面下可以刷的速度
100內 GDI+
200~300fps 從c#BitMap物件
900~1000fps data array刷到bitmap記憶體中然後rendering
1500~1600fps 直接把data array刷到裝置記憶體
1024*768狀況下
gdi+剩下 40多fps ...
直接把data array刷到裝置記憶體可達到近900fps
最慢的從c#BitMap物件 有150fps左右
data array刷到bitmap記憶體中然後rendering 550fps左右
如果你的需要只是一次又一次產生簡單的2d影像畫面刷上去,
沒牽涉到像是sprite的控制遊戲需求,這就夠好用了....
重點是省包directx相關wrapper,精簡扼要...
(directx使用非常麻煩...而且如果單這種狀況來說directx佔不到便宜)
--
※ 發信站: 批踢踢實業坊(ptt.cc), 來自: 60.248.56.181
※ 文章網址: https://www.ptt.cc/bbs/C_Sharp/M.1445504557.A.837.html
※ 編輯: erspicu (60.248.56.181), 10/22/2015 17:13:41
推 stu87616: 推 10/23 12:51
推 Litfal: 有種回到VB6時代的感覺ww 10/23 12:51
→ Litfal: 主要還是簡單影像GDI+足矣,複雜影像:需要繪製多個物件/ 10/23 12:54
推 Litfal: 坐標系轉換/遮罩(這個我記得GDI有)/一些resize算法(雖然 10/23 12:58
→ Litfal: GDI+也不強)回去用GDI重新寫過,應該會搞死人XD 10/23 12:58
推 zel: 推 10/23 21:29
推 dreamnook: 推 10/24 10:44
推 name2name2: 推 10/25 21:07
推 Harper34: 現在還有其他選擇WPF 10/25 21:59
推 KanoLoa: 大推 10/25 22:53
推 frank6780: 推 10/29 03:09
推 sunneo: great 10/13 13:23