6.3.7. 竖屏横用、横屏竖用

ZX 平台支持屏幕旋转,支持竖屏横用和横屏竖用,兼容单 buffer 和双 buffer 的应用程序。

display engine 并不提供旋转功能,旋转是由显示驱动调用 graphics engine 实现的。支持顺时针 0°/90°/180°/270° 旋转。

6.3.7.1. LCD 适配

无论是竖屏横用,还是横屏竖用,在适配 LCD 屏幕时,按照屏幕的真实宽高配置 display-timings 节点即可,不需要在屏端进行旋转。

LCD 屏幕适配可参考屏适配指南章节。

6.3.7.2. device tree

屏幕旋转的 device tree 配置集中在 fb0 节点。以下是一块 720x1280 的屏幕竖屏横用的配置例程。

&fb0 {
    zx,uboot-logo-on=<1>;
    rotation-degress = <270>;
    height-virtual = <1440>;  // 720 * 2

    port {
        fb0_out: endpoint {
            remote-endpoint = <&de0_in>;
        };
    };
};

Required properties:

  • rotation-degress

    framebuffer 旋转角度,支持 0°/90°/180°/270° 旋转。

Optional properties:

  • height-virtual

    framebuffer 的内存高度。在竖屏横用或横屏竖用时,也表示绘制 buf 的内存高度。

  • disp-buf-num

    显示 buffer 个数,可通过使用双 buffer 来避免撕裂。

小技巧

如果不设置 disp-buf-num 属性,显示 buffer 个数跟 height-virtual 绑定。

  • height-virtual 不设置,height-virtual 缺省等于 height,此时显示 buffer 个数为 1

  • height-virtual 设置为 height 的两倍,启用双 buffer 时,显示 buffer 个数为 2

6.3.7.3. 实现原理

在进行屏幕旋转时,显示驱动底层管理着两种 buffer

  • 上层 GUI 应用使用的绘制 buffer

  • display engine 使用的显示 buffer

竖屏横用 为例,底层的内存使用情况如下所示:

+--------------------+
|                    |
|      绘制 buf      |
|                    |
+---------+----------+
|         |
|         |
|         |
|显示 buf |
|         |
|         |
+---------+

这两块 buf 在底层是一大块物理连续内存,对应 /dev/fb0。只是横屏和竖屏的 stride 不同,才会有不同的内存布局。

GUI 以横屏的方式在绘制 buf 中绘制好界面,然后调用 pan_display ioctl。

如果配置了屏幕旋转,底层显示驱动在接收到 pan_display ioctl 后会调用 graphics engine,将横屏的界面旋转为竖屏,并把数据 bitblt 到显示 buf 中。

然后 display engine 将显示 buf 中的数据传送给 LCD 屏幕。

小技巧

对于 GUI 应用来说,它通过标准接口获取到的是横屏的参数。它以为此时外接了一块横屏,就会以横屏的方式绘制好界面。 在编写应用程序时要注意,通过标准接口获得的参数与屏幕的实际宽高是相反的。

此外,GUI 只能操作到绘制 buf ,显示 buf 对 GUI 来说是透明的。

横屏竖用的原理类似。

6.3.7.4. 场景选择

ZX 平台在进行屏幕旋转时,兼容单 buffer 和双 buffer 的应用程序,也可指定使用单显示 buffer 还是双显示 buffer。

以下以一块 720x1280 的屏幕竖屏横用为例。

6.3.7.4.1. 双绘制 buffer 和双显示 buffer

&fb0 {
    zx,uboot-logo-on=<1>;
    rotation-degress = <270>;
    height-virtual = <1440>;  // 720 * 2

    port {
        fb0_out: endpoint {
            remote-endpoint = <&de0_in>;
        };
    };
};

备注

该场景设置需要旋转角度和内存高度。

height-virtual 是横屏的高度 * 2,GUI 可以用双 buffer 的方式绘制界面,显示 buf 会自动适配为 2 个。

