[心得] GDI速度再进化

楼主: erspicu (.)   2015-10-22 17:02:34
又研究出一些心得分享
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占不到便宜)
作者: stu87616 (文组工程师)   2015-10-23 12:51:00
作者: Litfal (Litfal)   2015-10-23 12:51:00
有种回到VB6时代的感觉ww主要还是简单影像GDI+足矣,复杂影像:需要绘制多个物件/坐标系转换/遮罩(这个我记得GDI有)/一些resize算法(虽然GDI+也不强)回去用GDI重新写过,应该会搞死人XD
作者: zel (柚植)   2015-10-23 21:29:00
作者: dreamnook (亚龙)   2015-10-24 10:44:00
作者: name2name2 (yang~hi)   2015-10-25 21:07:00
作者: Harper34 (强打少年)   2015-10-25 21:59:00
现在还有其他选择WPF
作者: KanoLoa (卡)   2015-10-25 22:53:00
大推
作者: frank6780 ( 努力赚钱 )   2015-10-29 03:09:00

Links booklink

Contact Us: admin [ a t ] ucptt.com