关于显卡的基本工作原理

来源:互联网 发布:java hashcode 算法 编辑:程序博客网 时间:2024/05/17 23:21

基本显示原理如下:

 

一.顯示器的工作原理 
目前在個人計算機上廣泛使用的是採用陰極射線管(CRT)的光柵掃瞄顯示器,我們在屏幕上所看到的顏色是由電子槍發出的電子束打在CRT屏幕背面的螢光層上的點形成的,通過控制點的亮度可以產生不同的顏色。電子束不斷地從左到右、從上到下掃瞄整個屏幕,使屏幕顯示出圖案,電子束以大約每秒70次的速率在屏幕上重畫這一圖案,這個過程稱為顯示刷新或屏幕刷新,具體的掃瞄頻率依賴於所用的顯示適配器(又稱為顯示卡)。電子束從屏幕的左上角開始向右掃瞄,到達屏幕的右邊緣後,電子束被關閉(水平斷開),接著它又迅速地返回到屏幕的左邊緣(水平回掃)開始進行下一行水平方向的掃瞄,在完成全部的水平方向的掃瞄後,電子束在屏幕的右下角結束,此時電子束被關閉(垂直斷開),接著又迅速地返回到屏幕的左上角(垂直回掃),開始下一屏掃瞄。電子束就是這樣週而復始地掃瞄整個屏幕。顯示器在兩種方式下工作:文本方式和圖形方式,電腦遊戲一般在圖形方式下進行。 

二.顯示器的坐標系統 
計算機屏幕上的坐標與我們通常使用的直角坐標系不同,坐標原點(0,0)在屏幕的左上角,向右是水平方向的坐標,向下是垂直方向的坐標,且坐標沒有負值。 

三.顯示卡的結構 
顯示器上的顯示卡負責將圖形顯示在屏幕上。顯示存儲器中存放著在屏幕上顯示的圖像數據,顯示卡硬件不停地將顯存中的內容顯示在屏幕上。顯示存儲器實際上是安裝在顯示卡上的一塊或幾塊大規模集成電路,其容量有1M、2M、4M、8M等,在DOS下我們可以訪問的內存只有1MB空間(這就是DOS的局限性所在),地址從00000H到FFFFFH,這段內存根據用途又分為不同的塊,系統分配給圖形緩衝區(顯示存儲器)的地址在A0000H到BFFFFH之間,大小為128KB,其中,VGA佔用了A0000H到AFFFFH段,共64KB,這段地址是內存映射地址,供我們訪問顯示存儲器用。在VGA 13H圖形模式下,顯示內存使用A0000H到AF9FFH的一段線性內存空間,每個字節表示一個點,對應屏幕上的一個像點,320*200的屏幕分辨率共需要64000個字節,剛好64KB,因為一個字節可以表示的最大整數值為256,所以每個像點就可以表示256種顏色。 

四.設置視頻模式 
畫圖以前必須使屏幕工作在圖形方式,這就要設置屏幕的視頻模式。設置視頻模式有許多種方法,其中調用視頻BIOS功能是最簡單的一種,通過調用BIOS中斷0x10的服務程序,可以很方便地設置屏幕模式。調用方法是將值0放入ah寄存器,顯示模式放入al寄存器中,然後調用int86()函數。 

設置視頻模式的函數: 

void SetVideoMode(int mode) 

union REGS r; 
r.h.ah=0; 
r.h.al=mode; 
int86(0x10,&r,&r); 


其中mode是視頻模式。 


五.在屏幕上畫點 
由於顯示存儲器是線性排列的,每個像點用一個字節來表示,所以對像點尋址非常容易,像點在顯示內存中的偏移地址可由這個公式確定:y*320+x,其中,y是像點在垂直方向的坐標,x是水平方向的坐標,320是屏幕的寬度。有了像點的偏移地址,然後加上顯示內存的首地址即可得到像點在顯示內存中的絕對地址。只要將表示點的顏色值放到這個地址處,就可以在屏幕上畫點了。 


首先建立一個指針VideoBufferPtr,使它指向顯示內存的首地址: 

char far *VideoBufferPtr=( char far *)0xa0000000; 

將這個指針加上像點的偏移地址,像點的最終地址就確定了,它等於: 

VideoBufferPtr+y*320+x; 

把顏色值color寫到這個地址: 

*(VideoBufferPtr+y*320+x)=color; 

畫點的函數: 

void DrawPoint(int x,int y,unsigned char color) 

*(VideoBufferPtr+y*320+x)=color; 



六.顯示字符 

遊戲經常在屏幕上打印出顯示遊戲狀態的文本,使遊戲者瞭解當前遊戲的狀態,這是遊戲與遊戲者進行交互的必不可少的。然而在圖形模式下顯示文本與在文本模式下有很大區別,在圖形模式下文本必須使用基於畫位圖的方法來顯示。這裡介紹一種簡便的方法:利用計算機只讀存儲器中的ASCII字體數據。在計算機只讀存儲器(ROM)中固化有ASCII字體數據,這可在基址F000:FA6E上找到。我們只需瞭解數據是如何存儲的,然後再得出存取該數據的算法,就可以用任意顏色在屏幕上用畫位圖的方法把字符畫出來。在ROM中,字符按照ASCII字符編碼的順序放置,由於每個字符為8*8的點陣,所以每個字符佔據8個字節的存儲空間,要找出某個字符,我們只需將這個字符的ASCII碼與8相乘,然後將結果加到基址F000:FA6E上。由於字符用畫位圖的方法來顯示,因此我們可以隨意給它們指定顏色(前景或背景顏色)。在圖形模式下,我們只需要兩個函數就能夠打印文本:一個用於顯示單個字符,另一個用來顯示字符串。 


定義一個遠指針指向ROM字符集的開始位置: 

char far *RomCharPtr=(char far *)0xf000fa6e; 

下面是在屏幕上打印字符和字符串的函數: 

1.打印字符的函數。 

