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

一、双缓冲作用

双缓冲甚至是多缓冲,在许多情况下都很有用。一般需要使用双缓冲区的地方都是由于“生产者”和“消费者”供需不一致所造成的。这样的情况在很多地方后可能会发生,使用多缓冲可以很好的解决。我举几个常见的例子:

例 1. 在网络传输过程中数据的接收,有时可能数据来的太快来不及接收导致数据丢失。这是由于“发送者”和“接收者”速度不一致所致,在他们之间安排一个或多个缓冲区来存放来不及接收的数据,让速度较慢的“接收者”可以慢慢地取完数据不至于丢失。

 例2. 再如,计算机中的三级缓存结构:外存(硬盘)、内存、高速缓存(介于CPU和内存之间,可能由多级)。从左到右他们的存储容量不断减小,但速度不断提升,当然价格也是越来越贵。作为“生产者”的 CPU 处理速度很快,而内存存取速度相对CPU较慢,如果直接在内存中存取数据,他们的速度不一致会导致 CPU  能力下降。因此在他们之间又增加的高速缓存来作为缓冲区平衡二者速度上的差异。

例3. 在图形图像显示过程中,计算机从显示缓冲区取数据然后显示,很多图形的操作都很复杂需要大量的计算,很难访问一次显示缓冲区就能写入待显示的完整图形数据,通常需要多次访问显示缓冲区,每次访问时写入最新计算的图形数据。而这样造成的后果是一个需要复杂计算的图形,你看到的效果可能是一部分一部分地显示出来的,造成很大的闪烁不连贯。而使用双缓冲,可以使你先将计算的中间结果存放在另一个缓冲区中,但全部的计算结束,该缓冲区已经存储了完整的图形之后,再将该缓冲区的图形数据一次性复制到显示缓冲区。

例1 中使用双缓冲是为了防止数据丢失,例2 中使用双缓冲是为了提高 CPU 的处理效率,而例3使用双缓冲是为了防止显示图形时的闪烁延迟等不良体验。

二、双缓冲原理

这里,主要以双缓冲在图形图像显示中的应用做说明。

上面例3中提到了双缓冲的主要原理,这里通过一个图再次理解一下:

图 1  双缓冲示意图

注意,显示缓冲区是和显示器一起的,显示器只负责从显示缓冲区取数据显示。我们通常所说的在显示器上画一条直线,其实就是往该显示缓冲区中写入数据。显示器通过不断的刷新(从显示缓冲区取数据),从而使显示缓冲区中数据的改变及时的反映到显示器上。

这也是显示复杂图形时造成闪烁的原因,比如你现在要显示从屏幕中心向外发射的一簇射线,你开始编写代码用一个循环从0度开始到360度,每隔一定角度画一条从圆心开始向外的直线。你每次画线其实是往显示缓冲区写入数据,如果你还没有画完,显示器就从显示缓冲区取数据显示图形,此时你看到的是一个不完整的图形,然后你继续画线,等到显示器再次取显示缓冲区数据显示时,图形比上次完整了一些,依次下去直到显示完整的图形。你看到图形不是一次性完整地显示出来,而是每次显示一部分,从而造成闪烁。

原理懂了,看下 demo 就知道怎么用了。下面先介绍 Win32 API 和 C# 中如何使用双缓冲,其他环境下由于没有用到所以没写,等用到了再在下面补充,不过其他环境下过程也基本相似。

三、双缓冲使用 (Win32 版本)

