VC界面繪製雙緩存

轉載請註明原文網址:

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、工程源代碼下載

http://files.cnblogs.com/xianyunhe/DoubleBuffer.rar

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章