Framebuffer linux

http://betteros.org/tut/graphics1.php

This is the very first thing we want to do, open the device file:

int fb_fd = open("/dev/fb0",O_RDWR);

 Once the file is open, we can actually start writing to it. However, this would not be useful since we don’t know the dimensions or color depth of the screen yet, so we would not be able to accurately draw anything meaningful inside the buffer. So the next thing that we should do it get some basic information about the screen. We can do this with the ioctl function (or syscall). There are two structures defined in linux/fb.h for storing info about the screen. They are called fb_var_screeninfo and fb_fix_screeninfo. We should create an instance of each of these structs. (also, remember to include linux/fb.h)

#include <linux/fb.h>
...
struct fb_fix_screeninfo finfo;
struct fb_var_screeninfo vinfo;

These structures are defined in linux/fb.h as follows:

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;			/* Indicate to driver which	*/
					/*  specific chip/card we have	*/
	__u16 capabilities;		/* see FB_CAP_*			*/
	__u16 reserved[2];		/* Reserved for future compatibility */
};

...

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 = color, 1 = grayscale,	*/
					/* >1 = FOURCC			*/
	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;		/* (OBSOLETE) see fb_info.flags */

	/* 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 rotate;			/* angle we rotate counter clockwise */
	__u32 colorspace;		/* colorspace for FOURCC-based modes */
	__u32 reserved[4];		/* Reserved for future compatibility */
};

Now that we know all about these structures, we can use ioctl on our open file descriptor to fill these structures.

//Get variable screen information
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);

//Get fixed screen information
ioctl(fb_fd, FBIOGET_FSCREENINFO, &finfo);

Note that the fb_var_screeninfo structure is variable information. This means, in addition to the FBIOGET_VSCREENINFO, we can also call ioctl with FBIOPUT_VSCREENINFO to change the settings of the framebuffer. Most importantly, we probably will want to set the bits_per_pixel field to something reasonable, since by default it seems to be set to something like 8, and is not enough to render in color. You might also need to set grayscale to 0, but in practice, it seems to work even if you don’t do that. After that, you should get it again to make sure that your changes were successful.

//Get variable screen information
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);
vinfo.grayscale=0;
vinfo.bits_per_pixel=32;
ioctl(fb_fd, FBIOPUT_VSCREENINFO, &vinfo);
ioctl(fb_fd, FBIOGET_VSCREENINFO, &vinfo);

Once that is done, we can calculate the total size of the screen (in bytes). This is important because we will need to map exactly the right amount of memory, and only draw into that memory, otherwise bad things will happen. To calculate the size of the screen (the size of the buffer), we can use vinfo.yres_virtual, which is the number of horizontal lines on the screen, multiplied by finfo.line_length, the length of each line in bytes.

long screensize = vinfo.yres_virtual * finfo.line_length;

Once we have the size of the screen, we can use mmap to map the buffer to memory. mmap will return a pointer to the beginning of the memory.

uint8_t *fbp = mmap(0, screensize, PROT_READ | PROT_WRITE, MAP_SHARED, fb_fd, (off_t)0);

Now, you have your framebuffer mapped to memory. All that is left to do is to draw on it. This we can do just by setting the memory at the right location to the correct value of the pixel in the color you want. So next what we need to do is calculate the correct location in the mapped memory of the pixel that we want to set. For this, we can use the following algorithm:

long x,y; //location we want to draw the pixel
uint32_t pixel; //The pixel we want to draw at that location
//Make sure you set x,y and pixel correctly (details later)
long location = (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) + (y+vinfo.yoffset) * finfo.line_length;
*((uint32_t*)(fbp + location)) = pixel;

(y+vinfo.yoffset) * finfo.line_length gets the beginning of line y in memory, and (x+vinfo.xoffset) * (vinfo.bits_per_pixel/8) gets the offset of x on that line. All we have to do is add those together and we have the correct location in memory of the pixel we want to draw. Then we just set that memory to the pixel we want to draw. So we need to decide what color we want the pixel to be and then calculate what the value for a pixel of that color would be. We can use the vinfo structure to figure out the pixel format required for computing the correct pixel value, specifically the red, green, and blue fields, which are also structures. I like to write a little function that takes 8 bit values for each color and returns the pixel value, it looks like this:

inline uint32_t pixel_color(uint8_t r, uint8_t g, uint8_t b, struct fb_var_screeninfo *vinfo)
{
	return (r<<vinfo->red.offset) | (g<<vinfo->green.offset) | (b<<vinfo->blue.offset);
}

This function takes the 8 bit value and shifts it to the left the correct offset of that color. Then combines it with the other colors using the OR operator. So if we want to draw a pixel of color 0xFF,0x00,0xFF (purple), it take the red value (0xFF), shift it over the correct offset or red (probably 16) and the result would be 0x00FF0000, then it would take the green value (0x00) and shift that to the left (probably 8 bits) and then OR those together, resulting in the same value since green was set to 0, and then take blue (0xFF) and shift that the left (probably 0 bits) resulting in 0x000000FF, then OR that value with the red and green to get the final pixel color of 0x00FF00FF.

So you can see how easy it is to determine the correct pixel value. Now one important thing you need to remember and be aware of at all time is that you must never try to draw outside of the screen. This is because your program has gotten permission to write into that buffer, but if you try to write outside that buffer, you are essentially trying to modify somebody else’s memory. Linux probably won’t allow this and your program will end with a segfault. This is bad for your program, but if you have set the tty to graphics mode (I will explain later), then you can cause the whole machine to lock up, which is a very bad thing to do. To make sure we don’t draw outside the screen, we can use vinfo.xres (the width of the screen in pixels) and vinfo.yres (the height of the screen in pixels). If you never draw and pixels above vinfo.xres,vinfo.yres or below 0,0, then you should be fine. Also, you should note that it is actually safe to draw X values past vinfo.xres since the buffer is just one big block of memory, if you exceed vinfo.xres, you will actually be drawing on the line below Y. So (1+vinfo.xres),14 is the same as 1,15. Of course, this is probably not something that you would ever want to do, so it’s probably best to just never draw X greater than vinfo.xres.

I have one final thing to explain, as I promised earlier. However, you need to be aware that this is dangerous and not strictly required. If you use this and something goes wrong and your program doesn’t clean up properly, then you will lock up your computer (not really, but the screen will stop responding). You have been warned.
 What I am talking about is claiming the tty for graphics only. This will prevent the framebuffer console from trying to draw overtop of your graphics. You will need to use ioctl again, but this time not on the framebuffer device, instead you will have to use it on the tty device, probably /dev/tty0. You will need to call KDSETMODE with KD_GRAPHICS to set up the tty for graphics only, and then with KD_TEXT to undo it. You MUST set it back to KD_TEXT at exit, or else. I recommend never using this until you are 100% sure that your code will not cause a segfault, otherwise you are going to be rebooting your computer a lot. You could probably also set something up so that you can press some key combination or type some simple command that runs a program that just sets KDSETMODE back to KD_TEXT, that’s probably easier, but somehow I doubt you will actually bother. Anyways, the code looks like this:

int tty_fd = open("/dev/tty0", O_RDWR);
ioctl(tty_fd,KDSETMODE,KD_GRAPHICS);
...
//At exit:
ioctl(tty_fd,KDSETMODE,KD_TEXT);

That’s all there is to it.

Lascia un commento

Inserisci i tuoi dati qui sotto o clicca su un'icona per effettuare l'accesso:

Logo WordPress.com

Stai commentando usando il tuo account WordPress.com. Chiudi sessione / Modifica )

Foto Twitter

Stai commentando usando il tuo account Twitter. Chiudi sessione / Modifica )

Foto di Facebook

Stai commentando usando il tuo account Facebook. Chiudi sessione / Modifica )

Google+ photo

Stai commentando usando il tuo account Google+. Chiudi sessione / Modifica )

Connessione a %s...