1、雙緩衝做用
雙緩衝甚至是多緩衝,在許多狀況下都頗有用。通常須要使用雙緩衝區的地方都是因爲「生產者」和「消費者」供需不一致所形成的。這樣的狀況在不少地方後可能會發生,使用多緩衝能夠很好的解決。我舉幾個常見的例子:前端
例 1. 在網絡傳輸過程當中數據的接收,有時可能數據來的太快來不及接收致使數據丟失。這是因爲「發送者」和「接收者」速度不一致所致,在他們之間安排一個或多個緩衝區來存放來不及接收的數據,讓速度較慢的「接收者」能夠慢慢地取完數據不至於丟失。編程
例2. 再如,計算機中的三級緩存結構:外存(硬盤)、內存、高速緩存(介於CPU和內存之間,可能由多級)。從左到右他們的存儲容量不斷減少,但速度不斷提高,固然價格也是愈來愈貴。做爲「生產者」的 CPU 處理速度很快,而內存存取速度相對CPU較慢,若是直接在內存中存取數據,他們的速度不一致會致使 CPU 能力降低。所以在他們之間又增長的高速緩存來做爲緩衝區平衡兩者速度上的差別。緩存
例3. 在圖形圖像顯示過程當中,計算機從顯示緩衝區取數據而後顯示,不少圖形的操做都很複雜須要大量的計算,很難訪問一次顯示緩衝區就能寫入待顯示的完整圖形數據,一般須要屢次訪問顯示緩衝區,每次訪問時寫入最新計算的圖形數據。而這樣形成的後果是一個須要複雜計算的圖形,你看到的效果多是一部分一部分地顯示出來的,形成很大的閃爍不連貫。而使用雙緩衝,可使你先將計算的中間結果存放在另外一個緩衝區中,但所有的計算結束,該緩衝區已經存儲了完整的圖形以後,再將該緩衝區的圖形數據一次性複製到顯示緩衝區。網絡
例1 中使用雙緩衝是爲了防止數據丟失,例2 中使用雙緩衝是爲了提升 CPU 的處理效率,而例3使用雙緩衝是爲了防止顯示圖形時的閃爍延遲等不良體驗。this
2、雙緩衝原理spa
這裏,主要以雙緩衝在圖形圖像顯示中的應用作說明。
上面例3中提到了雙緩衝的主要原理,這裏經過一個圖再次理解一下:orm
圖 1 雙緩衝示意圖blog
注意,顯示緩衝區是和顯示器一塊兒的,顯示器只負責從顯示緩衝區取數據顯示。咱們一般所說的在顯示器上畫一條直線,其實就是往該顯示緩衝區中寫入數據。顯示器經過不斷的刷新(從顯示緩衝區取數據),從而使顯示緩衝區中數據的改變及時的反映到顯示器上。內存
這也是顯示覆雜圖形時形成延遲的緣由,好比你如今要顯示從屏幕中心向外發射的一簇射線,你開始編寫代碼用一個循環從0度開始到360度,每隔必定角度畫一條從圓心開始向外的直線。你每次畫線實際上是往顯示緩衝區寫入數據,若是你尚未畫完,顯示器就從顯示緩衝區取數據顯示圖形,此時你看到的是一個不完整的圖形,而後你繼續畫線,等到顯示器再次取顯示緩衝區數據顯示時,圖形比上次完整了一些,依次下去直到顯示完整的圖形。你看到圖形不是一次性完整地顯示出來,而是每次顯示一部分,從而形成閃爍。資源
原理懂了,看下 demo 就知道怎麼用了。下面先介紹 Win32 API 和 C# 中如何使用雙緩衝,其餘環境下因爲沒有用到因此沒寫,等用到了再在下面補充,不過其餘環境下過程也基本類似。
3、雙緩衝使用 (Win32 版本)
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { HDC hDC, hDCMem; HBITMAP hBmpMem, hPreBmp; switch (message) { case WM_PAINT: hDC = BeginPaint(hWnd, &ps); /* 建立雙緩衝區 */ // 建立與當前DC兼容的內存DC hDCMem = CreateCompatibleDC(hDC); // 建立一塊指定大小的位圖 hBmpMem = CreateCompatibleBitmap(hDC, rect.right, rect.bottom); // 將該位圖選入到內存DC中,默認是全黑色的 hPreBmp = SelectObject(hDCMem, hBmpMem); /* 在雙緩衝中繪圖 */ // 加載背景位圖 hBkBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1)); hBrush = CreatePatternBrush(hBkBmp); GetClientRect(hWnd, &rect); FillRect(hDCMem, &rect, hBrush); DeleteObject(hBrush); /* 將雙緩衝區圖像複製到顯示緩衝區 */ BitBlt(hDC, 0, 0, rect.right, rect.bottom, hDCMem, 0, 0, SRCCOPY); /* 釋放資源 */ SelectObject(hDCMem, hPreBmp); DeleteObject(hMemBmp); DeleteDC(hDCMem); EndPaint(hWnd, &ps); break; } }
使用 Win32 版本時注意釋放資源,釋放順序與建立順序相反。我在使用過程當中不當心遺漏了一句上面的 "DeleteObject(hMemBmp);"致使圖形顯示一段時間後就卡死了,查看內存使用發現內存隨時間推移飆升,加上上面這句代碼後,就沒這個問題了。這也再次提醒咱們釋放資源是多麼重要,成對編程的習慣是多麼重要。
圖 2 處理幾回WM_PAINT消息後內存變化圖
在使用過程當中,若是想更新使用雙緩衝區顯示的區域,可使用 InvalidateRect(hWnd, &rect, FALSE); ,這裏要注意第三個參數必定要設置成 FALSE ,第三個參數表示更新第二個參數指定的區域時是否擦除背景,由於使用雙緩衝技術時是直接複製整個緩衝區數據到顯示緩衝區,所以不管原有緩衝區裏面有什麼都會被覆蓋,所以第三個參數設置成 FALSE 有助於提升新能。更主要的緣由是,若是先擦除原有緩衝區,會致使中間有一瞬間顯示緩衝區被清空(顯示爲默認背景色),而後等到複製了雙緩衝區的數據後再顯示新的圖像,這將致使閃爍!這與使用雙緩衝的本意相違背,因此要注意這一點。
4、雙緩衝使用 (MFC 版本)
void CGame2Dlg::OnPaint() { CPaintDC dc(this); // device context for painting CRect rect; GetClientRect(&rect); // 建立內存DC CDC memDC; memDC.CreateCompatibleDC(&dc); // 建立內存位圖 CBitmap bmp; bmp.CreateCompatibleBitmap(&memDC, rect.right - rect.left, rect.bottom - rect.top); // 將位圖選入DC memDC.SelectObject(&bmp); // 繪圖 m_pGameEngine->Show(memDC.m_hDC); // 將後備緩衝區中的圖形拷貝到前端緩衝區 dc.BitBlt(0, 0, rect.right - rect.left, rect.bottom - rect.top, &memDC, 0, 0, SRCCOPY); }
5、雙緩衝使用 (C# 版本)
public void Show(System.Windows.Forms.Control control) { Graphics gc = control.CreateGraphics(); // 建立緩衝圖形上下文 (相似 Win32 中的CreateCompatibleDC) BufferedGraphicsContext dc = new BufferedGraphicsContext(); // 建立指定大小緩衝區 (相似 Win32 中的 CreateCompatibleBitmap) BufferedGraphics backBuffer = dc.Allocate(gc, new Rectangle(new Point(0, 0), control.Size)); gc = backBuffer.Graphics; // 獲取緩衝區畫布 /* 像使用通常的 Graphics 同樣繪圖 */ Pen pen = new Pen(Color.Gray); foreach (Step s in m_steps) { gc.DrawLine(pen, s.Start, s.End); } // 將雙緩衝區中的圖形渲染到指定畫布上 (相似 Win32 中的)BitBlt backBuffer.Render(control.CreateGraphics()); }