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

 

 

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