DRM遇到的实际问题及领悟(3)

接前一篇文章:DRM遇到的实际问题及领悟(2)

四、横向对比

前文中的例程能够实现截屏功能。实际上之前笔者手头还有一个网上的经典例程,用于实现屏幕依次显示红、绿、蓝三色,代码如下:

#include <errno.h>
#include <fcntl.h>
#include <stdbool.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/mman.h>
#include <time.h>
#include <unistd.h>
#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-bufferGEM(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可以看到我们前述两个例程的对应流程。

猜你喜欢

转载自blog.csdn.net/phmatthaus/article/details/133342892