display:weston渲染流程:commit

接上一篇

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

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

下面講commit

 

5.commit

https://happyseeker.github.io/graphic/2016/11/10/wayland-commit-relative-flow.html

Surface state (input, opaque and damage regions, attached buffers, etc.) is double-buffered.

表面狀態(輸入,不透明和損壞區域,附加緩衝區等)是雙緩衝的。=>[防止圖像抖動]

commit操作本質上就是爲double buffer而設計,多次操作、一次提交。注意到之前的操作attach,damage,frame等僅僅在commit以後纔會生效。(之前的操作都是對pending結構體的操作,只有commit以後纔會切換到current結構體上)

client:
wl_surface_commit(window->surface);
server:
surface_commit
...
weston_surface_is_pending_viewport_source_valid  //縮放相關的校驗
weston_surface_is_pending_viewport_dst_size_int  //縮放相關校驗,服務爲wp_viewporter
...
     if (sub) {
 	weston_subsurface_commit(sub); //1
 	return;
     }
 	weston_surface_commit(surface);
...
    
如果是subsurface則走subsurface_commit,否則走surface_commit


1.weston_subsurface_commit(struct weston_subsurface *sub)
    if (weston_subsurface_is_synchronized(sub)) {
	    weston_subsurface_commit_to_cache(sub);
    } else {
	    if (sub->has_cached_data) {
		    /* flush accumulated state from cache */
		    weston_subsurface_commit_to_cache(sub);
		    weston_subsurface_commit_from_cache(sub);
	    } else {
		    weston_surface_commit(surface);
	    }

	    wl_list_for_each(tmp, &surface->subsurface_list, parent_link) {
		    if (tmp->surface != surface)
			    weston_subsurface_parent_commit(tmp, 0);
	    }
    }

    判斷父子surface爲同步或異步,並完成相應的commit操作
    ...【todo】

 補充:

wl_subsurface::set_sync - set sub-surface to synchronized mode
Change the commit behaviour of the sub-surface to synchronized mode, also described
as the parent dependent mode.

In synchronized mode, wl_surface.commit on a sub-surface will accumulate the
committed state in a cache, but the state will not be applied and hence will not
change the compositor output. The cached state is applied to the sub-surface
immediately after the parent surface's state is applied. This ensures atomic
updates of the parent and all its synchronized sub-surfaces. Applying the cached
state will invalidate the cache, so further parent surface commits do not
(re-)apply old state.

