接前一篇文章:DRM遇到的实际问题及领悟(2)
前文中的例程能够实现截屏功能。实际上之前笔者手头还有一个网上的经典例程,用于实现屏幕依次显示红、绿、蓝三色,代码如下:
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "xf86drm.h"
#include "xf86drmMode.h"
#define uint32_t unsigned int
struct framebuffer {
uint32_t size;
uint32_t handle;
uint32_t fb_id;
uint32_t *vaddr;
};
static void create_fb(int fd,uint32_t width, uint32_t height, uint32_t color ,struct framebuffer *buf)
{
struct drm_mode_create_dumb create = {};
struct drm_mode_map_dumb map = {};
uint32_t i;
uint32_t fb_id;
create.width = width;
create.height = height;
create.bpp = 32;
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create); //创建显存,返回一个handle
drmModeAddFB(fd, create.width, create.height, 24, 32, create.pitch,create.handle, &fb_id);
map.handle = create.handle;
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map); //显存绑定fd,并根据handle返回offset
//通过offset找到对应的显存(framebuffer)并映射到用户空间
uint32_t *vaddr = mmap(0, create.size, PROT_READ | PROT_WRITE,MAP_SHARED, fd, map.offset);
for (i = 0; i < (create.size / 4); i++)
vaddr[i] = color;
buf->vaddr=vaddr;
buf->handle=create.handle;
buf->size=create.size;
buf->fb_id=fb_id;
return;
}
static void release_fb(int fd, struct framebuffer *buf)
{
struct drm_mode_destroy_dumb destroy = {};
destroy.handle = buf->handle;
drmModeRmFB(fd, buf->fb_id);
munmap(buf->vaddr, buf->size);
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
int main(int argc, char **argv)
{
int fd;
struct framebuffer buf[3];
drmModeConnector *connector;
drmModeRes *resources;
uint32_t conn_id;
uint32_t crtc_id;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC); //打开card0,card0一般绑定HDMI和LVDS
//fd = open("/dev/dri/renderD128", O_RDWR | O_CLOEXEC); //打开card0,card0一般绑定HDMI和LVDS
resources = drmModeGetResources(fd); //获取drmModeRes资源,包含fb、crtc、encoder、connector等
conn_id = resources->connectors[0]; //获取connector id
crtc_id = resources->crtcs[0]; //获取crtc id
printf("conn_id is: %d\n", conn_id);
printf("crtc_id is: %d\n", crtc_id);
//conn_id = resources->connectors[1]; //获取connector id
//crtc_id = resources->crtcs[1]; //获取crtc id
//printf("conn_id is: %d\n", conn_id);
//printf("crtc_id is: %d\n", crtc_id);
connector = drmModeGetConnector(fd, conn_id); //根据connector_id获取connector资源
printf("hdisplay:%d vdisplay:%d\n",connector->modes[0].hdisplay,connector->modes[0].vdisplay);
create_fb(fd,connector->modes[0].hdisplay,connector->modes[0].vdisplay, 0xff0000, &buf[0]); //创建显存和上色
create_fb(fd,connector->modes[0].hdisplay,connector->modes[0].vdisplay, 0x00ff00, &buf[1]);
create_fb(fd,connector->modes[0].hdisplay,connector->modes[0].vdisplay, 0x0000ff, &buf[2]);
drmModeSetCrtc(fd, crtc_id, buf[0].fb_id,
0, 0, &conn_id, 1, &connector->modes[0]); //初始化和设置crtc,对应显存立即刷新
sleep(5);
drmModeSetCrtc(fd, crtc_id, buf[1].fb_id,
0, 0, &conn_id, 1, &connector->modes[0]);
sleep(5);
drmModeSetCrtc(fd, crtc_id, buf[2].fb_id,
0, 0, &conn_id, 1, &connector->modes[0]);
sleep(5);
release_fb(fd, &buf[0]);
release_fb(fd, &buf[1]);
release_fb(fd, &buf[2]);
drmModeFreeConnector(connector);
drmModeFreeResources(resources);
close(fd);
return 0;
}
可以看到,此例程与之前那个有所不同,虽然前边一致,但从操作framebuffer相关的函数开始就不一样了:
之前的例程是使用DRM_IOCTL_GEM_FLINK、DRM_IOCTL_GEM_OPEN、DRM_IOCTL_GEM_CLOSE以及DRM_IOCTL_I915_GEM_MMAP_GTT等相关ioctl();
而此例程是使用DRM_IOCTL_MODE_CREATE_DUMB、DRM_IOCTL_MODE_ADDFB、DRM_IOCTL_MODE_DESTROY_DUMB以及DRM_IOCTL_MODE_MAP_DUMB以及等ioctl()。
并且后一例程只能对于framebuffer进行写入,并不能读取从而实现截屏功能;而前一例程既可以写入、又可以读取framebuffer。
在网上找到了一篇非常好的博客常见Soc平台图形内存管理学习笔记,其中详细说明了以上两种方式及其细节。
以下是其中部分关键内容:
硬件编解码、硬件图像scale等过程,是在专有的硬件单元里进行的,其使用的内存也是专有的内存,这种内存多是SoC中的图形内存。如此方便与硬件加速图形渲染、图像显示、硬件图像加速处理等功能相交互。
上述过程在使用图形内存时,自然需要使用对应的图形内存管理API。常见的图形内存管理API有以下几种:1. DRM
主要是指其中的内存管理部分,包括dumb-buffer和GEM(Graphics Execution Manager)两种类型接口。具体的驱动根据芯片支持情况做实现,并且为用户态提供相应的API。
1.1 dumb-buffer
其较为通用,分配之后可以做映射处理,获取一个用户态的指针,后续可据此在向图形内存中写入数据,但此种方式不能保证图形内存中数据缓存的一致性。其一般使用流程为:
(1)open() drm 设备节点,多是 /dev/dri/card0 等,得到设备操作fd句柄;
(2)ioctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, )创建一个 dumb-buffer,获取buffer句柄。需要指定 宽高/BPP 等参数;
(3)ioctl(fd, DRM_IOCTL_MODE_MAP_DUMB, ) 根据该 dumb-buffer对象句柄得到伪offset;
(4)mmap(, offset) 将该buffer对象映射到用户态,获得一个指针,后续就可以向其中写入数据了(注意,只是写入,不包括读取);
(5)ioctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, ) 销毁 dumb-buffer 对象。
1.2 GEM buffer
除了dumb-buffer所提供的功能外,对于创建和读写操作,厂商多是额外提供专有的API来进行,如此完成读写的同时,可以在其中进行缓存一致性的操作;而且GEM可以为每个Buffer对象分配一个32bit的名字,用于跨进程传递。
以 Intel i915 系列平台为例,其使用流程一般是:
(1)open() drm 设备节点,多是 /dev/dri/card0 等,得到设备操作fd句柄;
(2)ioctl(fd, DRM_IOCTL_I915_GEM_CREATE, struct drm_i915_gem_create) 创建一个 GEM buffer对象,获取其 handle,简单指定尺寸即可;
(3)ioctl(fd, DRM_IOCTL_I915_GEM_PREAD, struct drm_i915_gem_pread)/ioctl(fd, DRM_IOCTL_I915_GEM_PWRITE, struct drm_i915_gem_pwrite)进行读写,可以保证缓存一致性;或ioctl(fd, DRM_IOCTL_I915_GEM_MMAP, struct drm_i915_gem_mmap) 获取一个用户态的映射指针,不保证缓存一致性;
(4)ioctl(fd, DRM_IOCTL_GEM_CLOSE, struct drm_gem_close) 释放刚才的 GEM buffer对象。
获取GEM Buffer对象名,以及跨进程传递流程如下:
(1)ioctl(fd, DRM_IOCTL_GEM_FLINK, struct drm_gem_flink) 获取 GEM Buffer 的名字;
(2)ioctl(fd, DRM_IOCTL_GEM_OPEN, struct drm_gem_open) 根据名字,获取对应 GEM Buffer对象在当前进程内的 handle。
GEM Buffer对象实现是引用计数的,当所有用户态handle全部关闭时,其才被真正释放。
2. ION
ION是Google提出的一套纯粹的图形内存管理API,在现在Android系统中支持的比较广泛。其使用方式和GEM有些类似,也可以跨进程传递Buffer句柄,来将不同的图形任务分散到不同的进程中进行(Android系统实际上就是这么做的)。
其基本使用流程为:(1)open() drm 设备节点,多是 /dev/ion,得到设备操作fd句柄;
(2)ioctl(fd, ION_IOC_ALLOC, struct ion_allocation_data) 分配,返回 struct ion_handle,简单指定尺寸信息即可;
(3)ioctl(fd, ION_IOC_FREE, struct ion_handle_data) 释放;
(4)ioctl(fd, ION_IOC_SHARE, struct ion_fd_data)/ioctl(fd, ION_IOC_MAP, struct ion_fd_data) 返回 ION Buffer对象的 fd 表示。后续可用此 fd 调用 mmap() 获取用户态指针或跨进程传递;
(5)ioctl(fd, ION_IOC_IMPORT, struct ion_fd_data) 根据别处提供的fd,获取本地的 struct ion_handle;
(6)ioctl(fd, ION_IOC_SYNC, struct ion_fd_data) 对 ION Buffer做缓存刷新,保证一致性。
3. 厂商私有的API
3.1 Allwinner A20平台
其硬件编解码单元cedar,在用户态的提供的图形内存的接口就是一套私有的API。具体参见:
https://github.com/allwinner-zh/media-codec/tree/master/sunxi-cedarx/SOURCE/common
3.2 libva
libva是 VA-API的实现,背后依赖各个厂家提供的驱动。VA-API如其名(Video Acceleration),是硬件视频加速处理的一套API规范,并非Intel专有,ADM/Nvidia平台上其实也有相应的支持。libva图形内存管理的工作是用其他组来实现的,比如DRM/GLX/XWindow。
3.3 UMP(Unified Memory Provider)
是ARM平台下一套内存管理机制接口。
由以上1.1和1.2可以看到我们前述两个例程的对应流程。