如何在云电脑串流中实现多屏操作——WDDM虚拟显示器开发

article/2025/10/10 20:06:41

文章目录

  • 如何在云电脑串流中实现多屏操作——WDDM虚拟显示器开发
    • 1. 概述
    • 2. DxgkInitialize
    • 3. HOOK框架
    • 4. VIDPN
    • 5. 虚拟显示器
    • 6. 实现效果

如何在云电脑串流中实现多屏操作——WDDM虚拟显示器开发

“虚拟显示器”是一种新型的计算机图形显示端口技术,它可以将一台计算机的屏幕,通过虚拟化出多个独立的区域单独显示出来,每个显示器都可以同时运行多个应用程序,以便用户可以更容易地管理和操作他们。虚拟显示器在当前的云电脑或办公应用中,有着这十分重要的作用,可以解决如下的场景问题:

场景1:显示器需要进行多屏拓展,但外接端口不足
通常,我们看到的显示器都是连接在HDMI或者VGA等接口的。但是,在市场上我们可以看到一些产品,通过USB转HDMI(VGA)拓展坞,连接显示器,可以扩展显示适配器上面原有接口的限制,可以实现复制屏或者扩展屏

场景2:高性能GPU的云电脑如何获取显卡的显示数据
在云桌面的应用中,往往需要在1台宿主机上通过vGPU的给10台虚拟机提供算力,这种方式对通常的办公来说没什么问题;
但是近年基于AI的浮点算力需求,特别是这2年的高GPU性能的VDI里,需要给3D设计,游戏引擎等行业提供充足的算力,所以经常会采用显卡透传的方式,让每块显卡给每个虚拟机单独享用。这情况下就需要在虚拟机/宿主机上安装显卡的硬件驱动,这也会面临着如何采集数据的问题。

场景3:远程控制时进行云办公,或者云游戏时,希望多屏操作
在远程控制虚拟机或者宿主机时,我们想通过平板、手机多设备联合进行多屏进行操作,提升办公效率;或者玩云游戏时,游戏分辨率不想被拉伸,需要深度适配当前屏幕分辨率,这就需要用到虚拟显示器,来进行windows的虚拟显示器插入

那么,这些解答以上问题,都可以通过“虚拟显示器”技术完成,这里有两项技术需要解决:
1. USB可以将图像等信息通过转接线转换成HDMI等信号,让显示器显示数据。
2. 系统需要创建一个虚拟的显示器,让系统识别到有插入两个显示器。

本文我们探究一下虚拟显示器的实现技术
这里是基于Windows 10 之前版本的WDDM虚拟显示器的开发,在Windows 10 1067版本之后,Windows主动支持了显卡过滤框架,可以直接通过Indirect Display Driver来实现。
但是在Win7这些系统,微软并没有提供相关框架;只能采用mirror驱动,因为显卡特性是,得插入显示器后显卡驱动才能正常工作,所以我们这里提供一种HOOK显卡驱动,然后模拟DXGK的各种请求,伪造一个显示器来实现虚拟显示器,从过滤驱动获取到显示画面
这样就可以通过USB或者网线等更广泛廉价的接口,或者完成透传显卡

1. 概述

我们先来看一下WDDM显示驱动模型的框架图,如下:
在这里插入图片描述

通过这个图我们可以发现,无论是D3D,OpenGL还是GDI绘图,最终在内核层都是通过Dxgkrnl.sys模块来实现;Dxgkrnl.sys取代了XP下面的Win32k.sys,成为了新一代的图像显示子系统库。

DirectX图形库都需要在应用层提供用户模式的驱动,这样做的目的是可以在应用层渲染图像,这样大量的图像相关计算全在用户层完成。

无论是D3D,OpenGL还是GDI绘图最终的图像都是通过Dxgkrnl.sys驱动,提交给内核模式的显卡驱动去管理和分配(Display Moinport Driver)。内核模式的驱动的功能是对资源的管理分配,显存和内存之间的DMA数据传输, GPU管理等。

对于当前的显卡设备按照功能将它分成显示和计算两类,因此针对我们的驱动来讲一般可以有如下三种:

  1. Display Only Driver仅仅用作显示,并不支持运算功能。
  2. Render Only Driver仅仅支持运输,不支持显示(一般这种我们很少见)。
  3. Full Graphics Driver(Complete Function Driver)全功能驱动,既支持显卡的显示功能,又支持运算功能。

2. DxgkInitialize

