繁体   English   中英

如何在调整窗口大小时平滑丑陋的抖动/闪烁/跳跃,尤其是拖动左/上边框(Win 7-10;bg、bitblt 和 DWM)?

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

问题:当我抓住我的 Windows 应用程序的调整大小边框,尤其是顶部或左边框,并调整窗口大小时,窗口的内容会在我拖动时“实时”调整大小,但它们以一种可怕的方式调整大小,看起来像即使是最新手的用户也有一个明显的错误:窗口对面边缘的内容我正在疯狂地前后拖动抖动/闪烁/跳跃。 根据情况,该现象可能如下所示:

  • 当我们放慢或停止拖动时,内容似乎会离开窗口边缘并弹回
  • 似乎被拉进窗口的内容,被不同颜色的边框间歇性地取代,通常是黑色或白色
  • 一个非常丑陋的“双重图像”,内容的两个重叠副本被移动的距离与我们拖动的速度/速度成正比

一旦我停止拖动,丑陋的现象就会停止,但在拖动过程中它使应用程序看起来很业余和不专业。

毫不夸张地说,这个 Windows 问题已经让数以千计的应用程序开发人员抓狂了

以下是该现象的两张示例图片,为Roman Starkov提出的相关问题精心准备

抖动:
示例 1:抖动

边界:
示例 2:边框

另一个例子显示了Kenny Liu的邪恶“双重图像”现象(注意快速闪光):

示例 2:双重图像

任务管理器现象的另一个示例视频在这里

问题:任何遇到此问题的开发人员都会很快发现,至少有 30 个 Stack Overflow 问题,有些是最近的,有些是 2008 年的,其中充满了听起来很有希望但很少奏效的答案。 现实情况是,这个问题有很多原因,现有的 Stack Overflow 问题/答案从未使更广泛的上下文变得清晰。 这个问题试图回答:

  • 这种丑陋的抖动/闪烁/跳跃的最可能原因是什么?
  • 我怎么知道我看到的是哪个原因?
  • 这是特定于特定图形驱动程序的原因还是 Windows 的一般原因?
  • 我如何解决每个原因? 一个应用程序可以修复它吗?

(这是一个规范的问答,用于解释窗口调整大小抖动的所有不同原因,以便用户可以确定是哪个原因导致了他们的问题并解决了它。正如答案所解释的,上面的所有排列(本机/托管,窗口/dialog, XP-10) 归结为只有两个根本原因,但确定您拥有的是哪个是棘手的部分。)

这个问题的范围对于这个问题的范围,这种现象发生在:

  • 本机 Win32 和托管 .NET/WPF/Windows 窗体应用程序
  • 普通的 Win32 窗口和 Win32 对话框窗口
  • Windows 版本,包括 XP、Vista、7、8 和 10(但请参阅下文了解多种原因的黑暗真相)

不在这个问题的范围内:

  • 如果您的应用程序有一个或多个子窗口(子 HWND),则此问题中的信息对您很有用(因为我们将描述的引起混蛋的BitBlts与父窗口一起应用于您的子窗口),但在窗口调整大小期间您还有一个额外的问题需要处理,这超出了本问题的范围:您需要让所有子窗口自动移动并与父窗口同步。 对于此任务,您可能需要BeginDeferWindowPos/DeferWindowPos/EndDeferWindowPos ,您可以在此处此处找到有关它们的信息

  • 这个问题假设如果你的应用程序使用 GDI、DirectX 或 OpenGL 绘制到一个窗口,那么你已经在wndproc中实现了一个WM_ERASEBKGND处理程序, wndproc返回WM_ERASEBKGND是 Windows 3.1 的神秘 Windows 残余,在WM_PAINT之前给出在您绘制窗口之前,您的应用程序有机会“擦除窗口的背景”......嗯。 如果您让WM_ERASEBKGND消息进入DefWindowProc() ,这将导致您的整个窗口在每次重绘时都被绘制为纯色,通常为白色,包括在实时窗口调整大小期间发生的重绘。 结果是丑陋的全窗口闪烁,但不是我们在这个问题中谈论的抖动/闪烁/跳跃类型。 拦截WM_ERASEBKGND立即修复了这个问题。

  • 这个问题主要是关于通过用鼠标拖动窗口边框来实时调整大小。 然而,这里写的大部分内容也适用于当应用程序使用SetWindowPos()手动调整一次性窗口大小时您可以看到的丑陋工件。 但它们不太明显,因为它们只在屏幕上轻弹一瞬间,而不是长时间拖动。

  • 这个问题不是关于如何让你的特定于应用程序的绘图代码运行得更快,尽管这样做在许多情况下可能是解决丑陋的调整大小问题的方法。 如果您的应用程序在实时窗口调整大小期间确实需要大量时间来重新显示其内容,请考虑优化您的绘图代码,或者至少在调整大小期间通过拦截WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE消息来切换到更快、质量较低的绘图模式以进行检测调整大小。

  • 如果您的应用程序在调整大小期间根本无法调整大小(例如,在调整大小期间“挂起”,特别是如果它是使用 GLFW 或其他库的 OpenGL),请参阅这些其他问题,这些问题解释了在拖动期间 Microsoft 在WM_SYSCOMMAND内的可怕的嵌套/模态事件循环: 这里特别是这个很好的答案这里这里这里这里

第 2 部分:识别和修复 Windows 调整大小问题

注意:您想先阅读第 1 部分,以使此答案有意义。

这个答案不会解决您所有的调整大小问题。

它组织了来自其他帖子的仍然可用的想法,并添加了一些新颖的想法。

微软的 MSDN 上根本没有记录这种行为,下面是我自己的实验和查看其他 StackOverflow 帖子的结果。

2a. 来自SetWindowPos() BitBlt和背景填充的调整大小问题

以下问题发生在所有版本的 Windows 上 它们可以追溯到 Windows 平台 (Windows XP) 上实时滚动的最初几天,并且仍然存在于 Windows 10 上。在较新的 Windows 版本中,其他调整大小问题可能会叠加在此问题之上,如下文所述。

以下是与单击窗口边框并拖动该边框的典型会话相关联的 Windows 事件。 缩进表示嵌套wndproc (由于已发送(未发布)消息或由于上述问题中“不在此问题范围内”中提到的可怕的 Windows 模式事件循环而嵌套):