void PrintChar(int cx,int cy,char c,unsigned char Fcolor,unsigned char Bcolor,int flag) 

int offset,x,y; 
char far *TempPtr; 
unsigned char bit_mask; 
TempPtr=RomCharPtr+(c<<3); 
offset=(cy<<8)+(cy<<6)+cx; 
for(y=0;y<8;y++) 

bit_mask=0x80; 
for(x=0;x<8;x++) 

if((*TempPtr&bit_mask)) 
*(VideoBufferPtr+offset+x)=Fcolor; 
else if(flag==1) 
*(VideoBufferPtr+offset+x)=Bcolor; 
bit_mask=(bit_mask>>1); 

offset+=320; 
TempPtr++; 



說明: 

cx,cy 是字符在屏幕上的坐標。 

c 字符的ASCII碼。 

Fcolor,Bcolor 分別是字符的前景和背景顏色。 

flag 打印標誌,當flag=1時顯示字符的背景色,否則打印的字符具有透明效果。 

2.打印字符串的函數。 

void PrintString(int x,int y,char *string,unsigned char Fcolor,unsigned char Bcolor,int flag) 

int index; 
for(index=0;string[index]!=0;index++) 
PrintChar(x+(index<<3),y,string[index],Fcolor,Bcolor,flag); 


說明: 

1.x,y 是字符串在屏幕上的坐標。 

2.*string 字符串指針。 

3.Fcolor,Bcolor 分別是字符串的前景和背景顏色。 

4.flag 打印標誌,當flag=1時顯示字符串的背景色,否則打印的字符串具有透明效果。 


七.設置顏色寄存器 

我們知道VGA顯示卡具有顯示256種顏色的能力,每種顏色能夠用一個0-255之間的數值來表示,那麼這些數值與我們在屏幕上實際見到的顏色之間有什麼關係呢?其實這些數值只是VGA顯示卡上的顏色寄存器的索引值,顏色寄存器裡才保存了屏幕上顏色的真實值。VGA顯示卡上有一個包含256個單元的顏色寄存器(又稱為調色板),每個單元由三部份組成,這三部份分別代表顏色中的紅、綠、藍三種成份(顯示器就是用這三種成份來組成任何我們所看到的顏色),用三個字節表示,顏色寄存器一共有768個字節(3*256=768)。當我們要在屏幕上顯示某種顏色時,顯示卡硬件就根據顏色的索引值在顏色寄存器中查找,找到後再從相應單元中取出顏色值顯示在屏幕上,這個過程與畫家使用調色板相似,顏色寄存器相當於調色板,顏色寄存器中的單元相當於調色板上的色格,在色格中裝有預先調好的顏色,當畫家需要用某種顏色作畫時,就從裝有那種顏色的色格中把顏色取出來。例如,我們要顯示顏色索引值為30的顏色,顯示卡硬件就去查找顏色寄存器的第30單元,30單元位於距顏色寄存器首址3*30=90處(因為每個單元有三個字節),然後取出90處記錄有紅、綠、藍三種成份的三個字節作為在屏幕上顯示的色彩信號。但是實際上每個字節只用了六位來表示顏色,其它兩位沒用,這六位表示的數的值域為0-63,所以每種顏色(紅、綠、藍)成份具有64種亮度的表現能力,三種顏色成份組合共可以產生64*64*64=262,144種顏色(VGA 13H模式從這262,144種顏色中取出256種在同一屏幕上顯示)。我們可以通過事先設置顏色寄存器的值來使用我們自己的顏色。 
設置顏色寄存器有多種方法,如調用BIOS功能,但是這種方法速度比較慢,遊戲設計中通常採用直接訪問VGA顯示卡的I/O端口的方法來快速設置顏色寄存器,我們只需訪問四個I/O端口就可以完成設置顏色寄存器的工作。這四個端口分別是: 
0x3c6、0x3c7、0x3c8和0x3c9。 
端口0x3c6稱為調色板屏蔽寄存器,用來屏蔽所要求的調色板寄存器的位,如果你在這個寄存器中放入0xff,你就可以通過調色板索引寄存器0x3c7和0x3c8(一個用於讀,一個用於寫)訪問任何你希望訪問的顏色寄存器,端口0x3c9稱為調色板數據寄存器,紅、綠、藍三種成份就是通過它進行讀寫(顏色值要讀或寫三次)。 


我們定義一個結構來方便處理顏色寄存器: 

typedef struct RGB_COLOR 

unsigned char red; 
unsigned char green; 
unsigned char blue; 
}RGBColor,*RGBColorPtr; 

結構中的red、green和blue變量用來保存顏色的紅、綠、藍三種成份。 

設置顏色寄存器值的函數: 

void SetPaletteRegister(int index,RGBColorPtr color) 

outportb(0x3c6,0xff); 
outportb(0x3c8,index); 
outportb(0x3c9,color->red); 
outportb(0x3c9,color->green); 
outportb(0x3c9,color->blue); 


獲取顏色寄存器值的函數: 

void GetPaletteRegister(int index,RGBColorPtr color) 

outportb(0x3c6,0xff); 
outportb(0x3c7,index); 
color->red=inportb(0x3c9); 
color->green=inportb(0x3c9); 
color->blue=inportb(0x3c9); 



八.在屏幕上畫位圖 
計算機繪製圖像通常採用一種稱為位映射圖(BITMAP)的圖形處理方法進行,位映射圖是一個矩形的點陣結構(二維矩陣),顯示在屏幕上時,對應屏幕上一個矩形區域,組成位圖的數據儲存在內存中一段連續的區間。我們比較常見的位圖文件有:BMP、PCX、GIF、JPG等。位圖通常存儲在外部文件中,使用以前必須將其從磁盤文件調入內存。下面介紹將一個256色PCX圖形文件讀入內存的方法: 


1.定義PCX文件頭結構: 

typedef struct PCX_HEADER 