对于Display Miniport Driver驱动,通过调用DxgkInitialize来注册回调函数来实现的,这个是Miniport驱动的基本框架。在MSDN上面我们可以找到实例:

NTSTATUS
DriverEntry(IN PDRIVER_OBJECT DriverObject,IN PUNICODE_STRING RegistryPath)
{DRIVER_INITIALIZATION_DATA DriverInitializationData = {0};PAGED_CODE();//...DriverInitializationData.Version  = DXGKDDI_INTERFACE_VERSION;DriverInitializationData.DxgkDdiAddDevice  = AtiAddDevice;DriverInitializationData.DxgkDdiStartDevice  = AtiStartDevice;DriverInitializationData.DxgkDdiStopDevice  = AtiStopDevice;//...return DxgkInitialize(DriverObject,RegistryPath,&DriverInitializationData);
}

DxgkInitialize作为一个核心函数,我们看一下这个函数的具体流程;这个也是我们虚拟显示器需要实现的核心点。

NTSTATUS DxgkInitialize(_DRIVER_OBJECT *DriverObject, _UNICODE_STRING *RegistryPath, _DRIVER_INITIALIZATION_DATA *DriverInitializationData)
{NTSTATUS Status;//...Status = DlpLoadDxgkrnl(&FileObject, &DxgDeviceObject);if (Status >= 0 || Status == STATUS_IMAGE_ALREADY_LOADED){Status = DlpGetIoctlCode(&Information);if (Status < 0){goto Exit;}for (i = Information; ; i = 2293823){Status = DlpCallSyncDeviceIoControl(DxgDeviceObject, i, 1, 0, 0, &DpiInitialize, 4u, &Information);Status = v9;if (v9 != STATUS_INVALID_DEVICE_REQUEST)break;}}if (Status >= 0){//...return DpiInitialize(DriverObject, RegistryPath, DriverInitializationData);}//...
}

这里我们通过DlpCallSyncDeviceIoControl来获取到一个函数地址DpiInitialize,这里实际调用的就是DpiInitialize函数了。

3. HOOK框架

因此我们的HOOK原理比较简单了,就是替换DpiInitialize函数达到HOOK的目的。因为DpiInitialize这个函数通过DeviceIoControl来获取,因此我们拦截IOCTRL的请求,就可以获取到函数地址了。

这个拦截过程是非常简单的,属性DWM驱动的就非常熟悉了:

  1. IoCreateDevice创建一个设备对象。
  2. 通过IoAttachDeviceToDeviceStack挂载到DXGRNL.SYS上面即可。

因此我们可以得到HOOK基本框架

