在具有 DWM 合成的窗口上使用 GDI 绘图时是不是可以防止撕裂伪影?

Posted

技术标签:

【中文标题】在具有 DWM 合成的窗口上使用 GDI 绘图时是不是可以防止撕裂伪影?【英文标题】:Is it possible to prevent tearing artifacts when drawing using GDI on a window with DWM composition?在具有 DWM 合成的窗口上使用 GDI 绘图时是否可以防止撕裂伪影? 【发布时间】:2014-11-27 12:42:59 【问题描述】:

我正在窗口上使用双缓冲 GDI 绘制动画,在启用 DWM 合成的系统上,并且在屏幕上看到清晰可见的tearing。有没有办法防止这种情况发生?

详情

动画采用相同的图像,并在屏幕上从右到左移动;像素数由当前时间与动画开始时间和结束时间之间的差异决定,以获得应用于整个窗口宽度的完整分数,使用timeGetTime 和1ms resolution。动画在不处理应用消息的情况下循环绘制;它调用(VCL 库)方法Repaint,该方法在内部无效,然后为有问题的窗口调用UpdateWindow,直接使用WM_PAINT 调用消息过程。绘制处理程序的 VCL 实现使用BeginBufferedPaint。绘画本身是双缓冲的。

这样做的目的是获得尽可能高的帧速率,以便在屏幕上获得流畅的动画。 (绘图使用双缓冲来消除闪烁并确保整个图像或帧在任何时候都在屏幕上。它通过调用消息过程直接无效和更新,而不进行其他消息处理。绘画是使用现代技术实现的(例如,用于 Aero 合成的 BeginBufferedPaint。)在其中,绘画是在几个 BitBlt 调用中完成的(一个用于动画的左侧,即在屏幕外移动的东西,另一个用于动画的右侧,即在屏幕上移动的东西。 )

观看动画时,tearing 清晰可见。这发生在 Windows Vista、7 和 8.1 的多个系统上,使用不同的显卡。

我处理此问题的方法是降低绘制速度,或者尝试在再次绘制之前等待 VSync。这可能是错误的方法,所以这个问题的答案可能是“完全做其他事情:X”。如果是这样,那就太好了:)

(我真正想要的是一种让 DWM 为这个特定窗口撰写/仅使用完全绘制的帧的方法。)

我尝试了以下方法,但都没有消除所有可见的撕裂。因此问题是,在使用 DWM 组合时是否可以避免撕裂,如果可以,如何避免?

尝试的方法:

通过GetDeviceCaps(Application.MainForm.Handle, VREFRESH)获取显示器刷新率;休眠 1 / 刷新率毫秒。尽可能快地比绘画略有改进,但可能是一厢情愿。感觉上平滑动画速率稍差。 (调整:正常的Sleep 和使用timeGetTime 的高分辨率旋转等待。)

使用DwmSetPresentParameters 尝试将更新限制在与代码绘制相同的速率。 (变体:大量缓冲区(cBuffer = 8)(无可见效果);指定监视器刷新率 / 1 的源速率并使用上述代码休眠(与尝试休眠方法相同);指定每帧刷新1、10 等(无可见效果);更改源帧覆盖范围(无可见效果。)

以多种方式使用DwmGetCompositionTimingInfo

cFramesPending > 0,旋转; 获取cFrame(帧组合)并在此数字不变的情况下旋转; 获取cFrameDisplayed 并在这不变的情况下旋转; 通过添加 qpcVBlank + qpcRefreshPeriod 来计算睡眠时间,然后当 QueryPerformanceCounter 返回的时间小于此时间时,旋转

所有这些方法也因绘画而变化,然后在再次绘画之前旋转/睡觉;或者相反:睡觉然后画画。

似乎很少有任何可见的效果,很难确定有什么效果,可能只是帧速率较低的结果。没有一个可以防止撕裂,即没有一个可以让 DWM 用窗口 DC 内容的“完整”副本组成窗口。

建议 :)

【问题讨论】:

GDI 陈旧而硬朗,无法达到流畅的动画效果。也许试试 Direct2D? @JonathanPotter 好点。我受到的限制之一是不使用任何类型的 DX - 一个原因是平台的数量和缺乏要求,最终代码需要在其上运行。 (并非所有都是现代 Windows。)也许只有当 Aero 存在时才能使用辅助 DX 代码......?但是,它必须无缝地在 GDI/DX 之间切换,而不会出现视觉故障,因为它会打开/关闭动画。这可能吗? Direct2D 相当慢。您可以防止撕裂,以使用 Direct2D 换取较慢的动画。本质上,Direct2D 只是建立在 Direct3D 之上的支持/实用程序库,它将 2D 渲染调用映射到 D3D 渲染原语。某些操作非常慢(圆角矩形、虚线等),大约比 GDI 渲染慢一个数量级。 @Eldarien:在 Windows 上使用 OpenGL 比使用 DirectX 更合适吗?使用 OpenGL 比使用 DirectX 解决了什么样的问题? OpenGL 如何更好地集成到 DWM 中?如何更容易调试?使用 OpenGL 的建议如何解决这个问题? 我相信您可以通过调用 DwmFlush() 以一种不错的方式同步到视频帧速率。我不确定为什么没有其他人建议这样做。 【参考方案1】:

由于您使用的是BitBlt,请确保您的 DIB 为 4 字节/像素。使用 3 字节/像素,GDI 在 DWM 运行时非常慢,这可能是您撕裂的根源。我遇到的另一个BitBlt 问题,如果你的DIB 比BitBlt 调用要大一些,会花费很长时间。如果您将一个调用拆分为更小的调用,而不是仅绘制一部分数据,它可能会有所帮助。这两项对我的案例都有帮助,只是因为 BitBlt 本身运行速度太慢,从而导致视频伪影。

【讨论】:

以防万一添加:不要忘记只更新脏矩形(GetUpdateRect)而不是整个窗口...

以上是关于在具有 DWM 合成的窗口上使用 GDI 绘图时是不是可以防止撕裂伪影?的主要内容,如果未能解决你的问题,请参考以下文章

在 Windows 10 上使用 DWM API 的 Aero 标题栏问题

在 Qt 中通过 Windows GDI 绘图

使用 Direct2D 在非客户区绘图

GDI+ 多线程绘图

如何使用 GDI 将 RGB 位图绘制到窗口?

GDI 设备环境句柄