天天看點

weston中panel資料合成commit

讓我們來回顧一下panel surface的建立過程,panel本身是一個支援widget的window視窗類型。在使用window_create_custom建立視窗的時候,由于目前大部分裝置上的cairo都是支援EGL的,是以panel surface 的buffer類型是WINDOW_BUFFER_TYPE_EGL_WINDOW,如果cairo不支援EGL繪制,那麼panel surface的buffer類型就是WINDOW_BUFFER_TYPE_SHM。

cr = widget_cairo_create(panel->widget)//建立panel的surface
cairo_paint(cr)//繪制panel資料
           

EGL surface建立邏輯:

widget_cairo_create
	->cairo_surface = widget_get_cairo_surface(widget)
  	->surface_create_surface(surface, 0)
  		->egl_window_surface_create
  			->surface->base.prepare = egl_window_surface_prepare;
					surface->base.swap = egl_window_surface_swap;
					surface->base.acquire = egl_window_surface_acquire;
				->surface->egl_window = wl_egl_window_create(surface->surface,
						   rectangle->width,
						   rectangle->height);

					surface->egl_surface =weston_platform_create_egl_surface(display->dpy,
						   display->argb_config,
						   surface->egl_window, NULL);

					surface->cairo_surface =
									cairo_gl_surface_create_for_egl(display->argb_device,
											surface->egl_surface,
											rectangle->width,
											rectangle->height);
  		
	->cr = cairo_create(cairo_surface);
           

cairo_paint的簡單調邏輯參考上一節,cairo_paint就直接渲染顯示了。

shm surface的建立與繪制

shm類型的window渲染顯示的流程總結:

1.attach

根據client端的surface resource在server端配置設定buffer,給pending state作為pending中的buffer,下面的操作對象均是pending中的buffer。

2.damage

新舊damage區域融合得到新的damage區域。

3.commit

​ 3.1 opengl渲染前的準備

根據shm buffer的類型、大小,設定對應的gl參數。儲存到gl_surface_state結構體中。如果紋理和之前存在的不比對,會手動生成一個合适的新紋理。

​ 3.2 正式渲染顯示

weston_output_repaint使用opengl渲染surface并将framebuffer寫入drm裝置。repaint_flush應用pending state。渲染步驟在repaint循環中。

現在我們以shm類型的window建立為例,了解顯示過程。上一節最後有一個細節,在一次idle重繪任務中,cairo繪制window結構體中的main_surface和subsurfaces,window_flush會使用surface_flush先送出所有的subsurfaces,最後再送出main_surface。surface->toysurface在建立的時候使用的是shm_surface_create。是以這裡的swap将會使用shm_surface_swap。

surface->toysurface->swap(surface->toysurface,
				  surface->buffer_transform, surface->buffer_scale,
				  &surface->server_allocation);
           
static void
shm_surface_swap(struct toysurface *base,
		 enum wl_output_transform buffer_transform, int32_t buffer_scale,
		 struct rectangle *server_allocation)
{
	struct shm_surface *surface = to_shm_surface(base);
	struct shm_surface_leaf *leaf = surface->current;

	server_allocation->width =
		cairo_image_surface_get_width(leaf->cairo_surface);
	server_allocation->height =
		cairo_image_surface_get_height(leaf->cairo_surface);

	buffer_to_surface_size (buffer_transform, buffer_scale,
				&server_allocation->width,
				&server_allocation->height);
	//attach
	wl_surface_attach(surface->surface, leaf->data->buffer,
			  surface->dx, surface->dy);
  //dmage
	wl_surface_damage(surface->surface, 0, 0,
			  server_allocation->width, server_allocation->height);
  //commit
	wl_surface_commit(surface->surface);

	DBG_OBJ(surface->surface, "leaf %d busy\n",
		(int)(leaf - &surface->leaf[0]));

	leaf->busy = 1;
	surface->current = NULL;
}
           