NTSTATUS HookDispatch(PDEVICE_OBJECT DeviceObject, PIRP Irp)
{PIO_STACK_LOCATION IoStackLocation = IoGetCurrentIrpStackLocation(Irp);switch (IoStackLocation->MajorFunction){case IRP_MJ_CREATE:break;case IRP_MJ_CLEANUP:break;case IRP_MJ_CLOSE:break;case IRP_MJ_INTERNAL_DEVICE_CONTROL:if (irpStack->Parameters.DeviceIoControl.IoControlCode == IOCTL_XXXX){//HOOK 函数地址return STATUS_SUCCESS;}break;}return DispatchToLower(DeviceObject, Irp);
}

因此我们得到了DpiInitialize的函数地址,通过替换我们就可以进行具体显卡驱动回调函数的HOOK了。

NTSTATUS HookDpiInitialize(PDRIVER_OBJECT DriverObject,PUNICODE_STRING RegistryPath,DRIVER_INITIALIZATION_DATA* DriverInitData)
{//...DriverInitData->DxgkDdiAddDevice = DxgkDdiAddDevice;DriverInitData->DxgkDdiRemoveDevice = DxgkDdiRemoveDevice;DriverInitData->DxgkDdiStartDevice = DxgkDdiStartDevice;DriverInitData->DxgkDdiStopDevice = DxgkDdiStopDevice;//...
}

4. VIDPN

上面我们已经完成了HOOK框架了,可以开始进行显卡回调函数的编写了;但是在完成显卡回调函数的实现之前,我们需要掌握一个概念VIDPN(Video Present Network),这个是一个非常抽象的概念。MSDN上面说的比较详细,下面用最简单的方式来介绍一下,希望能够理解。

VIDPN是一个视频网络的软件抽象模型,在这个抽象模型中包括如下概念:

  1. 拓扑图。
  2. 路径。
  3. 源模式集合。
  4. 目标模式集合。

在这里插入图片描述

在上图中,可以得到如下关系:

  1. 一个VIDPN中包括一个拓扑。
    • 一个拓扑对映多个路径。
    • 一个路对应一个目标。
    • 一个源可以在多个路径中对应多个目标。
  2. 一个VIDPN包括多个源模式集合(每个源有自己的模式集合)。
  3. 一个VIDPN包括多个目标模式集合(每个目标也有自己的模式集合)。

VIDPN在驱动中通过D3DKMDR_HVIDPN来标识,我们通过DXGKRNL提供的函数DxgkcbQueryvidpninterface来获取DXGK_VIDPN_INTERFACE:

DXGKCB_QUERYVIDPNINTERFACE DxgkcbQueryvidpninterface;NTSTATUS DxgkcbQueryvidpninterface(IN_CONST_D3DKMDT_HVIDPN hVidPn,IN_CONST_DXGK_VIDPN_INTERFACE_VERSION VidPnInterfaceVersion,DEREF_OUT_CONST_PPDXGK_VIDPN_INTERFACE ppVidPnInterface
)
{...}

DXGK_VIDPN_INTERFACE结构定义如下:

typedef struct _DXGK_VIDPN_INTERFACE {DXGK_VIDPN_INTERFACE_VERSION               Version;DXGKDDI_VIDPN_GETTOPOLOGY                  pfnGetTopology;DXGKDDI_VIDPN_ACQUIRESOURCEMODESET         pfnAcquireSourceModeSet;DXGKDDI_VIDPN_RELEASESOURCEMODESET         pfnReleaseSourceModeSet;DXGKDDI_VIDPN_CREATENEWSOURCEMODESET       pfnCreateNewSourceModeSet;DXGKDDI_VIDPN_ASSIGNSOURCEMODESET          pfnAssignSourceModeSet;DXGKDDI_VIDPN_ASSIGNMULTISAMPLINGMETHODSET pfnAssignMultisamplingMethodSet;DXGKDDI_VIDPN_ACQUIRETARGETMODESET         pfnAcquireTargetModeSet;DXGKDDI_VIDPN_RELEASETARGETMODESET         pfnReleaseTargetModeSet;DXGKDDI_VIDPN_CREATENEWTARGETMODESET       pfnCreateNewTargetModeSet;DXGKDDI_VIDPN_ASSIGNTARGETMODESET          pfnAssignTargetModeSet;
} DXGK_VIDPN_INTERFACE;

DXGK_VIDPN_INTERFACE这个结构出发,我们能够获取到VIDPN包括的所有信息(拓扑,路径,模式等)。

5. 虚拟显示器

启动显卡适配器的时候,Dxkgrnl会调用回调函数BddDdiStartDevice,在DOD示例代码中这个函数实现如下:

NTSTATUS BASIC_DISPLAY_DRIVER::StartDevice(_In_  DXGK_START_INFO*   pDxgkStartInfo,_In_  DXGKRNL_INTERFACE* pDxgkInterface,_Out_ ULONG*             pNumberOfViews,_Out_ ULONG*             pNumberOfChildren)
{PAGED_CODE();//...*pNumberOfViews = MAX_VIEWS;*pNumberOfChildren = MAX_CHILDREN;return STATUS_SUCCESS;
}

在这个函数中有两个参数返回的是外接子设备的信息:

  1. pNumberOfViews返回的源数目。
  2. pNumberOfChildren返回子设备的数目(显示接口)。

我们通过这个函数大致可以实现如下来新增一个显示器:

NTSTATUS BddDdiStartDevice(_In_  DXGK_START_INFO*   pDxgkStartInfo,_In_  DXGKRNL_INTERFACE* pDxgkInterface,_Out_ ULONG*             pNumberOfViews,_Out_ ULONG*             pNumberOfChildren)
{PAGED_CODE();//CallOrgBddDdiStartDevice*pNumberOfViews += 1;*pNumberOfChildren +=1;return STATUS_SUCCESS;
}

新增显示器之后我们还需要做的事情是:

  1. BddDdiQueryChildRelations虚拟显示器信息。
  2. BddDdiQueryChildStatus虚拟显示器状态。
  3. BddDdiQueryDeviceDescriptor虚拟显示器的EDID信息。
  4. BddDdiEnumVidPnCofuncModality调整虚拟显示器的VIDPN关系。

通过上述接口的实现之后,我们就完成了显示器的虚拟化。

6. 实现效果

通过调整虚拟显示器的信息,我们可以实现如下常见场景:
1、在虚拟机里,插入任意分辨率的显示器,以适配我们的当前显示的屏幕(如ipadmini6-2266x1488),不会在渲染时,产生分辨率拉伸畸变(图6.1);

2、我们在用远程控制连接受控端时,通过热插拔虚拟显示器,实现远程双屏操作的 (图6.2)

图6.1——在虚拟机里插入通用显示器图6.1图6.2-这里用todesk软件远程连接云端服务器,由于通过这款软件没有虚拟显示器方案,只能串流桌面,所以我们的云端实现了双屏后,利用todesk自带的多屏串流切换功能,能够精准识别到2个分辨率显示器,实现双屏展示操作在这里插入图片描述

后续我们还将介绍如何通过虚拟显示器,实现窗口化独立串流的分享


http://chatgpt.dhexx.cn/article/a8ITEKMR.shtml

相关文章

ubuntu开机自启vnc虚拟显示器并使用向日葵远程连接

虚拟显示器 设置开机自动解锁 解锁后打开自动登录 1、预安装 依赖 sudo apt-get update sudo apt-get install xfce4 sudo apt install gnome-panel gnome-settings-daemon metacity nautilus gnome-terminal ubuntu-desktop sudo apt-get install vnc4server 初始化 在终…

RealVNC Server Ubuntu 20.04 无显示器连接 虚拟显示器

以前尝试过完全不接显示器&#xff0c;vnc连接设置总是不成功&#xff0c;这次很容易做成功了&#xff0c;记录一下。 以前记录的远程桌面使用心得&#xff1a; 远程桌面使用心得_捉不住的鼬鼠的博客-CSDN博客 RealVNC远程连接带显示器模式&#xff1a; Ubuntu18.04使用Rea…

【正版软件】Virtual Display Manager 虚拟显示器布局配置管理软件

前言 根据包括微软研究院在内的许多最新研究&#xff0c;多显示器系统以及更大的显示器可将用户工作效率提高 10% 至 50%。然而&#xff0c;多显示器采用的增长以及大屏幕显示器受到以下几个因素的影响&#xff1a; 购买额外硬件&#xff08;显示器和更新的视频板&#xff09…

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

在开发或者办公中,越大的屏幕看起来就显示越舒服了,通常我们的做法是有两块屏幕,这样显示的内容就变多了,可以很容易提高办公的效率。 在设置中显示中,如果我们有两块屏幕,在显示器中自然的会出现两个,在其中可以对两块屏幕进行相应的设置。 在这个驱动中,我们要解决的…

linux usb 虚拟显示器,Linux KVM虚拟机挂载主机USB接口设备

查看USB设备信息 harveymeilinux-7zyd:~> lsusb Bus 001 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 002 Device 002: ID 8087:0024 Intel Corp. Integrated Rate Matching Hub Bus 001 Device 001: ID 1d6b:0002 Linux Foundation 2.0 root h…

Virtual Display Manager(windows虚拟显示器软件)官方中文版V3.3.2.44650 | Win7/win10虚拟显示器下载

Virtual Display Manager 是一款非常实用的Windows虚拟显示器软件&#xff0c;通过附加虚拟显示器的便利性来补充您现有的单显示器或多显示器系统&#xff0c;这些显示器可以使用现有硬件共享现有的物理屏幕&#xff0c;适用于任意数量的物理显示器&#xff0c;并且可针对每个物…

Ubuntu18.04虚拟显示器+远程桌面

需求 ubuntu主机开启远程桌面功能&#xff0c; 实现无显示器的情况下的远程访问。 主机环境 rogerubuntu:~$ lsb_release -a No LSB modules are available. Distributor ID: Ubuntu Description: Ubuntu 18.04.6 LTS Release: 18.04 Codename: bionic roge…

windows虚拟显示器开发(三)USB显示器

我们常用的显示器接口有HDMI、VGA等接口&#xff0c;这些接口是直接在显卡上的&#xff0c;当显示器插在显卡上&#xff0c;显卡就直接可以将显示信号输出到显示器了。 关于USB显示器跟HDMI之类的显示器有本质区别&#xff0c;我们需要实现的有两个&#xff1a; 在USB上插入一…

远程服务器虚拟显示器(Ubuntu 20.04 LTS)

远程服务器虚拟显示器&#xff08;Ubuntu 20.04 LTS&#xff09; 1. 准备工作2. 安装软件包3. 修改配置文件4. 卸载虚拟显示器5. 异常处理 1. 准备工作 安装 ssh sever 并开启&#xff0c;确保虚拟显示器配置失败后&#xff0c;无法正常显示&#xff0c;仍可以通过ssh连接至服…

windows7/windows10 虚拟显示器部署(Virtual monitor)

最近有些网友看了我之前的博客之后&#xff0c;向我要虚拟显示器的bin文件&#xff0c;由于之前代码是绑定在VDI下的&#xff0c;没有单独的虚拟显示器代码&#xff0c;所以抽空提取了下相关代码&#xff0c;单独编译。 百度网盘地址&#xff1a;虚拟显示器下载地址 安装与卸…

Ubuntu 20.04 虚拟显示器 1080P 配置

Ubuntu 20.04 虚拟显示器 1080P 配置 一、背景二、配置方法1&#xff09;安装软件2&#xff09;添加配置文件3&#xff09;重启 三、效果Reference &#xff01;&#xff01;&#xff01;说明&#xff0c;必看 Σ(⊙▽⊙"a &#xff01;&#xff01;&#xff01; 1.完成配…

windows虚拟显示器开发(一)

目录 虚拟显示器概述虚拟显示器原理方案多显卡&#xff0c;多显示器一显卡&#xff0c;多显示器 听听大牛怎么说展望 虚拟显示器概述 最近因工作需要&#xff0c;需要在物理显卡上虚拟出一个显示器&#xff0c;我用的操作系统是win7&#xff0c;查询了下官方文档和网络资源&am…

Qt源码分析--QAction

定义&#xff1a; 在应用程序中&#xff0c;可以通过菜单、工具栏按钮和键盘快捷键调用许多常用命令。 由于用户希望每个命令都以相同的方式执行&#xff0c;而不管使用的用户界面如何&#xff0c;将每个命令表示为一个动作&#xff08;QAction&#xff09;是很有用的。 调用…

皮肤切换QAction的使用

/* 显示菜单 */ QMenu * menu ui->menuButton->getmenu(); //把几种皮肤加进来 //前面加图片后面加文字 b1 new QAction(QIcon(":/images/menu.png"), tr("&星球皮肤"), this); b2 new QAction(QIcon(":/images/menu.png"), tr(&qu…

Qt creator中操作QAction加入QToolBar

背景&#xff1a; 个人笔记。 我之前没有系统化学习过任何资料&#xff0c;使用很多工具都是按需出发&#xff0c;直接上手&#xff0c;遇到问题再研究的。所以会有一些弯路。本文言语中难免有对个人情绪的生动描述&#xff0c;希望不要影响读者心情&#xff0c;这只是我学习过…

Qt学习:QAction系列详解

一、QAction类详解 【详细描述】 QAction类提供了抽象的用户界面action&#xff0c;这些action可以被放置在窗口部件中。 应用程序可以通过菜单&#xff0c;工具栏按钮以及键盘快捷键来调用通用的命令。由于用户期望每个命令都能以相同的方式执行&#xff0c;而不管命令所使用的…

QAction、QWidgetAction、QActionGroup

QAction 一、描述 在应用程序中&#xff0c;可以通过菜单、工具栏按钮和键盘快捷键调用许多常用命令。由于用户希望每个命令都以相同的方式执行&#xff0c;因此无论使用何种用户界面&#xff0c;将每个命令表示为一个动作是很有用的。 二、类型成员 1、enum QAction::Acti…

QAction类详解

QAction类提供了抽象的用户界面action&#xff0c;这些action可以被放置在窗口部件中。 应用程序可以通过菜单&#xff0c;工具栏按钮以及键盘快捷键来调用通用的命令。由于用户期望每个命令都能以相同的方式执行&#xff0c;而不管命令所使用的用户界面&#xff0c; 这…

Qt 动作(QAction)

Qt 使用QAction类作为动作,QAction包含了图标、菜单文字、快捷键、状态栏文字、浮动帮助等信息,Qt自己选择使用哪个属性来显示&#xff0c;无需我们关心。同时&#xff0c;Qt 能够保证把QAction对象添加到不同的菜单、工具栏时&#xff0c;显示内容是同步的。也就是说&#xff…

Qt扫盲-QAction理论总结

QAction理论总结 一、概述二、使用 一、概述 在应用程序中&#xff0c;许多常用命令可以通过 菜单、工具栏按钮 和 键盘快捷键 调用。由于用户希望以相同的方式执行每个命令&#xff0c;而不管使用什么用户界面&#xff0c;因此将每个命令表示为一个Action操作是有效的。可以将…