WIN10系统 Indirect Display 虚拟显示器之特殊应用

时间:2024-04-04 12:29:32

by fanxiushu 2020-05-20 转载或引用请注明原始作者。

有人询问我是否可以实现这样一种功能:
对windows输出的每一帧图像数据显示做一些特殊处理(比如球形桌面,曲面化等特效),然后再显示到显示器上。
而且还不止一个人这样咨询过,虽然我不大清楚这种需求具体用在何处,估计也是一些特殊场所。

这种需求,最先想到的,也最直观的想法就是能否给显卡驱动添加一个过滤驱动,然后拦截图像数据,然后再做些特殊处理。
可惜想法是美好的,却是难以实现的,甚至是不大可能实现的。
首先windows中就没显卡过滤驱动一说。虽然windows许多的设备驱动程序,都有对应的过滤驱动,但是显卡驱动是个特例。
这是显卡驱动复杂性造成的,
显卡处理的本身就是数据量庞大的图像数据,少量数据是在系统内存中处理,大部分都是直接在显存中处理的。
如果给显卡驱动再挂载过滤驱动,获得的图像数据是继续在GPU中处理呢,还是运到CPU中处理呢,
系统中的软件绝大部分都需要借助CPU才能工作,这样就不得不把图像数据从显存搬到内存,处理完成之后再次搬到显存中,
这样不是没事找事么,这样的结果就是显示效率极其低下。
再说windows绘图是多方位的,显存中存储了多个windows窗口的图像数据,假设存在这么一个总的“framebuffer”,
由windows的dwm.exe窗口管理程序根据绘图变化情况,动态的把这些windows窗口数据合并到 “framebuffer”中,
再根据显示器的刷新率,比如每秒60HZ刷新率,
也就是大约16毫秒dwm.exe就再次把 “framebuffer” 中数据通过HDMI,DisplayPort等显示接口输出到显示器中,
这个速度都是极其快速的,基本都是利用显卡硬件在操作。
如果在其中硬插一杆,来个显卡过滤驱动,要我们通过软件方式转换变形一下 ”framebuffer“中的图像数据,这不是严重破坏显示速度么!

有人说windows中没有显卡过滤驱动,我们可以利用HOOK手段,自己制造一个”显卡过滤驱动“啊,确实可以这样做。
我CSDN上的文章也也介绍过这方面的内容:
https://blog.csdn.net/fanxiushu/article/details/82731673  WIN7以上系统WDDM虚拟显卡开发(WDDM Filter/Hook Driver 显卡过滤驱动开发之一)
但是这种HOOK,大部分都是用于制造一个额外的虚拟显示器,
因为在WIN10之前(WIN7,WIN8),是没法通过通用的编程方式生成额外显示器。
即使不生成额外显示器,通过HOOK DRIVER_INITIALIZATION_DATA结构里边的 DxgkDdiSetVidPnSourceAddress 回调函数,获取到
图像渲染的显存地址索引,再配合 HOOK DxgkddiQueryadapterinfo 回调函数,从而获取图像渲染的真正的显存的物理地址,
可是获取到这些显存地址能干嘛? 而且对于FLIP翻转渲染,这些地址都是动态变化的。
如果是单纯截屏,我们还得想办法把这个图像数据,从显存搬运到内存中,这样做的效果还不如GDI截屏来的简洁和有效。
如果是拦截,这事就更大了,这段显存地址不是我们能控制的,没办法像水龙头那样能截流。
即便能拦截,我们也会面临同样的问题:是在GPU中处理呢,还是在CPU中处理呢?
要获得更好的处理*度,就不得不使用CPU,把数据搬运到内存中处理,处理完成再次搬运回到显存。这样的效果谁能接受?

