4.5.1. Работа с VGA-режимами

Функция 00 прерывания BIOS 1 Oh позволяет переключаться не только в тек­стовые режимы, использовавшиеся в предыдущих главах, но и в некоторые гра­фические. Эти видеорежимы стандартны и поддерживаются всеми видеоадапте­рами (начиная с VGA), см. табл. 19.

Таблица19. Основные графические режимы VGA

Номер режима

Разрешение

Число цветов

11h

640x480

2

12h

640x480

16

13h

320x200

256

Существуют еще несколько видеорежимов, использовавшихся более старыми видеоадаптерами CGA и EGA (с номерами от 4 до

BIOS также видеофункции чтения и записи точки на экране

в графических режимах, но эти функции настолько медленно исполняются, что никогда не применяются в реальных программах.

INT 10h АН= OCh: Вывести точку на экран Вход:    АН = OCh

= номер видеостраницы (игнорируется для режима 13h, поддержи­вающего только одну страницу) DX = номер строки СХ = номер столбца

AL = номер цвета (для режимов 10h и llh, если старший бит 1, номер цвета точки на экране будет результатом операции «исключаю­щее ИЛИ») Выход: Никакого

INT 10h АН = 0Dh: Считать точку с экрана Вход:   АН = ODh

ВН = номер видеостраницы (игнорируется для режима поддержи­вающего только одну страницу) DX = номер строки СХ = номер столбца

Выход: AL = номер цвета

Попробуем тем не менее воспользоваться средствами BIOS для вывода на эк­ран. Следующая программа переводит экран в графический режим 13h (320x200), заселяет его точками случайным образом, после чего эти точки эволюционируют согласно законам алгоритма «Жизнь»: если у точки меньше двух или больше трех соседей, она погибает, а если у пустой позиции есть три соседа, в ней появляется новая точка. Мы будем использовать очень простой, но неоптимальный способ ре­ализации этого алгоритма: сначала для каждой точки вычисляется число соседей, затем каждая точка преобразуется в соответствии с полученным числом соседей, после чего каждая точка выводится на экран.

; lifebios.asm

;   Игра  "Жизнь"   на поле 320X200,   использующая  вывод на экран средствами BIOS.;

small

.stack lOOh

.code

start:

push

pop

FAH_BSS

ds

Явное задание стека - для ЕХЕ-программ. Для команд эИ1 а1,4 и эпг а1,4.

Сегментный адрес буфера в DS. '

Заполнение массива ячеек псевдослучайными значениями.

хог

int

ax, ax 1АЬ

mov

di,320*200+1

fill_buffer:

 

imul

dx,4E35h

inc

dx

mov

ax, dx

shr

ax, 15

mov

byte'ptr [d

dec

di .

jnz

-filljnjffer

mov

ax,00l3h

int

10h

Функция AH = 0 INT 1Ah: получить

время.

DX теперь содержит число секунд, прошедших с момента включения компьютера, которое используется как начальное значение генератора  случайных чисел.

Максимальный номер ячейки. Простой генератор случайных чисел

из двух команд.

Текущее случайное число копируется в АХ,

от него оставляется только один бит, и в массив копируется 00, если ячейка пуста,  и 01,  если заселена.

Следующая ячейка.

Продолжить цикл,   если DI не стал равен нулю.

Графический режим 320x200, 256 цветов.

; Основной цикл. пеи_сус1е:

; Шаг 1: для каждой ячейки вычисляется число соседей ; и записывается в старшие 4 бита этой ячейки.

mov di,320*200+1

Максимальный номер ячейки.

step_1:

mov

add add add add add add

add shl

or

dec

jnz

ptr. al,byte ptr al.byte ptr al.byte ptr al.byte ptr al.byte ptr al.byte ptr al.byte ptr al,4

[di+1] [di-1]

[dl+319] [di-319]

[di+320] [cli-320] [di+321] [di-321]

byte ptr [di],al

di

stepj

В А1 вычисляется сумма значений восьми соседних ячеек, при этом в младших четырех битах накапливается число соседей.

Теперь старшие четыре бита АЬ - число

соседей текущей ячейки.

Поместить их в старшие четыре бита

текущей ячейки.

Следующая ячейка.

Продолжить цикл, если Б! не стал равен нулю.

; Шаг 2: изменение состояния ячеек в соответствии с полученными в шаге 1 ; значениями числа соседей.

mov

di,320*200+1

Максимальный номер ячейки.

flip_cycle:

 

 

mov

al.byte ptr [di]

Считать ячейку из массива,

shr

al,4

АЬ = число соседей.

cmp

al,3

Если число соседей = 3,

je

birth

ячейка заселяется.

cmp

al,2

Если число соседей = 2,

je

f с continue

ячейка не изменяется.

mov

byte ptr [di],0

Иначе - ячейка погибает.

jmp

short f_c_continue

 

birth:

 

 

mov

byte ptr [di],1

 

f с continue:

 

 

and

byte ptr [di],OFh

;  Обнулить число соседей в

 

 

; битах ячейки.

dec

di

;  Следующая ячейка.

jnz

flip_cycle

 

Вывод массива

на экран средствами BIOS.

 

mov

Si,320*200+1

Максимальный номер ячейки.

mov

cx,319

Максимальный номер столбца

mov

dx,199

Максимальный номер строки.

старших

zdisplay:

mov mov

int dec dec

jns ptr

ah,ОСЬ 10h

si

cx

zdisplay

[si]

Цвет точки (00 - черный,  01 - синий).

Номер видеофункции в АН.

Вывести точку на экран.

Следующая ячейка.

Следующий номер столбца.

Если столбцы не закончились - продолжить.

Основы программноеания для IV! S DOS

mov

;

Иначе:  снова максимальный номер столбца в СХ

dec

dx    ' ;

и следующий номер строки в БХ.

jns

zdisplay ;

Если и строки закончились - выход из цикла.

mov

ah, 1

;  Если не нажата клавиша -

int

16h

 

jz

new_cycle

;  следующий шаг жизни.

mov

ax,0003h

;   Восстановить текстовый режим

int

10h

 

mov

;

и завершить программу.

int

21h

 

.fardata?                 ' ;

Сегмент дальних неинициализированных данных

 

db 320*200+1

dup(?) ; содержит массив ячеек.

end

start

 

Этот фрагмент оформлен как ЕХЕ-программа, потому что применяется мас­сив, близкий по размерам к сегменту, и если разместить его в одном сегменте с СОМ-программой, стек, растущий от самых старших адресов, может затереть область данных. В нашем примере стек не используется, но он нужен обработчи­ку прерывания BIOS 10h.

Скорость работы указанной программы - в среднем 200 тактов процессора Pentium на точку (измерения выполнены с помощью команды RDTSC, см. раз­дел 10.2), то есть всего 16 поколений в секунду для Pentium-200 (200 миллионов тактов в секунду разделить на 200 тактов на точку и на 320x200 точек). Разумеет­ся, используемый алгоритм крайне нерационален и кажется очевидным, что его оптимизация приведет к значительному выигрышу во времени. Но если измерить скорость выполнения каждого из трех циклов, то окажется, что первый цикл вы­полняется в среднем за 20,5 такта на точку, второй - за 13, а третий - за 170,5!

Исправить эту ситуацию весьма просто - достаточно отказаться от видеофунк­ций BIOS для работы с графикой и перейти к прямому копированию в видеопамять.

В видеорежиме 13h каждый байт в области памяти, начинающейся с адреса 0AO00h:0O00h, соответствует одной точке на экране, а значение, которое может принимать этот байт (0-255), - номеру цвета этой точки. (Цвета, которые соотносятся с данными номерами, могут быть перепрограммированы с помощью видеофункции 10h BIOS.) В видеорежимах lib и 12h каждый бит соответствует одной точке на экране, так что простым копированием в видеопамять можно по­лучить только черно-белое изображение (для вывода цветного изображения

в режиме 12h необходимо перепрограммировать видеоадаптер; об этом см. в раз­деле 5.10.4).

В нашем примере для хранения информации о каждой ячейке также использу­ется один байт, следовательно, для вывода данных на экран в режиме 13h достаточ­но выполнить простое копирование. Переименуем программу LIFEBIOS.ASM в LIFEDIR.ASM, заменив цикл вывода на экран от команды

mov       si, 320*200+1 .

до второй команды

jns

zdisplay

следующим фрагментом кода:

.push pop mov

OAOOOh

Сегментный адрес видеопамяти в ES.

Максимальный номер точки в видеопамяти - 320x200,

es

сх,320*200

mov

mov

inc

di.cx si, сх

si

а в массиве -

320x200 + 1.

Выполнить копирование видеопамять.

rep

raovsb

Теперь программа обрабатывает одну точку приблизительно за 61,5 такта про­цессора Pentium, что дает 51 поколение в секунду на Кроме того, сейчас эту программу можно переписать в виде СОМ-файла, так как и код, и мас­сив, и стек точно умещаются в одном сегменте размером 64 Кб. Такая СОМ-про­грамма (LIFECOM.ASM) займет 143 байта.

Оптимизация программы «Жизнь» - хорошее упражнение для программиро­вания на ассемблере. В 1997 году проводился конкурс на самую короткую и на самую быструю программу, выполняющую в точности то же, что и наш пример, -заполнение экрана случайными точками, их эволюция и выход по нажатию лю­бой клавиши. Самой короткой тогда оказалась программа размером в 72 байта, которая с тех пор была усовершенствована до 64 байт (ее скорость 52 такта на точку), а самая быстрая из программ тратит на каждую точку в сред-

нем всего 6 тактов процессора Pentium и имеет размер 689 байт. В ней состояния ячеек описываются отдельными битами массива, а для их обработки используют­ся команды логических операций над целыми словами, поэтому одна команда обслуживает сразу    точек. Применение команд с тем же алгоритмом

позволяет ускорить программу до 4,5 такта на точку.