翻譯|使用教程|編輯:吳園園|2020-05-27 09:55:21.820|閱讀 584 次
概述:在Qt圖形系列博客的第三部分(第一部分、第二部分),我們會(huì)了解在Qt 5.14中,將Qt Quick的Scene Graph切換到通過QRhi (Qt渲染硬件接口)渲染時(shí),著色器是如何工作的。
# 界面/圖表報(bào)表/文檔/IDE等千款熱門軟控件火熱銷售中 >>
相關(guān)鏈接:
Qt是目前最先進(jìn)、最完整的跨平臺(tái)C++開發(fā)工具。它不僅完全實(shí)現(xiàn)了一次編寫,所有平臺(tái)無差別運(yùn)行,更提供了幾乎所有開發(fā)過程中需要用到的工具。如今,Qt已被運(yùn)用于超過70個(gè)行業(yè)、數(shù)千家企業(yè),支持?jǐn)?shù)百萬設(shè)備及應(yīng)用。
在Qt圖形系列文章的第三部分(第一部分、第二部分),我們會(huì)了解在Qt 5.14中,將Qt Quick的Scene Graph切換到通過QRhi (Qt渲染硬件接口)渲染時(shí),著色器是如何工作的。我們先研究著色器的處理方式,然后再深入研究RHI,因?yàn)樵赒t Quick中當(dāng)需要使用ShaderEffect Item或自定義材質(zhì)時(shí),必須自己編寫片段和/或頂點(diǎn)著色器代碼,因此必需要了解新的著色器處理方法(到Qt 6時(shí)才能升級(jí))。
說到Qt 6:雖然我們?cè)谶@里描述的內(nèi)容只會(huì)應(yīng)用到Qt 5.14,后面的版本還會(huì)有許多修改,但是我們現(xiàn)在闡述的內(nèi)容極有可能是Qt 6中處理圖形和通用計(jì)算著色器的基礎(chǔ),當(dāng)然到時(shí)細(xì)節(jié)內(nèi)容會(huì)打磨得更加精致
為什么加入新東西?
問題一
查看qtdeclarative源代碼樹(即包含QtQml 、QtQuick和相關(guān)模塊的git代碼倉庫),然后進(jìn)入著色器目錄,其中包含了Qt Quick Scene Graph內(nèi)建材質(zhì)的頂點(diǎn)著色器和片段著色器代碼,你會(huì)發(fā)現(xiàn)Qt Quick已為每個(gè)GLSL頂點(diǎn)和片段著色器準(zhǔn)備了兩個(gè)版本:
為什么這樣處理呢?這是為了兼容支持使用了核心配置(core profile)的OpenGL(對(duì)應(yīng)OpenGL 3.2及以上)。由于OpenGL標(biāo)準(zhǔn)并沒有要求新版本的OpenGL實(shí)現(xiàn)必須支持GLSL 100/110/120的編譯(即老的GLSL版本),因此Qt不得不準(zhǔn)備了兩個(gè)版本的GLSL:一個(gè)適配OpenGL ES 2.0、OpenGL 2.1和兼容性配置(compatibility profile),另一個(gè)(GLSL版本號(hào)為150)專門用于適配核心配置。如本系列博客第一部分所述,在需要把自定義OpenGL渲染和基礎(chǔ)的Qt Quick UI結(jié)合的使用場(chǎng)景中,提供兩個(gè)版本的著色器代碼才能使開發(fā)者可以自由選擇使用哪個(gè)版本的OpenGL。因?yàn)椴徽撨x擇兼容性配置還是核心配置,Qt Quick都可以正常渲染。
當(dāng)著色器版本的數(shù)量是2時(shí),這種實(shí)現(xiàn)方式還是可以正常實(shí)施的。但如果現(xiàn)在我們還需要添加Vulkan風(fēng)格的GLSL、HLSL和MSL呢?遺憾的是,這種方式無法規(guī)?;瘮U(kuò)展。
問題二
與OpenGL不同,一些較新的圖形API不再支持內(nèi)置著色器編譯。(再見了,glCompileShader)。而即使最終還是支持著色器編譯,這部分功能可能變成了一個(gè)分離的庫,但它們可能不提供運(yùn)行時(shí)反射機(jī)制,這意味著沒有辦法動(dòng)態(tài)定位輸入頂點(diǎn)以及其他頂點(diǎn)、片段或通用計(jì)算著色器所需要的材質(zhì),以及這些材質(zhì)的布局。(例如,一個(gè)uniform變量的名稱和偏移量)
問題三
一個(gè)內(nèi)部細(xì)節(jié):Qt Quick Scene Graph的批次處理系統(tǒng)需要對(duì)頂點(diǎn)著色器進(jìn)行一些調(diào)整,在一個(gè)稱為合并批次中調(diào)整材質(zhì)(就是當(dāng)多個(gè)幾何節(jié)點(diǎn)最終合并到一個(gè)draw調(diào)用后得到的結(jié)果)。把著色器傳送到glCompileShader之前動(dòng)態(tài)修改,這種方式適用于只有一種著色語言在使用的情況,不能簡(jiǎn)單擴(kuò)展到必須為多種不同語言實(shí)現(xiàn)相同邏輯的情況。
如何改變呢?
看看Khronos的SPIR頁面,里面有一張很好的關(guān)于SPIR-V開源生態(tài)系統(tǒng)的信息圖片。為什么不嘗試在此基礎(chǔ)上進(jìn)行開發(fā)呢?
我們感興趣的關(guān)鍵組件如下:
因此,如果我們“標(biāo)準(zhǔn)化”一種語言,比如Vulkan風(fēng)格的GLSL,把它編譯成SPIR-V,我們就可以適配Vulkan了。然后,如果我們通過SPIRV-Cross運(yùn)行SPIR-V的二進(jìn)制文件,就可以獲得所需的反射信息,并可以為各種版本的GLSL、HLSL和Metal著色器語言生成源代碼。
(是的,GLSL仍然至關(guān)重要,因?yàn)殡m然有讓OpenGL可以直接使用SPIR-V的擴(kuò)展,但是指望這套方式在實(shí)際中應(yīng)用并不現(xiàn)實(shí),因?yàn)檫@樣的擴(kuò)展在90%的Qt目標(biāo)平臺(tái)和設(shè)備上不存在——例如,OpenGL ES 2.0在2019年仍然常見。)
最后,將所有這些(包括元數(shù)據(jù)反射特性)打包到一個(gè)可以方便(反)序列化的包中,這樣就得到了我們的解決方案。
因此,設(shè)置QSG_RHI=1然后運(yùn)行Qt Quick應(yīng)用程序,其后端渲染管道是這樣的:
Vulkan-flavor GLSL [ -> generate batching-friendly variant for vertex shaders] -> glslang : SPIR-V bytecode -> SPIRV-Cross : reflection metadata + GLSL/HLSL/MSL source -> pack it all together and serialize to a .qsb file
.qsb擴(kuò)展名來自于執(zhí)行上述步驟的命令行工具的名稱——qsb,Qt Shader Baker的縮寫。(不要與qbs混淆)
在運(yùn)行時(shí),.qsb文件被反序列化成QShader實(shí)例。它是一個(gè)相當(dāng)簡(jiǎn)單的容器,遵循標(biāo)準(zhǔn)的Qt模式,如隱式共享,并為一個(gè)著色器托管多個(gè)版本的源碼和字節(jié)碼以及包含反射數(shù)據(jù)的QShaderDescription。與RHI的其他部分一樣,這些類目前都是私有的API。
圖形層直接使用QShader實(shí)例。圖形流水線的狀態(tài)對(duì)象為每個(gè)激活的著色步驟分配一個(gè)QShader。然后QRhi后端從QShader容器中選擇適當(dāng)?shù)闹靼姹尽?
在Qt 5.14中,具體選擇規(guī)則如下:
上表中的HLSL和MSL條目初看可能會(huì)有些奇怪。這是因?yàn)槲覀兗纯梢栽谶\(yùn)行時(shí)源碼編譯HLSL和MSL(我們的默認(rèn)方法),同時(shí)也做了一些實(shí)驗(yàn),允許在.qsb包中包含預(yù)編譯的中間格式。在實(shí)踐中,這意味著調(diào)用fxc(目前還不支持dxc——它也在計(jì)劃中,但只有在我們開始研究D3D12時(shí)才真正相關(guān))或Metal命令行工具,然后再在管道中執(zhí)行上面所示的“打包”步驟。這里的挑戰(zhàn)當(dāng)然是這些工具與它們的平臺(tái)(分別是Windows和macOS)綁定在一起,因此qsb只有當(dāng)在該平臺(tái)上運(yùn)行時(shí)才能被啟用。例如,在Linux上手動(dòng)生成.qsb文件不可行。從長遠(yuǎn)來看,這可能不是什么大問題,因?yàn)樵赒t 6的規(guī)劃中,我們會(huì)研究更好地與構(gòu)建系統(tǒng)集成,所以像qsb這樣的手動(dòng)運(yùn)行工具就不那么常見了。
等等,qsb是怎么來的?
來自Qt Shader Tools模塊。它提供了一個(gè)稱為QShaderBaker的API以及一個(gè)稱為qsb的命令行工具來執(zhí)行上面描述的編譯、轉(zhuǎn)換和打包步驟。
這里有一點(diǎn)需要注意:這是一個(gè)Qt-labs模塊,所以它不會(huì)隨Qt 5.14一起發(fā)布。
為什么呢?主要是因?yàn)榈谌揭蕾?,例如glslang和SPIRV-Cross。涉及到需要在我們所有的目標(biāo)平臺(tái)上編譯和運(yùn)行的情況時(shí),就會(huì)有許多事情需要調(diào)查和確認(rèn),有些與許可證相關(guān)。如果所有這些聽起來都很熟悉,那是因?yàn)樵诒静┛拖盗械牡谝徊糠钟懻揂PI轉(zhuǎn)換解決方案時(shí)提到了其中的一些問題。因此,現(xiàn)在生成.qsb包就牽涉到了該模塊的檢查和構(gòu)建,然后才能手動(dòng)運(yùn)行.qsb工具。
盡管我們還是需要一個(gè)新的集成打包在Qt中的解決方案,目前依賴一個(gè)離線著色處理并不是件壞事。不管發(fā)生什么,它都是Qt 6的目標(biāo)之一。我們的愿景是擁有一些與Qt構(gòu)建系統(tǒng)集成的東西,這樣上述的著色器處理步驟就可以在應(yīng)用程序(或庫)構(gòu)建時(shí)完成。但這推遲到成為一個(gè)未來的目標(biāo),主要是因?yàn)榧磳⒌絹淼膓make -> cmake切換。一旦情況穩(wěn)定下來,我們就可以開始在新系統(tǒng)上構(gòu)建解決方案了。
那么Qt Quick在Qt 5.14中表現(xiàn)怎么樣?
看看qt/ src/quick/scenegraph/shaders_ng,答案很明顯:通過手動(dòng)運(yùn)行qsb(注意名稱很貼切的compile.bat),并通過Qt資源系統(tǒng)在Qt quick庫中引入生成的.qsb文件。正如上面所概述的,稍后應(yīng)該會(huì)變得更加精巧一些,但是現(xiàn)在已經(jīng)完成了任務(wù)。
.vert和.frag文件包含了與Vulkan兼容的GLSL代碼,并且沒有包含在Qt Quick 構(gòu)建中。scenegraph.qrc中只有.qsb文件。
每個(gè)材質(zhì)只有一對(duì)頂點(diǎn)和片段著色器,總是以與Vulkan兼容的GLSL的形式編寫,遵循一些簡(jiǎn)單的約定(比如只使用一個(gè)uniform緩沖區(qū),位于binding 0處)。
所有這些文件都通過著色器機(jī)制運(yùn)行,產(chǎn)生一個(gè)QShader包。在這個(gè)例子中,結(jié)果是相同著色器的六個(gè)版本,加上反射數(shù)據(jù)(qsb可以打印為JSON文本;然而.qsb文件本身是壓縮的不可讀的二進(jìn)制文件)。這樣就解決了上面的問題一和二。
注意著色器列表中的[Standard]標(biāo)簽。如果這是一個(gè)頂點(diǎn)著色器,并且指定了-b參數(shù),輸出著色器的數(shù)量將是12個(gè),而不是6個(gè)。另外的6個(gè)將被標(biāo)記為[Batchable],這表明它們是非常友好的批處理,對(duì)Qt Quick scenegraph的渲染器做了輕微的修改。這解決了問題3,代價(jià)是存儲(chǔ)會(huì)有所提高。(由于減少了運(yùn)行時(shí)工作,所以最終是值得的)
本文涵蓋了新著色器管道背后的核心概念。我們將會(huì)在另一篇文章中討論ShaderEffect和QSGMaterial?;舅枷耄≦t 5.14中的)是傳遞.qsb文件的名稱,而不是著色器源碼字符串,但對(duì)于材質(zhì)需要特別注意幾個(gè)問題(主要是由于使用uniform緩沖區(qū)代替了單一的緩沖區(qū),而且由于沒有了線程上下文的概念,因此任何人都可以隨意改變狀態(tài))。下次再詳細(xì)講。
本站文章除注明轉(zhuǎn)載外,均為本站原創(chuàng)或翻譯。歡迎任何形式的轉(zhuǎn)載,但請(qǐng)務(wù)必注明出處、不得修改原文相關(guān)鏈接,如果存在內(nèi)容上的異議請(qǐng)郵件反饋至chenjj@ke049m.cn
文章轉(zhuǎn)載自: