Собиратели жемчужин
Литература
Листинг 1
Листинг 2

Собиратели жемчужин

О программировании на ассемблере (assembler - сборщик, англ.) теперь пишут совсем мало. Действительно, поводов для этого, казалось бы, нет. Все, что можно, об этом языке программирования и его применении в самых различных ситуациях уже написано. Остается только изредка переиздавать пухлые тома для новых программистов - любителей самоистязания. Языки высокого уровня могут все или почти все, а вопрос о существенном повышении вычислительной эффективности алгоритмов встает все реже. Тем не менее в языке ассемблера - этой захватывающей и головоломной игре с регистрами, чем-то напоминающей раскладывание пасьянса, - есть что-то притягательное. Возможно, это - власть над всеми ресурсами машины, а может быть, творческое удовлетворение от наилучшим образом сделанной работы. Во всяком случае, интерес к этой теме не ослабевает, об этом говорит опыт С.А. Андрианова и вызов, брошенный Виталием Ланским.

C сегодняшней точки зрения видеоадаптер CGA можно считать верхом целесообразности. Трудно представить, как можно было бы лучше использовать имеющиеся у него 16 килобайт видеопамяти, которая используется "на всю катушку" в любом из текстовых или графических режимов. Трудно придумать и видеорежимы, которыми его следовало бы дополнить. А текстовый режим и до сих пор не претерпел каких-либо существенных модернизаций. Гораздо хуже обстоит дело с VGA: ну почему его разработчики организовали в режиме 13h только одну видеостраницу? Почему ни один из его режимов - 320x240, 320x400, 360x400, 360x480 - не является стандартным, хотя для их программирования можно обойтись уже имеющимися регистрами [1,2]? Пожалуй, только для отсутствия режима 640x400x256 можно найти рациональное объяснение: для него требуется вдвое более быстродействующая видеопамять, чем для любого из стандартных VGA-режимов.

Но оказалось, что VGA еще не самое худшее. Сегодня то, что называется SVGA, на самом деле - полное отсутствие какого-либо стандарта. Теперь каждому, кто хочет программировать с использованием 256 цветовых оттенков с разрешением выше 320x200 точек, приходится либо разбираться с регистрами конкретного видеоадаптера, установленного на его компьютере, либо (если он хочет, чтобы его программа работала еще где-нибудь) учиться определять тип доброй дюжины видеоадаптеров или более, таская с собой библиотеку драйверов для каждого из них [3,4]. Кстати, далеко не у каждого программиста есть возможность отладить эту библиотеку на соответствующем "железе", и появляются программные монстры, претендующие на SVGA, но не желающие работать ни на чем, кроме, например, видеоадаптера Trident. И отпугивает от программирования в SVGA отнюдь не размер экранной памяти в 4-5 сегментов [1], а боязнь существующей программной несовместимости SVGA-плат между собой.

Попытаемся продемонстрировать возможность быстрого (без использования BIOS) и аппаратно-независимого программирования в режиме 640x400x256 для любых SVGA (и многих VGA)-плат.

Если вы еще колеблетесь в выборе экранного разрешения для программы, не зная что предпочесть, 360x480 [2] или 640x400, выведите в каждом из этих режимов какой-нибудь текст на экран, и, мне кажется, ваши колебания будут рассеяны. Кстати, в уже упоминавшемся видеоадаптере CGA есть режим 640x200, но нет 320x400, в видеоадаптере Hercules - 720x350, а в текстовом режиме VGA - 720x400. Наверное, не только аппаратные ограничения можно привести в качестве критериев, по которым следует отдавать предпочтение повышенному разрешению именно по горизонтали. Рискну выдвинуть гипотезу, что оптимальное соотношение эстетическое совершенство / число точек изображения диктует прямоугольную форму точки с отношением сторон, равным золотому сечению, причем длинная сторона вертикальна. Как известно, для инженерной графики оптимальная форма точки - квадрат. В рассматриваемом режиме ее форма - прямоугольник с отношением сторон, лежащим где-то посередине.

