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, чему и будет посвящена следующая глава.