Windows10基于Indirect Display Driver的虚拟显示器开发
虚拟显示器,在如今的串流和远程的互联网应用中,有着诸多用途。它不仅可以帮助我们进行多桌面拓展,能够帮助我们极大提高的利用虚拟机的效率,以及在公有云的租借的服务器,再结合虚拟磁盘映射,做到文件同步,妥妥的多了一台生产力电脑。
在如今如火如荼AR/XR领域,为拓展应用生态,虚拟现实器的技术也有着极其重要的作用。如下可以看到在VR场景里的多个窗口或者桌面流的应用

在VR中串流多个工作桌面流
前文我们探讨了在Win7系统下面基于WDDM HOOK的虚拟显示器开发技术;在微软更新了Windows 10 1067版本之后,Windows支持了显卡过滤框架,通过Indirect Display Driver(IDD)可以很简单的就完成虚拟显示器的开发。
在通过Indirect Display Driver可以生成一个虚拟的显示适配器,针对这个适配器模拟一个显示器设备的插入,就可以实现虚拟显示器的功能了。

在这一篇文章,我们分析Indirect Display Driver的技术原理和虚拟显示器的开发流程。
1. 技术概述
IDD是一个基于WDF的用户层驱动程序,基本架构如下:

IDDCX_ADAPTER表示逻辑显示适配器的对象。 IDDCX_MONITOR表示连接的显示器的对象。 IDDCX_SWAPCHAIN表示桌面图像的交换链。
extern "C" NTSTATUS DriverEntry(
PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pRegistryPath
)
{
WDF_DRIVER_CONFIG Config;
NTSTATUS Status;
WDF_OBJECT_ATTRIBUTES Attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
WDF_DRIVER_CONFIG_INIT(&Config,
IddSampleDeviceAdd
);
Status = WdfDriverCreate(pDriverObject, pRegistryPath, &Attributes, &Config, WDF_NO_HANDLE);
if (!NT_SUCCESS(Status))
{
return Status;
}
return Status;
}
IddSampleDeviceAdd,当驱动被加载并且创建设备对象的时候就会被调用,在这个回调函数中,我们通过IddCx初始化IDD驱动即可。
NTSTATUS IddSampleDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
{
NTSTATUS Status = STATUS_SUCCESS;
WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
UNREFERENCED_PARAMETER(Driver);
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);
PnpPowerCallbacks.EvtDeviceD0Entry = IddSampleDeviceD0Entry;
WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, &PnpPowerCallbacks);
IDD_CX_CLIENT_CONFIG IddConfig;
IDD_CX_CLIENT_CONFIG_INIT(&IddConfig);
IddConfig.EvtIddCxDeviceIoControl = IddSampleIoDeviceControl;
IddConfig.EvtIddCxAdapterInitFinished = IddSampleAdapterInitFinished;
IddConfig.EvtIddCxParseMonitorDescription = IddSampleParseMonitorDescription;
IddConfig.EvtIddCxMonitorGetDefaultDescriptionModes = IddSampleMonitorGetDefaultModes;
IddConfig.EvtIddCxMonitorQueryTargetModes = IddSampleMonitorQueryModes;
IddConfig.EvtIddCxAdapterCommitModes = IddSampleAdapterCommitModes;
IddConfig.EvtIddCxMonitorAssignSwapChain = IddSampleMonitorAssignSwapChain;
IddConfig.EvtIddCxMonitorUnassignSwapChain = IddSampleMonitorUnassignSwapChain;
Status = IddCxDeviceInitConfig(pDeviceInit, &IddConfig);
if (!NT_SUCCESS(Status))
{
return Status;
}
WDF_OBJECT_ATTRIBUTES Attr;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectDeviceContextWrapper);
Attr.EvtCleanupCallback = [](WDFOBJECT Object)
{
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Object);
if (pContext)
{
pContext->Cleanup();
}
};
WDFDEVICE Device = nullptr;
Status = WdfDeviceCreate(&pDeviceInit, &Attr, &Device);
if (!NT_SUCCESS(Status))
{
return Status;
}
Status = IddCxDeviceInitialize(Device);
auto* pContext = WdfObjectGet_IndirectDeviceContextWrapper(Device);
pContext->pContext = new IndirectDeviceContext(Device);
return Status;
}
IddSampleDeviceD0Entry表示设备进入工作电源状态,在这里我们将会创建IDDCX_ADAPTER适配器对象。 IddSampleAdapterInitFinished由于适配器创函数IddCxAdapterInitAsync是一个异步的过程,因此这个函数表示适配器初始化完成的函数。 IddSampleParseMonitorDescription,IddSampleMonitorGetDefaultModes和IddSampleMonitorQueryModes是分别对于不同场景下面模式集合的处理。 IddSampleMonitorAssignSwapChain桌面图片交换链的创建回调函数。
void IndirectDeviceContext::Plugin(UINT ConnectorIndex)
{
WDF_OBJECT_ATTRIBUTES Attr;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, IndirectMonitorContextWrapper);
// In the sample driver, we report a monitor right away but a real driver would do this when a monitor connection event occurs
IDDCX_MONITOR_INFO MonitorInfo = {};
MonitorInfo.Size = sizeof(MonitorInfo);
MonitorInfo.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
MonitorInfo.ConnectorIndex = ConnectorIndex;
MonitorInfo.MonitorDescription.Size = sizeof(MonitorInfo.MonitorDescription);
MonitorInfo.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;
if (ConnectorIndex >= ARRAYSIZE(s_SampleMonitors))
{
MonitorInfo.MonitorDescription.DataSize = 0;
MonitorInfo.MonitorDescription.pData = nullptr;
}
else
{
MonitorInfo.MonitorDescription.DataSize = IndirectSampleMonitor::szEdidBlock;
MonitorInfo.MonitorDescription.pData = const_cast<BYTE*>(s_SampleMonitors[ConnectorIndex].pEdidBlock);
}
CoCreateGuid(&MonitorInfo.MonitorContainerId);
IDARG_IN_MONITORCREATE MonitorCreate = {};
MonitorCreate.ObjectAttributes = &Attr;
MonitorCreate.pMonitorInfo = &MonitorInfo;
// Create a monitor object with the specified monitor descriptor
IDARG_OUT_MONITORCREATE MonitorCreateOut;
NTSTATUS Status = IddCxMonitorCreate(m_Adapter, &MonitorCreate, &MonitorCreateOut);
if (NT_SUCCESS(Status))
{
// Create a new monitor context object and attach it to the Idd monitor object
auto* pMonitorContextWrapper = WdfObjectGet_IndirectMonitorContextWrapper(MonitorCreateOut.MonitorObject);
pMonitorContextWrapper->pContext = new IndirectMonitorContext(MonitorCreateOut.MonitorObject);
// Tell the OS that the monitor has been plugged in
IDARG_OUT_MONITORARRIVAL ArrivalOut;
Status = IddCxMonitorArrival(MonitorCreateOut.MonitorObject, &ArrivalOut);
}
}
IddCxMonitorCreate创建显示器对象,然后通过IddCxMonitorArrival报备系统,显示器已经插入。
3. 安装
[MyDevice_Install.NT.Services]
AddService=WUDFRd,0x000001fa,WUDFRD_ServiceInstall
[WUDFRD_ServiceInstall]
DisplayName = %WudfRdDisplayName%
ServiceType = 1
StartType = 3
ErrorControl = 1
ServiceBinary = %12%\WUDFRd.sys
[MyDevice_Install.NT.Wdf]
UmdfService=IddSampleDriver,IddSampleDriver_Install
UmdfServiceOrder=IddSampleDriver
UmdfKernelModeClientPolicy = AllowKernelModeClients
[IddSampleDriver_Install]
UmdfLibraryVersion=2.25.0
ServiceBinary=%12%\UMDF\IddSampleDriver.dll
UmdfExtensions = IddCx0102
4. 实现效果

