【问题标题】:Reduce flicker with GDI+ and C++使用 GDI+ 和 C++ 减少闪烁
【发布时间】:2010-09-16 21:52:23
【问题描述】:

我在 C++/MFC 应用程序中使用 GDI+,我似乎无法在调整窗口大小时避免闪烁。

我已经尝试了这些步骤:

  • OnEraseBkGnd() 上返回 TRUE;
  • OnCtlColor() 上返回 NULL;
  • 根据此代码使用双缓冲:

void vwView::OnDraw(CDC* pDC) 
{
   CRect rcClient;
   GetClientRect(rcClient);

   Bitmap bmp(rcClient.Width(), rcClient.Height());
   Graphics graphics(&bmp);

   graphics.DrawImage(m_image, rcClient.left, rcClient.top);

   Graphics grph(pDC->m_hDC);
   grph.DrawImage(&bmp, 0, 0);
}

我做错了吗?或者有其他方法可以实现吗?

【问题讨论】:

    标签: c++ windows gdi+


    【解决方案1】:

    为了完全避免闪烁,您需要在屏幕更新的间隔内完成全部绘制。对于普通的窗口绘制,Windows 没有提供任何简单的方法来完成此操作(Vista 通过DWM 提供复合绘制,但即使在运行 Vista 的系统上也不能依赖此功能)。因此,尽量减少闪烁的最佳方法是尽可能快地绘制所有内容( 通过增加在刷新周期内完成所有绘制的机会来减少撕裂),并避免过度绘制(绘制部分屏幕,然后在顶部绘制其他内容:可能会给用户呈现部分绘制的屏幕)。

    让我们讨论一下目前这里介绍的技术:

    • Do-nothing OnEraseBkgnd():通过防止窗口的无效区域被窗口的背景颜色填充来帮助避免过度绘制。当您将在 WM_PAINT 处理 anyway 期间再次绘制整个区域时很有用,例如双缓冲绘图的情况...但请参阅 关于避免过度绘制的注意事项通过在您的 WM_PAINT 方法之后阻止绘图。

    • OnCtlColor() 返回 NULL:这实际上不应该做任何事情...除非您的表单上有子控件。在这种情况下,请参阅在您的 WM_PAINT 方法 之后通过防止绘制来避免过度绘制的注意事项。

    • 双缓冲绘图:通过将实际的屏幕绘图减少到单个BitBLT,有助于避免撕裂(以及潜在的过度绘制)。但是可能会影响绘图所需的时间:不能使用硬件加速(尽管使用 GDI+,使用任何硬件辅助绘图的机会都很渺茫),必须为每次重绘创建和填充屏幕外位图,并且每次重绘都必须重绘整个窗口。请参阅关于高效双缓冲的说明

    • 对 BitBlt 使用 GDI 调用而不是 GDI+:这通常是个好主意 - Graphics::DrawImage() 可能会非常慢。我什至发现普通 GDI BitBlt() 调用在某些系统上更快。尝试一下,但只有在先尝试了其他一些建议之后。

    • 避免在每次调整大小时强制完全重绘的窗口类样式(CS_VREDRAWCS_HREDRAW:这会有所帮助,但前提是您当大小改变时,不需要重绘整个窗口。

    通过在 WM_PAINT 方法之前防止绘制来避免过度绘制的注意事项

    当一个窗口的全部或部分失效时,它将被擦除并重新绘制。如前所述,如果您打算重新绘制整个无效区域,则可以跳过擦除。 但是,如果您正在使用子窗口,则必须确保父窗口不会同时擦除您的屏幕区域。 WS_CLIPCHILDREN 样式应在所有父窗口上设置 - 这将防止子窗口(包括您的视图)占用的区域被绘制。

    通过在 WM_PAINT 方法之后防止绘制来避免过度绘制的注意事项

    如果您的表单上有任何子控件,您将需要使用 WS_CLIPCHILDREN 样式来避免绘制它们(并随后被它们过度绘制。请注意,这会在一定程度上影响 BitBlt 例程的速度。

    关于高效双缓冲的说明

    现在,每次视图绘制自己时,您都会创建一个新的后台缓冲区图像。对于较大的窗口,这可能表示正在分配和释放大量内存,导致严重的性能问题。我建议在您的视图对象中保留一个动态分配的位图,并根据需要重新分配它以匹配您的视图大小。

    请注意,在调整窗口大小时,这将导致与当前系统一样多的分配,因为每个新大小都需要分配一个新的后台缓冲区位图来匹配它 - 您可以通过以下方式减轻痛苦将维度四舍五入到 4、8、16 等的下一个最大倍数,这样您就可以避免在每次微小的大小变化时重新分配。

    请注意,如果自上次渲染到后台缓冲区后窗口的大小没有改变,则在窗口无效时不需要重新渲染它 - 只需将已经渲染的 Blt图像到屏幕上。

    另外,分配一个与屏幕的位深度相匹配的位图。您当前使用的Bitmap 的构造函数将默认为 32bpp,ARGB 布局;如果这与屏幕不匹配,则必须对其进行转换。考虑使用 GDI 方法CreateCompatibleBitmap() 来获取匹配的位图。

    最后...我假设您的示例代码就是这样,一个说明性的 sn-p。但是,如果您实际上除了将现有图像渲染到屏幕上之外什么都不做,那么您根本不需要维护后台缓冲区 - 只需直接从图像中 Blt(并提前将图像的格式转换为匹配屏幕)。

    【讨论】:

    • 所以“将实际屏幕绘图减少到单个 BitBLt”实际上不足以防止所有撕裂?就像 windows 可能允许在屏幕刷新中间出现单个 bitblt,即使它是单个操作也允许撕裂?
    • 当然。传输实际数据需要时间,多长时间取决于特定机器的设置。
    【解决方案2】:

    您可以尝试使用老式 GDI 而不是 GDI+ 来写入 DC,尤其是因为您已经在缓冲图像。使用 Bitmap::LockBits 访问原始位图数据,创建 BITMAPINFO 结构,并使用 SetDIBitsToDevice 显示位图。

    【讨论】:

      【解决方案3】:

      确保窗口的窗口类在其样式中不包含 CS_VREDRAW 和 CS_HREDRAW 标志。

      http://msdn.microsoft.com/en-us/library/ms633574(VS.85).aspx

      【讨论】:

      【解决方案4】:

      您可能会通过使用Direct3D 来“告诉”您何时发生 vsync 等,从而获得一些牵引力,这样您就可以适时进行 BitBlt/更新。请参阅 GDI vsync to avoid tearing(尽管在某些情况下,将事情归结为一个小的 BitBlt 可能“足够好”)。

      另请注意,GDI BitBlt 似乎与屏幕垂直同步不同步。请参阅 Faster than BitBlt

      另请注意,使用 CAPTUREBLT(允许您捕获透明窗口)会导致鼠标在使用时闪烁(如果未使用 aero)。

      【讨论】:

      【解决方案5】:

      此链接包含一些有用的信息: http://www.catch22.net/tuts/flicker-free-drawing

      (我知道这是对线程的一个很晚的补充,但这是为任何人(如我)在我的 Win32 应用程序中寻找减少闪烁时发现它的人...)

      【讨论】:

      • 这或多或少只是一个链接回答which is discouraged。截至 2018 年 6 月 11 日,这看起来几乎是一个仅限死链接的答案,尽管读者应该注意该网站尚未“维护中”all that long (it was valid in 2017)。就我个人而言,我觉得这很好,该页面有有用的建议。希望它会很快回来。无论如何,我建议在您的答案中包含页面中的内容(用您自己的话)。
      【解决方案6】:

      窗体上是否有子窗口?窗口管理器首先通过发送 WM_ERASEBKGND 消息让父窗口擦除其背景,然后发送 wM_PAINT 消息 - 大概这映射到您的 wx::OnDraw 方法。然后它遍历每个子控件并让它们自己绘制。

      如果这是您的情况...使用 Vistas 新的 aero 外观将解决您的问题,因为 aero 桌面窗口管理器会自动进行窗口合成。使用较旧的窗口管理器,它是一个皮塔饼。

      【讨论】:

        猜你喜欢
        • 2011-08-26
        • 1970-01-01
        • 2018-12-22
        • 1970-01-01
        • 2015-07-15
        • 1970-01-01
        • 2011-03-27
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多