開局一目錄
我大致的將 IDD 的實現(xiàn)按照功能分為下面的三個章節(jié)
- 初始化一個沒有功能的 IDD 殼子.
- [本文] 通過 IDD 來創(chuàng)建顯示器.
- 獲取顯示器的輸出內容與驅動的加載.
你可以根據當前對 IDD 的了解情況來選擇性的進行閱讀.
課前預習
本著以人為本的原則, 也為了避免半途而廢帶來的挫敗感, 在開始閱讀前, 我準備了一份簡單的條件自測, 如果您滿足這些條件, 那么恭喜你, 踩過這個坑對你來說可能不是難事, 如果你并不具備這些條件但是自詡智慧過人, 那么我相信硬著頭皮看完之后可能就能 Get 到這些能力了.
- 使用 VC++ 完成一些中等復雜程度的應用開發(fā).
- 聽說過 DirectX 并且知道他是干啥的.
- 打開過 Windows "設備管理器", 知道如何通過 "設備管理器" 安裝/更換設備驅動.
- 了解過 WDK 甚至用過 WDK 寫一個啥功能都沒的無聊驅動.
- 把電腦搞藍屏過, 知道如何通過安全模式修好驅動導致的藍屏.
- 知道 Microsoft(微軟) 別名 Hugehard(巨硬)
- 基礎的英文閱讀能力, 或者掌握百度翻譯的入門級使用.
書接上回
上回我們已經可以完成顯示基礎的設備初始化, 本章將會開始手把手教你制造年輕人的第一臺虛擬現(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.
EDID 即 Extended 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 且已經獲得原作者許可.