0 前言
Linux桌面通常包含多个窗口(含系统窗口和应用窗口,应用只管渲染各自的窗口,系统负责将它们和进行合成并送显。
1 合成
图像合成工作可由窗口管理器完成,也可由显示服务器完成。
1.1 窗管合成
窗口管理器主要有平铺式窗口管理器、堆叠式窗口管理器、动态窗口管理器以及复合窗口管理器四大类型,其中复合窗口管理器支持图像合成功能:应用程序窗口先画到离屏缓冲区,然后由复合器合成显示到屏幕上[2]:
图1.1 窗口合成[2]
常用复合窗口管理器主要有Mutter和KWin,其中后者可关闭合成功能,相关命令如下[2]:
# UOS/Kubuntu
# 查看合成状态以及类型
qdbus org.kde.KWin /Compositor org.kde.kwin.Compositing.active # 合成状态[2]
qdbus org.kde.KWin /Compositor org.kde.kwin.Compositing.compositingType # 合成类型
# 关闭/开启 合成
qdbus org.kde.KWin /Compositor org.kde.kwin.Compositing.suspend # 关闭合成[2]
qdbus org.kde.KWin /Compositor org.kde.kwin.Compositing.resume # 开启合成[2]
1.2 显服合成
对于窗口管理器不支持合成(或关闭合成)的情况,需要显示服务器来完成图像合成工作,xorg-server代码流程如下:
(1)opengl copy/swap buffer最终调用XShmPutImage()
glXCopySubBufferMESA(Display * dpy, GLXDrawable drawable, int x, int y, int width, int height)@mesa-21.2.6/src/glx/glxcmds.c
|-->...
|
|<--|
|
| glXSwapBuffers(Display * dpy, GLXDrawable drawable)@src/glx/glxcmds.c
| |-->...
|<-----|
|
|-->XShmPutImage(Display *dpy, Drawable d, GC gc, XImage *image, int src_x, int src_y, int dst_x, int dst_y, unsigned int src_width, unsigned int src_height, Bool send_event)@libXext-1.3.5/src/XShm.c
|-->XExtDisplayInfo *info = find_display (dpy)
|-->XShmSegmentInfo *shminfo = (XShmSegmentInfo *)image->obdata
|-->xShmPutImageReq *req // xShmPutImageReq @ xorgproto-2022.2/include/X11/extensions/shmproto.h
|-->ShmCheckExtension (dpy, info, 0)
|-->LockDisplay(dpy)
|-->FlushGC(dpy, gc)
|-->GetReq(ShmPutImage, req)
|-->req->reqType = info->codes->major_opcode
|-->req->shmReqType = X_ShmPutImage
|-->req->drawable = d
|-->req->gc = gc->gid
|-->req->srcX = src_x
|-->req->srcY = src_y
|-->req->srcWidth = src_width
|-->req->srcHeight = src_height
|-->req->dstX = dst_x
|-->req->dstY = dst_y
|-->req->totalWidth = image->width
|-->req->totalHeight = image->height
|-->req->depth = image->depth
|-->req->format = image->format
|-->req->sendEvent = send_event
|-->req->shmseg = shminfo->shmseg
|-->req->offset = image->data - shminfo->shmaddr
|-->UnlockDisplay(dpy)
|-->SyncHandle()
(2)XShmPutImage()在xorg-server中更新damage并发出通知:
dix_main()@dix/main.c
|-->...
|-->ProcShmPutImage(ClientPtr client)@xorg-server-1.20.13/Xext/shm.c
|-->if ((((stuff->format == ZPixmap) && (stuff->srcX == 0)) || ((stuff->format != ZPixmap) && (stuff->srcX < screenInfo.bitmapScanlinePad) && ((stuff->format == XYBitmap) || ((stuff->srcY == 0) && (stuff->srcHeight == stuff->totalHeight))))) && ((stuff->srcX + stuff->srcWidth) == stuff->totalWidth))
| |-->(*pGC->ops->PutImage) (pDraw, pGC, stuff->depth, stuff->dstX, stuff->dstY, stuff->totalWidth, stuff->srcHeight, stuff->srcX, stuff->format, shmdesc->addr + stuff->offset + (stuff->srcY * length)/* Q:加上最后一个,地址不是超出了图像范围吗 */)
| |-->damagePutImage(DrawablePtr pDrawable, GCPtr pGC, int depth, int x, int y, int w, int h, int leftPad, int format, char *pImage)@xorg-server-1.20.13/miext/damage/damage.c
| |-->damageRegionProcessPending(pDrawable)
| |-->for (; pDamage != NULL; pDamage = pDamage->pNext)
| |-->if (pDamage->reportAfter)
| |-->if (pDamage->damageReport)
| |-->DamageReportDamage(pDamage, &pDamage->pendingDamage)
| |-->DamageReportDamage(DamagePtr pDamage, RegionPtr pDamageRegion)@xorg-server-1.20.13/miext/damage/damage.c
| |-->switch (pDamage->damageLevel)
| |-->case DamageReportNonEmpty
| |-->was_empty = !RegionNotEmpty(&pDamage->damage)
| |-->RegionUnion(&pDamage->damage, &pDamage->damage, pDamageRegion)
| |-->if (was_empty && RegionNotEmpty(&pDamage->damage))
| |-->(*pDamage->damageReport)(pDamage, &pDamage->damage, pDamage->closure)
| | // KWin打开合成,报给应用层
| |-->DamageExtReport()
| | |-->... // 同上,此处略去
| or // KWin关闭合成,Xorg内部消化掉!
| |-->compReportDamage(DamagePtr pDamage, RegionPtr pRegion, void *closure)@xorg-server-1.20.4/composite/compalloc.c
| |-->WindowPtr pWin = (WindowPtr) closure
| |-->ScreenPtr pScreen = pWin->drawable.pScreen
| |-->CompScreenPtr cs = GetCompScreen(pScreen)
| |-->CompWindowPtr cw = GetCompWindow(pWin)
| |-->if (!cs->pendingScreenUpdate)
| | |-->QueueWorkProc(compScreenUpdate, serverClient, pScreen) // compScreenUpdate()调用流程详见《X扩展 —— XComposite》
| | | |-->QueueWorkProc(Bool (*function) (ClientPtr pClient, void *closure), ClientPtr client, void *closure)@xorg-server-1.20.4/dix/dixutils.c
| | | |-->WorkQueuePtr q = malloc(sizeof *q)
| | | |-->q->function = function // 瞧这里!
| | | |-->q->client = client
| | | |-->q->closure = closure
| | | |-->q->next = NULL
| | | |-->*workQueueLast = q // 下面workQueue指向的就是该列表
| | | |-->workQueueLast = &q->next
| | | |-->return TRUE
| | |-->cs->pendingScreenUpdate = TRUE
| |-->cw->damaged = TRUE
| |-->compMarkAncestors(pWin)
|-->else
|-->doShmPutImage(DrawablePtr dst, GCPtr pGC, int depth, unsigned int format, int w, int h, int sx, int sy, int sw, int sh, int dx, int dy, char *data)@xorg-server-1.20.13/Xext/shm.c
|-->if (format == ZPixmap || (format == XYPixmap && depth == 1))
|-->pPixmap = GetScratchPixmapHeader(dst->pScreen, w, h, depth, BitsPerPixel(depth), PixmapBytePad(w, depth), data)
|-->pGC->ops->CopyArea((DrawablePtr) pPixmap, dst, pGC, sx, sy, sw, sh, dx, dy)
|-->damageCopyArea(DrawablePtr pSrc, DrawablePtr pDst, ...)@xorg-server-1.15.1/miext/damage/damage.c
|-->damageRegionProcessPending(pDst)
|-->... // 前面有,此处略去
(3)xorg-server处理上述damage并触发内核更新dirty区域
dix_main()@dix/main.c
|-->Dispatch()@xorg-server-1.20.13/dix/dispatch.c
|-->WaitForSomething(Bool are_ready)@xorg-server-1.20.4/os/WaitFor.c
|-->while(1)
| // 合成(处理damage)
|-->if (workQueue) // workQueue @ xorg-server-1.20.4/dix/dixutils.c, = workQueueLast
|-->ProcessWorkQueue()@xorg-server/1.20.4/dix/dixutils.c
|-->WorkQueuePtr q, *p = &workQueue
|-->while ((q = *p))
| |-->if ((*q->function) (q->client, q->closure))
| | | |-->compScreenUpdate(ClientPtr pClient, void *closure)@xorg-server/1.20.4/composite/compalloc.c
| | | |-->compPaintChildrenToWindow(pScreen->root) // pScreen->root类型为WindowPtr
| | | | |-->compPaintChildrenToWindow(WindowPtr pWin)@xorg-server-1.20.4/composite/compwindow.c
| | | | |-->for (pChild = pWin->lastChild; pChild; pChild = pChild->prevSib)
| | | | | |-->compPaintWindowToParent(pChild)
| | | | | |-->compPaintWindowToParent(WindowPtr pWin)@
| | | | | |-->compPaintChildrenToWindow(pWin)
| | | | | |-->if (pWin->redirectDraw != RedirectDrawNone)
| | | | | |-->CompWindowPtr cw = GetCompWindow(pWin)
| | | | | |-->if (cw->damaged)
| | | | | |-->compWindowUpdateAutomatic(pWin)
| | | | | | |-->compWindowUpdateAutomatic(WindowPtr pWin)@xorg-server/1.20.4/composite/compwindow.c
| | | | | | |-->CompositePicture(PictOpSrc, pSrcPicture, 0, pDstPicture, 0, 0, 0, 0, pSrcPixmap->screen_x - pParent->drawable.x, pSrcPixmap->screen_y - pParent->drawable.y, pSrcPixmap->drawable.width, pSrcPixmap->drawable.height)
| | | | | | | |-->CompositePicture(CARD8 op, PicturePtr pSrc, PicturePtr pMask, PicturePtr pDst, INT16 xSrc, INT16 ySrc, INT16 xMask, INT16 yMask, INT16 xDst, INT16 yDst, CARD16 width, CARD16 height)
| | | | | | | |-->(*ps->Composite) (op, pSrc, pMask, pDst, xSrc, ySrc, xMask, yMask, xDst, yDst, width, height)
| | | | | | | |-->damageComposite(CARD8 op, PicturePtr pSrc, PicturePtr pMask, PicturePtr pDst, INT16 xSrc, INT16 ySrc, INT16 xMask, INT16 yMask, INT16 xDst, INT16 yDst, CARD16 width, CARD16 height)
| | | | | | | |-->ScreenPtr pScreen = pDst->pDrawable->pScreen
| | | | | | | |-->PictureScreenPtr ps = GetPictureScreen(pScreen)
| | | | | | | |-->damageScrPriv(pScreen)
| | | | | | | | |-->DamageScrPrivPtr pScrPriv = damageGetScrPriv(pScreen)
| | | | | | | |-->if (checkPictureDamage(pDst))
| | | | | | | | |-->BoxRec box
| | | | | | | | |-->box.x1 = xDst + pDst->pDrawable->x
| | | | | | | | |-->box.y1 = yDst + pDst->pDrawable->y
| | | | | | | | |-->box.x2 = box.x1 + width
| | | | | | | | |-->box.y2 = box.y1 + height
| | | | | | | | |-->TRIM_PICTURE_BOX(box, pDst)
| | | | | | | | |-->if (BOX_NOT_EMPTY(box))
| | | | | | | | |-->damageDamageBox(pDst->pDrawable, &box, pDst->subWindowMode)
| | | | | | | | |-->... // 前面有,此处略去...
| | | | | | | |-->if (pSrc->pDrawable && WindowDrawable(pSrc->pDrawable->type))
| | | | | | | | |-->miCompositeSourceValidate(pSrc)
| | | | | | | |-->if (pMask && pMask->pDrawable && WindowDrawable(pMask->pDrawable->type))
| | | | | | | | |-->miCompositeSourceValidate(pMask);
| | | | | | | |-->unwrap(pScrPriv, ps, Composite)
| | | | | | | | |-->ps->Composite = pScrPriv->Composite
| | | | | | | |-->(*ps->Composite) (op, pSrc, pMask, pDst, xSrc, ySrc, xMask, yMask, xDst, yDst, width, height)
| | | | | | | |-->damageRegionProcessPending(pDst->pDrawable)
| | | | | | | | |-->... // 前面有,此处略去...
| | | | | | | |-->wrap(pScrPriv, ps, Composite, damageComposite)
| | | | | | | |-->pScrPriv->Composite = ps->Composite
| | | | | | | |-->ps->Composite = damageComposite
| | | | | | |-->FreePicture(pSrcPicture, 0)
| | | | | | |-->FreePicture(pDstPicture, 0)
| | | | | | |-->DamageEmpty(cw->damage)
| | | | | |-->cw->damaged = FALSE
| | | | |-->pWin->damagedDescendants = FALSE
| | | |-->cs->pendingScreenUpdate = FALSE
| | |-->*p = q->next
| | |-->free(q)
| |-->else
| |-->p = &q->next /* don't fetch until after func called */
|-->workQueueLast = p
2 送显
触发内核更新dirty区域即可达到送显的目的:
dix_main()@dix/main.c
|-->Dispatch()@xorg-server-1.20.13/dix/dispatch.c
|-->WaitForSomething(Bool are_ready)@xorg-server-1.20.4/os/WaitFor.c
|-->while(1)
| // 送显(内核更新dirty)
|-->BlockHandler(void *pTimeout)@xorg-server-1.20.13/dix/dixutils.c
|-->++inHandler
|-->for (int i = 0; i < screenInfo.numScreens; i++)
|-->(*screenInfo.screens[i]->BlockHandler)(screenInfo.screens[i], pTimeout)
|-->msBlockHandler(ScreenPtr pScreen, void *timeout)
|-->if (pScreen->isGPU && !ms->drmmode.reverse_prime_offload_mode)
| |-->dispatch_slave_dirty(pScreen)
|-->else if (ms->dirty_enabled)
|-->dispatch_dirty(pScreen)
|-->ScrnInfoPtr scrn = xf86ScreenToScrn(pScreen)
|-->modesettingPtr ms = modesettingPTR(scrn)
|-->PixmapPtr pixmap = pScreen->GetScreenPixmap(pScreen)
|-->int fb_id = ms->drmmode.fb_id
|-->dispatch_dirty_region(scrn, pixmap, ms->damage, fb_id)
|-->dispatch_dirty_region(ScrnInfoPtr scrn, PixmapPtr pixmap, DamagePtr damage, int fb_id)@xorg-server-1.20.13/hw/xfree86/drivers/modesetting/driver.c
|-->modesettingPtr ms = modesettingPTR(scrn)
|-->RegionPtr dirty = DamageRegion(damage)
|-->unsigned num_cliprects = REGION_NUM_RECTS(dirty)
|-->if (num_cliprects)
|-->drmModeClip *clip = xallocarray(num_cliprects, sizeof(drmModeClip))
|-->BoxPtr rect = REGION_RECTS(dirty)
|-->for (int i = 0; i < num_cliprects; i++, rect++)
| |-->clip[i].x1 = rect->x1
| |-->clip[i].y1 = rect->y1
| |-->clip[i].x2 = rect->x2
| |-->clip[i].y2 = rect->y2
|-->drmModeDirtyFB(ms->fd, fb_id, clip, num_cliprects) // 触发内核更新dirty区域
|-->free(clip)
|-->DamageEmpty(damage)
3 帧率
采用窗管合成时,合成和送显的帧率通常和屏幕刷新率一致;采用显示服务器合成时,以Xorg为例,合成和送显的帧率将会变得不可控,主要由各应用的渲染帧率决定。
参考资料
[1]Linux桌面环境
[2]Linux窗口管理器
[3]KWin窗口管理器