天天看點

display:weston渲染流程:buffer+attach+damage+frame

渲染流水線

一個Wayland client要将記憶體渲染到螢幕上,首先要申請一個graphic buffer,繪制完後傳給Wayland compositor并通知其重繪。Wayland compositor收集所有Wayland client的請求,然後以一定周期把所有送出的graphic buffer進行合成。合成完後進行輸出。本質上,client需要将視窗内容繪制到一個和compositor共享的buffer上。這個buffer可以是普通共享記憶體,也可以是DRM中的GBM或是gralloc提供的可供硬體(如GPU)操作的graphic buffer,當然你也可以直接調用ion的接口去建立buffer。在大多數移動平台上,沒有專門的顯存,是以它們最終都來自系統記憶體,差別在于圖形加速硬體一般會要求實體連續且符合對齊要求的記憶體。如果是普通共享記憶體,一般是實體不連續的,多數情況用軟體渲染。有些圖形驅動也支援用實體不連續記憶體做硬體加速,但效率相對會低一些。根據buffer類型的不同,client可以選擇自己繪制,或是通過Cairo,OpenGL繪制,或是更高層的如Qt,GTK+這些widget庫等繪制。繪制完後client将buffer的handle傳給server,以及需要重繪的區域。在server端,compositor将該buffer轉為紋理(如果是共享記憶體使用glTexImage2D上傳紋理[我在gl_render_attach_shm中沒有找到這個函數,隻看到了ensure_textures,fd的傳輸是在client建立buffer的時候的surface_attach,對應的内容應該就在wl_shm_pool之中;因為雖然client到server直接給fd,ok,那麼server到gpu的copy是哪裡實作的呢?猜測可能是get_surface_state->gl_renderer_create_surface->gl_renderer_flush_damage;或者weston_output_repaint->output_accumulate_damage->surface_flush_damage],

硬體加速buffer用GL_OES_EGL_image_external擴充生成外部紋理)。最後将其與其它的視窗内容進行合成。下面是抽象的流程圖。

display:weston渲染流程:buffer+attach+damage+frame
display:weston渲染流程:buffer+attach+damage+frame

 使用drm-backend,client繪畫如果用opengl+opengles,直接與gpu的driver互動;compositor下面的gl-renderer與pixman-renderer一般是二選一。

display:weston渲染流程:buffer+attach+damage+frame

 标準的基于egl的繪畫,可以參考simple-egl.c的代碼,client調用opengl/opengles實作繪畫,最終是eglswapbuffer;server這邊有對應buffer的gbm object以及handle,根據damage等條件,一系列條件決定合成,合成會最少有一張經過gpu合成的完整的scan-out圖(可能有overlay,也可能有cursor)然後addfb2為對應的buffer建立framebuffer對象,并且傳回fbid,後面就是ioctl的标準處理流程

display:weston渲染流程:buffer+attach+damage+frame

參考https://github.com/wayland-project/weston/blob/master/clients/simple-shm.c &  https://github.com/wayland-project/wayland/blob/master/protocol/wayland.xml

寫在前面:

https://www.apertis.org/waylandevaluation/compositors/

https://fossies.org/dox/weston-9.0.0/

https://fossies.org/dox/wayland-1.19.0/

用戶端渲染

在Wayland架構中,用戶端UI的所有呈現均由用戶端代碼執行,通常由用戶端使用的圖形工具包執行。 這與X11的現代用法沒有什麼不同。

圖形工具箱可以使用其希望呈現UI元素的任何方法:在CPU上進行軟體呈現,或使用GLES進行硬體呈現。 Wayland所需要做的就是将用戶端渲染的每個幀和視窗的結果像素發送到合成器。 像素資料可能以幾種方式傳輸,具體取決于渲染方式以及用戶端和合成器互相支援的内容:

  •      包含實際像素資料的共享記憶體緩沖區。 如果沒有其他機制,則支援這些備用機制。
  •      GPU緩沖區共享(DRM / DRI)。 用戶端直接在GPU上渲染視窗,結果像素資料保留在GPU記憶體中,并将其句柄傳遞給合成器。 這防止了像素資料的不必要和昂貴的複制。

合成器的工作

一旦合成器擁有了所有像素資料(或包含它的GPU緩沖區的句柄),它便可以合成一幀。 與用戶端渲染一樣,這可以通過幾種方式完成:

  •      軟體渲染。 CPU密集型,用作備用。 這還需要将像素資料從GPU記憶體中拉出,這很昂貴。
  •      使用GLES進行完整的GPU渲染。 這将擷取像素資料并在GPU上對其進行合成,如果動畫需要的話,可能會應用着色器和3D轉換。
  •      顯示控制器上特定于硬體的API。 這些通常是2D合成API,與完整的3D計算相比,它們的資源占用較少,但仍在顯示控制器而非CPU上進行處理,并且不需要額外的像素資料副本。

例如,在傳統的顯示卡上,可能有四個寫死覆寫層:

  •     Primary: main overlay
  •     Scanout: a single, full-screen surface
  •     Sprite: typically a video overlay in a different colour space
  •     Cursor

