为头装载和专用监视器生成自定义合成器应用

Windows.Devices.Display.Core API 是适用于第三方组合器以及内部 Windows 组件的低级别Windows 运行时 (WinRT) API,位于所有其他公共 API 下面,用于枚举、配置和驱动 Windows 中的显示适配器和显示目标。 其思路是将显示控制器视为单独的引擎,类似于 GPU 上的 3D 引擎和媒体引擎。 此 API 负责:

  • 回答有关显示硬件的查询(如功能和可能的显示模式)
  • 回答有关当前配置的查询
  • 在显示硬件上设置属性(如显示模式)
  • 配置显示硬件(连接的监视器分辨率、其线路格式等)
  • 分配和扫描称为主节点的特殊 GPU 图面
  • 允许在 Direct3D 与 Windows.Devices.Display.Core API 之间实现互操作(例如共享图面、围栏)

值得指出的是,Windows.Devices.Display.Core 不是

  • 它不是游戏或应用用于在窗口中显示内容的 API。 应用仍使用 DXGIXAML合成 APIGDI 等。
  • 它不是游戏或应用用于显示内容全屏的 API。 应用仍使用 DXGI、Win32 应用仍使用 HWND,UWP 应用始终在 CoreWindow 中显示内容。

此 API 仅适用于驱动专用硬件的 compositor 应用。

显示专用显示的 API 体系结构层的关系图。

用于生成自定义合成器的方案

Windows.Devices.Display.Core API 适用于以下方案:

  • 虚拟和增强现实显示器,需要专有组合器来直接驱动显示控制器,并接收与 Windows 桌面分开的计时和模式配置的精细控制。
  • 专用显示硬件方案,需要对商业设置中的显示器进行专用控制。 例如,如果 Windows 桌面由于硬件扭曲、灰度显示器等而无法在此类显示器上正确呈现。
  • 专用的“设备”方案,其中监视器可能完全专用于应用,而不会长时间受到 Windows 桌面体验的任何干扰(例如专用视频监视器)。

API 通过以下方式实现此目的:

  • 提供对完整显示模式信息的精细控制,包括线路格式、HDR 等。
  • 使用围栏来同步演示文稿,可让合成器跨进程或子组件链接演示文稿,且性能开销几乎为零。
  • 提高查询和配置基础视频演示网络(VidPN)的能力,以允许系统组件和低级别组合组件以不太容易出错且更可扩展的方式执行更复杂的操作。

请注意,此 API 仅适用于 具有专用硬件的第三方用例集 。 它的使用高度仅限于声明自己需要此 API 功能的硬件。 因此,开发人员应熟悉硬件概念,合作伙伴应直接与Microsoft联系,以帮助解决问题。

硬件和软件要求

第三方自定义合成器只能获取已预先指定为头载显示器(HMD)或“专用”显示器的显示器。 必须以以下两种方式之一提供此指定:

  • EDID 扩展 - 用于永久用作 HMD、X 射线监视器、视频墙或其他专用方案的自定义显示设备应实现 用于头部装载和专用显示器的 Microsoft EDID 扩展。
  • 用户替代 - 对于使用现成监视器的自定义硬件安装,Windows 提供了一个 UI 开关,用于将监视器指定为“专用”。

通过覆盖软件中的 EDID,显示器可能 指定为 HMD 或专用显示器。

注意

专用显示器仅在 Windows 10 版本 2004 中可用,需要Windows 10 企业版、Windows 10 专业工作站版或Windows 10 IoT 企业版。

实现自定义合成器路线图

实现自定义合成器可以分为几个阶段:

  • 枚举和发现关联的 HMD 或专用显示
  • 获取所选显示器的所有权
  • 为所有选定的显示配置模式
  • 创建用于显示帧的资源
  • 呈现内容和计划框架演示文稿
API 用途和目标受众
DisplayInformation 用于检索 CoreWindow 的呈现和布局属性。
HdmiDisplayInformation 用于枚举和设置受限模式集的仅限 Xbox 的 API。 高度专用于 Xbox 媒体应用方案。
DisplayMonitor 用于查询物理监视器设备的属性。 不公开有关操作系统配置或当前使用监视器的方式的任何运行时信息。
EnumDisplayDevicesEnumDisplayMonitorsEnumDisplaySettingsEx 用于查询 HMONITOR、GDI 设备和物理监视器映射的旧 Win32 API。 此处返回的信息经过高度虚拟化和维护,以实现应用程序兼容性。
Direct3D 用于将像素内容呈现到 GPU 图面,并在 GPU 上执行计算。
DXGI 交换链 用于窗口化和无边框的全屏演示文稿。 应用交换链内容流经系统合成器 DWM。
DXGI 输出枚举 提供 HMONITOR 周围的 DXGI 包装器。
QueryDisplayConfig、SetDisplayConfigDisplayConfigGetDeviceInfoDisplayConfigSetDeviceInfo 用于配置显示拓扑的 Win32 API。 不提供枚举多个模式的机制,但提供了有关当前配置和设置的丰富信息集。 但是,并非所有较新的模式属性都由这些 API 公开。
Windows.Devices.Display.Core (本文档) 用于枚举目标、枚举模式、配置模式、分配用于演示的 GPU 图面以及显示要显示的内容。

