仙人指路的编程乐园
留言簿 主人介绍 返回主页
Windows驱动 毕设 电子电路 电脑 PE外壳 心情 Windows应用程序 随笔 Web技术 加密算法 古老的DOS编程 杂项
DOS下的SVGA编程
类别: 古老的DOS编程  上传时间: 2009-12-30 12:39:08  浏览(354)  留言(0) 
摘要:     虽然不同的SVGA显示卡的体系结构不同,但它们最初都是从标准VGA的结构上扩充而来的,包括五大功能模块,即显示控制器、定序器,属性控制器、图形控制器和显示存储器(VRAM)。SVGA卡640*480*256色的显示模式也和VGA卡的256色模式类似,每个像素点8位,整个VRAM地址空间按扫描行连续存放,超过 64K的地址空间采用位面映射机制分块影射到主机提供的地址上。主机提供的地址空间叫做窗口,SVGA把VRAM分成小块(称为位面),每块64K,分别映射到窗口上。

DOS下的SVGA编程

前言

在不经意间,我的头脑就闪过一丝疑惑,聪明的人都能把编程的资料在网上各种角落里找到,为什么我还要费力把它们写出来呢。但是,内心深处,我依然觉得写这些很有用。至少,在最低限度上,写了这些帮助我的同事们减少了查找相关资料的时间。更进一步,相对于网上零碎而不具体的资料,这些文件对于我们的工作很实际,我们肯定会用到它。

 

我会尽量把这几篇文件写得正确而详细,内容包括在我的工作中需要的所有知识。让其他人在实现这方面功能和遇到这方面问题的时候,不用再到处去找资料了。在浩渺的网络上搜索我们需要的东西也是件很折腾人的事情,但愿别人经历过的麻烦不要再让同事们再次经历。还有,开发DOS应用程序现在已经不多了,编程资料也不容易找,市场又不断地给DOS开发者提出新的要求,我就想让同事们在开发需要时想到我写的这些。

 

为了我们共同的事业,就不再多说了,进入正题。

 

第一章 SVGA显示卡和VBE标准

视频图形阵列适配器(vidio graphics array, VGA)IBM公司在1987年制定的显示卡标准,它提供的字符和图形两种模式,图形分辨率最大是640 * 480 * 16色或者320 * 200 * 256色,这个标准是显示卡发展的一个丰碑,改变了各厂商混战相互不兼容的局面,而且统一了软件接口标准,为程序开发提供了特别大的方便。VGA显示的调用方法放在了BIOS中,统一使用int 10h功能,主要功能有设置显示模式,文本窗口上卷下卷、光标和字符串,使用调色板、位面结构、读像素和写像素等。

 

当然,VGA逐渐不能满足需要了。在80年代末至90年代初,市场上出现了以TVGA系列、S3系列、Cirrus Logic系列、ET系列等为首的一批显示卡,它们都提供了比VGA分辨率更高、颜色更丰富的显示模式,又完全兼容VGA显示卡,它们被统称为Super VGA(超级VGA,简写为SVGA)。他们在高分辨率高颜色数的控制方面又需要制定一个新的标准。为此视频电子学标准协会VESA(Vidio Electronics Standards Association)提出了一组扩展的BIOS功能调用接口——VBE(VESA BIOS Extension)标准,在软件接口层次上实现了各种SVGA显示卡之间的兼容性。

 

时至今日,或许有些显示卡已不兼容VGA标准,但是所有的显示卡厂商都无一例外地支持VBE标准。几乎所有的Super VGA卡都提供了符合VESA SVGA标准的扩展BIOS。通过一组int 10h, AH=4Fh中断调用,使用VESA SVGA的扩展功能而不必了解各种显示卡的硬件细节,基于该标准编写的程序就具有非常广泛的硬件兼容性。

 

VBE标准到已经发布过3个版本。199110VESA发布了VBE1.2,这个标准规定了应用程序访问高性能显示卡的简易性接口,允许应用程序查询显示卡的特性并设置成合适的模式,包括分辨率,色彩丰富的模式。VBE1.2是现代显示卡广泛采用的标准。

 

