讓我們來回顧一下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計數。
request.sequence是過去某個時間點以來的 vblank 計數,例如系統啟動。
DRM_VBLANK_ABSOLUTE:
request.sequence是目前值的 vblank 計數。例如 1 指定下一個 vblank。該值可以與這些值的任意組合進行按位或運算:
DRM_VBLANK_RELATIVE:
使用第二顯示器的 vblank。
DRM_VBLANK_SECONDARY:
立即傳回并觸發事件回調而不是等待指定的 vblank。
DRM_VBLANK_EVENT:
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; }