swap的過程分為surface_attach、surface_damage、surface_commit三步。這些函數都是在compositor中定義的。libweston\compositor.c中實作了wl_surface_interface接口,所有wetson中的client調用wl_surface_xxx都會使用這裡定義的這些接口。

static const struct wl_surface_interface surface_interface = {
	surface_destroy,
	surface_attach,
	surface_damage,
	surface_frame,
	surface_set_opaque_region,
	surface_set_input_region,
	surface_commit,
	surface_set_buffer_transform,
	surface_set_buffer_scale,
	surface_damage_buffer
};
           

surface_attach

attach函數拿到client端的buffer,并傳給surface->pending state結構體。

static void
surface_attach(struct wl_client *client,
	       struct wl_resource *resource,
	       struct wl_resource *buffer_resource, int32_t sx, int32_t sy)
{
	struct weston_surface *surface = wl_resource_get_user_data(resource);
	struct weston_buffer *buffer = NULL;

	if (buffer_resource) {
    //核心功能:根據client端的resource在server配置設定buffer
		buffer = weston_buffer_from_resource(buffer_resource);
		if (buffer == NULL) {
			wl_client_post_no_memory(client);
			return;
		}
	}
  //将配置設定的buffer設定為pending,待更新到current
	weston_surface_state_set_buffer(&surface->pending, buffer);

	surface->pending.sx = sx;
	surface->pending.sy = sy;
	surface->pending.newly_attached = 1;
}
           

surface_damage

static void
surface_damage(struct wl_client *client,
	       struct wl_resource *resource,
	       int32_t x, int32_t y, int32_t width, int32_t height)
{
	struct weston_surface *surface = wl_resource_get_user_data(resource);

	if (width <= 0 || height <= 0)
		return;

	pixman_region32_union_rect(&surface->pending.damage_surface,
				   &surface->pending.damage_surface,
				   x, y, width, height);
}
           

pixman_region32_union_rect将原有的damage區域與新的damage區域進行組合,得到新的damage區域。

surface_commit

先對pending中的buffer進行縮放、旋轉、長寬有效性等一系列驗證,然後weston_surface_commit函數将surface送出給opengl渲染并合成顯示。下面會詳細講解surface的渲染顯示過程。

->surface_commit
    ->1. weston_surface_is_pending_viewport_source_valid//對pending buffer進行縮放、旋轉等資料校驗操作
    ->2. weston_surface_is_pending_viewport_dst_size_int//校驗surface長寬的有效性
    ->3. surface->pending.acquire_fence_fd//fence_fd有效
  		->wl_shm_buffer_get(surface->pending.buffer->resource)//檢查shm buffer
    ->4.weston_surface_commit(surface)//commit surface
    	->4.1 weston_surface_commit_state(surface, &surface->pending)//重點函數
  		->4.2 weston_surface_commit_subsurface_order(surface)
    	->4.3 weston_surface_schedule_repaint//重繪定時器
  
    ->5. weston_subsurface_commit(surface)//subsurface commit
           

weston_surface_commit_state

先将pending中的buffer提取到surface中。attach的時候需要判斷weston_buffer的類型,shm、egl、dmabuf三種類型對應了三種gl render attach函數。

weston_surface_commit_state(操作的是pending buffer)
    1.->weston_surface_attach(surface, state->buffer)
    	weston_buffer_reference(&surface->buffer_ref, buffer)//關聯weston_surface和weston_buffer
		surface->compositor->renderer->attach(surface, buffer)//使用合成器裡的渲染器gl_renderer_attach
    		gl_renderer_attach_shm//
    		gl_renderer_attach_egl//建立對應surface buffer的egl_image,激活綁定紋理。
    		gl_renderer_attach_dmabuf//使用dmabuf時的函數
    2.//渲染結束清理pending buffer
    3.-> weston_surface_build_buffer_matrix(surface, &surface->surface_to_buffer_matrix)//對surface進行旋轉,裁剪,縮放等矩陣操作
    4.->weston_surface_update_size(surface)//設定surface大小
    5.->surface->committed(surface, state->sx, state->sy)
    //若desktop,則surface.c:: weston_desktop_surface_committed, 看上去是設定surface在螢幕上的位置,也就是顯示位置,視窗管理一部分;主要是update了size,需要做對應的改動
    6.->apply_damage_buffer//dmage 區域合并
    7.->wl_surface.set_input_region
    8.->wl_surface.frame//插入frame_callbake_list連結清單
    9.->wl_signal_emit(&surface->commit_signal, surface)
		//!最後發出commit_signal,将output從pending list移除掉并添加進合成器的output_list
           

