本文尝试从源码调用链的角度,梳理一次 KWin 完整的渲染流程。
需要提前说明的是,文章并不会对出现的代码做逐行语义解释,也不会深入讨论具体的渲染算法、OpenGL 状态细节或特效本身的视觉实现。这里关注的核心问题只有一个:一次画面是如何在 KWin 内部被触发、调度并最终提交的。
在阅读 KWin 源码时,很容易被大量接口类、虚函数和跨子系统调用打断思路,例如 RenderLoop、Compositor、Scene、EffectsHandler、ItemRenderer 等模块彼此交织。如果不先把调用顺序和职责边界理清楚,单独看某个函数往往很难判断它处在渲染管线的哪个阶段。
因此,下面的分析会刻意保持在调用关系和阶段划分这一层级:
渲染请求是由谁、在什么时机发起的
合成器在一次 composite 中拆分了哪些阶段
场景、特效和渲染后端分别介入在哪一步
特效链是如何通过迭代器串联执行的
代码片段的选取仅用于定位关键路径,其它分支和边缘逻辑会被省略或忽略。
flowchart LR
RenderLoop["RenderLoop<br/>帧调度 / 时序控制"]
Compositor["Compositor<br/>合成器"]
Scene["Scene<br/>渲染调度中枢"]
Effects["Effects System<br/>特效链"]
Renderer["ItemRenderer<br/>渲染后端"]
Backend["Backend / Output<br/>送显"]
RenderLoop -->|frameRequested| Compositor
Compositor --> Scene
Scene --> Effects
Effects --> Scene
Scene --> Renderer
Renderer --> Backend
合成器执行 KWin 的渲染是以物理屏幕为单位执行的,所以首先在添加屏幕时,会添加一个 RenderLayer 作为渲染层,当需要为该屏幕更新画面时,调用对应的 RenderLayer 对象就可以了。
void Compositor::addOutput (Output *output) { Q_ASSERT (kwinApp ()->operationMode () != Application::OperationModeX11); auto workspaceLayer = new RenderLayer (output->renderLoop ()); workspaceLayer->setDelegate (std::make_unique <SceneDelegate>(m_scene.get (), output)); workspaceLayer->setGeometry (output->rect ()); addSuperLayer (workspaceLayer); }
void Compositor::addSuperLayer (RenderLayer *layer) { m_superlayers.insert (layer->loop (), layer); connect (layer->loop (), &RenderLoop::frameRequested, this , &Compositor::handleFrameRequested); }
void Compositor::handleFrameRequested (RenderLoop *renderLoop) { composite (renderLoop); }
从上述代码可以看出,添加一个屏幕后,会为 RenderLayer 绑定信号槽,在槽函数中执行 composite。
现在让我们关注一下 frameRequested 是从哪里发出的。
RenderLoopPrivate::RenderLoopPrivate (RenderLoop *q) : q (q) { compositeTimer.setSingleShot (true ); QObject::connect (&compositeTimer, &QTimer::timeout, q, [this ]() { dispatch (); }); }
void RenderLoopPrivate::scheduleRepaint () { if (presentMode == SyncMode::Async || presentMode == SyncMode::AdaptiveAsync) { compositeTimer.start (0 ); } else { const std::chrono::nanoseconds waitInterval = nextRenderTimestamp - currentTime; compositeTimer.start (std::chrono::duration_cast <std::chrono::milliseconds>(waitInterval)); } }
void RenderLoopPrivate::dispatch () { pendingRepaint = true ; Q_EMIT q->frameRequested (q); pendingRepaint = false ; }
scheduleRepaint 表示该输出存在未呈现的视觉变化,其来源可能是 Wayland surface damage、动画、特效、光标或屏幕输出状态变化。
RenderLoop 并不关心变化来自哪里,而是根据 presentMode 决定何时通过 frameRequested 触发一次合成。
至此,我们已经知道了 KWin 是从何时产生一次渲染操作的,回到最开始的位置,开始执行一次 composite 时,里面又在做哪些事情。
void Compositor::composite (RenderLoop *renderLoop) { if (m_backend->checkGraphicsReset ()) { qCDebug (KWIN_CORE) << "Graphics reset occurred..." ; reinitialize (); return ; } Output *output = findOutput (renderLoop); OutputLayer *primaryLayer = m_backend->primaryLayer (output); fTraceDuration ("Paint (" , output->name (), ")" ); RenderLayer *superLayer = m_superlayers[renderLoop]; prePaintPass (superLayer); superLayer->setOutputLayer (primaryLayer); SurfaceItem *scanoutCandidate = superLayer->delegate ()->scanoutCandidate (); renderLoop->setFullscreenSurface (scanoutCandidate); output->setContentType (scanoutCandidate ? scanoutCandidate->contentType () : ContentType::None); renderLoop->beginFrame (); bool directScanout = false ; if (scanoutCandidate) { const auto sublayers = superLayer->sublayers (); const bool scanoutPossible = std::none_of (sublayers.begin (), sublayers.end (), [](RenderLayer *sublayer) { return sublayer->isVisible (); }); if (scanoutPossible && !output->directScanoutInhibited ()) { directScanout = primaryLayer->scanout (scanoutCandidate); } } if (!directScanout) { QRegion surfaceDamage = primaryLayer->repaints (); primaryLayer->resetRepaints (); preparePaintPass (superLayer, &surfaceDamage); if (auto beginInfo = primaryLayer->beginFrame ()) { auto &[renderTarget, repaint] = beginInfo.value (); renderTarget.setDevicePixelRatio (output->scale ()); const QRegion bufferDamage = surfaceDamage.united (repaint).intersected (superLayer->rect ()); primaryLayer->aboutToStartPainting (bufferDamage); if (waylandServer () && workspace ()->outputs ().size () > 1 ) { surfaceDamage = bufferDamage; } paintPass (superLayer, &renderTarget, bufferDamage); primaryLayer->endFrame (bufferDamage, surfaceDamage); } } postPaintPass (superLayer); renderLoop->endFrame (); if (workspace ()->getDebugPixmapState () & 0x01 ) { workspace ()->setDebugPixmaState (workspace ()->getDebugPixmapState () ^ 0x01 ); workspace ()->getDebugPixmapPtr ()->saveCompositePixmap (); } m_backend->present (output); if (waylandServer ()) { const std::chrono::milliseconds frameTime = std::chrono::duration_cast <std::chrono::milliseconds>(output->renderLoop ()->lastPresentationTimestamp ()); if (!Cursors::self ()->isCursorHidden ()) { Cursor *cursor = Cursors::self ()->currentCursor (); if (cursor->geometry ().intersects (output->geometry ())) { cursor->markAsRendered (frameTime); } } } }
KWin 将绘制画面的操作分为了四个阶段:prePaintPass、preparePaintPass、paintPass、PostPaintPass。
prePaintPass —— 状态预处理阶段
职责
通知各个 RenderLayerDelegate、Scene 和 Effect:一帧即将开始
更新与下一帧相关的逻辑状态
动画推进
特效内部状态更新
窗口属性变化(透明度、变换、可见性等)
阶段语义
为即将到来的这一帧准备状态,但不决定画哪里,也不画任何东西。
void Compositor::prePaintPass (RenderLayer *layer) { layer->delegate ()->prePaint (); const auto sublayers = layer->sublayers (); for (RenderLayer *sublayer : sublayers) { prePaintPass (sublayer); } }
preparePaintPass —— 重绘区域汇总阶段
职责
汇总各个 RenderLayer 及其 delegate 上报的 repaint 区域
将局部 repaint 区域统一映射到全局坐标系
生成本次 composite 的最终 repaint / buffer damage 区域
阶段语义
决定这一帧“哪些区域需要被重画”。
void Compositor::preparePaintPass (RenderLayer *layer, QRegion *repaint) { *repaint += layer->mapToGlobal (layer->repaints () + layer->delegate ()->repaints ()); layer->resetRepaints (); const auto sublayers = layer->sublayers (); for (RenderLayer *sublayer : sublayers) { if (sublayer->isVisible ()) { preparePaintPass (sublayer, repaint); } } }
paintPass —— 实际绘制阶段
职责
驱动 Scene → Effects → Renderer 的完整绘制链路
执行屏幕级与窗口级的绘制流程:
将绘制指令最终提交给具体渲染后端(如 OpenGL)
阶段语义
真正把这一帧画出来的阶段。
void Compositor::paintPass (RenderLayer *layer, RenderTarget *target, const QRegion ®ion) { layer->delegate ()->paint (target, region); const auto sublayers = layer->sublayers (); for (RenderLayer *sublayer : sublayers) { if (sublayer->isVisible ()) { paintPass (sublayer, target, region); } } }
postPaintPass —— 帧结束清理阶段
职责
通知各层和特效:本帧绘制已完成
清理本帧使用的一次性状态与临时数据
恢复到可安全开始下一帧的稳定状态
阶段语义
帧结束后的收尾与清理,为下一帧做准备。
void Compositor::postPaintPass (RenderLayer *layer) { layer->delegate ()->postPaint (); const auto sublayers = layer->sublayers (); for (RenderLayer *sublayer : sublayers) { postPaintPass (sublayer); } }
在四个函数中,都支持递归处理 sublayer,这些代码都可以先无视掉。
重点关注 paintPass 中调用的 layer->delegate()->paint(target, region);,在这里开始,KWin 才是真正开始着手处理渲染。
在 KWin 中,所有的子系统都是由接口类定义的,这使得 KWin 可以随意更换子系统的实现,例如合成器可以换成 XRander、OpenGL,送显有窗口化和 DRM。
paint 函数一个接口函数,layer、delegate、scene 都是渲染子系统定义的多个接口类,在 scene 中定义了交互方式,之后就由不同的渲染后端提供对应的实现。
渲染子系统里定义的接口类: RenderLayerDelegate ItemRenderer Scene Item
RenderLayerDelegate 是 RenderLayer 的代理,负责调用 scene 的接口 Scene 是场景调度,负责连通特效子系统和渲染子系统 ItemRenderer 是渲染后端,负责渲染一个 Item Item 是一个渲染对象,通常理解为窗口
void SceneDelegate::paint (RenderTarget *renderTarget, const QRegion ®ion) { m_scene->paint (renderTarget, region.translated (viewport ().topLeft ())); }
delegate 里最终调用的是 WorkspaceScene::paint,WorkspaceScene 就是接口类 Scene 的实现。
void WorkspaceScene::paint (RenderTarget *renderTarget, const QRegion ®ion) { m_renderer->beginFrame (renderTarget); ScreenPaintData data (m_renderer->renderTargetProjectionMatrix(), EffectScreenImpl::get(painted_screen)) ; effects->paintScreen (m_paintContext.mask, region, data); m_paintScreenCount = 0 ; Q_EMIT frameRendered () ; m_renderer->endFrame (); }
ScreenPaintData 里存储的是绘制相关的数据,里面其实只有屏幕和矩阵的信息。
class ScreenPaintData ::Private{ public : QMatrix4x4 projectionMatrix; EffectScreen *screen = nullptr ; };
在 WorkspaceScene::paint 中,在 beginFrame 和 endFrame之间,effects->paintScreen() 开始一幅画面的处理了。
从名字上也能看得出,这里是特效子系统绘制屏幕的函数,和渲染子系统一样,特效子系统也是有好几个接口类规定了交互方式。
本次我们只关注流程,函数是如何调用的,所以就对不重要的地方先略过了。
beginFrame 和 endFrame 调用的都是 m_renderer,根据上述的介绍,渲染器就要看此时配置使用的哪个,目前应该以 OpenGL 渲染方式为主。
void ItemRendererOpenGL::beginFrame (RenderTarget *renderTarget) { GLFramebuffer *fbo = std::get <GLFramebuffer *>(renderTarget->nativeHandle ()); GLFramebuffer::pushFramebuffer (fbo); GLVertexBuffer::streamingBuffer ()->beginFrame (); }
void ItemRendererOpenGL::endFrame () { GLVertexBuffer::streamingBuffer ()->endOfFrame (); GLFramebuffer::popFramebuffer (); }
这两个函数的代码量不多,就是做些绘制前和绘制后的工作,让我们重点开始看特效的调用,特效子系统的实现是在 EffectsHandlerImpl 类中,在该类中,有这些函数是我们需要关注的。
class KWIN_EXPORT EffectsHandlerImpl : public EffectsHandlerEx{ Q_OBJECT public : void prePaintScreen (ScreenPrePaintData &data, std::chrono::milliseconds presentTime) override ; void paintScreen (int mask, const QRegion ®ion, ScreenPaintData &data) override ; void postPaintScreen () override ; void prePaintWindow (EffectWindow *w, WindowPrePaintData &data, std::chrono::milliseconds presentTime) override ; void paintWindow (EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override ; void postPaintWindow (EffectWindow *w) override ; void drawWindow (EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override ; void renderWindow (EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) override ; };
从函数的名字上可以看出,KWin 将很多内容都拆分为三个阶段:pre、paint和post,然后将具体的工作放在 paint 中,但是当我们真的打开 paintScreen 时,我们就会傻眼了,这都写的啥、啥、啥(自动播放王宝强语音)。
void EffectsHandlerImpl::paintScreen (int mask, const QRegion ®ion, ScreenPaintData &data) { if (m_currentPaintScreenIterator != m_activeEffects.constEnd ()) { (*m_currentPaintScreenIterator++)->paintScreen (mask, region, data); --m_currentPaintScreenIterator; } else { m_scene->finalPaintScreen (mask, region, data); } }
从现在开始,会开始烧脑,特别是在编辑器里点一下函数发现是虚函数,然后又要跳回去重新找实现,然后发现又回到了原地时。
根据代码可以看出,这里是在检查当前的迭代器是不是激活特效的最后一个,如果不是,就调用下一个,然后再减回来。如果已经是最后一个,则会调用 finalPaintScreen。
至少我们能先看懂这个最终绘制屏幕函数,让我们先看一下这个迭代器里有什么吧。
void EffectsHandlerImpl::startPaint () { m_activeEffects.clear (); m_activeEffects.reserve (loaded_effects.count ()); for (QVector<KWin::EffectPair>::const_iterator it = loaded_effects.constBegin (); it != loaded_effects.constEnd (); ++it) { if (it->second->isActive ()) { m_activeEffects << it->second; } } m_currentDrawWindowIterator = m_activeEffects.constBegin (); m_currentPaintWindowIterator = m_activeEffects.constBegin (); m_currentPaintScreenIterator = m_activeEffects.constBegin (); }
看起来这里就是所有激活的特效插件,那么 paintScreen 里应该就是调用一个特效插件的 paintScreen。来看一下代码。
void Effect::paintScreen (int mask, const QRegion ®ion, ScreenPaintData &data) { effects->paintScreen (mask, region, data); }
这个 effects 是 EffectsHandler,而 EffectsHandlerImpl 的基类就是 EffectsHandler…
噫,这什么鬼代码,怎么自己又调回来自己了。
这时候就要回想一些很重要的事情了,KWin 是 C++ 开发的,C++ 是面向对象的语言,而面向对象编程有三个特点:封装 继承 多态。既然这里代码这么诡异,那就应该看一下 paintScreen 到底是个啥了!
其实在上面我已经贴出来了重要信息,不知道各位有没有发现,paintScreen 竟然有一个 override 的小尾巴,那 Effect::paintScreen 也是虚函数吗?
class KWINEFFECTS_EXPORT Effect : public QObject{ Q_OBJECT public : virtual void paintScreen (int mask, const QRegion ®ion, ScreenPaintData &data) ; };
果然,这个函数也是虚的!
这也符合最开始的想法,迭代器里执行的函数确实是特效插件的函数,让我们打开一个特效插件,看一下里面的内容。
void MultitaskViewEffect::paintScreen (int mask, const QRegion ®ion, ScreenPaintData &data) { effects->paintScreen (mask, region, data); }
果然,在每个特效插件里,都主动调用了一次 effects->paintScreen,从而回到了最开始看到的奇怪迭代器调用。
现在让我们梳理一遍流程:
delegate 调用 scene 的 paintScreen
scene 的派生类 WorkspaceScene 调用了特效的 paintScreen
EffectsHandlerImpl 判断当前迭代器是不是末尾
如果不是末尾,则在自增前调用特效的 paintScreen
特效在 paintScreen 函数里调用 EffectsHandlerImpl 的 paintScreen
重复 4 和 5,直到特效执行到最后一个,开始逐层返回
迭代器自减,直到所有特效执行完毕
执行 finalPaintScreen
此处省略在分析代码时的心酸
既然已经执行到了 finalPaintScreen,那就看一下 finalPaintScreen 的实现吧。
void WorkspaceScene::finalPaintScreen (int mask, const QRegion ®ion, ScreenPaintData &data) { m_paintScreenCount++; if (mask & (PAINT_SCREEN_TRANSFORMED | PAINT_SCREEN_WITH_TRANSFORMED_WINDOWS)) { paintGenericScreen (mask, data); } else { paintSimpleScreen (mask, region); } }
无论是 paintGenericScreen 还是 paintSimpleScreen,基本结构都是类似的,遍历窗口列表,为每个窗口执行 paint。
void WorkspaceScene::paintGenericScreen (int , const ScreenPaintData &) { if (m_paintContext.mask & PAINT_SCREEN_BACKGROUND_FIRST) { if (m_paintScreenCount == 1 ) { m_renderer->renderBackground (infiniteRegion ()); } } else { m_renderer->renderBackground (infiniteRegion ()); } for (const Phase2Data &paintData : std::as_const (m_paintContext.phase2Data)) { paintWindow (paintData.item, paintData.mask, paintData.region); } } void WorkspaceScene::paintSimpleScreen (int , const QRegion ®ion) { QRegion visible = region; for (int i = m_paintContext.phase2Data.size () - 1 ; i >= 0 ; --i) { Phase2Data *data = &m_paintContext.phase2Data[i]; data->region = visible; if (!(data->mask & PAINT_WINDOW_TRANSFORMED)) { data->region &= data->item->mapToGlobal (data->item->boundingRect ()).toAlignedRect (); if (!(data->mask & PAINT_WINDOW_TRANSLUCENT)) { visible -= data->opaque; } } } if (!visible.isEmpty ()) { m_renderer->renderBackground (visible); } for (const Phase2Data &paintData : std::as_const (m_paintContext.phase2Data)) { paintWindow (paintData.item, paintData.mask, paintData.region); } if (m_dndIcon) { const QRegion repaint = region & m_dndIcon->mapToGlobal (m_dndIcon->boundingRect ()).toRect (); if (!repaint.isEmpty ()) { m_renderer->renderItem (m_dndIcon.get (), 0 , repaint, WindowPaintData (m_renderer->renderTargetProjectionMatrix ())); } } }
有一个需要注意的是,在 paintSimpleScreen 中存在一个遮挡剔除的功能。
paintWindow 就比较简答了,和 screen 一个套路。
void WorkspaceScene::paintWindow (WindowItem *item, int mask, const QRegion ®ion) { if (region.isEmpty ()) { return ; } WindowPaintData data (m_renderer->renderTargetProjectionMatrix()) ; effects->paintWindow (item->window ()->effectWindow (), mask, region, data); }
void EffectsHandlerImpl::paintWindow (EffectWindow *w, int mask, const QRegion ®ion, WindowPaintData &data) { if (m_currentPaintWindowIterator != m_activeEffects.constEnd ()) { (*m_currentPaintWindowIterator++)->paintWindow (w, mask, region, data); --m_currentPaintWindowIterator; } else { m_scene->finalPaintWindow (static_cast <EffectWindowImpl *>(w), mask, region, data); } }
但是需要留意的是,在开发插件时,如果我们想要直接替换画面,那应该先调用 effects->paintWindow,将其他插件的画面都绘制上去,再绘制自己的。
举个例子,窗口模糊插件是先在窗口位置上绘制一块模糊背景,再调用 effectc->paintWindow 绘制窗口本体画面,而水印插件则是先绘制窗口本体画面,再绘制水印内容。
这是一个非常关键的设计约束:
如果不调用,下游特效和最终绘制将被完全截断
调用的位置,决定了绘制顺序
在 effects 的 paintWindow 的最后一步里,又调用回了 scene 的 finalPaintWindow,然后调用 drawWindow,再经过同样的一套流程,最终执行到了 scene 的 finalDrawWindow 函数。
void WorkspaceScene::finalDrawWindow (EffectWindowImpl *w, int mask, const QRegion ®ion, WindowPaintData &data) { m_renderer->renderItem (w->windowItem (), mask, region, data); }
在这个函数里,调用到了渲染的 renderItem 函数,看一眼 OpenGL 渲染后端的代码。
void ItemRendererOpenGL::renderItem (Item *item, int mask, const QRegion ®ion, const WindowPaintData &data) { if (region.isEmpty ()) { return ; } RenderContext renderContext{ .clip = region, .hardwareClipping = region != infiniteRegion () && ((mask & Scene::PAINT_WINDOW_TRANSFORMED) || (mask & Scene::PAINT_SCREEN_TRANSFORMED)), .renderTargetScale = data.renderTargetScale ().value_or (renderTargetScale ()), }; renderContext.transformStack.push (QMatrix4x4 ()); renderContext.opacityStack.push (data.opacity ()); item->setTransform (data.toMatrix (renderTargetScale ())); createRenderNode (item, &renderContext); int totalVertexCount = 0 ; for (const RenderNode &node : std::as_const (renderContext.renderNodes)) { totalVertexCount += node.geometry.count (); } if (totalVertexCount == 0 ) { return ; } const size_t size = totalVertexCount * sizeof (GLVertex2D); ShaderTraits shaderTraits = ShaderTrait::MapTexture; if (data.brightness () != 1.0 ) { shaderTraits |= ShaderTrait::Modulate; } if (data.saturation () != 1.0 ) { shaderTraits |= ShaderTrait::AdjustSaturation; } GLVertexBuffer *vbo = GLVertexBuffer::streamingBuffer (); vbo->reset (); vbo->setAttribLayout (GLVertexBuffer::GLVertex2DLayout, 2 , sizeof (GLVertex2D)); GLVertex2D *map = (GLVertex2D *)vbo->map (size); for (int i = 0 , v = 0 ; i < renderContext.renderNodes.count (); i++) { RenderNode &renderNode = renderContext.renderNodes[i]; if (renderNode.geometry.isEmpty () || !renderNode.texture) { continue ; } if (renderNode.opacity != 1.0 ) { shaderTraits |= ShaderTrait::Modulate; } if (renderNode.texture->target () == GL_TEXTURE_EXTERNAL_OES) { shaderTraits |= ShaderTrait::MapExternalTexture; } renderNode.firstVertex = v; renderNode.vertexCount = renderNode.geometry.count (); renderNode.geometry.postProcessTextureCoordinates (renderNode.texture->matrix (renderNode.coordinateType)); renderNode.geometry.copy (&map[v], renderNode.geometry.count ()); v += renderNode.geometry.count (); } vbo->unmap (); vbo->bindArrays (); GLShader *shader = data.shader; if (!shader) { shader = ShaderManager::instance ()->pushShader (shaderTraits); } shader->setUniform (GLShader::Saturation, data.saturation ()); if (renderContext.hardwareClipping) { glEnable (GL_SCISSOR_TEST); } glBlendFunc (GL_ONE, GL_ONE_MINUS_SRC_ALPHA); float opacity = -1.0 ; QRegion scissorRegion = infiniteRegion (); if (renderContext.hardwareClipping) { scissorRegion = mapToRenderTarget (region); } const QMatrix4x4 projectionMatrix = data.projectionMatrix (); for (int i = 0 ; i < renderContext.renderNodes.count (); i++) { const RenderNode &renderNode = renderContext.renderNodes[i]; if (renderNode.vertexCount == 0 ) { continue ; } setBlendEnabled (renderNode.hasAlpha || renderNode.opacity < 1.0 ); shader->setUniform (GLShader::ModelViewProjectionMatrix, projectionMatrix * renderNode.transformMatrix); if (opacity != renderNode.opacity) { shader->setUniform (GLShader::ModulationConstant, modulate (renderNode.opacity, data.brightness ())); opacity = renderNode.opacity; } renderNode.texture->setFilter (GL_LINEAR); renderNode.texture->setWrapMode (GL_CLAMP_TO_EDGE); renderNode.texture->bind (); vbo->draw (scissorRegion, GL_TRIANGLES, renderNode.firstVertex, renderNode.vertexCount, renderContext.hardwareClipping); } vbo->unbindArrays (); setBlendEnabled (false ); if (!data.shader) { ShaderManager::instance ()->popShader (); } if (renderContext.hardwareClipping) { glDisable (GL_SCISSOR_TEST); } }
代码略长,大概内容就是绘制窗口数据,设置一些参数。
renderItem() 是一个很好的分界点:
在此之前,所有数据仍然是 逻辑对象 + 渲染参数,从这里开始,才进入 顶点、纹理、Shader、Draw Call。
一个重要但容易忽略的事实是:
特效系统并不直接操作 OpenGL,也不关心具体的绘制 API
它能做的事情被严格限制在:
修改 WindowPaintData
改变 Item 的变换、透明度、裁剪区域
决定是否继续向下传递绘制
真正的几何拆解、VBO 填充、Shader 选择,全部发生在渲染后端内部。
到这里,其实已经可以给整个流程下一个阶段性结论了:
KWin 的渲染管线,本质上是一条由 RenderLoop 驱动、以 Scene 为中枢、通过 Effects 进行可插拔加工,最终落到具体渲染后端的分层流水线
梳理一遍过程:
从 finalPaintScreen 遍历所有窗口,执行 paintWindow
执行遮挡剔除,调用 prePaintWindow 设置窗口渲染参数
完成特效链的加工
调用渲染子系统的 renderItem 将窗口绘制到屏幕上
本次我们只进行代码的梳理,并未涉及底层的内容,窗口的画面是如何经过特效链加工的?怎么绘制到屏幕上的?WindowPaintData 又是什么?这些都没有涉及。
通过对代码调用链的梳理,可以看出 KWin 的渲染流程并不是一条线性的“绘制函数调用序列”,而是一套由时序驱动、阶段划分明确、子系统职责严格隔离的渲染管线。
从触发机制上看,渲染并非由窗口事件直接发起,而是由 RenderLoop 根据当前的呈现模式(VSync、Async、Adaptive)调度,在合适的时间点通过 frameRequested 信号进入合成器。这一设计将何时渲染与渲染什么 彻底解耦。
在合成器内部,一次 composite() 被拆分为 prePaint、preparePaint、paint、postPaint 四个阶段。这种拆分并非形式上的分层,而是明确区分了:
状态收集与预处理
重绘区域与可见性计算
实际绘制行为
帧结束后的清理与收尾
进一步向下,Scene 作为渲染流程的中枢,将“屏幕级”与“窗口级”绘制分离处理。屏幕级流程负责背景、遍历顺序以及全局遮挡策略,而窗口级流程则以 WindowItem 为最小单位,逐一执行特效链和最终绘制。这种分离使得复杂特效只需关注自身作用的层级,而不必感知整个屏幕状态。
特效子系统的实现是整个流程中最具辨识度的部分。KWin 并未采用传统的“前后回调”或“事件分发”模型,而是通过一个显式维护的迭代器递归调用链,将所有激活的特效串联成一条严格有序的执行路径。特效插件必须主动调用 effects->paintScreen / paintWindow 才能将控制权传递下去,这一设计在机制上强制了绘制顺序的可控性,也为特效之间的叠加提供了明确的语义边界。
当特效链执行完毕后,流程才真正进入渲染后端。无论是 OpenGL 还是其他实现,后端只接收已经完全加工好的渲染数据:几何、纹理、变换矩阵、混合参数。特效系统不直接参与任何 API 级别的绘制调用,这保证了渲染后端的可替换性,同时也限制了特效的能力边界。
综合来看,KWin 的渲染架构体现出几个鲜明特征:
调度与绘制解耦:RenderLoop 控制时序,Compositor 负责流程
阶段明确:每一帧都被拆分为可预测、可插拔的处理阶段
接口驱动:Scene、Effect、Renderer 均以接口定义交互方式
可组合的特效链:通过递归迭代器实现严格顺序的效果叠加
后端无感知特效:渲染后端只关心“画什么”,不关心“为什么这么画”
本文只完成了对整体流程和结构的梳理,并未深入到具体的数据结构和渲染实现细节。但在理解了这条主干之后,再回头分析某一个特效、某一次性能回退,或某个渲染异常,其定位成本都会显著降低。