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

         

Трансцендентные функции


Многие операции при работе с графикой используют умножение числа на синус (или косинус) некоторого угла, например при повороте: s = sin(n) * v. При использовании арифметики с фиксированной точкой 16:16 это уравнение преобразуется в s = int(sin(n) * 65 536) * v/65 536 (где int — целая часть). Для требовательных ко времени работы участков программ, например для работы с графикой, принято вообще не вычислять значения синусов, а считывать из таблицы, содержащей значения выражения int(sin(n) * 65 535), где n меняется от 0 до 90 градусов с требуемым шагом (редко требуется шаг меньше 0,1 градуса). Затем синус любого угла от 0 до 90 градусов можно вычислить с помощью всего одного умножения и сдвига на 16 бит. Синусы других углов и косинусы вычисляются в соответствии с обычными формулами приведения:

sin(x) = sin(180-x) для 90 < х < 180 sin(x) = -sin(x-180) для 180 < х < 270 sin(x) = -sin(360-x) для 270 < х < 360 cos(x) = sin(90-x)

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

Таблицы синусов (или косинусов), используемые в программе, можно создать заранее с помощью простой программы на языке высокого уровня в виде текстового файла с псевдокомандами DW и включить в текст программы директивой include. Другой способ, занимающий меньше места в тексте, но чуть больше времени при запуске программы, — однократное вычисление всей таблицы. Таблицу можно вычислять как с помощью команды FPU fsin и потом преобразовывать к желаемому формату, так и сразу в формате с фиксированной запятой. Существует довольно популярный алгоритм, позволяющий вычислить таблицу косинусов (или синусов, с небольшой модификацией), используя рекуррентное выражение

cos(xk) = 2cos(step)cos(xk-1) - cos(xk-2)

где step — шаг, с которым вычисляются косинусы, например 0,1 градуса.

; liss.asm ; строит фигуры Лиссажу, используя арифметику с фиксированной запятой ; и генерацию таблицы косинусов. ; Фигуры Лиссажу - семейство кривых, задаваемых параметрическими выражениями ; x(t) = cos(SCALE_V * t) ; y(t) = sin(SCALE_H * t) ; ; чтобы выбрать новую фигуру, измените параметры SCALE_H и SCALE_V, ; для построения незамкнутых фигур удалите строку add di,5l2 ; в процедуре move_point


.model tiny .code .386 ; будут использоваться 32-битные регистры org 100h ; СОМ-программа

SCALE_H equ 3 ; число периодов в фигуре по горизонтали SCALE_V equ 5 ; число периодов по вертикали

start proc near cld ; для команд строковой обработки mov di,offset cos_table ; адрес начала таблицы косинусов mov ebx,16777137 ; 224 * cos(360/2048) - заранее вычисленное mov cx,2048 ; число элементов для таблицы call build_table ; построить таблицу косинусов

mov ax,0013h ; графический режим int 10h ; 320x200x256

mov ax,1012h ; установить набор регистров палитры VGA, mov bx,70h ; начиная с регистра 70h mov cx,4 ; четыре регистра mov dx,offset palette ; адрес таблицы цветов int 10h

push 0A000h ; сегментный адрес видеопамяти pop es ; в ES

main_loop: call display_picture ; изобразить точку со следом mov dx,5000 xor cx,cx mov ah,86h int 15h ; пауза на CX:DX микросекунд mov ah,11h ; проверить, была ли нажата клавиша, int 16h jz main_loop ; если нет - продолжить основной цикл

mov ах,0003h ; текстовый режим int 10h ; 80x24



ret ; конец программы start endp

; процедура build_table ; строит таблицу косинусов в формате с фиксированной запятой 8:24 ; по рекуррентной формуле cos(xk) = 2 * cos(span/steps) * cos(xk-1) - cos(xk-2), ; где span - размер области, на которой вычисляются косинусы (например, 360), ; a steps - число шагов, на которые разбивается область ; Ввод: DS:DI = адрес таблицы ; DS:[DI] = 224 ; EBX = 224 * cos(span/steps) ; СХ = число элементов таблицы, которые надо вычислить ; Вывод: таблица размером СХ * 4 байта заполнена ; Модифицируются: DI,CX,EAX,EDX

build_table proc near mov dword ptr [di+4],ebx ; заполнить второй элемент таблицы sub ex,2 ; два элемента уже заполнены add di,8 mov eax,ebx build_table_loop: imul ebx ; умножить cos(span/steps) на cos(xk-1) shrd eax,edx,23 ; поправка из-за действий с фиксированной ; запятой 8:24 и умножение на 2 sub eax,dword ptr [di-8] ; вычитание cos(xk-2) stosd ; запись результата в таблицу loop build_table_loop ret build_table endp