199411月发布VBE2.0,它最重要的改进是增加了保护模式支持(VBE功能0Ah),提高了VBE的性能。另外,VESA组织还从VBE V2.0版标准开始增加了扩展VBE功能,包括显示器能源管理PM,显示器数据通道DDC等一些非常有用的功能。19989月发布VBE V3.0,主要增加了对显示卡,显示器扫描频率的控制和对平面显示器的控制。每个标准都完全向前兼容,在VESA的中文网站上可以免费下载到VBE3.0,网址http://www.vesa.org.cn

 

下面章节我们就开始讲SVGA 640*480*256色分辨率怎样调用VBE1.2 BIOS功能编程。

 

第二章 DOS内存和SVGA的位面结构

虽然不同的SVGA显示卡的体系结构不同,但它们最初都是从标准VGA的结构上扩充而来的,包括五大功能模块,即显示控制器、定序器,属性控制器、图形控制器和显示存储器(VRAM)SVGA640*480*256色的显示模式也和VGA卡的256色模式类似,每个像素点8位,整个VRAM地址空间按扫描行连续存放,超过 64K的地址空间采用位面映射机制分块影射到主机提供的地址上。主机提供的地址空间叫做窗口SVGAVRAM分成小块(称为位面),每块64K,分别映射到窗口上。

 

下面的表格就是是DOS系统中的内存安排,注意从地址A0000h(640KB)开始是用于显示的视频缓冲区和BIOS的地址空间,这正是我们图形编程中要用到的地址。

 

在上图中“图形模式视频缓冲区”就是主机提供的窗口,大小是64KB。下面的图表示640*480*256色显示模式的位面映射机制。

 

有的显示模式窗口大小和位面大小不一定是64KB,有可能内存窗口小,显示存储器(VRAM)中的每一个64K位面没有放满,也就是说VRAM中有效的显示信息不是连续的。如果要确定某个显示模式具体的窗口大小、位面尺寸和个数,颜色信息等等,就要调用VBE BIOS功能4F01h

 

第三章 调色板

SVGA 640*480*256显示模式下,一个像素对应显示存储器(VRAM)中用一个字节,每个字节表示一种颜色。但它不是一个真正的颜色,而是颜色的索引号,对应于SVGA调色板上的256个颜色寄存器。实际的颜色码来自于颜色寄存器中,每种颜色18位,红绿蓝各6位,共可以表示256K种不同的颜色,不过同时在显示器上显示的只有256种。

 

通过I/O端口地址3C8h3C9h设置调色板,这样写代码:

struct COLOR

{

    BYTE R;

    BYTE G;

    BYTE B;

};

struct COLOR colors[256];

 

//假设颜色变量已经保存在上面的数组中

 

for(int i=0; i < 256; i++)

{

    outportb(0x3C8, i);                //调色板寄存器索引号

    outportb(0x3C9, (colors[i].R)>>2); //传入红色分量,6

    outportb(0x3C9, (colors[i].G)>>2); //传入蓝色分量,6

    outportb(0x3C9, (colors[i].B)>>2); //传入绿色分量,6

}

 

颜色从哪里来,在本目录中有个颜色表文件ColPal.col,每个颜色4个字节,依次是蓝、绿、红、空,把它读出来放到颜色数组中。它的颜色表就是下面的样子:

 

 

第三章 VBE BIOS功能调用

这一章写怎样使用VBE BIOS功能编写DOS下的图形界面程序,更详细的编程参考资料也放在了本目录中,一个是VBE3.0标准<VBE Core Functions Standard 3.0(1998).pdf>,还有一个详细说明BIOS int 10h功能调用的文件<int 10h.txt>VBE功能调用有一个约定,或者说是规定:

1. AH必须等于4Fh,说明示调用VBE功能。

2. AL等于VBE的功能号,其中0AL0Bh

3. BL等于子功能号,也可以没有子功能;

4. 调用int 10h

