Devotes - Предисловие
Уважаеме посетители, Сайт переезжает на другой хост http://www.devote.ru/
   Поиск по сайту:  
» Главная
Книги
Музыка
Download
Форум
[Назад] [Далее]

6.4.3. Вывод на экран через линейный кадровый буфер

; lfbfire.asm
; Программа, работающая с SVGA при помощи линейного кадрового буфера (LFB),
; демонстрирует стандартный алгоритм генерации пламени.
; Требуется поддержка LFB видеоплатой (или загруженный эмулятор univbe),
; для компиляции необходим DOS-расширитель
;
        .486p                    ; для команды xadd
        .model     flat          ; основная модель памяти в защищенном режиме
        .code
        assume     fs:nothing    ; нужно только для MASM
_start:
        jmp        short _main
        db         "WATCOM"      ; нужно только для DOS/4GW

; начало программы
; на входе обычно CS, DS и SS указывают на один и тот же сегмент с лимитом 4 Гб,
; ES указывает на сегмент с PSP на 100h байт,
; остальные регистры не определены
_main:
        sti                      ; даже флаг прерываний не определен,
        cld                      ; не говоря уже о флаге направления

        mov        ax,0100h      ; функция DPMI 0100h
        mov        bx,100h       ; размер в 16-байтных параграфах
        int        31h           ; выделить блок памяти ниже 1 Мб
        jc         DPMI_error
        mov        fs,dx         ; FS - селектор для выделенного блока

; получить блок общей информации о VBE 2.0 (см. главу 4.5.2)
        mov        dword ptr fs:[0],'2EBV' ; сигнатура VBE2 в начало блока
        mov        word ptr v86_eax,4F00h  ; функция VBE 00h
        mov        word ptr v86_es,ax      ; сегмент, выделенный DPMI
        mov        ax,0300h                ; функция DPMI 300h
        mov        bx,0010h                ; эмуляция прерывания 10h
        xor        есх,есх
        mov        edi,offset v86_regs
        push       ds
        pop        es                      ; ES:EDI - структура v86_regs
        int        31h                     ; получить общую информацию VBE2
        jc         DPMI_error
        cmp        byte ptr v86_eax,4Fh    ; проверка поддержки VBE
        jne        VBE_error
        movzx      ebp,word ptr fs:[18]    ; объем SVGA-памяти в EBP
        shl        ebp,6                   ; в килобайтах

; получить информацию о видеорежиме 101h
        mov        word ptr v86_eax,4F01h  ; номер функции INT 10h
        mov        word ptr v86_ecx,101h   ; режим 101h - 640x480x256
                                           ; ES:EDI те же самые
        mov        ax,0300h                ; функция DPMI 300h - эмуляция
        mov        bx,0010h                ; прерывания INT 10h
        xor        ecx,ecx
        int        31h                     ; получить данные о режиме
        jc         DPMI_error
        cmp        byte ptr v86_eax,4Fh
        jne        VBE_error
        test       byte ptr fs:[0],80h     ; бит 7 байта атрибутов = 1 - LFB есть
        jz         LFB_error

; построение дескриптора сегмента, описывающего LFB
; лимит
        mov        eax,ebp                 ; видеопамять в килобайтах
        shl        eax,10                  ; теперь в байтах
        dec        еах                     ; лимит = размер - 1
        shr        еах,12                  ; лимит в 4-килобайтных единицах
        mov        word ptr videodsc+0,ax  ; записать биты 15-0 лимита
        shr        еах,8
        and        ah,0Fh
        or         byte ptr videodsc+6,ah  ; и биты 19 - 16 лимита
        mov        eax,ebp                 ; видеопамять в килобайтах
        shl        eax,10                  ; в байтах
        mov        edx,dword ptr fs:[40]   ; физический адрес LFB
        shld       ebx,edx,16              ; поместить его в CX:DX,
        shld       esi,eax,16              ; а размер - в SI:DI
        mov        ax,800h                 ; и вызвать функцию DPMI 0800h
        int        31h                     ; отобразить физический адрес в линейный
        shrd       edx,ebx,16              ; перенести полученный линейный адрес
        mov        dx,cx                   ; из BS:CX в EDX
        mov        word ptr videodsc+2,dx  ; и записать биты 15-0 базы
        mov        byte ptr videodsc+4,dl  ; биты 23 - 16
        mov        byte ptr videodsc+7,dh  ; и биты 31 -24