В чем же еще прелесть этого режима? Так как ему достаточно всего 256 килобайт видеопамяти, т. е. столько, сколько есть в обычном VGA, можно получить доступ к каждой точке с использованием только стандартных VGA-регистров и ничего не зная ни об уникальных регистрах, которые есть в каждой SVGA-плате, ни о программной несовместимости различных видеоадаптеров между собой. Надо только уметь установить соответствующий видеорежим и уметь рисовать в нем точку.

Если вы работаете с VESA-совместимой видеоплатой, то проблемы установки нужного видеорежима не существует. Это самый первый из VESA-режимов - 100h. Однако здесь вас ждет первое разочарование. Стандарт VESA хотя и узаконил список видеорежимов, но не избавил от разнообразия способов доступа к видеопамяти и ничего не говорит о регистрах, которые нужно программировать для этого доступа. В большинстве случаев память оказывается организована так же, как и в режиме 13h, когда каждой точке соответствует свой адрес в видеопамяти. Однако длина области видеопамяти больше одного сегмента, отводимого в адресном пространстве компьютера под графические режимы, и чтобы нарисовать точку где-нибудь ниже 102-й строки, приходится переключать банки памяти, а делают это регистры, номера и способы программирования которых уникальны для каждого типа SVGA-плат. Предлагаемый же стандартом VESA способ переключения банков через прерывания весьма медлителен и все равно требует учета особенностей аппаратуры.

Но, к счастью, можно так перепрограммировать видеоадаптер, что доступ ко всем 256 килобайтам видеопамяти будет осуществляться через адресное пространство размером 64 килобайта. При этом как в режиме перепрограммирования, так и в режиме доступа можно обойтись лишь стандартными VGA-регистрами. Таким образом, проблема несовместимости видеоадаптеров снимается. Нужно разделить видеопамять на четыре плоскости аналогично тому, как это делается в 16-цветных режимах. Способ этот описан в литературе [1,2,5] и вовсю используется разработчиками компьютерных игр в режимах от 320x200 до 360x480, как правило, для организации нескольких страниц видеопамяти [5]. При этом по одному адресу находятся четыре точки, а нужная из них выбирается с помощью регистра маски. Если же нужно быстро закрасить часть экрана (или экран целиком), разрешается запись во все четыре плоскости, и за одно обращение к видеопамяти "красятся" четыре точки.

Демонстрационная программа, заполняющая экран в этом режиме наклонными линиями всех 256 оттенков, приведена в листинге 1. В качестве языка выбран Паскаль, как наиболее понятный, программирующие на Бейсике или Си без труда смогут переписать программы под свой любимый компилятор.

Однако что же делать, если, запустив приведенную программу, вы узнали, что видеоплата несовместима со стандартом VESA? А может быть, вы хотите написать свою программу, которая будет уметь работать с любой видеоплатой, а использование загружаемого VESA-драйвера вам не подходит.

Следует сказать, что, как не каждая видеоплата способна воспроизводить 256 оттенков цвета (например, CGA или EGA), точно так же не любая VGA-плата способна поддерживать режим 640x400x256. Чудес не бывает, и оригинальные VGA или MCGA-платы, которые сейчас приняты за образец, не поддерживают данный режим. Хуже другое: уже после того, как появилось много 256-килобайтных плат, способных работать в этом режиме, некоторые разработчики 512-килобайтных видеоадаптеров отказались от него, полагая, что при наличии 640x480x256 и 800x600x256 старый добрый 640x400x256 уже не обязателен (кстати, в этом случае 640x400x256 можно сделать из 640x480x256). К счастью, конкурентная борьба не оставила шансов на выживание производителям, не поддерживающим VESA-стандарт, и с любой современной видеоплатой проблем у вас уже не будет.

Как же определить, есть ли заинтересовавший нас режим на VESA-несовместимой плате? Здесь можно предложить несколько вариантов. Например, попытаться определить тип используемого набора микросхем и по таблице найти нужный видеорежим (либо установить, что он не поддерживается) [4]. Можно перебрать все доступные номера видеорежимов (благо их не очень много: c 14h по 7Fh) и попытаться выяснить, какой из них нам подходит. Одна из возможных программ, которая пытается это сделать, приведена в листинге 2.

