Drawing In Protected Mode

From OSDev Wiki
Jump to: navigation, search

Now that you know how you can easily write text to the screen using Hardware VGA support, you might be wondering how you'll be able to display nice images, windows, menus, icons, fancy cursors and buttons, etc.


Graphics Modes

Main article: Getting VBE Mode Info
Main article: GOP

VGA (And VESA) modes can be selected using the standard BIOS interrupt 0x10 during boot-time (before switching to protected mode). Int 0x10 seems like a decent enough reference for int 0x10 (No VESA extension) while VESA contains the various VESA standards.

VGA is limited to a 640x480x16, VESA (Depending on the video card) can present higher resolutions.

On modern systems, you'll have to use GOP.


The cleanest way to set up your video mode is to go through the video BIOS. It can be performed through the regular Int 0x10 interface, or through the (optional) Protected mode interface offered by VBE3. As you can guess, Int 0x10 requires a 16-bit environment, so you can only use it in Real Mode or Virtual 8086 Mode

Practically, the options are (in order of difficulty):

  • Set up the mode you want at early stage (in the bootloader) before entering protected mode.
    • Develop on an UEFI system, and get a GOP framebuffer at boot-time.
    • Let GRUB do the switch for you.
  • Switch back to Real Mode or Unreal Mode for setting the proper video mode (Napalm at rohitab.com has a neat little function for reference.)
  • Write a VGA driver that can do low-resolution modes on practically all hardware
  • Use the PMID from VBE3, if present
  • Set up a V8086 monitor that will execute the mode-switching code
  • Run a software code translation tool to produce pmode code out of bios rmode code. (SANiK is on the catch)
  • You write a driver for your specific graphics card

Locating Video Memory

For standard VGA video modes the video memory will be at address 0xA0000 for EGA/VGA video modes and 0xB8000 for CGA and text modes. To find out which one look at the following table:

00 text 40*25 16 color (mono)
01 text 40*25 16 color
02 text 80*25 16 color (mono)
03 text 80*25 16 color
04 CGA 320*200 4 color
05 CGA 320*200 4 color (m)
06 CGA 640*200 2 color
07 MDA monochrome text 80*25
08 PCjr
09 PCjr
0A PCjr
0B reserved
0C reserved
0D EGA 320*200 16 color
0E EGA 640*200 16 color
0F EGA 640*350 mono
10 EGA 640*350 16 color
11 VGA 640*480 mono
12 VGA 640*480 16 color
13 VGA 320*200 256 color

For VESA modes, the framebuffer address is stored in the mode info block. This is the physical address of the linear framebuffer (it is not a 16-bit far pointer but a 32-bit linear pointer) : if you use paging, you have to map it somewhere to use it. In QEMU is it on 0xFC000000(in many modes) and in Bochs on 0xE0000000.

For GOP, the framebuffer address is in the EFI_GRAPHICS_PROTOCOL struct, gop->Mode->FrameBufferBase. GOP does not support teletype character modes, only graphics pixel oriented modes.

Plotting Pixels


If you wanted to plot a red pixel in the middle of your screen. The first thing you have to know is where the middle of the screen is. In 320x200x8 (mode 13), this will be at 100x320+160 = 32160. In general, your screen can be described by:

width how many pixels you have on a horizontal line
height how many horizontal lines of pixels are present
pitch how many bytes of VRAM you should skip to go one pixel down
depth how many bits of color you have
"pixelwidth" how many bytes of VRAM you should skip to go one pixel right.

"pitch" and "width" may seem redundant at first sight but they aren't. It's not rare once you go to higher (and exotic) resolutions to have e.g. 8K bytes per line while your screen is actually 1500 pixels wide (32-bits per pixel). The good news is that it allows smooth horizontal scrolling (which is mainly useful for 2D games :P )

Pitch and pixel width are usually announced by VESA mode info. Once you know them, you can calculate the place where you plot your pixel as:

unsigned char *pixel = vram + y*pitch + x*pixelwidth;


The second thing to know is what value you should write for "red". This depends on your screen setup, again. In EGA mode, you have a fixed palette featuring dark-red (color 4) and light-red (color 12). Yet, EGA requires you to plot each bit of that on different pixel plane, so refer to EGA programming tutorials if you really want such modes supported. In conventional 320x200x8 VGA mode, you have the same colours 4 and 12 as in EGA so you would plot your red pixel with

*pixel = 4;

Yet, in VGA, the palette is reprogrammable (as you can learn in FreeVGA documents), so virtually any value between 0..255 could be 'red' if you program the palette so :P

Finally, in VESA modes, you usually have truecolor or hicolor, and in both of them, you have to give independent red, green and blue values for each pixel. modeinfo will (again) instruct you of how the RGB components are organized in the pixel bits. E.g. you will have xRRRRRGGGGGBBBBB for 15-bits mode, meaning that #ff0000 red is there 0x7800, and #808080 grey is 0x4210 (pickup pencil, draw the bits and see by yourself)

/* only valid for 800x600x16M */
static void putpixel(unsigned char* screen, int x,int y, int color) {
    unsigned where = x*3 + y*2400;
    screen[where] = color & 255;              // BLUE
    screen[where + 1] = (color >> 8) & 255;   // GREEN
    screen[where + 2] = (color >> 16) & 255;  // RED
/* only valid for 800x600x32bpp */
static void putpixel(unsigned char* screen, int x,int y, int color) {
    unsigned where = x*4 + y*3200;
    screen[where] = color & 255;              // BLUE
    screen[where + 1] = (color >> 8) & 255;   // GREEN
    screen[where + 2] = (color >> 16) & 255;  // RED


It can be tempting from here to write fill_rect, draw_hline, draw_vline, etc. from calls to putpixel ... don't. Drawing a filled rectangle means you access successive pixels and then advance by "pitch - rect_width" to fill the next line. If you do a "for(y=100;y<200;y++) for(x=100;x<200;x++) putpixel (screen,x,y,RED);" loop, you'll recompute 'where' about 10,000 times. Even if the compiler has done good job to translate y*3200 into adds and shifts instead of multiplication, it's silly to run that so much time while you could do

static void fillrect(unsigned char *vram, unsigned char r, unsigned char g, unsigned   char b, unsigned char w, unsigned char h) {
    unsigned char *where = vram;
    int i, j;
    for (i = 0; i < w; i++) {
        for (j = 0; j < h; j++) {
            //putpixel(vram, 64 + j, 64 + i, (r << 16) + (g << 8) + b);
            where[j*4] = r;
            where[j*4 + 1] = g;
            where[j*4 + 2] = b;

That should be enough to get you started coding (or googling for) a decent video library.

Drawing Text

Main article: VGA Fonts

Once in graphic mode, you no longer have the BIOS or the hardware to draw fonts for you. The basic idea is to have font data for each character and use it to plot (or not to plot) pixels. There are plenty of ways to store those fonts depending on whether they have multiple colors or not, alpha channel or not etc. What you will basically have, however is:

// holding what you need for every character of the set
font_char* font_data[CHARS];
// rendering one of the character, given its font_data
void draw_char(screen, where, font_char*);
void draw_string(screen, where, char* input) {
    while(*input) {
        where += char_width;
void draw_char(screen, where, font_char*) {
    for (l = 0; l < 8; l++) {
        for (i = 8; i > 0; i--) {
            if ((font_char[l] & (1 << i))) {
                c = c1;
                put_pixel(j, h, c);
        j = x;

See Also

External Links

Personal tools