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::weston_surface_commit_state

 

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;关于attach,本质上是执行ensure_textures(gs, num_planes); 
            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,有wl_shm_buffer的实例,会走入这个逻辑;     
                gl_renderer_attach_egl                
                gl_renderer_attach_dmabuf             //A,判断从app传来的资源是否使用linux_dmabuf_buffer实例,如有,则调用此函数;实质上还是对应client端申请的buffer的server种类   
                gl_renderer_attach_gbm_buffer         //与A类似,判断从app传来的资源是否有对应的gbm_buffer类型,使用对应的buffer;实质上还是对应client端申请的buffer的server种类
                    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端的操作   

关于attch需要多讲几点:
    1.本质上都是去申请对应的texture;texture才是gpu能够处理的包含图像数据的类型
    2.不同的图像格式需要的texture不一样,rgb相关都是1,yuv可能是2或者3,因为yuv分开存储
    3.gbm-ob是用来统一管理buffers的,比方说查询buffer的某些状态
        gbm_bo_import + gbm_perform + gbm_bo_destroy
    4.gl_renderer_attach_shm通过往egl通过gs->surface来传入buffer?
    5.gl_renderer_attach_egl通过gs->images[i] = egl_image_create创建egl_image来处理对应单独的buffer,用于texture
    6.gl_renderer_attach_dmabuf通过image = linux_dmabuf_buffer_get_user_data(dmabuf); + gs->images[i] = egl_image_ref(image->images[i]); 填充gs参数,用于texture

weston_surface_commit::weston_surface_commit_subsurface_order

weston_surface_commit_subsurface_order(struct weston_surface *surface)
    weston_surface_damage_subsurfaces
        weston_surface_schedule_repaint
        ...
//对此surface的子surface进行排序;并设定damage区域;标记surface的output属性,表明即将被重绘

 

weston_surface_commit::weston_surface_schedule_repaint

output_repaint_timer_handler

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);
        //填充drm_fb *fb,并返回给output->next


2. SetVSyncState(display_id, ENABLE, output);
   //Method to enable VSync State, i.e. whether to generate callback on next frame.

3. Commit(display_id, output);


Flush
 Method to flush any pending buffers/fences submitted previously via Commit() call.
 Client shall call this method to request the Display manager to release all buffers and respective fences currently in use. This operation may result in a blank display on the panel until a new frame is submitted for composition.
 For virtual displays this would result in output buffer getting cleared with border color.
 
Commit
 Method to commit layers of a frame submitted in a former call to Prepare()
 Client shall call this method to submit layers for final composition. The composed output shall be displayed on the panel or written in output buffer. This method shall be called only once for each frame.
 In the event of an error as well, this call will cause any fences returned in the previous call to Commit() to eventually become signaled, so the client's wait on fences can be released to prevent deadlocks.

 

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

 

Q buffer 对应于上层,是指将已经渲染完毕的buffer放入buffer队列

Dq buffer对应于上册,是指将buffer从buffer队列拿出来,随后进行渲染操作

Release 对应于底层,是指将显示完毕的buffer重新送入buffer队列

Acquire 对应于底层 ,是指将buffer从buffer队列拿出,进行显示

GBM本身是负责buffer管理的,可能存储一些buffer的格式等信息【宽高,format等】

写在最后:

感谢shilun无私的帮助,解惑 

 

 

你可能感兴趣的:(weston/wayland,dispaly,hal,wayland/weston)