该系列文章总目录链接与各部分简介: Android Qcom Display学习(零)
最简单的DRM应用程序 (single-buffer)- 学习drm看这个大神的就行
DRM驱动支持MODESET;
DRM驱动支持dumb-buffer(即连续物理内存);
DRM驱动至少支持1个CRTC,1个Encoder,1个Connector;
DRM驱动的Connector至少包含1个有效的drm_display_mode
先基于博主的最简单的DRM应用程序进行学习实现,最进阶,主要分为以下几个步骤
(1)调用 drmModeGetResources():获取 crtc_id 和 connector_id
drivers/gpu/drm/drm_iotcl.c
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETRESOURCES, drm_mode_getresources, DRM_UNLOCKED),
获取可支持 min/max width + height connector num = 1 encoder num = 1(android通常配置)
(2)调用 drmModeGetConnector():获取 drm_mode
drivers/gpu/drm/drm_iotcl.c
DRM_IOCTL_DEF(DRM_IOCTL_MODE_GETCONNECTOR, drm_mode_getconnector, DRM_UNLOCKED),
一个crtc可以对应多个encoder,encoder与connector一般都绑定在一起,
多个encoder对应对个connector对应多个lcd,用于多屏幕显示(primary secondary dsi display)
typedef struct _drmModeModeInfo {
uint32_t clock;
uint16_t hdisplay, hsync_start, hsync_end, htotal, hskew;
uint16_t vdisplay, vsync_start, vsync_end, vtotal, vscan;
uint32_t vrefresh;
uint32_t flags;
uint32_t type;
char name[DRM_DISPLAY_MODE_LEN];
} drmModeModeInfo, *drmModeModeInfoPtr;
(3)调用 drmIoctl+DRM_IOCTL_MODE_CREATE_DUMB: 创建dumb-buffer
techpack/display/msm/msm_gem.c + msm_drv.c
.dumb_create = msm_gem_dumb_create,
创建buffer的三要素:width宽 height高 bpp每个像素占用的bit数
构建GEM显存管理的buffer对象以及用于用户空间的handle
返回的两个对象: pitch每一行图像数据的字节数 size图像大小
pitch(snapdragon adreno need aligned to 32 pixel)
static inline int align_pitch(int width, int bpp)
{
int bytespp = (bpp + 7) / 8;
return bytespp * ALIGN(width, 32);
}
宽的单位是像素,pitch的像素是字节
width = 1080 height = 2160 bpp = 32
pitch = 1080 * 4 = 4320 -> 32bit对齐 -> 4352
size = pitch * height = 4352 * 2160 = 9400320
(4)调用 drmModeAddFB():获取fb_id,并绑定handle对应的dumb-buffer
drmModeAddFB(int fd, uint32_t width, uint32_t height, uint8_t depth,
uint8_t bpp, uint32_t pitch, uint32_t bo_handle, uint32_t *buf_id)
drivers/gpu/drm/drm_iotcl.c
DRM_IOCTL_DEF(DRM_IOCTL_MODE_ADDFB, drm_mode_addfb_ioctl, DRM_UNLOCKED),
pixel_format: bpp + depth 可以看到显示通常是使用RGB 并且是32bit的RGB888
switch(bpp) {
case 8:
fmt = DRM_FORMAT_C8;
break;
case 16:
if (depth == 15)
fmt = DRM_FORMAT_XRGB1555;
else
fmt = DRM_FORMAT_RGB565;
break;
case 24:
fmt = DRM_FORMAT_RGB888;
break;
case 32:
if (depth == 24)
fmt = DRM_FORMAT_XRGB8888;
else if (depth == 30)
fmt = DRM_FORMAT_XRGB2101010;
else
fmt = DRM_FORMAT_ARGB8888;
break;
default:
DRM_ERROR("bad bpp, assuming x8r8g8b8 pixel format\n");
fmt = DRM_FORMAT_XRGB8888;
break;
}
(5)调用 drmIoctl(DRM_IOCTL_MODE_MAP_DUMB)+mmap: 返回虚拟地址,用于绘制图像
获取map的offset地址后,通过mmap.offset将虚拟地址映射到当前进程中
(6)调用 drmModeSetCrtc():显示Framebuffer,在整体pipepline实现
RGB888像素点颜色bit分布
高位 低位
B(8 bit) G(8bit) R(8bit) reserverd(8bit)
黄色 R(255) G(255) B(0)
addr+3 B = 0xff
addr+2 G = 0xff
addr+1 R = 0x00
addr+0 reserved
Demo Code
由于DRM框架下只有一个master存在,起初以为是surfacefinger占用了,于是杀surfacefinger或者删除了system/etc/init/surfaceflinger.rc,再运行drm_test发现并没有效果,仔细考虑了一下,surfacefinger主要负责plane,drm的相关操作应该都是hwcomposer负责,于是删除了
vendor/etc/init/vendor.qti.hardware.display.composer-service.rc ,后再运行脚本效果就出来了
Android.bp
cc_binary {
name: "drm_test",
srcs: ["main.cpp"],
shared_libs: [
"libutils",
"libcutils",
"liblog",
"libdrm",
],
}
drm_test
#include <fcntl.h>
#include <string.h>
#include <utils/Log.h>
#include <sys/mman.h>
#include <xf86drm.h>
#include <xf86drmMode.h>
struct buffer_object {
uint32_t width;
uint32_t height;
uint32_t pitch;
uint32_t handle;
uint32_t size;
uint8_t *vaddr;
uint32_t fb_id;
};
struct buffer_object buf;
static int modeset_create_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_create_dumb create = {};
//set up for mmap of a dumb scanout buffer
struct drm_mode_map_dumb map = {};
/* create a dumb-buffer, the pixel format is XRGB888 */
create.width = bo->width;
create.height = bo->height;
create.bpp = 32;
//3.
drmIoctl(fd, DRM_IOCTL_MODE_CREATE_DUMB, &create);
/* bind the dumb-buffer to an FB object */
bo->pitch = create.pitch;
bo->size = create.size;
bo->handle = create.handle;
//4.
drmModeAddFB(fd, bo->width, bo->height, 24, 32, bo->pitch,
bo->handle, &bo->fb_id);
ALOGD("jerry createfb pitch = %d , size = %d", bo->pitch, bo->size);
/* map the dumb-buffer to userspace */
map.handle = create.handle;
//5.
drmIoctl(fd, DRM_IOCTL_MODE_MAP_DUMB, &map);
ALOGD("jerry map.offset = %llu", map.offset);
bo->vaddr = (uint8_t *) mmap(0, create.size, PROT_READ | PROT_WRITE,
MAP_SHARED, fd, map.offset);
/* initialize the dumb-buffer with white-color */
memset(bo->vaddr, 0xff, bo->size);
for(int j = 0; j< bo->height/2; j++){
for(int i = 0; i< bo->pitch; i+=4){
memset(bo->vaddr+0+i+(j*bo->pitch), 0x00,sizeof(uint8_t)); //reserved
memset(bo->vaddr+1+i+(j*bo->pitch), 0xFF,sizeof(uint8_t)); //R
memset(bo->vaddr+2+i+(j*bo->pitch), 0xFF,sizeof(uint8_t)); //G
memset(bo->vaddr+3+i+(j*bo->pitch), 0x00,sizeof(uint8_t)); //B
//指针+1要看指向的数据类型
}
}
return 0;
}
static void modeset_destroy_fb(int fd, struct buffer_object *bo)
{
struct drm_mode_destroy_dumb destroy = {};
drmModeRmFB(fd, bo->fb_id);
munmap(bo->vaddr, bo->size);
destroy.handle = bo->handle;
drmIoctl(fd, DRM_IOCTL_MODE_DESTROY_DUMB, &destroy);
}
int main()
{
int fd;
drmModeConnector *conn;
drmModeRes *res;
uint32_t conn_id;
uint32_t crtc_id;
fd = open("/dev/dri/card0", O_RDWR | O_CLOEXEC);
if(fd < 0 )
ALOGE("jerry open card0 fail");
//1.
res = drmModeGetResources(fd);
crtc_id = res->crtcs[0];
conn_id = res->connectors[0];
ALOGD("jerry resource connecotr.count = %d, encoders.count = %d", res->count_connectors, res->count_encoders);
ALOGD("jerry resource minw = %d maxw = %d minh = %d maxh = %d", res->min_width ,res->max_width ,res->min_height ,res->max_height );
//2.
conn = drmModeGetConnector(fd, conn_id);
buf.width = conn->modes[0].hdisplay; //hsync_start hsync_end htotal hskew
buf.height = conn->modes[0].vdisplay; //vsync_start vsync_end vtotal vscan
ALOGD("jerry connecotr width = %d, height = %d, vrefresh = %d encoder_count = %d", buf.width, buf.height, conn->modes[0].vrefresh, conn->count_encoders);
//3..
modeset_create_fb(fd, &buf);
//6.
drmModeSetCrtc(fd, crtc_id, buf.fb_id,
0, 0, &conn_id, 1, &conn->modes[0]);
sleep(3);
modeset_destroy_fb(fd, &buf);
drmModeFreeConnector(conn);
drmModeFreeResources(res);
close(fd);
return 0;
}
Other API
drmModePageFlip():
drmModePageFlip()只会等到VSYNC到来后才会真正执行framebuffer切换动作,drmModeSetCrtc()则会立即执行framebuffer切换动作。
所以drmModeSetCrtc()对于某些硬件来说,很容易造成撕裂(tear effect)问题。Vsync 60HZ = 16.7ms Display Controller固定时间上报
第一次还是需要调用drmModeSetCrtc(),它能完成整体显示流程pipeline的初始化,后续再通过drmModePageFlip显示内容
qcom处理Vsync event的位置/hardware/qcom/display/sdm/libs/core/drm/hw_events_drm.cpp
drmEventContext event = {};
event.version = DRM_EVENT_CONTEXT_VERSION;
event.vblank_handler = &HWEventsDRM::VSyncHandlerCallback;
Android帧的产生(2)——FPS、Vsync和Triple Buffer
由于图像绘制和屏幕读取使用的同一个buffer,所以屏幕刷新的时候可能读取到的是不完整的一帧画面,造成撕裂问题,Android使用了Vsync+Double Buffer技术。让图像绘制和屏幕拥有的各自的buffer,cpu/gpu始终将完成的一帧图像写入到Back Buffer,显示器读取使用Frame Buffer,当屏幕刷新时,Frame Buffer并不会发生变化,当Back Buffer准备就绪时才进行两个buffer交换。VSync则是用于负责调度从Back Buffer到Frame Buffer的复制操作,认为该复制操作在瞬间完成,因为实际上双缓冲的实现方式是交换Back Buffer和Frame Buffer的内存地址。
当然这也存在一个问题,屏幕的VSync信号只是用来控制帧缓冲区的切换,并未控制上层的绘制节奏,CPU没有立刻开始准备数据,绘制还未完成,导致没有交换数据,显示还是上一帧,画面卡顿发生Jank的情况。于是为了解决CPU和GPU没能在下一个VSync信号到来之前完成下一帧的绘制工作,引入了Triple Buffer技术,CPU、GPU、Display各占一个buffer来处理。
帧率代表GPU 在一秒内绘制操作的帧数,单位fps。刷新率代表屏幕在一秒内刷新屏幕的次数单位, 60Hz。
drmModeSetPlane()
因为如果不设置,drmModeGetPlaneResources()就只会返回Overlay Plane,其他Plane都不会返回。
而如果设置了,DRM驱动则会返回所有支持的Plane资源,包括cursor、overlay和primary。
以上的接口都是Legacy(过时的) 接口了,而目前DRM主要推荐使用的是 Atomic(原子的) 接口。