双缓冲(Double Buffer)原理和使用

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()); 
}
相关文章
相关标签/搜索