7. framebuffer

7.1. Introduction to the framebuffer device

This is a programming HOWTO. If you have not yet set up your framebuffer device properly, do so before you compile/execute the examples.

With the framebuffer device you can use your computer screen as a true graphics device. You can modify the resolution, the refresh rate, and color depth. Best of all, you can plot pixels everywhere you want. The framebuffer device is not a graphics library, but a rather lowlevel generic device. This creates great flexibility, but has its drawbacks as well. To use the framebuffer device there are a few things you really should do:

The device to open is usually /dev/fb0, but if the user has multiple vidoe cards and monitors, it can be something else. Most applications read the environment variable FRAMEBUFFER (use getenv(); for that...) to determine the device to use. If it doesn't exist, /dev/fb0 is used.

Open the device using the open() call. reading the device means reading the screen memory. Try $ cat /dev/fb0 >screenshot to dump the screen memory to a file. To restore the screenshot do the opposite: $ cat screenshot >/dev/fb0.

7.2. basic use of the device

Using the screen memory this way is not very usuable. Constant seeking (see man lseek) before reading or writing causes a lot of overhead. This is why you should map the screen memory. When you map the screen memory to your application, you will have a pointer that will point to the screen memory directly.

Before we can map the memory, we need to know how much we can map, and how much we should map. The first thing we do is to retrieve information on our newly acquired framebuffer device. There are two structures that contain the information we need. The first contains fixed screen information. This is information that depends on the capabilities of the hardware and the driver.The second one contains vairiable screen information. It depends on the current state of the hardware. It can be altered by user space programs in the blink of an ioctl().
struct fb_fix_screeninfo {
	char id[16];			/* identification string eg "TT Builtin" */
	unsigned long smem_start;	/* Start of frame buffer mem */
					/* (physical address) */
	__u32 smem_len;			/* Length of frame buffer mem */
	__u32 type;			/* see FB_TYPE_*		*/
	__u32 type_aux;			/* Interleave for interleaved Planes */
	__u32 visual;			/* see FB_VISUAL_*		*/ 
	__u16 xpanstep;			/* zero if no hardware panning  */
	__u16 ypanstep;			/* zero if no hardware panning  */
	__u16 ywrapstep;		/* zero if no hardware ywrap    */
	__u32 line_length;		/* length of a line in bytes    */
	unsigned long mmio_start;	/* Start of Memory Mapped I/O   */
					/* (physical address) */
	__u32 mmio_len;			/* Length of Memory Mapped I/O  */
	__u32 accel;			/* Type of acceleration available */
	__u16 reserved[3];		/* Reserved for future compatibility */
};
The really important fields in here are smem_len and line_length. The first one gives us the size of the framebuffer memory, the second one gives you the amount of bytes your pointer has to advance in order to get to the next line. The second structure is far more interesting. It gives changable information.
/* more kernel header files copied shamelessly */

struct fb_bitfield {
	__u32 offset;			/* beginning of bitfield	*/
	__u32 length;			/* length of bitfield		*/
	__u32 msb_right;		/* != 0 : Most significant bit is */ 
					/* right */ 
};

struct fb_var_screeninfo {
	__u32 xres;			/* visible resolution		*/
	__u32 yres;
	__u32 xres_virtual;		/* virtual resolution		*/
	__u32 yres_virtual;
	__u32 xoffset;			/* offset from virtual to visible */
	__u32 yoffset;			/* resolution			*/

	__u32 bits_per_pixel;		/* guess what			*/
	__u32 grayscale;		/* != 0 Graylevels instead of colors */

	struct fb_bitfield red;		/* bitfield in fb mem if true color, */
	struct fb_bitfield green;	/* else only length is significant */
	struct fb_bitfield blue;
	struct fb_bitfield transp;	/* transparency			*/	

	__u32 nonstd;			/* != 0 Non standard pixel format */

	__u32 activate;			/* see FB_ACTIVATE_*		*/

	__u32 height;			/* height of picture in mm    */
	__u32 width;			/* width of picture in mm     */

	__u32 accel_flags;		/* acceleration flags (hints)	*/