5. 返回值均在AX中,回想起调用Windows API也是把返回值放到AX中。对VBE功能的调用一般需检查AX中的返回值,常见的返回值有:(1)功能调用成功,返回AX = 004Fh(2)不支持该功能,一般返回AX = 4F00h(3)支持该功能但该功能调用失败,返回AX = 014Fh

6. 返回值的含义如下:

(1) AL = 4Fh:支持该功能;

(2) AL != 4Fh:不支持该功能;

(3) AH = 00h:功能调用成功;

(4) AH = 01h:功能调用失败;

(5) AH = 02h:当前的硬件配置不支持该功能;

(6) AH = 03h:当前的显示模式不支持该功能。

 

1 进入SVGA彩色模式

__asm

{

    mov AX, 4F02h

    mov BX, 101h   //显示模式 640 * 480 * 256

    int 10h

}

常用的显示模式如下表所示,VBE标准涉及到的最大分辨率就是1280*1024,更高级的显示模式可以由厂家自己定义。

BX

像素分辨率 * 颜色数

101h

640 * 480 * 256

103h

800 * 600 * 256

105h

1024 * 768 * 256

111h

640 * 480 * 64K

112h

640 * 480 * 16M

114h

800 * 600 * 64K

115h

800 * 600 * 16M

118h

1024 * 768 * 16M

 

2 返回某个显示模式的信息

有时候不能确定显示模式的窗口和位面大小是不是64KB,就用这个函数确定一下。

 

int NumberOfPlanes;         //这三个变量保存模式的参数

int WinGran;

int WinSize;

 

struct ModeInfo mode_info;  //模式信息块

WORD segx = FP_SEG(mode_info);

WORD offx = FP_OFF(mode_info);

BYTE result;

 

__asm

{

    mov AX, 4F01h   //功能号

    mov CX, 118h    //显示模式

    mov ES, segx

    mov DI, offx    //ES:DI指向模式信息块的指针

    int 10h

    mov result, AH

}

if(result == 0x4F)  //调用成功,显示卡支持该功能

{

    NumberOfPlanes = mode_info.NumberOfPlanes; //位平面的个数

    WinGran = mode_info.WinGran;  //位面大小(窗口粒度),KB为单位

    WinSize = mode_info.Winsize;  //窗口大小,KB为单位

}

 

VBE把特定模式的信息保存在struct ModeInfo结构中,我们有时间可以了解一下,反正没有坏处。

struct ModeInfo //256字节

{

    WORD ModeAttr;           //模式的属性

BYTE WinAAttr, WinBAttr; //窗口A,B的属性   

/*

    还有其他的位面-窗口映射方法中包含两个窗口,不过现在这种情况极少

    */

    WORD WinGran;             //位面大小(窗口粒度),KB为单位

    WORD WinSize;             //窗口大小,KB为单位

    WORD WinASeg, WinBSeg;    //窗口A,B的起始段址

BYTE far *BankFunc;       //换页调用入口指针

/*

换页时可以调用该功能,也可以用VBE功能05h完成,但是直接调用该功能可以加快调用速度,因为int指令需要耗费大量的CPU周期。高性能的程序设计都是以直接调用该功能代替05h功能进行换页。(PS:我们暂时不编高性能的程序,所以使用int 10,AX=5F05h换页)

*/

    WORD BytesPerScanLine;    //每条水平扫描线所占的字节数

    WORD XRes, YRes;          //水平,垂直方向的分辨率

    BYTE XCharSize, YCharSize;//字符的宽度和高度

    BYTE NumberOfplanes;      //位平面的个数

    BYTE BitsPerPixel;        //每像素的位数

    BYTE NumberOfBanks        //CGA逻辑扫描线分组数

    BYTE MemoryModel;         //显示内存模式

    BYTE BankSize;            //CGA每组扫描线的大小

    BYTE NumberOfImagePages;  //可同时载入的最大满屏图像数

    BYTE reserve1;            //为页面功能保留

 

    //对直接写颜色模式的定义区域

    BYTE RedMaskSize;          //红色所占的位数

    BYTE RedFieldPosition;     //红色的最低有效位位置