msg=0xa1 (WM_NCLBUTTONDOWN)  [click mouse button on border]
  msg=0x112 (WM_SYSCOMMAND)  [window resize command: modal event loop]
    msg=0x24 (WM_GETMINMAXINFO)
    msg=0x24 (WM_GETMINMAXINFO) done
    msg=0x231 (WM_ENTERSIZEMOVE)      [starting to size/move window]
    msg=0x231 (WM_ENTERSIZEMOVE) done
    msg=0x2a2 (WM_NCMOUSELEAVE)
    msg=0x2a2 (WM_NCMOUSELEAVE) done

  loop:
    msg=0x214 (WM_SIZING)             [mouse dragged]
    msg=0x214 (WM_SIZING) done
    msg=0x46 (WM_WINDOWPOSCHANGING)
      msg=0x24 (WM_GETMINMAXINFO)
      msg=0x24 (WM_GETMINMAXINFO) done
    msg=0x46 (WM_WINDOWPOSCHANGING) done
    msg=0x83 (WM_NCCALCSIZE)
    msg=0x83 (WM_NCCALCSIZE) done
    msg=0x85 (WM_NCPAINT)
    msg=0x85 (WM_NCPAINT) done
    msg=0x14 (WM_ERASEBKGND)
    msg=0x14 (WM_ERASEBKGND) done
    msg=0x47 (WM_WINDOWPOSCHANGED)
      msg=0x3 (WM_MOVE)
      msg=0x3 (WM_MOVE) done
      msg=0x5 (WM_SIZE)
      msg=0x5 (WM_SIZE) done
    msg=0x47 (WM_WINDOWPOSCHANGED) done
    msg=0xf (WM_PAINT)                    [may or may not come: see below]
    msg=0xf (WM_PAINT) done
goto loop;

    msg=0x215 (WM_CAPTURECHANGED)       [mouse released]
    msg=0x215 (WM_CAPTURECHANGED) done
    msg=0x46 (WM_WINDOWPOSCHANGING)
      msg=0x24 (WM_GETMINMAXINFO)
      msg=0x24 (WM_GETMINMAXINFO) done
    msg=0x46 (WM_WINDOWPOSCHANGING) done
    msg=0x232 (WM_EXITSIZEMOVE)
    msg=0x232 (WM_EXITSIZEMOVE) done  [finished size/moving window]
  msg=0x112 (WM_SYSCOMMAND) done
msg=0xa1 (WM_NCLBUTTONDOWN) done

每次拖动鼠标时,Windows 都会为您提供上面循环中显示的一系列消息。 最有趣的是,你得到WM_SIZING然后WM_NCCALCSIZE然后WM_MOVE/WM_SIZE ,那么你可能(低于更多)收到WM_PAINT

请记住,我们假设您提供了一个返回 1 的WM_ERASEBKGND处理程序(请参阅上面问题中的“不在此问题范围内”),因此该消息不执行任何操作,我们可以忽略它。

在处理这些消息期间(在WM_WINDOWPOSCHANGING返回后不久),Windows 对SetWindowPos()进行内部调用以实际调整窗口大小。 SetWindowPos()调用首先调整非客户区(例如标题栏和窗口边框)的大小,然后将注意力转向客户区(您负责的窗口的主要部分)。

在一次拖动的每个消息序列中,Microsoft 都会给您一定的时间来自行更新客户区。

WM_NCCALCSIZE返回后,这个截止日期的时钟显然开始滴答作响。 在 OpenGL 窗口的情况下,当您调用SwapBuffers()以呈现新缓冲区时(而不是在输入或返回WM_PAINTSwapBuffers() ,截止日期显然已满足。 我不使用 GDI 或 DirectX,所以我不知道对SwapBuffers()的等效调用是什么,但您可能会做出很好的猜测,您可以通过在代码中的各个点插入Sleep(1000)来验证当以下行为被触发时。

你有多少时间来满足你的截止日期? 根据我的实验,这个数字似乎在 40-60 毫秒左右,但考虑到 Microsoft 经常使用的各种恶作剧,如果这个数字取决于您的硬件配置甚至您的应用程序以前的行为,我不会感到惊讶。

如果您确实在截止日期前更新了您的客户区,那么 Microsoft 将使您的客户区保持完好无损。 您的用户只会看到您绘制的像素,并且您将获得尽可能平滑的调整大小。

如果您没有在截止日期前更新您的客户区,那么 Microsoft 将介入并“帮助”您,首先向您的用户显示一些其他像素,基于“填充某些背景颜色”技术的组合(第 1c3 节)第 1 部分)和“切掉一些像素”技术(第 1部分的第 1c4 节)。 Microsoft 向您的用户显示的确切像素是复杂的:

  • 如果您的窗口的WNDCLASS.style包含CS_HREDRAW|CS_VREDRAW位(您将WNDCLASS结构传递给RegisterClassEx ):

    • 一些令人惊讶的合理的事情发生了。 您将获得第 1 部分的图 1c3-1、1c3-2、1c4-1 和 1c4-2 中所示的逻辑行为。 扩大客户区时,Windows 将在您拖动的窗口的同一侧用“背景颜色”(见下文)填充新暴露的像素。 如果需要(左边框和上边框),Microsoft 会使用BitBlt来完成此操作。 缩小客户区时,Microsoft 将在您拖动的窗口的同一侧切掉像素。 这意味着您避免了真正令人发指的工件,它使您的客户区中的对象看起来朝一个方向移动,然后又朝另一个方向移动。

    • 这可能足以为您提供可通过的调整大小行为,除非您真的想推动它并查看是否可以在您有机会绘制之前完全防止 Windows 骚扰您的客户区(见下文)。

    • 在这种情况下,不要实现您自己的WM_NCCALCSIZE处理程序,以避免下面描述的错误的 Windows 行为。

  • 如果您的窗口的WNDCLASS.style不包括CS_HREDRAW|CS_VREDRAW位(包括对话框,Windows 不允许您设置WNDCLASS.style ):

    • Windows 尝试通过执行BitBlt来“帮助”您,该BitBlt从旧客户区复制某个像素矩形并将该矩形写入新客户区中的某个位置。 这个BitBlt是 1:1(它不会缩放或缩放你的像素)。

    • 然后,Windows 用“背景色”填充新客户区的其他部分(Windows 在BitBlt操作期间没有覆盖的部分)。

    • BitBlt操作通常是调整大小看起来如此糟糕的关键原因。 这是因为 Windows 对您的应用程序在调整大小后将如何重绘客户区做出了错误的猜测。 Windows 将您的内容放在错误的位置。 最终结果是,当用户首先看到BitBlt像素,然后看到由您的代码绘制的真实像素时,您的内容似乎首先向一个方向移动,然后向另一个方向猛拉。 正如我们在第 1 部分中解释的那样,这会创建最可怕的调整大小工件类型。

    • 因此,修复调整大小问题的大多数解决方案都涉及禁用BitBlt

    • 如果您实现WM_NCCALCSIZE处理程序并且该处理程序在wParam为 1 时返回WVR_VALIDRECTS ,则您实际上可以控制 Windows 从旧客户区复制哪些像素 ( BitBlts ) 以及 Windows 在新客户区中放置这些像素的位置。 WM_NCCALCSIZE只是勉强记载,但看到有关提示WVR_VALIDRECTSNCCALCSIZE_PARAMS.rgrc[1] and [2]在MSDN页WM_NCCALCSIZENCCALCSIZE_PARAMS 您甚至可以提供NCCALCSIZE_PARAMS.rgrc[1] and [2]返回值,以完全阻止 Windows 将旧客户区的任何像素BitBlting到新客户区,或导致 Windows 从和到同一位置BitBlt一个像素,这实际上是同一件事,因为屏幕上的像素不会被修改。 只需将NCCALCSIZE_PARAMS.rgrc[1] and [2]设置为相同的 1 像素矩形。 与消除“背景颜色”(见下文)相结合,这为您提供了一种方法,可以在您有时间绘制窗口像素之前防止 Windows 骚扰您的窗口像素。

    • 如果您实现了WM_NCCALCSIZE处理程序,并且当wParam为 1 时它返回WVR_VALIDRECTS以外的任何内容,那么您会得到一种(至少在 Windows 10 上)与 MSDN 所说的完全不同的行为。 Windows 似乎忽略了您返回的任何左/右/上/下对齐标志。 我建议你不要这样做。 特别是流行的 StackOverflow 文章如何在用户调整对话框大小时强制窗口不重绘对话框中的任何内容? 返回WVR_ALIGNLEFT|WVR_ALIGNTOP并且至少在我的 Windows 10 测试系统上现在这似乎完全被破坏了。 如果更改为返回WVR_VALIDRECTS ,那篇文章中的代码可能会起作用。

    • 如果您没有自己的自定义WM_NCCALCSIZE处理程序,您会得到一个可能最好避免的非常无用的行为:

      • 如果您缩小客户区,则什么也不会发生(您的应用根本没有WM_PAINT )! 如果您使用顶部或左边框,您的客户区内容将与客户区的左上角一起移动。 为了在缩小窗口时实时调整大小,您必须手动从wndproc消息(如WM_SIZE绘制,或调用InvalidateWindow()以触​​发稍后的WM_PAINT

      • 如果您扩大客户区

        • 如果拖动底部或右侧窗口边框,Microsoft 会用“背景色”填充新像素(见下文)

        • 如果拖动顶部或左侧窗口边框,Microsoft 会将现有像素复制到展开窗口的左上角,并在新打开的空间中留下旧像素的旧垃圾副本

因此,从这个肮脏的故事中可以看出,似乎有两种有用的组合:

  • 2a1. 带有CS_HREDRAW|CS_VREDRAW WNDCLASS.style为您提供第 1 部分的图 1c3-1、1c3-2、1c4-1 和 1c4-2 中的行为,这并不完美,但至少您的客户区内容不会向一个方向移动向另一个方向猛拉

  • 2a2. WNDCLASS.style没有CS_HREDRAW|CS_VREDRAW加上WM_NCCALCSIZE处理程序返回WVR_VALIDRECTS (当wParam为 1 时) BitBlts什么都没有,加上禁用“背景颜色”(见下文)可能会完全禁用 Windows 对您的客户区的骚扰。

显然还有另外一种方式可以达到组合2a2的效果。 您可以拦截WM_WINDOWPOSCHANGING (首先将其传递给DefWindowProc )并设置WINDOWPOS.flags |= SWP_NOCOPYBITS ,而不是实现您自己的WM_NCCALCSIZE ,这将禁用 Windows 在窗口调整大小期间对SetWindowPos()的内部调用中的BitBlt 我自己没有尝试过这个技巧,但许多 SO 用户报告说它有效。

在上面的几个点中,我们提到了“背景颜色”。 此颜色由您传递给RegisterClassExWNDCLASS.hbrBackground字段决定。 该字段包含一个HBRUSH对象。 大多数人使用以下样板代码设置它:

wndclass.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);

