Android Qcom Display学习(九)

该系列文章总目录链接与各部分简介: 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(原子的) 接口。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值