[授權原創(chuàng)][IDD][本站難得一見的正經文] Chapter 2: 參上! 年輕人的第一臺虛擬現(xiàn)實器

開局一目錄

我大致的將 IDD 的實現(xiàn)按照功能分為下面的三個章節(jié)

  1. 初始化一個沒有功能的 IDD 殼子.
  2. [本文] 通過 IDD 來創(chuàng)建顯示器.
  3. 獲取顯示器的輸出內容與驅動的加載.

你可以根據當前對 IDD 的了解情況來選擇性的進行閱讀.

課前預習

本著以人為本的原則, 也為了避免半途而廢帶來的挫敗感, 在開始閱讀前, 我準備了一份簡單的條件自測, 如果您滿足這些條件, 那么恭喜你, 踩過這個坑對你來說可能不是難事, 如果你并不具備這些條件但是自詡智慧過人, 那么我相信硬著頭皮看完之后可能就能 Get 到這些能力了.

  1. 使用 VC++ 完成一些中等復雜程度的應用開發(fā).
  2. 聽說過 DirectX 并且知道他是干啥的.
  3. 打開過 Windows "設備管理器", 知道如何通過 "設備管理器" 安裝/更換設備驅動.
  4. 了解過 WDK 甚至用過 WDK 寫一個啥功能都沒的無聊驅動.
  5. 把電腦搞藍屏過, 知道如何通過安全模式修好驅動導致的藍屏.
  6. 知道 Microsoft(微軟) 別名 Hugehard(巨硬)
  7. 基礎的英文閱讀能力, 或者掌握百度翻譯的入門級使用.

書接上回

上回我們已經可以完成顯示基礎的設備初始化, 本章將會開始手把手教你制造年輕人的第一臺虛擬現(xiàn)實器.

graph LR
創(chuàng)建WDF驅動-->創(chuàng)建WDF設備-->電源初始化:上電-->ID((初始化顯示設備))-->初始化SwapChain

與人方便, 與己偷懶.

在上一章節(jié)中, 我們來到了 Monitor::Initialize 的門口準備開始初始化我們的顯示設備, 其實實際顯示設備的初始化并不是一個簡單的 函數調用 過程, 其中涉及到了上一章中在 創(chuàng)建 WDF 設備 時提供的幾個回調事件, 在此我們再貼一次部分本章中需要用的幾個關鍵回調注冊偽代碼方便后續(xù)閱讀:

    iddFunctions.EvtIddCxParseMonitorDescription = Monitor::ParseDescription;
    iddFunctions.EvtIddCxMonitorGetDefaultDescriptionModes = Monitor::GetDefaultModes;
    iddFunctions.EvtIddCxMonitorQueryTargetModes = Monitor::QueryModes;
    iddFunctions.EvtIddCxMonitorAssignSwapChain = Monitor::AssignSwapChain;
    iddFunctions.EvtIddCxMonitorUnassignSwapChain = Monitor::UnassignSwapChain;

0. 知識點記住了! 初始化顯示設備

如同字面意思一般, Monitor::Initialize 中必須要初始化那么些個基礎但又沒用的信息, 如果要做類比的話, 這個過程類似于創(chuàng)建一個 上電前的 IDD 設備.

我們先看一份偽代碼來了解一下大致需要的信息:

Monitor::Initialize(monitorIndex, adapter) {
  
  monitorInformation.MonitorType = DISPLAYCONFIG_OUTPUT_TECHNOLOGY_HDMI;
    monitorInformation.ConnectorIndex = monitorIndex;
  
    monitorInformation.MonitorDescription.Type = IDDCX_MONITOR_DESCRIPTION_TYPE_EDID;
  
  monitorInformation.MonitorDescription.DataSize = sizeof(EDID) || 0;
  monitorInformation.MonitorDescription.pData = EDID || NULL;
  
  monitorInformation.MonitorContainerId = GUID();
  
  monitorCreation.pMonitorInfo = monitorInformation;
  
  createdMonitor = IddCxMonitorCreate(adapter, monitorCreation);
  
  monitorArrived = IddCxMonitorArrival(createdMonitor.MonitorObject);
}