COLOR_WINDOW+1咒语为您提供白色背景色。 有关 +1 的解释,请参阅 MSDN dox for WNDCLASS ,并注意 StackOverflow 和 MS 论坛上有很多关于 +1 的错误信息。

您可以像这样选择自己的颜色:

wndclass.hbrBackground = CreateSolidBrush(RGB(255,200,122));

您还可以使用以下方法禁用背景填充:

wndclass.hbrBackground = NULL;

这是上述组合 2a2 的另一个关键成分。 但请注意,新发现的像素将呈现一些本质上随机的颜色或图案(无论图形帧缓冲区中出现什么垃圾),直到您的应用程序赶上并绘制新的客户区像素,因此实际上使用组合 2a1 和选择适合您的应用的背景颜色。

2b. 从 DWM 组合填充调整大小问题

在 Aero 开发过程中的某个时刻,微软在上述全 Windows 版本问题之上添加了另一个实时调整大小抖动问题。

阅读早期的 StackOverflow 帖子,实际上很难说这个问题是什么时候引入的,但我们可以说:

  • 这个问题肯定会出现在 Windows 10 中
  • 这个问题几乎肯定会在 Windows 8 中发生
  • 这个问题可能也发生在启用 Aero 的 Windows Vista 中(许多在 Vista 下有调整大小问题的帖子都没有说明是否启用了 Aero)。
  • 即使启用了 Aero,Windows 7 下也可能不会出现此问题。

问题围绕着 Microsoft 在 Windows Vista 中引入的称为DWM 桌面组合的体系结构的重大变化。 应用程序不再直接绘制到图形帧缓冲区。 相反,所有应用程序实际上都在绘制一个离屏帧缓冲区,然后由 Windows 的新的、邪恶的桌面窗口管理器 (DWM) 进程与其他应用程序的输出合成。

因此,因为显示像素涉及另一个过程,所以还有另一个机会弄乱您的像素。

微软绝不会错过这样的机会。

以下是 DWM Compostion 显然发生的情况:

  • 用户在窗口边框上单击鼠标并开始拖动鼠标

  • 每次用户拖动鼠标时,都会触发我们在上面第 2a 节中描述的应用程序中的wndproc事件序列。

  • 但是,与此同时,DWM(记住它是一个与您的应用程序异步运行的单独进程)启动自己的截止时间计时器。

  • 与上面的第 2a 节类似,计时器显然在WM_NCCALCSIZE返回后开始计时,并且在您的应用程序绘制和调用SwapBuffers()

  • 如果更新的截止时间你的客户区域,然后DWM会离开你的客户区精美不受干扰。 您的客户区仍有可能受到第 2a 节中的问题的骚扰,因此请务必阅读第 2a 节。

  • 如果你没有在截止日期前更新你的客户区,那么微软将做一些真正可怕且令人难以置信的糟糕事情(微软没有吸取教训吗?):

    • 假设这是调整大小之前的客户区,其中 A、B、C 和 D 代表客户区顶部、左侧、右侧和底部边缘中间的像素颜色:
    \n   ---------------AAA-----------------\n   |  |\n  公元前\n  公元前\n  公元前\n   |  |\n   --------------DDD-----------------\n  
    • 假设您正在使用鼠标在两个维度上放大您的客户区。 Genius Windows DWM(或者可能是 Nvidia:稍后会详细介绍)将始终将您的客户区的像素复制到新客户区的左上角(无论您拖动哪个窗口边框),然后做最荒谬的事情可以想象到客户区的其余部分。 Windows 将沿客户区底部边缘使用任何像素值,将它们拉伸到新的客户区宽度(我们在第 1部分第 1c2 节中探讨的一个糟糕的想法,并复制这些像素以填充所有新的客户区宽度)在底部打开空间(看看 D 会发生什么)。然后 Windows 将采用沿客户区右边缘的任何像素值,将它们拉伸到新的客户区高度,并复制它们以填充新的右上角的开放空间:
    \n   --------------AAA----------------------------------- ------------\n   |  |  |\n  卑诗省 |\n  卑诗省 |\n   B CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n   |  |CCCCCCCCCCCCCCCCCCCCCCCCCCCCCC\n   --------------DDD-----------------CCCCCCCCCCCCCCCCCCCCCCCCCCCC\n   | 滴滴滴滴滴|\n   | 滴滴滴滴滴|\n   | 滴滴滴滴滴|\n   | 滴滴滴滴滴|\n   | 滴滴滴滴滴|\n   ------------------------------DDDDDDDDD------------------- ------\n  
    • 我什至无法想象他们在抽什么烟。 在许多情况下,这种行为会产生最坏的结果。 首先,当拖动左侧和顶部窗口边框时,几乎可以保证产生我们在第 1 部分的图 1c3-3 和图 1c4-3 中展示的可怕的来回运动,因为复制的矩形总是在左上角无论您拖动的是哪个窗口边框。 其次,如果您碰巧在那里设置了除背景颜色以外的任何像素,那么复制边缘像素时发生的更荒谬的事情将产生丑陋的条。 请注意创建的 C 和 D 条甚至与复制的旧像素中的原始 C 和 D 不对齐。 我可以理解他们为什么要复制边缘,希望在那里找到背景像素以“自动化”背景颜色检测过程,但似乎这种实际工作的可能性被黑客因素和失败的可能性大大超过了。 如果 DWM 使用应用程序选择的“背景颜色”(在WNDCLASS.hbrBackground )会更好,但我怀疑 DWM 可能无法访问该信息,因为 DWM 处于不同的进程中,因此是 hack。 叹。

但我们甚至还没有到最糟糕的部分:

  • 在 DWM 通过这种笨拙的猜测破坏它之前,DWM 给您绘制自己的客户区的最后期限实际上是多少? 显然(根据我的实验)截止日期约为 10-15 毫秒 鉴于 15 毫秒接近 1/60,我猜测截止日期实际上是当前帧的结束。 绝大多数应用程序在大多数情况下都无法满足这个期限。

这就是为什么,如果您在 Windows 10 上启动 Windows 资源管理器并拖动左边框,您很可能会看到右侧的滚动条不规则地抖动/闪烁/跳跃,就好像 Windows 是由四年级学生编写的一样。

我不敢相信微软已经发布了这样的代码并认为它​​“完成了”。 也有可能相关代码在图形驱动程序中(例如 Nvidia、Intel 等),但一些 StackOverflow 帖子让我相信这种行为是跨设备的。

在使用左侧或顶部窗口边框调整大小时,您几乎无法防止这一层无能产生可怕的抖动/闪烁/跳跃。 那是因为对您的客户区的粗鲁、未经同意的修改发生在另一个进程中。

我真的希望某些 StackOverflow 用户能够在 Windows 10 中提出一些神奇的 DWM 设置或标志,我们可以使用它们来延长截止日期或完全禁用可怕的行为。

但与此同时,我确实想出了一个技巧,可以在一定程度上降低窗口调整大小期间可怕的来回工件的频率。

受到https://stackoverflow.com/a/25364123/1046167 中的评论启发的 hack 是尽最大努力将应用程序进程与驱动 DWM 活动的垂直回溯同步。 实际上,在 Windows 中进行这项工作并非易事。 这个 hack 的代码应该是WM_NCCALCSIZE处理程序中的最后一件事:

LARGE_INTEGER freq, now0, now1, now2;
QueryPerformanceFrequency(&freq); // hz

// this absurd code makes Sleep() more accurate
// - without it, Sleep() is not even +-10ms accurate
// - with it, Sleep is around +-1.5 ms accurate
TIMECAPS tc;
MMRESULT mmerr;
MMC(timeGetDevCaps(&tc, sizeof(tc)), {});
int ms_granularity = tc.wPeriodMin;
timeBeginPeriod(ms_granularity); // begin accurate Sleep() !

QueryPerformanceCounter(&now0);

// ask DWM where the vertical blank falls
DWM_TIMING_INFO dti;
memset(&dti, 0, sizeof(dti));
dti.cbSize = sizeof(dti);
HRESULT hrerr;
HRC(DwmGetCompositionTimingInfo(NULL, &dti), {});

QueryPerformanceCounter(&now1);

// - DWM told us about SOME vertical blank
//   - past or future, possibly many frames away
// - convert that into the NEXT vertical blank

__int64 period = (__int64)dti.qpcRefreshPeriod;

__int64 dt = (__int64)dti.qpcVBlank - (__int64)now1.QuadPart;

__int64 w, m;

if (dt >= 0)
{
    w = dt / period;
}
else // dt < 0
{
    // reach back to previous period
    // - so m represents consistent position within phase
    w = -1 + dt / period;
}

// uncomment this to see worst-case behavior
// dt += (sint_64_t)(0.5 * period);

m = dt - (period * w);

assert(m >= 0);
assert(m < period);

double m_ms = 1000.0 * m / (double)freq.QuadPart;

Sleep((int)round(m_ms));

timeEndPeriod(ms_granularity);

您可以通过取消注释显示“最坏情况”行为的行来说服自己这个 hack 正在起作用,方法是尝试在帧的中间而不是垂直同步安排绘图,并注意您还有多少工件。 您还可以尝试缓慢改变该行中的偏移量,您会看到伪像在大约 90% 的时间段突然消失(但不是完全),并在大约 5-10% 的时间段再次出现。

由于Windows不是一个实时操作系统,可以为您的应用程序将在此代码的任何地方抢占,导致不准确的配对now1dti.qpcVBlank 这个小代码段中的抢占很少见,但有可能。 如果你愿意,你可以比较now0now1如果结合不够紧密再次运转并循环。 另外,也可以用于抢占破坏的定时Sleep()之前或之后或代码Sleep() 对此您无能为力,但事实证明这部分代码中的计时错误被 DWM 的不确定行为所淹没; 即使您的时机是完美的,您仍然会得到一些调整窗口大小的伪像。 这只是一个启发式。

还有第二个技巧,这是一个非常有创意的技巧:如 StackOverflow 帖子中所述,拖动窗口的左边框时无法消除抖动,您实际上可以在您的应用程序中创建两个主窗口,并且每次Windows 会执行SetWindowPos ,您接受这一点,而是隐藏一个窗口并显示另一个! 我还没有尝试过这个,但 OP 报告说它绕过了上面描述的疯狂像素 DWM 像素副本。

