名词概念

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 用于窗口系统集成,而 QPlatformThemeQStyleHint 则用于深层次的平台主题和集成。

基本工作流程

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

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

QQuickWindow 的私有类提供了自定义 QQuickGraphicsDevice 对象的接口,而 QQuickGraphicsDevice 可以使用 fromOpenGLContextfromPhyicalDevice 创建新的对象,那么 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 处理底层事件与上层事件的流程

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 的事件过滤器,确保合成器可以拦截掉一部分事件,例如将一部分按键拦截下来,不发送给客户端。

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 对象。

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 中,发送给激活的客户端。

// 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

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 的代码

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 的类型,需要适配 vulkangles等。

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

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

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

Qt Viewport

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

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

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

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

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 的事件中,判断如果是渲染的事件,就执行渲染。

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 的渲染。

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();
}
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;
}
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;
}
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 把画面送到屏幕上。

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),会优先使用硬件来加速计算过程。

} 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。

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 更新渲染。

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 使用相同的窗口画面,例如某些场景需要临时创建一个窗口的分身,窗口切换列表、多任务视图等。

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。

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 的画面是怎么绘制的。

相关文档