需要注意的是,如何通過client使用的api去反推server的操作,需要多參考wayland/protocol裡面的協定檔案。https://blog.csdn.net/u012839187/article/details/107861193

1.buffer

weston-simple-shm::create_shm_buffer

使用者空間:
struct buffer {
	struct wl_buffer *buffer;
	void *shm_data;
	int busy;
};


fd = os_create_anonymous_file(size);   
    使用者空間建立size大小檔案,取得fd。
data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
    将fd使用者空間buffer映射到程序核心空間。
pool = wl_shm_create_pool(display->shm, fd, size);   
    這個地方建立一個pool。這個pool可以很大,然後後續wl_shm_pool_create_buffer再從這個pool中分出來一部分用作繪畫

對應的server端操作:    wayland/src/wayland-shm.c::shm_create_pool
    struct wl_shm_pool *pool;
    pool->data = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);[server]
    建立pool。映射同一個fd;此時client與weston達成共享記憶體。

也通過server端的操作,為client端的wl_shm_pool指派。并最終将pool傳給client
    wl_resource_set_implementation(pool->resource,
			       &shm_pool_interface,
			       pool, destroy_pool);


需要明确,buffer是使用者端建立的,使用者端和server端通過傳遞fd實作buffer共享。



使用者空間:
buffer->buffer = wl_shm_pool_create_buffer(pool, 0, width, height, stride, format);
    這個buffer->buffer對應的是wl_buffer結構體。從pool中取出一部分,套上一個wl_buffer

對應的server端操作:    wayland/src/wayalnd-shm.c::shm_pool_create_buffer
建立wl_shm_buffer結構體,并指派,width/height/format/stride/offset;将之前建立的pool結構體付給buffer。
    buffer->pool = pool;

通過pool擷取wl_buffer,對使用者空間的buffer進行指派。注意是将wl_shm_buffer賦給wl_buffer
	wl_resource_set_implementation(buffer->resource,
				       &shm_buffer_interface,
				       buffer, destroy_buffer);



使用者空間:
wl_shm_pool_destroy(pool);

對應的server端操作:    wayland/src/wayalnd-shm.c::shm_pool_destroy,随後調用wl_resource_destroy
    内部計數器-1,如果pool的計數為0[destroy pool且destroy buffer之後],則銷毀pool的所有資源


最終使用者空間将mmap的位址給了buffer->shm_data  //也就是對應weston:server的pool->data
buffer->shm_data = data;
    提供給client的繪畫位址。
           

 随即client會對buffer->shm_data空間執行繪畫;

 client和sever分别通過wl_buffer以及wl_shm_buffer對實際的繪畫部分内容進行管理,他倆應該是等價的。

 Server端是wl_shm_buffer->wl_shm_pool->data;

1.buffer本質上隻有一塊,是使用者空間建立以後用來建立pool用到的buffer。

2.pool的本質是可以對這個buffer進行分割以及管理,分割通過offset的方式。

3.weston_buffer.release是由wl_buffer_send_release實作的通知。weston_buffer結構體本身有一個busy_count的計數器,在weston_buffer_reference或者其他函數調用的時候,如果發現busy_count為0,就會調用wl_buffer_send_release發送信号

https://wayland-book.com/surfaces/shared-memory.html#creating-buffers-from-a-pool

參考上述内容也可以知道pool的作用。

2.attach

client:
wl_surface_attach(window->surface, buffer->buffer, 0, 0);
    buffer->buffer是一個指向struct wl_buffer的指針
           
server:    weston/libweston/compositor.c::surface_attach
surface_attach      //buffer_resource是client傳遞過來的buffer->buffer,即wl_buffer
	if (buffer_resource) {
		buffer = weston_buffer_from_resource(buffer_resource);
        實際就是weston_buffer->resource = buffer_resource;也就是在weston内部去管理這個使用者空間的buffer資源
        ...}
    weston_surface_state_set_buffer(&surface->pending, buffer);
        state->buffer = buffer;
   	surface->pending.newly_attached = 1;

    在通過weston_buffer_from_resource拿到使用者端的buffer以後,把weston_surface_state->buffer與weston_buffer對象綁定;
    buffer是一個指向weston_buffer的指針;實際上是:weston_surface->pending->buffer = buffer;
    /* All the pending state, that wl_surface.commit will apply. */
    所謂的pending狀态,應該是涉及一個雙緩沖概念,此時的這些修改暫存在pending的結構體裡面,隻有執行commit操作的時候才真的用到。
    對此我了解當wl_surface.commit執行時,會用到這些pending->buffer
    surface->pending.newly_attached = 1; 這個狀态在接下來的操作中經常判斷
           

1.attach将wl_buffer設定為pending wl_buffer而不是currect。[wayland 0.99版本之後,都使用了double buffer state,更新的都是pending的buffer,在commit之後,才将pending.buffer指派給current buffer,然後clear掉pending.buffer供下次使用。然後,在服務端repaint surface時,會清理掉current.buffer供下次使用。]

本質上是需要window->surface與buffer->buffer 綁定;把使用者空間的buffer管理起來,指派到weston裡面的weston_buffer結構體中,并且将weston_buffer與他的weston_surface->pending結構體關聯起來。