小朋友是不是有很多問號, 為什么沒有設置顯示器分辨率刷新率這些個耳熟能詳參數的位置呢? 關于這個問題, 我們先暫且埋葬自己的好奇, 往下看.

ConnectorIndex 是一個很重要但是也很次要的參數, 系統(tǒng)通過他來保存用戶對顯示器的設置, 比如: 顯示器排序, 采用的分辨率. 但是這個值的變動并不會造成系統(tǒng)的問題, 最多給用戶帶來一些體驗上的麻煩, 比如: 重新開機后顯示器的順序需要重新調整, 分辨率出現(xiàn)錯誤 等等.

至于 MonitorType 則代表了顯示設備的連接接口, 我強烈推薦你設置為我提供的參考值, 沒有為什么, 只是能正常工作, 當然了你也可以設置成其他的選項, 區(qū)別無非是能否正常工作.

在余下的這么個簡短的代碼片段中, 憑借著多年練就的火眼金睛, 你應該一眼就能看到出境次數有些多的 EDID.

EDIDExtended Display Identification Data, 一言蔽之就是一個用來描述顯示器信息的數據結構, 在此我們不做展開, 因為對于我們的虛擬現(xiàn)實器而言, 這個信息可以直接忽略, 不過這會影響到接下來的劇情路線.

如果你千辛萬苦構建出了自己的 EDID 信息, 那么系統(tǒng)將會在 IddCxMonitorCreate 方法之后調用在上一篇中提到并被我貼心附在上一節(jié)中的回調方法 EvtIddCxParseMonitorDescription(Monitor::ParseDescription). 我們需要在這個方法中進行 EDID 的解析并將解析出來的結果告知系統(tǒng). 由于涉及到 EDID 數據結構, 有興趣的小朋友可以自己去百度一下.

我們的例子中將 monitorInformation.MonitorDescription.pData 簡單的設置為 NULL 并將 DataSize 設置為 0 就會使得系統(tǒng)使用另一種獲取顯示器信息的方法 EvtIddCxMonitorGetDefaultDescriptionModes(Monitor::GetDefaultModes).

之后的例子中我們將以 EvtIddCxMonitorGetDefaultDescriptionModes(Monitor::GetDefaultModes) 作為唯一的回調方法來繼續(xù). 畢竟雖然數據的來源不同, 但是要設置的信息都是一樣的.

有一說一的提一嘴: 不管你是否啟用 EDID, 上面兩個回調方法都是需要注冊的喲

在完成了上面的這些個一攬子工作后, 我們就可以將顯示器帶上流水線了, 通過 IddCxMonitorArrival, 顯示器就能正常的開始顯示內容了.

1. 醒醒! 先處理顯示器配置

看完上一張是不是產生了一種一看就會的錯覺? 可別忘了 1 分鐘前說的 Monitor::GetDefaultModes 回調.

我為 Monitor::GetDefaultModes 準備了一份參考的偽代碼實現(xiàn):

Monitor::GetDefaultModes(monitorObject, pInArgs, pOutArgs) {
  
  monitorMode.totalSize.cx = monitorMode.activeSize.cx = WIDTH;
  monitorMode.totalSize.cy = monitorMode.activeSize.cy = HEIGHT;

  monitorMode.AdditionalSignalInfo.vSyncFreqDivider = 0;
  monitorMode.AdditionalSignalInfo.videoStandard = 255;

  monitorMode.vSyncFreq.Numerator = RATE;
  monitorMode.vSyncFreq.Denominator = 1;
  monitorMode.hSyncFreq.Numerator = RATE * HEIGHT;
  monitorMode.hSyncFreq.Denominator = 1;

  monitorMode.scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;

  monitorMode.pixelRate = RATE * WIDTH * HEIGHT;

  pInArgs->pDefaultMonitorModes[0] = monitorMode;
  pOutArgs->DefaultMonitorModeBufferOutputCount = 1;
  pOutArgs->PreferredMonitorModeIdx = 0;
}