或许我们在应用层中,打dwm.exe程序的主意,dwm.exe是桌面管理程序,WIN8以上下图,dwm.exe是强制运行的。
主要功能是对桌面图像进行管理,比如多种窗口合成,各种绘图库的生成的图像合成,dwm.exe大部分时间任然是直接操作显存中的图像。
也许我们通过dwm.exe里边的某些未知接口函数,拦截到图像数据,
但是因为是“未知接口”,因此谁知道究竟该拦截哪些呢?我也不是专门搞**的。
再说,即便拦截成功了,也是同样的问题:是在GPU中处理呢,还是搬运到CPU中处理?

或许我们从更底层实现这种特殊需求,就是在显卡的输出接口,
比如HDMI,DisplayPort接口的地方,再插入一个我们制造的特殊硬件设备,这个硬件设备拦截HDMI,DP显示接口的图像数据流,
做些转换再发送到显示器。
但是这种做法就是纯硬件的做法了,已经与具体的操作系统没啥关系。这也不是本文描述的范围了。

我们也可以找到具体的显卡制造厂商,让他们在驱动和显卡中集成这种特殊需求,
但是这是具体的定制需求,也不是本文讨论的范围。

在CSDN上,专门介绍过windows10以上平台的Indirect Display Driver 虚拟显卡驱动。
https://blog.csdn.net/fanxiushu/article/details/93524220
Windows远程桌面开发之九-虚拟显示器(Windows 10 Indirect Display 虚拟显示器驱动开发)

当时的目的是为了给windows增加一个额外的虚拟显示器,以方便xdisp_virt远程控制程序使用扩展桌面效果。
然而 Indirect Display Driver 的实际用处,却不单是这样。
它的用处是给电脑增加一个其他接口,比如USB接口,来增加一个新的显示器。还比如Miracast无线接口的显示器。
这些都需要增加一个虚拟显示器驱动,然后把windows桌面图像输出到这些显示器中。
有些人把USB接口输出到显示器的设备,称作USB外置显卡,其实不是"显卡",   严格来说就是个扩展坞。
真正的显卡功能还是电脑内部插上的那块显卡。
那为何不制造真正的外置显卡呢? 首先就是接口速度跟不上。
现在电脑内插的独立显卡大都是 PCI-E 2.0 X16 接口的,X16代表速度,能达到 16GB/s 的速度,注意是字节,换算成位的话是 128 Gbps 。
我们再来看看电脑外接接口的速度:
USB3.0    5 Gbps
USB3.1   10Gbps
USB3.2    20Gbps
USB4.0    40Gbps (4.0标准正在指定中,这是据称的速度)
雷电3       40Gbps
HDMI2.0  18Gbps
HDMI2.1   48Gbps
DP1.4       32.4Gbps
DP2.0       80Gbps

这些外置接口算是电脑外置接口速度中最快的了,除了DP2.0 勉强能与PCI-E 2.0 X8相比较外,其他都无法比较,
因此目前的情况来看,实现真正的外置显卡还不大现实。
但是作为扩展坞,还是可行的。我们做个简单计算(实际速度会有少许差别):

1920X1080,32位真彩色,60HZ每秒的刷新率,需要多大的传输带宽:
1920*1080* 4 * 60* 8 = 3,996,057,600 bps = 3.7 Gbps,  USB3.0接口就能胜任这个速度。
如果是 120HZ的刷新率(比如有些刷新率),需要 3.7*2=7.4Gbps,需要USB3.1以上才可以。

至于 4K 显示器, 60HZ刷新率:
3840*2169*4*60*8 = 14.9 Gbps, 这个需要 USB3.2以上才能支持。
120HZ刷新率, 需要 14.9=30Gbps,这个需要USB4.0以上或雷电3以上才能支持。

至于8K显示器,60HZ刷新率:
7680*4320*4*60*8 = 60Gbps, 这个目前来说,就只有DP2.0才能支持了。