[cpp] view plaincopyprint?

  1. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  2. {
  3. HDC hDC, hDCMem;
  4. HBITMAP hBmpMem, hPreBmp;
  5. switch (message)
  6. {
  7. case WM_PAINT:
  8. hDC = BeginPaint(hWnd, &ps);
  9. /* 创建双缓冲区 */
  10. // 创建与当前DC兼容的内存DC
  11. hDCMem = CreateCompatibleDC(hDC);
  12. // 创建一块指定大小的位图
  13. hBmpMem = CreateCompatibleBitmap(hDC, rect.right, rect.bottom);
  14. // 将该位图选入到内存DC中,默认是全黑色的
  15. hPreBmp = SelectObject(hDCMem, hMemBmp);
  16. /* 在双缓冲中绘图 */
  17. // 加载背景位图
  18. hBkBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
  19. hBrush = CreatePatternBrush(hBkBmp);
  20. GetClientRect(hWnd, &rect);
  21. FillRect(hDCMem, &rect, hBrush);
  22. DeleteObject(hBrush);
  23. /* 将双缓冲区图像复制到显示缓冲区 */
  24. BitBlt(hDC, 0, 0, rect.right, rect.bottom, hDCMem, 0, 0, SRCCOPY);
  25. /* 释放资源 */
  26. SelectObject(hDCMem, hPreBmp);
  27. DeleteObject(hMemBmp);
  28. DeleteDC(hDCMem);
  29. EndPaint(hWnd, &ps);
  30. break;
  31. }
  32. }

[cpp] view plain copy print?

  1. LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
  2. {
  3. HDC hDC, hDCMem;
  4. HBITMAP hBmpMem, hPreBmp;
  5. switch (message)
  6. {
  7. case WM_PAINT:
  8. hDC = BeginPaint(hWnd, &ps);
  9. /* 创建双缓冲区 */
  10. // 创建与当前DC兼容的内存DC
  11. hDCMem = CreateCompatibleDC(hDC);
  12. // 创建一块指定大小的位图
  13. hBmpMem = CreateCompatibleBitmap(hDC, rect.right, rect.bottom);
  14. // 将该位图选入到内存DC中,默认是全黑色的
  15. hPreBmp = SelectObject(hDCMem, hMemBmp);
  16. /* 在双缓冲中绘图 */
  17. // 加载背景位图
  18. hBkBmp = LoadBitmap(hInst, MAKEINTRESOURCE(IDB_BITMAP1));
  19. hBrush = CreatePatternBrush(hBkBmp);
  20. GetClientRect(hWnd, &rect);
  21. FillRect(hDCMem, &rect, hBrush);
  22. DeleteObject(hBrush);
  23. /* 将双缓冲区图像复制到显示缓冲区 */
  24. BitBlt(hDC, 0, 0, rect.right, rect.bottom, hDCMem, 0, 0, SRCCOPY);
  25. /* 释放资源 */
  26. SelectObject(hDCMem, hPreBmp);
  27. DeleteObject(hMemBmp);
  28. DeleteDC(hDCMem);
  29. EndPaint(hWnd, &ps);
  30. break;
  31. }
  32. }

使用 Win32 版本时注意释放资源,释放顺序与创建顺序相反。我在使用过程中不小心遗漏了一句上面的 "DeleteObject(hMemBmp);"导致图形显示一段时间后就卡死了,查看内存使用发现内存随时间推移飙升,加上上面这句代码后,就没这个问题了。这也再次提醒我们释放资源是多么重要,成对编程的习惯是多么重要。

图 2  处理几次WM_PAINT消息后内存变化图

在使用过程中,如果想更新使用双缓冲区显示的区域,可以使用 InvalidateRect(hWnd, &rect, FALSE); ,这里要注意第三个参数一定要设置成 FALSE ,第三个参数表示更新第二个参数指定的区域时是否擦除背景,因为使用双缓冲技术时是直接复制整个缓冲区数据到显示缓冲区,因此无论原有缓冲区里面有什么都会被覆盖,因此第三个参数设置成 
FALSE 有助于提高新能。更主要的原因是,如果先擦除原有缓冲区,会导致中间有一瞬间显示缓冲区被清空(显示为默认背景色),然后等到复制了双缓冲区的数据后再显示新的图像,这将导致闪烁!这与使用双缓冲的本意相违背,所以要注意这一点。