wl_subsurface::set_sync—將subsurface設置爲同步模式
將子表面的提交行爲更改爲同步模式,也就是描述作爲父依賴模式。
在同步模式下,子表面上的wl_surface.commit將提交狀態累積在緩存中,
if (weston_subsurface_is_synchronized(sub)) {
		weston_subsurface_commit_to_cache(sub);
但是該狀態不會被應用,因此也不會被更改compositor的輸出。
在應用父表面的狀態之後立即執行“緩存的狀態應用於子表面”。
這樣可以確保原子性父類及其所有同步子表面的更新。
已經使用的subsurface的緩存將被使用後無效話,下次父表面的提交將不會再次應用子表面的老的state.



另外一種:
wl_subsurface::set_desync - set sub-surface to desynchronized mode
Change the commit behaviour of the sub-surface to desynchronized mode, also described as 
independent or freely running mode.
In desynchronized mode, wl_surface.commit on a sub-surface will apply the pending state 
directly, without caching, as happens normally with a wl_surface. Calling
wl_surface.commit on the parent surface has no effect on the sub-surface's wl_surface
state. This mode allows a sub-surface to be updated on its own.
If cached state exists when wl_surface.commit is called in desynchronized mode, the
pending state is added to the cached state, and applied as a whole. This invalidates the cache.

Note: even if a sub-surface is set to desynchronized, a parent sub-surface may override
it to behave as synchronized. For details, see wl_subsurface.
If a surface's parent surface behaves as desynchronized, then the cached state is applied
on set_desync.
將子表面的提交行爲更改爲去同步模式,也稱爲獨立或自由運行模式。
在不同步模式下,子表面上的wl_surface.commit將直接應用掛起狀態,而不使用緩存,就像在wl_surface上
通常所做的那樣。在父表面上調用wl_surface.commit對子表面的wl_surface狀態沒有影響。此模式允許子表
面自行更新。
如果在以非同步模式調用wl_surface.commit時存在緩存狀態,則將掛起狀態添加到緩存狀態,並作爲一個整
體應用。這會使緩存失效。也就是,本來應該是對pending結構體做操作,變量換成了cached結構體

 

server:
2.weston_surface_commit
    weston_surface_commit_state(surface, &surface->pending);
      
             
    weston_surface_commit_subsurface_order(surface);
            //更新subsurface的順序,其實也是將subsurface從pending列表換到當前的列表中
            ...【todo】

    weston_surface_schedule_repaint(surface);
            //重繪界面,這塊邏輯複雜
weston_surface_commit_state
0    surface->buffer_viewport = state->buffer_viewport;
     具體內容查看buffer_viewport結構體,主要與縮放設置相關
1    weston_surface_attach(surface, state->buffer);
        surface->compositor->renderer->attach(surface, buffer);//一般來說,有gl_renderer就不會使用pixman等軟件渲染backend
            gl_renderer_attach(struct weston_surface *es, struct weston_buffer *buffer)
            最終調用gl_renderer_attach;根據buffer->resource的不同,調用不同的attch函數:
                gl_renderer_attach_shm    //不同的圖像格式需要的plane不一樣,yuv普遍多一些[yuv分開存放,一個存y,一個存v或者uv...],app申請了shm的server會走入這個邏輯         
                gl_renderer_attach_egl                
                gl_renderer_attach_dmabuf             //A   
                gl_renderer_attach_gbm_buffer         //與A類似,判斷從app傳來的資源是否有對應的buffer類型,使用對應的buffer
                    ensure_textures(gs, gs->num_images);
                    //以上四則函數均會調用;實際調用glGenTextures + glBindTexture + glTexParameteri ;
                    gr->image_target_texture_2d(gs->target, gs->images[i]->image);
                    //創建texture並且將buffer內容置入texture;gs對象保存了一切。             
2    weston_surface_state_set_buffer(state, NULL);
        //綁定buffer;將surface_attch的時候放在weston_surface_state裏的buffer與weston_surface的buffer綁定。實際上是將pending-buffer送到currect_buffer。
        //本意是綁定state->buffer與buffer,因爲這裏傳入buffer爲NUll,將state->buffer置空
3    weston_surface_build_buffer_matrix(surface, &surface->surface_to_buffer_matrix); weston_matrix_invert
        //對surface進行旋轉,裁剪,縮放等動作
4    weston_surface_update_size(surface);
        //設置surface大小;這兩個函數不太明白

這裏3,4應該都是根據0裏面的結構體內容做旋轉,裁剪,縮放等動作,處理完以後重新設置surface的w,h參數。
......
5    surface->committed(surface, state->sx, state->sy);

6    wl_surface.damage與wl_surface.damage_buffer相關內容處理
     ...
     apply_damage_buffer
     ...

7    wl_surface.set_input_region相關操作

8    wl_surface.frame相關操作

總體上:1方面將pending state更新到current state中,state包括前面描述的諸多內容
從上到下處理attach,damage,frame,相關client端的操作   

 

weston_surface_commit_subsurface_order(struct weston_surface *surface)
    weston_surface_damage_subsurfaces
        weston_surface_schedule_repaint
        ...
//對此surface的子surface進行排序;並設定damage區域;標記surface的output屬性,表明即將被重繪

 

weston_surface_schedule_repaint(struct weston_surface *surface)
    weston_output_schedule_repaint(struct weston_output *output)

        output->idle_repaint_source = wl_event_loop_add_idle(loop, idle_repaint, output);
        //將idle_repaint加入idle loop事件中,當服務端沒有其它epoll事件時,會執行idle_repaint
            idle_repaint(void *data) -> output->start_repaint_loop(output);

關於start_repaint_loop,compositor用的不一樣,則對應函數不一致;我這裏是compositor-drm.c
https://github.com/randcd-APY/QuectelShare/blob/0cbf32c65b5ab6bf266db60e51793009eb6330ec/display/weston/src/compositor-sdm.c

//調用在前面wayland_output_create初始化的接口,由於默認使用drm_output_start_repaint_loop  
drm_output_start_repaint_loop(struct weston_output *output_base)
    //Read the current time from the Presentation clock
    weston_compositor_read_presentation_clock(output_base->compositor, &ts); 
                       
    weston_output_finish_frame(output_base, &ts, WP_PRESENTATION_FEEDBACK_INVALID);
    //一堆基於時間戳的精準計算,隨後引入output_repaint_timer_arm




output_repaint_timer_arm(struct weston_compositor *compositor)
    wl_event_source_timer_update(compositor->repaint_timer, msec_to_next);//硬性規定delay
    ==>wl_event_loop_add_timer(loop, output_repaint_timer_handler, ec);
    //故此處會調用output_repaint_timer_handler
        output_repaint_timer_handler(void *data)
1            compositor->backend->repaint_begin(compositor);

2            weston_output_maybe_repaint(output, &now, repaint_data);
                weston_output_repaint(struct weston_output *output, void *repaint_data)

                    output->assign_planes(output, repaint_data);
                        drm_assign_planes(struct weston_output *output_base)
                            assign_planes(struct weston_output *output_base, bool is_virtual_output)

                    compositor_accumulate_damage(ec);
                    weston_output_update_matrix(output);

                    output->repaint(output, &output_damage, repaint_data);
                        drm_output_repaint(struct weston_output *output_base, pixman_region32_t *damage)
                            output_repaint(output_base, damage, false);
                        
3            compositor->backend->repaint_flush(compositor, repaint_data);
            output_repaint_timer_arm(compositor);
            //最後調用原函數,完成循環

 

 Assign_planes

assign_planes
//講一下幾個重點
1. primary = &output_base->compositor->primary_plane;
    //這個primary很重要,一方面後續涉及到渲染是走overlay還是走gpu;[primary走gpu渲染]
    另一方面在flush_damage裏面,目前還不知道這個地方是幹嘛的

2. bool is_skip = false;
    /* Some views may neither be composited by GPU nor display engine directly,
     * they are in the "skip" status, even no buffer is attached. We can't pass them
     * to SDM because format check will fail which may cause SDM can't filter
     * correct strategy result. If so, assign those views directly to primary plane.
     */
    對於is_skip爲true的view,直接走primary_plane,也就如同上面的1,走gpu渲染。

3. es->keep_buffer = true;
    看上去是個保留buffer的標識位,具體是爲什麼不得而知。

4. sdm_layer = create_sdm_layer(output, ev, &surface_overlap, is_cursor, is_skip);
    一個view對應一個sdm-layer,將很多之前weston-buffer的東西傳遞給了sdm-layer結構體

5. output->view_count++;
    /*
     * SDM always need FB target layer, however, in Weston there is no explicit
     * fb target view, need to fake one
     */
    如果你專注繪圖,你會發現實際上dump出來的layer會比你繪畫的多一個,這是因爲gpu會將所有的gpu繪畫
的layer合成爲一張大的layer,所以此函數在執行完view_count++以後任然會再額外做一次view_count++
    
6. int error = Prepare(display_id, output);
https://github.com/randcd-APY/QuectelShare/blob/0cbf32c65b5ab6bf266db60e51793009eb6330ec/display/weston/sdm-service/sdm_display.cpp
    
    //最終是sdmdisplay::prepare
    //客戶端應使用此方法發送與當前顯示目標幀關聯的所有圖層,並檢查可在顯示管理器中完全處理的圖層。
    //此方法可以多次調用,但僅以最後一個調用爲準。此方法後必須跟 Commit()。


Layer 包含layer properties,和 drawing buffer

7. sdmdisplay::prepare
       PrePrepare(output);
       GetLayerStackDump(&layer_stack_, dump_buffer, sizeof(dump_buffer));
       display_intf_->Prepare(&layer_stack_);
       PostPrepare(output);


sdm_layer->view->plane,這個結構體何用,目前不得而知,但是一定和渲染決策相關
7. 在sdmdispaly::prepare裏面 

Preprepare->PrePrepareLayerStack
	1. FreeLayerStack (清空layer_stack_數組,visible&dirty-region)
        [This structure defines a layer stack that contains layers which need to be composed and rendered onto the target]

        2. AllocLayerStackMemory(output);根據output->view_count數量申請layer_stack中layer的數量

	3. PrepareNormalLayerGeometry  創建gbmbuffer等操作
	4. AddGeometryLayerToLayerStack
		a. AllocateMemoryForLayerGeometry(將visible&dirty_rect push入 vector)
		b. PopulateLayerGeometryOnToLayerStack(如函數名,layer_geometry賦值給layer_buffer&layer結構體)
	5. FreeLayerGeometry(glayer); 完成b以後,清空layer_geometry結構

	6. PrepareFbLayerGeometry 直接把drm_output的屬性賦值給LayerGeometry結構的的指針fb_layer
	7. AddGeometryLayerToLayerStack 動作同4
	8. FreeLayerGeometry 動作同5

GetLayerStackDump
	1. Dump layer-stack 信息

display_intf_->Prepare(&layer_stack_);
	1. 這裏面有多個派生函數,實際用哪一個是由display_type確定的
		enum DisplayType {
		  kPrimary,         //!< Main physical display which is attached to the handheld device.              
                    display_primary.cpp
		  kHDMI,            //!< HDMI physical display which is generally detachable.    
                    display_hdmi.cpp
		  kVirtual,         //!< Contents would be rendered into the output buffer provided by the client     
                    display_virtual.cpp
		                    //!< e.g. wireless display.
		  kDisplayMax,
		};
		最終調用display_base.cpp裏面的perpare

PostPrepare
    1. 編譯layer_stack,根據之前layer_stack的composition屬性定義sdm_layer的類型,是gpu還是overlay

 

總體來說,assign_planes:
1.初次判定wl_output的view是走gpu還是overlay
2.創建sdm_layer
3.再在prepare裏面完全判斷是走gpu還是overlay,賦值給sdm_layer
4.GPU:Move to primary plane if Strategy set it to GPU composition; 
   Overlay:Composed by Display Hardware directly

NOTE:

注意這裏面的layer變換,sdm_layer -> layer_stack.layer -> hw_layer

1.hw_layer->app_layer_count,上層client傳下來的layer數量。

2.hw_layer會對之前的layer做一個處理,分爲overlay以及唯一的一個gpu-layer

 

Output_repaint

output_repaint
    1. drm_output_render
    2. SetVSyncState(display_id, ENABLE, output);
    3. Commit(display_id, output);



1. drm_output_render
   //(use_pixman)? drm_output_render_pixman(output, damage):drm_output_render_gl(output, damage);[各家細節不同];後續判斷use_pixman? render_pixman:render_gl

    假設是drm_output_render_gl
        1. output->base.compositor->renderer->repaint_output(&output->base,damage);
            gl_renderer_repaint_output

                A. repaint_views(output, &total_damage);//這地方是個重點
                    if (view->plane == &compositor->primary_plane) {

                        //gpu render,後續就是pixman+gl API 實現繪畫?
                       draw_view(view, output, damage);
                    }
                    else {
                      /* this view is composed directly by overlay */
                    ...
                    ...
                        //pixman+gl API 清理view?
                       clear_view(view, output, damage);
                    }

                B. 
                    if (gr->swap_buffers_with_damage) {
		        ...
		        ret = gr->swap_buffers_with_damage(gr->egl_display,
				             go->egl_surface,
				             egl_damage, nrects);
		        ...
	            } else {
		        ret = eglSwapBuffers(gr->egl_display, go->egl_surface);
	            }

        2. bo = gbm_surface_lock_front_buffer(output->surface);
        //鎖定surface當前的frontbuffer,返回此surface的bufferobject.調用eglSwapBuffer以後需要立刻調用此函數。
          要想釋放此suface的buffer,必須釋放所有的關於此surface的bo.

        3. output->next = drm_fb_get_from_bo(bo, b, output->format);


2. SetVSyncState(display_id, ENABLE, output);

3. Commit(display_id, output);


 

drm_output_repaint利用modsetting接口將繪製的內容最終顯示到屏幕上,其機制還比較複雜,主要利用了pageflip、vblank和plane,相關原理與drm的API編程強相關,內容比較多

 

爲什麼wayland中要有repaint操作?不是號稱都是Client繪圖、wayland負責合成?
確實Client繪圖,compositor(服務端)負責合成。但由於client繪圖實際實現爲double buffer,client最初的繪圖操作都是在pending buffer中進行的,並沒有直接繪製到framebuffer中,所以,在client完成繪製後,需要進行commit操作,commit後,服務端會將相應的buffer會更新到當前的surface中,此時,需要進行repaint操作,將客戶端相應的繪製內容最終拷貝到framebuffer中

這裏的repaint操作,本質上只是將客戶端更新的繪製內容(damage區域)提交給compositor,最後由compositor進行合成後繪製到界面上

 

 

output初始化

兩個關鍵的地方:

    weston支持在不同的backend(後端)上運行,包括drm、wayland和X11。這裏需要注意這集中後端的區別,尤其是wayland後端,很容易弄混。

  1. drm後端,是wayland環境中默認使用的後端,其使用Linux KMS輸出,使用evdev作爲輸入,實際就是利用drm的接口實現合成。利用了硬件加速。

  2. wayland後端,這是指讓weston運行於另一個wayland compositor(比如另一個weston,嵌套運行)之上,作爲wayland的客戶端運行,此時的weston實例表現爲另一個compositor上的一個窗口。注意:這個不是默認的方式,這是嵌套方式,使用較少。

  3. X11後端,即weston運行在X11之上,每個weston output作爲一個X window。這種方式對於weston的測試非常有用(可以在現有的Xorg環境中測試weston的功能)。

    使用特定後端的情況下,繪圖時,支持使用不同的renderer(繪圖引擎),比如OpenGL(EGL,硬件加速)或者時pixman(軟件繪製),使用wayland作爲後端時,默認使用OpenGL作爲renderer。

這兩點的實現,都需要進行抽象、分層,以便於隔離,減少耦合,比較常見的實現手法,函數指針(回調函數)(對應於OO語言(如C++、Java)中的多態)。典型的依賴倒置原則。

對於關鍵點1的實現還有點不一樣(雖然思想是類似的),其實現爲在運行時動態加載共享庫,使用其中的接口,簡單說,就是用dlopen,然後每個backend實現爲相應的動態庫。相應流程爲:

main ->
  load_backend ->
    load_drm_backend ->
      weston_compositor_load_backend ->
        weston_load_module

 

寫在最後:

感謝shilun無私的幫助,解惑 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章