Наиболее качественная компьютерная анимация встречается в игровых телеприставках и специальных игровых компьютерах. Это объясняется тем, что в них есть множество аппаратных средств, предназначенных исключительно для вывода спрайтов, прокрутки фона и подобных операций. Когда-нибудь аналогичные функции появятся и в компьютерных видеокартах общего назначения, пока же мы ограничены тем, что имеем, и вынуждены делать все программными методами. Давайте внимательнее посмотрим, что происходит при выводе анимации. После этого мы попытаемся разобраться, почему Windows всегда проигрывает DOS в отношении качества анимации, и что можно сделать, чтобы это исправить.
Анимация в средах DOS и Windows
Подавляющее большинство дисплеев
PC хранит выводимые на экран данные в специальной области памяти — видеобуфере
(видеопамяти), и для того, что-бы изменить картинку на экране, программа
должна изменить содержимое этой области.
Программисты, пишущие программы
для DOS, отлично знают, что максимальной производительности можно достичь
только в том случае, если обращаться к этой памяти напрямую. Вместо того,
чтобы выводить изображение на экран стандартными средствами BIOS и операционной
системы, они получают указатель на область видеопамяти и напрямую копируют
в нее необходимые данные. Такой подход гарантирует скорость и качество
анимации, которые вы видите в большинстве видеоигр, работающих в среде
DOS.
Программистам для Windows
повезло меньше. Одно из основных преимуществ Windows — независимость от
устройств — становится и одним из наиболее серьезных недостатков, делая
невозможным прямой доступ к видеопамяти. Все программы должны выводить
данные на экран при помощи функций стандартного интерфейса GDI, который
имеет две стороны. Со стороны Windows это набор функций для управления
дисплеем (BitBlt(), StretchBlt(), LineTo(), Ellipse() и т.п.). Со стороны
оборудования этот интерфейс взаимодействует с аппаратурой вашего компьютера,
совершая все необходимые конкретные действия. Со стороны Windows интерфейс
обычно представляется контекстом устройства, который в программе на VC++
имеет вид объекта CDC.
Многообразие графических программ
для Windows доказывает эффективность такого подхода к графическому программированию.
Все графические операции происходят достаточно быстро, и причину этому
отыскать несложно — записывая информацию в контекст устройства при помощи
функций GDI, вы, в сущности, пишете прямо в видеопамять. Но есть и ограничение
(а где их нет?): для вывода информации в контекст устройства вы можете
пользоваться ТОЛЬКО функциями GDI. Скоро вы поймете, почему это порождает
проблемы.
Как вы, наверное, заметили, большинство программ
с быстрой графской и видео все еще пишутся для DOS, а не для Windows. Почему?
Ведь интерфейс GDI так быстр и удобен! Проблема заключается в том, что
этот интерфейс не содержит многих полезных функций, в частности тех, которые
требуются для работы с анимацией.
Анимация спрайтов в среде DOS
Пользуясь ничем не ограниченным
доступом к видеопамяти, DOS-программа может выполнять анимацию спрайтов
с максимально возможной эффективностью. В качестве примера можно рассмотреть
анимацию "прозрачного" спрайта. Программа анализирует его пиксел за пикселом
и игнорирует те пикселы, которые имеют "прозрачный" цвет. Те же, которые
окрашены по-другому, просто котируются в необходимую точку видеопамяти.
Если, например, в нашем спрайте размером 50х50 пикселов половина из них
имеет "прозрачный" цвет фона, то для вывода такого спрайта нам потребуется
скопировать 0.5х50х50= =1 250 байтов (если исходить из того, что каждый
байт занимает один пиксел). Теперь давайте сравним это с тем, что происходит
в системе Windows.
Анимация спрайтов в среде Windows
Давайте рассмотрим ту технику
анимации, которой мы пользовались в предыдущей главе. Сначала мы копируем
на экран функцией StretchBlt() маску, имеющую размер 50х50 пикселов, то
есть 2 500 байтов. После этого мы копируем сам спрайт, то есть еще 2 500
байтов — всего 5 000 байтов, то есть в четыре раза больше того количества,
которое требуется при программировании в среде DOS. Таким образом, анимация
под Windows оказывается как минимум в четыре раза менее эффективной, чем
в среде DOS. Если учесть сохранение и восстановление фона, то разница становится
еще более ощутимой. Кроме того, есть проблема мерцания. От нее можно избавиться,
производя все предварительные операции копирования в контексте устройства,
хранящемся в памяти, и потом просто копируя законченный участок на необходимое
место экрана. Однако при этом добавляется еще одна блит-операция, и эффективность
снова снижается.
Почему мы не можем просто
скопировать наш спрайт на экран, имитируя то, что происходит в среде DOS?
Дело в том, что BitBlt() и ее приятельница StretchBItO могут копировать
только прямоугольные области, поэтому нельзя скопировать только пикселы
спрайта, игнорируя фон.
"WinG, это Алиса. Алиса, это WinG. Унесите
WinG."
Поспешим вас успокоить: парни
из Microsoft знают о проблеме и уже попытались ее решить. Их решение называется
"графическая библиотека WinG"(или DirectDRAW из DirectX комплекта) — мы
кратко знакомили вас с ней в пятой главе. Буква "G" в ее названии означает
"Games" — игры. Основная цель WinG — облегчить жизнь разработчикам игр
и других проектов, требующих быстрой анимации.
Несмотря на сложность реализации
WinG, ее идея достаточно проста: эта библиотека обеспечивает непосредственный
доступ к видеопамяти. Ну, может быть, не совсем так. Мы все еще не можем
добраться к самой видеопамяти, так как это противоречило бы требованию
независимости от конкретного устройства, однако предоставляемые WinG возможности
почти так же хороши. WinG позволяет вам создать контекст устройства, который
можно обрабатывать как при помощи стандартных функций GDI, так и при помощи
непосредственных манипуляций с битами. После того, как изображение полностью
нарисовано, вы можете скопировать его на экран одной операцией BitBlt().
Конечно, по сравнению с DOS добавляется один лишний шаг — копирование,
— но разница не очень заметна, особенно если учесть скорость работы современных
видеокарт и процессоров. WinG позволяет Windows-программам выводить анимацию
почти так же эффективно, как и в среде DOS.
Теперь, когда мы познакомили
вас в общих чертах с библиотекой WinG, мы скажем вам, что вы должны о ней
забыть. Как забыть?! Очень просто. WinG разрабатывалась как расширение
старой версии Windows 3.11. Новая система Windows 95 (а также Windows NT)
уже содержит в себе возможности WinG, в частности, функцию CreateDIBSection(),
которая создает контекст устройства для непосредственной работы с битами.
Именно это и ляжет в основу нашего следующего проекта.
Реализация внеэкранного буфера
Внеэкранный буфер, создаваемый
функцией CreateDIBSection(), прост только на словах, но не на деле. Информация,
приведенная в документации на VC++, не отличается полнотой. Для того, чтобы
заставить нашу программу работать так, как надо, нам пришлось провести
ряд экспериментов, и мы все еще не
уверены, что понимаем, ПОЧЕМУ она работает. Но
будем надеяться, что сам факт ее работы (и, отметим, правильной работы!)
вас несколько утешит.
Вот примерное описание примененного нами подхода
— о деталях мы поговорим чуть позже.
Ускорение работы с палитрами
Как вы уже убедились, главное
при программировании анимации — это скорость. Конечно, значение CreateDIBSection()
трудно переоценить, однако есть и другие способы ускорить работу программы.
Давайте посмотрим: быть может, нам удастся усовершенствовать механизм работы
с палитрами. Начнем с того, что изучим стандартную блит-операцию, например,
StretchDIBits(), использованную в функции DrawBitmap() класса CDIBitmap.
Для каждого пиксела битового изображения GDI делает следующее:
Создание класса спрайтов
Мы с гордостью несем почетное
звание "объектно-ориентированных программистов", так давайте же оправдаем
его и создадим специальный класс для спрайтов, который впоследствии можно
будет использовать в других программах. Этот класс должен обладать гибкостью,
необходимой для воспроизведения самых разных последовательностей анимации,
и кроме того, должен учитывать механизм анимации, использующий CresteDIBSection().
Класс спрайта должен заниматься всеми операциями, необходимыми для загрузки
битового изображения, переключения между отдельными кадрами последовательности
и вывода спрайта. "Вывод" в данном случае означает копирование всех пикселов
спрайта, кроме имеющих "прозрачный" цвет, в определенное место внеэкранного
контекста устройства, созданного функцией CreateDIBSection().
В предыдущем проекте для хранения
каждого кадра спрайта мы использовали разные битовые изображения. Это ненужная
роскошь. Гораздо удобнее хранить все кадры спрайта в одном изображении,
подобно тому, как мы это делали при анимации перелистывающейся книги. Дополнительное
преимущество такого метода заключается в том, что классу спрайта приходится
загружать только одно битовое изображение. Мы уже создали класс CDIBitmap,
поэтому давайте произведем класс спрайта именно от него. Обратите внимание
на то, что класс спрайта не может самостоятельно определить размеры и количество
строк и столбцов в изображении с кадрами, а также общее количество кадров
— всю эту информацию необходимо указывать в явном виде.
Для нашего нового проекта
мы будем использовать битовое изображение с четырьмя кадрами, полученными
простым копированием спрайтов, созданных нами в предыдущей главе. Это изображение
приведено на рис .12.1 и может быть создано при помощи любого графического
редактора. Анимация с использованием CresteDIBSection() быстрой и
качественной анимации с использованием возможностей функции CreateDIBSection().