DRM( Direct Rendering Manager)即直接渲染管理器。它是为了解决多个程序对 Video Card 资源的协同使用问题而产生的。它向用户空间提供了一组 API,用以访问操纵 GPU。
简单理解,DRM是Linux下的图形渲染架构,用来管理显示输出和分配buffer。应用程序可以直接操纵 DRM的 ioctl 或者是用 framebuffer 提供的接口进行显示相关操作。后来封装成了 libdrm 库,让用户可以更加方便的进行显示控制。
要弄明白 DRM 是怎么把用户的绘图输出到显示屏上,绕不开以下几个概念,具体关系如下图所示:
它是一块内存区域,可以理解为一块画布,驱动和应用层都能访问它。绘制前需要将它格式化,设定绘制的色彩模式(例如RGB24,YUV 等)和画布的大小(分辨率)。
阴极摄像管上下文。这个看名字很很难懂,但简单的来说他就是显示输出的上下文,可以理解为扫描仪。CRTC对内连接 Framebuffer 地址,对外连接 Encoder,会扫描 Framebuffer 上的内容,叠加上 Planes 的内容,最后传给Encoder。
平面。它和 Framebuffer 一样是内存地址。它的作用是干什么呢?打个比方,在电脑上,一边打字聊微信一边看电影,这里对立出来两个概念,打字是文字交互,是小范围更新的 Graphics 模式;看电影是全幅高速更新的 Video 模式,这两种模式将显卡的使用拉上了两个极端。
这时Planes就发挥了很好的作用,它给 Video 刷新提供了高速通道,使 Video 单独为一个图层,可以叠加在 Graphic 上或之下,并具有缩放等功能。
Planes 是可以有多个的,相当于图层叠加,因此扫描仪(CRTC)扫描的图像实际上往往是 Framebuffer 和 Planes 的组合(Blending)。
编码器。它的作用就是将内存的 pixel 像素编码(转换)为显示器所需要的信号。简单理解就是,如果需要将画面显示到不同的设备(Display Device)上,需要将画面转化为不同的电信号,例如 DVID、VGA、YPbPr、CVBS、Mipi、eDP 等。
Encoder 和 CRTC 之间的交互就是我们所说的 ModeSetting,其中包含了前面提到的色彩模式、还有时序(Timing)等。
连接器。它常常对应于物理连接器 (例如 VGA, DVI, FPD-Link, HDMI, DisplayPort, S-Video等) ,它不是指物理线,在 DRM中,Connector 是一个抽象的数据结构,代表连接的显示设备,从Connector中可以得到当前物理连接的输出设备相关的信息 ,例如,连接状态,EDID数据,DPMS状态、支持的视频模式等。
以rk的image_process.cc为例:
mpp解码->缓存drmdescript buff yuv420sp到缓存链表->rga 缩放合成格式转换->drm dumpbuffer list(通过handle操作)->drm送显(handler操作)
如上buffer操作都是通过硬件操作。
rga会先创建drm buffer来存放转换后的图像数据
AllocJoinBO(drm_fd, rga, jb, w, h)
RkRgaAllocBuffer(drmfd, &bo, w, h, 32)
drmIoctl(drm_fd, DRM_IOCTL_MODE_CREATE_DUMB, &arg);
bo_info->handle = arg.handle;
RkRgaGetBufferFd(&bo, &fd)
//将handler转换成fd以便后面drmModeSetPlane直接使用
drmPrimeHandleToFD(bo_info->fd, bo_info->handle, 0, fd);
接着将多路AVFrame 通过rag进行缩放合成及格式转换
std::shared_ptr<JoinBO> jb = get_available_buffer(frames);//获取空闲的drm framebuffer
scale_frame_to(av_frame, jb.get(), i)
AVDRMFrameDescriptor *desc = (AVDRMFrameDescriptor *)av_frame->data[0];//data应该也是指向drm的某种buffer
AVDRMLayerDescriptor *layer = &desc->layers[0];
rga_info_t src, dst;
src.fd = desc->objects[0].fd;
rga_set_rect(&src.rect, 0, 0, av_frame->width, av_frame->height,
layer->planes[0].pitch,
layer->planes[1].offset / layer->planes[0].pitch,
RK_FORMAT_YCrCb_420_SP); //设置源rect
dst.fd = jb->fd;
Rect &dts_rect = slice_rect[index];
rga_set_rect(&dst.rect, dts_rect.x, dts_rect.y,
dts_rect.w, dts_rect.h,
jb->width, jb->height,
dst_format); //设置目的rect及转换格式dst_format NV12/RGB888...
rga.RkRgaBlit(&src, &dst, NULL); //进行数据转换及拷贝
转换完成后放入h->PushBO(jb);进行零拷贝显示
创建drm framebuffer并初始化drm显示组件
AllocJoinBO(drm_fd, rga, &crtc_jb, cur_mode.hdisplay, cur_mode.vdisplay)
add_fb(drm_fd, &crtc_jb, &crtc_fb_id)
//将drm framebuffer添加到framebuffer从而在drmModeSetCrtc时显示,在这里可以设置显示格式nv12 rgb等
drmModeAddFB2(fd, jb->width, jb->height, fourcc_fmt,
handles, pitches, offsets, fb_id, 0);
int ret = drmModeSetCrtc(dev.fd, crtc_id, crtc_fb_id, 0, 0, &conn_id, 1,
&cur_mode);
从函数参数可以得知需要crtc设备的id,connecter id及drm设备/dev/dri/card0 节点文件描述符。获取方式参考代码实现。
std::shared_ptr<JoinBO> sjb = PopBO(); //取得rga转换后的drm framebuffer
render_jb = sjb;
std::pair<uint32_t, bool> &render_fb = get_fb_id(dev.fd, jb_fb_map, render_jb.get());
add_fb(dmafd, jb, &fb_id)
drmModeAddFB2(fd, jb->width, jb->height, fourcc_fmt,
handles, pitches, offsets, fb_id, 0);
render_fb_id = render_fb.first;//将handler传入
drmModeSetPlane(dev.fd, plane_id, crtc_id, render_fb_id, 0,
0, 0, render_jb->width, render_jb->height,
0, 0, render_jb->width << 16, render_jb->height << 16);
//根据vblank刷新
drmModePageFlip(dev.fd, crtc_id, render_fb_id,
DRM_MODE_PAGE_FLIP_EVENT, &waiting_for_flip);