接上一篇
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後端,很容易弄混。
-
drm後端,是wayland環境中默認使用的後端,其使用Linux KMS輸出,使用evdev作爲輸入,實際就是利用drm的接口實現合成。利用了硬件加速。
-
wayland後端,這是指讓weston運行於另一個wayland compositor(比如另一個weston,嵌套運行)之上,作爲wayland的客戶端運行,此時的weston實例表現爲另一個compositor上的一個窗口。注意:這個不是默認的方式,這是嵌套方式,使用較少。
-
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無私的幫助,解惑