1. 程式人生 > >VC介面繪製雙快取

VC介面繪製雙快取

1、閃屏的問題

在GDI的繪圖系統中,每呼叫一次區域繪圖操作,如FillRect、BitBlt等,圖形顯示系統就會在螢幕中對指定的區域進行一次重新整理操作。如果頻繁的進行區域繪製操作的操作的話,我們就會發現,螢幕會出現閃屏。
使用下面的程式碼對閃屏的問題進行測試,在XP系統閃屏尤其嚴重,在Win7系統,閃屏問題有所改善。Win7系統在繪製效率上有所提升。

void CDoubleBufferView::DrawDirect(CDC* pDC)
{
    ASSERT_VALID(pDC);
 
    /*用漸變色粉刷背景*/
    CRect rect(0,0,0,0);
    CSize szTotal = GetTotalSize();
    for(int i = 0; i <= szTotal.cy; i++)
    {
        rect.left = 0;
        rect.right = szTotal.cx;
        rect.top = i*10;
        rect.bottom = (i+1)*10;
        pDC->FillSolidRect(&rect, RGB((i*10)%255,0,0));
    }
 
    /*繪製圖片*/
    CDC dcPic;
    dcPic.CreateCompatibleDC(pDC);
    CBitmap bmpImport;
    bmpImport.LoadBitmap(IDB_BITMAP1);
    CBitmap* pOldBmp = dcPic.SelectObject(&bmpImport);
    BITMAP bitmap;
    bmpImport.GetBitmap(&bitmap);
    intiWidth = bitmap.bmWidth;
    intiHeight = bitmap.bmHeight;
    pDC->BitBlt(0, 0, iWidth, iHeight,
        &dcPic, 0, 0, SRCCOPY);
    dcPic.SelectObject(pOldBmp);
 
    /*釋放資源*/
    bmpImport.DeleteObject();
    dcPic.DeleteDC();
}


2、雙快取
產生閃屏的原因是類似於多程序之間的通訊問題,每次DC的繪圖操作,都要把相關的顯示資料傳送到顯示卡,顯示卡處理後,在顯示器上顯示。借鑑提升多執行緒之間的通訊效率的解決方法,可通過減少與顯示卡之間的互動次數來提升繪製的效率。這也就是雙快取的思路。雙快取的原理是先把更新操作中所有繪製資料先寫入記憶體,然後再呼叫BitBlt或StretchBlt一次性的把所有資料傳送到顯示卡中。
用一個比喻來說,沒有用雙快取就像老師講課時在黑板上使用粉筆寫板書,學生能看到老師寫板書的整個過程,如果把黑板看做是一個螢幕的話,老師在寫板書的過程,就是一個閃屏的過程。
使用了雙快取,就像老師採用了多媒體教學,老師就可以提前在家把板書用PPT做好,上課時只要一頁一頁的翻PPT就可以了,學生們是看不到PPT製作的過程,也就會有閃屏的問題了。
可採用瞭如下程式碼來實現雙快取。

void CDoubleBufferView::DrawWithBufferInefficient(CDC* pDC)
{
    ASSERT_VALID(pDC);
 
    /*建立記憶體DC*/
    CDC dcMemory;
    dcMemory.CreateCompatibleDC(pDC);
    dcMemory.SetBkColor(pDC->GetBkColor());
     
    /*設定記憶體DC的畫板,大小整個視窗一樣*/
    CSize szTotal = GetTotalSize();
    CBitmap bmpMemory;
    bmpMemory.CreateCompatibleBitmap(pDC,
        szTotal.cx, szTotal.cy);
    dcMemory.SelectObject(&bmpMemory);
     
    /*設定記憶體DC的起始點*/
    dcMemory.SetViewportOrg(0, 0);
     
    /*粉刷背景*/
    dcMemory.FillSolidRect(0, 0, szTotal.cx, szTotal.cy, pDC->GetBkColor());
     
    DrawDirect(&dcMemory);
     
    /*把記憶體DC複製到輸入DC中*/
    pDC->BitBlt(0, 0, szTotal.cx, szTotal.cy,
        &dcMemory, 0, 0, SRCCOPY);
     
    /*釋放資源*/
    bmpMemory.DeleteObject();
    dcMemory.DeleteDC();
}


