Qt ApplicationManager的Compositor功能分析
- 根据Qt ApplicationManager官网介绍,它基于Wayland协议实现了Compositor功能。下述为官网介绍。实际上,QtApplicationManager是使用了QtWayland模块来实现Compositor的。Wayland是一套旨在替代XWindow的 Compositor标准,感兴趣的可自行了解。
To support multiple UI processes on an embedded Linux system, you need a central window compositor: a Wayland compositor is the state-of-the-art solution for this. Consequently, the application manager incorporates a compositor that is fully-compliant with the Wayland protocol, based on the QtWayland module.
- 关于Wayland Compositor,简单来说就是Client绘制Buffer,Compositor将这些Buffer融合,最终显示到屏幕上。大概就是下图这个样子(图片摘自QtWayland官网)。
- QtApplicationManager推荐使用QML方式进行开发,可以QML编写一套Compositor服务(进程)。
这里基于QML方式的Compositor Sample,分析一下QtApplicationManager是如何实现Compositor功能。
一个QML的Compositor例子
- 路径qtapplicationmanager\examples\applicationmanager\hello-world下提供了Hello World程序,其中system-ui.qml(启动入口)实现如下
/****************************************************************************
**
** Copyright (C) 2021 The Qt Company Ltd.
** Copyright (C) 2019 Luxoft Sweden AB
** Copyright (C) 2018 Pelagicore AG
** Contact: https://www.qt.io/licensing/
**
** This file is part of the QtApplicationManager module of the Qt Toolkit.
**
** $QT_BEGIN_LICENSE:BSD$
** Commercial License Usage
** Licensees holding valid commercial Qt licenses may use this file in
** accordance with the commercial license agreement provided with the
** Software or, alternatively, in accordance with the terms contained in
** a written agreement between you and The Qt Company. For licensing terms
** and conditions see https://www.qt.io/terms-conditions. For further
** information use the contact form at https://www.qt.io/contact-us.
**
** BSD License Usage
** Alternatively, you may use this file under the terms of the BSD license
** as follows:
**
** "Redistribution and use in source and binary forms, with or without
** modification, are permitted provided that the following conditions are
** met:
** * Redistributions of source code must retain the above copyright
** notice, this list of conditions and the following disclaimer.
** * Redistributions in binary form must reproduce the above copyright
** notice, this list of conditions and the following disclaimer in
** the documentation and/or other materials provided with the
** distribution.
** * Neither the name of The Qt Company Ltd nor the names of its
** contributors may be used to endorse or promote products derived
** from this software without specific prior written permission.
**
**
** THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
** "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
** LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
** A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
** OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
** SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
** LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
** DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
** THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
** (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
** OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE."
**
** $QT_END_LICENSE$
**
****************************************************************************/
import QtQuick 2.4
import QtApplicationManager.SystemUI 2.0
Item {
// 省略
// 重点关注这段代码
// Show windows
Column {
anchors.right: parent.right
Repeater {
model: WindowManager
WindowItem {
width: 600
height: 200
window: model.window
}
}
}
}
-
上面的代码中,将WindowManager对象内部的一系列window对象,绑定到 WindowItem上,并设置了宽和高。因为WaylandSurface与WindowManager中的 window对象是一对一的(后面会解释),所以这段代码,就是让Surface显示出来的本质。
-
创建WaylandSurface时WindowManager创建Window对象,具体实现如下。
// 绑定了QWaylandSurface::hasContentChanged信号,当有该信号时触发surfaceMapped信号。
// 该信号在Surface中有Buffer被Commit时触发
// 这块代码可以看出来 QtApplicationManager目前只支持 XDG和WlShell两种类型。
void WaylandCompositor::createWlSurface(QWaylandSurface *surface, const QWaylandResource &resource)
{
WindowSurface *windowSurface = static_cast<WindowSurface *>(surface);
QWaylandWlShellSurface *ss = new QWaylandWlShellSurface(m_wlShell, windowSurface, resource);
windowSurface->setShellSurface(ss);
connect(windowSurface, &QWaylandSurface::hasContentChanged, this, [this, windowSurface]() {
if (windowSurface->hasContent())
emit this->surfaceMapped(static_cast<WindowSurface*>(windowSurface));
});
}
void WaylandCompositor::onXdgSurfaceCreated(QWaylandXdgSurface *xdgSurface)
{
WindowSurface *windowSurface = static_cast<WindowSurface*>(xdgSurface->surface());
Q_ASSERT(!windowSurface->m_wlSurface);
windowSurface->m_xdgSurface = xdgSurface;
connect(windowSurface, &QWaylandSurface::hasContentChanged, this, [this, windowSurface]() {
if (windowSurface->hasContent())
emit this->surfaceMapped(windowSurface);
});
emit windowSurface->xdgSurfaceChanged();
}
void WindowManager::registerCompositorView(QQuickWindow *view)
{
// 省略
#if defined(AM_MULTI_PROCESS)
if (!ApplicationManager::instance()->isSingleProcess()) {
if (!d->waylandCompositor) {
d->waylandCompositor = new WaylandCompositor(view, d->waylandSocketName);
for (const auto &extraSocket : d->extraWaylandSockets)
d->waylandCompositor->addSocketDescriptor(extraSocket);
connect(d->waylandCompositor, &QWaylandCompositor::surfaceCreated,
this, &WindowManager::waylandSurfaceCreated);
connect(d->waylandCompositor, &WaylandCompositor::surfaceMapped,
this, &WindowManager::waylandSurfaceMapped);
// 省略
}
#else
if (!once)
qCInfo(LogGraphics) << "WindowManager: running in single-process mode [forced at compile-time]";
#endif
// 省略
}
// 这里函数里面,会创建Window对象,并将Window对象与Surface对象绑定。
void WindowManager::waylandSurfaceMapped(WindowSurface *surface)
{
qint64 processId = surface->processId();
const auto apps = ApplicationManager::instance()->fromProcessId(processId);
Application *app = nullptr;
if (apps.size() == 1) {
app = apps.constFirst();
} else if (apps.size() > 1) {
// if there is more than one app within the same process, check the XDG surface appId
const QString xdgAppId = surface->applicationId();
if (!xdgAppId.isEmpty()) {
for (const auto &checkApp : apps) {
if (checkApp->id() == xdgAppId) {
app = checkApp;
break;
}
}
}
}
// 只有开启了非安全模式,才可以接收非App侧创建的Surface
if (!app && ApplicationManager::instance()->securityChecksEnabled()) {
qCCritical(LogGraphics) << "SECURITY ALERT: an unknown application with pid" << processId
<< "tried to map a Wayland surface!";
return;
}
Q_ASSERT(surface);
qCDebug(LogGraphics) << "Mapping Wayland surface" << surface << "of" << d->applicationId(app, surface);
// Only create a new Window if we don't have it already in the window list, as the user controls
// whether windows are removed or not
int index = d->findWindowByWaylandSurface(surface->surface());
if (index == -1) {
WaylandWindow *w = new WaylandWindow(app, surface);
// 这里会将Window绑定到List上
setupWindow(w);
}
}
- 为什么将Window(对应Surface)绑定到WindowItem上可以显示出Surface的内容?将Window赋值给WindowItem,实际上调用的是下面的函数。所以实际上,是Surface设置给了QWaylandQuickItem。
void WindowItem::WaylandImpl::setup(Window *window)
{
Q_ASSERT(!m_waylandWindow);
Q_ASSERT(window && !window->isInProcess());
m_waylandWindow = static_cast<WaylandWindow*>(window);
if (!m_waylandItem)
createWaylandItem();
m_waylandItem->setBufferLocked(false);
m_waylandItem->setSurface(m_waylandWindow->surface());
}
void WindowItem::WaylandImpl::createWaylandItem()
{
m_waylandItem = new QWaylandQuickItem(q);
connect(m_waylandItem, &QWaylandQuickItem::surfaceDestroyed, q, [this]() {
// keep the buffer there to allow us to animate the window destruction
m_waylandItem->setBufferLocked(true);
});
}
- QWaylandQuickItem继承QQuickItem,并实现了updatePaintNode函数。继承QQuickItem的类,可以通过实现该虚函数来绘制自己想要的内容。这段代码比较长,大体上就是从Surface(也就是View)中拿出Buffer,做成纹理节点并返回。QT底层的RenderThread会将该节点绘制到屏幕上。
QSGNode *QWaylandQuickItem::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *data)
{
Q_D(QWaylandQuickItem);
d->lastMatrix = data->transformNode->combinedMatrix();
const bool bufferHasContent = d->view->currentBuffer().hasContent();
if (d->view->isBufferLocked() && d->paintEnabled)
return oldNode;
if (!bufferHasContent || !d->paintEnabled || !surface()) {
delete oldNode;
return nullptr;
}
QWaylandBufferRef ref = d->view->currentBuffer();
const bool invertY = ref.origin() == QWaylandSurface::OriginBottomLeft;
const QRectF rect = invertY ? QRectF(0, height(), width(), -height())
: QRectF(0, 0, width(), height());
if (ref.isSharedMemory()
#if QT_CONFIG(opengl)
|| bufferTypes[ref.bufferFormatEgl()].canProvideTexture
#endif
) {
#if QT_CONFIG(opengl)
if (oldNode && !d->paintByProvider) {
// Need to re-create a node
delete oldNode;
oldNode = nullptr;
}
d->paintByProvider = true;
#endif
// This case could covered by the more general path below, but this is more efficient (especially when using ShaderEffect items).
QSGSimpleTextureNode *node = static_cast<QSGSimpleTextureNode *>(oldNode);
if (!node) {
node = new QSGSimpleTextureNode();
if (smooth())
node->setFiltering(QSGTexture::Linear);
d->newTexture = true;
}
if (!d->provider)
d->provider = new QWaylandSurfaceTextureProvider();
if (d->newTexture) {
d->newTexture = false;
d->provider->setBufferRef(this, ref);
node->setTexture(d->provider->texture());
}
d->provider->setSmooth(smooth());
node->setRect(rect);
qreal scale = surface()->bufferScale();
QRectF source = surface()->sourceGeometry();
node->setSourceRect(QRectF(source.topLeft() * scale, source.size() * scale));
return node;
}
#if QT_CONFIG(opengl)
Q_ASSERT(!d->provider);
if (oldNode && d->paintByProvider) {
// Need to re-create a node
delete oldNode;
oldNode = nullptr;
}
d->paintByProvider = false;
QSGGeometryNode *node = static_cast<QSGGeometryNode *>(oldNode);
if (!node) {
node = new QSGGeometryNode;
d->newTexture = true;
}
QSGGeometry *geometry = node->geometry();
QWaylandBufferMaterial *material = static_cast<QWaylandBufferMaterial *>(node->material());
if (!geometry)
geometry = new QSGGeometry(QSGGeometry::defaultAttributes_TexturedPoint2D(), 4);
if (!material)
material = new QWaylandBufferMaterial(ref.bufferFormatEgl());
if (d->newTexture) {
d->newTexture = false;
material->setBufferRef(this, ref);
}
const QSize surfaceSize = ref.size() / surface()->bufferScale();
const QRectF sourceGeometry = surface()->sourceGeometry();
const QRectF normalizedCoordinates =
sourceGeometry.isValid()
? QRectF(sourceGeometry.x() / surfaceSize.width(),
sourceGeometry.y() / surfaceSize.height(),
sourceGeometry.width() / surfaceSize.width(),
sourceGeometry.height() / surfaceSize.height())
: QRectF(0, 0, 1, 1);
QSGGeometry::updateTexturedRectGeometry(geometry, rect, normalizedCoordinates);
node->setGeometry(geometry);
node->setFlag(QSGNode::OwnsGeometry, true);
node->setMaterial(material);
node->setFlag(QSGNode::OwnsMaterial, true);
return node;
#else
qCWarning(qLcWaylandCompositor) << "Without OpenGL support only shared memory textures are supported";
return nullptr;
#endif // QT_CONFIG(opengl)
}