接上一篇
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无私的帮助,解惑