还有第三个技巧,它可能会根据您的应用程序起作用(尤其是与上面的计时技巧结合使用)。 在实时调整大小期间(您可以通过拦截WM_ENTERSIZEMOVE/WM_EXITSIZEMOVE来检测),您可以修改您的绘图代码以最初绘制更简单的东西,这些东西更有可能在问题 2a 和 2b 规定的期限内完成,然后调用SwapBuffers()领取您的奖品:这足以防止 Windows 执行第 2a 和 2b 节中描述的错误 blit/fill。 然后,在部分绘制之后,立即进行另一次绘制以完全更新窗口内容并再次调用SwapBuffers() 这可能看起来仍然有些奇怪,因为用户将看到您的窗口更新分为两部分,但它看起来可能比 Windows 可怕的来回运动伪影要好得多。

还有一点很吸引人:Windows 10 中的一些应用程序,包括控制台(启动cmd.exe ),即使在拖动左边框时也没有 DWM 合成伪影。 所以有一些方法可以绕过这个问题。 让我们找到它!

2c。 如何诊断您的问题

当您尝试解决特定的调整大小问题时,您可能想知道您看到的是第 2a 节和第 2b 节中的哪些重叠效果。

将它们分开的一种方法是在 Windows 7 上调试一下(为了安全起见,禁用 Aero 的情况下)。

快速确定您是否在第 2b 节中看到问题的另一种方法是修改您的应用程序以显示第 2b 节中描述的测试模式,如下例所示(注意四个边缘中每条边上的 1 像素细彩色线):

测试模式

然后抓住任何窗口边框并开始快速调整该边框的大小。 如果您看到间歇性的巨大彩色条(在此测试模式中为蓝色或绿色条,因为底部边缘为蓝色,右侧边缘为绿色),那么您就知道您在第 2b 节中看到了问题。

您可以通过将WNDCLASS.hbrBackground设置为不同的背景颜色(如红色)来测试您是否在第 2a 节中看到了问题。 当您调整窗口大小时,新暴露的部分将显示为该颜色。 但是通读第 2a 节以确保您的消息处理程序不会导致 Windows 对整个客户区进行BitBlt ,这会导致 Windows 不绘制任何背景颜色。

请记住,第 2a 节和第 2b 节中的问题仅在您的应用程序未能在特定截止日期前绘制时出现,并且每个问题都有不同的截止日期。

因此,如果不进行修改,您的应用程序可能只会显示第 2b 节的问题,但是如果您修改应用程序以使其绘制更慢(例如,在SwapBuffers()之前的WM_PAINT插入Sleep() ),您可能会错过第 2a 节和第 2b 节的最后期限第 2b 节并开始同时看到这两个问题。

当您在较慢的DEBUG构建和RELEASE构建之间更改应用程序时,也可能会发生这种情况,这会使追逐这些调整大小问题变得非常令人沮丧。 了解幕后发生的事情可以帮助您处理令人困惑的结果。

第 1 部分:是什么让调整大小看起来好或坏?

StackOverflow 上关于平滑调整大小的问题有太多的歧义和不明确,我们需要建立一个通用词汇来帮助人们更清晰地回答。

这就是我们在本节中要做的。

为简单起见,我们将仅在水平维度上解释平滑调整大小的问题,但这里的所有内容都同样适用于垂直调整大小。

下面我们将参考一个窗口的

  • “非客户区”:Windows 管理的窗口部分,包括顶部的标题栏和所有边缘的窗口边框,以及

  • “客户区:”您负责的窗口的主要部分

假设您有一个应用程序:

  • 应该保持左齐平的按钮或标签 L
  • 应该保持齐平的按钮或标签 R

无论窗口如何调整大小。

您的应用程序可能会自己绘制 L/R(例如在一个窗口内使用 GDI/OpenGL/DirectX),或者 L/R 可能是一些 Microsoft 控件(它有自己的 HWND 与您的主窗口 HWND 分开); 没关系。

这是应用程序窗口客户区的简化表示。 如您所见,我们在客户区最左侧有三列宽的 LLL,在客户区最右侧有三列宽的 RRR,其他各种客户区内容以“-”表示之间(请忽略 StackOverflow 坚持添加的灰色背景;L 和 R 位于客户区的最左侧和最右侧):

LLL-----------RRR

现在想象你抓住这个窗口的左边界或右边界并拖动它来使窗口变大或变小。

1a. 简单案例:按时绘图

想象一下,您的应用程序绘制速度非常快,因此它始终可以在 1 毫秒内响应用户的拖动操作,而操作系统让您的应用程序可以快速绘制,而无需尝试在屏幕上绘制任何其他内容来“帮助”您。

当您拖动应用程序边框时,用户会在屏幕上看到以下内容(这些数字的每一行代表一个时刻):

向右拖动右边框(扩大宽度):

(Figure 1a-1)
LLL-----------RRR     (initially, when you click the mouse)
LLL------------RRR    (as you drag the mouse)
LLL-------------RRR   (as you drag the mouse)
LLL--------------RRR  (when you release the mouse)

向左拖动右边框(缩小宽度):

(Figure 1a-2)
LLL-----------RRR
LLL----------RRR
LLL---------RRR
LLL--------RRR

向左拖动左边框(扩大宽度):

(Figure 1a-3)
   LLL-----------RRR
  LLL------------RRR
 LLL-------------RRR
LLL--------------RRR

将左边框向右拖动(缩小宽度):

(Figure 1a-4)
LLL-----------RRR
 LLL----------RRR
  LLL---------RRR
   LLL--------RRR

这些看起来都很好而且很流畅:

  • 调整右边框时,R 似乎在一个方向上以恒定速度移动,而 L 保持原样不动。
  • 调整左边框时,L 似乎在一个方向上以恒定速度移动,而 R 保持原样不动。

到现在为止还挺好。

1b. 硬案例:绘图落后

现在,想象一下您的应用程序绘制速度如此之慢,以至于当您用鼠标拖动时,该应用程序无法跟上您的步伐。 是的,最终,您的绘图会赶上来,但我们正在讨论在您用手拖动鼠标期间会发生什么。 显然计算机无法伸手抓住你的手来减慢你的鼠标移动速度,所以关键问题是:

  • 在此期间屏幕上应该显示什么,以及
  • 谁来决定应该展示什么?

例如,向右拖动右边框时(扩大宽度):

(Figure 1b-1)
LLL-----------RRR
??????????????????    (what should show here?)
???????????????????   (what should show here?)
LLL--------------RRR  (app catches up)

再举一个例子,当向左拖动左边框时(缩小宽度):

(Figure 1b-2)
LLL-----------RRR
 ????????????????  (what should show here?)
  ???????????????  (what should show here?)
   LLL--------RRR  (app catches up)

这些是决定运动是否流畅的关键问题,也是整个 StackOverflow 问题所围绕的关键问题。

不同版本的 Windows 在不同的上下文中对这些问题提供了不同的答案,这意味着更平滑地调整大小的解决方案取决于您所处的情况。

