1.3.6 窗口显示
- 1 窗口显示PRESENT
- 1.1 协议
- 1.1.1 请求
- 1.1.2 事件
- 1.1.3 能力capabilities
- 1.2 xfree86平台实现
- 1.2.1 present_execute
- 1.2.2 present_event_notify
- 1.3 wayland平台实现
- 1.3.1 xwl_present_flip
- 1.1 协议
- 2 DRI3渲染
- 2.1 请求
- 2.2 调用流程
- 2.2.1 共享client buffer流程
- 2.2.2 共享server buffer流程
- 2.2.3 翻页
窗口显示以present和DRI3扩展协议说明,以及DRI3交换帧缓冲时使用present的流程
窗口显示PRESENT
present处理帧缓冲的显示和翻页,涉及CRTC硬件相关操作
协议
请求
macro | function | description |
---|---|---|
X_PresentQueryVersion | proc_present_query_version | 获取扩展协议版本 |
X_PresentPixmap | proc_present_pixmap | 显示画布内容 |
X_PresentNotifyMSC | proc_present_notify_msc | MSC计数器达到某个值时发送通知 |
X_PresentSelectInput | proc_present_select_input | 选择关注的事件(3个事件) |
X_PresentQueryCapabilities | proc_present_query_capabilities | 查询CRTC能力 |
MSC:media stream counter,vblank次数
事件
macro | mask | description |
---|---|---|
PresentConfigureNotify | PresentConfigureNotifyMask | 窗口尺寸变化 |
PresentCompleteNotify | PresentCompleteNotifyMask | PresentPixmap操作完成 |
PresentIdleNotify | PresentIdleNotifyMask | pixmap可重用(Server不再使用) |
PresentRedirectNotify | PresentRedirectNotifyMask | 有重定向窗口的pixmap,暂未实现 |
能力capabilities
macro | description |
---|---|
PresentCapabilityNone | 失能 |
PresentCapabilityAsync | 支持立即翻页功能 |
PresentCapabilityFence | 支持屏障同步 |
PresentCapabilityUST | 支持UST(微秒级)时间 |
PresentCapabilityAsyncMayTear | 支持立即翻页,可能会产生撕裂 |
UST:微秒级时间,为指定帧缓冲第一个像素发送时间
xfree86平台实现
present扩展协议的实现提供了12个回调函数,用于抽象不同平台的实现,提供了xfree86的默认实现。窗口内容的显示由present_scmd_pixmap函数实现。有硬件提供两种更新方式:
内存拷贝:调用drmWaitVBlank,等下一个vblank信号时回调present_execute_copy,将离屏窗口(pixmap)数据拷到屏幕窗口中。注意:数据拷贝必须在该vblank间隔内完成,否可能会导致画面撕裂。
翻页:需硬件支持,调用drmModePageFlip,等下一个vblank信号时,硬件自动实现翻页操作(当flags指定DRM_MODE_PAGE_FLIP_ASYNC时,硬件立即翻页),翻页完成后回调present_event_notify。注意:翻页后,pixmap内存被CRTC硬件使用,此时,client不能使用pixmap,待下次翻页操作将pixmap替换出来后client才能使用,此时present向client发送PresentIdleNotify事件。这种方式减少了内存拷贝操作。
是否支持窗口翻页特性的检查(present_check_flip)
ModeSetting驱动支持翻页特性
窗口的pixmap为屏幕pixmap,或者正在翻页的pixmap,或者已翻页的pixmap。
窗口的可见区域(clipList)与根窗口可绘制区域(winSize)一致。(全屏窗口)
窗口的尺寸和位置与离屏的pixmap一致。(在屏和离屏的画布尺寸一样)
硬件设备支持翻页特性 (ms_present_check_flip)
未使用软件光标,需使用设备提供的硬件光标,否则无法提供翻页功能
前端framebuffer的宽度与pixmap一致(pitch)
CRTC支持pixmap的像素格式和修饰符(内存布局,压缩格式等)
mode hooks | default | description |
|
---|---|---|---|
query_capabilities | present_scmd_query_capabilities | 查询CRTC能力 |
|
get_crtc | present_scmd_get_crtc | 获取CRTC |
|
check_flip | present_check_flip | 检查是否支持窗口翻转 |
|
check_flip_window | present_check_flip_window | 窗口变化后,检查是否支持窗口翻转 |
|
can_window_flip | present_scmd_can_window_flip | 检查是否为正在翻转或已翻转的窗口 |
|
clear_window_flip | present_scmd_clear_window_flip | 清理翻转窗口资源,将内容移至屏幕窗口中 |
|
present_pixmap | present_scmd_pixmap | 显示窗口内容 |
|
queue_vblank | present_queue_vblank | 发送vblank请求,等待vblank或pageflip完成 |
|
flush | present_flush | 即时发送队列请求 |
|
re_execute | present_re_execute | 重新执行vblank等操作 |
|
abort_vblank | present_scmd_abort_vblank | 终止翻页 |
|
flip_destroy | present_scmd_flip_destroy | 销毁翻页请求 |
|
默认实现中抽象了与硬件相关的操作,定义在present_screen_info结构中,具体可由硬件驱动实现。默认实现是基于xfree86的EXA架构实现的。X11 Server在加载modesetting驱动,初始化屏幕时调用ms_present_screen_init初始化present_screen_info数据,在加载present扩展时初始化present请求和事件
hooks | modesetting | description |
---|---|---|
get_crtc | ms_present_get_crtc | 获取窗口所在的CRTC |
get_ust_msc | ms_present_get_ust_msc | 获取CRTC当前vblank的MSC和UST |
queue_vblank | ms_present_queue_vblank | 将vblank事件插入队列,vblank事件由msc指定 |
abort_vblank | ms_present_abort_vblank | 取消vblank,从队列中删除定时器 |
flush | ms_present_flush | 即时发送队列请求 |
check_flip | N/A |
|
flip | ms_present_flip | 发送翻转pixmap命令 |
unflip | ms_present_unflip | 取消翻转(pixmap正在翻转或已翻转) |
check_flip2 | ms_present_check_flip | 检查是否支持window的pixmap的翻转 |
present_execute
在收到vblank信号或者执行时间已到时,调用present_execute执行翻页操作,这些操作应该在一个vblank时间内完成,否则会出现撕裂问题。
// crtc_msc:CRTC当前的msc,即framebuffer刷新的次数
// ust:CRTC当前的msc开始的事件,单位为ns,有的硬件ust为32位,present将其转成64位
static void present_execute(present_vblank_ptr vblank, uint64_t ust, uint64_t crtc_msc){
WindowPtr window = vblank->window;
ScreenPtr screen = window->drawable.pScreen;
// 窗口所在屏幕
present_screen_priv_ptr screen_priv = present_screen_priv(screen);
if (vblank && vblank->crtc) {
// CRTC对应屏幕,当窗口从一个显示器切换至另一个显示器时,这两个屏幕不同
screen_priv=present_screen_priv(vblank->crtc->pScreen);
}
// 若vblank的target_msc为crtc_msc+1,则调用queue_vblank注册vblank,结束处理
if (present_execute_wait(vblank, crtc_msc))
return;
if (vblank->flip && vblank->pixmap && vblank->window) {
// vblank需要翻页,但当前有窗口正在翻页或者取消翻页,则该vblank挂起,最终会删除该操作
if (screen_priv->flip_pending || screen_priv->unflip_event_id) {
xorg_list_del(&vblank->event_queue);
xorg_list_append(&vblank->event_queue, &present_flip_queue);
vblank->flip_ready = TRUE;
return;
}
}
将vblank从present_exec_queue或者present_flip_queue列表中删除
xorg_list_del(&vblank->event_queue);
将vblank从其对应窗口的vblank列表中删除
xorg_list_del(&vblank->window_list);
vblank->queued = FALSE;
// 正常操作,需要翻页或赋值
if (vblank->pixmap && vblank->window &&
(vblank->reason < PRESENT_FLIP_REASON_DRIVER_TEARFREE ||
// TearFree导致的暂时不能使用硬件翻页的情况,在将来某个时间可能可以
vblank->exec_msc != vblank->target_msc)) {
if (vblank->flip) {
// 缓冲正翻页的vblank,并加入present_flip_queue列表中
screen_priv->flip_pending = vblank;
xorg_list_add(&vblank->event_queue, &present_flip_queue);
// 发起翻页命令,如:drmModePageFlip
if (present_flip(vblank->crtc, vblank->event_id, vblank->target_msc, vblank->pixmap, vblank->sync_flip)) {
RegionPtr damage;
// 与已翻页的窗口不同(窗口切换了),将已翻页的窗口的pixmap更新为屏幕的pixmap
// 注意:以下都是数据结构更新,而非显示更新
if (screen_priv->flip_window && screen_priv->flip_window != window)
present_set_tree_pixmap(screen_priv->flip_window, screen_priv->flip_pixmap, (*screen->GetScreenPixmap)(screen));
// 将窗口和屏幕根窗口的pixmap更新为待显示的pixmap
present_set_tree_pixmap(vblank->window, NULL, vblank->pixmap);
present_set_tree_pixmap(screen->root, NULL, vblank->pixmap);
// Report update region as damaged
if (vblank->update) {
damage = vblank->update;
RegionIntersect(damage, damage, &window->clipList);
} else
damage = &window->clipList;
DamageDamageRegion(&vblank->window->drawable, damage);
return;
}
// 若是翻页操作失败,则将vblank从present_flip_queue列表中删除
xorg_list_del(&vblank->event_queue);
// 此时间无翻页请求,因此请空flip_pending
screen_priv->flip_pending = NULL;
vblank->flip = FALSE;
// 硬件翻页时,更新了exec_msc,如:TearFree硬件翻页或者同步翻页
vblank->exec_msc = vblank->target_msc;
}
if (screen_priv->flip_pending) {
// vblank指定的窗口正在翻页中,则终止该窗口的翻页,将该窗口和根窗口的pixmap更新为屏幕pixmap
if (window == screen_priv->flip_pending->window)
present_set_abort_flip(screen);
} else if (!screen_priv->unflip_event_id) {
// 该窗口的内容已经翻页了,取消翻页,将该窗口和根窗口的pixmap更新为屏幕pixmap,更新为屏幕pixmap
if (window == screen_priv->flip_window)
present_unflip(screen); // 消翻页,更新为屏幕pixmap
}
// 将pixmap中的内容复制到窗口的pixmap中,更新窗口内容
present_execute_copy(vblank, crtc_msc);
// TareFree翻页,因CRTC正在进行翻页,导致该vblank无法硬件翻页
if (!vblank->queued && vblank->reason >= PRESENT_FLIP_REASON_DRIVER_TEARFREE) {
uint64_t completion_msc = crtc_msc + 1;
// If TearFree is already flipping then the presentation will be visible at the *next* next vblank.
// This calculation only matters or the vblank event fallback.
if (vblank->reason == PRESENT_FLIP_REASON_DRIVER_TEARFREE_FLIPPING && vblank->exec_msc < crtc_msc)
completion_msc++;
// Try the fake flip first and then fall back to a vblank event
if (present_flip(vblank->crtc, vblank->event_id, 0, NULL, TRUE) ||
Success == screen_priv->queue_vblank(screen, window, vblank->crtc, vblank->event_id, completion_msc)) {
// Ensure present_execute_post() runs at the next execution
vblank->exec_msc = vblank->target_msc;
vblank->queued = TRUE;
}
}
if (vblank->queued) {
xorg_list_add(&vblank->event_queue, &present_exec_queue);
xorg_list_append(&vblank->window_list, &present_get_window_priv(window, TRUE)->vblank);
return;
}
}
// 发送PresentCompleteNofity事件
present_execute_post(vblank, ust, crtc_msc);
}
present_event_notify
当调用drmHandleEvent处理时,会响应3个事件:
DRM_EVENT_FLIP_COMPLETE:翻页完成,drmModePageFlip或者drmModePageFlipTarget,亦或drmModeAtomicCommit
DRM_EVENT_VBLANK:vblank信号,响应drmWaitVBlank发出的事件
DRM_EVENT_CRTC_SEQUENCE:响应drmCrtcQueueSequence发出的事件
这些信号处理函数最终由present_event_notify实现。
void present_event_notify(uint64_t event_id, uint64_t ust, uint64_t msc){
present_vblank_ptr vblank;
int s;
if (!event_id)
return;
xorg_list_for_each_entry(vblank, &present_exec_queue, event_queue) {
int64_t match = event_id - vblank->event_id;
if (match == 0) { // vblank时间到,处理该事件传递vblank的窗口显示(翻页或拷贝)
present_execute(vblank, ust, msc);
return;
}
}
xorg_list_for_each_entry(vblank, &present_flip_queue, event_queue) {
if (vblank->event_id == event_id) {
if (vblank->queued)
// 可能是flipe_ready的vblank,执行翻页操作
present_execute(vblank, ust, msc);
else
// 1. 为flip_window触发SyncFence,发送PresentIdleNofity事件
// 2. 完成了翻页操作,更新flip_window为此窗口
// 3. 为此窗口发送PresentCompleteNofity事件
// 4. 调用重新执行因等待翻页而挂起的任务,(flip_ready=true)
present_flip_notify(vblank, ust, msc);
return;
}
}
for (s = 0; s < screenInfo.numScreens; s++) {
ScreenPtr screen = screenInfo.screens[s];
present_screen_priv_ptr screen_priv = present_screen_priv(screen);
// 取消翻页,发送PresentIdleNotify事件通知
if (event_id == screen_priv->unflip_event_id) {
screen_priv->unflip_event_id = 0;
present_flip_idle(screen);
present_flip_try_ready(screen);
return;
}
}
}
wayland平台实现
xwayland的无根模式重新实现了这些回调函数。xwayland调用xwl_present_init初始化回调函数,在加载present扩展时,调用present_extension_init初始化present请求和事件。
xwayland通过xdg-output扩展协议与wayland compositor中的输出设备交互,通过drm-lease协议租赁设备。通过linux-dmabuf实现bo与wl_buffer的关联
mode hooks | xwl | description |
---|---|---|
query_capabilities | xwl_present_query_capabilities | PresentCapabilityAsync | PresentCapabilityAsyncMayTear |
get_crtc | xwl_present_get_crtc | 获取CRTC,返回第一个CRTC |
check_flip | xwl_present_check_flip | 检查是否支持窗口翻转 |
check_flip_window | xwl_present_check_flip_window | 窗口变化后,检查是否支持窗口翻转 |
can_window_flip | N/A |
|
clear_window_flip | xwl_present_clear_window_flip | 清理翻转窗口资源,将内容移至屏幕窗口中 |
present_pixmap | xwl_present_pixmap | 呈现窗口内容,由wayland compositor实现 |
queue_vblank | xwl_present_queue_vblank | 发送vblank请求,由定时器模拟 |
flush | xwl_present_flush |
|
re_execute | xwl_present_re_execute |
|
abort_vblank | xwl_present_abort_vblank |
|
flip_destroy | N/A |
|
xwl_present_flip
翻页操作由wayland compositor最终实现,xwayland通过linux-dmabuf等实现数据零拷贝。具体步骤如下:
调用xwl_glamor_pixmap_get_wl_buffer构建一个临时的wl_buffer,与pixmap中的bo关联
设置wayland释放wl_buffer时的处理函数xwl_present_buffer_release,该函数向X11 client发送IdleNotify事件,通知pixmap可重用
调用wl_surface_attach将wl_buffer与窗口(wl_surface)关联
调用xwl_window_create_frame_callback创建vblank信号处理函数,处理wayland发来的frame事件
调用wl_surface_commit提交请求,等待响应vblank事件
回调frame_callback,处理翻页,发送PresentCompleteNotify事件给client
当wayland销毁wl_buffer时,回调xwl_present_buffer_release,发送PresentIdleNotify事件给client
static Bool xwl_present_flip(present_vblank_ptr vblank, RegionPtr damage){
WindowPtr present_window = vblank->window;
PixmapPtr pixmap = vblank->pixmap;
struct xwl_window *xwl_window = xwl_window_from_window(present_window);
struct xwl_present_window *xwl_present_window = xwl_present_window_priv(present_window);
BoxPtr damage_box;
struct wl_buffer *buffer;
struct xwl_present_event *event = xwl_present_event_from_vblank(vblank);
if (!xwl_window)
return FALSE;
// 根据pixmap中的bo,使用zwp linux-dmabuf扩展协议创建与bo关联的wl_buffer
buffer = xwl_glamor_pixmap_get_wl_buffer(pixmap);
if (!buffer) {
ErrorF("present: Error getting buffer\n");
return FALSE;
}
// 修改区域
damage_box = RegionExtents(damage);
pixmap->refcnt++;
event->pixmap = pixmap;
// 注册wayland释放wl_buffer时的回调函数,触发回调时发送IdleNotify事件,pixmap可重用
xwl_pixmap_set_buffer_release_cb(pixmap, xwl_present_buffer_release, event);
// wl_buffer与窗口(surface)关联
wl_surface_attach(xwl_window->surface, buffer, 0, 0);
if (xorg_list_is_empty(&xwl_present_window->frame_callback_list)) {
xorg_list_add(&xwl_present_window->frame_callback_list, &xwl_window->frame_callback_list);
}
// 未注册vblank信号处理函数,则调用wl_surface_frame注册
if (!xwl_window->frame_callback)
xwl_window_create_frame_callback(xwl_window);
xwl_surface_damage(xwl_window->xwl_screen, xwl_window->surface,
damage_box->x1 - present_window->drawable.x, damage_box->y1 - present_window->drawable.y,
damage_box->x2 - damage_box->x1, damage_box->y2 - damage_box->y1);
if (xwl_window->tearing_control) { // tearing-control协议实现锯齿控制
uint32_t hint;
if (event->async_may_tear) // 立即翻页
hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_ASYNC;
else // 等下一个vblank再翻页
hint = WP_TEARING_CONTROL_V1_PRESENTATION_HINT_VSYNC;
wp_tearing_control_v1_set_presentation_hint(xwl_window->tearing_control, hint);
}
// 提交显示请求
wl_surface_commit(xwl_window->surface);
if (!vblank->sync_flip) {
// 处理翻页事件,利用wayland请求和事件处理顺序,收到该sync事件时,上述请求已执行完成
xwl_present_window->sync_callback = wl_display_sync(xwl_window->xwl_screen->display);
wl_callback_add_listener(xwl_present_window->sync_callback, &xwl_present_sync_listener, &event->vblank);
}
wl_display_flush(xwl_window->xwl_screen->display);
xwl_window->present_flipped = TRUE;
return TRUE;
}
// frame事件处理函数
static void frame_callback(void *data, struct wl_callback *callback, uint32_t time){
struct xwl_window *xwl_window = data;
wl_callback_destroy (xwl_window->frame_callback);
xwl_window->frame_callback = NULL;
if (xwl_window->xwl_screen->present) {
xwl_present_for_each_frame_callback(xwl_window, xwl_present_frame_callback);
// If xwl_window_create_frame_callback was called from xwl_present_frame_callback,
// need to make sure all fallback timers are adjusted correspondingly.
if (xwl_window->frame_callback)
xwl_present_for_each_frame_callback(xwl_window, xwl_present_reset_timer);
}
}
// 单个窗口的frame事件处理
void xwl_present_frame_callback(struct xwl_present_window *xwl_present_window){
xorg_list_del(&xwl_present_window->frame_callback_list);
// 处理翻页事件,发送PresentCompleteNotify事件,并处理下一个vblank请求
xwl_present_msc_bump(xwl_present_window);
// we do not need the timer anymore for this frame, reset it for potentially the next one
xwl_present_reset_timer(xwl_present_window);
}
DRI3渲染
DRI3支持client直接渲染,一般由openegl封装,X11 client调用eglGetPlatformDisplayEXT指定EGL_PLATFORM_X11_EXT或EGL_PLATFORM_XCB_EXT平台获得EGLDisplay,然后再调用eglInitialize初始化EGL环境。
请求
SyncFence的作用:
client发送无返回值的请求的屏障(barrier),client在屏障处(wait)阻塞等待;server收到屏障后唤醒(屏障之前的请求以处理完成)。
client与server间的同步,如:client等待server发送的pixmap idle通知,server先使用SyncFence唤醒等待,再发送通知。
macro | function | description |
---|---|---|
X_DRI3QueryVersion | proc_dri3_query_version | 获取扩展协议版本 |
X_DRI3Open | proc_dri3_open | 申请GPU硬件设备 |
X_DRI3PixmapFromBuffer | proc_dri3_pixmap_from_buffer | 申请创建pixmap,绑定buffer |
X_DRI3BufferFromPixmap | proc_dri3_buffer_from_pixmap | 从pixmap中获取buffer |
X_DRI3FenceFromFD | proc_dri3_fence_from_fd | 创建fence,绑定fd |
X_DRI3FDFromFence | proc_dri3_fd_from_fence | 从fence中获得fd |
xDRI3GetSupportedModifiers | proc_dri3_get_supported_modifiers | 获取支持的修饰符 |
xDRI3PixmapFromBuffers | proc_dri3_pixmap_from_buffers | 申请创建pixmap,绑定buffers,每个buffer是一个颜色平面(多平面) |
xDRI3BuffersFromPixmap | proc_dri3_buffers_from_pixmap | 从pixmap中获得buffers |
xDRI3SetDRMDeviceInUse | N/A |
|
Xorg server对DRI3的实现提供了抽象接口,已支持不同平台的DRI扩展,如xfree86和wayland平台,其接口定义为dri3_screen_info_rec
name | glamor | xwayland |
---|---|---|
open_client | glamor_dri3_open_client | xwl_dri3_open_client |
pixmap_from_fds | glamor_pixmap_from_fds | glamor_pixmap_from_fds |
fd_from_pixmap | glamor_egl_fd_from_pixmap | NULL |
fds_from_pixmap | glamor_egl_fds_from_pixmap | glamor_fds_from_pixmap |
get_formats | glamor_get_formats | xwl_glamor_get_formats |
get_modifiers | glamor_get_modifiers | xwl_glamor_get_modifiers |
get_drawable_modifiers | glamor_get_drawable_modifiers | xwl_glamor_get_drawable_modifiers |
调用流程
当client启动连接X11 server后,调用oader_dri3_open指定window(根窗口),发送DRI3Open请求,server调用proc_dri3_open,根据window选择GPU设备,并打开该设备,然后将句柄返回给client。此时client获得了一个由X11 server打开的GPU设备,称之为display GPU,而client可根据DRM_PRIME环境变量打开指定的GPU,称之为render GPU,若未设定环境变量,则render GPU为display GPU。在分配front buffer时有两种分配方式:
pixmap buffer,且display GPU与render GPU相同,则pixmap buffer共享使用server的buffer,即发送DRI3BufferFromPixmap请求获取buffer
其它方式,包括window buffer等,则由client创建buffer,发送DRI3PixmapFromBuffer请求获得pixmap等。
对于窗口来说,至少需要4个buffer,1个front buffer,3个back buffer,其中包括正在显示的buffer,正在翻页的buffer,正在渲染的buffer等,mesa的egl实现中使用1个front buffer和4个back buffer。
共享client buffer流程
eglBindTexImage的实现会调用loader_dri3_get_buffers获取front buffer和back buffer。对window buffer来说,均使用该流程创建,对于pixmap buffer来说,pixmap只有一个buffer,当client和server使用不同GPU时,采用该流程创建。(dri3_get_buffer)
client调用dri3_alloc_render_buffer分配渲染内存,发送DRI3PixmapFromBuffer请求发送给server绑定pixmap
调用xshmfence_alloc_shm申请内存共享fence句柄,调用xshmfence_map_shm生成一个xshmfence对象
loader_dri_create_image创建image
调用queryImage获得image的句柄,plane等
调用createImageFromFds2创建image的buffer
调用xcb_generate_id生成一个pixmap标识
调用xcb_dri3_pixmap_from_buffer发送DRI3PixmapFromBuffer请求
调用xcb_generate_id生成同步fence标识
调用xcb_dri3_fence_from_fd发送DRI3FenceFromFD请求,将共享内存和同步fence标识传递给server
调用xshmfence_trigger触发xshmfence
server调用proc_dri3_pixmap_from_buffer创建pixmap,并与其标识关联。(glamor_pixmap_from_fds)
调用CreatePixmap创建pixmap
调用gbm_bo_import将fd指定的buffer与bo关联
调用glamor_egl_make_current将display和context进行绑定(eglMakeCurrent)
调用eglCreateImageKHR创建image
调用glamor_create_texture_from_image创建纹理
调用glamor_set_pixmap_type设置pixmap类型为GLAMOR_TEXTURE_DRM
调用glamor_set_pixmap_texture设置pixmap纹理
调用glamor_egl_set_pixmap_image将image与pixmap绑定
server调用proc_dri3_fence_from_fd创建SyncFence资源。
调用SyncCreate创建SyncFence资源
调用miSyncShmCreateFenceFromFd初始化SyncFence资源的回调函数,映射共享内存fence
调用miSyncInitFence初始化回调函数,为一个回调函数链,包括miSyncShmFenceFuncs,以及glamor_sync_fence_set_triggered的SetTriggered回调。注:miSyncShmFenceFuncs覆盖了miSyncFenceFuncs的实现。
调用xshmfence_map_shm关联client的共享内存fence,并将共享内存fence作为SyncFence的私有属性
共享server buffer流程
创建pixmap buffer,且client和server的GPU一致时,使用该流程创建。(dri3_get_pixmap_buffer)
client调用dri3_get_pixmap_buffer,发送DRI3BufferFromPixmap请求获得pixmap buffer
调用xshmfence_alloc_shm申请内存共享fence句柄,调用xshmfence_map_shm生成一个xshmfence对象
调用xcb_dri3_fence_from_fd发送DRI3FenceFromFD请求,将共享内存和同步fence标识传递给server
调用xcb_dri3_buffers_from_pixmap发送DRI3BuffersFromPixmap请求给server
server调用proc_dri3_fence_from_fd创建SyncFence资源(同上节)
server调用proc_dri3_buffers_from_pixmap创建pixmap buffer,并返回buffer句柄给client。(glamor_egl_fds_from_pixmap)
调用gbm_bo_create_with_modifiers创建bo对象
调用CreatePixmap创建一个新的pixmap,用于与bo关联
调用glamor_egl_create_textured_pixmap_from_gbm_bo创建image,关联pixmap,纹理等
调用CopyArea将旧的pixmap内容拷贝至新的pixmap中
调用glamor_egl_exchange_buffers交换新旧pixmap数据,更新了旧pixmap内容
调用gbm_bo_get_fd获得bo buffer的句柄
client调用loader_dri3_create_image根据buffer句柄创建image
翻页
eglSwapBuffers实现了翻页,DRI3中的SyncFence在该实现中有调用,loader_dri3_swap_buffers_msc实现了翻页部分内容,涉及present等扩展。
对于client和server使用的GPU不同的情况下,调用loader_dri3_blit_image将back buffer的数据同步至linear buffer中
调用dri3_fence_reset,client重置xshmfence
调用xcb_present_pixmap将back buffer的pixmap以及其SyncFence作为IdleFence传递给X11 server
server调用proc_present_pixmap处理请求,当完成翻页时,发送PresentCompleteNofity
当pixmap变成idle时,触发SyncFence,并发送PresentIdleNofity事件
调用dri3_flush_present_events处理事件通知,mesa在分配内存过程中,时常调用该函数回收并复用内存。
static void dri3_flush_present_events(struct loader_dri3_drawable *draw) {
if (draw->special_event) {
xcb_generic_event_t *ev;
while ((ev = xcb_poll_for_special_event(draw->conn, draw->special_event)) != NULL) {
xcb_present_generic_event_t *ge = (void *) ev;
if (!dri3_handle_present_event(draw, ge))
break;
}
}
}
static bool dri3_handle_present_event(struct loader_dri3_drawable *draw, xcb_present_generic_event_t *ge){
switch (ge->evtype) {
case XCB_PRESENT_CONFIGURE_NOTIFY: {
xcb_present_configure_notify_event_t *ce = (void *) ge;
if (ce->pixmap_flags & PresentWindowDestroyed) {
free(ge);
return false;
}
draw->width = ce->width;
draw->height = ce->height;
// 调用egl_dri3_set_drawable_size重新设置画布大小。(egl_dri3_vtable)
draw->vtable->set_drawable_size(draw, draw->width, draw->height);
// 调用dri2_invalidate_drawable使纹理失效,(dri2FlushExtension)
draw->ext->flush->invalidate(draw->dri_drawable);
break;
}
case XCB_PRESENT_COMPLETE_NOTIFY: {
xcb_present_complete_notify_event_t *ce = (void *) ge;
// 处理协议的32位的序号,serial为低32位的序号,本地序号保存在sbc交换缓冲计数器中
if (ce->kind == XCB_PRESENT_COMPLETE_KIND_PIXMAP) {
uint64_t recv_sbc = (draw->send_sbc & 0xffffffff00000000LL) | ce->serial;
// Only assume wraparound if that results in exactly the previous SBC + 1,
// otherwise ignore received SBC > sent SBC to avoid calculating bogus target MSC values in loader_dri3_swap_buffers_msc
if (recv_sbc <= draw->send_sbc)
draw->recv_sbc = recv_sbc;
else if (recv_sbc == (draw->recv_sbc + 0x100000001ULL))
draw->recv_sbc = recv_sbc - 0x100000000ULL;
// When moving from flip to copy, we assume that we can allocate in a more optimal way
// if we don't need to cater for the display controller.
if (ce->mode == XCB_PRESENT_COMPLETE_MODE_COPY &&
draw->last_present_mode == XCB_PRESENT_COMPLETE_MODE_FLIP) {
// 硬件翻页功能失效可能使窗口尺寸发生变化导致,尝试重新分配缓冲
for (int b = 0; b < ARRAY_SIZE(draw->buffers); b++) {
if (draw->buffers[b])
draw->buffers[b]->reallocate = true;
}
}
// If the server tells us that our allocation is suboptimal, we reallocate once.
if (ce->mode == XCB_PRESENT_COMPLETE_MODE_SUBOPTIMAL_COPY &&
draw->last_present_mode != ce->mode) {
for (int b = 0; b < ARRAY_SIZE(draw->buffers); b++) {
if (draw->buffers[b])
draw->buffers[b]->reallocate = true;
}
}
draw->last_present_mode = ce->mode;
draw->ust = ce->ust;
draw->msc = ce->msc;
} else if (ce->serial == draw->eid) {
draw->notify_ust = ce->ust;
draw->notify_msc = ce->msc;
}
break;
}
case XCB_PRESENT_EVENT_IDLE_NOTIFY: {
xcb_present_idle_notify_event_t *ie = (void *) ge;
int b;
for (b = 0; b < ARRAY_SIZE(draw->buffers); b++) {
struct loader_dri3_buffer *buf = draw->buffers[b];
if (buf && buf->pixmap == ie->pixmap)
buf->busy = 0; // 标记buffer为空闲,可复用了
}
break;
}
}
free(ge);
return true;
}