自动显示切换

重要

某些信息与在商业发布之前可能进行大幅修改的预发行产品有关。 Microsoft对此处提供的信息不作任何明示或暗示的保证。

本文介绍自动显示开关(ADS)功能,该功能支持笔记本电脑的内部面板在集成 GPU(iGPU)和离散 GPU(dGPU)之间无缝切换。 ADS 是可选的 WDDM 功能,从 Windows 11 版本 24H2 update 2025.01D(WDDM 3.2)开始受支持。

在本文中:

  • GPU0 是指集成面板当前连接到的 GPU。
  • GPU1 是指面板要切换到的 GPU。

概述

一些已发布的笔记本电脑配备了多路复用器 (mux) 设备,允许内部面板在集成 GPU (iGPU) 和独立 GPU (dGPU) 之间切换。 目前,图形驱动程序会在这些笔记本电脑中触发并执行切换,而 OS 对此一无所知,这就导致了一些不理想的用户体验。

ADS 允许 OS 控制使用系统中的多路复用器设备,以便在扫描到内部面板时在 iGPU 和 dGPU 之间进行切换。 因此,OS 可以提供更好的用户体验。

ADS 的初始版本仅支持在 iGPU 和 dGPU 之间切换内部面板。 将来,此功能可能会扩展,以支持笔记本电脑中外部连接器的复用功能。

高级设计

通常,系统必须确保在切换过程中,内部面板的内容不会出现任何闪烁或故障。 OS 不会将此功能限制为任何特定的显示协议。 本文重点介绍如何使用 eDP 实现 ADS,但可以使用更多行业标准(例如 MIPI 或 DSI)。 在无需进行任何其他操作系统更改的情况下,平台设计可以自由使用另一种显示连接协议,只要他们可以实现相同的体验。

本部分中的子部分标识功能设计的各个要素,并详细介绍了每个要素的高层次方法。

控制多路复用器设备

为了减少 iGPU 和 dGPU 图形驱动程序之间的依赖性,多路复用器被作为独立设备公开,OS 可以独立于图形驱动程序进行控制。 此方法的优点是:

  1. 它降低了图形驱动程序的复杂性,因为驱动程序不需要知道如何控制 OEM 可能使用的每种不同的多路复用器。
  2. 它可以减少或消除图形驱动程序之间的依赖关系,这样可以减少驱动程序更新的频率,并使 OEM 更轻松地选择 GPU 和复用器。
  3. 当图形驱动程序不可用时,OS 可以切换多路复用器。

公开多路复用器设备

由于此解决方案用于内部 iGPU 和 dGPU 之间的多路复用,因此通过 ACPI 公开多路复用器是合理的。

多路复用器驱动程序的功能

多路复用器驱动器必须满足以下高级功能要求:

  1. 它必须提供多路复用器的状态、当前控制内部面板的目标以及任何能力上限。
  2. 它必须提供触发开关并报告开关状态的方法。

有关多路复用器 ACPI 设备及其方法的详细信息,请参阅 ACPI

为了实现无缝切换,在 GPU 切换期间,多路复用器设备始终需要满足以下条件:

  1. 面板电源。 在任何时候,多路复用器都需要由任一 GPU 提供面板电源。 让两个 GPU 同时提供面板电源是可以的。
  2. 在切换时,来自两个 GPU 的亮度启用控制信号。
  3. 切换时两个 GPU 的亮度级别(脉宽调制)。

多路复用器在两个 GPU 和面板之间切换以下信息:

  1. 亮度启用控制信号
  2. 亮度等级(脉宽调制)
  3. DisplayPort (DP) 辅助线
  4. 热插拔检测 (HPD) 线
  5. DP 数据线

多路复用器必须能够在面板未处于活动状态时进行切换。 至少在内部面板切换时,多路复用器不应该触发任何 HPD 信号到 GPU。

GPU 驱动程序不应调用多路复用器 ACPI 方法。

自动显示切换 DDI

为满足多路复用要求,增加了多个 DDI。 OS 调用驱动程序的 DDI 有五个不同的点。 各种调用取决于交换机的阶段,以及驱动程序是否控制当前具有显示器控制权的 GPU。

DDI 描述
DxgkDdiDisplayMuxPreSwitchAway 调用当前连接到显示器的驱动程序。 此调用会通知驱动程序,系统计划将显示器切换到另一个 GPU(从 GPU0 切换到 GPU1)。
DxgkDdiDisplayMuxPreSwitchAwayGetPrivateData 从当前连接到面板的驱动程序(来自 GPU0)调用以收集任何专用切换数据。
DxgkDdiDisplayMuxPreSwitchTo 对当前未连接到显示器的驱动程序进行呼叫。 此调用会通知驱动程序,OS 计划将显示器切换到该 GPU (GPU1)。
DxgkDdiDisplayMuxSwitchCanceled 调用驱动程序,以指示切换顺序在切换完成前被取消。
DxgkDdiDisplayMuxPostSwitchAway 多路复用器切换完成,GPU0 的驱动程序不再与显示器连接。
DxgkDdiDisplayMuxPostSwitchToPhase1 多路复用器切换完成,GPU1 的驱动程序已连接到显示器。 此驱动程序现在应执行阶段 1 任务。
DxgkDdiDisplayMuxPostSwitchToPhase2 多路复用器切换完成,并且 GPU1 的驱动程序已连接到显示器。 此驱动程序现在应执行阶段 2 任务。
DxgkDdiDisplayMuxUpdateState 在适配器启动和返回 D0 电源状态时调用,以便让驱动程序知道当前的多路复用器状态。

驾驶员在每个阶段需要完成明确的操作。 本文稍后将介绍这些操作。

在 GPU0 和 GPU1 之间共享数据

在某些情况下,可以在以下情况下生成更好的用户体验:

  • GPU0 和 GPU1 来自同一 IHV。
  • GPU0 可以将操作系统无法解读的显示配置相关信息传递给 GPU1。

数据 Blob 由 GUID 描述,如果 GPU1 的驱动程序能理解数据 Blob,它就能快速识别。 在高级层面上,OS 会调用 GPU0 在切换之前获取 Blob GUID 和数据,并在要求 GPU1 在显示屏中进行 HPD 之前将其传递给 GPU1。

GPU1 的驱动程序负责:

  • 检查它是否能理解 Blob 的 GUID。
  • 验证 Blob 中每个数据元素,以避免 Blob 中格式不正确的数据产生任何有害影响。

驱动程序互操作性

如果 WDDM 驱动程序支持 ADS,则无论它在哪个 OEM 系统上运行或系统上的其他 GPU 是什么,它都需要支持 ADS。

开关序列

虽然从技术上讲,当 GPU 的驱动程序停止运行时,可以从该 GPU 切换到其他 GPU,但目前还不支持此方案。 因此,仅当两个 GPU 都加载了支持切换 DDI 的驱动程序后,才会进行切换。