3、取消擦除背景
當我們使用了上面的雙快取技術後,我們看到閃屏問題有所緩解,但是有些操作仍然會導致閃屏,比如在有滾動條的檢視視窗拖動滾動條時。為什麼會產生這樣的原因呢?先來分析一下介面的繪製原理。
當我們需要對視窗繪製時,可呼叫UpdateWindow、RedrawWindow、Invalidate或InvalidateRect等函式。通過檢視這些函式的MSDN中得知,有些視窗繪製函式中,有一個是否擦除背景的選項。如果要擦除背景,那一次繪製就要進行兩部操作,也就是要跟顯示卡互動兩次,一是擦除背景,一是重新繪製圖形,那雙快取的作用就失去了。
如果我們再繪製圖形的時候,自己對背景進行一次粉刷,也就沒有必要再使用擦除背景,同時也能保證雙快取的效果。
取消擦除北京的方法主要有兩種:
(1)取消重繪時的擦除選項。
如使用Invalidate(FALSE)。
這種方法雖然有效,但是需要對所有的重繪函式進行處理,難以完全取消擦除背景。
(2)截斷擦除訊息。
背景的擦除是通過WM_ERASEBKGND訊息來完成。於是,我們只要截獲了該訊息,就能徹底取消擦除背景。
可在視窗類中為WM_ERASEBKGND提供訊息響應函式,然後直接返回TRUE。

BOOL CDoubleBufferView::OnEraseBkgnd(CDC* pDC)
{
    // TODO: Add your message handler code here and/or call default
     
    returnTRUE;
    //return CScrollView::OnEraseBkgnd(pDC);
}


4、繪製效率的提升
在重新整理介面的時候,重新整理的區域越小,重新整理效率更高,因此,在重新整理介面的時候,我們應該儘量較少不必要的重新整理。作業系統也會對介面的重新整理操作進行優化,如拉動滾動條的時候,並不是對整個介面進行重新整理,而只是對已經無效的區域中換上新的圖形,然後再在螢幕調整圖形區域在介面上的位置。因此,就有一個裁剪區域的概念,在重繪的過程中,只有裁剪區域需要重繪。因此,我們在雙快取中,也只需對裁剪區域重繪。可通過CDC::GetClipBox來獲得裁剪區域的大小。
因此,對雙快取的優化程式碼如下所示:

void CDoubleBufferView::DrawWithBufferEfficient(CDC* pDC)
{
    ASSERT_VALID(pDC);
 
    /*建立記憶體DC*/
    CDC dcMemory;
    dcMemory.CreateCompatibleDC(pDC);
    dcMemory.SetBkColor(pDC->GetBkColor());
     
    /*設定記憶體DC的畫板,大小與輸入DC的裁剪區域一樣*/
    /*只對裁剪區域進行重新繪製*/
    CRect rectClip(0,0,0,0);
    pDC->GetClipBox(&rectClip);
    CBitmap bmpMemory;
    bmpMemory.CreateCompatibleBitmap(pDC,
        rectClip.Width(), rectClip.Height());
    dcMemory.SelectObject(&bmpMemory);
     
    /*設定記憶體DC的起始點*/
    dcMemory.SetViewportOrg(-1*rectClip.left, -1*rectClip.top);
 
    /*粉刷背景*/
    dcMemory.FillSolidRect(&rectClip, pDC->GetBkColor());
 
    DrawDirect(&dcMemory);
 
    /*把記憶體DC複製到輸入DC中*/
    pDC->BitBlt(rectClip.left, rectClip.top, rectClip.Width(), rectClip.Height(),
        &dcMemory, rectClip.left, rectClip.top, SRCCOPY);
     
    /*釋放資源*/
    bmpMemory.DeleteObject();
    dcMemory.DeleteDC();
}


 5、工程原始碼下載