在這裡,client端的wl_buffer和server端的weston_buffer應該是等價的。

做完這一步,那麼weston_surface->pending->buffer也就有了對應的weston-buffer

3.damage

This request is used to describe the regions where the pending buffer is different from the current surface contents, and where the surface therefore needs to be repainted. The compositor ignores the parts of the damage that fall outside of the surface.

此請求用于描述pending->buffer與current->surface->contents不同的區域,以及是以需要重新繪制的surface區域。compositor忽略掉surface以外的部分損壞。

client:
wl_surface_damage(window->surface, x, y, window->width - 40, window->height - 40);
此請求用于描述挂起緩沖區[attach操作中的pending->buffer]與目前表面内容不同的區域,以及表面是以需要重新繪制的位置。合成器忽略掉落在表面之外的部分。
損壞是雙緩沖狀态,其中x和y指定損壞矩形的左上角。
           
server:weston/libweston/compositor.c::surface_damage
surface_damage
    struct weston_surface *surface = wl_resource_get_user_data(resource);
    pixman_region32_union_rect(&surface->pending.damage_surface,
 				   &surface->pending.damage_surface,
 				   x, y, width, height);
    擷取surface後,将新的pending.damage區域與原有的pending.damage區域進行組合,得到新的pending.damage區域。
           

 在這裡client空間的wl_surface與weston:server空間weston_surface應該是等價的。

"Damaged" meaning "this area needs to be redrawn"

4.frame

 Request a notification when it is a good time to start drawing a new frame, by creating a frame callback. This is useful for throttling redrawing operations, and driving animations.

如果用戶端送出的更新時間早于某個更新,則某些更新可能無法顯示,并且用戶端通過過于頻繁的繪制而浪費資源。【callback的作用就是解決上述問題】

    A server should avoid signaling the frame callbacks if the surface is not visible in any way, e.g. the surface is off-screen, or completely obscured by other opaque surfaces.

client:
window->callback = wl_surface_frame(window->surface);
    幀繪制回調的時候,通知這個surface;
wl_callback_add_listener(window->callback, &frame_listener, window);
    每當繪制完一幀就發送wl_callback::done消息給使用者client,接到這個幀繪制回調的信号,調用frame_listener函數
           
server:weston/libweston/compositor.c::surface_frame
surface_frame
    wl_list_insert(surface->pending.frame_callback_list.prev, &cb->link);
    在Server端建立Frame callback,它會被放在該surface下的frame_callback_list清單中。傳回它的代理對象wl_callback
           

這個done事件何時觸發?weston_output_repaint裡面:  

 wl_list_for_each_safe(cb, cnext, &frame_callback_list, link) {

        wl_callback_send_done(cb->resource, frame_time_msec);

        wl_resource_destroy(cb->resource);

    }

需要注意的是,這個地方還沒有執行output_flush的動作,也就是說,如果是隻有一個buffer的話,那麼frame的動作如果處理了buffer,就會導緻真正顯示的buffer出問題。

接下一篇

display:weston渲染流程:commit

https://blog.csdn.net/u012839187/article/details/106469038

NOTE:

Q: Weston will never use HW planes for wl_shm_buffer, only for GBM or dmabuf type buffer it will be used. Weston不會在wl_shm_buffer中使用HW平面[Overlay-plane],隻會在GBM或dmabuf類型的buffer中使用。

A: That's correct. The reason is that we need to be able to get a KMS framebuffer object with the pixel content from the client buffer in it. Effectively, the only way to import client content into a KMS framebuffer is via dmabuf; KMS has no method of creating framebuffers from an arbitrary pointer to user memory. And we need a framebuffer object in order to display anything on a plane.

那是正确的。原因是我們需要能夠從用戶端緩沖區中獲得包含像素内容的KMS framebuffer對象。實際上,将用戶端内容導入KMS framebuffer的唯一方法是通過dmabuf;KMS沒有從使用者記憶體的任意指針建立framebuffer的方法。為了在平面上顯示任何内容,我們需要一個framebuffer對象。

wl_surface.attach assigns the given wl_buffer as the pending wl_buffer.

wl_surface.commit makes the pending wl_buffer the new surface contents, and the size of the surface becomes the size calculated from the wl_buffer, as described above. After commit, there is no pending buffer until the next attach.

Committing a pending wl_buffer allows the compositor to read the pixels in the wl_buffer. The compositor may access the pixels at any time after the wl_surface.commit request. When the compositor will not access the pixels anymore, it will send the wl_buffer.release event. Only after receiving wl_buffer.release, the client may reuse the wl_buffer. A wl_buffer that has been attached and then replaced by another attach instead of committed will not receive a release event, and is not used by the compositor.

If wl_surface.attach is sent with a NULL wl_buffer, the following wl_surface.commit will remove the surface content. 

看wayland官方文檔的内容,是允許attch一個NULLbuffer随後commit的。但是我測試下來client會死等callback消息【卡死】;猜測是否為client端自己定義timer進行commit的送出能夠實作透明效果,不應該等待server的callback消息。

繼續閱讀