使用 GDI+ 减少闪烁 和 C++

发布于 2024-07-07 22:30:57 字数 578 浏览 4 评论 0原文

我在 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);
}

难道我做错了什么? 或者还有其他方法可以实现这一目标吗?

I'm using GDI+ in a C++/MFC application and I just can't seem to avoid flickering whenever the window is resized.

I have already tried these steps:

  • returned TRUE on OnEraseBkGnd();
  • returned NULL on OnCtlColor();
  • used double buffering according to this code:
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);
}

Am I doing something wrong? Or is there another way to achieve this?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

晨光如昨 2024-07-14 22:30:57

为了完全避免闪烁,您需要在屏幕更新之间的时间间隔内完成所有绘图。 Windows 没有提供任何简单的方法来完成普通窗口绘制(Vista 通过 DWM,但即使在运行 Vista 的系统上也不能依赖这一点)。 因此,要最大限度地减少闪烁,您能做的最好的事情就是尽快绘制所有内容(通过增加在刷新周期内完成所有绘制的机会来减少撕裂),并避免过度绘制(绘制部分内容)屏幕,然后在顶部绘制其他内容:存在向用户呈现部分绘制的屏幕的风险)。

让我们讨论一下目前为止介绍的技术:

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

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


  • 双缓冲绘图:通过将实际的屏幕绘图减少为单个BitBLT。 但可能会影响绘图所需的时间:无法使用硬件加速(尽管使用 GDI+,使用任何硬件辅助绘图的机会非常小),必须为每次重绘创建并填充离屏位图,并且每次重绘都必须重新绘制整个窗口。 请参阅有关高效双缓冲的注释

  • 对 BitBlt 使用 GDI 调用而不是 GDI+:这通常是个好主意 - Graphics::DrawImage() 可能会非常慢。 我什至找到了正常的 GDI BitBlt()< /a> 在某些系统上调用速度更快。 尝试一下这个,但前提是先尝试一些其他建议。


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

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

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

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

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

有关高效双缓冲的注意事项

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

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

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

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

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

To completely avoid flicker, you would need to complete all drawing in the interval between screen updates. Windows does not provide any easy means of accomplishing this for normal window painting (Vista provides composite drawing via the DWM, but this cannot be relied on even on systems running Vista). Therefore, the best you can do to minimize flicker is to draw everything as quickly as possible (reduce tearing by increasing your chances of completing all drawing within a refresh cycle), and avoid overdraw (drawing part of the screen and then drawing something else over the top: risks presenting user with a partially-drawn screen).

Let's discuss the techniques presented here so far:

  • Do-nothing OnEraseBkgnd(): helps to avoid over-draw by preventing the invalidated area of the window from being filled with the window's background color. Useful when you will be drawing the entire area again during WM_PAINT handling anyway, as in the case of double-buffered drawing... but see Notes on avoiding overdraw by preventing drawing after your WM_PAINT method.

  • Returning NULL for OnCtlColor(): this shouldn't actually do anything... unless you have child controls on your form. In that case, see Notes on avoiding overdraw by preventing drawing after your WM_PAINT method instead.

  • Double buffered drawing: helps to avoid tearing (and potentially overdraw as well), by reducing the actual on-screen drawing to a single BitBLT. May hurt the time needed for drawing though: hardware acceleration cannot be used (although with GDI+, the chances of any hardware-assisted drawing being used are quite slim), an off-screen bitmap must be created and filled for each redraw, and the entire window must be repainted for each redraw. See Notes on efficient double-buffering.

  • Using GDI calls rather than GDI+ for the BitBlt: This is often a good idea - Graphics::DrawImage() can be very slow. I've even found the normal GDI BitBlt() call to be faster on some systems. Play around with this, but only after trying a few other suggestions first.

  • Avoiding window class styles that force a full redraw on each resize (CS_VREDRAW, CS_HREDRAW): This will help, but only if you don't need to redraw the entire window when size changes.

Notes on avoiding overdraw by preventing drawing prior to your WM_PAINT method