暈了不? 關于 monitorMode 的那些取值規(guī)則字段 BALABALA, 微軟官方提供了一份非常完整的文檔, 我就不班門弄斧了, 將鏈接貼在這方便大家取閱 點我點我.

我們重點說說文檔沒的吧. WIDTH / HEIGHT / RATE 分別代表了 水平分辨率 / 垂直分辨率 / 刷新率, 這些個都是大路貨, 大家都知道.

由于在我們的例子中只提供了 1 種可用分辨率, 所以我們將 DefaultMonitorModeBufferOutputCount 設置為了 1 而默認使用的分辨率模式 PreferredMonitorModeIdx 則指向了 0.

顯示設備的分辨率就此完成!

2. 才怪嘞! 被遺忘的那個方法

回頭看看本文開頭的與人方便, 除了字面意思很明顯的 SwapChain 被拖更到了下一章節(jié), 是不是有啥不對勁?

EvtIddCxMonitorQueryTargetModes(Monitor::QueryModes) 好像沒用啊?

怎么可能! Monitor::QueryModes 才是分辨率中的爸爸, 沒有他萬物皆虛空.

Monitor::QueryModes 提供了 WDF 設備層面上能支持的分辨率模式, 簡單的說

Monitor::GetDefaultModes 提供的分辨率必須是 Monitor::QueryModes 的子集.

納尼, 那么重要的為什么不早說? 因為說早了也沒用啊.

其實光看偽代碼, 兩者還真的沒啥大差異:

Monitor::QueryModes(monitorObject, pInArgs, pOutArgs) {
  
  pInArgs->pTargetModes[0].totalSize.cx = pInArgs->pTargetModes[0].activeSize.cx = WIDTH;
  pInArgs->pTargetModes[0].totalSize.cy = pInArgs->pTargetModes[0].activeSize.cy = HEIGHT;

  pInArgs->pTargetModes[0].AdditionalSignalInfo.vSyncFreqDivider = 1;
  pInArgs->pTargetModes[0].AdditionalSignalInfo.videoStandard = 255;

  pInArgs->pTargetModes[0].vSyncFreq.Numerator = RATE;
  pInArgs->pTargetModes[0].vSyncFreq.Denominator = 1;
  pInArgs->pTargetModes[0].hSyncFreq.Numerator = RATE * HEIGHT;
  pInArgs->pTargetModes[0].hSyncFreq.Denominator = 1;

  pInArgs->pTargetModes[0].scanLineOrdering = DISPLAYCONFIG_SCANLINE_ORDERING_PROGRESSIVE;

  pInArgs->pTargetModes[0].pixelRate = RATE * WIDTH * HEIGHT;
  
  pInArgs->TargetModeBufferInputCount = 1;
  pOutArgs->TargetModeBufferOutputCount = 1;
  
}

定眼一看, 這不就是一個數組里面塞上一個 monitorMode 嗎, 居然那么簡單?

還真是. 我們依然在例子中只做了一個分辨率作為示例, 多個分辨率只要向下疊加即可.

重點在于 IddCxAdapterInitAsync, 明眼人一看就知道, 這是一個異步方法, 異步方法就一定有回調, 那么回調是哪個呢?

顯然幫助你們踩坑的我一直提前知道了答案, 那就是 EvtIddCxAdapterInitFinished, 對應著我們的 Device::Ready.

其實也好理解, 來電了, 設備初始化了, 下一步就是設備就緒了.

3. 只剩一步了? 對!

如果你有驅動開發(fā)經驗. 如果你會自己創(chuàng)建一個設備進行驅動掛載測試, 至此你應該已經可以看到一個可用的設備了, 可以說如果你只是想要創(chuàng)建一個虛擬的顯示器, 那么你的目的已經完成了.

