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

本文介绍了如何通过驱动开发在Windows 10上创建虚拟显示器,实现无物理屏幕的情况下扩展显示。通过分析源码,讲解了关键函数如`DriverEntry`、`DispatchCreate`、`DispatchIoctl`等在数据传输和屏幕投影中的作用,从而实现将应用程序拖到虚拟第二显示器上显示的功能。
摘要由CSDN通过智能技术生成

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

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

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

关于虚拟屏幕,微软也有相应的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的源代码,可以私信我。

  • 2
    点赞
  • 28
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 10
    评论
Indirect display驱动开发,是一种将多个显示设备进行联动,以实现显示效果的技术。这种技术对于多个设备之间的信息交互、数据共享均有着较为重要的作用。 首先,indirect display技术可以将多个显示设备进行联动,其实现原理是通过引入一个控制设备,统一对多个显示设备进行信号输出,进而实现对多个设备的控制。这样,在同一时刻,多个设备可以达到同步显示,从而有效提高了工作效率。在信息展示方面,也能更加清晰地展示各种信息,提高了用户的使用体验。 其次,在底层架构上,indirect display技术采用了多种技术进行支持,如数据压缩、分割、数据传输等等,这样可以更好地保障数据的传输效率和数据的完整性,从而更加稳定可靠地实现设备之间的数据交互。 由于indirect display技术架构较为复杂,在驱动开发过程中,需要开发人员熟练掌握各类技术,例如驱动接口开发、显示控制逻辑实现等,才能够更加顺利地进行开发工作。同时,对于不同的硬件设备,也需要有不同的驱动适配,因此开发人员还需要具备较强的适应性。 总体而言,indirect display驱动开发的实现,可以将多个显示设备集成在一起,提高了显示效果和信息传递的效率。随着多显示设备的出现,indirect display技术也有更广泛的应用前景,尤其在电子信息领域的研究开发中,indirect display技术还将有着更为广阔的应用空间。
评论 10
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

go2coding

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值