When all or a portion of a window is invalidated, it will be erased and repainted. As already noted, you can skip erasing if you plan to repaint the entire invalid area. However, if you are working with a child window, then you must ensure that parent window(s) are not also erasing your area of the screen. The WS_CLIPCHILDREN style should be set on all parent windows - this will prevent the areas occupied by child windows (including your view) from being drawn on.

Notes on avoiding overdraw by preventing drawing after your WM_PAINT method

If you have any child controls hosted on your form, you will want to use the WS_CLIPCHILDREN style to avoid drawing over them (and subsequently being over drawn by them. Be aware, this will impact the speed of the BitBlt routine somewhat.

Notes on efficient double-buffering

Right now, you're creating a new back-buffer image each time the view draws itself. For larger windows, this can represent a significant amount of memory being allocated and released, and will result in significant performance problems. I recommend keeping a dynamically-allocated bitmap in your view object, re-allocating it as needed to match the size of your view.

Note that while the window is being resized, this will result in just as many allocations as the present system, since each new size will require a new back buffer bitmap to be allocated to match it - you can ease the pain somewhat by rounding dimensions up to the next largest multiple of 4, 8, 16, etc., allowing you to avoid re-allocated on each tiny change in size.

Note that, if the size of the window hasn't changed since the last time you rendered into the back buffer, you don't need to re-render it when the window is invalidated - just Blt out the already-rendered image onto the screen.

Also, allocate a bitmap that matches the bit depth of the screen. The constructor for Bitmap you're currently using will default to 32bpp, ARGB-layout; if this doesn't match the screen, then it will have to be converted. Consider using the GDI method CreateCompatibleBitmap() to get a matching bitmap.

Finally... I assume your example code is just that, an illustrative snippet. But, if you are actually doing nothing beyond rendering an existing image onto the screen, then you don't really need to maintain a back buffer at all - just Blt directly from the image (and convert the format of the image ahead of time to match the screen).

陌路终见情 2024-07-14 22:30:57

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

You might try using old-fashioned GDI rather than GDI+ to write to the DC, especially since you're already buffering the image. Use Bitmap::LockBits to access the raw bitmap data, create a BITMAPINFO structure, and use SetDIBitsToDevice to display the bitmap.

浅紫色的梦幻 2024-07-14 22:30:57

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

请参阅 http://msdn.microsoft.com/en-我们/library/ms633574(VS.85).aspx

Make sure that the window class for the window does not include the CS_VREDRAW and CS_HREDRAW flags in its style.

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

挽容 2024-07-14 22:30:57

您可能会通过使用 Direct3D 来“告诉”您何时发生垂直同步等,从而获得一些牵引力,因此您可以在适当的时候进行 BitBlt/更新。 请参阅GDI 垂直同步以避免撕裂< /em> (尽管在某些情况下将事情简化为一个小的 BitBlt 可能“足够好”)。

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

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

You may get some traction through using Direct3D to "tell" you when vsync's occur, et al, so you can BitBlt/update at a good time. See GDI vsync to avoid tearing (though getting things down to a single small BitBlt may be "good enough" for some cases).

Also note that it appears that GDI BitBlt isn't synchronized with screen vsync. See
Faster than BitBlt
.

Also note that using CAPTUREBLT (which allows you to capture transparent windows) causes the mouse to flicker (if aero is not in use) if used.

夜吻♂芭芘 2024-07-14 22:30:57

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

(我知道这是一个很晚才添加到线程中的内容,但这适用于在我的 Win32 应用程序中寻求减少闪烁时发现它的任何人(像我一样)...)

This link contains some useful info:
http://www.catch22.net/tuts/flicker-free-drawing

(I know this is a very late addition to the thread, but this is for anyone (like me) who found it when looking to reduce flicker in my Win32 app...)

傲娇萝莉攻 2024-07-14 22:30:57

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

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

Are there child windows on the form? The Window manager starts by getting the parent window to erase its background by sending a WM_ERASEBKGND message, THEN it sends a wM_PAINT message - presumably this maps to your wx::OnDraw method. Then it iterates over each child control and gets those to paint themselves.

If this is your scenario... using Vistas new aero look would solve your problem as the aero desktop window manager does window compositing automatically. With the older window manager its a pita.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文