searchusermenu
  • 发布文章
  • 消息中心
点赞
收藏
评论
分享
原创

Linux图像合成与送显

2023-06-28 01:53:20
184
0

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窗口管理器

0条评论
0 / 1000
李****海
14文章数
0粉丝数
李****海
14 文章 | 0 粉丝