char menufactrue; /* 廠家標識編號 0x0a */ 
char version; /* 文件版本編號 */ 
char packing_type; /* 壓縮模式 */ 
char bits_per_pixel; /* 每點佔用的位數 */ 
int minx; /* 最小X坐標值 */ 
int miny; /* 最小Y坐標值 */ 
int maxx; /* 最大X坐標值 */ 
int maxy; /* 最大Y坐標值 */ 
int hres; /* 水平分辨率 */ 
int vres; /* 垂直分辨率 */ 
char palette[48]; /* 顏色調色板 */ 
char unused; /* 未使用 */ 
char bit_plance; /* 位平面個數 */ 
int bytes; /* 單一水平線佔用的字節數 */ 
int palette_type; /* 調色板類型 */ 
char unused2[58]; /* 未使用 */ 
}PCXHeader,*PCXHeaderPtr; 

2.定義用來存放PCX圖像數據的結構: 

typedef struct PCX_PICTURE 

int width; 
int height; 
char far *buffer; 
RGBColor palette[256]; 
}PCXPicture,*PCXPicturePtr; 

3.初始化圖像數據的函數: 

int InitPCX(PCXPicturePtr image,int w,int h) 

unsigned size=w*h; 
image->width=w; 
image->height=h; 
image->buffer=(char far *)farmalloc(size); 
if(image->buffer==NULL) return 0; 
return 1; 


4.從外部文件讀入數據的函數: 

int LoadPCX(char *filename,PCXPicturePtr image,int flag) 

FILE *fp; 
unsigned num_bytes,count,size; 
int index; 
unsigned char data; 
PCXHeader PcxHeader; 
size=image->width*image->height; 
if((fp=fopen(filename,"rb"))==NULL) 
return 0; 
fread(&PcxHeader,sizeof(PCXHeader),1,fp); 
count=0; 
while(count<=size) 

data=fgetc(fp); 
if(data>=192&&data<=255) 

num_bytes=data-192; 
data=fgetc(fp); 
while(num_bytes-->0) 

*(image->buffer+count)=data; 
++count; 


else 

*(image->buffer+count)=data; 
++count; 


fseek(fp,-768L,SEEK_END); 
for(index=0;index<256;index++) 

image->palette[index].red=((fgetc(fp))>>2); 
image->palette[index].green=((fgetc(fp))>>2); 
image->palette[index].blue=((fgetc(fp))>>2); 

fclose(fp); 
if(flag==1) 
for(index=0;index<256;index++) 
SetPaletteRegister(index,(RGBColorPtr)&image->palette[index]); 
return 1; 


其中參數flag用來指明調入文件的同時是否設置顏色寄存器(flag=1設置)。 

5.畫位圖的函數: 

void DrawImage(int x,int y,int width,int height,char far *image) 

int i,j; 
for(i=0;i<height;i++) 
for(j=0;j<width;j++) 

