转至元数据结尾
转至元数据起始

窗口显示以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个事件:

  1. DRM_EVENT_FLIP_COMPLETE:翻页完成,drmModePageFlip或者drmModePageFlipTarget,亦或drmModeAtomicCommit

  2. DRM_EVENT_VBLANK:vblank信号,响应drmWaitVBlank发出的事件

  3. 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等实现数据零拷贝。具体步骤如下:

  1. 调用xwl_glamor_pixmap_get_wl_buffer构建一个临时的wl_buffer,与pixmap中的bo关联

  2. 设置wayland释放wl_buffer时的处理函数xwl_present_buffer_release,该函数向X11 client发送IdleNotify事件,通知pixmap可重用

  3. 调用wl_surface_attach将wl_buffer与窗口(wl_surface)关联

  4. 调用xwl_window_create_frame_callback创建vblank信号处理函数,处理wayland发来的frame事件

  5. 调用wl_surface_commit提交请求,等待响应vblank事件

  6. 回调frame_callback,处理翻页,发送PresentCompleteNotify事件给client

  7. 当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的作用:

  1. client发送无返回值的请求的屏障(barrier),client在屏障处(wait)阻塞等待;server收到屏障后唤醒(屏障之前的请求以处理完成)。

  2. 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时有两种分配方式:

  1. pixmap buffer,且display GPU与render GPU相同,则pixmap buffer共享使用server的buffer,即发送DRI3BufferFromPixmap请求获取buffer

  2. 其它方式,包括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)

  1. client调用dri3_alloc_render_buffer分配渲染内存,发送DRI3PixmapFromBuffer请求发送给server绑定pixmap

    1. 调用xshmfence_alloc_shm申请内存共享fence句柄,调用xshmfence_map_shm生成一个xshmfence对象

    2. loader_dri_create_image创建image

    3. 调用queryImage获得image的句柄,plane等

    4. 调用createImageFromFds2创建image的buffer

    5. 调用xcb_generate_id生成一个pixmap标识

    6. 调用xcb_dri3_pixmap_from_buffer发送DRI3PixmapFromBuffer请求

    7. 调用xcb_generate_id生成同步fence标识

    8. 调用xcb_dri3_fence_from_fd发送DRI3FenceFromFD请求,将共享内存和同步fence标识传递给server

    9. 调用xshmfence_trigger触发xshmfence

  2. server调用proc_dri3_pixmap_from_buffer创建pixmap,并与其标识关联。(glamor_pixmap_from_fds)

    1. 调用CreatePixmap创建pixmap

    2. 调用gbm_bo_import将fd指定的buffer与bo关联

    3. 调用glamor_egl_make_current将display和context进行绑定(eglMakeCurrent)

    4. 调用eglCreateImageKHR创建image

    5. 调用glamor_create_texture_from_image创建纹理

    6. 调用glamor_set_pixmap_type设置pixmap类型为GLAMOR_TEXTURE_DRM

    7. 调用glamor_set_pixmap_texture设置pixmap纹理

    8. 调用glamor_egl_set_pixmap_image将image与pixmap绑定

  3. server调用proc_dri3_fence_from_fd创建SyncFence资源。

    1. 调用SyncCreate创建SyncFence资源

    2. 调用miSyncShmCreateFenceFromFd初始化SyncFence资源的回调函数,映射共享内存fence

      1. 调用miSyncInitFence初始化回调函数,为一个回调函数链,包括miSyncShmFenceFuncs,以及glamor_sync_fence_set_triggered的SetTriggered回调。注:miSyncShmFenceFuncs覆盖了miSyncFenceFuncs的实现。

      2. 调用xshmfence_map_shm关联client的共享内存fence,并将共享内存fence作为SyncFence的私有属性

共享server buffer流程

创建pixmap buffer,且client和server的GPU一致时,使用该流程创建。(dri3_get_pixmap_buffer)

  1. client调用dri3_get_pixmap_buffer,发送DRI3BufferFromPixmap请求获得pixmap buffer

    1. 调用xshmfence_alloc_shm申请内存共享fence句柄,调用xshmfence_map_shm生成一个xshmfence对象

    2. 调用xcb_dri3_fence_from_fd发送DRI3FenceFromFD请求,将共享内存和同步fence标识传递给server

    3. 调用xcb_dri3_buffers_from_pixmap发送DRI3BuffersFromPixmap请求给server

  2. server调用proc_dri3_fence_from_fd创建SyncFence资源(同上节)

  3. server调用proc_dri3_buffers_from_pixmap创建pixmap buffer,并返回buffer句柄给client。(glamor_egl_fds_from_pixmap)

    1. 调用gbm_bo_create_with_modifiers创建bo对象

    2. 调用CreatePixmap创建一个新的pixmap,用于与bo关联

    3. 调用glamor_egl_create_textured_pixmap_from_gbm_bo创建image,关联pixmap,纹理等

    4. 调用CopyArea将旧的pixmap内容拷贝至新的pixmap中

    5. 调用glamor_egl_exchange_buffers交换新旧pixmap数据,更新了旧pixmap内容

    6. 调用gbm_bo_get_fd获得bo buffer的句柄

  4. client调用loader_dri3_create_image根据buffer句柄创建image

翻页

eglSwapBuffers实现了翻页,DRI3中的SyncFence在该实现中有调用,loader_dri3_swap_buffers_msc实现了翻页部分内容,涉及present等扩展。

  1. 对于client和server使用的GPU不同的情况下,调用loader_dri3_blit_image将back buffer的数据同步至linear buffer中

  2. 调用dri3_fence_reset,client重置xshmfence

  3. 调用xcb_present_pixmap将back buffer的pixmap以及其SyncFence作为IdleFence传递给X11 server

  4. server调用proc_present_pixmap处理请求,当完成翻页时,发送PresentCompleteNofity

  5. 当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;
}