Search by Tags

Using multiple layers and alpha blending on Vybrid's display controller

 
Applicable for

Tags
display | dma

Compare with Revision

Subscribe for this article updates

The Vybrid Display Controller Unit (DCU4) module is a bus master that fetches graphics stored in internal or external memory and displays them on a TFT LCD panel. Graphics are read directly from memory and then blended in real-time, which allows for dynamic content creation with minimal CPU intervention. Users control the graphical content of the TFT panel by manipulating the configuration of elements in the DCU4 called layers.

Starting with v2.3 Beta5 the Vybrid framebuffer driver provides the following features:

  • Full RGB888 output to TFT LCD panel.
  • Blending of each pixel using up to 4 source layers dependent on size of panel.
  • Each graphic layer can be placed with one pixel resolution in either axis.
  • Each graphic layer support RGB565 and RGB888 direct colours without alpha channel and BGRA8888 direct colours with an alpha channel.
  • Each graphic layer support alpha blending with 8-bit resolution.

The Vyrbid framebuffer driver provides five framebuffer nodes. The framebuffer nodes have a fixed priority. fb0 has the highest priority and fb5 the lowest. This priority controls how the image which has been rendered on different layers, interact with each other as per the alpha value and this priority, in giving a blended image. fb0 having the highest priority with an alpha value of 255 results in the content drawn on other framebuffer nodes to be opaque. Setting an alpha value different then 255 will allow the content drawn on other layers to be visible and transparent depending on the alpha value of the said layer in question.

Note: Currently, the driver uses multiple framebuffer (fbX) device nodes to export this functionality. In the future, this functionality will probably be exported using planes through the more modern KMS/DRM interface.

Layer Configuration

The sample code below uses the ioctl() calls provided by the driver to change the alpha value and bits per pixel setting. There is also a function to change and set the layers content X and Y axis offset for completion sake. To set the alpha or bits per pixel value for a node, open the said fb node and then use set alpha and set bits per pixel functionality.

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <linux/types.h>
#include <linux/ioctl.h>
#include <linux/fb.h>
#include <sys/mman.h>
#include <cairo/cairo.h>
 
/* Refer drivers/video/fbdev/fsl-dcu-fb.c */
#define MFB_SET_ALPHA     _IOW('M', 0, __u8)
#define MFB_GET_ALPHA     _IOR('M', 0, __u8)
#define MFB_SET_LAYER     _IOW('M', 4, struct layer_display_offset)
#define MFB_GET_LAYER     _IOR('M', 4, struct layer_display_offset)
 
#define GET_LAYER           1
#define SET_LAYER           2
#define GET_ALPHA           3
#define SET_ALPHA           4
#define SET_BITS_PER_PIXEL  5
#define GET_SCREENINFO      6
#define EXIT                7
 
/* Refer drivers/video/fbdev/fsl-dcu-fb.c */
struct layer_display_offset {
    int x_layer_d;
    int y_layer_d;
};
 
/*
 * Refer struct mfb_info in drivers/video/fbdev/fsl-dcu-fb.c
 * Layer 0: x_layer_d = 0; y_layer_d = 0,
 * Layer 1: x_layer_d = 50; y_layer_d = 50
 * Layer 2: x_layer_d = 100; y_layer_d = 100
 * Layer 3: x_layer_d = 150; y_layer_d = 150
 * Layer 4: x_layer_d = 200; y_layer_d = 200
 * Layer 5: x_layer_d = 250; y_layer_d = 250
 */
 
int main(void) {
    int fd;
    int ret;
    int choice;
    int layer_number;
    int bits_per_pixel;
    char frame_buffer_number;
    char fb_node[16] = {0};
    unsigned char alpha;
    struct layer_display_offset data;
    struct fb_var_screeninfo vscreeninfo;
 
    printf("Enter frame buffer number(0-5):\t");
    scanf("%c", &frame_buffer_number);
    sprintf(fb_node, "/dev/fb");
    fb_node[strlen(fb_node)] = frame_buffer_number;
    fb_node[strlen(fb_node)] = '\0';
    printf("Frame buffer node is: %s\n", fb_node);
    fd = open(fb_node, O_RDWR);
    if (fd < 0) {
        perror("Error");
        return -1;
    }
 
    while (1) {
        printf("\n1. Get layer\n");
        printf("2. Set layer\n");
        printf("3. Get alpha value\n");
        printf("4. Set alpha value\n");
        printf("5. Set bits per pixel\n");
        printf("6. Get screen information\n");
        printf("7. Exit\n");
        printf("\nEnter your choice (1-7):\t");
        scanf("%d[0-7]", &choice);
 
        switch(choice) {
        case GET_LAYER:
            ret = ioctl(fd, MFB_GET_LAYER, &data);
            if (ret)
                perror("Error");
            else
                if (data.x_layer_d == 0 && data.y_layer_d == 0)
                    printf("Current layer is 0\n");
                else if (data.x_layer_d == 50 && data.y_layer_d == 50)
                    printf("Current layer is 1\n");
                else if (data.x_layer_d == 100 && data.y_layer_d == 100)
                    printf("Current layer is 2\n");
                else if (data.x_layer_d == 150 && data.y_layer_d == 150)
                    printf("Current layer is 3\n");
                else if (data.x_layer_d == 200 && data.y_layer_d == 200)
                    printf("Current layer is 4\n");
                else if (data.x_layer_d == 250 && data.y_layer_d == 250)
                    printf("Currnet layer is 5\n");
            break;
        case SET_LAYER:
            layer_number = -1;
            printf("Enter the layer number(0-5) to set:\t");
            scanf("%d", &layer_number);
            switch (layer_number) {
            case 0:
                data.x_layer_d = 0;
                data.y_layer_d = 0;
                break;
            case 1:
                data.x_layer_d = 50;
                data.y_layer_d = 50;
                break;
            case 2:
                data.x_layer_d = 100;
                data.y_layer_d = 100;
                break;
            case 3:
                data.x_layer_d = 150;
                data.y_layer_d = 150;
                break;
            case 4:
                data.x_layer_d = 200;
                data.y_layer_d = 200;
                break;
            case 5:
                data.x_layer_d = 250;
                data.y_layer_d = 250;
                break;
            default:
                layer_number = -1;
                printf("Invalid layer number\n");
                break;
            }
            if (layer_number != -1) {
                ret = ioctl(fd, MFB_SET_LAYER, &data);
                if (ret)
                    perror("Error");
                else
                    printf("Layer set successfully\n");
            }
            break;
        case GET_ALPHA:
            alpha = -1;
            ret = ioctl(fd, MFB_GET_ALPHA, &alpha);
            if (ret)
                perror("Error");
            else
                printf("Alpha value is: %d\n", alpha);
            break;
        case SET_ALPHA:
            printf("Enter the value of alpha to set:\t");
            scanf("%d", &alpha);
            ret = ioctl(fd, MFB_SET_ALPHA, &alpha);
            if (ret)
                perror("Error");
            else
                printf("Alpha value set\n");
            break;
        case SET_BITS_PER_PIXEL:
                printf("\nEnter the value of bits per pixel:\t");
                scanf("%d", &bits_per_pixel);
                vscreeninfo.bits_per_pixel = bits_per_pixel;
                ret = ioctl(fd, FBIOPUT_VSCREENINFO, &vscreeninfo);
                if (ret)
                    perror("Error");
                else
                    printf("Bits per pixel set\n");
                break;
        case GET_SCREENINFO:
                ret = ioctl(fd, FBIOGET_VSCREENINFO, &vscreeninfo);
                if (ret)
                    perror("Error");
                else {
                    printf("\nVscreen Info:-\n");
                    printf(" Xres   = %4ld | Yres   = %4ld\n", vscreeninfo.xres,vscreeninfo.yres);
                    printf(" BPP    = %4ld | Height = %4ld | Width = %4ld\n", vscreeninfo.bits_per_pixel,
                           vscreeninfo.height,
                           vscreeninfo.width);
                    printf(" Xres_V = %4ld | Yres_V = %4ld\n", vscreeninfo.height,vscreeninfo.width);
                    printf(" Pixel format : RGBX_%ld%ld%ld%ld\n", vscreeninfo.red.length,
                           vscreeninfo.green.length,
                           vscreeninfo.blue.length,
                           vscreeninfo.transp.length);
                    printf(" Begin of bitfields(Byte ordering):-\n");    
                    printf("  Red    : %ld\n", vscreeninfo.red.offset); 
                    printf("  Blue   : %ld\n", vscreeninfo.blue.offset);
                    printf("  Green  : %ld\n", vscreeninfo.green.offset);
                    printf("  Transp : %ld\n", vscreeninfo.transp.offset);
 
                }
            break;
        case EXIT:
            close(fd);
            exit(0);
            break;
        default:
            printf("Invalid choice\n");
            choice = -1;
            break;
        }
    }
}

