Бесплатная реклама

Автор: Дмитрий Сазонов

Опубликовано: 10.12.2012

Изменено: 10.12.2012

Постоянная ссылка

Комментарии [0]

Команды MMX для оптимизации графики


Все слышали рекламные фразы о небывалом ускорении мультимедийных приложений, которое обеспечивает технология MMX. Некоторые даже запускали демонстрационные программки... А ведь можно и самому использовать команды MMX – и главное, это просто!



В не таком уж далеком 1996 году корпорация Intel внедрила в свои процессоры новую мультимедийную технологию под названием MMX (MultiMedia eXtension), которая давала (со слов фирмы) 400-процентный выигрыш в скорости работы с графикой, звуком и т.п.

Прошло время... появились уже более новые и эффективные разработки (3D-Now, SSE), а MMX так и не получила особой популярности из-за малой документированности; всё, что мне удалось найти в Internet - это немного примеров на страничке самой Intel, плюс очень небольшое количество других сайтов... в общем, негусто. В данной статье я хотел бы устранить этот пробел и рассказать вам о широчайших возможностях применения и использования MMX команд. Итак...

Технология MMX состоит из 57-ми совершенно новых инструкций процессора (специально для обработки графики и звука), а также восьми универсальных 64-битных регистров (mm0-mm7). И самое главное - добавлено четыре новых 64-разрядных типа данных:
• упакованные байты (восемь байт, представляющих собой одно большое 64-битное число),
• упакованные слова (четыре обычных слова)
• упакованные двойные слова (два 32-разрядных слова)
• простое 64-битное число.

Ещё хотелось бы отметить, что практически все MMX-инструкции (за исключением умножения) выполняются за один такт работы процессора.

Сначала хотелось бы кратко рассмотреть все эти команды (более подробное описание можно найти практически в любом современном справочнике по ассемблеру):

1) Команды пересылки данных – обмен данными между: MMX регистрами, стандартными 32-битными регистрами, а также ячейками памяти (Movd, Movq)

2) Команды преобразования типов – конверсия из одного типа данных MMX в другой (Packss, Packus, Punpckh, Punpckl)

3) Арифметические операции – универсальные команды сложения, вычитания, умножения упакованных типов данных, как с насыщением (т.е при переполнении остается максимальное или минимальное значение), так и без него (Padd, Padds, Paddus, Psub, Psubs, Psubus, Pmulhw, Pmullw, Pmaddwd)

4) Команды сравнения – сравнивают элементы данных (байты, слова, двойные слова) с последующим созданием маски результата (Pcmpeq, Pcmpgt)

5) Логические операции – обычные команды логики, за исключением, что работают с 64-битной точностью (Pxor, Por, Pand, Pandn).

6) Команды сдвига – аналог стандартных команд сдвига, только эти предназначены специально для работы с упакованными типами данных (Psll, Psrl, Psra).

7) Обнуление FPU регистров (Emms) – обязательно должно стоять после всех MMX процедур, т.к. регистры mm0-mm7 заодно являются мантиссой регистров математического сопроцессора (st0-st7).

А теперь – вперед! Применим все это на практике...

Примечание:
Все процедуры, описанные здесь, основаны на 32-х и 24-х битных RGB-режимах графики.




1. Для первого примера возьмем реализацию быстрого вывода картинки (спрайта) с наложением – байты изображения складываются с уже имеющимися байтами на экране (источники освещения [lensflares], частицы [particles], различные многослойные эффекты и т.д.).

Edi - адрес экрана (буфера экрана)
Ebx - адрес спрайта

Mov al, [edi] ; загрузка байта с экрана
Mov ah, [ebx] ; загрузка байта спрайта
Add al,ah ; их сложение
Jnc loop ; если число получилось меньше 255, то переход на loop
Mov al,255 ; при числе >255 делаем его равным 255
Loop: Mov [edi],al ; поместить содержимое регистра al на экран
Inc edi ; следующий байт экрана
Int ebx ; следующий байт спрайта
....
.... ; (так всего 3 раза, т.е отдельно для каждой R,G,B-компоненты)


Попробуем переделать это под MMX:

Movd mm0,[edi] ; загрузка четырех байт с экрана
Movd mm1,[ebx] ; загрузка четырех байт спрайта
Paddusb mm0,mm1 ; сложение их с проверкой переполнения на 255 (сразу четыре байта !!!)
Movd [edi],mm0 ; поместить эти четыре байта обратно на экран
Add ebx,4 ; следующие байты спрайта
Add edi,4 ; следующие байты экрана


Сразу очевиден огромный прирост в скорости из-за параллельной обработки сразу четырех байт, а также из-за отсутствия команд условных переходов. Кстати - при использовании Psubusb (с константой) вместо Paddusb можно получить супер-быстрый RGB-фильтр, что тоже весьма приятно.




