Bootstrap

Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发

在开发或者办公中,越大的屏幕看起来就显示越舒服了,通常我们的做法是有两块屏幕,这样显示的内容就变多了,可以很容易提高办公的效率。

在这里插入图片描述
在设置中显示中,如果我们有两块屏幕,在显示器中自然的会出现两个,在其中可以对两块屏幕进行相应的设置。

在这个驱动中,我们要解决的问题是,我们没有物理的第二块屏幕,我们通过驱动的方式,虚拟出第二屏幕出来,只要我们得到第二屏幕的数据,我们很容易可以把屏幕数据流投影到想投的地方。

关于虚拟屏幕,微软也有相应的demo。关于 Indirect Display的微软demo,可以去相应的地方找到文档,这里几个比较重要的github项目如下:

VirtualDisplay

ScreenExpander

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的源代码,可以私信我。

;