    BYTE GreenMaskSize;        //绿色所占位数

    BYTE GreenFieldPosition;   //绿色的最低有效位位置

    BYTE BlueMaskSize;         //蓝色所占位数

    BYTE BlueFieldPosition;    //蓝色最低有效位位置

    BYTE RsvMaskSize;          //保留色所占位数

    BYTE RsvFieldPosition;     //保留色的最低有效位位置

    BYTE DirectColorModeInfo;  //直接颜色模式属性

 

    //以下为VBE2.0版本以上定义

    BYTE far *PhyBasePtr;      //可使用的大的帧缓存时为指向其首址的32位物理地址

    DWORD OffScreenMenOffset;  //帧缓存首址的32位偏移量

    WORD OffScreenMemSize;     //可用的,连续的显示缓冲区,KB为单位

 

    //以下为VBE3.0版以上定义

    WORD LinBytesPerScanLine;   //线形缓冲区中每条扫描线的长度,以字节为单位

    BYTE BnkNumberOfImagePages; //使用窗口功能时的显示页面数

    BYTE LinNumberOfImagePages; //使用大的线性缓冲区时的显示页面数

    BYTE LinRedMaskSize;        //使用大的线性缓冲区时红色所占位数

    BYTE LinRedFieldPosition;   //使用大的线性缓冲区时红色最低有效位位置

    BYTE LinGreenMaskSize;      //使用大的线性缓冲区时绿色所占的位数

    BYTE LinGreenFieldPosition; //使用大的线性缓冲区时绿色最低有效位位置

    BYTE LinBlueMaskSize;       //使用大的线性缓冲区时蓝色所占的位数

    BYTE LinBlueFieldPosition;  //使用大的线性缓冲区时蓝色最低有效位位置

    BYTE LinRsvMaskSize;        //使用大的线性缓冲区时保留色所占位数

    BYTE LinRsvFieldPosition;   //使用大的线性缓冲区时保留色最低有效位位置

    BYTE reserve2[194];         //保留

 

}

 

3 保存和恢复视频状态

在实现屏幕保护的时候用到这个功能,如果发现一段时间没有输入,就把显示器关闭,等到输入设备有动作的时候就恢复显示器。在把视频状态保存到缓冲区之前,先确定缓冲区的大小。

int size = 0;

__asm

{

    mov AX, 4F04h  //功能号

    mov DX, 0      //子功能

    mov CX, 1      //CX==1表示保存硬件控制器状态

    int 10h

    mov size, BX   //返回得到缓冲区大小,以64字节为单位

}

假设得到的size大小是128字节(64 * 2),下面接着写保存硬件控制器状态的程序:

BYTE* vga_mode[128];

WORD segx = FP_SEG(vga_mode);

WORD offx = FP_OFF(vga_mode);

__asm

{

    mov AX, 4F04h

    mov DX, 1       //子功能——保存

    mov CX, 1       //保存硬件控制器状态

    mov ES, segx

    mov BX, offx

    int 10h

}

恢复:

__asm

{

    mov AX, 4F04h

    mov DX, 2       //子功能——恢复

    mov CX, 1       //恢复硬件控制器状态

    mov ES, segx

    mov BX, offx

    int 10h

}

dongsuoying发明了发现的关闭显示器的一种方法,挺好玩:

//首先:

BYTE* vga_mode[128];

for(int i=0; i < 128; i++)

    vga_mode[i] = 0;

//然后:

WORD segx = FP_SEG(vga_mode);

WORD offx = FP_OFF(vga_mode);

__asm

{

    mov AX, 4F04h

    mov DX, 2       //子功能--恢复

    mov CX, 1       //恢复硬件控制器状态

    mov ES, segx

    mov BX, offx

    int 10h

}

 

4 选择位面

从前面的位面-窗口映射图上可以看到,因为内存窗口太小,不可能包含整个显示区域,所以一定要选择位面,在作图过程中会不停地选择位面。

void SelectPlane(int page)

