轉載請註明原文網址:
http://www.cnblogs.com/xianyunhe/archive/2011/11/20/2255811.html
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);
int iWidth = bitmap.bmWidth;
int iHeight = 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
return TRUE;
//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、工程源代碼下載