在Vulkan、Metal和Direct3D上运行Qt Quick-第1部分

作者:Richard Lin | Nov 30, 2019 3:02:55 PM

本文翻译自Qt Quick on Vulkan, Metal, and Direct3D

 

原文作者:Laszlo Agocs,Qt公司高级开发工程师

校审:Richard Lin

马上要发布Qt 5.14首个beta版了,是时候聊一聊其中一个重要新特性了。我们很难在一篇博文里分析所有图形技术栈和向Qt 6演进的演进细节,所以本文的第一、二部分将描述背景知识,并分析Qt 5.14中的新特性,然后在后续博文中深入探讨技术细节和未来发展方向。

Qt 5.14新功能页面提到:添加了独立于图形API的Scene Graph渲染引擎,这是第一个预览版而且是一个可选功能。这使得Qt Quick应用程序,除了OpenGL以外,还可以运行在Vulkan、Metal或Direct3D 11上。

这具体是什么意思?

Qt 6技术概览一文中说过,Qt 6的一个主要目标是避免在Qt的许多代码中直接使用OpenGL,通过适当的抽象来兼容更多的图形API,比如VulkanMetalDirect3D。当然,OpenGL(和OpenGL ES)仍然是一种选择。这背后的主要动机不是提升性能,而是确保Qt Everywhere在未来仍然成立,未来可能运行在不支持或不推荐使用OpenGL的平台和设备上。同时,能有这样一套先进的、结构简单且清晰易懂的API还可以创造许多可能性,特别是对于那些需要提高性能的场景(比如,更少的API开销实现更低的CPU占用率),以及在Qt Quick背后的渲染引擎和其他模块中增加新的功能,比如最近公布的Qt Quick 3D

此外,能够使用平台首选的、支持最好的图形API来渲染用户界面,这对于那些使用Qt来呈现UI,同时还用到原生API写的2D或3D图形的应用程序来说是个好消息。在这种应用中,一般Qt无法决定使用何种图形API:例如,一个macOS上的桌面应用程序希望使用Metal去渲染自己的3D内容,同时依靠Qt Quick去呈现2D UI元素,此时如果Qt Quick也使用Metal渲染会大有好处。对于那些过去关注Qt 5.x时代图形部分变化的人来说,这一点很熟悉:概念上,这和当时Qt Quick渲染引擎引入对OpenGL core profile上下文的操作的支持一样 —— Qt Quick本身并不需要关心这个,但是为了能够集成使用了core profile的外部渲染代码,Qt Quick必须顾及和它们协同的能力。所以从这个角度看,这是Qt 5的自然延续,现在扩展到OpenGL之外的其他图形API。

到这里,有两个显而易见问题:

  • 这为什么与Qt 5.x有关?这不都是Qt 6的内容吗?
  • 为什么不直接使用<某种图形API>转化为<标准API>的图形API转换方案呢?

那么Qt 5.14中有什么?

对Qt所有(或至少大部分)有关图形渲染的部分进行全面的检查确实是Qt 6的目标。然而,停止支持Qt 5.x,然后试图设计、开发和重构一切,并指望一切都能变好,并不现实。正如Qt开发者过去、现在仍会提及的,任何API刚开始迭代时都可能不是最优的。因此,我们采用并行开发的方法,先专注于Qt中的一种特定的UI技术:Qt Quick。

Qt 5.14将发布使用全新渲染方法的Qt Quick预览版。默认情况下,不启用新的渲染方法,因此对应用程序来说不需要任何特殊的更改——会像在以往版本中一样,内部使用相同的直接基于OpenGL的渲染路径。但是,那些希望尝试新方法的人可以选择通过设置环境变量或使用main()中的C++ API启用新的渲染路径。我们希望尽早得到反馈,我们好继续迭代和演进,而不必等到Qt 6.0的发布。

阅读Qt 5.14文档简介,就会找到以下内容:

并不是所有的应用程序都能直接使用QSG_RHI环境变量运行。在Scene Graph节点中执行OpenGL调用、或在自定义材质中使用GLSL着色器代码定制的QQuickItem实现,就不能启用RHI渲染。这同样适用于带有GLSL代码的ShaderEffect元件。其实有更好的自定义材质和效果的办法,但是这些需要对应用程序进行移植。在Qt 5.14和Qt 5.15之间我们会尝试做一些适配,但是在Qt 6.0之前,不会进行大范围的推广和移植。另一方面,许多现有的QML应用程序应该可以正常工作,即使底层渲染引擎使用完全不同的API(如Vulkan或Metal)。

