在开发或者办公中,越大的屏幕看起来就显示越舒服了,通常我们的做法是有两块屏幕,这样显示的内容就变多了,可以很容易提高办公的效率。
在设置中显示
中,如果我们有两块屏幕,在显示器中自然的会出现两个,在其中可以对两块屏幕进行相应的设置。
在这个驱动中,我们要解决的问题是,我们没有物理的第二块屏幕,我们通过驱动的方式,虚拟出第二屏幕出来,只要我们得到第二屏幕的数据,我们很容易可以把屏幕数据流投影到想投的地方。
关于虚拟屏幕,微软也有相应的demo
。关于 Indirect Display
的微软demo,可以去相应的地方找到文档,这里几个比较重要的github
项目如下:
ScreenExpander
会出现Error value: 259 Message的问题,可以参考我自己的修改代码。
以ScreenExpander
为例,安装完驱动后,打开ConsoleDriverApplication
,按n
开启一个虚拟显示器。
用WpfTestingClient
程序来接收视频流。
这时候我们就有一个第二屏的显示器,可以把应用拖到第二屏上去显示出来。
这样就实现了虚拟出第二显示器。
原理都在代码中,我们稍作分析。
还是原来的驱动入口 DriverEntry
:
extern "C" NTSTATUS DriverEntry(
PDRIVER_OBJECT pDriverObject,
PUNICODE_STRING pRegistryPath
)
{
WDF_DRIVER_CONFIG Config;
NTSTATUS Status;
PrintfDebugString("DriverEntry \n");
KdPrint(( " indirect" "DriverEntry\n"));
WDF_OBJECT_ATTRIBUTES Attributes;
WDF_OBJECT_ATTRIBUTES_INIT(&Attributes);
WDF_DRIVER_CONFIG_INIT(&Config,
Evt_IddDeviceAdd
);
Status = WdfDriverCreate(pDriverObject, pRegistryPath, &Attributes, &Config, WDF_NO_HANDLE);
if (!NT_SUCCESS(Status))
{
return Status;
}
return Status;
}
重要的函数看 Evt_IddDeviceAdd
,
_Use_decl_annotations_
NTSTATUS Evt_IddDeviceAdd(WDFDRIVER Driver, PWDFDEVICE_INIT pDeviceInit)
{
NTSTATUS Status = STATUS_SUCCESS;
WDF_PNPPOWER_EVENT_CALLBACKS PnpPowerCallbacks;
UNREFERENCED_PARAMETER(Driver);
PrintfDebugString("Evt_IddDeviceAdd\n");
IDARG_OUT_GETVERSION IddCxVersion;
IddCxGetVersion(&IddCxVersion);
if (!NT_SUCCESS(Status)) {
PrintfDebugString("IddCxVersion() Failed: 0x%x\n", Status);
}
PrintfDebugString("IddCx Version: 0x%lx\n", IddCxVersion.IddCxVersion);
if (IDDCX_VERSION_LATEST > IddCxVersion.IddCxVersion) {
PrintfDebugString("Error: Driver's IddCx Version 0x%lx is greater than System's 0x%lx\n",
IDDCX_VERSION_LATEST, IddCxVersion.IddCxVersion);
}
// Register for power callbacks - in this sample only power-on is needed
//
WDF_PNPPOWER_EVENT_CALLBACKS_INIT(&PnpPowerCallbacks);
PnpPowerCallbacks.EvtDeviceD0Entry = Evt_IddDeviceD0Entry;
WdfDeviceInitSetPnpPowerEventCallbacks(pDeviceInit, &PnpPowerCallbacks);
IDD_CX_CLIENT_CONFIG IddConfig;
IDD_CX_CLIENT_CONFIG_INIT(&IddConfig);
PrintfDebugString("IddConfig.Size After Init: %lu\n", IddConfig.Size);
// If the driver wishes to handle custom IoDeviceControl requests, it's necessary to use this callback since IddCx
// redirects IoDeviceControl requests to an internal queue. This sample does not need this.
IddConfig.EvtIddCxDeviceIoControl = Evt_IddIoDeviceControl;
IddConfig.EvtIddCxParseMonitorDescription = Evt_IddParseMonitorDescription;
IddConfig.EvtIddCxAdapterInitFinished = Evt_IddAdapterInitFinished;
IddConfig.EvtIddCxAdapterCommitModes = Evt_IddAdapterCommitModes;
IddConfig.EvtIddCxMonitorGetDefaultDescriptionModes = Evt_IddMonitorGetDefaultModes;
IddConfig.EvtIddCxMonitorQueryTargetModes = Evt_IddMonitorQueryModes;
IddConfig.EvtIddCxMonitorAssignSwapChain = Evt_IddMonitorAssignSwapChain;
IddConfig.EvtIddCxMonitorUnassignSwapChain = Evt_IddMonitorUnassignSwapChain;
#if IDDCX_VERSION_MINOR >= 4
IddConfig.EvtIddCxMonitorGetPhysicalSize = Evt_IddMonitorGetPhysicalSize;
#endif
Status = IddCxDeviceInitConfig(pDeviceInit, &IddConfig);
if (!NT_SUCCESS(Status))
{
PrintfDebugString("IddCxDeviceInitConfig Failed: 0x%x\n", Status);
return Status;
}
WDF_OBJECT_ATTRIBUTES Attr;
WDF_OBJECT_ATTRIBUTES_INIT_CONTEXT_TYPE(&Attr, AdapterWdfContext);
Attr.EvtCleanupCallback = [](WDFOBJECT Object)
{
// Automatically cleanup the context when the WDF object is about to be deleted
auto* pContext = WdfObjectGet_AdapterWdfContext(Object);
if (pContext)
{
pContext->Cleanup();
}
};
WDFDEVICE Device = nullptr;
Status = WdfDeviceCreate(&pDeviceInit, &Attr, &Device);
if (!NT_SUCCESS(Status))
{
PrintfDebugString("WdfDeviceCreate Failed!\n");
return Status;
}
Status = WdfDeviceCreateDeviceInterface(
Device,
(LPGUID)&GUID_DEVINTERFACE_INDIRECT_DEVICE,
NULL // ReferenceString
);
if (!NT_SUCCESS(Status)) {
PrintfDebugString("WdfDeviceCreateDeviceInterface failed.\n");
return Status;
}
Status = IddCxDeviceInitialize(Device);
if (!NT_SUCCESS(Status)) {
PrintfDebugString("IddCxDeviceInitialize Failed.\n");
return Status;
}
PrintfDebugString("Exit Evt_IddDeviceAdd.\n");
return Status;
}
接着看Evt_IddMonitorAssignSwapChain
-> AssignSwapChain
-> SwapChainProcessor
->RunCore
。
AcquiredBuffer.attach(Buffer.MetaData.pSurface);
if (!AcquiredBuffer.try_as(AcquiredTexture)) {
PrintfDebugString("[SwapChainProcessor::RunCore] Cannot Convert Acquired Buffer to Texture.\n");
goto EndOneSurfaceProcessing;
}
D3D11_TEXTURE2D_DESC TextureDesc;
AcquiredTexture->GetDesc(&TextureDesc);
//PrintfDebugString("Current Surface Width: Format: %u, Width: %u, Height: %u\n",
// SurDesc.Format, SurDesc.Width, SurDesc.Height);
D3D11_MAPPED_SUBRESOURCE MappedSubResc;
hr = m_Device->DeviceContext->Map(AcquiredTexture.get(), 0, D3D11_MAP_READ, 0, &MappedSubResc);
if (FAILED(hr)) {
if (hr == E_INVALIDARG) {
D3D11_TEXTURE2D_DESC StagingTextureDesc;
StagingTextureDesc = TextureDesc;
StagingTextureDesc.Usage = D3D11_USAGE_STAGING;
StagingTextureDesc.BindFlags = 0;
StagingTextureDesc.CPUAccessFlags = D3D11_CPU_ACCESS_READ;
StagingTextureDesc.MiscFlags = 0;
hr = m_Device->Device->CreateTexture2D(&StagingTextureDesc, nullptr, CopiedTexture.put());
if (FAILED(hr)) {
PrintfDebugString("Create Staging Texture failed: 0x%x\n", hr);
goto EndOneSurfaceProcessing;
}
m_Device->DeviceContext->CopyResource(CopiedTexture.get(), AcquiredTexture.get());
hr = m_Device->DeviceContext->Map(CopiedTexture.get(), 0, D3D11_MAP_READ, 0, &MappedSubResc);
if (FAILED(hr)) {
PrintfDebugString("Mapping Staging Texture failed: 0x%x\n", hr);
goto EndOneSurfaceProcessing;
}
AcquiredTexture = std::move(CopiedTexture);
}
else {
PrintfDebugString("Mapping GPU Texture failed: 0x%x\n", hr);
goto EndOneSurfaceProcessing;
}
}
// The image format is always DXGI_FORMAT_B8G8R8A8_UNORM,
// so the size of a frame is Height*Width*32/8 bytes.
DWORD dwImageSizeBytes = TextureDesc.Width * TextureDesc.Height * 32 / 8;
m_pImageBuf->dwWidth = TextureDesc.Width;
m_pImageBuf->dwHeight = TextureDesc.Height;
m_pImageBuf->dwMonitorIndex = m_pMonitorContext->MonitorIndex;
CopyMemory(m_pImageBuf->pData, MappedSubResc.pData, dwImageSizeBytes);
m_pMonitorContext->pAdapterContext->pAdaterClass->m_PipeServer.WriteBytes(
m_pImageBuf.get(),
sizeof(DWORD) * 2 + dwImageSizeBytes);
m_Device->DeviceContext->Unmap(AcquiredTexture.get(), 0);
在m_PipeServer.WriteBytes
中,把数据发走。
ScreenExpander
还涉及到相关的开启和关闭,两个驱动的写法,可以参考这源码来理解。
如果需要 demo的源代码,可以私信我。