Qt 6.6中的Direct3D 12支持

Direct3D 12 Support in Qt 6.6

Qt 6.6中的Direct3D 12支持

October 31, 2023 by Laszlo Agocs | Comments

​2023年10月31日 LaszloAgocs|评论

Qt 6.6 introduces a new QRhi backend, for Direct3D 12. This means that the number of supported 3D APIs is now up to five: Vulkan, Metal, OpenGL / OpenGL ES, Direct3D 11, and Direct3D 12. Applications using Qt Quick and Qt Quick 3D can now choose to use D3D12 to render the contents of a QQuickWindow or QQuickView. In addition, building on the architectural improvements introduced earlier in this blog postQQuickWidget is also fully supported.

​Qt 6.6为Direct3D 12引入了一个新的QRhi后端。这意味着现在支持的3D API的数量达到了五个:Vulkan、Metal、OpenGL/ENGGLES、Direct3D11和Direct3D12。使用Qt Quick和Qt Quick 3D的应用程序现在可以选择使用D3D12来渲染QQuickWindow或QQuickView的内容。此外,在本博客文章前面介绍的架构改进的基础上,QQuickWidget也得到了完全支持。​

Let's take a quick look at what this means in practice.

让我们快速了解一下这在实践中意味着什么。

What does this mean for me?

这对我来说意味着什么?

In many cases, nothing. This is because for Qt Quick applications the default rendering backend continues to be Direct3D 11 on Windows, and this is not expected to change in the foreseeable future. Using Direct3D 12 needs to be an explicit choice now, similarly to how applications can also request using Vulkan or OpenGL when running on Windows.

在很多情况下,什么都没有。这是因为对于Qt Quick应用程序,默认的渲染后端仍然是Windows上的Direct3D 11,预计在可预见的未来不会改变。使用Direct3D12现在需要成为一个明确的选择,类似于应用程序在Windows上运行时也可以请求使用Vulkan或OpenGL。

While the D3D12 backend is expected to be quite robust already, some shortcoming can always surface. In addition, compatibility for existing applications, preventing surprises and unexpected behavioral changes, and avoiding deprecations with little practical value are important goals for the Qt graphics stack. Continuing with the existing defaults helps minimizing surprises when deploying and shipping applications to a wide range of systems (including virtual machines and old systems with a less-than-ideal driver situation), while those who want to or have to, can always opt-in to the new code paths.

虽然D3D12后端预计已经相当强大,但一些缺点总是会浮出水面。此外,与现有应用程序的兼容性,防止意外和意外的行为变化,以及避免没有什么实际价值的弃用,都是Qt图形堆栈的重要目标。继续使用现有的默认设置有助于在将应用程序部署和运送到各种系统(包括虚拟机和驱动程序不太理想的旧系统)时最大限度地减少意外,而那些想要或必须这样做的人总是可以选择使用新的代码路径。

How to use it in Qt Quick applications?

如何在Qt Quick应用程序中使用它?

When working a QQuickWindow (or the Window QML element), QQuickView, QQuickWidget, call QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D12). This is typically done early on in main(). The setting is application global and applies to all Qt Quick windows.

​在处理QQuickWindow(或Window QML元素)、QQuickView、QQuickWidget时,调用QQuickWindow::setGraphicsApi(QSGRendererInterface::Direct3D12)。这通常在main()的早期完成。该设置是应用程序全局设置,适用于所有Qt Quick窗口。

Alternatively, set the QSG_RHI_BACKEND environment variable to d3d12 before launching the application.

或者,在启动应用程序之前,将QSG_RHI_BACKEND环境变量设置为d3d12。

When troubleshooting, or just to see if the request is correctly accepted, enabling the common scenegraph logs by setting QSG_INFO=1 (or by enabling the qt.scenegraph.general/qt.rhi.general logging categories) is useful, as always.

在进行故障排除时,或者只是为了查看请求是否被正确接受,通过设置QSG_INFO=1(或通过启用qt.scenegraph.general/qt.rhi.general日志记录类别)来启用通用场景日志一如既往地有用。