В большинстве случаев программа выдает один номер, тогда вопросов не возникает. Впрочем, когда ни одного - тоже :- ( . Однако что же делать, когда "подозрительных" номеров режимов оказалось больше одного? В этом случае тоже можно порекомендовать несколько способов. Можно попытаться нарисовать, а затем считать точку (не рекомендуется делать это в ОС, работающих в защищенном режиме: Windows, OS/2 etc., так как некоторые видеоBIOS делают это некорректно), можно проверить, является ли данный режим графическим и поддерживает ли он 256 оттенков и т. д. Способы получения подобной информации также содержатся в программе.

Если программа выдала несколько номеров и анализ содержимого регистров ни к чему не привел, остается показать все эти режимы пользователю вашей программы, что-либо нарисовав на экране, и спросить его, что он видит. Следует только помнить, что полученные видеорежимы могут различаться лишь частотами синхронизации, поэтому следует предупредить пользователя о возможности срыва синхронизации и предпринять меры к тому, чтобы не повредить его дисплей, например не дожидаться, пока он нажмет клавишу, а самому сбросить режим через 3-5 секунд (не забудьте сразу очистить буфер клавиатуры - редко кто может удержаться от нажатия на клавиши, если с экраном происходит что-то непонятное). Можно также таймером измерить частоту кадров [6] (так как переменная BIOS, служащая для отсчета времени, изменяется слишком редко) и установить наименьшую, благо она в этом режиме равна 70 Гц.

Если вы работаете с такими приложениями, как средства просмотра изображений или визуализации научного эксперимента, приведенной информации вполне достаточно. Теперь о скорости предлагаемого способа: написанная в технике a-ля Wolf3D программа достаточно плавно работает на машинах класса 486DX4-100, обеспечивая 15-20 кадров в секунду, при этом на работу с экраном тратится не более четверти времени, остальное - на расчет изображения.


ЛИТЕРАТУРА

1. А. Хошенко. Видеорежим будущей игры. "Мир ПК", #7-8/96.

2. А.В. Фролов, Г.В. Фролов. Программирование видеоадаптеров CGA,EGA и VGA. М.: Диалог-МИФИ, 1992.

3. А.Н. Ефлеев. Программирование видеоадаптеров SVGA в графических режимах. "Мир ПК", #2/94.

4. Е.А. Липунов. SVGA: по ту сторону экрана. "Мир ПК", #5/94.

5. Б. Телесин. Адаптер VGA. Режим 256 цветов. "Монитор", #1/93.

6. Р. Джордейн. Справочник программиста персональных компьютеров типа IBM PC, XT и AT. М.: "Финансы и статистика", 1991.


Андрианов Сергей Андреевич - к.т.н., военнослужащий, тел.: (254) 6-19-62.

Листинг 1

program TestHiResMode;
{Демонстрация работы в режиме 640х400х256 цветов}
{$x+}
uses crt;

const
   SeqP = $3c4;    { Базовый номер порта контроллера синхронизации }
   CrtP = $3d4;    { Базовый номер порта контроллера ЭЛТ }
   SegA000=$a000;  { Сегмент видеопамяти }

{Устанавливает один из регистров VGA:
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;

{Устанавливает видеорежим 640x400x256}
Procedure SetHiResMode;
var OutStatus:word;
Begin
   SetVgaReg(SeqP,$20,$20,1);          { выключаем экран }
   asm
      mov ax,$4f02 {пытаемся установить режим 640х400х256}
      mov bx,$100
      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);          
{разрешаем все плоскости для записи} 
   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 * (640 div 4) + X div 4) и рисуем точку}
  Mem[SegA000 : Y shl 7 + Y shl 5 + X shr 2] := Color;
end;

{Основная программа}
var i,j:integer;
begin
   SetHiResMode;
   for i:=0 to 639 do for j:=0 to 399 do PutPixel(i,j,lo(i+j));
                {выводим наклонные линии всех 256 цветов}
   ReadKey;                  { ждем нажатия на клавишу }
   SetTextMode;