显示配置概述

物理硬件枚举

Windows.Devices.Display.Core API 具有各种对象来表示物理硬件对象。 DisplayAdapter 通常是物理硬件设备,例如 PCI Express 连接的 GPU 或 CPU 上的集成 GPU。 DisplayTarget 对象表示可从 GPU 连接到的物理连接器(例如 HDMI、VGA、DisplayPort 等)。 这可能包括具有内部监视器(笔记本电脑、平板电脑等)的设备的内部非用户可见连接。 软件中可能 有更多的 DisplayTarget 对象,而用户一次可以物理连接。 例如,由于 DisplayPort 连接标准允许菊花链接,因此 GPU 驱动程序通常会为每个物理端口枚举多个 DisplayPort 目标,以便考虑链接的监视器。

显示适配器和显示目标的硬件拓扑图示。

用于设置模式的对象

对于枚举 DisplayTarget 对象、设置和查询模式等,使用 DisplayPath 对象表示与 DisplayTarget 对象的连接 显示相同内容(克隆组)的路径组由 DisplayView 表示,这些路径聚合为 DisplayState。 因此,一个 DisplayState 对象可以表示一组完整的模式状态,这些状态可以发送到多个监视器的驱动程序。

显示路径、显示视图和显示状态对象的模式拓扑图示。

模式配置和枚举的原子状态

Windows.Devices.Display.Core API 旨在确保组合器能够以原子方式获取对各种系统显示状态的访问,并具有明确定义的过期行为。 这一点很重要,因为 GPU 是共享资源,带宽和电源约束非常紧张。 在现代系统中,设备可以随时到达/离开,其他内容可能会影响可用显示模式的列表(例如停靠/取消停靠、睡眠状态、另一个组件更改模式在另一条路径上)。 因此,组合器通过使用 Windows.Devices.Display.Core API 和以下用于配置状态的建议模式,对系统配置的更改具有复原能力非常重要。

因此,Windows.Devices.Display.Core API 提供了一个简单的事务性读取-修改-提交模型,类似于数据库。 客户端可以原子方式读取显示系统中设备的 DisplayState 对象。 所有对象都是不可变的,或者提供定义完善的 API,以便将/提交状态更新回系统。 在调用 DisplayState.TryApply 之前不会进行更改,这会对系统进行“提交”更改。 将更改 提交/应用于 DisplayState 失败,不会造成任何影响,或者应用完整更改成功。

若要利用 API 的原子性功能,请执行以下操作:

  • 重试循环中写入任何模式配置逻辑。
  • 请在 模式配置开始时在每个循环内创建新的 DisplayState。
  • 调用 DisplayState.TryApply,请使用 FailIfStateChanged 标志来检测系统状态不再与创建 DisplayState的状态相同。 这样,你就有机会重试该操作。 如果操作失败并出现 SystemStateChanged,请重试整个循环。
  • 不要 将读取或更改状态的其他 API(DXGI、GDI 等)与使用 Windows.Devices.Display.Core API 混合使用,因为它们可能没有相同的原子性保证。
#include <winrt\Windows.Devices.Display.Core.h>
using namespace winrt::Windows::Devices::Display::Core;
...

// Create a DisplayManager
DisplayManager manager = DisplayManager::Create(DisplayManagerOptions::EnforceSourceOwnership);

// Loop around trying to acquire a target and set a mode
bool shouldRetry;
do
{
    shouldRetry = false;

    // ... Find the target that you want to use
    auto targets = manager.GetCurrentTargets();
    DisplayTarget selectedTarget = ...;

    auto stateCreationResult = manager.TryAcquireTargetsAndCreateEmptyState(
        winrt::single_threaded_vector<DisplayTarget>({ selectedTarget }));

    if (stateCreationResult.ErrorCode() != DisplayManagerResult::Success)
    {
        winrt::check_hresult(stateCreationResult.ExtendedErrorCode());
    }

    auto state = stateCreationResult.State();
    DisplayPath newPath = state.ConnectTarget(selectedTarget);

    // ... Configure the path

    auto applyResult = state.TryApply(DisplayStateApplyOptions::FailIfStateChanged);

    if (applyResult.Status() == DisplayStateOperationStatus::SystemStateChanged)
    {
        shouldRetry = true;
    }
    else if (applyResult.Status() != DisplayStateOperationStatus::Success)
    {
        winrt::check_hresult(applyResult.ExtendedErrorCode());
    }

} while (shouldRetry);

以下 API 从系统原子读取状态:

以下 API 将状态提交回系统:

  • DisplayManager
  • DisplayState
    • TryApply 通过显示驱动程序在系统中所有拥有的目标上设置或清除模式来更新当前系统显示状态。

已知限制

Windows.Devices.Display.Core API 有几个已知限制(从 Windows 10 版本 2004 起):

示例代码

有关示例应用程序,请参阅 Windows.Devices.Display.Core 自定义合成器示例