Видеоадаптер: как выйти за предел 256 Кбайт
Рассмотрен метод программирования VGA- и SVGA- графических плат в режиме 640x480 и 800x600 точек при 256 цветах.
Поразительно, что, ограничив память своих VGA-адаптеров 256 Кбайт, корпорация IBM предусмотрела возможность доступа к вдвое большему объему, а это значит, что, пользуясь рассмотренным в упомянутой статье методом, мы можем получить в свое распоряжение режимы 640x480x256 и 800x600x256. Вообще говоря, можно запрограммировать и другие режимы, 830x631 к примеру, но это связано с глубоким перепрограммированием видеоадаптера, не совсем безопасно для дисплея, и, самое главное, не очень понятно, для чего бы это могло понадобиться, поэтому мы такую экзотику рассматривать не будем. Более высокое разрешение, чем 800x600, в DOS обычно не используется, по крайней мере будем надеяться, что для небольших коллективов и отдельных программистов, которые в основном и испытывают трудности из-за обилия несовместимых стандартов на видеоадаптеры, указанных режимов будет достаточно.
Как правило, объем видеопамяти более 512 Кбайт нужен для того, чтобы увеличить разрядность числа, хранящего информацию о цвете (глубину цвета), до 16 или даже 24 разрядов (около 65 тыс. и 16 млн. цветовых оттенков соответственно), а не для увеличения пространственного разрешения. Кстати, адаптер VGA не поддерживает глубину цвета более 8 разрядов, более того, создается впечатление, что IBM и не предполагала серьезное использование 256 цветов и добавила единственный многоцветный режим скорее в рекламных целях.
Проблема в том, что выход за пределы первых 256 Кбайт видеопамяти не совсем безболезнен, - для ее адресации в этом случае приходится использовать сегмент B000h, и именно здесь нас могут подстерегать неприятности. Чтобы лучше разобраться с ними, обратимся сначала к истории.
До IBM PC все персональные компьютеры (да-да, существовали и такие, причем не так уж мало, и в отличие от отечественных БК0010, РК86 и "Микрош" они были не игрушками, а благодаря операционной системе CP/M и разнообразному ПО вполне работоспособными устройствами) могли адресовать не более 64 Кбайт памяти. Один мегабайт адресного пространства процессора i8086 (i8088) в то время казался таким же неисчерпаемым, как и 64 терабайта 386-го процессора сегодня, а сам компьютер IBM PC имел только 64 Кбайт ОЗУ, как и большинство персоналок того времени.
IBM щедро отвела десять 64-Кбайт сегментов адресного пространства (десятикратный запас!) под оперативную память, два - под видеопамять и оставшиеся четыре - под ROM BIOS. При этом BIOS компьютера располагалась в последнем сегменте, а три предшествующих сегмента использовались платами расширения, на которых были расположены части BIOS (драйверы соответствующих факультативных устройств). В настоящее время таким образом подключается только видеоадаптер, а для остальных устройств имеются драйверы, загружаемые в оперативную память. Кстати, первая версия MS-DOS не предусматривала возможности использования загружаемых драйверов, и подключать нестандартное оборудование можно было лишь с использованием микросхем ПЗУ на платах расширения. Кроме того, в BIOS IBM PC и IBM PC XT не были заложены возможности работы с жестким диском, и на его контроллер приходилось также ставить микросхемы ПЗУ - так что отведение пятой части всего адресного пространства на эти нужды отнюдь не выглядит неоправданной роскошью.
В те далекие времена, когда большинство IBM-совместимых компьютеров были еще не "-совместимыми", а просто IBM, не существовало видеоадаптера хорошего во всех отношениях, зато имелись два специализированных адаптера - MDA (монохромный), предназначенный для работы с текстом, со знакоместом 9514 точек и без графических возможностей, и CGA, поддерживающий четырехцветную графику, но зато обладающий знакоместом 858 точек, что делало работу с текстом очень утомительной. Таким образом, на самом деле существовал не один компьютер IBM PC, а два, - они различались видеоадаптерами, а следовательно, и областью применения. Для удобной работы с текстом и графикой в компьютер можно было установить оба видеоадаптера, при этом приходилось пользоваться двумя дисплеями. Чтобы избежать конфликта адресов при одновременном использовании, видеоадаптерам были выделены различные области памяти. MDA при этом достался диапазон адресов B0000 - B7FFFh, который запрещалось использовать CGA. И хотя MDA оказался тупиковой ветвью эволюции, по традиции все наследники CGA, т. е. EGA, VGA и SVGA, также не используют эту область памяти ни в одном из стандартных режимов. Аппаратно никаких ограничений на это нет, именно этим мы и собираемся воспользоваться.
Появление процессора 386 с его блоком подкачки страниц (Paging Unit) позволило заполнить не используемые ROM BIOS области верхней памяти "перенесенными" туда блоками оперативной памяти для размещения частей операционной системы и программ либо при работе в многозадачной ОС для DOS-сессии сымитировать работу в первом мегабайте. В некоторых случаях программы, осуществляющие подобные манипуляции, исходят из того, что область монохромного адаптера свободна и ее можно использовать. Поэтому необходимо проверять, не использует ли ваш диспетчер верхней памяти (EMM386, QEMM) область монохромного дисплея. Не следует также запускать программы, обращающиеся к видеосегменту B000h, из Windows. В общем-то ничего необычного в этих требованиях нет, многие игры, использующие видеопамять нестандартным способом, отказываются работать в Windows и требуют либо приведения файлов config.sys и autoexec.bat в соответствие со своими требованиями, либо вообще загрузки со специальной дискеты. Ничего не поделаешь - это плата за скорость вывода на экран.
Программа, демонстрирующая установку и использование режима 800x600x256, приведена в листинге. Для режима 640x480x256 необходимо заменить номер видеорежима на 101h и изменить ряд констант в процедурах PutPixel и Main. Подпрограмма PutPixel, приведенная в листинге, служит лишь для демонстрации способа доступа к видеопамяти. Если вы собираетесь ее использовать, необходимо провести ассемблерную оптимизацию. Два варианта оптимизированной подпрограммы приведены в этом же листинге под именами PutPixel1 и PutPixel2. Время десятикратного заполнения экрана с помощью различных подпрограмм, а также фрагмента кода, помещенного непосредственно в программу, выраженное в единицах отсчета системного таймера, приведено в таблице для двух аппаратных конфигураций компьютера в реальном и защищенном режимах работы процессора.
Время десятикратного заполнения экрана
|
|
|||
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
Как видно из таблицы, наибольшего увеличения скорости вывода удается достичь только при отказе от вызова подпрограммы и размещении операторов вывода непосредственно в теле программы. Особенно это заметно при работе в защищенном режиме процессора. Вообще же, на мой взгляд, процедуры рисования точки могут использоваться только в демонстрационных программах, а при реальном программировании следует применять подпрограммы, оперирующие более крупными объектами, чем точка. Если вас заинтересовал приведенный метод, но видеоадаптер вашего компьютера не поддерживает стандарт VESA или ваша программа должна работать на максимально возможном числе различных компьютеров, следует воспользоваться рекомендациями, изложенными в нашей статье ("Мир ПК", # 5/97, с. 70).
program TestHiResMode; uses crt; const SeqP = $3c4; { Базовый номер порта } { контроллера синхронизации } CrtP = $3d4; { Базовый номер порта контроллера ЭЛТ } GraP = $3ce; { Базовый номер порта } { графического контроллера } SegA000=$a000; { Сегмент видеопамяти } SegB000=$b000; { Сегмент видеопамяти } Seg0040=$0040; { Сегмент области данных BIOS } var P - Базовый адрес регистра New_v - новое значение,которое нужно записать в регистр, Mask - маска, Number - индекс регистра} Procedure SetVgaReg(P:word;New_V,Mask,Number:byte); Begin Inline($0FA);{ Cli - запрещаем прерывания} Port[P] := Number; Port[P+1] := (Port[P+1] and (not Mask))or (New_V and Mask); Inline($0FB);{ Sti - разрешаем прерывания} End; {Ожидаем вертикальный обратный ход луча} Procedure WaitRetrace; Begin While(Port[$3DA] and $08)=0 do; End; {Устанавливаем текстовый видеорежим 3h} Procedure SetTextMode; Begin asm mov ax,3 int $10 {установка видеорежима} end; End; {Читаем текущее показание системных часов} function clock:longint; begin clock:=MemL[Seg0040:$6c]; end; {Устанавливаем видеорежим 800x600x256} Procedure SetHiResMode; var OutStatus:word; Begin SetVgaReg(SeqP,$20,$20,1); { выключаем экран } asm mov ax,$4f02 {пытаемся установить режим 800x600x256} mov bx,$103 { для режима 640x480x256 - заменить на $101} int $10 mov OutStatus,ax { и узнаем, что из этого получилось} end; if Lo(OutStatus) <> $4f then begin {VESA не поддерживается} SetTextMode; writeln('Your card is not VESA-compartible'); halt; { завершаем работу программы} end; if Hi(OutStatus) <> 0 then begin { видеорежим не поддерживается } SetTextMode; writeln('Videomode is not supported'); halt; {завершаем работу программы} end; WaitRetrace; SetVgaReg(CrtP,$40,$40,$17); { устанавливаем режим байтов контроллера ЭЛТ } SetVgaReg(CrtP,0,$40,$14); { сбрасываем режим "двойное слово" контр.ЭЛТ} WaitRetrace; SetVgaReg(SeqP,0,$08,4); { сбрасываем режим "цепочка 4"} SetVgaReg(SeqP,$0F,$0F,2); { разрешаем все плоскости для записи } SetVgaReg(GraP,0,$0C,6); { диапазон адресов видеопамяти A000:0000-B000:FFFF} FillChar(mem[SegA000:0],64000,0); { очистка экрана } SetVgaReg(SeqP,0,$20,1); { включаем экран } End; {Рисуем точку на экране} procedure PutPixel(X, Y : word; Color : byte); begin { Устанавливаем маску для выбора нужной плоскости } PortW[SeqP] := 2 + $100 shl (X and 3); { Вычисляем смещение (Y * 800 div 4 + X div 4) и рисуем точку } { Для режима 640x480x256 заменить в трех местах 200 на 160 } if (longint(Y) * 200 + X div 4) < $10000 then Mem[SegA000 : Y * 200 + X shr 2] := Color else Mem[SegB000 : word(longint(Y) * 200 + X shr 2-$10000)] := Color; end; {Рисуем точку на экране с оптимизацией} procedure PutPixel1(X, Y : word; Color : byte);assembler; asm mov dx,SeqP mov cx,X and cx,3 mov ax,$100 shl ax,cl add ax,2 out dx,ax {устанавливаем маску} mov ax,200 mul Y mov bx,X shr bx,2 {ax = X div 2} add bx,ax {bx - 16 младших битов смещения} adc dx,dx { если dx не равен 0, то используем сегмент SegB000} mov ax,SegA000 jz @l mov ax,SegB000 @l: mov es,ax mov al,Color mov es:[bx],al end; const b:byte = 255; {номер плоскости, к которой было последнее обращение} {Рисуем точку на экране с оптимизацией (2-й вариант)} procedure PutPixel2(X, Y : word; Color : byte);assembler; asm mov dx,SeqP mov cx,X and cx,3 cmp cl,b jz @l1 mov b,cl mov ax,$100 shl ax,cl add ax,2 out dx,ax {устанавливаем маску} @l1: mov ax,200 mul Y mov bx,X shr bx,2 {ax = X div 2} add bx,ax {bx - 16 младших битов смещения} adc dx,dx {если dx не равен 0, то используем сегмент SegB000} mov ax,SegA000 jz @l2 mov ax,SegB000 @l2: mov es,ax mov al,Color1 mov es:[bx],al end; {Основная программа} var i,j,k:integer; c1,c2,c3,c4,c5:longint; begin SetHiResMode; {выводим наклонные линии всех 256 цветов} {для режима 640x480x256 соответственно уменьшить длину циклов} c1 := clock; for k := 0 to 9 do for i:=0 to 799 do for j:=0 to 599 do PutPixel(i,j,lo(i+j));{} c2 := clock; for k := 0 to 9 do for i:=0 to 799 do for j:=0 to 599 do PutPixel1(i,j,lo(i+j));{} c3 := clock; for k := 0 to 9 do for i:=0 to 799 do for j:=0 to 599 do PutPixel2(i,j,lo(i+j));{} c4 := clock; for k := 0 to 9 do for i:=0 to 799 do for j:=0 to 599 do asm { рисование точки непосредственно из тела программы } mov dx,SeqP mov cx,i and cx,3 cmp cl,b jz @l1 mov b,cl mov ax,$100 shl ax,cl add ax,2 out dx,ax {устанавливаем маску} @l1: mov ax,200 mul j mov bx,i shr bx,2 {ax = X div 2} add bx,ax {bx - 16 младших битов смещения} adc dx,dx {если dx не равен 0, то используем сегмент SegB000} mov ax,SegA000 jz @l2 mov ax,SegB000 @l2: mov es,ax mov ax,i add ax,j mov es:[bx],al end; c5 := clock;{} SetTextMode; writeln('Elapsed time - PutPixel:',c2-c1,' PutPixel1:', c3-c2,' PutPixel2:', c4-c3,' Direct:',c5-c4); end.