Drawing In Protected Mode: Difference between revisions

From OSDev.wiki
Jump to navigation Jump to search
[unchecked revision][unchecked revision]
Content added Content deleted
 
(12 intermediate revisions by 6 users not shown)
Line 1: Line 1:
#REDIRECT [[Drawing_In_a_Linear_Framebuffer]]
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.


{{Redirect page}}
==Graphics Modes==
{{Main|Getting VBE Mode Info}}
VGA (And VESA) modes can be selected using the standard BIOS interrupt 0x10. [http://www.ctyme.com/intr/int-10.htm Int 0x10] seems like a decent enough reference for int 0x10 (No VESA extension) while [http://www.vesa.org/ VESA] contains the various VESA standards.

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

==Switching==

The cleanest way to set up your video mode is to go through the video BIOS. It can be performed through the regular <tt>Int 0x10</tt> interface, or through the (optional) [[Protected mode]] interface offered by VBE3. As you can guess, <tt>Int 0x10</tt> 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 a [[UEFI]] system, and get a 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 [http://www.rohitab.com/discuss/topic/35103-switch-between-real-mode-and-protected-mode/ rohitab.com] has a neat little function for reference.)
*Write a [[VGA Hardware|VGA driver]] that can do low-resolution modes on practically all hardware
*Use the PMID from VBE3, if present
*Set up a [[Virtual 8086 Mode|V8086]] [[Virtual_Monitor|monitor]] that will execute the mode-switching code
*Run a software code translation tool to produce pmode code out of bios rmode code. ([http://www.osdev.org/phpBB2/viewtopic.php?t=10321 SANiK is on the catch])
*You write a driver for your specific [[:Category:Video|graphics card]]

==Locating Video Memory==

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

{| {{wikitable}}
|-
| 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 [[Getting VBE Mode Info|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 0xFD000000 and in Bochs on 0xE0000000

== Plotting Pixels ==

=== Location ===
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:
{| {{wikitable}}
| 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:
<source lang="c">unsigned char *pixel = vram + y*pitch + x*pixelwidth;</source>

=== Color ===
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 <tt>xRRRRRGGGGGBBBBB</tt> for 15-bits mode, meaning that #ff0000 red is there <tt>0x7800</tt>, and #808080 grey is <tt>0x4210</tt> (pickup pencil, draw the bits and see by yourself)
<source lang="c">
/* 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
}
</source>

=== Optimizations ===

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
<source lang="c">
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;
}
where+=3200;
}
}
</source>
That should be enough to get you started coding (or googling for) a decent video library.

== Drawing Text ==

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:
<source lang="c">
// 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) {
draw_char(screen,where,font_data[input]);
where += char_width;
input++;
}
}
void draw_char(screen, where, font_char*) {
for (l = 0; l < 8; l++) {
for (i = 8; i > 0; i--) {
j++;
if ((font_char[l] & (1 << i))) {
c = c1;
put_pixel(j, h, c);
}
}
h++;
j = x;
}
}
</source>
=== Font Encoding ===

The most common encoding that allows you not to overwrite the background over which you draw your text is the ''font bitmap'', that is, an "A" character will e.g. be encoded as (. is 0 in binary and X is 1)

...XX... = 0*128+0*64+0*32+1*16+1*8+0*4+0*2+0*1 = 0x18
..XXXX.. = 0x3C
.XX..XX. = 0x66
.XXXXXX. = 0x7E
.XX..XX. = 0x66
.XX..XX. = 0x66
........ = 0x00
........ = 0x00

In which case you test each bit of the font data to tell whether it's 1 or 0 and only put the pixel if it's 1.
For larger fonts you might want to use RLE encoding instead, for instance. Finally, state-of-the-art true-type fonts will require you to support the "freetype" library.

=== Optimizations ===

Use of a "put_pixel()" function is almost always a performance problem. For an 8 x 8 character you can find out the address of the first (top left) pixel and increment it to get the address of the next pixel in the row, and add the number of bytes per line to get the address of the next row. This is much faster than calculating "address = video_address + y * horizontal_resolution + x" 64 times (per character).

However, it's even faster to work on more than one pixel at a time. If you look at the font encoding above you'll notice that there's an 8-bit number for each row of the character. This 8-bit number can be used as the index in a lookup table containing masks. For example, for 8 bits per pixel you could do 8 pixels at a time, like this:

<source lang="c">
uint32_t font_data_lookup_table[16] = {
0x00000000,
0x000000FF,
0x0000FF00,
0x0000FFFF,
0x00FF0000,
0x00FF00FF,
0x00FFFF00,
0x00FFFFFF,
0xFF000000,
0xFF0000FF,
0xFF00FF00,
0xFF00FFFF,
0xFFFF0000,
0xFFFF00FF,
0xFFFFFF00,
0xFFFFFFFF
}

void draw_char(uint8_t *where, uint32_t character, uint8_t foreground_colour, uint8_t background_colour) {
int row;
uint8_t row_data;
uint32_t mask1, mask2;
uint8_t *font_data_for_char = &system_font_data_address[character * 8];
uint32_t packed_foreground = (foreground << 24) | (foreground << 16) | (foreground << 8) | foreground;
uint32_t packed_background = (background << 24) | (background << 16) | (background << 8) | background;

for (row = 0; row < 8; row++) {
row_data = font_data_for_char[row];
mask1 = font_data_lookup_table[row_data / 16];
mask2 = font_data_lookup_table[row_data & 0x0F];
*(uint32_t *)where = (packed_foreground & mask1) | (packed_background & ~mask1);
*(uint32_t *)(&where[4]) = (packed_foreground & mask2) | (packed_background & ~mask2);
where += bytes_per_line;
}
}
</source>

==See Also==
*[[How_do_I_set_a_graphics_mode|Extra Notes]]
*[http://bos.asmhackers.net/forum/viewtopic.php?id=65 Covers basically the same, and is ASM-oriented.]
*[[3D Renderer Basics]]
*[https://thecool1james.github.io/tutorials/2016/10/13/osdev-font.html A Tutorial on Encoding Fonts]

[[Category:Video]]
[[Category:Graphical UI]]
[[Category:Tutorials]]

Latest revision as of 09:41, 22 June 2024

This page is a redirect.
This redirect is categorized in following manner: