User Tools

Site Tools

blog:2024-09-20_001



2024-09-30 Share: GDI速度再進化

Local Backup

  • 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佔不到便宜)

TAGS

  • 11 person(s) visited this page until now.

Permalink blog/2024-09-20_001.txt · Last modified: 2024/09/30 16:01 by jethro

oeffentlich