Глава 12 Интерфейс DirectDraw
Добро пожаловать в
<машинное отделение>! В этой главе мы
рассмотрим интерфейс DirectDraw, который открывает
перед нами низкоуровневый доступ к
видеоадаптеру. Мы узнаем, для чего нужен данный
интерфейс, как он устроен и что с ним делать.
Приложение для этой главы находится в каталоге
DDEval.
Что такое DirectDraw?
Библиотека DirectDraw
предоставляет единый программный интерфейс для
работы с различными видеоадаптерами. Но ведь
подобный интерфейс, Microsoft Windows Graphics Device Interface (GDI),
существовал и ранее? Вы совершенно правы. Главное
отличие между DirectDraw и GDI заключается в том, что
DirectDraw позволяет работать непосредственно с
видеоадаптером, a GDI - наоборот, ограждает вас от
этого! Возможно, сказанное не совсем справедливо
по отношению к GDI - интерфейс проектировался для
создания переносимых приложений, а о какой
переносимости можно говорить, если кто угодно
как угодно развлекается с видеоадаптером?
Конечно, переносимые
приложения - вещь хорошая, но если программные
прослойки, обеспечивающие переносимость,
оказываются слишком <толстыми> или
неэффективными, это снижает производительность
приложения и делает его недопустимо медленным.
Игры как раз и составляют такой класс приложений,
для которых незначительная разница в
производительности может обернуться разницей
между доходами и потерями для
фирмы-разработчика.
Даже несмотря на то, что
Windows GDI совершенствовался с годами, всегда
хотелось обойти его и напрямую обращаться к
видеоадаптеру - например, когда приложение
работает в полноэкранном режиме. Если приложение
занимает весь экран, почему бы не дать ему полную
свободу в работе с видеоадаптером? Интерфейс
DirectDraw позволяет нам почти напрямую обращаться к
видеоадаптеру в любом Windows-приложении. Остается
лишь решить, стоит этим пользоваться или нет?
Чтобы вам было проще
принять решение, мы кратко рассмотрим наиболее
интересные возможности DirectDraw на примере
несложного приложения. Это поможет вам в
дальнейшем самостоятельно экспериментировать с
DirectDraw.
Архитектура DirectDraw
На рис. 12-1 изображена
слегка упрощенная архитектура GDI и DirectDraw с точки
зрения приложения, работающего с трехмерными
объектами.
Как видите, для рисования
трехмерного объекта у приложения есть четыре
возможности:
GDI.
OpenGL.
Абстрактный режим Direct3D.
DirectDraw.
Рис. 12-1. Архитектуры GDI и
DirectDraw
Рисование в GDI
Windows-программисты хорошо
знают путь, изображенный слева на рис. 12-1.
Приложение вызывает функции GDI, преобразуемые в
вызовы DIB-механизма. DIB-механизм обращается к
драйверу видеоустройства, который работает с
видеоадаптером. Чтобы создать трехмерное
приложение, пользующееся услугами GDI, вам
придется самостоятельно выполнять все
преобразования координат, вычисления глубины,
отсечение невидимых линий и т. д. В результате
всей этой работы должно появиться приложение,
которое компилируется и работает на
разнообразных платформах.
Рисование в OpenGL
Для полноты картины я
включил в наше рассмотрение механизм
визуализации OpenGL. OpenGL входит в Win32 и позволяет
создавать переносимые трехмерные приложения,
обходясь без самостоятельных вычислений глубины
и т. д. Язык OpenGL поддерживается на большом
количестве платформ, что повышает переносимость
вашего приложения. Качество реализации OpenGL
отличается на разных платформах, так что
производительность приложения также может
изменяться. Поскольку реализация OpenGL в Microsoft Windows
95 использует прослойку Direct3D, ее
производительность оказывается лучше, чем в ряде
других систем.
Рисование в абстрактном
режиме Direct3D
До настоящего времени мы
использовали только абстрактный режим Direct3D.
Механизм абстрактного режима во многих
отношениях напоминает OpenGL. Главное отличие
заключается в том, что абстрактный режим
оптимизирован для работы с прослойкой Direct3D и
обеспечивает бесспорно лучшую
производительность из всех возможных.
Рисование в DirectDraw
Путь, который мы
рассмотрим сейчас, идет от приложения прямо к
прослойке DirectDraw/Direct3D. Особый интерес
представляет ветка DirectDraw, которая идет вниз, к
видеоадаптеру, через HAL (прослойка абстрактной
аппаратуры, Hardware Abstraction Layer) и HEL (прослойка
эмуляции аппаратуры, Hardware Emulation Layer). Работа
только с сервисом DirectDraw в определенном отношении
напоминает обращение с GDI, поскольку немалую
часть вычислении вам придется выполнять
самостоятельно. Тем не менее если вы уверены в
своих силах, то этот путь теоретически позволит
добиться наилучшей производительности,
поскольку перед вами открывается почти прямой
доступ к видеоадаптеру. Если вы собираетесь
перенести в Windows уже существующий механизм
визуализации, то вам следует идти именно этим
путем.
На рис. 12-1 компоненты DirectDraw
и Direct3D изображены в одной общей рамке. Между ними
существует немало сходства, так что в условном
изображении архитектуры их можно разместить
вместе. Тем не менее они все же отличаются между
собой, и в этой главе мы будем рассматривать
только DirectDraw. О Direct3D рассказывается в главе 13.
Прослойка абстрактной
аппаратуры (HAL)
Мне не нравятся термины
<прослойка> или <абстрактный>, поскольку
они наводят на мысли о какой-то жирной и
неповоротливой программе, хотя HAL с подобным
представлением не имеет ничего общего. HAL
существует для того, чтобы обеспечить
возможность унифицированного взаимодействия с
видеоадаптером. Во многих случаях ее можно
просто считать таблицей, которая показывает, где
хранятся те или иные данные. Например, при
отображении видеопамяти на основное адресное
пространство HAL содержит информацию о том, с
какого адреса начинается блок видеопамяти. Кроме
того, HAL знает о том, через какой порт можно
работать с аппаратной палитрой и т. д. На самом
деле прослойка HAL оказывается очень тонкой.
Память большинства
видеокарт организована в виде одного большого
непрерывного блока, поэтому вы можете получить
указатель на видеопамять и рисовать
непосредственно в ней. Однако в некоторых старых
картах эпохи VGA используется технология
переключения банков памяти, которая отображает
фрагменты видеопамяти в небольшое окно,
расположенное в адресном пространстве
процессора. Это означает, что вы можете получить
указатель лишь на часть видеопамяти. На выручку
приходит HAL. HAL имитирует большой, непрерывный
буфер видеопамяти и позволяет осуществлять
чтение/запись по несуществующим адресам. При
попытке чтения/записи по такому адресу процессор
генерирует исключение, которое HAL перехватывает
и использует для правильного
отображения адреса на
нужный фрагмент видеопамяти. Благодаря
виртуальной адресации практически все
видеокарты для программы выглядят так, словно
они обладают единым, непрерывным адресным
пространством.
Прослойка эмуляции аппаратуры
(HEL)
HEL существует для того,
чтобы предоставлять услуги, не поддерживаемые
конкретной моделью видеоадаптера. Многие
видеокарты содержат блиттеры - аппаратные
средства для пересылки битовых блоков (bitbit) из
одного участка видеопамяти в другой. На
некоторых картах имеются блиттеры для пересылки
блоков в основной памяти, а также в видеопамяти.
Пересылая спрайт в своей программе, вы не
задумываетесь о том, где он хранится в данный
момент. Это означает, что некоторая внешняя
программа должна следить за аппаратными и
программными средствами. Такая задача возложена
на HEL, которая осуществляет программную эмуляцию
той или иной операции в случае, если ваши
аппаратные средства не могут ее выполнить.
Компоненты DirectDraw
Архитектуру DirectDraw можно
рассмотреть и с несколько иной точки зрения, как
показано на рис. 12-2.
В этом случае DirectDraw
предоставляет интерфейс для работы с
видеопамятью и палитрой. Я изобразил на рисунке
палитру, поскольку 256-цветные дисплеи с палитрой
продолжают оставаться самыми распространенными.
Если ваша видеосистема работает с 16-, 24- или
32-битной кодировкой пикселей, то палитра не
понадобится, и схема несколько упрощается. Если
временно не обращать внимания на палитру, мы
увидим, что приложение должно располагать
средствами для доступа к видеопамяти и
перемещения данных в ней.
В типичном приложении с
анимацией используется несколько буферов
видеопамяти. Первичный буфер содержит
изображение, которое в настоящий момент
выводится на экране видеоадаптером. Вторичный
буфер предназначен для построения следующего
кадра. В типичной плоской анимации для каждого
персонажа имеется несколько спрайтов.
Желательно кэшировать их в буфере, из которого
блиттер мог бы извлекать их с минимальными
усилиями, - обычно такой буфер находится в
видеопамяти.
Если учесть, что HEL
обеспечивает выполнение блитовых операций даже
в том случае, если они не поддерживаются на
аппаратном уровне, то становится понятно, почему
на рис. 12-2 нет аппаратного блиттера. Приложение
должно работать лишь с различными областями
видеопамяти и, возможно, с палитрой.
Для приложения с
трехмерными объектами схема выглядит более
сложно, однако мы рассмотрим соответствующие
отличия в следующей главе. А сейчас давайте
обсудим различные компоненты DirectDraw и способы
управления ими.
Поверхности в видеопамяти
DirectDraw делит видеопамять на
поверхности. Поскольку объем памяти каждой
поверхности может достигать размера наибольшего
свободного блока памяти, вы можете завести одну
поверхность, которая использует всю память, или
несколько меньших поверхностей. Поверхности
разумно создавать в основной памяти, что-
Рис. 12-2. Компоненты DirectDrow
бы при исчерпании всей
свободной видеопамяти вам не пришлось вносить
исправления в программу. Разумеется, рисование
на поверхностях в основной памяти происходит
медленнее, чем обращение к поверхности в
видеопамяти, поэтому за расположением
поверхностей необходимо следить.
К одним поверхностям могут
присоединяться другие. Взгляните на рис. 12-2.
Вторичный буфер присоединен к первичному, в этом
случае ими легче управлять, поскольку вам нужно
хранить указатель лишь на первичный буфер.
Указатель на вторичный буфер всегда можно
получить из списка присоединенных поверхностей
первичного буфера. При работе с трехмерными
объектами иногда бывает необходимо отводить
поверхность под Z-буфер, ее тоже удобно
присоединить к первичному буферу. Кроме того,
поверхность можно выделить под альфа-буфер и
также присоединить ее к другой поверхности.
Поверхность описывается
структурой DDSURFACEDESC, в которой содержатся поля для
высоты поверхности, ее ширины и т. д. Кроме того, в
нее входит структура DDPIXELFORMAT, описывающая формат
отдельных пикселей. Пиксели могут быть заданы в
виде RGB-тройки, индекса палитры, YUV-значения или в
каком-нибудь другом формате, который
поддерживается вашим видеоадаптером. Количество
бит на пиксель изменяется от 1 до 32. Для
поверхностей, где число бит на пиксель
составляет 16 и менее, цветовые составляющие
(например, красная, зеленая и синяя) задаются в
виде масок, для которых следует выполнить
операцию поразрядного AND со значением
конкретного пикселя.
Чтобы получить доступ к
поверхности, необходимо заблокировать ее и
получить указатель. Завершив работу с
поверхностью, вы разблокируете ее. Блоки-
ровка нужна для доступа к
поверхности в режиме чтение/запись; она также
помогает организовать взаимодействие с
аппаратным блиттером и т. д. Следовательно, перед
тем как пытаться получить доступ к поверхности,
необходимо знать о том, кто ее использует. Пример
будет приведен немного ниже, в разделе
<Тестирование прямого доступа к пикселям> на
стр. 296.
Рабочая поверхность может
быть потеряна, если в системе присутствует
другое приложение, также использующее DirectDraw. При
попытке выполнить какую-нибудь операцию с
потерянной поверхностью обычно возвращается код
ошибки DDERR_SURFACELOST. Потерянная поверхность
восстанавливается функцией IDirectDrawSurface::Restore
(ресурсы Windows используются совместно - со
временем к этому привыкаешь).
Поверхности также могут
содержать отдельный цвет или диапазон цветов,
используемых в качестве цветового ключа.
Цветовые ключи находят достаточно разнообразное
применение, в частности с их помощью задаются
области поверхности-источника или приемника, не
подлеж`щие копированию. Функция IDirectDrawSurface::Blt
содержит бпециальный флаг, который управлпет
использованием цветовых ключей при копиаовании.
<`>Палитры
Для описания цветов в
поверхностях с 1-, 4- и 8-битной кодировкой пикселей
применяются палитры. В DirectDraw поддерживаются 4- и
8-битные палитры, которые содержат 16 и 256 цветов
соответственно. Палитра также может
представлять собой набор индексов в другой
палитре. Если вам приходилось работать с
палитрами в Windows, то и палитры DirectDraw покажутся
хорошо знакомыми. Если же вы привыкли <играть>
с цветовыми таблицами видеоадаптера, то вам,
вероятно, понравится гибкость работы с палитрами
DirectDraw. Создание палитр рассматривается ниже, на
стр. 298.
Большая часть
видеоадаптеров способна работать всего с одной
палитрой, но DirectDraw позволяет связать с каждой
поверхностью произвольную палитру. Тем не менее
следует учесть, что при блитовых операциях цвета
не преобразуются; при выполнении блиттинга между
поверхностями, палитры которых отличаются,
результат окажется довольно жутким.
При создании палитры вы
указываете, какую ее часть можно отдать в
распоряжение DirectDraw. При работе в оконном режиме
обычно приходится резервировать 20 системных
цветов Windows (по 10 с каждого конца палитры) и
разрешать DirectDraw пользоваться оставшимися 236
элементами. В полноэкранном режиме другие
приложения все равно не видны, поэтому
резервировать для них системные цвета незачем, и
вы можете предоставить в распоряжение DirectDraw всю
палитру. DirectDraw определяет несколько новых
флагов, показывающих, как используется тот или
иной элемент палитры (в дополнение к
существующим флагам Windows). Флаг D3DRMPALEФTE_FREE
позволяет DirectDraw использовать данный элемент
паливры. Флаг D3DRMPALETTE_READONLY позволяет DirectDraw читать
элемент палитры и работать с ним, не изменяя его
значение (используется длп системных цветов в
оконном режиме©, а флаг D3DRMPALETTE_RESERVED резервирует
элемент палитры для ваших собственных целей.
DirectDraw не изменяет и не использует элементы
палитры, помеченные флагом D3DRMPALETTE_RESERVED.
Ограничители
DirectDraw работает в одном из
двух режимов. Первый режим, который больше всего
привлекает бывших DOS-программистов, -
полноэкранный. В этом случае вы можете полностью
распоряжаться изображением на экране. Обычно в
полноэкранном режиме создаются два буфера, как
показано на рис. 12-3.
Рис. 12-3. Работа с буферами в
полноэкранном режиме
Первичный и вторичный
буфера имеют одинаковый размер. В полноэкранном
режиме можно переключать буфера, то есть
выводить на экран содержимое вторичного буфера
вместо первичного. Таким образом реализуется
переключение страниц видеопамяти с минимальными
накладными расходами. Разумеется, при желании
можно воспользоваться блит-функциями для
переноса содержимого вторичного буфера в
первичный.
Второй режим больше
соответствует облику стандартных приложений
Windows. В оконном режиме приходится работать с
привычным окном приложения, расположенным на
рабочем столе, как показано на рис. 12-4.
Оконный режим связан с
определенными сложностями, поскольку
поверхность вторичного буфера используется
совместно с GDI. При непосредственной записи в
видеопамять необходимо соблюдать осторожность и
не выйти за пределы области вашего окна. Чтобы
помочь вам в этом, DirectDraw предоставляет
объект-ограничитель DirectDrawClipper, который
присоединяется к первичному буферу.
Ограничитель следит за окном, созданным на
рабочем столе, и определяет границы памяти
первичного буфера. В оконном режиме вы уже не
можете переключать страницы, как это делается в
полноэкранном режиме, и размер вторичного буфера
обычно равен лишь размеру окна. Некоторые
видеоадаптеры выполняют сложные перекрытия,
которые позволяют в оконном режиме переключать
страницы так, как это делается в полноэкранном;
при соответствующей аппаратной поддержке DirectDraw
позволяет вызвать функцию переключения Flip для
поверхности в оконном режиме.
Компоненты DirectDraw
281
Рис. 12-4. Оконный режим
Ограничитель не только
следит за тем, чтобы вы не пытались что-нибудь
записать за пределы прямоугольной области окна.
Если другое окно частично перекроет ваше, то вы
также не сможете рисовать поверх перекрывающего
окна.
Поверхности и GDI
Возможно, вы уже решили,
что отныне пользуетесь только DirectDraw и можете
забыть про существование GDI. Это неверно сразу по
двум причинам. Во-первых, GDI никуда не пропадает и
пользуется видеопамятью совместно с вашими
программами. Во-вторых, функциями GDI можно
пользоваться для рисования на поверхностях
DirectDraw. На первый взгляд это кажется излишним, но
что вы будете делать, если, например, необходимо
вывести текст?
Важно понимать, что GDI не
исчезает, даже если вы получили прямой доступ к
видеопамяти. GDI продолжает работать с
поверхностью, которая использовалась перед
запуском вашего приложения. Если приложение
требует монопольного управления и переключается
в полноэкранный режим, GDI все равно осуществляет
вывод в используемый ранее буфер и теоретически
способен испортить содержимое ваших буферов.
Вмешательство GDI можно предотвратить - для этого
перед переходом в полноэкранный режим следует
создать окно, занимающее всю площадь рабочего
стола. GDI поймет, что ваше окно должно находиться
наверху, и не будет пытаться рисовать поверх
того, что он принимает за экранную область.
Интерфейс GDI используется
и для работы с поверхностями. С помощью DirectDraw
получают контекст устройства (DC) для любой
созданной поверхности, после чего по DC
вызываются функции GDI для рисования на
поверхности.
282
Глава 12. Интерфейс DirectDraw
Пример будет рассмотрен
ниже, на стр. 291. С некоторыми операциями GDI
справляется очень хорошо (например, вывод текста
и цветовая заливка областей). Не стоит полностью
отвергать этот интерфейс лишь потому, что
основная часть вашего приложения напрямую
работает с видеопамятью. Если сомневаетесь -
попробуйте сами, измерьте скорость и оцените
результаты.
Интересный побочный
эффект от использования видеопамяти в GDI
прослеживается при попытке отладить программу,
непосредственно работающую с пикселями, в
графическом отладчике (например, отладчике Microsoft
Visual C++). Перед тем, как работать с поверхностью, ее
необходимо заблокировать. При этом DirectDraw
блокирует подсистему Win 16, через которую
осуществляется доступ к таким компонентам Windows,
как USER и GDI. Когда DirectDraw заблокирует Winl6, GDI
перестанет работать, поэтому ваш отладчик не
сможет ничего вывести на экран и <повиснет>.
Выход заключается в том,
чтобы работать с отладчиком, не использующим GDI
(например, WDEB386), или запустить Visual C++ в сеансе
удаленной отладки, подключаясь к целевому
компьютеру через кабель или сетевое соединение
TCP/IP. Инструкции по удаленной отладке приведены в
справочном разделе Books Online среды Visual C++.
Работа с DirectDraw
После краткого знакомства
с <машинным отделением> DirectDraw, давайте
посмотрим, как же пользоваться интерфейсом DirectDraw
в наших приложениях. Код, которым мы будем
заниматься, взят из примера, находящегося в
каталоге DDeval. Это приложение написано мной
специально для экспериментов с DirectDraw. Его даже
трудно назвать приложением - скорее, это
инструмент для тестирования и оценки, поэтому
средства его пользовательского интерфейса
ограничены. DDEval не делает ничего
сверхъестественного, но показывает вам, как
использовать GDI для работы с поверхностью, как
запустить программу в полноэкранном или оконном
режиме, как напрямую работать с пикселями
поверхности. Тесты позволяют измерить скорость
работы приложения, выраженную в количестве
кадров в секунду (fps), чтобы вы могли наглядно
представить себе производительность.
ПРИМЕЧАНИЕ
Помните, что скорость работы
приложения может заметно отличаться для
различных моделей видеоадаптеров. Если вы
захотите измерить производительность своего
кода, обязательно протестируйте его на различных
видеоадаптерах.
Структура программы DDEval
Я создал программу DDEval с
помощью Visual C++ AppWizard. Я решил оформить ее в виде
окна диалога, а не в виде приложения с
интерфейсом SDI или MDI. Окно диалога изображено на
рис. 12-5.
DDEval проводит четыре теста,
каждый из которых может выполняться либо в
оконном, либо в полноэкранном режиме. В оконном
режиме размер окна выбирается из списка (от 320х200
до 1024х768). Количество цветов всегда совпадает с
Работа с DirectDraw 'тЩ! 283
Рис. 12-5. Приложение DDEval
числом цветов в системе, на
которой выполняется программа. Чтобы изменить
цветовой режим, необходимо обратиться к
свойствам экрана в Панели управления'Windows.
При выборе полноэкранного
режима программа составляет перечень всех
возможных режимов. На моем компьютере Dell он
состоит из 17 режимов, так что на однообразие
жаловаться не приходится. Полноэкранные режимы
не ограничиваются текущим количеством цветов.
Даже если вы работаете с 8-битными цветами, то при
желании можете провести тестирование в
полноэкранном режиме с 16--битными цветами.
Рассматриваемая нами
программа пользуется классами библиотеки SdPlus,
которые представляют собой очень тонкие
оболочки классов C++ для интерфейсов DirectDraw. По
возможности я буду приводить в функциях
обращения как к классам C++, так и к базовым
интерфейсам.
Программа DDEval
Начнем с простейшего:
подготовки объекта CDirectDraw к работе и составления
перечня доступных полноэкранных режимов.
Управляющий объект CDirectDraw создается следующим
образом:
void CDDEvalDIg::SetInitialState () (
// Создать объект DirectDraw m_pDD =
new CDirectDraw;
BOOL b = m_pDD->Create () ;
}
В этом простейшем
фрагменте спрятан довольно большой объем кода,
создающего первичный и вторичный буфера, а также
связанную с ними палитру. Мы рассмотрим его чуть
позже.
После того как создан
объект CDirectDraw, можно составлять перечень
доступных полноэкранных режимов. Приведенная
ниже функция заполняет список в окне диалога
DirectDraw Evaluation:
284
Глава 12. Интерфейс DirectDraw
void CDDEvalDIg::ShowModes(
int iModes = m_pDD->GetNumModes () ;
DDSORFACEDESC dds;
for (int i = 0; i < iModes; i++) (
m_pDD->GetMode!nfo (i, &dds);
sprintf(buf,
"%41u x %41u x %21u", dds.dwWidth,
dds.dwHeight,
dds.ddpfPixelFormat.dwRGBBitCount)
m_cbModes.AddString(buf) ;
}
Как видно из листинга, мы
определяем режимы и получаем описание
поверхности для каждого из них. Затем для каждого
режима конструируется строка, которая заносится
в список. Код объекта CDirectDraw выглядит несколько
сложнее, поскольку для нумерации режимов в DirectDraw
используются функции косвенного вызова. Я
реализовал функции CDirectDraw::GetNumModes и CDirectDraw::GetModelnfo
с помощью одной функции косвенного вызова,
расположенной в файле 3dDirDrw.cpp:
// Информационная
структура для нумерации режимов typedef struct _EnumModeInfo {
int iModeCount;
int iMode;
DDSURFACEDESC ddSurf;
} EnumModeInfo;
// Функция косвенного
вызова для нумерации // режимов устройства
static HRESULT FAR PASCAL
EnumModesFn(LPDDSURFACEDESC psd, LPVOID pArg)
1
EnumModeInfo* pinfo = (EnumModeInfo*)
pArg;
ASSERT(pinfo) ;
// Проверить правильность
режима if (p!nfo->iMode == p!nfo->iModeCount) {
p!nfo->ddSurf = *psd;
return DDENuMRET_CANCEL; // Прекратить
нумерацию
}
p!nfo->iModeCount++;
return DDENUMRET_OK;
}
Работа с DirectDraw ТЙВ 285
// Определить количество
поддерживаемых экранных режимов int
CDirectDraw::GetNumModes()
{
ASSERT(m_pIDD) ;
EnumModeInfo mi;
mi.iModeCount = 0;
mi.iMode = -1;
m_hr = m_pIDD->EnumDisplayModes (0, NULL, Smi,
EnumModesFn) ;
ASSERT(SUCCEEDED(m_hr)) ;
return mi.iModeCount;
}
// Получить данные для
заданного режима BOOL CDirectDraw::GetModeInfo(int iMode,
DDSURFACEDESC* pDesc)
{
int iModes = GetNumModes();
if (iMode >= iModes) return FALSE;
ASSERT(m_pIDD) ;
EnumModeInfo mi;
mi. iModeCount =0;
mi.iMode = iMode;
m hr = m pIDD->EnumDisplayModes (0, NULL, &mi,
EnumModesFn);
ASSERT(SUCCEEDED(m_hr)) ;
*pDesc = mi.ddSurf;
return TRUE;
}
Структура EnumModeInfo
управляет функцией нумерации и служит для
возвращения результата. При подсчете количества
режимов возвращаемые данные не используются. При
получении информации по конкретному режиму
функция нумерации вызывается до тех пор, пока
номер режима, возвращаемый интерфейсом DirectDraw, не
совпадет с заданным.
После выбора теста
пользователем, функция тестирования заполняет
структуру сведениями о размере экрана и
количестве цветов, а затем создает окно для
выполнения теста. Давайте поочередно рассмотрим
все четыре теста, их назначение и принципы
работы.
Подготовка тестового окна
Для каждого теста мы
создаем окно, устанавливаем соответствующий
режим DirectDraw и производим тестирование. Каждый
тест выполняется примерно для 100 кадров, сообщает
о результатах и проверяет, не была ли нажата
клавиша, прекращающая процесс тестирования. Ниже
приведена функция, которая создает окно,
устанавливает режим и запускает тест:
286 в^ Глава 12. Интерфейс DirectDraw
BOOL CTestWnd::Create(TESTINFO* pti)
{
// Сохранить информацию о
тесте m_pTI = pti;
ASSERT(m_pTI) ;
// Создать объект DirectDraw m_pDD =
new CDirectDraw;
BOOL b = m_pDD->Create () ;
ASSERT(b);~
II Зарегистрировать
класс окна
CString strClass =
AfxRegisterWndClass(CS_HREDRAW I CS_VREDRAW,
::LoadCursor(NULL, IDC_ARROW), (HBRUSH)::GetStockObject(GRAY_BRUSH)
// Задать стиль и размеры
окна DWORD dwStyle = WS_VISIBLE Ѓ WS_POPUP;
RECT re;
if (m_pTI->bFullScreen) {
re.top = 0;
re.left = 0;
re.right = ::GetSystemMetrics(SM_CXSCREEN);
re.bottom = ::GetSystemMetrics(SM_CYSCREEN);
} else { // Оконный режим
dwStyle Ѓ= WS_CAPTION Ѓ WS_SYSMENU;
re.top = 50;
re.left = 50;
re.bottom = re.top + m_pT I->i Height;
re. right = re. left + m_pTI->iWidth;
::AdjustWindowRect(&rc, dwStyle, FALSE);
\
if (!CreateEx(0,
strClass,
"DirectDraw Window",
dwStyle,
re.left, re.top,
re.right - re.left, re.bottom - re.top,
m pTI->pParent->GetSafeHwnd() ,
NULL)) {
return FALSE;
i
// Обеспечить отображение
окна на экране UpdateWindow() ;
// Установить режим для окна
Работа с DirectDraw 'Ч^' 287
ASSERT(m_pTI) ;
if (m_pTI->bFullScreen) f
b = m_pDD->SetFull3creenMode (GetSafeHwndO ,
m_pTI->iWidth,
m_pTI->iHeight,
m_pTI->iBpp) ;
} else (
b = m_pDD->SetWindowedMode (GetSafeHwnd () ,
m_pTI->iWidth,
m_pTI->iHeight) ;
} ASSERT(b) ;
// Выполнить тестирование SetTimerfl,
100, NULL);
return TRUE;
>
В первой половине функции
мы создаем объект CDirectDraw и тестовое окно, которое
появляется на экране. Большей частью она состоит
из стандартного кода Windows. После того как окно
создано, объект CDirectDraw переводится в оконный или
полноэкранный режим. Наиболее сложной
оказывается завершающая часть. Функции SetFullScreenMode
и SetWindowedMode равносильны вызову функции
CDirectDraw::_SetMode, выполняющей всю работу по созданию
первичного и вторичного буферов, а также
связанной с ними палитры. Установка режима
состоит из трех этапов:
1. Задание уровня
кооперации (cooperative level), который определяет, какие
действия разрешается выполнять с DirectDraw. Если
приложение должно работать в окне, выбирается
нормальный режим. Для полного экрана следует
затребовать монопольный (exclusive) режим. Уровень
кооперации помогает распределять ресурсы между
системой и приложениями DirectDraw.
2. Создание первичного и
вторичного буферов. Буфера являются
поверхностями DirectDraw. Если мы собираемся работать
в полноэкранном режиме, то создаем так
называемую сложную переключаемую поверхность,
состоящую из двух одинаковых буферов. Для работы
в окне создается два отдельных буфера: первичный,
используемый совместно с GDI, и вторичный,
принадлежащий только приложению.
3. Определить, нужен ли
ограничитель, и если да, то создать его.
Ниже приводится функция
для установки режима:
BOOL CDirectDraw::_SetMode(HWND hWnd, int ex, int
cy,
int bpp, BOOL bFullScreen) t
ASSERT(m_pIDD) ;
// Освободить все
существующие буфера ReleaseAllO ;
288 вг Глава 12. Интерфейс
DirectDraw
// Задать уровень
кооперации if (bFullScreen) {
if (!SetCooperativeLevel(hWnd, DDSCL_EXCLOSIVE Ѓ
DDSCL_FULLSCREEN)) ( return FALSE;
}
m_hr = m_pIDD->SetDisplayMode (ex, cy, bpp) ;
if (FAILED(m_hr)) { return FALSE;
} m_bRestore = TRUE;
} else {
if (!SetCooperativeLevel(hWnd, DDSCL_NORMAL)) (
return FALSE;
} )
// Создать первичную и
вторичную поверхности
m_iWidth = ex;
m_iHeight = су;
DDSURFACEDESC sd;
inemset(&sd, 0, sizeof(sd));
sd.dwSize == sizeof(sd);
if (bFullScreen) {
// Создать сложную
переключаемую поверхность // с первичным и
вторичным буферами sd.dwFlags = DDSD_CAPS
I DDSD_BACKBUFFERCOUNT;
sd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE
I DDSCAPS_FLIP
I DDSCAPS_COMPLEX
I DDSCAPS_3DDEVICE;
sd.dwBackBufferCount = 1;
// Создать поверхности для
первичного и вторичного буфера
m_pFront Buffer = new CDDSurface/if (
!m_pFrontBuffer->Create (this, Ssd) ) { return FALSE;
} ASSERT(m_pFrontBuffer) ;
// Получить указатель на
присоединенный вторичный буфер DDSCAPS caps;
:::::^^ Работа с DirectDraw ч!И 289
memset(Scaps, 0, sizeof(caps));
caps.dwCaps = DDSCAPS_BACKBUFFER;
m_pBackBuffer =
m_pFrontBuffer->GetAttachedSurface(&caps) ;
if (!m_pBackBuffer) {
delete m_pFrontBuffer;
m_pFrontBuffer = NULL;
return FALSE;
}
) else { // Оконный режим
// Создать две поверхности
для оконного режима - // первичную, используемую
совместно с GDI, //и вторичный буфер для вывода
изображения.
// Создать поверхность
первичного буфера. // Примечание: поскольку
первичный буфер является // основной
(существующей) поверхностью, // мы не указываем
его ширину и высоту. sd.dwFlags = DDSD_CAPS;
sd.ddsCaps.dwCaps =
DDSCAPS_PRIMARYSURFACE;
m_pFrontBuffer = new CDDSurface;
if ( !m_pFrontBuffer->Create (this, &sd) ) {
return FALSE;
}
// Создать поверхность
вторичного буфера sd.dwFlags = DDSD_WIDTH
I DDSD_HEIGHT
I DDSD_CAPS;
sd.dwWidth = ex;
sd.dwHeight = cy;
sd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN
I DDSCAPS_3DDEVICE;
m_pBackBuffer = new CDDSurface;
if ( !m_pBackBuffer->Create(this, &sd)) (
delete m_pFrontBuffer;
m_pFrontBuffer = NULL;
return FALSE;
}
// Создать
объект-ограничитель для первичного буфера, //
чтобы вывод ограничивался пределами окна m_pClipper =
new CDDClipper;
if ( !m_pClipper->Create(this, hWnd) ) { return
FALSE;
} }
290 ^^' Глава 12. Интерфейс DirectDraw
Хотя многие функции в
данном фрагменте относятся к классам SdPlus, я не
стану приводить обращения к соответствующим
интерфейсам DirectDraw, поскольку в основном роль
функции сводится к простой передаче полученных
параметров инкапсулированному интерфейсу.
Приведенный фрагмент
содержит большое количество аспектов, которые я
не стану подробно объяснять. Интерфейсы DirectDraw
описаны в документации по DirectX SDK. Вы можете
самостоятельно провести ряд экспериментов, взяв
за основу данный код.
Тестирование
Для организации тестовых
циклов используется таймер. Тестирующая функция
вызывается из обработчика сообщений таймера:
void CTestWnd::OnTimer(UINT nIDEvent) {
// Выполнить следующий тест
switch (m_pTI->iTest) { case 1:
TestGDIText() ;
breaks-case 2:
TestGDIGfxf) ;
break;
case 3:
TestDDSprite() ;
break;
case 4:
TestDirectPixels() ;
break;
default:
ASSERT(0) ;
break;
}
Тестирование GDI при работе с
текстом
В этом тесте я решил
прибегнуть к услугам GDI для того, чтобы вывести в
окне небольшой текст. При этом мне хотелось
оценить, насколько медленно GDI будет справляться
с данной задачей и будет ли вывод текста влиять
на другие тесты. Результаты, полученные на моем
компьютере Dell, меня вполне устроили - при
выполнении теста в окне 320х200 скорость превышала
200 кадров в секунду. Все остальные тесты
построены на основе этого кода, поэтому мы
подробно рассмотрим его, шаг за шагом:
void CTestWnd::TestGDIText() (
ASSERT(m_pTI) ;
// Получить указатели на
первичный и вторичный буфера
Работа с DirectDraw ''TO 291
CDDSurface* pBB = m_pDD->GetBackBuffer () ;
ASSERT(pBB) ;
CDDSurface* pFB = m_pDD->GetFrontBuffer();
ASSERT(pFB) ;
// Получить прямоугольник,
описывающий первичный буфер RECT rcFront;
if (m_pTI->bFullScreen) {
pFB->GetRect (rcFront) ;
} else (
GetClientRect(SrcFront) ;
ClientToScreen(&rcFront) ;
}
RECT rcBack;
pBB->GetRect (rcBack) ;
DWORD dwStart = timeGetTime() ;
int nFrames = 100;
for (int iFrame = 0; iFrame <
nFrames; iFrame++) (
DWORD dwNow = timeGetTime();
double fps;
if (dwNow == dwStart) (
fps = 0;
} else {
fps = iFrame * 1000.0 / (double)(dwNow -
dwStart);
}
// Подготовить выводимый
текст char buf[64];
sprintffbuf, "Frame td (%3.1f fps)",
iFrame, fps);
// Получить DC для
вторичного буфера CDC* pdc = pBB->GetDC() ;
ASSERT(pdc) ;
// Заполнить буфер белым
цветом pdc->PatBlt (rcBack.left,
rcBack.top,
rcBack.right - rcBack.left,
rcBack.bottom - rcBack.top,
WHITENESS) ;
// Вывести текст pdc->DrawText
(buf,
-1,
srcBack,
DT_CENTER Ѓ DT_BOTTOM I DT_SINGLELINE) ;
292 вЃ1' Глава 12. Интеофейс
DirectDraw
// Освободить DC pBB->ReleaseDC(pdc)
;
// Переключить буфера
if (m_pTI->bFullScreen) { pFB->Flip() ;
} else (
pFB->Blt (SrcFront, pBB, SrcBack) ;
} } )
Тестирование начинается с
получения указателей на первичный и вторичный
буфера и подготовки прямоугольника,
описывающего их. Обратите внимание на то, что
размер прямоугольника первичного буфера зависит
от того, в каком режиме проводится тестирование -
в полноэкранном или оконном. В оконном режиме
поверхность первичного буфера используется
совместно с другими приложениями, работающими в
системе.
Каждый цикл тестирования в
функции TestGDIText состоит из следующих этапов:
1. Определить текущее время
и вычислить текущую скорость вывода.
2. Создать выводимый текст.
3. Получить контекст
устройства для вторичного буфера.
4. Стереть весь вторичный
буфер, заполняя его белым цветом с помощью
функции GDI.
5. Вызвать другую функцию GDI
для вывода текста во вторичный буфер.
6. Освободить контекст
устройства.
7. Переключить буфера и
вывести результат на экран.
После получения DC в
программе встречаются привычные вызовы функций
GDI, так что нетрудно забыть, что мы работаем с
поверхностью DirectDraw, а не с оконным DC.
Разумеется, суть
последнего этапа не всегда заключается в том, что
первичный буфер меняется местами со вторичным.
При работе в полноэкранном режиме функция
CDDSurface::Flip действительно меняет буфера местами,
однако в оконном режиме содержимое вторичного
буфера копируется в первичный функцией Bit. В
случае смены буферов не нужно беспокоиться о
новой роли каждого из них, так как за всем следит
DirectDraw, и когда мы требуем указатель на вторичный
буфер, то всегда получаем именно то, что нужно.
Производительность GDI при
выводе текста оказалась хорошей, и потому в
следующем тесте я решил определить, насколько
быстро GDI может рисовать на поверхности.
Тестирование GDI при работе с
графикой
Чтобы проверить
производительность GDI при работе с графикой, я
внес небольшие изменения в приведенную выше
функцию и заставил ее рисовать средствами GDI
прямоугольник, который перемещается внутри окна.
Для этого мне при-
Работа с DirectDraw ^Щ 293
шлось добавить в функцию
CTestWnd::TestGDIGfx приведенную ниже строку и в каждом
цикле отслеживать положение прямоугольника в
окне:
// Нарисовать
прямоугольник pdc->Rectangle (х, у, х+сх, у+су);
Вы можете самостоятельно
протестировать интерфейс GDI и посмотреть, как он
справляется с рисованием прямоугольников.
Тестирование DirectDraw при работе
со спрайтами
Этот тест значительно
интереснее предыдущих. Я хотел воспользоваться
поверхностью в видеопамяти для создания спрайта,
задать прозрачные области с помощью цветового
ключа и затем посмотреть, насколько быстро можно
будет двигать спрайт в окне. За его основу я взял
тест по выводу текста и добавил к нему два
дополнительных фрагмента. Первый из них создает
спрайт, а второй в цикле выводит спрайт во
вторичный буфер. В течение некоторого времени я
мучительно размышлял над тем, как же мне создать
спрайт, потом махнул рукой и воспользовался
функциями GDI:
void CTestWnd::TestDDSprite() {
DDSURFACEDESC sd;
memset(&sd, 0, sizeof(sd));
sd.dwSize = sizeof(sd);
sd.dwFlags = DDSD_WIDTH
I DDSD_HEIGHT
I DDSD_CAPS;
sd.dwWidth = ex;
sd.dwHeight = cy;
sd.ddsCaps.dwCaps = DDSCAPS_OFFSCREENPLAIN;
CDDSurface sprite;
BOOL b = sprite.Create(m_pDD, &sd) ;
ASSERT(b) ;
// Получить DC для
поверхности спрайта.
// Нарисовать
спрайт в виде красного круга на черном
фоне
CDC* pdc = sprite.GetDC();
ASSERT(pdc) ;
pdc->PatBlt(0, 0, ex, cy, BLACKNESS);
CBrush br;
br.CreateSolidBrush(RGB(255, 0, 0) ) ;
CBrush* pbrOld = pdc->SelectObject (Sbr) ;
pdc->Ellipse (0, 0, ex, cy) ;
pdc->SelectObject (pbrOld) ;
sprite.ReleaseDC(pdc) ;
// Задать черньй цвет в
качестве цветового ключа
294 Глава 12. Интерфейс DirectDraw
DDCOLORKEY ck;
ck.dwColorSpaceLowValue =0; // Черный
ck.dwColorSpaceHighValue = 0;
sprite.SetColorKey(DDCKEY_SRCBLT, &ck) ;
Я создал поверхность,
размер которой равен размеру спрайта (сх х су) и
затем воспользовался функциями GDI, чтобы
заполнить поверхность черным цветом и
нарисовать красный круг. Поскольку
цветовым-ключом поверхности задан черный цвет,
при выводе спрайта рисуется только красный круг.
Признаюсь, здесь я немного смошенничал и выбрал
черный цвет в качестве цветового ключа лишь
потому, что 0 соответствует черному цвету
независимо от того, как его рассматривать - как
RGB-значение или индекс палитры.
Создав простой спрайт, я
добавил в функцию следующий фрагмент, который
рисует спрайт во вторичном буфере:
// Закрасить буфер белым
цветом CDC* pdc = pBB->GetDC() ;
ASSERT(pdc) ;
pdc->PatBlt (rcBack. left,
rcBack.top,
rcBack.right - rcBack.left,
rcBack.bottom - rcBack.top, i
WHITENESS) ;
// Вывести текст pdc->DrawText (buf,
-1,
SrcBack,
DT_CENTER Ѓ DT_BOTTOM Ѓ DTJ3INGLELINE) ;
pBB->Relea5eDC (pdc) ;
// Вывести спрайт RECT rcDst;
rcDst.left = x;
rcDst.top = y;
rcDst.right = rcDst.left + ex;
rcDst.bottom = rcDst.top + cy;
RECT rcSrc;
rcSrc.left = 0;
rcSrc.top = 0;
rcSrc.right = rcSrc.left + ex;
rcSrc.bottom = rcSrc.top + cy;
// Вызвать Bit с
цветовым ключом pBB->Blt (SrcDst, Ssprite, SrcSrc, DDBLT WAITIDDBLT
KEYSRC) ;
Работа с DirectDraw
295
Перед тем как копировать
спрайт во вторичный буфер, мы с помощью функции GDI
закрашиваем буфер белым цветом и выводим строку
с количеством кадров в секунду. Обратите
внимание на то, что среди аргументов функции Bit
имеется флаг DDBLT_KEYSRC, который указывает ей на
необходимость использования цветового ключа
поверхности-источника. Для того чтобы прозрачные
области обрабатывались правильно, необходимо
задать цветовой ключ в поверхности-источнике и
указать этот флаг. Я провел пять или шесть часов в
недоумении, пока не догадался включить флаг
DDBLT_KEYSRC в вызов функции Bit. Что тут можно сказать?
Видимо, я соображаю недостаточно быстро.
Поскольку в окне этого
приложения нет ничего, кроме красного круга, вам
придется самостоятельно запустить тест и
посмотреть на скорость работы. Думаю, она
произведет на вас впечатление.
Тестирование прямого доступа
к пикселям
Я едва не отказался от
этого теста. Когда в течение целого дня мой
компьютер <зависал> через каждые пять минут, я
возненавидел DirectDraw и решил, что в дальнейшем буду
работать с графикой только через GDI. Все это время
я пытался заблокировать буфер, чтобы получить
возможность писать в него. Вместе с буфером
почему-то блокировался и компьютер, и мне
приходилось перегружаться. Намучившись, я лег
спать, и в лучших нердовских традициях решение
пришло во сне. Чтобы моя программа заработала, из
нее нужно было убрать всего один символ. Привожу
старую и новую версию функции CDDSurface::Unlock из файла
SdDirDraw.cpp, которая причинила мне столько огорчений:
void CDDSurface::Unlock() (
if (!m_SurfDesc.IpSurface) return; //
Поверхность
// не заблокирована
m_hr = m_pISurf->Unlock(&m_Surf Desc.
IpSurface) ;
ASSERT(SUCCEEDED(m_hr)) ;
m_SurfDesc.IpSurface = NULL;
}
void CDDSurface::Unlock () (
if (!m_SurfDesc.IpSurface) return; //
Поверхность
// не заблокирована
m_hr != m_pISurf->Unlock(m
SurfDesc.IpSurface);
ASSERT(SUCCEEDED(m_hr)) ;
m_SurfDesc.IpSurface == NULL;
}
Удалось ли вам найти
отличие? Эти указатели так похожи друг на друга -
до тех пор, пока вы не попытаетесь их
использовать!
Хорошо запомнив
полученный урок, я дописал код для тестирования
прямого доступа к пикселям. Он стирает
содержимое вторичного буфера, выводит в него
29А SSSi' Глава 17 Hu-rorvheui-
Diror-tnraiiu
строку с количеством
кадров в секунду и рисует горизонтальные цветные
линии посредством прямого доступа к пикселям.
Перед тем как что-либо
рисовать в буфере, необходимо выполнить два
условия. Прежде всего следует заблокировать
буфер и получить указатель на связанную с ним
область памяти. Затем нужно проверить формат
поверхности и определить способ записи
отдельных пикселей. Вероятно, в реальном
приложении можно выбрать один формат буфера и
пользоваться только им, но в нашем тесте формат
проверяется каждый раз заново.
После того как мы будем
знать количество бит на пиксель и маски для
красной, зеленой и синей составляющих (или
индексы палитры), можно приступать к рисованию.
Чтобы определить смещение в буфере, по которому
нужно записать пиксель, умножьте ширину буфера
на номер строки. Ширина буфера измеряется в
байтах. Она может отличаться от количества
пикселей, умноженного на количество байт на
пиксель, потому что строки часто дополняются по
границе ближайших 4 байтов (32-разрядное
выравнивание).
Приведенный ниже фрагмент
определяет формат пикселей для вторичного
буфера:
void CTestWnd::TestDirectPixels() (
// Получить информацию о
вторичном буфере
int iBpp = pBB->GetBitsPerPixel () ;
ASSERT(iBpp >= 8);
int iWidth - pBB->GetWidth() ;
int iHeight = pBB->GetHeight () ;
int iPitch = pBB->GetPitch() ;
// Получить RGB-маски DWORD dwRMask,
dwGMask, dwBMask;
pBB->GetRGBMasks (dwRMask, dwGMask, dwBMask) ;
// Для каждой маски
определить ширину в битах // и количество бит, на
которые маска // смещена от конца младшего байта
DWORD dwRShift, dwGShift, dwBShift;
DWORD dwRBits, dwGBits dwBBits;
dwRShift = dwRBits = 0 dwGShift = dwGBits = 0
dwBShift = dwBBits = 0 if (iBpp > 8) {
DWORD d = dwRMask;
while ((d & 0х1) == 0) ( d = d >> 1;
dwRShift++;
)
while (d & 0х01) { d = d >> 1;
dwRBits++;
}
Работа с DirectDraw ж! 297
d = dwGMask;
while ( (d & 0х1) ==0) {
d = d >> 1;
dwGShift++;
}
while (d & 0х01) { d = d >>
1;
dwGBits++;
} d = dwBMask;
while ((d & 0х1) == 0) (
d = d >> 1;
dwBShift++;
}
while (d & 0х01) { d = d >>
1;
dwBBits++;
}
i
Обратите внимание на то,
что цветовые маски нужны лишь в том случае, когда
пиксели поверхности кодируются более чем 8
битами. Кроме того, предполагается, что
поверхность имеет формат RGB, а не YUV или
какой-нибудь другой. В случае 8-битной кодировки
пикселей необходимо создать палитру:
// Если буфер имеет 8-битную
кодировку пикселей, // получить элементы палитры
и присвоить им // нужные цветовые значения PALETTEENTRY
ре [256];
BYTE r, g, b;
CDDPalette* pPal = pBB->GetPalette () ;
if (pPal) {
pPal->GetEntries (0, 256, ре) ;
// Задать нужные
цветовые значения. // Мы воспользуемся моделью с
2-битной кодировкой // R, Си В-составляющих for (r = 0; r
< 4; г++) { for (g = 0; g < 4; g++) ( for (b = 0; b < 4; b++) {
int index =10+r*16+g*4+b;
pe[index].peRed = r * 85;
ре[index].peGreen = g * 85;
pe[index].peBlue = b * 85;
}
298 ЦУ Глава 12. Интерфейс
DirectDraw
// Заполнить оставшиеся
элементы палитры серым цветом, // чтобы их можно
было увидеть при отладке for (int i = 10 + 4*4*4; i < 246; i++) {
ре[i].peRed = 192;
pe[i].peGreen = 192;
pe[i].peBlue = 192;
}
// Обновить палитру
pPal->SetEntries (0, 256, ре) ;
// Удалить объект палитры delete
pPal;
}
Я заполнил свободные
элементы палитры серым цветом, чтобы проследить
за тем, как механизм визуализации распределяет
используемые цвета. Рисование линий в буфере
происходит следующим образом:
// Заблокировать буфер и
получить указатель.
// ВНИМАНИЕ: Не пытайтесь
включать пошаговое выполнение
// до вызова Unlock.
BYTE* pBuf = (BYTE*) pBB->Lock () ;
if (pBuf) (
for (int у = 0; у < iHeight; y++) (
// Определить смещение
начала строки int n = iwidth;
DWORD dwOffset = у * iPitch; //
В байтах
// Получить цвет int ir =
GetRValue(cIrLine);
int ig = GetGValue(clrLine);
int ib = GetBValue(cIrLine);
// Вывести пиксели
непосредственно в буфер
switch (iBpp) (
case 8: {
// Найти индекс для цвета
// в соответствии с
принятой нами моделью int index = 10 + (ir / 85) * 16 + (ig /85) * 4 +
(ib / 85) ;
BYTE* p = pBuf + dwOffset;
while (n-) {
*p++ = (BYTE) index;
Работа с DirectDraw ^Щ 299
) breaks-case 16: (
// Построить цветовое
значение
DWORD dw = (ir >> (8 - dwRBits)) <<
dwRShift
I (ig >> (8 - dwGBits)) << dwGShift
I (ib >> (8 - dwBBits)) << dwBShift;
WORD w = (WORD)dw;
WORD* p = (WORD*)(pBuf + dwOffset);
while (n-) *p++ = w;
) breaks-case 24:
// Упражнение для
самостоятельной работы:
breaks-case 32: {
DWORD dw = (ir >> (8 - dwRBits)) << dwRShift
I (ig >> (8 - dwGBits)) << dwGShift
I (ib >> (8 - dwBBits)) << dwBShift;
DWORD* p = (DWORD*)(pBuf + dwOffset);
while (n-) *p++ = dw;
) breaks-default:
break;
i
// Перейти к следующему
цвету NextColor(clrLine) ;
} pBB->Unlock() ;
// Снова можно работать с
отладчиком
> NextColor(cIrStart) ;
Программа несколько
отличается для разных цветовых кодировок,
поскольку пиксели занимают разное количество
байт и для них требуются различные RGB-маски.
Пожалуйста, соблюдайте осторожность при
выполнении арифметических операций с
указателями. Вы можете перепутать указатели на
байт с указа-
300 ЩЦ' Глава 12. Интерфейс
DirectDraw
телями на DWORD и получить
неверный результат, поскольку компилятор
прибавит 2 или 4 вместо 1 или наоборот.
Чтобы тест генерировал
другой набор линий, следует внести изменения в
функцию NextColor, которая определяет цвет для вывода
следующей линии.
Наверняка вы обратили
внимание, что я пропустил код для 24-битной
кодировки. Мой видеоадаптер работает только с 8-,
16-и 32-битными цветами, поэтому я не мог
протестировать 24-битный код. Результаты данного
теста приведены на цветной вкладке.
Веселье продолжается
Разработка приложений для
DirectDraw в моем представлении не является
утомительным и занудньм кодированием. Скорее это
сплошное развлечение. Хотелось бы видеть, как вы
примените на практике продемонстрированные мной
методы.
Если после этой главы вам
нестерпимо захотелось написать свой собственный
механизм визуализации, продолжайте читать. В
следующей главе рассмотрена прослойка DirectSD -
перед тем, как нажимать клавиши, стоит
познакомиться с ней поближе.
Далее... |