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

这篇具有很好参考价值的文章主要介绍了Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

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

Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发
在设置中显示中,如果我们有两块屏幕,在显示器中自然的会出现两个,在其中可以对两块屏幕进行相应的设置。

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

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

VirtualDisplay

ScreenExpander

ScreenExpander 会出现Error value: 259 Message的问题,可以参考我自己的修改代码。
ScreenExpander为例,安装完驱动后,打开ConsoleDriverApplication,按n开启一个虚拟显示器。

Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发
WpfTestingClient 程序来接收视频流。

Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发
这时候我们就有一个第二屏的显示器,可以把应用拖到第二屏上去显示出来。

Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发
这样就实现了虚拟出第二显示器。

原理都在代码中,我们稍作分析。

还是原来的驱动入口 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的源代码,可以私信我。文章来源地址https://www.toymoban.com/news/detail-414918.html

到了这里,关于Windows 10驱动开发入门(五):创建虚拟显示器 Indirect Display驱动开发的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【FPGA显示驱动(Display)】- 使用Verilog实现8位数字显示器

    【FPGA显示驱动(Display)】- 使用Verilog实现8位数字显示器 FPGA是一种可编程的逻辑器件,它可以通过不同的配置来实现多种应用。在本文中,我们将探讨如何使用Verilog语言在FPGA上实现8位数字显示器。 硬件环境 Xilinx ISE Design Suite 14.7 FPGA开发板 八段数码管 设计实现 在Verilog代

    2024年02月04日
    浏览(34)
  • VGA显示器驱动设计与验证

            VGA全称是Video Graphics Array,即视频图形阵列,是模拟信号的一种视频传输标准。VGA就是如今很多显示器上连接主机的信号传输接口,有三排15个引脚,其中比较重要的是3根RGB彩色分量信号和2根扫描同步信号HSYNC和VSYNC针,其引脚编号图如下所示: 引脚 定义 引脚 定

    2024年02月11日
    浏览(37)
  • Ubuntu20.04 配置虚拟显示器(最高2048*1152)和切回物理显示器

    1、安装软件 用终端安装虚拟显示器软件 2、添加配置文件 进入/usr/share/X11/xorg.conf.d/ 创建xorg.conf文件 编辑xorg.conf文件 往里面添加以下内容: 3、重启电脑 4、后续如果想要却换回物理显示器,只需卸载卸载 xserver-xorg-core-hwe-18.04 和 xserver-xorg-video-dummy 包。 5、删除 xorg.conf 文件

    2024年04月14日
    浏览(32)
  • 显示器10bit

    显示器的参数有面板材质、屏幕比例、响应时间、刷新率、分辨率、色域等。   显示器bit是什么意思?越大越好吗? 我们知道显示器的色彩能力主要是三个参数决定的,分别为 色准、色域以及色深 。 色准:色准指的是显示器的色彩准确度; 色域:色域指的是显示器的色彩

    2024年02月03日
    浏览(36)
  • VGA驱动原理(以1080p显示器为例)

    1080p显示器的刷新率是60Hz(每秒刷新67500/1125=60帧图像),行频率是67.5KHz(每秒刷新148500000/2200=67 500行),像素频率是148.5MHz(每秒刷新148 500 000个像素点) 1080p显示器的驱动时序如下图所示, HSync,VSync信号为正极性 下图是基于1920 x 1080@60Hz这个分辨率的具体时序参数表 对照时序参数

    2024年03月20日
    浏览(56)
  • ubuntu设置虚拟显示器且远程连接

    ubuntu 20.04 在 /usr/share/X11/xorg.conf.d/ 中添加 xorg.conf 文件(系统默认就会使用虚拟显示器): 虽然配置上面写了 “1920x1080”,但是实际上最大支持 “1360x768”,重启即可生效。 使用teamviewer、todesk或者向日葵等远程工具实现远程连接即可。 重启计算机后,系统会默认使用虚拟显

    2024年02月16日
    浏览(42)
  • FPGA—HDMI 显示器驱动设计与验证(附代码)

    目录 1.理论 2.实操 2.1 顶层模块 2.2 时钟生成模块 2.3 HDMI 驱动控制模块 2.3.1 编码模块 2.3.2 并行转串行模块 2.4 顶层仿真验证 3.总结 HDMI简介       VGA 接口体积较大;且传输的模拟信号易受外界干扰。因此在VGA 接口之后,首先推出的是 DVI 接口, DVI 是基于 TMDS(Transition Minim

    2024年02月10日
    浏览(35)
  • 使用STM32微控制器驱动LCD1602显示器

    驱动LCD1602显示器是嵌入式系统常见的任务之一,而STM32微控制器因其灵活性和丰富的外设而成为了广泛采用的解决方案。 在这篇文章中,我们将探讨如何使用STM32微控制器来驱动LCD1602显示器。我们将从STM32的GPIO配置、延时函数以及LCD1602的初始化和写入数据等方面展开讨论,

    2024年04月17日
    浏览(33)
  • windows驱动开发8:虚拟摄像头方案

    一、摄像头框架 在业务场景中,有许多是需要应用能够通过摄像头的方式来访问相关的音视频数据,比如美颜、摄像头多路复用、IP摄像头接入视频会议等。这些功能通过虚拟摄像头的方式来实现,是一个比较通用的解决方案。那么如何及选用哪种技术方案来开发虚拟摄像头

    2024年02月02日
    浏览(34)
  • 树莓派ubuntu系统安装+远程桌面vnc+没有显示器进行远程桌面连接设置+虚拟显示器

    jj首先将SD卡插入·读卡器·之后进行格式化(SD Card Formatter下载:(9条消息) 树莓派上手前的准备工作(一)——格式化sd卡(sd卡格式化工具的使用)_树莓派sd卡格式化_peng_YuJun的博客-CSDN博客)  选择插入的U盘之后点击Format(小心选择的磁盘,不要选错,要不然哭都来不及)  点击

    2024年02月10日
    浏览(58)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包