双缓冲本质是用内存画布换屏幕稳定,根本原因是避免直接在窗口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="/ai/2358" title="面多多"><img
src="https://img.php.cn/upload/ai_manual/001/246/273/176127600344295.png" alt="面多多" onerror="this.onerror='';this.src='/static/lhimages/moren/morentu.png'" ></a>
<div class="aritcle_card_info flexColumn">
<a href="/ai/2358" title="面多多">面多多</a>
<p>面试鸭推出的AI面试训练平台</p>
</div>
<a href="/ai/2358" title="面多多" 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。