The D3D debug layer can be enabled either by launching with QSG_RHI_DEBUG_LAYER=1 or by setting the appropriate flag in QQuickGraphicsConfiguration and setting it on the QQuickWindow/View. (i.e., the exact same way one requests enabling the Vulkan validation layer when using Vulkan.) This requires that the debug layer is available at run time. Look for a line stating "Enabling D3D12 debug layer" in the logs when in doubt. This is probably less interesting for the typical Qt Quick application (as we do not expect standard Qt rendering to generate debug layer errors, do we), but may become useful when integrating external D3D12-based graphics, compute, or machine learning code into a Qt Quick application.

​D3D调试层可以通过在QSG_RHI_DEBUG_LAYER=1的情况下启动,也可以通过在QQuickGraphicsConfiguration中设置适当的标志并在QQuickWindow/View上设置来启用。(即,与使用Vulkan时请求启用Vulkan验证层的方式完全相同。)这要求调试层在运行时可用。如果有疑问,请在日志中查找一行“启用D3D12调试层”。对于典型的Qt-Quick应用程序来说,这可能不太有趣(因为我们不希望标准的Qt渲染会产生调试层错误,是吗),但当将外部基于D3D12的图形、计算或机器学习代码集成到Qt-Quick应用程序中时,这可能会变得有用。

Speaking of external rendering engines, QQuickRenderTarget gets an appropriate fromD3D12Texture() function to allow wrapping an existing D3D12 texture and redirecting Qt Quick rendering into it when driving the QQuickWindow via QQuickRenderControl.  In QQuickGraphicsDevice, used to get QQuickWindow to adopt existing native physical device (adapter), device, context, etc. objects, fromAdapter() serves both D3D11 and D3D12 from now on.

​说到外部渲染引擎,QQuickRenderTarget从fromD3D12Texture()函数中获得一个适当的函数,以允许在通过QQuickRender控件驱动QQuickWindow时包装现有的D3D12纹理并将Qt Quick渲染重定向到其中。在QQuickGraphicsDevice中,用于让QQuickWindow采用现有的本地物理设备(适配器)、设备、上下文等对象,fromAdapter()从现在起同时为D3D11和D3D12服务。

How to use it in applications working directly with QRhi?

如何在直接使用QRhi的应用程序中使用它?

Qt 6.6 opens up the QRhi family of APIs to a large degree, meaning the documentation for these classes is now part of the standard Qt documentation set, and the APIs are lifted to a "semi-public" level similarly to the QPA classes. Applications that wish to use the same cross-platform rendering facilities Qt itself uses to implement Qt Quick and Qt Quick 3D, can now do so without having to deal with fully private APIs with no generated documentations.

​Qt 6.6在很大程度上开放了QRhi系列API,这意味着这些类的文档现在是标准Qt文档集的一部分,API被提升到了类似于QPA类的“半公共”级别。希望使用Qt本身用于实现Qt Quick和Qt Quick 3D的相同跨平台渲染功能的应用程序,现在可以这样做,而不必处理完全私有的API,也不需要生成文档。

When working directly with QRhi, one can pass QRhi::D3D12 to QRhi::create() and specify a QRhiD3D12InitParams. This is currently only used to control if the debug layer should be enabled. To use a specific adapter, or adopt an existing device+command queue combo, QRhiD3D12NativeHandles can be used. PreferSoftwareRenderer is honored by this backend as well, similarly to D3D11; this will request choosing the adapter with DXGI_ADAPTER_FLAG_SOFTWARE.  (e.g. WARP)

​当直接使用QRhi时,可以将QRhi::D3D12传递给QRhi::create()并指定QRhiD3D12InitParams。这目前仅用于控制是否应启用调试层。要使用特定的适配器,或者采用现有的设备+命令队列组合,可以使用QRhiD3D12NativeHandles。PreferSoftwareRenderer也受到这个后端的尊重,类似于D3D11;这将请求选择具有DXGI_adapter_FLAG_SOFTWARE的适配器。(例如WARP)​

When pulling out native objects from the QRhi instance, QRhi::nativeHandles() provides a QRhiD3D12NativeHandles that reports the used adapter, ID3D12Device, and ID3D12CommandQueue. Unsurprisingly, querying the native texture from a QRhiTexture returns a ID3D12Resource and a D3D12_RESOURCE_STATES.

