mesa中的winsys层

winsys有什么用

winsys像一个桥梁,它要将GPU渲染的结果传输到窗口系统的framebuffer,由显示系统将其呈现在屏幕上。

mesa中的实现

mesa中winsys有两种实现: 一种是基于dri的,另一种是不基于dri的。两者的主要区别在以下几点:

  • winsys提供的FBO(上图中的framebuffer)的来源

基于dri

  • amdgpu_winsys
  • radeon_winsys
  • nouveau_winsys

基于dri的winsys的实现代码在src/gallium/frontends/dri目录

基于dri winsys的gallium driver的加载

所有使用基于dri的winsys的gallium drivers都会利用下面的宏声明和定义一个函数符号(除了swrast)

1
2
3
4
5
6
7
#define DEFINE_LOADER_DRM_ENTRYPOINT(drivername)                          \
const __DRIextension **__driDriverGetExtensions_##drivername(void); \
PUBLIC const __DRIextension **__driDriverGetExtensions_##drivername(void) \
{ \
globalDriverAPI = &galliumdrm_driver_api; \
return galliumdrm_driver_extensions; \
}

例如,const __DRIextension **__driDriverGetExtensions_r600(void);. 你可以在相应的动态库里查找到该符号。
上面宏中的galliumdrm_driver_apigalliumdrm_driver_extensions都是全局的。

  • galliumdrm_driver_api
1
2
3
4
5
6
7
8
9
10
11
12
13
const struct __DriverAPIRec galliumdrm_driver_api = {
.InitScreen = dri2_init_screen,
.DestroyScreen = dri_destroy_screen,
.CreateContext = dri_create_context,
.DestroyContext = dri_destroy_context,
.CreateBuffer = dri2_create_buffer,
.DestroyBuffer = dri_destroy_buffer,
.MakeCurrent = dri_make_current,
.UnbindContext = dri_unbind_context,

.AllocateBuffer = dri2_allocate_buffer,
.ReleaseBuffer = dri2_release_buffer,
};
  • galliumdrm_driver_extensions
1
2
3
4
5
6
7
8
/* This is the table of extensions that the loader will dlsym() for. */
const __DRIextension *galliumdrm_driver_extensions[] = {
&driCoreExtension.base,
&driImageDriverExtension.base,
&driDRI2Extension.base,
&gallium_config_options.base,
NULL
};

这里出现的4个数组元素都是__DRIextension的子类,同时他们都是全局的。例如driImageDriverExtension被定义在src/mesa/drivers/dri/common/dri_util.c文件中:

1
2
3
4
5
6
7
8
9
/** Image driver interface */
const __DRIimageDriverExtension driImageDriverExtension = {
.base = { __DRI_IMAGE_DRIVER, 1 },

.createNewScreen2 = driCreateNewScreen2,
.createNewDrawable = driCreateNewDrawable,
.getAPIMask = driGetAPIMask,
.createContextAttribs = driCreateContextAttribs,
};

除了这个driImageDriverExtension,基于dri的winsys还定义了一个与__DRIimage有关的__DRIextension:

1
2
3
4
5
6
7
8
9
/* The image loader extension record for DRI3
*/
static const __DRIimageLoaderExtension imageLoaderExtension = {
.base = { __DRI_IMAGE_LOADER, 3 },

.getBuffers = loader_dri3_get_buffers,
.flushFrontBuffer = dri3_flush_front_buffer,
.flushSwapBuffers = dri3_flush_swap_buffers,
};

这个所谓的image loader extension是在多GPU的场景下会用到,也就是当系统中同时存在两个GPU时,可以通过设置mesa提供的环境变量DRI_PRIME,启用PRIME模式,此时其中一个GPU作为Display GPU(server GPU), 用做X11显示,另外一个GPU作为Render GPU, 用做3D渲染,当我们申请__DRI_IMAGE_BUFFER_FRONT时,我们不能直接使用pixmap,
因为所有pixmaps都是被server GPU所有,这种pixmap的格式可能不能被render GPU所理解。这种情况下,在loader_dri3_get_buffers中,会在render GPU的VRAM里创建一个Fake Front Buffer, 最后dri3_flush_front_buffer会将Fake Front Buffer的内容同步到真正的Front Buffer,即display GPU的pixmap里。

  • xcb_dri3_pixmap_from_buffer

这个函数是由c_client.py自动生成的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
xcb_void_cookie_t
xcb_dri3_pixmap_from_buffer (xcb_connection_t *c,
xcb_pixmap_t pixmap,
xcb_drawable_t drawable,
uint32_t size,
uint16_t width,
uint16_t height,
uint16_t stride,
uint8_t depth,
uint8_t bpp,
int32_t pixmap_fd)
{
static const xcb_protocol_request_t xcb_req = {
.count = 2,
.ext = &xcb_dri3_id,
.opcode = XCB_DRI3_PIXMAP_FROM_BUFFER,
.isvoid = 1
};

struct iovec xcb_parts[4];
xcb_void_cookie_t xcb_ret;
xcb_dri3_pixmap_from_buffer_request_t xcb_out;
int fds[1];
int fd_index = 0;

xcb_out.pixmap = pixmap;
xcb_out.drawable = drawable;
xcb_out.size = size;
xcb_out.width = width;
xcb_out.height = height;
xcb_out.stride = stride;
xcb_out.depth = depth;
xcb_out.bpp = bpp;

xcb_parts[2].iov_base = (char *) &xcb_out;
xcb_parts[2].iov_len = sizeof(xcb_out);
xcb_parts[3].iov_base = 0;
xcb_parts[3].iov_len = -xcb_parts[2].iov_len & 3;

fds[fd_index++] = pixmap_fd;
xcb_ret.sequence = xcb_send_request_with_fds(c, 0, xcb_parts + 2, &xcb_req, 1, fds);
return xcb_ret;
}

