轉載請註明原文網址:
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 defaultreturn 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、工程原始碼下載
http://files.cnblogs.com/xianyunhe/DoubleBuffer.rar