WIN10系統 Indirect Display 虛擬顯示器之特殊應用

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稍微做些修改,把圖形數據做個球面化的效果,如下圖所示:
這個就是變形之後的虛擬桌面圖像,



然後把這個程序全屏化,就像下圖這樣:


如果不看最上面的遠程桌面抓屏圖像,就只看筆記本的屏幕效果,誰能知道其實就是簡單的兩個擴展桌面玩的把戲。
如果有興趣,可以直接使用我的Indirect Display驅動來實現類似功能,雖然 indirect_display-image.exe並沒公開如何獲取圖像數據源。
但是你可以使用DXGI的方式來截取虛擬桌面的圖像數據。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章