The above source code can be downloaded from here. Below is the Makefile which can be used for building the above example code with make fbdev. The sysroot, INCLUDES and LIB_PATH in the below Makefile need to be modified to either point to the relevant path in your OpenEmbedded setup or path in the extracted image archive for Vybrid.

CC = ${HOME}/gcc-linaro/bin/arm-linux-gnueabihf-gcc
INCLUDES = "-I${HOME}/oe-core/build/out-glibc/sysroots/colibri-vf/usr/include"
LIB_PATH = "-L${HOME}/oe-core/build/out-glibc/sysroots/colibri-vf/usr/lib"
CFLAGS = -O2 -g -mfloat-abi=hard --sysroot=${HOME}/oe-core/build/out-glibc/sysroots/colibri-vf/
 
fbdev: 
    ${CC} ${CFLAGS} ${INCLUDES} ${LIB_PATH} ${LDFLAGS} -o fbdev fbdev.c
 
clean:
    rm -rf fbdev

Examples

Cairo on fb1 blending with LXDE

This can be used with the Cairo sample program to test alpha blending. The above code can be used to set the alpha value and bits per pixel setting for a layer while a Cairo library based application can be used for writing to the framebuffer. For Cairo, please note the fact that the bits per pixel setting for a layer needs to be 32 while using CAIRO_FORMAT_RGB24.

root@colibri-vf:~# ./fbdev 
Enter frame buffer number(0-5): 0
Frame buffer node is: /dev/fb0

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        4
Enter the value of alpha to set:        150
Alpha value set

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        7

Note that the LXDE running on X-Server is running on fb0. The Cairo example will use fb1:

root@colibri-vf:~# ./cairo arch_sh-600x600.png 
Enter frame buffer number:      1
Frame buffer node is: /dev/fb1

The above sample run gives a result as below. Depending on the alpha value set for fb0 and fb1 the results of the blending can be controlled.

root@colibri-vf:~# ./fbdev 
Enter frame buffer number(0-5): 0
Frame buffer node is: /dev/fb0

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        4
Enter the value of alpha to set:        0
Alpha value set

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        7
root@colibri-vf:~# ./cairo arch_sh-600x600.png 
Enter frame buffer number:      1
Frame buffer node is: /dev/fb1
root@colibri-vf:~# 

The above options give results as shown in the pictures below. The alpha value of fb0 has been set to 0 here, the fb0 layer shows up as complete black. The actual colours of the image drawn by Cairo on fb1 are now clear, due to the absence of blending effect with fb0. For the text in the left image, the RGB value was 110 respectively which was specified with Cairo. In this example, this text shows up clearly in yellow colour with only the red and green component being present, while the same text in the previous example has a different result, due to the blending effect (depending on the alpha values of fb0 and fb1).

Cairo on fb0 using RGB blending with Framebuffer Console on fb1

root@colibri-vf:~# ./fbdev 
Enter frame buffer number(0-5): 0
Frame buffer node is: /dev/fb0

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        4
Enter the value of alpha to set:        128
Alpha value set

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        7
root@colibri-vf:~# ./cairo dice.png 
Enter frame buffer number:      0
Frame buffer node is: /dev/fb0
root@colibri-vf:~# 

The image for the above and subsequent example was generated by mapping the framebuffer console to fb1 with fbcon=map:1 and then writing to fb0 with the Cairo sample.


Cairo on fb0 using ARGB blending with Framebuffer Console on fb1

root@colibri-vf:~# ./fbdev 
Enter frame buffer number(0-5): 0
Frame buffer node is: /dev/fb0

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        5
Enter the value of bits per pixel:        32
Bits per pixel set

1. Get layer
2. Set layer
3. Get alpha value
4. Set alpha value
5. Set bits per pixel
6. Get screen information
7. Exit

Enter your choice (1-7):        7

The Cairo sample uses CAIRO_FORMAT_ARGB32 as pixel format. The dice picture in the PNG file format uses transparency, which in turn is supported and transferred to the Framebuffer device by Cairo.

root@colibri-vf:~# ./cairo dice.png 
Enter frame buffer number:      0
Frame buffer node is: /dev/fb0
root@colibri-vf:~# 

Please refer the Vybrid frambuffer driver and the Section 55.5.4 Layer configuration and blending in the Vybrid Reference Manual which can be downloaded from here for more information.