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.0Item {// 省略// 重点关注这段代码// Show windowsColumn {anchors.right: parent.rightRepeater {model: WindowManagerWindowItem {width: 600height: 200window: 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);// 省略}
#elseif (!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 appIdconst QString xdgAppId = surface->applicationId();if (!xdgAppId.isEmpty()) {for (const auto &checkApp : apps) {if (checkApp->id() == xdgAppId) {app = checkApp;break;}}}}// 只有开启了非安全模式,才可以接收非App侧创建的Surfaceif (!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 notint 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 destructionm_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 nodedelete 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 nodedelete 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;
#elseqCWarning(qLcWaylandCompositor) << "Without OpenGL support only shared memory textures are supported";return nullptr;
#endif // QT_CONFIG(opengl)
}