2. Ещё один пример вывода картинки (спрайтов): на этот раз с прозрачностью – вывод на экран только тех байт, чьи значения не равны константе прозрачности (этот метод используется практически во всех современных 2D-играх).

Mov al,[ebx] ; загрузка байта спрайта
Cmp al,0 ; проверка на прозрачность (т.е. при al=0 на экран байт не записываем)
Jz loop
Mov [edi],al ; если al не равно 0 - кладём его на экран
Loop: Inc ebx ; следующий байт спрайта
Inc edi ; следующий байт экрана
....
.... ;(так всего 3 раза, т.е отдельно для каждой R,G,B-компоненты)


MMX вариант:

Pxor mm2,mm2 ; обнуляем регистр mm2
Movd mm0,[ebx] ; загрузка четырех байт спрайта
Movd mm1,[esi] ; загрузка четырех байт экрана (фона)
Pcmpeqb mm2,mm0 ; делаем маску тех байт которые надо вывести
Pand mm2,mm1 ; обнуляем ненужные
Por mm1,mm0 ; складываем их с нашими байтами спрайта
Movd [esi],mm1 ; кладём их обратно на экран
Add ebx,4
Add esi,4


Как и в первом примере, получаем немалое ускорение - за счёт отсутствия команд условных переходов, а также из-за параллельности обработки сразу нескольких байт изображения.




3. Размытие при движении (motion blur) – объекты оставляют за собой плавно угасающий шлейф (применяется и на телевидении, и в 3D играх).

mask7f1 = 0x7f7f7f7f7f7f7f7f;
movq mm4,mask7f1 ; загрузка маски в регистр mm4

movq mm0,[esi] ; загрузка восьми байт текущего кадра (изображение на экране в данный момент)
movq mm1,[edi] ; загрузка восьми байт предыдущего кадра
psrlq mm0,1 ; быстрая процедура деления на два каждого из 8 байт
psrlq mm1,1 ; (как в mm0, так и в mm1)
pand mm0,mm4 ; -//-
pand mm1,mm4 ; -//-
paddb mm0,mm1 ; сложение mm0 с mm1 (=среднее арифметическое между mm0 и mm1)
movq [esi],mm0 ; кладём это на экран (в видеобуфер)
movq [edi],mm0 ; а также в буфер предыдущего кадра
add esi,8
add edi,8


Сразу восемь байт! То самое 400% ускорение, которое нам так долго рекламировала Intel!




4. Канал прозрачности (alpha blending) [далее ab] – одно изображение плавно появляется или растворяется поверх другого (также очень часто применяется при работе с видео).

Формула

ab: a = b + (a - b) * alpha

где:
a – основное изображение
b – изображение которое накладывается
alpha – количество градаций(позиций) ab - обычно хватает 0...255.

Переведём это все в MMX-команды:

a) инициализация регистров

биты 31...24 23...16 15...8 7...0
eax = alpha alpha alpha alpha

movd mm4,eax ; переносим данных из eax в mm4
punpcklwd mm4,mm4 ; создаём четыре cлова alpha-канала
psrlw mm4,8 ; переносим старшую часть слов в младшую
pxor mm7,mm7 ; обнуляем mm7


б) основная процедура

movd mm0,[esi] ; загрузить в mm1 четыре байта накладываемого изображения
movd mm1,[edx] ; загрузить в mm0 четыре байта основного изображения
punpcklbw mm0,mm7
punpcklbw mm1,mm7
psubw mm0,mm1 ; вычитаем из накладываемого, основное изображение
psllw mm1,8 ; переносим младшую часть слов регистра mm1 в cтаршую
pmullw mm0,mm4 ; умножаем накладываемое изображение на alpha-канал
paddw mm1,mm0 ; складываем его с основным
psrlw mm1,8 ; переносим результат из старшей части слова в младшую
packuswb mm1,mm1 ; и переводим его в 32 бита
movd [edi],mm1 ; кладем полученное изображение на экран (в видеобуфер)
add esi,4
add edx,4
add edi,4


В данном примере используется быстрое 16-битное MMX-умножение, которое и даёт максимальное ускорение нашей процедуре. Плюс, уже ставшая традицией, обработка сразу четырех байт.

Ну вот, в приведённых примерах мы рассмотрели практически все MMX-команды и способы ММХ-оптимизации - применяя их. Осталось заметить, чтобы получить ещё более быстродействующие программы, нужно размещать команды согласно правилам оптимизации под соответствующий процессор (к сожалению, у Intel и AMD они разные), а также правильно использовать его внутренний кэш. Большую часть этой работы берут на себя компиляторы высокого уровня (C++, Pascal, Basic и т.д.), но ассемблерные вставки придется переделывать вручную. На чем хотелось бы и закончить.

Используйте MMX в своих программах!




Статья взята из журнала "Программист" (№ 1 за 2001 год).