; процедура display_picture ; изображает точку со следом

display_picture proc near call move_point ; переместить точку mov bp,73h ; темно-серый цвет в нашей палитре mov bx,3 ; точка, выведенная три шага назад, call draw_point ; изобразить ее dec bp ; 72h - серый цвет в нашей палитре dec bx ; точка, выведенная два шага назад, call draw_point ; изобразить ее dec bp ; 71h - светло-серый цвет в нашей палитре dec bx ; точка, выведенная один шаг назад, call draw_point ; изобразить ее dec bp ; 70h - белый цвет в нашей палитре dec bx ; текущая точка call draw_point ; изобразить ее ret display_picture endp

; процедура draw_point ; Ввод: BP - цвет ; BX - сколько шагов назад выводилась точка ; draw_point proc near movzx сx,byte ptr point_x[bx] ; Х-координата movzx dx,byte ptr point_y[bx] ; Y-координата call putpixel_13h ; вывод точки на экран ret draw_point endp

; процедура move_point ; вычисляет координаты для следующей точки, ; изменяет координаты точек, выведенных раньше

move_point proc near inc word ptr time and word ptr time,2047 ; эти две команды организуют ; счетчик в переменной time, который ; изменяется от 0 до 2047 (7FFh) mov еах,dword ptr point_x ; считать координаты точек mov ebx,dword ptr point_y ; (по байту на точку) mov dword ptr point_x[1],eax ; и записать их со сдвигом mov dword ptr point_y[1],ebx ; 1 байт mov di,word ptr time ; угол (или время) в DI imul di,di,SCALE_H ; умножить его на SCALE_H and di,2047 ; остаток от деления на 2048, shl di,2 ; так как в таблице 4 байта на косинус mov ax,50 ; масштаб по горизонтали mul word ptr cos_table[di+2] ; Умножение на косинус. ; Берется старшее слово (смещение + 2) от ; косинуса, записанного в формате 8:24, ; фактически происходит умножение на косинус ; в формате 8:8 mov dx,0A000h ; 320/2 (X центра экрана) в формате 8:8 sub dx,ax ; расположить центр фигуры в центре экрана mov byte ptr point_x,dh ; и записать новую текущую точку mov di,word ptr time ; угол (или время) в DI imul di,di,SCALE_V ; умножить его на SCALE_V add di,512 ; добавить 90 градусов, чтобы заменить ; косинус на синус. Так как у нас 2048 ; шагов на 360 градусов, ; 90 градусов - это 512 шагов and di,2047 ; остаток от деления на 2048, shl di,2 ; так как в таблице 4 байта на косинус mov ax,50 ; масштаб по вертикали mul word ptr cos_table[di+2] ; умножение на косинус mov dx,06400h ; 200/2 (Y центра экрана) в формате 8:8 sub dx,ax ; расположить центр фигуры в центре экрана mov byte ptr point_y,dh ; и записать новую текущую точку ret move_point endp



; putpixel_13h ; процедура вывода точки на экран в режиме 13h ; DX = строка, СХ = столбец, ВР = цвет, ES = A000h putpixel_13h proc near push di mov ax,dx ; номер строки shl ax,8 ; умножить на 256 mov di,dx shl di,6 ; умножить на 64 add di,ax ; и сложить - то же, что и умножение на 320 add di,cx ; добавить номер столбца mov ax,bp stosb ; записать в видеопамять pop di ret putpixel_13h endp

point_x db 0FFh,0FFh,0FFh,0FFh ; Х-координаты точки и хвоста point_y db 0FFh,0FFh,0FFh,0FFh ; Y-координаты точки и хвоста db ? ; пустой байт - нужен для команд ; сдвига координат на один байт time dw 0 ; параметр в уравнениях Лиссажу - время или угол palette db 3Fh,3Fh,3Fh ; белый db 30h,30h,30h ; светло-серый db 20h,20h,20h ; серый db 10h,10h,10h ; темно-серый cos_table dd 1000000h ; здесь начинается таблица косинусов

end start

При генерации таблицы использовались 32-битные регистры, что приводит к увеличению на 1 байт и замедлению на 1 такт каждой команды, использующей их в 16-битном сегменте, но на практике большинство программ, интенсивно работающих с графикой, — 32-битные.


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