if(*image!=0&&(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200) 
DrawPoint(x+j,y+i,*image); 
image++; 



x,y是圖像在屏幕上的左上角坐標,width,height是圖像的寬度和高度,image是指向內存中圖像的指針。 

我們對if(*image!=0&&(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200)語句進行一下分析: 

*image!=0用來檢查所畫的顏色值是否是透明色,如果是,則不畫出來,這樣我們就可以畫出有透明效果的圖像,即透過圖像可以看到背景,透明色通常取值0,也可以用其他的顏色值表示。 
(x+j)>=0&&(x+j)<320&&(y+i)>=0&&(y+i)<200語句用來判斷所畫點的坐標是否超出屏幕顯示的範圍,這樣可以畫出具有裁剪效果的圖像,如圖像的一部份在屏幕外。 

這個函數並不是最快的,因為它要執行width X height次判斷,更快的函數請看VGA13H函數庫中的繪圖函數。 

 

 

 

 


 

 

 

VGA顯示系統 

顯示硬件基礎 

  進行TC256色圖形編程時,是直接與顯卡硬件在打交道。那麼,就讓我們從顯卡談起吧。 
  顯示卡(adapter)是一塊插在PC主機的電路版。一般顯示卡由寄存器、存儲器(顯示RAM和ROM BIOS)、控制電路三大部分組成。隨著PC機的發展,PC機的顯示系統經歷了由CGA、EGA、VGA、SVGA到現在的VESA系列的發展過程。 
  在早期的顯示系統中,CGA、EGA等顯示卡配置的顯示器是嚴格配套的,一種顯卡只與一種顯示器匹配使用。隨著技術的發展,出現了可變頻的顯示器,它使用自動跟蹤技術,同一顯示器可以適應CGA、EGA、VGA等各種顯示卡。這使得我們用TC操作顯示卡模擬VESA工作模式成為可能。 
  顏色和灰度是衡量顯示系統的又一重要參數。最初的彩色顯示系統CGA只能顯示固定的16種顏色,到EGA時可以使用64種顏色種的16種顏色,而發展到VGA時可以使用256K種顏色中的16種顏色,現在的顯示卡甚至可以支持到32位真彩色並使用所有顏色。顏色與灰度的發展受顯示內存的限制。在16色模式中,一個像素點只需要log2 16=4位內存即半個字節;而在256色模式中,一個像素點則要占log2 256=8位即一個字節;在16位真彩色模式下,一個像素要佔用整整兩個字節。所以,在CGA高分辨率方式下,只能同時使用兩種顏色,因為640*200*1/8 = 16000(byte)接近16k。而到了VGA,實現640*480分辨率16種顏色時,由於一個像素有16種顏色,最少需要占4位,因此,需要內存640*480*1/8 = 153600(byte)≒ 150k。 


此外,RAM的擴大還受到PC機中留作顯示用的地址空間的限制,一般不超過64K。因為,當使用大於64K的地址空間時,通過PC機訪問顯示緩衝區的控制就變得較為複雜了(使用換頁機制)。 
  現在的顯卡可以兼容多種顯示模式,因此,顯卡在固化的ROM BIOS中為每一種模式分配了一個代號。用戶在使用PC機編程時,直接輸入該代號,再通過顯示卡的BIOS調用即可使用此種模式。 
  通常,顯示模式號<0x14的是標準模式,其它的為非標準模式,非標準模式因顯示卡的不同而不同。 
  1.下表列舉了所有的標準字符模式:<點擊此處> 
  2.下表列舉了所有的標準圖形模式:<點擊此處> 
  3.非標準字符模式: 
  這類字符模式一般與某種顯卡有關,其模式號由某類生產廠家自行確定。這類顯示模式的行列變化範圍都比較大,但均超出25行*80列的範圍。 
  4.非標準圖形模式: 
  非標準的圖形模式也是變化多樣的,但每個卡的特點主要體現在這些圖形模式中。下表介紹了常見的非標準圖形模式:<點擊此處> 

顯示緩衝區與顏色定義 

  PC機的顯示卡上的RAM存儲器中的數據按照顯示器顯示格式進行存儲。我們知道 ,計算機只能以二進制方式存儲數據,每位有兩種狀態(0與1)。對於單色顯示器,內存中只需存放一張表,表中每一位對應屏幕上一個像素點,該位為1則表示該點是亮點。而對於彩色顯卡來,要表示屏幕上像素點的信息,僅用一為就不夠了。對於顯示16色的顯示模式,就需4位定義一種顏色。到了EGA顯示卡時,已經開始有了顯示卡兼容性。在VGA模式時由於顏色較多,分辨率提高帶來點數的增加,所需要的顯存增大,從而出現了一種以「彩色位平面」的存儲結構來表示顏色信息。CGA400、CEGA、CH、VGA均採用彩色位平面的存儲結構。一般VGA的基本配置有256K的顯示緩衝區(BANK A)。當支持16種以下的顏色時,使用存儲位平面結構,此時256K的顯示緩衝區被分位4個64K的存儲位平面,屏幕上的一個點,由存儲位平面的各一位組合後表示,最多表示16種顏色。當支持256種顏色時,要使用線性內存結構,4個存儲位平麵線性鏈接,形成256K的線性內存(Linear system)。 
  在使用16色和256色模式顯示時,需要一張顏色表,以將顯存的數據「翻譯」為屏幕上的點信息。所謂顏色表,就是我們通常所指的「調色板」。當顯示器要顯示屏幕上的一個點時,先由顯示卡將顯存中的數據讀出,然後對照顏色表,得到一組RGB顏色信息,然後調整顯示器的射線管,在屏幕相應位置顯示一個點。當顯存中所有的點都顯示後,就得到我們所看到的圖像。用戶還可以根據自己顯示的需要,更改顏色表的RGB對應值,得到我們自定義的顏色。 
  在顯示模式到了真彩色級別時,由於顯示內存中存儲的已經是像素點的RGB信息,因此調色板就變得沒有意義。因此,在真彩色模式中,不再需要調色板。 

視頻BIOS ROM 

  顯示卡都有自己的專用視頻BIOS支持。此BIOS是視頻控制程序,固化在ROM中,成為顯卡的一部分,顯示卡上的視頻BIOS功能要比PC的視頻BIOS強大的多,它除了支持CGA、EGA、VGA等各標準顯示模式外,還支持各種專用模式和針對顯存的專用操作,可以獨立於CPU單獨處理顯存圖像,比如2D變換、3D環境計算等等非常複雜的運算。一般VGA視頻BIOS的入口地址在C000H——CFFFFH之間。 

BMP是bitmap的縮寫形式,bitmap顧名思義,就是位圖也即Windows位圖。它一般由4部分組成:文件頭信息塊、圖像描述信息塊、顏色表(在真彩色模式無顏色表)和圖像數據區組成。在系統中以BMP為擴展名保存。 
  打開Windows的畫圖程序,在保存圖像時,可以看到三個選項:2色位圖(黑白)、16色位圖、256色位圖和24位位圖。這是最普通的生成位圖的工具,在這裡講解的BMP位圖形式,主要就是指用畫圖生成的位圖(當然,也可以用其它工具軟件生成)。 
  現在講解BMP的4個組成部分: 

1.文件頭信息塊 

0000-0001:文件標識,為字母ASCII碼「BM」。 
0002-0005:文件大小。 
0006-0009:保留,每字節以「00」填寫。 
000A-000D:記錄圖像數據區的起始位置。各字節的信息依次含義為:文件頭信息塊大小,圖像描述信息塊的大小,圖像顏色表的大小,保留(為01)。 


2.圖像描述信息塊 


000E-0011:圖像描述信息塊的大小,常為28H。 
0012-0015:圖像寬度。 
0016-0019:圖像高度。 
001A-001B:圖像的plane總數(恆為1)。 

001C-001D:記錄像素的位數,很重要的數值,圖像的顏色數由該值決定。 
001E-0021:數據壓縮方式(數值位0:不壓縮;1:8位壓縮;2:4位壓縮)。 
0022-0025:圖像區數據的大小。 
0026-0029:水平每米有多少像素,在設備無關位圖(.DIB)中,每字節以00H填寫。 
002A-002D:垂直每米有多少像素,在設備無關位圖(.DIB)中,每字節以00H填寫。 
002E-0031:此圖像所用的顏色數,如值為0,表示所有顏色一樣重要。 

3.顏色表 

  顏色表的大小根據所使用的顏色模式而定:2色圖像為8字節;16色圖像位64字節;256色圖像為1024字節。其中,每4字節表示一種顏色,並以B(藍色)、G(綠色)、R(紅色)、alpha(32位位圖的透明度值,一般不需要)。即首先4字節表示顏色號0的顏色,接下來表示顏色號1的顏色,依此類推。 

4.圖像數據區 

  顏色表接下來位為位圖文件的圖像數據區,在此部分記錄著每點像素對應的顏色號,其記錄方式也隨顏色模式而定,既2色圖像每點占1位(8位為1字節);16色圖像每點占4位(半字節);256色圖像每點占8位(1字節);真彩色圖像每點占24位(3字節)。所以,整個數據區的大小也會隨之變化。究其規律而言,可的出如下計算公式:圖像數據信息大小=(圖像寬度*圖像高度*記錄像素的位數)/8。 

然而,未壓縮的圖像信息區的大小。除了真彩色模式外,其餘的均大於或等於數據信息的大小。這是為什麼呢?原因有兩個: 
  1.BMP文件記錄一行圖像是以字節為單位的。因此,就不存在一個字節中的數據位信息表示的點在不同的兩行中。也就是說,設顯示模式位16色,在每個字節分配兩個點信息時,如果圖像的寬度位奇數,那麼最後一個像素點的信息將獨佔一個字節,這個字節的後4位將沒有意義。接下來的一個字節將開始記錄下一行的信息。 
  2.為了顯示的方便,除了真彩色外,其他的每中顏色模式的行字節數要用數據「00」補齊為4的整數倍。如果顯示模式為16色,當圖像寬為19時,存儲時每行則要補充4-(19/2+1)%4=2個字節(加1是因為裡面有一個像素點要獨佔了一字節)。如果顯示模式為256色,當圖像寬為19時,每行也要補充4-19%4=1個字節。 
  還有一點我要申明,當屏幕初始化為16或256色模式時,一定要設置調色板或修正顏色值,否則無法得到正確的圖像顏色。 
置256色圖形模式 

  要使用256色圖形模式,使用TC的界面模式是不行的,因為它為VGA16色的文本模式,要使用256圖形模式,則要調用顯卡BIOS進行圖形界面初始化。具體是調用10H的顯示中斷,將功能號AH置為0,子功能號AL置為要使用的模式號,以調用IBM兼容顯卡的320×200的256色模式為列,程序如下。 
#include "dos.h" 
void init256(int Vmode) 
{ union REGS r; 
 r.h.ah=0; 
 r.h.al=Vmode; 
 int86(0x10,&r,&r); 

main() 
{ init256(0x13); 
 getch(); 
 init256(0x3); 

運行後,屏幕進行了一次閃爍,實際上,這是屏幕顯示模式切換引起的。切換模式後,屏幕一片黑暗,因為切換模式後,未對屏幕進行任何操作。接下來,我將演示在256色模式下對顯卡的操作。 

訪問顯存 

  初始化得到了256色圖形模式的屏幕,接下來要做的就是向屏幕顯示圖像了。在顯示圖像之前,我要介紹一個很重要的far指針。也許你在16色VGAHI模式裡用過,那就是內存中的圖形模式顯存映像指針0xa0000000,說它是映像指針,是因為顯示器只與顯示卡直接打交道,所以要更改屏幕內容還需更改顯存內容。因此,內存中提供一塊64k的區域,通過顯示中斷可以使它映射為顯存的不同區域,更改該內存中的值即更改了相應映射區域的顯存。讓我們先試一下下面的程序:(注意:運行時,最好將內存模式設置為large,否則可能出錯,以後我不再提示) 
#include "dos.h" 
main() 
{ long i; 
 char far *p=MK_FP(0xa000,0000); 
 union REGS r; 
 r.x.ax=0x13; 
 int86(0x10,&r,&r); 
 for(i=0;i<320*200L;i++)*(p+i)=i/320; 
 getch(); 
 r.x.ax=0x3; 
 int86(0x10,&r,&r); 



看見了什麼,你會驚詫地發現:多麼豐富的色彩!DOS模式下的256色顯示實現了! 

顯示卡換頁 

  前面將過由於內存只提供64k的內存進行顯存操作,在320×200的256模式下尚可訪問所有的屏幕像素點。但是在320×200以上的256模式下就不行了,因為在此模式下,比如在640×480的256模式下,存儲一屏所有的像素點需要640×480byte=300k的存儲單元。比如使用以下程序: 
#include "dos.h" 
main() 
{ long i; 
 char far *p=0xa0000000; 
 union REGS r; 
 r.x.ax=0x5f;/*TNT2、GeForce2的640×480的256色模式均使用該模式號*/ 
 int86(0x10,&r,&r); 
 for(i=0;i<640*400L;i++)*(p+i)=(i/640)%256; 
 getch(); 
 r.x.ax=0x3;/*恢復為16色文本模式*/ 
 int86(0x10,&r,&r); 

 

 


 

 

VESA虛擬屏幕編程 


-------------------------------------------------------------------------------- 

  本文已刊載於《計算機世界》1999年6月21日E23版,但這裡所公佈的比《計算機世界》更全面,請不要錯過機會。 
摘 要  本文提供了基於虛擬屏幕方案的VESA顯示驅動程序(800x600x16位增強色),包括一個對屏幕進行全屏刷新速度測試的示例程序。 
關鍵詞  VESA標準 顯示驅動程序 虛擬屏幕 遊戲編程 
開發環境 Watcom C++10.0、32位DOS平台 


-------------------------------------------------------------------------------- 

一、關於虛擬屏幕 

  虛擬屏幕技術是當今電腦遊戲編程中採用的一項先進技術,但此其編程內幕卻未見在電腦或軟件期刊、網頁等媒體上有多少透露。本人不揣冒昧,願將自己實踐的結果公諸於世,供廣大遊戲編程愛好者參考。 

  所謂虛擬屏幕,就是在內存中分配一塊內存,並將其當成顯存進行繪圖操作,當繪圖完成時,就將其複製到顯存中。 

  使用虛擬屏幕技術有什麼好處呢? 

  一、操作方便,速度快。在遊戲中,有大量的圖塊存取操作。但在高清晰度下,顯存是分頁操作的,特別是行內分頁的顯示模式下進行圖塊存取更是困難。由於虛擬屏幕的物理位置是在內存中的,所以不存在分頁問題,而且在操作速度上要比顯存快上二倍多。 

  二、虛擬屏幕大小不受顯示分辨率的影響。在內存中,可以創建任意大小的虛擬屏幕,在顯示時只拷貝一部分到顯存即可。 

  三、有利於播放動畫。在內存中,我們可以創建多個虛擬屏幕,各存入一幀圖像,然後按順序進行顯示,即可以實現動畫的快速播放。 

  四、卷軸動畫。所謂卷軸動畫,即創建幾層虛擬屏幕,一層疊加一層。《真侍魂》就是利用這一技術的代表作遊戲。 

二、虛擬屏幕技術的適用範圍 

  虛擬屏幕的應用主要有三種: 
  一、大屏幕。常見的大型遊戲中,比如《仙劍奇俠傳》、《紅色警戒》都是先將整個地圖創建成一個超大的虛擬屏幕,在虛擬屏幕上進行操作。顯示時就將要顯示的一部分拷貝到顯存中顯示出來。而只將提示信息、菜單等操作在真實屏幕中進行。 

  二、多層屏幕卷軸動畫。這一方面也主要應用於遊戲中。著名的2D格鬥遊戲《街霸》、《真侍魂》和國產RPG《江湖》就充分的利用了這一技術,在《真侍魂》中,程序創建了八層虛擬屏幕,分別是遠背景(雲,月,星星等)、近背景、裁判、第一層可砍景物(柳生場地右邊的竹子)、人物、動物(狗,鷹,猴)和氣功(旋風,火等)、第二層可砍景物(柳生場地左邊的竹子)、血槽和氣量表。對八個層次的虛擬屏幕分別進行操作,然後進行組合,組合時通過不同的移動速度就可以產生很強的立體感(遠景移動快;近景移動慢)。 

  三、多個屏幕。這一技術在MacOS、Windows(2.0及更高版本)、OS/2等圖形界面操作系統中沒有不使用的。拿常見的Windows來說,Windows為每個窗口建立一個虛擬屏幕,等有了窗口刷新的消息時,才將虛擬屏幕中的內容在真實屏幕上顯示出來。 

三、關於VESA、32位DOS平台和Watcom C++ 

  VESA是當今普遍使用的顯示卡編程接口,可以說在市場上已經沒有了不支持VESA標準的顯示卡。所以,我們在編寫顯示程序時只需編寫一個VESA顯示程序即可獲得最大的兼容性。 

  在16位實模式的DOS平台下,能夠直接操作的內存只有640k,而現在的內存16MB已是相當普及了。如果使用EMS或XMS,則要進行二次數據存取,將大大影響操作速度。倘若在編程時採用DPMI 32位保護模式技術進行編程,雖然可以越過640k基本內存的限制,但由於程序仍然是16位的,讓CPU在32位保護模式下運行16位程序,運行速度將會變的相當慢。然而,使用Watcom C/C++ 32位模式進行編程,則可以直接實現由16位到32位、640k到4GB的飛躍。當今的一些著名遊戲都是使用Watcom C/C++進行編寫的,例如《金庸群俠傳》、《阿貓阿狗》、《紅色警戒》等等。 

四、編程注意事項 

  由於Watcom C/C++編出的程序是32位的,所以在編程時同Borland C/C++有所不同。要注意以下幾點: 

  一、數據長度不同。在Borland C/C++3.1中,int數據是16位的。而在Watcom C/C++中,int數據則是32位的。 

  二,內存地址不同。32位保護模式下的內存(包括顯存在內)都是經過重新影射過的。同實模式下的內存地址不同。如果使用實模式的顯存地址進行讀寫,那後果則是無法預料的。所以在進行顯存讀寫操作時,必須對顯示地址進行轉換之後才能使用。 

  三、中斷調用方式不同。在32位保護模式下,不能使用int86、int86x進行中斷調用。要使用int386、int386x進行中斷調用。 

五、程序的特點 


  這是一套為開發遊戲而編寫的及其商業化的顯示驅動程序。其顯示速度超快無比。在Pentium 133、800x600x16位增強色模式、中文Windows98的DOS窗口下,全屏刷新速度高達每秒40屏左右,最高時可達每秒45屏。若是在純DOS方式下速度更快。而DirectX6.0中的DirectDraw6.0只能達到每秒鐘27屏左右。這主要是由於Windows不允許軟件直接操作硬件造成的,用DirectX編寫的遊戲,窗口的虛擬屏幕中其實根本就沒有寫入任何東西。DirectX越過了Windows的兼管,直接向顯存中寫入數據來實現快速顯示。這種手法真是讓人好笑,DirectX讓Windows程序又回到了DOS狀態。因為DOS程序從來都是直接操作硬件的,所以速度要比Windows程序快的多。 

  說它商業化,不僅是因為它速度快,主要是因為它是基於遊戲需要,完全為2D遊戲所設計編寫的。所以只提供了幾個簡單的函數:畫點(原本也沒有)、畫水平線、畫垂直線、畫矩形、畫塊;而沒有提供畫線、畫圓等函數。由於篇幅限制,省略了圖形變換、顯示中英文字符、顯示窗口、圖塊操作等函數。(如若需要可向作者索取) 

六、程序使用方法 

  本套顯示驅動程序有二個文件,頭文件Svga.h和源程序文件Svga.cpp。使用時將Svga.h連入程序,編譯時連Svga.cpp一起編譯即可。在程序中,首先要像DirectDraw一樣建立一個控制指針,再通過此指針進行圖形操作,要顯示時再執行Redraw()函數進行屏幕刷新。 

七、源程序 


示例程序TEST.CPP 
#include"svga.h" //加入本顯示驅動程序 
#include<conio.h> 
#include<time.h> 
uvar8 test() //屏幕刷新速度測試函數 

time_t t1,t2; //二個時間記載變量 
uvar16 i; 
VIEW vga; //創建控制指針 
for(i=0;i<512;i++) 
vga.HLine(0,i,800,RGB(255,i/2,0)); //畫水平線 
t1=time(NULL); //記載第一個時間 
for(i=0;i<256;i++) //屏幕刷新256次 
vga.Redraw(); //調用屏幕刷新函數 
t2=time(NULL); //記載第二個時間 
return(t2-t1); //返回兩個時間差 
} //函數退出時,控制指針自動關閉圖形模式 
void main() 

printf("256 screen/ %d second/n",test()); //顯示刷新256屏所用的秒數 

源程序Svga.h 
typedef signed char var8; // 8位有符號變量 
typedef unsigned char uvar8; // 8位無符號變量 
typedef signed short var16; //16位有符號變量 
typedef unsigned short uvar16; //16位無符號變量 
typedef signed int var32; //32位有符號變量 
typedef unsigned int uvar32; //32位無符號變量 
#define Yes 0 
#define No 1 
#include<stdio.h> 
#define RGB(r,g,b) (((r)/8)*2048+((g)/4)*32+(b)/8) //三原色轉換式 
class VIEW //顯示類 

uvar16 color; //當前操作顏色 
void SetVideo(uvar16); //初始化顯示模式 
void VideoPage(uvar8); //顯存跳頁函數 
protected: 
public: 
uvar16 *video,*_video; //虛擬屏幕內存指針 
VIEW(); //本類構造函數 
~VIEW(); //本類析構函數 
void Redraw(); //根據虛擬屏幕刷新真實屏幕 
void SetColor(uvar8 r,uvar8 g,uvar8 b) //根據3原色設置顏色 
{color=RGB(r,g,b);} 
void SetColor(uvar16 c) //直接設置顏色 
{color=c;} 
void Cls(); //設顏色為0並清屏幕 
void Clear(); //按當前顏色清屏幕 
void Clear(uvar16); //按指定顏色清屏 
void Clear(uvar8,uvar8,uvar8); //按指定3原色清屏 
void PutPixel(uvar16,uvar16); //畫點 
void PutPixel(uvar16,uvar16,uvar16); //指定顏色畫點 
void HLine(uvar16,uvar16,uvar16); //畫橫線 
void HLine(uvar16,uvar16,uvar16,uvar16); //指定顏色畫橫線 
void VLine(uvar16,uvar16,uvar16); //畫豎線 
void VLine(uvar16,uvar16,uvar16,uvar16); //指定顏色畫豎線 
void Rectangle(uvar16,uvar16,uvar16,uvar16); //畫空心矩形 
void Rectangle(uvar16,uvar16,uvar16,uvar16,uvar16); //指定顏色畫空心矩形 
void Bar(uvar16,uvar16,uvar16,uvar16); //畫實心矩形 
void Bar(uvar16,uvar16,uvar16,uvar16,uvar16); //指定顏色畫實心矩形 
}; 
源程序Svga.cpp 
#include"svga.h" 
#include<malloc.h> 
#include<i86.h> 
#include<string.h> 
VIEW::VIEW() //本類構造函數,自動執行 

SetVideo(0x114); //初始化圖形顯式模式 
_video=(uvar16 *)malloc(800*601*sizeof(uvar16)); //為虛擬屏幕分配內存 
video=_video;Cls(); //創建後備指針並清屏 

VIEW::~VIEW() //本類析構函數,自動執行 

free(_video); //釋放虛擬屏幕所用內存 
SetVideo(3); //初始化屏幕為文本狀態 

void VIEW::SetVideo(uvar16 mode) //設置顯示模式,mode為顯示模式 

union REGS i; 
if(mode>=0x100) //如果是VESA顯示模式 

i.w.bx=mode; 
i.w.ax=0x4F02; 
int386(0x10,&i,&i); 
return; 

else //如果是標準顯示模式 

i.w.ax=mode; 
int386(0x10,&i,&i); 
return; 


void VIEW::VideoPage(uvar8 pn) //選擇顯存頁函數,pn為頁號 

union REGS i; 
i.w.bx=0; 
i.w.dx=pn; 
//如果您的顯卡VESA標準版本較低,一頁是4k而不是64k的,將會顯示不正常,請將上一行改為: 
//i.w.dx=pn*16; 
i.w.ax=0x4F05; 
int386(0x10,&i,&i); 
return; 

void VIEW::Redraw() //屏幕刷新函數 

uvar8 i; 
uvar16 *VIDEO=(uvar16 *)(0xA000<<4); //顯存指針 
video=_video; //恢復虛擬屏幕指針 
for(i=0;i<14;i++) 

VideoPage(i); //選擇顯存頁面 
memcpy(VIDEO,video,65536); //從虛擬屏幕拷貝64k數據到顯存 
video+=32768; //虛擬屏幕指針後移 

VideoPage(14); //選擇最後一頁 
memcpy(VIDEO,video,42496); //寫入最後一頁數據 
video=_video; //恢復虛擬屏幕指針 

inline void VIEW::Cls() //將顏色置0(黑色)並清屏 
//內置式函數 

memset(_video,color=0,960000); //將整個虛擬屏幕全部寫入0 

inline void VIEW::Clear() //按當前顏色清屏,內置式函數 

Bar(0,0,799,599); //在整個屏幕內畫塊 

inline void VIEW::Clear(uvar16 c) //指定顏色並清屏,內置式函數 
// c為16位顏色代碼 

Bar(0,0,799,599,c); //按指定顏色在整個屏幕內畫塊 

void VIEW::Clear(uvar8 r,uvar8 g,uvar8 b) //指定三原色清屏 
// r=紅色量,g=綠色量,b=藍色量,範圍均為0-255 

SetColor(r,g,b); //按三原色設置顏色 
Bar(0,0,799,599); //在整個屏幕內畫塊 

void VIEW::PutPixel(uvar16 x,uvar16 y) //用當前顏色畫點函數 
// x=橫坐標,y=縱坐標 

if(x>799||y>599)return; //判斷坐標是否出界 
video=_video+y*800+x; //移動虛擬屏幕指針 
*video=color; //寫入顏色代碼 
video=_video; //恢復虛擬屏幕指針 

void VIEW::PutPixel(uvar16 x,uvar16 y,uvar16 c) //按指定顏色畫點 
// x=橫坐標,y=縱坐標,c=16位顏色代碼 

if(x>799||y>599)return; //判斷坐標是否出界 
video=_video+y*800+x; //移動虛擬屏幕指針 
*video=color=c; //寫入顏色代碼並改變當前顏色 
video=_video; //恢復虛擬屏幕指針 

void VIEW::HLine(uvar16 x,uvar16 y,uvar16 n) //畫水平線 
// x=橫坐標,y=縱坐標,n=水平線長度 

if(x>799||y>599)return; //判斷坐標是否出界 
if(x+n>800)n=800-x; //判斷水平線是否出界 
video=_video+y*800+x; //移動虛擬屏幕指針 
for(uvar16 i=0;i<n;i++)*video++=color; //寫入顏色代碼 
video=_video; //恢復虛擬屏幕指針 

void VIEW::HLine(uvar16 x,uvar16 y,uvar16 n,uvar16 c) //按指定顏色畫水平線 
// x=橫坐標,y=縱坐標,n=水平線長度,c=16位顏色代碼 

if(x>799||y>599)return; //判斷坐標是否出界 
if(x+n>800)n=800-x; //判斷水平線是否出界 
video=_video+y*800+x; //移動虛擬屏幕指針 
color=c; //改變當前顏色代碼 
for(uvar16 i=0;i<n;i++)*video++=color; //寫入顏色代碼 
video=_video; //恢復虛擬屏幕指針 

void VIEW::VLine(uvar16 x,uvar16 y,uvar16 n) //畫垂直線函數 
// x=橫坐標,y=縱坐標,n=垂直線長度 

if(x>799||y>599)return; //判斷坐標是否出界 
if(y+n>600)n=600-y; //判斷垂直線是否出界 
video=_video+y*800+x; //移動虛擬屏幕指針 
for(uvar16 i=0;i<n;i++) 

*video=color; //寫入顏色代碼 
video+=800; //移動虛擬屏幕指針 

video=_video; //恢復虛擬屏幕指針 

void VIEW::VLine(uvar16 x,uvar16 y,uvar16 n,uvar16 c) //按指定顏色畫垂直線 
// x=橫坐標,y=縱坐標,n=垂直線長度,c=16位顏色代碼 

if(x>799||y>599)return; //判斷坐標是否出界 
if(y+n>600)n=600-y; //判斷垂直線是否出界 
video=_video+y*800+x; //移動虛擬屏幕指針 
color=c; //改變當前使用顏色 
for(uvar16 i=0;i<n;i++) 

*video=color; //寫入顏色代碼 
video+=800; //移動虛擬屏幕指針 

video=_video; //恢復虛擬屏幕指針 

void VIEW::Rectangle(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1) //畫矩形框 
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標 

HLine(x,y ,x1-x+1); //畫上線 
HLine(x,y1,x1-x+1); //畫下線 
VLine(x,y+1,y1-y-1); //畫左線 
VLine(x1,y+1,y1-y-1); //畫右線 

void VIEW::Rectangle(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1,uvar16 c)//按指定顏色畫矩形框 
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標,c=16位顏色代碼 

HLine(x,y ,x1-x+1,c); //按指定顏色畫上線 
HLine(x,y1,x1-x+1); //畫下線 
VLine(x,y+1,y1-y-1); //畫左線 
VLine(x1,y+1,y1-y-1); //畫右線 

void VIEW::Bar(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1) //畫塊 
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標 

for(uvar16 i=y;i<=y1;i++)HLine(x,i,x1-x+1); //循環畫水平線 

void VIEW::Bar(uvar16 x,uvar16 y,uvar16 x1,uvar16 y1,uvar16 c) //按指定顏色畫塊 
// x=左上角橫坐標,y=左上角縱坐標,x1=右下角橫坐標,y1=右下角縱坐標,c=16位顏色代碼 

color=c; //改變當前操作顏色 
for(uvar16 i=y;i<=y1;i++)HLine(x,i,x1-x+1); //循環畫水平線 


示例程序編譯命令: WCL386 -fe=TEST -ox SVGA.CPP TEST.CPP 


以上可查看svgalib实现

原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 千牛重新获取enc-k怎么办 为什么说不能获取对方信息怎么办 qq炫舞客户端被修改怎么办 win7玩穿越卡顿怎么办 玩dnf就蓝屏怎么办win7 平板玩fgo闪退怎么办 微信总是说空间不足怎么办 激活卡时遇到服务器错误怎么办 悦平台服务器错误是怎么办 手机银行提示登录服务器错误怎么办 qq漂流瓶封了怎么办 我的世界被冻结怎么办 qq里被屏蔽了怎么办 qq领手游礼包账号存在异常怎么办 笔记本电脑太卡怎么办最有效 华为平板电脑忘记开机密码怎么办 平板电脑忘记开机密码怎么办 平板电脑忘了开机密码怎么办 qq文件已被损坏怎么办 斗地主没痘了怎么办 熹妃q传金币不够用怎么办 苹果手机玩王者卡怎么办 苹果6玩王者荣耀卡怎么办 苹果macbook开不了机怎么办 苹果7震动像拖拉机一样怎么办 win10笔记本玩lol卡怎么办 苹果笔记本密码忘了怎么办 苹果笔记本系统密码忘记了怎么办 qq加好友频繁了怎么办 淘宝买食品有问题怎么办 手机的设置图标没有了怎么办 国家创业贷款还不了会怎么办 手机mac显示:不好使.怎么办? 英雄联盟买皮肤重复怎么办 皮肤很油毛孔又粗怎么办 笔记本电脑玩英雄联盟卡怎么办 win10系统更新不动了怎么办 win7任务栏时间没了怎么办 win10桌面图标都没了怎么办 win10软件图标没了怎么办 电脑内存插板没用了怎么办