OpenGL 闪烁/损坏,窗口调整大小和 DWM 处于活动状态

Posted

技术标签:

【中文标题】OpenGL 闪烁/损坏,窗口调整大小和 DWM 处于活动状态【英文标题】:OpenGL flickering/damaged with window resize and DWM active 【发布时间】:2012-05-23 19:44:55 【问题描述】:

我有一个 wxWidgets 应用程序,它有许多子 opengl 窗口。我正在使用我自己的 GL 画布类,而不是 wx 类。窗口共享它们的 OpenGL 上下文。 我不认为它是 wxwidgets 的事实在这里真的很重要。

opengl 窗口是一个窗口的子窗口,它们是彼此的兄弟窗口,包含在选项卡控件中。一种 MDI 风格的界面,但它不是 MDI 窗口。每个都可以单独调整大小。除非启用 Aero 并且 DWM 处于活动状态,否则一切正常。

调整任何窗口(甚至是 opengl 窗口)的大小会导致所有 opengl 窗口偶尔闪烁,并出现陈旧的后备存储视图,其中包含当时屏幕上出现的任何非 opengl 垃圾。这只发生在启用 Aero 的情况下。

我很确定这是 DWM 实际上在其绘图表面后备存储中没有 opengl 内容,并且窗口没有在正确的时刻重新绘制。

我已经尝试了很多方法来解决这个问题,我确实有一个解决方案,但它不是很好,它涉及将带有 glReadPixels 的帧缓冲区读取到 DIB 中,然后在我的 onPaint 例程中将其传送到绘图 DC。仅当 DWM 处于活动状态时才启用此解决方法,但我宁愿根本不必这样做,因为它会稍微损害性能(但在功能强大的系统上并不算太糟糕 - 场景是相对简单的 3d 图)。也不建议将 GDI 和 opengl 混合使用,但令人惊讶的是,这种方法很有效。我现在可以忍受它,但我宁愿不必。无论如何,如果我想截取子窗口的屏幕截图,我仍然必须在 WM_PRINT 中执行此操作,我看不到解决方法。

有人知道更好的解决方案吗?

在有人问之前,我肯定会做到以下几点:

窗口类有 CS_OWNDC WM_ERASEBACKGROUND 什么都不做并返回 TRUE。 双缓冲已启用。 Windows 具有 WS_CLIPSIBLINGS 和 WS_CLIPCHILDREN 窗口样式。 在我的 resize 事件处理程序中,我立即重新绘制窗口。

我试过了:

在像素格式描述符中设置 PFD_SUPPORT_COMPOSITION。 不在绘制处理程序中使用 wxPaintDC 并调用 ::ValidateRect(hwnd, NULL) 代替。 处理 WM_NCPAINT 并排除客户区 通过 DWM API 禁用 NC 绘制 在绘制事件中排除客户区 在缓冲区交换前后调用 glFlush 和/或 glFinish。 在每次绘制事件时使窗口无效(作为测试!) - 仍然 闪烁! 不使用共享的 GL 上下文。 禁用双缓冲。 写入 GL_FRONT_AND_BACK

禁用 DWM 不是一种选择。

据我所知,如果您在 OpenGL 上使用 Direct3D,这甚至是一个问题,尽管我没有对此进行测试,因为它代表了很多工作。

【问题讨论】:

我在使用 MFC MDI 应用程序和 OpenGL 时遇到了类似的问题,其中 MFC 控件有时会在 GL 窗口中留下绘图伪影。这仅在启用 Aero 时发生。同样,我也没有找到满意的解决方案。 使用 glReadPixels 读取的 opengl 内容闪烁到窗口确实可以解决它,但必须有更好的方法。 【参考方案1】:

这是一个远景,但我刚刚自己解决了同样的问题。

出现长镜头部分是因为我们正在对围绕我们的 OpenGL 窗口的无标题组框的轮廓进行所有者绘制(即,制作一个漂亮的小边框),这可能无法描述您的情况。

我们发现导致问题的原因是:

我们一直在使用 RoundRect() 调用(带有 HOLLOW_BRUSH)来绘制组框的轮廓。将其更改为 MoveToEx() 和 LineTo() 调用以确保仅绘制线条并且在组框内没有任何操作可以防止 GDI 尝试意外地重绘控件的全部内容。失效逻辑可能存在差异(或者我们在加载预期的空心画笔时遇到了错误)。我们仍在调查中。

-诺埃尔

【讨论】:

【参考方案2】:

我的应用只有一个 OpenGL 窗口(主窗口),但我在调整窗口大小时遇到​​了一些令人讨厌的 DWM 撕裂问题,我想知道其中一种解决方案是否适合您。

首先,我发现在调整窗口大小期间,至少有两个不同的坏人想要通过修改您的客户区来“帮助”您,然后您才有机会自己更新窗口,从而产生闪烁。

第一个坏人可以追溯到 XP/Vista/7 BitBlt 内的 SetWindowPos(),Windows 在调整窗口大小期间内部执行此操作,并且可以通过涉及拦截 WM_NCCALCSIZE 的技巧或涉及拦截 @ 的其他技巧来消除987654325@.

在 Windows 8/10 中,我们仍然有这个问题,但我们有一个新的坏人,Aero DWM.exe 窗口管理器,当他认为你“落后”更新屏幕。

我怀疑您看到的垃圾像素实际上可能是 DWM 故意且非常糟糕的尝试,以便在等待您绘制时填充“可接受”的内容。我发现 DWM 在对新客户区进行 blit 时会扩展旧客户区数据的边缘像素,这太疯狂了。

不幸的是,我不知道有任何 100% 的解决方案可以阻止 DWM 这样做,但我确实有一个计时技巧可以大大降低它的频率。

有关WM_NCCALCSIZE/WM_WINDOWPOSCHANGING hack 以及 DWM 计时 hack 的源代码,请参阅:

How to smooth ugly jitter/flicker/jumping when resizing windows, especially dragging left/top border (Win 7-10; bg, bitblt and DWM)?

【讨论】:

【参考方案3】:

嗯,也许您遇到了同样的问题:如果您使用的是“新”MFC 它将使用选项卡和窗口拆分器创建和应用程序。

拆分器有一些逻辑(我猜测透明窗口周围的某个地方并绘制 XOR 拆分的行)导致此行为。卸下分离器以确认它已解决 你的问题。如果您需要拆分功能 - 放入不同的拆分器。

此外,选项卡允许停靠并再次拆分具有相同问题的窗口 -- 删除/替换。

祝你好运, 伊戈尔

【讨论】:

不是 wxWidgets 应用程序,还是您在回答 mcmcc 的评论?我认为最终的问题是 DWM 在不同的线程中合成。 我通过替换 Paint 方法中的所有 OpenGL 调用解决了同样的问题。直到星期一我才能访问我的代码,所以我现在不能给你更多细节......至少我使用 VCL,但由于它是相同的问题,它可以是相同的解决方案!

以上是关于OpenGL 闪烁/损坏,窗口调整大小和 DWM 处于活动状态的主要内容,如果未能解决你的问题,请参考以下文章

移动/调整窗口大小时闪烁

使用 OpenGL 和 SDL 处理窗口大小调整

使用 SDL 和 OpenGL 调整窗口大小

opengl:调整大小时如何将对象保留在窗口中

如何在不闪烁的情况下调整 Swing JWindow 的大小?

OpenGL 防止在调整窗口大小时拉伸