当面板处于活动状态时,以下序列是整个交换机序列的高级视图,其中 GPU0 和 GPU1 分别表示 iGPU 和 dGPU。 GPU0 目前通过多路复用器连接到内部面板,我们希望切换到 GPU1 扫描到面板。

  1. 在 API 层面进行切换调用。
  2. OS 收集当前内部面板状态(HDR、模式、刷新速率等)的属性,并检查临时显示模式。
  3. 由于系统中任何 GPU 都会产生 HPD,因此 OS 会禁止执行任何显示拓扑。
  4. OS 调用 GPU1 驱动程序的 DxgkDdiDisplayMuxPreSwitchTo 并传递当前亮度级别。 仅当盖子处于打开状态时,驱动程序才应执行以下操作:
    • 打开面板电源。
    • 设置亮度启用信号。
    • 设置 OS 传递的亮度级别。
  5. OS 禁用在 GPU0 上调用 DxgkDdiQueryConnectionChange,以确保在切换多路复用器之前无法处理另一个方向的 lid HPD。
  6. OS 调用 GPU0 驱动程序的 DxgkDdiDisplayMuxPreSwitchAway DDI。 司机应:
    • 如果 lid 处于活动状态,则启用面板上的 PSR1(面板自我刷新 1),并确保在 OS 稍后请求禁用之前不会被禁用。
    • 将数据包添加到其连接更改列表中,DXGK_CONNECTION_CHANGEConnectionStatus 设置为 MonitorStatusDisconnectedMonitorConnect.MonitorConnectFlags.DisplayMuxConnectionChange 设置为 1。
    • GPU0 无法向其队列中添加任何 lid 目标的连接更改数据包。 OS bug 会检查它是否这样做。
    • 将任何私有 ADS 数据块的大小(包括 GUID 和数据)返回给操作系统。 如果 GPU0 驱动程序调用失败,则需要确保在返回之前删除所有放入队列的 ADS 连接状态数据包。
  7. 如果 GPU0 的驱动程序返回的专用数据大小不为零,OS 就会分配该大小,并将其传递给 GPU0 的 DxgkDdiDisplayMuxPreSwitchAwayGetPrivateData 回调,以获取专用切换数据。
  8. OS 调用 mux 的 ACPI 方法,以便从 GPU0 切换到 GPU1。
  9. OS 会再次调用 GPU0 的 DxgkDdiQueryConnectionChange
  10. OS 调用 GPU0 的 DxgkDdiQueryConnectionChanges 来处理 MonitorStatusDisconnected 连接数据包,其中 DisplayMuxConnectionChange 设置为 1。
  11. OS 会调用 GPU0 的 DxgkddiSettimingsfromvidpn 来停用正在切换的显示路径。 GPU0 的驱动程序应:
    • 关闭面板电源。
    • 禁用亮度信号。
    • 停止向多路复用器发送亮度级别。
  12. OS 处理显示背离。 它不会触发拓扑更改以避免不必要的拓扑更改。
  13. OS 调用 GPU1 的 DxgkDdiDisplayMuxPostSwitchToPhase1 回调,并传递从 GPU0 获取的任何 ADS 专用 blob。 驱动程序应:
    • 确定盖子是打开还是关闭。
    • 使用 DXGK_CONNECTION_CHANGE 将数据包添加到其连接更改列表中:
      • MonitorConnect.MonitorConnectFlags.DisplayMuxConnectionChange 位集。
      • 如果 lid 打开,则 ConnectionStatus 设置为 MonitorStatusConnected;如果 lid 关闭,则设置为 MonitorStatusDisconnected
    • 如果 lid 关闭,则关闭电源和面板的亮度启用信号。
  14. 如果 OS 尚未为 GPU1 的内部目标调用 DxgkDdiQueryAdapterInfoDXGKQAITYPE_INTEGRATED_DISPLAY_DESCRIPTOR2,则它会进行调用。 由于此调用,OS 也会调用 DxgkDdiQueryDeviceDescriptor
  15. OS 调用 GPU1 的 DxgkDdiQueryConnectionChange 来处理其连接更改列表中的事件。 此调用将导致 DxgkDdiQueryDeviceDescriptor 被调用,以用于正在进行 HPD 的新监视器。
  16. OS 可显示 HPD 引起的拓扑变化。
  17. OS 将以异步方式处理 GPU0 和 GPU1 的连接数据包,DisplayMuxConnectionChange 设置为 1。
  18. 如果 GPU1 已排队 MonitorStatusConnected
  19. OS 会调用 GPU1 的 DxgkDdiDisplayMuxPostSwitchToPhase2 回调,如果 MonitorStatusConnected 被 GPU1 排在队列中,则驱动程序应关闭 PSR1 显示;否则,它不执行任何操作。
  20. OS 调用 GPU0 的 DxgkDdiDisplayMuxPreSwitchAway。 尽管驱动程序没有预期的操作,但此调用对于与切换相关的任何驱动程序清理或记帐都很有用。
  21. OS 收集当前内部面板状态的属性。 如果面板状态与以前保存的内容不同,OS 会触发遥测。

对于 iGPU->dGPU 和 dGPU->iGPU,此开关序列相同。 当面板处于非活动状态时,可能需要切换多路复用器。 在这种情况下,不需要此序列,操作系统只需在复用器上调用 ACPI 方法即可切换。

大多数 OS 不知道驱动程序处于 PSR 模式。 因此,尽管用户看不到这些情况,但驱动程序仍需要生成 Vsync 同步、报告翻转完成等。

恢复过程

如果在切换序列的任何阶段发生故障,将执行以下清理:

  1. 如果 GPU0 的 DxgkDdiDisplayMuxPreSwitchAway 被成功调用,但其 DxgkDdiDisplayMuxPostSwitchAway 尚未被调用,则 OS 会调用 GPU0 的 DxgkDdiDisplayMuxSwitchCanceled
  2. 如果 GPU1 的 DxgkDdiDisplayMuxPreSwitchTo 被成功调用,但其 DxgkDdiDisplayMuxPostSwitchToPhase2 尚未被调用,OS 就会调用 GPU1 的 DxgkDdiDisplayMuxSwitchCanceled
  3. 如果禁用了显示拓扑更改,操作系统将重新启用它。
  4. 如已禁用,OS 将重新启用在 GPU0 上调用 DxgkDdiQueryConnectionChange
  5. OS 会在 lid 所连接的 GPU 上轮询 lid 的连接性。
  6. OS 会触发设置显示配置 (SDC) 重置。 通过多路复用器(从 DxgkDdiDisplayMuxSwitchCanceled 返回)连接面板的驱动程序需要确保 PSR 已禁用。

开关期间可能发生的异常事件

  • 用户从外部插拔监视器

    作为交换机序列的一部分,我们禁用 OS 对 HPD 事件的处理。 这样,任何 HPD 都会排队等待,并在一个原子操作中与 lid 到达一起处理。

  • 另一个应用会在切换过程中调用 SDC

    当切换进行中时,对 SDC 的调用将被阻止,并在切换完成后执行。

  • 驱动程序在切换过程中会被禁用

    当驱动程序停止时,切换序列中的调用将失败,恢复序列将激活。 PnpStop 部分还详细介绍了如何确保屏幕始终可见。

Lid 关闭情景

驱动程序通常可以使用以下任一方法来检测盖子打开/关闭事件:

但是,对于一般的 WDDM,驱动程序必须使用 DxgkDdiNotifyAcpiEvent 方法,因为它允许 Dxgkrnl 状态和驱动程序状态保持同步。鉴于 iGPU 和 dGPU 在切换序列中都可以是 GPU1,因此所有 ADS 驱动程序都必须跟踪 lid 状态,即使在 lid 被切换时也是如此。

OS 处理来自 GPU0 的 DisplayMuxConnectionChange 事件后,它会认为 GPU0 不再拥有盖子状态,因此 GPU0 在切换回盖之前无法报告该目标的更多连接状态数据包。 如果 GPU0 这样做,OS 将进行 bug 检查。 一旦 OS 处理了来自 GPU1 的 DisplayMuxConnectionChange,它就会认为 GPU1 是 lid 状态的所有者。 在这两个事件之间,任何盖子的开启或关闭事件都可以被忽略,因为GPU1应当清楚盖子状态并报告正确的DisplayMuxConnectionChange数据包。

当 OS 认为驱动程序拥有面板时

下表描述了操作系统将 GPU 视为拥有面板的序列阶段。 拥有的 GPU 可使用切换序列来报告连接变化。 步骤编号来自前面所述的开关序列。

阶段自 阶段至 哪个 GPU 控制显示屏
切换前 步骤 5 GPU0
步骤 6 步骤 12 无 GPU
步骤 13 切换后 GPU 1

当 GPU 不控制面板时,OS bug 会检查是否在驱动程序队列中看到了针对多路复用目标的连接更改数据包。

控制面板自我刷新 (PSR)

ADS 功能使用 PSR 来避免转换过程中出现故障。 具体而言,使用 PSR1(全屏更新模式),以便 GPU0 和 GPU1 无需协商要使用的 PSR 模式。

即使在 PSR1 中,面板也需要支持以下可选功能:

接收器功能 详细信息 接收器公开方式
DPCD & eDP 版本 公开 eDP v1.3 或更高版本。 DPCD
PSR 功能和版本 接收器应支持版本 1。 DPCD 00070h 位 7:0
支持 VSC SDP 以传达 PSR 状态 仅适用于 PSR;接收器应至少支持修订版 2,最多 8 个有效字节,以传输 PSR 状态和 CRC 值。 DPCD 170
接收器应正确报告与 PSR 相关的状态 接收器应公开状态,例如链路 CRC 错误、RFB 存储错误、接收器设备自我刷新状态、最大重新同步帧计数、接收器中最后一次实际同步延迟以及最后一次接收 PSR SDP。 DPCD 2008h、2009h、200Ah 应反映接收器的正确状态。

当 GPU1 作为来自 OS 的 DxgkddiSettimingsfromvidpn 调用的一部分执行链接训练时,驱动程序不知道 GPU0 使用的 DP 通道和带宽设置,因此必须执行完整的链接训练序列,而不是快速链接训练。 OS 不会协商 GPU 之间的任何 PSR 策略,因此面板需要支持 GPU 将使用的所有 PSR 版本和功能。 例如,面板必须支持这样一种情况:GPU0 可能使用 PSR2 并具有某些设定的功能,然后 PSR1 将用于切换,接着 GPU1 可能使用 PSR2 并具有不同的功能。

确保面板在切换期间保持在PSR状态

当 GPU1 在面板上设置模式时,不能保证 GPU1 在面板处于 PSR 状态时设置的链接属性会与 PSR 进入模式相匹配。 例如,刷新速率或活动大小可能会发生变化。 如今,DP 或其他行业标准都没有办法让面板报告它可以在改变链接属性时将面板保持在 PSR 中。 从长远来看,我们希望努力使此功能添加到 DP 规范中。 在此之前,对于支持 ADS 的系统,OEM 必须选择一个 TCon/面板/多路复用器组合,当链路属性(如刷新率、活动大小)在 EDID 中显示的任意两个组合之间发生变化时,该组合仍能保持 PSR 状态。 此方法可确保 PSR 在切换期间保持活动状态。

为了使 ADS HLK 测试能够验证在切换过程中 PSR 是否被保持,我们希望找到一种方法,让操作系统知道在 GPU1 测试模式后 PSR 是否未处于活动状态。 面临的一个挑战是,如果面板不能支持整个链接训练的 PSR,它将如何应对还没有明确规定。

作为 DxgkDdiDisplayMuxPostSwitchToPhase2 的一部分,驱动程序会在 pWasPanelInPSR 中返回一个布尔值,以告知 OS 是否检测到面板不在 PSR 中。

内部面板的 EDID

为了让 OS 在选择显示模式和拓扑结构(连接不同的监视器)时提供预期的行为,两个 GPU 都必须报告内部显示的 EDID/DisplayId。 此要求可确保存储显示模式和拓扑的 CCD 数据库将选取这些相同的设置,而不管哪个 GPU 控制内部显示。

驱动程序向 OS 报告的 EDID 应该是使用 aux 命令从面板中查询的 EDID,而无需进行任何修改。

目前,在启动报告内部面板的驱动程序时,OS 将调用 DxgkDdiQueryAdapterInfo(DXGKQAITYPE_INTEGRATED_DISPLAY_DESCRIPTOR2)。 如果多路复用器从集成目标切换,驱动程序就无法与面板通信,也就无法收集所需的信息。 解决方案是,当启动驱动程序并将多路复用器从其内部目标切换开时,OS 会延迟调用 DxgkDdiQueryAdapterInfo(DXGKQAITYPE_INTEGRATED_DISPLAY_DESCRIPTOR2),直到多路复用器首次切换到内部目标。

OS 如何确定是否在系统上启用了 ADS 功能,并允许切换

OS 执行以下检查列表,以确定 ADS 是否在系统上可用。 要支持 ADS,所有检查都必须为 true。

  1. 有一个 GPU 被标记为集成混合 (DXGK_DRIVERCAPS.HybridIntegrated):
  2. 有一个 GPU 被标记为独立混合 (DXGK_DRIVERCAPS.HybridDiscrete):
  3. 步骤 1 和 2 中的 ACPI DMID 方法返回的多路复用器 ACPI 名称相匹配。
  4. ACPI 多路复用器设备有 ACPI DMQU、DMCF 和 DMSL 方法。
  5. 多路复用器 ACPI DMQU 方法会从其中一个 GPU 返回内部面板目标的 ACPI 名称。
  6. ADS 目前仅支持具有单个内部面板的系统。
  7. 可以是:
    1. GPU0、GPU1 和 Mux ACPI 都报告完整的 ADS 支持。
    2. GPU0、GPU1 和多路复用器 ACPI 都报告了实验性或完全 ADS 支持,并设置了 EnableMDMExperimentalFeature 注册表项。

条件 1 和 2 意味着必须同时启动两个适配器才能切换多路复用器。

控制 ADS 功能推出的质量

为了提供良好的用户体验,以下所有组件必须完美地协同工作:

  1. OS 显示多路复用器功能。
  2. 用于切换多路复用器的平台 ACPI 方法。
  3. iGPU 和 dGPU 驱动程序中的显示多路复用器切换功能。

为了帮助 IHV/OEM 在版本中获得非同类质量的代码,它们可以提供以下任何级别的 ADS 支持:

  • 不支持:驱动程序不支持任何 ADS 功能。
  • 开发支持:驱动程序支持 ADS,但驱动程序的实现仍在开发中,不应超出此目的使用。
  • 实验性支持:驱动程序支持 ADS,但尚未达到发布质量。 默认情况下,OS 不会启用 ADS,但可以配置为启用它。
  • 全面支持:驱动程序以发布级别的质量支持 ADS。 OS 认为驱动程序支持 ADS。

显示切换后应保持不变的显示属性

显示开关不应更改以下任何显示属性:

  1. 桌面分辨率
  2. VidPn 路径(包括 VidPn 源模式、目标模式、缩放等)
  3. DPI
  4. 夜灯设置
  5. 伽马
  6. 显示拓扑
  7. HDR 打开/关闭
  8. SDR 白色等级
  9. 颜色配置文件
  10. 监视器的 OPM 目标类型
  11. 显示亮度

匹配 GPU 上限,以实现无缝切换体验

为了给用户提供无缝的切换体验,显示器在切换后的配置应与切换前保持一致。 有一些 GPU 功能,两个 GPU 都需要相同的支持来实现此行为。 例如,如果一个 GPU 支持 HDR,而另一个 GPU 不支持,那么在一个 GPU 上启用 HDR 时进行切换就不会是无缝的。

下表列出了 GPU 显示功能和功能,并描述了两个 GPU 之间的对齐要求。

功能 需要无缝支持的 GPU
HDR 如果面板支持 HDR,则两个 GPU 要么必须支持 fp16 HDR,要么不支持 HDR。
Hw 游标 不。 OS 适应不同的游标功能,而不会对用户造成明显中断。
MPO 不。 OS 适应不同的 MPO 功能,而不会对用户造成明显中断。
PSR 这两个 GPU 都需要支持此功能。
EDID/DisplayID 这两个 GPU 必须公开相同的 EDID/DisplayId。
亮度上限 这两个 GPU 必须支持相同的亮度接口和亮度上限。
亮度级别 这两个 GPU 都需要公开相同的亮度级别和间隔。
解决方法 这两个 GPU 都需要支持相同的源模式和目标分辨率。
刷新速率 有关详细信息,请参阅如果 GPU1 不支持 GPU0 运行面板时的刷新率
动态刷新速率 不。 OS 可适应不同的虚拟刷新率支持。
可变刷新速率 有关详细信息,请参阅如果 GPU1 不支持 GPU0 运行面板时的刷新率

如果 GPU1 不支持 GPU0 运行面板时的刷新率,则会出现问题