列举了这些数据,与我们文章最开头提到的需要实现特殊功能有何用处?
其实就是我们完全可以使用Indirect Display Driver驱动,实现一个USB显卡扩展坞的显示器。
然后在Indirect Display驱动中实现我们需要的各种效果(比如球面化桌面,曲面化桌面等)。这是目前最好的选择。
因为 Indirect Display驱动是UMD驱动,也就是应用层驱动,图像数据的处理截取全都在应用层。
我们完全可以在Indirect Display驱动中使用DirectX做GPU加速的图像处理,或者实现CPU加速方式的各种图像转换,等等。
而这些图像处理都是非常方便的,而且也是高效的。
再把转换好的图像数据输入给USB接口。
我们需要制造一个专门的USB接口的设备,因为现在显示器大都是HDMI或DisplayPort接口的,所以增加一个接口转换硬件。
相信这对硬件工程师来说,都是容易办到的。这也花不了多少成本。
显示器接到这个简单的硬件设备上,而真正的显卡不需要接任何显示器。
这样windows10系统,就只会把我们这个USB扩展坞接的显示器当成主显示器,也是唯一的显示器。
(当然这样的显示器,windows的启动过程是看不到的,是黑屏的。但是如果不在意或者不出BUG,估计也不会去关心。)
这样文章开头提到的特殊效果自然就能实现了。
其实像DuetDIsplay,DisplayLink这些专门做扩展显示器的,把他们的思路稍微转换一下,
在生成的虚拟显示器驱动中,增加对这些桌面输出图像特效的处理,自然就能满足文章开头提到的特殊需求。

上面介绍的是使用一个实实在在的USB硬件设备来达到这种特殊效果,
有没有不使用任何额外的硬件设备,就利用现成的硬件来达到这种效果呢?
这也是提问者的最终目的,不需要另外制造硬件,但是也要达到这种特殊效果。
其实利用Indirect Display驱动也可以办到,就是不大完美,但是这也是一个解决办法:
windows10系统利用Indirect display 驱动生成一个不需要任何硬件的虚拟显示器,然后再利用电脑中一个真实的显示器。
这样系统中实际上有两个显示器,设置成分辨率大小一样的成扩展显示模式,让虚拟显示器成为主显示器,
再然后运行一个程序,这个程序以全屏方式填充到真实显示器中,并且这个程序截取虚拟显示器内容并且显示出来。
这样看到的效果就是真实显示器显示的完全是虚拟显示器的内容,这个程序再对截取到的虚拟显示器的图像数据做些转换,
自然就能达到这种特殊效果。
至于说这种办法不大完善,是因为系统中毕竟出现了两个显示器,而且还是扩展模式。
鼠标移动后很容易”飘“,不知道移到哪去了(其实是移动到别的显示器上了)
还有就是新打开的程序也很容易跑到别的显示器上面。因此还得开发一些额外的程序解决这些问题。

如果对这种办法有兴趣,可以自行去实现。至于Indirect Display驱动的开发问题。
可以直接借用我在GITHUB上xdisp_virt项目下的indirect_display目录中的驱动。
https://github.com/fanxiushu/xdisp_virt
上面链接中的indirect_display目录中有详细安装使用介绍。

其中的indirect_display-plug.exe程序是模拟插入虚拟显示器。
而 indirect_display-image.exe程序则是显示虚拟显示器内容的程序。
这次我把indirect_display-image.exe稍微做些修改,把图形数据做个球面化的效果,如下图所示:
这个就是变形之后的虚拟桌面图像,

WIN10系统 Indirect Display 虚拟显示器之特殊应用

然后把这个程序全屏化,就像下图这样:
WIN10系统 Indirect Display 虚拟显示器之特殊应用

如果不看最上面的远程桌面抓屏图像,就只看笔记本的屏幕效果,谁能知道其实就是简单的两个扩展桌面玩的把戏。
如果有兴趣,可以直接使用我的Indirect Display驱动来实现类似功能,虽然 indirect_display-image.exe并没公开如何获取图像数据源。
但是你可以使用DXGI的方式来截取虚拟桌面的图像数据。