這裡以shm為例進行深入分析。在repaint之前gl_renderer_attach_shm需要為渲染做一些準備,擷取shm buffer,綁定到傳入的surface buffer,根據shm buffer的參數設定gl相關的參數(像素格式、大小),紋理不比對時ensure_textures生成新紋理,gl_surface_state表示這個surface的渲染狀态。

static void
gl_renderer_attach_shm(struct weston_surface *es, struct weston_buffer *buffer,
		       struct wl_shm_buffer *shm_buffer)
{
	struct weston_compositor *ec = es->compositor;
	struct gl_renderer *gr = get_renderer(ec);
	struct gl_surface_state *gs = get_surface_state(es);
	GLenum gl_format[3] = {0, 0, 0};
	GLenum gl_pixel_type;
	int pitch;
	int num_planes;

	buffer->shm_buffer = shm_buffer;
	buffer->width = wl_shm_buffer_get_width(shm_buffer);
	buffer->height = wl_shm_buffer_get_height(shm_buffer);

	num_planes = 1;
	gs->offset[0] = 0;
	gs->hsub[0] = 1;
	gs->vsub[0] = 1;

	switch (wl_shm_buffer_get_format(shm_buffer)) {
	case WL_SHM_FORMAT_XRGB8888:
	...
	case WL_SHM_FORMAT_ARGB8888:
		gs->shader_variant = SHADER_VARIANT_RGBA;
		pitch = wl_shm_buffer_get_stride(shm_buffer) / 4;
		gl_format[0] = GL_BGRA_EXT;
		gl_pixel_type = GL_UNSIGNED_BYTE;
		es->is_opaque = false;
		break;
	case WL_SHM_FORMAT_RGB565:
	...
	case WL_SHM_FORMAT_YUV420:
	...
	case WL_SHM_FORMAT_NV12:
	...
	case WL_SHM_FORMAT_YUYV:
	...
	default:
		weston_log("warning: unknown shm buffer format: %08x\n",
			   wl_shm_buffer_get_format(shm_buffer));
		return;
	}

	//紋理不比對的時候,生成新紋理
	if (pitch != gs->pitch ||
	    buffer->height != gs->height ||
	    gl_format[0] != gs->gl_format[0] ||
	    gl_format[1] != gs->gl_format[1] ||
	    gl_format[2] != gs->gl_format[2] ||
	    gl_pixel_type != gs->gl_pixel_type ||
	    gs->buffer_type != BUFFER_TYPE_SHM) {
		...
		...
    
		ensure_textures(gs, GL_TEXTURE_2D, num_planes);
	}
}
           

weston_surface_schedule_repaint(surface)

渲染前的準備工作完成之後,下面就是渲染并顯示這個surface,這裡才開始真正調用到opengl和drm。

weston_output_schedule_repaint
    ->idle_repaint
    	->start_repaint_loop
    		->drm_output_start_repaint_loop//libweston/backend-drm/drm.c
           
drmVBlank vbl = {
		.request.type = DRM_VBLANK_RELATIVE,
		.request.sequence = 0,
		.request.signal = 0,
	};
//設定用于輸出的vblank同步的類型,傳入crtc的pipeline,傳回目前的顯示器是主顯示器,還是
//副顯示器
vbl.request.type |= drm_waitvblank_pipe(output->crtc);
//等待vblank事件觸發,成功則傳回0
ret = drmWaitVBlank(backend->drm.fd, &vbl);

/* Error ret or zero timestamp means failure to get valid timestamp */
if ((ret == 0) && (vbl.reply.tval_sec > 0 || vbl.reply.tval_usec > 0)) {
		//螢幕重新整理率計算
		weston_compositor_read_presentation_clock(backend->compositor,
							  &tnow);
		drm_output_update_msc(output, vbl.reply.sequence);
		weston_output_finish_frame(output_base, &ts,
						WP_PRESENTATION_FEEDBACK_INVALID);
}
//第一次啟動不會走到這裡,在weston_output_finish_frame中啟動repaint循環,repaint得到資料後才會走到這裡。
pending_state = drm_pending_state_alloc(backend);
//複制pending state到current state
drm_output_state_duplicate(output->state_cur, pending_state,
				   DRM_OUTPUT_STATE_PRESERVE_PLANES);
//在pending狀态的時候,更新所有drm output的狀态
ret = drm_pending_state_apply(pending_state);
           

指定了drmvblank的類型為DRM_VBLANK_RELATIVE,設定sequence為目前的vblank計數。

  • DRM_VBLANK_ABSOLUTE:

    request.sequence是過去某個時間點以來的 vblank 計數,例如系統啟動。
  • DRM_VBLANK_RELATIVE:

    request.sequence是目前值的 vblank 計數。例如 1 指定下一個 vblank。該值可以與這些值的任意組合進行按位或運算:
  • DRM_VBLANK_SECONDARY:

    使用第二顯示器的 vblank。
  • DRM_VBLANK_EVENT:

    立即傳回并觸發事件回調而不是等待指定的 vblank。

weston_output_finish_frame

output_repaint_timer_handler函數最後會開啟一輪新的repaint定時,repaint進而一直循環下去。repaint_begin建立一個合成器的drm_pending_state repaint_data,每一次repaint都要配置設定一個新的drm_pending_state給drm使用。

weston_output_repaint使用opengl渲染surface并将framebuffer寫入drm裝置。repaint_flush應用pending state。

weston_output_finish_frame//計算幀率
	->output_repaint_timer_arm//啟動repaint定時器
		->output_repaint_timer_handler//定時器溢出處理函數,最後再次啟動repaint定時器,進而實作repaint循環。
output_repaint_timer_handler
  //建立一個合成器的drm_pending_state repaint_data,每一次repaint都要配置設定一個
  //新的drm_pending_state給drm使用。
 ->repaint_data =compositor->backend->repaint_begin
 //資料繪制,渲染完的資料儲存在output
 ->weston_output_maybe_repaint(output, &now, repaint_data)
    -->weston_output_repaint(output, repaint_data);
 //送顯
 ->compositor->backend->repaint_flush(compositor,
							 repaint_data);
           

weston_output_repaint

weston_compositor_build_view_list(ec);
if (output->assign_planes && !output->disable_planes) {
    	//assign planes配置設定planes
		output->assign_planes(output, repaint_data);
	} else {
		wl_list_for_each(ev, &ec->view_list, link) {
			weston_view_move_to_plane(ev, &ec->primary_plane);
	}
//計算damage區域
output_accumulate_damage(output);

pixman_region32_init(&output_damage);
pixman_region32_intersect(&output_damage,
				  &ec->primary_plane.damage, &output->region);
pixman_region32_subtract(&output_damage,
				 &output_damage, &ec->primary_plane.clip);
//repaint渲染出結果
output->repaint(output, &output_damage, repaint_data);
pixman_region32_fini(&output_damage);
//向client端發送done消息
wl_list_for_each_safe(cb, cnext, &frame_callback_list, link) {
		wl_callback_send_done(cb->resource, frame_time_msec);
		wl_resource_destroy(cb->resource);
	}

           
drm_assign_planes
drm_pending_state *pending_state = repaint_data;
*primary = &output_base->compositor->primary_plane//primary走GPU渲染
drm_output_propose_state_mode mode = DRM_OUTPUT_PROPOSE_STATE_PLANES_ONLY//預設使用overlay模式
    
state = drm_output_propose_state(output_base, pending_state, mode); 
//為每個output中的view配置設定對應的plane,并且配置設定plane對應的合成器,預設是GPU合成。
//如果這裡overlay失敗,就會嘗試使用mix模式;再失敗,就是嘗試GPU-ONLY的合成模式
weston_view_move_to_plane(ev, primary);//如果沒有assign_planes,直接全部交給primary_plane

           
drm_output_repaint

output->repaint最終指向的是drm_output_render_gl函數,走到gl_renderer_repaint_output這裡,我們終于來到了最核心的opengl渲染部分。通過通路drm compositor的drm_fd來通路drm裝置。在opengl畫完資料後,drm_fb_get_from_bo将fb寫入drm裝置(最重要的函數)。

drm_output_render(state, damage)
	->drm_output_render_gl(state, damage)
  			//第一步 gl渲染
        ->gl_renderer_repaint_output
        	->repaint_views
        		->draw_view //opengl渲染過程
  			//第二步 拿到gbm buffer
        ->bo = gbm_surface_lock_front_buffer(output->gbm_surface);//opengl畫好以後從gbm_surface中拿到gbm_buffer,gbm buffer object

				//第三步,将buffer寫入drm裝置。
				//************送顯最最核心的函數************
				//通過gbm_bo拿到drm frame buffer
				//ret作為傳回值,是drm_fb類型,drm_fb_get_from_bo設定了drm_fb中的drm_fd,以及						buffer的實際資料與大小。
				//其中drm_fb_addfb函數通過drm api(drmModeAddFB2WithModifiers,		
				//drmModeAddFB2,drmModeAddFB)将framebuffer寫入進drm裝置。
        ->ret = drm_fb_get_from_bo(bo, b, true, BUFFER_GBM_SURFACE);
				
				//傳回給scanout_state,為了後續嘗試計算新damage區域,如果失敗就是0,那麼也就是全圖
				->ret->gbm_surface = output->gbm_surface;
  ->drmModeCreatePropertyBlob(b->drm.fd, rects, sizeof(*rects) * n_rects, &scanout_state->damage_blob_id);
           

gl_renderer_repaint_output

gl_renderer_repaint_output中仍然有縮放、旋轉操作,設定視圖邊界的操作。

wl_list_for_each_reverse(view, &compositor->view_list, link) {
		if (view->plane == &compositor->primary_plane) {
			struct gl_surface_state *gs =
				get_surface_state(view->surface);
			gs->used_in_output_repaint = false;
		}
	}
get_surface_state->gl_renderer_create_surface(surface)
  ->
  //将buffer引用和gl surface中的buffer引用關聯起來
  weston_buffer_reference(&gs->buffer_ref, buffer);
	weston_buffer_release_reference(&gs->buffer_release_ref,
					es->buffer_release_ref.buffer_release);  

  if (surface->buffer_ref.buffer) {
    //attach到buffer_ref.buffer
    //其中gl_renderer_attach_shm将共享緩存中的内容讀到surface中,這裡涉及大量的gl參數
    //有gl_renderer_attach_egl、gl_renderer_attach_dmabuf
		gl_renderer_attach(surface, surface->buffer_ref.buffer);
    //将buffer_ref.buffer
    //重新整理damage區域
		gl_renderer_flush_damage(surface);
	}


//gr->create_sync(gr->egl_display, EGL_SYNC_NATIVE_FENCE_ANDROID,attribs)

//調用EGL的"eglCreateSyncKHR"建立一個sync對象
go->begin_render_sync = create_render_sync(gr);
//渲染damage區域
repaint_views(output, &total_damage);

wl_signal_emit(&output->frame_signal, output_damage);
//
go->end_render_sync = create_render_sync(gr);
//swapSwapBuffers的作用
ret = eglSwapBuffers(gr->egl_display, go->egl_surface);
//在swap buffer之後必須送出render sync 對象,隻有在flush之後, render sync
//對象才真正擁有一個有效的sync file fd
timeline_submit_render_sync(gr, output, go->begin_render_sync,
				    TIMELINE_RENDER_POINT_TYPE_BEGIN);
timeline_submit_render_sync(gr, output, go->end_render_sync,
				    TIMELINE_RENDER_POINT_TYPE_END);

update_buffer_release_fences(compositor, output);

gl_renderer_garbage_collect_programs(gr);
           

repaint_views

static void
repaint_views(struct weston_output *output, pixman_region32_t *damage)
{
	struct weston_compositor *compositor = output->compositor;
	struct weston_view *view;

	wl_list_for_each_reverse(view, &compositor->view_list, link)
		if (view->plane == &compositor->primary_plane)
      //!
			draw_view(view, output, damage);
  			->ensure_surface_buffer_is_ready//等待EGLSyncKHR object
          ->attribs[1] = dup(surface->acquire_fence_fd);//複制檔案描述符
  				->sync = gr->create_sync(gr->egl_display,
			       EGL_SYNC_NATIVE_FENCE_ANDROID,
			       attribs);//根據複制的fd建立sync對象
  				->wait_ret = gr->wait_sync(gr->egl_display, sync, 0);//阻塞等待egl完成fence
  				->destroy_ret = gr->destroy_sync(gr->egl_display, sync);//完成後摧毀sync對象
        ->gl_shader_config_init_for_view//配置輸入紋理的着色器,surface的旋轉矩陣、邊緣濾波器種類(linear/nearest)
          //blend之後,opengl進行渲染
        ->	if (pixman_region32_not_empty(&surface_blend)) {
									glEnable(GL_BLEND);
          				//GL渲染
									repaint_region(gr, ev, &repaint, &surface_blend, &sconf);
									gs->used_in_output_repaint = true;
						}
}
           

實際渲染過程repaint_region,使能頂點着色器/片段着色器,打開shader program,glDrawArrays繪制三角形(一個四邊形,由兩個三角形組成,六個頂點資料)。

drm_repaint_flush

應用pending buffer

drm_pending_state_apply
 	->drm_pending_state_apply_atomic
        ->drm_output_apply_state_atomic//注意是以plane_list周遊
            ->drmModeAtomicCommit //這個就是commit,沒什麼好講的;
           

drm_output_enable

static int
drm_output_enable(struct weston_output *base)
{
	struct drm_output *output = to_drm_output(base);
	struct drm_backend *b = to_drm_backend(base->compositor);
	int ret;

	assert(!output->virtual);

	ret = drm_output_attach_crtc(output);
	if (ret < 0)
		return -1;

	ret = drm_output_init_planes(output);
	if (ret < 0)
		goto err_crtc;

	if (drm_output_init_gamma_size(output) < 0)
		goto err_planes;

	if (b->pageflip_timeout)
		drm_output_pageflip_timer_create(output);

	if (b->use_pixman) {
		if (drm_output_init_pixman(output, b) < 0) {
			weston_log("Failed to init output pixman state\n");
			goto err_planes;
		}
	} else if (drm_output_init_egl(output, b) < 0) {
		weston_log("Failed to init output gl state\n");
		goto err_planes;
	}

	drm_output_init_backlight(output);

	output->base.start_repaint_loop = drm_output_start_repaint_loop;
	output->base.repaint = drm_output_repaint;
	output->base.assign_planes = drm_assign_planes;
	output->base.set_dpms = drm_set_dpms;
	output->base.switch_mode = drm_output_switch_mode;
	output->base.set_gamma = drm_output_set_gamma;

	weston_log("Output %s (crtc %d) video modes:\n",
		   output->base.name, output->crtc->crtc_id);
	drm_output_print_modes(output);

	return 0;

err_planes:
	drm_output_deinit_planes(output);
err_crtc:
	drm_output_detach_crtc(output);
	return -1;
}