如果 GPU1 不支持与 GPU0 相同的模式,则缩减模式很可能存储在显示拓扑数据库中。 然后,当系统切换回 GPU0 时,将设置缩减模式。 例如,如果 GPU0 支持 120Hz,但 GPU1 仅支持 60Hz,则可能会出现以下序列:

  1. 系统已配置,以便 GPU0 控制显示器,模式为 120Hz。
  2. 用户手动切换到 GPU1。
  3. 显示拓扑数据库为显示器存储了 120Hz,但 GPU1 不支持它,因此 OS 选取 60Hz。
  4. 60Hz 已设定并存储在显示拓扑数据库中。
  5. 用户手动切换回 GPU0。
  6. 显示拓扑数据库从数据库中读取 60Hz。

为了提供最佳体验,OEM 应选择同时支持内部面板最大刷新率的 iGPU 和 dGPU。 如果这是不可能的,而一个 GPU 无法支持面板的最大刷新速率,那么能够支持面板刷新率的 GPU 必须支持 Windows 动态刷新率(DRR)功能,并且需要包含以下范围:

  • 其他 GPU 的最高刷新率。
  • 内部面板的最高刷新率。

例如,如果面板可以支持 300Hz,iGPU 只能支持 60Hz,则 dGPU 必须支持至少 60Hz 到 300Hz 的 VRR。

总之,ADS 对刷新率的要求是:

  1. 内部面板的最大刷新率由 iGPU 和 dGPU 支持。
  2. 支持内部面板最大刷新率的 GPU 必须支持从其他 GPU 所能支持的最高刷新率到内部面板最大刷新率范围的 DRR。

HDR 和 Dolby Vision

OS 在切换后,GPU1 的内部面板上会设置与切换前 GPU0 内部面板上相同的 HDR/Dolby 视觉状态。 用户不应注意到任何更改。

夜灯

夜灯是通过 WDDM gamma 或色彩矩阵接口(DDI)实现的。 在这两种情况下,OS 切换到 GPU1 后,设置的夜灯级别与切换前的 GPU0 相同。

颜色配置文件

OS 会在面板切换后应用与切换前相同的颜色配置文件。

显示故障检查屏幕

目前,OS 支持在非 POST 设备上显示 bug 检查屏幕。 当发生 bug 检查时,OS:

  • 不会切换多路复用器。
  • 使用当前操作系统的支持来显示故障检查屏幕。

在评估要显示 bug 检查的潜在目标时,OS 会跳过与切换到不同目标的多路复用器相连的任何目标。

在从 GPU0 分离的 HPD 被处理的那一小段时间里,来自 GPU1 的 HPD 尚未完全处理。 如果在此时间段内发生故障检测,用户将不会看到故障检测。 如果在 PSR 仍处于启用状态的一小段时间内发生 bug 检查,控制显示器的驱动程序应确保在 OS 调用 DxgkDdiSystemDisplayEnable 时面板未处于 PSR 模式。

内容自适应亮度算法

在理想世界中,这两个 GPU 使用的内容自适应算法应产生相同的效果。 然而,相同的效果可能不会出现,用户在切换内部面板时可能会注意到差异。

亮度数据

为了确保用户不会注意到由于开关而发生亮度变化,GPU0 和 GPU1 公开的所有亮度属性都需要相同。 此要求可确保在切换 GPU0 至 GPU1 之前的任何亮度级别,在切换至 GPU1 后都可以支持。

为此,GPU0 和 GPU1 的驱动程序必须:

  1. 使用相同的亮度接口(DXGK_BRIGHTNESS_INTERFACE_2DXGK_BRIGHTNESS_INTERFACE_3),其中强烈建议使用版本 3。
  2. 对于亮度 v3 接口,两种驱动程序都必须显示基于尼特的亮度或未校准亮度。
  3. 对于亮度 v2 接口,两个驱动程序都必须从 GetPossibleBrightness 返回完全相同的可能亮度级别。
  4. 对于亮度 v3 接口,两个驱动程序必须返回完全相同的范围;也就是说,每个驱动程序都应从 GetNitRanges 返回完全相同的 DXGK_BRIGHTNESS_GET_NIT_RANGES_OUT 结构。
  5. 驱动程序用来将 OS 提供的亮度级别转换为面板特定设置的内部表必须相同。

在大多数笔记本电脑中,GPU 驱动程序以非标准方式从平台获取部分或全部的亮度级别数据。 我们预计,可能需要扩展此平台到 GPU 的数据交换才能满足这些要求。

虽然在适配器启动时会查询亮度接口,但在内部面板进行 HPD 编辑之前,OS 不会调用亮度接口的任何 DDI。 HPD 发生在多路复用器切换到 GPU 之后,因此此时驱动程序可以访问内部面板的 EDID。

我们了解到,驱动程序可以通过特定于 IHV 的方式为不支持 PWM 的面板设置面板亮度。 但是,这种方法会增加 TCon 的复杂性,因为它可能必须支持以不同的 IHV 特定方式获取亮度,这取决于通过多路复用器连接的 GPU。

多路复用器的启动配置

在系统启动时,系统固件会控制哪个 GPU 连接到内部面板。 OS 会存储上一次控制面板的 GPU。 然后,在启动序列中,OS 会根据需要切换多路复用器,以便由正确的 GPU 控制面板。

为了在需要切换多路复用器时保留任何启动映像,只有在以下情况下才会进行切换:

  • 这两个 GPU 都已启动。
  • OS 已从控制输出的启动图形过渡到控制输出的 DWM/Shell。

因此,当控制内部面板的 GPU 调用 DxgkddiSettimingsfromvidpn 后,切换就会发生。在切换过程中,如果面板处于 PSR 状态,用户将会遇到屏幕冻结的现象。

向驱动程序提供多路复用器信息

此功能旨在让 OS 调用驱动程序以提供信息,而不是提供驱动程序随时可以调用的回调。 此方法避免驱动程序在切换序列期间查询 OS 状态时感到困惑。

在下列情况下,OS 会调用驱动程序的 DxgkDdiDisplayMuxUpdateState DDI 向驱动程序提供当前的多路复用器状态:

  1. 在驱动程序启动时,这允许驱动程序在面板未连接时避免及时轮询序列。
  2. 从 Dx 返回 D0 时。 当从某些电源状态(如休眠)返回时,固件可能需要重置多路复用器,因此驱动程序不知道状态。

这些情况以及切换序列中涉及的正常 DDI 可确保驱动程序在 GPU 处于活动状态的任何时候都能确定多路复用器的切换方向。

在此功能的第一个版本中,没有计划在内部面板未处于活动状态时切换多路复用器,因此所有切换都将经过相同的序列。

适配器开始时间

驱动程序启动时,它需要响应来自 OS 的轮询请求。 驱动程序可以通过尝试通信来发现多路复用器是否切换到了它们,但这可能会很耗时或不可靠。 作为 GPU 启动序列的一部分,OS 会调用 DxgkDdiDisplayMuxUpdateState DDI,以显示连接到多路复用器的每个目标,并指示是否已切换到该目标。

驱动程序启动时,它需要响应来自 OS 的轮询请求。 驱动程序可以尝试通过与 OS 通信来发现是否已将多路复用器切换到其 GPU,但这可能会很耗时或不可靠。

取而代之的是,作为 GPU 启动序列的一部分,OS 会为每个连接到多路复用器的目标调用 DxgkDdiDisplayMuxUpdateState 并指示多路复用器是否已切换到该目标。 在调用任何轮询 DDI 之前,OS会向驱动程序报告多路复用器是否已切换到驱动程序的 GPU。

ADS 驱动程序继续以相同方式向 OS 报告内部面板,OS 调用 DxgkDdiQueryAdapterInfo(DXGKQAITYPE_INTEGRATED_DISPLAY_DESCRIPTOR2) 来查询内部面板的详细信息。 对于连接到多路复用器的任何目标,驱动程序需要确保 DXGK_CHILD_CAPABILITIES.HpdAwareness 设置为 HpdAwarenessInterruptible

D0 转换

每当连接了多路复用器的 GPU 从低功耗状态返回到电源开启状态时,OS 都会调用 DxgkDdiDisplayMuxUpdateState 来告诉驱动程序多路复用器是连接到了目标 GPU 还是切换到了其他 GPU。

启动序列

