2

我曾经在我的 Win7 系统上注意到一个问题,认为这是一个 DWM 错误,因为它在重新启动后得到了修复。但现在我意识到它正在其他人的系统上发生(作为默认行为),这也是 Surface Pro 上的正常行为。

如何重现问题:使用 GDI 实现一个基本的套索系统。定义一个由鼠标控制的矩形,当矩形发生变化时,使旧的和新的无效(或使两个矩形的并集无效,无论是作为新矩形还是复杂区域,都无所谓,“错误”无论如何仍然显示)。

期间wm_paint,您只需擦除背景并绘制矩形(它必须是矩形轮廓,如果它是填充矩形,问题将不可见)。如果你想确定这不是一个闪烁的问题,你可以做双缓冲(相信我不是)。

所以你会看到,如果你有一个像我这样的系统(桌面 Win7 带有 geforce,aero on),是一个普通的套索系统,没有比显示器自己的重影。在其他系统上(如 Surface Pro,定义一个完全已知的系统),您会看到,当您向外扩展套索时,套索的边界消失了。有点像 LCD 重影,但更明显。

现在,不要使套索的矩形无效,而是尝试使整个窗口无效。在那里,不再有重影。

我发现并不是“修复”它的失效,而是 GDI 访问。您也可以使整个矩形无效,但只绘制套索的区域,仍然重影。但是,如果您绘制套索区域并在窗口的每个角上绘制一个小像素,就不会再出现重影了。

DWM 中肯定有一些东西,可能从 1.1 版开始,它使用了最后一次 GDI 访问的边界框的某种缓存,并且出于某种奇怪的原因,最后一个边界框内的内容将立即出现在屏幕上,而新的部分将至少延迟 1 帧。

这非常糟糕,因为它破坏了每个人都使用的非常基本的窗口失效,而且我还没有找到任何方法来修复它(当然,除了使整个窗口失效,但这很愚蠢,而且这是一个问题会影响整个 GDI,因此您在任何地方的视觉效果都很差)。

同样,它最有可能在 DWM 1.1 中,我认为您无法在 Vista 中获得它,但我不确定。我也不知道为什么它在我的桌面上不这样做,可能它取决于显卡的驱动程序。

所以如果有人碰巧知道更多关于这...

4

2 回答 2

5

对此的更新。我没有找到任何干净的解决方案,但是一个可行的黑客。

首先,我还发现这个“错误”似乎会影响所有系统,只是方式不同。

使用 DwmGetCompositionTimingInfo,一次可以制作垂直同步的 GDI 动画。这是理论上的,因为在实践中它不会起作用,因为同样的“错误”,即使在不受我上面描述的明显影响的系统上也是如此。DWM 将简单地决定在没有足够的刷新时不刷新任何内容,这将导致在使用 ScrollWindowEx 滚动窗口时跳帧并且没有足够的像素无效。

那么解决方案是什么?愚弄 DWM 认为它必须立即交换缓冲区,因为这就是整个问题的所在。在进行 GDI 操作时,可能会认为结果会出现在 DWM 的下一次 vblank-synced 刷新时,但事实并非如此。有些部分会,有些会延迟。所以诀窍是强制 DWM 交换,这是我发现的:

-首先,DWMFlush 不这样做(GDIFlush 也不这样做)。无论如何,DWMflush 或多或少是一个 WaitForVBlank

-DWMSetPresentParameters 似乎也不允许这样做,尽管我并没有太在意那个功能,因为它已经在 Windows 8 中消失了

- 当有足够的像素可以交换时,DWM 似乎会刷新,但它似乎也被分区,很可能是宽但短的矩形(这就是它在 Surface Pro 上的样子——但不是在我的桌面上)。是否与 VRAM 的孔径或分割成小纹理有关,我不知道,真的,也许有人知道?

那么什么对我有用:告诉 GDI 刷新垂直条纹,1 像素宽,相距约 500 像素,遍布整个屏幕。如果您只在屏幕的某些部分上这样做,您仍然会在其他部分上闪烁。它也可以使用水平条纹,但是你需要更多的水平条纹,这就是为什么我相信分割是在宽的、短的矩形中完成的。在欺骗 DWM 时,您可以看到这些矩形。

哪些 GDI 函数有效?许多都这样做,但它们的 CPU 使用率并不相同。首先,忘记 GetPixel,它可以工作,但 CPU 使用率显然非常高。其次,GDI 似乎足够智能,可以检测可以作为命令缓冲的内容,因此使用空画笔填充矩形或绘制空文本不会强制 DWM 刷新。起作用的是使用空垂直条纹的 BitBlt 或 AlphaBlend。CPU使用率仍然可以,即使它距离整个屏幕不远。它必须在顶级窗口而不是桌面上完成。

为 DC 创建 Direct2D 渲染目标并执行 begin/enddraw 也可以,但这是正常的,因为它会强制刷新整个矩形,因此 CPU 成本更高。

如果有人知道强制刷新 DWM 的更好方法,我想知道。但由于 Windows 8 已经淘汰了大多数有趣的 DWM 功能(幸运的是 DwmGetCompositionTimingInfo 仍然部分有效,或者没有 DirectX 就不可能实现 vsynced 计时器),我不确定是否有更好的方法。

此外,它不必在所有顶级窗口上完成。当 Windows 桌面蓝色套索靠近使条纹无效的顶级窗口时,您可以看到效果在它进入附近的区域后立即停止闪烁(我在上面谈到的分段)。

于 2013-11-28T19:23:37.537 回答
0

以下是关于您的代码的一些与 GDI 相关的通用指针:

  1. 当您调用InvalidateRect API 时,它可能不会立即发送 WM_PAINT 通知,因此像在 TForm1.FormMouseMove 方法中那样连续调用两次 InvalidateRect 肯定会导致这种视觉效果。当您第二次调用它时,第一次重绘尚未处理。

  2. 我不确定 Canvas.Rectangle 究竟“在内部”做了什么或它调用了哪些 API。我假设它使用的是DrawFocusRect,在这种情况下你必须知道它的绘图是异或的,所以在同一个矩形上做两次会擦除它。

好的,如果我在绘制那个选择框,我会这样做:

  1. 仅调用一次InvalidateRect API。为此,您必须计算包含移动前后选择框位置的边界矩形。如果我没记错的话,您可以使用UnionRect API,或者自己计算边界矩形。

  2. 如果避免视觉滞后很重要,我会在 TForm1.FormMouseMove 中完成所有选择框的绘制。您可以通过在窗口句柄上调用GetDC API来获取设备上下文。之后做同样的画。在这种情况下,您不需要任何失效。(抱歉,我不能为您提供 Delphi 的程序。这就是我使用普通 WinAPI 的方式。)

编辑:查看您的C++ 代码后,我能够重现您所描述的视觉闪烁。我还制作了两个 C++ 项目,源自您的原始代码,希望能够解决它:这是一个简单的版本这是一个支持鼠标拖动的版本。他们都没有解决这个问题。

我能够在 Windows 7 桌面上重现闪烁。在做了一些测试之后,我现在确信这个视觉伪影是由显示驱动程序引起的。在我的情况下,桌面有 ATI Radeon 视频卡。这就是我能够得出结论的方式:我在具有 Windows XP 操作系统的不同(旧)桌面上运行了测试可执行文件,并且不存在闪烁。我在产生闪烁的台式计算机上安装了 Windows XP 的虚拟机中运行完全相同的可执行文件。当可执行文件在该虚拟机的 Windows XP 中运行时,会出现闪烁。由于相同的视频驱动程序负责渲染实际桌面和虚拟机中的桌面,因此视频驱动程序中的优化或缓存肯定存在一些“有趣的事情”,这导致了这个工件。

所以,现在的问题是如何解决它。在您的项目的第二次构建中,我添加了代码来渲染窗口的整个客户区,以消除计算错误的可能性,但这并没有帮助。所以在这一点上,你无法通过普通的 API 来解决它。

您的下一步可能应该是这些:

  • 请联系您遇到此问题的显卡的驱动程序制造商。看看他们是否有帮助。我会向他们展示你的 YouTube 视频,并给他们 C++ 项目来重现它。

  • 在 Windows 驱动程序开发论坛上发布问题,最好是针对视频驱动程序开发人员。不幸的是,这是一个正在减少的社区,所以预计会出现很长时间的延误。

  • 在此处更改您问题的标签。添加这些:C、C++、WinAPI、GDI、DWM 并删除您现在拥有的内容。这样,它将对 Win32 开发社区可见,因此您将获得更多视图。

除此之外,祝你好运!这是一个很小的错误(即使有人可以这样称呼它),我怀疑您会认真对待它。尽管这非常令人钦佩,但在我的书中,您正努力使您的软件达到如此完美。

另外,如果您需要我在这方面为您提供支持,我可以这样做。

于 2013-06-27T02:09:06.847 回答