{

    __asm

    {

        mov AX, 4F05h

        mov BX, 0      //表示当前窗口

        mov DX, page   //在显示存储器中的位面号

        int 10h

    }

}

选择位面的意思就是说,在内存的视频缓冲区中作图时,它会在显示器的哪一块区域显示出来。

 

5 写像素

这是作图的最基本的元素,我们就理解成在内存中640K地址的的一块区域描点,然后显示卡通过某种机制,把这些点送到在显示器上。下面就是描点函数,参数x, y是屏幕上的坐标,color是颜色索引值, 640*480*256色显示模式中:

void SetPixel(int x, int y, BYTE color)

{

    //先求线性坐标

    int pos = y * 640 + x;

    //然后求对应显示存储器中(VRAM)的的位面,一个位面大小是64KB

    int page = pos / (64 * 1024);

    //选择位面

    SelectPlane(page);

    //图形模式视频缓冲区地址

    BYTE far * VramBase = (BYTE far *)0xA0000000L;

    //"窗口"画点

    *(VramBase + pos) = color;

}

 

可能有人问内存中的"窗口"64K,(VramBase + pos)越界了怎么办?以前我也这么想,但是碰到高人指点了一下就醒悟了。原来VramBase是个<段地址:偏移地址>形式的远指针,VramBase再加上pos不会改变段地址,如果pos超过了64K,最终地址就会在段内折返回来。*(VramBase + pos) = color;语句的功能和*(VramBase + (pos % (64 * 1024))) = color;语句的功能是一样的。读像素和写像素的方法相同。

 

6 构造自己的图形函数库

Borland C++没有提供SVGA显示模式的图形函数库,据说有可以使用的商业图形库,但它们编译后的占用的空间太大了,有点划不来。所以必须根据需要,自己动手写一些常用的图形函数,比如画线,圆,长方形,填充等等。下面就要画一条简单的水平线。

void DrawLineH(int y, int x1, int x2, BYTE color)

{

    int x;

    if(x1 > x2) //为了方便循环,交换一下

    {

        x = x1;

        x1 = x2;

        x2 = x;

    }

    for(x = x1; x <= x2; x++)

    {

        SetPixel(x, y, color); //写像素

    }

}

不管任何作图函数,都要写像素,所以要把写像素的函数优化。比如,把SetPixel()做成内联函数,在选择位面之前先判断一下是否有必要,如果位面没有改变就不用去选择了。其它的作图函数也是要用写像素的方法实现,不过更要麻烦一些。dongsuoying在过电压程序中创造了很多函数,请大家编程时参考。

 

7 显示汉字

假如一个16*16的点阵汉字,它的字模存放在BYTE Pattern[32]数组中,数组每两个字节表示一行,每一位表示在屏幕上显示的一个点。

void ShowHanZiSample(int x, int y, BYTE color, BYTE* Pattern)

{

    //掩码

static BYTE mask[8]={0x80, 0x40, 0x20, 0x10, 0x08, 0x04, 0x02, 0x01};

 

    for(int i=0; i < 16; i++)

    {

        for(int j=0; j < 16; j++)

        {

            BYTE b1 = Pattern[i * 2 + j / 8];  //读字模

            b1 = b1 & mask[j % 8];             //这个位置是0还是1

            if(b1)                             //是不是要显示这个点

                SetPixel(x + j, y + i, color); //描点         

        }   

    }

}

显示ASCII字符也一样,就是把字模拷贝到屏幕上。实际中应用是先根据汉字或字符的编码,在字库中查找字模,然后根据字模在屏幕上画出来。总之,点阵字符很简单。还有一些图形拷贝运算函数图形加速技术,介于篇幅就不易以介绍了。

 

8 退出SVGA图形模式

回到默认字符模式:

__asm

{

    mov AX, 03h

    int 10h

}

VGA BIOS功能调用,AH=0子功能是设置模式,AL=3是将要设置的模式编号,对于VGA来说AL=3代表了字符模式,80*2516色。

 

第四章 鼠标