	/* Timing: All values in pixclocks, except pixclock (of course) */
	__u32 pixclock;			/* pixel clock in ps (pico seconds) */
	__u32 left_margin;		/* time from sync to picture	*/
	__u32 right_margin;		/* time from picture to sync	*/
	__u32 upper_margin;		/* time from sync to picture	*/
	__u32 lower_margin;
	__u32 hsync_len;		/* length of horizontal sync	*/
	__u32 vsync_len;		/* length of vertical sync	*/
	__u32 sync;			/* see FB_SYNC_*		*/
	__u32 vmode;			/* see FB_VMODE_*		*/
	__u32 reserved[6];		/* Reserved for future compatibility */
};
the first few entries determine the resolution. xres and yres are the real resolution visible on the screen. In normal vga mode this would be 640 and 400. *res_virtual determines the way the video card reads the screen memory when building the screen. When the real vertical resolution is 400, the virtual resolution can be 800. This means 800 lines are reserved in screen memory. Because only 400 lines can be displayed, it is up to you to determine which line to start with. This is done by *offset. Put 0 in yoffset to display the first 400 lines, 35 to display the 36th to 435th line and so on. This feature can come in handy on various occasions. It can be used for double bufferring. Double buffering means that your program allocates memory to fill two screens. it sets the offset to 0, displaying the top 400 lines (assuming standard vga). It secretly builds up a new screen on lines 400 to 799. When it is done, it changes the yoffset to 400, and the new screen is displayed instantaniously. Now it starts building the next screen in the first block of memory, and so on. It is very useful in animations.

Another application is smooth scrolling of the entire screen. Allocate 800 lines just like in the previous screen. Set up a timer (see man setitimer and man signal/man sigaction) every 10 milliseconds or so. set the offset one or a few lines higher than the last time, and voila you have a smooth scrolling screen. Make sure your signal is not blocked within the signal handler for the optimal result.

Just like the vertical scrolling in the examples, there are facilities to change the horizontal resolution. Most drivers don't support this yet though.

Set bits_per_pixel to 1, 2, 4, 8, 16, 24 or 32 to change the color depth. Not all cards or drivers can handle all color depths. When the color depth has changed, the driver automatically changes the fb_bitfields. These indicate on a per color basis how many and which bits are used for which colors. If the bits_per_pixel is less than 8, the fb_bitfields are undefined and color maps are used.

The timing values at the end of the fb_var_screeninfo structure are there to set video timings when you choose a new resolution. EXAMINE AND EXPLAIN TIMINGS!

Now we know what goes in the structures, but not how to get or set them. There are a few ioctl's available.
#include <linux/fb.h>

int main ()
{
	int framebuffer_handler;
	struct fb_fix_screeninfo fixed_info;
	struct fb_var_screeninfo variable_info;

	open ("/dev/fb0", O_RDWR);
	/* in real life, check every ioctl if it returns -1 */

	ioctl (framebuffer_handler, FBIOGET_VSCREENINFO, &variable_info);
	variable_info.bits_per_pixel = 32;
	ioctl (framebuffer_handler, FBIOPUT_VSCREENINFO, &variable_info);

	ioctl (framebuffer_handler, FBIOGET_FSCREENINFO, &fixed_info);

	variable_info.yoffset = 513; 
	ioctl (framebuffer_handler, FBIOPAN_DISPLAY, &variable_info);
}
The FBIOGET_* ioctls puts the requested information in the structure pointed to by the last argument. FBIOPUT_VSCREENINFO copies all supplies information back into the kernel. If the kernel is not able to activate the new settings, it will return -1. The FBIOPAN_DISPLAY copies information from the user as well, but it does not reinitialize the video mode. Use it when only the xoffset and/or the yoffset have changed.

7.3. Display memory management

To access the memory itself, it can be mapped, and then accessed directly. some steps have to be taken:

Here is an example:
#include <linux/fb.h>
#include <unistd.h>
#include <sys/mman.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main ()
{
	int framebuffer_device;
	int line_size, buffer_size, *i;
	int *screen_memory;
	struct fb_var_screeninfo var_info;

	framebuffer_device = open ("/dev/fb0", O_RDWR);
	ioctl (framebuffer_device, FBIOGET_VSCREENINFO, &var_info);

	line_size = var_info.xres * var_info.bits_per_pixel / 8;
	buffer_size = line_size * var_info.yres;

	var_info.xoffset = 0;
	var_info.yoffset = 0;
	ioctl (framebuffer_device, FBIOPAN_DISPLAY, &var_info) == -1);
	
	screen_memory = (char *) mmap (513, buffer_size, PROT_READ | PROT_WRITE, MAP_SHARED, framebuffer_device, 0);

	for (i=0; i < buffer_size ; i++ ) {
		*(screen_memory+i) = i%236;
	}
	sleep(2);
	return 0;
}
As you can see we use the information retrieval from the previous section. New in this section is the mmap function. The first argument is ignored in this case, the second is the size of the memory mapped. The third argument states that we want to read from the shared memory an write to it as well.

The fourth argument states that the memory will be shared with other processes. It is not possible to do a MAP_PRIVATE on the framebuffer. Normally this means you should have to intercept console switches to back up and restore the screen contents, and not to write to the screen memory while it is not your turn.

For more information on mmap see man mmap

7.4.

colormap

CON2FBMAP

power save modes

GLYPH

HWCINFO

modeinfo/dispinfo