​从QRhi实例中拉出本地对象时,QRhi::nativeHandles()提供一个QRhiD3D12NativeHandles,用于报告使用的适配器、ID3D12Device和ID3D12CommandQueue。不出所料,从QRhiTexture查询原生纹理会返回ID3D12Resource和D3D12_RESOURCE_STATES。

Some internal aspects

一些内部方面

Internally, the D3D12 backend is similar in many ways to the Vulkan backend. In some aspects it is simpler (no render pass and subpass nightmares), while in some other aspects it has to deal with a number of annoying problems, esp. in the area of shader resource bindings and when it comes to managing the  shader visible descriptor heaps.

在内部,D3D12后端在许多方面与Vulkan后端相似。在某些方面,它更简单(没有渲染过程和子过程噩梦),而在其他一些方面,它必须处理许多令人讨厌的问题,特别是在着色器资源绑定领域,以及在管理着色器可见描述符堆时。

Regarding shaders, the same pipeline is used as with everything else: the Vulkan-compatible GLSL source code is compiled to SPIR-V and then translated further via SPIRV-Cross to HLSL. The catch here is that certain constructs do not quite map as-is between SPIR-V and HLSL, so the translation process generates additional metadata, for example mapping tables that say, for example, to map SPIR-V combined image sampler binding X to HLSL shader registers tY and sZ. This metadata is then consumed by the QRhi backend. This then presents some interesting challenges when building root signatures and laying out descriptor tables, consider for instance that the mentioned mapping tables are per-shader and completely independent (i.e. the binding-register mappings may differ between the vertex and fragment shaders, ouch), which means not everything as optimal yet as it could be. (this applies to D3D11 as well, but that APIs higher-level nature conveniently hides some of these issues)

​关于着色器,与其他一切都使用相同的管道:Vulkan兼容的GLSL源代码被编译为SPIR-V,然后通过SPIRV Cross进一步翻译为HLSL。这里的问题是,某些构造在SPIR-V和HLSL之间并没有完全映射,因此转换过程会生成额外的元数据,例如映射表,例如,将SPIR-V组合图像采样器绑定X映射到HLSL着色器寄存器tY和sZ。然后,QRhi后端将使用这些元数据。这在构建根签名和布局描述符表时提出了一些有趣的挑战,例如,考虑到提到的映射表是每个着色器的并且完全独立(即,顶点着色器和片段着色器之间的绑定寄存器映射可能不同,ouch),这意味着并不是所有的东西都是最佳的。(这也适用于D3D11,但API更高级别的特性很方便地隐藏了其中的一些问题)

Textures, buffers, and command buffers work similarly to Vulkan. Texture uploads go through a staging buffer, although the D3D12 backend tries something new by reserving a small staging area per frame (slot), currently 16 MB and use that for all non-Dynamic buffer and texture uploads as long as they fit. Dynamic (host visible, i.e. upload heap) QRhiBuffers are backed by multiple ID3D12Resources, and the usual frame slot model is used to allow having potentially more than one frame in flight. (i.e., like other well-behaving backends, the D3D12 one also does not simply block and wait for completion when submitting the commands for a frame). Same goes for command buffers, these will cycle through FRAMES_IN_FLIGHT ID3D12GraphicsCommandLists in consecutive QRhi::beginFrame() calls. FRAMES_IN_FLIGHT is set to 2 at the moment.

​纹理、缓冲区和命令缓冲区的工作方式与Vulkan类似。纹理上传通过一个暂存缓冲区,尽管D3D12后端尝试了一些新的方法,为每帧(插槽)保留一个小的暂存区,目前为16MB,并将其用于所有非动态缓冲区和纹理上传,只要它们合适。动态(主机可见,即上传堆)QRhiBuffer由多个ID3D12Resources支持,并且通常的帧槽模型用于允许潜在的多个帧在飞行中。(即,与其他性能良好的后端一样,D3D12在提交帧的命令时也不会简单地阻塞并等待完成)。命令缓冲区也是如此,它们将在连续的QRhi::beginFrame()调用中循环通过FRAMES_IN_FLIGHT ID3D12GraphicsCommandLists。FRAMES_IN_FLIGHT此时被设置为2。

Compute shader support and storage images and buffers are implemented similarly to the other backends. In the unlikely event of using readonly SSBOs in the GLSL shader (e.g., layout (binding = 0, std430) readonly buffer someBuffer { ... }), it needs to be noted that Qt 6.6.0 has a bug where this gets translated to a HLSL ByteAddressBuffer (and that involves using an SRV), whereas the QRhi backend is only prepared to deal with RWByteAddressBuffer (and an UAV).

​计算着色器支持以及存储图像和缓冲区的实现与其他后端类似。在GLSL着色器中使用只读SSBO的不太可能的情况下(例如,布局(binding=0,std430)只读缓冲区someBuffer{…}),需要注意的是,Qt 6.6.0有一个错误,它被转换为HLSL ByteAddressBuffer(涉及使用SRV),而QRhi后端只准备处理RWByteAddressBuffer(和UAV)。

As with other backends, there are a number of environment variables that control backend-specific behavior or can be used to override some settings without changing the application:

与其他后端一样,有许多环境变量可以控制后端特定的行为,或者可以用于在不更改应用程序的情况下覆盖某些设置:

  • QT_D3D_ADAPTER_INDEX - As with D3D11, this is the manual, environment variable-based override for the adapter to use. To get the index, check the logs printed from QSG_INFO=1 (or qt.rhi.general).
  • QT_D3D_ADAPTER_INDEX-与D3D11一样,这是适配器要使用的基于环境变量的手动覆盖。要获取索引,请检查从QSG_INFO=1(或qt.ri.general)打印的日志。
  • QT_D3D_STABLE_POWER_STATE - Calls SetStablePowerState(), with all the consequences and implications described in the D3D documentation. This can become relevant when working with timestamps via QRhiCommandBuffer::lastCompletedGpuTime().  GPU timings are in fact another new feature in Qt 6.6, and are a topic for a future blog post.
  • ​QT_D3D_STABLE_POWER_STATE-调用SetTablePowerState(),具有D3D文档中描述的所有结果和含义。当通过QRhiCommandBuffer::lastCompletedGpuTime()处理时间戳时,这可能会变得相关。GPU计时实际上是Qt 6.6中的另一个新功能,也是未来博客文章的主题。
  • QT_RHI_LEAK_CHECK - Relevant in Release (or RelWithDebInfo) builds since Debug has this enabled automatically. When set to a non-zero value, the QRhi will print some helpful reminders if there are any resources (buffers, textures, etc.) not yet released at the time of destroying the QRhi instance. With the D3D12 backend this is extended to do more, as it checks for unreleased descriptors (RTVs, DSVs, SRVs, etc.) as well.
  • QT_RHI_LEAK_CHECK-在Release(或RelWithDebInfo)构建中相关,因为Debug已自动启用此功能。当设置为非零值时,如果在销毁QRhi实例时有任何资源(缓冲区、纹理等)尚未释放,QRhi将打印一些有用的提醒。对于D3D12后端,它进行了扩展以做更多的事情,因为它还检查未发布的描述符(RTV、DSV、SRV等)。
  • QT_D3D_DEBUG_BREAK - Enables breaking on Corruption, Error, and Warning severity messages from the debug layer.
  • ​QT_D3D_DEBUG_BREAK-启用中断来自调试层的损坏、错误和警告严重性消息。
  • QT_D3D_NO_SUBALLOC - Disables using D3D12MemoryAllocator. Doing so means every buffer and texture does its own CreateCommittedResource().
  • ​QT_D3D_NO_SUBALLOC-禁用使用D3D12MemoryAllocator。这样做意味着每个缓冲区和纹理都有自己的CreateCommittedResource()。

Why is this useful?

为什么这有用?

The D3D12 backend has two main benefits:

D3D12后端有两个主要优点:

One is mentioned above already: applications that have to integrate with non-Qt-based D3D12 code can now do this in a much more natural manner since they can get the QQuickWindow to also use D3D12. No need for going through translation layers such as D3D11On12 anymore. Examples of this are rendering/compute engines using features that are not currently used or do not apply to Qt's own 2D/3D renderers (e.g., raytracing, some sophisticated compute, work graphs, etc.) or machine learning code using DirectML.

