Qt Quick and Widgets, Qt 6.4 Edition
Qt Quick和Widgets,Qt 6.4版
August 22, 2022 by Laszlo Agocs | Comments
2022年8月22日Laszlo Agocs |评论
QQuickWidget is a QWidget subclass, originally introduced in Qt 5.3. It is the bridge between Traditional widgets and the world of QML and Qt Quick. In this post we are going to take a look at its evolution and the latest improvements in the upcoming Qt 6.4 release, because there are some quite significant changes happening under the hood.
QQuickWidget是QWidget子类,最初在Qt 5.3中引入。它是传统Widget与QML和Qt Quick世界之间的桥梁。在这篇文章中,我们将看看它的发展和即将发布的Qt6.4版本中的最新改进,因为在幕后发生了一些非常重要的变化。
The contents of a widget-based window and a window showing a Qt Quick scene are rendered in a quite different way: the latter relies on 3D graphics APIs (*), such as OpenGL, Vulkan, Metal, or Direct 3D, whereas a widget-based user interface is rendered using QPainter and then blitted to the native window by windowing system specific means, for example, either BitBlt or UpdateLayeredWindowIndirect in the case of Windows. A QQuickWindow, or its convenience subclass QQuickView, are not directly usable in the QWidget hierarchy of a top-level widget window. (after all, QQuickWindow itself is a true QWindow, nothing to do with the world of QWidgets)
基于widget的窗口和显示Qt Quick场景的窗口的内容以完全不同的方式呈现:后者依赖于3D图形API(*),如OpenGL、Vulkan、Metal或Direct 3D,而基于Widget的用户界面则使用QPainter呈现,然后通过窗口系统特定的方式(例如,在Windows的情况下,BitBlt或UpdatelayeredWindow间接实现)。QQuickWindow或其方便的子类qquickview不能直接用于顶级widget窗口的QWidget层次结构。(毕竟,QQuickWindow本身就是一个真正的QWindow,与QWidgets的世界无关)
(*) For completeness it needs to be mentioned that Qt Quick does have a feature-limited software backend that renders what it can from the scene graph using QPainter, but that alone does not make it usable within a QWidget hierarchy.
(*)为了完整性,需要提到的是,Qt Quick确实有一个功能有限的软件后端,它可以使用QPaint从场景图中渲染所需的内容,但仅此一点并不能使其在QWidget层次结构中可用。
How does one combine the two worlds then?
那么,如何将这两个世界结合起来呢?
And why would one want to do so?
为什么要这样做?
A good example is one of Qt's tools: Qt Design Studio. While it may at first appear to be an application created fully with the Traditional widgets, it is in fact a combination of widgets of Qt Quick, using the best suited technology for each of its views and panels.
一个很好的例子是Qt的工具之一:Qt Design Studio。虽然它最初看起来是一个完全使用传统窗口小部件创建的应用程序,但实际上它是Qt Quick、widgets的组合,使用最适合其每个视图和面板的技术。
This Qt Creator window with the Design view open for a Qt Quick 3D example is in fact composed of four Qt Quick scenes and a single widget hierarchy, with the top-level window being a QWidget. For example, the properties panel on the right, the component grid on the left, or the state view on the bottom are all implemented with QML and Qt Quick.
该Qt Creator窗口打开了一个Qt Quick 3D示例的设计视图,实际上由四个Qt Quick场景和一个widget层次结构组成,顶层窗口是一个QWidget。例如,右侧的属性面板、左侧的组件网格或底部的状态视图都使用QML和Qt Quick实现。
The state of things as of Qt 5.1
Qt 5.1的状态
Before QQuickWidget, the solution to add Qt Quick content into a QWidget-based user interface was to use QWidget::createWindowContainer(), which is still a valid option today in some cases, but it comes with some potential limitations since there is no magic here: the QQuickWindow continues to be a proper native window in this case, Qt relies on the windowing system to ensure the window with Qt Quick content is positioned appropriately by making it a child window.
在QQuickWidget之前,将Qt Quick内容添加到基于QWidget的用户界面的解决方案是使用QWidge::createWindowContainer(),这在某些情况下仍然是一个有效的选项,但它有一些潜在的限制,因为这里没有魔力:在这种情况下,QQuickWindow继续是一个适当的本地窗口,Qt依赖于窗口系统,通过使其成为子窗口来确保具有Qt Quick内容的窗口被适当定位。
This is demonstrated by the embeddedinwidgets example that comes with Qt. From the windowing system's perspective this is not one but two windows. For simple cases, such as here, where we effectively just want the "foreign" Qt Quick content to occupy a rectangular area in the middle of our user interface, not having to worry about stacking, clipping, resizing, reparenting, or more complicated widgetish behavior, this works well.
Qt附带的embeddedinwidgets示例演示了这一点。从窗口系统的角度来看,这不是一个窗口,而是两个窗口。对于简单的情况,例如在这里,我们实际上只希望“外来”Qt Quick内容占据用户界面中间的矩形区域,而不必担心堆叠、剪切、调整大小、重新租用或更复杂的寡妇行为,这很好。
Qt 5.3: QQuickWidget!
The story changed slightly with Qt 5.3. That release introduced QQuickWidget, backed by a fairly sizable and somewhat complex infrastructure internally. The complications stem from the fact that while a QQuickWidget (or a QOpenGLWidget, for that matter, which is built on the same infrastructure) is a true QWidget on the surface, the underlying rendering system (Qt Quick) kNows nothing about widgets, backing stores, and such, and wants to produce frames using OpenGL, targeting a WGL/GLX/EGL/etc. surface. What happens under the hood is that the presence of such special, texture-based widgets trigger switching the corresponding top-level window over to being OpenGL-based, with a mini-compositor drawing quads textured with the content from the QQuick/OpenGLWidget children within that top-level combined with the QPainter-rendered widgets (i.e. the rest of the top-level window content) which Now needs to be uploaded into a texture as well.
随着Qt5.3的发布,情况发生了轻微变化。该版本引入了QQuickWidget,其背后有一个相当大且内部有点复杂的基础设施。复杂的原因在于,虽然QQuickWidget(或qopenglwidget,构建在同一基础设施上)在表面上是一个真正的QWidget,但底层渲染系统(Qt-Quick)对Widget、后台存储等一无所知,并希望使用OpenGL生成帧,以WGL/GLX/EGL/等表面为目标。这种特殊的、基于纹理的widget的存在触发了相应的顶层窗口切换为基于OpenGL,使用一个小型合成器绘制四边形,该四边形使用来自该顶层中的QQuick/OpenGLWidget子级的内容与QPaint渲染的widget(即,顶层窗口内容的其余部分)进行纹理化,该widget现在也需要上传到纹理中。
The full solution consists of the following components:
完整解决方案由以下组件组成:
1.The producer, such as QQuickWidget or qopenglwidget.
This implies that the producer is able to render to a texture. For Qt Quick in particular this was not trivial, but is enabled by QQuickRenderControl and QQuickRenderTarget (Qt 6) / setRenderTarget (Qt 5).
1.生产者,如QQuickWidget或qopenglwidget。
这意味着生产者能够渲染到纹理。特别是对于Qt-Quick,这并不是小事,而是通过QQuickRenderControl和QQuickrendertarget(Qt 6)/setrendertarget(Qt 5)启用的。
2.VarIoUs plumbing in the Widgets module. For instance, top-level widgets need to do tracking and bookkeeping to recognize whenever a special widget becomes visible in their widget hierarchy.
2.Widgets模块中的各种管道。例如,顶级小部件需要进行跟踪和簿记,以便在其widget层次结构中看到特定widget时进行识别。
3.The consumer: the compositor in the Gui module, implemented somewhere around QPlatformbackingStore. Complemented by functionality to get a texture out of the non-OpenGL widgets' backing images. In Qt 5.3 all this was implemented with directly with OpenGL, naturally.
3.消费者:Gui模块中的合成器,在QPlatformbackingStore附近实现。补充了从非OpenGL widget的背景图像中获取纹理的功能。在Qt5.3中,所有这些都是用OpenGL直接实现的。
In practice things are a bit more convoluted, for example QQuickWidget has to have support for the software backend of Qt Quick as well, which then requires it to be able to operate also like a Traditional widget (with regards to paint events and alike), but let's set these complications aside for Now and focus on the OpenGL side of things.
在实践中,事情有点复杂,例如QQuickWidget也必须支持Qt Quick的软件后端,这就要求它也能够像传统的widget一样操作(关于绘制事件等),但现在让我们把这些复杂的事情放在一边,专注于OpenGL方面的事情。
The QQuickWidget API is modeled after QQuickView. This makes it fairly intuitive, one can for instance do something like this:
QQuickWidget API以qquickview为模型。这使得它相当直观,例如,可以执行以下操作:
QQuickWidget *w = new QQuickWidget;
w->setResizeMode(QQuickWidget::SizeRootObjectToView);
w->setSource(QUrl("qrc:/main.qml"));
mainWindow->setCentralWidget(w);
One of the classic Qt 5 examples of QQuickWidget is the QQuickWidget - qquickview comparison. This demonstrates on of the benefits of QQuickWidget being a proper widget, as it allows a child widget to be placed on top as expected:
QQuickWidget的经典Qt5示例之一是QQuickWidget-qquickview比较。这演示了QQuickWidget作为一个合适的widget的好处,因为它允许将子widget按预期放置在顶部:
What about Qt 6?
Qt 6呢?
In Qt 6 OpenGL is not the only option. On some platforms OpenGL is not even the default choice anymore.
在Qt 6中,OpenGL不是唯一的选项。在某些平台上,OpenGL甚至不再是默认选择。
For the 6.0 release Qt Quick and Qt Quick 3D got ported to use the new 3D API abstraction layer (QRhi), instead of working directly with OpenGL. This Now means that launching a Qt Quick application on Windows renders using Direct 3D 11, whereas compiling and running the same application on macOS renders with Metal, unless the application explicitly requests using some other 3D API.
在6.0版本中,Qt Quick和Qt Quick3D被移植为使用新的3D API抽象层(QRhi),而不是直接使用OpenGL。这意味着在Windows上启动Qt Quick应用程序使用Direct 3D 11渲染,而在macOS上编译和运行同一应用程序使用Metal渲染,除非应用程序明确请求使用其他3D API。
This left QQuickWidget and the supporting infrastructure out in the cold to a large degree. With the backing infrastructure being stuck with OpenGL and OpenGL texture objects, in Qt 6.0 and the first few releases afterwards QQuickWidget was only usable if the application's requested graphics API was OpenGL. Thus developers had two choices:
这使得QQuickWidget和支持的基础设施在很大程度上处于困境。在Qt 6.0和之后的前几个版本中,由于支持基础设施与OpenGL和OpenGL纹理对象有关,QQuickWidget仅在应用程序请求的图形API为OpenGL时可用。因此,开发人员有两种选择:
- Request OpenGL by calling QQuickWindow::setGraphicsApi() (**) and accept that that is what the application uses, regardless of the platform.
- 通过调用QQuickWindow::setGraphicsApi()(**)请求OpenGL,并接受应用程序使用的是OpenGL,而不考虑平台。
- Move to the native child window approach, and embed a qquickview with QWidget::createWindowContainer(). This meant losing the widget benefits of QQuickWidget, but allowed using any of the supported graphics APIs at run time.
- 移动到本机子窗口方法,并使用QWidget::createWindowContainer()嵌入qquickview。这意味着失去了QQuickWidget的widget优势,但允许在运行时使用任何受支持的图形API。
(**) For the curIoUs, this being a static function is mandated by certain legacy from Qt 5.0 and even pre-5.0 times, and while not entirely ideal, it is not likely to change in the near future. It is not ideal that the API to use can only be specified on a global level, affecting all QQuickWindow/View/Widgets created afterwards, without the ability to switch to something else later on, but as parts of the backing internal infrastructure are either application global (render loops) or thread local at best (animation driver), switching over to finer granularity for these things is not entirely trivial. It is recognized however that this will need to change at some point, to allow control on a per-window basis.
(**)出于好奇,这是一个静态函数,由Qt 5.0甚至5.0之前的某些遗留版本强制执行,虽然并不完全理想,但在不久的将来不太可能改变。不理想的是,只能在全局级别上指定要使用的API,影响随后创建的所有QQuickWindow/View/Widget,而不能在以后切换到其他组件,但作为后台内部基础设施的一部分,应用程序全局(渲染循环)或线程局部(动画驱动程序),为这些事情切换到更精细的粒度并不完全是小事。然而,我们认识到,这将需要在某个点上进行更改,以允许在每个窗口的基础上进行控制。
What is new in Qt 6.4?
Qt 6.4有什么新功能?
Comparing the documentation for 6.3 and 6.4 reveals that QQuickWidget finally drops the OpenGL requirement.
比较6.3和6.4的文档表明,QQuickWidget最终放弃了OpenGL要求。
Therefore, applications using QQuickWidget no longer have to do something like QQuickWindow::setGraphicsApi(QSGRendererInterface::OpenGL); in their main(). (unless of course if they themselves do rendering directly with OpenGL)
因此,使用QQuickWidget的应用程序不再需要像QQuickWindow::setGraphicsApi(QsgrenderInterface::OpenGL),在main()中调用。(当然,除非他们自己直接使用OpenGL进行渲染。)
The rules for choosing the 3D API match QQuickWindow and qquickview. If the platform default is not deemed suitable, the application can still call setGraphicsApi() or set the appropriate environment variables.
选择3D API的规则与QQuickWindow和qquickview相匹配。如果认为平台默认值不合适,应用程序仍然可以调用setGraphicsApi()或设置适当的环境变量。
Take this example application: Laszlo Agocs / qwsample · GitLab
以这个应用程序为例:Laszlo Agocs/qwsample·GitLab
This puts three Qt Quick scenes, some with some true 3D content, into a QTabWidget. This is not very special on its own. What makes this test application more interesting is that it interactively asks for the 3D graphics API to use on startup and then calls QQuickWindow::setGraphicsApi() with the selection. This sort of user-controlled API selection is not something most real world application should do, but is useful for demonstration purposes.
这将三个Qt Quick场景,其中一些具有真实的3D内容,放入一个QTabWidget中。这本身并不特别。这个测试应用程序更有趣的是,它以交互方式要求启动时使用3D图形API,然后使用选择调用QQuickWindow::setGraphicsApi()。这种用户控制的API选择不是大多数真实应用程序应该做的,但对于演示目的很有用。
The Qt Quick scenes show the active API queried via GraphicsInfo in the top right corner. Additionally, one Could launch with the environment variable QSG_INFO=1 to verify what is happening. Or take a frame capture with a tool like RenderDoc.
Qt Quick场景在右上角显示了通过GraphicsInfo查询的活动API。此外,可以使用环境变量QSG_INFO=1启动以验证正在发生的情况。或者使用RenderDoc等工具进行帧捕获。
Some notes on the implementation as of Qt 6.4
关于Qt 6.4实现的一些注意事项
For applications that employ multiple QQuickWidget instances, or perhaps a qopenglwidget or two within one top-level widget window, there is one important limit to keep in mind: all the widgets within the same window must use the same rendering API.
对于使用多个QQuickWidget实例的应用程序,或者在一个顶级widget窗口中使用一个或两个qopenglwidget的应用程序来说,需要记住一个重要的限制:同一窗口中的所有widget必须使用相同的渲染API。
This is relevant in particular when one is using qopenglwidget: putting a qopenglwidget into a window mandates that any QQuickWidget in the same window is also rendering using OpenGL. When there is disagreement between the widgets, bad things will happen. (in less bombastic terms, the widget(s) losing the fight will not render their content)
这一点在使用qopenglwidget时尤为重要:将qopenglwidget放入一个窗口要求同一窗口中的任何QQuickWidget也使用OpenGL进行渲染。当widget之间存在分歧时,就会发生不好的事情。(用不那幺夸张的话来说,输掉比赛的widget将不会呈现其内容)
In general, qopenglwidget is expected to function like before, and there are no changes in the public API. Even though internally certain things function quite differently than before, from the applications' perspective there should be no visible changes.
一般来说,qopenglwidget的功能与以前一样,公共API没有变化。尽管某些东西的内部功能与以前大不相同,但从应用程序的角度来看,应该没有明显的变化。
To make all this possible, the backing infrastructure had to undergo a number of changes:
为了使这一切成为可能,支持基础架构必须进行一些更改:
1.When it comes to the producers (QQuickWidget, qopenglwidget), both needed some changes, to migrate away from relying on things like QOpenGLContext and to work with QRhi's objects for textures and render targets instead of just sending gluint texture IDs all over the place.
1.当涉及到生产者(QQuickWidget、qopenglwidget)时,两者都需要一些改变,以摆脱对QOpenGLContext等东西的依赖,并使用QRhi的对象来处理纹理和渲染目标,而不是只在各地发送gluint纹理ID。
Fortunately for QQuickWidget, Qt 6.0 and 6.1 already introduced all the necessary plumbing and APIs to enable getting Qt Quick to render to a Vulkan image, Metal texture, etc. Fun fact: one of the reasons for this being in such good shape was the VR and OpenXR experiments recently made publicly available. As integrating with OpenXR while supporting all of Vulkan, Direct 3D, and OpenGL presents, to a certain degree at least, similar requirements and challenges as the implementation of QQuickWidget (redirect Qt Quick into a texture, pass it on to another either internal or foreign component, etc.), most things were in place already for some time.
幸运的是,对于QQuickWidget,Qt 6.0和6.1已经引入了所有必要的管道和API,以使Qt Quick渲染到Vulkan图像、Metal纹理等。有趣的事实:这种良好状态的原因之一是最近公开的VR和OpenXR实验。由于与OpenXR集成,同时支持所有Vulkan、Direct 3D和OpenGL,至少在一定程度上提出了与QQuickWidget实现类似的要求和挑战(将Qt Quick重定向到纹理中,将其传递到另一个内部或外部组件等),大多数事情已经存在了一段时间。
2.The plumbing in the Widgets module. This had to be changed in a number of ways. Moving away from OpenGL constructs such as texture IDs is fairly straightforward. What is not straightforward, and one important consequence of supporting more than just OpenGL is how the top-level widgets' backing QWindows are handled.
2.Widgets模块中的管道。这必须以多种方式加以改变。远离OpenGL构造(如纹理ID)是相当简单的。这并不简单,而且支持OpenGL的一个重要后果是如何处理顶级widgets的支持QWindows。
Qt 5.3 introduced, together with QQuickWidget, a somewhat odd, fairly undocumented RasterGLSurface value in QSurface::SurfaceType. For applications this enumeration value did not play much of a role in practice, but was used extensively internally, and was meant to indicate that the intended usage of the window was not kNown in advance. Such a window may be used with a backing store for raster content, or may eventually be switched over to a window targeted with OpenGL rendering. Which is exactly what was happening for a top-level widget's window in case a QQuickWidget or qopenglwidget got added and made visible in its child widget hierarchy. This system is no longer acceptable due to how some of the window and surface/layer plumbing works for some of the other 3D APIs. (note that Qt 6 has dedicated surface types for all the supported 3D APIs) Therefore, similarly to reparenting between top-levels or when moving a window between screens, the associated QWindow (and so native window) is Now recreated by QWidget.
Qt5.3与QQuickWidget一起,在QSurface::SurfaceType中引入了一个有点奇怪、相当没有文档记录的RasterGLSurface值。对于应用程序,该枚举值在实践中并没有发挥多大作用,但在内部被广泛使用,旨在表明窗口的预期用途事先未知。这样的窗口可以与光栅内容的后备存储一起使用,或者最终可以切换到以OpenGL渲染为目标的窗口。这正是在添加QQuickWidget或qopenglwidget并使其在其子widget层次结构中可见的情况下,顶级widget的窗口所发生的情况。由于某些窗口和表面/层管道对某些其他3D API的工作方式,此系统不再可接受。(请注意,Qt 6为所有支持的3D API提供了专用的表面类型)因此,类似于在顶层之间重新租用或在屏幕之间移动窗口时,关联的QWindow(以及本机窗口)现在由QWidget重新创建。
This presents no visible changes for applications that have a (mostly) static widget hierarchy and then make the top-level visible (because it will get the correct type right from the start if any special widgets are already there in the hierarchy), but it does present a potential visible effect, depending on the platform and windowing system for applications that dynamically parent QQuickWidget or qopenglwidget instances into the widget hierarchy at a later point, because it is only at that point then that the window switches over to 3D API-based composition, which implies an appropriately configured QWindow under the hood.
这对于具有(大部分)静态widget层次结构的应用程序没有可见的变化,然后使顶层可见(因为如果层次结构中已经存在任何特殊widget,它将从一开始就获得正确的类型),但它确实存在潜在的可见效果,根据应用程序的平台和窗口系统,这些应用程序会在稍后将QQuickWidget或qopenglwidget实例动态地父级化到widget层次结构中,因为只有在这一点上,窗口才会切换到基于3D API的组合,这意味着在幕后有一个适当配置的QWindow。
3.The compositor in the Gui module is Now working with the (as of Now still internal) QRhi APIs, no direct OpenGL usage is allowed anymore. The end result should match the rendering from pre-6.4 (same set of draw calls), with the added bonus of being able to perform the same with Vulkan, Metal, or Direct 3D as well.
3.Gui模块中的合成器现在正在使用QRhi API(目前仍然是内部的),不再允许直接使用OpenGL。最终结果应与6.4版之前的渲染(相同的绘制调用集)相匹配,此外,还可以在Vulkan、Metal或Direct 3D中执行相同的渲染。
As a positive side effect, this may also pave the way for other features in the future. The ability to output widget content in a cross-platform manner using an accelerated 3D API can be useful for other purposes as well. For example, for performing scaling in the context of high DPI support, which is currently under research. Or, in case the QRhi family of APIs become public at some point in the future, implementing a supporting QRhiWidget is fairly straightforward then since the enablers are all there already.
作为一个积极的副作用,这也可能为将来的其他功能铺平道路。使用加速3D API以跨平台方式输出widget内容的能力也可用于其他目的。例如,用于在当前正在研究的高DPI支持的情况下执行缩放。或者,如果QRhi系列API在未来某个时候公开,那么实现支持QRhiWidget是相当简单的,因为启用程序已经存在。
It should be noted that in the uncommon case of an application wanting to use QQuickWidget in combination with the software (QPainter-based) rendering path of Qt Quick, this is fully supported still.
需要注意的是,在应用程序希望将QQuickWidget与Qt Quick的软件(基于QPaint的)渲染路径结合使用的罕见情况下,这仍然是完全支持的。
Let's dissect Qt Design Studio's UI rendering
让我们剖析一下Qt Design Studio的UI呈现
All this means that applications such as Qt Design Studio can Now function using each platform's default, best-supported rendering API, even when QQuickWidget is in use. Using a custom build of Qt Creator and the designer components against a build of Qt with some of the upcoming 6.4 work integrated, we can see all this in action also in a real world application as well.
所有这一切意味着,Qt Design Studio等应用程序现在可以使用每个平台的默认、最受支持的渲染API运行,即使在使用QQuickWidget时也是如此。使用Qt Creator的自定义构建和针对Qt构建的设计器组件,并集成了即将到来的6.4工作,我们也可以在现实世界的应用程序中看到所有这些。
Taking RenderDoc capture on Windows gives us the following frame capture, showing the mini-compositor in action. The main window is Now apparently rendering using Direct 3D 11, and the presence of 4 QQuickWidgets triggers the following steps in the main render pass:
在Windows上进行RenderDoc捕获,我们将获得以下帧捕获,显示正在运行的迷你合成器。现在,主窗口显然正在使用Direct 3D 11进行渲染,4个QQuickWidgets的出现将触发主渲染过程中的以下步骤:
(note the highlighted command in the left panel; the texture sampled in the last draw call is the Traditional QPainter-rendered widget content)
(请注意左面板中高亮显示的命令;上次绘制调用中采样的纹理是传统的QPaint渲染widget内容。)
And there we have it, the expected final image in the window, no longer forced to use OpenGL. This can be particularly important on platforms where OpenGL is not desired (e.g. because it has been deprecated), meaning the 2D and 3D design views can Now work with the platform's primary graphics API. In addition, not being tied to OpenGL may become useful later on, for example in case Qt Quick 3D takes on features that work best with more modern graphics APIs, because using the same underlying rendering in both the design views and at run time is certainly a better choice then.
在这里,我们看到了窗口中预期的最终图像,不再强制使用OpenGL。这在不需要OpenGL的平台上尤其重要(例如,因为它已被弃用),这意味着2D和3D设计视图现在可以与平台的主要图形API一起工作。此外,不与OpenGL绑定可能会在以后变得有用,例如,如果Qt Quick 3D采用的功能与更现代的图形API配合使用效果最佳,因为在设计视图和运行时使用相同的底层渲染肯定是更好的选择。