Part.1 如何实现一个基于 wlroots 的合成器

名词概念

Qt

Qt（/ˈkjuːt/，发音同 “cute”）是一个跨平台的 C++ 应用程序开发框架。广泛用于开发 GUI 程序，这种情况下又被称为部件工具箱。也可用于开发非 GUI 程序，例如控制台工具和服务器。

wlroots

用于构建 Wayland 合成器的模块化工具集，简化了约 60,000 行代码的开发工作。

提供抽象底层显示和输入的后端，支持 KMS/DRM、libinput、Wayland、X11 等，可动态创建和销毁。

实现多种 Wayland 接口，支持协议扩展，促进合成器标准化。

提供通用合成器组件，如物理空间输出管理。

集成 Xwayland 抽象，简化 X11 窗口管理。

提供渲染器抽象，支持简单和自定义渲染需求。

seat

由分配给特定工作场景的所有硬件设备组成。它至少包含一个图形设备，通常还有键盘和鼠标。此外，它可能包括摄像头、声卡等设备。座位由座位名称标识，这是一个短字符串（不超过 64 个字符），以 "seat" 四个字符开头，后跟至少一个 a-zA-Z0-9 范围内的字符，或 "_" 和 "-"。这种命名方式适合用于文件名。座位名称可能是稳定的，也可能不稳定，如果座位再次可用，其名称可以重复使用。

RHI

RHI 是 Renderer Hardware Interface（渲染硬件接口）的缩写，是一套对硬件的抽象，在上层只需要设置参数，底层具体使用的是 OpenGL、Vulkan、DX12 还是 Metal 哪套接口，我们是不必关心的。

Qt6 提供了 QRHI，为 Qt 程序提供了底层的硬件抽象，这样上层的 QtQuick 组件在执行 GPU 渲染时，就可以自动调用对应的驱动接口。

QPA

Qt 平台抽象（QPA）是 Qt 中的核心平台抽象层。

QPA 的 API 可通过类前缀 "QPlatform*" 识别，用于实现 Qt GUI 中的高级类。例如，QPlatformWindow 用于窗口系统集成，而 QPlatformTheme 和 QStyleHint 则用于深层次的平台主题和集成。

基本工作流程

Treeland 使用 QQuickWindow 作为渲染的根，这样在 Treeland 里开发时，就如同开发一个普通 Qt 程序一样，先创建一个 Window，在 Window 内创建 Qt 控件，使用 QEvent 处理各种事件。

那么 Treeland 是如何实现这件事的呢？

QQuickWindow 的私有类提供了自定义 QQuickGraphicsDevice 对象的接口，而 QQuickGraphicsDevice 可以使用 fromOpenGLContext 和 fromPhyicalDevice 创建新的对象，那么 Treeland 只需要继承 QQuickWindow，并从 wlroots 获取 OpenGL context 和 phyical device，就可以将 Qt QuickWindow 的渲染，嫁接到 wlroots 上。

通过将 wlroots 的渲染上下文与 Qt 的渲染上下文进行结合，可以将 wlroots 渲染的图形结果嵌入到 Qt 应用程序的渲染流程中，可以直接使用 wlroots 提供的图形资源和设备对象，如物理设备（phdev）、逻辑设备（dev）和队列族（queue_family），以减少不必要的上下文切换和资源拷贝。这样，Qt 就可以利用 wlroots 提供的渲染能力，同时能够继续使用 Qt 的渲染框架和 API。

之后在 Qt QPA 中将屏幕信息，以及输入信息转换成 Qt 内部对象，从而利用 Qt 自身的事件循环等机制继续处理。

Qt QPA

QPA 为 Qt 提供了跨平台的接口抽象能力，我们可以提供自己的 QPA 插件来为 Qt 程序提供新的能力，例如将 wlroots 的输入事件转换成 Qt 内部事件。

输入事件处理

Treeland 处理底层事件与上层事件的流程

Copy
bool WOutputRenderWindow::event(QEvent *event)
{
Q_D(WOutputRenderWindow);

if (event->type() == doRenderEventType) {
QCoreApplication::removePostedEvents(this, doRenderEventType);
d_func()->doRender();
return true;
}

if (QW::RenderWindow::beforeDisposeEventFilter(this, event)) {
event->accept();
QW::RenderWindow::afterDisposeEventFilter(this, event);
return true;
}

bool isAccepted = QQuickWindow::event(event);
if (QW::RenderWindow::afterDisposeEventFilter(this, event))
return true;

return isAccepted;
}

在 WOutputRenderWindow 的事件处理中，会额外调用下 seat 的事件过滤器，确保合成器可以拦截掉一部分事件，例如将一部分按键拦截下来，不发送给客户端。

Copy
bool QWlrootsRenderWindow::beforeDisposeEventFilter(QEvent *event)
{
if (event->isInputEvent()) {
auto ie = static_cast<QInputEvent*>(event);
auto device = WInputDevice::from(ie->device());
Q_ASSERT(device);
Q_ASSERT(device->seat());
lastActiveCursor = device->seat()->cursor();
return device->seat()->filterEventBeforeDisposeStage(window(), ie);
}

return false;
}