​上面已经提到:必须与非基于Qt的D3D12代码集成的应用程序现在可以以更自然的方式实现这一点,因为它们可以让QQuickWindow也使用D3D12。不再需要经过像D3D11On12这样的转换层。这方面的例子是使用当前未使用或不适用于Qt自己的2D/3D渲染器的功能的渲染/计算引擎(例如,光线跟踪、一些复杂的计算、工作图等)或使用DirectML的机器学习代码。

The other, which benefits Qt itself in the long run, is to enable using new and upcoming features that are no longer made available in D3D11. A good example of this is in the (currently in-development) 6.7 branch of Qt. This already has the lowest-level enablers for multiview rendering integrated. (think GL_OVR_multiview and the equivalents in the other 3D APIs; having multiview support is going to be important for having efficient VR/AR support in Qt Quick 3D in the future) While there were (and still are) no plans to enable this in the D3D11 backend, since multiview is only available as vendor-specific extensions there, it turned out that having a D3D12 backend was great news because there we could use view instancing as-is.

​从长远来看,Qt本身的另一个好处是能够使用D3D11中不再提供的新功能和即将推出的功能。Qt的6.7分支(目前正在开发中)就是一个很好的例子。这已经集成了用于多视图渲染的最低级别的启用程序。(想想GL_OVR_multiveview和其他3D API中的等价物;具有多视图支持对于未来在Qt Quick 3D中实现高效的VR/AR支持将非常重要)虽然没有(现在仍然没有)在D3D11后端启用这一功能的计划,因为多视图仅作为供应商特定的扩展提供,事实证明,拥有D3D12后端是个好消息,因为在那里我们可以按原样使用视图实例化。​

(plus one: being able to run Qt applications on top of D3D12 allows using tools, such as PIX that were not possible before, or at least not without using a translation layer)

​(还有一点:能够在D3D12之上运行Qt应用程序,可以使用以前不可能使用的工具,如PIX,或者至少在不使用翻译层的情况下是不可能的)

What does this mean for graphics and compute shaders?

这对图形和计算着色器意味着什么?

For now pretty much nothing, because targeting Shader Model 5.0 is going to be sufficient. There are no changes around this in Qt Quick or Qt Quick 3D: all built-in material shaders are conditioned with --hlsl 50 when running qsb (either directly or via CMake). Applications should need no changes either: switching the QRhi backend to D3D12 is expected to just work without touching anything related to the shader conditioning settings.

​目前几乎没有什么,因为以Shader Model 5.0为目标就足够了。Qt Quick或Qt Quick 3D对此没有任何更改:当运行qsb(直接或通过CMake)时,所有内置材质着色器都使用--hlsl 50进行调节。应用程序也不需要更改:将QRhi后端切换到D3D12预计只需工作,而无需触摸与着色器条件设置相关的任何内容。

When thinking of some new features that may be taken into use in the future, Shader Model 5.0 may not be sufficient. For example, with the above-mentioned multiview work in Qt 6.7, shaders involved in multiview will need to use Shader Model 6.1 because SV_ViewID (to which gl_ViewIndex is translated) is only available from that version on. And the old DXBC-based tools (fxcD3DCompile()) are no longer sufficient for that, one needs to switch to the DXIL-based toolchain. This is why in Qt 6.7 we will transparently introduce running the modern shader compiler, dxc, and use the dxcapi.h interfaces at run time whenever the requested Shader Model is 6.0 or higher. For 5.0 nothing changes, that will continue to use the old tools.

​当考虑到未来可能使用的一些新功能时,Shader Model 5.0可能还不够。例如,在Qt 6.7中使用上述多视图时,多视图中涉及的着色器将需要使用Shader Model 6.1,因为SV_ViewID(gl_ViewIndex转换为SV_ViewID)只能从该版本开始使用。而旧的基于DXBC的工具(fxc,D3DCompile())已不足以实现这一点,需要切换到基于DXIL的工具链。这就是为什么在Qt 6.7中,我们将透明地引入运行现代着色器编译器dxc,并在请求的着色器模型为6.0或更高版本时在运行时使用dxcapi.h接口。对于5.0,没有任何更改,将继续使用旧工具。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值