SVGA: быстрый вывод на экран
С.А. Андрианов
-
- Игры в зеркале видеорежимов
- Что лучше?
- Несколько советов
- Литература
Листинг. Фрагмент библиотеки для
работы с экраном в режиме 640x400x256
Если вы несколько неудовлетворены видеорежимом 13h и хотите чего-то
большего, но не уверены, что сможете обеспечить совместимость программ
со всеми существующими видеоадаптерами, а также опасаетесь, что переход
к высокому разрешению значительно замедлит скорость вывода на экран,
то, возможно, вам подойдет способ программирования видеоадаптера в
режиме 640x400x256 цветов, предложенный в работе [1]. Или даже в режиме
800x600x256, описанный в статье [2].
Перед тем как сделать свой выбор, давайте вспомним, с каким разрешением
приходилось работать видеоадаптерам IBM-совместимых компьютеров, и
попытаемся понять связь между разрешением экрана по вертикали и разрешением
по горизонтали. Для этого рассмотрим величину, называемую aspect ratio,
представляющую собой отношение размера точки растра по горизонтали
к ее размеру по вертикали.
Для различных видеорежимов это отношение может изменяться в довольно
широких пределах (см. таблицу).
Свойства экранных режимов
Тип устройства |
Режим |
Разрешение |
Aspect ratio (точек/знакомест) |
CGA |
Графический |
320x200 |
5/6 = 0,83 |
CGA |
Графический |
640x200 |
5/12 = 0,42 |
CGA |
Текстовый |
40x25 |
5/6 = 0,83 |
CGA |
Текстовый |
80x25 |
5/12 = 0,42 |
MDA |
Текстовый |
80x25 |
35/54 = 0,65 |
EGA |
Графический |
640x350 |
35/48 = 0,73 |
EGA |
Текстовый |
80x25 |
35/48 = 0,73 |
VGA |
Текстовый |
80x25 |
40/54 = 0,74 |
VGA |
Графический |
640x480 |
1/1 = 1 |
SVGA |
Графический |
640x400 |
5/6 = 0,83 |
SVGA |
Графический |
800x600 |
1/1 = 1 |
SVGA |
Графический |
1024x5768 |
1/1 = 1 |
Нестандартное |
Графический |
320x240 |
1/1 = 1 |
Нестандартное |
Графический |
320x400 |
10/6 = 1,67 |
Нестандартное |
Графический |
360x480 |
16/9 = 1,78 |
Нестандартное |
Графический |
720x350 |
35/54 = 0,65 |
При анализе таблицы следует учитывать два обстоятельства: во-первых,
особенностью человеческого восприятия является более высокая требовательность
к разрешению по горизонтали, чем по вертикали, и, во-вторых, для некоторых
приложений значительно более удобным или просто необходимым является
равенство разрешения по вертикали и по горизонтали, причем особенно
там, где требуется возможно более точное соответствие изображения
на экране твердой копии, или тогда, когда необходимо вращать изображение
в плоскости экрана, например, в инженерной графике, в издательском
деле и для летных имитаторов.
На ранних этапах развития ПК, когда оптимизировалось использование
каждого килобайта видеопамяти (как с точки зрения стоимости видеоадаптера,
так и с точки зрения скорости перерисовки изображения), рассматриваемое
соотношение было меньше единицы, а иногда даже меньше 1/2. Ранее я
уже высказывал предположение, что оптимальным является соотношение,
величина которого равна золотому сечению (примерно 0,62), что близко
нестандартному режиму 720x350. Однако глубокое перепрограммирование
видеоадаптера не входит в наши задачи, так что лучше остановиться
на режиме 640x400.
Все современные режимы высокого разрешения имеют aspect ratio, равное
1, что связано главным образом с особенностями Windows, а именно с
отсутствием возможности переопределять видеорежим, подбирая оптимальный
для каждого приложения. При этом оптимальность приносится в жертву
универсальности, да и сама задача подбора в значительной степени утратила
актуальность из-за стремительного роста мощности ПК. Однако, несмотря
на господство Windows практически во всех областях применения персональных
компьютеров, разработчики игр по-прежнему отдают предпочтение DOS,
дающей возможность управлять всеми ресурсами компьютера, в том числе
и выбором разрешения экрана.
Игры в зеркале видеорежимов
Игры с точки зрения необходимого экранного разрешения можно разделить
на четыре группы.
Игры на неподвижном фоне. При оптимальном программировании
вполне достижимо разрешение 800x600.
Игры на подвижном фоне. Это большая часть стратегических и
аркадных игр с видом со стороны. Разрешение - от 640x400 (для аркадных
допустимо и ниже) до 800x600.
Игры со значительной долей анимации (или только анимация). Проблем
с выводом на экран нет, могут возникнуть проблемы с местом, отводимым
для хранения изображений, скоростями считывания данных с диска и декомпрессии
изображения. Рекомендуемое разрешение - не выше 640x480.
Игры с видом от первого лица. Фон должен просчитываться по законам
перспективы, и поэтому заранее подготовленные картинки неприменимы.
Большая часть времени тратится на вычисление изображения, а не на
вывод его на экран. В настоящее время такие игры по сложности можно
разделить на три подгруппы:
простейшие, когда все действие происходит на одном уровне, все стены,
полы и потолки пересекаются под прямыми углами, все остальные объекты
представлены растровыми масштабируемыми картинками. Первая игра подобного
рода - Wolfensten3D - работала даже на процессоре 286. Оптимальное
разрешение - 640x400 (компьютер как минимум 486 DX2-66 VESA/PCI);
средней сложности, когда пол и потолок всегда горизонтальны, но могут
располагаться на различных уровнях, стены всегда вертикальны, но могут
пересекаться под произвольным углом, а все остальные объекты представлены
растровыми масштабируемыми изображениями. Первая игра такой сложности
- Doom. Рекомендуемое разрешение - до 360x240;
высшей сложности, когда все объекты состоят из полигонов, а фон -
из плоскостей, пересекающихся под произвольными углами. Первой такой
игрой, вероятно, можно назвать Descent, а может быть и более ранние
летные имитаторы. Поскольку при малом числе полигонов из-за высокого
разрешения подчеркиваются угловатость и схематичность моделей, а большое
порождает слишком высокие требования к аппаратуре, рекомендуется разрешение
320x200, но даже при этом необходим процессор Pentium.
Для летных имитаторов и других игр, в которых угол крена может отличаться
от нуля, вероятно, лучше выбрать разрешение 320x240.
Что лучше?
Итак, каков же итог? Давайте примем, что мы будем рассматривать лишь
режимы с 256 цветами. Все рассматриваемые режимы с разрешением 320x200,
320x240, 320x400, 360x480, 640x400, 640x480 и 800x600 можно запрограммировать,
используя технику, описанную в работах [1-3].
Режим 320x240, получивший имя собственное ModeX, широко используется
в играх и легко получается из хорошо известного 320x200. Он может
быть реализован на любом VGA-адаптере, т. е. относится не к SVGA,
а к нестандартным VGA-режимам, следовательно, его рассмотрение выходит
за рамки настоящей статьи, поэтому мы его непосредственно касаться
не будем, но практически все рекомендации, изложенные ниже, применимы
и к нему.
Режимы 320x400 и 360x480 могут вызвать, скорее всего, лишь академический
интерес, хотя, если как следует задуматься, то можно, наверное, подобрать
такие приложения, для которых эти режимы окажутся оптимальными. Разрешение
640x400, на мой взгляд, является наиболее перспективным и обладает
следующими преимуществами по сравнению с 640x480x256:
- требуется меньше видеопамяти (256 Кбайт, а не 512 Кбайт);
- осуществляется на 20% меньше вычислений при практически том же
качестве (из-за неоптимальной величины aspect ratio при разрешении
640x480 улучшение незаметно);
- отсутствует необходимость в отслеживании межсегментного перехода
при 16-разрядном коде, что еще более повышает производительность;
- можно не использовать видеосегмент B000h, что снижает требования
к условиям успешной работы программы [2];
- позволяет организовать две страницы видеопамяти (при использовании
видеосегмента B000h), а не одну, как при разрешении 640x480;
- обеспечивает частоту регенерации экрана не ниже 70 Гц, тогда как
при 640x480 частота может составить только 60 Гц.
Несколько советов
Режим 800x600 может оказаться полезен для стратегических игр на большой
карте, а также для игр типа Strip Poke:). Практически все сказанное
ниже относится и к нему. Некоторые особенности этого режима описаны
в работе [2].
Сначала приведу несколько советов, полезных для любого из видеорежимов,
в которых вы программируете. Не обессудьте, если некоторые из них
покажутся вам тривиальными.
В вашей графической библиотеке не должно быть ни процедуры типа PutPixel,
ни аналогичной по назначению. И хотя это во много раз быстрее, чем
при использовании BIOS, для игр и анимации это недопустимо медленно.
Игровая программа должна обращаться к процедурам работы с полигонами,
растровыми изображениями и другими достаточно крупными объектами.
Внутри графической библиотеки в процедурах работы с объектами также
следует избегать вызовов процедуры рисования точки. Все операторы,
делающие это, должны находиться непосредственно в теле процедуры.
Это, конечно, приведет к многократному дублированию одних и тех же
фрагментов кода, но зато существенно повысит скорость работы (см.
таблицу в работе [2]).
Для того чтобы передвинуть какой-либо объект относительно фона, как
правило, сначала восстанавливают фон под объектом, стирая объект,
а затем рисуют его в новом месте. Эту процедуру следует проводить
не на видимом экране, а на теневом или виртуальном либо в буфере,
куда помещают сохраненный фрагмент фона. Виртуальный экран - это та
область оперативной памяти, в которой вы формируете изображение, чтобы
затем одной быстрой операцией пересылки перенести его на реальный
экран (видимый или теневой).
Теневой экран (теневая страница) - часть видеопамяти. Если позволяет
объем видеопамяти, то можно организовать несколько страниц, на каждой
из которых можно поместить изображение, однако в любой момент времени
видимой является только одна, а все остальные - теневые. Любую из
таких страниц можно сделать видимой.
Если вы создали теневые страницы, то используйте их только для записи,
так как скорость чтения из видеопамяти обычно более чем на порядок
ниже скорости чтения из оперативной памяти. При подготовке изображения
на экране обычно сначала рисуют фон, а затем поверх него все объекты,
переходя от дальних к ближним. Таким образом, некоторые точки прорисовываются
по два раза и более. Если коэффициент перекрытия превышает 1,5-2,
то предпочтение следует отдать виртуальному экрану, так как скорость
записи в видеопамять, как правило, меньше скорости записи в обычную
память более чем в 2 раза. Следовательно, работа с теневыми экранами
не исключает организации виртуальных.
В общем случае работа с теневыми экранами происходит медленнее, чем
с виртуальными, так как пересылка данных из виртуального экрана на
видимый осуществляется быстрее, чем переключение страниц, и требует
более аккуратного программирования, зато при отсутствии ошибок (они
могут проявляться далеко не на всех видеоадаптерах, что усложняет
отладку) гарантирует отсутствие каких-либо помех.
Вы ведь хотите, чтобы программа работала на любом компьютере, а если
это невозможно, то ей следует вместо таинственных оптических эффектов
выдавать вразумительные сообщения, чередующиеся с извинениями и обещаниями
исправить все в следующей версии. Для этого желательно предусмотреть
специальную программу настройки, которая тестирует оборудование один
раз при установке программы, а не при каждом запуске, и создает специальный
файл конфигурации, в котором отражает обнаруженные ею особенности.
Что должна делать основная программа при отсутствии файла конфигурации
- запускать программу настройки автоматически или выдавать на экран
просьбу сделать это вручную - дело ваше. В файл конфигурации целесообразно
вписать номер нужного видеорежима, так как при отсутствии поддержки
VESA может понадобиться довольно длительное тестирование аппаратуры
[1].
Если вы используете несколько страниц, то следует проверить, как
реагирует видеоадаптер на их переключение. Некоторые делают это сразу,
а другие ждут импульса обратного хода луча вертикальной развертки.
Если вы не произведете этой проверки, то программа может начать готовить
следующий кадр изображения не на той странице, на которой вам бы хотелось,
и часть экрана будет испорчена. Программа конфигурации должна также
проверить, допускает ли тестируемая видеоплата работу с 32-разрядными
словами. Если работа невозможна, то пользователь может увидеть картинку
через густую решетку из черных вертикальных полос. Вряд ли ему это
понравится. Обнаруженную особенность следует занести в файл конфигурации,
и в зависимости от результата тестирования основная программа должна
осуществлять пересылки на экран либо двойными, либо одинарными словами.
Видимо, вам придется работать с виртуальными экранами. Даже если
вы используете несколько страниц, то на виртуальном экране можно хранить
фон. Виртуальный экран целесообразно организовать в виде четырех сегментов,
соответствующих цветовым плоскостям видеоадаптера.
Фрагмент библиотеки для работы с экранами приведен в листинге.
Отображение из виртуального экрана в видеопамять лучше производить
последовательно, сегмент за сегментом, операторами пересылки строки
(процедура ToVgaScreen). Может быть организовано несколько виртуальных
экранов, один из которых является активным. Перед работой процедура
проверяет, имеется ли активный экран, а затем пересылает в видеопамять
поочередно все сегменты виртуального экрана. Дескрипторы сегментов
хранятся в массиве Screens, куда заносятся при отведении памяти для
виртуального экрана. Для общности в тексте программы предполагается,
что ассемблер "не знает" 32-разрядных команд, а если это
не так, то комбинацию db $66 movsw можно заменить на movsd. Естественно,
библиотека должна содержать и 16-разрядный вариант этой подпрограммы
для видеоадаптеров, не поддерживающих возможность передачи данных
двойными словами.
Крупные и неподвижные прямоугольные объекты целесообразно выводить
на экран тем же методом, каким происходит пересылка данных между экранами.
Для этого изображения должны быть заранее (лучше на стадии написания
программы) разбиты на четыре области, каждая из которых соответствует
своей плоскости видеопамяти. Мелкие и перемещающиеся изображения,
а также изображения со сложной границей (спрайты) лучше выводить по
столбцам (процедура PtBl), и, кроме того, они должны таким же образом
размещаться и в памяти. Приведенная процедура служит для отображения
прямоугольных изображений, изображения сложной формы выводятся сходным
образом.
В листинге приведена только одна процедура
работы с реальным экраном. В общем-то все построения целесообразно
выполнять на виртуальном экране, а на реальный сбрасывать уже готовое
изображение. Однако может оказаться полезной и процедура, сбрасывающая
на реальный экран лишь фрагмент виртуального экрана, тогда, когда
есть уверенность, что остальная часть изображения осталась неизменной.
Процедуры рисования точки и заливки экрана одним цветом помещены
в листинг в демонстрационных целях. В реальной
работе они вам вряд ли понадобятся. Следует обратить внимание лишь
на то, что при заливке за одно обращение к видеопамяти закрашиваются
четыре точки.
Литература
1. Андрианов С.А. Как программировать SVGA
без головной боли//Мир ПК. 1997. # 5. С. 70.
2. Андрианов С.А. Видеоадаптер: как выйти
за предел 256 Кбайт//Мир ПК. 1997. # 9. С. 70.
3. Фролов А.В., Фролов Г.В. Программирование видеоадаптеров CGA,
EGA и VGA М.: Диалог-МИФИ, 1992.
Андрианов Сергей Андреевич - к.т.н., тел.: (254) 6-19-62, FIDO: 2:50/430.40@fidonet
Листинг. Фрагмент библиотеки для работы с экраном
в режиме 640x400x256
unit HiResLib;
{Работа с экраном в режимe 640*400*256}
{Позволяет организовать до 4 виртуальных}
{экранов (это 1Mбайт)}
{Все процедуры отображения, кроме ToScreen}
{работают только с активным виртуальным экраном}
interface
Function ScreenInit(var scr:word):boolean;
{Создает новый виртуальный экран, возвратить дескриптор}
Function ScreenDone(scr:word):boolean;
{Удаляет виртуальный экран}
Function ActiveScreen(scr:word):boolean;
{Делает экран активным}
Procedure PutPixel(X,Y:word;Color:byte);
{Рисует точку}
Procedure FillScreen(Color:byte);
{Заполняет экран цветом "Color"}
Procedure ptbl(var massiv:byte;X,Lenght,Y,Height:integer);
{Блок на активный экран}
Procedure ToSCreen(scr1,scr2:word);
{Пересылка виртуальных экранов из scr1 в scr2}
Procedure ToVgaScreen;
{Пересылка из активного экрана на видеоэкран}
implementation
type
Screen4Type = array[0..63999]of byte;
const
SegA000 = $a000;
SeqP = $3c4;
NumberScreens:word = 0;
{Количество открытых виртуальных экранов}
ScreenPresent: array [0..3] of boolean = (FALSE,FALSE,FALSE,FALSE);
{Факт инициализации экранов}
var
Screens: array [0..3,0..3] of word;
{Дескрипторы[экранов, сегментов экрана]}
ScreenAN: integer;{Номер активного экрана}
{Инициализация нового виртуального экрана}
Function ScreenInit(var scr:word):boolean;
{Возвращает номер экрана}
var ptr1:^Screen4Type; i:integer;
begin
if (NumberScreens < 4) and
(Memavail > longint(Sizeof(Screen4Type))*4) then begin
NumberScreens := NumberScreens + 1; {Число экранов}
if not ScreenPresent[0] then ScreenAN := 0 {Присвоение}
else if not ScreenPresent[1] then ScreenAN := 1 {номера}
else if not ScreenPresent[2] then ScreenAN := 2 {новому}
else if not ScreenPresent[3] then ScreenAN := 3; {экрану}
for i := 0 to 3 do begin {Отведение памяти}
{под все 4 сегмента}
new(ptr1);
Screens[ScreenAN,i] := seg(ptr1^);
end;
ScreenPresent[ScreenAN] := TRUE;
ScreenInit := TRUE;
Scr := ScreenAN; {Возвращает номер экрана}
end
else begin
ScreenInit := FALSE;
writeln('Слишком много экранов:',NumberScreens);
end;
end;
Function ScreenDone(scr:word):boolean;
{Уничтожение виртуального экрана}
var ptr1:^Screen4Type; i:integer;
begin
if ScreenPresent[scr] then begin
NumberScreens := NumberScreens - 1;
{Число экранов}
for i := 3 downto 0 do begin
ptr1 := ptr(Screens[scr,i],0);
dispose(ptr1);
end;
ScreenPresent[ScreenAN] := FALSE;
ScreenDone := TRUE;
if ScreenPresent[0] then ScreenAN := 0
{Делаем другой активным}
else if ScreenPresent[1] then ScreenAN := 1
else if ScreenPresent[2] then ScreenAN := 2
else if ScreenPresent[3] then ScreenAN := 3;
end
else
ScreenDone := FALSE;
end;
Function ActiveScreen(scr:word):boolean;{Сделать экран}
{активным}
begin
if ScreenPresent[scr] then begin
ScreenAN := scr;
ActiveScreen := TRUE;
end
else ActiveScreen := FALSE;
end;
{Вывод пиксела с координатами X,Y цветом "Color"}
Procedure PutPixel(X,Y:word;Color:byte);
Begin
mem[Screens[ScreenAN,X and 3]:Y * 160 + X shr 2] := Color;
End;
{Заполнение активного виртуального экрана цветом "Color"}
Procedure FillScreen(Color:byte);
var i:integer;
Begin
for i := 0 to 3 do FillChar(mem[Screens[ScreenAN,i]:0],64000,Color);
End;
{Отображение прямоугольной области на активный экран}
Procedure ptbl(var massiv:byte;X,Lenght,Y,Height:integer);
var
W1:array[0..3]of word; {Дескрипторы активного экрана}
k,l,l4 : word;
NachSt : word; {Смещение начала текущего столбца}
w2 : word absolute w1;
pw1 : pointer;
const
w159 : word = 159; {160-1 для спуска на строку}
begin
if not ScreenPresent[ScreenAN] then begin
writeln('Экран не определен');
halt;
end;
pw1 := @w1;
for k := 0 to 3 do w1[k] := Screens[ScreenAN,k];
l := X;
l4 := word(Y)*160; {l + (l4 * 4) - координата начала столбца на экране 640x}
k := Lenght; {k - оставшееся число столбцов}
asm
mov dx,159 {dx=160-1}
cld
les si,massiv {si - смещение во входном массиве}
@l1:
les bx,pw1 {ds:bx - адрес начала w1}
mov ax,l
and ax,3
shl ax,1 {Смещение от начала w1}
add bx,ax {ds:bx - адрес нужного дескриптора }
mov es,es:[bx] {es - дескриптор сегмента активного экрана}
mov cx,Height {cx}
mov di,l {l - начало столбца на экране 640x}
shr di,2 {di - начало столбца на экране 160x}
add di,l4
push ds
lds ax,massiv {ds}
@l2: {Для цикла нужно : ds,si,es,di,cx,dx}
movsb
add di,dx{159}
loop @l2
pop ds
inc l
dec k
jnz @l1
end;
end;
Procedure ToSCreen(scr1,scr2:word);
{Пересылка виртуальных экранов из scr1 в scr2}
var i:integer; w1,w2:word;
begin
if (ScreenPresent[scr1] and ScreenPresent[scr2]) then
for i := 0 to 3 do begin
w1 := Screens[scr1,i];
w2 := Screens[scr2,i];
asm
push ds
mov ax, w2
mov es, ax
mov ax, w1
mov ds, ax
xor si, si
xor di, di
mov cx, 16000
rep
db $66 {Так выглядит инструкция "movsd"}
movsw
pop ds
end;
end;
end;
Procedure ToVgaScreen;
{Пересылка из активного экрана на видеоэкран}
var i:integer; w1:word;
begin
if (ScreenPresent[ScreenAN]) then
for i := 0 to 3 do begin
w1 := Screens[ScreenAN,i];
PortW[SeqP] := 2 + $100 shl i;
asm
push ds
mov ax, SegA000
mov es, ax
mov ax, w1
mov ds, ax
xor si, si
xor di, di
mov cx, 16000
rep
db $66 {Так выглядит инструкция "movsd"}
movsw
pop ds
end;
end;
end;
END.