其底层的内存使用情况如下所示:

+-------------------------+
|      绘制 buf 0         |
+-------------------------+
|      绘制 buf 1         |
+-----+-------------------+
|     |
||
||
| buf |
| 0   |
|     |
+-----+
|     |
||
||
| buf |
| 1   |
|     |
+-----+

GUI 在绘制 buf0 绘制完界面后,显示驱动会把数据旋转到显示 buf0 进行显示,绘制 buf1 同理。

这种使用方式的优缺点如下:

  • 优势

    对应用非常友好,不需要修改上层应用,只修改 dts 配置即可达成竖屏横用。

  • 劣势

    占用的内存资源多,需要 4 块 buf,有一块绘制 buf 是可以节省下来的。720x1280 的屏幕显示 32 位 RGB 数据时,需要约 14M 的物理连续内存。

备注

占用的内存资源来源于 CMA 内存,需要确保 CMA 设定满足内存需求,不然无法生成 /dev/fb0,详细配置可参看参数配置章节。

6.3.7.4.2. 单绘制 buffer 和双显示 buffer

&fb0 {
    zx,uboot-logo-on=<1>;
    rotation-degress = <270>;
    disp-buf-num = <2>;

    port {
        fb0_out: endpoint {
            remote-endpoint = <&de0_in>;
        };
    };
};

备注

该场景只有1块 buf, 不需要设置 height-virtual ,但需要指定 disp-buf-num。

其底层的内存使用情况如下所示:

+-------------------------+
|      绘制 buf 0         |
+-----+-------------------+
|     |
||
||
| buf |
| 0   |
|     |
+-----+
|     |
||
||
| buf |
| 1   |
|     |
+-----+

GUI 在绘制 buf0 绘制完界面后,在调用 ioctl pan_display 时,底层驱动会自动切换显示 buf,避免撕裂现象的发生,GUI 应用程序不需要关注显示 buf 的切换。

struct fb_var_screeninfo var = {0};
int zero = 0;

if (ioctl(fbfd, FBIOPAN_DISPLAY, &var) == 0) {
    if (ioctl(fbfd, FBIO_WAITFORVSYNC, &zero) < 0) {
        printf("ioctl FBIO_WAITFORVSYNC fail\n");
        return;
    }
} else {
    printf("pan display err\n");
}

这种使用方式能避免显示撕裂现象,能节省下一块绘制 buf,但需要修改应用程序。

6.3.7.4.3. 单绘制 buffer 和单显示 buffer

&fb0 {
    zx,uboot-logo-on=<1>;
    rotation-degress = <270>;

    port {
        fb0_out: endpoint {
            remote-endpoint = <&de0_in>;
        };
    };
};

备注

该场景只需要指定旋转角度。

其底层的内存使用情况如下所示:

+-------------------------+
|      绘制 buf 0         |
+-----+-------------------+
|     |
||
||
| buf |
| 0   |
|     |
+-----+

GUI 在绘制 buf0 绘制完界面后,需要手工调用 ioctl pan_display 才可以触发显示驱动把数据旋转到显示 buf0。

备注

因为 display engine 无法确定 CPU 绘制完成的时机,所以需要应用使用 ioctl pan_display 去手动触发更新。

这种方式优劣如下所示:

  • 优势

    占用的内存资源最小

  • 劣势

    需要手动触发更新,必须修改应用;

    只有一块显示 buf,撕裂现象不可避免,只能在一些特定的场景中使用。

struct fb_var_screeninfo var = {0};
int zero = 0;

if (ioctl(fbfd, FBIOPAN_DISPLAY, &var) == 0) {
    if (ioctl(fbfd, FBIO_WAITFORVSYNC, &zero) < 0) {
        printf("ioctl FBIO_WAITFORVSYNC fail\n");
        return;
    }
} else {
    printf("pan display err\n");
}