回看開篇注冊的那些回調函數, 目前我們只剩下 SwapChain 的兩個函數了, 手把手創(chuàng)建的過程應該就此告一段落.

末了拋個磚: SwapChain 其實才是最多變的部分. 如果說設備的創(chuàng)建幾乎都是按部就班, 那么 SwapChain 才是真正需要切合業(yè)務實際的部分, 下一章節(jié)會很難, 官方示例中也沒有包含更多的 SwapChain 處理, 但是如果您真的需要創(chuàng)建一個可用的虛擬顯示屏, 繼續(xù)下一章的閱讀一定會對你頗有裨益.

末尾復讀機

作為閏更作者, 打算來個 Triple Kill 也是付出了老大的勇氣的.

如果確實對文中的代碼存疑或者確實需要幫助, 歡迎郵箱聯(lián)系 nvmjs#soxos.me, 除了伸手要全套服務的, 我一定給到力所能及的幫助.

本文采用 CC BY-NC-SA 4.0 協(xié)議進行許可
原文地址: soxos.m2d.in/go/tzYAA. 首發(fā)于 nvmjs.com 且已經獲得原作者許可.

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市年碘,隨后出現(xiàn)的幾起案子待德,更是在濱河造成了極大的恐慌馋没,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,816評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件山憨,死亡現(xiàn)場離奇詭異原杂,居然都是意外死亡,警方通過查閱死者的電腦和手機事甜,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,729評論 3 385
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來滔韵,“玉大人逻谦,你說我怎么就攤上這事∨泸撸” “怎么了邦马?”我有些...
    開封第一講書人閱讀 158,300評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長宴卖。 經常有香客問我滋将,道長,這世上最難降的妖魔是什么嘱腥? 我笑而不...
    開封第一講書人閱讀 56,780評論 1 285
  • 正文 為了忘掉前任耕渴,我火速辦了婚禮,結果婚禮上齿兔,老公的妹妹穿的比我還像新娘橱脸。我一直安慰自己,他們只是感情好分苇,可當我...
    茶點故事閱讀 65,890評論 6 385
  • 文/花漫 我一把揭開白布添诉。 她就那樣靜靜地躺著,像睡著了一般医寿。 火紅的嫁衣襯著肌膚如雪栏赴。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,084評論 1 291
  • 那天靖秩,我揣著相機與錄音须眷,去河邊找鬼。 笑死沟突,一個胖子當著我的面吹牛花颗,可吹牛的內容都是我干的。 我是一名探鬼主播惠拭,決...
    沈念sama閱讀 39,151評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼扩劝,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起棒呛,我...
    開封第一講書人閱讀 37,912評論 0 268
  • 序言:老撾萬榮一對情侶失蹤聂示,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后簇秒,有當地人在樹林里發(fā)現(xiàn)了一具尸體鱼喉,經...
    沈念sama閱讀 44,355評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,666評論 2 327
  • 正文 我和宋清朗相戀三年宰睡,在試婚紗的時候發(fā)現(xiàn)自己被綠了蒲凶。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片气筋。...
    茶點故事閱讀 38,809評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡拆内,死狀恐怖,靈堂內的尸體忽然破棺而出宠默,到底是詐尸還是另有隱情麸恍,我是刑警寧澤,帶...
    沈念sama閱讀 34,504評論 4 334
  • 正文 年R本政府宣布搀矫,位于F島的核電站抹沪,受9級特大地震影響,放射性物質發(fā)生泄漏瓤球。R本人自食惡果不足惜融欧,卻給世界環(huán)境...
    茶點故事閱讀 40,150評論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望卦羡。 院中可真熱鬧噪馏,春花似錦、人聲如沸绿饵。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,882評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽拟赊。三九已至刺桃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間吸祟,已是汗流浹背瑟慈。 一陣腳步聲響...
    開封第一講書人閱讀 32,121評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留屋匕,地道東北人葛碧。 一個月前我還...
    沈念sama閱讀 46,628評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像炒瘟,于是被迫代替她去往敵國和親吹埠。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,724評論 2 351