; права
        mov        bx,cs
        lar        cx,bx                   ; прочитать права нашего сегмента
        and        cx,6000h                ; и перенести биты DPL
        or         byte ptr videodsc+5,ch  ; в строящийся дескриптор

; поместить наш дескриптор в LDT и получить селектор
        mov        ex,1                    ; получить один новый дескриптор
        mov        ах,0                    ; у DPMI
        int        31h
        jc         DPMI_error
        mov        word ptr videosel,ax    ; записать его селектор
        push       ds
        pop        es
        mov        edi,offset videodsc     ; ES:EDI - адрес нашего дескриптора
        mov        bx,ax                   ; BX - выданный нам селектор
        mov        ax,0Ch                  ; функция DPMI 0Ch
        int        31h                     ; загрузить дескриптор в LDT
        jc         DPMI_error              ; теперь в videosel лежит селектор на LFB
; переключение в режим 4101h (101h + LFB)
        mov        word ptr v86_eax,4F02h  ; 4F02h - установить SVGA-режим
        mov        word ptr v86_ebx,4101h  ; режим 4101h = 101h + LFB
        mov        edi,offset v86_regs     ; ES:EDI - структура v86_regs
        mov        ах,0300h                ; функция DPMI 300h
        mov        bx,0010h                ; эмуляция прерывания 10h
        xor        ecx,ecx
        int        31h

        mov        ax,word ptr videosel    ; AX - наш селектор
enter_flame:                               ; сюда придет управление с селектором
                                           ; в ах на A000h:0000, если произошла
                                           ; ошибка в любой VBE-функции
        mov        es,ax                   ; ES - селектор видеопамяти или LFB

; отсюда начинается процедура генерации пламени

; генерация палитры для пламени
        xor        edi,edi                 ; начать писать палитру с адреса ES:0000
        xor        есх,есх
palette_gen:
        xor        еах,еах                 ; цвета начинаются с 0, 0, 0
        mov        cl,63                   ; число значений для одной компоненты
palette_11:
        stosb                              ; записать байт
        inc        eax                     ; увеличить компоненту
        cmpsw                              ; пропустить два байта
        loop       palette_11              ; и так 64 раза

        push       edi
        mov        cl,192
palette_12:
        stosw                              ; записать два байта
        inc        di                      ; и пропустить один
        loop       palette_12              ; и так 192 раза (до конца палитры)
        pop        edi                     ; восстановить EDI
        inc        di
        jns        palette_gen

; палитра сгенерирована, записать ее в регистры VGA DAC (см. главу 5.10.4)
        mov        al,0                    ; начать с регистра 0
        mov        dx,03C8h                ; индексный регистр на запись
        out        dx,al
        push       es
        push       ds                      ; поменять местами ES и DS
        pop        es
        pop        ds
        xor        esi,esi
        mov        ecx,256*3               ; писать все 256 регистров
        mov        edx,03C9h               ; в порт данных VGA DAC
        rep        outsb
        push       es
        push       ds                      ; поменять местами ES и DS
        pop        es
        pop        ds

; основной цикл - анимация пламени, пока не будет нажата любая клавиша,
        xor        edx,edx                 ; должен быть равен нулю
        mov        ebp,7777h               ; любое число
        mov        ecx,dword ptr scr_width ; ширина экрана
main_loop:
        push       es                      ; сохранить ES
        push       ds
        pop        es                      ; ES = DS - мы работаем только с буфером
; анимация пламени (классический алгоритм)
        inc        ecx
        mov        edi,offset buffer
        mov        ebx,dword ptr scr_height
        shr        ebx,1
        dec        ebx
        mov        esi,scr_width
animate_flame:
        mov        ax,[edi+esi*2-1]        ; вычислить среднее значение цвета
        add        al,ah                   ; в данной точке (EDI) из значений
        setc       ah                      ; цвета в точке слева и на две строки
        mov        dl,[edi+esi*2+1]        ; вниз, справа и на две строки вниз
        add        ax,dx
        mov        dl,[edi+esi*4]          ; и на четыре строки вниз.
        add        ax,dx                   ; причем первое значение
        shr        ax,2                    ; модифицировать
        jz         already_zero            ; уменьшить яркость цвета
        dec        ax
already_zero:
        stosb                              ; записать новый цвет в буфер
        add        eax,edx
        shr        eax,1
        mov        byte ptr [edi+esi-1],al
        loop       animate_flame
        mov        ecx,esi
        add        edi,ecx
        dec        ebx
        jnz        animate_flame