1c。 等待应用程序绘制时的临时解决方案

在用户开始拖动鼠标以调整窗口大小之后,但在您的应用程序通过以新大小绘制窗口来赶上之前的时间段内,有多种选择。

1c1。 没做什么

屏幕可以保持原样,直到应用程序赶上(您的客户端像素甚至非客户端区域中的窗口边框都不会发生变化):

向右拖动右边框(扩大宽度)时的示例:

(Figure 1c1-1)
LLL-----------RRR
LLL-----------RRR
LLL-----------RRR
LLL--------------RRR  (app catches up)

向左拖动左边框时的示例(缩小宽度):

(Figure 1c1-2)
LLL-----------RRR
LLL-----------RRR
LLL-----------RRR
   LLL--------RRR  (app catches up)

这种方法的明显缺点是,在相关期间,应用程序似乎“挂起”并且似乎对您的鼠标移动没有响应,因为 R 和“-”、L 和窗口边框都没有移动。

微软经常被指责为 Windows 是一个没有响应的操作系统(有时是他们的错,有时是应用程序开发人员的错),所以自从微软推出实时调整大小(Windows XP?)以来,微软从未使用过“什么都不做”的方法通过它自己。

“什么都不做”的方法对用户来说很烦人,而且看起来不专业,但事实证明(非常不明显)它并不总是最糟糕的选择。 继续阅读...

1c2。 缩放内容

另一种可能性是,Windows 总是可以让窗口边框立即跟随您的鼠标移动(因为 Windows 本身有足够的处理能力,至少可以及时绘制非客户区),并且在等待您的应用程序时,Windows 可以获取客户区域的旧像素并放大或缩小这些像素,就像放大/放大图像一样,以便它们“适合”更小或更大的空间。

这种技术通常比任何其他技术都差,因为它会导致原始内容的模糊图像很可能不成比例。 所以在任何情况下都没有人应该这样做。 除了,正如我们将在第 2 部分中看到的,有时微软会这样做。

1c3。 放大时填一些背景色

放大窗口时可以使用的另一种技术如下:Windows 始终可以使窗口边框立即跟随您的鼠标移动,并且 Windows 可以用一些临时背景色 B 填充现在更大的客户区的新像素:

例如,向右拖动右边框时(扩大宽度):

(Figure 1c3-1)
LLL-----------RRR
LLL-----------RRRB
LLL-----------RRRBB
LLL--------------RRR  (app catches up)

这种方法的优点是在所讨论的时间段内,至少您的窗口边框移动的,因此应用程序感觉响应。

另一个不错的功能是在拖动过程中,L 保持静止,就像它应该的那样。

有点奇怪的是,你在拖动时创建的新空间被一些随机颜色填充,更奇怪的是 R 直到稍后才真正移动(注意 R 在最后一刻向右猛拉 3 列),但至少 R 只在正确的方向上移动。 这是部分改进。

一个巨大而重要的问题是:新填充的背景色 B 应该是什么颜色? 如果 B 恰好是黑色,而您的应用程序恰好具有大部分白色背景,反之亦然,则比 B 与您现有内容的背景颜色匹配时要丑得多。 正如我们将在第 2 部分中看到的,Windows 已经部署了几种不同的策略来改进 B 的选择。

现在考虑相同的想法,但将其应用于我们将左边框向左拖动(扩大宽度)的情况。

合乎逻辑的事情是在窗口左侧填充新的背景颜色:

(Figure 1c3-2)
   LLL-----------RRR
  BLLL-----------RRR
 BBLLL-----------RRR
LLL--------------RRR  (app catches up)

这是合乎逻辑的,因为 R 会保持原状,就像它应该的那样。 L 将具有与我们在上面的图 1c3-1 中描述的相同的怪异(L 会保持静止,然后在最后一刻突然向左猛拉 3 列),但至少 L 只会向正确的方向移动。

然而——这真的会让人感到震惊——在你必须处理的几个重要情况下,Windows 并没有做合乎逻辑的事情。

相反,即使您拖动左侧窗口边框,Windows 有时也会填充右侧的背景像素 B:

(Figure 1c3-3)
   LLL-----------RRR
  LLL-----------RRRB
 LLL-----------RRRBB
LLL--------------RRR  (app catches up)

是的,这太疯狂了。

考虑一下这对用户的看法:

  • L 看起来在一个方向上以恒定速度非常平稳地移动,所以这实际上很好,但是

  • 看看R在做什么:

    \n  存款准备金率\n 存款准备金率\n存款准备金率  \n   RRR(应用赶上)\n
    • R 首先向左移动两列,它不应该这样做:R 应该始终保持齐平
    • R 然后再次向右 碉堡了!

这看起来太可怕了,可怕的,糟糕的,恶心的,......甚至没有词来形容这看起来有多糟糕。

人眼对运动极其敏感,即使是发生在几帧时间内的运动。 我们的眼睛立即注意到 R 的这种奇怪的来回运动,我们立即知道有些事情严重错误。

因此,在这里您可以开始了解为什么某些丑陋的调整大小问题仅在您拖动左侧(或顶部)边框而不是右侧(或底部)边框时发生。

实际上,这两种情况(图 1c3-2 与图 1c3-3)都做了一些奇怪的事情。 在图 1c3-2 中,我们临时添加了一些不属于那里的背景像素 B。 但是这种奇怪的行为远不如图 1c3-3 的来回运动那么明显。

这种来回运动许多 StackOverflow 问题所涉及的抖动/闪烁/跳跃。

因此,平滑调整大小问题的任何解决方案都必须:

  • 至少防止您客户区中的项目出现向一个方向跳跃然后返回另一个方向的情况。

  • 如果可能的话,最好也避免添加背景像素 B

1c4。 收缩时,切掉一些像素

第 1c3 节涉及扩展窗口。 如果我们查看缩小窗口,我们将看到一组完全类似的情况。

缩小窗口时可以使用的技术如下:Windows 始终可以使窗口边框立即跟随您的鼠标移动,并且 Windows 可以简单地切掉(裁剪)您现在更小的客户区的一些像素。

例如,当向左拖动右边框时(缩小宽度):

(Figure 1c4-1)
LLL-----------RRR
LLL-----------RR
LLL-----------R
LLL--------RRR     (app catches up)

使用这种技术,L 保持原样,但右侧发生了奇怪的事情:R,无论窗口大小如何,都应该保持齐平,但它的右边缘似乎被右边缘逐渐切掉客户区,直到 R 消失,然后当应用程序赶上时,突然 R 重新出现在正确的位置。 这很奇怪,但请记住,R 似乎在任何时候都不会向右移动。 R 的左边缘似乎保持静止,直到所有 R 向左跳回 3 列的最后时刻。 所以,就像我们在图 1c3-1 中看到的那样,R 只会在正确的方向上移动。

现在考虑当我们向右拖动左边框(缩小宽度)时会发生什么。

合乎逻辑的做法是将客户区左侧的像素刮掉:

(Figure 1c4-2)
LLL-----------RRR
 LL-----------RRR
  L-----------RRR
   LLL--------RRR  (app catches up)

这将具有与图 1c4-1 相同的奇怪属性,只是左右角色颠倒了。 L 似乎从 L 的左边缘逐渐被剃光,但 L 的右边缘将保持静止,直到最后一刻 L 似乎向右跳。 所以 L 只会朝着正确的方向移动,尽管是突然的。

但是——是的,再次为完全震惊做好准备——在你必须处理的几个重要情况下,Windows 并没有做合乎逻辑的事情。

相反,即使您拖动左侧窗口边框,Windows 有时也会从右侧切掉像素:

(Figure 1c4-3)
LLL-----------RRR
 LLL-----------RR
  LLL-----------R
   LLL--------RRR  (app catches up)

考虑一下这对用户的看法:

  • L 看起来在一个方向上以恒定速度非常平稳地移动,所以这实际上很好,但是

  • 看看R在做什么:

    \n存款准备金率\n  RR\n  电阻\n RRR(应用赶上)\n
    • R 首先向右滑动两列。 R 的左边缘似乎与 R 的其余部分一起向右移动。
    • R 然后再次向左

正如您在阅读 1c3 部分后现在应该意识到的那样,这种来回运动看起来绝对可怕,并且比图 1c4-1 和图 1c4-2 公认的怪异行为要糟糕得多。

1c5。 稍等,然后尝试以上之一

到目前为止,当用户开始拖动窗口边框但应用程序尚未重绘时,我们已经提出了不同的想法。

这些方法实际上可以组合使用。

暂时,试着从微软的角度思考这个问题。 在用户开始拖动鼠标来调整窗口大小的那一刻,微软无法提前知道你的应用绘制需要多长时间。 所以微软必须取得一个平衡:

  • 如果您的应用程序要快速响应,那么微软对屏幕所做的任何更改都会使您的应用程序看起来比微软只是让您绘制真实内容更糟(请记住,上述所有技巧在不同程度上都很奇怪,并且会您的内容看起来很奇怪,所以不使用任何这些技巧肯定会更好)。

  • 但是,如果 Microsoft 等待您绘制的时间过长,您的应用程序(以及扩展的 Windows)将看起来像我们在第 1c1 节中所解释的那样卡顿和无响应。 这让微软失去面子,即使是你的错。

所以,另一种选择是先推迟任何屏幕变化,给应用程序一定的时间来绘制,如果应用程序未能按时完成,则使用上述方法之一暂时“填补空白”。 ”

这对你来说听起来很可怕吗? 你猜怎么着? 这就是 Windows 所做的,至少以 2 种不同的方式同时具有 2 个不同的截止时间。 第 2 部分将深入探讨这些案例......

第 3 部分:悲伤画廊:相关链接的注释列表

您可能可以通过查看源材料来收集我遗漏的想法:

2014 与 2017 更新: 拖动窗口左边框时无法消除抖动:可能是最新的问题,但仍然缺乏上下文; 建议有两个窗口并在实时调整大小期间交替取消隐藏它们的创造性但相当疯狂的技巧! 也是我在答案中发现的唯一问题,其中提到了 DWM 中的竞争条件和DwmGetCompositionTimingInfo()的部分时间修复。

2014 为什么每次调整 WPF 窗口大小时都会出现黑色延迟? : 是的 WPF 也这样做。 没有有用的答案

2009 如何修复 WPF 表单调整大小 - 控件滞后和黑色背景? : 控制滞后和黑色背景?” 多 HWND 示例。提到WM_ERASEBKGND和背景刷技巧,但没有现代答案。

2018 使用WPF时有没有办法减少或防止表单闪烁? : 是的,截至 2018 年仍未修复。

2018 使用 SetWindowPos 更改窗口左边缘时减少闪烁:未回答的问题有许多过时的建议,如WM_NCCALCSIZE

2012 OpenGL 闪烁/损坏,窗口调整大小和 DWM 处于活动状态:很好的问题陈述,回答者完全误解了上下文并提供了不适用的答案。

2012 如何避免 GUI 调整大小中的瞬时更新? : 提到了拦截WM_WINDOWPOSCHANGING并设置WINDOWPOS.flags |= SWP_NOCOPYBITS

2016 Unity 错误报告:“窗口大小调整非常断断续续(边框不能顺滑地跟随鼠标)”在数百个应用程序中发现的典型错误报告部分是由于此错误报告中的问题,部分是由于某些应用程序具有画得慢。 我发现的唯一一个文档实际上说 Windows 10 DWM 会限制并扩展旧窗口的外部像素,我可以确认这一点。