end.


Листинг 2

program setup;
{программа определения номера режима 640х400х256}
const Seg0040 = $40; {область данных BIOS}

Procedure SetVideoMode(Mode:byte); {устанавливает видеорежим}
Begin
    asm
    mov al,Mode
    xor ah,ah
    int 10h
    end;
End;

Function GetVideoMode:byte;        {читает видеорежим}
Begin
    asm
    mov ah,$0f
    int 10h
    mov @Result,al
    end;
End;

type
  vidrec=record   {структура, характеризующая видеорежим}
     mode:byte;                 {номер видеорежима}
     wide:Word;     {предполагаемая ширина экрана в точках}
     Heig:Word;     {предполагаемая высота экрана в точках}
     pixel:boolean; {можно ли прочитать нарисованную точку}
     Graph1,Graph2:boolean;    
{является ли режим графическим (2 варианта)}
     c256:boolean;         {является ли режим 256-цветным}
  end;
var
   i,k:integer;
   vr:vidrec;         {параметры текущего режима}
   q:array[0..$7f]of vidrec; {массив для выбранных режимов}
   m:byte;        {для номера VESA-режима}

procedure GetConst; {заполняет структуру видеопараметров}
var
   colpix:byte;
begin
   vr.mode := mem[Seg0040:$49];   {Номер видеорежима}
   vr.wide := memW[Seg0040:$4a] * 8;  
{Ширина экрана, если в знаке 8 точек}
   vr.Heig := (mem[Seg0040:$84] + 1) * memW[Seg0040:$85];  {Линий растра}
   asm
      mov ah,$0c  {проверка методом записи и чтения точки}
      mov al,$aa  {номер цвета}
      mov bh,0
      mov cx,639  {X-координата}
      mov dx,399  {Y-координата}
      int $10     {пишем точку}
      mov ah,$0d
      mov bh,0
      mov cx,639  {X}
      mov dx,399  {Y}
      int $10     {читаем точку}
      mov colpix,al
   end;
   vr.pixel :=  (colpix = $aa);
   colpix := Port[$3da];         { чтение регистра MCR }
   Port[$3c0] := $10;   { контроллера }
   colpix := Port[$3c1];        { атрибутов   }
   vr.Graph1 := (colpix and 1) = 1;     { графический?}
   Port[$3ce] := 6;     {            }
   vr.Graph2 := (Port[$3cf] and 1) = 1; { графический?}
   Port[$3ce] := 5;     {            }
   vr.c256 := (Port[$3cf] and $40) = $40;       { 256 цветов? }
end;

begin                    {основная программа}
   Port[$3c4] := 1;
   Port[$3c5] := Port[$3c5] or $20;  {выключаем экран}
   k := 0;
   for i := $14 to $7f do begin {перебираем по всем номерам}
      SetVideoMode(i);
      if i = GetVideoMode then begin 
{отбираем только существующие режимы}
         GetConst;
         if (vr.Wide = 640) and (vr.Heig = 400) then begin 
{отбираем по размерам}
            q[k] := vr;
            k := k + 1;
         end;
      end;
   end;
   SetVideoMode(3);
   asm                 {проверка VESA}
      mov ax,$4f02
      mov bx,$100
      int $10
   end;
   m := GetVideoMode;
   Port[$3c4] := 1;
   Port[$3c5] := Port[$3c5] and $DF;  {включаем экран}
   SetVideoMode(3);
   if k > 0 then          {если отобран хотя бы один режим}
      for i := 0 to k-1 do 
{выводим на печать его параметры}
         writeln('Mode=',q[i].mode,' Wid=',q[i].Wide,' 
Hei=',q[i].Heig,
         ' Pixel:',q[i].pixel,' 
Graphic:',q[i].Graph1,'/',q[i].Graph2,
         ' 256colors:',q[i].c256)
   else writeln('Suitable mode not detected');
   if m > $13 then      writeln('VESA mode:',m)
   else                 writeln('VESA not supported');
end.