"Графика для Windows средствами DirectDraw: Библиотека программиста", С-П., "Питер", 1998
В книге рассматривается DirectX 3 и 5(optional) версий, поэтому не удивляйтесь, что некоторых вещей здесь нет.
Полностью рассмотреть в одной главе всю библиотеку DirectDraw было бы нереально. В конце концов, даже о простейших аспектах DirectDraw написаны целые книги. DirectDraw представляет собой мощный и гибкий API, с помощью которого можно создать практически любое графическое приложение Windows. Именно гибкость существенно усложняет любые описания. Следовательно, было бы глупо пытаться рассмотреть все, от начала до конца, в одной главе. И все же я решил попробовать.
Позвольте мне для начала рассказать о том, чего в этой главе не будет. Несомненно, вам уже приходилось слышать о DirectDraw. Наверняка вы видели демонстрационные программы и игры, написанные на базе этой библиотеки. Я избавлю вас от длинной тирады о светлом будущем графики в Windows. Хорошо написанное приложение DirectDraw говорит само за себя, поэтому мы обойдемся без охов и ахов.
Кроме того, я пропускаю многословные рассуждения о HAL (Hardware Abstraction Layer, прослойка абстрактной аппаратуры), HEL (Hardware Emulation Layer, прослойка эмуляции аппаратуры) и все кошмарные диаграммы, которые встречаются в справочных файлах SDK и некоторых книгах по DirectDraw. Вы читаете эту книгу, чтобы освоить программирование для DirectDraw, а не потому, что собираетесь писать драйверы устройств DirectDraw или изучать тонкости внутреннего устройства библиотеки.
В этой главе мы поговорим о практическом применении DirectDraw с точки зрения программиста. Прежде всего мы разберемся с тем, что же такое DirectDraw, и перейдем к обсуждению DirectDraw API. После этого будут рассмотрены некоторые практические вопросы, несомненно представляющие интерес при программировании для DirectDraw.
Приняв к сведению все эти описания DirectDraw, давайте познакомимся с некоторыми терминами и концепциями, составляющими неотъемлемую часть используемого жаргона. Мы начнем с простейших, но основных понятий, которые относятся к графике вообще, а затем перейдем к специфике DirectDraw.
Весьма интересное определение DirectDraw можно найти у одного из его самых яростных противников — FastGraph. Графический пакет FastGraph появился уже довольно давно. В настоящее время существует версия FastGraph, которая поддерживает DirectDraw, но скрывает DirectDraw API за своим собственным нестандартным API. Тед и Диана Грубер (Ted and Diana Gruber), создатели и поставщики FastGraph, разместили на своем Web-узле файл, в котором доказывается, что FastGraph лучше DirectDraw.
В числе прочих доводов Груберы заявляют, что DirectDraw представляет собой “просто механизм блиттинга”. Такая формулировка оказывается довольно точной, но чрезмерно упрощенной. Правильнее было бы сказать, что DirectDraw — аппа-ратно-независимый механизм блиттинга, наделенный некоторыми возможностями программной эмуляции. Главная задача DirectDraw как раз и заключается в том, чтобы по возможности быстро и надежно копировать графические изображения в память видеоустройств (блиттинг).
DirectDraw можно рассматривать и с другой точки зрения — как менеджер видеопамяти. DirectDraw распределяет блоки памяти и следит за состоянием каждого блока. Программы могут по своему усмотрению создавать, копировать, изменять и уничтожать такие блоки, причем конкретные подробности этих операций остаются скрытыми от программиста. Такое описание тоже оказывается излишне упрощенным. Во-первых, DirectDraw может использовать не только видеопамять, но и обычную память (RAM). Кроме того, при проектировании менеджеров памяти основное внимание обычно уделяется надежности, а не быстродействию. При проектировании же DirectDraw главной целью было именно быстродействие.
С технической точки зрения DirectDraw представляет собой переносимый API в сочетании с набором драйверов устройств. В своей работе DirectDraw полностью обходит традиционный графический механизм Windows (интерфейс графических устройств, GDI). GDI завоевал дурную славу своим низким быстродействием, поэтому независимость от него крайне важна для достижения оптимальной скорости.
После рассмотрения всех интерфейсов и функций DirectDraw мы переходим к структурам данных. Всего в DirectDraw определено восемь структур:
DDBLTFX
DDCAPS
DDOVERLAYFX
DDPIXELFORMAT
DDSURFACEDESC
DDSCAPS
DDBLTBATCH
DDCOLORKEY
С некоторыми из этих структур мы уже встречались. Например, структура DDCOLORKEY упоминалась при обсуждении функции SetColorKey() интерфейса DirectDrawSurface. В настоящем разделе мы не станем детально рассматривать каждую структуру, а вместо этого разберемся с одной особенностью DirectDraw, которая способна причинить немало бед, если о ней забыть.
Пять (точнее, первые пять) из восьми перечисленных структур содержат поле с именем dwSize, в котором хранится размер структуры. Присвоение значения этому полю лежит на вашей ответственности. Более того, все функции DirectDraw, получающие эти структуры в качестве аргументов, не смогут работать, если полю dwSize не будет присвоено правильное значение.
Например, фрагмент для работы со структурой DDSURFACEDESC может выглядеть так:
DDSURFACEDESC surfdesc;
surfdesc. dwSize = sizeof (surfdesc);
surf->GetSurfaceDesc( &surfdesc );
Сначала мы объявляем структуру, затем присваиваем полю dwSize значение, используя функцию sizeof(). После этого структура передается функции GetSurfaceDesc интерфейса DIrectDrawSurface. Если забыть присвоить значение полю dwSize, вызов функции закончится неудачей.
На первый взгляд это выглядит глупо. С какой радости DirectDraw настаивает на передаче размера структуры, в ней же и определенной? Причина, по которой эти пять структур содержат поле dwSize, состоит в том, что в будущем они могут измениться. DirectDraw будет проверять размер структуры и по нему определять ее версию. Сейчас DirectDraw требует передачи правильного размера, чтобы приучить к этому разработчиков. Позднее это окупится, поскольку дальнейшие версии DirectDraw смогут корректно работать со старыми программами DirectDraw.
Раз уж речь зашла о структурах, следует упомянуть, что перед использованием структур желательно заполнять их нулями. В этом случае предыдущий фрагмент будет выглядеть так:
DDSURFACEDESC surfdesc;
ZeroMemory( &surfdesc, sizeof(surfdesc) );
surfdesc. dwSize = sizeof(surfdesc);
surf->GetSurfaceDesc( &surfdesc );
Функция Win32 ZeroMemory() заполняет нулями область памяти, начало которой передается в качестве первого аргумента. Второй аргумент функции определяет размер инициализируемой области. Преимущество такого подхода состоит в том, что теперь можно выяснить, какие поля структуры изменились в результате вызова функции GetSurfaceDesc(). Если не инициализировать структуру, случайные значения в ее полях можно принять за величины, занесенные в нее DirectDraw.
Библиотека DirectDraw реализована в соответствии со спецификацией СОМ (многокомпонентная модель объекта, Component Object Model) фирмы Microsoft. Спецификация СОМ предназначена для создания стопроцентно переносимых программных компонентов, наделенных возможностью безопасного обновления. О СОМ можно рассказать довольно много, но эта книга посвящена другой теме. Мы рассмотрим СОМ лишь в объеме, необходимом для использования DirectDraw.
В СОМ используется объектно-ориентированная модель, более жесткая, чем модели, принятые в языках типа C++. Так, доступ к СОМ-объектам всегда осуществляется с помощью функций. СОМ-объекты не могут иметь открытых переменных. Кроме того, наследование в СОМ выглядит ограниченным по сравнению с C++.
В СОМ четко разграничены понятия объектов и интерфейсов. СОМ-объекты обеспечивают настоящую функциональность, тогда как СОМ-интерфейсы предоставляют способы для работы с ней. Обращения к СОМ-объектам никогда не осуществляются напрямую, а только через интерфейсы. Это правило соблюдается так строго, что мы даже не знаем имен СОМ-объектов. Известны лишь имена интерфейсов, используемых для работы с объектами. Поскольку прямое обращение к СОМ-объектам невозможно, в дальнейшем речь пойдет в основном об интерфейсах.
СОМ-объект может поддерживать сразу несколько интерфейсов. На первый взгляд это может показаться странным, но все объясняется тем, что в соответствии со спецификацией СОМ-интерфейс после своего определения не может быть изменен или дополнен. Это было сделано для того, чтобы не нарушать работу старых программ при обновлении СОМ-объекта. Исходный интерфейс остается неизменным, а для работы с новыми функциональными возможностями объекта добавляется новый альтернативный интерфейс.
Все СОМ-интерфейсы являются производными от интерфейса IUnknown. Префикс I (от слова interface, то есть интерфейс) является стандартным для имен СОМ-интерфейсов. Имена всех интерфейсов DirectDraw начинаются с I, однако в документации обычно приводятся без префикса. В этой книге при упоминании СОМ-интерфейсов префикс I также будет опускаться.
Интерфейс lUnknown содержит три функции, наследуемые всеми СОМ-интерфей-сами.
AddRef()
Release()
Querylnterface
Функции AddRef и Release обеспечивают поддержку такого средства СОМ, как инкапсуляция времени существования (lifetime encapsulation). Она представляет собой протокол, согласно которому каждый объект сам отвечает за свое уничтожение.
Инкапсуляция времени существования реализована с помощью счетчика ссылок. Каждый объект содержит внутреннюю переменную, в которой отслеживается количество указателей или ссылок на него. В момент создания объекта счетчик равен 1. При создании дополнительных интерфейсов или указателей на интерфейсы значение счетчика увеличивается, а при уничтожении указателей на интерфейсы — уменьшается. Когда счетчик ссылок падает до нуля, объект уничтожает себя.
Функция AddRef служит для увеличения внутреннего счетчика ссылок объекта. В подавляющем большинстве случаев она вызывается самими функциями Direct Draw API. Например, при создании нового интерфейса функцией DirectDraw API создающая функция автоматически вызывает AddRef.
Функция Release уменьшает значение внутреннего счетчика ссылок. Ее следует применять при завершении работы с указателем или его выходе из области видимости. Обе функции, AddRef и Release, возвращают значение, равное новому состоянию счетчика ссылок объекта.
Функция QueryInterface() позволяет обратиться к СОМ-объекту с запросом о том, поддерживает ли он тот или иной интерфейс. Вспомните, например, что обновленные СОМ-объекты предоставляют дополнительные интерфейсы, не изменяя существующих. Если данный интерфейс не поддерживается запрашиваемым объектом, возвращается указатель на альтернативный интерфейс.
Чтобы обратиться к объекту с запросом о поддержке некоторого интерфейса, используя функцию QueryInterface, необходимо как-то идентифицировать этот интерфейс. Для этого используется значение GUID (глобально-уникального идентификатора, Globally Unique IDentifier) данного интерфейса. GUID представляет собой 128-битное значение, уникальное для всех практических целей. Значения GUID всех интерфейсов DirectDraw включены в заголовочные файлы DirectX.
Такого краткого введения в СОМ вполне достаточно для эффективной работы с DirectDraw API. Далее, по мере обсуждения DirectDraw API, вы поймете, насколько важна эта информация.
Один из способов оценить API — посмотреть на его размер. Большой, сложный API может быть результатом неудачного планирования. С другой стороны, большой API иногда свидетельствует и о том, что разработчики учли все возможные ситуации и позаботились о вас. Маленькие API нередко характерны для новых пакетов с ограничейными возможностями. С другой стороны, это может говорить и о том, что API делает только самое необходимое и ничего больше.
DirectDraw API невелик. В сущности, он настолько мал, что все его функции можно рассмотреть в одной главе (так мы и поступим), не превращая ее в справочное руководство. DirectDraw обладает некоторыми удобными средствами и подчиняется нескольким ограничениям.
Библиотека DirectDraw оформлена в виде четырех СОМ-объектов. Доступ к каждому объекту осуществляется через один или несколько интерфейсов. Вот их полный список:
DirectDraw
DirectDraw2
DirectDrawSurface
DirectDrawSurface2
DirectDrawSurface3
DilrectDrawPatette
DirectDrawClipper
Мы рассмотрим все интерфейсы вместе с входящими в них функциями. Тем не менее этот раздел не претендует на то, чтобы заменить собой справочное руководство. Help-файл, входящий в состав DirectX SDK, несмотря на все ограничения, содержит достаточно справочной информации, так что мы не станем подробно рассматривать все функции, а поговорим вместо этого о том, что делает каждая функция, для чего и с какой вероятностью она вам может понадобиться.
В первоначальном варианте библиотеки DirectX (еще в те времена, когда она называлась Game SDK) вся основная функциональность DirectDraw была сосредоточена в интерфейсе DirectDraw. Позднее, с выходом DirectX 2, рабочий интерфейс был усовершенствован. В соответствии со спецификацией СОМ интерфейс DirectDraw не изменился, а для работы с новыми возможностями использовался новый интерфейс DirectDraw2.
Следует заметить, что интерфейс DirectDraw2 представляет собой расширение DirectDraw. Он предоставляет все возможности интерфейса DirectDraw, а также ряд дополнительных. При работе с DirectX версий 2 и выше можно выбирать между интерфейсом DirectDraw и DirectDraw2. Поскольку DirectDraw2 делает все то же, что и DirectDraw, а также многое другое, вряд ли можно найти какие-то доводы в пользу работы с DirectDraw. Кроме того, Microsoft выступает против хаотичного, непоследовательного использования этих интерфейсов. По этим причинам во всех программах, приведенных в книге, будет использован интерфейс DirectDraw2.
Ниже перечислены все функции интерфейсов DirectDraw и DirectDraw2 (в алфавитном порядке):
Compact ()
CreateCIpper()
CreatePalette()
CreateSurface()
DuplicateSurface()
EnumDisplayModes()
EnumSurfaces()
FlipToGDISurface()
GetAvailableVidMem()
GetCaps()
GetDisplayMode()
GetFourCCCodes()
GetGDISurface()
GetMonitorFrequency()
GetScanLine()
GetVerticalBlankStatus()
RestoreDisplayMode()
SetCooperativeLevel()
SetDisplayMode()
WaitForVerticalBlank()
Далее рассмотрены функции интерфейса DirectDraw. Обратите внимание на то, что в оставшейся части этой главы термин интерфейс DirectDraw относится как к интерфейсу DirectDraw, так и к DirectDraw2. Уточнения будут приведены лишь в тех случаях, когда функция отличается в двух интерфейсах.
Интерфейс DirectDraw представляет саму библиотеку DirectDraw. Этот интерфейс является главным в том отношении, что в нем создаются экземпляры всех остальных интерфейсов DirectDraw. Интерфейс DirectDraw содержит три функции, предназначенные для создания экземпляров интерфейсов:
CreateClipper()
CreatePalette()
CreateSurface()
Функция CreateClipper создаетэкземплярыинтерфейса DirectDrawClipper. Объекты отсечения (clipper) используются не всеми приложениями DirectDraw, так что в некоторых программах эта функция может отсутствовать. Вскоре мы рассмотрим интерфейс DirectDrawClipper подробнее.
Функция CreatePalette создает экземпляры интерфейса DirectDrawPalette. Палитры, как и интерфейс DirectDrawClipper, используются не всеми приложениями DirectDraw. Например, приложению, работающему только с 16-битными видеорежимами, палитра не понадобится. Тем не менее приложение, работающее в 8-битном видеорежиме, должно создать хотя бы один экземпляр DirectDrawPalette.
Экземпляры интерфейса DirectDrawSurface создаются функцией CreateSurface. Поверхности обязательно присутствуют в любом приложении DirectDraw, работающем с графическими данными, поэтому данная функция используется очень часто.
Экземпляры самого интерфейса DirectDraw создаются функцией DirectDraw Create(), DirectDrawCreate() — одна из немногих самостоятельных функций DirectDraw, не принадлежащих никакому СОМ-интерфейсу.
Интерфейс DirectDraw позволяет точно узнать, какие возможности поддерживаются как на программном, так и на аппаратном уровне. Функция GetCaps() инициализирует два экземпляра структуры DDCAPS. Первая структура показывает, какие возможности поддерживаются непосредственно видеокартой, а вторая — что доступно посредством программной эмуляции. Функция GetCaps() помогает определить, поддерживаются ли нужные возможности.
DirectDraw автоматически использует аппаратную поддержку, если она имеется, и по умолчанию в случае необходимости переключается на программную эмуляцию. Неудачей заканчиваются вызовы лишь тех функций, которые не поддерживаются ни на аппаратном, ни на программном уровне.
DirectX Viewer
В DirectX SDK входит программа DXVIEW, которая сообщает о возможностях всех компонентов DirectX, в том числе и DirectDraw. На большинстве компьютеров информация о DirectDraw отображается в двух категориях: Primary Display Driver и Hardware Emulation Layer. Первая категория сообщает о возможностях аппаратных видеосредств. Во второй перечислены возможности, эмулируемые DirectDraw при отсутствии аппаратной поддержки. На компьютерах с двумя и более видеокартами, поддерживаемыми DirectDraw, DXVIEW выводит сведения о способностях каждой из них.
Функция SetCooperativeLevelО определяет уровень кооперации — степень контроля над видеокартой, необходимую для данного приложения. Например, нормальный (normal) уровень кооперации означает, что приложение не сможет изменить текущий видеорежим или задать содержимое всей системной палитры. Монопольный (exclusive) уровень допускает переключение видеорежимов и предоставляет приложению полный контроль над палитрой. Независимо от выбранного уровня вам необходимо вызвать SetCooperativeLevel().
Интерфейс DirectDraw содержит четыре функции для работы с видеорежимами:
EnumDisplayModes()
GetDisplayMode()
RestoreDisplayMode()
SetDisplayMode()
С помощью функции EnumDisplayModes() можно получить от DirectDraw список доступных видеорежимов. По умолчанию EnumDisplayModes() перечисляет все видеорежимы, но по описаниям можно исключить из списка режимы, не представляющие для вас интереса. Функция EnumDisplayModes() не обязана присутствовать в программе, однако это желательно, если вы собираетесь организовать переключение видеорежимов. На рынке существует огромное количество видеоустройств, каждое из которых обладает своими возможностями и ограничениями. Не стоит полагаться на автоматическую поддержку любого конкретного видеорежима, за исключением принятого по умолчанию в Windows режима 640х480х8.
Функция GetDisplayMode() получает сведения о текущем видеорежиме. Она заполняет структуру DDSURFACEDESC информацией о горизонтальном и вертикальном разрешениях, глубине и формате пикселей текущего видеорежима. Ту же информацию можно получить и другими способами (например, по описанию первичной поверхности), поэтому эта функция встречается не во всех программах.
Функция SetDisplayMode() активизирует заданный видеорежим. Версия SetDisplay Mode() из интерфейса DirectDraw2 позволяет дополнительно задать частоту смены кадров. Этим она отличается от функции из интерфейса DirectDraw, в которой можно задать только горизонтальное и вертикальное разрешения и глубину пикселей. Функция SetDisp1ayMode() присутствует в любой программе, осуществляющей переключение видеорежимов.
Функция RestoreDisplayMode() восстанавливает видеорежим, действовавший до вызова SetDisplayMode(). Перед вызовом функций SetDisplayMode() и RestoreDisplayMode() необходимо предварительно установить монопольный уровень кооперации вызовом функции SetCooperativeLevel ().
Помимо функции CreateSurface() интерфейс DirectDraw содержит следующие функции для работы с поверхностями:
DuplicateSurface()
EnumSurfaces()
FlipToGDISurface()
GetGDISurface()
GetAvailableVidMem()
Compact()
Функция DuplicateSurface() создает копию существующей поверхности. Она копирует только интерфейс поверхности, но не ее содержимое. Копия поверхности использует ту же область памяти, поэтому модификация содержимого памяти приведет к изменению изображения, представленного обеими поверхностями.
Функция EnumSurfaces() используется для перебора всех поверхностей, удовлетворяющих заданному критерию. Если критерий не указан, составляется список всех существующих поверхностей.
Функция FlipToGDISurface() используется перед завершением приложения, осуществляющего переключение страниц, чтобы обеспечить правильное восстановление первичной поверхности. Вспомните о том, что при переключении страниц происходит попеременное отображение двух поверхностей. Это означает, что приложение может завершиться, не восстановив исходной поверхности, отображаемой на экране. Если это произойдет, Windows будет осуществлять вывод на невидимую поверхность. Такой ситуации можно легко избежать с помощью функции FlipToGDISurface().
Функция GetGDISurface возвращает указатель на единственную поверхность, с которой работает GDI. Весь графический вывод Windows осуществляется именно на поверхность GDI. Примером ситуации, когда эта функция может оказаться полезной, является программа копирования экрана, в которой DirectDraw используется для копирования произвольной части рабочего стола.
Функция GetAvailableVidMem() возвращает объем текущей доступной видеопамяти. Эта функция присутствует в интерфейсе DirectDraw2, но отсутствует в DirectDraw. С ее помощью приложение может определить, сколько поверхностей ваше приложение сможет создать в видеопамяти.
Функция Compact() не реализована в DirectX, однако в будущем она обеспечит механизм дефрагментации видеопамяти. Если ваше приложение постоянно создает и уничтожает поверхности, находящиеся в видеопамяти, дефрагментация может высвободить немало места.
Интерфейс DirectDraw содержит четыре функции, относящихся не к видеокарте, а к устройству отображения (монитору):
GetMonitorFrequency()
GetScanLine()
GetVerticalBlankStatus()
WaitForVerticalBlank()
Говоря конкретно, эти функции относятся к механизму смены кадров на мониторе. Сих помощью можно реализовать анимации с минимальным мерцанием и задержками. Тем не менее следует учесть, что эти функции поддерживаются не всеми комбинациями видеокарт и мониторов.
Функция GetMonitorFrequency() возвращает текущую частоту смены кадров монитора. Эта частота обычно измеряется в герцах (Гц). Например, частота в 60 Гц означает, что состояние экрана обновляется 60 раз в секунду.
Функция GetScanLine() возвращает номер строки развертки (горизонтального ряда пикселей), обновляемой в данный момент. Она не поддерживается некоторыми комбинациями видеокарт и мониторов. Если данная способность не поддерживается, функция возвращает код ошибки DDERR_UNSUPPORTED.
В высокопроизводительных графических приложениях обновление экрана обычно синхронизируется с процессом вертикальной развертки. Другими словами, первичную поверхность желательно обновлять в тот момент, когда монитор закончил очередное обновление экрана. В противном случае в одной части экрана будут отображаться новые данные, а в другой — старые. Подобный эффект называется расхождением (tearing). По умолчанию DirectDraw автоматически синхронизирует обновление экрана с завершением вертикальной развертки. В нестандартных ситуациях можно добиться синхронизации с помощью функций GetVerticalBlankStatus() и WaitForVerticalBlank().
Наш обзор интерфейса DirectDraw завершается функцией GetFourCCCodes(). Она возвращает коды FourCC, поддерживаемые видеокартой. Коды FourCC используются для описания YUV-поверхностей, не относящихся к стандарту RGB. Мы не будем рассматривать такие поверхности, так как они выходят за рамки этой книги.
Множественные интерфейсы DirectDrawSurface, как и интерфейсы DirectDraw, возникли из-за особенностей спецификации СОМ. В исходном варианте работа с поверхностями осуществлялась через интерфейс DirectDrawSurface. В DirectX 2 появились новые функциональные возможности, представленные интерфейсом DirectDrawSurface2, а в DirectX 5 возник интерфейс DirectDrawSurface3.
Хотя в этой книге вместо DirectDraw повсюду используется интерфейс DirectDraw2, для работы с поверхностями мы будем придерживаться исходного интерфейса DirectDrawSurface, потому что нововведения интерфейсов DirectDrawSurface2 и DirectDrawSurface3 не слишком важны. В оставшейся части книги термин интерфейс DirectDrawSurface будет обозначать все три интерфейса, если при этом не возникает двусмысленности.
Самый большой из всех интерфейсов DirectDraw, DirectDrawSurface, позволяет копировать и стирать содержимое поверхности, а также напрямую работать с ним из программы. В общей сложности он поддерживает 36 функций, перечисленных ниже (в алфавитном порядке):
AddAttachedSurface()
AddOverIayDirtyRect()
BIt()
BItBatch()
BItFast()
DeleteAttachedSurface()
EnumAttachedSurfaces()
EnumOverIayZOrdersFlip
GetAttachedSurface()
GetBltStatus()
GetCaps()
GetClipper()
GetColorKey()
GetDC()
GetDDInterface()
GetFlipStatus()
GetOverlayPosition()
GetPalette()
GetPixelFormat()
GetSurfaceDesc()
IsLost()
Lock()
PageLock()
PageUnlock()
ReleaseDC()
Restore()
SetClipper()
SetColorKey()
SetOverIayPosition()
SetPalette()
SetSurfaceDesc()
Unlock()
UpdateOverIay()
UpdateOverIayDisplay()
UpdateOverIayZOrder()
Мы начнем с четырех функций, с помощью которых можно получить информацию о самой поверхности:
GetCaps()
GetPixelFormat ()
GetSurfaceDesc()
SetSurfaceDesc()
Функция GetCaps() по аналогии с одноименной функцией интерфейса DirectDraw заполняет структуру информацией о том, какие возможности поддерживаются данной поверхностью. В частности, в нее заносятся сведения о том, является ли данная поверхность первичной или внеэкранной, и где она находится — в системной или видеопамяти.
Функция GetPixelFormat особенно важна при работе с поверхностями форматов High и True Color, поскольку формат пикселей может зависеть от видеокарты. Функция возвращает маски, которые определяют способ хранения отдельных цветовых составляющих.
Функция GetSurfaceDesc() возвращает описание поверхности. Сведения включают ширину и высоту поверхности, а также глубину пикселей. В описание поверхности также входит формат ее пикселей (в том же виде, что и получаемый с помощью функции GetPixelFormat()).
Функция SetSurfaceDesc() (появилась только в DirectX 5 и поддерживается только интерфейсом DirectDrawSurface3) позволяет задать значения некоторых атрибутов поверхности. Например, с ее помощью можно выбрать тип памяти, в которой должна находиться поверхность. Данная функция помогает реализовать нестандартную схему управления поверхностями.
Интерфейс DirectDrawSurface поддерживает три функции, предназначенные для выполнения блиттинга:
BIt()
BItBatch()
BItFast()
Функция Blt() выполняет всю основную работу. Она осуществляет классический блиттинг (простое копирование данных между поверхностями без применения специальных эффектов), а также поддерживает растяжение, повороты, зеркальные отображения и цветовые заливки. В случае применения Blt() для поверхностей, связанных с объектом отсечения, выполняется блиттинг с отсечением.
Функция BltBatch() не реализована в DirectX 5 (ее можно вызвать, но при этом ничего не произойдет). После реализации эта функция будет выполнять сразу несколько операций блиттинга, по возможности одновременно.
Функция BltFast() является оптимизированным вариантом функции Blt(). Повышение эффективности достигается за счет сокращения возможностей, поэтому BltFast() не умеет выполнять специальные операции блиттинга, поддерживаемые функцией Blt(). Кроме того, BltFast() не выполняет отсечения. Тем не менее функция BltFastO поддерживает блиттинг с использованием цветовых ключей источника и приемника. В сочетании с нестандартными алгоритмами отсечения функция BltFast() обеспечивает выполнение самого быстрого и универсального блиттинга, которого можно добиться от DirectDraw. Нестандартный алгоритм отсечения будет реализован в главе 3.
Все три блит-функции в качестве аргументов получают две поверхности — источник и приемник. Также передаются дополнительные данные, более детально описывающие операцию блиттинга (например, положение копируемого участка на поверхности-приемнике). Там, где это возможно, функция выполняет блиттинг с аппаратным ускорением.
Функция Flip() выполняет переключение страниц. При вызове F11p() поверхность, ранее отображавшаяся на экране, становится невидимой, а вместо нее отображается вторичный буфер. Функция Flip() действует лишь для поверхностей, созданных как переключаемые.
Необходимо учитывать, что настоящее (аппаратное) переключение страниц возможно не всегда. Для переключения страниц требуется видеопамять в объеме, достаточном для хранения двух полных экранов с данными. Если это требование не выполняется, вторичный буфер создается в системной памяти. В этом случае при вызове Flip() вместо переключения страниц выполняется блиттинг — содержимое вторичного буфера из видеопамяти копируется на первичную поверхность. Это ведет к заметному снижению быстродействия, но в тех случаях, когда видеопамяти не хватает для настоящего переключения страниц, ничего другого не остается (не считая аварийного завершения программы). Если ваше приложение требует максимального быстродействия, можно запретить активизацию видеорежимов, в которых не удается организовать настоящее переключение страниц.
Две следующие функции предназначены для получения информации о ходе операций блиттинга и переключения:
GetBltStatus()
GetFIipStatus()
С помощью функции GetBltStatusO можно определить, выполняется ли блиттинг в данный момент. Это может быть важно, поскольку копируемые поверхности не могут участвовать в других операциях. Функция показывает, занята ли данная поверхность в качестве источника или приемника блиттинга.
Аналогично, функция GetFlipStatus() показывает, происходит ли в данный момент переключение страниц. Для получения информации о переключении страниц, вызванном функцией Flip(), вместо GetBltStatus() следует пользоваться именно этой функцией даже в том случае, если DirectDraw имитирует переключение страниц посредством блиттинга.
Интерфейс DirectDraw содержит две функции для назначения и просмотра цветового ключа (или интервала цветовых ключей) поверхности:
GetColorKey()
SetColorKey()
По умолчанию поверхность не имеет цветового ключа. Чаще всего цветовой ключ представляет собой один цвет, однако на некоторых видеокартах возможна работа с интервалами цветовых ключей. Цветовые ключи и их интервалы описываются структурой DDCOLORKEY. Указатели на эту структуру передаются функциям GetColorKeyO и SetColorKeyO в качестве аргументов. Функции GetColorKeyO и SetColorKeyO используются при частичном копировании поверхностей, а также при выполнении операций с цветовыми ключами приемника.
Функции Lock() и Unlock()
Одна из важнейших особенностей DirectDraw — возможность прямого доступа к графическим данным. Прямой доступ обеспечивает максимум быстродействия и гибкости, поскольку он не замедляется использованием промежуточных API, a разработчик может делать с графическими данными все, что считает нужным. Для прямого доступа к памяти поверхности существуют две функции:
Unlock()
Lock()
Функция Lock() возвращает указатель на область памяти, занятую поверхностью, независимо от того, где поверхность находится — в системной памяти или в видеопамяти. Память поверхности всегда организована линейно, что позволяет максимально упростить обращение к графическим данным. Функция Unlock() сообщает DirectDraw о том, что прямой доступ к памяти поверхности завершен.
Впрочем, за эту мощную возможность приходится расплачиваться. Чтобы обеспечить прямой доступ, DirectDraw приходится на время блокировки поверхности отключать некоторые фундаментальные механизмы Windows. Если вы забудете разблокировать поверхность в Windows 95, компьютер наверняка “зависнет”.
По этой причине блокировка поверхностей должна применяться в течение как можно меньшего промежутка времени. Перед тем как приступать к тестированию, необходимо тщательно проверить фрагмент программы между вызовами функций Lock() и Unlock(), потому что его невозможно отладить традиционными средствами.
Для заблокированных поверхностей не выполняются операции блиттинга и переключения, так что хранение поверхности в заблокированном состоянии не даст вам никаких преимуществ. Указатель, полученный функцией Lock(), после разблокирования поверхности становится недействительным.
Заблокированную поверхность невозможно заблокировать снова. Попытка вызова функции Lock() для уже заблокированной поверхности закончится неудачей.
Прямой доступ к памяти поверхности — замечательная возможность, но иногда бывает удобнее рассматривать поверхность как обычное графическое устройство Windows. Для этой цели в интерфейсе DirectDrawSurface предусмотрены две функции:
GetDC()
ReleaseDC()
Функция GetDC() предоставляет в ваше распоряжение DC (контекст устройства, Device Context), через который можно осуществлять вывод на поверхность стандартными функциями Win32. Например, передавая его функции Win32 TextOut(), можно вывести на поверхность текст. Функция ReleaseDC() должна быть вызвана сразу же после завершения работы с DC.
Как и в случае с Lock() и Unlock(), функцию ReleaseDC() необходимо вызывать после GetDCO как можно быстрее. Это связано с тем, что внутри функции GetDC() вызывается Lock(), а внутри ReleaseDC () — Unlock().
Перейдем к двум функциям, внешне похожим на Lock() и Unlock():
PageLock()
PageUnlock()
Вероятно, имена этих функций были выбраны неудачно, потому что они предназначены совсем для других целей. С помощью PageLock() и PageUnlock() можно управлять тем, как Windows обходится с поверхностями в системной памяти. Для работы с ними используется интерфейс DirectDrawSurface2, в DirectDrawSurface они отсутствуют.
Обычно система Windows переносит содержимое памяти на диск, когда по ее мнению другое приложение или процесс в данный момент смогут лучше распорядиться памятью. Это относится ко всей системной памяти, поэтому поверхности DirectDraw, хранящиеся в ней, также могут переноситься на диск. Когда в такой поверхности возникнет необходимость, получение данных с диска будет сопровождаться ощутимой паузой.
Функция PageLock сообщает Windows о том, что данную поверхность нельзя переносить на диск. В этом случае поверхность всегда остается доступной и не требует долгих обращений к жесткому диску. Функция PageUnlock разрешает Windows переносить поверхность на диск.
Следует помнить, что частое использование PageLock приведет к замедлению работы Windows из-за сокращения общего объема переносимой памяти. Когда именно это произойдет, зависит от объема памяти, для которой запрещен перенос на диск, и общего объема системной памяти.
Функции PageLock и PageUnlock используются в первую очередь самой библиотекой DirectDraw, а не приложениями. Например, DirectDraw автоматически вызывает PageLock и PageUnlock, чтобы поверхности, находящиеся в системной памяти, не переносились на диск в ходе блиттинга.
Функцию PageLock можно вызывать для одной поверхности многократно. DirectDraw следит за количеством вызовов PageLock, используя механизм подсчета ссылок, поэтому для отмены многократных вызовов PageLock потребуется несколько вызовов PageUnlock.
Функции PageLock и PageUnlock не влияют на поверхности, находящиеся в видеопамяти.
Функции IsLost() и Restore()
Две следующие функции предназначены для работы с поверхностями в видеопамяти:
IsLost()
Restore()
Рассмотрим следующую ситуацию: ваше приложение начинает работу. Вы размещаете как можно больше поверхностей в видеопамяти, а все остальные — в системной памяти. В течение некоторого времени приложение работает, но затем пользователь запускает другое приложение или переключается на него. Другое приложение может быть чем угодно — скажем, стандартной Windows-программой (например, Windows Explorer или Notepad). Оно также может оказаться другим приложением, которое тоже работает с DirectDraw и стремится разместить как можно больше поверхностей в видеопамяти. Если DirectDraw откажется выделить новому приложению видеопамять, оно будет работать плохо (а то и вообще не будет). Возможна и обратная ситуация — видеопамять окажется недоступной для вашего приложения.
По этой причине DirectDraw может забрать у неактивного приложения видеопамять, занятую некоторыми (или всеми) поверхностями. Такие поверхности называются потерянными (lost). Вообще говоря, такие поверхности остаются у вашей программы, но они перестают быть связанными с какой-либо областью памяти. Любая попытка использовать потерянную поверхность приводит к ошибке DDERR_SURFACELOST. Функция IsLost позволяет узнать, была ли потеряна память данной поверхности.
Потерянную поверхность можно восстановить функцией Restore, но только после повторной активизации вашего приложения. Тем самым предотвращается восстановление поверхностей для приложений, находящихся в свернутом виде на панели задач.
При этом существует одна загвоздка. Функция Restore восстанавливает лишь память, закрепленную за поверхностью, но не ее содержимое. Следовательно, после восстановления поверхности ваше приложение само должно восстановить ее содержимое.
Обратите внимание: сказанное не относится к поверхностям, находящимся в системной памяти. Если память, занятая такими поверхностями, потребуется для других целей, Windows перенесет их на диск. Все эти действия Windows выполняет автоматически, включая восстановление содержимого поверхностей.
Функция GetDDInterface() возвращает указатель на интерфейс DirectDraw, использованный для создания заданной поверхности. Эта функция используется очень редко, поскольку ваши программы, вероятно, будут обходиться одним экземпляром интерфейса DirectDraw. Тем не менее в одном приложении разрешено иметь несколько интерфейсов DirectDraw. В этом случае функция GetDDInterface() может оказаться полезной.
Интерфейс DirectDrawSurface содержит четыре функции для управления взаимосвязанными поверхностями:
AddAttachedSurface()
DeleteAttachedSurface()
EnumAttachedSurface()
GetAttachedSurface()
В DirectDraw возможно несколько ситуаций, при которых поверхности могут присоединяться к другим поверхностям. Самая распространенная из них — переключение страниц. Чтобы переключение страниц стало возможным, необходимо циклически соединить две или несколько поверхностей. При каждом вызове Flip() на экране будет отображаться следующая поверхность из цепочки.
Перечисленные выше функции используются для создания, просмотра и уничтожения связей между поверхностями, однако в программах они встречаются довольно редко. Обычно DirectDraw создает за вас нужные поверхности вместе с взаимными связями. Например, при создании первичной переключаемой поверхности вы указываете количество присоединенных к ней вторичных буферов. DirectDraw создает все необходимые поверхности и должным образом присоединяет их друг к другу.
Поддержкаоверлеевв DirectDrawSurface представлена следующими функциями:
AddOverlayDirtyRect()
EnumOverIayZOrder()
GetOverIayPosition()
SetOverIayPosition()
UpdateOverIay()
UpdateOverIayDisplay()
UpdateOverIayZOrder()
Функции GetOverlayPosition и SetOverlayPosition управляютположениемоверлеев. Функция UpdateOverlay() изменяет параметры оверлея; в частности, она определяет, должен ли оверлей отображаться на экране и следует ли применять для него альфа-наложение или копирование с цветовым ключом.
Функция UpdateOverlayDisplay обновляет изображение с учетом новых значений параметров. Данная функция может обновить все изображение оверлея или ограничиться его прямоугольными областями, заданными функцией AddOverlayDirtyRect. Наконец, функция EnumOverlayZOrders используется для перебора оверлеев в порядкеих Z-координаты (Z-координата определяет, какие оверлеи выводятся поверх других). Возможен перебор как в прямом порядке (от передних оверлеев к задним), так и в обратном (от задних — к передним).
DirectDraw позволяет присоединить к поверхности экземпляр интерфейса DIrectDrawClipper (который мы еще не рассматривали). После того как такое присоединение состоится, операция блиттинга на данную поверхность будет регулироваться объектом отсечения. Для работы с объектами отсечения в интерфейсе DirectDrawSurface имеются две функции:
GetClipper()
SetClipper()
Функция SetClipper присоединяет объект отсечения к поверхности. Функция GetClipper возвращает указатель на присоединенный ранее объект отсечения. С помощью функции SetClipper можно разорвать связь между поверхностью и объектом отсечения, для этого в качестве указателя на интерфейс DirectDrawClipper следует задать NULL.
Палитры, как и объекты отсечения, можно присоединять к поверхностям. Для этой цели в интерфейсе DirectDrawSurface предусмотрены две функции:
GetPalette()
SetPalette()
Функция SetPalette присоединяет к поверхности экземпляр интерфейса DirectDrawPalette (о нем речь пойдет ниже). Функция GetPaletteO применяется для получения указателя на палитру, присоединенную ранее.
Палитру можно присоединить к любой поверхности, однако действовать она будет лишь в том случае, если поверхность является первичной. Палитра, присоединенная к первичной поверхности, управляет палитрой видеокарты.
Интерфейс DirectDrawPalette предназначен для работы с палитровыми видеорежимами и поверхностями. Несмотря на то что в Windows поддерживается ряд видеорежимов с глубиной пикселей менее 8 бит, DirectDraw поддерживает лишь 8-битные палитровые режимы.
Экземпляры интерфейса DirectDrawPalette создаются функцией CreatePalette интерфейса DirectDraw. Функция CreatePalette получает набор флагов, определяющих тип палитры.
Интерфейс DirectDrawPalette содержит всего три функции:
GetCaps()
GetEntries()
SetEntries()
Функция GetCaps определяет возможности палитры. В числе получаемых сведений — количество элементов палитры, поддержка палитрой вертикальной синхронизации и (в случае 8-битной палитры) возможность заполнения всех 256 элементов.
Для заполнения палитры используется функция SetEntries. Содержимое палитры чаще всего берется из файла. Тем не менее значения элементов палитры можно рассчитать и занести в палитру во время выполнения программы. Функция GetEntMes возвращает значения элементов, ранее занесенных в палитру.
Экземпляры интерфейса DirectDrawPalette присоединяются к поверхности функцией SetPalette интерфейса DirectDrawSurface. Палитровая анимация выполняется либо присоединением разных палитр к первичной поверхности, либо изменением содержимого палитры функцией SetEntries.
Интерфейс DirectDrawClipper предназначен для поддержки отсечения. Чтобы выполнить отсечение, следует присоединить объект отсечения к поверхности и использовать ее в качестве приемника блиттинга. Экземпляры интерфейса DirectDrawClipper создаются функцией CreateClipper интерфейса DirectDraw. Интерфейс DirectDrawClipper содержит следующие функции:
SetHWnd()
GetHWnd()
IsClipListChanged()
SetClipList()
GetClipList()
Объекты отсечения обычно используются для ограничения вывода, необходимого при работе приложений DirectDraw в окне. Объект отсечения гарантирует, что при выполнении блиттинга будет учитываться присутствие на рабочем столе других окон. Например, если окно приложения будет полностью или частично закрыто другим окном, объект отсечения позаботится о том, чтобы содержимое верхнего окна не было испорчено приложением DirectDraw.
Отсечение для рабочего стола активизируется функцией SetHWnd. Функция SetHWnd присоединяет объект отсечения к логическому номеру (handle) окна. В результате инициируется взаимодействие Windows с объектом отсечения. Объект отсечения получает уведомления обо всех изменениях окон на рабочем столе и действует соответствующим образом. Функция GetHWnd определяет, к какому логическому номеру окна присоединен заданный объект отсечения (и присоединен ли он вообще). Функция IsClipListChanged определяет, был ли внутренний список отсечений изменен вследствие изменений на рабочем столе.
Функции SetClipList и GetClipList упрощают нестандартное использование интерфейса DirectDrawClipper. Функция SetClipList определяет набор прямоугольныхобластей, для которых разрешено выполнение блиттинга. Функция GetClipList извлекает внутренние данные объекта отсечения.
После того как экземпляр DirectDrawClipper будет присоединен к поверхности, происходит автоматическое отсечение операций блиттинга, выполняемых функциями B1t(), BltBatch() и UpdateOverlay(). Обратите внимание на то, что в список не входит функция BltFast(). Для нее отсечение не поддерживается.
Дополнительные интерфейсы DirectDraw
Строго говоря, DirectDraw содержит еще три интерфейса, не рассмотренных нами:
DDVideoPortContainer
DirectDrawColorControl
DirectDrawVideoPort
Эти интерфейсы, появившиеся в DirectX 5, предназначены для низкоуровневого управления видеопортами. Точнее, они предоставляют средства для потоковой пересылки “живого видео” на поверхности DirectDraw. Хотя с их помощью можно организовать в приложениях DirectDraw поддержку работы с видео, это не рекомендуется, за исключением случаев, когда высокоуровневые видео-АРI не отвечают вашим потребностям. В книге эти интерфейсы не рассматриваются.
Для начала необходимо подключить к проекту библиотеку ddraw.libи заголовочный файл ddraw.h Для использования DirectDraw необходимо создать ссылку на объект библиотеки DirectDraw (одновременно могут работать несколько приложений использующих эту библиотеку, поэтому прямое создание и удаление объектов DirectX недопустимо).
DirectDraw работает с так называемыми поверхностями. Давайте разберемся, что понимают под этим термином разработчики из Майкрософт. Поверхность это некий кусочек виртуальной памяти операционной системы, обычно содержащий изображение. Поверхность обычно не привязана к конкретному адресному пространству и может как бы плавать в нем, в том числе поверхности могут находиться на диске в файле подкачки и даже фрагментироваться. Но драйвер делает так, что для программиста поверхность всегда представляет линейное пространство. Поверхности бывают нескольких типов. Первая и самая важная: первичная поверхность, за ней закреплена видеопамять или участок видеопамяти (доступ к которой мы и пытаемся получить). Второй тип внеэкранная поверхность она может находиться, как в видеопамяти (если размер оной позволяет, не будем забывать, что многие современные видеоадаптеры имеют размер памяти значительно больший, чем необходимо для представления типичного рабочего разрешения), так и в системной памяти. Как внеэкранные так и первичные поверхности могут быть палитровыми и беспалитровыми. Так как моя демка изначально нацелена на работу в беспалитровых режимах Hicolor, палитровые поверхности мы рассматривать не будем. За любой поверхностью могут быть закреплены вторичные буферы. Которые можно переключать, делая активным любой из подключенных буферов, таким образом можно, например, переключать страницы видеоадаптера.
Самое главное достоинство работы с первичной поверхностью заключается в ее линейности, специальный виртуальный драйвер берет всю работу по переключению банков памяти видеоадаптера на себя, предоставляя нам всю видеопамять как единый кусок памяти.
В демке я использовал только первичную поверхность, все остальные, в том числе видео-буфер я выделял "в ручную" фунцией new(). Это связанно с несколькими обстоятельствами. Во-первых у многих пользователей размер видеопамяти компьютера не привышает 1 мегабайта, и не справедливо было бы лешить их возможности, увидеть проектируемую игру. Во-вторых мои эксперименты с поверхностями показали, что на таком рядовом видеоадаптере как S3trio64V+ аппаратное ускорение практически отсутствует и необходимость в работе с поверхностями практически отпала. На мой взгляд, всегда лучше полностью контролировать работу программы самому, нежели отдовать часть работы другим разработчикам, т.к. намного легче чего-то изменить.Впрочем, в следующих своих поделках (имется ввиду движок игрухи) я буду использовать вторичный буфер прикрепленный к первичной поверхности, проще говоря странички всеже быстрее будут :) (поправка от 6.10.99 скорее все же буфер)
// Итак мы описываем два объекта:
LPDIRECTDRAW lpDD=NULL; // DirectDraw объект
LPDIRECTDRAWSURFACE lpFront=NULL; //DDraw первичнаяповерхность
Типы LPDIRECTDRAW и LPDIRECTDRAWSURFACE описаны в подключенном заголовочном файле ddraw.h Далее нам необходимо написать функцию, которая создаст объект DirectDraw и установит нужный режим работы. До вызова этой функции мы должны проделать такие необходимые, при программировании под Windows, вещи как регистрация класса окна и создание главного окна приложения, после чего в нашем распоряжении будет необходимая переменная- дескриптор окна HWND. Так как мы не собираемся создавать вторичные поверхности, функция будет очень простой.
Необходимо заметить, что обмен параметрами с функциями DirectX часто идет через заполнение структур, которых в DirectX просто ужасающее количество.
// Создает главный объект DD , устанавливает режим работы
BOOL ddInit(HWND hwnd, HINSTANCE hInst)
{
HRESULT result; // возвращаемое значение
DDSURFACEDESC ddsd; //структура, описывающая поверхность
Создаем объект DirectDraw, используя специально предназначенную для этого функцию, в качестве одного из параметров передадим указатель на объект DirectDraw.
result = DirectDrawCreate( NULL, &lpDD, NULL );
if( result!=DD_OK ) goto dderror;
Если все нормально то... Получим эксклюзивные права доступа к экрану и полноэкранный режим. Обращение к этой функции уже идет не непосредственно, а через указатель на объект DirectDraw (очевидно в этом и проявляется объектная модель DirectX). Параметры функции - дескриптор окна, и флаги привилегий.
result = lpDD->SetCooperativeLevel( hwnd, DDSCL_EXCLUSIVE |
DDSCL_FULLSCREEN );
if( result!=DD_OK ) goto dderror;
Если все нормально то продолжим работу. И установим нужный нам видео режим. У меня его задают соответствующие константы ширина, высота, глубина цвета. Вызов функции опять же через объект DirectDraw. Замечание: здесь приводится описание интерфейса DirectDraw, а существует еще DirectDraw2, и т.д. DirectDraw2, например позволяет переключать не только разрешение, но и частоту вертикального развертки монитора.
result = lpDD->SetDisplayMode( SCREEN_WIDTH, SCREEN_HEIGHT,
SCREEN_DEPTH);
if( result!=DD_OK ) goto dderror;
Если все пока нормально то… Создадим первичную поверхность с одним буфером. Для этого заполним структуру работы с поверхностью. Вначале всегда необходимо обнулить содержимое структуры. Кстати размер структур в windows вообще не постоянен, поэтому рекомендуется использовать метод sizeof для определения занимаемого структурой места.
memset(&ddsd,0,sizeof( ddsd ));
ddsd.dwSize = sizeof( ddsd );
ddsd.dwFlags = DDSD_CAPS;
// в структуре используются другие структуры,
// об изменении которых необходимо проинформировать вызываемую
// функцию.
ddsd.ddsCaps.dwCaps = DDSCAPS_PRIMARYSURFACE;
// указали, что это первичная поверхность
// И теперь вызываем функцию для создания поверхности.
result = lpDD->CreateSurface( &ddsd, &lpFront, NULL );
if( result!=DD_OK ) goto dderror;
//Теперь все необходимое проделали и можно выходить из функции.
return TRUE;
//Если произошла ошибка, проинформируем об этом пользователя
// и выйдем.
dderror:
MessageBox( hwnd, "Direct Draw Init Failed", "ERROR", MB_OK );
ddRelease();
return FALSE;
}
Нужно еще описать функцию освобождения всех созданных объектов, для корректного выхода из программы. Эта функция обычно вызывается при уничтожении главного окна приложения.
// освобождает объекты DirectDraw1 и DirectSurface
void ddRelease()
{
if( lpDD != NULL )
{
if( lpFront != NULL )
{
lpFront>Release(); lpFront = NULL;
}
lpDD>Release(); lpDD = NULL;
}
}
Удаление функцией delete не допустимо, т.к. нельзя забывать, что мы работаем в многозадачной среде, и возможно, что другое приложение тоже использует DirectX. Функция Release просто уменьшает счетчик созданных объектов и при достижении им нуля, объект сам удаляется из памяти.
Все это хорошо, но пока совсем не понятно как работать с поверхностью, которая может плавать по памяти и вообще находиться непонятно где. Для решения этой проблемы придумали специальный метод объекта поверхность, который назвали блокировкой поверхности. Вмести с ним, используется обратный метод- разблокировка, позволяющий поверхности свободно плавать. Можно заблокировать отдельную часть поверхности, указав ее в качестве одного из параметров как прямоугольную область. В литературе метод блокировки используется в основном во время вызова внутренних функций DirectDraw для работы с поверхностью. Я же его использую с единственной целью получить адрес битовой карты поверхности (собстенно мы подошли к тому моменту из-за чего все затевалось).
В качестве примера приведу исходник функции, копирующей картинку в формате .spr или .pct (мои собственные форматы, созданные для удобства) на первичную поверхность или любую другую поверхность.
Функция принимает следующие параметры: lps-указатель на поверхность, rect-рамка по которой необходимо обрезать выводимую картинку, psrc -объект картинка, X,Y-координаты вывода картинки относительно верхнего - левого угла поверхности, dark и light - коэффициенты затемнения и осветления картинки.
void picPutS(LPDIRECTDRAWSURFACE lps, RECT& rect, PIC& psrc,
long X, long Y, long dark, long light)
{
if(psrc.data==NULL) return; // обычная проверка
DDSURFACEDESC ddsd; // структура для работы с поверхностью
HRESULT result; // возвращаемый результат
memset(&ddsd,0,sizeof(ddsd)); // обнулим и заполним структуру
ddsd.dwSize=sizeof(ddsd);
заблокируем поверхность в памяти, заметим, что Lock является методом объекта поверхность, в качестве флага показывающего как реагировать в случаи невозможности блокировки, укажем, что нужно ждать, пока поверхность не заблокируется. Любая поверхность- это общий ресурс операционной системы и может быть она уже используется другим потоком или например была утеряна приложением, но об этом позже.
result=lps->Lock(NULL, &ddsd, DDLOCK_WAIT,NULL);
Если все нормально, то функция Lock помимо блокировки поверхностти заполнила структуру ddsd конкретными характеристиками поверхности.
if(result==DD_OK)
{
далее я создаю структуру, которая повторяет заголовок картинок в моем формате. Я как будто подменяю поверхность на картинку.
// ddsd.dwWidth -ширина поверхности
// ddsd.dwHeight- высота поверхности
// ddsd.lpSurface- адрес битового массива поверхности
PIC desc;
desc.type=PIC_pct | PIC_16;
desc.width=ddsd.dwWidth;
desc.height=ddsd.dwHeight;
desc.data=(char*)ddsd.lpSurface;
Далее просто вызывается обычная функция для копирования изображения, которая ничего не подозревает о существовании DirectX вообще и DirectDraw в частности.
picPut(desc,rect,psrc,X,Y,dark,light);
// Остается только разблокировать поверхность.
lps->Unlock(0);
}
}
Критическое замечание:
Разблокировать поверхность необходимо всегда, иначе это может привести к непредсказуемым последствиям, вплоть до останова работы Windows. Вообще в документации к DirectX говорится, что при блокировке поверхности операционная система приостанавливает свою работу. Теперь о потере поверхности. Потерять поверхность, находящеюся в видеопамяти, DirectDraw может в случаи переключения разрешения экрана другим приложением. При этом необходимо будет восстановить поверхность, но, к сожалению, все содержимое поверхности будет утеряно. Пример потери внеэкранной поверхности, находящейся в системной памяти привести сложнее, этот случай может произойти только при какой-нибудь нештатной ситуации.
Пример проверки на потерю поверхности:
double2:
result=lpFront->IsLost();
if(result==DDERR_SURFACELOST)
{
lpFront->Restore(); goto double2;
}