2014 使用 Windows-8 之前的答案(包括CS_HREDRAW/CS_VREDRAWWM_NCCALCSIZE 从左侧调整大小时在窗口上闪烁

2013 Resizing Window 导致右边框附近出现拖尾现象,老式的 Win-7-only 解决方案禁用 Aero。

2018 Flicker-free expand (resize) of a window to left一个多窗口(multi-HWND)案例的例子,没有真正的答案。

2013 WinAPI C++: Reprogramming Window Resize : 太含糊不清地询问是关于客户区闪烁(如这个问题)还是非客户区闪烁。

2018 GLFW 错误“在 Windows 10 上调整窗口大小会显示跳跃行为”是许多此类错误之一,它们从未解释上下文,就像许多 StackOverflow 帖子一样

2008 年“Flicker Free Main Frame Resizing” CodeProject实际上执行 StretchBlt 但在 Windows 8+ 世界中不起作用,当屏幕上显示不正确的像素时,应用程序无法控制。

2014 在 Windows 中平滑调整窗口大小(使用 Direct2D 1.1)? :Windows 8+ DWM 副本的明确但未解决的问题

2010 当用户调整我的对话框大小时,我如何强制窗口不在我的对话框中重绘任何内容? :WM_NCCALCSIZE 修复了禁用在 Windows 8+ 中不再有效的 bitblt,因为 DWM 在应用程序有机会显示之前破坏了屏幕。

2014 移动/调整窗口大小时闪烁:以前在 Windows 8+ 中不起作用的修复的综述。

2007 WinXP时代“减少闪烁”CodeProject推荐WM_ERASEBKGND+SWP_NOCOPYBITS

2008 年早期的Google Bug报告新的 Vista DWM 问题

目录

因为这是一个复杂的、多方面的问题,我建议按以下顺序阅读答案:

以及可以帮助其他人收集见解的源材料列表:

请随时以创造性的方式提供更多答案,以避免 2a 中描述的问题,尤其是 2b!

请参阅博客文章平滑调整大小测试,其中有一些分析和解决方案的指针。 基本上有一个获胜策略,即在实时调整大小时渲染到重定向表面,并在其他时间使用交换链。 我不确定这是否能解决您的具体问题,因为您需要对演示文稿的工作方式进行足够的低级控制才能实现它。 这种方法还假设您正在使用 Direct2D(正如我目前正在做的那样)或 DirectX 进行绘图。

如果您使用的是 DXGI,您可以使用 DirectComposition + WS_EX_NOREDIRECTIONBITMAP 完全绕过重定向表面并在从 WM_NCCALCSIZE 返回之前(即在任何截止时间计时器开始之前)渲染/呈现具有新大小的客户区。 这是使用 D3D11 的最小示例:

#include <Windows.h>

#include <d3d11.h>
#include <dcomp.h>
#include <dxgi1_2.h>

ID3D11Device* d3d;
ID3D11DeviceContext* ctx;
IDXGISwapChain1* sc;

/// <summary>
/// Crash if hr != S_OK.
/// </summary>
void hr_check(HRESULT hr)
{
    if (hr == S_OK) return;
    while (true) __debugbreak();
}

/// <summary>
/// Passthrough (t) if truthy. Crash otherwise.
/// </summary>
template<class T> T win32_check(T t)
{
    if (t) return t;

    // Debuggers are better at displaying HRESULTs than the raw DWORD returned by GetLastError().
    HRESULT hr = HRESULT_FROM_WIN32(GetLastError());
    while (true) __debugbreak();
}

/// <summary>
/// Win32 message handler.
/// </summary>
LRESULT window_proc(HWND hwnd, UINT message, WPARAM wparam, LPARAM lparam)
{
    switch (message)
    {
    case WM_CLOSE:
        ExitProcess(0);
        return 0;

    case WM_NCCALCSIZE:
        // Use the result of DefWindowProc's WM_NCCALCSIZE handler to get the upcoming client rect.
        // Technically, when wparam is TRUE, lparam points to NCCALCSIZE_PARAMS, but its first
        // member is a RECT with the same meaning as the one lparam points to when wparam is FALSE.
        DefWindowProc(hwnd, message, wparam, lparam);
        if (RECT* rect = (RECT*)lparam; rect->right > rect->left && rect->bottom > rect->top)
        {
            // A real app might want to compare these dimensions with the current swap chain
            // dimensions and skip all this if they're unchanged.
            UINT width = rect->right - rect->left;
            UINT height = rect->bottom - rect->top;
            hr_check(sc->ResizeBuffers(0, width, height, DXGI_FORMAT_UNKNOWN, 0));

            // Do some minimal rendering to prove this works.
            ID3D11Resource* buffer;
            ID3D11RenderTargetView* rtv;
            FLOAT color[] = { 0.0f, 0.2f, 0.4f, 1.0f };
            hr_check(sc->GetBuffer(0, IID_PPV_ARGS(&buffer)));
            hr_check(d3d->CreateRenderTargetView(buffer, NULL, &rtv));
            ctx->ClearRenderTargetView(rtv, color);
            buffer->Release();
            rtv->Release();

            // Discard outstanding queued presents and queue a frame with the new size ASAP.
            hr_check(sc->Present(0, DXGI_PRESENT_RESTART));

            // Wait for a vblank to really make sure our frame with the new size is ready before
            // the window finishes resizing.
            // TODO: Determine why this is necessary at all. Why isn't one Present() enough?
            // TODO: Determine if there's a way to wait for vblank without calling Present().
            // TODO: Determine if DO_NOT_SEQUENCE is safe to use with SWAP_EFFECT_FLIP_DISCARD.
            hr_check(sc->Present(1, DXGI_PRESENT_DO_NOT_SEQUENCE));
        }
        // We're never preserving the client area so we always return 0.
        return 0;

    default:
        return DefWindowProc(hwnd, message, wparam, lparam);
    }
}

/// <summary>
/// The app entry point.
/// </summary>
int WinMain(HINSTANCE hinstance, HINSTANCE, LPSTR, int)
{
    // Create the DXGI factory.
    IDXGIFactory2* dxgi;
    hr_check(CreateDXGIFactory1(IID_PPV_ARGS(&dxgi)));

    // Create the D3D device.
    hr_check(D3D11CreateDevice(
        NULL, D3D_DRIVER_TYPE_HARDWARE, NULL, D3D11_CREATE_DEVICE_BGRA_SUPPORT,
        NULL, 0, D3D11_SDK_VERSION, &d3d, NULL, &ctx));

    // Create the swap chain.
    DXGI_SWAP_CHAIN_DESC1 scd = {};
    // Just use a minimal size for now. WM_NCCALCSIZE will resize when necessary.
    scd.Width = 1;
    scd.Height = 1;
    scd.Format = DXGI_FORMAT_B8G8R8A8_UNORM;
    scd.SampleDesc.Count = 1;
    scd.BufferUsage = DXGI_USAGE_RENDER_TARGET_OUTPUT;
    scd.BufferCount = 2;
    // TODO: Determine if PRESENT_DO_NOT_SEQUENCE is safe to use with SWAP_EFFECT_FLIP_DISCARD.
    scd.SwapEffect = DXGI_SWAP_EFFECT_FLIP_SEQUENTIAL;
    scd.AlphaMode = DXGI_ALPHA_MODE_IGNORE;
    hr_check(dxgi->CreateSwapChainForComposition(d3d, &scd, NULL, &sc));

    // Register the window class.
    WNDCLASS wc = {};
    wc.lpfnWndProc = window_proc;
    wc.hInstance = hinstance;
    wc.hCursor = win32_check(LoadCursor(NULL, IDC_ARROW));
    wc.lpszClassName = TEXT("D3DWindow");
    win32_check(RegisterClass(&wc));

    // Create the window. We can use WS_EX_NOREDIRECTIONBITMAP
    // since all our presentation is happening through DirectComposition.
    HWND hwnd = win32_check(CreateWindowEx(
        WS_EX_NOREDIRECTIONBITMAP, wc.lpszClassName, TEXT("D3D Window"),
        WS_OVERLAPPEDWINDOW, CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, NULL, NULL, hinstance, NULL));

    // Bind our swap chain to the window.
    // TODO: Determine what DCompositionCreateDevice(NULL, ...) actually does.
    // I assume it creates a minimal IDCompositionDevice for use with D3D that can't actually
    // do any adapter-specific resource allocations itself, but I'm yet to verify this.
    IDCompositionDevice* dcomp;
    IDCompositionTarget* target;
    IDCompositionVisual* visual;
    hr_check(DCompositionCreateDevice(NULL, IID_PPV_ARGS(&dcomp)));
    hr_check(dcomp->CreateTargetForHwnd(hwnd, FALSE, &target));
    hr_check(dcomp->CreateVisual(&visual));
    hr_check(target->SetRoot(visual));
    hr_check(visual->SetContent(sc));
    hr_check(dcomp->Commit());

    // Show the window and enter the message loop.
    ShowWindow(hwnd, SW_SHOWNORMAL);
    while (true)
    {
        MSG msg;
        win32_check(GetMessage(&msg, NULL, 0, 0) > 0);
        TranslateMessage(&msg);
        DispatchMessage(&msg);
    }
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM