[Назад] [Далее]

4.5. Графические видеорежимы

4.5.1. Работа с 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 такта на точку.


п»ї
"target=_blank><\/a>") //-->