以下启动序列突出显示了 ADS 特定的方面。 在此序列中,系统启动时:

  • 连接到多路复用器的 iGPU。
  • 用户重启前的最后配置是多路复用器连接到 dGPU。

启动序列本质上是异步的,因此此序列仅供参考。

  1. 系统开机,iGPU 通过多路复用器与面板连接。
  2. iGPU 在面板上显示启动屏幕。
  3. Windows 加载并在内部 lid 上显示启动动画。
  4. 由于 iGPU 和 dGPU 上都有 _DEP 功能,OS 的多路复用器驱动程序会在 GPU 驱动程序之前启动。 多路复用器驱动程序使用 ACPI 调用来确保多路复用器配置正确。 复用器驱动程序验证 ACPI 复用器实现是否满足 ADS 要求。
  5. Dxgkrnl 为 iGPU 调用 DxgkDdiAddDevice
  6. Dxgkrnl 为 iGPU 调用 DxgkDdiQueryInterface(DXGK_DISPLAYMUX_INTERFACE)。 即使当前系统不支持 ADS,驱动程序也返回其接口(如果它确实支持 ADS)。
  7. Dxgkrnl 调用 DxgkDdiDisplayMuxGetDriverSupportLevel 以获取驱动程序的 ADS 支持级别。
  8. Dxgkrnl 调用 DxgkDdiDisplayMuxReportPresence (TRUE),让 iGPU 知道系统中的 ADS 多路复用器正在运行。
  9. Dxgkrnl 调用 DxgkDdiStartDevice。 iGPU 驱动程序会返回包括内部面板 VidPn 目标在内的子数量。
  10. Dxgkrnl 调用 DxgkDdiDisplayMuxGetRuntimeStatus 来检查 iGPU 是否支持 ADS,以及驱动程序是否从系统获取了所有必需的信息。
  11. Dxgkrnl 为 iGPU 公开的每个子节点调用 DxgkDdiQueryChildStatus
  12. 一旦 Dxgkrnl 找到连接到多路复用器的 iGPU 报告子节点,它就会调用 DxgkDdiDisplayMuxUpdateState 通知 iGPU 多路复用器已连接到该目标。
  13. 由于 iGPU 公开了连接的内部监视器,因此 Dxgkrnl 使用 DxgkddiSettimingsfromvidpn 在 iGPU 上设置模式。
  14. Dxgkrnl 启动 dGPU 驱动程序,然后对 dGPU 重复步骤 5-12。
  15. Dxgkrnl 检测到 iGPU、dGPU 和多路复用器均已正确配置,因此为多路复用器对创建了多路复用器对和 Pnp 设备接口属性。
  16. Dxgkrnl 从注册表中读取最后一个多路复用器配置。 由于上一个配置为 dGPU,因此 Dxgkrnl 现在将启动前面所述的多路复用器切换序列,将多路复用器切换到 dGPU。

面板驱动程序

监视器面板驱动程序根据 EDID 生成的 Pnp 硬件 ID 进行加载。 鉴于 EDID 保持不变,当任一 GPU 控制内部面板时,面板驱动程序将加载。 这两个驱动程序都将公开相同的亮度功能。 因此,加载应该不会造成任何问题,面板驱动程序也不需要知道哪个 GPU 在控制多路复用器。

识别多路复用器控制的目标

当 OS 启动驱动程序时,它会调用驱动程序的 DxgkDdiQueryChildRelations 来查询所报告子节点的信息。 驱动程序会为每个子节点填写 DXGK_CHILD_DESCRIPTOR 结构。 AcpiUid 成员定义为 ACPI 命名空间中该子级下_ADR方法返回的值,这允许 OS 查找该子项的 ACPI 名称。

对于 ADS,我们定义了一个 ACPI DMID 方法,该方法需要位于目标的子 ACPI 命名空间下。 此 DMID 方法返回多路复用器设备的 ACPI 名称。 它允许 OS 查找目标的多路复用器 ACPI 名称。

Pnp 停止正在向目标扫描的适配器

当扫描到内部面板的 GPU 停止时,OS 不会切换多路复用器。 以下是 GPU 停止运行的不同情况。

  1. GPU0 是 post。 它连接到内部面板并停止运行。

    在这种情况下,基本显示驱动程序(BDD)接管 GPU0 上的当前活动模式,并继续更新屏幕。

  2. GPU0 是 post,但 GPU1 连接到内部面板。 GPU0 已停止。

    由于当前 OS 的设计,BDD 是在 GPU0 上启动的,这会导致报告幽灵监视器并出现在显示器 CPL 中。

  3. GPU1 不是 post,而是连接到内部面板。 GPU1 已停止。

    由于当前的 OS 设计,BDD 未在 GPU1 上启动,因此用户将无法看到面板。

  4. GPU1 不是 post。 GPU0 已连接到内部面板,GPU1 已停止。

    不会发生任何切换,也不会发生任何事情。 GPU0 继续显示在面板上。

方案 2 和 3 为用户创造了糟糕的体验。 ADS 功能更改行为以修复这两种情况。

不支持插件/外部 GPU

我们不认为这个功能在插件 GPU 上有任何用途。

ADS 仅限于单个内部面板

第一个版本的 ADS 仅支持单个内部面板。 但是,该功能的设计方式允许它在未来支持外部和多个内部显示器的多路复用器(如果 OS 支持),只需对驱动程序进行最少的更改。

当前 POST 适配器策略更改

OS 以前有一些有关 POST 适配器的策略。 例如,POST 适配器是唯一可以公开内部目标的适配器。 随着 ADS 的引入,这些类型的限制被从操作系统中移除。

禁用监视器到达视觉效果

在 Windows 11 中连接监视器时,shell/DWM 会出现动画序列。 此动画在显示切换方案中处于禁用状态。

禁用 Pnp bonk

添加或删除监视器时,Pnp 系统播放“bonk”声音以通知用户。 此“bonk”在显示切换场景中被禁用。

应用程序通知

当发生显示切换时,系统会通过常规的 HPD 移除和 HPD 到达代码路径。 因此,所有正常的应用程序通知都会正常触发;例如,HPD 输出和 HPD 输入的 Pnp 通知以及 WM_DISPLAYCHANGE 窗口消息。

用于触发交换机的 API

计划将提供一个公共 API,以便 OS 和 IHV 控制面板可以触发切换。

鉴于内部面板仅连接到单个 GPU,显示 API 和 Win+P 功能均按预期工作。

HLK 测试

如果 GPU 驱动程序或 ACPI 固件报告全面的 ADS 支持,那么它需要在启用了 ADS 的系统上通过 ADS HLK 测试。

当多路复用器从 GPU 切换开时,GPU 正在对内部面板进行 HPD

当一个内部面板被报告为从一个驱动程序连接时,OS 会触发 bug 检查,而该多路复用器目前已从该驱动程序中切换出来。

AC/DC 转换

对于 ADS 功能的第一个版本,OS 不会存储 AC 与 DC 的多路复用器设置,也不会在 AC <-> 直流转换时触发多路复用器切换。

系统电源转换

电源转换的主要问题是当固件重置多路复用器状态时(例如休眠),恢复电源时多路复用器没有切换到电源转换前的面板。

最初的方法是在同时开启 iGPU 和 dGPU 后,将多路复用器切换回 dGPU。 这种方法的问题在于,根据不同的异步事件,结果可能是多种模式的变化。

帮助简化用户体验的最新方法是,当 iGPU 和 dGPU 都处于睡眠状态时,系统会将多路复用器切换回预期目标,从而避免多重模式切换。

电源转换序列