四、双缓冲使用 (C# 版本)

[csharp] view plaincopyprint?

  1. public void Show(System.Windows.Forms.Control control)
  2. {
  3. Graphics gc = control.CreateGraphics();
  4. // 创建缓冲图形上下文 (类似 Win32 中的CreateCompatibleDC)
  5. BufferedGraphicsContext dc = new BufferedGraphicsContext();
  6. // 创建指定大小缓冲区 (类似 Win32 中的 CreateCompatibleBitmap)
  7. BufferedGraphics backBuffer = dc.Allocate(gc, new Rectangle(new Point(0, 0), control.Size));
  8. /* 像使用一般的 Graphics 一样绘图 */
  9. Pen pen = new Pen(Color.Gray);
  10. foreach (Step s in m_steps)
  11. {
  12. gc.DrawLine(pen, s.Start, s.End);
  13. }
  14. // 将双缓冲区中的图形渲染到指定画布上 (类似 Win32 中的)BitBlt
  15. backBuffer.Render(control.CreateGraphics());
  16. }

[csharp] view plain copy print?

  1. public void Show(System.Windows.Forms.Control control)
  2. {
  3. Graphics gc = control.CreateGraphics();
  4. // 创建缓冲图形上下文 (类似 Win32 中的CreateCompatibleDC)
  5. BufferedGraphicsContext dc = new BufferedGraphicsContext();
  6. // 创建指定大小缓冲区 (类似 Win32 中的 CreateCompatibleBitmap)
  7. BufferedGraphics backBuffer = dc.Allocate(gc, new Rectangle(new Point(0, 0), control.Size));
  8. /* 像使用一般的 Graphics 一样绘图 */
  9. Pen pen = new Pen(Color.Gray);
  10. foreach (Step s in m_steps)
  11. {
  12. gc.DrawLine(pen, s.Start, s.End);
  13. }
  14. // 将双缓冲区中的图形渲染到指定画布上 (类似 Win32 中的)BitBlt
  15. backBuffer.Render(control.CreateGraphics());
  16. }

其他版本后续用到时再补充。

时间: 2024-10-03 22:49:43

双缓冲(Double Buffer)原理和使用的相关文章

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

原文出自:http://blog.csdn.net/xiaohui_hubei/article/details/16319249 一.双缓冲作用 双缓冲甚至是多缓冲,在许多情况下都很有用.一般需要使用双缓冲区的地方都是由于"生产者"和"消费者"供需不一致所造成的.这样的情况在很多地方后可能会发生,使用多缓冲可以很好的解决.我举几个常见的例子: 例 1. 在网络传输过程中数据的接收,有时可能数据来的太快来不及接收导致数据丢失.这是由于"发送者"和&

双缓冲技术讲解

笔者介绍:姜雪伟,IT公司技术合伙人,IT高级讲师,CSDN社区专家,特邀编辑,畅销书作者,国家专利发明人;已出版书籍:<手把手教你架构3D游戏引擎>电子工业出版社和<Unity3D实战核心技术详解>电子工业出版社等. CSDN视频网址:http://edu.csdn.net/lecturer/144 首先要搞清楚计算机运行原理,计算机载运行时是将将最大的任务分解成多个任务,然后一个接一个地执行. 一个典型的例子,每个游戏引擎必须解决的问题是渲染. 当游戏画出用户看到的世界时,比如

GDI双缓冲绘图

一.简介 在进行复杂图形绘制时,若直接在屏幕DC上进行绘制,则会出现明显的闪烁.闪烁产生的原因是当绘制的图形较为 复杂时,图形绘制过程中就被刷新到屏幕上,导致结果断断续续地显示出来.双缓冲绘图的原理是在另开辟一块内存用于绘制,当所有绘制工作完成后将内存数据一 次性拷贝到屏幕上. 双缓冲绘图步骤: 创建兼容DC(CreateCompatibleDC) 创建兼容位图(CreateCompatibleBitmap) 将兼容位图选入兼容DC(SelectObject) 在兼容DC中进行绘制工作 将兼容D

STM32的bulk双缓冲传输速度的讨论,硬件的坑永远填不完

详情:http://bbs.21ic.com/forum.php?mod=viewthread&tid=109584 USB 1.0的最高12Mbps. USB 2.0的高速模式480Mbps,全速模式12Mbps,低速模式1.5Mbps 而是设置STM32端的USART的波特率.PC与STM32传输速度是以USB1.1的理论速度传输的,是不能设置的. 接收到数据,置NAK->将缓冲区数据拷贝到用户区(用户处理过程)->发ACK通知主机完成了完整的接收可以发送下一个->主机发送下

控制台双缓冲技术

简介 双缓冲技术主要使用在绘图方面,不过,借鉴双缓冲技术的原理,可以让你的控制台程序优势更加优秀的体验,例如你编写的控制台游戏,往往需要不停地刷新屏幕,这时候会造成非常严重的问题-----屏幕闪烁!这极大的降低了用户体验(虽然说没什么用户吧,但这并不妨碍我们追求完美嘛~),双缓冲就可以消除这种闪烁 控制台双缓冲技术原理: 背景知识:你所看到的黑色界面所显示的东西 都是在"屏幕缓冲区"中显示的,我们能看的这个缓冲区,叫做"当前(激活的)屏幕缓冲区",我们可以创建其他缓

双缓冲技术(Double Buffering)(1、简介和源代码部分)

这一节实在是有些长,翻译完后统计了一下,快到2w字了.考虑到阅读的方便和网络的速度,打算把这节分为5个部分,第一部分为双缓冲技术的一个 简介和所有的代码,如果能够看懂代码,不用看译文也就可以了.第二部分为Plotter控件的公有函数的实现,第三部分为Plotter的事件处理函数的 实现,第四部分为Plotter控件的私有函数实现,第五部分为辅助类PlotSettings的实现. 这里给出一些常用的中英文对照(不一定准确,我这样用的): Rubber band(橡皮筋线,或者橡皮线), pixma

WTL-双缓冲(double buffer)绘图

WTL中有两个Mix-in类: CDoubleBufferImpl和CDoubleBufferWindowImpl,用于创建双缓冲绘图窗口,用法非常简单. 下面创建了一个普通的WTL窗口类,在窗口的客户区中有大量的绘图工作,使用CDoubleBufferImpl类来消除绘图时的闪烁现象: const COLORREF WHITE_COLOR = RGB(255,255,255); const COLORREF BLUE_COLOR = RGB(0,0,255); class CMainWindo

Win32双缓冲画图原理

网上有许多文章讲述了如何使用Visual C++程序实现双缓冲,都是用C++面向对象语言写的,可能对很多没有接触过面向对象语言的C语言初学者来说理解起来有些困难,并且有些好心人也只是把源代码贴上去,不做注释,这就使读者读起来更费劲了.    在这里,我会就每一条语句作出解释.其中有一个地方比较有趣,值得讨论(见下文).好了,我们首先看一下双缓冲的基本原理: 一.双缓冲原理及图解 (1)定义设备描述表及位图句柄    HDC hMemDC;    HBITMAP hBitmap;(2)创建一个与窗

OpenGL学习03_双缓冲DoubleBuffering

双缓冲的是什么? 我们看电视时,看到的屏幕称为OSD层,也就是说,只有在OSD层上显示图像我们才能看到.现在,我需要创建一个虚拟的.看不见但是可以在上面画图(比如说画点.线)的OSD层,我称之为offscreen(后台缓冲区).这个offscreen存在于内存中,我们在上面画图,这个offscreen上面的东西可以显示在OSD层上,需要一个创建这个offscreen的函数,返回这个offscreen的句柄(整型指针).宽度.高度.指向新建offscreen数据缓冲区的指针,该缓冲区是一个在函数外