; псевдослучайная полоска внизу экрана, которая служит генератором пламени
generator_bar:
        xadd       bp,ax
        stosw
        stosw
        loop       generator_bar
        pop        es                      ; восстановить ES для вывода на экран

;вывод пламени на экран
        xor        edi,edi                 ; ES:EDI - LFB
        push       esi
        add        esi,offset buffer       ; DS:ESI - буфер
        mov        ecx,dword ptr scr_size  ; размер буфера в двойных словах
        rep        movsd                   ; скопировать буфер на экран
        pop        esi
        mov        ah,1                    ; если не была нажата
        int        16h                     ; никакая клавиша,
        jz         main_loop               ; продолжить основной цикл,
        mov        ah,0                    ; иначе -
        int        16h                     ; считать эту клавишу

exit_all:
        mov        ах,03h                  ; восстановить текстовый режим
        int        10h
        mov        ax,4C00h                ; AH = 4Ch
        int        21h                     ; выход из программы под расширителем DOS

; различные обработчики ошибок
DPMI_error:                                ; ошибка при выполнении
                                           ; одной из функций DPMI
        mov        edx,offset DPMI_error_msg
        mov        ah,9
        int        21h                     ; вывести сообщение об ошибке
        jmp        short exit_all          ; и выйти
VBE_error:                                 ; не поддерживается VBE
        mov        edx,offset VBE_error_msg
        mov        ah,9
        int        21h                     ; вывести сообщение об ошибке
        jmp        short start_with_vga    ; и использовать VGA
LFB_error:                                 ; не поддерживается LFB
        mov        edx,offset LFB_error_msg
        mov        ah,9                    ; вывести сообщение об ошибке
        int        21h
start_with_vga:
        mov        ah,0                    ; подождать нажатия любой клавиши
        int        16h
        mov        ax,13h                  ; переключиться в видеорежим 13h
        int        10h                     ; 320x200x256
        mov        ax,2                    ; функция DPMI 0002h
        mov        bx,0A000h               ; построить дескриптор для реального
        int        31h                     ; сегмента
        mov        dword ptr scr_width,320 ; установить параметры режима
        mov        dword ptr scr_height,200
        mov        dword ptr scr_size,320*200/4
        jmp        enter_flame             ; и перейти к пламени

        .data
; различные сообщения об ошибках
VBE_error_msg      db      "Ошибка VBE 2.0",0Dh,0Ah
                   db      "Будет использоваться режим VGA 320x200",0Dh,0Ah,'$'
DPMI_error_msg     db      "Ошибка DPMI$"
LFB_error_msg      db      "LFB недоступен",0Dh,0Ah
                   db      "Будет использоваться режим VGA 320x200",0Dh,0Ah,'$'
; параметры видеорежима
scr_width          dd      640
scr_height         dd      480
scr_size           dd      640*480/4
; структура, используемая функцией DPMI 0300h
v86_regs label byte
v86_edi            dd      0
v86_esi            dd      0
v86_ebp            dd      0
v86_res            dd      0
v86_ebx            dd      0
v86_edx            dd      0
v86_ecx            dd      0
v86_eax            dd      0
v86_flags          dw      0
v86_es             dw      0
v86_ds             dw      0
v86_fs             dw      0
v86_gs             dw      0
v86_ip             dw      0
v86_cs             dw      0
v86_sp             dw      0
v86_ss             dw      0
; дескриптор сегмента, соответствующего LFB
videodsc           dw      0               ; биты 15-0 лимита
                   dw      0               ; биты 15-0 базы
                   db      0               ; биты 16-23 базы
                   db      10010010b       ; доступ
                   db      10000000b       ; биты 16-19 лимита и другие биты
                   db      0               ; биты 24 - 31 базы
; селектор сегмента, описывающего LFB
videosel           dw      0

        .data?
; буфер для экрана
buffer             db      640*483 dup(?)

; стек
        .stack     1000h
        end        _start

Программирование с DOS-расширителями — один из лучших выходов для приложений, которые должны работать в любых условиях, включая старые версии DOS, и в то же время требуют 32-битный режим. Еще совсем недавно большинство компьютерных игр, в частности знаменитые Doom и Quake, выпускались именно как программы, использующие расширители DOS. Сейчас, с повсеместным распространением операционных систем для PC, работающих в 32-битном защищенном режиме, требование работы в любых версиях DOS становится менее актуальным и все больше программ выходят только в версиях для Windows 95 или NT, чему и будет посвящена следующая глава.


Hosted by uCoz