以下示例介绍 ADS 系统上的休眠电源转换。

  1. 系统配置为连接 dGPU 的多路复用器。
  2. 系统进入休眠状态。
  3. iGPU 和 dGPU 都切换到了 D3 电源状态。
  4. 系统关闭电源。
  5. 用户开启系统电源。
  6. 固件配置 iGPU 的多路复用器和 iGPU 在内部面板上的显示启动序列。
  7. Dxgkrnl 读取最后的多路复用器配置(本例中为 dGPU),并将其与使用 ACPI 的当前多路复用器位置(本例中为 iGPU)进行比较。 Dxgkrnl 然后调用 ACPI 将多路复用器切换到 dGPU。
  8. Dxgkrnl 将 iGPU 转换为 D0,然后调用 iGPU 的DxgkDdiDisplayMuxUpdateState,以通知驱动程序该多路复用器未连接到它。
  9. Dxgkrnl 将 dGPU 转换为 D0,然后调用 dGPU 的DxgkDdiDisplayMuxUpdateState,以通知驱动程序该多路复用器已连接到它。
  10. Dxgkrnl 在 dGPU 上设置模式。

一体机系统(AIO)

任何想要支持 ADS 的 AIO 系统都必须在两个 GPU 上将内部面板作为内部目标类型进行公开。

多路复用器 ACPI 设备

OEM 负责在 ACPI 命名空间中添加复用器设备并提供操作复用器所需的方法。

GPU 驱动程序不应调用复用器的 ACPI 方法,因为复用器设备可以位于 ACPI 树中的任何位置。 建议将多路复用器置于两个 GPU 最接近的共享上级之下。

当前复用器设备仅支持两个输入,我们预计未来的复用器不会支持超过两个输入,因此设计可以假设每个复用器有两个输入和一个输出。

在系统运行时,绝对不能停止多路复用器设备。 这是一个隐藏的系统设备。

多路复用器设备 ACPI 方法

只有 ACPI 设备的驱动程序堆栈可以调用以评估设备上的 ACPI 方法。 因此,要调用多路复用器设备方法来切换多路复用器,OS 需要为多路复用器设备加载驱动程序。 因此,OS 现在提供了一个显示多路复用器驱动程序,作为所有显示切换多路复用器的驱动程序。

多路复用器设备必须具备以下方法:

  • _HID 通过硬件 ID 来识别多路复用器设备。 我们为 ACPI 显示多路复用器保留了“MSFT0005”。
  • DMQU(显示多路复用器查询)返回多路复用器的当前状态。
  • DMCF(显示多路复用器配置)配置多路复用器。

方法 _HID(硬件 ID)

参数:

没有

返回:

包含硬件 ID(即“MSFT0005”)的 ASCII 字符串。

方法 DMQU(显示多路复用器查询)

在将来的版本中,我们预计将向查询添加更多信息。 若要将来启用其他查询,Arg0 用于指示查询类型。 如果 DMQU 方法不了解查询类型,则该方法应失败,因为不支持。

参数:

Arg0:指定查询类型的整数。 下表列出了查询类型值及其含义。

查询类型值 意义
1 查询当前切换状态
2 查询多路复用器 ADS 支持级别
3 查询多路复用器连接的第一个 GPU 子级
4 查询多路复用器连接的第二个 GPU 子级

返回:

如果该方法了解指定的查询类型,则应按照下表中所述返回相应的数据。 如果方法不了解指定的查询类型,它应返回一个空字符串。

查询类型值 返回数据
1 ASCII 字符串,包含多路复用器当前切换到的 GPU 子设备的 ACPI 名称。
2 表示 ADS 支持级别的整数。 有关详细信息,请参阅下一个表。
3 ASCII 字符串,包含多路复用器所连接的第一个 GPU 子设备的 ACPI 名称。
4 ASCII 字符串,包含多路复用器所连接的第二个 GPU 子设备的 ACPI 名称。

下表列出了 ADS 支持级别值及其在查询类型为 2 时的含义。

返回的数据 意义
0 不支持
1 开发支持。 系统可以随此设置一起交付,无需通过任何 HLK 测试,因为默认情况下客户系统上将禁用 ADS。
2 实验性支持。 系统可以随此设置一起交付,无需通过任何 HLK 测试,因为默认情况下客户系统上将禁用 ADS。
3 完全支持。 如果该系统与完全支持的图形驱动程序配对,则默认情况下将启用 ADS。 系统需要通过 ADS HLK 测试才能交付。

方法 DMCF(显示多路复用器配置)

参数:

Arg0多路复用器应切换到的 ACPI GPU 子设备的 ASCII 名称。

返回:

整数 0 表示成功;非零表示失败。 OEM 可以定义非零值,以便更好地进行诊断。

GPU 设备 ACPI 方法

在启动 GPU 的图形驱动程序之前,系统需要知道多路复用 ACPI 设备是否正常工作,以及它的当前状态。 为此,ACPI 多路复用器设备的驱动程序必须已经启动。 系统在每个 GPU 的 ACPI 命名空间下使用 ACPI _DEP 方法来保证设备关系。

如果 GPU 已有 _DEP 方法,则应将多路复用器设备的 ACPI 名称添加到返回的依赖关系列表中。 如果 GPU 还没有 _DEP 方法,则应添加一个。

为了让 ACPI 固件仅在 OS 支持 ADS 的情况下声明 GPU 对多路复用器的依赖项,增加了 ACPI _OSI 查询。 ACPI 固件可以使用此查询来检查 ADS 支持。 支持 ADS 的 OS 版本将通过向 _OSI(“DisplayMux”) ACPI 命令返回 true 来报告支持情况。

GPU 子设备 ACPI 方法

对于连接到多路复用器的每个目标,该子设备的 ACPI 设备都会公开一个 ACPI 方法,返回其所连接的多路复用器设备的 ACPI 名称。 有关详细信息,请参阅确定多路复用器控制的目标

方法 DMID(显示多路复用器标识符)

参数:

没有

返回:

ASCII 字符串,包含该输出所连接的 ACPI 多路复用器设备的 ACPI 名称

以下示例演示了如何在 ACPI 框架内设置和管理带有两个 GPU(GPU0 和 GPU1)和一个多路复用器的系统。

  • 多路复用器设备的 ACPI 名称为“SB.MUX1”。

  • 对于 GPU0:

    • GPU0 的 ACPI 名称为 'SB.PCI0.GFX0'。
    • 它公开了 VidPn 目标 0x40f04,该目标报告的 DXGK_CHILD_DESCRIPTOR.AcpiUid 值为 0x400。
    • 与连接到多路复用器的目标相对应的 ACPI 子设备名称是“SB.PCI0.GFX0.DD1F”。
    • ACPI 方法 _ADR 在 'SB.PCI0.GFX0.DD1F' 下返回 0x400。 此返回值是操作系统如何识别此 ACPI 设备与 VidPn 目标 0x40f04 相对应的依据。
    • 在“SB.PCI0.GFX0.DD1F”下的ACPI方法DMID返回“SB.MUX1”。
  • 对于 GPU1:

    • GPU1 的 ACPI 名称为“SB.PCI0.PEG0.PEGP”。
    • 它公开了 VidPn 目标 0x1103,该目标报告的 DXGK_CHILD_DESCRIPTOR.AcpiUid 值为 0x100。
    • 与连接到多路复用器的目标相对应的 ACPI 子设备名称是“SB.PCI0.PEG0.PEGP.EDP1”。
    • 在“SB.PCI0.PEG0.PEGP.EDP1”下,ACPI 方法_ADR 返回 0x100。 此返回值使得操作系统知道此 ACPI 设备对应 VidPn 目标 0x1103。
    • 在“SB.PCI0.PEG0.PEGP.EDP1”下的 ACPI 方法 DMID 返回“SB.MUX1”。
  • OS 知道 GPU0 目标 0x40f04 和 GPU1 目标 0x1103 连接到同一个 ACPI 名称为“SB.MUX1”的多路复用器。

  • 如果 GPU1 当前连接到面板,OS 可以通过调用“SB.MUX1”上的 DMCF 方法,传入“SB.PCI0.GFX0.DD1F”,将多路复用器切换到 GPU0。

以下 ACPI 计算机语言代码适用于示例的相关部分。 平台逻辑的伪代码用 <> 括起来。


