双缓冲本质是用内存画布换屏幕稳定,根本原因是避免直接在窗口HDC绘图导致的刷新不同步撕裂;需在WM_PAINT中动态创建匹配客户区的内存DC与位图,完整绘制后再BitBlt一次性输出。

双缓冲本质是用内存画布换屏幕稳定
闪烁的根本原因是直接在窗口设备上下文(HDC)上绘图,每次 BitBlt 或 TextOut 都会触发局部重绘,而系统刷新节奏和你的绘制节奏不同步,导致人眼看到“撕裂”或“闪白”。双缓冲不神秘——就是先在内存里画完一整帧,再一次性拷过去。
关键判断:你用的是 GDI 还是现代 API?如果还在 Win32 + BeginPaint/EndPaint 流程里硬刚闪烁,那必须自己建兼容 DC 和位图;如果用了 Qt、SDL 或 DirectX,它们底层早封装好了,别重复造轮子。
Win32 GDI 下手动双缓冲的三步铁律
不是加个 CreateCompatibleDC 就完事。漏掉任意一步,照样闪:
- 必须在
WM_PAINT中创建/复用内存 DC 和位图,不能在WM_CREATE里一次性分配——窗口缩放时位图尺寸会错 - 内存位图尺寸必须严格匹配客户区大小(用
GetClientRect,别信GetWindowRect) - 每次
BitBlt必须从内存 DC 拷到PAINTSTRUCT.hdc,而不是直接绘到窗口HDC
典型错误现象:BitBlt 返回 0、画面偏移、缩放后模糊——基本都是尺寸没对齐或 DC 选入失败没检查 SelectObject 返回值。
立即学习“C++免费学习笔记(深入)”;
// 正确节选(WM_PAINT 内)
RECT rc;
GetClientRect(hWnd, &rc);
HDC hdc = BeginPaint(hWnd, &ps);
HDC memDC = CreateCompatibleDC(hdc);
HBITMAP hBmp = CreateCompatibleBitmap(hdc, rc.right - rc.left, rc.bottom - rc.top);
HGDIOBJ oldObj = SelectObject(memDC, hBmp);
<p>// ... 所有绘图操作都对 memDC 做(TextOut, Rectangle 等)</p><div class="aritcle_card flexRow">
<div class="artcardd flexRow">
<a class="aritcle_card_img" href="/xiazai/gongju/2823" title="C++14"><img
src="https://img.php.cn/upload/manual/001/431/639/6ac8b33c327c4749.png" alt="C++14" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/xiazai/gongju/2823" title="C++14">C++14</a>
<p>C++14 对 C++11 的修正与增强版本,适合旧系统维护和较老工具链兼容。</p>
</div>
<a href="/xiazai/gongju/2823" title="C++14" class="aritcle_card_btn flexRow flexcenter"><b></b><span>下载</span> </a>
</div>
</div><p>BitBlt(hdc, 0, 0, rc.right - rc.left, rc.bottom - rc.top, memDC, 0, 0, SRCCOPY);
SelectObject(memDC, oldObj); // 必须还原
DeleteObject(hBmp);
DeleteDC(memDC);
EndPaint(hWnd, &ps);SetClassLong(hwnd, GCL_HBRBACKGROUND, NULL) 不要乱设
很多教程让你清空背景刷来“避免擦除”,结果反而更闪。原因:清空后系统在 WM_ERASEBKGND 里啥也不干,但你的 WM_PAINT 又没覆盖整个客户区(比如留了边距),裸露的旧像素就露出来了。
正确做法:
- 保留默认背景刷(别动
GCL_HBRBACKGROUND) - 在
WM_ERASEBKGND中直接 return 1(告诉系统“我已处理”,避免默认擦除干扰双缓冲节奏) - 确保
WM_PAINT绘制逻辑覆盖全部客户区,不留空白
性能影响:禁用背景擦除能省一次全屏填充,但前提是你的绘制真的完整——否则是拿稳定性换假性能。
Qt / wxWidgets 用户请立刻停手写双缓冲
Qt 的 QWidget::setAutoFillBackground(false) + 重写 paintEvent 默认就是双缓冲;wxWidgets 的 wxPanel 在启用 wxCLIP_CHILDREN 样式后也自动启用。自己再套一层 QPixmap 缓存,反而增加内存拷贝开销,还可能因未监听 resizeEvent 导致缓存位图尺寸错配。
容易被忽略的点:Qt 6 默认启用了原生 OpenGL 后端,此时 QPainter 绘图走 GPU,双缓冲由驱动层管理——你写的内存位图反而成了瓶颈。真要优化,该看的是 QOpenGLWidget 的帧同步设置,不是 QPixmap。

















