在上一章 交换链处理器 (SwapChainProcessor) 中,我们成功启动了驱动的"图形处理引擎",它现在可以源源不断地接收来自操作系统的桌面图像了。我们的虚拟显示器终于"亮"了起来!
但是,仅仅接收图像是不够的。我们接收到的只是标准的、色彩范围有限的图像数据。这就像看一台老式电视,虽然有颜色,但远不如现代影院屏幕那样生动逼真。如何让我们的虚拟显示器也能显示出像阳光一样耀眼的亮部,和深邃夜空一样的暗部呢?
这就是本章要探索的领域:让我们的虚拟显示器变身为一台支持高动态范围(HDR)和广色域的顶级显示设备。
为什么需要高级色彩管理?
想象一下,你正在用电脑观看一部高质量的科幻电影。在一个场景中,一艘飞船的引擎发出耀眼的白光,同时,深邃的太空中点缀着五彩斑斓的星云。
- 在普通显示器上:引擎的白光可能只是一片"死白",你看不清其中的细节。太空可能是漆黑一片,而不是深邃的黑色。多彩的星云颜色也会显得有些暗淡。
- 在 HDR 显示器上:引擎的白光会非常明亮,甚至让你感觉有些刺眼,但你依然能看清引擎喷口的细节。太空是真正的深黑色,与明亮的星星形成强烈对比。星云的色彩会异常鲜艳、饱满,更接近真实世界。
这种天壤之别就是由高动态范围 (HDR) 和广色域 (Wide Color Gamut) 带来的。
- 高动态范围 (HDR):指的是图像能同时表现出极高的亮度和极低的暗度,就像人眼在现实世界中看到的那样。
- 广色域:指的是显示器能显示的颜色范围更广,能呈现出比标准 sRGB 色域更丰富、更鲜艳的色彩。
要让 Windows 系统向我们的虚拟显示器发送这种高质量的图像信号,我们必须首先"告诉"Windows:"你好,我不是一台普通显示器,我是一台支持 HDR 的高级显示器!"。这个"自我介绍"的过程,就是高级色彩与 HDR 管理的核心。
核心概念:通过"元数据"进行沟通
驱动程序并不是真的去处理光子和色彩,它只是在和操作系统玩一场"角色扮演"游戏。为了扮演好一个"高级显示器"的角色,它需要向 Windows 提供一份详细的"能力说明书",这份说明书在技术上被称为元数据 (Metadata)。
这份元数据包含了以下关键信息:
- 色彩三原色坐标 (Color Primaries):定义了这台显示器能产生的最纯正的红、绿、蓝是什么样的。这决定了它的"色域"有多广。
- 最大/最小亮度 (Luminance):告诉系统这台显示器最亮能达到多少尼特(nits),最暗又能达到多少尼特。这决定了它的"动态范围"。
- 伽马校正 (Gamma Correction):描述了显示器的亮度响应曲线,确保图像的中间色调看起来正确,符合人眼感知。
MttVDD
的高级色彩管理模块,就是一个专业的"元数据生成器"。它根据你在 vdd_settings.xml
中的配置,生成一份完全符合行业标准(如 SMPTE ST.2086)的元数据,然后通过特定的回调函数递交给 Windows。
MttVDD 如何扮演"高级显示器"?
整个过程就像一场精心安排的对话。Windows 会在特定的时候向我们的驱动程序发问,而我们的驱动则需要给出专业且准确的回答。
1. 从配置开始:vdd_settings.xml
一切的起点都在于我们的配置文件。MttVDD
提供了丰富的选项让你来定义虚拟显示器的色彩能力。
xml
<!-- C:\VirtualDisplayDriver\vdd_settings.xml -->
<!-- ... -->
<hdr_advanced>
<!-- 是否启用 HDR10 静态元数据 -->
<enabled name="Hdr10StaticMetadataEnabled">true</enabled>
<!-- 显示器最大亮度 (单位: nits) -->
<max_display_mastering_luminance>1000.0</max_display_mastering_luminance>
<!-- 显示器最小亮度 (单位: nits) -->
<min_display_mastering_luminance>0.05</min_display_mastering_luminance>
<!-- ... -->
</hdr_advanced>
<color_primaries>
<!-- 是否启用自定义色彩三原色 -->
<enabled name="ColorPrimariesEnabled">true</enabled>
<!-- 红色坐标 -->
<red_x>0.708</red_x>
<red_y>0.292</red_y>
<!-- ... 绿色和蓝色坐标 ... -->
</color_primaries>
<!-- ... -->
当驱动启动时,这些设置会被加载到全局变量中,比如 hdr10StaticMetadataEnabled
、maxDisplayMasteringLuminance
等,为后续生成元数据做好准备。
2. 认识新的"色彩专家"回调函数
在 驱动回调与入口点 章节中,我们见识了各种"服务员"回调。现在,让我们认识几位专门负责色彩的"专家":
VirtualDisplayDriverEvtIddCxAdapterQueryTargetInfo
: 当系统想了解我们的虚拟显卡支持哪些高级色彩特性时,会调用这个函数。它就像在问:"你们餐厅提供什么档次的菜品?"VirtualDisplayDriverEvtIddCxMonitorSetDefaultHdrMetadata
: 当系统确认要开启 HDR 模式时,会调用这个函数来获取详细的 HDR 参数。这就像顾客点了"顶级牛排"后,服务员需要向后厨确认具体的熟度和配料。VirtualDisplayDriverEvtIddCxMonitorSetGammaRamp
: 当用户在系统设置里拖动亮度、对比度滑块时,系统会通过这个回调通知我们应用新的色彩校准方案。
3. 工作流程揭秘
让我们通过一个简化的流程图,看看当系统准备在一个虚拟显示器上启用 HDR 时,后台发生了什么。
这个流程清晰地展示了驱动如何响应系统的请求,将用户配置转化为系统可以理解的专业数据。
深入代码:元数据的诞生之旅
现在,让我们深入代码,看看这些"色彩专家"是如何工作的。
第一步:宣告能力 (VirtualDisplayDriverEvtIddCxAdapterQueryTargetInfo
)
这是第一声问候。当 IddCx 框架初始化我们的虚拟显卡时,它会调用这个回调来询问我们支持哪些高级功能。我们的回答非常简洁:
cpp
// Driver.cpp
NTSTATUS VirtualDisplayDriverEvtIddCxAdapterQueryTargetInfo(
/* ... */
)
{
// 告诉系统,我们支持高色彩空间和广色域
pOutArgs->TargetCaps = IDDCX_TARGET_CAPS_HIGH_COLOR_SPACE |
IDDCX_TARGET_CAPS_WIDE_COLOR_SPACE;
return STATUS_SUCCESS;
}
IDDCX_TARGET_CAPS_HIGH_COLOR_SPACE
这个标志就像举起一块牌子,上面写着"我支持 HDR!"。看到这块牌子后,Windows 才会在显示设置中为我们的虚拟显示器显示"使用 HDR"的开关。
第二步:提供详细参数 (VirtualDisplayDriverEvtIddCxMonitorSetDefaultHdrMetadata
)
当用户打开"使用 HDR"的开关后,IddCx 就会立刻调用这个回调,要求我们提供详细的 HDR 元数据。
cpp
// Driver.cpp
NTSTATUS VirtualDisplayDriverEvtIddCxMonitorSetDefaultHdrMetadata(
IDDCX_MONITOR MonitorObject,
/* ... */
)
{
// 首先检查用户是否在 XML 中启用了此功能
if (!hdr10StaticMetadataEnabled) {
return STATUS_SUCCESS; // 未启用则直接返回
}
VddHdrMetadata metadata = {};
// 检查是否应该使用手动配置的元数据
if (colorPrimariesEnabled) {
// 调用辅助函数,从全局配置变量生成元数据
metadata = ConvertManualToSmpteMetadata();
}
// (这里还可以有逻辑来从 EDID 文件加载元数据)
// 如果成功生成了有效的元数据
if (metadata.isValid) {
// 将这份元数据存入一个全局的 map 中,以显示器句柄为 key
g_HdrMetadataStore[MonitorObject] = metadata;
vddlog("i", "HDR 元数据已成功配置并存储");
}
return STATUS_SUCCESS;
}
这个回调函数本身并不复杂,它的核心职责是决策 和存储 。真正的"脏活累活"------数据转换,是由辅助函数 ConvertManualToSmpteMetadata
完成的。
探秘辅助函数:ConvertManualToSmpteMetadata
这个函数是元数据生成的"工厂"。它读取我们在 Driver.cpp
顶部定义的那些全局配置变量,然后把它们转换成符合 SMPTE ST.2086 标准的格式。
cpp
// Driver.cpp
VddHdrMetadata ConvertManualToSmpteMetadata() {
VddHdrMetadata metadata = {};
// 转换亮度值(从 nits 转换为 0.0001 cd/m² 单位)
metadata.max_display_mastering_luminance = ConvertLuminanceToSmpte(maxDisplayMasteringLuminance);
metadata.min_display_mastering_luminance = ConvertLuminanceToSmpte(minDisplayMasteringLuminance);
// 转换色彩三原色坐标(从 0.0-1.0 浮点数转换为 0-50000 整数)
metadata.display_primaries_x[0] = ConvertChromaticityToSmpte(redX); // 红色 x
metadata.display_primaries_y[0] = ConvertChromaticityToSmpte(redY); // 红色 y
// ... 对绿色和蓝色也进行同样操作 ...
metadata.isValid = true;
return metadata;
}
这里的 VddHdrMetadata
是我们自定义的一个结构体,用于临时存放这些转换后的数据。它的结构与 Windows IddCx 框架要求的数据结构非常相似,便于后续使用。
第三步:响应动态调整 (VirtualDisplayDriverEvtIddCxMonitorSetGammaRamp
)
最后,当用户在 Windows 颜色管理中进行调整时,VirtualDisplayDriverEvtIddCxMonitorSetGammaRamp
会被调用。它的逻辑与 HDR 元数据回调非常相似:
cpp
// Driver.cpp
NTSTATUS VirtualDisplayDriverEvtIddCxMonitorSetGammaRamp(
IDDCX_MONITOR MonitorObject,
const IDARG_IN_SET_GAMMARAMP* pInArgs
)
{
if (!colorSpaceEnabled) {
return STATUS_SUCCESS;
}
// 根据配置生成一份 GammaRamp 数据
VddGammaRamp gammaRamp = ConvertManualToGammaRamp();
if (gammaRamp.isValid) {
// 将新的 Gamma 设置存入全局 map
g_GammaRampStore[MonitorObject] = gammaRamp;
}
return STATUS_SUCCESS;
}
这份存储起来的伽马数据,理论上应该被 交换链处理器 (SwapChainProcessor) 在处理每一帧图像时应用,以实现实时的颜色校正。
总结
在本章中,我们揭开了 MttVDD
实现高级色彩与 HDR 支持的秘密。我们学到了:
- 为何重要:支持 HDR 和广色域能让虚拟显示器呈现出更逼真、更生动的图像,使其行为更像一台现代高端显示器。
- 核心机制 :驱动通过向 Windows 提供精确的元数据(如亮度和色彩三原色信息)来"宣告"自己的高级能力。
- 配置驱动 :所有高级色彩功能都通过
vdd_settings.xml
文件进行详细配置,提供了极高的灵活性。 - 专用回调 :IddCx 框架提供了一系列专门的回调函数(如
SetDefaultHdrMetadata
和SetGammaRamp
)用于处理与色彩相关的请求。 - 工作流程:驱动在响应这些回调时,会读取全局配置,通过辅助函数将配置值转换为标准格式,然后将生成的元数据存储起来,供系统使用。
至此,我们的虚拟显示器不仅能工作,而且还是一款功能强大的"高端设备"。它拥有了合法的身份、高效的图像接收能力以及出色的色彩表现力。
但是,目前我们对这个显示器的所有控制都依赖于修改 XML 文件和重启驱动。这显然不够方便。如果我们想在运行时动态地添加或删除显示器,或者实时更改它的设置,该怎么办呢?这就需要一种让外部应用程序与驱动直接"对话"的机制。