Applying C - Framebuffer Graphics
Written by Harry Fairhead   
Monday, 10 June 2019
Article Index
Applying C - Framebuffer Graphics
Color
The Main Program

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

  1. C,IoT, POSIX & LINUX
  2. Kernel Mode, User Mode & Syscall
  3. Execution, Permissions & Systemd
    Extract Running Programs With Systemd
  4. Signals & Exceptions
    Extract  Signals
  5. Integer Arithmetic
    Extract: Basic Arithmetic As Bit Operations
  6. Fixed Point
    Extract: Simple Fixed Point Arithmetic
  7. Floating Point
  8. File Descriptors
    Extract: Simple File Descriptors 
    Extract: Pipes 
  9. The Pseudo-File System
    Extract: The Pseudo File System
    Extract: Memory Mapped Files ***NEW
  10. Graphics
    Extract: framebuffer
  11. Sockets
    Extract: Sockets The Client
    Extract: Socket Server
  12. Threading
    Extract:  Pthreads
    Extract:  Condition Variables
    Extract:  Deadline Scheduling
  13. Cores Atomics & Memory Management
    Extract: Applying C - Cores 
  14. Interupts & Polling
    Extract: Interrupts & Polling 
  15. Assembler
    Extract: Assembler

Also see the companion book: Fundamental C

<ASIN:1871962609>

<ASIN:1871962463>

<ASIN:1871962617>

<ASIN:1871962455>

ACcover

 

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

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.
At this point you can't map it into memory because you don't know how big it is. The framebuffer has two structs that you can use to get and set information about the graphics mode and state. The fb_fix_screeninfo struct gives you the current state of the video hardware:

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:

yres* line_length

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 + 
y*finfo.line_length;

This is the address of the first byte of the pixel.



Last Updated ( Monday, 01 July 2019 )