而X Server收到这个request后的处理函数如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
int
dri3_pixmap_from_fds(PixmapPtr *ppixmap, ScreenPtr screen,
CARD8 num_fds, const int *fds,
CARD16 width, CARD16 height,
const CARD32 *strides, const CARD32 *offsets,
CARD8 depth, CARD8 bpp, CARD64 modifier)
{
dri3_screen_priv_ptr ds = dri3_screen_priv(screen);
const dri3_screen_info_rec *info = ds->info;
PixmapPtr pixmap;

if (!info)
return BadImplementation;

if (info->version >= 2 && info->pixmap_from_fds != NULL) {
pixmap = (*info->pixmap_from_fds) (screen, num_fds, fds, width, height,
strides, offsets, depth, bpp, modifier);
} else if (info->pixmap_from_fd != NULL && num_fds == 1) {
pixmap = (*info->pixmap_from_fd) (screen, fds[0], width, height,
strides[0], depth, bpp);
} else {
return BadImplementation;
}

if (!pixmap)
return BadAlloc;

*ppixmap = pixmap;
return Success;
}

可以看到最终是调用了pixmap_from_fdspixmap_from_fd, 接下来的工作由display GPU的kmd完成,最后当display GPU gbm_bo_import调用了drmPrimeFDToHandle函数后,X Server进程进入内核态,接下来内核DRM子系统的ioctl会调用drm_prime_fd_to_handle_ioctl, 它会调用display GPU的prime_fd_to_handle callback. 整个过程其实是将显存(buffer, 确切说是back buffer)抽象成dma-buf(dma-buf实际上是一个文件,所以它有fd, 可供在进程间传递)来实现用户应用进程与X Server进程间的Buffer共享。

下面是内核函数dma_buf_get的实现,从这个小函数的实现我们可以清楚地看到fd -> file -> dma_buf的转换

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
/**
* dma_buf_get - returns the struct dma_buf related to an fd
* @fd: [in] fd associated with the struct dma_buf to be returned
*
* On success, returns the struct dma_buf associated with an fd; uses
* file's refcounting done by fget to increase refcount. returns ERR_PTR
* otherwise.
*/
struct dma_buf *dma_buf_get(int fd)
{
struct file *file;

file = fget(fd);

if (!file)
return ERR_PTR(-EBADF);

if (!is_dma_buf_file(file)) {
fput(file);
return ERR_PTR(-EINVAL);
}

return file->private_data;
}
EXPORT_SYMBOL_GPL(dma_buf_get);

基于dri winsys的gallium driver的winsys实现

winsys是一个桥梁,它主要要实现的就是将color buffer传输到窗口系统的framebuffer.那么基于dri的winsys是怎么实现这个桥梁的? 它主要依赖下面两个对象:

  • DRIimage
    • __DRIimageRec
    • __DRIimageExtensionRec

DRIimage通过调用pipe_screen.resource_create函数创建,创建后还需要queryImage一些信息,这些信息为X11所需要。

  • PIPE_RESOURCE_PARAM_NPLANES
  • PIPE_RESOURCE_PARAM_STRIDE
  • PIPE_RESOURCE_PARAM_OFFSET
  • PIPE_RESOURCE_PARAM_MODIFIER
  • PIPE_RESOURCE_PARAM_HANDLE_TYPE_FD

PIPE_RESOURCE_PARAM_HANDLE_TYPE_FD通过向kernel发送下面请求,由一个gem handle返回一个file descriptor. 有了这个FD, 不同的进程间就可以共享内存。比方,当xserver获取到这个FD后,它可以通过DRM_IOCTL_PRIME_FD_TO_HANDLE又重新获取到gem bo的handle.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
int
panfrost_bo_export(struct panfrost_bo *bo)
{
struct drm_prime_handle args = {
.handle = bo->gem_handle,
.flags = DRM_CLOEXEC,
};

int ret = drmIoctl(bo->dev->fd, DRM_IOCTL_PRIME_HANDLE_TO_FD, &args);
if (ret == -1)
return -1;

bo->flags |= PAN_BO_SHARED;
return args.fd;
}
  • X11 present extension

非dri

  • sw_winsys

非dri的winsys的实现代码在src/gallium/winsys/sw/xlib目录

不同的winsys实际上是不同的接口(函数),下面以sw_winsys为例说明一下mesa里的winsys的接口与实现。

接口 实现 功能
void
(*destroy)(struct sw_winsys *ws);
xlib_displaytarget_destroy 销毁这个winsys