鼠标和VBE好像不相干,但是图形界面中必须的工具。DOS7.10中包含鼠标驱动程序CTMOUSE.EXE,在config.sys文件中增加一行

DEVICE=C:\DOS71\CTMOUSE.EXE

就可以在程序中应用鼠标功能了,但是要注意,一个只有5K的驱动程序不会把鼠标的样子也在屏幕上画出来的,所以我们如果想在程序运行的时候看到鼠标,必须自己动手画出来。据说也有的驱动程序能自己画鼠标,不过我们写过的程序还暂时用不到,所以不去关心它了。

 

学过SVGA之后,我想即使驱动程序自己可以画出鼠标的样子来,这个程序也是相当的复杂。因为在显卡功能标准还不统一的古代,它要判断显示卡是哪一种,显示模式是哪一种,然后才能决定怎样作图。即使大多数情况下能画出来,我想它也未必认得“先进的”SVGA 800 * 600 * 256色模式。所以要是真的想看到鼠标的话,还是不要偷懒,自己动手画出来。

 

鼠标功能通过BIOSint 33h调用。

1 检测是否安装了鼠标

int check;

__asm

{

    mov AX, 0

    int 33h

    mov check, AX          //返回AX=0是未安装,AX=1是安装

}

if(check)

    printf("Installed");   //安装了

else

    printf("not install"); //未安装

2 设置鼠标移动范围

如果我们的屏幕分辨率是640 * 480,那么就这样设置鼠标的范围:

__asm

{

    mov AX, 7

    mov CX, 0

    mov DX, 639 //设置鼠标水平范围0-639

    int 33h

    mov AX, 8

    mov CX, 0

    mov DX, 479 //设置鼠标垂直范围0-479

    int 33h

}

 

3 显示和隐藏鼠标

会显示一个默认的鼠标形状,这个功能有时候能用,很多时候用不了。在DosBox模拟器中可以显示,调试的时候可能用得着。

__asm

{

    mov AX, 1 //显示

    int 33h

}

__asm

{

    mov AX, 2 //隐藏

    int 33h

}

4 得到鼠标位置和按钮状态

int mouseX, mouseY //保存鼠标的坐标

int mouseBtn

__asm

{

    mov AX, 3           //BIOS鼠标功能3

    int 33h

    mov mouseX, CX      //在屏幕上横坐标

    mov mouseY, DX      //在屏幕上纵坐标

    mov mouseBtn, BX    //鼠标按键状态

}

if(mouseBtn == 1)       //鼠标左键按下了

{

    printf("mouse left key downX=%d Y=%d !\n", mouseX, mouseY);

}

 

在嵌入汇编语句时的注意事项

C语言中嵌入汇编有100个好处,但是要注意:不要去改变DSSSBPSP寄存器的值。比较可靠的方法是,除了AX, BXCX, DX四个通用寄存器和SI, DI两个变址寄存器外,其他的寄存器在使用后一定要恢复过来。由于C语言中的寄存器变量实际上使用SIDI,所以在函数中有寄存器变量的时候也不要改变SIDI的值。

 

总结

VESA / SVGA和图形学编程的内容完全可以写几大套专著出来,但是我们在实际用到的功能不太复杂,而且局限在DOS实模式编程,所以本篇就先写这么多。感谢dongsuoying提供的程序,终于写完了!

 

 

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

杨志朋 2008年4月13

yzp3646@163.com

yzp3646@sina.com.cn

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

 

上一篇: VC++写的故障录波查看分析软件  下一篇: Win32汇编写的俄罗斯方块小程序(源码)


留言
 
查看全部留言



Copyright © 2009 BY MISSSIR,网站版权所有。 京ICP备09095257号

花褪残红青青杏小。燕子飞时,绿水人家绕。枝上柳绵吹又少。天涯何处无芳草。
墙里秋千墙外道。墙外行人,墙里佳人笑。笑渐不闻声渐悄。多情总被无情恼。

今年访问量 24087  今月访问量 647  今日访问量 50 

关于 您的地址
您的大名 您的邮箱
QQ/MSN/Tel 是否保密
留言内容