这段代码展示了转换输入设备的功能，判断输入设备的类型，创建对应的 QInputDevice 对象。

Copy
QPointer<QInputDevice> QWlrootsIntegration::addInputDevice(WInputDevice *device, const QString &seatName)
{
QPointer<QInputDevice> qtdev;
auto qwDevice = device->handle();
const QString name = QString::fromUtf8(qwDevice->handle()->name);
qint64 systemId = reinterpret_cast<qint64>(device);

switch (qwDevice->handle()->type) {
case WLR_INPUT_DEVICE_KEYBOARD: {
qtdev = new QInputDevice(name, systemId, QInputDevice::DeviceType::Keyboard, seatName);
break;
}
case WLR_INPUT_DEVICE_POINTER: {
qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::TouchPad, QPointingDevice::PointerType::Generic,
QInputDevice::Capability::Position | QInputDevice::Capability::Hover
| QInputDevice::Capability::Scroll | QInputDevice::Capability::MouseEmulation,
10, 32, seatName, QPointingDeviceUniqueId());
break;
}
case WLR_INPUT_DEVICE_TOUCH: {
qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::TouchScreen, QPointingDevice::PointerType::Finger,
QInputDevice::Capability::Position | QInputDevice::Capability::Area | QInputDevice::Capability::MouseEmulation,
10, 32, seatName, QPointingDeviceUniqueId());
break;
}
case WLR_INPUT_DEVICE_TABLET_TOOL: {
qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::Stylus, QPointingDevice::PointerType::Pen,
QInputDevice::Capability::XTilt | QInputDevice::Capability::YTilt | QInputDevice::Capability::Pressure,
1, 32, seatName, QPointingDeviceUniqueId());
break;
}
case WLR_INPUT_DEVICE_TABLET_PAD: {
auto pad = wlr_tablet_pad_from_input_device(qwDevice->handle());
qtdev = new QPointingDevice(name, systemId, QInputDevice::DeviceType::TouchPad, QPointingDevice::PointerType::Pen,
QInputDevice::Capability::Position | QInputDevice::Capability::Hover | QInputDevice::Capability::Pressure,
1, pad->button_count, seatName, QPointingDeviceUniqueId());
break;
}
case WLR_INPUT_DEVICE_SWITCH: {
qtdev = new QInputDevice(name, systemId, QInputDevice::DeviceType::Keyboard, seatName);
break;
}
}

if (qtdev) {
device->setQtDevice(qtdev);
QWindowSystemInterface::registerInputDevice(qtdev);

if (qtdev->type() == QInputDevice::DeviceType::Mouse || qtdev->type() == QInputDevice::DeviceType::TouchPad) {
auto primaryQtDevice = QPointingDevice::primaryPointingDevice();
if (!WInputDevice::from(primaryQtDevice)) {
// Ensure the primary pointing device is the WInputDevice
auto pd = const_cast<QPointingDevice*>(primaryQtDevice);
pd->setParent(nullptr);
delete pd;
}
Q_ASSERT(WInputDevice::from(QPointingDevice::primaryPointingDevice()));
} else if (qtdev->type() == QInputDevice::DeviceType::Keyboard) {
auto primaryQtDevice = QInputDevice::primaryKeyboard();
if (!WInputDevice::from(primaryQtDevice)) {
// Ensure the primary keyboard device is the WInputDevice
auto pd = const_cast<QInputDevice*>(primaryQtDevice);
pd->setParent(nullptr);
delete pd;
}
Q_ASSERT(WInputDevice::from(QInputDevice::primaryKeyboard()));
}
}

return qtdev;
}

客户端事件

在 Treeland 还有一种事件需要处理，当用户点击一个窗口，合成器需要告知客户端哪个坐标点击了。或者使用键盘进行输入时，需要告知客户端输入的内容。

首先，Treeland 会标记一个窗口成为激活窗口，设置给 seat，这样 wlroots 就知道哪个窗口此时拥有焦点。

之后当键盘发生输入事件时，Treeland 没有过滤掉按键事件，或者是放行某些按键，这些剩余的输入事件就会在 wseat 的 sendEvent 中，发送给激活的客户端。

Copy
// for keyboard event
inline bool doNotifyKey(WInputDevice *device, uint32_t keycode, uint32_t state, uint32_t timestamp) {
if (!keyboardFocusSurface())
return false;

q_func()->setKeyboard(device);
/* Send modifiers to the client. */
this->handle()->keyboard_notify_key(timestamp, keycode, state);
return true;
}

屏幕信息

在 QPA 中还对 WOutput 进行了封装 QWlrootsScreen。

