|Applying C - Framebuffer Graphics
|Written by Harry Fairhead
|Monday, 10 June 2019
Page 1 of 3
Graphics in Linux go from low level to full 3D. The most basic and in many ways most useful for low level code is the Framebuffer but is is hard to find out how it all works. This extract is from my book on C in an IoT context.
Now available as a paperback or ebook from Amazon.
Applying C For The IoT With Linux
Also see the companion book: Fundamental C
C is not a language known in the context of developing user interfaces, but even small systems occasionally need to communicate with their users. Often all that is needed is the display of a few digits to provide feedback on the current state. A common and low-cost way of doing this is to interface directly to a 7-segment LCD display driven directly by the GPIO lines. Any input is also often provided by physical switches and buttons and handled directly by programming GPIO lines.
In this sense the user interface is just an extension of the low-level GPIO programming in the rest of the system. However, with many small devices being equipped with a full HDMI or VGA graphics output, using a full screen display is a real alternative to the GPIO-driven user interface. This means we have to move to using full Linux graphics from C to create either simple displays at one end of the spectrum or full GUI interfaces, complete with touchscreen, trackball, mouse or keyboard input.
Choosing a GUI System - Final version in book.
The framebuffer is a pseudo file stored in /dev. If the system you are working with has a framebuffer there will be a file called fb0 or fb1 etc. This is a file-like interface to the graphics buffer - yes its a pseudo file and it can be memory mapped so that you can use it as a conventional memory based graphics buffer. However, if you want to you can treat it as a file and work with read, write and seek. You can also use commands such as cp to copy the entire file and hence take a screen dump. In Linux/Unix everything is a file.
To open the framebuffer you use:
int fd = open("/dev/fb0", O_RDWR);
assuming the framebuffer is fb0 and you want to both read and write it.
table omitted - look it up on the web
Which of these fields is important depends on the graphics hardware in use.
The variable information is usually of much more direct use:
table omitted - look it up on the web
Most of the important information is in the first part of this struct. The geometry of the image is determined by six fields:
xres; yres; xres_virtual; yres_virtual; xoffset; yoffset;
The xres and yres values give you the number of pixels in a row and a column in the framebuffer. The xres_virtual and yres_virtual values give you the number of pixels displayed on the screen. In general the two are the same but if the screenbuffer is bigger than the screen can display then it is a portal into the screenbuffer and its location is given by the value of xoffset and yoffset. In most cases the actual and virtual are the same and the offsets are zero.
We also need to know the value of bits_per_pixel, which can be divided by 8 to give the number of bytes per pixel.
This almost gives us enough information to work out the size of the framebuffer and locate any given pixel. However there is one small but standard complication in such calculations. A row of pixels should take xres* bits_per_pixel/8 bytes to store but often padding bytes have to be added to make each row align on an address boundary. The number of bytes including padding is given in fb_fix_screeninfo.line_length. This is often called the stride because its the number of bytes you need to skip over to reach a pixel at the same x coordinate but at y+1.
Putting all this together gives the size of the framebuffer as:
To map the file into memory all we need is:
int fd = open("/dev/fb0", O_RDWR); struct fb_fix_screeninfo finfo; struct fb_var_screeninfo vinfo; ioctl(fd, FBIOGET_VSCREENINFO, &vinfo); ioctl(fd, FBIOGET_FSCREENINFO, &finfo); size_t size = vinfo.yres * finfo.line_length; uint8_t *fbp = mmap(0, size, PROT_READ | PROT_WRITE, MAP_SHARED, fd,0);
The ioctl calls are the standard way to get the information into the structs. Using this we compute the total size of the buffer in bytes. The memory mapping is straightforward (see previous chapter) but notice that we have opted to work in byte addresses by using a pointer to uint8_t.
The next step is to work out where a pixel at coordinate x,y is stored in the buffer. First we need to decide if we are working in screen coordinates i.e. virtual coordinates or physical coordinates. If the screenbuffer is larger than the screen then this makes a difference. In most cases it makes sense to draw in the screen buffer and ignore the fact that only a portion is visible on the screen. Thus x and y are give the location of the pixel in an image of size xres and yres. The location of this pixel in the buffer is given by:
uint32_t location = x*vinfo.bits_per_pixel/8 +
This is the address of the first byte of the pixel.
|Last Updated ( Monday, 01 July 2019 )