『楼 主』:
DJGPP教程2 -- VGA编程
[已使用 LLM 解释]
In this page I'll explain how to programming the VGA at a resolution of 320x200x256. I will also include a demo program (see it later).
Structure of the video memory at 320x200x256
The memory structure at this resolution is very simple to use: is only a sequence of bytes mapped in the central memory. First, remember this: a pixel on the screen is a byte in the memory. So at a resolution of 320x200 the screen use 64000 bytes of memory. This memory start at the address A000:0000 (A0000 in protected mode) of the main memory. Under a real mode system is simple to access at this memory by using a far pointer, like this:
char far *vgamemory = (char *)0xA0000000L;
or (on old compilators)
char far *vgamemory = (char *)MK_FP(0xA000, 0);
But under DJGPP isn't so simple because the memory is protected, so you can't access directly to all the memory of the system. The best solution is to disable the protection of the DOS memory by calling the procedure __djgpp_nearptr_enable(). There are a simple code that shows how to access at the DOS memory:
#include
__djgpp_nearptr_enable();
vgamemory=(char *)(0xa0000 + __djgpp_conventional_base);
__djgpp_nearptr_disable();
The header nearptr.h is necessary for the prototipes of the functions. We should use __djgpp_conventional_base to get the right address of the DOS memory (the protected mode is very intricate). At the end of the program, use __djgpp_nearptr_disable() to get the program back to regular protected mode. But you must remember that with this code you disable the protection of the memory, so you can damage the DOS area and block the system. Use the program info (you must download the file txi390b.zip) for more informations.
For clearing the screen with a specified color simply call the function memset() with a pointer to the video memory.
Setting the VGA mode 320x200x256
The BIOS supplies a procedure that provide to set the VGA mode 320x200x256 via interrupt 10h. We must only set a register (ax) and call this interrupt. To return to the text mode (usually mode 03h) we can use the same procedure. There are the simple code to swap to the wanted mode:
#define TEXT_MODE 0x03
#define VGA_MODE 0x13
void setmode(short mode)
{
union REGS regs;
regs.h.ah = 0x00; // Procedure 0x00: Set Video Mode
regs.h.al = mode; // The requested video mode
int86(0x10,®s,®s) // Call the BIOS
}
If you set the most significant bit of the register al (use 0x93 instead of 0x13) the video memory is not cleared. If you don't like to use the BIOS you can download the file modes.zip that explain how to set the video mode without the BIOS.
Put the first pixel
In the paragraph Structure of the video memory I've introduced how to access to the video memory. Now is very simple to draw a pixel: is enough to write at the right offset in the video memory. So I've draw a simple image to explain how the video memory is used at this resolution:
If you write a color byte at the start of the video memory (A000:0000) you'll show a pixel at the upper-left corner of the screen (0, 0). When you write at the offset 319 (A000:013F) you'll show a pixel at the upper right corner. Writing at the next byte (A000:0140) and the pixel will appear at the position (0, 1). So we can understand that if we want to show a pixel at the position (x, y) we must write at the address A000:(y*320+x) of the video memory. This is a fast method for write a pixel. But we can optimize a bit the speed by using shifting instead of multiplications. So we divide the number 320 into the powers of two 256 and 64 and we have 320y = 256y + 64y and next offset = (y<<8) + (y<<6) + x .
The basical algorithms
Lines
For drawing a line we must calculate where an immaginary line intersect an immaginary rect. For each square of the rect correspond a pixel on the screen. So for each square intersected by a immaginary line we have a pixel of the real line to draw. We must calculate what squares is intersected by the line. A simple method is by using the equation x-x1=m(y-y1) , but is a bit slow.
Instead we can use the Bresenham's line-drawing algorithm that use only additions and subtractions. It is based on the relation:
sizex/sizey=countx/county
sizex and sizey are the sizes of the rectangle that contain the line
countx and county are the counters impiegated by the algorithm
so we have:
county * sizex
calculatex= --------------
sizey
countx * sizey
calculatey= --------------
sizex
But the Bresenham's algorithm transform the equations for drawing the lines without the multiplications and the divisions. See the demo program for the code of the algorithm.
Rectangles
Drawing rectangles is very simple: simply drawing 2 horizontals and 2 verticals lines. But we can make an optimized code that not use the drawline() function and draw a rectangle more faster. We know the structure of the video memory, so is simple to design an algorithm for a rectangle.
draw sizex pixels from (x1, y1)
onerow:
jump 320-sizex pixels
draw a pixel
jump sizex-2 (intsizex) pixels
draw a pixel
while row drawed < sizey-2
jump 320-sizex pixel
draw sizex pixels
See the demo program for the code of the algorithm.
Circles
Now I've no time for this.
Using the palette
In mode 13h we can use 256 colors for display graphics. Each color is selected from a palette of 262144 colors, so if we must use a special color we can modify the default selection of a color for setting the one that we want. For make this is necessary to tell to the VGA card the number of the color that we want to change and next the RGB value. This value is a triple of byte in the range 0..63, that specify the intensity of Red, Green and Blue. Whit the mixing of these colors we can make the wanted color. The VGA card have 3 register for change the palette: RGB_READ for telling the index of the color to read, RGB_WRITE for telling the index of the color to write and RGB_DATA for read/write the RGB value. RGB_RESET is used for prepare the VGA card (isn't necessary). The following code shows how to setting the RGB value of the color 15 (WHITE):
#define RGB_RESET 0x03C6
#define RGB_READ 0x03C7
#define RGB_WRITE 0x03C8
#define RGB_DATA 0x03C9
void setwhite(void)
{
outp(RGB_RESET, 0xFF); // Prepare the VGA card
outp(RGB_WRITE, WHITE); // Tell that we want to write the color 15 (WHITE)
outp(RGB_DATA, 64); // Red value
outp(RGB_DATA, 64); // Green value
outp(RGB_DATA, 64); // Blue value
}
For setting all the 256 colors simply tell to the VGA card that we want to write the color 0, and next write the RGB of all the colors:
void setpalette(char *palette)
{
register int i;
outp(RGB_RESET, 0xFF); // Prepare the VGA card
outp(RGB_WRITE, 0); // Tell that we want to write the entire palette
for(i=0;i<256;i++) {
outp(RGB_DATA, palette[i*3]); // Red value
outp(RGB_DATA, palette[i*3+1]); // Green value
outp(RGB_DATA, palette[i*3+2]); // Blue value
}
}
For reading the RGB value of a color or of all the colors simply write the index to RGB_READ and read the values from RGB_DATA.See the demo program for the code.
How about snow or noise?
Is possible that a noise will appear on the screen when the palette is changed continuously. This effect is done because we change the palette in the same time that the VGA card update the screen. For prevent this effect is necessary to wait the retrace signal of the card via the bit 3 of the register 0x03DA. There are the code:
void waitretrace(void)
{
// Wait vertical retrace
while(inportb(0x03DA)&0x08);
// Wait refresh
while(!(inportb(0x03DA)&0x08));
}
In this page I'll explain how to programming the VGA at a resolution of 320x200x256. I will also include a demo program (see it later).
Structure of the video memory at 320x200x256
The memory structure at this resolution is very simple to use: is only a sequence of bytes mapped in the central memory. First, remember this: a pixel on the screen is a byte in the memory. So at a resolution of 320x200 the screen use 64000 bytes of memory. This memory start at the address A000:0000 (A0000 in protected mode) of the main memory. Under a real mode system is simple to access at this memory by using a far pointer, like this:
char far *vgamemory = (char *)0xA0000000L;
or (on old compilators)
char far *vgamemory = (char *)MK_FP(0xA000, 0);
But under DJGPP isn't so simple because the memory is protected, so you can't access directly to all the memory of the system. The best solution is to disable the protection of the DOS memory by calling the procedure __djgpp_nearptr_enable(). There are a simple code that shows how to access at the DOS memory:
#include
__djgpp_nearptr_enable();
vgamemory=(char *)(0xa0000 + __djgpp_conventional_base);
__djgpp_nearptr_disable();
The header nearptr.h is necessary for the prototipes of the functions. We should use __djgpp_conventional_base to get the right address of the DOS memory (the protected mode is very intricate). At the end of the program, use __djgpp_nearptr_disable() to get the program back to regular protected mode. But you must remember that with this code you disable the protection of the memory, so you can damage the DOS area and block the system. Use the program info (you must download the file txi390b.zip) for more informations.
For clearing the screen with a specified color simply call the function memset() with a pointer to the video memory.
Setting the VGA mode 320x200x256
The BIOS supplies a procedure that provide to set the VGA mode 320x200x256 via interrupt 10h. We must only set a register (ax) and call this interrupt. To return to the text mode (usually mode 03h) we can use the same procedure. There are the simple code to swap to the wanted mode:
#define TEXT_MODE 0x03
#define VGA_MODE 0x13
void setmode(short mode)
{
union REGS regs;
regs.h.ah = 0x00; // Procedure 0x00: Set Video Mode
regs.h.al = mode; // The requested video mode
int86(0x10,®s,®s) // Call the BIOS
}
If you set the most significant bit of the register al (use 0x93 instead of 0x13) the video memory is not cleared. If you don't like to use the BIOS you can download the file modes.zip that explain how to set the video mode without the BIOS.
Put the first pixel
In the paragraph Structure of the video memory I've introduced how to access to the video memory. Now is very simple to draw a pixel: is enough to write at the right offset in the video memory. So I've draw a simple image to explain how the video memory is used at this resolution:
If you write a color byte at the start of the video memory (A000:0000) you'll show a pixel at the upper-left corner of the screen (0, 0). When you write at the offset 319 (A000:013F) you'll show a pixel at the upper right corner. Writing at the next byte (A000:0140) and the pixel will appear at the position (0, 1). So we can understand that if we want to show a pixel at the position (x, y) we must write at the address A000:(y*320+x) of the video memory. This is a fast method for write a pixel. But we can optimize a bit the speed by using shifting instead of multiplications. So we divide the number 320 into the powers of two 256 and 64 and we have 320y = 256y + 64y and next offset = (y<<8) + (y<<6) + x .
The basical algorithms
Lines
For drawing a line we must calculate where an immaginary line intersect an immaginary rect. For each square of the rect correspond a pixel on the screen. So for each square intersected by a immaginary line we have a pixel of the real line to draw. We must calculate what squares is intersected by the line. A simple method is by using the equation x-x1=m(y-y1) , but is a bit slow.
Instead we can use the Bresenham's line-drawing algorithm that use only additions and subtractions. It is based on the relation:
sizex/sizey=countx/county
sizex and sizey are the sizes of the rectangle that contain the line
countx and county are the counters impiegated by the algorithm
so we have:
county * sizex
calculatex= --------------
sizey
countx * sizey
calculatey= --------------
sizex
But the Bresenham's algorithm transform the equations for drawing the lines without the multiplications and the divisions. See the demo program for the code of the algorithm.
Rectangles
Drawing rectangles is very simple: simply drawing 2 horizontals and 2 verticals lines. But we can make an optimized code that not use the drawline() function and draw a rectangle more faster. We know the structure of the video memory, so is simple to design an algorithm for a rectangle.
draw sizex pixels from (x1, y1)
onerow:
jump 320-sizex pixels
draw a pixel
jump sizex-2 (intsizex) pixels
draw a pixel
while row drawed < sizey-2
jump 320-sizex pixel
draw sizex pixels
See the demo program for the code of the algorithm.
Circles
Now I've no time for this.
Using the palette
In mode 13h we can use 256 colors for display graphics. Each color is selected from a palette of 262144 colors, so if we must use a special color we can modify the default selection of a color for setting the one that we want. For make this is necessary to tell to the VGA card the number of the color that we want to change and next the RGB value. This value is a triple of byte in the range 0..63, that specify the intensity of Red, Green and Blue. Whit the mixing of these colors we can make the wanted color. The VGA card have 3 register for change the palette: RGB_READ for telling the index of the color to read, RGB_WRITE for telling the index of the color to write and RGB_DATA for read/write the RGB value. RGB_RESET is used for prepare the VGA card (isn't necessary). The following code shows how to setting the RGB value of the color 15 (WHITE):
#define RGB_RESET 0x03C6
#define RGB_READ 0x03C7
#define RGB_WRITE 0x03C8
#define RGB_DATA 0x03C9
void setwhite(void)
{
outp(RGB_RESET, 0xFF); // Prepare the VGA card
outp(RGB_WRITE, WHITE); // Tell that we want to write the color 15 (WHITE)
outp(RGB_DATA, 64); // Red value
outp(RGB_DATA, 64); // Green value
outp(RGB_DATA, 64); // Blue value
}
For setting all the 256 colors simply tell to the VGA card that we want to write the color 0, and next write the RGB of all the colors:
void setpalette(char *palette)
{
register int i;
outp(RGB_RESET, 0xFF); // Prepare the VGA card
outp(RGB_WRITE, 0); // Tell that we want to write the entire palette
for(i=0;i<256;i++) {
outp(RGB_DATA, palette); // Red value
outp(RGB_DATA, palette); // Green value
outp(RGB_DATA, palette); // Blue value
}
}
For reading the RGB value of a color or of all the colors simply write the index to RGB_READ and read the values from RGB_DATA.See the demo program for the code.
How about snow or noise?
Is possible that a noise will appear on the screen when the palette is changed continuously. This effect is done because we change the palette in the same time that the VGA card update the screen. For prevent this effect is necessary to wait the retrace signal of the card via the bit 3 of the register 0x03DA. There are the code:
void waitretrace(void)
{
// Wait vertical retrace
while(inportb(0x03DA)&0x08);
// Wait refresh
while(!(inportb(0x03DA)&0x08));
}
LLM 解释
1. 视频内存结构解释
-
320x200x256分辨率下的内存结构:
- 在320x200x256分辨率下,屏幕由连续的字节组成,一个像素对应内存中的一个字节。该分辨率下屏幕占用64000字节内存,内存起始地址在实模式下为A000:0000(保护模式下为A0000)。
- 在DJGPP环境下,由于内存受保护,不能直接访问系统内存。需要调用
__djgpp_nearptr_enable() 来启用对DOS内存的访问,访问视频内存的地址计算为(char *)(0xa0000 + __djgpp_conventional_base) ,使用完后调用__djgpp_nearptr_disable() 恢复保护模式。例如:
#include <dpmi.h>
#include <go32.h>
__djgpp_nearptr_enable();
char far *vgamemory = (char *)(0xa0000 + __djgpp_conventional_base);
__djgpp_nearptr_disable();
- 清空屏幕可以使用
memset() 函数,传入视频内存指针和指定颜色来实现。
-
设置VGA模式:
- 通过BIOS中断10h设置VGA模式,例如设置为320x200x256模式(模式0x13)和文本模式(模式0x03)的代码如下:
#include <dos.h>
#define TEXT_MODE 0x03
#define VGA_MODE 0x13
void setmode(short mode) {
union REGS regs;
regs.h.ah = 0x00;
regs.h.al = mode;
int86(0x10, ®s, ®s);
}
2. 像素绘制
-
像素位置与内存偏移:屏幕上位置
(x, y) 的像素在视频内存中的偏移地址为A000:(y*320 + x) 。可以优化计算,利用位移操作:offset = (y << 8) + (y << 6) + x ,因为320y = 256y + 64y ,即y<<8 是256y,y<<6 是64y。
3. 基本算法
-
直线绘制(Bresenham算法):
- Bresenham算法基于
sizex/sizey = countx/county 的关系,通过加法和减法来计算直线经过的像素点。具体代码可参考示例程序,其避免了乘法和除法操作,提高了绘制效率。
-
矩形绘制:
- 可以通过绘制水平和垂直线来实现矩形,也可以优化代码直接利用视频内存结构来快速绘制矩形。例如,先绘制指定长度的一行像素,然后处理多行来形成矩形。
-
调色板使用:
- 在VGA模式13h下,可通过VGA卡的寄存器来修改调色板。使用
RGB_RESET 、RGB_WRITE 、RGB_DATA 等寄存器来设置颜色的RGB值。例如设置颜色15(白色)的代码:
#define RGB_RESET 0x03C6
#define RGB_WRITE 0x03C8
#define RGB_DATA 0x03C9
void setwhite(void) {
outp(RGB_RESET, 0xFF);
outp(RGB_WRITE, 15);
outp(RGB_DATA, 64);
outp(RGB_DATA, 64);
outp(RGB_DATA, 64);
}
void setpalette(char *palette) {
int i;
outp(RGB_RESET, 0xFF);
outp(RGB_WRITE, 0);
for (i = 0; i < 256; i++) {
outp(RGB_DATA, palette[i * 3]);
outp(RGB_DATA, palette[i * 3 + 1]);
outp(RGB_DATA, palette[i * 3 + 2]);
}
}
-
等待垂直回扫:
- 为了防止改变调色板时出现屏幕噪声,需要等待VGA卡的垂直回扫信号。通过读取寄存器0x03DA的位3来判断回扫状态,代码如下:
void waitretrace(void) {
while (inportb(0x03DA) & 0x08);
while (!(inportb(0x03DA) & 0x08));
}
以上就是在DJGPP环境下进行VGA 320x200x256分辨率编程的相关内容,包括内存访问、模式设置、像素绘制、基本图形算法、调色板操作以及回扫等待等方面的知识。
|