Assembler - язык неограниченных возможностей

         

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


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

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

Номер режима Разрешение Число цветов
11h 640x480 2
12h 640x480 16
13h 320x200 256

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

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

INТ 10h АН = 0Ch — Вывести точку на экран

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

INТ 10h AH = 0Dh — Считать точку с экрана

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

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

; lifebios.asm ; Игра "Жизнь" на поле 320x200, использующая вывод на экран средствами BIOS .model small .stack 100h ; явное задание стека - для ЕХЕ-программ .code .186 ; для команд shl al,4 и shr al,4 start: push FAR_BSS ; сегментный адрес буфера в DS pop ds


; заполнение массива ячеек псевдослучайными значениями xor ах,ах int 1Ah ; Функция АН = 0 INT 1Ah: получить текущее ; время DX теперь содержит число секунд, ; прошедших с момента включения компьютера, ; которое используется как начальное значение ; генератора случайных чисел mov di,320*200+1 ; максимальный номер ячейки fill_buffer: imul dx,4E35h ; простой генератор случайных чисел inc dx ; из двух команд mov ax,dx ; текущее случайное число копируется в АХ, shr ax,15 ; от него оставляется только один бит, mov byte ptr [di],al ; и в массив копируется 00, если ячейка ; пуста, и 01, если заселена dec di ; следующая ячейка jnz fill_buffer ; продолжить цикл, если DI не стал равен нулю

mov ах,0013h ; графический режим 320x200, 256 цветов int 10h

; основной цикл

new_cycle:

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

mov di,320*200+1 ; максимальный номер ячейки step_1: mov al,byte ptr [di+1] ; в AL вычисляется сумма add al,byte ptr [di-1] ; значений восьми соседних ячеек, add al,byte ptr [di+319] ; при этом в младших четырех add al,byte ptr [di-319] ; битах накапливается число add al,byte ptr [di+320] ; соседей add al,byte ptr [di-320] add al,byte ptr [di+321] add al,byte ptr [di-321] shl al,4 ; теперь старшие четыре бита AL - число ; соседей текущей ячейки or byte ptr [di],al ; поместить их в старшие четыре бита ; текущей ячейки dec di ; следующая ячейка jnz step_1 ; продолжить цикл, если DI не стал равен нулю

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

mov di,320*200+1 ; максимальный номер ячейки flip_cycle: mov al,byte ptr [di] ; считать ячейку из массива shr al,4 ; AL = число соседей cmp al,3 ; если число соседей = 3, je birth ; ячейка заселяется, cmp al,2 ; если число соседей = 2, je f_c_continue ; ячейка не изменяется, mov byte ptr [di],0 ; иначе - ячейка погибает jmp short f_c_continue birth: mov byte ptr [di],1 f_c_continue: and byte ptr [di],0Fh ; обнулить число соседей в старших ; битах ячейки dec di ; следующая ячейка jnz flip_cycle ; ; Вывод массива на экран средствами BIOS ; mov si,320*200+1 ; максимальный номер ячейки mov сх,319 ; максимальный номер столбца mov dx,199 ; максимальный номер строки zdisplay: mov al,byte ptr [si] ; цвет точки (00 - черный, 01 - синий) mov ah,0Ch ; номер видеофункции в АН int 10h ; вывести точку на экран dec si ; следующая ячейка dec cx ; следующий номер столбца jns zdisplay ; если столбцы не закончились - продолжить, mov сх,319 ; иначе: снова максимальный номер столбца в СХ dec dx ; и следующий номер строки в DX, jns zdisplay ; если и строки закончились - выход из цикла mov ah,1 ; если не нажата клавиша int 16h jz new_cycle ; следующий шаг жизни



mov ах,0003h ; восстановить текстовый режим int 10h mov ax,4C00h ; и завершить программу 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 каждый байт в области памяти, начинающейся с адреса A000h:0000h, соответствует одной точке на экране, а значение, которое может принимать этот байт (0 – 255), соответствует номеру цвета этой точки. (Цвета, которые соответствуют этим номерам, могут быть перепрограммированы с помощью видеофункции 10h BIOS.) В видеорежимах 11h и 12h каждый бит соответствует одной точке на экране, так что простым копированием в видеопамять можно получить только черно-белое изображение (для вывода цветного изображения в режиме 12h необходимо перепрограммировать видеоадаптер, об этом см. в главе 5.10.4).

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

mov si,320*200+1



до команды

jns zdisplay

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

push 0A000h ; сегментный адрес видеопамяти pop es ; в ES mov cx,320*200 ; максимальный номер точки mov di,cx ; в видеопамяти - 320 * 200 mov si,cx ; а в массиве - inc si ; 320 * 200 + 1 rep movsb ; выполнить копирование в видеопамять

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

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


Содержание раздела