名词概念#
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 处理底层事件与上层事件的流程
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 的类型,需要适配 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 等参数。
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 的画面是怎么绘制的。