DefinitionBlock
{
    Device (MUX1) // This is _SB_.MUX1
    {
        Name (_HID, "MSFT0007")  // _HID: Hardware ID

        Method (DMQU, 1, Serialized)  // DMQU: Display Mux Query
        {
            Switch (ToInteger(Arg0))
            {
                Case (1)
                {
                    If (<Mux is in error>)
                    {
                        Return ("")
                    }
                    If (<Mux switched to GPU0>)
                    {
                        Return ("_SB_.PCI0.GFX0.DD1F")
                    }
                    Else
                    {
                        Return ("_SB_.PCI0.PEG0.PEGP.EDP1")
                    }
                }
                Case (2) 
                {
                    Return (1)  // Mux only has developmental support
                }
                Case (3)
                {
                    If (<Mux is in error>)
                    {
                        Return ("")
                    }
                    Return ("_SB_.PCI0.GFX0.DD1F")
                }
                Case (4)
                {
                    If (<Mux is in error>)
                    {
                        Return ("")
                    }
                    Return ("_SB_.PCI0.PEG0.PEGP.EDP1")
                }

            }
            // Unknown type
            Return ("")
        }

        Method (DMCF, 1, Serialized)  // DMCF: Display Mux Configure
        {
            If (<Arg0 does not match either of the GPU children this mux is connected to>)
            {
                Return (1) // Failure, use 1 to indicate this particular failure
            }

            // Switch the mux

            If (<Mux switch was successful>)
            {
                Return (0) // Success
            }
            Else
            {
                Return (2) // Failure, use 2 to indicate this particular failure
            }
        }
    }

    Scope (_SB_.PCI0.GFX0) // ACPI Device for GPU0
    {
        Method (_DEP, 0, NotSerialized)  // _DEP: Dependency on Mux device
        {
            If (_OSI(“DisplayMux”))
            {
                Return (Package {"_SB_.MUX1"})
            }
            Else
            {
                Return (Package (0x00){})
            }
        }

        Device (DD1F) // SB.PCI0.GFX0.DD1F which is child of GPU that is connected to the Mux
        {
            Name (_ADR, 0x400)  // _ADR: Matches the AcpiUid driver reports for the target connected to mux
            Method (DMID, 0, NotSerialized)  // DMID: ACPI name of the mux this target is connected to
            {
                Return ("_SB_.MUX1")
            }
        }
    }

    Scope (_SB_.PCI0.PEG0.PEGP) // ACPI Device for GPU1
    {
        Method (_DEP, 0, NotSerialized)  // _DEP: Dependency on Mux device
        {
            If (_OSI(“DisplayMux”))
            {
                Return (Package {"_SB_.MUX1"})
            }
            Else
            {
                Return (Package (0x00){})
            }
        }

        Device (EDP1) // SB.PCI0.PEG0.PEGP.EDP1 which is child of GPU that is connected to the Mux
        {
            Name (_ADR, 0x100)  // _ADR: Matches the AcpiUid driver reports for the target connected to mux
            Method (DMID, 0, NotSerialized)  // DMID: ACPI name of the mux this target is connected to
            {
                Return ("_SB_.MUX1")
            }
        }
    }
}

API 更改

ADS 功能添加了以下公共 API 功能:

  1. 枚举系统中的多路复用器设备。
  2. 查询有关多路复用器的信息,例如,它连接了哪些目标,以及当前切换到哪个目标。
  3. 触发多路复用器切换。
  4. 如何检测多路复用器是否已切换。

枚举系统中的多路复用器设备

应用程序可以使用通用的即插即用 API 来查找代表正常显示多路复用器的设备接口。 用户模式组件可使用 Windows.Devices.Enumeration.DeviceInformation。 无论是 C# 还是 C++,都可以使用这些 API 来枚举多路复用器设备。

// Display Mux device interface
// {93c33929-3180-46d3-8aab-008c84ad1e6e}
DEFINE_GUID(GUID_DEVINTERFACE_DISPLAYMUX, 0x93c33929, 0x3180, 0x46d3, 0x8a, 0xab, 0x00, 0x8c, 0x84, 0xad, 0x1e, 0x6e);

IDisplayMuxDevice 接口

添加 IDisplayMuxDevice 接口来表示多路复用器设备。

以下代码演示如何枚举显示复用器设备、查询其状态、切换活动显示目标,以及使用 Windows 运行时 API 对状态更改做出反应。

#include <winrt/Windows.Foundation.h>
#include <winrt/Windows.Devices.Enumeration.h>
#include <winrt/Windows.Foundation.Collections.h>
#include <winrt/Windows.Devices.Display.Core.h>

#include <string>
#include <sstream>
#include <iomanip>
#include <windows.h>

namespace winrt
{
using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Foundation::Collections;
using namespace winrt::Windows::Devices::Enumeration;
using namespace winrt::Windows::Devices::Display;
using namespace winrt::Windows::Devices::Display::Core;
} // namespace winrt

void SwitchDisplayMuxTarget()
{
    // Pnp device interface search string for Mux device interface
    std::wstring muxDeviceSelector = L"System.Devices.InterfaceClassGuid:=\"{93c33929-3180-46d3-8aab-008c84ad1e6e}\" AND System.Devices.InterfaceEnabled:=System.StructuredQueryType.Boolean#True";

    // Execute the device interface query
    winrt::DeviceInformationCollection deviceInformations = winrt::DeviceInformation::FindAllAsync(muxDeviceSelector, nullptr).get();
    if (deviceInformations.Size() == 0)
    {
        printf("No DisplayMux devices\n");
        return;
    }
    printf("%ld display mux devices found\n\n", deviceInformations.Size());

    // Only one mux in first release but here is generic code for multiple
    for (unsigned int i = 0; i < deviceInformations.Size(); i++)
    {
        printf("Display Mux device %ld :\n", i);

        // Get the device interface so we can query the info
        winrt::DeviceInformation deviceInfo = deviceInformations.GetAt(i);

        // Get the device id
        std::wstring deviceId = deviceInfo.Id().c_str();
        printf("    Device ID string : %S \n", deviceId.c_str());

        // Create the DisplayMuxDevice object
        auto displayMuxDevice = winrt::DisplayMuxDevice::FromIdAsync(deviceId).get();
        if (!displayMuxDevice)
        {
            printf("Failed to create DisplayMuxDevice object");
            continue;
        }

        // Check if DisplayMux is active
        auto displayMuxActive = displayMuxDevice.IsActive();
        printf("    DisplayMux state : %s \n", displayMuxActive ? "Active" : "Inactive");
        if (!displayMuxActive)
        {
            continue;
        }

        // Register for call back when the state of the DisplayMux changes
        UINT changeCount = 0;
        auto token = displayMuxDevice.Changed([&changeCount](auto, auto Args) -> HRESULT {
            changeCount++;
            return S_OK;
        });

        // Find targets connected to the DisplayMux and the current target
        auto targetsList = displayMuxDevice.GetAvailableMuxTargets();
        winrt::DisplayTarget currentTarget = displayMuxDevice.CurrentTarget();

        // Switch the display mux to the other target
        // NOTE SetPreferredTarget() is a sync method so use .get() to wait for the operation to complete
        printf("\n");
        if (currentTarget == targetsList.GetAt(0))
        {
            printf("DisplayMux currently connected to first target\n");
            displayMuxDevice.SetPreferredTarget(targetsList.GetAt(1)).get();
            printf("Calling SetPreferredTarget to switch DisplayMux to second target\n");
        }
        else if (currentTarget == targetsList.GetAt(1))
        {
            printf("DisplayMux currently connected to second target\n");
            displayMuxDevice.SetPreferredTarget(targetsList.GetAt(0)).get();
            printf("Calling SetPreferredTarget to switch DisplayMux to first target\n");
        }
        else
        {
            printf("Could not find current target in target list\n");
        }

        // Now read the current position
        currentTarget = displayMuxDevice.CurrentTarget();
        targetsList = displayMuxDevice.GetAvailableMuxTargets();
        if (currentTarget == targetsList.GetAt(0))
        {
            printf("DisplayMux is now currently connected to first target\n");
        }
        else if (currentTarget == targetsList.GetAt(1))
        {
            printf("DisplayMux is now currently connected to second target\n");
        }
        else
        {
            printf("Could not find current target in target list\n");
        }

        // Now unregister for change callback and display the
        displayMuxDevice.Changed(token);
        printf("DisplayMux state change callback was called %ld times\n\n", changeCount);
    }
}