为什么不采用XYZ类型的转换层?

首先,需要说明的是,尽管不能总是实现开箱即用,使用API和着色器转换层(如MoltenVKMoltenGLANGLEZink等)的可能性仍然存在。例如,MoltenVK允许基于Vulkan在macOS上渲染Qt Quick的UI。如果Qt Quick应用程序希望只使用Vulkan,并且仍然希望在macOS上运行,就也可以采用MoltenVK。(只要用户系统中安装了正确配置的Qt库,MoltenVK也已经就绪,那就可以运行)

把这个转换层变成一个强制性的依赖,并基于此将它和Qt一起部署,情况就完全不一样了。

  • 显然,将“Qt Everywhere ”改为“Qt Only Where External Dependencies Allow”是不切实际的。
    Qt面对的平台和环境比我们想象的还要复杂得多。它只能依赖那些强制性安装、可以在各种各样环境中运行\而且对于特定的需求可以轻松适配的第三方库。(想想Integrity、QNX和定制的嵌入式Linux环境,图形栈有bug的或正在开发中的系统,偶尔需要适应专有的数据和模块,或者有时必须与各种图形或复杂的API进行交互,也许是以厂商自己定制的非标准形式,也许是早已过时的API,所有这些都需要Qt渲染软件栈中的每个层级都具备灵活性和可伸缩性)。
  • 运行时在各种着色器语言之间转换(或者使用中间格式)不是一个好主意。Qt 6中的着色器管道设计将会专注于离线缓存能力,或者至少在应用程序编译时进行预缓存。在中间插入转换层,隐藏各种实现(用了什么API,用了哪种着色器语言),之前的努力就白费了。因为我们不可能为真正底层API准备或者使用字节码。
  • 之前提到的一些选项也没用了,因为目前实际情况是:在可预见的未来,OpenGL(ES)在大多数设备上仍旧是工作主力。因此如果继续坚持只使用一种API,那么只能是OpenGL,或者一个能兼容OpenGL的转换层(它也要在低性能设备上性能表现良好)。
  • 有一种特殊情况,客户自己扩展Qt渲染引擎的功能,底层的渲染代码往往需要使用相同的API才能完美配合。当然如果转换层允许访问底层原生对象(比如Qt 5在Windows上使用ANGLE实现了Qt Quick 到Direct3D的转换,它就可以扩展EGL extensions),那这也不是一种阻碍。但是不是每次都可以成功的,而且过程中可能产生新的麻烦。
  • 还有许可方面的问题。可以认为Apache 2.0与GPLv2不兼容。在任何情况下,依靠纯商业解决方案都是不现实的。
  • 从经验来看(有些是从ANGLE来的,有些是从MoltenVK来的),使用这样的解决方案绝不像人们最初希望的那样简单。在某种程度上,保持所有选项正常运行所需要的工作可能会变得过于繁重——那么最好还是把这些精力用到对的方向:直接使用原生的API。这些API转换方案中的某些依赖于平台的特性也不是很理想——如果说我们需要为每个Qt的目标平台引入一个不同的API,这很快就会变得难以忍受。

因此,Qt没有采用依赖低级API转换程序的方法,而是自己定义了3D图形的高级抽象(内部使用,暂时不向应用程序开放)。抽象层的后端有许多针对特定API的实现,这和许多Qt组件的模式差不多。有些后端和操作系统平台是固定搭配(Metal,D3D),而其他后端可以对应多个平台(Vulkan,OpenGL)。有一个新开发的着色器管理单元来实现不同后台的切换,它用到了一些第三方库,比如glslangSPIRV-Cross。更多细节将在之后的博文中详述。现在,让我们的视线转向稍高层面,看看它会给Qt 5.14中的Qt Quick带来什么。

真的会有效吗?

让我们来看一个例子,即QUIt Coding著名的Qt5 Cinematic Experience演示程序。我们使用的是稍作修改的版本,其中少数Shader Effect Item被更新为同时可以使用两种Qt Quick Scene Graph渲染路径的格式。该版本可在这里找到

正常启动应用程序,设置QSG_INFO=1,得到:

就像在调试输出中打印的日志一样,这是在Linux桌面的OpenGL上运行的:

qt.scenegraph.general: threaded render loop
qt.scenegraph.general: Using sg animation driver
qt.scenegraph.general: Animation Driver: using vsync: 16.95 ms
qt.scenegraph.general: opengl texture atlas dimensions: 2048x1024
qt.scenegraph.general: GL_VENDOR: X.Org
qt.scenegraph.general: GL_RENDERER: AMD Radeon (TM) R9 M360 (VERDE, DRM 3.23.0, 4.15.0-62-generic, LLVM 8.0.1)
qt.scenegraph.general: GL_VERSION: 4.5 (Compatibility Profile) Mesa 19.2.0-devel (git-08f1cef 2019-07-25 bionic-oibaf-ppa)
qt.scenegraph.general: GL_EXTENSIONS: ...
qt.scenegraph.general: Max Texture Size: 16384
qt.scenegraph.general: Debug context: false

如果我们设置QSG_RHI=1,会发生什么变化?

qt.scenegraph.general: Using QRhi with backend OpenGL
graphics API debug/validation layers: 0
QRhi profiling and debug markers: 0
qt.scenegraph.general: threaded render loop
qt.scenegraph.general: Using sg animation driver
qt.scenegraph.general: Animation Driver: using vsync: 16.95 ms
qt.rhi.general: Created OpenGL context QSurfaceFormat(version 4.5, options QFlags<QSurfaceFormat::FormatOption>(DeprecatedFunctions), depthBufferSize 24, redBufferSize 8, greenBufferSize 8, blueBufferSize 8, alphaBufferSize 0, stencilBufferSize 8, samples -1, swapBehavior QSurfaceFormat::DoubleBuffer, swapInterval 1, colorSpace QSurfaceFormat::DefaultColorSpace, profile QSurfaceFormat::CompatibilityProfile)
qt.rhi.general: OpenGL VENDOR: X.Org RENDERER: AMD Radeon (TM) R9 M360 (VERDE, DRM 3.23.0, 4.15.0-62-generic, LLVM 8.0.1) VERSION: 4.5 (Compatibility Profile) Mesa 19.2.0-devel (git-08f1cef 2019-07-25 bionic-oibaf-ppa)
qt.scenegraph.general: MSAA sample count for the swapchain is 1. Alpha channel requested = no.
qt.scenegraph.general: rhi texture atlas dimensions: 2048x1024

乍一看差别不大。它似乎还在使用OpenGL。然而,内部没有直接使用OpenGL,也没有Qt Quick Scene Graph中各处穿插的GLSL着色器源码。相反,会使用QRhi(即QtGui模块中的一个私有API,全称为Qt Rendering Hardware Interface)进行渲染。

让我们把它变得更有趣一点。设置QSG_RHI_BACKEND=vulkan

qt.scenegraph.general: Using QRhi with backend Vulkan
graphics API debug/validation layers: 0
QRhi profiling and debug markers: 0
qt.scenegraph.general: threaded render loop
qt.scenegraph.general: Using sg animation driver
qt.scenegraph.general: Animation Driver: using vsync: 16.95 ms
WARNING: radv is not a conformant vulkan implementation, testing use only.
qt.rhi.general: Physical device 0: 'AMD RADV CAPE VERDE (LLVM 8.0.1)' 19.1.99
qt.rhi.general: using this physical device
qt.rhi.general: queue family 0: flags=0xf count=1
qt.rhi.general: queue family 1: flags=0xe count=2
qt.rhi.general: 55 device extensions available
qt.scenegraph.general: MSAA sample count for the swapchain is 1. Alpha channel requested = no.
qt.scenegraph.general: rhi texture atlas dimensions: 2048x1024
qt.rhi.general: Creating new swapchain of 3 buffers, size 1280x720, presentation mode 2

处理的很好。显然它现在正在通过Vulkan渲染。甚至更奇异的Qt Quick特性,如distance field text渲染、着色器效果和粒子效果都如预期的那样存在。

RenderDoc中运行应用程序并捕获帧,结果如下所示。Qt Quick确实正在构建Vulkan管道状态对象和命令缓冲区,着色器代码以SPIR-V字节码格式提供。

就这些了。在本系列的第二篇博文中,我们将研究Qt 5.14为macOS和Windows提供了什么。在那之后,我们将继续研究所有这些是如何在底层工作的,以及对需要自定义材质和特效的应用程序有什么影响。

期待激动人心的时刻吧!