Copy
QWlrootsScreen *QWlrootsIntegration::addScreen(WOutput *output)
{
m_screens << new QWlrootsScreen(output);

if (isMaster()) {
QWindowSystemInterface::handleScreenAdded(m_screens.last());

if (m_placeholderScreen) {
QWindowSystemInterface::handleScreenRemoved(m_placeholderScreen.release());
}
} else {
Q_UNUSED(new QScreen(m_screens.last()))
}

m_screens.last()->initialize();
output->setScreen(m_screens.last());

return m_screens.last();
}

QWlrootsScreen 继承自 QPlatformScreen，做的事情是将部分参数进行转换，例如 physicalSize、devicePixelRatio、DPI 等，之后通过 QWindowSystemInterface::handleScreenAdded 将创建好的 QWlrootsScreen 添加进 Qt 内。

Qt RHI

摘抄一段来自 waylib 中初始化 Qt RHI 的代码

Copy
bool WOutputRenderWindowPrivate::initRCWithRhi()
{
W_Q(WOutputRenderWindow);

QQuickRenderControlPrivate *rcd = QQuickRenderControlPrivate::get(rc());
QSGRhiSupport *rhiSupport = QSGRhiSupport::instance();

// sanity check for Vulkan
#ifdef ENABLE_VULKAN_RENDER
if (rhiSupport->rhiBackend() == QRhi::Vulkan) {
vkInstance.reset(new QVulkanInstance());

auto phdev = wlr_vk_renderer_get_physical_device(m_renderer->handle());
auto dev = wlr_vk_renderer_get_device(m_renderer->handle());
auto queue_family = wlr_vk_renderer_get_queue_family(m_renderer->handle());

#if QT_VERSION > QT_VERSION_CHECK(6, 6, 0)
auto instance = wlr_vk_renderer_get_instance(m_renderer->handle());
vkInstance->setVkInstance(instance);
#endif
// vkInstance->setExtensions(fromCStyleList(vkRendererAttribs.extension_count, vkRendererAttribs.extensions));
// vkInstance->setLayers(fromCStyleList(vkRendererAttribs.layer_count, vkRendererAttribs.layers));
vkInstance->setApiVersion({1, 1, 0});
vkInstance->create();
q->setVulkanInstance(vkInstance.data());

auto gd = QQuickGraphicsDevice::fromDeviceObjects(phdev, dev, queue_family);
q->setGraphicsDevice(gd);
} else
#endif
if (rhiSupport->rhiBackend() == QRhi::OpenGLES2) {
Q_ASSERT(wlr_renderer_is_gles2(m_renderer->handle()));
auto egl = wlr_gles2_renderer_get_egl(m_renderer->handle());
auto display = wlr_egl_get_display(egl);
auto context = wlr_egl_get_context(egl);

this->glContext = new QW::OpenGLContext(display, context, rc());
bool ok = this->glContext->create();
if (!ok)
return false;

q->setGraphicsDevice(QQuickGraphicsDevice::fromOpenGLContext(this->glContext));
} else {
return false;
}

QOffscreenSurface *offscreenSurface = new QW::OffscreenSurface(nullptr, q);
offscreenSurface->create();

QSGRhiSupport::RhiCreateResult result = rhiSupport->createRhi(q, offscreenSurface);
if (!result.rhi) {
qWarning("WOutput::initRhi: Failed to initialize QRhi");
return false;
}

rcd->rhi = result.rhi;
// Ensure the QQuickRenderControl don't reinit the RHI
rcd->ownRhi = true;
if (!rc()->initialize())
return false;
rcd->ownRhi = result.own;
Q_ASSERT(rcd->rhi == result.rhi);
Q_ASSERT(!swapchain);

return true;
}

先获取 QSGRhiSupport 及相关控制对象。

判断 RHI backend 的类型，需要适配 vulkan、gles等。

从 wlroots 获取物理设备等参数，使用 QQuickGraphicsDevice::fromDeviceObjects 创建 Qt 的 QQuickGraphicsDevice。

render window 的私有类是继承自 QQuickWindowPrivate，只需要将获取到的 QQuickGraphicsDevice 设置给 QQuickWindowPrivate::setGraphicsDevice 即可。

之后创建一个离屏渲染表面，用于 RHI 的初始化。

Qt Viewport

在 Qt 中，想要查看或者渲染一个组件，需要使用 Viewport 组件，俗称照相机。

视口（Viewport）是一个可观察的多边形区域，只有 Viewport 范围内的画面才能显示到屏幕上。

wlroots 中的 Viewport 是一个与 Wayland 显示协议相关的概念，主要用于定义渲染输出在屏幕上的显示区域。它允许在渲染时对显示内容进行缩放、裁剪或平移，以适应不同的分辨率和显示需求。

Treeland 使用 WOutputViewport 提供 Viewport 功能，使用 wlroots 的 wlr_output 中的屏幕信息，对画面进行矩阵变换，这里会涉及到屏幕的缩放、DPI 等参数。

Copy
QMatrix4x4 WOutputViewport::renderMatrix() const
{
QMatrix4x4 renderMatrix;

if (auto customTransform = viewportTransform()) {
customTransform->applyTo(&renderMatrix);
} else if (parentItem() && !ignoreViewport() && input() != this) {
auto d = QQuickItemPrivate::get(const_cast<WOutputViewport*>(this));
auto viewportMatrix = d->itemNode()->matrix().inverted();
if (auto inputItem = input()) {
QMatrix4x4 matrix = QQuickItemPrivate::get(parentItem())->itemToWindowTransform();
matrix *= QQuickItemPrivate::get(inputItem)->windowToItemTransform();
renderMatrix = viewportMatrix * matrix.inverted();
} else { // the input item is window's contentItem
auto pd = QQuickItemPrivate::get(parentItem());
QMatrix4x4 parentMatrix = pd->itemToWindowTransform().inverted();
renderMatrix = viewportMatrix * parentMatrix;
}
}

return renderMatrix;
}

WOutputViewport 提供了 Viewport 所需的所有参数，变换矩阵、源几何大小、目标几何大小等信息。

在 WOutputRenderWindow 的事件中，判断如果是渲染的事件，就执行渲染。

Copy
bool WOutputRenderWindow::event(QEvent *event)
{
Q_D(WOutputRenderWindow);

if (event->type() == doRenderEventType) {
QCoreApplication::removePostedEvents(this, doRenderEventType);
d_func()->doRender();
return true;
}

if (QW::RenderWindow::beforeDisposeEventFilter(this, event)) {
event->accept();
QW::RenderWindow::afterDisposeEventFilter(this, event);
return true;
}

bool isAccepted = QQuickWindow::event(event);
if (QW::RenderWindow::afterDisposeEventFilter(this, event))
return true;

return isAccepted;
}

在 doRender 中，遍历所有的 Output，执行 beginRender，然后执行 Output 的渲染。

Copy
void WOutputRenderWindowPrivate::doRender(const QList<OutputHelper *> &outputs,
bool forceRender, bool doCommit)
{
Q_ASSERT(rendererList.isEmpty());
Q_ASSERT(!inRendering);
inRendering = true;

W_Q(WOutputRenderWindow);
for (OutputLayer *layer : std::as_const(layers)) {
layer->beforeRender(q);
}

rc()->polishItems();

if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi()))
rc()->beginFrame();
rc()->sync();

QQuickAnimatorController_advance(animationController.get());
Q_EMIT q->beforeRendering();
runAndClearJobs(&beforeRenderingJobs);

auto needsCommit = doRenderOutputs(outputs, forceRender);

Q_EMIT q->afterRendering();
runAndClearJobs(&afterRenderingJobs);

if (QSGRendererInterface::isApiRhiBased(WRenderHelper::getGraphicsApi()))
rc()->endFrame();

if (doCommit) {
for (auto i : std::as_const(needsCommit)) {
bool ok = i.first->commit(i.second);

if (i.second->currentBuffer()) {
i.second->endRender();
}

i.first->resetState(ok);
}
}

resetGlState();

// On Intel&Nvidia multi-GPU environment, wlroots using Intel card do render for all
// outputs, and blit nvidia's output buffer in drm_connector_state_update_primary_fb,
// the 'blit' behavior will make EGL context to Nvidia renderer. So must done current
// OpenGL context here in order to ensure QtQuick always make EGL context to Intel
// renderer before next frame.
if (glContext)
glContext->doneCurrent();

inRendering = false;
Q_EMIT q->renderEnd();
}

Copy
qw_buffer *WBufferRenderer::beginRender(const QSize &pixelSize, qreal devicePixelRatio,
uint32_t format, RenderFlags flags)
{
Q_ASSERT(!state.buffer);
Q_ASSERT(m_output);

if (pixelSize.isEmpty())
return nullptr;

Q_EMIT beforeRendering();

m_damageRing.set_bounds(pixelSize.width(), pixelSize.height());

// configure swapchain
if (flags.testFlag(RenderFlag::DontConfigureSwapchain)) {
auto renderFormat = pickFormat(m_output->renderer(), format);
if (!renderFormat) {
qWarning("wlr_renderer doesn't support format 0x%s", drmGetFormatName(format));
return nullptr;
}

if (!m_swapchain || QSize(m_swapchain->handle()->width, m_swapchain->handle()->height) != pixelSize
|| m_swapchain->handle()->format.format != renderFormat->format) {
if (m_swapchain)
delete m_swapchain;
m_swapchain = qw_swapchain::create(m_output->allocator()->handle(), pixelSize.width(), pixelSize.height(), renderFormat);
}
} else if (flags.testFlag(RenderFlag::UseCursorFormats)) {
bool ok = m_output->configureCursorSwapchain(pixelSize, format, &m_swapchain);
if (!ok)
return nullptr;
} else {
bool ok = m_output->configurePrimarySwapchain(pixelSize, format, &m_swapchain,
!flags.testFlag(DontTestSwapchain));
if (!ok)
return nullptr;
}

// TODO: Support scanout buffer of wlr_surface(from WSurfaceItem)
int bufferAge;
auto wbuffer = m_swapchain->acquire(&bufferAge);
if (!wbuffer)
return nullptr;
auto buffer = qw_buffer::from(wbuffer);

if (!m_renderHelper)
m_renderHelper = new WRenderHelper(m_output->renderer());
m_renderHelper->setSize(pixelSize);

auto wd = QQuickWindowPrivate::get(window());
Q_ASSERT(wd->renderControl);
auto lastRT = m_renderHelper->lastRenderTarget();
auto rt = m_renderHelper->acquireRenderTarget(wd->renderControl, buffer);
if (rt.isNull()) {
buffer->unlock();
return nullptr;
}

auto rtd = QQuickRenderTargetPrivate::get(&rt);
QSGRenderTarget sgRT;

if (rtd->type == QQuickRenderTargetPrivate::Type::PaintDevice) {
sgRT.paintDevice = rtd->u.paintDevice;
} else {
Q_ASSERT(rtd->type == QQuickRenderTargetPrivate::Type::RhiRenderTarget);
sgRT.rt = rtd->u.rhiRt;
sgRT.cb = wd->redirect.commandBuffer;
Q_ASSERT(sgRT.cb);
sgRT.rpDesc = rtd->u.rhiRt->renderPassDescriptor();

#ifndef QT_NO_OPENGL
if (wd->rhi->backend() == QRhi::OpenGLES2) {
auto glRT = QRHI_RES(QGles2TextureRenderTarget, rtd->u.rhiRt);
Q_ASSERT(glRT->framebuffer >= 0);
auto glContext = QOpenGLContext::currentContext();
Q_ASSERT(glContext);
QOpenGLContextPrivate::get(glContext)->defaultFboRedirect = glRT->framebuffer;
}
#endif
}

state.flags = flags;
state.context = wd->context;
state.pixelSize = pixelSize;
state.devicePixelRatio = devicePixelRatio;
state.bufferAge = bufferAge;
state.lastRT = lastRT;
state.buffer = buffer;
state.renderTarget = rt;
state.sgRenderTarget = sgRT;

return buffer;
}

Copy
QVector<std::pair<OutputHelper*, WBufferRenderer*>>
WOutputRenderWindowPrivate::doRenderOutputs(const QList<OutputHelper*> &outputs, bool forceRender)
{
QVector<OutputHelper*> renderResults;
renderResults.reserve(outputs.size());
for (OutputHelper *helper : std::as_const(outputs)) {
if (Q_LIKELY(!forceRender)) {
if (!helper->renderable()
|| Q_UNLIKELY(!WOutputViewportPrivate::get(helper->output())->renderable())
|| !helper->output()->output()->isEnabled())
continue;

if (!helper->contentIsDirty()) {
if (helper->needsFrame())
renderResults.append(helper);
continue;
}
}

Q_ASSERT(helper->output()->output()->scale() <= helper->output()->devicePixelRatio());

const auto &format = helper->qwoutput()->handle()->render_format;
const auto renderMatrix = helper->output()->renderMatrix();

// maybe using the other WOutputViewport's QSGTextureProvider
if (!helper->output()->depends().isEmpty())
updateDirtyNodes();

qw_buffer *buffer = helper->beginRender(helper->bufferRenderer(), helper->output()->output()->size(), format,
WBufferRenderer::RedirectOpenGLContextDefaultFrameBufferObject);
Q_ASSERT(buffer == helper->bufferRenderer()->currentBuffer());
if (buffer) {
helper->render(helper->bufferRenderer(), 0, renderMatrix,
helper->output()->effectiveSourceRect(),
helper->output()->targetRect(),
helper->output()->preserveColorContents());
}
renderResults.append(helper);
}

QVector<std::pair<OutputHelper*, WBufferRenderer*>> needsCommit;
needsCommit.reserve(renderResults.size());
for (auto helper : std::as_const(renderResults)) {
auto bufferRenderer = helper->afterRender();
if (bufferRenderer)
needsCommit.append({helper, bufferRenderer});
}

rendererList.clear();

return needsCommit;
}

Copy
void WBufferRenderer::render(int sourceIndex, const QMatrix4x4 &renderMatrix,
const QRectF &sourceRect, const QRectF &targetRect,
bool preserveColorContents)
{
Q_ASSERT(state.buffer);

const auto &source = m_sourceList.at(sourceIndex);
QSGRenderer *renderer = ensureRenderer(sourceIndex, state.context);
auto wd = QQuickWindowPrivate::get(window());

const qreal devicePixelRatio = state.devicePixelRatio;
state.renderer = renderer;
state.worldTransform = renderMatrix;
renderer->setDevicePixelRatio(devicePixelRatio);
renderer->setDeviceRect(QRect(QPoint(0, 0), state.pixelSize));
renderer->setRenderTarget(state.sgRenderTarget);
const auto viewportRect = scaleToRect(targetRect, devicePixelRatio);

auto softwareRenderer = dynamic_cast<QSGSoftwareRenderer*>(renderer);
{ // before render
if (softwareRenderer) {
// because software renderer don't supports viewportRect,
// so use transform to simulation.
const auto mapTransform = inputMapToOutput(sourceRect, targetRect,
state.pixelSize, state.devicePixelRatio);
if (!mapTransform.isIdentity())
state.worldTransform = mapTransform * state.worldTransform;
state.worldTransform.optimize();
auto image = getImageFrom(state.renderTarget);
image->setDevicePixelRatio(devicePixelRatio);

// TODO: Should set to QSGSoftwareRenderer, but it's not support specify matrix.
// If transform is changed, it will full repaint.
if (isRootItem(source.source)) {
auto rootTransform = QQuickItemPrivate::get(wd->contentItem)->itemNode();
if (rootTransform->matrix() != state.worldTransform)
rootTransform->setMatrix(state.worldTransform);
} else {
auto t = state.worldTransform.toTransform();
if (t.type() > QTransform::TxTranslate) {
(image->operator QImage &()).fill(renderer->clearColor());
softwareRenderer->markDirty();
}

applyTransform(softwareRenderer, t);
}
} else {
state.worldTransform.optimize();

bool flipY = wd->rhi ? !wd->rhi->isYUpInNDC() : false;
if (state.renderTarget.mirrorVertically())
flipY = !flipY;

if (viewportRect.isValid()) {
QRect vr = viewportRect;
if (flipY)
vr.moveTop(-vr.y() + state.pixelSize.height() - vr.height());
renderer->setViewportRect(vr);
} else {
renderer->setViewportRect(QRect(QPoint(0, 0), state.pixelSize));
}

QRectF rect = sourceRect;
if (!rect.isValid())
rect = QRectF(QPointF(0, 0), QSizeF(state.pixelSize) / devicePixelRatio);

const float left = rect.x();
const float right = rect.x() + rect.width();
float bottom = rect.y() + rect.height();
float top = rect.y();

if (flipY)
std::swap(top, bottom);

QMatrix4x4 matrix;
matrix.ortho(left, right, bottom, top, 1, -1);

QMatrix4x4 projectionMatrix, projectionMatrixWithNativeNDC;
projectionMatrix = matrix * state.worldTransform;

if (wd->rhi && !wd->rhi->isYUpInNDC()) {
std::swap(top, bottom);

matrix.setToIdentity();
matrix.ortho(left, right, bottom, top, 1, -1);
}
projectionMatrixWithNativeNDC = matrix * state.worldTransform;

renderer->setProjectionMatrix(projectionMatrix);
renderer->setProjectionMatrixWithNativeNDC(projectionMatrixWithNativeNDC);

auto textureRT = static_cast<QRhiTextureRenderTarget*>(state.sgRenderTarget.rt);
if (preserveColorContents) {
textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents);
} else {
textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents);
}
}
}

state.context->renderNextFrame(renderer);

{ // after render
if (!softwareRenderer) {
// TODO: get damage area from QRhi renderer
m_damageRing.add_whole();
// ###: maybe Qt bug? Before executing QRhi::endOffscreenFrame, we may
// use the same QSGRenderer for multiple drawings. This can lead to
// rendering the same content for different QSGRhiRenderTarget instances
// when using the RhiGles backend. Additionally, considering that the
// result of the current drawing may be needed when drawing the next
// sourceIndex, we should let the RHI (Rendering Hardware Interface)
// complete the results of this drawing here to ensure the current
// drawing result is available for use.
wd->rhi->finish();
} else {
auto currentImage = getImageFrom(state.renderTarget);
Q_ASSERT(currentImage && currentImage == softwareRenderer->m_rt.paintDevice);
currentImage->setDevicePixelRatio(1.0);
const auto scaleTF = QTransform::fromScale(devicePixelRatio, devicePixelRatio);
const auto scaledFlushRegion = scaleTF.map(softwareRenderer->flushRegion());
PixmanRegion scaledFlushDamage;
bool ok = WTools::toPixmanRegion(scaledFlushRegion, scaledFlushDamage);
Q_ASSERT(ok);

{
PixmanRegion damage;
m_damageRing.get_buffer_damage(state.bufferAge, damage);

if (viewportRect.isValid()) {
QRect imageRect = (currentImage->operator const QImage &()).rect();
QRegion invalidRegion(imageRect);
invalidRegion -= viewportRect;
if (!scaledFlushRegion.isEmpty())
invalidRegion &= scaledFlushRegion;

if (!invalidRegion.isEmpty()) {
QPainter pa(currentImage);
for (const auto r : std::as_const(invalidRegion))
pa.fillRect(r, softwareRenderer->clearColor());
}
}

if (!damage.isEmpty() && state.lastRT.first != state.buffer && !state.lastRT.second.isNull()) {
auto image = getImageFrom(state.lastRT.second);
Q_ASSERT(image);
Q_ASSERT(image->size() == state.pixelSize);

// TODO: Don't use the previous render target, we can get the damage region of QtQuick
// before QQuickRenderControl::render for qw_damage_ring, and add dirty region to
// QSGAbstractSoftwareRenderer to force repaint the damage region of current render target.
QPainter pa(currentImage);

PixmanRegion remainderDamage;
ok = pixman_region32_subtract(remainderDamage, damage, scaledFlushDamage);
Q_ASSERT(ok);

int count = 0;
auto rects = pixman_region32_rectangles(remainderDamage, &count);
for (int i = 0; i < count; ++i) {
auto r = rects[i];
pa.drawImage(r.x1, r.y1, *image, r.x1, r.y1, r.x2 - r.x1, r.y2 - r.y1);
}
}
}

if (!isRootItem(source.source))
applyTransform(softwareRenderer, state.worldTransform.inverted().toTransform());
m_damageRing.add(scaledFlushDamage);
}
}

if (auto dr = qobject_cast<QSGDefaultRenderContext*>(state.context)) {
QRhiResourceUpdateBatch *resourceUpdates = wd->rhi->nextResourceUpdateBatch();
dr->currentFrameCommandBuffer()->resourceUpdate(resourceUpdates);
}

if (shouldCacheBuffer())
wTextureProvider()->setBuffer(state.buffer);
}

处理完画面以后，如果需要上屏画面，就调用 commit 把画面送到屏幕上。

Copy
bool OutputHelper::commit(WBufferRenderer *buffer)
{
if (output()->offscreen())
return true;

if (!buffer || !buffer->currentBuffer()) {
Q_ASSERT(!this->buffer());
return WOutputHelper::commit();
}

setBuffer(buffer->currentBuffer());

if (m_lastCommitBuffer == buffer) {
if (pixman_region32_not_empty(&buffer->damageRing()->handle()->current))
setDamage(&buffer->damageRing()->handle()->current);
}

m_lastCommitBuffer = buffer;

return WOutputHelper::commit();
}

还会判断是否有硬件加速（GPU），会优先使用硬件来加速计算过程。

Copy
} else {
state.worldTransform.optimize();

bool flipY = wd->rhi ? !wd->rhi->isYUpInNDC() : false;
if (state.renderTarget.mirrorVertically())
flipY = !flipY;

if (viewportRect.isValid()) {
QRect vr = viewportRect;
if (flipY)
vr.moveTop(-vr.y() + state.pixelSize.height() - vr.height());
renderer->setViewportRect(vr);
} else {
renderer->setViewportRect(QRect(QPoint(0, 0), state.pixelSize));
}

QRectF rect = sourceRect;
if (!rect.isValid())
rect = QRectF(QPointF(0, 0), QSizeF(state.pixelSize) / devicePixelRatio);

const float left = rect.x();
const float right = rect.x() + rect.width();
float bottom = rect.y() + rect.height();
float top = rect.y();

if (flipY)
std::swap(top, bottom);

QMatrix4x4 matrix;
matrix.ortho(left, right, bottom, top, 1, -1);

QMatrix4x4 projectionMatrix, projectionMatrixWithNativeNDC;
projectionMatrix = matrix * state.worldTransform;

if (wd->rhi && !wd->rhi->isYUpInNDC()) {
std::swap(top, bottom);

matrix.setToIdentity();
matrix.ortho(left, right, bottom, top, 1, -1);
}
projectionMatrixWithNativeNDC = matrix * state.worldTransform;

renderer->setProjectionMatrix(projectionMatrix);
renderer->setProjectionMatrixWithNativeNDC(projectionMatrixWithNativeNDC);

auto textureRT = static_cast<QRhiTextureRenderTarget*>(state.sgRenderTarget.rt);
if (preserveColorContents) {
textureRT->setFlags(textureRT->flags() | QRhiTextureRenderTarget::PreserveColorContents);
} else {
textureRT->setFlags(textureRT->flags() & ~QRhiTextureRenderTarget::PreserveColorContents);
}
}

Surface 渲染

在 Treeland 中，为 Surface 创建了 WSurfaceItem，用于表示一个窗口，并创建了 WSurfaceContent 作为 WSurfaceItem 的 delegate。

Copy
void WSurfaceItemPrivate::initForDelegate()
{
Q_Q(WSurfaceItem);

std::unique_ptr<QQuickItem> newContentContainer;

if (!delegate) {
if (getItemContent()) {
Q_ASSERT(!delegateIsDirty);
return;
}

delegateIsDirty = false;
auto contentItem = new WSurfaceItemContent(q);
if (surface)
contentItem->setSurface(surface);
contentItem->setCacheLastBuffer(!surfaceFlags.testFlag(WSurfaceItem::DontCacheLastBuffer));
contentItem->setSmooth(q->smooth());
contentItem->setLive(!q->flags().testFlag(WSurfaceItem::NonLive));
QObject::connect(q, &WSurfaceItem::smoothChanged, contentItem, &WSurfaceItemContent::setSmooth);
newContentContainer.reset(contentItem);
} else if (delegateIsDirty) {
auto obj = delegate->createWithInitialProperties({{"surface", QVariant::fromValue(q)}}, qmlContext(q));
if (!obj) {
qWarning() << "Failed on create surface item from delegate, error mssage:"
<< delegate->errorString();
return;
}

delegateIsDirty = false;
auto contentItem = qobject_cast<QQuickItem*>(obj);
if (!contentItem)
qFatal() << "SurfaceItem's delegate must is Item";

newContentContainer.reset(new QQuickItem(q));
QQmlEngine::setObjectOwnership(contentItem, QQmlEngine::CppOwnership);
contentItem->setParent(newContentContainer.get());
contentItem->setParentItem(newContentContainer.get());
}

if (!newContentContainer)
return;

newContentContainer->setZ(qreal(WSurfaceItem::ZOrder::ContentItem));

if (contentContainer) {
newContentContainer->setPosition(contentContainer->position());
newContentContainer->setSize(contentContainer->size());
newContentContainer->setTransformOrigin(contentContainer->transformOrigin());
newContentContainer->setScale(contentContainer->scale());

contentContainer->disconnect(q);
contentContainer->deleteLater();
}
contentContainer = newContentContainer.release();
updateEventItem(false);
updateBoundingRect();
if (eventItem)
updateEventItemGeometry();

Q_EMIT q->contentItemChanged();
}

之后当 WSurfaceItem 需要更新画面时，就能调用 updatePaintNode 更新渲染。

Copy
QSGNode *WSurfaceItemContent::updatePaintNode(QSGNode *oldNode, UpdatePaintNodeData *)
{
W_D(WSurfaceItemContent);

auto tp = wTextureProvider();
if (d->live || !tp->texture()) {
auto texture = d->surface ? d->surface->handle()->get_texture() : nullptr;
if (texture) {
tp->setTexture(qw_texture::from(texture), d->buffer.get());
} else {
tp->setBuffer(d->buffer.get());
}
}

if (!tp->texture() || width() <= 0 || height() <= 0) {
delete oldNode;
return nullptr;
}

auto node = static_cast<QSGImageNode*>(oldNode);
if (Q_UNLIKELY(!node)) {
node = window()->createImageNode();
node->setOwnsTexture(false);
QSGNode *fpnode = new WSGRenderFootprintNode(this);
node->appendChildNode(fpnode);
}

auto texture = tp->texture();
node->setTexture(texture);
const QRectF textureGeometry = d->bufferSourceBox;
node->setSourceRect(textureGeometry);
const QRectF targetGeometry(d->ignoreBufferOffset ? QPointF() : d->bufferOffset, size());
node->setRect(targetGeometry);
node->setFiltering(smooth() ? QSGTexture::Linear : QSGTexture::Nearest);

return node;
}

而使用 delegate 的目的是为了能让多个 WSurfaceItem 使用相同的窗口画面，例如某些场景需要临时创建一个窗口的分身，窗口切换列表、多任务视图等。

Copy
QSGTextureProvider *WSurfaceItemContent::textureProvider() const
{
if (QQuickItem::isTextureProvider())
return QQuickItem::textureProvider();

return wTextureProvider();
}

WSGTextureProvider *WSurfaceItemContent::wTextureProvider() const
{
W_DC(WSurfaceItemContent);

auto w = qobject_cast<WOutputRenderWindow*>(d->window);
if (!w || !d->sceneGraphRenderContext() || QThread::currentThread() != d->sceneGraphRenderContext()->thread()) {
qWarning("WQuickCursor::textureProvider: can only be queried on the rendering thread of an WOutputRenderWindow");
return nullptr;
}

if (!d->textureProvider) {
d->textureProvider = new WSGTextureProvider(w);
if (d->surface) {
if (auto texture = d->surface->handle()->get_texture()) {
d->textureProvider->setTexture(qw_texture::from(texture), d->buffer.get());
} else {
d->textureProvider->setBuffer(d->buffer.get());
}
}
}
return d->textureProvider;
}

Treeland 使用 WQuickTextureProxy 创建窗口的代理显示，而其中就是获取 WSurfaceItem 的 textureProvider。

Copy
QSGTextureProvider *WQuickTextureProxy::textureProvider() const
{
if (QQuickItem::isTextureProvider())
return QQuickItem::textureProvider();

W_DC(WQuickTextureProxy);
if (!d->sourceItem)
return nullptr;

return d->sourceItem->textureProvider();
}

这样多个 proxy 就可以显示同一个窗口的内容，比 QML 的 ShaderEffectSource 效率更高。

结尾

上述仅仅是 Treeland 实现 Qt 和 wlroots 缝合的一部分流程，实际上对事件的处理就十分复杂，不止键盘输入，还需要处理光标、触控、触摸等其他设备。还有光标的绘制也需要区分硬光标和软光标，渲染画面时的硬件加速及软件实现等。

后续准备写一下光标相关的处理，以及还没介绍 Treeland 的画面是怎么绘制的。

相关文档

💡
Qt QPA https://doc.qt.io/qt-6/qpa.html
QOffscreenSurface https://doc.qt.io/qt-6/qoffscreensurface.html