用于自动显示切换的 WDDM DDI 更改

本部分介绍对 WDDM DDI 所做的添加和更改,以支持 ADS。 这些更改从 Windows 11 版本 24H2 update 2025.01D(WDDM 3.2)开始可用。

查询 KMD 的 ADS 支持接口

添加了 DXGK_DISPLAYMUX_INTERFACE_2 接口结构。 它包含支持 ADS 版本 2 所需的 OS 到驱动程序的调用。 OS 在驱动程序启动时查询驱动程序支持的 ADS 接口,将 InterfaceType 设置为GUID_WDDM_INTERFACE_DISPLAYMUX_2。

DXGK_DISPLAYMUX_INTERFACE 包含操作系统对驱动程序所必需的调用,以支持 ADS 功能的第 1 版。此版本用于 ADS 的预发行期间。)

支持 ADS 的 KMD 功能

KMD 实现以下函数以支持 ADS。 Dxgkrnl 通过调用 KMD 的 DxgkddiQueryInterface获取 KMD 的 ADS 功能接口。

驱动程序报告 ADS 功能

当 OS 调用其 DxgkDdiDisplayMuxGetDriverSupportLevel DDI 时,驱动程序会报告其 ADS 支持级别。 如果驱动程序未实现 DXGK_DISPLAYMUX_INTERFACE 接口,OS 会将支持级别视为DXGK_DISPLAYMUX_SUUPORT_LEVEL_NONE。

无论运行哪个系统,驱动程序都应报告其 ADS 支持级别。 驱动程序报告的支持级别应仅基于驱动程序。 报告其 ADS 支持级别时,驱动程序不应考虑以下任何条件:

  1. 系统 OEM。
  2. 系统中的任何其他 GPU。
  3. 是否存在 ACPI 多路复用器设备。
  4. GPU 的 ACPI 节点下是否存在 ACPI 条目。

在启动适配器时间更新报告目标

适配器启动时,它会通过 DxgkDdiQueryChildRelations DDI 报告其所有子设备。 报告包括连接到多路复用器的任何内部目标。 内部目标包括 DXGK_CHILD_CAPABILITIES.Type.IntegratedDisplayChild.DescriptorLength 字段。

如果在适配器启动时将多路复用器切换到另一个 GPU,则会出现问题。 在这种情况下,驱动程序无法与内部面板通信以查询 EDID/DisplayId 大小。 因此,公开 GUID_WDDM_INTERFACE_DISPLAYMUX_2 接口的驱动程序必须在适配器启动时将DXGK_CHILD_CAPABILITIES.Type.IntegratedDisplayChild.DescriptorLength 设置为零,但前提是当前没有将多路复用器切换到驱动程序的 GPU。 否则,OS 将导致适配器启动失败。

OS 会在第一次进行多路复用器切换操作时更新有关内部描述符大小的内部信息。

更新连接变化

如前所述,在自动显示切换序列中,有一种特定于 ADS 的方法来报告内部面板状态。 为了表示连接更改数据包是 ADS 切换序列的一部分,DisplayMuxConnectionChange 标志被添加到 DXGK_CONNECTION_MONITOR_CONNECT_FLAGS 中。 当 DisplayMuxConnectionChange 被设置时,表示 MonitorStatusConnected or MonitorStatusDisconnectedconnection status 与自动显示切换相关。

DisplayMuxConnectionChange 只能在 ADS 切换时使用,不得用于任何其他目的。 它应在以下 ADS 场合使用:

  • 当驱动程序正在处理 DxgkDdiDisplayMuxPreSwitchAway 时。

    如果内部面板已连接,驱动程序应在其连接更改列表中添加 DXGK_CONNECTION_CHANGE 数据包,其中 DXGK_CONNECTION_CHANGE.ConnectionStatus 设置为 MonitorStatusDisconnectedDXGK_CONNECTION_CHANGE.MonitorConnect.MonitorConnectFlags.DisplayMuxConnectionChange 设置为 1。 这些设置向 OS 指示驱动程序已释放对内部面板的控制。

  • 当驱动程序正在处理 DxgkDdiDisplayMuxPostSwitchToPhase1 时。

    • 驱动程序应首先确定内部面板是否已连接。
    • 如果内部面板已连接,驱动程序应在其连接更改列表中添加 DXGK_CONNECTION_CHANGE 数据包,其中 DXGK_CONNECTION_CHANGE.ConnectionStatus 设置为 MonitorStatusConnectedDXGK_CONNECTION_CHANGE.MonitorConnect.MonitorConnectFlags.DisplayMuxConnectionChange 设置为 1。
    • 如果面板未连接,驱动程序应添加一个 DXGK_CONNECTION_CHANGE 数据包到连接更改列表中,并将 DXGK_CONNECTION_CHANGE.ConnectionStatus 设置为 MonitorStatusDisconnected,及将 DXGK_CONNECTION_CHANGE.MonitorConnect.MonitorConnectFlags.DisplayMuxConnectionChange 设置为 1。
  • 当驱动程序正在处理 DxgkDdiDisplayMuxSwitchCanceled时。

  • *如果在切换过程中收到目标轮询请求,DisplayMuxConnectionChange 只应针对从 DxgkDdiDisplayMuxPreSwitchAwayDxgkDdiDisplayMuxPostSwitchToPhase1DxgkDdiDisplayMuxSwitchCanceled 添加的连接更改数据包进行设置。

更新了 DxgkDdiSystemDisplayEnable 的指南

在调用 ADS 驱动程序的 DxgkDdiSystemDisplayEnable(/windows-hardware/drivers/ddi/dispmprt/nc-dispmprt-dxgkddi_system_display_enable) DDI 时,驱动程序必须确保在 DxgkDdiSystemDisplayEnable DDI 调用结束时禁用 PSR。

OEM 指南

在平台中,ADS 功能的某些方面处于 OS 控制层次之下。 OEM 确保其正常工作至关重要。 以下列表总结了 OEM 需要考虑的一些要点:

  • 混合集成和混合离散驱动程序都需要支持 ADS。
  • 为平台选择的多路复用器可通过 ACPI 控制。
  • 内部目标的多路复用器设备和 GPU 子 ACPI 设备下的 _HID、DMQU 和 DMCF 方法已经实现,并具有 DMID ACPI 方法。
  • 两个 GPU 的 ACPI 设备都必须有 _DEP 以标记它们对多路复用器 ACPI 设备的依赖性。
  • 两个 GPU 公开的亮度接口/上限/范围完全匹配。
  • 亮度数据 部分所述,强烈建议使用亮度 v3 接口而非亮度 V2 接口。
  • 如果使用监视器面板驱动程序,则代码应与 GPU 无关;也就是说,当任一 GPU 控制面板时,可以使用相同的逻辑。
  • 至少对于内部多路复用器来说,切换多路复用器的行为不应该产生 HPD 事件。
  • 如果 OEM 想要禁用系统中的多路复用器,那么当调用 DMQU ACPI 方法并将 Arg0 设置为 2 时,该方法应返回 0。
  • 即使驱动程序处于低功耗状态,多路复用器也必须能够在 GPU 之间进行切换。 这种情况下不会使用 PSR。
  • 当多路复用器从一个 GPU 切换到另一个 GPU 时,面板亮度应保持不变,不会出现任何亮度波动。 有多种方法可以执行此操作,包括以下方法。 OEM 负责确保系统在各切换之间保持亮度。
    • 使用基于 DisplayPort Aux Nits 的亮度控件。
    • 使用带有 PWM 重建功能的 Tcon,以避免亮度闪烁。
  • 如果 EDID 公开了切换前和切换后链路配置,且 iGPU 和 dGPU 都支持这些配置,则所使用的面板和 Tcon 可保持自我刷新状态(eDP 为 PSR1)。 这包括但不限于:
    • 刷新速率
    • 活动大小
    • 使用的 eDP 通道数和通道带宽
    • eDP DSC 设置
    • 使用的 eDP VSC SDP 版本
    • 用于非切换方案的 PSR 版本和功能